Files
meshtastic_firmware/src/modules/MeshBeaconModule.h
T
2026-07-01 19:01:27 -05:00

164 lines
7.4 KiB
C++

#pragma once
#include "MeshRadio.h"
#include "Observer.h"
#include "ProtobufModule.h"
#include "RadioInterface.h"
#include "concurrency/OSThread.h"
#include "mesh/generated/meshtastic/mesh_beacon.pb.h"
#include "mesh/generated/meshtastic/module_config.pb.h"
// Short aliases for the MeshBeaconConfig.flags bitfield (see module_config.proto MeshBeaconConfig.Flags).
#define MESH_BEACON_FLAG_LISTEN_ENABLED meshtastic_ModuleConfig_MeshBeaconConfig_Flags_FLAG_LISTEN_ENABLED
#define MESH_BEACON_FLAG_BROADCAST_ENABLED meshtastic_ModuleConfig_MeshBeaconConfig_Flags_FLAG_BROADCAST_ENABLED
#define MESH_BEACON_FLAG_LEGACY_SPLIT meshtastic_ModuleConfig_MeshBeaconConfig_Flags_FLAG_LEGACY_SPLIT
// Sidecar entry pairing a packet ID with target radio settings for beacon TX.
typedef struct {
bool inUse;
PacketId id;
meshtastic_Config_LoRaConfig_ModemPreset preset;
uint16_t slot;
// When true, reconfigureForBeaconTX sets hop_start=1 so pre-2.7.20 firmware
// (which drops hop_start==0 packets) accepts the zero-hop beacon.
bool legacyHopOverride;
// Per-target radio settings. UNSET region means use current lora.region.
meshtastic_Config_LoRaConfig_RegionCode region;
bool has_channel;
meshtastic_ChannelSettings channel;
} MeshBeaconModule_TargetRadioSettings;
/**
* Base class: holds the radio-switching sidecar table and static helpers.
* The sidecar avoids touching MeshPacket proto fields for per-packet radio state.
*/
class MeshBeaconModule
{
public:
MeshBeaconModule();
/**
* Reconfigure the radio for beacon TX, or restore to original config if p is NULL.
* Returns true if the radio was reconfigured (caller must re-run transmit delay for CCA).
* Driven by broadcast_on_preset / broadcast_on_channel from MeshBeaconConfig.
*/
static bool reconfigureForBeaconTX(RadioInterface *iface, meshtastic_MeshPacket *p);
/**
* Associate target radio settings with an outgoing packet by its ID.
* Sidecar holds 8 entries; evicts slot 0 on overflow.
*/
static void
setTargetRadioSettings(const meshtastic_MeshPacket *p, meshtastic_Config_LoRaConfig_ModemPreset preset, uint16_t slot,
bool legacyHopOverride = false,
meshtastic_Config_LoRaConfig_RegionCode region = meshtastic_Config_LoRaConfig_RegionCode_UNSET,
bool has_channel = false, const meshtastic_ChannelSettings *channel = nullptr);
/**
* Returns true if the sidecar table contains an entry for this packet's ID.
* Used by RadioLibInterface to gate the channel-active check.
*/
static bool hasTargetRadioSettings(const meshtastic_MeshPacket *p);
/**
* Remove the sidecar entry for this packet after it has been sent.
* Called from RadioLibInterface::completeSending().
*/
static void clearTargetRadioSettings(const meshtastic_MeshPacket *p);
/**
* True if p is tagged for a beacon radio switch whose target config must NOT be transmitted:
* preset invalid for the target region, or an unlicensed node would key up on a ham-only
* (licensed-only) region. The radio driver drops such packets rather than sending them on the
* current config. False for any packet without a sidecar entry (normal traffic is never affected).
*/
static bool beaconTxConfigInvalid(const meshtastic_MeshPacket *p);
protected:
/**
* Build the ChannelSettings the beacon transmits on: the base (primary) channel overlaid with
* any broadcast_on_channel overrides, defaulting an empty name to the target preset's display
* name. Shared by the encrypt-time channel swap and the radio-thread RF swap so the channel
* key + hash are identical at both points.
*/
static meshtastic_ChannelSettings beaconChannelSettings(const meshtastic_ChannelSettings &base,
meshtastic_Config_LoRaConfig_ModemPreset preset,
const meshtastic_ChannelSettings *overrideChannel = nullptr);
static meshtastic_Config_LoRaConfig_ModemPreset originalModemPreset;
static uint16_t originalLoraChannel;
static meshtastic_Config_LoRaConfig_RegionCode originalRegion;
static meshtastic_ChannelSettings originalPrimaryChannel;
};
/**
* Broadcaster: periodically sends MeshBeacon packets on the configured preset/channel.
* Active only when the FLAG_BROADCAST_ENABLED bit is set in moduleConfig.mesh_beacon.flags.
* Inherits ProtobufModule to access allocDataProtobuf + setStartDelay.
*
* Packet flow:
* Normal (combined): one MESH_BEACON_APP carrying offer + message on the beacon radio config.
* Legacy split: two packets when both text and offer are present and FLAG_LEGACY_SPLIT is set,
* both sent on the same beacon radio settings:
* A) MESH_BEACON_APP with offer only (no text).
* B) TEXT_MESSAGE_APP with the text (for clients that only decode TEXT_MESSAGE_APP).
*/
class MeshBeaconBroadcastModule : private MeshBeaconModule,
public ProtobufModule<meshtastic_MeshBeacon>,
private concurrency::OSThread
{
public:
MeshBeaconBroadcastModule();
// Mark the cached payload dirty (call after config change).
void invalidateCache() { payloadCacheDirty = true; }
protected:
virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &, meshtastic_MeshBeacon *) override { return false; }
virtual int32_t runOnce() override;
protected:
void sendBeacon();
void rebuildCache();
// Send one beacon packet. When overrideChannel is set and has a name/PSK override,
// the packet is encrypted with that channel's key (not the primary's).
void sendBeaconPacket(meshtastic_MeshPacket *p, meshtastic_Config_LoRaConfig_ModemPreset targetPreset,
bool has_channel = false, const meshtastic_ChannelSettings *overrideChannel = nullptr);
bool payloadCacheDirty = true;
uint8_t payloadCache[meshtastic_MeshBeacon_size] = {};
pb_size_t payloadCacheSize = 0;
};
extern MeshBeaconBroadcastModule *meshBeaconBroadcastModule;
/**
* Listener: receives MESH_BEACON_APP packets and caches any offered channel/preset for the client
* app to retrieve. It does NOT unwrap the text into a separate message - the original beacon packet
* already reaches the client (handler returns CONTINUE), which reads `message` from it directly.
* Does NOT auto-apply offered settings - client app must do so explicitly.
* Active only when the FLAG_LISTEN_ENABLED bit is set in moduleConfig.mesh_beacon.flags.
*/
class MeshBeaconListenerModule : public ProtobufModule<meshtastic_MeshBeacon>, public Observable<const meshtastic_MeshPacket *>
{
public:
MeshBeaconListenerModule();
struct BeaconOffer {
bool valid;
NodeNum sender;
bool has_channel;
meshtastic_ChannelSettings channel;
meshtastic_Config_LoRaConfig_RegionCode region;
meshtastic_Config_LoRaConfig_ModemPreset preset;
uint32_t received_at;
};
// Last received offer - accessible to admin/API for client app retrieval.
static BeaconOffer lastReceivedOffer;
protected:
virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_MeshBeacon *b) override;
virtual bool wantPacket(const meshtastic_MeshPacket *p) override;
};
extern MeshBeaconListenerModule *meshBeaconListenerModule;