mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2026-10-06 09:57:47 +00:00
Merge pull request #3626 from Msprg/pm5-idle-lowpower
PM5: idle power save, BWM power/WiFi/BLE settings, idle auto power-off, byte-window flow control
This commit is contained in:
@@ -3,6 +3,11 @@ All notable changes to this project will be documented in this file.
|
||||
This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log...
|
||||
|
||||
## [unreleased][unreleased]
|
||||
- Added `hw bwm ble` - the BWM's BLE settings: `on`/`off` (persisted radio switch), `pairing` (require a 6-digit passkey, LE Secure Connections; the module ships open), `forget` bonded devices, `txpower`, `status` (@Msprg)
|
||||
- Changed `hw bwm autooff` - `--idle <sec>` adds an opt-in power-off after that long idle on battery (no command, button press or BLE/WiFi client), off by default; `--unplug off` makes an unplug only restart that idle clock instead of powering off at once; all stored on the BWM, `hw status` shows it (@Msprg)
|
||||
- Added `hw bwm wifipower` - turn the BWM WiFi fully off, or set its modem power-save type (none/min/max), persisted on the module (@Msprg)
|
||||
- Added `hw bwm powersave` - show/set the BWM (ESP32) power-save switch: DFS, light sleep and slow advertising after 30 s, or the stock always-on behaviour, persisted on the module (@Msprg)
|
||||
- Added `hw powersave` - PM5 low-power idle, on by default: between commands the core drops to 48 MHz with the PLL off, the FPGA clock stopped and the CPU halted (WFI); USB now runs crystal-less off HICK. Ported from the Fantasi firmware (@Msprg)
|
||||
- Fixed `lf t55xx dump -p` - reuses the password already confirmed by `lf t55xx detect` (@iceman1001)
|
||||
- Fixed `lf t55xx p1detect`, `lf search` - T5577 clones with a non-Atmel manufacturer byte now recognised (@iceman1001)
|
||||
- Added `PLATFORM_EXTRAS=CEP` on PM5 - wires up the Flipper Zero link on the Type-C extended port (handshake + NG frames over SPI, independent transport flag so it can run alongside BWM); RF-dependent functionality still needs the FPGA side, tracked in #3667 (@TomHarkness)
|
||||
|
||||
@@ -313,6 +313,7 @@ if (PM5)
|
||||
list(APPEND THUMBSRC startup_at32f435_437.s)
|
||||
list(APPEND THUMBSRC i2c.c)
|
||||
list(APPEND THUMBSRC buzzer.c) # generic mainboard buzzer (QC self-test + BWM low-batt)
|
||||
list(APPEND THUMBSRC pm5_power.c) # low-power idle (core clock scaling + WFI)
|
||||
list(APPEND THUMBSRC at32_unit_test.c) # TODO DXL: AT32单元测试代码
|
||||
endif ()
|
||||
|
||||
|
||||
@@ -144,6 +144,13 @@ else
|
||||
SRC_BUZZER =
|
||||
endif
|
||||
|
||||
# PM5 low-power idle (core clock scaling + WFI)
|
||||
ifeq ($(PLATFORM),PM5)
|
||||
SRC_PM5_POWER = pm5_power.c
|
||||
else
|
||||
SRC_PM5_POWER =
|
||||
endif
|
||||
|
||||
# PM5 antenna-RGB power/battery indicator
|
||||
ifneq (,$(findstring WITH_PM5_PWR_LED,$(APP_CFLAGS)))
|
||||
SRC_RGB_INDICATOR = rgb_indicator.c
|
||||
@@ -211,6 +218,7 @@ THUMBSRC = start.c \
|
||||
$(SRC_CEP) \
|
||||
$(SRC_BWM_CHARGER) \
|
||||
$(SRC_BUZZER) \
|
||||
$(SRC_PM5_POWER) \
|
||||
$(SRC_RGB_INDICATOR) \
|
||||
$(SRC_HITAG) \
|
||||
$(SRC_EM4x50) \
|
||||
|
||||
+369
-37
@@ -92,38 +92,95 @@
|
||||
#include "bwm_charger.h" // BWM charger / fuel-gauge + low-batt warning (WITH_BWM_*)
|
||||
#include "buzzer.h" // PM5 mainboard buzzer API
|
||||
#include "rgb_indicator.h" // PM5 antenna-RGB power/battery indicator (WITH_PM5_PWR_LED)
|
||||
#include "pm5_power.h" // PM5 power-save idle (clock scaling + WFI)
|
||||
|
||||
|
||||
#ifdef WITH_PM5_AUTOOFF
|
||||
// Automatic power-off on USB unplug. When the BWM keeps the PM5 alive on battery,
|
||||
// users leave it draining. This powers the board down after USB has been absent
|
||||
// continuously for a grace period, using the SAME latch release as the long-press
|
||||
// shutdown (Gpio_ARM_Power_ON_Low). Button power-ON is a hardware function and is
|
||||
// unaffected - once powered off there is no firmware running to interfere with it.
|
||||
//
|
||||
// Runtime toggle (default ON) via CMD_PM5_BWM_AUTOOFF; resets to default each boot.
|
||||
// Standalone / BLE-relay users who run unplugged on purpose can disable it.
|
||||
// Automatic power-off, through the same latch release as the long-press shutdown
|
||||
// (Gpio_ARM_Power_ON_Low); button power-ON is hardware and unaffected. Two
|
||||
// triggers: a USB unplug (g_autooff_unplug) and, opt-in, g_autooff_idle_ms idle
|
||||
// on battery. Idle resets on a command, a button press or a client coming/going.
|
||||
// The switch and both trigger settings are set over CMD_PM5_BWM_AUTOOFF, persisted
|
||||
// on the BWM and reloaded at boot; without a module the defaults hold: on, unplug
|
||||
// on, idle off.
|
||||
#ifndef PM5_AUTOOFF_POLL_MS
|
||||
#define PM5_AUTOOFF_POLL_MS 250 // how often to sample VUSB
|
||||
#define PM5_AUTOOFF_POLL_MS 250 // how often to sample VUSB and the link state
|
||||
#endif
|
||||
#ifndef PM5_AUTOOFF_IDLE_MS
|
||||
#define PM5_AUTOOFF_IDLE_MS 0 // e.g. (5 * 60 * 1000) for 5 min
|
||||
#endif
|
||||
|
||||
bool g_autooff_enabled = true; // default on; toggled by CMD_PM5_BWM_AUTOOFF
|
||||
bool g_autooff_enabled = true; // default on; CMD_PM5_BWM_AUTOOFF
|
||||
uint32_t g_autooff_idle_ms = PM5_AUTOOFF_IDLE_MS; // 0 = no idle power-off
|
||||
bool g_autooff_unplug = true; // power off when USB is pulled
|
||||
|
||||
static bool s_autooff_setup = false;
|
||||
static uint32_t s_autooff_activity_tick = 0; // last interaction, see above
|
||||
static bool s_autooff_usb_seen = false;
|
||||
static bool s_autooff_link = false;
|
||||
static bool s_autooff_persisted = false; // setting loaded from / saved to the module
|
||||
|
||||
// Auto power-off on USB unplug. CRITICAL: only powers off on a USB-present -> absent
|
||||
// TRANSITION - i.e. the board was running on USB and the cable was pulled. A board that
|
||||
// booted on battery (button press, no USB) must NOT auto-off, or it could never be used
|
||||
// unplugged at all (and the hw bwm autooff toggle would be unreachable, since setting it
|
||||
// needs a client/USB). So we require having seen USB present at least once this session
|
||||
// before an absent reading triggers shutdown.
|
||||
void pm5_autooff_touch(void) {
|
||||
s_autooff_activity_tick = GetTickCount();
|
||||
}
|
||||
|
||||
void pm5_autooff_get_status(bwm_autooff_status_t *st) {
|
||||
st->enabled = g_autooff_enabled ? 1 : 0;
|
||||
st->idle_s = g_autooff_idle_ms / 1000;
|
||||
st->idle_now_s = GetTickCountDelta(s_autooff_activity_tick) / 1000;
|
||||
st->link = s_autooff_link ? 1 : 0;
|
||||
st->ble_live = 0xFF;
|
||||
#ifdef WITH_BWM_FORWARD
|
||||
(void)bwm_esp_get_ble_state(&st->ble_live, 800);
|
||||
#endif
|
||||
st->usb = Gpio_VUSB_Read() ? 1 : 0;
|
||||
st->usb_seen = s_autooff_usb_seen ? 1 : 0;
|
||||
st->persisted = s_autooff_persisted ? 1 : 0;
|
||||
st->unplug = g_autooff_unplug ? 1 : 0;
|
||||
}
|
||||
|
||||
// The setting lives on the BWM host value slots: the PM5 has no store of its own.
|
||||
static void pm5_autooff_save(void) {
|
||||
#ifdef WITH_BWM_FORWARD
|
||||
// 1 s each: an NVS write is tens of ms, and three must still fit the client's wait.
|
||||
s_autooff_persisted = (bwm_esp_host_value_set(BWM_HOSTVAL_AUTOOFF_ENABLED, g_autooff_enabled ? 1 : 0, 1000) == PM3_SUCCESS) &&
|
||||
(bwm_esp_host_value_set(BWM_HOSTVAL_AUTOOFF_IDLE_S, g_autooff_idle_ms / 1000, 1000) == PM3_SUCCESS) &&
|
||||
(bwm_esp_host_value_set(BWM_HOSTVAL_AUTOOFF_UNPLUG, g_autooff_unplug ? 1 : 0, 1000) == PM3_SUCCESS);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void pm5_autooff_load(void) {
|
||||
#ifdef WITH_BWM_FORWARD
|
||||
uint32_t v = 0;
|
||||
int r = bwm_esp_host_value_get(BWM_HOSTVAL_AUTOOFF_ENABLED, &v, 300);
|
||||
if (r == PM3_SUCCESS) {
|
||||
g_autooff_enabled = (v != 0);
|
||||
}
|
||||
if (r == PM3_SUCCESS || r == PM3_ENODATA) {
|
||||
s_autooff_persisted = true; // the module keeps the setting, stored or still default
|
||||
if (bwm_esp_host_value_get(BWM_HOSTVAL_AUTOOFF_IDLE_S, &v, 300) == PM3_SUCCESS) {
|
||||
g_autooff_idle_ms = MIN(v, BWM_AUTOOFF_IDLE_MAX_S) * 1000;
|
||||
}
|
||||
if (bwm_esp_host_value_get(BWM_HOSTVAL_AUTOOFF_UNPLUG, &v, 300) == PM3_SUCCESS) {
|
||||
g_autooff_unplug = (v != 0);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static void pm5_power_off(void) {
|
||||
LEDsoff();
|
||||
Gpio_ARM_Power_ON_Low();
|
||||
while (1); // wait for hardware power-off (button press powers back on, in hardware)
|
||||
}
|
||||
|
||||
// CRITICAL: the unplug trigger fires only on a USB present -> absent TRANSITION.
|
||||
// A board that booted on battery must not go off at once, or it could never be
|
||||
// used unplugged (and the toggle would be unreachable): it gets the idle timer.
|
||||
static void bwm_autooff_check(void) {
|
||||
static uint32_t last_tick = 0;
|
||||
static bool usb_was_present = false; // have we seen USB present since boot?
|
||||
static bool usb_prev = false; // VUSB at the previous poll
|
||||
|
||||
if (g_autooff_enabled == false) {
|
||||
return;
|
||||
}
|
||||
if ((last_tick != 0) && (GetTickCountDelta(last_tick) < PM5_AUTOOFF_POLL_MS)) {
|
||||
return;
|
||||
}
|
||||
@@ -131,24 +188,94 @@ static void bwm_autooff_check(void) {
|
||||
|
||||
if (s_autooff_setup == false) {
|
||||
gpio_vusb_setup();
|
||||
pm5_autooff_load();
|
||||
#ifdef WITH_BWM_FORWARD
|
||||
// The ESP only broadcasts link changes: ask once for the state at boot
|
||||
// (a BLE client can outlive an AT32 reset). Silence = no ESP or old fw.
|
||||
uint8_t st = 0;
|
||||
if (bwm_esp_get_ble_state(&st, 300) == PM3_SUCCESS) {
|
||||
bwm_fwd_link_seed_ble(st == BWM_BLE_STATE_CONNECTED);
|
||||
}
|
||||
#endif
|
||||
pm5_autooff_touch(); // the boot itself counts
|
||||
s_autooff_setup = true;
|
||||
}
|
||||
if (g_autooff_enabled == false) {
|
||||
usb_prev = Gpio_VUSB_Read(); // keep following VUSB: a re-enable on battery is not an unplug
|
||||
return;
|
||||
}
|
||||
|
||||
// Gpio_VUSB_Read() == true means USB power present.
|
||||
bool link_up = false;
|
||||
#ifdef WITH_BWM_FORWARD
|
||||
link_up = bwm_fwd_link_connected();
|
||||
#endif
|
||||
if (link_up != s_autooff_link) {
|
||||
s_autooff_link = link_up;
|
||||
pm5_autooff_touch(); // a client came or went: the idle clock restarts
|
||||
}
|
||||
|
||||
// Gpio_VUSB_Read() == true means USB power present: never power off.
|
||||
if (Gpio_VUSB_Read()) {
|
||||
usb_was_present = true; // latch: USB has been present this session
|
||||
s_autooff_usb_seen = true; // latch: USB has been present this session
|
||||
usb_prev = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// USB absent. Only power off if USB had previously been present (a real unplug).
|
||||
// If it booted on battery and never saw USB, leave it running.
|
||||
if (usb_was_present == false) {
|
||||
return;
|
||||
// USB absent after having been present: a real unplug. Off at once, or with
|
||||
// the unplug trigger off, just restart the idle clock.
|
||||
if (usb_prev) {
|
||||
usb_prev = false;
|
||||
if (g_autooff_unplug) {
|
||||
pm5_power_off();
|
||||
}
|
||||
pm5_autooff_touch();
|
||||
}
|
||||
|
||||
LEDsoff();
|
||||
Gpio_ARM_Power_ON_Low();
|
||||
while (1); // wait for hardware power-off (button press powers back on, in hardware)
|
||||
// On battery: off only after g_autooff_idle_ms with no interaction and no client.
|
||||
if (g_autooff_idle_ms == 0) {
|
||||
return;
|
||||
}
|
||||
if (GetTickCountDelta(s_autooff_activity_tick) < g_autooff_idle_ms) {
|
||||
return;
|
||||
}
|
||||
if (link_up) {
|
||||
#ifdef WITH_BWM_FORWARD
|
||||
static uint32_t last_verify_tick = 0;
|
||||
// The timer would fire but a client is (still) tracked: the broadcast
|
||||
// that reported it leaving could have been lost, so ask the module,
|
||||
// at most every 10 s. Only BLE can be asked; a WiFi client is trusted.
|
||||
if ((last_verify_tick == 0) || (GetTickCountDelta(last_verify_tick) >= 10000)) {
|
||||
last_verify_tick = GetTickCount();
|
||||
uint8_t st = 0;
|
||||
if (bwm_esp_get_ble_state(&st, 300) == PM3_SUCCESS) {
|
||||
bwm_fwd_link_seed_ble(st == BWM_BLE_STATE_CONNECTED);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return; // (a corrected flag is picked up on the next pass)
|
||||
}
|
||||
#ifdef WITH_BWM_FORWARD
|
||||
// A client the module never reported (module firmware without LINK_STATE, or
|
||||
// a lost broadcast): ask once before going off; no answer means off.
|
||||
uint8_t ble = 0;
|
||||
if ((bwm_esp_get_ble_state(&ble, 300) == PM3_SUCCESS) && (ble == BWM_BLE_STATE_CONNECTED)) {
|
||||
bwm_fwd_link_seed_ble(true);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
pm5_power_off();
|
||||
}
|
||||
|
||||
static void pm5_autooff_print_status(void) {
|
||||
if (g_autooff_enabled == false) {
|
||||
Dbprintf(" Auto power-off...... " _YELLOW_("off"));
|
||||
} else if (g_autooff_idle_ms == 0) {
|
||||
Dbprintf(" Auto power-off...... " _GREEN_("on") " (%s)", g_autooff_unplug ? "USB unplug only" : "no trigger set");
|
||||
} else if (g_autooff_unplug) {
|
||||
Dbprintf(" Auto power-off...... " _GREEN_("on") " (USB unplug, or %u s idle on battery)", g_autooff_idle_ms / 1000);
|
||||
} else {
|
||||
Dbprintf(" Auto power-off...... " _GREEN_("on") " (%u s idle on battery, also after an unplug)", g_autooff_idle_ms / 1000);
|
||||
}
|
||||
}
|
||||
#endif // WITH_PM5_AUTOOFF
|
||||
|
||||
@@ -599,15 +726,53 @@ static void SendStatus(uint32_t wait) {
|
||||
{
|
||||
// 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).
|
||||
// Keep the wait short: the client gives hw status about 2 s in total. One
|
||||
// retry - a slow light-sleep wake eats the first frame but leaves the module up.
|
||||
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) {
|
||||
int vres = bwm_esp_get_version(bwm_ver, &bwm_ver_len, 800);
|
||||
if (vres == PM3_ETIMEOUT) {
|
||||
bwm_ver_len = sizeof(bwm_ver) - 1;
|
||||
vres = bwm_esp_get_version(bwm_ver, &bwm_ver_len, 800);
|
||||
}
|
||||
if (vres == PM3_SUCCESS) {
|
||||
bwm_ver[bwm_ver_len] = 0x00;
|
||||
Dbprintf(" BWM fw version...... " _YELLOW_("%s"), bwm_ver);
|
||||
// Only once the ESP answered; older fw lacks these, so keep the wait short.
|
||||
uint8_t ps = 0;
|
||||
if (bwm_esp_get_power_save(&ps, 300) == PM3_SUCCESS) {
|
||||
Dbprintf(" BWM power save...... " _YELLOW_("%s"), ps ? "on" : "off");
|
||||
}
|
||||
uint8_t bs = 0;
|
||||
if (bwm_esp_get_ble_state(&bs, 300) == PM3_SUCCESS) {
|
||||
Dbprintf(" BWM BLE............. " _YELLOW_("%s"), (bs == BWM_BLE_STATE_CONNECTED) ? "client connected" : (bs == 1) ? "advertising" : "off");
|
||||
}
|
||||
// Our cached copy of the ESP's LINK_STATE broadcasts (BLE or WiFi), which holds
|
||||
// off the idle power-off. Debug only: it should agree with the live line above.
|
||||
if (g_dbglevel >= DBG_DEBUG) {
|
||||
Dbprintf(" BWM tracked client.. " _YELLOW_("%s"), bwm_fwd_link_connected() ? "connected (BLE or WiFi)" : "none");
|
||||
}
|
||||
} else {
|
||||
Dbprintf(" BWM fw version...... " _YELLOW_("%s"), "unknown");
|
||||
}
|
||||
}
|
||||
if (g_dbglevel >= DBG_DEBUG) {
|
||||
// Flow-control diagnostics (since boot). Read before this reply is queued.
|
||||
bwm_fc_stats_t fc;
|
||||
bwm_fwd_fc_stats(&fc);
|
||||
Dbprintf(" BWM fwd frames...... " _YELLOW_("%u") " sent, " _YELLOW_("%u") " acked, " _YELLOW_("%u") " errored",
|
||||
fc.frames, fc.acks, fc.errors);
|
||||
Dbprintf(" BWM fwd gate........ " _YELLOW_("%u") " timeouts, " _YELLOW_("%u") " forgotten, peak in flight " _YELLOW_("%u") " B",
|
||||
fc.timeouts, fc.forgotten, fc.bytes_max);
|
||||
Dbprintf(" BWM fwd now......... " _YELLOW_("%u") " frames / " _YELLOW_("%u") " B in flight",
|
||||
fc.in_flight_frames, fc.in_flight_bytes);
|
||||
}
|
||||
#endif
|
||||
#ifdef PM5
|
||||
pm5_power_print_status();
|
||||
#endif
|
||||
#ifdef WITH_PM5_AUTOOFF
|
||||
pm5_autooff_print_status();
|
||||
#endif
|
||||
#ifdef WITH_CEP
|
||||
Dbprintf(" CEP (Flipper) link.. " _YELLOW_("%s"), cep_is_active() ? "attached" : "not attached");
|
||||
@@ -4216,7 +4381,7 @@ static void PacketReceived(PacketCommandNG *packet) {
|
||||
// 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);
|
||||
res = bwm_esp_get_version(ver, &vlen, 3000);
|
||||
reply_ng(CMD_PM5_BWM_ESP_OTA, res, ver, (res == PM3_SUCCESS) ? vlen : 0);
|
||||
replied = true;
|
||||
break;
|
||||
@@ -4300,22 +4465,164 @@ static void PacketReceived(PacketCommandNG *packet) {
|
||||
}
|
||||
#else
|
||||
reply_ng(CMD_PM5_BWM_BLE_NAME, PM3_ENOTIMPL, NULL, 0);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case CMD_PM5_BWM_POWERSAVE: {
|
||||
#ifdef WITH_BWM_FORWARD
|
||||
// Payload / reply layout: pm3_cmd.h. The ESP persists it; nothing kept here.
|
||||
if (packet->length < 1) {
|
||||
reply_ng(CMD_PM5_BWM_POWERSAVE, PM3_EINVARG, NULL, 0);
|
||||
break;
|
||||
}
|
||||
uint8_t action = packet->data.asBytes[0];
|
||||
uint8_t state = 0;
|
||||
int res;
|
||||
if (action == BWM_POWERSAVE_ACTION_GET) {
|
||||
res = bwm_esp_get_power_save(&state, 3000);
|
||||
} else if (action == BWM_POWERSAVE_ACTION_SET && packet->length >= 2) {
|
||||
res = bwm_esp_set_power_save(packet->data.asBytes[1] != 0, &state);
|
||||
} else {
|
||||
reply_ng(CMD_PM5_BWM_POWERSAVE, PM3_EINVARG, NULL, 0);
|
||||
break;
|
||||
}
|
||||
reply_ng(CMD_PM5_BWM_POWERSAVE, res, &state, (res == PM3_SUCCESS) ? 1 : 0);
|
||||
#else
|
||||
reply_ng(CMD_PM5_BWM_POWERSAVE, PM3_ENOTIMPL, NULL, 0);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case CMD_PM5_BWM_BLE: {
|
||||
#ifdef WITH_BWM_FORWARD
|
||||
// BLE settings on the module: one action, then a full status snapshot back.
|
||||
if (packet->length < 1) {
|
||||
reply_ng(CMD_PM5_BWM_BLE, PM3_EINVARG, NULL, 0);
|
||||
break;
|
||||
}
|
||||
uint8_t action = packet->data.asBytes[0];
|
||||
const uint8_t *arg = &packet->data.asBytes[1];
|
||||
uint16_t alen = packet->length - 1;
|
||||
int res = PM3_SUCCESS;
|
||||
bool restart = false; // a bonding change applies at the next stack start
|
||||
switch (action) {
|
||||
case BWM_BLE_ACTION_STATUS:
|
||||
break;
|
||||
case BWM_BLE_ACTION_ENABLE: {
|
||||
uint8_t stored = 0;
|
||||
res = (alen >= 1) ? bwm_esp_set_ble_enable(arg[0] != 0, &stored) : PM3_EINVARG;
|
||||
break;
|
||||
}
|
||||
case BWM_BLE_ACTION_PAIRING:
|
||||
res = (alen >= 1) ? bwm_esp_set_ble_bonding(arg[0] != 0) : PM3_EINVARG;
|
||||
restart = (res == PM3_SUCCESS);
|
||||
break;
|
||||
case BWM_BLE_ACTION_KEY:
|
||||
res = (alen >= 6) ? bwm_esp_set_ble_key(arg) : PM3_EINVARG;
|
||||
break;
|
||||
case BWM_BLE_ACTION_FORGET:
|
||||
res = (alen >= 1) ? bwm_esp_ble_forget(arg[0]) : PM3_EINVARG;
|
||||
break;
|
||||
case BWM_BLE_ACTION_TXPOWER:
|
||||
res = (alen >= 2) ? bwm_esp_set_ble_txpower(arg[0], arg[1]) : PM3_EINVARG;
|
||||
break;
|
||||
default:
|
||||
res = PM3_EINVARG;
|
||||
break;
|
||||
}
|
||||
if (res != PM3_SUCCESS) {
|
||||
reply_ng(CMD_PM5_BWM_BLE, res, NULL, 0);
|
||||
break;
|
||||
}
|
||||
bwm_ble_status_t st;
|
||||
res = bwm_esp_ble_status(&st);
|
||||
if (res != PM3_SUCCESS) {
|
||||
reply_ng(CMD_PM5_BWM_BLE, res, NULL, 0);
|
||||
if (restart) {
|
||||
(void)bwm_esp_ble_restart();
|
||||
}
|
||||
break;
|
||||
}
|
||||
reply_ng(CMD_PM5_BWM_BLE, PM3_SUCCESS, (uint8_t *)&st, sizeof(st));
|
||||
if (restart) {
|
||||
// Reply first: over BLE the restart drops the client's own link.
|
||||
SpinDelay(500);
|
||||
(void)bwm_esp_ble_restart();
|
||||
}
|
||||
#else
|
||||
reply_ng(CMD_PM5_BWM_BLE, PM3_ENOTIMPL, NULL, 0);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case CMD_PM5_BWM_WIFI_PS: {
|
||||
#ifdef WITH_BWM_FORWARD
|
||||
// Payload / reply layout: pm3_cmd.h. wifi_state rides along so the host
|
||||
// can say the type is moot while WiFi is down.
|
||||
if (packet->length < 1) {
|
||||
reply_ng(CMD_PM5_BWM_WIFI_PS, PM3_EINVARG, NULL, 0);
|
||||
break;
|
||||
}
|
||||
uint8_t action = packet->data.asBytes[0];
|
||||
uint8_t out[2] = { 0, BWM_WIFI_STATE_OFF };
|
||||
int res;
|
||||
if (action == BWM_WIFI_PS_ACTION_GET) {
|
||||
res = bwm_esp_get_wifi_ps(&out[0]);
|
||||
} else if (action == BWM_WIFI_PS_ACTION_SET && packet->length >= 2 && packet->data.asBytes[1] <= BWM_WIFI_PS_MAX) {
|
||||
res = bwm_esp_set_wifi_ps(packet->data.asBytes[1], &out[0]);
|
||||
} else {
|
||||
reply_ng(CMD_PM5_BWM_WIFI_PS, PM3_EINVARG, NULL, 0);
|
||||
break;
|
||||
}
|
||||
if (res == PM3_SUCCESS) {
|
||||
uint32_t ip = 0;
|
||||
(void)bwm_wifi_forward_status(&out[1], &ip); // 0xFF when the WiFi stack is down
|
||||
}
|
||||
reply_ng(CMD_PM5_BWM_WIFI_PS, res, out, (res == PM3_SUCCESS) ? sizeof(out) : 0);
|
||||
#else
|
||||
reply_ng(CMD_PM5_BWM_WIFI_PS, PM3_ENOTIMPL, NULL, 0);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case CMD_PM5_BWM_AUTOOFF: {
|
||||
// Toggle automatic power-off on USB unplug (runtime, default on).
|
||||
// Payload: 1 byte, non-zero = enable (default), zero = disable.
|
||||
// Automatic power-off switch + idle timeout. Layouts: pm3_cmd.h.
|
||||
#ifdef WITH_PM5_AUTOOFF
|
||||
g_autooff_enabled = (packet->length >= 1) ? (packet->data.asBytes[0] != 0) : true;
|
||||
reply_ng(CMD_PM5_BWM_AUTOOFF, PM3_SUCCESS, (uint8_t *)&g_autooff_enabled, 1);
|
||||
if (packet->length == 1) {
|
||||
// old client: enable flag only
|
||||
g_autooff_enabled = (packet->data.asBytes[0] != 0);
|
||||
pm5_autooff_touch();
|
||||
pm5_autooff_save();
|
||||
} else if ((packet->length >= 6) && (packet->data.asBytes[0] == BWM_AUTOOFF_ACTION_SET)) {
|
||||
uint8_t en = packet->data.asBytes[1];
|
||||
uint32_t idle_s;
|
||||
memcpy(&idle_s, &packet->data.asBytes[2], sizeof(idle_s));
|
||||
if (en != BWM_AUTOOFF_KEEP_U8) {
|
||||
g_autooff_enabled = (en != 0);
|
||||
}
|
||||
if (idle_s != BWM_AUTOOFF_KEEP_U32) {
|
||||
g_autooff_idle_ms = MIN(idle_s, BWM_AUTOOFF_IDLE_MAX_S) * 1000;
|
||||
}
|
||||
if ((packet->length >= 7) && (packet->data.asBytes[6] != BWM_AUTOOFF_KEEP_U8)) {
|
||||
g_autooff_unplug = (packet->data.asBytes[6] != 0);
|
||||
}
|
||||
pm5_autooff_touch(); // a new timeout counts from now
|
||||
pm5_autooff_save();
|
||||
}
|
||||
bwm_autooff_status_t reply;
|
||||
pm5_autooff_get_status(&reply);
|
||||
reply_ng(CMD_PM5_BWM_AUTOOFF, PM3_SUCCESS, (uint8_t *)&reply, sizeof(reply));
|
||||
#else
|
||||
reply_ng(CMD_PM5_BWM_AUTOOFF, PM3_ENOTIMPL, NULL, 0);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
#endif // WITH_BWM_STATUS
|
||||
case CMD_PM5_POWERSAVE: {
|
||||
// Payload: 1 byte, non-zero = enable (the default), zero = disable.
|
||||
pm5_power_set_enabled((packet->length >= 1) ? (packet->data.asBytes[0] != 0) : true);
|
||||
uint8_t state = pm5_power_get_enabled() ? 1 : 0;
|
||||
reply_ng(CMD_PM5_POWERSAVE, PM3_SUCCESS, &state, 1);
|
||||
break;
|
||||
}
|
||||
#endif // PM5
|
||||
default: {
|
||||
Dbprintf("%s: 0x%04x", "unknown command:", packet->cmd);
|
||||
break;
|
||||
@@ -4400,12 +4707,19 @@ void __attribute__((noreturn)) AppMain(void) {
|
||||
// Negotiate the link up from the boot baud; harmless no-op if the ESP is
|
||||
// older or the target is unreachable (stays at BWM_UART_BAUD).
|
||||
bwm_fwd_negotiate_baud(BWM_UART_BAUD_TARGET);
|
||||
// The ESP only light-sleeps once it has seen a preamble, and ours went out
|
||||
// while it was still booting if both powered up together: send it again.
|
||||
bwm_uart_wake_next();
|
||||
#endif
|
||||
|
||||
#ifdef WITH_BWM_CHARGERKICK
|
||||
bwm_charger_kick();
|
||||
#endif
|
||||
|
||||
#ifdef PM5
|
||||
pm5_power_init();
|
||||
#endif
|
||||
|
||||
#ifdef WITH_CEP
|
||||
cep_init(); // USART1/SPI1 + CC-controller I2C bring-up; AFTER usb_enable()
|
||||
#endif
|
||||
@@ -4440,7 +4754,16 @@ void __attribute__((noreturn)) AppMain(void) {
|
||||
|
||||
int ret = receive_ng(&rx);
|
||||
if (ret == PM3_SUCCESS) {
|
||||
#ifdef PM5
|
||||
pm5_power_boost(); // commands assume the full 288 MHz clock
|
||||
#endif
|
||||
PacketReceived(&rx);
|
||||
#ifdef PM5
|
||||
pm5_power_unboost();
|
||||
#endif
|
||||
#ifdef WITH_PM5_AUTOOFF
|
||||
pm5_autooff_touch(); // a command, over any transport
|
||||
#endif
|
||||
last_activity_label = GetTickCountLabel();
|
||||
last_activity_tick = GetTickCount();
|
||||
} else if (ret != PM3_ENODATA) {
|
||||
@@ -4461,6 +4784,11 @@ void __attribute__((noreturn)) AppMain(void) {
|
||||
|
||||
// Press button for one second to enter a possible standalone mode
|
||||
button_status = BUTTON_HELD(1000);
|
||||
#ifdef WITH_PM5_AUTOOFF
|
||||
if (button_status != BUTTON_NO_CLICK) {
|
||||
pm5_autooff_touch(); // any press is an interaction
|
||||
}
|
||||
#endif
|
||||
if (button_status == BUTTON_HOLD) {
|
||||
/*
|
||||
* So this is the trigger to execute a standalone mod. Generic entrypoint by following the standalone/standalone.h headerfile
|
||||
@@ -4529,5 +4857,9 @@ void __attribute__((noreturn)) AppMain(void) {
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef PM5
|
||||
pm5_power_idle();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
+16
-7
@@ -27,6 +27,7 @@
|
||||
#include "i2c.h"
|
||||
#include "dbprint.h"
|
||||
#include "ticks_apis.h" // WaitMS / GetTickCount / GetTickCountDelta
|
||||
#include "pm5_power.h" // pm5_power_boost / unboost around the low-batt poll
|
||||
#include "ansi.h"
|
||||
|
||||
#ifdef WITH_BWM_LOWBATT_BEEP
|
||||
@@ -470,18 +471,12 @@ static void bwm_beep_low_batt(void) {
|
||||
// charging or if the BWM/gauge is not present. With WITH_PM5_LOWBATT_SHUTDOWN it
|
||||
// also powers the board off once the pack drops to the critical floor, confirmed
|
||||
// across several consecutive polls so a transient HF-field sag can't trip it.
|
||||
void bwm_lowbatt_check(void) {
|
||||
static uint32_t last_tick = 0;
|
||||
static void bwm_lowbatt_poll(void) {
|
||||
#ifdef WITH_PM5_LOWBATT_SHUTDOWN
|
||||
static uint8_t crit = 0;
|
||||
static bool s_vusb_setup = false;
|
||||
#endif
|
||||
|
||||
if ((last_tick != 0) && (GetTickCountDelta(last_tick) < BWM_LOWBATT_PERIOD_MS)) {
|
||||
return;
|
||||
}
|
||||
last_tick = GetTickCount();
|
||||
|
||||
StartTicks();
|
||||
I2C_init(true);
|
||||
|
||||
@@ -539,4 +534,18 @@ void bwm_lowbatt_check(void) {
|
||||
|
||||
bwm_beep_low_batt(); // BuzzerBeep() lazily sets the buzzer up on first use
|
||||
}
|
||||
|
||||
void bwm_lowbatt_check(void) {
|
||||
static uint32_t last_tick = 0;
|
||||
|
||||
if ((last_tick != 0) && (GetTickCountDelta(last_tick) < BWM_LOWBATT_PERIOD_MS)) {
|
||||
return;
|
||||
}
|
||||
last_tick = GetTickCount();
|
||||
|
||||
// Ticks timer, I2C and buzzer timing assume the full core clock.
|
||||
pm5_power_boost();
|
||||
bwm_lowbatt_poll();
|
||||
pm5_power_unboost();
|
||||
}
|
||||
#endif // WITH_BWM_LOWBATT_BEEP
|
||||
|
||||
+84
-19
@@ -14,6 +14,7 @@
|
||||
#include "bwm_forward.h"
|
||||
|
||||
#include "bwm_uart_at32.h"
|
||||
#include "bwm_wifi.h" // BWM_CMD_CMD_ERROR
|
||||
#include "pm3_cmd.h" // PM3_CMD_DATA_SIZE, PM3_* return codes
|
||||
#include "ticks_apis.h" // SpinDelay
|
||||
#include "string.h"
|
||||
@@ -49,11 +50,51 @@ static uint16_t bwm_crc16(const uint8_t *data, size_t len, uint16_t crc) {
|
||||
|
||||
static void bwm_pump(void); // fwd decl: TX gate pumps RX to collect forward-frame acks
|
||||
|
||||
// --- Flow control (ack window) ---------------------------------------------
|
||||
// s_fwd_inflight: forward frames sent but not yet acked by the ESP. Bumped on
|
||||
// send, decremented when a SLAVE_RESP echoing cmd=SEND_FORWARD_DATA arrives.
|
||||
// We may send while it is below BWM_FC_WINDOW; at the cap we wait for an ack.
|
||||
static volatile int16_t s_fwd_inflight = 0;
|
||||
// --- Flow control (byte window) --------------------------------------------
|
||||
// Forward frames sent but not yet acked by the ESP: their lengths, oldest
|
||||
// first (acks arrive in order), and their sum. Bumped on every forward frame,
|
||||
// popped by the parser on the ESP's SLAVE_RESP. See BWM_FC_BYTES.
|
||||
static volatile uint16_t s_fc_len[BWM_FC_MAX_FRAMES];
|
||||
static volatile uint8_t s_fc_head = 0;
|
||||
static volatile uint8_t s_fc_count = 0;
|
||||
static volatile uint32_t s_fc_bytes = 0;
|
||||
static bwm_fc_stats_t s_fc_stats; // diagnostics since boot, see bwm_fwd_fc_stats()
|
||||
|
||||
static void fc_reset(void) {
|
||||
s_fc_head = 0;
|
||||
s_fc_count = 0;
|
||||
s_fc_bytes = 0;
|
||||
}
|
||||
|
||||
static void fc_push(uint16_t len) {
|
||||
s_fc_stats.frames++;
|
||||
s_fc_len[(uint8_t)((s_fc_head + s_fc_count) % BWM_FC_MAX_FRAMES)] = len;
|
||||
s_fc_count++;
|
||||
s_fc_bytes += len;
|
||||
if (s_fc_bytes > s_fc_stats.bytes_max) {
|
||||
s_fc_stats.bytes_max = s_fc_bytes;
|
||||
}
|
||||
}
|
||||
|
||||
// Release the oldest frame: its ack (or CMD_ERROR) came, or the gate gave up on
|
||||
// it. Acks are not matched to frames, so an ack that was eaten elsewhere (bwm_cmd()
|
||||
// drains the ring, see #3648) or one that comes after the gate forgot its frame
|
||||
// just shifts the window by one frame: the gate is one frame too pessimistic or
|
||||
// too optimistic until the window drains, and drains it does after every command.
|
||||
static void fc_pop(void) {
|
||||
if (s_fc_count == 0) {
|
||||
return;
|
||||
}
|
||||
s_fc_bytes -= s_fc_len[s_fc_head];
|
||||
s_fc_head = (uint8_t)((s_fc_head + 1) % BWM_FC_MAX_FRAMES);
|
||||
s_fc_count--;
|
||||
}
|
||||
|
||||
void bwm_fwd_fc_stats(bwm_fc_stats_t *out) {
|
||||
*out = s_fc_stats;
|
||||
out->in_flight_frames = s_fc_count;
|
||||
out->in_flight_bytes = s_fc_bytes;
|
||||
}
|
||||
|
||||
// Set true by the parser when a SLAVE_RESP echoing BWM_CMD_SET_UART_BAUD arrives
|
||||
// (the ESP's ack for a baud-set request). Consumed by bwm_fwd_negotiate_baud().
|
||||
@@ -63,6 +104,18 @@ static volatile bool s_baud_ack = false;
|
||||
// ESP is alive and responding at the baud we just switched to (verify step).
|
||||
static volatile bool s_getbaud_ack = false;
|
||||
|
||||
// Last LINK_STATE broadcast from the ESP: a client on BLE / on the TCP server.
|
||||
static volatile uint8_t s_link_ble = 0;
|
||||
static volatile uint8_t s_link_wifi = 0;
|
||||
|
||||
bool bwm_fwd_link_connected(void) {
|
||||
return (s_link_ble != 0) || (s_link_wifi != 0);
|
||||
}
|
||||
|
||||
void bwm_fwd_link_seed_ble(bool connected) {
|
||||
s_link_ble = connected ? 1 : 0;
|
||||
}
|
||||
|
||||
int bwm_fwd_writebuffer_sync(const uint8_t *data, size_t len) {
|
||||
static uint8_t frame[BWM_TX_BUFSZ]; // single-threaded bare-metal: static OK
|
||||
|
||||
@@ -71,18 +124,23 @@ int bwm_fwd_writebuffer_sync(const uint8_t *data, size_t len) {
|
||||
}
|
||||
|
||||
size_t idx = 0;
|
||||
// Flow control: block while the in-flight window is full, waiting for the
|
||||
// ESP to ack an earlier forward frame. bwm_pump() drains the IRQ-filled RX
|
||||
// ring, so acks are collected even while we sit inside a tight download loop
|
||||
// (the reply_old firehose). The spin cap is a safety valve so a dead or
|
||||
// disconnected ESP can't hard-hang us. A window >= 1 means single command
|
||||
// replies never block - only sustained bursts hit the cap.
|
||||
// Flow control: block while this frame would not fit the in-flight byte budget
|
||||
// (or the length FIFO is full). bwm_pump() drains the IRQ-filled RX ring, so
|
||||
// acks land even inside a tight download loop (the reply_old firehose). The
|
||||
// time cap is a safety valve so a dead or disconnected ESP can't hard-hang us.
|
||||
{
|
||||
uint32_t t0 = GetTickCount();
|
||||
while (s_fwd_inflight >= BWM_FC_WINDOW) {
|
||||
uint32_t need = (uint32_t)len + BWM_TX_OVERHEAD;
|
||||
while ((s_fc_count >= BWM_FC_MAX_FRAMES) || (s_fc_bytes + need > BWM_FC_BYTES)) {
|
||||
bwm_pump();
|
||||
if (GetTickCountDelta(t0) > BWM_FC_ACK_TIMEOUT_MS) {
|
||||
s_fwd_inflight = 0; // best-effort: assume the pipe cleared, never hard-hang
|
||||
s_fc_stats.timeouts++;
|
||||
// Never hard-hang: make room for this frame only, so a stalled ESP
|
||||
// gets one frame per timeout on top of its ring, not a second window.
|
||||
while (s_fc_count && ((s_fc_count >= BWM_FC_MAX_FRAMES) || (s_fc_bytes + need > BWM_FC_BYTES))) {
|
||||
fc_pop();
|
||||
s_fc_stats.forgotten++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -103,7 +161,7 @@ int bwm_fwd_writebuffer_sync(const uint8_t *data, size_t len) {
|
||||
frame[idx++] = (uint8_t)((crc >> 8) & 0xFF);
|
||||
|
||||
int wr = bwm_uart_write(frame, idx);
|
||||
s_fwd_inflight++; // one more forward frame awaiting its ack
|
||||
fc_push((uint16_t)idx); // one more forward frame awaiting its ack
|
||||
return wr;
|
||||
}
|
||||
|
||||
@@ -241,16 +299,23 @@ static void bwm_feed_byte(bwm_parser_t *p, uint8_t byte) {
|
||||
fifo_push(p->payload[i]);
|
||||
}
|
||||
} else if ((p->is_bcast == false) && p->cmd == BWM_CMD_SEND_FORWARD_DATA) {
|
||||
// SLAVE_RESP ack for a forward frame -> one slot freed
|
||||
if (s_fwd_inflight > 0) {
|
||||
s_fwd_inflight--;
|
||||
}
|
||||
// SLAVE_RESP ack for a forward frame -> its bytes leave the budget
|
||||
s_fc_stats.acks++;
|
||||
fc_pop();
|
||||
} else if (p->is_bcast && p->cmd == BWM_CMD_CMD_ERROR && p->len >= 2 &&
|
||||
(((uint16_t)p->payload[0] | ((uint16_t)p->payload[1] << 8)) == BWM_CMD_SEND_FORWARD_DATA)) {
|
||||
// the ESP could not deliver a forward frame: this is its answer instead of the ack
|
||||
s_fc_stats.errors++;
|
||||
fc_pop();
|
||||
} else if ((p->is_bcast == false) && p->cmd == BWM_CMD_SET_UART_BAUD) {
|
||||
// SLAVE_RESP ack for a baud-set request (see negotiate below)
|
||||
s_baud_ack = true;
|
||||
} else if ((p->is_bcast == false) && p->cmd == BWM_CMD_GET_UART_BAUD) {
|
||||
// SLAVE_RESP for our GET_BAUD verify probe
|
||||
s_getbaud_ack = true;
|
||||
} else if (p->is_bcast && p->cmd == BWM_CMD_LINK_STATE && p->len >= 2) {
|
||||
s_link_ble = p->payload[0];
|
||||
s_link_wifi = p->payload[1];
|
||||
}
|
||||
}
|
||||
// valid non-DATA_FORWARD frames and CRC failures alike: just resync
|
||||
@@ -391,7 +456,7 @@ static void bwm_link_reset(void) {
|
||||
s_p.state = S_IDLE;
|
||||
s_fifo_head = 0;
|
||||
s_fifo_tail = 0;
|
||||
s_fwd_inflight = 0;
|
||||
fc_reset();
|
||||
}
|
||||
|
||||
bool bwm_fwd_negotiate_baud(uint32_t target) {
|
||||
|
||||
+38
-16
@@ -47,32 +47,35 @@
|
||||
|
||||
#define BWM_CMD_SEND_FORWARD_DATA 5000 // host cmd: payload -> BLE/WiFi endpoint
|
||||
#define BWM_CMD_DATA_FORWARD 8089 // slave bcast: payload came from endpoint
|
||||
#define BWM_CMD_LINK_STATE 8092 // slave bcast: [ble u8][wifi u8], 1 = a client is connected
|
||||
// System command: set the ESP<->AT32 UART baud (app_com_defs.h, enum @1000).
|
||||
// SET is a HOST_CMD carrying u32 LE baud; the ESP replies with a SLAVE_RESP
|
||||
// echoing this cmd (len 0) at the OLD baud, then commits to the new baud.
|
||||
#define BWM_CMD_SET_UART_BAUD 1011
|
||||
#define BWM_CMD_GET_UART_BAUD 1009 // read back the ESP's live baud (negotiation verify)
|
||||
// Flow control (ack window) - ARM-side only, no BWM firmware change required.
|
||||
// The ESP already replies to every forward frame with a SLAVE_RESP echoing
|
||||
// cmd=SEND_FORWARD_DATA, and it sends that ack only *after* app_ble_send() has
|
||||
// drained the frame to BLE. So the un-acked count is a live measure of how far
|
||||
// ahead of the wireless link we are. We allow up to BWM_FC_WINDOW frames in
|
||||
// flight, then block for an ack before sending more - which paces us to the real
|
||||
// BLE/WiFi rate and prevents the ESP UART-RX overrun that dropped bulk downloads.
|
||||
// WINDOW frames must fit the ESP UART RX FIFO + wireless send buffer.
|
||||
// Ceiling on un-acked forward frames. On a download the ESP acks steadily so
|
||||
// this never bites; it only matters on a bidirectional UPLOAD, where the ESP
|
||||
// defers the small acks while forwarding large incoming chunks. A tight value
|
||||
// (4) let inflight hit the cap and stall the AT32 past the client timeout, so
|
||||
// keep enough headroom to ride out delayed acks. Only ~1 response is ever
|
||||
// really in flight during an upload, so this does not risk an ESP overrun.
|
||||
#define BWM_FC_WINDOW 16 // max un-acked forward frames in flight
|
||||
// Flow control (byte window). The ESP acks each forward frame with a SLAVE_RESP
|
||||
// echoing SEND_FORWARD_DATA once it has taken the frame out of its UART ring
|
||||
// (module firmware from the companion PR; older firmware acks after the radio,
|
||||
// which also works, just slower), and acks come back in order, so un-acked bytes
|
||||
// bound what that ring holds. The window is sized to the ring (UART_RX_BUF_SIZE
|
||||
// in the BWM firmware's app_cmd_uart.h): keep BWM_ESP_UART_RX_BUF equal to it.
|
||||
// Frames run ~30 B to ~2.1 KB, hence bytes. A frame the ESP cannot deliver is
|
||||
// answered with CMD_ERROR instead and leaves the window the same way. When acks
|
||||
// stall on a full window the gate waits BWM_FC_ACK_TIMEOUT_MS, then forgets just
|
||||
// enough of the oldest frames to send this one. Acks are counted, not matched:
|
||||
// a lost or late ack shifts the window by a frame until it drains at idle.
|
||||
#define BWM_ESP_UART_RX_BUF 12288
|
||||
#define BWM_FC_BYTES (BWM_ESP_UART_RX_BUF - 1024) // in-flight bytes allowed; slack for the ESP's FIFO and parser lag
|
||||
#define BWM_FC_MAX_FRAMES 64 // depth of the in-flight length FIFO; also caps tiny frames in flight
|
||||
#ifndef BWM_FC_ACK_TIMEOUT_MS
|
||||
// Hard cap (ms) on how long a forward write may block the main loop waiting for
|
||||
// acks. A spin COUNT was unbounded in wall-clock time and could hang the main
|
||||
// loop long enough that the client gives up and the device looks dead (USB still
|
||||
// enumerates on interrupts). Time-bounded => the main loop is always serviced.
|
||||
#define BWM_FC_ACK_TIMEOUT_MS 50 // safety valve: proceed if acks stall, never hard-hang
|
||||
// A full window that stays full this long means the ESP is stuck on the radio
|
||||
// (a phone scanning stalls BLE for a few hundred ms); every timeout then leaks
|
||||
// one frame into a full ring, so wait long enough for the usual stalls to pass.
|
||||
#define BWM_FC_ACK_TIMEOUT_MS 200 // safety valve: proceed if acks stall, never hard-hang
|
||||
#endif // safety valve: give up waiting for credit (avoid hard hang)
|
||||
|
||||
#define BWM_CRC16_POLY 0x1021
|
||||
@@ -92,6 +95,25 @@ uint32_t bwm_read_ng(uint8_t *data, size_t len);
|
||||
// >0 when raw bytes are waiting on the FPC USART (gate for receive_ng()).
|
||||
uint16_t bwm_fwd_rxdata_available(void);
|
||||
|
||||
// Flow-control diagnostics since boot (hw status at debug level).
|
||||
typedef struct {
|
||||
uint32_t frames; // forward frames sent
|
||||
uint32_t acks; // SLAVE_RESP acks seen
|
||||
uint32_t errors; // CMD_ERROR answers seen (undeliverable frames)
|
||||
uint32_t timeouts; // gate waited BWM_FC_ACK_TIMEOUT_MS on a full window
|
||||
uint32_t forgotten; // frames the gate stopped counting on those timeouts
|
||||
uint32_t bytes_max; // peak bytes in flight
|
||||
uint8_t in_flight_frames; // now
|
||||
uint32_t in_flight_bytes; // now
|
||||
} bwm_fc_stats_t;
|
||||
void bwm_fwd_fc_stats(bwm_fc_stats_t *out);
|
||||
|
||||
// True while the ESP reports a client on BLE or on its WiFi TCP server (the
|
||||
// LINK_STATE broadcast, sent on change only). ESP firmware without it: never true.
|
||||
bool bwm_fwd_link_connected(void);
|
||||
// Seed the BLE half from a status query at boot (a client can outlive an AT32 reset).
|
||||
void bwm_fwd_link_seed_ble(bool connected);
|
||||
|
||||
// Bring the ESP<->AT32 UART to `target` baud: adopt it if the ESP is already
|
||||
// there (its baud survives an AT32-only reset), else negotiate up via app_com
|
||||
// cmd 1011 and re-init UART4 to match. Returns true if the link runs at
|
||||
|
||||
+40
-1
@@ -21,6 +21,7 @@
|
||||
#include "bwm_uart_at32.h"
|
||||
|
||||
#include "pm3_cmd.h"
|
||||
#include "ticks_apis.h" // GetTickCount / GetTickCountDelta / SpinDelay
|
||||
#include "at32f435_437.h"
|
||||
#include "at32f435_437_crm.h"
|
||||
#include "at32f435_437_gpio.h"
|
||||
@@ -52,6 +53,16 @@ static volatile uint16_t s_rx_tail = 0; // software read cursor; head comes fr
|
||||
static volatile bool s_inited = false;
|
||||
static volatile uint32_t s_cur_baud = BWM_UART_BAUD;
|
||||
|
||||
// The ESP light-sleeps BWM_ESP_AWAKE_MS after the last byte either way and loses
|
||||
// the bytes whose edges wake it, so the first write, and any after
|
||||
// BWM_ESP_WAKE_AFTER_MS of silence of ours (RX is not tracked, see bwm_uart_read),
|
||||
// leads with a disposable preamble; ESP fw without light sleep resyncs past it.
|
||||
#define BWM_ESP_AWAKE_MS 2000 // == UART_LINK_AWAKE_MS on the ESP
|
||||
#define BWM_ESP_WAKE_AFTER_MS 1000 // preamble when quiet longer than this (margin for drift)
|
||||
#define BWM_ESP_WAKE_SETTLE_MS 10 // light-sleep exit + UART driver back up
|
||||
static volatile uint32_t s_last_traffic_tick = 0;
|
||||
static volatile bool s_tx_ever = false; // nothing sent yet: the tick above means nothing
|
||||
|
||||
// Bytes the DMA controller has written so far, wrapped into the ring.
|
||||
// The channel's DTCNT counts DOWN from buffer_size and reloads to buffer_size
|
||||
// at wrap (loop mode), so head = size - remaining, always in [0, size-1].
|
||||
@@ -138,7 +149,17 @@ uint32_t bwm_uart_get_baud(void) {
|
||||
return s_cur_baud;
|
||||
}
|
||||
|
||||
int bwm_uart_write(const uint8_t *data, size_t len) {
|
||||
void bwm_uart_clock_update(void) {
|
||||
if (s_inited == false) {
|
||||
return;
|
||||
}
|
||||
// Recompute the baud divider for the new APB1 clock. usart_init() only
|
||||
// rewrites the divider and the (unchanged) 8N1 frame bits; it does not
|
||||
// touch the enable bits or the DMA request, so it is safe on a live port.
|
||||
usart_init(BWM_UART, s_cur_baud, USART_DATA_8BITS, USART_STOP_1_BIT);
|
||||
}
|
||||
|
||||
static void bwm_uart_write_raw(const uint8_t *data, size_t len) {
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
while (usart_flag_get(BWM_UART, USART_TDBE_FLAG) == RESET) {
|
||||
}
|
||||
@@ -146,9 +167,25 @@ int bwm_uart_write(const uint8_t *data, size_t len) {
|
||||
}
|
||||
while (usart_flag_get(BWM_UART, USART_TDC_FLAG) == RESET) {
|
||||
}
|
||||
}
|
||||
|
||||
int bwm_uart_write(const uint8_t *data, size_t len) {
|
||||
if ((s_tx_ever == false) || (GetTickCountDelta(s_last_traffic_tick) > BWM_ESP_WAKE_AFTER_MS)) {
|
||||
// 0x55 = five rising edges per byte; the ESP wakes after three.
|
||||
static const uint8_t wake[4] = { 0x55, 0x55, 0x55, 0x55 };
|
||||
bwm_uart_write_raw(wake, sizeof(wake));
|
||||
SpinDelay(BWM_ESP_WAKE_SETTLE_MS);
|
||||
}
|
||||
bwm_uart_write_raw(data, len);
|
||||
s_last_traffic_tick = GetTickCount();
|
||||
s_tx_ever = true;
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
void bwm_uart_wake_next(void) {
|
||||
s_tx_ever = false;
|
||||
}
|
||||
|
||||
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
|
||||
@@ -170,5 +207,7 @@ uint32_t bwm_uart_read(uint8_t *data, size_t len) {
|
||||
data[n++] = s_rx_ring[s_rx_tail];
|
||||
s_rx_tail = (uint16_t)((s_rx_tail + 1) & (BWM_RX_RING_SZ - 1));
|
||||
}
|
||||
// RX does not refresh s_last_traffic_tick: the ring can be drained long after
|
||||
// the ESP sent the bytes, so it says nothing about the module's awake window.
|
||||
return n;
|
||||
}
|
||||
|
||||
@@ -36,8 +36,18 @@ void bwm_uart_set_baud(uint32_t baud);
|
||||
// Baud the link is currently running at (updated by bwm_uart_set_baud).
|
||||
uint32_t bwm_uart_get_baud(void);
|
||||
|
||||
// Re-derive the baud divider for the current APB1 clock. Called on every core
|
||||
// clock switch (pm5_power.c); the divider set at 288 MHz is 6x off at 48 MHz.
|
||||
void bwm_uart_clock_update(void);
|
||||
|
||||
// Blocking write of one whole frame. On the first write, and after ~1 s without
|
||||
// a write of our own, it first sends a wake preamble for the ESP's light sleep
|
||||
// and waits ~10 ms (see the BWM_ESP_* defines in bwm_uart_at32.c).
|
||||
int bwm_uart_write(const uint8_t *data, size_t len);
|
||||
|
||||
// Lead the next write with the wake preamble, whatever the quiet time.
|
||||
void bwm_uart_wake_next(void);
|
||||
|
||||
uint16_t bwm_uart_rx_available(void);
|
||||
|
||||
uint32_t bwm_uart_read(uint8_t *data, size_t len);
|
||||
|
||||
+138
-3
@@ -160,7 +160,11 @@ int bwm_cmd(uint16_t cmd, const uint8_t *req, uint16_t req_len,
|
||||
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);
|
||||
// Expected in places (e.g. a status query while WiFi is
|
||||
// off): only worth seeing when debugging the link.
|
||||
if (g_dbglevel >= DBG_DEBUG) {
|
||||
Dbprintf("[bwm-wifi] cmd 0x%04x failed, esp_err=0x%08x", (unsigned)cmd, (unsigned)esp_err);
|
||||
}
|
||||
return PM3_EFAILED;
|
||||
}
|
||||
}
|
||||
@@ -313,8 +317,139 @@ int bwm_wifi_forward_down(void) {
|
||||
// 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_get_version(uint8_t *buf, uint16_t *buflen, uint32_t timeout_ms) {
|
||||
return bwm_cmd(BWM_CMD_GET_VERSION_INFO, NULL, 0, buf, buflen, timeout_ms);
|
||||
}
|
||||
|
||||
// One u8-in / u8-out ESP setting round trip: send req (NULL/0 for a GET) and
|
||||
// expect a one-byte reply in *out. PM3_EFAILED if the ESP acked without a payload.
|
||||
static int bwm_esp_u8_cmd(uint16_t cmd, const uint8_t *req, uint16_t req_len, uint8_t *out, uint32_t timeout_ms) {
|
||||
uint16_t len = 1;
|
||||
int r = bwm_cmd(cmd, req, req_len, out, &len, timeout_ms);
|
||||
if (r == PM3_SUCCESS && len < 1) {
|
||||
r = PM3_EFAILED;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
int bwm_esp_get_power_save(uint8_t *state, uint32_t timeout_ms) {
|
||||
return bwm_esp_u8_cmd(BWM_CMD_GET_SYS_POWER_SAVE, NULL, 0, state, timeout_ms);
|
||||
}
|
||||
|
||||
int bwm_esp_get_ble_state(uint8_t *state, uint32_t timeout_ms) {
|
||||
return bwm_esp_u8_cmd(BWM_CMD_GET_BLE_SPP_STATUS, NULL, 0, state, timeout_ms);
|
||||
}
|
||||
|
||||
int bwm_esp_host_value_set(uint8_t id, uint32_t value, uint32_t timeout_ms) {
|
||||
uint8_t req[5] = { id };
|
||||
memcpy(&req[1], &value, sizeof(value));
|
||||
return bwm_cmd(BWM_CMD_SET_SYS_HOST_VALUE, req, sizeof(req), NULL, NULL, timeout_ms);
|
||||
}
|
||||
|
||||
int bwm_esp_host_value_get(uint8_t id, uint32_t *value, uint32_t timeout_ms) {
|
||||
uint8_t resp[5] = {0};
|
||||
uint16_t len = sizeof(resp);
|
||||
int res = bwm_cmd(BWM_CMD_GET_SYS_HOST_VALUE, &id, 1, resp, &len, timeout_ms);
|
||||
if (res != PM3_SUCCESS) {
|
||||
return res;
|
||||
}
|
||||
if (len < sizeof(resp) || resp[0] == 0) {
|
||||
return PM3_ENODATA;
|
||||
}
|
||||
memcpy(value, &resp[1], sizeof(*value));
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
int bwm_esp_set_ble_enable(bool on, uint8_t *stored) {
|
||||
uint8_t v = on ? 1 : 0;
|
||||
return bwm_esp_u8_cmd(BWM_CMD_SET_BLE_ENABLE, &v, 1, stored, 3000);
|
||||
}
|
||||
|
||||
int bwm_esp_set_ble_bonding(bool on) {
|
||||
uint8_t v = on ? 1 : 0;
|
||||
return bwm_cmd(BWM_CMD_SET_BLE_BONDING_ENABLE, &v, 1, NULL, NULL, 3000);
|
||||
}
|
||||
|
||||
int bwm_esp_set_ble_key(const uint8_t key[6]) {
|
||||
return bwm_cmd(BWM_CMD_SET_BLE_BONDING_KEY, key, 6, NULL, NULL, 3000);
|
||||
}
|
||||
|
||||
int bwm_esp_ble_forget(uint8_t idx) {
|
||||
if (idx == 0xFF) {
|
||||
return bwm_cmd(BWM_CMD_CLEAR_BLE_BONDED, NULL, 0, NULL, NULL, 3000);
|
||||
}
|
||||
return bwm_cmd(BWM_CMD_DEL_BLE_BONDED, &idx, 1, NULL, NULL, 3000);
|
||||
}
|
||||
|
||||
int bwm_esp_set_ble_txpower(uint8_t type, uint8_t level) {
|
||||
uint8_t req[2] = { type, level };
|
||||
return bwm_cmd(BWM_CMD_SET_BLE_TX_POWER, req, sizeof(req), NULL, NULL, 3000);
|
||||
}
|
||||
|
||||
int bwm_esp_ble_restart(void) {
|
||||
uint8_t state = 0;
|
||||
int res = bwm_esp_get_ble_state(&state, 3000);
|
||||
if (res != PM3_SUCCESS || state == 0) {
|
||||
return res; // stopped (or switched off): nothing to restart
|
||||
}
|
||||
res = bwm_cmd(BWM_CMD_STOP_BLE_SPP, NULL, 0, NULL, NULL, 3000);
|
||||
if (res != PM3_SUCCESS) {
|
||||
return res;
|
||||
}
|
||||
return bwm_cmd(BWM_CMD_START_BLE_SPP, NULL, 0, NULL, NULL, 3000);
|
||||
}
|
||||
|
||||
int bwm_esp_ble_status(bwm_ble_status_t *st) {
|
||||
memset(st, 0xFF, sizeof(*st));
|
||||
// The first query wakes a light-sleeping module; the rest land while it is up.
|
||||
// Its timeout is also the only one we pay when no module answers at all.
|
||||
int res = bwm_esp_get_ble_state(&st->state, 1500);
|
||||
if (res == PM3_ETIMEOUT) {
|
||||
return res;
|
||||
}
|
||||
(void)bwm_esp_u8_cmd(BWM_CMD_GET_BLE_ENABLE, NULL, 0, &st->enabled, 800); // older fw: CMD_ERROR or silence
|
||||
(void)bwm_esp_u8_cmd(BWM_CMD_GET_BLE_BONDING_ENABLE, NULL, 0, &st->bonding, 1500);
|
||||
uint16_t len = sizeof(st->passkey);
|
||||
(void)bwm_cmd(BWM_CMD_GET_BLE_BONDING_KEY, NULL, 0, (uint8_t *)st->passkey, &len, 1500);
|
||||
uint8_t type = 0;
|
||||
(void)bwm_esp_u8_cmd(BWM_CMD_GET_BLE_TX_POWER, &type, 1, &st->txp_adv, 1500);
|
||||
type = 1;
|
||||
(void)bwm_esp_u8_cmd(BWM_CMD_GET_BLE_TX_POWER, &type, 1, &st->txp_conn, 1500);
|
||||
len = sizeof(st->addr);
|
||||
(void)bwm_cmd(BWM_CMD_GET_BLE_DEVICE_ADDR, NULL, 0, st->addr, &len, 1500);
|
||||
memset(st->name, 0, sizeof(st->name));
|
||||
len = sizeof(st->name) - 1;
|
||||
if (bwm_esp_get_ble_name((uint8_t *)st->name, &len) != PM3_SUCCESS) {
|
||||
st->name[0] = 0;
|
||||
}
|
||||
st->bonded_count = 0;
|
||||
uint8_t n = 0;
|
||||
if (bwm_esp_u8_cmd(BWM_CMD_GET_BLE_BONDED_NUMS, NULL, 0, &n, 1500) == PM3_SUCCESS) {
|
||||
for (uint8_t i = 0; i < n && i < BWM_BLE_BONDED_MAX; i++) {
|
||||
len = 7;
|
||||
if (bwm_cmd(BWM_CMD_GET_BLE_BONDED_ADDR, &i, 1, st->bonded[i], &len, 1500) == PM3_SUCCESS && len == 7) {
|
||||
st->bonded_count++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
int bwm_esp_set_power_save(bool on, uint8_t *state) {
|
||||
// Applied at once on the ESP (no reboot) and saved to its NVS; the reply
|
||||
// carries the state the ESP ended up in.
|
||||
uint8_t v = on ? 1 : 0;
|
||||
return bwm_esp_u8_cmd(BWM_CMD_SET_SYS_POWER_SAVE, &v, 1, state, 3000);
|
||||
}
|
||||
|
||||
int bwm_esp_get_wifi_ps(uint8_t *mode) {
|
||||
return bwm_esp_u8_cmd(BWM_CMD_GET_WIFI_CFG_PS_MODE, NULL, 0, mode, 3000);
|
||||
}
|
||||
|
||||
int bwm_esp_set_wifi_ps(uint8_t mode, uint8_t *applied) {
|
||||
return bwm_esp_u8_cmd(BWM_CMD_SET_WIFI_CFG_PS_MODE, &mode, 1, applied, 3000);
|
||||
}
|
||||
|
||||
int bwm_esp_get_ble_name(uint8_t *buf, uint16_t *buflen) {
|
||||
|
||||
+50
-1
@@ -10,6 +10,7 @@
|
||||
#define BWM_WIFI_H
|
||||
|
||||
#include "common.h"
|
||||
#include "pm3_cmd.h" // bwm_ble_status_t
|
||||
|
||||
// app_com command codes (authoritative, from BWM main/app_com_defs.h)
|
||||
#define BWM_CMD_SET_TO_WIFI_DISABLE_MODE 2000 // no payload: tear down WiFi, back to BLE-only
|
||||
@@ -88,7 +89,55 @@ int bwm_wifi_forward_status(uint8_t *state, uint32_t *ip_out);
|
||||
#define BWM_CMD_START_BLE_SPP 4021 // no payload: restore BLE after OTA
|
||||
#define BWM_CMD_SET_BLE_DEVICE_NAME 4002 // req: name bytes
|
||||
#define BWM_CMD_GET_BLE_DEVICE_NAME 4003 // resp: current BLE device name string
|
||||
int bwm_esp_get_version(uint8_t *buf, uint16_t *buflen);
|
||||
// ESP power-save switch (DFS + light sleep + low-duty advertising), persisted on
|
||||
// the ESP. Both return the ESP's applied state in *state.
|
||||
#define BWM_CMD_SET_SYS_POWER_SAVE 1019 // req: u8 0=off 1=on; resp: u8 applied state
|
||||
#define BWM_CMD_GET_SYS_POWER_SAVE 1020 // resp: u8 state
|
||||
int bwm_esp_get_power_save(uint8_t *state, uint32_t timeout_ms);
|
||||
// Host settings the module stores for us (app_host NVS); the ids are ours.
|
||||
#define BWM_CMD_SET_SYS_HOST_VALUE 1021 // req: [id u8][value u32]; resp: u32
|
||||
#define BWM_CMD_GET_SYS_HOST_VALUE 1022 // req: [id u8]; resp: [present u8][value u32]
|
||||
#define BWM_HOSTVAL_AUTOOFF_ENABLED 0
|
||||
#define BWM_HOSTVAL_AUTOOFF_IDLE_S 1
|
||||
#define BWM_HOSTVAL_AUTOOFF_UNPLUG 2
|
||||
int bwm_esp_host_value_set(uint8_t id, uint32_t value, uint32_t timeout_ms);
|
||||
// PM3_SUCCESS with the value, PM3_ENODATA if never stored, else the link error.
|
||||
int bwm_esp_host_value_get(uint8_t id, uint32_t *value, uint32_t timeout_ms);
|
||||
#define BWM_CMD_GET_BLE_SPP_STATUS 4020 // resp: u8 0=stopped 1=advertising 2=client connected
|
||||
#define BWM_BLE_STATE_CONNECTED 2
|
||||
int bwm_esp_get_ble_state(uint8_t *state, uint32_t timeout_ms);
|
||||
|
||||
// BLE settings behind `hw bwm ble` (app_com_defs.h, BLE block @4000).
|
||||
#define BWM_CMD_GET_BLE_DEVICE_ADDR 4007 // resp: 6 bytes
|
||||
#define BWM_CMD_SET_BLE_BONDING_ENABLE 4008 // req: u8; takes effect at the next stack start
|
||||
#define BWM_CMD_GET_BLE_BONDING_ENABLE 4009 // resp: u8
|
||||
#define BWM_CMD_SET_BLE_BONDING_KEY 4010 // req: 6 ASCII digits
|
||||
#define BWM_CMD_GET_BLE_BONDING_KEY 4011 // resp: 6 ASCII digits
|
||||
#define BWM_CMD_GET_BLE_BONDED_NUMS 4012 // resp: u8 count
|
||||
#define BWM_CMD_GET_BLE_BONDED_ADDR 4013 // req: u8 index; resp: addr[6] + type
|
||||
#define BWM_CMD_DEL_BLE_BONDED 4014 // req: u8 index
|
||||
#define BWM_CMD_CLEAR_BLE_BONDED 4015 // no payload
|
||||
#define BWM_CMD_SET_BLE_TX_POWER 4018 // req: [type][level]
|
||||
#define BWM_CMD_GET_BLE_TX_POWER 4019 // req: u8 type; resp: u8 level
|
||||
#define BWM_CMD_SET_BLE_ENABLE 4023 // req: u8 (persisted, applied at once); resp: u8 stored. Newer ESP fw only
|
||||
#define BWM_CMD_GET_BLE_ENABLE 4024 // resp: u8
|
||||
int bwm_esp_set_ble_enable(bool on, uint8_t *stored);
|
||||
int bwm_esp_set_ble_bonding(bool on);
|
||||
int bwm_esp_set_ble_key(const uint8_t key[6]);
|
||||
int bwm_esp_ble_forget(uint8_t idx); // 0xFF = all
|
||||
int bwm_esp_set_ble_txpower(uint8_t type, uint8_t level);
|
||||
// Stop + start the stack so a bonding change applies; no-op while it is stopped.
|
||||
int bwm_esp_ble_restart(void);
|
||||
// Best-effort snapshot for `hw bwm ble`; fields the module cannot answer read 0xFF.
|
||||
// PM3_ETIMEOUT (nothing filled in) when the module does not answer the first query.
|
||||
int bwm_esp_ble_status(bwm_ble_status_t *st);
|
||||
// ESP WiFi modem power-save type (0 none, 1 min, 2 max), persisted on the ESP.
|
||||
#define BWM_CMD_SET_WIFI_CFG_PS_MODE 2052 // req: u8 mode; resp: u8 applied mode
|
||||
#define BWM_CMD_GET_WIFI_CFG_PS_MODE 2053 // resp: u8 mode
|
||||
int bwm_esp_get_wifi_ps(uint8_t *mode);
|
||||
int bwm_esp_set_wifi_ps(uint8_t mode, uint8_t *applied);
|
||||
int bwm_esp_set_power_save(bool on, uint8_t *state);
|
||||
int bwm_esp_get_version(uint8_t *buf, uint16_t *buflen, uint32_t timeout_ms);
|
||||
int bwm_esp_get_ble_name(uint8_t *buf, uint16_t *buflen);
|
||||
int bwm_esp_set_ble_name(const uint8_t *name, uint16_t len);
|
||||
int bwm_esp_ota_begin(uint32_t total_size);
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// See LICENSE.txt for the text of the license.
|
||||
//-----------------------------------------------------------------------------
|
||||
// Proxmark5 (AT32F435) power-save idle - see pm5_power.h.
|
||||
//
|
||||
// Idle: SCLK PLL-288 -> HICK-48, PLL off, LDO 1.3 -> 1.1 V (good to 144 MHz),
|
||||
// FPGA CLKOUT1 parked low, core in WFI until an IRQ or a 1 ms SysTick wake.
|
||||
// USB is crystal-less off HICK-48 (ACC-trimmed on the SOF, usb_cdc_at32.c) and
|
||||
// the 1 kHz tick counter is on the ERTC (HEXT/20), so neither notices the PLL
|
||||
// going away. Everything else keeps its 288 MHz assumption via the boost.
|
||||
//
|
||||
// Ported from the Fantasi firmware.
|
||||
//-----------------------------------------------------------------------------
|
||||
#include "pm5_power.h"
|
||||
|
||||
#include "proxmark3_arm.h"
|
||||
#include "dbprint.h"
|
||||
#include "ticks_apis.h"
|
||||
#include "fpga_apis.h"
|
||||
#include "config_gpio_proxmark5.h"
|
||||
#ifdef WITH_BWM_FORWARD
|
||||
#include "bwm_uart_at32.h"
|
||||
#endif
|
||||
|
||||
#include "at32f435_437.h"
|
||||
#include "at32f435_437_crm.h"
|
||||
#include "at32f435_437_pwc.h"
|
||||
#include "at32f435_437_gpio.h"
|
||||
|
||||
// Full speed through USB enumeration and the rest of the boot.
|
||||
#define PM5_POWER_BOOT_GRACE_MS 3000
|
||||
// Quiet time after the last boost before downclocking: back-to-back commands
|
||||
// (autopwn, scripts, BWM forwarded traffic) don't pay the switch each time.
|
||||
#define PM5_POWER_IDLE_MS 200
|
||||
#define PM5_POWER_CLK_WAIT 500000u
|
||||
|
||||
static volatile uint32_t s_boost; // >0 = something holds 288 MHz
|
||||
static volatile bool s_clk_high = true; // boot runs at 288 MHz
|
||||
static bool s_powersave = true;
|
||||
static uint32_t s_last_busy_tick;
|
||||
|
||||
// Stats for `hw status`.
|
||||
static uint32_t s_downclocks;
|
||||
static uint32_t s_low_ms;
|
||||
static uint32_t s_low_since_tick;
|
||||
static uint32_t s_sleeps;
|
||||
static uint32_t s_asleep_ms;
|
||||
static uint32_t s_asleep_us_resid;
|
||||
|
||||
static void pm5_fpga_clk_park(void) {
|
||||
// Static low, not floating, while the PLL behind CLKOUT1 is off.
|
||||
gpio_init_type gpio_init_struct;
|
||||
gpio_default_para_init(&gpio_init_struct);
|
||||
gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
|
||||
gpio_init_struct.gpio_out_type = GPIO_OUTPUT_PUSH_PULL;
|
||||
gpio_init_struct.gpio_mode = GPIO_MODE_OUTPUT;
|
||||
gpio_init_struct.gpio_pins = AT32_GPIO_FPGA_24M_CLK_PIN;
|
||||
gpio_init_struct.gpio_pull = GPIO_PULL_NONE;
|
||||
gpio_bits_reset(AT32_GPIO_FPGA_24M_CLK, AT32_GPIO_FPGA_24M_CLK_PIN);
|
||||
gpio_init(AT32_GPIO_FPGA_24M_CLK, &gpio_init_struct);
|
||||
}
|
||||
|
||||
// Voltage-safe ordering: up = LDO before frequency, down = frequency before LDO.
|
||||
// The LDO is only writable while SCLK is on HICK/HEXT, which holds on both paths.
|
||||
// Runs with IRQs masked.
|
||||
static void pm5_clk_apply(bool high) {
|
||||
uint32_t i;
|
||||
if (high) {
|
||||
pwc_ldo_output_voltage_set(PWC_LDO_OUTPUT_1V3);
|
||||
for (volatile uint32_t d = 0; d < 2000; d++) {} // LDO settle
|
||||
|
||||
crm_clock_source_enable(CRM_CLOCK_SOURCE_PLL, TRUE);
|
||||
for (i = 0; i < PM5_POWER_CLK_WAIT; i++) {
|
||||
if (crm_flag_get(CRM_PLL_STABLE_FLAG) == SET) break;
|
||||
}
|
||||
crm_auto_step_mode_enable(TRUE);
|
||||
crm_sysclk_switch(CRM_SCLK_PLL);
|
||||
for (i = 0; i < PM5_POWER_CLK_WAIT; i++) {
|
||||
if (crm_sysclk_switch_status_get() == CRM_SCLK_PLL) break;
|
||||
}
|
||||
crm_auto_step_mode_enable(FALSE);
|
||||
system_core_clock_update();
|
||||
|
||||
FpgaSetup24MHzClk(); // CLKOUT1 back on PA8
|
||||
} else {
|
||||
pm5_fpga_clk_park();
|
||||
|
||||
crm_clock_source_enable(CRM_CLOCK_SOURCE_HICK, TRUE);
|
||||
for (i = 0; i < PM5_POWER_CLK_WAIT; i++) {
|
||||
if (crm_flag_get(CRM_HICK_STABLE_FLAG) == SET) break;
|
||||
}
|
||||
crm_auto_step_mode_enable(TRUE);
|
||||
crm_sysclk_switch(CRM_SCLK_HICK);
|
||||
for (i = 0; i < PM5_POWER_CLK_WAIT; i++) {
|
||||
if (crm_sysclk_switch_status_get() == CRM_SCLK_HICK) break;
|
||||
}
|
||||
crm_auto_step_mode_enable(FALSE);
|
||||
crm_clock_source_enable(CRM_CLOCK_SOURCE_PLL, FALSE);
|
||||
pwc_ldo_output_voltage_set(PWC_LDO_OUTPUT_1V1);
|
||||
system_core_clock_update();
|
||||
}
|
||||
#ifdef WITH_BWM_FORWARD
|
||||
bwm_uart_clock_update(); // UART4 baud follows APB1
|
||||
#endif
|
||||
s_clk_high = high;
|
||||
}
|
||||
|
||||
void pm5_power_init(void) {
|
||||
// SCLK-from-HICK must be 48 MHz, not the 8 MHz reset default. usb_enable()
|
||||
// already selects this for the crystal-less USB clock; make it explicit.
|
||||
crm_hick_sclk_frequency_select(CRM_HICK_SCLK_48MHZ);
|
||||
|
||||
// Free-running cycle counter: measures the time actually spent in WFI.
|
||||
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
|
||||
DWT->CYCCNT = 0;
|
||||
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
|
||||
|
||||
s_last_busy_tick = GetTickCount();
|
||||
}
|
||||
|
||||
void pm5_power_boost(void) {
|
||||
__disable_irq();
|
||||
if (s_boost++ == 0 && s_clk_high == false) {
|
||||
pm5_clk_apply(true);
|
||||
s_low_ms += GetTickCountDelta(s_low_since_tick);
|
||||
}
|
||||
__enable_irq();
|
||||
}
|
||||
|
||||
void pm5_power_unboost(void) {
|
||||
__disable_irq();
|
||||
if (s_boost > 0) {
|
||||
s_boost--;
|
||||
}
|
||||
__enable_irq();
|
||||
// The downclock itself is left to pm5_power_idle() after PM5_POWER_IDLE_MS.
|
||||
if (s_boost == 0) {
|
||||
s_last_busy_tick = GetTickCount();
|
||||
}
|
||||
}
|
||||
|
||||
static bool pm5_power_may_downclock(void) {
|
||||
if (s_boost != 0 || s_clk_high == false) {
|
||||
return false;
|
||||
}
|
||||
if (GetTickCount() < PM5_POWER_BOOT_GRACE_MS) {
|
||||
return false;
|
||||
}
|
||||
if (GetTickCountDelta(s_last_busy_tick) < PM5_POWER_IDLE_MS) {
|
||||
return false;
|
||||
}
|
||||
// A reader field or emulation left running needs the FPGA clock.
|
||||
return FpgaIsOff();
|
||||
}
|
||||
|
||||
// Halt until the next IRQ, at most ~1 ms (one-shot SysTick, AHB/8 source like
|
||||
// SpinDelayUs which reprograms SysTick anyway). IRQs stay masked across the
|
||||
// WFI so the cycle delta measures only the sleep; the waking ISR runs after.
|
||||
static void pm5_power_wfi(void) {
|
||||
SysTick->CTRL = 0;
|
||||
SysTick->LOAD = (system_core_clock / 8 / 1000) - 1;
|
||||
SysTick->VAL = 0;
|
||||
SysTick->CTRL = SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk;
|
||||
|
||||
__disable_irq();
|
||||
uint32_t t0 = DWT->CYCCNT;
|
||||
__WFI();
|
||||
uint32_t cycles = DWT->CYCCNT - t0;
|
||||
__enable_irq();
|
||||
|
||||
SysTick->CTRL = 0;
|
||||
|
||||
s_sleeps++;
|
||||
s_asleep_us_resid += cycles / (system_core_clock / 1000000);
|
||||
if (s_asleep_us_resid >= 1000) {
|
||||
s_asleep_ms += s_asleep_us_resid / 1000;
|
||||
s_asleep_us_resid %= 1000;
|
||||
}
|
||||
}
|
||||
|
||||
void pm5_power_idle(void) {
|
||||
if (s_powersave == false) {
|
||||
return;
|
||||
}
|
||||
if (pm5_power_may_downclock()) {
|
||||
SpinDelayUs(10); // let a just-sent FPGA conf word land before its clock stops
|
||||
__disable_irq();
|
||||
pm5_clk_apply(false);
|
||||
__enable_irq();
|
||||
s_downclocks++;
|
||||
s_low_since_tick = GetTickCount();
|
||||
}
|
||||
pm5_power_wfi();
|
||||
}
|
||||
|
||||
void pm5_power_set_enabled(bool on) {
|
||||
s_powersave = on;
|
||||
if (on == false && s_clk_high == false) {
|
||||
__disable_irq();
|
||||
pm5_clk_apply(true);
|
||||
__enable_irq();
|
||||
s_low_ms += GetTickCountDelta(s_low_since_tick);
|
||||
}
|
||||
}
|
||||
|
||||
bool pm5_power_get_enabled(void) {
|
||||
return s_powersave;
|
||||
}
|
||||
|
||||
// Per-mille of uptime, printed as a percentage with one decimal.
|
||||
static uint32_t pm5_power_permille(uint32_t ms, uint32_t uptime_ms) {
|
||||
if (uptime_ms == 0) {
|
||||
return 0;
|
||||
}
|
||||
return (uint32_t)(((uint64_t)ms * 1000) / uptime_ms);
|
||||
}
|
||||
|
||||
void pm5_power_print_status(void) {
|
||||
uint32_t uptime = GetTickCount();
|
||||
uint32_t low = pm5_power_permille(s_low_ms, uptime);
|
||||
uint32_t asleep = pm5_power_permille(s_asleep_ms, uptime);
|
||||
|
||||
DbpString(_CYAN_("Power"));
|
||||
Dbprintf(" Power-save idle..... %s", s_powersave ? _GREEN_("enabled") : _YELLOW_("disabled"));
|
||||
DbpString(" Core clock.......... 288 MHz active, 48 MHz idle ( PLL off, LDO 1.1 V )");
|
||||
Dbprintf(" Uptime.............. %u ms", uptime);
|
||||
Dbprintf(" Idle at 48 MHz...... %u.%u %% ( %u downclocks )", low / 10, low % 10, s_downclocks);
|
||||
Dbprintf(" Halted in WFI....... %u.%u %% ( %u sleeps )", asleep / 10, asleep % 10, s_sleeps);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// See LICENSE.txt for the text of the license.
|
||||
//-----------------------------------------------------------------------------
|
||||
// Proxmark5 power-save idle: core clock scaling + WFI - see pm5_power.c.
|
||||
//-----------------------------------------------------------------------------
|
||||
#ifndef PM5_POWER_H__
|
||||
#define PM5_POWER_H__
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#ifdef PM5
|
||||
|
||||
// Call once from AppMain after USB is up.
|
||||
void pm5_power_init(void);
|
||||
|
||||
// Refcounted hold on the full 288 MHz core clock. Wrap anything with timing
|
||||
// that assumes the boot clock (commands, standalone mode, I2C, buzzer).
|
||||
void pm5_power_boost(void);
|
||||
void pm5_power_unboost(void);
|
||||
|
||||
// Main-loop idle step: drop to the idle clock when allowed, then halt the core
|
||||
// until the next IRQ or the 1 ms wake tick.
|
||||
void pm5_power_idle(void);
|
||||
|
||||
// Runtime toggle (default on). Off = stay at 288 MHz and spin.
|
||||
void pm5_power_set_enabled(bool on);
|
||||
bool pm5_power_get_enabled(void);
|
||||
|
||||
// `hw status` section.
|
||||
void pm5_power_print_status(void);
|
||||
|
||||
#endif // PM5
|
||||
|
||||
#endif // PM5_POWER_H__
|
||||
@@ -21,6 +21,7 @@
|
||||
#ifdef WITH_PM5_PWR_LED
|
||||
|
||||
#include "rgb_apis.h" // RgbLedSet
|
||||
#include "pm5_power.h" // pm5_power_boost / unboost
|
||||
#include "gpio_apis.h" // gpio_vusb_setup / Gpio_VUSB_Read (USB-present detection)
|
||||
#include "ticks_apis.h" // GetTickCount / GetTickCountDelta
|
||||
|
||||
@@ -68,6 +69,7 @@ void rgb_indicator_update(void) {
|
||||
return; // edge-triggered: only write RGB when the state changes
|
||||
}
|
||||
|
||||
pm5_power_boost(); // I2C bit-bang timing assumes the full core clock
|
||||
// Commit last_state only if the controller actually ACKed. On a cold boot the
|
||||
// I2C RGB controller can still be powering up when the first write fires; if we
|
||||
// latched last_state regardless, a NAK here would leave the LED dark until the
|
||||
@@ -78,6 +80,7 @@ void rgb_indicator_update(void) {
|
||||
if (ok) {
|
||||
last_state = on_battery;
|
||||
}
|
||||
pm5_power_unboost();
|
||||
}
|
||||
|
||||
#endif // WITH_PM5_PWR_LED
|
||||
|
||||
+539
-27
@@ -35,40 +35,77 @@
|
||||
#include "util_posix.h"
|
||||
|
||||
static int CmdBwmAutoOff(const char *Cmd) {
|
||||
// Positional sub-action (no dashes): hw bwm autooff on | off
|
||||
// Positional sub-action (no dashes): hw bwm autooff [on|off] [--idle <sec>]
|
||||
char verb[16] = {0};
|
||||
sscanf(Cmd, "%15s", verb);
|
||||
int consumed = 0;
|
||||
sscanf(Cmd, "%15s%n", verb, &consumed);
|
||||
bool on = (strcmp(verb, "on") == 0);
|
||||
bool off = (strcmp(verb, "off") == 0);
|
||||
const char *rest = (on || off) ? Cmd + consumed : Cmd;
|
||||
|
||||
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"));
|
||||
CLIParserContext *ctx;
|
||||
CLIParserInit(&ctx, "hw bwm autooff",
|
||||
"Show or set automatic power-off (PM5 only). Two triggers:\n"
|
||||
" - USB was present and the cable is pulled: off at once, so a BWM-equipped\n"
|
||||
" PM5 doesn't silently drain the battery; --unplug off only restarts the\n"
|
||||
" idle clock instead;\n"
|
||||
" - on battery with no command, button press or BLE/WiFi client for --idle\n"
|
||||
" seconds: off (0 = never, the default).\n"
|
||||
"Button power-on is unaffected. Stored on the BWM; without a module, defaults.",
|
||||
"hw bwm autooff --> show the current state\n"
|
||||
"hw bwm autooff off --> disable both triggers\n"
|
||||
"hw bwm autooff on --> re-enable\n"
|
||||
"hw bwm autooff --idle 300 --> also off after 5 min idle on battery\n"
|
||||
"hw bwm autooff --idle 0 --> USB-unplug trigger only (default)\n"
|
||||
"hw bwm autooff --unplug off --idle 300 --> survive the unplug, off 5 min idle later");
|
||||
void *argtable[] = {
|
||||
arg_param_begin,
|
||||
arg_int0("i", "idle", "<sec>", "idle timeout on battery in seconds, 0 = never"),
|
||||
arg_str0("u", "unplug", "<on|off>", "power off when USB is pulled (default on)"),
|
||||
arg_param_end
|
||||
};
|
||||
CLIExecWithReturn(ctx, rest, argtable, true);
|
||||
int idle = arg_get_int_def(ctx, 1, -1);
|
||||
uint8_t ubuf[8] = {0};
|
||||
int ulen = 0;
|
||||
int ures = CLIParamStrToBuf(arg_get_str(ctx, 2), ubuf, sizeof(ubuf) - 1, &ulen);
|
||||
CLIParserFree(ctx);
|
||||
if (ures) {
|
||||
PrintAndLogEx(WARNING, "--unplug takes " _YELLOW_("on") " or " _YELLOW_("off"));
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
uint8_t unplug = BWM_AUTOOFF_KEEP_U8;
|
||||
if (ulen) {
|
||||
if (strcmp((char *)ubuf, "on") == 0) {
|
||||
unplug = 1;
|
||||
} else if (strcmp((char *)ubuf, "off") == 0) {
|
||||
unplug = 0;
|
||||
} else {
|
||||
PrintAndLogEx(WARNING, "--unplug takes " _YELLOW_("on") " or " _YELLOW_("off"));
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
}
|
||||
if ((idle < -1) || (idle > (int)BWM_AUTOOFF_IDLE_MAX_S)) {
|
||||
PrintAndLogEx(WARNING, "idle must be 0 to %lu seconds", (unsigned long)BWM_AUTOOFF_IDLE_MAX_S);
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
|
||||
uint8_t payload = off ? 0 : 1; // on -> 1 (enable), off -> 0 (disable)
|
||||
struct {
|
||||
uint8_t action;
|
||||
uint8_t enabled;
|
||||
uint32_t idle_s;
|
||||
uint8_t unplug;
|
||||
} PACKED payload = {
|
||||
.action = (on || off || (idle >= 0) || ulen) ? BWM_AUTOOFF_ACTION_SET : BWM_AUTOOFF_ACTION_GET,
|
||||
.enabled = on ? 1 : (off ? 0 : BWM_AUTOOFF_KEEP_U8),
|
||||
.idle_s = (idle >= 0) ? (uint32_t)idle : BWM_AUTOOFF_KEEP_U32,
|
||||
.unplug = unplug,
|
||||
};
|
||||
|
||||
clearCommandBuffer();
|
||||
SendCommandNG(CMD_PM5_BWM_AUTOOFF, &payload, sizeof(payload));
|
||||
SendCommandNG(CMD_PM5_BWM_AUTOOFF, (uint8_t *)&payload, sizeof(payload));
|
||||
PacketResponseNG resp;
|
||||
if (WaitForResponseTimeout(CMD_PM5_BWM_AUTOOFF, &resp, 2500) == false) {
|
||||
if (WaitForResponseTimeout(CMD_PM5_BWM_AUTOOFF, &resp, 5000) == false) {
|
||||
PrintAndLogEx(WARNING, "command timeout (is this a PM5?)");
|
||||
return PM3_ETIMEOUT;
|
||||
}
|
||||
@@ -80,7 +117,34 @@ static int CmdBwmAutoOff(const char *Cmd) {
|
||||
PrintAndLogEx(FAILED, "failed to set auto power-off");
|
||||
return resp.status;
|
||||
}
|
||||
PrintAndLogEx(SUCCESS, "Auto power-off %s.", payload ? _GREEN_("enabled") : _YELLOW_("disabled"));
|
||||
bool enabled = (resp.length >= 1) && (resp.data.asBytes[0] != 0);
|
||||
PrintAndLogEx(SUCCESS, "Auto power-off....... %s", enabled ? _GREEN_("on") : _YELLOW_("off"));
|
||||
if (resp.length < 5) {
|
||||
// firmware before the idle timeout: it only reports the switch
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
uint32_t idle_s;
|
||||
memcpy(&idle_s, &resp.data.asBytes[1], sizeof(idle_s));
|
||||
if (idle_s) {
|
||||
PrintAndLogEx(INFO, "Idle timeout......... " _YELLOW_("%u") " s", idle_s);
|
||||
} else {
|
||||
PrintAndLogEx(INFO, "Idle timeout......... off");
|
||||
}
|
||||
if (resp.length >= sizeof(bwm_autooff_status_t)) {
|
||||
const bwm_autooff_status_t *st = (const bwm_autooff_status_t *)resp.data.asBytes;
|
||||
const char *live = (st->ble_live == 2) ? "client connected" : (st->ble_live == 1) ? "advertising, no client" : (st->ble_live == 0) ? "off" : "no answer";
|
||||
PrintAndLogEx(INFO, "Unplug power-off..... %s", st->unplug ? "on" : _YELLOW_("off") " (an unplug only restarts the idle clock)");
|
||||
PrintAndLogEx(INFO, "Stored on module..... %s", st->persisted ? "yes" : _YELLOW_("no"));
|
||||
PrintAndLogEx(INFO, "USB power............ %s", st->usb ? "present" : "absent");
|
||||
PrintAndLogEx(INFO, "USB seen since boot.. %s", st->usb_seen ? "yes" : "no");
|
||||
PrintAndLogEx(INFO, "Unplug trigger....... %s", (enabled && st->unplug && st->usb_seen) ? _GREEN_("armed") : "not armed");
|
||||
PrintAndLogEx(INFO, "Tracked client....... %s", st->link ? "connected (BLE or WiFi)" : "none");
|
||||
PrintAndLogEx(INFO, "Module BLE state..... %s", live);
|
||||
PrintAndLogEx(INFO, "Idle for............. %u s", st->idle_now_s);
|
||||
if (st->persisted == 0) {
|
||||
PrintAndLogEx(HINT, "no module, or one too old to store it: the defaults return at the next boot");
|
||||
}
|
||||
}
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -697,15 +761,463 @@ static int CmdBwmName(const char *Cmd) {
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
// Round trip for the BWM u8 settings (`hw bwm powersave`, `hw bwm wifipower`):
|
||||
// send [action][value] (value only when has_value) and wait for the ESP's answer.
|
||||
// Prints the generic failure messages; on PM3_SUCCESS resp holds >= 1 byte.
|
||||
static int bwm_setting_txn(uint16_t cmd, uint8_t action, uint8_t value, bool has_value,
|
||||
PacketResponseNG *resp, const char *what) {
|
||||
uint8_t payload[2] = { action, value };
|
||||
clearCommandBuffer();
|
||||
SendCommandNG(cmd, payload, has_value ? 2 : 1);
|
||||
if (WaitForResponseTimeout(cmd, resp, 8000) == false) {
|
||||
PrintAndLogEx(WARNING, "command timeout (is this a PM5 with a responsive BWM?)");
|
||||
return PM3_ETIMEOUT;
|
||||
}
|
||||
if (resp->status == PM3_ENOTIMPL) {
|
||||
PrintAndLogEx(WARNING, "firmware built without BWM link support");
|
||||
return resp->status;
|
||||
}
|
||||
if (resp->status != PM3_SUCCESS || resp->length < 1) {
|
||||
PrintAndLogEx(FAILED, "BWM did not answer the %s command (BWM firmware too old?)", what);
|
||||
return (resp->status != PM3_SUCCESS) ? resp->status : PM3_EFAILED;
|
||||
}
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
static const char *bwm_wifi_state_name(uint8_t state) {
|
||||
switch (state) {
|
||||
case 0: return "disconnected";
|
||||
case 1: return "connecting";
|
||||
case 2: return "connected";
|
||||
case 3: return "reconnecting";
|
||||
case 4: return "connect task stopped";
|
||||
default: return "unknown state";
|
||||
}
|
||||
}
|
||||
|
||||
static int CmdBwmPowerSave(const char *Cmd) {
|
||||
// Positional sub-action (no dashes): hw bwm powersave [on | off]; no verb shows the state
|
||||
char verb[16] = {0};
|
||||
sscanf(Cmd, "%15s", verb);
|
||||
bool on = (strcmp(verb, "on") == 0);
|
||||
bool off = (strcmp(verb, "off") == 0);
|
||||
bool show = (verb[0] == 0);
|
||||
|
||||
if (!on && !off && !show) {
|
||||
// Not a recognised sub-action: render help (also serves -h), or error on
|
||||
// a stray token, then stop.
|
||||
CLIParserContext *ctx;
|
||||
CLIParserInit(&ctx, "hw bwm powersave",
|
||||
"Show or set the BWM (ESP32) power-save switch, stored on the BWM in NVS.\n"
|
||||
"Default is " _GREEN_("on") ": the ESP scales its clock down, light-sleeps between\n"
|
||||
"link traffic, and after 30 s of fast advertising (boot, disconnect) advertises\n"
|
||||
"once a second. " _YELLOW_("off") " pins it at full clock, no sleep, fast advertising\n"
|
||||
"throughout. Applies at once and survives reboots. PM5 only.",
|
||||
"hw bwm powersave --> show the current state\n"
|
||||
"hw bwm powersave off --> stock always-on behaviour\n"
|
||||
"hw bwm powersave on --> re-enable power saving");
|
||||
void *argtable[] = {
|
||||
arg_param_begin,
|
||||
arg_param_end
|
||||
};
|
||||
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
||||
CLIParserFree(ctx);
|
||||
PrintAndLogEx(WARNING, "specify " _YELLOW_("on") ", " _YELLOW_("off") " or nothing to show the state");
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
|
||||
PacketResponseNG resp;
|
||||
int res = bwm_setting_txn(CMD_PM5_BWM_POWERSAVE, show ? BWM_POWERSAVE_ACTION_GET : BWM_POWERSAVE_ACTION_SET,
|
||||
on ? 1 : 0, !show, &resp, "power-save");
|
||||
if (res != PM3_SUCCESS) {
|
||||
return res;
|
||||
}
|
||||
bool state = (resp.data.asBytes[0] != 0);
|
||||
PrintAndLogEx(SUCCESS, "BWM power save..... %s", state ? _GREEN_("on") : _YELLOW_("off"));
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
static const char *bwm_wifi_ps_name(uint8_t mode) {
|
||||
switch (mode) {
|
||||
case BWM_WIFI_PS_NONE: return "none";
|
||||
case BWM_WIFI_PS_MIN: return "min";
|
||||
case BWM_WIFI_PS_MAX: return "max";
|
||||
default: return "?";
|
||||
}
|
||||
}
|
||||
|
||||
static int CmdBwmWifiPower(const char *Cmd) {
|
||||
// Positional sub-action (no dashes): hw bwm wifipower [off | none | min | max]; no verb shows the state
|
||||
char verb[16] = {0};
|
||||
sscanf(Cmd, "%15s", verb);
|
||||
int mode = -1;
|
||||
if (verb[0] == 0) {
|
||||
mode = -1;
|
||||
} else if (strcmp(verb, "off") == 0) {
|
||||
// WiFi fully off: same as `hw bwm wifi stop`.
|
||||
return CmdBWMWifi("stop");
|
||||
} else if (strcmp(verb, "none") == 0) {
|
||||
mode = BWM_WIFI_PS_NONE;
|
||||
} else if (strcmp(verb, "min") == 0) {
|
||||
mode = BWM_WIFI_PS_MIN;
|
||||
} else if (strcmp(verb, "max") == 0) {
|
||||
mode = BWM_WIFI_PS_MAX;
|
||||
} else {
|
||||
// Not a recognised sub-action: render help (also serves -h), or error on
|
||||
// a stray token, then stop.
|
||||
CLIParserContext *ctx;
|
||||
CLIParserInit(&ctx, "hw bwm wifipower",
|
||||
"Show or set how much power the BWM (ESP32) spends on WiFi.\n"
|
||||
_YELLOW_("off") " turns WiFi fully off (BLE-only, persisted; the default state, same as\n"
|
||||
"`hw bwm wifi stop`). Bring it back with " _YELLOW_("hw bwm wifi --ssid <ssid> --pwd <pwd>") ".\n"
|
||||
"The other three set the modem power-save type while WiFi is up, stored on the\n"
|
||||
"BWM: " _GREEN_("min") " (default) sleeps between DTIM beacons, " _YELLOW_("max") " for the whole\n"
|
||||
"listen interval, " _YELLOW_("none") " never. Independent of " _YELLOW_("hw bwm powersave") ". PM5 only.",
|
||||
"hw bwm wifipower --> show whether WiFi is up and the power-save type\n"
|
||||
"hw bwm wifipower off --> WiFi fully off, BLE-only\n"
|
||||
"hw bwm wifipower none --> modem always on while WiFi is up\n"
|
||||
"hw bwm wifipower min --> ESP-IDF default (DTIM sleep)\n"
|
||||
"hw bwm wifipower max --> deepest modem sleep");
|
||||
void *argtable[] = {
|
||||
arg_param_begin,
|
||||
arg_param_end
|
||||
};
|
||||
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
||||
CLIParserFree(ctx);
|
||||
PrintAndLogEx(WARNING, "specify " _YELLOW_("off") ", " _YELLOW_("none") ", " _YELLOW_("min") ", " _YELLOW_("max") " or nothing to show the state");
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
|
||||
PacketResponseNG resp;
|
||||
int res = bwm_setting_txn(CMD_PM5_BWM_WIFI_PS, (mode < 0) ? BWM_WIFI_PS_ACTION_GET : BWM_WIFI_PS_ACTION_SET,
|
||||
(mode < 0) ? 0 : (uint8_t)mode, mode >= 0, &resp, "WiFi power");
|
||||
if (res != PM3_SUCCESS) {
|
||||
return res;
|
||||
}
|
||||
uint8_t wifi_state = (resp.length >= 2) ? resp.data.asBytes[1] : BWM_WIFI_STATE_OFF;
|
||||
if (wifi_state == BWM_WIFI_STATE_OFF) {
|
||||
PrintAndLogEx(SUCCESS, "BWM WiFi................ " _GREEN_("off") " (BLE-only)");
|
||||
} else {
|
||||
PrintAndLogEx(SUCCESS, "BWM WiFi................ " _YELLOW_("on") " (%s)", bwm_wifi_state_name(wifi_state));
|
||||
}
|
||||
PrintAndLogEx(SUCCESS, "BWM WiFi power save..... " _YELLOW_("%s"), bwm_wifi_ps_name(resp.data.asBytes[0]));
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// hw bwm ble: the module's BLE settings (CMD_PM5_BWM_BLE)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static const char *ble_state_str(uint8_t state) {
|
||||
switch (state) {
|
||||
case 0: return "off";
|
||||
case 1: return "advertising, no client";
|
||||
case 2: return "client connected";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
// esp_power_level_t index <-> dBm: -24 dBm + 3 dB per step, index 15 = +20 dBm
|
||||
static int ble_level_to_dbm(uint8_t level) {
|
||||
if (level == 15) return 20;
|
||||
return -24 + 3 * (int)level;
|
||||
}
|
||||
|
||||
static int ble_dbm_to_level(int dbm) {
|
||||
if (dbm == 20) return 15;
|
||||
if (dbm < -24 || dbm > 18 || ((dbm + 24) % 3) != 0) return -1;
|
||||
return (dbm + 24) / 3;
|
||||
}
|
||||
|
||||
static void ble_print_status(const bwm_ble_status_t *st) {
|
||||
PrintAndLogEx(INFO, "--- " _CYAN_("BWM BLE") " ---------------------------");
|
||||
if (st->enabled == 0xFF) {
|
||||
PrintAndLogEx(INFO, "BLE................ %s " _YELLOW_("(module firmware without the on/off switch)"), ble_state_str(st->state));
|
||||
} else if (st->enabled == 0) {
|
||||
PrintAndLogEx(INFO, "BLE................ " _YELLOW_("off") " (persisted; radio silent, USB/WiFi only)");
|
||||
} else {
|
||||
PrintAndLogEx(INFO, "BLE................ " _GREEN_("on") ", %s", ble_state_str(st->state));
|
||||
}
|
||||
if (st->name[0]) {
|
||||
PrintAndLogEx(INFO, "Name............... " _YELLOW_("%s"), st->name);
|
||||
}
|
||||
if (st->addr[0] != 0xFF || st->addr[5] != 0xFF) {
|
||||
PrintAndLogEx(INFO, "Address............ %02X:%02X:%02X:%02X:%02X:%02X",
|
||||
st->addr[0], st->addr[1], st->addr[2], st->addr[3], st->addr[4], st->addr[5]);
|
||||
}
|
||||
if (st->bonding == 1) {
|
||||
PrintAndLogEx(INFO, "Pairing............ " _GREEN_("required") " (LE Secure Connections, passkey " _YELLOW_("%.6s") ")", st->passkey);
|
||||
if (memcmp(st->passkey, "123456", 6) == 0) {
|
||||
PrintAndLogEx(WARNING, "the passkey is the factory default, change it: " _YELLOW_("hw bwm ble pairing -k <6 digits>"));
|
||||
}
|
||||
} else if (st->bonding == 0) {
|
||||
PrintAndLogEx(INFO, "Pairing............ " _RED_("not required") " - anyone in range can connect and use the device");
|
||||
PrintAndLogEx(HINT, "enable it with " _YELLOW_("hw bwm ble pairing on"));
|
||||
}
|
||||
if (st->bonded_count != 0xFF) {
|
||||
PrintAndLogEx(INFO, "Bonded devices..... %u%s", st->bonded_count, (st->bonded_count == BWM_BLE_BONDED_MAX) ? " (or more)" : "");
|
||||
for (uint8_t i = 0; i < st->bonded_count; i++) {
|
||||
const uint8_t *a = st->bonded[i];
|
||||
PrintAndLogEx(INFO, " [%u] %02X:%02X:%02X:%02X:%02X:%02X (%s)", i,
|
||||
a[0], a[1], a[2], a[3], a[4], a[5], (a[6] == 0) ? "public" : "random");
|
||||
}
|
||||
}
|
||||
if (st->txp_adv != 0xFF && st->txp_conn != 0xFF) {
|
||||
PrintAndLogEx(INFO, "TX power........... advertising %d dBm, connection %d dBm",
|
||||
ble_level_to_dbm(st->txp_adv), ble_level_to_dbm(st->txp_conn));
|
||||
}
|
||||
}
|
||||
|
||||
// One CMD_PM5_BWM_BLE round trip. Prints the status on success unless quiet.
|
||||
static int ble_action(uint8_t action, const uint8_t *arg, size_t alen, bool quiet) {
|
||||
uint8_t payload[1 + 8] = { action };
|
||||
if (alen > sizeof(payload) - 1) {
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
if (alen) {
|
||||
memcpy(&payload[1], arg, alen);
|
||||
}
|
||||
clearCommandBuffer();
|
||||
SendCommandNG(CMD_PM5_BWM_BLE, payload, 1 + alen);
|
||||
PacketResponseNG resp;
|
||||
// Up to a dozen module round trips behind one reply, plus a light-sleep wake.
|
||||
if (WaitForResponseTimeout(CMD_PM5_BWM_BLE, &resp, 8000) == false) {
|
||||
PrintAndLogEx(WARNING, "command timeout (is this a PM5? over BLE, a pairing or on/off change drops your own link)");
|
||||
return PM3_ETIMEOUT;
|
||||
}
|
||||
if (resp.status == PM3_ENOTIMPL) {
|
||||
PrintAndLogEx(WARNING, "firmware built without the BWM link");
|
||||
return resp.status;
|
||||
}
|
||||
if (resp.status != PM3_SUCCESS) {
|
||||
PrintAndLogEx(FAILED, "module refused or did not answer (%s)", (resp.status == PM3_ETIMEOUT) ? "timeout" : "error");
|
||||
return resp.status;
|
||||
}
|
||||
if (resp.length < sizeof(bwm_ble_status_t)) {
|
||||
PrintAndLogEx(FAILED, "short reply (%u bytes)", resp.length);
|
||||
return PM3_EFAILED;
|
||||
}
|
||||
if (quiet == false) {
|
||||
ble_print_status((const bwm_ble_status_t *)resp.data.asBytes);
|
||||
}
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
static int CmdBwmBleStatus(const char *Cmd) {
|
||||
CLIParserContext *ctx;
|
||||
CLIParserInit(&ctx, "hw bwm ble status",
|
||||
"Show the module's BLE settings: on/off, name, address, pairing and passkey,\n"
|
||||
"bonded devices, TX power.",
|
||||
"hw bwm ble status");
|
||||
void *argtable[] = {
|
||||
arg_param_begin,
|
||||
arg_param_end
|
||||
};
|
||||
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
||||
CLIParserFree(ctx);
|
||||
return ble_action(BWM_BLE_ACTION_STATUS, NULL, 0, false);
|
||||
}
|
||||
|
||||
static int ble_set_enable(const char *Cmd, bool on) {
|
||||
CLIParserContext *ctx;
|
||||
CLIParserInit(&ctx, on ? "hw bwm ble on" : "hw bwm ble off",
|
||||
"Switch the module's BLE radio on or off. Persisted on the module, applied at\n"
|
||||
"once. Off means nothing can connect over BLE at all; the PM5 stays reachable\n"
|
||||
"over USB (and WiFi if configured). Needs a BWM firmware with the BLE switch.\n"
|
||||
_YELLOW_("Over BLE, `off` cuts your own connection."),
|
||||
on ? "hw bwm ble on" : "hw bwm ble off");
|
||||
void *argtable[] = {
|
||||
arg_param_begin,
|
||||
arg_param_end
|
||||
};
|
||||
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
||||
CLIParserFree(ctx);
|
||||
uint8_t v = on ? 1 : 0;
|
||||
return ble_action(BWM_BLE_ACTION_ENABLE, &v, 1, false);
|
||||
}
|
||||
|
||||
static int CmdBwmBleOn(const char *Cmd) {
|
||||
return ble_set_enable(Cmd, true);
|
||||
}
|
||||
|
||||
static int CmdBwmBleOff(const char *Cmd) {
|
||||
return ble_set_enable(Cmd, false);
|
||||
}
|
||||
|
||||
static int CmdBwmBlePairing(const char *Cmd) {
|
||||
// Positional sub-action (no dashes): hw bwm ble pairing [on|off] [-k <digits>]
|
||||
char verb[16] = {0};
|
||||
int consumed = 0;
|
||||
sscanf(Cmd, "%15s%n", verb, &consumed);
|
||||
bool on = (strcmp(verb, "on") == 0);
|
||||
bool off = (strcmp(verb, "off") == 0);
|
||||
const char *rest = (on || off) ? Cmd + consumed : Cmd;
|
||||
|
||||
CLIParserContext *ctx;
|
||||
CLIParserInit(&ctx, "hw bwm ble pairing",
|
||||
"Require pairing before a BLE client can use the device. The module ships with\n"
|
||||
"pairing " _RED_("off") ": anyone in range can connect and run commands. With it on, a\n"
|
||||
"client must pair once with the 6-digit passkey (LE Secure Connections, MITM)\n"
|
||||
"and the data characteristic only works over the encrypted link; paired\n"
|
||||
"(bonded) devices reconnect without the key until you forget them.\n"
|
||||
"Both settings persist on the module. A change of on/off restarts the BLE\n"
|
||||
"stack, which " _YELLOW_("drops a connected client - reconnect (and pair) afterwards.") "\n"
|
||||
"The factory passkey is 123456; set your own.",
|
||||
"hw bwm ble pairing on --> require pairing\n"
|
||||
"hw bwm ble pairing on -k 482913 --> require pairing with this passkey\n"
|
||||
"hw bwm ble pairing -k 482913 --> change the passkey only\n"
|
||||
"hw bwm ble pairing off --> open access again");
|
||||
void *argtable[] = {
|
||||
arg_param_begin,
|
||||
arg_str0("k", "key", "<6 digits>", "passkey a client must enter"),
|
||||
arg_param_end
|
||||
};
|
||||
CLIExecWithReturn(ctx, rest, argtable, true);
|
||||
uint8_t key[8] = {0};
|
||||
int keylen = 0;
|
||||
int kres = CLIParamStrToBuf(arg_get_str(ctx, 1), key, sizeof(key) - 1, &keylen);
|
||||
CLIParserFree(ctx);
|
||||
if (kres) {
|
||||
PrintAndLogEx(WARNING, "the passkey must be exactly 6 digits");
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
|
||||
if (keylen) {
|
||||
bool ok = (keylen == 6);
|
||||
for (int i = 0; ok && i < 6; i++) {
|
||||
ok = isdigit(key[i]);
|
||||
}
|
||||
if (ok == false) {
|
||||
PrintAndLogEx(WARNING, "the passkey must be exactly 6 digits");
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
int res = ble_action(BWM_BLE_ACTION_KEY, key, 6, (on || off));
|
||||
if (res != PM3_SUCCESS) {
|
||||
return res;
|
||||
}
|
||||
if (!on && !off) {
|
||||
PrintAndLogEx(SUCCESS, "passkey set");
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
} else if (!on && !off) {
|
||||
PrintAndLogEx(WARNING, "specify " _YELLOW_("on") ", " _YELLOW_("off") " and/or " _YELLOW_("-k <6 digits>"));
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
uint8_t v = on ? 1 : 0;
|
||||
int res = ble_action(BWM_BLE_ACTION_PAIRING, &v, 1, false);
|
||||
if (res == PM3_SUCCESS) {
|
||||
PrintAndLogEx(SUCCESS, "pairing %s; the BLE stack is restarting, a connected client is dropped", on ? _GREEN_("required") : _YELLOW_("not required"));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static int CmdBwmBleForget(const char *Cmd) {
|
||||
CLIParserContext *ctx;
|
||||
CLIParserInit(&ctx, "hw bwm ble forget",
|
||||
"Remove bonded (paired) devices from the module, so they must pair again with\n"
|
||||
"the passkey. Indexes are the ones `hw bwm ble status` lists.",
|
||||
"hw bwm ble forget --all\n"
|
||||
"hw bwm ble forget -i 0");
|
||||
void *argtable[] = {
|
||||
arg_param_begin,
|
||||
arg_int0("i", "idx", "<dec>", "index of the bonded device to remove"),
|
||||
arg_lit0(NULL, "all", "remove all bonded devices"),
|
||||
arg_param_end
|
||||
};
|
||||
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
||||
int idx = arg_get_int_def(ctx, 1, -1);
|
||||
bool all = arg_get_lit(ctx, 2);
|
||||
CLIParserFree(ctx);
|
||||
if ((all && idx >= 0) || (!all && idx < 0)) {
|
||||
PrintAndLogEx(WARNING, "specify either " _YELLOW_("-i <idx>") " or " _YELLOW_("--all"));
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
if (idx > 254) {
|
||||
PrintAndLogEx(WARNING, "index out of range");
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
uint8_t v = all ? 0xFF : (uint8_t)idx;
|
||||
return ble_action(BWM_BLE_ACTION_FORGET, &v, 1, false);
|
||||
}
|
||||
|
||||
static int CmdBwmBleTxPower(const char *Cmd) {
|
||||
CLIParserContext *ctx;
|
||||
CLIParserInit(&ctx, "hw bwm ble txpower",
|
||||
"Set the module's BLE transmit power for advertising and/or connections.\n"
|
||||
"Steps of 3 dB from -24 to 18 dBm, plus 20. Lower saves a little battery and\n"
|
||||
"shrinks the range at which the device can be found. Persisted on the module.",
|
||||
"hw bwm ble txpower -a 0 -c 0 --> 0 dBm for both\n"
|
||||
"hw bwm ble txpower -a -6 --> quieter advertising only");
|
||||
void *argtable[] = {
|
||||
arg_param_begin,
|
||||
arg_int0("a", "adv", "<dBm>", "advertising power"),
|
||||
arg_int0("c", "conn", "<dBm>", "connection power"),
|
||||
arg_param_end
|
||||
};
|
||||
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
||||
bool has_adv = (arg_get_int_count(ctx, 1) > 0);
|
||||
bool has_conn = (arg_get_int_count(ctx, 2) > 0);
|
||||
int adv = arg_get_int_def(ctx, 1, 0);
|
||||
int conn = arg_get_int_def(ctx, 2, 0);
|
||||
CLIParserFree(ctx);
|
||||
if ((has_adv == false) && (has_conn == false)) {
|
||||
PrintAndLogEx(WARNING, "specify " _YELLOW_("-a <dBm>") " and/or " _YELLOW_("-c <dBm>"));
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
int la = has_adv ? ble_dbm_to_level(adv) : 0;
|
||||
int lc = has_conn ? ble_dbm_to_level(conn) : 0;
|
||||
if (la < 0 || lc < 0) {
|
||||
PrintAndLogEx(WARNING, "valid values: -24, -21, ... 15, 18, 20 dBm");
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
int res = PM3_SUCCESS;
|
||||
if (has_adv) {
|
||||
uint8_t v[2] = { 0, (uint8_t)la };
|
||||
res = ble_action(BWM_BLE_ACTION_TXPOWER, v, 2, has_conn);
|
||||
}
|
||||
if (res == PM3_SUCCESS && has_conn) {
|
||||
uint8_t v[2] = { 1, (uint8_t)lc };
|
||||
res = ble_action(BWM_BLE_ACTION_TXPOWER, v, 2, false);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static int CmdHelpBwmBle(const char *Cmd);
|
||||
static command_t BwmBleCommandTable[] = {
|
||||
{"help", CmdHelpBwmBle, AlwaysAvailable, "This help"},
|
||||
{"status", CmdBwmBleStatus, IfBwm, "Show BLE settings: on/off, pairing, bonded devices, TX power"},
|
||||
{"on", CmdBwmBleOn, IfBwm, "Switch BLE on (persisted)"},
|
||||
{"off", CmdBwmBleOff, IfBwm, "Switch BLE off (persisted) - nothing can connect"},
|
||||
{"pairing", CmdBwmBlePairing, IfBwm, "Require pairing with a passkey, set the passkey"},
|
||||
{"forget", CmdBwmBleForget, IfBwm, "Remove bonded devices"},
|
||||
{"txpower", CmdBwmBleTxPower, IfBwm, "Set advertising / connection TX power"},
|
||||
{NULL, NULL, NULL, NULL}
|
||||
};
|
||||
|
||||
static int CmdHelpBwmBle(const char *Cmd) {
|
||||
(void)Cmd;
|
||||
CmdsHelp(BwmBleCommandTable);
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
static int CmdBwmBle(const char *Cmd) {
|
||||
clearCommandBuffer();
|
||||
return CmdsParse(BwmBleCommandTable, Cmd);
|
||||
}
|
||||
|
||||
static command_t BwmCommandTable[] = {
|
||||
{"help", CmdHelpBwm, IfBwm, "This help"},
|
||||
{"autooff", CmdBwmAutoOff, IfBwm, "Toggle auto power-off on USB unplug"},
|
||||
{"help", CmdHelpBwm, AlwaysAvailable, "This help"},
|
||||
{"autooff", CmdBwmAutoOff, IfBwm, "Show/set auto power-off (USB unplug, idle on battery)"},
|
||||
{"ble", CmdBwmBle, IfBwm, "{ BLE: on/off, pairing, bonded devices, TX power... }"},
|
||||
{"charge", CmdBwmCharge, IfBwm, "Enable/disable battery charging (one-shot)"},
|
||||
{"name", CmdBwmName, IfBwm, "Get/set the BWM BLE advertising name"},
|
||||
{"powersave", CmdBwmPowerSave, IfBwm, "Show/set the BWM power-save switch (DFS, light sleep, slow adv)"},
|
||||
{"setcap", CmdBwmSetCap, IfBwm, "Set fuel-gauge design capacity (run once after battery change)"},
|
||||
{"upgrade", CmdBWMUpgrade, IfBwm, "Reflash BWM (ESP32) firmware over the BWM link, no header"},
|
||||
{"vchg", CmdBwmVchg, IfBwm, "Set charger charge-voltage target (default 4100 mV)"},
|
||||
{"wifi", CmdBWMWifi, IfBwm, "Bring up WiFi (STA + TCP server) for a tcp: connection"},
|
||||
{"wifipower", CmdBwmWifiPower, IfBwm, "WiFi fully off, or the modem power-save type (none/min/max)"},
|
||||
{NULL, NULL, NULL, NULL}
|
||||
};
|
||||
|
||||
|
||||
@@ -2033,6 +2033,50 @@ static int CmdPM5QCTest(const char *Cmd) {
|
||||
}
|
||||
|
||||
|
||||
static int CmdPowerSave(const char *Cmd) {
|
||||
// Positional sub-action (no dashes): hw powersave on | off
|
||||
char verb[16] = {0};
|
||||
sscanf(Cmd, "%15s", verb);
|
||||
bool on = (strcmp(verb, "on") == 0);
|
||||
bool off = (strcmp(verb, "off") == 0);
|
||||
|
||||
if (!on && !off) {
|
||||
CLIParserContext *ctx;
|
||||
CLIParserInit(&ctx, "hw powersave",
|
||||
"Toggle the PM5 power-save idle. Default is " _GREEN_("on") ": between commands the\n"
|
||||
"core drops from 288 to 48 MHz with the PLL off, the FPGA clock stopped and the\n"
|
||||
"CPU halted (WFI) until the next interrupt or the 1 ms wake tick. Commands run\n"
|
||||
"at full speed; turn it off to keep the core at 288 MHz and spinning.\n"
|
||||
_YELLOW_("Runtime only:") " resets to on at each boot. See `hw status` for the idle stats.",
|
||||
"hw powersave off --> stay at 288 MHz, no idle sleep\n"
|
||||
"hw powersave on --> re-enable the power-save idle");
|
||||
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;
|
||||
|
||||
clearCommandBuffer();
|
||||
SendCommandNG(CMD_PM5_POWERSAVE, &payload, sizeof(payload));
|
||||
PacketResponseNG resp;
|
||||
if (WaitForResponseTimeout(CMD_PM5_POWERSAVE, &resp, 2500) == false) {
|
||||
PrintAndLogEx(WARNING, "command timeout (is this a PM5?)");
|
||||
return PM3_ETIMEOUT;
|
||||
}
|
||||
if (resp.status != PM3_SUCCESS) {
|
||||
PrintAndLogEx(FAILED, "failed to set power-save idle");
|
||||
return resp.status;
|
||||
}
|
||||
PrintAndLogEx(SUCCESS, "Power-save idle %s.", payload ? _GREEN_("enabled") : _YELLOW_("disabled"));
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
static command_t CommandTable[] = {
|
||||
{"help", CmdHelp, AlwaysAvailable, "This help"},
|
||||
{"-------------", CmdHelp, AlwaysAvailable, "----------------------- " _CYAN_("Operation") " -----------------------"},
|
||||
@@ -2054,6 +2098,7 @@ static command_t CommandTable[] = {
|
||||
{"lcd", CmdLCD, IfPm3Lcd, "Send command/data to LCD"},
|
||||
{"lcdreset", CmdLCDReset, IfPm3Lcd, "Hardware reset LCD"},
|
||||
{"ping", CmdPing, IfPm3Present, "Test if the Proxmark3 is responsive"},
|
||||
{"powersave", CmdPowerSave, IfPm5, "Enable/disable the PM5 power-save idle"},
|
||||
{"readmem", CmdReadmem, IfPm3Present, "Read from MCU flash"},
|
||||
{"reset", CmdReset, IfPm3Present, "Reset the device"},
|
||||
{"setlfdivisor", CmdSetDivisor, IfPm3Lf, "Drive LF antenna at 12MHz / (divisor + 1)"},
|
||||
|
||||
@@ -327,6 +327,13 @@ void FpgaSendCommand(uint16_t cmd, uint16_t v);
|
||||
//-----------------------------------------------------------------------------
|
||||
void FpgaWriteConfWord(uint16_t v);
|
||||
|
||||
#ifdef PM5
|
||||
//-----------------------------------------------------------------------------
|
||||
// True when the last configuration word selected FPGA_MAJOR_MODE_OFF.
|
||||
//-----------------------------------------------------------------------------
|
||||
bool FpgaIsOff(void);
|
||||
#endif
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// enable FPGA internal tracing
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
@@ -15,8 +15,15 @@ bool FpgaIs16BitMsbMode(uint16_t fpga_mode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef PM5
|
||||
static uint16_t s_fpga_conf_word = FPGA_MAJOR_MODE_OFF;
|
||||
#endif
|
||||
|
||||
void FpgaWriteConfWord(uint16_t v) {
|
||||
const int current = FpgaGetCurrent();
|
||||
#ifdef PM5
|
||||
s_fpga_conf_word = v;
|
||||
#endif
|
||||
|
||||
// Keep track of whether or not we should be monitoring the HF field timeout
|
||||
if (current == FPGA_BITSTREAM_HF || current == FPGA_BITSTREAM_HF_15 || current == FPGA_BITSTREAM_HF_FELICA) {
|
||||
@@ -44,6 +51,12 @@ void FpgaWriteConfWord(uint16_t v) {
|
||||
FpgaSendCommand(FPGA_CMD_SET_CONFREG, v);
|
||||
}
|
||||
|
||||
#ifdef PM5
|
||||
bool FpgaIsOff(void) {
|
||||
return (s_fpga_conf_word & FPGA_MAJOR_MODE_MASK) == FPGA_MAJOR_MODE_OFF;
|
||||
}
|
||||
#endif
|
||||
|
||||
void FpgaEnableTracing(void) {
|
||||
FpgaSendCommand(FPGA_CMD_TRACE_ENABLE, 1);
|
||||
}
|
||||
|
||||
@@ -162,26 +162,23 @@ static usbd_desc_handler cdc_desc_handler = {
|
||||
*/
|
||||
static void usb_clock48m_select(usb_clk48_s clk_s) {
|
||||
if (clk_s == USB_CLK_HICK) {
|
||||
/* UNUSED!!!
|
||||
|
||||
// HICK-48 (also makes SCLK-from-HICK 48 MHz), trimmed by the ACC against
|
||||
// the USB SOF to +/-0.25%. Independent of the PLL, so the PLL can be
|
||||
// powered down at idle (armsrc/pm5_power.c) without touching USB.
|
||||
crm_usb_clock_source_select(CRM_USB_CLOCK_SOURCE_HICK);
|
||||
|
||||
// enable the acc calibration ready interrupt
|
||||
crm_periph_clock_enable(CRM_ACC_PERIPH_CLOCK, TRUE);
|
||||
|
||||
// update the c1\c2\c3 value
|
||||
// compare window around the ideal 8000 HICK/6 counts per 1 ms SOF
|
||||
acc_write_c1(7980);
|
||||
acc_write_c2(8000);
|
||||
acc_write_c3(8020);
|
||||
#if (USB_ID == 0)
|
||||
#if (USB_ID == 0)
|
||||
acc_sof_select(ACC_SOF_OTG1);
|
||||
#else
|
||||
#else
|
||||
acc_sof_select(ACC_SOF_OTG2);
|
||||
#endif
|
||||
// open acc calibration
|
||||
#endif
|
||||
acc_calibration_mode_enable(ACC_CAL_HICKTRIM, TRUE);
|
||||
|
||||
*/
|
||||
} else {
|
||||
switch (system_core_clock) {
|
||||
/* 48MHz */
|
||||
@@ -374,7 +371,11 @@ void usb_enable(void) {
|
||||
usb_gpio_config();
|
||||
|
||||
crm_periph_clock_enable(OTG_CLOCK, TRUE); // enable otgfs clock
|
||||
#ifdef AS_BOOTROM
|
||||
usb_clock48m_select(USB_CLK_HEXT); // select usb 48m clcok source
|
||||
#else
|
||||
usb_clock48m_select(USB_CLK_HICK); // crystal-less, survives the idle PLL-off (pm5_power.c)
|
||||
#endif
|
||||
nvic_irq_enable(OTG_IRQ, 0, 0); // enable otgfs irq
|
||||
usbd_init(&otg_core_struct, USB_FULL_SPEED_CORE_ID, USB_ID, &cdc_class_handler, &cdc_desc_handler); // init usb
|
||||
|
||||
|
||||
+151
-14
@@ -6994,7 +6994,9 @@
|
||||
"hf mf sen",
|
||||
"hf mf sen --keep-nonces",
|
||||
"hf mf sen --no-oob",
|
||||
"hf mf sen --reader"
|
||||
"hf mf sen --reader",
|
||||
"hf mf sen --suffix mycard",
|
||||
"hf mf sen --ns"
|
||||
],
|
||||
"offline": false,
|
||||
"options": [
|
||||
@@ -7005,9 +7007,11 @@
|
||||
"--offline-only stop after offline filtering, do not verify online",
|
||||
"--online-confirm only do online confirmation (skip generation if nonces loaded)",
|
||||
"--max-online-candidates <n> abort online phase if total candidates exceed this limit",
|
||||
"--parity-mask <hex> parity filter mask 1..F (default 1 = vetted staticnested_1nt behavior)"
|
||||
"--parity-mask <hex> parity filter mask 1..F (default 1 = vetted staticnested_1nt behavior)",
|
||||
"--suffix <txt> Add this suffix to generated files",
|
||||
"--ns no save to file (--keep-nonces still writes its evidence file)"
|
||||
],
|
||||
"usage": "hf mf sen [-h] [--keep-nonces] [--no-oob] [--reader] [--offline-only] [--online-confirm] [--max-online-candidates <n>] [--parity-mask <hex>]"
|
||||
"usage": "hf mf sen [-h] [--keep-nonces] [--no-oob] [--reader] [--offline-only] [--online-confirm] [--max-online-candidates <n>] [--parity-mask <hex>] [--suffix <txt>] [--ns]"
|
||||
},
|
||||
"hf mf setmod": {
|
||||
"command": "hf mf setmod",
|
||||
@@ -8699,7 +8703,7 @@
|
||||
"--dfname <hex> Certificate application DF name (1-16 bytes)",
|
||||
"--fid <hex> Certificate file ID (1 byte)",
|
||||
"--no-auth Read certificate file without authentication",
|
||||
"--ca <name|cert|pubkey|path|skip> CA input, or `skip` to skip certificate signature validation. Repeat --ca for multiple entries",
|
||||
"--ca <name|cert|pubkey|path|skip> CA input, or `skip` to skip validation",
|
||||
"--keyaid <hex> Key application ID (default: cert app)",
|
||||
"--keyisoid <hex> Key application ISO DF ID (2 bytes)",
|
||||
"--keydfname <hex> Key application DF name (default: cert app)",
|
||||
@@ -8713,15 +8717,17 @@
|
||||
"command": "hf mfdes view",
|
||||
"description": "Print a DESFire card dump file (json)",
|
||||
"notes": [
|
||||
"hf mfdes view -f hf-mfdes-01020304050607-dump.json"
|
||||
"hf mfdes view -f hf-mfdes-01020304050607-dump.json",
|
||||
"hf mfdes view --selftest"
|
||||
],
|
||||
"offline": true,
|
||||
"options": [
|
||||
"-h, --help This help",
|
||||
"-f, --file <fn> Filename of dump",
|
||||
"-v, --verbose Verbose output"
|
||||
"-v, --verbose Verbose output",
|
||||
"--selftest Run the dump parsers self tests and exit"
|
||||
],
|
||||
"usage": "hf mfdes view [-hv] -f <fn>"
|
||||
"usage": "hf mfdes view [-hv] [-f <fn>] [--selftest]"
|
||||
},
|
||||
"hf mfdes write": {
|
||||
"command": "hf mfdes write",
|
||||
@@ -11024,6 +11030,88 @@
|
||||
],
|
||||
"usage": "hw break [-h]"
|
||||
},
|
||||
"hw bwm ble forget": {
|
||||
"command": "hw bwm ble forget",
|
||||
"description": "Remove bonded (paired) devices from the module, so they must pair again with the passkey. Indexes are the ones `hw bwm ble status` lists.",
|
||||
"notes": [
|
||||
"hw bwm ble forget --all",
|
||||
"hw bwm ble forget -i 0"
|
||||
],
|
||||
"offline": false,
|
||||
"options": [
|
||||
"-h, --help This help",
|
||||
"-i, --idx <dec> index of the bonded device to remove",
|
||||
"--all remove all bonded devices"
|
||||
],
|
||||
"usage": "hw bwm ble forget [-h] [-i <dec>] [--all]"
|
||||
},
|
||||
"hw bwm ble help": {
|
||||
"command": "hw bwm ble help",
|
||||
"description": "help This help --------------------------------------------------------------------------------------- hw bwm ble status available offline: no Show the module's BLE settings: on/off, name, address, pairing and passkey, bonded devices, TX power.",
|
||||
"notes": [
|
||||
"hw bwm ble status"
|
||||
],
|
||||
"offline": true,
|
||||
"options": [
|
||||
"-h, --help This help"
|
||||
],
|
||||
"usage": "hw bwm ble status [-h]"
|
||||
},
|
||||
"hw bwm ble off": {
|
||||
"command": "hw bwm ble off",
|
||||
"description": "Switch the module's BLE radio on or off. Persisted on the module, applied at once. Off means nothing can connect over BLE at all; the PM5 stays reachable over USB (and WiFi if configured). Needs a BWM firmware with the BLE switch. Over BLE, `off` cuts your own connection.",
|
||||
"notes": [
|
||||
"hw bwm ble off"
|
||||
],
|
||||
"offline": false,
|
||||
"options": [
|
||||
"-h, --help This help"
|
||||
],
|
||||
"usage": "hw bwm ble off [-h]"
|
||||
},
|
||||
"hw bwm ble on": {
|
||||
"command": "hw bwm ble on",
|
||||
"description": "Switch the module's BLE radio on or off. Persisted on the module, applied at once. Off means nothing can connect over BLE at all; the PM5 stays reachable over USB (and WiFi if configured). Needs a BWM firmware with the BLE switch. Over BLE, `off` cuts your own connection.",
|
||||
"notes": [
|
||||
"hw bwm ble on"
|
||||
],
|
||||
"offline": false,
|
||||
"options": [
|
||||
"-h, --help This help"
|
||||
],
|
||||
"usage": "hw bwm ble on [-h]"
|
||||
},
|
||||
"hw bwm ble pairing": {
|
||||
"command": "hw bwm ble pairing",
|
||||
"description": "Require pairing before a BLE client can use the device. The module ships with pairing off: anyone in range can connect and run commands. With it on, a client must pair once with the 6-digit passkey (LE Secure Connections, MITM) and the data characteristic only works over the encrypted link; paired (bonded) devices reconnect without the key until you forget them. Both settings persist on the module. A change of on/off restarts the BLE stack, which drops a connected client - reconnect (and pair) afterwards. The factory passkey is 123456; set your own.",
|
||||
"notes": [
|
||||
"hw bwm ble pairing on -> require pairing",
|
||||
"hw bwm ble pairing on -k 482913 -> require pairing with this passkey",
|
||||
"hw bwm ble pairing -k 482913 -> change the passkey only",
|
||||
"hw bwm ble pairing off -> open access again"
|
||||
],
|
||||
"offline": false,
|
||||
"options": [
|
||||
"-h, --help This help",
|
||||
"-k, --key <6 digits> passkey a client must enter"
|
||||
],
|
||||
"usage": "hw bwm ble pairing [-h] [-k <6 digits>]"
|
||||
},
|
||||
"hw bwm ble txpower": {
|
||||
"command": "hw bwm ble txpower",
|
||||
"description": "Set the module's BLE transmit power for advertising and/or connections. Steps of 3 dB from -24 to 18 dBm, plus 20. Lower saves a little battery and shrinks the range at which the device can be found. Persisted on the module.",
|
||||
"notes": [
|
||||
"hw bwm ble txpower -a 0 -c 0 -> 0 dBm for both",
|
||||
"hw bwm ble txpower -a -6 -> quieter advertising only"
|
||||
],
|
||||
"offline": false,
|
||||
"options": [
|
||||
"-h, --help This help",
|
||||
"-a, --adv <dBm> advertising power",
|
||||
"-c, --conn <dBm> connection power"
|
||||
],
|
||||
"usage": "hw bwm ble txpower [-h] [-a <dBm>] [-c <dBm>]"
|
||||
},
|
||||
"hw bwm charge": {
|
||||
"command": "hw bwm charge",
|
||||
"description": "Enable or disable BWM battery charging by clearing/setting the AW32001E charge-enable bit (CEB, REG01[3]). PM5 only. One-shot: the charger watchdog reverts this after ~160 s unless serviced, so charging may stop on its own. Use to nudge a top-up.",
|
||||
@@ -11039,16 +11127,22 @@
|
||||
},
|
||||
"hw bwm help": {
|
||||
"command": "hw bwm help",
|
||||
"description": "help This help --------------------------------------------------------------------------------------- hw bwm autooff available offline: no Toggle automatic power-off when the PM5 is unplugged from USB (BWM only). Default is on. When on, the board powers itself down ~10s after USB is removed, so a BWM-equipped PM5 doesn't silently drain the battery. Button power-on is unaffected. Disable for standalone/BLE use on battery. Runtime only: resets to on at each boot.",
|
||||
"description": "help This help --------------------------------------------------------------------------------------- hw bwm autooff available offline: no Show or set automatic power-off (PM5 only). Two triggers: - USB was present and the cable is pulled: off at once, so a BWM-equipped PM5 doesn't silently drain the battery; --unplug off only restarts the idle clock instead; - on battery with no command, button press or BLE/WiFi client for --idle seconds: off (0 = never, the default). Button power-on is unaffected. Stored on the BWM; without a module, defaults.",
|
||||
"notes": [
|
||||
"hw bwm autooff off -> disable auto power-off",
|
||||
"hw bwm autooff on -> re-enable auto power-off"
|
||||
"hw bwm autooff -> show the current state",
|
||||
"hw bwm autooff off -> disable both triggers",
|
||||
"hw bwm autooff on -> re-enable",
|
||||
"hw bwm autooff --idle 300 -> also off after 5 min idle on battery",
|
||||
"hw bwm autooff --idle 0 -> USB-unplug trigger only (default)",
|
||||
"hw bwm autooff --unplug off --idle 300 -> survive the unplug, off 5 min idle later"
|
||||
],
|
||||
"offline": true,
|
||||
"options": [
|
||||
"-h, --help This help"
|
||||
"-h, --help This help",
|
||||
"-i, --idle <sec> idle timeout on battery in seconds, 0 = never",
|
||||
"-u, --unplug <on|off> power off when USB is pulled (default on)"
|
||||
],
|
||||
"usage": "hw bwm autooff [-h]"
|
||||
"usage": "hw bwm autooff [-h] [-i <sec>] [-u <on|off>]"
|
||||
},
|
||||
"hw bwm name": {
|
||||
"command": "hw bwm name",
|
||||
@@ -11064,6 +11158,20 @@
|
||||
],
|
||||
"usage": "hw bwm name [-h] [--set <name>]"
|
||||
},
|
||||
"hw bwm powersave": {
|
||||
"command": "hw bwm powersave",
|
||||
"description": "Show or set the BWM (ESP32) power-save switch, stored on the BWM in NVS. Default is on: the ESP scales its clock down, light-sleeps between link traffic, and after 30 s of fast advertising (boot, disconnect) advertises once a second. off pins it at full clock, no sleep, fast advertising throughout. Applies at once and survives reboots. PM5 only.",
|
||||
"notes": [
|
||||
"hw bwm powersave -> show the current state",
|
||||
"hw bwm powersave off -> stock always-on behaviour",
|
||||
"hw bwm powersave on -> re-enable power saving"
|
||||
],
|
||||
"offline": false,
|
||||
"options": [
|
||||
"-h, --help This help"
|
||||
],
|
||||
"usage": "hw bwm powersave [-h]"
|
||||
},
|
||||
"hw bwm setcap": {
|
||||
"command": "hw bwm setcap",
|
||||
"description": "Program the BWM fuel gauge (BQ27427) Design Capacity for the fitted cell. Run ONCE after fitting or replacing the battery. This triggers a gauge config-update; do not run it repeatedly, as that disrupts the Impedance Track learning cycle. PM5 only.",
|
||||
@@ -11125,6 +11233,22 @@
|
||||
],
|
||||
"usage": "hw bwm wifi [-h] [--ssid <ssid>] [--pwd <pwd>] [--port <dec>] [--hostname <name>]"
|
||||
},
|
||||
"hw bwm wifipower": {
|
||||
"command": "hw bwm wifipower",
|
||||
"description": "Show or set how much power the BWM (ESP32) spends on WiFi. off turns WiFi fully off (BLE-only, persisted; the default state, same as `hw bwm wifi stop`). Bring it back with hw bwm wifi --ssid <ssid> --pwd <pwd>. The other three set the modem power-save type while WiFi is up, stored on the BWM: min (default) sleeps between DTIM beacons, max for the whole listen interval, none never. Independent of hw bwm powersave. PM5 only.",
|
||||
"notes": [
|
||||
"hw bwm wifipower -> show whether WiFi is up and the power-save type",
|
||||
"hw bwm wifipower off -> WiFi fully off, BLE-only",
|
||||
"hw bwm wifipower none -> modem always on while WiFi is up",
|
||||
"hw bwm wifipower min -> ESP-IDF default (DTIM sleep)",
|
||||
"hw bwm wifipower max -> deepest modem sleep"
|
||||
],
|
||||
"offline": false,
|
||||
"options": [
|
||||
"-h, --help This help"
|
||||
],
|
||||
"usage": "hw bwm wifipower [-h]"
|
||||
},
|
||||
"hw connect": {
|
||||
"command": "hw connect",
|
||||
"description": "Connects to a Proxmark3 device via specified serial port. Baudrate here is only for physical UART or UART-BT, NOT for USB-CDC or blue shark add-on",
|
||||
@@ -11255,6 +11379,19 @@
|
||||
],
|
||||
"usage": "hw ping [-h] [-l <dec>]"
|
||||
},
|
||||
"hw powersave": {
|
||||
"command": "hw powersave",
|
||||
"description": "Toggle the PM5 power-save idle. Default is on: between commands the core drops from 288 to 48 MHz with the PLL off, the FPGA clock stopped and the CPU halted (WFI) until the next interrupt or the 1 ms wake tick. Commands run at full speed; turn it off to keep the core at 288 MHz and spinning. Runtime only: resets to on at each boot. See `hw status` for the idle stats.",
|
||||
"notes": [
|
||||
"hw powersave off -> stay at 288 MHz, no idle sleep",
|
||||
"hw powersave on -> re-enable the power-save idle"
|
||||
],
|
||||
"offline": false,
|
||||
"options": [
|
||||
"-h, --help This help"
|
||||
],
|
||||
"usage": "hw powersave [-h]"
|
||||
},
|
||||
"hw qc_pm5": {
|
||||
"command": "hw qc_pm5",
|
||||
"description": "QC Test for the PM5",
|
||||
@@ -16645,8 +16782,8 @@
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
"commands_extracted": 926,
|
||||
"commands_extracted": 935,
|
||||
"extracted_by": "PM3Help2JSON v1.00",
|
||||
"extracted_on": "2026-09-22T14:39:12+00:00"
|
||||
"extracted_on": "2026-09-24T08:32:19+00:00"
|
||||
}
|
||||
}
|
||||
|
||||
+19
-1
@@ -1029,6 +1029,7 @@ Check column "offline" for their availability.
|
||||
|`hw lcd `|N |`Send command/data to LCD`
|
||||
|`hw lcdreset `|N |`Hardware reset LCD`
|
||||
|`hw ping `|N |`Test if the Proxmark3 is responsive`
|
||||
|`hw powersave `|N |`Enable/disable the PM5 power-save idle`
|
||||
|`hw readmem `|N |`Read from MCU flash`
|
||||
|`hw reset `|N |`Reset the device`
|
||||
|`hw setlfdivisor `|N |`Drive LF antenna at 12MHz / (divisor + 1)`
|
||||
@@ -1047,13 +1048,30 @@ Check column "offline" for their availability.
|
||||
|command |offline |description
|
||||
|------- |------- |-----------
|
||||
|`hw bwm help `|Y |`This help`
|
||||
|`hw bwm autooff `|N |`Toggle auto power-off on USB unplug`
|
||||
|`hw bwm autooff `|N |`Show/set auto power-off (USB unplug, idle on battery)`
|
||||
|`hw bwm charge `|N |`Enable/disable battery charging (one-shot)`
|
||||
|`hw bwm name `|N |`Get/set the BWM BLE advertising name`
|
||||
|`hw bwm powersave `|N |`Show/set the BWM power-save switch (DFS, light sleep, slow adv)`
|
||||
|`hw bwm setcap `|N |`Set fuel-gauge design capacity (run once after battery change)`
|
||||
|`hw bwm upgrade `|N |`Reflash BWM (ESP32) firmware over the BWM link, no header`
|
||||
|`hw bwm vchg `|N |`Set charger charge-voltage target (default 4100 mV)`
|
||||
|`hw bwm wifi `|N |`Bring up WiFi (STA + TCP server) for a tcp: connection`
|
||||
|`hw bwm wifipower `|N |`WiFi fully off, or the modem power-save type (none/min/max)`
|
||||
|
||||
|
||||
### hw bwm ble
|
||||
|
||||
{ BLE: on/off, pairing, bonded devices, TX power... }
|
||||
|
||||
|command |offline |description
|
||||
|------- |------- |-----------
|
||||
|`hw bwm ble help `|Y |`This help`
|
||||
|`hw bwm ble status `|N |`Show BLE settings: on/off, pairing, bonded devices, TX power`
|
||||
|`hw bwm ble on `|N |`Switch BLE on (persisted)`
|
||||
|`hw bwm ble off `|N |`Switch BLE off (persisted) - nothing can connect`
|
||||
|`hw bwm ble pairing `|N |`Require pairing with a passkey, set the passkey`
|
||||
|`hw bwm ble forget `|N |`Remove bonded devices`
|
||||
|`hw bwm ble txpower `|N |`Set advertising / connection TX power`
|
||||
|
||||
|
||||
### lf
|
||||
|
||||
+60
-1
@@ -919,8 +919,30 @@ typedef struct {
|
||||
#define CMD_PM5_BWM_SET_CAP 0x0179
|
||||
// PM5, enable/disable BWM battery charging (AW32001E CEB). Used by `hw bwm charge`.
|
||||
#define CMD_PM5_BWM_CHARGE_EN 0x017A
|
||||
// PM5, toggle automatic power-off on USB unplug. Used by `hw bwm autooff`.
|
||||
// PM5, automatic power-off: USB unplug, and idle on battery. Used by `hw bwm autooff`.
|
||||
// req: [action:u8][enabled:u8][idle_s:u32 LE][unplug:u8, optional] (0xFF /
|
||||
// 0xFFFFFFFF = keep); a 1-byte req is the old enable flag. resp:
|
||||
// bwm_autooff_status_t. Persisted on the BWM.
|
||||
#define CMD_PM5_BWM_AUTOOFF 0x017B
|
||||
// Both actions are non-zero: an old firmware reads byte 0 as the enable flag,
|
||||
// so a new client can only ever leave it enabled.
|
||||
#define BWM_AUTOOFF_ACTION_GET 0x01
|
||||
#define BWM_AUTOOFF_ACTION_SET 0x02
|
||||
#define BWM_AUTOOFF_KEEP_U8 0xFF
|
||||
#define BWM_AUTOOFF_KEEP_U32 0xFFFFFFFFUL
|
||||
#define BWM_AUTOOFF_IDLE_MAX_S (7UL * 24 * 3600) // ms tick wraps at 49 days
|
||||
// Reply of CMD_PM5_BWM_AUTOOFF: the setting plus the live inputs of the idle decision.
|
||||
typedef struct {
|
||||
uint8_t enabled;
|
||||
uint32_t idle_s; // 0 = idle trigger off
|
||||
uint32_t idle_now_s; // since the last interaction
|
||||
uint8_t link; // tracked wireless client (LINK_STATE broadcast)
|
||||
uint8_t ble_live; // module's answer right now: 0 off 1 advertising 2 connected, 0xFF no answer
|
||||
uint8_t usb; // VUSB now
|
||||
uint8_t usb_seen; // VUSB seen since boot (arms the unplug trigger)
|
||||
uint8_t persisted; // 1 = the setting was saved on the module, 0 = module absent or too old
|
||||
uint8_t unplug; // 1 = power off when USB is pulled (default); 0 = an unplug only restarts the idle clock
|
||||
} PACKED bwm_autooff_status_t;
|
||||
#define CMD_PM5_BWM_WIFI 0x017C
|
||||
#define CMD_PM5_BWM_SET_VCHG 0x017D
|
||||
#define CMD_PM5_BWM_ESP_OTA 0x017E
|
||||
@@ -928,6 +950,43 @@ typedef struct {
|
||||
#define BWM_BLE_NAME_ACTION_GET 0x00 // resp: current BLE device name string
|
||||
#define BWM_BLE_NAME_ACTION_SET 0x01 // req: name bytes follow the action byte; BWM reboots to apply
|
||||
#define BWM_BLE_NAME_MAX_LEN 15 // usable chars; ESP name buffer is 16 incl NUL
|
||||
// PM5, toggle the power-save idle (48 MHz + WFI when idle). Used by `hw powersave`.
|
||||
#define CMD_PM5_POWERSAVE 0x0180
|
||||
// PM5, BWM power-save switch on the ESP (DFS, light sleep, low-duty advertising;
|
||||
// persisted on the BWM). Used by `hw bwm powersave`.
|
||||
#define CMD_PM5_BWM_POWERSAVE 0x0181 // payload: [action:u8][state:u8 if SET]; resp: u8 applied state
|
||||
#define BWM_POWERSAVE_ACTION_GET 0x00
|
||||
#define BWM_POWERSAVE_ACTION_SET 0x01
|
||||
// PM5, BWM WiFi modem power-save type (persisted on the ESP). Used by `hw bwm wifipower`.
|
||||
#define CMD_PM5_BWM_WIFI_PS 0x0182 // payload: [action:u8][mode:u8 if SET]; resp: [mode:u8][wifi_state:u8, 0xFF = WiFi off]
|
||||
#define BWM_WIFI_PS_ACTION_GET 0x00
|
||||
#define BWM_WIFI_PS_ACTION_SET 0x01
|
||||
#define BWM_WIFI_PS_NONE 0 // modem never sleeps
|
||||
#define BWM_WIFI_PS_MIN 1 // sleeps between DTIM beacons (ESP-IDF default)
|
||||
#define BWM_WIFI_PS_MAX 2 // sleeps for the listen interval
|
||||
#define BWM_WIFI_STATE_OFF 0xFF // wifi_state byte: WiFi stack down (BLE-only)
|
||||
// PM5, BWM BLE settings (`hw bwm ble`). req: [action:u8][args]; resp: bwm_ble_status_t
|
||||
// on success. Unknown fields (module firmware without the command) read 0xFF.
|
||||
#define CMD_PM5_BWM_BLE 0x0183
|
||||
#define BWM_BLE_ACTION_STATUS 0x00
|
||||
#define BWM_BLE_ACTION_ENABLE 0x01 // [on:u8], persisted on the module
|
||||
#define BWM_BLE_ACTION_PAIRING 0x02 // [on:u8], persisted; restarts the stack (drops a BLE client)
|
||||
#define BWM_BLE_ACTION_KEY 0x03 // [6 ASCII digits], persisted
|
||||
#define BWM_BLE_ACTION_FORGET 0x04 // [idx:u8, 0xFF = all bonded devices]
|
||||
#define BWM_BLE_ACTION_TXPOWER 0x05 // [type:u8 0 adv 1 conn][level:u8 esp_power_level_t 0..15, -24 dBm + 3/step, 15 = 20 dBm]
|
||||
#define BWM_BLE_BONDED_MAX 8
|
||||
typedef struct {
|
||||
uint8_t enabled; // persisted switch
|
||||
uint8_t state; // 0 off, 1 advertising, 2 client connected
|
||||
uint8_t bonding; // 1 = pairing with passkey required, SPP characteristic encrypted
|
||||
char passkey[6]; // ASCII digits, not NUL-terminated
|
||||
uint8_t txp_adv; // esp_power_level_t index
|
||||
uint8_t txp_conn;
|
||||
uint8_t addr[6]; // as the module reports it (big-endian display order)
|
||||
char name[16]; // NUL-padded
|
||||
uint8_t bonded_count;
|
||||
uint8_t bonded[BWM_BLE_BONDED_MAX][7]; // addr[6] + type, first bonded_count valid
|
||||
} PACKED bwm_ble_status_t;
|
||||
#define BWM_OTA_ACTION_BEGIN 0x00
|
||||
#define BWM_OTA_ACTION_WRITE 0x01
|
||||
#define BWM_OTA_ACTION_END 0x02
|
||||
|
||||
Reference in New Issue
Block a user