Add support for SGP41 TVOC/NOx Sensor (#18880)

* Initial support for SGP41

* Removing delay() use from SGP4x driver

* Using i18n for TVOC/NOx raw values as well
This commit is contained in:
Andrew Klaus 2023-06-29 01:04:08 -06:00 committed by GitHub
parent 952811b4eb
commit eb655a4a8f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 1795 additions and 3 deletions

View File

@ -119,6 +119,7 @@ Note: `minimal` variant is not listed as it shouldn't be used outside of the [up
| USE_MGS | - | - / x | - | x | - | - | | USE_MGS | - | - / x | - | x | - | - |
| USE_SGP30 | - | - / x | - | x | - | - | | USE_SGP30 | - | - / x | - | x | - | - |
| USE_SGP40 | - | - / x | - | x | - | - | | USE_SGP40 | - | - / x | - | x | - | - |
| USE_SGP4X | - | - / x | - | x | - | - |
| USE_SEN5X | - | - / x | - | x | - | - | | USE_SEN5X | - | - / x | - | x | - | - |
| USE_SI1145 | - | - / - | - | - | - | - | | USE_SI1145 | - | - / - | - | - | - | - |
| USE_LM75AD | - | - / x | - | x | - | - | | USE_LM75AD | - | - / x | - | x | - | - |

View File

@ -117,3 +117,4 @@ Index | Define | Driver | Device | Address(es) | Description
79 | USE_GDK101 | xsns_106 | GDK101 | 0x18 - 0x1B | Gamma Radiation Sensor 79 | USE_GDK101 | xsns_106 | GDK101 | 0x18 - 0x1B | Gamma Radiation Sensor
80 | USE_TC74 | xsns_108 | TC74 | 0x48 - 0x4F | Temperature sensor 80 | USE_TC74 | xsns_108 | TC74 | 0x48 - 0x4F | Temperature sensor
81 | USE_PCA9557 | xdrv_69 | PCA95xx | 0x18 - 0x1F | 8-bit I/O expander as virtual button/switch/relay 81 | USE_PCA9557 | xdrv_69 | PCA95xx | 0x18 - 0x1F | 8-bit I/O expander as virtual button/switch/relay
82 | USE_SGP4X | xsns_109 | SGP4X | 0x59 | Gas (TVOC/NOx index)

View File

@ -0,0 +1,13 @@
# Changelog
All notable changes to this project will be documented in this file.
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).
## [0.1.0] - 2021-11-17
Initial release
[0.1.0]: https://github.com/Sensirion/arduino-i2c-sgp41/releases/tag/0.1.0

View File

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

View File

@ -0,0 +1,78 @@
# Sensirion I2C SGP41 Arduino Library
This is the Sensirion SGP41 library for Arduino using the
modules I2C interface.
[<center><img src="images/SGP41.png" width="300px"></center>](https://www.sensirion.com/en/environmental-sensors/gas-sensors/sgp41)
Click [here](https://www.sensirion.com/en/environmental-sensors/gas-sensors/sgp41) to learn more about the SGP41 sensor.
# Installation
To install, 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 `Add
.ZIP Library`
Note: Installation via the Arduino Library Manager is coming soon.
# Dependencies
* [Sensirion Core](https://github.com/Sensirion/arduino-core)
# Quick Start
1. Connect the SGP41 Sensor to your Arduino board's standard
I2C bus. Check the pinout of your Arduino board to find the correct pins.
The pinout of the SGP41 Sensor board can be found in the
data sheet.
* **VDD** of the SEK-SGP41 to the **3.3V** of your Arduino board
* **GND** of the SEK-SGP41 to the **GND** of your Arduino board
* **SCL** of the SEK-SGP41 to the **SCL** of your Arduino board
* **SDA** of the SEK-SGP41 to the **SDA** of your Arduino board
2. Open the `exampleUsage` sample project within the Arduino IDE
File => Examples => Sensirion I2C SGP41 => exampleUsage
3. 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 corresponding window has to be set to `115200 baud`.
# Contributing
**Contributions are welcome!**
We develop and test this driver using our company internal tools (version
control, continuous integration, code review etc.) and automatically
synchronize the master branch with GitHub. But this doesn't mean that we don't
respond to issues or don't accept pull requests on GitHub. In fact, you're very
welcome to open issues or create pull requests :)
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).

View File

@ -0,0 +1,124 @@
/*
* I2C-Generator: 0.3.0
* Yaml Version: 0.1.0
* Template Version: 0.7.0-62-g3d691f9
*/
/*
* Copyright (c) 2021, 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 <SensirionI2CSgp41.h>
#include <Wire.h>
SensirionI2CSgp41 sgp41;
// time in seconds needed for NOx conditioning
uint16_t conditioning_s = 10;
void setup() {
Serial.begin(115200);
while (!Serial) {
delay(100);
}
Wire.begin();
uint16_t error;
char errorMessage[256];
sgp41.begin(Wire);
uint16_t serialNumber[3];
uint8_t serialNumberSize = 3;
error = sgp41.getSerialNumber(serialNumber, serialNumberSize);
if (error) {
Serial.print("Error trying to execute getSerialNumber(): ");
errorToString(error, errorMessage, 256);
Serial.println(errorMessage);
} else {
Serial.print("SerialNumber:");
Serial.print("0x");
for (size_t i = 0; i < serialNumberSize; i++) {
uint16_t value = serialNumber[i];
Serial.print(value < 4096 ? "0" : "");
Serial.print(value < 256 ? "0" : "");
Serial.print(value < 16 ? "0" : "");
Serial.print(value, HEX);
}
Serial.println();
}
uint16_t testResult;
error = sgp41.executeSelfTest(testResult);
if (error) {
Serial.print("Error trying to execute executeSelfTest(): ");
errorToString(error, errorMessage, 256);
Serial.println(errorMessage);
} else if (testResult != 0xD400) {
Serial.print("executeSelfTest failed with error: ");
Serial.println(testResult);
}
}
void loop() {
uint16_t error;
char errorMessage[256];
uint16_t defaultRh = 0x8000;
uint16_t defaultT = 0x6666;
uint16_t srawVoc = 0;
uint16_t srawNox = 0;
delay(1000);
if (conditioning_s > 0) {
// During NOx conditioning (10s) SRAW NOx will remain 0
error = sgp41.executeConditioning(defaultRh, defaultT, srawVoc);
conditioning_s--;
} else {
// Read Measurement
error = sgp41.measureRawSignals(defaultRh, defaultT, srawVoc, srawNox);
}
if (error) {
Serial.print("Error trying to execute measureRawSignals(): ");
errorToString(error, errorMessage, 256);
Serial.println(errorMessage);
} else {
Serial.print("SRAW_VOC:");
Serial.print(srawVoc);
Serial.print("\t");
Serial.print("SRAW_NOx:");
Serial.println(srawNox);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 KiB

View File

@ -0,0 +1,27 @@
#######################################
# Syntax Coloring Map
#######################################
#######################################
# Datatypes (KEYWORD1)
#######################################
SensirionI2CSgp41 KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
executeConditioning KEYWORD2
measureRawSignals KEYWORD2
executeSelfTest KEYWORD2
turnHeaterOff KEYWORD2
getSerialNumber KEYWORD2
#######################################
# Instances (KEYWORD2)
#######################################
sgp41 KEYWORD2
#######################################
# Constants (LITERAL1)
#######################################

View File

@ -0,0 +1,10 @@
name=Sensirion I2C SGP41
version=0.1.0
author=Sensirion
maintainer=Sensirion
sentence=Library for the SGP41 sensor family by Sensirion
paragraph=Enables you to use the SGP41 via I2C.
url=https://github.com/Sensirion/arduino-i2c-sgp41
category=Sensors
depends=Sensirion Core
includes=SensirionI2CSgp41.h

View File

@ -0,0 +1,219 @@
/*
* Copyright (c) 2021, Sensirion AG
* Copyright (c) 2023, Andrew Klaus (Removing delay functions)
* 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 "SensirionI2CSgp4x.h"
#include "Arduino.h"
#include "SensirionCore.h"
#include <Wire.h>
#define SGP4X_I2C_ADDRESS 0x59
SensirionI2CSgp4x::SensirionI2CSgp4x() {
}
void SensirionI2CSgp4x::begin(TwoWire& i2cBus) {
_i2cBus = &i2cBus;
}
uint16_t SensirionI2CSgp4x::sendConditioningCmd(uint16_t defaultRh,
uint16_t defaultT) {
uint16_t error;
uint8_t buffer[8];
SensirionI2CTxFrame txFrame =
SensirionI2CTxFrame::createWithUInt16Command(0x2612, buffer, 8);
error = txFrame.addUInt16(defaultRh);
error |= txFrame.addUInt16(defaultT);
if (error) {
return error;
}
error = SensirionI2CCommunication::sendFrame(SGP4X_I2C_ADDRESS, txFrame,
*_i2cBus);
return error;
}
uint16_t SensirionI2CSgp4x::readConditioningValue(uint16_t& srawVoc){
// This must run at least 50ms after initiateConditioning
uint16_t error;
uint8_t buffer[8];
SensirionI2CRxFrame rxFrame(buffer, 8);
error = SensirionI2CCommunication::receiveFrame(SGP4X_I2C_ADDRESS, 3,
rxFrame, *_i2cBus);
if (error) {
return error;
}
error |= rxFrame.getUInt16(srawVoc);
return error;
}
uint16_t SensirionI2CSgp4x::sendRawSignalsCmd(uint16_t relativeHumidity,
uint16_t temperature) {
uint16_t error;
uint8_t buffer[8];
SensirionI2CTxFrame txFrame =
SensirionI2CTxFrame::createWithUInt16Command(0x2619, buffer, 8);
error = txFrame.addUInt16(relativeHumidity);
error |= txFrame.addUInt16(temperature);
if (error) {
return error;
}
error = SensirionI2CCommunication::sendFrame(SGP4X_I2C_ADDRESS, txFrame,
*_i2cBus);
return error;
}
uint16_t SensirionI2CSgp4x::readRawSignalsValue(uint16_t& srawVoc,
uint16_t& srawNox) {
// This must run 50ms after initiateRawSignals
uint16_t error;
uint8_t buffer[6];
SensirionI2CRxFrame rxFrame(buffer, 6);
error = SensirionI2CCommunication::receiveFrame(SGP4X_I2C_ADDRESS, 6,
rxFrame, *_i2cBus);
if (error) {
return error;
}
error |= rxFrame.getUInt16(srawVoc);
error |= rxFrame.getUInt16(srawNox);
return error;
}
uint16_t SensirionI2CSgp4x::sendRawSignalCmd(uint16_t relativeHumidity,
uint16_t temperature) {
uint16_t error;
uint8_t buffer[8];
SensirionI2CTxFrame txFrame =
SensirionI2CTxFrame::createWithUInt16Command(0x260F, buffer, 8);
error = txFrame.addUInt16(relativeHumidity);
error |= txFrame.addUInt16(temperature);
if (error) {
return error;
}
error = SensirionI2CCommunication::sendFrame(SGP4X_I2C_ADDRESS, txFrame,
*_i2cBus);
return error;
}
uint16_t SensirionI2CSgp4x::readRawSignalValue(uint16_t& srawVoc) {
uint16_t error;
uint8_t buffer[8];
SensirionI2CRxFrame rxFrame(buffer, 8);
error = SensirionI2CCommunication::receiveFrame(SGP4X_I2C_ADDRESS, 3,
rxFrame, *_i2cBus);
if (error) {
return error;
}
error |= rxFrame.getUInt16(srawVoc);
return error;
}
uint16_t SensirionI2CSgp4x::sendSelfTestCmd() {
uint16_t error;
uint8_t buffer[3];
SensirionI2CTxFrame txFrame =
SensirionI2CTxFrame::createWithUInt16Command(0x280E, buffer, 3);
error = SensirionI2CCommunication::sendFrame(SGP4X_I2C_ADDRESS, txFrame,
*_i2cBus);
return error;
}
uint16_t SensirionI2CSgp4x::readSelfTestValue(uint16_t& testResult) {
// Must run 320ms after initiateSelfTest
uint16_t error;
uint8_t buffer[3];
SensirionI2CRxFrame rxFrame(buffer, 3);
error = SensirionI2CCommunication::receiveFrame(SGP4X_I2C_ADDRESS, 3,
rxFrame, *_i2cBus);
if (error) {
return error;
}
error |= rxFrame.getUInt16(testResult);
return error;
}
uint16_t SensirionI2CSgp4x::turnHeaterOff() {
uint16_t error;
uint8_t buffer[2];
SensirionI2CTxFrame txFrame =
SensirionI2CTxFrame::createWithUInt16Command(0x3615, buffer, 2);
error = SensirionI2CCommunication::sendFrame(SGP4X_I2C_ADDRESS, txFrame,
*_i2cBus);
delay(1);
return error;
}
uint16_t SensirionI2CSgp4x::getSerialNumber(uint16_t serialNumber[],
uint8_t serialNumberSize) {
uint16_t error;
uint8_t buffer[9];
SensirionI2CTxFrame txFrame =
SensirionI2CTxFrame::createWithUInt16Command(0x3682, buffer, 9);
error = SensirionI2CCommunication::sendFrame(SGP4X_I2C_ADDRESS, txFrame,
*_i2cBus);
if (error) {
return error;
}
delay(1);
SensirionI2CRxFrame rxFrame(buffer, 9);
error = SensirionI2CCommunication::receiveFrame(SGP4X_I2C_ADDRESS, 9,
rxFrame, *_i2cBus);
if (error) {
return error;
}
error |= rxFrame.getUInt16(serialNumber[0]);
error |= rxFrame.getUInt16(serialNumber[1]);
error |= rxFrame.getUInt16(serialNumber[2]);
return error;
}

View File

@ -0,0 +1,145 @@
/*
* THIS FILE IS AUTOMATICALLY GENERATED
*
* I2C-Generator: 0.3.0
* Yaml Version: 0.1.0
* Template Version: 0.7.0-62-g3d691f9
*/
/*
* Copyright (c) 2021, 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 SENSIRIONI2CSGP4X_H
#define SENSIRIONI2CSGP4X_H
#include <Wire.h>
#include <SensirionCore.h>
class SensirionI2CSgp4x {
public:
SensirionI2CSgp4x();
/**
* begin() - Initializes the SensirionI2CSgp4x class.
*
* @param serial Arduino stream object to be communicated with.
*
*/
void begin(TwoWire& i2cBus);
/**
* executeConditioning() - This command starts the conditioning, i.e., the
* VOC pixel will be operated at the same temperature as it is by calling
* the sgp41_measure_raw command while the NOx pixel will be operated at a
* different temperature for conditioning. This command returns only the
* measured raw signal of the VOC pixel SRAW_VOC as 2 bytes (+ 1 CRC byte).
*
* @param defaultRh Default conditions for relative humidty.
*
* @param defaultT Default conditions for temperature.
*
* @param srawVoc u16 unsigned integer directly provides the raw signal
* SRAW_VOC in ticks which is proportional to the logarithm of the
* resistance of the sensing element.
*
* @return 0 on success, an error code otherwise
*/
uint16_t sendConditioningCmd(uint16_t defaultRh, uint16_t defaultT);
uint16_t readConditioningValue(uint16_t& srawVoc);
/**
* measureRawSignals() - This command starts/continues the VOC+NOx
* measurement mode
*
* @param relativeHumidity Leaves humidity compensation disabled by sending
* the default value 0x8000 (50%RH) or enables humidity compensation when
* sending the relative humidity in ticks (ticks = %RH * 65535 / 100)
*
* @param temperature Leaves humidity compensation disabled by sending the
* default value 0x6666 (25 degC) or enables humidity compensation when
* sending the temperature in ticks (ticks = (degC + 45) * 65535 / 175)
*
* @param srawVoc u16 unsigned integer directly provides the raw signal
* SRAW_VOC in ticks which is proportional to the logarithm of the
* resistance of the sensing element.
*
* @param srawNox u16 unsigned integer directly provides the raw signal
* SRAW_NOX in ticks which is proportional to the logarithm of the
* resistance of the sensing element.
*
* @return 0 on success, an error code otherwise
*/
uint16_t sendRawSignalsCmd(uint16_t relativeHumidity, uint16_t temperature);
uint16_t readRawSignalsValue(uint16_t& srawVoc, uint16_t& srawNox);
uint16_t sendRawSignalCmd(uint16_t relativeHumidity, uint16_t temperature);
uint16_t readRawSignalValue(uint16_t& srawVoc);
/**
* executeSelfTest() - This command triggers the built-in self-test checking
* for integrity of both hotplate and MOX material and returns the result of
* this test as 2 bytes
*
* @param testResult 0xXX 0xYY: ignore most significant byte 0xXX. The four
* least significant bits of the least significant byte 0xYY provide
* information if the self-test has or has not passed for each individual
* pixel. All zero mean all tests passed successfully. Check the datasheet
* for more detailed information.
*
* @return 0 on success, an error code otherwise
*/
uint16_t sendSelfTestCmd(void);
uint16_t readSelfTestValue(uint16_t& testResult);
/**
* turnHeaterOff() - This command turns the hotplate off and stops the
* measurement. Subsequently, the sensor enters the idle mode.
*
* @return 0 on success, an error code otherwise
*/
uint16_t turnHeaterOff(void);
/**
* getSerialNumber() - This command provides the decimal serial number of
* the SGP41 chip by returning 3x2 bytes.
*
* @param serialNumber 48-bit unique serial number
*
* @return 0 on success, an error code otherwise
*/
uint16_t getSerialNumber(uint16_t serialNumber[], uint8_t serialNumberSize);
private:
TwoWire* _i2cBus = nullptr;
};
#endif /* SENSIRIONI2CSGP4X_H */

View File

@ -0,0 +1,582 @@
/*
* Copyright (c) 2022, 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 "sensirion_gas_index_algorithm.h"
#include <math.h>
static void GasIndexAlgorithm__init_instances(GasIndexAlgorithmParams* params);
static void GasIndexAlgorithm__mean_variance_estimator__set_parameters(
GasIndexAlgorithmParams* params);
static void GasIndexAlgorithm__mean_variance_estimator__set_states(
GasIndexAlgorithmParams* params, float mean, float std, float uptime_gamma);
static float GasIndexAlgorithm__mean_variance_estimator__get_std(
const GasIndexAlgorithmParams* params);
static float GasIndexAlgorithm__mean_variance_estimator__get_mean(
const GasIndexAlgorithmParams* params);
static bool GasIndexAlgorithm__mean_variance_estimator__is_initialized(
GasIndexAlgorithmParams* params);
static void GasIndexAlgorithm__mean_variance_estimator___calculate_gamma(
GasIndexAlgorithmParams* params);
static void GasIndexAlgorithm__mean_variance_estimator__process(
GasIndexAlgorithmParams* params, float sraw);
static void
GasIndexAlgorithm__mean_variance_estimator___sigmoid__set_parameters(
GasIndexAlgorithmParams* params, float X0, float K);
static float GasIndexAlgorithm__mean_variance_estimator___sigmoid__process(
GasIndexAlgorithmParams* params, float sample);
static void
GasIndexAlgorithm__mox_model__set_parameters(GasIndexAlgorithmParams* params,
float SRAW_STD, float SRAW_MEAN);
static float
GasIndexAlgorithm__mox_model__process(GasIndexAlgorithmParams* params,
float sraw);
static void GasIndexAlgorithm__sigmoid_scaled__set_parameters(
GasIndexAlgorithmParams* params, float X0, float K, float offset_default);
static float
GasIndexAlgorithm__sigmoid_scaled__process(GasIndexAlgorithmParams* params,
float sample);
static void GasIndexAlgorithm__adaptive_lowpass__set_parameters(
GasIndexAlgorithmParams* params);
static float
GasIndexAlgorithm__adaptive_lowpass__process(GasIndexAlgorithmParams* params,
float sample);
void GasIndexAlgorithm_init_with_sampling_interval(
GasIndexAlgorithmParams* params, int32_t algorithm_type,
float sampling_interval) {
params->mAlgorithm_Type = algorithm_type;
params->mSamplingInterval = sampling_interval;
if ((algorithm_type == GasIndexAlgorithm_ALGORITHM_TYPE_NOX)) {
params->mIndex_Offset = GasIndexAlgorithm_NOX_INDEX_OFFSET_DEFAULT;
params->mSraw_Minimum = GasIndexAlgorithm_NOX_SRAW_MINIMUM;
params->mGating_Max_Duration_Minutes =
GasIndexAlgorithm_GATING_NOX_MAX_DURATION_MINUTES;
params->mInit_Duration_Mean = GasIndexAlgorithm_INIT_DURATION_MEAN_NOX;
params->mInit_Duration_Variance =
GasIndexAlgorithm_INIT_DURATION_VARIANCE_NOX;
params->mGating_Threshold = GasIndexAlgorithm_GATING_THRESHOLD_NOX;
} else {
params->mIndex_Offset = GasIndexAlgorithm_VOC_INDEX_OFFSET_DEFAULT;
params->mSraw_Minimum = GasIndexAlgorithm_VOC_SRAW_MINIMUM;
params->mGating_Max_Duration_Minutes =
GasIndexAlgorithm_GATING_VOC_MAX_DURATION_MINUTES;
params->mInit_Duration_Mean = GasIndexAlgorithm_INIT_DURATION_MEAN_VOC;
params->mInit_Duration_Variance =
GasIndexAlgorithm_INIT_DURATION_VARIANCE_VOC;
params->mGating_Threshold = GasIndexAlgorithm_GATING_THRESHOLD_VOC;
}
params->mIndex_Gain = GasIndexAlgorithm_INDEX_GAIN;
params->mTau_Mean_Hours = GasIndexAlgorithm_TAU_MEAN_HOURS;
params->mTau_Variance_Hours = GasIndexAlgorithm_TAU_VARIANCE_HOURS;
params->mSraw_Std_Initial = GasIndexAlgorithm_SRAW_STD_INITIAL;
GasIndexAlgorithm_reset(params);
}
void GasIndexAlgorithm_init(GasIndexAlgorithmParams* params,
int32_t algorithm_type) {
GasIndexAlgorithm_init_with_sampling_interval(
params, algorithm_type, GasIndexAlgorithm_DEFAULT_SAMPLING_INTERVAL);
}
void GasIndexAlgorithm_reset(GasIndexAlgorithmParams* params) {
params->mUptime = 0.f;
params->mSraw = 0.f;
params->mGas_Index = 0;
GasIndexAlgorithm__init_instances(params);
}
static void GasIndexAlgorithm__init_instances(GasIndexAlgorithmParams* params) {
GasIndexAlgorithm__mean_variance_estimator__set_parameters(params);
GasIndexAlgorithm__mox_model__set_parameters(
params, GasIndexAlgorithm__mean_variance_estimator__get_std(params),
GasIndexAlgorithm__mean_variance_estimator__get_mean(params));
if ((params->mAlgorithm_Type == GasIndexAlgorithm_ALGORITHM_TYPE_NOX)) {
GasIndexAlgorithm__sigmoid_scaled__set_parameters(
params, GasIndexAlgorithm_SIGMOID_X0_NOX,
GasIndexAlgorithm_SIGMOID_K_NOX,
GasIndexAlgorithm_NOX_INDEX_OFFSET_DEFAULT);
} else {
GasIndexAlgorithm__sigmoid_scaled__set_parameters(
params, GasIndexAlgorithm_SIGMOID_X0_VOC,
GasIndexAlgorithm_SIGMOID_K_VOC,
GasIndexAlgorithm_VOC_INDEX_OFFSET_DEFAULT);
}
GasIndexAlgorithm__adaptive_lowpass__set_parameters(params);
}
void GasIndexAlgorithm_get_sampling_interval(
const GasIndexAlgorithmParams* params, float* sampling_interval) {
*sampling_interval = params->mSamplingInterval;
}
void GasIndexAlgorithm_get_states(const GasIndexAlgorithmParams* params,
float* state0, float* state1) {
*state0 = GasIndexAlgorithm__mean_variance_estimator__get_mean(params);
*state1 = GasIndexAlgorithm__mean_variance_estimator__get_std(params);
return;
}
void GasIndexAlgorithm_set_states(GasIndexAlgorithmParams* params, float state0,
float state1) {
GasIndexAlgorithm__mean_variance_estimator__set_states(
params, state0, state1, GasIndexAlgorithm_PERSISTENCE_UPTIME_GAMMA);
GasIndexAlgorithm__mox_model__set_parameters(
params, GasIndexAlgorithm__mean_variance_estimator__get_std(params),
GasIndexAlgorithm__mean_variance_estimator__get_mean(params));
params->mSraw = state0;
}
void GasIndexAlgorithm_set_tuning_parameters(
GasIndexAlgorithmParams* params, int32_t index_offset,
int32_t learning_time_offset_hours, int32_t learning_time_gain_hours,
int32_t gating_max_duration_minutes, int32_t std_initial,
int32_t gain_factor) {
params->mIndex_Offset = ((float)(index_offset));
params->mTau_Mean_Hours = ((float)(learning_time_offset_hours));
params->mTau_Variance_Hours = ((float)(learning_time_gain_hours));
params->mGating_Max_Duration_Minutes =
((float)(gating_max_duration_minutes));
params->mSraw_Std_Initial = ((float)(std_initial));
params->mIndex_Gain = ((float)(gain_factor));
GasIndexAlgorithm__init_instances(params);
}
void GasIndexAlgorithm_get_tuning_parameters(
const GasIndexAlgorithmParams* params, int32_t* index_offset,
int32_t* learning_time_offset_hours, int32_t* learning_time_gain_hours,
int32_t* gating_max_duration_minutes, int32_t* std_initial,
int32_t* gain_factor) {
*index_offset = ((int32_t)(params->mIndex_Offset));
*learning_time_offset_hours = ((int32_t)(params->mTau_Mean_Hours));
*learning_time_gain_hours = ((int32_t)(params->mTau_Variance_Hours));
*gating_max_duration_minutes =
((int32_t)(params->mGating_Max_Duration_Minutes));
*std_initial = ((int32_t)(params->mSraw_Std_Initial));
*gain_factor = ((int32_t)(params->mIndex_Gain));
return;
}
void GasIndexAlgorithm_process(GasIndexAlgorithmParams* params, int32_t sraw,
int32_t* gas_index) {
if ((params->mUptime <= GasIndexAlgorithm_INITIAL_BLACKOUT)) {
params->mUptime = (params->mUptime + params->mSamplingInterval);
} else {
if (((sraw > 0) && (sraw < 65000))) {
if ((sraw < (params->mSraw_Minimum + 1))) {
sraw = (params->mSraw_Minimum + 1);
} else if ((sraw > (params->mSraw_Minimum + 32767))) {
sraw = (params->mSraw_Minimum + 32767);
}
params->mSraw = ((float)((sraw - params->mSraw_Minimum)));
}
if (((params->mAlgorithm_Type ==
GasIndexAlgorithm_ALGORITHM_TYPE_VOC) ||
GasIndexAlgorithm__mean_variance_estimator__is_initialized(
params))) {
params->mGas_Index =
GasIndexAlgorithm__mox_model__process(params, params->mSraw);
params->mGas_Index = GasIndexAlgorithm__sigmoid_scaled__process(
params, params->mGas_Index);
} else {
params->mGas_Index = params->mIndex_Offset;
}
params->mGas_Index = GasIndexAlgorithm__adaptive_lowpass__process(
params, params->mGas_Index);
if ((params->mGas_Index < 0.5f)) {
params->mGas_Index = 0.5f;
}
if ((params->mSraw > 0.f)) {
GasIndexAlgorithm__mean_variance_estimator__process(params,
params->mSraw);
GasIndexAlgorithm__mox_model__set_parameters(
params,
GasIndexAlgorithm__mean_variance_estimator__get_std(params),
GasIndexAlgorithm__mean_variance_estimator__get_mean(params));
}
}
*gas_index = ((int32_t)((params->mGas_Index + 0.5f)));
return;
}
static void GasIndexAlgorithm__mean_variance_estimator__set_parameters(
GasIndexAlgorithmParams* params) {
params->m_Mean_Variance_Estimator___Initialized = false;
params->m_Mean_Variance_Estimator___Mean = 0.f;
params->m_Mean_Variance_Estimator___Sraw_Offset = 0.f;
params->m_Mean_Variance_Estimator___Std = params->mSraw_Std_Initial;
params->m_Mean_Variance_Estimator___Gamma_Mean =
(((GasIndexAlgorithm_MEAN_VARIANCE_ESTIMATOR__ADDITIONAL_GAMMA_MEAN_SCALING *
GasIndexAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING) *
(params->mSamplingInterval / 3600.f)) /
(params->mTau_Mean_Hours + (params->mSamplingInterval / 3600.f)));
params->m_Mean_Variance_Estimator___Gamma_Variance =
((GasIndexAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING *
(params->mSamplingInterval / 3600.f)) /
(params->mTau_Variance_Hours + (params->mSamplingInterval / 3600.f)));
if ((params->mAlgorithm_Type == GasIndexAlgorithm_ALGORITHM_TYPE_NOX)) {
params->m_Mean_Variance_Estimator___Gamma_Initial_Mean =
(((GasIndexAlgorithm_MEAN_VARIANCE_ESTIMATOR__ADDITIONAL_GAMMA_MEAN_SCALING *
GasIndexAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING) *
params->mSamplingInterval) /
(GasIndexAlgorithm_TAU_INITIAL_MEAN_NOX +
params->mSamplingInterval));
} else {
params->m_Mean_Variance_Estimator___Gamma_Initial_Mean =
(((GasIndexAlgorithm_MEAN_VARIANCE_ESTIMATOR__ADDITIONAL_GAMMA_MEAN_SCALING *
GasIndexAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING) *
params->mSamplingInterval) /
(GasIndexAlgorithm_TAU_INITIAL_MEAN_VOC +
params->mSamplingInterval));
}
params->m_Mean_Variance_Estimator___Gamma_Initial_Variance =
((GasIndexAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING *
params->mSamplingInterval) /
(GasIndexAlgorithm_TAU_INITIAL_VARIANCE + params->mSamplingInterval));
params->m_Mean_Variance_Estimator__Gamma_Mean = 0.f;
params->m_Mean_Variance_Estimator__Gamma_Variance = 0.f;
params->m_Mean_Variance_Estimator___Uptime_Gamma = 0.f;
params->m_Mean_Variance_Estimator___Uptime_Gating = 0.f;
params->m_Mean_Variance_Estimator___Gating_Duration_Minutes = 0.f;
}
static void GasIndexAlgorithm__mean_variance_estimator__set_states(
GasIndexAlgorithmParams* params, float mean, float std,
float uptime_gamma) {
params->m_Mean_Variance_Estimator___Mean = mean;
params->m_Mean_Variance_Estimator___Std = std;
params->m_Mean_Variance_Estimator___Uptime_Gamma = uptime_gamma;
params->m_Mean_Variance_Estimator___Initialized = true;
}
static float GasIndexAlgorithm__mean_variance_estimator__get_std(
const GasIndexAlgorithmParams* params) {
return params->m_Mean_Variance_Estimator___Std;
}
static float GasIndexAlgorithm__mean_variance_estimator__get_mean(
const GasIndexAlgorithmParams* params) {
return (params->m_Mean_Variance_Estimator___Mean +
params->m_Mean_Variance_Estimator___Sraw_Offset);
}
static bool GasIndexAlgorithm__mean_variance_estimator__is_initialized(
GasIndexAlgorithmParams* params) {
return params->m_Mean_Variance_Estimator___Initialized;
}
static void GasIndexAlgorithm__mean_variance_estimator___calculate_gamma(
GasIndexAlgorithmParams* params) {
float uptime_limit;
float sigmoid_gamma_mean;
float gamma_mean;
float gating_threshold_mean;
float sigmoid_gating_mean;
float sigmoid_gamma_variance;
float gamma_variance;
float gating_threshold_variance;
float sigmoid_gating_variance;
uptime_limit = (GasIndexAlgorithm_MEAN_VARIANCE_ESTIMATOR__FIX16_MAX -
params->mSamplingInterval);
if ((params->m_Mean_Variance_Estimator___Uptime_Gamma < uptime_limit)) {
params->m_Mean_Variance_Estimator___Uptime_Gamma =
(params->m_Mean_Variance_Estimator___Uptime_Gamma +
params->mSamplingInterval);
}
if ((params->m_Mean_Variance_Estimator___Uptime_Gating < uptime_limit)) {
params->m_Mean_Variance_Estimator___Uptime_Gating =
(params->m_Mean_Variance_Estimator___Uptime_Gating +
params->mSamplingInterval);
}
GasIndexAlgorithm__mean_variance_estimator___sigmoid__set_parameters(
params, params->mInit_Duration_Mean,
GasIndexAlgorithm_INIT_TRANSITION_MEAN);
sigmoid_gamma_mean =
GasIndexAlgorithm__mean_variance_estimator___sigmoid__process(
params, params->m_Mean_Variance_Estimator___Uptime_Gamma);
gamma_mean = (params->m_Mean_Variance_Estimator___Gamma_Mean +
((params->m_Mean_Variance_Estimator___Gamma_Initial_Mean -
params->m_Mean_Variance_Estimator___Gamma_Mean) *
sigmoid_gamma_mean));
gating_threshold_mean =
(params->mGating_Threshold +
((GasIndexAlgorithm_GATING_THRESHOLD_INITIAL -
params->mGating_Threshold) *
GasIndexAlgorithm__mean_variance_estimator___sigmoid__process(
params, params->m_Mean_Variance_Estimator___Uptime_Gating)));
GasIndexAlgorithm__mean_variance_estimator___sigmoid__set_parameters(
params, gating_threshold_mean,
GasIndexAlgorithm_GATING_THRESHOLD_TRANSITION);
sigmoid_gating_mean =
GasIndexAlgorithm__mean_variance_estimator___sigmoid__process(
params, params->mGas_Index);
params->m_Mean_Variance_Estimator__Gamma_Mean =
(sigmoid_gating_mean * gamma_mean);
GasIndexAlgorithm__mean_variance_estimator___sigmoid__set_parameters(
params, params->mInit_Duration_Variance,
GasIndexAlgorithm_INIT_TRANSITION_VARIANCE);
sigmoid_gamma_variance =
GasIndexAlgorithm__mean_variance_estimator___sigmoid__process(
params, params->m_Mean_Variance_Estimator___Uptime_Gamma);
gamma_variance =
(params->m_Mean_Variance_Estimator___Gamma_Variance +
((params->m_Mean_Variance_Estimator___Gamma_Initial_Variance -
params->m_Mean_Variance_Estimator___Gamma_Variance) *
(sigmoid_gamma_variance - sigmoid_gamma_mean)));
gating_threshold_variance =
(params->mGating_Threshold +
((GasIndexAlgorithm_GATING_THRESHOLD_INITIAL -
params->mGating_Threshold) *
GasIndexAlgorithm__mean_variance_estimator___sigmoid__process(
params, params->m_Mean_Variance_Estimator___Uptime_Gating)));
GasIndexAlgorithm__mean_variance_estimator___sigmoid__set_parameters(
params, gating_threshold_variance,
GasIndexAlgorithm_GATING_THRESHOLD_TRANSITION);
sigmoid_gating_variance =
GasIndexAlgorithm__mean_variance_estimator___sigmoid__process(
params, params->mGas_Index);
params->m_Mean_Variance_Estimator__Gamma_Variance =
(sigmoid_gating_variance * gamma_variance);
params->m_Mean_Variance_Estimator___Gating_Duration_Minutes =
(params->m_Mean_Variance_Estimator___Gating_Duration_Minutes +
((params->mSamplingInterval / 60.f) *
(((1.f - sigmoid_gating_mean) *
(1.f + GasIndexAlgorithm_GATING_MAX_RATIO)) -
GasIndexAlgorithm_GATING_MAX_RATIO)));
if ((params->m_Mean_Variance_Estimator___Gating_Duration_Minutes < 0.f)) {
params->m_Mean_Variance_Estimator___Gating_Duration_Minutes = 0.f;
}
if ((params->m_Mean_Variance_Estimator___Gating_Duration_Minutes >
params->mGating_Max_Duration_Minutes)) {
params->m_Mean_Variance_Estimator___Uptime_Gating = 0.f;
}
}
static void GasIndexAlgorithm__mean_variance_estimator__process(
GasIndexAlgorithmParams* params, float sraw) {
float delta_sgp;
float c;
float additional_scaling;
if ((params->m_Mean_Variance_Estimator___Initialized == false)) {
params->m_Mean_Variance_Estimator___Initialized = true;
params->m_Mean_Variance_Estimator___Sraw_Offset = sraw;
params->m_Mean_Variance_Estimator___Mean = 0.f;
} else {
if (((params->m_Mean_Variance_Estimator___Mean >= 100.f) ||
(params->m_Mean_Variance_Estimator___Mean <= -100.f))) {
params->m_Mean_Variance_Estimator___Sraw_Offset =
(params->m_Mean_Variance_Estimator___Sraw_Offset +
params->m_Mean_Variance_Estimator___Mean);
params->m_Mean_Variance_Estimator___Mean = 0.f;
}
sraw = (sraw - params->m_Mean_Variance_Estimator___Sraw_Offset);
GasIndexAlgorithm__mean_variance_estimator___calculate_gamma(params);
delta_sgp = ((sraw - params->m_Mean_Variance_Estimator___Mean) /
GasIndexAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING);
if ((delta_sgp < 0.f)) {
c = (params->m_Mean_Variance_Estimator___Std - delta_sgp);
} else {
c = (params->m_Mean_Variance_Estimator___Std + delta_sgp);
}
additional_scaling = 1.f;
if ((c > 1440.f)) {
additional_scaling = ((c / 1440.f) * (c / 1440.f));
}
params->m_Mean_Variance_Estimator___Std =
(sqrtf((additional_scaling *
(GasIndexAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING -
params->m_Mean_Variance_Estimator__Gamma_Variance))) *
sqrtf(
((params->m_Mean_Variance_Estimator___Std *
(params->m_Mean_Variance_Estimator___Std /
(GasIndexAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING *
additional_scaling))) +
(((params->m_Mean_Variance_Estimator__Gamma_Variance *
delta_sgp) /
additional_scaling) *
delta_sgp))));
params->m_Mean_Variance_Estimator___Mean =
(params->m_Mean_Variance_Estimator___Mean +
((params->m_Mean_Variance_Estimator__Gamma_Mean * delta_sgp) /
GasIndexAlgorithm_MEAN_VARIANCE_ESTIMATOR__ADDITIONAL_GAMMA_MEAN_SCALING));
}
}
static void
GasIndexAlgorithm__mean_variance_estimator___sigmoid__set_parameters(
GasIndexAlgorithmParams* params, float X0, float K) {
params->m_Mean_Variance_Estimator___Sigmoid__K = K;
params->m_Mean_Variance_Estimator___Sigmoid__X0 = X0;
}
static float GasIndexAlgorithm__mean_variance_estimator___sigmoid__process(
GasIndexAlgorithmParams* params, float sample) {
float x;
x = (params->m_Mean_Variance_Estimator___Sigmoid__K *
(sample - params->m_Mean_Variance_Estimator___Sigmoid__X0));
if ((x < -50.f)) {
return 1.f;
} else if ((x > 50.f)) {
return 0.f;
} else {
return (1.f / (1.f + expf(x)));
}
}
static void
GasIndexAlgorithm__mox_model__set_parameters(GasIndexAlgorithmParams* params,
float SRAW_STD, float SRAW_MEAN) {
params->m_Mox_Model__Sraw_Std = SRAW_STD;
params->m_Mox_Model__Sraw_Mean = SRAW_MEAN;
}
static float
GasIndexAlgorithm__mox_model__process(GasIndexAlgorithmParams* params,
float sraw) {
if ((params->mAlgorithm_Type == GasIndexAlgorithm_ALGORITHM_TYPE_NOX)) {
return (((sraw - params->m_Mox_Model__Sraw_Mean) /
GasIndexAlgorithm_SRAW_STD_NOX) *
params->mIndex_Gain);
} else {
return (((sraw - params->m_Mox_Model__Sraw_Mean) /
(-1.f * (params->m_Mox_Model__Sraw_Std +
GasIndexAlgorithm_SRAW_STD_BONUS_VOC))) *
params->mIndex_Gain);
}
}
static void GasIndexAlgorithm__sigmoid_scaled__set_parameters(
GasIndexAlgorithmParams* params, float X0, float K, float offset_default) {
params->m_Sigmoid_Scaled__K = K;
params->m_Sigmoid_Scaled__X0 = X0;
params->m_Sigmoid_Scaled__Offset_Default = offset_default;
}
static float
GasIndexAlgorithm__sigmoid_scaled__process(GasIndexAlgorithmParams* params,
float sample) {
float x;
float shift;
x = (params->m_Sigmoid_Scaled__K * (sample - params->m_Sigmoid_Scaled__X0));
if ((x < -50.f)) {
return GasIndexAlgorithm_SIGMOID_L;
} else if ((x > 50.f)) {
return 0.f;
} else {
if ((sample >= 0.f)) {
if ((params->m_Sigmoid_Scaled__Offset_Default == 1.f)) {
shift = ((500.f / 499.f) * (1.f - params->mIndex_Offset));
} else {
shift = ((GasIndexAlgorithm_SIGMOID_L -
(5.f * params->mIndex_Offset)) /
4.f);
}
return (((GasIndexAlgorithm_SIGMOID_L + shift) / (1.f + expf(x))) -
shift);
} else {
return ((params->mIndex_Offset /
params->m_Sigmoid_Scaled__Offset_Default) *
(GasIndexAlgorithm_SIGMOID_L / (1.f + expf(x))));
}
}
}
static void GasIndexAlgorithm__adaptive_lowpass__set_parameters(
GasIndexAlgorithmParams* params) {
params->m_Adaptive_Lowpass__A1 =
(params->mSamplingInterval /
(GasIndexAlgorithm_LP_TAU_FAST + params->mSamplingInterval));
params->m_Adaptive_Lowpass__A2 =
(params->mSamplingInterval /
(GasIndexAlgorithm_LP_TAU_SLOW + params->mSamplingInterval));
params->m_Adaptive_Lowpass___Initialized = false;
}
static float
GasIndexAlgorithm__adaptive_lowpass__process(GasIndexAlgorithmParams* params,
float sample) {
float abs_delta;
float F1;
float tau_a;
float a3;
if ((params->m_Adaptive_Lowpass___Initialized == false)) {
params->m_Adaptive_Lowpass___X1 = sample;
params->m_Adaptive_Lowpass___X2 = sample;
params->m_Adaptive_Lowpass___X3 = sample;
params->m_Adaptive_Lowpass___Initialized = true;
}
params->m_Adaptive_Lowpass___X1 =
(((1.f - params->m_Adaptive_Lowpass__A1) *
params->m_Adaptive_Lowpass___X1) +
(params->m_Adaptive_Lowpass__A1 * sample));
params->m_Adaptive_Lowpass___X2 =
(((1.f - params->m_Adaptive_Lowpass__A2) *
params->m_Adaptive_Lowpass___X2) +
(params->m_Adaptive_Lowpass__A2 * sample));
abs_delta =
(params->m_Adaptive_Lowpass___X1 - params->m_Adaptive_Lowpass___X2);
if ((abs_delta < 0.f)) {
abs_delta = (-1.f * abs_delta);
}
F1 = expf((GasIndexAlgorithm_LP_ALPHA * abs_delta));
tau_a = (((GasIndexAlgorithm_LP_TAU_SLOW - GasIndexAlgorithm_LP_TAU_FAST) *
F1) +
GasIndexAlgorithm_LP_TAU_FAST);
a3 = (params->mSamplingInterval / (params->mSamplingInterval + tau_a));
params->m_Adaptive_Lowpass___X3 =
(((1.f - a3) * params->m_Adaptive_Lowpass___X3) + (a3 * sample));
return params->m_Adaptive_Lowpass___X3;
}

View File

@ -0,0 +1,290 @@
/*
* Copyright (c) 2022, 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 GASINDEXALGORITHM_H_
#define GASINDEXALGORITHM_H_
#include <stdint.h>
#ifndef __cplusplus
#if __STDC_VERSION__ >= 199901L
#include <stdbool.h>
#else
#ifndef bool
#define bool int
#define true 1
#define false 0
#endif // bool
#endif // __STDC_VERSION__
#endif // __cplusplus
// Should be set by the building toolchain
#ifndef LIBRARY_VERSION_NAME
#define LIBRARY_VERSION_NAME "3.2.0"
#endif
#define GasIndexAlgorithm_ALGORITHM_TYPE_VOC (0)
#define GasIndexAlgorithm_ALGORITHM_TYPE_NOX (1)
#define GasIndexAlgorithm_DEFAULT_SAMPLING_INTERVAL (1.f)
#define GasIndexAlgorithm_INITIAL_BLACKOUT (45.f)
#define GasIndexAlgorithm_INDEX_GAIN (230.f)
#define GasIndexAlgorithm_SRAW_STD_INITIAL (50.f)
#define GasIndexAlgorithm_SRAW_STD_BONUS_VOC (220.f)
#define GasIndexAlgorithm_SRAW_STD_NOX (2000.f)
#define GasIndexAlgorithm_TAU_MEAN_HOURS (12.f)
#define GasIndexAlgorithm_TAU_VARIANCE_HOURS (12.f)
#define GasIndexAlgorithm_TAU_INITIAL_MEAN_VOC (20.f)
#define GasIndexAlgorithm_TAU_INITIAL_MEAN_NOX (1200.f)
#define GasIndexAlgorithm_INIT_DURATION_MEAN_VOC ((3600.f * 0.75f))
#define GasIndexAlgorithm_INIT_DURATION_MEAN_NOX ((3600.f * 4.75f))
#define GasIndexAlgorithm_INIT_TRANSITION_MEAN (0.01f)
#define GasIndexAlgorithm_TAU_INITIAL_VARIANCE (2500.f)
#define GasIndexAlgorithm_INIT_DURATION_VARIANCE_VOC ((3600.f * 1.45f))
#define GasIndexAlgorithm_INIT_DURATION_VARIANCE_NOX ((3600.f * 5.70f))
#define GasIndexAlgorithm_INIT_TRANSITION_VARIANCE (0.01f)
#define GasIndexAlgorithm_GATING_THRESHOLD_VOC (340.f)
#define GasIndexAlgorithm_GATING_THRESHOLD_NOX (30.f)
#define GasIndexAlgorithm_GATING_THRESHOLD_INITIAL (510.f)
#define GasIndexAlgorithm_GATING_THRESHOLD_TRANSITION (0.09f)
#define GasIndexAlgorithm_GATING_VOC_MAX_DURATION_MINUTES ((60.f * 3.f))
#define GasIndexAlgorithm_GATING_NOX_MAX_DURATION_MINUTES ((60.f * 12.f))
#define GasIndexAlgorithm_GATING_MAX_RATIO (0.3f)
#define GasIndexAlgorithm_SIGMOID_L (500.f)
#define GasIndexAlgorithm_SIGMOID_K_VOC (-0.0065f)
#define GasIndexAlgorithm_SIGMOID_X0_VOC (213.f)
#define GasIndexAlgorithm_SIGMOID_K_NOX (-0.0101f)
#define GasIndexAlgorithm_SIGMOID_X0_NOX (614.f)
#define GasIndexAlgorithm_VOC_INDEX_OFFSET_DEFAULT (100.f)
#define GasIndexAlgorithm_NOX_INDEX_OFFSET_DEFAULT (1.f)
#define GasIndexAlgorithm_LP_TAU_FAST (20.0f)
#define GasIndexAlgorithm_LP_TAU_SLOW (500.0f)
#define GasIndexAlgorithm_LP_ALPHA (-0.2f)
#define GasIndexAlgorithm_VOC_SRAW_MINIMUM (20000)
#define GasIndexAlgorithm_NOX_SRAW_MINIMUM (10000)
#define GasIndexAlgorithm_PERSISTENCE_UPTIME_GAMMA ((3.f * 3600.f))
#define GasIndexAlgorithm_TUNING_INDEX_OFFSET_MIN (1)
#define GasIndexAlgorithm_TUNING_INDEX_OFFSET_MAX (250)
#define GasIndexAlgorithm_TUNING_LEARNING_TIME_OFFSET_HOURS_MIN (1)
#define GasIndexAlgorithm_TUNING_LEARNING_TIME_OFFSET_HOURS_MAX (1000)
#define GasIndexAlgorithm_TUNING_LEARNING_TIME_GAIN_HOURS_MIN (1)
#define GasIndexAlgorithm_TUNING_LEARNING_TIME_GAIN_HOURS_MAX (1000)
#define GasIndexAlgorithm_TUNING_GATING_MAX_DURATION_MINUTES_MIN (0)
#define GasIndexAlgorithm_TUNING_GATING_MAX_DURATION_MINUTES_MAX (3000)
#define GasIndexAlgorithm_TUNING_STD_INITIAL_MIN (10)
#define GasIndexAlgorithm_TUNING_STD_INITIAL_MAX (5000)
#define GasIndexAlgorithm_TUNING_GAIN_FACTOR_MIN (1)
#define GasIndexAlgorithm_TUNING_GAIN_FACTOR_MAX (1000)
#define GasIndexAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING (64.f)
#define GasIndexAlgorithm_MEAN_VARIANCE_ESTIMATOR__ADDITIONAL_GAMMA_MEAN_SCALING \
(8.f)
#define GasIndexAlgorithm_MEAN_VARIANCE_ESTIMATOR__FIX16_MAX (32767.f)
/**
* Struct to hold all parameters and states of the gas algorithm.
*/
typedef struct {
int mAlgorithm_Type;
float mSamplingInterval;
float mIndex_Offset;
int32_t mSraw_Minimum;
float mGating_Max_Duration_Minutes;
float mInit_Duration_Mean;
float mInit_Duration_Variance;
float mGating_Threshold;
float mIndex_Gain;
float mTau_Mean_Hours;
float mTau_Variance_Hours;
float mSraw_Std_Initial;
float mUptime;
float mSraw;
float mGas_Index;
bool m_Mean_Variance_Estimator___Initialized;
float m_Mean_Variance_Estimator___Mean;
float m_Mean_Variance_Estimator___Sraw_Offset;
float m_Mean_Variance_Estimator___Std;
float m_Mean_Variance_Estimator___Gamma_Mean;
float m_Mean_Variance_Estimator___Gamma_Variance;
float m_Mean_Variance_Estimator___Gamma_Initial_Mean;
float m_Mean_Variance_Estimator___Gamma_Initial_Variance;
float m_Mean_Variance_Estimator__Gamma_Mean;
float m_Mean_Variance_Estimator__Gamma_Variance;
float m_Mean_Variance_Estimator___Uptime_Gamma;
float m_Mean_Variance_Estimator___Uptime_Gating;
float m_Mean_Variance_Estimator___Gating_Duration_Minutes;
float m_Mean_Variance_Estimator___Sigmoid__K;
float m_Mean_Variance_Estimator___Sigmoid__X0;
float m_Mox_Model__Sraw_Std;
float m_Mox_Model__Sraw_Mean;
float m_Sigmoid_Scaled__K;
float m_Sigmoid_Scaled__X0;
float m_Sigmoid_Scaled__Offset_Default;
float m_Adaptive_Lowpass__A1;
float m_Adaptive_Lowpass__A2;
bool m_Adaptive_Lowpass___Initialized;
float m_Adaptive_Lowpass___X1;
float m_Adaptive_Lowpass___X2;
float m_Adaptive_Lowpass___X3;
} GasIndexAlgorithmParams;
/**
* Initialize the gas index algorithm parameters for the specified algorithm
* type and reset its internal states. Call this once at the beginning.
* @param params Pointer to the GasIndexAlgorithmParams struct
* @param algorithm_type 0 (GasIndexAlgorithm_ALGORITHM_TYPE_VOC) for VOC or
* 1 (GasIndexAlgorithm_ALGORITHM_TYPE_NOX) for NOx
*/
void GasIndexAlgorithm_init(GasIndexAlgorithmParams* params,
int32_t algorithm_type);
/**
* Initialize the gas index algorithm parameters for the specified algorithm
* type and reset its internal states. Call this once at the beginning.
* @param params Pointer to the GasIndexAlgorithmParams struct
* @param algorithm_type 0 (GasIndexAlgorithm_ALGORITHM_TYPE_VOC) for VOC or
* 1 (GasIndexAlgorithm_ALGORITHM_TYPE_NOX) for NOx
* @param sampling_interval The sampling interval in seconds the algorithm is
* called. Tested for 1s and 10s.
*/
void GasIndexAlgorithm_init_with_sampling_interval(
GasIndexAlgorithmParams* params, int32_t algorithm_type,
float sampling_interval);
/**
* Reset the internal states of the gas index algorithm. Previously set tuning
* parameters are preserved. Call this when resuming operation after a
* measurement interruption.
* @param params Pointer to the GasIndexAlgorithmParams struct
*/
void GasIndexAlgorithm_reset(GasIndexAlgorithmParams* params);
/**
* Get current algorithm states. Retrieved values can be used in
* GasIndexAlgorithm_set_states() to resume operation after a short
* interruption, skipping initial learning phase.
* NOTE: This feature can only be used for VOC algorithm type and after at least
* 3 hours of continuous operation.
* @param params Pointer to the GasIndexAlgorithmParams struct
* @param state0 State0 to be stored
* @param state1 State1 to be stored
*/
void GasIndexAlgorithm_get_states(const GasIndexAlgorithmParams* params,
float* state0, float* state1);
/**
* Set previously retrieved algorithm states to resume operation after a short
* interruption, skipping initial learning phase. This feature should not be
* used after interruptions of more than 10 minutes. Call this once after
* GasIndexAlgorithm_init() or GasIndexAlgorithm_reset() and the optional
* GasIndexAlgorithm_set_tuning_parameters(), if desired. Otherwise, the
* algorithm will start with initial learning phase.
* NOTE: This feature can only be used for VOC algorithm type.
* @param params Pointer to the GasIndexAlgorithmParams struct
* @param state0 State0 to be restored
* @param state1 State1 to be restored
*/
void GasIndexAlgorithm_set_states(GasIndexAlgorithmParams* params, float state0,
float state1);
/**
* Set parameters to customize the gas index algorithm. Call this once after
* GasIndexAlgorithm_init() and before optional GasIndexAlgorithm_set_states(),
* if desired. Otherwise, the default values will be used.
*
* @param params Pointer to the GasIndexAlgorithmParams
* struct
* @param index_offset Gas index representing typical (average)
* conditions. Range 1..250,
* default 100 for VOC and 1 for NOx
* @param learning_time_offset_hours Time constant of long-term estimator for
* offset. Past events will be forgotten
* after about twice the learning time.
* Range 1..1000 [hours], default 12 [hours]
* @param learning_time_gain_hours Time constant of long-term estimator for
* gain. Past events will be forgotten
* after about twice the learning time.
* Range 1..1000 [hours], default 12 [hours]
* NOTE: This value is not relevant for NOx
* algorithm type
* @param gating_max_duration_minutes Maximum duration of gating (freeze of
* estimator during high gas index signal).
* 0 (no gating) or range 1..3000 [minutes],
* default 180 [minutes] for VOC and
* 720 [minutes] for NOx
* @param std_initial Initial estimate for standard deviation.
* Lower value boosts events during initial
* learning period, but may result in larger
* device-to-device variations.
* Range 10..5000, default 50
* NOTE: This value is not relevant for NOx
* algorithm type
* @param gain_factor Factor used to scale applied gain value
* when calculating gas index. Range 1..1000,
* default 230
*/
void GasIndexAlgorithm_set_tuning_parameters(
GasIndexAlgorithmParams* params, int32_t index_offset,
int32_t learning_time_offset_hours, int32_t learning_time_gain_hours,
int32_t gating_max_duration_minutes, int32_t std_initial,
int32_t gain_factor);
/**
* Get current parameters to customize the gas index algorithm.
* Refer to GasIndexAlgorithm_set_tuning_parameters() for description of the
* parameters.
*/
void GasIndexAlgorithm_get_tuning_parameters(
const GasIndexAlgorithmParams* params, int32_t* index_offset,
int32_t* learning_time_offset_hours, int32_t* learning_time_gain_hours,
int32_t* gating_max_duration_minutes, int32_t* std_initial,
int32_t* gain_factor);
/**
* Get the sampling interval parameter used by the algorithm.
*/
void GasIndexAlgorithm_get_sampling_interval(
const GasIndexAlgorithmParams* params, float* sampling_interval);
/**
* Calculate the gas index value from the raw sensor value.
*
* @param params Pointer to the GasIndexAlgorithmParams struct
* @param sraw Raw value from the SGP4x sensor
* @param gas_index Calculated gas index value from the raw sensor value. Zero
* during initial blackout period and 1..500 afterwards
*/
void GasIndexAlgorithm_process(GasIndexAlgorithmParams* params, int32_t sraw,
int32_t* gas_index);
#endif /* GASINDEXALGORITHM_H_ */

View File

@ -99,6 +99,7 @@
#define USE_MGS // [I2cDriver17] Enable Xadow and Grove Mutichannel Gas sensor using library Multichannel_Gas_Sensor (+10k code) #define USE_MGS // [I2cDriver17] Enable Xadow and Grove Mutichannel Gas sensor using library Multichannel_Gas_Sensor (+10k code)
#define USE_SGP30 // [I2cDriver18] Enable SGP30 sensor (I2C address 0x58) (+1k1 code) #define USE_SGP30 // [I2cDriver18] Enable SGP30 sensor (I2C address 0x58) (+1k1 code)
#define USE_SGP40 // [I2cDriver69] Enable SGP40 sensor (I2C address 0x59) (+1k4 code) #define USE_SGP40 // [I2cDriver69] Enable SGP40 sensor (I2C address 0x59) (+1k4 code)
#define USE_SGP4X // [I2cDriver69] Enable SGP40 sensor (I2C address 0x59) (+1k4 code)
#define USE_SEN5X // [I2cDriver76] Enable SEN5X sensor (I2C address 0x69) (+3k code) #define USE_SEN5X // [I2cDriver76] Enable SEN5X sensor (I2C address 0x69) (+3k code)
//#define USE_SI1145 // [I2cDriver19] Enable SI1145/46/47 sensor (I2C address 0x60) (+1k code) //#define USE_SI1145 // [I2cDriver19] Enable SI1145/46/47 sensor (I2C address 0x60) (+1k code)
#define USE_LM75AD // [I2cDriver20] Enable LM75AD sensor (I2C addresses 0x48 - 0x4F) (+0k5 code) #define USE_LM75AD // [I2cDriver20] Enable LM75AD sensor (I2C addresses 0x48 - 0x4F) (+0k5 code)

View File

@ -376,6 +376,7 @@
//#define USE_MGS // [I2cDriver17] Enable Xadow and Grove Mutichannel Gas sensor using library Multichannel_Gas_Sensor (+10k code) //#define USE_MGS // [I2cDriver17] Enable Xadow and Grove Mutichannel Gas sensor using library Multichannel_Gas_Sensor (+10k code)
//#define USE_SGP30 // [I2cDriver18] Enable SGP30 sensor (I2C address 0x58) (+1k1 code) //#define USE_SGP30 // [I2cDriver18] Enable SGP30 sensor (I2C address 0x58) (+1k1 code)
//#define USE_SGP40 // [I2cDriver69] Enable SGP40 sensor (I2C address 0x59) (+1k4 code) //#define USE_SGP40 // [I2cDriver69] Enable SGP40 sensor (I2C address 0x59) (+1k4 code)
//#define USE_SGP4X // [I2cDriver69] Enable SGP4X sensor (I2C address 0x59) (+1k4 code)
//#define USE_SEN5X // [I2cDriver76] Enable SEN5X sensor (I2C address 0x69) (+3k code) //#define USE_SEN5X // [I2cDriver76] Enable SEN5X sensor (I2C address 0x69) (+3k code)
//#define USE_SI1145 // [I2cDriver19] Enable SI1145/46/47 sensor (I2C address 0x60) (+1k code) //#define USE_SI1145 // [I2cDriver19] Enable SI1145/46/47 sensor (I2C address 0x60) (+1k code)
//#define USE_LM75AD // [I2cDriver20] Enable LM75AD sensor (I2C addresses 0x48 - 0x4F) (+0k5 code) //#define USE_LM75AD // [I2cDriver20] Enable LM75AD sensor (I2C addresses 0x48 - 0x4F) (+0k5 code)
@ -609,6 +610,7 @@
#define USE_MGS // [I2cDriver17] Enable Xadow and Grove Mutichannel Gas sensor using library Multichannel_Gas_Sensor (+10k code) #define USE_MGS // [I2cDriver17] Enable Xadow and Grove Mutichannel Gas sensor using library Multichannel_Gas_Sensor (+10k code)
#define USE_SGP30 // [I2cDriver18] Enable SGP30 sensor (I2C address 0x58) (+1k1 code) #define USE_SGP30 // [I2cDriver18] Enable SGP30 sensor (I2C address 0x58) (+1k1 code)
#define USE_SGP40 // [I2cDriver69] Enable SGP40 sensor (I2C address 0x59) (+1k4 code) #define USE_SGP40 // [I2cDriver69] Enable SGP40 sensor (I2C address 0x59) (+1k4 code)
#define USE_SGP4X // [I2cDriver69] Enable SGP4X sensor (I2C address 0x59) (+1k4 code)
#define USE_SEN5X // [I2cDriver76] Enable SEN5X sensor (I2C address 0x69) (+3k code) #define USE_SEN5X // [I2cDriver76] Enable SEN5X sensor (I2C address 0x69) (+3k code)
//#define USE_SI1145 // [I2cDriver19] Enable SI1145/46/47 sensor (I2C address 0x60) (+1k code) //#define USE_SI1145 // [I2cDriver19] Enable SI1145/46/47 sensor (I2C address 0x60) (+1k code)
#define USE_LM75AD // [I2cDriver20] Enable LM75AD sensor (I2C addresses 0x48 - 0x4F) (+0k5 code) #define USE_LM75AD // [I2cDriver20] Enable LM75AD sensor (I2C addresses 0x48 - 0x4F) (+0k5 code)

View File

@ -897,8 +897,10 @@ void ResponseAppendFeatures(void)
#if defined(USE_I2C) && defined(USE_PCA9557) #if defined(USE_I2C) && defined(USE_PCA9557)
feature9 |= 0x00800000; // xdrv_69_pca9557.ino feature9 |= 0x00800000; // xdrv_69_pca9557.ino
#endif #endif
#if defined(USE_I2C) && defined(USE_SGP4X)
feature9 |= 0x01000000; // xdrv_109_sgp4x.ino
#endif
// feature9 |= 0x01000000;
// feature9 |= 0x02000000; // feature9 |= 0x02000000;
// feature9 |= 0x04000000; // feature9 |= 0x04000000;
// feature9 |= 0x08000000; // feature9 |= 0x08000000;

View File

@ -0,0 +1,268 @@
/*
xsns_109_sgp4x.ino - SGP4X VOC and NOx sensor support for Tasmota
Copyright (C) 2023 Andrew Klaus
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
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef USE_I2C
#ifdef USE_SGP4X
/*********************************************************************************************\
* SGP4x - Gas (TVOC - Total Volatile Organic Compounds) and NOx
*
* Source: Sensirion Driver, with mods by Andrew Klaus
* Adaption for TASMOTA: Andrew Klaus
*
* I2C Address: 0x59
\*********************************************************************************************/
#define XSNS_109 109
#define XI2C_82 82 // See I2CDEVICES.md
#define SGP4X_ADDRESS 0x59
#include "SensirionI2CSgp4x.h"
extern "C" {
#include "sensirion_gas_index_algorithm.h"
};
enum SGP4X_State {
STATE_SGP4X_START,
STATE_SGP4X_SELFTEST_SENT,
STATE_SGP4X_SELFTEST_WAIT,
STATE_SGP4X_SELFTEST_DONE,
STATE_SGP4X_COND_SENT,
STATE_SGP4X_COND_DONE,
STATE_SGP4X_NORMAL,
STATE_SGP4X_FAIL,
};
SensirionI2CSgp4x sgp4x;
SGP4X_State sgp4x_state = STATE_SGP4X_START;
bool sgp4x_init = false;
bool sgp4x_read_pend = false;
uint16_t srawVoc;
uint16_t srawNox;
int32_t voc_index_sgp4x;
int32_t nox_index_sgp4x;
uint16_t conditioning_s = 10; // 10 second delay for startup
GasIndexAlgorithmParams voc_algorithm_params;
GasIndexAlgorithmParams nox_algorithm_params;
/********************************************************************************************/
void sgp4x_Init(void)
{
if (!I2cSetDevice(SGP4X_ADDRESS)) { return; }
uint8_t serialNumberSize = 3;
uint16_t serialNumber[serialNumberSize];
uint16_t error;
sgp4x.begin(Wire);
error = sgp4x.getSerialNumber(serialNumber, serialNumberSize);
if (error) {
Sgp4xHandleError(error);
return;
} else {
AddLog(LOG_LEVEL_INFO, PSTR("SGP4X serial nr 0x%X 0x%X 0x%X") ,serialNumber[0], serialNumber[1], serialNumber[2]);
}
I2cSetActiveFound(SGP4X_ADDRESS, "SGP4X");
sgp4x_init = true;
error = sgp4x.sendSelfTestCmd();
if (error) {
Sgp4xHandleError(error);
sgp4x_state = STATE_SGP4X_FAIL;
return;
}
sgp4x_state = STATE_SGP4X_SELFTEST_SENT;
GasIndexAlgorithm_init(&nox_algorithm_params, GasIndexAlgorithm_ALGORITHM_TYPE_NOX);
GasIndexAlgorithm_init(&voc_algorithm_params, GasIndexAlgorithm_ALGORITHM_TYPE_VOC);
}
void Sgp4xHandleError(uint16_t error) {
char errorMessage[256];
errorToString(error, errorMessage, 256);
AddLog(LOG_LEVEL_ERROR, PSTR("SGP4X error: %s"), errorMessage);
}
void Sgp4xSendReadCmd(void)
{
// Check if we're already waiting for a read
// Or if we need to wait a cycle before initiating a reading
if (sgp4x_read_pend){return;}
uint16_t error;
uint16_t rhticks = (uint16_t)((TasmotaGlobal.humidity * 65535) / 100 + 0.5);
uint16_t tempticks = (uint16_t)(((TasmotaGlobal.temperature_celsius + 45) * 65535) / 175);
// Handle self testing
// Wait 1 cycle (at least 320ms to read selftest value)
if (sgp4x_state == STATE_SGP4X_SELFTEST_SENT) {
sgp4x_state = STATE_SGP4X_SELFTEST_WAIT;
return;
} else if (sgp4x_state == STATE_SGP4X_SELFTEST_WAIT) {
if (Sgp4xReadSelfTest()){
sgp4x_state = STATE_SGP4X_FAIL;
return;
} else {
sgp4x_state = STATE_SGP4X_SELFTEST_DONE;
}
}
// Initiate conditioning
if (sgp4x_state == STATE_SGP4X_SELFTEST_DONE) {
error = sgp4x.sendConditioningCmd(0x8000, 0x6666);
sgp4x_state = STATE_SGP4X_COND_SENT;
if (error) {
Sgp4xHandleError(error);
}
return;
}
if (sgp4x_state == STATE_SGP4X_COND_DONE) {
sgp4x_state = STATE_SGP4X_NORMAL;
}
// Normal operation
if (sgp4x_state == STATE_SGP4X_NORMAL) {
error = sgp4x.sendRawSignalsCmd(rhticks, tempticks);
if (error) {
Sgp4xHandleError(error);
} else {
sgp4x_read_pend = true;
}
return;
}
return;
}
bool Sgp4xReadSelfTest() {
uint16_t testResult;
uint16_t error;
error = sgp4x.readSelfTestValue(testResult);
if (error) {
Sgp4xHandleError(error);
return true;
} else if (testResult != 0xD400) {
AddLog(LOG_LEVEL_ERROR, PSTR("SGP4X: executeSelfTest failed with error: %s"), testResult);
return true;
}
return false;
}
void Sgp4xUpdate(void)
{
uint16_t error;
// Conditioning - NOx needs 10s to warmup
if (sgp4x_state == STATE_SGP4X_COND_SENT) {
if (conditioning_s > 0) {
conditioning_s--;
return;
} else {
sgp4x_state = STATE_SGP4X_COND_DONE;
}
}
if (sgp4x_state == STATE_SGP4X_NORMAL && sgp4x_read_pend) {
error = sgp4x.readRawSignalsValue(srawVoc, srawNox);
sgp4x_read_pend = false;
if (!error) {
GasIndexAlgorithm_process(&voc_algorithm_params, srawVoc, &voc_index_sgp4x);
GasIndexAlgorithm_process(&nox_algorithm_params, srawNox, &nox_index_sgp4x);
} else {
Sgp4xHandleError(error);
}
}
}
#ifdef USE_WEBSERVER
const char HTTP_SNS_SGP4X[] PROGMEM =
"{s}SGP4X " D_TVOC "_" D_JSON_RAW "{m}%d " "{e}" // {s} = <tr><th>, {m} = </th><td>, {e} = </td></tr>
"{s}SGP4X " D_NOX "_" D_JSON_RAW "{m}%d " "{e}"
"{s}SGP4X " D_TVOC "{m}%d " "{e}"
"{s}SGP4X " D_NOX "{m}%d " "{e}";
#endif
void Sgp4xShow(bool json)
{
if (sgp4x_state == STATE_SGP4X_NORMAL) {
if (json) {
ResponseAppend_P(PSTR(",\"SGP4X\":{\"" D_TVOC "_" D_JSON_RAW "\":%d,\"" D_NOX "_" D_JSON_RAW "\":%d,\"" D_TVOC "\":%d,\"" D_NOX "\":%d"), srawVoc, srawNox, voc_index_sgp4x, nox_index_sgp4x);
ResponseJsonEnd();
#ifdef USE_DOMOTICZ
if (0 == TasmotaGlobal.tele_period) DomoticzSensor(DZ_AIRQUALITY, srawVoc);
#endif // USE_DOMOTICZ
#ifdef USE_WEBSERVER
} else {
WSContentSend_PD(HTTP_SNS_SGP4X, srawVoc, srawNox, voc_index_sgp4x, nox_index_sgp4x);
#endif
}
}
}
/*********************************************************************************************\
* Interface
\*********************************************************************************************/
bool Xsns109(uint32_t function)
{
if (!I2cEnabled(XI2C_82)) { return false; }
bool result = false;
if (FUNC_INIT == function) {
sgp4x_Init();
}
else if (sgp4x_init) {
switch (function) {
case FUNC_EVERY_250_MSECOND:
Sgp4xSendReadCmd();
break;
case FUNC_EVERY_SECOND:
Sgp4xUpdate();
break;
case FUNC_JSON_APPEND:
Sgp4xShow(1);
break;
#ifdef USE_WEBSERVER
case FUNC_WEB_SENSOR:
Sgp4xShow(0);
break;
#endif // USE_WEBSERVER
}
}
return result;
}
#endif // USE_sgp4x
#endif // USE_I2C

View File

@ -296,7 +296,7 @@ a_features = [[
"USE_DISPLAY_TM1650","USE_PCA9632","USE_TUYAMCUBR","USE_SEN5X", "USE_DISPLAY_TM1650","USE_PCA9632","USE_TUYAMCUBR","USE_SEN5X",
"USE_BIOPDU","USE_MCP23XXX_DRV","USE_PMSA003I","USE_LOX_O2", "USE_BIOPDU","USE_MCP23XXX_DRV","USE_PMSA003I","USE_LOX_O2",
"USE_GDK101","USE_GM861","USE_TC74","USE_PCA9557", "USE_GDK101","USE_GM861","USE_TC74","USE_PCA9557",
"","","","", "USE_SGP4X","","","",
"","","","" "","","",""
]] ]]