Files
Max Gramser d76461d774 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>
2026-09-18 22:57:14 +02:00

67 lines
3.7 KiB
Python

"""Drawing that needs no buffers of its own: styles that make LVGL 9.5 render a widget into a separate layer.
Such a layer is a full-size buffer (width x height x 4 bytes) allocated on every redraw. The CYD has no PSRAM:
a slider fill rounded less than its track cost 15-20 KB per frame there, and when LVGL cannot get that memory
it retries inside the frame until the task watchdog resets the board.
"""
import re
import sys
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / 'tools'))
import profiles # noqa: E402
PROFILES = ['home-like-2432s028.yaml', 'guition-4848s040.yaml', 'packages/cyd.yaml', 'packages/guition.yaml']
HEADERS = sorted((ROOT / 'components/smart_display').glob('*.h'))
# Style properties that give a widget a layer (lv_obj_style.c calculate_layer_type, lv_refr.c clip corners).
YAML_LAYER_STYLES = re.compile(r'^\s*(opa_layered|transform_rotation|transform_scale(?:_x|_y)?|transform_zoom|transform_skew(?:_x|_y)?'
r'|blend_mode|bitmap_mask_src|drop_shadow_\w+|blur_\w+):', re.M)
C_LAYER_STYLES = re.compile(r'lv_obj_set_style_(opa_layered|transform_rotation|transform_scale(?:_x|_y)?|transform_skew(?:_x|_y)?'
r'|blend_mode|bitmap_mask_src|drop_shadow_\w+|blur_\w+)\(|LV_STYLE_(OPA_LAYERED|TRANSFORM_ROTATION'
r'|TRANSFORM_SCALE\w*|TRANSFORM_SKEW\w*|BLEND_MODE|BITMAP_MASK_SRC|CLIP_CORNER)\b')
class LayerFreeTests(unittest.TestCase):
def test_board_profiles_use_no_layer_styles(self):
for name in PROFILES:
text = profiles.text(name)
self.assertEqual(YAML_LAYER_STYLES.findall(text), [], name)
self.assertEqual(re.findall(r'^\s*clip_corner: true', text, re.M), [], name)
def test_firmware_sets_no_layer_styles(self):
for path in HEADERS:
text = path.read_text()
self.assertEqual(C_LAYER_STYLES.findall(text), [], path.name)
self.assertNotRegex(text, r'lv_obj_set_style_clip_corner\([^;]*\btrue\b', path.name)
def test_slider_fill_keeps_the_track_radius_in_code(self):
"""Every fill radius in the firmware is the same expression as a track radius set beside it."""
statement = re.compile(r'(?:lv_obj_set_style_radius\((\w+(?:\.\w+)?),\s*|set_number\((\w+),\s*LV_STYLE_RADIUS,\s*)'
r'([^;]*?),\s*(LV_PART_MAIN|LV_PART_INDICATOR)\s*\)')
checked = 0
for path in HEADERS:
text = path.read_text()
found = [(m.group(1) or m.group(2), m.group(3).strip(), m.group(4)) for m in statement.finditer(text)]
for widget, value, part in found:
if part != 'LV_PART_INDICATOR':
continue
checked += 1
self.assertIn((widget, value, 'LV_PART_MAIN'), found, f'{path.name}: fill radius {value} of {widget} differs from its track')
self.assertGreaterEqual(checked, 2)
def test_yaml_slider_fill_keeps_the_track_radius(self):
for name in PROFILES:
text = profiles.text(name)
for match in re.finditer(r'^(\s+)- (?:slider|bar):\n((?:\1 .*\n|\s*\n)+)', text, re.M):
block, indent = match.group(2), match.group(1) + ' '
track = re.search(r'^' + indent + r'radius: (\S+)', block, re.M)
fill = re.search(r'^' + indent + r'indicator:\n(?:' + indent + r' .*\n)*?' + indent + r' radius: (\S+)', block, re.M)
if fill:
self.assertTrue(track and track.group(1) == fill.group(1), f'{name}: slider fill radius {fill.group(1)} differs from its track')
if __name__ == '__main__':
unittest.main()