Validate beacon power without stranding saved settings

This commit is contained in:
mikecarper committed 2026-10-16 00:39:07 -07:00
1 parent adbeb2d007
commit d5cc73c43d
15 files changed
+662 -7

No files matched your search

+3
View File
@@ -94,6 +94,9 @@ public:
virtual bool supportsMeshtext() const { return false; }
virtual bool supportsEthernetTunnel() const { return false; }
#if MESH_ENABLE_TX_BEACONS
// Check the requested antenna power at the prospective beacon frequency.
// Validation must not retune the radio or alter its current power setting.
virtual bool validateBeaconPower(float, int8_t) const { return false; }
virtual RadioParamApplyResult prepareBeaconTransmit(const RadioProfileParams&, int8_t) {
return RadioParamApplyResult::FAILED;
}
+1 -2
View File
@@ -3,7 +3,6 @@
#if MESH_ENABLE_TX_BEACONS
#include <helpers/BeaconFrames.h>
#include <helpers/radiolib/RadioPowerLimits.h>
#include <string.h>
namespace mesh {
@@ -12,7 +11,7 @@ bool Mesh::validateTextBeacon(const BeaconSlot& slot) const {
if (!beacon::validText(slot.text) || slot.hops > 7
|| !RadioProfiles::valid(slot.radio)
|| !RadioProfiles::safePreamble(slot.radio, slot.radio.preamble ? slot.radio.preamble : 32)
|| slot.power < -9 || !isLoRaTxPowerValid(slot.power, slot.radio.freq)
|| !_radio->validateBeaconPower(slot.radio.freq, slot.power)
|| !_radio->validateProfile(slot.radio)) return false;
if (slot.radio.protocol == RadioProfileProtocol::MeshCore) return true;
#if MESH_ENABLE_MESHTEXT_WIRE
+12 -4
View File
@@ -5,6 +5,7 @@
#include <Mesh.h>
#include <helpers/FilePresence.h>
#include <helpers/MeshtextChat.h>
#include <helpers/UTF8Helpers.h>
#include <Arduino.h>
#include <ctype.h>
#include <errno.h>
@@ -75,12 +76,15 @@ bool decodeSlot(const uint8_t bytes[RecordSize], BeaconSlot& slot) {
slot.power = int8_t(bytes[13]); slot.preset = bytes[14]; slot.hops = bytes[15];
slot.enabled = true;
memcpy(slot.text, bytes + 17, BeaconSlot::TextCapacity);
const size_t text_length = strlen(slot.text);
// Reject noncanonical trailing data instead of carrying hidden text bytes.
for (size_t i = strlen(slot.text) + 1; i < BeaconSlot::TextCapacity; ++i)
for (size_t i = text_length + 1; i < BeaconSlot::TextCapacity; ++i)
if (slot.text[i]) return false;
for (const unsigned char* c = reinterpret_cast<const unsigned char*>(slot.text); *c; ++c)
if (*c < 32 || *c == 127) return false;
return isfinite(slot.radio.freq) && isfinite(slot.radio.bw)
return validUtf8PrefixLength(slot.text, text_length) == text_length
&& RadioProfiles::valid(slot.radio)
&& RadioProfiles::safePreamble(slot.radio, slot.radio.preamble ? slot.radio.preamble : 32)
&& (slot.radio.protocol != RadioProfileProtocol::Meshtext
|| (slot.preset <= uint8_t(mt::Preset::LongSlow) && slot.radio.preamble == 16));
}
@@ -218,7 +222,9 @@ BeaconManager::ReadResult BeaconManager::readConfig(
}
crc = crcUpdate(crc, bytes, sizeof(bytes));
BeaconSlot slot;
if (!decodeSlot(bytes, slot) || (slot.enabled && (!mesh_ || !mesh_->validateTextBeacon(slot)))
// Disk validity is independent of the current board's radio capabilities.
// Preserve unsupported slots so an upgrade can still query/repair them.
if (!decodeSlot(bytes, slot)
|| (slot.enabled && slot.radio.protocol == RadioProfileProtocol::Meshtext && !header[6]))
valid = false;
if (output) output[i] = slot;
@@ -403,7 +409,9 @@ void BeaconManager::loop(uint32_t now) {
if (hour > 0 && handled_hour_ != hour) {
const unsigned index = unsigned((hour - 1) % SlotCount);
BeaconSlot& slot = slots_[index];
if (!slot.enabled) handled_hour_ = hour;
// A retained slot may be unsupported by this board or firmware. Skip it
// before allocating a durable Meshtext packet ID or entering the TX queue.
if (!slot.enabled || !mesh_->validateTextBeacon(slot)) handled_hour_ = hour;
else {
uint64_t guard = last_send_ && elapsed_ - last_send_ < HourMillis
? HourMillis - (elapsed_ - last_send_) : 0;
@@ -139,6 +139,12 @@ 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) {
+38 -1
View File
@@ -3,6 +3,9 @@
#include "RadioLibWrappers.h"
#include "RadioInterruptEvents.h"
#include "RadioProfileChip.h"
#if MESH_ENABLE_TX_BEACONS
#include "RadioPowerLimits.h"
#endif
#include <new>
#if defined(ESP32_PLATFORM)
#include <esp_heap_caps.h>
@@ -559,6 +562,39 @@ mesh::RadioParamApplyResult RadioLibWrapper::tuneProfile(uint8_t profile) {
}
#if MESH_ENABLE_TX_BEACONS
bool RadioLibWrapper::validateBeaconPower(float frequency_mhz, int8_t power) const {
return isfinite(frequency_mhz) && mesh::isLoRaTxPowerValid(power, frequency_mhz)
&& checkBeaconOutputPower(frequency_mhz, power);
}
bool RadioLibWrapper::checkBeaconOutputPower(float frequency_mhz, int8_t power) const {
(void)frequency_mhz;
#if MESH_RADIO_PROFILE_CHIP == 1276
// SX127x's setter automatically selects RFO below 2 dBm. Its two-argument
// checker assumes PA_BOOST, so use the same selection as setOutputPower().
return static_cast<SX1276*>(_radio)->checkOutputPower(power, nullptr, power < 2)
== RADIOLIB_ERR_NONE;
#elif MESH_RADIO_PROFILE_CHIP == 1272
return static_cast<SX1272*>(_radio)->checkOutputPower(power, nullptr, power < 2)
== RADIOLIB_ERR_NONE;
#elif MESH_RADIO_PROFILE_CHIP == 1121
// RadioLib's checker uses its current band. Validate the prospective band
// without changing the cached frequency or triggering a hardware retune.
if (frequency_mhz > 1000.0f) return power >= -18 && power <= 13;
#ifdef LR1121_FORCE_HP_PA
return power >= -9 && power <= 22;
#else
return power >= -17 && power <= 22;
#endif
#elif MESH_RADIO_PROFILE_CHIP == 2021
return mesh::lr2021::isHighBand(frequency_mhz)
? power >= -19 && power <= 12 : power >= -9 && power <= 22;
#else
// These RadioLib checkers inspect configuration only; no SPI is performed.
return _radio->checkOutputPower(power, nullptr) == RADIOLIB_ERR_NONE;
#endif
}
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
@@ -596,7 +632,8 @@ mesh::RadioParamApplyResult RadioLibWrapper::prepareBeaconTransmit(
_hil_beacon_tx_frequency_mhz = 0;
if (!mesh::hilTransmitFrequencyAllowed(p.freq)) return Result::FAILED;
#endif
if (!_params_valid || !_dbm_valid || !getBeaconAirtime(p, MAX_TRANS_UNIT)) return Result::FAILED;
if (!_params_valid || !_dbm_valid || !getBeaconAirtime(p, MAX_TRANS_UNIT)
|| !validateBeaconPower(p.freq, power)) return Result::FAILED;
if (isCalibratingNoiseFloor()) return Result::BUSY;
beginProfileRetune(isInRecvMode() && !_rx_ps_armed && !_rx_ps_enabled);
+6
View File
@@ -272,6 +272,11 @@ 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; }
virtual int16_t enterCarrierWave() { return _radio->transmitDirect(); }
virtual bool validateMorseFrequency(float frequency_mhz) const {
@@ -409,6 +414,7 @@ public:
mesh::RadioParamApplyResult prepareTransmitProfile(uint8_t profile) override;
mesh::RadioParamApplyResult prepareTransmitProfile(uint8_t profile, bool reply_rx_override) override;
#if MESH_ENABLE_TX_BEACONS
bool validateBeaconPower(float frequency_mhz, int8_t power) const override;
mesh::RadioParamApplyResult prepareBeaconTransmit(const mesh::RadioProfileParams& params,
int8_t power) override;
mesh::RadioParamApplyResult restoreBeaconReceive() override;