I run a mixed mesh at home: Meshtastic radios plus a Reticulum network (RNode LoRa and LAN transport). I wanted both on one potato-mesh dashboard, so I built Reticulum support and have it running in production on my instance. Before opening PRs I want a maintainer read on a few conventions, since one of them touches the federation spec.
What it does
A new PROTOCOL=reticulum provider in data/mesh_ingestor, modeled on the meshcore provider. It is a passive announce listener using the rns package: it registers announce handlers for the lxmf.delivery and nomadnetwork.node aspects, maps each announce to a node record (display name from app_data, receipt time, hop count), and posts through the existing queue and API. The dependency is import-guarded the same way meshcore's is, so meshtastic-only deployments don't install it.
On the web side: reticulum joins the protocol whitelist and gets its own stats scope, an instances.reticulum_nodes_count column, an icon, and a filter chip. Reticulum nodes carry no SNR, battery, or position, so those columns render as dashes and the nodes stay off the map.
Decisions I'd like your read on
Node id mapping. CONTRACTS.md leaves the Reticulum id space undefined. I used ! plus the first 4 bytes of the 16-byte destination hash, with the full hash preserved in user.publicKey. This mirrors the meshcore first-4-bytes-of-pubkey rule. Fine to standardize, or do you want a different scheme?
Federation. SPEC S6 froze the protocol whitelist, and my change amends it: the self announcement now signs a live reticulum_nodes_count. The v2 canonical form already carried the field as a signed zero, so signatures from older peers still verify, and older peers relay the count as 0. Acceptable, or should this wait for a spec revision?
One physical peer can appear as two node rows, one per destination aspect. The payload doesn't carry the aspect today. I can add a field if you'd rather distinguish or merge them.
Scope. Announces only in this cut. Message, position, and telemetry ingest would need an LXMF delivery identity on the ingestor and an opt-in story, so I left them out.
Testing
Python suite went from 1215 to 1264 tests, Ruby from 1684 to 1699, JS from 1706 to 1711, all passing, black clean. My production instance has been running it with 30 meshtastic nodes and 3 reticulum nodes side by side.
Happy to split the work into an ingestor PR and a web PR referencing this issue.
I run a mixed mesh at home: Meshtastic radios plus a Reticulum network (RNode LoRa and LAN transport). I wanted both on one potato-mesh dashboard, so I built Reticulum support and have it running in production on my instance. Before opening PRs I want a maintainer read on a few conventions, since one of them touches the federation spec.
## What it does
A new `PROTOCOL=reticulum` provider in `data/mesh_ingestor`, modeled on the meshcore provider. It is a passive announce listener using the `rns` package: it registers announce handlers for the `lxmf.delivery` and `nomadnetwork.node` aspects, maps each announce to a node record (display name from app_data, receipt time, hop count), and posts through the existing queue and API. The dependency is import-guarded the same way meshcore's is, so meshtastic-only deployments don't install it.
On the web side: `reticulum` joins the protocol whitelist and gets its own stats scope, an `instances.reticulum_nodes_count` column, an icon, and a filter chip. Reticulum nodes carry no SNR, battery, or position, so those columns render as dashes and the nodes stay off the map.
## Decisions I'd like your read on
1. Node id mapping. CONTRACTS.md leaves the Reticulum id space undefined. I used `!` plus the first 4 bytes of the 16-byte destination hash, with the full hash preserved in `user.publicKey`. This mirrors the meshcore first-4-bytes-of-pubkey rule. Fine to standardize, or do you want a different scheme?
2. Federation. SPEC S6 froze the protocol whitelist, and my change amends it: the self announcement now signs a live `reticulum_nodes_count`. The v2 canonical form already carried the field as a signed zero, so signatures from older peers still verify, and older peers relay the count as 0. Acceptable, or should this wait for a spec revision?
3. One physical peer can appear as two node rows, one per destination aspect. The payload doesn't carry the aspect today. I can add a field if you'd rather distinguish or merge them.
4. Scope. Announces only in this cut. Message, position, and telemetry ingest would need an LXMF delivery identity on the ingestor and an opt-in story, so I left them out.
## Testing
Python suite went from 1215 to 1264 tests, Ruby from 1684 to 1699, JS from 1706 to 1711, all passing, `black` clean. My production instance has been running it with 30 meshtastic nodes and 3 reticulum nodes side by side.
Happy to split the work into an ingestor PR and a web PR referencing this issue.
<!-- github-sync-id:5189594547 -->
Thank you. I have been orbiting this topic for a while as you may have noticed in the code where I prepared future reticulum support on protocol level already.
I appreciate and welcome your initiative. Let me take a look at each item at once, but first your questions:
Node id, yes. Take the ! plus first four bytes of the identity, however, do not store the full value in publicKey. A Reticulum destination hash is not a public key. It is a truncated hash over the identity hash and the name hash. The announce carries the real identity public key, so put that in publicKey and give the destination hash its own column destHash (might even contain a list of destination hashes)?
Federation, I would suggest you simply ignore that for now and focus on the protocol implementation. I'll keep a tab on that. But if you already implemented this value in the Reticulum node count field, go ahead! It should work but I fear there is more work required to get it implemented properly for the federation.
Do not add aspects to distinguish them, better to merge them. lxmf.delivery and nomadnetwork.node are distinct destinations derived from one identity. RNS.Identity.recall(destination_hash) should give you the identity hash at announce time, work with that if possible.
Correct scope and good starting point! Thanks for that!
But now, I have two questions:
This is something I couldn't yet wrap my head around: How do you determine a Reticulum node (per identity) to be a LoRa peer? I would try to avoid that we load it the entire Reticulum network (e.g., over an automatic IP inteface) and clog the dashboard.
Does the ingestor call RNS.Reticulum(configdir=...) against its own directory, or inherit ~/.reticulum? Try to make it explicit and isolated. The dashboard should not adopt an operator's transport node configuration unless explicitly stated.
Thank you. I have been orbiting this topic for a while as you may have noticed in the code where I prepared future reticulum support on protocol level already.
I appreciate and welcome your initiative. Let me take a look at each item at once, but first your questions:
1. Node id, yes. Take the `!` plus first four bytes of the identity, however, do not store the full value in `publicKey`. A Reticulum destination hash is not a public key. It is a truncated hash over the identity hash and the name hash. The announce carries the real identity public key, so put _that_ in `publicKey` and give the destination hash its own column `destHash` (might even contain a list of destination hashes)?
2. Federation, I would suggest you simply _ignore_ that for now and focus on the protocol implementation. I'll keep a tab on that. But if you already implemented this value in the Reticulum node count field, go ahead! It _should_ work but I fear there is more work required to get it implemented properly for the federation.
3. Do not add aspects to distinguish them, better to merge them. `lxmf.delivery` and `nomadnetwork.node` are distinct destinations derived from one identity. `RNS.Identity.recall(destination_hash)` should give you the identity hash at announce time, work with that if possible.
4. Correct scope and good starting point! Thanks for that!
But now, I have two questions:
1. This is something I couldn't yet wrap my head around: How do you determine a Reticulum node (per identity) to be a LoRa peer? I would try to avoid that we load it the entire Reticulum network (e.g., over an automatic IP inteface) and clog the dashboard.
2. Does the ingestor call `RNS.Reticulum(configdir=...)` against its own directory, or inherit `~/.reticulum`? Try to make it explicit and isolated. The dashboard should not adopt an operator's transport node configuration unless explicitly stated.
<!-- github-comment-id:5394191620 -->
INGESTOR_NODE_ID is required for PROTOCOL=reticulum
Set INGESTOR_NODE_ID. Reticulum has no handshake revealing "our" node id.
Without it the ingestor heartbeat never registers and the Reticulum
packets/hour figure stays at zero even while announces are being ingested. The
ingestor warns about this at startup.
This needs work. (Why?)
Also, is CONNECTION and INTERFACE/CONFIG_DIR in conflict? Shoudl this be streamlined?
`INGESTOR_NODE_ID` is required for PROTOCOL=reticulum
Set `INGESTOR_NODE_ID`. Reticulum has no handshake revealing "our" node id.
Without it the ingestor heartbeat never registers and the Reticulum
packets/hour figure stays at zero even while announces are being ingested. The
ingestor warns about this at startup.
This needs work. (Why?)
Also, is CONNECTION and INTERFACE/CONFIG_DIR in conflict? Shoudl this be streamlined?
<!-- github-comment-id:5424128838 -->
for short name we use the first two bytes but for long name we use the last two bytes, e.g., !27716218, [2771], "Reticulum 6218" - maybe ingestor and web app diverged here? worth drive-by fixing. I'd prefer the first two bytes since it is
the beginning of the hash. #899
More:
- [x] Frequency (from config?) #900
- [x] LoRa "preset" (reuse meshtastic dict?) #900
- [x] logs/chat: #900
```
-- 2026-08-27 --
[18:53:11][ ][ ] 2771 ☀️ New node:[ Department of Decentralization](https://dweb.potatomesh.net/nodes/!27716218)
-- 2026-08-28 --
[13:55:10][ ][ ] fc64 ☀️ New node:[ Department of Decentralization](https://dweb.potatomesh.net/nodes/!fc64583c)
[20:54:10][ ][ ] fc64 💾 Updated node info (advert)
[21:26:51][ ][ ] 2771 💾 Updated node info (advert)
```
- [x] federation #900
- [ ] matrix bot
- [x] for short name we use the first two bytes but for long name we use the last two bytes, e.g., !27716218, [2771], "Reticulum 6218" - maybe ingestor and web app diverged here? worth drive-by fixing. I'd prefer the first two bytes since it is
the beginning of the hash. #899
<!-- github-comment-id:5456879664 -->
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
I run a mixed mesh at home: Meshtastic radios plus a Reticulum network (RNode LoRa and LAN transport). I wanted both on one potato-mesh dashboard, so I built Reticulum support and have it running in production on my instance. Before opening PRs I want a maintainer read on a few conventions, since one of them touches the federation spec.
What it does
A new
PROTOCOL=reticulumprovider indata/mesh_ingestor, modeled on the meshcore provider. It is a passive announce listener using thernspackage: it registers announce handlers for thelxmf.deliveryandnomadnetwork.nodeaspects, maps each announce to a node record (display name from app_data, receipt time, hop count), and posts through the existing queue and API. The dependency is import-guarded the same way meshcore's is, so meshtastic-only deployments don't install it.On the web side:
reticulumjoins the protocol whitelist and gets its own stats scope, aninstances.reticulum_nodes_countcolumn, an icon, and a filter chip. Reticulum nodes carry no SNR, battery, or position, so those columns render as dashes and the nodes stay off the map.Decisions I'd like your read on
!plus the first 4 bytes of the 16-byte destination hash, with the full hash preserved inuser.publicKey. This mirrors the meshcore first-4-bytes-of-pubkey rule. Fine to standardize, or do you want a different scheme?reticulum_nodes_count. The v2 canonical form already carried the field as a signed zero, so signatures from older peers still verify, and older peers relay the count as 0. Acceptable, or should this wait for a spec revision?Testing
Python suite went from 1215 to 1264 tests, Ruby from 1684 to 1699, JS from 1706 to 1711, all passing,
blackclean. My production instance has been running it with 30 meshtastic nodes and 3 reticulum nodes side by side.Happy to split the work into an ingestor PR and a web PR referencing this issue.
Thank you. I have been orbiting this topic for a while as you may have noticed in the code where I prepared future reticulum support on protocol level already.
I appreciate and welcome your initiative. Let me take a look at each item at once, but first your questions:
!plus first four bytes of the identity, however, do not store the full value inpublicKey. A Reticulum destination hash is not a public key. It is a truncated hash over the identity hash and the name hash. The announce carries the real identity public key, so put that inpublicKeyand give the destination hash its own columndestHash(might even contain a list of destination hashes)?lxmf.deliveryandnomadnetwork.nodeare distinct destinations derived from one identity.RNS.Identity.recall(destination_hash)should give you the identity hash at announce time, work with that if possible.But now, I have two questions:
RNS.Reticulum(configdir=...)against its own directory, or inherit~/.reticulum? Try to make it explicit and isolated. The dashboard should not adopt an operator's transport node configuration unless explicitly stated.Checklist
INGESTOR_NODE_IDis required for PROTOCOL=reticulumSet
INGESTOR_NODE_ID. Reticulum has no handshake revealing "our" node id.Without it the ingestor heartbeat never registers and the Reticulum
packets/hour figure stays at zero even while announces are being ingested. The
ingestor warns about this at startup.
This needs work. (Why?)
Also, is CONNECTION and INTERFACE/CONFIG_DIR in conflict? Shoudl this be streamlined?
More:
the beginning of the hash. #899