esp32: multi ap slots plus PEM certs and parallel build fixes
authorAndy Green <andy@warmcat.com>
Thu, 11 May 2017 07:02:01 +0000 (15:02 +0800)
committerAndy Green <andy@warmcat.com>
Thu, 11 May 2017 07:02:01 +0000 (15:02 +0800)
CMakeLists.txt
component.mk
lib/libwebsockets.h
lib/lws-plat-esp32.c
lib/private-libwebsockets.h
lib/ssl-server.c
lib/ssl.c
plugins/protocol_esp32_lws_scan.c
scripts/esp32.mk

index b035f8d..4f1fd54 100644 (file)
@@ -1118,7 +1118,8 @@ if (GENCERTS)
                        COMMAND "${OPENSSL_EXECUTABLE}"
                                req -new -newkey rsa:1024 -days 10000 -nodes -x509 -keyout "${TEST_SERVER_SSL_KEY}" -out "${TEST_SERVER_SSL_CERT}"
                        RESULT_VARIABLE OPENSSL_RETURN_CODE
-                       OUTPUT_QUIET ERROR_QUIET)
+                       #               OUTPUT_QUIET ERROR_QUIET
+                       )
 
                if (OPENSSL_RETURN_CODE)
                        message(WARNING "!!! Failed to generate SSL certificate for Test Server!!!:\nOpenSSL return code = ${OPENSSL_RETURN_CODE}")
index ed334a8..340bd8a 100644 (file)
@@ -11,6 +11,8 @@ CROSS_PATH:= $(shell dirname $(CROSS_PATH1) )/..
 #              -DOPENSSL_INCLUDE_DIRS="${PWD}/../../boringssl/include" \
 
 # -DNDEBUG=1 after cflags
+#              -DOPENSSL_LIBRARIES=x \
+#              -DCOMPONENT_PATH=$(COMPONENT_PATH) \
 
 .PHONY: build
 build:
@@ -19,12 +21,10 @@ build:
        cmake $(COMPONENT_PATH)  -DLWS_C_FLAGS="$(CFLAGS) -DNDEBUG=1" \
                -DIDF_PATH=$(IDF_PATH) \
                -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 \
                -DOPENSSL_INCLUDE_DIR=${IDF_PATH}/components/openssl/include \
-               -DOPENSSL_LIBRARIES=x \
                -DLWS_WITH_STATS=1 \
                -DZLIB_LIBRARY=$(BUILD_DIR_BASE)/zlib/libzlib.a \
                -DZLIB_INCLUDE_DIR=$(COMPONENT_PATH)/../zlib \
index 4065aad..679dddc 100644 (file)
@@ -565,6 +565,9 @@ struct lws_esp32 {
        char serial[16];
        char opts[16];
        char model[16];
+       char group[16];
+       char role[16];
+       char active_ssid[32];
        char access_pw[16];
        mdns_server_t *mdns;
                char region;
index dc54911..8294fef 100644 (file)
@@ -584,8 +584,52 @@ struct lws_esp32 lws_esp32 = {
        .region = WIFI_COUNTRY_US, // default to safest option
 };
 
+/*
+ * Group AP / Station State
+ */
+
+enum lws_gapss {
+       LWS_GAPSS_INITIAL,      /* just started up, init and move to LWS_GAPSS_SCAN */
+       LWS_GAPSS_SCAN,         /*
+                                * Unconnected, scanning: AP known in one of the config
+                                * slots -> configure it, start timeout + LWS_GAPSS_STAT,
+                                * if no AP already up in same group with lower MAC,
+                                * after a random period start up our AP (LWS_GAPSS_AP)
+                                */
+       LWS_GAPSS_AP,           /*
+                                * Trying to be the group AP... periodically do a scan
+                                * LWS_GAPSS_AP_SCAN, faster and then slower
+                                        */
+       LWS_GAPSS_AP_SCAN,      /*
+                                * doing a scan while trying to be the group AP... if
+                                * we see a lower MAC being the AP for the same group
+                                * AP, abandon being an AP and join that AP as a
+                                * station
+                                */
+       LWS_GAPSS_STAT_GRP_AP,  /*
+                                * We have decided to join another group member who is
+                                * being the AP, as its MAC is lower than ours.  This
+                                * is a stable state, but we still do periodic scans
+                                * (LWS_GAPSS_STAT_GRP_AP_SCAN) and will always prefer
+                                * an AP configured in a slot.
+                                */
+       LWS_GAPSS_STAT_GRP_AP_SCAN,
+                               /*
+                                * We have joined a group member who is doing the AP
+                                * job... we want to check every now and then if a
+                                * configured AP has appeared that we should better
+                                * use instead.  Otherwise stay in LWS_GAPSS_STAT_GRP_AP
+                                */
+       LWS_GAPSS_STAT,         /*
+                                * trying to connect to another non-group AP.  If we
+                                * don't get an IP within a timeout and retries,
+                                * blacklist it and go back 
+                                */
+};
+
 static romfs_t lws_esp32_romfs;
 static TimerHandle_t leds_timer;
+//static enum lws_gapss gapss = LWS_GAPSS_INITIAL;
 
 struct esp32_file {
        const struct inode *i;
@@ -599,9 +643,9 @@ uint32_t lws_esp32_get_reboot_type(void)
        int n = 0;
 
        ESP_ERROR_CHECK(nvs_open("lws-station", NVS_READWRITE, &nvh));
-       if (nvs_get_blob(nvh, "ssl-pub.der", NULL, &s) == ESP_OK)
+       if (nvs_get_blob(nvh, "ssl-pub.pem", NULL, &s) == ESP_OK)
                n = 1;
-       if (nvs_get_blob(nvh, "ssl-pri.der", NULL, &s) == ESP_OK)
+       if (nvs_get_blob(nvh, "ssl-pri.pem", NULL, &s) == ESP_OK)
                n |= 2;
        nvs_close(nvh);
 
@@ -642,9 +686,14 @@ esp_err_t lws_esp32_event_passthru(void *ctx, system_event_t *event)
                break;
        case SYSTEM_EVENT_STA_GOT_IP:
                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);
-               render_ip(lws_esp32.sta_mask, sizeof(lws_esp32.sta_mask) - 1, (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);
+               render_ip(lws_esp32.sta_ip, sizeof(lws_esp32.sta_ip) - 1,
+                               (uint8_t *)&event->event_info.got_ip.ip_info.ip);
+               render_ip(lws_esp32.sta_mask, sizeof(lws_esp32.sta_mask) - 1,
+                               (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);
+               break;
+       case SYSTEM_EVENT_SCAN_DONE:
                break;
        default:
                break;
@@ -770,10 +819,15 @@ lws_esp32_wlan_nvs_get(int retry)
        ESP_ERROR_CHECK(nvs_open("lws-station", NVS_READWRITE, &nvh));
 
        s = sizeof(config.sta.ssid) - 1;
-       if (nvs_get_str(nvh, "ssid", (char *)sta_config.sta.ssid, &s) != ESP_OK)
+       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';
+
        s = sizeof(config.sta.password) - 1;
-       if (nvs_get_str(nvh, "password", (char *)sta_config.sta.password, &s) != ESP_OK)
+       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)
@@ -793,6 +847,14 @@ lws_esp32_wlan_nvs_get(int retry)
        lws_esp32.access_pw[0] = '\0';
        nvs_get_str(nvh, "access_pw", lws_esp32.access_pw, &s);
 
+       lws_esp32.group[0] = '\0';
+       s = sizeof(lws_esp32.group);
+       nvs_get_str(nvh, "group", lws_esp32.group, &s);
+
+       lws_esp32.role[0] = '\0';
+       s = sizeof(lws_esp32.role);
+       nvs_get_str(nvh, "role", lws_esp32.role, &s);
+
        nvs_close(nvh);
 
        if (retry && sta_config.sta.ssid[0]) {
@@ -957,8 +1019,8 @@ lws_esp32_set_creation_defaults(struct lws_context_creation_info *info)
        info->options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS |
                       LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
 
-       info->ssl_cert_filepath = "ssl-pub.der";
-       info->ssl_private_key_filepath = "ssl-pri.der";
+       info->ssl_cert_filepath = "ssl-pub.pem";
+       info->ssl_private_key_filepath = "ssl-pri.pem";
 }
 
 int
@@ -1024,10 +1086,10 @@ lws_esp32_init(struct lws_context_creation_info *info)
        ESP_ERROR_CHECK(nvs_open("lws-station", NVS_READWRITE, &nvh));
        n = 0;
        s = 1;
-       if (nvs_get_blob(nvh, "ssl-pub.der", NULL, &s) == ESP_OK)
+       if (nvs_get_blob(nvh, "ssl-pub.pem", NULL, &s) == ESP_OK)
                n = 1;
        s = 1;
-       if (nvs_get_blob(nvh, "ssl-pri.der", NULL, &s) == ESP_OK)
+       if (nvs_get_blob(nvh, "ssl-pri.pem", NULL, &s) == ESP_OK)
                n |= 2;
        nvs_close(nvh);
 
index 21fccdc..e2cee95 100644 (file)
@@ -2078,6 +2078,9 @@ LWS_EXTERN int LWS_WARN_UNUSED_RESULT
 lws_check_utf8(unsigned char *state, unsigned char *buf, size_t len);
 LWS_EXTERN int alloc_file(struct lws_context *context, const char *filename, uint8_t **buf,
                                lws_filepos_t *amount);
+LWS_EXTERN int alloc_pem_to_der_file(struct lws_context *context, const char *filename, uint8_t **buf,
+              lws_filepos_t *amount);
+
 LWS_EXTERN void
 lws_same_vh_protocol_remove(struct lws *wsi);
 LWS_EXTERN void
index 039c7bf..873ad04 100644 (file)
@@ -365,7 +365,7 @@ lws_context_init_server_ssl(struct lws_context_creation_info *info,
                lws_filepos_t flen;
                int err;
 
-               if (alloc_file(vhost->context, info->ssl_cert_filepath, &p,
+               if (alloc_pem_to_der_file(vhost->context, info->ssl_cert_filepath, &p,
                                                &flen)) {
                        lwsl_err("couldn't find cert file %s\n",
                                 info->ssl_cert_filepath);
@@ -378,7 +378,7 @@ lws_context_init_server_ssl(struct lws_context_creation_info *info,
                        return 1;
                }
 
-               if (alloc_file(vhost->context,
+               if (alloc_pem_to_der_file(vhost->context,
                               info->ssl_private_key_filepath, &p, &flen)) {
                        lwsl_err("couldn't find cert file %s\n",
                                 info->ssl_cert_filepath);
index b5f9e1e..ea1f4af 100644 (file)
--- a/lib/ssl.c
+++ b/lib/ssl.c
@@ -72,8 +72,11 @@ int alloc_file(struct lws_context *context, const char *filename, uint8_t **buf,
                n = 2;
                goto bail;
        }
-       if (nvs_get_blob(nvh, filename, (char *)*buf, &s) != ESP_OK)
+       if (nvs_get_blob(nvh, filename, (char *)*buf, &s) != ESP_OK) {
+               free(*buf);
                n = 1;
+               goto bail;
+       }
 
        *amount = s;
 
@@ -82,6 +85,60 @@ bail:
 
        return n;
 }
+int alloc_pem_to_der_file(struct lws_context *context, const char *filename, uint8_t **buf,
+              lws_filepos_t *amount)
+{
+       uint8_t *pem, *p, *q, *end;
+       lws_filepos_t len;
+       int n;
+
+       n = alloc_file(context, filename, &pem, &len);
+       if (n)
+               return n;
+
+       /* trim the first line */
+
+       p = pem;
+       end = p + len;
+       if (strncmp((char *)p, "-----", 5))
+               goto bail;
+       p += 5;
+       while (p < end && *p != '\n' && *p != '-')
+               p++;
+
+       if (*p != '-')
+               goto bail;
+
+       while (p < end && *p != '\n')
+               p++;
+
+       if (p >= end)
+               goto bail;
+
+       p++;
+
+       /* trim the last line */
+
+       q = end - 2;
+
+       while (q > pem && *q != '\n')
+               q--;
+
+       if (*q != '\n')
+               goto bail;
+
+       *q = '\0';
+
+       *amount = lws_b64_decode_string((char *)p, (char *)pem, len);
+       *buf = pem;
+
+       return 0;
+
+bail:
+       free(pem);
+
+       return 4;
+}
 #endif
 
 int openssl_websocket_private_data_index,
@@ -336,7 +393,7 @@ lws_ssl_capable_read(struct lws *wsi, unsigned char *buf, int len)
 
        if (n < 0) {
                n = lws_ssl_get_error(wsi, n);
-               lwsl_notice("get_ssl_err result %d\n", n);
+               // lwsl_notice("get_ssl_err result %d\n", n);
                if (n ==  SSL_ERROR_WANT_READ || SSL_want_read(wsi->ssl)) {
                        lwsl_debug("%s: WANT_READ\n", __func__);
                        lwsl_debug("%p: LWS_SSL_CAPABLE_MORE_SERVICE\n", wsi);
index b6b2963..1a8c9f4 100644 (file)
@@ -27,6 +27,7 @@ typedef enum {
        SCAN_STATE_NONE,
        SCAN_STATE_INITIAL,
        SCAN_STATE_INITIAL_MANIFEST,
+       SCAN_STATE_KNOWN,
        SCAN_STATE_LIST,
        SCAN_STATE_FINAL
 } scan_state;
@@ -84,9 +85,17 @@ struct per_vhost_data__esplws_scan {
 };
 
 static const struct store_json store_json[] = {
-       { "ssid\":\"", "ssid" },
-       { ",\"pw\":\"", "password" },
+       { "\"ssid0\":\"", "ssid0" },
+       { ",\"pw0\":\"", "password0" },
+       { "\"ssid1\":\"", "ssid1" },
+       { ",\"pw1\":\"", "password1" },
+       { "\"ssid2\":\"", "ssid2" },
+       { ",\"pw2\":\"", "password2" },
+       { "\"ssid3\":\"", "ssid3" },
+       { ",\"pw3\":\"", "password3" },
        { ",\"access_pw\":\"", "access_pw" },
+       { "{\"group\":\"", "group" },
+       { "{\"role\":\"", "role" },
        { ",\"region\":\"", "region" },
 };
 
@@ -240,7 +249,7 @@ scan_finished(void *v)
                esp_wifi_connect();
 }
 
-static const char *ssl_names[] = { "ssl-pub.der", "ssl-pri.der" };
+static const char *ssl_names[] = { "ssl-pub.pem", "ssl-pri.pem" };
 
 static int
 file_upload_cb(void *data, const char *name, const char *filename,
@@ -363,10 +372,9 @@ callback_esplws_scan(struct lws *wsi, enum lws_callback_reasons reason,
 
                switch (pss->scan_state) {
                        struct timeval t;
-                       char ssid[32];
                        uint8_t mac[6];
                        struct lws_esp32_image i;
-                       char img_factory[512], img_ota[512];
+                       char img_factory[512], img_ota[512], group[16];
                        int grt;
 
                case SCAN_STATE_INITIAL:
@@ -379,13 +387,13 @@ callback_esplws_scan(struct lws *wsi, enum lws_callback_reasons reason,
 
                        ESP_ERROR_CHECK(nvs_open("lws-station", NVS_READWRITE, &nvh));
                        n = 0;
-                       if (nvs_get_blob(nvh, "ssl-pub.der", NULL, &s) == ESP_OK)
+                       if (nvs_get_blob(nvh, "ssl-pub.pem", NULL, &s) == ESP_OK)
                                n = 1;
-                       if (nvs_get_blob(nvh, "ssl-pri.der", NULL, &s) == ESP_OK)
+                       if (nvs_get_blob(nvh, "ssl-pri.pem", NULL, &s) == ESP_OK)
                                n |= 2;
-                       s = sizeof(ssid) - 1;
-                       ssid[0] = '\0';
-                       nvs_get_str(nvh, "ssid", ssid, &s);
+                       s = sizeof(group) - 1;
+                       group[0] = '\0';
+                       nvs_get_str(nvh, "group", group, &s);
 
                        nvs_close(nvh);
 
@@ -422,6 +430,7 @@ callback_esplws_scan(struct lws *wsi, enum lws_callback_reasons reason,
                                      " \"conn_ip\":\"%s\",\n"
                                      " \"conn_mask\":\"%s\",\n"
                                      " \"conn_gw\":\"%s\",\n"
+                                     " \"group\":\"%s\",\n"
                                      " \"img_factory\": %s,\n"
                                      " \"img_ota\": %s,\n",
                                      lws_esp32.model,
@@ -432,10 +441,11 @@ callback_esplws_scan(struct lws *wsi, enum lws_callback_reasons reason,
                                      lws_esp32.region,
                                      n & 1, (n >> 1) & 1,
                                      mac[0], mac[1], mac[2], mac[3], mac[4], mac[5] | 1,
-                                     ssid,
+                                     lws_esp32.active_ssid,
                                      lws_esp32.sta_ip,
                                      lws_esp32.sta_mask,
                                      lws_esp32.sta_gw,
+                                     group,
                                        img_factory,
                                        img_ota
                                      );
@@ -455,7 +465,46 @@ callback_esplws_scan(struct lws *wsi, enum lws_callback_reasons reason,
                                      );
 
                        p += snprintf((char *)p, end - p,
-                                      " \"aps\":[\n");
+                                      " \"known\":[\n");
+
+                       n = LWS_WRITE_CONTINUATION | LWS_WRITE_NO_FIN;
+                       pss->scan_state = SCAN_STATE_KNOWN;
+                       break;
+
+               case SCAN_STATE_KNOWN:
+                       if (nvs_open("lws-station", NVS_READONLY, &nvh)) {
+                               lwsl_notice("unable to open nvh\n");
+                               return -1;
+                       }
+
+                       for (m = 0; m < 4; m++) {
+                               char name[10], ssid[32];
+                               unsigned int pp = 0, use = 0;
+
+                               if (m)
+                                       *p++ = ',';
+
+                               s = sizeof(ssid) - 1;
+                               ssid[0] = '\0';
+                               lws_snprintf(name, sizeof(name) - 1, "ssid%d", m);
+                               nvs_get_str(nvh, name, ssid, &s);
+                               lws_snprintf(name, sizeof(name) - 1, "password%d", m);
+                               s = 10;
+                               nvs_get_str(nvh, name, NULL, &s);
+                               pp = !!s;
+                               lws_snprintf(name, sizeof(name) - 1, "use%d", m);
+                               nvs_get_u32(nvh, name, &use);
+
+                               p += snprintf((char *)p, end - p,
+                                       "{\"ssid\":\"%s\",\n"
+                                       " \"pp\":\"%u\",\n"
+                                       "\"use\":\"%u\"}\n",
+                                       ssid, pp, use);
+                       }
+                       nvs_close(nvh);
+
+                       p += snprintf((char *)p, end - p,
+                                      "], \"aps\":[\n");
 
                        n = LWS_WRITE_CONTINUATION | LWS_WRITE_NO_FIN;
                        pss->scan_state = SCAN_STATE_LIST;
@@ -534,7 +583,7 @@ issue:
                        if (vhd->json_len && strstr((const char *)in, "update-ota"))
                                goto auton;
 
-                       if (strstr((const char *)in, "reset"))
+                       if (strstr((const char *)in, "\"reset\""))
                                goto sched_reset;
 
                        if (nvs_open("lws-station", NVS_READWRITE, &nvh) != ESP_OK) {
@@ -547,10 +596,18 @@ issue:
                                        continue;
 
                                /* only change access password if he has physical access to device */
-                               if (n == 2 && lws_esp32_get_reboot_type() != LWS_MAGIC_REBOOT_TYPE_FORCED_FACTORY_BUTTON)
+                               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);
+                               if (n == 9) {
+                                       strncpy(lws_esp32.group, p, sizeof(lws_esp32.group) - 1);
+                                       lws_esp32.group[sizeof(lws_esp32.group) - 1] = '\0';
+                               }
+                               if (n == 10) {
+                                       strncpy(lws_esp32.role, p, sizeof(lws_esp32.role) - 1);
+                                       lws_esp32.role[sizeof(lws_esp32.role) - 1] = '\0';
+                               }
 
                                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);
@@ -561,17 +618,21 @@ issue:
                        nvs_commit(nvh);
                        nvs_close(nvh);
 
-                       if (lws_esp32_get_reboot_type() == LWS_MAGIC_REBOOT_TYPE_FORCED_FACTORY_BUTTON) {
+                       if (strstr((const char *)in, "\"factory-reset\"")) {
+                               if (lws_esp32_get_reboot_type() ==
+                                       LWS_MAGIC_REBOOT_TYPE_FORCED_FACTORY_BUTTON) {
 
-                               if (strstr((const char *)in, "factory-reset")) {
+                                       lwsl_notice("Doing factory reset\n");
                                        ESP_ERROR_CHECK(nvs_open("lws-station", NVS_READWRITE, &nvh));
-                                       nvs_erase_all(nvh);
+                                       n = nvs_erase_all(nvh);
+                                       if (n)
+                                               lwsl_notice("erase_all failed %d\n", n);
                                        nvs_commit(nvh);
                                        nvs_close(nvh);
 
                                        goto sched_reset;
-                               }
-
+                               } else
+                                       lwsl_notice("failed on factory button boot\n");
                        }
 
                        if (vhd->scan_ongoing)
index 0049fe6..95d03f8 100644 (file)
@@ -15,13 +15,12 @@ ifeq ($(FAC),)
        FAC=0
 endif
 export FAC
+DIRNAME:=$(shell basename $$(pwd) | tr -d '\n')
 
-.PHONY: romfs.img
-pack.img:
+$(COMPONENT_PATH)/../build/pack.img: $(APP_BIN)
        GNUSTAT=stat ;\
        if [ `which gstat 2>/dev/null` ] ; then GNUSTAT=gstat ; fi ;\
        DIRNAME=$$(basename $$(pwd) | tr -d '\n') ;\
-       cp $(COMPONENT_PATH)/../build/$(PROJECT_NAME).bin $(COMPONENT_PATH)/../build/$$DIRNAME.bin ; \
        genromfs -f $(COMPONENT_PATH)/../build/romfs.img -d $(COMPONENT_PATH)/../romfs-files ; \
         RLEN=$$($$GNUSTAT -c %s $(COMPONENT_PATH)/../build/romfs.img) ;\
         LEN=$$($$GNUSTAT -c %s $(COMPONENT_PATH)/../build/$$DIRNAME.bin) ;\
@@ -54,6 +53,7 @@ pack.img:
        printf %02x $$(( ( $$JLEN / 65536 ) % 256 )) | xxd -r -p >> $(COMPONENT_PATH)/../build/$$DIRNAME.bin ;\
        printf %02x $$(( ( $$JLEN / 16777216 ) % 256 )) | xxd -r -p >> $(COMPONENT_PATH)/../build/$$DIRNAME.bin ;\
        cat $(jbi) >> $(COMPONENT_PATH)/../build/$$DIRNAME.bin ;\
+       cp $(COMPONENT_PATH)/../build/$$DIRNAME.bin $(COMPONENT_PATH)/../build/pack.img ;\
         LEN=$$($$GNUSTAT -c %s $(COMPONENT_PATH)/../build/$$DIRNAME.bin) ;\
        cp $(COMPONENT_PATH)/../build/$$DIRNAME.bin $(COMPONENT_PATH)/../build/$$DIRNAME-$$UNIXTIME.bin ;\
        printf "    After ROMFS + Build info: 0x%06x (%8d)\n" $$LEN $$LEN
@@ -74,9 +74,11 @@ endif
        cat $(F)/build/json-buildinfo >> build/manifest.json
        echo -n -e "\r\n}\r\n" >> build/manifest.json
 
-all: pack.img
+all: $(COMPONENT_PATH)/../build/pack.img
 
-flash_ota:
+flash: $(COMPONENT_PATH)/../build/pack.img
+
+flash_ota: $(COMPONENT_PATH)/../build/pack.img
        DIRNAME=$$(basename $$(pwd) | tr -d '\n') ;\
        $(IDF_PATH)/components/esptool_py/esptool/esptool.py \
                --chip esp32 \