From 17acd2138f4d8a34c58d009de6d8beb306e26c38 Mon Sep 17 00:00:00 2001 From: Tycorc Date: Tue, 19 May 2026 01:37:17 +0200 Subject: [PATCH 01/22] chore(esphome): parameterize OTA password (preserve current default) Promote the hardcoded `password: "apolloautomation"` literal in `MSR-1.yaml` and `MSR-1_BLE.yaml` to a `${ota_password}` substitution defined in `Core.yaml`, defaulting to the same value. Existing consumers see no behavior change. Consumers who include this file via `packages:` can override per-device via their own `substitutions:` block. --- Integrations/ESPHome/Core.yaml | 4 ++++ Integrations/ESPHome/MSR-1.yaml | 2 +- Integrations/ESPHome/MSR-1_BLE.yaml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Integrations/ESPHome/Core.yaml b/Integrations/ESPHome/Core.yaml index 70e3687..50da2d9 100644 --- a/Integrations/ESPHome/Core.yaml +++ b/Integrations/ESPHome/Core.yaml @@ -1,6 +1,10 @@ substitutions: version: "26.3.2.1" device_description: ${name} made by Apollo Automation - version ${version}. + # Default OTA password. Override in your device YAML by re-declaring + # `substitutions: { ota_password: !secret _ota_password }` so each + # device on your network uses a unique secret instead of the shared default. + ota_password: "apolloautomation" esp32: variant: esp32c3 diff --git a/Integrations/ESPHome/MSR-1.yaml b/Integrations/ESPHome/MSR-1.yaml index 04d38b4..72245d3 100644 --- a/Integrations/ESPHome/MSR-1.yaml +++ b/Integrations/ESPHome/MSR-1.yaml @@ -28,7 +28,7 @@ logger: ota: - platform: esphome - password: "apolloautomation" + password: ${ota_password} wifi: power_save_mode: none diff --git a/Integrations/ESPHome/MSR-1_BLE.yaml b/Integrations/ESPHome/MSR-1_BLE.yaml index 4465d8e..a6cf842 100644 --- a/Integrations/ESPHome/MSR-1_BLE.yaml +++ b/Integrations/ESPHome/MSR-1_BLE.yaml @@ -29,7 +29,7 @@ dashboard_import: ota: - platform: esphome - password: "apolloautomation" + password: ${ota_password} wifi: ap: From 90a6706e08fe2c938e6fd4a76039bc548df1e12d Mon Sep 17 00:00:00 2001 From: bharvey88 <8107750+bharvey88@users.noreply.github.com> Date: Wed, 10 Jun 2026 16:00:27 -0500 Subject: [PATCH 02/22] Run label-check and auto-assign via pull_request_target Fork-submitted PRs get a read-only token on pull_request runs, so the label and assignee bots fail with 403. pull_request_target runs in the base repo context with a write token; safe here because neither job checks out or executes PR code. Build jobs stay on pull_request. Trim ci.yml permissions to what the builds need. --- .github/workflows/autoassign.yml | 5 ++++- .github/workflows/ci.yml | 7 ------- .github/workflows/label-check.yml | 20 ++++++++++++++++++++ 3 files changed, 24 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/label-check.yml diff --git a/.github/workflows/autoassign.yml b/.github/workflows/autoassign.yml index 604d6d5..d4895a8 100644 --- a/.github/workflows/autoassign.yml +++ b/.github/workflows/autoassign.yml @@ -1,8 +1,11 @@ name: Auto Assign +# pull_request_target (not pull_request) so assignment works on +# fork-submitted PRs; fork pull_request runs get a read-only token. +# Safe because this workflow never checks out or executes PR code. on: issues: types: [opened] - pull_request: + pull_request_target: types: [opened] jobs: run: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f02c6d7..ccdea2d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,17 +3,10 @@ on: pull_request: permissions: - # Allow GITHUB_TOKEN to add labels to pull requests - pull-requests: write - issues: write contents: read id-token: write jobs: - label-check: - name: Label Check - uses: ApolloAutomation/Workflows/.github/workflows/label-check.yml@main - ci: name: Building ${{ matrix.file }} / ESPHome ${{ matrix.esphome-version }} runs-on: ubuntu-latest diff --git a/.github/workflows/label-check.yml b/.github/workflows/label-check.yml new file mode 100644 index 0000000..96a9751 --- /dev/null +++ b/.github/workflows/label-check.yml @@ -0,0 +1,20 @@ +name: Label Check + +# pull_request_target (not pull_request) so the job gets a write token on +# fork-submitted PRs too; plain pull_request runs from forks are read-only +# and cannot add labels. Safe because the called workflow only reads the PR +# body and never checks out or executes PR code. The "edited" type re-runs +# the check when the template checkboxes are changed. +on: + pull_request_target: + types: [opened, edited, reopened, synchronize] + +permissions: + pull-requests: write + issues: write + contents: read + +jobs: + label-check: + name: Label Check + uses: ApolloAutomation/Workflows/.github/workflows/label-check.yml@main From 98865afdcc4fa8ec6b119cb4e9678e9d019140df Mon Sep 17 00:00:00 2001 From: Brandon Harvey <8107750+bharvey88@users.noreply.github.com> Date: Wed, 10 Jun 2026 21:55:32 -0500 Subject: [PATCH 03/22] Add HTTP request OTA update system from R_PRO-1 Port the managed firmware update system from R_PRO-1 to all MSR-1 variants: - Add http_request OTA platform alongside the existing esphome OTA - Add update component pulling the firmware manifest from GitHub Pages - Add safe_mode for recovery from failed updates - Build and publish the BLE variant (firmware-ble) so BLE devices update from their own manifest instead of being converted to the factory firmware --- Integrations/ESPHome/MSR-1.yaml | 13 +++++++++++++ Integrations/ESPHome/MSR-1_BLE.yaml | 13 +++++++++++++ Integrations/ESPHome/MSR-1_Factory.yaml | 13 +++++++++++++ 3 files changed, 39 insertions(+) diff --git a/Integrations/ESPHome/MSR-1.yaml b/Integrations/ESPHome/MSR-1.yaml index 72245d3..9227eb8 100644 --- a/Integrations/ESPHome/MSR-1.yaml +++ b/Integrations/ESPHome/MSR-1.yaml @@ -29,6 +29,8 @@ logger: ota: - platform: esphome password: ${ota_password} + - platform: http_request + id: ota_managed wifi: power_save_mode: none @@ -41,5 +43,16 @@ web_server: port: 80 version: 3 +http_request: + verify_ssl: true + +safe_mode: + +update: + - platform: http_request + id: update_http_request + name: Firmware Update + source: https://apolloautomation.github.io/MSR-1/firmware/manifest.json + packages: core: !include Core.yaml \ No newline at end of file diff --git a/Integrations/ESPHome/MSR-1_BLE.yaml b/Integrations/ESPHome/MSR-1_BLE.yaml index a6cf842..edf57ec 100644 --- a/Integrations/ESPHome/MSR-1_BLE.yaml +++ b/Integrations/ESPHome/MSR-1_BLE.yaml @@ -30,6 +30,8 @@ dashboard_import: ota: - platform: esphome password: ${ota_password} + - platform: http_request + id: ota_managed wifi: ap: @@ -38,5 +40,16 @@ wifi: bluetooth_proxy: active: true +http_request: + verify_ssl: true + +safe_mode: + +update: + - platform: http_request + id: update_http_request + name: Firmware Update + source: https://apolloautomation.github.io/MSR-1/firmware-ble/manifest.json + packages: core: !include Core.yaml \ No newline at end of file diff --git a/Integrations/ESPHome/MSR-1_Factory.yaml b/Integrations/ESPHome/MSR-1_Factory.yaml index ecc54f9..1371759 100644 --- a/Integrations/ESPHome/MSR-1_Factory.yaml +++ b/Integrations/ESPHome/MSR-1_Factory.yaml @@ -38,6 +38,19 @@ logger: ota: - platform: esphome id: ota_esphome + - platform: http_request + id: ota_managed + +http_request: + verify_ssl: true + +safe_mode: + +update: + - platform: http_request + id: update_http_request + name: Firmware Update + source: https://apolloautomation.github.io/MSR-1/firmware/manifest.json packages: core: !include Core.yaml \ No newline at end of file From 64494c629f26eb51d04dcc7267cdc3262eabc621 Mon Sep 17 00:00:00 2001 From: Brandon Harvey <8107750+bharvey88@users.noreply.github.com> Date: Wed, 10 Jun 2026 21:55:33 -0500 Subject: [PATCH 04/22] Check for updates when WiFi connects The http_request update component polls every 6h and the first poll fires before the network is up, so a freshly booted device would not see an available update for 6 hours. Trigger a manifest check as soon as WiFi connects. --- Integrations/ESPHome/MSR-1.yaml | 2 ++ Integrations/ESPHome/MSR-1_BLE.yaml | 2 ++ Integrations/ESPHome/MSR-1_Factory.yaml | 2 ++ 3 files changed, 6 insertions(+) diff --git a/Integrations/ESPHome/MSR-1.yaml b/Integrations/ESPHome/MSR-1.yaml index 9227eb8..dafabba 100644 --- a/Integrations/ESPHome/MSR-1.yaml +++ b/Integrations/ESPHome/MSR-1.yaml @@ -33,6 +33,8 @@ ota: id: ota_managed wifi: + on_connect: + - component.update: update_http_request power_save_mode: none ap: ssid: "Apollo MSR1 Hotspot" diff --git a/Integrations/ESPHome/MSR-1_BLE.yaml b/Integrations/ESPHome/MSR-1_BLE.yaml index edf57ec..5eb2aa3 100644 --- a/Integrations/ESPHome/MSR-1_BLE.yaml +++ b/Integrations/ESPHome/MSR-1_BLE.yaml @@ -34,6 +34,8 @@ ota: id: ota_managed wifi: + on_connect: + - component.update: update_http_request ap: ssid: "Apollo MSR1 Hotspot" diff --git a/Integrations/ESPHome/MSR-1_Factory.yaml b/Integrations/ESPHome/MSR-1_Factory.yaml index 1371759..ed0ad7f 100644 --- a/Integrations/ESPHome/MSR-1_Factory.yaml +++ b/Integrations/ESPHome/MSR-1_Factory.yaml @@ -30,6 +30,8 @@ esp32_improv: authorizer: none wifi: + on_connect: + - component.update: update_http_request ap: ssid: "Apollo MSR1 Hotspot" From 250f272882f97ed487e3ceb418eaccd41e742742 Mon Sep 17 00:00:00 2001 From: Brandon Harvey <8107750+bharvey88@users.noreply.github.com> Date: Wed, 10 Jun 2026 21:55:33 -0500 Subject: [PATCH 05/22] Raise min_version to 2025.11.0 to match R_PRO-1's update-system floor --- Integrations/ESPHome/MSR-1.yaml | 2 +- Integrations/ESPHome/MSR-1_BLE.yaml | 2 +- Integrations/ESPHome/MSR-1_Factory.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Integrations/ESPHome/MSR-1.yaml b/Integrations/ESPHome/MSR-1.yaml index dafabba..0b83d1f 100644 --- a/Integrations/ESPHome/MSR-1.yaml +++ b/Integrations/ESPHome/MSR-1.yaml @@ -18,7 +18,7 @@ esphome: name: "ApolloAutomation.MSR-1" version: "${version}" - min_version: 2025.2.0 + min_version: 2025.11.0 dashboard_import: package_import_url: github://ApolloAutomation/MSR-1/Integrations/ESPHome/MSR-1.yaml diff --git a/Integrations/ESPHome/MSR-1_BLE.yaml b/Integrations/ESPHome/MSR-1_BLE.yaml index 5eb2aa3..92f7f96 100644 --- a/Integrations/ESPHome/MSR-1_BLE.yaml +++ b/Integrations/ESPHome/MSR-1_BLE.yaml @@ -18,7 +18,7 @@ esphome: name: "ApolloAutomation.MSR-1_BLE" version: "${version}" - min_version: 2025.2.0 + min_version: 2025.11.0 logger: diff --git a/Integrations/ESPHome/MSR-1_Factory.yaml b/Integrations/ESPHome/MSR-1_Factory.yaml index ed0ad7f..0b14b27 100644 --- a/Integrations/ESPHome/MSR-1_Factory.yaml +++ b/Integrations/ESPHome/MSR-1_Factory.yaml @@ -18,7 +18,7 @@ esphome: name: "ApolloAutomation.MSR-1_Factory" version: "${version}" - min_version: 2025.2.0 + min_version: 2025.11.0 dashboard_import: package_import_url: github://ApolloAutomation/MSR-1/Integrations/ESPHome/MSR-1.yaml From 3c6eb13dfb824b5fca49fd4328ec8c5aaa602843 Mon Sep 17 00:00:00 2001 From: Brandon Harvey <8107750+bharvey88@users.noreply.github.com> Date: Wed, 10 Jun 2026 21:55:51 -0500 Subject: [PATCH 06/22] Build and publish the BLE variant to its own firmware-ble manifest --- .github/workflows/build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 657c95a..cebfa8e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -26,7 +26,8 @@ jobs: device-name: msr-2 yaml-files: | Integrations/ESPHome/MSR-1_Factory.yaml - firmware-names: "1_Factory:firmware" + Integrations/ESPHome/MSR-1_BLE.yaml + firmware-names: "1_Factory:firmware,1_BLE:firmware-ble" core-yaml-path: Integrations/ESPHome/Core.yaml esphome-version: stable # Bypass check if manually triggered with bypass option From b6efb98544e47775756b4f4c4bba31c692693130 Mon Sep 17 00:00:00 2001 From: Brandon Harvey <8107750+bharvey88@users.noreply.github.com> Date: Wed, 10 Jun 2026 22:33:55 -0500 Subject: [PATCH 07/22] Release notes: compact formatting for the HA update dialog Home Assistant shows only the first 255 characters of an ESPHome release summary, so boilerplate is expensive and ## headings render oversized in the update dialog: - Render category titles and What's Changed in bold instead of H2 - Drop the star-the-repo footer - Drop the Full Changelog line: it semver-truncates 4-part versions (always links ...X.Y.Z.1) - the shared build workflow now appends a correct compare link instead --- .github/release-drafter.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index 15d30ad..436184d 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -2,6 +2,7 @@ name-template: 'Release v$NEXT_PATCH_VERSION' tag-template: "$RESOLVED_VERSION" change-template: "- #$NUMBER $TITLE @$AUTHOR" sort-direction: ascending +category-template: '**$TITLE**' categories: - title: "🚨 Breaking changes" @@ -25,11 +26,9 @@ include-labels: no-changes-template: '- No changes' +# The shared build workflow appends a Full Changelog compare link to the +# release body (release-drafter cannot render 4-part version tags). template: | - ## What's Changed + **What's Changed** $CHANGES - - **Full Changelog**: https://github.com/ApolloAutomation/MSR-1/compare/$PREVIOUS_TAG...$RESOLVED_VERSION.1 - - Be sure to 🌟 this repository for updates! \ No newline at end of file From c624ed099f8b36e95cf4019fbc1ac2c23b58bdc9 Mon Sep 17 00:00:00 2001 From: Brandon Harvey <8107750+bharvey88@users.noreply.github.com> Date: Wed, 17 Jun 2026 15:00:57 -0500 Subject: [PATCH 08/22] Bump Core.yaml version to 26.6.17.1 --- Integrations/ESPHome/Core.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Integrations/ESPHome/Core.yaml b/Integrations/ESPHome/Core.yaml index 50da2d9..e60417c 100644 --- a/Integrations/ESPHome/Core.yaml +++ b/Integrations/ESPHome/Core.yaml @@ -1,5 +1,5 @@ substitutions: - version: "26.3.2.1" + version: "26.6.17.1" device_description: ${name} made by Apollo Automation - version ${version}. # Default OTA password. Override in your device YAML by re-declaring # `substitutions: { ota_password: !secret _ota_password }` so each From 7616da530433963f6e33606e778e9d1ee22b02d8 Mon Sep 17 00:00:00 2001 From: Brandon Harvey <8107750+bharvey88@users.noreply.github.com> Date: Tue, 7 Jul 2026 15:13:20 -0500 Subject: [PATCH 09/22] Add Firmware Channel switching from HA MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same Stable/Beta channel switching as CAST-1 (ApolloAutomation/CAST-1#43 naming): - Firmware Channel select (Stable/Beta) sets the OTA manifest URL via a new apply_ota_source script; each image tracks its own variant's manifests through the ble_firmware substitution. - Firmware Update button force-installs the selected channel's firmware. - build-beta.yml publishes beta builds to a rolling beta pre-release. - build.yml now builds MSR-1.yaml as firmware/ so updates serve the end-user image; the Factory image moves to firmware-factory/ for the web installer only. Version: 26.7.7.1 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- .github/workflows/build-beta.yml | 105 ++++++++++++++++++++++++++++ .github/workflows/build.yml | 5 +- Integrations/ESPHome/Core.yaml | 82 +++++++++++++++++++++- Integrations/ESPHome/MSR-1_BLE.yaml | 3 + static/index.html | 2 +- 5 files changed, 194 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/build-beta.yml diff --git a/.github/workflows/build-beta.yml b/.github/workflows/build-beta.yml new file mode 100644 index 0000000..9266cf6 --- /dev/null +++ b/.github/workflows/build-beta.yml @@ -0,0 +1,105 @@ +name: Build and Publish Beta + +# Builds MSR-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 + # (MSR-1.yaml), not the first-flash Factory image. + - { yaml: Integrations/ESPHome/MSR-1.yaml, name: firmware-standard } + - { yaml: Integrations/ESPHome/MSR-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 MSR-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." diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cebfa8e..5e95847 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,10 +24,13 @@ jobs: pull-requests: write with: device-name: msr-2 + # MSR-1.yaml is the end-user image served at firmware/ (OTA updates). The Factory image (improv + + # factory test) is only used for first flashes via the web installer. yaml-files: | + Integrations/ESPHome/MSR-1.yaml Integrations/ESPHome/MSR-1_Factory.yaml Integrations/ESPHome/MSR-1_BLE.yaml - firmware-names: "1_Factory:firmware,1_BLE:firmware-ble" + firmware-names: "1:firmware,1_Factory:firmware-factory,1_BLE:firmware-ble" core-yaml-path: Integrations/ESPHome/Core.yaml esphome-version: stable # Bypass check if manually triggered with bypass option diff --git a/Integrations/ESPHome/Core.yaml b/Integrations/ESPHome/Core.yaml index e60417c..3b934e9 100644 --- a/Integrations/ESPHome/Core.yaml +++ b/Integrations/ESPHome/Core.yaml @@ -1,10 +1,17 @@ substitutions: - version: "26.6.17.1" + version: "26.7.7.1" device_description: ${name} made by Apollo Automation - version ${version}. # Default OTA password. Override in your device YAML by re-declaring # `substitutions: { ota_password: !secret _ota_password }` so each # device on your network uses a unique secret instead of the shared default. ota_password: "apolloautomation" + # Firmware variant identity: overridden to "true" by MSR-1_BLE.yaml so + # each image tracks its own OTA manifests. + ble_firmware: "false" + # Manifest URL bases. Stable = GitHub Pages (main branch builds). + # Beta = rolling "beta" pre-release assets (beta branch builds). + stable_manifest_base: "https://apolloautomation.github.io/MSR-1" + beta_manifest_base: "https://github.com/ApolloAutomation/MSR-1/releases/download/beta" esp32: variant: esp32c3 @@ -655,6 +662,41 @@ button: value: 420 id: scd40 + - platform: template + name: "Firmware Update" + id: update_firmware + icon: mdi:cloud-download + entity_category: "config" + on_press: + - logger.log: "Applying firmware based on selected channel and variant" + # Free heap for the TLS download on the BLE variant. Guarded so the + # non-BLE variant compiles the same YAML. + - lambda: |- + #ifdef USE_ESP32_BLE + if (esp32_ble::global_ble && esp32_ble::global_ble->is_active()) { + ESP_LOGI("firmware", "Disabling BLE for firmware update"); + esp32_ble::global_ble->disable(); + } + #endif + - delay: 3s + - script.execute: apply_ota_source + - script.wait: apply_ota_source + # The manifest fetch runs in its own task and YAML has no "fetch done" + # condition to wait on (update.is_available stays false for same-version + # variant switches), so give it a fixed window like R_PRO-1/CAST-1 do. + - delay: 5s + - update.perform: + id: update_http_request + force_update: true + # Only reached if the update did not start (e.g. manifest unreachable). + - lambda: |- + #ifdef USE_ESP32_BLE + if (esp32_ble::global_ble) { + ESP_LOGI("firmware", "Re-enabling BLE (no update performed)"); + esp32_ble::global_ble->enable(); + } + #endif + switch: - platform: ld2410 @@ -706,6 +748,44 @@ select: name: "Radar Distance Resolution" disabled_by_default: true + - platform: template + name: "Firmware Channel" + id: firmware_channel + icon: mdi:source-branch + entity_category: "config" + optimistic: true + restore_value: true + options: + - "Stable" + - "Beta" + initial_option: "Stable" + on_value: + then: + - script.execute: apply_ota_source + +script: + - id: apply_ota_source + # Sets the OTA manifest URL from the Firmware Channel select + # (Stable/Beta) for the firmware variant this image was built as. + # Stable = GitHub Pages, Beta = rolling "beta" release assets. + then: + - lambda: |- + const bool ble = std::string("${ble_firmware}") == "true"; + const bool beta = id(firmware_channel).current_option() == "Beta"; + std::string url; + if (beta) { + url = ble + ? "${beta_manifest_base}/manifest-ble.json" + : "${beta_manifest_base}/manifest-standard.json"; + } else { + url = ble + ? "${stable_manifest_base}/firmware-ble/manifest.json" + : "${stable_manifest_base}/firmware/manifest.json"; + } + ESP_LOGI("firmware", "OTA manifest set to: %s", url.c_str()); + id(update_http_request).set_source_url(url); + - component.update: update_http_request + #Used To Control RGB Light On Startup. Tells User Status Of Device diff --git a/Integrations/ESPHome/MSR-1_BLE.yaml b/Integrations/ESPHome/MSR-1_BLE.yaml index 92f7f96..86aa45a 100644 --- a/Integrations/ESPHome/MSR-1_BLE.yaml +++ b/Integrations/ESPHome/MSR-1_BLE.yaml @@ -1,3 +1,6 @@ +substitutions: + ble_firmware: "true" + esphome: name: apollo-msr-1 friendly_name: Apollo Multisensor Mk1 (MSR-1) diff --git a/static/index.html b/static/index.html index bcf0172..b680838 100644 --- a/static/index.html +++ b/static/index.html @@ -82,7 +82,7 @@

Apollo MSR-1 Installer

- +

From 6d3ebcf3c231d2e48177e002a2912bb11a8e467e Mon Sep 17 00:00:00 2001 From: Brandon Harvey <8107750+bharvey88@users.noreply.github.com> Date: Wed, 8 Jul 2026 10:50:27 -0500 Subject: [PATCH 10/22] Rename rolling beta release tag to beta-fw (branch/tag collision) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The rolling pre-release created a tag named "beta", colliding with the beta branch. git resolves a bare fetch refspec to refs/tags/ before refs/heads/, so ESPHome remote packages pinned to ref: beta silently fetch the tag (which pointed at main's HEAD at creation and never moves) instead of the branch tip - users following beta as a package get stale YAML no matter how often they clean caches. Rename the release tag to beta-fw and update beta_manifest_base to match. The old beta release/tag should be deleted after this merges and the renamed release is populated. Version: 26.7.8.2 (26.7.8.1 is reserved by the open Bluetooth Proxy PR #90, which will need a rebase over this) 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- .github/workflows/build-beta.yml | 14 +++++++------- Integrations/ESPHome/Core.yaml | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build-beta.yml b/.github/workflows/build-beta.yml index 9266cf6..8ffbdaf 100644 --- a/.github/workflows/build-beta.yml +++ b/.github/workflows/build-beta.yml @@ -1,7 +1,7 @@ name: Build and Publish Beta # Builds MSR-1 firmware from the beta branch and publishes it as assets on a -# rolling "beta" pre-release. The on-device "Firmware Channel" select points +# rolling "beta-fw" 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). @@ -63,12 +63,12 @@ jobs: path: fw pattern: firmware* - - name: Ensure rolling 'beta' pre-release exists + - name: Ensure rolling 'beta-fw' 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 }}" \ + gh release view beta-fw -R "${{ github.repository }}" >/dev/null 2>&1 \ + || gh release create beta-fw -R "${{ github.repository }}" \ --prerelease --title "Beta (rolling)" \ --notes "Latest MSR-1 beta firmware. Auto-updated on every push to the beta branch." @@ -76,7 +76,7 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - BASE="https://github.com/${{ github.repository }}/releases/download/beta" + BASE="https://github.com/${{ github.repository }}/releases/download/beta-fw" declare -A DIRS=( [standard]=firmware-standard [ble]=firmware-ble-beta ) for v in standard ble; do src="fw/${DIRS[$v]}" @@ -98,8 +98,8 @@ jobs: | .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 + gh release upload beta-fw "manifest-$v.json" -R "${{ github.repository }}" --clobber find "$src" -name '*.bin' -print -exec \ - gh release upload beta {} -R "${{ github.repository }}" --clobber \; + gh release upload beta-fw {} -R "${{ github.repository }}" --clobber \; done echo "Beta assets published." diff --git a/Integrations/ESPHome/Core.yaml b/Integrations/ESPHome/Core.yaml index 3b934e9..872ad36 100644 --- a/Integrations/ESPHome/Core.yaml +++ b/Integrations/ESPHome/Core.yaml @@ -1,5 +1,5 @@ substitutions: - version: "26.7.7.1" + version: "26.7.8.2" device_description: ${name} made by Apollo Automation - version ${version}. # Default OTA password. Override in your device YAML by re-declaring # `substitutions: { ota_password: !secret _ota_password }` so each @@ -11,7 +11,7 @@ substitutions: # Manifest URL bases. Stable = GitHub Pages (main branch builds). # Beta = rolling "beta" pre-release assets (beta branch builds). stable_manifest_base: "https://apolloautomation.github.io/MSR-1" - beta_manifest_base: "https://github.com/ApolloAutomation/MSR-1/releases/download/beta" + beta_manifest_base: "https://github.com/ApolloAutomation/MSR-1/releases/download/beta-fw" esp32: variant: esp32c3 From 289f9399ee9494a513518345616a71d4a3acad93 Mon Sep 17 00:00:00 2001 From: Brandon Harvey <8107750+bharvey88@users.noreply.github.com> Date: Wed, 8 Jul 2026 11:38:09 -0500 Subject: [PATCH 11/22] Default the Firmware Channel select to Beta on beta-channel builds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A fresh flash has no stored channel preference, so the select fell back to "Stable" even on firmware obtained from the beta channel - and the update entity then offered the older stable build as a "downgrade". Make initial_option a substitution (firmware_channel_default, still "Stable") and add 8-line beta-channel/ wrapper yamls that override it to "Beta"; build-beta.yml now builds those wrappers. Stable (Pages) builds are unchanged. Same override pattern as variant wrappers. Version: 26.7.8.4 (26.7.8.3 is held by open PR #90) 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- .github/workflows/build-beta.yml | 4 ++-- Integrations/ESPHome/Core.yaml | 9 +++++++-- Integrations/ESPHome/beta-channel/MSR-1.yaml | 9 +++++++++ Integrations/ESPHome/beta-channel/MSR-1_BLE.yaml | 9 +++++++++ 4 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 Integrations/ESPHome/beta-channel/MSR-1.yaml create mode 100644 Integrations/ESPHome/beta-channel/MSR-1_BLE.yaml diff --git a/.github/workflows/build-beta.yml b/.github/workflows/build-beta.yml index 8ffbdaf..e07128a 100644 --- a/.github/workflows/build-beta.yml +++ b/.github/workflows/build-beta.yml @@ -41,8 +41,8 @@ jobs: include: # Beta serves OTA updates only, so it builds the end-user image # (MSR-1.yaml), not the first-flash Factory image. - - { yaml: Integrations/ESPHome/MSR-1.yaml, name: firmware-standard } - - { yaml: Integrations/ESPHome/MSR-1_BLE.yaml, name: firmware-ble-beta } + - { yaml: Integrations/ESPHome/beta-channel/MSR-1.yaml, name: firmware-standard } + - { yaml: Integrations/ESPHome/beta-channel/MSR-1_BLE.yaml, name: firmware-ble-beta } uses: esphome/workflows/.github/workflows/build.yml@025a1e6255610c498ed590403b7e510b69e474df # 2026.4.1 with: files: ${{ matrix.yaml }} diff --git a/Integrations/ESPHome/Core.yaml b/Integrations/ESPHome/Core.yaml index 872ad36..f1cbbdb 100644 --- a/Integrations/ESPHome/Core.yaml +++ b/Integrations/ESPHome/Core.yaml @@ -1,5 +1,5 @@ substitutions: - version: "26.7.8.2" + version: "26.7.8.4" device_description: ${name} made by Apollo Automation - version ${version}. # Default OTA password. Override in your device YAML by re-declaring # `substitutions: { ota_password: !secret _ota_password }` so each @@ -8,6 +8,11 @@ substitutions: # Firmware variant identity: overridden to "true" by MSR-1_BLE.yaml so # each image tracks its own OTA manifests. ble_firmware: "false" + # Default update channel on first boot (no stored user choice yet, i.e. a + # fresh flash). The beta-channel builds override this to "Beta" (see + # Integrations/ESPHome/beta-channel/) so firmware obtained from the beta + # channel keeps tracking it instead of offering a stable "downgrade". + firmware_channel_default: "Stable" # Manifest URL bases. Stable = GitHub Pages (main branch builds). # Beta = rolling "beta" pre-release assets (beta branch builds). stable_manifest_base: "https://apolloautomation.github.io/MSR-1" @@ -758,7 +763,7 @@ select: options: - "Stable" - "Beta" - initial_option: "Stable" + initial_option: "${firmware_channel_default}" on_value: then: - script.execute: apply_ota_source diff --git a/Integrations/ESPHome/beta-channel/MSR-1.yaml b/Integrations/ESPHome/beta-channel/MSR-1.yaml new file mode 100644 index 0000000..63b5067 --- /dev/null +++ b/Integrations/ESPHome/beta-channel/MSR-1.yaml @@ -0,0 +1,9 @@ +# Beta-channel build of MSR-1.yaml: the identical image except the Firmware +# Channel select defaults to "Beta" on first boot, so firmware obtained from +# the beta channel keeps tracking it. Built by build-beta.yml only; the +# stable (GitHub Pages) builds use MSR-1.yaml directly. +substitutions: + firmware_channel_default: "Beta" + +packages: + base: !include ../MSR-1.yaml diff --git a/Integrations/ESPHome/beta-channel/MSR-1_BLE.yaml b/Integrations/ESPHome/beta-channel/MSR-1_BLE.yaml new file mode 100644 index 0000000..1316fa6 --- /dev/null +++ b/Integrations/ESPHome/beta-channel/MSR-1_BLE.yaml @@ -0,0 +1,9 @@ +# Beta-channel build of MSR-1_BLE.yaml: the identical image except the Firmware +# Channel select defaults to "Beta" on first boot, so firmware obtained from +# the beta channel keeps tracking it. Built by build-beta.yml only; the +# stable (GitHub Pages) builds use MSR-1_BLE.yaml directly. +substitutions: + firmware_channel_default: "Beta" + +packages: + base: !include ../MSR-1_BLE.yaml From 3f5d351ac72db6ba934d9c5184f2133652c632eb Mon Sep 17 00:00:00 2001 From: Brandon Harvey <8107750+bharvey88@users.noreply.github.com> Date: Wed, 8 Jul 2026 10:35:02 -0500 Subject: [PATCH 12/22] Add Bluetooth Proxy firmware switching from HA MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Second half of the AIR-1 #107 split: lets users swap between the standard and Bluetooth-proxy images from HA instead of reflashing over USB or editing YAML. - Bluetooth Proxy select (Disabled/Enabled) joins Firmware Channel in composing the OTA manifest URL via apply_ota_source. - On boot each image publishes its real identity into the select (ble_firmware substitution), so a failed or abandoned switch snaps back to the truth. - Same-version variant switches install via the existing Firmware Update force button (merged in #89), which already frees heap by disabling BLE during the download. No workflow changes: build-beta.yml and Pages already publish both variants and their manifests. Version: 26.7.8.5 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- Integrations/ESPHome/Core.yaml | 44 +++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/Integrations/ESPHome/Core.yaml b/Integrations/ESPHome/Core.yaml index f1cbbdb..8cc47bf 100644 --- a/Integrations/ESPHome/Core.yaml +++ b/Integrations/ESPHome/Core.yaml @@ -1,12 +1,13 @@ substitutions: - version: "26.7.8.4" + version: "26.7.8.5" device_description: ${name} made by Apollo Automation - version ${version}. # Default OTA password. Override in your device YAML by re-declaring # `substitutions: { ota_password: !secret _ota_password }` so each # device on your network uses a unique secret instead of the shared default. ota_password: "apolloautomation" - # Firmware variant identity: overridden to "true" by MSR-1_BLE.yaml so - # each image tracks its own OTA manifests. + # Firmware variant identity: overridden to "true" by MSR-1_BLE.yaml. Seeds + # the Bluetooth Proxy select on boot so it self-corrects to what is + # actually running after a failed or abandoned switch. ble_firmware: "false" # Default update channel on first boot (no stored user choice yet, i.e. a # fresh flash). The beta-channel builds override this to "Beta" (see @@ -18,6 +19,22 @@ substitutions: stable_manifest_base: "https://apolloautomation.github.io/MSR-1" beta_manifest_base: "https://github.com/ApolloAutomation/MSR-1/releases/download/beta-fw" +esphome: + # List form so package merging concatenates with each variant's own on_boot + # entries (mapping form would be replaced by the variant's block instead). + on_boot: + - priority: -100 + then: + # The Bluetooth Proxy select mirrors the firmware actually running, so + # a failed or abandoned switch snaps back to the truth on reboot. + - lambda: |- + if (std::string("${ble_firmware}") == "true") { + id(firmware_ble).publish_state("Enabled"); + } else { + id(firmware_ble).publish_state("Disabled"); + } + - script.execute: apply_ota_source + esp32: variant: esp32c3 flash_size: 4MB @@ -768,14 +785,29 @@ select: then: - script.execute: apply_ota_source + - platform: template + name: "Bluetooth Proxy" + id: firmware_ble + icon: mdi:bluetooth + entity_category: "config" + optimistic: true + restore_value: true + options: + - "Disabled" + - "Enabled" + initial_option: "Disabled" + on_value: + then: + - script.execute: apply_ota_source + script: - id: apply_ota_source - # Sets the OTA manifest URL from the Firmware Channel select - # (Stable/Beta) for the firmware variant this image was built as. + # Sets the OTA manifest URL from the two selectors: Bluetooth Proxy + # (Disabled/Enabled) x Firmware Channel (Stable/Beta). # Stable = GitHub Pages, Beta = rolling "beta" release assets. then: - lambda: |- - const bool ble = std::string("${ble_firmware}") == "true"; + const bool ble = id(firmware_ble).current_option() == "Enabled"; const bool beta = id(firmware_channel).current_option() == "Beta"; std::string url; if (beta) { From 3635ac7222124e5b144e7562e8ab3efd9d3f9a01 Mon Sep 17 00:00:00 2001 From: Brandon Harvey <8107750+bharvey88@users.noreply.github.com> Date: Wed, 8 Jul 2026 15:57:42 -0500 Subject: [PATCH 13/22] Fix beta manifest fetch and guard force-install against stale manifests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hardware testing surfaced two OTA bugs (thanks Brandon): - GitHub release-asset URLs answer with a redirect whose signed Location header exceeds esp_http_client's 512-byte default rx buffer, so every beta-channel manifest fetch failed with "HTTP_CLIENT: Out of buffer". Set buffer_size_rx: 2048 on all images. - update.perform(force) installs whatever manifest was last fetched successfully. Combined with the fetch failure above, pressing Firmware Update on the Beta channel silently installed the cached STABLE manifest - downgrading the device to the old image (which also wiped its saved settings, including WiFi, on boot). The button now verifies the cached manifest URL matches the selected channel and variant before performing, and refuses with a clear log line otherwise. Version: 26.7.8.6 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- Integrations/ESPHome/Core.yaml | 22 ++++++++++++++++++---- Integrations/ESPHome/MSR-1.yaml | 4 ++++ Integrations/ESPHome/MSR-1_BLE.yaml | 4 ++++ Integrations/ESPHome/MSR-1_Factory.yaml | 4 ++++ 4 files changed, 30 insertions(+), 4 deletions(-) diff --git a/Integrations/ESPHome/Core.yaml b/Integrations/ESPHome/Core.yaml index 8cc47bf..c306271 100644 --- a/Integrations/ESPHome/Core.yaml +++ b/Integrations/ESPHome/Core.yaml @@ -1,5 +1,5 @@ substitutions: - version: "26.7.8.5" + version: "26.7.8.6" device_description: ${name} made by Apollo Automation - version ${version}. # Default OTA password. Override in your device YAML by re-declaring # `substitutions: { ota_password: !secret _ota_password }` so each @@ -707,9 +707,23 @@ button: # condition to wait on (update.is_available stays false for same-version # variant switches), so give it a fixed window like R_PRO-1/CAST-1 do. - delay: 5s - - update.perform: - id: update_http_request - force_update: true + # perform() installs whatever manifest was last fetched successfully - + # if the refresh above failed (e.g. network blip), that can be the OTHER + # channel's image. Refuse instead of installing the wrong firmware. + - lambda: |- + const bool want_beta = id(firmware_channel).current_option() == "Beta"; + const bool want_ble = id(firmware_ble).current_option() == "Enabled"; + const std::string &url = id(update_http_request).update_info.firmware_url; + const bool url_beta = url.find("/releases/download/") != std::string::npos; + const bool url_ble = url.find("ble") != std::string::npos; + if (url.empty() || url_beta != want_beta || url_ble != want_ble) { + ESP_LOGE("firmware", + "Cached manifest (%s) does not match the selected channel/variant - " + "the manifest refresh likely failed. Not installing; press again.", + url.empty() ? "" : url.c_str()); + } else { + id(update_http_request).perform(true); + } # Only reached if the update did not start (e.g. manifest unreachable). - lambda: |- #ifdef USE_ESP32_BLE diff --git a/Integrations/ESPHome/MSR-1.yaml b/Integrations/ESPHome/MSR-1.yaml index 0b83d1f..bbf591f 100644 --- a/Integrations/ESPHome/MSR-1.yaml +++ b/Integrations/ESPHome/MSR-1.yaml @@ -47,6 +47,10 @@ web_server: http_request: verify_ssl: true + # GitHub release-asset downloads answer with a redirect whose signed + # Location header exceeds the 512-byte default and fails with + # "HTTP_CLIENT: Out of buffer". + buffer_size_rx: 2048 safe_mode: diff --git a/Integrations/ESPHome/MSR-1_BLE.yaml b/Integrations/ESPHome/MSR-1_BLE.yaml index 86aa45a..d846901 100644 --- a/Integrations/ESPHome/MSR-1_BLE.yaml +++ b/Integrations/ESPHome/MSR-1_BLE.yaml @@ -47,6 +47,10 @@ bluetooth_proxy: http_request: verify_ssl: true + # GitHub release-asset downloads answer with a redirect whose signed + # Location header exceeds the 512-byte default and fails with + # "HTTP_CLIENT: Out of buffer". + buffer_size_rx: 2048 safe_mode: diff --git a/Integrations/ESPHome/MSR-1_Factory.yaml b/Integrations/ESPHome/MSR-1_Factory.yaml index 0b14b27..d74805b 100644 --- a/Integrations/ESPHome/MSR-1_Factory.yaml +++ b/Integrations/ESPHome/MSR-1_Factory.yaml @@ -45,6 +45,10 @@ ota: http_request: verify_ssl: true + # GitHub release-asset downloads answer with a redirect whose signed + # Location header exceeds the 512-byte default and fails with + # "HTTP_CLIENT: Out of buffer". + buffer_size_rx: 2048 safe_mode: From 0b7f590be2d97a31979d01ea9adde2f21522e30f Mon Sep 17 00:00:00 2001 From: Brandon Harvey <8107750+bharvey88@users.noreply.github.com> Date: Wed, 8 Jul 2026 16:16:07 -0500 Subject: [PATCH 14/22] Raise http_request rx buffer to 5120 for GitHub's CSP header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The redirect on release-asset downloads carries a ~3.6 KB Content-Security-Policy header line; esp_http_client requires each header line to fit the rx buffer, so 2048 still failed with "HTTP_CLIENT: Out of buffer" (confirmed on hardware running 26.7.8.6; the new manifest guard correctly refused to install, so no downgrade). Version: 26.7.8.7 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- Integrations/ESPHome/Core.yaml | 2 +- Integrations/ESPHome/MSR-1.yaml | 8 ++++---- Integrations/ESPHome/MSR-1_BLE.yaml | 8 ++++---- Integrations/ESPHome/MSR-1_Factory.yaml | 8 ++++---- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Integrations/ESPHome/Core.yaml b/Integrations/ESPHome/Core.yaml index c306271..273890d 100644 --- a/Integrations/ESPHome/Core.yaml +++ b/Integrations/ESPHome/Core.yaml @@ -1,5 +1,5 @@ substitutions: - version: "26.7.8.6" + version: "26.7.8.7" device_description: ${name} made by Apollo Automation - version ${version}. # Default OTA password. Override in your device YAML by re-declaring # `substitutions: { ota_password: !secret _ota_password }` so each diff --git a/Integrations/ESPHome/MSR-1.yaml b/Integrations/ESPHome/MSR-1.yaml index bbf591f..a7bcf4d 100644 --- a/Integrations/ESPHome/MSR-1.yaml +++ b/Integrations/ESPHome/MSR-1.yaml @@ -47,10 +47,10 @@ web_server: http_request: verify_ssl: true - # GitHub release-asset downloads answer with a redirect whose signed - # Location header exceeds the 512-byte default and fails with - # "HTTP_CLIENT: Out of buffer". - buffer_size_rx: 2048 + # GitHub release-asset downloads answer with a redirect carrying a + # ~3.6 KB Content-Security-Policy header; each header line must fit + # this buffer or the request fails with "HTTP_CLIENT: Out of buffer". + buffer_size_rx: 5120 safe_mode: diff --git a/Integrations/ESPHome/MSR-1_BLE.yaml b/Integrations/ESPHome/MSR-1_BLE.yaml index d846901..e55fe71 100644 --- a/Integrations/ESPHome/MSR-1_BLE.yaml +++ b/Integrations/ESPHome/MSR-1_BLE.yaml @@ -47,10 +47,10 @@ bluetooth_proxy: http_request: verify_ssl: true - # GitHub release-asset downloads answer with a redirect whose signed - # Location header exceeds the 512-byte default and fails with - # "HTTP_CLIENT: Out of buffer". - buffer_size_rx: 2048 + # GitHub release-asset downloads answer with a redirect carrying a + # ~3.6 KB Content-Security-Policy header; each header line must fit + # this buffer or the request fails with "HTTP_CLIENT: Out of buffer". + buffer_size_rx: 5120 safe_mode: diff --git a/Integrations/ESPHome/MSR-1_Factory.yaml b/Integrations/ESPHome/MSR-1_Factory.yaml index d74805b..e3484d9 100644 --- a/Integrations/ESPHome/MSR-1_Factory.yaml +++ b/Integrations/ESPHome/MSR-1_Factory.yaml @@ -45,10 +45,10 @@ ota: http_request: verify_ssl: true - # GitHub release-asset downloads answer with a redirect whose signed - # Location header exceeds the 512-byte default and fails with - # "HTTP_CLIENT: Out of buffer". - buffer_size_rx: 2048 + # GitHub release-asset downloads answer with a redirect carrying a + # ~3.6 KB Content-Security-Policy header; each header line must fit + # this buffer or the request fails with "HTTP_CLIENT: Out of buffer". + buffer_size_rx: 5120 safe_mode: From 8ea86a8ac0d40ae58ab110a2594a1f7a317e34cd Mon Sep 17 00:00:00 2001 From: Brandon Harvey <8107750+bharvey88@users.noreply.github.com> Date: Wed, 8 Jul 2026 16:49:47 -0500 Subject: [PATCH 15/22] Set http_request buffer_size_tx: 2048 - the real Out of buffer fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit IDF's esp_http_client emits "Out of buffer" from request-line composition: GET ? must fit buffer_size_tx. GitHub's release-asset redirect points at a signed URL with a ~850-char query string, so the follow-up request could never be built in the 512-byte default TX buffer - this is why esp_http_client_open failed, and why raising the RX buffer (#93, #94) didn't help. Confirmed against the IDF source (esp_http_client.c, http_client_prepare_first_line). Version: 26.7.8.8 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- Integrations/ESPHome/Core.yaml | 2 +- Integrations/ESPHome/MSR-1.yaml | 4 ++++ Integrations/ESPHome/MSR-1_BLE.yaml | 4 ++++ Integrations/ESPHome/MSR-1_Factory.yaml | 4 ++++ 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Integrations/ESPHome/Core.yaml b/Integrations/ESPHome/Core.yaml index 273890d..4fccc5b 100644 --- a/Integrations/ESPHome/Core.yaml +++ b/Integrations/ESPHome/Core.yaml @@ -1,5 +1,5 @@ substitutions: - version: "26.7.8.7" + version: "26.7.8.8" device_description: ${name} made by Apollo Automation - version ${version}. # Default OTA password. Override in your device YAML by re-declaring # `substitutions: { ota_password: !secret _ota_password }` so each diff --git a/Integrations/ESPHome/MSR-1.yaml b/Integrations/ESPHome/MSR-1.yaml index a7bcf4d..6d87396 100644 --- a/Integrations/ESPHome/MSR-1.yaml +++ b/Integrations/ESPHome/MSR-1.yaml @@ -51,6 +51,10 @@ http_request: # ~3.6 KB Content-Security-Policy header; each header line must fit # this buffer or the request fails with "HTTP_CLIENT: Out of buffer". buffer_size_rx: 5120 + # The redirect target is a signed URL with a ~850-char query string; the + # follow-up request line must fit the TX buffer or esp_http_client_open + # fails with "Out of buffer" before sending anything. + buffer_size_tx: 2048 safe_mode: diff --git a/Integrations/ESPHome/MSR-1_BLE.yaml b/Integrations/ESPHome/MSR-1_BLE.yaml index e55fe71..a1fed10 100644 --- a/Integrations/ESPHome/MSR-1_BLE.yaml +++ b/Integrations/ESPHome/MSR-1_BLE.yaml @@ -51,6 +51,10 @@ http_request: # ~3.6 KB Content-Security-Policy header; each header line must fit # this buffer or the request fails with "HTTP_CLIENT: Out of buffer". buffer_size_rx: 5120 + # The redirect target is a signed URL with a ~850-char query string; the + # follow-up request line must fit the TX buffer or esp_http_client_open + # fails with "Out of buffer" before sending anything. + buffer_size_tx: 2048 safe_mode: diff --git a/Integrations/ESPHome/MSR-1_Factory.yaml b/Integrations/ESPHome/MSR-1_Factory.yaml index e3484d9..55a5de7 100644 --- a/Integrations/ESPHome/MSR-1_Factory.yaml +++ b/Integrations/ESPHome/MSR-1_Factory.yaml @@ -49,6 +49,10 @@ http_request: # ~3.6 KB Content-Security-Policy header; each header line must fit # this buffer or the request fails with "HTTP_CLIENT: Out of buffer". buffer_size_rx: 5120 + # The redirect target is a signed URL with a ~850-char query string; the + # follow-up request line must fit the TX buffer or esp_http_client_open + # fails with "Out of buffer" before sending anything. + buffer_size_tx: 2048 safe_mode: From 66739415f1a4f965919e706f621b6b5e5387bc63 Mon Sep 17 00:00:00 2001 From: Brandon Harvey <8107750+bharvey88@users.noreply.github.com> Date: Thu, 9 Jul 2026 10:19:44 -0500 Subject: [PATCH 16/22] Bump Core.yaml version to 26.7.9.1 for release MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- Integrations/ESPHome/Core.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Integrations/ESPHome/Core.yaml b/Integrations/ESPHome/Core.yaml index 4fccc5b..3b05c96 100644 --- a/Integrations/ESPHome/Core.yaml +++ b/Integrations/ESPHome/Core.yaml @@ -1,5 +1,5 @@ substitutions: - version: "26.7.8.8" + version: "26.7.9.1" device_description: ${name} made by Apollo Automation - version ${version}. # Default OTA password. Override in your device YAML by re-declaring # `substitutions: { ota_password: !secret _ota_password }` so each From 9a00325c0fd595f08b6ca4bcdcde6e6ba937506b Mon Sep 17 00:00:00 2001 From: Brandon Harvey <8107750+bharvey88@users.noreply.github.com> Date: Thu, 9 Jul 2026 12:31:28 -0500 Subject: [PATCH 17/22] Add API encryption with no pre-configured key MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The encryption key is set during Home Assistant adoption and saved to flash, so it persists across OTA/firmware updates. No key is committed to the repo. Non-breaking: unencrypted clients keep working until a key is set. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- Integrations/ESPHome/Core.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Integrations/ESPHome/Core.yaml b/Integrations/ESPHome/Core.yaml index 3b05c96..7942e7a 100644 --- a/Integrations/ESPHome/Core.yaml +++ b/Integrations/ESPHome/Core.yaml @@ -47,6 +47,7 @@ web_server: version: 3 api: + encryption: on_client_connected: - delay: 1s - light.turn_off: rgb_light From a6d9f2a859b46801fdcdfc324cd108135bbb77d6 Mon Sep 17 00:00:00 2001 From: Brandon Harvey <8107750+bharvey88@users.noreply.github.com> Date: Thu, 9 Jul 2026 14:49:21 -0500 Subject: [PATCH 18/22] Unify BLE into one firmware: runtime Bluetooth Proxy switch + channel-only OTA MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the build-time BLE variant machinery with runtime toggles: - Compile bluetooth_proxy + esp32_ble_tracker into Core.yaml so every variant inherits it (base ~94.1% flash, Factory ~95.4% - both fit 4MB). - Add a "Bluetooth Proxy" switch (default off) that starts/stops scanning via esp32_ble_tracker; the esp32_ble stack stays up so esp32_improv keeps working on the Factory image. - Remove the firmware_ble manifest selector. apply_ota_source is now channel-only, using per-variant manifest bases (ota_stable_manifest / ota_beta_manifest) so legacy MSR-1_BLE devices keep their firmware-ble manifest. - Delete the manifest-matching guard and simplify the Firmware Update flow: drop the BLE stack for the TLS download, restore it + the switch's scan state on the not-started path, and let the post-update reboot restore the switch. This also fixes the unconditional BLE re-enable CodeRabbit flagged. - MSR-1_BLE.yaml: drop its now-inherited bluetooth_proxy; add the manifest subs. Part of the single 26.7.9.1 release; no version bump. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- Integrations/ESPHome/Core.yaml | 131 +++++++++++++--------------- Integrations/ESPHome/MSR-1_BLE.yaml | 6 +- 2 files changed, 65 insertions(+), 72 deletions(-) diff --git a/Integrations/ESPHome/Core.yaml b/Integrations/ESPHome/Core.yaml index 7942e7a..b274a24 100644 --- a/Integrations/ESPHome/Core.yaml +++ b/Integrations/ESPHome/Core.yaml @@ -18,6 +18,11 @@ substitutions: # Beta = rolling "beta" pre-release assets (beta branch builds). stable_manifest_base: "https://apolloautomation.github.io/MSR-1" beta_manifest_base: "https://github.com/ApolloAutomation/MSR-1/releases/download/beta-fw" + # Per-variant OTA manifest URLs. Core defaults to the unified (standard) + # image; MSR-1_BLE.yaml overrides these so legacy BLE devices stay on the + # firmware-ble manifest. + ota_stable_manifest: "${stable_manifest_base}/firmware/manifest.json" + ota_beta_manifest: "${beta_manifest_base}/manifest-standard.json" esphome: # List form so package merging concatenates with each variant's own on_boot @@ -25,21 +30,34 @@ esphome: on_boot: - priority: -100 then: - # The Bluetooth Proxy select mirrors the firmware actually running, so - # a failed or abandoned switch snaps back to the truth on reboot. - - lambda: |- - if (std::string("${ble_firmware}") == "true") { - id(firmware_ble).publish_state("Enabled"); - } else { - id(firmware_ble).publish_state("Disabled"); - } - script.execute: apply_ota_source + # Re-apply the Bluetooth Proxy switch after all components set up, so BLE + # scanning matches the persisted switch (proxy stays off by default). + - priority: -300 + then: + - if: + condition: + switch.is_on: bluetooth_proxy_switch + then: + - esp32_ble_tracker.start_scan: + continuous: true + else: + - esp32_ble_tracker.stop_scan: esp32: variant: esp32c3 flash_size: 4MB framework: type: esp-idf + +esp32_ble_tracker: + id: ble_tracker + scan_parameters: + continuous: true + +bluetooth_proxy: + active: true + captive_portal: web_server: @@ -691,48 +709,35 @@ button: icon: mdi:cloud-download entity_category: "config" on_press: - - logger.log: "Applying firmware based on selected channel and variant" - # Free heap for the TLS download on the BLE variant. Guarded so the - # non-BLE variant compiles the same YAML. + - logger.log: "Applying firmware update for the selected channel" + # Free RAM for the TLS download by dropping the BLE stack. On success the + # device reboots and the Bluetooth Proxy switch restores its scan state; + # on the not-started path below we re-enable and re-apply the switch. - lambda: |- #ifdef USE_ESP32_BLE - if (esp32_ble::global_ble && esp32_ble::global_ble->is_active()) { - ESP_LOGI("firmware", "Disabling BLE for firmware update"); - esp32_ble::global_ble->disable(); - } + if (esp32_ble::global_ble) esp32_ble::global_ble->disable(); #endif - delay: 3s - script.execute: apply_ota_source - script.wait: apply_ota_source - # The manifest fetch runs in its own task and YAML has no "fetch done" - # condition to wait on (update.is_available stays false for same-version - # variant switches), so give it a fixed window like R_PRO-1/CAST-1 do. + # The manifest fetch runs in its own task; give it a fixed window to land + # (update.is_available stays false for same-version switches). - delay: 5s - # perform() installs whatever manifest was last fetched successfully - - # if the refresh above failed (e.g. network blip), that can be the OTHER - # channel's image. Refuse instead of installing the wrong firmware. - - lambda: |- - const bool want_beta = id(firmware_channel).current_option() == "Beta"; - const bool want_ble = id(firmware_ble).current_option() == "Enabled"; - const std::string &url = id(update_http_request).update_info.firmware_url; - const bool url_beta = url.find("/releases/download/") != std::string::npos; - const bool url_ble = url.find("ble") != std::string::npos; - if (url.empty() || url_beta != want_beta || url_ble != want_ble) { - ESP_LOGE("firmware", - "Cached manifest (%s) does not match the selected channel/variant - " - "the manifest refresh likely failed. Not installing; press again.", - url.empty() ? "" : url.c_str()); - } else { - id(update_http_request).perform(true); - } - # Only reached if the update did not start (e.g. manifest unreachable). + - lambda: id(update_http_request).perform(true); + # Reached only if the update did not start. Restore the BLE stack and the + # Bluetooth Proxy switch's scan state. - lambda: |- #ifdef USE_ESP32_BLE - if (esp32_ble::global_ble) { - ESP_LOGI("firmware", "Re-enabling BLE (no update performed)"); - esp32_ble::global_ble->enable(); - } + if (esp32_ble::global_ble) esp32_ble::global_ble->enable(); #endif + - if: + condition: + switch.is_on: bluetooth_proxy_switch + then: + - esp32_ble_tracker.start_scan: + continuous: true + else: + - esp32_ble_tracker.stop_scan: switch: @@ -760,6 +765,19 @@ switch: optimistic: true entity_category: "config" + - platform: template + name: "Bluetooth Proxy" + id: bluetooth_proxy_switch + icon: mdi:bluetooth + entity_category: "config" + restore_mode: RESTORE_DEFAULT_OFF + optimistic: true + on_turn_on: + - esp32_ble_tracker.start_scan: + continuous: true + on_turn_off: + - esp32_ble_tracker.stop_scan: + text_sensor: - platform: ld2410 version: @@ -800,40 +818,15 @@ select: then: - script.execute: apply_ota_source - - platform: template - name: "Bluetooth Proxy" - id: firmware_ble - icon: mdi:bluetooth - entity_category: "config" - optimistic: true - restore_value: true - options: - - "Disabled" - - "Enabled" - initial_option: "Disabled" - on_value: - then: - - script.execute: apply_ota_source - script: - id: apply_ota_source - # Sets the OTA manifest URL from the two selectors: Bluetooth Proxy - # (Disabled/Enabled) x Firmware Channel (Stable/Beta). - # Stable = GitHub Pages, Beta = rolling "beta" release assets. + # Sets the OTA manifest URL from the Firmware Channel select (Stable/Beta). + # The manifest base is per-variant (ota_*_manifest substitutions) so legacy + # MSR-1_BLE builds keep their firmware-ble manifest. then: - lambda: |- - const bool ble = id(firmware_ble).current_option() == "Enabled"; const bool beta = id(firmware_channel).current_option() == "Beta"; - std::string url; - if (beta) { - url = ble - ? "${beta_manifest_base}/manifest-ble.json" - : "${beta_manifest_base}/manifest-standard.json"; - } else { - url = ble - ? "${stable_manifest_base}/firmware-ble/manifest.json" - : "${stable_manifest_base}/firmware/manifest.json"; - } + std::string url = beta ? "${ota_beta_manifest}" : "${ota_stable_manifest}"; ESP_LOGI("firmware", "OTA manifest set to: %s", url.c_str()); id(update_http_request).set_source_url(url); - component.update: update_http_request diff --git a/Integrations/ESPHome/MSR-1_BLE.yaml b/Integrations/ESPHome/MSR-1_BLE.yaml index a1fed10..c026398 100644 --- a/Integrations/ESPHome/MSR-1_BLE.yaml +++ b/Integrations/ESPHome/MSR-1_BLE.yaml @@ -1,5 +1,8 @@ substitutions: ble_firmware: "true" + # Legacy BLE devices keep updating from the firmware-ble manifest. + ota_stable_manifest: "https://apolloautomation.github.io/MSR-1/firmware-ble/manifest.json" + ota_beta_manifest: "https://github.com/ApolloAutomation/MSR-1/releases/download/beta-fw/manifest-ble.json" esphome: name: apollo-msr-1 @@ -42,9 +45,6 @@ wifi: ap: ssid: "Apollo MSR1 Hotspot" -bluetooth_proxy: - active: true - http_request: verify_ssl: true # GitHub release-asset downloads answer with a redirect carrying a From 502c4e7b0d4050d5bef882fe5eeb20e4f264ee12 Mon Sep 17 00:00:00 2001 From: Brandon Harvey <8107750+bharvey88@users.noreply.github.com> Date: Thu, 9 Jul 2026 15:47:00 -0500 Subject: [PATCH 19/22] Remove vestigial ble_firmware substitution; fix build.yml device-name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ble_firmware substitution is no longer read by anything after the runtime Bluetooth Proxy switch replaced the firmware_ble manifest selector (#100) - remove it from Core.yaml and MSR-1_BLE.yaml. Also correct the copy-pasted device-name input in build.yml (msr-2 -> msr-1); it is a required-but-unused input in the shared workflow, so this is a no-op correctness fix. No compiled-output change; does not publish. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- .github/workflows/build.yml | 2 +- Integrations/ESPHome/Core.yaml | 4 ---- Integrations/ESPHome/MSR-1_BLE.yaml | 1 - 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5e95847..637ab4b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,7 +23,7 @@ jobs: id-token: write pull-requests: write with: - device-name: msr-2 + device-name: msr-1 # MSR-1.yaml is the end-user image served at firmware/ (OTA updates). The Factory image (improv + # factory test) is only used for first flashes via the web installer. yaml-files: | diff --git a/Integrations/ESPHome/Core.yaml b/Integrations/ESPHome/Core.yaml index b274a24..e57d891 100644 --- a/Integrations/ESPHome/Core.yaml +++ b/Integrations/ESPHome/Core.yaml @@ -5,10 +5,6 @@ substitutions: # `substitutions: { ota_password: !secret _ota_password }` so each # device on your network uses a unique secret instead of the shared default. ota_password: "apolloautomation" - # Firmware variant identity: overridden to "true" by MSR-1_BLE.yaml. Seeds - # the Bluetooth Proxy select on boot so it self-corrects to what is - # actually running after a failed or abandoned switch. - ble_firmware: "false" # Default update channel on first boot (no stored user choice yet, i.e. a # fresh flash). The beta-channel builds override this to "Beta" (see # Integrations/ESPHome/beta-channel/) so firmware obtained from the beta diff --git a/Integrations/ESPHome/MSR-1_BLE.yaml b/Integrations/ESPHome/MSR-1_BLE.yaml index c026398..7e66e0a 100644 --- a/Integrations/ESPHome/MSR-1_BLE.yaml +++ b/Integrations/ESPHome/MSR-1_BLE.yaml @@ -1,5 +1,4 @@ substitutions: - ble_firmware: "true" # Legacy BLE devices keep updating from the firmware-ble manifest. ota_stable_manifest: "https://apolloautomation.github.io/MSR-1/firmware-ble/manifest.json" ota_beta_manifest: "https://github.com/ApolloAutomation/MSR-1/releases/download/beta-fw/manifest-ble.json" From 4be81c35cdcb188e051f7b410d18e25ffa8c7495 Mon Sep 17 00:00:00 2001 From: Brandon Harvey <8107750+bharvey88@users.noreply.github.com> Date: Thu, 9 Jul 2026 15:56:24 -0500 Subject: [PATCH 20/22] Consolidate http_request block into Core.yaml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The http_request component config (verify_ssl + the OTA-download buffer sizes buffer_size_rx/tx) was duplicated identically across MSR-1.yaml, MSR-1_BLE.yaml, and MSR-1_Factory.yaml. Move it to Core.yaml so there is one source of truth; the ota/update http_request PLATFORMS stay in the variants and use the shared component. No compiled-output change; does not publish. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- Integrations/ESPHome/Core.yaml | 11 +++++++++++ Integrations/ESPHome/MSR-1.yaml | 11 ----------- Integrations/ESPHome/MSR-1_BLE.yaml | 11 ----------- Integrations/ESPHome/MSR-1_Factory.yaml | 11 ----------- 4 files changed, 11 insertions(+), 33 deletions(-) diff --git a/Integrations/ESPHome/Core.yaml b/Integrations/ESPHome/Core.yaml index b274a24..d0bd823 100644 --- a/Integrations/ESPHome/Core.yaml +++ b/Integrations/ESPHome/Core.yaml @@ -64,6 +64,17 @@ web_server: port: 80 version: 3 +http_request: + verify_ssl: true + # GitHub release-asset downloads answer with a redirect carrying a + # ~3.6 KB Content-Security-Policy header; each header line must fit + # this buffer or the request fails with "HTTP_CLIENT: Out of buffer". + buffer_size_rx: 5120 + # The redirect target is a signed URL with a ~850-char query string; the + # follow-up request line must fit the TX buffer or esp_http_client_open + # fails with "Out of buffer" before sending anything. + buffer_size_tx: 2048 + api: encryption: on_client_connected: diff --git a/Integrations/ESPHome/MSR-1.yaml b/Integrations/ESPHome/MSR-1.yaml index 6d87396..7f32c74 100644 --- a/Integrations/ESPHome/MSR-1.yaml +++ b/Integrations/ESPHome/MSR-1.yaml @@ -45,17 +45,6 @@ web_server: port: 80 version: 3 -http_request: - verify_ssl: true - # GitHub release-asset downloads answer with a redirect carrying a - # ~3.6 KB Content-Security-Policy header; each header line must fit - # this buffer or the request fails with "HTTP_CLIENT: Out of buffer". - buffer_size_rx: 5120 - # The redirect target is a signed URL with a ~850-char query string; the - # follow-up request line must fit the TX buffer or esp_http_client_open - # fails with "Out of buffer" before sending anything. - buffer_size_tx: 2048 - safe_mode: update: diff --git a/Integrations/ESPHome/MSR-1_BLE.yaml b/Integrations/ESPHome/MSR-1_BLE.yaml index c026398..bd874fa 100644 --- a/Integrations/ESPHome/MSR-1_BLE.yaml +++ b/Integrations/ESPHome/MSR-1_BLE.yaml @@ -45,17 +45,6 @@ wifi: ap: ssid: "Apollo MSR1 Hotspot" -http_request: - verify_ssl: true - # GitHub release-asset downloads answer with a redirect carrying a - # ~3.6 KB Content-Security-Policy header; each header line must fit - # this buffer or the request fails with "HTTP_CLIENT: Out of buffer". - buffer_size_rx: 5120 - # The redirect target is a signed URL with a ~850-char query string; the - # follow-up request line must fit the TX buffer or esp_http_client_open - # fails with "Out of buffer" before sending anything. - buffer_size_tx: 2048 - safe_mode: update: diff --git a/Integrations/ESPHome/MSR-1_Factory.yaml b/Integrations/ESPHome/MSR-1_Factory.yaml index 55a5de7..a21e383 100644 --- a/Integrations/ESPHome/MSR-1_Factory.yaml +++ b/Integrations/ESPHome/MSR-1_Factory.yaml @@ -43,17 +43,6 @@ ota: - platform: http_request id: ota_managed -http_request: - verify_ssl: true - # GitHub release-asset downloads answer with a redirect carrying a - # ~3.6 KB Content-Security-Policy header; each header line must fit - # this buffer or the request fails with "HTTP_CLIENT: Out of buffer". - buffer_size_rx: 5120 - # The redirect target is a signed URL with a ~850-char query string; the - # follow-up request line must fit the TX buffer or esp_http_client_open - # fails with "Out of buffer" before sending anything. - buffer_size_tx: 2048 - safe_mode: update: From 9de835cb9bc5a92adf7d48b286bc2a147a97b402 Mon Sep 17 00:00:00 2001 From: Brandon Harvey <8107750+bharvey88@users.noreply.github.com> Date: Thu, 9 Jul 2026 16:02:45 -0500 Subject: [PATCH 21/22] Remove pre-OTA BLE stack disable/re-enable from Firmware Update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Verified on hardware that the unified build (~21% RAM at rest, ~260 KB free) completes the TLS firmware download with the Bluetooth proxy still active, so freeing RAM by dropping the BLE stack before the update is unnecessary. Remove the disable-before / re-enable-and-restore-scan-after logic; the Bluetooth proxy now stays up through the update. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- Integrations/ESPHome/Core.yaml | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/Integrations/ESPHome/Core.yaml b/Integrations/ESPHome/Core.yaml index b274a24..41de2bb 100644 --- a/Integrations/ESPHome/Core.yaml +++ b/Integrations/ESPHome/Core.yaml @@ -710,13 +710,6 @@ button: entity_category: "config" on_press: - logger.log: "Applying firmware update for the selected channel" - # Free RAM for the TLS download by dropping the BLE stack. On success the - # device reboots and the Bluetooth Proxy switch restores its scan state; - # on the not-started path below we re-enable and re-apply the switch. - - lambda: |- - #ifdef USE_ESP32_BLE - if (esp32_ble::global_ble) esp32_ble::global_ble->disable(); - #endif - delay: 3s - script.execute: apply_ota_source - script.wait: apply_ota_source @@ -724,20 +717,6 @@ button: # (update.is_available stays false for same-version switches). - delay: 5s - lambda: id(update_http_request).perform(true); - # Reached only if the update did not start. Restore the BLE stack and the - # Bluetooth Proxy switch's scan state. - - lambda: |- - #ifdef USE_ESP32_BLE - if (esp32_ble::global_ble) esp32_ble::global_ble->enable(); - #endif - - if: - condition: - switch.is_on: bluetooth_proxy_switch - then: - - esp32_ble_tracker.start_scan: - continuous: true - else: - - esp32_ble_tracker.stop_scan: switch: From a5200c54c5899a359df79ad73b938b50d0471c0c Mon Sep 17 00:00:00 2001 From: Brandon Harvey <8107750+bharvey88@users.noreply.github.com> Date: Thu, 9 Jul 2026 16:18:51 -0500 Subject: [PATCH 22/22] Drop redundant bluetooth_proxy active: true MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The bluetooth_proxy component now enables active connections by default, and Home Assistant manages active-vs-passive dynamically - it opens an active connection only when an integration needs one, staying passive otherwise. So the explicit active: true is redundant with the current default. Dropping it leaves the bare bluetooth_proxy: line with identical behavior/compiled output. Ref: https://esphome.io/components/bluetooth_proxy/ 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- Integrations/ESPHome/Core.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/Integrations/ESPHome/Core.yaml b/Integrations/ESPHome/Core.yaml index 717f854..5b5cfc1 100644 --- a/Integrations/ESPHome/Core.yaml +++ b/Integrations/ESPHome/Core.yaml @@ -52,7 +52,6 @@ esp32_ble_tracker: continuous: true bluetooth_proxy: - active: true captive_portal: