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:
mikecarper committed 2026-10-25 11:57:03 -07:00
1 parent af69918094
commit 8dfdba3f6d
17 files changed
+470 -57

No files matched your search

+4
View File
@@ -69,6 +69,10 @@ public:
uint32_t intID();
bool setTxPower(int8_t dbm);
mesh::RadioParamApplyResult trySetTxPower(int8_t dbm) {
return setTxPower(dbm) ? mesh::RadioParamApplyResult::APPLIED
: mesh::RadioParamApplyResult::FAILED;
}
};
#if ESPNOW_DEBUG_LOGGING && ARDUINO
+24 -11
View File
@@ -243,6 +243,7 @@ void RadioLibWrapper::begin() {
#if MESH_ENABLE_TX_BEACONS
if (_beacon_owned) return;
#endif
_dbm_applied = false;
#if MESH_HIL_PROFILE_TIMING
mesh::profiletiming::initializeClock();
#endif
@@ -320,8 +321,13 @@ uint32_t RadioLibWrapper::getRngSeed() {
}
bool RadioLibWrapper::setTxPower(int8_t dbm) {
return trySetTxPower(dbm) == mesh::RadioParamApplyResult::APPLIED;
}
mesh::RadioParamApplyResult RadioLibWrapper::trySetTxPower(int8_t dbm) {
using Result = mesh::RadioParamApplyResult;
#if MESH_ENABLE_TX_BEACONS
if (_beacon_owned) return false;
if (_beacon_owned) return Result::BUSY;
#endif
#if MESH_ENABLE_CARRIER_TOOLS || MESH_ENABLE_MORSE_RX
// A CW power-meter sweep may re-key the carrier. During Morse that would
@@ -331,37 +337,42 @@ bool RadioLibWrapper::setTxPower(int8_t dbm) {
#if MESH_ENABLE_MORSE_RX
|| _morse_rx_owned
#endif
) && _cw_active) return false;
) && _cw_active) return Result::BUSY;
#if MESH_ENABLE_CARRIER_TOOLS
if (_cw_active) {
if (_cw_stopping) return false;
if (_cw_stopping) return Result::BUSY;
#if defined(MESH_HIL_TX_FREQ_MHZ)
if (!hilPacketTransmitAllowed() || _cw_profile != _active_profile
|| _cw_generation != _profile_generation) return false;
|| _cw_generation != _profile_generation) return Result::FAILED;
#endif
// Permit meter sweeps through the board's normal power/PA calibration.
// SX127x's power setter enters standby, so re-key without resetting the
// modem or extending the original CW deadline.
if (applyCachedTxPower(dbm) != RADIOLIB_ERR_NONE) {
stopCarrierWave();
return false;
return Result::FAILED;
}
if (_radio->transmitDirect() != RADIOLIB_ERR_NONE) {
stopCarrierWave();
return false;
return Result::FAILED;
}
_cur_dbm = dbm;
_dbm_valid = true;
return true;
return Result::APPLIED;
}
#else
if (_cw_active) return false;
if (_cw_active) return Result::BUSY;
#endif
#endif
// Settings screens also submit TX power when only another field changed.
// The verified normal power needs no SPI, standby, or interruption of RX.
if (_dbm_valid && _dbm_applied && _cur_dbm == dbm
&& !_profile_repair_required) return Result::APPLIED;
const uint8_t resume_rx = beginReconfigure();
if (resume_rx > 1) return false;
if (resume_rx > 1) return resume_rx == 2 ? Result::BUSY : Result::FAILED;
const int16_t status = applyCachedTxPower(dbm);
_dbm_applied = status == RADIOLIB_ERR_NONE;
if (status == RADIOLIB_ERR_NONE) {
_cur_dbm = dbm;
_dbm_valid = true;
@@ -370,7 +381,7 @@ bool RadioLibWrapper::setTxPower(int8_t dbm) {
(int)dbm, (int)status);
}
endReconfigure(resume_rx);
return status == RADIOLIB_ERR_NONE;
return status == RADIOLIB_ERR_NONE ? Result::APPLIED : Result::FAILED;
}
uint8_t RadioLibWrapper::beginReconfigure() {
@@ -516,6 +527,7 @@ bool RadioLibWrapper::restoreAfterDeepInit() {
#if MESH_ENABLE_TX_BEACONS
if (_beacon_owned) cacheBeaconRestoreParams();
#endif
_dbm_applied = false;
if (!radioDeepInit()) return false;
_rx_ps_armed = false;
@@ -533,7 +545,8 @@ bool RadioLibWrapper::restoreAfterDeepInit() {
restored = restored && applyProtocol(_physical_protocol);
#endif
if (_dbm_valid) {
restored = applyCachedTxPower(_cur_dbm) == RADIOLIB_ERR_NONE && restored;
_dbm_applied = applyCachedTxPower(_cur_dbm) == RADIOLIB_ERR_NONE;
restored = _dbm_applied && restored;
}
if (_rx_boosted_gain_valid) {
restored = applyRxBoostedGainMode(_cur_rx_boosted_gain) && restored;
+2
View File
@@ -108,6 +108,7 @@ protected:
int8_t _cur_dbm;
bool _cur_rx_boosted_gain;
bool _params_valid, _dbm_valid, _rx_boosted_gain_valid;
bool _dbm_applied = false;
mesh::RadioProfiles _profiles;
uint8_t _active_profile = 0;
uint16_t _physical_preamble = 0;
@@ -574,6 +575,7 @@ public:
const uint32_t* rx_ps_timings = NULL);
uint32_t getRngSeed();
bool setTxPower(int8_t dbm);
mesh::RadioParamApplyResult trySetTxPower(int8_t dbm);
virtual float getCurrentRSSI() =0;
virtual uint8_t getSpreadingFactor() const { return LORA_SF; }
+3
View File
@@ -76,6 +76,9 @@ public:
}
void powerOff() {}
bool setTxPower(int8_t /*dbm*/) { return true; }
mesh::RadioParamApplyResult trySetTxPower(int8_t /*dbm*/) {
return mesh::RadioParamApplyResult::APPLIED;
}
uint32_t getRngSeed() { return _simRandom(); }
uint32_t getPacketsRecv() const { return n_recv; }
uint32_t getPacketsSent() const { return n_sent; }