20
20
from __future__ import print_function
21
21
from __future__ import with_statement
22
22
23
- import socket
24
23
import json
24
+ import socket
25
+ import time
26
+
25
27
import paho .mqtt .client as mqtt
26
28
29
+
27
30
# The config class represents a config object. The constructor takes
28
31
# an optional pathname, and will switch on the suffix (.yaml for now)
29
32
# and read a dictionary.
@@ -42,6 +45,7 @@ class rtlconfig(object):
42
45
'MQTT_PASSWORD' : None ,
43
46
'MQTT_TLS' : False ,
44
47
'MQTT_PREFIX' : "sensor/rtl_433" ,
48
+ 'MQTT_DEDUP' : True ,
45
49
'MQTT_INDIVIDUAL_TOPICS' : True ,
46
50
'MQTT_JSON_TOPIC' : True ,
47
51
}
@@ -68,9 +72,75 @@ def __init__(self, f=None):
68
72
def __getitem__ (self , k ):
69
73
return self .c [k ]
70
74
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
+
71
137
# Create a config object, defaults modified by the config file if present.
72
138
c = rtlconfig ("rtl_433_mqtt_relay.yaml" )
73
139
140
+ # Create a dedup object for later use, even if it's configure off.
141
+ d = dedup ()
142
+
143
+
74
144
def mqtt_connect (client , userdata , flags , rc ):
75
145
"""Handle MQTT connection callback."""
76
146
print ("MQTT connected: " + mqtt .connack_string (rc ))
@@ -100,6 +170,18 @@ def sanitize(text):
100
170
def publish_sensor_to_mqtt (mqttc , data , line ):
101
171
"""Publish rtl_433 sensor data to MQTT."""
102
172
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
+
103
185
# Construct a topic from the information that identifies which
104
186
# device this frame is from.
105
187
# NB: id is only used if channel is not present.
0 commit comments