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:
+1
-1
@@ -99,6 +99,6 @@ missed taps. A full-screen BMP is about 390 KB; on the LAN that takes one to thr
|
||||
(`tests/test_camera_view.cpp`).
|
||||
- `components/smart_display/runtime_tiles.h`: the full-screen view, the alert picture and the
|
||||
`camera` message.
|
||||
- `guition-4848s040.yaml`: the two `online_image` components, the alert frame, and the diagnostic
|
||||
- `packages/boards/guition-4848s040.yaml`: the two `online_image` components, the alert frame, and the diagnostic
|
||||
action `preview_camera` (an entity opens it, an empty entity closes it).
|
||||
- `tests/test_camera.py`: the app side and the words both sides share.
|
||||
|
||||
+4
-2
@@ -13,8 +13,10 @@ or periodic recovery script is needed. The GT911 uses unmirrored coordinates.
|
||||
|
||||
## Files
|
||||
|
||||
- `guition-4848s040.yaml`: the board profile (hardware + 480×480 interface).
|
||||
- `packages/guition.yaml`: what a screen builds from; generated from that profile.
|
||||
- `packages/boards/guition-4848s040.yaml`: the board file (hardware, the 480×480 sizes, the Guition's own parts); the
|
||||
interface itself is `packages/core.yaml`, shared with the CYD (docs/PROFILES.md).
|
||||
- `packages/guition.yaml`: what a screen builds from over GitHub; `guition-4848s040.yaml` in the root is the same
|
||||
for a build from a checkout.
|
||||
- `<your-screen>.yaml`: the screen's own profile, written by ESP Screens, outside Git.
|
||||
- `secrets.yaml`: local Wi-Fi/API/OTA settings, outside Git.
|
||||
- `tools/verify_gt911.py`: physical pixel check; no resistive ADC calibration.
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
# One screen, three files: the shared core and the board files
|
||||
|
||||
Since app 0.2.84 a screen's ESPHome configuration is one shared file plus one file per board, put together by
|
||||
ESPHome's own [packages](https://esphome.io/components/packages/) mechanism. Before, each board was a 5,000-line
|
||||
profile of which 87 % was the same text as the other board's; every change had to be made twice and the two had
|
||||
already drifted apart in small ways.
|
||||
|
||||
| File | What it holds |
|
||||
|---|---|
|
||||
| `packages/core.yaml` | Everything every board shares: the LVGL tree (tiles, cards, overlays, the touch test page), the scripts, the API actions, the entities, the fonts, the globals. It contains no number that depends on the board: every such value is a `${NAME}` the board file defines. |
|
||||
| `packages/boards/cyd-2432s028.yaml` | The CYD: its hardware (ILI9341 over SPI, XPT2046 resistive touch, backlight, LED), its sizes and font sizes, the code the shared lambdas take from it (the *hooks*, below), and what only a CYD has: the calibration wizard, a page swipe by LVGL's gesture, `ui_self_test`, the two scripts that drive the backlight as a light action, its climate card layout. |
|
||||
| `packages/boards/guition-4848s040.yaml` | The Guition: its hardware (ST7701S RGB, GT911, PSRAM), its sizes, its hooks, and what only a Guition has: camera images (`online_image`, the alert's picture frame), the Rotation entity, the page swipe from the glass edge, the snapshot diagnostics, the climate card with its fan and swing card, the wider tile labels (`!extend` on each tile). |
|
||||
| `packages/cyd.yaml`, `packages/guition.yaml` | What a screen installed from ESP Screens builds from over GitHub, unchanged in name and place: ESP Screen Manager writes every screen's YAML with `files: [packages/<board>.yaml]`. Each is a few lines: where the fonts are, the components from GitHub, and `packages: {core: !include core.yaml, board: !include boards/<board>.yaml}`. |
|
||||
| `home-like-2432s028.yaml`, `guition-4848s040.yaml` | The same two files for a build from a checkout (the bench, the manual route of README_EXTENDED.md): the components of the checkout, the fonts from `fonts/`, and the secrets from `secrets.yaml`. |
|
||||
|
||||
ESPHome merges them in this order: the core, then the board file on top of it, then the entry file on top of both.
|
||||
A mapping merges key by key, a list of components with ids merges by id (`!extend` adds to an existing widget or
|
||||
script, `!remove` takes one away), any other list is the core's items followed by the board's, and a plain value of
|
||||
the board file replaces the core's. `esphome config home-like-2432s028.yaml` shows the result.
|
||||
|
||||
## The three ways a board differs from the core
|
||||
|
||||
1. **A size, a font size, a name.** `${TILE_ICON_SIZE}`, `${FONT_HEADLINE_SIZE}`, `${TOUCH_TEST_TITLE}`: the core
|
||||
writes the placeholder, the board file's `substitutions:` table gives the value. `tools/check_packages.py` fails
|
||||
when a board leaves one out, so a new board cannot forget a size.
|
||||
2. **A stretch of C++ inside a shared lambda.** The Guition sets `runtime_tiles::rotation_supported`, closes its
|
||||
camera when the cards close, prepares the alert's picture frame; the CYD sets its calibration up and lays the
|
||||
climate card out for a portrait panel. Each such stretch is a *hook*: a substitution whose value is that board's
|
||||
exact lines, written where the old profile had them (`${BOOT_ENABLED_HOOK}`, `${CLOSE_CARDS_HOOK}`,
|
||||
`${APPLY_ROTATION}`, ...). A board that adds nothing there names the hook with an empty value. The hooks sit at
|
||||
the end of the board file's `substitutions:` table, each with a line that says where it lands.
|
||||
3. **A whole section or script.** Hardware, `online_image`, the Rotation select, `capture_ui_snapshot`: these are
|
||||
the board file's own sections, merged next to the core's. `alert_dismiss`, `alert_flash` and `wake_display` are
|
||||
in both board files because their *steps* differ (a `light.turn_on` action on the CYD, the `set_backlight` script
|
||||
on the Guition); so are `ui_self_test` and `climate_card_refresh`, whose layouts differ by design.
|
||||
|
||||
## Adding a board
|
||||
|
||||
1. Copy the board file of the panel that resembles the new one most (a small SPI panel: the CYD; a big RGB panel
|
||||
with capacitive touch: the Guition) to `packages/boards/<board>.yaml` and change its hardware sections.
|
||||
2. Fill the sizes table for the new resolution, and go through the hooks: keep, change or empty each one.
|
||||
3. Add `packages/<board>.yaml` and `<board>.yaml` after the existing entries, `<board>` to `ENTRIES` in
|
||||
`tools/profiles.py` and to `installation_yaml()` in `screen_manager/app/core.py`, and a `BANDS` entry to
|
||||
`tools/render_topbar.py`.
|
||||
4. `python3 tools/check_packages.py`, `esphome config <board>.yaml`, `tools/check.sh --firmware`.
|
||||
|
||||
The tiles per page (two columns of three) are still fixed in the firmware, the add-on and the editor
|
||||
(`SLOTS_PER_PAGE`); a panel that wants more rows is a later round.
|
||||
|
||||
## How the split was verified
|
||||
|
||||
The split was made by a script that replaced every size in the old CYD profile and asserted the value it replaced
|
||||
in both old profiles (195 places, 84 names), and took every hook's text from the old profiles by line. Then the
|
||||
result of `esphome config` and the generated `main.cpp` of both boards, through the checkout entry and through the
|
||||
GitHub package route, were compared with the same of the old profiles:
|
||||
|
||||
- every scalar, every lambda body and every component is the same, apart from whitespace and `#line` directives;
|
||||
- on the CYD, the calibration setup runs in the same lambda as the effects page's hooks instead of a lambda of
|
||||
its own (the same statements, in the same order: `${BOOT_CARDS_TAIL}`);
|
||||
- on the Guition, the alert's picture frame is created after the OK button instead of before it (they never
|
||||
overlap), and the hidden `camera_image_seed` after the labels;
|
||||
- the order in which ESPHome declares and registers fonts, entities, scripts, API actions, styles, includes and
|
||||
build flags follows the merge (the core's first, the board's after), which changes the numbers in ESPHome's
|
||||
component tables and nothing that runs in sequence.
|
||||
|
||||
Every other generated source file of both builds was identical, byte for byte.
|
||||
+6
-5
@@ -18,14 +18,15 @@
|
||||
|
||||
## For every release
|
||||
|
||||
1. Update the source profiles and shared components. Run
|
||||
`python3 tools/generate_packages.py`; never edit `packages/*.yaml` directly.
|
||||
1. Update `packages/core.yaml` (shared by every board), the board files under `packages/boards/` and the
|
||||
shared components; docs/PROFILES.md says what goes where. `python3 tools/check_packages.py` checks that the
|
||||
boards define every name the core uses (tools/check.sh runs it).
|
||||
The editor is the Vue app in `web/`: after a change under `web/src`, run
|
||||
`cd web && npm ci && npm test && npm run check && npm run build` and commit
|
||||
`screen_manager/app/static` with it (that folder is the build output; never edit it by hand).
|
||||
2. Run `tools/check.sh` (with `PYTHON=.venv-portal/bin/python` on a development machine). It runs all
|
||||
Python tests with aiohttp, PyYAML, Pillow and fontTools installed, every `tests/*.cpp` with
|
||||
`clang++ -std=c++17 -Wall -Wextra -Werror -I.`, both generators with `--check`, and the editor's
|
||||
`clang++ -std=c++17 -Wall -Wextra -Werror -I.`, `tools/check_packages.py`, `tools/generate_icons.py --check`, and the editor's
|
||||
`npm ci`, `npm test`, `npm run check` and `npm run build`, and fails when that fresh build differs from the
|
||||
`screen_manager/app/static` in Git (committed or staged). For a firmware change, `tools/check.sh --firmware`
|
||||
compiles both board profiles with placeholder secrets from a temporary folder (never the real `secrets.yaml`) and
|
||||
@@ -209,9 +210,9 @@ font and otherwise falls back to the domain icon. Hence no `min_firmware`.
|
||||
An old editor that omits `icon` keeps the stored choice.
|
||||
|
||||
`tile_icons.py` is the single source list. `tools/generate_icons.py` writes the glyphs
|
||||
as a YAML anchor into the three MDI fonts of both board profiles and builds
|
||||
as a YAML anchor into the three MDI fonts of `packages/core.yaml` (both boards build from it) and builds
|
||||
`web/src/assets/tile-icons.woff` for the editor (with left bearing equal to xMin, otherwise
|
||||
icons sit off-center in the browser); run `generate_packages.py` afterward.
|
||||
icons sit off-center in the browser).
|
||||
The `MDI_GLYPH_*` substitutions are retired: `TILEn_ICON` in manual
|
||||
profiles must come from the set. No changed preferences or keys.
|
||||
|
||||
|
||||
+2
-1
@@ -141,7 +141,8 @@ The screen's preferences hold the value, so the entity reads it in a lambda inst
|
||||
(`restore_value` cannot be combined with a lambda). A switch publishes its own changes. A number, time or
|
||||
select takes `update_interval: never` and one line in `apply_screen_settings`, which publishes it when it
|
||||
differs; that script runs after every change and on every Home Assistant connection (the time sync), so the
|
||||
entity has a value before Home Assistant reads the states. Then run `python3 tools/generate_packages.py`.
|
||||
entity has a value before Home Assistant reads the states. The entities live in `packages/core.yaml`; the
|
||||
Rotation select, which only a Guition has, in its board file (docs/PROFILES.md).
|
||||
|
||||
### 4. The add-on
|
||||
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
# Test results app 0.2.84 / firmware 0.2.70 (2026-09-18)
|
||||
|
||||
Max asked whether the two board profiles carried much double code ("hebben we veel dubbele code voor de beide
|
||||
schermen?"), wanted one shared package with only the board differences apart ("zodat we ook makkelijker een scherm
|
||||
kunnen toevoegen later"), and set the bar for this round: a refactor that can be verified afterwards, one to one, with
|
||||
the same end result ("Ik wil echt dat je fully 100% zeker bent dat het precies hetzelfde eindresultaat is"). The rows
|
||||
per page stay as they are (his call: step 1 only).
|
||||
|
||||
## What changed
|
||||
|
||||
- `packages/core.yaml`: everything the two profiles shared (4,283 lines). No number in it depends on the board.
|
||||
- `packages/boards/cyd-2432s028.yaml` (738 lines) and `packages/boards/guition-4848s040.yaml` (1,015 lines): the
|
||||
hardware, the table of 84 sizes and font sizes, the 18 hooks (the lines of C++ a board adds inside a shared lambda,
|
||||
as substitutions whose values are the old profile's exact text), and the board's own sections and scripts.
|
||||
- `packages/cyd.yaml`, `packages/guition.yaml`, `home-like-2432s028.yaml`, `guition-4848s040.yaml`: entry files of
|
||||
15-45 lines that include the core and the board file. The two under `packages/` keep their names, so every screen's
|
||||
own YAML (`files: [packages/<board>.yaml]`) builds as before.
|
||||
- `tools/check_packages.py` replaces `tools/generate_packages.py` in `tools/check.sh` and CI; `tools/profiles.py` gives
|
||||
the tests and tools the three files as one text (`text`), with the substitutions filled in (`resolved`), or without
|
||||
the substitution blocks (`merged`). Twenty test files read the profiles through it; `tools/generate_icons.py` writes
|
||||
the glyph lists into the core only; `tools/render_topbar.py` takes the fonts from the core at each board's sizes;
|
||||
`tools/check.sh --firmware` links `packages/` next to the check copies of the profiles.
|
||||
- Docs: docs/PROFILES.md (new), pointers in AGENTS.md, RELEASING.md, GUITION.md, THEME.md, SETTINGS.md, CAMERA.md.
|
||||
|
||||
## How the split was made
|
||||
|
||||
A script (kept out of the repository) took the old CYD profile as the base of the core and replaced every size by its
|
||||
`${NAME}`, asserting the value it replaced in *both* old profiles: 195 places, 84 names. Every hook's text was cut from
|
||||
the old profiles by line. The Guition's tile labels (`height`, `width: 130`, `long_mode: DOT` on twenty labels) are
|
||||
`!extend` blocks in its board file; its alert picture frame, `camera_image_seed` and `climate_settings_card` likewise.
|
||||
|
||||
## The one-to-one check
|
||||
|
||||
Before the split, `esphome config` and `esphome compile --only-generate` (ESPHome 2026.6.2) were run for both boards
|
||||
along two routes: the checkout profile, and a screen YAML as `core.installation_yaml()` writes it, with
|
||||
`packages: display: {url: file://<checkout>, ref: <commit>, files: [packages/<board>.yaml]}`: the GitHub route the
|
||||
add-on uses, against the old commit. After the split the same four builds were run against the new commit, and
|
||||
compared:
|
||||
|
||||
- `esphome config`, read as data: every scalar, every lambda body and every component is the same, apart from
|
||||
whitespace inside a lambda where an empty hook leaves a blank line;
|
||||
- `main.cpp`, with `#line` directives, blank lines, leading whitespace and ESPHome's numbered ids taken out, compared
|
||||
as a sorted multiset of lines: the Guition's is identical except the lines that carry ESPHome's component-table
|
||||
indices and the numbers of two LVGL event triggers (the picture frame and the OK button swapped places); the CYD's
|
||||
differs by those indices and by one `StatelessLambdaAction` fewer (the boot step that set the calibration wizard up
|
||||
is now the tail of the boot step that sets the cards' hooks up: `${BOOT_CARDS_TAIL}`, the same statements in the
|
||||
same order, one lambda instead of two consecutive ones);
|
||||
- every other generated source file (361 for the CYD, 401 for the Guition; `build_info_data.cpp` with its build
|
||||
timestamp left out): identical, byte for byte;
|
||||
- `platformio.ini`: identical on the CYD; the Guition's two `build_flags` in the other order.
|
||||
|
||||
What the merge changes is the order in which ESPHome declares and registers fonts, entities, scripts, API actions,
|
||||
styles, includes and build flags (the core's first, the board's after). That order is what the component tables
|
||||
number; nothing that runs in sequence moved. The two z-order changes on the Guition (the picture frame after the OK
|
||||
button, the hidden image seed after the labels) concern widgets that never overlap or are never shown.
|
||||
|
||||
So the firmware version stays 0.2.70, and no screen is offered an update.
|
||||
|
||||
## Automated
|
||||
|
||||
- `tools/check.sh`: Python 466 tests (`tests/test_easy_package.py` rewritten for the new layout), C++ 19/19, the package
|
||||
check, the icon check, Vitest, vue-tsc, build and the bundle: 10 passed, 0 failed.
|
||||
- `tools/check.sh --firmware` (ESPHome 2026.6.2): CYD 1,676,912 B = 91.4 % of the 1,835,008 B slot (158,096 B free),
|
||||
Guition 2,054,080 B = 25.3 %. Against the old profiles built the same way on the same computer (CYD 1,675,472 B,
|
||||
Guition 2,052,544 B; the 0.2.83 release noted 1,675,424 B for the CYD from another build): +1,440 B and +1,536 B.
|
||||
- Where those bytes are (`xtensa-esp32-elf-size -A`, `nm -S` on both ELFs): every section is the same size except
|
||||
`.flash.text` (+1,432 B on the CYD, +1,528 B on the Guition; `.dram0.bss` 16 B smaller on the CYD: the lambda object
|
||||
that went). Of the symbols that changed size, ESPHome's generated `setup()` accounts for the growth (+1,412 B on the
|
||||
CYD): the components are registered in the merge's order, which changes its literal pool. The lambdas are the same
|
||||
functions under other numbers (`{lambda()#75}` +1,259 B where `{lambda()#56}` −1,259 B), net +19 B on the CYD and
|
||||
−18 B on the Guition from the line numbers ESPHome's log macro bakes in; the font arrays and every string are the
|
||||
same size, renamed.
|
||||
|
||||
## Not done
|
||||
|
||||
- No hardware was flashed: the check above is the proof that a screen would run the same code; Max's screens keep
|
||||
0.2.70 as they are.
|
||||
- Left for a later round, each a change of what the CYD runs and therefore not part of this one: the CYD could drive
|
||||
its backlight through the Guition's `set_backlight` script (then `alert_dismiss`, `alert_flash` and `wake_display`
|
||||
become shared); the camera hooks `camera_close()`, `camera_visible()`, `alert_prepare()`, `alert_clear()` and
|
||||
`camera_tick()` exist as no-ops on a board without images, so the core could call them on every board and five
|
||||
hooks would go; the portrait layout in `OPEN_VALUE_OVERLAY_TAIL` could move into the CYD's `climate_card_refresh`.
|
||||
The tiles per page (`SLOTS_PER_PAGE`, two columns of three) stay fixed in the firmware, the add-on and the editor.
|
||||
+4
-4
@@ -63,10 +63,10 @@ you set), and a palette pass that overwrote a colour the sun path had chosen its
|
||||
1. **A role**: one line in `enum Role` and one row in `ROLES`, in the same order (the tests read
|
||||
both). The light value is the design; choose the dark value on a render, not in the table.
|
||||
2. **In firmware code**: `theme::color(theme::YOUR_ROLE)`. Never `lv_color_hex(0x...)`.
|
||||
3. **In a board profile**: the widget takes a paint. For a new combination add it to `enum class
|
||||
Paint` and `PAINTS`, a colourless `- id: paint_<name>` in the `style_definitions` of both profiles,
|
||||
and `theme::fill(id(paint_<name>), Paint::<name>);` in their `theme::paints` list. Then
|
||||
`python3 tools/generate_packages.py`.
|
||||
3. **In the profiles**: the widget takes a paint. For a new combination add it to `enum class
|
||||
Paint` and `PAINTS`, a colourless `- id: paint_<name>` in the `style_definitions` of `packages/core.yaml`
|
||||
(or of one board file, for a paint only that board uses), and `theme::fill(id(paint_<name>), Paint::<name>);`
|
||||
in the core's `theme::paints` list (a board-only paint goes into the board's `BOARD_PAINT_FILL` hook).
|
||||
4. **Something drawn once and kept that changes colour by state**: set it where the state is drawn,
|
||||
and make sure the redraw above reaches that code.
|
||||
5. A local colour always wins over a paint: an object that takes a paint must not also get a local
|
||||
|
||||
Reference in New Issue
Block a user