Build every screen from one shared core plus a small file per board, the same firmware as before (0.2.84)
The two board profiles (5,000 lines each, 87 % the same text) become packages/core.yaml and one file per board under packages/boards/: the hardware, a table of 84 sizes and font sizes, 18 hooks with the lines of C++ only that board adds inside a shared lambda, and the board's own sections. packages/cyd.yaml and packages/guition.yaml keep their names and places (a screen installed from ESP Screens builds as before); home-like-2432s028.yaml and guition-4848s040.yaml become the same entries for a checkout. The firmware stays 0.2.70: the C++ ESPHome generates for each board, through the checkout profile and through the GitHub package route, was compared line by line with the old profiles' (docs/TEST_RESULTS_0284.md). tools/check_packages.py replaces tools/generate_packages.py; tools/profiles.py gives the tests and tools the three files as one; docs/PROFILES.md says how the files fit together and how a board is added. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
+19
-17
@@ -12,12 +12,14 @@ import unittest
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT / 'tools'))
|
||||
import profiles # noqa: E402
|
||||
sys.path.insert(0, str(ROOT / 'screen_manager/app'))
|
||||
from core import DARK_MODE_MIN_FIRMWARE, FIRMWARE_VERSION, SETTING_ENTITIES, SETTING_RULES, SETTINGS_BESIDE_BLOCK # noqa: E402
|
||||
|
||||
COMPONENT = ROOT / 'components/smart_display'
|
||||
THEME = (COMPONENT / 'theme.h').read_text()
|
||||
PROFILES = [ROOT / 'home-like-2432s028.yaml', ROOT / 'guition-4848s040.yaml', ROOT / 'packages/cyd.yaml', ROOT / 'packages/guition.yaml']
|
||||
PROFILES = list(profiles.NAMES)
|
||||
HEX = re.compile(r'0x[0-9A-Fa-f]{6}\b')
|
||||
# What may still carry a number that looks like a colour: the light's colour picker shows the colours a light can take,
|
||||
# a mask and a codepoint check are no colours, and the value card's colour key is a wheel of fixed hues.
|
||||
@@ -46,14 +48,14 @@ class OnePlace(unittest.TestCase):
|
||||
|
||||
def test_no_profile_writes_a_colour_of_its_own(self):
|
||||
for path in PROFILES:
|
||||
text = path.read_text()
|
||||
text = profiles.text(path)
|
||||
wheel = re.findall(r'^\s+arc_color: (0x[0-9A-F]{6})$', text, re.M)
|
||||
self.assertEqual(set(wheel), COLOUR_WHEEL, path.name)
|
||||
self.assertEqual(set(wheel), COLOUR_WHEEL, path)
|
||||
everything = HEX.findall(text)
|
||||
self.assertEqual(sorted(everything), sorted(wheel), f'{path.name}: only the colour wheel keeps fixed hues')
|
||||
self.assertEqual(sorted(everything), sorted(wheel), f'{path}: only the colour wheel keeps fixed hues')
|
||||
self.assertNotIn('BG_TOP_COLOR', text)
|
||||
self.assertNotIn('ACCENT_AUTO', text)
|
||||
self.assertNotRegex(text, r'^\s+(bg|text|border|bg_grad|line|outline|shadow)_color:', path.name)
|
||||
self.assertNotRegex(text, r'^\s+(bg|text|border|bg_grad|line|outline|shadow)_color:', path)
|
||||
|
||||
def test_every_role_has_a_light_and_a_dark_value(self):
|
||||
roles = re.search(r'enum Role : uint8_t \{(.*?)ROLE_COUNT', THEME, re.S)[1]
|
||||
@@ -67,7 +69,7 @@ class Paints(unittest.TestCase):
|
||||
def test_every_paint_is_defined_adopted_and_used_in_every_profile(self):
|
||||
paints = paint_names()
|
||||
for path in PROFILES:
|
||||
text = path.read_text()
|
||||
text = profiles.merged(path)
|
||||
defined = re.findall(r'^ - id: (paint_\w+)$', text, re.M)
|
||||
used = set(re.findall(r'styles: (paint_\w+)$', text, re.M))
|
||||
filling = text.split('theme::paints = []() {', 1)[1].split('\n };', 1)[0]
|
||||
@@ -75,33 +77,33 @@ class Paints(unittest.TestCase):
|
||||
listed = re.search(r'for \(auto \*style : \{([^}]*)\}\) theme::fill\(style, Paint::(\w+)\);', filling)
|
||||
looped = [(name, listed[2]) for name in re.findall(r'id\((\w+)\)', listed[1])]
|
||||
adopted = dict(explicit + looped)
|
||||
self.assertEqual(len(adopted), len(explicit) + len(looped), f'{path.name}: every style is filled once')
|
||||
self.assertEqual(text.count('theme::fill('), len(explicit) + 1, f'{path.name}: styles are filled in theme::paints only')
|
||||
self.assertEqual(set(defined), used, f'{path.name}: every paint style is used, every used one defined')
|
||||
self.assertEqual(len(adopted), len(explicit) + len(looped), f'{path}: every style is filled once')
|
||||
self.assertEqual(text.count('theme::fill('), len(explicit) + 1, f'{path}: styles are filled in theme::paints only')
|
||||
self.assertEqual(set(defined), used, f'{path}: every paint style is used, every used one defined')
|
||||
for style in defined + ['style_page', 'style_tile', 'style_icon_circle', 'style_room', 'style_time', 'style_title',
|
||||
'style_value', 'style_value_big']:
|
||||
self.assertIn(style, adopted, f'{path.name}: {style} takes its colours from theme.h')
|
||||
self.assertIn(adopted[style], paints, f'{path.name}: {style}')
|
||||
self.assertIn(style, adopted, f'{path}: {style} takes its colours from theme.h')
|
||||
self.assertIn(adopted[style], paints, f'{path}: {style}')
|
||||
for style, paint in adopted.items():
|
||||
if style.startswith('paint_'):
|
||||
self.assertEqual(style, f'paint_{paint}', f'{path.name}: a paint style is named after its paint')
|
||||
self.assertEqual(style, f'paint_{paint}', f'{path}: a paint style is named after its paint')
|
||||
# The styles that carry colours have none written down.
|
||||
styles = re.search(r'^ style_definitions:\n(.*?)^ # ---------- ALERT', text, re.M | re.S)[1]
|
||||
self.assertNotRegex(styles, r'_color:', path.name)
|
||||
self.assertNotRegex(styles, r'_color:', path)
|
||||
|
||||
def test_the_look_is_applied_at_boot_after_a_change_and_drawn_again(self):
|
||||
for path in PROFILES:
|
||||
text = path.read_text()
|
||||
text = profiles.text(path)
|
||||
boot = text.split('runtime_tiles::load_settings();', 1)[1].split('runtime_tiles::settings_changed', 1)[0]
|
||||
self.assertIn('theme::paints();\n theme::set_dark(settings_screen::dark_mode != 0);', boot,
|
||||
f'{path.name}: the paints filled, then the saved look, before the first frame')
|
||||
f'{path}: the paints filled, then the saved look, before the first frame')
|
||||
apply = text.split(' - id: apply_screen_settings\n', 1)[1].split('\n - id: ', 1)[0]
|
||||
self.assertIn('theme::set_dark(settings_screen::dark_mode != 0);', apply, f'{path.name}: a change of the setting')
|
||||
self.assertIn('theme::set_dark(settings_screen::dark_mode != 0);', apply, f'{path}: a change of the setting')
|
||||
redraw = text.split('theme::redraw = []() {', 1)[1].split('};', 1)[0]
|
||||
for needle in ('runtime_tiles::restyle();', 'light_controls::restyle();', 'settings_screen::restyle();',
|
||||
'theme::surface(id(alert_card_color))', 'id(climate_card_refresh).execute();',
|
||||
'id(open_climate_mode_picker).execute();'):
|
||||
self.assertIn(needle, redraw, f'{path.name}: {needle}')
|
||||
self.assertIn(needle, redraw, f'{path}: {needle}')
|
||||
self.assertIn('id(alert_card_color) = alert.color;', text)
|
||||
self.assertIn('lv_color_hex(theme::surface(alert.color))', text)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user