Add four Steins;Gate pages; shuffle the page rotation hourly

Divergence meter (Nixie tubes, world lines and clock), a D-mail sent
through the Phone Microwave using the panel's Message text, the Future
Gadget Lab inventory with live sensors, and a world line chart of today's
solar power. Original pixel art in tools/steins_sprites.py; the sprite
tool is shared between themes and Sprite, sprite(), polar() and arc()
move to el512_pages.h. HomeData gains the message and a 24 h solar
history in 15-minute slots.

The rotation now walks all pages in a random order that is reshuffled
every hour (Still Alive stays out, Now playing is skipped when idle).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mangavalkandClaude Opus 5.5 committed 2026-10-18 23:58:34 +02:00
1 parent e9b91c5628
commit 1e8a9a5958
11 files changed
+1057 -76

No files matched your search

+17 -21
View File
@@ -285,44 +285,39 @@ SPRITES["MEGUMIN_BLINK"] = list(SPRITES["MEGUMIN"])
SPRITES["MEGUMIN_BLINK"][17] = SPRITES["MEGUMIN"][17][:8] + ".." + SPRITES["MEGUMIN"][17][10:]
def write_header(path):
def write_header(path, sprites=SPRITES, title="KonoSuba", tool="konosuba_sprites.py", namespace="konosuba"):
out = [
"// Pixel art for the KonoSuba pages, generated by tools/konosuba_sprites.py;",
f"// Pixel art for the {title} pages, generated by tools/{tool};",
"// edit the art there. Original 1-bit fan art, '#' = lit pixel.",
"#pragma once",
"",
"#include <stdint.h>",
'#include "el512_pages.h" // Sprite',
"",
"namespace esphome::el512::konosuba {",
"",
"struct Sprite {",
" uint8_t w, h;",
" const char *const *rows;",
"};",
f"namespace esphome::el512::{namespace} {{",
"",
]
for name, rows in SPRITES.items():
for name, rows in sprites.items():
out.append(f"static const char *const {name}_ROWS[] = {{")
out += [f' "{r}",' for r in rows]
out.append("};")
out.append(f"static constexpr Sprite {name}{{{len(rows[0])}, {len(rows)}, {name}_ROWS}};")
out.append("")
out.append("} // namespace esphome::el512::konosuba")
out.append(f"}} // namespace esphome::el512::{namespace}")
path.write_text("\n".join(out) + "\n", encoding="utf-8")
def write_preview(path, scale=6):
def write_preview(path, sprites=SPRITES, scale=6):
from PIL import Image, ImageDraw
pad = 4
names = list(SPRITES)
width = sum(len(SPRITES[n][0]) + pad for n in names) * scale
height = (max(len(r) for r in SPRITES.values()) + pad) * scale + 14
names = list(sprites)
width = sum(len(sprites[n][0]) + pad for n in names) * scale
height = (max(len(r) for r in sprites.values()) + pad) * scale + 14
img = Image.new("RGB", (width, height), (20, 12, 0))
draw = ImageDraw.Draw(img)
x = 0
for name in names:
rows = SPRITES[name]
rows = sprites[name]
draw.text((x + 2, 2), name, fill=(120, 80, 0))
for y, row in enumerate(rows):
for cx, ch in enumerate(row):
@@ -333,16 +328,17 @@ def write_preview(path, scale=6):
img.save(path)
def main():
def main(sprites=SPRITES, title="KonoSuba", tool="konosuba_sprites.py", namespace="konosuba", doc=__doc__):
"""Command line for a sprite file: writes the header, optionally a preview."""
root = Path(__file__).resolve().parent.parent
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("--out", type=Path, default=root / "components/el512/konosuba_sprites.h")
ap = argparse.ArgumentParser(description=doc, formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("--out", type=Path, default=root / f"components/el512/{namespace}_sprites.h")
ap.add_argument("--preview", type=Path, help="also write a magnified PNG of all sprites")
args = ap.parse_args()
write_header(args.out)
write_header(args.out, sprites, title, tool, namespace)
print(f"wrote {args.out}")
if args.preview:
write_preview(args.preview)
write_preview(args.preview, sprites)
print(f"wrote {args.preview}")
+167
View File
@@ -0,0 +1,167 @@
#!/usr/bin/env python3
"""Pixel art for the Steins;Gate pages (components/el512/steins.cpp).
Original 1-bit fan art: '#' is a lit pixel. Writes
components/el512/steins_sprites.h and, with --preview, a magnified PNG.
python tools/steins_sprites.py
python tools/steins_sprites.py --preview sprites.png
"""
from konosuba_sprites import main, padded
SPRITES = {
# Okabe in his lab coat, and with the coat flaring out.
"OKABE_A": [
"......#######.........",
"....##########.#......",
"...############.......",
"..#############.#.....",
"..##.#.#.##.####......",
"..#.........###.......",
"..#..##..##..#........",
"..#...........#.......",
"..#.....#.....#.......",
"...#..#####..#........",
"....#.......#.........",
".....#######..........",
"...###.....###........",
"..##..#...#..##.......",
".##....#.#....##......",
".#.#....#....#.#......",
".#.#....#....#.#......",
".#..#...#...#..#......",
"#...#...#...#...#.....",
"#...#...#...#...#.....",
"#..#....#....#..#.....",
"#..#....#....#..#.....",
"##.#....#....#.##.....",
"##.#....#....#.##.....",
"...#....#....#........",
"...#...###...#........",
"...#...#.#...#........",
"..#....#.#....#.......",
"..#....#.#....#.......",
".#.....#.#.....#......",
".#.....#.#.....#......",
"#......#.#......#.....",
"########.########.....",
".......#.#............",
".......#.#............",
"......##.##...........",
],
"OKABE_B": [
"......#######.........",
"....##########.#......",
"...############.......",
"..#############.#.....",
"..##.#.#.##.####......",
"..#.........###.......",
"..#..##..##..#........",
"..#...........#.......",
"..#.....#.....#.......",
"...#..#####..#........",
"....#.......#.........",
".....#######..........",
"...###.....###........",
"..##..#...#..##.......",
".##....#.#....##......",
".#.#....#....#.##.....",
".#.#....#....#.#.#....",
".#..#...#...#..#..#...",
"#...#...#...#...#..#..",
"#...#...#...#...#...#.",
"#..#....#....#..#....#",
"#..#....#....#..#....#",
"##.#....#....#.##....#",
"##.#....#....#......#.",
"...#....#....#......#.",
"...#...###...#.....#..",
"...#...#.#...#.....#..",
"..#....#.#....#...#...",
"..#....#.#....#...#...",
".#.....#.#.....#.#....",
".#.....#.#.....##.....",
"#......#.#......#.....",
"########.########.....",
".......#.#............",
".......#.#............",
"......##.##...........",
],
# Kurisu: long hair, shirt and tie, belt.
"KURISU": [
".....######.......",
"...##########.....",
"..############....",
"..##.#.##.#.##....",
"..#..........#....",
"..#..##..##..#....",
"..#..........#....",
"..#....#.....#....",
"..##..###...##....",
"..###......###....",
"..#.##....##.#....",
"..#..######..#....",
"..#.#..#..#..#....",
"..##..###..#.#....",
"..#....#....##....",
".##...###...###...",
".#.#...#...#..#...",
".#.#.......#..#...",
".#..#.....#...#...",
".#..#######...#...",
"....#.....#.......",
"....#.....#.......",
"....##...##.......",
".....#...#........",
".....#...#........",
".....#...#........",
".....#...#........",
"....##...##.......",
],
# Upa, upright and squashed mid-bounce.
"UPA_A": [
"..#........#..",
"..##......##..",
"..#.######.#..",
".#..........#.",
"#...#....#...#",
"#............#",
"#.....##.....#",
".#..........#.",
"..##########..",
],
"UPA_B": [
"..............",
"...#......#...",
"..##.####.##..",
".#..........#.",
"#...#....#...#",
"#.....##.....#",
"##..........##",
".############.",
"..............",
],
# The lab's favourite fizzy drink.
"CAN": [
"..######..",
".#......#.",
"##########",
"#........#",
"#.######.#",
"#.#....#.#",
"#.#.##.#.#",
"#.#....#.#",
"#.######.#",
"#........#",
"#........#",
"##########",
".########.",
],
}
for _name in SPRITES:
SPRITES[_name] = padded(SPRITES[_name])
if __name__ == "__main__":
main(SPRITES, "Steins;Gate", "steins_sprites.py", "steins", __doc__)