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)
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))
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
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))
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)
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)
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()
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)
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)
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))
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)
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()
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)
def error(e): log.error( hp.lc( "Failed to determine if device matched filter", error=e, error_type=type(e).__name__, ))
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)
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()
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)
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)
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
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))
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
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
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)
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]
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)
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)
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())
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
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)