esp32: move helper code into lws
authorAndy Green <andy@warmcat.com>
Fri, 10 Mar 2017 06:29:21 +0000 (14:29 +0800)
committerAndy Green <andy@warmcat.com>
Fri, 10 Mar 2017 06:31:43 +0000 (14:31 +0800)
CMakeLists.txt
component.mk
cross-esp32.cmake
lib/libwebsockets.h
lib/lws-plat-esp32.c
lib/romfs.c [new file with mode: 0644]
lib/romfs.h [new file with mode: 0644]
plugins/protocol_esp32_lws_scan.c [new file with mode: 0644]

index 579faa4..be757da 100644 (file)
@@ -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)
index 7617999..becad0f 100644 (file)
@@ -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 \
index 2dc0a10..a0b3c1d 100644 (file)
@@ -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}")
index cfe403b..9e92e22 100644 (file)
@@ -460,6 +460,16 @@ struct pollfd {
 #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)
@@ -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;
index b706cf6..d787005 100644 (file)
@@ -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 (file)
index 0000000..f1f5093
--- /dev/null
@@ -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 <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);
+}
diff --git a/lib/romfs.h b/lib/romfs.h
new file mode 100644 (file)
index 0000000..0de3573
--- /dev/null
@@ -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 (file)
index 0000000..54e79f1
--- /dev/null
@@ -0,0 +1,333 @@
+/*
+ * 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 \
+       }
+