Handle ArmDisarm execute without arm level (#36942)

This commit is contained in:
Paulus Schoutsen 2020-06-22 16:06:30 -07:00 committed by GitHub
parent 6660cf701d
commit becc011135
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 51 additions and 42 deletions

View file

@ -988,6 +988,14 @@ class ArmDisArmTrait(_Trait):
STATE_ALARM_TRIGGERED: SERVICE_ALARM_TRIGGER,
}
state_to_support = {
STATE_ALARM_ARMED_HOME: alarm_control_panel.const.SUPPORT_ALARM_ARM_HOME,
STATE_ALARM_ARMED_AWAY: alarm_control_panel.const.SUPPORT_ALARM_ARM_AWAY,
STATE_ALARM_ARMED_NIGHT: alarm_control_panel.const.SUPPORT_ALARM_ARM_NIGHT,
STATE_ALARM_ARMED_CUSTOM_BYPASS: alarm_control_panel.const.SUPPORT_ALARM_ARM_CUSTOM_BYPASS,
STATE_ALARM_TRIGGERED: alarm_control_panel.const.SUPPORT_ALARM_TRIGGER,
}
@staticmethod
def supported(domain, features, device_class):
"""Test if state is supported."""
@ -998,11 +1006,20 @@ class ArmDisArmTrait(_Trait):
"""Return if the trait might ask for 2FA."""
return True
def _supported_states(self):
"""Return supported states."""
features = self.state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
return [
state
for state, required_feature in self.state_to_support.items()
if features & required_feature != 0
]
def sync_attributes(self):
"""Return ArmDisarm attributes for a sync request."""
response = {}
levels = []
for state in self.state_to_service:
for state in self._supported_states():
# level synonyms are generated from state names
# 'armed_away' becomes 'armed away' or 'away'
level_synonym = [state.replace("_", " ")]
@ -1014,6 +1031,7 @@ class ArmDisArmTrait(_Trait):
"level_values": [{"level_synonym": level_synonym, "lang": "en"}],
}
levels.append(level)
response["availableArmLevels"] = {"levels": levels, "ordered": False}
return response
@ -1031,11 +1049,26 @@ class ArmDisArmTrait(_Trait):
async def execute(self, command, data, params, challenge):
"""Execute an ArmDisarm command."""
if params["arm"] and not params.get("cancel"):
if self.state.state == params["armLevel"]:
arm_level = params.get("armLevel")
# If no arm level given, we can only arm it if there is
# only one supported arm type. We never default to triggered.
if not arm_level:
states = self._supported_states()
if STATE_ALARM_TRIGGERED in states:
states.remove(STATE_ALARM_TRIGGERED)
if len(states) != 1:
raise SmartHomeError(ERR_NOT_SUPPORTED, "ArmLevel missing")
arm_level = states[0]
if self.state.state == arm_level:
raise SmartHomeError(ERR_ALREADY_ARMED, "System is already armed")
if self.state.attributes["code_arm_required"]:
_verify_pin_challenge(data, self.state, challenge)
service = self.state_to_service[params["armLevel"]]
service = self.state_to_service[arm_level]
# disarm the system without asking for code when
# 'cancel' arming action is received while current status is pending
elif (

View file

@ -873,7 +873,11 @@ async def test_arm_disarm_arm_away(hass):
State(
"alarm_control_panel.alarm",
STATE_ALARM_ARMED_AWAY,
{alarm_control_panel.ATTR_CODE_ARM_REQUIRED: True},
{
alarm_control_panel.ATTR_CODE_ARM_REQUIRED: True,
ATTR_SUPPORTED_FEATURES: alarm_control_panel.const.SUPPORT_ALARM_ARM_HOME
| alarm_control_panel.const.SUPPORT_ALARM_ARM_AWAY,
},
),
PIN_CONFIG,
)
@ -892,25 +896,6 @@ async def test_arm_disarm_arm_away(hass):
{"level_synonym": ["armed away", "away"], "lang": "en"}
],
},
{
"level_name": "armed_night",
"level_values": [
{"level_synonym": ["armed night", "night"], "lang": "en"}
],
},
{
"level_name": "armed_custom_bypass",
"level_values": [
{
"level_synonym": ["armed custom bypass", "custom"],
"lang": "en",
}
],
},
{
"level_name": "triggered",
"level_values": [{"level_synonym": ["triggered"], "lang": "en"}],
},
],
"ordered": False,
}
@ -1031,6 +1016,11 @@ async def test_arm_disarm_arm_away(hass):
)
assert len(calls) == 2
with pytest.raises(error.SmartHomeError) as err:
await trt.execute(
trait.COMMAND_ARMDISARM, PIN_DATA, {"arm": True}, {},
)
async def test_arm_disarm_disarm(hass):
"""Test ArmDisarm trait Disarming support for alarm_control_panel domain."""
@ -1043,31 +1033,17 @@ async def test_arm_disarm_disarm(hass):
State(
"alarm_control_panel.alarm",
STATE_ALARM_DISARMED,
{alarm_control_panel.ATTR_CODE_ARM_REQUIRED: True},
{
alarm_control_panel.ATTR_CODE_ARM_REQUIRED: True,
ATTR_SUPPORTED_FEATURES: alarm_control_panel.const.SUPPORT_ALARM_TRIGGER
| alarm_control_panel.const.SUPPORT_ALARM_ARM_CUSTOM_BYPASS,
},
),
PIN_CONFIG,
)
assert trt.sync_attributes() == {
"availableArmLevels": {
"levels": [
{
"level_name": "armed_home",
"level_values": [
{"level_synonym": ["armed home", "home"], "lang": "en"}
],
},
{
"level_name": "armed_away",
"level_values": [
{"level_synonym": ["armed away", "away"], "lang": "en"}
],
},
{
"level_name": "armed_night",
"level_values": [
{"level_synonym": ["armed night", "night"], "lang": "en"}
],
},
{
"level_name": "armed_custom_bypass",
"level_values": [