first commit
This commit is contained in:
@@ -0,0 +1,16 @@
|
|||||||
|
# Meshtastic Add-ons for Home Assistant
|
||||||
|
|
||||||
|
A small Home Assistant add-on repository.
|
||||||
|
|
||||||
|
## Add-ons
|
||||||
|
|
||||||
|
- **Meshtastic Sensor Broadcaster** — periodically broadcasts selected Home
|
||||||
|
Assistant sensor values over a Meshtastic mesh (TCP or USB-connected node).
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
Add-on Store -> ⋮ (top right) -> **Repositories** -> paste this repo's URL.
|
||||||
|
Then install **Meshtastic Sensor Broadcaster** and configure it.
|
||||||
|
|
||||||
|
Or, for local development, copy the `meshtastic_broadcaster/` folder into the
|
||||||
|
`/addons` share and use **Check for updates** in the Add-on Store.
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## 1.0.0
|
||||||
|
|
||||||
|
- Initial release.
|
||||||
|
- Periodic broadcast of Home Assistant sensor values to a Meshtastic channel.
|
||||||
|
- TCP and serial node connections.
|
||||||
|
- Reads states via the Supervisor proxy (no long-lived token required).
|
||||||
|
- Auto-reconnect on send failure; skips unavailable sensors.
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# Meshtastic Sensor Broadcaster
|
||||||
|
|
||||||
|
Periodically reads selected Home Assistant sensors and broadcasts them as a
|
||||||
|
single short text message to a Meshtastic channel.
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
| Option | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `connection` | `tcp` (node on the network) or `serial` (node on USB). |
|
||||||
|
| `host` | Node IP/hostname (TCP mode). |
|
||||||
|
| `serial_port` | Device path, e.g. `/dev/ttyUSB0` (serial mode). |
|
||||||
|
| `channel_index` | Meshtastic channel index. `0` = primary. |
|
||||||
|
| `interval` | Seconds between broadcasts. |
|
||||||
|
| `prefix` | Optional text/emoji prepended to each message. |
|
||||||
|
| `sensors` | List of `{label, entity_id, unit}` to include. |
|
||||||
|
|
||||||
|
Example message: `🏠 T:21.4°C H:48% P:1013hPa`
|
||||||
|
|
||||||
|
## Airtime
|
||||||
|
|
||||||
|
Keep messages short and the interval reasonable (>= a few minutes) to stay within
|
||||||
|
LoRa duty-cycle limits and avoid congesting the mesh. Unavailable sensors are
|
||||||
|
skipped automatically.
|
||||||
|
|
||||||
|
## Serial nodes
|
||||||
|
|
||||||
|
Set `connection: serial` and `serial_port` to your device. The add-on requests
|
||||||
|
`uart`/`usb` access; check the device path under Settings -> System -> Hardware.
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
ARG BUILD_FROM
|
||||||
|
FROM ${BUILD_FROM}
|
||||||
|
|
||||||
|
ENV LANG=C.UTF-8
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends python3 python3-pip python3-venv \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Isolated venv (Debian bookworm marks the system env externally-managed)
|
||||||
|
RUN python3 -m venv /opt/venv
|
||||||
|
ENV PATH="/opt/venv/bin:$PATH"
|
||||||
|
|
||||||
|
COPY requirements.txt /
|
||||||
|
RUN pip install --no-cache-dir -r /requirements.txt
|
||||||
|
|
||||||
|
COPY run.sh /
|
||||||
|
COPY broadcaster.py /
|
||||||
|
RUN chmod a+x /run.sh
|
||||||
|
|
||||||
|
CMD [ "/run.sh" ]
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Broadcast Home Assistant sensor values over Meshtastic on a fixed interval."""
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
# --- Config: add-on options are written here by the Supervisor ---------------
|
||||||
|
with open("/data/options.json", encoding="utf-8") as fh:
|
||||||
|
OPTS = json.load(fh)
|
||||||
|
|
||||||
|
CONNECTION = OPTS.get("connection", "tcp")
|
||||||
|
HOST = OPTS.get("host", "")
|
||||||
|
SERIAL_PORT = OPTS.get("serial_port", "/dev/ttyUSB0")
|
||||||
|
CHANNEL_INDEX = int(OPTS.get("channel_index", 0))
|
||||||
|
INTERVAL = int(OPTS.get("interval", 900))
|
||||||
|
PREFIX = OPTS.get("prefix", "") or ""
|
||||||
|
SENSORS = OPTS.get("sensors", [])
|
||||||
|
|
||||||
|
# --- HA core API via the Supervisor proxy (token injected by Supervisor) -----
|
||||||
|
HA_API = "http://supervisor/core/api"
|
||||||
|
HEADERS = {"Authorization": f"Bearer {os.environ['SUPERVISOR_TOKEN']}"}
|
||||||
|
|
||||||
|
UNAVAILABLE = (None, "unknown", "unavailable", "none", "")
|
||||||
|
|
||||||
|
|
||||||
|
def log(msg):
|
||||||
|
print(msg, file=sys.stdout, flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
def get_state(entity_id):
|
||||||
|
try:
|
||||||
|
r = requests.get(f"{HA_API}/states/{entity_id}", headers=HEADERS, timeout=10)
|
||||||
|
if r.status_code == 200:
|
||||||
|
return r.json().get("state")
|
||||||
|
log(f"[warn] {entity_id}: HTTP {r.status_code}")
|
||||||
|
except Exception as exc: # noqa: BLE001
|
||||||
|
log(f"[warn] {entity_id}: {exc}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def build_message():
|
||||||
|
parts = []
|
||||||
|
for s in SENSORS:
|
||||||
|
state = get_state(s["entity_id"])
|
||||||
|
if state not in UNAVAILABLE:
|
||||||
|
label = s.get("label", "")
|
||||||
|
unit = s.get("unit", "")
|
||||||
|
parts.append(f"{label}:{state}{unit}" if label else f"{state}{unit}")
|
||||||
|
body = " ".join(parts)
|
||||||
|
return f"{PREFIX} {body}".strip() if PREFIX else body
|
||||||
|
|
||||||
|
|
||||||
|
def connect():
|
||||||
|
import meshtastic
|
||||||
|
if CONNECTION == "serial":
|
||||||
|
import meshtastic.serial_interface
|
||||||
|
log(f"Connecting to node via serial {SERIAL_PORT}")
|
||||||
|
return meshtastic.serial_interface.SerialInterface(devPath=SERIAL_PORT)
|
||||||
|
import meshtastic.tcp_interface
|
||||||
|
log(f"Connecting to node via TCP {HOST}")
|
||||||
|
return meshtastic.tcp_interface.TCPInterface(hostname=HOST)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
log(f"Broadcaster up: {CONNECTION}, channel {CHANNEL_INDEX}, every {INTERVAL}s")
|
||||||
|
iface = None
|
||||||
|
while True:
|
||||||
|
msg = build_message()
|
||||||
|
if not msg:
|
||||||
|
log("[warn] no sensor values available — skipping this cycle")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
if iface is None:
|
||||||
|
iface = connect()
|
||||||
|
iface.sendText(msg, channelIndex=CHANNEL_INDEX)
|
||||||
|
log(f"sent ({len(msg.encode('utf-8'))} bytes): {msg}")
|
||||||
|
except Exception as exc: # noqa: BLE001
|
||||||
|
log(f"[error] send failed, will reconnect: {exc}")
|
||||||
|
try:
|
||||||
|
if iface:
|
||||||
|
iface.close()
|
||||||
|
except Exception: # noqa: BLE001
|
||||||
|
pass
|
||||||
|
iface = None
|
||||||
|
time.sleep(INTERVAL)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# Debian base so the meshtastic deps (cryptography, protobuf, bleak) install
|
||||||
|
# from prebuilt wheels instead of compiling on Alpine.
|
||||||
|
build_from:
|
||||||
|
aarch64: ghcr.io/home-assistant/aarch64-base-debian:bookworm
|
||||||
|
amd64: ghcr.io/home-assistant/amd64-base-debian:bookworm
|
||||||
|
armv7: ghcr.io/home-assistant/armv7-base-debian:bookworm
|
||||||
|
armhf: ghcr.io/home-assistant/armhf-base-debian:bookworm
|
||||||
|
i386: ghcr.io/home-assistant/i386-base-debian:bookworm
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
name: Meshtastic Sensor Broadcaster
|
||||||
|
version: "1.0.0"
|
||||||
|
slug: meshtastic_broadcaster
|
||||||
|
description: Periodically broadcast Home Assistant sensor values over a Meshtastic mesh
|
||||||
|
url: https://github.com/youruser/ha-meshtastic-addon
|
||||||
|
arch:
|
||||||
|
- aarch64
|
||||||
|
- amd64
|
||||||
|
- armv7
|
||||||
|
- armhf
|
||||||
|
- i386
|
||||||
|
init: false
|
||||||
|
# Lets the add-on call the HA core API via the Supervisor proxy (no token needed)
|
||||||
|
homeassistant_api: true
|
||||||
|
# Expose serial devices (USB node). Harmless if you use TCP.
|
||||||
|
uart: true
|
||||||
|
usb: true
|
||||||
|
options:
|
||||||
|
connection: tcp
|
||||||
|
host: 192.168.1.50
|
||||||
|
serial_port: /dev/ttyUSB0
|
||||||
|
channel_index: 0
|
||||||
|
interval: 900
|
||||||
|
prefix: "🏠"
|
||||||
|
sensors:
|
||||||
|
- label: T
|
||||||
|
entity_id: sensor.outdoor_temperature
|
||||||
|
unit: "°C"
|
||||||
|
- label: H
|
||||||
|
entity_id: sensor.outdoor_humidity
|
||||||
|
unit: "%"
|
||||||
|
- label: P
|
||||||
|
entity_id: sensor.barometric_pressure
|
||||||
|
unit: "hPa"
|
||||||
|
schema:
|
||||||
|
connection: list(tcp|serial)
|
||||||
|
host: str?
|
||||||
|
serial_port: str?
|
||||||
|
channel_index: int(0,7)
|
||||||
|
interval: int(10,)
|
||||||
|
prefix: str?
|
||||||
|
sensors:
|
||||||
|
- label: str
|
||||||
|
entity_id: str
|
||||||
|
unit: str?
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
meshtastic
|
||||||
|
requests
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/with-contenv bashio
|
||||||
|
bashio::log.info "Starting Meshtastic Sensor Broadcaster..."
|
||||||
|
exec python3 /broadcaster.py
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
name: Meshtastic Add-ons
|
||||||
|
url: https://github.com/youruser/ha-meshtastic-addon
|
||||||
|
maintainer: Your Name <you@example.com>
|
||||||
Reference in New Issue
Block a user