Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions custom_components/opendisplay/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,20 @@ def native_value(self) -> float | int | str | datetime | None:
class OpenDisplayLastSeenSensor(OpenDisplaySensorEntity):
"""last_seen sourced from the bluetooth stack, not the gated callback."""

def __init__(
self,
coordinator,
description: OpenDisplaySensorEntityDescription,
) -> None:
"""Initialize the last_seen sensor."""
super().__init__(coordinator, description)
self._last_seen: datetime | None = None

@property
def available(self) -> bool:
"""Stay available once a last_seen value has been observed."""
return self._last_seen is not None or super().available

@property
def native_value(self) -> datetime | None:
# connectable=False matches the Bluetooth advertisement monitor's
Expand All @@ -150,8 +164,9 @@ def native_value(self) -> datetime | None:
self.hass, self.coordinator.address, connectable=False
)
if info is None:
return None
return self._last_seen
# info.time is a monotonic clock (monotonic_time_coarse); convert to
# wall time with the same offset the advertisement monitor uses.
wall = info.time + (time.time() - time.monotonic())
return datetime.fromtimestamp(wall, tz=timezone.utc)
self._last_seen = datetime.fromtimestamp(wall, tz=timezone.utc)
return self._last_seen
29 changes: 29 additions & 0 deletions tests/test_last_seen.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,35 @@ def test_native_value_none_when_no_service_info():
assert entity.native_value is None


def test_last_seen_available_after_first_value_when_coordinator_unavailable():
entity = _make_sensor()
entity.coordinator.available = False
mono = time.monotonic()

with patch.object(
sensor_mod,
"async_last_service_info",
return_value=SimpleNamespace(time=mono),
):
value = entity.native_value

assert value is not None
assert entity.available is True

with patch.object(sensor_mod, "async_last_service_info", return_value=None):
assert entity.native_value == value
assert entity.available is True


def test_last_seen_unavailable_before_first_value_when_coordinator_unavailable():
entity = _make_sensor()
entity.coordinator.available = False

with patch.object(sensor_mod, "async_last_service_info", return_value=None):
assert entity.native_value is None
assert entity.available is False


if __name__ == "__main__":
import pytest

Expand Down
Loading