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)
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 tile_dice(target, serials, afr, **kwargs): canvas = Canvas() def default_color_func(i, j): if j == -3: return Color(0, 1, 0.4, 3500) return Color(0, 0, 0, 3500) canvas.set_default_color_func(default_color_func) numbers = ["1", "2", "3", "4", "5"] characters = [dice[n] for n in numbers] color = Color(100, 1, 1, 3500) put_characters_on_canvas(canvas, characters, coords_for_horizontal_line, color) orientations = {} async for pkt, _, _ in target.script(TileMessages.GetDeviceChain()).run_with( serials, afr, **kwargs ): if pkt | TileMessages.StateDeviceChain: orientations[pkt.serial] = orientations_from(pkt) made, _ = make_rgb_and_color_pixels(canvas, 5) msgs = [] for serial in serials: os = orientations.get(serial) for msg in canvas_to_msgs( canvas, coords_for_horizontal_line, duration=1, acks=True, orientations=os ): msg.target = serial msgs.append(msg) await target.script(msgs).run_with_all(None, afr, **kwargs) return made
async def get_device_chain(collector, target, reference, **kwargs): """ Get the devices in your chain """ async for pkt, _, _ in target.script( TileMessages.GetDeviceChain()).run_with(reference): if pkt | TileMessages.StateDeviceChain: print(pkt.serial) for tile in tiles_from(pkt): print(" ", repr(tile))
async def get_tile_positions(collector, target, reference, **kwargs): """ Get the positions of the tiles in your chain. ``lan:get_tile_positions d073d5f09124`` """ async for pkt, _, _ in target.script( TileMessages.GetDeviceChain()).run_with(reference): print(pkt.serial) for tile in tiles_from(pkt): print(f"\tuser_x: {tile.user_x}, user_y: {tile.user_y}") print("")
async def gen(reference, sender, **kwargs): ps = sender.make_plans("capability") async for serial, _, info in sender.gatherer.gather( ps, reference, **kwargs): if info["cap"].has_matrix: for i, (user_x, user_y) in enumerate(positions): yield TileMessages.SetUserPosition( tile_index=i, user_x=user_x, user_y=user_y, res_required=False, target=serial, )
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 []
def chain_msgs(self, overrides): power_message = self.power_message(overrides) if power_message: yield power_message duration = self.determine_duration(overrides) for i, lst in enumerate(self.chain): colors = self.colors_from_hsbks(lst, overrides) yield TileMessages.Set64( tile_index=i, length=1, x=0, y=0, width=8, duration=duration, colors=colors, res_required=False, )
async def change(self, serial, tile_index, left_x, top_y, target, afr): if serial not in self.serials: return {"serial": serial, "data": None} user_x = (left_x + 4) / 8 user_y = (top_y - 4) / 8 msg = TileMessages.SetUserPosition(tile_index=tile_index, user_x=user_x, user_y=user_y, res_required=False) errors = [] await target.script(msg).run_with_all(serial, afr, error_catcher=errors, message_timeout=5) hp.async_as_background( self.highlight(serial, tile_index, target, afr, error_catcher=[], message_timeout=3)) if errors: return {"serial": serial, "data": None} plans = make_plans(chain=ChainPlan(refresh=True)) gatherer = Gatherer(target) got = await gatherer.gather_all(plans, serial, afr, error_catcher=errors, message_timeout=5) if serial in got and got[serial][0]: pixel_coords = user_coords_to_pixel_coords( got[serial][1]["chain"]["coords_and_sizes"]) self.serials[serial]["coords"] = [xy for xy, _ in pixel_coords] return {"serial": serial, "data": self.info_for_browser(serial)}
async def set_tile_positions(collector, target, reference, **kwargs): """ Set the positions of the tiles in your chain. ``lan:set_tile_positions d073d5f09124 -- '[[0, 0], [-1, 0], [-1, 1]]'`` """ extra = collector.configuration["photons_app"].extra_as_json positions = sb.listof(sb.listof(sb.float_spec())).normalise( Meta.empty(), extra) if any(len(position) != 2 for position in positions): raise PhotonsAppError( "Please enter positions as a list of two item lists of user_x, user_y" ) async with target.session() as afr: for i, (user_x, user_y) in enumerate(positions): msg = TileMessages.SetUserPosition(tile_index=i, user_x=user_x, user_y=user_y, res_required=False) await target.script(msg).run_with_all(reference, afr)
async def restore(self, serial, initial, target, afr): msgs = [DeviceMessages.SetPower(level=initial["power"])] for i, colors in enumerate(initial["colors"]): msgs.append( TileMessages.Set64( tile_index=i, length=1, width=8, x=0, y=0, colors=colors, res_required=False, ack_required=True, )) def errors(e): log.error(hp.lc("Error restoring tile", serial=serial, error=e)) await target.script(msgs).run_with_all(serial, afr, error_catcher=errors)
def __init__(self): self.targets_cache = LRU(1000) self.source_bits_cache = LRU(10) self.duration_bits_cache = LRU(10) self.cache = ProtocolColor.Meta.cache msg = TileMessages.SetState64( source = 0 , sequence = 0 , target = None , res_required = False , ack_required = True , tile_index = 0 , length = 1 , x = 0 , y = 0 , width = 8 , duration = 0 , colors = [{"hue": 0, "saturation": 0, "brightness": 0, "kelvin": 3500}] ) self.frame_header_start = msg.frame_header.pack()[:-32] self.frame_address_with_acks_middle = msg.frame_address.pack()[64:-8] msg.ack_required = False self.frame_address_without_acks_middle = msg.frame_address.pack()[64:-8] self.protocol_header_packd = msg.protocol_header.pack() # tile_index, width, duration and colors are variable self.payload_middle = msg.payload.pack()[8:-8 -32 - (64 * 64)] self.uint8_bits = {val: self.bits(T.Uint8, val) for val in range(256)} self.width_bits = self.uint8_bits self.sequence_bits = self.uint8_bits self.tile_index_bits = self.uint8_bits
async def tile_msgs(self): coords_and_sizes = None async for _, _, info in self.gather(self.make_plans("chain")): reorient = info["reorient"] coords_and_sizes = info["coords_and_sizes"] if not coords_and_sizes: log.warning( hp.lc("Couldn't work out how many zones the device had", serial=self.serial) ) return applied = self.aps["2d"].from_user_coords(coords_and_sizes).apply_theme(self.theme) for i, (hsbks, coords_and_size) in enumerate(zip(applied, coords_and_sizes)): colors = reorient( i, [ { "hue": self.options.overrides.get("hue", hsbk.hue), "saturation": self.options.overrides.get("saturation", hsbk.saturation), "brightness": self.options.overrides.get("brightness", hsbk.brightness), "kelvin": self.options.overrides.get("kelvin", hsbk.kelvin), } for hsbk in hsbks ], ) yield TileMessages.Set64( tile_index=i, length=1, x=0, y=0, width=coords_and_size[1][0], duration=self.options.duration, colors=colors, res_required=False, ack_required=True, )
async def fill(self, random_orientations=False): msgs = [] if self.background_option.type == "current": msgs.append( TileMessages.GetState64.empty_normalise(tile_index=0, length=255, x=0, y=0, width=8)) msgs.append(TileMessages.GetDeviceChain()) async for pkt, _, _ in self.target.script(msgs).run_with( self.serials, self.afr): serial = pkt.serial if pkt | TileMessages.State64: self.info_by_serial[serial].states.append( (pkt.tile_index, pkt)) elif pkt | TileMessages.StateDeviceChain: if self.coords is None: coords = [] for tile in tiles_from(pkt): coords.append(((tile.user_x, tile.user_y), (tile.width, tile.height))) self.info_by_serial[ serial].coords = user_coords_to_pixel_coords(coords) orientations = orientations_from(pkt) if random_orientations: self.info_by_serial[serial].orientations = { i: random.choice(list(O.__members__.values())) for i in orientations } else: self.info_by_serial[serial].orientations = orientations
assert part.height == height assert part.left == (2 * 8) assert part.right == (2 * 8) + 5 assert part.top == 3 * 8 assert part.bottom == (3 * 8) - 10 part._set_64.update({"source": 0, "sequence": 1}) real_set_64 = TileMessages.Set64( x=0, y=0, length=1, tile_index=5, colors=[], duration=0, ack_required=False, width=5, res_required=False, target=V.device.serial, source=0, sequence=1, ) assert part._set_64.pack()[36 * 8 :] == real_set_64.payload.pack() it "can be used as a key in dictionary", V: dct = {V.part: 1} assert dct[V.part] == 1 assert dct[(V.device, V.part.part_number)] == 1 assert dct[(V.device.serial, V.part.part_number)] == 1 it "can be compared for equality", V:
got = await self.gather(sender, serials, "colors") assert got[switch.serial][1]["colors"] is Skip assert got[light1.serial][1]["colors"] == tile_expected assert got[light2.serial][1]["colors"] == [[hp.Color(100, 0.5, 0.8, 2500)]] 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(),
describe "Matrix": @pytest.fixture def device(self): device = devices["tile"] devices.store(device).assertAttrs(matrix_effect=TileEffectType.OFF) return device @pytest.fixture() def assertResponse(self, device, **attrs): return makeAssertResponse(device, **attrs) async it "responds to changing user position", device, assertResponse: await assertResponse( TileMessages.SetUserPosition(tile_index=1, user_x=0, user_y=1), [], ) assert device.attrs.chain[1].user_x == 0 assert device.attrs.chain[1].user_y == 1 await assertResponse( TileMessages.SetUserPosition(tile_index=1, user_x=3, user_y=4), [], ) assert device.attrs.chain[1].user_x == 3 assert device.attrs.chain[1].user_y == 4 await assertResponse( TileMessages.SetUserPosition(tile_index=2, user_x=4, user_y=5), [],
describe "MatrixResponder": @pytest.fixture async def device(self): device = Device( chp.ProductResponder.from_product(Products.LCM3_TILE), chp.MatrixResponder() ) async with device: device.assertAttrs(matrix_effect=TileEffectType.OFF) yield device async it "responds to tile effect messages", device: await device.assertResponse( TileMessages.GetTileEffect(), [ TileMessages.StateTileEffect.empty_normalise( type=TileEffectType.OFF, palette_count=0, parameters={} ) ], ) await device.assertResponse( TileMessages.SetTileEffect( type=TileEffectType.FLAME, palette_count=1, palette=[chp.Color(1, 0, 1, 3500)] ), [ TileMessages.StateTileEffect.empty_normalise( type=TileEffectType.OFF, palette_count=0, parameters={}, palette=[] ) ],
async def gen(reference, sender, **kwargs): ps = sender.make_plans("capability") async for serial, _, info in sender.gatherer.gather( ps, reference, **kwargs): if info["cap"].has_matrix: yield TileMessages.GetDeviceChain(target=serial)
def setter(**kwargs): return TileMessages.Set64( length=1, x=0, y=0, width=8, res_required=False, **kwargs )
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 []
def messages(self): if self.zones is Zones.MATRIX: return [TileMessages.GetDeviceChain()] return []
def messages(self): if self.is_multizone: return [MultiZoneMessages.GetMultiZoneEffect()] elif self.is_matrix: return [TileMessages.GetTileEffect()] return Skip
async def highlight(self, serial, tile_index, target, afr, error_catcher=None, message_timeout=3): if serial not in self.serials or self.serials[serial][ "highlightlock"].locked(): return async with self.serials[serial]["highlightlock"]: if serial not in self.serials: return passed = 0 row = -1 pixels = self.serials[serial]["color_pixels"][tile_index] reorient = self.serials[serial]["reorient"] while passed < 2 and not afr.stop_fut.done(): colors = [] for i in range(8): if row < i: start = i * 8 colors.extend(pixels[start:start + 8]) else: colors.extend([{ "hue": 0, "saturation": 0, "brightness": 0, "kelvin": 3500 }] * 8) msg = TileMessages.Set64( tile_index=tile_index, length=1, x=0, y=0, width=8, colors=reorient(tile_index, colors), res_required=False, ack_required=False, ) await target.script(msg).run_with_all(serial, afr, error_catcher=[]) await asyncio.sleep(0.075) if passed == 0: row += 3 if row > 7: passed += 1 else: row -= 3 if row < 0: passed += 1 if not afr.stop_fut.done(): msg = TileMessages.Set64( tile_index=tile_index, length=1, x=0, y=0, width=8, colors=reorient(tile_index, pixels), res_required=False, ack_required=True, ) await target.script(msg).run_with_all(serial, afr, error_catcher=[], message_timeout=1)