Add pictures of all dynamic pages to the README

tools/make_page_screenshots.py asks the ESP32 to draw every page (with the
current Home Assistant values) and writes them as PNG in the style of the web
UI's live view to Pictures/pages/.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mangavalkandClaude Opus 5.5 committed 2026-10-15 22:29:12 +02:00
1 parent 53e9057d02
commit e991210ce6
16 files changed
+135

No files matched your search

+114
View File
@@ -0,0 +1,114 @@
"""Saves a picture of every dynamic page in Pictures/pages/, drawn by the ESP32 itself.
python tools/make_page_screenshots.py [ESP32 address, default 10.1.1.198]
The ESP32 draws each page (/api/pages/preview) with the current date and Home Assistant
values; pages whose background changes with the time of day get a picture per time slot.
The pictures look like the live view in the web UI: yellow and dark dots. Only the Python
standard library is used (the PNG files are written by hand).
"""
import json
import os
import re
import struct
import sys
import urllib.request
import zlib
ESP = 'http://' + (sys.argv[1] if len(sys.argv) > 1 else '10.1.1.198')
OUT = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'Pictures', 'pages')
W, H = 84, 19
CELL = 10 # pixels per dot
BORDER = 10
DOT = 0.42 # dot radius, part of a cell
ON, OFF, BG = (0xFF, 0xD2, 0x1F), (0x2C, 0x2F, 0x36), (0, 0, 0)
TIME = '12:34' # the time pages without time slots are drawn for
def call(path, body=None):
data = None if body is None else json.dumps(body).encode()
req = urllib.request.Request(ESP + path, data=data, headers={'Content-Type': 'application/json'})
with urllib.request.urlopen(req, timeout=20) as r:
return json.loads(r.read())
def dots(hexdata):
return [[int(hexdata[(y * 11 + x // 8) * 2:(y * 11 + x // 8) * 2 + 2], 16) >> (7 - x % 8) & 1 for x in range(W)]
for y in range(H)]
def png(path, image):
width, height = W * CELL + 2 * BORDER, H * CELL + 2 * BORDER
# One dot, 4x4 supersampled for smooth edges: coverage per pixel of a cell
sub = 4
cover = []
for py in range(CELL):
row = []
for px in range(CELL):
inside = 0
for sy in range(sub):
for sx in range(sub):
dx = px + (sx + 0.5) / sub - CELL / 2
dy = py + (sy + 0.5) / sub - CELL / 2
inside += dx * dx + dy * dy <= (DOT * CELL) ** 2
row.append(inside / (sub * sub))
cover.append(row)
def mix(color, a):
return bytes(round(BG[i] + (color[i] - BG[i]) * a) for i in range(3))
rows = []
blank = mix(BG, 0) * width
for y in range(height):
gy = y - BORDER
if gy < 0 or gy >= H * CELL:
rows.append(b'\0' + blank)
continue
line = bytearray(mix(BG, 0) * BORDER)
for gx in range(W * CELL):
line += mix(ON if image[gy // CELL][gx // CELL] else OFF, cover[gy % CELL][gx % CELL])
line += mix(BG, 0) * BORDER
rows.append(b'\0' + bytes(line))
def chunk(kind, data):
return struct.pack('>I', len(data)) + kind + data + struct.pack('>I', zlib.crc32(kind + data) & 0xFFFFFFFF)
with open(path, 'wb') as f:
f.write(b'\x89PNG\r\n\x1a\n')
f.write(chunk(b'IHDR', struct.pack('>IIBBBBB', width, height, 8, 2, 0, 0, 0)))
f.write(chunk(b'IDAT', zlib.compress(b''.join(rows), 9)))
f.write(chunk(b'IEND', b''))
def file_name(name, time=None):
base = re.sub(r'[^a-z0-9]+', '-', name.lower()).strip('-')
return base + ('-' + time.replace(':', '') if time else '') + '.png'
def main():
os.makedirs(OUT, exist_ok=True)
for p in call('/api/pages')['pages']:
page = p['page']
# The entities the page shows: read them now, so the picture has real values
for e in page['elements']:
if e.get('entity'):
query = urllib.parse.urlencode({'entity': e['entity'], 'attribute': e.get('attribute', '')})
try:
call('/api/ha/value?' + query)
except Exception:
pass
starts = [s['start'] for s in page['slots']]
times = [None] if len(starts) < 2 else starts
for t in times:
# A slot from 00:00 is drawn at TIME; other slots a little after they start
when = TIME if t is None else (TIME if t == '00:00' else '%s:%02d' % (t[:2], int(t[3:]) + 30))
r = call('/api/pages/preview', {'page': page, 'time': when})
name = file_name(p['name'], None if t is None else t)
png(os.path.join(OUT, name), dots(r['data']))
print(name)
if __name__ == '__main__':
import urllib.parse
main()