Skip to content

Commit bbe6767

Browse files
committed
examples: Add dup filtering to mqtt_relay
Keep information about the previous value sent. If it's been 8 seconds, or new value is different (ignorning keys like snr and frequency), then send it. Otherwise, just don't. This causes bursts of e.g. 4 transmissions to result in one MQTT message, on the theory that the 4 transmissions are not actually 4 messags, but a strategy to transmit one message more reliably. Define a new configuration option to enable duplicate filtering, and default it to True.
1 parent 3821498 commit bbe6767

File tree

1 file changed

+83
-1
lines changed

1 file changed

+83
-1
lines changed

examples/rtl_433_mqtt_relay.py

+83-1
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,13 @@
2020
from __future__ import print_function
2121
from __future__ import with_statement
2222

23-
import socket
2423
import json
24+
import socket
25+
import time
26+
2527
import paho.mqtt.client as mqtt
2628

29+
2730
# The config class represents a config object. The constructor takes
2831
# an optional pathname, and will switch on the suffix (.yaml for now)
2932
# and read a dictionary.
@@ -42,6 +45,7 @@ class rtlconfig(object):
4245
'MQTT_PASSWORD': None,
4346
'MQTT_TLS': False,
4447
'MQTT_PREFIX': "sensor/rtl_433",
48+
'MQTT_DEDUP': True,
4549
'MQTT_INDIVIDUAL_TOPICS': True,
4650
'MQTT_JSON_TOPIC': True,
4751
}
@@ -68,9 +72,75 @@ def __init__(self, f=None):
6872
def __getitem__(self, k):
6973
return self.c[k]
7074

75+
# A dedup class object supports deduping a stream of reports by
76+
# answering if a report is interesting relative to the history.
77+
# While more complicated deduping is allowed by the interface, for now
78+
# it is very simple, keeping track of only the previous interesting object.
79+
# For now, we more or less require that all reports have the same keys.
80+
# \todo Consider a cache with several entries.
81+
class dedup(object):
82+
83+
def __init__(self):
84+
# Make this long enough to skip repeats, but allow messages
85+
# every 10s to come through.
86+
self.duration = 5
87+
self.boring_keys = ('time', 'freq', 'rssi', 'snr', 'noise')
88+
# Initialize storage for what was last sent.
89+
(self.last_report, self.last_now) = (None, None)
90+
91+
def send_store(self, report, n):
92+
(self.last_report, self.last_now) = (report, n)
93+
return True
94+
95+
# Return True if j1 and j2 are the same, except for boring_keys.
96+
def equiv(self, j1, j2):
97+
for (k, v) in j1.items():
98+
# If in boring, we don't care.
99+
if k not in self.boring_keys:
100+
# If in j1 and not j2, they are different.
101+
if k not in j2:
102+
return False
103+
if j1[k] != j2[k]:
104+
return False
105+
# If the lengths are different, they must be different.
106+
if len(j1) != len(j2):
107+
return False
108+
109+
# If we get here, then the lengths are the same, and all
110+
# non-boring keys in j1 exist in j2, and have the same value.
111+
# It could be that j2 is missing a boring key and also has a
112+
# new non-boring key, but boring keys in particular should not
113+
# be variable.
114+
return True
115+
116+
# report is a python dictionary
117+
def is_interesting(self, report):
118+
n = time.time()
119+
120+
# If previous interesting is empty (or troubled), accept this
121+
# one.
122+
if self.last_report is None or self.last_now is None:
123+
# print("interesting: no previous")
124+
return self.send_store(report, n)
125+
126+
# If previous one was too long ago, accept this one.
127+
if n - self.last_now > self.duration:
128+
# print("interesting: time")
129+
return self.send_store(report, n)
130+
131+
if not self.equiv(self.last_report, report):
132+
# print("interesting: different")
133+
return self.send_store(report, n)
134+
135+
return False
136+
71137
# Create a config object, defaults modified by the config file if present.
72138
c = rtlconfig("rtl_433_mqtt_relay.yaml")
73139

140+
# Create a dedup object for later use, even if it's configure off.
141+
d = dedup()
142+
143+
74144
def mqtt_connect(client, userdata, flags, rc):
75145
"""Handle MQTT connection callback."""
76146
print("MQTT connected: " + mqtt.connack_string(rc))
@@ -100,6 +170,18 @@ def sanitize(text):
100170
def publish_sensor_to_mqtt(mqttc, data, line):
101171
"""Publish rtl_433 sensor data to MQTT."""
102172

173+
debug = False # \todo Hoist to program-wide debug control.
174+
175+
if c['MQTT_DEDUP']:
176+
# If this data is not novel relative to recent data, just skip it.
177+
# Otherwise, send it via MQTT.
178+
if not d.is_interesting(data):
179+
if debug:
180+
print("not interesting: %s" % (line))
181+
return
182+
if debug:
183+
print("INTERESTING: %s" % (line))
184+
103185
# Construct a topic from the information that identifies which
104186
# device this frame is from.
105187
# NB: id is only used if channel is not present.

0 commit comments

Comments
 (0)