|
|
@@ -0,0 +1,136 @@
|
|
|
+"""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": "zepd::LineOrder::SEQUENTIAL", "PAIRED_EDGES": "zepd::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 zepd::EpdCommand {seq_name}[] = {{")
|
|
|
+ out.extend(entries)
|
|
|
+ out.append("};")
|
|
|
+ else:
|
|
|
+ out.append(f"inline const zepd::EpdCommand *{seq_name} = nullptr;")
|
|
|
+ return seq_name, len(entries)
|
|
|
+
|
|
|
+
|
|
|
+def generate_cpp(model: dict[str, Any], var_name: str) -> str:
|
|
|
+ """Generates one `inline const zepd::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 zepd::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 zepd::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"
|