| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136 |
- """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"
|