Add support for Sensirion STCC4 CO2 sensor

- Change SHT1x software reset I2C bus after initial (un)detection
This commit is contained in:
Theo Arends
2026-02-23 16:16:54 +01:00
parent 598b030a2b
commit 189dd2dd30
29 changed files with 1748 additions and 38 deletions
+2
View File
@@ -7,6 +7,7 @@ All notable changes to this project will be documented in this file.
### Added
- Support for Sensirion SCD43 CO2 sensor
- I2S full duplex, auto rx sample rate (#24469)
- Support for Sensirion STCC4 CO2 sensor
### Breaking Changed
@@ -15,6 +16,7 @@ All notable changes to this project will be documented in this file.
- Matter improved parameters handling (#24471)
- Sensirion Core library from v0.6.0 to v0.7.2
- Sen5x power on delay of 60ms (#24452)
- SHT1x software reset I2C bus after initial (un)detection
### Fixed
- Do not free BT memory when in use (#24480)
+1
View File
@@ -236,6 +236,7 @@ In addition to @arendst the following code is mainly owned by:
| xsns_117_c8_co2_5k | @jeroenvermeulen
| xsns_118_ags02ma | Akshaylal S
| xsns_119_sen6x | @arendst
| xsns_120_stcc4 | @arendst
| |
| xsns_127_esp32_sensors | @arendst
| |
+2 -1
View File
@@ -139,5 +139,6 @@ Index | Define | Driver | Device | Address(es) | Bus2 | Descrip
96 | USE_RX8025 | xdrv_56 | RX8025 | 0x32 | Yes | RX8025 RTC
97 | USE_SEN6X | xsns_119 | SEN6X | 0x6B | Yes | Gas (CO2/VOC/NOx index) and air quality (PPM <1,<2.5,<4,<10)
98 | USE_FM24CXX | xdrv_93 | FM24CXX | 0x50 - 0x57| Yes | FM24CXX - External FRAM with console / berry R/W operations
99 | USE_STCC4 | xsns_120 | STCC4 | 0x64, 0x65 | Yes | CO2 and optional temperature and humidity sensor
NOTE: Bus2 supported on ESP32 only.
+2
View File
@@ -115,12 +115,14 @@ The latter links can be used for OTA upgrades too like ``OtaUrl https://ota.tasm
## Changelog v15.3.0.1
### Added
- Support for Sensirion SCD42 and SCD43 CO2 sensor
- Support for Sensirion STCC4 CO2 sensor
- I2S full duplex, auto rx sample rate [#24469](https://github.com/arendst/Tasmota/issues/24469)
- Sen5x power on delay of 60ms [#24452](https://github.com/arendst/Tasmota/issues/24452)
### Changed
- Sensirion Core library from v0.6.0 to v0.7.2
- LVGL library from v9.4.0 to v9.5.0 [#24470](https://github.com/arendst/Tasmota/issues/24470)
- SHT1x software reset I2C bus after initial (un)detection
- Matter improved parameters handling [#24471](https://github.com/arendst/Tasmota/issues/24471)
### Fixed
@@ -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']
...
@@ -0,0 +1,15 @@
# 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.0.0] - 2025-6-20
### Added
- Initial support for STCC4.
[Unreleased]: https://github.com/Sensirion/arduino-i2c-stcc4/compare/1.0.0...HEAD
[1.0.0]: https://github.com/Sensirion/arduino-i2c-stcc4/releases/tag/1.0.0
+29
View File
@@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2025, 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.
+197
View File
@@ -0,0 +1,197 @@
# Sensirion I²C STCC4 Arduino Library
This is the Sensirion STCC4 library for Arduino allowing you to
communicate with a STCC4 sensor
over I²C.
<img src="images/STCC4.png" width="300px">
Click [here](https://sensirion.com/products/catalog/STCC4) to learn more about the Sensirion STCC4 sensor.
The default I²C address of [STCC4](https://sensirion.com/products/catalog/STCC4) is **0x64**.
> [!NOTE]
> The SEK-STCC4 board from Sensirion includes a STCC4 and a SHT4x for temperature and humidity compensation, which is controlled by the STCC4 through the integrated I2C controller interface. The provided examples are designed considering this sensor configuration.
## 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 STCC4` 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 STCC4 to the standard I²C bus of your Arduino board:
<img src="images/STCC4_pinout.png" width="300px">
| *Pin* | *Cable Color* | *Name* | *Description* | *Comments* |
|-------|---------------|:------:|----------------|------------|
| 1 | black | GND | Ground |
| 2 | red | VDD | Supply Voltage | 2.7V to 5.5V
| 3 | green | SDA | I2C: Serial data input / output |
| 4 | yellow | SCL | I2C: Serial clock input |
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>
| *STCC4* | *STCC4 Pin* | *Cable Color* | *Board Pin* |
| :---: | --- | --- | --- |
| GND | 1 | black | GND |
| VDD | 2 | red | 3.3V |
| SDA | 3 | green | D18/SDA |
| SCL | 4 | yellow | D19/SCL |
<img src="images/Arduino-Uno-Rev3-i2c-pinout-3.3V.png" width="600px">
</p>
</details>
<details><summary>Arduino Nano</summary>
<p>
| *STCC4* | *STCC4 Pin* | *Cable Color* | *Board Pin* |
| :---: | --- | --- | --- |
| GND | 1 | black | GND |
| VDD | 2 | red | 3.3V |
| SDA | 3 | green | A4 |
| SCL | 4 | yellow | A5 |
<img src="images/Arduino-Nano-i2c-pinout-3.3V.png" width="600px">
</p>
</details>
<details><summary>Arduino Micro</summary>
<p>
| *STCC4* | *STCC4 Pin* | *Cable Color* | *Board Pin* |
| :---: | --- | --- | --- |
| GND | 1 | black | GND |
| VDD | 2 | red | 3.3V |
| SDA | 3 | green | D2/SDA |
| SCL | 4 | yellow | ~D3/SCL |
<img src="images/Arduino-Micro-i2c-pinout-3.3V.png" width="600px">
</p>
</details>
<details><summary>Arduino Mega 2560</summary>
<p>
| *STCC4* | *STCC4 Pin* | *Cable Color* | *Board Pin* |
| :---: | --- | --- | --- |
| GND | 1 | black | GND |
| VDD | 2 | red | 3.3V |
| SDA | 3 | green | D20/SDA |
| SCL | 4 | yellow | D21/SCL |
<img src="images/Arduino-Mega-2560-Rev3-i2c-pinout-3.3V.png" width="600px">
</p>
</details>
<details><summary>ESP32 DevKitC</summary>
<p>
| *STCC4* | *STCC4 Pin* | *Cable Color* | *Board Pin* |
| :---: | --- | --- | --- |
| GND | 1 | black | GND |
| VDD | 2 | red | 3V3 |
| SDA | 3 | green | GPIO 21 |
| SCL | 4 | yellow | GPIO 22 |
<img src="images/esp32-devkitc-i2c-pinout-3.3V.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 STCC4 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 STCC4``exampleUsage`
5. Click the `Upload` button in the Arduino IDE or `Sketch``Upload`
4. 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.
:
## License
See [LICENSE](LICENSE).
@@ -0,0 +1,139 @@
/*
* THIS FILE IS AUTOMATICALLY GENERATED
*
* Generator: sensirion-driver-generator 1.2.0
* Product: stcc4
* Model-Version: 3.4.0
*/
/*
* Copyright (c) 2025, 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 <SensirionI2cStcc4.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
SensirionI2cStcc4 sensor;
static char errorMessage[64];
static int16_t error;
void PrintUint64(uint64_t& value) {
Serial.print("0x");
Serial.print((uint32_t)(value >> 32), HEX);
Serial.print((uint32_t)(value & 0xFFFFFFFF), HEX);
}
void setup() {
Serial.begin(115200);
while (!Serial) {
delay(100);
}
Wire.begin();
sensor.begin(Wire, STCC4_I2C_ADDR_64);
uint32_t productId = 0;
uint64_t serialNumber = 0;
error = sensor.stopContinuousMeasurement();
if (error != NO_ERROR) {
Serial.print("Error trying to execute stopContinuousMeasurement(): ");
errorToString(error, errorMessage, sizeof errorMessage);
Serial.println(errorMessage);
return;
}
error = sensor.getProductId(productId, serialNumber);
if (error != NO_ERROR) {
Serial.print("Error trying to execute getProductId(): ");
errorToString(error, errorMessage, sizeof errorMessage);
Serial.println(errorMessage);
return;
}
Serial.print("productId: ");
Serial.print(productId);
Serial.print("\t");
Serial.print("serialNumber: ");
PrintUint64(serialNumber);
Serial.println();
error = sensor.startContinuousMeasurement();
if (error != NO_ERROR) {
Serial.print("Error trying to execute startContinuousMeasurement(): ");
errorToString(error, errorMessage, sizeof errorMessage);
Serial.println(errorMessage);
return;
}
}
void loop() {
int16_t co2Concentration = 0;
float temperature = 0.0;
float relativeHumidity = 0.0;
uint16_t sensorStatus = 0;
delay(1000);
error = sensor.readMeasurement(co2Concentration, temperature,
relativeHumidity, sensorStatus);
if (error != NO_ERROR) {
// A failed read can be caused by clock shifting. We advise to retry
// after a delay of 150ms.
Serial.print(
"Error trying to execute readMeasurement() (retry in 150ms): ");
errorToString(error, errorMessage, sizeof errorMessage);
Serial.println(errorMessage);
delay(150);
error = sensor.readMeasurement(co2Concentration, temperature,
relativeHumidity, sensorStatus);
if (error != NO_ERROR) {
Serial.print("Error trying to execute readMeasurement() after "
"additional delay: ");
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("relativeHumidity: ");
Serial.print(relativeHumidity);
Serial.print("\t");
Serial.print("sensorStatus: ");
Serial.print(sensorStatus);
Serial.println();
}
@@ -0,0 +1,165 @@
/*
* THIS FILE IS AUTOMATICALLY GENERATED
*
* Generator: sensirion-driver-generator 1.2.0
* Product: stcc4
* Model-Version: 3.4.0
*/
/*
* Copyright (c) 2025, 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 <SensirionI2cStcc4.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
SensirionI2cStcc4 sensor;
static char errorMessage[64];
static int16_t error;
void setup() {
Serial.begin(115200);
while (!Serial) {
delay(100);
}
Wire.begin();
sensor.begin(Wire, STCC4_I2C_ADDR_64);
delay(6);
// Ensure sensor is in idle state
error = sensor.exitSleepMode();
if (error != NO_ERROR) {
Serial.print("Error trying to execute exitSleepMode(): ");
errorToString(error, errorMessage, sizeof errorMessage);
Serial.println(errorMessage);
return;
}
error = sensor.stopContinuousMeasurement();
if (error != NO_ERROR) {
Serial.print("Error trying to execute stopContinuousMeasurement(): ");
errorToString(error, errorMessage, sizeof errorMessage);
Serial.println(errorMessage);
return;
}
// Enter sleep mode
error = sensor.enterSleepMode();
if (error != NO_ERROR) {
Serial.print("Error trying to execute enterSleepMode(): ");
errorToString(error, errorMessage, sizeof errorMessage);
Serial.println(errorMessage);
return;
}
}
void loop() {
int16_t co2Concentration = 0;
float temperature = 0.0;
float relativeHumidity = 0.0;
uint16_t status = 0;
//
// Measure every 10 seconds.
delay(10000);
//
// Exit sleep mode to put the sensor into idle mode
// to be able to perform a single shot measurement
error = sensor.exitSleepMode();
if (error != NO_ERROR) {
Serial.print("Error trying to execute exitSleepMode(): ");
errorToString(error, errorMessage, sizeof errorMessage);
Serial.println(errorMessage);
return;
}
//
// If humidity/temperature and/or pressure compensation is
// desired, you should call the respective compensation
// functions here. Check-out the header file for the definition
// of the compensation functions.
//
// Perform a single shot measurement and read the sensor data
error = sensor.measureSingleShot();
if (error != NO_ERROR) {
Serial.print("Error trying to execute measureSingleShot(): ");
errorToString(error, errorMessage, sizeof errorMessage);
Serial.println(errorMessage);
return;
}
error = sensor.readMeasurement(co2Concentration, temperature,
relativeHumidity, status);
if (error != NO_ERROR) {
// A failed read can be caused by clock shifting. We advise to retry
// after a delay of 150ms.
Serial.print(
"Error trying to execute readMeasurement() (retry in 150ms): ");
errorToString(error, errorMessage, sizeof errorMessage);
Serial.println(errorMessage);
delay(150);
error = sensor.readMeasurement(co2Concentration, temperature,
relativeHumidity, status);
if (error != NO_ERROR) {
Serial.print("Error trying to execute readMeasurement() after "
"additional delay: ");
errorToString(error, errorMessage, sizeof errorMessage);
Serial.println(errorMessage);
return;
}
}
//
// Power down the sensor to reduce power consumption.
error = sensor.enterSleepMode();
if (error != NO_ERROR) {
Serial.print("Error trying to execute enterSleepMode(): ");
errorToString(error, errorMessage, sizeof errorMessage);
Serial.println(errorMessage);
return;
}
//
// Print results as physical unit.
Serial.print("CO2 concentration [ppm] = ");
Serial.print(co2Concentration);
Serial.println();
Serial.print("Temperature [°C] = ");
Serial.print(temperature);
Serial.println();
Serial.print("Humidity [RH] = ");
Serial.print(relativeHumidity);
Serial.println();
Serial.print("Status = ");
Serial.print(status);
Serial.println();
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 405 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 524 KiB

@@ -0,0 +1,43 @@
#######################################
# Syntax Coloring Map
#######################################
#######################################
# Datatypes (KEYWORD1)
#######################################
SensirionI2cStcc4 KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
readMeasurement KEYWORD2
setPressureCompensation KEYWORD2
startContinuousMeasurement KEYWORD2
readMeasurementRaw KEYWORD2
stopContinuousMeasurement KEYWORD2
measureSingleShot KEYWORD2
performForcedRecalibration KEYWORD2
getProductId KEYWORD2
setRhtCompensation KEYWORD2
setPressureCompensationRaw KEYWORD2
performSelfTest KEYWORD2
performConditioning KEYWORD2
enterSleepMode KEYWORD2
exitSleepMode KEYWORD2
enableTestingMode KEYWORD2
disableTestingMode KEYWORD2
performFactoryReset KEYWORD2
signalTemperature KEYWORD2
signalRelativeHumidity KEYWORD2
#######################################
# Instances (KEYWORD2)
#######################################
sensor KEYWORD2
#######################################
# Constants (LITERAL1)
#######################################
@@ -0,0 +1,11 @@
name=Sensirion I2C STCC4
version=1.0.0
author=Sensirion
maintainer=Sensirion
sentence=Library for the STCC4 sensor by Sensirion
paragraph=Enables you to use the STCC4 sensor via I2C.
url=https://github.com/Sensirion/arduino-i2c-stcc4
category=Sensors
architectures=*
depends=Sensirion Core
includes=SensirionI2cStcc4.h
@@ -0,0 +1,7 @@
# driver generation metadata
generator_version: 1.2.0
model_version: 3.4.0
dg_status: released
is_manually_modified: true
first_generated: '2025-02-24 11:47'
last_generated: '2025-06-20 08:02'
@@ -0,0 +1,364 @@
/*
* THIS FILE IS AUTOMATICALLY GENERATED
*
* Generator: sensirion-driver-generator 1.2.0
* Product: stcc4
* Model-Version: 3.4.0
*/
/*
* Copyright (c) 2025, 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 "SensirionI2cStcc4.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[18] = {0};
SensirionI2cStcc4::SensirionI2cStcc4() {
}
float SensirionI2cStcc4::signalTemperature(uint16_t rawTemperature) {
float temperature = 0.0;
temperature = -45.0 + ((175.0 * rawTemperature) / 65535.0);
return temperature;
}
float SensirionI2cStcc4::signalRelativeHumidity(uint16_t rawRelativeHumidity) {
float relativeHumidity = 0.0;
relativeHumidity = -6.0 + ((125.0 * rawRelativeHumidity) / 65535.0);
return relativeHumidity;
}
int16_t SensirionI2cStcc4::readMeasurement(int16_t& co2Concentration,
float& temperature,
float& relativeHumidity,
uint16_t& sensorStatus) {
int16_t rawCo2Concentration = 0;
uint16_t rawTemperature = 0;
uint16_t rawRelativeHumidity = 0;
uint16_t sensorStatusRaw = 0;
int16_t localError = 0;
localError = readMeasurementRaw(rawCo2Concentration, rawTemperature,
rawRelativeHumidity, sensorStatusRaw);
if (localError != NO_ERROR) {
return localError;
}
co2Concentration = rawCo2Concentration;
temperature = SensirionI2cStcc4::signalTemperature(rawTemperature);
relativeHumidity =
SensirionI2cStcc4::signalRelativeHumidity(rawRelativeHumidity);
sensorStatus = sensorStatusRaw;
return localError;
}
int16_t SensirionI2cStcc4::setPressureCompensation(uint16_t pressure) {
int16_t localError = 0;
localError = setPressureCompensationRaw((uint16_t)(pressure / 2));
if (localError != NO_ERROR) {
return localError;
}
return localError;
}
int16_t SensirionI2cStcc4::startContinuousMeasurement() {
int16_t localError = NO_ERROR;
uint8_t* buffer_ptr = communication_buffer;
SensirionI2CTxFrame txFrame =
SensirionI2CTxFrame::createWithUInt16Command(0x218b, buffer_ptr, 2);
localError =
SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
return localError;
}
int16_t SensirionI2cStcc4::readMeasurementRaw(int16_t& co2ConcentrationRaw,
uint16_t& temperatureRaw,
uint16_t& relativeHumidityRaw,
uint16_t& sensorStatusRaw) {
int16_t localError = NO_ERROR;
uint8_t* buffer_ptr = communication_buffer;
SensirionI2CTxFrame txFrame =
SensirionI2CTxFrame::createWithUInt16Command(0xec05, buffer_ptr, 12);
localError =
SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
delay(1);
SensirionI2CRxFrame rxFrame(buffer_ptr, 12);
localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 12,
rxFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
localError |= rxFrame.getInt16(co2ConcentrationRaw);
localError |= rxFrame.getUInt16(temperatureRaw);
localError |= rxFrame.getUInt16(relativeHumidityRaw);
localError |= rxFrame.getUInt16(sensorStatusRaw);
return localError;
}
int16_t SensirionI2cStcc4::stopContinuousMeasurement() {
int16_t localError = NO_ERROR;
uint8_t* buffer_ptr = communication_buffer;
SensirionI2CTxFrame txFrame =
SensirionI2CTxFrame::createWithUInt16Command(0x3f86, buffer_ptr, 2);
localError =
SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
delay(1200);
return localError;
}
int16_t SensirionI2cStcc4::measureSingleShot() {
int16_t localError = NO_ERROR;
uint8_t* buffer_ptr = communication_buffer;
SensirionI2CTxFrame txFrame =
SensirionI2CTxFrame::createWithUInt16Command(0x219d, buffer_ptr, 2);
localError =
SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
delay(500);
return localError;
}
int16_t
SensirionI2cStcc4::performForcedRecalibration(int16_t targetCO2Concentration,
int16_t& frcCorrection) {
int16_t localError = NO_ERROR;
uint8_t* buffer_ptr = communication_buffer;
SensirionI2CTxFrame txFrame =
SensirionI2CTxFrame::createWithUInt16Command(0x362f, buffer_ptr, 5);
localError |= txFrame.addInt16(targetCO2Concentration);
if (localError != NO_ERROR) {
return localError;
}
localError =
SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
delay(90);
SensirionI2CRxFrame rxFrame(buffer_ptr, 5);
localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 3,
rxFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
localError |= rxFrame.getInt16(frcCorrection);
return localError;
}
int16_t SensirionI2cStcc4::getProductId(uint32_t& productId,
uint64_t& serialNumber) {
int16_t localError = NO_ERROR;
uint8_t* buffer_ptr = communication_buffer;
SensirionI2CTxFrame txFrame =
SensirionI2CTxFrame::createWithUInt16Command(0x365b, buffer_ptr, 18);
localError =
SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
delay(1);
SensirionI2CRxFrame rxFrame(buffer_ptr, 18);
localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 18,
rxFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
localError |= rxFrame.getUInt32(productId);
localError |= rxFrame.getInteger((uint8_t*)&serialNumber, LongInteger, 8);
return localError;
}
int16_t SensirionI2cStcc4::setRhtCompensation(uint16_t rawTemperature,
uint16_t rawHumidity) {
int16_t localError = NO_ERROR;
uint8_t* buffer_ptr = communication_buffer;
SensirionI2CTxFrame txFrame =
SensirionI2CTxFrame::createWithUInt16Command(0xe000, buffer_ptr, 8);
localError |= txFrame.addUInt16(rawTemperature);
if (localError != NO_ERROR) {
return localError;
}
localError |= txFrame.addUInt16(rawHumidity);
if (localError != NO_ERROR) {
return localError;
}
localError =
SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
delay(1);
return localError;
}
int16_t SensirionI2cStcc4::setPressureCompensationRaw(uint16_t pressure) {
int16_t localError = NO_ERROR;
uint8_t* buffer_ptr = communication_buffer;
SensirionI2CTxFrame txFrame =
SensirionI2CTxFrame::createWithUInt16Command(0xe016, buffer_ptr, 5);
localError |= txFrame.addUInt16(pressure);
if (localError != NO_ERROR) {
return localError;
}
localError =
SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
delay(1);
return localError;
}
int16_t SensirionI2cStcc4::performSelfTest(uint16_t& testResult) {
int16_t localError = NO_ERROR;
uint8_t* buffer_ptr = communication_buffer;
SensirionI2CTxFrame txFrame =
SensirionI2CTxFrame::createWithUInt16Command(0x278c, buffer_ptr, 3);
localError =
SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
delay(360);
SensirionI2CRxFrame rxFrame(buffer_ptr, 3);
localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 3,
rxFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
localError |= rxFrame.getUInt16(testResult);
return localError;
}
int16_t SensirionI2cStcc4::performConditioning() {
int16_t localError = NO_ERROR;
uint8_t* buffer_ptr = communication_buffer;
SensirionI2CTxFrame txFrame =
SensirionI2CTxFrame::createWithUInt16Command(0x29bc, buffer_ptr, 2);
localError =
SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
delay(22000);
return localError;
}
int16_t SensirionI2cStcc4::enterSleepMode() {
int16_t localError = NO_ERROR;
uint8_t* buffer_ptr = communication_buffer;
SensirionI2CTxFrame txFrame =
SensirionI2CTxFrame::createWithUInt16Command(0x3650, buffer_ptr, 2);
localError =
SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
delay(1);
return localError;
}
int16_t SensirionI2cStcc4::exitSleepMode() {
int16_t localError = NO_ERROR;
uint8_t* buffer_ptr = communication_buffer;
SensirionI2CTxFrame txFrame =
SensirionI2CTxFrame::createWithUInt8Command(0x0, buffer_ptr, 2);
SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
delay(5);
return localError;
}
int16_t SensirionI2cStcc4::enableTestingMode() {
int16_t localError = NO_ERROR;
uint8_t* buffer_ptr = communication_buffer;
SensirionI2CTxFrame txFrame =
SensirionI2CTxFrame::createWithUInt16Command(0x3fbc, buffer_ptr, 2);
localError =
SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
return localError;
}
int16_t SensirionI2cStcc4::disableTestingMode() {
int16_t localError = NO_ERROR;
uint8_t* buffer_ptr = communication_buffer;
SensirionI2CTxFrame txFrame =
SensirionI2CTxFrame::createWithUInt16Command(0x3f3d, buffer_ptr, 2);
localError =
SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
return localError;
}
int16_t SensirionI2cStcc4::performFactoryReset(uint16_t& factoryResetResult) {
int16_t localError = NO_ERROR;
uint8_t* buffer_ptr = communication_buffer;
SensirionI2CTxFrame txFrame =
SensirionI2CTxFrame::createWithUInt16Command(0x3632, buffer_ptr, 3);
localError =
SensirionI2CCommunication::sendFrame(_i2cAddress, txFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
delay(90);
SensirionI2CRxFrame rxFrame(buffer_ptr, 3);
localError = SensirionI2CCommunication::receiveFrame(_i2cAddress, 3,
rxFrame, *_i2cBus);
if (localError != NO_ERROR) {
return localError;
}
localError |= rxFrame.getUInt16(factoryResetResult);
return localError;
}
void SensirionI2cStcc4::begin(TwoWire& i2cBus, uint8_t i2cAddress) {
_i2cBus = &i2cBus;
_i2cAddress = i2cAddress;
}
@@ -0,0 +1,402 @@
/*
* THIS FILE IS AUTOMATICALLY GENERATED
*
* Generator: sensirion-driver-generator 1.2.0
* Product: stcc4
* Model-Version: 3.4.0
*/
/*
* Copyright (c) 2025, 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 SENSIRIONI2CSTCC4_H
#define SENSIRIONI2CSTCC4_H
#include <SensirionCore.h>
#include <Wire.h>
#define STCC4_I2C_ADDR_64 0x64
typedef enum {
STCC4_START_CONTINUOUS_MEASUREMENT_CMD_ID = 0x218b,
STCC4_READ_MEASUREMENT_RAW_CMD_ID = 0xec05,
STCC4_STOP_CONTINUOUS_MEASUREMENT_CMD_ID = 0x3f86,
STCC4_MEASURE_SINGLE_SHOT_CMD_ID = 0x219d,
STCC4_PERFORM_FORCED_RECALIBRATION_CMD_ID = 0x362f,
STCC4_GET_PRODUCT_ID_CMD_ID = 0x365b,
STCC4_SET_RHT_COMPENSATION_CMD_ID = 0xe000,
STCC4_SET_PRESSURE_COMPENSATION_RAW_CMD_ID = 0xe016,
STCC4_PERFORM_SELF_TEST_CMD_ID = 0x278c,
STCC4_PERFORM_CONDITIONING_CMD_ID = 0x29bc,
STCC4_ENTER_SLEEP_MODE_CMD_ID = 0x3650,
STCC4_EXIT_SLEEP_MODE_CMD_ID = 0x0,
STCC4_ENABLE_TESTING_MODE_CMD_ID = 0x3fbc,
STCC4_DISABLE_TESTING_MODE_CMD_ID = 0x3f3d,
STCC4_PERFORM_FACTORY_RESET_CMD_ID = 0x3632,
} STCC4CmdId;
class SensirionI2cStcc4 {
public:
SensirionI2cStcc4();
/**
* @brief Initializes the STCC4 class.
*
* @param i2cBus Arduino stream object to be used for communication.
*/
void begin(TwoWire& i2cBus, uint8_t i2cAddress);
/**
* @brief readMeasurement
*
* reads measurement data
*
* @param[out] co2Concentration
* @param[out] temperature
* @param[out] relativeHumidity
* @param[out] sensorStatus
*
* @return error_code 0 on success, an error code otherwise.
*/
int16_t readMeasurement(int16_t& co2Concentration, float& temperature,
float& relativeHumidity, uint16_t& sensorStatus);
/**
* @brief Set the pressure compensation in Pa
*
* External pressure values can be set through the set_pressure_compensation
* command. The written pressure value is applied for pressure compensation
* after a maximum of one measurement interval. Power cycling resets the
* sensor to the default value of 101'300 Pa.
*
* @param[in] pressure
*
* @return error_code 0 on success, an error code otherwise.
*/
int16_t setPressureCompensation(uint16_t pressure);
/**
* @brief Start a continuous measurement (interval 1 s).
*
* After sending the start_continuous_measurement command, the sensor will
* begin measuring the CO2 gas concentration continuously with a sampling
* interval of 1 second. The recommended communication sequence for
* continuous measurement is as follows: 1. The sensor is powered up into
* the idle state. 2. The I2C controller sends a
* start_continuous_measurement command. 3. The I2C controller periodically
* reads out data with the read_measurement command. 4. If desired, stop
* taking measurements using the stop_continuous_measurement command.
*
* @return error_code 0 on success, an error code otherwise.
*/
int16_t startContinuousMeasurement();
/**
* @brief Read CO, temperature, and humidity measurements.
*
* After the command *start_continuous_measurement* has been sent, the chip
* continuously measures and updates the measurement results. New results
* (co2_concentration, temperature, and relative_humidity) can be read
* continuously with this command.
*
* @param[out] co2ConcentrationRaw CO2 concentration in ppm. No conversion
* needed.
* @param[out] temperatureRaw Raw temperature signal. The signal s can be
* converted into a temperature in °C (t) by applying the formula t = -45 +
* 175
* * s / 65535.
* @param[out] relativeHumidityRaw Raw relative humidity signal. The signal
* s can be converted into relative humidity in % (rh) by applying the
* formula rh = -6 + 125 * s / 65535.
* @param[out] sensorStatusRaw The sensor status. If the sensor is in
* testing mode, byte 10 is equal to 4.
*
* @return error_code 0 on success, an error code otherwise.
*/
int16_t readMeasurementRaw(int16_t& co2ConcentrationRaw,
uint16_t& temperatureRaw,
uint16_t& relativeHumidityRaw,
uint16_t& sensorStatusRaw);
/**
* @brief The command stops the continuous measurement and puts the sensor
* into idle mode.
*
* After receiving the stop_continuous_measurement command, the sensor will
* finish the currently running measurement before returning to idle mode.
* Therefore, a wait time of one measurement interval plus a 200 ms clock
* tolerance is required before a new command is acknowledged.
*
* @return error_code 0 on success, an error code otherwise.
*/
int16_t stopContinuousMeasurement();
/**
* @brief Performs a single shot measurement.
*
* The measure_single_shot command conducts an on-demand measurement of CO2
* gas concentration. The typical communication sequence is as follows: 1.
* The sensor is powered up with the exit_sleep_mode command if previously
* powered down using the enter_sleep_mode command. 2. The I2C controller
* sends a measure_single_shot command and waits for the execution time. 3.
* The I2C controller reads out data with the read_measurement command. 4.
* Repeat steps 2-3 as required by the application. 5. If desired, set the
* sensor to sleep with the enter_sleep_mode command.
*
* @return error_code 0 on success, an error code otherwise.
*/
int16_t measureSingleShot();
/**
* @brief Perform a forced recalibration (FRC) of the CO concentration.
*
* The forced recalibration (FRC) can be applied to correct the sensor's
* output by means of an externally obtained CO 2 reference value. The
* recommended communication sequence for a successful FRC in continuous
* mode is as follows:
* 1. Operate the STCC4 for at least 3 minutes. Ensure the sensor reading
* and environmental conditions, including CO2 concentration, are stable. 2.
* The I2C controller sends the stop_continuous_measurement command
* (Section 5.3.2) 3. Wait for the specified execution time of
* stop_continuous_measurement command.
* 4. Issue the perform_forced_recalibration command with the target CO2
* concentration and read out the applied FRC correction.
*
* The recommended communication sequence for a successful FRC in single
* shot mode is as follows: 1. Operate the STCC4 for at least 3 minutes. For
* sampling intervals significantly larger than 10 seconds, increase the
* operation time accordingly. Ensure the sensor reading and environmental
* conditions, including CO2 concentration, are stable. 2. Issue the
* perform_forced_recalibration command with the reference CO2 concentration
* and read out the applied FRC correction. The sensor must remain in idle
* mode after operation before command execution.
*
* @param[in] targetCO2Concentration Target CO concentration in ppm.
* @param[out] frcCorrection Returns the FRC correction value if FRC has
* been successful. 0xFFFF on failure.
*
* @return error_code 0 on success, an error code otherwise.
*/
int16_t performForcedRecalibration(int16_t targetCO2Concentration,
int16_t& frcCorrection);
/**
* @brief Read the sensor's 32-bit product id and 64-bit serial number.
*
* The get_product_ID command retrieves the product identifier and serial
* number. The command can be used to check communication with the sensor.
*
* @param[out] productId 32-bit
* @param[out] serialNumber 64-bit unique serial number of the sensor.
*
* @return error_code 0 on success, an error code otherwise.
*/
int16_t getProductId(uint32_t& productId, uint64_t& serialNumber);
/**
* @brief Write the raw SHT4x temperature and humidity values to the STCC4.
*
* When the SHT4x is not directly connected to the STCC4 for humidity and
* temperature compensation, the set_rht_compensation command allows
* external input of relative humidity (RH) and temperature (T) values. The
* temperature value must be provided from the RHT sensor providing the RH
* value for correct absolute humidity calculation. The written RHT values
* are applied for compensation after a maximum of one measurement interval.
* The default or last written values are used for RHT compensation until
* overwritten. Power cycling resets the sensor to the default values of
* 25°C and 50 %RH.
*
* @param[in] rawTemperature raw temperature ticks as provided by SHT4x
* sensor
* @param[in] rawHumidity raw humidity ticks as provided by SHT4x sensor
*
* @return error_code 0 on success, an error code otherwise.
*
* Example:
* --------
*
* @code{.cpp}
*
* int16_t localError = 0;
* localError = sensor.setRhtCompensation(26214, 29359);
* if (localError != NO_ERROR) {
* return;
* }
*
* @endcode
*
*/
int16_t setRhtCompensation(uint16_t rawTemperature, uint16_t rawHumidity);
/**
* @brief Write the pressure value to the STCC4
*
* External pressure values can be set through the set_pressure_compensation
* command. The written pressure value is applied for pressure compensation
* after a maximum of one measurement interval. Power cycling resets the
* sensor to the default value of 101'300 Pa. The default or the last
* written value is used in pressure compensation until overwritten.
*
* @param[in] pressure The pressure value as Pa divided by 2. E.g. for
* 101300 Pa you have to send 50650
*
* @return error_code 0 on success, an error code otherwise.
*
* Example:
* --------
*
* @code{.cpp}
*
* int16_t localError = 0;
* localError = sensor.setPressureCompensationRaw(50650);
* if (localError != NO_ERROR) {
* return;
* }
*
* @endcode
*
*/
int16_t setPressureCompensationRaw(uint16_t pressure);
/**
* @brief Performs an internal check to determine if the sensor has a
* malfunction.
*
* The perform_self_test command can be used as an end-of-line test to check
* the sensor functionality. Word[0] = 0 pass Word[0] 0 malfunction
* detected
*
* @param[out] testResult Test result of the self-test. If the result is
* 0, the self-test failed.
*
* @return error_code 0 on success, an error code otherwise.
*/
int16_t performSelfTest(uint16_t& testResult);
/**
* @brief Conditions the sensor with a set operation profile.
*
* The perform_conditioning command is recommended to improve sensor
* performance when the sensor has not completed measurements for more than
* 3 hours. It is recommended to allow a settling time of 2 seconds after
* the execution time is complete. The recommended communication sequence is
* as follows: 1. The sensor is woken up with the exit_sleep_mode command if
* previously put to sleep using the enter_sleep_mode command. 2. The I2C
* controller sends a perform_conditioning command. 3. Wait for the
* specified execution time of perform_conditioning command plus a 2 second
* settling time. 4. The I2C controller sends a measurement command (e.g.
* start_continuous_measurement command or measure_single_shot command.
*
* @return error_code 0 on success, an error code otherwise.
*/
int16_t performConditioning();
/**
* @brief Sets the sensor from idle mode into sleep mode.
*
* The enter_sleep_mode command sets the sensor to sleep mode through the
* I2C interface. The written relative humidity, temperature, and pressure
* compensation values as well as the ASC state will be retained while in
* sleep mode.
*
* @return error_code 0 on success, an error code otherwise.
*/
int16_t enterSleepMode();
/**
* @brief The sensor is set from sleep mode into idle mode when it receives
* the valid I²C address and a write bit (0).
*
* The exit_sleep_mode command sets the sensor to idle mode through the I2C
* interface. The sensor's idle state can be verified by reading out the
* product ID.
*
* @return error_code 0 on success, an error code otherwise.
*/
int16_t exitSleepMode();
/**
* @brief Enable the sensor testing mode.
*
* The enable_testing_mode command is used to test the sensor with the ASC
* algorithm disabled. The sensor state can be verified by reading out the
* sensor status word in the read_measurement command.
*
* @return error_code 0 on success, an error code otherwise.
*/
int16_t enableTestingMode();
/**
* @brief Disable the sensor testing mode.
*
* The disable_testing_mode command is used to exit the testing mode. The
* sensor state can be verified by reading out the sensor status word in the
* read_measurement command.
*
* @return error_code 0 on success, an error code otherwise.
*/
int16_t disableTestingMode();
/**
* @brief Reset the FRC and ASC algorithm history.
*
* The perform_factory_reset command can be used to reset the FRC and ASC
* algorithm history.
*
* @param[out] factoryResetResult The result of the factory reset. If the
* result is 0, the factory reset failed.
*
* @return error_code 0 on success, an error code otherwise.
*/
int16_t performFactoryReset(uint16_t& factoryResetResult);
/**
* @brief signalTemperature
*
* @param[in] rawTemperature
*
* @return
*/
static float signalTemperature(uint16_t rawTemperature);
/**
* @brief signalRelativeHumidity
*
* @param[in] rawRelativeHumidity
*
* @return
*/
static float signalRelativeHumidity(uint16_t rawRelativeHumidity);
private:
TwoWire* _i2cBus = nullptr;
uint8_t _i2cAddress = 0;
};
#endif // SENSIRIONI2CSTCC4_H
+2 -1
View File
@@ -663,8 +663,9 @@
#define MGS_SENSOR_ADDR 0x04 // Default Mutichannel Gas sensor i2c address
// #define USE_SCD30 // [I2cDriver29] Enable Sensiron SCd30 CO2 sensor (I2C address 0x61) (+3k3 code)
// #define USE_SCD40 // [I2cDriver62] Enable Sensiron SCd40/Scd41 CO2 sensor (I2C address 0x62) (+3k5 code)
// #define USE_STCC4 // [I2cDriver99] Enable Sensiron STCC4 CO2 sensor (I2C address 0x64 or 0x65) (+3k8 code)
// #define USE_SEN5X // [I2cDriver76] Enable Sensiron SEN5X sensor (I2C address 0x69) (+3k code)
// #define USE_SEN6X // [I2cDriver97] Enable Sensiron SEN6X sensor (I2C address 0x6B) (+7k8 code)
// #define USE_SEN6X // [I2cDriver97] Enable Sensiron SEN6X sensor (I2C address 0x6B) (+8k8 code)
// #define USE_SHT3X // [I2cDriver15] Enable Sensiron SHT3x (I2C address 0x44 or 0x45) or SHTC3 (I2C address 0x70) sensor (+0k7 code)
// #define USE_SGP30 // [I2cDriver18] Enable Sensiron SGP30 sensor (I2C address 0x58) (+1k1 code)
// #define USE_SGP40 // [I2cDriver69] Enable Sensiron SGP40 sensor (I2C address 0x59) (+1k4 code)
+17
View File
@@ -337,6 +337,21 @@ bool I2cWriteBuffer(uint8_t addr, uint8_t reg, uint8_t *reg_data, uint16_t len,
/*-------------------------------------------------------------------------------------------*/
bool I2cReset(uint32_t bus = 0) {
/*
NXP UM10204 I2C-bus specification and user manual - Software Reset
Following a General Call, (0000 0000), sending 0000 0110 (06h) as the second byte
causes a software reset. This feature is optional and not all devices respond to this
command. On receiving this 2-byte sequence, all devices designed to respond to
the general call address reset and take in the programmable part of their address.
Precautions must be taken to ensure that a device is not pulling down the SDA or SCL
line after applying the supply voltage, since these low levels would block the bus.
*/
return I2cWrite0(0, 6, bus);
}
/*-------------------------------------------------------------------------------------------*/
void I2cScan(uint8_t bus = 0) {
// Return error codes defined in twi.h and core_esp8266_si2c.c
// I2C_OK 0
@@ -389,6 +404,8 @@ void I2cScan(uint8_t bus = 0) {
}
}
/*-------------------------------------------------------------------------------------------*/
void I2cResetActive(uint32_t addr, uint8_t bus = 0) {
addr &= 0x7F; // Max I2C address is 127
I2C.active[bus][addr / 32] &= ~(1 << (addr % 32));
+3 -1
View File
@@ -970,7 +970,9 @@ constexpr uint32_t feature[] = {
#if defined(USE_I2C) && defined(USE_FM24CXX)
0x00400000 | // xdrv_93_fm24cxx.ino
#endif
// 0x00800000 | //
#if defined(USE_I2C) && defined(USE_STCC4)
0x00800000 | // xsns_120_stcc4.ino
#endif
// 0x01000000 | //
// 0x02000000 | //
// 0x04000000 | //
@@ -162,6 +162,7 @@ void ShtDetect(void) {
AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_I2C D_SHT1X_FOUND));
}
I2cBegin(Sht1x.sda_pin, Sht1x.scl_pin); // Reinit I2C bus
I2cReset();
}
void ShtEverySecond(void) {
+46 -33
View File
@@ -51,6 +51,8 @@
#define XSNS_119 119
#define XI2C_97 97 // See I2CDEVICES.md
//#define SENSIRION_DEBUG // Adds 1k2 to code size
#include <SensirionI2cSen6x.h>
#define SEN6X_STATE_READ_MEASUREMENT 0
@@ -60,7 +62,7 @@
enum eSen6xFeatures { SEN6X_VOCNOX = 1, SEN6X_CO2 = 2, SEN6X_HCHO = 4 };
SensirionI2cSen6x *sen6x = nullptr;
SensirionI2cSen6x sen6x;
const char mSenNames[] PROGMEM = "SEN62|SEN63C||SEN65|SEN66||SEN68|SEN69C";
@@ -123,14 +125,20 @@ uint32_t ParseIntParameters(uint32_t count, int *params) {
void Sen6xStopStartMeasurement(void) {
// Stop measurement and restart after 1 second
sen6x->stopMeasurement(); // Performs internal delay(1000)
sen6x.stopMeasurement(); // Performs internal delay(1000)
SEN6XDATA->state = SEN6X_STATE_START_MEASUREMENT;
}
bool Sen6xError(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("S6X: %s error %d %s"), func, error, error_msg);
#else
AddLog(LOG_LEVEL_DEBUG, PSTR("S6X: %s error %d"), func, error);
#endif
}
return result;
}
@@ -139,7 +147,13 @@ bool CmndSen6xError(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;
}
@@ -153,26 +167,25 @@ void Sen6xInit(void) {
// Sen6xError("Scan", bus +1);
continue;
}
sen6x = new SensirionI2cSen6x();
sen6x->begin(I2cGetWire(bus), SEN6X_I2C_ADDR_6B);
sen6x.begin(I2cGetWire(bus), SEN6X_I2C_ADDR_6B);
if (Sen6xError("Reset", sen6x->deviceReset())) { // Performs delay(1200) if no error
if (Sen6xError("Reset", sen6x.deviceReset())) { // Performs delay(1200) if no error
continue;
}
uint8_t major;
uint8_t minor;
if (Sen6xError("Version", sen6x->getVersion(major, minor))) {
if (Sen6xError("Version", sen6x.getVersion(major, minor))) {
continue;
}
int8_t serial_number[32] = { 0 };
if (Sen6xError("Serialnumber", sen6x->getSerialNumber(serial_number, sizeof(serial_number)))) {
if (Sen6xError("Serialnumber", sen6x.getSerialNumber(serial_number, sizeof(serial_number)))) {
continue;
}
int8_t product_name[32] = { 0 };
if (Sen6xError("Productname", sen6x->getProductName(product_name, sizeof(product_name)))) {
if (Sen6xError("Productname", sen6x.getProductName(product_name, sizeof(product_name)))) {
continue;
}
@@ -206,7 +219,7 @@ void Sen6xUpdate(void) {
int16_t temperature;
int16_t vocIndex;
int16_t noxIndex;
if (!Sen6xError("Measurement", sen6x->readMeasuredValuesAsIntegers(
if (!Sen6xError("Measurement", sen6x.readMeasuredValuesAsIntegers(
SEN6XDATA->model,
massConcentrationPm1p0, massConcentrationPm2p5,
massConcentrationPm4p0, massConcentrationPm10p0,
@@ -227,7 +240,7 @@ void Sen6xUpdate(void) {
}
break;
case SEN6X_STATE_START_MEASUREMENT -1:
if (Sen6xError("StartContinuous", sen6x->startContinuousMeasurement())) {
if (Sen6xError("StartContinuous", sen6x.startContinuousMeasurement())) {
SEN6XDATA->state = SEN6X_STATE_START_MEASUREMENT +2;
}
break;
@@ -253,33 +266,33 @@ void (* const Sen6xCommand[])(void) PROGMEM = {
void CmndSen6xState(void) {
int8_t productname[32] = { 0 };
sen6x->getProductName(productname, sizeof(productname)); // No need to stop
sen6x.getProductName(productname, sizeof(productname)); // No need to stop
int8_t serial_number[32] = { 0 };
sen6x->getSerialNumber(serial_number, sizeof(serial_number)); // No need to stop
sen6x.getSerialNumber(serial_number, sizeof(serial_number)); // No need to stop
SEN6XDeviceStatus status;
sen6x->readDeviceStatus(status); // No need to stop
sen6x.readDeviceStatus(status); // No need to stop
Response_P(PSTR("{\"SEN6x\":{\"Name\":\"%s\",\"Serial\":\"%s\",\"Version\":\"%d.%d\",\"Status\":\"%04X\""),
productname, serial_number, SEN6XDATA->major, SEN6XDATA->minor, status);
bool stop_measurement = false;
if (SEN6XDATA->features & SEN6X_CO2) {
Sen6xStopStartMeasurement(); // Stop measurement and restart after 1 second
Sen6xStopStartMeasurement(); // Stop measurement and restart after 1 second
stop_measurement = true;
uint16_t pressure;
sen6x->getAmbientPressure(pressure); // No need to stop
sen6x.getAmbientPressure(pressure); // No need to stop
uint16_t altitude;
sen6x->getSensorAltitude(altitude); // Only in idle mode (stopped measurement)
sen6x.getSensorAltitude(altitude); // Only in idle mode (stopped measurement)
uint8_t padding;
bool calstatus;
sen6x->getCo2SensorAutomaticSelfCalibration(padding, calstatus); // Only in idle mode (stopped measurement)
sen6x.getCo2SensorAutomaticSelfCalibration(padding, calstatus); // Only in idle mode (stopped measurement)
ResponseAppend_P(PSTR(",\"CO2\":{\"Altitude\":%d,\"Pressure\":%d,\"AutoCal\":%d}"),
altitude, pressure, calstatus);
}
if (SEN6XDATA->features & SEN6X_VOCNOX) {
if (!stop_measurement) {
Sen6xStopStartMeasurement(); // Stop measurement and restart after 1 second
Sen6xStopStartMeasurement(); // Stop measurement and restart after 1 second
stop_measurement = true;
}
int16_t voc_io;
@@ -288,7 +301,7 @@ void CmndSen6xState(void) {
int16_t voc_gmdm;
int16_t voc_si;
int16_t voc_gf;
sen6x->getVocAlgorithmTuningParameters(voc_io, voc_ltoh, voc_ltgh, voc_gmdm, voc_si, voc_gf); // Only in idle mode (stopped measurement)
sen6x.getVocAlgorithmTuningParameters(voc_io, voc_ltoh, voc_ltgh, voc_gmdm, voc_si, voc_gf); // Only in idle mode (stopped measurement)
ResponseAppend_P(PSTR(",\"VOC\":{\"IdxOffset\":%d,\"LearningTime\":{\"Offset\":%d,\"Gain\":%d},\"GatingMaxDur\":%d,\"StdInit\":%d,\"GainFctr\":%d}"),
voc_io, voc_ltoh, voc_ltgh, voc_gmdm, voc_si, voc_gf);
int16_t nox_io;
@@ -297,7 +310,7 @@ void CmndSen6xState(void) {
int16_t nox_gmdm;
int16_t nox_si;
int16_t nox_gf;
sen6x->getNoxAlgorithmTuningParameters(nox_io, nox_ltoh, nox_ltgh, nox_gmdm, nox_si, nox_gf); // Only in idle mode (stopped measurement)
sen6x.getNoxAlgorithmTuningParameters(nox_io, nox_ltoh, nox_ltgh, nox_gmdm, nox_si, nox_gf); // Only in idle mode (stopped measurement)
ResponseAppend_P(PSTR(",\"NOx\":{\"IdxOffset\":%d,\"LearningTimeOffset\":%d,\"GatingMaxDur\":%d,\"GainFctr\":%d}"),
nox_io, nox_ltoh, nox_gmdm, nox_gf);
}
@@ -310,7 +323,7 @@ void CmndSen6xAltitude(void) {
if (SEN6XDATA->features & SEN6X_CO2) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 3000)) {
Sen6xStopStartMeasurement();
if (CmndSen6xError(sen6x->setSensorAltitude(XdrvMailbox.payload))) {
if (CmndSen6xError(sen6x.setSensorAltitude(XdrvMailbox.payload))) {
return;
}
}
@@ -324,7 +337,7 @@ void CmndSen6xPressure(void) {
// Sen6xPres <700..1200> - hPa
if (SEN6XDATA->features & SEN6X_CO2) {
if ((XdrvMailbox.payload >= 700) && (XdrvMailbox.payload <= 1200)) {
if (CmndSen6xError(sen6x->setAmbientPressure(XdrvMailbox.payload))) {
if (CmndSen6xError(sen6x.setAmbientPressure(XdrvMailbox.payload))) {
return;
}
}
@@ -340,7 +353,7 @@ void CmndSen6xTemperatureOffset(void) {
int value[4] = { 0 };
if (4 == ParseIntParameters(4, value)) {
Sen6xStopStartMeasurement();
if (!CmndSen6xError(sen6x->setTemperatureOffsetParameters(value[0], value[1], value[2], value[3]))) {
if (!CmndSen6xError(sen6x.setTemperatureOffsetParameters(value[0], value[1], value[2], value[3]))) {
ResponseCmndDone();
}
}
@@ -353,7 +366,7 @@ void CmndSen6xTemperatureAcceleration(void) {
uint32_t value[4] = { 0 };
if (4 == ParseParameters(4, value)) {
Sen6xStopStartMeasurement();
if (!CmndSen6xError(sen6x->setTemperatureAccelerationParameters(value[0], value[1], value[2], value[3]))) {
if (!CmndSen6xError(sen6x.setTemperatureAccelerationParameters(value[0], value[1], value[2], value[3]))) {
ResponseCmndDone();
}
}
@@ -370,21 +383,21 @@ void CmndSen6xCo2SelfCalibration(void) {
delay(400); // Wait at least 1400ms
uint16_t reference = XdrvMailbox.payload;
uint16_t correction;
if (!CmndSen6xError(sen6x->performForcedCo2Recalibration(reference, correction))) {
if (!CmndSen6xError(sen6x.performForcedCo2Recalibration(reference, correction))) {
ResponseCmndNumber(correction); // FRC = return_value - 0x8000. If the recalibration has failed this returned value is 0xFFFF.
}
return;
}
else if ((0 == XdrvMailbox.payload) || (1 == XdrvMailbox.payload)) {
Sen6xStopStartMeasurement();
if (CmndSen6xError(sen6x->setCo2SensorAutomaticSelfCalibration(XdrvMailbox.payload))) {
if (CmndSen6xError(sen6x.setCo2SensorAutomaticSelfCalibration(XdrvMailbox.payload))) {
return;
}
}
/*
else if (2 == XdrvMailbox.payload) {
Sen6xStopStartMeasurement();
if (CmndSen6xError(sen63->sensorFactoryReset())) { // Implemented for STCC2 in SEN63C
if (CmndSen6xError(sen63->sensorFactoryReset())) { // Implemented for STCC4 in SEN63C
return;
}
}
@@ -402,7 +415,7 @@ void CmndSen6xVocAlgorithmTuningParams(void) {
int value[6] = { 0 };
if (6 == ParseIntParameters(6, value)) {
Sen6xStopStartMeasurement();
if (CmndSen6xError(sen6x->setVocAlgorithmTuningParameters(value[0], value[1], value[2], value[3], value[4], value[5]))) {
if (CmndSen6xError(sen6x.setVocAlgorithmTuningParameters(value[0], value[1], value[2], value[3], value[4], value[5]))) {
return;
}
}
@@ -421,7 +434,7 @@ void CmndSen6xNoxAlgorithmTuningParams(void) {
Sen6xStopStartMeasurement();
uint16_t ltgh = 12; // This parameter has no impact for NOx and must always be set to 12 hours
uint16_t si = 50; // This parameter has no impact for NOx and must always be set to 50
if (CmndSen6xError(sen6x->setNoxAlgorithmTuningParameters(value[0], value[1], ltgh, value[2], si, value[3]))) {
if (CmndSen6xError(sen6x.setNoxAlgorithmTuningParameters(value[0], value[1], ltgh, value[2], si, value[3]))) {
return;
}
}
@@ -433,7 +446,7 @@ void CmndSen6xFanClean(void) {
// Sen6xClean - This command triggers fan cleaning. The fan is set to the maximum speed
// for 10 seconds and then automatically stopped.
Sen6xStopStartMeasurement();
if (CmndSen6xError(sen6x->startFanCleaning())) {
if (CmndSen6xError(sen6x.startFanCleaning())) {
return;
}
SEN6XDATA->state = SEN6X_STATE_CLEAN_FAN_WAIT;
@@ -444,7 +457,7 @@ void CmndSen6xSHTHeater(void) {
// Sen6xHeat - This command activates the SHT sensor heater with 200mW for 1s.
// The heater is then automatically deactivated again.
Sen6xStopStartMeasurement();
if (CmndSen6xError(sen6x->activateShtHeater())) {
if (CmndSen6xError(sen6x.activateShtHeater())) {
return;
}
SEN6XDATA->state = SEN6X_STATE_SHT_HEATER_WAIT;
@@ -458,12 +471,12 @@ void CmndSen6xVocState(void) {
// TBD - Restore voc_state after a restart/power cycle - needs filesystem
/*
Sen6xStopStartMeasurement();
if (CmndSen6xError(sen6x->setVocAlgorithmState(voc_state, sizeof(voc_state)))) {
if (CmndSen6xError(sen6x.setVocAlgorithmState(voc_state, sizeof(voc_state)))) {
return;
}
*/
} else {
if (CmndSen6xError(sen6x->getVocAlgorithmState(voc_state, sizeof(voc_state)))) {
if (CmndSen6xError(sen6x.getVocAlgorithmState(voc_state, sizeof(voc_state)))) {
return;
}
AddLog(LOG_LEVEL_DEBUG, PSTR("S6X: State %12_H"), voc_state);
@@ -0,0 +1,284 @@
/*
xsns_120_stcc4.ino - Sensirion STCC4 CO sensor support for Tasmota
SPDX-FileCopyrightText: 2026 Theo Arends
SPDX-License-Identifier: GPL-3.0-only
*/
#ifdef USE_I2C
#ifdef USE_STCC4
/*********************************************************************************************\
* Sensirion STCC4 CO (and optional embedded SHT4x RH&T sensor)
*
* Commands:
* Stcc - State information
* StccPres - Set the pressure compensation in hPa (default 1013)
* StccPres <400..1100>
* StccRHT - Write temperature and humidity values (default 25°C and 50 %RH) when SHT4x is not present
* StccRHT <temperature>,<humidity>
* StccRHT <0..200>,<0..100>
* StccCal - Re-calibrate CO sensor (FRC)
* StccCal <351..32000>
* StccReset 1 - Reset the Forced recalibration (FRC) and ASC algorithm history
*
* I2C Address: 0x64 or 0x65
\*********************************************************************************************/
#define XSNS_120 120
#define XI2C_99 99 // See I2CDEVICES.md
//#define SENSIRION_DEBUG // Adds 1k2 to code size
#include <SensirionI2cStcc4.h>
#define STCC4_STATE_READ_MEASUREMENT 0
#define STCC4_STATE_START_MEASUREMENT 2 // Wait at least 2 seconds as otherwise see sporadic error 527 - Not enough data received
SensirionI2cStcc4 stcc4;
struct StccData_s {
int16_t co2;
uint16_t humidity;
uint16_t temperature;
uint16_t sensor_status;
uint8_t state;
} *StccData = nullptr;
/********************************************************************************************/
void StccStopStartMeasurement(void) {
// Stop measurement and restart after 2 seconds
stcc4.stopContinuousMeasurement(); // Performs internal delay(1200)
StccData->state = STCC4_STATE_START_MEASUREMENT;
}
bool StccError(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("STC: %s error %d %s"), func, error, error_msg);
#else
AddLog(LOG_LEVEL_DEBUG, PSTR("STC: %s error %d"), func, error);
#endif
}
return result;
}
bool CmndStccError(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;
}
/********************************************************************************************/
void StccInit(void) {
PowerOnDelay(20); // Sensor startup time (Time after power-on until I2C communication can be started)
for (uint32_t address = STCC4_I2C_ADDR_64; address < STCC4_I2C_ADDR_64 +2; address++) {
for (uint32_t bus = 0; bus < 2; bus++) {
if (!I2cSetDevice(address, bus)) {
// StccError("Scan", address + bus +1);
continue;
}
stcc4.begin(I2cGetWire(bus), address);
/*
if (StccError("ExitSleep", stcc4.exitSleepMode())) { // Performs delay(5)
continue;
}
*/
if (StccError("ForceStop", stcc4.stopContinuousMeasurement())) { // Performs delay(1200) if no error
continue;
}
uint32_t product_id;
uint64_t serial_number;
if (StccError("Serialnumber", stcc4.getProductId(product_id, serial_number))) {
continue;
}
StccData = (StccData_s *)calloc(1, sizeof(struct StccData_s));
StccData->state = STCC4_STATE_START_MEASUREMENT;
I2cSetActiveFound(address, "STCC4", bus);
AddLog(LOG_LEVEL_DEBUG, PSTR("STC: STCC4 serialnumber %_U"), &serial_number);
return;
}
}
}
void StccUpdate(void) {
switch (StccData->state) {
case STCC4_STATE_READ_MEASUREMENT:
{
// Use temp vars in case the read fails; Do not store invalid values.
int16_t co2;
uint16_t humidity;
uint16_t temperature;
uint16_t sensor_status;
bool error = false;
if (stcc4.readMeasurementRaw(co2, temperature, humidity, sensor_status)) {
delay(150); // A failed read can be caused by clock shifting. We advise to retry after a delay of 150ms.
error = StccError("Measurement", stcc4.readMeasurementRaw(co2, temperature, humidity, sensor_status));
}
if (!error) {
StccData->co2 = co2;
StccData->temperature = temperature;
StccData->humidity = humidity;
StccData->sensor_status = sensor_status;
#ifdef USE_LIGHT
LightSetSignal(CO2_LOW, CO2_HIGH, StccData->co2); // SetOption18 - Pair light signal with CO₂ sensor
#endif // USE_LIGHT
StccData->state = STCC4_STATE_START_MEASUREMENT; // Take at least 2 seconds between read as otherwise see sporadic error 527 - Not enough data received
}
}
break;
case STCC4_STATE_START_MEASUREMENT:
if (StccError("StartContinuous", stcc4.startContinuousMeasurement())) {
StccData->state = STCC4_STATE_START_MEASUREMENT +2;
}
break;
}
if (StccData->state) {
StccData->state--;
}
}
/*********************************************************************************************\
* Commands
\*********************************************************************************************/
const char kStccCommands[] PROGMEM = "Stcc|" // Prefix
"|Pres|RHT|Cal|Reset";
void (* const StccCommand[])(void) PROGMEM = {
&CmndStccState, &CmndStccPressure, &CmndStccRHT, &CmndStccReset };
void CmndStccState(void) {
StccStopStartMeasurement();
uint32_t product_id;
uint64_t serial_number;
stcc4.getProductId(product_id, serial_number);
Response_P(PSTR("{\"STCC4\":{\"Product\":\"%08X\",\"Serial\":%_U,\"Status\":\"%04X\"}"),
product_id, &serial_number, StccData->sensor_status);
}
void CmndStccPressure(void) {
// StccPres - Set the pressure compensation in hPa (default 1013)
// StccPres <400..1100>
if ((XdrvMailbox.payload >= 400) && (XdrvMailbox.payload <= 1100)) {
if (!CmndStccError(stcc4.setPressureCompensationRaw(XdrvMailbox.payload * 50))) {
ResponseCmndDone();
}
}
}
void CmndStccRHT(void) {
// StccRHT - Write temperature and humidity values (default 25°C and 50 %RH) when SHT4x is not present
// StccRHT <temperature>,<humidity>
// StccRHT <0..200>,<0..100>
uint32_t value[2] = { 0 };
if (2 == ParseParameters(2, value)) {
float raw_temperature = (((float)value[0] + 45.0) * 65535.0) / 175.0;
float raw_humidity = (((float)value[1] + 6.0) * 65535.0) / 125.0;
if (!CmndStccError(stcc4.setRhtCompensation((uint16_t)raw_temperature, (uint16_t)raw_humidity))) {
ResponseCmndDone();
}
}
}
void CmndStccCalibration(void) {
// StccCal - Re-calibrate CO₂ sensor with target_value
// StccCal <351..32000>
if (XdrvMailbox.payload > 350) {
StccStopStartMeasurement();
int16_t reference = XdrvMailbox.payload;
int16_t correction;
if (!CmndStccError(stcc4.performForcedRecalibration(reference, correction))) {
ResponseCmndNumber(correction); // FRC = return_value - 0x8000. If the recalibration has failed this returned value is 0xFFFF.
}
}
}
void CmndStccReset(void) {
// StccReset 1 - Reset the Forced recalibration (FRC) and ASC algorithm history
if (1 == XdrvMailbox.payload) {
StccStopStartMeasurement();
uint16_t result;
if (!CmndStccError(stcc4.performFactoryReset(result))) {
ResponseCmndNumber(result); // If the factory reset has failed the returned value is not 0
}
}
}
/*********************************************************************************************\
* Presentation
\*********************************************************************************************/
#define STCC4_INT_INVALID 0x7FFF
float StccInt16(int16_t value) {
return (value == STCC4_INT_INVALID) ? NAN : value;
}
void StccShow(bool json) {
float co2 = StccInt16(StccData->co2); // Change invalid to null
float humidity = ConvertHumidity(stcc4.signalRelativeHumidity(StccData->humidity));
float temperature = ConvertTemp(stcc4.signalTemperature(StccData->temperature));
if (json) {
ResponseAppend_P(PSTR(",\"STCC4\":{\"" D_JSON_CO2 "\":%0_f,"), &co2);
ResponseAppendTHD(temperature, humidity);
ResponseAppend_P(PSTR("}"));
#ifdef USE_WEBSERVER
} else {
WSContentSend_PD(HTTP_SNS_F_CO2, "STCC4", &co2);
WSContentSend_THD("STCC4", temperature, humidity);
#endif
}
}
/*********************************************************************************************\
* Interface
\*********************************************************************************************/
bool Xsns120(uint32_t function) {
if (!I2cEnabled(XI2C_99)) { return false; }
bool result = false;
if (FUNC_INIT == function) {
StccInit();
}
else if (StccData) {
switch (function) {
case FUNC_EVERY_SECOND:
StccUpdate();
break;
case FUNC_JSON_APPEND:
StccShow(1);
break;
#ifdef USE_WEBSERVER
case FUNC_WEB_SENSOR:
StccShow(0);
break;
#endif // USE_WEBSERVER
case FUNC_COMMAND:
result = DecodeCommand(kStccCommands, StccCommand);
break;
}
}
return result;
}
#endif // USE_STCC4
#endif // USE_I2C
+2 -2
View File
@@ -313,7 +313,7 @@ a_features = [[
"USE_HX711_M5SCALES","USE_RX8010","USE_PCF85063","USE_ESP32_TWAI",
"USE_C8_CO2_5K","USE_WIZMOTE","USE_V9240","USE_TELNET",
"USE_XYZMODEM","USE_WIREGUARD","USE_AP33772S","USE_VID6608",
"USE_AGS02MA","USE_SEN6X","USE_FM24CXX","",
"USE_AGS02MA","USE_SEN6X","USE_FM24CXX","USE_STCC4",
"","","","",
"","","",""
]]
@@ -343,7 +343,7 @@ else:
obj = json.load(fp)
def StartDecode():
print ("\n*** decode-status.py v15.2.0.6 by Theo Arends and Jacek Ziolkowski ***")
print ("\n*** decode-status.py v15.3.0.1 by Theo Arends and Jacek Ziolkowski ***")
# print("Decoding\n{}".format(obj))