#!/usr/bin/env python3 """Add Winter profile: 6-light 2x3 grid, presets, and sequences.""" from __future__ import annotations import json from copy import deepcopy from pathlib import Path ROOT = Path(__file__).resolve().parents[1] DB = ROOT / "db" PROFILE_ID = "3" PALETTE_ID = "14" ZONE_PRESETS_ID = "11" ZONE_SEQUENCES_ID = "12" # 2x3 grid device MACs (placeholders — assign real devices in the UI) DEVICE_MACS = [ "a0b100000001", # r0c0 top-left "a0b100000002", # r0c1 "a0b100000003", # r0c2 "a0b100000004", # r1c0 bottom-left "a0b100000005", # r1c1 "a0b100000006", # r1c2 ] GROUP_CELL = { "a0b100000001": "6", "a0b100000002": "7", "a0b100000003": "8", "a0b100000004": "9", "a0b100000005": "10", "a0b100000006": "11", } GROUP_TOP_ROW = "12" GROUP_BOTTOM_ROW = "13" GROUP_COL_LEFT = "14" GROUP_COL_MID = "15" GROUP_COL_RIGHT = "16" GROUP_ALL = "17" PRESET_OFF = "78" PRESET_TWINKLE = "79" PRESET_ICICLES = "80" PRESET_BLIZZARD = "81" PRESET_RIME = "82" PRESET_AURORA = "83" PRESET_STARFALL = "84" PRESET_SPARKLE = "85" PRESET_COOL_WHITE = "86" PRESET_CHASE_ICE = "87" SEQ_CASCADE = "12" SEQ_ROWS = "13" SEQ_COLUMNS = "14" SEQ_BLIZZARD_ALL = "15" SEQ_ROTATION = "16" def load_json(name: str) -> dict: path = DB / f"{name}.json" return json.loads(path.read_text(encoding="utf-8")) def save_json(name: str, data: dict) -> None: path = DB / f"{name}.json" path.write_text(json.dumps(data, separators=(",", ":")), encoding="utf-8") def preset_skeleton(name: str, pattern: str, colors: list, **extra) -> dict: doc = { "name": name, "pattern": pattern, "colors": colors, "brightness": 220, "delay": 80, "auto": True, "n1": 0, "n2": 0, "n3": 0, "n4": 0, "n5": 0, "n6": 0, "n7": 0, "n8": 0, "profile_id": PROFILE_ID, "background": "#0A1520", "manual_beat_n": 1, } doc.update(extra) if "palette_refs" not in doc and pattern not in ("on", "off"): doc["palette_refs"] = [None] * len(colors) return doc def seq_doc( name: str, lanes: list, lanes_group_ids: list, *, loop: bool = True, simulated_bpm: int = 90, ) -> dict: steps = [step for lane in lanes for step in lane] return { "name": name, "profile_id": PROFILE_ID, "group_ids": [GROUP_ALL], "lanes": lanes, "lanes_group_ids": lanes_group_ids, "advance_mode": "beats", "steps": steps, "step_duration_ms": 3000, "simulated_bpm": simulated_bpm, "sequence_transition": 500, "loop": loop, } def main() -> None: profiles = load_json("profile") palettes = load_json("palette") groups = load_json("group") devices = load_json("device") zones = load_json("zone") sequences = load_json("sequence") presets = load_json("preset") labels = [ ("winter top-left", 0), ("winter top-centre", 1), ("winter top-right", 2), ("winter bottom-left", 3), ("winter bottom-centre", 4), ("winter bottom-right", 5), ] profiles[PROFILE_ID] = { "name": "Winter", "type": "zones", "zones": [ZONE_PRESETS_ID, ZONE_SEQUENCES_ID], "scenes": [], "palette_id": PALETTE_ID, } palettes[PALETTE_ID] = [ "#E8F4FF", "#9ECFFF", "#5080C8", "#FFFFFF", "#B0DCFF", "#0A1520", "#FF8020", "#071018", ] for mac, (label, _idx) in zip(DEVICE_MACS, labels): devices[mac] = { "id": mac, "name": label, "type": "led", "transport": "wifi", "address": "", "default_pattern": None, "zones": [], "output_brightness": 255, "wifi_color_order": "rgb", "wifi_startup_mode": "default", } def group_row(gid: str, name: str, macs: list) -> None: groups[gid] = { "name": name, "devices": macs, "profile_id": PROFILE_ID, "wifi_color_order": "rgb", "wifi_startup_mode": "default", "output_brightness": 255, "pattern": "on", "colors": ["000000", "E8F4FF"], "brightness": 100, "delay": 100, "step_offset": 0, "step_increment": 1, "n1": 0, "n2": 0, "n3": 0, "n4": 0, "n5": 0, "n6": 0, "n7": 0, "n8": 0, } for mac, gid in zip(DEVICE_MACS, GROUP_CELL.values()): group_row(gid, labels[DEVICE_MACS.index(mac)][0], [mac]) group_row(GROUP_TOP_ROW, "winter top row", DEVICE_MACS[:3]) group_row(GROUP_BOTTOM_ROW, "winter bottom row", DEVICE_MACS[3:]) group_row(GROUP_COL_LEFT, "winter left column", [DEVICE_MACS[0], DEVICE_MACS[3]]) group_row(GROUP_COL_MID, "winter centre column", [DEVICE_MACS[1], DEVICE_MACS[4]]) group_row(GROUP_COL_RIGHT, "winter right column", [DEVICE_MACS[2], DEVICE_MACS[5]]) group_row(GROUP_ALL, "winter grid (all)", list(DEVICE_MACS)) presets[PRESET_OFF] = preset_skeleton("winter off", "off", [], brightness=0, delay=100) presets[PRESET_TWINKLE] = preset_skeleton( "winter twinkle", "twinkle", ["#78C8FF", "#508CFF", "#B478FF", "#64DCE8", "#A0C8FF"], n1=150, n2=20, n4=10, delay=100, ) presets[PRESET_ICICLES] = preset_skeleton( "winter icicles", "icicles", ["#F0F8FF", "#9ECFFF", "#FFFFFF"], n1=14, n2=11, n3=1, delay=80, ) presets[PRESET_BLIZZARD] = preset_skeleton( "winter blizzard", "blizzard", ["#FFFFFF", "#CDE8FF", "#AACCF5"], n1=110, n2=2, n3=140, delay=45, ) presets[PRESET_RIME] = preset_skeleton( "winter rime", "rime", ["#E8F4FF", "#FFFFFF", "#B8DCF8"], n1=40, n2=18, n3=4, delay=120, ) presets[PRESET_AURORA] = preset_skeleton( "winter aurora", "aurora", ["#183050", "#5090C8", "#C8E8FF"], n1=22, n2=210, n6=1, delay=90, ) presets[PRESET_STARFALL] = preset_skeleton( "winter starfall", "particles", ["#FFFFFF", "#C8E8FF", "#FFF8E0"], n1=16, n2=2, n3=12, n6=1, delay=55, ) presets[PRESET_SPARKLE] = preset_skeleton( "winter ice sparkle", "sparkle", ["#E8F4FF", "#B0DCFF", "#FFFFFF"], n1=70, n2=165, n3=1, n6=1, delay=50, ) presets[PRESET_COOL_WHITE] = preset_skeleton( "winter cool white", "on", ["#E6F2FF"], brightness=200, delay=100, ) presets[PRESET_CHASE_ICE] = preset_skeleton( "winter ice chase", "chase", ["#E8F4FF", "#5080C8"], auto=False, n1=20, n2=20, n3=15, n4=15, delay=120, background="#071018", ) grid_presets = [ [PRESET_ICICLES, PRESET_TWINKLE, PRESET_BLIZZARD], [PRESET_RIME, PRESET_AURORA, PRESET_STARFALL], ] flat = [p for row in grid_presets for p in row] zones[ZONE_PRESETS_ID] = { "name": "Winter grid", "names": [], "group_ids": [GROUP_ALL], "preset_group_ids": {}, "presets": grid_presets, "presets_flat": flat, "default_preset": PRESET_TWINKLE, "brightness": 200, "sequence_ids": [], "content_kind": "presets", } sequences[SEQ_CASCADE] = seq_doc( "Winter cell cascade", [ [{"preset_id": PRESET_ICICLES, "beats": 6}], [{"preset_id": PRESET_SPARKLE, "beats": 6}], [{"preset_id": PRESET_BLIZZARD, "beats": 6}], [{"preset_id": PRESET_RIME, "beats": 6}], [{"preset_id": PRESET_AURORA, "beats": 6}], [{"preset_id": PRESET_STARFALL, "beats": 6}], ], [ [GROUP_CELL[DEVICE_MACS[0]]], [GROUP_CELL[DEVICE_MACS[1]]], [GROUP_CELL[DEVICE_MACS[2]]], [GROUP_CELL[DEVICE_MACS[3]]], [GROUP_CELL[DEVICE_MACS[4]]], [GROUP_CELL[DEVICE_MACS[5]]], ], simulated_bpm=85, ) sequences[SEQ_ROWS] = seq_doc( "Winter row waves", [ [ {"preset_id": PRESET_BLIZZARD, "beats": 8}, {"preset_id": PRESET_ICICLES, "beats": 8}, ], [ {"preset_id": PRESET_AURORA, "beats": 8}, {"preset_id": PRESET_RIME, "beats": 8}, ], ], [[GROUP_TOP_ROW], [GROUP_BOTTOM_ROW]], simulated_bpm=80, ) sequences[SEQ_COLUMNS] = seq_doc( "Winter column chase", [ [{"preset_id": PRESET_CHASE_ICE, "beats": 12}], [{"preset_id": PRESET_TWINKLE, "beats": 12}], [{"preset_id": PRESET_STARFALL, "beats": 12}], ], [[GROUP_COL_LEFT], [GROUP_COL_MID], [GROUP_COL_RIGHT]], simulated_bpm=95, ) sequences[SEQ_BLIZZARD_ALL] = seq_doc( "Winter full blizzard", [[{"preset_id": PRESET_BLIZZARD, "beats": 16}]], [[GROUP_ALL]], simulated_bpm=75, ) sequences[SEQ_ROTATION] = seq_doc( "Winter showcase", [ [ {"preset_id": PRESET_ICICLES, "beats": 8}, {"preset_id": PRESET_BLIZZARD, "beats": 8}, {"preset_id": PRESET_RIME, "beats": 8}, {"preset_id": PRESET_AURORA, "beats": 8}, {"preset_id": PRESET_STARFALL, "beats": 8}, {"preset_id": PRESET_TWINKLE, "beats": 8}, ] ], [[GROUP_ALL]], simulated_bpm=72, ) zones[ZONE_SEQUENCES_ID] = { "name": "Winter sequences", "names": [], "group_ids": [GROUP_ALL], "preset_group_ids": {}, "presets": [], "presets_flat": [], "default_preset": None, "brightness": 200, "sequence_ids": [ SEQ_CASCADE, SEQ_ROWS, SEQ_COLUMNS, SEQ_BLIZZARD_ALL, SEQ_ROTATION, ], "content_kind": "sequences", } save_json("profile", profiles) save_json("palette", palettes) save_json("group", groups) save_json("device", devices) save_json("zone", zones) save_json("sequence", sequences) save_json("preset", presets) print("Winter profile created:") print(f" profile {PROFILE_ID}, palette {PALETTE_ID}") print(f" zones {ZONE_PRESETS_ID} (presets 2x3), {ZONE_SEQUENCES_ID} (sequences)") print(f" devices {', '.join(DEVICE_MACS)}") print(f" groups {GROUP_CELL} + rows/cols/all") print(f" presets {PRESET_OFF}-{PRESET_CHASE_ICE}") print(f" sequences {SEQ_CASCADE}-{SEQ_ROTATION}") if __name__ == "__main__": main()