[core] Move arduino/ files to cores/

This commit is contained in:
Kuba Szczodrzyński
2023-02-25 17:59:25 +01:00
parent f1ecb312c7
commit 8bbc7e13fb
221 changed files with 1 additions and 632 deletions

View File

@@ -0,0 +1,208 @@
/* Copyright (c) Kuba Szczodrzyński 2022-06-19. */
#include <LibreTuyaAPI.h>
#include <libraries/Flash/Flash.h>
// can't include <flash.h> as it collides with <Flash.h> on Windows -_-
#define REG_FLASH_BASE 0x00803000
#define REG_FLASH_OPERATE_SW (REG_FLASH_BASE + 0 * 4)
#define REG_FLASH_RDID (REG_FLASH_BASE + 4 * 4)
#define FLASH_BUSY_SW (0x01UL << 31)
#define FLASH_WP_VALUE (0x01UL << 30)
#define FLASH_OP_SW (0x01UL << 29)
#define FLASH_OP_TYPE_POS 24
#define FLASH_OP_RDID 20
extern "C" {
#include <flash_pub.h>
#include <param_config.h>
#include <start_type_pub.h>
#include <sys_ctrl.h>
#include <sys_rtos.h>
#include <wdt_pub.h>
#include <wlan_ui_pub.h>
extern uint8_t system_mac[];
extern uint32_t wdt_ctrl(uint32_t cmd, void *param);
} // extern "C"
void LibreTuya::restart() {
bk_reboot();
}
void LibreTuya::restartDownloadMode() {
bk_reboot();
}
ResetReason LibreTuya::getResetReason() {
switch (bk_misc_get_start_type()) {
case RESET_SOURCE_POWERON:
return RESET_REASON_POWER;
case RESET_SOURCE_REBOOT:
return RESET_REASON_SOFTWARE;
case RESET_SOURCE_WATCHDOG:
return RESET_REASON_WATCHDOG;
case RESET_SOURCE_CRASH_XAT0:
case RESET_SOURCE_CRASH_UNDEFINED:
case RESET_SOURCE_CRASH_PREFETCH_ABORT:
case RESET_SOURCE_CRASH_DATA_ABORT:
case RESET_SOURCE_CRASH_UNUSED:
case RESET_SOURCE_CRASH_PER_XAT0:
return RESET_REASON_CRASH;
case RESET_SOURCE_DEEPPS_GPIO:
case RESET_SOURCE_DEEPPS_RTC:
return RESET_REASON_SLEEP;
}
return RESET_REASON_UNKNOWN;
}
/* CPU-related */
ChipType LibreTuya::getChipType() {
uint8_t chipId = *(uint8_t *)(SCTRL_CHIP_ID);
return CHIP_TYPE_ENUM(FAMILY, chipId);
}
const char *LibreTuya::getChipModel() {
return STRINGIFY_MACRO(MCU);
}
uint32_t LibreTuya::getChipId() {
uint8_t mac[6];
cfg_load_mac(mac); // force loading MAC from TLV (ignore user-set WiFi MAC)
return (mac[3]) | (mac[4] << 8) | (mac[5] << 16);
}
uint8_t LibreTuya::getChipCores() {
return 1;
}
const char *LibreTuya::getChipCoreType() {
return "ARM968E-S";
}
uint32_t LibreTuya::getCpuFreq() {
return configCPU_CLOCK_HZ;
}
uint32_t LibreTuya::getCycleCount() {
// TODO
return 0;
}
/* Flash memory utilities */
FlashId LibreTuya::getFlashChipId() {
uint32_t data = (FLASH_OP_RDID << FLASH_OP_TYPE_POS) | FLASH_OP_SW | FLASH_WP_VALUE;
REG_WRITE(REG_FLASH_OPERATE_SW, data);
while (REG_READ(REG_FLASH_OPERATE_SW) & FLASH_BUSY_SW) {}
FlashId id = {
.manufacturerId = REG_RD8(REG_FLASH_RDID, 2),
.chipId = REG_RD8(REG_FLASH_RDID, 1),
.chipSizeId = REG_RD8(REG_FLASH_RDID, 0),
};
return id;
}
/* Memory management */
uint32_t LibreTuya::getRamSize() {
return 256 * 1024;
}
uint32_t LibreTuya::getHeapSize() {
#if configDYNAMIC_HEAP_SIZE
extern unsigned char _empty_ram;
#if CFG_SOC_NAME == SOC_BK7231N
return (0x00400000 + 192 * 1024) - (uint32_t)(&_empty_ram);
#else
return (0x00400000 + 256 * 1024) - (uint32_t)(&_empty_ram);
#endif
#else
return configTOTAL_HEAP_SIZE;
#endif
}
uint32_t LibreTuya::getFreeHeap() {
return xPortGetFreeHeapSize();
}
uint32_t LibreTuya::getMinFreeHeap() {
return xPortGetMinimumEverFreeHeapSize();
}
uint32_t LibreTuya::getMaxAllocHeap() {
return 0;
}
/* OTA-related */
static int8_t otaImage2Valid = -1;
uint8_t LibreTuya::otaGetRunning() {
// Beken has bootloader-based OTA, running app is always index 1
return 1;
}
uint8_t LibreTuya::otaGetStoredIndex() {
return otaHasImage2() ? 2 : 1;
}
bool LibreTuya::otaSupportsDual() {
return true;
}
bool LibreTuya::otaHasImage1() {
return true;
}
bool LibreTuya::otaHasImage2() {
if (otaImage2Valid != -1)
return otaImage2Valid;
// check download RBL
// TODO: maybe check header CRC or even binary hashes
uint32_t magic;
Flash.readBlock(FLASH_DOWNLOAD_OFFSET, (uint8_t *)&magic, 4);
otaImage2Valid = magic == 0x004C4252; // "RBL\0", little-endian
return otaImage2Valid;
}
bool LibreTuya::otaSwitch(bool force) {
// no need to check otaGetStoredIndex() as it does the same as otaHasImage2()
// force checking validity again
otaImage2Valid = -1;
if (otaHasImage2() && force) {
// "rollback" - abort bootloader upgrade operation by wiping first sector
return Flash.eraseSector(FLASH_DOWNLOAD_OFFSET);
}
return otaHasImage2(); // false if second image is not valid
}
/* Watchdog */
bool LibreTuya::wdtEnable(uint32_t timeout) {
wdt_ctrl(WCMD_SET_PERIOD, &timeout);
wdt_ctrl(WCMD_POWER_UP, NULL);
}
void LibreTuya::wdtDisable() {
wdt_ctrl(WCMD_POWER_DOWN, NULL);
}
void LibreTuya::wdtFeed() {
wdt_ctrl(WCMD_RELOAD_PERIOD, NULL);
}
/* Global instance */
LibreTuya LT;
LibreTuya ESP = LT;

View File

@@ -0,0 +1,109 @@
/* Copyright (c) Kuba Szczodrzyński 2022-06-23. */
#include "SerialClass.h"
extern "C" {
#include <uart_pub.h>
extern void bk_send_byte(uint8_t uport, uint8_t data);
extern void uart_hw_set_change(uint8_t uport, bk_uart_config_t *uart_config);
extern int uart_rx_callback_set(int uport, uart_callback callback, void *param);
} // extern "C"
#ifdef PIN_SERIAL1_TX
SerialClass Serial1(UART1_PORT);
#endif
#ifdef PIN_SERIAL2_TX
SerialClass Serial2(UART2_PORT);
#endif
SerialClass::SerialClass(uint8_t port) {
this->port = port;
this->buf = NULL;
}
#if LT_AUTO_DOWNLOAD_REBOOT
static uint8_t adrState = 0;
static const uint8_t adrCmd[] = {0x01, 0xE0, 0xFC, 0x01, 0x00};
static void adrParse(uint8_t c) {
// parse and respond to link check command (CMD_LinkCheck=0)
adrState = (adrState + 1) * (c == adrCmd[adrState]);
if (adrState == 5) {
LT_I("Auto download mode: rebooting");
LT.restart();
}
}
#endif
static void callback(int port, void *param) {
RingBuffer *buf = (RingBuffer *)param;
int ch;
while ((ch = uart_read_byte(port)) != -1) {
#if LT_AUTO_DOWNLOAD_REBOOT && defined(PIN_SERIAL1_RX)
// parse UART protocol commands on UART1
if (port == UART1_PORT)
adrParse(ch);
#endif
buf->store_char(ch);
}
}
void SerialClass::begin(unsigned long baudrate, uint16_t config) {
uint8_t dataWidth = ((config & SERIAL_DATA_MASK) >> 8) - 1; // 0x100..0x400 -> 0..3
uint8_t parity = 3 - (config & SERIAL_PARITY_MASK); // 0x3..0x1 -> 0..2
uint8_t stopBits = (config & SERIAL_STOP_BIT_MASK) == SERIAL_STOP_BIT_2; // 0x10..0x30 -> 0..1
bk_uart_config_t cfg = {
.baud_rate = baudrate,
.data_width = (uart_data_width_t)dataWidth,
.parity = (uart_parity_t)parity,
.stop_bits = (uart_stop_bits_t)stopBits,
.flow_control = FLOW_CTRL_DISABLED,
};
if (this->buf) {
this->buf->clear();
} else {
this->buf = new RingBuffer();
}
uart_hw_set_change(port, &cfg);
uart_rx_callback_set(port, callback, this->buf);
}
void SerialClass::end() {
uart_rx_callback_set(port, NULL, NULL);
switch (port) {
case 1:
uart1_exit();
break;
case 2:
uart2_exit();
break;
}
delete this->buf;
}
int SerialClass::available() {
return buf->available();
}
int SerialClass::peek() {
return buf->peek();
}
int SerialClass::read() {
return buf->read_char();
}
void SerialClass::flush() {
uart_wait_tx_over();
}
size_t SerialClass::write(uint8_t c) {
bk_send_byte(port, c);
return 1;
}

View File

@@ -0,0 +1,38 @@
/* Copyright (c) Kuba Szczodrzyński 2022-06-23. */
#pragma once
#include <Arduino.h>
#include <api/HardwareSerial.h>
#include <api/RingBuffer.h>
using namespace arduino;
class SerialClass : public HardwareSerial {
private:
uint8_t port;
RingBuffer *buf;
public:
SerialClass(uint8_t port);
inline void begin(unsigned long baudrate) {
begin(baudrate, SERIAL_8N1);
}
void begin(unsigned long baudrate, uint16_t config);
void end();
int available();
int peek();
int read();
void flush();
size_t write(uint8_t c);
operator bool() {
return !!buf;
}
using Print::write;
};
#define HAS_SERIAL_CLASS 1

View File

@@ -0,0 +1,81 @@
/* Copyright (c) Kuba Szczodrzyński 2022-06-26. */
#include "WiFiPriv.h"
WiFiClass::WiFiClass() {
memset(&data, 0x00, sizeof(WiFiData));
data.scanSem = xSemaphoreCreateBinary();
}
WiFiClass::~WiFiClass() {
vSemaphoreDelete(data.scanSem);
}
void WiFiClass::dataInitialize() {
if (data.statusIp)
return;
LT_DM(WIFI, "Data init");
data.configSta = zalloc(sizeof(network_InitTypeDef_st));
data.configAp = zalloc(sizeof(network_InitTypeDef_ap_st));
data.statusIp = malloc(sizeof(IPStatusTypedef));
data.statusLink = malloc(sizeof(LinkStatusTypeDef));
STA_CFG->dhcp_mode = DHCP_CLIENT;
LT_DM(WIFI, "Data = %p", data.configSta);
}
void WiFiClass::dataFree() {
LT_DM(WIFI, "Data free");
free(data.configSta);
free(data.configAp);
free(data.statusIp);
free(data.statusLink);
data.configSta = NULL;
data.configAp = NULL;
data.statusIp = NULL;
data.statusLink = NULL;
}
WiFiStatus eventTypeToStatus(uint8_t type) {
// rw_msg_pub.h:9
switch (type) {
case RW_EVT_STA_IDLE:
return WL_IDLE_STATUS;
case RW_EVT_STA_NO_AP_FOUND:
return WL_NO_SSID_AVAIL;
case RW_EVT_STA_CONNECTING:
case RW_EVT_STA_CONNECTED:
return WL_SCAN_COMPLETED;
case RW_EVT_STA_GOT_IP:
return WL_CONNECTED;
case RW_EVT_STA_PASSWORD_WRONG:
case RW_EVT_STA_ASSOC_FULL:
case RW_EVT_STA_CONNECT_FAILED:
return WL_CONNECT_FAILED;
case RW_EVT_STA_BEACON_LOSE:
return WL_CONNECTION_LOST;
case RW_EVT_STA_DISCONNECTED:
return WL_DISCONNECTED;
}
}
WiFiAuthMode securityTypeToAuthMode(uint8_t type) {
// wlan_ui_pub.h:62
switch (type) {
case BK_SECURITY_TYPE_NONE:
return WIFI_AUTH_OPEN;
case BK_SECURITY_TYPE_WEP:
return WIFI_AUTH_WEP;
case BK_SECURITY_TYPE_WPA_TKIP:
case BK_SECURITY_TYPE_WPA_AES:
return WIFI_AUTH_WPA_PSK;
case BK_SECURITY_TYPE_WPA2_TKIP:
case BK_SECURITY_TYPE_WPA2_AES:
case BK_SECURITY_TYPE_WPA2_MIXED:
return WIFI_AUTH_WPA2_PSK;
case BK_SECURITY_TYPE_WPA3_SAE:
return WIFI_AUTH_WPA3_PSK;
case BK_SECURITY_TYPE_WPA3_WPA2_MIXED:
return WIFI_AUTH_WPA2_WPA3_PSK;
}
return WIFI_AUTH_INVALID;
}

View File

@@ -0,0 +1,127 @@
/* Copyright (c) Kuba Szczodrzyński 2022-07-01. */
#include "WiFiPriv.h"
bool WiFiClass::softAP(const char *ssid, const char *passphrase, int channel, bool ssidHidden, int maxClients) {
if (!enableAP(true))
return WL_CONNECT_FAILED;
if (!validate(ssid, passphrase))
return WL_CONNECT_FAILED;
LT_HEAP_I();
// Beken SDK bug: bk_wlan_ap_init_adv() doesn't null-terminate the passphrase
memset(g_ap_param_ptr->key, '\0', 65);
strcpy(AP_CFG->wifi_ssid, ssid);
if (passphrase) {
strcpy(AP_CFG->wifi_key, passphrase);
AP_CFG->security = BK_SECURITY_TYPE_WPA2_MIXED;
} else {
AP_CFG->wifi_key[0] = '\0';
AP_CFG->security = BK_SECURITY_TYPE_NONE;
}
AP_CFG->channel = channel;
AP_CFG->ssid_hidden = ssidHidden;
AP_CFG->max_con = maxClients;
AP_CFG->dhcp_mode = DHCP_SERVER;
AP_CFG->wifi_retry_interval = 100;
LT_IM(WIFI, "Creating SoftAP %s", ssid);
if (!AP_CFG->local_ip_addr[0]) {
LT_DM(WIFI, "Setting default IP config");
softAPConfig((uint32_t)0, (uint32_t)0, (uint32_t)0);
}
LT_DM(WIFI, "Static IP: %s / %s / %s", AP_CFG->local_ip_addr, AP_CFG->net_mask, AP_CFG->gateway_ip_addr);
__wrap_bk_printf_disable();
OSStatus ret = bk_wlan_start_ap_adv(AP_CFG);
__wrap_bk_printf_enable();
if (ret != 0) {
LT_EM(WIFI, "SoftAP failed; ret=%d", ret);
return false;
}
LT_DM(WIFI, "AP start OK");
return true;
}
bool WiFiClass::softAPConfig(IPAddress localIP, IPAddress gateway, IPAddress subnet) {
dataInitialize();
if (!localIP) {
localIP = gateway = IPAddress(192, 168, 43, 1);
subnet = IPAddress(255, 255, 255, 0);
}
sprintf(AP_CFG->local_ip_addr, IP_FMT, localIP[0], localIP[1], localIP[2], localIP[3]);
sprintf(AP_CFG->net_mask, IP_FMT, subnet[0], subnet[1], subnet[2], subnet[3]);
sprintf(AP_CFG->gateway_ip_addr, IP_FMT, gateway[0], gateway[1], gateway[2], gateway[3]);
// from wlan_ui.c:1370
if (uap_ip_is_start()) {
uap_ip_down();
ip_address_set(
BK_STATION,
AP_CFG->dhcp_mode,
AP_CFG->local_ip_addr,
AP_CFG->net_mask,
AP_CFG->gateway_ip_addr,
AP_CFG->dns_server_ip_addr
);
uap_ip_start();
}
return true;
}
bool WiFiClass::softAPdisconnect(bool wifiOff) {
if (!(getMode() & WIFI_MODE_AP))
// do not call SDK methods before even initializing WiFi first
return true;
return bk_wlan_stop(BK_SOFT_AP) == kNoErr;
}
uint8_t WiFiClass::softAPgetStationNum() {
return 0;
}
IPAddress WiFiClass::softAPIP() {
AP_GET_IP_STATUS_RETURN((uint32_t)0);
IPAddress ip;
ip.fromString(IP_STATUS->ip);
return ip;
}
IPAddress WiFiClass::softAPSubnetMask() {
AP_GET_IP_STATUS_RETURN((uint32_t)0);
IPAddress ip;
ip.fromString(IP_STATUS->mask);
return ip;
}
const char *WiFiClass::softAPgetHostname() {
struct netif *ifs = (struct netif *)net_get_uap_handle();
return netif_get_hostname(ifs);
}
bool WiFiClass::softAPsetHostname(const char *hostname) {
struct netif *ifs = (struct netif *)net_get_uap_handle();
netif_set_hostname(ifs, (char *)hostname);
return true;
}
uint8_t *WiFiClass::softAPmacAddress(uint8_t *mac) {
setMacAddress(mac);
return mac;
}
String WiFiClass::softAPmacAddress(void) {
uint8_t mac[ETH_ALEN];
wifi_get_mac_address((char *)mac, CONFIG_ROLE_AP);
return macToString(mac);
}
const String WiFiClass::softAPSSID(void) {
AP_GET_LINK_STATUS_RETURN("");
return AP_CFG->wifi_ssid;
}

View File

@@ -0,0 +1,25 @@
/* Copyright (c) Kuba Szczodrzyński 2022-06-26. */
#pragma once
#include <Arduino.h>
extern "C" {
#include <FreeRTOS.h>
#include <rw_msg_pub.h>
#include <semphr.h>
} // extern "C"
typedef struct {
void *configSta;
void *configAp;
unsigned long scannedAt;
SemaphoreHandle_t scanSem;
void *statusIp;
void *statusLink;
rw_evt_type lastStaEvent;
rw_evt_type lastApEvent;
bool apEnabled;
} WiFiData;

View File

@@ -0,0 +1,149 @@
/* Copyright (c) Kuba Szczodrzyński 2022-07-10. */
#include "WiFiPriv.h"
#include <vector>
static xQueueHandle wifiEventQueueHandle = NULL;
static xTaskHandle wifiEventTaskHandle = NULL;
static const rw_evt_type eventConnected = RW_EVT_STA_CONNECTED;
// callback for bk_wlan_status_register_cb()
static void wifiStatusCallback(rw_evt_type *pEvent) {
if (wifiEventQueueHandle && wifiEventTaskHandle) {
xQueueSend(wifiEventQueueHandle, pEvent, portMAX_DELAY);
} else {
wifiEventHandler(*pEvent);
}
}
static void wifiEventTask(void *arg) {
rw_evt_type event = RW_EVT_MAX;
for (;;) {
if (xQueueReceive(wifiEventQueueHandle, &event, portMAX_DELAY) == pdTRUE) {
wifiEventHandler(event);
}
}
}
void wifiEventSendArduino(EventId event) {
event = (EventId)(RW_EVT_ARDUINO | event);
wifiStatusCallback((rw_evt_type *)&event);
}
void startWifiTask() {
if (!wifiEventQueueHandle) {
LT_HEAP_I();
wifiEventQueueHandle = xQueueCreate(32, sizeof(rw_evt_type));
LT_HEAP_I();
}
if (!wifiEventTaskHandle) {
LT_HEAP_I();
xTaskCreate(wifiEventTask, "wifievent", 512, NULL, 4, &wifiEventTaskHandle);
LT_HEAP_I();
}
bk_wlan_status_register_cb((FUNC_1PARAM_PTR)wifiStatusCallback);
}
void wifiEventHandler(rw_evt_type event) {
if (!pWiFi)
return; // failsafe
LT_DM(WIFI, "BK event %u", event);
if (event <= RW_EVT_STA_GOT_IP)
pWiFi->data.lastStaEvent = event;
else
pWiFi->data.lastApEvent = event;
EventId eventId;
EventInfo eventInfo;
String ssid;
memset(&eventInfo, 0, sizeof(EventInfo));
switch (event) {
case RW_EVT_STA_IDLE:
eventId = ARDUINO_EVENT_WIFI_READY;
break;
case RW_EVT_STA_BEACON_LOSE:
case RW_EVT_STA_PASSWORD_WRONG:
case RW_EVT_STA_NO_AP_FOUND:
case RW_EVT_STA_ASSOC_FULL:
case RW_EVT_STA_DISCONNECTED:
case RW_EVT_STA_CONNECT_FAILED:
eventId = ARDUINO_EVENT_WIFI_STA_DISCONNECTED;
eventInfo.wifi_sta_disconnected.ssid_len = 0;
switch (event) {
case RW_EVT_STA_BEACON_LOSE:
eventInfo.wifi_sta_disconnected.reason = WIFI_REASON_BEACON_TIMEOUT;
break;
case RW_EVT_STA_PASSWORD_WRONG:
eventInfo.wifi_sta_disconnected.reason = WIFI_REASON_AUTH_FAIL;
break;
case RW_EVT_STA_NO_AP_FOUND:
eventInfo.wifi_sta_disconnected.reason = WIFI_REASON_NO_AP_FOUND;
break;
case RW_EVT_STA_ASSOC_FULL:
eventInfo.wifi_sta_disconnected.reason = WIFI_REASON_ASSOC_TOOMANY;
break;
case RW_EVT_STA_DISCONNECTED:
eventInfo.wifi_sta_disconnected.reason = WIFI_REASON_ASSOC_LEAVE;
break;
case RW_EVT_STA_CONNECT_FAILED:
eventInfo.wifi_sta_disconnected.reason = WIFI_REASON_CONNECTION_FAIL;
break;
default:
eventInfo.wifi_sta_disconnected.reason = WIFI_REASON_UNSPECIFIED;
break;
}
break;
case RW_EVT_STA_CONNECTED:
eventId = ARDUINO_EVENT_WIFI_STA_CONNECTED;
ssid = pWiFi->SSID();
eventInfo.wifi_sta_connected.ssid_len = ssid.length();
eventInfo.wifi_sta_connected.channel = pWiFi->channel();
eventInfo.wifi_sta_connected.authmode = pWiFi->getEncryption();
memcpy(eventInfo.wifi_sta_connected.ssid, ssid.c_str(), eventInfo.wifi_sta_connected.ssid_len + 1);
memcpy(eventInfo.wifi_sta_connected.bssid, pWiFi->BSSID(), 6);
break;
case RW_EVT_STA_GOT_IP:
eventId = ARDUINO_EVENT_WIFI_STA_GOT_IP;
eventInfo.got_ip.if_index = 0;
eventInfo.got_ip.ip_changed = true;
eventInfo.got_ip.ip_info.ip.addr = WiFi.localIP();
eventInfo.got_ip.ip_info.netmask.addr = WiFi.subnetMask();
eventInfo.got_ip.ip_info.gw.addr = WiFi.gatewayIP();
break;
case RW_EVT_AP_CONNECTED:
eventId = ARDUINO_EVENT_WIFI_AP_STACONNECTED;
// TODO station MAC
break;
case RW_EVT_AP_DISCONNECTED:
eventId = ARDUINO_EVENT_WIFI_AP_STADISCONNECTED;
// TODO station MAC
break;
case RW_EVT_ARDUINO | ARDUINO_EVENT_WIFI_SCAN_DONE:
eventId = ARDUINO_EVENT_WIFI_SCAN_DONE;
eventInfo.wifi_scan_done.status = 0;
if (pWiFi->scan)
eventInfo.wifi_scan_done.number = pWiFi->scan->count;
eventInfo.wifi_scan_done.scan_id = 0;
break;
default:
if (event < RW_EVT_ARDUINO)
return;
eventId = (EventId)(event - RW_EVT_ARDUINO);
break;
}
pWiFi->postEvent(eventId, eventInfo);
}

View File

@@ -0,0 +1,89 @@
/* Copyright (c) Kuba Szczodrzyński 2022-06-26. */
#include "WiFiPriv.h"
bool WiFiClass::modePriv(WiFiMode mode, WiFiModeAction sta, WiFiModeAction ap) {
__wrap_bk_printf_disable();
startWifiTask();
if (!__bk_rf_is_init) {
LT_DM(WIFI, "Initializing func&app");
func_init_extended();
app_pre_start();
// wait for the init_thread to finish its job
while (xTaskGetHandle("init_thread")) {
LT_VM(WIFI, "Waiting for init_thread");
delay(10);
}
LT_DM(WIFI, "Init OK");
__bk_rf_is_init = true;
}
LT_HEAP_I();
if (mode) {
LT_DM(WIFI, "Wakeup RF");
uint32_t reg = 1; // this is only checked for being true-ish
sddev_control(SCTRL_DEV_NAME, CMD_RF_HOLD_BIT_SET, &reg);
}
if (sta == WLMODE_ENABLE) {
LT_DM(WIFI, "Enabling STA");
bk_wlan_sta_init(NULL);
#if CFG_WPA_CTRL_IFACE
wlan_sta_enable();
#endif
wifiEventSendArduino(ARDUINO_EVENT_WIFI_STA_START);
} else if (sta == WLMODE_DISABLE) {
LT_DM(WIFI, "Disabling STA");
bk_wlan_stop(BK_STATION);
wifiEventSendArduino(ARDUINO_EVENT_WIFI_STA_STOP);
}
LT_HEAP_I();
if (ap == WLMODE_ENABLE) {
LT_DM(WIFI, "Enabling AP");
// fake it - on BK7231, enabling the AP without starting it breaks all connection attempts
data.apEnabled = true;
wifiEventSendArduino(ARDUINO_EVENT_WIFI_AP_START);
} else if (ap == WLMODE_DISABLE) {
LT_DM(WIFI, "Disabling AP");
bk_wlan_stop(BK_SOFT_AP);
data.apEnabled = false;
wifiEventSendArduino(ARDUINO_EVENT_WIFI_AP_STOP);
}
// force checking actual mode again
mode = getMode();
if (!mode)
dataFree();
LT_HEAP_I();
__wrap_bk_printf_enable();
return true;
}
WiFiMode WiFiClass::getMode() {
uint8_t sta = !!bk_wlan_has_role(VIF_STA) * WIFI_MODE_STA;
uint8_t ap = data.apEnabled * WIFI_MODE_AP; // report the faked value
return (WiFiMode)(sta | ap);
}
WiFiStatus WiFiClass::status() {
rw_evt_type status = data.lastStaEvent;
if (status == RW_EVT_STA_CONNECTED && STA_CFG->dhcp_mode == DHCP_DISABLE)
status = RW_EVT_STA_GOT_IP;
return eventTypeToStatus(status);
}
IPAddress WiFiClass::hostByName(const char *hostname) {
ip_addr_t ip;
int ret = netconn_gethostbyname(hostname, &ip);
if (ret == ERR_OK) {
return ip.addr;
}
return IPAddress();
}

View File

@@ -0,0 +1,90 @@
/* Copyright (c) Kuba Szczodrzyński 2022-06-26. */
#pragma once
#include <api/WiFi/WiFi.h>
extern "C" {
#include <lwip/api.h>
#include <lwip/ip_addr.h>
#include <lwip/netif.h>
// port/net.h
#include <net.h>
#include <FreeRTOS.h>
#include <semphr.h>
#include <common.h>
#include <config.h>
#include <main_none.h>
#include <param_config.h>
#include <rw_msg_rx.h>
#include <sa_ap.h>
#include <sys_ctrl_pub.h>
#include <vif_mgmt.h>
#include <wlan_ui_pub.h>
#include <wpa_supplicant_i.h>
extern void func_init_extended();
extern void app_pre_start();
extern void bk_wlan_ap_init(network_InitTypeDef_st *inNetworkInitPara);
// func/hostapd-2.5/wpa_supplicant/main_supplicant.c
extern struct wpa_ssid_value *wpas_connect_ssid;
// app/param_config.c
extern general_param_t *g_wlan_general_param;
extern ap_param_t *g_ap_param_ptr;
extern sta_param_t *g_sta_param_ptr;
extern uint8_t system_mac[6];
// WiFi.cpp
WiFiStatus eventTypeToStatus(uint8_t type);
WiFiAuthMode securityTypeToAuthMode(uint8_t type);
// WiFiEvents.cpp
extern void wifiEventSendArduino(EventId event);
extern void startWifiTask();
extern void wifiEventHandler(rw_evt_type event);
#define RW_EVT_ARDUINO (1 << 7)
#define IP_FMT "%u.%u.%u.%u"
#define STA_CFG ((network_InitTypeDef_st *)data.configSta)
#define AP_CFG ((network_InitTypeDef_ap_st *)data.configAp)
#define IP_STATUS ((IPStatusTypedef *)data.statusIp)
#define LINK_STATUS ((LinkStatusTypeDef *)data.statusLink)
#define STA_GET_LINK_STATUS_RETURN(ret) \
{ \
if (!sta_ip_is_start()) \
return ret; \
memset(LINK_STATUS, 0x00, sizeof(LinkStatusTypeDef)); \
bk_wlan_get_link_status(LINK_STATUS); \
}
#define STA_GET_IP_STATUS_RETURN(ret) \
{ \
if (!sta_ip_is_start()) \
return ret; \
memset(IP_STATUS, 0x00, sizeof(IPStatusTypedef)); \
bk_wlan_get_ip_status(IP_STATUS, BK_STATION); \
}
#define AP_GET_LINK_STATUS_RETURN(ret) \
{ \
if (!uap_ip_is_start()) \
return ret; \
}
#define AP_GET_IP_STATUS_RETURN(ret) \
{ \
if (!uap_ip_is_start()) \
return ret; \
memset(IP_STATUS, 0x00, sizeof(IPStatusTypedef)); \
bk_wlan_get_ip_status(IP_STATUS, BK_SOFT_AP); \
}
} // extern "C"

View File

@@ -0,0 +1,224 @@
/* Copyright (c) Kuba Szczodrzyński 2022-06-27. */
#include "WiFiPriv.h"
WiFiStatus
WiFiClass::begin(const char *ssid, const char *passphrase, int32_t channel, const uint8_t *bssid, bool connect) {
if (!enableSTA(true))
return WL_CONNECT_FAILED;
if (!validate(ssid, passphrase))
return WL_CONNECT_FAILED;
LT_HEAP_I();
disconnect(false);
strcpy(STA_CFG->wifi_ssid, ssid);
if (passphrase) {
strcpy(STA_CFG->wifi_key, passphrase);
} else {
STA_CFG->wifi_bssid[0] = '\0';
}
if (reconnect(bssid))
return WL_CONNECTED;
return WL_CONNECT_FAILED;
}
bool WiFiClass::config(IPAddress localIP, IPAddress gateway, IPAddress subnet, IPAddress dns1, IPAddress dns2) {
dataInitialize();
STA_CFG->dhcp_mode = localIP ? DHCP_DISABLE : DHCP_CLIENT;
if (localIP) {
sprintf(STA_CFG->local_ip_addr, IP_FMT, localIP[0], localIP[1], localIP[2], localIP[3]);
sprintf(STA_CFG->net_mask, IP_FMT, subnet[0], subnet[1], subnet[2], subnet[3]);
sprintf(STA_CFG->gateway_ip_addr, IP_FMT, gateway[0], gateway[1], gateway[2], gateway[3]);
if (dns1) {
sprintf(STA_CFG->dns_server_ip_addr, IP_FMT, dns1[0], dns1[1], dns1[2], dns1[3]);
} else {
STA_CFG->dns_server_ip_addr[0] = '\0';
}
} else {
STA_CFG->local_ip_addr[0] = '\0';
STA_CFG->net_mask[0] = '\0';
STA_CFG->gateway_ip_addr[0] = '\0';
STA_CFG->dns_server_ip_addr[0] = '\0';
}
// from wlan_ui.c:1370
if (sta_ip_is_start()) {
sta_ip_down();
ip_address_set(
BK_STATION,
STA_CFG->dhcp_mode,
STA_CFG->local_ip_addr,
STA_CFG->net_mask,
STA_CFG->gateway_ip_addr,
STA_CFG->dns_server_ip_addr
);
sta_ip_start();
}
return true;
}
bool WiFiClass::reconnect(const uint8_t *bssid) {
dataInitialize();
if (!bssid && !STA_CFG->wifi_ssid[0]) {
LT_EM(WIFI, "(B)SSID not specified");
goto error;
}
if (bssid) {
LT_IM(WIFI, "Connecting to " MACSTR, MAC2STR(bssid));
} else {
LT_IM(WIFI, "Connecting to %s", STA_CFG->wifi_ssid);
}
LT_DM(WIFI, "Data = %p", data.configSta);
STA_CFG->wifi_mode = BK_STATION;
STA_CFG->wifi_retry_interval = 100;
if (bssid)
memcpy(STA_CFG->wifi_bssid, bssid, 6);
else
memset(STA_CFG->wifi_bssid, 0x00, 6);
if (STA_CFG->dhcp_mode == DHCP_DISABLE) {
LT_DM(WIFI, "Static IP: %s / %s / %s", STA_CFG->local_ip_addr, STA_CFG->net_mask, STA_CFG->gateway_ip_addr);
LT_DM(WIFI, "Static DNS: %s", STA_CFG->dns_server_ip_addr);
} else {
LT_DM(WIFI, "Using DHCP");
}
LT_DM(WIFI, "Starting WiFi...");
__wrap_bk_printf_disable();
bk_wlan_start_sta(STA_CFG);
__wrap_bk_printf_enable();
LT_DM(WIFI, "Start OK");
return true;
error:
return false;
}
bool WiFiClass::disconnect(bool wifiOff) {
#if LT_DEBUG_WIFI
memset(LINK_STATUS, 0x00, sizeof(LinkStatusTypeDef));
bk_wlan_get_link_status(LINK_STATUS);
LT_DM(WIFI, "Disconnecting from %s (wifiOff=%d)", LINK_STATUS ? LINK_STATUS->ssid : NULL, wifiOff);
#endif
bk_wlan_connection_loss();
if (wifiOff)
enableSTA(false);
return true;
}
bool WiFiClass::setAutoReconnect(bool autoReconnect) {
return false;
}
bool WiFiClass::getAutoReconnect() {
return false;
}
IPAddress WiFiClass::localIP() {
STA_GET_IP_STATUS_RETURN((uint32_t)0);
IPAddress ip;
ip.fromString(IP_STATUS->ip);
return ip;
}
IPAddress WiFiClass::subnetMask() {
STA_GET_IP_STATUS_RETURN((uint32_t)0);
IPAddress ip;
ip.fromString(IP_STATUS->mask);
return ip;
}
IPAddress WiFiClass::gatewayIP() {
STA_GET_IP_STATUS_RETURN((uint32_t)0);
IPAddress ip;
ip.fromString(IP_STATUS->gate);
return ip;
}
IPAddress WiFiClass::dnsIP(uint8_t dns_no) {
STA_GET_IP_STATUS_RETURN((uint32_t)0);
IPAddress ip;
ip.fromString(IP_STATUS->dns);
return ip;
}
IPAddress WiFiClass::broadcastIP() {
return calculateBroadcast(localIP(), subnetMask());
}
const char *WiFiClass::getHostname() {
struct netif *ifs = (struct netif *)net_get_sta_handle();
return netif_get_hostname(ifs);
}
bool WiFiClass::setHostname(const char *hostname) {
struct netif *ifs = (struct netif *)net_get_sta_handle();
netif_set_hostname(ifs, (char *)hostname);
return true;
}
uint8_t *WiFiClass::macAddress(uint8_t *mac) {
wifi_get_mac_address((char *)mac, CONFIG_ROLE_STA);
return mac;
}
bool WiFiClass::setMacAddress(const uint8_t *mac) {
if (mac[0] & 0x01) {
LT_EM(WIFI, "Invalid MAC address");
return false;
}
// ensure "mac_inited" is true
wifi_get_mac_address((char *)system_mac, BK_STATION);
// store the MAC globally
memcpy(system_mac, mac, 6);
WiFiMode previousMode = getMode();
if (previousMode) {
mode(WIFI_MODE_NULL);
mode(previousMode);
}
return true;
}
const String WiFiClass::SSID() {
STA_GET_LINK_STATUS_RETURN("");
return (char *)LINK_STATUS->ssid;
}
const String WiFiClass::psk() {
if (!isConnected())
return "";
struct wpa_supplicant *wpas = wpa_suppliant_ctrl_get_wpas();
if (!wpas || !wpas->conf || !wpas->conf->ssid)
return "";
return (char *)wpas->conf->ssid->passphrase;
}
uint8_t *WiFiClass::BSSID() {
STA_GET_LINK_STATUS_RETURN(NULL);
return LINK_STATUS->bssid;
}
int32_t WiFiClass::channel() {
STA_GET_LINK_STATUS_RETURN(0);
return LINK_STATUS->channel;
}
int8_t WiFiClass::RSSI() {
STA_GET_LINK_STATUS_RETURN(0);
return LINK_STATUS->wifi_strength;
}
WiFiAuthMode WiFiClass::getEncryption() {
STA_GET_LINK_STATUS_RETURN(WIFI_AUTH_INVALID);
return securityTypeToAuthMode(LINK_STATUS->security);
}

View File

@@ -0,0 +1,95 @@
/* Copyright (c) Kuba Szczodrzyński 2022-06-27. */
#include "WiFiPriv.h"
static void scanHandler(void *ctx, uint8_t param) {
LT_HEAP_I();
WiFiClass *cls = (WiFiClass *)ctx;
if (!cls) {
LT_WM(WIFI, "Called without ctx");
return;
}
WiFiScanData *scan = cls->scan;
if (!scan) {
LT_WM(WIFI, "Called without cls->scan");
return;
}
ScanResult_adv result;
if (wlan_sta_scan_result(&result)) {
LT_EM(WIFI, "Failed to get scan result");
goto end;
}
LT_IM(WIFI, "Found %d APs", result.ApNum);
cls->scanAlloc(result.ApNum);
if (!scan->ap) {
LT_WM(WIFI, "scan->ap alloc failed");
goto end;
}
for (uint8_t i = 0; i < result.ApNum; i++) {
scan->ap[i].ssid = strdup(result.ApList[i].ssid);
scan->ap[i].auth = securityTypeToAuthMode(result.ApList[i].security);
scan->ap[i].rssi = result.ApList[i].ApPower;
scan->ap[i].channel = result.ApList[i].channel;
memcpy(scan->ap[i].bssid.addr, result.ApList[i].bssid, 6);
}
cls->data.scannedAt = millis();
wifiEventSendArduino(ARDUINO_EVENT_WIFI_SCAN_DONE);
end:
scan->timeout = 0;
if (scan->running) {
// running == false means it was discarded (timeout)
scan->running = false;
xSemaphoreGive(cls->data.scanSem);
}
LT_HEAP_I();
return;
}
int16_t WiFiClass::scanNetworks(bool async, bool showHidden, bool passive, uint32_t maxMsPerChannel, uint8_t channel) {
if (scan && scan->running) {
if (scan->timeout && millis() > scan->timeout) {
LT_WM(WIFI, "Scan timeout, discarding");
scan->running = false;
} else {
return WIFI_SCAN_RUNNING;
}
}
enableSTA(true);
scanDelete();
scanInit();
LT_IM(WIFI, "Starting WiFi scan");
__wrap_bk_printf_disable();
mhdr_scanu_reg_cb(scanHandler, this);
bk_wlan_start_scan();
LT_HEAP_I();
scan->running = true;
scan->timeout = millis() + maxMsPerChannel * 20 + 1000;
int16_t ret = WIFI_SCAN_RUNNING;
if (!async) {
LT_IM(WIFI, "Waiting for results");
xSemaphoreTake(data.scanSem, 1); // reset the semaphore quickly
xSemaphoreTake(data.scanSem, pdMS_TO_TICKS(maxMsPerChannel * 20));
if (scan->running) {
scanDelete();
ret = WIFI_SCAN_FAILED;
goto exit;
}
ret = scan->count;
goto exit;
}
exit:
__wrap_bk_printf_enable();
return ret;
}

View File

@@ -0,0 +1,32 @@
/* Copyright (c) Kuba Szczodrzyński 2022-06-14. */
#pragma once
#ifdef __cplusplus
#include "WCharacterFixup.h"
#endif
#define delay delayMilliseconds // change delay()'s signature - it's defined as static inline in WVariant.h
#include <api/ArduinoAPI.h>
#include <core/LibreTuyaAPI.h>
#undef delay
// Include family-specific code
#include "WVariant.h"
// Include board variant
#include "variant.h"
// Choose the main UART output port
#ifndef LT_UART_DEFAULT_PORT
#if defined(PIN_SERIAL2_TX)
#define LT_UART_DEFAULT_PORT 2
#else
#define LT_UART_DEFAULT_PORT 1
#endif
#endif
// Define available serial ports
#ifdef __cplusplus
#include "SerialClass.h"
#include <core/SerialExtern.h>
#endif

View File

@@ -0,0 +1,5 @@
/* Copyright (c) Kuba Szczodrzyński 2022-07-11. */
#pragma once
#define LT_MD5_USE_HOSTAPD 1

View File

@@ -0,0 +1,22 @@
/* Copyright (c) Kuba Szczodrzyński 2022-06-18. */
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "sdk_extern.h"
#include "sdk_mem.h"
// define an inline delay() which overrides BDK's delay()
static inline __attribute__((always_inline)) void delay(unsigned long ms) {
delayMilliseconds(ms);
}
// from fixups/arch_main.c
extern unsigned char __bk_rf_is_init;
#ifdef __cplusplus
} // extern "C"
#endif

View File

@@ -0,0 +1,38 @@
/* Copyright (c) Kuba Szczodrzyński 2022-06-19. */
#include <Arduino.h>
extern "C" {
#include <rtos_pub.h>
#include <sys_rtos.h>
extern int uart_print_port;
} // extern "C"
beken_thread_t mainThread;
void initArduino() {
// set default UART output port
uart_print_port = LT_UART_DEFAULT_PORT - 1;
#if LT_AUTO_DOWNLOAD_REBOOT && defined(PIN_SERIAL1_RX) && defined(PIN_SERIAL1_TX)
// initialize auto-download-reboot parser
Serial1.begin(115200);
#endif
}
bool startMainTask() {
OSStatus ret = rtos_create_thread(
&mainThread,
THD_APPLICATION_PRIORITY,
"main",
(beken_thread_function_t)mainTask,
8192,
NULL
);
if (ret != kNoErr)
return false;
vTaskStartScheduler();
return true;
}

View File

@@ -0,0 +1,109 @@
/* Copyright (c) Kuba Szczodrzyński 2022-06-19. */
#include <Arduino.h>
#include <arm_arch.h>
#include <bk_timer.h>
#include <bk_timer_pub.h>
#include <rtos_pub.h>
#include <sys_rtos.h>
#define TICKS_PER_US (CFG_XTAL_FREQUENCE / 1000 / 1000)
#define US_PER_OVERFLOW (portTICK_PERIOD_MS * 1000)
#define TICKS_PER_OVERFLOW (TICKS_PER_US * US_PER_OVERFLOW)
void delayMilliseconds(unsigned long ms) {
rtos_delay_milliseconds(ms);
}
static uint32_t getTicksCount() {
// copied from bk_timer_ctrl(), for speeds
uint32_t timeout = 0;
REG_WRITE(TIMER0_2_READ_CTL, (BKTIMER0 << 2) | 1);
while (REG_READ(TIMER0_2_READ_CTL) & 1) {
timeout++;
if (timeout > (120 * 1000))
return 0;
}
return REG_READ(TIMER0_2_READ_VALUE);
}
void delayMicroseconds(unsigned int us) {
#if LT_MICROS_HIGH_RES
if (us == 0)
return;
us--;
uint32_t startTick = getTicksCount();
/* startTick2 accounts for the case where the timer counter overflows */
uint32_t startTick2 = startTick - TICKS_PER_OVERFLOW;
uint32_t delayTicks = TICKS_PER_US * us;
while (delayTicks > TICKS_PER_OVERFLOW) {
// The delay is longer than what the timer can count.
// Let it overflow until only a fraction of TICKS_PER_OVERFLOW remain.
while (getTicksCount() > startTick) {}
while (getTicksCount() < startTick) {}
delayTicks -= TICKS_PER_OVERFLOW;
}
while ((getTicksCount() - startTick < delayTicks) || // normal case
(getTicksCount() - startTick2 < delayTicks) // counter overflow case
) {}
#else
volatile uint32_t i, j;
for (i = 0; i < us; i++) {
for (j = 0; j < 6; j++) {}
}
#endif
}
unsigned long millis() {
return xTaskGetTickCount() * portTICK_PERIOD_MS;
}
unsigned long micros() {
#if (CFG_SOC_NAME == SOC_BK7231)
#error "Not implemented"
#endif
#if LT_MICROS_HIGH_RES
static uint32_t lastMillis = 0;
static uint32_t correctedMillis = 0;
static uint32_t lastTicks = 0;
uint32_t nowMillis = millis();
uint32_t nowTicks = getTicksCount();
bool tickOverflow = nowTicks < lastTicks;
bool millisUpdated = nowMillis != lastMillis;
if (millisUpdated) {
/* reset artificially corrected millis */
correctedMillis = nowMillis;
} else if (tickOverflow) {
/*
This can happen if micros is called from within a interruptLock block (interrupts disabled).
In this case, if the tick counter rolls over, millis() won't be updated, and micros will
lag by US_PER_OVERFLOW milliseconds (one rollover).
The workaround only works as long as micros() calls happen within 2ms of eachother.
WARNING: if interrupts are disabled for more than 2ms, micros() and millis() will temporarily get out of sync.
*/
correctedMillis += portTICK_PERIOD_MS;
}
lastMillis = nowMillis;
lastTicks = nowTicks;
return correctedMillis * 1000 + nowTicks / (CFG_XTAL_FREQUENCE / 1000 / 1000);
#else
#if 0
uint32_t timeout = 0;
REG_WRITE(TIMER3_5_READ_CTL, (BKTIMER3 << 2) | 1);
while (REG_READ(TIMER3_5_READ_CTL) & 1) {
timeout++;
if (timeout > (120 * 1000))
return 0;
}
return millis() * 1000 + REG_READ(TIMER3_5_READ_VALUE) / 32;
#endif
return millis() * 1000;
#endif
}
void yield() {
runPeriodicTasks();
vTaskDelay(1);
taskYIELD();
}

View File

@@ -0,0 +1,142 @@
/* Copyright (c) Kuba Szczodrzyński 2022-06-20. */
#include <Arduino.h>
#include <pwm_pub.h>
#include <saradc_pub.h>
static GPIO_INDEX pwmToGpio[] = {
GPIO6, // PWM0
GPIO7, // PWM1
GPIO8, // PWM2
GPIO9, // PWM3
GPIO24, // PWM4
GPIO26, // PWM5
};
#if CFG_SOC_NAME == SOC_BK7231N
static GPIO_INDEX adcToGpio[] = {
-1, // ADC0 - VBAT
GPIONUM, // ADC1
GPIONUM, // ADC2
GPIO23, // ADC3
GPIONUM, // ADC4
GPIONUM, // ADC5
GPIONUM, // ADC6
GPIONUM, // ADC7
};
#else
static GPIO_INDEX adcToGpio[] = {
-1, // ADC0 - VBAT
GPIO4, // ADC1
GPIO5, // ADC2
GPIO23, // ADC3
GPIO2, // ADC4
GPIO3, // ADC5
GPIO12, // ADC6
GPIO13, // ADC7
};
#endif
static uint8_t gpioToPwm(GPIO_INDEX gpio) {
for (uint8_t i = 0; i < sizeof(pwmToGpio) / sizeof(GPIO_INDEX); i++) {
if (pwmToGpio[i] == gpio)
return i;
}
return 0;
}
static uint8_t gpioToAdc(GPIO_INDEX gpio) {
for (uint8_t i = 0; i < sizeof(adcToGpio) / sizeof(GPIO_INDEX); i++) {
if (adcToGpio[i] == gpio)
return i;
}
return 0;
}
static pwm_param_t pwm;
static uint16_t adcData[1];
uint16_t analogReadVoltage(pin_size_t pinNumber) {
PinInfo *pin = pinInfo(pinNumber);
if (!pin)
return 0;
if (!pinSupported(pin, PIN_ADC))
return 0;
UINT32 status;
saradc_desc_t adc;
DD_HANDLE handle;
saradc_config_param_init(&adc);
adc.channel = gpioToAdc(pin->gpio);
adc.mode = ADC_CONFIG_MODE_CONTINUE;
adc.pData = adcData;
adc.data_buff_size = 1;
handle = ddev_open(SARADC_DEV_NAME, &status, (uint32_t)&adc);
if (status)
return 0;
// wait for data
while (!adc.has_data || adc.current_sample_data_cnt < 1) {
delay(1);
}
ddev_control(handle, SARADC_CMD_RUN_OR_STOP_ADC, (void *)false);
ddev_close(handle);
return adcData[0];
}
uint16_t analogReadMaxVoltage(pin_size_t pinNumber) {
return 3300;
}
void analogWrite(pin_size_t pinNumber, int value) {
PinInfo *pin = pinInfo(pinNumber);
if (!pin)
return;
if (!pinSupported(pin, PIN_PWM))
return;
float percent = value * 1.0 / ((1 << _analogWriteResolution) - 1);
uint32_t frequency = 26 * _analogWritePeriod - 1;
uint32_t dutyCycle = percent * frequency;
pwm.channel = gpioToPwm(pin->gpio);
#if CFG_SOC_NAME != SOC_BK7231N
pwm.duty_cycle = dutyCycle;
#else
pwm.duty_cycle1 = dutyCycle;
pwm.duty_cycle2 = 0;
pwm.duty_cycle3 = 0;
#endif
if (dutyCycle) {
if (!pinEnabled(pin, PIN_PWM)) {
// enable PWM and set its value
pwm.cfg.bits.en = PWM_ENABLE;
pwm.cfg.bits.int_en = PWM_INT_DIS;
pwm.cfg.bits.mode = PWM_PWM_MODE;
pwm.cfg.bits.clk = PWM_CLK_26M;
pwm.end_value = frequency;
pwm.p_Int_Handler = NULL;
__wrap_bk_printf_disable();
sddev_control(PWM_DEV_NAME, CMD_PWM_INIT_PARAM, &pwm);
sddev_control(PWM_DEV_NAME, CMD_PWM_INIT_LEVL_SET_HIGH, &pwm.channel);
sddev_control(PWM_DEV_NAME, CMD_PWM_UNIT_ENABLE, &pwm.channel);
__wrap_bk_printf_enable();
pin->enabled &= ~PIN_GPIO;
pin->enabled |= PIN_PWM;
} else {
// update duty cycle
sddev_control(PWM_DEV_NAME, CMD_PWM_SET_DUTY_CYCLE, &pwm);
}
} else {
if (pinEnabled(pin, PIN_PWM)) {
// disable PWM
pwm.cfg.bits.en = PWM_DISABLE;
__wrap_bk_printf_disable();
sddev_control(PWM_DEV_NAME, CMD_PWM_DEINIT_PARAM, &pwm);
__wrap_bk_printf_enable();
pin->enabled &= ~PIN_PWM;
}
// force level as LOW
pinMode(pinNumber, OUTPUT);
digitalWrite(pinNumber, LOW);
}
}

View File

@@ -0,0 +1,62 @@
/* Copyright (c) Kuba Szczodrzyński 2022-06-20. */
#include <Arduino.h>
void pinMode(pin_size_t pinNumber, PinMode pinMode) {
PinInfo *pin = pinInfo(pinNumber);
if (!pin)
return;
if (!pinSupported(pin, PIN_GPIO))
return;
if (pinEnabled(pin, PIN_PWM))
// disable PWM before using the pin
analogWrite(pinNumber, 0);
if (pinEnabled(pin, PIN_GPIO) && pin->mode == pinMode)
return;
switch (pinMode) {
case INPUT:
gpio_config(pin->gpio, GMODE_INPUT);
break;
case OUTPUT:
gpio_config(pin->gpio, GMODE_OUTPUT);
break;
case INPUT_PULLUP:
gpio_config(pin->gpio, GMODE_INPUT_PULLUP);
break;
case INPUT_PULLDOWN:
gpio_config(pin->gpio, GMODE_INPUT_PULLDOWN);
break;
case OUTPUT_OPENDRAIN:
gpio_config(pin->gpio, GMODE_SET_HIGH_IMPENDANCE);
break;
}
pin->enabled |= PIN_GPIO;
pin->mode = pinMode;
}
void digitalWrite(pin_size_t pinNumber, PinStatus status) {
// verify level is 0 or 1
if (status > HIGH)
return;
PinInfo *pin = pinInfo(pinNumber);
if (!pin)
return;
// pin is not GPIO yet or not OUTPUT; enable or disable input pullup
if (!pinEnabled(pin, PIN_GPIO) || !pinIsOutput(pin)) {
pinMode(pinNumber, status * INPUT_PULLUP);
return;
}
// write the new state
gpio_output(pin->gpio, status);
}
PinStatus digitalRead(pin_size_t pinNumber) {
PinInfo *pin = pinInfo(pinNumber);
if (!pin)
return 0;
// pin is not GPIO yet or not INPUT; change the mode
if (!pinEnabled(pin, PIN_GPIO) || !pinIsInput(pin))
pinMode(pinNumber, INPUT);
// read the value
return gpio_input(pin->gpio);
}

View File

@@ -0,0 +1,99 @@
/* Copyright (c) Kuba Szczodrzyński 2022-07-31. */
#include <Arduino.h>
static void *irqHandlerList[PINS_COUNT] = {NULL};
static void *irqHandlerArgs[PINS_COUNT] = {NULL};
static bool irqChangeList[PINS_COUNT];
static void irqHandler(unsigned char gpio) {
int pin = -1;
for (pin_size_t i = 0; i < PINS_COUNT; i++) {
if (pinTable[i].gpio == gpio) {
pin = i;
break;
}
}
if (pin == -1)
return;
if (!irqHandlerList[pin])
return;
if (irqChangeList[pin]) {
if (pinTable[pin].mode == INPUT_PULLDOWN) {
pinTable[pin].mode = INPUT_PULLUP;
gpio_int_enable(pinTable[pin].gpio, GPIO_INT_LEVEL_FALLING, irqHandler);
} else if (pinTable[pin].mode == INPUT_PULLUP) {
pinTable[pin].mode = INPUT_PULLDOWN;
gpio_int_enable(pinTable[pin].gpio, GPIO_INT_LEVEL_RISING, irqHandler);
}
}
if (irqHandlerArgs[pin] == NULL) {
((voidFuncPtr)irqHandlerList[pin])();
} else {
((voidFuncPtrParam)irqHandlerList[pin])(irqHandlerArgs[pin]);
}
}
void attachInterrupt(pin_size_t interruptNumber, voidFuncPtr callback, PinStatus mode) {
attachInterruptParam(interruptNumber, (voidFuncPtrParam)callback, mode, NULL);
}
void attachInterruptParam(pin_size_t interruptNumber, voidFuncPtrParam callback, PinStatus mode, void *param) {
PinInfo *pin = pinInfo(interruptNumber);
if (!pin)
return;
if (!pinSupported(pin, PIN_IRQ))
return;
uint32_t event = 0;
PinMode modeNew = 0;
bool change = 0;
switch (mode) {
case LOW:
event = GPIO_INT_LEVEL_LOW;
modeNew = INPUT_PULLUP;
change = false;
break;
case HIGH:
event = GPIO_INT_LEVEL_HIGH;
modeNew = INPUT_PULLDOWN;
change = false;
break;
case FALLING:
event = GPIO_INT_LEVEL_FALLING;
modeNew = INPUT_PULLUP;
change = false;
break;
case RISING:
event = GPIO_INT_LEVEL_RISING;
modeNew = INPUT_PULLDOWN;
change = false;
break;
case CHANGE:
event = GPIO_INT_LEVEL_FALLING;
modeNew = INPUT_PULLUP;
change = true;
break;
default:
return;
}
irqHandlerList[interruptNumber] = callback;
irqHandlerArgs[interruptNumber] = param;
irqChangeList[interruptNumber] = change;
gpio_int_enable(pin->gpio, event, irqHandler);
pin->enabled |= PIN_IRQ | PIN_GPIO;
pin->mode = modeNew;
}
void detachInterrupt(pin_size_t interruptNumber) {
PinInfo *pin = pinInfo(interruptNumber);
if (!pin)
return;
if (!pinSupported(pin, PIN_IRQ))
return;
irqHandlerList[interruptNumber] = NULL;
irqHandlerArgs[interruptNumber] = NULL;
irqChangeList[interruptNumber] = false;
gpio_int_disable(pin->gpio);
pin->enabled &= ~PIN_IRQ;
}

View File

@@ -0,0 +1,65 @@
/* Copyright (c) Kuba Szczodrzyński 2022-06-19. */
#include <lt_logger.h>
#include <sdk_extern.h>
#include <fal.h>
#include <flash_pub.h>
#define FLASH_ERASE_MIN_SIZE (4 * 1024)
extern uint32_t flash_ctrl(uint32_t cmd, void *param);
extern PROTECT_TYPE get_flash_protect();
extern void flash_protection_op(uint8_t mode, PROTECT_TYPE type);
static void unprotect() {
PROTECT_TYPE type = get_flash_protect();
if (type != FLASH_PROTECT_NONE) {
flash_protection_op(0, FLASH_PROTECT_NONE);
#if LT_LOGLEVEL <= LT_LEVEL_DEBUG
LT_D("Flash protect: %u -> %u", type, get_flash_protect());
uint16_t sr = 0;
flash_ctrl(CMD_FLASH_READ_SR, &sr);
LT_D("SR = %04x", sr);
#endif
}
}
static int init() {
__wrap_bk_printf_disable();
flash_init();
flash_ctrl(CMD_FLASH_WRITE_ENABLE, NULL);
unprotect();
__wrap_bk_printf_enable();
return 0;
}
static int read(long offset, uint8_t *buf, size_t size) {
flash_read((char *)buf, size, offset);
return size;
}
static int write(long offset, const uint8_t *buf, size_t size) {
unprotect();
flash_write((char *)buf, size, offset);
return size;
}
static int erase(long offset, size_t size) {
unprotect();
size = ((size - 1) / FLASH_ERASE_MIN_SIZE) + 1;
for (uint16_t i = 0; i < size; i++) {
uint32_t addr = offset + i * FLASH_ERASE_MIN_SIZE;
flash_ctrl(CMD_FLASH_ERASE_SECTOR, &addr);
}
return size * FLASH_ERASE_MIN_SIZE;
}
const struct fal_flash_dev flash0 = {
.name = FAL_FLASH_DEV_NAME,
.addr = 0x0,
.len = FLASH_LENGTH,
.blk_size = FLASH_ERASE_MIN_SIZE,
.ops = {init, read, write, erase},
.write_gran = 1,
};

View File

@@ -0,0 +1,21 @@
/* Copyright (c) Kuba Szczodrzyński 2022-06-19. */
#include <printf_config.h>
#include <printf/printf.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
extern void bk_send_byte(uint8_t uport, uint8_t data);
extern int uart_print_port;
void putchar_(char c) {
bk_send_byte(uart_print_port, c);
}
void putchar_p(char c, unsigned long port) {
bk_send_byte((port & 0xFF) - 1, c);
}
WRAP_PRINTF(bk_printf);

View File

@@ -0,0 +1,7 @@
/* Copyright (c) Kuba Szczodrzyński 2022-06-20. */
#pragma once
#include <printf_config.h>
WRAP_DISABLE_DEF(bk_printf);

View File

@@ -0,0 +1,20 @@
/* Copyright (c) Kuba Szczodrzyński 2022-06-18. */
#pragma once
#include <stddef.h>
// Beken SDK is actually pretty good, in terms of declaring
// stdlib functions properly! So no need for any #define hell.
#include <mem_pub.h>
// All the MemMang functions are in stdlib, just wrapped
// during linking.
#include <stdlib.h>
// for memcpy etc.
#include <string.h>
// ...except zalloc, which is apparently not in the stdlib
#define zalloc os_zalloc
#define LT_HEAP_FUNC xPortGetFreeHeapSize

View File

@@ -0,0 +1,30 @@
/* Copyright (c) Kuba Szczodrzyński 2022-06-18. */
#pragma once
// for printf() etc (they are wrapped anyway)
#include <stdio.h>
// most stuff is here
#include <include.h>
// for os_printf
#include <uart_pub.h>
// for GPIO names
#include <gpio_pub.h>
// conflict with stl_algobase.h
#undef min
#undef max
// include printf() wrapper disable methods
#include <printf_port.h>
// make non-SDK code call the proper printf()
#undef bk_printf
#undef os_printf
#undef warning_prf
#undef fatal_prf
#define bk_printf printf
#define os_printf printf
#define warning_prf printf
#define fatal_prf printf

View File

@@ -0,0 +1,148 @@
/* Copyright (c) Kuba Szczodrzyński 2022-06-06. */
#include "LibreTuyaClass.h"
/**
* @brief Get LibreTuya version string.
*/
const char *LibreTuya::getVersion() {
return LT_VERSION_STR;
}
/**
* @brief Get board name.
*/
const char *LibreTuya::getBoard() {
return LT_BOARD_STR;
}
/**
* @brief Get CPU family ID.
*/
ChipFamily LibreTuya::getChipFamily() {
return FAMILY;
}
/**
* @brief Get CPU family name as string.
*/
const char *LibreTuya::getChipFamilyName() {
return STRINGIFY_MACRO(FAMILY) + 2;
}
static char *deviceName = NULL;
/**
* @brief Get device friendly name in format "LT-<board>-<chip id>".
* Can be used as hostname.
*/
const char *LibreTuya::getDeviceName() {
if (deviceName)
return deviceName;
uint32_t chipId = getChipId();
uint8_t *id = (uint8_t *)&chipId;
const char *board = getBoard();
uint8_t boardLen = strlen(board);
deviceName = (char *)malloc(3 + boardLen + 1 + 6 + 1);
sprintf(deviceName, "LT-%s-%02x%02x%02x", board, id[0], id[1], id[2]);
return deviceName;
}
/**
* @brief Get a textual representation of a reset reason.
*
* @param reason value to convert to text, uses getResetReason() by default
*/
const char *LibreTuya::getResetReasonName(ResetReason reason) {
if (reason >= RESET_REASON_MAX)
reason = getResetReason();
switch (reason) {
case RESET_REASON_POWER:
return "Power-On";
case RESET_REASON_BROWNOUT:
return "Brownout";
case RESET_REASON_HARDWARE:
return "HW Reboot";
case RESET_REASON_SOFTWARE:
return "SW Reboot";
case RESET_REASON_WATCHDOG:
return "WDT Reset";
case RESET_REASON_CRASH:
return "Crash";
case RESET_REASON_SLEEP:
return "Sleep Wakeup";
}
return "Unknown";
}
/**
* @brief Get CPU frequency in MHz.
*/
uint32_t LibreTuya::getCpuFreqMHz() {
return getCpuFreq() / 1000000;
}
/**
* @brief Get flash chip total size.
* The default implementation uses the least significant
* byte of the chip ID to determine the size.
*/
__attribute__((weak)) uint32_t LibreTuya::getFlashChipSize() {
FlashId id = getFlashChipId();
if (id.chipSizeId >= 0x14 && id.chipSizeId <= 0x19) {
return (1 << id.chipSizeId);
}
#ifdef FLASH_LENGTH
return FLASH_LENGTH;
#else
return 0;
#endif
}
/**
* @brief Get the OTA index for updated firmware.
*
* Note: returns 1 for chips without dual-OTA.
*/
uint8_t LibreTuya::otaGetTarget() {
if (!otaSupportsDual())
return 1;
return otaGetRunning() ^ 0b11;
}
/**
* @brief Perform OTA rollback: switch to the previous image, or abort current
* switched OTA update, if not rebooted yet.
*
* @return false if no second image to run, writing failed or dual-OTA not supported
*/
bool LibreTuya::otaRollback() {
if (!otaCanRollback())
return false;
if (otaGetRunning() != otaGetStoredIndex())
// force switching back to current image
return otaSwitch(true);
return true;
}
/**
* @brief Check if OTA rollback is supported and available (there is another image to run).
* @return false if no second image to run or dual-OTA not supported
*/
bool LibreTuya::otaCanRollback() {
if (!otaSupportsDual())
return false;
if (otaGetRunning() == otaGetStoredIndex())
return true;
if (otaGetRunning() == 1 && otaHasImage1())
return true;
if (otaGetRunning() == 2 && otaHasImage2())
return true;
return false;
}
__attribute__((weak)) void LibreTuya::gpioRecover() {
// nop by default
}

View File

@@ -0,0 +1,193 @@
/* Copyright (c) Kuba Szczodrzyński 2022-06-06. */
#pragma once
#ifdef __cplusplus
#include "LibreTuyaAPI.h"
#include <core/ChipType.h>
typedef enum {
RESET_REASON_UNKNOWN = 0,
RESET_REASON_POWER = 1,
RESET_REASON_BROWNOUT = 2,
RESET_REASON_HARDWARE = 3,
RESET_REASON_SOFTWARE = 4,
RESET_REASON_WATCHDOG = 5,
RESET_REASON_CRASH = 6,
RESET_REASON_SLEEP = 7,
RESET_REASON_MAX = 8,
} ResetReason;
/**
* @brief Flash chip ID structure.
*/
typedef struct {
uint8_t manufacturerId;
uint8_t chipId;
uint8_t chipSizeId;
} FlashId;
/**
* @brief Main LibreTuya API class.
*
* This class contains all functions common amongst all families.
* Implementations of these methods may vary between families.
*
* The class is accessible using the `LT` global object (defined by the family).
*/
class LibreTuya {
public: /* Common methods - note: these are documented in LibreTuyaAPI.cpp */
const char *getVersion();
const char *getBoard();
ChipFamily getChipFamily();
const char *getChipFamilyName();
const char *getDeviceName();
const char *getResetReasonName(ResetReason reason = RESET_REASON_MAX);
uint32_t getCpuFreqMHz();
uint32_t getFlashChipSize();
uint8_t otaGetTarget();
bool otaRollback();
bool otaCanRollback();
public: /* Compatibility methods */
/**
* @brief Alias of getMaxAllocHeap().
*/
inline uint32_t getMaxFreeBlockSize() {
return getMaxAllocHeap();
}
public: /* Family-defined methods */
/**
* @brief Reboot the CPU.
*/
void restart();
/**
* @brief Reboot the CPU and stay in download mode (if possible).
*/
void restartDownloadMode();
/**
* @brief Get the reason of last chip reset.
*/
ResetReason getResetReason();
/**
* @brief Reconfigure GPIO pins used for debugging
* (SWD/JTAG), so that they can be used as normal I/O.
*/
void gpioRecover();
public: /* CPU-related */
/**
* @brief Get CPU model ID.
*/
ChipType getChipType();
/**
* @brief Get CPU model name as string.
*/
const char *getChipModel();
/**
* @brief Get CPU unique ID. This may be based on MAC, eFuse, etc.
* Note: the number should be 24-bit (with most significant byte being zero).
*/
uint32_t getChipId();
/**
* @brief Get CPU core count.
*/
uint8_t getChipCores();
/**
* @brief Get CPU core type name as string.
*/
const char *getChipCoreType();
/**
* @brief Get CPU frequency in Hz.
*/
uint32_t getCpuFreq();
/**
* @brief Get CPU cycle count.
*/
uint32_t getCycleCount();
public: /* Flash memory utilities */
/**
* @brief Read flash chip ID and return a FlashId struct.
*/
FlashId getFlashChipId();
public: /* Memory management */
/**
* @brief Get total RAM size.
*/
uint32_t getRamSize();
/**
* @brief Get total heap size.
*/
uint32_t getHeapSize();
/**
* @brief Get free heap size.
*/
uint32_t getFreeHeap();
/**
* @brief Get lowest level of free heap memory.
*/
uint32_t getMinFreeHeap();
/**
* @brief Get largest block of heap that can be allocated at once.
*/
uint32_t getMaxAllocHeap();
public: /* OTA-related */
/**
* @brief Get the currently running firmware OTA index.
*/
uint8_t otaGetRunning();
/**
* @brief Read the currently active OTA index, i.e. the one that will boot upon restart.
*/
uint8_t otaGetStoredIndex();
/**
* @brief Check if the chip supports dual-OTA (i.e. OTA is flashed to a different partition).
*
* TODO: make this work for actual dual-OTA chips; remove checking this in otaGetTarget() etc.
*/
bool otaSupportsDual();
/**
* @brief Check if OTA1 image is valid.
*/
bool otaHasImage1();
/**
* @brief Check if OTA2 image is valid.
*/
bool otaHasImage2();
/**
* @brief Try to switch OTA index to the other image.
*
* Note: should return true for chips without dual-OTA. Should return false if one of two images is not valid.
*
* @param force switch even if other image already marked as active
* @return false if writing failed; true otherwise
*/
bool otaSwitch(bool force = false);
public: /* Watchdog */
/**
* @brief Enable the hardware watchdog.
*
* @param timeout watchdog timeout, milliseconds (defaults to 10s)
* @return whether the chip has a hardware watchdog
*/
bool wdtEnable(uint32_t timeout = 10000);
/**
* @brief Disable the hardware watchdog.
*/
void wdtDisable();
/**
* @brief Feed/reset the hardware watchdog timer.
*/
void wdtFeed();
};
extern LibreTuya LT;
extern LibreTuya ESP;
#endif

View File

@@ -0,0 +1,45 @@
/* Copyright (c) Kuba Szczodrzyński 2022-07-03. */
#include "SoftwareSerial.h"
#ifdef LT_ARD_HAS_SOFTSERIAL
SoftwareSerial::SoftwareSerial(pin_size_t receivePin, pin_size_t transmitPin, bool inverted) {
data.rx.buf = NULL;
data.tx.buf = NULL;
data.rx.pin = receivePin;
data.tx.pin = transmitPin;
data.invert = inverted == true;
}
int SoftwareSerial::available() {
return data.rx.buf->available();
}
int SoftwareSerial::peek() {
return data.rx.buf->peek();
}
int SoftwareSerial::read() {
return data.rx.buf->read_char();
}
void SoftwareSerial::flush() {
while (data.rx.buf->available()) {
yield();
}
}
size_t SoftwareSerial::write(uint8_t c) {
while (data.tx.buf->isFull()) {
yield();
}
data.tx.buf->store_char(c);
if (data.tx.state == SS_IDLE) {
data.tx.state = SS_START;
this->startTx();
}
return 1;
}
#endif

View File

@@ -0,0 +1,76 @@
/* Copyright (c) Kuba Szczodrzyński 2022-07-03. */
#pragma once
#ifdef LT_ARD_HAS_SOFTSERIAL
#include <Arduino.h>
#include <api/HardwareSerial.h>
#include <api/RingBuffer.h>
using namespace arduino;
typedef enum {
SS_IDLE = 0,
SS_START,
SS_DATA0,
SS_DATA1,
SS_DATA2,
SS_DATA3,
SS_DATA4,
SS_DATA5,
SS_DATA6,
SS_DATA7,
SS_STOP,
SS_END,
} SoftState;
typedef struct {
SoftState state;
RingBuffer *buf;
uint8_t byte;
pin_size_t pin;
void *param;
} SoftData;
typedef struct {
SoftData rx;
SoftData tx;
uint8_t invert;
void *param;
} SoftSerial;
class SoftwareSerial : public HardwareSerial {
private:
SoftSerial data;
void *param;
public:
SoftwareSerial(pin_size_t receivePin, pin_size_t transmitPin, bool inverted = false);
inline void begin(unsigned long baudrate) {
begin(baudrate, SERIAL_8N1);
}
int available();
int peek();
int read();
void flush();
size_t write(uint8_t c);
operator bool() {
return data.rx.buf || data.tx.buf;
}
public: // Family needs to implement these methods only
void begin(unsigned long baudrate, uint16_t config);
void end();
private:
void startTx();
void endTx();
using Print::write;
};
#endif

View File

@@ -0,0 +1,77 @@
/* Copyright (c) Kuba Szczodrzyński 2022-04-25. */
#include "WiFi.h"
void WiFiClass::printDiag(Print &dest) {
const char *modes[] = {"NULL", "STA", "AP", "STA+AP"};
const char *enc[] = {"Open", "WEP", "WPA PSK", "WPA2 PSK", "WPA/WPA2", "WPA", "WPA2"};
dest.print("Mode: ");
dest.println(modes[getMode()]);
if (getMode() & WIFI_MODE_STA) {
dest.println("-- Station --");
dest.print("SSID: ");
if (isConnected()) {
dest.println(SSID());
dest.print("Channel: ");
dest.println(channel());
dest.print("BSSID: ");
dest.println(BSSIDstr());
dest.print("RSSI: ");
dest.println(RSSI());
dest.print("Encryption: ");
dest.println(enc[getEncryption()]);
dest.print("IP: ");
dest.println(localIP());
dest.print("MAC: ");
dest.println(macAddress());
dest.print("Hostname: ");
dest.println(getHostname());
} else {
dest.println("disconnected");
}
}
if (getMode() & WIFI_MODE_AP) {
dest.println("-- Access Point --");
dest.print("SSID: ");
if (softAPSSID().length()) {
dest.println(softAPSSID());
dest.print("IP: ");
dest.println(softAPIP());
dest.print("MAC: ");
dest.println(softAPmacAddress());
dest.print("Hostname: ");
dest.println(softAPgetHostname());
} else {
dest.println("disconnected");
}
}
}
bool WiFiClass::validate(const char *ssid, const char *passphrase) {
if (!ssid || *ssid == 0x00 || strlen(ssid) > 32) {
LT_WM(WIFI, "SSID not specified or too long");
return false;
}
if (passphrase) {
uint16_t length = strlen(passphrase);
if (length < 8) {
LT_WM(WIFI, "Passphrase too short (%u)", length);
return false;
}
if (length > 63) {
LT_WM(WIFI, "Passphrase too long (%u)", length);
return false;
}
}
return true;
}
__attribute__((weak)) void WiFiClass::dataInitialize() {}
__attribute__((weak)) void WiFiClass::dataFree() {}
WiFiClass WiFi;
WiFiClass *pWiFi = NULL;

View File

@@ -0,0 +1,197 @@
/*
WiFi.h - esp32 Wifi support.
Based on WiFi.h from Arduino WiFi shield library.
Copyright (c) 2011-2014 Arduino. All right reserved.
Modified by Ivan Grokhotkov, December 2014
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#pragma once
#include <Arduino.h>
#include <api/Events.h>
#include <api/IPAddress.h>
#include <api/IPv6Address.h>
#include <vector>
#include "WiFiType.h"
#ifdef LT_ARD_HAS_WIFI
// family's data structure
#include <WiFiData.h>
#endif
class WiFiClass {
public:
#ifdef LT_ARD_HAS_WIFI
// must be public for WiFiEvents & WiFiScan static handlers
WiFiData data;
#endif
WiFiScanData *scan = NULL;
public: /* WiFi.cpp */
WiFiClass();
~WiFiClass();
void printDiag(Print &dest);
bool validate(const char *ssid, const char *passphrase);
void dataInitialize();
void dataFree();
public: /* WiFiGeneric.cpp */
bool mode(WiFiMode mode);
bool modePriv(WiFiMode mode, WiFiModeAction sta, WiFiModeAction ap);
WiFiMode getMode();
WiFiStatus status();
bool enableSTA(bool enable);
bool enableAP(bool enable);
bool setSleep(bool enable);
bool getSleep();
bool setTxPower(int power);
int getTxPower();
int hostByName(const char *hostname, IPAddress &aResult);
IPAddress hostByName(const char *hostname);
static IPAddress calculateNetworkID(IPAddress ip, IPAddress subnet);
static IPAddress calculateBroadcast(IPAddress ip, IPAddress subnet);
static uint8_t calculateSubnetCIDR(IPAddress subnetMask);
static String macToString(uint8_t *mac);
protected: /* WiFiEvents.cpp */
static std::vector<EventHandler> handlers;
public: /* WiFiEvents.cpp */
uint16_t onEvent(EventCb callback, EventId eventId = ARDUINO_EVENT_MAX);
uint16_t onEvent(EventFuncCb callback, EventId eventId = ARDUINO_EVENT_MAX);
uint16_t onEvent(EventSysCb callback, EventId eventId = ARDUINO_EVENT_MAX);
void removeEvent(EventCb callback, EventId eventId);
void removeEvent(EventSysCb callback, EventId eventId);
void removeEvent(uint16_t id);
static void postEvent(EventId eventId, EventInfo eventInfo);
public: /* WiFiSTA.cpp */
WiFiStatus begin(
const char *ssid,
const char *passphrase = NULL,
int32_t channel = 0,
const uint8_t *bssid = NULL,
bool connect = true
);
WiFiStatus
begin(char *ssid, char *passphrase = NULL, int32_t channel = 0, const uint8_t *bssid = NULL, bool connect = true);
bool config(
IPAddress localIP,
IPAddress gateway,
IPAddress subnet,
IPAddress dns1 = (uint32_t)0x00000000,
IPAddress dns2 = (uint32_t)0x00000000
);
bool reconnect(const uint8_t *bssid = NULL);
bool disconnect(bool wifiOff = false);
bool isConnected();
bool setAutoReconnect(bool autoReconnect);
bool getAutoReconnect();
WiFiStatus waitForConnectResult(unsigned long timeout);
IPAddress localIP();
uint8_t *macAddress(uint8_t *mac);
String macAddress();
IPAddress subnetMask();
IPAddress gatewayIP();
IPAddress dnsIP(uint8_t dns_no = 0);
IPAddress broadcastIP();
IPAddress networkID();
uint8_t subnetCIDR();
bool enableIpV6();
IPv6Address localIPv6();
const char *getHostname();
bool setHostname(const char *hostname);
bool setMacAddress(const uint8_t *mac);
inline bool hostname(const String &aHostname) {
return setHostname(aHostname.c_str());
}
const String SSID();
const String psk();
uint8_t *BSSID();
String BSSIDstr();
int32_t channel();
int8_t RSSI();
WiFiAuthMode getEncryption();
public: /* WiFiScan.cpp */
int16_t scanNetworks(
bool async = false,
bool showHidden = false,
bool passive = false,
uint32_t maxMsPerChannel = 300,
uint8_t channel = 0
);
bool getNetworkInfo(
uint8_t networkItem,
String &ssid,
WiFiAuthMode &encryptionType,
int32_t &RSSI,
uint8_t *&BSSID,
int32_t &channel
);
int16_t scanComplete();
uint8_t scanAlloc(uint8_t count);
void scanInit();
void scanDelete();
String SSID(uint8_t networkItem);
WiFiAuthMode encryptionType(uint8_t networkItem);
int32_t RSSI(uint8_t networkItem);
uint8_t *BSSID(uint8_t networkItem);
String BSSIDstr(uint8_t networkItem);
int32_t channel(uint8_t networkItem);
public: /* WiFiAP.cpp */
bool softAP(
const char *ssid, const char *passphrase = NULL, int channel = 1, bool ssidHidden = false, int maxClients = 4
);
bool softAPConfig(IPAddress localIP, IPAddress gateway, IPAddress subnet);
bool softAPdisconnect(bool wifiOff = false);
uint8_t softAPgetStationNum();
IPAddress softAPIP();
IPAddress softAPBroadcastIP();
IPAddress softAPNetworkID();
uint8_t softAPSubnetCIDR();
IPAddress softAPSubnetMask();
bool softAPenableIpV6();
IPv6Address softAPIPv6();
const char *softAPgetHostname();
bool softAPsetHostname(const char *hostname);
uint8_t *softAPmacAddress(uint8_t *mac);
String softAPmacAddress(void);
const String softAPSSID(void);
};
extern WiFiClass WiFi;
extern WiFiClass *pWiFi;

View File

@@ -0,0 +1,23 @@
/* Copyright (c) Kuba Szczodrzyński 2022-04-25. */
#include "WiFi.h"
IPAddress WiFiClass::softAPBroadcastIP() {
return calculateBroadcast(softAPIP(), softAPSubnetMask());
}
IPAddress WiFiClass::softAPNetworkID() {
return calculateNetworkID(softAPIP(), softAPSubnetMask());
}
uint8_t WiFiClass::softAPSubnetCIDR() {
return calculateSubnetCIDR(softAPSubnetMask());
}
__attribute__((weak)) bool WiFiClass::softAPenableIpV6() {
return false;
}
__attribute__((weak)) IPv6Address WiFiClass::softAPIPv6() {
return IPv6Address();
}

View File

@@ -0,0 +1,84 @@
/* Copyright (c) Kuba Szczodrzyński 2022-05-17. */
#include "WiFi.h"
std::vector<EventHandler> WiFiClass::handlers;
uint16_t WiFiClass::onEvent(EventCb callback, EventId eventId) {
if (!callback)
return 0;
EventHandler handler;
handler.cb = callback;
handler.eventId = eventId;
handlers.push_back(handler);
return handler.id;
}
uint16_t WiFiClass::onEvent(EventFuncCb callback, EventId eventId) {
if (!callback)
return 0;
EventHandler handler;
handler.fcb = callback;
handler.eventId = eventId;
handlers.push_back(handler);
return handler.id;
}
uint16_t WiFiClass::onEvent(EventSysCb callback, EventId eventId) {
if (!callback)
return 0;
EventHandler handler;
handler.scb = callback;
handler.eventId = eventId;
handlers.push_back(handler);
return handler.id;
}
void WiFiClass::removeEvent(EventCb callback, EventId eventId) {
if (!callback)
return;
for (uint16_t i = 0; i < handlers.size(); i++) {
EventHandler handler = handlers[i];
if (handler.cb == callback && handler.eventId == eventId) {
handlers.erase(handlers.begin() + i);
}
}
}
void WiFiClass::removeEvent(EventSysCb callback, EventId eventId) {
if (!callback)
return;
for (uint16_t i = 0; i < handlers.size(); i++) {
EventHandler handler = handlers[i];
if (handler.scb == callback && handler.eventId == eventId) {
handlers.erase(handlers.begin() + i);
}
}
}
void WiFiClass::removeEvent(uint16_t id) {
for (uint16_t i = 0; i < handlers.size(); i++) {
EventHandler handler = handlers[i];
if (handler.id == id) {
handlers.erase(handlers.begin() + i);
}
}
}
void WiFiClass::postEvent(EventId eventId, EventInfo eventInfo) {
for (auto handler : handlers) {
if (handler.eventId != ARDUINO_EVENT_MAX && handler.eventId != eventId)
continue;
if (handler.cb) {
handler.cb(eventId);
} else if (handler.fcb) {
handler.fcb(eventId, eventInfo);
} else if (handler.scb) {
Event_t event = {
.event_id = eventId,
.event_info = eventInfo,
};
handler.scb(&event);
}
}
}

View File

@@ -0,0 +1,173 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "WiFiType.h"
/** Argument structure for WIFI_EVENT_SCAN_DONE event */
typedef struct {
uint32_t status; /**< status of scanning APs: 0 - success, 1 - failure */
uint8_t number; /**< number of scan results */
uint8_t scan_id; /**< scan sequence number, used for block scan */
} wifi_event_sta_scan_done_t;
/** Argument structure for WIFI_EVENT_STA_CONNECTED event */
typedef struct {
uint8_t ssid[32]; /**< SSID of connected AP */
uint8_t ssid_len; /**< SSID length of connected AP */
uint8_t bssid[6]; /**< BSSID of connected AP*/
uint8_t channel; /**< channel of connected AP*/
wifi_auth_mode_t authmode; /**< authentication mode used by AP*/
} wifi_event_sta_connected_t;
/** Argument structure for WIFI_EVENT_STA_DISCONNECTED event */
typedef struct {
uint8_t ssid[32]; /**< SSID of disconnected AP */
uint8_t ssid_len; /**< SSID length of disconnected AP */
uint8_t bssid[6]; /**< BSSID of disconnected AP */
uint8_t reason; /**< reason of disconnection */
} wifi_event_sta_disconnected_t;
/** Argument structure for WIFI_EVENT_STA_AUTHMODE_CHANGE event */
typedef struct {
wifi_auth_mode_t old_mode; /**< the old auth mode of AP */
wifi_auth_mode_t new_mode; /**< the new auth mode of AP */
} wifi_event_sta_authmode_change_t;
/** Argument structure for WIFI_EVENT_STA_WPS_ER_PIN event */
typedef struct {
uint8_t pin_code[8]; /**< PIN code of station in enrollee mode */
} wifi_event_sta_wps_er_pin_t;
/** Argument structure for WIFI_EVENT_STA_WPS_ER_FAILED event */
typedef enum {
WPS_FAIL_REASON_NORMAL = 0, /**< ESP32 WPS normal fail reason */
WPS_FAIL_REASON_RECV_M2D, /**< ESP32 WPS receive M2D frame */
WPS_FAIL_REASON_MAX
} wifi_event_sta_wps_fail_reason_t;
#define MAX_SSID_LEN 32
#define MAX_PASSPHRASE_LEN 64
#define MAX_WPS_AP_CRED 3
/** Argument structure for WIFI_EVENT_STA_WPS_ER_SUCCESS event */
typedef struct {
uint8_t ap_cred_cnt; /**< Number of AP credentials received */
struct {
uint8_t ssid[MAX_SSID_LEN]; /**< SSID of AP */
uint8_t passphrase[MAX_PASSPHRASE_LEN]; /**< Passphrase for the AP */
} ap_cred[MAX_WPS_AP_CRED]; /**< All AP credentials received from WPS handshake */
} wifi_event_sta_wps_er_success_t;
/** Argument structure for WIFI_EVENT_AP_STACONNECTED event */
typedef struct {
uint8_t mac[6]; /**< MAC address of the station connected to ESP32 soft-AP */
uint8_t aid; /**< the aid that ESP32 soft-AP gives to the station connected to */
bool is_mesh_child; /**< flag to identify mesh child */
} wifi_event_ap_staconnected_t;
/** Argument structure for WIFI_EVENT_AP_STADISCONNECTED event */
typedef struct {
uint8_t mac[6]; /**< MAC address of the station disconnects to ESP32 soft-AP */
uint8_t aid; /**< the aid that ESP32 soft-AP gave to the station disconnects to */
bool is_mesh_child; /**< flag to identify mesh child */
} wifi_event_ap_stadisconnected_t;
/** Argument structure for WIFI_EVENT_AP_PROBEREQRECVED event */
typedef struct {
int rssi; /**< Received probe request signal strength */
uint8_t mac[6]; /**< MAC address of the station which send probe request */
} wifi_event_ap_probe_req_rx_t;
/**
* @brief FTM operation status types
*
*/
typedef enum {
FTM_STATUS_SUCCESS = 0, /**< FTM exchange is successful */
FTM_STATUS_UNSUPPORTED, /**< Peer does not support FTM */
FTM_STATUS_CONF_REJECTED, /**< Peer rejected FTM configuration in FTM Request */
FTM_STATUS_NO_RESPONSE, /**< Peer did not respond to FTM Requests */
FTM_STATUS_FAIL, /**< Unknown error during FTM exchange */
} wifi_ftm_status_t;
/** Argument structure for */
typedef struct {
uint8_t dlog_token; /**< Dialog Token of the FTM frame */
int8_t rssi; /**< RSSI of the FTM frame received */
uint32_t rtt; /**< Round Trip Time in pSec with a peer */
uint64_t t1; /**< Time of departure of FTM frame from FTM Responder in pSec */
uint64_t t2; /**< Time of arrival of FTM frame at FTM Initiator in pSec */
uint64_t t3; /**< Time of departure of ACK from FTM Initiator in pSec */
uint64_t t4; /**< Time of arrival of ACK at FTM Responder in pSec */
} wifi_ftm_report_entry_t;
/** Argument structure for WIFI_EVENT_FTM_REPORT event */
typedef struct {
uint8_t peer_mac[6]; /**< MAC address of the FTM Peer */
wifi_ftm_status_t status; /**< Status of the FTM operation */
uint32_t rtt_raw; /**< Raw average Round-Trip-Time with peer in Nano-Seconds */
uint32_t rtt_est; /**< Estimated Round-Trip-Time with peer in Nano-Seconds */
uint32_t dist_est; /**< Estimated one-way distance in Centi-Meters */
wifi_ftm_report_entry_t
*ftm_report_data; /**< Pointer to FTM Report with multiple entries, should be freed after use */
uint8_t ftm_report_num_entries; /**< Number of entries in the FTM Report data */
} wifi_event_ftm_report_t;
#define WIFI_STATIS_BUFFER (1 << 0)
#define WIFI_STATIS_RXTX (1 << 1)
#define WIFI_STATIS_HW (1 << 2)
#define WIFI_STATIS_DIAG (1 << 3)
#define WIFI_STATIS_PS (1 << 4)
#define WIFI_STATIS_ALL (-1)
/** Argument structure for WIFI_EVENT_ACTION_TX_STATUS event */
typedef struct {
int ifx; /**< WiFi interface to send request to */
uint32_t context; /**< Context to identify the request */
uint8_t da[6]; /**< Destination MAC address */
uint8_t status; /**< Status of the operation */
} wifi_event_action_tx_status_t;
/** Argument structure for WIFI_EVENT_ROC_DONE event */
typedef struct {
uint32_t context; /**< Context to identify the request */
} wifi_event_roc_done_t;
/** Event structure for IP_EVENT_STA_GOT_IP, IP_EVENT_ETH_GOT_IP events */
typedef struct {
esp_ip4_addr_t ip; /**< Interface IPV4 address */
esp_ip4_addr_t netmask; /**< Interface IPV4 netmask */
esp_ip4_addr_t gw; /**< Interface IPV4 gateway address */
} esp_netif_ip_info_t;
/** @brief IPV6 IP address information
*/
typedef struct {
esp_ip6_addr_t ip; /**< Interface IPV6 address */
} esp_netif_ip6_info_t;
typedef struct {
int if_index; /*!< Interface index for which the event is received (left for legacy compilation) */
void *esp_netif; /*!< Pointer to corresponding esp-netif object */
esp_netif_ip_info_t ip_info; /*!< IP address, netmask, gatway IP address */
bool ip_changed; /*!< Whether the assigned IP has changed or not */
} ip_event_got_ip_t;
/** Event structure for IP_EVENT_GOT_IP6 event */
typedef struct {
int if_index; /*!< Interface index for which the event is received (left for legacy compilation) */
void *esp_netif; /*!< Pointer to corresponding esp-netif object */
esp_netif_ip6_info_t ip6_info; /*!< IPv6 address of the interface */
int ip_index; /*!< IPv6 address index */
} ip_event_got_ip6_t;
/** Event structure for IP_EVENT_AP_STAIPASSIGNED event */
typedef struct {
esp_ip4_addr_t ip; /*!< IP address which was assigned to the station */
} ip_event_ap_staipassigned_t;

View File

@@ -0,0 +1,117 @@
/* Copyright (c) Kuba Szczodrzyński 2022-04-25. */
#include "WiFi.h"
bool WiFiClass::mode(WiFiMode mode) {
// store a pointer to WiFi for WiFiEvents.cpp
pWiFi = this;
WiFiMode currentMode = getMode();
LT_DM(WIFI, "Mode changing %u -> %u", currentMode, mode);
if (mode == currentMode)
return true;
// get mode changes as 0/1
WiFiModeAction sta = WiFiModeAction((mode & WIFI_MODE_STA) != (currentMode & WIFI_MODE_STA));
WiFiModeAction ap = WiFiModeAction((mode & WIFI_MODE_AP) != (currentMode & WIFI_MODE_AP));
// change 0/1 to 1/2
sta = WiFiModeAction(sta + sta * !!(mode & WIFI_MODE_STA));
ap = WiFiModeAction(ap + ap * !!(mode & WIFI_MODE_AP));
// initialize data structures if wifi is enabled
if (mode)
dataInitialize();
// actually change the mode
LT_HEAP_I();
if (!modePriv(mode, sta, ap))
return false;
if (getMode() != mode) {
LT_WM(WIFI, "Mode changed to %d (requested %d)", getMode(), mode);
}
return true;
}
bool WiFiClass::enableSTA(bool enable) {
WiFiMode currentMode = getMode();
if (((currentMode & WIFI_MODE_STA) != 0) != enable) {
return mode((WiFiMode)(currentMode ^ WIFI_MODE_STA));
}
return true;
}
bool WiFiClass::enableAP(bool enable) {
WiFiMode currentMode = getMode();
if (((currentMode & WIFI_MODE_AP) != 0) != enable) {
return mode((WiFiMode)(currentMode ^ WIFI_MODE_AP));
}
return true;
}
__attribute__((weak)) bool WiFiClass::setSleep(bool enable) {
return false;
}
__attribute__((weak)) bool WiFiClass::getSleep() {
return false;
}
__attribute__((weak)) bool WiFiClass::setTxPower(int power) {
return false;
}
__attribute__((weak)) int WiFiClass::getTxPower() {
return 0;
}
int WiFiClass::hostByName(const char *hostname, IPAddress &aResult) {
aResult = hostByName(hostname);
return true;
}
IPAddress WiFiClass::calculateNetworkID(IPAddress ip, IPAddress subnet) {
IPAddress networkID;
for (size_t i = 0; i < 4; i++)
networkID[i] = subnet[i] & ip[i];
return networkID;
}
IPAddress WiFiClass::calculateBroadcast(IPAddress ip, IPAddress subnet) {
IPAddress broadcastIp;
for (int i = 0; i < 4; i++)
broadcastIp[i] = ~subnet[i] | ip[i];
return broadcastIp;
}
uint8_t WiFiClass::calculateSubnetCIDR(IPAddress subnetMask) {
uint8_t CIDR = 0;
for (uint8_t i = 0; i < 4; i++) {
if (subnetMask[i] == 0x80) // 128
CIDR += 1;
else if (subnetMask[i] == 0xC0) // 192
CIDR += 2;
else if (subnetMask[i] == 0xE0) // 224
CIDR += 3;
else if (subnetMask[i] == 0xF0) // 242
CIDR += 4;
else if (subnetMask[i] == 0xF8) // 248
CIDR += 5;
else if (subnetMask[i] == 0xFC) // 252
CIDR += 6;
else if (subnetMask[i] == 0xFE) // 254
CIDR += 7;
else if (subnetMask[i] == 0xFF) // 255
CIDR += 8;
}
return CIDR;
}
String WiFiClass::macToString(uint8_t *mac) {
char macStr[18]; // 6*2 + 5*':' + '\0'
sprintf(macStr, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
return macStr;
}

View File

@@ -0,0 +1,48 @@
/* Copyright (c) Kuba Szczodrzyński 2022-04-25. */
#include "WiFi.h"
WiFiStatus WiFiClass::begin(char *ssid, char *passphrase, int32_t channel, const uint8_t *bssid, bool connect) {
return begin((const char *)ssid, (const char *)passphrase, channel, bssid, connect);
}
bool WiFiClass::isConnected() {
return status() == WL_CONNECTED;
}
WiFiStatus WiFiClass::waitForConnectResult(unsigned long timeout) {
if ((getMode() & WIFI_MODE_STA) == 0) {
return WL_DISCONNECTED;
}
unsigned long start = millis();
while ((!status() || status() >= WL_DISCONNECTED) && (millis() - start) < timeout) {
delay(100);
}
return status();
}
String WiFiClass::macAddress(void) {
uint8_t mac[6];
macAddress(mac);
return macToString(mac);
}
IPAddress WiFiClass::networkID() {
return calculateNetworkID(gatewayIP(), subnetMask());
}
uint8_t WiFiClass::subnetCIDR() {
return calculateSubnetCIDR(subnetMask());
}
String WiFiClass::BSSIDstr() {
return macToString(BSSID());
}
__attribute__((weak)) bool WiFiClass::enableIpV6() {
return false;
}
__attribute__((weak)) IPv6Address WiFiClass::localIPv6() {
return IPv6Address();
}

View File

@@ -0,0 +1,83 @@
/* Copyright (c) Kuba Szczodrzyński 2022-04-25. */
#include "WiFi.h"
bool WiFiClass::getNetworkInfo(
uint8_t networkItem, String &ssid, WiFiAuthMode &encType, int32_t &rssi, uint8_t *&bssid, int32_t &channel
) {
ssid = SSID(networkItem);
encType = encryptionType(networkItem);
rssi = RSSI(networkItem);
bssid = BSSID(networkItem);
channel = this->channel(networkItem);
return true;
}
int16_t WiFiClass::scanComplete() {
if (!scan)
return 0;
if (scan->running)
return WIFI_SCAN_RUNNING;
return scan->count;
}
void WiFiClass::scanInit() {
if (scan)
return;
scan = (WiFiScanData *)zalloc(sizeof(WiFiScanData));
}
void WiFiClass::scanDelete() {
if (!scan)
return;
for (uint8_t i = 0; i < scan->count; i++) {
free(scan->ap[i].ssid);
}
free(scan->ap);
free(scan);
scan = NULL;
}
uint8_t WiFiClass::scanAlloc(uint8_t count) {
uint8_t last = scan->count;
scan->count = count;
scan->ap = (WiFiScanAP *)realloc(scan->ap, count * sizeof(WiFiScanAP));
if (!scan->ap)
return 255;
memset(scan->ap + last, 0, sizeof(WiFiScanAP));
return last;
}
String WiFiClass::SSID(uint8_t networkItem) {
if (!scan || networkItem >= scan->count)
return "";
return scan->ap[networkItem].ssid;
}
WiFiAuthMode WiFiClass::encryptionType(uint8_t networkItem) {
if (!scan || networkItem >= scan->count)
return WIFI_AUTH_INVALID;
return scan->ap[networkItem].auth;
}
int32_t WiFiClass::RSSI(uint8_t networkItem) {
if (!scan || networkItem >= scan->count)
return 0;
return scan->ap[networkItem].rssi;
}
uint8_t *WiFiClass::BSSID(uint8_t networkItem) {
if (!scan || networkItem >= scan->count)
return NULL;
return scan->ap[networkItem].bssid.addr;
}
String WiFiClass::BSSIDstr(uint8_t networkItem) {
return macToString(BSSID(networkItem));
}
int32_t WiFiClass::channel(uint8_t networkItem) {
if (!scan || networkItem >= scan->count)
return 0;
return scan->ap[networkItem].channel;
}

View File

@@ -0,0 +1,150 @@
/*
ESP8266WiFiType.h - esp8266 Wifi support.
Copyright (c) 2011-2014 Arduino. All right reserved.
Modified by Ivan Grokhotkov, December 2014
Reworked by Markus Sattler, December 2015
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#pragma once
#include <Arduino.h>
#define WIFI_SCAN_RUNNING (-1)
#define WIFI_SCAN_FAILED (-2)
#define WiFiMode_t wifi_mode_t
#define WiFiMode wifi_mode_t
#define WiFiStatus wl_status_t
#define WiFiAuthMode wifi_auth_mode_t
#define WIFI_OFF WIFI_MODE_NULL
#define WIFI_STA WIFI_MODE_STA
#define WIFI_AP WIFI_MODE_AP
#define WIFI_AP_STA WIFI_MODE_APSTA
#define WiFiEvent_t arduino_event_id_t
#define WiFiEventInfo_t arduino_event_info_t
#define WiFiEventId_t uint16_t
typedef struct {
uint8_t addr[6];
} WiFiMacAddr;
struct esp_ip6_addr {
uint32_t addr[4];
uint8_t zone;
};
struct esp_ip4_addr {
uint32_t addr;
};
typedef struct esp_ip4_addr esp_ip4_addr_t;
typedef struct esp_ip6_addr esp_ip6_addr_t;
typedef enum {
WIFI_MODE_NULL = 0, /**< null mode */
WIFI_MODE_STA, /**< WiFi station mode */
WIFI_MODE_AP, /**< WiFi soft-AP mode */
WIFI_MODE_APSTA, /**< WiFi station + soft-AP mode */
WIFI_MODE_MAX
} wifi_mode_t;
typedef enum {
WL_NO_SHIELD = 255, // for compatibility with WiFi Shield library
WL_IDLE_STATUS = 0,
WL_NO_SSID_AVAIL = 1,
WL_SCAN_COMPLETED = 2,
WL_CONNECTED = 3,
WL_CONNECT_FAILED = 4,
WL_CONNECTION_LOST = 5,
WL_DISCONNECTED = 6,
} wl_status_t;
typedef enum {
WIFI_AUTH_OPEN = 0, /**< authenticate mode : open */
WIFI_AUTH_WEP, /**< authenticate mode : WEP */
WIFI_AUTH_WPA_PSK, /**< authenticate mode : WPA_PSK */
WIFI_AUTH_WPA2_PSK, /**< authenticate mode : WPA2_PSK */
WIFI_AUTH_WPA_WPA2_PSK, /**< authenticate mode : WPA_WPA2_PSK */
WIFI_AUTH_WPA2_ENTERPRISE, /**< authenticate mode : WPA2_ENTERPRISE */
WIFI_AUTH_WPA3_PSK, /**< authenticate mode : WPA3_PSK */
WIFI_AUTH_WPA2_WPA3_PSK, /**< authenticate mode : WPA2_WPA3_PSK */
WIFI_AUTH_WAPI_PSK, /**< authenticate mode : WAPI_PSK */
WIFI_AUTH_WPA,
WIFI_AUTH_WPA2,
WIFI_AUTH_AUTO = 200,
WIFI_AUTH_INVALID = 255,
WIFI_AUTH_MAX
} wifi_auth_mode_t;
typedef enum {
WIFI_REASON_UNSPECIFIED = 1,
WIFI_REASON_AUTH_EXPIRE = 2,
WIFI_REASON_AUTH_LEAVE = 3,
WIFI_REASON_ASSOC_EXPIRE = 4,
WIFI_REASON_ASSOC_TOOMANY = 5,
WIFI_REASON_NOT_AUTHED = 6,
WIFI_REASON_NOT_ASSOCED = 7,
WIFI_REASON_ASSOC_LEAVE = 8,
WIFI_REASON_ASSOC_NOT_AUTHED = 9,
WIFI_REASON_DISASSOC_PWRCAP_BAD = 10,
WIFI_REASON_DISASSOC_SUPCHAN_BAD = 11,
WIFI_REASON_BSS_TRANSITION_DISASSOC = 12,
WIFI_REASON_IE_INVALID = 13,
WIFI_REASON_MIC_FAILURE = 14,
WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT = 15,
WIFI_REASON_GROUP_KEY_UPDATE_TIMEOUT = 16,
WIFI_REASON_IE_IN_4WAY_DIFFERS = 17,
WIFI_REASON_GROUP_CIPHER_INVALID = 18,
WIFI_REASON_PAIRWISE_CIPHER_INVALID = 19,
WIFI_REASON_AKMP_INVALID = 20,
WIFI_REASON_UNSUPP_RSN_IE_VERSION = 21,
WIFI_REASON_INVALID_RSN_IE_CAP = 22,
WIFI_REASON_802_1X_AUTH_FAILED = 23,
WIFI_REASON_CIPHER_SUITE_REJECTED = 24,
WIFI_REASON_INVALID_PMKID = 53,
WIFI_REASON_BEACON_TIMEOUT = 200,
WIFI_REASON_NO_AP_FOUND = 201,
WIFI_REASON_AUTH_FAIL = 202,
WIFI_REASON_ASSOC_FAIL = 203,
WIFI_REASON_HANDSHAKE_TIMEOUT = 204,
WIFI_REASON_CONNECTION_FAIL = 205,
WIFI_REASON_AP_TSF_RESET = 206,
WIFI_REASON_ROAMING = 207,
} wifi_err_reason_t;
typedef struct {
char *ssid;
WiFiAuthMode auth;
int32_t rssi;
WiFiMacAddr bssid;
int32_t channel;
} WiFiScanAP;
typedef struct {
bool running = false;
unsigned long timeout = 0;
uint8_t count = 0;
WiFiScanAP *ap = NULL;
} WiFiScanData;
typedef enum {
WLMODE_NONE = 0,
WLMODE_DISABLE = 1,
WLMODE_ENABLE = 2,
} WiFiModeAction;

View File

@@ -0,0 +1,228 @@
/*
FS.cpp - file system wrapper
Copyright (c) 2015 Ivan Grokhotkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "FS.h"
using namespace fs;
size_t File::write(uint8_t c) {
if (!*this) {
return 0;
}
return _p->write(&c, 1);
}
time_t File::getLastWrite() {
if (!*this) {
return 0;
}
return _p->getLastWrite();
}
size_t File::write(const uint8_t *buf, size_t size) {
if (!*this) {
return 0;
}
return _p->write(buf, size);
}
int File::available() {
if (!*this) {
return false;
}
return _p->size() - _p->position();
}
int File::read() {
if (!*this) {
return -1;
}
uint8_t result;
if (_p->read(&result, 1) != 1) {
return -1;
}
return result;
}
size_t File::read(uint8_t *buf, size_t size) {
if (!*this) {
return -1;
}
return _p->read(buf, size);
}
int File::peek() {
if (!*this) {
return -1;
}
size_t curPos = _p->position();
int result = read();
seek(curPos, SeekSet);
return result;
}
void File::flush() {
if (!*this) {
return;
}
_p->flush();
}
bool File::seek(uint32_t pos, SeekMode mode) {
if (!*this) {
return false;
}
return _p->seek(pos, mode);
}
size_t File::position() const {
if (!*this) {
return 0;
}
return _p->position();
}
size_t File::size() const {
if (!*this) {
return 0;
}
return _p->size();
}
bool File::setBufferSize(size_t size) {
if (!*this) {
return 0;
}
return _p->setBufferSize(size);
}
void File::close() {
if (_p) {
_p->close();
_p = nullptr;
}
}
File::operator bool() const {
return _p != nullptr && *_p != false;
}
const char *File::path() const {
if (!*this) {
return nullptr;
}
return _p->path();
}
const char *File::name() const {
if (!*this) {
return nullptr;
}
return _p->name();
}
// to implement
boolean File::isDirectory(void) {
if (!*this) {
return false;
}
return _p->isDirectory();
}
File File::openNextFile(const char *mode) {
if (!*this) {
return File();
}
return _p->openNextFile(mode);
}
void File::rewindDirectory(void) {
if (!*this) {
return;
}
_p->rewindDirectory();
}
File FS::open(const String &path, const char *mode, const bool create) {
return open(path.c_str(), mode, create);
}
File FS::open(const char *path, const char *mode, const bool create) {
if (!_impl) {
return File();
}
return File(_impl->open(path, mode, create));
}
bool FS::exists(const char *path) {
if (!_impl) {
return false;
}
return _impl->exists(path);
}
bool FS::exists(const String &path) {
return exists(path.c_str());
}
bool FS::remove(const char *path) {
if (!_impl) {
return false;
}
return _impl->remove(path);
}
bool FS::remove(const String &path) {
return remove(path.c_str());
}
bool FS::rename(const char *pathFrom, const char *pathTo) {
if (!_impl) {
return false;
}
return _impl->rename(pathFrom, pathTo);
}
bool FS::rename(const String &pathFrom, const String &pathTo) {
return rename(pathFrom.c_str(), pathTo.c_str());
}
bool FS::mkdir(const char *path) {
if (!_impl) {
return false;
}
return _impl->mkdir(path);
}
bool FS::mkdir(const String &path) {
return mkdir(path.c_str());
}
bool FS::rmdir(const char *path) {
if (!_impl) {
return false;
}
return _impl->rmdir(path);
}
bool FS::rmdir(const String &path) {
return rmdir(path.c_str());
}

View File

@@ -0,0 +1,152 @@
/*
FS.h - file system wrapper
Copyright (c) 2015 Ivan Grokhotkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#pragma once
#include <Arduino.h>
#include <memory>
namespace fs {
#define FILE_READ "r"
#define FILE_WRITE "w"
#define FILE_APPEND "a"
class File;
class FileImpl;
typedef std::shared_ptr<FileImpl> FileImplPtr;
class FSImpl;
typedef std::shared_ptr<FSImpl> FSImplPtr;
enum SeekMode { SeekSet = 0, SeekCur = 1, SeekEnd = 2 };
class File : public Stream {
public:
File(FileImplPtr p = FileImplPtr()) : _p(p) {
_timeout = 0;
}
size_t write(uint8_t) override;
size_t write(const uint8_t *buf, size_t size) override;
int available() override;
int read() override;
int peek() override;
void flush() override;
size_t read(uint8_t *buf, size_t size);
size_t readBytes(char *buffer, size_t length) {
return read((uint8_t *)buffer, length);
}
bool seek(uint32_t pos, SeekMode mode);
bool seek(uint32_t pos) {
return seek(pos, SeekSet);
}
size_t position() const;
size_t size() const;
bool setBufferSize(size_t size);
void close();
operator bool() const;
time_t getLastWrite();
const char *path() const;
const char *name() const;
boolean isDirectory(void);
File openNextFile(const char *mode = FILE_READ);
void rewindDirectory(void);
protected:
FileImplPtr _p;
};
class FileImpl {
public:
virtual ~FileImpl() {}
virtual size_t write(const uint8_t *buf, size_t size) = 0;
virtual size_t read(uint8_t *buf, size_t size) = 0;
virtual void flush() = 0;
virtual bool seek(uint32_t pos, SeekMode mode) = 0;
virtual size_t position() const = 0;
virtual size_t size() const = 0;
virtual bool setBufferSize(size_t size) = 0;
virtual void close() = 0;
virtual time_t getLastWrite() = 0;
virtual const char *path() const = 0;
virtual const char *name() const = 0;
virtual boolean isDirectory(void) = 0;
virtual FileImplPtr openNextFile(const char *mode) = 0;
virtual void rewindDirectory(void) = 0;
virtual operator bool() = 0;
};
class FS {
public:
FS(FSImplPtr impl) : _impl(impl) {}
File open(const char *path, const char *mode = FILE_READ, const bool create = false);
File open(const String &path, const char *mode = FILE_READ, const bool create = false);
bool exists(const char *path);
bool exists(const String &path);
bool remove(const char *path);
bool remove(const String &path);
bool rename(const char *pathFrom, const char *pathTo);
bool rename(const String &pathFrom, const String &pathTo);
bool mkdir(const char *path);
bool mkdir(const String &path);
bool rmdir(const char *path);
bool rmdir(const String &path);
protected:
FSImplPtr _impl;
};
class FSImpl {
public:
FSImpl() {}
virtual ~FSImpl() {}
virtual FileImplPtr open(const char *path, const char *mode, const bool create) = 0;
virtual bool exists(const char *path) = 0;
virtual bool rename(const char *pathFrom, const char *pathTo) = 0;
virtual bool remove(const char *path) = 0;
virtual bool mkdir(const char *path) = 0;
virtual bool rmdir(const char *path) = 0;
};
} // namespace fs
#ifndef FS_NO_GLOBALS
using fs::File;
using fs::FS;
using fs::SeekCur;
using fs::SeekEnd;
using fs::SeekMode;
using fs::SeekSet;
#endif // FS_NO_GLOBALS

View File

@@ -0,0 +1,30 @@
/* Copyright (c) Kuba Szczodrzyński 2022-04-24. */
#include "Flash.h"
extern "C" {
#include <fal.h>
}
// Global Flash object.
FlashClass Flash;
FlashId FlashClass::getChipId() {
return LT.getFlashChipId();
}
uint32_t FlashClass::getSize() {
return LT.getFlashChipSize();
}
bool FlashClass::eraseSector(uint32_t offset) {
return fal_partition_erase(fal_root_part, offset, 1) >= 0;
}
bool FlashClass::readBlock(uint32_t offset, uint8_t *data, size_t size) {
return fal_partition_read(fal_root_part, offset, data, size) >= 0;
}
bool FlashClass::writeBlock(uint32_t offset, uint8_t *data, size_t size) {
return fal_partition_write(fal_root_part, offset, data, size) >= 0;
}

View File

@@ -0,0 +1,17 @@
/* Copyright (c) Kuba Szczodrzyński 2022-04-24. */
#pragma once
#include <Arduino.h>
class FlashClass {
public:
FlashId getChipId();
uint32_t getSize();
bool eraseSector(uint32_t offset);
bool readBlock(uint32_t offset, uint8_t *data, size_t size);
bool writeBlock(uint32_t offset, uint8_t *data, size_t size);
};
extern FlashClass Flash;

View File

@@ -0,0 +1,98 @@
/*
IPv6Address.cpp - Base class that provides IPv6Address
Copyright (c) 2011 Adrian McEwen. All right reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "IPv6Address.h"
#include <Arduino.h>
#include <api/Print.h>
IPv6Address::IPv6Address() {
memset(_address.bytes, 0, sizeof(_address.bytes));
}
IPv6Address::IPv6Address(const uint8_t *address) {
memcpy(_address.bytes, address, sizeof(_address.bytes));
}
IPv6Address::IPv6Address(const uint32_t *address) {
memcpy(_address.bytes, (const uint8_t *)address, sizeof(_address.bytes));
}
IPv6Address &IPv6Address::operator=(const uint8_t *address) {
memcpy(_address.bytes, address, sizeof(_address.bytes));
return *this;
}
bool IPv6Address::operator==(const uint8_t *addr) const {
return memcmp(addr, _address.bytes, sizeof(_address.bytes)) == 0;
}
size_t IPv6Address::printTo(Print &p) const {
/* size_t n = 0;
for(int i = 0; i < 16; i+=2) {
if(i){
n += p.print(':');
}
n += p.printf("%02x", _address.bytes[i]);
n += p.printf("%02x", _address.bytes[i+1]);
}
return n; */
}
String IPv6Address::toString() const {
char szRet[40];
sprintf(
szRet,
"%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x",
_address.bytes[0],
_address.bytes[1],
_address.bytes[2],
_address.bytes[3],
_address.bytes[4],
_address.bytes[5],
_address.bytes[6],
_address.bytes[7],
_address.bytes[8],
_address.bytes[9],
_address.bytes[10],
_address.bytes[11],
_address.bytes[12],
_address.bytes[13],
_address.bytes[14],
_address.bytes[15]
);
return String(szRet);
}
bool IPv6Address::fromString(const char *address) {
// format 0011:2233:4455:6677:8899:aabb:ccdd:eeff
if (strlen(address) != 39) {
return false;
}
char *pos = (char *)address;
size_t i = 0;
for (i = 0; i < 16; i += 2) {
if (!sscanf(pos, "%2hhx", &_address.bytes[i]) || !sscanf(pos + 2, "%2hhx", &_address.bytes[i + 1])) {
return false;
}
pos += 5;
}
return true;
}

View File

@@ -0,0 +1,97 @@
/*
IPv6Address.h - Base class that provides IPv6Address
Copyright (c) 2011 Adrian McEwen. All right reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#pragma once
#include <api/Print.h>
#include <api/String.h>
#include <stdint.h>
// A class to make it easier to handle and pass around IP addresses
namespace arduino {
class IPv6Address : public Printable {
private:
union {
uint8_t bytes[16]; // IPv4 address
uint32_t dword[4];
} _address;
// Access the raw byte array containing the address. Because this returns a pointer
// to the internal structure rather than a copy of the address this function should only
// be used when you know that the usage of the returned uint8_t* will be transient and not
// stored.
uint8_t *raw_address() {
return _address.bytes;
}
public:
// Constructors
IPv6Address();
IPv6Address(const uint8_t *address);
IPv6Address(const uint32_t *address);
virtual ~IPv6Address() {}
bool fromString(const char *address);
bool fromString(const String &address) {
return fromString(address.c_str());
}
operator const uint8_t *() const {
return _address.bytes;
}
operator const uint32_t *() const {
return _address.dword;
}
bool operator==(const IPv6Address &addr) const {
return (_address.dword[0] == addr._address.dword[0]) && (_address.dword[1] == addr._address.dword[1]) &&
(_address.dword[2] == addr._address.dword[2]) && (_address.dword[3] == addr._address.dword[3]);
}
bool operator==(const uint8_t *addr) const;
// Overloaded index operator to allow getting and setting individual octets of the address
uint8_t operator[](int index) const {
return _address.bytes[index];
}
uint8_t &operator[](int index) {
return _address.bytes[index];
}
// Overloaded copy operators to allow initialisation of IPv6Address objects from other types
IPv6Address &operator=(const uint8_t *address);
// TODO implement printTo()
virtual size_t printTo(Print &p) const;
String toString() const;
friend class UDP;
friend class Client;
friend class Server;
};
} // namespace arduino
using arduino::IPv6Address;

View File

@@ -0,0 +1,5 @@
/* Copyright (c) Kuba Szczodrzyński 2022-04-30. */
#pragma once
#include "../IPv6Address.h"

View File

@@ -0,0 +1,37 @@
/* Copyright (c) Kuba Szczodrzyński 2022-06-03. */
#pragma once
#include <Arduino.h>
#include <MD5Impl.h>
// available built-in implementations
#if LT_MD5_USE_POLARSSL
#include "MD5PolarSSLImpl.h"
#endif
#if LT_MD5_USE_MBEDTLS
#include "MD5MbedTLSImpl.h"
#endif
#if LT_MD5_USE_HOSTAPD
#include "MD5HostapdImpl.h"
#endif
// common API
#ifdef __cplusplus
extern "C" {
#endif
#ifndef LT_MD5_CTX_T
#define LT_MD5_CTX_T void
#endif
// for compatibility with ESP8266
typedef LT_MD5_CTX_T md5_context_t;
void MD5Init(LT_MD5_CTX_T *context);
void MD5Update(LT_MD5_CTX_T *context, const unsigned char *buf, unsigned len);
void MD5Final(unsigned char digest[16], LT_MD5_CTX_T *context);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,14 @@
/* Copyright (c) Kuba Szczodrzyński 2022-07-12. */
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <crypto/md5_i.h>
#define LT_MD5_CTX_T struct MD5Context
#ifdef __cplusplus
} // extern "C"
#endif

View File

@@ -0,0 +1,28 @@
/* Copyright (c) Kuba Szczodrzyński 2022-07-11. */
#if LT_ARD_HAS_MD5
#include "MD5.h"
#if LT_MD5_USE_MBEDTLS
extern "C" {
void MD5Init(LT_MD5_CTX_T *context) {
mbedtls_md5_init(context);
mbedtls_md5_starts(context);
}
void MD5Update(LT_MD5_CTX_T *context, const unsigned char *buf, unsigned len) {
mbedtls_md5_update(context, buf, len);
}
void MD5Final(unsigned char digest[16], LT_MD5_CTX_T *context) {
mbedtls_md5_finish(context, digest);
}
} // extern "C"
#endif // LT_MD5_USE_MBEDTLS
#endif // LT_ARD_HAS_MD5

View File

@@ -0,0 +1,14 @@
/* Copyright (c) Kuba Szczodrzyński 2022-07-11. */
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <mbedtls/md5.h>
#define LT_MD5_CTX_T mbedtls_md5_context
#ifdef __cplusplus
} // extern "C"
#endif

View File

@@ -0,0 +1,28 @@
/* Copyright (c) Kuba Szczodrzyński 2022-06-03. */
#if LT_ARD_HAS_MD5
#include "MD5.h"
#if LT_MD5_USE_POLARSSL
extern "C" {
void MD5Init(LT_MD5_CTX_T *context) {
md5_init(context);
md5_starts(context);
}
void MD5Update(LT_MD5_CTX_T *context, const unsigned char *buf, unsigned len) {
md5_update(context, buf, len);
}
void MD5Final(unsigned char digest[16], LT_MD5_CTX_T *context) {
md5_finish(context, digest);
}
} // extern "C"
#endif // LT_MD5_USE_POLARSSL
#endif // LT_ARD_HAS_MD5

View File

@@ -0,0 +1,14 @@
/* Copyright (c) Kuba Szczodrzyński 2022-06-03. */
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <polarssl/md5.h>
#define LT_MD5_CTX_T md5_context
#ifdef __cplusplus
} // extern "C"
#endif

View File

@@ -0,0 +1,85 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <api/String.h>
typedef enum {
PT_I8,
PT_U8,
PT_I16,
PT_U16,
PT_I32,
PT_U32,
PT_I64,
PT_U64,
PT_STR,
PT_BLOB,
PT_INVALID,
} PreferenceType;
class IPreferences {
public:
IPreferences() {}
~IPreferences() {}
bool begin(const char *name, bool readOnly = false, const char *partition_label = NULL);
void end();
bool clear();
bool remove(const char *key);
size_t putChar(const char *key, int8_t value);
size_t putUChar(const char *key, uint8_t value);
size_t putShort(const char *key, int16_t value);
size_t putUShort(const char *key, uint16_t value);
size_t putInt(const char *key, int32_t value);
size_t putUInt(const char *key, uint32_t value);
size_t putLong(const char *key, int32_t value);
size_t putULong(const char *key, uint32_t value);
size_t putLong64(const char *key, int64_t value);
size_t putULong64(const char *key, uint64_t value);
size_t putFloat(const char *key, float_t value);
size_t putDouble(const char *key, double_t value);
size_t putBool(const char *key, bool value);
size_t putString(const char *key, const char *value);
size_t putString(const char *key, String value);
size_t putBytes(const char *key, const void *value, size_t len);
bool isKey(const char *key);
PreferenceType getType(const char *key);
int8_t getChar(const char *key, int8_t defaultValue = 0);
uint8_t getUChar(const char *key, uint8_t defaultValue = 0);
int16_t getShort(const char *key, int16_t defaultValue = 0);
uint16_t getUShort(const char *key, uint16_t defaultValue = 0);
int32_t getInt(const char *key, int32_t defaultValue = 0);
uint32_t getUInt(const char *key, uint32_t defaultValue = 0);
int32_t getLong(const char *key, int32_t defaultValue = 0);
uint32_t getULong(const char *key, uint32_t defaultValue = 0);
int64_t getLong64(const char *key, int64_t defaultValue = 0);
uint64_t getULong64(const char *key, uint64_t defaultValue = 0);
float_t getFloat(const char *key, float_t defaultValue = NAN);
double_t getDouble(const char *key, double_t defaultValue = NAN);
bool getBool(const char *key, bool defaultValue = false);
size_t getString(const char *key, char *value, size_t maxLen);
String getString(const char *key, String defaultValue = String());
size_t getBytesLength(const char *key);
size_t getBytes(const char *key, void *buf, size_t maxLen);
size_t freeEntries();
};

View File

@@ -0,0 +1,224 @@
/* Copyright (c) Kuba Szczodrzyński 2022-05-29. */
#include "Update.h"
UpdateClass::UpdateClass() : ctx(NULL), info(NULL), buf(NULL) {
cleanup();
}
/**
* @brief Initialize the update process.
*
* @param size total UF2 file size
* @param command must be U_FLASH
* @return false if parameters are invalid or update is running, true otherwise
*/
bool UpdateClass::begin(size_t size, int command, int unused2, uint8_t unused3, const char *unused4) {
if (ctx)
return false;
cleanup();
LT_DM(OTA, "begin(%u, ...) / OTA curr: %u, trgt: %u", size, LT.otaGetRunning(), LT.otaGetTarget());
ctx = uf2_ctx_init(LT.otaGetTarget(), FAMILY);
info = uf2_info_init();
if (!size) {
cleanup(UPDATE_ERROR_SIZE);
return false;
}
if (command != U_FLASH) {
cleanup(UPDATE_ERROR_BAD_ARGUMENT);
return false;
}
bytesTotal = size;
return true;
}
/**
* @brief Finalize the update process. Check for errors and update completion, then activate the new firmware image.
*
* @param evenIfRemaining no idea
* @return false in case of errors or no update running, true otherwise
*/
bool UpdateClass::end(bool evenIfRemaining) {
if (hasError() || !ctx)
// false if not running
return false;
if (!isFinished() && !evenIfRemaining) {
// abort if not finished
cleanup(UPDATE_ERROR_ABORT);
return false;
}
// TODO what is evenIfRemaining for?
if (!LT.otaSwitch()) {
// try to activate the second OTA
cleanup(UPDATE_ERROR_ACTIVATE);
return false;
}
cleanup();
return true;
}
/**
* @brief Write a chunk of data to the buffer or flash memory.
*
* It's advised to write in 512-byte chunks (or its multiples).
*
* @param data
* @param len
* @return size_t
*/
size_t UpdateClass::write(uint8_t *data, size_t len) {
size_t written = 0;
if (hasError() || !ctx)
// 0 if not running
return 0;
LT_VM(OTA, "write(%u) / buf %u/512", len, bufSize());
/* while (buf == bufPos && len >= UF2_BLOCK_SIZE) {
// buffer empty and entire block is in data
if (!tryWriteData(data, UF2_BLOCK_SIZE)) {
// returns 0 if data contains an invalid block
return written;
}
data += UF2_BLOCK_SIZE;
len -= UF2_BLOCK_SIZE;
written += UF2_BLOCK_SIZE;
} */
// write until buffer space is available
uint16_t toWrite; // 1..512
while (len && (toWrite = min(len, bufLeft()))) {
tryWriteData(data, toWrite);
if (hasError()) {
// return on errors
printErrorContext2(data, toWrite);
return written;
}
data += toWrite;
len -= toWrite;
written += toWrite;
}
return written;
}
size_t UpdateClass::writeStream(Stream &data) {
size_t written = 0;
if (hasError() || !ctx)
// 0 if not running
return 0;
uint32_t lastData = millis();
// loop until the update is complete
while (remaining()) {
// check stream availability
int available = data.available();
if (available <= 0) {
if (millis() - lastData > UPDATE_TIMEOUT_MS) {
// waited for data too long; abort with error
cleanup(UPDATE_ERROR_STREAM);
return written;
}
continue;
}
// available > 0
lastData = millis();
// read data to fit in the remaining buffer space
bufAlloc();
uint16_t read = data.readBytes(bufPos, bufLeft());
bufPos += read;
written += read;
tryWriteData();
if (hasError()) {
// return on errors
printErrorContext2(NULL, read); // buf is not valid anymore
return written;
}
}
return written;
}
/**
* @brief Try to use the buffer as a block to write. In case of UF2 errors,
* error codes are set, the update is aborted and 0 is returned
*
* @param data received data to copy to buffer or NULL if already in buffer
* @param len received data length - must be at most bufLeft()
* @return size_t "used" data size - 0 or 512
*/
size_t UpdateClass::tryWriteData(uint8_t *data, size_t len) {
uf2_block_t *block = NULL;
LT_VM(OTA, "Writing %u to buffer (%u/512)", len, bufSize());
if (len == UF2_BLOCK_SIZE) {
// data has a complete block
block = (uf2_block_t *)data;
} else if (data && len) {
// data has a part of a block, copy it to buffer
bufAlloc();
memcpy(bufPos, data, len);
bufPos += len;
}
if (!block && bufSize() == UF2_BLOCK_SIZE) {
// use buffer as block (only if not found above)
block = (uf2_block_t *)buf;
}
// a complete block has been found
if (block) {
if (checkUf2Error(uf2_check_block(ctx, block)))
// block is invalid
return 0;
if (errUf2 == UF2_ERR_IGNORE)
// treat ignored blocks as valid
return UF2_BLOCK_SIZE;
if (!bytesWritten) {
// parse header block to allow retrieving firmware info
if (checkUf2Error(uf2_parse_header(ctx, block, info)))
// header is invalid
return 0;
LT_IM(OTA, "%s v%s - LT v%s @ %s", info->fw_name, info->fw_version, info->lt_version, info->board);
if (bytesTotal == UPDATE_SIZE_UNKNOWN) {
// set total update size from block count info
bytesTotal = block->block_count * UF2_BLOCK_SIZE;
} else if (bytesTotal != block->block_count * UF2_BLOCK_SIZE) {
// given update size does not match the block count
LT_EM(OTA, "Image size wrong; got %u, calculated %u", bytesTotal, block->block_count * UF2_BLOCK_SIZE);
cleanup(UPDATE_ERROR_SIZE);
return 0;
}
} else {
// write data blocks normally
if (checkUf2Error(uf2_write(ctx, block)))
// block writing failed
return 0;
}
// increment total writing progress
bytesWritten += UF2_BLOCK_SIZE;
// call progress callback
if (callback)
callback(bytesWritten, bytesTotal);
// reset the buffer as it's used already
if (bufSize() == UF2_BLOCK_SIZE)
bufPos = buf;
return UF2_BLOCK_SIZE;
}
return 0;
}
UpdateClass Update;

View File

@@ -0,0 +1,154 @@
#pragma once
#include <Arduino.h>
#include <functional>
#include <uf2ota/uf2ota.h>
// No Error
#define UPDATE_ERROR_OK (0)
// Flash Write Failed
#define UPDATE_ERROR_WRITE (1)
// Flash Erase Failed
#define UPDATE_ERROR_ERASE (2)
// Flash Read Failed
#define UPDATE_ERROR_READ (3)
// Not Enough Space
#define UPDATE_ERROR_SPACE (4)
// Bad Size Given
#define UPDATE_ERROR_SIZE (5)
// Stream Read Timeout
#define UPDATE_ERROR_STREAM (6)
// MD5 Check Failed
#define UPDATE_ERROR_MD5 (7)
// Wrong Magic Byte
#define UPDATE_ERROR_MAGIC_BYTE (8)
// Could Not Activate The Firmware
#define UPDATE_ERROR_ACTIVATE (9)
// Partition Could Not be Found
#define UPDATE_ERROR_NO_PARTITION (10)
// Bad Argument
#define UPDATE_ERROR_BAD_ARGUMENT (11)
// Aborted
#define UPDATE_ERROR_ABORT (12)
#define UPDATE_SIZE_UNKNOWN 0xFFFFFFFF
#define U_FLASH 0
#define U_SPIFFS 100
#define U_AUTH 200
#define ENCRYPTED_BLOCK_SIZE 16
#define UPDATE_TIMEOUT_MS 30 * 1000
class UpdateClass {
public:
typedef std::function<void(size_t, size_t)> THandlerFunction_Progress;
public: /* Update.cpp */
UpdateClass();
bool begin(
size_t size = UPDATE_SIZE_UNKNOWN,
int command = U_FLASH,
int unused2 = -1,
uint8_t unused3 = LOW,
const char *unused4 = NULL // this is for SPIFFS
);
bool end(bool evenIfRemaining = false);
size_t write(uint8_t *data, size_t len);
size_t writeStream(Stream &data);
bool canRollBack();
bool rollBack();
// bool setMD5(const char *expected_md5);
private: /* Update.cpp */
size_t tryWriteData(uint8_t *data = NULL, size_t len = 0);
public: /* UpdateUtil.cpp */
UpdateClass &onProgress(THandlerFunction_Progress callback);
void abort();
void printError(Print &out);
const char *errorString();
const char *getFirmwareName();
const char *getFirmwareVersion();
const char *getLibreTuyaVersion();
const char *getBoardName();
private: /* UpdateUtil.cpp */
void cleanup(uint8_t ardErr = UPDATE_ERROR_OK, uf2_err_t uf2Err = UF2_ERR_OK);
bool checkUf2Error(uf2_err_t err);
void bufAlloc();
void printErrorContext1();
void printErrorContext2(const uint8_t *data, size_t len);
uint16_t bufLeft();
uint16_t bufSize();
private:
// uf2ota context
uf2_ota_t *ctx;
uf2_info_t *info;
// block buffer
uint8_t *buf;
uint8_t *bufPos;
// update progress - multiplies of 512 bytes
uint32_t bytesWritten;
uint32_t bytesTotal;
// errors
uf2_err_t errUf2;
uint8_t errArd;
// progress callback
THandlerFunction_Progress callback;
// String _target_md5;
// MD5Builder _md5;
public:
String md5String(void) {
// return _md5.toString();
}
void md5(uint8_t *result) {
// return _md5.getBytes(result);
}
uint8_t getError() {
return errArd;
}
uf2_err_t getUF2Error() {
return errUf2;
}
uint16_t getErrorCode() {
return (errArd << 8) | errUf2;
}
void clearError() {
cleanup(UPDATE_ERROR_OK);
}
bool hasError() {
return errArd != UPDATE_ERROR_OK;
}
bool isRunning() {
return ctx != NULL;
}
bool isFinished() {
return bytesWritten == bytesTotal;
}
size_t size() {
return bytesTotal;
}
size_t progress() {
return bytesWritten;
}
size_t remaining() {
return bytesTotal - bytesWritten;
}
};
extern UpdateClass Update;

View File

@@ -0,0 +1,190 @@
/* Copyright (c) Kuba Szczodrzyński 2022-05-30. */
#include "Update.h"
static const uint8_t errorMap[] = {
UPDATE_ERROR_OK, /* UF2_ERR_OK - no error */
UPDATE_ERROR_OK, /* UF2_ERR_IGNORE - block should be ignored */
UPDATE_ERROR_MAGIC_BYTE, /* UF2_ERR_MAGIC - wrong magic numbers */
UPDATE_ERROR_BAD_ARGUMENT, /* UF2_ERR_FAMILY - family ID mismatched */
UPDATE_ERROR_BAD_ARGUMENT, /* UF2_ERR_NOT_HEADER - block is not a header */
UPDATE_ERROR_BAD_ARGUMENT, /* UF2_ERR_OTA_VER - unknown/invalid OTA format version */
UPDATE_ERROR_MAGIC_BYTE, /* UF2_ERR_OTA_WRONG - no data for current OTA index */
UPDATE_ERROR_NO_PARTITION, /* UF2_ERR_PART_404 - no partition with that name */
UPDATE_ERROR_BAD_ARGUMENT, /* UF2_ERR_PART_ONE - only one partition tag in a block */
UPDATE_ERROR_BAD_ARGUMENT, /* UF2_ERR_PART_UNSET - attempted to write without target partition */
UPDATE_ERROR_BAD_ARGUMENT, /* UF2_ERR_DATA_TOO_LONG - data too long - tags won't fit */
UPDATE_ERROR_BAD_ARGUMENT, /* UF2_ERR_SEQ_MISMATCH - sequence number mismatched */
UPDATE_ERROR_ERASE, /* UF2_ERR_ERASE_FAILED - erasing flash failed */
UPDATE_ERROR_WRITE, /* UF2_ERR_WRITE_FAILED - writing to flash failed */
UPDATE_ERROR_WRITE /* UF2_ERR_WRITE_LENGTH - wrote fewer data than requested */
};
static char errorStr[14];
/**
* @brief Set the callback invoked after writing data to flash.
*/
UpdateClass &UpdateClass::onProgress(THandlerFunction_Progress callback) {
this->callback = callback;
return *this;
}
void UpdateClass::cleanup(uint8_t ardErr, uf2_err_t uf2Err) {
errUf2 = uf2Err;
errArd = ardErr;
#if LT_DEBUG_OTA
if (hasError())
printErrorContext1();
#endif
free(ctx); // NULL in constructor
ctx = NULL;
uf2_info_free(info); // NULL in constructor
info = NULL;
free(buf); // NULL in constructor
buf = bufPos = NULL;
bytesWritten = 0;
bytesTotal = 0;
}
/**
* @brief Check for UF2 errors. Set errArd and errUf2 in case of errors.
* Ignored blocks are not reported as errors.
* Abort the update.
* Use like: "if (errorUf2(...)) return false;"
* @return true if err is not OK, false otherwise
*/
bool UpdateClass::checkUf2Error(uf2_err_t err) {
if (err <= UF2_ERR_IGNORE)
return false;
cleanup(errorMap[err], err);
return true;
}
/**
* @brief Abort the update with UPDATE_ERROR_ABORT reason.
*/
void UpdateClass::abort() {
LT_DM(OTA, "Aborting update");
cleanup(UPDATE_ERROR_ABORT);
}
void UpdateClass::bufAlloc() {
if (!buf)
buf = bufPos = (uint8_t *)malloc(UF2_BLOCK_SIZE);
}
uint16_t UpdateClass::bufLeft() {
return buf + UF2_BLOCK_SIZE - bufPos;
}
uint16_t UpdateClass::bufSize() {
return bufPos - buf;
}
/**
* @brief Print string error info to the stream.
*/
void UpdateClass::printError(Print &out) {
out.println(errorString());
}
/**
* @brief Print details about the error and current OTA state.
*/
void UpdateClass::printErrorContext1() {
#if LT_DEBUG_OTA
LT_EM(OTA, "Error: %s", errorString());
if (errArd == UPDATE_ERROR_ABORT)
return;
LT_EM(OTA, "- written: %u of %u", bytesWritten, bytesTotal);
LT_EM(OTA, "- buf: size=%u, left=%u", bufSize(), bufLeft());
hexdump(buf, bufSize());
if (ctx)
LT_EM(
OTA,
"- ctx: seq=%u, part1=%s, part2=%s",
ctx->seq - 1, // print last parsed block seq
ctx->part1 ? ctx->part1->name : NULL,
ctx->part2 ? ctx->part2->name : NULL
);
uf2_block_t *block = (uf2_block_t *)buf;
if (buf)
LT_EM(OTA, "- buf: seq=%u/%u, addr=%u, len=%u", block->block_seq, block->block_count, block->addr, block->len);
#endif
}
void UpdateClass::printErrorContext2(const uint8_t *data, size_t len) {
#if LT_DEBUG_OTA
LT_EM(OTA, "- while writing %u bytes", len);
if (data)
hexdump(data, len);
#endif
}
/**
* @brief Get string representation of the error in format
* "ard=..,uf2=..". Returns "" if no error.
*/
const char *UpdateClass::errorString() {
if (!errArd && !errUf2)
return "";
sprintf(errorStr, "ard=%u,uf2=%u", errArd, errUf2);
return errorStr;
}
/**
* @brief Get firmware name from UF2 info.
*/
const char *UpdateClass::getFirmwareName() {
if (info)
return info->fw_name;
return NULL;
}
/**
* @brief Get firmware version from UF2 info.
*/
const char *UpdateClass::getFirmwareVersion() {
if (info)
return info->fw_version;
return NULL;
}
/**
* @brief Get LibreTuya version from UF2 info.
*/
const char *UpdateClass::getLibreTuyaVersion() {
if (info)
return info->lt_version;
return NULL;
}
/**
* @brief Get target board name from UF2 info.
*/
const char *UpdateClass::getBoardName() {
if (info)
return info->board;
return NULL;
}
/**
* @brief See LT.otaCanRollback() for more info.
*/
bool UpdateClass::canRollBack() {
return LT.otaCanRollback();
}
/**
* @brief See LT.otaRollback() for more info.
*/
bool UpdateClass::rollBack() {
return LT.otaRollback();
}

View File

@@ -0,0 +1,391 @@
/* Copyright (c) Kuba Szczodrzyński 2022-04-26. */
#if LT_ARD_HAS_WIFI && LT_HAS_LWIP
#include "LwIPClient.h"
#define MAX_SOCK_NUM 4
#define WIFI_CLIENT_CONNECT_TIMEOUT 3000
#define WIFI_CLIENT_READ_TIMEOUT 3000
#define WIFI_CLIENT_WRITE_RETRY 10
#define WIFI_CLIENT_SELECT_TIMEOUT 1000
#define WIFI_CLIENT_FLUSH_BUF_SIZE 1024
// disable #defines removing lwip_ prefix
#undef LWIP_COMPAT_SOCKETS
#define LWIP_COMPAT_SOCKETS 0
extern "C" {
#include <lwip/api.h>
#include <lwip/dns.h>
#include <lwip/err.h>
#include <lwip/sockets.h>
#include <sys/time.h>
} // extern "C"
class SocketHandle {
public:
int fd;
SocketHandle(int fd) : fd(fd) {}
~SocketHandle() {
lwip_close(fd);
}
};
LwIPClient::LwIPClient() {
LT_VM(CLIENT, "LwIPClient()");
_connected = false;
_sock = NULL;
_rxBuffer = NULL;
_timeout = WIFI_CLIENT_CONNECT_TIMEOUT;
}
LwIPClient::LwIPClient(int sock) {
LT_VM(CLIENT, "LwIPClient(%d)", sock);
_connected = true;
_sock = std::make_shared<SocketHandle>(sock);
_rxBuffer = std::make_shared<LwIPRxBuffer>(sock);
_timeout = WIFI_CLIENT_CONNECT_TIMEOUT;
}
LwIPClient::~LwIPClient() {
LT_VM(CLIENT, "~LwIPClient()");
stop();
}
LwIPClient &LwIPClient::operator=(const LwIPClient &other) {
stop();
_connected = other._connected;
_sock = other._sock;
_rxBuffer = other._rxBuffer;
return *this;
}
bool IWiFiClient::operator==(const IWiFiClient &other) const {
return fd() == other.fd() && remoteIP() == other.remoteIP() && remotePort() == other.remotePort();
}
int LwIPClient::connect(IPAddress ip, uint16_t port) {
return connect(ip, port, _timeout);
}
int LwIPClient::connect(const char *host, uint16_t port) {
return connect(host, port, _timeout);
}
int LwIPClient::connect(const char *host, uint16_t port, int32_t timeout) {
IPAddress ip = WiFi.hostByName(host);
if (!ip)
return 0;
return connect(ip, port, timeout);
}
int LwIPClient::connect(IPAddress ip, uint16_t port, int32_t timeout) {
if (_connected)
stop();
int sock = lwip_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock < 0) {
LT_DM(CLIENT, "socket failed");
return -1;
}
if (timeout <= 0)
timeout = _timeout; // use default when -1 passed as timeout
lwip_fcntl(sock, F_SETFL, lwip_fcntl(sock, F_GETFL, 0) | O_NONBLOCK);
LT_ERRNO();
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = ip;
addr.sin_port = htons(port);
fd_set fdset;
struct timeval tv;
FD_ZERO(&fdset);
FD_SET(sock, &fdset);
tv.tv_sec = 0;
tv.tv_usec = timeout * 1000; // millis -> micros
int res = lwip_connect(sock, (struct sockaddr *)&addr, sizeof(addr));
if (res < 0 && errno != EINPROGRESS) {
LT_EM(CLIENT, "Connect failed; errno=%d", errno);
lwip_close(sock);
return -1;
}
res = lwip_select(sock + 1, NULL, &fdset, NULL, timeout < 0 ? NULL : &tv);
if (res < 0) {
LT_EM(CLIENT, "Select failed; errno=%d", errno);
lwip_close(sock);
return 0;
}
if (res == 0) {
LT_EM(CLIENT, "Select timeout; errno=%d", errno);
lwip_close(sock);
return 0;
}
int sockerr;
socklen_t len = (socklen_t)sizeof(sockerr);
res = lwip_getsockopt(sock, SOL_SOCKET, SO_ERROR, &sockerr, &len);
if (res < 0 || sockerr != 0) {
LT_EM(CLIENT, "Socket error; res=%d, sockerr=%d", res, sockerr);
lwip_close(sock);
return 0;
}
int enable = 1;
lwip_setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
lwip_setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
lwip_setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(enable));
lwip_setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &enable, sizeof(enable));
LT_ERRNO();
lwip_fcntl(sock, F_SETFL, lwip_fcntl(sock, F_GETFL, 0) & ~O_NONBLOCK);
LT_ERRNO();
_connected = true;
_sock = std::make_shared<SocketHandle>(sock);
_rxBuffer = std::make_shared<LwIPRxBuffer>(sock);
return 1;
}
size_t LwIPClient::write(uint8_t data) {
return write(&data, 1);
}
size_t LwIPClient::write(Stream &stream) {
uint8_t *buf = (uint8_t *)malloc(1360);
if (!buf) {
return 0;
}
size_t toRead = 0, toWrite = 0, written = 0;
size_t available = stream.available();
while (available) {
toRead = (available > 1360) ? 1360 : available;
toWrite = stream.readBytes(buf, toRead);
written += write(buf, toWrite);
available = stream.available();
}
free(buf);
return written;
}
size_t LwIPClient::write(const uint8_t *buf, size_t size) {
if (_sock < 0 || !_connected || !size) {
setWriteError();
return 0;
}
int retry = WIFI_CLIENT_WRITE_RETRY;
int written = 0;
while (retry) {
fd_set fdset;
struct timeval tv;
FD_ZERO(&fdset);
FD_SET(fd(), &fdset);
tv.tv_sec = 0;
tv.tv_usec = WIFI_CLIENT_SELECT_TIMEOUT * 1000;
retry--;
if (lwip_select(fd() + 1, NULL, &fdset, NULL, &tv) < 0) {
LT_WM(CLIENT, "Select failed; errno=%d", errno);
return 0;
}
if (FD_ISSET(fd(), &fdset)) {
int res = lwip_send(fd(), buf, size, MSG_DONTWAIT);
if (res > 0) {
written += res;
if (res >= size) {
retry = 0;
} else {
buf += res;
size -= res;
retry = WIFI_CLIENT_WRITE_RETRY;
}
} else if (res < 0 && errno != EAGAIN) {
LT_WM(CLIENT, "Send failed; errno=%d", errno);
setWriteError(res);
_connected = false;
retry = 0;
} else {
// Try again
}
}
}
LT_DM(CLIENT, "wrote %d bytes", written);
return written;
}
int LwIPClient::available() {
if (!_connected || !_rxBuffer)
return 0;
int res = _rxBuffer->available();
if (_rxBuffer->failed()) {
LT_ERRNO();
stop();
}
return res;
}
int LwIPClient::fd() const {
if (!_sock)
return -1;
return _sock->fd;
}
int LwIPClient::socket() {
return fd();
}
int LwIPClient::setTimeout(uint32_t seconds) {
Client::setTimeout(seconds * 1000);
lwip_setsockopt(fd(), SOL_SOCKET, SO_RCVTIMEO, &_timeout, sizeof(_timeout));
return lwip_setsockopt(fd(), SOL_SOCKET, SO_SNDTIMEO, &_timeout, sizeof(_timeout));
}
int LwIPClient::read() {
uint8_t data;
int res = read(&data, 1);
if (res < 0)
return res;
if (res == 0)
return -1;
return data;
}
int LwIPClient::read(uint8_t *buf, size_t size) {
int res = -1;
if (_rxBuffer) {
res = _rxBuffer->read(buf, size);
if (_rxBuffer->failed()) {
stop();
}
}
return res;
}
int LwIPClient::peek() {
int res = -1;
if (_rxBuffer) {
res = _rxBuffer->peek();
if (_rxBuffer->failed()) {
stop();
}
}
return res;
}
void LwIPClient::flush() {
int res;
size_t len = available();
if (!len)
return;
uint8_t *buf = (uint8_t *)malloc(WIFI_CLIENT_FLUSH_BUF_SIZE);
if (!buf)
return;
while (len) {
res = lwip_recv(fd(), buf, LWIP_MIN(len, WIFI_CLIENT_FLUSH_BUF_SIZE), MSG_DONTWAIT);
if (res < 0) {
stop();
break;
}
len -= res;
}
free(buf);
}
void LwIPClient::stop() {
LT_VM(CLIENT, "Stopping TCP");
_connected = false;
_sock = NULL;
_rxBuffer = NULL;
}
uint8_t LwIPClient::connected() {
if (_connected) {
uint8_t dummy;
if (lwip_recv(fd(), &dummy, 0, MSG_DONTWAIT) <= 0) {
switch (errno) {
case EWOULDBLOCK:
case ENOENT: // caused by vfs
case 0:
_connected = true;
break;
case ENOTCONN:
case EPIPE:
case ECONNRESET:
case ECONNREFUSED:
case ECONNABORTED:
LT_IM(CLIENT, "Connection closed; errno=%d", errno);
_connected = false;
break;
default:
LT_WM(CLIENT, "Connection status unknown; errno=%d", errno);
_connected = true;
break;
}
}
}
return _connected;
}
IPAddress __attribute__((noinline)) getaddr(int sock, int (*func)(int, struct sockaddr *, socklen_t *)) {
struct sockaddr addr;
socklen_t len = sizeof(addr);
func(sock, &addr, &len);
struct sockaddr_in *s = (struct sockaddr_in *)&addr;
return IPAddress((uint32_t)(s->sin_addr.s_addr));
}
uint16_t __attribute__((noinline)) getport(int sock, int (*func)(int, struct sockaddr *, socklen_t *)) {
struct sockaddr addr;
socklen_t len = sizeof(addr);
func(sock, &addr, &len);
struct sockaddr_in *s = (struct sockaddr_in *)&addr;
return ntohs(s->sin_port);
}
IPAddress LwIPClient::remoteIP() const {
return getaddr(fd(), lwip_getpeername);
}
IPAddress LwIPClient::remoteIP(int fd) const {
return getaddr(fd, lwip_getpeername);
}
uint16_t LwIPClient::remotePort() const {
return getport(fd(), lwip_getpeername);
}
uint16_t LwIPClient::remotePort(int fd) const {
return getport(fd, lwip_getpeername);
}
IPAddress LwIPClient::localIP() const {
return getaddr(fd(), lwip_getsockname);
}
IPAddress LwIPClient::localIP(int fd) const {
return getaddr(fd, lwip_getsockname);
}
uint16_t LwIPClient::localPort() const {
return getport(fd(), lwip_getsockname);
}
uint16_t LwIPClient::localPort(int fd) const {
return getport(fd, lwip_getsockname);
}
#endif

View File

@@ -0,0 +1,56 @@
/* Copyright (c) Kuba Szczodrzyński 2022-04-26. */
#pragma once
#include <api/WiFi/WiFi.h>
#include <api/WiFiClient.h>
#include <lwip/LwIPRxBuffer.h>
#include <memory>
class SocketHandle;
class LwIPClient : public IWiFiClient {
private:
bool _connected;
std::shared_ptr<SocketHandle> _sock;
std::shared_ptr<LwIPRxBuffer> _rxBuffer;
public:
LwIPClient();
LwIPClient(int sock);
~LwIPClient();
int connect(IPAddress ip, uint16_t port);
int connect(const char *host, uint16_t port);
int connect(IPAddress ip, uint16_t port, int32_t timeout);
int connect(const char *host, uint16_t port, int32_t timeout);
size_t write(uint8_t data);
size_t write(const uint8_t *buf, size_t size);
size_t write(Stream &stream);
int available();
int fd() const;
int socket();
int setTimeout(uint32_t seconds);
int read();
int read(uint8_t *buf, size_t size);
int peek();
void flush();
void stop();
uint8_t connected();
LwIPClient &operator=(const LwIPClient &other);
IPAddress remoteIP() const;
IPAddress remoteIP(int sock) const;
uint16_t remotePort() const;
uint16_t remotePort(int sock) const;
IPAddress localIP() const;
IPAddress localIP(int sock) const;
uint16_t localPort() const;
uint16_t localPort(int sock) const;
using Print::write;
};

View File

@@ -0,0 +1,117 @@
#if LT_HAS_LWIP
#include "LwIPRxBuffer.h"
// disable #defines removing lwip_ prefix
#undef LWIP_COMPAT_SOCKETS
#define LWIP_COMPAT_SOCKETS 0
extern "C" {
#include <lwip/sockets.h>
} // extern "C"
size_t LwIPRxBuffer::r_available() {
if (_sock < 0) {
LT_DM(CLIENT, "_sock < 0");
return 0;
}
int count = 0; // must be of same size as in lwip_ioctl()
int res = lwip_ioctl(_sock, FIONREAD, &count);
if (res < 0) {
LT_DM(CLIENT, "lwip_ioctl()=%d, errno=%d", res, errno);
_failed = true;
return 0;
}
return count;
}
size_t LwIPRxBuffer::fillBuffer() {
if (!_buffer) {
_buffer = (uint8_t *)malloc(_size);
if (!_buffer) {
LT_E("buffer alloc failed");
_failed = true;
return 0;
}
}
if (_fill && _pos == _fill) {
_fill = 0;
_pos = 0;
}
if (!_buffer || _size <= _fill || !r_available()) {
return 0;
}
int res = lwip_recv(_sock, _buffer + _fill, _size - _fill, MSG_DONTWAIT);
if (res < 0) {
if (errno != EWOULDBLOCK) {
LT_ERRNO();
_failed = true;
}
return 0;
}
_fill += res;
return res;
}
LwIPRxBuffer::LwIPRxBuffer(int sock, size_t size)
: _size(size), _buffer(NULL), _pos(0), _fill(0), _sock(sock), _failed(false) {
//_buffer = (uint8_t *)malloc(_size);
}
LwIPRxBuffer::~LwIPRxBuffer() {
free(_buffer);
}
bool LwIPRxBuffer::failed() {
return _failed;
}
int LwIPRxBuffer::read(uint8_t *dst, size_t len) {
if (!dst || !len || (_pos == _fill && !fillBuffer())) {
return _failed ? -1 : 0;
}
size_t a = _fill - _pos;
if (len <= a || ((len - a) <= (_size - _fill) && fillBuffer() >= (len - a))) {
if (len == 1) {
*dst = _buffer[_pos];
} else {
memcpy(dst, _buffer + _pos, len);
}
_pos += len;
return len;
}
size_t left = len;
size_t toRead = a;
uint8_t *buf = dst;
memcpy(buf, _buffer + _pos, toRead);
_pos += toRead;
left -= toRead;
buf += toRead;
while (left) {
if (!fillBuffer()) {
return len - left;
}
a = _fill - _pos;
toRead = (a > left) ? left : a;
memcpy(buf, _buffer + _pos, toRead);
_pos += toRead;
left -= toRead;
buf += toRead;
}
return len;
}
int LwIPRxBuffer::peek() {
if (_pos == _fill && !fillBuffer()) {
return -1;
}
return _buffer[_pos];
}
size_t LwIPRxBuffer::available() {
return _fill - _pos + r_available();
}
#endif

View File

@@ -0,0 +1,24 @@
#pragma once
#include <Arduino.h>
#include <stdlib.h>
class LwIPRxBuffer {
private:
size_t _size;
uint8_t *_buffer;
size_t _pos;
size_t _fill;
int _sock;
bool _failed;
size_t r_available();
size_t fillBuffer();
public:
LwIPRxBuffer(int sock, size_t size = 1436);
~LwIPRxBuffer();
bool failed();
int read(uint8_t *dst, size_t len);
int peek();
size_t available();
};

View File

@@ -0,0 +1,453 @@
/* Copyright (c) Kuba Szczodrzyński 2022-04-30. */
#if LT_ARD_HAS_WIFI && LT_HAS_MBEDTLS
#include "MbedTLSClient.h"
extern "C" {
#include <mbedtls/debug.h>
#include <mbedtls/platform.h>
#include <mbedtls/sha256.h>
#include <mbedtls/ssl.h>
} // extern "C"
MbedTLSClient::MbedTLSClient() : WiFiClient() {
init(); // ensure the context is zero filled
}
MbedTLSClient::MbedTLSClient(int sock) : WiFiClient(sock) {
init(); // ensure the context is zero filled
}
MbedTLSClient::~MbedTLSClient() {
LT_VM(CLIENT, "~MbedTLSClient()");
stop();
}
void MbedTLSClient::stop() {
LT_VM(SSL, "Stopping SSL");
if (_sslCfg.ca_chain) {
mbedtls_x509_crt_free(&_caCert);
}
if (_sslCfg.key_cert) {
mbedtls_x509_crt_free(&_clientCert);
mbedtls_pk_free(&_clientKey);
}
mbedtls_ssl_free(&_sslCtx);
mbedtls_ssl_config_free(&_sslCfg);
LT_HEAP_I();
}
void MbedTLSClient::init() {
// Realtek AmbZ: init platform here to ensure HW crypto is initialized in ssl_init
mbedtls_platform_set_calloc_free(calloc, free);
mbedtls_ssl_init(&_sslCtx);
mbedtls_ssl_config_init(&_sslCfg);
}
int MbedTLSClient::connect(IPAddress ip, uint16_t port, int32_t timeout) {
return connect(ipToString(ip).c_str(), port, timeout);
}
int MbedTLSClient::connect(const char *host, uint16_t port, int32_t timeout) {
if (_pskIdentStr && _pskStr)
return connect(host, port, timeout, NULL, NULL, NULL, _pskIdentStr, _pskStr) == 0;
return connect(host, port, timeout, _caCertStr, _clientCertStr, _clientKeyStr, NULL, NULL) == 0;
}
int MbedTLSClient::connect(
IPAddress ip, uint16_t port, const char *rootCABuf, const char *clientCert, const char *clientKey
) {
return connect(ipToString(ip).c_str(), port, 0, rootCABuf, clientCert, clientKey, NULL, NULL) == 0;
}
int MbedTLSClient::connect(
const char *host, uint16_t port, const char *rootCABuf, const char *clientCert, const char *clientKey
) {
return connect(host, port, 0, rootCABuf, clientCert, clientKey, NULL, NULL) == 0;
}
int MbedTLSClient::connect(IPAddress ip, uint16_t port, const char *pskIdent, const char *psk) {
return connect(ipToString(ip).c_str(), port, 0, NULL, NULL, NULL, pskIdent, psk) == 0;
}
int MbedTLSClient::connect(const char *host, uint16_t port, const char *pskIdent, const char *psk) {
return connect(host, port, 0, NULL, NULL, NULL, pskIdent, psk) == 0;
}
static int ssl_random(void *data, unsigned char *output, size_t len) {
lt_rand_bytes((uint8_t *)output, len);
return 0;
}
void debug_cb(void *ctx, int level, const char *file, int line, const char *str) {
// do not print the trailing \n
uint16_t len = strlen(str);
char *msg = (char *)str;
msg[len - 1] = '\0';
LT_IM(SSL, "%04d: |%d| %s", line, level, msg);
}
int MbedTLSClient::connect(
const char *host,
uint16_t port,
int32_t timeout,
const char *rootCABuf,
const char *clientCert,
const char *clientKey,
const char *pskIdent,
const char *psk
) {
LT_HEAP_I();
if (!rootCABuf && !pskIdent && !psk && !_insecure && !_useRootCA)
return -1;
if (timeout <= 0)
timeout = _timeout; // use default when -1 passed as timeout
IPAddress addr = WiFi.hostByName(host);
if (!(uint32_t)addr)
return -1;
int ret = WiFiClient::connect(addr, port, timeout);
if (ret < 0) {
LT_EM(SSL, "SSL socket failed");
return ret;
}
char *uid = "lt-ssl"; // TODO
LT_VM(SSL, "Init SSL");
init();
LT_HEAP_I();
// mbedtls_debug_set_threshold(4);
// mbedtls_ssl_conf_dbg(&_sslCfg, debug_cb, NULL);
ret = mbedtls_ssl_config_defaults(
&_sslCfg,
MBEDTLS_SSL_IS_CLIENT,
MBEDTLS_SSL_TRANSPORT_STREAM,
MBEDTLS_SSL_PRESET_DEFAULT
);
LT_RET_NZ(ret);
#ifdef MBEDTLS_SSL_ALPN
if (_alpnProtocols) {
ret = mbedtls_ssl_conf_alpn_protocols(&_sslCfg, _alpnProtocols);
LT_RET_NZ(ret);
}
#endif
if (_insecure) {
mbedtls_ssl_conf_authmode(&_sslCfg, MBEDTLS_SSL_VERIFY_NONE);
} else if (rootCABuf) {
mbedtls_x509_crt_init(&_caCert);
mbedtls_ssl_conf_authmode(&_sslCfg, MBEDTLS_SSL_VERIFY_REQUIRED);
ret = mbedtls_x509_crt_parse(&_caCert, (const unsigned char *)rootCABuf, strlen(rootCABuf) + 1);
mbedtls_ssl_conf_ca_chain(&_sslCfg, &_caCert, NULL);
if (ret < 0) {
mbedtls_x509_crt_free(&_caCert);
LT_RET(ret);
}
} else if (_useRootCA) {
return -1; // not implemented
} else if (pskIdent && psk) {
#ifdef MBEDTLS_KEY_EXCHANGE__SOME__PSK_ENABLED
uint16_t len = strlen(psk);
if ((len & 1) != 0 || len > 2 * MBEDTLS_PSK_MAX_LEN) {
LT_EM(SSL, "PSK length invalid");
return -1;
}
unsigned char pskBin[MBEDTLS_PSK_MAX_LEN] = {};
for (uint8_t i = 0; i < len; i++) {
uint8_t c = psk[i];
c |= 0b00100000; // make lowercase
c -= '0' * (c >= '0' && c <= '9');
c -= ('a' - 10) * (c >= 'a' && c <= 'z');
if (c > 0xf)
return -1;
pskBin[i / 2] |= c << (4 * ((i & 1) ^ 1));
}
ret = mbedtls_ssl_conf_psk(&_sslCfg, pskBin, len / 2, (const unsigned char *)pskIdent, strlen(pskIdent));
LT_RET_NZ(ret);
#else
return -1;
#endif
} else {
return -1;
}
if (!_insecure && clientCert && clientKey) {
mbedtls_x509_crt_init(&_clientCert);
mbedtls_pk_init(&_clientKey);
LT_VM(SSL, "Loading client cert");
ret = mbedtls_x509_crt_parse(&_clientCert, (const unsigned char *)clientCert, strlen(clientCert) + 1);
if (ret < 0) {
mbedtls_x509_crt_free(&_clientCert);
LT_RET(ret);
}
LT_VM(SSL, "Loading private key");
ret = mbedtls_pk_parse_key(&_clientKey, (const unsigned char *)clientKey, strlen(clientKey) + 1, NULL, 0);
if (ret < 0) {
mbedtls_x509_crt_free(&_clientCert);
LT_RET(ret);
}
mbedtls_ssl_conf_own_cert(&_sslCfg, &_clientCert, &_clientKey);
}
LT_VM(SSL, "Setting TLS hostname");
ret = mbedtls_ssl_set_hostname(&_sslCtx, host);
LT_RET_NZ(ret);
mbedtls_ssl_conf_rng(&_sslCfg, ssl_random, NULL);
ret = mbedtls_ssl_setup(&_sslCtx, &_sslCfg);
LT_RET_NZ(ret);
_sockTls = fd();
mbedtls_ssl_set_bio(&_sslCtx, &_sockTls, mbedtls_net_send, mbedtls_net_recv, NULL);
mbedtls_net_set_nonblock((mbedtls_net_context *)&_sockTls);
LT_HEAP_I();
LT_VM(SSL, "SSL handshake");
if (_handshakeTimeout == 0)
_handshakeTimeout = timeout;
unsigned long start = millis();
while (ret = mbedtls_ssl_handshake(&_sslCtx)) {
if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
LT_RET(ret);
}
if ((millis() - start) > _handshakeTimeout) {
LT_EM(SSL, "SSL handshake timeout");
return -1;
}
delay(2);
}
LT_HEAP_I();
if (clientCert && clientKey) {
LT_DM(
SSL,
"Protocol %s, ciphersuite %s",
mbedtls_ssl_get_version(&_sslCtx),
mbedtls_ssl_get_ciphersuite(&_sslCtx)
);
ret = mbedtls_ssl_get_record_expansion(&_sslCtx);
if (ret >= 0)
LT_DM(SSL, "Record expansion: %d", ret);
else {
LT_WM(SSL, "Record expansion unknown");
}
}
LT_VM(SSL, "Verifying certificate");
ret = mbedtls_ssl_get_verify_result(&_sslCtx);
if (ret) {
char buf[512];
memset(buf, 0, sizeof(buf));
mbedtls_x509_crt_verify_info(buf, sizeof(buf), " ! ", ret);
LT_EM(SSL, "Failed to verify peer certificate! Verification info: %s", buf);
return ret;
}
if (rootCABuf)
mbedtls_x509_crt_free(&_caCert);
if (clientCert)
mbedtls_x509_crt_free(&_clientCert);
if (clientKey != NULL)
mbedtls_pk_free(&_clientKey);
return 0; // OK
}
size_t MbedTLSClient::write(const uint8_t *buf, size_t size) {
int ret = -1;
while ((ret = mbedtls_ssl_write(&_sslCtx, buf, size)) <= 0) {
if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE && ret < 0) {
LT_RET(ret);
}
delay(2);
}
return ret;
}
int MbedTLSClient::available() {
bool peeked = _peeked >= 0;
if (!connected())
return peeked;
int ret = mbedtls_ssl_read(&_sslCtx, NULL, 0);
if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE && ret < 0) {
stop();
return peeked ? peeked : ret;
}
return mbedtls_ssl_get_bytes_avail(&_sslCtx) + peeked;
}
int MbedTLSClient::read(uint8_t *buf, size_t size) {
bool peeked = false;
int toRead = available();
if ((!buf && size) || toRead <= 0)
return -1;
if (!size)
return 0;
if (_peeked >= 0) {
buf[0] = _peeked;
_peeked = -1;
size--;
toRead--;
if (!size || !toRead)
return 1;
buf++;
peeked = true;
}
int ret = mbedtls_ssl_read(&_sslCtx, buf, size);
if (ret < 0) {
stop();
return peeked ? peeked : ret;
}
return ret + peeked;
}
int MbedTLSClient::peek() {
if (_peeked >= 0)
return _peeked;
_peeked = timedRead();
return _peeked;
}
void MbedTLSClient::flush() {}
int MbedTLSClient::lastError(char *buf, const size_t size) {
return 0; // TODO (?)
}
void MbedTLSClient::setInsecure() {
_caCertStr = NULL;
_clientCertStr = NULL;
_clientKeyStr = NULL;
_pskIdentStr = NULL;
_pskStr = NULL;
_insecure = true;
}
void MbedTLSClient::setPreSharedKey(const char *pskIdent, const char *psk) {
_pskIdentStr = pskIdent;
_pskStr = psk;
}
void MbedTLSClient::setCACert(const char *rootCA) {
_caCertStr = rootCA;
}
void MbedTLSClient::setCertificate(const char *clientCA) {
_clientCertStr = clientCA;
}
void MbedTLSClient::setPrivateKey(const char *privateKey) {
_clientKeyStr = privateKey;
}
char *streamToStr(Stream &stream, size_t size) {
char *buf = (char *)malloc(size + 1);
if (!buf)
return NULL;
if (size != stream.readBytes(buf, size)) {
free(buf);
return NULL;
}
buf[size] = '\0';
return buf;
}
bool MbedTLSClient::loadCACert(Stream &stream, size_t size) {
char *str = streamToStr(stream, size);
if (str) {
_caCertStr = str;
return true;
}
return false;
}
bool MbedTLSClient::loadCertificate(Stream &stream, size_t size) {
char *str = streamToStr(stream, size);
if (str) {
_clientCertStr = str;
return true;
}
return false;
}
bool MbedTLSClient::loadPrivateKey(Stream &stream, size_t size) {
char *str = streamToStr(stream, size);
if (str) {
_clientKeyStr = str;
return true;
}
return false;
}
bool MbedTLSClient::verify(const char *fingerprint, const char *domainName) {
uint8_t fpLocal[32] = {};
uint16_t len = strlen(fingerprint);
uint8_t byte = 0;
for (uint8_t i = 0; i < len; i++) {
uint8_t c = fingerprint[i];
while ((c == ' ' || c == ':') && i < len) {
c = fingerprint[++i];
}
c |= 0b00100000; // make lowercase
c -= '0' * (c >= '0' && c <= '9');
c -= ('a' - 10) * (c >= 'a' && c <= 'z');
if (c > 0xf)
return -1;
fpLocal[byte / 2] |= c << (4 * ((byte & 1) ^ 1));
byte++;
if (byte >= 64)
break;
}
uint8_t fpRemote[32];
if (!getFingerprintSHA256(fpRemote))
return false;
if (memcmp(fpLocal, fpRemote, 32)) {
LT_DM(SSL, "Fingerprints don't match");
return false;
}
if (!domainName)
return true;
// TODO domain name verification
return true;
}
void MbedTLSClient::setHandshakeTimeout(unsigned long handshakeTimeout) {
_handshakeTimeout = handshakeTimeout * 1000;
}
void MbedTLSClient::setAlpnProtocols(const char **alpnProtocols) {
_alpnProtocols = alpnProtocols;
}
bool MbedTLSClient::getFingerprintSHA256(uint8_t result[32]) {
const mbedtls_x509_crt *cert = mbedtls_ssl_get_peer_cert(&_sslCtx);
if (!cert) {
LT_EM(SSL, "Failed to get peer certificate");
return false;
}
mbedtls_sha256_context shaCtx;
mbedtls_sha256_init(&shaCtx);
mbedtls_sha256_starts(&shaCtx, false);
mbedtls_sha256_update(&shaCtx, cert->raw.p, cert->raw.len);
mbedtls_sha256_finish(&shaCtx, result);
return true;
}
#endif // LT_ARD_HAS_WIFI && LT_HAS_MBEDTLS

View File

@@ -0,0 +1,88 @@
/* Copyright (c) Kuba Szczodrzyński 2022-04-30. */
#pragma once
#include <api/WiFi/WiFi.h>
#include <api/WiFiClient.h>
#include <api/WiFiClientSecure.h>
#include <WiFiClient.h> // extend family's WiFiClient impl
extern "C" {
#include <mbedtls/net.h>
} // extern "C"
class MbedTLSClient : public WiFiClient, public IWiFiClientSecure {
private:
mbedtls_ssl_context _sslCtx;
mbedtls_ssl_config _sslCfg;
mbedtls_x509_crt _caCert;
mbedtls_x509_crt _clientCert;
mbedtls_pk_context _clientKey;
uint32_t _handshakeTimeout = 0;
void init();
int _sockTls = -1;
bool _insecure = false;
bool _useRootCA = false;
int _peeked = -1;
const char *_caCertStr;
const char *_clientCertStr;
const char *_clientKeyStr;
const char *_pskIdentStr;
const char *_pskStr;
const char **_alpnProtocols;
int connect(
const char *host,
uint16_t port,
int32_t timeout,
const char *rootCABuf,
const char *clientCert,
const char *clientKey,
const char *pskIdent,
const char *psk
);
public:
MbedTLSClient();
MbedTLSClient(int sock);
~MbedTLSClient();
int connect(IPAddress ip, uint16_t port, int32_t timeout);
int connect(const char *host, uint16_t port, int32_t timeout);
int connect(IPAddress ip, uint16_t port, const char *rootCABuf, const char *clientCert, const char *clientKey);
int connect(const char *host, uint16_t port, const char *rootCABuf, const char *clientCert, const char *clientKey);
int connect(IPAddress ip, uint16_t port, const char *pskIdent, const char *psk);
int connect(const char *host, uint16_t port, const char *pskIdent, const char *psk);
size_t write(const uint8_t *buf, size_t size);
int available();
int read(uint8_t *buf, size_t size);
int peek();
void flush();
void stop();
int lastError(char *buf, const size_t size);
void setInsecure(); // Don't validate the chain, just accept whatever is given. VERY INSECURE!
void setPreSharedKey(const char *pskIdent, const char *psk); // psk in hex
void setCACert(const char *rootCA);
void setCertificate(const char *clientCA);
void setPrivateKey(const char *privateKey);
bool loadCACert(Stream &stream, size_t size);
bool loadCertificate(Stream &stream, size_t size);
bool loadPrivateKey(Stream &stream, size_t size);
bool verify(const char *fingerprint, const char *domainName);
void setHandshakeTimeout(unsigned long handshakeTimeout);
void setAlpnProtocols(const char **alpnProtocols);
bool getFingerprintSHA256(uint8_t result[32]);
using WiFiClient::connect;
using WiFiClient::read;
};

View File

@@ -0,0 +1,74 @@
/*
Client.h - Base class that provides Client
Copyright (c) 2011 Adrian McEwen. All right reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#pragma once
#include <Arduino.h>
#include <api/Client.h>
class IWiFiClient : public Client {
public:
IWiFiClient() {}
IWiFiClient(int sock) {}
~IWiFiClient() {}
virtual int connect(IPAddress ip, uint16_t port, int32_t timeout) = 0;
virtual int connect(const char *host, uint16_t port, int32_t timeout) = 0;
virtual size_t write(Stream &stream) = 0;
size_t write_P(PGM_P buffer, size_t size) {
return write((const uint8_t *)buffer, size);
}
virtual int fd() const = 0;
virtual int socket() = 0;
virtual int setTimeout(uint32_t seconds) = 0;
bool operator==(const IWiFiClient &other) const;
operator bool() {
return connected();
}
virtual bool operator==(const bool value) {
return bool() == value;
}
virtual bool operator!=(const bool value) {
return bool() != value;
}
virtual bool operator!=(const IWiFiClient &other) {
return !this->operator==(other);
};
virtual IPAddress remoteIP() const = 0;
virtual IPAddress remoteIP(int sock) const = 0;
virtual uint16_t remotePort() const = 0;
virtual uint16_t remotePort(int sock) const = 0;
virtual IPAddress localIP() const = 0;
virtual IPAddress localIP(int sock) const = 0;
virtual uint16_t localPort() const = 0;
virtual uint16_t localPort(int sock) const = 0;
using Print::write;
};

View File

@@ -0,0 +1,49 @@
/*
WiFiClientSecure.h - Base class that provides Client SSL to ESP32
Copyright (c) 2011 Adrian McEwen. All right reserved.
Additions Copyright (C) 2017 Evandro Luis Copercini.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#pragma once
#include <Arduino.h>
#include "WiFiClient.h"
class IWiFiClientSecure {
public:
virtual int
connect(IPAddress ip, uint16_t port, const char *rootCABuf, const char *clientCert, const char *clientKey) = 0;
virtual int
connect(const char *host, uint16_t port, const char *rootCABuf, const char *clientCert, const char *clientKey) = 0;
virtual int connect(IPAddress ip, uint16_t port, const char *pskIdent, const char *psk) = 0;
virtual int connect(const char *host, uint16_t port, const char *pskIdent, const char *psk) = 0;
virtual int lastError(char *buf, const size_t size) = 0;
virtual void setInsecure() = 0; // Don't validate the chain, just accept whatever is given. VERY INSECURE!
virtual void setPreSharedKey(const char *pskIdent, const char *psk) = 0; // psk in hex
virtual void setCACert(const char *rootCA) = 0;
virtual void setCertificate(const char *clientCA) = 0;
virtual void setPrivateKey(const char *privateKey) = 0;
virtual bool loadCACert(Stream &stream, size_t size) = 0;
virtual bool loadCertificate(Stream &stream, size_t size) = 0;
virtual bool loadPrivateKey(Stream &stream, size_t size) = 0;
virtual bool verify(const char *fingerprint, const char *domainName) = 0;
virtual void setHandshakeTimeout(unsigned long handshakeTimeout) = 0;
virtual void setAlpnProtocols(const char **alpnProtocols) = 0;
virtual bool getFingerprintSHA256(uint8_t result[32]) = 0;
};

View File

@@ -0,0 +1,142 @@
/* Copyright (c) Kuba Szczodrzyński 2022-04-26. */
#if LT_ARD_HAS_WIFI && LT_HAS_LWIP
#include "LwIPServer.h"
// disable #defines removing lwip_ prefix
#undef LWIP_COMPAT_SOCKETS
#define LWIP_COMPAT_SOCKETS 0
extern "C" {
#include <lwip/api.h>
// #include <lwip/dns.h>
#include <lwip/err.h>
#include <lwip/sockets.h>
#include <sys/time.h>
}
LwIPServer::LwIPServer(uint32_t addr, uint16_t port, uint8_t maxClients)
: _sock(-1), _sockAccepted(-1), _addr(addr), _port(port), _maxClients(maxClients), _active(false), _noDelay(false) {
}
LwIPServer::operator bool() {
return _active;
}
bool LwIPServer::begin(uint16_t port, bool reuseAddr) {
if (_active)
return true;
if (port)
_port = port;
_sock = lwip_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (_sock < 0) {
LT_EM(SERVER, "Socket failed; errno=%d", errno);
return false;
}
int enable = reuseAddr;
lwip_setsockopt(_sock, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable));
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = _addr;
addr.sin_port = htons(_port);
if (lwip_bind(_sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
LT_EM(SERVER, "Bind failed; errno=%d", errno);
return false;
}
if (lwip_listen(_sock, _maxClients) < 0) {
LT_EM(SERVER, "Bind failed; errno=%d", errno);
return false;
}
uint8_t *addrB = (uint8_t *)&_addr;
LT_IM(SERVER, "Server running on %hhu.%hhu.%hhu.%hhu:%hu", addrB[0], addrB[1], addrB[2], addrB[3], _port);
lwip_fcntl(_sock, F_SETFL, O_NONBLOCK);
_active = true;
_noDelay = false;
_sockAccepted = -1;
}
void LwIPServer::end() {
if (_sock == -1)
return;
lwip_close(_sock);
_sock = -1;
_active = -1;
}
WiFiClient LwIPServer::accept() {
if (!_active)
return WiFiClient();
int sock;
if (_sockAccepted >= 0) {
sock = _sockAccepted;
_sockAccepted = -1;
} else {
struct sockaddr_in addr;
socklen_t len = sizeof(addr);
sock = lwip_accept(_sock, (struct sockaddr *)&addr, &len);
}
if (sock >= 0) {
int enable = 1;
if (lwip_setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &enable, sizeof(enable)) == ERR_OK) {
enable = _noDelay;
if (lwip_setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(enable)) == ERR_OK) {
// HOTFIX: allow the TCP thread to receive data
// I'm not sure what's happening there, so this should probably be fixed properly.
// When a connection arrives, sometimes TCP hasn't received anything yet. This causes
// calling WiFiClient::connected() check for lwip_recv(), which returns EWOULDBLOCK
// as the client is still connected. The problem is that there's basically an infinite loop
// created: nowhere in that code is a yield()/delay() that would allow TCP thread to work
// and receive data, so LwIP still sees a connected client that sends nothing. At least
// that's what I understand. And any loop that doesn't call delay() seems to block the TCP
// stack completely and prevents it from even being pinged.
LT_DM(SERVER, "Got client");
delay(5);
return WiFiClient(sock);
}
}
}
return WiFiClient();
}
int LwIPServer::setTimeout(uint32_t seconds) {
struct timeval tv;
tv.tv_sec = seconds;
tv.tv_usec = 0;
if (lwip_setsockopt(_sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0)
return -1;
return lwip_setsockopt(_sock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
}
void LwIPServer::setNoDelay(bool noDelay) {
_noDelay = noDelay;
}
bool LwIPServer::getNoDelay() {
return _noDelay;
}
bool LwIPServer::hasClient() {
if (_sockAccepted >= 0) {
return true;
}
struct sockaddr_in addr;
socklen_t len = sizeof(addr);
_sockAccepted = lwip_accept(_sock, (struct sockaddr *)&addr, &len);
if (_sockAccepted >= 0) {
return true;
}
return false;
}
#endif

View File

@@ -0,0 +1,47 @@
/* Copyright (c) Kuba Szczodrzyński 2022-04-26. */
#pragma once
#include <api/WiFi/WiFi.h>
#include <api/WiFiServer.h>
#include <WiFiClient.h>
class LwIPServer : public IWiFiServer<WiFiClient> {
private:
int _sock;
int _sockAccepted;
uint32_t _addr;
uint16_t _port;
uint8_t _maxClients;
bool _active;
bool _noDelay = false;
private:
LwIPServer(uint32_t addr, uint16_t port = 80, uint8_t maxClients = 4);
public:
LwIPServer(uint16_t port = 80, uint8_t maxClients = 4) : LwIPServer((uint32_t)0, port, maxClients) {}
LwIPServer(int port = 80, uint8_t maxClients = 4) : LwIPServer((uint32_t)0, port, maxClients) {}
LwIPServer(const IPAddress &addr, uint16_t port = 80, uint8_t maxClients = 4)
: LwIPServer((uint32_t)addr, port, maxClients) {}
operator bool();
bool begin(uint16_t port = 0, bool reuseAddr = true);
void end();
WiFiClient accept();
size_t write(const uint8_t *buffer, size_t size) {
return 0;
}
void stopAll() {}
int setTimeout(uint32_t seconds);
void setNoDelay(bool noDelay);
bool getNoDelay();
bool hasClient();
};

View File

@@ -0,0 +1,72 @@
/*
Server.h - Server class for Raspberry Pi
Copyright (c) 2016 Hristo Gochkov All right reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#pragma once
#include <Arduino.h>
#include <api/Print.h>
#include "WiFiClient.h"
#include <type_traits>
template <typename TWiFiClient, typename = std::enable_if<std::is_base_of<IWiFiClient, TWiFiClient>::value>>
class IWiFiServer : public Print { // arduino::Server is useless anyway
public:
void listenOnLocalhost() {}
IWiFiServer(uint16_t port = 80, uint8_t maxClients = 4) {}
IWiFiServer(const IPAddress &addr, uint16_t port = 80, uint8_t maxClients = 4) {}
~IWiFiServer() {
stop();
}
TWiFiClient available() {
return accept();
};
virtual operator bool() = 0;
virtual bool begin(uint16_t port = 0, bool reuseAddr = true) = 0;
virtual void end() = 0;
virtual TWiFiClient accept() = 0;
void close() {
end();
}
void stop() {
end();
}
virtual int setTimeout(uint32_t seconds) = 0;
virtual void stopAll() = 0;
virtual void setNoDelay(bool noDelay) = 0;
virtual bool getNoDelay() = 0;
virtual bool hasClient() = 0;
size_t write(uint8_t data) {
return write(&data, 1);
}
using Print::write;
};

View File

@@ -0,0 +1,286 @@
/*
Udp.cpp - UDP class for Raspberry Pi
Copyright (c) 2016 Hristo Gochkov All right reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#if LT_ARD_HAS_WIFI && LT_HAS_LWIP
#include "LwIPUdp.h"
#include <errno.h>
extern "C" {
#include <lwip/netdb.h>
#include <lwip/sockets.h>
} // extern "C"
#undef write
#undef read
LwIPUDP::LwIPUDP() : udp_server(-1), server_port(0), remote_port(0), tx_buffer(0), tx_buffer_len(0), rx_buffer(0) {}
LwIPUDP::~LwIPUDP() {
stop();
}
uint8_t LwIPUDP::begin(IPAddress address, uint16_t port) {
stop();
server_port = port;
tx_buffer = new char[1460];
if (!tx_buffer) {
log_e("could not create tx buffer: %d", errno);
return 0;
}
if ((udp_server = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
log_e("could not create socket: %d", errno);
return 0;
}
int yes = 1;
if (setsockopt(udp_server, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0) {
log_e("could not set socket option: %d", errno);
stop();
return 0;
}
struct sockaddr_in addr;
memset((char *)&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(server_port);
addr.sin_addr.s_addr = (in_addr_t)address;
if (bind(udp_server, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
log_e("could not bind socket: %d", errno);
stop();
return 0;
}
fcntl(udp_server, F_SETFL, O_NONBLOCK);
return 1;
}
uint8_t LwIPUDP::begin(uint16_t p) {
return begin(IPAddress(INADDR_ANY), p);
}
uint8_t LwIPUDP::beginMulticast(IPAddress a, uint16_t p) {
if (begin(IPAddress(INADDR_ANY), p)) {
if ((uint32_t)a != 0) {
struct ip_mreq mreq;
mreq.imr_multiaddr.s_addr = (in_addr_t)a;
mreq.imr_interface.s_addr = INADDR_ANY;
if (setsockopt(udp_server, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) {
log_e("could not join igmp: %d", errno);
stop();
return 0;
}
multicast_ip = a;
}
return 1;
}
return 0;
}
void LwIPUDP::stop() {
if (tx_buffer) {
delete[] tx_buffer;
tx_buffer = NULL;
}
tx_buffer_len = 0;
if (rx_buffer) {
cbuf *b = rx_buffer;
rx_buffer = NULL;
delete b;
}
if (udp_server == -1)
return;
if ((uint32_t)multicast_ip != 0) {
struct ip_mreq mreq;
mreq.imr_multiaddr.s_addr = (in_addr_t)multicast_ip;
mreq.imr_interface.s_addr = (in_addr_t)0;
setsockopt(udp_server, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq));
multicast_ip = IPAddress(INADDR_ANY);
}
close(udp_server);
udp_server = -1;
}
int LwIPUDP::beginMulticastPacket() {
if (!server_port || multicast_ip == IPAddress(INADDR_ANY))
return 0;
remote_ip = multicast_ip;
remote_port = server_port;
return beginPacket();
}
int LwIPUDP::beginPacket() {
if (!remote_port)
return 0;
// allocate tx_buffer if is necessary
if (!tx_buffer) {
tx_buffer = new char[1460];
if (!tx_buffer) {
log_e("could not create tx buffer: %d", errno);
return 0;
}
}
tx_buffer_len = 0;
// check whereas socket is already open
if (udp_server != -1)
return 1;
if ((udp_server = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
log_e("could not create socket: %d", errno);
return 0;
}
fcntl(udp_server, F_SETFL, O_NONBLOCK);
return 1;
}
int LwIPUDP::beginPacket(IPAddress ip, uint16_t port) {
remote_ip = ip;
remote_port = port;
return beginPacket();
}
int LwIPUDP::beginPacket(const char *host, uint16_t port) {
struct hostent *server;
server = gethostbyname(host);
if (server == NULL) {
log_e("could not get host from dns: %d", errno);
return 0;
}
return beginPacket(IPAddress((const uint8_t *)(server->h_addr_list[0])), port);
}
int LwIPUDP::endPacket() {
struct sockaddr_in recipient;
recipient.sin_addr.s_addr = (uint32_t)remote_ip;
recipient.sin_family = AF_INET;
recipient.sin_port = htons(remote_port);
int sent = sendto(udp_server, tx_buffer, tx_buffer_len, 0, (struct sockaddr *)&recipient, sizeof(recipient));
if (sent < 0) {
log_e("could not send data: %d", errno);
return 0;
}
return 1;
}
size_t LwIPUDP::write(uint8_t data) {
if (tx_buffer_len == 1460) {
endPacket();
tx_buffer_len = 0;
}
tx_buffer[tx_buffer_len++] = data;
return 1;
}
size_t LwIPUDP::write(const uint8_t *buffer, size_t size) {
size_t i;
for (i = 0; i < size; i++)
write(buffer[i]);
return i;
}
int LwIPUDP::parsePacket() {
if (rx_buffer)
return 0;
struct sockaddr_in si_other;
int slen = sizeof(si_other), len;
char *buf = new char[1460];
if (!buf) {
return 0;
}
if ((len = recvfrom(udp_server, buf, 1460, MSG_DONTWAIT, (struct sockaddr *)&si_other, (socklen_t *)&slen)) == -1) {
delete[] buf;
if (errno == EWOULDBLOCK) {
return 0;
}
log_e("could not receive data: %d", errno);
return 0;
}
remote_ip = IPAddress(si_other.sin_addr.s_addr);
remote_port = ntohs(si_other.sin_port);
if (len > 0) {
rx_buffer = new cbuf(len);
rx_buffer->write(buf, len);
}
delete[] buf;
return len;
}
int LwIPUDP::available() {
if (!rx_buffer)
return 0;
return rx_buffer->available();
}
int LwIPUDP::read() {
if (!rx_buffer)
return -1;
int out = rx_buffer->read();
if (!rx_buffer->available()) {
cbuf *b = rx_buffer;
rx_buffer = 0;
delete b;
}
return out;
}
int LwIPUDP::read(unsigned char *buffer, size_t len) {
return read((char *)buffer, len);
}
int LwIPUDP::read(char *buffer, size_t len) {
if (!rx_buffer)
return 0;
int out = rx_buffer->read(buffer, len);
if (!rx_buffer->available()) {
cbuf *b = rx_buffer;
rx_buffer = 0;
delete b;
}
return out;
}
int LwIPUDP::peek() {
if (!rx_buffer)
return -1;
return rx_buffer->peek();
}
void LwIPUDP::flush() {
if (!rx_buffer)
return;
cbuf *b = rx_buffer;
rx_buffer = 0;
delete b;
}
IPAddress LwIPUDP::remoteIP() {
return remote_ip;
}
uint16_t LwIPUDP::remotePort() {
return remote_port;
}
#endif

View File

@@ -0,0 +1,43 @@
/* Copyright (c) Kuba Szczodrzyński 2022-09-10. */
#pragma once
#include <api/WiFi/WiFi.h>
#include <api/WiFiUdp.h>
#include <cbuf.h>
class LwIPUDP : public IWiFiUDP {
private:
int udp_server;
IPAddress multicast_ip;
IPAddress remote_ip;
uint16_t server_port;
uint16_t remote_port;
char *tx_buffer;
size_t tx_buffer_len;
cbuf *rx_buffer;
public:
LwIPUDP();
~LwIPUDP();
uint8_t begin(IPAddress ip, uint16_t port);
uint8_t begin(uint16_t port);
uint8_t beginMulticast(IPAddress ip, uint16_t port);
void stop();
int beginMulticastPacket();
int beginPacket();
int beginPacket(IPAddress ip, uint16_t port);
int beginPacket(const char *host, uint16_t port);
int endPacket();
size_t write(uint8_t);
size_t write(const uint8_t *buffer, size_t size);
int parsePacket();
int available();
int read();
int read(unsigned char *buffer, size_t len);
int read(char *buffer, size_t len);
int peek();
void flush();
IPAddress remoteIP();
uint16_t remotePort();
};

View File

@@ -0,0 +1,32 @@
#pragma once
#include <Arduino.h>
#include <api/Udp.h>
class IWiFiUDP : public UDP {
public:
IWiFiUDP() {}
~IWiFiUDP() {}
virtual uint8_t begin(IPAddress ip, uint16_t port) = 0;
virtual uint8_t begin(uint16_t port) = 0;
virtual uint8_t beginMulticast(IPAddress ip, uint16_t port) = 0;
virtual void stop() = 0;
virtual int beginMulticastPacket() = 0;
virtual int beginPacket() = 0;
virtual int beginPacket(IPAddress ip, uint16_t port) = 0;
virtual int beginPacket(const char *host, uint16_t port) = 0;
virtual int endPacket() = 0;
virtual size_t write(uint8_t) = 0;
virtual size_t write(const uint8_t *buffer, size_t size) = 0;
virtual int parsePacket() = 0;
virtual int available() = 0;
virtual int read() = 0;
virtual int read(unsigned char *buffer, size_t len) = 0;
virtual int read(char *buffer, size_t len) = 0;
virtual int peek() = 0;
virtual void flush() = 0;
virtual IPAddress remoteIP() = 0;
virtual uint16_t remotePort() = 0;
};

View File

@@ -0,0 +1,131 @@
/* Copyright (c) Kuba Szczodrzyński 2022-05-23. */
#ifdef LT_HAS_LWIP2
#include "mDNS.h"
#include <vector>
extern "C" {
#include <errno.h>
#include <lwip/apps/mdns.h>
#include <lwip/igmp.h>
#include <lwip/init.h>
#include <lwip/netif.h>
}
static std::vector<char *> services;
static std::vector<uint8_t> protos;
static std::vector<std::vector<char *>> records;
mDNS::mDNS() {}
mDNS::~mDNS() {}
static void mdnsTxtCallback(struct mdns_service *service, void *userdata) {
size_t index = (size_t)userdata;
if (index >= records.size())
return;
for (const auto record : records[index]) {
err_t err = mdns_resp_add_service_txtitem(service, record, strlen(record));
if (err != ERR_OK)
return;
}
}
static void mdnsStatusCallback(struct netif *netif, uint8_t result) {
LT_DM(MDNS, "Status: netif %u, status %u", netif->num, result);
}
bool mDNS::begin(const char *hostname) {
setInstanceName(hostname);
LT_DM(MDNS, "Starting (%s)", hostname);
#if LWIP_VERSION_MAJOR >= 2 && LWIP_VERSION_MINOR >= 1
mdns_resp_register_name_result_cb(mdnsStatusCallback);
#endif
mdns_resp_init();
uint8_t enabled = 0;
struct netif *netif;
for (netif = netif_list; netif != NULL; netif = netif->next) {
if (!netif_is_up(netif))
continue;
LT_DM(MDNS, "Adding netif %u", netif->num);
if ((netif->flags & NETIF_FLAG_IGMP) == 0) {
LT_DM(MDNS, "Enabling IGMP");
netif->flags |= NETIF_FLAG_IGMP;
igmp_start(netif);
}
err_t ret = mdns_resp_add_netif(netif, hostname, 255);
if (ret == ERR_OK)
enabled++;
else
LT_DM(MDNS, "Cannot add netif %u; ret=%d, errno=%d", netif->num, ret, errno);
}
return enabled > 0;
}
void mDNS::end() {
struct netif *netif = netif_list;
while (netif != NULL) {
if (netif_is_up(netif))
mdns_resp_remove_netif(netif);
netif = netif->next;
}
}
bool mDNS::addServiceImpl(const char *name, const char *service, uint8_t proto, uint16_t port) {
bool added = false;
struct netif *netif = netif_list;
while (netif != NULL) {
if (netif_is_up(netif)) {
// register TXT callback;
// pass service index as userdata parameter
LT_DM(MDNS, "Add service: netif %u / %s / %s / %u / %u", netif->num, name, service, proto, port);
mdns_resp_add_service(
netif,
name,
service,
(mdns_sd_proto)proto,
port,
255,
mdnsTxtCallback,
(void *)services.size() // index of newly added service
);
added = true;
}
netif = netif->next;
}
if (!added)
return false;
// add the service to TXT record arrays
services.push_back(strdup(service));
protos.push_back(proto);
records.emplace_back();
return true;
}
bool mDNS::addServiceTxtImpl(const char *service, uint8_t proto, const char *item) {
int8_t index = -1;
for (uint8_t i = 0; i < services.size(); i++) {
// find a matching service
if (strcmp(services[i], service) == 0 && protos[i] == proto) {
index = i;
break;
}
}
if (index == -1)
return false;
records[index].push_back(strdup(item));
return true;
}
MDNSResponder MDNS;
#endif

View File

@@ -0,0 +1,40 @@
/* Copyright (c) Kuba Szczodrzyński 2022-08-26. */
#include "mDNS.h"
static char *ensureUnderscore(const char *value) {
uint8_t len = strlen(value) + 1;
char *result = (char *)malloc(len);
result[0] = '_';
strcpy(result + 1, value + (value[0] == '_'));
return result;
}
void mDNS::setInstanceName(const char *name) {
if (instanceName)
free(instanceName);
instanceName = strdup(name);
}
bool mDNS::addService(char *service, char *proto, uint16_t port) {
char *_service = ensureUnderscore(service);
uint8_t _proto = strncmp(proto + (proto[0] == '_'), "tcp", 3) == 0 ? MDNS_TCP : MDNS_UDP;
bool result = addServiceImpl(instanceName ? instanceName : "LT mDNS", _service, _proto, port);
free(_service);
return result;
}
bool mDNS::addServiceTxt(char *service, char *proto, char *key, char *value) {
char *_service = ensureUnderscore(service);
uint8_t _proto = strncmp(proto + (proto[0] == '_'), "tcp", 3) == 0 ? MDNS_TCP : MDNS_UDP;
uint8_t txt_len = strlen(key) + strlen(value) + 1;
char *txt = (char *)malloc(txt_len + 1);
sprintf(txt, "%s=%s", key, value);
bool result = addServiceTxtImpl(_service, _proto, txt);
free(_service);
free(txt);
return result;
}

View File

@@ -0,0 +1,128 @@
/*
ESP8266 Multicast DNS (port of CC3000 Multicast DNS library)
Version 1.1
Copyright (c) 2013 Tony DiCola (tony@tonydicola.com)
ESP8266 port (c) 2015 Ivan Grokhotkov (ivan@esp8266.com)
MDNS-SD Suport 2015 Hristo Gochkov (hristo@espressif.com)
Extended MDNS-SD support 2016 Lars Englund (lars.englund@gmail.com)
Rewritten for ESP32 by Hristo Gochkov (hristo@espressif.com)
This is a simple implementation of multicast DNS query support for an Arduino
running on ESP32 chip.
Usage:
- Include the ESP32 Multicast DNS library in the sketch.
- Call the begin method in the sketch's setup and provide a domain name (without
the '.local' suffix, i.e. just provide 'foo' to resolve 'foo.local'), and the
Adafruit CC3000 class instance. Optionally provide a time to live (in seconds)
for the DNS record--the default is 1 hour.
- Call the update method in each iteration of the sketch's loop function.
License (MIT license):
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#pragma once
#include <Arduino.h>
#include <api/IPv6Address.h>
#define MDNS_UDP 0
#define MDNS_TCP 1
class mDNS {
private:
bool addServiceImpl(const char *name, const char *service, uint8_t proto, uint16_t port);
bool addServiceTxtImpl(const char *service, uint8_t proto, const char *item);
char *instanceName = NULL;
public:
mDNS();
~mDNS();
bool begin(const char *hostname);
void end();
void setInstanceName(const char *name);
bool addService(char *service, char *proto, uint16_t port);
bool addServiceTxt(char *service, char *proto, char *key, char *value);
// void enableArduino(uint16_t port = 3232, bool auth = false);
// void disableArduino();
// void enableWorkstation(esp_interface_t interface = ESP_IF_WIFI_STA);
// void disableWorkstation();
IPAddress queryHost(char *host, uint32_t timeout = 2000);
int queryService(char *service, char *proto);
String hostname(int idx);
IPAddress IP(int idx);
IPv6Address IPv6(int idx);
uint16_t port(int idx);
int numTxt(int idx);
bool hasTxt(int idx, const char *key);
String txt(int idx, const char *key);
String txt(int idx, int txtIdx);
String txtKey(int idx, int txtIdx);
void setInstanceName(String name) {
setInstanceName(name.c_str());
}
void setInstanceName(char *name) {
setInstanceName((const char *)name);
}
bool addService(const char *service, const char *proto, uint16_t port) {
return addService((char *)service, (char *)proto, port);
}
bool addService(String service, String proto, uint16_t port) {
return addService(service.c_str(), proto.c_str(), port);
}
void addServiceTxt(const char *service, const char *proto, const char *key, const char *value) {
addServiceTxt((char *)service, (char *)proto, (char *)key, (char *)value);
}
void addServiceTxt(String service, String proto, String key, String value) {
addServiceTxt(service.c_str(), proto.c_str(), key.c_str(), value.c_str());
}
IPAddress queryHost(const char *host, uint32_t timeout = 2000) {
return queryHost((char *)host, timeout);
}
IPAddress queryHost(String host, uint32_t timeout = 2000) {
return queryHost(host.c_str(), timeout);
}
int queryService(const char *service, const char *proto) {
return queryService((char *)service, (char *)proto);
}
int queryService(String service, String proto) {
return queryService(service.c_str(), proto.c_str());
}
};
typedef mDNS MDNSResponder;
extern MDNSResponder MDNS;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,305 @@
/**
* HTTPClient.h
*
* Created on: 02.11.2015
*
* Copyright (c) 2015 Markus Sattler. All rights reserved.
* This file is part of the HTTPClient for Arduino.
* Port to ESP32 by Evandro Luis Copercini (2017),
* changed fingerprints to CA verification.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifndef HTTPClient_H_
#define HTTPClient_H_
#ifndef HTTPCLIENT_1_1_COMPATIBLE
#define HTTPCLIENT_1_1_COMPATIBLE
#endif
#include <Arduino.h>
#include <WiFiClient.h>
#include <WiFiClientSecure.h>
#include <memory>
/// Cookie jar support
#include <vector>
#define HTTPCLIENT_DEFAULT_TCP_TIMEOUT (5000)
/// HTTP client errors
#define HTTPC_ERROR_CONNECTION_REFUSED (-1)
#define HTTPC_ERROR_SEND_HEADER_FAILED (-2)
#define HTTPC_ERROR_SEND_PAYLOAD_FAILED (-3)
#define HTTPC_ERROR_NOT_CONNECTED (-4)
#define HTTPC_ERROR_CONNECTION_LOST (-5)
#define HTTPC_ERROR_NO_STREAM (-6)
#define HTTPC_ERROR_NO_HTTP_SERVER (-7)
#define HTTPC_ERROR_TOO_LESS_RAM (-8)
#define HTTPC_ERROR_ENCODING (-9)
#define HTTPC_ERROR_STREAM_WRITE (-10)
#define HTTPC_ERROR_READ_TIMEOUT (-11)
/// size for the stream handling
#define HTTP_TCP_BUFFER_SIZE (1460)
/// HTTP codes see RFC7231
typedef enum {
HTTP_CODE_CONTINUE = 100,
HTTP_CODE_SWITCHING_PROTOCOLS = 101,
HTTP_CODE_PROCESSING = 102,
HTTP_CODE_OK = 200,
HTTP_CODE_CREATED = 201,
HTTP_CODE_ACCEPTED = 202,
HTTP_CODE_NON_AUTHORITATIVE_INFORMATION = 203,
HTTP_CODE_NO_CONTENT = 204,
HTTP_CODE_RESET_CONTENT = 205,
HTTP_CODE_PARTIAL_CONTENT = 206,
HTTP_CODE_MULTI_STATUS = 207,
HTTP_CODE_ALREADY_REPORTED = 208,
HTTP_CODE_IM_USED = 226,
HTTP_CODE_MULTIPLE_CHOICES = 300,
HTTP_CODE_MOVED_PERMANENTLY = 301,
HTTP_CODE_FOUND = 302,
HTTP_CODE_SEE_OTHER = 303,
HTTP_CODE_NOT_MODIFIED = 304,
HTTP_CODE_USE_PROXY = 305,
HTTP_CODE_TEMPORARY_REDIRECT = 307,
HTTP_CODE_PERMANENT_REDIRECT = 308,
HTTP_CODE_BAD_REQUEST = 400,
HTTP_CODE_UNAUTHORIZED = 401,
HTTP_CODE_PAYMENT_REQUIRED = 402,
HTTP_CODE_FORBIDDEN = 403,
HTTP_CODE_NOT_FOUND = 404,
HTTP_CODE_METHOD_NOT_ALLOWED = 405,
HTTP_CODE_NOT_ACCEPTABLE = 406,
HTTP_CODE_PROXY_AUTHENTICATION_REQUIRED = 407,
HTTP_CODE_REQUEST_TIMEOUT = 408,
HTTP_CODE_CONFLICT = 409,
HTTP_CODE_GONE = 410,
HTTP_CODE_LENGTH_REQUIRED = 411,
HTTP_CODE_PRECONDITION_FAILED = 412,
HTTP_CODE_PAYLOAD_TOO_LARGE = 413,
HTTP_CODE_URI_TOO_LONG = 414,
HTTP_CODE_UNSUPPORTED_MEDIA_TYPE = 415,
HTTP_CODE_RANGE_NOT_SATISFIABLE = 416,
HTTP_CODE_EXPECTATION_FAILED = 417,
HTTP_CODE_MISDIRECTED_REQUEST = 421,
HTTP_CODE_UNPROCESSABLE_ENTITY = 422,
HTTP_CODE_LOCKED = 423,
HTTP_CODE_FAILED_DEPENDENCY = 424,
HTTP_CODE_UPGRADE_REQUIRED = 426,
HTTP_CODE_PRECONDITION_REQUIRED = 428,
HTTP_CODE_TOO_MANY_REQUESTS = 429,
HTTP_CODE_REQUEST_HEADER_FIELDS_TOO_LARGE = 431,
HTTP_CODE_INTERNAL_SERVER_ERROR = 500,
HTTP_CODE_NOT_IMPLEMENTED = 501,
HTTP_CODE_BAD_GATEWAY = 502,
HTTP_CODE_SERVICE_UNAVAILABLE = 503,
HTTP_CODE_GATEWAY_TIMEOUT = 504,
HTTP_CODE_HTTP_VERSION_NOT_SUPPORTED = 505,
HTTP_CODE_VARIANT_ALSO_NEGOTIATES = 506,
HTTP_CODE_INSUFFICIENT_STORAGE = 507,
HTTP_CODE_LOOP_DETECTED = 508,
HTTP_CODE_NOT_EXTENDED = 510,
HTTP_CODE_NETWORK_AUTHENTICATION_REQUIRED = 511
} t_http_codes;
typedef enum { HTTPC_TE_IDENTITY, HTTPC_TE_CHUNKED } transferEncoding_t;
/**
* redirection follow mode.
* + `HTTPC_DISABLE_FOLLOW_REDIRECTS` - no redirection will be followed.
* + `HTTPC_STRICT_FOLLOW_REDIRECTS` - strict RFC2616, only requests using
* GET or HEAD methods will be redirected (using the same method),
* since the RFC requires end-user confirmation in other cases.
* + `HTTPC_FORCE_FOLLOW_REDIRECTS` - all redirections will be followed,
* regardless of a used method. New request will use the same method,
* and they will include the same body data and the same headers.
* In the sense of the RFC, it's just like every redirection is confirmed.
*/
typedef enum {
HTTPC_DISABLE_FOLLOW_REDIRECTS,
HTTPC_STRICT_FOLLOW_REDIRECTS,
HTTPC_FORCE_FOLLOW_REDIRECTS
} followRedirects_t;
#ifdef HTTPCLIENT_1_1_COMPATIBLE
class TransportTraits;
typedef std::unique_ptr<TransportTraits> TransportTraitsPtr;
#endif
// cookie jar support
typedef struct {
String host; // host which tries to set the cookie
time_t date; // timestamp of the response that set the cookie
String name;
String value;
String domain;
String path = "";
struct {
time_t date = 0;
bool valid = false;
} expires;
struct {
time_t duration = 0;
bool valid = false;
} max_age;
bool http_only = false;
bool secure = false;
} Cookie;
typedef std::vector<Cookie> CookieJar;
class HTTPClient {
public:
HTTPClient();
~HTTPClient();
/*
* Since both begin() functions take a reference to client as a parameter, you need to
* ensure the client object lives the entire time of the HTTPClient
*/
bool begin(WiFiClient &client, String url);
bool begin(WiFiClient &client, String host, uint16_t port, String uri = "/", bool https = false);
#ifdef HTTPCLIENT_1_1_COMPATIBLE
bool begin(String url);
bool begin(String url, const char *CAcert);
bool begin(String host, uint16_t port, String uri = "/");
bool begin(String host, uint16_t port, String uri, const char *CAcert);
bool begin(String host, uint16_t port, String uri, const char *CAcert, const char *cli_cert, const char *cli_key);
#endif
void end(void);
bool connected(void);
void setReuse(bool reuse); /// keep-alive
void setUserAgent(const String &userAgent);
void setAuthorization(const char *user, const char *password);
void setAuthorization(const char *auth);
void setAuthorizationType(const char *authType);
void setConnectTimeout(int32_t connectTimeout);
void setTimeout(uint16_t timeout);
// Redirections
void setFollowRedirects(followRedirects_t follow);
void setRedirectLimit(uint16_t limit); // max redirects to follow for a single request
bool setURL(const String &url);
void useHTTP10(bool usehttp10 = true);
/// request handling
int GET();
int PATCH(uint8_t *payload, size_t size);
int PATCH(String payload);
int POST(uint8_t *payload, size_t size);
int POST(String payload);
int PUT(uint8_t *payload, size_t size);
int PUT(String payload);
int sendRequest(const char *type, String payload);
int sendRequest(const char *type, uint8_t *payload = NULL, size_t size = 0);
int sendRequest(const char *type, Stream *stream, size_t size = 0);
void addHeader(const String &name, const String &value, bool first = false, bool replace = true);
/// Response handling
void collectHeaders(const char *headerKeys[], const size_t headerKeysCount);
String header(const char *name); // get request header value by name
String header(size_t i); // get request header value by number
String headerName(size_t i); // get request header name by number
int headers(); // get header count
bool hasHeader(const char *name); // check if header exists
int getSize(void);
const String &getLocation(void);
WiFiClient &getStream(void);
WiFiClient *getStreamPtr(void);
int writeToStream(Stream *stream);
// String getString(void);
static String errorToString(int error);
/// Cookie jar support
void setCookieJar(CookieJar *cookieJar);
void resetCookieJar();
void clearAllCookies();
protected:
struct RequestArgument {
String key;
String value;
};
bool beginInternal(String url, const char *expectedProtocol);
void disconnect(bool preserveClient = false);
void clear();
int returnError(int error);
bool connect(void);
bool sendHeader(const char *type);
int handleHeaderResponse();
int writeToStreamDataBlock(Stream *stream, int len);
/// Cookie jar support
void setCookie(String date, String headerValue);
bool generateCookieString(String *cookieString);
#ifdef HTTPCLIENT_1_1_COMPATIBLE
TransportTraitsPtr _transportTraits;
std::unique_ptr<WiFiClient> _tcpDeprecated;
#endif
WiFiClient *_client = nullptr;
/// request handling
String _host;
uint16_t _port = 0;
int32_t _connectTimeout = -1;
bool _reuse = true;
uint16_t _tcpTimeout = HTTPCLIENT_DEFAULT_TCP_TIMEOUT;
bool _useHTTP10 = false;
bool _secure = false;
String _uri;
String _protocol;
String _headers;
String _userAgent = "ESP32HTTPClient";
String _base64Authorization;
String _authorizationType = "Basic";
/// Response handling
RequestArgument *_currentHeaders = nullptr;
size_t _headerKeysCount = 0;
int _returnCode = 0;
int _size = -1;
bool _canReuse = false;
followRedirects_t _followRedirects = HTTPC_DISABLE_FOLLOW_REDIRECTS;
uint16_t _redirectLimit = 10;
String _location;
transferEncoding_t _transferEncoding = HTTPC_TE_IDENTITY;
/// Cookie jar support
CookieJar *_cookieJar = nullptr;
};
#endif /* HTTPClient_H_ */

View File

@@ -0,0 +1,2 @@
DisableFormat: true
SortIncludes: Never

View File

@@ -0,0 +1,61 @@
/**
StreamString.cpp
Copyright (c) 2015 Markus Sattler. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <Arduino.h>
#include "StreamString.h"
size_t StreamString::write(const uint8_t *data, size_t size) {
if(size && data) {
concat(data, size);
return size;
}
return 0;
}
size_t StreamString::write(uint8_t data) {
return concat((char) data);
}
int StreamString::available() {
return length();
}
int StreamString::read() {
if(length()) {
char c = charAt(0);
remove(0, 1);
return c;
}
return -1;
}
int StreamString::peek() {
if(length()) {
char c = charAt(0);
return c;
}
return -1;
}
void StreamString::flush() {
}

View File

@@ -0,0 +1,39 @@
/**
StreamString.h
Copyright (c) 2015 Markus Sattler. All rights reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef STREAMSTRING_H_
#define STREAMSTRING_H_
class StreamString: public Stream, public String
{
public:
size_t write(const uint8_t *buffer, size_t size) override;
size_t write(uint8_t data) override;
int available() override;
int read() override;
int peek() override;
void flush() override;
};
#endif /* STREAMSTRING_H_ */

View File

@@ -0,0 +1,56 @@
#pragma once
/* Request Methods */
#define HTTP_METHOD_MAP(XX) \
XX(0, DELETE, DELETE) \
XX(1, GET, GET) \
XX(2, HEAD, HEAD) \
XX(3, POST, POST) \
XX(4, PUT, PUT) \
/* pathological */ \
XX(5, CONNECT, CONNECT) \
XX(6, OPTIONS, OPTIONS) \
XX(7, TRACE, TRACE) \
/* WebDAV */ \
XX(8, COPY, COPY) \
XX(9, LOCK, LOCK) \
XX(10, MKCOL, MKCOL) \
XX(11, MOVE, MOVE) \
XX(12, PROPFIND, PROPFIND) \
XX(13, PROPPATCH, PROPPATCH) \
XX(14, SEARCH, SEARCH) \
XX(15, UNLOCK, UNLOCK) \
XX(16, BIND, BIND) \
XX(17, REBIND, REBIND) \
XX(18, UNBIND, UNBIND) \
XX(19, ACL, ACL) \
/* subversion */ \
XX(20, REPORT, REPORT) \
XX(21, MKACTIVITY, MKACTIVITY) \
XX(22, CHECKOUT, CHECKOUT) \
XX(23, MERGE, MERGE) \
/* upnp */ \
XX(24, MSEARCH, M - SEARCH) \
XX(25, NOTIFY, NOTIFY) \
XX(26, SUBSCRIBE, SUBSCRIBE) \
XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \
/* RFC-5789 */ \
XX(28, PATCH, PATCH) \
XX(29, PURGE, PURGE) \
/* CalDAV */ \
XX(30, MKCALENDAR, MKCALENDAR) \
/* RFC-2068, section 19.6.1.2 */ \
XX(31, LINK, LINK) \
XX(32, UNLINK, UNLINK) \
/* icecast */ \
XX(33, SOURCE, SOURCE)
enum http_method {
#define XX(num, name, string) HTTP_##name = num,
HTTP_METHOD_MAP(XX)
#undef XX
};
typedef enum http_method HTTPMethod;
#define HTTP_ANY (HTTPMethod)(255)

View File

@@ -0,0 +1,609 @@
/*
Parsing.cpp - HTTP request parsing.
Copyright (c) 2015 Ivan Grokhotkov. All rights reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling)
*/
#if LT_ARD_HAS_WIFI
#include <Arduino.h>
#include "WebServer.h"
#include "WiFiClient.h"
#include "WiFiServer.h"
#include "detail/mimetable.h"
#ifndef WEBSERVER_MAX_POST_ARGS
#define WEBSERVER_MAX_POST_ARGS 32
#endif
#define __STR(a) #a
#define _STR(a) __STR(a)
const char *_http_method_str[] = {
#define XX(num, name, string) _STR(name),
HTTP_METHOD_MAP(XX)
#undef XX
};
static const char Content_Type[] PROGMEM = "Content-Type";
static const char filename[] PROGMEM = "filename";
static char *readBytesWithTimeout(WiFiClient &client, size_t maxLength, size_t &dataLength, int timeout_ms) {
char *buf = nullptr;
dataLength = 0;
while (dataLength < maxLength) {
int tries = timeout_ms;
size_t newLength;
while (!(newLength = client.available()) && tries--)
delay(1);
if (!newLength) {
break;
}
if (!buf) {
buf = (char *)malloc(newLength + 1);
if (!buf) {
return nullptr;
}
} else {
char *newBuf = (char *)realloc(buf, dataLength + newLength + 1);
if (!newBuf) {
free(buf);
return nullptr;
}
buf = newBuf;
}
client.readBytes(buf + dataLength, newLength);
dataLength += newLength;
buf[dataLength] = '\0';
}
return buf;
}
bool WebServer::_parseRequest(WiFiClient &client) {
// Read the first line of HTTP request
String req = client.readStringUntil('\r');
client.readStringUntil('\n');
// reset header value
for (int i = 0; i < _headerKeysCount; ++i) {
_currentHeaders[i].value = String();
}
// First line of HTTP request looks like "GET /path HTTP/1.1"
// Retrieve the "/path" part by finding the spaces
int addr_start = req.indexOf(' ');
int addr_end = req.indexOf(' ', addr_start + 1);
if (addr_start == -1 || addr_end == -1) {
log_e("Invalid request: %s", req.c_str());
return false;
}
String methodStr = req.substring(0, addr_start);
String url = req.substring(addr_start + 1, addr_end);
String versionEnd = req.substring(addr_end + 8);
_currentVersion = atoi(versionEnd.c_str());
String searchStr = "";
int hasSearch = url.indexOf('?');
if (hasSearch != -1) {
searchStr = url.substring(hasSearch + 1);
url = url.substring(0, hasSearch);
}
_currentUri = url;
_chunked = false;
HTTPMethod method = HTTP_ANY;
size_t num_methods = sizeof(_http_method_str) / sizeof(const char *);
for (size_t i = 0; i < num_methods; i++) {
if (methodStr == _http_method_str[i]) {
method = (HTTPMethod)i;
break;
}
}
if (method == HTTP_ANY) {
log_e("Unknown HTTP Method: %s", methodStr.c_str());
return false;
}
_currentMethod = method;
log_v("method: %s url: %s search: %s", methodStr.c_str(), url.c_str(), searchStr.c_str());
// attach handler
RequestHandler *handler;
for (handler = _firstHandler; handler; handler = handler->next()) {
if (handler->canHandle(_currentMethod, _currentUri))
break;
}
_currentHandler = handler;
String formData;
// below is needed only when POST type request
if (method == HTTP_POST || method == HTTP_PUT || method == HTTP_PATCH || method == HTTP_DELETE) {
String boundaryStr;
String headerName;
String headerValue;
bool isForm = false;
bool isEncoded = false;
uint32_t contentLength = 0;
// parse headers
while (1) {
req = client.readStringUntil('\r');
client.readStringUntil('\n');
if (req == "")
break; // no moar headers
int headerDiv = req.indexOf(':');
if (headerDiv == -1) {
break;
}
headerName = req.substring(0, headerDiv);
headerValue = req.substring(headerDiv + 1);
headerValue.trim();
_collectHeader(headerName.c_str(), headerValue.c_str());
log_v("headerName: %s", headerName.c_str());
log_v("headerValue: %s", headerValue.c_str());
if (headerName.equalsIgnoreCase(FPSTR(Content_Type))) {
using namespace mime;
if (headerValue.startsWith(FPSTR(mimeTable[txt].mimeType))) {
isForm = false;
} else if (headerValue.startsWith(F("application/x-www-form-urlencoded"))) {
isForm = false;
isEncoded = true;
} else if (headerValue.startsWith(F("multipart/"))) {
boundaryStr = headerValue.substring(headerValue.indexOf('=') + 1);
boundaryStr.replace("\"", "");
isForm = true;
}
} else if (headerName.equalsIgnoreCase(F("Content-Length"))) {
contentLength = headerValue.toInt();
} else if (headerName.equalsIgnoreCase(F("Host"))) {
_hostHeader = headerValue;
}
}
if (!isForm) {
size_t plainLength;
char *plainBuf = readBytesWithTimeout(client, contentLength, plainLength, HTTP_MAX_POST_WAIT);
if (plainLength < contentLength) {
free(plainBuf);
return false;
}
if (contentLength > 0) {
if (isEncoded) {
// url encoded form
if (searchStr != "")
searchStr += '&';
searchStr += plainBuf;
}
_parseArguments(searchStr);
if (!isEncoded) {
// plain post json or other data
RequestArgument &arg = _currentArgs[_currentArgCount++];
arg.key = F("plain");
arg.value = String(plainBuf);
}
log_v("Plain: %s", plainBuf);
free(plainBuf);
} else {
// No content - but we can still have arguments in the URL.
_parseArguments(searchStr);
}
}
if (isForm) {
_parseArguments(searchStr);
if (!_parseForm(client, boundaryStr, contentLength)) {
return false;
}
}
} else {
String headerName;
String headerValue;
// parse headers
while (1) {
req = client.readStringUntil('\r');
client.readStringUntil('\n');
if (req == "")
break; // no moar headers
int headerDiv = req.indexOf(':');
if (headerDiv == -1) {
break;
}
headerName = req.substring(0, headerDiv);
headerValue = req.substring(headerDiv + 2);
_collectHeader(headerName.c_str(), headerValue.c_str());
log_v("headerName: %s", headerName.c_str());
log_v("headerValue: %s", headerValue.c_str());
if (headerName.equalsIgnoreCase("Host")) {
_hostHeader = headerValue;
}
}
_parseArguments(searchStr);
}
client.flush();
log_v("Request: %s", url.c_str());
log_v(" Arguments: %s", searchStr.c_str());
return true;
}
bool WebServer::_collectHeader(const char *headerName, const char *headerValue) {
for (int i = 0; i < _headerKeysCount; i++) {
if (_currentHeaders[i].key.equalsIgnoreCase(headerName)) {
_currentHeaders[i].value = headerValue;
return true;
}
}
return false;
}
void WebServer::_parseArguments(String data) {
log_v("args: %s", data.c_str());
if (_currentArgs)
delete[] _currentArgs;
_currentArgs = 0;
if (data.length() == 0) {
_currentArgCount = 0;
_currentArgs = new RequestArgument[1];
return;
}
_currentArgCount = 1;
for (int i = 0; i < (int)data.length();) {
i = data.indexOf('&', i);
if (i == -1)
break;
++i;
++_currentArgCount;
}
log_v("args count: %d", _currentArgCount);
_currentArgs = new RequestArgument[_currentArgCount + 1];
int pos = 0;
int iarg;
for (iarg = 0; iarg < _currentArgCount;) {
int equal_sign_index = data.indexOf('=', pos);
int next_arg_index = data.indexOf('&', pos);
log_v("pos %d =@%d &@%d", pos, equal_sign_index, next_arg_index);
if ((equal_sign_index == -1) || ((equal_sign_index > next_arg_index) && (next_arg_index != -1))) {
log_e("arg missing value: %d", iarg);
if (next_arg_index == -1)
break;
pos = next_arg_index + 1;
continue;
}
RequestArgument &arg = _currentArgs[iarg];
arg.key = urlDecode(data.substring(pos, equal_sign_index));
arg.value = urlDecode(data.substring(equal_sign_index + 1, next_arg_index));
log_v("arg %d key: %s value: %s", iarg, arg.key.c_str(), arg.value.c_str());
++iarg;
if (next_arg_index == -1)
break;
pos = next_arg_index + 1;
}
_currentArgCount = iarg;
log_v("args count: %d", _currentArgCount);
}
void WebServer::_uploadWriteByte(uint8_t b) {
if (_currentUpload->currentSize == HTTP_UPLOAD_BUFLEN) {
if (_currentHandler && _currentHandler->canUpload(_currentUri))
_currentHandler->upload(*this, _currentUri, *_currentUpload);
_currentUpload->totalSize += _currentUpload->currentSize;
_currentUpload->currentSize = 0;
}
_currentUpload->buf[_currentUpload->currentSize++] = b;
}
int WebServer::_uploadReadByte(WiFiClient &client) {
int res = client.read();
if (res < 0) {
// keep trying until you either read a valid byte or timeout
unsigned long startMillis = millis();
long timeoutIntervalMillis = client.getTimeout();
boolean timedOut = false;
for (;;) {
if (!client.connected())
return -1;
// loosely modeled after blinkWithoutDelay pattern
while (!timedOut && !client.available() && client.connected()) {
delay(2);
timedOut = millis() - startMillis >= timeoutIntervalMillis;
}
res = client.read();
if (res >= 0) {
return res; // exit on a valid read
}
// NOTE: it is possible to get here and have all of the following
// assertions hold true
//
// -- client.available() > 0
// -- client.connected == true
// -- res == -1
//
// a simple retry strategy overcomes this which is to say the
// assertion is not permanent, but the reason that this works
// is elusive, and possibly indicative of a more subtle underlying
// issue
timedOut = millis() - startMillis >= timeoutIntervalMillis;
if (timedOut) {
return res; // exit on a timeout
}
}
}
return res;
}
bool WebServer::_parseForm(WiFiClient &client, String boundary, uint32_t len) {
(void)len;
log_v("Parse Form: Boundary: %s Length: %d", boundary.c_str(), len);
String line;
int retry = 0;
do {
line = client.readStringUntil('\r');
++retry;
} while (line.length() == 0 && retry < 3);
client.readStringUntil('\n');
// start reading the form
if (line == ("--" + boundary)) {
if (_postArgs)
delete[] _postArgs;
_postArgs = new RequestArgument[WEBSERVER_MAX_POST_ARGS];
_postArgsLen = 0;
while (1) {
String argName;
String argValue;
String argType;
String argFilename;
bool argIsFile = false;
line = client.readStringUntil('\r');
client.readStringUntil('\n');
if (line.length() > 19 && line.substring(0, 19).equalsIgnoreCase(F("Content-Disposition"))) {
int nameStart = line.indexOf('=');
if (nameStart != -1) {
argName = line.substring(nameStart + 2);
nameStart = argName.indexOf('=');
if (nameStart == -1) {
argName = argName.substring(0, argName.length() - 1);
} else {
argFilename = argName.substring(nameStart + 2, argName.length() - 1);
argName = argName.substring(0, argName.indexOf('"'));
argIsFile = true;
log_v("PostArg FileName: %s", argFilename.c_str());
// use GET to set the filename if uploading using blob
if (argFilename == F("blob") && hasArg(FPSTR(filename)))
argFilename = arg(FPSTR(filename));
}
log_v("PostArg Name: %s", argName.c_str());
using namespace mime;
argType = FPSTR(mimeTable[txt].mimeType);
line = client.readStringUntil('\r');
client.readStringUntil('\n');
if (line.length() > 12 && line.substring(0, 12).equalsIgnoreCase(FPSTR(Content_Type))) {
argType = line.substring(line.indexOf(':') + 2);
// skip next line
client.readStringUntil('\r');
client.readStringUntil('\n');
}
log_v("PostArg Type: %s", argType.c_str());
if (!argIsFile) {
while (1) {
line = client.readStringUntil('\r');
client.readStringUntil('\n');
if (line.startsWith("--" + boundary))
break;
if (argValue.length() > 0)
argValue += "\n";
argValue += line;
}
log_v("PostArg Value: %s", argValue.c_str());
RequestArgument &arg = _postArgs[_postArgsLen++];
arg.key = argName;
arg.value = argValue;
if (line == ("--" + boundary + "--")) {
log_v("Done Parsing POST");
break;
} else if (_postArgsLen >= WEBSERVER_MAX_POST_ARGS) {
log_e("Too many PostArgs (max: %d) in request.", WEBSERVER_MAX_POST_ARGS);
return false;
}
} else {
_currentUpload.reset(new HTTPUpload());
_currentUpload->status = UPLOAD_FILE_START;
_currentUpload->name = argName;
_currentUpload->filename = argFilename;
_currentUpload->type = argType;
_currentUpload->totalSize = 0;
_currentUpload->currentSize = 0;
log_v(
"Start File: %s Type: %s",
_currentUpload->filename.c_str(),
_currentUpload->type.c_str()
);
if (_currentHandler && _currentHandler->canUpload(_currentUri))
_currentHandler->upload(*this, _currentUri, *_currentUpload);
_currentUpload->status = UPLOAD_FILE_WRITE;
int argByte = _uploadReadByte(client);
readfile:
while (argByte != 0x0D) {
if (argByte < 0)
return _parseFormUploadAborted();
_uploadWriteByte(argByte);
argByte = _uploadReadByte(client);
}
argByte = _uploadReadByte(client);
if (argByte < 0)
return _parseFormUploadAborted();
if (argByte == 0x0A) {
argByte = _uploadReadByte(client);
if (argByte < 0)
return _parseFormUploadAborted();
if ((char)argByte != '-') {
// continue reading the file
_uploadWriteByte(0x0D);
_uploadWriteByte(0x0A);
goto readfile;
} else {
argByte = _uploadReadByte(client);
if (argByte < 0)
return _parseFormUploadAborted();
if ((char)argByte != '-') {
// continue reading the file
_uploadWriteByte(0x0D);
_uploadWriteByte(0x0A);
_uploadWriteByte((uint8_t)('-'));
goto readfile;
}
}
uint8_t endBuf[boundary.length()];
uint32_t i = 0;
while (i < boundary.length()) {
argByte = _uploadReadByte(client);
if (argByte < 0)
return _parseFormUploadAborted();
if ((char)argByte == 0x0D) {
_uploadWriteByte(0x0D);
_uploadWriteByte(0x0A);
_uploadWriteByte((uint8_t)('-'));
_uploadWriteByte((uint8_t)('-'));
uint32_t j = 0;
while (j < i) {
_uploadWriteByte(endBuf[j++]);
}
goto readfile;
}
endBuf[i++] = (uint8_t)argByte;
}
if (strstr((const char *)endBuf, boundary.c_str()) != NULL) {
if (_currentHandler && _currentHandler->canUpload(_currentUri))
_currentHandler->upload(*this, _currentUri, *_currentUpload);
_currentUpload->totalSize += _currentUpload->currentSize;
_currentUpload->status = UPLOAD_FILE_END;
if (_currentHandler && _currentHandler->canUpload(_currentUri))
_currentHandler->upload(*this, _currentUri, *_currentUpload);
log_v(
"End File: %s Type: %s Size: %d",
_currentUpload->filename.c_str(),
_currentUpload->type.c_str(),
_currentUpload->totalSize
);
line = client.readStringUntil(0x0D);
client.readStringUntil(0x0A);
if (line == "--") {
log_v("Done Parsing POST");
break;
}
continue;
} else {
_uploadWriteByte(0x0D);
_uploadWriteByte(0x0A);
_uploadWriteByte((uint8_t)('-'));
_uploadWriteByte((uint8_t)('-'));
uint32_t i = 0;
while (i < boundary.length()) {
_uploadWriteByte(endBuf[i++]);
}
argByte = _uploadReadByte(client);
goto readfile;
}
} else {
_uploadWriteByte(0x0D);
goto readfile;
}
break;
}
}
}
}
int iarg;
int totalArgs = ((WEBSERVER_MAX_POST_ARGS - _postArgsLen) < _currentArgCount)
? (WEBSERVER_MAX_POST_ARGS - _postArgsLen)
: _currentArgCount;
for (iarg = 0; iarg < totalArgs; iarg++) {
RequestArgument &arg = _postArgs[_postArgsLen++];
arg.key = _currentArgs[iarg].key;
arg.value = _currentArgs[iarg].value;
}
if (_currentArgs)
delete[] _currentArgs;
_currentArgs = new RequestArgument[_postArgsLen];
for (iarg = 0; iarg < _postArgsLen; iarg++) {
RequestArgument &arg = _currentArgs[iarg];
arg.key = _postArgs[iarg].key;
arg.value = _postArgs[iarg].value;
}
_currentArgCount = iarg;
if (_postArgs) {
delete[] _postArgs;
_postArgs = nullptr;
_postArgsLen = 0;
}
return true;
}
log_e("Error: line: %s", line.c_str());
return false;
}
String WebServer::urlDecode(const String &text) {
String decoded = "";
char temp[] = "0x00";
unsigned int len = text.length();
unsigned int i = 0;
while (i < len) {
char decodedChar;
char encodedChar = text.charAt(i++);
if ((encodedChar == '%') && (i + 1 < len)) {
temp[2] = text.charAt(i++);
temp[3] = text.charAt(i++);
decodedChar = strtol(temp, NULL, 16);
} else {
if (encodedChar == '+') {
decodedChar = ' ';
} else {
decodedChar = encodedChar; // normal ascii char
}
}
decoded += decodedChar;
}
return decoded;
}
bool WebServer::_parseFormUploadAborted() {
_currentUpload->status = UPLOAD_FILE_ABORTED;
if (_currentHandler && _currentHandler->canUpload(_currentUri))
_currentHandler->upload(*this, _currentUri, *_currentUpload);
return false;
}
#endif // LT_ARD_HAS_WIFI

View File

@@ -0,0 +1,29 @@
#pragma once
#include <Arduino.h>
#include <vector>
class Uri {
protected:
const String _uri;
public:
Uri(const char *uri) : _uri(uri) {}
Uri(const String &uri) : _uri(uri) {}
Uri(const __FlashStringHelper *uri) : _uri(String(uri)) {}
virtual ~Uri() {}
virtual Uri *clone() const {
return new Uri(_uri);
};
virtual void initPathArgs(__attribute__((unused)) std::vector<String> &pathArgs) {}
virtual bool canHandle(const String &requestUri, __attribute__((unused)) std::vector<String> &pathArgs) {
return _uri == requestUri;
}
};

View File

@@ -0,0 +1,721 @@
/*
WebServer.cpp - Dead simple web-server.
Supports only one simultaneous client, knows how to handle GET and POST.
Copyright (c) 2014 Ivan Grokhotkov. All rights reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling)
*/
#if LT_ARD_HAS_WIFI
#include <Arduino.h>
#include "FS.h"
#include "WebServer.h"
#include "WiFiClient.h"
#include "WiFiServer.h"
#include "detail/RequestHandlersImpl.h"
// #include "mbedtls/md5.h"
#include <libb64/cencode.h>
static const char AUTHORIZATION_HEADER[] = "Authorization";
static const char qop_auth[] PROGMEM = "qop=auth";
static const char qop_auth_quoted[] PROGMEM = "qop=\"auth\"";
static const char WWW_Authenticate[] = "WWW-Authenticate";
static const char Content_Length[] = "Content-Length";
WebServer::WebServer(IPAddress addr, int port)
: _corsEnabled(false), _server(addr, port), _currentMethod(HTTP_ANY), _currentVersion(0), _currentStatus(HC_NONE),
_statusChange(0), _nullDelay(true), _currentHandler(nullptr), _firstHandler(nullptr), _lastHandler(nullptr),
_currentArgCount(0), _currentArgs(nullptr), _postArgsLen(0), _postArgs(nullptr), _headerKeysCount(0),
_currentHeaders(nullptr), _contentLength(0), _chunked(false) {
log_v("WebServer::Webserver(addr=%s, port=%d)", ipToString(addr).c_str(), port);
}
WebServer::WebServer(int port)
: _corsEnabled(false), _server(port), _currentMethod(HTTP_ANY), _currentVersion(0), _currentStatus(HC_NONE),
_statusChange(0), _nullDelay(true), _currentHandler(nullptr), _firstHandler(nullptr), _lastHandler(nullptr),
_currentArgCount(0), _currentArgs(nullptr), _postArgsLen(0), _postArgs(nullptr), _headerKeysCount(0),
_currentHeaders(nullptr), _contentLength(0), _chunked(false) {
log_v("WebServer::Webserver(port=%d)", port);
}
WebServer::~WebServer() {
_server.close();
if (_currentHeaders)
delete[] _currentHeaders;
RequestHandler *handler = _firstHandler;
while (handler) {
RequestHandler *next = handler->next();
delete handler;
handler = next;
}
}
void WebServer::begin() {
close();
_server.begin();
_server.setNoDelay(true);
}
void WebServer::begin(uint16_t port) {
close();
_server.begin(port);
_server.setNoDelay(true);
}
String WebServer::_extractParam(String &authReq, const String &param, const char delimit) {
int _begin = authReq.indexOf(param);
if (_begin == -1)
return "";
return authReq.substring(_begin + param.length(), authReq.indexOf(delimit, _begin + param.length()));
}
static String md5str(String &in) {
/* char out[33] = {0};
mbedtls_md5_context _ctx;
uint8_t i;
uint8_t *_buf = (uint8_t *)malloc(16);
if (_buf == NULL)
return String(out);
memset(_buf, 0x00, 16);
mbedtls_md5_init(&_ctx);
mbedtls_md5_starts_ret(&_ctx);
mbedtls_md5_update_ret(&_ctx, (const uint8_t *)in.c_str(), in.length());
mbedtls_md5_finish_ret(&_ctx, _buf);
for (i = 0; i < 16; i++) {
sprintf(out + (i * 2), "%02x", _buf[i]);
}
out[32] = 0;
free(_buf);
return String(out); */
return "";
}
bool WebServer::authenticate(const char *username, const char *password) {
if (hasHeader(FPSTR(AUTHORIZATION_HEADER))) {
String authReq = header(FPSTR(AUTHORIZATION_HEADER));
if (authReq.startsWith(F("Basic"))) {
authReq = authReq.substring(6);
authReq.trim();
char toencodeLen = strlen(username) + strlen(password) + 1;
char *toencode = new char[toencodeLen + 1];
if (toencode == NULL) {
authReq = "";
return false;
}
char *encoded = new char[base64_encode_expected_len(toencodeLen) + 1];
if (encoded == NULL) {
authReq = "";
delete[] toencode;
return false;
}
sprintf(toencode, "%s:%s", username, password);
if (base64_encode_chars(toencode, toencodeLen, encoded) > 0 && authReq.equals(encoded)) {
authReq = "";
delete[] toencode;
delete[] encoded;
return true;
}
delete[] toencode;
delete[] encoded;
} else if (authReq.startsWith(F("Digest"))) {
authReq = authReq.substring(7);
log_v("%s", authReq.c_str());
String _username = _extractParam(authReq, F("username=\""), '\"');
if (!_username.length() || _username != String(username)) {
authReq = "";
return false;
}
// extracting required parameters for RFC 2069 simpler Digest
String _realm = _extractParam(authReq, F("realm=\""), '\"');
String _nonce = _extractParam(authReq, F("nonce=\""), '\"');
String _uri = _extractParam(authReq, F("uri=\""), '\"');
String _response = _extractParam(authReq, F("response=\""), '\"');
String _opaque = _extractParam(authReq, F("opaque=\""), '\"');
if ((!_realm.length()) || (!_nonce.length()) || (!_uri.length()) || (!_response.length()) ||
(!_opaque.length())) {
authReq = "";
return false;
}
if ((_opaque != _sopaque) || (_nonce != _snonce) || (_realm != _srealm)) {
authReq = "";
return false;
}
// parameters for the RFC 2617 newer Digest
String _nc, _cnonce;
if (authReq.indexOf(FPSTR(qop_auth)) != -1 || authReq.indexOf(FPSTR(qop_auth_quoted)) != -1) {
_nc = _extractParam(authReq, F("nc="), ',');
_cnonce = _extractParam(authReq, F("cnonce=\""), '\"');
}
String _H1 = md5str(String(username) + ':' + _realm + ':' + String(password));
log_v("Hash of user:realm:pass=%s", _H1.c_str());
String _H2 = "";
if (_currentMethod == HTTP_GET) {
_H2 = md5str(String(F("GET:")) + _uri);
} else if (_currentMethod == HTTP_POST) {
_H2 = md5str(String(F("POST:")) + _uri);
} else if (_currentMethod == HTTP_PUT) {
_H2 = md5str(String(F("PUT:")) + _uri);
} else if (_currentMethod == HTTP_DELETE) {
_H2 = md5str(String(F("DELETE:")) + _uri);
} else {
_H2 = md5str(String(F("GET:")) + _uri);
}
log_v("Hash of GET:uri=%s", _H2.c_str());
String _responsecheck = "";
if (authReq.indexOf(FPSTR(qop_auth)) != -1 || authReq.indexOf(FPSTR(qop_auth_quoted)) != -1) {
_responsecheck = md5str(_H1 + ':' + _nonce + ':' + _nc + ':' + _cnonce + F(":auth:") + _H2);
} else {
_responsecheck = md5str(_H1 + ':' + _nonce + ':' + _H2);
}
log_v("The Proper response=%s", _responsecheck.c_str());
if (_response == _responsecheck) {
authReq = "";
return true;
}
}
authReq = "";
}
return false;
}
String WebServer::_getRandomHexString() {
char buffer[33]; // buffer to hold 32 Hex Digit + /0
int i;
for (i = 0; i < 4; i++) {
sprintf(buffer + (i * 8), "%08x", rand());
}
return String(buffer);
}
void WebServer::requestAuthentication(HTTPAuthMethod mode, const char *realm, const String &authFailMsg) {
if (realm == NULL) {
_srealm = String(F("Login Required"));
} else {
_srealm = String(realm);
}
if (mode == BASIC_AUTH) {
sendHeader(String(FPSTR(WWW_Authenticate)), String(F("Basic realm=\"")) + _srealm + String(F("\"")));
} else {
_snonce = _getRandomHexString();
_sopaque = _getRandomHexString();
sendHeader(
String(FPSTR(WWW_Authenticate)),
String(F("Digest realm=\"")) + _srealm + String(F("\", qop=\"auth\", nonce=\"")) + _snonce +
String(F("\", opaque=\"")) + _sopaque + String(F("\""))
);
}
using namespace mime;
send(401, String(FPSTR(mimeTable[html].mimeType)), authFailMsg);
}
void WebServer::on(const Uri &uri, WebServer::THandlerFunction handler) {
on(uri, HTTP_ANY, handler);
}
void WebServer::on(const Uri &uri, HTTPMethod method, WebServer::THandlerFunction fn) {
on(uri, method, fn, _fileUploadHandler);
}
void WebServer::on(const Uri &uri, HTTPMethod method, WebServer::THandlerFunction fn, WebServer::THandlerFunction ufn) {
_addRequestHandler(new FunctionRequestHandler(fn, ufn, uri, method));
}
void WebServer::addHandler(RequestHandler *handler) {
_addRequestHandler(handler);
}
void WebServer::_addRequestHandler(RequestHandler *handler) {
if (!_lastHandler) {
_firstHandler = handler;
_lastHandler = handler;
} else {
_lastHandler->next(handler);
_lastHandler = handler;
}
}
void WebServer::serveStatic(const char *uri, FS &fs, const char *path, const char *cache_header) {
_addRequestHandler(new StaticRequestHandler(fs, path, uri, cache_header));
}
void WebServer::handleClient() {
if (_currentStatus == HC_NONE) {
WiFiClient client = _server.available();
if (!client) {
if (_nullDelay) {
delay(1);
}
return;
}
log_v("New client: client.localIP()=%s", ipToString(client.localIP()).c_str());
_currentClient = client;
_currentStatus = HC_WAIT_READ;
_statusChange = millis();
}
bool keepCurrentClient = false;
bool callYield = false;
if (_currentClient.connected()) {
switch (_currentStatus) {
case HC_NONE:
// No-op to avoid C++ compiler warning
break;
case HC_WAIT_READ:
// Wait for data from client to become available
if (_currentClient.available()) {
if (_parseRequest(_currentClient)) {
// because HTTP_MAX_SEND_WAIT is expressed in milliseconds,
// it must be divided by 1000
_currentClient.setTimeout(HTTP_MAX_SEND_WAIT / 1000);
_contentLength = CONTENT_LENGTH_NOT_SET;
_handleRequest();
// Fix for issue with Chrome based browsers:
// https://github.com/espressif/arduino-esp32/issues/3652
// if (_currentClient.connected()) {
// _currentStatus = HC_WAIT_CLOSE;
// _statusChange = millis();
// keepCurrentClient = true;
// }
}
} else { // !_currentClient.available()
if (millis() - _statusChange <= HTTP_MAX_DATA_WAIT) {
keepCurrentClient = true;
}
callYield = true;
}
break;
case HC_WAIT_CLOSE:
// Wait for client to close the connection
if (millis() - _statusChange <= HTTP_MAX_CLOSE_WAIT) {
keepCurrentClient = true;
callYield = true;
}
}
}
if (!keepCurrentClient) {
_currentClient = WiFiClient();
_currentStatus = HC_NONE;
_currentUpload.reset();
}
if (callYield) {
yield();
}
}
void WebServer::close() {
_server.close();
_currentStatus = HC_NONE;
if (!_headerKeysCount)
collectHeaders(0, 0);
}
void WebServer::stop() {
close();
}
void WebServer::sendHeader(const String &name, const String &value, bool first) {
String headerLine = name;
headerLine += F(": ");
headerLine += value;
headerLine += "\r\n";
if (first) {
_responseHeaders = headerLine + _responseHeaders;
} else {
_responseHeaders += headerLine;
}
}
void WebServer::setContentLength(const size_t contentLength) {
_contentLength = contentLength;
}
void WebServer::enableDelay(boolean value) {
_nullDelay = value;
}
void WebServer::enableCORS(boolean value) {
_corsEnabled = value;
}
void WebServer::enableCrossOrigin(boolean value) {
enableCORS(value);
}
void WebServer::_prepareHeader(String &response, int code, const char *content_type, size_t contentLength) {
response = String(F("HTTP/1.")) + String(_currentVersion) + ' ';
response += String(code);
response += ' ';
response += _responseCodeToString(code);
response += "\r\n";
using namespace mime;
if (!content_type)
content_type = mimeTable[html].mimeType;
sendHeader(String(F("Content-Type")), String(FPSTR(content_type)), true);
if (_contentLength == CONTENT_LENGTH_NOT_SET) {
sendHeader(String(FPSTR(Content_Length)), String(contentLength));
} else if (_contentLength != CONTENT_LENGTH_UNKNOWN) {
sendHeader(String(FPSTR(Content_Length)), String(_contentLength));
} else if (_contentLength == CONTENT_LENGTH_UNKNOWN && _currentVersion) { // HTTP/1.1 or above client
// let's do chunked
_chunked = true;
sendHeader(String(F("Accept-Ranges")), String(F("none")));
sendHeader(String(F("Transfer-Encoding")), String(F("chunked")));
}
if (_corsEnabled) {
sendHeader(String(FPSTR("Access-Control-Allow-Origin")), String("*"));
sendHeader(String(FPSTR("Access-Control-Allow-Methods")), String("*"));
sendHeader(String(FPSTR("Access-Control-Allow-Headers")), String("*"));
}
sendHeader(String(F("Connection")), String(F("close")));
response += _responseHeaders;
response += "\r\n";
_responseHeaders = "";
}
void WebServer::send(int code, const char *content_type, const String &content) {
String header;
// Can we asume the following?
// if(code == 200 && content.length() == 0 && _contentLength == CONTENT_LENGTH_NOT_SET)
// _contentLength = CONTENT_LENGTH_UNKNOWN;
_prepareHeader(header, code, content_type, content.length());
_currentClientWrite(header.c_str(), header.length());
if (content.length())
sendContent(content);
}
void WebServer::send_P(int code, PGM_P content_type, PGM_P content) {
size_t contentLength = 0;
if (content != NULL) {
contentLength = strlen_P(content);
}
String header;
char type[64];
strncpy_P(type, (PGM_P)content_type, sizeof(type));
_prepareHeader(header, code, (const char *)type, contentLength);
_currentClientWrite(header.c_str(), header.length());
sendContent_P(content);
}
void WebServer::send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength) {
String header;
char type[64];
strncpy_P(type, (PGM_P)content_type, sizeof(type));
_prepareHeader(header, code, (const char *)type, contentLength);
sendContent(header);
sendContent_P(content, contentLength);
}
void WebServer::send(int code, char *content_type, const String &content) {
send(code, (const char *)content_type, content);
}
void WebServer::send(int code, const String &content_type, const String &content) {
send(code, (const char *)content_type.c_str(), content);
}
void WebServer::sendContent(const String &content) {
sendContent(content.c_str(), content.length());
}
void WebServer::sendContent(const char *content, size_t contentLength) {
const char *footer = "\r\n";
if (_chunked) {
char *chunkSize = (char *)malloc(11);
if (chunkSize) {
sprintf(chunkSize, "%x%s", contentLength, footer);
_currentClientWrite(chunkSize, strlen(chunkSize));
free(chunkSize);
}
}
_currentClientWrite(content, contentLength);
if (_chunked) {
_currentClient.write(footer, 2);
if (contentLength == 0) {
_chunked = false;
}
}
}
void WebServer::sendContent_P(PGM_P content) {
sendContent_P(content, strlen_P(content));
}
void WebServer::sendContent_P(PGM_P content, size_t size) {
const char *footer = "\r\n";
if (_chunked) {
char *chunkSize = (char *)malloc(11);
if (chunkSize) {
sprintf(chunkSize, "%x%s", size, footer);
_currentClientWrite(chunkSize, strlen(chunkSize));
free(chunkSize);
}
}
_currentClientWrite_P(content, size);
if (_chunked) {
_currentClient.write(footer, 2);
if (size == 0) {
_chunked = false;
}
}
}
void WebServer::_streamFileCore(const size_t fileSize, const String &fileName, const String &contentType) {
using namespace mime;
setContentLength(fileSize);
if (fileName.endsWith(String(FPSTR(mimeTable[gz].endsWith))) &&
contentType != String(FPSTR(mimeTable[gz].mimeType)) &&
contentType != String(FPSTR(mimeTable[none].mimeType))) {
sendHeader(F("Content-Encoding"), F("gzip"));
}
send(200, contentType, "");
}
String WebServer::pathArg(unsigned int i) {
if (_currentHandler != nullptr)
return _currentHandler->pathArg(i);
return "";
}
String WebServer::arg(String name) {
for (int j = 0; j < _postArgsLen; ++j) {
if (_postArgs[j].key == name)
return _postArgs[j].value;
}
for (int i = 0; i < _currentArgCount; ++i) {
if (_currentArgs[i].key == name)
return _currentArgs[i].value;
}
return "";
}
String WebServer::arg(int i) {
if (i < _currentArgCount)
return _currentArgs[i].value;
return "";
}
String WebServer::argName(int i) {
if (i < _currentArgCount)
return _currentArgs[i].key;
return "";
}
int WebServer::args() {
return _currentArgCount;
}
bool WebServer::hasArg(String name) {
for (int j = 0; j < _postArgsLen; ++j) {
if (_postArgs[j].key == name)
return true;
}
for (int i = 0; i < _currentArgCount; ++i) {
if (_currentArgs[i].key == name)
return true;
}
return false;
}
String WebServer::header(String name) {
for (int i = 0; i < _headerKeysCount; ++i) {
if (_currentHeaders[i].key.equalsIgnoreCase(name))
return _currentHeaders[i].value;
}
return "";
}
void WebServer::collectHeaders(const char *headerKeys[], const size_t headerKeysCount) {
_headerKeysCount = headerKeysCount + 1;
if (_currentHeaders)
delete[] _currentHeaders;
_currentHeaders = new RequestArgument[_headerKeysCount];
_currentHeaders[0].key = FPSTR(AUTHORIZATION_HEADER);
for (int i = 1; i < _headerKeysCount; i++) {
_currentHeaders[i].key = headerKeys[i - 1];
}
}
String WebServer::header(int i) {
if (i < _headerKeysCount)
return _currentHeaders[i].value;
return "";
}
String WebServer::headerName(int i) {
if (i < _headerKeysCount)
return _currentHeaders[i].key;
return "";
}
int WebServer::headers() {
return _headerKeysCount;
}
bool WebServer::hasHeader(String name) {
for (int i = 0; i < _headerKeysCount; ++i) {
if ((_currentHeaders[i].key.equalsIgnoreCase(name)) && (_currentHeaders[i].value.length() > 0))
return true;
}
return false;
}
String WebServer::hostHeader() {
return _hostHeader;
}
void WebServer::onFileUpload(THandlerFunction fn) {
_fileUploadHandler = fn;
}
void WebServer::onNotFound(THandlerFunction fn) {
_notFoundHandler = fn;
}
void WebServer::_handleRequest() {
bool handled = false;
if (!_currentHandler) {
log_e("request handler not found");
} else {
handled = _currentHandler->handle(*this, _currentMethod, _currentUri);
if (!handled) {
log_e("request handler failed to handle request");
}
}
if (!handled && _notFoundHandler) {
_notFoundHandler();
handled = true;
}
if (!handled) {
using namespace mime;
send(404, String(FPSTR(mimeTable[html].mimeType)), String(F("Not found: ")) + _currentUri);
handled = true;
}
if (handled) {
_finalizeResponse();
}
_currentUri = "";
}
void WebServer::_finalizeResponse() {
if (_chunked) {
sendContent("");
}
}
String WebServer::_responseCodeToString(int code) {
switch (code) {
case 100:
return F("Continue");
case 101:
return F("Switching Protocols");
case 200:
return F("OK");
case 201:
return F("Created");
case 202:
return F("Accepted");
case 203:
return F("Non-Authoritative Information");
case 204:
return F("No Content");
case 205:
return F("Reset Content");
case 206:
return F("Partial Content");
case 300:
return F("Multiple Choices");
case 301:
return F("Moved Permanently");
case 302:
return F("Found");
case 303:
return F("See Other");
case 304:
return F("Not Modified");
case 305:
return F("Use Proxy");
case 307:
return F("Temporary Redirect");
case 400:
return F("Bad Request");
case 401:
return F("Unauthorized");
case 402:
return F("Payment Required");
case 403:
return F("Forbidden");
case 404:
return F("Not Found");
case 405:
return F("Method Not Allowed");
case 406:
return F("Not Acceptable");
case 407:
return F("Proxy Authentication Required");
case 408:
return F("Request Time-out");
case 409:
return F("Conflict");
case 410:
return F("Gone");
case 411:
return F("Length Required");
case 412:
return F("Precondition Failed");
case 413:
return F("Request Entity Too Large");
case 414:
return F("Request-URI Too Large");
case 415:
return F("Unsupported Media Type");
case 416:
return F("Requested range not satisfiable");
case 417:
return F("Expectation Failed");
case 500:
return F("Internal Server Error");
case 501:
return F("Not Implemented");
case 502:
return F("Bad Gateway");
case 503:
return F("Service Unavailable");
case 504:
return F("Gateway Time-out");
case 505:
return F("HTTP Version not supported");
default:
return F("");
}
}
#endif // LT_ARD_HAS_WIFI

View File

@@ -0,0 +1,224 @@
/*
WebServer.h - Dead simple web-server.
Supports only one simultaneous client, knows how to handle GET and POST.
Copyright (c) 2014 Ivan Grokhotkov. All rights reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling)
*/
#pragma once
#include "HTTP_Method.h"
#include "Uri.h"
#include <WiFi.h>
#include <functional>
#include <memory>
enum HTTPUploadStatus { UPLOAD_FILE_START, UPLOAD_FILE_WRITE, UPLOAD_FILE_END, UPLOAD_FILE_ABORTED };
enum HTTPClientStatus { HC_NONE, HC_WAIT_READ, HC_WAIT_CLOSE };
enum HTTPAuthMethod { BASIC_AUTH, DIGEST_AUTH };
#define HTTP_DOWNLOAD_UNIT_SIZE 1436
#ifndef HTTP_UPLOAD_BUFLEN
#define HTTP_UPLOAD_BUFLEN 1436
#endif
#define HTTP_MAX_DATA_WAIT 5000 // ms to wait for the client to send the request
#define HTTP_MAX_POST_WAIT 5000 // ms to wait for POST data to arrive
#define HTTP_MAX_SEND_WAIT 5000 // ms to wait for data chunk to be ACKed
#define HTTP_MAX_CLOSE_WAIT 2000 // ms to wait for the client to close the connection
#define CONTENT_LENGTH_UNKNOWN ((size_t)-1)
#define CONTENT_LENGTH_NOT_SET ((size_t)-2)
class WebServer;
typedef struct {
HTTPUploadStatus status;
String filename;
String name;
String type;
size_t totalSize; // file size
size_t currentSize; // size of data currently in buf
uint8_t buf[HTTP_UPLOAD_BUFLEN];
} HTTPUpload;
#include "detail/RequestHandler.h"
namespace fs {
class FS;
}
class WebServer {
public:
WebServer(IPAddress addr, int port = 80);
WebServer(int port = 80);
virtual ~WebServer();
virtual void begin();
virtual void begin(uint16_t port);
virtual void handleClient();
virtual void close();
void stop();
bool authenticate(const char *username, const char *password);
void requestAuthentication(
HTTPAuthMethod mode = BASIC_AUTH, const char *realm = NULL, const String &authFailMsg = String("")
);
typedef std::function<void(void)> THandlerFunction;
void on(const Uri &uri, THandlerFunction fn);
void on(const Uri &uri, HTTPMethod method, THandlerFunction fn);
void on(const Uri &uri, HTTPMethod method, THandlerFunction fn, THandlerFunction ufn); // ufn handles file uploads
void addHandler(RequestHandler *handler);
void serveStatic(const char *uri, fs::FS &fs, const char *path, const char *cache_header = NULL);
void onNotFound(THandlerFunction fn); // called when handler is not assigned
void onFileUpload(THandlerFunction ufn); // handle file uploads
String uri() {
return _currentUri;
}
HTTPMethod method() {
return _currentMethod;
}
virtual WiFiClient client() {
return _currentClient;
}
HTTPUpload &upload() {
return *_currentUpload;
}
String pathArg(unsigned int i); // get request path argument by number
String arg(String name); // get request argument value by name
String arg(int i); // get request argument value by number
String argName(int i); // get request argument name by number
int args(); // get arguments count
bool hasArg(String name); // check if argument exists
void collectHeaders(const char *headerKeys[], const size_t headerKeysCount); // set the request headers to collect
String header(String name); // get request header value by name
String header(int i); // get request header value by number
String headerName(int i); // get request header name by number
int headers(); // get header count
bool hasHeader(String name); // check if header exists
String hostHeader(); // get request host header if available or empty String if not
// send response to the client
// code - HTTP response code, can be 200 or 404
// content_type - HTTP content type, like "text/plain" or "image/png"
// content - actual content body
void send(int code, const char *content_type = NULL, const String &content = String(""));
void send(int code, char *content_type, const String &content);
void send(int code, const String &content_type, const String &content);
void send_P(int code, PGM_P content_type, PGM_P content);
void send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength);
void enableDelay(boolean value);
void enableCORS(boolean value = true);
void enableCrossOrigin(boolean value = true);
void setContentLength(const size_t contentLength);
void sendHeader(const String &name, const String &value, bool first = false);
void sendContent(const String &content);
void sendContent(const char *content, size_t contentLength);
void sendContent_P(PGM_P content);
void sendContent_P(PGM_P content, size_t size);
static String urlDecode(const String &text);
template <typename T>
size_t streamFile(T &file, const String &contentType) {
_streamFileCore(file.size(), file.name(), contentType);
return _currentClient.write(file);
}
protected:
virtual size_t _currentClientWrite(const char *b, size_t l) {
return _currentClient.write(b, l);
}
virtual size_t _currentClientWrite_P(PGM_P b, size_t l) {
return _currentClient.write_P(b, l);
}
void _addRequestHandler(RequestHandler *handler);
void _handleRequest();
void _finalizeResponse();
bool _parseRequest(WiFiClient &client);
void _parseArguments(String data);
static String _responseCodeToString(int code);
bool _parseForm(WiFiClient &client, String boundary, uint32_t len);
bool _parseFormUploadAborted();
void _uploadWriteByte(uint8_t b);
int _uploadReadByte(WiFiClient &client);
void _prepareHeader(String &response, int code, const char *content_type, size_t contentLength);
bool _collectHeader(const char *headerName, const char *headerValue);
void _streamFileCore(const size_t fileSize, const String &fileName, const String &contentType);
String _getRandomHexString();
// for extracting Auth parameters
String _extractParam(String &authReq, const String &param, const char delimit = '"');
struct RequestArgument {
String key;
String value;
};
boolean _corsEnabled;
WiFiServer _server;
WiFiClient _currentClient;
HTTPMethod _currentMethod;
String _currentUri;
uint8_t _currentVersion;
HTTPClientStatus _currentStatus;
unsigned long _statusChange;
boolean _nullDelay;
RequestHandler *_currentHandler;
RequestHandler *_firstHandler;
RequestHandler *_lastHandler;
THandlerFunction _notFoundHandler;
THandlerFunction _fileUploadHandler;
int _currentArgCount;
RequestArgument *_currentArgs;
int _postArgsLen;
RequestArgument *_postArgs;
std::unique_ptr<HTTPUpload> _currentUpload;
int _headerKeysCount;
RequestArgument *_currentHeaders;
size_t _contentLength;
String _responseHeaders;
String _hostHeader;
bool _chunked;
String _snonce; // Store noance and opaque for future comparison
String _sopaque;
String _srealm; // Store the Auth realm between Calls
};

View File

@@ -0,0 +1,53 @@
#pragma once
#include <assert.h>
#include <vector>
class RequestHandler {
public:
virtual ~RequestHandler() {}
virtual bool canHandle(HTTPMethod method, String uri) {
(void)method;
(void)uri;
return false;
}
virtual bool canUpload(String uri) {
(void)uri;
return false;
}
virtual bool handle(WebServer &server, HTTPMethod requestMethod, String requestUri) {
(void)server;
(void)requestMethod;
(void)requestUri;
return false;
}
virtual void upload(WebServer &server, String requestUri, HTTPUpload &upload) {
(void)server;
(void)requestUri;
(void)upload;
}
RequestHandler *next() {
return _next;
}
void next(RequestHandler *r) {
_next = r;
}
private:
RequestHandler *_next = nullptr;
protected:
std::vector<String> pathArgs;
public:
const String &pathArg(unsigned int i) {
assert(i < pathArgs.size());
return pathArgs[i];
}
};

View File

@@ -0,0 +1,149 @@
#pragma once
#include "RequestHandler.h"
#include "Uri.h"
#include "WString.h"
#include "mimetable.h"
using namespace mime;
class FunctionRequestHandler : public RequestHandler {
public:
FunctionRequestHandler(
WebServer::THandlerFunction fn, WebServer::THandlerFunction ufn, const Uri &uri, HTTPMethod method
)
: _fn(fn), _ufn(ufn), _uri(uri.clone()), _method(method) {
_uri->initPathArgs(pathArgs);
}
~FunctionRequestHandler() {
delete _uri;
}
bool canHandle(HTTPMethod requestMethod, String requestUri) override {
if (_method != HTTP_ANY && _method != requestMethod)
return false;
return _uri->canHandle(requestUri, pathArgs);
}
bool canUpload(String requestUri) override {
if (!_ufn || !canHandle(HTTP_POST, requestUri))
return false;
return true;
}
bool handle(WebServer &server, HTTPMethod requestMethod, String requestUri) override {
(void)server;
if (!canHandle(requestMethod, requestUri))
return false;
_fn();
return true;
}
void upload(WebServer &server, String requestUri, HTTPUpload &upload) override {
(void)server;
(void)upload;
if (canUpload(requestUri))
_ufn();
}
protected:
WebServer::THandlerFunction _fn;
WebServer::THandlerFunction _ufn;
Uri *_uri;
HTTPMethod _method;
};
class StaticRequestHandler : public RequestHandler {
public:
StaticRequestHandler(FS &fs, const char *path, const char *uri, const char *cache_header)
: _fs(fs), _uri(uri), _path(path), _cache_header(cache_header) {
File f = fs.open(path);
_isFile = (f && (!f.isDirectory()));
log_v(
"StaticRequestHandler: path=%s uri=%s isFile=%d, cache_header=%s\r\n",
path,
uri,
_isFile,
cache_header ? cache_header : ""
); // issue 5506 - cache_header can be nullptr
_baseUriLength = _uri.length();
}
bool canHandle(HTTPMethod requestMethod, String requestUri) override {
if (requestMethod != HTTP_GET)
return false;
if ((_isFile && requestUri != _uri) || !requestUri.startsWith(_uri))
return false;
return true;
}
bool handle(WebServer &server, HTTPMethod requestMethod, String requestUri) override {
if (!canHandle(requestMethod, requestUri))
return false;
log_v("StaticRequestHandler::handle: request=%s _uri=%s\r\n", requestUri.c_str(), _uri.c_str());
String path(_path);
if (!_isFile) {
// Base URI doesn't point to a file.
// If a directory is requested, look for index file.
if (requestUri.endsWith("/"))
requestUri += "index.htm";
// Append whatever follows this URI in request to get the file path.
path += requestUri.substring(_baseUriLength);
}
log_v("StaticRequestHandler::handle: path=%s, isFile=%d\r\n", path.c_str(), _isFile);
String contentType = getContentType(path);
// look for gz file, only if the original specified path is not a gz. So part only works to send gzip via
// content encoding when a non compressed is asked for if you point the the path to gzip you will serve the gzip
// as content type "application/x-gzip", not text or javascript etc...
if (!path.endsWith(FPSTR(mimeTable[gz].endsWith)) && !_fs.exists(path)) {
String pathWithGz = path + FPSTR(mimeTable[gz].endsWith);
if (_fs.exists(pathWithGz))
path += FPSTR(mimeTable[gz].endsWith);
}
File f = _fs.open(path, "r");
if (!f || !f.available())
return false;
if (_cache_header.length() != 0)
server.sendHeader("Cache-Control", _cache_header);
server.streamFile(f, contentType);
return true;
}
static String getContentType(const String &path) {
char buff[sizeof(mimeTable[0].mimeType)];
// Check all entries but last one for match, return if found
for (size_t i = 0; i < sizeof(mimeTable) / sizeof(mimeTable[0]) - 1; i++) {
strcpy_P(buff, mimeTable[i].endsWith);
if (path.endsWith(buff)) {
strcpy_P(buff, mimeTable[i].mimeType);
return String(buff);
}
}
// Fall-through and just return default type
strcpy_P(buff, mimeTable[sizeof(mimeTable) / sizeof(mimeTable[0]) - 1].mimeType);
return String(buff);
}
protected:
FS _fs;
String _uri;
String _path;
String _cache_header;
bool _isFile;
size_t _baseUriLength;
};

View File

@@ -0,0 +1,33 @@
#include "mimetable.h"
#include "pgmspace.h"
namespace mime {
// Table of extension->MIME strings stored in PROGMEM, needs to be global due to GCC section typing rules
const Entry mimeTable[maxType] = {
{".html", "text/html" },
{".htm", "text/html" },
{".css", "text/css" },
{".txt", "text/plain" },
{".js", "application/javascript" },
{".json", "application/json" },
{".png", "image/png" },
{".gif", "image/gif" },
{".jpg", "image/jpeg" },
{".ico", "image/x-icon" },
{".svg", "image/svg+xml" },
{".ttf", "application/x-font-ttf" },
{".otf", "application/x-font-opentype" },
{".woff", "application/font-woff" },
{".woff2", "application/font-woff2" },
{".eot", "application/vnd.ms-fontobject"},
{".sfnt", "application/font-sfnt" },
{".xml", "text/xml" },
{".pdf", "application/pdf" },
{".zip", "application/zip" },
{".gz", "application/x-gzip" },
{".appcache", "text/cache-manifest" },
{"", "application/octet-stream" }
};
} // namespace mime

View File

@@ -0,0 +1,38 @@
#pragma once
namespace mime {
enum type {
html,
htm,
css,
txt,
js,
json,
png,
gif,
jpg,
ico,
svg,
ttf,
otf,
woff,
woff2,
eot,
sfnt,
xml,
pdf,
zip,
gz,
appcache,
none,
maxType
};
struct Entry {
const char endsWith[16];
const char mimeType[32];
};
extern const Entry mimeTable[maxType];
} // namespace mime

View File

@@ -0,0 +1,61 @@
#pragma once
#include "Uri.h"
class UriBraces : public Uri {
public:
explicit UriBraces(const char *uri) : Uri(uri){};
explicit UriBraces(const String &uri) : Uri(uri){};
Uri *clone() const override final {
return new UriBraces(_uri);
};
void initPathArgs(std::vector<String> &pathArgs) override final {
int numParams = 0, start = 0;
do {
start = _uri.indexOf("{}", start);
if (start > 0) {
numParams++;
start += 2;
}
} while (start > 0);
pathArgs.resize(numParams);
}
bool canHandle(const String &requestUri, std::vector<String> &pathArgs) override final {
if (Uri::canHandle(requestUri, pathArgs))
return true;
size_t uriLength = _uri.length();
unsigned int pathArgIndex = 0;
unsigned int requestUriIndex = 0;
for (unsigned int i = 0; i < uriLength; i++, requestUriIndex++) {
char uriChar = _uri[i];
char requestUriChar = requestUri[requestUriIndex];
if (uriChar == requestUriChar)
continue;
if (uriChar != '{')
return false;
i += 2; // index of char after '}'
if (i >= uriLength) {
// there is no char after '}'
pathArgs[pathArgIndex] = requestUri.substring(requestUriIndex);
return pathArgs[pathArgIndex].indexOf("/") == -1; // path argument may not contain a '/'
} else {
char charEnd = _uri[i];
int uriIndex = requestUri.indexOf(charEnd, requestUriIndex);
if (uriIndex < 0)
return false;
pathArgs[pathArgIndex] = requestUri.substring(requestUriIndex, uriIndex);
requestUriIndex = (unsigned int)uriIndex;
}
pathArgIndex++;
}
return requestUriIndex >= requestUri.length();
}
};

View File

@@ -0,0 +1,19 @@
#pragma once
#include "Uri.h"
#include <fnmatch.h>
class UriGlob : public Uri {
public:
explicit UriGlob(const char *uri) : Uri(uri){};
explicit UriGlob(const String &uri) : Uri(uri){};
Uri *clone() const override final {
return new UriGlob(_uri);
};
bool canHandle(const String &requestUri, __attribute__((unused)) std::vector<String> &pathArgs) override final {
return fnmatch(_uri.c_str(), requestUri.c_str(), 0) == 0;
}
};

View File

@@ -0,0 +1,41 @@
#pragma once
#include "Uri.h"
#include <regex>
class UriRegex : public Uri {
public:
explicit UriRegex(const char *uri) : Uri(uri){};
explicit UriRegex(const String &uri) : Uri(uri){};
Uri *clone() const override final {
return new UriRegex(_uri);
};
void initPathArgs(std::vector<String> &pathArgs) override final {
std::regex rgx((_uri + "|").c_str());
std::smatch matches;
std::string s{""};
std::regex_search(s, matches, rgx);
pathArgs.resize(matches.size() - 1);
}
bool canHandle(const String &requestUri, std::vector<String> &pathArgs) override final {
if (Uri::canHandle(requestUri, pathArgs))
return true;
unsigned int pathArgIndex = 0;
std::regex rgx(_uri.c_str());
std::smatch matches;
std::string s(requestUri.c_str());
if (std::regex_search(s, matches, rgx)) {
for (size_t i = 1; i < matches.size(); ++i) { // skip first
pathArgs[pathArgIndex] = String(matches[i].str().c_str());
pathArgIndex++;
}
return true;
}
return false;
}
};

View File

@@ -0,0 +1,248 @@
/**
*
* @file WiFiMulti.cpp
* @date 16.05.2015
* @author Markus Sattler
*
* Copyright (c) 2015 Markus Sattler. All rights reserved.
* This file is part of the esp8266 core for Arduino environment.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#if LT_ARD_HAS_WIFI
#include "WiFiMulti.h"
#include <Arduino.h>
#include <limits.h>
#include <string.h>
WiFiMulti::WiFiMulti() {}
WiFiMulti::~WiFiMulti() {
for (uint32_t i = 0; i < APlist.size(); i++) {
WifiAPlist_t entry = APlist[i];
if (entry.ssid) {
free(entry.ssid);
}
if (entry.passphrase) {
free(entry.passphrase);
}
}
APlist.clear();
}
bool WiFiMulti::addAP(const char *ssid, const char *passphrase) {
WifiAPlist_t newAP;
if (!ssid || *ssid == 0x00 || strlen(ssid) > 31) {
// fail SSID too long or missing!
LT_EM(WIFI, "SSID missing or too long");
return false;
}
if (passphrase && strlen(passphrase) > 64) {
// fail passphrase too long!
LT_EM(WIFI, "Passphrase too long");
return false;
}
newAP.ssid = strdup(ssid);
if (!newAP.ssid) {
LT_EM(WIFI, "Fail newAP.ssid == 0");
return false;
}
if (passphrase && *passphrase != 0x00) {
newAP.passphrase = strdup(passphrase);
if (!newAP.passphrase) {
LT_EM(WIFI, "Fail newAP.passphrase == 0");
free(newAP.ssid);
return false;
}
} else {
newAP.passphrase = NULL;
}
APlist.push_back(newAP);
LT_VM(WIFI, "Add SSID: %s", newAP.ssid);
return true;
}
uint8_t WiFiMulti::run(uint32_t connectTimeout) {
int8_t scanResult;
uint8_t status = WiFi.status();
if (status == WL_CONNECTED) {
for (uint32_t x = 0; x < APlist.size(); x++) {
if (WiFi.SSID() == APlist[x].ssid) {
return status;
}
}
WiFi.disconnect(false);
delay(10);
status = WiFi.status();
}
scanResult = WiFi.scanNetworks();
if (scanResult == WIFI_SCAN_RUNNING) {
// scan is running
return WL_NO_SSID_AVAIL;
} else if (scanResult >= 0) {
// scan done analyze
WifiAPlist_t bestNetwork{NULL, NULL};
int bestNetworkDb = INT_MIN;
uint8_t bestBSSID[6];
int32_t bestChannel = 0;
LT_IM(WIFI, "Scan finished");
if (scanResult == 0) {
LT_IM(WIFI, "No networks found");
} else {
LT_IM(WIFI, "%d networks found", scanResult);
for (int8_t i = 0; i < scanResult; ++i) {
String ssid_scan;
int32_t rssi_scan;
WiFiAuthMode sec_scan;
uint8_t *BSSID_scan;
int32_t chan_scan;
WiFi.getNetworkInfo(i, ssid_scan, sec_scan, rssi_scan, BSSID_scan, chan_scan);
bool known = false;
for (uint32_t x = APlist.size(); x > 0; x--) {
WifiAPlist_t entry = APlist[x - 1];
if (ssid_scan == entry.ssid) { // SSID match
known = true;
if (rssi_scan > bestNetworkDb) { // best network
if (sec_scan == WIFI_AUTH_OPEN ||
entry.passphrase) { // check for passphrase if not open wlan
bestNetworkDb = rssi_scan;
bestChannel = chan_scan;
memcpy((void *)&bestNetwork, (void *)&entry, sizeof(bestNetwork));
memcpy((void *)&bestBSSID, (void *)BSSID_scan, sizeof(bestBSSID));
}
}
break;
}
}
if (known) {
LT_DM(
WIFI,
" ---> %d: [%d][%02X:%02X:%02X:%02X:%02X:%02X] %s (%d) %c",
i,
chan_scan,
BSSID_scan[0],
BSSID_scan[1],
BSSID_scan[2],
BSSID_scan[3],
BSSID_scan[4],
BSSID_scan[5],
ssid_scan.c_str(),
rssi_scan,
(sec_scan == WIFI_AUTH_OPEN) ? ' ' : '*'
);
} else {
LT_DM(
WIFI,
" %d: [%d][%02X:%02X:%02X:%02X:%02X:%02X] %s (%d) %c",
i,
chan_scan,
BSSID_scan[0],
BSSID_scan[1],
BSSID_scan[2],
BSSID_scan[3],
BSSID_scan[4],
BSSID_scan[5],
ssid_scan.c_str(),
rssi_scan,
(sec_scan == WIFI_AUTH_OPEN) ? ' ' : '*'
);
}
}
}
// clean up ram
WiFi.scanDelete();
if (bestNetwork.ssid) {
LT_IM(
WIFI,
"Connecting to BSSID: %02X:%02X:%02X:%02X:%02X:%02X SSID: %s Channel: %d (%d)",
bestBSSID[0],
bestBSSID[1],
bestBSSID[2],
bestBSSID[3],
bestBSSID[4],
bestBSSID[5],
bestNetwork.ssid,
bestChannel,
bestNetworkDb
);
WiFi.begin(bestNetwork.ssid, bestNetwork.passphrase, bestChannel, bestBSSID);
status = WiFi.status();
auto startTime = millis();
// wait for connection, fail, or timeout
while (status != WL_CONNECTED && status != WL_NO_SSID_AVAIL && status != WL_CONNECT_FAILED &&
(millis() - startTime) <= connectTimeout) {
delay(10);
status = WiFi.status();
}
IPAddress ip;
switch (status) {
case WL_CONNECTED:
LT_IM(WIFI, "Connecting done");
LT_DM(WIFI, "SSID: %s", WiFi.SSID().c_str());
// TODO fix this after implementing IP format for printf()
ip = WiFi.localIP();
LT_DM(WIFI, "IP: %u.%u.%u.%u", ip[0], ip[1], ip[2], ip[3]);
LT_DM(WIFI, "MAC: %s", WiFi.BSSIDstr().c_str());
LT_DM(WIFI, "Channel: %d", WiFi.channel());
break;
case WL_NO_SSID_AVAIL:
LT_EM(WIFI, "Connecting failed; AP not found");
break;
case WL_CONNECT_FAILED:
LT_EM(WIFI, "Connecting failed");
break;
default:
LT_EM(WIFI, "Connecting failed (%d)", status);
break;
}
} else {
LT_EM(WIFI, "No matching network found!");
}
} else {
// start scan
LT_VM(WIFI, "Delete old wifi config...");
WiFi.disconnect();
LT_DM(WIFI, "Start scan");
// scan wifi async mode
WiFi.scanNetworks(true);
}
return status;
}
#endif // LT_ARD_HAS_WIFI

Some files were not shown because too many files have changed in this diff Show More