Jelajahi Sumber

Reconcile on mode sync enable; document why WiFi is not driven at reconcile

Turning mode sync off→on now calls async_reconcile_from_store() so the system
snaps to the correct guest mode state and reschedules handles immediately, rather
than waiting for the next timed transition. WiFi/lock actions remain lazy
(corrected by _diff_with_unifi_check on the next poll); Force Refresh triggers
that immediately if needed. Added a docstring to async_reconcile_from_store()
recording why WiFi is intentionally excluded from reconcile.
ZiB 3 bulan lalu
induk
melakukan
31aa435e72

+ 9 - 1
custom_components/room_cal_ha/coordinator.py

@@ -340,7 +340,15 @@ class RoomCalCoordinator(DataUpdateCoordinator[CoordinatorData]):
   async def async_reconcile_from_store(self, *, _startup: bool = False) -> None:
     """Load store state, apply elapsed transitions, set room state, reschedule mode sync.
 
-    Called at startup (via async_post_setup_reconcile) and when leaving override mode.
+    Called at startup (via async_post_setup_reconcile), when leaving override mode, and
+    when mode sync is turned on (off→on toggle).
+
+    WiFi and lock actions are intentionally NOT driven here. UniFi/lock state is hardware
+    that outlives HA restarts and mode-sync toggles, so re-pushing on every reconcile
+    would be noisy and redundant. Any real drift is detected and corrected by
+    _diff_with_unifi_check on the next backend poll. Use Force Refresh to trigger that
+    poll immediately if prompt correction is needed.
+
     _startup=True uses direct state mutation on the room state switch instead of
     async_turn_on/off, because HA state writes are deferred until commit_state().
     """

+ 4 - 0
custom_components/room_cal_ha/switch.py

@@ -95,6 +95,10 @@ class ModeSyncSwitch(_SwitchBase):
     super().__init__(coordinator, entry, device_name)
     self._attr_unique_id = f"{entry.entry_id}_mode_sync"
 
+  async def async_turn_on(self, **kwargs: object) -> None:
+    await super().async_turn_on(**kwargs)
+    await self._coordinator.async_reconcile_from_store()
+
   async def async_turn_off(self, **kwargs: object) -> None:
     await super().async_turn_off(**kwargs)
     self._coordinator.cancel_mode_sync()

+ 8 - 0
tests/test_switches.py

@@ -25,6 +25,7 @@ def _mode_sync():
   s._state = False
   s._coordinator = MagicMock()
   s._coordinator.cancel_mode_sync = MagicMock()
+  s._coordinator.async_reconcile_from_store = AsyncMock()
   s.async_write_ha_state = MagicMock()
   return s
 
@@ -102,6 +103,13 @@ async def test_mode_sync_turn_on_does_not_cancel_sync():
   s._coordinator.cancel_mode_sync.assert_not_called()
 
 
+async def test_mode_sync_turn_on_triggers_reconcile():
+  s = _mode_sync()
+  await s.async_turn_on()
+  assert s.is_on is True
+  s._coordinator.async_reconcile_from_store.assert_awaited_once()
+
+
 async def test_mode_sync_initial_state_is_false():
   s = _mode_sync()
   assert s.is_on is False