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 get_zones(self, start_index=0, end_index=255): buf = [] bufs = [] for i, zone in enumerate(self.device.attrs.zones): if i < start_index or i > end_index: continue if len(buf) == 8: bufs.append(buf) buf = [] buf.append((i, zone)) if buf: bufs.append(buf) for buf in bufs: if len(buf) == 1: yield MultiZoneMessages.StateZone( zones_count=len(self.device.attrs.zones), zone_index=buf[0][0], **buf[0][1].as_dict(), ) continue yield MultiZoneMessages.StateMultiZone( zones_count=len(self.device.attrs.zones), zone_index=buf[0][0], colors=[b.as_dict() for _, b in buf], )
def zone_msgs(self, overrides): power_message = self.power_message(overrides) if power_message: yield power_message colors = self.colors_from_hsbks(self.zones, overrides) duration = self.determine_duration(overrides) start = 0 color = None i = -1 while i < len(colors) - 1: i += 1 if color is None: color = colors[i] continue if colors[i] != color: yield MultiZoneMessages.SetColorZones( start_index=start, end_index=i - 1, **color, duration=duration, res_required=False, ) color = colors[i] start = i color = colors[i] yield MultiZoneMessages.SetColorZones(start_index=start, end_index=i, **color, duration=duration, res_required=False)
def messages(self): if self.is_multizone: if self.has_extended_multizone: return [MultiZoneMessages.GetExtendedColorZones()] else: return [MultiZoneMessages.GetColorZones(start_index=0, end_index=255)] return Skip
async def zones_from_reference(target, reference, afr=sb.NotSpecified, **kwargs): """ Return a dictionary of {serial: [(zone_index, colors), ...]} for the provided reference We assume all the devices support multizone """ final = {} msg = MultiZoneMessages.GetColorZones(start_index=0, end_index=255) options = MergedOptions.using({"timeout": 5}, kwargs).as_dict() by_serial = defaultdict(list) async for pkt, _, _ in target.script(msg).run_with(reference, afr, **options): by_serial[pkt.serial].append(pkt) for serial, pkts in by_serial.items(): final[serial] = [] for p in pkts: if p | MultiZoneMessages.StateMultiZone: for i, color in enumerate(p.colors): final[serial].append((p.zone_index + i, color)) return final
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})
def make_hsbk_new_messages(self, zone_index, colors, duration): return MultiZoneMessages.SetExtendedColorZones( duration=duration, colors_count=len(colors), colors=colors, zone_index=zone_index, ack_required=True, res_required=False, )
def setter(h, s, b, k, **kwargs): return MultiZoneMessages.SetColorZones( hue=h, saturation=s, brightness=b, kelvin=k, res_required=False, **kwargs, )
def make_hsbk_old_messages(self, zone_index, colors, duration): set_color_old = [] end = zone_index start = zone_index current = None for i, color in enumerate(colors): i = i + zone_index if current is None: current = color continue if current != color: set_color_old.append( MultiZoneMessages.SetColorZones( start_index=start, end_index=end, duration=duration, ack_required=True, res_required=False, **current, )) start = i current = color end = i if not set_color_old or set_color_old[-1].end_index != i: set_color_old.append( MultiZoneMessages.SetColorZones( start_index=start, end_index=end, duration=duration, ack_required=True, res_required=False, **current, )) return set_color_old
def make_old_messages(self): if not self.colors: return end = self.zone_index start = self.zone_index current = Empty sections = [] for i, color in enumerate(self.colors): i = i + self.zone_index if current is Empty: current = color continue if current != color: sections.append([start, end, current]) start = i start = i current = color end = i if current is not Empty and (not sections or sections[-1][1] != i): sections.append([start, end, current]) msgs = [] for start, end, color in sections: h, s, b, k = color msgs.append( MultiZoneMessages.SetColorZones( start_index=start, end_index=end, duration=self.duration, ack_required=True, res_required=False, target=self.serial, hue=h, saturation=s, brightness=b, kelvin=k, )) return msgs
def make_new_messages(self): if not self.colors: return colors = [] for c in self.colors: if isinstance(c, dict) or getattr(c, "is_dict", False): colors.append(c) else: colors.append(Color(*c)) return (MultiZoneMessages.SetExtendedColorZones( duration=self.duration, colors_count=len(self.colors), colors=colors, target=self.serial, zone_index=self.zone_index, ack_required=True, res_required=False, ), )
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(), DeviceMessages.GetVersion(), MultiZoneMessages.GetExtendedColorZones(), ], } for device in devices: if device not in expected:
describe "Zones": async def make_device(self, name, zones=None): device = devices[name] if zones is not None: await device.change_one("zones", zones, event=None) return device async it "doesn't respond if we aren't a multizone device": device = devices["a19"] assert "zones_effect" not in device.attrs assert "zones" not in device.attrs assertUnhandled = makeAssertUnhandled(device) await assertUnhandled(MultiZoneMessages.GetMultiZoneEffect()) await assertUnhandled( MultiZoneMessages.SetMultiZoneEffect.create( type=MultiZoneEffectType.MOVE, parameters={} ) ) await assertUnhandled(MultiZoneMessages.GetColorZones(start_index=0, end_index=255)) await assertUnhandled( MultiZoneMessages.SetColorZones( start_index=0, end_index=1, hue=0, saturation=1, brightness=1, kelvin=3500 ) ) await assertUnhandled(MultiZoneMessages.GetExtendedColorZones()) await assertUnhandled(
def messages(self): if self.zones is Zones.MATRIX: return [TileMessages.GetDeviceChain()] elif self.zones is Zones.LINEAR: return [MultiZoneMessages.GetColorZones(start_index=0, end_index=0)] return []
return Device( chp.ProductResponder.from_product(enum, firmware), chp.ZonesResponder(zones=zones), ) async it "complains if you try to set too many zones": zones = [chp.Color(0, 0, 0, 0)] * 83 with assertRaises(PhotonsAppError, "Can only have up to 82 zones!"): async with self.make_device(Products.LCM1_Z, chp.Firmware(0, 0, 0), zones): pass async it "doesn't respond if we aren't a multizone device": async with self.make_device(Products.LCMV4_A19_COLOR, chp.Firmware(0, 0, 0)) as device: assert "zones_effect" not in device.attrs assert "zones" not in device.attrs await device.assertResponse(MultiZoneMessages.GetMultiZoneEffect(), []) await device.assertResponse( MultiZoneMessages.SetMultiZoneEffect.empty_normalise( type=MultiZoneEffectType.MOVE, parameters={} ), [], ) await device.assertResponse( MultiZoneMessages.GetColorZones(start_index=0, end_index=255), [] ) await device.assertResponse( MultiZoneMessages.SetColorZones( start_index=0, end_index=1, hue=0, saturation=1, brightness=1, kelvin=3500 ), [],
assert got == { striplcm1.serial: [(i, c) for i, c in enumerate(zones1)], striplcm2noextended.serial: [(i, c) for i, c in enumerate(zones2)], striplcm2extended.serial: [(i, c) for i, c in enumerate(zones3)], } async it "resends messages if no gatherer is reset between runs", sender: async for serial, zones in zones_from_reference(devices.serials, sender): pass want = { device: [DeviceMessages.GetHostFirmware(), DeviceMessages.GetVersion()] for device in devices } want[striplcm1].append(MultiZoneMessages.GetColorZones(start_index=0, end_index=255)) want[striplcm2noextended].append( MultiZoneMessages.GetColorZones(start_index=0, end_index=255) ) want[striplcm2extended].append(MultiZoneMessages.GetExtendedColorZones()) self.compare_received(want) del sender.gatherer async for serial, zones in zones_from_reference(devices.serials, sender): pass self.compare_received(want) async it "uses cached gatherer on the sender", sender: async for serial, zones in zones_from_reference(devices.serials, sender): pass
def messages(self): if self.is_multizone: return [MultiZoneMessages.GetMultiZoneEffect()] elif self.is_matrix: return [TileMessages.GetTileEffect()] return Skip
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", ) it "can format messages with lists": pkt = MultiZoneMessages.SetExtendedColorZones( ack_required=False, res_required=False, sequence=5, target="d073d5004444", colors_count=3, colors=[ {"hue": 3, "brightness": 1, "saturation": 1, "kelvin": 9000}, {"hue": 20, "saturation": 1}, {"hue": 30, "saturation": 1, "brightness": 0.2}, ], ) self.assertLines( pkt, "SetExtendedColorZones(ack=False,res=False,source=<NOT_SPECIFIED>,sequence=5,target=d073d5004444)", " duration: 0.0", " apply: <MultiZoneExtendedApplicationRequest.APPLY: 1>", " zone_index: <NOT_SPECIFIED>", " colors_count: 3", " colors:", " {'brightness': 1.0, 'hue': 3.0, 'kelvin': 9000, 'saturation': 1.0}", " {'brightness': '<NOT_SPECIFIED>', 'hue': 20.0, 'kelvin': 3500, 'saturation': 1.0}",
device.io["MEMORY"], pkt=CoreMessages.Acknowledgement, replying_to=original, ), devices.Events.OUTGOING( device, device.io["MEMORY"], pkt=expected, replying_to=original ), ] async it "can get multiple replies", send_single, device: await device.event( devices.Events.SET_ZONES, zones=[(i, hp.Color(i, 1, 1, 3500)) for i in range(22)] ) devices.store(device).clear() original = MultiZoneMessages.GetColorZones(start_index=0, end_index=255) expected = [ ( MultiZoneMessages.StateMultiZone, { "zones_count": 22, "zone_index": 0, "colors": [hp.Color(i, 1, 1, 3500) for i in range(8)], }, ), ( MultiZoneMessages.StateMultiZone, { "zones_count": 22, "zone_index": 8, "colors": [hp.Color(i, 1, 1, 3500) for i in range(8, 16)],