mirror of
https://github.com/meshtastic/firmware.git
synced 2026-11-07 23:04:49 -05:00
feat(beacon): per-target and offered frequency slots (#12126)
* feat(beacon): per-target and offered frequency slots Implements the slot fields from meshtastic/protobufs#1059 on top of the broadcast_targets scheme from #11646: - BroadcastTarget.frequency_slot pins a target's slot. Unset derives it the way a node on that channel would: the target region's override slot, else the hash of the target channel's name on the target preset. A target on the home radio keeps the home slot. A pin the target region does not hold skips the target. The deprecated ChannelSettings.channel_num is no longer read, and the home node's channel_num no longer leaks into other presets. - broadcast_offer_frequency_slot is advertised as MeshBeacon.offer_frequency_slot only when a receiver could not derive it. An offer pinned to a slot its region lacks is withheld; the text still goes out. - The listener keeps a received offer's slot. - Admin clears slot 0, and a slot past an explicit region and preset. - userPrefs: USERPREFS_MESH_BEACON_OFFER_FREQUENCY_SLOT and USERPREFS_MESH_BEACON_TARGET_<N>_FREQUENCY_SLOT. Also from #11662: - A beacon is addressed at its target's channel index, so perhapsEncode() keys it like any other packet; the primary channel is never overwritten. The TX hook writes the target preset's channel hash for a blank-named channel on another preset. - A beacon whose send() did not queue releases its target entry on every failure, not only ERRNO_SHOULD_RELEASE. - A beacon still queued a broadcast interval after it was armed, or whose entry was evicted from a full table, is dropped at TX instead of going out on the home radio. Supersedes #11662. Co-authored-by: Tom <116762865+NomDeTom@users.noreply.github.com> * fix(beacon): address review of frequency slots - TX gate checks the target preset against its region or EU sibling, not the home slot - Targets and offers run an EU sibling's preset in that sibling, as the switch does - Switch clears override_frequency and restores tx_power; restore keeps fields edited mid-switch - An unusable channel_index skips the target instead of falling back to the primary - Beacon config validation moved to MeshBeaconModule::sanitizeConfig(), also run on load; message cap no longer ends mid-character - Evicted-id ring sized to the TX queue * fix(radio): frequencySlotCount() reports no slots for UNSET UNSET's table span is a placeholder; an unknown region code resolves to it too. * fix(beacon): slot count and restore for custom bandwidth - frequencySlotCount() counts a custom bandwidth wider than the region at the region default, as the clamp runs it - resolveFrequencySlot() falls back to the hash when the region override slot exceeds the count - The beacon switch restores a custom bandwidth applyModemConfig() clamped for the target region * fix(beacon): run a target's named preset on a node with custom modem params The switch sets use_preset for a target that names a preset and restores it after TX. A target naming no preset keeps the custom params. * fix(beacon): keep an out-of-range target channel_index sanitizeConfig() cleared has_channel_index for an index past the channel table. That turns the entry into an unset-index target, and sendBeacon() sends those on the primary channel - under a name and key the operator never named. sanitizeConfig() now also runs from loadFromDisk(), so a stored or userPrefs index did that at every boot, not only on the write that set it. Keep the index and only warn. sendBeacon() already bounds-checks it against channels.getNumChannels(), which is tighter than MAX_NUM_CHANNELS, before getByIndex(), and skips the target - so nothing can index out of bounds, which is what the clear was there for. test_adminValidation_targetChannelIndexOutOfRange_isCleared becomes _isKeptAndSkipped: the index survives the write, and the stored config emits no beacon rather than one on the primary. Reported by NomDeTom on #12126. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SwNPp17REf1c9je42aWyJa --------- Co-authored-by: Tom <116762865+NomDeTom@users.noreply.github.com> Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
10 files changed
+1307
-331
No files matched your search
@@ -1383,63 +1383,7 @@ bool AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c)
|
||||
// Sanitize a local copy rather than const_cast-ing the const input (UB if a truly-const
|
||||
// object is ever passed); the validated copy is assigned into moduleConfig below.
|
||||
auto beaconCfg = c.payload_variant.mesh_beacon;
|
||||
// Cap at the generated field size, so the limit follows the proto's max_size.
|
||||
beaconCfg.broadcast_message[sizeof(beaconCfg.broadcast_message) - 1] = '\0';
|
||||
// Enforce interval minimum (0 means unset/use default).
|
||||
if (beaconCfg.broadcast_interval_secs != 0 &&
|
||||
beaconCfg.broadcast_interval_secs < default_mesh_beacon_min_broadcast_interval_secs)
|
||||
beaconCfg.broadcast_interval_secs = default_mesh_beacon_min_broadcast_interval_secs;
|
||||
// Validate broadcast_offer_preset against broadcast_offer_region (or current region if unset).
|
||||
if (beaconCfg.has_broadcast_offer_preset) {
|
||||
meshtastic_Config_LoRaConfig probe = config.lora;
|
||||
probe.use_preset = true;
|
||||
probe.modem_preset = beaconCfg.broadcast_offer_preset;
|
||||
if (beaconCfg.broadcast_offer_region != meshtastic_Config_LoRaConfig_RegionCode_UNSET)
|
||||
probe.region = beaconCfg.broadcast_offer_region;
|
||||
if (!RadioInterface::validateConfigLora(probe)) {
|
||||
LOG_WARN("Beacon: broadcast_offer_preset %d invalid for region, clearing", beaconCfg.broadcast_offer_preset);
|
||||
beaconCfg.has_broadcast_offer_preset = false;
|
||||
}
|
||||
}
|
||||
// Validate broadcast_offer_region is a known region code.
|
||||
if (beaconCfg.broadcast_offer_region != meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
|
||||
const RegionInfo *r = getRegion(beaconCfg.broadcast_offer_region);
|
||||
if (r->code != beaconCfg.broadcast_offer_region) {
|
||||
LOG_WARN("Beacon: broadcast_offer_region %d invalid, clearing", beaconCfg.broadcast_offer_region);
|
||||
beaconCfg.broadcast_offer_region = meshtastic_Config_LoRaConfig_RegionCode_UNSET;
|
||||
}
|
||||
}
|
||||
// Validate each broadcast target so a bad preset/region is cleared on write rather than
|
||||
// relying on the runtime TX drop.
|
||||
for (pb_size_t i = 0; i < beaconCfg.broadcast_targets_count; i++) {
|
||||
auto &t = beaconCfg.broadcast_targets[i];
|
||||
// Region must be a known region code (UNSET = use running config at TX time).
|
||||
if (t.region != meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
|
||||
const RegionInfo *r = getRegion(t.region);
|
||||
if (r->code != t.region) {
|
||||
LOG_WARN("Beacon: broadcast_targets[%u] region %d invalid, clearing", i, t.region);
|
||||
t.region = meshtastic_Config_LoRaConfig_RegionCode_UNSET;
|
||||
}
|
||||
}
|
||||
// Preset must be valid for the target region (or current region if unset).
|
||||
if (t.has_preset) {
|
||||
meshtastic_Config_LoRaConfig probe = config.lora;
|
||||
probe.use_preset = true;
|
||||
probe.modem_preset = t.preset;
|
||||
if (t.region != meshtastic_Config_LoRaConfig_RegionCode_UNSET)
|
||||
probe.region = t.region;
|
||||
if (!RadioInterface::validateConfigLora(probe)) {
|
||||
LOG_WARN("Beacon: broadcast_targets[%u] preset %d invalid for region, clearing", i, t.preset);
|
||||
t.has_preset = false;
|
||||
t.has_channel_index = false;
|
||||
}
|
||||
}
|
||||
// channel_index must reference a real channel-table slot.
|
||||
if (t.has_channel_index && t.channel_index >= MAX_NUM_CHANNELS) {
|
||||
LOG_WARN("Beacon: broadcast_targets[%u] channel_index %u out of range, clearing", i, t.channel_index);
|
||||
t.has_channel_index = false;
|
||||
}
|
||||
}
|
||||
MeshBeaconModule::sanitizeConfig(beaconCfg);
|
||||
moduleConfig.has_mesh_beacon = true;
|
||||
moduleConfig.mesh_beacon = beaconCfg;
|
||||
shouldReboot = false;
|
||||
|
||||
Reference in new issue
Block a user