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

View File

@ -17,6 +17,8 @@ from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_NIGHT, STATE_ALARM_ARMED_NIGHT,
STATE_ALARM_ARMED_VACATION,
STATE_ALARM_ARMING,
STATE_ALARM_DISARMED, STATE_ALARM_DISARMED,
STATE_ALARM_TRIGGERED, STATE_ALARM_TRIGGERED,
STATE_UNKNOWN, 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_arm_night = async_mock_service(hass, DOMAIN, "alarm_arm_night")
call_disarm = async_mock_service(hass, DOMAIN, "alarm_disarm") 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() await hass.async_block_till_done()
assert call_arm_home assert call_arm_home
assert call_arm_home[0].data[ATTR_ENTITY_ID] == entity_id 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 len(events) == 1
assert events[-1].data[ATTR_VALUE] is None 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() await hass.async_block_till_done()
assert call_arm_away assert call_arm_away
assert call_arm_away[0].data[ATTR_ENTITY_ID] == entity_id 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 len(events) == 2
assert events[-1].data[ATTR_VALUE] is None 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() await hass.async_block_till_done()
assert call_arm_night assert call_arm_night
assert call_arm_night[0].data[ATTR_ENTITY_ID] == entity_id 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 len(events) == 3
assert events[-1].data[ATTR_VALUE] is None 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() await hass.async_block_till_done()
assert call_disarm assert call_disarm
assert call_disarm[0].data[ATTR_ENTITY_ID] == entity_id 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 # Set from HomeKit
call_arm_home = async_mock_service(hass, DOMAIN, "alarm_arm_home") 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() await hass.async_block_till_done()
assert call_arm_home assert call_arm_home
assert call_arm_home[0].data[ATTR_ENTITY_ID] == entity_id 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 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): async def test_supported_states(hass, hk_driver, events):
"""Test different supported states.""" """Test different supported states."""
code = "1234" code = "1234"