The greeting option returns, but it is now sent as an automatic reply to incoming messages only when "Route incoming messages to Assist" is disabled, so senders get an acknowledgement instead of silence. When Assist handles messages the greeting is not used. Blank disables it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reticulum for Home Assistant
A Home Assistant custom integration that connects Home Assistant to a Reticulum network over TCP and exchanges LXMF messages with users.
The headline feature: a user can chat with the Home Assistant Assist agent over Reticulum. Any LXMF client (Sideband, MeshChat, Nomad Network) can send a message to Home Assistant's address; the text is routed through the Assist conversation pipeline and the answer is sent straight back — over LoRa, packet radio, I2P, TCP, or anything else Reticulum runs on.
It also exposes a notify entity, a rich service API, sensors, and bus events
so LXMF messaging can drive and be driven by automations.
How it fits together
┌────────────────────────┐ TCP ┌─────────────────────────┐
│ Home Assistant (k8s) │ TCPClientInterface│ Neighbouring machine │
│ │ ─────────────────► │ Reticulum instance │
│ reticulum integration │ │ (TCPServerInterface) │
│ • runs its own RNS │ ◄───────────────── │ + any other interfaces │
│ • LXMF delivery dest. │ │ (LoRa / RNode / I2P …) │
│ • Assist bridge │ └─────────────────────────┘
└────────────────────────┘
Home Assistant runs a standalone, non-shared Reticulum instance inside its
own process and reaches the wider network through the single TCPClientInterface
pointing at the neighbouring stack. No shared-instance socket is used, which is
what you want inside a container/pod.
Requirements
-
Home Assistant 2024.11 or newer.
-
A reachable Reticulum instance on another machine with a
TCPServerInterfaceenabled. Example~/.reticulum/configon that machine:[interfaces] [[TCP Server Interface]] type = TCPServerInterface enabled = yes listen_ip = 0.0.0.0 listen_port = 4242In Kubernetes, expose that port to the HA pod (Service / NodePort / host networking as appropriate). Do not enable
kiss_framingon either side.
The integration declares its Python dependencies (rns, lxmf) in
manifest.json; Home Assistant installs them automatically on first setup.
Installation
HACS (custom repository)
- HACS → ⋮ → Custom repositories.
- Add this repository URL, category Integration.
- Install Reticulum, then restart Home Assistant.
Manual
Copy custom_components/reticulum into your Home Assistant config/custom_components/
directory and restart.
Configuration
Settings → Devices & Services → Add Integration → Reticulum.
| Field | Meaning |
|---|---|
| Target host | Host/IP of the neighbouring Reticulum TCP server |
| Target port | Its listen_port (default 4242) |
| Display name | Name announced to other peers (e.g. Home Assistant) |
| Interface name | Label for the connection in the generated RNS config |
On first start the integration generates a stable identity and announces it. Find your LXMF address on the LXMF address sensor, or in the log:
Reticulum ready. LXMF address: a1b2c3… (Home Assistant)
Add that address as a contact in Sideband/MeshChat and send a message — Assist will answer.
Options (⚙ → Configure)
- Route incoming messages to Assist – turn the chat bridge on/off.
- Assist agent / language – pick the conversation agent from the dropdown.
Want free-form chat? The default Home Assistant agent only recognises device-control commands and will answer anything else with "Sorry, I couldn't understand that". To actually chat, install an LLM conversation integration (OpenAI, Google Generative AI, Anthropic, Ollama, …), then select that agent here.
- Reply to any sender / Allowed sender addresses – restrict who Assist will answer.
- Default recipient – address the
notify.reticulum_send_messageentity sends to (see Entities below). - Greeting / auto-reply – message sent automatically in reply to incoming messages only when "Route incoming messages to Assist" is off (so senders get an acknowledgement instead of silence). Blank = disabled.
- Default delivery method –
direct,opportunistic, orpropagated. - Announce interval – periodic re-announce (seconds;
0disables). - Propagation node + sync interval – optional store-and-forward node so messages queue while HA is offline.
- Reticulum log level –
0–7.
Entities
| Entity | Description |
|---|---|
sensor.reticulum_lxmf_address |
This HA's LXMF address (share this with users) |
sensor.reticulum_messages_received / _sent / _failed |
Counters |
sensor.reticulum_known_peers |
Count + peers attribute (address, name, hops) |
sensor.reticulum_last_message |
Last inbound text + source/title attributes |
sensor.reticulum_last_message_time |
Timestamp of last inbound message |
sensor.reticulum_received / _transmitted |
Interface RX/TX byte counters |
sensor.reticulum_bitrate |
Interface bitrate |
sensor.reticulum_rssi / _snr / _link_quality |
Physical-link telemetry (disabled by default) |
binary_sensor.reticulum_connected |
Whether the TCP interface is online |
button.reticulum_announce |
Announce now |
button.reticulum_sync_propagation_node |
Pull queued messages now |
notify.reticulum_send_message |
notify entity that sends an LXMF message to the default recipient set in options — use it in automations/scripts (action: notify.send_message) or the Developer Tools; for arbitrary addresses use the reticulum.send_message service |
The telemetry sensors read from the outbound interface. RX/TX bytes and bitrate work for the TCP link. RSSI, SNR and link quality only carry data when the underlying interface is a physical one (RNode/LoRa) — they stay unavailable on a plain TCP link, so they are disabled by default. Enable them from the device page if you run a physical interface.
Services
reticulum.send_message
Send to any destination. Returns the message hash.
action: reticulum.send_message
data:
destination: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6
content: "The garage door is still open."
title: "Home Assistant"
method: direct # direct | opportunistic | propagated
Other services: reticulum.announce, reticulum.request_path,
reticulum.set_propagation_node, reticulum.sync_propagation.
Events
reticulum_message_received—{content, title, source, destination, timestamp, signature_validated, fields}reticulum_message_delivered—{message_hash, destination}reticulum_message_failed—{message_hash, destination}reticulum_announce_received—{destination, display_name, stamp_cost, hops}
Example automation — forward every inbound message to a mobile push
alias: Reticulum → phone
trigger:
- platform: event
event_type: reticulum_message_received
action:
- action: notify.mobile_app_phone
data:
title: "Reticulum: {{ trigger.event.data.source[:8] }}"
message: "{{ trigger.event.data.content }}"
Device triggers (automation UI)
Instead of raw event triggers you can pick these from the automation editor under the Reticulum device ("When… → Reticulum → …"):
- Message received
- Message delivered
- Message delivery failed
- Announce received (peer heard)
Each wraps the matching bus event, so trigger.event.data carries the same
payload described above.
Example automation — alert over Reticulum when a door opens
alias: Door alert over Reticulum
trigger:
- platform: state
entity_id: binary_sensor.front_door
to: "on"
action:
- action: reticulum.send_message
data:
destination: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6
content: "Front door opened at {{ now().strftime('%H:%M') }}"
Notes & limitations
- Reticulum uses a single process-wide instance. Only one Reticulum config entry is allowed, and because RNS/LXMF cannot be cleanly torn down inside a running process, the running stack is reused across reloads. Changing the host/port (or recovering from a failed first start) therefore requires a full Home Assistant restart, not just an integration reload — a reload reuses the already-running stack. Option changes that don't touch the stack apply on reload.
- The identity is stored in
config/reticulum/identity— back it up to keep the same address. - RNS and LXMF are standalone-daemon-style libraries — both install
SIGINT/SIGTERM handlers and
atexitpersistence in their constructors. To embed them cleanly the integration suppresses those signal handlers during init (so Home Assistant keeps ownership of SIGINT/SIGTERM — important under Kubernetes) and unregisters their blockingatexitpersistence, persisting RNS/LXMF state itself, off the event loop, on thehomeassistant_stopevent. RNS still spawns a couple of non-daemon worker threads, so on shutdown you may see a "non-daemonic threads" notice and a short delay before the process exits (within the pod's termination grace period). - Assist replies use the standard conversation pipeline, so whichever agent you select (built-in intents, a local LLM, a cloud LLM, …) is what answers.
License
MIT