Forty-eight tiles, a tile over the whole page and tiles that go to a page (0.2.74)

Max asked for many more tiles, a tile that takes the whole page as a light
switch you push without looking, and tiles that go to a page. The limit is
one tile per slot of the eight pages, 48, and the firmware keeps its tile
list on the heap sized to the layout (PSRAM on a Guition). A tile's size can
be `full`: one big button that lights up in the state colour while on, or the
double-width card's head with its small slider, direct controls or graph at
the bottom and the value the double-width card had no room for in between;
the clock, the forecast (with an hours strip) and the sun path fill the page.
Navigation tiles are the built-in entities screen.page_1 to screen.page_8:
an arrow or a chosen icon, the name and "Page n", a tap opens that page.
The app sends the three only to firmware 0.2.62+, the editor (the Vue port of
this round by the editor session, 00c9a4e) offers Normal / Double-width /
Full page and "Goes to page", and Claude in Home Assistant gets size: full and
the page tiles. Built-in tiles now send a chosen icon as its codepoint.

Found and fixed by the new host harness before release: the one-second tick
moved the analog clock's second hand (part 18) on a full forecast's hour label
during a page switch and corrupted it. Firmware 0.2.62; both C++ and Python
suites, host harness 37/37 on both boards, hardware pass on both bench boards
in docs/TEST_RESULTS_0274.md.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Max Gramser
2026-09-18 08:44:44 +02:00
parent 00c9a4e4f3
commit bb25353089
29 changed files with 948 additions and 128 deletions
+11 -7
View File
@@ -23,10 +23,14 @@ class IconSetTests(unittest.TestCase):
for name in ('home-like-2432s028.yaml', 'guition-4848s040.yaml', 'packages/cyd.yaml', 'packages/guition.yaml'):
text = (ROOT / name).read_text()
fonts = re.findall(r'materialdesignicons-webfont\.ttf["\']\n id: (\w+)\n size: \d+\n glyphs: (.*)\n', text)
self.assertEqual([font for font, _ in fonts], ['materialdesign_icons', 'materialdesign_icons_mini', 'watch_icon'], name)
self.assertTrue(fonts[0][1].startswith('&tile_icons') and all(g == '*tile_icons' for _, g in fonts[1:]), name)
block = text.split('glyphs: &tile_icons', 1)[1].split('\n\n', 1)[0]
self.assertEqual([font for font, _ in fonts], ['materialdesign_icons', 'materialdesign_icons_mini', 'materialdesign_icons_big', 'watch_icon'], name)
self.assertTrue(fonts[0][1].startswith('&tile_icons ') and all(g == '*tile_icons' for font, g in fonts[1:] if font != 'materialdesign_icons_big'), name)
block = text.split('glyphs: &tile_icons ', 1)[1].split('\n\n', 1)[0]
self.assertEqual(re.findall(r'- "(\\U000F[0-9A-F]{4})"', block), wanted, name)
# The big font of the full-page card (firmware 0.2.62+) carries the subset the screen draws on its own.
self.assertEqual(fonts[2][1].split(' #')[0].strip(), '&tile_icons_big', name)
big = text.split('glyphs: &tile_icons_big', 1)[1].split('\n\n', 1)[0]
self.assertEqual(re.findall(r'- "(\\U000F[0-9A-F]{4})"', big), [f'\\U000{tile_icons.GLYPHS[n]}' for n in tile_icons.BIG_GLYPHS], name)
self.assertNotIn('MDI_GLYPH', text, name)
self.assertTrue((ROOT / 'web/src/assets/tile-icons.woff').exists())
@@ -48,11 +52,11 @@ class IconSetTests(unittest.TestCase):
for domains, code in re.findall(r'if \((d == "\w+"(?: \|\| d == "\w+")*)\) return "\\U000(F[0-9A-F]{4})";', body):
for domain in re.findall(r'"(\w+)"', domains):
native[domain] = code
# The built-in cards pick per entity, not per domain: a clock or the settings page.
settings, clock = re.search(
r'if \(d == "screen"\) return tile\.is_settings\(\) \? "\\U000(F[0-9A-F]{4})" : "\\U000(F[0-9A-F]{4})";', body).groups()
# The built-in cards pick per entity, not per domain: a clock, the settings page or a page to go to.
settings, page, clock = re.search(
r'if \(d == "screen"\) return tile\.is_settings\(\) \? "\\U000(F[0-9A-F]{4})" : tile\.is_page\(\) \? "\\U000(F[0-9A-F]{4})" : "\\U000(F[0-9A-F]{4})";', body).groups()
builtin = {entity: tile_icons.GLYPHS[name] for entity, name in tile_icons.BUILTIN_TILES.items()}
self.assertEqual({'screen.settings': settings, 'screen.clock': clock}, builtin)
self.assertEqual({'screen.settings': settings, 'screen.clock': clock, **{f'screen.page_{n}': page for n in range(1, 9)}}, builtin)
self.assertEqual(tile_icons.editor()['builtin'], builtin)
self.assertEqual(tile_icons.GLYPHS[tile_icons.DEFAULTS['screen']], clock)
expected = {domain: tile_icons.GLYPHS[name] for domain, name in tile_icons.DEFAULTS.items() if domain != 'screen'}