rtl_433/examples/rtl_433_mqtt_hass.py
2020-05-14 15:40:05 +02:00

381 lines
10 KiB
Python
Executable file

#!/usr/bin/env python
# coding=utf-8
"""MQTT Home Assistant auto discovery for rtl_433 events."""
# It is strongly recommended to run rtl_433 with "-C si" and "-M newmodel".
# Needs Paho-MQTT https://pypi.python.org/pypi/paho-mqtt
# Option: PEP 3143 - Standard daemon process library
# (use Python 3.x or pip install python-daemon)
# import daemon
from __future__ import print_function
from __future__ import with_statement
import time
import json
import paho.mqtt.client as mqtt
MQTT_HOST = "127.0.0.1"
MQTT_PORT = 1883
MQTT_TOPIC = "rtl_433/+/events"
DISCOVERY_PREFIX = "homeassistant"
DISCOVERY_INTERVAL = 600 # Seconds before refreshing the discovery
discovery_timeouts = {}
mappings = {
"temperature_C": {
"device_type": "sensor",
"object_suffix": "T",
"config": {
"device_class": "temperature",
"name": "Temperature",
"unit_of_measurement": "°C",
"value_template": "{{ value|float }}"
}
},
"temperature_1_C": {
"device_type": "sensor",
"object_suffix": "T1",
"config": {
"device_class": "temperature",
"name": "Temperature 1",
"unit_of_measurement": "°C",
"value_template": "{{ value|float }}"
}
},
"temperature_2_C": {
"device_type": "sensor",
"object_suffix": "T2",
"config": {
"device_class": "temperature",
"name": "Temperature 2",
"unit_of_measurement": "°C",
"value_template": "{{ value|float }}"
}
},
"temperature_F": {
"device_type": "sensor",
"object_suffix": "F",
"config": {
"device_class": "temperature",
"name": "Temperature",
"unit_of_measurement": "°F",
"value_template": "{{ value|float }}"
}
},
"battery_ok": {
"device_type": "sensor",
"object_suffix": "B",
"config": {
"device_class": "battery",
"name": "Battery",
"unit_of_measurement": "%",
"value_template": "{{ float(value|int) * 99 + 1 }}"
}
},
"humidity": {
"device_type": "sensor",
"object_suffix": "H",
"config": {
"device_class": "humidity",
"name": "Humidity",
"unit_of_measurement": "%",
"value_template": "{{ value|float }}"
}
},
"moisture": {
"device_type": "sensor",
"object_suffix": "H",
"config": {
"device_class": "moisture",
"name": "Moisture",
"unit_of_measurement": "%",
"value_template": "{{ value|float }}"
}
},
"pressure_hPa": {
"device_type": "sensor",
"object_suffix": "P",
"config": {
"device_class": "pressure",
"name": "Pressure",
"unit_of_measurement": "hPa",
"value_template": "{{ value|float }}"
}
},
"wind_speed_km_h": {
"device_type": "sensor",
"object_suffix": "WS",
"config": {
"device_class": "weather",
"name": "Wind Speed",
"unit_of_measurement": "km/h",
"value_template": "{{ value|float }}"
}
},
"wind_speed_m_s": {
"device_type": "sensor",
"object_suffix": "WS",
"config": {
"device_class": "weather",
"name": "Wind Speed",
"unit_of_measurement": "km/h",
"value_template": "{{ float(value|float) * 3.6 }}"
}
},
"gust_speed_km_h": {
"device_type": "sensor",
"object_suffix": "GS",
"config": {
"device_class": "weather",
"name": "Gust Speed",
"unit_of_measurement": "km/h",
"value_template": "{{ value|float }}"
}
},
"gust_speed_m_s": {
"device_type": "sensor",
"object_suffix": "GS",
"config": {
"device_class": "weather",
"name": "Gust Speed",
"unit_of_measurement": "km/h",
"value_template": "{{ float(value|float) * 3.6 }}"
}
},
"wind_dir_deg": {
"device_type": "sensor",
"object_suffix": "WD",
"config": {
"device_class": "weather",
"name": "Wind Direction",
"unit_of_measurement": "°",
"value_template": "{{ value|float }}"
}
},
"rain_mm": {
"device_type": "sensor",
"object_suffix": "RT",
"config": {
"device_class": "weather",
"name": "Rain Total",
"unit_of_measurement": "mm",
"value_template": "{{ value|float }}"
}
},
"rain_mm_h": {
"device_type": "sensor",
"object_suffix": "RR",
"config": {
"device_class": "weather",
"name": "Rain Rate",
"unit_of_measurement": "mm/h",
"value_template": "{{ value|float }}"
}
},
"tamper": {
"device_type": "binary_sensor",
"object_suffix": "tamper",
"config": {
"device_class": "safety",
"force_update": "true",
"payload_on": "1",
"payload_off": "0"
}
},
"alarm": {
"device_type": "binary_sensor",
"object_suffix": "alarm",
"config": {
"device_class": "safety",
"force_update": "true",
"payload_on": "1",
"payload_off": "0"
}
},
"rssi": {
"device_type": "sensor",
"object_suffix": "rssi",
"config": {
"device_class": "signal_strength",
"unit_of_measurement": "dB",
"value_template": "{{ value|float|round(2) }}"
}
},
"snr": {
"device_type": "sensor",
"object_suffix": "snr",
"config": {
"device_class": "signal_strength",
"unit_of_measurement": "dB",
"value_template": "{{ value|float|round(2) }}"
}
},
"noise": {
"device_type": "sensor",
"object_suffix": "noise",
"config": {
"device_class": "signal_strength",
"unit_of_measurement": "dB",
"value_template": "{{ valuei|float|round(2) }}"
}
},
"depth_cm": {
"device_type": "sensor",
"object_suffix": "D",
"config": {
"device_class": "depth",
"name": "Depth",
"unit_of_measurement": "cm",
"value_template": "{{ value|float }}"
}
},
"power_W": {
"device_type": "sensor",
"object_suffix": "watts",
"config": {
"device_class": "power",
"name": "Power",
"unit_of_measurement": "W",
"value_template": "{{ value|float }}"
}
},
}
def mqtt_connect(client, userdata, flags, rc):
"""Callback for MQTT connects."""
print("MQTT connected: " + mqtt.connack_string(rc))
if rc != 0:
print("Could not connect. Error: " + str(rc))
else:
client.subscribe(MQTT_TOPIC)
def mqtt_disconnect(client, userdata, rc):
"""Callback for MQTT disconnects."""
print("MQTT disconnected: " + mqtt.connack_string(rc))
def mqtt_message(client, userdata, msg):
"""Callback for MQTT message PUBLISH."""
try:
# Decode JSON payload
data = json.loads(msg.payload.decode())
topicprefix = "/".join(msg.topic.split("/", 2)[:2])
bridge_event_to_hass(client, topicprefix, data)
except json.decoder.JSONDecodeError:
print("JSON decode error: " + msg.payload.decode())
return
def sanitize(text):
"""Sanitize a name for Graphite/MQTT use."""
return (text
.replace(" ", "_")
.replace("/", "_")
.replace(".", "_")
.replace("&", ""))
def publish_config(mqttc, topic, model, instance, mapping):
"""Publish Home Assistant auto discovery data."""
global discovery_timeouts
device_type = mapping["device_type"]
object_suffix = mapping["object_suffix"]
object_id = "-".join([model, instance])
object_name = "-".join([object_id,object_suffix])
path = "/".join([DISCOVERY_PREFIX, device_type, object_id, object_name, "config"])
# check timeout
now = time.time()
if path in discovery_timeouts:
if discovery_timeouts[path] > now:
return
discovery_timeouts[path] = now + DISCOVERY_INTERVAL
config = mapping["config"].copy()
config["name"] = object_name
config["state_topic"] = topic
config["device"] = { "identifiers": instance, "name": object_id, "model": model }
mqttc.publish(path, json.dumps(config))
print(path, " : ", json.dumps(config))
def bridge_event_to_hass(mqttc, topicprefix, data):
"""Translate some rtl_433 sensor data to Home Assistant auto discovery."""
if "model" not in data:
# not a device event
return
model = sanitize(data["model"])
if "channel" in data:
channel = str(data["channel"])
instance = channel
elif "id" in data:
device_id = str(data["id"])
instance = device_id
if not instance:
# no unique device identifier
return
# detect known attributes
for key in data.keys():
if key in mappings:
topic = "/".join([topicprefix,"devices",model,instance,key])
publish_config(mqttc, topic, model, instance, mappings[key])
def rtl_433_bridge():
"""Run a MQTT Home Assistant auto discovery bridge for rtl_433."""
mqttc = mqtt.Client()
mqttc.on_connect = mqtt_connect
mqttc.on_disconnect = mqtt_disconnect
mqttc.on_message = mqtt_message
mqttc.connect_async(MQTT_HOST, MQTT_PORT, 60)
mqttc.loop_start()
while True:
time.sleep(1)
def run():
"""Run main or daemon."""
# with daemon.DaemonContext(files_preserve=[sock]):
# detach_process=True
# uid
# gid
# working_directory
rtl_433_bridge()
if __name__ == "__main__":
run()