Expose Companion WiFi and radio power controls

This commit is contained in:
mikecarper committed 2026-08-18 16:51:59 -07:00
1 parent 7a4da3ccfb
commit e4c8ae5a0b
27 files changed
+1840 -1349

No files matched your search

+12 -2
View File
@@ -332,7 +332,9 @@ You can flash the merged firmware using either the web flasher or the command li
- `WITH_SNMP=1` - Enable SNMP agent (optional, see [MQTT_SNMP.md](MQTT_SNMP.md))
- `MQTT_DEBUG=1` - Enable debug logging (optional)
- `MQTT_WIFI_TX_POWER` - WiFi TX power level (default: `WIFI_POWER_11dBm`)
- ~~`MQTT_WIFI_POWER_SAVE_DEFAULT`~~ - Removed; all builds now default to `none` (no power save)
- `DEFAULT_WIFI_POWER_SAVE_MODE` - Fresh-install WiFi modem-sleep default:
`0` = `min`, `1` = `none`, `2` = `max`. The Cascade profile sets `0`;
target-default builds use `1`.
#### Compile-time fresh-install defaults (`src/helpers/MQTTDefaults.h`)
@@ -344,6 +346,7 @@ Optional PlatformIO `build_flags` override defaults written when `/mqtt_prefs` i
| `MQTT_DEFAULT_IATA` | (empty) | e.g. `'"YYZ"'` |
| `MQTT_DEFAULT_TIMEZONE` | (empty) | e.g. `'"America/Toronto"'` |
| `MQTT_DEFAULT_TIMEZONE_OFFSET` | `0` | Fallback hours when TZ string is empty |
| `DEFAULT_WIFI_POWER_SAVE_MODE` | `1` (`none`); Cascade profile: `0` (`min`) | Fresh MQTT and standalone WebConfig WiFi setting; saved settings take precedence |
Example community build:
@@ -377,7 +380,8 @@ The MQTT bridge comes with the following defaults for fresh installs (unless ove
- **Per-slot packet filters**: `all` (every payload type is uploaded)
- **WiFi SSID**: (blank - must be configured)
- **WiFi Password**: (blank - optional for open networks)
- **WiFi Power Save**: `none` (no power save)
- **WiFi Power Save**: `min` for Cascade-profile builds; otherwise `none`
(a saved setting takes precedence)
- **Timezone**: (blank - uses UTC until configured, unless `MQTT_DEFAULT_TIMEZONE` is set at build time)
- **Timezone Offset**: 0 (fallback, no offset, unless `MQTT_DEFAULT_TIMEZONE_OFFSET` is set)
- **Repeat (forwarding)**: On (set `repeat off` for receive-only observers)
@@ -613,6 +617,12 @@ These settings apply across all MQTT slots:
- `min` - Minimum power saving (balanced performance and power)
- `max` - Maximum power saving (lowest power consumption, may affect performance)
On an ESP32 MQTT Companion, the Companion-owned `mesh-wifi` setting is the
canonical WiFi power-save value. It is exposed in Companion WebConfig and the
binary Companion protocol. Full Companion also exposes it on the USB terminal
and TCP port 5002, but rejects `none` because its BLE transport requires modem
sleep. Changing Companion device `powersaving` does not overwrite this value.
### Timezone Commands
#### Get Commands
+2 -2
View File
@@ -934,7 +934,7 @@ prompt_for_radio_build_settings() {
prompt_for_firmware_profile_settings() {
local -a options=(
"Keep target defaults"
"Cascade: path.hash.mode=2 / loop.detect=minimal / cad=on / rxdelay=2 / agc.reset.interval=8 / advert.interval=0 / flood.advert.interval=83 / multi.acks=1 / companion.manual.add=1 / companion.autoadd=0"
"Cascade: power saving + RXPS on / WiFi power save=min / path.hash.mode=2 / loop.detect=minimal / cad=on / rxdelay=2 / agc.reset.interval=8 / advert.interval=0 / flood.advert.interval=83 / multi.acks=1 / companion.manual.add=1 / companion.autoadd=0"
)
clear_firmware_profile_overrides
@@ -2419,7 +2419,7 @@ apply_radio_overrides() {
apply_firmware_profile_overrides() {
case "${FIRMWARE_PROFILE_OVERRIDE,,}" in
cascade)
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DCASCADE_PROFILE=1 -DDEFAULT_PATH_HASH_MODE=2 -DDEFAULT_LOOP_DETECT=1 -DDEFAULT_CAD_ENABLED=1 -DDEFAULT_RX_DELAY_BASE=2.0f -DDEFAULT_AGC_RESET_INTERVAL_SECONDS=8 -DDEFAULT_ADVERT_INTERVAL_MINUTES=0 -DDEFAULT_FLOOD_ADVERT_INTERVAL_HOURS=83 -DDEFAULT_MULTI_ACKS=1 -DDEFAULT_MANUAL_ADD_CONTACTS=1 -DDEFAULT_AUTOADD_CONFIG=0 -DDEFAULT_POWERSAVING_ENABLED=1 -DDEFAULT_RXPS_ENABLED=1 -DDEFAULT_RXPS_LEVEL=8 -DDEFAULT_RXPS_PREAMBLE=16 -DRXPS_FIXED_ENABLED=1 -DRXPS_FIXED_LEVEL=8 -DRXPS_FIXED_PREAMBLE=16"
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DCASCADE_PROFILE=1 -DDEFAULT_PATH_HASH_MODE=2 -DDEFAULT_LOOP_DETECT=1 -DDEFAULT_CAD_ENABLED=1 -DDEFAULT_RX_DELAY_BASE=2.0f -DDEFAULT_AGC_RESET_INTERVAL_SECONDS=8 -DDEFAULT_ADVERT_INTERVAL_MINUTES=0 -DDEFAULT_FLOOD_ADVERT_INTERVAL_HOURS=83 -DDEFAULT_MULTI_ACKS=1 -DDEFAULT_MANUAL_ADD_CONTACTS=1 -DDEFAULT_AUTOADD_CONFIG=0 -DDEFAULT_POWERSAVING_ENABLED=1 -DDEFAULT_RXPS_ENABLED=1 -DDEFAULT_RXPS_LEVEL=8 -DDEFAULT_RXPS_PREAMBLE=16 -DRXPS_FIXED_ENABLED=1 -DRXPS_FIXED_LEVEL=8 -DRXPS_FIXED_PREAMBLE=16 -DDEFAULT_WIFI_POWER_SAVE_MODE=0"
;;
esac
}
+34 -13
View File
@@ -77,6 +77,8 @@ Fresh MQTT observer settings are:
- slot 1 set to `analyzer-us`;
- slot 2 set to `analyzer-eu`;
- slots 3 through 6 disabled;
- WiFi modem power saving set to `min` in the Cascade profile and `none` in
target-default builds;
- WiFi SSID and IATA code empty.
Up to six broker configurations can be saved. The number that can be active at
@@ -196,8 +198,17 @@ the station IP.
If the configured network remains unavailable for two minutes, the companion
opens its setup AP so the credentials can be repaired. It continues retrying
the saved network. WiFi modem sleep is forced off on WiFi companions because
modem-sleep pauses can interfere with timely LoRa radio servicing.
the saved network. WiFi modem sleep has its own persisted `wifi.powersave`
setting and is independent of the Companion device `powersaving` setting. The
WiFi card in Companion WebConfig exposes `none`, `min`, and `max`. Fresh
Cascade-profile builds select `min`; target-default builds select `none`.
A saved setting takes precedence after an upgrade.
WiFi-only Companions can use all three modes. A Full Companion also runs BLE,
so ESP32 WiFi/Bluetooth coexistence requires at least minimum modem sleep. It
reports `min` when an old or target-default `none` value is found and rejects a
new `none` selection while Bluetooth is compiled in. Device power saving can
therefore be turned on or off without changing the selected WiFi modem policy.
WiFi companions do not have the repeater/room-server admin CLI password model,
so their LAN WebConfig page is intentionally unauthenticated. Use them only on
@@ -207,14 +218,16 @@ The WebConfig **Advanced** card exposes device power saving on WiFi Companion,
repeater, and room-server builds. It also exposes RX power saving on radio chips
that support receive duty cycling. RXPS can select continuous receive, levels
1-10, automatic or explicit 16/32-symbol preambles, or manual receive/sleep
windows. Both settings are persisted across reboots.
windows. The WebConfig **WiFi** card exposes WiFi modem power saving. All three
settings are persisted across reboots.
The two settings are independent. A WiFi Companion keeps its transports
The three settings are independent. A WiFi Companion keeps its transports
available while device power saving reduces CPU and GPS idle power. An
infrastructure node can sleep when device power saving is enabled, so its WiFi
services may be temporarily unavailable. RXPS only duty-cycles the LoRa
receiver. Fresh Cascade-profile builds default to device power saving on and
RXPS on at level 8 with a 16-symbol preamble.
receiver. Fresh Cascade-profile builds default to device power saving on,
RXPS on at level 8 with a 16-symbol preamble, and WiFi modem power saving at
`min` on every build that includes ESP32 WiFi.
When `ENABLE_OTA` is included, a WiFi companion also listens on:
@@ -223,8 +236,8 @@ When `ENABLE_OTA` is included, a WiFi companion also listens on:
These ports do not replace the companion protocol on TCP 5000.
On a `companion_radio_full` build, port 5002 additionally accepts bounded
`tempradio` and `normalradio` commands, while LoRa staging and installation on
the Companion itself are disabled.
`tempradio`, `normalradio`, and `get/set wifi.powersave` commands, while LoRa
staging and installation on the Companion itself are disabled.
FULL ESP32 builds share the port 5001 folder seeder. It starts whenever that
role has a usable WiFi station or setup access point and stops when WiFi stops.
@@ -395,10 +408,16 @@ set wifi.powersave min
set wifi.powersave max
```
The default is `none`, which gives the most predictable MQTT and radio
performance at the highest power use. `min` and `max` reduce power but may add
latency or reduce reliability on busy nodes. WiFi companions always disable
modem sleep regardless of this observer setting.
Fresh Cascade-profile builds default to `min`; target-default builds use
`none`, which gives the most predictable MQTT and radio performance at the
highest power use. `min` and `max` reduce power but may add latency or reduce
reliability on busy nodes. A saved operator setting takes precedence on an
upgrade. ESP32 WiFi Companions expose the same values in their WebConfig WiFi
card. Full Companion also accepts the text commands from its USB terminal and
TCP port 5002. The normal binary Companion protocol can read or write the
setting over USB, BLE, or TCP port 5000 without entering terminal mode. Full
Companion rejects `none` because its simultaneous BLE transport requires WiFi
modem sleep.
MQTT observer targets normally limit ESP32 WiFi transmit power to 11 dBm unless
the board configuration overrides `MQTT_WIFI_TX_POWER`. This setting affects
@@ -408,7 +427,9 @@ WiFi only, not LoRa transmit power.
`get wifi.status`, `get wifi.ssid`, `get wifi.powersave`, and `get wifi.cli`
are available on MQTT observers and on FULL non-MQTT repeater/room-server
builds with WebConfig.
builds with WebConfig. ESP32 WiFi Companions expose `wifi.powersave` through
their role-specific interfaces described above; they do not expose the full
infrastructure WiFi CLI family.
MQTT commands such as `get mqtt.status` and `set mqtt1.preset ...` still require
an MQTT observer target. Unknown settings return `Error: unknown setting:
<name>`. Older portable builds can instead report `Unsupported in this
+3 -1
View File
@@ -113,7 +113,9 @@ does not exist on that target:
- WebConfig and the `wifi.ssid`, `wifi.status`, `wifi.powersave`, and `wifi.cli` command
family require an ESP32 WebConfig build. FULL standalone repeater and
room-server builds support the corresponding WiFi setters and status
commands.
commands. ESP32 WiFi Companion exposes `wifi.powersave` in its WebConfig
form and binary protocol; Full Companion also exposes the text getter and
setter on its USB terminal and TCP port 5002.
- MQTT commands require an MQTT observer target.
- `discover.scopes` requires an MQTT observer with compiled neighbor support;
it does not independently require PSRAM or the FULL parser.
+11 -1
View File
@@ -201,7 +201,9 @@ SSID, LAN IP, and RSSI. When the shared OTA seeder is running, the reply appends
`OTA TCP 5001: listening` or `OTA TCP 5001: client connected`. WiFi being off
is normal while WebConfig is inactive; run `start webconfig` when a temporary
connection is wanted. `get wifi.powersave` reports the saved standalone setting
as `none`, `min`, or `max`; the default is `none`.
as `none`, `min`, or `max`. Fresh Cascade-profile builds default to `min`;
target-default builds use `none`. A saved setting takes precedence after an
upgrade.
The WiFi `set` commands work on MQTT observers and on FULL standalone
ESP32 repeater/room-server builds. On a standalone build, changing the SSID or
@@ -211,6 +213,14 @@ Power-save changes are applied immediately when WiFi is running and otherwise
take effect on the next connection. `get wifi.pwd` is intentionally unavailable
so the standalone password is never returned by the CLI.
ESP32 WiFi Companion WebConfig exposes the same `wifi.powersave` values in its
WiFi card. Full Companion also accepts `get/set wifi.powersave` from its USB
terminal and TCP port 5002. Binary Companion clients can use command bytes
`0x46` and `0x47` over USB, BLE, or TCP port 5000 without the terminal-start
token. 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.
The browser portal is not compiled into the two 4 MB
`LilyGo_TLora_V2_1_1_6_*_observer_mqtt` targets because it does not fit while
retaining the two app slots required for LoRa OTA. Their normal CLI settings
+37
View File
@@ -219,10 +219,47 @@ catalog; bytes `0x2C`-`0x31` are parked and `0x35` is unused.
| `0x3F` / `0x40` | `CMD_SET_DEFAULT_FLOOD_SCOPE` / `CMD_GET_DEFAULT_FLOOD_SCOPE` | Write or read the default flood scope. |
| `0x41` | `CMD_SEND_RAW_PACKET` | Queue a fully encoded raw mesh packet. |
| `0x42` / `0x43` | `CMD_GET_RADIO_FEM_RXGAIN` / `CMD_SET_RADIO_FEM_RXGAIN` | Read or set FEM receive gain. |
| `0x44` / `0x45` | `CMD_GET_RADIO_RXGAIN` / `CMD_SET_RADIO_RXGAIN` | Read or set the radio chip's boosted receive-gain mode. |
| `0x46` / `0x47` | `CMD_GET_WIFI_POWER_SAVE` / `CMD_SET_WIFI_POWER_SAVE` | Read or set ESP32 Companion WiFi modem sleep. |
The sections below detail the most common frames. Refer to the source named
above for command bodies that are not expanded here.
The additive hardware-setting commands `0x42`-`0x47` do not change any existing
version-13 frame layout. Clients should probe the command they need and treat
`ERR_CODE_UNSUPPORTED_CMD` as feature absence.
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
text such as `set radio.rxgain off` still requires the USB terminal start token.
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 one mode byte.
`CMD_SET_WIFI_POWER_SAVE` is followed by that mode byte and replies with
`RESP_CODE_OK` after saving it:
| Value | Mode |
|---:|---|
| `0` | `min` - minimum modem sleep |
| `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. A storage failure also returns
`ERR_CODE_BAD_STATE`. If an already-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.
### 1. App Start
**Purpose**: Initialize communication with the device. Must be sent first after connection.
+33 -9
View File
@@ -54,6 +54,9 @@ runtime, so the Companion bulk-build commands omit the redundant legacy
boost** switch. From the USB terminal, use:
```text
get radio.rxgain
set radio.rxgain off
set radio.rxgain on
get radio.fem.rxgain
set radio.fem.rxgain off
set radio.fem.rxgain on
@@ -62,8 +65,10 @@ set radio.fem.txgain off
set radio.fem.txgain on
```
The selected states are applied immediately and retained after reboot. FEM TX
gain is reported as unsupported on boards without software-selectable PA gain.
`radio.rxgain` controls the radio chip's boosted receive-gain mode; the FEM
commands control the external receive and transmit paths. The selected states
are applied immediately and retained after reboot. FEM TX gain is reported as
unsupported on boards without software-selectable PA gain.
Device power saving is separate from LoRa RXPS. It can be changed in WebConfig
with the **Device power saving** switch or from the USB terminal:
@@ -74,6 +79,23 @@ powersaving on
powersaving off
```
On ESP32, WiFi modem power saving is a third independent setting. Select it in
the WebConfig **WiFi** card, or use the Full Companion USB terminal or TCP port
5002:
```text
get wifi.powersave
set wifi.powersave min
set wifi.powersave max
```
The normal binary Companion connection can also read or write this setting over
USB, BLE, or TCP port 5000 without entering terminal mode. The saved values are
`0` for `min`, `1` for `none`, and `2` for `max`; see the
[Companion protocol](./companion_protocol.md#commands). Full Companion rejects
`none` because simultaneous WiFi and BLE require modem sleep. Fresh Cascade
builds select `min`, and an existing saved selection takes precedence.
On radios with RX duty-cycle support, WebConfig and the USB terminal also
expose the persisted RXPS setting:
@@ -98,11 +120,12 @@ On ESP32, enabling it lowers the CPU clock to 80 MHz, enables idle yielding,
and enables the configured GPS duty cycle. Disabling it restores the normal CPU
clock and keeps GPS awake. Full Companion transports remain available in both
states; WiFi modem sleep stays enabled when BLE is present because coexistence
requires it. While a native-USB host is enumerated, the platform sleep attempt
is held off so USB CDC remains responsive; detaching the host releases that
guard. CPU, radio-modem, and GPS power-saving settings remain active, and USB
power from a charger alone does not create a Companion session. The selected
state is retained after reboot.
requires it. Changing device power saving does not overwrite the saved WiFi
power-save mode. While a native-USB host is enumerated, the platform sleep
attempt is held off so USB CDC remains responsive; detaching the host releases
that guard. CPU, radio-modem, and GPS power-saving settings remain active, and
USB power from a charger alone does not create a Companion session. The
selected state is retained after reboot.
On the LilyGo T-Beam 1W Full Companion, press the physical `BOOT` button once
to turn the ESP32 WiFi radio and all WiFi services off or on. The screen confirms
@@ -139,7 +162,7 @@ itself.
| ESP32 | TCP 5000 | Binary Companion over WiFi |
| ESP32 | HTTP 80 | Companion WebConfig and first-boot WiFi setup |
| ESP32 | TCP 5001 | Host `.mota` folder from `motatool serve --tcp` |
| ESP32 | TCP 5002 | Local `ota`, `tempradio`, and `normalradio` console |
| ESP32 | TCP 5002 | Local `ota`, `tempradio`, `normalradio`, and `wifi.powersave` console |
| nRF52 | USB mOTA mode | Host `.mota` folder from `motatool serve --serial` |
Delivery-required replies are returned only to the interface which supplied the
@@ -194,7 +217,8 @@ The terminal supports Companion chat commands, including `channels`,
`channel <name-or-slot> <message>`, remote administration with
`login <admin-password>` and `cmd <remote-command>`, and routed
`trace [recipient-name-or-prefix]`, plus local `ota`, `tempradio`, and
`normalradio` controls. For example:
`normalradio` controls. ESP32 builds also provide local
`get/set wifi.powersave`. For example:
```text
channels
+30 -4
View File
@@ -85,16 +85,42 @@ set af {air-time-factor}
Sets the transmit air-time-factor. Deprecated - use `set dutycycle` instead.
```
powersaving
powersaving {on|off}
set powersaving {on|off}
```
Shows or changes Companion device power saving. On ESP32 this controls CPU and
GPS idle behavior; it does not change LoRa RXPS or WiFi modem sleep.
```
get radio.rxps
set radio.rxps {off|on|level 1-10 [preamble 16|32]|rx_us sleep_us}
```
Shows or changes LoRa receive duty cycling on supported radios. Fresh Cascade
builds select level 8 with a 16-symbol preamble.
```
get wifi.powersave
set wifi.powersave {none|min|max}
```
Shows or changes the persisted WiFi modem-sleep policy on ESP32 WiFi Companion
builds. Full Companion requires at least `min` while BLE is present and rejects
`none`. The WebConfig WiFi card and the normal binary Companion protocol expose
the same setting; binary clients do not need the terminal-start token.
```
get radio.rxgain
set radio.rxgain {on|off}
get radio.fem.rxgain
set radio.fem.rxgain {on|off}
get radio.fem.txgain
set radio.fem.txgain {on|off}
```
Shows or changes the external FEM receive-path LNA or transmit-path gain on
`radio.rxgain` changes the radio chip's boosted receive-gain mode. The FEM
settings control the external receive-path LNA or transmit-path gain on
supported boards. Changes are applied immediately and saved across reboots;
changing RX gain also recalibrates the radio noise floor. These settings are
separate from the SX126x `radio.rxgain` setting. Boards without the respective
control report it as unsupported.
changing either receive-gain path also recalibrates the radio noise floor.
Boards without the respective control report it as unsupported.
```
+21
View File
@@ -1,10 +1,31 @@
#pragma once
#include <stdint.h>
#if defined(ESP32) && defined(WIFI_SSID)
enum class CompanionWiFiPowerSaveResult : uint8_t {
Applied,
SavedForNextConnection,
InvalidMode,
BluetoothConflict,
StorageError,
};
// The requested state is persisted immediately. Network services transition
// from the main loop so a button callback never tears down an active server.
bool toggleCompanionWiFi();
bool isCompanionWiFiEnabled();
// Companion WiFi power save is stored in the shared mesh-wifi NVS namespace.
// The returned mode is always the effective mode: WiFi+BLE builds map a stale
// or default "none" value to "min" because modem sleep is required for radio
// coexistence.
uint8_t getCompanionWiFiPowerSave();
const char* getCompanionWiFiPowerSaveName();
const char* companionWiFiPowerSaveName(uint8_t mode);
CompanionWiFiPowerSaveResult setCompanionWiFiPowerSave(uint8_t mode);
void reloadCompanionWiFiPowerSave();
bool applyCompanionWiFiPowerSave();
#endif
+224 -6
View File
@@ -6,6 +6,11 @@
#include "helpers/radiolib/RXPowerSaving.h"
#include "helpers/radiolib/RxBoostedGainDefaults.h"
#if defined(ESP32) && defined(WIFI_SSID)
#include "CompanionWiFi.h"
#include <helpers/WiFiPowerSave.h>
#endif
#if defined(ESP32_PLATFORM)
#include <helpers/ESP32TrueRandom.h>
#endif
@@ -90,7 +95,7 @@ static const uint32_t COMMAND_RADIO_APPLY_TIMEOUT_MS = 5000UL;
#define CMD_SET_CUSTOM_VAR 41
#define CMD_GET_ADVERT_PATH 42
#define CMD_GET_TUNING_PARAMS 43
// NOTE: CMD range 44..49 parked, potentially for WiFi operations
// NOTE: decimal command IDs 44..49 (0x2C..0x31) remain parked
#define CMD_SEND_BINARY_REQ 50
#define CMD_FACTORY_RESET 51
#define CMD_SEND_PATH_DISCOVERY_REQ 52
@@ -108,6 +113,10 @@ static const uint32_t COMMAND_RADIO_APPLY_TIMEOUT_MS = 5000UL;
#define CMD_SEND_RAW_PACKET 65
#define CMD_GET_RADIO_FEM_RXGAIN 66
#define CMD_SET_RADIO_FEM_RXGAIN 67
#define CMD_GET_RADIO_RXGAIN 68
#define CMD_SET_RADIO_RXGAIN 69
#define CMD_GET_WIFI_POWER_SAVE 70
#define CMD_SET_WIFI_POWER_SAVE 71
#if defined(RADIO_FEM_RXGAIN) && (RADIO_FEM_RXGAIN == 0)
static constexpr uint8_t DEFAULT_FEM_RX_GAIN = 0;
@@ -1513,6 +1522,9 @@ void MyMesh::begin(bool has_display, bool radio_available) {
applyMQTTDefaults(&_mqtt_prefs);
_mqtt_configured = CompanionMqttSetupPortal::loadStoredConfig(_mqtt_prefs);
if (!_mqtt_configured) applyMQTTDefaults(&_mqtt_prefs);
// Companion WiFi owns the connection and its mesh-wifi NVS setting is
// canonical. Keep MQTT reconnects from restoring a stale MQTT-pref value.
_mqtt_prefs.wifi_power_save = getCompanionWiFiPowerSave();
MQTTNodeInfo node_info;
node_info.node_name = _prefs.node_name;
@@ -1711,6 +1723,21 @@ bool MyMesh::handleFullOtaCommand(const char* command, char* reply,
if (!command || !reply || reply_size == 0) return false;
while (*command == ' ') command++;
#if defined(ESP32) && defined(WIFI_SSID)
if (strcmp(command, "get wifi.powersave") == 0) {
formatWiFiPowerSaving(reply, reply_size);
return true;
}
if (strcmp(command, "set wifi.powersave") == 0
|| strncmp(command, "set wifi.powersave ", 19) == 0) {
const char* value = command + 18;
while (*value == ' ') value++;
applyAndSaveWiFiPowerSaving(value, reply, reply_size);
return true;
}
#endif
if (strcmp(command, "tempradio") == 0) {
if (_temp_radio_set_at) {
snprintf(reply, reply_size, "TempRadio pending: %.3f,%.2f,%u,%u",
@@ -1938,8 +1965,13 @@ void MyMesh::getNodeSnapshot(WebConfigServer::NodeSnapshot& s) {
s.power_saving = _prefs.powersaving_enabled;
s.repeat = _prefs.client_repeat != 0;
s.capabilities = WebConfigServer::CAP_LOCATION | WebConfigServer::CAP_AIRTIME
| WebConfigServer::CAP_RX_DELAY | WebConfigServer::CAP_RX_GAIN
| WebConfigServer::CAP_POWER_SAVING;
| WebConfigServer::CAP_RX_DELAY | WebConfigServer::CAP_POWER_SAVING;
#if defined(ESP32) && defined(WIFI_SSID)
s.capabilities |= WebConfigServer::CAP_WIFI_POWER_SAVE;
#endif
if (radio_driver.supportsRxBoostedGainMode()) {
s.capabilities |= WebConfigServer::CAP_RX_GAIN;
}
if (board.canControlLoRaFemLna()) {
s.capabilities |= WebConfigServer::CAP_FEM_RX_GAIN;
}
@@ -2010,6 +2042,13 @@ void MyMesh::onConfigBatchStart() {
}
void MyMesh::onConfigBatchEnd() {
#if defined(ESP32) && defined(WIFI_SSID)
// The WebConfig WiFi form writes the canonical mesh-wifi namespace itself.
// Reload it here so the Companion runtime and MQTT reconnect policy cannot
// retain a stale modem-sleep mode.
reloadCompanionWiFiPowerSave();
syncWiFiPowerSaving();
#endif
#ifdef WITH_MQTT_BRIDGE
if (_wc_mqtt_dirty) {
CompanionMqttSetupPortal::saveStoredConfig(_mqtt_prefs);
@@ -2018,6 +2057,9 @@ void MyMesh::onConfigBatchEnd() {
MQTTPrefs verified;
_mqtt_configured = CompanionMqttSetupPortal::loadStoredConfig(verified);
if (_mqtt_configured) _mqtt_prefs = verified;
// The standalone Companion setting remains canonical even if MQTT config
// verification reloaded an older copy of this field.
_mqtt_prefs.wifi_power_save = getCompanionWiFiPowerSave();
}
#endif
_wc_mqtt_dirty = false;
@@ -2025,6 +2067,12 @@ void MyMesh::onConfigBatchEnd() {
void MyMesh::execCommand(char* cmd, char* reply) {
reply[0] = 0;
#if defined(ESP32) && defined(WIFI_SSID)
if (cmd && strcmp(cmd, "get wifi.powersave") == 0) {
formatWiFiPowerSaving(reply, 160);
return;
}
#endif
if (!cmd || strncmp(cmd, "set ", 4) != 0) {
strcpy(reply, "Error: unsupported command");
return;
@@ -2113,12 +2161,18 @@ void MyMesh::execCommand(char* cmd, char* reply) {
strcpy(reply, "Error: must be on or off");
} else {
if (strcmp(key, "radio.rxgain") == 0) {
_prefs.rx_boosted_gain = enabled;
if (_radio_available) radio_driver.setRxBoostedGainMode(enabled);
if (!radio_driver.supportsRxBoostedGainMode()) {
strcpy(reply, "Error: RX boosted gain unsupported");
return;
}
if (!applyAndSaveRxBoostedGain(enabled)) {
strcpy(reply, "Error: radio busy; retry");
return;
}
} else {
_prefs.client_repeat = enabled;
savePrefs();
}
savePrefs();
strcpy(reply, "OK");
}
return;
@@ -2131,6 +2185,12 @@ void MyMesh::execCommand(char* cmd, char* reply) {
applyAndSavePowerSaving(value, reply);
return;
}
#if defined(ESP32) && defined(WIFI_SSID)
if (strcmp(key, "wifi.powersave") == 0) {
applyAndSaveWiFiPowerSaving(value, reply, 160);
return;
}
#endif
if (strcmp(key, "radio.fem.rxgain") == 0) {
bool enabled;
if (!wcParseBool(value, enabled)) {
@@ -3357,6 +3417,50 @@ void MyMesh::handleCmdFrame(size_t len) {
} else {
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
}
} else if (cmd_frame[0] == CMD_GET_RADIO_RXGAIN) {
if (!radio_driver.supportsRxBoostedGainMode()) {
writeErrFrame(ERR_CODE_UNSUPPORTED_CMD);
} else {
out_frame[0] = RESP_CODE_OK;
out_frame[1] = _prefs.rx_boosted_gain ? 1 : 0;
_serial->writeFrame(out_frame, 2);
}
} else if (cmd_frame[0] == CMD_SET_RADIO_RXGAIN && len >= 2) {
uint8_t value = cmd_frame[1];
if (value > 1) {
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
} else if (!radio_driver.supportsRxBoostedGainMode()) {
writeErrFrame(ERR_CODE_UNSUPPORTED_CMD);
} else if (!applyAndSaveRxBoostedGain(value != 0)) {
writeErrFrame(ERR_CODE_BAD_STATE);
} else {
writeOKFrame();
}
} else if (cmd_frame[0] == CMD_GET_WIFI_POWER_SAVE) {
#if defined(ESP32) && defined(WIFI_SSID)
out_frame[0] = RESP_CODE_OK;
out_frame[1] = getCompanionWiFiPowerSave();
_serial->writeFrame(out_frame, 2);
#else
writeErrFrame(ERR_CODE_UNSUPPORTED_CMD);
#endif
} else if (cmd_frame[0] == CMD_SET_WIFI_POWER_SAVE && len >= 2) {
#if defined(ESP32) && defined(WIFI_SSID)
const uint8_t value = cmd_frame[1];
if (value > mesh::wifi::kPowerSaveMax) {
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
} else {
char reply[160];
if (applyAndSaveWiFiPowerSaving(
companionWiFiPowerSaveName(value), reply, sizeof(reply))) {
writeOKFrame();
} else {
writeErrFrame(ERR_CODE_BAD_STATE);
}
}
#else
writeErrFrame(ERR_CODE_UNSUPPORTED_CMD);
#endif
} else if (cmd_frame[0] == CMD_GET_ADVERT_PATH && len >= PUB_KEY_SIZE+2) {
// FUTURE use: uint8_t reserved = cmd_frame[1];
uint8_t *pub_key = &cmd_frame[2];
@@ -3579,6 +3683,17 @@ bool MyMesh::applyAndSaveFemTxGain(bool enabled) {
return true;
}
bool MyMesh::applyAndSaveRxBoostedGain(bool enabled) {
if (!radio_driver.supportsRxBoostedGainMode()
|| (_radio_available && !radio_driver.setRxBoostedGainMode(enabled))) {
return false;
}
_prefs.rx_boosted_gain = enabled ? 1 : 0;
savePrefs();
return true;
}
bool MyMesh::applyAndSavePowerSaving(const char* value, char* reply) {
bool enabled;
if (strcmp(value, "on") == 0) {
@@ -3597,6 +3712,66 @@ bool MyMesh::applyAndSavePowerSaving(const char* value, char* reply) {
return true;
}
#if defined(ESP32) && defined(WIFI_SSID)
void MyMesh::syncWiFiPowerSaving() {
const uint8_t mode = getCompanionWiFiPowerSave();
#ifdef WITH_MQTT_BRIDGE
_mqtt_prefs.wifi_power_save = mode;
#endif
#ifdef WITH_WEBCONFIG
if (_webconfig) _webconfig->reloadStandaloneWiFi();
#endif
}
void MyMesh::formatWiFiPowerSaving(char* reply, size_t reply_size) const {
if (!reply || reply_size == 0) return;
snprintf(reply, reply_size, "> %s", getCompanionWiFiPowerSaveName());
}
bool MyMesh::applyAndSaveWiFiPowerSaving(const char* value, char* reply,
size_t reply_size) {
if (!reply || reply_size == 0) return false;
uint8_t mode = mesh::wifi::kDefaultPowerSave;
if (value && strcmp(value, "min") == 0) {
mode = mesh::wifi::kPowerSaveMin;
} else if (value && strcmp(value, "none") == 0) {
mode = mesh::wifi::kPowerSaveNone;
} else if (value && strcmp(value, "max") == 0) {
mode = mesh::wifi::kPowerSaveMax;
} else {
snprintf(reply, reply_size,
"Error: power save must be none, min, or max");
return false;
}
const CompanionWiFiPowerSaveResult result =
setCompanionWiFiPowerSave(mode);
if (result == CompanionWiFiPowerSaveResult::BluetoothConflict) {
snprintf(reply, reply_size,
"Error: power save none is unavailable while Bluetooth is active");
return false;
}
if (result == CompanionWiFiPowerSaveResult::InvalidMode) {
snprintf(reply, reply_size,
"Error: power save must be none, min, or max");
return false;
}
if (result == CompanionWiFiPowerSaveResult::StorageError) {
snprintf(reply, reply_size, "Error: failed to save WiFi power save");
return false;
}
syncWiFiPowerSaving();
if (result == CompanionWiFiPowerSaveResult::Applied) {
snprintf(reply, reply_size, "OK - WiFi power save set to %s", value);
} else {
snprintf(reply, reply_size,
"OK - saved; WiFi power save applies on next connection");
}
return true;
}
#endif
bool MyMesh::applyAndSaveRxPowerSaving(const char* value, char* reply) {
if (!radio_driver.supportsRxPowerSaving()) {
strcpy(reply, "Error: RX power saving unsupported");
@@ -4505,6 +4680,19 @@ void MyMesh::handleTerminalCommand(char* command) {
(unsigned long)_prefs.rx_ps_rx_us,
(unsigned long)_prefs.rx_ps_sleep_us);
}
#if defined(ESP32) && defined(WIFI_SSID)
} else if (strcmp(command, "get wifi.powersave") == 0) {
char reply[160];
formatWiFiPowerSaving(reply, sizeof(reply));
Serial.printf(" %s\r\n", reply);
#endif
} else if (strcmp(command, "get radio.rxgain") == 0) {
if (!radio_driver.supportsRxBoostedGainMode()) {
Serial.print(" ERROR: RX boosted gain is unsupported on this radio\r\n");
} else {
Serial.printf(" radio.rxgain %s\r\n",
_prefs.rx_boosted_gain ? "on" : "off");
}
} else if (strcmp(command, "get radio.fem.rxgain") == 0) {
if (!board.canControlLoRaFemLna()) {
Serial.print(" ERROR: FEM RX gain control is unsupported on this board\r\n");
@@ -4529,6 +4717,30 @@ void MyMesh::handleTerminalCommand(char* command) {
char reply[160];
applyAndSaveRxPowerSaving(config + 11, reply);
Serial.printf(" %s\r\n", reply);
#if defined(ESP32) && defined(WIFI_SSID)
} else if (strncmp(config, "wifi.powersave", 14) == 0
&& (config[14] == 0 || config[14] == ' '
|| config[14] == '\t')) {
const char* value = config + 14;
while (*value == ' ' || *value == '\t') value++;
char reply[160];
applyAndSaveWiFiPowerSaving(value, reply, sizeof(reply));
Serial.printf(" %s\r\n", reply);
#endif
} else if (strncmp(config, "radio.rxgain", 12) == 0
&& (config[12] == 0 || config[12] == ' '
|| config[12] == '\t')) {
const char* value = config + 12;
while (*value == ' ' || *value == '\t') value++;
if (strcmp(value, "on") != 0 && strcmp(value, "off") != 0) {
Serial.print(" ERROR: use set radio.rxgain <on|off>\r\n");
} else if (!radio_driver.supportsRxBoostedGainMode()) {
Serial.print(" ERROR: RX boosted gain is unsupported on this radio\r\n");
} else if (!applyAndSaveRxBoostedGain(strcmp(value, "on") == 0)) {
Serial.print(" ERROR: radio busy; retry\r\n");
} else {
Serial.printf(" OK - radio.rxgain %s\r\n", value);
}
} else if (strncmp(config, "af ", 3) == 0) {
_prefs.airtime_factor = constrain((float)atof(config + 3), 0.0f, 9.0f);
savePrefs();
@@ -4591,8 +4803,14 @@ void MyMesh::handleTerminalCommand(char* command) {
Serial.print("Commands:\r\n");
Serial.print(" set {name|lat|lon|freq|tx|af} {value}\r\n");
Serial.print(" powersaving [on|off]\r\n");
#if defined(ESP32) && defined(WIFI_SSID)
Serial.print(" get wifi.powersave\r\n");
Serial.print(" set wifi.powersave <none|min|max>\r\n");
#endif
Serial.print(" get radio.rxps\r\n");
Serial.print(" set radio.rxps <off|on|level 1-10 [preamble 16|32]|rx_us sleep_us>\r\n");
Serial.print(" get radio.rxgain\r\n");
Serial.print(" set radio.rxgain <on|off>\r\n");
Serial.print(" get radio.fem.rxgain\r\n");
Serial.print(" set radio.fem.rxgain <on|off>\r\n");
Serial.print(" get radio.fem.txgain\r\n");
+7
View File
@@ -284,8 +284,15 @@ private:
void checkSerialInterface();
bool applyAndSaveFemRxGain(bool enabled);
bool applyAndSaveFemTxGain(bool enabled);
bool applyAndSaveRxBoostedGain(bool enabled);
bool applyAndSavePowerSaving(const char* value, char* reply);
bool applyAndSaveRxPowerSaving(const char* value, char* reply);
#if defined(ESP32) && defined(WIFI_SSID)
bool applyAndSaveWiFiPowerSaving(const char* value, char* reply,
size_t reply_size);
void formatWiFiPowerSaving(char* reply, size_t reply_size) const;
void syncWiFiPowerSaving();
#endif
#ifdef ENABLE_USB_INTERFACE
ContactInfo* getTerminalRecipient();
void printTerminalPath(const ContactInfo& recipient);
+93 -18
View File
@@ -52,6 +52,9 @@ MultiSerialInterface interface_manager;
#include <helpers/esp32/SerialWifiInterface.h>
#include <helpers/WiFiSetupPortal.h>
#include <helpers/WiFiReconnectPolicy.h>
#include <helpers/WiFiPowerSave.h>
#include <Preferences.h>
#include <esp_wifi.h>
SerialWifiInterface wifi_interface;
#ifndef WIFI_PWD
#define WIFI_PWD ""
@@ -188,15 +191,6 @@ static bool applyCompanionPowerSaving(bool enabled) {
}
#endif
#ifdef WIFI_SSID
#if defined(BLE_PIN_CODE)
// WiFi modem sleep is required for WiFi/BLE coexistence in Full Companion.
WiFi.setSleep(true);
#else
WiFi.setSleep(enabled);
#endif
#endif
Serial.printf("Device power saving %s: CPU %lu MHz\r\n",
enabled ? "on" : "off", (unsigned long)target_mhz);
return true;
@@ -473,6 +467,88 @@ void halt() {
static bool companion_wifi_active = false;
static bool companion_wifi_disable_in_progress = false;
static bool companion_wifi_services_stopped = false;
static bool companion_wifi_power_save_loaded = false;
static uint8_t companion_wifi_power_save = mesh::wifi::kDefaultPowerSave;
static bool companionWiFiBluetoothActive() {
#if defined(BLE_PIN_CODE)
return true;
#else
return false;
#endif
}
const char* companionWiFiPowerSaveName(uint8_t mode) {
if (mode == mesh::wifi::kPowerSaveMin) return "min";
if (mode == mesh::wifi::kPowerSaveMax) return "max";
return "none";
}
static wifi_ps_type_t companionWiFiPowerSaveType(uint8_t mode) {
if (mode == mesh::wifi::kPowerSaveNone) return WIFI_PS_NONE;
if (mode == mesh::wifi::kPowerSaveMax) return WIFI_PS_MAX_MODEM;
return WIFI_PS_MIN_MODEM;
}
void reloadCompanionWiFiPowerSave() {
uint8_t configured = mesh::wifi::kDefaultPowerSave;
Preferences nvs;
if (nvs.begin("mesh-wifi", true)) {
configured = nvs.getUChar("powersave", mesh::wifi::kDefaultPowerSave);
nvs.end();
}
companion_wifi_power_save = mesh::wifi::effectivePowerSave(
configured, companionWiFiBluetoothActive());
companion_wifi_power_save_loaded = true;
applyCompanionWiFiPowerSave();
}
uint8_t getCompanionWiFiPowerSave() {
if (!companion_wifi_power_save_loaded) reloadCompanionWiFiPowerSave();
return companion_wifi_power_save;
}
const char* getCompanionWiFiPowerSaveName() {
return companionWiFiPowerSaveName(getCompanionWiFiPowerSave());
}
bool applyCompanionWiFiPowerSave() {
if (!companion_wifi_power_save_loaded) {
reloadCompanionWiFiPowerSave();
return true;
}
if (WiFi.getMode() == WIFI_OFF) return true;
return esp_wifi_set_ps(
companionWiFiPowerSaveType(companion_wifi_power_save)) == ESP_OK;
}
CompanionWiFiPowerSaveResult setCompanionWiFiPowerSave(uint8_t mode) {
if (mode > mesh::wifi::kPowerSaveMax) {
return CompanionWiFiPowerSaveResult::InvalidMode;
}
if (mesh::wifi::effectivePowerSave(mode, companionWiFiBluetoothActive())
!= mode) {
return CompanionWiFiPowerSaveResult::BluetoothConflict;
}
Preferences nvs;
if (!nvs.begin("mesh-wifi", false)) {
return CompanionWiFiPowerSaveResult::StorageError;
}
const bool saved =
nvs.putUChar("powersave", mode) == sizeof(uint8_t);
nvs.end();
if (!saved) return CompanionWiFiPowerSaveResult::StorageError;
companion_wifi_power_save = mode;
companion_wifi_power_save_loaded = true;
if (WiFi.getMode() == WIFI_OFF) {
return CompanionWiFiPowerSaveResult::SavedForNextConnection;
}
return applyCompanionWiFiPowerSave()
? CompanionWiFiPowerSaveResult::Applied
: CompanionWiFiPowerSaveResult::SavedForNextConnection;
}
static void loadCompanionWiFiCredentials() {
companion_wifi_has_credentials = WiFiSetupPortal::loadStoredCredentials(
@@ -571,8 +647,9 @@ void halt() {
/* WIFI OTA CONSOLE - a tiny text CLI for OTA over WiFi. Connect with e.g. `nc <ip> 5002` and type
`ota status` / `ota ls` / `ota announce` / ... - one client at a time, on a DEDICATED port separate from
the companion (5000) and the seeder (5001). Full companions also accept `tempradio ...` and
`normalradio` here so a host can run an end-to-end mOTA source without occupying the binary port. */
the companion (5000) and the seeder (5001). Full companions also accept `tempradio ...`,
`normalradio`, and WiFi power-save commands here so a host can manage the source without
occupying the binary port. */
#if defined(ESP32) && defined(WIFI_SSID) && defined(ENABLE_OTA)
#include <helpers/ota/OtaCli.h> // mesh::ota::handle_ota_command(line, reply, board)
#include <helpers/esp32/WiFiOtaSeeder.h>
@@ -603,6 +680,7 @@ void halt() {
ota_console_client.print("OTA console - type `ota ...`");
#if defined(COMPANION_RADIO_FULL)
ota_console_client.print(" or `tempradio freq,bw,sf,cr,minutes`");
ota_console_client.print("; `get/set wifi.powersave`");
#endif
ota_console_client.print("\r\n> "); }
return;
@@ -615,7 +693,7 @@ void halt() {
char reply[160]; reply[0] = 0;
#if defined(COMPANION_RADIO_FULL)
if (!the_mesh.handleFullOtaCommand(ota_console_line, reply, sizeof(reply)))
strcpy(reply, "supported: ota ... | tempradio ... | normalradio");
strcpy(reply, "supported: ota ... | tempradio ... | normalradio | get/set wifi.powersave");
#else
if (!mesh::ota::handle_ota_command(ota_console_line, reply, board))
strcpy(reply, "only `ota ...` commands are supported on this console");
@@ -663,11 +741,7 @@ void halt() {
}
#endif
#if defined(BLE_PIN_CODE)
WiFi.setSleep(true);
#else
WiFi.setSleep(false);
#endif
applyCompanionWiFiPowerSave();
wifi_interface.begin(TCP_PORT);
wifi_interface.enable();
#ifdef ENABLE_OTA
@@ -952,7 +1026,8 @@ void setup() {
#endif
// keep frames intact and pace the contact stream when the host is slow
usb_serial_interface.enableFlowControl(true);
#if defined(ESP32) && defined(ARDUINO_USB_MODE) && ARDUINO_USB_MODE == 1
#if defined(ESP32) && defined(ARDUINO_USB_MODE) && ARDUINO_USB_MODE == 1 \
&& defined(ARDUINO_USB_CDC_ON_BOOT) && ARDUINO_USB_CDC_ON_BOOT
// a 256 byte TX buffer overflows during a contact sync, and write() blocks
// up to tx_timeout_ms per call against a stalled host (same reasoning as
// the kiss_modem tuning)
+3 -1
View File
@@ -7,6 +7,7 @@
#include "MQTTPrefs.h"
#include "MQTTPacketFilter.h"
#include "MQTTPresets.h"
#include "WiFiPowerSave.h"
// Compile-time defaults for fresh /mqtt_prefs (override via platformio build_flags).
// Example:
@@ -14,6 +15,7 @@
// -D MQTT_DEFAULT_IATA='"YYZ"'
// -D MQTT_DEFAULT_TIMEZONE='"America/Toronto"'
// -D MQTT_DEFAULT_TIMEZONE_OFFSET=-5
// -D DEFAULT_WIFI_POWER_SAVE_MODE=0 // min; 1=none, 2=max
#ifndef MQTT_DEFAULT_SLOT1_PRESET
#define MQTT_DEFAULT_SLOT1_PRESET "analyzer-us"
@@ -69,7 +71,7 @@ static inline void applyMQTTDefaults(MQTTPrefs* prefs) {
prefs->mqtt_tx_enabled = 2;
prefs->mqtt_rx_enabled = 1;
prefs->mqtt_status_interval = 300000;
prefs->wifi_power_save = 1;
prefs->wifi_power_save = mesh::wifi::kDefaultPowerSave;
mqttDefaultSlotPreset(prefs->mqtt_slot_preset[0], sizeof(prefs->mqtt_slot_preset[0]),
MQTT_DEFAULT_SLOT1_PRESET);
+21 -5
View File
@@ -2,16 +2,32 @@
#include <stdint.h>
// Stored WiFi power-save values are 0=min, 1=none, and 2=max. Build profiles
// may override the fresh-install default without changing the persisted wire /
// file representation. Existing saved settings always take precedence.
#ifndef DEFAULT_WIFI_POWER_SAVE_MODE
#define DEFAULT_WIFI_POWER_SAVE_MODE 1
#endif
#if DEFAULT_WIFI_POWER_SAVE_MODE < 0 || DEFAULT_WIFI_POWER_SAVE_MODE > 2
#error "DEFAULT_WIFI_POWER_SAVE_MODE must be 0 (min), 1 (none), or 2 (max)"
#endif
namespace mesh {
namespace wifi {
// MeshCore stores WiFi power save as: 0=min, 1=none, 2=max. ESP32 WiFi and
// Bluetooth coexistence requires modem sleep, so "none" must fall back to the
// minimum sleep policy whenever Bluetooth is active.
static constexpr uint8_t kPowerSaveMin = 0;
static constexpr uint8_t kPowerSaveNone = 1;
static constexpr uint8_t kPowerSaveMax = 2;
static constexpr uint8_t kDefaultPowerSave = DEFAULT_WIFI_POWER_SAVE_MODE;
// ESP32 WiFi and Bluetooth coexistence requires modem sleep, so "none" must
// fall back to the minimum sleep policy whenever Bluetooth is active.
inline uint8_t effectivePowerSave(uint8_t configured,
bool bluetooth_active) {
if (configured > 2) configured = 1;
return bluetooth_active && configured == 1 ? 0 : configured;
if (configured > kPowerSaveMax) configured = kDefaultPowerSave;
return bluetooth_active && configured == kPowerSaveNone
? kPowerSaveMin : configured;
}
} // namespace wifi
+1
View File
@@ -49,6 +49,7 @@ public:
* Radio interface.
*/
virtual bool setRxBoostedGainMode(bool) { return false; }
virtual bool supportsRxBoostedGainMode() const { return false; }
virtual bool getRxBoostedGainMode() const { return false; }
// ESP-NOW has no LoRa RX power-saving watchdog or noise-floor calibration.
File diff suppressed because it is too large. Load diff
+19 -15
View File
@@ -205,7 +205,8 @@ WebConfigServer::WebConfigServer(Callbacks* callbacks, void* mqtt_prefs, bool ow
if (obs) {
strncpy(_wifi_ssid, obs->wifi_ssid, sizeof(_wifi_ssid) - 1);
strncpy(_wifi_password, obs->wifi_password, sizeof(_wifi_password) - 1);
_wifi_power_save = obs->wifi_power_save <= 2 ? obs->wifi_power_save : 1;
_wifi_power_save = obs->wifi_power_save <= mesh::wifi::kPowerSaveMax
? obs->wifi_power_save : mesh::wifi::kDefaultPowerSave;
// Companion builds keep their canonical connection credentials in the
// shared mesh-wifi namespace. A fresh MQTT configuration can therefore
// have an empty MQTTPrefs WiFi field even while the companion is already
@@ -270,7 +271,8 @@ bool WebConfigServer::loadStandaloneWiFi(char* ssid, size_t ssid_len,
if (!nvs.begin("mesh-wifi", true)) return false;
String stored_ssid = nvs.getString("ssid", "");
String stored_password = nvs.getString("password", "");
uint8_t stored_ps = nvs.getUChar("powersave", 1);
uint8_t stored_ps = nvs.getUChar(
"powersave", mesh::wifi::kDefaultPowerSave);
nvs.end();
if (stored_ssid.length() >= ssid_len
|| stored_password.length() >= password_len) {
@@ -288,7 +290,8 @@ bool WebConfigServer::saveStandaloneWiFi(const char* ssid, const char* password,
uint8_t power_save) {
power_save = effectiveWiFiPowerSave(power_save);
if (!ssid || !ssid[0] || strlen(ssid) >= 32
|| (password && strlen(password) >= 64) || power_save > 2) {
|| (password && strlen(password) >= 64)
|| power_save > mesh::wifi::kPowerSaveMax) {
return false;
}
Preferences nvs;
@@ -347,7 +350,7 @@ bool WebConfigServer::setStandaloneWiFiPassword(const char* value, char* reply,
bool WebConfigServer::setStandaloneWiFiPowerSave(const char* value, char* reply,
size_t reply_len) {
if (!reply || reply_len == 0) return false;
uint8_t power_save = 1;
uint8_t power_save = mesh::wifi::kDefaultPowerSave;
if (!mesh::cli::parseStandaloneWiFiPowerSave(value, power_save)) {
snprintf(reply, reply_len, "Error: power save must be none, min, or max");
return false;
@@ -374,9 +377,9 @@ bool WebConfigServer::setStandaloneWiFiPowerSave(const char* value, char* reply,
esp_err_t apply_result = ESP_OK;
if (WiFi.getMode() != WIFI_OFF) {
const wifi_ps_type_t ps_mode =
power_save == 1 ? WIFI_PS_NONE
: power_save == 2 ? WIFI_PS_MAX_MODEM
: WIFI_PS_MIN_MODEM;
power_save == mesh::wifi::kPowerSaveNone ? WIFI_PS_NONE
: power_save == mesh::wifi::kPowerSaveMax ? WIFI_PS_MAX_MODEM
: WIFI_PS_MIN_MODEM;
apply_result = esp_wifi_set_ps(ps_mode);
}
if (apply_result == ESP_OK) {
@@ -419,7 +422,7 @@ bool WebConfigServer::formatWiFiSSID(char* reply, size_t reply_len) {
char ssid[32] = "";
char password[64] = "";
uint8_t power_save = 1;
uint8_t power_save = mesh::wifi::kDefaultPowerSave;
bool configured = loadStandaloneWiFi(
ssid, sizeof(ssid), password, sizeof(password), &power_save);
if (_active && _active->_wifi_ssid[0]) {
@@ -435,7 +438,7 @@ bool WebConfigServer::formatWiFiSSID(char* reply, size_t reply_len) {
bool WebConfigServer::formatWiFiPowerSave(char* reply, size_t reply_len) {
if (!reply || reply_len == 0) return false;
uint8_t power_save = 1;
uint8_t power_save = mesh::wifi::kDefaultPowerSave;
if (_active) {
power_save = _active->_wifi_power_save;
} else {
@@ -446,9 +449,9 @@ bool WebConfigServer::formatWiFiPowerSave(char* reply, size_t reply_len) {
}
const char* name = "none";
if (power_save == 0) {
if (power_save == mesh::wifi::kPowerSaveMin) {
name = "min";
} else if (power_save == 2) {
} else if (power_save == mesh::wifi::kPowerSaveMax) {
name = "max";
}
snprintf(reply, reply_len, "> %s", name);
@@ -477,7 +480,7 @@ bool WebConfigServer::formatWiFiStatus(char* reply, size_t reply_len) {
char ssid[32] = "";
char password[64] = "";
uint8_t power_save = 1;
uint8_t power_save = mesh::wifi::kDefaultPowerSave;
bool configured = loadStandaloneWiFi(
ssid, sizeof(ssid), password, sizeof(password), &power_save);
WebConfigServer* active = _active;
@@ -678,9 +681,10 @@ bool WebConfigServer::startAutoMode(char reply[]) {
_wifi_reconnect_tracker.noteDisconnected(millis());
WiFi.begin(_wifi_ssid, _wifi_password);
_wifi_power_save = effectiveWiFiPowerSave(_wifi_power_save);
wifi_ps_type_t ps_mode = _wifi_power_save == 1 ? WIFI_PS_NONE
: _wifi_power_save == 2 ? WIFI_PS_MAX_MODEM
: WIFI_PS_MIN_MODEM;
wifi_ps_type_t ps_mode =
_wifi_power_save == mesh::wifi::kPowerSaveNone ? WIFI_PS_NONE
: _wifi_power_save == mesh::wifi::kPowerSaveMax ? WIFI_PS_MAX_MODEM
: WIFI_PS_MIN_MODEM;
esp_wifi_set_ps(ps_mode);
_mode = MODE_CONNECTING;
_connect_deadline = millis() + 15000;
+2 -1
View File
@@ -28,6 +28,7 @@
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
#include <helpers/WebConfigBatch.h>
#include <helpers/WiFiPowerSave.h>
#include <helpers/WiFiReconnectPolicy.h>
class AsyncWebServer;
@@ -229,7 +230,7 @@ private:
uint32_t _connect_deadline = 0;
char _wifi_ssid[32] = {0};
char _wifi_password[64] = {0};
uint8_t _wifi_power_save = 1;
uint8_t _wifi_power_save = mesh::wifi::kDefaultPowerSave;
bool _cli_enabled = true;
// A `password` command has succeeded this session. Lets the CLI satisfy the
// initial-setup invariant across separate submissions; the form batch always
@@ -55,6 +55,7 @@ protected:
return ((CustomLLCC68 *)_radio)->setRxBoostedGainMode(en) == RADIOLIB_ERR_NONE;
}
public:
bool supportsRxBoostedGainMode() const override { return true; }
bool getRxBoostedGainMode() const override {
return ((CustomLLCC68 *)_radio)->getRxBoostedGainMode();
}
@@ -134,6 +134,7 @@ protected:
return ((CustomLR1110 *)_radio)->setRxBoostedGainMode(en) == RADIOLIB_ERR_NONE;
}
public:
bool supportsRxBoostedGainMode() const override { return true; }
bool getRxBoostedGainMode() const override {
return ((CustomLR1110 *)_radio)->getRxBoostedGainMode();
}
@@ -178,6 +178,7 @@ public:
void doResetAGC() override { }
bool supportsRxBoostedGainMode() const override { return true; }
bool getRxBoostedGainMode() const override {
return ((CustomLR2021 *)_radio)->getRxBoostedGainMode();
}
@@ -160,6 +160,7 @@ protected:
return ((CustomSX1262 *)_radio)->setRxBoostedGainMode(en) == RADIOLIB_ERR_NONE;
}
public:
bool supportsRxBoostedGainMode() const override { return true; }
bool getRxBoostedGainMode() const override {
return ((CustomSX1262 *)_radio)->getRxBoostedGainMode();
}
@@ -57,6 +57,7 @@ protected:
return ((CustomSX1268 *)_radio)->setRxBoostedGainMode(en) == RADIOLIB_ERR_NONE;
}
public:
bool supportsRxBoostedGainMode() const override { return true; }
bool getRxBoostedGainMode() const override {
return ((CustomSX1268 *)_radio)->getRxBoostedGainMode();
}
+1
View File
@@ -210,6 +210,7 @@ public:
float packetScore(float snr, int packet_len) override { return packetScoreInt(snr, 10, packet_len); } // assume sf=10
bool setRxBoostedGainMode(bool enabled);
virtual bool supportsRxBoostedGainMode() const { return false; }
virtual bool getRxBoostedGainMode() const { return false; }
virtual bool configSideDetectors(const uint8_t sideDetSFs[], uint8_t num, float bw) { return false; }
+1
View File
@@ -81,6 +81,7 @@ public:
uint32_t getPacketsSent() const { return n_sent; }
void resetStats() { n_recv = n_sent = n_recv_errors = 0; }
bool setRxBoostedGainMode(bool /*enabled*/) { return false; }
bool supportsRxBoostedGainMode() const { return false; }
bool getRxBoostedGainMode() const { return false; }
uint32_t getRxPsWatchdogSoftCount() const { return 0; }
uint32_t getRxPsWatchdogHardCount() const { return 0; }
@@ -442,12 +442,22 @@ TEST(CLICommandUtils, ValidatesStandaloneWiFiValues) {
}
TEST(CLICommandUtils, EnforcesWiFiSleepForBluetoothCoexistence) {
EXPECT_EQ(0, mesh::wifi::effectivePowerSave(0, true));
EXPECT_EQ(0, mesh::wifi::effectivePowerSave(1, true));
EXPECT_EQ(2, mesh::wifi::effectivePowerSave(2, true));
EXPECT_EQ(1, mesh::wifi::effectivePowerSave(1, false));
EXPECT_EQ(0, mesh::wifi::effectivePowerSave(99, true));
EXPECT_EQ(1, mesh::wifi::effectivePowerSave(99, false));
EXPECT_EQ(mesh::wifi::kPowerSaveMin,
mesh::wifi::effectivePowerSave(mesh::wifi::kPowerSaveMin, true));
EXPECT_EQ(mesh::wifi::kPowerSaveMin,
mesh::wifi::effectivePowerSave(mesh::wifi::kPowerSaveNone, true));
EXPECT_EQ(mesh::wifi::kPowerSaveMax,
mesh::wifi::effectivePowerSave(mesh::wifi::kPowerSaveMax, true));
EXPECT_EQ(mesh::wifi::kPowerSaveNone,
mesh::wifi::effectivePowerSave(mesh::wifi::kPowerSaveNone, false));
EXPECT_EQ(mesh::wifi::kPowerSaveMin,
mesh::wifi::effectivePowerSave(99, true));
EXPECT_EQ(mesh::wifi::kPowerSaveNone,
mesh::wifi::effectivePowerSave(99, false));
}
TEST(CLICommandUtils, DefaultsWiFiPowerSaveToNoneWithoutProfileOverride) {
EXPECT_EQ(mesh::wifi::kPowerSaveNone, mesh::wifi::kDefaultPowerSave);
}
TEST(CLICommandUtils, MatchesDiscoverNeighborsWithoutPrefixCollisions) {
+1 -1
View File
@@ -494,7 +494,7 @@ body.tab-cli{padding-bottom:0}
</div></div>
<div class="f"><label>Password</label><input type="password" data-k="wifi.pwd" autocomplete="off"></div>
<div class="f" data-cap="1024"><label>Power save</label>
<select data-k="wifi.powersave"><option value="min">Minimum</option><option value="none">None (default; lowest latency)</option><option value="max">Maximum (battery)</option></select></div>
<select data-k="wifi.powersave"><option value="min">Minimum</option><option value="none">None (lowest latency)</option><option value="max">Maximum (battery)</option></select></div>
</div>
<div class="card">
<h2>Maintenance</h2>