Grouped light control

I took at stab at grouped light control because I have 5 hue bulbs in a fixture and want to dim them all at the same time. It supports on/off, color, brightness, and temperature depending on whether or not all bulbs in the group are reporting those values.

I implemented it as a light platform since that was the only way I could think of to do it. Maybe it makes sense however because that is also how I think about my lights which are physically grouped in the fixture – they are a single light to me.

https://gist.github.com/fignuts/ace6271e097adcc62e824c2ec6184f51

Then for example in my configuration.yml I have

light:
  - platform: grouped_light
    name: Test Light
    entities:
      - light.upstairs_hall_1
      - light.upstairs_hall_2
      - light.upstairs_hall_3
      - light.upstairs_hall_4
      - light.upstairs_hall_5
  - platform: grouped_light
    name: Test Color
    entities:
      - light.office_computer
      - light.spot

This gives me two new devices in the UI which control all attributes of those lights in unison.

The issue I have found with this solution so far is that the UI lags behind what my hue bulbs are actually doing. I turn the grouped_light off in the UI and the lights begin to fade out but the UI immediately switches back to on. It eventually catches up the correct state when it is polled.

I don’t have much python experience and I am very new at Home Assistant so I am sure I did a lot wrong and would appreciate feedback. How could I improve this, make it more robust, general, and “do it right”?

1 Like

It would be more efficient to create a light group within a hue app than to make five API calls to the bridge.

My goal here is to have a light entity (with corresponding UI element) in Home Assistant that controls the behavior of several lights regardless of their underlying technology.

I don’t see how making a group within the hue app would help with this. I don’t think Home Assistant has a way of dealing with hue groups as a light entity. Also not all of the lights I want to group may necessarily be hue lights. As for efficiency, making 5 api calls to the hue does not seem to cause any problems so I don’t see why I should avoid it.

2 Likes

I saw your other thread where you mentioned groups have no detail card which is not strictly true. If you add a group to another group, you can click on the group to get details. It would make a lot of sense to add it there.

That could work, but what about the case where you group some lights and that group is not placed inside of another group? Where would the controls for brightness, color, etc be shown?

With the way I have implemented it here I think it is closer to my intention of treating a group of lights as if they were a single physical device.

Yeah there are probably some issues that would need to straightened out with such a solution. However, if this were to end up in master it would make for a confusing model to have two different kind of groups. Groups already have a master on/off switch and I see no reason why you couldn’t get a state card by clicking on the header there. It’s not something that exists today but it could probably be implemented easily. After all, groups are entities too.

However, if you have no intention of contributing this, I think you should do whatever suits you the best :slight_smile:

I would like to have a solution for this use case in master. It seems to have been requested quite a few times. I just don’t have much python experience (or Home Assistant experience yet) so it is difficult for me to figure out how it could be implemented with the existing group component. I don’t fully understand how the group component works after reading through the code. That is why I posted my solution here as a gist hoping to get some feedback on how to do it “right” within the Home Assistant architecture. Hopefully @balloob can weigh in on how he thinks grouped light control should work.

Same here, lost a lot of time creating scripts for intervals :confused:

Implementing this as a light group can indeed work but I would consider solving this in the frontend. If the frontend encounters a group with only entities of 1 domain (ie light), render it like it is that domain.

1 Like

There are two different user intentions here:

  1. Group some lights together because they should be logically grouped in the UI – ex: overhead light and bedside light should be grouped together in a “bedroom” group but the user may want to control them individually.

  2. Group some lights together because the intention is for them to behave and appear in the UI as a single physical device – ex: 3 hue lux bulbs in one fixture should behave and appear as one light.

I’m not sure how that approach can support both use cases. Also, how can the frontend know what capabilities the group has? As far as I can tell the group component only tracks on/off state and offers no way to set brightness, color, etc.

Btw, the issue you are having with the UI not reflecting the state is because when the turn_on/turn_off calls are done, the functions return before the actual calls have been finished because the service calls are non-blocking. Then right after, HA checks the state of the bulbs which has not been changed yet so the UI turns the switch back to the original position. Then, after a little bit, HA polls for the state again and the UI updates to reflect the actual state.

One way I found around this is to make the service call blocking as you can see in this modified version:


Is this the right way to do it or even wise in general? I have no idea.

We can never support both use cases with 1 UI. We need to make choices. Configuration options are the road to unmaintainable code.

I’m not quite sure I follow you.

As far as I can tell there are two ways we could allow grouped control of brightness/color/etc for lights.

  1. As implemented in the gist above with a platform where the user explicitly groups lights together into new entities, or
  2. Make the UI display additional controls if all members of a group belong to a certain domain (maybe clicking on the group title could bring up a detail card with brightness/color controls in the case of lights?)

Are you saying that you would prefer the second method because it does not add additional configuration options? Or am I misunderstanding you?

Adjusting the brightness/color of all the lights in a group is an extremely common use case, I think. Thus, having that be the always-on behavior in the UI as described by @balloob makes a lot of sense I think.

@imagio
The thing you’re after is probably not as common but still a very useful thing to have. But as you said, the use cases are very different so it might make sense to do the first one as a UI thing and this as a platform. The fact that this multi-light thing actually consists of different other entities could be seen as an implementation detail.

The advantage of approach # 2 is that it would work for other components that are not lights as well :slight_smile:

Ok, I am trying to figure out the best way to do this…

For example if all entities in a group are lights it would make sense to render more-info-group as if it were more-info-light. That should be easy to do with some logic in more-info-group.js. However, to render more-info-light as a representation of a group is a little more complicated. More-info-light.js expects a stateObj which represents a single light entity.

To solve this I could modify more-info-light.js to accept a group entity. It would calculate its state based off of the group member’s states. For example brightness would be the average of the brightness of all lights in the group. Any service calls would be called for all entities in the group. This is essentially the same as what my grouped_light platform in the gist above does, just on the client side.

With this approach we would need to modify the more-info card for every type of device that we want to support with this type of “collapsed grouping”. As far as I can tell this is the only way to do it since the logic for how a particular device type behaves and how group state is calculated is dependent upon the type of device.

Is this a good and reasonable approach?

edit: After reading the code some more I realized that a service call to light.turn_on for a group should work just fine and there is no need to make individual service calls for each group member from the frontend. It seems that the only thing preventing more-info-light from working with a group entity is the lack of brightness, color temp, and color attributes on a group’s state. Does it make sense to add those state attributes to a group if the group’s members support them?

It makes a ton of sense to add that, I think.

It seems like a somewhat messy solution to start implementing state attributes (and functions to compute them) from other components onto the group component. Maybe it makes more sense to have subclasses of Group for each “group domain”?

Something like this (pseudocode):

class GroupLight(Group, Light):
     def __init__(.....):
          # call Group __init__ here

     @property
     def brightness(self):
          # calculate brightness for lights in group

     @property
     def xy_color(self):
          # calculate color for lights in group

     @property
     def state_attributes(self):
          group_state_attrs = Group.state_attributes(self)
          light_state_attrs = Light.state_attributes(self)
          return merge_state_attributes(group_state_attrs, light_state_attrs)

Then in group.py setup function (again pseudocode):

def setup(hass, config):
    """Setup all groups found definded in the configuration."""
    for object_id, conf in config.get(DOMAIN, {}).items():
        name = conf.get(CONF_NAME, object_id)
        entity_ids = conf.get(CONF_ENTITIES) or []
        icon = conf.get(CONF_ICON)
        view = conf.get(CONF_VIEW)
        # get domain to which all group members belong or 'group' if mixed
        group_domain = get_group_domain(entity_ids) 
        if group_domain == DOMAIN:
            group_domain_classname = "Group"
        else
            group_domain_classname = "Group" + group_domain
        class_ = getattr(module, group_domain_classname )
        class_ (hass, name, entity_ids, icon=icon, view=view,
              object_id=object_id)
    return True

My python fu is weak, I just started learning it when I discovered Home Assistant last week so I don’t know if this multiple inheritance approach makes sense but it seems like it could be a good way to keep the code nice and clean. Any thoughts?

Ok, so I took a crack at it and got something basic working. Clicking a header on a group of lights now opens the group detail card which will have controls for brightness etc if the lights support it. Finally I can control the brightness of all my lights together and with no additional configuration!

The frontend solution I came up with is fairly minimal and should extend easily to other types of devices people might want to control as a group. See this commit for the frontend:

https://github.com/fignuts/home-assistant-polymer/commit/bef202fe6e15f844387a3b15ff6cced4fb022fc7

As for the backend I added a group_domain attribute to groups state. This is the domain of the group’s members if they are all of the same domain or ‘group’ if the members are mixed. If the group domain is ‘light’ the group adds attributes based on the state of the lights in the group. These are the same attributes that the light component adds to its state. This lets the frontend treat the group exactly as if it were a single light so we can just re-use the more-info-light polymer component to control all the lights in the group.

I think this approach is fairly clean and extensible for all types of devices.

edit: removed old commit

Feedback on this approach would be appreciated! It seems to work well so far.

I updated my repo with a much cleaner implementation of domain specific group subtypes. This should make it easy to add a new subtype for any domain which it might be useful to control as a group. Please check it out and let me know if this looks like a good solution!

https://github.com/fignuts/home-assistant/commit/9d060b04dfcdb5ff4d2e28684bf534ca3aae6bed