New panel design

This commit is contained in:
matt committed 2026-08-04 11:55:02 -07:00
1 parent face2432e4
commit b5d6b4a897
30 files changed
+4540 -171

No files matched your search

+109
View File
@@ -0,0 +1,109 @@
# Desktop simulator
Runs the firmware's UI on a Linux desktop so layout work does not need a
flash-and-look cycle. `main/ui/ui_manager.c` is compiled **unmodified** — the
same source the device runs.
## Build
```bash
cmake -S sim -B sim/build -G Ninja
cmake --build sim/build
```
LVGL comes from `managed_components/lvgl__lvgl`, which the ESP-IDF component
manager populates. If that directory is missing, CMake says so; run
`. ~/esp/esp-idf/export.sh && idf.py reconfigure` once to fetch it.
No ESP-IDF environment is needed to *build* the simulator — only a host
compiler, CMake, Ninja and `libsdl2-dev`.
## Run
```bash
./sim/build/thermostat_sim # SDL window, mouse acts as touch
./sim/build/thermostat_sim --png shot.png # render one frame, write a PNG, exit
./sim/build/thermostat_sim --png shot.png --settle-ms 1500
```
The `--png` mode is what makes UI review possible without a human at a screen:
it renders headless, so it works over SSH and in CI, and the resulting image
can be inspected directly.
Clicking a light tile in the window calls the same `ui_set_light_cb` path the
firmware uses; the simulator applies the change locally and logs the command,
so tiles respond with no Home Assistant present.
## Snapshots
The interesting screens — rain falling, a light dimmed, a sensor dropping out
— do not happen on demand, and live data changes underneath a before/after
comparison. So capture real responses and render those:
```bash
export HA_TOKEN=$(cat ~/.config/thermostat-hw/ha_token)
python3 tools/ha_snapshot.py --name rainy
./sim/build/thermostat_sim --data sim/snapshots/rainy.json \
--forecast sim/snapshots/rainy.forecast.json
```
Snapshots are plain JSON and are meant to be hand-edited into states that have
not occurred yet — `sim/snapshots/evening.json` was made that way, to check
the on/dimmed/unavailable tile styling while every real light was off.
`ha_snapshot.py` does **not** carry its own copy of the Jinja template. It
reads it from `thermostat_sim --print-template`, which the preprocessor builds
from `main/api/ha_template.h`, so a snapshot cannot be captured with a
template the firmware does not use.
## How it stays honest
A simulator that renders differently from the panel is worse than none, so
these are deliberately pinned to the device:
| Property | Value | Enforced by |
|---|---|---|
| Resolution | 800×480 | `PANEL_H_RES/V_RES` in `sim_main.c` |
| Colour depth | RGB565 | `LV_COLOR_DEPTH 16` in `lv_conf.h` |
| Render mode | `DIRECT` | `lv_conf.h` / `headless_display_create()` |
| LVGL version | whatever IDF resolved | builds the same `managed_components` tree |
| Fonts | Montserrat 12–48 | `lv_conf.h` **and** `sdkconfig.defaults` |
**The font lists must be edited together.** Enabling a size in one build and
not the other makes the simulator lie: text silently falls back to a different
size and every metric shifts.
## What it does not simulate
- **PSRAM bandwidth, the bounce-buffer refill ISR, and RGB scan timing.** The
left-edge artefacts in the root README are invisible here by construction.
This tool answers "does the layout look right", never "does the panel keep
up". Timing-sensitive changes still need hardware.
- **Touch hardware.** The mouse is a stand-in for the GT911: one contact
point, pixel-accurate, no palm rejection, and none of the I2C contention the
real panel has with the backlight controller.
- **Anything in `main/drivers/`.** Those touch real peripherals and are never
compiled here.
## Layout
| Path | Purpose |
|---|---|
| `sim_main.c` | Entry point: display setup, PNG capture, mock data |
| `lvgl_port_sim.c` | Host implementation of the firmware's `lvgl_port.h` |
| `compat/` | Stand-ins for the ESP-IDF headers shared sources include |
| `lv_conf.h` | Host LVGL config, mirroring the device's Kconfig settings |
`compat/` exists so firmware sources compile as-is instead of growing
`#ifdef`s. If a newly shared source fails to compile, extend `compat/` — do
not fork the firmware source.
## Gotchas
- **Montserrat has no `·` (U+00B7).** It renders as a tofu box.
`weather_get_string()` uses one, but its output only ever reaches a log
line. Anything routed to an on-screen label must avoid it.
- **`lodepng_encode32_file()` does not work here.** LVGL patches its bundled
lodepng to do file I/O through `lv_fs`, which needs a registered driver and
drive-letter paths. `sim_main.c` encodes to memory and uses `fwrite`.