fix: bound Companion help writes and refresh runtime fixtures

This commit is contained in:
mikecarper committed 2026-10-20 12:31:48 -07:00
1 parent b125806833
commit 2fbc075aaa
16 files changed
+754 -112

No files matched your search

+79 -55
View File
@@ -10320,7 +10320,8 @@ void MyMesh::handleTerminalCommand(char* command, bool reader_prompt,
terminalOutput().printf(" -> Companion %s (protocol %u, build %s)\r\n",
FIRMWARE_VERSION, (unsigned)FIRMWARE_VER_CODE, FIRMWARE_BUILD_DATE);
} else if (strcmp(command, "help") == 0) {
terminalOutput().print(
auto& output = terminalOutput();
output.print(
"Commands:\r\n"
" stats-core / stats-radio / stats-radio-diag / stats-packets\r\n"
" get prv.key (if export enabled)\r\n"
@@ -10330,48 +10331,59 @@ void MyMesh::handleTerminalCommand(char* command, bool reader_prompt,
#else
" get password (no admin password)\r\n"
#endif
);
output.print(
" erase (settings and identity)\r\n"
" board\r\n"
" version\r\n"
" get storage.layout\r\n");
#if MESH_ENABLE_TX_BEACONS
terminalOutput().print(" get beacon [slot] / set beacon <slot> <meshcore|meshtext> ...\r\n"
output.print(
" get beacon [slot] / set beacon <slot> <meshcore|meshtext> ...\r\n"
" delete beacon <slot|all> / set beacon.interval <12|24|36|48> (default 24h)\r\n");
#endif
#if MESH_ENABLE_MESHTEXT_CHAT
terminalOutput().print(" get mt.status / get mt.region / get mt.slot / get mt.hops\r\n"
output.print(
" get mt.status / get mt.region / get mt.slot / get mt.hops\r\n"
" get mt.node / get mt.shortname\r\n"
" get mt.rebroadcast / set mt.rebroadcast <on|off> (saved; default off)\r\n"
" Relay decoded channel text only; adds 5 packet airtimes before TX\r\n"
" Relay decoded channel text only; adds 5 packet airtimes before TX\r\n");
output.print(
" set mt.region <auto|us|eu868|eu433|anz>\r\n"
" set mt.slot <0|1-based frequency slot>\r\n"
" set mt.hops <0..7> (saved; default 7)\r\n"
" set mt.node <!8hex|default> (saved; use a unique ID)\r\n"
" set mt.node <!8hex|default> (saved; use a unique ID)\r\n");
output.print(
" set mt.shortname <1..4 UTF-8 characters|default> (saved; NodeInfo uses 4 bytes)\r\n"
" send mt.nodeinfo <channel slot> (GPS-free; also automatic after first chat)\r\n"
" set mt.preset <longfast|shortturbo|...> (saved manual radio2)\r\n"
" set mt.preset <longfast|shortturbo|...> (saved manual radio2)\r\n");
output.print(
" set mt.channel <slot> <mt-name> <32/64 hex digits|AQ==>\r\n"
" get mt.channel <slot> / delete mt.channel <slot>\r\n"
" Join #mt-longfast or #mt-shortturbo in the app for automatic radio2\r\n");
#endif
#if defined(NRF52_PLATFORM) && defined(EXTRAFS) && !defined(QSPIFLASH)
terminalOutput().print(" get flash.health\r\n");
output.print(" get flash.health\r\n");
#endif
#if COMPANION_FEATURE_READER
terminalOutput().print(" get reader <chapter>[:<verse>[-<last>]] [page] (World English Bible, offline)\r\n");
output.print(" get reader <chapter>[:<verse>[-<last>]] [page] (World English Bible, offline)\r\n");
#endif
terminalOutput().print(" get/set tx.media auto|radio|radio2|both|off (binary media)\r\n"
output.print(
" get/set tx.media auto|radio|radio2|both|off (binary media)\r\n"
" get/set tx.ssh auto|radio|radio2|both|off (tagged tunnel data)\r\n");
#if MESH_ENABLE_ETHERNET_TUNNEL
terminalOutput().print(" get ethernet / get ethernet.radio\r\n"
output.print(
" get ethernet / get ethernet.radio\r\n"
" set ethernet.radio <MHz>,<kHz>,<SF>,<CR>[,<preamble>]\r\n"
" set ethernet on|off (saved; trusted-LAN KISS TCP 5003, radio2 only)\r\n");
#endif
#if defined(OTA_RAK15002_SD_STORE) && defined(ETHERNET_USE_RAK13800)
terminalOutput().print(" get eth / set eth on|off (physical RAK13800; default off, this boot)\r\n"
output.print(
" get eth / set eth on|off (physical RAK13800; default off, this boot)\r\n"
" get sdcard [free] / ota cache [on|off] (RAK15002; mutually exclusive with Ethernet)\r\n");
#endif
terminalOutput().print(" get pwrmgt.bootreason\r\n"
output.print(
" get pwrmgt.bootreason\r\n"
#if COMPANION_FEATURE_MEMORY_DIAGNOSTICS
" memory\r\n"
#endif
@@ -10382,32 +10394,40 @@ void MyMesh::handleTerminalCommand(char* command, bool reader_prompt,
#if defined(PIN_VIBRATION) || defined(HAS_DRV2605)
" get/set vibration on|off\r\n"
#endif
#if COMPANION_FEATURE_NOTIFICATIONS
);
#if COMPANION_FEATURE_NOTIFICATIONS
output.print(
" get notify / get notify.rules / get notify.gpio.pins\r\n"
" set notify.{sound|vibration|led|screen|gpio} on|off\r\n"
" set notify.<output> <all|contact:key|room:key|channel:slot> <pattern>\r\n"
" set notify.<output> <all|contact:key|room:key|channel:slot> <pattern>\r\n");
output.print(
" set notify.remote <contact:key|room:key> on|off (!notify, max 15s, no GPIO)\r\n"
" notify.test <target> / notify.stop / notify.delete <target>\r\n"
#endif
" notify.test <target> / notify.stop / notify.delete <target>\r\n");
#endif
output.print(
" get/set display.inbox <history|pending|unread>\r\n"
" get/set display.touch <on|off> (this boot)\r\n"
" get public.key\r\n"
" set {name|lat|lon|freq|tx|af} {value}\r\n"
" get wifi|espnow|2.4ghz\r\n"
" set wifi|espnow on|off [force] (force only with off)\r\n"
" set wifi|espnow on|off [force] (force only with off)\r\n");
output.print(
" set 2.4ghz on [all|force]|off [force] (this boot)\r\n"
" get/set bluetooth.name <name|default>\r\n"
" get/set bluetooth.name <name|default>\r\n");
#if defined(BLE_PIN_CODE)
output.print(
" get bluetooth (alias: get ble)\r\n"
" set bluetooth <on|off> [force] (saved; force only with off)\r\n"
" get bluetooth.mac\r\n"
" set bluetooth.mac <address|random|random-every-boot|random-after-connect|default>\r\n"
" get/set bluetooth.stealth on|off\r\n"
" get/set bluetooth.stealth on|off\r\n");
#endif
output.print(
#if MESH_ENABLE_LOST_REPLY
" get/set lost.reply <off|no|yes>\r\n"
#endif
" set pin <0-999999>\r\n" " powersaving [on|off]\r\n"
" set pin <0-999999>\r\n"
" powersaving [on|off]\r\n"
#if MESH_USB_LOGGING_AVAILABLE
" get/set usb.logging <on|off|stream> [reboot]\r\n"
" get/set usb.debug <on|off>\r\n"
@@ -10419,54 +10439,56 @@ void MyMesh::handleTerminalCommand(char* command, bool reader_prompt,
);
#if defined(ESP32) && defined(WIFI_SSID)
#if defined(COMPANION_RADIO_FULL)
terminalOutput().print(" start ota [ap] (WiFi firmware update on port 8080)\r\n");
terminalOutput().print(" stop ota\r\n");
output.print(" start ota [ap] (WiFi firmware update on port 8080)\r\n");
output.print(" stop ota\r\n");
#endif
#if defined(COMPANION_EXCLUSIVE_WIFI_BLE)
terminalOutput().print(" get companion.transport\r\n");
terminalOutput().print(" set companion.transport <wifi|ble>\r\n");
output.print(" get companion.transport\r\n");
output.print(" set companion.transport <wifi|ble>\r\n");
#endif
terminalOutput().print(" get wifi.powersave\r\n");
terminalOutput().print(" set wifi.powersave <none|min|max>\r\n");
terminalOutput().print(" get wifi.protocol\r\n");
terminalOutput().print(" get wifi.status (connection, saved/boot/live settings)\r\n");
terminalOutput().print(" set wifi.protocol <bg|bgn> (saved; reboot required)\r\n");
output.print(" get wifi.powersave\r\n");
output.print(" set wifi.powersave <none|min|max>\r\n");
output.print(" get wifi.protocol\r\n");
output.print(" get wifi.status (connection, saved/boot/live settings)\r\n");
output.print(" set wifi.protocol <bg|bgn> (saved; reboot required)\r\n");
#if !defined(COMPANION_EXCLUSIVE_WIFI_BLE)
terminalOutput().print(" get wifi.mode\r\n");
terminalOutput().print(" set wifi.mode <normal|wifi-only> (saved; reboot required)\r\n");
output.print(" get wifi.mode\r\n");
output.print(" set wifi.mode <normal|wifi-only> (saved; reboot required)\r\n");
#endif
#ifdef WITH_WEBCONFIG
terminalOutput().print(" get wifi.{ssid|pwd}\r\n");
terminalOutput().print(" set wifi.ssid <network name>\r\n");
terminalOutput().print(" set wifi.pwd <password>\r\n");
terminalOutput().print(" get webui\r\n");
terminalOutput().print(" set webui <on|off>\r\n");
terminalOutput().print(" start webconfig [ap]\r\n");
terminalOutput().print(" stop webconfig\r\n");
output.print(" get wifi.{ssid|pwd}\r\n");
output.print(" set wifi.ssid <network name>\r\n");
output.print(" set wifi.pwd <password>\r\n");
output.print(" get webui\r\n");
output.print(" set webui <on|off>\r\n");
output.print(" start webconfig [ap]\r\n");
output.print(" stop webconfig\r\n");
#endif
#endif
#if defined(MESH_PRIMARY_ESPNOW) && MESH_PRIMARY_ESPNOW
terminalOutput().print(" get espnow.channel\r\n");
terminalOutput().print(" set espnow.channel <1-13>\r\n");
output.print(" get espnow.channel\r\n");
output.print(" set espnow.channel <1-13>\r\n");
#endif
// Concise help and grouped Print calls preserve small-board flash space.
terminalOutput().print(
// Keep native USB producer writes within 256 bytes without changing help text.
output.print(
" get radio.rxps.config\r\n"
" get/set radio.rxps <off|on|level 1-10 [preamble 16|32]|rx_us sleep_us>\r\n"
" get/set radio.cad <on|off>\r\n"
" set radio.cad timings <scan_ms|auto> <retry_ms|auto> <max_ms|auto>\r\n"
" get/set radio.rxgain <on|off>\r\n"
" get/set radio.rxgain <on|off>\r\n");
output.print(
" get/set radio.fem.rxgain <on|off>\r\n"
" get/set radio.fem.txgain <on|off>\r\n"
" get/set flood.retry.count <0-15>\r\n"
" get/set flood.retry.path <0-63|off>\r\n"
" get/set flood.retry.group.path <0-63|off>\r\n"
" get/set flood.retry.advert <on|off>\r\n"
" card\r\n"
" card\r\n");
output.print(
" import <meshcore://card>\r\n"
" clock\r\n"
" time <epoch-seconds>\r\n");
terminalOutput().print(
output.print(
" list [n]\r\n"
" show [adverts|channels|emergency] [on|off]\r\n"
" to [name|prefix]\r\n"
@@ -10474,7 +10496,7 @@ void MyMesh::handleTerminalCommand(char* command, bool reader_prompt,
" send <text>\r\n"
" login <admin-password>\r\n"
" cmd <remote-command>\r\n");
terminalOutput().print(
output.print(
" trace [name|prefix]\r\n"
" trace path <1|2|4> <prefixes...>\r\n"
" advert\r\n"
@@ -10483,26 +10505,28 @@ void MyMesh::handleTerminalCommand(char* command, bool reader_prompt,
" channels\r\n"
" channel <name-or-slot> <text>\r\n");
#if defined(NRF52_PLATFORM) && defined(EXTRAFS) && !defined(QSPIFLASH)
terminalOutput().print(" repair extrafs (erases/rebuilds internal ExtraFS)\r\n");
terminalOutput().print(" scan extrafs erase (USB only; reboot for destructive page test)\r\n");
output.print(" repair extrafs (erases/rebuilds internal ExtraFS)\r\n");
output.print(" scan extrafs erase (USB only; reboot for destructive page test)\r\n");
#endif
#if COMPANION_FEATURE_TEMP_RADIO || COMPANION_FEATURE_OTA_CLI
#if COMPANION_FEATURE_TEMP_RADIO
terminalOutput().print(" tempradio [freq,bw,sf,cr,minutes]\r\n");
terminalOutput().print(" normalradio\r\n");
output.print(" tempradio [freq,bw,sf,cr,minutes]\r\n");
output.print(" normalradio\r\n");
#endif
#if COMPANION_FEATURE_OTA_CLI
terminalOutput().print(" ota {status|ls|announce|folder|config|...}\r\n");
output.print(" ota {status|ls|announce|folder|config|...}\r\n");
#endif
#endif
terminalOutput().print(" reboot\r\n" " ver\r\n");
output.print(
" reboot\r\n"
" ver\r\n");
#if defined(NRF52_PLATFORM)
terminalOutput().println(" uf2reset");
output.println(" uf2reset");
#endif
if (_terminal_mode) {
terminalOutput().print(" +++MESHCORE-TERM-STOP\r\n");
output.print(" +++MESHCORE-TERM-STOP\r\n");
} else {
terminalOutput().print(" disconnect (network terminal)\r\n");
output.print(" disconnect (network terminal)\r\n");
}
} else if (handleCommand(command, 0, local_reply)) {
// Fill the same safe shared-command surface for getters (`get name`,
+492
View File
@@ -0,0 +1,492 @@
{
"profiles": {
"all-options-native0-lost0": {
"flags": [
"MESH_USB_LOGGING_AVAILABLE=1",
"COMPANION_FEATURE_READER=1",
"COMPANION_FEATURE_MEMORY_DIAGNOSTICS=1",
"COMPANION_FEATURE_NOTIFICATIONS=1",
"COMPANION_RADIO_FULL=1",
"COMPANION_EXCLUSIVE_WIFI_BLE=1",
"WITH_WEBCONFIG=1",
"MESH_PRIMARY_ESPNOW=1",
"COMPANION_FEATURE_TEMP_RADIO=1",
"COMPANION_FEATURE_OTA_CLI=1",
"BLE_PIN_CODE=1",
"ESP32=1",
"NRF52_PLATFORM=1",
"EXTRAFS=1",
"WIFI_SSID=\"test\"",
"MESH_ENABLE_TX_BEACONS=1",
"MESH_ENABLE_MESHTEXT_CHAT=1",
"MESH_ENABLE_ETHERNET_TUNNEL=1",
"OTA_RAK15002_SD_STORE=1",
"ETHERNET_USE_RAK13800=1",
"PIN_BUZZER=1",
"PIN_VIBRATION=1",
"HAS_DRV2605=1",
"ESP32_PLATFORM=1",
"MESH_USB_CONSOLE_COOPERATIVE=0",
"MESH_ENABLE_LOST_REPLY=0"
],
"output_bytes": [
4644,
4636
],
"output_sha256": [
"c830a4086f88e06ec7fe21bb6f1287e78fe4e72cefd7c3999740913f9ce3831d",
"b71c0ba723d2ff693c1ed8ce23998d977607a213dba4af688fe4db16a59e38f6"
]
},
"all-options-native0-lost1": {
"flags": [
"MESH_USB_LOGGING_AVAILABLE=1",
"COMPANION_FEATURE_READER=1",
"COMPANION_FEATURE_MEMORY_DIAGNOSTICS=1",
"COMPANION_FEATURE_NOTIFICATIONS=1",
"COMPANION_RADIO_FULL=1",
"COMPANION_EXCLUSIVE_WIFI_BLE=1",
"WITH_WEBCONFIG=1",
"MESH_PRIMARY_ESPNOW=1",
"COMPANION_FEATURE_TEMP_RADIO=1",
"COMPANION_FEATURE_OTA_CLI=1",
"BLE_PIN_CODE=1",
"ESP32=1",
"NRF52_PLATFORM=1",
"EXTRAFS=1",
"WIFI_SSID=\"test\"",
"MESH_ENABLE_TX_BEACONS=1",
"MESH_ENABLE_MESHTEXT_CHAT=1",
"MESH_ENABLE_ETHERNET_TUNNEL=1",
"OTA_RAK15002_SD_STORE=1",
"ETHERNET_USE_RAK13800=1",
"PIN_BUZZER=1",
"PIN_VIBRATION=1",
"HAS_DRV2605=1",
"ESP32_PLATFORM=1",
"MESH_USB_CONSOLE_COOPERATIVE=0",
"MESH_ENABLE_LOST_REPLY=1"
],
"output_bytes": [
4679,
4671
],
"output_sha256": [
"7375ec52046c4479f32f33a1845689344eb6d9c3b29d1d0a1404d57c148eb703",
"03401a37d0b371186885ddda541f8c011c76125f98c46b27be4f3d559fe2cd3e"
]
},
"all-options-native1-lost0": {
"flags": [
"MESH_USB_LOGGING_AVAILABLE=1",
"COMPANION_FEATURE_READER=1",
"COMPANION_FEATURE_MEMORY_DIAGNOSTICS=1",
"COMPANION_FEATURE_NOTIFICATIONS=1",
"COMPANION_RADIO_FULL=1",
"COMPANION_EXCLUSIVE_WIFI_BLE=1",
"WITH_WEBCONFIG=1",
"MESH_PRIMARY_ESPNOW=1",
"COMPANION_FEATURE_TEMP_RADIO=1",
"COMPANION_FEATURE_OTA_CLI=1",
"BLE_PIN_CODE=1",
"ESP32=1",
"NRF52_PLATFORM=1",
"EXTRAFS=1",
"WIFI_SSID=\"test\"",
"MESH_ENABLE_TX_BEACONS=1",
"MESH_ENABLE_MESHTEXT_CHAT=1",
"MESH_ENABLE_ETHERNET_TUNNEL=1",
"OTA_RAK15002_SD_STORE=1",
"ETHERNET_USE_RAK13800=1",
"PIN_BUZZER=1",
"PIN_VIBRATION=1",
"HAS_DRV2605=1",
"ESP32_PLATFORM=1",
"MESH_USB_CONSOLE_COOPERATIVE=1",
"MESH_ENABLE_LOST_REPLY=0"
],
"output_bytes": [
4729,
4721
],
"output_sha256": [
"7c2f2ca10b445bd5a4ea9f747198dc41bf4275997b38a5af1a108edb1fada34c",
"8841b225cf40dcad34d7de15b945384e933c668dd0d63f2abbf9449cf542af95"
]
},
"all-options-native1-lost1": {
"flags": [
"MESH_USB_LOGGING_AVAILABLE=1",
"COMPANION_FEATURE_READER=1",
"COMPANION_FEATURE_MEMORY_DIAGNOSTICS=1",
"COMPANION_FEATURE_NOTIFICATIONS=1",
"COMPANION_RADIO_FULL=1",
"COMPANION_EXCLUSIVE_WIFI_BLE=1",
"WITH_WEBCONFIG=1",
"MESH_PRIMARY_ESPNOW=1",
"COMPANION_FEATURE_TEMP_RADIO=1",
"COMPANION_FEATURE_OTA_CLI=1",
"BLE_PIN_CODE=1",
"ESP32=1",
"NRF52_PLATFORM=1",
"EXTRAFS=1",
"WIFI_SSID=\"test\"",
"MESH_ENABLE_TX_BEACONS=1",
"MESH_ENABLE_MESHTEXT_CHAT=1",
"MESH_ENABLE_ETHERNET_TUNNEL=1",
"OTA_RAK15002_SD_STORE=1",
"ETHERNET_USE_RAK13800=1",
"PIN_BUZZER=1",
"PIN_VIBRATION=1",
"HAS_DRV2605=1",
"ESP32_PLATFORM=1",
"MESH_USB_CONSOLE_COOPERATIVE=1",
"MESH_ENABLE_LOST_REPLY=1"
],
"output_bytes": [
4764,
4756
],
"output_sha256": [
"c141a0237099f1f63ae8768001614673d6d54914ae78d41fc4a483f031326f25",
"d562a7dcac544c502908205b14eaa20e921c76830ba5921018c4aa19798efea8"
]
},
"full-native0-lost0": {
"flags": [
"MESH_USB_LOGGING_AVAILABLE=1",
"COMPANION_FEATURE_READER=1",
"COMPANION_FEATURE_MEMORY_DIAGNOSTICS=1",
"COMPANION_FEATURE_NOTIFICATIONS=1",
"COMPANION_RADIO_FULL=1",
"COMPANION_EXCLUSIVE_WIFI_BLE=1",
"WITH_WEBCONFIG=1",
"MESH_PRIMARY_ESPNOW=1",
"COMPANION_FEATURE_TEMP_RADIO=1",
"COMPANION_FEATURE_OTA_CLI=1",
"BLE_PIN_CODE=1",
"ESP32=1",
"NRF52_PLATFORM=1",
"EXTRAFS=1",
"WIFI_SSID=\"test\"",
"MESH_USB_CONSOLE_COOPERATIVE=0",
"MESH_ENABLE_LOST_REPLY=0"
],
"output_bytes": [
3214,
3206
],
"output_sha256": [
"ef1d9548ee100678eac89f0d60a42dd356bf99127eb819beccc4a15895145b94",
"8a3fe836f56831c9c8de86254fd8186f0ece25d0d63acf3b1c916990341a0c0a"
]
},
"full-native0-lost1": {
"flags": [
"MESH_USB_LOGGING_AVAILABLE=1",
"COMPANION_FEATURE_READER=1",
"COMPANION_FEATURE_MEMORY_DIAGNOSTICS=1",
"COMPANION_FEATURE_NOTIFICATIONS=1",
"COMPANION_RADIO_FULL=1",
"COMPANION_EXCLUSIVE_WIFI_BLE=1",
"WITH_WEBCONFIG=1",
"MESH_PRIMARY_ESPNOW=1",
"COMPANION_FEATURE_TEMP_RADIO=1",
"COMPANION_FEATURE_OTA_CLI=1",
"BLE_PIN_CODE=1",
"ESP32=1",
"NRF52_PLATFORM=1",
"EXTRAFS=1",
"WIFI_SSID=\"test\"",
"MESH_USB_CONSOLE_COOPERATIVE=0",
"MESH_ENABLE_LOST_REPLY=1"
],
"output_bytes": [
3249,
3241
],
"output_sha256": [
"ef5d1cd1f549568acf0b1e769bdcd68bd96855301ec8e7e053df9e9c87a03b6d",
"17a4afab06e7e61cccde52da08ea6d939ead8c34f4ca321cabdaed3efdf9da62"
]
},
"full-native1-lost0": {
"flags": [
"MESH_USB_LOGGING_AVAILABLE=1",
"COMPANION_FEATURE_READER=1",
"COMPANION_FEATURE_MEMORY_DIAGNOSTICS=1",
"COMPANION_FEATURE_NOTIFICATIONS=1",
"COMPANION_RADIO_FULL=1",
"COMPANION_EXCLUSIVE_WIFI_BLE=1",
"WITH_WEBCONFIG=1",
"MESH_PRIMARY_ESPNOW=1",
"COMPANION_FEATURE_TEMP_RADIO=1",
"COMPANION_FEATURE_OTA_CLI=1",
"BLE_PIN_CODE=1",
"ESP32=1",
"NRF52_PLATFORM=1",
"EXTRAFS=1",
"WIFI_SSID=\"test\"",
"MESH_USB_CONSOLE_COOPERATIVE=1",
"MESH_ENABLE_LOST_REPLY=0"
],
"output_bytes": [
3299,
3291
],
"output_sha256": [
"3402723097417e800d48275bb7e4b7a8911ac57a8370a544900137938fcbde66",
"2313fef0b2d9f3f74ab816dd6d8d27b44acb126469b3e08446544c3f0c291e77"
]
},
"full-native1-lost1": {
"flags": [
"MESH_USB_LOGGING_AVAILABLE=1",
"COMPANION_FEATURE_READER=1",
"COMPANION_FEATURE_MEMORY_DIAGNOSTICS=1",
"COMPANION_FEATURE_NOTIFICATIONS=1",
"COMPANION_RADIO_FULL=1",
"COMPANION_EXCLUSIVE_WIFI_BLE=1",
"WITH_WEBCONFIG=1",
"MESH_PRIMARY_ESPNOW=1",
"COMPANION_FEATURE_TEMP_RADIO=1",
"COMPANION_FEATURE_OTA_CLI=1",
"BLE_PIN_CODE=1",
"ESP32=1",
"NRF52_PLATFORM=1",
"EXTRAFS=1",
"WIFI_SSID=\"test\"",
"MESH_USB_CONSOLE_COOPERATIVE=1",
"MESH_ENABLE_LOST_REPLY=1"
],
"output_bytes": [
3334,
3326
],
"output_sha256": [
"71c96f2d042f38c896fe14dc6f867898bb413498dfe669d874130e1595a8e560",
"feb8bb64cca5032c07d967f7428dafb4787fc7eb342fc398ea78bb3746b4b7b8"
]
},
"minimal-native0-lost0": {
"flags": [
"MESH_USB_LOGGING_AVAILABLE=0",
"MESH_USB_CONSOLE_COOPERATIVE=0",
"MESH_ENABLE_LOST_REPLY=0"
],
"output_bytes": [
1696,
1688
],
"output_sha256": [
"82ed92c5c52cbd699c5b57f11006367b338cab5347f06aeccb0edf80f8b16ec2",
"c0047e0c3eae1e9d828dabe6157ed4296af1d0c93c249aa1dda0ea5920f5ef25"
]
},
"minimal-native0-lost1": {
"flags": [
"MESH_USB_LOGGING_AVAILABLE=0",
"MESH_USB_CONSOLE_COOPERATIVE=0",
"MESH_ENABLE_LOST_REPLY=1"
],
"output_bytes": [
1731,
1723
],
"output_sha256": [
"de5cbd88c77ed4f30817bcd89904725c97fb49142cf048e7b90e897d646e7b6b",
"20daa08b614642afffe00bc1c89621a020e688d79a32113da16037e3a3f4c460"
]
},
"minimal-native1-lost0": {
"flags": [
"MESH_USB_LOGGING_AVAILABLE=0",
"MESH_USB_CONSOLE_COOPERATIVE=1",
"MESH_ENABLE_LOST_REPLY=0"
],
"output_bytes": [
1696,
1688
],
"output_sha256": [
"82ed92c5c52cbd699c5b57f11006367b338cab5347f06aeccb0edf80f8b16ec2",
"c0047e0c3eae1e9d828dabe6157ed4296af1d0c93c249aa1dda0ea5920f5ef25"
]
},
"minimal-native1-lost1": {
"flags": [
"MESH_USB_LOGGING_AVAILABLE=0",
"MESH_USB_CONSOLE_COOPERATIVE=1",
"MESH_ENABLE_LOST_REPLY=1"
],
"output_bytes": [
1731,
1723
],
"output_sha256": [
"de5cbd88c77ed4f30817bcd89904725c97fb49142cf048e7b90e897d646e7b6b",
"20daa08b614642afffe00bc1c89621a020e688d79a32113da16037e3a3f4c460"
]
},
"qspi-native0-lost0": {
"flags": [
"MESH_USB_LOGGING_AVAILABLE=1",
"COMPANION_FEATURE_READER=1",
"COMPANION_FEATURE_MEMORY_DIAGNOSTICS=1",
"COMPANION_FEATURE_NOTIFICATIONS=1",
"COMPANION_RADIO_FULL=1",
"COMPANION_EXCLUSIVE_WIFI_BLE=1",
"WITH_WEBCONFIG=1",
"MESH_PRIMARY_ESPNOW=1",
"COMPANION_FEATURE_TEMP_RADIO=1",
"COMPANION_FEATURE_OTA_CLI=1",
"BLE_PIN_CODE=1",
"ESP32=1",
"NRF52_PLATFORM=1",
"EXTRAFS=1",
"WIFI_SSID=\"test\"",
"MESH_ENABLE_TX_BEACONS=1",
"MESH_ENABLE_MESHTEXT_CHAT=1",
"MESH_ENABLE_ETHERNET_TUNNEL=1",
"OTA_RAK15002_SD_STORE=1",
"ETHERNET_USE_RAK13800=1",
"PIN_BUZZER=1",
"PIN_VIBRATION=1",
"HAS_DRV2605=1",
"ESP32_PLATFORM=1",
"QSPIFLASH=1",
"MESH_USB_CONSOLE_COOPERATIVE=0",
"MESH_ENABLE_LOST_REPLY=0"
],
"output_bytes": [
4504,
4496
],
"output_sha256": [
"32c607f77a4c48829a6bbd91538d0dc717f813fc32251455702913a8ffea0cdb",
"cfb475576309abb89fcad75d7d019049ed2a5489dd325fea5bcaf0ed186c9d82"
]
},
"qspi-native0-lost1": {
"flags": [
"MESH_USB_LOGGING_AVAILABLE=1",
"COMPANION_FEATURE_READER=1",
"COMPANION_FEATURE_MEMORY_DIAGNOSTICS=1",
"COMPANION_FEATURE_NOTIFICATIONS=1",
"COMPANION_RADIO_FULL=1",
"COMPANION_EXCLUSIVE_WIFI_BLE=1",
"WITH_WEBCONFIG=1",
"MESH_PRIMARY_ESPNOW=1",
"COMPANION_FEATURE_TEMP_RADIO=1",
"COMPANION_FEATURE_OTA_CLI=1",
"BLE_PIN_CODE=1",
"ESP32=1",
"NRF52_PLATFORM=1",
"EXTRAFS=1",
"WIFI_SSID=\"test\"",
"MESH_ENABLE_TX_BEACONS=1",
"MESH_ENABLE_MESHTEXT_CHAT=1",
"MESH_ENABLE_ETHERNET_TUNNEL=1",
"OTA_RAK15002_SD_STORE=1",
"ETHERNET_USE_RAK13800=1",
"PIN_BUZZER=1",
"PIN_VIBRATION=1",
"HAS_DRV2605=1",
"ESP32_PLATFORM=1",
"QSPIFLASH=1",
"MESH_USB_CONSOLE_COOPERATIVE=0",
"MESH_ENABLE_LOST_REPLY=1"
],
"output_bytes": [
4539,
4531
],
"output_sha256": [
"de162715b7175070aa2fce814b9b20dcef2a1a33ca1a14a5d0063f7dbccb489a",
"3ea7895b287cbe415d3febfc4d1f97084828f37ae4fa78e98ab8da0b0c2bf4fe"
]
},
"qspi-native1-lost0": {
"flags": [
"MESH_USB_LOGGING_AVAILABLE=1",
"COMPANION_FEATURE_READER=1",
"COMPANION_FEATURE_MEMORY_DIAGNOSTICS=1",
"COMPANION_FEATURE_NOTIFICATIONS=1",
"COMPANION_RADIO_FULL=1",
"COMPANION_EXCLUSIVE_WIFI_BLE=1",
"WITH_WEBCONFIG=1",
"MESH_PRIMARY_ESPNOW=1",
"COMPANION_FEATURE_TEMP_RADIO=1",
"COMPANION_FEATURE_OTA_CLI=1",
"BLE_PIN_CODE=1",
"ESP32=1",
"NRF52_PLATFORM=1",
"EXTRAFS=1",
"WIFI_SSID=\"test\"",
"MESH_ENABLE_TX_BEACONS=1",
"MESH_ENABLE_MESHTEXT_CHAT=1",
"MESH_ENABLE_ETHERNET_TUNNEL=1",
"OTA_RAK15002_SD_STORE=1",
"ETHERNET_USE_RAK13800=1",
"PIN_BUZZER=1",
"PIN_VIBRATION=1",
"HAS_DRV2605=1",
"ESP32_PLATFORM=1",
"QSPIFLASH=1",
"MESH_USB_CONSOLE_COOPERATIVE=1",
"MESH_ENABLE_LOST_REPLY=0"
],
"output_bytes": [
4589,
4581
],
"output_sha256": [
"a173923cd67bf96c4c1f6f5d4fa933214ecaa68c8c43d7d881f53f3e49a807c5",
"c367f8675987d86270c0b763e1249201872d0e725a20cee4b93e155d360cf12d"
]
},
"qspi-native1-lost1": {
"flags": [
"MESH_USB_LOGGING_AVAILABLE=1",
"COMPANION_FEATURE_READER=1",
"COMPANION_FEATURE_MEMORY_DIAGNOSTICS=1",
"COMPANION_FEATURE_NOTIFICATIONS=1",
"COMPANION_RADIO_FULL=1",
"COMPANION_EXCLUSIVE_WIFI_BLE=1",
"WITH_WEBCONFIG=1",
"MESH_PRIMARY_ESPNOW=1",
"COMPANION_FEATURE_TEMP_RADIO=1",
"COMPANION_FEATURE_OTA_CLI=1",
"BLE_PIN_CODE=1",
"ESP32=1",
"NRF52_PLATFORM=1",
"EXTRAFS=1",
"WIFI_SSID=\"test\"",
"MESH_ENABLE_TX_BEACONS=1",
"MESH_ENABLE_MESHTEXT_CHAT=1",
"MESH_ENABLE_ETHERNET_TUNNEL=1",
"OTA_RAK15002_SD_STORE=1",
"ETHERNET_USE_RAK13800=1",
"PIN_BUZZER=1",
"PIN_VIBRATION=1",
"HAS_DRV2605=1",
"ESP32_PLATFORM=1",
"QSPIFLASH=1",
"MESH_USB_CONSOLE_COOPERATIVE=1",
"MESH_ENABLE_LOST_REPLY=1"
],
"output_bytes": [
4624,
4616
],
"output_sha256": [
"9ba51362d80560bafd41f170e4791542dea7f128cca2c07c8e13413a42e74b09",
"d3a7f18a0f799b95b3a44cee602fc3d6e60a314bdd15e5bcf4f29ad510b45cda"
]
}
},
"schema": 1,
"scope": "Exact existing help bytes before bounded print-call splitting; network then USB terminal mode",
"source_commit": "c46defaefd642973cbbdb473815d56b5b261cb2e",
"source_file_sha256": "4665bf3d9c60abe3961aa3548d51c0d3a242d49a2ff061a4732fa07d06ae5a6a"
}
+9 -1
View File
@@ -1,6 +1,7 @@
// Full actual handlers/field initializers are injected. RGB is not configured.
// Arduino clock and output writes are the only hardware boundaries mocked.
#include <helpers/CompanionNotificationPolicy.h>
#include <helpers/LedPolicy.h>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
@@ -86,9 +87,16 @@ int main(int argc, char** argv) {
CHECK(argc == 2);
active_case = argv[1];
UITask ui;
CHECK(mesh::leds::enabled());
const char* name = argv[1];
#ifdef PIN_STATUS_LED
if (!std::strcmp(name, "zero")) {
if (!std::strcmp(name, "quiet-fresh")) {
mesh::leds::setEnabled(false);
at(ui, 0, 0, false);
at(ui, 0x80000000u, 0, false);
mesh::leds::setEnabled(true);
cadence(ui, 0x80000000u, LED_ON_MILLIS);
} else if (!std::strcmp(name, "zero")) {
at(ui, 0, 0, false);
cadence(ui, 1, LED_ON_MILLIS);
} else if (!std::strcmp(name, "upper")) {
+18 -1
View File
@@ -161,17 +161,34 @@ int main() {
assert(w.stopMorseTx() == MResult::Stopped && w.chip.carrier && w.chip.starts == 1);
assert(w.setCarrierWave(0, 0) == mesh::RadioParamApplyResult::APPLIED);
}
{
RadioLibWrapper w;
w.chip.standby_result = -705;
// RX admission failed before Morse owns or changes the modem. The shared
// reconfiguration gate requires repair and defers the attempted operation.
assert(w.startMorseTx(902.1f, 25, "E") == MResult::Busy);
assert(w.chip.standby_calls == 1 && !w.chip.starts && !w.board.before);
assert(!w._cw_active && !w._morse_owned && !w.board.awake);
assert(!w.board.frequency_prepares && !w.isInRecvMode());
assert(w._profile_refresh_required && w._profile_repair_required);
w.chip.standby_result = 0;
assert(w.startMorseTx(902.1f, 25, "E") == MResult::Started);
assert(w.stopMorseTx() == MResult::Stopped);
fresh_receive(w);
}
for (int reason = 0; reason < 5; ++reason) {
RadioLibWrapper w;
if (reason == 0) w.board.frequency_ok = false;
if (reason == 1) w.chip.frequency_result = -705;
if (reason == 2) w.chip.power_result = -705;
if (reason == 3) w.chip.standby_result = -705;
// Fail the post-admission standby, not the earlier RX admission gate.
if (reason == 3) w.chip.standby_fail_call = 2;
if (reason == 4) w.chip.irq_result = -705;
assert(w.startMorseTx(902.1f, 25, "E") == MResult::Failed);
assert(!w.chip.starts && !w.board.before);
if (w._cw_active) assert(w._cw_stopping && w.board.awake);
w.board.frequency_ok = true;
w.chip.standby_fail_call = 0;
w.chip.frequency_result = w.chip.power_result = w.chip.standby_result = w.chip.irq_result = 0;
if (w._cw_active) { advance(100); w.serviceCarrierWave(); }
assert(!w._cw_active && !w.board.awake && w.isInRecvMode());
+12
View File
@@ -3,6 +3,7 @@
#include <helpers/PowerManagementUtils.h>
#include <cstdarg>
static unsigned usb_services=0,command_services=0,transport_services=0,delays=0;
static unsigned usb_bluetooth_priority_services=0;
static bool usb_watchdog_armed=false,wireless_pending=false;
bool profileIdlePreparationAllowed();
bool profileIdleQueryAllowed();
@@ -120,6 +121,12 @@ void serviceDeferredCompanionBluetooth() { ++transport_services; }
void serviceCompanionBluetoothIdentity() { ++transport_services; }
void serviceCompanionBluetoothControl() { ++transport_services; }
void serviceCompanionBluetoothName() { ++transport_services; }
// Transport handoff has separate policy/state tests. Keep its loop boundary
// live here, including during Morse, before the authoritative sleep snapshot.
void serviceCompanionUsbBluetoothPriority() {
assert(usb_bluetooth_priority_services == board.preparations);
++usb_bluetooth_priority_services;
}
@LOOP@
int main() {
assert(!mesh::morseTimingActive(nullptr));
@@ -268,4 +275,9 @@ int main() {
#if !ROLE_REPEATER
assert(mesh::power::regulator_services==board.calls);
#endif
#if ROLE_COMPANION
assert(usb_bluetooth_priority_services==board.preparations);
#else
assert(usb_bluetooth_priority_services==0);
#endif
}
+10 -1
View File
@@ -4,6 +4,7 @@
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <helpers/LedPolicy.h>
namespace mesh { namespace notify {
@OUTPUT_ENUM@
@@ -151,8 +152,16 @@ int main(int argc, char** argv) {
active_case = argv[1];
const char* name = argv[1];
UITask ui;
CHECK(mesh::leds::enabled());
#ifdef STATUS_LED_RGB
if (!std::strcmp(name, "boot-zero") || !std::strcmp(name, "boot-upper") ||
if (!std::strcmp(name, "quiet-fresh")) {
mesh::leds::setEnabled(false);
tick(ui, 0); tick(ui, 0x80000000u);
CHECK(writes == 0); CHECK(batt_reads == 0); CHECK(power_reads == 0);
CHECK(charge_reads == 0);
mesh::leds::setEnabled(true);
boot(ui, 0x80000000u);
} else if (!std::strcmp(name, "boot-zero") || !std::strcmp(name, "boot-upper") ||
!std::strcmp(name, "boot-wrap") || !std::strcmp(name, "boot-zero-end")) {
const uint32_t start = !std::strcmp(name, "boot-zero") ? 0u :
!std::strcmp(name, "boot-upper") ? 0x80000000u :
+6
View File
@@ -21,6 +21,12 @@ static void check(bool okay, unsigned line) {
extern "C" void _start() {
genericBuzzer buzzer;
buzzer.begin();
CHECK(buzzer.isQuiet());
CHECK(buzzer.speakGps(true));
buzzer.loop();
CHECK(tone_calls == 0 && !buzzer.isPlaying() && !registers.ENABLE);
buzzer.quiet(false); // The UI applies its saved sound-on choice before use.
CHECK(!buzzer.isQuiet());
now_ms = 0xffffffffU - 100;
CHECK(buzzer.speakGps(true));
buzzer.loop();
+4 -3
View File
@@ -45,7 +45,7 @@ struct Chip {
int power=17, initial_fsk_power=0, current_limit=60;
int ramp_time=4;
unsigned ramp_calls=0, ramp_power_calls=0;
unsigned starts=0, stops=0, standby_calls=0, fsk_starts=0;
unsigned starts=0, stops=0, standby_calls=0, fsk_starts=0, standby_fail_call=0;
unsigned raw_rx_starts=0;
unsigned admission_order=0,last_receive_order=0;
unsigned morse_ramp_calls=0;
@@ -59,8 +59,9 @@ struct Chip {
void (*callback)()=setFlag;
int standby() {
++standby_calls; g_mock_millis+=standby_latency;
if (!standby_result) carrier=false;
return standby_result;
const int result=standby_fail_call==standby_calls ? -705 : standby_result;
if (!result) carrier=false;
return result;
}
int standby(uint8_t mode) {
++warm_standby_calls; last_standby_mode=mode;
+1
View File
@@ -26,6 +26,7 @@ class CompanionOtaIncludeTest(unittest.TestCase):
directory = Path(temp)
for header in (
"helpers/RegulatorSettings.h",
"helpers/LedSettings.h",
"helpers/ui/StartupScreen.h", "helpers/ui/DisplayPowerSettings.h",
"helpers/UsbLoggingWatchdog.h", "helpers/UsbLoggingClientActivity.h",
"Mesh.h", "helpers/BluetoothMac.h", "CompanionBluetooth.h",
+2 -2
View File
@@ -99,8 +99,8 @@ for help_line in (' board\\r\\n', ' version\\r\\n',
assert "no automatic adverts" not in help_branch
assert re.search(
r"#if COMPANION_FEATURE_MEMORY_DIAGNOSTICS\s+"
r'terminalOutput\(\)\.print\(" memory\\r\\n"\);\s+#endif',
terminal,
r'" memory\\r\\n"\s+#endif',
help_branch,
)
# Terminal-specific handlers keep precedence, then both the generic `set`
+28 -18
View File
@@ -1,5 +1,7 @@
"""Execute Companion debug persistence/CLI and bounded packet logging paths."""
from pathlib import Path
import hashlib
import json
import os
import subprocess
import tempfile
@@ -322,6 +324,7 @@ class CompanionUsbLoggingTests(unittest.TestCase):
self.assertEqual(built.returncode, 0, built.stdout + built.stderr)
ran = subprocess.run([str(binary)], capture_output=True, text=True)
self.assertEqual(ran.returncode, 0, ran.stdout + ran.stderr)
return ran.stdout
def prepare(self, work):
(work / 'Utils.h').write_text('''#pragma once
@@ -407,6 +410,7 @@ inline char* utoa(unsigned int value,char* output,int base){
end = source.index(' } else if (handleCommand(command, 0, local_reply))', start)
harness = r'''
#include <cassert>
#include <cstdio>
#include <cstring>
#include <string>
struct Port {
@@ -427,38 +431,44 @@ int main(){
assert(text.find("Commands:\r\n stats-core / stats-radio / stats-radio-diag / stats-packets\r\n")==0);
assert(text.find(" get public.key\r\n")!=std::string::npos);
#if MESH_ENABLE_LOST_REPLY
assert(text.find(" get lost.reply\r\n set lost.reply <off|no|yes>\r\n")!=std::string::npos);
assert(text.find(" get/set lost.reply <off|no|yes>\r\n")!=std::string::npos);
#else
assert(text.find("lost.reply")==std::string::npos);
#endif
assert(text.find(" get usb.debug\r\n set usb.debug <on|off>\r\n")!=std::string::npos);
#if MESH_USB_CONSOLE_COOPERATIVE
assert(text.find(" get usb.watchdog\r\n get usb.watchdog.last\r\n set usb.watchdog <off|on|auto> (saved; default auto)\r\n")!=std::string::npos);
#if MESH_USB_LOGGING_AVAILABLE
assert(text.find(" get/set usb.debug <on|off>\r\n")!=std::string::npos);
#else
assert(text.find("usb.debug")==std::string::npos);
#endif
#if MESH_USB_LOGGING_AVAILABLE && MESH_USB_CONSOLE_COOPERATIVE
assert(text.find(" get usb.watchdog.last\r\n get/set usb.watchdog <off|on|auto> (saved; default auto)\r\n")!=std::string::npos);
#else
assert(text.find("usb.watchdog")==std::string::npos);
#endif
assert(text.find(" reboot\r\n ver\r\n")!=std::string::npos);
assert(text.find(" +++MESHCORE-TERM-STOP\r\n")!=std::string::npos ? terminal : !terminal);
assert(text.find(" disconnect (closes the network terminal)\r\n")!=std::string::npos ? !terminal : terminal);
assert(text.find(" disconnect (network terminal)\r\n")!=std::string::npos ? !terminal : terminal);
for(unsigned char value : text) printf("%02x",value);
printf("\n");
}
}
'''.replace('@HELP@', source[start:end])
features = ['MESH_USB_LOGGING_AVAILABLE', 'COMPANION_FEATURE_READER',
'COMPANION_FEATURE_MEMORY_DIAGNOSTICS', 'COMPANION_FEATURE_NOTIFICATIONS',
'COMPANION_RADIO_FULL', 'COMPANION_EXCLUSIVE_WIFI_BLE',
'WITH_WEBCONFIG', 'MESH_PRIMARY_ESPNOW', 'COMPANION_FEATURE_TEMP_RADIO',
'COMPANION_FEATURE_OTA_CLI', 'BLE_PIN_CODE', 'ESP32', 'NRF52_PLATFORM',
'EXTRAFS']
# These hashes were captured from the unmodified c46 help branch.
# Compare the complete output bytes, including every conditional line,
# rather than duplicating a second help implementation in this test.
fixture = json.loads((ROOT / 'test/fixtures/companion_help_output.sha256.json').read_text())
self.assertEqual(fixture['schema'], 1)
with tempfile.TemporaryDirectory() as directory:
work = Path(directory)
self.prepare(work)
for native in (0, 1):
for lost in (0, 1):
with self.subTest(native=native, lost=lost):
self.compile_and_run(work, harness,
[flag + '=1' for flag in features] + ['WIFI_SSID="test"',
'MESH_USB_CONSOLE_COOPERATIVE=' + str(native),
'MESH_ENABLE_LOST_REPLY=' + str(lost)])
for name, expected in fixture['profiles'].items():
with self.subTest(profile=name):
raw = self.compile_and_run(work, harness, expected['flags'])
outputs = [bytes.fromhex(line) for line in raw.splitlines()]
self.assertEqual(len(outputs), 2)
self.assertEqual([len(value) for value in outputs], expected['output_bytes'])
self.assertEqual([hashlib.sha256(value).hexdigest() for value in outputs], expected['output_sha256'])
if __name__ == '__main__':
+28 -7
View File
@@ -33,7 +33,7 @@ HARNESS = r'''
uint32_t now_ms = 100;
uint32_t millis() { return now_ms; }
unsigned identity_calls=0, address_calls=0, begin_calls=0, enable_calls=0;
unsigned register_calls=0, sdk_ps_calls=0;
unsigned register_calls=0, manager_enable_calls=0, sdk_ps_calls=0;
bool identity_ok=true, register_ok=true, begin_ok=true;
enum class InterfaceType { Bluetooth };
struct CompanionNodePrefs {
@@ -49,16 +49,24 @@ struct Mesh {
uint32_t getBLEPin() { return 123456; }
bool isBluetoothEnabledPreference() { return preference; }
} the_mesh;
struct {
struct TestInterfaceManager {
bool enabled=true;
bool addInterface(InterfaceType, void*) { ++register_calls; return register_ok; }
bool removeInterface(void*) { return true; }
bool isEnabled() const { return true; }
bool isEnabled() const { return enabled; }
void enableBluetooth();
} interface_manager;
struct {
template<typename... T> bool begin(T...) { ++begin_calls; return begin_ok; }
void enable() { ++enable_calls; }
const char* beginFailure() const { return "test failure"; }
} bluetooth_interface;
// Startup now requests Bluetooth through the manager; its transport policy
// has a separate suite. These non-T1000 variants forward an allowed request.
void TestInterfaceManager::enableBluetooth() {
++manager_enable_calls;
bluetooth_interface.enable();
}
namespace mesh {
struct Logger {
template<typename... T> void printf(T...) {}
@@ -144,7 +152,9 @@ void rebootCompatibility() {
assert(mesh::wifi::loadCompanionModeForBoot());
}
void resetBootCounters() {
identity_calls=address_calls=begin_calls=enable_calls=register_calls=0;
identity_calls=address_calls=begin_calls=enable_calls=register_calls=manager_enable_calls=0;
interface_manager.enabled=true;
the_mesh.preference=true;
companion_bluetooth_initialized=false;
companion_bluetooth_start_at=0;
}
@@ -227,7 +237,7 @@ int main() {
// The guard must precede identity writes, registration, SDK init and retry.
startCompanionBluetooth();
assert(!companion_bluetooth_initialized && !companion_bluetooth_start_at);
assert(!identity_calls && !address_calls && !register_calls && !begin_calls && !enable_calls);
assert(!identity_calls && !address_calls && !register_calls && !begin_calls && !enable_calls && !manager_enable_calls);
assert(companion_bluetooth_start_failure[0]);
// Even an old retry deadline cannot resurrect Bluetooth in this boot.
companion_bluetooth_start_at=now_ms;
@@ -254,9 +264,9 @@ int main() {
resetBootCounters();
startCompanionBluetooth();
assert(companion_bluetooth_initialized);
assert(identity_calls==1 && register_calls==1 && address_calls==1 && begin_calls==1 && enable_calls==1);
assert(identity_calls==1 && register_calls==1 && address_calls==1 && begin_calls==1 && enable_calls==1 && manager_enable_calls==1);
startCompanionBluetooth(); serviceDeferredCompanionBluetooth();
assert(begin_calls==1); // Already initialized remains idempotent.
assert(begin_calls==1 && manager_enable_calls==1); // Already initialized remains idempotent.
#if defined(ESP32) && defined(WIFI_SSID)
assert(companionWiFiBluetoothActive());
@@ -286,6 +296,16 @@ int main() {
now_ms+=4999; serviceDeferredCompanionBluetooth(); assert(identity_calls==1);
++now_ms; identity_ok=true; serviceDeferredCompanionBluetooth();
assert(companion_bluetooth_initialized && identity_calls==2 && begin_calls==1);
// Initializing the stack must not bypass the manager or saved enable gate.
resetBootCounters(); interface_manager.enabled=false;
startCompanionBluetooth();
assert(companion_bluetooth_initialized && begin_calls==1);
assert(!manager_enable_calls && !enable_calls && !interface_manager.enabled);
resetBootCounters(); the_mesh.preference=false;
startCompanionBluetooth();
assert(companion_bluetooth_initialized && begin_calls==1);
assert(!manager_enable_calls && !enable_calls && !the_mesh.preference);
}
'''
@@ -295,6 +315,7 @@ class CompanionWiFiCompatibilityTest(unittest.TestCase):
main = (ROOT / "examples/companion_radio/main.cpp").read_text()
signatures = [
"static void scheduleCompanionBluetoothRetry(",
"static void serviceCompanionUsbBluetoothPriority(",
"static void startCompanionBluetooth(",
"static void serviceDeferredCompanionBluetooth(",
]
+40 -19
View File
@@ -566,33 +566,54 @@ int main() {
self.run_rak_uart_probe(negative=True)
def test_ble_led_optout_preserves_default_and_begin_order(self):
from test_nrf52_led_policy import compile_run as run_led_policy
source = (ROOT / "src/helpers/nrf52/SerialBLEInterface.cpp").read_text()
begin = extract_braced(source, "bool SerialBLEInterface::begin(")
block = re.search(r"#ifdef DISABLE_BLE_LED[\s\S]*?#endif", begin).group()
self.assertLess(begin.index(block), begin.index("Bluefruit.configPrphConn("))
self.assertLess(begin.index(block), begin.index("if (!Bluefruit.begin())"))
self.assertLess(begin.index("if (_begin_attempted)"), begin.index(block))
policy = "mesh_nrf52::applyBluetoothLedPolicy();"
self.assertEqual(begin.count(policy), 1)
self.assertLess(begin.index(policy), begin.index("Bluefruit.configPrphConn("))
self.assertLess(begin.index(policy), begin.index("if (!Bluefruit.begin())"))
self.assertLess(begin.index("if (_begin_attempted)"), begin.index(policy))
harness = r'''
#include <cassert>
struct BluefruitMock {
bool controlled=true;unsigned calls=0;
void autoConnLed(bool enabled){controlled=enabled;++calls;}
} Bluefruit;
void configure(){
@BLOCK@
}
#include <helpers/nrf52/BluetoothLed.h>
MockBluefruit Bluefruit;
int led_level = LED_STATE_ON, writes = 0;
int main(){
configure();
#ifdef DISABLE_BLE_LED
assert(!Bluefruit.controlled&&Bluefruit.calls==1);
const bool default_enabled = false;
#else
assert(Bluefruit.controlled&&Bluefruit.calls==0);
const bool default_enabled = true;
#endif
Bluefruit.controlled=false;configure();assert(!Bluefruit.controlled);
assert(mesh::leds::enabled());
mesh_nrf52::applyBluetoothLedPolicy();
assert(Bluefruit.automatic == default_enabled);
// Before begin(), configuring the owner must not query a connection or use
// the advertising timer that Bluefruit has not created yet.
assert(!Bluefruit.ready && Bluefruit.connection_queries == 0);
assert(Bluefruit.timer_starts == 0 && Bluefruit.timer_stops == 0);
mesh::leds::setEnabled(false);
mesh_nrf52::applyBluetoothLedPolicy();
assert(!Bluefruit.automatic && led_level == !LED_STATE_ON);
assert(Bluefruit.connection_queries == 0 && Bluefruit.timer_starts == 0);
// The shared saved policy now explicitly restores enabled LEDs; the compile
// time opt-out continues to override that choice.
mesh::leds::setEnabled(true);
mesh_nrf52::applyBluetoothLedPolicy();
assert(Bluefruit.automatic == default_enabled);
assert(Bluefruit.connection_queries == 0 && Bluefruit.timer_starts == 0);
mesh::leds::setEnabled(false);
Bluefruit.ready = true;
mesh_nrf52::bluetoothLedStarted();
assert(!Bluefruit.automatic && Bluefruit.timer_starts == 0);
}
'''.replace("@BLOCK@", block)
self.compile_run(harness)
self.compile_run(harness, ("DISABLE_BLE_LED=1",))
'''
for disabled in (False, True):
with self.subTest(disabled=disabled):
defines = ["LED_BLUE=12", "LED_STATE_ON=1"]
if disabled:
defines.append("DISABLE_BLE_LED=1")
run_led_policy(self, harness, defines)
def test_shared_success_mark_and_rak_discovery_reset_are_wired(self):
provider = (ROOT / "src/helpers/sensors/MicroNMEALocationProvider.h").read_text()
+10 -2
View File
@@ -20,7 +20,7 @@ UIS = ('ui-orig', 'ui-new', 'ui-tiny')
CASES = ('zero', 'upper', 'max', 'normal', 'unread', 'signed', 'wrap', 'off-wrap',
'zero-stamp', 'message-on', 'message-off', 'owner-fresh', 'mask-fresh',
'owner-on-long', 'owner-off-long', 'mask-on-long', 'mask-off-long',
'suppressed-short', 'delayed-one-phase', 'both-vetoes')
'suppressed-short', 'delayed-one-phase', 'both-vetoes', 'quiet-fresh')
def harness(ui, mutation=None):
@@ -41,7 +41,12 @@ def harness(ui, mutation=None):
abstract, re.MULTILINE))
setters = '\n'.join(extract_braced(abstract, signature) for signature in (
'void setNotificationLedOwnership(', 'void setNotificationOutputs('))
if mutation == 'no-owner-guard':
if mutation == 'no-policy-guard':
guard = 'if (!mesh::leds::enabled()) return;'
if handler.count(guard) != 1:
raise AssertionError('the actual board LED policy guard must be present')
handler = handler.replace(guard, '', 1)
elif mutation == 'no-owner-guard':
guard = 'if (_notification_led_owned || !(_notification_outputs & mesh::notify::Led)) return;'
if handler.count(guard) != 1:
raise AssertionError('the actual ownership/output guard must be present')
@@ -68,6 +73,9 @@ class MonoLedRolloverTest(unittest.TestCase):
def test_missing_ownership_guard_fails_semantic_check(self):
self.run_variants('no-owner-guard', ('owner-fresh',), 'FAIL owner-fresh: writes == count')
def test_missing_board_policy_guard_fails_semantic_check(self):
self.run_variants('no-policy-guard', ('quiet-fresh',), 'FAIL quiet-fresh: writes == count')
def test_early_edge_regression_is_rejected(self):
self.run_variants('non-strict-edge', ('zero',), 'FAIL zero: writes == count')
+10 -2
View File
@@ -24,7 +24,7 @@ CASES = (
'poll-wrap-before', 'poll-wrap-after', 'poll-zero-stamp', 'poll-long',
'poll-near-full', 'poll-read-delays', 'owner-fresh', 'mask-fresh',
'owner-flourish-short', 'mask-flourish-long', 'owner-on-long',
'owner-off-long', 'mask-on-long', 'mask-off-long', 'both-vetoes',
'owner-off-long', 'mask-on-long', 'mask-off-long', 'both-vetoes', 'quiet-fresh',
)
POWER_CASES = (
'power-cadence', 'charging', 'charged', 'charge-unread',
@@ -45,7 +45,12 @@ def harness(mutation=None):
setters = '\n'.join(extract_braced(abstract, signature) for signature in (
'void setNotificationLedOwnership(', 'void setNotificationOutputs('))
handler = extract_braced(source, 'void UITask::userLedHandler()')
if mutation == 'no-owner-guard':
if mutation == 'no-policy-guard':
guard = 'if (!mesh::leds::enabled()) return;'
if handler.count(guard) != 1:
raise AssertionError('the actual board LED policy guard must be present')
handler = handler.replace(guard, '', 1)
elif mutation == 'no-owner-guard':
guard = 'if (_notification_led_owned || !(_notification_outputs & mesh::notify::Led)) return;'
if handler.count(guard) != 1:
raise AssertionError('the actual ownership/output guard must be present')
@@ -81,6 +86,9 @@ class RgbLedRolloverTest(unittest.TestCase):
def test_ownership_regression_is_rejected(self):
self.run_variants('no-owner-guard', ('owner-fresh',), 'batt_reads == 0')
def test_board_policy_regression_is_rejected(self):
self.run_variants('no-policy-guard', ('quiet-fresh',), 'writes == 0')
def test_captured_poll_anchor_regression_is_rejected(self):
self.run_variants('completion-time-anchor', ('poll-read-delays',), 'batt_reads == 2')
+5 -1
View File
@@ -187,10 +187,14 @@ class RuntimeTests(unittest.TestCase):
return subprocess.run([str(binary)], capture_output=True, timeout=10).returncode
def test_actual_unpatched_versions_reproduce_both_semantic_rollover_failures(self):
queue_lines = (FIXTURES / 'queue.cpp').read_text(encoding='ascii').splitlines()
rollover_check = ' CHECK(tone_calls == 2 && buzzer.isPlaying() && !registers.ENABLE);'
self.assertEqual(queue_lines.count(rollover_check), 1)
rollover_failure = queue_lines.index(rollover_check) + 1
for version in VERSIONS:
with self.subTest(version=version):
self.assertEqual(self.build_run(source_for(version), version, "regression.cpp"), 30)
self.assertEqual(self.build_run(source_for(version), version, "queue.cpp"), 33)
self.assertEqual(self.build_run(source_for(version), version, "queue.cpp"), rollover_failure)
def test_patched_versions_keep_tempo_empty_stop_replacement_and_real_voice_queue(self):
for version in VERSIONS: