mirror of https://github.com/arendst/Tasmota.git
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:
parent
952811b4eb
commit
eb655a4a8f
|
@ -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 | - | - |
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
|
@ -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).
|
|
@ -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 |
|
@ -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)
|
||||||
|
#######################################
|
|
@ -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
|
|
@ -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;
|
||||||
|
}
|
|
@ -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 */
|
|
@ -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;
|
||||||
|
}
|
|
@ -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_ */
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
|
@ -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","","","",
|
||||||
"","","",""
|
"","","",""
|
||||||
]]
|
]]
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue