mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2026-09-16 04:06:20 +00:00
Merge branch 'master' of https://github.com/Antiklesys/proxmark3
This commit is contained in:
@@ -589,6 +589,18 @@ static void SendStatus(uint32_t wait) {
|
||||
#endif
|
||||
#ifdef WITH_BWM_FORWARD
|
||||
Dbprintf(" BWM link baud....... " _YELLOW_("%u") " bps", bwm_uart_get_baud());
|
||||
{
|
||||
// Read the ESP firmware version so hw status shows what the BWM runs
|
||||
// (and lets you confirm an OTA took: the string flips after a reflash).
|
||||
uint8_t bwm_ver[64] = {0};
|
||||
uint16_t bwm_ver_len = sizeof(bwm_ver) - 1;
|
||||
if (bwm_esp_get_version(bwm_ver, &bwm_ver_len) == PM3_SUCCESS) {
|
||||
bwm_ver[bwm_ver_len] = 0x00;
|
||||
Dbprintf(" BWM fw version...... " _YELLOW_("%s"), bwm_ver);
|
||||
} else {
|
||||
Dbprintf(" BWM fw version...... " _YELLOW_("%s"), "unknown");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
printConnSpeed(wait);
|
||||
DbpString(_CYAN_("Various"));
|
||||
@@ -3940,6 +3952,72 @@ static void PacketReceived(PacketCommandNG *packet) {
|
||||
}
|
||||
#else
|
||||
reply_ng(CMD_PM5_BWM_WIFI, PM3_ENOTIMPL, NULL, 0);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case CMD_PM5_BWM_ESP_OTA: {
|
||||
// ESP32 OTA over the existing BWM app_com link (see bwm_wifi.c).
|
||||
// Payload: [action:u8] + action-specific data.
|
||||
// BEGIN: u32 LE total image size
|
||||
// WRITE: firmware chunk
|
||||
// END: (no payload) finalize + set boot partition
|
||||
#if defined(WITH_BWM_FORWARD)
|
||||
uint8_t action = packet->data.asBytes[0];
|
||||
int res = PM3_EINVARG;
|
||||
bool replied = false;
|
||||
switch (action) {
|
||||
case BWM_OTA_ACTION_VERSION: {
|
||||
// Return the running ESP firmware version string so the client
|
||||
// can confirm an update actually took (esp. when the finalize
|
||||
// ack is lost). Replies here with a payload, unlike the others.
|
||||
uint8_t ver[64];
|
||||
uint16_t vlen = sizeof(ver);
|
||||
res = bwm_esp_get_version(ver, &vlen);
|
||||
reply_ng(CMD_PM5_BWM_ESP_OTA, res, ver, (res == PM3_SUCCESS) ? vlen : 0);
|
||||
replied = true;
|
||||
break;
|
||||
}
|
||||
case BWM_OTA_ACTION_BEGIN: {
|
||||
uint32_t total_size = 0;
|
||||
if (packet->length >= 5) {
|
||||
memcpy(&total_size, packet->data.asBytes + 1, sizeof(total_size));
|
||||
}
|
||||
res = bwm_esp_ota_begin(total_size);
|
||||
break;
|
||||
}
|
||||
case BWM_OTA_ACTION_WRITE:
|
||||
// Defensive: bwm_esp_ota_write()'s app_com frame buffer caps a
|
||||
// single chunk at BWM_OTA_CHUNK_MAX (see bwm_wifi.c: bwm_cmd()).
|
||||
// The client is expected to respect this, but fail explicitly
|
||||
// here rather than let bwm_cmd() silently overflow/reject.
|
||||
if (packet->length - 1 > BWM_OTA_CHUNK_MAX) {
|
||||
res = PM3_EOVFLOW;
|
||||
} else {
|
||||
res = bwm_esp_ota_write(packet->data.asBytes + 1, packet->length - 1);
|
||||
}
|
||||
break;
|
||||
case BWM_OTA_ACTION_END:
|
||||
res = bwm_esp_ota_end();
|
||||
if (res == PM3_SUCCESS) {
|
||||
// Finalize succeeded and the new partition is now marked
|
||||
// bootable; the ESP won't switch to it on its own, so
|
||||
// kick the reboot here (DEV.md 12.8). Best-effort: don't
|
||||
// fail the whole OTA over a lost reboot ack.
|
||||
(void)bwm_esp_reboot();
|
||||
}
|
||||
break;
|
||||
case BWM_OTA_ACTION_ABORT:
|
||||
res = bwm_esp_ota_abort();
|
||||
break;
|
||||
default:
|
||||
res = PM3_EINVARG;
|
||||
break;
|
||||
}
|
||||
if (replied == false) {
|
||||
reply_ng(CMD_PM5_BWM_ESP_OTA, res, NULL, 0);
|
||||
}
|
||||
#else
|
||||
reply_ng(CMD_PM5_BWM_ESP_OTA, PM3_ENOTIMPL, NULL, 0);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -150,6 +150,16 @@ int bwm_uart_write(const uint8_t *data, size_t len) {
|
||||
}
|
||||
|
||||
uint16_t bwm_uart_rx_available(void) {
|
||||
// An unhandled overrun (ROERR) latches on this USART and stops it feeding the
|
||||
// DMA - after one overrun every subsequent byte is lost until a re-init, which
|
||||
// is why a stalled OTA "recovers on re-run" but mostly fails in a session.
|
||||
// Clear it here so reception resumes on its own. ROERR clears by reading STS
|
||||
// then DT; we only do that when the flag is actually set - the DMA is stalled
|
||||
// then, so the byte we consume is the already-lost overrun byte.
|
||||
if (usart_flag_get(BWM_UART, USART_ROERR_FLAG) != RESET) {
|
||||
(void)BWM_UART->sts;
|
||||
(void)BWM_UART->dt;
|
||||
}
|
||||
return (uint16_t)((bwm_uart_rx_head() - s_rx_tail) & (BWM_RX_RING_SZ - 1));
|
||||
}
|
||||
|
||||
|
||||
+108
-7
@@ -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)));
|
||||
@@ -130,13 +152,18 @@ int bwm_cmd(uint16_t cmd, const uint8_t *req, uint16_t req_len,
|
||||
}
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
// a CMD_ERROR broadcast referencing our cmd -> failure.
|
||||
// (payload carries the failing cmd; treat any error report
|
||||
// that arrives while we wait as a failure of this step.)
|
||||
if (!is_resp && rcmd == BWM_CMD_CMD_ERROR) {
|
||||
return PM3_EFAILED;
|
||||
if (!is_resp && rcmd == BWM_CMD_CMD_ERROR && rlen >= 2) {
|
||||
uint16_t failed_cmd = (uint16_t)pbuf[0] | ((uint16_t)pbuf[1] << 8);
|
||||
if (failed_cmd == cmd) {
|
||||
int32_t esp_err = 0;
|
||||
if (rlen >= 6) {
|
||||
esp_err = (int32_t)((uint32_t)pbuf[2] | ((uint32_t)pbuf[3] << 8) |
|
||||
((uint32_t)pbuf[4] << 16) | ((uint32_t)pbuf[5] << 24));
|
||||
}
|
||||
Dbprintf("[bwm-wifi] cmd 0x%04x failed, esp_err=0x%08x", (unsigned)cmd, (unsigned)esp_err);
|
||||
return PM3_EFAILED;
|
||||
}
|
||||
}
|
||||
// otherwise: some other frame (e.g. a stray broadcast) - ignore
|
||||
}
|
||||
st = W_H1;
|
||||
break;
|
||||
@@ -256,7 +283,12 @@ int bwm_wifi_forward_status(uint8_t *state, uint32_t *ip_out) {
|
||||
}
|
||||
|
||||
int bwm_wifi_forward_down(void) {
|
||||
// Once WiFi is gone the status query returns a non-timeout error.
|
||||
// The ESP tears down the STA + TCP server and persists to NVS BEFORE it acks,
|
||||
// and its command task blocks during the teardown - so that ack can outlast
|
||||
// any timeout or be dropped. A plain ack-wait therefore reports failure on a
|
||||
// disable that actually worked. Instead: fire the disable, then CONFIRM by
|
||||
// polling the connect status. Once WiFi is gone the status query returns a
|
||||
// non-timeout error (the subsystem is down) - that is our proof of success.
|
||||
(void)bwm_cmd(BWM_CMD_SET_TO_WIFI_DISABLE_MODE, NULL, 0, NULL, NULL, 3000);
|
||||
|
||||
uint32_t t0 = GetTickCount();
|
||||
@@ -274,3 +306,72 @@ int bwm_wifi_forward_down(void) {
|
||||
}
|
||||
return PM3_ETIMEOUT;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ESP OTA forwarders. Each maps a host request to one ESP OTA app_com command.
|
||||
// esp_ota_begin erases the target partition and esp_ota_end finalizes + sets the
|
||||
// boot slot, so those get generous timeouts.
|
||||
// ---------------------------------------------------------------------------
|
||||
// Read the ESP's running firmware version string (APP_CMD_GET_VERSION_INFO).
|
||||
int bwm_esp_get_version(uint8_t *buf, uint16_t *buflen) {
|
||||
return bwm_cmd(BWM_CMD_GET_VERSION_INFO, NULL, 0, buf, buflen, 3000);
|
||||
}
|
||||
|
||||
int bwm_esp_ota_begin(uint32_t total_size) {
|
||||
// SILENCE ESP log forwarding for the OTA. With it on, the ESP's background
|
||||
// log broadcasts (WiFi/coex/BLE) interleave with the per-chunk acks across
|
||||
// the thousands of round-trips; one landing in an ack window drops that
|
||||
// chunk's reply -> random PM3_ETIMEOUT. Restored in bwm_esp_ota_end().
|
||||
// Best-effort: ignore failure.
|
||||
uint8_t off = 0;
|
||||
(void)bwm_cmd(BWM_CMD_LOG_FORWARD_ENABLE, &off, 1, NULL, NULL, 500);
|
||||
|
||||
// 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, BWM_OTA_BEGIN_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
int bwm_esp_ota_write(const uint8_t *data, uint16_t len) {
|
||||
// Documented OTA has no resume/offset: the payload is the raw chunk, written
|
||||
// sequentially. A dropped chunk cannot be re-sent (it would double-write and
|
||||
// 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, BWM_OTA_WRITE_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
int bwm_esp_ota_end(void) {
|
||||
int r = bwm_cmd(BWM_CMD_OTA_END, NULL, 0, NULL, NULL, 20000);
|
||||
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) {
|
||||
// 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;
|
||||
}
|
||||
|
||||
int bwm_esp_reboot(void) {
|
||||
// The ESP acks then calls esp_restart() - the ack itself may or may not
|
||||
// make it back before the UART goes away, so treat a timeout here as a
|
||||
// benign race, not a failure: the reboot was still requested.
|
||||
int r = bwm_cmd(BWM_CMD_REBOOT, NULL, 0, NULL, NULL, 3000);
|
||||
return (r == PM3_ETIMEOUT) ? PM3_SUCCESS : r;
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@
|
||||
|
||||
#define BWM_WIFI_FORWARD_TCP_SERVER 0 // wifi_forward_type_t::WIFI_FORWARD_TCP_SERVER
|
||||
#define BWM_CMD_CMD_ERROR 8091 // slave bcast: command error report
|
||||
#define BWM_CMD_LOG_FORWARD_ENABLE 1014 // payload: u8 (0=stop, non-zero=start)
|
||||
#define BWM_CMD_LOG_MESSAGE 8090 // slave bcast: ESP_LOGx output (string)
|
||||
|
||||
// Low-level: send one app_com HOST_CMD and wait for its SLAVE_RESP.
|
||||
// resp/resp_len may be NULL if no response payload is expected.
|
||||
@@ -65,4 +67,30 @@ int bwm_wifi_forward_down(void);
|
||||
#define BWM_WIFI_STATE_OFF 0xFF
|
||||
int bwm_wifi_forward_status(uint8_t *state, uint32_t *ip_out);
|
||||
|
||||
// ESP OTA over the BWM UART link (no header/soldering): drives the ESP's own
|
||||
// OTA commands to reflash a *working* BWM to a new ESP image.
|
||||
#define BWM_CMD_OTA_BEGIN 1800 // req: u32 total size
|
||||
#define BWM_CMD_OTA_WRITE 1801 // req: firmware chunk
|
||||
#define BWM_CMD_OTA_END 1802 // no payload: finalize + set boot partition
|
||||
// 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
|
||||
// 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);
|
||||
int bwm_esp_ota_end(void);
|
||||
int bwm_esp_reboot(void);
|
||||
int bwm_esp_ota_abort(void);
|
||||
|
||||
#endif
|
||||
|
||||
+382
-102
@@ -1508,32 +1508,35 @@ static int CmdTearoff(const char *Cmd) {
|
||||
}
|
||||
|
||||
static int CmdBwmAutoOff(const char *Cmd) {
|
||||
CLIParserContext *ctx;
|
||||
CLIParserInit(&ctx, "hw bwmautooff",
|
||||
"Toggle automatic power-off when the PM5 is unplugged from USB (BWM only).\n"
|
||||
"Default is " _GREEN_("on") ". When on, the board powers itself down ~10s after\n"
|
||||
"USB is removed, so a BWM-equipped PM5 doesn't silently drain the battery.\n"
|
||||
"Button power-on is unaffected. Disable for standalone/BLE use on battery.\n"
|
||||
_YELLOW_("Runtime only:") " resets to on at each boot.",
|
||||
"hw bwmautooff --off --> disable auto power-off\n"
|
||||
"hw bwmautooff --on --> re-enable auto power-off");
|
||||
// Positional sub-action (no dashes): hw bwm autooff on | off
|
||||
char verb[16] = {0};
|
||||
sscanf(Cmd, "%15s", verb);
|
||||
bool on = (strcmp(verb, "on") == 0);
|
||||
bool off = (strcmp(verb, "off") == 0);
|
||||
|
||||
void *argtable[] = {
|
||||
arg_param_begin,
|
||||
arg_lit0(NULL, "on", "enable auto power-off (default)"),
|
||||
arg_lit0(NULL, "off", "disable auto power-off"),
|
||||
arg_param_end
|
||||
};
|
||||
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
||||
bool on = arg_get_lit(ctx, 1);
|
||||
bool off = arg_get_lit(ctx, 2);
|
||||
CLIParserFree(ctx);
|
||||
|
||||
if (on && off) {
|
||||
PrintAndLogEx(WARNING, "pick one of --on / --off");
|
||||
if (!on && !off) {
|
||||
// Not a recognised sub-action: render help (also serves -h / empty),
|
||||
// or error on a stray token, then stop.
|
||||
CLIParserContext *ctx;
|
||||
CLIParserInit(&ctx, "hw bwm autooff",
|
||||
"Toggle automatic power-off when the PM5 is unplugged from USB (BWM only).\n"
|
||||
"Default is " _GREEN_("on") ". When on, the board powers itself down ~10s after\n"
|
||||
"USB is removed, so a BWM-equipped PM5 doesn't silently drain the battery.\n"
|
||||
"Button power-on is unaffected. Disable for standalone/BLE use on battery.\n"
|
||||
_YELLOW_("Runtime only:") " resets to on at each boot.",
|
||||
"hw bwm autooff off --> disable auto power-off\n"
|
||||
"hw bwm autooff on --> re-enable auto power-off");
|
||||
void *argtable[] = {
|
||||
arg_param_begin,
|
||||
arg_param_end
|
||||
};
|
||||
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
||||
CLIParserFree(ctx);
|
||||
PrintAndLogEx(WARNING, "specify " _YELLOW_("on") " or " _YELLOW_("off"));
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
uint8_t payload = off ? 0 : 1; // default (neither flag) = enable
|
||||
|
||||
uint8_t payload = off ? 0 : 1; // on -> 1 (enable), off -> 0 (disable)
|
||||
|
||||
clearCommandBuffer();
|
||||
SendCommandNG(CMD_PM5_BWM_AUTOOFF, &payload, sizeof(payload));
|
||||
@@ -1555,48 +1558,15 @@ static int CmdBwmAutoOff(const char *Cmd) {
|
||||
}
|
||||
|
||||
static int CmdBWMWifi(const char *Cmd) {
|
||||
CLIParserContext *ctx;
|
||||
CLIParserInit(&ctx, "hw bwmwifi",
|
||||
"Bring up the BWM in STA + TCP-server mode: join a WiFi network and\n"
|
||||
"start a TCP server so the client can connect over WiFi. PM5 only.",
|
||||
"hw bwmwifi --ssid Home --pwd secret --> port 7777\n"
|
||||
"hw bwmwifi --ssid Home --pwd secret --port 9000\n"
|
||||
"hw bwmwifi --status --> show connection state + IP");
|
||||
// Sub-actions that carry no other arguments are positional keywords now:
|
||||
// hw bwm wifi status (was --status)
|
||||
// hw bwm wifi stop (was --stop)
|
||||
// Bringing WiFi up still takes value flags, so it stays the default
|
||||
// (no-keyword) form: hw bwm wifi --ssid <ssid> --pwd <pwd> [--port <n>]
|
||||
char verb[16] = {0};
|
||||
sscanf(Cmd, "%15s", verb);
|
||||
|
||||
void *argtable[] = {
|
||||
arg_param_begin,
|
||||
arg_str0(NULL, "ssid", "<ssid>", "WiFi SSID to join (omit with --stop)"),
|
||||
arg_str0(NULL, "pwd", "<pwd>", "WiFi password (omit for open network)"),
|
||||
arg_int0(NULL, "port", "<dec>", "TCP server listen port (default 7777)"),
|
||||
arg_str0(NULL, "hostname", "<name>", "DHCP hostname (default Proxmark5)"),
|
||||
arg_lit0(NULL, "stop", "tear down WiFi and return to BLE-only"),
|
||||
arg_lit0(NULL, "status", "show current WiFi connection state + IP"),
|
||||
arg_param_end
|
||||
};
|
||||
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
||||
|
||||
uint8_t ssid[64] = {0};
|
||||
int ssid_len = 0;
|
||||
CLIParamStrToBuf(arg_get_str(ctx, 1), ssid, sizeof(ssid) - 1, &ssid_len);
|
||||
|
||||
uint8_t pwd[64] = {0};
|
||||
int pwd_len = 0;
|
||||
CLIParamStrToBuf(arg_get_str(ctx, 2), pwd, sizeof(pwd) - 1, &pwd_len);
|
||||
|
||||
int port = arg_get_int_def(ctx, 3, 7777);
|
||||
|
||||
uint8_t host[33] = {0};
|
||||
int host_len = 0;
|
||||
CLIParamStrToBuf(arg_get_str(ctx, 4), host, sizeof(host) - 1, &host_len);
|
||||
if (host_len == 0) {
|
||||
strcpy((char *)host, "Proxmark5");
|
||||
host_len = 9;
|
||||
}
|
||||
bool stop = arg_get_lit(ctx, 5);
|
||||
bool status = arg_get_lit(ctx, 6);
|
||||
CLIParserFree(ctx);
|
||||
|
||||
if (status) {
|
||||
if (strcmp(verb, "status") == 0) {
|
||||
uint8_t q[1] = { BWM_WIFI_ACTION_STATUS };
|
||||
clearCommandBuffer();
|
||||
SendCommandNG(CMD_PM5_BWM_WIFI, q, sizeof(q));
|
||||
@@ -1616,7 +1586,7 @@ static int CmdBWMWifi(const char *Cmd) {
|
||||
}
|
||||
switch (state) {
|
||||
case 0xFF:
|
||||
PrintAndLogEx(INFO, "BWM WiFi disabled (BLE-only). Bring it up with " _YELLOW_("hw bwmwifi --ssid <ssid> --pwd <pwd>"));
|
||||
PrintAndLogEx(INFO, "BWM WiFi disabled (BLE-only). Bring it up with " _YELLOW_("hw bwm wifi --ssid <ssid> --pwd <pwd>"));
|
||||
break;
|
||||
case 2: // connected
|
||||
if (ip) {
|
||||
@@ -1645,7 +1615,7 @@ static int CmdBWMWifi(const char *Cmd) {
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
if (stop) {
|
||||
if (strcmp(verb, "stop") == 0) {
|
||||
uint8_t off[1] = { BWM_WIFI_ACTION_STOP };
|
||||
clearCommandBuffer();
|
||||
SendCommandNG(CMD_PM5_BWM_WIFI, off, sizeof(off));
|
||||
@@ -1662,8 +1632,47 @@ static int CmdBWMWifi(const char *Cmd) {
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
CLIParserContext *ctx;
|
||||
CLIParserInit(&ctx, "hw bwm wifi",
|
||||
"Bring up the BWM in STA + TCP-server mode: join a WiFi network and\n"
|
||||
"start a TCP server so the client can connect over WiFi. PM5 only.\n"
|
||||
"Sub-actions (no dashes): 'status' shows state, 'stop' tears WiFi down.",
|
||||
"hw bwm wifi status --> show connection state + IP\n"
|
||||
"hw bwm wifi stop --> tear down WiFi, back to BLE-only\n"
|
||||
"hw bwm wifi --ssid Home --pwd secret --> bring up, port 7777\n"
|
||||
"hw bwm wifi --ssid Home --pwd secret --port 9000");
|
||||
|
||||
void *argtable[] = {
|
||||
arg_param_begin,
|
||||
arg_str0(NULL, "ssid", "<ssid>", "WiFi SSID to join"),
|
||||
arg_str0(NULL, "pwd", "<pwd>", "WiFi password (omit for open network)"),
|
||||
arg_int0(NULL, "port", "<dec>", "TCP server listen port (default 7777)"),
|
||||
arg_str0(NULL, "hostname", "<name>", "DHCP hostname (default Proxmark5)"),
|
||||
arg_param_end
|
||||
};
|
||||
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
||||
|
||||
uint8_t ssid[64] = {0};
|
||||
int ssid_len = 0;
|
||||
CLIParamStrToBuf(arg_get_str(ctx, 1), ssid, sizeof(ssid) - 1, &ssid_len);
|
||||
|
||||
uint8_t pwd[64] = {0};
|
||||
int pwd_len = 0;
|
||||
CLIParamStrToBuf(arg_get_str(ctx, 2), pwd, sizeof(pwd) - 1, &pwd_len);
|
||||
|
||||
int port = arg_get_int_def(ctx, 3, 7777);
|
||||
|
||||
uint8_t host[33] = {0};
|
||||
int host_len = 0;
|
||||
CLIParamStrToBuf(arg_get_str(ctx, 4), host, sizeof(host) - 1, &host_len);
|
||||
if (host_len == 0) {
|
||||
strcpy((char *)host, "Proxmark5");
|
||||
host_len = 9;
|
||||
}
|
||||
CLIParserFree(ctx);
|
||||
|
||||
if (ssid_len == 0) {
|
||||
PrintAndLogEx(FAILED, "an SSID is required (or use --stop to tear down)");
|
||||
PrintAndLogEx(FAILED, "an SSID is required (or " _YELLOW_("hw bwm wifi stop") " to tear down)");
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
if (port < 1 || port > 65535) {
|
||||
@@ -1699,7 +1708,7 @@ static int CmdBWMWifi(const char *Cmd) {
|
||||
}
|
||||
if (resp.status != PM3_SUCCESS) {
|
||||
PrintAndLogEx(FAILED, "BWM WiFi bring-up failed (check SSID/password and signal)");
|
||||
PrintAndLogEx(HINT, "If it may have joined after DHCP, check: " _YELLOW_("hw bwmwifi --status"));
|
||||
PrintAndLogEx(HINT, "If it may have joined after DHCP, check: " _YELLOW_("hw bwm wifi status"));
|
||||
return resp.status;
|
||||
}
|
||||
|
||||
@@ -1713,31 +1722,34 @@ static int CmdBWMWifi(const char *Cmd) {
|
||||
}
|
||||
|
||||
static int CmdBwmCharge(const char *Cmd) {
|
||||
CLIParserContext *ctx;
|
||||
CLIParserInit(&ctx, "hw bwmcharge",
|
||||
"Enable or disable BWM battery charging by clearing/setting the\n"
|
||||
"AW32001E charge-enable bit (CEB, REG01[3]). PM5 only.\n"
|
||||
_RED_("One-shot:") " the charger watchdog reverts this after ~160 s unless\n"
|
||||
"serviced, so charging may stop on its own. Use to nudge a top-up.",
|
||||
"hw bwmcharge -on --> enable charging\n"
|
||||
"hw bwmcharge --off --> disable charging");
|
||||
// Positional sub-action (no dashes): hw bwm charge on | off
|
||||
char verb[16] = {0};
|
||||
sscanf(Cmd, "%15s", verb);
|
||||
bool on = (strcmp(verb, "on") == 0);
|
||||
bool off = (strcmp(verb, "off") == 0);
|
||||
|
||||
void *argtable[] = {
|
||||
arg_param_begin,
|
||||
arg_lit0(NULL, "on", "enable charging (default)"),
|
||||
arg_lit0(NULL, "off", "disable charging"),
|
||||
arg_param_end
|
||||
};
|
||||
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
||||
bool on = arg_get_lit(ctx, 1);
|
||||
bool off = arg_get_lit(ctx, 2);
|
||||
CLIParserFree(ctx);
|
||||
|
||||
if (on && off) {
|
||||
PrintAndLogEx(WARNING, "pick one of --on / --off");
|
||||
if (!on && !off) {
|
||||
// Not a recognised sub-action: render help (also serves -h / empty),
|
||||
// or error on a stray token, then stop.
|
||||
CLIParserContext *ctx;
|
||||
CLIParserInit(&ctx, "hw bwm charge",
|
||||
"Enable or disable BWM battery charging by clearing/setting the\n"
|
||||
"AW32001E charge-enable bit (CEB, REG01[3]). PM5 only.\n"
|
||||
_RED_("One-shot:") " the charger watchdog reverts this after ~160 s unless\n"
|
||||
"serviced, so charging may stop on its own. Use to nudge a top-up.",
|
||||
"hw bwm charge off --> disable charging\n"
|
||||
"hw bwm charge on --> enable charging");
|
||||
void *argtable[] = {
|
||||
arg_param_begin,
|
||||
arg_param_end
|
||||
};
|
||||
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
||||
CLIParserFree(ctx);
|
||||
PrintAndLogEx(WARNING, "specify " _YELLOW_("on") " or " _YELLOW_("off"));
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
uint8_t payload = off ? 0 : 1; // default (neither flag) = enable
|
||||
|
||||
uint8_t payload = off ? 0 : 1; // on -> 1 (enable), off -> 0 (disable)
|
||||
PrintAndLogEx(INFO, "%s BWM battery charging...", off ? "Disabling" : "Enabling");
|
||||
|
||||
clearCommandBuffer();
|
||||
@@ -1761,14 +1773,14 @@ static int CmdBwmCharge(const char *Cmd) {
|
||||
|
||||
static int CmdBwmVchg(const char *Cmd) {
|
||||
CLIParserContext *ctx;
|
||||
CLIParserInit(&ctx, "hw bwmvchg",
|
||||
CLIParserInit(&ctx, "hw bwm vchg",
|
||||
"Set the BWM charger (AW32001E) charge-voltage regulation target.\n"
|
||||
"Lowering it below 4.2 V reduces top-of-charge stress and extends cell\n"
|
||||
"life. Snaps to the nearest 15 mV step; clamped to 3600..4200 mV. This is\n"
|
||||
"a runtime register write (reverts on the charger watchdog / POR); the\n"
|
||||
"firmware re-applies the " _YELLOW_("4100 mV") " default at every boot. PM5 only.",
|
||||
"hw bwmvchg --> set charge voltage to default 4100 mV (->4.095 V)\n"
|
||||
"hw bwmvchg --mv 4200 --> set charge voltage to 4200 mV");
|
||||
"hw bwm vchg --> set charge voltage to default 4100 mV (->4.095 V)\n"
|
||||
"hw bwm vchg --mv 4200 --> set charge voltage to 4200 mV");
|
||||
|
||||
void *argtable[] = {
|
||||
arg_param_begin,
|
||||
@@ -1805,13 +1817,13 @@ static int CmdBwmVchg(const char *Cmd) {
|
||||
|
||||
static int CmdBwmSetCap(const char *Cmd) {
|
||||
CLIParserContext *ctx;
|
||||
CLIParserInit(&ctx, "hw bwmsetcap",
|
||||
CLIParserInit(&ctx, "hw bwm setcap",
|
||||
"Program the BWM fuel gauge (BQ27427) Design Capacity for the fitted cell.\n"
|
||||
"Run ONCE after fitting or replacing the battery. This triggers a gauge\n"
|
||||
"config-update; do not run it repeatedly, as that disrupts the Impedance\n"
|
||||
"Track learning cycle. PM5 only.",
|
||||
"hw bwmsetcap --> set design capacity to default 500 mAh\n"
|
||||
"hw bwmsetcap --cap 500 --> set design capacity to 500 mAh");
|
||||
"hw bwm setcap --> set design capacity to default 500 mAh\n"
|
||||
"hw bwm setcap --cap 500 --> set design capacity to 500 mAh");
|
||||
|
||||
void *argtable[] = {
|
||||
arg_param_begin,
|
||||
@@ -1932,7 +1944,7 @@ static int CmdPing(const char *Cmd) {
|
||||
|
||||
if (len > PM3_CMD_DATA_SIZE)
|
||||
len = PM3_CMD_DATA_SIZE;
|
||||
|
||||
|
||||
if (len) {
|
||||
PrintAndLogEx(INFO, "Ping sent with payload len... " _YELLOW_("%d"), len);
|
||||
} else {
|
||||
@@ -2294,6 +2306,278 @@ static int CmdPM5QCTest(const char *Cmd) {
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
static void progressbar(long sent, long total, int style) {
|
||||
int percent = (int)((double)sent / total * 100);
|
||||
|
||||
// Use \r at the start to move the cursor back to the beginning of the line
|
||||
printf("\rProgress: [%d%%]", percent);
|
||||
|
||||
// Force stdout to print immediately without waiting for a newline
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
// One full OTA attempt: BEGIN -> WRITE... -> END. The BWM OTA has no resume
|
||||
// (DEV.md 8.4): a dropped chunk can't be re-sent, so any failure here means the
|
||||
// caller must restart the whole thing.
|
||||
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. 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, 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);
|
||||
|
||||
// WRITE chunks. Bounded by BWM_OTA_CHUNK_MAX (the ESP forwards each WRITE over
|
||||
// its own small app_com UART frame - see bwm_wifi.c), not just the USB frame.
|
||||
size_t maxchunk = MIN((size_t)g_conn.max_cmd_data_size - 1, (size_t)BWM_OTA_CHUNK_MAX);
|
||||
uint8_t *buf = calloc(1, maxchunk + 1);
|
||||
if (buf == NULL) {
|
||||
return PM3_EMALLOC;
|
||||
}
|
||||
size_t sent = 0;
|
||||
while (sent < fwlen) {
|
||||
size_t n = MIN(maxchunk, fwlen - sent);
|
||||
buf[0] = BWM_OTA_ACTION_WRITE;
|
||||
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, 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);
|
||||
}
|
||||
free(buf);
|
||||
return PM3_EFAILED;
|
||||
}
|
||||
sent += n;
|
||||
progressbar(sent, fwlen, STYLE_MIXED);
|
||||
|
||||
// Pace the stream. The client->AT32 hop (USB/BLE) is far faster than the
|
||||
// AT32->ESP UART, so back-to-back writes can outrun the UART and drop a
|
||||
// chunk -> esp_ota_end() then sees written < total and aborts. A small
|
||||
// gap gives the UART time to drain. (BLE is naturally paced, which is why
|
||||
// it "worked" and bursty USB did not - see nemanjan00.)
|
||||
if (write_delay_ms) {
|
||||
msleep(write_delay_ms);
|
||||
}
|
||||
}
|
||||
free(buf);
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
|
||||
// END: finalize + set the new boot partition
|
||||
uint8_t end[1] = { BWM_OTA_ACTION_END };
|
||||
clearCommandBuffer();
|
||||
SendCommandNG(CMD_PM5_BWM_ESP_OTA, end, sizeof(end));
|
||||
bool got_end = WaitForResponseTimeout(CMD_PM5_BWM_ESP_OTA, &resp, 30000);
|
||||
if (got_end && (resp.status == PM3_SUCCESS)) {
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
if (got_end) {
|
||||
// The BWM answered END with an error. The common one is a size mismatch:
|
||||
// esp_ota_end() found written < total, i.e. chunks were dropped in transit.
|
||||
// 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 <fw> --delay 20"));
|
||||
return PM3_EFAILED;
|
||||
}
|
||||
// No answer at all. Over BLE the END auto-reboot drops the link before the ack
|
||||
// returns, so a timeout here means "reached END, reboot likely happened" -
|
||||
// verify by version rather than discarding a possibly-good flash.
|
||||
return PM3_ETIMEOUT;
|
||||
}
|
||||
|
||||
// Query the BWM's running firmware version string (APP_CMD_GET_VERSION_INFO).
|
||||
static int bwm_get_version(char *out, size_t outlen) {
|
||||
uint8_t a[1] = { BWM_OTA_ACTION_VERSION };
|
||||
clearCommandBuffer();
|
||||
SendCommandNG(CMD_PM5_BWM_ESP_OTA, a, sizeof(a));
|
||||
PacketResponseNG r;
|
||||
if ((WaitForResponseTimeout(CMD_PM5_BWM_ESP_OTA, &r, 5000) == false) || (r.status != PM3_SUCCESS)) {
|
||||
return PM3_EFAILED;
|
||||
}
|
||||
uint16_t n = (r.length < (uint16_t)(outlen - 1)) ? r.length : (uint16_t)(outlen - 1);
|
||||
memcpy(out, r.data.asBytes, n);
|
||||
out[n] = 0;
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
static int CmdBWMUpgrade(const char *Cmd) {
|
||||
CLIParserContext *ctx;
|
||||
CLIParserInit(&ctx, "hw bwm upgrade",
|
||||
"Reflash the BWM (ESP32) firmware over the BWM link - no header, no soldering.\n"
|
||||
"Requires a BWM that still responds; this updates a wrong-version ESP, it cannot\n"
|
||||
"recover a fully bricked one (that still needs the 5-pin header + esptool).",
|
||||
"hw bwm upgrade -f bwm_esp32.bin");
|
||||
void *argtable[] = {
|
||||
arg_param_begin,
|
||||
arg_str1("f", "file", "<fn>", "ESP32 firmware image (.bin)"),
|
||||
arg_int0(NULL, "delay", "<ms>", "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, 20);
|
||||
CLIParserFree(ctx);
|
||||
|
||||
if (fnlen == 0) {
|
||||
PrintAndLogEx(FAILED, "no filename given");
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
|
||||
uint8_t *fw = NULL;
|
||||
size_t fwlen = 0;
|
||||
if ((loadFile_safe(fn, "", (void **)&fw, &fwlen) != PM3_SUCCESS) || (fwlen == 0)) {
|
||||
PrintAndLogEx(FAILED, "could not read " _YELLOW_("%s"), fn);
|
||||
return PM3_EFILE;
|
||||
}
|
||||
|
||||
// Safeguard: refuse to flash anything that is not an ESP32-C2 app image. The
|
||||
// BWM ESP is an ESP32-C2; a wrong/other-chip image would brick it.
|
||||
// [0x00] == 0xE9 -> ESP image magic
|
||||
// [0x0C..0x0D] == 0x000C -> chip_id ESP32-C2 (LE uint16)
|
||||
if (fwlen < 16) {
|
||||
PrintAndLogEx(FAILED, "file is too small to be an ESP firmware image (%zu bytes)", fwlen);
|
||||
free(fw);
|
||||
return PM3_EFILE;
|
||||
}
|
||||
if (fw[0] != 0xE9) {
|
||||
PrintAndLogEx(FAILED, "refusing to flash: not an ESP image (magic " _YELLOW_("0x%02X") ", expected 0xE9)", fw[0]);
|
||||
free(fw);
|
||||
return PM3_EFILE;
|
||||
}
|
||||
uint16_t chip_id = (uint16_t)(fw[0x0C] | (fw[0x0D] << 8));
|
||||
if (chip_id != 0x000C) {
|
||||
PrintAndLogEx(FAILED, "refusing to flash: image chip_id " _YELLOW_("0x%04X") " is not ESP32-C2 (0x000C)", chip_id);
|
||||
free(fw);
|
||||
return PM3_EFILE;
|
||||
}
|
||||
|
||||
// Record the running version first, so we can confirm the update actually took
|
||||
// even when the finalize ack is lost (the case that used to discard a completed
|
||||
// flash and restart from scratch).
|
||||
char ver_before[64] = {0};
|
||||
bool have_before = (bwm_get_version(ver_before, sizeof(ver_before)) == PM3_SUCCESS);
|
||||
if (have_before) {
|
||||
PrintAndLogEx(INFO, "Current BWM firmware..... " _YELLOW_("%s"), ver_before);
|
||||
}
|
||||
|
||||
// No resume (DEV.md 8.4): a chunk lost mid-transfer restarts the whole upload.
|
||||
const int max_attempts = 6; // a single dropped chunk restarts the whole upload;
|
||||
// more attempts make an all-fail run rare until per-chunk
|
||||
// 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);
|
||||
|
||||
// Failed during BEGIN/WRITE: image incomplete, restart the whole thing.
|
||||
if ((res != PM3_SUCCESS) && (res != PM3_ETIMEOUT)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Reached OTA_END (acked, or ack lost). The image is written and the boot
|
||||
// partition is set - reboot into it and confirm by version.
|
||||
if (res == PM3_ETIMEOUT) {
|
||||
PrintAndLogEx(INFO, "finalize ack not seen - all data was sent, confirming by version...");
|
||||
}
|
||||
// The device's OTA_END handler already reboots the ESP into the new image
|
||||
// (that is what drops the finalize ack over BLE). Just wait for it to come
|
||||
// back and re-link, then confirm by version.
|
||||
PrintAndLogEx(INFO, "BWM rebooting into the new image (link drops briefly)...");
|
||||
msleep(8000); // reboot + re-negotiate baud + re-link
|
||||
|
||||
char ver_after[64] = {0};
|
||||
bool have_after = (bwm_get_version(ver_after, sizeof(ver_after)) == PM3_SUCCESS);
|
||||
|
||||
if (have_after && have_before) {
|
||||
if (strncmp(ver_before, ver_after, sizeof(ver_before)) != 0) {
|
||||
PrintAndLogEx(SUCCESS, "BWM firmware updated: %s -> " _YELLOW_("%s"), ver_before, ver_after);
|
||||
free(fw);
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
PrintAndLogEx(WARNING, "BWM still reports " _YELLOW_("%s") " - update did not take, retrying", ver_after);
|
||||
continue;
|
||||
}
|
||||
if (have_after) {
|
||||
PrintAndLogEx(SUCCESS, "BWM now running " _YELLOW_("%s"), ver_after);
|
||||
free(fw);
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
// Could not re-read the version (link dropped on reboot, common over BLE).
|
||||
// All data was uploaded, so treat as done and let the user confirm.
|
||||
PrintAndLogEx(WARNING, "Could not re-read BWM version after reboot (link dropped?)");
|
||||
PrintAndLogEx(HINT, "Reconnect and run " _YELLOW_("hw status") " to confirm the version.");
|
||||
free(fw);
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
free(fw);
|
||||
|
||||
// Exhausted retries without a confirmed update - restore the link and report.
|
||||
PacketResponseNG resp;
|
||||
uint8_t ab[1] = { BWM_OTA_ACTION_ABORT };
|
||||
clearCommandBuffer();
|
||||
SendCommandNG(CMD_PM5_BWM_ESP_OTA, ab, sizeof(ab));
|
||||
(void)WaitForResponseTimeout(CMD_PM5_BWM_ESP_OTA, &resp, 8000);
|
||||
PrintAndLogEx(FAILED, "BWM firmware update could not be confirmed after %d attempts", max_attempts);
|
||||
return PM3_EFAILED;
|
||||
}
|
||||
static int CmdHelpBwm(const char *Cmd);
|
||||
|
||||
static command_t BwmCommandTable[] = {
|
||||
{"help", CmdHelpBwm, AlwaysAvailable, "This help"},
|
||||
{"autooff", CmdBwmAutoOff, IfPm5, "Toggle auto power-off on USB unplug"},
|
||||
{"charge", CmdBwmCharge, IfPm5, "Enable/disable battery charging (one-shot)"},
|
||||
{"setcap", CmdBwmSetCap, IfPm5, "Set fuel-gauge design capacity (run once after battery change)"},
|
||||
{"upgrade", CmdBWMUpgrade, IfPm5, "Reflash BWM (ESP32) firmware over the BWM link, no header"},
|
||||
{"vchg", CmdBwmVchg, IfPm5, "Set charger charge-voltage target (default 4100 mV)"},
|
||||
{"wifi", CmdBWMWifi, IfPm5, "Bring up WiFi (STA + TCP server) for a tcp: connection"},
|
||||
{NULL, NULL, NULL, NULL}
|
||||
};
|
||||
|
||||
static int CmdHelpBwm(const char *Cmd) {
|
||||
(void)Cmd;
|
||||
CmdsHelp(BwmCommandTable);
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
static int CmdBwm(const char *Cmd) {
|
||||
clearCommandBuffer();
|
||||
return CmdsParse(BwmCommandTable, Cmd);
|
||||
}
|
||||
|
||||
static command_t CommandTable[] = {
|
||||
{"help", CmdHelp, AlwaysAvailable, "This help"},
|
||||
{"-------------", CmdHelp, AlwaysAvailable, "----------------------- " _CYAN_("Operation") " -----------------------"},
|
||||
@@ -2322,11 +2606,7 @@ static command_t CommandTable[] = {
|
||||
{"setmux", CmdSetMux, IfPm3Present, "Set the ADC mux to a specific value"},
|
||||
{"standalone", CmdStandalone, IfPm3Present, "Start installed standalone mode on device"},
|
||||
{"tia", CmdTia, IfPm3Present, "Trigger a Timing Interval Acquisition to re-adjust the RealTimeCounter divider"},
|
||||
{"bwmsetcap", CmdBwmSetCap, IfPm5, "Set BWM fuel-gauge design capacity (PM5, run once after battery change)"},
|
||||
{"bwmvchg", CmdBwmVchg, IfPm5, "Set BWM charger charge-voltage target (PM5, default 4100 mV)"},
|
||||
{"bwmcharge", CmdBwmCharge, IfPm5, "Enable/disable BWM battery charging (PM5, one-shot)"},
|
||||
{"bwmautooff", CmdBwmAutoOff, IfPm5, "Toggle auto power-off on USB unplug (PM5, BWM)"},
|
||||
{"bwmwifi", CmdBWMWifi, IfPm5, "Bring up BWM WiFi (STA + TCP server) for a tcp: connection (PM5)"},
|
||||
{"bwm", CmdBwm, IfPm5, "{ BWM (battery/wireless module) commands... }"},
|
||||
{"tune", CmdTune, IfPm3Lf, "Measure tuning of device antenna"},
|
||||
{"decay", CmdDecay, IfPm3Present, "Measure HF antenna decay after field-off"},
|
||||
{NULL, NULL, NULL, NULL}
|
||||
|
||||
@@ -10,6 +10,7 @@ is compiled in with the BWM platform extra.
|
||||
- [1. Battery / BWM control](#1-battery--bwm-control)
|
||||
- [2. BLE](#2-ble)
|
||||
- [3. WiFi](#3-wifi)
|
||||
- [4. Update BWM firmware](#4-update-bwm-firmware)
|
||||
- [Notes](#notes)
|
||||
|
||||
---
|
||||
@@ -53,7 +54,7 @@ charge state, input limit, charge current/voltage/enable) and the **BQ27427** fu
|
||||
gauge (state-of-charge %, voltage, current, remaining/full capacity, temperature,
|
||||
health).
|
||||
|
||||
### `hw bwmsetcap` — set fuel-gauge design capacity
|
||||
### `hw bwm setcap` — set fuel-gauge design capacity
|
||||
|
||||
Program the BQ27427 **Design Capacity** for the fitted cell.
|
||||
|
||||
@@ -62,32 +63,33 @@ Program the BQ27427 **Design Capacity** for the fitted cell.
|
||||
| `--cap <mAh>` | Design capacity in mAh (default `500`) |
|
||||
|
||||
```
|
||||
hw bwmsetcap # set design capacity to the default 500 mAh
|
||||
hw bwmsetcap --cap 500 # set design capacity to 500 mAh
|
||||
hw bwm setcap # set design capacity to the default 500 mAh
|
||||
hw bwm setcap --cap 500 # set design capacity to 500 mAh
|
||||
```
|
||||
|
||||
> [!WARNING]
|
||||
> Run this **once** after fitting or replacing the battery. Running it repeatedly
|
||||
> triggers a gauge config-update and disrupts the Impedance Track learning cycle.
|
||||
|
||||
### `hw bwmcharge` — enable/disable charging (one-shot)
|
||||
### `hw bwm charge` — enable/disable charging (one-shot)
|
||||
|
||||
Clears/sets the AW32001E charge-enable bit (`CEB`, `REG01[3]`).
|
||||
|
||||
| Argument | Description |
|
||||
| -------- | ------------------------------------ |
|
||||
| `--off` | Disable charging (default is enable) |
|
||||
| Sub-action | Description |
|
||||
| ---------- | ----------------- |
|
||||
| `on` | Enable charging |
|
||||
| `off` | Disable charging |
|
||||
|
||||
```
|
||||
hw bwmcharge # enable charging
|
||||
hw bwmcharge --off # disable charging
|
||||
hw bwm charge on # enable charging
|
||||
hw bwm charge off # disable charging
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> **One-shot:** the charger watchdog reverts this after ~160 s unless serviced, so
|
||||
> charging may stop on its own. Use it to nudge a top-up.
|
||||
|
||||
### `hw bwmvchg` — set charge-voltage limit
|
||||
### `hw bwm vchg` — set charge-voltage limit
|
||||
|
||||
Sets the AW32001E charge-voltage regulation target (`VBAT_REG`, `REG04[7:2]`).
|
||||
Lowering it below 4.2 V reduces top-of-charge stress and extends cell life.
|
||||
@@ -97,8 +99,8 @@ Lowering it below 4.2 V reduces top-of-charge stress and extends cell life.
|
||||
| `--mv <mV>` | Charge voltage in mV (default `4100`, clamped `3600`..`4200`) |
|
||||
|
||||
```
|
||||
hw bwmvchg # set the default 4100 mV target (-> 4.095 V)
|
||||
hw bwmvchg --mv 4200 # set 4200 mV
|
||||
hw bwm vchg # set the default 4100 mV target (-> 4.095 V)
|
||||
hw bwm vchg --mv 4200 # set 4200 mV
|
||||
```
|
||||
|
||||
The value snaps to the nearest 15 mV hardware step, and the command reports the
|
||||
@@ -111,20 +113,20 @@ voltage actually applied (e.g. `4100` → `4.095 V`; `4110` would be the next st
|
||||
> the next power cycle (which re-applies it), so you don't need to re-run it per
|
||||
> charge.
|
||||
|
||||
### `hw bwmautooff` — auto power-off on USB unplug
|
||||
### `hw bwm autooff` — auto power-off on USB unplug
|
||||
|
||||
Toggles automatic power-off when the PM5 is unplugged from USB. **Default is `on`:**
|
||||
the board powers down after USB is removed so a BWM-equipped PM5 doesn't silently
|
||||
drain the battery. Button power-on is unaffected.
|
||||
|
||||
| Argument | Description |
|
||||
| -------- | ------------------------------- |
|
||||
| `--on` | Enable auto power-off (default) |
|
||||
| `--off` | Disable auto power-off |
|
||||
| Sub-action | Description |
|
||||
| ---------- | ----------------------- |
|
||||
| `on` | Enable auto power-off |
|
||||
| `off` | Disable auto power-off |
|
||||
|
||||
```
|
||||
hw bwmautooff --off # disable auto power-off
|
||||
hw bwmautooff --on # re-enable auto power-off
|
||||
hw bwm autooff off # disable auto power-off
|
||||
hw bwm autooff on # re-enable auto power-off
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
@@ -278,15 +280,15 @@ Bluetooth, so one setup covers a Blueshark-equipped Proxmark3 too.
|
||||
|
||||
## 3. WiFi (STA + TCP server)
|
||||
|
||||
`hw bwmwifi` joins your WiFi network as a **station** and starts a **TCP server** on
|
||||
the BWM. You then connect the client over plain TCP. Run `hw bwmwifi` over an
|
||||
existing link (USB, or an existing BLE connection). Use `--status` to check an
|
||||
`hw bwm wifi` joins your WiFi network as a **station** and starts a **TCP server** on
|
||||
the BWM. You then connect the client over plain TCP. Run `hw bwm wifi` over an
|
||||
existing link (USB, or an existing BLE connection). Use `hw bwm wifi status` to check an
|
||||
existing connection without touching the config.
|
||||
|
||||
### 3.1 Bring up WiFi
|
||||
|
||||
```
|
||||
hw bwmwifi --ssid <ssid> [--pwd <password>] [--port <n>] [--hostname <name>]
|
||||
hw bwm wifi --ssid <ssid> [--pwd <password>] [--port <n>] [--hostname <name>]
|
||||
```
|
||||
|
||||
| Argument | Description |
|
||||
@@ -295,13 +297,14 @@ hw bwmwifi --ssid <ssid> [--pwd <password>] [--port <n>] [--hostname <name>]
|
||||
| `--pwd <password>` | WiFi password (omit for an open network) |
|
||||
| `--port <dec>` | TCP server listen port (default `7777`) |
|
||||
| `--hostname <name>` | DHCP hostname (default `Proxmark5`) |
|
||||
| `--status` | Show current WiFi connection state + IP (no reconnect) |
|
||||
| `--stop` | Tear down WiFi and return to BLE-only |
|
||||
|
||||
Two sub-actions take no other arguments and are positional (no dashes):
|
||||
`hw bwm wifi status` and `hw bwm wifi stop` (see 3.3 and 3.4).
|
||||
|
||||
```
|
||||
hw bwmwifi --ssid Home --pwd secret
|
||||
hw bwmwifi --ssid Home --pwd secret --port 9000 --hostname pm5-lab
|
||||
hw bwmwifi --ssid OpenGuestWiFi # open network
|
||||
hw bwm wifi --ssid Home --pwd secret
|
||||
hw bwm wifi --ssid Home --pwd secret --port 9000 --hostname pm5-lab
|
||||
hw bwm wifi --ssid OpenGuestWiFi # open network
|
||||
```
|
||||
|
||||
The join can take ~15 s. On success it prints the assigned IP and the connect
|
||||
@@ -325,7 +328,7 @@ strings:
|
||||
> don't do it at all). There is **no mDNS / `.local`** responder, so the IP is the
|
||||
> reliable path.
|
||||
|
||||
### 3.3 Check WiFi status — `hw bwmwifi --status`
|
||||
### 3.3 Check WiFi status — `hw bwm wifi status`
|
||||
|
||||
Queries the BWM's current connection state and IP **without** reconnecting or
|
||||
changing the WiFi config. Handy to confirm a join actually completed — the
|
||||
@@ -333,7 +336,7 @@ bring-up occasionally reports failure if the DHCP lease lands late, even though
|
||||
the STA connected a moment later.
|
||||
|
||||
```
|
||||
hw bwmwifi --status
|
||||
hw bwm wifi status
|
||||
```
|
||||
|
||||
```
|
||||
@@ -346,21 +349,63 @@ hw bwmwifi --status
|
||||
> not connected right after a bring-up, wait a few seconds and re-check — the
|
||||
> lease may still be in flight.
|
||||
|
||||
### 3.4 Stop WiFi — `hw bwmwifi --stop`
|
||||
### 3.4 Stop WiFi — `hw bwm wifi stop`
|
||||
|
||||
Tears down the WiFi station and TCP server and returns the BWM to **BLE-only**.
|
||||
Run it over an existing link (USB or BLE); it does not need any other arguments.
|
||||
|
||||
```
|
||||
hw bwmwifi --stop
|
||||
hw bwm wifi stop
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> If you issue `--stop` over the **WiFi** connection itself, that connection
|
||||
> If you issue `hw bwm wifi stop` over the **WiFi** connection itself, that connection
|
||||
> drops as the TCP server goes away — reconnect over USB or BLE afterwards.
|
||||
|
||||
---
|
||||
|
||||
## 4. Update BWM firmware — `hw bwm upgrade`
|
||||
|
||||
Reflashes the BWM (ESP32-C2) firmware **over the existing BWM link** — no 5-pin
|
||||
header, no soldering, no `esptool`. Run it over whatever link you already have
|
||||
(USB or BLE). It updates a BWM that still responds; it **cannot** recover a fully
|
||||
bricked one — that still needs the production 5-pin header + `esptool`.
|
||||
|
||||
```
|
||||
hw bwm upgrade -f <bwm_esp32.bin> [--delay <ms>]
|
||||
```
|
||||
|
||||
| Argument | Description |
|
||||
| -------------- | ------------------------------------------------------------- |
|
||||
| `-f`, `--file` | ESP32-C2 firmware image `.bin` **(required)** |
|
||||
| `--delay <ms>` | Per-chunk delay pacing the slow AT32-ESP UART (default `20`) |
|
||||
|
||||
The client refuses to flash anything that is not an ESP32-C2 app image (it checks
|
||||
the ESP image magic `0xE9` and `chip_id 0x000C`), so a wrong-chip image can't
|
||||
brick the module.
|
||||
|
||||
It prints the running version first, uploads the image, then reboots the ESP into
|
||||
the new image and confirms by reading the version back:
|
||||
|
||||
```
|
||||
[=] Current BWM firmware..... v1.2.3
|
||||
[=] Uploading 812345 bytes of ESP firmware over the BWM link...
|
||||
[=] BWM rebooting into the new image (link drops briefly)...
|
||||
[+] BWM firmware updated: v1.2.3 -> v1.3.0
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> There is **no resume**: a chunk dropped mid-transfer restarts the whole upload,
|
||||
> and the client retries automatically (up to 6 attempts). If uploads keep failing
|
||||
> on the slow link, raise the pacing, e.g. `--delay 40`.
|
||||
|
||||
> [!NOTE]
|
||||
> The link drops briefly while the ESP reboots into the new image. Over BLE the
|
||||
> post-reboot version read sometimes can't complete even though the flash
|
||||
> succeeded — reconnect and run `hw status` to confirm the running version.
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- Bulk transfers over BLE/WiFi (`lf read`, `mem dump`, trace download) are paced by
|
||||
|
||||
@@ -886,6 +886,15 @@ typedef struct {
|
||||
#define CMD_PM5_BWM_AUTOOFF 0x017B
|
||||
#define CMD_PM5_BWM_WIFI 0x017C
|
||||
#define CMD_PM5_BWM_SET_VCHG 0x017D
|
||||
#define CMD_PM5_BWM_ESP_OTA 0x017E
|
||||
#define BWM_OTA_ACTION_BEGIN 0x00
|
||||
#define BWM_OTA_ACTION_WRITE 0x01
|
||||
#define BWM_OTA_ACTION_END 0x02
|
||||
#define BWM_OTA_ACTION_ABORT 0x03
|
||||
#define BWM_OTA_ACTION_VERSION 0x04
|
||||
#define BWM_OTA_ACTION_REBOOT 0x05
|
||||
// Max firmware bytes per WRITE action.
|
||||
#define BWM_OTA_CHUNK_MAX 240
|
||||
// CMD_PM5_BWM_WIFI payload: [action:u8][port:u16 LE][ssid\0][pwd\0][hostname\0]
|
||||
#define BWM_WIFI_ACTION_START 0x00 // join AP + start TCP server
|
||||
#define BWM_WIFI_ACTION_STOP 0x01 // tear down, back to BLE-only
|
||||
|
||||
Reference in New Issue
Block a user