codegen.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. """Merged model dict -> C++ EngineModel codegen.
  2. Plain `(dict) -> str`, no ESPHome/`cg`/`CORE` dependency (see
  3. claude/DESCRIPTOR_REGISTRY_PLAN.md) - callable from display.py's to_code()
  4. at ESPHome compile time, and from standalone host tooling
  5. (check_descriptors.py and friends) outside any ESPHome build.
  6. """
  7. from __future__ import annotations
  8. from typing import Any
  9. _LINE_ORDERS = {"SEQUENTIAL": "LineOrder::SEQUENTIAL", "PAIRED_EDGES": "LineOrder::PAIRED_EDGES"}
  10. def _hex_bytes(s: str) -> list[int]:
  11. s = s or ""
  12. if len(s) % 2 != 0:
  13. raise ValueError(f"odd-length hex byte string: {s!r}")
  14. return [int(s[i : i + 2], 16) for i in range(0, len(s), 2)]
  15. def _byte_array_literal(data: list[int]) -> str:
  16. return "{" + ", ".join(f"0x{b:02X}" for b in data) + "}"
  17. def _emit_sequence(out: list[str], prefix: str, seq: list[dict]) -> tuple[str, int]:
  18. """Emits one EpdCommand table (and its per-entry data byte arrays) as
  19. `prefix`-named globals; returns (array name, length)."""
  20. entries = []
  21. for i, c in enumerate(seq):
  22. data = _hex_bytes(c.get("data", ""))
  23. data_name = "nullptr"
  24. if data:
  25. data_name = f"{prefix}_{i}_DATA"
  26. out.append(f"inline const uint8_t {data_name}[] = {_byte_array_literal(data)};")
  27. cmd = _hex_bytes(c["cmd"])[0]
  28. entries.append(
  29. " {{0x{cmd:02X}, {data_name}, {data_len}, {before}, {after}, {wait}}},".format(
  30. cmd=cmd,
  31. data_name=data_name,
  32. data_len=len(data),
  33. before=c.get("delay_before_ms", 0),
  34. after=c.get("delay_after_ms", 0),
  35. wait="true" if c.get("wait_busy_after", False) else "false",
  36. )
  37. )
  38. seq_name = f"{prefix}_SEQ"
  39. if entries:
  40. out.append(f"inline const EpdCommand {seq_name}[] = {{")
  41. out.extend(entries)
  42. out.append("};")
  43. else:
  44. out.append(f"inline const EpdCommand *{seq_name} = nullptr;")
  45. return seq_name, len(entries)
  46. def generate_cpp(model: dict[str, Any], var_name: str) -> str:
  47. """Generates one `inline const EngineModel {var_name} = {...};`
  48. plus its backing command/palette arrays, as a single translation unit's
  49. worth of text (no #pragma once / includes - the caller wraps this)."""
  50. out: list[str] = []
  51. prefix = var_name.upper()
  52. init_name, init_len = _emit_sequence(out, f"{prefix}_INIT", model["init_sequence"])
  53. refresh_name, refresh_len = _emit_sequence(out, f"{prefix}_REFRESH", model["refresh_sequence"])
  54. power_off_name, power_off_len = _emit_sequence(out, f"{prefix}_POWEROFF", model.get("power_off_sequence") or [])
  55. hibernate_name, hibernate_len = _emit_sequence(out, f"{prefix}_HIBERNATE", model.get("hibernate_sequence") or [])
  56. planes = model["planes"]
  57. if len(planes) > 2:
  58. raise ValueError(f"{var_name}: {len(planes)} planes given, engine supports at most 2")
  59. window_names: list[str] = ["nullptr", "nullptr"]
  60. window_lens: list[int] = [0, 0]
  61. plane_write_cmd: list[int] = [0, 0]
  62. plane_write_cmd_full: list[int] = [0, 0]
  63. plane_invert: list[bool] = [False, False]
  64. window_per_plane = False
  65. plane_width = 0
  66. base_window = planes[0].get("window_sequence")
  67. for i, p in enumerate(planes):
  68. window_seq = p.get("window_sequence") or (base_window if i == 0 else None)
  69. if window_seq is not None:
  70. if i > 0 and p.get("window_sequence") is not None:
  71. window_per_plane = True
  72. window_names[i], window_lens[i] = _emit_sequence(out, f"{prefix}_WINDOW{i}", window_seq)
  73. plane_write_cmd[i] = _hex_bytes(p["plane_write_cmd"])[0]
  74. plane_write_cmd_full[i] = _hex_bytes(p.get("plane_write_cmd_for_full_refresh", "00"))[0]
  75. plane_invert[i] = bool(p.get("plane_invert", False))
  76. if p.get("width") is not None:
  77. plane_width = p["width"]
  78. palette_name = "nullptr"
  79. palette_len = 0
  80. palette_entries = model.get("palette") or []
  81. if palette_entries:
  82. palette_name = f"{prefix}_PALETTE"
  83. out.append(f"inline const EpdPaletteEntry {palette_name}[] = {{")
  84. for e in palette_entries:
  85. r, g, b = _hex_bytes(e["rgb"])
  86. code = _hex_bytes(e["code"])[0]
  87. out.append(f" {{0x{r:02X}, 0x{g:02X}, 0x{b:02X}, 0x{code:02X}}},")
  88. out.append("};")
  89. palette_len = len(palette_entries)
  90. out.append(f"inline const EngineModel {var_name} = {{")
  91. out.append(f" .width = {model['width']},")
  92. out.append(f" .height = {model['height']},")
  93. out.append(f" .busy_active_level = {'true' if model['busy_active_level'] else 'false'},")
  94. out.append(f" .busy_timeout_ms = {model['busy_timeout_ms']},")
  95. out.append(f" .init_sequence = {init_name},")
  96. out.append(f" .init_sequence_len = {init_len},")
  97. out.append(f" .window_sequence = {{{window_names[0]}, {window_names[1]}}},")
  98. out.append(f" .window_sequence_len = {{{window_lens[0]}, {window_lens[1]}}},")
  99. out.append(f" .window_per_plane = {'true' if window_per_plane else 'false'},")
  100. out.append(f" .plane_count = {len(planes)},")
  101. out.append(f" .plane_write_cmd = {{0x{plane_write_cmd[0]:02X}, 0x{plane_write_cmd[1]:02X}}},")
  102. out.append(
  103. " .plane_write_cmd_for_full_refresh = "
  104. f"{{0x{plane_write_cmd_full[0]:02X}, 0x{plane_write_cmd_full[1]:02X}}},"
  105. )
  106. out.append(f" .plane_invert = {{{'true' if plane_invert[0] else 'false'}, {'true' if plane_invert[1] else 'false'}}},")
  107. out.append(f" .plane_width = {plane_width},")
  108. out.append(f" .line_order = {_LINE_ORDERS[model.get('line_order', 'SEQUENTIAL')]},")
  109. out.append(f" .bits_per_pixel = {model.get('bits_per_pixel', 1)},")
  110. out.append(f" .palette = {palette_name},")
  111. out.append(f" .palette_len = {palette_len},")
  112. out.append(f" .blank_code = 0x{model.get('blank_code', 0):02X},")
  113. out.append(f" .refresh_sequence = {refresh_name},")
  114. out.append(f" .refresh_sequence_len = {refresh_len},")
  115. out.append(f" .power_off_sequence = {power_off_name},")
  116. out.append(f" .power_off_sequence_len = {power_off_len},")
  117. out.append(f" .hibernate_sequence = {hibernate_name},")
  118. out.append(f" .hibernate_sequence_len = {hibernate_len},")
  119. out.append("};")
  120. return "\n".join(out) + "\n"