-
-
Notifications
You must be signed in to change notification settings - Fork 30.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
"Alarm group" platform #11323
"Alarm group" platform #11323
Conversation
Any news? |
I've had a brief look and I think I like it. I haven't had time to go through it in detail. I would like to have a second opinion though too, since this is a structural change and also core change. |
Another gentle ping... Sorry :-) |
And now it's been a couple weeks more. |
Yeah, sorry about this. I'll try to get some time this weekend, if nobody else beats me to it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The most important question before we go ahead is, what is the difference between this new group platform and a regular home assistant group of alarm control panels, in terms of features.
I haven't looked at the test for the new platform.
I like this if a normal group doesn't work, but I want at least a second opinion from another member, before we go ahead and merge. Grouping stuff in home assistant is controversial sometimes. 😄
There's also a merge conflict currently.
See comments below.
from collections import Counter | ||
from copy import deepcopy | ||
import logging | ||
import voluptuous as vol |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a blank line between standard library imports and 3rd party imports.
|
||
DEFAULT_ALARM_NAME = 'Group Alarm' | ||
|
||
PLATFORM_SCHEMA = vol.Schema(vol.All({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should extend the base alarm_control_panel component PLATFORM_SCHEMA
.
vol.Optional(CONF_NAME, default=DEFAULT_ALARM_NAME): cv.string, | ||
vol.Optional(CONF_CODE_FORMAT, default=''): cv.string, | ||
vol.Required(CONF_PANELS): vol.All(cv.ensure_list, [{ | ||
vol.Required(CONF_PANEL): cv.entity_id, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why should this be a list of dictionaries? Can it not just be a list of entity_ids? The CONF_PANEL
key seems to not add anything.
Do we want to allow the same entity_id multiple times?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The CONF_PANEL
key is mostly for future extensibility, in case we want to add more panel-specific configuration.
Having the same entity_id multiple times would probably be a bug in the configuration. However, I haven't seen uses of vol.Unique
in hass, which is why I left it out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think something like the scene config makes more sense. But right now there are no values for the entity keys:
https://home-assistant.io/components/scene/.
|
||
|
||
def setup_platform(hass, config, add_devices, discovery_info=None): | ||
"""Set up the manual alarm platform.""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Stale docstring.
hass, | ||
config[CONF_NAME], | ||
config[CONF_CODE_FORMAT], | ||
config.get(CONF_PANELS) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't use dict.get
if accessing a required key in the config.
"""Return the state of the device.""" | ||
# _update_state always behaves as if a transition was in progress. | ||
# But if the pre- and post-state match, we're not "pending"! | ||
self._update_state() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would move all this logic to _update_state
which should be renamed to async_update
. Just return self._state
here.
def _common_state(states): | ||
# This can happen if the sub-panels have not loaded yet. | ||
if len(states) == 0: | ||
return STATE_ALARM_DISARMED |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Return None
if state is unknown.
if states[STATE_ALARM_PENDING]: | ||
return STATE_ALARM_PENDING | ||
|
||
# If all entities have the same state, great. Otherwise, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's two spaces after the period, before "Otherwise". Please remove one space.
return STATE_ALARM_PENDING | ||
|
||
# If all entities have the same state, great. Otherwise, | ||
# we can only summarize the state as "custom bypass". |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this? I'm not following here, sorry.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suppose we follow the example in the group documentation, and add a group alarm that is composed of two subpanels (one per floor). The group alarm is then placed under the "home" tab, while the subpanels would go under the "upstairs" and "downstairs" tabs respectively.
You can go to the "upstairs" tab and only arm the "upstairs" control panel. The result is that you have a custom setup for the whole group, where some environments have had their sensors bypassed. The same holds if you arm the entire home via the group alarm, and then disarm the "upstairs" control panel manually. This is expressed with return STATE_ALARM_ARMED_CUSTOM_BYPASS
here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, makes sense.
post_states = Counter() | ||
for entity_id, state in self._states: | ||
if state.state == STATE_ALARM_PENDING and \ | ||
(ATTR_PRE_PENDING_STATE in state.attributes): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's a bit weird that we depend on the state attributes to define state. Maybe we can store the previous pre/post states in two new instance attibutes, instead? Let me know if I'm missing the point here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code is looking "inside" the subpanels' state attributes to understand more clearly what the subpanels are doing, and possible synthesize those same state attributes for the group (ATTR_PRE_PENDING_STATE
goes to self._pre_state
, ATTR_POST_PENDING_STATE
goes to self._post_state
.
It would certainly be possible to make before/after states into instance attributes that are available even in non-PENDING states; and in fact it may possibly make some sense for the "before" state, because that is effectively the "current operating state" of the panel (I think it makes less sense for the "after" state). However, considering that most alarm platforms do not provide PRE_PENDING_STATE, I think it's cleaner to leave it as a "nice to have" state attribute.
The group alarm platform and the control groups are for different use cases. If you have multiple alarm panels, placing them in a group only has a "visual" effect, each control panel is still completely independent and is armed/disarmed separately. The alarm group platform instead lets you use a single control to arm/disarm multiple control panels, e.g. "clicking" a single control (the group alarm) to arm/disarm alarms in the entire home. The two can also be used together. Following the example in the group documentation, you could have a group alarm that is composed of two subpanels (one per floor). The group alarm is then placed under the "home" tab, while the subpanels would go under the "upstairs" and "downstairs" tabs respectively. |
|
||
|
||
class GroupAlarm(alarm.AlarmControlPanel): | ||
"""Implement services and state computation for the group alarm platform.""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
line too long (80 > 79 characters)
DOMAIN, PLATFORM_SCHEMA, SERVICE_ALARM_DISARM, SERVICE_ALARM_ARM_HOME, | ||
SERVICE_ALARM_ARM_AWAY, SERVICE_ALARM_ARM_NIGHT, SERVICE_ALARM_TRIGGER, | ||
SERVICE_ALARM_ARM_CUSTOM_BYPASS) | ||
from homeassistant.const import ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'homeassistant.const.STATE_ALARM_DISARMED' imported but unused
'homeassistant.const.CONF_PLATFORM' imported but unused
|
||
|
||
class GroupAlarm(alarm.AlarmControlPanel): | ||
"""Implement services and state computation for the group alarm platform.""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
line too long (80 > 79 characters)
DOMAIN, PLATFORM_SCHEMA, SERVICE_ALARM_DISARM, SERVICE_ALARM_ARM_HOME, | ||
SERVICE_ALARM_ARM_AWAY, SERVICE_ALARM_ARM_NIGHT, SERVICE_ALARM_TRIGGER, | ||
SERVICE_ALARM_ARM_CUSTOM_BYPASS) | ||
from homeassistant.const import ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'homeassistant.const.STATE_ALARM_DISARMED' imported but unused
'homeassistant.const.CONF_PLATFORM' imported but unused
There's been a similar discussion in #12229, regarding grouping of lights. As mentioned there an alternative is to update the behavior of grouped entities, in our case alarms, via change of the frontend. What would be the limitations if we go that route? |
This is interesting! I think the main one is that the group alarm mediates between sensors and switches in the automation rules: you attach sensors to the individual room alarms, and switches (e.g. siren) to the group. So the state update algorithm has a more "active" role than the light group in #12229. Sure it's not hard to add an OR to the "trigger siren" and a NOR to the opposite, but it's a bit simpler and comes out very natural with the group alarm. In my case, I am also adding GPIO-based arm/disarm (could be done with a keypad, with RFID keys, or similar) which goes beyond what the frontend can provide. And another difference is that there is no equivalent to TemplateLight for alarms. |
@MartinHjelmare one argument against having groups be aware if it is grouping lights/switches/alarms etc is that all of a sudden group becomes tightly coupled with other implementations which is something we should try to avoid. |
As for implementation of a group alarm platform, I guess I'm not against. I think that if we want to make this more common, we should make sure that they are all called |
Included the comments from home-assistant#11323
* Add grouped_light platform * 📝 Fix Lint issues * 🎨 Reformat code with yapf * A Few changes * ✨ Python 3.5 magic * Improvements Included the comments from #11323 * Fixes * Updates * Fixes & Tests * Fix bad-whitespace * Domain Config Validation ... by rebasing onto #12592 * Style changes & Improvements * Lint * Changes according to Review Comments * Use blocking light.async_turn_* * Revert "Use blocking light.async_turn_*" This reverts commit 9e83198. * Update service calls and state reporting * Add group service call tests * Remove unused constant.
They will be used by the group alarm platform too. While having to adjust the test code for over-80 characters lines, switch to assertEqual too.
The group alarm platform provides a mechanism to automatically arm sub-panels and compute the state of the overall alarm system. For example, a siren switch can be easily attached to the group entering or exiting the triggered state. A full example is included in the documentation pull request.
|
||
CONF_CODE_FORMAT = 'code_format' | ||
|
||
DEFAULT_ALARM_NAME = 'Group Alarm' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please also change the default name to Alarm Group
for consistency among group platforms.
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ | ||
vol.Optional(CONF_NAME, default=DEFAULT_ALARM_NAME): cv.string, | ||
vol.Optional(CONF_CODE_FORMAT, default=''): cv.string, | ||
vol.Required(CONF_ENTITIES): vol.All(cv.ensure_list, [cv.entity_id]), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use cv.entities_domain
instead:
vol.Required(CONF_ENTITIES): cv.entities_domain(alarm.DOMAIN)
|
||
self.async_schedule_update_ha_state(True) | ||
|
||
self.hass.bus.async_listen_once( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe have a look at how light groups do async_added_to_hass
:
Your current code would (I think) break with dynamically loading/unloading your entity.
EVENT_HOMEASSISTANT_START, group_alarm_startup) | ||
|
||
@asyncio.coroutine | ||
def _async_send_message(self, service, **kwargs): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the only kwarg
is code
, why make this accept **kwargs
?
sending_payload = deepcopy(payload.copy()) | ||
sending_payload[ATTR_ENTITY_ID] = entity_id | ||
tasks.append(self.hass.services.async_call( | ||
DOMAIN, service, sending_payload)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You need to specify blocking=True
. Also the entities will automatically be expanded for you, so no need for a for-loop I think.
What is the state of this? @bonzini can you address the open comments so that we can merge it? |
I should be able to look at it again over the weekend. |
This PR seems to have gone stale. Closing it. You can reopen it when you're ready to finish it. |
Description:
This pull request introduces a "group" alarm control panel platform. The platform distributes the commands to multiple sub-panels, and reports a unified state from all the sub-panels. The main use case is to apply different delay times to different rooms, and to disable some rooms in the "armed_home" or "armed_night" states.
Pull request in home-assistant.github.io with documentation (if applicable): home-assistant/home-assistant.io#4283
Example entry for
configuration.yaml
(if applicable):Checklist:
If user exposed functionality or configuration variables are added/changed:
If the code does not interact with devices:
tox
run successfully. Your PR cannot be merged unless tests pass.coveragerc
.