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 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})
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)
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))
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 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())
"/v1/lifx/command", { "command": "transform", "args": {"transform": {"power": "on", "color": "blue"}, "timeout": 0.2}, }, json_output=expected, ) for device in devices: io = device.io["MEMORY"] if device.attrs.label == "tv": devices.store(device).assertNoSetMessages() elif not device.cap.is_light: assert ( devices.store(device).count( Events.UNHANDLED(device, io, pkt=LightMessages.GetColor()) ) == 1 ) elif device.serial in ("d073d5000001", "d073d5000003"): devices.store(device).count( Events.INCOMING( device, io, pkt=ColourParser.msg("blue", overrides={"res_required": False}) ) ) == 1 else: for pkt in [ ColourParser.msg("blue", overrides={"brightness": 0, "res_required": False}), DeviceMessages.SetPower(level=65535, res_required=False), ColourParser.msg("blue", overrides={"brightness": 0.5, "res_required": False}), ]:
assert got[striplcm1.serial][1]["colors"] == [expectedlcm1] assert got[striplcm2extended.serial][1]["colors"] == [expectedlcm2] expected = { clean: [], switch: [DeviceMessages.GetHostFirmware(), DeviceMessages.GetVersion()], light1: [ DeviceMessages.GetHostFirmware(), DeviceMessages.GetVersion(), TileMessages.GetDeviceChain(), TileMessages.Get64(length=255, tile_index=0, width=8, x=0, y=0), ], light2: [ DeviceMessages.GetHostFirmware(), DeviceMessages.GetVersion(), LightMessages.GetColor(), ], striplcm1: [ DeviceMessages.GetHostFirmware(), DeviceMessages.GetVersion(), MultiZoneMessages.GetColorZones(start_index=0, end_index=255), ], striplcm2noextended: [], striplcm2extended: [ DeviceMessages.GetHostFirmware(), DeviceMessages.GetVersion(), MultiZoneMessages.GetExtendedColorZones(), ], } for device in devices:
assert ss == sorted(V.serials) for device in V.devices: V.devices.store(device).assertIncoming(DiscoveryMessages.GetService()) V.devices.store(device).clear() reference = DeviceFinder.from_kwargs(label="kitchen") found, ss = await reference.find(V.sender, timeout=5) reference.raise_on_missing(found) assert sorted(list(found)) == sorted(binascii.unhexlify(s)[:6] for s in ss) assert ss == [] for device in V.devices: expected = [ DiscoveryMessages.GetService(), DeviceMessages.GetVersion(), LightMessages.GetColor(), ] if device is V.devices["d4"]: expected.append(DeviceMessages.GetLabel) V.devices.store(device).assertIncoming(*expected) V.devices.store(device).clear() await V.d3.change_one("label", "kitchen", event=None) reference = DeviceFinder.from_kwargs(label="kitchen") found, ss = await reference.find(V.sender, timeout=5) reference.raise_on_missing(found) assert sorted(list(found)) == sorted(binascii.unhexlify(s)[:6] for s in ss) assert ss == [V.d3.serial] for device in V.devices: expected = [
power_message = transformer.power_message(state) assert color_message is not None assert power_message is not None await runner.reset_devices() expected = {device: [power_message, color_message] for device in runner.devices} await self.transform(runner, state, expected=expected) describe "When power on and need color": async it "sets power on if it needs to", runner: state = {"color": "blue", "power": "on"} expected = { light1: [ LightMessages.GetColor(), ColourParser.msg("blue", overrides={"brightness": 0}), DeviceMessages.SetPower(level=65535), ColourParser.msg( "blue", overrides={"brightness": light1.attrs.color.brightness} ), ], light2: [LightMessages.GetColor(), ColourParser.msg("blue")], light3: [LightMessages.GetColor(), ColourParser.msg("blue")], } await self.transform(runner, state, expected=expected) async it "sets power on if it needs to with duration", runner: state = {"color": "blue brightness:0.3", "power": "on", "duration": 10} expected = { light1: [
async def checker(): info = {"serial": V.fake_device.serial} await futs[1] assert V.device.info == info await asyncio.wait(all_futs) assert V.t.time == len(all_futs) - 1 V.received(*msgs) info.update( { "label": "kitchen", "power": "off", "hue": 0.0, "saturation": 1.0, "brightness": 1.0, "kelvin": 3500, "firmware_version": "2.80", "product_id": 27, "product_identifier": "lifx_a19", "cap": [ "color", "not_chain", "not_ir", "not_matrix", "not_multizone", "variable_color_temp", ], "group_id": "aa000000000000000000000000000000", "group_name": "g1", } ) assert V.device.info == info await asyncio.wait([Futs.color]) V.received( LightMessages.GetColor(), keep_duplicates=True, ) if V.t.time == 16: V.t.set(15) assert V.t.time == len(all_futs) + 10 await asyncio.wait([Futs.group, Futs.location]) V.received( *([LightMessages.GetColor()] * 3), DeviceMessages.GetGroup(), DeviceMessages.GetLocation(), keep_duplicates=True, ) # First location was at t=4 # We then wait until at least 64 # 60 is at 12 rounds, and next location after that is after 5 assert V.t.time >= 69 assert V.device.point_futures[InfoPoints.LIGHT_STATE].result() == 61 await asyncio.wait([Futs.color]) V.received( LightMessages.GetColor(), keep_duplicates=True, ) assert V.t.time <= 76 await asyncio.wait([Futs.firmware]) # First firmware was at t=2 # So next refresh after 102 # So needs a full cycle after that assert V.t.time >= 107 V.received( LightMessages.GetColor(), LightMessages.GetColor(), DeviceMessages.GetHostFirmware(), keep_duplicates=True, )
for p, f in s.device.point_futures.items(): if p in points: assert f.done() and f.result() == points[p] else: assert not f.done() return V() async it "can match against a fltr", V: V.t.add(1) assert await V.matches(None) V.received() assert await V.matches(Filter.from_kwargs(label="kitchen")) V.received(LightMessages.GetColor()) V.assertTimes({InfoPoints.LIGHT_STATE: 1}) V.t.add(5) assert not (await V.matches(Filter.from_kwargs(label="den"))) V.received() V.assertTimes({InfoPoints.LIGHT_STATE: 1}) V.t.add(2) assert not (await V.matches(Filter.from_kwargs(label="attic", refresh_info=True))) V.received(LightMessages.GetColor()) V.assertTimes({InfoPoints.LIGHT_STATE: 8}) V.t.add(1) assert not (await V.matches(Filter.from_kwargs(group_name="aa", cap=["matrix"]))) V.received(DeviceMessages.GetVersion(), DeviceMessages.GetGroup())
import asyncio collector = library_setup() lan_target = collector.configuration['target_register'].resolve("lan") color_names = [ "blue", "red", "orange", "yellow", "cyan", "green", "blue", "purple", "pink" ] spread = 2 power_on = DeviceMessages.SetPower(level=65535) get_color = LightMessages.GetColor() color_msgs = [ Parser.color_to_msg(name, overrides={ "res_required": False, "duration": spread }) for name in color_names ] async def doit(): async with lan_target.session() as afr: # By using a pipeline we can introduce a wait time between successful sending of colors colors = Pipeline(*color_msgs, spread=spread, synchronized=True) # by having power_on and get_color in an array we are sending both at the same time
async def checker(ff): info = {"serial": V.fake_device.serial, "product_type": "unknown"} assert V.device.info == info await hp.wait_for_all_futures( *[V.device.point_futures[kls] for kls in InfoPoints if kls is not InfoPoints.LABEL] ) found = [] for kls in list(InfoPoints): if kls is not InfoPoints.LABEL: found.append(V.device.point_futures[kls].result()) assert found == [1, 2, 3, 4, 5] assert V.t.time == 5 V.received(*msgs) info.update( { "label": "kitchen", "power": "off", "hue": 0.0, "saturation": 0.0, "brightness": 1.0, "kelvin": 3500, "firmware_version": "2.80", "product_id": 27, "product_name": "LIFX A19", "product_type": "light", "cap": pytest.helpers.has_caps_list("color", "variable_color_temp"), "group_id": "aa000000000000000000000000000000", "group_name": "g1", "location_id": "bb000000000000000000000000000000", "location_name": "l1", } ) assert V.device.info == info await hp.wait_for_all_futures(Futs.color) V.received(LightMessages.GetColor(), keep_duplicates=False) assert V.t.time == 12 await hp.wait_for_all_futures(Futs.group, Futs.location) V.received( *([LightMessages.GetColor()] * 5), DeviceMessages.GetGroup(), DeviceMessages.GetLocation(), keep_duplicates=True, ) # First location was at t=5 # We then wait another 60 # 60 is at 12 rounds, and next location after that is after 5 assert V.t.time == 65 assert V.device.point_futures[InfoPoints.LIGHT_STATE].result() == 62 await hp.wait_for_all_futures(Futs.color) V.received(LightMessages.GetColor(), keep_duplicates=True) # 62 + 10 = 72 assert V.t.time == 72 await hp.wait_for_all_futures(Futs.firmware) assert V.device.point_futures[InfoPoints.LIGHT_STATE].result() == 102 assert V.t.time == 103 V.received( LightMessages.GetColor(), LightMessages.GetColor(), LightMessages.GetColor(), DeviceMessages.GetHostFirmware(), keep_duplicates=True, ) ff.cancel()
@pytest.fixture() async def finder(self, sender): async with Finder(sender) as finder: yield finder async it "can match against a fltr", sender, finder, fake_time, final_future: V = VBase(fake_time, sender, finder, final_future) await V.choose_device("light") V.t.add(1) assert await V.matches(None) V.received() assert await V.matches(Filter.from_kwargs(label="kitchen")) V.received(LightMessages.GetColor()) V.assertTimes({InfoPoints.LIGHT_STATE: 1, InfoPoints.VERSION: 1}) V.t.add(5) assert not (await V.matches(Filter.from_kwargs(label="den"))) V.received() V.assertTimes({InfoPoints.LIGHT_STATE: 1, InfoPoints.VERSION: 1}) V.t.add(2) assert not (await V.matches(Filter.from_kwargs(label="attic", refresh_info=True))) V.received(LightMessages.GetColor()) V.assertTimes({InfoPoints.LIGHT_STATE: 8, InfoPoints.VERSION: 1}) V.t.add(1) assert not (await V.matches(Filter.from_kwargs(group_name="aa", cap=["matrix"]))) V.received(DeviceMessages.GetGroup())
found, ss = await reference.find(runner.sender, timeout=5) reference.raise_on_missing(found) assert sorted(list(found)) == sorted(binascii.unhexlify(s)[:6] for s in ss) assert ss == sorted(V.serials) for device in V.devices: device.compare_received([]) device.reset_received() reference = DeviceFinder.from_kwargs(label="kitchen") found, ss = await reference.find(runner.sender, timeout=5) reference.raise_on_missing(found) assert sorted(list(found)) == sorted(binascii.unhexlify(s)[:6] for s in ss) assert ss == [] for device in V.devices: device.compare_received([LightMessages.GetColor()]) device.reset_received() V.d3.attrs.label = "kitchen" reference = DeviceFinder.from_kwargs(label="kitchen") found, ss = await reference.find(runner.sender, timeout=5) reference.raise_on_missing(found) assert sorted(list(found)) == sorted(binascii.unhexlify(s)[:6] for s in ss) assert ss == [V.d3.serial] for device in V.devices: device.compare_received([LightMessages.GetColor()]) device.reset_received() reference = DeviceFinder.from_kwargs(cap="matrix") found, ss = await reference.find(runner.sender, timeout=5)
return assertUnhandled async it "responds to label messages", device, assertResponse: await assertResponse(DeviceMessages.GetLabel(), [DeviceMessages.StateLabel(label="")]) await assertResponse( DeviceMessages.SetLabel(label="sam"), [DeviceMessages.StateLabel(label="sam")], label="sam", ) await assertResponse( DeviceMessages.GetLabel(), [DeviceMessages.StateLabel(label="sam")], label="sam" ) async it "replies to light messages with a StateUnhandled packet", device, assertUnhandled: await assertUnhandled(LightMessages.GetColor()) await assertUnhandled(LightMessages.GetLightPower()) describe "LightState": @pytest.fixture() def device(self): device = devices["a19"] devices.store(device).assertAttrs(label="", power=0, color=hp.Color(0, 0, 1, 3500)) return device @pytest.fixture() def assertResponse(self, device, **attrs): return makeAssertResponse(device, **attrs)
async def checker(ff, l): info = {"serial": V.fake_device.serial, "product_type": "unknown"} assert V.device.info == info await hp.wait_for_all_futures( *[V.device.point_futures[kls] for kls in InfoPoints if kls is not InfoPoints.LABEL] ) found = [] for kls in list(InfoPoints): if kls is not InfoPoints.LABEL: found.append(V.device.point_futures[kls].result()) assert found == [1, 2, 3, 4, 5] assert V.t.time == 5 V.received(*msgs) info.update( { "label": "kitchen", "power": "off", "hue": 0.0, "saturation": 0.0, "brightness": 1.0, "kelvin": 3500, "firmware_version": "2.80", "product_id": 27, "product_name": "LIFX A19", "product_type": "light", "cap": pytest.helpers.has_caps_list("color", "variable_color_temp"), "group_id": "aa000000000000000000000000000000", "group_name": "g1", "location_id": "bb000000000000000000000000000000", "location_name": "l1", } ) assert V.device.info == info await hp.wait_for_all_futures(Futs.color) V.received(LightMessages.GetColor(), keep_duplicates=True) assert V.t.time == 12 await hp.wait_for_all_futures(Futs.group, Futs.location) V.received( *([LightMessages.GetColor()] * 5), DeviceMessages.GetGroup(), DeviceMessages.GetLocation(), keep_duplicates=True, ) # First location was at t=5 # We then wait another 60 # 60 is at 12 rounds, and next location after that is after 5 assert V.t.time == 65 # Now we remove knowledge of the device # And expect the information loop to end assert V.device.serial in V.sender.found async with V.fake_device.offline(): with assertRaises(FoundNoDevices): await FoundSerials().find(V.sender, timeout=1) assert V.device.serial not in V.sender.found assert not l.done() # This will timeout if it hasn't ended await l ff.cancel()
) async it "responds to Color messages", device: def light_state(label, power, hue, saturation, brightness, kelvin): return LightMessages.LightState.empty_normalise( label=label, power=power, hue=hue, saturation=saturation, brightness=brightness, kelvin=kelvin, ) await device.assertResponse( LightMessages.GetColor(), [light_state("", 0, 0, 0, 1, 3500)] ) await device.assertResponse( DeviceMessages.SetLabel(label="bob"), [DeviceMessages.StateLabel(label="bob")], label="bob", ) await device.assertResponse( LightMessages.GetColor(), [light_state("bob", 0, 0, 0, 1, 3500)] ) await device.assertResponse( DeviceMessages.SetPower(level=300), [DeviceMessages.StatePower(level=0)], power=300 ) await device.assertResponse(