claude f9dd8bc1c9 Point documentation/issue_tracker at the actual Gitea repo
Replace the placeholder github.com/tewris URLs (guessed from the user's email
domain) with the real repository on git.stalkersvr.ru.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 21:48:47 +03:00

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 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.

You configure the shared stack once (the RNS connection), and a first LXMF identity is created automatically. Additional identities are added as sub-entries from the integration page.

Stack field Meaning
Target host Host/IP of the neighbouring Reticulum TCP server
Target port Its listen_port (default 4242)
Interface name Label for the connection in the generated RNS config
First identity name Announce name of the first identity

Identities (sub-entries)

Each identity is an independent LXMF address with its own settings, its own device, and its own entities. Add one via the integration's ⋮ → Add identity (or reconfigure an existing one). Typical uses:

  • an assistant identity (Assist enabled) that users chat with;
  • a receive-only identity (Assist disabled) whose messages are handled by automations via the reticulum_message_received event.

Per-identity settings:

  • Name contact name announced to peers (also the identity's HA name). Change it any time by reconfiguring the identity; future announces use it.
  • 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. To chat, install an LLM conversation integration (OpenAI, Google Generative AI, Anthropic, Ollama, …) and select it here.

  • Reply to any sender / Allowed sender addresses restrict who is answered.
  • Default recipient address this identity's notify entity sends to.
  • Auto-reply (when Assist is off) message sent automatically in reply to incoming messages only when Assist routing is off. Blank = disabled.
  • Default delivery method, announce interval, propagation node + sync interval.

Stack-level options (⚙ → Configure): Reticulum log level (07).

Find each identity's LXMF address on its LXMF address sensor, or in the log (Reticulum identity '…' ready: a1b2c3…). Add it as a contact in Sideband/MeshChat and message it.


Entities

Hub (stack) device — Reticulum:

Entity Description
binary_sensor Connected Whether the TCP interface is online
sensor Known peers Count + peers attribute (address, name, hops)
sensor Received / Transmitted Interface RX/TX byte counters
sensor Bitrate Interface bitrate
sensor RSSI / SNR / Link quality Physical-link telemetry (disabled by default)

Per identity device (one per identity):

Entity Description
sensor LXMF address This identity's address (share it with users)
sensor Messages received / sent / failed Counters
sensor Last message Last inbound text + source/title attributes
button Announce Announce this identity now
button Sync propagation node Pull queued messages now
button Regenerate identity Generate a brand-new address for this identity
notify Send message Sends an LXMF message from this identity to its default recipient (action: notify.send_message); for arbitrary addresses use the reticulum.send_message service

RSSI, SNR and link quality only carry data on a physical interface (RNode/LoRa) — they stay unavailable on a plain TCP link, so they are disabled by default.

Regenerate identity replaces this identity's address with a fresh one and re-announces. The old address stops working — contacts must re-add the new one.


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
  identity: "Home Assistant"   # optional: which identity to send from (name or
                               # LXMF address); defaults to the first identity

Other services: reticulum.announce, reticulum.request_path, reticulum.set_propagation_node, reticulum.sync_propagation. Each accepts an optional identity to pick which identity to act through.


Events

  • reticulum_message_received{content, title, source, destination, timestamp, signature_validated, fields, identity, identity_name, local_address}
  • reticulum_message_delivered{message_hash, destination, identity, identity_name, local_address}
  • reticulum_message_failed{message_hash, destination, identity, identity_name, local_address}
  • reticulum_announce_received{destination, display_name, stamp_cost, hops}

identity is the subentry id and identity_name the identity's name, so automations can tell which identity received/sent a message.

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, so only one Reticulum config entry (the stack) is allowed; add multiple identities as sub-entries under it. Because RNS/LXMF cannot be cleanly torn down inside a running process, the running stack and each identity's router are reused across reloads. Changing the host/port (or recovering from a failed first start) therefore requires a full Home Assistant restart, not just a reload.
  • Each identity is stored in config/reticulum/identities/<subentry_id>/identity — back it up to keep the same address. Regenerating an identity overwrites it with a new address.
  • RNS and LXMF are standalone-daemon-style libraries — both install SIGINT/SIGTERM handlers and atexit persistence 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 blocking atexit persistence, persisting RNS/LXMF state itself, off the event loop, on the homeassistant_stop event. 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

Description
No description provided
Readme 217 KiB
Languages
Python 100%