Save and restore state of lights

I want to continue the topic from two threads since I have started a thread about getting Home Assistant to perform tasks when my phone rings. I don’t want to resurrect an old thread and I don’t want my new question to get lost in my thread specific to the topic.

Continuing the discussion from Have Home Assistant Do Something When Phone Rings (Android):

Continuing the discussion from Save and restore Functionality (state of lights):

At first, for the second thread, I’ve been to that thread before – about SceneGen. I’m not sure if Home Assistant supports dynamic scenes – meaning “generate a scene that saves state of all the lights, interrupt the playback/mute the receiver (see the first thread), flash lights, wait, and if the person answering the phone answers or did not answer, wait for a second, call the dynamically-generated scene, and destroy the scene.”

Is this possible if Home Assistant supports it? In other words, if Home Assistant supports it, how am I going to go about using SceneGen?

(Update: Forgot I was in Feature Request forum. Moved to Configuration.)

From what I’ve seen HA is more of a check the status now and do something, trigger and response type of application. For something as complex as you are talking about where you are looking at storing the last state of a device, you might want to look at AppDaemon. It interfaces with HA so you have all the devices setup under HA but offers a Python interface instead of the YAML automation section.

1 Like

AppDaemon? I must have came across that when I venture into Third Party Integrations forum and I also saw a mention of AppDaemon in a blog as well when I was checking for the latest released of Home Assistant! I will check into it as I might have questions about how to use AppDaemon.

I wan’t sure if I could set up some kind of “global variable” where I could store the state of each light’s on/off and brightness in separate variables so that other script can use them. I’ve looked at the variables section of the Script page in HA’s website, but I don’t know of any examples in which multiple scripts could use it.

Thanks.

As a programmer, I find the python automation a lot more intuitive than YAML. But that maybe just me.

I cobbled together a way to remember the state of a light for future ref. Not perfect, but it’s all YAML-based.

1 Like

I was looking for the same functionality and came up with a way to do it via Python Scripts (i.e., https://www.home-assistant.io/components/python_script/.) I’m still pretty new to HA, so this may or may not be an optimal or “proper” way, but it does seem to work. Any comments/feedback are welcome.

In my case I wanted to save the current state of all switches/lights, turn them all on for a period of time, then restore them all to their previous state. Here are the two scripts I came up with:

To save the current state (which I put in ~homeassistant/.homeassistant/python_scripts/save_lights.py):

DOMAIN = 'light_store'
STORE_ENTITY_ID = '{}.{{}}'.format(DOMAIN)

saved = hass.states.entity_ids(DOMAIN)
for entity_id in saved:
    hass.states.remove(entity_id)
all_switches = hass.states.entity_ids('switch')
all_lights = hass.states.entity_ids('light')

for entity_id in all_switches:
    cur_state = hass.states.get(entity_id)
    if cur_state is None:
        logger.error('Could not get current state for {}.'.format(entity_id))
    else:
        hass.states.set(STORE_ENTITY_ID.format(entity_id.replace('.', '_')),
            cur_state.state)
for entity_id in all_lights:
    cur_state = hass.states.get(entity_id)
    if cur_state is None:
        logger.error('Could not get current state for {}.'.format(entity_id))
    else:
        attributes = {}
        brightness = cur_state.attributes.get('brightness')
        if brightness is not None:
            attributes['brightness'] = brightness
        hass.states.set(STORE_ENTITY_ID.format(entity_id.replace('.', '_')),
            cur_state.state, attributes)

And to restore the previous states (~homeassistant/.homeassistant/restore_lights.py):

DOMAIN = 'light_store'
STORE_ENTITY_ID = '{}.{{}}'.format(DOMAIN)

saved = hass.states.entity_ids(DOMAIN)
all_switches = hass.states.entity_ids('switch')
all_lights = hass.states.entity_ids('light')

for entity_id in all_switches:
    old_state = hass.states.get(STORE_ENTITY_ID.format(entity_id.replace('.', '_')))
    if old_state is None:
        logger.error('No saved state for {}.'.format(entity_id))
    else:
        turn_on = old_state.state == 'on'
        service_data = {'entity_id': entity_id}
        hass.services.call('switch', 'turn_on' if turn_on else 'turn_off',
            service_data)
for entity_id in all_lights:
    old_state = hass.states.get(STORE_ENTITY_ID.format(entity_id.replace('.', '_')))
    if old_state is None:
        logger.error('No saved state for {}.'.format(entity_id))
    else:
        turn_on = old_state.state == 'on'
        service_data = {'entity_id': entity_id}
        brightness = old_state.attributes.get('brightness')
        if turn_on and brightness is not None:
            service_data['brightness'] = brightness
        hass.services.call('light', 'turn_on' if turn_on else 'turn_off',
            service_data)

for entity_id in saved:
    hass.states.remove(entity_id)

The state is saved to new entities named ‘light_store.xxx’ where xxx is the entity ID of the switch or light being saved (with dots replaced with underscores). E.g.:

image

These entities are removed after using them to restore the switches/lights back to their previous states.

Although these scripts as written save/restore all switches and lights, I think it would be very easy to enhance to take a list of entity IDs to operate on instead. Also, I suppose a unique name could be specified for the “light store” if that was needed.

Comments/thoughts? Is it reasonable to dynamically create and destroy entities for temporarily saving state?

4 Likes

Hi, and thanks for a great script! First time I have a reason to try out python_scripts. :slight_smile: However, a) I’m having a problem, and b) I’d like to have something additional implemented but unsure how. If you have time, I would very much appreciate your help!

a) In my setup, I have quite a few light.stuff. In a room, say roomB, I would have both individual light.bulb1 and light.bulb2, as well as light.roomb which is a light group composed of light.bulb1 and light.bulb2. There might even be some additional light.stuff, subgroups or the like.

However, the way the two scripts currently work (I think), brightness is saved for light.bulb1, bulb2, and roomb. If bulb1 has brightness 130 and bulb2 is off, then brightness for roomb is saved as 130. And the result is that all three are set as brightness 130 on restore. The crucial point being that bulb2 goes from off to 130, though it should be restored to off.

Not sure how this could best be solved. Perhaps by whitelisting lights-to-be-saved-and-restored or something?

b) I believe only on/off and brightness is currently saved? Would be great if colour (xy or rgb) would be saved / restored as well!

Brightness should only be saved on an entity_id by entity_id basis. To say that another way, if an entity_id does not have a brightness attribute (at the time of saving, e.g., because it’s off), then brightness should not be saved for that entity_id. In the example you provide, brightness should only be saved for bulb1. And even if it were saved for bulb2 (because it had a brightness attribute at the time for some reason), it won’t be restored (in your example scenario) because the restore script only uses the saved brightness value (for a particular entity_id) if the saved state was ‘on’.

Have you tried it and found that it is not working that way?

Regarding light groups, I have to admit I don’t have any (other than the default group.all_lights, which these scripts don’t use), so I haven’t tested it with any. I’m not immediately sure how to deal with those. Can you show me what one looks like (from the front end’s states page)?

Again, I don’t have any lights that have these attributes. It should be pretty straightforward to add support for additional attributes, though.

Yes, but this means that brightness is saved for light.bulb1 (130), light.bulb2 (off) and light.roomb (130). When it is restored, roomb takes precedence: setting light.roomb to brightness=130 means making it so for both bulb1 and bulb2. I would need to make the script ignore roomb for it to work properly, but am not sure how.

Sorry, but I think you misunderstand how the scripts work. For now, let’s ignore light.roomb, since again I didn’t write the scripts to deal with light groups. We can get to that later.

In your example, light.bulb1’s state is ‘on’, and it has a brightness attribute with a value of 130. This would create light_store.light_bulb1 with a state of ‘on’ and a brightness attribute of 130. light.bulb2’s state is ‘off’ and it should not have a brightness attribute. This would create light_store.light_bulb2 with a state of ‘off’ and no brightness attribute.

For this case, the restore script would restore light.bulb1’s state to ‘on’, and its brightness attribute to 130. And it would restore light.bulb2’s state to ‘off’, and would do nothing with its brightness attribute.

There’s no “precedence”, because the saved state for each light has its own state, and its own brightness attribute (if necessary.) There’s not just one brightness attribute saved for all bulbs.

Does that make sense?

It doesn’t go under the group domain, but light; thus the problem. I have got light.roomb (light group through Ha) and light.roomba (light group through hue system, automatically imported into Ha) as well as, in bigger rooms light.roomb_groupa and light.roomb_groupb though all of these really consists of a number of individual lights with their own light.entities. I’d only want the script to save status of the actual light.entities (eg. light.bulb1 and light.bulb2). I can’t of a better way of doing it than white/blacklisting entitiy_id’s though.

It makes sense, but it’s not how it works in practice, if there’s a entity_id under light domain that actually is a group (eg. light.roomb).

Though light.bulb2 is restored to off, it still gets turned on with brightness 130 due to light.roomb being set to that. light.roomb is light.bulb1 + light.bulb2. But there’s no way for the script to know that, it just restores all three.

Ah, ok, I think I know what I missed. light.bulb1 and light.bulb2 are IN light.roomb! lol! (I realized you weren’t talking about group’s, but light’s that ARE groups, but I missed that 1 & 2 were in roomb.)

So, to make this work for that scenario I’d have to figure out how to tell the difference between a light entity that is an individual light and a light entity that is actually a group of other light entities. Since I don’t have any light components that are groups of other light components I don’t know what they look like and how to tell them apart from non-group light components. If you could snip a picture of what light.roomb looks like on the states page, that might help. I’d be happy to update the scripts, but I don’t know (yet) how to tell individual light entities from light groups.

Here’s two. One that currently is composed of lights currently all switched off; another composed of some lights currently switched on (but not all). Neither of these is a light.entity, but a light.group (eg. light.roomb and light.rooma).

First consists of two actual lights (bulb1 and 2), whereas the second is composed of three. None of these details are exposed in attributes. Perhaps filtering out “supported_features=63” might do the trick; don’t think I’ve seen it in light entities (eg. light.bulbs).

No; found a light.bulb with that attribute.

I’m going to take a look at the light/init.py and light/group.py code tonight. There may be a way to do this. I’ll try to get back to you tomorrow.

As far as I can see, there is no way to tell the difference. This is why I’m thinking a white-/blacklist of entity_id:s under a domain might be the only way to make it work?

Great! Much appreciated. (The color-thing I might take a stab at myself tomorrow; seems straight forward enough to add.)

Well, admittedly, I’m pretty new to HA, but from what I can understand, it’s not possible to tell the difference, especially from a python_script. (This is probably by design.) The only thing I can think of would require modifying light/group.py (either directly, or maybe better by copying it to your custom_components/light folder) to make it add another attribute (e.g., ‘light_group’, whose value can probably be anything but None.) But that’s probably not a good idea.

Your idea is probably better - i.e., pass a parameter into the scripts which is a list of lights to ignore (which would be the entity_id’s of the lights that are actually groups.) Of course, this would be less than optimal because you’d have to make sure to update this list every time you added or removed a light group. :frowning: Or possibly the list could be a list of lights to save/restore, but then again, maintaining the list would be problematic.

On the other hand, there might be occasions when you only want to save/restore a subset of all the switches & lights, so providing a list to save/restore might not be a bad feature. It could always default to all if no list was provided. And as I think I commented before (in the referenced topic), it might be nice to be able to specify a domain name for the “store” (instead of ‘light_store’) so that multiple, independent groups could be saved/restored. Hmm…

For the time being, either hardcoding entities to black- or whitelist them (whichever is shorter; in my case whitelisting would be more convenient) would work.

In the long run though, adding an attribute to differentiate between actual lights, and light groups only seems to be sensible. I’ve opened a feature request for it (issue 14081). (Light groups imported through Philips Hue already have an attribute is_hue_group: true!)

As for color, adding these lines to save_lights.py, before hass.states.set, should do the trick, I think;

rgb_color = cur_state.attributes.get('rgb_color')
        if rgb_color is not None:
            attributes['rgb_color'] = rgb_color

And the following to restore_lights.py, before hass.services.call:

rgb_color = old_state.attributes.get('rgb_color')
        if turn_on and rgb_color is not None:
            service_data['rgb_color'] = rgb_color