Last active
January 22, 2022 10:07
-
-
Save Mictronics/7dc0c703b721b897d1002943e9aea054 to your computer and use it in GitHub Desktop.
Reads statistics from readsb stats.json and forwards to homeassistant (HASS) via MQTT broker.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /usr/bin/python3 | |
# Reads statistics from readsb stats.json and forwards to homeassistant (HASS) | |
# via MQTT broker. | |
# | |
# Dependencies: | |
# pip3 install paho-mqtt | |
# pip3 install watchdog | |
import sys | |
import signal | |
import json | |
import paho.mqtt.client as mqtt | |
from watchdog.observers import Observer | |
from watchdog.events import FileSystemEventHandler | |
# MQTT broker configuration | |
MQTT_HOST = "localhost" | |
MQTT_USER = "user" | |
MQTT_PASSWORD = "pass" | |
MQTT_PORT = "1883" | |
MQTT_TOPIC_PREFIX = "homeassistant/sensor" | |
MQTT_CLIENT_ID = "feeder001" | |
statistics = [ | |
{"id": "messages", "name": "Messages", "unit": "Messages", "value": 0}, | |
{"id": "tracks_new", "name": "Tracking", "unit": "Aircraft", "value": 0}, | |
{"id": "tracks_single", "name": "Single", "unit": "Aircraft", "value": 0}, | |
{"id": "tracks_mlat", "name": "MLAT", "unit": "Aircraft", "value": 0}, | |
{"id": "tracks_position", "name": "Positions", "unit": "Aircraft", "value": 0}, | |
{"id": "max_dist_metric", "name": "Maximum Distance Metric", "unit": "km", "value": 0}, | |
{"id": "max_dist_imp", "name": "Maximum Distance Imperial", "unit": "nm", "value": 0}, | |
{"id": "local_strong", "name": "Strong Signals", "unit": "Messages", "value": 0}, | |
{"id": "local_signal", "name": "Signal", "unit": "dBFS", "value": 0}, | |
{"id": "local_noise", "name": "Noise", "unit": "dBFS", "value": 0}, | |
{"id": "local_peak", "name": "Peak", "unit": "dBFS", "value": 0}, | |
{"id": "temperatur", "name": "Temperature", "unit": "°C", "value": 0} | |
] | |
if __name__ == '__main__': | |
def on_mqtt_message(mqttc, obj, msg): | |
print(msg.topic + " " + str(msg.qos) + " " + str(msg.payload)) | |
def on_mqtt_disconnect(client, userdata, rc): | |
if rc != 0: | |
print("unexpected disconnection") | |
global app_exit | |
app_exit = True | |
def on_mqtt_publish(client, userdata, mid): | |
pass | |
def signal_handler(signal, frame): | |
global app_exit | |
app_exit = True | |
def update_from_stats(evt): | |
global new_stats, last_timestamp, feeder_status | |
if str(evt.dest_path).split("/").pop() == "stats.json": | |
try: | |
f = open(evt.dest_path, "rb") | |
stats = json.load(f) | |
f.close() | |
except Exception as e: | |
print("read json stats error: {}".format(e)) | |
return | |
try: | |
statistics[0]["value"] = int(stats["last1min"]["messages"]) | |
statistics[1]["value"] = int(stats["aircaft_with_pos"]) + int(stats["aircaft_without_pos"]) | |
statistics[2]["value"] = int(stats["last1min"]["tracks"]["single_message"]) | |
statistics[3]["value"] = int(stats["aircraft_count_by_type"]["mlat"]) | |
statistics[4]["value"] = int(stats["aircaft_with_pos"]) | |
statistics[5]["value"] = int(stats["last1min"]["max_distance"]) / 1000 | |
statistics[6]["value"] = int(stats["last1min"]["max_distance"]) * 0.0005399565 | |
except: | |
statistics[0]["value"] = -1 | |
statistics[1]["value"] = -1 | |
statistics[2]["value"] = -1 | |
statistics[3]["value"] = -1 | |
statistics[4]["value"] = -1 | |
statistics[5]["value"] = -1 | |
statistics[6]["value"] = -1 | |
# Separate block, local only available when SDR connected, missing in --net-only | |
try: | |
statistics[7]["value"] = int(stats["last1min"]["local"]["strong_signals"]) | |
statistics[8]["value"] = int(stats["last1min"]["local"]["signal"]) | |
statistics[9]["value"] = int(stats["last1min"]["local"]["noise"]) | |
statistics[10]["value"] = int(stats["last1min"]["local"]["peak_signal"]) | |
except: | |
statistics[7]["value"] = 0 | |
statistics[8]["value"] = 0 | |
statistics[9]["value"] = 0 | |
statistics[10]["value"] = 0 | |
if int(stats["now"]) - last_timestamp > 90: | |
feeder_status = 0 | |
else: | |
feeder_status = 1 | |
last_timestamp = int(stats.last_1min.stop) | |
new_stats = True | |
try: | |
f = open("/sys/class/hwmon/hwmon0/temp1_input", "r") | |
temp = int(f.readline()) | |
f.close() | |
statistics[11]["value"] = temp / 1000 | |
except Exception as e: | |
print("update error: {}".format(e)) | |
# Entry point | |
app_exit = False | |
new_stats = False | |
feeder_status = 0 | |
last_timestamp = 0 | |
signal.signal(signal.SIGABRT, signal_handler) | |
signal.signal(signal.SIGTERM, signal_handler) | |
signal.signal(signal.SIGINT, signal_handler) | |
try: | |
obs = Observer() | |
evt = FileSystemEventHandler() | |
obs.schedule(evt, "/run/readsb", recursive=False) | |
evt.on_moved = update_from_stats | |
obs.start() | |
except Exception as e: | |
print("observer error: {}".format(e)) | |
sys.exit(1) | |
try: | |
mqtt_client = mqtt.Client() | |
mqtt_client.on_message = on_mqtt_message | |
mqtt_client.on_disconnect = on_mqtt_disconnect | |
mqtt_client.on_publish = on_mqtt_publish | |
mqtt_client.username_pw_set(MQTT_USER, MQTT_PASSWORD) | |
# Set last will: client not running | |
topic = "{}/{}/properties".format(MQTT_TOPIC_PREFIX, MQTT_CLIENT_ID) | |
payload = {"running": 0} | |
mqtt_client.will_set(topic, json.dumps(payload), 1) | |
mqtt_client.connect(MQTT_HOST, int(MQTT_PORT)) | |
except Exception as e: | |
print("mqtt client error: {}".format(e)) | |
obs.stop() | |
obs.join() | |
sys.exit(1) | |
mqtt_client.loop_start() | |
while not app_exit: | |
if new_stats: | |
new_stats = False | |
for entry in statistics: | |
# Create topic configuration | |
topic = "{}/{}/{}/config".format(MQTT_TOPIC_PREFIX, MQTT_CLIENT_ID, entry["id"]) | |
payload = {} | |
payload["name"] = "{} {}".format(MQTT_CLIENT_ID, entry["name"]) | |
payload["unique_id"] = "{}.{}".format(MQTT_CLIENT_ID, entry["id"]) | |
payload["state_topic"] = "{}/{}/properties".format(MQTT_TOPIC_PREFIX, MQTT_CLIENT_ID) | |
payload["val_tpl"] = "{{value_json.{}}}".format(entry["id"]), | |
payload["icon"] = "mdi:airplane", | |
payload["platform"] = "mqtt", | |
payload["unit_of_measurement"] = entry["unit"] | |
mqtt_client.publish(topic, json.dumps(payload), qos=1).wait_for_publish(1) | |
print(topic) | |
print(json.dumps(payload)) | |
# Create status configuration | |
topic = "homeassistant/binary_sensor/{}/running/config".format(MQTT_CLIENT_ID) | |
payload = {} | |
payload["name"] = "{} Status".format(MQTT_CLIENT_ID) | |
payload["unique_id"] = "{}.running".format(MQTT_CLIENT_ID) | |
payload["device_class"] = "running" | |
payload["state_topic"] = "{}/{}/properties".format(MQTT_TOPIC_PREFIX, MQTT_CLIENT_ID) | |
payload["val_tpl"] = "{{value_json.running}}" | |
payload["payload_on"] = "1" | |
payload["payload_off"] = "0" | |
payload["platform"] = "mqtt" | |
mqtt_client.publish(topic, json.dumps(payload), qos=1).wait_for_publish(1) | |
print(topic) | |
print(json.dumps(payload)) | |
# Create properties | |
topic = "{}/{}/properties".format(MQTT_TOPIC_PREFIX, MQTT_CLIENT_ID) | |
payload = {} | |
# for statistic entries | |
for entry in statistics: | |
payload[entry["id"]] = entry["value"] | |
# and add feeder status | |
payload["running"] = feeder_status | |
mqtt_client.publish(topic, json.dumps(payload), qos=1).wait_for_publish(1) | |
print(topic) | |
print(json.dumps(payload)) | |
# Clean up and exit | |
obs.stop() | |
obs.join() | |
mqtt_client.disconnect() | |
mqtt_client.loop_stop() | |
sys.exit(0) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment