esp32: multi ap slots
authorAndy Green <andy@warmcat.com>
Sat, 13 May 2017 02:26:59 +0000 (10:26 +0800)
committerAndy Green <andy@warmcat.com>
Sat, 13 May 2017 02:26:59 +0000 (10:26 +0800)
lib/libwebsockets.c
lib/libwebsockets.h
lib/lws-plat-esp32.c
lib/private-libwebsockets.h
lib/ssl.c
plugins/protocol_esp32_lws_scan.c

index 7750429..b69a4ef 100755 (executable)
@@ -3144,6 +3144,8 @@ lws_stats_log_dump(struct lws_context *context)
        lwsl_notice("LWSSTATS_C_WRITEABLE_CB:                    %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_WRITEABLE_CB));
        lwsl_notice("LWSSTATS_C_SSL_CONNECTIONS_FAILED:          %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_SSL_CONNECTIONS_FAILED));
        lwsl_notice("LWSSTATS_C_SSL_CONNECTIONS_ACCEPTED:        %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_SSL_CONNECTIONS_ACCEPTED));
+       lwsl_notice("LWSSTATS_C_SSL_CONNS_HAD_RX:                %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_SSL_CONNS_HAD_RX));
+
        lwsl_notice("LWSSTATS_C_TIMEOUTS:                        %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_TIMEOUTS));
        lwsl_notice("LWSSTATS_C_SERVICE_ENTRY:                   %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_SERVICE_ENTRY));
        lwsl_notice("LWSSTATS_B_READ:                            %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_B_READ));
@@ -3154,6 +3156,12 @@ lws_stats_log_dump(struct lws_context *context)
                lwsl_notice("  Avg accept delay:                         %8llums\n",
                        (unsigned long long)(lws_stats_get(context, LWSSTATS_MS_SSL_CONNECTIONS_ACCEPTED_DELAY) /
                        lws_stats_get(context, LWSSTATS_C_SSL_CONNECTIONS_ACCEPTED)) / 1000);
+       lwsl_notice("LWSSTATS_MS_SSL_RX_DELAY:                   %8llums\n", (unsigned long long)lws_stats_get(context, LWSSTATS_MS_SSL_RX_DELAY) / 1000);
+       if (lws_stats_get(context, LWSSTATS_C_SSL_CONNS_HAD_RX))
+               lwsl_notice("  Avg accept-rx delay:                      %8llums\n",
+                       (unsigned long long)(lws_stats_get(context, LWSSTATS_MS_SSL_RX_DELAY) /
+                       lws_stats_get(context, LWSSTATS_C_SSL_CONNS_HAD_RX)) / 1000);
+
        lwsl_notice("LWSSTATS_MS_WRITABLE_DELAY:                 %8lluus\n",
                        (unsigned long long)lws_stats_get(context, LWSSTATS_MS_WRITABLE_DELAY));
        lwsl_notice("LWSSTATS_MS_WORST_WRITABLE_DELAY:           %8lluus\n",
index 679dddc..d40268f 100644 (file)
@@ -555,8 +555,7 @@ 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;
+typedef void (*lws_cb_scan_done)(uint16_t count, wifi_ap_record_t *recs, void *arg);
 
 struct lws_esp32 {
        char sta_ip[16];
@@ -567,11 +566,15 @@ struct lws_esp32 {
        char model[16];
        char group[16];
        char role[16];
+       char ssid[4][16];
+       char password[4][32];
        char active_ssid[32];
        char access_pw[16];
        mdns_server_t *mdns;
                char region;
                char inet;
+       lws_cb_scan_done scan_consumer;
+       void *scan_consumer_arg;
 };
 
 struct lws_esp32_image {
@@ -598,6 +601,8 @@ extern struct lws_context *
 lws_esp32_init(struct lws_context_creation_info *);
 extern int
 lws_esp32_wlan_nvs_get(int retry);
+extern esp_err_t
+lws_nvs_set_str(nvs_handle handle, const char* key, const char* value);
 extern void
 lws_esp32_restart_guided(uint32_t type);
 extern const esp_partition_t *
@@ -4808,6 +4813,7 @@ enum {
        LWSSTATS_C_WRITEABLE_CB, /**< count of writable callbacks */
        LWSSTATS_C_SSL_CONNECTIONS_FAILED, /**< count of failed SSL connections */
        LWSSTATS_C_SSL_CONNECTIONS_ACCEPTED, /**< count of accepted SSL connections */
+       LWSSTATS_C_SSL_CONNS_HAD_RX, /**< count of accepted SSL conns that have had some RX */
        LWSSTATS_C_TIMEOUTS, /**< count of timed-out connections */
        LWSSTATS_C_SERVICE_ENTRY, /**< count of entries to lws service loop */
        LWSSTATS_B_READ, /**< aggregate bytes read */
@@ -4816,6 +4822,7 @@ enum {
        LWSSTATS_MS_SSL_CONNECTIONS_ACCEPTED_DELAY, /**< aggregate delay in accepting connection */
        LWSSTATS_MS_WRITABLE_DELAY, /**< aggregate delay between asking for writable and getting cb */
        LWSSTATS_MS_WORST_WRITABLE_DELAY, /**< single worst delay between asking for writable and getting cb */
+       LWSSTATS_MS_SSL_RX_DELAY, /**< aggregate delay between ssl accept complete and first RX */
 
        /* Add new things just above here ---^
         * This is part of the ABI, don't needlessly break compatibility */
index 8294fef..c8eaf42 100644 (file)
@@ -625,16 +625,34 @@ enum lws_gapss {
                                 * don't get an IP within a timeout and retries,
                                 * blacklist it and go back 
                                 */
+       LWS_GAPSS_STAT_HAPPY,
+};
+
+static const char *gapss_str[] = {
+       "LWS_GAPSS_INITIAL",
+        "LWS_GAPSS_SCAN",
+        "LWS_GAPSS_AP",
+        "LWS_GAPSS_AP_SCAN",
+        "LWS_GAPSS_STAT_GRP_AP",
+        "LWS_GAPSS_STAT_GRP_AP_SCAN",
+        "LWS_GAPSS_STAT",
+       "LWS_GAPSS_STAT_HAPPY",
 };
 
 static romfs_t lws_esp32_romfs;
-static TimerHandle_t leds_timer;
-//static enum lws_gapss gapss = LWS_GAPSS_INITIAL;
+static TimerHandle_t leds_timer, scan_timer;
+static enum lws_gapss gapss = LWS_GAPSS_INITIAL;
 
 struct esp32_file {
        const struct inode *i;
 };
 
+static void lws_gapss_to(enum lws_gapss to)
+{
+       lwsl_notice("gapss from %s to %s\n", gapss_str[gapss], gapss_str[to]);
+       gapss = to;
+}
+
 uint32_t lws_esp32_get_reboot_type(void)
 {
        uint32_t *p = (uint32_t *)LWS_MAGIC_REBOOT_TYPE_ADS, val = *p;
@@ -674,17 +692,178 @@ void lws_esp32_restart_guided(uint32_t type)
        esp_restart();
 }
 
+/*
+ * esp-idf goes crazy with zero length str nvs.  Use this as a workaround
+ * to delete the key in that case.
+ */
+
+esp_err_t lws_nvs_set_str(nvs_handle handle, const char* key, const char* value)
+{
+       if (*value)
+               return nvs_set_str(handle, key, value);
+
+       return nvs_erase_key(handle, key);
+}
+
+static wifi_scan_config_t scan_config = {
+        .ssid = 0,
+        .bssid = 0,
+        .channel = 0,
+        .show_hidden = true
+};
+
+static char scan_ongoing = 0i, scan_timer_exists = 0;
+static int try_slot = -1;
+
+static wifi_config_t config = {
+       .ap = {
+           .channel = 6,
+           .authmode = WIFI_AUTH_OPEN,
+           .max_connection = 1,
+       } }, sta_config = {
+       .sta = {
+               .bssid_set = 0,
+       } };
+
+static void lws_esp32_scan_timer_cb(TimerHandle_t th)
+{
+       int n;
+
+       scan_ongoing = 0;
+       n = esp_wifi_scan_start(&scan_config, false);
+       if (n != ESP_OK)
+               lwsl_err("scan start failed %d\n", n);
+}
+
+
+static int
+start_scan()
+{
+       /* if no APs configured, no point... */
+
+       if (!lws_esp32.ssid[0][0] &&
+           !lws_esp32.ssid[1][0] &&
+           !lws_esp32.ssid[2][0] &&
+           !lws_esp32.ssid[3][0])
+               return 0;
+
+       if (scan_timer_exists && !scan_ongoing) {
+               scan_ongoing = 1;
+               xTimerStart(scan_timer, 0);
+       } else
+               lwsl_notice("%s: ignoring, no scan timer\n", __func__);
+
+       return 0;
+}
+
+
+
+static void
+end_scan()
+{
+       wifi_ap_record_t ap_records[10];
+       uint16_t count_ap_records;
+       int n, m;
+
+       count_ap_records = ARRAY_SIZE(ap_records);
+       if (esp_wifi_scan_get_ap_records(&count_ap_records, ap_records) != ESP_OK) {
+               lwsl_err("%s: failed\n", __func__);
+               return;
+       }
+
+       if (!count_ap_records)
+               goto passthru;
+
+       if (gapss != LWS_GAPSS_SCAN) {
+               lwsl_notice("ignoring scan as gapss %s\n", gapss_str[gapss]);
+               goto passthru;
+       }
+
+       /* no point if no APs set up */
+       if (!lws_esp32.ssid[0][0] &&
+           !lws_esp32.ssid[1][0] &&
+           !lws_esp32.ssid[2][0] &&
+           !lws_esp32.ssid[3][0])
+               goto passthru;
+
+       lwsl_notice("checking %d scan records\n", count_ap_records);
+
+       for (n = 0; n < 4; n++) {
+
+               if (!lws_esp32.ssid[(n + try_slot + 1) & 3][0])
+                       continue;
+
+               lwsl_notice("looking for %s\n", lws_esp32.ssid[(n + try_slot + 1) & 3]);
+
+               /* this ssid appears in scan results? */
+
+               for (m = 0; m < count_ap_records; m++) {
+               //      lwsl_notice("  %s\n", ap_records[m].ssid);
+                       if (strcmp((char *)ap_records[m].ssid, lws_esp32.ssid[(n + try_slot + 1) & 3]) == 0)
+                               goto hit;
+               }
+
+               continue;
+
+hit:
+               m = (n + try_slot + 1) & 3;
+               try_slot = m;
+               lwsl_notice("Attempting connection with slot %d: %s:\n", m,
+                               lws_esp32.ssid[m]);
+               /* set the ssid we last tried to connect to */
+               strncpy(lws_esp32.active_ssid, lws_esp32.ssid[m],
+                               sizeof(lws_esp32.active_ssid) - 1);
+               lws_esp32.active_ssid[sizeof(lws_esp32.active_ssid) - 1] = '\0';
+
+               strncpy((char *)sta_config.sta.ssid, lws_esp32.ssid[m], sizeof(sta_config.sta.ssid) - 1);
+               strncpy((char *)sta_config.sta.password, lws_esp32.password[m], sizeof(sta_config.sta.password) - 1);
+
+               tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_STA, (const char *)&config.ap.ssid[7]);
+               lws_gapss_to(LWS_GAPSS_STAT);
+
+               esp_wifi_set_config(WIFI_IF_STA, &sta_config);
+               esp_wifi_connect();
+
+               mdns_init(TCPIP_ADAPTER_IF_STA, &lws_esp32.mdns);
+               mdns_set_hostname(lws_esp32.mdns,  (const char *)&config.ap.ssid[7]);
+               mdns_set_instance(lws_esp32.mdns, "instance");
+               break;
+       }
+
+       if (n == 4)
+               start_scan();
+
+passthru:
+       if (lws_esp32.scan_consumer)
+               lws_esp32.scan_consumer(count_ap_records, ap_records, lws_esp32.scan_consumer_arg);
+
+}      
+
 esp_err_t lws_esp32_event_passthru(void *ctx, system_event_t *event)
 {
+       char slot[8];
+       nvs_handle nvh;
+       uint32_t use;
+
        switch(event->event_id) {
        case SYSTEM_EVENT_STA_START:
-               esp_wifi_connect();
-               break;
+               //esp_wifi_connect();
+//             break;
+               /* fallthru */
        case SYSTEM_EVENT_STA_DISCONNECTED:
+               lwsl_notice("SYSTEM_EVENT_STA_DISCONNECTED\n");
+               lws_esp32.inet = 0;
+               lws_esp32.sta_ip[0] = '\0';
+               lws_esp32.sta_mask[0] = '\0';
+               lws_esp32.sta_gw[0] = '\0';
+               lws_gapss_to(LWS_GAPSS_SCAN);
                lws_esp32.inet = 0;
+               start_scan();
                esp_wifi_connect();
                break;
        case SYSTEM_EVENT_STA_GOT_IP:
+               lwsl_notice("SYSTEM_EVENT_STA_GOT_IP\n");
+
                lws_esp32.inet = 1;
                render_ip(lws_esp32.sta_ip, sizeof(lws_esp32.sta_ip) - 1,
                                (uint8_t *)&event->event_info.got_ip.ip_info.ip);
@@ -692,8 +871,23 @@ esp_err_t lws_esp32_event_passthru(void *ctx, system_event_t *event)
                                (uint8_t *)&event->event_info.got_ip.ip_info.netmask);
                render_ip(lws_esp32.sta_gw, sizeof(lws_esp32.sta_gw) - 1,
                                (uint8_t *)&event->event_info.got_ip.ip_info.gw);
+
+               lwsl_notice(" --- Got IP %s\n", lws_esp32.sta_ip);
+
+               if (!nvs_open("lws-station", NVS_READWRITE, &nvh)) {
+                       lws_snprintf(slot, sizeof(slot) - 1, "%duse", try_slot);
+                       use = 0;
+                       nvs_get_u32(nvh, slot, &use);
+                       nvs_set_u32(nvh, slot, use + 1);
+                       nvs_commit(nvh);
+                       nvs_close(nvh);
+               }
+
+               lws_gapss_to(LWS_GAPSS_STAT_HAPPY);
                break;
        case SYSTEM_EVENT_SCAN_DONE:
+               lwsl_notice("SYSTEM_EVENT_SCAN_DONE\n");
+               end_scan();
                break;
        default:
                break;
@@ -794,41 +988,38 @@ static const struct lws_plat_file_ops fops = {
        .LWS_FOP_SEEK_CUR = esp32_lws_fops_seek_cur,
 };
 
-static wifi_config_t config = {
-       .ap = {
-           .channel = 6,
-           .authmode = WIFI_AUTH_OPEN,
-           .max_connection = 1,
-       } }, sta_config = {
-       .sta = {
-               .bssid_set = 0,
-       } };
-
 int
 lws_esp32_wlan_nvs_get(int retry)
 {
        nvs_handle nvh;
-       char r[2], lws_esp32_force_ap = 0;
+       char r[2], lws_esp32_force_ap = 0, slot[12];
        size_t s;
        uint8_t mac[6];
+       int n, m;
 
-       esp_efuse_read_mac(mac);
+       esp_efuse_mac_get_default(mac);
        mac[5] |= 1; /* match the AP MAC */
        snprintf(lws_esp32.serial, sizeof(lws_esp32.serial) - 1, "%02X%02X%02X", mac[3], mac[4], mac[5]);
 
        ESP_ERROR_CHECK(nvs_open("lws-station", NVS_READWRITE, &nvh));
 
-       s = sizeof(config.sta.ssid) - 1;
-       if (nvs_get_str(nvh, "ssid0", (char *)sta_config.sta.ssid, &s) != ESP_OK)
-               lws_esp32_force_ap = 1;
-
-       /* set the ssid we last tried to connect to */
-       strncpy(lws_esp32.active_ssid, (char *)sta_config.sta.ssid, sizeof(lws_esp32.active_ssid) - 1);
-       lws_esp32.active_ssid[sizeof(lws_esp32.active_ssid) - 1] = '\0';
+       config.sta.ssid[0] = '\0';
+       config.sta.password[0] = '\0';
+
+       for (n = 0; n < 4; n++) {
+               lws_snprintf(slot, sizeof(slot) - 1, "%dssid", n);
+               s = sizeof(lws_esp32.ssid[0]) - 1;
+               lws_esp32.ssid[n][0] = '\0';
+               m = nvs_get_str(nvh, slot, lws_esp32.ssid[n], &s);
+               lwsl_notice("%s: %s: %d\n", slot, lws_esp32.ssid[n], m);
+
+               lws_snprintf(slot, sizeof(slot) - 1, "%dpassword", n);
+               s = sizeof(lws_esp32.password[0]) - 1;
+               lws_esp32.password[n][0] = '\0';
+               m = nvs_get_str(nvh, slot, lws_esp32.password[n], &s);
+               lwsl_notice("%s: %s: %d\n", slot, lws_esp32.password[n], m);
+       }
 
-       s = sizeof(config.sta.password) - 1;
-       if (nvs_get_str(nvh, "password0", (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;
@@ -857,15 +1048,8 @@ lws_esp32_wlan_nvs_get(int retry)
 
        nvs_close(nvh);
 
-       if (retry && sta_config.sta.ssid[0]) {
-               tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_STA, (const char *)&config.ap.ssid[7]);
-               ESP_ERROR_CHECK( esp_wifi_set_config(WIFI_IF_STA, &sta_config));
-               ESP_ERROR_CHECK( esp_wifi_connect());
-
-               ESP_ERROR_CHECK(mdns_init(TCPIP_ADAPTER_IF_STA, &lws_esp32.mdns));
-               mdns_set_hostname(lws_esp32.mdns,  (const char *)&config.ap.ssid[7]);
-               mdns_set_instance(lws_esp32.mdns, "instance");
-       }
+       lws_gapss_to(LWS_GAPSS_SCAN);
+       start_scan();
 
        return lws_esp32_force_ap;
 }
@@ -873,6 +1057,26 @@ lws_esp32_wlan_nvs_get(int retry)
 void
 lws_esp32_wlan_config(void)
 {
+       ledc_timer_config_t ledc_timer = {
+               .bit_num = LEDC_TIMER_13_BIT,
+               .freq_hz = 5000,
+               .speed_mode = LEDC_HIGH_SPEED_MODE,
+               .timer_num = LEDC_TIMER_0
+       };
+
+       ledc_timer_config(&ledc_timer);
+
+       /* user code needs to provide lws_esp32_leds_timer_cb */
+
+        leds_timer = xTimerCreate("lws_leds", pdMS_TO_TICKS(25), 1, NULL,
+                          (TimerCallbackFunction_t)lws_esp32_leds_timer_cb);
+        scan_timer = xTimerCreate("lws_scan", pdMS_TO_TICKS(10000), 0, NULL,
+                          (TimerCallbackFunction_t)lws_esp32_scan_timer_cb);
+
+       scan_timer_exists = 1;
+        xTimerStart(leds_timer, 0);
+
+
        lws_esp32_wlan_nvs_get(0);
        tcpip_adapter_init();
 }
@@ -891,6 +1095,8 @@ lws_esp32_wlan_start_ap(void)
        ESP_ERROR_CHECK( esp_wifi_set_config(WIFI_IF_STA, &sta_config));
        ESP_ERROR_CHECK( esp_wifi_start());
 
+       esp_wifi_scan_start(&scan_config, false);
+
        if (sta_config.sta.ssid[0]) {
                tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_STA, (const char *)&config.ap.ssid[7]);
                esp_wifi_set_auto_connect(1);
@@ -1011,11 +1217,12 @@ lws_esp32_set_creation_defaults(struct lws_context_creation_info *info)
 
        info->port = 443;
        info->fd_limit_per_thread = 30;
-       info->max_http_header_pool = 2;
+       info->max_http_header_pool = 3;
        info->max_http_header_data = 1024;
        info->pt_serv_buf_size = 4096;
-       info->keepalive_timeout = 5;
-       info->simultaneous_ssl_restriction = 2;
+       info->keepalive_timeout = 30;
+       info->timeout_secs = 30;
+       info->simultaneous_ssl_restriction = 3;
        info->options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS |
                       LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
 
@@ -1068,20 +1275,6 @@ lws_esp32_init(struct lws_context_creation_info *info)
        char buf[512];
        size_t s;
        int n;
-       ledc_timer_config_t ledc_timer = {
-               .bit_num = LEDC_TIMER_13_BIT,
-               .freq_hz = 5000,
-               .speed_mode = LEDC_HIGH_SPEED_MODE,
-               .timer_num = LEDC_TIMER_0
-       };
-
-       ledc_timer_config(&ledc_timer);
-
-       /* user code needs to provide lws_esp32_leds_timer_cb */
-
-        leds_timer = xTimerCreate("lws_leds", pdMS_TO_TICKS(25), 1, NULL,
-                          (TimerCallbackFunction_t)lws_esp32_leds_timer_cb);
-        xTimerStart(leds_timer, 0);
 
        ESP_ERROR_CHECK(nvs_open("lws-station", NVS_READWRITE, &nvh));
        n = 0;
index e2cee95..1d86c58 100644 (file)
@@ -1473,6 +1473,7 @@ struct lws {
        struct lws *pending_read_list_prev, *pending_read_list_next;
 #if defined(LWS_WITH_STATS)
        uint64_t accept_start_us;
+       char seen_rx;
 #endif
 #endif
 #ifdef LWS_WITH_HTTP_PROXY
index ea1f4af..d32e3a4 100644 (file)
--- a/lib/ssl.c
+++ b/lib/ssl.c
@@ -365,6 +365,15 @@ lws_ssl_capable_read(struct lws *wsi, unsigned char *buf, int len)
                return LWS_SSL_CAPABLE_ERROR;
        }
 #endif
+#if defined(LWS_WITH_STATS)
+       if (!wsi->seen_rx) {
+                lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_MS_SSL_RX_DELAY,
+                               time_in_microseconds() - wsi->accept_start_us);
+                lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_SSL_CONNS_HAD_RX, 1);
+               wsi->seen_rx = 1;
+       }
+#endif
+
 
        lwsl_debug("%p: SSL_read says %d\n", wsi, n);
        /* manpage: returning 0 means connection shut down */
@@ -762,6 +771,7 @@ accepted:
                lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_SSL_CONNECTIONS_ACCEPTED, 1);
 #if defined(LWS_WITH_STATS)
                lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_MS_SSL_CONNECTIONS_ACCEPTED_DELAY, time_in_microseconds() - wsi->accept_start_us);
+               wsi->accept_start_us = time_in_microseconds();
 #endif
 
                /* OK, we are accepted... give him some time to negotiate */
index 1a8c9f4..686599c 100644 (file)
@@ -85,14 +85,14 @@ struct per_vhost_data__esplws_scan {
 };
 
 static const struct store_json store_json[] = {
-       { "\"ssid0\":\"", "ssid0" },
-       { ",\"pw0\":\"", "password0" },
-       { "\"ssid1\":\"", "ssid1" },
-       { ",\"pw1\":\"", "password1" },
-       { "\"ssid2\":\"", "ssid2" },
-       { ",\"pw2\":\"", "password2" },
-       { "\"ssid3\":\"", "ssid3" },
-       { ",\"pw3\":\"", "password3" },
+       { "\"ssid0\":\"", "0ssid" },
+       { ",\"pw0\":\"", "0password" },
+       { "\"ssid1\":\"", "1ssid" },
+       { ",\"pw1\":\"", "1password" },
+       { "\"ssid2\":\"", "2ssid" },
+       { ",\"pw2\":\"", "2password" },
+       { "\"ssid3\":\"", "3ssid" },
+       { ",\"pw3\":\"", "3password" },
        { ",\"access_pw\":\"", "access_pw" },
        { "{\"group\":\"", "group" },
        { "{\"role\":\"", "role" },
@@ -106,9 +106,6 @@ static wifi_scan_config_t scan_config = {
         .show_hidden = true
 };
 
-extern void (*lws_cb_scan_done)(void *);
-extern void *lws_cb_scan_done_arg;
-
 const esp_partition_t *
 ota_choose_part(void);
 
@@ -130,7 +127,7 @@ enum enum_param_names {
 
 
 static void
-scan_finished(void *v);
+scan_finished(uint16_t count, wifi_ap_record_t *recs, void *v);
 
 static int
 esplws_simple_arg(char *dest, int len, const char *in, const char *match)
@@ -161,8 +158,8 @@ scan_start(struct per_vhost_data__esplws_scan *vhd)
                return;
 
        vhd->scan_ongoing = 1;
-       lws_cb_scan_done = scan_finished;
-       lws_cb_scan_done_arg = vhd;
+       lws_esp32.scan_consumer = scan_finished;
+       lws_esp32.scan_consumer_arg = vhd;
        n = esp_wifi_scan_start(&scan_config, false);
        if (n != ESP_OK)
                lwsl_err("scan start failed %d\n", n);
@@ -216,18 +213,21 @@ client_connection(struct per_vhost_data__esplws_scan *vhd, const char *file)
 }
 
 static void
-scan_finished(void *v)
+scan_finished(uint16_t count, wifi_ap_record_t *recs, void *v)
 {
        struct per_vhost_data__esplws_scan *vhd = v;
        struct per_session_data__esplws_scan *p = vhd->live_pss_list;
 
+       lwsl_notice("%s: count %d\n", __func__, count);
+
        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;
-       }
+       if (count < ARRAY_SIZE(vhd->ap_records))
+               vhd->count_ap_records = count;
+       else
+               vhd->count_ap_records = ARRAY_SIZE(vhd->ap_records);
+
+       memcpy(vhd->ap_records, recs, vhd->count_ap_records * sizeof(*recs));
        
        while (p) {
                if (p->scan_state != SCAN_STATE_INITIAL && p->scan_state != SCAN_STATE_NONE)
@@ -333,7 +333,6 @@ callback_esplws_scan(struct lws *wsi, enum lws_callback_reasons reason,
                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;
                strcpy(vhd->json, " { }");
                scan_start(vhd);
@@ -347,6 +346,11 @@ callback_esplws_scan(struct lws *wsi, enum lws_callback_reasons reason,
                break;
 
        case LWS_CALLBACK_ESTABLISHED:
+               lwsl_notice("%s: ESTABLISHED\n", __func__);
+               if (!vhd->live_pss_list) {
+                       scan_start(vhd);
+                       xTimerStart(vhd->timer, 0);
+               }
                vhd->count_live_pss++;
                pss->next = vhd->live_pss_list;
                vhd->live_pss_list = pss;
@@ -374,7 +378,7 @@ callback_esplws_scan(struct lws *wsi, enum lws_callback_reasons reason,
                        struct timeval t;
                        uint8_t mac[6];
                        struct lws_esp32_image i;
-                       char img_factory[512], img_ota[512], group[16];
+                       char img_factory[512], img_ota[512], group[16], role[16];
                        int grt;
 
                case SCAN_STATE_INITIAL:
@@ -385,7 +389,10 @@ callback_esplws_scan(struct lws *wsi, enum lws_callback_reasons reason,
 
                        pss->last_send = t;
 
-                       ESP_ERROR_CHECK(nvs_open("lws-station", NVS_READWRITE, &nvh));
+                       if (nvs_open("lws-station", NVS_READWRITE, &nvh)) {
+                               lwsl_err("unable to open nvs\n");
+                               return -1;
+                       }
                        n = 0;
                        if (nvs_get_blob(nvh, "ssl-pub.pem", NULL, &s) == ESP_OK)
                                n = 1;
@@ -393,7 +400,9 @@ callback_esplws_scan(struct lws *wsi, enum lws_callback_reasons reason,
                                n |= 2;
                        s = sizeof(group) - 1;
                        group[0] = '\0';
+                       role[0] = '\0';
                        nvs_get_str(nvh, "group", group, &s);
+                       nvs_get_str(nvh, "role", role, &s);
 
                        nvs_close(nvh);
 
@@ -405,7 +414,7 @@ callback_esplws_scan(struct lws *wsi, enum lws_callback_reasons reason,
 
                        grt = lws_esp32_get_reboot_type();
 
-                       esp_efuse_read_mac(mac);
+                       esp_efuse_mac_get_default(mac);
                        strcpy(img_factory, " { \"date\": \"Empty\" }");
                        strcpy(img_ota, " { \"date\": \"Empty\" }");
 
@@ -431,6 +440,7 @@ callback_esplws_scan(struct lws *wsi, enum lws_callback_reasons reason,
                                      " \"conn_mask\":\"%s\",\n"
                                      " \"conn_gw\":\"%s\",\n"
                                      " \"group\":\"%s\",\n"
+                                     " \"role\":\"%s\",\n"
                                      " \"img_factory\": %s,\n"
                                      " \"img_ota\": %s,\n",
                                      lws_esp32.model,
@@ -445,7 +455,7 @@ callback_esplws_scan(struct lws *wsi, enum lws_callback_reasons reason,
                                      lws_esp32.sta_ip,
                                      lws_esp32.sta_mask,
                                      lws_esp32.sta_gw,
-                                     group,
+                                     group, role,
                                        img_factory,
                                        img_ota
                                      );
@@ -486,13 +496,13 @@ callback_esplws_scan(struct lws *wsi, enum lws_callback_reasons reason,
 
                                s = sizeof(ssid) - 1;
                                ssid[0] = '\0';
-                               lws_snprintf(name, sizeof(name) - 1, "ssid%d", m);
+                               lws_snprintf(name, sizeof(name) - 1, "%dssid", m);
                                nvs_get_str(nvh, name, ssid, &s);
-                               lws_snprintf(name, sizeof(name) - 1, "password%d", m);
+                               lws_snprintf(name, sizeof(name) - 1, "%dpassword", m);
                                s = 10;
                                nvs_get_str(nvh, name, NULL, &s);
                                pp = !!s;
-                               lws_snprintf(name, sizeof(name) - 1, "use%d", m);
+                               lws_snprintf(name, sizeof(name) - 1, "%duse", m);
                                nvs_get_u32(nvh, name, &use);
 
                                p += snprintf((char *)p, end - p,
@@ -568,8 +578,8 @@ issue:
                {
                        const char *sect = "\"app\": {", *b;
                        nvs_handle nvh;
-                       char p[64];
-                       int n;
+                       char p[64], use[6];
+                       int n, si = -1;
 
                        if (strstr((const char *)in, "identify")) {
                                lws_esp32_identify_physical_device();
@@ -591,6 +601,11 @@ issue:
                                break;
                        }
 
+                       if (!esplws_simple_arg(p, sizeof(p), in, ",\"slot\":\""))
+                               si = atoi(p);
+
+                       lwsl_notice("si %d\n", si);
+
                        for (n = 0; n < ARRAY_SIZE(store_json); n++) {
                                if (esplws_simple_arg(p, sizeof(p), in, store_json[n].j))
                                        continue;
@@ -599,7 +614,7 @@ issue:
                                if (n == 8 && lws_esp32_get_reboot_type() != LWS_MAGIC_REBOOT_TYPE_FORCED_FACTORY_BUTTON)
                                        continue;
 
-                               lwsl_notice("%s '%s\n", store_json[n].nvs, p);
+                               //lwsl_notice("%s: %s '%s'\n", __func__, store_json[n].nvs, p);
                                if (n == 9) {
                                        strncpy(lws_esp32.group, p, sizeof(lws_esp32.group) - 1);
                                        lws_esp32.group[sizeof(lws_esp32.group) - 1] = '\0';
@@ -609,10 +624,29 @@ issue:
                                        lws_esp32.role[sizeof(lws_esp32.role) - 1] = '\0';
                                }
 
-                               if (nvs_set_str(nvh, store_json[n].nvs, p) != ESP_OK) {
+                               if (lws_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;
                                }
+
+                               if (si != -1 && n < 8) {
+                                       if (!(n & 1)) {
+                                               strncpy(lws_esp32.ssid[(n >> 1) & 3], p,
+                                                               sizeof(lws_esp32.ssid[0]));
+                                               lws_esp32.ssid[(n >> 1) & 3]
+                                                       [sizeof(lws_esp32.ssid[0]) - 1] = '\0';
+                                               lws_snprintf(use, sizeof(use) - 1, "%duse", si);
+                                               lwsl_notice("resetting %s to 0\n", use);
+                                               nvs_set_u32(nvh, use, 0);
+
+                                       } else {
+                                               strncpy(lws_esp32.password[(n >> 1) & 3], p,
+                                                               sizeof(lws_esp32.password[0]));
+                                               lws_esp32.password[(n >> 1) & 3]
+                                                       [sizeof(lws_esp32.password[0]) - 1] = '\0';
+                                       }
+                               }
+
                        }
 
                        nvs_commit(nvh);
@@ -702,6 +736,8 @@ auton:
 
                        vhd->count_live_pss--;
                }
+               if (!vhd->live_pss_list)
+                       xTimerStop(vhd->timer, 0);
                break;
 
        /* "factory" POST handling */
@@ -738,7 +774,7 @@ auton:
                if (lws_esp32_get_reboot_type() == LWS_MAGIC_REBOOT_TYPE_FORCED_FACTORY_BUTTON) {
 
                        if (lws_spa_get_string(pss->spa, EPN_SERIAL)) {
-                               if (nvs_set_str(nvh, "serial", lws_spa_get_string(pss->spa, EPN_SERIAL)) != ESP_OK) {
+                               if (lws_nvs_set_str(nvh, "serial", lws_spa_get_string(pss->spa, EPN_SERIAL)) != ESP_OK) {
                                        lwsl_err("Unable to store serial in nvm\n");
                                        goto bail_nvs;
                                }
@@ -747,7 +783,7 @@ auton:
                        }
 
                        if (lws_spa_get_string(pss->spa, EPN_OPTS)) {
-                               if (nvs_set_str(nvh, "opts", lws_spa_get_string(pss->spa, EPN_OPTS)) != ESP_OK) {
+                               if (lws_nvs_set_str(nvh, "opts", lws_spa_get_string(pss->spa, EPN_OPTS)) != ESP_OK) {
                                        lwsl_err("Unable to store options in nvm\n");
                                        goto bail_nvs;
                                }