From 5edca40f539703fc9500b8ac962477ddfe989d6a Mon Sep 17 00:00:00 2001 From: bharvey88 <8107750+bharvey88@users.noreply.github.com> Date: Wed, 10 Jun 2026 14:28:37 -0500 Subject: [PATCH 01/10] Enable CO2 automatic self-calibration with GUI toggle Enable the SCD40's automatic self-calibration by default (ESPHome default) so users no longer need to manually recalibrate every 1-2 years. Adds a "CO2 Auto Calibration" switch so users in spaces that never see fresh-air CO2 levels can turn ASC off and keep using the manual 420ppm calibration button instead. The switch re-applies the saved choice after every boot, since the sensor loses the setting on power loss and the scd4x component re-applies the YAML default during setup. --- Integrations/ESPHome/Core.yaml | 51 ++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/Integrations/ESPHome/Core.yaml b/Integrations/ESPHome/Core.yaml index fc36704..a842a3d 100644 --- a/Integrations/ESPHome/Core.yaml +++ b/Integrations/ESPHome/Core.yaml @@ -409,7 +409,7 @@ sensor: co2: name: "CO2" id: "co2" - automatic_self_calibration: false + automatic_self_calibration: true update_interval: 60s measurement_mode: "periodic" i2c_id: bus_a @@ -767,6 +767,23 @@ switch: restore_mode: RESTORE_DEFAULT_OFF optimistic: true entity_category: "config" + - platform: template + name: "CO2 Auto Calibration" + id: co2_auto_calibration + icon: mdi:molecule-co2 + restore_mode: RESTORE_DEFAULT_ON + optimistic: true + entity_category: "config" + on_turn_on: + then: + - script.execute: + id: setCo2AutoCalibration + enable: true + on_turn_off: + then: + - script.execute: + id: setCo2AutoCalibration + enable: false text_sensor: @@ -798,7 +815,37 @@ select: script: - - id: testScript + - id: setCo2AutoCalibration + mode: restart + parameters: + enable: bool + then: + #The SCD40 forgets this setting on power loss and the scd4x component + #re-applies the YAML default during setup (~1.5s after boot), so when this + #runs at boot (via the restored switch state) wait for setup to finish first. + - if: + condition: + lambda: "return millis() < 20000;" + then: + - delay: 20s + #The sensor only accepts the ASC command while idle, so stop periodic + #measurement first, then restart it. Same sequence the component uses + #for forced calibration. + - lambda: |- + id(scd40).write_command((uint16_t) 0x3F86); //stop periodic measurement + - delay: 500ms + - lambda: |- + id(scd40).set_automatic_self_calibration(enable); + if (!id(scd40).write_command((uint16_t) 0x2416, (uint16_t) (enable ? 1 : 0))) { + ESP_LOGE("Apollo", "Failed to set CO2 auto calibration"); + } else { + ESP_LOGI("Apollo", "CO2 auto calibration %s", enable ? "enabled" : "disabled"); + } + - delay: 10ms + - lambda: |- + id(scd40).write_command((uint16_t) 0x21B1); //start periodic measurement + + - id: testScript then: if: condition: From 289987ec19ad5a6e9f523424890fc2bdba91d57a Mon Sep 17 00:00:00 2001 From: bharvey88 <8107750+bharvey88@users.noreply.github.com> Date: Wed, 10 Jun 2026 14:46:09 -0500 Subject: [PATCH 02/10] Bump version to 26.6.10.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 a842a3d..f204468 100644 --- a/Integrations/ESPHome/Core.yaml +++ b/Integrations/ESPHome/Core.yaml @@ -1,6 +1,6 @@ substitutions: name: apollo-msr-2 - version: "26.3.2.1" + version: "26.6.10.1" device_description: ${name} made by Apollo Automation - version ${version}. esp32: From 681a5af74a0328236b80f0fdc88d4f77968ff2ea Mon Sep 17 00:00:00 2001 From: bharvey88 <8107750+bharvey88@users.noreply.github.com> Date: Wed, 10 Jun 2026 15:48:45 -0500 Subject: [PATCH 03/10] 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 d5a6557..dfdfada 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 662e387..01823c3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,17 +4,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 }} 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 f4721761a9aee6f9c7e1379606fda41bed1b370b Mon Sep 17 00:00:00 2001 From: Brandon Harvey <8107750+bharvey88@users.noreply.github.com> Date: Wed, 10 Jun 2026 21:56:55 -0500 Subject: [PATCH 04/10] Add HTTP request OTA update system from R_PRO-1 Port the managed firmware update system from R_PRO-1 to all MSR-2 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 --- Integrations/ESPHome/MSR-2.yaml | 13 +++++++++++++ Integrations/ESPHome/MSR-2_BLE.yaml | 13 +++++++++++++ Integrations/ESPHome/MSR-2_Factory.yaml | 13 +++++++++++++ 3 files changed, 39 insertions(+) diff --git a/Integrations/ESPHome/MSR-2.yaml b/Integrations/ESPHome/MSR-2.yaml index 78fdd46..7757e65 100644 --- a/Integrations/ESPHome/MSR-2.yaml +++ b/Integrations/ESPHome/MSR-2.yaml @@ -36,6 +36,8 @@ logger: ota: - platform: esphome id: ota_default + - platform: http_request + id: ota_managed wifi: @@ -46,5 +48,16 @@ wifi: ssid: "Apollo MSR2 Hotspot" +http_request: + verify_ssl: true + +safe_mode: + +update: + - platform: http_request + id: update_http_request + name: Firmware Update + source: https://apolloautomation.github.io/MSR-2/firmware/manifest.json + packages: core: !include Core.yaml diff --git a/Integrations/ESPHome/MSR-2_BLE.yaml b/Integrations/ESPHome/MSR-2_BLE.yaml index 480104e..7a23b62 100644 --- a/Integrations/ESPHome/MSR-2_BLE.yaml +++ b/Integrations/ESPHome/MSR-2_BLE.yaml @@ -40,6 +40,8 @@ wifi: ota: - platform: esphome id: ota_esphome + - platform: http_request + id: ota_managed bluetooth_proxy: active: true @@ -47,5 +49,16 @@ esp32_ble_tracker: scan_parameters: active: false +http_request: + verify_ssl: true + +safe_mode: + +update: + - platform: http_request + id: update_http_request + name: Firmware Update + source: https://apolloautomation.github.io/MSR-2/firmware-ble/manifest.json + packages: core: !include Core.yaml diff --git a/Integrations/ESPHome/MSR-2_Factory.yaml b/Integrations/ESPHome/MSR-2_Factory.yaml index 04b3aae..011d87f 100644 --- a/Integrations/ESPHome/MSR-2_Factory.yaml +++ b/Integrations/ESPHome/MSR-2_Factory.yaml @@ -47,6 +47,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-2/firmware/manifest.json packages: core: !include Core.yaml From 7ce645e8e21ba3d92969c865ac39c2ef897465c8 Mon Sep 17 00:00:00 2001 From: Brandon Harvey <8107750+bharvey88@users.noreply.github.com> Date: Wed, 10 Jun 2026 21:56:55 -0500 Subject: [PATCH 05/10] 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 6b7e07a..6d5ab44 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-2_Factory.yaml - firmware-names: "2_Factory:firmware" + Integrations/ESPHome/MSR-2_BLE.yaml + firmware-names: "2_Factory:firmware,2_BLE:firmware-ble" core-yaml-path: Integrations/ESPHome/Core.yaml esphome-version: stable # Bypass check if manually triggered with bypass option From 40cdaaf46c7c2d223c719f5bd05a8a3fcbb5c50c Mon Sep 17 00:00:00 2001 From: Brandon Harvey <8107750+bharvey88@users.noreply.github.com> Date: Wed, 10 Jun 2026 21:56:55 -0500 Subject: [PATCH 06/10] 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-2.yaml | 2 ++ Integrations/ESPHome/MSR-2_BLE.yaml | 2 ++ Integrations/ESPHome/MSR-2_Factory.yaml | 2 ++ 3 files changed, 6 insertions(+) diff --git a/Integrations/ESPHome/MSR-2.yaml b/Integrations/ESPHome/MSR-2.yaml index 7757e65..0180d5c 100644 --- a/Integrations/ESPHome/MSR-2.yaml +++ b/Integrations/ESPHome/MSR-2.yaml @@ -40,6 +40,8 @@ ota: id: ota_managed wifi: + on_connect: + - component.update: update_http_request power_save_mode: none diff --git a/Integrations/ESPHome/MSR-2_BLE.yaml b/Integrations/ESPHome/MSR-2_BLE.yaml index 7a23b62..b74387e 100644 --- a/Integrations/ESPHome/MSR-2_BLE.yaml +++ b/Integrations/ESPHome/MSR-2_BLE.yaml @@ -34,6 +34,8 @@ dashboard_import: import_full_config: false wifi: + on_connect: + - component.update: update_http_request ap: ssid: "Apollo MSR2 Hotspot" diff --git a/Integrations/ESPHome/MSR-2_Factory.yaml b/Integrations/ESPHome/MSR-2_Factory.yaml index 011d87f..f61d670 100644 --- a/Integrations/ESPHome/MSR-2_Factory.yaml +++ b/Integrations/ESPHome/MSR-2_Factory.yaml @@ -39,6 +39,8 @@ esp32_improv: authorizer: none wifi: + on_connect: + - component.update: update_http_request ap: ssid: "Apollo MSR2 Hotspot" From 4f42b5ada24c79305ece3c0a1448310ac2db98a1 Mon Sep 17 00:00:00 2001 From: Brandon Harvey <8107750+bharvey88@users.noreply.github.com> Date: Wed, 10 Jun 2026 21:56:56 -0500 Subject: [PATCH 07/10] Raise min_version to 2025.11.0 to match R_PRO-1's update-system floor --- Integrations/ESPHome/MSR-2.yaml | 2 +- Integrations/ESPHome/MSR-2_BLE.yaml | 2 +- Integrations/ESPHome/MSR-2_Factory.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Integrations/ESPHome/MSR-2.yaml b/Integrations/ESPHome/MSR-2.yaml index 0180d5c..6f83199 100644 --- a/Integrations/ESPHome/MSR-2.yaml +++ b/Integrations/ESPHome/MSR-2.yaml @@ -25,7 +25,7 @@ esphome: name: "ApolloAutomation.MSR-2" version: "${version}" - min_version: 2025.2.0 + min_version: 2025.11.0 dashboard_import: package_import_url: github://ApolloAutomation/MSR-2/Integrations/ESPHome/MSR-2.yaml diff --git a/Integrations/ESPHome/MSR-2_BLE.yaml b/Integrations/ESPHome/MSR-2_BLE.yaml index b74387e..6e9e103 100644 --- a/Integrations/ESPHome/MSR-2_BLE.yaml +++ b/Integrations/ESPHome/MSR-2_BLE.yaml @@ -25,7 +25,7 @@ esphome: name: "ApolloAutomation.MSR-2_BLE" version: "${version}" - min_version: 2025.2.0 + min_version: 2025.11.0 logger: diff --git a/Integrations/ESPHome/MSR-2_Factory.yaml b/Integrations/ESPHome/MSR-2_Factory.yaml index f61d670..791c36e 100644 --- a/Integrations/ESPHome/MSR-2_Factory.yaml +++ b/Integrations/ESPHome/MSR-2_Factory.yaml @@ -25,7 +25,7 @@ esphome: name: "ApolloAutomation.MSR-2_Factory" version: "${version}" - min_version: 2025.2.0 + min_version: 2025.11.0 dashboard_import: From e129a045fde1cbdb5be7c7e9d111f18cfedb46b5 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 08/10] 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 5fb32e3..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-2/compare/$PREVIOUS_TAG...$RESOLVED_VERSION.1 - - Be sure to 🌟 this repository for updates! \ No newline at end of file From 6f359dbe5b2ec2ddfe05a54ec88c59fef407a6a7 Mon Sep 17 00:00:00 2001 From: Brandon Harvey <8107750+bharvey88@users.noreply.github.com> Date: Sun, 12 Jul 2026 12:38:36 -0500 Subject: [PATCH 09/10] Port MSR-1 unified firmware: runtime Bluetooth Proxy switch + channel-only OTA MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit One firmware image per channel replaces the build-time _BLE split: bluetooth_proxy + esp32_ble_tracker compile into every variant and a "Bluetooth Proxy" switch (default off, persisted) starts/stops scanning at runtime. The Firmware Channel select swaps the OTA manifest URL directly - no ble_firmware selector, no manifest-matching guard, no pre-OTA BLE disable (ESPHome's OTA quiesces BLE itself). - http_request consolidated into Core.yaml with the proven buffer sizes (rx 5120 for GitHub's ~3.6KB CSP header line, tx 2048 for the ~850-char signed-redirect query) - apply_ota_source is channel-only; MSR-2_BLE.yaml keeps legacy devices on the firmware-ble manifests via substitution overrides - beta-channel/ wrappers default the select to Beta; build-beta.yml publishes manifest-standard.json + manifest-ble.json to the rolling beta-fw pre-release (absolute URLs) - build.yml now serves the end-user MSR-2.yaml image at firmware/ and moves the improv Factory image to firmware-factory/ (installer page repointed) - version 26.7.12.1 Ported from MSR-1 26.7.9.1 (ApolloAutomation/MSR-1 #100, #103, #104). Supersedes #79. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- .github/workflows/build-beta.yml | 105 +++++++++++++++++ .github/workflows/build.yml | 5 +- Integrations/ESPHome/Core.yaml | 111 +++++++++++++++++- Integrations/ESPHome/MSR-2.yaml | 4 - Integrations/ESPHome/MSR-2_BLE.yaml | 14 +-- Integrations/ESPHome/MSR-2_Factory.yaml | 3 - Integrations/ESPHome/beta-channel/MSR-2.yaml | 9 ++ .../ESPHome/beta-channel/MSR-2_BLE.yaml | 9 ++ static/index.html | 2 +- 9 files changed, 242 insertions(+), 20 deletions(-) create mode 100644 .github/workflows/build-beta.yml create mode 100644 Integrations/ESPHome/beta-channel/MSR-2.yaml create mode 100644 Integrations/ESPHome/beta-channel/MSR-2_BLE.yaml diff --git a/.github/workflows/build-beta.yml b/.github/workflows/build-beta.yml new file mode 100644 index 0000000..1a990cd --- /dev/null +++ b/.github/workflows/build-beta.yml @@ -0,0 +1,105 @@ +name: Build and Publish Beta + +# Builds MSR-2 firmware from the beta branch and publishes it as assets on a +# 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). + +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-2.yaml), not the first-flash Factory image. + - { yaml: Integrations/ESPHome/beta-channel/MSR-2.yaml, name: firmware-standard } + - { yaml: Integrations/ESPHome/beta-channel/MSR-2_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-fw' pre-release exists + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + 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-2 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-fw" + 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-fw "manifest-$v.json" -R "${{ github.repository }}" --clobber + find "$src" -name '*.bin' -print -exec \ + gh release upload beta-fw {} -R "${{ github.repository }}" --clobber \; + done + echo "Beta assets published." diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6d5ab44..e270fd2 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-2.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-2.yaml Integrations/ESPHome/MSR-2_Factory.yaml Integrations/ESPHome/MSR-2_BLE.yaml - firmware-names: "2_Factory:firmware,2_BLE:firmware-ble" + firmware-names: "2:firmware,2_Factory:firmware-factory,2_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 f204468..64bcb20 100644 --- a/Integrations/ESPHome/Core.yaml +++ b/Integrations/ESPHome/Core.yaml @@ -1,20 +1,72 @@ substitutions: name: apollo-msr-2 - version: "26.6.10.1" + version: "26.7.12.1" device_description: ${name} made by Apollo Automation - version ${version}. - + # 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-fw" pre-release assets (beta branch builds). + stable_manifest_base: "https://apolloautomation.github.io/MSR-2" + beta_manifest_base: "https://github.com/ApolloAutomation/MSR-2/releases/download/beta-fw" + # Per-variant OTA manifest URLs. Core defaults to the unified (standard) + # image; MSR-2_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 + # entries (mapping form would be replaced by the variant's block instead). + on_boot: + - priority: -100 + then: + - 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: + captive_portal: 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 + globals: - id: button_press_timestamp restore_value: no @@ -720,6 +772,21 @@ 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 update for the selected channel" + - delay: 3s + - script.execute: apply_ota_source + - script.wait: apply_ota_source + # 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 + - lambda: id(update_http_request).perform(true); + interval: - interval: 1s @@ -785,6 +852,19 @@ switch: id: setCo2AutoCalibration enable: false + - 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 @@ -812,9 +892,36 @@ select: name: "ld2410 Gate Size" 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: "${firmware_channel_default}" + 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). + # The manifest base is per-variant (ota_*_manifest substitutions) so legacy + # MSR-2_BLE builds keep their firmware-ble manifest. + then: + - lambda: |- + const bool beta = id(firmware_channel).current_option() == "Beta"; + 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 + - id: setCo2AutoCalibration mode: restart parameters: diff --git a/Integrations/ESPHome/MSR-2.yaml b/Integrations/ESPHome/MSR-2.yaml index 6f83199..e234925 100644 --- a/Integrations/ESPHome/MSR-2.yaml +++ b/Integrations/ESPHome/MSR-2.yaml @@ -49,10 +49,6 @@ wifi: ap: ssid: "Apollo MSR2 Hotspot" - -http_request: - verify_ssl: true - safe_mode: update: diff --git a/Integrations/ESPHome/MSR-2_BLE.yaml b/Integrations/ESPHome/MSR-2_BLE.yaml index 6e9e103..7e1e13a 100644 --- a/Integrations/ESPHome/MSR-2_BLE.yaml +++ b/Integrations/ESPHome/MSR-2_BLE.yaml @@ -1,3 +1,8 @@ +substitutions: + # Legacy BLE devices keep updating from the firmware-ble manifest. + ota_stable_manifest: "https://apolloautomation.github.io/MSR-2/firmware-ble/manifest.json" + ota_beta_manifest: "https://github.com/ApolloAutomation/MSR-2/releases/download/beta-fw/manifest-ble.json" + esphome: name: "${name}" friendly_name: Apollo MSR-2 @@ -45,15 +50,6 @@ ota: - platform: http_request id: ota_managed -bluetooth_proxy: - active: true -esp32_ble_tracker: - scan_parameters: - active: false - -http_request: - verify_ssl: true - safe_mode: update: diff --git a/Integrations/ESPHome/MSR-2_Factory.yaml b/Integrations/ESPHome/MSR-2_Factory.yaml index 791c36e..4bb51ee 100644 --- a/Integrations/ESPHome/MSR-2_Factory.yaml +++ b/Integrations/ESPHome/MSR-2_Factory.yaml @@ -52,9 +52,6 @@ ota: - platform: http_request id: ota_managed -http_request: - verify_ssl: true - safe_mode: update: diff --git a/Integrations/ESPHome/beta-channel/MSR-2.yaml b/Integrations/ESPHome/beta-channel/MSR-2.yaml new file mode 100644 index 0000000..df3dd71 --- /dev/null +++ b/Integrations/ESPHome/beta-channel/MSR-2.yaml @@ -0,0 +1,9 @@ +# Beta-channel build of MSR-2.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-2.yaml directly. +substitutions: + firmware_channel_default: "Beta" + +packages: + base: !include ../MSR-2.yaml diff --git a/Integrations/ESPHome/beta-channel/MSR-2_BLE.yaml b/Integrations/ESPHome/beta-channel/MSR-2_BLE.yaml new file mode 100644 index 0000000..c29efdf --- /dev/null +++ b/Integrations/ESPHome/beta-channel/MSR-2_BLE.yaml @@ -0,0 +1,9 @@ +# Beta-channel build of MSR-2_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-2_BLE.yaml directly. +substitutions: + firmware_channel_default: "Beta" + +packages: + base: !include ../MSR-2_BLE.yaml diff --git a/static/index.html b/static/index.html index 1c803db..bf16163 100644 --- a/static/index.html +++ b/static/index.html @@ -82,7 +82,7 @@