From 4095eaccac96278bfbfb1fb98876b036f79a1684 Mon Sep 17 00:00:00 2001 From: Brandon Harvey <8107750+bharvey88@users.noreply.github.com> Date: Sat, 4 Jul 2026 15:42:03 -0500 Subject: [PATCH 1/6] Add Firmware Channel and Bluetooth Proxy selects with OTA source script MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Selects compose the update manifest URL (Stable = GitHub Pages, Beta = rolling beta release assets); boot hook syncs the Bluetooth Proxy select to the firmware actually running. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- Integrations/ESPHome/Core.yaml | 73 ++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/Integrations/ESPHome/Core.yaml b/Integrations/ESPHome/Core.yaml index 1250e1d..5714665 100644 --- a/Integrations/ESPHome/Core.yaml +++ b/Integrations/ESPHome/Core.yaml @@ -6,6 +6,27 @@ substitutions: # `substitutions: { ota_password: !secret _ota_password }` so each # device on your network uses a unique secret instead of the shared default. ota_password: "apolloautomation" + # Firmware variant identity: overridden to "true" by AIR-1_BLE.yaml so the + # Bluetooth Proxy select can self-correct to what is actually running. + ble_firmware: "false" + # Manifest URL bases. Stable = GitHub Pages (main branch builds). + # Beta = rolling "beta" pre-release assets (beta branch builds). + stable_manifest_base: "https://apolloautomation.github.io/AIR-1" + beta_manifest_base: "https://github.com/ApolloAutomation/AIR-1/releases/download/beta" + +esphome: + on_boot: + priority: -100 + then: + # The Bluetooth Proxy select mirrors the firmware actually running, so a + # failed or abandoned switch snaps back to the truth on reboot. + - lambda: |- + if (std::string("${ble_firmware}") == "true") { + id(firmware_ble).publish_state("Enabled"); + } else { + id(firmware_ble).publish_state("Disabled"); + } + - script.execute: apply_ota_source esp32: variant: esp32c3 @@ -536,7 +557,59 @@ select: then: - script.execute: update_air_quality_led + - platform: template + name: "Firmware Channel" + id: firmware_channel + icon: mdi:source-branch + entity_category: "config" + optimistic: true + restore_value: true + options: + - "Stable" + - "Beta" + initial_option: "Stable" + on_value: + then: + - script.execute: apply_ota_source + + - platform: template + name: "Bluetooth Proxy" + id: firmware_ble + icon: mdi:bluetooth + entity_category: "config" + optimistic: true + restore_value: true + options: + - "Disabled" + - "Enabled" + initial_option: "Disabled" + on_value: + then: + - script.execute: apply_ota_source + script: + - id: apply_ota_source + # Sets the OTA manifest URL from the two selectors: Bluetooth Proxy + # (Disabled/Enabled) x Firmware Channel (Stable/Beta). + # Stable = GitHub Pages, Beta = rolling "beta" release assets. + then: + - lambda: |- + const bool ble = id(firmware_ble).state == "Enabled"; + const bool beta = id(firmware_channel).state == "Beta"; + std::string url; + if (beta) { + url = ble + ? "${beta_manifest_base}/manifest-ble.json" + : "${beta_manifest_base}/manifest-factory.json"; + } else { + url = ble + ? "${stable_manifest_base}/firmware-ble/manifest.json" + : "${stable_manifest_base}/firmware/manifest.json"; + } + ESP_LOGI("firmware", "OTA manifest set to: %s", url.c_str()); + id(update_http_request).set_source_url(url); + - component.update: update_http_request + - id: setCo2AutoCalibration mode: restart parameters: From ad6e052a57af6d1169b045813a635cda99ee54bd Mon Sep 17 00:00:00 2001 From: Brandon Harvey <8107750+bharvey88@users.noreply.github.com> Date: Sat, 4 Jul 2026 15:45:41 -0500 Subject: [PATCH 2/6] Add Firmware Update button and boot-time firmware identity sync MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The button force-installs whatever the Firmware Channel and Bluetooth Proxy selects point at (needed when switching variant/channel at the same version). Variant on_boot blocks converted to list form so the Core.yaml boot-sync automation merges instead of being replaced. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- Integrations/ESPHome/AIR-1.yaml | 33 ++++++++-------- Integrations/ESPHome/AIR-1_BLE.yaml | 30 ++++++++------- Integrations/ESPHome/Core.yaml | 59 +++++++++++++++++++++++------ 3 files changed, 82 insertions(+), 40 deletions(-) diff --git a/Integrations/ESPHome/AIR-1.yaml b/Integrations/ESPHome/AIR-1.yaml index 479748c..18ed8bc 100644 --- a/Integrations/ESPHome/AIR-1.yaml +++ b/Integrations/ESPHome/AIR-1.yaml @@ -8,23 +8,24 @@ esphome: version: "${version}" min_version: 2025.11.0 + # List form so Core.yaml's on_boot entries merge in (see Core.yaml). on_boot: - priority: 500 - then: - - text_sensor.template.publish: - id: apollo_firmware_version - state: "${version}" - - lambda: |- - id(deep_sleep_1).set_sleep_duration(id(deep_sleep_sleep_duration).state * 60 * 1000); - - if: - condition: - or: - - binary_sensor.is_on: ota_mode - - switch.is_on: prevent_sleep - then: - - lambda: |- - ESP_LOGW("Apollo", "Preventing Deep Sleep Due To OTA On Boot"); - id(deep_sleep_1).prevent_deep_sleep(); + - priority: 500 + then: + - text_sensor.template.publish: + id: apollo_firmware_version + state: "${version}" + - lambda: |- + id(deep_sleep_1).set_sleep_duration(id(deep_sleep_sleep_duration).state * 60 * 1000); + - if: + condition: + or: + - binary_sensor.is_on: ota_mode + - switch.is_on: prevent_sleep + then: + - lambda: |- + ESP_LOGW("Apollo", "Preventing Deep Sleep Due To OTA On Boot"); + id(deep_sleep_1).prevent_deep_sleep(); on_shutdown: - light.turn_off: rgb_light diff --git a/Integrations/ESPHome/AIR-1_BLE.yaml b/Integrations/ESPHome/AIR-1_BLE.yaml index 2f3ae53..5732128 100644 --- a/Integrations/ESPHome/AIR-1_BLE.yaml +++ b/Integrations/ESPHome/AIR-1_BLE.yaml @@ -1,3 +1,6 @@ +substitutions: + ble_firmware: "true" + esphome: name: "${name}" friendly_name: Apollo AIR-1 @@ -8,20 +11,21 @@ esphome: version: "${version}" min_version: 2025.11.0 + # List form so Core.yaml's on_boot entries merge in (see Core.yaml). on_boot: - priority: 500 - then: - - lambda: |- - id(deep_sleep_1).set_sleep_duration(id(deep_sleep_sleep_duration).state * 60 * 60 * 1000); - - if: - condition: - or: - - binary_sensor.is_on: ota_mode - - switch.is_on: prevent_sleep - then: - - lambda: |- - ESP_LOGW("Apollo", "Preventing Deep Sleep Due To OTA On Boot"); - id(deep_sleep_1).prevent_deep_sleep(); + - priority: 500 + then: + - lambda: |- + id(deep_sleep_1).set_sleep_duration(id(deep_sleep_sleep_duration).state * 60 * 60 * 1000); + - if: + condition: + or: + - binary_sensor.is_on: ota_mode + - switch.is_on: prevent_sleep + then: + - lambda: |- + ESP_LOGW("Apollo", "Preventing Deep Sleep Due To OTA On Boot"); + id(deep_sleep_1).prevent_deep_sleep(); on_shutdown: - light.turn_off: rgb_light diff --git a/Integrations/ESPHome/Core.yaml b/Integrations/ESPHome/Core.yaml index 5714665..ea46b6b 100644 --- a/Integrations/ESPHome/Core.yaml +++ b/Integrations/ESPHome/Core.yaml @@ -15,18 +15,20 @@ substitutions: beta_manifest_base: "https://github.com/ApolloAutomation/AIR-1/releases/download/beta" esphome: + # List form so package merging concatenates with each variant's own on_boot + # entries (mapping form would be replaced by the variant's block instead). on_boot: - priority: -100 - then: - # The Bluetooth Proxy select mirrors the firmware actually running, so a - # failed or abandoned switch snaps back to the truth on reboot. - - lambda: |- - if (std::string("${ble_firmware}") == "true") { - id(firmware_ble).publish_state("Enabled"); - } else { - id(firmware_ble).publish_state("Disabled"); - } - - script.execute: apply_ota_source + - priority: -100 + then: + # The Bluetooth Proxy select mirrors the firmware actually running, so + # a failed or abandoned switch snaps back to the truth on reboot. + - lambda: |- + if (std::string("${ble_firmware}") == "true") { + id(firmware_ble).publish_state("Enabled"); + } else { + id(firmware_ble).publish_state("Disabled"); + } + - script.execute: apply_ota_source esp32: variant: esp32c3 @@ -463,6 +465,41 @@ button: on_press: - sen5x.start_fan_autoclean: sen55 + - platform: template + name: "Firmware Update" + id: update_firmware + icon: mdi:cloud-download + entity_category: "config" + on_press: + - logger.log: "Applying firmware based on selected channel and type" + # OTA download needs the device awake for its whole duration. + - lambda: |- + id(deep_sleep_1).prevent_deep_sleep(); + # Free heap for the TLS download on the BLE variant. Guarded so the + # non-BLE variant compiles the same YAML. + - lambda: |- + #ifdef USE_ESP32_BLE + if (esp32_ble::global_ble && esp32_ble::global_ble->is_active()) { + ESP_LOGI("firmware", "Disabling BLE for firmware update"); + esp32_ble::global_ble->disable(); + } + #endif + - delay: 3s + - script.execute: apply_ota_source + - script.wait: apply_ota_source + - delay: 2s + - update.perform: + id: update_http_request + force_update: true + # Only reached if the update did not start (e.g. manifest unreachable). + - lambda: |- + #ifdef USE_ESP32_BLE + if (esp32_ble::global_ble) { + ESP_LOGI("firmware", "Re-enabling BLE (no update performed)"); + esp32_ble::global_ble->enable(); + } + #endif + text_sensor: # Convert VOC Index To Text: # https://sensirion.com/media/documents/02232963/6294E043/Info_Note_VOC_Index.pdf From 8e4b35a18c30829a6706a6bc46a6136885cbb4c2 Mon Sep 17 00:00:00 2001 From: Brandon Harvey <8107750+bharvey88@users.noreply.github.com> Date: Sat, 4 Jul 2026 15:46:35 -0500 Subject: [PATCH 3/6] Add beta channel build publishing to rolling beta pre-release MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same approach as CAST-1: pushes to beta build both variants and clobber-upload manifests (rewritten to absolute URLs) plus bins to a rolling beta pre-release. Bin filenames are prefixed per variant because Factory and BLE share a device name. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- .github/workflows/build-beta.yml | 101 +++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 .github/workflows/build-beta.yml diff --git a/.github/workflows/build-beta.yml b/.github/workflows/build-beta.yml new file mode 100644 index 0000000..5adc855 --- /dev/null +++ b/.github/workflows/build-beta.yml @@ -0,0 +1,101 @@ +name: Build and Publish Beta + +# Builds AIR-1 firmware from the beta branch and publishes it as assets on a +# rolling "beta" pre-release. The on-device "Firmware Channel" select points +# OTA updates at these assets. Stable firmware is built/published separately +# by build.yml (push to main -> GitHub Pages). + +on: + push: + branches: [beta] + paths: + - 'Integrations/ESPHome/**' + workflow_dispatch: + +# Least privilege: read-only by default; only publish-beta is elevated to write. +permissions: + contents: read + +jobs: + version: + name: Read version + runs-on: ubuntu-latest + outputs: + v: ${{ steps.read.outputs.v }} + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - id: read + run: | + v=$(awk '/substitutions:/ {f=1} f && /version:/ {print $2; exit}' \ + Integrations/ESPHome/Core.yaml | tr -d '"') + echo "v=$v" >> "$GITHUB_OUTPUT" + echo "Beta version: $v" + + build: + name: Build ${{ matrix.name }} + needs: version + strategy: + matrix: + include: + - { yaml: Integrations/ESPHome/AIR-1_Factory.yaml, name: firmware-factory } + - { yaml: Integrations/ESPHome/AIR-1_BLE.yaml, name: firmware-ble-beta } + uses: esphome/workflows/.github/workflows/build.yml@025a1e6255610c498ed590403b7e510b69e474df # 2026.4.1 + with: + files: ${{ matrix.yaml }} + esphome-version: stable + combined-name: ${{ matrix.name }} + release-version: ${{ needs.version.outputs.v }} + + publish-beta: + name: Publish beta release assets + needs: [version, build] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Download firmware artifacts + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + path: fw + pattern: firmware* + + - name: Ensure rolling 'beta' pre-release exists + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release view beta -R "${{ github.repository }}" >/dev/null 2>&1 \ + || gh release create beta -R "${{ github.repository }}" \ + --prerelease --title "Beta (rolling)" \ + --notes "Latest AIR-1 beta firmware. Auto-updated on every push to the beta branch." + + - name: Rewrite manifests to absolute URLs and upload assets + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + BASE="https://github.com/${{ github.repository }}/releases/download/beta" + declare -A DIRS=( [factory]=firmware-factory [ble]=firmware-ble-beta ) + for v in factory ble; do + src="fw/${DIRS[$v]}" + man=$(find "$src" -name manifest.json | head -1) + if [ -z "$man" ]; then + echo "::error::manifest.json not found for ${DIRS[$v]}" + exit 1 + fi + # Factory and BLE share a device name, so their bin filenames match. + # Release assets are a flat namespace: prefix per variant. + find "$src" -name '*.bin' | while read -r bin; do + mv "$bin" "$(dirname "$bin")/$v-$(basename "$bin")" + done + echo "Rewriting $man" + # Make ota.path and parts[].path absolute release-asset URLs so the + # device never has to resolve a relative path against a redirect. + jq --arg base "$BASE" --arg pfx "$v-" ' + .builds[0].ota.path = ($base + "/" + $pfx + (.builds[0].ota.path | sub(".*/"; ""))) + | .builds[0].parts |= map(.path = ($base + "/" + $pfx + (.path | sub(".*/"; "")))) + ' "$man" > "manifest-$v.json" + cat "manifest-$v.json" + gh release upload beta "manifest-$v.json" -R "${{ github.repository }}" --clobber + find "$src" -name '*.bin' -print -exec \ + gh release upload beta {} -R "${{ github.repository }}" --clobber \; + done + echo "Beta assets published." From b9a3d8aa8c6d26bf0cf465b3b81d13351fc00b32 Mon Sep 17 00:00:00 2001 From: Brandon Harvey <8107750+bharvey88@users.noreply.github.com> Date: Mon, 6 Jul 2026 09:35:55 -0500 Subject: [PATCH 4/6] Serve the end-user image for updates; reserve Factory for first flashes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI now builds AIR-1.yaml as firmware/ (what OTA updates and the Bluetooth Proxy "Disabled" option deliver) and moves the Factory image (improv + factory test, no OTA password, missing version publish) to firmware-factory/, which only the web installer uses. Beta builds the standard image as manifest-standard.json. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- .github/workflows/build-beta.yml | 12 +++++++----- .github/workflows/build.yml | 6 +++++- Integrations/ESPHome/Core.yaml | 2 +- static/index.html | 2 +- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build-beta.yml b/.github/workflows/build-beta.yml index 5adc855..e345907 100644 --- a/.github/workflows/build-beta.yml +++ b/.github/workflows/build-beta.yml @@ -37,8 +37,10 @@ jobs: strategy: matrix: include: - - { yaml: Integrations/ESPHome/AIR-1_Factory.yaml, name: firmware-factory } - - { yaml: Integrations/ESPHome/AIR-1_BLE.yaml, name: firmware-ble-beta } + # Beta serves OTA updates only, so it builds the end-user image + # (AIR-1.yaml), not the first-flash Factory image. + - { yaml: Integrations/ESPHome/AIR-1.yaml, name: firmware-standard } + - { yaml: Integrations/ESPHome/AIR-1_BLE.yaml, name: firmware-ble-beta } uses: esphome/workflows/.github/workflows/build.yml@025a1e6255610c498ed590403b7e510b69e474df # 2026.4.1 with: files: ${{ matrix.yaml }} @@ -73,15 +75,15 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | BASE="https://github.com/${{ github.repository }}/releases/download/beta" - declare -A DIRS=( [factory]=firmware-factory [ble]=firmware-ble-beta ) - for v in factory ble; do + declare -A DIRS=( [standard]=firmware-standard [ble]=firmware-ble-beta ) + for v in standard ble; do src="fw/${DIRS[$v]}" man=$(find "$src" -name manifest.json | head -1) if [ -z "$man" ]; then echo "::error::manifest.json not found for ${DIRS[$v]}" exit 1 fi - # Factory and BLE share a device name, so their bin filenames match. + # Both variants share a device name, so their bin filenames match. # Release assets are a flat namespace: prefix per variant. find "$src" -name '*.bin' | while read -r bin; do mv "$bin" "$(dirname "$bin")/$v-$(basename "$bin")" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cc32322..89193b6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,10 +24,14 @@ jobs: pull-requests: write with: device-name: air-1 + # AIR-1.yaml is the end-user image served at firmware/ (OTA updates and + # the Bluetooth Proxy "Disabled" option). The Factory image (improv + + # factory test) is only used for first flashes via the web installer. yaml-files: | + Integrations/ESPHome/AIR-1.yaml Integrations/ESPHome/AIR-1_Factory.yaml Integrations/ESPHome/AIR-1_BLE.yaml - firmware-names: "1_Factory:firmware,1_BLE:firmware-ble" + firmware-names: "1:firmware,1_Factory:firmware-factory,1_BLE:firmware-ble" core-yaml-path: Integrations/ESPHome/Core.yaml esphome-version: stable # Bypass check if manually triggered with bypass option diff --git a/Integrations/ESPHome/Core.yaml b/Integrations/ESPHome/Core.yaml index ea46b6b..5924933 100644 --- a/Integrations/ESPHome/Core.yaml +++ b/Integrations/ESPHome/Core.yaml @@ -637,7 +637,7 @@ script: if (beta) { url = ble ? "${beta_manifest_base}/manifest-ble.json" - : "${beta_manifest_base}/manifest-factory.json"; + : "${beta_manifest_base}/manifest-standard.json"; } else { url = ble ? "${stable_manifest_base}/firmware-ble/manifest.json" diff --git a/static/index.html b/static/index.html index 0235fd5..99a5cfd 100644 --- a/static/index.html +++ b/static/index.html @@ -84,7 +84,7 @@

Apollo AIR-1 Installer

AIR-1 Firmware

- +
From 644f8b05c65f5d29f6e0924cf2bda23b030fd8d9 Mon Sep 17 00:00:00 2001 From: Brandon Harvey <8107750+bharvey88@users.noreply.github.com> Date: Tue, 7 Jul 2026 14:06:26 -0500 Subject: [PATCH 5/6] Use current_option() for selects; bump version to 26.7.7.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Select::state is deprecated (removal planned for ESPHome 2026.7.0). 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- Integrations/ESPHome/Core.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Integrations/ESPHome/Core.yaml b/Integrations/ESPHome/Core.yaml index 5924933..9267547 100644 --- a/Integrations/ESPHome/Core.yaml +++ b/Integrations/ESPHome/Core.yaml @@ -1,6 +1,6 @@ substitutions: name: apollo-air-1 - version: "26.7.1.1" + version: "26.7.7.1" device_description: ${name} made by Apollo Automation - version ${version}. # Default OTA password. Override in your device YAML by re-declaring # `substitutions: { ota_password: !secret _ota_password }` so each @@ -631,8 +631,8 @@ script: # Stable = GitHub Pages, Beta = rolling "beta" release assets. then: - lambda: |- - const bool ble = id(firmware_ble).state == "Enabled"; - const bool beta = id(firmware_channel).state == "Beta"; + const bool ble = id(firmware_ble).current_option() == "Enabled"; + const bool beta = id(firmware_channel).current_option() == "Beta"; std::string url; if (beta) { url = ble From f6ca24fdbb60637923e70083bab633e9bf87e61b Mon Sep 17 00:00:00 2001 From: Brandon Harvey <8107750+bharvey88@users.noreply.github.com> Date: Tue, 7 Jul 2026 14:52:39 -0500 Subject: [PATCH 6/6] Address review: BLE boot fixes, sleep restore on failed update, hardening MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - BLE image: publish apollo_firmware_version on boot (ports merged #96, which only landed on main) and fix the sleep-duration math treating minutes as hours (60x too long; pre-existing on all branches). - BLE image now forces Prevent Sleep on at boot: a Bluetooth proxy is useless asleep, and the switch also gates the api on_client_connected deep_sleep.enter path. - Firmware Update button: restore deep sleep in the no-update fallback when Prevent Sleep and OTA Mode are both off. - Manifest-fetch window bumped 2s -> 5s with a comment on why a fixed delay (no fetch-done condition exists; update.is_available stays false for same-version variant switches). - build-beta.yml: persist-credentials: false on checkout. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- .github/workflows/build-beta.yml | 2 ++ Integrations/ESPHome/AIR-1_BLE.yaml | 21 +++++++++++---------- Integrations/ESPHome/Core.yaml | 13 ++++++++++++- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build-beta.yml b/.github/workflows/build-beta.yml index e345907..9c72641 100644 --- a/.github/workflows/build-beta.yml +++ b/.github/workflows/build-beta.yml @@ -24,6 +24,8 @@ jobs: v: ${{ steps.read.outputs.v }} steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false - id: read run: | v=$(awk '/substitutions:/ {f=1} f && /version:/ {print $2; exit}' \ diff --git a/Integrations/ESPHome/AIR-1_BLE.yaml b/Integrations/ESPHome/AIR-1_BLE.yaml index 5732128..62204ad 100644 --- a/Integrations/ESPHome/AIR-1_BLE.yaml +++ b/Integrations/ESPHome/AIR-1_BLE.yaml @@ -15,17 +15,18 @@ esphome: on_boot: - priority: 500 then: + - text_sensor.template.publish: + id: apollo_firmware_version + state: "${version}" - lambda: |- - id(deep_sleep_1).set_sleep_duration(id(deep_sleep_sleep_duration).state * 60 * 60 * 1000); - - if: - condition: - or: - - binary_sensor.is_on: ota_mode - - switch.is_on: prevent_sleep - then: - - lambda: |- - ESP_LOGW("Apollo", "Preventing Deep Sleep Due To OTA On Boot"); - id(deep_sleep_1).prevent_deep_sleep(); + id(deep_sleep_1).set_sleep_duration(id(deep_sleep_sleep_duration).state * 60 * 1000); + # A Bluetooth proxy is useless asleep, so this image keeps the device + # awake: forcing the Prevent Sleep switch on also gates the + # api on_client_connected deep_sleep.enter path in Core.yaml. + - lambda: |- + ESP_LOGI("Apollo", "Bluetooth proxy firmware: forcing Prevent Sleep on"); + id(prevent_sleep).turn_on(); + id(deep_sleep_1).prevent_deep_sleep(); on_shutdown: - light.turn_off: rgb_light diff --git a/Integrations/ESPHome/Core.yaml b/Integrations/ESPHome/Core.yaml index 9267547..c1ba6e9 100644 --- a/Integrations/ESPHome/Core.yaml +++ b/Integrations/ESPHome/Core.yaml @@ -487,7 +487,10 @@ button: - delay: 3s - script.execute: apply_ota_source - script.wait: apply_ota_source - - delay: 2s + # The manifest fetch runs in its own task and YAML has no "fetch done" + # condition to wait on (update.is_available stays false for same-version + # variant switches), so give it a fixed window like R_PRO-1/CAST-1 do. + - delay: 5s - update.perform: id: update_http_request force_update: true @@ -499,6 +502,14 @@ button: esp32_ble::global_ble->enable(); } #endif + - if: + condition: + and: + - switch.is_off: prevent_sleep + - binary_sensor.is_off: ota_mode + then: + - lambda: |- + id(deep_sleep_1).allow_deep_sleep(); text_sensor: # Convert VOC Index To Text: