Defer busy Companion TX-power updates without interrupting RX
A settings save could reach the power setter while RX, TX, duty cycling or a profile transition owned the radio, returning BAD_STATE after earlier settings had already saved. Distinguish busy admission from a hardware rejection and reuse the existing command route, storage and three-second deadline for power. Acknowledge only after hardware acceptance and a successful preference save. Skip hardware work for verified unchanged power. Invalidate that verification after failed writes or radio initialization, retaining rollback and exclusive beacon/Morse ownership. Keep the existing firmware memory limits and features. Cover retries, captured routes, timeout/rollover, disconnect, partial writes, failed saves and recovery. Keep ESP-NOW/simulation adapters compatible and add ESP-NOW Companion compilation to the existing sequential smoke job.
This commit is contained in:
1 parent
af69918094
commit
8dfdba3f6d
17 files changed
+470
-57
No files matched your search
@@ -3546,12 +3546,24 @@ void MyMesh::finishRadioParamApply(float freq, float bw, uint8_t sf,
|
||||
writeOKFrame(route);
|
||||
}
|
||||
|
||||
void MyMesh::deferRadioCommand(uint8_t kind) {
|
||||
command_radio_reply_route = _serial->captureReplyRoute();
|
||||
if (command_radio_reply_route == NULL) {
|
||||
writeErrFrame(ERR_CODE_BAD_STATE);
|
||||
return;
|
||||
}
|
||||
command_radio_apply_pending = kind;
|
||||
command_radio_apply_deadline = futureMillis(COMMAND_RADIO_APPLY_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
void MyMesh::cancelPendingRadioParamApply() {
|
||||
if (!command_radio_apply_pending) return;
|
||||
|
||||
const bool restore_tuple = command_radio_apply_pending == 1;
|
||||
command_radio_apply_pending = false;
|
||||
command_radio_apply_deadline = 0;
|
||||
command_radio_reply_route = NULL;
|
||||
if (!restore_tuple) return;
|
||||
// The requested tuple was not committed. Reassert the persisted tuple in
|
||||
// case a failed hardware apply only restored part of the old configuration.
|
||||
saved_radio_apply_pending = true;
|
||||
@@ -3569,6 +3581,22 @@ void MyMesh::servicePendingRadioParamApply() {
|
||||
// Leave enough queue headroom for the eventual command response.
|
||||
if (_serial->isReplyRouteWriteBusy(command_radio_reply_route)) return;
|
||||
|
||||
const bool expired = static_cast<int32_t>(static_cast<uint32_t>(
|
||||
_ms->getMillis() - command_radio_apply_deadline)) >= 0;
|
||||
if (command_radio_apply_pending == 2) {
|
||||
const auto result = expired ? RadioSettingResult::RadioBusy
|
||||
: applyAndSaveTxPower(static_cast<int8_t>(command_radio_repeat), true);
|
||||
if (result == RadioSettingResult::RadioBusy
|
||||
&& !expired) return;
|
||||
if (result == RadioSettingResult::Saved) writeOKFrame(command_radio_reply_route);
|
||||
else writeErrFrame(result == RadioSettingResult::SaveFailed
|
||||
? ERR_CODE_FILE_IO_ERROR : ERR_CODE_BAD_STATE, command_radio_reply_route);
|
||||
// The checked setter owns any failed-save rollback. Retire this command
|
||||
// without forcing a tuple restore or consuming a pending receive.
|
||||
cancelPendingRadioParamApply();
|
||||
return;
|
||||
}
|
||||
|
||||
mesh::RadioParamApplyResult result = tryApplyRadioParams(
|
||||
command_radio_freq, command_radio_bw, command_radio_sf, command_radio_cr);
|
||||
if (result == mesh::RadioParamApplyResult::APPLIED) {
|
||||
@@ -3585,8 +3613,7 @@ void MyMesh::servicePendingRadioParamApply() {
|
||||
} else if (result == mesh::RadioParamApplyResult::FAILED) {
|
||||
writeErrFrame(ERR_CODE_ILLEGAL_ARG, command_radio_reply_route);
|
||||
cancelPendingRadioParamApply();
|
||||
} else if (command_radio_apply_deadline == _ms->getMillis()
|
||||
|| millisHasNowPassed(command_radio_apply_deadline)) {
|
||||
} else if (expired) {
|
||||
writeErrFrame(ERR_CODE_BAD_STATE, command_radio_reply_route);
|
||||
cancelPendingRadioParamApply();
|
||||
}
|
||||
@@ -3608,7 +3635,7 @@ bool MyMesh::scheduleTempRadio(float freq, float bw, uint8_t sf, uint8_t cr,
|
||||
uint32_t timeout_mins, char* reply,
|
||||
size_t reply_size) {
|
||||
if (command_radio_apply_pending) {
|
||||
snprintf(reply, reply_size, "ERR companion radio change is already pending");
|
||||
snprintf(reply, reply_size, "ERR radio change pending");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -5648,13 +5675,13 @@ void MyMesh::execCommand(char* cmd, char* reply) {
|
||||
if (strcmp(key, "tx") == 0) {
|
||||
long parsed;
|
||||
if (!wcParseLong(value, -9, MAX_LORA_TX_POWER, parsed)) {
|
||||
snprintf(reply, 160, "Error: TX power must be -9 to %d", MAX_LORA_TX_POWER);
|
||||
snprintf(reply, 160, "Error: TX -9..%d dBm", MAX_LORA_TX_POWER);
|
||||
} else {
|
||||
const auto result = applyAndSaveTxPower(static_cast<int8_t>(parsed));
|
||||
strcpy(reply, result == RadioSettingResult::Saved ? "OK"
|
||||
: result == RadioSettingResult::RadioRejected
|
||||
: result != RadioSettingResult::SaveFailed
|
||||
? "Error: radio busy or TX power rejected"
|
||||
: "Error: TX power could not be saved");
|
||||
: "Error: TX not saved");
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -6767,18 +6794,12 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
// Capture this requester instead of relying on the mutable last-RX
|
||||
// route. This remains correct if a contact stream later unlocks or a
|
||||
// different transport supplies another command before completion.
|
||||
command_radio_reply_route = _serial->captureReplyRoute();
|
||||
if (command_radio_reply_route == NULL) {
|
||||
writeErrFrame(ERR_CODE_BAD_STATE);
|
||||
return;
|
||||
}
|
||||
command_radio_apply_pending = true;
|
||||
command_radio_freq = new_freq;
|
||||
command_radio_bw = new_bw;
|
||||
command_radio_sf = sf;
|
||||
command_radio_cr = cr;
|
||||
command_radio_repeat = repeat;
|
||||
command_radio_apply_deadline = futureMillis(COMMAND_RADIO_APPLY_TIMEOUT_MS);
|
||||
deferRadioCommand(1);
|
||||
MESH_DEBUG_PRINTLN("Deferred CMD_SET_RADIO_PARAMS while radio is busy");
|
||||
return;
|
||||
}
|
||||
@@ -6806,8 +6827,15 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
int8_t power = (int8_t)cmd_frame[1];
|
||||
if (power < -9 || power > MAX_LORA_TX_POWER) {
|
||||
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
|
||||
} else if (command_radio_apply_pending) {
|
||||
writeErrFrame(ERR_CODE_BAD_STATE);
|
||||
} else {
|
||||
const auto result = applyAndSaveTxPower(power);
|
||||
if (result == RadioSettingResult::RadioBusy) {
|
||||
command_radio_repeat = static_cast<uint8_t>(power);
|
||||
deferRadioCommand(2);
|
||||
return;
|
||||
}
|
||||
if (result == RadioSettingResult::Saved) writeOKFrame();
|
||||
else writeErrFrame(result == RadioSettingResult::RadioRejected
|
||||
? ERR_CODE_BAD_STATE : ERR_CODE_FILE_IO_ERROR);
|
||||
@@ -8451,10 +8479,15 @@ bool MyMesh::saveAdvertLocation(double latitude, double longitude) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MyMesh::RadioSettingResult MyMesh::applyAndSaveTxPower(int8_t power) {
|
||||
MyMesh::RadioSettingResult MyMesh::applyAndSaveTxPower(int8_t power, bool from_pending) {
|
||||
if (command_radio_apply_pending && !from_pending) return RadioSettingResult::RadioBusy;
|
||||
const int8_t previous = _prefs.tx_power_dbm;
|
||||
if (_radio_available && !radio_driver.setTxPower(power)) {
|
||||
return RadioSettingResult::RadioRejected;
|
||||
if (_radio_available) {
|
||||
const auto result = radio_driver.trySetTxPower(power);
|
||||
if (result != mesh::RadioParamApplyResult::APPLIED) {
|
||||
return result == mesh::RadioParamApplyResult::BUSY
|
||||
? RadioSettingResult::RadioBusy : RadioSettingResult::RadioRejected;
|
||||
}
|
||||
}
|
||||
_prefs.tx_power_dbm = power;
|
||||
if (savePrefs()) return RadioSettingResult::Saved;
|
||||
@@ -10924,15 +10957,15 @@ bool MyMesh::handleCommand(const char* command, uint32_t sender_timestamp,
|
||||
if (!mesh::cli::parseIntegerStrict(command + 7, parsed)) {
|
||||
mesh::cli::formatError(reply, reply_capacity, "invalid TX power");
|
||||
} else if (parsed < -9 || parsed > MAX_LORA_TX_POWER) {
|
||||
snprintf(reply, reply_capacity, "Error: TX power must be -9 to %d",
|
||||
snprintf(reply, reply_capacity, "Error: TX -9..%d dBm",
|
||||
MAX_LORA_TX_POWER);
|
||||
} else {
|
||||
const int8_t requested = static_cast<int8_t>(parsed);
|
||||
const auto result = applyAndSaveTxPower(requested);
|
||||
if (result == RadioSettingResult::RadioRejected) {
|
||||
if (result == RadioSettingResult::RadioRejected || result == RadioSettingResult::RadioBusy) {
|
||||
mesh::cli::formatError(reply, reply_capacity, "radio busy or TX power rejected");
|
||||
} else if (result == RadioSettingResult::SaveFailed) {
|
||||
mesh::cli::formatError(reply, reply_capacity, "TX power could not be saved");
|
||||
mesh::cli::formatError(reply, reply_capacity, "TX not saved");
|
||||
} else {
|
||||
strcpy(reply, "OK");
|
||||
}
|
||||
|
||||
@@ -675,8 +675,8 @@ private:
|
||||
bool applyAndSaveFemRxGain(bool enabled);
|
||||
bool applyAndSaveFemTxGain(bool enabled);
|
||||
bool applyAndSaveRxBoostedGain(bool enabled);
|
||||
enum class RadioSettingResult { Saved, RadioRejected, SaveFailed };
|
||||
RadioSettingResult applyAndSaveTxPower(int8_t power);
|
||||
enum class RadioSettingResult { Saved, RadioRejected, SaveFailed, RadioBusy };
|
||||
RadioSettingResult applyAndSaveTxPower(int8_t power, bool from_pending = false);
|
||||
bool handleCadCommand(const char* command, char* reply, size_t reply_size);
|
||||
bool saveBluetoothNameOverride(const char* name);
|
||||
bool applyAndSaveBluetoothName(const char* value, char* reply,
|
||||
@@ -749,6 +749,7 @@ private:
|
||||
void finishRadioParamApply(float freq, float bw, uint8_t sf, uint8_t cr,
|
||||
uint8_t repeat,
|
||||
BaseSerialInterface* route = nullptr);
|
||||
void deferRadioCommand(uint8_t kind);
|
||||
void cancelPendingRadioParamApply();
|
||||
void servicePendingRadioParamApply();
|
||||
void servicePendingSerialReply();
|
||||
@@ -912,7 +913,8 @@ private:
|
||||
bool _radio_available;
|
||||
unsigned long radio_apply_retry_at;
|
||||
uint8_t radio_apply_failures;
|
||||
bool command_radio_apply_pending;
|
||||
// 0: none, 1: modulation tuple, 2: TX power (stored in the repeat byte).
|
||||
uint8_t command_radio_apply_pending;
|
||||
float command_radio_freq;
|
||||
float command_radio_bw;
|
||||
uint8_t command_radio_sf;
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
#include "CompanionBluetooth.h"
|
||||
#include "CompanionWireless.h"
|
||||
#include "CompanionWiFi.h"
|
||||
#if defined(MESH_PRIMARY_ESPNOW) && MESH_PRIMARY_ESPNOW
|
||||
#include <WiFi.h>
|
||||
#endif
|
||||
#if COMPANION_FEATURE_USB_MOTA_SOURCE || COMPANION_FEATURE_BLE_MOTA_SOURCE
|
||||
#include <helpers/ota/MotaSourceSerial.h>
|
||||
#include <helpers/ota/OtaContext.h>
|
||||
|
||||
Reference in new issue
Block a user