Exemplo n.º 1
0
class get_chain_state(task.Task):
    """
    Get the colors of the tiles in your chain
    """

    target = task.requires_target()
    reference = task.provides_reference(special=True)

    async def execute_task(self, **kwargs):

        async with self.target.session() as sender:
            plans = sender.make_plans("parts_and_colors")

            def error(e):
                log.error(e)

            async for serial, _, parts in sender.gatherer.gather(
                    plans, self.reference, error_catcher=error):
                if not parts or not parts[0].device.cap.has_matrix:
                    continue

                print(serial)
                for part in parts:
                    print(f"    Tile {part.part_number}")
                    for i, color in enumerate(part.colors):
                        color = (
                            round(color[0], 3),
                            round(color[1], 3),
                            round(color[2], 3),
                            color[3],
                        )
                        print(f"        color {i:<2d}", repr(color))
                    print("")
Exemplo n.º 2
0
class get_clean_status(task.Task):
    """
    Get the current cleaning status from the specified bulb.
    """

    target = task.requires_target()
    reference = task.provides_reference(special=True)

    async def execute_task(self, **kwargs):
        async with self.target.session() as sender:
            plans = sender.make_plans("hev_status")
            async for serial, _, info in sender.gatherer.gather(
                    plans, self.reference, **kwargs):
                if info is not Skip:
                    print(serial)
                    if info["current"]["active"]:
                        power_state = "off" if info["current"][
                            "last_power"] == 0 else "on"
                        remaining = humanize_duration(
                            info["current"]["remaining"])
                        print("    Cycle in progress: yes")
                        print(f"    Time left: {remaining}")
                        print(f"    Power when done: {power_state}")
                    else:
                        print("    Cycle in progress: no")
                        print(
                            f"    Last cycle result: {info['last']['result'].name}"
                        )
                    print("")
Exemplo n.º 3
0
class get_effects(task.Task):
    """
    Determine what firmware effects are running on your devices
    """

    target = task.requires_target()
    reference = task.provides_reference(special=True)

    async def execute_task(self, **kwargs):
        async with self.target.session() as sender:
            plans = sender.make_plans("firmware_effects")

            async for serial, _, info in sender.gatherer.gather(plans, self.reference):
                if info is Skip:
                    continue

                print(f"{serial}: {info['type']}")
                for field, value in info["options"].items():
                    if field == "palette":
                        if value:
                            print("\tpalette:")
                            for c in value:
                                print(f"\t\t{repr(c)}")
                    else:
                        print(f"\t{field}: {value}")
                print()
Exemplo n.º 4
0
class find_devices(task.Task):
    """
    List the devices that can be found on the network::

        lifx lan:find_devices

    You can specify a different broadcast address by saying::

        lifx lan:find_devices _ 192.168.0.255

    Otherwise it will use the default broadcast address for the target you are
    using. (i.e. the lan target by default broadcasts to 255.255.255.255)

    You can find specific devices by specifying a reference::

        lifx lan:find_devices match:label=kitchen
    """

    target = task.requires_target()
    artifact = task.provides_artifact()
    reference = task.provides_reference(special=True)

    async def execute_task(self, **kwargs):
        broadcast = True
        if self.artifact is not sb.NotSpecified:
            broadcast = self.artifact

        async with self.target.session() as sender:
            _, serials = await self.reference.find(sender,
                                                   timeout=20,
                                                   broadcast=broadcast)
            for serial in serials:
                print(serial)
Exemplo n.º 5
0
class apply_theme(task.Task):
    """
    Apply a theme to specified device

    ``lan:apply_theme d073d5000001 -- `{"colors": [<color>, <color>, ...], "overrides": {<hsbk dictionary>}}'``

    If you don't specify serials, then the theme will apply to all devices found
    on the network.

    Colors may be words like "red", "blue", etc. Or may be [h, s, b, k] arrays
    where each part is optional.

    You may also specify ``duration`` which is how long to take to apply in
    seconds.

    And you may also supply ``overrides`` with ``hue``, ``saturation``,
    ``brightness`` and ``kelvin`` to override the specified colors.
    """

    target = task.requires_target()
    reference = task.provides_reference(special=True)

    async def execute_task(self, **kwargs):
        def errors(e):
            log.error(e)

        msg = ApplyTheme.msg(self.collector.photons_app.extra_as_json)
        await self.target.send(msg,
                               self.reference,
                               error_catcher=errors,
                               message_timeout=2)
Exemplo n.º 6
0
class set_tile_positions(task.Task):
    """
    Set the positions of the tiles in your chain.

    ``lan:set_tile_positions d073d5f09124 -- '[[0, 0], [-1, 0], [-1, 1]]'``
    """

    target = task.requires_target()
    reference = task.provides_reference(special=True)

    async def execute_task(self, **kwargs):
        extra = self.photons_app.extra_as_json
        positions = sb.listof(sb.listof(sb.float_spec())).normalise(
            Meta.empty(), extra)

        if any(len(position) != 2 for position in positions):
            raise PhotonsAppError(
                "Please enter positions as a list of two item lists of user_x, user_y"
            )

        async def gen(reference, sender, **kwargs):
            ps = sender.make_plans("capability")
            async for serial, _, info in sender.gatherer.gather(
                    ps, reference, **kwargs):
                if info["cap"].has_matrix:
                    for i, (user_x, user_y) in enumerate(positions):
                        yield TileMessages.SetUserPosition(
                            tile_index=i,
                            user_x=user_x,
                            user_y=user_y,
                            res_required=False,
                            target=serial,
                        )

        await self.target.send(FromGenerator(gen), self.reference)
Exemplo n.º 7
0
class attr_actual(task.Task):
    """
    Same as the attr command but prints out the actual values on the replies rather than transformed values
    """

    target = task.requires_target()
    artifact = task.provides_artifact()
    reference = task.provides_reference(special=True)

    async def execute_task(self, **kwargs):
        protocol_register = self.collector.configuration["protocol_register"]

        if self.artifact is sb.NotSpecified:
            raise BadOption(
                f"Please specify what you want to get\nUsage: {sys.argv[0]} <target>:attr_actual <reference> <attr_to_get>"
            )

        kls = find_packet(protocol_register, self.artifact, "")
        if kls is None:
            raise BadOption(
                "Sorry, couldn't a class for this message", prefix="", want=self.artifact
            )

        extra = self.photons_app.extra_as_json

        if "extra_payload_kwargs" in kwargs:
            extra.update(kwargs["extra_payload_kwargs"])

        def lines(pkt, indent="    "):
            for field in pkt.Meta.all_names:
                val = pkt[field]
                if isinstance(val, list):
                    yield f"{indent}{field}:"
                    for item in val:
                        ind = f"{indent}    "
                        ls = list(lines(item, ind))
                        first = list(ls[0])
                        first[len(indent) + 2] = "*"
                        ls[0] = "".join(first)
                        yield from ls
                else:
                    yield f"{indent}{field}: {pkt.actual(field)}"

        msg = kls.create(extra)
        async for pkt in self.target.send(msg, self.reference, **kwargs):
            print()
            print(f"""{"=" * 10}: {pkt.serial}""")
            for line in lines(pkt):
                print(line)
Exemplo n.º 8
0
class get_zones(task.Task):
    """
    Get the zones colors from a multizone device
    """

    target = task.requires_target()
    reference = task.provides_reference(special=True)

    async def execute_task(self, **kwargs):
        async with self.target.session() as sender:
            async for serial, zones in zones_from_reference(
                    self.reference, sender):
                print(serial)
                for zone, color in zones:
                    print("\tZone {0}: {1}".format(zone, repr(color)))
Exemplo n.º 9
0
class DeviceFinderTask(task.Task):
    target = task.requires_target()
    reference = task.provides_reference()

    async def make_device_finder(self, sender):
        if self.reference is not sb.NotSpecified:
            reference = self.collector.reference_object(self.reference)
        else:
            reference = DeviceFinder.from_options(self.photons_app.extra_as_json)

        if not isinstance(reference, DeviceFinder):
            found, serials = await reference.find(sender, timeout=5)
            reference.raise_on_missing(found)
            reference = DeviceFinder.from_options({"serial": serials})

        return reference
Exemplo n.º 10
0
class stop_clean_cycle(task.Task):
    """
    Stop a cleaning cycle on an HEV-capable bulb

    ``target:stop_clean_cycle match:cap=hev``

    """

    target = task.requires_target()
    reference = task.provides_reference(special=True)

    async def execute_task(self, **kwargs):
        options = sb.dictionary_spec().normalise(
            Meta.empty(), self.photons_app.extra_as_json)
        options["enable"] = False
        await self.target.send(ChangeCleanCycle(**options), self.reference)
Exemplo n.º 11
0
class AnimationTask(task.Task):
    target = task.requires_target()
    artifact = task.provides_artifact()
    reference = task.provides_reference()

    animation_kls = NotImplemented
    animation_name = None

    async def execute_task(self, **kwargs):
        if self.reference == "help":
            print_help(animation_kls=self.animation_kls,
                       animation_name=self.animation_name)
            return

        self.reference = self.collector.reference_object(self.reference)
        return await self.run_animation(**kwargs)

    @property
    def run_options(self):
        return {}

    @property
    def message_timeout(self):
        return 1

    def error_catcher(self, e):
        log.error(e)

    async def run_animation(self, **kwargs):
        try:
            with self.collector.photons_app.using_graceful_future(
            ) as final_future:
                async with self.target.session() as sender:
                    runner = AnimationRunner(
                        sender,
                        self.reference,
                        self.run_options,
                        final_future=final_future,
                        message_timeout=self.message_timeout,
                        error_catcher=self.error_catcher,
                    )

                    async with runner:
                        await runner.run()
        except Finish:
            pass
Exemplo n.º 12
0
class start_clean_cycle(task.Task):
    """
    Start a cleaning cycle on an HEV-capable bulb

    ``target:start_clean_cycle match:cap=hev -- '{"duration_s": 7200}'``

    It  takes a ``duration_s`` field that is the seconds of the duration. If not
    provided, or set to 0, the default duration will be used.
    """

    target = task.requires_target()
    reference = task.provides_reference(special=True)

    async def execute_task(self, **kwargs):
        options = sb.dictionary_spec().normalise(
            Meta.empty(), self.photons_app.extra_as_json)
        options["enable"] = True
        await self.target.send(ChangeCleanCycle(**options), self.reference)
Exemplo n.º 13
0
class effect_off(task.Task):
    target = task.requires_target()
    reference = task.provides_reference(special=True)

    async def execute_task(self, **kwargs):
        async def gen(reference, sender, **kwargs):
            plans = sender.make_plans("capability")
            async for serial, _, info in sender.gatherer.gather(plans, reference):
                print(f"Turning off effects for {serial}")

                yield LightMessages.SetWaveformOptional(res_required=False, target=serial)

                if info["cap"].has_multizone:
                    yield SetZonesEffect("OFF", power_on=False)
                elif info["cap"].has_matrix:
                    yield SetTileEffect("OFF", power_on=False)

        await self.target.send(FromGenerator(gen), self.reference)
Exemplo n.º 14
0
class pack_payload(task.Task):
    """
    Pack json found after the ``--`` into hexlified string

    ``pack_payload 21 -- '{"level": 65535}'``
    """

    reference = task.provides_reference()

    async def execute_task(self, **kwargs):
        extra = self.photons_app.extra_as_json
        message_register = protocol_register.message_register(1024)

        if "extra_payload_kwargs" in kwargs:
            extra.update(kwargs["extra_payload_kwargs"])

        packd = Messages.pack_payload(self.reference, extra, message_register)
        print(binascii.hexlify(packd.tobytes()).decode())
Exemplo n.º 15
0
class arrange(task.GracefulTask):
    """
    Start a web GUI you can use to change the positions of the panels in your tile sets
    such that the tile sets know where they are relative to each other.

    Your web browser will automatically open to this GUI unless you have ``NO_WEB_OPEN=1``
    in your environment.
    """

    target = task.requires_target()
    reference = task.provides_reference(special=True)

    @property
    def options(self):
        return self.collector.configuration["arranger"]

    async def open_browser(self):
        async with hp.tick(0.1, max_time=3) as ticker:
            async for _ in ticker:
                if port_connected(self.options.port):
                    break

        if not port_connected(self.options.port):
            self.photons_app.final_future.set_exception(
                PhotonsAppError("Failed to start the server"))
            return

        if "NO_WEB_OPEN" not in os.environ:
            webbrowser.open(f"http://{self.options.host}:{self.options.port}")

    async def execute_task(self, graceful_final_future, **kwargs):
        self.task_holder.add(self.open_browser())

        async with self.target.session() as sender:
            await Server(self.photons_app.final_future,
                         server_end_future=graceful_final_future).serve(
                             self.options.host,
                             self.options.port,
                             self.options,
                             self.task_holder,
                             sender,
                             self.reference,
                             self.photons_app.cleaners,
                         )
Exemplo n.º 16
0
class get_clean_config(task.Task):
    """
    Get the default HEV cleaning cycle configuration values for the specified devices
    """

    target = task.requires_target()
    reference = task.provides_reference(special=True)

    async def execute_task(self, **kwargs):
        async with self.target.session() as sender:
            plans = sender.make_plans("hev_config")
            async for serial, _, info in sender.gatherer.gather(
                    plans, self.reference, **kwargs):
                if info is not Skip:
                    indicator = "yes" if info["indication"] else "no"
                    duration_s = humanize_duration(info["duration_s"])
                    print(serial)
                    print(f"    Clean cycle duration: {duration_s}")
                    print(f"    Flash when cycle ends: {indicator}")
                    print("")
Exemplo n.º 17
0
class get_device_chain(task.Task):
    """
    Get the devices in your chain
    """

    target = task.requires_target()
    reference = task.provides_reference(special=True)

    async def execute_task(self, **kwargs):
        async def gen(reference, sender, **kwargs):
            ps = sender.make_plans("capability")
            async for serial, _, info in sender.gatherer.gather(
                    ps, reference, **kwargs):
                if info["cap"].has_matrix:
                    yield TileMessages.GetDeviceChain(target=serial)

        async for pkt in self.target.send(FromGenerator(gen), self.reference):
            print(pkt.serial)
            for tile in tiles_from(pkt):
                print("    ", repr(tile))
Exemplo n.º 18
0
class attr(task.Task):
    """
    Send a message to your bulb and print out all the replies.

    ``target:attr d073d5000000 GetHostFirmware``
    """

    target = task.requires_target()
    artifact = task.provides_artifact()
    reference = task.provides_reference(special=True)

    async def execute_task(self, **kwargs):
        protocol_register = self.collector.configuration["protocol_register"]

        if self.artifact is sb.NotSpecified:
            raise BadOption(
                f"Please specify what you want to get\nUsage: {sys.argv[0]} <target>:attr <reference> <attr_to_get>"
            )

        kls = find_packet(protocol_register, self.artifact, "")
        if kls is None:
            raise BadOption(
                "Sorry, couldn't a class for this message", prefix="", want=self.artifact
            )

        extra = self.photons_app.extra_as_json

        if "extra_payload_kwargs" in kwargs:
            extra.update(kwargs["extra_payload_kwargs"])

        msg = kls.create(extra)
        async with self.target.session() as sender:
            found, serials = await self.reference.find(sender, timeout=20)
            self.reference.raise_on_missing(found)

            msg = kls.create(extra)
            async for pkt in sender(msg, serials, **kwargs):
                if len(serials) == 1:
                    print(repr(pkt.payload))
                else:
                    print(f"{pkt.serial}: {repr(pkt.payload)}")
Exemplo n.º 19
0
class set_clean_config(task.Task):
    """
    Set the default clean cycle configuration for a device

    ``target:clean_config match:cap=hev -- '{"indication": false, "duration_s": 7200}'``

    Options are:
        indication: run a short flashing indication at the end of the HEV cycle.
        duration_s: in seconds, how long to run a cleaning cycle by default
    """

    target = task.requires_target()
    reference = task.provides_reference(special=True)

    async def execute_task(self, **kwargs):
        options = sb.set_options(
            indication=sb.required(sb.boolean()),
            duration_s=sb.required(sb.integer_spec()),
        ).normalise(Meta.empty(), self.photons_app.extra_as_json)

        await self.target.send(SetCleanConfig(**options), self.reference,
                               **kwargs)
Exemplo n.º 20
0
class tile_effect(task.Task):
    """
    Set an animation on your tile!

    ``lan:tile_effect d073d5000001 <type> -- '{<options>}'``

    Where type is one of the available effect types:

    OFF
        Turn of the animation off

    MORPH
        Move through a perlin noise map, assigning pixel values from a
        16-color palette

    FLAME
        A flame effect

    For effects that take in a palette option, you may specify palette as
    ``[{"hue": 0, "saturation": 1, "brightness": 1, "kelvin": 2500}, ...]``

    or as ``[[0, 1, 1, 2500], ...]`` or as ``[[0, 1, 1], ...]``

    or as ``["red", "hue:100 saturation:1", "blue"]``
    """

    target = task.requires_target()
    artifact = task.provides_artifact()
    reference = task.provides_reference(special=True)

    async def execute_task(self, **kwargs):
        options = self.photons_app.extra_as_json

        if self.artifact is sb.NotSpecified:
            raise PhotonsAppError(
                "Please specify type of effect with --artifact")

        await self.target.send(SetTileEffect(self.artifact, **options),
                               self.reference)
Exemplo n.º 21
0
class arranger_assets(task.Task):
    reference = task.provides_reference()

    async def execute_task(self, **kwargs):
        extra = self.photons_app.extra
        assets = self.collector.configuration["arranger"].assets
        available = ["run", "install", "static", "watch"]

        if self.reference is sb.NotSpecified:
            raise PhotonsAppError("Please specify what command to run",
                                  available=available)

        assets.ensure_npm()

        try:
            if self.reference == "install":
                assets.run("install", *shlex.split(extra))
                return

            if self.reference == "run":
                assets.run(*shlex.split(extra))
                return

            if assets.needs_install:
                assets.run("ci", no_node_env=True)

            if self.reference == "static":
                assets.run("run", "build")

            elif self.reference == "watch":
                assets.run("run", "generate")

            else:
                raise PhotonsAppError("Didn't get a recognised command",
                                      want=self.reference,
                                      available=available)
        except subprocess.CalledProcessError as error:
            raise PhotonsAppError("Failed to run command", error=error)
Exemplo n.º 22
0
class get_tile_positions(task.Task):
    """
    Get the positions of the tiles in your chain.

    ``lan:get_tile_positions d073d5f09124``
    """

    target = task.requires_target()
    reference = task.provides_reference(special=True)

    async def execute_task(self, **kwargs):
        async def gen(reference, sender, **kwargs):
            ps = sender.make_plans("capability")
            async for serial, _, info in sender.gatherer.gather(
                    ps, reference, **kwargs):
                if info["cap"].has_matrix:
                    yield TileMessages.GetDeviceChain(target=serial)

        async for pkt in self.target.send(FromGenerator(gen), self.reference):
            print(pkt.serial)
            for tile in tiles_from(pkt):
                print(f"\tuser_x: {tile.user_x}, user_y: {tile.user_y}")
            print("")
Exemplo n.º 23
0
class multizone_effect(task.Task):
    """
    Set an animation on your multizone device

    ``lan:multizone_effect d073d5000001 <type> -- '{<options>}'``

    Where type is one of the available effect types:

    OFF
        Turn the animation off

    MOVE
        A moving animation

    Options include:
      - speed: duration in seconds to complete one cycle of the effect
      - duration: duration in seconds the effect will run.
      - direction: either "left" or "right" (default: "right")

    Example:
        ``{"speed": 5, "duration": 0, "direction": "left"}``

    """

    target = task.requires_target()
    artifact = task.provides_artifact()
    reference = task.provides_reference(special=True)

    async def execute_task(self, **kwargs):
        options = self.photons_app.extra_as_json

        if self.artifact is sb.NotSpecified:
            raise PhotonsAppError(
                "Please specify type of effect with --artifact")

        await self.target.send(SetZonesEffect(self.artifact, **options),
                               self.reference)
Exemplo n.º 24
0
class set_color(task.Task):
    """
    Change specified bulb to specified colour

    ``target:set_color d073d50000 red -- '{"hue": 205}'``

    The format of this task is ``<reference> <color> -- <overrides>`` where
    overrides is optional.

    The color may be any valid color specifier.
    """

    target = task.requires_target()
    artifact = task.provides_artifact()
    reference = task.provides_reference(special=True)

    async def execute_task(self, **kwargs):
        overrides = self.photons_app.extra_as_json

        if self.artifact is sb.NotSpecified:
            raise PhotonsAppError("Please specify a color as artifact")

        msg = ColourParser.msg(self.artifact, overrides)
        await self.target.send(msg, self.reference)
Exemplo n.º 25
0
class set_zones(task.Task):
    """
    Set the zones colors on a multizone device

    Usage looks like::

        lifx lan:set_zones d073d5000001 -- '{"colors": [["red", 10], ["blue", 3], ["green", 5]]}'

    In that example the device will have the first 10 zones set to red, then 3
    blue zones and then 5 green zones
    """

    target = task.requires_target()
    reference = task.provides_reference(special=True)

    async def execute_task(self, **kwargs):
        options = self.photons_app.extra_as_json

        if "colors" not in options:
            raise PhotonsAppError(
                """Say something like ` -- '{"colors": [["red", 10], ["blue", 3]]}'`"""
            )

        await self.target.send(SetZones(**options), self.reference)
Exemplo n.º 26
0
class animate(task.Task):
    """
    Run animations on LIFX tile sets.

    Run `lifx lan:animate help` for more information
    """

    target = task.requires_target()
    artifact = task.provides_reference()
    reference = task.provides_reference()

    async def execute_task(self, **kwargs):
        if self.reference == "help":
            if self.artifact in register.animations:
                print_help(
                    animation_kls=register.animations[self.artifact].Animation,
                    animation_name=self.artifact,
                )
            else:
                print_help()
            return

        if self.reference in register.available_animations():
            ref = self.artifact
            self.artifact = self.reference
            self.reference = ref

        extra = self.collector.photons_app.extra_as_json
        reference = self.collector.reference_object(self.reference)

        options = {}
        specific_animation = self.artifact not in (None, "", sb.NotSpecified)

        if specific_animation:
            options = extra
            run_options = extra.pop("run_options", {})
        else:
            run_options = extra
            if isinstance(run_options, list):
                run_options = {"animations": run_options}

        if specific_animation:
            background = sb.NotSpecified
            layered = {
                "animations": [[self.artifact, background, options]],
                "animation_limit": 1
            }
            run_options = MergedOptions.using(layered, run_options).as_dict()

        def errors(e):
            if isinstance(e, KeyboardInterrupt):
                return

            if not isinstance(e, PhotonsAppError):
                log.exception(e)
            else:
                log.error(e)

        conf = self.collector.configuration
        photons_app = conf["photons_app"]

        with photons_app.using_graceful_future() as final_future:
            async with self.target.session() as sender:
                runner = AnimationRunner(
                    sender,
                    reference,
                    run_options,
                    final_future=final_future,
                    error_catcher=errors,
                    animation_options=conf.get("animation_options", {}),
                )
                async with runner:
                    await runner.run()
Exemplo n.º 27
0
class find_ips(task.Task):
    """
    List the ips of the devices that can be found on the network

        lifx lan:find_ips

    You can specify a different broadcast address by saying::

        lifx lan:find_ips 192.168.0.255

    Options include:

    cli_output - default True
        Print "{serial}: {ip}" for each device found

        Note that if you choose settings_output or env_output then this will
        default to False. Explicitly setting it to true will turn it on.

    settings_output - default False
        Print yaml output that you can copy into a lifx.yml

    env_output - default False
        Print an ENV variable you can copy into your terminal to set these
        ips as a HARDCODED_DISCOVERY for future commands.
    """

    target = task.requires_target()
    artifact = task.provides_artifact()
    reference = task.provides_reference(special=True)

    async def execute_task(self, **kwargs):
        broadcast = True
        if self.artifact is not sb.NotSpecified:
            broadcast = self.artifact

        options = sb.set_options(
            cli_output=sb.defaulted(sb.boolean(), None),
            env_output=sb.defaulted(sb.boolean(), False),
            settings_output=sb.defaulted(sb.boolean(), False),
        ).normalise(Meta.empty(), self.collector.photons_app.extra_as_json)

        if options["env_output"] is False and options[
                "settings_output"] is False:
            if options["cli_output"] is None:
                options["cli_output"] = True

        env_output = options["env_output"]
        cli_output = options["cli_output"]
        settings_output = options["settings_output"]

        ips = {}

        async with self.target.session() as sender:
            found, serials = await self.reference.find(sender,
                                                       timeout=20,
                                                       broadcast=broadcast)
            for serial in serials:
                services = found[binascii.unhexlify(serial)]
                if Services.UDP in services:
                    ip = services[Services.UDP].host
                    ips[serial] = ip

        sorted_ips = sorted(ips.items(),
                            key=lambda item: ipaddress.ip_address(item[1]))

        if cli_output:
            for serial, ip in sorted_ips:
                print(f"{serial}: {ip}")

        if cli_output and (env_output or settings_output):
            print()

        if env_output:
            print(f"export HARDCODED_DISCOVERY='{json.dumps(sorted_ips)}'")
            if settings_output:
                print()

        if settings_output:
            print("discovery_options:")
            print("  hardcoded_discovery:")

            for serial, ip in sorted_ips:
                print(f'    {serial}: "{ip}"')
Exemplo n.º 28
0
class set_chain_state(task.Task):
    """
    Set the state of colors on your tile

    So say you have state.json::

        {
            "colors": [[[0, 1, 0, 3500], [0, 1, 0, 3500]], [[100, 0, 1, 3500], ...], ...],
            "tile_index": 0,
            "length": 1,
            "x": 0,
            "y": 0
        }

    ``lan:set_chain_state d073d5f09124 -- file://state.json``

    Where the colors is a grid of 8 rows of 8 ``[h, s, b, k]`` values.

    Rows with less than 8 will fill out with zero values for that row and if there are less
    than 8 rows, then remaining values up to the 64 on the device will be zeroed out.
    """

    target = task.requires_target()
    reference = task.provides_reference(special=True)

    async def execute_task(self, **kwargs):
        options = self.photons_app.extra_as_json

        width = options.get("width", 8)
        options["width"] = width

        if "colors" in options:
            spec = sb.listof(
                sb.listof(
                    list_spec(sb.integer_spec(), sb.float_spec(),
                              sb.float_spec(), sb.integer_spec())))
            colors = spec.normalise(Meta.empty().at("colors"),
                                    options["colors"])

            row_lengths = [len(row) for row in colors]
            if len(set(row_lengths)) != 1:
                raise PhotonsAppError(
                    "Please specify colors as a grid with the same length rows",
                    got=row_lengths)

            cells = []
            for row in colors:
                while len(row) < width:
                    row.append(None)

                for col in row:
                    if col is None:
                        cells.append({
                            "hue": 0,
                            "saturation": 0,
                            "brightness": 0,
                            "kelvin": 3500
                        })
                        continue

                    cells.append({
                        "hue": col[0],
                        "saturation": col[1],
                        "brightness": col[2],
                        "kelvin": col[3],
                    })

            options["colors"] = cells
        else:
            raise PhotonsAppError(
                "Please specify colors in options after -- as a grid of [h, s, b, k]"
            )

        missing = []
        for field in TileMessages.Set64.Payload.Meta.all_names:
            if field not in options and field not in ("duration", "reserved6"):
                missing.append(field)

        if missing:
            raise PhotonsAppError(
                "Missing options for the SetTileState message",
                missing=missing)

        options["res_required"] = False
        msg = TileMessages.Set64.create(**options)
        await self.target.send(msg, self.reference)