diff --git a/CMakeLists.txt b/CMakeLists.txt index 579faa4dc..be757da0b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -598,7 +598,8 @@ else() else() if (LWS_WITH_ESP32) list(APPEND SOURCES - lib/lws-plat-esp32.c) + lib/lws-plat-esp32.c + lib/romfs.c) else() list(APPEND SOURCES lib/lws-plat-unix.c) diff --git a/component.mk b/component.mk index 7617999d5..becad0fc3 100644 --- a/component.mk +++ b/component.mk @@ -17,6 +17,7 @@ build: cmake $(COMPONENT_PATH) -DLWS_C_FLAGS="$(CFLAGS)" \ -DCROSS_PATH=$(CROSS_PATH) \ -DCOMPONENT_PATH=$(COMPONENT_PATH) \ + -DBUILD_DIR_BASE=$(BUILD_DIR_BASE) \ -DCMAKE_TOOLCHAIN_FILE=$(COMPONENT_PATH)/cross-esp32.cmake \ -DCMAKE_BUILD_TYPE=RELEASE \ -DLWS_WITH_NO_LOGS=0 \ diff --git a/cross-esp32.cmake b/cross-esp32.cmake index 2dc0a1014..a0b3c1d34 100644 --- a/cross-esp32.cmake +++ b/cross-esp32.cmake @@ -12,7 +12,7 @@ set(CMAKE_SYSTEM_NAME Linux) # Name of C compiler. set(CMAKE_C_COMPILER "${CROSS_PATH}/bin/xtensa-esp32-elf-gcc") -SET(CMAKE_C_FLAGS "-nostdlib -Wall -Werror -I${COMPONENT_PATH}/../lwip/include/lwip/posix -I${COMPONENT_PATH}/../lwip/include/lwip -I${COMPONENT_PATH}/../lwip/include/lwip/port -I${COMPONENT_PATH}/../esp32/include/ ${LWS_C_FLAGS} -I${COMPONENT_PATH}/../nvs_flash/test_nvs_host -I${COMPONENT_PATH}/../freertos/include -Os" CACHE STRING "" FORCE) +SET(CMAKE_C_FLAGS "-nostdlib -Wall -Werror -I${BUILD_DIR_BASE}/include -I${COMPONENT_PATH}/../driver/include -I${COMPONENT_PATH}/../spi_flash/include -I${COMPONENT_PATH}/../nvs_flash/include -I${COMPONENT_PATH}/../tcpip_adapter/include -I${COMPONENT_PATH}/../lwip/include/lwip/posix -I${COMPONENT_PATH}/../lwip/include/lwip -I${COMPONENT_PATH}/../lwip/include/lwip/port -I${COMPONENT_PATH}/../esp32/include/ ${LWS_C_FLAGS} -I${COMPONENT_PATH}/../nvs_flash/test_nvs_host -I${COMPONENT_PATH}/../freertos/include -Os" CACHE STRING "" FORCE) # Where to look for the target environment. (More paths can be added here) set(CMAKE_FIND_ROOT_PATH "${CROSS_PATH}") diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h index cfe403b0d..9e92e2233 100644 --- a/lib/libwebsockets.h +++ b/lib/libwebsockets.h @@ -460,6 +460,16 @@ struct pollfd { #define POLLHUP 0x0010 #define POLLNVAL 0x0020 +#include +#include +#include +#include "esp_wifi.h" +#include "esp_system.h" +#include "esp_event.h" +#include "esp_event_loop.h" +#include "nvs.h" +#include "driver/gpio.h" +#include "esp_spi_flash.h" #include "freertos/timers.h" #if !defined(CONFIG_FREERTOS_HZ) @@ -513,8 +523,35 @@ static inline void uv_close(uv_handle_t *h, void *v) xTimerDelete(*(uv_timer_t *)h, 0); } +/* ESP32 helper declarations */ +#define LWS_PLUGIN_STATIC +/* user code provides these */ + +extern char lws_esp32_model[16]; + +extern int +lws_esp32_is_booting_in_ap_mode(void); +extern void +lws_esp32_identify_physical_device(void); + +/* lws-plat-esp32 provides these */ + +extern void (*lws_cb_scan_done)(void *); +extern void *lws_cb_scan_done_arg; + +extern char lws_esp32_serial[], lws_esp32_force_ap, lws_esp32_region; + +extern esp_err_t +lws_esp32_event_passthru(void *ctx, system_event_t *event); +extern void +lws_esp32_wlan_config(void); +extern void +lws_esp32_wlan_start(void); +struct lws_context_creation_info; +extern struct lws_context * +lws_esp32_init(struct lws_context_creation_info *, unsigned int _romfs); #else typedef int lws_sockfd_type; diff --git a/lib/lws-plat-esp32.c b/lib/lws-plat-esp32.c index b706cf64d..d78700505 100644 --- a/lib/lws-plat-esp32.c +++ b/lib/lws-plat-esp32.c @@ -528,3 +528,277 @@ char *ERR_error_string(unsigned long e, char *buf) return "unknown"; } + + +/* helper functionality */ + +#include "romfs.h" + +void (*lws_cb_scan_done)(void *); +void *lws_cb_scan_done_arg; +char lws_esp32_serial[16] = "unknown", lws_esp32_force_ap = 0, + lws_esp32_region = WIFI_COUNTRY_US; // default to safest option + +static romfs_t lws_esp32_romfs; + +/* + * configuration related to the AP setup website + * + * The 'esplws-scan' protocol drives the configuration + * site, and updates the scan results in realtime over + * a websocket link. + */ + +#include "../plugins/protocol_esp32_lws_scan.c" + +static const struct lws_protocols protocols_ap[] = { + { + "http-only", + lws_callback_http_dummy, + 0, /* per_session_data_size */ + 900, 0, NULL + }, + LWS_PLUGIN_PROTOCOL_ESPLWS_SCAN, + { NULL, NULL, 0, 0, 0, NULL } /* terminator */ +}; + +static const struct lws_protocol_vhost_options ap_pvo = { + NULL, + NULL, + "esplws-scan", + "" +}; + +static const struct lws_http_mount mount_ap = { + .mountpoint = "/", + .origin = "/ap", + .def = "index.html", + .origin_protocol = LWSMPRO_FILE, + .mountpoint_len = 1, +}; + +struct esp32_file { + const struct inode *i; +}; + +esp_err_t lws_esp32_event_passthru(void *ctx, system_event_t *event) +{ + switch(event->event_id) { + case SYSTEM_EVENT_SCAN_DONE: + if (lws_cb_scan_done) + lws_cb_scan_done(lws_cb_scan_done_arg); + break; + case SYSTEM_EVENT_STA_START: + esp_wifi_connect(); + break; + case SYSTEM_EVENT_STA_DISCONNECTED: + /* This is a workaround as ESP32 WiFi libs don't currently + auto-reassociate. */ + esp_wifi_connect(); + break; + default: + break; + } + return ESP_OK; +} + +static lws_fop_fd_t IRAM_ATTR +esp32_lws_fops_open(const struct lws_plat_file_ops *fops, const char *filename, + const char *vfs_path, lws_fop_flags_t *flags) +{ + struct esp32_file *f = malloc(sizeof(*f)); + lws_fop_fd_t fop_fd; + size_t len; + + lwsl_notice("%s: %s\n", __func__, filename); + + if (!f) + return NULL; + + f->i = romfs_get_info(lws_esp32_romfs, filename, &len); + if (!f->i) + goto bail; + + fop_fd = malloc(sizeof(*fop_fd)); + if (!fop_fd) + goto bail; + + fop_fd->fops = fops; + fop_fd->filesystem_priv = f; + fop_fd->flags = *flags; + + fop_fd->len = len; + fop_fd->pos = 0; + + return fop_fd; + +bail: + free(f); + + return NULL; +} + +static int IRAM_ATTR +esp32_lws_fops_close(lws_fop_fd_t *fop_fd) +{ + free((*fop_fd)->filesystem_priv); + free(*fop_fd); + + *fop_fd = NULL; + + return 0; +} +static lws_fileofs_t IRAM_ATTR +esp32_lws_fops_seek_cur(lws_fop_fd_t fop_fd, lws_fileofs_t offset_from_cur_pos) +{ + fop_fd->pos += offset_from_cur_pos; + + if (fop_fd->pos > fop_fd->len) + fop_fd->pos = fop_fd->len; + + return 0; +} + +static int IRAM_ATTR +esp32_lws_fops_read(lws_fop_fd_t fop_fd, lws_filepos_t *amount, uint8_t *buf, + lws_filepos_t len) +{ + struct esp32_file *f = fop_fd->filesystem_priv; + + if ((long)buf & 3) { + lwsl_err("misaligned buf\n"); + + return -1; + } + + if (fop_fd->pos >= fop_fd->len) + return 0; + + if (len > fop_fd->len - fop_fd->pos) + len = fop_fd->len - fop_fd->pos; + + spi_flash_read((uint32_t)(char *)f->i + fop_fd->pos, buf, len); + + *amount = len; + fop_fd->pos += len; + + return 0; +} + +static const struct lws_plat_file_ops fops = { + .LWS_FOP_OPEN = esp32_lws_fops_open, + .LWS_FOP_CLOSE = esp32_lws_fops_close, + .LWS_FOP_READ = esp32_lws_fops_read, + .LWS_FOP_SEEK_CUR = esp32_lws_fops_seek_cur, +}; + +static wifi_config_t sta_config = { + .sta = { + .bssid_set = false + } + }, ap_config = { + .ap = { + .channel = 6, + .authmode = WIFI_AUTH_OPEN, + .max_connection = 1, + } + }; + +void +lws_esp32_wlan_config(void) +{ + nvs_handle nvh; + char r[2]; + size_t s; + + ESP_ERROR_CHECK(nvs_open("lws-station", NVS_READWRITE, &nvh)); + + s = sizeof(sta_config.sta.ssid) - 1; + if (nvs_get_str(nvh, "ssid", (char *)sta_config.sta.ssid, &s) != ESP_OK) + lws_esp32_force_ap = 1; + s = sizeof(sta_config.sta.password) - 1; + if (nvs_get_str(nvh, "password", (char *)sta_config.sta.password, &s) != ESP_OK) + lws_esp32_force_ap = 1; + s = sizeof(lws_esp32_serial) - 1; + if (nvs_get_str(nvh, "serial", lws_esp32_serial, &s) != ESP_OK) + lws_esp32_force_ap = 1; + else + snprintf((char *)ap_config.ap.ssid, sizeof(ap_config.ap.ssid) - 1, + "config-%s-%s", lws_esp32_model, lws_esp32_serial); + s = sizeof(r); + if (nvs_get_str(nvh, "region", r, &s) != ESP_OK) + lws_esp32_force_ap = 1; + else + lws_esp32_region = atoi(r); + + nvs_close(nvh); + + tcpip_adapter_init(); +} + +void +lws_esp32_wlan_start(void) +{ + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + + ESP_ERROR_CHECK( esp_wifi_init(&cfg)); + ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM)); + ESP_ERROR_CHECK( esp_wifi_set_country(lws_esp32_region)); + + if (!lws_esp32_is_booting_in_ap_mode() && !lws_esp32_force_ap) { + ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA)); + ESP_ERROR_CHECK( esp_wifi_set_config(WIFI_IF_STA, &sta_config)); + } else { + ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_APSTA) ); + ESP_ERROR_CHECK( esp_wifi_set_config(WIFI_IF_AP, &ap_config) ); + } + + ESP_ERROR_CHECK( esp_wifi_start()); + tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_STA, (const char *)&ap_config.ap.ssid[7]); + + if (!lws_esp32_is_booting_in_ap_mode() && !lws_esp32_force_ap) + ESP_ERROR_CHECK( esp_wifi_connect()); +} + +struct lws_context * +lws_esp32_init(struct lws_context_creation_info *info, unsigned int _romfs) +{ + size_t romfs_size; + struct lws_context *context; + + lws_set_log_level(65535, lwsl_emit_syslog); + + context = lws_create_context(info); + if (context == NULL) { + lwsl_err("Failed to create context\n"); + return NULL; + } + + lws_esp32_romfs = (romfs_t)(void *)_romfs; + romfs_size = romfs_mount_check(lws_esp32_romfs); + if (!romfs_size) { + lwsl_err("Failed to mount ROMFS\n"); + return NULL; + } + + lwsl_notice("ROMFS length %uKiB\n", romfs_size >> 10); + + /* set the lws vfs to use our romfs */ + + lws_set_fops(context, &fops); + + if (lws_esp32_is_booting_in_ap_mode() || lws_esp32_force_ap) { + info->vhost_name = "ap"; + info->protocols = protocols_ap; + info->mounts = &mount_ap; + info->pvo = &ap_pvo; + } + + if (!lws_create_vhost(context, info)) + lwsl_err("Failed to create vhost\n"); + + lws_protocol_init(context); + + return context; +} + diff --git a/lib/romfs.c b/lib/romfs.c new file mode 100644 index 000000000..f1f509304 --- /dev/null +++ b/lib/romfs.c @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2017 National Institute of Advanced Industrial Science + * and Technology (AIST) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of AIST nor the names of its contributors may be used + * to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include "romfs.h" +#include "esp_spi_flash.h" + +#define RFS_STRING_MAX 64 + +static u32_be_t cache[(RFS_STRING_MAX + 32) / 4]; +static romfs_inode_t ci = (romfs_inode_t)cache; +static romfs_t cr = (romfs_t)cache; + +static void +set_cache(romfs_inode_t inode, size_t len) +{ + spi_flash_read((uint32_t)inode, cache, len); +} + +static uint32_t +ntohl(const u32_be_t be) +{ + return ((be >> 24) & 0xff) | + ((be >> 16) & 0xff) << 8 | + ((be >> 8) & 0xff) << 16 | + (be & 0xff) << 24; +} +static romfs_inode_t +romfs_lookup(romfs_t romfs, romfs_inode_t start, const char *path); + +static int +plus_padding(const uint8_t *s) +{ + int n; + + set_cache((romfs_inode_t)s, RFS_STRING_MAX); + n = strlen((const char *)cache); + + if (!(n & 15)) + n += 0x10; + + return (n + 15) & ~15; +} + +static romfs_inode_t +skip_and_pad(romfs_inode_t ri) +{ + const uint8_t *p = ((const uint8_t *)ri) + sizeof(*ri); + + return (romfs_inode_t)(p + plus_padding(p)); +} + +size_t +romfs_mount_check(romfs_t romfs) +{ + set_cache((romfs_inode_t)romfs, sizeof(*romfs)); + + if (cr->magic1 != 0x6d6f722d || + cr->magic2 != 0x2d736631) + return 0; + + return ntohl(cr->size); +} + +static romfs_inode_t +romfs_symlink(romfs_t romfs, romfs_inode_t level, romfs_inode_t i) +{ + const char *p = (const char *)skip_and_pad(i); + + if (*p == '/') { + level = skip_and_pad((romfs_inode_t)romfs); + p++; + } + + return romfs_lookup(romfs, level, p); +} + +static romfs_inode_t +dir_link(romfs_t romfs, romfs_inode_t i) +{ + set_cache(i, sizeof(*i)); + return (romfs_inode_t)((const uint8_t *)romfs + + ntohl(ci->dir_start)); +} + +static romfs_inode_t +romfs_lookup(romfs_t romfs, romfs_inode_t start, const char *path) +{ + romfs_inode_t level, i = start; + const char *p, *n, *cp; + uint32_t next_be; + + if (start == (romfs_inode_t)romfs) + i = skip_and_pad((romfs_inode_t)romfs); + level = i; + while (i != (romfs_inode_t)romfs) { + p = path; + n = ((const char *)i) + sizeof(*i); + + set_cache(i, sizeof(*i)); + next_be = ci->next; + + cp = (const char *)cache; + set_cache((romfs_inode_t)n, RFS_STRING_MAX); + + while (*p && *p != '/' && *cp && *p == *cp) { + p++; + n++; + cp++; + } + + if (!*cp && (!*p || *p == '/') && + (ntohl(next_be) & 7) == RFST_HARDLINK) { + set_cache(i, sizeof(*i)); + return (romfs_inode_t) + ((const uint8_t *)romfs + + (ntohl(ci->dir_start) & ~15)); + } + + if (!*p && !*cp) { + set_cache(i, sizeof(*i)); + if ((ntohl(ci->next) & 7) == RFST_SYMLINK) { + i = romfs_symlink(romfs, level, i); + continue; + } + return i; + } + + if (*p == '/' && !*cp) { + set_cache(i, sizeof(*i)); + switch (ntohl(ci->next) & 7) { + case RFST_SYMLINK: + i = romfs_symlink(romfs, level, i); + if (!i) + return NULL; + i = dir_link(romfs, i); + while (*path != '/' && *path) + path++; + if (!*path) + return NULL; + path++; + continue; + case RFST_DIR: + path = p + 1; + i = dir_link(romfs, i); + break; + default: + path = p + 1; + i = skip_and_pad(i); + break; + } + level = i; + continue; + } + + set_cache(i, sizeof(*i)); + if (!(ntohl(ci->next) & ~15)) + return NULL; + + i = (romfs_inode_t)((const uint8_t *)romfs + + (ntohl(ci->next) & ~15)); + } + + return NULL; +} + +const void * +romfs_get_info(romfs_t romfs, const char *path, size_t *len) +{ + romfs_inode_t i; + + if (*path == '/') + path++; + + i = romfs_lookup(romfs, (romfs_inode_t)romfs, path); + + if (!i) + return NULL; + + set_cache(i, sizeof(*i)); + *len = ntohl(ci->size); + + return (void *)skip_and_pad(i); +} diff --git a/lib/romfs.h b/lib/romfs.h new file mode 100644 index 000000000..0de357317 --- /dev/null +++ b/lib/romfs.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2017 National Institute of Advanced Industrial Science + * and Technology (AIST) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of AIST nor the names of its contributors may be used + * to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +typedef uint32_t u32_be_t; + +struct romfs_superblock { + u32_be_t magic1; + u32_be_t magic2; + u32_be_t size; + u32_be_t checksum; +}; + +struct romfs_i { + u32_be_t next; + u32_be_t dir_start; + u32_be_t size; + u32_be_t checksum; +}; + +enum { + RFST_HARDLINK = 0, + RFST_DIR = 1, + RFST_SYMLINK = 3, +}; + +typedef const struct romfs_i *romfs_inode_t; +typedef const struct romfs_superblock *romfs_t; + +const void * +romfs_get_info(romfs_t romfs, const char *path, size_t *len); +size_t +romfs_mount_check(romfs_t romfs); + diff --git a/plugins/protocol_esp32_lws_scan.c b/plugins/protocol_esp32_lws_scan.c new file mode 100644 index 000000000..54e79f1fa --- /dev/null +++ b/plugins/protocol_esp32_lws_scan.c @@ -0,0 +1,333 @@ +/* + * Example ESP32 app code using Libwebsockets + * + * Copyright (C) 2017 Andy Green + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + * + * The person who associated a work with this deed has dedicated + * the work to the public domain by waiving all of his or her rights + * to the work worldwide under copyright law, including all related + * and neighboring rights, to the extent allowed by law. You can copy, + * modify, distribute and perform the work, even for commercial purposes, + * all without asking permission. + * + * The test apps are intended to be adapted for use in your code, which + * may be proprietary. So unlike the library itself, they are licensed + * Public Domain. + * + */ +#include +#include + +typedef enum { + SCAN_STATE_NONE, + SCAN_STATE_INITIAL, + SCAN_STATE_LIST, + SCAN_STATE_FINAL +} scan_state; + +struct store_json { + const char *j; + const char *nvs; +}; + +struct per_session_data__esplws_scan { + struct per_session_data__esplws_scan *next; + scan_state scan_state; + char ap_record; + unsigned char subsequent:1; + unsigned char changed_partway:1; +}; + +struct per_vhost_data__esplws_scan { + wifi_ap_record_t ap_records[20]; + TimerHandle_t timer; + struct per_session_data__esplws_scan *live_pss_list; + struct lws_context *context; + struct lws_vhost *vhost; + const struct lws_protocols *protocol; + uint16_t count_ap_records; + char count_live_pss; + unsigned char scan_ongoing:1; + unsigned char completed_any_scan:1; + unsigned char reboot:1; +}; + +static const struct store_json store_json[] = { + { "ssid\":\"", "ssid" }, + { ",\"pw\":\"", "password" }, + { ",\"serial\":\"", "serial" }, + { ",\"region\":\"", "region" }, +}; + +static wifi_scan_config_t scan_config = { + .ssid = 0, + .bssid = 0, + .channel = 0, + .show_hidden = true +}; + +extern void (*lws_cb_scan_done)(void *); +extern void *lws_cb_scan_done_arg; + + +static void +scan_finished(void *v); + +static int +esplws_simple_arg(char *dest, int len, const char *in, const char *match) +{ + const char *p = strstr(in, match); + int n = 0; + + if (!p) { + lwsl_err("No match %s\n", match); + return 1; + } + + p += strlen(match); + while (*p && *p != '\"' && n < len - 1) + dest[n++] = *p++; + dest[n] = '\0'; + + return 0; +} + +static void +scan_start(struct per_vhost_data__esplws_scan *vhd) +{ + int n; + + if (vhd->reboot) + esp_restart(); + + if (vhd->scan_ongoing) + return; + + vhd->scan_ongoing = 1; + lws_cb_scan_done = scan_finished; + lws_cb_scan_done_arg = vhd; + n = esp_wifi_scan_start(&scan_config, false); + if (n != ESP_OK) + lwsl_err("scan start failed %d\n", n); +} + +static void timer_cb(TimerHandle_t t) +{ + struct per_vhost_data__esplws_scan *vhd = pvTimerGetTimerID(t); + + scan_start(vhd); +} + +static void +scan_finished(void *v) +{ + struct per_vhost_data__esplws_scan *vhd = v; + struct per_session_data__esplws_scan *p = vhd->live_pss_list; + + vhd->scan_ongoing = 0; + + vhd->count_ap_records = ARRAY_SIZE(vhd->ap_records); + if (esp_wifi_scan_get_ap_records(&vhd->count_ap_records, vhd->ap_records) != ESP_OK) { + lwsl_err("%s: failed\n", __func__); + return; + } + + while (p) { + if (p->scan_state != SCAN_STATE_INITIAL && p->scan_state != SCAN_STATE_NONE) + p->changed_partway = 1; + else + p->scan_state = SCAN_STATE_INITIAL; + p = p->next; + } + + lws_callback_on_writable_all_protocol(vhd->context, vhd->protocol); +} + +static int +callback_esplws_scan(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len) +{ + struct per_session_data__esplws_scan *pss = + (struct per_session_data__esplws_scan *)user; + struct per_vhost_data__esplws_scan *vhd = + (struct per_vhost_data__esplws_scan *) + lws_protocol_vh_priv_get(lws_get_vhost(wsi), + lws_get_protocol(wsi)); + char buf[LWS_PRE + 384], /*ip[24],*/ *start = buf + LWS_PRE - 1, *p = start, + *end = buf + sizeof(buf) - 1; + wifi_ap_record_t *r; + int n, m; + + switch (reason) { + + case LWS_CALLBACK_PROTOCOL_INIT: + vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi), + lws_get_protocol(wsi), + sizeof(struct per_vhost_data__esplws_scan)); + vhd->context = lws_get_context(wsi); + vhd->protocol = lws_get_protocol(wsi); + vhd->vhost = lws_get_vhost(wsi); + vhd->timer = xTimerCreate("x", pdMS_TO_TICKS(10000), 1, vhd, + (TimerCallbackFunction_t)timer_cb); + xTimerStart(vhd->timer, 0); + vhd->scan_ongoing = 0; + scan_start(vhd); + break; + + case LWS_CALLBACK_PROTOCOL_DESTROY: + if (!vhd) + break; + xTimerStop(vhd->timer, 0); + xTimerDelete(vhd->timer, 0); + break; + + case LWS_CALLBACK_ESTABLISHED: + vhd->count_live_pss++; + pss->next = vhd->live_pss_list; + vhd->live_pss_list = pss; + /* if we have scan results, update them. Otherwise wait */ + if (vhd->count_ap_records) { + pss->scan_state = SCAN_STATE_INITIAL; + lws_callback_on_writable(wsi); + } + break; + + case LWS_CALLBACK_SERVER_WRITEABLE: + switch (pss->scan_state) { + case SCAN_STATE_INITIAL: + n = LWS_WRITE_TEXT | LWS_WRITE_NO_FIN;; + p += snprintf(p, end - p, + "{ \"model\":\"%s\"," + " \"serial\":\"%s\"," + " \"host\":\"%s-%s\"," + " \"region\":\"%d\"," + " \"aps\":[", + lws_esp32_model, + lws_esp32_serial, + lws_esp32_model, lws_esp32_serial, + lws_esp32_region); + pss->scan_state = SCAN_STATE_LIST; + pss->ap_record = 0; + pss->subsequent = 0; + break; + case SCAN_STATE_LIST: + n = LWS_WRITE_CONTINUATION | LWS_WRITE_NO_FIN; + if (pss->ap_record >= vhd->count_ap_records) + goto scan_state_final; + + if (pss->subsequent) + *p++ = ','; + pss->subsequent = 1; + + r = &vhd->ap_records[(int)pss->ap_record++]; + p += snprintf(p, end - p, + "{\"ssid\":\"%s\"," + "\"bssid\":\"%02X:%02X:%02X:%02X:%02X:%02X\"," + "\"rssi\":\"%d\"," + "\"chan\":\"%d\"," + "\"auth\":\"%d\"}", + r->ssid, + r->bssid[0], r->bssid[1], r->bssid[2], + r->bssid[3], r->bssid[4], r->bssid[5], + r->rssi, r->primary, r->authmode); + if (pss->ap_record >= vhd->count_ap_records) + pss->scan_state = SCAN_STATE_FINAL; + break; + + case SCAN_STATE_FINAL: +scan_state_final: + n = LWS_WRITE_CONTINUATION; + p += sprintf(p, "]}"); + if (pss->changed_partway) { + pss->subsequent = 0; + pss->scan_state = SCAN_STATE_INITIAL; + } else + pss->scan_state = SCAN_STATE_NONE; + break; + default: + return 0; + } + + m = lws_write(wsi, (unsigned char *)start, p - start, n); + if (m < 0) { + lwsl_err("ERROR %d writing to di socket\n", m); + return -1; + } + + if (pss->scan_state != SCAN_STATE_NONE) + lws_callback_on_writable(wsi); + + break; + + case LWS_CALLBACK_RECEIVE: + { + nvs_handle nvh; + char p[64]; + int n; + + if (strstr((const char *)in, "identify")) { + lws_esp32_identify_physical_device(); + break; + } + + if (nvs_open("lws-station", NVS_READWRITE, &nvh) != ESP_OK) { + lwsl_err("Unable to open nvs\n"); + break; + } + + for (n = 0; n < ARRAY_SIZE(store_json); n++) { + if (esplws_simple_arg(p, sizeof(p), in, store_json[n].j)) + goto bail_nvs; + + if (nvs_set_str(nvh, store_json[n].nvs, p) != ESP_OK) { + lwsl_err("Unable to store %s in nvm\n", store_json[n].nvs); + goto bail_nvs; + } + } + + nvs_commit(nvh); + nvs_close(nvh); + + vhd->reboot = 1; + break; + +bail_nvs: + nvs_close(nvh); + + return 1; + } + + case LWS_CALLBACK_CLOSED: + { + struct per_session_data__esplws_scan **p = &vhd->live_pss_list; + + while (*p) { + if ((*p) == pss) { + *p = pss->next; + continue; + } + + p = &((*p)->next); + } + + vhd->count_live_pss--; + } + break; + default: + break; + } + + return 0; +} + +#define LWS_PLUGIN_PROTOCOL_ESPLWS_SCAN \ + { \ + "esplws-scan", \ + callback_esplws_scan, \ + sizeof(struct per_session_data__esplws_scan), \ + 512, 0, NULL \ + } +