Exemplo n.º 1
0
async def transform(collector, target, reference, **kwargs):
    """
    Do a http-api like transformation over whatever target you specify

    ``target:transform d073d5000000 -- '{"color": "red", "effect": "pulse"}'``

    It takes in ``color``, ``effect``, ``power`` and valid options for a
    ``SetWaveformOptional``.

    You may also specify ``transform_options`` that change how the transform works.

    keep_brightness
        Ignore brightness options in the request

    transition_color
        If the light is off and we power on, setting this to True will mean the
        color of the light is not set to the new color before we make it appear
        to be on. This defaults to False, which means it will appear to turn on
        with the new color
    """
    extra = collector.photons_app.extra_as_json
    extra = sb.dictionary_spec().normalise(Meta.empty(), extra)

    transform_options = sb.set_options(
        transform_options=sb.dictionary_spec()).normalise(
            Meta.empty(), extra)["transform_options"]

    msg = Transformer.using(extra, **transform_options)

    if not msg:
        raise PhotonsAppError(
            'Please specify valid options after --. For example ``transform -- \'{"power": "on", "color": "red"}\'``'
        )

    await target.send(msg, reference)
Exemplo n.º 2
0
class TransformCommand(store.Command):
    """
    Apply a http api like transformation to the lights
    """

    finder = store.injected("finder")
    target = store.injected("targets.lan")

    matcher = df.matcher_field
    timeout = df.timeout_field
    refresh = df.refresh_field

    transform = dictobj.Field(
        sb.dictionary_spec(),
        wrapper=sb.required,
        help="""
            A dictionary of what options to use to transform the lights with.

            For example,
            ``{"power": "on", "color": "red"}``

            Or,
            ``{"color": "blue", "effect": "breathe", "cycles": 5}``
          """,
    )

    transform_options = dictobj.Field(
        sb.dictionary_spec(),
        help="""
            A dictionay of options that modify the way the tranform
            is performed:

            keep_brightness
                Ignore brightness options in the request

            transition_color
                If the light is off and we power on, setting this to True will mean the
                color of the light is not set to the new color before we make it appear
                to be on. This defaults to False, which means it will appear to turn on
                with the new color
            """,
    )

    async def execute(self):
        fltr = chp.filter_from_matcher(self.matcher, self.refresh)
        msg = Transformer.using(self.transform, **self.transform_options)
        script = self.target.script(msg)
        return await chp.run(
            script, fltr, self.finder, add_replies=False, message_timeout=self.timeout
        )
Exemplo n.º 3
0
    def normalise(self, meta, val):
        if self.unpacking:
            if type(val) in (bitarray, bytes):
                return self.kls.create(self.spec.normalise(meta, val))
            elif isinstance(val, self.kls):
                return val
            elif isinstance(val, dict):
                return self.kls.create(val)
            elif val is sb.NotSpecified:
                return self.kls.create()
            else:
                raise BadSpecValue("Expected to unpack bytes",
                                   found=val,
                                   transforming_into=self.kls)
        else:
            if type(val) not in (bytes, bitarray):
                try:
                    fields = sb.dictionary_spec().normalise(meta, val)
                except BadSpecValue as error:
                    raise BadSpecValue(
                        "Sorry, dynamic fields only supports a dictionary of values",
                        error=error)
                else:
                    val = self.kls.create(fields).pack()

            # The spec is likely a T.Bytes and will ensure we have enough bytes length in the result
            return self.spec.normalise(meta, val)
Exemplo n.º 4
0
    def make_command(self, meta, val, existing):
        v = sb.set_options(path=sb.required(sb.string_spec()),
                           allow_ws_only=sb.defaulted(sb.boolean(),
                                                      False)).normalise(
                                                          meta, val)

        path = v["path"]
        allow_ws_only = v["allow_ws_only"]

        if path not in self.paths:
            raise NoSuchPath(path, sorted(self.paths))

        val = sb.set_options(body=sb.required(
            sb.set_options(args=sb.dictionary_spec(),
                           command=sb.required(sb.string_spec())))).normalise(
                               meta, val)

        args = val["body"]["args"]
        name = val["body"]["command"]

        if existing:
            name = val["body"]["command"] = f"{existing['path']}:{name}"

        extra_context = {}
        if existing:
            extra_context["_parent_command"] = existing["command"]

        everything = meta.everything
        if isinstance(meta.everything, MergedOptions):
            everything = meta.everything.wrapped()
        everything.update(extra_context)
        meta = Meta(everything, []).at("body")

        available_commands = self.paths[path]

        if name not in available_commands:
            raise BadSpecValue(
                "Unknown command",
                wanted=name,
                available=self.available(available_commands,
                                         allow_ws_only=allow_ws_only),
                meta=meta.at("command"),
            )

        command = available_commands[name]["spec"].normalise(
            meta.at("args"), args)

        if not allow_ws_only and command.__whirlwind_ws_only__:
            raise BadSpecValue(
                "Command is for websockets only",
                wanted=name,
                available=self.available(available_commands,
                                         allow_ws_only=allow_ws_only),
                meta=meta.at("command"),
            )

        return command, name
Exemplo n.º 5
0
            def normalise_filled(self, meta, val):
                val = sb.dictionary_spec().normalise(meta, val)

                def normalise(uuid):
                    if "uuid" in val:
                        del val["uuid"]
                    return spec.normalise(meta, {"uuid": uuid, **val})

                return normalise
Exemplo n.º 6
0
 class Fields(dictobj.Spec):
     uuid = dictobj.Field(sb.string_spec, wrapper=sb.required)
     matcher = dictobj.Field(
         json_string_spec(sb.dictionary_spec(), storing), wrapper=sb.required
     )
     power = dictobj.NullableField(sb.boolean)
     color = dictobj.NullableField(sb.string_spec)
     zones = dictobj.NullableField(json_string_spec(sb.listof(hsbk()), storing))
     chain = dictobj.NullableField(json_string_spec(sb.listof(chain_spec), storing))
     duration = dictobj.NullableField(sb.integer_spec)
Exemplo n.º 7
0
    def val_to_kls(self, kls, meta, val):
        if kls is None:
            return self.spec.normalise(meta, val)

        if isinstance(val, kls):
            return val
        elif val is sb.NotSpecified or isinstance(val, (bytes, bitarray)):
            return kls.unpack(self.spec.normalise(meta, val))
        else:
            val = sb.dictionary_spec().normalise(meta, val)
            return kls.normalise(meta, val)
Exemplo n.º 8
0
class TransformCommand(store.Command, DeviceChangeMixin):
    """
    Apply a http api like transformation to the lights
    """

    transform = dictobj.Field(
        sb.dictionary_spec(),
        wrapper=sb.required,
        help="""
            A dictionary of what options to use to transform the lights with.

            For example,
            ``{"power": "on", "color": "red"}``

            Or,
            ``{"color": "blue", "effect": "breathe", "cycles": 5}``
          """,
    )

    transform_options = dictobj.Field(
        sb.dictionary_spec(),
        help="""
            A dictionay of options that modify the way the tranform
            is performed:

            keep_brightness
                Ignore brightness options in the request

            transition_color
                If the light is off and we power on, setting this to True will mean the
                color of the light is not set to the new color before we make it appear
                to be on. This defaults to False, which means it will appear to turn on
                with the new color
            """,
    )

    async def execute(self):
        msg = Transformer.using(self.transform, **self.transform_options)
        return await self.send(msg, add_replies=False)
Exemplo n.º 9
0
class Options(dictobj.Spec):
    host = dictobj.Field(host_spec, help="The host to serve the server on")

    port = dictobj.Field(port_spec, help="The port to serve the server on")

    zeroconf = dictobj.Field(
        Zeroconf.FieldSpec(formatter=MergedOptionStringFormatter),
        help="Options for zeroconf dns discovery",
    )

    database = dictobj.Field(
        lambda: __import__("interactor.database.database").database.database.Database.FieldSpec(
            formatter=MergedOptionStringFormatter
        ),
        help="Database options",
    )

    daemon_options = dictobj.Field(
        sb.dictionary_spec(),
        default={
            "search_interval": 30 * 60,
            "time_between_queries": {
                "LIGHT_STATE": 10 * 60,
                "GROUP": 13 * 60,
                "LOCATION": 16 * 60,
                "FIRMWARE": 24 * 3600,
            },
        },
        help="""
        Options for the device finder daemon. Defaults are::

            { "search_interval": 1800 # do a discovery every 30 minutes
            , "limit": 30 # Limit of 30 messages inflight at any one time
            , "time_between_queries": <shown below>
            }

        The time_between_queries is used to determine how long to between asking devices
        for particular information. It is a dictionary like the following::

            { "LIGHT_STATE": 10 * 60 # label, power, hsbk every 10 minutes
            , "VERSION": None # The type of product can be cached forever
            , "FIRMWARE": 24 * 3600 # Cache the firmware version for a day
            , "GROUP": 13 * 60 # Cache group information for 13 minutes
            , "LOCATION": 16 * 600 # Cache location information for 16 minutes
            }
    """,
    )
Exemplo n.º 10
0
    def normalise(self, meta, val):
        val = sb.dictionary_spec().normalise(meta, val)

        pkt = self.kls()
        for attr, spec in self.attrs:
            if callable(spec):
                spec = spec(pkt, False)

            v = val.get(attr, pkt.actual(attr))
            if attr not in val and attr in self.name_to_group:
                g = self.name_to_group[attr]
                if g in val and attr in val[g]:
                    v = val[g][attr]

            v = spec.normalise(meta.at(attr), v)
            dictobj.__setitem__(pkt, attr, v)

        return pkt
Exemplo n.º 11
0
        assert " ".join(signature(spec)) == want

    it "knows about integer_spec":
        self.assertSignature(sb.integer_spec(), "integer")

    it "knows about float_spec":
        self.assertSignature(sb.float_spec(), "float")

    it "knows about string_spec":
        self.assertSignature(sb.string_spec(), "string")

    it "knows about boolean":
        self.assertSignature(sb.boolean(), "boolean")

    it "knows about dictionary_spec":
        self.assertSignature(sb.dictionary_spec(), "dictionary")

    it "knows about string_choice_spec":
        self.assertSignature(sb.string_choice_spec(["one", "two"]), "choice of (one | two)")

    it "knows about optional_spec":
        self.assertSignature(sb.optional_spec(sb.integer_spec()), "integer (optional)")
        self.assertSignature(sb.optional_spec(sb.any_spec()), "(optional)")

    it "knows about defaulted":
        self.assertSignature(sb.defaulted(sb.integer_spec(), 20), "integer (default 20)")
        self.assertSignature(sb.defaulted(sb.any_spec(), True), "(default True)")

    it "knows about required":
        self.assertSignature(sb.required(sb.integer_spec()), "integer (required)")
        self.assertSignature(sb.required(sb.any_spec()), "(required)")
Exemplo n.º 12
0
class PresetAnimation(dictobj.Spec):
    animation = dictobj.Field(sb.string_choice_spec(
        list(dict(Animations.animators()))),
                              wrapper=sb.required)
    options = dictobj.Field(sb.dictionary_spec())
Exemplo n.º 13
0
refresh_field = dictobj.NullableField(
    sb.boolean,
    help="""
        Whether to refresh our idea of what is on the network

        If this is False then we will use the cached notion of what's on the network.
      """,
)

timeout_field = dictobj.Field(
    sb.float_spec,
    default=20,
    help="The max amount of time we wait for replies from the lights")

matcher_field = dictobj.NullableField(
    sb.or_spec(sb.string_spec(), sb.dictionary_spec()),
    help="""
        What lights to target. If this isn't specified then we interact with all
        the lights that can be found on the network.

        This can be specfied as either a space separated key=value string or as
        a dictionary.

        For example,
        "label=kitchen,bathroom location_name=home"
        or
        ``{"label": ["kitchen", "bathroom"], "location_name": "home"}``

        See https://delfick.github.io/photons-core/modules/photons_device_finder.html#valid-filters
        for more information on what filters are available.
      """,
Exemplo n.º 14
0
from delfick_project.norms import dictobj, sb

pkt_type_field = dictobj.Field(
    sb.or_spec(sb.integer_spec(), sb.string_spec()),
    wrapper=sb.required,
    help="""
        The type of packet to send to the lights. This can be a number or
        the name of the packet as known by the photons framework.

        A list of what's available can be found at
        https://photons.delfick.com/interacting/packets.html
      """,
)

pkt_args_field = dictobj.NullableField(
    sb.dictionary_spec(),
    help="""
        A dictionary of fields that make up the payload of the packet we
        are sending to the lights.
      """,
)


@store.command(name="status")
class StatusCommand(store.Command):
    async def execute(self):
        return {"on": True}


@store.command(name="discover")
class DiscoverCommand(store.Command, DeviceChangeMixin):
Exemplo n.º 15
0
 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)