From 8bbe0104fa8828c5fe03005a946b1a6245027ab0 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sun, 25 Oct 2026 08:21:50 -0700 Subject: [PATCH 1/5] Disable the Reboot Timeouts on the Example Devices An example device flashed to a bench unit is never adopted by Home Assistant, so the API's default 15 minute reboot_timeout restarted it every 15 minutes. Each example device now sets api_reboot_timeout and wifi_reboot_timeout to 0s, and the change-detection job fails one that does not, so a new example device cannot regress. --- .github/workflows/test-pull-request.yml | 9 +++++++++ HISTORY.md | 1 + OPERATIONS.md | 1 + test/adafruit-esp32-s3-feather.yaml | 2 ++ test/aoycocr-x5p.yaml | 2 ++ test/apollo-plt-1b.yaml | 2 ++ test/easystart.yaml | 2 ++ test/efun-sh331.yaml | 2 ++ test/elecrow-thinknode-m7.yaml | 2 ++ test/esp32-s3-devkitc.yaml | 2 ++ test/esp32-s3-wroom-1-n16r8.yaml | 2 ++ test/esp32-s3-wroom-2-n16r8v.yaml | 2 ++ test/esp32-s3-wroom-2-n32r8v.yaml | 2 ++ test/gls10-bluetooth-proxy.yaml | 2 ++ test/heltec-wifi-lora32-v4-r8.yaml | 2 ++ test/kincony-kc868-asr.yaml | 2 ++ test/konnected-blaq.yaml | 2 ++ test/m5stack-cores3.yaml | 2 ++ test/m5stack-dial.yaml | 2 ++ test/norvi-enet-ae06-r.yaml | 2 ++ test/rocket-astra.yaml | 2 ++ test/smarthome-ceilsense.yaml | 2 ++ test/sonoff-s31.yaml | 2 ++ test/sonoff-th10.yaml | 2 ++ test/unexpectedmaker-pros3d.yaml | 2 ++ test/waveshare-esp32-s3-eth.yaml | 2 ++ test/wemos-lolin32-lite.yaml | 2 ++ 27 files changed, 59 insertions(+) diff --git a/.github/workflows/test-pull-request.yml b/.github/workflows/test-pull-request.yml index 41021bb..4b09c61 100644 --- a/.github/workflows/test-pull-request.yml +++ b/.github/workflows/test-pull-request.yml @@ -105,6 +105,15 @@ jobs: echo "Templates that no example device in test/ includes: ${missing[*]}" exit 1 fi + # A flashed example device runs on a bench with no Home Assistant, so it must not reboot for lack of one. + rebooting=() + for path in test/*.yaml; do + grep -qE "^ api_reboot_timeout: 0s$" "$path" && grep -qE "^ wifi_reboot_timeout: 0s$" "$path" || rebooting+=("$path") + done + if (( ${#rebooting[@]} > 0 )); then + echo "Example devices not setting api_reboot_timeout and wifi_reboot_timeout to 0s: ${rebooting[*]}" + exit 1 + fi CONFIGS="$(basename -a -s .yaml test/*.yaml | jq -Rsc 'split("\n") | map(select(length > 0))')" echo "Template matrix: $CONFIGS" echo "template-configs=$CONFIGS" >> "$GITHUB_OUTPUT" diff --git a/HISTORY.md b/HISTORY.md index 30041f9..be89848 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -8,6 +8,7 @@ ESPHome configuration templates and projects. - Added M5Stack CoreS3 support, with an LVGL touch screen example device. - Added M5Stack GC0308 camera overlay support. - Added M5Stack Dial support, with an LVGL and rotary encoder example device. + - Changed the example devices to disable the API and WiFi reboot timeouts, so a flashed one runs on a bench without Home Assistant. - Version 2.3: - Changed OTA to encryption only, using the API key, so only the upgrade bridge build still needs the `ota_password` secret. - Fixed the compile warnings the templates and the EasyStart component can address. diff --git a/OPERATIONS.md b/OPERATIONS.md index b2e178d..6b1a8d1 100644 --- a/OPERATIONS.md +++ b/OPERATIONS.md @@ -134,6 +134,7 @@ fi - **Point `config_dir` at a checkout rather than at the deployed tree.** The dashboard's container already serves that tree, so compile it there with `docker exec` rather than in a second container. - **`latest` gives CI parity, not dashboard parity.** CI pulls `latest` fresh on every run, so run `docker pull ptr727/esphome-nonroot:latest` first for parity. That can be newer than the dashboard's last pull. Before an install, rerun with the image tag set to the dashboard's ESPHome version, per [Running the ESPHome CLI][running-the-esphome-cli]. The image's Docker Hub tag list carries each ESPHome release as a tag, such as `2026.9.1`. - **Compile a [`test/`][test] example device to gate a template change.** A top-level device config works the same way, but the example device is what CI builds. +- **An example device sets `api_reboot_timeout` and `wifi_reboot_timeout` to `0s`.** A flashed one then keeps running on a bench with no Home Assistant and no network, rather than rebooting every 15 minutes. CI's change-detection job fails an example device that leaves either one out. - **`secrets.yaml` belongs at the repository root**, not beside the config being compiled, because [`templates/secrets.yaml`][secrets-template] re-exports the root file and every template resolves `!secret` through it. Generate one from [`secrets._yaml`][secrets-example], and the guard above keeps a real one from being overwritten. - **`/entrypoint/cache.sh` runs first**, and it prepares the `/cache` mount the image expects. - **`/cache` holds the toolchain and the build tree, so keep it between runs.** A fresh directory each time makes every local compile a cold ESP-IDF build of several minutes. It also holds ESPHome's ccache store, `home/.cache/esphome/idf/ccache`, so a kept directory turns most repeat compiles into cache hits. CI uses one regardless, since it discards the runner. diff --git a/test/adafruit-esp32-s3-feather.yaml b/test/adafruit-esp32-s3-feather.yaml index a76f0cb..488f2da 100644 --- a/test/adafruit-esp32-s3-feather.yaml +++ b/test/adafruit-esp32-s3-feather.yaml @@ -2,6 +2,8 @@ substitutions: device_name: adafruit-esp32-s3-feather-test device_friendly_name: "Adafruit ESP32-S3 Feather Test" + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/adafruit-esp32-s3-feather.yaml diff --git a/test/aoycocr-x5p.yaml b/test/aoycocr-x5p.yaml index a258275..cd3f202 100644 --- a/test/aoycocr-x5p.yaml +++ b/test/aoycocr-x5p.yaml @@ -2,6 +2,8 @@ substitutions: device_name: aoycocr-x5p-test device_friendly_name: "Aoycocr X5P Test" + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/aoycocr-x5p.yaml diff --git a/test/apollo-plt-1b.yaml b/test/apollo-plt-1b.yaml index 2c60dc3..25249f0 100644 --- a/test/apollo-plt-1b.yaml +++ b/test/apollo-plt-1b.yaml @@ -2,6 +2,8 @@ substitutions: device_name: apollo-plt-1b-test device_friendly_name: "Apollo PLT-1B Test" + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/apollo-plt-1b.yaml diff --git a/test/easystart.yaml b/test/easystart.yaml index a4b0ada..7e4c2f9 100644 --- a/test/easystart.yaml +++ b/test/easystart.yaml @@ -5,6 +5,8 @@ substitutions: device_name: easystart-test device_friendly_name: "EasyStart Test" + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/waveshare-esp32-s3-eth.yaml diff --git a/test/efun-sh331.yaml b/test/efun-sh331.yaml index d2db792..206c34d 100644 --- a/test/efun-sh331.yaml +++ b/test/efun-sh331.yaml @@ -2,6 +2,8 @@ substitutions: device_name: efun-sh331-test device_friendly_name: "EFUN SH331 Test" + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/efun-sh331.yaml diff --git a/test/elecrow-thinknode-m7.yaml b/test/elecrow-thinknode-m7.yaml index 23e75ba..b964c92 100644 --- a/test/elecrow-thinknode-m7.yaml +++ b/test/elecrow-thinknode-m7.yaml @@ -2,6 +2,8 @@ substitutions: device_name: elecrow-thinknode-m7-test device_friendly_name: "Elecrow ThinkNode M7 Test" + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/elecrow-thinknode-m7.yaml diff --git a/test/esp32-s3-devkitc.yaml b/test/esp32-s3-devkitc.yaml index 9fd858b..49e1d65 100644 --- a/test/esp32-s3-devkitc.yaml +++ b/test/esp32-s3-devkitc.yaml @@ -2,6 +2,8 @@ substitutions: device_name: esp32-s3-devkitc-test device_friendly_name: "ESP32-S3-DevKitC Test" + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/esp32-s3-devkitc.yaml diff --git a/test/esp32-s3-wroom-1-n16r8.yaml b/test/esp32-s3-wroom-1-n16r8.yaml index 901f96a..baa65e2 100644 --- a/test/esp32-s3-wroom-1-n16r8.yaml +++ b/test/esp32-s3-wroom-1-n16r8.yaml @@ -6,6 +6,8 @@ substitutions: device_friendly_name: "ESP32-S3-WROOM-1-N16R8 Test" # Third-party N16R8 boards commonly follow the DevKitC-1 v1.0 layout, RGB LED on GPIO48 not GPIO38. rgb_led_pin: GPIO48 + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/esp32-s3-devkitc.yaml diff --git a/test/esp32-s3-wroom-2-n16r8v.yaml b/test/esp32-s3-wroom-2-n16r8v.yaml index 0a98bea..7241a07 100644 --- a/test/esp32-s3-wroom-2-n16r8v.yaml +++ b/test/esp32-s3-wroom-2-n16r8v.yaml @@ -4,6 +4,8 @@ substitutions: device_name: esp32-s3-wroom2-n16r8v-test device_friendly_name: "ESP32-S3-WROOM-2-N16R8V Test" + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/esp32-s3-devkitc.yaml diff --git a/test/esp32-s3-wroom-2-n32r8v.yaml b/test/esp32-s3-wroom-2-n32r8v.yaml index aeb6adb..cc70123 100644 --- a/test/esp32-s3-wroom-2-n32r8v.yaml +++ b/test/esp32-s3-wroom-2-n32r8v.yaml @@ -4,6 +4,8 @@ substitutions: device_name: esp32-s3-wroom2-n32r8v-test device_friendly_name: "ESP32-S3-WROOM-2-N32R8V Test" + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/esp32-s3-devkitc.yaml diff --git a/test/gls10-bluetooth-proxy.yaml b/test/gls10-bluetooth-proxy.yaml index 3f4ecf3..3a1a2c8 100644 --- a/test/gls10-bluetooth-proxy.yaml +++ b/test/gls10-bluetooth-proxy.yaml @@ -2,6 +2,8 @@ substitutions: device_name: gls10-bluetooth-proxy-test device_friendly_name: "GL-S10 Bluetooth Proxy Test" + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/gls10-bluetooth-proxy.yaml diff --git a/test/heltec-wifi-lora32-v4-r8.yaml b/test/heltec-wifi-lora32-v4-r8.yaml index 2d17e91..c56e2b3 100644 --- a/test/heltec-wifi-lora32-v4-r8.yaml +++ b/test/heltec-wifi-lora32-v4-r8.yaml @@ -3,6 +3,8 @@ substitutions: device_name: heltec-wifi-lora32-v4-r8-test device_friendly_name: "Heltec WiFi LoRa 32 V4-R8 Test" + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/heltec-wifi-lora32-v4-r8.yaml diff --git a/test/kincony-kc868-asr.yaml b/test/kincony-kc868-asr.yaml index c6d48bf..4a774c4 100644 --- a/test/kincony-kc868-asr.yaml +++ b/test/kincony-kc868-asr.yaml @@ -2,6 +2,8 @@ substitutions: device_name: kincony-kc868-asr-test device_friendly_name: "Kincony KC868-ASR Test" + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/kincony-kc868-asr.yaml diff --git a/test/konnected-blaq.yaml b/test/konnected-blaq.yaml index 94ea609..29b7770 100644 --- a/test/konnected-blaq.yaml +++ b/test/konnected-blaq.yaml @@ -2,6 +2,8 @@ substitutions: device_name: konnected-blaq-test device_friendly_name: "Konnected blaQ Test" + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/konnected-blaq.yaml diff --git a/test/m5stack-cores3.yaml b/test/m5stack-cores3.yaml index c4121c5..53daa39 100644 --- a/test/m5stack-cores3.yaml +++ b/test/m5stack-cores3.yaml @@ -3,6 +3,8 @@ substitutions: device_name: m5stack-cores3-test device_friendly_name: "M5Stack CoreS3 Test" + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/m5stack-cores3.yaml diff --git a/test/m5stack-dial.yaml b/test/m5stack-dial.yaml index 6bbc4a6..5f6fd38 100644 --- a/test/m5stack-dial.yaml +++ b/test/m5stack-dial.yaml @@ -4,6 +4,8 @@ substitutions: device_name: m5stack-dial-test device_friendly_name: "M5Stack Dial Test" + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/m5stack-dial.yaml diff --git a/test/norvi-enet-ae06-r.yaml b/test/norvi-enet-ae06-r.yaml index 384d21b..f4ddf65 100644 --- a/test/norvi-enet-ae06-r.yaml +++ b/test/norvi-enet-ae06-r.yaml @@ -2,6 +2,8 @@ substitutions: device_name: norvi-enet-ae06-r-test device_friendly_name: "Norvi ENET-AE06-R Test" + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/norvi-enet-ae06-r.yaml diff --git a/test/rocket-astra.yaml b/test/rocket-astra.yaml index 4a83b68..cf79d5e 100644 --- a/test/rocket-astra.yaml +++ b/test/rocket-astra.yaml @@ -2,6 +2,8 @@ substitutions: device_name: rocket-astra-test device_friendly_name: "RocketController ASTRA Test" + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/rocket-astra.yaml diff --git a/test/smarthome-ceilsense.yaml b/test/smarthome-ceilsense.yaml index 99dd488..c68412d 100644 --- a/test/smarthome-ceilsense.yaml +++ b/test/smarthome-ceilsense.yaml @@ -2,6 +2,8 @@ substitutions: device_name: smarthome-ceilsense-test device_friendly_name: "SmartHomeShop CeilSense Test" + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/smarthome-ceilsense.yaml diff --git a/test/sonoff-s31.yaml b/test/sonoff-s31.yaml index 3c15c39..fb64bfc 100644 --- a/test/sonoff-s31.yaml +++ b/test/sonoff-s31.yaml @@ -2,6 +2,8 @@ substitutions: device_name: sonoff-s31-test device_friendly_name: "Sonoff S31 Test" + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/sonoff-s31.yaml diff --git a/test/sonoff-th10.yaml b/test/sonoff-th10.yaml index 10ff2c2..2eaa176 100644 --- a/test/sonoff-th10.yaml +++ b/test/sonoff-th10.yaml @@ -2,6 +2,8 @@ substitutions: device_name: sonoff-th10-test device_friendly_name: "Sonoff TH10 Test" + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/sonoff-th10.yaml diff --git a/test/unexpectedmaker-pros3d.yaml b/test/unexpectedmaker-pros3d.yaml index b4bb6b1..9b8fc7e 100644 --- a/test/unexpectedmaker-pros3d.yaml +++ b/test/unexpectedmaker-pros3d.yaml @@ -2,6 +2,8 @@ substitutions: device_name: unexpectedmaker-pros3d-test device_friendly_name: "Unexpected Maker ProS3D Test" + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/unexpectedmaker-pros3d.yaml diff --git a/test/waveshare-esp32-s3-eth.yaml b/test/waveshare-esp32-s3-eth.yaml index aef3103..6a3c8f0 100644 --- a/test/waveshare-esp32-s3-eth.yaml +++ b/test/waveshare-esp32-s3-eth.yaml @@ -2,6 +2,8 @@ substitutions: device_name: waveshare-esp32-s3-eth-test device_friendly_name: "Waveshare ESP32-S3-ETH Test" + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/waveshare-esp32-s3-eth.yaml diff --git a/test/wemos-lolin32-lite.yaml b/test/wemos-lolin32-lite.yaml index 8aa8b36..be2996d 100644 --- a/test/wemos-lolin32-lite.yaml +++ b/test/wemos-lolin32-lite.yaml @@ -2,6 +2,8 @@ substitutions: device_name: wemos-lolin32-lite-test device_friendly_name: "WEMOS LOLIN32 Lite Test" + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/wemos-lolin32-lite.yaml From 0f9dcaaa59f441727fe163ec70f2d3cea88e9c58 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sun, 25 Oct 2026 08:25:15 -0700 Subject: [PATCH 2/5] Accept a Quoted Timeout and Mirror the Release Note in the README --- .github/workflows/test-pull-request.yml | 4 ++-- OPERATIONS.md | 4 ++-- README.md | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test-pull-request.yml b/.github/workflows/test-pull-request.yml index 4b09c61..69fc612 100644 --- a/.github/workflows/test-pull-request.yml +++ b/.github/workflows/test-pull-request.yml @@ -105,10 +105,10 @@ jobs: echo "Templates that no example device in test/ includes: ${missing[*]}" exit 1 fi - # A flashed example device runs on a bench with no Home Assistant, so it must not reboot for lack of one. + # A flashed example device runs on a bench with no Home Assistant and maybe no network, so it must reboot for lack of neither. rebooting=() for path in test/*.yaml; do - grep -qE "^ api_reboot_timeout: 0s$" "$path" && grep -qE "^ wifi_reboot_timeout: 0s$" "$path" || rebooting+=("$path") + grep -qE '^ api_reboot_timeout: "?0s"?( *#.*)?$' "$path" && grep -qE '^ wifi_reboot_timeout: "?0s"?( *#.*)?$' "$path" || rebooting+=("$path") done if (( ${#rebooting[@]} > 0 )); then echo "Example devices not setting api_reboot_timeout and wifi_reboot_timeout to 0s: ${rebooting[*]}" diff --git a/OPERATIONS.md b/OPERATIONS.md index 6b1a8d1..06b3243 100644 --- a/OPERATIONS.md +++ b/OPERATIONS.md @@ -134,7 +134,7 @@ fi - **Point `config_dir` at a checkout rather than at the deployed tree.** The dashboard's container already serves that tree, so compile it there with `docker exec` rather than in a second container. - **`latest` gives CI parity, not dashboard parity.** CI pulls `latest` fresh on every run, so run `docker pull ptr727/esphome-nonroot:latest` first for parity. That can be newer than the dashboard's last pull. Before an install, rerun with the image tag set to the dashboard's ESPHome version, per [Running the ESPHome CLI][running-the-esphome-cli]. The image's Docker Hub tag list carries each ESPHome release as a tag, such as `2026.9.1`. - **Compile a [`test/`][test] example device to gate a template change.** A top-level device config works the same way, but the example device is what CI builds. -- **An example device sets `api_reboot_timeout` and `wifi_reboot_timeout` to `0s`.** A flashed one then keeps running on a bench with no Home Assistant and no network, rather than rebooting every 15 minutes. CI's change-detection job fails an example device that leaves either one out. +- **An example device sets `api_reboot_timeout` and `wifi_reboot_timeout` to `0s`.** A flashed one then keeps running on a bench with no Home Assistant and no network, rather than rebooting every 15 minutes. Every template that sets a reboot timeout reads the matching substitution. An ethernet board carries no `wifi:` block, so the WiFi half does nothing there. CI's change-detection job fails an example device that leaves either one out. - **`secrets.yaml` belongs at the repository root**, not beside the config being compiled, because [`templates/secrets.yaml`][secrets-template] re-exports the root file and every template resolves `!secret` through it. Generate one from [`secrets._yaml`][secrets-example], and the guard above keeps a real one from being overwritten. - **`/entrypoint/cache.sh` runs first**, and it prepares the `/cache` mount the image expects. - **`/cache` holds the toolchain and the build tree, so keep it between runs.** A fresh directory each time makes every local compile a cold ESP-IDF build of several minutes. It also holds ESPHome's ccache store, `home/.cache/esphome/idf/ccache`, so a kept directory turns most repeat compiles into cache hits. CI uses one regardless, since it discards the runner. @@ -304,7 +304,7 @@ Do not `!remove` blocks from Apollo's package without checking what depends on t - **The 15% figure applies to a raw count read against the hardware scale.** A framework that normalizes a calibrated reading onto a fixed 1100mV scale is a separate case, and its constant is correct there. Check which scale a count is on before applying either number. - **The status LED is dark when healthy, deliberately.** ESPHome drives the pin low when healthy, which lights an active low LED. This board's LED is active high and bright white on a unit that runs from a cell. It blinks through a PWM output capped at `status_led_brightness`, 50% by default, since full drive glares. - **The SX1262 cannot be driven through ESPHome on this board.** The `sx126x` component reaches an RF switch only through the radio's own DIO2. The KCT8103L front-end is wired to three ESP32 pins instead, one selecting the transmit or receive path per packet. Those pins are documented and left unclaimed. -- **A bench unit needs both reboot timeouts disabled.** Override `api_reboot_timeout` and `wifi_reboot_timeout` to `0s`. Every template that sets a reboot timeout reads the matching substitution, so this reaches far more than this board. An ethernet board carries no `wifi:` block, so the WiFi half does nothing there. A unit on a desk with no Home Assistant restarts part way through a cold GNSS acquisition. +- **A bench unit needs both reboot timeouts disabled**, as every example device does per [Compiling the Way CI Does][compiling-the-way-ci-does]. On this board a unit on a desk with no Home Assistant otherwise restarts part way through a cold GNSS acquisition. ### M5Stack CoreS3 diff --git a/README.md b/README.md index 7fbc3b7..cffd04a 100755 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ ESPHome configuration templates and projects. - Added M5Stack CoreS3 support, with an LVGL touch screen example device. - Added M5Stack GC0308 camera overlay support. - Added M5Stack Dial support, with an LVGL and rotary encoder example device. +- Changed the example devices to disable the API and WiFi reboot timeouts, so a flashed one runs on a bench without Home Assistant. See [Release History][history] for complete release notes and older versions. From af2456f53fc08faa655ba0e14eff2aadbc30a81d Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sun, 25 Oct 2026 08:26:12 -0700 Subject: [PATCH 3/5] Correct the Reboot Timeout Comment and the Ethernet Board Claim --- .github/workflows/test-pull-request.yml | 2 +- OPERATIONS.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-pull-request.yml b/.github/workflows/test-pull-request.yml index 69fc612..aa92790 100644 --- a/.github/workflows/test-pull-request.yml +++ b/.github/workflows/test-pull-request.yml @@ -105,7 +105,7 @@ jobs: echo "Templates that no example device in test/ includes: ${missing[*]}" exit 1 fi - # A flashed example device runs on a bench with no Home Assistant and maybe no network, so it must reboot for lack of neither. + # A flashed example device runs on a bench with no Home Assistant and maybe no network, so it must not reboot for lack of either. rebooting=() for path in test/*.yaml; do grep -qE '^ api_reboot_timeout: "?0s"?( *#.*)?$' "$path" && grep -qE '^ wifi_reboot_timeout: "?0s"?( *#.*)?$' "$path" || rebooting+=("$path") diff --git a/OPERATIONS.md b/OPERATIONS.md index 06b3243..308ef9c 100644 --- a/OPERATIONS.md +++ b/OPERATIONS.md @@ -134,7 +134,7 @@ fi - **Point `config_dir` at a checkout rather than at the deployed tree.** The dashboard's container already serves that tree, so compile it there with `docker exec` rather than in a second container. - **`latest` gives CI parity, not dashboard parity.** CI pulls `latest` fresh on every run, so run `docker pull ptr727/esphome-nonroot:latest` first for parity. That can be newer than the dashboard's last pull. Before an install, rerun with the image tag set to the dashboard's ESPHome version, per [Running the ESPHome CLI][running-the-esphome-cli]. The image's Docker Hub tag list carries each ESPHome release as a tag, such as `2026.9.1`. - **Compile a [`test/`][test] example device to gate a template change.** A top-level device config works the same way, but the example device is what CI builds. -- **An example device sets `api_reboot_timeout` and `wifi_reboot_timeout` to `0s`.** A flashed one then keeps running on a bench with no Home Assistant and no network, rather than rebooting every 15 minutes. Every template that sets a reboot timeout reads the matching substitution. An ethernet board carries no `wifi:` block, so the WiFi half does nothing there. CI's change-detection job fails an example device that leaves either one out. +- **An example device sets `api_reboot_timeout` and `wifi_reboot_timeout` to `0s`.** A flashed one then keeps running on a bench with no Home Assistant and no network, rather than rebooting every 15 minutes. Every template that sets a reboot timeout reads the matching substitution. A template with no `wifi:` block, such as an ethernet-only board's, ignores the WiFi half. CI's change-detection job fails an example device that leaves either one out. - **`secrets.yaml` belongs at the repository root**, not beside the config being compiled, because [`templates/secrets.yaml`][secrets-template] re-exports the root file and every template resolves `!secret` through it. Generate one from [`secrets._yaml`][secrets-example], and the guard above keeps a real one from being overwritten. - **`/entrypoint/cache.sh` runs first**, and it prepares the `/cache` mount the image expects. - **`/cache` holds the toolchain and the build tree, so keep it between runs.** A fresh directory each time makes every local compile a cold ESP-IDF build of several minutes. It also holds ESPHome's ccache store, `home/.cache/esphome/idf/ccache`, so a kept directory turns most repeat compiles into cache hits. CI uses one regardless, since it discards the runner. From daa50e4305d081dba70146c4b6fb1f7610732d9c Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sun, 25 Oct 2026 08:39:33 -0700 Subject: [PATCH 4/5] Accept a Single-Quoted Zero Timeout in the Example Device Guard --- .github/workflows/test-pull-request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-pull-request.yml b/.github/workflows/test-pull-request.yml index aa92790..a82c2f4 100644 --- a/.github/workflows/test-pull-request.yml +++ b/.github/workflows/test-pull-request.yml @@ -108,7 +108,7 @@ jobs: # A flashed example device runs on a bench with no Home Assistant and maybe no network, so it must not reboot for lack of either. rebooting=() for path in test/*.yaml; do - grep -qE '^ api_reboot_timeout: "?0s"?( *#.*)?$' "$path" && grep -qE '^ wifi_reboot_timeout: "?0s"?( *#.*)?$' "$path" || rebooting+=("$path") + grep -qE '^ api_reboot_timeout: ["\x27]?0s["\x27]?( *#.*)?$' "$path" && grep -qE '^ wifi_reboot_timeout: ["\x27]?0s["\x27]?( *#.*)?$' "$path" || rebooting+=("$path") done if (( ${#rebooting[@]} > 0 )); then echo "Example devices not setting api_reboot_timeout and wifi_reboot_timeout to 0s: ${rebooting[*]}" From 2b66e8f139ed5244e49e8162247aae8513571b5d Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sun, 25 Oct 2026 08:40:53 -0700 Subject: [PATCH 5/5] Match a Real Single Quote in the Example Device Guard --- .github/workflows/test-pull-request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-pull-request.yml b/.github/workflows/test-pull-request.yml index a82c2f4..1c579c5 100644 --- a/.github/workflows/test-pull-request.yml +++ b/.github/workflows/test-pull-request.yml @@ -108,7 +108,7 @@ jobs: # A flashed example device runs on a bench with no Home Assistant and maybe no network, so it must not reboot for lack of either. rebooting=() for path in test/*.yaml; do - grep -qE '^ api_reboot_timeout: ["\x27]?0s["\x27]?( *#.*)?$' "$path" && grep -qE '^ wifi_reboot_timeout: ["\x27]?0s["\x27]?( *#.*)?$' "$path" || rebooting+=("$path") + grep -qE "^ api_reboot_timeout: [\"']?0s[\"']?( *#.*)?\$" "$path" && grep -qE "^ wifi_reboot_timeout: [\"']?0s[\"']?( *#.*)?\$" "$path" || rebooting+=("$path") done if (( ${#rebooting[@]} > 0 )); then echo "Example devices not setting api_reboot_timeout and wifi_reboot_timeout to 0s: ${rebooting[*]}"