parent
bbd1a85b09
commit
6eb3307734
|
@ -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 = {
|
|
||||||
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(
|
self.char_current_state = serv_alarm.configure_char(
|
||||||
CHAR_CURRENT_SECURITY_STATE,
|
CHAR_CURRENT_SECURITY_STATE,
|
||||||
value=HASS_TO_HOMEKIT[STATE_ALARM_DISARMED],
|
value=HASS_TO_HOMEKIT_CURRENT[STATE_ALARM_DISARMED],
|
||||||
valid_values=new_current_states,
|
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(
|
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)
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Reference in New Issue