diff --git a/docs/CAMERA.md b/docs/CAMERA.md index f3c471f..77835b8 100644 --- a/docs/CAMERA.md +++ b/docs/CAMERA.md @@ -99,8 +99,9 @@ Guition it comes in about 1.8 s (2.8 s with 4 KB). camera's last snapshot (it keeps one for 30 s after the last load), 4.5 s when it must ask Home Assistant first, of which 2.4 s is the camera's own snapshot. Firmware 0.2.72 took 3.4 s and 5.8 s. Details in docs/TEST_RESULTS_0287.md. -- `power_save_mode: none` under `wifi:` (the YAML ESP Screens writes has it since app 0.2.24) keeps - the screen from waiting on its Wi-Fi now and then while an image comes in. +- Wi-Fi without power save (firmware 0.2.74 sets `power_save_mode: none` itself) makes the screen + wait on its Wi-Fi less often while an image comes in: a loop held over 50 ms in one of eleven + opens, against five of twelve with power save. ## For developers diff --git a/docs/EASY_SETUP.md b/docs/EASY_SETUP.md index a262e85..3375505 100644 --- a/docs/EASY_SETUP.md +++ b/docs/EASY_SETUP.md @@ -111,9 +111,10 @@ button. The card disappears once the screen is in the list. ## 4. Choose and edit your tiles -Tip: in your ESPHome YAML, add the line `power_save_mode: none` under `wifi:` (new -wizard YAMLs already have it); the screen is mains-powered, so this keeps it responding to -Home Assistant without Wi-Fi sleep delay. +A screen keeps its Wi-Fi awake (`power_save_mode: none`): it is mains-powered, and this way +Home Assistant's messages reach it without waiting for the router. Firmware 0.2.74 and later +do this by themselves; an older screen gets it with its next **Update**. A `power_save_mode` +under `wifi:` in the screen's own ESPHome YAML still wins. Select your screen, tap the bar at the top of a page to give the screen its name, and search for entities in the **Library** on the right. It has domain filters with diff --git a/docs/PROFILES.md b/docs/PROFILES.md index 5ca627a..22e049b 100644 --- a/docs/PROFILES.md +++ b/docs/PROFILES.md @@ -7,7 +7,7 @@ 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/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, and Wi-Fi that never dozes (`power_save_mode: none`, firmware 0.2.74+; the network itself stays in the screen's own YAML). 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/.yaml]`. Each is a few lines: where the fonts are, the components from GitHub, and `packages: {core: !include core.yaml, board: !include boards/.yaml}`. | diff --git a/docs/RELEASING.md b/docs/RELEASING.md index da2b8b2..5b6b2bd 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -219,6 +219,16 @@ 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. +### Compatibility 0.2.88 / firmware 0.2.74 + +Firmware only, one setting: `packages/core.yaml` has `wifi: power_save_mode: none`, the first `wifi:` key in a package. +ESPHome merges it under the screen's own `wifi:` block, which keeps the network, the password and the fallback hotspot, +and the screen's own YAML wins on the mode as on any key (`merge_config`: the main config over every package). The +YAML ESP Screens writes has had the same line since app 0.2.24; screens with an older or hand-written YAML ran ESPHome's +ESP32 default `light` (`WIFI_PS_MIN_MODEM`). The app's Override YAML still refuses `wifi:` (`PROTECTED_OVERRIDE_KEYS`). +No protocol, storage, preference or key change. `tests/test_easy_package.py` keeps the core's `wifi:` block to that one +key and every other package without one. Details: docs/TEST_RESULTS_0288.md. + ### Compatibility 0.2.87 / firmware 0.2.73 Firmware only, plus README pictures and docs/CAMERA.md. No protocol, storage, preference or key change: an older app @@ -235,7 +245,7 @@ drives this firmware as before, and the camera messages are the same; only when 4 s refresh and the retries. `camera_spinner` turns until the first picture or a note; its ring is the new theme role `CAMERA_TRACK` (0x393D42 in both looks). "Loading image" is gone. - Guition `online_image` `buffer_size` 16384 (was 4096) for `camera_image` and `alert_image`: the full picture in - about 1.8 s instead of 2.8 s on the bench; without Wi-Fi power save no loop over 50 ms. 32 KB held a loop 0.1 s. + about 1.8 s instead of 2.8 s on the bench; a loop over 50 ms is rare with Wi-Fi awake. 32 KB held a loop 0.1 s. - Alert picture: `alert_image_frame` has the alert card's radius (18) and `camera_show()` gives the alert's picture the frame's radius. LVGL 9.5 clips an image to its own radius row by row (`radius_only` in `lv_draw_sw_img.c`) without a layer, so `clip_corner` stays out (`tests/test_layer_free.py`). About 5 % more render time for the picture on the diff --git a/docs/TEST_RESULTS_0288.md b/docs/TEST_RESULTS_0288.md new file mode 100644 index 0000000..e7462e8 --- /dev/null +++ b/docs/TEST_RESULTS_0288.md @@ -0,0 +1,58 @@ +# Test results app 0.2.88 / firmware 0.2.74 (2026-09-19) + +The camera measurements of 0.2.87 showed the bench screens' Wi-Fi dozing: their Device Builder YAMLs date from before +app 0.2.24 and have no `power_save_mode`, so they ran ESPHome's default for the ESP32, `light` (`WIFI_PS_MIN_MODEM`: the +radio sleeps between the router's beacons, and what Home Assistant sends waits at the router until the next one). After +the pros and cons Max chose: "zet hem maar op none, voor iedereen vanaf nu voor altijd", with overriding still possible. + +## What changed + +`packages/core.yaml` has a `wifi:` block with one key, `power_save_mode: none`. ESPHome merges it under the screen's own +`wifi:` (the network, the password, the fallback hotspot stay there), and the screen's own YAML wins on any key it sets +(`merge_config` in `esphome/config_helpers.py`: the main config over every package, a later package over an earlier +one). The YAML ESP Screens writes has had the same line since app 0.2.24 and keeps it. The app's Override YAML still +refuses a `wifi:` section (`Firmware.PROTECTED_OVERRIDE_KEYS`), so the way to another mode is the screen's own YAML in +the ESPHome Device Builder. + +## Checked + +`esphome config` (ESPHome 2026.6.2) on copies of the bench screens' Device Builder YAMLs with the packages of this +branch: + +| YAML | `power_save_mode` after the merge | +|---|---| +| Guition Wallbox, as the Device Builder has it (no line) | `NONE` | +| the same with `power_save_mode: light` under `wifi:` | `LIGHT` | +| CYD, as the Device Builder has it (no line) | `NONE` | + +The Guition's build from its Device Builder YAML generates `set_power_save_mode(wifi::WIFI_POWER_SAVE_NONE)`. + +## Measured on the bench Guition + +Flashed over the air from its Device Builder YAML (no `power_save_mode` line) with this branch: + +| | fw 0.2.73, `light` | fw 0.2.74, `none` from the package | +|---|---|---| +| Ping from the Mac (30 × 0.2 s) | 103-191 ms, 117 ms average | 5-89 ms, 15 ms average | +| Camera, cold open | 4.55 s | 4.37 s | +| Camera, warm open | 2.06 s | 2.08 s | +| Loop over 50 ms during an open | 5 of 12 opens (58-107 ms) | 1 of 5 opens (101 ms) | + +With the 0.2.87 measurements of a power-save-off build (none in six opens), a loop waited over 50 ms in one of eleven +opens with the Wi-Fi awake. The camera times hardly change: the camera's own snapshot and the download dominate them. +The power the radio now spends listening was not measured; by Espressif's datasheets the receiver draws about +90-100 mA while it listens, a few tenths of a watt on a mains-powered screen. + +## Automated + +- `tools/check.sh`: Python 474 tests OK (new: `test_easy_package` "the Wi-Fi never dozes but stays the owner's": the + core's `wifi:` block holds only that key, no other package has one, the app's YAML still writes it), C++ 19/19, + packages, icons, editor tests, types, build and bundle PASS. +- `tools/check.sh --firmware --baseline 1678816`: CYD 1,678,816 B = 91.5 % (+0 B: the checkout profiles already set + `power_save_mode: none`), Guition 2,055,280 B = 25.3 %. + +## Not tested + +- The CYD on its panel: config check only. It gets the setting with its next **Update**. +- Studio 1: its YAML is in the studio's Home Assistant; it gets the setting with its next **Update** unless its own + YAML sets another mode. diff --git a/packages/boards/guition-4848s040.yaml b/packages/boards/guition-4848s040.yaml index c651cae..896dd7b 100644 --- a/packages/boards/guition-4848s040.yaml +++ b/packages/boards/guition-4848s040.yaml @@ -372,9 +372,9 @@ api: # Camera images (firmware 0.2.57+): ESP Screen Manager serves each image already sized for this screen, as an uncompressed # BMP on its own port on the LAN (screen_manager/app/camera_feed.py). ESPHome decodes a BMP piece by piece as it arrives, # one `buffer_size` per loop, where a JPEG of the full screen held the loop for 0.6 s; touch and drawing keep their turn. -# 16 KB a loop (firmware 0.2.73+): the full picture came in 1.8 s instead of 2.8 s with 4 KB. Without Wi-Fi power save no -# loop waited over 50 ms for it (with power save now and then 0.1 s, against 65 ms with 4 KB); 32 KB saved 0.3 s more but -# held a loop 0.1 s even without power save (docs/TEST_RESULTS_0287.md). One image full screen and one in an alert's +# 16 KB a loop (firmware 0.2.73+): the full picture came in 1.8 s instead of 2.8 s with 4 KB. A loop waited over 50 ms +# for it in one of eleven opens with Wi-Fi awake (0.1 s), in five of twelve with power save; 32 KB saved 0.3 s more but +# held a loop 0.1 s more often (docs/TEST_RESULTS_0287.md, 0288). One image full screen and one in an alert's # frame; runtime_tiles.h decides when they load and frees them when they close. Plain HTTP on the LAN, no certificates. http_request: timeout: 4s diff --git a/packages/core.yaml b/packages/core.yaml index 58f51c8..603f8da 100644 --- a/packages/core.yaml +++ b/packages/core.yaml @@ -14,7 +14,7 @@ # fonts/materialdesignicons-webfont.ttf (Apache 2.0, github.com/Templarian/MaterialDesign-Webfont). ############################################################ substitutions: - SCREEN_FIRMWARE_VERSION: "0.2.73" + SCREEN_FIRMWARE_VERSION: "0.2.74" DASHBOARD_INBOX_INTERNAL: "false" CALIBRATION_ON_BOOT: "false" # a bench build: start on the touch test page ROOM_NAME: "" # top left until ESP Screens sends the screen name @@ -261,6 +261,13 @@ logger: homeassistant.sensor: WARN homeassistant.text_sensor: WARN +# A screen hangs on the mains, so its Wi-Fi never dozes (firmware 0.2.74+). With ESPHome's default for the ESP32, +# `light`, every message from Home Assistant waits at the router for the next beacon: 76-117 ms on the bench screens +# against about 20 ms, and an image download held a loop up to 0.1 s now and then (docs/TEST_RESULTS_0287.md). The +# network and its password stay in the screen's own YAML, and a `power_save_mode` there wins over this one. +wifi: + power_save_mode: none + # Credentials are loaded from secrets.yaml — copy secrets.yaml.example to secrets.yaml and fill in your values. api: diff --git a/screen_manager/CHANGELOG.md b/screen_manager/CHANGELOG.md index 5b72b86..f51cc24 100644 --- a/screen_manager/CHANGELOG.md +++ b/screen_manager/CHANGELOG.md @@ -1,3 +1,10 @@ +## 0.2.88 (firmware 0.2.74) + +Wi-Fi that never dozes, on every screen. + +- **A screen keeps its Wi-Fi awake.** ESPHome lets an ESP32's Wi-Fi doze between the router's beacons unless the screen's YAML says otherwise. Then everything Home Assistant sends to the screen waits at the router for the next beacon: 76-117 ms on the bench screens against about 20 ms awake. That wait shows in the confirmation after a tap and in a lamp reporting its brightness. With the Wi-Fi awake, the screen also stalls less often for a moment while a camera picture comes in. The YAML ESP Screens writes has said `power_save_mode: none` since app 0.2.24, but older screens and YAMLs written by hand still dozed. The firmware now keeps the Wi-Fi awake itself; the screens hang on the mains, and it costs a few tenths of a watt. The network and its password stay in the screen's own YAML, and a `power_save_mode` there still wins. +- Needs firmware 0.2.74: press **Update** on the screen. Includes everything from 0.2.87. CYD firmware: 1,678,816 bytes, 91.5 % of the update slot, the same as 0.2.87. + ## 0.2.87 (firmware 0.2.73) A starting screen with a spinner, a camera that opens sooner with a spinner, and round corners on an alert's camera picture. diff --git a/screen_manager/app/core.py b/screen_manager/app/core.py index 21da973..80ec427 100644 --- a/screen_manager/app/core.py +++ b/screen_manager/app/core.py @@ -37,7 +37,7 @@ WEEKDAYS = ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'] REPO = 'https://github.com/MaxGramser/homeassistant_espscreen' REFS = {'cyd': 'main', 'guition': 'main'} # Firmware shipped with this app release; screens below it get an update offer. -FIRMWARE_VERSION = '0.2.73' +FIRMWARE_VERSION = '0.2.74' # The Auto standby switch a screen offers Home Assistant automations. AUTO_STANDBY_MIN_FIRMWARE = '0.2.41' # The settings page the screen opens itself, and the screen.settings tile that opens it. diff --git a/screen_manager/config.yaml b/screen_manager/config.yaml index 537c5b6..87aba62 100644 --- a/screen_manager/config.yaml +++ b/screen_manager/config.yaml @@ -1,5 +1,5 @@ name: ESP Screen Manager -version: "0.2.87" +version: "0.2.88" slug: esp_screen_manager description: Choose tiles for your CYD and Guition touchscreen, without reflashing. url: https://github.com/MaxGramser/homeassistant_espscreen diff --git a/tests/test_easy_package.py b/tests/test_easy_package.py index ec35bfc..aec66af 100644 --- a/tests/test_easy_package.py +++ b/tests/test_easy_package.py @@ -57,6 +57,21 @@ class PackageTests(unittest.TestCase): block = re.search(r'^' + section + r':\n.*?(?=^[a-zA-Z_]+:|\Z)', text, re.M | re.S) self.assertFalse(block and re.search(r'^ - platform: homeassistant$', block[0], re.M), f'{path.name}: a {section} subscribes to Home Assistant') + def test_the_wifi_never_dozes_but_stays_the_owners(self): + # Firmware 0.2.74+: the core keeps Wi-Fi awake for every screen, and nothing else of Wi-Fi lives in a package; + # the network, its password and the fallback hotspot stay in the screen's own YAML, which also wins on the mode. + for path in sorted((ROOT / 'packages').rglob('*.yaml')): + text = path.read_text() + block = re.search(r'^wifi:\n(.*?)(?=^[a-zA-Z_]+:|\Z)', text, re.M | re.S) + if path == profiles.CORE: + self.assertIsNotNone(block, 'packages/core.yaml has no wifi: block') + lines = [line for line in block[1].split('\n') if line.strip() and not line.lstrip().startswith('#')] + self.assertEqual(lines, [' power_save_mode: none']) + else: + self.assertIsNone(block, f'{path.name} has a wifi: block') + # The YAML ESP Screens writes says so too (since app 0.2.24), so an older package changes nothing for those. + self.assertIn(' power_save_mode: none\n', (ROOT / 'screen_manager/app/core.py').read_text()) + def test_the_manual_profile_is_gone_from_profiles_and_packages(self): for board, name in BOARDS.items(): for entry in (name, f'packages/{board}.yaml'):