Fix invalid homekit state when arming (#53646)

- Maybe fixes #48538
pull/53666/head
J. Nick Koston 2021-07-28 18:09:49 -05:00 committed by GitHub
parent bbd1a85b09
commit 6eb3307734
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 127 additions and 77 deletions

View File

@ -2,13 +2,13 @@
import logging
from pyhap.const import CATEGORY_ALARM_SYSTEM
from pyhap.loader import get_loader
from homeassistant.components.alarm_control_panel import DOMAIN
from homeassistant.components.alarm_control_panel.const import (
SUPPORT_ALARM_ARM_AWAY,
SUPPORT_ALARM_ARM_HOME,
SUPPORT_ALARM_ARM_NIGHT,
SUPPORT_ALARM_ARM_VACATION,
SUPPORT_ALARM_TRIGGER,
)
from homeassistant.const import (
@ -22,6 +22,8 @@ from homeassistant.const import (
STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_NIGHT,
STATE_ALARM_ARMED_VACATION,
STATE_ALARM_ARMING,
STATE_ALARM_DISARMED,
STATE_ALARM_TRIGGERED,
)
@ -36,28 +38,43 @@ from .const import (
_LOGGER = logging.getLogger(__name__)
HASS_TO_HOMEKIT = {
STATE_ALARM_ARMED_HOME: 0,
STATE_ALARM_ARMED_AWAY: 1,
STATE_ALARM_ARMED_NIGHT: 2,
STATE_ALARM_DISARMED: 3,
STATE_ALARM_TRIGGERED: 4,
HK_ALARM_STAY_ARMED = 0
HK_ALARM_AWAY_ARMED = 1
HK_ALARM_NIGHT_ARMED = 2
HK_ALARM_DISARMED = 3
HK_ALARM_TRIGGERED = 4
HASS_TO_HOMEKIT_CURRENT = {
STATE_ALARM_ARMED_HOME: HK_ALARM_STAY_ARMED,
STATE_ALARM_ARMED_VACATION: HK_ALARM_AWAY_ARMED,
STATE_ALARM_ARMED_AWAY: HK_ALARM_AWAY_ARMED,
STATE_ALARM_ARMED_NIGHT: HK_ALARM_NIGHT_ARMED,
STATE_ALARM_ARMING: HK_ALARM_DISARMED,
STATE_ALARM_DISARMED: HK_ALARM_DISARMED,
STATE_ALARM_TRIGGERED: HK_ALARM_TRIGGERED,
}
HASS_TO_HOMEKIT_TARGET = {
STATE_ALARM_ARMED_HOME: HK_ALARM_STAY_ARMED,
STATE_ALARM_ARMED_VACATION: HK_ALARM_AWAY_ARMED,
STATE_ALARM_ARMED_AWAY: HK_ALARM_AWAY_ARMED,
STATE_ALARM_ARMED_NIGHT: HK_ALARM_NIGHT_ARMED,
STATE_ALARM_ARMING: HK_ALARM_AWAY_ARMED,
STATE_ALARM_DISARMED: HK_ALARM_DISARMED,
}
HASS_TO_HOMEKIT_SERVICES = {
SERVICE_ALARM_ARM_HOME: 0,
SERVICE_ALARM_ARM_AWAY: 1,
SERVICE_ALARM_ARM_NIGHT: 2,
SERVICE_ALARM_DISARM: 3,
SERVICE_ALARM_ARM_HOME: HK_ALARM_STAY_ARMED,
SERVICE_ALARM_ARM_AWAY: HK_ALARM_AWAY_ARMED,
SERVICE_ALARM_ARM_NIGHT: HK_ALARM_NIGHT_ARMED,
SERVICE_ALARM_DISARM: HK_ALARM_DISARMED,
}
HOMEKIT_TO_HASS = {c: s for s, c in HASS_TO_HOMEKIT.items()}
STATE_TO_SERVICE = {
STATE_ALARM_ARMED_AWAY: SERVICE_ALARM_ARM_AWAY,
STATE_ALARM_ARMED_HOME: SERVICE_ALARM_ARM_HOME,
STATE_ALARM_ARMED_NIGHT: SERVICE_ALARM_ARM_NIGHT,
STATE_ALARM_DISARMED: SERVICE_ALARM_DISARM,
HK_TO_SERVICE = {
HK_ALARM_AWAY_ARMED: SERVICE_ALARM_ARM_AWAY,
HK_ALARM_STAY_ARMED: SERVICE_ALARM_ARM_HOME,
HK_ALARM_NIGHT_ARMED: SERVICE_ALARM_ARM_NIGHT,
HK_ALARM_DISARMED: SERVICE_ALARM_DISARM,
}
@ -75,65 +92,51 @@ class SecuritySystem(HomeAccessory):
ATTR_SUPPORTED_FEATURES,
(
SUPPORT_ALARM_ARM_HOME
| SUPPORT_ALARM_ARM_VACATION
| SUPPORT_ALARM_ARM_AWAY
| SUPPORT_ALARM_ARM_NIGHT
| SUPPORT_ALARM_TRIGGER
),
)
loader = get_loader()
default_current_states = loader.get_char(
"SecuritySystemCurrentState"
).properties.get("ValidValues")
default_target_services = loader.get_char(
"SecuritySystemTargetState"
).properties.get("ValidValues")
serv_alarm = self.add_preload_service(SERV_SECURITY_SYSTEM)
current_char = serv_alarm.get_characteristic(CHAR_CURRENT_SECURITY_STATE)
target_char = serv_alarm.get_characteristic(CHAR_TARGET_SECURITY_STATE)
default_current_states = current_char.properties.get("ValidValues")
default_target_services = target_char.properties.get("ValidValues")
current_supported_states = [
HASS_TO_HOMEKIT[STATE_ALARM_DISARMED],
HASS_TO_HOMEKIT[STATE_ALARM_TRIGGERED],
]
target_supported_services = [HASS_TO_HOMEKIT_SERVICES[SERVICE_ALARM_DISARM]]
current_supported_states = [HK_ALARM_DISARMED, HK_ALARM_TRIGGERED]
target_supported_services = [HK_ALARM_DISARMED]
if supported_states & SUPPORT_ALARM_ARM_HOME:
current_supported_states.append(HASS_TO_HOMEKIT[STATE_ALARM_ARMED_HOME])
target_supported_services.append(
HASS_TO_HOMEKIT_SERVICES[SERVICE_ALARM_ARM_HOME]
)
current_supported_states.append(HK_ALARM_STAY_ARMED)
target_supported_services.append(HK_ALARM_STAY_ARMED)
if supported_states & SUPPORT_ALARM_ARM_AWAY:
current_supported_states.append(HASS_TO_HOMEKIT[STATE_ALARM_ARMED_AWAY])
target_supported_services.append(
HASS_TO_HOMEKIT_SERVICES[SERVICE_ALARM_ARM_AWAY]
)
if supported_states & (SUPPORT_ALARM_ARM_AWAY | SUPPORT_ALARM_ARM_VACATION):
current_supported_states.append(HK_ALARM_AWAY_ARMED)
target_supported_services.append(HK_ALARM_AWAY_ARMED)
if supported_states & SUPPORT_ALARM_ARM_NIGHT:
current_supported_states.append(HASS_TO_HOMEKIT[STATE_ALARM_ARMED_NIGHT])
target_supported_services.append(
HASS_TO_HOMEKIT_SERVICES[SERVICE_ALARM_ARM_NIGHT]
)
current_supported_states.append(HK_ALARM_NIGHT_ARMED)
target_supported_services.append(HK_ALARM_NIGHT_ARMED)
new_current_states = {
key: val
for key, val in default_current_states.items()
if val in current_supported_states
}
new_target_services = {
key: val
for key, val in default_target_services.items()
if val in target_supported_services
}
serv_alarm = self.add_preload_service(SERV_SECURITY_SYSTEM)
self.char_current_state = serv_alarm.configure_char(
CHAR_CURRENT_SECURITY_STATE,
value=HASS_TO_HOMEKIT[STATE_ALARM_DISARMED],
valid_values=new_current_states,
value=HASS_TO_HOMEKIT_CURRENT[STATE_ALARM_DISARMED],
valid_values={
key: val
for key, val in default_current_states.items()
if val in current_supported_states
},
)
self.char_target_state = serv_alarm.configure_char(
CHAR_TARGET_SECURITY_STATE,
value=HASS_TO_HOMEKIT_SERVICES[SERVICE_ALARM_DISARM],
valid_values=new_target_services,
valid_values={
key: val
for key, val in default_target_services.items()
if val in target_supported_services
},
setter_callback=self.set_security_state,
)
@ -144,9 +147,7 @@ class SecuritySystem(HomeAccessory):
def set_security_state(self, value):
"""Move security state to value if call came from HomeKit."""
_LOGGER.debug("%s: Set security state to %d", self.entity_id, value)
hass_value = HOMEKIT_TO_HASS[value]
service = STATE_TO_SERVICE[hass_value]
service = HK_TO_SERVICE[value]
params = {ATTR_ENTITY_ID: self.entity_id}
if self._alarm_code:
params[ATTR_CODE] = self._alarm_code
@ -156,20 +157,16 @@ class SecuritySystem(HomeAccessory):
def async_update_state(self, new_state):
"""Update security state after state changed."""
hass_state = new_state.state
if hass_state in HASS_TO_HOMEKIT:
current_security_state = HASS_TO_HOMEKIT[hass_state]
if self.char_current_state.value != current_security_state:
self.char_current_state.set_value(current_security_state)
if (current_state := HASS_TO_HOMEKIT_CURRENT.get(hass_state)) is not None:
if self.char_current_state.value != current_state:
self.char_current_state.set_value(current_state)
_LOGGER.debug(
"%s: Updated current state to %s (%d)",
self.entity_id,
hass_state,
current_security_state,
current_state,
)
# SecuritySystemTargetState does not support triggered
if (
hass_state != STATE_ALARM_TRIGGERED
and self.char_target_state.value != current_security_state
):
self.char_target_state.set_value(current_security_state)
if (target_state := HASS_TO_HOMEKIT_TARGET.get(hass_state)) is not None:
if self.char_target_state.value != target_state:
self.char_target_state.set_value(target_state)

View File

@ -17,6 +17,8 @@ from homeassistant.const import (
STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_NIGHT,
STATE_ALARM_ARMED_VACATION,
STATE_ALARM_ARMING,
STATE_ALARM_DISARMED,
STATE_ALARM_TRIGGERED,
STATE_UNKNOWN,
@ -79,7 +81,7 @@ async def test_switch_set_state(hass, hk_driver, events):
call_arm_night = async_mock_service(hass, DOMAIN, "alarm_arm_night")
call_disarm = async_mock_service(hass, DOMAIN, "alarm_disarm")
await hass.async_add_executor_job(acc.char_target_state.client_update_value, 0)
acc.char_target_state.client_update_value(0)
await hass.async_block_till_done()
assert call_arm_home
assert call_arm_home[0].data[ATTR_ENTITY_ID] == entity_id
@ -88,7 +90,7 @@ async def test_switch_set_state(hass, hk_driver, events):
assert len(events) == 1
assert events[-1].data[ATTR_VALUE] is None
await hass.async_add_executor_job(acc.char_target_state.client_update_value, 1)
acc.char_target_state.client_update_value(1)
await hass.async_block_till_done()
assert call_arm_away
assert call_arm_away[0].data[ATTR_ENTITY_ID] == entity_id
@ -97,7 +99,7 @@ async def test_switch_set_state(hass, hk_driver, events):
assert len(events) == 2
assert events[-1].data[ATTR_VALUE] is None
await hass.async_add_executor_job(acc.char_target_state.client_update_value, 2)
acc.char_target_state.client_update_value(2)
await hass.async_block_till_done()
assert call_arm_night
assert call_arm_night[0].data[ATTR_ENTITY_ID] == entity_id
@ -106,7 +108,7 @@ async def test_switch_set_state(hass, hk_driver, events):
assert len(events) == 3
assert events[-1].data[ATTR_VALUE] is None
await hass.async_add_executor_job(acc.char_target_state.client_update_value, 3)
acc.char_target_state.client_update_value(3)
await hass.async_block_till_done()
assert call_disarm
assert call_disarm[0].data[ATTR_ENTITY_ID] == entity_id
@ -128,7 +130,7 @@ async def test_no_alarm_code(hass, hk_driver, config, events):
# Set from HomeKit
call_arm_home = async_mock_service(hass, DOMAIN, "alarm_arm_home")
await hass.async_add_executor_job(acc.char_target_state.client_update_value, 0)
acc.char_target_state.client_update_value(0)
await hass.async_block_till_done()
assert call_arm_home
assert call_arm_home[0].data[ATTR_ENTITY_ID] == entity_id
@ -138,6 +140,57 @@ async def test_no_alarm_code(hass, hk_driver, config, events):
assert events[-1].data[ATTR_VALUE] is None
async def test_arming(hass, hk_driver, events):
"""Test to make sure arming sets the right state."""
entity_id = "alarm_control_panel.test"
hass.states.async_set(entity_id, None)
acc = SecuritySystem(hass, hk_driver, "SecuritySystem", entity_id, 2, {})
await acc.run()
await hass.async_block_till_done()
hass.states.async_set(entity_id, STATE_ALARM_ARMED_AWAY)
await hass.async_block_till_done()
assert acc.char_target_state.value == 1
assert acc.char_current_state.value == 1
hass.states.async_set(entity_id, STATE_ALARM_ARMED_HOME)
await hass.async_block_till_done()
assert acc.char_target_state.value == 0
assert acc.char_current_state.value == 0
hass.states.async_set(entity_id, STATE_ALARM_ARMED_VACATION)
await hass.async_block_till_done()
assert acc.char_target_state.value == 1
assert acc.char_current_state.value == 1
hass.states.async_set(entity_id, STATE_ALARM_ARMED_NIGHT)
await hass.async_block_till_done()
assert acc.char_target_state.value == 2
assert acc.char_current_state.value == 2
hass.states.async_set(entity_id, STATE_ALARM_ARMING)
await hass.async_block_till_done()
assert acc.char_target_state.value == 1
assert acc.char_current_state.value == 3
hass.states.async_set(entity_id, STATE_ALARM_DISARMED)
await hass.async_block_till_done()
assert acc.char_target_state.value == 3
assert acc.char_current_state.value == 3
hass.states.async_set(entity_id, STATE_ALARM_ARMED_AWAY)
await hass.async_block_till_done()
assert acc.char_target_state.value == 1
assert acc.char_current_state.value == 1
hass.states.async_set(entity_id, STATE_ALARM_TRIGGERED)
await hass.async_block_till_done()
assert acc.char_target_state.value == 1
assert acc.char_current_state.value == 4
async def test_supported_states(hass, hk_driver, events):
"""Test different supported states."""
code = "1234"