Handle ArmDisarm execute without arm level (#36942)
This commit is contained in:
parent
6660cf701d
commit
becc011135
2 changed files with 51 additions and 42 deletions
|
@ -988,6 +988,14 @@ class ArmDisArmTrait(_Trait):
|
||||||
STATE_ALARM_TRIGGERED: SERVICE_ALARM_TRIGGER,
|
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
|
@staticmethod
|
||||||
def supported(domain, features, device_class):
|
def supported(domain, features, device_class):
|
||||||
"""Test if state is supported."""
|
"""Test if state is supported."""
|
||||||
|
@ -998,11 +1006,20 @@ class ArmDisArmTrait(_Trait):
|
||||||
"""Return if the trait might ask for 2FA."""
|
"""Return if the trait might ask for 2FA."""
|
||||||
return True
|
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):
|
def sync_attributes(self):
|
||||||
"""Return ArmDisarm attributes for a sync request."""
|
"""Return ArmDisarm attributes for a sync request."""
|
||||||
response = {}
|
response = {}
|
||||||
levels = []
|
levels = []
|
||||||
for state in self.state_to_service:
|
for state in self._supported_states():
|
||||||
# level synonyms are generated from state names
|
# level synonyms are generated from state names
|
||||||
# 'armed_away' becomes 'armed away' or 'away'
|
# 'armed_away' becomes 'armed away' or 'away'
|
||||||
level_synonym = [state.replace("_", " ")]
|
level_synonym = [state.replace("_", " ")]
|
||||||
|
@ -1014,6 +1031,7 @@ class ArmDisArmTrait(_Trait):
|
||||||
"level_values": [{"level_synonym": level_synonym, "lang": "en"}],
|
"level_values": [{"level_synonym": level_synonym, "lang": "en"}],
|
||||||
}
|
}
|
||||||
levels.append(level)
|
levels.append(level)
|
||||||
|
|
||||||
response["availableArmLevels"] = {"levels": levels, "ordered": False}
|
response["availableArmLevels"] = {"levels": levels, "ordered": False}
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
@ -1031,11 +1049,26 @@ class ArmDisArmTrait(_Trait):
|
||||||
async def execute(self, command, data, params, challenge):
|
async def execute(self, command, data, params, challenge):
|
||||||
"""Execute an ArmDisarm command."""
|
"""Execute an ArmDisarm command."""
|
||||||
if params["arm"] and not params.get("cancel"):
|
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")
|
raise SmartHomeError(ERR_ALREADY_ARMED, "System is already armed")
|
||||||
if self.state.attributes["code_arm_required"]:
|
if self.state.attributes["code_arm_required"]:
|
||||||
_verify_pin_challenge(data, self.state, challenge)
|
_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
|
# disarm the system without asking for code when
|
||||||
# 'cancel' arming action is received while current status is pending
|
# 'cancel' arming action is received while current status is pending
|
||||||
elif (
|
elif (
|
||||||
|
|
|
@ -873,7 +873,11 @@ async def test_arm_disarm_arm_away(hass):
|
||||||
State(
|
State(
|
||||||
"alarm_control_panel.alarm",
|
"alarm_control_panel.alarm",
|
||||||
STATE_ALARM_ARMED_AWAY,
|
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,
|
PIN_CONFIG,
|
||||||
)
|
)
|
||||||
|
@ -892,25 +896,6 @@ async def test_arm_disarm_arm_away(hass):
|
||||||
{"level_synonym": ["armed away", "away"], "lang": "en"}
|
{"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,
|
"ordered": False,
|
||||||
}
|
}
|
||||||
|
@ -1031,6 +1016,11 @@ async def test_arm_disarm_arm_away(hass):
|
||||||
)
|
)
|
||||||
assert len(calls) == 2
|
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):
|
async def test_arm_disarm_disarm(hass):
|
||||||
"""Test ArmDisarm trait Disarming support for alarm_control_panel domain."""
|
"""Test ArmDisarm trait Disarming support for alarm_control_panel domain."""
|
||||||
|
@ -1043,31 +1033,17 @@ async def test_arm_disarm_disarm(hass):
|
||||||
State(
|
State(
|
||||||
"alarm_control_panel.alarm",
|
"alarm_control_panel.alarm",
|
||||||
STATE_ALARM_DISARMED,
|
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,
|
PIN_CONFIG,
|
||||||
)
|
)
|
||||||
assert trt.sync_attributes() == {
|
assert trt.sync_attributes() == {
|
||||||
"availableArmLevels": {
|
"availableArmLevels": {
|
||||||
"levels": [
|
"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_name": "armed_custom_bypass",
|
||||||
"level_values": [
|
"level_values": [
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue