Add ESPHome display component; ring-buffer driver shared with PlatformIO

Driver (components/el512/el512_panel.*, ESP-IDF only):
- DMA ring of 8 x 4 lines (17 KB) refilled from the I2S EOF interrupt
  (IRAM) instead of a 142 KB full-frame stream; ~107 KB heap freed.
- 256-entry lookup table encodes 8 pixels per step.
- Back buffer + displayed buffer, swapped at the start of a frame.
- PlatformIO builds it via lib_extra_dirs (EL512Core); lib/EL512 keeps
  the Adafruit GFX front end.

ESPHome external component (platform: el512):
- DisplayBuffer with optional ENABLE / _LOWPWR pins, set_enabled(),
  set_low_power(), helper functions for page lambdas.
- still_alive_page: plays the Portal end-credits show, then returns to
  the previous page.

Still Alive engine moved to components/el512/still_alive.* with a built-in
5x7 font (from Adafruit GFX, BSD); the PlatformIO demo uses the same code.
The importer writes components/el512/still_alive_data.h and stops at 200 s
(--until-ms), matching the hand-trimmed data.

esphome/el512-panel.yaml: 8 pages (clock, forecast, agenda, dashboard,
now playing, Plex, solar, Still Alive), page select, rotation, display
and low-power switches, message text. home_assistant/packages/el512_panel.yaml:
helper template sensors for forecast, agenda, now playing and Plex streams.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mangavalkandClaude Opus 5.5 committed 2026-10-18 20:20:13 +02:00
1 parent 410885de24
commit 963590cdfe
27 files changed
+2110 -828

No files matched your search

+16 -7
View File
@@ -3,14 +3,15 @@
Reads index.html and res/stillalive.js from
https://github.com/SDSkyKlouD/still-alive-web (downloaded, or local copies) and
writes src/still_alive/imported.h, which the firmware uses instead of the
placeholder lyrics.h / art.h / credits.h when it exists.
writes components/el512/still_alive_data.h, the content of the Still Alive show
(components/el512/still_alive.cpp) used by both the PlatformIO demo and ESPHome.
python tools/import_still_alive.py # download from GitHub
python tools/import_still_alive.py --html index.html --js stillalive.js
python tools/import_still_alive.py --out somewhere/imported.h
python tools/import_still_alive.py --out somewhere/still_alive_data.h
python tools/import_still_alive.py --until-ms 0 # keep the web version's outro too
imported.h is committed with a disclaimer and source reference at its top.
still_alive_data.h is committed with a disclaimer and source reference at its top.
Source format: each <div class="container_lyrics" data-start=ms> is one screen
(the console clears when it starts). Inside it, <span data-start data-dur
@@ -28,7 +29,11 @@ import urllib.request
from pathlib import Path
SOURCE_BASE = "https://raw.githubusercontent.com/SDSkyKlouD/still-alive-web/master/"
OUT = Path(__file__).resolve().parent.parent / "src" / "still_alive" / "imported.h"
OUT = Path(__file__).resolve().parent.parent / "components" / "el512" / "still_alive_data.h"
# The show ends after the "... are property of their respective owners." line;
# the web version's outro after it ("... is anyone there?") is left out.
UNTIL_MS = 200000
# Timing from stillalive.js.
SONG_START_MS = 6750
@@ -146,12 +151,16 @@ def main():
ap.add_argument("--html", help="local index.html (default: download)")
ap.add_argument("--js", help="local res/stillalive.js (default: download)")
ap.add_argument("--out", type=Path, default=OUT, help="output file (default: %(default)s)")
ap.add_argument("--until-ms", type=int, default=UNTIL_MS,
help="drop steps starting at or after this time, 0 = keep all (default: %(default)s)")
args = ap.parse_args()
page = load(args.html, "index.html")
arts = parse_art(page)
art_ids = {name: i + 1 for i, name in enumerate(arts)}
steps = parse_steps(page, art_ids)
if args.until_ms:
steps = [s for s in steps if s[0] < args.until_ms]
credits = parse_credits(load(args.js, "res/stillalive.js"))
if not steps or not arts or not credits:
sys.exit("error: no lyrics, art or credits found; has the source changed?")
@@ -168,7 +177,7 @@ def main():
"// owners. This project is not affiliated with or endorsed by Valve.",
"//",
"// Generated by tools/import_still_alive.py - do not edit by hand.",
"#pragma once", "", '#include "show_data.h"', ""]
"#pragma once", "", '#include "still_alive.h"', "", "namespace el512sa {", ""]
for name, lines in arts.items():
out.append(f"static const char* const ART_{name.upper()}_LINES[] = {{")
out += [f" {c_string(l)}," for l in lines]
@@ -187,7 +196,7 @@ def main():
out += [f"static constexpr uint32_t SONG_START_MS = {SONG_START_MS};",
f"static constexpr uint32_t CREDITS_START_MS = {CREDITS_START_MS};",
f"static constexpr uint32_t CREDIT_CHAR_US = {CREDIT_CHAR_US};",
"static constexpr bool CREDITS_LOOP = false;", ""]
"static constexpr bool CREDITS_LOOP = false;", "", "} // namespace el512sa", ""]
args.out.write_text("\n".join(out), encoding="utf-8", newline="\n")
screens = sum(1 for s in steps if s[3] == "STEP_CLEAR")