From 29f2fb468dc80fe9842c598b60b29c21577d42ca Mon Sep 17 00:00:00 2001 From: mikecarper Date: Fri, 28 Aug 2026 17:37:39 -0700 Subject: [PATCH] refactor: use companion CLI for hardware settings --- docs/cli_commands.md | 9 ++-- docs/companion_protocol.md | 82 ++++++++++------------------- examples/companion_radio/MyMesh.cpp | 39 ++++---------- 3 files changed, 42 insertions(+), 88 deletions(-) diff --git a/docs/cli_commands.md b/docs/cli_commands.md index de46b65f..bf0309e8 100644 --- a/docs/cli_commands.md +++ b/docs/cli_commands.md @@ -261,10 +261,11 @@ WiFi card. Every ESP32 WiFi Companion with WebConfig exposes the standalone families through its USB text terminal. Full Companion exposes the same role-specific terminal on TCP port 5002. Credential writes reply before restarting the WiFi station, so a TCP client should expect to reconnect at the -new address; USB password input is masked. Binary -Companion clients can use command bytes -`0x7C` and `0x7D` over USB, BLE, or TCP port 5000 without the terminal-start -token. WiFi-only Companions accept all three modes. Full Companion rejects +new address; USB password input is masked. Binary Companion clients can use +USB, BLE, or TCP port 5000 without the terminal-start token: send command +`0x42` (`CMD_RUN_CLI_COMMAND`) followed by the same CLI text, such as +`get wifi.powersave` or `set wifi.powersave min`. WiFi-only Companions accept +all three modes. Full Companion rejects `none` because its simultaneous BLE transport requires modem sleep. Companion device `powersaving` and LoRa `radio.rxps` remain independent. On an ESP32 Full Companion whose primary mesh radio is ESP-NOW, `max` is also unavailable: a diff --git a/docs/companion_protocol.md b/docs/companion_protocol.md index 708344a0..8e903516 100644 --- a/docs/companion_protocol.md +++ b/docs/companion_protocol.md @@ -237,10 +237,6 @@ catalog; bytes `0x2C`-`0x31` are parked and `0x35` is unused. | `0x42` | `CMD_RUN_CLI_COMMAND` | Run a local CLI command (protocol v14+). | | `0x4A` | `CMD_EXEC_LOCAL_OTA_CONTROL` | Run one bounded local TempRadio or OTA command on a Full Companion. | | `0x4B` | `CMD_BLE_MOTA_SOURCE` | Query, start, or stop an nRF52 Full Companion's Bluetooth-backed LoRa mOTA source. | -| `0x78` / `0x79` | `CMD_GET_RADIO_FEM_RXGAIN` / `CMD_SET_RADIO_FEM_RXGAIN` | Fork extension: read or set FEM receive gain. | -| `0x7A` / `0x7B` | `CMD_GET_RADIO_RXGAIN` / `CMD_SET_RADIO_RXGAIN` | Fork extension: read or set the radio chip's boosted receive-gain mode. | -| `0x7C` / `0x7D` | `CMD_GET_WIFI_POWER_SAVE` / `CMD_SET_WIFI_POWER_SAVE` | Fork extension: read or set ESP32 Companion WiFi modem sleep. | -| `0x7E` / `0x7F` | `CMD_GET_BLUETOOTH_NAME` / `CMD_SET_BLUETOOTH_NAME` | Fork extension: read or set the independent Bluetooth device name. | The sections below detail the most common frames. Refer to the source named above for command bodies that are not expanded here. @@ -252,39 +248,29 @@ text. This is separate from sending a remote on-air CLI command with one byte and must not contain an embedded NUL. An unknown command is returned as the normal CLI reply text `Unknown command`, not as an error frame. -The fork-specific hardware-setting commands occupy the host-to-device range -`0x78`-`0x7F`, the highest currently unused block below the push-frame bit. -Command IDs must stay below `0x80`; IDs with bit 7 set identify asynchronous -device-to-host push frames. These commands do not change any existing frame -layout. Clients should probe the command they need and treat -`ERR_CODE_UNSUPPORTED_CMD` as feature absence. - Firmware from this fork predating the upstream `0x42` allocation used `0x42`-`0x49` for these eight settings. This firmware accepts those values as -deprecated aliases so existing clients continue to work. A one-byte `0x42` -frame is the legacy FEM-gain GET; `0x42` followed by text is the official -`CMD_RUN_CLI_COMMAND`. New clients should use `0x78`-`0x7F` (or send the -equivalent text through `0x42`) because future upstream commands may reuse the -remaining legacy IDs. +deprecated inbound aliases so existing clients continue to work. A one-byte +`0x42` frame is the legacy FEM-gain GET; `0x42` followed by text is the official +`CMD_RUN_CLI_COMMAND`. New clients should use `CMD_RUN_CLI_COMMAND` for all of +these settings, rather than allocating additional command bytes. For example, +send `0x42` followed by `get radio.rxgain` or `set radio.rxgain on`. The reply is +`RESP_CODE_CLI_REPLY` followed by the normal CLI reply text. -Both gain-command pairs can be used over the normal binary Companion -connection; USB does not need to enter terminal mode. Inside the transport's -normal Companion frame, send a GET command as its single command byte. Send a -SET command followed by one byte (`0` for off, `1` for on). A GET reply is -`RESP_CODE_OK` followed by the saved state; a successful SET reply is -`RESP_CODE_OK`. Invalid states return `ERR_CODE_ILLEGAL_ARG`. A temporarily -busy radio returns `ERR_CODE_BAD_STATE`; unsupported boosted gain returns -`ERR_CODE_UNSUPPORTED_CMD`. The FEM pair also returns -`ERR_CODE_UNSUPPORTED_CMD` when the board cannot control its external LNA. Raw -unframed text such as `set radio.rxgain off` still requires the USB terminal -start token; the same text may instead be carried as a framed `0x42` command -over USB, BLE, or TCP. +The equivalent framed CLI commands are: -The WiFi power-save pair is available on ESP32 WiFi Companion builds over the -normal binary USB, BLE, or TCP port 5000 transport. `CMD_GET_WIFI_POWER_SAVE` -has no body and replies with `RESP_CODE_OK` followed by the effective mode byte. -`CMD_SET_WIFI_POWER_SAVE` is followed by that mode byte and replies with -`RESP_CODE_OK` after saving it: +| Setting | Commands | +|---|---| +| Radio receive gain | `get radio.rxgain`; `set radio.rxgain on|off` | +| FEM receive gain | `get radio.fem.rxgain`; `set radio.fem.rxgain on|off` | +| WiFi power save | `get wifi.powersave`; `set wifi.powersave none|min|max` | +| Bluetooth name | `get bluetooth.name`; `set bluetooth.name ` | + +The framed form works over the normal binary USB, BLE, or TCP transport and +does not need the USB terminal-start token. Unsupported settings return the +same explanatory text as the local CLI. + +WiFi power-save modes are: | Value | Mode | |---:|---| @@ -292,29 +278,15 @@ has no body and replies with `RESP_CODE_OK` followed by the effective mode byte. | `1` | `none` - no modem sleep | | `2` | `max` - maximum modem sleep | -An out-of-range value returns `ERR_CODE_ILLEGAL_ARG`. A non-WiFi Companion -returns `ERR_CODE_UNSUPPORTED_CMD`. Full Companion has Bluetooth enabled and -therefore returns `ERR_CODE_BAD_STATE` for `none`, because ESP32 WiFi/Bluetooth -coexistence requires modem sleep. On an ESP32 Full Companion whose primary mesh -radio is ESP-NOW, SET also returns `ERR_CODE_BAD_STATE` for `max` (value `2`): maximum -modem sleep can make the station miss ESP-NOW broadcasts, which the access point -cannot buffer. If an older image already saved `max`, GET reports `min` (value -`0`) and the running policy uses `min`. A storage failure also returns -`ERR_CODE_BAD_STATE`. If an otherwise permitted saved mode cannot be applied to -the active WiFi driver, SET still returns `RESP_CODE_OK` and the mode is applied -on the next connection. Device `powersaving` does not overwrite this setting. +Full Companion rejects WiFi mode `none` because simultaneous BLE transport +requires modem sleep. A Full Companion using ESP-NOW as its primary mesh radio +also rejects `max`, because maximum modem sleep can miss broadcasts that the +access point cannot buffer. If an older image saved `max`, the effective mode +is capped to and reported as `min`. Device `powersaving` remains independent. -The Bluetooth-name pair is available on Companion builds regardless of the -currently selected transport, so a host can configure a BLE-capable image over -USB or TCP before rebooting into it. `CMD_GET_BLUETOOTH_NAME` has no body and -replies with `RESP_CODE_OK`, a one-byte mode (`0` for the default name, `1` for -a custom name), then the effective UTF-8 device name without a terminator. -`CMD_SET_BLUETOOTH_NAME` is followed by zero to 31 UTF-8 bytes. A non-empty -body stores that exact complete Bluetooth name; a zero-length body clears the -override and restores `MeshCore-`. A successful SET replies with -`RESP_CODE_OK`, and the new label takes effect after reboot. Embedded NULs, -control characters, malformed UTF-8, or oversized values return -`ERR_CODE_ILLEGAL_ARG`; a storage failure returns `ERR_CODE_BAD_STATE`. +The Bluetooth name can be configured over USB or TCP before rebooting into BLE. +An empty/default selection restores `MeshCore-`; a custom name is +limited to 31 valid UTF-8 bytes and takes effect after reboot. ### Bluetooth LoRa mOTA source diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 18d4f92b..75210e77 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -123,21 +123,10 @@ static const uint32_t COMMAND_RADIO_APPLY_TIMEOUT_MS = 5000UL; #define CMD_SEND_RAW_PACKET 65 #define CMD_RUN_CLI_COMMAND 66 // v14+ -// Fork-specific host-to-device commands use the highest currently unused -// block below 0x80. Bit 7 is reserved by the transport for device-to-host -// push frames, so command IDs must remain in the low half of the byte range. -#define CMD_GET_RADIO_FEM_RXGAIN 0x78 -#define CMD_SET_RADIO_FEM_RXGAIN 0x79 -#define CMD_GET_RADIO_RXGAIN 0x7A -#define CMD_SET_RADIO_RXGAIN 0x7B -#define CMD_GET_WIFI_POWER_SAVE 0x7C -#define CMD_SET_WIFI_POWER_SAVE 0x7D -#define CMD_GET_BLUETOOTH_NAME 0x7E -#define CMD_SET_BLUETOOTH_NAME 0x7F - // Compatibility aliases used by this fork before upstream assigned 0x42 to // CMD_RUN_CLI_COMMAND. A one-byte 0x42 frame remains distinguishable from the -// official command, whose frame always includes CLI text. +// official command, whose frame always includes CLI text. New clients should +// use CMD_RUN_CLI_COMMAND rather than allocating more one-byte command IDs. #define LEGACY_CMD_GET_RADIO_FEM_RXGAIN 0x42 #define LEGACY_CMD_SET_RADIO_FEM_RXGAIN 0x43 #define LEGACY_CMD_GET_RADIO_RXGAIN 0x44 @@ -4113,8 +4102,7 @@ void MyMesh::handleCmdFrame(size_t len) { } else { writeErrFrame(ERR_CODE_ILLEGAL_ARG); } - } else if (cmd_frame[0] == CMD_GET_RADIO_FEM_RXGAIN - || cmd_frame[0] == LEGACY_CMD_GET_RADIO_FEM_RXGAIN) { + } else if (cmd_frame[0] == LEGACY_CMD_GET_RADIO_FEM_RXGAIN) { if (len != 1) { writeErrFrame(ERR_CODE_ILLEGAL_ARG); } else if (!board.canControlLoRaFemLna()) { @@ -4125,8 +4113,7 @@ void MyMesh::handleCmdFrame(size_t len) { memcpy(&out_frame[1], &value, 1); _serial->writeFrame(out_frame, 2); } - } else if (cmd_frame[0] == CMD_SET_RADIO_FEM_RXGAIN - || cmd_frame[0] == LEGACY_CMD_SET_RADIO_FEM_RXGAIN) { + } else if (cmd_frame[0] == LEGACY_CMD_SET_RADIO_FEM_RXGAIN) { uint8_t value = len >= 2 ? cmd_frame[1] : 0; if (len != 2) { writeErrFrame(ERR_CODE_ILLEGAL_ARG); @@ -4141,8 +4128,7 @@ void MyMesh::handleCmdFrame(size_t len) { } else { writeErrFrame(ERR_CODE_ILLEGAL_ARG); } - } else if (cmd_frame[0] == CMD_GET_RADIO_RXGAIN - || cmd_frame[0] == LEGACY_CMD_GET_RADIO_RXGAIN) { + } else if (cmd_frame[0] == LEGACY_CMD_GET_RADIO_RXGAIN) { if (len != 1) { writeErrFrame(ERR_CODE_ILLEGAL_ARG); } else if (!radio_driver.supportsRxBoostedGainMode()) { @@ -4152,8 +4138,7 @@ void MyMesh::handleCmdFrame(size_t len) { out_frame[1] = _prefs.rx_boosted_gain ? 1 : 0; _serial->writeFrame(out_frame, 2); } - } else if (cmd_frame[0] == CMD_SET_RADIO_RXGAIN - || cmd_frame[0] == LEGACY_CMD_SET_RADIO_RXGAIN) { + } else if (cmd_frame[0] == LEGACY_CMD_SET_RADIO_RXGAIN) { uint8_t value = len >= 2 ? cmd_frame[1] : 0; if (len != 2 || value > 1) { writeErrFrame(ERR_CODE_ILLEGAL_ARG); @@ -4164,8 +4149,7 @@ void MyMesh::handleCmdFrame(size_t len) { } else { writeOKFrame(); } - } else if (cmd_frame[0] == CMD_GET_WIFI_POWER_SAVE - || cmd_frame[0] == LEGACY_CMD_GET_WIFI_POWER_SAVE) { + } else if (cmd_frame[0] == LEGACY_CMD_GET_WIFI_POWER_SAVE) { if (len != 1) { writeErrFrame(ERR_CODE_ILLEGAL_ARG); } else { @@ -4177,8 +4161,7 @@ void MyMesh::handleCmdFrame(size_t len) { writeErrFrame(ERR_CODE_UNSUPPORTED_CMD); #endif } - } else if (cmd_frame[0] == CMD_SET_WIFI_POWER_SAVE - || cmd_frame[0] == LEGACY_CMD_SET_WIFI_POWER_SAVE) { + } else if (cmd_frame[0] == LEGACY_CMD_SET_WIFI_POWER_SAVE) { if (len != 2) { writeErrFrame(ERR_CODE_ILLEGAL_ARG); } else { @@ -4199,8 +4182,7 @@ void MyMesh::handleCmdFrame(size_t len) { writeErrFrame(ERR_CODE_UNSUPPORTED_CMD); #endif } - } else if (cmd_frame[0] == CMD_GET_BLUETOOTH_NAME - || cmd_frame[0] == LEGACY_CMD_GET_BLUETOOTH_NAME) { + } else if (cmd_frame[0] == LEGACY_CMD_GET_BLUETOOTH_NAME) { if (len != 1) { writeErrFrame(ERR_CODE_ILLEGAL_ARG); } else { @@ -4213,8 +4195,7 @@ void MyMesh::handleCmdFrame(size_t len) { BLE_NAME_PREFIX, _prefs.node_name); _serial->writeFrame(out_frame, 2 + strlen(effective_name)); } - } else if (cmd_frame[0] == CMD_SET_BLUETOOTH_NAME - || cmd_frame[0] == LEGACY_CMD_SET_BLUETOOTH_NAME) { + } else if (cmd_frame[0] == LEGACY_CMD_SET_BLUETOOTH_NAME) { const size_t name_len = len - 1; if (name_len > mesh::companion::BLUETOOTH_NAME_MAX_BYTES || memchr(&cmd_frame[1], 0, name_len) != NULL) {