Skip to content

feat: Add SCD4X - #7198

Merged
thebentern merged 140 commits into
meshtastic:masterfrom
fablabbcn:feat/add-scd4x
Feb 9, 2026
Merged

thebentern merged 140 commits into
meshtastic:masterfrom
fablabbcn:feat/add-scd4x

Conversation

@oscgonfer

@oscgonfer oscgonfer commented Jul 2, 2025 •

Copy link
Copy Markdown
Contributor

This PR adds support for the SCD4X CO2 sensors by sensirion.
It builds upon on the great work by @Coloradohusky, @hafu and @fifieldt in #4601 and https://github.com/hafu/meshtastic-firmware/tree/add-scd30 (although it doesn't add SCD30 support).

This PR tries to sync that work with the current state master branch, and it's rebased on top of #7190, which is an attempt to decouple AirQualityTelemetry from PMSA0003I sensor (I have just ordered one sensor to test). I also started using the new Sensirion library for this sensor, which has a new release (and a very annoying change in the library name import).

This also goes along with meshtastic/protobufs#719 to support having real temperature and the sensor temperature separately. Testing it, you can see some differences between two sensors that are right next to each other (SHT31 and the one inside the SCD4X).

decoded {
  portnum: TELEMETRY_APP
  payload: "time: 1751450604 air_quality_metrics {   co2: 1602   co2_temperature: 28.4126797   co2_humidity: 55.2819099 }"
}
decoded {
  portnum: TELEMETRY_APP
  payload: "time: 1751450589 environment_metrics {   temperature: 31.69   relative_humidity: 50.19 }"
}

There are some comments that I point in the code directly below. I also tried to make it work with the screen, but I don't think my system allows for testing it ATM.

🤝 Attestations

  • I have tested that my proposed changes behave as described.
  • I have tested that my proposed changes do not cause any obvious regressions on the following devices:
    • Seeed ESP32-S3 Xiao board

@oscgonfer
oscgonfer marked this pull request as ready for review July 2, 2025 10:08
Comment thread src/modules/Telemetry/Sensor/SCD4XSensor.cpp Outdated
@oscgonfer

Copy link
Copy Markdown
Contributor Author

An additional comment: these type of sensors generally require a "control" interface to enable or disable features. This is mapped here - and could be a good outcome of the hackathon.

@oscgonfer oscgonfer changed the title Add SCD4X feat: Add SCD4X Jul 2, 2025
@thebentern

Copy link
Copy Markdown
Contributor

@oscgonfer protobufs are merged and generated upstream now

@oscgonfer
oscgonfer force-pushed the feat/add-scd4x branch 2 times, most recently from 247768e to 84865cd Compare July 2, 2025 12:46
@oscgonfer

oscgonfer commented Jul 2, 2025 •

Copy link
Copy Markdown
Contributor Author

rebased onto #7202 - just to keep in mind that the sensirion library needs to be 1.0.0 @thebentern

@fifieldt

fifieldt commented Jul 3, 2025

Copy link
Copy Markdown
Member

sensirion 1.1.0 was released - updated upstream.

@oscgonfer

Copy link
Copy Markdown
Contributor Author

sensirion 1.1.0 was released - updated upstream.

Merged

@hafu

hafu commented Jul 5, 2025

Copy link
Copy Markdown
Contributor

Thanks for your effort! I can confirm it works with:

@oscgonfer

Copy link
Copy Markdown
Contributor Author

Thanks for your effort! I can confirm it works with:

* Heltec v3

* RAK11310

* RAK4631 (needs [some fixes](https://github.com/meshtastic/firmware/pull/7190#pullrequestreview-2990183787) in

Thank you @hafu ! I fixed and rebased #7190

@vidplace7 vidplace7 added the enhancement New feature or request label Jul 18, 2025
@CLAassistant

CLAassistant commented Jul 22, 2025 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
1 out of 2 committers have signed the CLA.

✅ oscgonfer
❌ peterzqx
You have signed the CLA already but the status is still pending? Let us recheck it.

@oscgonfer

oscgonfer commented Feb 6, 2026 •

Copy link
Copy Markdown
Contributor Author

Hi @paulwalko @hafu

Do you want to give this a go? I believe it'd be final

@oscgonfer
oscgonfer marked this pull request as draft February 7, 2026 17:43
@paulwalko

Copy link
Copy Markdown

Hi @paulwalko @hafu

Do you want to give this a go? I believe it'd be final

Looks good to me! Verified with a diy nrf52 node to take measurements (scd41 only) + rak 4631 to upload them.

@oscgonfer

Copy link
Copy Markdown
Contributor Author

Cool! I need to fix a minor detail on the Sen5x part, but it'd be done shortly! I'll mark it as ready when it's done!

@oscgonfer
oscgonfer marked this pull request as ready for review February 8, 2026 09:45
@hafu

hafu commented Feb 8, 2026

Copy link
Copy Markdown
Contributor

Hi @paulwalko @hafu

Do you want to give this a go? I believe it'd be final

I'll try to test this next week with at least a Heltec v3

@oscgonfer

Copy link
Copy Markdown
Contributor Author

Resolved conflicts on merge. All good on my end, but let's see after tests.

@thebentern
thebentern merged commit 579a0af into meshtastic:master Feb 9, 2026
77 checks passed
@oscgonfer
oscgonfer deleted the feat/add-scd4x branch February 9, 2026 16:15
@Tomas-Kuchta-FPV

Tomas-Kuchta-FPV commented Feb 13, 2026 •

Copy link
Copy Markdown

Thanks for yor work. It seems to be working.
The high value is me breathing on it.

Screenshot_20260213-213914.png

@hafu

hafu commented Feb 14, 2026 •

Copy link
Copy Markdown
Contributor

Can also confirm it works using a Heltec v3.

I also have a MCP9808 temperature sensor connected to the I2C bus. At first it won't work and I got only SCD4X: Data is not ready messages. After connecting only the SCD4X sensor it worked immediately (after reset/reboot). Connected the MCP9809 again and no problems since. I assume it was the wiring on the bread board, which lead to communication issues on the I2C bus.

@oscgonfer

Copy link
Copy Markdown
Contributor Author

Thank you both @hafu and @Tomas-Kuchta-FPV for testing. Admin commands are now (WIP) implemented in meshtastic/python#903 in case you want to give it a whirl. The commands are used to modify calibration parameters and other config settings.

@Tomas-Kuchta-FPV

Copy link
Copy Markdown

Thank you both @hafu and @Tomas-Kuchta-FPV for testing. Admin commands are now (WIP) implemented in meshtastic/python#903 in case you want to give it a whirl. The commands are used to modify calibration parameters and other config settings.

I'll test it out.
Just one more thing is that the mobile apps also need to support the CO2 measurement and show it in measurements. I have tried to set up android studio but there seems to be a memory leak with linux, it gobles up ram and crashes. 😅

jeek pushed a commit to jeek/Meshtastic-Exploiteers-Hacker-Pager that referenced this pull request Jun 30, 2026
* Move PMSA003I to separate class and update AQ telemetry

* AirQualityTelemetry module not depend on PM sensor presence

* Remove commented line

* Fixes on PMS class

* Add missing warmup period to wakeUp function

* Fixes on compilation for different variants

* Add functions to check for I2C bus speed and set it

* Add ScreenFonts.h

Co-authored-by: Hannes Fuchs <hannes.fuchs+git@0xef.de>

* PMSA003I 1st round test

* Fix I2C scan speed

* Fix minor issues and bring back I2C SPEED def

* Remove PMSA003I library as its no longer needed

* Add functional SCD4X

* Fix screen frame for CO2

* Add admin commands to SCD4X class

* Add further admin commands and fixes.

* Remove unused I2C speed functions and cleanup

* Cleanup of SEN5X specific code added from switching branches
* Remove SCAN_I2C_CLOCK_SPEED block as its not needed
* Remove associated functions for setting I2C speed

* Unify build epoch to add flag in platformio-custom.py (meshtastic#7917)

* Unify build_epoch replacement logic in platformio-custom

* Missed one

* Fix build error in rak_wismesh_tap_v2 (meshtastic#7905)

In the logs was:
"No screen resolution defined in build_flags. Please define DISPLAY_SIZE."

set according to similar devices.

* Put guards in place around debug heap operations (meshtastic#7955)

* Put guards in place around debug heap operations

* Add macros to clean up code

* Add pointer as well

* Cleanup

* Fix memory leak in NextHopRouter: always free packet copy when removing from pending

* Formatting

* Only queue 2 client notification

* Merge pull request meshtastic#7965 from compumike/compumike/fix-nrf52-bluetooth-memory-leak

Fix memory leak in `NRF52Bluetooth`: allocate `BluetoothStatus` on stack, not heap

* Merge pull request meshtastic#7964 from compumike/compumike/fix-nimble-bluetooth-memory-leak

Fix memory leak in `NimbleBluetooth`: allocate `BluetoothStatus` on stack, not heap

* Update protobufs (meshtastic#7973)

Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>

* T-Lora Pager: Support LR1121 and SX1280 models (meshtastic#7956)

* T-Lora Pager: Support LR1121 and SX1280 models

* Remove ifdefs

* Trunk

* Trunk

* Static memory pool allocation (meshtastic#7966)

* Static memory pool

* Initializer

* T-Lora Pager: Support LR1121 and SX1280 models (meshtastic#7956)

* T-Lora Pager: Support LR1121 and SX1280 models

* Remove ifdefs

---------

Co-authored-by: WillyJL <me@willyjl.dev>

* Portduino dynamic alloc

* Missed

* Drop the limit

* Update meshtastic-esp8266-oled-ssd1306 digest to 0cbc26b (meshtastic#7977)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* Fix json report crashes on esp32 (meshtastic#7978)

* Tweak maximums

* Fix DRAM overflow on old esp32 targets

* Guard bad time warning logs using GPS_DEBUG (meshtastic#7897)

In 2.7.7 / 2.7.8 we introduced some new checks for time accuracy.

In combination, these result in a spamming of the logs when a bad time is found

When the GPS is active, we're calling the GPS thread every 0.2secs.

So this log could be printed 4,500 times in a no-lock scenario :)

Reserve this experience for developers using GPS_DEBUG.

Fixes meshtastic#7896

* Scale probe buffer size based on current baud rate (meshtastic#7975)

* Scale probe buffer size based on current baud rate

* Throttle bad time validation logging and fix time comparison logic

* Remove comment

* Missed the other instances

* Copy pasta

* Fix GPS gm_mktime memory leak (meshtastic#7981)

* Fix overflow of time value (meshtastic#7984)

* Fix overflow of time value

* Revert "Fix overflow of time value"

This reverts commit 0847969.

* That got boogered up

* Remove PMSA003 include from modules

* Add flag to exclude air quality module

* Rework PMSA003I to align with new I2C scanner

* Reworks AQ telemetry to match new dynamic allocation method
* Adds VBLE_I2C_CLOCK_SPEED build flag for sensors with different I2C speed requirements
* Reworks PMSA003I

* Move add sensor template to separate file

* Split telemetry on screen options

* Add variable I2C clock compile flag

* Added to Seeed Xiao S3 as demo

* Fix drawFrame in AQ module

* Module settings override to i2cScan module function

* Move to CAN_RECLOCK_I2C per architecture

* Add reclock function in TelemetrySensor.cpp
* Add flag in ESP32 common

* Minor fix

* Move I2C reclock function to src/detect

* Fix uninitMemberVar errors and compile issue

* Make sleep, wakeUp functions generic

* Fix STM32 builds

* Add exclude AQ sensor to builds that have environmental sensor excludes
* Add includes to AddI2CSensorTemplate.h

* SEN5X first pass

* WIP Sen5X functions

* Further (non-working) progress in SEN5X

* WIP Sen5X functions

* Changes on SEN5X library - removing pm_env as well

* Small cleanup of SEN5X sensors

* Minor change for SEN5X detection

* Remove dup code

* Enable PM sensor before sending telemetry.

This enables the PM sensor for a predefined period to allow for warmup.

Once telemetry is sent, the sensor shuts down again.

* Small cleanups in SEN5X sensor

* Add dynamic measurement interval for SEN5X

* Only disable SEN5X if enough time after reading.

* Idle for SEN5X on communication error

* Cleanup of logs and remove unnecessary delays

* Small TODO

* Settle on uint16_t for SEN5X PM data

* Make AQTelemetry sensors non-exclusive

* Implementation of cleaning in FS prefs and cleanup

* Remove unnecessary LOGS
* Add cleaning date storage in FS
* Report non-cumulative PN

* Bring back detection code for SEN5X after branch rebase

* Add placeholder for admin message

* Add VOC measurements and persistence (WIP)

* Adds VOC measurements and state
* Still not working on VOC Index persistence
* Should it stay in continuous mode?

* Add one-shot mode config flag to SEN5X

* Add nan checks on sensor data from SEN5X

* Working implementation on VOCState

* Adds initial timer for SEN55 to not sleep if VOCstate is not stable (1h)
* Adds conditions for stability and sensor state

* Fixes on VOC state and mode swtiching

* Adds a new RHT/Gas only mode, with 3600s stabilization time
* Fixes the VOCState buffer mismatch
* Fixes SEN50/54/55 model mistake

* Adapt SEN5X to new sensor list structure. Improve reclock.

* Improve reClockI2C conditions for different variants
* Add sleep, wakeUp, pendingForReady, hasSleep functions to PM sensors to save battery
* Add SEN5X

* Fix merge errors

* Small reordering in PMS class for consistency

* If one sensor fails, AQ telemetry still reports data

* Small formatting fix

* Add SEN5X to AQI in ScanI2C

* SCD4X now part of AQ module with template list

* Fixes difference between idle and sleep
* In LowPower, sleep is disabled
* Requires testing for I2C clock comms for commands

* Remove unnecessary import

* Add co2 to serialized AQ metrics

* Update library dependencies in platformio.ini

* Fix unitialized variables in SEN5X constructor

* Fix missing import

* Fix uninitMemberVars

* Fix import error for SCD4X

* Fix I2CClock logic

* Fix not reclocking back to 700000Hz

* Fix multiple sensors being read simultaneously

*  The logic in AQ module is different to the one in EnvironmentTelemetryModule.  In Env module, if any sensor fails to getMetrics, no valid flag for the module. This prevents other sensors to report data.

* Fix pending clock change in PMSA003

* Cleanup of SEN5X class

* Exclude AQ sensor from wio-e5 due to flash limitations

* Fix I2C clock change logic

* Make sure clock is always set to needed value

* Fix returns

* Fix trunk

* Fix on condition in reclock

* Fix trunk

* Add check on polling interval of sen5x

* Add missing serializer

---------

Co-authored-by: Hannes Fuchs <hannes.fuchs+git@0xef.de>
Co-authored-by: Nashui-Yan <yannashui10@gmail.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Tom Fifield <tom@tomfifield.net>
Co-authored-by: Mike Robbins <mrobbins@alum.mit.edu>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
Co-authored-by: WillyJL <me@willyjl.dev>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Itzdavid01 pushed a commit to Itzdavid01/firmware that referenced this pull request Sep 5, 2026
* Move PMSA003I to separate class and update AQ telemetry

* AirQualityTelemetry module not depend on PM sensor presence

* Remove commented line

* Fixes on PMS class

* Add missing warmup period to wakeUp function

* Fixes on compilation for different variants

* Add functions to check for I2C bus speed and set it

* Add ScreenFonts.h

Co-authored-by: Hannes Fuchs <hannes.fuchs+git@0xef.de>

* PMSA003I 1st round test

* Fix I2C scan speed

* Fix minor issues and bring back I2C SPEED def

* Remove PMSA003I library as its no longer needed

* Add functional SCD4X

* Fix screen frame for CO2

* Add admin commands to SCD4X class

* Add further admin commands and fixes.

* Remove unused I2C speed functions and cleanup

* Cleanup of SEN5X specific code added from switching branches
* Remove SCAN_I2C_CLOCK_SPEED block as its not needed
* Remove associated functions for setting I2C speed

* Unify build epoch to add flag in platformio-custom.py (meshtastic#7917)

* Unify build_epoch replacement logic in platformio-custom

* Missed one

* Fix build error in rak_wismesh_tap_v2 (meshtastic#7905)

In the logs was:
"No screen resolution defined in build_flags. Please define DISPLAY_SIZE."

set according to similar devices.

* Put guards in place around debug heap operations (meshtastic#7955)

* Put guards in place around debug heap operations

* Add macros to clean up code

* Add pointer as well

* Cleanup

* Fix memory leak in NextHopRouter: always free packet copy when removing from pending

* Formatting

* Only queue 2 client notification

* Merge pull request meshtastic#7965 from compumike/compumike/fix-nrf52-bluetooth-memory-leak

Fix memory leak in `NRF52Bluetooth`: allocate `BluetoothStatus` on stack, not heap

* Merge pull request meshtastic#7964 from compumike/compumike/fix-nimble-bluetooth-memory-leak

Fix memory leak in `NimbleBluetooth`: allocate `BluetoothStatus` on stack, not heap

* Update protobufs (meshtastic#7973)

Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>

* T-Lora Pager: Support LR1121 and SX1280 models (meshtastic#7956)

* T-Lora Pager: Support LR1121 and SX1280 models

* Remove ifdefs

* Trunk

* Trunk

* Static memory pool allocation (meshtastic#7966)

* Static memory pool

* Initializer

* T-Lora Pager: Support LR1121 and SX1280 models (meshtastic#7956)

* T-Lora Pager: Support LR1121 and SX1280 models

* Remove ifdefs

---------

Co-authored-by: WillyJL <me@willyjl.dev>

* Portduino dynamic alloc

* Missed

* Drop the limit

* Update meshtastic-esp8266-oled-ssd1306 digest to 0cbc26b (meshtastic#7977)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* Fix json report crashes on esp32 (meshtastic#7978)

* Tweak maximums

* Fix DRAM overflow on old esp32 targets

* Guard bad time warning logs using GPS_DEBUG (meshtastic#7897)

In 2.7.7 / 2.7.8 we introduced some new checks for time accuracy.

In combination, these result in a spamming of the logs when a bad time is found

When the GPS is active, we're calling the GPS thread every 0.2secs.

So this log could be printed 4,500 times in a no-lock scenario :)

Reserve this experience for developers using GPS_DEBUG.

Fixes meshtastic#7896

* Scale probe buffer size based on current baud rate (meshtastic#7975)

* Scale probe buffer size based on current baud rate

* Throttle bad time validation logging and fix time comparison logic

* Remove comment

* Missed the other instances

* Copy pasta

* Fix GPS gm_mktime memory leak (meshtastic#7981)

* Fix overflow of time value (meshtastic#7984)

* Fix overflow of time value

* Revert "Fix overflow of time value"

This reverts commit 0847969.

* That got boogered up

* Remove PMSA003 include from modules

* Add flag to exclude air quality module

* Rework PMSA003I to align with new I2C scanner

* Reworks AQ telemetry to match new dynamic allocation method
* Adds VBLE_I2C_CLOCK_SPEED build flag for sensors with different I2C speed requirements
* Reworks PMSA003I

* Move add sensor template to separate file

* Split telemetry on screen options

* Add variable I2C clock compile flag

* Added to Seeed Xiao S3 as demo

* Fix drawFrame in AQ module

* Module settings override to i2cScan module function

* Move to CAN_RECLOCK_I2C per architecture

* Add reclock function in TelemetrySensor.cpp
* Add flag in ESP32 common

* Minor fix

* Move I2C reclock function to src/detect

* Fix uninitMemberVar errors and compile issue

* Make sleep, wakeUp functions generic

* Fix STM32 builds

* Add exclude AQ sensor to builds that have environmental sensor excludes
* Add includes to AddI2CSensorTemplate.h

* SEN5X first pass

* WIP Sen5X functions

* Further (non-working) progress in SEN5X

* WIP Sen5X functions

* Changes on SEN5X library - removing pm_env as well

* Small cleanup of SEN5X sensors

* Minor change for SEN5X detection

* Remove dup code

* Enable PM sensor before sending telemetry.

This enables the PM sensor for a predefined period to allow for warmup.

Once telemetry is sent, the sensor shuts down again.

* Small cleanups in SEN5X sensor

* Add dynamic measurement interval for SEN5X

* Only disable SEN5X if enough time after reading.

* Idle for SEN5X on communication error

* Cleanup of logs and remove unnecessary delays

* Small TODO

* Settle on uint16_t for SEN5X PM data

* Make AQTelemetry sensors non-exclusive

* Implementation of cleaning in FS prefs and cleanup

* Remove unnecessary LOGS
* Add cleaning date storage in FS
* Report non-cumulative PN

* Bring back detection code for SEN5X after branch rebase

* Add placeholder for admin message

* Add VOC measurements and persistence (WIP)

* Adds VOC measurements and state
* Still not working on VOC Index persistence
* Should it stay in continuous mode?

* Add one-shot mode config flag to SEN5X

* Add nan checks on sensor data from SEN5X

* Working implementation on VOCState

* Adds initial timer for SEN55 to not sleep if VOCstate is not stable (1h)
* Adds conditions for stability and sensor state

* Fixes on VOC state and mode swtiching

* Adds a new RHT/Gas only mode, with 3600s stabilization time
* Fixes the VOCState buffer mismatch
* Fixes SEN50/54/55 model mistake

* Adapt SEN5X to new sensor list structure. Improve reclock.

* Improve reClockI2C conditions for different variants
* Add sleep, wakeUp, pendingForReady, hasSleep functions to PM sensors to save battery
* Add SEN5X

* Fix merge errors

* Small reordering in PMS class for consistency

* If one sensor fails, AQ telemetry still reports data

* Small formatting fix

* Add SEN5X to AQI in ScanI2C

* SCD4X now part of AQ module with template list

* Fixes difference between idle and sleep
* In LowPower, sleep is disabled
* Requires testing for I2C clock comms for commands

* Remove unnecessary import

* Add co2 to serialized AQ metrics

* Update library dependencies in platformio.ini

* Fix unitialized variables in SEN5X constructor

* Fix missing import

* Fix uninitMemberVars

* Fix import error for SCD4X

* Fix I2CClock logic

* Fix not reclocking back to 700000Hz

* Fix multiple sensors being read simultaneously

*  The logic in AQ module is different to the one in EnvironmentTelemetryModule.  In Env module, if any sensor fails to getMetrics, no valid flag for the module. This prevents other sensors to report data.

* Fix pending clock change in PMSA003

* Cleanup of SEN5X class

* Exclude AQ sensor from wio-e5 due to flash limitations

* Fix I2C clock change logic

* Make sure clock is always set to needed value

* Fix returns

* Fix trunk

* Fix on condition in reclock

* Fix trunk

* Add check on polling interval of sen5x

* Add missing serializer

---------

Co-authored-by: Hannes Fuchs <hannes.fuchs+git@0xef.de>
Co-authored-by: Nashui-Yan <yannashui10@gmail.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Tom Fifield <tom@tomfifield.net>
Co-authored-by: Mike Robbins <mrobbins@alum.mit.edu>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
Co-authored-by: WillyJL <me@willyjl.dev>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.