ha-inlite

Home Assistant integration for in-lite
git clone https://git.stephank.nl/ha-inlite
Log | Files | Refs | README | LICENSE | ZIP

commit ba88c5fff1a25f0b731a2bcc92b89931455f26ce
parent 14719a6307c308c5c2d0db3cca79938a7bad2ad9
Author: Stéphan Kochen <git@stephank.nl>
Date: Thu, 03 Sep 2026 20:08:52 +0200

Fix outlet state reporting for timers

diff --git a/custom_components/inlite/lib/inlite_ble/hub.py b/custom_components/inlite/lib/inlite_ble/hub.py index b5461bc9a0ae51f1d3dfd0bb9aed886124796fc6..5edbbc3aaa0c379aee19c68f3ade25d16bf7d484 100644 --- a/custom_components/inlite/lib/inlite_ble/hub.py +++ b/custom_components/inlite/lib/inlite_ble/hub.py @@ -70,7 +70,13 @@ self.output_state = output_state @property def is_on(self) -> bool: - return (self.output_mode & 0x01) != 0 + """Whether the physical outlet is currently energized. + + ``output_mode`` configures permanent/motion/timer/dusk behaviour. + The hub reports the source(s) currently energizing the outlet in the + low four bits of ``output_state``. + """ + return (self.output_state & 0x0F) != 0 def __repr__(self) -> str: return "ZoneState(id=%d, %s, mode=0x%02X, state=0x%02X)" % ( @@ -567,8 +573,12 @@ if success and output_id in self._zone_states: zs = self._zone_states[output_id] if on: zs.output_mode = zs.output_mode | 0x01 + zs.output_state = zs.output_state | 0x01 else: zs.output_mode = zs.output_mode & ~0x01 + # Match the app: manual off clears every active-output source + # (permanent, motion, dusk-to-dawn, and timer). + zs.output_state = zs.output_state & ~0x0F return success diff --git a/tests/test_hub.py b/tests/test_hub.py index dbc422ecb96f3db3f296ffe98cc320639fe4422a..00193d4cd5bc4018541573208e821f9b76bcf5ac 100644 --- a/tests/test_hub.py +++ b/tests/test_hub.py @@ -9,19 +9,24 @@ class TestZoneState: """Tests for ZoneState.""" def test_is_on_when_mode_bit_set(self) -> None: - zs = ZoneState(output_id=0, output_mode=0x01) + zs = ZoneState(output_id=0, output_mode=0x01, output_state=0x01) assert zs.is_on is True def test_is_off_when_mode_bit_clear(self) -> None: - zs = ZoneState(output_id=0, output_mode=0x00) + zs = ZoneState(output_id=0, output_mode=0x00, output_state=0x00) assert zs.is_on is False - def test_is_on_with_other_bits(self) -> None: - zs = ZoneState(output_id=0, output_mode=0x03) + def test_is_on_for_timer_even_without_permanent_mode(self) -> None: + # Confirmed by inlite-timer-on-off.btsnoop: mode=0x02, state=0x08. + zs = ZoneState(output_id=0, output_mode=0x02, output_state=0x08) assert zs.is_on is True + def test_is_off_when_no_output_state_source_is_active(self) -> None: + zs = ZoneState(output_id=0, output_mode=0x03, output_state=0x00) + assert zs.is_on is False + def test_repr(self) -> None: - zs = ZoneState(output_id=1, output_mode=0x01, output_state=0x10) + zs = ZoneState(output_id=1, output_mode=0x01, output_state=0x01) r = repr(zs) assert "id=1" in r assert "ON" in r