Skip to content

Commit

Permalink
fix(homekit): patch HomeKit ARMING_STATE to prevent ARMED_AWAY transi…
Browse files Browse the repository at this point in the history
…tion
  • Loading branch information
palazzem committed Mar 6, 2024
1 parent 92eec34 commit e4fd86d
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 4 deletions.
7 changes: 7 additions & 0 deletions custom_components/econnect_metronet/const.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
"""Constants for the E-connect Alarm integration."""

from elmo import systems as s
from homeassistant.components.homekit.type_security_systems import (
HASS_TO_HOMEKIT_TARGET,
)
from homeassistant.const import STATE_ALARM_ARMING

SUPPORTED_SYSTEMS = {
s.ELMO_E_CONNECT: "Elmo e-Connect",
Expand Down Expand Up @@ -36,3 +40,6 @@
# Experimental Settings
CONF_EXPERIMENTAL = "experimental"
CONF_FORCE_UPDATE = "force_update"

# HomeKit Arming State
HK_ARMING_STATE = HASS_TO_HOMEKIT_TARGET.get(STATE_ALARM_ARMING)
22 changes: 20 additions & 2 deletions custom_components/econnect_metronet/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@
import logging

from elmo.api.exceptions import CodeError, LockError
from homeassistant.components.homekit.type_security_systems import (
HASS_TO_HOMEKIT_CURRENT,
HASS_TO_HOMEKIT_TARGET,
)
from homeassistant.const import STATE_ALARM_ARMING

from .const import HK_ARMING_STATE

_LOGGER = logging.getLogger(__name__)

Expand All @@ -19,11 +26,18 @@ def set_device_state(new_state, loader_state):
def decorator(func):
@functools.wraps(func)
async def func_wrapper(*args, **kwargs):
# Set loader state
self = args[0]
previous_state = self._device.state
self._device.state = loader_state
self.async_write_ha_state()

# Set HomeKit transition to the ending state
# NOTE: this is a workaround required to avoid a bug in the HomeKit component,
# where the device moves from PREV_STATE -> AWAY -> NEXT_STATE.
HASS_TO_HOMEKIT_TARGET[STATE_ALARM_ARMING] = HASS_TO_HOMEKIT_CURRENT.get(new_state)

try:
self.async_write_ha_state()
result = await func(*args, **kwargs)
self._device.state = new_state
self.async_write_ha_state()
Expand All @@ -34,7 +48,11 @@ async def func_wrapper(*args, **kwargs):
)
except CodeError:
_LOGGER.warning("Inserted code is not correct. Retry.")
# Reverting the state in case of any error
finally:
# Restore the original HomeKit arming state
HASS_TO_HOMEKIT_TARGET[STATE_ALARM_ARMING] = HK_ARMING_STATE

# Reverting previous state in case of errors
self._device.state = previous_state
self.async_write_ha_state()

Expand Down
44 changes: 42 additions & 2 deletions tests/test_decorators.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import pytest
from elmo.api.exceptions import CodeError, LockError
from homeassistant.components.homekit.type_security_systems import (
HASS_TO_HOMEKIT_TARGET,
HK_ALARM_AWAY_ARMED,
HK_ALARM_STAY_ARMED,
)
from homeassistant.const import STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMING

from custom_components.econnect_metronet.decorators import set_device_state

Expand All @@ -10,10 +16,10 @@ async def test_set_device_state_successful(panel):

@set_device_state("new_state", "loader_state")
async def test_func(self):
pass
return "value"

# Test
await test_func(panel)
assert await test_func(panel) == "value"
assert panel._device.state == "new_state"


Expand Down Expand Up @@ -56,3 +62,37 @@ async def test_func(self):

# Run test
await test_func(panel)


@pytest.mark.asyncio
async def test_set_device_state_homekit_transition(mocker, panel):
# Ensure HomeKit doesn't transition to ARM_AWAY state
# Regression test for: https://github.com/palazzem/ha-econnect-alarm/issues/154

def async_write_ha_state():
assert HASS_TO_HOMEKIT_TARGET[STATE_ALARM_ARMING] == HK_ALARM_STAY_ARMED

@set_device_state(STATE_ALARM_ARMED_HOME, "loader_state")
async def test_func(self):
assert HASS_TO_HOMEKIT_TARGET[STATE_ALARM_ARMING] == HK_ALARM_STAY_ARMED

mocker.patch.object(panel, "async_write_ha_state", side_effect=async_write_ha_state)

# Test
await test_func(panel) == "value"
assert HASS_TO_HOMEKIT_TARGET[STATE_ALARM_ARMING] == HK_ALARM_AWAY_ARMED


@pytest.mark.asyncio
async def test_set_device_state_homekit_restore(panel):
# Ensure HomeKit target transition is restored if an exception happens
# Regression test for: https://github.com/palazzem/ha-econnect-alarm/issues/154

@set_device_state(STATE_ALARM_ARMED_HOME, "loader_state")
async def test_func(self):
raise Exception()

# Test
with pytest.raises(Exception):
await test_func(panel)
assert HASS_TO_HOMEKIT_TARGET[STATE_ALARM_ARMING] == HK_ALARM_AWAY_ARMED

0 comments on commit e4fd86d

Please sign in to comment.