Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit f8a051f

Browse files
committed
Additional settings. Fix for HA 2023.5
1 parent 4205682 commit f8a051f

File tree

6 files changed

+126
-50
lines changed

6 files changed

+126
-50
lines changed

custom_components/format_ble_tracker/__init__.py

Lines changed: 68 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44
import asyncio
55
import json
66
import logging
7+
8+
# import numpy as np
9+
import math
710
import time
811
from typing import Any
9-
#import numpy as np
10-
import math
1112

1213
import 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+
203226
class 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

custom_components/format_ble_tracker/config_flow.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
from homeassistant.helpers import selector
1212

1313
from .const import (
14-
DOMAIN,
15-
AWAY_WHEN_OR,
1614
AWAY_WHEN_AND,
15+
AWAY_WHEN_OR,
16+
DOMAIN,
1717
MAC,
1818
MAC_REGEX,
1919
MERGE_IDS,
@@ -47,7 +47,7 @@
4747

4848
CONF_MERGE_LOGIC = {
4949
AWAY_WHEN_OR: "Show as away, when ANY tracker is away",
50-
AWAY_WHEN_AND: "Show as away, when ALL trackers are away"
50+
AWAY_WHEN_AND: "Show as away, when ALL trackers are away",
5151
}
5252

5353
MERGE_SCHEMA = vol.Schema(

custom_components/format_ble_tracker/device_tracker.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@
1212
from .__init__ import BeaconCoordinator
1313
from .common import BeaconDeviceEntity
1414
from .const import (
15+
AWAY_WHEN_AND,
16+
AWAY_WHEN_OR,
1517
DOMAIN,
1618
ENTITY_ID,
17-
AWAY_WHEN_OR,
18-
AWAY_WHEN_AND,
1919
MERGE_IDS,
2020
MERGE_LOGIC,
2121
NAME,

custom_components/format_ble_tracker/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"domain": "format_ble_tracker",
33
"name": "Format BLE Tracker",
4-
"version": "0.0.7",
4+
"version": "0.0.8",
55
"config_flow": true,
66
"documentation": "https://github.com/formatBCE/Format-BLE-Tracker/blob/main/README.md",
77
"issue_tracker": "https://github.com/formatBCE/Format-BLE-Tracker/issues",

custom_components/format_ble_tracker/number.py

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@ async def async_setup_entry(
1616
"""Add sensor entities from a config_entry."""
1717

1818
coordinator: BeaconCoordinator = hass.data[DOMAIN][entry.entry_id]
19-
async_add_entities([BleDataExpirationNumber(coordinator)], True)
19+
async_add_entities(
20+
[BleDataExpirationNumber(coordinator), BleMinimumRssiNumber(coordinator)], True
21+
)
2022

2123

2224
class BleDataExpirationNumber(BeaconDeviceEntity, RestoreNumber, NumberEntity):
23-
"""Define an room sensor entity."""
25+
"""Define expiration time number entity."""
2426

2527
_attr_should_poll = False
2628

@@ -39,7 +41,11 @@ def __init__(self, coordinator: BeaconCoordinator) -> None:
3941
async def async_added_to_hass(self):
4042
"""Entity has been added to hass, restoring state."""
4143
restored = await self.async_get_last_number_data()
42-
native_value = 2 if restored is None else restored.native_value
44+
native_value = (
45+
self.coordinator.default_expiration_time
46+
if restored is None
47+
else restored.native_value
48+
)
4349
await self.update_value(native_value)
4450

4551
async def async_set_native_value(self, value: float) -> None:
@@ -52,3 +58,42 @@ async def update_value(self, value: int):
5258
self._attr_native_value = value
5359
await self.coordinator.on_expiration_time_changed(value)
5460
self.async_write_ha_state()
61+
62+
63+
class BleMinimumRssiNumber(BeaconDeviceEntity, RestoreNumber, NumberEntity):
64+
"""Define minimum RSSI number entity."""
65+
66+
_attr_should_poll = False
67+
68+
def __init__(self, coordinator: BeaconCoordinator) -> None:
69+
"""Initialize."""
70+
super().__init__(coordinator)
71+
self._attr_name = coordinator.name + " minimum RSSI"
72+
self._attr_mode = NumberMode.SLIDER
73+
self._attr_native_unit_of_measurement = "dBm"
74+
self._attr_native_max_value = -20
75+
self._attr_native_min_value = -100
76+
self._attr_native_step = 1
77+
self._attr_unique_id = self.formatted_mac_address + "_min_rssi"
78+
self.entity_id = f"{input_number.DOMAIN}.{self._attr_unique_id}"
79+
80+
async def async_added_to_hass(self):
81+
"""Entity has been added to hass, restoring state."""
82+
restored = await self.async_get_last_number_data()
83+
native_value = (
84+
self.coordinator.default_min_rssi
85+
if restored is None
86+
else restored.native_value
87+
)
88+
await self.update_value(native_value)
89+
90+
async def async_set_native_value(self, value: float) -> None:
91+
"""Update the current value."""
92+
val = min(-20, max(-100, int(value)))
93+
await self.update_value(val)
94+
95+
async def update_value(self, value: int):
96+
"""Set value to HA and coordinator."""
97+
self._attr_native_value = value
98+
await self.coordinator.on_min_rssi_changed(value)
99+
self.async_write_ha_state()

custom_components/format_ble_tracker/sensor.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,8 @@ def extra_state_attributes(self):
4747
attr["current_rooms"] = {}
4848
for key, value in self.coordinator.filtered_room_data.items():
4949
attr["current_rooms"][key] = f"{value} dBm"
50+
attr["current_rooms_raw"] = {}
51+
for key, value in self.coordinator.room_data.items():
52+
attr["current_rooms_raw"][key] = f"{value} dBm"
53+
attr["last_adv"] = self.coordinator.time_from_previous
5054
return attr

0 commit comments

Comments
 (0)