Skip to content

Merge beta into main#81

Merged
TrevorSchirmer merged 16 commits into
mainfrom
beta
Jul 14, 2026
Merged

Merge beta into main#81
TrevorSchirmer merged 16 commits into
mainfrom
beta

Conversation

@bharvey88

@bharvey88 bharvey88 commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Merge beta into main.

What's included

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added in-device firmware updates with Stable and Beta channel selection.
    • Added support for standard, factory, and BLE firmware variants.
    • Added Bluetooth Proxy controls and CO₂ automatic calibration settings.
    • Added safe-mode support for more reliable recovery.
    • Added rolling Beta firmware releases for testing.
  • Improvements

    • Updated the installer to use the factory firmware image.
    • Improved firmware update handling after Wi-Fi connection and redirected downloads.
    • Increased the minimum supported ESPHome version.

bharvey88 and others added 13 commits June 10, 2026 15:32
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.
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.
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
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.
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
Enable CO2 auto calibration by default with toggle to disable
Fix label-check and auto-assign on fork PRs
…-only OTA

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)
Unified firmware: runtime Bluetooth Proxy switch + Stable/Beta channel OTA
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

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

Next review available in: 44 minutes

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

How can I continue?

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

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 55573a6a-5f95-4091-ac3a-dd6ca204ed75

📥 Commits

Reviewing files that changed from the base of the PR and between 67a6778 and 4c0f31d.

📒 Files selected for processing (1)
  • .github/workflows/autoassign.yml

Walkthrough

This PR adds Stable/Beta firmware-channel OTA support, HTTP-managed updates, BLE proxy and CO2 calibration controls, beta firmware publishing, expanded firmware builds, factory installer targeting, and revised GitHub automation and release formatting.

Changes

Firmware delivery and OTA

Layer / File(s) Summary
Runtime OTA and device controls
Integrations/ESPHome/Core.yaml
Adds firmware channel selection, OTA source application, firmware update execution, BLE proxy control, and CO2 calibration controls.
Firmware image OTA configurations
Integrations/ESPHome/MSR-2*.yaml, Integrations/ESPHome/beta-channel/*
Updates ESPHome requirements and configures HTTP-managed OTA, manifest-backed updates, safe mode, and beta-channel entrypoints.
Firmware build and publishing pipeline
.github/workflows/build*.yml, static/index.html
Expands stable image builds, adds beta build and rolling-release publishing, and points the installer to the factory manifest.

GitHub automation

Layer / File(s) Summary
Workflow permissions and label automation
.github/workflows/autoassign.yml, .github/workflows/ci.yml, .github/workflows/label-check.yml
Moves fork-compatible automation to pull_request_target, reduces CI permissions, and adds a dedicated label-check workflow.
Release formatting
.github/release-drafter.yml
Formats category titles and release headings in bold while removing static release-body content.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant GitHub
  participant BetaWorkflow
  participant ESPHomeBuild
  participant BetaRelease
  participant Device
  GitHub->>BetaWorkflow: push beta branch or manual dispatch
  BetaWorkflow->>ESPHomeBuild: build standard and BLE beta images
  ESPHomeBuild-->>BetaWorkflow: return firmware artifacts
  BetaWorkflow->>BetaRelease: rewrite manifests and upload assets
  Device->>BetaRelease: request selected firmware manifest
  BetaRelease-->>Device: return firmware metadata and assets
Loading

Possibly related PRs

Suggested labels: breaking-change

Suggested reviewers: trevorschirmer

Poem

I’m a rabbit with firmware to spare,
Stable and Beta hop through the air.
OTA carrots roll down the release lane,
BLE ears scan through sunshine and rain.
Build, flash, and update—
Thump-thump, ship it! 🐇

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

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Resolve workflow conflicts by taking main's consolidated reusable
workflows (ci.yml, autoassign.yml) from #78. Brings in the MSR-2
datasheet, standardized PR template, and dependabot updates.
Switch the auto-assign trigger from pull_request to
pull_request_target and drop the fork-skip guard, so PRs opened
from forks get auto-assigned. Fork pull_request runs only receive a
read-only GITHUB_TOKEN, which is why assignment was skipped for
them. pull_request_target runs in the base-repo context with a
write token; safe here because the workflow never checks out or
executes PR code.
Auto-assign on fork PRs via pull_request_target

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

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

96-99: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Make jq manifest rewrite resilient to missing parts.

If a build does not produce factory bins (e.g., if ESPHome configuration changes or standard OTA-only builds are used), the parts array will be missing from the manifest. In that case, |= map(...) on a null value will cause jq to throw a Cannot iterate over null error, failing the workflow.

Consider guarding the parts transformation to ensure it only applies if the array exists.

🛠️ Proposed fix
-            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"
+            jq --arg base "$BASE" --arg pfx "$v-" '
+              .builds[0].ota.path = ($base + "/" + $pfx + (.builds[0].ota.path | sub(".*/"; "")))
+              | if .builds[0].parts != null then .builds[0].parts |= map(.path = ($base + "/" + $pfx + (.path | sub(".*/"; "")))) else . end
+            ' "$man" > "manifest-$v.json"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/build-beta.yml around lines 96 - 99, Update the jq filter
in the manifest rewrite to apply the .builds[0].parts path transformation only
when parts exists as an array, leaving missing parts unchanged while preserving
the existing OTA path rewrite.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/autoassign.yml:
- Line 8: Restrict the pull_request_target trigger to the opened event only by
adding its explicit event-type configuration under pull_request_target in the
workflow. Preserve the existing workflow jobs and prevent execution for
synchronize and reopened events.

In `@Integrations/ESPHome/Core.yaml`:
- Around line 775-788: Update the Firmware Update template action and the
manifest-check completion flow associated with update_http_request so lambda
id(update_http_request).perform(true) runs only after the asynchronous manifest
check completes. Remove the fixed 5s delay and trigger installation from that
completion path, ensuring it uses the newly fetched channel URL and MD5.
- Around line 837-853: Update the CO2 auto-calibration flow around the
setCo2AutoCalibration script and co2_auto_calibration switch so SCD4x stop/start
command failures are detected before committing the optimistic switch state.
Check both command results and revert or defer the switch state when either
write fails, while preserving the requested state only after the restart
succeeds.

---

Nitpick comments:
In @.github/workflows/build-beta.yml:
- Around line 96-99: Update the jq filter in the manifest rewrite to apply the
.builds[0].parts path transformation only when parts exists as an array, leaving
missing parts unchanged while preserving the existing OTA path rewrite.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 49ba3d1a-4e4c-4e36-90f8-def37a850745

📥 Commits

Reviewing files that changed from the base of the PR and between 23fbf14 and 67a6778.

📒 Files selected for processing (13)
  • .github/release-drafter.yml
  • .github/workflows/autoassign.yml
  • .github/workflows/build-beta.yml
  • .github/workflows/build.yml
  • .github/workflows/ci.yml
  • .github/workflows/label-check.yml
  • Integrations/ESPHome/Core.yaml
  • Integrations/ESPHome/MSR-2.yaml
  • Integrations/ESPHome/MSR-2_BLE.yaml
  • Integrations/ESPHome/MSR-2_Factory.yaml
  • Integrations/ESPHome/beta-channel/MSR-2.yaml
  • Integrations/ESPHome/beta-channel/MSR-2_BLE.yaml
  • static/index.html
💤 Files with no reviewable changes (1)
  • .github/workflows/ci.yml

Comment thread .github/workflows/autoassign.yml
Comment on lines +775 to +788
- 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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n -C3 'min_version|apply_ota_source|update_http_request.*perform' Integrations/ESPHome
curl -fsSL \
  https://raw.githubusercontent.com/esphome/esphome/2025.11.0/esphome/components/http_request/update/http_request_update.cpp |
  tr '}' '}\n' |
  rg -n 'xTaskCreate|HttpRequestUpdate::perform|update_info'

Repository: ApolloAutomation/MSR-2

Length of output: 5210


🏁 Script executed:

#!/bin/bash
rg -n -C3 'get_update_available_trigger|update_available|on_update|perform\(true\)|apply_ota_source|update_http_request' Integrations/ESPHome

Repository: ApolloAutomation/MSR-2

Length of output: 5421


Wait for the manifest check to finish before calling perform(true)

The fixed 5s delay can still race the async update task, so perform(true) may consume the previous channel’s URL/MD5. Trigger the install from the manifest-check completion path instead of sleeping.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Integrations/ESPHome/Core.yaml` around lines 775 - 788, Update the Firmware
Update template action and the manifest-check completion flow associated with
update_http_request so lambda id(update_http_request).perform(true) runs only
after the asynchronous manifest check completes. Remove the fixed 5s delay and
trigger installation from that completion path, ensuring it uses the newly
fetched channel URL and MD5.

Comment on lines +837 to +853
- 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
curl -fsSL \
  https://raw.githubusercontent.com/esphome/esphome/2025.11.0/esphome/components/scd4x/scd4x.cpp |
  rg -n -C3 'write_command|start_measurement|automatic self calibration'

Repository: ApolloAutomation/MSR-2

Length of output: 4721


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the relevant sections with line numbers.
sed -n '820,870p' Integrations/ESPHome/Core.yaml
printf '\n---\n'
sed -n '930,970p' Integrations/ESPHome/Core.yaml

printf '\n=== script definitions ===\n'
rg -n -C4 'setCo2AutoCalibration|co2_auto_calibration|automatic self calibration|SCD4X_CMD_AUTOMATIC_SELF_CALIBRATION|SCD4X_CMD_STOP_MEASUREMENTS|SCD4X_CMD_START_CONTINUOUS_MEASUREMENTS' Integrations/ESPHome/Core.yaml

Repository: ApolloAutomation/MSR-2

Length of output: 4029


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '820,870p' Integrations/ESPHome/Core.yaml | cat -n
printf '\n---\n'
sed -n '930,970p' Integrations/ESPHome/Core.yaml | cat -n
printf '\n=== matches ===\n'
rg -n -C3 'setCo2AutoCalibration|co2_auto_calibration|automatic self calibration|SCD4X_CMD_STOP_MEASUREMENTS|SCD4X_CMD_START_CONTINUOUS_MEASUREMENTS' Integrations/ESPHome/Core.yaml

Repository: ApolloAutomation/MSR-2

Length of output: 4484


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show just the lines around the named helper.
rg -n -C8 'setCo2AutoCalibration' Integrations/ESPHome/Core.yaml

Repository: ApolloAutomation/MSR-2

Length of output: 1586


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the full helper body and surrounding logic.
sed -n '925,955p' Integrations/ESPHome/Core.yaml | cat -n

# Also inspect any direct calls to the switch state that might publish or revert it.
rg -n -C4 'co2_auto_calibration|setCo2AutoCalibration|publish_state|turn_on:|turn_off:' Integrations/ESPHome/Core.yaml

Repository: ApolloAutomation/MSR-2

Length of output: 4173


Handle SCD4x command failures before leaving the optimistic switch on. setCo2AutoCalibration ignores the stop/start write results, so a failed restart can leave periodic measurements stopped while the switch still shows the requested state. Check both commands and revert or delay the state update until they succeed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Integrations/ESPHome/Core.yaml` around lines 837 - 853, Update the CO2
auto-calibration flow around the setCo2AutoCalibration script and
co2_auto_calibration switch so SCD4x stop/start command failures are detected
before committing the optimistic switch state. Check both command results and
revert or defer the switch state when either write fails, while preserving the
requested state only after the restart succeeds.

@TrevorSchirmer
TrevorSchirmer merged commit f065139 into main Jul 14, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants