44import asyncio
55import json
66import logging
7+
8+ # import numpy as np
9+ import math
710import time
811from typing import Any
9- #import numpy as np
10- import math
1112
1213import voluptuous as vol
1314
@@ -61,9 +62,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
6162 _LOGGER .info ("Notifying alive to %s" , alive_topic )
6263 await mqtt .async_publish (hass , alive_topic , True , 1 , retain = True )
6364 hass .data [DOMAIN ][entry .entry_id ] = coordinator
64- hass .config_entries .async_setup_platforms (entry , PLATFORMS )
65+ await hass .config_entries .async_forward_entry_setups (entry , PLATFORMS )
6566 elif MERGE_IDS in entry .data :
66- hass .config_entries .async_setup_platforms (entry , [Platform .DEVICE_TRACKER ])
67+ await hass .config_entries .async_forward_entry_setups (
68+ entry , [Platform .DEVICE_TRACKER ]
69+ )
6770
6871 return True
6972
@@ -98,14 +101,17 @@ def __init__(self, hass: HomeAssistant, data) -> None:
98101 """Initialise coordinator."""
99102 self .mac = data [MAC ]
100103 self .expiration_time : int
104+ self .min_rssi : int
101105 self .default_expiration_time : int = 2
106+ self .default_min_rssi : int = - 80
102107 given_name = data [NAME ] if data .__contains__ (NAME ) else self .mac
103108 self .room_data = dict [str , int ]()
104109 self .filtered_room_data = dict [str , int ]()
105110 self .room_filters = dict [str , KalmanFilter ]()
106111 self .room_expiration_timers = dict [str , asyncio .TimerHandle ]()
107112 self .room : str | None = None
108113 self .last_received_adv_time = None
114+ self .time_from_previous = None
109115
110116 super ().__init__ (hass , _LOGGER , name = given_name )
111117
@@ -137,23 +143,29 @@ async def message_received(self, msg):
137143 try :
138144 data = MQTT_PAYLOAD (msg .payload )
139145 except vol .MultipleInvalid as error :
140- _LOGGER .debug ("Skipping update because of malformatted data : %s" , error )
146+ _LOGGER .debug ("Skipping malformed message : %s" , error )
141147 return
142148 msg_time = data .get (TIMESTAMP )
143149 if msg_time is not None :
144150 current_time = int (time .time ())
145151 if current_time - msg_time >= self .get_expiration_time ():
146- _LOGGER .info ("Received message with old timestamp, skipping " )
152+ _LOGGER .info ("Skipping message with old timestamp" )
147153 return
148-
149- self .time_from_previous = None if self .last_received_adv_time is None else (current_time - self .last_received_adv_time )
154+ rssi = data .get (RSSI )
155+ if rssi < self .get_min_rssi ():
156+ _LOGGER .info ("Skipping message with low RSSI (%s)" , rssi )
157+ return
158+ self .time_from_previous = (
159+ None
160+ if self .last_received_adv_time is None
161+ else (current_time - self .last_received_adv_time )
162+ )
150163 self .last_received_adv_time = current_time
151164
152165 room_topic = msg .topic .split ("/" )[2 ]
153166
154167 await self .schedule_data_expiration (room_topic )
155168
156- rssi = data .get (RSSI )
157169 self .room_data [room_topic ] = rssi
158170 self .filtered_room_data [room_topic ] = self .get_filtered_value (room_topic , rssi )
159171
@@ -171,7 +183,7 @@ async def schedule_data_expiration(self, room):
171183 self .room_expiration_timers [room ] = timer
172184
173185 def get_filtered_value (self , room , value ) -> int :
174- """Apply Kalman filter"""
186+ """Apply Kalman filter. """
175187 k_filter : KalmanFilter
176188 if room in self .room_filters :
177189 k_filter = self .room_filters [room ]
@@ -184,6 +196,10 @@ def get_expiration_time(self):
184196 """Calculate current expiration delay."""
185197 return getattr (self , "expiration_time" , self .default_expiration_time ) * 60
186198
199+ def get_min_rssi (self ):
200+ """Calculate current minimum RSSI to take."""
201+ return getattr (self , "min_rssi" , self .default_min_rssi )
202+
187203 async def expire_data (self , room ):
188204 """Set data for certain room expired."""
189205 del self .room_data [room ]
@@ -200,65 +216,76 @@ async def on_expiration_time_changed(self, new_time: int):
200216 for room in self .room_expiration_timers .keys ():
201217 await self .schedule_data_expiration (room )
202218
219+ async def on_min_rssi_changed (self , new_min_rssi : int ):
220+ """Respond to min RSSI changed by user."""
221+ if new_min_rssi is None :
222+ return
223+ self .min_rssi = new_min_rssi
224+
225+
203226class KalmanFilter :
204227 """Filtering RSSI data."""
205228
206- cov = float ('nan' )
207- x = float ('nan' )
229+ cov = float ("nan" )
230+ param_x = float ("nan" )
231+
232+ def __init__ (self , param_r , param_q ):
233+ """Initialize filter.
208234
209- def __init__ (self , R , Q ):
210- """
211- Constructor
212235 :param R: Process Noise
213236 :param Q: Measurement Noise
214237 """
215- self .A = 1
216- self .B = 0
217- self .C = 1
238+ self .param_a = 1
239+ self .param_b = 0
240+ self .param_c = 1
218241
219- self .R = R
220- self .Q = Q
242+ self .param_r = param_r
243+ self .param_q = param_q
221244
222245 def filter (self , measurement ):
223- """
224- Filters a measurement
246+ """Filter measurement.
247+
225248 :param measurement: The measurement value to be filtered
226249 :return: The filtered value
227250 """
228- u = 0
229- if math .isnan (self .x ):
230- self .x = (1 / self .C ) * measurement
231- self .cov = (1 / self .C ) * self .Q * (1 / self .C )
251+ param_u = 0
252+ if math .isnan (self .param_x ):
253+ self .param_x = (1 / self .param_c ) * measurement
254+ self .cov = (1 / self .param_c ) * self .param_q * (1 / self .param_c )
232255 else :
233- pred_x = (self .A * self .x ) + (self .B * u )
234- pred_cov = ((self .A * self .cov ) * self .A ) + self .R
256+ pred_x = (self .param_a * self .param_x ) + (self .param_b * param_u )
257+ pred_cov = ((self .param_a * self .cov ) * self .param_a ) + self .param_r
235258
236259 # Kalman Gain
237- K = pred_cov * self .C * (1 / ((self .C * pred_cov * self .C ) + self .Q ));
260+ param_k = (
261+ pred_cov
262+ * self .param_c
263+ * (1 / ((self .param_c * pred_cov * self .param_c ) + self .param_q ))
264+ )
238265
239266 # Correction
240- self .x = pred_x + K * (measurement - (self .C * pred_x ));
241- self .cov = pred_cov - (K * self .C * pred_cov );
267+ self .param_x = pred_x + param_k * (measurement - (self .param_c * pred_x ))
268+ self .cov = pred_cov - (param_k * self .param_c * pred_cov )
242269
243- return self .x
270+ return self .param_x
244271
245272 def last_measurement (self ):
246- """
247- Returns the last measurement fed into the filter
273+ """Return the last measurement fed into the filter.
274+
248275 :return: The last measurement fed into the filter
249276 """
250- return self .x
277+ return self .param_x
251278
252279 def set_measurement_noise (self , noise ):
253- """
254- Sets measurement noise
280+ """Set measurement noise.
281+
255282 :param noise: The new measurement noise
256283 """
257- self .Q = noise
284+ self .param_q = noise
258285
259286 def set_process_noise (self , noise ):
260- """
261- Sets process noise
287+ """Set process noise.
288+
262289 :param noise: The new process noise
263290 """
264- self .R = noise
291+ self .param_r = noise
0 commit comments