From a38c125765cb1373918b7404fae3945e74955470 Mon Sep 17 00:00:00 2001 From: Phil Bruckner Date: Wed, 28 Sep 2022 06:10:19 -0600 Subject: [PATCH] Improve Life360 address attribute (#76269) --- .../components/life360/coordinator.py | 18 ++---- .../components/life360/device_tracker.py | 56 ++++++++++++++++++- 2 files changed, 61 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/life360/coordinator.py b/homeassistant/components/life360/coordinator.py index a098f1f6735..3bee1b5827e 100644 --- a/homeassistant/components/life360/coordinator.py +++ b/homeassistant/components/life360/coordinator.py @@ -64,10 +64,7 @@ class Life360Circle: class Life360Member: """Life360 Member data.""" - # Don't include address field in eq comparison because it often changes (back and - # forth) between updates. If it was included there would be way more state changes - # and database updates than is useful. - address: str | None = field(compare=False) + address: str | None at_loc_since: datetime battery_charging: bool battery_level: int @@ -201,15 +198,12 @@ class Life360DataUpdateCoordinator(DataUpdateCoordinator[Life360Data]): place = loc["name"] or None - if place: - address: str | None = place + address1: str | None = loc["address1"] or None + address2: str | None = loc["address2"] or None + if address1 and address2: + address: str | None = ", ".join([address1, address2]) else: - address1 = loc["address1"] or None - address2 = loc["address2"] or None - if address1 and address2: - address = ", ".join([address1, address2]) - else: - address = address1 or address2 + address = address1 or address2 speed = max(0, float(loc["speed"]) * SPEED_FACTOR_MPH) if self._hass.config.units.is_metric: diff --git a/homeassistant/components/life360/device_tracker.py b/homeassistant/components/life360/device_tracker.py index f4047574f6a..2c05b944a27 100644 --- a/homeassistant/components/life360/device_tracker.py +++ b/homeassistant/components/life360/device_tracker.py @@ -3,6 +3,7 @@ from __future__ import annotations from collections.abc import Mapping +from contextlib import suppress from typing import Any, cast from homeassistant.components.device_tracker import SourceType @@ -114,6 +115,17 @@ class Life360DeviceTracker( self._attr_name = self._data.name self._attr_entity_picture = self._data.entity_picture + # Server sends a pair of address values on alternate updates. Keep the pair of + # values so they can be combined into the one address attribute. + # The pair will either be two different address values, or one address and a + # copy of the Place value (if the Member is in a Place.) In the latter case we + # won't duplicate the Place name, but rather just use one the address value. Use + # the value of None to hold one of the "slots" in the list so we'll know not to + # expect another address value. + if (address := self._data.address) == self._data.place: + address = None + self._addresses = [address] + @property def _options(self) -> Mapping[str, Any]: """Shortcut to config entry options.""" @@ -160,6 +172,30 @@ class Life360DeviceTracker( for attr in _LOC_ATTRS: setattr(self._data, attr, getattr(self._prev_data, attr)) + else: + # Process address field. + # Check if we got the name of a Place, which we won't use. + if (address := self._data.address) == self._data.place: + address = None + if last_seen != prev_seen: + # We have new location data, so we might have a new pair of address + # values. + if address not in self._addresses: + # We do. + # Replace the old values with the first value of the new pair. + self._addresses = [address] + elif self._data.address != self._prev_data.address: + # Location data didn't change in general, but the address field did. + # There are three possibilities: + # 1. The new value is one of the pair we've already seen before. + # 2. The new value is the second of the pair we haven't seen yet. + # 3. The new value is the first of a new pair of values. + if address not in self._addresses: + if len(self._addresses) < 2: + self._addresses.append(address) + else: + self._addresses = [address] + self._prev_data = self._data super()._handle_coordinator_update() @@ -250,8 +286,26 @@ class Life360DeviceTracker( ATTR_SPEED: None, ATTR_WIFI_ON: None, } + + # Generate address attribute from pair of address values. + # There may be two, one or no values. If there are two, sort the strings since + # one value is typically a numbered street address and the other is a street, + # town or state name, and it's helpful to start with the more detailed address + # value. Also, sorting helps to generate the same result if we get a location + # update, and the same pair is sent afterwards, but where the value that comes + # first is swapped vs the order they came in before the update. + address1: str | None = None + address2: str | None = None + with suppress(IndexError): + address1 = self._addresses[0] + address2 = self._addresses[1] + if address1 and address2: + address: str | None = " / ".join(sorted([address1, address2])) + else: + address = address1 or address2 + return { - ATTR_ADDRESS: self._data.address, + ATTR_ADDRESS: address, ATTR_AT_LOC_SINCE: self._data.at_loc_since, ATTR_BATTERY_CHARGING: self._data.battery_charging, ATTR_DRIVING: self.driving,