async def assertRemaining(duration, remaining, last_power, result): getter = LightMessages.GetHevCycle() state = LightMessages.StateHevCycle( duration_s=duration, remaining_s=remaining, last_power=last_power ) await assertResponse(getter, [state]) getter = LightMessages.GetLastHevCycleResult() state = LightMessages.StateLastHevCycleResult(result=result) await assertResponse(getter, [state])
async def gen(reference, sender, **kwargs): get_power = DeviceMessages.GetPower() async for pkt in sender(get_power, reference, **kwargs): if pkt | DeviceMessages.StatePower: if pkt.level == 0: yield LightMessages.SetLightPower(level=65535, res_required=False, duration=duration, target=pkt.serial) else: yield LightMessages.SetLightPower(level=0, res_required=False, duration=duration, target=pkt.serial)
class InfoPoints(enum.Enum): """ Enum used to determine what information is required for what keys """ # We need to know what kind of product the device is to get the label correctly VERSION = Point(DeviceMessages.GetVersion(), ["label", "product_id", "cap"], None) # We get Label from LIGHT_STATE for lights and from STATE_LABEL for non lights LIGHT_STATE = Point( LightMessages.GetColor(), ["label", "power", "hue", "saturation", "brightness", "kelvin"], 10, # Non lights are unimportant enough at this time to risk sending too many messages to them condition=lambda device: device.product_type is not DeviceType.NON_LIGHT, ) LABEL = Point( DeviceMessages.GetLabel(), ["label"], 10, condition=lambda device: device.product_type is DeviceType.NON_LIGHT, ) FIRMWARE = Point(DeviceMessages.GetHostFirmware(), ["firmware_version"], 300) GROUP = Point(DeviceMessages.GetGroup(), ["group_id", "group_name"], 60) LOCATION = Point(DeviceMessages.GetLocation(), ["location_id", "location_name"], 60)
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 ChangeCleanCycle(*, enable, duration_s=0, duration=None, **kwargs): """ Returns a valid message that will either start or stop the cleaning (HEV) cycle of devices used against it. When a cycle is started, if the duration_s=0 or is not provided, the default duration will be used. For example: .. code-block:: python await target.send(ChangeCleanCycle(enable=True), ["d073d5000001", "d073d5000001"]) Options are: enable - boolean (required) Pass True to start a cleaning cycle or False to stop a cycle in progress duration_s - integer (optional) Only used if enable=True. Specifies the duration (in seconds) for the cleaning cycle. If not specified, the default duration will be used. Default duration can be set using target:set_clean_config and returned using target_get_clean_config """ if duration is not None: raise ProgrammerError("Duration needs to be specified as duration_s") return ForCapability(hev=LightMessages.SetHevCycle( enable=enable, duration_s=duration_s, ack_required=True, res_required=False, ))
def SetCleanConfig(*, indication, duration_s, duration=None, **kwargs): """ Returns a valid message that will set the default clean cycle configuration for the device. For example: .. code-block:: python await target.send(SetCleanConfig(indication=True), ["d073d5000001", "d073d5000001"]) The options are: indication - boolean - default False whether to run a short flashing indication at the end of the HEV cycle. duration_s - seconds - default 7200 seconds duration in seconds for a cleaning cycle, if no duration provided. """ if duration is not None: raise ProgrammerError("Duration needs to be specified as duration_s") return ForCapability(hev=LightMessages.SetHevCycleConfiguration( indication=bool(indication), duration_s=duration_s, ack_required=True, res_required=False, ))
def power_message(self, overrides): power = overrides.get("power", self.power) duration = overrides.get("duration", self.duration) or 0 if power is not None: level = 0 if power not in (True, "on") else 65535 return LightMessages.SetLightPower(level=level, duration=duration)
class StatePlan(Plan): """ Return in a dictionary: * hue * saturation * brightness * kelvin * label * power """ messages = [LightMessages.GetColor()] default_refresh = 1 class Instance(Plan.Instance): def process(self, pkt): if pkt | LightMessages.LightState: dct = pkt.payload.as_dict() result = {} for k in ("hue", "saturation", "brightness", "kelvin", "label", "power"): result[k] = dct[k] self.dct = result return True async def info(self): return self.dct
async def execute(self): fltr = chp.filter_from_matcher(self.matcher, self.refresh) details = await self.finder.info_for(filtr=fltr) msgs = [] for serial, info in details.items(): msgs.append(DeviceMessages.GetPower(target=serial)) if "multizone" in info["cap"]: msgs.append( MultiZoneMessages.GetColorZones(start_index=0, end_index=255, target=serial) ) elif "chain" in info["cap"]: msgs.append( TileMessages.Get64(tile_index=0, length=5, x=0, y=0, width=8, target=serial) ) else: msgs.append(LightMessages.GetColor(target=serial)) state = defaultdict(dict) afr = await self.finder.args_for_run() async for pkt, _, _ in self.target.script(msgs).run_with( None, afr, multiple_replies=True, first_wait=0.5 ): if pkt | DeviceMessages.StatePower: state[pkt.serial]["power"] = pkt.level != 0 elif pkt | LightMessages.LightState: hsbk = f"kelvin:{pkt.kelvin} saturation:{pkt.saturation} brightness:{pkt.brightness} hue:{pkt.hue}" state[pkt.serial]["color"] = hsbk elif pkt | MultiZoneMessages.StateMultiZone: if "zones" not in state[pkt.serial]: state[pkt.serial]["zones"] = {} for i, zi in enumerate(range(pkt.zone_index, pkt.zone_index + 8)): c = pkt.colors[i] state[pkt.serial]["zones"][zi] = [c.hue, c.saturation, c.brightness, c.kelvin] elif pkt | TileMessages.State64: if "chain" not in state[pkt.serial]: state[pkt.serial]["chain"] = {} colors = [[c.hue, c.saturation, c.brightness, c.kelvin] for c in pkt.colors] state[pkt.serial]["chain"][pkt.tile_index] = colors scene = [] for serial, info in sorted(state.items()): if "zones" in info: info["zones"] = [hsbk for _, hsbk in sorted(info["zones"].items())] if "chain" in info: info["chain"] = [hsbks for _, hsbks in sorted(info["chain"].items())] scene.append({"matcher": {"serial": serial}, **info}) if self.just_return: return scene args = { "uuid": self.uuid, "scene": scene, "label": self.label, "description": self.description, } return await self.executor.execute(self.path, {"command": "scene_change", "args": args})
async def doit(): msg = LightMessages.GetColor() async for pkt, _, _ in lan_target.script(msg).run_with(FoundSerials()): hsbk = " ".join("{0}={1}".format(key, pkt.payload[key]) for key in ("hue", "saturation", "brightness", "kelvin")) print("{0}: {1}".format(pkt.serial, hsbk))
async def gen(reference, sender, **kwargs): get_color = LightMessages.GetColor(ack_required=False, res_required=True) async for pkt in sender(get_color, reference, **kwargs): if pkt | LightMessages.LightState: yield receiver(pkt.serial, pkt.payload)
def transition(self, state, keep_brightness=False): def receiver(reference, *states): if not states: return current_state = states[0].as_dict()["payload"] power_message = None if "power" not in state else self.power_message( state) msg_dict = dict(state) h, s, b, k = Parser.hsbk(state.get("color", None), overrides=state) msg_dict.update({ "hue": h, "saturation": s, "brightness": b, "kelvin": k }) final_overrides = dict(msg_dict) pipeline = [] reset = False now_off = current_state["power"] in (None, 0) if now_off: overrides = dict(msg_dict) overrides["brightness"] = 0 if "duration" in overrides: del overrides["duration"] msg = Parser.color_to_msg(None, overrides=overrides) msg.target = reference msg.ack_required = True msg.res_required = False reset = True pipeline.append(msg) if power_message is not None: want_off = power_message.level == 0 if now_off ^ want_off: power_message.target = reference pipeline.append(power_message) if keep_brightness or (reset and "brightness" not in state): final_overrides["brightness"] = current_state["brightness"] msg = Parser.color_to_msg(None, overrides=final_overrides) msg.target = reference msg.ack_required = True msg.res_required = False pipeline.append(msg) yield Pipeline(*pipeline) getter = LightMessages.GetColor(ack_required=False, res_required=True) return Decider(getter, receiver, [LightMessages.LightState])
async def doit(collector): lan_target = collector.resolve_target("lan") msg = LightMessages.GetColor() async for pkt in lan_target.send(msg, FoundSerials()): hsbk = " ".join("{0}={1}".format(key, pkt.payload[key]) for key in ("hue", "saturation", "brightness", "kelvin")) print("{0}: {1}".format(pkt.serial, hsbk))
async def apply_light(applier, target, afr, serial, theme, overrides): color = applier().apply_theme(theme) s = "kelvin:{} hue:{} saturation:{} brightness:{}".format( color.kelvin, color.hue, color.saturation, color.brightness) set_power = LightMessages.SetLightPower(level=65535, duration=overrides.get( "duration", 1)) await target.script([ set_power, Parser.color_to_msg(s, overrides=overrides) ]).run_with_all(serial, afr)
def power_message(self, state): power_level = 65535 if state["power"] == "on" else 0 if state.get("duration") in (sb.NotSpecified, "", 0, None): return DeviceMessages.SetPower(level=power_level, res_required=False) else: return LightMessages.SetLightPower(level=power_level, duration=state["duration"], res_required=False)
def make_response(self, pkt, protocol): self.received.append(pkt) if pkt | LightMessages.GetInfrared: return LightMessages.StateInfrared(brightness=self.infrared) if pkt | LightMessages.GetColor: return LightMessages.LightState( hue = self.hue , saturation = self.saturation , brightness = self.brightness , kelvin = self.kelvin , label = self.label , power = self.power ) elif pkt | DeviceMessages.GetVersion: return DeviceMessages.StateVersion( vendor = self.vendor_id , product = self.product_id , version = 0 ) elif pkt | DeviceMessages.GetHostFirmware: return DeviceMessages.StateHostFirmware( version = self.firmware_version , build = self.firmware_build_time ) elif pkt | DeviceMessages.GetGroup: return DeviceMessages.StateGroup( group = self.group , label = self.group_label , updated_at = self.group_updated_at ) elif pkt | DeviceMessages.GetLocation: return DeviceMessages.StateLocation( location = self.location , label = self.location_label , updated_at = self.location_updated_at )
async def gen(reference, sender, **kwargs): instance = kls(reference, sender, kwargs, aps, theme, options) # Turn on the device yield LightMessages.SetLightPower(level=65535, duration=options.duration) # Yield messages to turn on the theme for this device plans = sender.make_plans("capability") async for serial, _, info in sender.gatherer.gather(plans, reference): async for m in instance.apply(info["cap"]): yield m
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)
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)
class InfoPoints(enum.Enum): """ Enum used to determine what information is required for what keys """ LIGHT_STATE = Point( LightMessages.GetColor(), ["label", "power", "hue", "saturation", "brightness", "kelvin"]) VERSION = Point(DeviceMessages.GetVersion(), ["product_id", "product_identifier", "cap"]) FIRMWARE = Point(DeviceMessages.GetHostFirmware(), ["firmware_version"]) GROUP = Point(DeviceMessages.GetGroup(), ["group_id", "group_name"]) LOCATION = Point(DeviceMessages.GetLocation(), ["location_id", "location_name"])
def messages(self): if not self.is_light: return Skip if self.zones is Zones.SINGLE: return [LightMessages.GetColor()] elif self.zones is Zones.MATRIX: return [ TileMessages.Get64( x=0, y=0, tile_index=0, length=255, width=self.deps["chain"]["width"] ) ] else: return []
async def gen(ref, sender, **kwargs): r = ref if reference is None else reference plans = {"set_zones": SetZonesPlan(colors, **options)} async for serial, _, messages in sender.gatherer.gather( plans, r, **kwargs): if messages is not Skip: if power_on: yield LightMessages.SetLightPower( level=65535, target=serial, duration=options.get("duration", 1), ack_required=True, res_required=False, ) yield messages
async def doit(collector): lan_target = collector.resolve_target("lan") getter = [DeviceMessages.GetLabel(), LightMessages.GetColor()] info = defaultdict(dict) async for pkt in lan_target.send(getter, FoundSerials()): if pkt | DeviceMessages.StateLabel: info[pkt.serial]["label"] = pkt.label elif pkt | LightMessages.LightState: hsbk = " ".join("{0}={1}".format(key, pkt[key]) for key in ("hue", "saturation", "brightness", "kelvin")) info[pkt.serial]["hsbk"] = hsbk for serial, details in info.items(): print(f"{serial}: {details['label']}: {details['hsbk']}")
async def doit(): getter = [DeviceMessages.GetLabel(), LightMessages.GetColor()] def found(serial, *states): info = {"label": "", "hsbk": ""} for s in states: if s | DeviceMessages.StateLabel: info["label"] = s.label elif s | LightMessages.LightState: info["hsbk"] = " ".join("{0}={1}".format(key, s.payload[key]) for key in ("hue", "saturation", "brightness", "kelvin")) print("{0}: {1}: {2}".format(serial, info["label"], info["hsbk"])) return [] msg = Decider(getter, found, [DeviceMessages.StateLabel, LightMessages.LightState]) await lan_target.script(msg).run_with_all(FoundSerials())
async def gen(ref, sender, **kwargs): r = ref if reference is None else reference ps = sender.make_plans("capability") async for serial, _, info in sender.gatherer.gather(ps, r, **kwargs): if info["cap"].has_matrix: if power_on: yield LightMessages.SetLightPower( level=65535, target=serial, duration=power_on_duration, ack_required=True, res_required=False, ) msg = set_effect.clone() msg.target = serial yield msg
async def gen(reference, sender, **kwargs): serials = [] canvases = [] combined_canvas = Canvas() plans = sender.make_plans("parts") async for serial, _, info in sender.gatherer.gather(plans, reference, **kwargs): serials.append(serial) for part in info: if part.device.cap.has_chain: combined_canvas.add_parts(part) else: nxt = Canvas() nxt.add_parts(part) canvases.append(nxt) if combined_canvas: canvases.append(combined_canvas) msgs = [] if options.power_on: for serial in serials: msgs.append( LightMessages.SetLightPower( level=65535, duration=options.duration, target=serial, res_required=False, ) ) for canvas in canvases: Applier(canvas, options.colors).apply() for msg in canvas.msgs( options.override_layer, duration=options.duration, acks=True ): msgs.append(msg) yield msgs
def _msgs(self, colors, acks=False, duration=1, randomize=False): if self.device.cap.has_matrix: colors = [ c if c is not None else None for c in self.reorient(colors, randomize=randomize) ] kwargs = {"colors": colors} if duration != 0: kwargs["duration"] = duration if acks: kwargs["acks"] = acks msg = self._set_64.clone() msg.update(kwargs) return (msg, ) elif self.device.cap.has_multizone: return MultizoneMessagesMaker(self.device.serial, self.device.cap, colors, duration=duration).msgs elif colors: if isinstance(colors[0], tuple): h, s, b, k = colors[0] info = { "hue": h, "saturation": s, "brightness": b, "kelvin": k, } else: info = colors[0].as_dict() info["duration"] = duration return (LightMessages.SetColor(target=self.device.serial, res_required=False, **info), )
async def animate(self, reference, final_future, pauser=None): if pauser is None: pauser = asyncio.Condition() def errors(e): log.error(e) serials = await tile_serials_from_reference(self.target, reference, self.afr) state = TileStateGetter(self.target, self.afr, serials, self.options.background, coords=self.coords) await state.fill(random_orientations=self.random_orientations) by_serial = {} for serial in serials: by_serial[serial] = { "state": None, "coords": tuple(state.info_by_serial[serial].coords) } log.info("Starting!") await self.target.script( LightMessages.SetLightPower(level=65535, duration=1) ).run_with_all(serials, self.afr) combined_coords = [] for info in by_serial.values(): combined_coords.extend(info["coords"]) combined_info = {"state": None} while True: start = time.time() combined_canvas = None if getattr(self.options, "combine_tiles", False): combined_state = combined_info["state"] = self.next_state( combined_info["state"], combined_coords) combined_canvas = self.make_canvas(combined_state, combined_coords) msgs = [] for serial, info in by_serial.items(): coords = info["coords"] canvas = combined_canvas if canvas is None: info["state"] = self.next_state(info["state"], coords) canvas = self.make_canvas(info["state"], coords) canvas.set_default_color_func( state.info_by_serial[serial].default_color_func) orientations = state.info_by_serial[serial].orientations for msg in canvas_to_msgs(canvas, coords, duration=self.duration, acks=self.acks, orientations=orientations): msg.target = serial msgs.append(msg) async with pauser: if final_future.done(): break await self.target.script(msgs).run_with_all( None, self.afr, error_catcher=errors) if final_future.done(): break diff = time.time() - start if diff < self.every: await asyncio.sleep(self.every - diff)
ack = CoreMessages.Acknowledgement(source=20, sequence=2, target="d073d5001111") self.assertLines(ack, f"Ack(source={ack.source},sequence=2,target=d073d5001111)(empty)") it "can format simple messages": pkt = DeviceMessages.GetPower(source=21, sequence=3, target="d073d5002222") self.assertLines( pkt, f"GetPower(ack=True,res=True,source={pkt.source},sequence=3,target=d073d5002222)(empty)", ) it "can format messages with fields": pkt = LightMessages.SetColor( res_required=False, source=22, sequence=4, target="d073d5003333", hue=200, saturation=1, brightness=0.5, kelvin=9000, ) self.assertLines( pkt, f"SetColor(ack=True,res=False,source={pkt.source},sequence=4,target=d073d5003333)", mock.ANY, " hue: 200.0", " saturation: 1.0", " brightness: 0.5", " kelvin: 9000", " duration: 0.0", )
async it "can power on devices and set zones effect", sender: msg = SetZonesEffect("move") got = await sender(msg, devices.serials) assert got == [] for strip in strips: assert strip.attrs.zones_effect is MultiZoneEffectType.MOVE self.compare_received( { light1: [DeviceMessages.GetHostFirmware(), DeviceMessages.GetVersion()], light2: [DeviceMessages.GetHostFirmware(), DeviceMessages.GetVersion()], striplcm1: [ DeviceMessages.GetHostFirmware(), DeviceMessages.GetVersion(), LightMessages.SetLightPower(level=65535, duration=1), MultiZoneMessages.SetMultiZoneEffect.create(type=MultiZoneEffectType.MOVE), ], striplcm2noextended: [ DeviceMessages.GetHostFirmware(), DeviceMessages.GetVersion(), LightMessages.SetLightPower(level=65535, duration=1), MultiZoneMessages.SetMultiZoneEffect.create(type=MultiZoneEffectType.MOVE), ], striplcm2extended: [ DeviceMessages.GetHostFirmware(), DeviceMessages.GetVersion(), LightMessages.SetLightPower(level=65535, duration=1), MultiZoneMessages.SetMultiZoneEffect.create(type=MultiZoneEffectType.MOVE), ], }