-
Notifications
You must be signed in to change notification settings - Fork 0
LoRa Gateway Tags
Crow uses short gateway tags when forwarding gateway-originated cleartext messages OUT to local LoRa networks (Meshtastic or MeshCore). The outbound LoRa payload looks like:
CALLSIGN@TAG> message
Examples:
KJ6DZB@MCGW> Hello local MeshCore
KJ6DZB@MCG2> Same message via the second MeshCore backend
KJ6DZB@MTGW> Hello Meshtastic
KJ6DZB@MTG1> Same message via the second Meshtastic backend
LoRa-side users need to know that a message came through a gateway and which gateway/backend emitted it. Crow keeps the original sender's callsign at the front of the LoRa text payload and adds a compact backend tag.
This complements the inbound-side Strict Gatekeeper [SENDER via GATEWAY] annotation:
-
Inbound (LoRa → AREDN): gatekeeper rewrites text as
[KJ6DZB via W6XYZ] helloso AREDN readers see who sent it and which gateway carried it. -
Outbound (AREDN → LoRa): the tagged wrapper rewrites text as
W6XYZ@MCGW> helloso LoRa readers see who sent it and which gateway emitted it.
Tagging happens at the outbound backend layer, not globally in the router, because the backend knows the actual target path.
Tagging is implemented as a wrapper module that wraps the production backend. The wrapper:
- Receives the outbound message from the router via
send(msg). - Runs
lora_outbound_text.prepare(msg, target_transport, gateway_index, max_payload)to build the tagged payload. - Replaces
msg.data.text_messagewith the tagged text (the original message object is cloned, not mutated). - Hands the tagged message to the underlying production backend for actual transmission.
Inbound traffic (recv()) is passed through unchanged. Tagging only affects outbound LoRa text. Non-text messages and messages with no text_message field are passed through unchanged.
If the tagged payload exceeds the backend's payload budget, the original message text is truncated to fit and ... is appended. The callsign and tag are always preserved.
The helper module is lora_outbound_text.uc. The tag family is:
| Target backend family | Primary gateway | Additional gateways |
|---|---|---|
| MeshCore | MCGW |
MCG2, MCG3, MCG4, ... |
| Meshtastic | MTGW |
MTG1, MTG2, MTG3, ... |
Numbering preserves the short primary gateway names:
-
MCGWis the primary MeshCore gateway. -
MCG2is the second MeshCore gateway/backend. -
MTGWis the primary Meshtastic gateway. -
MTG1is the first additional Meshtastic gateway/backend.
The index is set via gateway_index in the matching backend config block.
Gateway tagging is off by default. Production routing uses the raw backends:
import * as meshtastic from "meshtastic"; // raw — no outbound tag
import * as meshcore from "meshcore"; // raw — no outbound tag
To turn tagging on, swap the import lines in router.uc to the tagged wrappers:
import * as meshtastic from "meshtastic_tagged";
import * as meshcore from "meshcore_tagged";
You can enable just one side if you only want tags on one transport.
In router.uc:
import * as meshtastic from "meshtastic_tagged";
In config:
{
"meshtastic": {
"enabled": true,
"gateway_index": 0,
"gateway_tag_max_payload": 200
}
}gateway_index |
Tag |
|---|---|
0 |
MTGW |
1 |
MTG1 |
2 |
MTG2 |
In router.uc:
import * as meshcore from "meshcore_tagged";
In config:
{
"meshcore": {
"enabled": true,
"gateway_index": 0,
"gateway_tag_max_payload": 150
}
}gateway_index |
Tag |
|---|---|
0 |
MCGW |
1 |
MCG2 |
2 |
MCG3 |
Swap the import lines back to the raw modules. No message-schema changes are required and no config keys need to be removed (extra config fields are simply ignored by the raw backends).
The helper signature is:
prepare(msg, target_transport, gateway_index, max_payload)
Returns a safely formatted string:
CALLSIGN@TAG> original text
Callsign lookup order:
msg.originating_callsign
msg.callsign
msg.from_callsign
msg.data.callsign
UNKNOWN
If the formatted payload exceeds max_payload, the original text is truncated and ... is appended. The header (CALLSIGN@TAG> ) is preserved; if even the header exceeds the budget, the header itself is truncated and the payload is dropped.
Default budget: 255 bytes.
Backends should pass a smaller budget if their packet format adds overhead or has a tighter text limit. This avoids hidden truncation deeper in the encoder.
Recommended budgets:
| Backend | Suggested call |
|---|---|
| MeshCore (UDP) | prepare(msg, "meshcore", gateway_index, 150) |
| Meshtastic (UDP) | prepare(msg, "meshtastic", gateway_index, 200) |
Gateway tagging happens immediately before the backend packet encoder.
Do not inject the tag globally in the router because msg.transport describes where the message came from, not where it is going. The router has a single send call but the tagging needs to know the actual outbound transport.
Correct placement:
AREDN/native message
→ router.uc decides outbound backend
→ meshtastic_tagged.send() or meshcore_tagged.send()
→ lora_outbound_text.prepare(...)
→ underlying meshtastic.send() or meshcore.send()
→ encoder / UDP multicast
The formatter emits DEBUG1 lines:
lora_outbound_text: formatted outbound target=meshcore tag=MCGW callsign=KJ6DZB total=42 max=150
lora_outbound_text: truncated outbound target=meshtastic tag=MTG1 callsign=KJ6DZB original=320 final=200 max=200
lora_outbound_text: header exceeds payload budget callsign=KJ6DZB tag=MCGW header_len=14 max=10
Run from the repo root:
node tests/run_formatter_tests.js
ucode -R -L tests/test_outbound_formatter.uc # on a node with ucode20 cases covering:
| Input | Backend | Index | Expected prefix |
|---|---|---|---|
Hello |
meshcore |
0 |
CALLSIGN@MCGW> Hello |
Hello |
meshcore |
1 |
CALLSIGN@MCG2> Hello |
Hello |
meshcore |
2 |
CALLSIGN@MCG3> Hello |
Hello |
meshtastic |
0 |
CALLSIGN@MTGW> Hello |
Hello |
meshtastic |
1 |
CALLSIGN@MTG1> Hello |
Hello |
meshtastic |
2 |
CALLSIGN@MTG2> Hello |
Plus truncation behavior, missing-text passthrough, callsign fallback chain, and exact-fit boundary.
| Module | Role |
|---|---|
lora_outbound_text.uc |
Shared formatter. gatewayTag() returns the tag string; prepare() returns the full tagged payload. |
meshtastic_tagged.uc |
Wrapper around meshtastic.uc (production UDP backend) that calls prepare() on every outbound send(msg). |
meshcore_tagged.uc |
Wrapper around meshcore.uc (production UDP backend) that calls prepare() on every outbound send(msg). |
meshtastic.uc |
Raw Meshtastic UDP backend. Untagged. |
meshcore.uc |
Raw MeshCore UDP backend. Untagged. |
The wrappers re-export enabled, setup, shutdown, handle, recv, send, tick, and process. They are drop-in replacements for the raw backends in router.uc.
| Transport | Tagging status |
|---|---|
meshtastic (UDP, production) |
Wrapper exists (meshtastic_tagged.uc). Opt-in via import swap. |
meshcore (UDP, production) |
Wrapper exists (meshcore_tagged.uc). Opt-in via import swap. |
meshtastic_API (TCP Port-API, experimental) |
No tagged wrapper. Backend isn't wired into the router yet. |
meshcore_tcp_api (TCP Companion API, experimental) |
No tagged wrapper. Backend only receives TXT_MSG/GRP_TXT today; outbound is stubbed and outbound LoRa still goes via meshcore.uc. |
When the experimental TCP backends are promoted to production, equivalent tagged wrappers should be added before they are wired into router.uc.
- Strict Gatekeeper — inbound-side annotation that pairs with outbound tagging
- Bridges — supported transports and what they do
- Home
- Change Log
- Configuration
- Configuring Channels
- Backend Selection and Test Deployment
- Command Reference
- APRS Bridge
- LoRa Gateway Tags
- Meshtastic API Backend
- Memory Use
- Strict Gatekeeper
- Winlink
- USB Storage
APRS.mdBackend-Selection-and-Deployment.mdChange-Log.mdCommand-Reference.mdConfiguration.mdConfiguring-Channels.mdHome.mdLoRa-Gateway-Tags.mdMeshtastic-API.mdMemory-Use.mdStrict-Gatekeeper.mdUSB-Storage.mdWinlink.md_Sidebar.md
- Keep every
.mdwiki page linked here. - Keep
Home.mdand_Sidebar.mdin sync. - When a wiki page is removed, remove it from both the Home page inventory and this sidebar.