Fix Cascade power saving, USB Companion, and mOTA flows

This commit is contained in:
mikecarper committed 2026-08-18 14:02:10 -07:00
1 parent 0d4274ceea
commit 818f610ba3
59 files changed
+3946 -2437

No files matched your search

+38 -9
View File
@@ -290,6 +290,12 @@ void MyMesh::writeContactRespFrame(uint8_t code, const ContactInfo &contact) {
_serial->writeFrame(out_frame, i);
}
void MyMesh::stopContactsIterator() {
if (!_iter_started) return;
_iter_started = false;
if (_serial != NULL) _serial->unlockReplyRoute();
}
void MyMesh::updateContactFromFrame(ContactInfo &contact, uint32_t& last_mod, const uint8_t *frame, int len) {
int i = 0;
uint8_t code = frame[i++]; // eg. CMD_ADD_UPDATE_CONTACT
@@ -2368,6 +2374,10 @@ void MyMesh::startInterface(BaseSerialInterface &serial) {
serial.enable();
}
void MyMesh::cancelSerialResponseStream() {
stopContactsIterator();
}
void MyMesh::handleCmdFrame(size_t len) {
if (len == 0) {
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
@@ -2401,7 +2411,7 @@ void MyMesh::handleCmdFrame(size_t len) {
cmd_frame[len] = 0; // make app_name null terminated
MESH_DEBUG_PRINTLN("App %s connected", app_name);
_iter_started = false; // stop any left-over ContactsIterator
stopContactsIterator(); // stop any left-over ContactsIterator
cancelPendingRadioParamApply();
int i = 0;
out_frame[i++] = RESP_CODE_SELF_INFO;
@@ -2613,16 +2623,18 @@ void MyMesh::handleCmdFrame(size_t len) {
_iter_filter_since = 0;
}
// CONTACTS_START, every CONTACT, and END_OF_CONTACTS are one response
// transaction. Keep them on the transport which requested the list.
_serial->lockReplyRoute();
_iter = startContactsIterator();
_iter_started = true;
_most_recent_lastmod = 0;
uint8_t reply[5];
reply[0] = RESP_CODE_CONTACTS_START;
uint32_t count = getNumContacts(); // total, NOT filtered count
memcpy(&reply[1], &count, 4);
_serial->writeFrame(reply, 5);
// start iterator
_iter = startContactsIterator();
_iter_started = true;
_most_recent_lastmod = 0;
}
} else if (cmd_frame[0] == CMD_SET_ADVERT_NAME && len >= 2) {
int nlen = len - 1;
@@ -2970,7 +2982,7 @@ void MyMesh::handleCmdFrame(size_t len) {
self_id = identity;
writeOKFrame();
// re-load contacts, to invalidate ecdh shared_secrets
_iter_started = false;
stopContactsIterator();
resetContacts();
_store->loadContacts(this);
updateGpsTelemetryPolicy();
@@ -4482,6 +4494,17 @@ void MyMesh::handleTerminalCommand(char* command) {
char reply[160];
applyAndSavePowerSaving(command + 12, reply);
Serial.printf(" %s\r\n", reply);
} else if (strcmp(command, "get radio.rxps") == 0) {
if (!radio_driver.supportsRxPowerSaving()) {
Serial.print(" ERROR: RX power saving is unsupported on this radio\r\n");
} else {
Serial.printf(" radio.rxps %s,level=%u,preamble=%u,rx=%lu,sleep=%lu\r\n",
_prefs.rx_powersaving_enabled ? "on" : "off",
(unsigned)_prefs.rx_ps_level,
(unsigned)_prefs.rx_ps_preamble,
(unsigned long)_prefs.rx_ps_rx_us,
(unsigned long)_prefs.rx_ps_sleep_us);
}
} 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");
@@ -4502,6 +4525,10 @@ void MyMesh::handleTerminalCommand(char* command) {
char reply[160];
applyAndSavePowerSaving(config + 12, reply);
Serial.printf(" %s\r\n", reply);
} else if (strncmp(config, "radio.rxps ", 11) == 0) {
char reply[160];
applyAndSaveRxPowerSaving(config + 11, reply);
Serial.printf(" %s\r\n", reply);
} else if (strncmp(config, "af ", 3) == 0) {
_prefs.airtime_factor = constrain((float)atof(config + 3), 0.0f, 9.0f);
savePrefs();
@@ -4564,6 +4591,8 @@ 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");
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.fem.rxgain\r\n");
Serial.print(" set radio.fem.rxgain <on|off>\r\n");
Serial.print(" get radio.fem.txgain\r\n");
@@ -4780,7 +4809,7 @@ void MyMesh::checkCLIRescueCmd() {
void MyMesh::checkSerialInterface() {
size_t len = _serial->checkRecvFrame(cmd_frame);
if (!_serial->isConnected()) {
_iter_started = false;
stopContactsIterator();
cancelPendingRadioParamApply();
return;
}
@@ -4803,7 +4832,7 @@ void MyMesh::checkSerialInterface() {
memcpy(&out_frame[1], &_most_recent_lastmod,
4); // include the most recent lastmod, so app can update their 'since'
_serial->writeFrame(out_frame, 5);
_iter_started = false;
stopContactsIterator();
}
//} else if (!_serial->isWriteBusy()) {
// checkConnections(); // TODO - deprecate the 'Connections' stuff
+2
View File
@@ -116,6 +116,7 @@ public:
void activateRadio();
bool isRadioReady() const { return _radio_available; }
void startInterface(BaseSerialInterface &serial);
void cancelSerialResponseStream();
const char *getNodeName();
CompanionNodePrefs *getNodePrefs();
@@ -268,6 +269,7 @@ private:
void writeErrFrame(uint8_t err_code);
void writeDisabledFrame();
void writeContactRespFrame(uint8_t code, const ContactInfo &contact);
void stopContactsIterator();
void updateContactFromFrame(ContactInfo &contact, uint32_t& last_mod, const uint8_t *frame, int len);
void addToOfflineQueue(const uint8_t frame[], int len);
int getFromOfflineQueue(uint8_t frame[]);
+7 -3
View File
@@ -279,6 +279,7 @@ static bool isUsbTerminalDataConnected() {
}
static void enterUsbTerminalMode() {
the_mesh.cancelSerialResponseStream();
usb_serial_interface.setPassthroughMode(true);
clearUsbTerminalLine();
usb_terminal_discard_line = false;
@@ -318,6 +319,7 @@ static void leaveUsbMotaMode(bool acknowledge) {
}
static void enterUsbMotaMode() {
the_mesh.cancelSerialResponseStream();
usb_serial_interface.setPassthroughMode(true);
usb_mota_mode = true;
usb_mota_line_len = 0;
@@ -962,7 +964,8 @@ void setup() {
// activity: a real client has to send us frames.
usb_serial_interface.setConnectedCheck([]() {
uint32_t last = usb_serial_interface.getLastFrameMillis();
return (bool)Serial && last != 0 && (millis() - last) < USB_CLIENT_IDLE_TIMEOUT;
return (bool)Serial && usb_serial_interface.hasReceivedFrame()
&& (millis() - last) < USB_CLIENT_IDLE_TIMEOUT;
});
#elif (defined(ESP32) && defined(ARDUINO_USB_CDC_ON_BOOT) && ARDUINO_USB_CDC_ON_BOOT) \
|| defined(NRF52_PLATFORM) || defined(RP2040_PLATFORM)
@@ -1058,10 +1061,11 @@ void loop() {
#endif
// USB power alone (for example, a wall charger) must not disable power
// saving. Stay awake only while a computer has an active USB data session.
// saving. Stay awake only while an enumerated USB host is attached.
bool can_sleep = the_mesh.getNodePrefs()->powersaving_enabled
&& !the_mesh.hasPendingWork();
#if defined(NRF52_PLATFORM)
#if defined(NRF52_PLATFORM) \
|| (defined(ESP32_PLATFORM) && defined(ENABLE_USB_INTERFACE))
can_sleep = can_sleep && !board.isUsbHostConnected();
#endif
if (can_sleep) {
+10 -1
View File
@@ -3079,13 +3079,20 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
_prefs.battery_alert_enabled = 0;
_prefs.battery_alert_low_percent = BATTERY_ALERT_LOW_PERCENT_DEFAULT;
_prefs.battery_alert_critical_percent = BATTERY_ALERT_CRITICAL_PERCENT_DEFAULT;
_prefs.powersaving_enabled = DEFAULT_POWERSAVING_ENABLED ? 1 : 0;
#ifdef WITH_MQTT_BRIDGE
_prefs.agc_reset_interval = 7; // 28 seconds (secs/4) - prevents AGC drift on long-running observers
#endif
// Observer defaults (radio_watchdog, alert.*, snmp.*) moved to applyMQTTDefaults()
// in MQTTDefaults.h - they live in /mqtt_prefs now, not NodePrefs.
_prefs.rx_powersaving_enabled = DEFAULT_RXPS_ENABLED ? 1 : 0;
_prefs.rx_ps_level = DEFAULT_RXPS_LEVEL;
_prefs.rx_ps_preamble = DEFAULT_RXPS_PREAMBLE;
_prefs.rx_ps_rx_us = RX_POWERSAVING_DEFAULT_RX_US;
_prefs.rx_ps_sleep_us = RX_POWERSAVING_DEFAULT_SLEEP_US;
recalcRxPowerSavingFromLevel(_prefs.rx_ps_level, _prefs.sf, _prefs.bw,
_prefs.rx_ps_preamble, &_prefs.rx_ps_rx_us,
&_prefs.rx_ps_sleep_us);
// bridge defaults
_prefs.bridge_enabled = 1; // enabled
@@ -9574,6 +9581,7 @@ void MyMesh::getNodeSnapshot(WebConfigServer::NodeSnapshot& s) {
s.rx_ps_preamble = _prefs.rx_ps_preamble;
s.rx_ps_rx_us = _prefs.rx_ps_rx_us;
s.rx_ps_sleep_us = _prefs.rx_ps_sleep_us;
s.power_saving = _prefs.powersaving_enabled;
s.repeat = !_prefs.disable_fwd;
s.advert_interval = _prefs.advert_interval * 2;
s.flood_advert_interval = _prefs.flood_advert_interval;
@@ -9585,7 +9593,8 @@ void MyMesh::getNodeSnapshot(WebConfigServer::NodeSnapshot& s) {
| WebConfigServer::CAP_DELAYS | WebConfigServer::CAP_CAD
| WebConfigServer::CAP_RX_GAIN | WebConfigServer::CAP_REPEAT
| WebConfigServer::CAP_ADVERT | WebConfigServer::CAP_FLOOD
| WebConfigServer::CAP_LOOP | WebConfigServer::CAP_WIFI_POWER_SAVE;
| WebConfigServer::CAP_LOOP | WebConfigServer::CAP_WIFI_POWER_SAVE
| WebConfigServer::CAP_POWER_SAVING;
if (board.canControlLoRaFemLna()) {
s.capabilities |= WebConfigServer::CAP_FEM_RX_GAIN;
}
+10 -1
View File
@@ -1182,8 +1182,15 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
_prefs.interference_threshold = 0; // disabled
_prefs.radio_fem_rxgain = 1; // LoRa FEM RX gain on by default (FEM boards)
_prefs.cad_enabled = DEFAULT_CAD_ENABLED; // Cascade defaults CAD on; target default remains off
_prefs.powersaving_enabled = DEFAULT_POWERSAVING_ENABLED ? 1 : 0;
_prefs.rx_powersaving_enabled = DEFAULT_RXPS_ENABLED ? 1 : 0;
_prefs.rx_ps_level = DEFAULT_RXPS_LEVEL;
_prefs.rx_ps_preamble = DEFAULT_RXPS_PREAMBLE;
_prefs.rx_ps_rx_us = RX_POWERSAVING_DEFAULT_RX_US;
_prefs.rx_ps_sleep_us = RX_POWERSAVING_DEFAULT_SLEEP_US;
recalcRxPowerSavingFromLevel(_prefs.rx_ps_level, _prefs.sf, _prefs.bw,
_prefs.rx_ps_preamble, &_prefs.rx_ps_rx_us,
&_prefs.rx_ps_sleep_us);
#ifdef ROOM_PASSWORD
StrHelper::strncpy(_prefs.guest_password, ROOM_PASSWORD, sizeof(_prefs.guest_password));
#endif
@@ -1568,6 +1575,7 @@ void MyMesh::getNodeSnapshot(WebConfigServer::NodeSnapshot& s) {
s.rx_ps_preamble = _prefs.rx_ps_preamble;
s.rx_ps_rx_us = _prefs.rx_ps_rx_us;
s.rx_ps_sleep_us = _prefs.rx_ps_sleep_us;
s.power_saving = _prefs.powersaving_enabled;
s.repeat = !_prefs.disable_fwd;
s.advert_interval = _prefs.advert_interval * 2;
s.flood_advert_interval = _prefs.flood_advert_interval;
@@ -1579,7 +1587,8 @@ void MyMesh::getNodeSnapshot(WebConfigServer::NodeSnapshot& s) {
| WebConfigServer::CAP_DELAYS | WebConfigServer::CAP_CAD
| WebConfigServer::CAP_RX_GAIN | WebConfigServer::CAP_REPEAT
| WebConfigServer::CAP_ADVERT | WebConfigServer::CAP_FLOOD
| WebConfigServer::CAP_LOOP | WebConfigServer::CAP_WIFI_POWER_SAVE;
| WebConfigServer::CAP_LOOP | WebConfigServer::CAP_WIFI_POWER_SAVE
| WebConfigServer::CAP_POWER_SAVING;
if (board.canControlLoRaFemLna()) {
s.capabilities |= WebConfigServer::CAP_FEM_RX_GAIN;
}
+7
View File
@@ -937,8 +937,15 @@ SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Millise
_prefs.interference_threshold = 0; // disabled
_prefs.radio_fem_rxgain = 1; // LoRa FEM RX gain on by default (FEM boards)
_prefs.cad_enabled = DEFAULT_CAD_ENABLED; // Cascade defaults CAD on; target default remains off
_prefs.powersaving_enabled = DEFAULT_POWERSAVING_ENABLED ? 1 : 0;
_prefs.rx_powersaving_enabled = DEFAULT_RXPS_ENABLED ? 1 : 0;
_prefs.rx_ps_level = DEFAULT_RXPS_LEVEL;
_prefs.rx_ps_preamble = DEFAULT_RXPS_PREAMBLE;
_prefs.rx_ps_rx_us = RX_POWERSAVING_DEFAULT_RX_US;
_prefs.rx_ps_sleep_us = RX_POWERSAVING_DEFAULT_SLEEP_US;
recalcRxPowerSavingFromLevel(_prefs.rx_ps_level, _prefs.sf, _prefs.bw,
_prefs.rx_ps_preamble, &_prefs.rx_ps_rx_us,
&_prefs.rx_ps_sleep_us);
// GPS defaults
_prefs.gps_enabled = 0;