Changed SCD30 library FrogmoreScd30 to Sensirion arduino-i2c-scd30 v1.1.1

This commit is contained in:
Theo Arends
2026-06-05 15:31:11 +02:00
parent 6191473dfd
commit 01d044cedc
23 changed files with 1828 additions and 257 deletions

View File

@@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file.
### Breaking Changed
### Changed
- SCD30 library FrogmoreScd30 to Sensirion arduino-i2c-scd30 v1.1.1
- SCD4x library FrogmoreScd40 to Sensirion arduino-i2c-scd4x v1.1.0
- SPS30 library Sensirion arduino-i2c-sps30 v1.0.1

View File

@@ -135,6 +135,7 @@ The latter links can be used for OTA upgrades too like ``OtaUrl https://ota.tasm
### Changed
- ESP32 Platform from 2025.04.30 to 2026.05.50, Framework (Arduino Core) from v3.1.11 to v3.3.8.260506 and IDF from v5.3.4.260127 to v5.5.4.260407 [#24718](https://github.com/arendst/Tasmota/issues/24718)
- SCD30 library FrogmoreScd30 to Sensirion arduino-i2c-scd30 v1.1.1
- SCD4x library FrogmoreScd40 to Sensirion arduino-i2c-scd4x v1.1.0
- SPS30 library Sensirion arduino-i2c-sps30 v1.0.1
- Increase security by inverting state of `define DISABLE_REFERER_CHK` controlling remote HTTP access which is now default off

View File

@@ -77,6 +77,10 @@ void TwoWire::setClockStretchLimit(uint32_t limit) {
twi.setClockStretchLimit(limit);
}
uint8_t TwoWire::status(void) {
return twi.status();
}
size_t TwoWire::requestFrom(uint8_t address, size_t size, bool sendStop) {
if (size > BUFFER_LENGTH) {
size = BUFFER_LENGTH;

View File

@@ -60,6 +60,7 @@ public:
void begin();
void setClock(uint32_t);
void setClockStretchLimit(uint32_t);
uint8_t status();
void beginTransmission(uint8_t);
void beginTransmission(int);
uint8_t endTransmission(void);

View File

@@ -0,0 +1,14 @@
---
Language: Cpp
BasedOnStyle: LLVM
IndentWidth: 4
AlignAfterOpenBracket: Align
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: false
IndentCaseLabels: true
SpacesBeforeTrailingComments: 2
PointerAlignment: Left
AlignEscapedNewlines: Left
ForEachMacros: ['TEST_GROUP', 'TEST']
...

View File

@@ -0,0 +1,33 @@
# CHANGELOG
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [1.1.1] - 2026-4-27
### Added
- Added `read_serial_number` to example usage
## [1.1.0] - 2026-4-24
### Added
- Added `read_serial_number` command to read out the sensor's serial number
## [1.0.0] - 2025-8-25
### Changed
- Updated to latest driver framework
## [0.1.0] - 2022-4-07
### Added
- Initial SCD30 driver release
[Unreleased]: https://github.com/Sensirion/arduino-i2c-scd30/compare/1.1.1...HEAD
[1.1.1]: https://github.com/Sensirion/arduino-i2c-scd30/compare/1.1.0...1.1.1
[1.1.0]: https://github.com/Sensirion/arduino-i2c-scd30/compare/1.0.0...1.1.0
[1.0.0]: https://github.com/Sensirion/arduino-i2c-scd30/compare/0.1.0...1.0.0
[0.1.0]: https://github.com/Sensirion/arduino-i2c-scd30/releases/tag/0.1.0

View File

@@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2026, Sensirion AG
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,221 @@
# Sensirion I²C SCD30 Arduino Library
This is the Sensirion SCD30 library for Arduino allowing you to
communicate with a SCD30 sensor
over I²C.
<img src="images/sensor_scd30_image.jpg" width="300px">
Click [here](https://sensirion.com/products/catalog/SCD30/) to learn more about the Sensirion SCD30 sensor.
The default I²C address of [SCD30](https://sensirion.com/products/catalog/SCD30/) is **0x61**.
## Installation of the library
This library can be installed using the Arduino Library manager:
Start the [Arduino IDE](http://www.arduino.cc/en/main/software) and open
the Library Manager via
`Sketch``Include Library``Manage Libraries...`
Search for the `Sensirion I2C SCD30` library in the `Filter
your search...` field and install it by clicking the `install` button.
If you cannot find it in the library manager, download the latest release as .zip file
and add it to your [Arduino IDE](http://www.arduino.cc/en/main/software) via
`Sketch``Include Library``Add .ZIP Library...`
Don't forget to **install the dependencies** listed below the same way via library
manager or `Add .ZIP Library`
#### Dependencies
* [Sensirion Core](https://github.com/Sensirion/arduino-core)
## Connect the sensor
Use the following pin description to connect your SCD30 to the standard I²C bus of your Arduino board:
<img src="images/scd30_pinout.jpg" width="300px">
| *Pin* | *Cable Color* | *Name* | *Description* | *Comments* |
|-------|---------------|:------:|----------------|------------|
| 1 | red | VDD | Supply Voltage | 3.3V to 5.5V
| 2 | black | GND | Ground |
| 3 | yellow | SCL | I2C: Serial clock input |
| 4 | green | SDA | I2C: Serial data input / output |
| 5 | | RDY | | High when data is available - do not connect
| 6 | | PWM | | do not connect
| 7 | blue | SEL | Interface select | Pull to ground or floating for I2C
The recommended voltage is 3.3V.
### Board specific wiring
You will find pinout schematics for recommended board models below:
<details><summary>Arduino Uno</summary>
<p>
| *SCD30* | *SCD30 Pin* | *Cable Color* | *Board Pin* |
| :---: | --- | --- | --- |
| VDD | 1 | red | 3.3V |
| GND | 2 | black | GND |
| SCL | 3 | yellow | D19/SCL |
| SDA | 4 | green | D18/SDA |
| RDY | 5 | | |
| PWM | 6 | | |
| SEL | 7 | blue | GND |
<img src="images/Arduino-Uno-Rev3-i2c-pinout-3.3V-SEL.png" width="600px">
</p>
</details>
<details><summary>Arduino Nano</summary>
<p>
| *SCD30* | *SCD30 Pin* | *Cable Color* | *Board Pin* |
| :---: | --- | --- | --- |
| VDD | 1 | red | 3.3V |
| GND | 2 | black | GND |
| SCL | 3 | yellow | A5 |
| SDA | 4 | green | A4 |
| RDY | 5 | | |
| PWM | 6 | | |
| SEL | 7 | blue | GND |
<img src="images/Arduino-Nano-i2c-pinout-3.3V-SEL.png" width="600px">
</p>
</details>
<details><summary>Arduino Micro</summary>
<p>
| *SCD30* | *SCD30 Pin* | *Cable Color* | *Board Pin* |
| :---: | --- | --- | --- |
| VDD | 1 | red | 3.3V |
| GND | 2 | black | GND |
| SCL | 3 | yellow | ~D3/SCL |
| SDA | 4 | green | D2/SDA |
| RDY | 5 | | |
| PWM | 6 | | |
| SEL | 7 | blue | GND |
<img src="images/Arduino-Micro-i2c-pinout-3.3V-SEL.png" width="600px">
</p>
</details>
<details><summary>Arduino Mega 2560</summary>
<p>
| *SCD30* | *SCD30 Pin* | *Cable Color* | *Board Pin* |
| :---: | --- | --- | --- |
| VDD | 1 | red | 3.3V |
| GND | 2 | black | GND |
| SCL | 3 | yellow | D21/SCL |
| SDA | 4 | green | D20/SDA |
| RDY | 5 | | |
| PWM | 6 | | |
| SEL | 7 | blue | GND |
<img src="images/Arduino-Mega-2560-Rev3-i2c-pinout-3.3V-SEL.png" width="600px">
</p>
</details>
<details><summary>ESP32 DevKitC</summary>
<p>
| *SCD30* | *SCD30 Pin* | *Cable Color* | *Board Pin* |
| :---: | --- | --- | --- |
| VDD | 1 | red | 3V3 |
| GND | 2 | black | GND |
| SCL | 3 | yellow | GPIO 22 |
| SDA | 4 | green | GPIO 21 |
| RDY | 5 | | |
| PWM | 6 | | |
| SEL | 7 | blue | GND |
<img src="images/esp32-devkitc-i2c-pinout-3.3V-SEL.png" width="600px">
</p>
</details>
## Quick Start
1. Install the libraries and dependencies according to [Installation of the library](#installation-of-the-library)
2. Connect the SCD30 sensor to your Arduino as explained in [Connect the sensor](#connect-the-sensor)
3. Open the `exampleUsage` sample project within the Arduino IDE:
`File``Examples``Sensirion I2C SCD30``exampleUsage`
4. Click the `Upload` button in the Arduino IDE or `Sketch``Upload`
5. When the upload process has finished, open the `Serial Monitor` or `Serial
Plotter` via the `Tools` menu to observe the measurement values. Note that
the `Baud Rate` in the used tool has to be set to `115200 baud`.
## Contributing
**Contributions are welcome!**
This Sensirion library uses
[`clang-format`](https://releases.llvm.org/download.html) to standardize the
formatting of all our `.cpp` and `.h` files. Make sure your contributions are
formatted accordingly:
The `-i` flag will apply the format changes to the files listed.
```bash
clang-format -i src/*.cpp src/*.h
```
Note that differences from this formatting will result in a failed build until
they are fixed.
:
## Known issues
* *softReset()*:
After using the ``softReset()`` function on an Arduino MKR WIFI 1010, subsequent commands are no longer acknowledged.
The I2C line remains low after receiving the first command byte.
To make the provided example work on the Arduino MKR WIFI 1010, the call to ``softReset()`` and the subsequent ``delay()`` can be removed.
## License
See [LICENSE](LICENSE).

View File

@@ -0,0 +1,127 @@
/*
* THIS FILE IS AUTOMATICALLY GENERATED
*
* Generator: sensirion-driver-generator 1.6.1
* Product: scd30
* Model-Version: 1.1.1
*/
/*
* Copyright (c) 2026, Sensirion AG
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of Sensirion AG nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <Arduino.h>
#include <SensirionI2cScd30.h>
#include <Wire.h>
// macro definitions
// make sure that we use the proper definition of NO_ERROR
#ifdef NO_ERROR
#undef NO_ERROR
#endif
#define NO_ERROR 0
SensirionI2cScd30 sensor;
static char errorMessage[64];
static int16_t error;
void setup() {
Serial.begin(115200);
while (!Serial) {
delay(100);
}
Wire.begin();
sensor.begin(Wire, SCD30_I2C_ADDR_61);
sensor.stopPeriodicMeasurement();
sensor.softReset();
delay(2000);
int8_t serialNumber[32] = {0};
error = sensor.readSerialNumber(serialNumber, 32);
if (error != NO_ERROR) {
Serial.print("Error trying to execute readSerialNumber(): ");
errorToString(error, errorMessage, sizeof errorMessage);
Serial.println(errorMessage);
return;
}
Serial.print("serialNumber: ");
Serial.print((const char*)serialNumber);
Serial.println();
uint8_t major = 0;
uint8_t minor = 0;
error = sensor.readFirmwareVersion(major, minor);
if (error != NO_ERROR) {
Serial.print("Error trying to execute readFirmwareVersion(): ");
errorToString(error, errorMessage, sizeof errorMessage);
Serial.println(errorMessage);
return;
}
Serial.print("major: ");
Serial.print(major);
Serial.print("\t");
Serial.print("minor: ");
Serial.print(minor);
Serial.println();
error = sensor.startPeriodicMeasurement(0);
if (error != NO_ERROR) {
Serial.print("Error trying to execute startPeriodicMeasurement(): ");
errorToString(error, errorMessage, sizeof errorMessage);
Serial.println(errorMessage);
return;
}
}
void loop() {
float co2Concentration = 0.0;
float temperature = 0.0;
float humidity = 0.0;
delay(1500);
error = sensor.blockingReadMeasurementData(co2Concentration, temperature,
humidity);
if (error != NO_ERROR) {
Serial.print("Error trying to execute blockingReadMeasurementData(): ");
errorToString(error, errorMessage, sizeof errorMessage);
Serial.println(errorMessage);
return;
}
Serial.print("co2Concentration: ");
Serial.print(co2Concentration);
Serial.print("\t");
Serial.print("temperature: ");
Serial.print(temperature);
Serial.print("\t");
Serial.print("humidity: ");
Serial.print(humidity);
Serial.println();
}

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 405 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 525 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -0,0 +1,43 @@
#######################################
# Syntax Coloring Map
#######################################
#######################################
# Datatypes (KEYWORD1)
#######################################
SensirionI2cScd30 KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
awaitDataReady KEYWORD2
blockingReadMeasurementData KEYWORD2
startPeriodicMeasurement KEYWORD2
stopPeriodicMeasurement KEYWORD2
setMeasurementInterval KEYWORD2
getMeasurementInterval KEYWORD2
getDataReady KEYWORD2
readMeasurementData KEYWORD2
activateAutoCalibration KEYWORD2
getAutoCalibrationStatus KEYWORD2
forceRecalibration KEYWORD2
getForceRecalibrationStatus KEYWORD2
setTemperatureOffset KEYWORD2
getTemperatureOffset KEYWORD2
getAltitudeCompensation KEYWORD2
setAltitudeCompensation KEYWORD2
readFirmwareVersion KEYWORD2
softReset KEYWORD2
readSerialNumber KEYWORD2
#######################################
# Instances (KEYWORD2)
#######################################
sensor KEYWORD2
#######################################
# Constants (LITERAL1)
#######################################

View File

@@ -0,0 +1,11 @@
name=Sensirion I2C SCD30
version=1.1.1
author=Sensirion
maintainer=Sensirion
sentence=Library for the SCD30 sensor by Sensirion
paragraph=Enables you to use the SCD30 sensor via I2C.
url=https://github.com/Sensirion/arduino-i2c-scd30
category=Sensors
architectures=*
depends=Sensirion Core
includes=SensirionI2cScd30.h

View File

@@ -0,0 +1,7 @@
# driver generation metadata
generator_version: 1.6.1
model_version: 1.1.1
dg_status: released
is_manually_modified: true
first_generated: '2022-04-07 17:45'
last_generated: '2026-04-27 08:44'

View File

@@ -0,0 +1,436 @@
/*
* THIS FILE IS AUTOMATICALLY GENERATED
*
* Generator: sensirion-driver-generator 1.6.1
* Product: scd30
* Model-Version: 1.1.1
*/
/*
* Copyright (c) 2026, Sensirion AG
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of Sensirion AG nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include "SensirionI2cScd30.h"
#include <Arduino.h>
// make sure that we use the proper definition of NO_ERROR
#ifdef NO_ERROR
#undef NO_ERROR
#endif
#define NO_ERROR 0
static uint8_t communication_buffer[48] = {0};
SensirionI2cScd30::SensirionI2cScd30() {
}
int16_t SensirionI2cScd30::awaitDataReady(void) {
uint16_t dataReady = 0;
int16_t localError = 0;
localError = getDataReady(dataReady);
if (localError != NO_ERROR) {
return localError;
}
while (dataReady == 0) {
delay(100);
localError = getDataReady(dataReady);
if (localError != NO_ERROR) {
return localError;
}
}
return localError;
}
int16_t SensirionI2cScd30::blockingReadMeasurementData(float& co2Concentration,
float& temperature,
float& humidity) {
int16_t localError = 0;
localError = awaitDataReady();
if (localError != NO_ERROR) {
return localError;
}
localError = readMeasurementData(co2Concentration, temperature, humidity);
return localError;
}
int16_t SensirionI2cScd30::startPeriodicMeasurement(uint16_t ambientPressure) {
int16_t localError = NO_ERROR;
uint8_t* buffer_ptr = communication_buffer;
SensirionI2CTxFrame txFrame = SensirionI2CTxFrame::createWithUInt16Command(
SCD30_START_PERIODIC_MEASUREMENT_CMD_ID, buffer_ptr, 5);
localError |= txFrame.addUInt16(ambientPressure);
if (localError != NO_ERROR) {
return localError;
}
localError =
SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
delay(10);
return localError;
}
int16_t SensirionI2cScd30::stopPeriodicMeasurement(void) {
int16_t localError = NO_ERROR;
uint8_t* buffer_ptr = communication_buffer;
SensirionI2CTxFrame txFrame = SensirionI2CTxFrame::createWithUInt16Command(
SCD30_STOP_PERIODIC_MEASUREMENT_CMD_ID, buffer_ptr, 2);
localError =
SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
delay(10);
return localError;
}
int16_t SensirionI2cScd30::setMeasurementInterval(uint16_t interval) {
int16_t localError = NO_ERROR;
uint8_t* buffer_ptr = communication_buffer;
SensirionI2CTxFrame txFrame = SensirionI2CTxFrame::createWithUInt16Command(
SCD30_SET_MEASUREMENT_INTERVAL_CMD_ID, buffer_ptr, 5);
localError |= txFrame.addUInt16(interval);
if (localError != NO_ERROR) {
return localError;
}
localError =
SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
delay(10);
return localError;
}
int16_t SensirionI2cScd30::getMeasurementInterval(uint16_t& interval) {
int16_t localError = NO_ERROR;
uint8_t* buffer_ptr = communication_buffer;
SensirionI2CTxFrame txFrame = SensirionI2CTxFrame::createWithUInt16Command(
SCD30_GET_MEASUREMENT_INTERVAL_CMD_ID, buffer_ptr, 3);
localError =
SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
delay(10);
SensirionI2CRxFrame rxFrame(buffer_ptr, 3);
localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 3,
rxFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
localError |= rxFrame.getUInt16(interval);
return localError;
}
int16_t SensirionI2cScd30::getDataReady(uint16_t& dataReadyFlag) {
int16_t localError = NO_ERROR;
uint8_t* buffer_ptr = communication_buffer;
SensirionI2CTxFrame txFrame = SensirionI2CTxFrame::createWithUInt16Command(
SCD30_GET_DATA_READY_CMD_ID, buffer_ptr, 3);
localError =
SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
delay(10);
SensirionI2CRxFrame rxFrame(buffer_ptr, 3);
localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 3,
rxFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
localError |= rxFrame.getUInt16(dataReadyFlag);
return localError;
}
int16_t SensirionI2cScd30::readMeasurementData(float& co2Concentration,
float& temperature,
float& humidity) {
int16_t localError = NO_ERROR;
uint8_t* buffer_ptr = communication_buffer;
SensirionI2CTxFrame txFrame = SensirionI2CTxFrame::createWithUInt16Command(
SCD30_READ_MEASUREMENT_DATA_CMD_ID, buffer_ptr, 18);
localError =
SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
delay(10);
SensirionI2CRxFrame rxFrame(buffer_ptr, 18);
localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 18,
rxFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
localError |= rxFrame.getFloat(co2Concentration);
localError |= rxFrame.getFloat(temperature);
localError |= rxFrame.getFloat(humidity);
return localError;
}
int16_t SensirionI2cScd30::activateAutoCalibration(uint16_t doActivate) {
int16_t localError = NO_ERROR;
uint8_t* buffer_ptr = communication_buffer;
SensirionI2CTxFrame txFrame = SensirionI2CTxFrame::createWithUInt16Command(
SCD30_ACTIVATE_AUTO_CALIBRATION_CMD_ID, buffer_ptr, 5);
localError |= txFrame.addUInt16(doActivate);
if (localError != NO_ERROR) {
return localError;
}
localError =
SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
delay(10);
return localError;
}
int16_t SensirionI2cScd30::getAutoCalibrationStatus(uint16_t& isActive) {
int16_t localError = NO_ERROR;
uint8_t* buffer_ptr = communication_buffer;
SensirionI2CTxFrame txFrame = SensirionI2CTxFrame::createWithUInt16Command(
SCD30_GET_AUTO_CALIBRATION_STATUS_CMD_ID, buffer_ptr, 3);
localError =
SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
delay(10);
SensirionI2CRxFrame rxFrame(buffer_ptr, 3);
localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 3,
rxFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
localError |= rxFrame.getUInt16(isActive);
return localError;
}
int16_t SensirionI2cScd30::forceRecalibration(uint16_t co2RefConcentration) {
int16_t localError = NO_ERROR;
uint8_t* buffer_ptr = communication_buffer;
SensirionI2CTxFrame txFrame = SensirionI2CTxFrame::createWithUInt16Command(
SCD30_FORCE_RECALIBRATION_CMD_ID, buffer_ptr, 5);
localError |= txFrame.addUInt16(co2RefConcentration);
if (localError != NO_ERROR) {
return localError;
}
localError =
SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
delay(10);
return localError;
}
int16_t
SensirionI2cScd30::getForceRecalibrationStatus(uint16_t& co2RefConcentration) {
int16_t localError = NO_ERROR;
uint8_t* buffer_ptr = communication_buffer;
SensirionI2CTxFrame txFrame = SensirionI2CTxFrame::createWithUInt16Command(
SCD30_GET_FORCE_RECALIBRATION_STATUS_CMD_ID, buffer_ptr, 3);
localError =
SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
delay(10);
SensirionI2CRxFrame rxFrame(buffer_ptr, 3);
localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 3,
rxFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
localError |= rxFrame.getUInt16(co2RefConcentration);
return localError;
}
int16_t SensirionI2cScd30::setTemperatureOffset(uint16_t temperatureOffset) {
int16_t localError = NO_ERROR;
uint8_t* buffer_ptr = communication_buffer;
SensirionI2CTxFrame txFrame = SensirionI2CTxFrame::createWithUInt16Command(
SCD30_SET_TEMPERATURE_OFFSET_CMD_ID, buffer_ptr, 5);
localError |= txFrame.addUInt16(temperatureOffset);
if (localError != NO_ERROR) {
return localError;
}
localError =
SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
delay(10);
return localError;
}
int16_t SensirionI2cScd30::getTemperatureOffset(uint16_t& temperatureOffset) {
int16_t localError = NO_ERROR;
uint8_t* buffer_ptr = communication_buffer;
SensirionI2CTxFrame txFrame = SensirionI2CTxFrame::createWithUInt16Command(
SCD30_GET_TEMPERATURE_OFFSET_CMD_ID, buffer_ptr, 3);
localError =
SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
delay(10);
SensirionI2CRxFrame rxFrame(buffer_ptr, 3);
localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 3,
rxFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
localError |= rxFrame.getUInt16(temperatureOffset);
return localError;
}
int16_t SensirionI2cScd30::getAltitudeCompensation(uint16_t& altitude) {
int16_t localError = NO_ERROR;
uint8_t* buffer_ptr = communication_buffer;
SensirionI2CTxFrame txFrame = SensirionI2CTxFrame::createWithUInt16Command(
SCD30_GET_ALTITUDE_COMPENSATION_CMD_ID, buffer_ptr, 3);
localError =
SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
delay(10);
SensirionI2CRxFrame rxFrame(buffer_ptr, 3);
localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 3,
rxFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
localError |= rxFrame.getUInt16(altitude);
return localError;
}
int16_t SensirionI2cScd30::setAltitudeCompensation(uint16_t altitude) {
int16_t localError = NO_ERROR;
uint8_t* buffer_ptr = communication_buffer;
SensirionI2CTxFrame txFrame = SensirionI2CTxFrame::createWithUInt16Command(
SCD30_SET_ALTITUDE_COMPENSATION_CMD_ID, buffer_ptr, 5);
localError |= txFrame.addUInt16(altitude);
if (localError != NO_ERROR) {
return localError;
}
localError =
SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
delay(10);
return localError;
}
int16_t SensirionI2cScd30::readFirmwareVersion(uint8_t& major, uint8_t& minor) {
int16_t localError = NO_ERROR;
uint8_t* buffer_ptr = communication_buffer;
SensirionI2CTxFrame txFrame = SensirionI2CTxFrame::createWithUInt16Command(
SCD30_READ_FIRMWARE_VERSION_CMD_ID, buffer_ptr, 3);
localError =
SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
delay(10);
SensirionI2CRxFrame rxFrame(buffer_ptr, 3);
localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 3,
rxFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
localError |= rxFrame.getUInt8(major);
localError |= rxFrame.getUInt8(minor);
return localError;
}
int16_t SensirionI2cScd30::softReset(void) {
int16_t localError = NO_ERROR;
uint8_t* buffer_ptr = communication_buffer;
SensirionI2CTxFrame txFrame = SensirionI2CTxFrame::createWithUInt16Command(
SCD30_SOFT_RESET_CMD_ID, buffer_ptr, 2);
localError =
SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
delay(2000);
return localError;
}
int16_t SensirionI2cScd30::readSerialNumber(int8_t serialNumber[],
uint16_t serialNumberSize) {
int16_t localError = NO_ERROR;
uint8_t* buffer_ptr = communication_buffer;
SensirionI2CTxFrame txFrame = SensirionI2CTxFrame::createWithUInt16Command(
SCD30_READ_SERIAL_NUMBER_CMD_ID, buffer_ptr, 48);
localError =
SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
delay(10);
SensirionI2CRxFrame rxFrame(buffer_ptr, 48);
localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 48,
rxFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
localError |= rxFrame.getBytes((uint8_t*)serialNumber, serialNumberSize);
return localError;
}
void SensirionI2cScd30::begin(TwoWire& i2cBus, uint8_t i2cAddress) {
_i2cBus = &i2cBus;
_i2cAddress = i2cAddress;
}

View File

@@ -0,0 +1,463 @@
/*
* THIS FILE IS AUTOMATICALLY GENERATED
*
* Generator: sensirion-driver-generator 1.6.1
* Product: scd30
* Model-Version: 1.1.1
*/
/*
* Copyright (c) 2026, Sensirion AG
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of Sensirion AG nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef SENSIRIONI2CSCD30_H
#define SENSIRIONI2CSCD30_H
#include <SensirionCore.h>
#include <Wire.h>
#define SCD30_I2C_ADDR_61 0x61
typedef enum {
SCD30_START_PERIODIC_MEASUREMENT_CMD_ID = 0x10,
SCD30_STOP_PERIODIC_MEASUREMENT_CMD_ID = 0x104,
SCD30_SET_MEASUREMENT_INTERVAL_CMD_ID = 0x4600,
SCD30_GET_MEASUREMENT_INTERVAL_CMD_ID = 0x4600,
SCD30_GET_DATA_READY_CMD_ID = 0x202,
SCD30_READ_MEASUREMENT_DATA_CMD_ID = 0x300,
SCD30_ACTIVATE_AUTO_CALIBRATION_CMD_ID = 0x5306,
SCD30_GET_AUTO_CALIBRATION_STATUS_CMD_ID = 0x5306,
SCD30_FORCE_RECALIBRATION_CMD_ID = 0x5204,
SCD30_GET_FORCE_RECALIBRATION_STATUS_CMD_ID = 0x5204,
SCD30_SET_TEMPERATURE_OFFSET_CMD_ID = 0x5403,
SCD30_GET_TEMPERATURE_OFFSET_CMD_ID = 0x5403,
SCD30_GET_ALTITUDE_COMPENSATION_CMD_ID = 0x5102,
SCD30_SET_ALTITUDE_COMPENSATION_CMD_ID = 0x5102,
SCD30_READ_FIRMWARE_VERSION_CMD_ID = 0xd100,
SCD30_SOFT_RESET_CMD_ID = 0xd304,
SCD30_READ_SERIAL_NUMBER_CMD_ID = 0xd033,
} SCD30CmdId;
class SensirionI2cScd30 {
public:
SensirionI2cScd30();
/**
* @brief Initializes the SCD30 class.
*
* @param i2cBus Arduino stream object to be used for communication.
*/
void begin(TwoWire& i2cBus, uint8_t i2cAddress);
/**
* @brief Poll the data ready flag.
*
* Repeatedly call the get_data_ready() until the ready flag is set to 1. If
* the minimal measurement interval is 2s we iterate at most 200 times. Note
* that this is blocking the system for a considerable amount of time!
*
* @return error_code 0 on success, an error code otherwise.
*/
int16_t awaitDataReady(void);
/**
* @brief Block until data is available and return measurement results.
*
* This is a convenience method that combines polling the data ready flag
* and reading out the data. Note that this is blocking the system for a
* considerable amount of time!
*
* @param[out] co2Concentration
* @param[out] temperature
* @param[out] humidity
*
* @return error_code 0 on success, an error code otherwise.
*/
int16_t blockingReadMeasurementData(float& co2Concentration,
float& temperature, float& humidity);
/**
* @brief Starts continuous measurement of CO₂, relative humidity and
* temperature.
*
* Starts continuous measurement of the SCD30 to measure CO₂ concentration,
* humidity and temperature. Measurement data which is not read from the
* sensor will be overwritten. The CO₂ measurement value can be compensated
* for ambient pressure by feeding the pressure value in mBar to the sensor.
* Setting the ambient pressure will overwrite previous settings of altitude
* compensation. Setting the argument to zero will deactivate the ambient
* pressure compensation (default ambient pressure = 1013.25 mBar). For
* setting a new ambient pressure when continuous measurement is running the
* whole command has to be written to SCD30.
*
* @param[in] ambientPressure Ambient pressure in millibar.
*
* @return error_code 0 on success, an error code otherwise.
*
* Example:
* --------
*
* @code{.cpp}
*
* int16_t localError = 0;
*
* localError = sensor.startPeriodicMeasurement(0);
* if (localError != NO_ERROR) {
* return;
* }
*
* @endcode
*
*/
int16_t startPeriodicMeasurement(uint16_t ambientPressure);
/**
* @brief Stops continuous measurement of the sensor.
*
* Stops the continuous measurement of the SCD30.
*
* @return error_code 0 on success, an error code otherwise.
*/
int16_t stopPeriodicMeasurement(void);
/**
* @brief Sets the interval used to measure in continuous measurement mode.
*
* Sets the interval used by the SCD30 sensor to measure in continuous
* measurement mode. Initial value is 2s. The chosen measurement interval is
* saved in non-volatile memory and thus is not reset to its initial value
* after power up.
*
* @param[in] interval Measurement interval in seconds.
*
* @return error_code 0 on success, an error code otherwise.
*
* Example:
* --------
*
* @code{.cpp}
*
* int16_t localError = 0;
*
* localError = sensor.setMeasurementInterval(4);
* if (localError != NO_ERROR) {
* return;
* }
*
* @endcode
*
*/
int16_t setMeasurementInterval(uint16_t interval);
/**
* @brief Read the configured measurement interval.
*
* Reads out the active measurement interval.
*
* @param[out] interval Configured measurement interval
*
* @return error_code 0 on success, an error code otherwise.
*/
int16_t getMeasurementInterval(uint16_t& interval);
/**
* @brief Queries if data is ready for readout.
*
* Data ready command is used to determine if a measurement can be read from
* the sensors buffer. Whenever there is a measurement available from the
* internal buffer this command returns 1 and 0 otherwise. As soon as the
* measurement has been read by SCD30 the return value changes to 0.
*
* @param[out] dataReadyFlag Data ready flag
*
* @note The read header should be sent with a delay of >3ms following the
* write sequence.
*
* @return error_code 0 on success, an error code otherwise.
*/
int16_t getDataReady(uint16_t& dataReadyFlag);
/**
* @brief Reads out the measurement values.
*
* Allows to read new measurement data if data is available.
*
* @param[out] co2Concentration
* @param[out] temperature
* @param[out] humidity
*
* @return error_code 0 on success, an error code otherwise.
*/
int16_t readMeasurementData(float& co2Concentration, float& temperature,
float& humidity);
/**
* @brief Activates or deactivates continuous automatic self-calibration.
*
* Continuous automatic self-calibration (ASC) can be (de-)activated with
* this command. When activated for the first time a period of minimum 7
* days is needed so that the algorithm can find its initial parameter set
* for ASC. The sensor has to be exposed to fresh air for at least 1 hour
* every day. Also during that period, the sensor may not be disconnected
* from the power supply. Otherwise the procedure to find calibration
* parameters is aborted and has to be restarted from the beginning. The
* successfully calculated parameters are stored in non-volatile memory of
* the SCD30 having the effect that after a restart the previously found
* parameters for ASC are still present.
*
* @param[in] doActivate Set activate flag.
*
* @note Note that the most recently found self-calibration parameters will
* be actively used for self-calibration disregarding the status of this
* feature. Finding a new parameter set by the here described method will
* always overwrite the settings from external recalibration and vice-versa.
* The feature is switched off by default. To work properly SCD30 has to see
* fresh air on a regular basis. Optimal working conditions are given when
* the sensor sees fresh air for one hour every day so that ASC can
* constantly re-calibrate. ASC only works in continuous measurement mode.
* ASC status is saved in non-volatile memory. When the sensor is powered
* down while ASC is activated SCD30 will continue with automatic
* self-calibration after repowering without sending the command.
*
* @return error_code 0 on success, an error code otherwise.
*
* Example:
* --------
*
* @code{.cpp}
*
* int16_t localError = 0;
*
* localError = sensor.activateAutoCalibration(1);
* if (localError != NO_ERROR) {
* return;
* }
*
* @endcode
*
*/
int16_t activateAutoCalibration(uint16_t doActivate);
/**
* @brief Gets the status of auto calibration.
*
* Read out the status of the active self calibration.
*
* @param[out] isActive Indication if automatic calibration is active
*
* @return error_code 0 on success, an error code otherwise.
*/
int16_t getAutoCalibrationStatus(uint16_t& isActive);
/**
* @brief Forces recalibration with a new value for the CO₂ concentration.
*
* Forced recalibration (FRC) is used to compensate for sensor drifts when a
* reference value of the CO₂ concentration in close proximity to the SCD30
* is available. For best results, the sensor has to be run in a stable
* environment in continuous mode at a measurement rate of 2s for at least
* two minutes before applying the FRC command and sending the reference
* value. Setting a reference CO₂ concentration by the method described here
* will always supersede corrections from the ASC (see command
* activate_auto_calibration) and vice-versa. The reference CO₂
* concentration has to be within the range 400 ppm ≤ cref(CO₂) ≤ 2000 ppm.
* The FRC method imposes a permanent update of the CO₂ calibration curve
* which persists after repowering the sensor. The most recently used
* reference value is retained in volatile memory and can be read out with
* the command sequence given below. After repowering the sensor, the
* command will return the standard reference value of 400 ppm.
*
* @param[in] co2RefConcentration New CO2 reference concentration.
*
* @return error_code 0 on success, an error code otherwise.
*
* Example:
* --------
*
* @code{.cpp}
*
* int16_t localError = 0;
*
* localError = sensor.forceRecalibration(500);
* if (localError != NO_ERROR) {
* return;
* }
*
* @endcode
*
*/
int16_t forceRecalibration(uint16_t co2RefConcentration);
/**
* @brief Gets the force recalibration status.
*
* Read out the CO₂ reference concentration.
*
* @param[out] co2RefConcentration Currently used CO2 reference
* concentration.
*
* @return error_code 0 on success, an error code otherwise.
*/
int16_t getForceRecalibrationStatus(uint16_t& co2RefConcentration);
/**
* @brief Set the temperature offset. Unit ℃ * 100.
*
* The on-board RH/T sensor is influenced by thermal self-heating of SCD30
* and other electrical components. Design-in alters the thermal properties
* of SCD30 such that temperature and humidity offsets may occur when
* operating the sensor in end-customer devices. Compensation of those
* effects is achievable by writing the temperature offset found in
* continuous operation of the device into the sensor. Temperature offset
* value is saved in non-volatile memory. The last set value will be used
* for temperature offset compensation after repowering.
*
* @param[in] temperatureOffset
*
* @return error_code 0 on success, an error code otherwise.
*
* Example:
* --------
*
* @code{.cpp}
*
* int16_t localError = 0;
*
* localError = sensor.setTemperatureOffset(2000);
* if (localError != NO_ERROR) {
* return;
* }
*
* @endcode
*
*/
int16_t setTemperatureOffset(uint16_t temperatureOffset);
/**
* @brief Get the temperature offset. Unit ℃ * 100.
*
* Read out the actual temperature offset. The result can be converted to ℃
* by dividing it by 100.
*
* @param[out] temperatureOffset
*
* @return error_code 0 on success, an error code otherwise.
*/
int16_t getTemperatureOffset(uint16_t& temperatureOffset);
/**
* @brief Get the configured altitude (height over sea level in m).
*
* Read out the configured altitude (height in [m] over sea level).
*
* @param[out] altitude
*
* @return error_code 0 on success, an error code otherwise.
*/
int16_t getAltitudeCompensation(uint16_t& altitude);
/**
* @brief Set a new value for altitude.
*
* Measurements of CO₂ concentration based on the NDIR principle are
* influenced by altitude. SCD30 offers to compensate deviations due to
* altitude by using this command. Setting altitude is disregarded when an
* ambient pressure is given to the sensor (see command
* start_periodic_measurement). Altitude value is saved in non-volatile
* memory. The last set value will be used for altitude compensation after
* repowering.
*
* @param[in] altitude
*
* @return error_code 0 on success, an error code otherwise.
*
* Example:
* --------
*
* @code{.cpp}
*
* int16_t localError = 0;
*
* localError = sensor.setAltitudeCompensation(440);
* if (localError != NO_ERROR) {
* return;
* }
*
* @endcode
*
*/
int16_t setAltitudeCompensation(uint16_t altitude);
/**
* @brief Read the version of the current firmware.
*
* Read the version of the current firmware.
*
* @param[out] major Major version number.
* @param[out] minor Minor version number.
*
* @return error_code 0 on success, an error code otherwise.
*/
int16_t readFirmwareVersion(uint8_t& major, uint8_t& minor);
/**
* @brief Performs a soft reset of the sensor. The device will be
* unavailable for 2 seconds.
*
* The SCD30 provides a soft reset mechanism that forces the sensor into the
* same state as after powering up without the need for removing the
* power-supply. It does so by restarting its system controller. After soft
* reset the sensor will reload all calibrated data. However, it is worth
* noting that the sensor reloads calibration data prior to every
* measurement by default. This includes previously set reference values
* from ASC or FRC as well as temperature offset values last setting. The
* sensor is able to receive the command at any time, regardless of its
* internal state.
*
* @return error_code 0 on success, an error code otherwise.
*/
int16_t softReset(void);
/**
* @brief Read the serial number of the sensor.
*
* Null-terminated ASCII string containing the serial number. Up to 32
* characters can be read from the device.
*
* @param[out] serialNumber Serial number of the sensor.
*
* @return error_code 0 on success, an error code otherwise.
*/
int16_t readSerialNumber(int8_t serialNumber[], uint16_t serialNumberSize);
private:
TwoWire* _i2cBus = nullptr;
uint8_t _i2cAddress = 0;
};
#endif // SENSIRIONI2CSCD30_H

View File

@@ -93,6 +93,28 @@ bool I2cSetClock(uint32_t frequency, uint32_t bus) {
return true;
}
uint8_t I2cClearBus(uint32_t bus = 0);
uint8_t I2cClearBus(uint32_t bus) {
#ifdef ESP8266
/**
* twi_status() attempts to read out any data left that is holding SDA low, so a new transaction can take place
* something like (http://www.forward.com.au/pfod/ArduinoProgramming/I2C_ClearBus/index.html)
*
* Returns:
* 0 - No error
* 1 - SCL held low by another device, no procedure available to recover
* 2 - I2C bus error. SCL held low beyond slave clock stretch time
* 3 - I2C bus error. SDA line held low by slave/another_master after n bits
*/
TwoWire& myWire = I2cGetWire(bus);
if (&myWire == nullptr) { return 0; } // No valid I2c bus
return myWire.status();
#else
return 0;
#endif
}
/*-------------------------------------------------------------------------------------------*\
* Return code: 0 = Error, 1 = OK
\*-------------------------------------------------------------------------------------------*/

View File

@@ -1,7 +1,7 @@
/*
xsns_42_scd30.ino - SCD30 CO2 sensor support for Tasmota
xsns_42_scd30.ino - Sensirion SCD30 CO2 sensor support for Tasmota
Copyright (C) 2021 Frogmore42
Copyright (C) 2021 Frogmore42, Theo Arends
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -21,325 +21,483 @@
#ifdef USE_SCD30
/*********************************************************************************************\
* SCD30 NDIR CO2 Temperature and Humidity sensor
*
* I2C Address: 0x61
\*********************************************************************************************/
#define XSNS_42 42
#define XI2C_29 29 // See I2CDEVICES.md
#define XSNS_42 42
#define XI2C_29 29 // See I2CDEVICES.md
//#define SCD30_DEBUG
#define SCD30_MAX_MISSED_READS 3
#define SCD30_I2C_BUS_SPEED 50000 // Sensirion recommends to operate the SCD30 at a baud rate of 50 kHz or smaller
#define SCD30_ADDRESS 0x61
#define SCD30_MAX_MISSED_READS 3
#define SCD30_STATE_NO_ERROR 0
#define SCD30_STATE_ERROR_DATA_CRC 1
#define SCD30_STATE_ERROR_READ_MEAS 2
#define SCD30_STATE_ERROR_SOFT_RESET 3
#define SCD30_STATE_ERROR_I2C_RESET 4
#define SCD30_STATE_ERROR_UNKNOWN 5
const char kScd30Commands[] PROGMEM = "Scd30|" // Prefix
"Alt|Auto|Cal|FW|Int|Pres|TOff";
void (*const kScd30Command[])(void) PROGMEM = {
&CmndScd30Altitude, &CmndScd30AutoMode, &CmndScd30Calibrate, &CmndScd30Firmware, &CmndScd30Interval, &CmndScd30Pressure, &CmndScd30TempOffset };
//#define SENSIRION_DEBUG // Adds 1k2 to code size
/********************************************************************************************/
#include <FrogmoreScd30.h>
#include <SensirionI2cScd30.h>
FrogmoreScd30 scd30;
SensirionI2cScd30 scd30;
struct {
float humidity = 0.0f;
float temperature = 0.0f;
int error_state = SCD30_STATE_NO_ERROR;
int loop_count = 0;
int data_not_available_count = 0;
int good_measure_count = 0;
int reset_count = 0;
int error_count = 0;
int co2_zero_count = 0;
int i2c_reset_count = 0;
struct SCD30DATA_s {
float humidity;
float temperature;
uint16_t co2;
uint16_t pressure;
uint16_t interval;
uint16_t co2 = 0;
uint16_t co2e_avg = 0;
uint8_t bus;
bool init_once;
bool found = false;
bool data_valid = false;
} Scd30;
uint8_t loop_count;
bool data_valid;
} *SCD30DATA = nullptr;
void Scd30BusSpeed(bool set) {
I2cSetClock((set) ? 50000 : 0, Scd30.bus);
uint8_t scd30_bus = 0;
bool scd30_init_once = false;
/********************************************************************************************/
bool Scd30Error(const char* func, int error) {
bool result = (error != 0);
if (result) {
#ifdef SENSIRION_DEBUG
char error_msg[64];
errorToString(error, error_msg, sizeof(error_msg));
AddLog(LOG_LEVEL_DEBUG, PSTR("SCD: %s error %d %s"), func, error, error_msg);
#else
AddLog(LOG_LEVEL_DEBUG, PSTR("SCD: %s error %d"), func, error);
#endif
}
return result;
}
void Scd30Detect(void) {
for (Scd30.bus = 0; Scd30.bus < MAX_I2C; Scd30.bus++) {
Scd30BusSpeed(1);
if (I2cSetDevice(SCD30_ADDRESS, Scd30.bus)) {
scd30.begin(&I2cGetWire(Scd30.bus));
void Scd30BusSpeed(uint32_t bus_speed) {
I2cSetClock(bus_speed, scd30_bus);
}
void Scd30ClearI2CBus(void) {
#ifdef ESP8266
/**
* SCD30 driver known issues:
*
* *softReset()*:
* After using the ``softReset()`` function on an Arduino MKR WIFI 1010 (software I2C),
* subsequent commands are no longer acknowledged. The I2C line remains low after
* receiving the first command byte.
*
* To make the provided example work on the Arduino MKR WIFI 1010, the call to
* ``softReset()`` and the subsequent ``delay()`` can be removed.
*/
Scd30BusSpeed(SCD30_I2C_BUS_SPEED);
Scd30Error("ClearI2cBus", I2cClearBus(scd30_bus));
Scd30BusSpeed(0);
#endif
}
/********************************************************************************************/
void Scd30Init(void) {
/**
* Maximal I2C speed is 100 kHz and the master has to support clock stretching.
* Sensirion recommends to operate the SCD30 at a baud rate of 50 kHz or smaller.
* Clock stretching period in write- and read-frames is 30 ms, however, due to
* internal calibration processes a maximal clock stretching of 150 ms may occur
* once per day.
*/
PowerOnDelay(2000); // Sensor startup time (Time after power-on until I2C communication can be started)
bool quit = false;
for (scd30_bus = 0; scd30_bus < MAX_I2C; scd30_bus++) {
Scd30BusSpeed(SCD30_I2C_BUS_SPEED);
if (I2cSetDevice(SCD30_I2C_ADDR_61, scd30_bus)) {
scd30.begin(I2cGetWire(scd30_bus), SCD30_I2C_ADDR_61);
// Don't stop in case of error, try to continue
Scd30Error("StopMeasurement", scd30.stopPeriodicMeasurement()); // Performs delay(10)
Scd30Error("ReInit", scd30.softReset()); // Performs delay(2000)
uint8_t major;
uint8_t minor;
if (!scd30.getFirmwareVersion(&major, &minor)) {
if (!scd30.getMeasurementInterval(&Scd30.interval)) {
if (!scd30.beginMeasuring()) {
I2cSetActiveFound(SCD30_ADDRESS, "SCD30", Scd30.bus);
AddLog(LOG_LEVEL_DEBUG, PSTR("SCD: SCD30 v%d.%d"), major, minor);
Scd30.found = true;
if (!Scd30Error("Version", scd30.readFirmwareVersion(major, minor))) {
int8_t serial_number[32] = { 0 };
if (!Scd30Error("Serialnumber", scd30.readSerialNumber(serial_number, sizeof(serial_number)))) {
uint16_t interval;
if (!Scd30Error("GetInterval", scd30.getMeasurementInterval(interval))) {
if (!Scd30Error("StartMeasurement", scd30.startPeriodicMeasurement(0))) {
SCD30DATA = (SCD30DATA_s *)calloc(1, sizeof(struct SCD30DATA_s));
if (SCD30DATA != nullptr) {
SCD30DATA->interval = interval;
I2cSetActiveFound(SCD30_I2C_ADDR_61, "SCD30", scd30_bus);
AddLog(LOG_LEVEL_DEBUG, PSTR("SCD: SCD30 serialnumber %s v%d.%d"), serial_number, major, minor);
}
quit = true;
}
}
}
}
}
Scd30BusSpeed(0);
if (Scd30.found) { break; }
if (quit) { break; }
}
}
// gets data from the sensor every 3 seconds or so to give the sensor time to gather new data
void Scd30Update(void) {
Scd30.loop_count++;
if (Scd30.loop_count > (Scd30.interval - 1)) {
Scd30BusSpeed(1);
uint32_t error = 0;
switch (Scd30.error_state) {
case SCD30_STATE_NO_ERROR: {
error = scd30.readMeasurement(&Scd30.co2, &Scd30.co2e_avg, &Scd30.temperature, &Scd30.humidity);
switch (error) {
case ERROR_SCD30_NO_ERROR:
Scd30.loop_count = 0;
Scd30.data_valid = true;
Scd30.good_measure_count++;
#ifdef USE_LIGHT
LightSetSignal(CO2_LOW, CO2_HIGH, Scd30.co2);
#endif // USE_LIGHT
break;
if (SCD30DATA->loop_count > (SCD30DATA->interval)) {
uint16_t data_ready;
bool error = false;
case ERROR_SCD30_NO_DATA:
Scd30.data_not_available_count++;
break;
case ERROR_SCD30_CRC_ERROR:
Scd30.error_state = SCD30_STATE_ERROR_DATA_CRC;
Scd30.error_count++;
#ifdef SCD30_DEBUG
AddLog(LOG_LEVEL_ERROR, PSTR("SCD30: CRC error, CRC error: %ld, CO2 zero: %ld, good: %ld, no data: %ld, sc30_reset: %ld, i2c_reset: %ld"),
Scd30.error_count, Scd30.co2_zero_count, Scd30.good_measure_count, Scd30.data_not_available_count, Scd30.reset_count, Scd30.i2c_reset_count);
#endif
break;
case ERROR_SCD30_CO2_ZERO:
Scd30.co2_zero_count++;
#ifdef SCD30_DEBUG
AddLog(LOG_LEVEL_ERROR, PSTR("SCD30: CO2 zero, CRC error: %ld, CO2 zero: %ld, good: %ld, no data: %ld, sc30_reset: %ld, i2c_reset: %ld"),
Scd30.error_count, Scd30.co2_zero_count, Scd30.good_measure_count, Scd30.data_not_available_count, Scd30.reset_count, Scd30.i2c_reset_count);
#endif
break;
default: {
Scd30.error_state = SCD30_STATE_ERROR_READ_MEAS;
#ifdef SCD30_DEBUG
AddLog(LOG_LEVEL_ERROR, PSTR("SCD30: Update: ReadMeasurement error: 0x%lX, counter: %ld"), error, Scd30.loop_count);
#endif
Scd30BusSpeed(0);
return;
}
break;
}
}
break;
case SCD30_STATE_ERROR_DATA_CRC: {
//Scd30.data_valid = false;
#ifdef SCD30_DEBUG
AddLog(LOG_LEVEL_ERROR, PSTR("SCD30: in error state: %d, good: %ld, no data: %ld, sc30 reset: %ld, i2c reset: %ld"),
Scd30.error_state, Scd30.good_measure_count, Scd30.data_not_available_count, Scd30.reset_count, Scd30.i2c_reset_count);
AddLog(LOG_LEVEL_ERROR, PSTR("SCD30: got CRC error, try again, counter: %ld"), Scd30.loop_count);
#endif
Scd30.error_state = ERROR_SCD30_NO_ERROR;
}
break;
case SCD30_STATE_ERROR_READ_MEAS: {
//Scd30.data_valid = false;
#ifdef SCD30_DEBUG
AddLog(LOG_LEVEL_ERROR, PSTR("SCD30: in error state: %d, good: %ld, no data: %ld, sc30 reset: %ld, i2c reset: %ld"),
Scd30.error_state, Scd30.good_measure_count, Scd30.data_not_available_count, Scd30.reset_count, Scd30.i2c_reset_count);
AddLog(LOG_LEVEL_ERROR, PSTR("SCD30: not answering, sending soft reset, counter: %ld"), Scd30.loop_count);
#endif
Scd30.reset_count++;
error = scd30.softReset();
if (error) {
#ifdef SCD30_DEBUG
AddLog(LOG_LEVEL_ERROR, PSTR("SCD30: resetting got error: 0x%lX"), error);
#endif
error >>= 8;
if (error == 4) {
Scd30.error_state = SCD30_STATE_ERROR_SOFT_RESET;
} else {
Scd30.error_state = SCD30_STATE_ERROR_UNKNOWN;
}
} else {
Scd30.error_state = ERROR_SCD30_NO_ERROR;
}
}
break;
case SCD30_STATE_ERROR_SOFT_RESET: {
//Scd30.data_valid = false;
#ifdef SCD30_DEBUG
AddLog(LOG_LEVEL_ERROR, PSTR("SCD30: in error state: %d, good: %ld, no data: %ld, sc30 reset: %ld, i2c reset: %ld"),
Scd30.error_state, Scd30.good_measure_count, Scd30.data_not_available_count, Scd30.reset_count, Scd30.i2c_reset_count);
AddLog(LOG_LEVEL_ERROR, PSTR("SCD30: clearing i2c bus"));
#endif
Scd30.i2c_reset_count++;
error = scd30.clearI2CBus();
if (error) {
Scd30.error_state = SCD30_STATE_ERROR_I2C_RESET;
#ifdef SCD30_DEBUG
AddLog(LOG_LEVEL_ERROR, PSTR("SCD30: error clearing i2c bus: 0x%lX"), error);
#endif
} else {
Scd30.error_state = ERROR_SCD30_NO_ERROR;
}
}
break;
default: {
//Scd30.data_valid = false;
#ifdef SCD30_DEBUG
AddLog(LOG_LEVEL_ERROR, PSTR("SCD30: unknown error state: 0x%lX"), Scd30.error_state);
#endif
Scd30.error_state = SCD30_STATE_ERROR_SOFT_RESET; // try again
}
/**
* @brief Queries if data is ready for readout.
*
* Data ready command is used to determine if a measurement can be read from
* the sensors buffer. Whenever there is a measurement available from the
* internal buffer this command returns 1 and 0 otherwise. As soon as the
* measurement has been read by SCD30 the return value changes to 0.
*
* @param[out] dataReadyFlag Data ready flag
*
* @note The read header should be sent with a delay of >3ms following the
* write sequence.
*/
Scd30BusSpeed(SCD30_I2C_BUS_SPEED);
if (scd30.getDataReady(data_ready)) {
delay(100);
error = Scd30Error("DataReady", scd30.getDataReady(data_ready));
}
Scd30BusSpeed(0);
if (Scd30.loop_count > (SCD30_MAX_MISSED_READS * Scd30.interval)) {
Scd30.data_valid = false;
if (!error && data_ready) {
/**
* @brief Reads out the measurement values.
*
* Allows to read new measurement data if data is available.
*
* @param[out] co2Concentration
* @param[out] temperature
* @param[out] humidity
*/
float co2;
float temperature;
float humidity;
if (scd30.readMeasurementData(co2, temperature, humidity)) {
delay(150);
error = Scd30Error("Measurement", scd30.readMeasurementData(co2, temperature, humidity));
}
if (!error) {
SCD30DATA->co2 = (uint16_t)co2;
SCD30DATA->temperature = temperature;
SCD30DATA->humidity = humidity;
#ifdef USE_LIGHT
LightSetSignal(CO2_LOW, CO2_HIGH, SCD30DATA->co2);
#endif // USE_LIGHT
SCD30DATA->loop_count = 0;
SCD30DATA->data_valid = true;
}
}
}
SCD30DATA->loop_count++;
if (SCD30DATA->loop_count > (SCD30_MAX_MISSED_READS * SCD30DATA->interval)) {
SCD30DATA->data_valid = false;
AddLog(LOG_LEVEL_DEBUG, PSTR("SCD: Reinit"));
Scd30Error("StopMeasurement", scd30.stopPeriodicMeasurement()); // Performs delay(10)
Scd30Error("ReInit", scd30.softReset()); // Performs delay(2000)
Scd30ClearI2CBus();
Scd30Error("Measurement", scd30.startPeriodicMeasurement(SCD30DATA->pressure));
SCD30DATA->loop_count = 0;
}
Scd30BusSpeed(0);
}
/*********************************************************************************************\
* Command Scd30
* Commands
\*********************************************************************************************/
bool CmndScd30Error(int error) {
bool result = (error != 0);
if (result) {
ResponseCmnd();
#ifdef SENSIRION_DEBUG
char error_msg[64];
errorToString(error, error_msg, sizeof(error_msg));
ResponseAppend_P(PSTR("{\"Error\":\"%d %s\"}"), error, error_msg);
#else
ResponseAppend_P(PSTR("{\"Error\":%d}"), error);
#endif
}
return result;
}
const char kScd30Commands[] PROGMEM = "Scd30|" // Prefix
"Alt|Auto|Cal|Int|Pres|TOff";
void (* const kScd30Command[])(void) PROGMEM = {
&CmndScd30Altitude, &CmndScd30AutoMode, &CmndScd30Calibrate,
&CmndScd30Interval, &CmndScd30Pressure, &CmndScd30TempOffset };
void CmndScd30Altitude(void) {
uint16_t value = 0;
Scd30BusSpeed(1);
if (XdrvMailbox.data_len > 0) {
value = XdrvMailbox.payload;
scd30.setAltitudeCompensation(value);
} else {
scd30.getAltitudeCompensation(&value);
/**
* Scd30Alt 440
*
* @brief Set a new value for altitude.
*
* Measurements of CO₂ concentration based on the NDIR principle are
* influenced by altitude. SCD30 offers to compensate deviations due to
* altitude by using this command. Setting altitude is disregarded when an
* ambient pressure is given to the sensor (see command
* start_periodic_measurement). Altitude value is saved in non-volatile
* memory. The last set value will be used for altitude compensation after
* repowering.
*
* @param[in] altitude
*/
Scd30BusSpeed(SCD30_I2C_BUS_SPEED);
if (XdrvMailbox.payload >= 0) {
scd30.setAltitudeCompensation(XdrvMailbox.payload);
}
/**
* Scd30Alt
*
* @brief Get the configured altitude (height over sea level in m).
*
* Read out the configured altitude (height in [m] over sea level).
*
* @param[out] altitude
*/
uint16_t altitude;
if (!CmndScd30Error(scd30.getAltitudeCompensation(altitude))) {
ResponseCmndNumber(altitude);
}
Scd30BusSpeed(0);
ResponseCmndNumber(value);
};
void CmndScd30AutoMode(void) {
uint16_t value = 0;
Scd30BusSpeed(1);
if (XdrvMailbox.data_len > 0) {
value = XdrvMailbox.payload;
scd30.setCalibrationType(value);
} else {
scd30.getCalibrationType(&value);
/**
* Scd30Auto 1
*
* @brief Activates or deactivates continuous automatic self-calibration.
*
* Continuous automatic self-calibration (ASC) can be (de-)activated with
* this command. When activated for the first time a period of minimum 7
* days is needed so that the algorithm can find its initial parameter set
* for ASC. The sensor has to be exposed to fresh air for at least 1 hour
* every day. Also during that period, the sensor may not be disconnected
* from the power supply. Otherwise the procedure to find calibration
* parameters is aborted and has to be restarted from the beginning. The
* successfully calculated parameters are stored in non-volatile memory of
* the SCD30 having the effect that after a restart the previously found
* parameters for ASC are still present.
*
* @param[in] doActivate Set activate flag.
*
* @note Note that the most recently found self-calibration parameters will
* be actively used for self-calibration disregarding the status of this
* feature. Finding a new parameter set by the here described method will
* always overwrite the settings from external recalibration and vice-versa.
* The feature is switched off by default. To work properly SCD30 has to see
* fresh air on a regular basis. Optimal working conditions are given when
* the sensor sees fresh air for one hour every day so that ASC can
* constantly re-calibrate. ASC only works in continuous measurement mode.
* ASC status is saved in non-volatile memory. When the sensor is powered
* down while ASC is activated SCD30 will continue with automatic
* self-calibration after repowering without sending the command.
*/
Scd30BusSpeed(SCD30_I2C_BUS_SPEED);
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) {
scd30.activateAutoCalibration(XdrvMailbox.payload);
}
/**
* Scd30Auto
*
* @brief Gets the status of auto calibration.
*
* Read out the status of the active self calibration.
*
* @param[out] isActive Indication if automatic calibration is active
*/
uint16_t state;
if (!CmndScd30Error(scd30.getAutoCalibrationStatus(state))) {
ResponseCmndStateText(state);
}
Scd30BusSpeed(0);
ResponseCmndNumber(value);
};
void CmndScd30Calibrate(void) {
uint16_t value = 0;
Scd30BusSpeed(1);
if (XdrvMailbox.data_len > 0) {
value = XdrvMailbox.payload;
scd30.setForcedRecalibrationFactor(value);
} else {
scd30.getForcedRecalibrationFactor(&value);
/**
* Scd30Cal 420
*
* @brief Forces recalibration with a new value for the CO₂ concentration.
*
* Forced recalibration (FRC) is used to compensate for sensor drifts when a
* reference value of the CO₂ concentration in close proximity to the SCD30
* is available. For best results, the sensor has to be run in a stable
* environment in continuous mode at a measurement rate of 2s for at least
* two minutes before applying the FRC command and sending the reference
* value. Setting a reference CO₂ concentration by the method described here
* will always supersede corrections from the ASC (see command
* activate_auto_calibration) and vice-versa. The reference CO₂
* concentration has to be within the range 400 ppm ≤ cref(CO₂) ≤ 2000 ppm.
* The FRC method imposes a permanent update of the CO₂ calibration curve
* which persists after repowering the sensor. The most recently used
* reference value is retained in volatile memory and can be read out with
* the command sequence given below. After repowering the sensor, the
* command will return the standard reference value of 400 ppm.
*
* @param[in] co2RefConcentration New CO2 reference concentration.
*/
Scd30BusSpeed(SCD30_I2C_BUS_SPEED);
if ((XdrvMailbox.payload >= 400) && (XdrvMailbox.payload <= 2000)) {
CmndScd30Error(scd30.forceRecalibration(XdrvMailbox.payload)); // Performs internal delay(10)
}
/**
* Scd30Cal
*
* @brief Gets the force recalibration status.
*
* Read out the CO₂ reference concentration.
*
* @param[out] co2RefConcentration Currently used CO2 reference
* concentration (default 400).
*/
uint16_t co2RefConcentration;
if (!CmndScd30Error(scd30.getForceRecalibrationStatus(co2RefConcentration))) {
ResponseCmndNumber(co2RefConcentration);
}
Scd30BusSpeed(0);
ResponseCmndNumber(value);
};
void CmndScd30Firmware(void) {
uint8_t major = 0;
uint8_t minor = 0;
Scd30BusSpeed(1);
int error = scd30.getFirmwareVersion(&major, &minor);
Scd30BusSpeed(0);
if (!error) {
float firmware = major + ((float)minor / 100);
ResponseCmndFloat(firmware, 2);
}
};
void CmndScd30Interval(void) {
uint16_t value = 0;
Scd30BusSpeed(1);
if (XdrvMailbox.data_len > 0) {
value = XdrvMailbox.payload;
int error = scd30.setMeasurementInterval(value);
if (!error) {
Scd30.interval = value;
/**
* Scd30Int 4
*
* @brief Sets the interval used to measure in continuous measurement mode.
*
* Sets the interval used by the SCD30 sensor to measure in continuous
* measurement mode. Initial value is 2s. The chosen measurement interval is
* saved in non-volatile memory and thus is not reset to its initial value
* after power up.
*
* @param[in] interval Measurement interval in seconds.
*/
Scd30BusSpeed(SCD30_I2C_BUS_SPEED);
if ((XdrvMailbox.payload >= 2) && (XdrvMailbox.payload <= 1800)) {
if (!CmndScd30Error(scd30.setMeasurementInterval(XdrvMailbox.payload))) {
SCD30DATA->interval = XdrvMailbox.payload;
}
}
scd30.getMeasurementInterval(&value);
/**
* Scd30Int
*
* @brief Read the configured measurement interval.
*
* Reads out the active measurement interval.
*
* @param[out] interval Configured measurement interval
*/
uint16_t interval;
if (!CmndScd30Error(scd30.getMeasurementInterval(interval))) {
ResponseCmndNumber(interval);
}
Scd30BusSpeed(0);
ResponseCmndNumber(value);
};
void CmndScd30Pressure(void) {
uint16_t value = 0;
Scd30BusSpeed(1);
if (XdrvMailbox.data_len > 0) {
value = XdrvMailbox.payload;
scd30.setAmbientPressure(value);
} else {
scd30.getAmbientPressure(&value);
// Scd30Pres 1013
// Scd30Pres
if ((0 == XdrvMailbox.payload) || ((XdrvMailbox.payload >= 700) && (XdrvMailbox.payload <= 1400))) {
if (XdrvMailbox.payload != SCD30DATA->pressure) {
/**
* @brief Starts continuous measurement of CO₂, relative humidity and
* temperature.
*
* Starts continuous measurement of the SCD30 to measure CO₂ concentration,
* humidity and temperature. Measurement data which is not read from the
* sensor will be overwritten. The CO₂ measurement value can be compensated
* for ambient pressure by feeding the pressure value in mBar to the sensor.
* Setting the ambient pressure will overwrite previous settings of altitude
* compensation. Setting the argument to zero will deactivate the ambient
* pressure compensation (default ambient pressure = 1013.25 mBar). For
* setting a new ambient pressure when continuous measurement is running the
* whole command has to be written to SCD30.
*
* @param[in] ambientPressure Ambient pressure in millibar (0, 700 to 1400).
*/
Scd30BusSpeed(SCD30_I2C_BUS_SPEED);
if (!CmndScd30Error(scd30.startPeriodicMeasurement(XdrvMailbox.payload))) {
SCD30DATA->pressure = XdrvMailbox.payload;
}
Scd30BusSpeed(0);
}
}
Scd30BusSpeed(0);
ResponseCmndNumber(value);
ResponseCmndNumber(SCD30DATA->pressure);
};
void CmndScd30TempOffset(void) {
uint16_t value = 0;
Scd30BusSpeed(1);
if (XdrvMailbox.data_len > 0) {
value = XdrvMailbox.payload;
scd30.setTemperatureOffset(value);
} else {
scd30.getTemperatureOffset(&value);
/**
* Scd30TOff 4.2
*
* @brief Set the temperature offset. Unit ℃ * 100.
*
* The on-board RH/T sensor is influenced by thermal self-heating of SCD30
* and other electrical components. Design-in alters the thermal properties
* of SCD30 such that temperature and humidity offsets may occur when
* operating the sensor in end-customer devices. Compensation of those
* effects is achievable by writing the temperature offset found in
* continuous operation of the device into the sensor. Temperature offset
* value is saved in non-volatile memory. The last set value will be used
* for temperature offset compensation after repowering.
*
* @param[in] temperatureOffset Temperature in ℃ * 100 (0 to 2000).
*/
Scd30BusSpeed(SCD30_I2C_BUS_SPEED);
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 20)) {
float offset_f = CharToFloat(XdrvMailbox.data); // 0 to 20.00
uint16_t offset = offset_f * 100.0;
scd30.setTemperatureOffset(offset);
}
/**
* Scd30TOff
*
* @brief Get the temperature offset. Unit ℃ * 100.
*
* Read out the actual temperature offset. The result can be converted to ℃
* by dividing it by 100.
*
* @param[out] temperatureOffset
*/
uint16_t offset;
if (!CmndScd30Error(scd30.getTemperatureOffset(offset))) {
float offset_f = offset / 100.0;
ResponseCmndFloat(offset_f, Settings->flag2.temperature_resolution); // TempRes
}
Scd30BusSpeed(0);
ResponseCmndNumber(value);
};
/********************************************************************************************/
/*********************************************************************************************\
* Presentation
\*********************************************************************************************/
void Scd30Show(bool json) {
if (Scd30.data_valid) {
float t = ConvertTemp(Scd30.temperature);
float h = ConvertHumidity(Scd30.humidity);
if (!SCD30DATA->data_valid) { return; }
if (json) {
ResponseAppend_P(PSTR(",\"SCD30\":{\"" D_JSON_CO2 "\":%d,\"" D_JSON_ECO2 "\":%d,"), Scd30.co2, Scd30.co2e_avg);
ResponseAppendTHD(t, h);
ResponseJsonEnd();
float t = ConvertTemp(SCD30DATA->temperature);
float h = ConvertHumidity(SCD30DATA->humidity);
if (json) {
ResponseAppend_P(PSTR(",\"SCD30\":{\"" D_JSON_CO2 "\":%d,"), SCD30DATA->co2);
ResponseAppendTHD(t, h);
ResponseJsonEnd();
#ifdef USE_DOMOTICZ
if (0 == TasmotaGlobal.tele_period) {
DomoticzSensor(DZ_AIRQUALITY, Scd30.co2);
DomoticzTempHumPressureSensor(t, h);
}
if (0 == TasmotaGlobal.tele_period) {
DomoticzSensor(DZ_AIRQUALITY, SCD30DATA->co2);
DomoticzTempHumPressureSensor(t, h);
}
#endif // USE_DOMOTICZ
#ifdef USE_WEBSERVER
} else {
WSContentSend_PD(HTTP_SNS_CO2EAVG, "SCD30", Scd30.co2e_avg);
WSContentSend_PD(HTTP_SNS_CO2, "SCD30", Scd30.co2);
WSContentSend_THD("SCD30", t, h);
} else {
WSContentSend_PD(HTTP_SNS_CO2, "SCD30", SCD30DATA->co2);
WSContentSend_THD("SCD30", t, h);
#endif // USE_WEBSERVER
}
}
}
@@ -358,18 +516,15 @@ bool Xsns42(uint32_t function) {
Scd30Detect();
}
*/
if (!Scd30.init_once && (FUNC_EVERY_SECOND == function) && (TasmotaGlobal.uptime > 3)) {
Scd30.init_once = true;
Scd30Detect();
if (!scd30_init_once && (FUNC_EVERY_SECOND == function) && (TasmotaGlobal.uptime > 3)) {
scd30_init_once = true;
Scd30Init();
}
else if (Scd30.found) {
else if (SCD30DATA) {
switch (function) {
case FUNC_EVERY_SECOND:
Scd30Update();
break;
case FUNC_COMMAND:
result = DecodeCommand(kScd30Commands, kScd30Command);
break;
case FUNC_JSON_APPEND:
Scd30Show(1);
break;
@@ -378,6 +533,9 @@ bool Xsns42(uint32_t function) {
Scd30Show(0);
break;
#endif // USE_WEBSERVER
case FUNC_COMMAND:
result = DecodeCommand(kScd30Commands, kScd30Command);
break;
}
}
return result;