Apply saved TX power automatically across firmware roles
This commit is contained in:
1 parent
31e5d54074
commit
65928aae95
51 files changed
+3838
-2728
No files matched your search
@@ -173,6 +173,7 @@ void CommonCLI::loop() {
|
||||
}
|
||||
#endif
|
||||
if (!mesh::morseTimingActive(_callbacks->getProfileRadio())) {
|
||||
servicePendingTxPower();
|
||||
_radio_profiles.loop();
|
||||
loopManagement();
|
||||
#if MESH_ENABLE_TX_BEACONS
|
||||
@@ -197,6 +198,55 @@ bool CommonCLI::hasActiveUserGpioTimer() const {
|
||||
#endif
|
||||
}
|
||||
|
||||
void CommonCLI::servicePendingTxPower() {
|
||||
if (!_tx_power_pending || (_tx_power_retry_at
|
||||
&& static_cast<int32_t>(millis() - _tx_power_retry_at) < 0)) return;
|
||||
// A pending normal setting must not become a power-meter sweep if a CW
|
||||
// test started after the setting was accepted.
|
||||
const mesh::Radio* radio = _callbacks->getProfileRadio();
|
||||
if (radio && radio->isCarrierWaveActive()) return;
|
||||
const auto result = _callbacks->trySetTxPower(_prefs->tx_power_dbm);
|
||||
if (result == mesh::RadioParamApplyResult::APPLIED) _tx_power_pending = false;
|
||||
_tx_power_retry_at = result == mesh::RadioParamApplyResult::FAILED
|
||||
? millis() + 1000 : 0;
|
||||
if (result == mesh::RadioParamApplyResult::FAILED && !_tx_power_retry_at)
|
||||
_tx_power_retry_at = 1;
|
||||
}
|
||||
|
||||
void CommonCLI::applyAndSaveTxPower(int8_t power, char* reply) {
|
||||
using Result = mesh::RadioParamApplyResult;
|
||||
const int8_t previous = _prefs->tx_power_dbm;
|
||||
const bool previous_pending = _tx_power_pending;
|
||||
const uint32_t previous_retry_at = _tx_power_retry_at;
|
||||
const auto result = _callbacks->trySetTxPower(power);
|
||||
if (result == Result::FAILED) {
|
||||
// A failed SPI write may have changed the hardware partially. Reassert
|
||||
// the saved desired power without accepting or saving the rejected one.
|
||||
_tx_power_pending = true;
|
||||
_tx_power_retry_at = 0;
|
||||
mesh::cli::formatError(reply, 160, "TX power rejected");
|
||||
return;
|
||||
}
|
||||
_prefs->tx_power_dbm = power;
|
||||
if (trySavePrefs()) {
|
||||
_tx_power_pending = result == Result::BUSY;
|
||||
_tx_power_retry_at = 0;
|
||||
strcpy(reply, _tx_power_pending ? "OK - saved; pending" : "OK");
|
||||
return;
|
||||
}
|
||||
|
||||
_prefs->tx_power_dbm = previous;
|
||||
_tx_power_pending = previous_pending;
|
||||
_tx_power_retry_at = previous_retry_at;
|
||||
if (result == Result::APPLIED) {
|
||||
const auto restored = _callbacks->trySetTxPower(previous);
|
||||
_tx_power_pending = restored != Result::APPLIED;
|
||||
_tx_power_retry_at = restored == Result::FAILED ? millis() + 1000 : 0;
|
||||
if (restored == Result::FAILED && !_tx_power_retry_at) _tx_power_retry_at = 1;
|
||||
}
|
||||
mesh::cli::formatError(reply, 160, "TX save unconfirmed; reboot may differ");
|
||||
}
|
||||
|
||||
// Old fork firmware persisted the (since removed) NodePrefs MQTT fields to /com_prefs
|
||||
// as a zero-filled gap between owner_info (which ends at offset 290) and a trailing
|
||||
// observer block (rx_boosted_gain, flood_max_*, snmp/watchdog/alert settings).
|
||||
@@ -5322,12 +5372,8 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
|
||||
} else if (!mesh::isLoRaTxPowerValid(power, _prefs->freq)) {
|
||||
sprintf(reply, "Error: TX power range is %d to %d dBm",
|
||||
(int)minimum, (int)maximum);
|
||||
} else if (!_callbacks->setTxPower(static_cast<int8_t>(power))) {
|
||||
mesh::cli::formatError(reply, 160, "radio busy or TX power rejected");
|
||||
} else {
|
||||
_prefs->tx_power_dbm = static_cast<int8_t>(power);
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
applyAndSaveTxPower(static_cast<int8_t>(power), reply);
|
||||
}
|
||||
} else if (memcmp(config, "freq ", 5) == 0) {
|
||||
float freq = 0.0f;
|
||||
@@ -6045,6 +6091,12 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep
|
||||
} else {
|
||||
strcpy(reply, "> strict");
|
||||
}
|
||||
#if defined(MESH_HIL_TX_FREQ_MHZ)
|
||||
} else if (configKeyEquals(config, "tx.status")) {
|
||||
sprintf(reply, "> desired=%d,apply=%s", (int)_prefs->tx_power_dbm,
|
||||
!_tx_power_pending && _callbacks->isTxPowerApplied(_prefs->tx_power_dbm)
|
||||
? "applied" : "pending");
|
||||
#endif
|
||||
} else if (configKeyEquals(config, "tx")) {
|
||||
sprintf(reply, "> %d", (int32_t) _prefs->tx_power_dbm);
|
||||
} else if (configKeyEquals(config, "freq")) {
|
||||
|
||||
@@ -564,6 +564,13 @@ public:
|
||||
virtual void eraseLogFile() = 0;
|
||||
virtual void dumpLogFile() = 0;
|
||||
virtual bool setTxPower(int8_t power_dbm) = 0;
|
||||
virtual mesh::RadioParamApplyResult trySetTxPower(int8_t power_dbm) {
|
||||
return setTxPower(power_dbm) ? mesh::RadioParamApplyResult::APPLIED
|
||||
: mesh::RadioParamApplyResult::FAILED;
|
||||
}
|
||||
#if defined(MESH_HIL_TX_FREQ_MHZ)
|
||||
virtual bool isTxPowerApplied(int8_t) const { return false; }
|
||||
#endif
|
||||
virtual void formatNeighborsReply(char *reply) = 0;
|
||||
// A zero-length prefix clears the whole table; pubkey may be null then.
|
||||
// Nonempty prefixes must contain 1..PUB_KEY_SIZE bytes.
|
||||
@@ -941,6 +948,8 @@ class CommonCLI {
|
||||
bool _com_prefs_needs_upgrade = false; // old-format legacy prefs detected; rewrite once after load
|
||||
bool _common_save_result_known = false;
|
||||
bool _common_save_succeeded = false;
|
||||
bool _tx_power_pending = false;
|
||||
uint32_t _tx_power_retry_at = 0;
|
||||
uint32_t _prefs_save_failures = 0;
|
||||
mesh::RadioProfileCLI _radio_profiles;
|
||||
#if MESH_ENABLE_TX_BEACONS
|
||||
@@ -961,6 +970,7 @@ class CommonCLI {
|
||||
PrefsSaveRouting::Scope scope = PrefsSaveRouting::Scope::Common);
|
||||
bool trySavePrefs(
|
||||
PrefsSaveRouting::Scope scope = PrefsSaveRouting::Scope::Common);
|
||||
void applyAndSaveTxPower(int8_t power, char* reply);
|
||||
bool applyAndSaveAdcMultiplier(float multiplier, char* reply, size_t capacity);
|
||||
bool saveObserverPrefs();
|
||||
void loadPrefsInt(FILESYSTEM* _fs, const char* filename);
|
||||
@@ -1051,6 +1061,8 @@ public:
|
||||
#endif
|
||||
uint8_t batteryPercent(uint16_t mv) const;
|
||||
bool hasActiveUserGpioTimer() const;
|
||||
bool hasPendingTxPower() const { return _tx_power_pending; }
|
||||
void servicePendingTxPower();
|
||||
void handleCommand(uint32_t sender_timestamp, char* command, char* reply);
|
||||
mesh::MainBoard* getBoard() { return _board; }
|
||||
uint8_t buildAdvertData(uint8_t node_type, uint8_t* app_data);
|
||||
|
||||
@@ -202,6 +202,10 @@ void CommonRadioPrefs::formatRxPowerSavingStatus(char* reply, size_t reply_size)
|
||||
RXPowerSavingCLI::get(config, status, sf, bw, reply, reply_size, wire_preamble);
|
||||
}
|
||||
|
||||
#if defined(NRF52_PLATFORM)
|
||||
// Keep shared radio parsing out of the Companion's larger command dispatcher.
|
||||
__attribute__((noinline))
|
||||
#endif
|
||||
bool CommonRadioPrefs::handleCommand(const char* command, uint32_t sender_timestamp, char* reply) {
|
||||
(void)sender_timestamp;
|
||||
if (command == nullptr || reply == nullptr) return false;
|
||||
|
||||
@@ -68,9 +68,23 @@ public:
|
||||
bool isCalibratingNoiseFloor() const { return false; }
|
||||
|
||||
uint32_t intID();
|
||||
bool isTxPowerValid(int8_t dbm) const {
|
||||
// The SDK accepts 8..84 quarter-dBm units; the public API uses whole dBm.
|
||||
if (dbm < 2 || dbm > 21) return false;
|
||||
#ifdef MIN_LORA_TX_POWER
|
||||
if (dbm < MIN_LORA_TX_POWER) return false;
|
||||
#endif
|
||||
#ifdef MAX_LORA_TX_POWER
|
||||
if (dbm > MAX_LORA_TX_POWER) return false;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
bool isTxPowerApplied(int8_t dbm) const {
|
||||
return initialized_ && tx_power_dbm_ == dbm;
|
||||
}
|
||||
bool setTxPower(int8_t dbm);
|
||||
mesh::RadioParamApplyResult trySetTxPower(int8_t dbm) {
|
||||
return setTxPower(dbm) ? mesh::RadioParamApplyResult::APPLIED
|
||||
return isTxPowerValid(dbm) && setTxPower(dbm) ? mesh::RadioParamApplyResult::APPLIED
|
||||
: mesh::RadioParamApplyResult::FAILED;
|
||||
}
|
||||
};
|
||||
|
||||
+2400
-2397
File diff suppressed because it is too large.
Load diff
@@ -139,12 +139,10 @@ public:
|
||||
}
|
||||
|
||||
protected:
|
||||
#if MESH_ENABLE_TX_BEACONS
|
||||
bool checkBeaconOutputPower(float frequency_mhz, int8_t dbm) const override {
|
||||
(void)frequency_mhz;
|
||||
return dbm >= _min_dbm && dbm <= _max_dbm;
|
||||
}
|
||||
#endif
|
||||
// Emit a gain-control code. Override for a PA driven by PWM or an external
|
||||
// DAC instead of the MCU's own.
|
||||
virtual void writeGainControl(uint8_t code) {
|
||||
|
||||
@@ -324,8 +324,33 @@ bool RadioLibWrapper::setTxPower(int8_t dbm) {
|
||||
return trySetTxPower(dbm) == mesh::RadioParamApplyResult::APPLIED;
|
||||
}
|
||||
|
||||
bool RadioLibWrapper::isTxPowerValid(int8_t dbm) const {
|
||||
#ifdef MIN_LORA_TX_POWER
|
||||
if (dbm < MIN_LORA_TX_POWER) return false;
|
||||
#endif
|
||||
#ifdef MAX_LORA_TX_POWER
|
||||
if (dbm > MAX_LORA_TX_POWER) return false;
|
||||
#endif
|
||||
#if MESH_RADIO_PROFILE_CHIP == 1121 || MESH_RADIO_PROFILE_CHIP == 2021
|
||||
// These drivers intentionally clip desired power to the physically selected
|
||||
// band's PA limit. A temporary/radio2 band must not reject saved intent.
|
||||
return true;
|
||||
#else
|
||||
return checkBeaconOutputPower(_cur_freq, dbm);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool RadioLibWrapper::isTxPowerApplied(int8_t dbm) const {
|
||||
#if MESH_ENABLE_TX_BEACONS
|
||||
if (_beacon_owned) return false;
|
||||
#endif
|
||||
return !_cw_active && _dbm_valid && _dbm_applied && _cur_dbm == dbm
|
||||
&& !_profile_repair_required;
|
||||
}
|
||||
|
||||
mesh::RadioParamApplyResult RadioLibWrapper::trySetTxPower(int8_t dbm) {
|
||||
using Result = mesh::RadioParamApplyResult;
|
||||
if (!isTxPowerValid(dbm)) return Result::FAILED;
|
||||
#if MESH_ENABLE_TX_BEACONS
|
||||
if (_beacon_owned) return Result::BUSY;
|
||||
#endif
|
||||
@@ -348,7 +373,8 @@ mesh::RadioParamApplyResult RadioLibWrapper::trySetTxPower(int8_t dbm) {
|
||||
// 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) {
|
||||
_dbm_applied = applyCachedTxPower(dbm) == RADIOLIB_ERR_NONE;
|
||||
if (!_dbm_applied) {
|
||||
stopCarrierWave();
|
||||
return Result::FAILED;
|
||||
}
|
||||
@@ -882,6 +908,7 @@ bool RadioLibWrapper::validateBeaconPower(float frequency_mhz, int8_t power) con
|
||||
return isfinite(frequency_mhz) && mesh::isLoRaTxPowerValid(power, frequency_mhz)
|
||||
&& checkBeaconOutputPower(frequency_mhz, power);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool RadioLibWrapper::checkBeaconOutputPower(float frequency_mhz, int8_t power) const {
|
||||
(void)frequency_mhz;
|
||||
@@ -911,6 +938,7 @@ bool RadioLibWrapper::checkBeaconOutputPower(float frequency_mhz, int8_t power)
|
||||
#endif
|
||||
}
|
||||
|
||||
#if MESH_ENABLE_TX_BEACONS
|
||||
uint32_t RadioLibWrapper::getBeaconAirtime(const mesh::RadioProfileParams& p, int length) {
|
||||
if (length <= 0 || length > MAX_TRANS_UNIT || !validateProfile(p)) return 0;
|
||||
const uint16_t preamble = p.protocol == mesh::RadioProfileProtocol::Meshtext
|
||||
@@ -967,6 +995,7 @@ mesh::RadioParamApplyResult RadioLibWrapper::prepareBeaconTransmit(
|
||||
// an exclusive lease until the ordinary receiver has been restored.
|
||||
_beacon_restore_profile = _active_profile;
|
||||
_beacon_owned = true;
|
||||
_dbm_applied = false;
|
||||
_beacon_restore_at = 0;
|
||||
_beacon_cad_timeout = cadScanTimeoutMillis(p.sf, p.bw);
|
||||
_physical_preamble = p.protocol == mesh::RadioProfileProtocol::Meshtext
|
||||
@@ -1019,7 +1048,7 @@ mesh::RadioParamApplyResult RadioLibWrapper::restoreBeaconReceive() {
|
||||
#if MESH_ENABLE_MESHTEXT_WIRE || MESH_ENABLE_ETHERNET_WIRE
|
||||
restored = restored && applyProtocol(p.protocol);
|
||||
#endif
|
||||
restored = restored && applyCachedTxPower(_cur_dbm) == RADIOLIB_ERR_NONE;
|
||||
restored = _dbm_applied = restored && applyCachedTxPower(_cur_dbm) == RADIOLIB_ERR_NONE;
|
||||
endProfileRetune(restored);
|
||||
if (restored) {
|
||||
// No RX ran on the beacon tuple, so any remaining event belongs to its
|
||||
@@ -2163,6 +2192,7 @@ bool RadioLibWrapper::stopCarrierWave() {
|
||||
if (_morse_rx_owned && _morse_rx) _morse_rx->stop(static_cast<uint32_t>(millis()));
|
||||
#endif
|
||||
_cw_stopping = true;
|
||||
_dbm_applied = false;
|
||||
// Deassert external TX even if SPI fails. SX127x must also rebuild its LoRa
|
||||
// modem after FSK direct mode before normal packet work can resume.
|
||||
const int16_t status = _radio->finishTransmit();
|
||||
@@ -2184,6 +2214,7 @@ bool RadioLibWrapper::stopCarrierWave() {
|
||||
restored = status == RADIOLIB_ERR_NONE || standby_status == RADIOLIB_ERR_NONE
|
||||
? restoreCarrierWaveModem() : restoreAfterDeepInit();
|
||||
}
|
||||
_dbm_applied = restored && _dbm_valid;
|
||||
if (!restored) {
|
||||
_cw_retry_at = static_cast<uint32_t>(millis()) + 100;
|
||||
return false; // hold normal work until stopping the carrier is confirmed
|
||||
|
||||
@@ -355,11 +355,9 @@ protected:
|
||||
virtual int16_t applyCachedTxPower(int8_t dbm) {
|
||||
return _radio->setOutputPower(dbm);
|
||||
}
|
||||
#if MESH_ENABLE_TX_BEACONS
|
||||
// External amplifiers override this in antenna dBm. The public capability
|
||||
// still checks the board's explicit limits before consulting this hook.
|
||||
virtual bool checkBeaconOutputPower(float frequency_mhz, int8_t dbm) const;
|
||||
#endif
|
||||
virtual bool applyRxBoostedGainMode(bool) { return false; }
|
||||
#if MESH_ENABLE_CARRIER_TOOLS || !defined(STM32_PLATFORM)
|
||||
virtual int16_t enterCarrierWave() { return _radio->transmitDirect(); }
|
||||
@@ -574,6 +572,8 @@ public:
|
||||
bool setParams(float freq, float bw, uint8_t sf, uint8_t cr,
|
||||
const uint32_t* rx_ps_timings = NULL);
|
||||
uint32_t getRngSeed();
|
||||
bool isTxPowerValid(int8_t dbm) const;
|
||||
bool isTxPowerApplied(int8_t dbm) const;
|
||||
bool setTxPower(int8_t dbm);
|
||||
mesh::RadioParamApplyResult trySetTxPower(int8_t dbm);
|
||||
|
||||
|
||||
@@ -75,6 +75,8 @@ public:
|
||||
== mesh::RadioParamApplyResult::APPLIED;
|
||||
}
|
||||
void powerOff() {}
|
||||
bool isTxPowerValid(int8_t /*dbm*/) const { return true; }
|
||||
bool isTxPowerApplied(int8_t /*dbm*/) const { return true; }
|
||||
bool setTxPower(int8_t /*dbm*/) { return true; }
|
||||
mesh::RadioParamApplyResult trySetTxPower(int8_t /*dbm*/) {
|
||||
return mesh::RadioParamApplyResult::APPLIED;
|
||||
|
||||
Reference in new issue
Block a user