Skip to content

Instantly share code, notes, and snippets.

@Mictronics
Last active January 22, 2022 10:06
Embed
What would you like to do?
Reads statistics from readsb stats.json or stats.pb and forwards to homeassistant (HASS) via MQTT broker.
#! /usr/bin/python3
# Reads statistics from readsb stats.json or stats.pb and forwards to homeassistant (HASS)
# via MQTT broker.
#
# Dependencies:
# pip3 install paho-mqtt
# pip3 install watchdog
# https://github.com/protocolbuffers/protobuf
import sys
import signal
import json
import paho.mqtt.client as mqtt
import readsb_pb2
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"
# Set True to read statistics in JSON format
USE_JSON = False
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 USE_JSON == True:
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
else:
if str(evt.dest_path).split("/").pop() == "stats.pb":
try:
stats = readsb_pb2.Statistics()
f = open(evt.dest_path, "rb")
stats.ParseFromString(f.read())
f.close()
except Exception as e:
print("read pb stats error: {}".format(e))
return
statistics[0]["value"] = stats.last_1min.messages
statistics[1]["value"] = stats.last_1min.tracks_new
statistics[2]["value"] = stats.last_1min.tracks_single_message
statistics[3]["value"] = stats.last_1min.tracks_mlat_position
statistics[4]["value"] = stats.last_1min.tracks_with_position
statistics[5]["value"] = stats.last_1min.max_distance_in_metres / 1000
statistics[6]["value"] = stats.last_1min.max_distance_in_nautical_miles
statistics[7]["value"] = stats.last_1min.local_strong_signals
statistics[8]["value"] = stats.last_1min.local_signal
statistics[9]["value"] = stats.last_1min.local_noise
statistics[10]["value"] = stats.last_1min.local_peak_signal
if int(stats.last_1min.stop) - 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