Skip to content

Add Firmware Channel and Bluetooth Proxy switching from HA#107

Closed
bharvey88 wants to merge 6 commits into
ApolloAutomation:betafrom
bharvey88:firmware-channel
Closed

Add Firmware Channel and Bluetooth Proxy switching from HA#107
bharvey88 wants to merge 6 commits into
ApolloAutomation:betafrom
bharvey88:firmware-channel

Conversation

@bharvey88

@bharvey88 bharvey88 commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

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:

  • Firmware Channel select (Stable/Beta) and Bluetooth Proxy select
    (Disabled/Enabled) compose the OTA manifest URL via an apply_ota_source
    script, so the existing update entity always tracks the chosen channel and
    variant.
  • Firmware Update button force-installs the selected firmware (needed
    when switching variant or channel at the same version). Prevents deep
    sleep for the download and temporarily disables BLE to free heap for TLS.
  • On boot each image publishes its real identity into the Bluetooth Proxy
    select (ble_firmware substitution), so a failed or abandoned switch
    snaps back to the truth. Variant on_boot blocks are converted to list
    form so this Core.yaml automation merges instead of being replaced.
  • build-beta.yml: pushes to beta build both images and publish them to
    a rolling beta pre-release (same approach as CAST-1), with manifests
    rewritten to absolute URLs and bins prefixed standard-/ble- (both
    variants share a device name, and release assets are a flat namespace).
  • Updates now serve the end-user image: CI builds AIR-1.yaml as
    firmware/ and moves the Factory image to firmware-factory/, which only
    the 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 Version sensor (the
    Factory config never published it).

Notes for the team:

  • Beta-channel devices are only auto-offered an update when beta's
    Core.yaml version is bumped, so beta-bound PRs should bump it. The
    Firmware Update button force-installs regardless.
  • Channel option is named "Stable" rather than CAST-1's "Main"; separate PR
    renames CAST-1 to match.
  • Selects read via current_option() (Select::state is deprecated, removal
    planned for 2026.7.0).

Verified end-to-end on a fork: all five manifest paths publish correctly
(stable firmware/, firmware-factory/, firmware-ble/; beta
manifest-standard.json, manifest-ble.json), beta asset URLs resolve
through 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

  • Bugfix (fixed change that fixes an issue)
  • New feature (thanks!)
  • Breaking change (repair/feature that breaks existing functionality)
  • Dependency Update - Does not publish
  • Other - Does not publish
  • Website of github readme file update - Does not publish
  • Github workflows - Does not publish

Checklist / Checklijst:

  • The code change has been tested and works locally
  • The code change has not yet been tested

If user-visible functionality or configuration variables are added/modified:

  • Added/updated documentation for the web page

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added firmware channel and Bluetooth proxy options so users can choose stable or beta builds and enable BLE firmware.
    • Added an in-app firmware update button to start OTA updates directly from the device.
    • Introduced a beta release pipeline that publishes rolling pre-release firmware artifacts for both standard and BLE variants.
  • Bug Fixes

    • Improved firmware packaging and download links so installers and updates point to the correct build variant.

bharvey88 added 5 commits July 7, 2026 14:05
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)
@bharvey88 bharvey88 added the new-feature New feature label Jul 7, 2026
@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@bharvey88, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 12 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f9a560e9-caf5-412e-a3d0-ea8e6a739498

📥 Commits

Reviewing files that changed from the base of the PR and between 644f8b0 and f6ca24f.

📒 Files selected for processing (3)
  • .github/workflows/build-beta.yml
  • Integrations/ESPHome/AIR-1_BLE.yaml
  • Integrations/ESPHome/Core.yaml

Walkthrough

Adds 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.

Changes

Beta firmware pipeline and OTA update flow

Layer / File(s) Summary
Beta build and publish workflow
.github/workflows/build-beta.yml
New workflow builds AIR-1 and AIR-1_BLE beta variants on push/dispatch, then rewrites manifests to absolute release URLs, renames binaries, and publishes to a rolling beta pre-release.
Standard build workflow variant mapping
.github/workflows/build.yml
Adds AIR-1.yaml to yaml-files and updates firmware-names to include the non-factory 1:firmware mapping alongside factory and BLE variants.
on_boot list-form refactor across AIR-1 variants
Integrations/ESPHome/AIR-1.yaml, Integrations/ESPHome/AIR-1_BLE.yaml
Converts on_boot blocks in AIR-1.yaml and AIR-1_BLE.yaml into list form for merge compatibility with Core.yaml, preserving deep sleep/OTA-mode logic, and adds a ble_firmware substitution to AIR-1_BLE.yaml.
Core.yaml OTA substitutions and boot sync
Integrations/ESPHome/Core.yaml
Bumps AIR-1 firmware version, adds ble_firmware/manifest base substitutions, and extends on_boot to sync firmware_ble state and run apply_ota_source.
Firmware update button, channel/BLE selectors, and apply_ota_source script
Integrations/ESPHome/Core.yaml
Adds a "Firmware Update" button, "Firmware Channel" and "Bluetooth Proxy" selectors, and an apply_ota_source script that computes and applies the OTA manifest URL and triggers forced HTTP updates.
Installer manifest path update
static/index.html
Changes the ESP Web Tools install button manifest reference from firmware/manifest.json to firmware-factory/manifest.json.

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)
Loading
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)
Loading

Possibly related PRs

  • ApolloAutomation/AIR-1#71: Similar firmware-names mapping change for the AIR-1 factory configuration in .github/workflows/build.yml.
  • ApolloAutomation/AIR-1#89: Related changes to on_boot logic publishing ${version} to apollo_firmware_version in AIR-1.yaml/Core.yaml.

Suggested reviewers: TrevorSchirmer, firstof9

Poem

A beta branch hops down the trail,
With manifests rewritten, no bin left stale,
Selectors spin for channel and BLE glow,
The update button whispers "time to flow,"
This bunny thumps twice — release day, hooray! 🐇✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly reflects the main user-facing change: Home Assistant controls for firmware channel and Bluetooth Proxy switching.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
.github/workflows/build-beta.yml (1)

26-26: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Set persist-credentials: false on this checkout.

This job only reads Core.yaml; leaving the default token persisted in .git/config needlessly widens the credential-exposure surface (flagged by zizmor as artipacked).

🔒 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

📥 Commits

Reviewing files that changed from the base of the PR and between db35b84 and 644f8b0.

📒 Files selected for processing (6)
  • .github/workflows/build-beta.yml
  • .github/workflows/build.yml
  • Integrations/ESPHome/AIR-1.yaml
  • Integrations/ESPHome/AIR-1_BLE.yaml
  • Integrations/ESPHome/Core.yaml
  • static/index.html

Comment thread Integrations/ESPHome/AIR-1_BLE.yaml Outdated
Comment thread Integrations/ESPHome/Core.yaml
Comment on lines +487 to +491
- delay: 3s
- script.execute: apply_ota_source
- script.wait: apply_ota_source
- delay: 2s
- update.perform:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 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.yaml

Repository: 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.yaml

Repository: 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)
PY

Repository: 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:


🌐 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:


🌐 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:


🏁 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/ESPHome

Repository: 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.md

Repository: 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:


🏁 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.yaml

Repository: 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}')
PY

Repository: 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)
@bharvey88

Copy link
Copy Markdown
Contributor Author

Review addressed in f6ca24f:

  • BLE on_boot (sleep units + version publish): both taken. The version publish ports Fix Apollo Firmware Version sensor blank on factory/BLE firmware #96, which merged to main only — beta never got it. The 60× sleep math was unfixed on every branch. The BLE image now also forces Prevent Sleep on at boot: a Bluetooth proxy is useless asleep, and the switch additionally gates the api: on_client_connected deep_sleep.enter path that prevent_deep_sleep() alone doesn't cover.
  • Deep sleep restore on failed update: taken — the fallback re-allows sleep when Prevent Sleep and OTA Mode are both off, mirroring the Prevent Sleep switch's own on_turn_off logic.
  • Wait for manifest fetch instead of fixed sleeps: partially taken. There's no fetch-complete condition to wait on in YAML, and update.is_available can't be used because it stays false for same-version variant switches (the force-install case this button exists for). Bumped the window 2s → 5s to match the CAST-1/R_PRO-1 flows and documented why it's a fixed delay. Worst case is self-healing: the boot-time identity sync corrects the select and the user retries.
  • persist-credentials: false: taken.

🤖 Generated with Claude Code

bharvey88 added a commit to ApolloAutomation/R_PRO-1 that referenced this pull request Jul 8, 2026
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)
bharvey88 added a commit to ApolloAutomation/PUMP-1 that referenced this pull request Jul 8, 2026
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)
bharvey88 added a commit to ApolloAutomation/BTN-1 that referenced this pull request Jul 8, 2026
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)
bharvey88 added a commit to ApolloAutomation/PLT-1 that referenced this pull request Jul 8, 2026
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)
bharvey88 added a commit to ApolloAutomation/TEMP-1 that referenced this pull request Jul 8, 2026
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)
bharvey88 added a commit to ApolloAutomation/PLT-1 that referenced this pull request Jul 8, 2026
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)
bharvey88 added a commit to ApolloAutomation/TEMP-1 that referenced this pull request Jul 8, 2026
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)
bharvey88 added a commit to ApolloAutomation/R_PRO-1 that referenced this pull request Jul 8, 2026
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)
bharvey88 added a commit to ApolloAutomation/PUMP-1 that referenced this pull request Jul 8, 2026
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)
bharvey88 added a commit to ApolloAutomation/BTN-1 that referenced this pull request Jul 8, 2026
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)
bharvey88 added a commit to ApolloAutomation/PLT-1 that referenced this pull request Jul 8, 2026
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)
bharvey88 added a commit to ApolloAutomation/TEMP-1 that referenced this pull request Jul 8, 2026
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)
bharvey88 added a commit to ApolloAutomation/BTN-1 that referenced this pull request Jul 8, 2026
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)
bharvey88 added a commit to ApolloAutomation/PLT-1 that referenced this pull request Jul 8, 2026
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)
bharvey88 added a commit to ApolloAutomation/TEMP-1 that referenced this pull request Jul 8, 2026
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)
@bharvey88

Copy link
Copy Markdown
Contributor Author

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).

@bharvey88 bharvey88 closed this Jul 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

new-feature New feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant