Add Firmware Channel and Bluetooth Proxy switching from HA#107
Add Firmware Channel and Bluetooth Proxy switching from HA#107bharvey88 wants to merge 6 commits into
Conversation
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)
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)
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)
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)
Select::state is deprecated (removal planned for ESPHome 2026.7.0). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
|
Warning Review limit reached
Next review available in: 12 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
WalkthroughAdds a new beta firmware build-and-publish GitHub Actions workflow with manifest rewriting and rolling pre-release publishing; extends the standard build workflow's variant mapping; refactors on_boot YAML into list form; adds OTA channel/BLE selectors, an update button, and an apply_ota_source script to Core.yaml; updates the static installer manifest path. ChangesBeta firmware pipeline and OTA update flow
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant GitHubActions
participant BuildWorkflow
participant GitHubRelease
GitHubActions->>BuildWorkflow: build AIR-1 and AIR-1_BLE beta variants
BuildWorkflow-->>GitHubActions: firmware artifacts
GitHubActions->>GitHubRelease: ensure beta pre-release exists
GitHubActions->>GitHubActions: rewrite manifest.json OTA paths
GitHubActions->>GitHubActions: rename .bin files with variant prefix
GitHubActions->>GitHubRelease: upload manifests and .bin assets (clobber)
sequenceDiagram
participant User
participant FirmwareChannelSelector
participant ApplyOtaSourceScript
participant UpdateHttpRequest
participant UpdateFirmwareButton
User->>FirmwareChannelSelector: change channel or BLE selector
FirmwareChannelSelector->>ApplyOtaSourceScript: script.execute
ApplyOtaSourceScript->>UpdateHttpRequest: set source URL
User->>UpdateFirmwareButton: press Firmware Update
UpdateFirmwareButton->>ApplyOtaSourceScript: execute and wait
UpdateFirmwareButton->>UpdateHttpRequest: update.perform (forced HTTP)
Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
.github/workflows/build-beta.yml (1)
26-26: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winSet
persist-credentials: falseon this checkout.This job only reads
Core.yaml; leaving the default token persisted in.git/configneedlessly widens the credential-exposure surface (flagged by zizmor asartipacked).🔒 Suggested change
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/build-beta.yml at line 26, The checkout step in the build-beta workflow is still persisting the default Git credentials, which unnecessarily expands credential exposure. Update the existing actions/checkout usage in the build-beta job to disable persisted credentials by setting persist-credentials to false, so the step only reads Core.yaml without leaving the token in .git/config.Source: Linters/SAST tools
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@Integrations/ESPHome/AIR-1_BLE.yaml`:
- Around line 14-28: The AIR-1_BLE on_boot setup is using the wrong
sleep-duration conversion and is missing firmware version publication. In the
on_boot lambda that calls id(deep_sleep_1).set_sleep_duration, change the unit
math to match Core.yaml’s minute-based deep_sleep_sleep_duration state, and in
the same boot flow publish apollo_firmware_version so the text sensor is
initialized on this variant. Use the existing on_boot block,
id(deep_sleep_sleep_duration), id(deep_sleep_1), and apollo_firmware_version to
keep behavior aligned with the shared firmware.
In `@Integrations/ESPHome/Core.yaml`:
- Around line 487-491: The OTA flow in the `apply_ota_source`/`update.perform`
sequence still depends on fixed delays, which can let the firmware check run
before the manifest fetch has refreshed the update state. Replace the
sleep-based timing with an explicit wait for the `component.update:
update_http_request` manifest check to complete before calling `update.perform`,
using the existing `apply_ota_source` script path as the anchor for where to add
the synchronization.
- Around line 468-502: The fallback path in the Firmware Update template does
not restore deep sleep after calling prevent_deep_sleep(), leaving the device
awake when no update starts. Update the on_press sequence for update_firmware so
the no-update branch explicitly re-allows sleep when both prevent_sleep and
ota_mode are off, likely in the final lambda after update.perform and before BLE
re-enable logic. Use the existing deep_sleep_1, prevent_deep_sleep(), and
ota_mode/prevent_sleep checks to locate the correct branch.
---
Nitpick comments:
In @.github/workflows/build-beta.yml:
- Line 26: The checkout step in the build-beta workflow is still persisting the
default Git credentials, which unnecessarily expands credential exposure. Update
the existing actions/checkout usage in the build-beta job to disable persisted
credentials by setting persist-credentials to false, so the step only reads
Core.yaml without leaving the token in .git/config.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: b2e5fb03-1d6a-402f-9600-9ae68e138e16
📒 Files selected for processing (6)
.github/workflows/build-beta.yml.github/workflows/build.ymlIntegrations/ESPHome/AIR-1.yamlIntegrations/ESPHome/AIR-1_BLE.yamlIntegrations/ESPHome/Core.yamlstatic/index.html
| - delay: 3s | ||
| - script.execute: apply_ota_source | ||
| - script.wait: apply_ota_source | ||
| - delay: 2s | ||
| - update.perform: |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate the referenced file and nearby definitions.
git ls-files | rg '^Integrations/ESPHome/Core\.yaml$|^Integrations/ESPHome/'
# Show the relevant section around the cited lines.
sed -n '430,540p' Integrations/ESPHome/Core.yaml
# Find where apply_ota_source and update_http_request are defined/used.
rg -n "apply_ota_source|update_http_request|update\.perform|script\.wait" Integrations/ESPHome/Core.yamlRepository: ApolloAutomation/AIR-1
Length of output: 4154
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Read only the parts of the file that define the scripts and update flow.
sed -n '1,220p' Integrations/ESPHome/Core.yaml
sed -n '220,420p' Integrations/ESPHome/Core.yamlRepository: ApolloAutomation/AIR-1
Length of output: 11059
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Use a small Python probe to understand the ordering semantics from the YAML structure only.
python3 - <<'PY'
from pathlib import Path
text = Path('Integrations/ESPHome/Core.yaml').read_text()
for key in ['apply_ota_source', 'update_http_request', 'update.perform', 'script.wait']:
print(f'-- {key} --')
for i, line in enumerate(text.splitlines(), 1):
if key in line:
print(i, line)
PYRepository: ApolloAutomation/AIR-1
Length of output: 721
🌐 Web query:
ESPHome script.wait component.update update.perform semantics asynchronous script action update component check_for_update
💡 Result:
In ESPHome, script.wait, update.perform, and update.check are specific automation actions with defined semantics [1][2][3]. script.wait The script.wait action is used to suspend the execution of an automation until a specified script has finished running [2]. If no script is currently running, the action completes immediately [2]. If multiple instances of the script are running in parallel, it blocks until all of them have terminated [2]. Internally, as of early 2026, script.wait is classified as an asynchronous action, meaning it does not block the device's main execution loop while waiting [4]. update.perform and update.check The update component provides two primary actions for managing firmware or software updates: 1. update.perform: This action triggers the update entity to begin the installation process [1][5]. It accepts a boolean force_update parameter, which, if set to true, forces an update even if the device is already on the same version [1][5]. 2. update.check: This action triggers the update entity to check for available updates without initiating an installation [1]. Important Semantics Users should note that script.wait is designed for orchestration between scripts and is not suitable for use within lambdas, as doing so would block the device's execution and cause it to crash [2]. Furthermore, while script.wait handles queueing for concurrent script executions, incorrect configurations in child scripts (such as improperly handled delays) have historically caused script.wait to return prematurely [6]. Finally, developers should be aware that recent changes (as of March 2026) require actions to explicitly define their synchronous or asynchronous nature when registered [4].
Citations:
- 1: https://esphome.io/components/update/
- 2: https://new.esphome.io/components/script/
- 3: https://api-docs.esphome.io/classesphome_1_1update_1_1_check_action
- 4: https://developers.esphome.io/blog/2026/03/12/register_action-now-requires-explicit-synchronous-parameter/
- 5: https://new.esphome.io/components/update/
- 6: delay broken ? script.wait unlock before end when script contain a delay command esphome/esphome#12044
🌐 Web query:
ESPHome "component.update" action synchronous update entity semantics
💡 Result:
The ESPHome component.update action is a synchronous action that manually triggers the update method of a specified component [1][2]. Because it is categorized as a synchronous action within the ESPHome automation framework, it executes immediately when called during an automation sequence [3][4]. Key semantic and technical details include: 1. Synchronous Execution: The component.update action is considered a synchronous action, meaning it executes its logic and returns control back to the automation runner immediately after the component's update method has been invoked [3][4]. It is not an asynchronous operation that waits for the component's update process to complete if that process involves delayed, non-blocking I/O [3]. 2. PollingComponent Requirement: The action is designed specifically for components that inherit from the PollingComponent class [1][2]. Attempting to use this action on incompatible component types will result in a compile-time error [1][2]. 3. Implementation Details: Under the hood, the action simply calls the target component's update method (e.g., id(my_component).update;) [1][2]. The framework includes safety checks to ensure that the component has completed its setup phase before the update is triggered to prevent potential crashes (e.g., if memory has not been allocated) [5]. 4. Usage Context: It is often used to trigger updates on-demand for components whose automatic polling interval is set to never or is suspended [1][2]. Developers should be cautious when calling this action from multiple places simultaneously, as rapid-fire updates to complex components may lead to performance issues or system instability [6]. For developers building external components, note that as of 2026.3.0, all actions registered via register_action must explicitly define their synchronous nature [3][4]. The component.update action is categorized as synchronous (synchronous=True) [3][4].
Citations:
- 1: https://new.esphome.io/automations/actions/
- 2: https://esphome-docs.pages.dev/automations/actions/
- 3: https://developers.esphome.io/blog/2026/03/12/register_action-now-requires-explicit-synchronous-parameter/
- 4: [core] Require explicit synchronous= for register_action esphome/esphome#14606
- 5: Ensure component is ready before update. esphome/esphome#4523
- 6: https://community.home-assistant.io/t/how-to-make-template-sensor-update-immediately-and-not-wait-x-seconds/451874
🌐 Web query:
site:esphome.io "component.update" "update entity" ESPHome
💡 Result:
In ESPHome, component.update and update entities represent two different, distinct concepts. component.update Action The component.update action is a general-purpose automation action used to manually trigger the update method of a component [1]. This is commonly used in scenarios where you want to force a component to perform its routine work (e.g., refreshing a display, updating an image, or re-drawing a screen) outside of its standard update_interval [2][3][4][5]. Example usage in an automation: on_...: then: - component.update: my_component_id Update Entity (update Component) The update entity is a specific, high-level component type within ESPHome designed to manage software or firmware updates for a device [6][7]. It is not a generic "update" action, but rather a dedicated entity that tracks update availability, state, and progress [8][9][10]. Key features of the update entity: - Purpose: It allows you to check for firmware or software updates (e.g., via a manifest file) [6][7]. - Automation: You can use the update.check action specifically on an update entity to trigger a check for updates [6]. - States: It tracks states such as unknown, no update, update available, and installing [9][10]. - Implementation: It is managed via the UpdateEntity class in the ESPHome source code, which provides methods like publish_state and perform (to install an update) [8][9][10]. In summary, component.update is a generic trigger to make any component run its internal update logic, whereas an update entity is a specialized sensor-like component for handling device update management [1][6].
Citations:
- 1: https://esphome.io/automations/actions/
- 2: https://esphome.io/components/online_image/
- 3: https://esphome.io/components/display/
- 4: https://esphome.io/components/display/epaper_spi/
- 5: https://new.esphome.io/components/display_menu/graphical_display_menu/
- 6: https://esphome.io/components/update/
- 7: https://esphome.io/components/update/http_request/
- 8: https://api-docs.esphome.io/classesphome_1_1update_1_1_update_entity
- 9: https://api-docs-beta.esphome.io/update__entity_8h_source
- 10: https://api-docs.esphome.io/update__entity_8cpp_source
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Show the apply_ota_source script and the update entity definition.
sed -n '620,660p' Integrations/ESPHome/Core.yaml
sed -n '1,120p' Integrations/ESPHome/Core.yaml | rg -n "update_http_request|http_request|update:"Repository: ApolloAutomation/AIR-1
Length of output: 1680
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find the update_http_request entity definition and any related actions.
rg -n "id:\s*update_http_request|platform:\s*update|http_request|update\.check|update\.perform|set_source_url" Integrations/ESPHomeRepository: ApolloAutomation/AIR-1
Length of output: 1464
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Check whether the same update flow exists in the variant files or README, which may clarify intent.
rg -n "apply_ota_source|update_http_request|delay: 3s|delay: 2s|update.perform" Integrations/ESPHome/AIR-1*.yaml Integrations/ESPHome/README.mdRepository: ApolloAutomation/AIR-1
Length of output: 610
🌐 Web query:
ESPHome update http_request entity update() asynchronous manifest check source_url set_source_url perform semantics
💡 Result:
In ESPHome, the http_request update platform provides a mechanism to automatically check for and perform firmware updates by fetching a JSON manifest file [1]. Key architectural and functional details include: Asynchronous Execution The update() function in the http_request update component runs in a dedicated task (on ESP32) to prevent blocking the main loop [2][3]. This prevents watchdog timeouts if the HTTP request is slow or blocked, as the task can yield while waiting for the esp_http_client to return [3]. Because network operations and state updates occur in different contexts, the component uses defer() to schedule state changes (such as publishing the state or triggering the update_available event) back to the main loop to ensure thread safety [2][4]. Manifest Handling The component expects a JSON manifest structured for ESP-Web-Tools [1]. During the update() cycle, it fetches this manifest from the source_url [2]. It validates the metadata and determines if an update is available based on the current firmware version [1]. Users can optionally define custom check_update logic in their YAML configuration to further refine whether an update should be considered available [5]. Semantics of Source URL and Perform - set_source_url: This method (or the source configuration variable) defines the URI of the JSON manifest [1][6]. - perform(bool force): This method is called to initiate the installation of an update [7]. When invoked, it sets the component state to UPDATE_STATE_INSTALLING and begins the OTA process, often involving configuring the ota parent component with the required MD5 hash [2]. Important Behaviors - Network Awareness: Recent updates have improved reliability by checking network::is_connected() before attempting to fetch the manifest [8]. This prevents errors at startup when the device might not yet have an active network connection [8][9]. - Automation: You can trigger checks or perform updates via ESPHome's internal update.check or ota.http_request.flash actions, depending on your configuration requirements [8][10]. Top results: [1][2][3][4][6][5][8][10]
Citations:
- 1: https://esphome.io/components/update/http_request/
- 2: https://api-docs-beta.esphome.io/http__request__update_8cpp_source
- 3: [http_request] Bugfix: run update function in a task esphome/esphome#8018
- 4: [update, http_request_update] Implement update available trigger esphome/esphome#9174
- 5: [http_request] Add custom UpdateInfo checks esphome/esphome#7193
- 6: https://api-docs.esphome.io/http__request__update_8h_source
- 7: https://api-docs.esphome.io/classesphome_1_1http__request_1_1_http_request_update
- 8: [http_request] Skip update check when network not connected esphome/esphome#12418
- 9: Managed Updates attempt to fetch manifest before network is connected esphome/esphome#12416
- 10: https://esphome.io/components/ota/http_request/
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '35,75p' Integrations/ESPHome/AIR-1.yaml
sed -n '35,75p' Integrations/ESPHome/AIR-1_BLE.yaml
sed -n '35,75p' Integrations/ESPHome/AIR-1_Factory.yamlRepository: ApolloAutomation/AIR-1
Length of output: 1981
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the ESPHome update action docs for perform/check behavior.
python3 - <<'PY'
import urllib.request, re
urls = [
'https://esphome.io/components/update/',
'https://new.esphome.io/components/update/',
'https://esphome.io/components/update/http_request/',
]
for u in urls:
try:
with urllib.request.urlopen(u, timeout=20) as r:
html = r.read().decode('utf-8', 'ignore')
print(f'== {u} ==')
for pat in ['perform', 'check', 'source_url', 'manifest']:
if pat in html:
print(f'contains {pat}')
except Exception as e:
print(f'ERR {u}: {e}')
PYRepository: ApolloAutomation/AIR-1
Length of output: 708
Wait for the manifest check instead of relying on fixed sleeps. script.wait only waits for apply_ota_source to finish; it does not wait for the component.update: update_http_request fetch to complete. On a slow network, update.perform can still run before the update state refreshes, causing a stale or skipped firmware check.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Integrations/ESPHome/Core.yaml` around lines 487 - 491, The OTA flow in the
`apply_ota_source`/`update.perform` sequence still depends on fixed delays,
which can let the firmware check run before the manifest fetch has refreshed the
update state. Replace the sleep-based timing with an explicit wait for the
`component.update: update_http_request` manifest check to complete before
calling `update.perform`, using the existing `apply_ota_source` script path as
the anchor for where to add the synchronization.
…ning - BLE image: publish apollo_firmware_version on boot (ports merged ApolloAutomation#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)
|
Review addressed in f6ca24f:
🤖 Generated with Claude Code |
Same feature as CAST-1 and AIR-1 (ApolloAutomation/AIR-1#107): - Firmware Channel select (Stable/Beta) in Core.yaml combines with the existing per-variant Firmware Type select via a shared apply_ota_source script; the inline URL if/else in the W/ETH yamls moves there. - build-beta.yml publishes beta builds of both variants to a rolling beta pre-release with manifests rewritten to absolute URLs. Version: 26.7.8.1 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Same feature as CAST-1 and AIR-1 (ApolloAutomation/AIR-1#107): - Firmware Channel select (Stable/Beta) sets the OTA manifest URL via a new apply_ota_source script; Firmware Update button force-installs. - PUMP-1_Minimal.yaml (the adopted end-user config) gains the managed update system so fielded devices can keep updating after leaving the improv image. - build.yml now serves PUMP-1_Minimal.yaml at firmware/; PUMP-1.yaml (improv + BLE) moves to firmware-factory/ for the web installer only. - build-beta.yml publishes beta builds to a rolling beta pre-release. - update component id renamed firmware_update -> update_http_request to match the other Apollo repos. Version: 26.7.8.1 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Same feature as CAST-1 and AIR-1 (ApolloAutomation/AIR-1#107): - Firmware Channel select (Stable/Beta) sets the OTA manifest URL via a new apply_ota_source script; Firmware Update button force-installs and prevents deep sleep for the download duration. - BTN-1_Minimal.yaml (the adopted end-user config) gains the managed update system so fielded devices can keep updating after leaving the improv image. - build.yml now serves BTN-1_Minimal.yaml at firmware/; BTN-1.yaml (improv + BLE) moves to firmware-factory/ for the web installer only. - build-beta.yml publishes beta builds to a rolling beta pre-release. - update component id renamed firmware_update -> update_http_request to match the other Apollo repos. Version: 26.7.8.1 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Same feature as CAST-1 and AIR-1 (ApolloAutomation/AIR-1#107): - Firmware Channel select (Stable/Beta) sets the OTA manifest URL via a new apply_ota_source script keyed by a per-variant variant_slug substitution ("" / "-b"); Firmware Update button force-installs and prevents deep sleep for the download duration. - The Minimal yamls (the adopted end-user configs) gain the managed update system, and their device identity is aligned with the improv images (apollo-plt-1 / apollo-plt-1b) since they become the images fielded devices update onto. - build.yml now serves the Minimal images at firmware/ and firmware-b/; the improv images move to *-factory/ for the web installer only. - build-beta.yml publishes beta builds of both variants to a rolling beta pre-release. - update component id renamed firmware_update -> update_http_request to match the other Apollo repos. Version: 26.7.8.1 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Same feature as CAST-1 and AIR-1 (ApolloAutomation/AIR-1#107): - Firmware Channel select (Stable/Beta) sets the OTA manifest URL via a new apply_ota_source script keyed by a per-variant variant_slug substitution ("", "-b", "2", "-b2"), replacing the per-file firmware_update_manifest_url; Firmware Update button force-installs and prevents deep sleep for the download duration. - The four Minimal yamls (the adopted end-user configs) gain the managed update system, and their device identity is aligned with the improv images since they become the images fielded devices update onto. - build.yml now serves the Minimal images at firmware*/; the improv images move to *-factory/ for the web installer only. - build-beta.yml publishes beta builds of all four variants to a rolling beta pre-release. - update component id renamed firmware_update -> update_http_request to match the other Apollo repos, and wifi on_connect refreshes the update entity (same fix as PR #61). Version: 26.7.8.1 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Same feature as CAST-1 and AIR-1 (ApolloAutomation/AIR-1#107): - Firmware Channel select (Stable/Beta) sets the OTA manifest URL via a new apply_ota_source script keyed by a per-variant variant_slug substitution ("" / "-b"); Firmware Update button force-installs and prevents deep sleep for the download duration. - The Minimal yamls (the adopted end-user configs) gain the managed update system, and their device identity is aligned with the improv images (apollo-plt-1 / apollo-plt-1b) since they become the images fielded devices update onto. - build.yml now serves the Minimal images at firmware/ and firmware-b/; the improv images move to *-factory/ for the web installer only. - build-beta.yml publishes beta builds of both variants to a rolling beta pre-release. - update component id renamed firmware_update -> update_http_request to match the other Apollo repos. The BLE variant yamls also gain the managed update system (they inherit the Firmware Update button and apply_ota_source from Core.yaml, so they must define update_http_request for CI to pass). Version: 26.7.8.1 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Same feature as CAST-1 and AIR-1 (ApolloAutomation/AIR-1#107): - Firmware Channel select (Stable/Beta) sets the OTA manifest URL via a new apply_ota_source script keyed by a per-variant variant_slug substitution ("", "-b", "2", "-b2"), replacing the per-file firmware_update_manifest_url; Firmware Update button force-installs and prevents deep sleep for the download duration. - The four Minimal yamls (the adopted end-user configs) gain the managed update system, and their device identity is aligned with the improv images since they become the images fielded devices update onto. - build.yml now serves the Minimal images at firmware*/; the improv images move to *-factory/ for the web installer only. - build-beta.yml publishes beta builds of all four variants to a rolling beta pre-release. - update component id renamed firmware_update -> update_http_request to match the other Apollo repos, and wifi on_connect refreshes the update entity (same fix as PR #61). The BLE variant yamls also gain the managed update system (they inherit the Firmware Update button and apply_ota_source from Core.yaml, so they must define update_http_request for CI to pass). Version: 26.7.8.1 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Same feature as CAST-1 and AIR-1 (ApolloAutomation/AIR-1#107): - Firmware Channel select (Stable/Beta) in Core.yaml combines with the existing per-variant Firmware Type select via a shared apply_ota_source script; the inline URL if/else in the W/ETH yamls moves there. - build-beta.yml publishes beta builds of both variants to a rolling beta pre-release with manifests rewritten to absolute URLs. Beta-channel builds compile thin beta-channel/ wrapper yamls so the Firmware Channel select defaults to Beta on firmware obtained from the beta channel (fresh flashes only; stored choices still win). Version: 26.7.8.1 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Same feature as CAST-1 and AIR-1 (ApolloAutomation/AIR-1#107): - Firmware Channel select (Stable/Beta) sets the OTA manifest URL via a new apply_ota_source script; Firmware Update button force-installs. - PUMP-1_Minimal.yaml (the adopted end-user config) gains the managed update system so fielded devices can keep updating after leaving the improv image. - build.yml now serves PUMP-1_Minimal.yaml at firmware/; PUMP-1.yaml (improv + BLE) moves to firmware-factory/ for the web installer only. - build-beta.yml publishes beta builds to a rolling beta pre-release. - update component id renamed firmware_update -> update_http_request to match the other Apollo repos. Beta-channel builds compile thin beta-channel/ wrapper yamls so the Firmware Channel select defaults to Beta on firmware obtained from the beta channel (fresh flashes only; stored choices still win). Version: 26.7.8.1 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Same feature as CAST-1 and AIR-1 (ApolloAutomation/AIR-1#107): - Firmware Channel select (Stable/Beta) sets the OTA manifest URL via a new apply_ota_source script; Firmware Update button force-installs and prevents deep sleep for the download duration. - BTN-1_Minimal.yaml (the adopted end-user config) gains the managed update system so fielded devices can keep updating after leaving the improv image. - build.yml now serves BTN-1_Minimal.yaml at firmware/; BTN-1.yaml (improv + BLE) moves to firmware-factory/ for the web installer only. - build-beta.yml publishes beta builds to a rolling beta pre-release. - update component id renamed firmware_update -> update_http_request to match the other Apollo repos. Beta-channel builds compile thin beta-channel/ wrapper yamls so the Firmware Channel select defaults to Beta on firmware obtained from the beta channel (fresh flashes only; stored choices still win). Version: 26.7.8.1 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Same feature as CAST-1 and AIR-1 (ApolloAutomation/AIR-1#107): - Firmware Channel select (Stable/Beta) sets the OTA manifest URL via a new apply_ota_source script keyed by a per-variant variant_slug substitution ("" / "-b"); Firmware Update button force-installs and prevents deep sleep for the download duration. - The Minimal yamls (the adopted end-user configs) gain the managed update system, and their device identity is aligned with the improv images (apollo-plt-1 / apollo-plt-1b) since they become the images fielded devices update onto. - build.yml now serves the Minimal images at firmware/ and firmware-b/; the improv images move to *-factory/ for the web installer only. - build-beta.yml publishes beta builds of both variants to a rolling beta pre-release. - update component id renamed firmware_update -> update_http_request to match the other Apollo repos. The BLE variant yamls also gain the managed update system (they inherit the Firmware Update button and apply_ota_source from Core.yaml, so they must define update_http_request for CI to pass). Beta-channel builds compile thin beta-channel/ wrapper yamls so the Firmware Channel select defaults to Beta on firmware obtained from the beta channel (fresh flashes only; stored choices still win). Version: 26.7.8.1 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Same feature as CAST-1 and AIR-1 (ApolloAutomation/AIR-1#107): - Firmware Channel select (Stable/Beta) sets the OTA manifest URL via a new apply_ota_source script keyed by a per-variant variant_slug substitution ("", "-b", "2", "-b2"), replacing the per-file firmware_update_manifest_url; Firmware Update button force-installs and prevents deep sleep for the download duration. - The four Minimal yamls (the adopted end-user configs) gain the managed update system, and their device identity is aligned with the improv images since they become the images fielded devices update onto. - build.yml now serves the Minimal images at firmware*/; the improv images move to *-factory/ for the web installer only. - build-beta.yml publishes beta builds of all four variants to a rolling beta pre-release. - update component id renamed firmware_update -> update_http_request to match the other Apollo repos, and wifi on_connect refreshes the update entity (same fix as PR #61). The BLE variant yamls also gain the managed update system (they inherit the Firmware Update button and apply_ota_source from Core.yaml, so they must define update_http_request for CI to pass). Beta-channel builds compile thin beta-channel/ wrapper yamls so the Firmware Channel select defaults to Beta on firmware obtained from the beta channel (fresh flashes only; stored choices still win). Version: 26.7.8.1 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Same feature as CAST-1 and AIR-1 (ApolloAutomation/AIR-1#107): - Firmware Channel select (Stable/Beta) sets the OTA manifest URL via a new apply_ota_source script; Firmware Update button force-installs and prevents deep sleep for the download duration. - BTN-1_Minimal.yaml (the adopted end-user config) gains the managed update system so fielded devices can keep updating after leaving the improv image. - build.yml now serves BTN-1_Minimal.yaml at firmware/; BTN-1.yaml (improv + BLE) moves to firmware-factory/ for the web installer only. - build-beta.yml publishes beta builds to a rolling beta pre-release. - update component id renamed firmware_update -> update_http_request to match the other Apollo repos. Beta-channel builds compile thin beta-channel/ wrapper yamls so the Firmware Channel select defaults to Beta on firmware obtained from the beta channel (fresh flashes only; stored choices still win). Version: 26.7.8.1 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Same feature as CAST-1 and AIR-1 (ApolloAutomation/AIR-1#107): - Firmware Channel select (Stable/Beta) sets the OTA manifest URL via a new apply_ota_source script keyed by a per-variant variant_slug substitution ("" / "-b"); Firmware Update button force-installs and prevents deep sleep for the download duration. - The Minimal yamls (the adopted end-user configs) gain the managed update system, and their device identity is aligned with the improv images (apollo-plt-1 / apollo-plt-1b) since they become the images fielded devices update onto. - build.yml now serves the Minimal images at firmware/ and firmware-b/; the improv images move to *-factory/ for the web installer only. - build-beta.yml publishes beta builds of both variants to a rolling beta pre-release. - update component id renamed firmware_update -> update_http_request to match the other Apollo repos. The BLE variant yamls also gain the managed update system (they inherit the Firmware Update button and apply_ota_source from Core.yaml, so they must define update_http_request for CI to pass). Beta-channel builds compile thin beta-channel/ wrapper yamls so the Firmware Channel select defaults to Beta on firmware obtained from the beta channel (fresh flashes only; stored choices still win). Version: 26.7.8.1 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Same feature as CAST-1 and AIR-1 (ApolloAutomation/AIR-1#107): - Firmware Channel select (Stable/Beta) sets the OTA manifest URL via a new apply_ota_source script keyed by a per-variant variant_slug substitution ("", "-b", "2", "-b2"), replacing the per-file firmware_update_manifest_url; Firmware Update button force-installs and prevents deep sleep for the download duration. - The four Minimal yamls (the adopted end-user configs) gain the managed update system, and their device identity is aligned with the improv images since they become the images fielded devices update onto. - build.yml now serves the Minimal images at firmware*/; the improv images move to *-factory/ for the web installer only. - build-beta.yml publishes beta builds of all four variants to a rolling beta pre-release. - update component id renamed firmware_update -> update_http_request to match the other Apollo repos, and wifi on_connect refreshes the update entity (same fix as PR #61). The BLE variant yamls also gain the managed update system (they inherit the Firmware Update button and apply_ota_source from Core.yaml, so they must define update_http_request for CI to pass). Beta-channel builds compile thin beta-channel/ wrapper yamls so the Firmware Channel select defaults to Beta on firmware obtained from the beta channel (fresh flashes only; stored choices still win). Version: 26.7.8.1 🤖 Generated with [Claude Code](https://claude.com/claude-code)
|
Superseded by #111 (and previously by #110). Note for the record: this branch also fixed the _BLE sleep-duration units (hours vs minutes) and the missing apollo_firmware_version publish - those fixes are not in #111 and deserve their own small PR. Its rolling release tag named 'beta' must not be used either way: a tag sharing a branch name shadows the branch for git fetch and ESPHome remote packages (the beta-fw tag avoids this). |
Version: 26.7.7.1
What does this implement/fix?
Lets users switch firmware from Home Assistant instead of adopting in the
ESPHome dashboard and editing YAML. Same pattern as R_PRO-1's Firmware Type
select and CAST-1's Firmware Channel select:
(Disabled/Enabled) compose the OTA manifest URL via an
apply_ota_sourcescript, so the existing update entity always tracks the chosen channel and
variant.
when switching variant or channel at the same version). Prevents deep
sleep for the download and temporarily disables BLE to free heap for TLS.
select (
ble_firmwaresubstitution), so a failed or abandoned switchsnaps back to the truth. Variant
on_bootblocks are converted to listform so this Core.yaml automation merges instead of being replaced.
betabuild both images and publish them toa rolling
betapre-release (same approach as CAST-1), with manifestsrewritten to absolute URLs and bins prefixed
standard-/ble-(bothvariants share a device name, and release assets are a flat namespace).
AIR-1.yamlasfirmware/and moves the Factory image tofirmware-factory/, which onlythe web installer uses. On their next update, fielded devices leave the
Factory image: they drop improv BLE and the factory test, gain an esphome
OTA password, and get a working
Apollo Firmware Versionsensor (theFactory config never published it).
Notes for the team:
Core.yamlversion is bumped, so beta-bound PRs should bump it. TheFirmware Update button force-installs regardless.
renames CAST-1 to match.
current_option()(Select::state is deprecated, removalplanned for 2026.7.0).
Verified end-to-end on a fork: all five manifest paths publish correctly
(stable
firmware/,firmware-factory/,firmware-ble/; betamanifest-standard.json,manifest-ble.json), beta asset URLs resolvethrough GitHub's redirects, and both images compile at 91.5%/94.5% flash.
Hardware test on a real AIR-1 is in progress.
Types of changes
Checklist / Checklijst:
If user-visible functionality or configuration variables are added/modified:
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes