Files
firmware/src/mesh/Default.h
T
ec5d230305 Feat/mesh beacon (#10618)
* Tips robot virtual node / relayer to different LoRa modes & channels

Note that this commit has details hardcoded for the Wellington (NZ)
mesh, and also requires the following patch to the protobufs:

-----
diff --git a/meshtastic/mesh.proto b/meshtastic/mesh.proto
index 03162d8..ec54c99 100644
--- a/meshtastic/mesh.proto
+++ b/meshtastic/mesh.proto
@@ -1393,6 +1393,21 @@ message MeshPacket {
    * Set by the firmware internally, clients are not supposed to set this.
    */
   uint32 tx_after = 20;
+
+  /*
+   * The modem preset to use fo rthis packet
+   */
+  uint32 modem_preset = 21;
+
+  /*
+   * The frequency slot to use for this packet
+   */
+  uint32 frequency_slot = 22;
+
+  /*
+   * Whether the packet has a nonstandard radio config
+   */
+  bool nonstandard_radio_config = 23;
 }

 /*
-----

* fix: repair mesh tips CI build

* feat: add MeshBeacon module (Phase 1 — proto + generated code + initial stub)

* feat(beacon): implement broadcaster + listener (phases 2-5)

* feat(beacon): wire RadioLibInterface hooks + admin validation (phases 6-7)

* fix(beacon): fix LocalModuleConfig flat access (no payload_variant), add localonly proto field

* feat(beacon): fix broadcaster inheritance, add preset/region validation + proto cache

- MeshBeaconBroadcastModule now inherits ProtobufModule<meshtastic_MeshBeacon>
  (alongside private MeshBeaconModule + OSThread), giving it allocDataPacket()
  and setStartDelay() without extra includes.

- Payload cache: rebuildCache() encodes the MeshBeacon protobuf once and stores
  it in payloadCache[]/payloadCacheSize; sendBeacon() only calls rebuildCache()
  when payloadCacheDirty==true. AdminModule calls invalidateCache() after saving
  new config so the next broadcast picks up changes.

- Region/preset validation in handleSetModuleConfig (mesh_beacon_tag):
  broadcast_on_preset is validated against the device's current region via
  RadioInterface::validateConfigLora(); broadcast_offer_region is validated via
  RadioInterface::validateConfigRegion(). Invalid values are zeroed with a
  LOG_WARN before saving.

* feat(beacon): add unit tests for MeshBeaconModule and AdminModule configuration validation

* remove old meshtips

* more  validation in NodeDB and AdminModule, and userprefs for baked in goodness

* copilot is my gravity

* mmmmm... beacon

* oops

* Enhance unit tests for MeshBeaconModule with detailed validation checks and output formatting

* new lines. Why not?

* finally

* legacy mode activate!

* Update protobufs (#17)

Co-authored-by: NomDeTom <116762865+NomDeTom@users.noreply.github.com>

* better logic, fixed a test

* updated for packet signing
fixed a test
added guards for licensed/ham mode

* channel numbers

* beacon: encrypt on the beacon channel PSK; fix split note

When broadcast_on_channel overrides the primary channel's name/PSK, the
beacon was encrypted with the PRIMARY PSK: perhapsEncode keys encryption
off the primary slot, but the radio-thread channel switch happens only
after encryption. sendBeaconPacket() now installs the beacon channel into
the primary slot for the synchronous duration of send() (cooperative
threading => no interleaving) so encryption/hash use the beacon channel,
then restores it. A shared beaconChannelSettings() helper builds the
channel for both the encrypt-time swap and the RF-time swap so the
key+hash cannot drift.

Also: correct the legacy-split comments (both packets go out on the same
beacon radio settings, not the normal config) and merge the two
consecutive `if (hasText)` blocks in the listener (cppcheck
duplicateCondition).

Tests: add channelPskOverride_swapsBeaconChannelAndRestores and
noChannelOverride_doesNotSwapPrimary; MockRouter snapshots the primary
channel at send() time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test/beacon: drain toPhoneQueue in tearDown to fix LSan leak abort

The listener delivers received text via MeshService::sendToPhone(), which
enqueues the packet into toPhoneQueue and takes ownership. Nothing dequeues
it in tests, so the three listener tests carrying message text stranded a
MeshPacket each — 1272 bytes / 3 allocations that LeakSanitizer flagged at
process exit, aborting the coverage run (surfaced by pio as [ERRORED] /
SIGHUP even though all 40 assertions passed).

Drain the phone queue in tearDown (getForPhone()/releaseToPool) so the
packets return to packetPool. Suite is now GREEN with no sanitizer abort.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* legacy hop override for zero-hoppers

* ever more beacons

* beacon: comment out broadcast_send_as_node pending further review

Functionality preserved in comments with full signing/has_bitfield notes
for when it is re-enabled. Proto tag 3 retained on the wire.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test/beacon: fix fromIsCustomNodeWhenSet now that send-as-node is disabled

broadcast_send_as_node is commented out; from is always the local node.
Update the test assertion and doc comment to match current behaviour.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Update protobufs (#21)

Co-authored-by: NomDeTom <116762865+NomDeTom@users.noreply.github.com>

* flags for beacons

* beacon: do more with less — slot-index targets + validation

Multi-target beacons embedded a full ChannelSettings in every BroadcastTarget,
blowing ModuleConfig past the 512-byte BLE FromRadio budget so the firmware would
not compile. Targets now reference an existing channel-table slot by channel_index
and the broadcaster resolves it via channels.getByIndex() at TX time. Net effect:
the same multi-target capability for a fraction of the bytes —
FromRadio 609 -> 510 B, MeshBeaconConfig 596 -> 324 B, AdminMessage 615 -> 511 B.

- proto: BroadcastTarget.channel (embedded) -> channel_index (uint32 ref); regen all
  generated headers (size constants propagate to admin/localonly/deviceonly/mesh).
- broadcaster: resolve channel_index from the channel table; an out-of-range or blank
  slot falls back to the default channel for the target preset rather than borrowing
  the primary's name/PSK.
- AdminModule: validate broadcast_targets entries on write (region/preset sanitised
  like the single-target fields; channel_index range-checked).
- userPrefs: TARGET_<n>_CHANNEL_{NAME,NUM,PSK} collapse to a single CHANNEL_INDEX.
- docs: two-step (set_channel -> set_module_config) multi-target setup, inline-vs-
  reference distinction, and single-/multi-target are equal (not "legacy") options.
- tests: target validation + channel-index resolution incl. blank-slot fallback
  (47/47 green on `./bin/run-tests.sh -e native -f test_mesh_beacon`).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NRAF5csgsMn6p1zEcFL8Qz

* throttling after reboot

* address copilot review

* simplify

* fix(beacon): use 0x%08x for node/packet IDs in logs; register test suite

The %#08lx log specifiers passed uint32_t (NodeNum/PacketId) to a %lx
length modifier — undefined behaviour on 64-bit (native test) targets and
non-standard width. Switch to the project-standard 0x%08x. Also bump
test/native-suite-count to 25 for the added test_mesh_beacon suite.

clod helped too

* copilot & clarity
clod helped too

* refactor(beacon): use auto for the sanitized config copy

clod helped too

* fix(beacon): guard empty-payload sends; gate has_mesh_beacon on build flag; document ISR_TX pre-switch

clod helped too

---------

Co-authored-by: Steve Gilberd <steve@erayd.net>
Co-authored-by: Darafei Praliaskouski <me@komzpa.net>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 08:20:51 -05:00

132 lines
6.9 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#pragma once
#include <MeshRadio.h>
#include <NodeDB.h>
#include <RadioInterface.h>
#include <cmath>
#include <cstdint>
#include <meshUtils.h>
#define ONE_DAY 24 * 60 * 60
#define ONE_MINUTE_MS 60 * 1000
#define THIRTY_SECONDS_MS 30 * 1000
#define TWO_SECONDS_MS 2 * 1000
#define FIVE_SECONDS_MS 5 * 1000
#define TEN_SECONDS_MS 10 * 1000
#define MAX_INTERVAL INT32_MAX // FIXME: INT32_MAX to avoid overflow issues with Apple clients but should be UINT32_MAX
#define min_default_telemetry_interval_secs IF_ROUTER(ONE_DAY / 2, 30 * 60)
#define default_gps_update_interval IF_ROUTER(ONE_DAY, 2 * 60)
#define default_telemetry_broadcast_interval_secs IF_ROUTER(ONE_DAY / 2, 60 * 60)
#define default_broadcast_interval_secs IF_ROUTER(ONE_DAY / 2, 60 * 60)
#define default_broadcast_smart_minimum_interval_secs 5 * 60
// Floor for our own position broadcasts when stationary (unchanged beyond the broadcast
// precision) or fixed_position: identical positions get deduped by traffic management anyway.
#define default_position_stationary_broadcast_secs (12 * 60 * 60)
#define min_default_broadcast_interval_secs IF_ROUTER(ONE_DAY / 2, 60 * 60)
#define min_default_broadcast_smart_minimum_interval_secs 5 * 60
#define default_wait_bluetooth_secs IF_ROUTER(1, 60)
#define default_sds_secs IF_ROUTER(ONE_DAY, UINT32_MAX) // Default to forever super deep sleep
#define default_ls_secs IF_ROUTER(ONE_DAY, 5 * 60)
#define default_min_wake_secs 10
#define default_screen_on_secs IF_ROUTER(1, 60 * 10)
#define default_node_info_broadcast_secs 3 * 60 * 60
#define default_neighbor_info_broadcast_secs 6 * 60 * 60
#define default_mesh_beacon_min_broadcast_interval_secs 3600
#define min_node_info_broadcast_secs 60 * 60 // No regular broadcasts of more than once an hour
#define min_neighbor_info_broadcast_secs 4 * 60 * 60
#define default_map_publish_interval_secs 60 * 60
enum class TrafficType { POSITION, TELEMETRY };
// Traffic management defaults
#define default_traffic_mgmt_position_precision_bits 19 // ~90m grid cells (±45m)
#define default_traffic_mgmt_position_min_interval_secs (11 * 60 * 60) // 11 hours between identical positions
// Role cap: tracker-role origins may refresh a duplicate position this often (vs the 11h default).
#define default_traffic_mgmt_tracker_position_min_interval_secs (60 * 60) // 1 hour
// Role cap: lost-and-found origins may refresh a duplicate position this often, so a lost
// device updates frequently without flooding. (Quantised to the dedup tick: ~2 ticks.)
// Unlike before, lost-and-found is NOT exempt from the relayed precision clamp.
#define default_traffic_mgmt_lost_and_found_position_min_interval_secs (15 * 60) // 15 minutes
// Hop scaling defaults
#define default_hop_scaling_min_target_nodes 40 // walk threshold: first hop reaching this cumulative count
#define default_hop_scaling_max_target_nodes 80 // generous extension ceiling (2 × min)
#define default_hop_scaling_min_target_nodes_floor 5 // minimum allowed min_target_nodes
#define default_hop_scaling_max_target_nodes_ceiling 512 // maximum allowed max_target_nodes
#ifdef USERPREFS_RINGTONE_NAG_SECS
#define default_ringtone_nag_secs USERPREFS_RINGTONE_NAG_SECS
#else
#define default_ringtone_nag_secs 15
#endif
#define default_network_ipv6_enabled false
#define default_mqtt_address "mqtt.meshtastic.org"
#define default_mqtt_username "meshdev"
#define default_mqtt_password "large4cats"
#define default_mqtt_root "msh"
#define default_mqtt_encryption_enabled true
#define default_mqtt_tls_enabled false
#define IF_ROUTER(routerVal, normalVal) \
((config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER || \
config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE) \
? (routerVal) \
: (normalVal))
class Default
{
public:
static uint32_t getConfiguredOrDefaultMs(uint32_t configuredInterval);
static uint32_t getConfiguredOrDefaultMs(uint32_t configuredInterval, uint32_t defaultInterval);
static uint32_t getConfiguredOrDefault(uint32_t configured, uint32_t defaultValue);
// Note: numOnlineNodes uses uint32_t to match the public API and allow flexibility,
// even though internal node counts use uint16_t (max 65535 nodes)
static uint32_t getConfiguredOrDefaultMsScaled(uint32_t configured, uint32_t defaultValue, uint32_t numOnlineNodes);
static uint32_t getConfiguredOrDefaultMsScaled(uint32_t configured, uint32_t defaultValue, uint32_t numOnlineNodes,
TrafficType type);
static uint8_t getConfiguredOrDefaultHopLimit(uint8_t configured);
static uint32_t getConfiguredOrMinimumValue(uint32_t configured, uint32_t minValue);
private:
// Note: Kept as uint32_t to match the public API parameter type
static float congestionScalingCoefficient(uint32_t numOnlineNodes)
{
if (numOnlineNodes <= 40) {
return 1.0;
} else {
// Resolve SF and BW from preset or manual config
// When use_preset is true, config.lora.spread_factor and bandwidth may be 0
// because applyModemConfig() sets them on RadioInterface, not on config.lora
float bwKHz;
uint8_t sf;
uint8_t cr;
if (config.lora.use_preset) {
modemPresetToParams(config.lora.modem_preset, false, bwKHz, sf, cr);
} else {
sf = config.lora.spread_factor;
bwKHz = bwCodeToKHz(config.lora.bandwidth);
}
// Guard against invalid values
sf = clampSpreadFactor(sf);
bwKHz = clampBandwidthKHz(bwKHz);
// throttlingFactor = 2^SF / (BW_in_kHz * scaling_divisor)
// With scaling_divisor=100:
// In SF11 and BW=250khz (longfast), this gives 0.08192 rather than the original 0.075
// In SF10 and BW=250khz (mediumslow), this gives 0.04096 rather than the original 0.04
// In SF9 and BW=250khz (mediumfast), this gives 0.02048 rather than the original 0.02
// In SF7 and BW=250khz (shortfast), this gives 0.00512 rather than the original 0.01
float throttlingFactor = static_cast<float>(pow_of_2(sf)) / (bwKHz * 100.0f);
#if USERPREFS_EVENT_MODE
// If we are in event mode, scale down the throttling factor by 4
throttlingFactor = static_cast<float>(pow_of_2(sf)) / (bwKHz * 25.0f);
#endif
// Scaling up traffic based on number of nodes over 40
int nodesOverForty = (numOnlineNodes - 40);
return 1.0 + (nodesOverForty * throttlingFactor); // Each number of online node scales by throttle factor
}
}
};