Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions homeassistant/components/google_assistant/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
sensor,
switch,
vacuum,
alarm_control_panel,
)

DOMAIN = "google_assistant"
Expand Down Expand Up @@ -48,6 +49,7 @@
"lock",
"binary_sensor",
"sensor",
"alarm_control_panel",
]

PREFIX_TYPES = "action.devices.types."
Expand All @@ -66,6 +68,7 @@
TYPE_DOOR = PREFIX_TYPES + "DOOR"
TYPE_TV = PREFIX_TYPES + "TV"
TYPE_SPEAKER = PREFIX_TYPES + "SPEAKER"
TYPE_ALARM = PREFIX_TYPES + "SECURITYSYSTEM"

SERVICE_REQUEST_SYNC = "request_sync"
HOMEGRAPH_URL = "https://homegraph.googleapis.com/"
Expand All @@ -81,6 +84,9 @@
ERR_UNKNOWN_ERROR = "unknownError"
ERR_FUNCTION_NOT_SUPPORTED = "functionNotSupported"

ERR_ALREADY_DISARMED = "alreadyDisarmed"
ERR_ALREADY_ARMED = "alreadyArmed"

ERR_CHALLENGE_NEEDED = "challengeNeeded"
ERR_CHALLENGE_NOT_SETUP = "challengeFailedNotSetup"
ERR_TOO_MANY_FAILED_ATTEMPTS = "tooManyFailedAttempts"
Expand All @@ -106,6 +112,7 @@
script.DOMAIN: TYPE_SCENE,
switch.DOMAIN: TYPE_SWITCH,
vacuum.DOMAIN: TYPE_VACUUM,
alarm_control_panel.DOMAIN: TYPE_ALARM,
}

DEVICE_CLASS_TO_GOOGLE_TYPES = {
Expand Down
107 changes: 107 additions & 0 deletions homeassistant/components/google_assistant/trait.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
sensor,
switch,
vacuum,
alarm_control_panel,
)
from homeassistant.components.climate import const as climate
from homeassistant.const import (
Expand All @@ -31,6 +32,19 @@
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,
ATTR_CODE,
STATE_UNKNOWN,
)
from homeassistant.core import DOMAIN as HA_DOMAIN
Expand All @@ -43,6 +57,8 @@
CHALLENGE_ACK_NEEDED,
CHALLENGE_PIN_NEEDED,
CHALLENGE_FAILED_PIN_NEEDED,
ERR_ALREADY_DISARMED,
ERR_ALREADY_ARMED,
)
from .error import SmartHomeError, ChallengeNeeded

Expand All @@ -62,6 +78,7 @@
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"
Expand All @@ -85,6 +102,7 @@
COMMAND_OPENCLOSE = PREFIX_COMMANDS + "OpenClose"
COMMAND_SET_VOLUME = PREFIX_COMMANDS + "setVolume"
COMMAND_VOLUME_RELATIVE = PREFIX_COMMANDS + "volumeRelative"
COMMAND_ARMDISARM = PREFIX_COMMANDS + "ArmDisarm"

TRAITS = []

Expand Down Expand Up @@ -873,6 +891,95 @@ async def execute(self, command, data, params, challenge):
)


@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 = {
"level_name": state,
"level_values": [
{
"level_synonym": [
state.replace("_", " "),
state.split("_")[

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's going on here ? why?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to generate the synonyms from the state name itself. so armed_away turns into armed away or away instead of hard coding it.
Do you recommend to keep it this way or just had code the synonyms?

1 if state != STATE_ALARM_TRIGGERED else 0
],
],
"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

return {
"isArmed": armed_state in self.state_to_service,
"currentArmLevel": armed_state,
}

async def execute(self, command, data, params, challenge):
"""Execute an ArmDisarm command."""
if params["arm"] and self.state.attributes["code_arm_required"]:
_verify_pin_challenge(data, self.state, challenge)
elif params["arm"] and not params.get("cancel"):
if self.state.state == params["armLevel"]:
raise SmartHomeError(ERR_ALREADY_ARMED, "System is already armed")
service = self.state_to_service[params["armLevel"]]
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: challenge["pin"] if challenge else "",
},
blocking=True,
context=data.context,
)


@register_trait
class FanSpeedTrait(_Trait):
"""Trait to control speed of Fan.
Expand Down