Fix ness alarm armed_home state appearing as disarmed/armed_away (#94351)

* Fix nessclient arm home appearing as arm away

* patch arming mode enum and use dynamic access

* Revert "patch arming mode enum and use dynamic access"

This reverts commit b9cca8e92bcb382abe364381a8cb1674c32d1d2a.

* Remove mock enums
This commit is contained in:
Nick Whyte 2023-08-16 21:56:52 +10:00 committed by GitHub
parent cf8c9ad184
commit 2c48f0e416
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 43 additions and 37 deletions

View file

@ -3,7 +3,7 @@ from collections import namedtuple
import datetime import datetime
import logging import logging
from nessclient import ArmingState, Client from nessclient import ArmingMode, ArmingState, Client
import voluptuous as vol import voluptuous as vol
from homeassistant.components.binary_sensor import ( from homeassistant.components.binary_sensor import (
@ -136,9 +136,11 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
hass, SIGNAL_ZONE_CHANGED, ZoneChangedData(zone_id=zone_id, state=state) hass, SIGNAL_ZONE_CHANGED, ZoneChangedData(zone_id=zone_id, state=state)
) )
def on_state_change(arming_state: ArmingState): def on_state_change(arming_state: ArmingState, arming_mode: ArmingMode | None):
"""Receives and propagates arming state updates.""" """Receives and propagates arming state updates."""
async_dispatcher_send(hass, SIGNAL_ARMING_STATE_CHANGED, arming_state) async_dispatcher_send(
hass, SIGNAL_ARMING_STATE_CHANGED, arming_state, arming_mode
)
client.on_zone_change(on_zone_change) client.on_zone_change(on_zone_change)
client.on_state_change(on_state_change) client.on_state_change(on_state_change)

View file

@ -3,12 +3,15 @@ from __future__ import annotations
import logging import logging
from nessclient import ArmingState, Client from nessclient import ArmingMode, ArmingState, Client
import homeassistant.components.alarm_control_panel as alarm import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.alarm_control_panel import AlarmControlPanelEntityFeature from homeassistant.components.alarm_control_panel import AlarmControlPanelEntityFeature
from homeassistant.const import ( from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_NIGHT,
STATE_ALARM_ARMED_VACATION,
STATE_ALARM_ARMING, STATE_ALARM_ARMING,
STATE_ALARM_DISARMED, STATE_ALARM_DISARMED,
STATE_ALARM_PENDING, STATE_ALARM_PENDING,
@ -23,6 +26,15 @@ from . import DATA_NESS, SIGNAL_ARMING_STATE_CHANGED
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
ARMING_MODE_TO_STATE = {
ArmingMode.ARMED_AWAY: STATE_ALARM_ARMED_AWAY,
ArmingMode.ARMED_HOME: STATE_ALARM_ARMED_HOME,
ArmingMode.ARMED_DAY: STATE_ALARM_ARMED_AWAY, # no applicable state, fallback to away
ArmingMode.ARMED_NIGHT: STATE_ALARM_ARMED_NIGHT,
ArmingMode.ARMED_VACATION: STATE_ALARM_ARMED_VACATION,
ArmingMode.ARMED_HIGHEST: STATE_ALARM_ARMED_AWAY, # no applicable state, fallback to away
}
async def async_setup_platform( async def async_setup_platform(
hass: HomeAssistant, hass: HomeAssistant,
@ -79,7 +91,9 @@ class NessAlarmPanel(alarm.AlarmControlPanelEntity):
await self._client.panic(code) await self._client.panic(code)
@callback @callback
def _handle_arming_state_change(self, arming_state: ArmingState) -> None: def _handle_arming_state_change(
self, arming_state: ArmingState, arming_mode: ArmingMode | None
) -> None:
"""Handle arming state update.""" """Handle arming state update."""
if arming_state == ArmingState.UNKNOWN: if arming_state == ArmingState.UNKNOWN:
@ -91,7 +105,9 @@ class NessAlarmPanel(alarm.AlarmControlPanelEntity):
elif arming_state == ArmingState.EXIT_DELAY: elif arming_state == ArmingState.EXIT_DELAY:
self._attr_state = STATE_ALARM_ARMING self._attr_state = STATE_ALARM_ARMING
elif arming_state == ArmingState.ARMED: elif arming_state == ArmingState.ARMED:
self._attr_state = STATE_ALARM_ARMED_AWAY self._attr_state = ARMING_MODE_TO_STATE.get(
arming_mode, STATE_ALARM_ARMED_AWAY
)
elif arming_state == ArmingState.ENTRY_DELAY: elif arming_state == ArmingState.ENTRY_DELAY:
self._attr_state = STATE_ALARM_PENDING self._attr_state = STATE_ALARM_PENDING
elif arming_state == ArmingState.TRIGGERED: elif arming_state == ArmingState.TRIGGERED:

View file

@ -5,5 +5,5 @@
"documentation": "https://www.home-assistant.io/integrations/ness_alarm", "documentation": "https://www.home-assistant.io/integrations/ness_alarm",
"iot_class": "local_push", "iot_class": "local_push",
"loggers": ["nessclient"], "loggers": ["nessclient"],
"requirements": ["nessclient==0.10.0"] "requirements": ["nessclient==1.0.0"]
} }

View file

@ -1240,7 +1240,7 @@ nad-receiver==0.3.0
ndms2-client==0.1.2 ndms2-client==0.1.2
# homeassistant.components.ness_alarm # homeassistant.components.ness_alarm
nessclient==0.10.0 nessclient==1.0.0
# homeassistant.components.netdata # homeassistant.components.netdata
netdata==1.1.0 netdata==1.1.0

View file

@ -951,7 +951,7 @@ mutesync==0.0.1
ndms2-client==0.1.2 ndms2-client==0.1.2
# homeassistant.components.ness_alarm # homeassistant.components.ness_alarm
nessclient==0.10.0 nessclient==1.0.0
# homeassistant.components.nmap_tracker # homeassistant.components.nmap_tracker
netmap==0.7.0.2 netmap==0.7.0.2

View file

@ -1,7 +1,7 @@
"""Tests for the ness_alarm component.""" """Tests for the ness_alarm component."""
from enum import Enum
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
from nessclient import ArmingMode, ArmingState
import pytest import pytest
from homeassistant.components import alarm_control_panel from homeassistant.components import alarm_control_panel
@ -24,6 +24,8 @@ from homeassistant.const import (
SERVICE_ALARM_DISARM, SERVICE_ALARM_DISARM,
SERVICE_ALARM_TRIGGER, SERVICE_ALARM_TRIGGER,
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_NIGHT,
STATE_ALARM_ARMING, STATE_ALARM_ARMING,
STATE_ALARM_DISARMED, STATE_ALARM_DISARMED,
STATE_ALARM_PENDING, STATE_ALARM_PENDING,
@ -84,7 +86,7 @@ async def test_dispatch_state_change(hass: HomeAssistant, mock_nessclient) -> No
await hass.async_block_till_done() await hass.async_block_till_done()
on_state_change = mock_nessclient.on_state_change.call_args[0][0] on_state_change = mock_nessclient.on_state_change.call_args[0][0]
on_state_change(MockArmingState.ARMING) on_state_change(ArmingState.ARMING, None)
await hass.async_block_till_done() await hass.async_block_till_done()
assert hass.states.is_state("alarm_control_panel.alarm_panel", STATE_ALARM_ARMING) assert hass.states.is_state("alarm_control_panel.alarm_panel", STATE_ALARM_ARMING)
@ -174,13 +176,16 @@ async def test_dispatch_zone_change(hass: HomeAssistant, mock_nessclient) -> Non
async def test_arming_state_change(hass: HomeAssistant, mock_nessclient) -> None: async def test_arming_state_change(hass: HomeAssistant, mock_nessclient) -> None:
"""Test arming state change handing.""" """Test arming state change handing."""
states = [ states = [
(MockArmingState.UNKNOWN, STATE_UNKNOWN), (ArmingState.UNKNOWN, None, STATE_UNKNOWN),
(MockArmingState.DISARMED, STATE_ALARM_DISARMED), (ArmingState.DISARMED, None, STATE_ALARM_DISARMED),
(MockArmingState.ARMING, STATE_ALARM_ARMING), (ArmingState.ARMING, None, STATE_ALARM_ARMING),
(MockArmingState.EXIT_DELAY, STATE_ALARM_ARMING), (ArmingState.EXIT_DELAY, None, STATE_ALARM_ARMING),
(MockArmingState.ARMED, STATE_ALARM_ARMED_AWAY), (ArmingState.ARMED, None, STATE_ALARM_ARMED_AWAY),
(MockArmingState.ENTRY_DELAY, STATE_ALARM_PENDING), (ArmingState.ARMED, ArmingMode.ARMED_AWAY, STATE_ALARM_ARMED_AWAY),
(MockArmingState.TRIGGERED, STATE_ALARM_TRIGGERED), (ArmingState.ARMED, ArmingMode.ARMED_HOME, STATE_ALARM_ARMED_HOME),
(ArmingState.ARMED, ArmingMode.ARMED_NIGHT, STATE_ALARM_ARMED_NIGHT),
(ArmingState.ENTRY_DELAY, None, STATE_ALARM_PENDING),
(ArmingState.TRIGGERED, None, STATE_ALARM_TRIGGERED),
] ]
await async_setup_component(hass, DOMAIN, VALID_CONFIG) await async_setup_component(hass, DOMAIN, VALID_CONFIG)
@ -188,24 +193,12 @@ async def test_arming_state_change(hass: HomeAssistant, mock_nessclient) -> None
assert hass.states.is_state("alarm_control_panel.alarm_panel", STATE_UNKNOWN) assert hass.states.is_state("alarm_control_panel.alarm_panel", STATE_UNKNOWN)
on_state_change = mock_nessclient.on_state_change.call_args[0][0] on_state_change = mock_nessclient.on_state_change.call_args[0][0]
for arming_state, expected_state in states: for arming_state, arming_mode, expected_state in states:
on_state_change(arming_state) on_state_change(arming_state, arming_mode)
await hass.async_block_till_done() await hass.async_block_till_done()
assert hass.states.is_state("alarm_control_panel.alarm_panel", expected_state) assert hass.states.is_state("alarm_control_panel.alarm_panel", expected_state)
class MockArmingState(Enum):
"""Mock nessclient.ArmingState enum."""
UNKNOWN = "UNKNOWN"
DISARMED = "DISARMED"
ARMING = "ARMING"
EXIT_DELAY = "EXIT_DELAY"
ARMED = "ARMED"
ENTRY_DELAY = "ENTRY_DELAY"
TRIGGERED = "TRIGGERED"
class MockClient: class MockClient:
"""Mock nessclient.Client stub.""" """Mock nessclient.Client stub."""
@ -253,10 +246,5 @@ def mock_nessclient():
with patch( with patch(
"homeassistant.components.ness_alarm.Client", new=_mock_factory, create=True "homeassistant.components.ness_alarm.Client", new=_mock_factory, create=True
), patch(
"homeassistant.components.ness_alarm.ArmingState", new=MockArmingState
), patch(
"homeassistant.components.ness_alarm.alarm_control_panel.ArmingState",
new=MockArmingState,
): ):
yield _mock_instance yield _mock_instance