Add Reticulum/LXMF integration for Home Assistant

Custom integration connecting Home Assistant to a Reticulum network over a
TCPClientInterface and exchanging LXMF messages, with an Assist conversation
bridge, notify entity, sensors (incl. interface telemetry), buttons, services,
bus events and device triggers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
claude
2026-07-22 18:08:12 +03:00
parent 22408c2bd5
commit b40c527515
19 changed files with 2727 additions and 1 deletions

219
README.md
View File

@@ -1,2 +1,219 @@
# ha-reticulum
# Reticulum for Home Assistant
A Home Assistant custom integration that connects Home Assistant to a
[Reticulum](https://reticulum.network/) network over TCP and exchanges
[LXMF](https://github.com/markqvist/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 `TCPServerInterface`
enabled. Example `~/.reticulum/config` on that machine:
```
[interfaces]
[[TCP Server Interface]]
type = TCPServerInterface
enabled = yes
listen_ip = 0.0.0.0
listen_port = 4242
```
In Kubernetes, expose that port to the HA pod (Service / NodePort / host
networking as appropriate). Do **not** enable `kiss_framing` on 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)
1. HACS → ⋮ → **Custom repositories**.
2. Add this repository URL, category **Integration**.
3. 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 a specific conversation agent, or leave
blank for the default pipeline.
- **Reply to any sender** / **Allowed sender addresses** restrict who Assist
will answer.
- **Default recipient** address used by the `notify.reticulum` entity.
- **Default delivery method** `direct`, `opportunistic`, or `propagated`.
- **Announce interval** periodic re-announce (seconds; `0` disables).
- **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_message` | Send a message to the default recipient |
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.
```yaml
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
```yaml
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
```yaml
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, changing the host/port or fully restarting the stack requires
restarting Home Assistant. 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.
- 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