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)
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"])
class CapabilityPlan(Plan): """ Return capability information for this device:: { "cap": <capability object with filled out firmware version>, "product": <product object>, "firmware": <object with build/version_major/version_minor attributes>, "state_version": <the StateVersion packet we received from the device> } The capability and product objects come from the :ref:`registry <products_root>`. """ messages = [DeviceMessages.GetHostFirmware(), DeviceMessages.GetVersion()] class Instance(Plan.Instance): def process(self, pkt): if pkt | DeviceMessages.StateHostFirmware: self.firmware = FirmwareInfo( build=pkt.build, version_major=pkt.version_major, version_minor=pkt.version_minor, ) elif pkt | DeviceMessages.StateVersion: self.version = pkt return hasattr(self, "firmware") and hasattr(self, "version") async def info(self): product = Products[self.version.vendor, self.version.product] cap = product.cap(self.firmware.version_major, self.firmware.version_minor) return { "cap": cap, "product": product, "firmware": self.firmware, "state_version": DeviceMessages.StateVersion.Payload.create( self.version.payload.pack() ), }
class FirmwarePlan(Plan): """ Return in a dictionary * build - The build timestamp of this firmware * version_major - the major component of the firmware version * version_minor - the minor component of the firmware version """ messages = [DeviceMessages.GetHostFirmware()] class Instance(Plan.Instance): def process(self, pkt): if pkt | DeviceMessages.StateHostFirmware: self.firmware = FirmwareInfo( build=pkt.build, version_major=pkt.version_major, version_minor=pkt.version_minor, ) return True async def info(self): return self.firmware
async for serial, cap in find_multizone(devices.serials, sender): assert serial not in got got[serial] = cap.has_extended_multizone assert got == { striplcm1.serial: False, striplcm2noextended.serial: False, striplcm2extended.serial: True, } async it "resends messages each time if we reset the gatherer", sender: async for serial, cap in find_multizone(devices.serials, sender): pass want = { device: [DeviceMessages.GetHostFirmware(), DeviceMessages.GetVersion()] for device in devices } self.compare_received(want) del sender.gatherer async for serial, cap in find_multizone(devices.serials, sender): pass want = { device: [DeviceMessages.GetHostFirmware(), DeviceMessages.GetVersion()] for device in devices } self.compare_received(want) async it "uses cached gatherer on the sender", sender:
expected = { clean.serial: (True, {"zones": Skip}), switch.serial: (True, {"zones": Skip}), light1.serial: (True, {"zones": Skip}), light2.serial: (True, {"zones": Skip}), striplcm1.serial: (True, {"zones": [(i, c) for i, c in enumerate(zones1)]}), striplcm2noextended.serial: ( True, {"zones": [(i, c) for i, c in enumerate(zones2)]}, ), striplcm2extended.serial: (True, {"zones": [(i, c) for i, c in enumerate(zones3)]}), } assert got == expected expected = { clean: [DeviceMessages.GetHostFirmware(), DeviceMessages.GetVersion()], switch: [DeviceMessages.GetHostFirmware(), DeviceMessages.GetVersion()], light1: [DeviceMessages.GetHostFirmware(), DeviceMessages.GetVersion()], light2: [DeviceMessages.GetHostFirmware(), DeviceMessages.GetVersion()], striplcm1: [ DeviceMessages.GetHostFirmware(), DeviceMessages.GetVersion(), MultiZoneMessages.GetColorZones(start_index=0, end_index=255), ], striplcm2noextended: [ DeviceMessages.GetHostFirmware(), DeviceMessages.GetVersion(), MultiZoneMessages.GetColorZones(start_index=0, end_index=255), ], striplcm2extended: [ DeviceMessages.GetHostFirmware(),
"group_id": "aa000000000000000000000000000000", "product_id": 27, "product_name": "LIFX A19", "product_type": "light", "group_name": "g1", "location_id": "bb000000000000000000000000000000", "location_name": "l1", } for device in V.devices: if device is V.d3: V.devices.store(device).assertIncoming( DiscoveryMessages.GetService(), LightMessages.GetColor(), DeviceMessages.GetVersion(), DeviceMessages.GetHostFirmware(), DeviceMessages.GetGroup(), DeviceMessages.GetLocation(), ) elif device is V.d4: V.devices.store(device).assertIncoming( DiscoveryMessages.GetService(), DeviceMessages.GetVersion(), LightMessages.GetColor(), DeviceMessages.GetLabel(), ) else: V.devices.store(device).assertIncoming( DiscoveryMessages.GetService(), DeviceMessages.GetVersion(), LightMessages.GetColor(),
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, )
assert len(devices) > 0 for device in devices: if device not in expected: assert False, f"No expectation for {device.serial}" for device, msgs in expected.items(): assert device in devices devices.store(device).assertIncoming(*msgs, ignore=[DiscoveryMessages.GetService]) devices.store(device).clear() async it "sets the default config", sender: expected = { light1: [ DeviceMessages.GetHostFirmware(), DeviceMessages.GetVersion(), LightMessages.SetHevCycleConfiguration(indication=True, duration_s=3600), ], light2: [ DeviceMessages.GetHostFirmware(), DeviceMessages.GetVersion(), LightMessages.SetHevCycleConfiguration(indication=True, duration_s=3600), ], light3: [DeviceMessages.GetHostFirmware(), DeviceMessages.GetVersion()], } await self.run_and_compare( sender, SetCleanConfig(indication=True, duration_s=3600), expected=expected )
async it "complains if we have more than 16 colors in the palette", runner: with assertRaises(PhotonsAppError, "Palette can only be up to 16 colors", got=17): SetTileEffect("flame", palette=["red"] * 17) async it "can power on devices and set tile effect", runner: msg = SetTileEffect("flame") got = await runner.sender(msg, runner.serials) assert got == [] for tile in tiles: assert tile.attrs.matrix_effect is TileEffectType.FLAME self.compare_received( { nottile: [DeviceMessages.GetHostFirmware(), DeviceMessages.GetVersion()], tile1: [ DeviceMessages.GetHostFirmware(), DeviceMessages.GetVersion(), LightMessages.SetLightPower(level=65535, duration=1), TileMessages.SetTileEffect.empty_normalise( type=TileEffectType.FLAME, palette=default_tile_palette, palette_count=len(default_tile_palette), ), ], tile2: [ DeviceMessages.GetHostFirmware(), DeviceMessages.GetVersion(), LightMessages.SetLightPower(level=65535, duration=1), TileMessages.SetTileEffect.empty_normalise(
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.LIGHT_STATE ] ) found = [] for kls in list(InfoPoints): if kls is not InfoPoints.LIGHT_STATE: 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": "switcharoo", "firmware_version": "3.90", "product_id": 89, "product_name": "LIFX Switch", "product_type": "non_light", "cap": pytest.helpers.has_caps_list("buttons", "unhandled", "relays"), "group_id": "aa000000000000000000000000000000", "group_name": "g1", "location_id": "bb000000000000000000000000000000", "location_name": "l1", } ) assert V.device.info == info await hp.wait_for_all_futures(Futs.label) V.received(DeviceMessages.GetLabel(), keep_duplicates=True) assert V.t.time == 12 await hp.wait_for_all_futures(Futs.group, Futs.location) V.received( *([DeviceMessages.GetLabel()] * 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.LABEL].result() == 62 await hp.wait_for_all_futures(Futs.label) V.received(DeviceMessages.GetLabel(), 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.LABEL].result() == 102 assert V.t.time == 103 V.received( DeviceMessages.GetLabel(), DeviceMessages.GetLabel(), DeviceMessages.GetLabel(), DeviceMessages.GetHostFirmware(), keep_duplicates=True, ) ff.cancel()
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()
for device in devices: if device not in expected: assert False, f"No expectation for {device.serial}" for device, msgs in expected.items(): assert device in devices devices.store(device).assertIncoming(*msgs, ignore=[DiscoveryMessages.GetService]) devices.store(device).clear() async it "sends the messages to devices with only correct capability", sender: msg = ForCapability(hev=LightMessages.GetHevCycle()) expected = { a19: [DeviceMessages.GetHostFirmware(), DeviceMessages.GetVersion()], clean: [ DeviceMessages.GetHostFirmware(), DeviceMessages.GetVersion(), LightMessages.GetHevCycle(), ], ir: [DeviceMessages.GetHostFirmware(), DeviceMessages.GetVersion()], } await self.assertScript(sender, msg, expected=expected) async it "can send message to groups", sender: msg = ForCapability(**{"ir,hev": DeviceMessages.SetPower(level=65535)}) expected = {