Skip to content

Commit

Permalink
fix: Metadevices not going Unavailable
Browse files Browse the repository at this point in the history
- bug introduced from per-device ref_power changes.
- There is another bug that metadevices will fight other devices about who's ref_power to use. Will address separately.
  • Loading branch information
agittins committed Nov 13, 2024
1 parent 79487cc commit b7f075d
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 31 deletions.
53 changes: 32 additions & 21 deletions custom_components/bermuda/bermuda_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,20 +128,29 @@ def __init__(self, address, options) -> None:
else:
self.address_type = BDADDR_TYPE_OTHER

def set_ref_power(self, value: float):
def set_ref_power(self, new_ref_power: float):
"""
Set a new reference power for this device and immediately apply
an interim distance calculation.
This gets called by the calibration routines, but also by metadevice
updates, as they need to apply their own ref_power if necessary.
"""
self.ref_power = value
nearest_distance = 9999 # running tally to find closest scanner
nearest_scanner = None
for scanner in self.scanners.values():
rawdist = scanner.set_ref_power(value)
if rawdist < nearest_distance:
nearest_distance = rawdist
nearest_scanner = scanner
if nearest_scanner is not None:
if new_ref_power != self.ref_power:
# it's actually changed, proceed...
self.ref_power = new_ref_power
nearest_distance = 9999 # running tally to find closest scanner
nearest_scanner = None
for scanner in self.scanners.values():
rawdist = scanner.set_ref_power(new_ref_power)
if rawdist is not None and rawdist < nearest_distance:
nearest_distance = rawdist
nearest_scanner = scanner
# Even though the actual scanner should not have changed (it should
# remain none or a given scanner, since the relative distances won't have
# changed due to ref_power), we still call apply so that the new area_distance
# gets applied.
# if nearest_scanner is not None:
self.apply_scanner_selection(nearest_scanner)

def apply_scanner_selection(self, closest_scanner: BermudaDeviceScanner | None):
Expand All @@ -151,37 +160,39 @@ def apply_scanner_selection(self, closest_scanner: BermudaDeviceScanner | None):
Used to apply a "winning" scanner's data to the device for setting closest Area.
"""
# FIXME: This might need to check if it's a metadevice source or dest, and
# ensure things are applied correctly. Might be a non-issue.
old_area = self.area_name
if closest_scanner is not None:
# We found a winner
old_area = self.area_name
self.area_id = closest_scanner.area_id
self.area_name = closest_scanner.area_name
self.area_distance = closest_scanner.rssi_distance
self.area_rssi = closest_scanner.rssi
self.area_scanner = closest_scanner.name
if (old_area != self.area_name) and self.create_sensor:
# We check against area_name so we can know if the
# device's area changed names.
_LOGGER.debug(
"Device %s was in '%s', now in '%s'",
self.name,
old_area,
self.area_name,
)
else:
# Not close to any scanners!
self.area_id = None
self.area_name = None
self.area_distance = None
self.area_rssi = None
self.area_scanner = None
if (old_area != self.area_name) and self.create_sensor:
# Our area has changed!
_LOGGER.debug(
"Device %s was in '%s', now '%s'",
self.name,
old_area,
self.area_name,
)

def calculate_data(self):
"""
Call after doing update_scanner() calls so that distances
etc can be freshly smoothed and filtered.
"""
# Run calculate_data on each child scanner of this device:
for scanner in self.scanners.values():
if isinstance(scanner, BermudaDeviceScanner):
# in issue #355 someone had an empty dict instead of a scanner object.
Expand All @@ -193,7 +204,7 @@ def calculate_data(self):
"scanner_not_instance", "Scanner device is not a BermudaDevice instance, skipping."
)

# Update whether the device has been seen recently, for device_tracker:
# Update whether this device has been seen recently, for device_tracker:
if (
self.last_seen is not None
and MONOTONIC_TIME() - self.options.get(CONF_DEVTRACK_TIMEOUT, DEFAULT_DEVTRACK_TIMEOUT) < self.last_seen
Expand Down
32 changes: 25 additions & 7 deletions custom_components/bermuda/bermuda_device_scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ def update_advertisement(self, scandata: BluetoothScannerDevice):
claims to have data.
"""
# In case the scanner has changed it's details since startup:
# FIXME: This should probably be a separate function that the refresh_scanners
# calls if necessary, rather than re-doing it every cycle.
self.name = scandata.scanner.name
self.area_id = self.scanner_device.area_id
self.area_name = self.scanner_device.area_name
Expand Down Expand Up @@ -240,9 +242,12 @@ def _update_raw_distance(self, reading_is_new=True) -> float:
# Add a new historical reading
self.hist_distance.insert(0, distance)
# don't insert into hist_distance_by_interval, that's done by the caller.
else:
# We are over-riding readings between cycles. Force the
# new value in-place.
elif self.rssi_distance is not None:
# We are over-riding readings between cycles.
# We will force the new measurement, but only if we were
# already showing a "current" distance, as we don't want
# to "freshen" a measurement that was already out of date,
# hence the elif not none above.
self.rssi_distance = distance
if len(self.hist_distance) > 0:
self.hist_distance[0] = distance
Expand All @@ -254,12 +259,25 @@ def _update_raw_distance(self, reading_is_new=True) -> float:
# modify in-place.
return distance

def set_ref_power(self, value: float):
"""Set a new reference power from the parent device and immediately update distance."""
def set_ref_power(self, value: float) -> float | None:
"""
Set a new reference power and return the resulting distance.
Typically called from the parent device when either the user changes the calibration
of ref_power for a device, or when a metadevice takes on a new source device, and
propagates its own ref_power to our parent.
Note that it is unlikely to return None as its only returning the raw, not filtered
distance = the exception being uninitialised entries.
"""
# When the user updates the ref_power we want to reflect that change immediately,
# and not subject it to the normal smoothing algo.
self.ref_power = value
return self._update_raw_distance(False)
# But make sure it's actually different, in case it's just a metadevice propagating
# its own ref_power without need.
if value != self.ref_power:
self.ref_power = value
return self._update_raw_distance(False)
return self.rssi_distance_raw

def calculate_data(self):
"""
Expand Down
24 changes: 21 additions & 3 deletions custom_components/bermuda/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -926,10 +926,12 @@ def update_metadevices(self):
other initialisation.
"""
# First seed the metadevice skeletons and set their latest beacon_source entries
# Private BLE Devices:
# Private BLE Devices. It will only do anything if the self._do_private_device_init
# flag is set.
self.discover_private_ble_metadevices()

# iBeacon devices should already have their metadevices created.
# FIXME: irk and ibeacons will fight over their relative ref_power too.

for metadev in self.metadevices.values():
# We Expect the first beacon source to be the current one.
Expand All @@ -945,8 +947,24 @@ def update_metadevices(self):
# Map the source device's scanner list into ours
metadev.scanners = source_device.scanners

# Set the source device's ref_power from our own
source_device.set_ref_power(metadev.ref_power)
# Set the source device's ref_power from our own. This will cause
# the source device and all its scanner entries to update their
# distance measurements. This won't affect Area wins though, because
# they are "relative", not absolute.

# FIXME: This has two potential bugs:
# - if multiple metadevices share a source, they will
# "fight" over their preferred ref_power, if different.
# - The non-meta device (if tracked) will receive distances
# based on the meta device's ref_power.
# - The non-meta device if tracked will have its own ref_power ignored.
#
# None of these are terribly awful, but worth fixing.

# Note we are setting the ref_power on the source_device, not the
# individual scanner entries (it will propagate to them though)
if source_device.ref_power != metadev.ref_power:
source_device.set_ref_power(metadev.ref_power)

# anything that isn't already set to something interesting, overwrite
# it with the new device's data.
Expand Down

0 comments on commit b7f075d

Please sign in to comment.