diff --git a/nessclient/alarm.py b/nessclient/alarm.py index 88c1737..53832f5 100644 --- a/nessclient/alarm.py +++ b/nessclient/alarm.py @@ -2,7 +2,7 @@ from enum import Enum from typing import Optional, Callable, List -from .event import BaseEvent, ZoneUpdate, SystemStatusEvent +from .event import BaseEvent, ZoneUpdate, ArmingUpdate, SystemStatusEvent class ArmingState(Enum): @@ -35,21 +35,37 @@ class Zone: triggered: Optional[bool] def __init__(self) -> None: - # TODO: Determine the best way to query the alarm initial state. For - # now, we assume the alarm is initially disarmed on connection. - self.arming_state: ArmingState = ArmingState.DISARMED + self.arming_state: ArmingState = ArmingState.UNKNOWN self.zones: List[Alarm.Zone] = [Alarm.Zone(triggered=None) for _ in range(16)] self._on_state_change: Optional[Callable[['ArmingState'], None]] = None self._on_zone_change: Optional[Callable[[int, bool], None]] = None def handle_event(self, event: BaseEvent) -> None: - if (isinstance(event, ZoneUpdate) - and event.request_id == ZoneUpdate.RequestID.ZONE_INPUT_UNSEALED): + if isinstance(event, ArmingUpdate): + self._handle_arming_update(event) + elif (isinstance(event, ZoneUpdate) + and event.request_id == ZoneUpdate.RequestID.ZONE_INPUT_UNSEALED): self._handle_zone_input_update(event) elif isinstance(event, SystemStatusEvent): self._handle_system_status_event(event) + def _handle_arming_update(self, update: ArmingUpdate) -> None: + if update.status == [ArmingUpdate.ArmingStatus.MANUAL_EXCLUDE_MODE]: + return self._update_arming_state(ArmingState.EXIT_DELAY) + if ArmingUpdate.ArmingStatus.MANUAL_EXCLUDE_MODE in update.status and \ + ArmingUpdate.ArmingStatus.DAY_ZONE_SELECT: + # TODO(NW): This might not be the correct condition for an "armed" + # system, in fact, the same states are shown throughout armed, and + # entry delay. We should determine a better way to query the + # current alarm state. + return self._update_arming_state(ArmingState.ARMED) + elif self.arming_state == ArmingState.UNKNOWN: + # TODO(NW): Initially update to disarmed when the state is unknown. + # In the future, it would be ideal to infer other states by making + # calls to other status commands (i.e. zones in delay). + return self._update_arming_state(ArmingState.DISARMED) + def _handle_zone_input_update(self, update: ZoneUpdate) -> None: for i, zone in enumerate(self.zones): zone_id = i + 1 diff --git a/nessclient/cli/server/alarm_server.py b/nessclient/cli/server/alarm_server.py index 20011e7..02fef8b 100644 --- a/nessclient/cli/server/alarm_server.py +++ b/nessclient/cli/server/alarm_server.py @@ -146,7 +146,10 @@ def get_arming_status(state: Alarm.ArmingState) -> List[ArmingUpdate.ArmingStatu ArmingUpdate.ArmingStatus.MANUAL_EXCLUDE_MODE, ArmingUpdate.ArmingStatus.DAY_ZONE_SELECT ] - return [] + elif state == Alarm.ArmingState.EXIT_DELAY: + return [ArmingUpdate.ArmingStatus.MANUAL_EXCLUDE_MODE] + else: + return [] def toggled_state(state: Zone.State) -> Zone.State: diff --git a/nessclient/client.py b/nessclient/client.py index af06908..63ce2de 100644 --- a/nessclient/client.py +++ b/nessclient/client.py @@ -59,10 +59,12 @@ async def aux(self, output_id: int, state: bool = True) -> None: return await self.send_command(command) async def update(self) -> None: - """Force update of zones""" + """Force update of alarm status and zones""" await asyncio.gather( # List unsealed Zones self.send_command('S00'), + # Arming status update + self.send_command('S14'), ) async def _connect(self) -> None: @@ -130,7 +132,7 @@ async def _update_loop(self) -> None: await asyncio.sleep(self._update_interval) while not self._closed: _LOGGER.debug("Forcing a keepalive state update") - await self.update() + await self.send_command('S00') # List unsealed Zones await asyncio.sleep(self._update_interval) async def keepalive(self) -> None: