diff --git a/armsrc/bwm_wifi.c b/armsrc/bwm_wifi.c index 9b580c56e..99636819f 100644 --- a/armsrc/bwm_wifi.c +++ b/armsrc/bwm_wifi.c @@ -24,9 +24,30 @@ static uint16_t wifi_crc16(const uint8_t *d, size_t n, uint16_t crc) { // Runs only during setup, so it fully owns the RX stream here. typedef enum { W_H1, W_H2, W_CL, W_CH, W_LL, W_LH, W_PL, W_KL, W_KH } wstate_t; +#ifndef MIN +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif + +// Drop whatever is already sitting in the UART RX ring so a late ACK from a +// timed-out command cannot be consumed as the next command's response (same +// cmd code, e.g. a slow OTA_BEGIN ack arriving after we already gave up). +static void bwm_cmd_drain_rx(void) { + uint8_t buf[64]; + uint32_t t0 = GetTickCount(); + while (bwm_uart_rx_available()) { + uint16_t avail = bwm_uart_rx_available(); + (void)bwm_uart_read(buf, (uint32_t)MIN(avail, (uint16_t)sizeof(buf))); + if (GetTickCountDelta(t0) > 50) { + break; + } + } +} + int bwm_cmd(uint16_t cmd, const uint8_t *req, uint16_t req_len, uint8_t *resp, uint16_t *resp_len, uint32_t timeout_ms) { + bwm_cmd_drain_rx(); + // ---- build + send HOST_CMD frame ---- uint8_t frame[8 + 256]; if (req_len > sizeof(frame) - 8) { @@ -63,6 +84,7 @@ int bwm_cmd(uint16_t cmd, const uint8_t *req, uint16_t req_len, uint8_t buf[64]; uint16_t avail = bwm_uart_rx_available(); if (avail == 0) { + SpinDelay(1); continue; } uint32_t n = bwm_uart_read(buf, (uint32_t)MIN(avail, (uint16_t)sizeof(buf))); @@ -304,16 +326,20 @@ int bwm_esp_ota_begin(uint32_t total_size) { uint8_t off = 0; (void)bwm_cmd(BWM_CMD_LOG_FORWARD_ENABLE, &off, 1, NULL, NULL, 500); - // Drop the AT32<->ESP link to a slow, forgiving baud for the transfer. An OTA - // is one-shot so speed is irrelevant, and 921600 is marginal against the - // flash-write / BLE contention that drops the odd frame -> random timeouts. - (void)bwm_fwd_negotiate_baud(BWM_OTA_BAUD); + // Stop BLE so NimBLE is not hitting flash (NVS / auto-suspend) while we + // erase and program the OTA slot. WiFi stays as-is: tearing it down here + // is slow and the USB OTA path does not need it off. + (void)bwm_cmd(BWM_CMD_STOP_BLE_SPP, NULL, 0, NULL, NULL, 2000); + + // Do NOT retune the UART here. Version already proved the current baud + // works; dropping 921600 -> 460800/460000 can leave the ESP on one rate + // and the AT32 on the other, after which every OTA_BEGIN times out. uint8_t p[4] = { (uint8_t)(total_size & 0xFF), (uint8_t)((total_size >> 8) & 0xFF), (uint8_t)((total_size >> 16) & 0xFF), (uint8_t)((total_size >> 24) & 0xFF) }; - return bwm_cmd(BWM_CMD_OTA_BEGIN, p, sizeof(p), NULL, NULL, 15000); + return bwm_cmd(BWM_CMD_OTA_BEGIN, p, sizeof(p), NULL, NULL, BWM_OTA_BEGIN_TIMEOUT_MS); } int bwm_esp_ota_write(const uint8_t *data, uint16_t len) { @@ -322,25 +348,23 @@ int bwm_esp_ota_write(const uint8_t *data, uint16_t len) { // fail the OTA_END size check) - recovery is to restart the whole OTA, which // the client does. Keep a generous timeout so a slow flash write is not // mistaken for a drop. - return bwm_cmd(BWM_CMD_OTA_WRITE, data, len, NULL, NULL, 15000); + return bwm_cmd(BWM_CMD_OTA_WRITE, data, len, NULL, NULL, BWM_OTA_WRITE_TIMEOUT_MS); } int bwm_esp_ota_end(void) { - // OTA_END goes out at the slow OTA baud (both ends still there); only after it - // do we restore the fast link and log forwarding (both set in _begin). int r = bwm_cmd(BWM_CMD_OTA_END, NULL, 0, NULL, NULL, 20000); - (void)bwm_fwd_negotiate_baud(BWM_UART_BAUD_TARGET); uint8_t on = 1; (void)bwm_cmd(BWM_CMD_LOG_FORWARD_ENABLE, &on, 1, NULL, NULL, 500); + (void)bwm_cmd(BWM_CMD_START_BLE_SPP, NULL, 0, NULL, NULL, 2000); return r; } int bwm_esp_ota_abort(void) { - // OTA gave up mid-transfer: restore the fast link + logs so the module is - // usable again. The ESP's incomplete OTA state is discarded by the next BEGIN. - (void)bwm_fwd_negotiate_baud(BWM_UART_BAUD_TARGET); + // Restore logs + BLE. Leave the UART baud alone (see begin). The ESP's + // incomplete OTA state is discarded by the next BEGIN. uint8_t on = 1; (void)bwm_cmd(BWM_CMD_LOG_FORWARD_ENABLE, &on, 1, NULL, NULL, 500); + (void)bwm_cmd(BWM_CMD_START_BLE_SPP, NULL, 0, NULL, NULL, 2000); return PM3_SUCCESS; } diff --git a/armsrc/bwm_wifi.h b/armsrc/bwm_wifi.h index f62be2e09..d9528137c 100644 --- a/armsrc/bwm_wifi.h +++ b/armsrc/bwm_wifi.h @@ -75,11 +75,17 @@ int bwm_wifi_forward_status(uint8_t *state, uint32_t *ip_out); // After a working OTA_END, the ESP has marked the new partition bootable but // does not reboot on its own - REBOOT must be sent explicitly (DEV.md 12.8). #define BWM_CMD_REBOOT 1803 -// Run the OTA at a slow, reliable baud (restored to the fast rate at end/abort). -#ifndef BWM_OTA_BAUD -#define BWM_OTA_BAUD 460000 +// BEGIN erases (or finishes aborting) an OTA slot. A 1.8 MB erase on ESP32-C2 +// commonly takes 20-40 s, so this must be well above the old 15 s race. +#ifndef BWM_OTA_BEGIN_TIMEOUT_MS +#define BWM_OTA_BEGIN_TIMEOUT_MS 60000 +#endif +#ifndef BWM_OTA_WRITE_TIMEOUT_MS +#define BWM_OTA_WRITE_TIMEOUT_MS 20000 #endif #define BWM_CMD_GET_VERSION_INFO 1000 // resp: running firmware version string +#define BWM_CMD_STOP_BLE_SPP 4022 // no payload: stop BLE during OTA (flash contention) +#define BWM_CMD_START_BLE_SPP 4021 // no payload: restore BLE after OTA int bwm_esp_get_version(uint8_t *buf, uint16_t *buflen); int bwm_esp_ota_begin(uint32_t total_size); int bwm_esp_ota_write(const uint8_t *data, uint16_t len); diff --git a/client/src/cmdhw.c b/client/src/cmdhw.c index a11993698..258fad109 100644 --- a/client/src/cmdhw.c +++ b/client/src/cmdhw.c @@ -2310,15 +2310,23 @@ static void progressbar(long sent, long total, int style) { static int bwm_ota_once(const uint8_t *fw, size_t fwlen, uint32_t write_delay_ms) { PacketResponseNG resp; - // BEGIN: tell the BWM how many bytes are coming (it erases the target partition) + // BEGIN: tell the BWM how many bytes are coming. The ESP erases the idle + // OTA slot here; that can take 20-40 s on a 4 MB ESP32-C2, so wait longer + // than the device-side 60 s timeout plus USB round-trip. uint8_t beg[5] = { BWM_OTA_ACTION_BEGIN, (uint8_t)(fwlen & 0xFF), (uint8_t)((fwlen >> 8) & 0xFF), (uint8_t)((fwlen >> 16) & 0xFF), (uint8_t)((fwlen >> 24) & 0xFF) }; clearCommandBuffer(); SendCommandNG(CMD_PM5_BWM_ESP_OTA, beg, sizeof(beg)); - if ((WaitForResponseTimeout(CMD_PM5_BWM_ESP_OTA, &resp, 20000) == false) || (resp.status != PM3_SUCCESS)) { - PrintAndLogEx(FAILED, "OTA begin failed (is a responsive BWM fitted?)"); - return PM3_EFAILED; + if (WaitForResponseTimeout(CMD_PM5_BWM_ESP_OTA, &resp, 75000) == false) { + PrintAndLogEx(FAILED, "OTA begin timed out (ESP is likely still erasing the OTA slot)"); + PrintAndLogEx(HINT, "Wait a few seconds and retry; do not power-cycle mid-erase."); + return PM3_ETIMEOUT; + } + if (resp.status != PM3_SUCCESS) { + PrintAndLogEx(FAILED, "OTA begin failed (status %d)%s", resp.status, + (resp.status == PM3_ETIMEOUT) ? " - UART timeout waiting for ESP" : ""); + return resp.status; } PrintAndLogEx(INFO, "Uploading " _YELLOW_("%zu") " bytes of ESP firmware over the BWM link...", fwlen); @@ -2336,11 +2344,13 @@ static int bwm_ota_once(const uint8_t *fw, size_t fwlen, uint32_t write_delay_ms memcpy(buf + 1, fw + sent, n); clearCommandBuffer(); SendCommandNG(CMD_PM5_BWM_ESP_OTA, buf, (uint16_t)(n + 1)); - bool got = WaitForResponseTimeout(CMD_PM5_BWM_ESP_OTA, &resp, 15000); + bool got = WaitForResponseTimeout(CMD_PM5_BWM_ESP_OTA, &resp, 25000); if (!got || resp.status != PM3_SUCCESS) { PrintAndLogEx(NORMAL, ""); if (!got) { PrintAndLogEx(WARNING, "OTA write stalled at offset %zu (no response)", sent); + } else if (resp.status == PM3_ETIMEOUT) { + PrintAndLogEx(WARNING, "OTA write timed out at offset %zu (ESP did not ACK that chunk)", sent); } else { PrintAndLogEx(WARNING, "OTA write rejected at offset %zu (status %d)", sent, resp.status); } @@ -2376,7 +2386,7 @@ static int bwm_ota_once(const uint8_t *fw, size_t fwlen, uint32_t write_delay_ms // That is a genuine failure (boot partition NOT switched) - restart, and // hint at pacing, which is the usual cure. PrintAndLogEx(WARNING, "OTA finalize rejected (status %d) - data was lost in transit", resp.status); - PrintAndLogEx(HINT, "Try a per-write delay: " _YELLOW_("hw bwm upgrade -f --delay 10")); + PrintAndLogEx(HINT, "Try a per-write delay: " _YELLOW_("hw bwm upgrade -f --delay 20")); return PM3_EFAILED; } // No answer at all. Over BLE the END auto-reboot drops the link before the ack @@ -2410,14 +2420,14 @@ static int CmdBWMUpgrade(const char *Cmd) { void *argtable[] = { arg_param_begin, arg_str1("f", "file", "", "ESP32 firmware image (.bin)"), - arg_int0(NULL, "delay", "", "per-chunk delay to pace the slow AT32<->ESP UART (default 10)"), + arg_int0(NULL, "delay", "", "per-chunk delay to pace the slow AT32<->ESP UART (default 20)"), arg_param_end, }; CLIExecWithReturn(ctx, Cmd, argtable, false); int fnlen = 0; char fn[FILE_PATH_SIZE] = {0}; CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)fn, sizeof(fn), &fnlen); - uint32_t write_delay_ms = (uint32_t)arg_get_int_def(ctx, 2, 10); + uint32_t write_delay_ms = (uint32_t)arg_get_int_def(ctx, 2, 20); CLIParserFree(ctx); if (fnlen == 0) { @@ -2468,7 +2478,16 @@ static int CmdBWMUpgrade(const char *Cmd) { // retry (offset-idempotent ESP write) lands. for (int attempt = 1; attempt <= max_attempts; attempt++) { if (attempt > 1) { + // Abort the in-flight ESP OTA (if any) and give a slow erase a chance + // to finish before we BEGIN again. Otherwise attempt N's BEGIN races + // attempt N-1's still-running erase and times out. PrintAndLogEx(INFO, "restarting OTA from the beginning (attempt " _YELLOW_("%d") "/%d)", attempt, max_attempts); + uint8_t ab[1] = { BWM_OTA_ACTION_ABORT }; + clearCommandBuffer(); + SendCommandNG(CMD_PM5_BWM_ESP_OTA, ab, sizeof(ab)); + PacketResponseNG abortr; + (void)WaitForResponseTimeout(CMD_PM5_BWM_ESP_OTA, &abortr, 8000); + msleep(3000); } int res = bwm_ota_once(fw, fwlen, write_delay_ms);