Changed SCD30 library FrogmoreScd30 to Sensirion arduino-i2c-scd30 v1.1.1
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
14
lib/lib_i2c/arduino-i2c-scd30/.clang-format
Normal 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']
|
||||
...
|
||||
33
lib/lib_i2c/arduino-i2c-scd30/CHANGELOG.md
Normal 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
|
||||
29
lib/lib_i2c/arduino-i2c-scd30/LICENSE
Normal 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.
|
||||
221
lib/lib_i2c/arduino-i2c-scd30/README.md
Normal 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).
|
||||
@@ -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();
|
||||
}
|
||||
|
After Width: | Height: | Size: 405 KiB |
|
After Width: | Height: | Size: 322 KiB |
|
After Width: | Height: | Size: 297 KiB |
|
After Width: | Height: | Size: 316 KiB |
|
After Width: | Height: | Size: 525 KiB |
BIN
lib/lib_i2c/arduino-i2c-scd30/images/scd30_pinout.jpg
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
lib/lib_i2c/arduino-i2c-scd30/images/sensor_scd30_image.jpg
Normal file
|
After Width: | Height: | Size: 13 KiB |
43
lib/lib_i2c/arduino-i2c-scd30/keywords.txt
Normal 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)
|
||||
#######################################
|
||||
11
lib/lib_i2c/arduino-i2c-scd30/library.properties
Normal 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
|
||||
7
lib/lib_i2c/arduino-i2c-scd30/metadata.yml
Normal 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'
|
||||
436
lib/lib_i2c/arduino-i2c-scd30/src/SensirionI2cScd30.cpp
Normal 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;
|
||||
}
|
||||
463
lib/lib_i2c/arduino-i2c-scd30/src/SensirionI2cScd30.h
Normal 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 sensor’s 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
|
||||
@@ -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
|
||||
\*-------------------------------------------------------------------------------------------*/
|
||||
|
||||
@@ -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 sensor’s 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;
|
||||
|
||||