mirror of
https://github.com/home-assistant/core.git
synced 2025-11-14 21:40:16 +00:00
Add google_assistant alarm_control_panel (#26249)
* add alarm_control_panel to google_assistant * add cancel arming option * raise error if requested state is same as current * rework executing command logic * Add tests * fixed tests * fixed level synonyms
This commit is contained in:
committed by
Paulus Schoutsen
parent
4582b6e668
commit
d1adb28c6b
@@ -16,6 +16,7 @@ from homeassistant.components import (
|
||||
sensor,
|
||||
switch,
|
||||
vacuum,
|
||||
alarm_control_panel,
|
||||
)
|
||||
from homeassistant.components.climate import const as climate
|
||||
from homeassistant.const import (
|
||||
@@ -31,6 +32,20 @@ from homeassistant.const import (
|
||||
ATTR_SUPPORTED_FEATURES,
|
||||
ATTR_TEMPERATURE,
|
||||
ATTR_ASSUMED_STATE,
|
||||
SERVICE_ALARM_DISARM,
|
||||
SERVICE_ALARM_ARM_HOME,
|
||||
SERVICE_ALARM_ARM_AWAY,
|
||||
SERVICE_ALARM_ARM_NIGHT,
|
||||
SERVICE_ALARM_ARM_CUSTOM_BYPASS,
|
||||
SERVICE_ALARM_TRIGGER,
|
||||
STATE_ALARM_ARMED_HOME,
|
||||
STATE_ALARM_ARMED_AWAY,
|
||||
STATE_ALARM_ARMED_NIGHT,
|
||||
STATE_ALARM_ARMED_CUSTOM_BYPASS,
|
||||
STATE_ALARM_DISARMED,
|
||||
STATE_ALARM_TRIGGERED,
|
||||
STATE_ALARM_PENDING,
|
||||
ATTR_CODE,
|
||||
STATE_UNKNOWN,
|
||||
)
|
||||
from homeassistant.core import DOMAIN as HA_DOMAIN
|
||||
@@ -43,6 +58,8 @@ from .const import (
|
||||
CHALLENGE_ACK_NEEDED,
|
||||
CHALLENGE_PIN_NEEDED,
|
||||
CHALLENGE_FAILED_PIN_NEEDED,
|
||||
ERR_ALREADY_DISARMED,
|
||||
ERR_ALREADY_ARMED,
|
||||
)
|
||||
from .error import SmartHomeError, ChallengeNeeded
|
||||
|
||||
@@ -62,6 +79,7 @@ TRAIT_FANSPEED = PREFIX_TRAITS + "FanSpeed"
|
||||
TRAIT_MODES = PREFIX_TRAITS + "Modes"
|
||||
TRAIT_OPENCLOSE = PREFIX_TRAITS + "OpenClose"
|
||||
TRAIT_VOLUME = PREFIX_TRAITS + "Volume"
|
||||
TRAIT_ARMDISARM = PREFIX_TRAITS + "ArmDisarm"
|
||||
|
||||
PREFIX_COMMANDS = "action.devices.commands."
|
||||
COMMAND_ONOFF = PREFIX_COMMANDS + "OnOff"
|
||||
@@ -85,6 +103,7 @@ COMMAND_MODES = PREFIX_COMMANDS + "SetModes"
|
||||
COMMAND_OPENCLOSE = PREFIX_COMMANDS + "OpenClose"
|
||||
COMMAND_SET_VOLUME = PREFIX_COMMANDS + "setVolume"
|
||||
COMMAND_VOLUME_RELATIVE = PREFIX_COMMANDS + "volumeRelative"
|
||||
COMMAND_ARMDISARM = PREFIX_COMMANDS + "ArmDisarm"
|
||||
|
||||
TRAITS = []
|
||||
|
||||
@@ -873,6 +892,98 @@ class LockUnlockTrait(_Trait):
|
||||
)
|
||||
|
||||
|
||||
@register_trait
|
||||
class ArmDisArmTrait(_Trait):
|
||||
"""Trait to Arm or Disarm a Security System.
|
||||
|
||||
https://developers.google.com/actions/smarthome/traits/armdisarm
|
||||
"""
|
||||
|
||||
name = TRAIT_ARMDISARM
|
||||
commands = [COMMAND_ARMDISARM]
|
||||
|
||||
state_to_service = {
|
||||
STATE_ALARM_ARMED_HOME: SERVICE_ALARM_ARM_HOME,
|
||||
STATE_ALARM_ARMED_AWAY: SERVICE_ALARM_ARM_AWAY,
|
||||
STATE_ALARM_ARMED_NIGHT: SERVICE_ALARM_ARM_NIGHT,
|
||||
STATE_ALARM_ARMED_CUSTOM_BYPASS: SERVICE_ALARM_ARM_CUSTOM_BYPASS,
|
||||
STATE_ALARM_TRIGGERED: SERVICE_ALARM_TRIGGER,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def supported(domain, features, device_class):
|
||||
"""Test if state is supported."""
|
||||
return domain == alarm_control_panel.DOMAIN
|
||||
|
||||
@staticmethod
|
||||
def might_2fa(domain, features, device_class):
|
||||
"""Return if the trait might ask for 2FA."""
|
||||
return True
|
||||
|
||||
def sync_attributes(self):
|
||||
"""Return ArmDisarm attributes for a sync request."""
|
||||
response = {}
|
||||
levels = []
|
||||
for state in self.state_to_service:
|
||||
# level synonyms are generated from state names
|
||||
# 'armed_away' becomes 'armed away' or 'away'
|
||||
level_synonym = [state.replace("_", " ")]
|
||||
if state != STATE_ALARM_TRIGGERED:
|
||||
level_synonym.append(state.split("_")[1])
|
||||
|
||||
level = {
|
||||
"level_name": state,
|
||||
"level_values": [{"level_synonym": level_synonym, "lang": "en"}],
|
||||
}
|
||||
levels.append(level)
|
||||
response["availableArmLevels"] = {"levels": levels, "ordered": False}
|
||||
return response
|
||||
|
||||
def query_attributes(self):
|
||||
"""Return ArmDisarm query attributes."""
|
||||
if "post_pending_state" in self.state.attributes:
|
||||
armed_state = self.state.attributes["post_pending_state"]
|
||||
else:
|
||||
armed_state = self.state.state
|
||||
response = {"isArmed": armed_state in self.state_to_service}
|
||||
if response["isArmed"]:
|
||||
response.update({"currentArmLevel": armed_state})
|
||||
return response
|
||||
|
||||
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"]:
|
||||
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"]]
|
||||
# disarm the system without asking for code when
|
||||
# 'cancel' arming action is received while current status is pending
|
||||
elif (
|
||||
params["arm"]
|
||||
and params.get("cancel")
|
||||
and self.state.state == STATE_ALARM_PENDING
|
||||
):
|
||||
service = SERVICE_ALARM_DISARM
|
||||
else:
|
||||
if self.state.state == STATE_ALARM_DISARMED:
|
||||
raise SmartHomeError(ERR_ALREADY_DISARMED, "System is already disarmed")
|
||||
_verify_pin_challenge(data, self.state, challenge)
|
||||
service = SERVICE_ALARM_DISARM
|
||||
|
||||
await self.hass.services.async_call(
|
||||
alarm_control_panel.DOMAIN,
|
||||
service,
|
||||
{
|
||||
ATTR_ENTITY_ID: self.state.entity_id,
|
||||
ATTR_CODE: data.config.secure_devices_pin,
|
||||
},
|
||||
blocking=True,
|
||||
context=data.context,
|
||||
)
|
||||
|
||||
|
||||
@register_trait
|
||||
class FanSpeedTrait(_Trait):
|
||||
"""Trait to control speed of Fan.
|
||||
@@ -1343,7 +1454,6 @@ def _verify_pin_challenge(data, state, challenge):
|
||||
"""Verify a pin challenge."""
|
||||
if not data.config.should_2fa(state):
|
||||
return
|
||||
|
||||
if not data.config.secure_devices_pin:
|
||||
raise SmartHomeError(ERR_CHALLENGE_NOT_SETUP, "Challenge is not set up")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user