-
Notifications
You must be signed in to change notification settings - Fork 33
Add Firmware Channel and Bluetooth Proxy switching from HA #107
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
4095eac
Add Firmware Channel and Bluetooth Proxy selects with OTA source script
bharvey88 ad6e052
Add Firmware Update button and boot-time firmware identity sync
bharvey88 8e4b35a
Add beta channel build publishing to rolling beta pre-release
bharvey88 b9a3d8a
Serve the end-user image for updates; reserve Factory for first flashes
bharvey88 644f8b0
Use current_option() for selects; bump version to 26.7.7.1
bharvey88 f6ca24f
Address review: BLE boot fixes, sleep restore on failed update, harde…
bharvey88 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| 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 | ||
| with: | ||
| persist-credentials: false | ||
| - 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: | ||
| # 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 }} | ||
| 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=( [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 | ||
| # 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")" | ||
| 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." |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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:
Repository: ApolloAutomation/AIR-1
Length of output: 4154
🏁 Script executed:
Repository: ApolloAutomation/AIR-1
Length of output: 11059
🏁 Script executed:
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:
Repository: ApolloAutomation/AIR-1
Length of output: 1680
🏁 Script executed:
Repository: ApolloAutomation/AIR-1
Length of output: 1464
🏁 Script executed:
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_requestupdate 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 Theupdate()function in thehttp_requestupdate 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 theesp_http_clientto return [3]. Because network operations and state updates occur in different contexts, the component usesdefer()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 theupdate()cycle, it fetches this manifest from thesource_url[2]. It validates the metadata and determines if an update is available based on the current firmware version [1]. Users can optionally define customcheck_updatelogic 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 thesourceconfiguration 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 toUPDATE_STATE_INSTALLINGand begins the OTA process, often involving configuring theotaparent component with the required MD5 hash [2]. Important Behaviors - Network Awareness: Recent updates have improved reliability by checkingnetwork::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 internalupdate.checkorota.http_request.flashactions, depending on your configuration requirements [8][10]. Top results: [1][2][3][4][6][5][8][10]Citations:
🏁 Script executed:
Repository: ApolloAutomation/AIR-1
Length of output: 1981
🏁 Script executed:
Repository: ApolloAutomation/AIR-1
Length of output: 708
Wait for the manifest check instead of relying on fixed sleeps.
script.waitonly waits forapply_ota_sourceto finish; it does not wait for thecomponent.update: update_http_requestfetch to complete. On a slow network,update.performcan still run before the update state refreshes, causing a stale or skipped firmware check.🤖 Prompt for AI Agents