Skip to content

Commit

Permalink
Add MQTT output
Browse files Browse the repository at this point in the history
  • Loading branch information
zuckschwerdt committed Apr 3, 2019
1 parent 680e1e5 commit 2ee2913
Show file tree
Hide file tree
Showing 10 changed files with 884 additions and 8 deletions.
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ Usage: = General options =
[-w <filename> | help] Save data stream to output file (a '-' dumps samples to stdout)
[-W <filename> | help] Save data stream to output file, overwrite existing file
= Data output options =
[-F kv | json | csv | syslog | null | help] Produce decoded output in given format.
[-F kv | json | csv | mqtt | syslog | null | help] Produce decoded output in given format.
Append output to file with :<filename> (e.g. -F csv:log.csv), defaults to stdout.
Specify host/port for syslog with e.g. -F syslog:127.0.0.1:1514
[-M time | reltime | notime | hires | utc | protocol | level | stats | bits | help] Add various meta data to each output.
Expand Down Expand Up @@ -206,7 +206,7 @@ Option -d:
[-d "" Open default SoapySDR device
[-d driver=rtlsdr Open e.g. specific SoapySDR device
To set gain for SoapySDR use -g ELEM=val,ELEM=val,... e.g. -g LNA=20,TIA=8,PGA=2 (for LimeSDR).
[-d rtl_tcp[:host[:port]] (default: localhost:1234)
[-d rtl_tcp[:[//]host[:port]] (default: localhost:1234)
Specify host/port to connect to with e.g. -d rtl_tcp:127.0.0.1:1234
Option -g:
Expand Down Expand Up @@ -269,9 +269,17 @@ E.g. -X "n=doorbell,m=OOK_PWM,s=400,l=800,r=7000,g=1000,match={24}0xa9878c,repea
Option -F:
[-F kv|json|csv|syslog|null] Produce decoded output in given format.
[-F kv|json|csv|mqtt|syslog|null] Produce decoded output in given format.
Without this option the default is KV output. Use "-F null" to remove the default.
Append output to file with :<filename> (e.g. -F csv:log.csv), defaults to stdout.
Specify MQTT server with e.g. -F mqtt://localhost:1883
Add MQTT options with e.g. -F "mqtt://host:1883,opt=arg"
MQTT options are: user=foo, pass=bar, retain[=0|1],
usechannel=replaceid|afterid|beforeid|no, <format>[=topic]
Supported MQTT formats: (default is all)
events: posts JSON event data
states: posts JSON state data
devices: posts device and sensor info in nested topics
Specify host/port for syslog with e.g. -F syslog:127.0.0.1:1514
Option -M:
Expand Down
15 changes: 12 additions & 3 deletions conf/rtl_433.example.conf
Original file line number Diff line number Diff line change
Expand Up @@ -159,9 +159,18 @@ signal_grabber none
## Data output options

# as command line option:
# [-F] kv|json|csv|syslog Produce decoded output in given format. Not yet supported by all drivers.
# append output to file with :<filename> (e.g. -F csv:log.csv), defaults to stdout.
# specify host/port for syslog with e.g. -F syslog:127.0.0.1:1514
# [-F kv|json|csv|mqtt|syslog|null] Produce decoded output in given format.
# Without this option the default is KV output. Use "-F null" to remove the default.
# Append output to file with :<filename> (e.g. -F csv:log.csv), defaults to stdout.
# Specify MQTT server with e.g. -F mqtt://localhost:1883
# Add MQTT options with e.g. -F "mqtt://host:1883,opt=arg"
# MQTT options are: user=foo, pass=bar, retain[=0|1],
# usechannel=replaceid|afterid|beforeid|no, <format>[=topic]
# Supported MQTT formats: (default is all)
# events: posts JSON event data
# states: posts JSON state data
# devices: posts device and sensor info in nested topics
# Specify host/port for syslog with e.g. -F syslog:127.0.0.1:1514
# default is "kv", multiple outputs can be used.
output json

Expand Down
318 changes: 318 additions & 0 deletions examples/rtl_433_mqtt_hass.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,318 @@
#!/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 socket
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_json.temperature_C }}"
}
},
"temperature_1_C": {
"device_type": "sensor",
"object_suffix": "T1",
"config": {
"device_class": "temperature",
"name": "Temperature 1",
"unit_of_measurement": "°C",
"value_template": "{{ value_json.temperature_1_C }}"
}
},
"temperature_2_C": {
"device_type": "sensor",
"object_suffix": "T2",
"config": {
"device_class": "temperature",
"name": "Temperature 2",
"unit_of_measurement": "°C",
"value_template": "{{ value_json.temperature_2_C }}"
}
},
"temperature_F": {
"device_type": "sensor",
"object_suffix": "F",
"config": {
"device_class": "temperature",
"name": "Temperature",
"unit_of_measurement": "°F",
"value_template": "{{ value_json.temperature_F }}"
}
},

"battery_ok": {
"device_type": "sensor",
"object_suffix": "B",
"config": {
"device_class": "battery",
"name": "Battery",
"unit_of_measurement": "%",
"value_template": "{{ float(value_json.battery_ok) * 99 + 1 }}"
}
},

"humidity": {
"device_type": "sensor",
"object_suffix": "H",
"config": {
"device_class": "humidity",
"name": "Humidity",
"unit_of_measurement": "%",
"value_template": "{{ value_json.humidity }}"
}
},

"moisture": {
"device_type": "sensor",
"object_suffix": "H",
"config": {
"device_class": "moisture",
"name": "Moisture",
"unit_of_measurement": "%",
"value_template": "{{ value_json.moisture }}"
}
},

"pressure_hPa": {
"device_type": "sensor",
"object_suffix": "P",
"config": {
"device_class": "pressure",
"name": "Pressure",
"unit_of_measurement": "hPa",
"value_template": "{{ value_json.pressure_hPa }}"
}
},

"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_json.wind_speed_km_h }}"
}
},

"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_json.wind_speed_m_s) * 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_json.gust_speed_km_h }}"
}
},

"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_json.gust_speed_m_s) * 3.6 }}"
}
},

"wind_dir_deg": {
"device_type": "sensor",
"object_suffix": "WD",
"config": {
"device_class": "weather",
"name": "Wind Direction",
"unit_of_measurement": "°",
"value_template": "{{ value_json.wind_dir_deg }}"
}
},

"rain_mm": {
"device_type": "sensor",
"object_suffix": "RT",
"config": {
"device_class": "weather",
"name": "Rain Total",
"unit_of_measurement": "mm",
"value_template": "{{ value_json.rain_mm }}"
}
},

"rain_mm_h": {
"device_type": "sensor",
"object_suffix": "RR",
"config": {
"device_class": "weather",
"name": "Rain Rate",
"unit_of_measurement": "mm/h",
"value_template": "{{ value_json.rain_mm_h }}"
}
},

# motion...

# switches...

"depth_cm": {
"device_type": "sensor",
"object_suffix": "D",
"config": {
"device_class": "depth",
"name": "Depth",
"unit_of_measurement": "cm",
"value_template": "{{ value_json.depth_cm }}"
}
},
}


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())
bridge_event_to_hass(client, msg.topic, 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_suffix])

path = "/".join([DISCOVERY_PREFIX, device_type, object_id, "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["state_topic"] = topic

mqttc.publish(path, json.dumps(config))
print(path, " : ", json.dumps(config))


def bridge_event_to_hass(mqttc, topic, 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:
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()
19 changes: 19 additions & 0 deletions include/output_mqtt.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/** @file
MQTT output for rtl_433 events
Copyright (C) 2019 Christian Zuckschwerdt
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
*/

#ifndef INCLUDE_OUTPUT_MQTT_H_
#define INCLUDE_OUTPUT_MQTT_H_

#include "data.h"

struct data_output *data_output_mqtt_create(char const *host, char const *port, char *opts, char const *dev_hint);

#endif /* INCLUDE_OUTPUT_MQTT_H_ */
Loading

0 comments on commit 2ee2913

Please sign in to comment.