LIFX Z Strip Integration - Commands to LED zones not batched (breaks transitions)

I am in the process of porting over some of my custom environment to Home Assistant and I have run into a pretty weird snag. On my original set up I could send a single command via set_zone_colors() and simultaneously set all the zones to begin transitioning to their different colors, hues, and brightness levels.

I’m still pretty new to YAML so I may be missing something obvious but take this script as the example:

sunrise:
  alias: Sunrise
  sequence:
  - service: scene.turn_on
    data:
      transition: 15
    target:
      entity_id: scene.sunrise
  - service: lifx.set_state
    data:
      zones:
      - 0
      - 1
      - 14
      - 15
      hs_color:
      - 36
      - 100
      brightness: 5
      transition: 15
    target:
      entity_id: light.window_sunshine
  - service: lifx.set_state
    data:
      zones:
      - 4
      - 6
      - 9
      - 11
      hs_color:
      - 275
      - 36
      brightness: 5
      transition: 15
    target:
      entity_id: light.window_sunshine
  - service: lifx.set_state
    data:
      zones:
      - 2
      - 3
      - 5
      - 7
      - 8
      - 10
      - 12
      - 13
      color_name: yellow
      brightness: 0
      transition: 15
    target:
      entity_id: light.window_sunshine
  - delay:
      hours: 0
      minutes: 0
      seconds: 15
      milliseconds: 0
  mode: restart
  icon: mdi:weather-sunset-up

This does not work as intended and only the final transition fires. It took me ages to work out what was going on but it would appear each of these is issuing their own command to the light strip. This is causing each successive command to override the previous and therefore not complete their transitions. Whereas if they are all gathered together in a single payload they would behave as expected.

How can I fire these all off at once in a single command/payload so they all transition together?

Also - how can I execute this with a scene rather than a script?

Alright I have now spent days trying to get this working in HA with essentially no luck. I’ve tried Photon and Bardolph (which seems to be broken?) without much success. So I’m just going to use the Pyscript integration and port over the spirit of my original script. If anyone cares to follow suit here’s my sunrise script that transitions different zones to different colors during the same transition.

There may now be a better way of handling the color specifications in the lifxlan python library. Back when I was writing it - it was hue, saturation, brightness, kelvin for each zone but specified as a value from 0 to 65535. You will have to do some very simple math to convert from hue, for example, to this weird number format. So a hue of 240 out of 360 would have a value of ((240/360)*65535) or 45510. Kelvin is the only exception which runs from 2500 to 9000 as usual.

@service
def z_sunrise_1(transition: 60):
    from lifxlan import *
    import asyncio

    #Device Discovery
    lifx = LifxLAN() #specifying number of lights e.g. (2) makes discovery faster
    light = MultiZoneLight("LIGHT MAC","LIGHT IP")

    #discovery try again
    if not light:
        light = lifx.get_device_by_name("LIGHT NICKNAME")

    #phase 0
    if light:
        light.set_zone_colors([(6553, 65535, 1965, 4000), (6553, 65535, 1965, 4000), (0, 0, 0, 0), (0, 0, 0, 0), (50243, 24247, 1966, 6500), (0, 0, 0, 0), (50243, 24247, 1966, 6500), (0, 0, 0, 0), (0, 0, 0, 0), (50243, 24247, 1966, 6500), (0, 0, 0, 0), (50243, 24247, 1966, 6500), (0, 0, 0, 0), (0, 0, 0, 0), (6553, 65535, 1965, 4000), (6553, 65535, 1965, 4000)],1)
        light.set_power(True,10000)

        asyncio.sleep(11)
        asyncio.sleep(transition * 60 * .15)

        #phase 1
        step1 = (transition * 60 * .25)
        light.set_zone_colors([(6400, 0, 11796, 9000), (6400, 0, 11796, 9000), (0, 0, 0, 0), (0, 0, 0, 0), (6400, 0, 11796, 9000), (0, 0, 0, 0), (6400, 0, 11796, 9000), (0, 0, 0, 0), (0, 0, 0, 0), (6400, 0, 11796, 9000), (0, 0, 0, 0), (6400, 0, 11796, 9000), (0, 0, 0, 0), (0, 0, 0, 0), (6400, 0, 11796, 9000), (6400, 0, 11796, 9000)],(step1*1000))
        asyncio.sleep(step1 + 1)

        #phase 2
        #use the remaining time to fade up to the color we want, add a little extra so it's not full blast at wakeup time
        step2 = (transition * 60 * .6) + (transition * 60 * .2)
        light.set_color([6400,0,65535,6500], (step2*1000))
        asyncio.sleep(step2 + 1)

This will appear in HA as service pyscript.z_sunrise

You should look at aiolifx-themes which is what HA uses to paint themes onto LIFX Z and Beams. You could just create one or more themes and paint them with that instead.

I believe I saw this earlier and I came away with the impression that it takes the theme from the lifx app and applies it to the light - is that correct? If not - sounds like it’s time I dive into source code!

Personally, I want to have HA scenes recognize the current state of each zone when a scene is saved. That way I can apply a theme or paint with the app and save it in HA. As it is the scene appears to grab the state of the first zone and apply it to the entire light.

They’re copied from the LIFX app and stored in the source of aiolifx-themes: https://github.com/Djelibeybi/aiolifx-themes/blob/47fbe90b394241e7bdfab0e3da4d61a28e27f57a/src/aiolifx_themes/themes.py

The themes from that library are enumerated on startup, so if you add themes, they’ll just appear in the select drop-down in Home Assistant. Also, PRs welcome to add themes (or the ability to define new themes via config).

I personally wouldn’t bother with trying to save scenes with a painted strip. I don’t think the integration supports that level of granularity. However, you can just call the select.select_option service to change the value of the Theme entity and it’ll paint it for you.

Theme docs: LIFX - Home Assistant

Am I understanding you correctly that if you add custom themes to the lifx app they will be imported into HA? What happens if I delete it from lifx? The docs seem to imply that it’s ONLY that list of themes that get imported.

No, you have to add custom themes to the aiolifx-themes library. I just copied the colours from the LIFX app manually when I created the library in the first place.

Ahh understood. So either way I’m pretty much hard coding the zones.

Can you point me to documentation on how to add themes to the aiolifx-themes library? I’m feeling like I must be blind - surely I’m not editing the source code in the docker container of HA?

There isn’t any documentation, I’m afraid. I’ve been meaning to add support for adding custom themes to the integration itself, but haven’t had sufficient free time yet. You could either fork the library and add the themes to your own fork, or if you have time, you could implement custom themes and submit that as a PR.

That’s probably going to be a little above my ability right now unfortunately. I spent most of the day trying to wrap my head around the asyncio calls that aiolifx uses (and just in general trying to establish any communication with a light). So until I get a better understanding home assistant, and probably python 3 in general, I’m probably the wrong man for the job.

e.g.

light = Light(asyncio.get_running_loop(),"MAC ADDRESS","IP ADDRESS")

I tried to combine that with some of your code but painter.paint() expects an array of lights rather than a single one. So I tried plopping light into an array by itself but there’s some keys missing. So I raised exceptions to the HA log and light was definitely wrong in terms of the light that was right in front of me. Seems like the light isn’t responding or I’m doing something else wrong.

Unfortunately the lifxlan library stopped working today due to bitstring changing its requirements. So I only got to enjoy my script working for about 4 days and now I’m on the hunt for a new solution. I don’t even need a theme necessarily. Just the ability to set_color_zones() without having to do rest commands but at this point that might be the next thing I try. Right now I can’t even send on/off commands via a script - so we’re a long ways out from me creating my own fork. :upside_down_face:

You don’t have to change any of the code if you want a new theme: just add it to the LIFX_APP_THEMES array.

Edit to add that the Photons Interactor add-on for Home Assistant is probably the simplest way to achieve this using REST commands.

I tried Photons and gave up when I couldn’t get it working. The documentation is great but I couldn’t get any of the rest commands to actually show up. The documentation around the HA addon is extremely sparse. So I gave up on it.

Would I be able to just append to that array from another script? I assumed that wouldn’t work since you said the dropdown enumerated at startup. Even if I forked and edited instead I’m not sure how to make Home Assistant (Supervised) load that one instead - I’m also not sure I want to.

At this point I just want a simple script I can call as a service through pyscript integration to do all the things.

I’ve just re-read your original post where you asked:

In your automation, add a Wait for time to pass (delay) action after the scene.start and each lifx.set_state that’s the same duration as the transition. You only have one at the end, but you need to delay after each all.

Reason: the automation doesn’t know what each action actually does. It just does what it’s told so it fires off all those lifx.set_state actions with no gaps. LIFX bulbs have very little memory so each call overrides the previous one. If you add some delays to your automation, you can get Home Assistant to send each lifx.set_state call as the previous transition ends.

Edit to apologise for not reading your first post more carefully. I thought it was related to a change made to how the multizone devices are now being handled.

No problem man. I appreciate the input anyway. The thing is I don’t want them to be delayed or chained in series like that. Those transitions should all be happening congruently with no delays in between. In super super short: HA is sending more packets than it needs to which is why the calls are overriding.

With lifxlan, I could use set_zone_colors() to set each zone to a unique color and then give that entire batch of colors a single transition time. From there I can issue another set_zone_colors() after a wait to transition each zone to whatever color I want. In two steps - two packets sent to the light. There is truly no limit to this. If I wanted to I could set each zone to a different color/brightness and transition each of them to a different color at the same time.

The limitation of home assistant is that the yaml (as far as I can tell) only allows me to set one color at a time. I can do multiple zones but I can’t do different colors for the zones that I pick. As a result, when that packet gets sent to the light, it is only one color with a transition time. Thus, you’re right, subsequent packets to the light will override the previous. Because it is firing off as separate packets for each color.

My sunrise has three “batches” of colors that all transition together (and I should note this script has been running just fine for 5 years so I know this works):
Purple → Blue Gray → Soft White
Orange → Yellow → Soft White
Off → Orange → Soft White

This should only require three packets and three transition times + waits. Doing these one at a time will not achieve the desired effect and there is no apparent way to group them up more. So HA is sending 9 packets for a job that should take 3 and that severely limits how transitions can happen. Hopefully that makes sense.

Just in case it doesn’t here’s how that same process would look in Home Assistant:
Purple → Blue Gray | Orange → Yellow | Off → Orange | Blue Gray → Soft White… etc
As you can see this takes twice as long (or the time for each transition would need to be cut short) and the colors are not fading in together. They are happening in series rather than in parallel.

My suggestion for an HA change would be an input array in the yaml that has key->value pairs that are structured as zone->color, or, better yet, zones->color.

So instead of:

zones:
    - 0
    - 1
    - 14
hs_color:
    - 36
    - 100
brightness: 5
transition: 15

I would like to see something along these lines (I’m sure this is bad yaml but hopefully the idea comes across. Basically it needs to be a table of data rather than a one-dimensional array.

data:
    0: [36,100,5]
    1: [36,100,5]
    14: [36,100,5]
    4: [275,36,5]
    6: [275,36,5]
transition: 15

In this way HA can transition a complex set of colors and zones (i.e. a custom theme) to another complex set of colors in one packet. I would also firmly argue that this should be integrated into scenes. Seems like the natural place for it.

Thanks for attending my TED talk.

This is relative easy to do with aiolifx. Here’s a proof of concept script that I’m hoping you can convert into a pyscript for your own purposes:

import asyncio

from aiolifx.aiolifx import Light
from aiolifx.connection import LIFXConnection

IP_ADDR="1.2.3.4"
MAC="11:22:33:44:55:66"
TRANSITION_TIME=15

async def async_main():
    """The main loop."""
    connection = LIFXConnection(IP_ADDR, MAC)
    await connection.async_setup()

    beam: Light = connection.device
    
    # we need to get a bunch of values to determine
    # if the device is multizoned otherwise
    # the set_extended_color_zone method doesn't
    # work
    beam.get_version()
    beam.get_hostfirmware()
    beam.get_extended_color_zones()

    # we also have to wait for aiolifx to get
    # replies from the device
    while beam.color_zones is None:
        await asyncio.sleep(0)

    # this will print the current HSBK values as a
    # list of tuples the length of your beam/strip
    print(beam.color_zones)

    # You can then create a new list in any way you
    # like, as long as it's a list of tuples
    new_zones = beam.color_zones

    # The extended_color_zone packet needs 82 zones
    # which is the longest possible strip/beam length
    # so pad your list to that length with blank tuples:    
    for _ in range(len(new_zones),  82):
        new_zones.append((0, 0, 0, 0))

    # Then send a single packet to paint your beam/strip
    duration = int(round(TRANSITION_TIME * 1000))
    beam.set_extended_color_zones(new_zones, len(new_zones), duration=10000)


if __name__ == "__main__":
    """Run the loop."""
    asyncio.run(async_main())

I was hoping the effect.move service call allowed for a custom palette but it doesn’t because the LAN packet that starts the effect doesn’t support it, unlike effect.morph which does. Matrix devices have better palette handling. To be honest, I don’t use the lifx.set_state script much at all these days. For any heavy lifting I use either Photons (as a library) or Photons Interactor via REST.

This is a good scaffolding and helps me with the async stuff that I haven’t played with much. I appreciate it! Let me poke at this a bit and I’ll get back to you.

If you want to wait for aiolifx to have handled all expected responses, change the while loop to this:

while len(beam.messages) > 0:
    ...

Reason: whenever aiolifx gets a response, it adds that packet to beams.message and removes it after the content of the response has been actioned. It has it’s own timeout mechanism, so you don’t have to add one yourself.

Oddly enough I can get any print() or log.warning() style commands to print to the log from inside the loop. I had wanted to see the structure of the reply. Oh well - tomorrow with fresh eyes.