2026-10-15 22:42:46 +02:00
|
|
|
"""Saves screenshots of the web UI tabs in Pictures/web-ui/, with Microsoft Edge (or Chrome)
|
|
|
|
|
in headless mode.
|
|
|
|
|
|
|
|
|
|
python tools/make_webui_screenshots.py [ESP32 address, default 10.1.1.198]
|
|
|
|
|
|
|
|
|
|
Each tab is opened by its address (http://<ESP32>/#settings); the page editor with
|
|
|
|
|
#pages/<page name>. Edge runs with its own temporary profile. Only the Python standard
|
|
|
|
|
library is used.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import json
|
|
|
|
|
import os
|
|
|
|
|
import shutil
|
|
|
|
|
import subprocess
|
|
|
|
|
import sys
|
|
|
|
|
import tempfile
|
|
|
|
|
import urllib.parse
|
|
|
|
|
import urllib.request
|
|
|
|
|
|
|
|
|
|
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', 'web-ui')
|
|
|
|
|
BROWSERS = [
|
|
|
|
|
r'C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe',
|
|
|
|
|
r'C:\Program Files\Microsoft\Edge\Application\msedge.exe',
|
|
|
|
|
r'C:\Program Files\Google\Chrome\Application\chrome.exe',
|
|
|
|
|
'microsoft-edge', 'google-chrome', 'chromium',
|
|
|
|
|
]
|
|
|
|
|
WIDTH = 1000
|
|
|
|
|
# File name, address part, window height
|
|
|
|
|
SHOTS = [
|
|
|
|
|
('show', 'show', 2150),
|
|
|
|
|
('pages', 'pages/Weer', 3000), # the page list and the editor (tall: nothing has to scroll)
|
|
|
|
|
('schedule', 'schedule', 1150),
|
|
|
|
|
('stopwatch', 'stopwatch', 560),
|
|
|
|
|
('bitmaps', 'bitmaps', 1600),
|
2026-10-16 22:03:18 +02:00
|
|
|
('icons', 'icons', 1900),
|
2026-10-15 22:42:46 +02:00
|
|
|
('draw', 'draw', 820),
|
|
|
|
|
('settings', 'settings', 1550),
|
|
|
|
|
('home-assistant', 'ha', 780),
|
|
|
|
|
('system', 'system', 1080),
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def browser():
|
|
|
|
|
for b in BROWSERS:
|
|
|
|
|
path = shutil.which(b) or (b if os.path.exists(b) else None)
|
|
|
|
|
if path:
|
|
|
|
|
return path
|
|
|
|
|
sys.exit('No Edge or Chrome found')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def read_ha_values():
|
|
|
|
|
"""Reads the Home Assistant values of all pages, so the previews show them"""
|
|
|
|
|
with urllib.request.urlopen(ESP + '/api/pages', timeout=20) as r:
|
|
|
|
|
pages = json.loads(r.read())['pages']
|
|
|
|
|
for entity in {(e['entity'], e.get('attribute', '')) for p in pages for e in p['page']['elements'] if e.get('entity')}:
|
|
|
|
|
query = urllib.parse.urlencode({'entity': entity[0], 'attribute': entity[1]})
|
|
|
|
|
try:
|
|
|
|
|
urllib.request.urlopen(ESP + '/api/ha/value?' + query, timeout=10).read()
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def shoot(exe, profile, path, address, height):
|
|
|
|
|
for attempt in range(3):
|
|
|
|
|
try:
|
|
|
|
|
subprocess.run([exe, '--headless=new', '--disable-gpu', '--hide-scrollbars', '--user-data-dir=' + profile,
|
|
|
|
|
'--window-size=%d,%d' % (WIDTH, height), '--virtual-time-budget=20000',
|
|
|
|
|
'--screenshot=' + path, address],
|
|
|
|
|
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=90)
|
|
|
|
|
return True
|
|
|
|
|
except subprocess.TimeoutExpired:
|
|
|
|
|
pass # the browser hung: try again
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
|
exe = browser()
|
|
|
|
|
read_ha_values()
|
|
|
|
|
os.makedirs(OUT, exist_ok=True)
|
|
|
|
|
profile = tempfile.mkdtemp(prefix='flipdot-edge-')
|
|
|
|
|
try:
|
|
|
|
|
for name, hash_part, height in SHOTS:
|
|
|
|
|
path = os.path.join(OUT, name + '.png')
|
|
|
|
|
ok = shoot(exe, profile, path, ESP + '/#' + hash_part, height)
|
|
|
|
|
print(name, os.path.getsize(path) if ok and os.path.exists(path) else 'failed')
|
|
|
|
|
finally:
|
|
|
|
|
shutil.rmtree(profile, ignore_errors=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
main()
|