"""Merged model dict -> C++ EngineModel codegen. Plain `(dict) -> str`, no ESPHome/`cg`/`CORE` dependency (see claude/DESCRIPTOR_REGISTRY_PLAN.md) - callable from display.py's to_code() at ESPHome compile time, and from standalone host tooling (check_descriptors.py and friends) outside any ESPHome build. """ from __future__ import annotations from typing import Any _LINE_ORDERS = {"SEQUENTIAL": "LineOrder::SEQUENTIAL", "PAIRED_EDGES": "LineOrder::PAIRED_EDGES"} def _hex_bytes(s: str) -> list[int]: s = s or "" if len(s) % 2 != 0: raise ValueError(f"odd-length hex byte string: {s!r}") return [int(s[i : i + 2], 16) for i in range(0, len(s), 2)] def _byte_array_literal(data: list[int]) -> str: return "{" + ", ".join(f"0x{b:02X}" for b in data) + "}" def _emit_sequence(out: list[str], prefix: str, seq: list[dict]) -> tuple[str, int]: """Emits one EpdCommand table (and its per-entry data byte arrays) as `prefix`-named globals; returns (array name, length).""" entries = [] for i, c in enumerate(seq): data = _hex_bytes(c.get("data", "")) data_name = "nullptr" if data: data_name = f"{prefix}_{i}_DATA" out.append(f"inline const uint8_t {data_name}[] = {_byte_array_literal(data)};") cmd = _hex_bytes(c["cmd"])[0] entries.append( " {{0x{cmd:02X}, {data_name}, {data_len}, {before}, {after}, {wait}}},".format( cmd=cmd, data_name=data_name, data_len=len(data), before=c.get("delay_before_ms", 0), after=c.get("delay_after_ms", 0), wait="true" if c.get("wait_busy_after", False) else "false", ) ) seq_name = f"{prefix}_SEQ" if entries: out.append(f"inline const EpdCommand {seq_name}[] = {{") out.extend(entries) out.append("};") else: out.append(f"inline const EpdCommand *{seq_name} = nullptr;") return seq_name, len(entries) def generate_cpp(model: dict[str, Any], var_name: str) -> str: """Generates one `inline const EngineModel {var_name} = {...};` plus its backing command/palette arrays, as a single translation unit's worth of text (no #pragma once / includes - the caller wraps this).""" out: list[str] = [] prefix = var_name.upper() init_name, init_len = _emit_sequence(out, f"{prefix}_INIT", model["init_sequence"]) refresh_name, refresh_len = _emit_sequence(out, f"{prefix}_REFRESH", model["refresh_sequence"]) power_off_name, power_off_len = _emit_sequence(out, f"{prefix}_POWEROFF", model.get("power_off_sequence") or []) hibernate_name, hibernate_len = _emit_sequence(out, f"{prefix}_HIBERNATE", model.get("hibernate_sequence") or []) planes = model["planes"] if len(planes) > 2: raise ValueError(f"{var_name}: {len(planes)} planes given, engine supports at most 2") window_names: list[str] = ["nullptr", "nullptr"] window_lens: list[int] = [0, 0] plane_write_cmd: list[int] = [0, 0] plane_write_cmd_full: list[int] = [0, 0] plane_invert: list[bool] = [False, False] window_per_plane = False plane_width = 0 base_window = planes[0].get("window_sequence") for i, p in enumerate(planes): window_seq = p.get("window_sequence") or (base_window if i == 0 else None) if window_seq is not None: if i > 0 and p.get("window_sequence") is not None: window_per_plane = True window_names[i], window_lens[i] = _emit_sequence(out, f"{prefix}_WINDOW{i}", window_seq) plane_write_cmd[i] = _hex_bytes(p["plane_write_cmd"])[0] plane_write_cmd_full[i] = _hex_bytes(p.get("plane_write_cmd_for_full_refresh", "00"))[0] plane_invert[i] = bool(p.get("plane_invert", False)) if p.get("width") is not None: plane_width = p["width"] palette_name = "nullptr" palette_len = 0 palette_entries = model.get("palette") or [] if palette_entries: palette_name = f"{prefix}_PALETTE" out.append(f"inline const EpdPaletteEntry {palette_name}[] = {{") for e in palette_entries: r, g, b = _hex_bytes(e["rgb"]) code = _hex_bytes(e["code"])[0] out.append(f" {{0x{r:02X}, 0x{g:02X}, 0x{b:02X}, 0x{code:02X}}},") out.append("};") palette_len = len(palette_entries) out.append(f"inline const EngineModel {var_name} = {{") out.append(f" .width = {model['width']},") out.append(f" .height = {model['height']},") out.append(f" .busy_active_level = {'true' if model['busy_active_level'] else 'false'},") out.append(f" .busy_timeout_ms = {model['busy_timeout_ms']},") out.append(f" .init_sequence = {init_name},") out.append(f" .init_sequence_len = {init_len},") out.append(f" .window_sequence = {{{window_names[0]}, {window_names[1]}}},") out.append(f" .window_sequence_len = {{{window_lens[0]}, {window_lens[1]}}},") out.append(f" .window_per_plane = {'true' if window_per_plane else 'false'},") out.append(f" .plane_count = {len(planes)},") out.append(f" .plane_write_cmd = {{0x{plane_write_cmd[0]:02X}, 0x{plane_write_cmd[1]:02X}}},") out.append( " .plane_write_cmd_for_full_refresh = " f"{{0x{plane_write_cmd_full[0]:02X}, 0x{plane_write_cmd_full[1]:02X}}}," ) out.append(f" .plane_invert = {{{'true' if plane_invert[0] else 'false'}, {'true' if plane_invert[1] else 'false'}}},") out.append(f" .plane_width = {plane_width},") out.append(f" .line_order = {_LINE_ORDERS[model.get('line_order', 'SEQUENTIAL')]},") out.append(f" .bits_per_pixel = {model.get('bits_per_pixel', 1)},") out.append(f" .palette = {palette_name},") out.append(f" .palette_len = {palette_len},") out.append(f" .blank_code = 0x{model.get('blank_code', 0):02X},") out.append(f" .refresh_sequence = {refresh_name},") out.append(f" .refresh_sequence_len = {refresh_len},") out.append(f" .power_off_sequence = {power_off_name},") out.append(f" .power_off_sequence_len = {power_off_len},") out.append(f" .hibernate_sequence = {hibernate_name},") out.append(f" .hibernate_sequence_len = {hibernate_len},") out.append("};") return "\n".join(out) + "\n"