From ac8dcff3d5341193bb1b7b83ec2c2de665aa305b Mon Sep 17 00:00:00 2001 From: Oliver Walter Date: Sat, 27 Jun 2026 00:38:05 +0200 Subject: [PATCH] first commit --- README.md | 16 +++++ meshtastic_broadcaster/CHANGELOG.md | 9 +++ meshtastic_broadcaster/DOCS.md | 29 ++++++++ meshtastic_broadcaster/Dockerfile | 21 ++++++ meshtastic_broadcaster/broadcaster.py | 92 +++++++++++++++++++++++++ meshtastic_broadcaster/build.yaml | 8 +++ meshtastic_broadcaster/config.yaml | 45 ++++++++++++ meshtastic_broadcaster/requirements.txt | 2 + meshtastic_broadcaster/run.sh | 3 + repository.yaml | 3 + 10 files changed, 228 insertions(+) create mode 100644 README.md create mode 100644 meshtastic_broadcaster/CHANGELOG.md create mode 100644 meshtastic_broadcaster/DOCS.md create mode 100644 meshtastic_broadcaster/Dockerfile create mode 100644 meshtastic_broadcaster/broadcaster.py create mode 100644 meshtastic_broadcaster/build.yaml create mode 100644 meshtastic_broadcaster/config.yaml create mode 100644 meshtastic_broadcaster/requirements.txt create mode 100644 meshtastic_broadcaster/run.sh create mode 100644 repository.yaml diff --git a/README.md b/README.md new file mode 100644 index 0000000..2848c62 --- /dev/null +++ b/README.md @@ -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. diff --git a/meshtastic_broadcaster/CHANGELOG.md b/meshtastic_broadcaster/CHANGELOG.md new file mode 100644 index 0000000..e8a8d9e --- /dev/null +++ b/meshtastic_broadcaster/CHANGELOG.md @@ -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. diff --git a/meshtastic_broadcaster/DOCS.md b/meshtastic_broadcaster/DOCS.md new file mode 100644 index 0000000..a5f9a0b --- /dev/null +++ b/meshtastic_broadcaster/DOCS.md @@ -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. diff --git a/meshtastic_broadcaster/Dockerfile b/meshtastic_broadcaster/Dockerfile new file mode 100644 index 0000000..8bbaf69 --- /dev/null +++ b/meshtastic_broadcaster/Dockerfile @@ -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" ] diff --git a/meshtastic_broadcaster/broadcaster.py b/meshtastic_broadcaster/broadcaster.py new file mode 100644 index 0000000..65d090d --- /dev/null +++ b/meshtastic_broadcaster/broadcaster.py @@ -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() diff --git a/meshtastic_broadcaster/build.yaml b/meshtastic_broadcaster/build.yaml new file mode 100644 index 0000000..2fd2edc --- /dev/null +++ b/meshtastic_broadcaster/build.yaml @@ -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 diff --git a/meshtastic_broadcaster/config.yaml b/meshtastic_broadcaster/config.yaml new file mode 100644 index 0000000..c550a2a --- /dev/null +++ b/meshtastic_broadcaster/config.yaml @@ -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? diff --git a/meshtastic_broadcaster/requirements.txt b/meshtastic_broadcaster/requirements.txt new file mode 100644 index 0000000..37ff9a6 --- /dev/null +++ b/meshtastic_broadcaster/requirements.txt @@ -0,0 +1,2 @@ +meshtastic +requests diff --git a/meshtastic_broadcaster/run.sh b/meshtastic_broadcaster/run.sh new file mode 100644 index 0000000..5f9a251 --- /dev/null +++ b/meshtastic_broadcaster/run.sh @@ -0,0 +1,3 @@ +#!/usr/bin/with-contenv bashio +bashio::log.info "Starting Meshtastic Sensor Broadcaster..." +exec python3 /broadcaster.py diff --git a/repository.yaml b/repository.yaml new file mode 100644 index 0000000..fe514e9 --- /dev/null +++ b/repository.yaml @@ -0,0 +1,3 @@ +name: Meshtastic Add-ons +url: https://github.com/youruser/ha-meshtastic-addon +maintainer: Your Name