"""Regenerate catalog.json from GxEPD2's example headers. Re-run whenever the vendored GxEPD2 version is bumped. """ import json import pathlib import re HERE = pathlib.Path(__file__).parent SOURCES = [ HERE / "GxEPD2_display_selection.h", HERE / "GxEPD2_display_selection_added.h", ] VENDOR_SRC = HERE.parent.parent / "third_party" / "GxEPD2" / "src" OUT = HERE / "catalog.json" def find_source(driver: str) -> str: """Path (relative to vendor/src) of the .cpp implementing `driver`.""" matches = list(VENDOR_SRC.glob(f"**/{driver}.cpp")) if len(matches) != 1: raise ValueError(f"expected exactly one {driver}.cpp under {VENDOR_SRC}, found {matches}") return str(matches[0].relative_to(VENDOR_SRC)) LINE_RE = re.compile( r"^//?\s*" r"(?PGxEPD2_(?:BW|BW_SHM|3C|4C|7C))\s*<\s*" r"(?PGxEPD2_[A-Za-z0-9_]+)\s*,\s*(?P=driver)\s*::\s*HEIGHT" r"(?:\s*/\s*(?P\d+))?\s*>\s*" r"display\s*\(\s*(?P=driver)\s*\((?P[^)]*)\)\s*\)\s*;" r"\s*(?://\s*(?P.*))?$" ) def split_args(args: str): return [a.strip() for a in args.split(",") if a.strip()] def main(): # (driver, wrapper) -> best entry seen. by_driver_wrapper = {} skipped_nonstandard = [] for src in SOURCES: text = src.read_text() for line in text.splitlines(): m = LINE_RE.match(line.strip()) if not m: continue driver = m.group("driver") wrapper = m.group("wrapper") args = split_args(m.group("args")) comment = (m.group("comment") or "").strip() divisor = int(m.group("divisor") or 1) # 4 args: standard single-CS. 5: dual-CS "master+slave". Else skip. if len(args) not in (4, 5): skipped_nonstandard.append((driver, len(args), comment)) continue key = (driver, wrapper) entry = { "driver": driver, "wrapper": wrapper, "comment": comment, "height_divisor": divisor, "ctor_args": len(args), } existing = by_driver_wrapper.get(key) if existing: # Keep the smallest height_divisor and the longest comment seen. entry["height_divisor"] = min(existing["height_divisor"], divisor) if len(comment) <= len(existing["comment"]): entry["comment"] = existing["comment"] by_driver_wrapper[key] = entry # Model id = driver class name, disambiguated with a wrapper suffix # when a driver has more than one wrapper. drivers_seen = {} for (driver, _wrapper) in by_driver_wrapper: drivers_seen.setdefault(driver, []).append(_wrapper) catalog = {} for (driver, wrapper), entry in by_driver_wrapper.items(): wrapper_suffix = wrapper.removeprefix("GxEPD2_") model_id = driver if len(drivers_seen[driver]) == 1 else f"{driver}_{wrapper_suffix}" entry["source"] = find_source(driver) catalog[model_id] = entry OUT.write_text(json.dumps(catalog, indent=2, sort_keys=True) + "\n") dual_cs = sum(1 for e in catalog.values() if e["ctor_args"] == 5) print(f"wrote {len(catalog)} panel models ({dual_cs} dual-CS) to {OUT}") print(f"skipped {len(skipped_nonstandard)} non-standard-constructor drivers (need special-casing later):") for driver, nargs, comment in skipped_nonstandard: print(f" {driver} ({nargs} ctor args) // {comment}") if __name__ == "__main__": main()