emdashes begone (#10847)

This commit is contained in:
Tom
2026-07-01 19:01:27 -05:00
committed by GitHub
co-authored by GitHub
parent dee94e0758
commit 3becaf2d95
276 changed files with 1795 additions and 1793 deletions
+60 -60
View File
@@ -1,22 +1,22 @@
# Mesh Beacon Module Function, Settings, and Client Interface Spec
# Mesh Beacon Module - Function, Settings, and Client Interface Spec
Status: draft, tracks firmware branch `feat/mesh-beacon`.
Audience: firmware reviewers (Part 1) and client-app developers Android / Apple / Web / Python (Part 2).
Audience: firmware reviewers (Part 1) and client-app developers - Android / Apple / Web / Python (Part 2).
The Mesh Beacon module lets a node periodically **advertise the existence of a mesh** to
nodes that are not yet on it broadcasting a short human-readable message plus an optional
nodes that are not yet on it - broadcasting a short human-readable message plus an optional
"join offer" (a channel, region, and modem preset). It is the mechanism behind invitations
like _"Join us on NarrowSlow"_: a node sitting on one preset/region can shout an invitation
that listeners on other presets/regions can hear and surface to their user.
The module is deliberately **advisory**. The firmware never auto-joins an advertised
channel or auto-switches preset/region in response to a received beacon it delivers the
channel or auto-switches preset/region in response to a received beacon - it delivers the
information to the client app and stops there. All "should I act on this?" decisions belong
to the client and, ultimately, the user.
---
## Part 1 Function and settings choices
## Part 1 - Function and settings choices
### 1.1 Two roles in one module
@@ -25,7 +25,7 @@ to the client and, ultimately, the user.
| **Broadcaster** | `MeshBeaconBroadcastModule` | `FLAG_BROADCAST_ENABLED` set | Periodically transmits `MESH_BEACON_APP` packets on the configured radio settings. |
| **Listener** | `MeshBeaconListenerModule` | `FLAG_LISTEN_ENABLED` set | Receives `MESH_BEACON_APP` packets and caches the offer for the client (the packet itself flows to the client unchanged). |
The boolean toggles live in a single `flags` bitfield (see [§1.8](#18-settings-reference-moduleconfigmeshbeaconconfig-tag-17)) broadcasting and
The boolean toggles live in a single `flags` bitfield (see [§1.8](#18-settings-reference-moduleconfigmeshbeaconconfig-tag-17)) - broadcasting and
listening can be enabled independently on the same node. The whole module compiles out under the
`MESHTASTIC_EXCLUDE_BEACON` build flag.
@@ -60,16 +60,16 @@ Every outgoing beacon packet is stamped uniformly (`sendBeacon` → `stampPacket
- `to = NODENUM_BROADCAST`
- `from = local node` (see [§1.6](#16-broadcast_send_as_node-currently-disabled) for the disabled spoof path)
- **`hop_limit = 0`** beacons are **zero-hop**. They are never rebroadcast by the mesh; only
- **`hop_limit = 0`** - beacons are **zero-hop**. They are never rebroadcast by the mesh; only
direct RF neighbours hear them. This is the primary spam-control mechanism. (`hop_start` is
normally `0` too, but `FLAG_LEGACY_SPLIT` raises it to `1` for old-firmware compatibility see
normally `0` too, but `FLAG_LEGACY_SPLIT` raises it to `1` for old-firmware compatibility - see
[§1.5](#15-legacy-split-flag_legacy_split).)
- `priority = BACKGROUND`, `want_ack = false`.
Broadcasting is additionally gated at runtime by:
- airtime utilisation (`isTxAllowedAirUtil()`), and
- device role **`CLIENT_HIDDEN` never broadcasts**.
- device role - **`CLIENT_HIDDEN` never broadcasts**.
#### Interval
@@ -78,7 +78,7 @@ Broadcasting is additionally gated at runtime by:
floor are silently raised, both at config-set time (AdminModule) and at runtime.
The cadence is **reboot-safe**. Each broadcast's time is persisted to flash via `TransmitHistory`
(keyed by `MESH_BEACON_APP`), and the broadcaster reads it back on boot so a node that reboots
(keyed by `MESH_BEACON_APP`), and the broadcaster reads it back on boot - so a node that reboots
(or crash-loops) won't re-broadcast until a full interval has elapsed since its last real send,
rather than firing ~30 s after every boot. The timestamp is written **before** the transmit, so a
brown-out during the high-current LoRa TX still counts as "sent." This mirrors `NodeInfoModule` /
@@ -90,13 +90,13 @@ A beacon's whole point is often to reach a mesh on a _different_ preset/region/c
broadcaster currently runs. Before transmitting a beacon tagged with target radio settings, the
module temporarily reconfigures the radio (`reconfigureForBeaconTX`), sends, then restores the
prior config. Per-packet target settings are held in an 8-entry **sidecar table** keyed by packet
ID chosen so the `MeshPacket` proto carries no extra per-packet radio fields, and normal
ID - chosen so the `MeshPacket` proto carries no extra per-packet radio fields, and normal
(non-beacon) traffic is never touched.
Two safety guards run before any radio switch (`beaconTxConfigInvalid`):
1. **An unlicensed node never keys up on a licensed-only (ham) region.** (The reverse a licensed
node operating in a non-ham region is allowed. The switch only touches preset/region/channel,
1. **An unlicensed node never keys up on a licensed-only (ham) region.** (The reverse - a licensed
node operating in a non-ham region - is allowed. The switch only touches preset/region/channel,
never `owner.is_licensed`.)
2. **The preset must be valid for the target region** (`validateConfigLora`).
@@ -109,13 +109,13 @@ Encryption keys off the **primary** channel slot, and the radio-thread channel s
_after_ encryption. So when a beacon goes out on an override channel (different name/PSK), the
module installs the beacon channel into the primary slot for the synchronous duration of
`send()`, then restores it (`sendBeaconPacket`). This guarantees the packet is encrypted with the
beacon channel's key and stamped with its hash not the primary's. Meshtastic threading is
beacon channel's key and stamped with its hash - not the primary's. Meshtastic threading is
cooperative, so there is no preemption between swap and restore.
### 1.4 Where beacons are sent: single-target and multi-target
The broadcaster can send to one set of radio settings or to several. **Single- and multi-target
are equal options neither is preferred and neither is legacy.** Pick whichever matches the
are equal options - neither is preferred and neither is legacy.** Pick whichever matches the
deployment.
- **Single-target:** the scalar `broadcast_on_preset` / `broadcast_on_region` /
@@ -124,9 +124,9 @@ deployment.
non-empty it takes over from the scalar `broadcast_on_*` fields, and the broadcaster sends **one
beacon copy per entry**. Each `BroadcastTarget` is `{ optional preset, region, optional channel_index }`,
where `channel_index` references a slot in the node's own channel table (the channel must already be
configured locally its key is needed to encrypt the beacon). Within one cycle, targets that
resolve to the **same** effective preset/region/channel are de-duplicated only the first is
transmitted so an accidentally repeated entry costs no extra airtime.
configured locally - its key is needed to encrypt the beacon). Within one cycle, targets that
resolve to the **same** effective preset/region/channel are de-duplicated - only the first is
transmitted - so an accidentally repeated entry costs no extra airtime.
#### Same-settings vs. other-settings
@@ -135,7 +135,7 @@ settings** or specify **different** ones:
- **Same-settings ("message of the day"):** leave the preset / region / channel unset. They fall
back to the running config, so the beacon goes out on the node's current mesh with **no radio
switch** a plain periodic broadcast to whoever is already on this preset/region.
switch** - a plain periodic broadcast to whoever is already on this preset/region.
- **Other-settings (cross-mesh invite):** set a preset / region / channel that differs from the
running config. The radio is temporarily switched for that copy's TX, then restored (see
[§1.3](#radio-switching-for-tx)).
@@ -154,8 +154,8 @@ offer, but old firmware only decodes `TEXT_MESSAGE_APP` and would never show the
`FLAG_LEGACY_SPLIT` is set **and both text and offer content are present**, the broadcaster
emits **two** packets on the same beacon radio settings instead of one:
- **Packet A** `MESH_BEACON_APP` carrying the **offer only** (no text).
- **Packet B** `TEXT_MESSAGE_APP` carrying the **text only**.
- **Packet A** - `MESH_BEACON_APP` carrying the **offer only** (no text).
- **Packet B** - `TEXT_MESSAGE_APP` carrying the **text only**.
This is an independent two-packet decision, not an either/or: offer-only and text-only payloads
still go out as a single packet in their respective cases; only the both-present case splits.
@@ -163,7 +163,7 @@ still go out as a single packet in their respective cases; only the both-present
**(b) `hop_start = 1` override.** When `FLAG_LEGACY_SPLIT` is set, **every** beacon packet it sends
(combined, split-A, or split-B; even same-settings ones) is stamped with `hop_start = 1` while
`hop_limit` stays `0`. Pre-2.7.20 firmware drops `hop_start == 0` packets in a pre-decryption check
before it can read the bitfield, so `hop_start = 1` lets those nodes accept the beacon and it
before it can read the bitfield, so `hop_start = 1` lets those nodes accept the beacon - and it
remains genuinely zero-hop (`hop_limit = 0` still prevents any rebroadcast).
> **Side effect for clients:** with `hop_start = 1, hop_limit = 0`, receivers compute
@@ -181,26 +181,26 @@ AdminModule and should be treated as canonical:
> A remote admin may only set `broadcast_send_as_node` to **their own** node ID
> (`mp.from`). Any other value is rejected and reset to the stored value.
Design note for when it is re-enabled: it is a _node-ID_ spoof only it rewrites `from` but forges
Design note for when it is re-enabled: it is a _node-ID_ spoof only - it rewrites `from` but forges
no signature. Once `from` is not us, the packet is no longer `isFromUs()`, so the router skips
XEdDSA signing and receivers get an unsigned packet attributed to another node.
### 1.7 Reception behaviour (listener)
When `FLAG_LISTEN_ENABLED` is **off**, the router drops incoming `MESH_BEACON_APP` packets up front
(`Router::handleReceived`, same pattern as a disabled NeighborInfo module) so they reach neither
(`Router::handleReceived`, same pattern as a disabled NeighborInfo module) - so they reach neither
the modules nor the phone. When it is **on**, the packet flows normally and the listener's
`wantPacket` accepts it (`has_mesh_beacon` + `FLAG_LISTEN_ENABLED` + `portnum == MESH_BEACON_APP`).
On a valid beacon (`handleReceivedProtobuf`):
1. **Offer → cache.** Any offer (`offer_channel` / `offer_region` / `offer_preset`) is stored in
the static `lastReceivedOffer` (sender, channel, region, preset, `received_at`). `received_at`
is `0` if the node has no RTC fix yet **consumers must not treat `0` as a valid timestamp.**
is `0` if the node has no RTC fix yet - **consumers must not treat `0` as a valid timestamp.**
2. **Never auto-applied.** The firmware does not switch channel/preset/region from a received
offer. Acting on it is the client app's job.
3. The handler returns `CONTINUE` (not `STOP`), so the original `MESH_BEACON_APP` packet **flows to
the client unchanged** through the normal FromRadio path (see Part 2). The client reads the
`message` field directly from that packet there is no separate copy.
`message` field directly from that packet - there is no separate copy.
The firmware deliberately does **not** unwrap a combined beacon's text into a synthesized
`TEXT_MESSAGE_APP`, and does **not** fire `EVENT_RECEIVED_MSG`: a beacon is an advisory broadcast,
@@ -236,35 +236,35 @@ a real `TEXT_MESSAGE_APP` over RF (see [§1.5](#15-legacy-split-flag_legacy_spli
| 2 | `FLAG_BROADCAST_ENABLED` | Periodically broadcast beacons from this node. |
| 4 | `FLAG_LEGACY_SPLIT` | Legacy compatibility: (a) split text+offer into separate `TEXT_MESSAGE_APP` + `MESH_BEACON_APP` packets, and (b) stamp `hop_start = 1` on every beacon so pre-2.7.20 firmware accepts it (see [§1.5](#15-legacy-split-flag_legacy_split)). |
`BroadcastTarget`: `1 preset` (optional, falls back to running config), `2 region` (`UNSET` = running config), `4 channel_index` (optional `uint32`, index into the node's channel table; if unset, the default channel for the preset is used). Tag `3` is an unused gap it previously held an embedded `ChannelSettings`, dropped to keep `ModuleConfig` within the BLE `FromRadio` size budget.
`BroadcastTarget`: `1 preset` (optional, falls back to running config), `2 region` (`UNSET` = running config), `4 channel_index` (optional `uint32`, index into the node's channel table; if unset, the default channel for the preset is used). Tag `3` is an unused gap - it previously held an embedded `ChannelSettings`, dropped to keep `ModuleConfig` within the BLE `FromRadio` size budget.
---
## Part 2 Client interface specification
## Part 2 - Client interface specification
This section is what a client app needs to integrate with the beacon module. Everything goes
through the **standard admin / ToRadio / FromRadio protocol** there is no bespoke transport.
through the **standard admin / ToRadio / FromRadio protocol** - there is no bespoke transport.
### 2.1 Capability detection
The module is build-flag optional. Treat it as present when the node's `LocalModuleConfig`
contains a `mesh_beacon` sub-message (`LocalModuleConfig.mesh_beacon`, tag 18). If absent, the
firmware was built with `MESHTASTIC_EXCLUDE_BEACON` hide the beacon UI.
firmware was built with `MESHTASTIC_EXCLUDE_BEACON` - hide the beacon UI.
### 2.2 Reading and writing configuration
Standard module-config flow no new admin messages:
Standard module-config flow - no new admin messages:
- **Read:** `AdminMessage.get_module_config_request = ModuleConfig.MeshBeaconConfig` (variant 17).
Reply is `get_module_config_response` with the `mesh_beacon` payload.
- **Write:** `AdminMessage.set_module_config { mesh_beacon = … }`.
The on/off toggles (listen, broadcast, legacy-split) are bits in the `flags` field, not separate
booleans read/write them with the `MeshBeaconConfig.Flags` values
booleans - read/write them with the `MeshBeaconConfig.Flags` values
(`FLAG_LISTEN_ENABLED = 1`, `FLAG_BROADCAST_ENABLED = 2`, `FLAG_LEGACY_SPLIT = 4`). To toggle one
bit, read the current `flags`, set/clear the bit, and write the whole config back.
The firmware **sanitises on write** your value may be silently adjusted. Mirror these rules
The firmware **sanitises on write** - your value may be silently adjusted. Mirror these rules
client-side so the UI doesn't disagree with the device:
| Rule | Firmware behaviour |
@@ -276,7 +276,7 @@ client-side so the UI doesn't disagree with the device:
| `broadcast_offer_region` not a known region | Cleared to `UNSET`. |
| `broadcast_targets[i].region` not a known region | That entry's region cleared to `UNSET` (TX falls back to running config). |
| `broadcast_targets[i].preset` invalid for that entry's region | That entry's `preset` and `channel_index` cleared. |
| `broadcast_targets[i].channel_index``MAX_NUM_CHANNELS` (8) | That entry's `channel_index` cleared (existence is **not** checked see §2.5). |
| `broadcast_targets[i].channel_index``MAX_NUM_CHANNELS` (8) | That entry's `channel_index` cleared (existence is **not** checked - see §2.5). |
| `broadcast_send_as_node` ≠ sender's node ID (remote admin) | Rejected, reset to stored value. |
Setting beacon config does **not** trigger a reboot (`shouldReboot = false`); changes take effect
@@ -285,7 +285,7 @@ effective (sanitised) values.
### 2.3 Receiving beacons
A received beacon reaches the client as a normal `FromRadio.packet` (`MeshPacket`) the listener
A received beacon reaches the client as a normal `FromRadio.packet` (`MeshPacket`) - the listener
returns `CONTINUE`, so the packet is **not** consumed on-device. The client must:
1. Subscribe to the FromRadio packet stream as usual.
@@ -295,48 +295,48 @@ returns `CONTINUE`, so the packet is **not** consumed on-device. The client must
4. `packet.from` is the **originating beaconer** (the firmware preserves it).
> **Requires `FLAG_LISTEN_ENABLED` set in `flags`.** With listening disabled the firmware drops
> received `MESH_BEACON_APP` packets in the router before they reach the phone or any on-device
> handler the same way it drops a disabled module's packets (e.g. NeighborInfo). The node still
> received `MESH_BEACON_APP` packets in the router - before they reach the phone or any on-device
> handler - the same way it drops a disabled module's packets (e.g. NeighborInfo). The node still
> physically receives the RF, but the client will not see beacons over the FromRadio stream until
> listening is enabled.
#### Reading the text no duplication
#### Reading the text - no duplication
For a beacon-aware client the text is **simply the `message` field of the `MESH_BEACON_APP`
packet** you already decode for the offer (step 3 above). One packet, one field the firmware does
packet** you already decode for the offer (step 3 above). One packet, one field - the firmware does
**not** inject a separate `TEXT_MESSAGE_APP` copy, so there is nothing to deduplicate.
The only time a beacon's text arrives as a separate `TEXT_MESSAGE_APP` is when the broadcaster set
`FLAG_LEGACY_SPLIT`: in that mode the `MESH_BEACON_APP` carries the **offer only** (empty `message`)
and the text is sent as a normal `TEXT_MESSAGE_APP` over RF, so legacy/non-beacon-aware clients can
display it. These two cases are mutually exclusive a given beacon's text appears exactly once,
either in `MESH_BEACON_APP.message` (combined) or as a `TEXT_MESSAGE_APP` (legacy-split) so a
display it. These two cases are mutually exclusive - a given beacon's text appears exactly once,
either in `MESH_BEACON_APP.message` (combined) or as a `TEXT_MESSAGE_APP` (legacy-split) - so a
client never needs to dedup. Render whichever it receives.
### 2.4 Acting on an offer (the core client responsibility)
When a `MESH_BEACON_APP` carries offer content, present it to the user as an **invitation**
When a `MESH_BEACON_APP` carries offer content, present it to the user as an **invitation** -
e.g. _"Node ⟨from⟩ invites you to join '⟨offer_channel.name⟩' on ⟨preset⟩/⟨region⟩."_ Then, only on
explicit user confirmation, apply it by writing normal config:
- `offer_channel` → add/replace a `Channel` (`set_channel`), typically as a secondary channel.
- `offer_region` / `offer_preset``set_config { lora = … }` (`use_preset = true`, set
`modem_preset` and `region`). **Note this changes the node's own radio and will drop it off its
current mesh** make that consequence explicit in the UI.
current mesh** - make that consequence explicit in the UI.
**The firmware will never do any of this for the user. No silent auto-apply.** The on-device
`lastReceivedOffer` cache is a firmware-internal convenience and is **not** currently exposed via
an admin message clients should source offers from the live `MESH_BEACON_APP` packet stream
an admin message - clients should source offers from the live `MESH_BEACON_APP` packet stream
(§2.3), not expect a "get last offer" RPC.
#### Offer trust model read before applying
#### Offer trust model - read before applying
- **The advertised PSK is not a secret.** `offer_channel.psk` is a public join token sent in the
clear inside a broadcast; it is a convenience, not a security boundary. An operator who wants a
genuinely private channel must distribute the PSK out-of-band and leave `offer_channel` unset.
Surface offered channels as **public/open** to the user.
- **Validate before applying.** Reject or warn if `offer_preset` is not valid for `offer_region`,
and **never** apply a licensed-only (ham) region for a user who is not a licensed operator
and **never** apply a licensed-only (ham) region for a user who is not a licensed operator -
mirror the firmware's own guard.
- Beacons are **unsigned** when sent as another node (the disabled send-as path), and even normal
beacons assert nothing about the sender's authority. Treat `from` as informational.
@@ -356,7 +356,7 @@ broadcast_offer_preset = NARROW_SLOW
broadcast_offer_region = EU_N_868
broadcast_offer_channel = { name: "MyChannel", psk: <32-byte key> }
broadcast_interval_secs = 3600
// channel_index points at slots in THIS node's channel table configure those channels first.
// channel_index points at slots in THIS node's channel table - configure those channels first.
broadcast_targets = [
{ preset: LONG_FAST, region: EU_868, channel_index: 0 },
{ preset: NARROW_SLOW, region: EU_N_868, channel_index: 1 },
@@ -364,13 +364,13 @@ broadcast_targets = [
```
The same fields can be baked in at build time via `userPrefs.jsonc`
(`USERPREFS_MESH_BEACON_*`) see that file for the full list, including
(`USERPREFS_MESH_BEACON_*`) - see that file for the full list, including
`USERPREFS_MESH_BEACON_TARGET_<n>_*` for multi-target entries.
#### Single-target vs. multi-target equal options, different channel representation
#### Single-target vs. multi-target - equal options, different channel representation
Single-target and multi-target are **equal, first-class options**. Neither is preferred,
deprecated, or a "legacy" fallback pick whichever matches the deployment (a single-target
deprecated, or a "legacy" fallback - pick whichever matches the deployment (a single-target
beacon with no overrides is a plain message-of-the-day; a multi-target list reaches several
preset/region/channel combinations). The broadcaster uses `broadcast_targets` when it is
non-empty and the scalar `broadcast_on_*` fields when it is empty.
@@ -379,13 +379,13 @@ The one **subtle implementation difference** is how each names its TX channel:
| Path | TX channel is specified by | Channel name/PSK live… |
| ------------- | ------------------------------------------------------- | ----------------------------------------- |
| Single-target | `broadcast_on_channel` an embedded `ChannelSettings` | …inline in the beacon config |
| Multi-target | `broadcast_targets[i].channel_index` a `uint32` index | …in the node's channel table (referenced) |
| Single-target | `broadcast_on_channel` - an embedded `ChannelSettings` | …inline in the beacon config |
| Multi-target | `broadcast_targets[i].channel_index` - a `uint32` index | …in the node's channel table (referenced) |
This asymmetry is deliberate: embedding a full `ChannelSettings` in every one of the (up to
four) targets would push `ModuleConfig` past the BLE `FromRadio` size limit, so a target
references an already-configured channel-table slot instead. `broadcast_offer_channel` (the
advertised join token) is **always** inline regardless of path it is the advertisement payload
advertised join token) is **always** inline regardless of path - it is the advertisement payload
and must carry the actual name/PSK.
#### Configuring a multi-target broadcaster (two-step)
@@ -412,19 +412,19 @@ admin writes**, in order:
Notes:
- A target may **only** reference a channel that already exists locally the node needs that
- A target may **only** reference a channel that already exists locally - the node needs that
channel's key to encrypt the beacon. A `channel_index` that is out of range, or points at a
blank/unconfigured slot, is not an error: the beacon falls back to the node's **current/primary
channel** (its name, PSK, and slot) on the target preset/region. The channel name only defaults
to the preset's display name (e.g. `LongFast`) when the primary channel itself is unnamed so
to the preset's display name (e.g. `LongFast`) when the primary channel itself is unnamed - so
the fallback is "broadcast on my home channel," **not** a freshly-synthesised default-PSK channel
for that preset.
- `channel_index` must be `< MAX_NUM_CHANNELS` (8); the firmware clears it on write otherwise (see
§2.2 sanitise rules). This is the **only** check on write the firmware does **not** verify that
§2.2 sanitise rules). This is the **only** check on write - the firmware does **not** verify that
the referenced slot is actually populated, because you may legitimately write the beacon config
before creating the channel. **Validating that a referenced channel exists is the client app's
responsibility.** A dangling reference doesn't error; it silently falls back to the preset's
default channel so without a client-side check, the user can believe they're advertising
default channel - so without a client-side check, the user can believe they're advertising
channel _X_ while the node is really transmitting on the preset default. Before writing, confirm
each `channel_index` maps to a configured `Channel`, and warn the user otherwise.
- **No automatic deduplication of channels.** Neither the beacon config nor the channel table
@@ -432,9 +432,9 @@ Notes:
indices whose slots hold identical settings, and `set_channel` will happily store two slots with
the same name/PSK. The broadcaster _does_ skip transmitting a target whose effective
preset/region/channel duplicates an earlier one in the same cycle (so a duplicated entry wastes
no airtime), but it does not rewrite or reject your config keeping the target list free of
no airtime), but it does not rewrite or reject your config - keeping the target list free of
redundant entries is up to the client.
- The single-target path needs no separate `set_channel` step its `broadcast_on_channel` is
- The single-target path needs no separate `set_channel` step - its `broadcast_on_channel` is
written inline in the same beacon-config message.
### 2.6 Quick reference
@@ -449,6 +449,6 @@ Notes:
| Min broadcast interval | 3600 s (1 h) |
| Message max length | 100 bytes |
| Hop behaviour | Zero-hop (`hop_limit = 0`), never rebroadcast; `hop_start = 1` under `FLAG_LEGACY_SPLIT` |
| Auto-apply offers? | **Never** client + user decide |
| Auto-apply offers? | **Never** - client + user decide |
| Offer PSK | Public join token, not a secret |
| Disabled today | `broadcast_send_as_node` application |