Beispiel #1
0
    async def write(self, source, received_data, bts):
        if not self.attrs.online:
            return

        addr = None
        for service in self.services:
            a = service.address(source)
            if a:
                addr = a
                break

        if addr is None:
            log.warning(
                hp.lc(
                    "Tried to write a packet to the fake device, but no appropriate service found",
                    source=source,
                    serial=self.serial,
                ))
            return

        log.debug(
            hp.lc("RECV",
                  bts=binascii.hexlify(bts).decode(),
                  source=source,
                  serial=self.serial))

        pkt = Messages.unpack(bts, self.protocol_register, unknown_ok=True)
        if pkt.serial not in ("000000000000", self.serial):
            return

        async for msg in self.got_message(pkt, source):
            await received_data(msg.tobytes(serial=self.serial), addr)
Beispiel #2
0
    async def interpret_loop(self):
        """
        Endless loop of looking at ``queue`` for packets and giving them to our
        ``store``.
        """
        while True:
            if self.finished.is_set():
                break

            try:
                nxt = await self.queue.get()

                if nxt is Done:
                    break

            except asyncio.CancelledError:
                raise
            except Exception as error:
                log.error(
                    hp.lc("Failed to get item off the queue", error=error))
            else:
                try:
                    self.interpret(nxt)
                except Exception as error:
                    log.error(
                        hp.lc("Failed to interpret item from queue",
                              error=error))
Beispiel #3
0
    async def catch_errors(self, session, tries):
        try:
            yield
            await session.commit()

        except sqlalchemy.exc.OperationalError as error:
            await session.rollback()
            log.error(
                hp.
                lc("Failed to use database, will rollback and maybe try again",
                   error=error))

            if tries > 1:
                raise

        except sqlalchemy.exc.InvalidRequestError as error:
            await session.rollback()
            log.error(
                hp.lc("Failed to perform database operation", error=error))
            raise

        except PhotonsAppError as error:
            await session.rollback()
            log.error(hp.lc("Failed to use database", error=error))
            raise

        except:
            await session.rollback()
            exc_info = sys.exc_info()
            log.exception(
                hp.lc("Unexpected failure when using database",
                      error=exc_info[1]))
            raise
Beispiel #4
0
    def do_log(self, body, message, info, **kwargs):
        command_name = "Command"
        if body and type(body) is dict and "command" in body:
            command_name = body["command"]

        if "error" in info:
            logging.getLogger(self.logger_name).error(hp.lc(f"{command_name} progress", **info))
        else:
            logging.getLogger(self.logger_name).info(hp.lc(f"{command_name} progress", **info))
Beispiel #5
0
    async def got_message(self, pkt, source):
        payload_repr = repr(pkt.payload)
        if len(payload_repr) > 300:
            payload_repr = f"{payload_repr[:300]}..."

        log.info(
            hp.lc(
                "Got packet",
                source=source,
                pkt=pkt.__class__.__name__,
                payload=payload_repr,
                pkt_source=pkt.source,
                serial=self.serial,
            ))

        if self.intercept_got_message:
            if await self.intercept_got_message(pkt, source) is False:
                return

        ack = await self.ack_for(pkt, source)
        if ack:
            ack.sequence = pkt.sequence
            ack.source = pkt.source
            ack.target = self.serial
            yield ack
            await self.process_reply(ack, source, pkt)

        try:
            async for res in self.response_for(pkt, source):
                res.sequence = pkt.sequence
                res.source = pkt.source
                res.target = self.serial
                log.info(
                    hp.lc(
                        "Created response",
                        source=source,
                        pkt=res.__class__.__name__,
                        payload=res.payload,
                        pkt_source=res.source,
                        pkt_sequence=res.sequence,
                        serial=self.serial,
                    ))
                yield res
                await self.process_reply(res, source, pkt)
        except IgnoreMessage:
            pass

        for key, fut in list(self.waiters.items()):
            s, kls = key
            if s == source and pkt | kls:
                del self.waiters[key]
                fut.set_result(pkt)
Beispiel #6
0
async def apply_zone(applier, target, afr, serial, theme, overrides):
    length = None
    msg = MultiZoneMessages.GetColorZones(start_index=0, end_index=255)
    async for pkt, _, _ in target.script(msg).run_with(serial, afr):
        if pkt | MultiZoneMessages.StateMultiZone:
            length = pkt.zones_count

    if length is None:
        log.warning(
            hp.lc("Couldn't work out how many zones the light had",
                  serial=serial))
        return

    messages = []
    for (start_index, end_index), hsbk in applier(length).apply_theme(theme):
        messages.append(
            MultiZoneMessages.SetColorZones(start_index=start_index,
                                            end_index=end_index,
                                            hue=hsbk.hue,
                                            saturation=hsbk.saturation,
                                            brightness=hsbk.brightness,
                                            kelvin=hsbk.kelvin,
                                            duration=overrides.get(
                                                "duration", 1),
                                            res_required=False,
                                            ack_required=True))

    set_power = LightMessages.SetLightPower(level=65535,
                                            duration=overrides.get(
                                                "duration", 1))
    pipeline = Pipeline(*messages, spread=0.005)
    await target.script([set_power, pipeline]).run_with_all(serial, afr)
Beispiel #7
0
    def peek_valid_request(self, meta, command, path, body):
        request = meta.everything["request_handler"].request

        if isinstance(body, dict) and "command" in body:
            request.__whirlwind_commander_command__ = body["command"]

            if body["command"] == "status":
                return

        command = getattr(request, "__whirlwind_commander_command__", None)
        remote_ip = request.remote_ip
        identifier = request.headers[REQUEST_IDENTIFIER_HEADER]

        matcher = None
        if (isinstance(body, dict) and isinstance(body.get("args"), dict)
                and "matcher" in body["args"]):
            matcher = body["args"]["matcher"]

        log.info(
            hp.lc(
                "Command",
                method=request.method,
                uri=request.uri,
                path=path,
                command=command,
                matcher=matcher,
                remote_ip=remote_ip,
                request_identifier=identifier,
            ))
    async def execute(self):
        if not isinstance(self.request_handler, websocket.WebSocketHandler):
            raise NotAWebSocket(
                "status stream can only be called from a websocket")

        u, fut = self.animations.add_listener()
        self.progress_cb({"available": self.animations.available()})

        while True:
            futs = [
                self.request_handler.connection_future, self.final_future, fut
            ]
            await asyncio.wait(futs, return_when=asyncio.FIRST_COMPLETED)

            if self.request_handler.connection_future.done(
            ) or self.final_future.done():
                log.info(hp.lc("Connection to status stream went away"))
                afr = await self.finder.args_for_run()
                await self.animations.remove_listener(u,
                                                      self.request_handler.key,
                                                      self.target, afr)
                break

            self.progress_cb(
                {"status": self.animations.status(sb.NotSpecified)})
            fut.reset()
Beispiel #9
0
            def datagram_received(sp, data, addr):
                if not self.online:
                    return

                log.debug(
                    hp.lc("RECV",
                          bts=binascii.hexlify(data).decode(),
                          protocol="udp",
                          serial=self.serial))

                pkt = Messages.unpack(data,
                                      self.protocol_register,
                                      unknown_ok=True)
                if pkt.serial not in ("000000000000", self.serial):
                    return

                ack = self.ack_for(pkt, "udp")
                if ack:
                    ack.sequence = pkt.sequence
                    ack.source = pkt.source
                    ack.target = self.serial
                    self.udp_transport.sendto(ack.tobytes(serial=self.serial),
                                              addr)

                for res in self.response_for(pkt, "udp"):
                    res.sequence = pkt.sequence
                    res.source = pkt.source
                    res.target = self.serial
                    self.udp_transport.sendto(res.tobytes(serial=self.serial),
                                              addr)
Beispiel #10
0
    async def raw_search_loop(self, quickstart=False):
        """
        An endless loop that searches for new devices on the network

        Run every ``service_search_interval`` seconds

        After the first attempt we will send out queries to any new devices we
        find so that we don't have to wait for the ``findings`` loop to do a pass.
        """
        first = True
        while True:
            if self.finished.is_set():
                break

            try:
                afr = await self.args_for_run()
                found = await afr.find_devices(afr.default_broadcast,
                                               ignore_lost=True)
                query_new_devices = quickstart or not first
                self.store.update_found(found,
                                        query_new_devices=query_new_devices)
                first = False
            except asyncio.CancelledError:
                raise
            except FoundNoDevices:
                pass
            except Exception as error:
                log.exception(
                    hp.lc("Unexpected error getting new serials", error=error))

            await asyncio.sleep(self.service_search_interval)
Beispiel #11
0
    async def run(self):
        cannon = self.make_cannon()
        self.started = time.time()

        animations = self.run_options.animations_iter
        self.combined_state = State(self.final_future)

        async with self.reinstate(), hp.TaskHolder(
                self.final_future,
                name="AnimationRunner::run[task_holder]") as ts:
            self.transfer_error(
                ts,
                ts.add(
                    self.animate(ts, cannon, self.combined_state, animations)))

            async for collected in self.collect_parts(ts):
                try:
                    if self.run_options.combined:
                        await self.combined_state.add_collected(collected)
                    else:
                        state = State(self.final_future)
                        await state.add_collected(collected)
                        self.transfer_error(
                            ts,
                            ts.add(self.animate(ts, cannon, state,
                                                animations)))
                except asyncio.CancelledError:
                    raise
                except Finish:
                    pass
                except Exception as error:
                    log.exception(hp.lc("Failed to add device", error=error))
Beispiel #12
0
    def transform_first_chunk(self, status_code, headers, chunk, finishing):
        headers[REQUEST_IDENTIFIER_HEADER] = self.identifier

        request = self.request
        took = time.time() - request.__interactor_request_start__
        command = getattr(request, "__whirlwind_commander_command__", None)
        remote_ip = request.remote_ip
        identifier = request.headers[REQUEST_IDENTIFIER_HEADER]

        if command != "status":
            method = "error"
            if status_code < 400:
                method = "info"

            getattr(log, method)(hp.lc(
                "Response",
                method=request.method,
                uri=request.uri,
                status=status_code,
                command=command,
                remote_ip=remote_ip,
                took_seconds=round(took, 2),
                request_identifier=identifier,
            ))

        return super().transform_first_chunk(status_code, headers, chunk,
                                             finishing)
Beispiel #13
0
    async def animate(self, reference, final_future, pauser=None):
        def errors(e):
            log.error(e)

        def error(e):
            if not isinstance(e, TimedOut):
                log.error(e)

        if self.global_options.noisy_network:
            inflight_limit = self.global_options.inflight_limit
            log.info(
                hp.lc("Using noisy_network code",
                      inflight_limit=inflight_limit))
            task = NoisyNetworkAnimateTask(self.sender,
                                           wait_timeout=self.message_timeout,
                                           inflight_limit=inflight_limit)
        else:
            task = FastNetworkAnimateTask(self.sender)

        try:
            async for msgs in self.generate_messages(reference, final_future,
                                                     pauser):
                if self.retries:
                    await self.sender(msgs,
                                      message_timeout=self.every,
                                      error_catcher=error)
                else:
                    await task.add(msgs)
        finally:
            await task.finish()
Beispiel #14
0
    async def received_data(self, data, addr, allow_zero=False):
        """What to do when we get some data"""
        if type(data) is bytes:
            log.debug(
                hp.lc("Received bytes", bts=binascii.hexlify(data).decode()))

        try:
            protocol_register = self.transport_target.protocol_register
            protocol, pkt_type, Packet, PacketKls, data = Messages.get_packet_type(
                data, protocol_register)

            if protocol == 1024 and pkt_type == 45:
                if isinstance(data, bytes):
                    source = struct.unpack("<I", data[4:8])[0]
                    target = data[8:16]
                    sequence = data[23]
                else:
                    source = data.source
                    target = data.target
                    sequence = data.sequence

                serial = binascii.hexlify(target[:6]).decode()
                pkt = FakeAck(source, sequence, target, serial, addr)
            else:
                if PacketKls is None:
                    PacketKls = Packet
                if isinstance(data, PacketKls):
                    pkt = data.clone()
                else:
                    pkt = PacketKls.create(data)
        except Exception as error:
            log.exception(error)
        else:
            await self.receiver.recv(pkt, addr, allow_zero=allow_zero)
Beispiel #15
0
 def error(e):
     log.error(
         hp.lc(
             "Failed to determine if device matched filter",
             error=e,
             error_type=type(e).__name__,
         ))
Beispiel #16
0
 async def ensure_conn(self):
     if not self.writer.bridge.is_sock_active(self.conn):
         log.info(
             hp.lc("Connection is no longer active, making a new one",
                   serial=self.serial))
         self.conn = await self.writer.determine_conn(
             self.addr, self.target)
Beispiel #17
0
        def ret():
            tries = 0
            while True:
                (db, ) = args

                # Clone our database with a new session
                database = db.new_session()

                # Do the work
                try:
                    res = proc(database)
                    database.commit()
                    return res

                except sqlalchemy.exc.OperationalError as error:
                    database.rollback()
                    log.error(
                        hp.
                        lc("Failed to use database, will rollback and maybe try again",
                           error=error))
                    tries += 1

                    if tries > 1:
                        raise

                except sqlalchemy.exc.InvalidRequestError as error:
                    database.rollback()
                    log.error(
                        hp.lc("Failed to perform database operation",
                              error=error))
                    raise

                except PhotonsAppError as error:
                    database.rollback()
                    log.error(hp.lc("Failed to use database", error=error))
                    raise

                except:
                    database.rollback()
                    exc_info = sys.exc_info()
                    log.exception(
                        hp.lc("Unexpected failure when using database",
                              error=exc_info[1]))
                    raise

                finally:
                    database.close()
Beispiel #18
0
    async def recv(self, pkt, addr, allow_zero=False):
        """Find the result for this packet and add the packet"""
        if getattr(pkt, "represents_ack", False):
            log.debug(
                hp.lc("Got ACK",
                      source=pkt.source,
                      sequence=pkt.sequence,
                      serial=pkt.serial))
        else:
            log.debug(
                hp.lc(
                    "Got RES",
                    source=pkt.source,
                    sequence=pkt.sequence,
                    serial=pkt.serial,
                    pkt_type=pkt.pkt_type,
                ))

        key = (pkt.source, pkt.sequence, pkt.target)
        broadcast_key = (pkt.source, pkt.sequence, self.blank_target)

        if pkt.source == 0 and pkt.sequence == 0:
            if not allow_zero:
                log.warning("Received message with 0 source and sequence")
                return

        if key not in self.results and broadcast_key not in self.results:
            if self.message_catcher is not NotImplemented and callable(
                    self.message_catcher):
                await self.message_catcher(pkt)
            else:
                # This usually happens when Photons retries a message
                # But gets a reply from multiple of these requests
                # The first one back will unregister the future
                # And so there's nothing to resolve with this newly received data
                log.debug(
                    hp.lc("Received a message that wasn't expected",
                          key=key,
                          serial=pkt.serial))
            return

        if key not in self.results:
            key = broadcast_key

        original = self.results[key][0]
        pkt.Information.update(remote_addr=addr, sender_message=original)
        self.results[key][1].add_packet(pkt)
Beispiel #19
0
    def from_options(kls, options):
        """Create a Filter based on the provided dictionary"""
        if isinstance(options, dict):
            for option in options:
                if option not in kls.fields:
                    log.warning(hp.lc("Unknown option provided for filter", wanted=option))

        return kls.FieldSpec().normalise(Meta.empty(), options)
Beispiel #20
0
    async def apply(self, cap):
        if cap.has_multizone:
            if cap.has_extended_multizone:
                log.info(hp.lc("found a strip with extended multizone", serial=self.serial))
            else:
                log.info(hp.lc("found a strip without extended multizone", serial=self.serial))
            async for m in self.zone_msgs():
                yield m

        elif cap.has_matrix:
            log.info(hp.lc("found a device with matrix zones", serial=self.serial))
            async for m in self.tile_msgs():
                yield m

        else:
            log.info(hp.lc("found a light with a single zone", serial=self.serial))
            async for m in self.light_msgs():
                yield m
Beispiel #21
0
 async def finish(self):
     await super().finish()
     for t in self.broadcast_transports.values():
         try:
             await t.close()
         except asyncio.CancelledError:
             pass
         except Exception as error:
             log.error(hp.lc("Failed to close broadcast transport", error=error))
Beispiel #22
0
    async def start_arrange(self, serials, ref, target, afr):
        log.info(hp.lc("Starting arrange", serials=serials, ref=ref))
        all_errors = []

        for serial in list(self.serials):
            if serial not in serials:
                del self.serials[serial]

        for serial in serials:
            tasks = []

            info = {"refs": [], "highlightlock": asyncio.Lock()}
            if serial in self.serials:
                info = self.serials[serial]

            tasks.append(
                (serial,
                 hp.async_as_background(self.start(serial, info, target,
                                                   afr))))

            for serial, t in tasks:
                try:
                    errors, data = await t
                except Exception as error:
                    errors = [error]

                if errors:
                    all_errors.extend(errors)
                else:
                    info.update(data)
                    self.serials[serial] = info

        final = {"serials": {}}
        if all_errors:
            final["error"] = ", ".join(str(e) for e in all_errors)
            log.error(hp.lc("Failed to start arrange", errors=all_errors))

        for serial in serials:
            if serial in self.serials:
                if ref not in self.serials[serial]["refs"]:
                    self.serials[serial]["refs"].append(ref)
                final["serials"][serial] = self.info_for_browser(serial)

        return final
Beispiel #23
0
async def do_apply_theme(target, reference, afr, options):
    aps = appliers[options.theme]

    theme = Theme()
    for color in options.colors:
        theme.add_hsbk(color.hue, color.saturation, color.brightness,
                       color.kelvin)

    tasks = []
    async for pkt, _, _ in target.script(DeviceMessages.GetVersion()).run_with(
            reference, afr):
        serial = pkt.serial
        capability = capability_for_ids(pkt.product, pkt.vendor)
        if capability.has_multizone:
            log.info(hp.lc("Found a strip", serial=serial))
            t = hp.async_as_background(
                apply_zone(aps["1d"], target, afr, pkt.serial, theme,
                           options.overrides))
        elif capability.has_chain:
            log.info(hp.lc("Found a tile", serial=serial))
            t = hp.async_as_background(
                apply_tile(aps["2d"], target, afr, pkt.serial, theme,
                           options.overrides))
        else:
            log.info(hp.lc("Found a light", serial=serial))
            t = hp.async_as_background(
                apply_light(aps["0d"], target, afr, pkt.serial, theme,
                            options.overrides))

        tasks.append((serial, t))

    results = {}

    for serial, t in tasks:
        try:
            await t
        except Exception as error:
            results[serial] = error
        else:
            results[serial] = "ok"

    return results
Beispiel #24
0
    async def finish(self, exc_typ=None, exc=None, tb=None):
        self.stop_fut.cancel()
        for serial in self.found.serials:
            try:
                await self.forget(serial)
            except Exception as error:
                log.error(
                    hp.lc("Failed to close transport",
                          error=error,
                          serial=serial))

        await self.received_data_tasks.finish(exc_typ, exc, tb)
Beispiel #25
0
    async def remove_lost(self, found_now):
        found_now = [self.cleanse_serial(serial) for serial in found_now]

        for target in list(self):
            if target not in found_now:
                for transport in self[target].values():
                    try:
                        await transport.close()
                    except Exception as error:
                        log.error(
                            hp.lc("Failed to close transport", error=error))
                del self[target]
Beispiel #26
0
async def apply_tile(applier, target, afr, serial, theme, overrides):
    from photons_tile_paint.orientation import Orientation as O, reorient
    from photons_tile_paint.animation import orientations_from

    chain = []
    orientations = {}
    async for pkt, _, _ in target.script(
            TileMessages.GetDeviceChain()).run_with(serial, afr):
        if pkt | TileMessages.StateDeviceChain:
            for tile in tiles_from(pkt):
                chain.append(tile)
            orientations = orientations_from(pkt)

    if chain is None:
        log.warning(
            hp.lc("Couldn't work out how many tiles the light had",
                  serial=serial))
        return

    coords_and_sizes = [((t.user_x, t.user_y), (t.width, t.height))
                        for t in chain]

    messages = []
    for i, (hsbks, coords_and_size) in enumerate(
            zip(
                applier.from_user_coords(coords_and_sizes).apply_theme(theme),
                coords_and_sizes)):
        colors = [{
            "hue": overrides.get("hue", hsbk.hue),
            "saturation": overrides.get("saturation", hsbk.saturation),
            "brightness": overrides.get("brightness", hsbk.brightness),
            "kelvin": overrides.get("kelvin", hsbk.kelvin)
        } for hsbk in hsbks]

        colors = reorient(colors, orientations.get(i, O.RightSideUp))

        messages.append(
            TileMessages.SetState64(tile_index=i,
                                    length=1,
                                    x=0,
                                    y=0,
                                    width=coords_and_size[1][0],
                                    duration=overrides.get("duration", 1),
                                    colors=colors,
                                    res_required=False,
                                    ack_required=True))

    set_power = LightMessages.SetLightPower(level=65535,
                                            duration=overrides.get(
                                                "duration", 1))
    pipeline = Pipeline(*messages, spread=0.005)
    await target.script([set_power, pipeline]).run_with_all(serial, afr)
Beispiel #27
0
    def interpret(self, item):
        """
        Add a pkt to the ``store``.

        Log an error and do nothing if the provided item is not a LIFXPacket.
        """
        if not isinstance(item, LIFXPacket):
            log.error(
                hp.lc(
                    "Got item off the queue that wasn't a lifx binary packet",
                    got=item))
            return
        self.store.add(item)
Beispiel #28
0
 async def start(self):
     if self.options.enabled:
         log.info(
             hp.lc(
                 "Enabling Zeroconf service discovery",
                 hostname=f"{socket.getfqdn()}.",
                 ipaddress=self.options.ip_address,
                 port=self.port,
                 sd=self.options.name,
             ))
         self.zeroconf = AsyncZeroconf(ip_version=IPVersion.V4Only)
         await self.zeroconf.async_register_service(
             await self.get_interactor_service_info())
Beispiel #29
0
    async def leave_arrange(self, ref, target, afr):
        log.info(hp.lc("Leaving arrange", ref=ref))

        tasks = []

        for serial, info in list(self.serials.items()):
            info["refs"] = [r for r in info["refs"] if r != ref]
            if not info["refs"]:
                tasks.append(
                    hp.async_as_background(
                        self.restore(serial, info["initial"], target, afr)))
                del self.serials[serial]

        for t in tasks:
            await t
Beispiel #30
0
    async def finish(self):
        self.stop_fut.cancel()
        for serial in self.found.serials:
            try:
                await self.forget(serial)
            except Exception as error:
                log.error(
                    hp.lc("Failed to close transport",
                          error=error,
                          serial=serial))

        if self.received_data_tasks:
            for t in self.received_data_tasks:
                t.cancel()
            await asyncio.wait(self.received_data_tasks)