Skip to content

Enable CO2 auto calibration by default with toggle to disable#97

Open
bharvey88 wants to merge 2 commits into
ApolloAutomation:betafrom
bharvey88:co2-asc-toggle
Open

Enable CO2 auto calibration by default with toggle to disable#97
bharvey88 wants to merge 2 commits into
ApolloAutomation:betafrom
bharvey88:co2-asc-toggle

Conversation

@bharvey88

@bharvey88 bharvey88 commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Version: 26.6.10.1

What does this implement/fix?

Enables the SCD40's automatic self-calibration (ASC) by default and adds a "CO2 Auto Calibration" switch so users can turn it off.

  • With ASC on (the new default, matching the ESPHome default), the sensor corrects its own baseline as long as it sees fresh air (about 420 ppm) at least once a week. The manual 420 ppm recalibration every 1-2 years is no longer needed for most homes.
  • The new switch is visible under Configuration, defaults to on, and persists across reboots and updates. Toggling takes effect immediately: the script stops periodic measurement, sends the ASC command over I2C (using the scd4x component's public write_command), and restarts measurement, the same sequence the component uses for forced calibration. The saved choice is re-applied ~20 seconds after every boot, since the SCD40 loses the setting on power loss and ESPHome re-applies the YAML default during setup.
  • The "Calibrate SCD40 To 420ppm" button and the calibrate_co2_value action are unchanged and keep working whether ASC is on or off.
  • Version bumped to 26.6.10.1.

Why a breaking change: existing devices get ASC turned on when they update. Readings may shift over the first days as the baseline self-corrects. Users whose device sits in a space that rarely gets fresh air (a sealed office, a basement, a grow room) should turn the new switch off and keep calibrating manually. Suggest including this in the release notes.

The same change is rolling out to MSR-2 and MTR-1 (identical), and R_PRO-1 (toggle fix/rename; its ASC was already on by default). A wiki update covering all four products is ready to merge alongside the firmware releases.

Verification: esphome compile passes for AIR-1.yaml, AIR-1_BLE.yaml, and AIR-1_Factory.yaml (ESPHome 2026.1.3). Tested on hardware.

Types of changes

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

Checklist / Checklijst:

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

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

  • Added/updated documentation for the web page

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a new CO2 Auto Calibration toggle switch enabling users to control automatic sensor calibration settings.
  • Chores

    • Updated ESPHome framework to the latest version for enhanced stability and features.
    • Enabled automatic self-calibration for the CO2 sensor by default to improve measurement accuracy and reliability.

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

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

More reviews will be available in 21 minutes and 21 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

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.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 96686609-a7cc-4ce8-a9c1-085970b8da63

📥 Commits

Reviewing files that changed from the base of the PR and between 01db027 and ccbfe30.

📒 Files selected for processing (1)
  • Integrations/ESPHome/Core.yaml

Walkthrough

ESPHome configuration upgraded to version 26.6.10.1 with SCD4x CO2 sensor automatic self-calibration enabled by default. New user-facing switch allows runtime toggle of calibration via a dedicated restart-mode script that manages boot delay, measurement state, calibration application, and command sequencing.

Changes

CO2 Auto-Calibration Feature

Layer / File(s) Summary
Version and SCD4x baseline configuration
Integrations/ESPHome/Core.yaml
Version substitution updated to 26.6.10.1 and SCD4x sensor automatic_self_calibration enabled by default.
CO2 calibration user controls
Integrations/ESPHome/Core.yaml
New co2_auto_calibration template switch with on/off handlers invoking setCo2AutoCalibration script; script handles boot wait, measurement pausing, calibration state application via set_automatic_self_calibration(), command writing, and measurement restart.
Supporting configuration and formatting
Integrations/ESPHome/Core.yaml
Prevent_sleep switch YAML formatting adjusted around lambda block; existing testScript reference preserved.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Suggested labels

new-feature, breaking-change

Suggested reviewers

  • TrevorSchirmer

Poem

🐰 A switch flips for CO2's keen care,
Auto-calibration now in the air,
Scripts dance in ESPHome's delight,
Sensors breathe true, day and night! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: enabling CO2 automatic self-calibration by default and adding a toggle switch, which matches the primary objectives of the PR.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

❤️ Share

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

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

🧹 Nitpick comments (3)
Integrations/ESPHome/Core.yaml (3)

505-516: ⚡ Quick win

Add error checking for start/stop measurement commands.

The script checks the return value of write_command for the calibration setting (line 509) but not for stop (line 505) or start (line 516) periodic measurement. If these commands fail, the sensor could be left in an inconsistent state (e.g., stopped but not restarted).

🛡️ Proposed error checking
      - lambda: |-
-         id(scd40).write_command((uint16_t) 0x3F86); //stop periodic measurement
+         if (!id(scd40).write_command((uint16_t) 0x3F86)) {
+           ESP_LOGE("Apollo", "Failed to stop CO2 periodic measurement");
+           return;  // Abort if we can't stop 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");
+           // Still try to restart measurement even if calibration failed
          } else {
            ESP_LOGI("Apollo", "CO2 auto calibration %s", enable ? "enabled" : "disabled");
          }
      - delay: 10ms
      - lambda: |-
-         id(scd40).write_command((uint16_t) 0x21B1); //start periodic measurement
+         if (!id(scd40).write_command((uint16_t) 0x21B1)) {
+           ESP_LOGE("Apollo", "Failed to restart CO2 periodic measurement");
+         }

Note: Even if calibration fails, the measurement should be restarted to avoid leaving the sensor in a stopped state.

🤖 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 505 - 516, The stop/start
measurement commands (id(scd40).write_command with 0x3F86 and 0x21B1) currently
lack error checking; update the lambdas that call
id(scd40).write_command((uint16_t)0x3F86) and
id(scd40).write_command((uint16_t)0x21B1) to check their boolean return values
and log failures via ESP_LOGE (include a clear message like "Failed to stop
periodic measurement" / "Failed to start periodic measurement"); ensure you
still attempt to start measurements after attempting calibration even if earlier
write_command calls fail so the sensor is not left stopped.

469-485: ⚡ Quick win

Consider adding error feedback for calibration failures.

The switch uses optimistic: true, so it updates immediately when toggled. If the setCo2AutoCalibration script fails (I2C error, sensor not responding), the switch state won't reflect the actual sensor configuration.

♻️ Proposed enhancement to sync state on error

Modify the script to return a status value and update the switch handlers to revert state on failure:

  - platform: template
    name: "CO2 Auto Calibration"
    id: co2_auto_calibration
    icon: mdi:molecule-co2
    restore_mode: RESTORE_DEFAULT_ON
-   optimistic: true
+   optimistic: false
    entity_category: "config"
+   lambda: |-
+     // Read actual state from sensor if possible, or use switch state
+     return id(co2_auto_calibration).state;
    on_turn_on:
      then:
        - script.execute:
            id: setCo2AutoCalibration
            enable: true
+       - if:
+           condition:
+             lambda: "return id(setCo2AutoCalibration).is_running();"
+           then:
+             - script.wait: setCo2AutoCalibration
    on_turn_off:
      then:
        - script.execute:
            id: setCo2AutoCalibration
            enable: false
+       - if:
+           condition:
+             lambda: "return id(setCo2AutoCalibration).is_running();"
+           then:
+             - script.wait: setCo2AutoCalibration

Note: ESPHome doesn't provide a direct way to read the ASC setting back from the sensor, so perfect synchronization isn't possible. At minimum, consider logging prominently when the write_command fails so users can investigate.

🤖 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 469 - 485, The CO2 Auto
Calibration switch (platform: template, name: "CO2 Auto Calibration", id:
co2_auto_calibration) is optimistic and can get out of sync if the script
setCo2AutoCalibration fails; update setCo2AutoCalibration to return a status
(success/failure) and add explicit error logging inside that script when the
sensor write_command fails, then change the switch's on_turn_on/on_turn_off
handlers to check the returned status and, on failure, revert the switch state
(e.g., call switch.turn_off/turn_on or publish the opposite state) and log a
prominent error so the UI reflects the real outcome.

496-500: 💤 Low value

Boot delay could wait longer than necessary.

The current logic checks if millis() < 20000 and then delays for 20s. If the script runs early (e.g., at 5s after boot), the total wait is 25s. If it runs at 19s, the wait is 39s.

♻️ More precise delay calculation
      - if:
          condition:
            lambda: "return millis() < 20000;"
          then:
-           - delay: 20s
+           - lambda: |-
+               uint32_t wait_ms = 20000 - millis();
+               delay(wait_ms);

Or use a simpler unconditional delay if the script is only called at boot:

-     - if:
-         condition:
-           lambda: "return millis() < 20000;"
-         then:
-           - delay: 20s
+     - lambda: |-
+         if (millis() < 20000) {
+           delay(20000 - millis());
+         }

Note: The current approach is conservative and ensures at least 20s has passed, which may be preferred for robustness.

🤖 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 496 - 500, The current
conditional uses the lambda "return millis() < 20000;" and then always applies a
fixed "delay: 20s", which can overshoot; change the block so it delays only the
remaining time until 20s since boot (e.g., compute remaining = 20000 - millis()
and delay that many milliseconds/seconds) or, if this action always runs at
boot, replace the conditional with a single unconditional "delay: 20s". Update
the conditional branch that contains the lambda and the "delay: 20s" to
implement the remaining-time calculation or the unconditional delay as
appropriate.
🤖 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.

Nitpick comments:
In `@Integrations/ESPHome/Core.yaml`:
- Around line 505-516: The stop/start measurement commands
(id(scd40).write_command with 0x3F86 and 0x21B1) currently lack error checking;
update the lambdas that call id(scd40).write_command((uint16_t)0x3F86) and
id(scd40).write_command((uint16_t)0x21B1) to check their boolean return values
and log failures via ESP_LOGE (include a clear message like "Failed to stop
periodic measurement" / "Failed to start periodic measurement"); ensure you
still attempt to start measurements after attempting calibration even if earlier
write_command calls fail so the sensor is not left stopped.
- Around line 469-485: The CO2 Auto Calibration switch (platform: template,
name: "CO2 Auto Calibration", id: co2_auto_calibration) is optimistic and can
get out of sync if the script setCo2AutoCalibration fails; update
setCo2AutoCalibration to return a status (success/failure) and add explicit
error logging inside that script when the sensor write_command fails, then
change the switch's on_turn_on/on_turn_off handlers to check the returned status
and, on failure, revert the switch state (e.g., call switch.turn_off/turn_on or
publish the opposite state) and log a prominent error so the UI reflects the
real outcome.
- Around line 496-500: The current conditional uses the lambda "return millis()
< 20000;" and then always applies a fixed "delay: 20s", which can overshoot;
change the block so it delays only the remaining time until 20s since boot
(e.g., compute remaining = 20000 - millis() and delay that many
milliseconds/seconds) or, if this action always runs at boot, replace the
conditional with a single unconditional "delay: 20s". Update the conditional
branch that contains the lambda and the "delay: 20s" to implement the
remaining-time calculation or the unconditional delay as appropriate.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7118d14d-bb94-4647-a777-24452958a39e

📥 Commits

Reviewing files that changed from the base of the PR and between 025e0f7 and 01db027.

📒 Files selected for processing (1)
  • Integrations/ESPHome/Core.yaml

@bharvey88 bharvey88 changed the base branch from main to beta June 10, 2026 20:28
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking-change Breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant