|
|
@@ -34,19 +34,25 @@ _UNSET = object()
|
|
|
|
|
|
@dataclass
|
|
|
class CoordinatorData:
|
|
|
+ active: ReservationData
|
|
|
current: ReservationData
|
|
|
next: ReservationData
|
|
|
current_last_refresh: datetime | None
|
|
|
next_last_refresh: datetime | None
|
|
|
current_last_updated: datetime | None = None
|
|
|
next_last_updated: datetime | None = None
|
|
|
+ active_last_refresh: datetime | None = None
|
|
|
+ active_last_updated: datetime | None = None
|
|
|
override_active: bool = False
|
|
|
api_status: ApiStatusData | None = None
|
|
|
- current_arrived: bool = False
|
|
|
- current_departed: bool = False
|
|
|
+ active_arrived: bool = False
|
|
|
+ active_departed: bool = False
|
|
|
|
|
|
|
|
|
class RoomCalCoordinator(DataUpdateCoordinator[CoordinatorData]):
|
|
|
+ """Presents three records: `active` (drives WiFi/locks/guest mode, maskable by
|
|
|
+ override), and `current`/`next` (pure, never-overridable mirrors of the backend
|
|
|
+ feed, used only for display). Override is a mask on `active` only."""
|
|
|
|
|
|
def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None:
|
|
|
interval = entry.options.get(CONF_REFRESH_INTERVAL, DEFAULT_REFRESH_INTERVAL)
|
|
|
@@ -64,68 +70,57 @@ class RoomCalCoordinator(DataUpdateCoordinator[CoordinatorData]):
|
|
|
self._next_last_refresh: datetime | None = None
|
|
|
self._current_last_updated: datetime | None = None
|
|
|
self._next_last_updated: datetime | None = None
|
|
|
+ self._active_last_refresh: datetime | None = None
|
|
|
+ self._active_last_updated: datetime | None = None
|
|
|
self._override: OverrideData | None = None
|
|
|
self._override_end_handle: Any = None
|
|
|
self._override_end_target: OverrideData | None = None
|
|
|
- self._current_arrived: bool = False
|
|
|
- self._current_departed: bool = False
|
|
|
+ self._active_arrived: bool = False
|
|
|
+ self._active_departed: bool = False
|
|
|
|
|
|
async def async_setup(self) -> None:
|
|
|
await self.store.async_load()
|
|
|
self._current_last_updated = self.store.get_last_updated(STORE_CURRENT)
|
|
|
self._next_last_updated = self.store.get_last_updated(STORE_NEXT)
|
|
|
- self._current_arrived = self.store.get_arrived()
|
|
|
- self._current_departed = self.store.get_departed()
|
|
|
+ # Active isn't independently persisted (override is ephemeral); when no override
|
|
|
+ # is active it always mirrors current, so seed it from current's own timestamp.
|
|
|
+ self._active_last_updated = self._current_last_updated
|
|
|
+ self._active_arrived = self.store.get_arrived()
|
|
|
+ self._active_departed = self.store.get_departed()
|
|
|
|
|
|
def inject_room_state_switch(self, switch: Any) -> None:
|
|
|
self._room_state_switch = switch
|
|
|
|
|
|
# -- Effective-layer resolution (override vs. store) ---------------------
|
|
|
|
|
|
- def _target_covered_by_override(self, target: str) -> bool:
|
|
|
- """Whether `target` ("current"/"next") is currently owned by the override."""
|
|
|
- if self._override is None:
|
|
|
- return False
|
|
|
- if target == STORE_CURRENT:
|
|
|
- return True
|
|
|
- return self._override.next is not None
|
|
|
-
|
|
|
- def _effective(self, target: str) -> ReservationData:
|
|
|
- """Read `target` from whichever layer currently owns it, with defaults applied."""
|
|
|
- if self._target_covered_by_override(target):
|
|
|
- raw = self._override.current if target == STORE_CURRENT else self._override.next
|
|
|
- else:
|
|
|
- raw = self.store.get(target)
|
|
|
+ def _effective_active(self) -> ReservationData:
|
|
|
+ """Read `active` from the override if one is set, else mirror the store's current."""
|
|
|
+ raw = self._override.active if self._override is not None else self.store.get(STORE_CURRENT)
|
|
|
return self._with_defaults(raw)
|
|
|
|
|
|
- async def _async_write(self, target: str, value: ReservationData | None) -> None:
|
|
|
- """Write `value` into whichever layer currently owns `target`."""
|
|
|
- if self._target_covered_by_override(target):
|
|
|
- if target == STORE_CURRENT:
|
|
|
- self._override = OverrideData(current=value, next=self._override.next)
|
|
|
- else:
|
|
|
- self._override = OverrideData(current=self._override.current, next=value)
|
|
|
+ async def _async_write_active(self, value: ReservationData | None) -> None:
|
|
|
+ """Write `value` into whichever layer currently owns `active`."""
|
|
|
+ if self._override is not None:
|
|
|
+ self._override = OverrideData(active=value)
|
|
|
else:
|
|
|
- await self.store.async_set(target, value)
|
|
|
+ await self.store.async_set(STORE_CURRENT, value)
|
|
|
|
|
|
- # -- Shared transition primitive ------------------------------------------
|
|
|
+ def _current_mirror(self) -> ReservationData:
|
|
|
+ """Pure backend mirror — never masked by an override."""
|
|
|
+ return self._with_defaults(self.store.get(STORE_CURRENT))
|
|
|
|
|
|
- async def _async_transition(self, target: str, value: ReservationData | None) -> "GuestStateDiff | None":
|
|
|
- """Write `value` into whichever layer owns `target`.
|
|
|
+ def _next_mirror(self) -> ReservationData:
|
|
|
+ """Pure backend mirror — never masked by an override."""
|
|
|
+ return self._with_defaults(self.store.get(STORE_NEXT))
|
|
|
|
|
|
- Returns the diff (old effective vs. new effective) when target is Current; None
|
|
|
- for Next, since Next never drives guest-mode/WiFi/lock actions directly.
|
|
|
- """
|
|
|
- old = self._effective(target)
|
|
|
- await self._async_write(target, value)
|
|
|
- now = dt_util.utcnow()
|
|
|
- if target == STORE_CURRENT:
|
|
|
- self._current_last_refresh = now
|
|
|
- else:
|
|
|
- self._next_last_refresh = now
|
|
|
- if target != STORE_CURRENT:
|
|
|
- return None
|
|
|
- new = self._effective(target)
|
|
|
+ # -- Shared transition primitive ------------------------------------------
|
|
|
+
|
|
|
+ async def _async_transition_active(self, value: ReservationData | None) -> "GuestStateDiff":
|
|
|
+ """Write `value` into whichever layer owns `active` and return the resulting diff."""
|
|
|
+ old = self._effective_active()
|
|
|
+ await self._async_write_active(value)
|
|
|
+ self._active_last_refresh = dt_util.utcnow()
|
|
|
+ new = self._effective_active()
|
|
|
await self._async_maybe_reset_arrival_flags(old, new)
|
|
|
now_naive = dt_util.now().replace(tzinfo=None)
|
|
|
return self._diff_with_unifi_check(old, new, now_naive)
|
|
|
@@ -134,40 +129,46 @@ class RoomCalCoordinator(DataUpdateCoordinator[CoordinatorData]):
|
|
|
if diff.checkout_needed:
|
|
|
await self._async_run_check_out_actions(diff.departing)
|
|
|
if diff.checkin_needed or diff.wifi_key_changed or diff.door_code_changed:
|
|
|
- await self._async_run_check_in_actions(self._effective(STORE_CURRENT))
|
|
|
+ await self._async_run_check_in_actions(self._effective_active())
|
|
|
|
|
|
def _build_coordinator_data(
|
|
|
self,
|
|
|
+ active: ReservationData,
|
|
|
current: ReservationData,
|
|
|
next_: ReservationData,
|
|
|
- api_status: ApiStatusData | None = _UNSET, # type: ignore[assignment]
|
|
|
+ api_status: ApiStatusData | None | object = _UNSET,
|
|
|
) -> CoordinatorData:
|
|
|
if api_status is _UNSET:
|
|
|
api_status = self.data.api_status if self.data else None
|
|
|
return CoordinatorData(
|
|
|
+ active=active,
|
|
|
current=current,
|
|
|
next=next_,
|
|
|
current_last_refresh=self._current_last_refresh,
|
|
|
next_last_refresh=self._next_last_refresh,
|
|
|
current_last_updated=self._current_last_updated,
|
|
|
next_last_updated=self._next_last_updated,
|
|
|
+ active_last_refresh=self._active_last_refresh,
|
|
|
+ active_last_updated=self._active_last_updated,
|
|
|
override_active=self._override is not None,
|
|
|
api_status=api_status,
|
|
|
- current_arrived=self._current_arrived,
|
|
|
- current_departed=self._current_departed,
|
|
|
+ active_arrived=self._active_arrived,
|
|
|
+ active_departed=self._active_departed,
|
|
|
)
|
|
|
|
|
|
async def _async_publish(self) -> None:
|
|
|
"""Note displayed-state changes, reschedule mode sync, push CoordinatorData."""
|
|
|
now = dt_util.utcnow()
|
|
|
- new_current = self._effective(STORE_CURRENT)
|
|
|
- new_next = self._effective(STORE_NEXT)
|
|
|
- await self._async_note_displayed_change(new_current, new_next, now)
|
|
|
- self.async_set_updated_data(self._build_coordinator_data(new_current, new_next))
|
|
|
- self._reschedule_mode_sync(new_current, new_next)
|
|
|
+ new_active = self._effective_active()
|
|
|
+ new_current = self._current_mirror()
|
|
|
+ new_next = self._next_mirror()
|
|
|
+ await self._async_note_displayed_change(new_active, new_current, new_next, now)
|
|
|
+ self.async_set_updated_data(self._build_coordinator_data(new_active, new_current, new_next))
|
|
|
+ self._reschedule_mode_sync(new_active, new_next)
|
|
|
|
|
|
async def _async_note_displayed_change(
|
|
|
self,
|
|
|
+ new_active: ReservationData,
|
|
|
new_current: ReservationData,
|
|
|
new_next: ReservationData,
|
|
|
now: datetime,
|
|
|
@@ -177,15 +178,21 @@ class RoomCalCoordinator(DataUpdateCoordinator[CoordinatorData]):
|
|
|
On first call (self.data is None), uses _empty_reservation() as the baseline
|
|
|
unless a persisted timestamp already exists — in that case the persisted value
|
|
|
is trusted and no update is issued, avoiding a false 'changed now' on startup.
|
|
|
+ `active`'s last_updated is in-memory only (override state is never persisted).
|
|
|
"""
|
|
|
empty = self._empty_reservation()
|
|
|
if self.data is not None:
|
|
|
+ old_active: ReservationData = self.data.active
|
|
|
old_current: ReservationData = self.data.current
|
|
|
old_next: ReservationData = self.data.next
|
|
|
else:
|
|
|
+ old_active = empty if self._active_last_updated is None else new_active
|
|
|
old_current = empty if self._current_last_updated is None else new_current
|
|
|
old_next = empty if self._next_last_updated is None else new_next
|
|
|
|
|
|
+ if old_active != new_active:
|
|
|
+ self._active_last_updated = now
|
|
|
+
|
|
|
if old_current != new_current:
|
|
|
self._current_last_updated = now
|
|
|
await self.store.async_set_last_updated(STORE_CURRENT, now)
|
|
|
@@ -236,9 +243,9 @@ class RoomCalCoordinator(DataUpdateCoordinator[CoordinatorData]):
|
|
|
|
|
|
if backend_fetched:
|
|
|
# Calendar is authoritative for the store: write its result including None
|
|
|
- # (clears the slot), regardless of whether an override currently covers Current.
|
|
|
+ # (clears the slot), regardless of whether an override currently masks active.
|
|
|
old_store_current = self.store.get(STORE_CURRENT)
|
|
|
- old_effective_current = self._effective(STORE_CURRENT)
|
|
|
+ old_effective_active = self._effective_active()
|
|
|
await self.store.async_set(STORE_CURRENT, backend_current)
|
|
|
await self.store.async_set(STORE_NEXT, backend_next)
|
|
|
await self._async_maybe_reset_arrival_flags(old_store_current, backend_current)
|
|
|
@@ -246,21 +253,22 @@ class RoomCalCoordinator(DataUpdateCoordinator[CoordinatorData]):
|
|
|
self._current_last_refresh = now
|
|
|
self._next_last_refresh = now
|
|
|
|
|
|
- # Diffing effective (not store) old vs. new naturally no-ops while an override
|
|
|
- # covers Current, since old == new == the override's own value in that case.
|
|
|
- new_effective_current = self._effective(STORE_CURRENT)
|
|
|
+ # Diffing effective active (not the store write) naturally no-ops while an
|
|
|
+ # override masks active, since old == new == the override's own value in that case.
|
|
|
+ new_effective_active = self._effective_active()
|
|
|
now_naive = dt_util.now().replace(tzinfo=None)
|
|
|
- diff = self._diff_with_unifi_check(old_effective_current, new_effective_current, now_naive)
|
|
|
+ diff = self._diff_with_unifi_check(old_effective_active, new_effective_active, now_naive)
|
|
|
await self._async_dispatch_diff(diff)
|
|
|
|
|
|
now = dt_util.utcnow()
|
|
|
- new_current = self._effective(STORE_CURRENT)
|
|
|
- new_next = self._effective(STORE_NEXT)
|
|
|
- await self._async_note_displayed_change(new_current, new_next, now)
|
|
|
- self._reschedule_mode_sync(new_current, new_next)
|
|
|
- return self._build_coordinator_data(new_current, new_next, api_status=api_status)
|
|
|
-
|
|
|
- def _reschedule_mode_sync(self, current: ReservationData, nxt: ReservationData) -> None:
|
|
|
+ new_active = self._effective_active()
|
|
|
+ new_current = self._current_mirror()
|
|
|
+ new_next = self._next_mirror()
|
|
|
+ await self._async_note_displayed_change(new_active, new_current, new_next, now)
|
|
|
+ self._reschedule_mode_sync(new_active, new_next)
|
|
|
+ return self._build_coordinator_data(new_active, new_current, new_next, api_status=api_status)
|
|
|
+
|
|
|
+ def _reschedule_mode_sync(self, active: ReservationData, nxt: ReservationData) -> None:
|
|
|
for cancel in self._mode_sync_handles:
|
|
|
cancel()
|
|
|
self._mode_sync_handles.clear()
|
|
|
@@ -270,7 +278,8 @@ class RoomCalCoordinator(DataUpdateCoordinator[CoordinatorData]):
|
|
|
|
|
|
now = dt_util.now().replace(tzinfo=None)
|
|
|
|
|
|
- # At next reservation's check-in: promote next → current, then activate guest mode.
|
|
|
+ # At next reservation's check-in: promote next → active, then activate guest mode.
|
|
|
+ # `nxt` is always the pure backend mirror — never masked by an override.
|
|
|
if nxt.check_in_date is not None and nxt.check_in_time is not None:
|
|
|
check_in = datetime.combine(nxt.check_in_date, nxt.check_in_time)
|
|
|
delay = (check_in - now).total_seconds()
|
|
|
@@ -279,9 +288,9 @@ class RoomCalCoordinator(DataUpdateCoordinator[CoordinatorData]):
|
|
|
async_call_later(self.hass, delay, self._async_handle_check_in)
|
|
|
)
|
|
|
|
|
|
- # At current reservation's check-out: clear current, then deactivate guest mode.
|
|
|
- if current.check_out_date is not None and current.check_out_time is not None:
|
|
|
- check_out = datetime.combine(current.check_out_date, current.check_out_time)
|
|
|
+ # At active reservation's check-out: clear active, then deactivate guest mode.
|
|
|
+ if active.check_out_date is not None and active.check_out_time is not None:
|
|
|
+ check_out = datetime.combine(active.check_out_date, active.check_out_time)
|
|
|
delay = (check_out - now).total_seconds()
|
|
|
if delay > 0:
|
|
|
self._mode_sync_handles.append(
|
|
|
@@ -368,7 +377,7 @@ class RoomCalCoordinator(DataUpdateCoordinator[CoordinatorData]):
|
|
|
Operates purely on the store — no override awareness needed, since this is
|
|
|
specifically "catch the store up to real time when nothing else has been polling
|
|
|
it," used at HA startup, when Mode Sync is toggled on, and (to catch drift in the
|
|
|
- store's own Current) when an override ends.
|
|
|
+ store's own current) when an override ends.
|
|
|
|
|
|
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
|
|
|
@@ -446,14 +455,14 @@ class RoomCalCoordinator(DataUpdateCoordinator[CoordinatorData]):
|
|
|
self.hass.async_create_task(self._async_do_check_in())
|
|
|
|
|
|
async def _async_do_check_in(self) -> None:
|
|
|
- await self.async_promote_next_to_current()
|
|
|
+ await self.async_promote_next_to_active()
|
|
|
|
|
|
@callback
|
|
|
def _async_handle_check_out(self, _now: datetime) -> None:
|
|
|
self.hass.async_create_task(self._async_do_check_out())
|
|
|
|
|
|
async def _async_do_check_out(self) -> None:
|
|
|
- await self.async_clear_current()
|
|
|
+ await self.async_clear_active()
|
|
|
|
|
|
@callback
|
|
|
def _async_handle_override_timeout(self, _now: datetime) -> None:
|
|
|
@@ -472,19 +481,17 @@ class RoomCalCoordinator(DataUpdateCoordinator[CoordinatorData]):
|
|
|
self._override_end_handle = None
|
|
|
self._override_end_target = None
|
|
|
|
|
|
- async def async_clear_current(self) -> None:
|
|
|
- """Clear whichever layer (override or store) currently owns Current."""
|
|
|
- diff = await self._async_transition(STORE_CURRENT, None)
|
|
|
- if diff:
|
|
|
- await self._async_dispatch_diff(diff)
|
|
|
+ async def async_clear_active(self) -> None:
|
|
|
+ """Clear whichever layer (override or store) currently owns active."""
|
|
|
+ diff = await self._async_transition_active(None)
|
|
|
+ await self._async_dispatch_diff(diff)
|
|
|
await self._async_publish()
|
|
|
|
|
|
- async def async_promote_next_to_current(self) -> None:
|
|
|
- """Copy Next into whichever layer owns Current. Next is left unchanged."""
|
|
|
- next_value = self._effective(STORE_NEXT)
|
|
|
- diff = await self._async_transition(STORE_CURRENT, next_value)
|
|
|
- if diff:
|
|
|
- await self._async_dispatch_diff(diff)
|
|
|
+ async def async_promote_next_to_active(self) -> None:
|
|
|
+ """Copy the (pure, backend-mirror) Next into whichever layer owns active."""
|
|
|
+ next_value = self._next_mirror()
|
|
|
+ diff = await self._async_transition_active(next_value)
|
|
|
+ await self._async_dispatch_diff(diff)
|
|
|
await self._async_publish()
|
|
|
|
|
|
async def async_start_override(self, override: OverrideData, *, end_at: datetime | None = None) -> None:
|
|
|
@@ -496,17 +503,14 @@ class RoomCalCoordinator(DataUpdateCoordinator[CoordinatorData]):
|
|
|
override replacing this one) cancels the timer.
|
|
|
"""
|
|
|
self._cancel_override_end_timer()
|
|
|
- old_current = self._effective(STORE_CURRENT)
|
|
|
+ old_active = self._effective_active()
|
|
|
self._override = override
|
|
|
- _LOGGER.info("Override started: guest=%s end_at=%s", override.current.guest_name, end_at)
|
|
|
- now = dt_util.utcnow()
|
|
|
- self._current_last_refresh = now
|
|
|
- if override.next is not None:
|
|
|
- self._next_last_refresh = now
|
|
|
- new_current = self._effective(STORE_CURRENT)
|
|
|
- await self._async_maybe_reset_arrival_flags(old_current, new_current)
|
|
|
+ _LOGGER.info("Override started: guest=%s end_at=%s", override.active.guest_name, end_at)
|
|
|
+ self._active_last_refresh = dt_util.utcnow()
|
|
|
+ new_active = self._effective_active()
|
|
|
+ await self._async_maybe_reset_arrival_flags(old_active, new_active)
|
|
|
now_naive = dt_util.now().replace(tzinfo=None)
|
|
|
- diff = self._diff_with_unifi_check(old_current, new_current, now_naive)
|
|
|
+ diff = self._diff_with_unifi_check(old_active, new_active, now_naive)
|
|
|
await self._async_dispatch_diff(diff)
|
|
|
await self._async_publish()
|
|
|
|
|
|
@@ -518,29 +522,43 @@ class RoomCalCoordinator(DataUpdateCoordinator[CoordinatorData]):
|
|
|
self.hass, delay, self._async_handle_override_timeout
|
|
|
)
|
|
|
|
|
|
+ async def async_ensure_override(self) -> None:
|
|
|
+ """Start a snapshot override (freezing `active` at its current value) if none is
|
|
|
+ active yet. Idempotent — a no-op if an override is already running. This is what
|
|
|
+ lets "turn the Override switch on" and "patch a field via service call" both mean
|
|
|
+ "stop auto-updating active" without needing separate machinery."""
|
|
|
+ if self._override is not None:
|
|
|
+ return
|
|
|
+ await self.async_start_override(OverrideData(active=self._effective_active()))
|
|
|
+
|
|
|
async def async_end_override(self) -> None:
|
|
|
"""End the active override: reconcile the store's own elapsed state, then diff back."""
|
|
|
self._cancel_override_end_timer()
|
|
|
if self._override is None:
|
|
|
return
|
|
|
- old_current = self._override.current
|
|
|
- _LOGGER.info("Override ended: guest=%s", old_current.guest_name)
|
|
|
+ old_active = self._override.active
|
|
|
+ _LOGGER.info("Override ended: guest=%s", old_active.guest_name)
|
|
|
await self._reconcile_store_elapsed()
|
|
|
self._override = None
|
|
|
- new_current = self._effective(STORE_CURRENT)
|
|
|
- await self._async_maybe_reset_arrival_flags(old_current, new_current)
|
|
|
+ new_active = self._effective_active()
|
|
|
+ await self._async_maybe_reset_arrival_flags(old_active, new_active)
|
|
|
now_naive = dt_util.now().replace(tzinfo=None)
|
|
|
- diff = self._diff_with_unifi_check(old_current, new_current, now_naive)
|
|
|
+ diff = self._diff_with_unifi_check(old_active, new_active, now_naive)
|
|
|
await self._async_dispatch_diff(diff)
|
|
|
await self._async_publish()
|
|
|
|
|
|
- async def async_apply_update(self, target: str, partial: dict[str, Any]) -> None:
|
|
|
- """Apply a thin update (service call) to whichever layer owns `target`."""
|
|
|
- _LOGGER.info("Patch applied: target=%s fields=%s", target, sorted(partial))
|
|
|
- new_value = self._effective(target).apply_partial(partial)
|
|
|
- diff = await self._async_transition(target, new_value)
|
|
|
- if diff:
|
|
|
- await self._async_dispatch_diff(diff)
|
|
|
+ async def async_apply_update(self, partial: dict[str, Any]) -> None:
|
|
|
+ """Apply a thin update (service call) to `active`.
|
|
|
+
|
|
|
+ If no override is running yet, one is started first (snapshotting `active` as-is)
|
|
|
+ so the patch can't be silently overwritten by the next backend poll or scheduled
|
|
|
+ transition — the same protection an explicit override gives.
|
|
|
+ """
|
|
|
+ _LOGGER.info("Patch applied: fields=%s", sorted(partial))
|
|
|
+ await self.async_ensure_override()
|
|
|
+ new_value = self._effective_active().apply_partial(partial)
|
|
|
+ diff = await self._async_transition_active(new_value)
|
|
|
+ await self._async_dispatch_diff(diff)
|
|
|
await self._async_publish()
|
|
|
|
|
|
def _diff_with_unifi_check(
|
|
|
@@ -601,22 +619,22 @@ class RoomCalCoordinator(DataUpdateCoordinator[CoordinatorData]):
|
|
|
new: ReservationData | None,
|
|
|
) -> None:
|
|
|
if self._arrival_identity(old) != self._arrival_identity(new):
|
|
|
- self._current_arrived = False
|
|
|
- self._current_departed = False
|
|
|
+ self._active_arrived = False
|
|
|
+ self._active_departed = False
|
|
|
await self.store.async_set_arrived(False)
|
|
|
await self.store.async_set_departed(False)
|
|
|
|
|
|
async def async_set_arrived(self, arrived: bool) -> None:
|
|
|
- self._current_arrived = arrived
|
|
|
+ self._active_arrived = arrived
|
|
|
await self.store.async_set_arrived(arrived)
|
|
|
if self.data is not None:
|
|
|
- self.async_set_updated_data(dataclass_replace(self.data, current_arrived=arrived))
|
|
|
+ self.async_set_updated_data(dataclass_replace(self.data, active_arrived=arrived))
|
|
|
|
|
|
async def async_set_departed(self, departed: bool) -> None:
|
|
|
- self._current_departed = departed
|
|
|
+ self._active_departed = departed
|
|
|
await self.store.async_set_departed(departed)
|
|
|
if self.data is not None:
|
|
|
- self.async_set_updated_data(dataclass_replace(self.data, current_departed=departed))
|
|
|
+ self.async_set_updated_data(dataclass_replace(self.data, active_departed=departed))
|
|
|
|
|
|
def cancel_mode_sync(self) -> None:
|
|
|
for cancel in self._mode_sync_handles:
|