LIFX Developer Zone

Need help running scripts

Hello. I’m attempting to create my own scripts to run on a LAN network. Either A I’m just a huge newby at this, or I’m missing something that I cannot see. I’m attempting to run the following script:

from photons_app.special import HardCodedSerials
from photons_messages import DeviceMessages, LightMessages

async def my_action(target):
reference = HardCodedSerials([“d073d528ce91”, “d073d528c5d2”, “d073d528566b”, “d073d5285d16”])

async with target.session() as sender:
    found, serials = await reference.find(sender, timeout=5)
    reference.raise_on_missing(found)

assert serials == ["d073d528ce91", "d073d528c5d2", "d073d528566b", "d073d5285d16"]

get_color1 = LightMessages.GetColor(target="d073d528c5d2")
get_color2 = LightMessages.GetColor(target="d073d528566b")
get_color3 = LightMessages.GetColor(target="d073d528ce91")
get_color4 = LightMessages.GetColor(target="d073d5285d16")


get_power1 = DeviceMessages.GetPower(target="d073d528c5d2")
get_power2 = DeviceMessages.GetPower(target="d073d528566b")
get_power3 = DeviceMessages.GetPower(target="d073d528ce91")
get_power4 = DeviceMessages.GetPower(target="d073d5285d16")


async with pkt in sender([get_color1, get_color2, get_color3, get_color4,
                          get_power1, get_power2, get_power3, get_power4
                          ]):
    if pkt | LightMessages.LightState:
        assert pkt.serial == ["d073d528ce91", "d073d528c5d2", "d073d528566b", "d073d5285d16"]

    elif pkt | DeviceMessages.StatePower:
        assert pkt.serial in ("d073d528ce91", "d073d528c5d2", "d073d528566b", "d073d5285d16")

if name == “main”:
my_action(target)

I’m learning as I go, but would like to first discover my lights using their MAC address, then give each light individual commands. First and foremost would like to discover them though, but getting a “target not defined” error message. If I’m not mistaken is this not defined from everything under the “async def” notation? Or is this entirely wrong?

Hello!

That’s a really good start :slight_smile:

So, first of all, trick with showing code in the forum. If you have three bactick characters on their own line before and after code it’ll format the code nicer.

So

```
some code
```

Becomes

some code

In this case, I think the full sample above is this?

from photons_app.special import HardCodedSerials
from photons_messages import DeviceMessages, LightMessages

async def my_action(target):
    reference = HardCodedSerials(["d073d528ce91", "d073d528c5d2", "d073d528566b", "d073d5285d16"])

    async with target.session() as sender:
        found, serials = await reference.find(sender, timeout=5)
        reference.raise_on_missing(found)

        assert serials == ["d073d528ce91", "d073d528c5d2", "d073d528566b", "d073d5285d16"]

        get_color1 = LightMessages.GetColor(target="d073d528c5d2")
        get_color2 = LightMessages.GetColor(target="d073d528566b")
        get_color3 = LightMessages.GetColor(target="d073d528ce91")
        get_color4 = LightMessages.GetColor(target="d073d5285d16")


        get_power1 = DeviceMessages.GetPower(target="d073d528c5d2")
        get_power2 = DeviceMessages.GetPower(target="d073d528566b")
        get_power3 = DeviceMessages.GetPower(target="d073d528ce91")
        get_power4 = DeviceMessages.GetPower(target="d073d5285d16")


        async with pkt in sender([get_color1, get_color2, get_color3, get_color4,
                                get_power1, get_power2, get_power3, get_power4
                                ]):
            if pkt | LightMessages.LightState:
                assert pkt.serial == ["d073d528ce91", "d073d528c5d2", "d073d528566b", "d073d5285d16"]

            elif pkt | DeviceMessages.StatePower:
                assert pkt.serial in ("d073d528ce91", "d073d528c5d2", "d073d528566b", "d073d5285d16")

if name == "main":
    my_action(target)

So the problem here is in that last line target doesn’t exist. So you need to tell Photons to create one.

The way I recommend is using the photons_core.run method. https://photons.delfick.com/scripts/photons_action.html

So your example would look like

from photons_app.special import HardCodedSerials
from photons_app.actions import an_action

from photons_messages import DeviceMessages, LightMessages


@an_action(needs_target=True)
async def action(collector, target, **kwargs):
    reference = HardCodedSerials(
        ["d073d528ce91", "d073d528c5d2", "d073d528566b", "d073d5285d16"]
    )

    async with target.session() as sender:
        found, serials = await reference.find(sender, timeout=5)
        reference.raise_on_missing(found)

        assert serials == [
            "d073d528ce91",
            "d073d528c5d2",
            "d073d528566b",
            "d073d5285d16",
        ]

        get_color1 = LightMessages.GetColor(target="d073d528c5d2")
        get_color2 = LightMessages.GetColor(target="d073d528566b")
        get_color3 = LightMessages.GetColor(target="d073d528ce91")
        get_color4 = LightMessages.GetColor(target="d073d5285d16")

        get_power1 = DeviceMessages.GetPower(target="d073d528c5d2")
        get_power2 = DeviceMessages.GetPower(target="d073d528566b")
        get_power3 = DeviceMessages.GetPower(target="d073d528ce91")
        get_power4 = DeviceMessages.GetPower(target="d073d5285d16")

        msgs = [
            get_color1,
            get_color2,
            get_color3,
            get_color4,
            get_power1,
            get_power2,
            get_power3,
            get_power4,
        ]

        async for pkt in sender(msgs):
            if pkt | LightMessages.LightState:
                assert pkt.serial in [
                    "d073d528ce91",
                    "d073d528c5d2",
                    "d073d528566b",
                    "d073d5285d16",
                ]

            elif pkt | DeviceMessages.StatePower:
                assert pkt.serial in (
                    "d073d528ce91",
                    "d073d528c5d2",
                    "d073d528566b",
                    "d073d5285d16",
                )


if __name__ == "__main__":
    __import__("photons_core").run("lan:action {@:1:}")

And then if you have that in a script say my_script.py, you would say python my_script.py and it would work.

We can make it better though. So if you already know the exact serials then you don’t need to do discovery yourself because Photons will realise it doesn’t know where they are when you tell it to give messages to those devices. So we can shorten to something like

from photons_app.actions import an_action

from photons_messages import DeviceMessages, LightMessages


@an_action(needs_target=True)
async def action(collector, target, **kwargs):
    ds = ["d073d528ce91", "d073d528c5d2", "d073d528566b", "d073d5285d16"]

    async with target.session() as sender:
        get_color1 = LightMessages.GetColor(target=ds[0])
        get_color2 = LightMessages.GetColor(target=ds[1])
        get_color3 = LightMessages.GetColor(target=ds[2])
        get_color4 = LightMessages.GetColor(target=ds[3])

        get_power1 = DeviceMessages.GetPower(target=ds[0])
        get_power2 = DeviceMessages.GetPower(target=ds[1])
        get_power3 = DeviceMessages.GetPower(target=ds[2])
        get_power4 = DeviceMessages.GetPower(target=ds[3])

        msgs = [
            get_color1,
            get_color2,
            get_color3,
            get_color4,
            get_power1,
            get_power2,
            get_power3,
            get_power4,
        ]

        async for pkt in sender(msgs):
            if pkt | LightMessages.LightState:
                assert pkt.serial in [
                    "d073d528ce91",
                    "d073d528c5d2",
                    "d073d528566b",
                    "d073d5285d16",
                ]

            elif pkt | DeviceMessages.StatePower:
                assert pkt.serial in (
                    "d073d528ce91",
                    "d073d528c5d2",
                    "d073d528566b",
                    "d073d5285d16",
                )


if __name__ == "__main__":
    __import__("photons_core").run("lan:action {@:1:}")

Now in this particular example your messages are the same for your devices so you can skip the specific targets

from photons_app.actions import an_action

from photons_messages import DeviceMessages, LightMessages


@an_action(needs_target=True)
async def action(collector, target, **kwargs):
    ds = ["d073d528ce91", "d073d528c5d2", "d073d528566b", "d073d5285d16"]

    async with target.session() as sender:
        msgs = [LightMessages.GetColor(), DeviceMessages.GetPower()]

        async for pkt in sender(msgs, ds):
            if pkt | LightMessages.LightState:
                assert pkt.serial in [
                    "d073d528ce91",
                    "d073d528c5d2",
                    "d073d528566b",
                    "d073d5285d16",
                ]

            elif pkt | DeviceMessages.StatePower:
                assert pkt.serial in (
                    "d073d528ce91",
                    "d073d528c5d2",
                    "d073d528566b",
                    "d073d5285d16",
                )


if __name__ == "__main__":
    __import__("photons_core").run("lan:action {@:1:}")

So now lets say you want to send messages based on the results of those. I’d recommend one of these

For example

from photons_app.actions import an_action

from photons_messages import DeviceMessages, LightMessages
from photons_control.script import FromGenerator


@an_action(needs_target=True)
async def action(collector, target, **kwargs):
    async def gen(reference, sender, **kwargs):
        async for pkt in sender(LightMessages.GetColor(), reference):
            if pkt | LightMessages.LightState:
                if pkt.hue > 200:
                    yield DeviceMessages.SetPower(level=0, target=pkt.serial)
                else:
                    yield DeviceMessages.SetPower(level=65535, target=pkt.serial)

    ds = ["d073d528ce91", "d073d528c5d2", "d073d528566b", "d073d5285d16"]
    msg = FromGenerator(gen)
    await target.send(msg, ds)


if __name__ == "__main__":
    __import__("photons_core").run("lan:action {@:1:}")

Also, you can make your script not know the serials themselves.

from photons_app.actions import an_action

from photons_messages import DeviceMessages, LightMessages
from photons_control.script import FromGenerator


@an_action(needs_target=True, special_reference=True)
async def action(collector, target, reference, **kwargs):
    async def gen(reference, sender, **kwargs):
        async for pkt in sender(LightMessages.GetColor(), reference):
            if pkt | LightMessages.LightState:
                if pkt.hue > 200:
                    yield DeviceMessages.SetPower(level=0, target=pkt.serial)
                else:
                    yield DeviceMessages.SetPower(level=65535, target=pkt.serial)

    msg = FromGenerator(gen)
    await target.send(msg, reference)


if __name__ == "__main__":
    __import__("photons_core").run("lan:action {@:1:}")

And then you would run python my_script.py d073d528ce91,d073d528c5d2,d073d528566b,d073d5285d16

Now, I’ve thrown a lot of information at you all at once, so don’t worry if it flies right by your head! And I’ll answer all your questions :slight_smile:

So, what do you want your script to end up being able to do with your lights?

2 Likes

Oh my. I didn’t expect a response so quickly. Thank you so much.

I’ll go through what you’ve written in a bit, but to answer your question, my buddy and I will build a UI that’ll issue commands to the lights using hot keys over a LAN.

So, this particular script I wanted to do a hard discovery on the lights, then power them on with a set color. I then wanted to play around toggling on single, or grouped, lights with different colors.

Oh yeah. Nice :slight_smile:

This may be useful - https://photons.delfick.com/interacting/device_finder.html#device-finder

Also, I wrote a HTTP server for LAN control that might be interesting to look at. Though @Djelibeybi tells me with enough devices it has a bit of a memory and CPU leak :frowning:

Yeah, when I had 55+ bulbs being tracked, the Docker container would leak ~100MB or hour or so. I just added docker restart photons to run via cron each night which solved the problem. :slight_smile:

Hey man! Apologies for the delay in responding.

I tried to run your third script down, one that begins with:

@an_action(needs_target=True)
async def action(collector, target, **kwargs):
    ds = ["d073d528ce91", "d073d528c5d2", "d073d528566b", "d073d5285d1

It returned the following error message:

Traceback (most recent call last):
File “First_Script.py”, line 39, in
import(“photons_core”).run(“lan:action [@:1:}”)
File “/home/omari/.local/lib/python3.8/site-packages/photons_core.py”, line 156, in run
command = CommandSplitter({“argv”: argv or sys.argv}, command).split()
File “/home/omari/.local/lib/python3.8/site-packages/photons_core.py”, line 70, in split
command = shlex.split(self.format())
File “/home/omari/.local/lib/python3.8/site-packages/delfick_project/option_merge/formatter.py”, line 64, in format
return super().format(val)
File “/usr/lib/python3.8/string.py”, line 163, in format
return self.vformat(format_string, args, kwargs)
File “/home/omari/.local/lib/python3.8/site-packages/delfick_project/option_merge/formatter.py”, line 147, in vformat
result = self._vformat(format_string, args, kwargs, used_args, 2)
File “/home/omari/.local/lib/python3.8/site-packages/delfick_project/option_merge/formatter.py”, line 158, in _vformat
for literal_text, field_name, format_spec, conversion in self.parse(format_string):
ValueError: Single ‘}’ encountered in format string

I looked in that formatter.py file and didn’t see any single ‘}’ in the format string. What do you think that problem can be? I have been learning a lot the last few days. Just haven’t been trying hard enough lol.

What’s the last line in your script look like?

Here it is:

if name == “main”:
import(“photons_core”).run(“lan:action [@:1:}”)

And there it is, I used a bracket instead of the squiggly bracket!! Hahahaha. Indeed it worked when I changed it. It helps me better understand what I’m doing when typing it myself instead of copy and pasting. User error!

hehehhe, yay!

Hey man, I have a question about the following script:

What significance is the "msg = FromGenerator(gen) here? I don’t think I see what that variable is accomplishing.

That creates a “special message” object and then we tell the target object to send it to our serials as mentioned in the ds list.

So in this case, that object will use our gen function to generate messages to send out.

This page says more https://photons.delfick.com/interacting/sender_interface.html

Please do tell me if parts in there are still confusing (probably a lot of it, I’m not great at docs :stuck_out_tongue: )

Hey man, honestly the confusing parts to me are the messages that I can send. At some point I wanted to use the Lan Protocol, but I feel Photons is best for the short term right now. I just don’t know how to create messages. This may just be a product of me being new and not knowing how to do so.

Your Photons guide is fine in terms of describing what’s going on. I’m just not seeing how to use it myself. I only want to turn on a target light. I’ve also been trying to figure how to turn it on with a certain color, but I don’t know how to write the functions to do that. Editing your work, or what you have sent up there, hasn’t been helpful, but I think that’s more because I haven’t figured how to play with it other than changing the numbers.

Hello,

Yeah, Photons is a lot. https://github.com/mclarkk/lifxlan might be a better place to start :slight_smile:

In terms of Photons, it focuses on control through sending messages. So in the case of powering on and turning it to red you’d send a message to change power (so either DeviceSetPower or LightSetPower depending on whether you want a duration) and a LightSetWaveformOptional.

Photons does provide a nice helper for creating these particular messages though. You can say something like:

from photons_app.actions import an_action

from photons_control.transform import Transformer


@an_action(needs_target=True)
async def action(collector, target, **kwargs):
    ds = ["d073d528ce91", "d073d528c5d2", "d073d528566b", "d073d5285d16"]
    msg = Transformer.using({"power": "on", "color": "yellow"})
    await target.send(msg, ds)


if __name__ == "__main__":
    __import__("photons_core").run("lan:action {@:1:}")