added receiver plugin
This commit is contained in:
@@ -6,11 +6,17 @@ A small Home Assistant add-on repository.
|
|||||||
|
|
||||||
- **Meshtastic Sensor Broadcaster** — periodically broadcasts selected Home
|
- **Meshtastic Sensor Broadcaster** — periodically broadcasts selected Home
|
||||||
Assistant sensor values over a Meshtastic mesh (TCP or USB-connected node).
|
Assistant sensor values over a Meshtastic mesh (TCP or USB-connected node).
|
||||||
|
- **Meshtastic Sensor Receiver** — subscribes to a Meshtastic channel over MQTT,
|
||||||
|
decrypts it, and exposes the latest message plus parsed temperature/humidity
|
||||||
|
values as Home Assistant sensors (via MQTT Discovery).
|
||||||
|
|
||||||
|
Together they form a round trip: the broadcaster sends `🏠 T1:.. H1:..` messages
|
||||||
|
from one site, and the receiver turns them back into sensors at another.
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
Add-on Store -> ⋮ (top right) -> **Repositories** -> paste this repo's URL.
|
Add-on Store -> ⋮ (top right) -> **Repositories** -> paste this repo's URL.
|
||||||
Then install **Meshtastic Sensor Broadcaster** and configure it.
|
Then install the add-on you want and configure it.
|
||||||
|
|
||||||
Or, for local development, copy the `meshtastic_broadcaster/` folder into the
|
Or, for local development, copy the `meshtastic_broadcaster/` folder into the
|
||||||
`/addons` share and use **Check for updates** in the Add-on Store.
|
`/addons` share and use **Check for updates** in the Add-on Store.
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## 1.0.0
|
||||||
|
|
||||||
|
- Initial release.
|
||||||
|
- Subscribes to a Meshtastic channel over MQTT (public server or local broker).
|
||||||
|
- Decrypts channel packets (AES-CTR) using the channel PSK.
|
||||||
|
- Exposes the latest text message as a sensor (with from/to/time attributes).
|
||||||
|
- Parses `LABEL:VALUE` messages into individual temperature/humidity sensors.
|
||||||
|
- Auto-creates entities via MQTT Discovery; resolves the HA broker through the
|
||||||
|
Supervisor (no manual credentials required).
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
# Meshtastic Sensor Receiver
|
||||||
|
|
||||||
|
Subscribes to a Meshtastic channel over MQTT, decrypts the packets, and exposes
|
||||||
|
the results in Home Assistant via MQTT Discovery:
|
||||||
|
|
||||||
|
- **Last Message** — the most recent text message on the channel (with `from`,
|
||||||
|
`to`, and timestamp as attributes).
|
||||||
|
- **Parsed sensors** — values pulled out of structured messages such as
|
||||||
|
`🏠 T1:20.8°C T2:26.7°C H1:83.7%` become individual numeric sensors.
|
||||||
|
|
||||||
|
This is the receiving counterpart to the **Meshtastic Sensor Broadcaster** add-on.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
An MQTT broker configured in Home Assistant (e.g. the **Mosquitto broker**
|
||||||
|
add-on). The add-on discovers it automatically via the Supervisor — no manual
|
||||||
|
credentials needed for the HA side.
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
| Option | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `source_broker` | MQTT broker the mesh packets arrive on. `mqtt.meshtastic.org` for the public server, or your local broker. |
|
||||||
|
| `source_port` | Broker port. `1883` plain, `8883` TLS. |
|
||||||
|
| `source_username` / `source_password` | Source broker credentials. Public server uses `meshdev` / `large4cats`. |
|
||||||
|
| `source_tls` | `true` when using port 8883. |
|
||||||
|
| `region` | Region code in the topic, e.g. `EU_868`, `US`. Use `+` to match any region. |
|
||||||
|
| `channel_name` | Exact (case-sensitive) channel name, e.g. `DET.Baulog`. |
|
||||||
|
| `channel_psk` | Base64 channel key from the Meshtastic app. `AQ==` is the default public key. |
|
||||||
|
| `match_prefix` | Only messages containing this string are parsed into sensors. Leave empty to parse every message. The Last Message sensor always updates. |
|
||||||
|
| `device_name` | Name of the HA device that groups the created entities. |
|
||||||
|
| `discovery_prefix` | MQTT Discovery prefix (default `homeassistant`). |
|
||||||
|
| `sensors` | List of `{label, name, device_class, unit}`. Each `label` (e.g. `T1`) is matched as `label:value` in the message. |
|
||||||
|
|
||||||
|
## How parsing works
|
||||||
|
|
||||||
|
The add-on scans each message for `LABEL:VALUE` pairs (units after the value are
|
||||||
|
ignored). For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
🏠 T1:20.8°C T2:26.7°C T3:26.6°C H1:83.7% H2:54.4% H3:55.5%
|
||||||
|
```
|
||||||
|
|
||||||
|
produces `T1=20.8`, `T2=26.7`, `T3=26.6`, `H1=83.7`, `H2=54.4`, `H3=55.5`,
|
||||||
|
each published to the sensor configured with that `label`.
|
||||||
|
|
||||||
|
## Topics used
|
||||||
|
|
||||||
|
- Discovery: `{discovery_prefix}/sensor/{device}/{label}/config`
|
||||||
|
- State: `meshtastic_receiver/{device}/{label}`
|
||||||
|
- Last message: `meshtastic_receiver/{device}/last_message` (+ `/attributes`)
|
||||||
|
- Availability: `meshtastic_receiver/{device}/status`
|
||||||
|
|
||||||
|
## Privacy note
|
||||||
|
|
||||||
|
On the public server your packets stay AES-encrypted in transit; only holders of
|
||||||
|
the channel PSK can read them. For full privacy and no traffic restrictions, run
|
||||||
|
a local MQTT broker and point both your node and `source_broker` at it.
|
||||||
@@ -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 receiver.py /
|
||||||
|
RUN chmod a+x /run.sh
|
||||||
|
|
||||||
|
CMD [ "/run.sh" ]
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# Debian base so the meshtastic deps (cryptography, protobuf) 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,74 @@
|
|||||||
|
name: Meshtastic Sensor Receiver
|
||||||
|
version: "1.0.0"
|
||||||
|
slug: meshtastic_receiver
|
||||||
|
description: Read Meshtastic mesh text messages over MQTT and expose the latest message plus parsed temperature/humidity values as Home Assistant sensors
|
||||||
|
url: https://git.revwal.de/oliver/ha-meshtastic-addon
|
||||||
|
arch:
|
||||||
|
- aarch64
|
||||||
|
- amd64
|
||||||
|
- armv7
|
||||||
|
- armhf
|
||||||
|
- i386
|
||||||
|
init: false
|
||||||
|
# Asks the Supervisor for the configured MQTT broker (e.g. the Mosquitto add-on)
|
||||||
|
# so sensors can be published via MQTT Discovery without manual credentials.
|
||||||
|
services:
|
||||||
|
- mqtt:want
|
||||||
|
options:
|
||||||
|
# --- Source: where the mesh packets are read from -------------------------
|
||||||
|
source_broker: mqtt.meshtastic.org
|
||||||
|
source_port: 1883
|
||||||
|
source_username: meshdev
|
||||||
|
source_password: large4cats
|
||||||
|
source_tls: false
|
||||||
|
region: EU_868
|
||||||
|
channel_name: DET.Baulog
|
||||||
|
channel_psk: "AQ=="
|
||||||
|
# --- Parsing / presentation ----------------------------------------------
|
||||||
|
# Only messages containing this string are parsed into sensors. Leave empty
|
||||||
|
# to parse every text message. The "last message" sensor always updates.
|
||||||
|
match_prefix: "🏠"
|
||||||
|
device_name: Meshtastic Mesh
|
||||||
|
discovery_prefix: homeassistant
|
||||||
|
sensors:
|
||||||
|
- label: T1
|
||||||
|
name: Temperature 1
|
||||||
|
device_class: temperature
|
||||||
|
unit: "°C"
|
||||||
|
- label: T2
|
||||||
|
name: Temperature 2
|
||||||
|
device_class: temperature
|
||||||
|
unit: "°C"
|
||||||
|
- label: T3
|
||||||
|
name: Temperature 3
|
||||||
|
device_class: temperature
|
||||||
|
unit: "°C"
|
||||||
|
- label: H1
|
||||||
|
name: Humidity 1
|
||||||
|
device_class: humidity
|
||||||
|
unit: "%"
|
||||||
|
- label: H2
|
||||||
|
name: Humidity 2
|
||||||
|
device_class: humidity
|
||||||
|
unit: "%"
|
||||||
|
- label: H3
|
||||||
|
name: Humidity 3
|
||||||
|
device_class: humidity
|
||||||
|
unit: "%"
|
||||||
|
schema:
|
||||||
|
source_broker: str
|
||||||
|
source_port: int(1,65535)
|
||||||
|
source_username: str?
|
||||||
|
source_password: str?
|
||||||
|
source_tls: bool
|
||||||
|
region: str
|
||||||
|
channel_name: str
|
||||||
|
channel_psk: str
|
||||||
|
match_prefix: str?
|
||||||
|
device_name: str
|
||||||
|
discovery_prefix: str
|
||||||
|
sensors:
|
||||||
|
- label: str
|
||||||
|
name: str
|
||||||
|
device_class: str?
|
||||||
|
unit: str?
|
||||||
@@ -0,0 +1,289 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Receive Meshtastic mesh text messages over MQTT and expose them in Home Assistant.
|
||||||
|
|
||||||
|
Flow:
|
||||||
|
1. Subscribe to a Meshtastic gateway's MQTT topic (public server or local broker).
|
||||||
|
2. Decrypt channel packets (AES-CTR) using the channel PSK.
|
||||||
|
3. Publish the latest text message and parsed Temperature/Humidity values to
|
||||||
|
Home Assistant via MQTT Discovery (so entities appear automatically).
|
||||||
|
"""
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
import paho.mqtt.client as mqtt
|
||||||
|
import requests
|
||||||
|
from cryptography.hazmat.backends import default_backend
|
||||||
|
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||||
|
from meshtastic.protobuf import mesh_pb2, mqtt_pb2, portnums_pb2
|
||||||
|
|
||||||
|
|
||||||
|
def log(msg):
|
||||||
|
print(msg, file=sys.stdout, flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
# --- Add-on options (written by the Supervisor) ------------------------------
|
||||||
|
with open("/data/options.json", encoding="utf-8") as fh:
|
||||||
|
OPTS = json.load(fh)
|
||||||
|
|
||||||
|
SRC_BROKER = OPTS["source_broker"]
|
||||||
|
SRC_PORT = int(OPTS.get("source_port", 1883))
|
||||||
|
SRC_USER = OPTS.get("source_username", "") or ""
|
||||||
|
SRC_PASS = OPTS.get("source_password", "") or ""
|
||||||
|
SRC_TLS = bool(OPTS.get("source_tls", False))
|
||||||
|
REGION = OPTS.get("region", "+") or "+"
|
||||||
|
CHANNEL_NAME = OPTS["channel_name"]
|
||||||
|
CHANNEL_PSK = OPTS.get("channel_psk", "AQ==")
|
||||||
|
MATCH_PREFIX = OPTS.get("match_prefix", "") or ""
|
||||||
|
DEVICE_NAME = OPTS.get("device_name", "Meshtastic Mesh")
|
||||||
|
DISCOVERY_PREFIX = OPTS.get("discovery_prefix", "homeassistant")
|
||||||
|
SENSORS = OPTS.get("sensors", [])
|
||||||
|
|
||||||
|
DEVICE_UID = re.sub(r"[^a-z0-9_]", "_", DEVICE_NAME.lower()) or "meshtastic_mesh"
|
||||||
|
BASE_TOPIC = f"meshtastic_receiver/{DEVICE_UID}"
|
||||||
|
AVAIL_TOPIC = f"{BASE_TOPIC}/status"
|
||||||
|
|
||||||
|
# --- PSK handling ------------------------------------------------------------
|
||||||
|
# "AQ==" (byte 0x01) is Meshtastic's shorthand for this well-known default key.
|
||||||
|
_DEFAULT_KEY = base64.b64decode("1PG7OiApB1nwvP+rz05pAQ==")
|
||||||
|
|
||||||
|
|
||||||
|
def expand_psk(psk_b64):
|
||||||
|
raw = base64.b64decode(psk_b64)
|
||||||
|
return _DEFAULT_KEY if raw == b"\x01" else raw
|
||||||
|
|
||||||
|
|
||||||
|
CHANNEL_KEY = expand_psk(CHANNEL_PSK)
|
||||||
|
|
||||||
|
# Matches "T1:20.8", "H2:54.4", "P:1013" etc. (unit suffix is ignored here).
|
||||||
|
PAIR_RE = re.compile(r"([A-Za-z][A-Za-z0-9_]*)\s*:\s*(-?\d+(?:\.\d+)?)")
|
||||||
|
|
||||||
|
# Set once the HA broker client is connected.
|
||||||
|
ha_client = None
|
||||||
|
|
||||||
|
|
||||||
|
# --- Decryption --------------------------------------------------------------
|
||||||
|
def decrypt_packet(encrypted, key, from_id, packet_id):
|
||||||
|
"""AES-CTR decrypt. Nonce = packetId(uint64 LE) | fromNode(uint32 LE) | 0*4."""
|
||||||
|
try:
|
||||||
|
nonce = (
|
||||||
|
packet_id.to_bytes(4, "little")
|
||||||
|
+ b"\x00" * 4
|
||||||
|
+ from_id.to_bytes(4, "little")
|
||||||
|
+ b"\x00" * 4
|
||||||
|
)
|
||||||
|
cipher = Cipher(
|
||||||
|
algorithms.AES(key), modes.CTR(nonce), backend=default_backend()
|
||||||
|
)
|
||||||
|
d = cipher.decryptor()
|
||||||
|
return d.update(encrypted) + d.finalize()
|
||||||
|
except Exception as exc: # noqa: BLE001
|
||||||
|
log(f"[warn] decryption error: {exc}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# --- Home Assistant MQTT broker resolution (via Supervisor service) ----------
|
||||||
|
def resolve_ha_mqtt():
|
||||||
|
token = os.environ.get("SUPERVISOR_TOKEN")
|
||||||
|
if not token:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
r = requests.get(
|
||||||
|
"http://supervisor/services/mqtt",
|
||||||
|
headers={"Authorization": f"Bearer {token}"},
|
||||||
|
timeout=10,
|
||||||
|
)
|
||||||
|
if r.status_code == 200:
|
||||||
|
d = r.json()["data"]
|
||||||
|
return {
|
||||||
|
"host": d["host"],
|
||||||
|
"port": int(d["port"]),
|
||||||
|
"username": d.get("username") or None,
|
||||||
|
"password": d.get("password") or None,
|
||||||
|
"ssl": bool(d.get("ssl", False)),
|
||||||
|
}
|
||||||
|
log(f"[warn] supervisor mqtt service returned HTTP {r.status_code}")
|
||||||
|
except Exception as exc: # noqa: BLE001
|
||||||
|
log(f"[warn] could not resolve HA MQTT via supervisor: {exc}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# --- MQTT Discovery ----------------------------------------------------------
|
||||||
|
def device_block():
|
||||||
|
return {
|
||||||
|
"identifiers": [DEVICE_UID],
|
||||||
|
"name": DEVICE_NAME,
|
||||||
|
"manufacturer": "Meshtastic",
|
||||||
|
"model": "MQTT Mesh Receiver",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def publish_discovery():
|
||||||
|
# Latest text message (with from/to/time as attributes).
|
||||||
|
cfg = {
|
||||||
|
"name": "Last Message",
|
||||||
|
"unique_id": f"{DEVICE_UID}_last_message",
|
||||||
|
"state_topic": f"{BASE_TOPIC}/last_message",
|
||||||
|
"json_attributes_topic": f"{BASE_TOPIC}/last_message/attributes",
|
||||||
|
"icon": "mdi:message-text",
|
||||||
|
"availability_topic": AVAIL_TOPIC,
|
||||||
|
"device": device_block(),
|
||||||
|
}
|
||||||
|
ha_client.publish(
|
||||||
|
f"{DISCOVERY_PREFIX}/sensor/{DEVICE_UID}/last_message/config",
|
||||||
|
json.dumps(cfg),
|
||||||
|
retain=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
for s in SENSORS:
|
||||||
|
label = s["label"]
|
||||||
|
cfg = {
|
||||||
|
"name": s.get("name", label),
|
||||||
|
"unique_id": f"{DEVICE_UID}_{label}",
|
||||||
|
"state_topic": f"{BASE_TOPIC}/{label}",
|
||||||
|
"availability_topic": AVAIL_TOPIC,
|
||||||
|
"device": device_block(),
|
||||||
|
}
|
||||||
|
if s.get("unit"):
|
||||||
|
cfg["unit_of_measurement"] = s["unit"]
|
||||||
|
if s.get("device_class"):
|
||||||
|
cfg["device_class"] = s["device_class"]
|
||||||
|
cfg["state_class"] = "measurement"
|
||||||
|
ha_client.publish(
|
||||||
|
f"{DISCOVERY_PREFIX}/sensor/{DEVICE_UID}/{label}/config",
|
||||||
|
json.dumps(cfg),
|
||||||
|
retain=True,
|
||||||
|
)
|
||||||
|
log(f"[ha] published discovery for last_message + {len(SENSORS)} sensors")
|
||||||
|
|
||||||
|
|
||||||
|
# --- Message handling --------------------------------------------------------
|
||||||
|
def handle_text(text, from_id, to_id):
|
||||||
|
# HA sensor states are limited to 255 chars.
|
||||||
|
ha_client.publish(f"{BASE_TOPIC}/last_message", text[:255], retain=True)
|
||||||
|
ha_client.publish(
|
||||||
|
f"{BASE_TOPIC}/last_message/attributes",
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"from": f"!{from_id:08x}",
|
||||||
|
"to": f"!{to_id:08x}",
|
||||||
|
"received": time.strftime("%Y-%m-%dT%H:%M:%S%z"),
|
||||||
|
"full_text": text,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
retain=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
if MATCH_PREFIX and MATCH_PREFIX not in text:
|
||||||
|
return
|
||||||
|
|
||||||
|
known = {s["label"] for s in SENSORS}
|
||||||
|
published = []
|
||||||
|
for label, value in PAIR_RE.findall(text):
|
||||||
|
if label in known:
|
||||||
|
ha_client.publish(f"{BASE_TOPIC}/{label}", value, retain=True)
|
||||||
|
published.append(f"{label}={value}")
|
||||||
|
if published:
|
||||||
|
log(f"[parse] {' '.join(published)}")
|
||||||
|
|
||||||
|
|
||||||
|
# --- Source MQTT callbacks ---------------------------------------------------
|
||||||
|
def on_src_connect(client, userdata, flags, reason_code, properties=None):
|
||||||
|
if reason_code == 0:
|
||||||
|
topic = f"msh/{REGION}/2/e/{CHANNEL_NAME}/#"
|
||||||
|
client.subscribe(topic)
|
||||||
|
log(f"[source] connected; subscribed to {topic}")
|
||||||
|
else:
|
||||||
|
log(f"[source] connection refused: reason_code={reason_code}")
|
||||||
|
|
||||||
|
|
||||||
|
def on_src_message(client, userdata, msg):
|
||||||
|
try:
|
||||||
|
env = mqtt_pb2.ServiceEnvelope()
|
||||||
|
env.ParseFromString(msg.payload)
|
||||||
|
except Exception as exc: # noqa: BLE001
|
||||||
|
log(f"[warn] could not parse ServiceEnvelope: {exc}")
|
||||||
|
return
|
||||||
|
|
||||||
|
mp = env.packet
|
||||||
|
from_id = getattr(mp, "from")
|
||||||
|
|
||||||
|
if mp.HasField("decoded"):
|
||||||
|
data = mp.decoded
|
||||||
|
elif mp.HasField("encrypted"):
|
||||||
|
plain = decrypt_packet(mp.encrypted, CHANNEL_KEY, from_id, mp.id)
|
||||||
|
if plain is None:
|
||||||
|
return
|
||||||
|
data = mesh_pb2.Data()
|
||||||
|
try:
|
||||||
|
data.ParseFromString(plain)
|
||||||
|
except Exception: # noqa: BLE001
|
||||||
|
return # wrong PSK or not for this channel
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
|
||||||
|
if data.portnum == portnums_pb2.PortNum.TEXT_MESSAGE_APP:
|
||||||
|
text = data.payload.decode("utf-8", errors="replace")
|
||||||
|
log(f"[rx] !{from_id:08x}: {text}")
|
||||||
|
handle_text(text, from_id, mp.to)
|
||||||
|
|
||||||
|
|
||||||
|
# --- Entry point -------------------------------------------------------------
|
||||||
|
def main():
|
||||||
|
global ha_client
|
||||||
|
|
||||||
|
ha = resolve_ha_mqtt()
|
||||||
|
if ha is None:
|
||||||
|
log(
|
||||||
|
"[fatal] No MQTT broker available. Install and start the Mosquitto "
|
||||||
|
"broker add-on, then restart this add-on."
|
||||||
|
)
|
||||||
|
sys.exit(1)
|
||||||
|
log(f"[ha] using MQTT broker {ha['host']}:{ha['port']}")
|
||||||
|
|
||||||
|
ha_client = mqtt.Client(
|
||||||
|
mqtt.CallbackAPIVersion.VERSION2, client_id="meshtastic_receiver"
|
||||||
|
)
|
||||||
|
if ha["username"]:
|
||||||
|
ha_client.username_pw_set(ha["username"], ha["password"])
|
||||||
|
if ha["ssl"]:
|
||||||
|
ha_client.tls_set()
|
||||||
|
ha_client.will_set(AVAIL_TOPIC, "offline", retain=True)
|
||||||
|
|
||||||
|
def on_ha_connect(client, userdata, flags, reason_code, properties=None):
|
||||||
|
if reason_code == 0:
|
||||||
|
log("[ha] connected")
|
||||||
|
client.publish(AVAIL_TOPIC, "online", retain=True)
|
||||||
|
publish_discovery()
|
||||||
|
else:
|
||||||
|
log(f"[ha] connection refused: reason_code={reason_code}")
|
||||||
|
|
||||||
|
ha_client.on_connect = on_ha_connect
|
||||||
|
ha_client.connect(ha["host"], ha["port"], keepalive=60)
|
||||||
|
ha_client.loop_start()
|
||||||
|
|
||||||
|
src = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
|
||||||
|
if SRC_USER:
|
||||||
|
src.username_pw_set(SRC_USER, SRC_PASS)
|
||||||
|
if SRC_TLS:
|
||||||
|
src.tls_set()
|
||||||
|
src.on_connect = on_src_connect
|
||||||
|
src.on_message = on_src_message
|
||||||
|
log(f"[source] connecting to {SRC_BROKER}:{SRC_PORT} …")
|
||||||
|
src.connect(SRC_BROKER, SRC_PORT, keepalive=60)
|
||||||
|
|
||||||
|
try:
|
||||||
|
src.loop_forever()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
ha_client.publish(AVAIL_TOPIC, "offline", retain=True)
|
||||||
|
ha_client.loop_stop()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
meshtastic
|
||||||
|
paho-mqtt
|
||||||
|
cryptography
|
||||||
|
requests
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/with-contenv bashio
|
||||||
|
bashio::log.info "Starting Meshtastic Sensor Receiver..."
|
||||||
|
exec python3 /receiver.py
|
||||||
Reference in New Issue
Block a user