Files

90 lines
3.6 KiB
Python
Raw Permalink Normal View History

"""Writes examples/home-pages.json from the pages on a display: the dynamic pages with the
icons, bitmaps and fonts they use, as a backup file that the web UI imports (System tab).
python tools/export_examples.py [ESP32 address, default 10.1.1.198]
Private parts of entity ids are replaced by placeholders (PRIVATE below); the export stops
when a page still contains one of them. The pages that come with the firmware (Clock) and
their icons (the Pikachus) are left out: every display has them. Only the Python standard
library is used.
"""
import json
import os
import re
import sys
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__))), 'examples', 'home-pages.json')
# Private text -> placeholder (in entity ids, attributes and texts)
PRIVATE = {
'vigilantestraat': 'home', # the street (climate entity)
'122225067403': '000000000000', # the Envoy's serial number
'mangavalkserver': 'plex_server', # the Plex server's name
}
FROM_FIRMWARE_PAGES = {'clock'}
FROM_FIRMWARE_ICONS = {'pikachu', 'pikachu yawning'}
def get(path):
with urllib.request.urlopen(ESP + path, timeout=60) as r:
return json.loads(r.read())
def clean(value):
if isinstance(value, str):
for private, placeholder in PRIVATE.items():
value = re.sub(re.escape(private), placeholder, value, flags=re.IGNORECASE)
return value
def main():
pages = [p for p in get('/api/pages')['pages'] if p['name'].lower() not in FROM_FIRMWARE_PAGES]
icons = {i['name'].lower(): i for i in get('/api/icons')['icons']}
bitmaps = {b['name'].lower(): b for b in get('/api/bitmaps')['bitmaps'] if not b['builtin']}
fonts = {f['name'].lower(): f for f in get('/api/fonts')['fonts'] if not f['builtin']}
# What the pages use: backgrounds, fixed images, the images by state (name start), fonts
images, used_fonts = set(), set()
for p in pages:
for s in p['page']['slots']:
images.add(s['bitmap'].lower())
for e in p['page']['elements']:
used_fonts.add(e['font'].lower())
if e['type'] != 'image':
continue
if not e['entity']:
images.add(e['text'].lower())
else:
start = e['text'].lower()
images.update(n for n in list(icons) + list(bitmaps) if n.startswith(start))
for e in p['page']['elements']:
for k, v in e.items():
e[k] = clean(v)
out = {
'format': 'flipdot-backup', 'version': 1, 'firmware': get('/api/status')['version'],
'exported': '2026-10-16T00:00:00Z',
'bitmaps': [{'name': b['name'], 'data': b['data']} for n, b in sorted(bitmaps.items()) if n in images],
'icons': [{k: i[k] for k in ('name', 'width', 'height', 'data')} for n, i in sorted(icons.items())
if n in images and n not in FROM_FIRMWARE_ICONS],
'pages': [{'name': p['name'], 'page': p['page']} for p in pages],
'fonts': [],
}
for n in sorted(used_fonts & set(fonts)):
out['fonts'].append(get('/api/font?name=' + urllib.parse.quote(fonts[n]['name'])))
text = json.dumps(out, ensure_ascii=False, indent=1)
left = [p for p in PRIVATE if p.lower() in text.lower()]
if left:
sys.exit('Private text still in the export: ' + ', '.join(left))
with open(OUT, 'w', encoding='utf-8', newline='\n') as f:
f.write(text + '\n')
print(len(out['pages']), 'pages,', len(out['icons']), 'icons,', len(out['bitmaps']), 'bitmaps,',
len(out['fonts']), 'fonts ->', OUT)
main()