generate_catalog.py 3.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. """Regenerate catalog.json from GxEPD2's example headers. Re-run whenever
  2. the vendored GxEPD2 version is bumped.
  3. """
  4. import json
  5. import pathlib
  6. import re
  7. HERE = pathlib.Path(__file__).parent
  8. SOURCES = [
  9. HERE / "GxEPD2_display_selection.h",
  10. HERE / "GxEPD2_display_selection_added.h",
  11. ]
  12. VENDOR_SRC = HERE.parent.parent / "third_party" / "GxEPD2" / "src"
  13. OUT = HERE / "catalog.json"
  14. def find_source(driver: str) -> str:
  15. """Path (relative to vendor/src) of the .cpp implementing `driver`."""
  16. matches = list(VENDOR_SRC.glob(f"**/{driver}.cpp"))
  17. if len(matches) != 1:
  18. raise ValueError(f"expected exactly one {driver}.cpp under {VENDOR_SRC}, found {matches}")
  19. return str(matches[0].relative_to(VENDOR_SRC))
  20. LINE_RE = re.compile(
  21. r"^//?\s*"
  22. r"(?P<wrapper>GxEPD2_(?:BW|BW_SHM|3C|4C|7C))\s*<\s*"
  23. r"(?P<driver>GxEPD2_[A-Za-z0-9_]+)\s*,\s*(?P=driver)\s*::\s*HEIGHT"
  24. r"(?:\s*/\s*(?P<divisor>\d+))?\s*>\s*"
  25. r"display\s*\(\s*(?P=driver)\s*\((?P<args>[^)]*)\)\s*\)\s*;"
  26. r"\s*(?://\s*(?P<comment>.*))?$"
  27. )
  28. def split_args(args: str):
  29. return [a.strip() for a in args.split(",") if a.strip()]
  30. def main():
  31. # (driver, wrapper) -> best entry seen.
  32. by_driver_wrapper = {}
  33. skipped_nonstandard = []
  34. for src in SOURCES:
  35. text = src.read_text()
  36. for line in text.splitlines():
  37. m = LINE_RE.match(line.strip())
  38. if not m:
  39. continue
  40. driver = m.group("driver")
  41. wrapper = m.group("wrapper")
  42. args = split_args(m.group("args"))
  43. comment = (m.group("comment") or "").strip()
  44. divisor = int(m.group("divisor") or 1)
  45. # 4 args: standard single-CS. 5: dual-CS "master+slave". Else skip.
  46. if len(args) not in (4, 5):
  47. skipped_nonstandard.append((driver, len(args), comment))
  48. continue
  49. key = (driver, wrapper)
  50. entry = {
  51. "driver": driver,
  52. "wrapper": wrapper,
  53. "comment": comment,
  54. "height_divisor": divisor,
  55. "ctor_args": len(args),
  56. }
  57. existing = by_driver_wrapper.get(key)
  58. if existing:
  59. # Keep the smallest height_divisor and the longest comment seen.
  60. entry["height_divisor"] = min(existing["height_divisor"], divisor)
  61. if len(comment) <= len(existing["comment"]):
  62. entry["comment"] = existing["comment"]
  63. by_driver_wrapper[key] = entry
  64. # Model id = driver class name, disambiguated with a wrapper suffix
  65. # when a driver has more than one wrapper.
  66. drivers_seen = {}
  67. for (driver, _wrapper) in by_driver_wrapper:
  68. drivers_seen.setdefault(driver, []).append(_wrapper)
  69. catalog = {}
  70. for (driver, wrapper), entry in by_driver_wrapper.items():
  71. wrapper_suffix = wrapper.removeprefix("GxEPD2_")
  72. model_id = driver if len(drivers_seen[driver]) == 1 else f"{driver}_{wrapper_suffix}"
  73. entry["source"] = find_source(driver)
  74. catalog[model_id] = entry
  75. OUT.write_text(json.dumps(catalog, indent=2, sort_keys=True) + "\n")
  76. dual_cs = sum(1 for e in catalog.values() if e["ctor_args"] == 5)
  77. print(f"wrote {len(catalog)} panel models ({dual_cs} dual-CS) to {OUT}")
  78. print(f"skipped {len(skipped_nonstandard)} non-standard-constructor drivers (need special-casing later):")
  79. for driver, nargs, comment in skipped_nonstandard:
  80. print(f" {driver} ({nargs} ctor args) // {comment}")
  81. if __name__ == "__main__":
  82. main()