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)
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 \
# 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}")
#define POLLHUP 0x0010
#define POLLNVAL 0x0020
+#include <freertos/FreeRTOS.h>
+#include <freertos/event_groups.h>
+#include <string.h>
+#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)
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;
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;
+}
+
--- /dev/null
+/*
+ * 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 <string.h>
+#include <stdint.h>
+#include <stdio.h>
+#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);
+}
--- /dev/null
+/*
+ * 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);
+
--- /dev/null
+/*
+ * Example ESP32 app code using Libwebsockets
+ *
+ * Copyright (C) 2017 Andy Green <andy@warmcat.com>
+ *
+ * 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 <string.h>
+#include <nvs.h>
+
+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 \
+ }
+