mirror of https://github.com/arendst/Tasmota.git
Add support for SGP40 sensor
This commit is contained in:
parent
74414d8304
commit
214f4bbdfd
|
@ -103,3 +103,4 @@ Index | Define | Driver | Device | Address(es) | Description
|
|||
66 | USE_PCF85363 | xsns_99 | PCF85363 | 0x51 | Real time clock
|
||||
67 | USE_DS3502 | xdrv_61 | DS3502 | 0x28 - 0x2B | Digital potentiometer
|
||||
68 | USE_HYT | xsns_97 | HYTxxx | 0x28 | Temperature and Humidity sensor
|
||||
69 | USE_SGP40 | xsns_98 | SGP40 | 0x59 | Gas (TVOC) and air quality
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
*~
|
||||
doxygen_sqlite3.db
|
||||
html# osx
|
||||
.DS_Store
|
||||
|
||||
# doxygen
|
||||
doxygen_sqlite3.db
|
||||
html
|
||||
*.tmp
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,53 @@
|
|||
# Adafruit SGP40 Gas / Air Quality I2C sensor [![Build Status](https://github.com/adafruit/Adafruit_SGP40/workflows/Arduino%20Library%20CI/badge.svg)](https://github.com/adafruit/Adafruit_SGP40/actions)[![Documentation](https://github.com/adafruit/ci-arduino/blob/master/assets/doxygen_badge.svg)](http://adafruit.github.io/Adafruit_SGP40/html/index.html)
|
||||
|
||||
<a href="https://www.adafruit.com/product/4829"><img src="https://cdn-shop.adafruit.com/970x728/4829-05.jpg" width="500px"></a>
|
||||
|
||||
This is the Adafruit SGP40 Gas / Air Quality I2C sensor library
|
||||
|
||||
Tested and works great with the Aadafruit SGP40 Breakout Board
|
||||
* http://www.adafruit.com/products/4829
|
||||
|
||||
This chip uses I2C to communicate, 2 pins are required to interface
|
||||
|
||||
Adafruit invests time and resources providing this open source code, please support Adafruit and open-source hardware by purchasing products from Adafruit!
|
||||
|
||||
# Installation
|
||||
To install, use the Arduino Library Manager and search for "Adafruit SGP40" and install the library.
|
||||
|
||||
## Dependencies
|
||||
* [Adafruit Bus IO](https://github.com/adafruit/Adafruit_BusIO)
|
||||
|
||||
# Contributing
|
||||
|
||||
Contributions are welcome! Please read our [Code of Conduct](https://github.com/adafruit/Adafruit_SGP40/blob/master/CODE_OF_CONDUCT.md>)
|
||||
before contributing to help this project stay welcoming.
|
||||
|
||||
## Documentation and doxygen
|
||||
Documentation is produced by doxygen. Contributions should include documentation for any new code added.
|
||||
|
||||
Some examples of how to use doxygen can be found in these guide pages:
|
||||
|
||||
https://learn.adafruit.com/the-well-automated-arduino-library/doxygen
|
||||
|
||||
https://learn.adafruit.com/the-well-automated-arduino-library/doxygen-tips
|
||||
|
||||
## Formatting and clang-format
|
||||
This library uses [`clang-format`](https://releases.llvm.org/download.html) to standardize the formatting of `.cpp` and `.h` files.
|
||||
Contributions should be formatted using `clang-format`:
|
||||
|
||||
The `-i` flag will make the changes to the file.
|
||||
```bash
|
||||
clang-format -i *.cpp *.h
|
||||
```
|
||||
If you prefer to make the changes yourself, running `clang-format` without the `-i` flag will print out a formatted version of the file. You can save this to a file and diff it against the original to see the changes.
|
||||
|
||||
Note that the formatting output by `clang-format` is what the automated formatting checker will expect. Any diffs from this formatting will result in a failed build until they are addressed. Using the `-i` flag is highly recommended.
|
||||
|
||||
### clang-format resources
|
||||
* [Binary builds and source available on the LLVM downloads page](https://releases.llvm.org/download.html)
|
||||
* [Documentation and IDE integration](https://clang.llvm.org/docs/ClangFormat.html)
|
||||
|
||||
## About this Driver
|
||||
Written by Limor Fried for Adafruit Industries.
|
||||
BSD license, check license.txt for more information
|
||||
All text above must be included in any redistribution
|
|
@ -0,0 +1,127 @@
|
|||
# Adafruit Community Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and leaders pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, gender identity and expression, level or type of
|
||||
experience, education, socio-economic status, nationality, personal appearance,
|
||||
race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
We are committed to providing a friendly, safe and welcoming environment for
|
||||
all.
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Be kind and courteous to others
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Collaborating with other community members
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and sexual attention or advances
|
||||
* The use of inappropriate images, including in a community member's avatar
|
||||
* The use of inappropriate language, including in a community member's nickname
|
||||
* Any spamming, flaming, baiting or other attention-stealing behavior
|
||||
* Excessive or unwelcome helping; answering outside the scope of the question
|
||||
asked
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate
|
||||
|
||||
The goal of the standards and moderation guidelines outlined here is to build
|
||||
and maintain a respectful community. We ask that you don’t just aim to be
|
||||
"technically unimpeachable", but rather try to be your best self.
|
||||
|
||||
We value many things beyond technical expertise, including collaboration and
|
||||
supporting others within our community. Providing a positive experience for
|
||||
other community members can have a much more significant impact than simply
|
||||
providing the correct answer.
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project leaders are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project leaders have the right and responsibility to remove, edit, or
|
||||
reject messages, comments, commits, code, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any community member for other behaviors that they deem
|
||||
inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Moderation
|
||||
|
||||
Instances of behaviors that violate the Adafruit Community Code of Conduct
|
||||
may be reported by any member of the community. Community members are
|
||||
encouraged to report these situations, including situations they witness
|
||||
involving other community members.
|
||||
|
||||
You may report in the following ways:
|
||||
|
||||
In any situation, you may send an email to <support@adafruit.com>.
|
||||
|
||||
On the Adafruit Discord, you may send an open message from any channel
|
||||
to all Community Helpers by tagging @community helpers. You may also send an
|
||||
open message from any channel, or a direct message to @kattni#1507,
|
||||
@tannewt#4653, @Dan Halbert#1614, @cater#2442, @sommersoft#0222, or
|
||||
@Andon#8175.
|
||||
|
||||
Email and direct message reports will be kept confidential.
|
||||
|
||||
In situations on Discord where the issue is particularly egregious, possibly
|
||||
illegal, requires immediate action, or violates the Discord terms of service,
|
||||
you should also report the message directly to Discord.
|
||||
|
||||
These are the steps for upholding our community’s standards of conduct.
|
||||
|
||||
1. Any member of the community may report any situation that violates the
|
||||
Adafruit Community Code of Conduct. All reports will be reviewed and
|
||||
investigated.
|
||||
2. If the behavior is an egregious violation, the community member who
|
||||
committed the violation may be banned immediately, without warning.
|
||||
3. Otherwise, moderators will first respond to such behavior with a warning.
|
||||
4. Moderators follow a soft "three strikes" policy - the community member may
|
||||
be given another chance, if they are receptive to the warning and change their
|
||||
behavior.
|
||||
5. If the community member is unreceptive or unreasonable when warned by a
|
||||
moderator, or the warning goes unheeded, they may be banned for a first or
|
||||
second offense. Repeated offenses will result in the community member being
|
||||
banned.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct and the enforcement policies listed above apply to all
|
||||
Adafruit Community venues. This includes but is not limited to any community
|
||||
spaces (both public and private), the entire Adafruit Discord server, and
|
||||
Adafruit GitHub repositories. Examples of Adafruit Community spaces include
|
||||
but are not limited to meet-ups, audio chats on the Adafruit Discord, or
|
||||
interaction at a conference.
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. As a community
|
||||
member, you are representing our community, and are expected to behave
|
||||
accordingly.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 1.4, available at
|
||||
<https://www.contributor-covenant.org/version/1/4/code-of-conduct.html>,
|
||||
and the [Rust Code of Conduct](https://www.rust-lang.org/en-US/conduct.html).
|
||||
|
||||
For other projects adopting the Adafruit Community Code of
|
||||
Conduct, please contact the maintainers of those projects for enforcement.
|
||||
If you wish to use this code of conduct for your own project, consider
|
||||
explicitly mentioning your moderation policy or making a copy with your
|
||||
own moderation policy so as to avoid confusion.
|
|
@ -0,0 +1,48 @@
|
|||
#include "Adafruit_SGP40.h"
|
||||
#include "Adafruit_SHT31.h"
|
||||
|
||||
Adafruit_SGP40 sgp;
|
||||
Adafruit_SHT31 sht31;
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
while (!Serial) { delay(10); } // Wait for serial console to open!
|
||||
|
||||
Serial.println("SGP40 test with SHT31 compensation");
|
||||
|
||||
if (! sgp.begin()){
|
||||
Serial.println("SGP40 sensor not found :(");
|
||||
while (1);
|
||||
}
|
||||
|
||||
if (! sht31.begin(0x44)) { // Set to 0x45 for alternate i2c addr
|
||||
Serial.println("Couldn't find SHT31");
|
||||
while (1);
|
||||
}
|
||||
|
||||
Serial.print("Found SHT3x + SGP40 serial #");
|
||||
Serial.print(sgp.serialnumber[0], HEX);
|
||||
Serial.print(sgp.serialnumber[1], HEX);
|
||||
Serial.println(sgp.serialnumber[2], HEX);
|
||||
}
|
||||
|
||||
int counter = 0;
|
||||
void loop() {
|
||||
uint16_t sraw;
|
||||
int32_t voc_index;
|
||||
|
||||
float t = sht31.readTemperature();
|
||||
Serial.print("Temp *C = "); Serial.print(t); Serial.print("\t\t");
|
||||
float h = sht31.readHumidity();
|
||||
Serial.print("Hum. % = "); Serial.println(h);
|
||||
|
||||
sraw = sgp.measureRaw(t, h);
|
||||
Serial.print("Raw measurement: ");
|
||||
Serial.println(sraw);
|
||||
|
||||
voc_index = sgp.measureVocIndex(t, h);
|
||||
Serial.print("Voc Index: ");
|
||||
Serial.println(voc_index);
|
||||
|
||||
delay(1000);
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
#include <Wire.h>
|
||||
#include "Adafruit_SGP40.h"
|
||||
|
||||
Adafruit_SGP40 sgp;
|
||||
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
while (!Serial) { delay(10); } // Wait for serial console to open!
|
||||
|
||||
Serial.println("SGP40 test");
|
||||
|
||||
if (! sgp.begin()){
|
||||
Serial.println("Sensor not found :(");
|
||||
while (1);
|
||||
}
|
||||
Serial.print("Found SGP40 serial #");
|
||||
Serial.print(sgp.serialnumber[0], HEX);
|
||||
Serial.print(sgp.serialnumber[1], HEX);
|
||||
Serial.println(sgp.serialnumber[2], HEX);
|
||||
}
|
||||
|
||||
int counter = 0;
|
||||
void loop() {
|
||||
uint16_t raw;
|
||||
|
||||
raw = sgp.measureRaw();
|
||||
|
||||
Serial.print("Measurement: ");
|
||||
Serial.println(raw);
|
||||
delay(1000);
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
name=Adafruit SGP40 Sensor
|
||||
version=1.1.0
|
||||
author=Adafruit
|
||||
maintainer=Adafruit <info@adafruit.com>
|
||||
sentence=This is an Arduino library for the Adafruit SGP40 Gas / Air Quality Sensor
|
||||
paragraph=This is an Arduino library for the Adafruit SGP40 Gas / Air Quality Sensor
|
||||
category=Sensors
|
||||
url=https://github.com/adafruit/Adafruit_SGP40
|
||||
architectures=*
|
||||
depends=Adafruit BusIO, Adafruit SHT31 Library
|
|
@ -0,0 +1,26 @@
|
|||
Software License Agreement (BSD License)
|
||||
|
||||
Copyright (c) 2012, Adafruit Industries
|
||||
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 holders 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 ''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 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,230 @@
|
|||
/*!
|
||||
* @file Adafruit_SGP40.cpp
|
||||
*
|
||||
* @mainpage Adafruit SGP40 gas sensor driver
|
||||
*
|
||||
* @section intro_sec Introduction
|
||||
*
|
||||
* This is the documentation for Adafruit's SGP40 driver for the
|
||||
* Arduino platform. It is designed specifically to work with the
|
||||
* Adafruit SGP40 breakout: http://www.adafruit.com/products/4829
|
||||
*
|
||||
* These sensors use I2C to communicate, 2 pins (SCL+SDA) are required
|
||||
* to interface with the breakout.
|
||||
*
|
||||
* Adafruit invests time and resources providing this open source code,
|
||||
* please support Adafruit and open-source hardware by purchasing
|
||||
* products from Adafruit!
|
||||
*
|
||||
*
|
||||
* @section author Author
|
||||
* Written by Ladyada for Adafruit Industries.
|
||||
*
|
||||
* @section license License
|
||||
* BSD license, all text here must be included in any redistribution.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "Adafruit_SGP40.h"
|
||||
#include "Arduino.h"
|
||||
|
||||
//#define I2C_DEBUG
|
||||
|
||||
/*!
|
||||
* @brief Instantiates a new SGP40 class
|
||||
*/
|
||||
Adafruit_SGP40::Adafruit_SGP40() {}
|
||||
|
||||
/*!
|
||||
* @brief Setups the hardware and detects a valid SGP40. Initializes I2C
|
||||
* then reads the serialnumber and checks that we are talking to an
|
||||
* SGP40
|
||||
* @param theWire
|
||||
* Optional pointer to I2C interface, otherwise use Wire
|
||||
* @return True if SGP40 found on I2C, False if something went wrong!
|
||||
*/
|
||||
boolean Adafruit_SGP40::begin(TwoWire *theWire) {
|
||||
if (i2c_dev) {
|
||||
delete i2c_dev; // remove old interface
|
||||
}
|
||||
|
||||
i2c_dev = new Adafruit_I2CDevice(SGP40_I2CADDR_DEFAULT, theWire);
|
||||
|
||||
if (!i2c_dev->begin()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t command[2];
|
||||
command[0] = 0x36;
|
||||
command[1] = 0x82;
|
||||
if (!readWordFromCommand(command, 2, 10, serialnumber, 3))
|
||||
return false;
|
||||
|
||||
uint16_t featureset;
|
||||
command[0] = 0x20;
|
||||
command[1] = 0x2F;
|
||||
if (!readWordFromCommand(command, 2, 10, &featureset, 1))
|
||||
return false;
|
||||
// Serial.print("Featureset 0x"); Serial.println(featureset, HEX);
|
||||
|
||||
VocAlgorithm_init(&voc_algorithm_params);
|
||||
|
||||
return selfTest();
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Commands the sensor to perform a soft reset using the "General
|
||||
* Call" mode. Take note that this is not sensor specific and all devices that
|
||||
* support the General Call mode on the on the same I2C bus will perform this.
|
||||
*
|
||||
* @return True if command completed successfully, false if something went
|
||||
* wrong!
|
||||
*/
|
||||
boolean Adafruit_SGP40::softReset(void) {
|
||||
uint8_t command[2];
|
||||
command[0] = 0x00;
|
||||
command[1] = 0x06;
|
||||
return readWordFromCommand(command, 2, 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Request the sensor to turn off heater to lower curent consumption.
|
||||
* Launching a measurement automatically wakes up the sensor again.
|
||||
*
|
||||
* @return true: success false: failure
|
||||
*/
|
||||
bool Adafruit_SGP40::heaterOff(void) {
|
||||
uint8_t command[2];
|
||||
command[0] = 0x36;
|
||||
command[1] = 0x15;
|
||||
return i2c_dev->write(command, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Request the sensor to perform a self-test, returning the result
|
||||
*
|
||||
* @return true: success false:failure
|
||||
*/
|
||||
bool Adafruit_SGP40::selfTest(void) {
|
||||
uint8_t command[2];
|
||||
uint16_t reply;
|
||||
|
||||
command[0] = 0x28;
|
||||
command[1] = 0x0E;
|
||||
|
||||
if (!readWordFromCommand(command, 2, 250, &reply, 1))
|
||||
return false;
|
||||
if ((reply == 0xD400)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Combined the measured gasses, temperature, and humidity
|
||||
* to calculate the VOC Index
|
||||
*
|
||||
* @param temperature The measured temperature in degrees C
|
||||
* @param humidity The measured relative humidity in % rH
|
||||
* @return int32_t The VOC Index
|
||||
*/
|
||||
int32_t Adafruit_SGP40::measureVocIndex(float temperature, float humidity) {
|
||||
int32_t voc_index;
|
||||
uint16_t sraw = measureRaw(temperature, humidity);
|
||||
|
||||
VocAlgorithm_process(&voc_algorithm_params, sraw, &voc_index);
|
||||
return voc_index;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the raw gas measurement
|
||||
*
|
||||
* @param temperature The measured temperature in degrees C
|
||||
* @param humidity The measured relative humidity in % rH
|
||||
* @return uint16_t The current raw gas measurement
|
||||
*/
|
||||
uint16_t Adafruit_SGP40::measureRaw(float temperature, float humidity) {
|
||||
uint8_t command[8];
|
||||
uint16_t reply;
|
||||
|
||||
command[0] = 0x26;
|
||||
command[1] = 0x0F;
|
||||
|
||||
uint16_t rhticks = (uint16_t)((humidity * 65535) / 100 + 0.5);
|
||||
command[2] = rhticks >> 8;
|
||||
command[3] = rhticks & 0xFF;
|
||||
command[4] = generateCRC(command + 2, 2);
|
||||
uint16_t tempticks = (uint16_t)(((temperature + 45) * 65535) / 175);
|
||||
command[5] = tempticks >> 8;
|
||||
command[6] = tempticks & 0xFF;
|
||||
command[7] = generateCRC(command + 5, 2);
|
||||
;
|
||||
|
||||
if (!readWordFromCommand(command, 8, 250, &reply, 1))
|
||||
return 0x0;
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief I2C low level interfacing
|
||||
*/
|
||||
|
||||
bool Adafruit_SGP40::readWordFromCommand(uint8_t command[],
|
||||
uint8_t commandLength,
|
||||
uint16_t delayms, uint16_t *readdata,
|
||||
uint8_t readlen) {
|
||||
|
||||
if (!i2c_dev->write(command, commandLength)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
delay(delayms);
|
||||
|
||||
if (readlen == 0)
|
||||
return true;
|
||||
|
||||
uint8_t replylen = readlen * (SGP40_WORD_LEN + 1);
|
||||
uint8_t replybuffer[replylen];
|
||||
|
||||
if (!i2c_dev->read(replybuffer, replylen)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < readlen; i++) {
|
||||
uint8_t crc = generateCRC(replybuffer + i * 3, 2);
|
||||
#ifdef I2C_DEBUG
|
||||
Serial.print("\t\tCRC calced: 0x");
|
||||
Serial.print(crc, HEX);
|
||||
Serial.print(" vs. 0x");
|
||||
Serial.println(replybuffer[i * 3 + 2], HEX);
|
||||
#endif
|
||||
if (crc != replybuffer[i * 3 + 2])
|
||||
return false;
|
||||
// success! store it
|
||||
readdata[i] = replybuffer[i * 3];
|
||||
readdata[i] <<= 8;
|
||||
readdata[i] |= replybuffer[i * 3 + 1];
|
||||
#ifdef I2C_DEBUG
|
||||
Serial.print("\t\tRead: 0x");
|
||||
Serial.println(readdata[i], HEX);
|
||||
#endif
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t Adafruit_SGP40::generateCRC(uint8_t *data, uint8_t datalen) {
|
||||
// calculates 8-Bit checksum with given polynomial
|
||||
uint8_t crc = SGP40_CRC8_INIT;
|
||||
|
||||
for (uint8_t i = 0; i < datalen; i++) {
|
||||
crc ^= data[i];
|
||||
for (uint8_t b = 0; b < 8; b++) {
|
||||
if (crc & 0x80)
|
||||
crc = (crc << 1) ^ SGP40_CRC8_POLYNOMIAL;
|
||||
else
|
||||
crc <<= 1;
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
/*!
|
||||
* @file Adafruit_SGP40.h
|
||||
*
|
||||
* This is the documentation for Adafruit's SGP40 driver for the
|
||||
* Arduino platform. It is designed specifically to work with the
|
||||
* Adafruit SGP40 breakout: http://www.adafruit.com/products/4829
|
||||
*
|
||||
* These sensors use I2C to communicate, 2 pins (SCL+SDA) are required
|
||||
* to interface with the breakout.
|
||||
*
|
||||
* Adafruit invests time and resources providing this open source code,
|
||||
* please support Adafruit and open-source hardware by purchasing
|
||||
* products from Adafruit!
|
||||
*
|
||||
* Written by Ladyada for Adafruit Industries.
|
||||
*
|
||||
* BSD license, all text here must be included in any redistribution.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef ADAFRUIT_SGP40_H
|
||||
#define ADAFRUIT_SGP40_H
|
||||
|
||||
#include "Arduino.h"
|
||||
#include <Adafruit_BusIO_Register.h>
|
||||
#include <Adafruit_I2CDevice.h>
|
||||
extern "C" {
|
||||
#include "sensirion_arch_config.h"
|
||||
#include "sensirion_voc_algorithm.h"
|
||||
};
|
||||
|
||||
// the i2c address
|
||||
#define SGP40_I2CADDR_DEFAULT 0x59 ///< SGP40 has only one I2C address
|
||||
|
||||
// commands and constants
|
||||
#define SGP40_FEATURESET 0x0020 ///< The required set for this library
|
||||
#define SGP40_CRC8_POLYNOMIAL 0x31 ///< Seed for SGP40's CRC polynomial
|
||||
#define SGP40_CRC8_INIT 0xFF ///< Init value for CRC
|
||||
#define SGP40_WORD_LEN 2 ///< 2 bytes per word
|
||||
|
||||
/*!
|
||||
* @brief Class that stores state and functions for interacting with
|
||||
* SGP40 Gas Sensor
|
||||
*/
|
||||
class Adafruit_SGP40 {
|
||||
public:
|
||||
Adafruit_SGP40();
|
||||
bool begin(TwoWire *theWire = &Wire);
|
||||
bool selfTest(void);
|
||||
|
||||
bool softReset();
|
||||
bool heaterOff();
|
||||
uint16_t measureRaw(float temperature = 25, float humidity = 50);
|
||||
int32_t measureVocIndex(float temperature = 25, float humidity = 50);
|
||||
|
||||
/** The 48-bit serial number, this value is set when you call {@link begin()}
|
||||
* **/
|
||||
uint16_t serialnumber[3];
|
||||
|
||||
private:
|
||||
Adafruit_I2CDevice *i2c_dev = NULL; ///< Pointer to I2C bus interface
|
||||
void write(uint8_t address, uint8_t *data, uint8_t n);
|
||||
void read(uint8_t address, uint8_t *data, uint8_t n);
|
||||
bool readWordFromCommand(uint8_t command[], uint8_t commandLength,
|
||||
uint16_t delayms, uint16_t *readdata = NULL,
|
||||
uint8_t readlen = 0);
|
||||
uint8_t generateCRC(uint8_t data[], uint8_t datalen);
|
||||
|
||||
VocAlgorithmParams voc_algorithm_params;
|
||||
};
|
||||
#endif // ndef ADAFRUIT_SGP40_H
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* Copyright (c) 2019, 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 SENSIRION_ARCH_CONFIG_H
|
||||
#define SENSIRION_ARCH_CONFIG_H
|
||||
|
||||
/**
|
||||
* If your platform does not provide the library stdlib.h you have to remove the
|
||||
* include and define NULL yourself (see below).
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
|
||||
/**
|
||||
* #ifndef NULL
|
||||
* #define NULL ((void *)0)
|
||||
* #endif
|
||||
*/
|
||||
|
||||
/**
|
||||
* If your platform does not provide the library stdint.h you have to
|
||||
* define the integral types yourself (see below).
|
||||
*/
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* Typedef section for types commonly defined in <stdint.h>
|
||||
* If your system does not provide stdint headers, please define them
|
||||
* accordingly. Please make sure to define int64_t and uint64_t.
|
||||
*/
|
||||
/* typedef unsigned long long int uint64_t;
|
||||
* typedef long long int int64_t;
|
||||
* typedef long int32_t;
|
||||
* typedef unsigned long uint32_t;
|
||||
* typedef short int16_t;
|
||||
* typedef unsigned short uint16_t;
|
||||
* typedef char int8_t;
|
||||
* typedef unsigned char uint8_t; */
|
||||
|
||||
#ifndef __cplusplus
|
||||
|
||||
/**
|
||||
* If your platform doesn't define the bool type we define it as int. Depending
|
||||
* on your system update the definition below.
|
||||
*/
|
||||
#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 */
|
||||
|
||||
/**
|
||||
* The clock period of the i2c bus in microseconds. Increase this, if your GPIO
|
||||
* ports cannot support a 200 kHz output rate. (2 * 1 / 10usec == 200Khz)
|
||||
*
|
||||
* This is only relevant for the sw-i2c HAL (bit-banging on GPIO pins). The
|
||||
* pulse length is half the clock period, the number should thus be even.
|
||||
*/
|
||||
#define SENSIRION_I2C_CLOCK_PERIOD_USEC 10
|
||||
|
||||
#endif /* SENSIRION_ARCH_CONFIG_H */
|
|
@ -0,0 +1,805 @@
|
|||
/*
|
||||
* 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 "sensirion_voc_algorithm.h"
|
||||
|
||||
/* The fixed point arithmetic parts of this code were originally created by
|
||||
* https://github.com/PetteriAimonen/libfixmath
|
||||
*/
|
||||
|
||||
/*!< the maximum value of fix16_t */
|
||||
#define FIX16_MAXIMUM 0x7FFFFFFF
|
||||
/*!< the minimum value of fix16_t */
|
||||
#define FIX16_MINIMUM 0x80000000
|
||||
/*!< the value used to indicate overflows when FIXMATH_NO_OVERFLOW is not
|
||||
* specified */
|
||||
#define FIX16_OVERFLOW 0x80000000
|
||||
/*!< fix16_t value of 1 */
|
||||
#define FIX16_ONE 0x00010000
|
||||
|
||||
static inline fix16_t fix16_from_int(int32_t a) {
|
||||
return a * FIX16_ONE;
|
||||
}
|
||||
|
||||
static inline int32_t fix16_cast_to_int(fix16_t a) {
|
||||
return (a >= 0) ? (a >> 16) : -((-a) >> 16);
|
||||
}
|
||||
|
||||
/*! Multiplies the two given fix16_t's and returns the result. */
|
||||
static fix16_t fix16_mul(fix16_t inArg0, fix16_t inArg1);
|
||||
|
||||
/*! Divides the first given fix16_t by the second and returns the result. */
|
||||
static fix16_t fix16_div(fix16_t inArg0, fix16_t inArg1);
|
||||
|
||||
/*! Returns the square root of the given fix16_t. */
|
||||
static fix16_t fix16_sqrt(fix16_t inValue);
|
||||
|
||||
/*! Returns the exponent (e^) of the given fix16_t. */
|
||||
static fix16_t fix16_exp(fix16_t inValue);
|
||||
|
||||
static fix16_t fix16_mul(fix16_t inArg0, fix16_t inArg1) {
|
||||
// Each argument is divided to 16-bit parts.
|
||||
// AB
|
||||
// * CD
|
||||
// -----------
|
||||
// BD 16 * 16 -> 32 bit products
|
||||
// CB
|
||||
// AD
|
||||
// AC
|
||||
// |----| 64 bit product
|
||||
uint32_t absArg0 = (uint32_t)((inArg0 >= 0) ? inArg0 : (-inArg0));
|
||||
uint32_t absArg1 = (uint32_t)((inArg1 >= 0) ? inArg1 : (-inArg1));
|
||||
uint32_t A = (absArg0 >> 16), C = (absArg1 >> 16);
|
||||
uint32_t B = (absArg0 & 0xFFFF), D = (absArg1 & 0xFFFF);
|
||||
|
||||
uint32_t AC = A * C;
|
||||
uint32_t AD_CB = A * D + C * B;
|
||||
uint32_t BD = B * D;
|
||||
|
||||
uint32_t product_hi = AC + (AD_CB >> 16);
|
||||
|
||||
// Handle carry from lower 32 bits to upper part of result.
|
||||
uint32_t ad_cb_temp = AD_CB << 16;
|
||||
uint32_t product_lo = BD + ad_cb_temp;
|
||||
if (product_lo < BD)
|
||||
product_hi++;
|
||||
|
||||
#ifndef FIXMATH_NO_OVERFLOW
|
||||
// The upper 17 bits should all be zero.
|
||||
if (product_hi >> 15)
|
||||
return (fix16_t)FIX16_OVERFLOW;
|
||||
#endif
|
||||
|
||||
#ifdef FIXMATH_NO_ROUNDING
|
||||
fix16_t result = (fix16_t)((product_hi << 16) | (product_lo >> 16));
|
||||
if ((inArg0 < 0) != (inArg1 < 0))
|
||||
result = -result;
|
||||
return result;
|
||||
#else
|
||||
// Adding 0x8000 (= 0.5) and then using right shift
|
||||
// achieves proper rounding to result.
|
||||
// Handle carry from lower to upper part.
|
||||
uint32_t product_lo_tmp = product_lo;
|
||||
product_lo += 0x8000;
|
||||
if (product_lo < product_lo_tmp)
|
||||
product_hi++;
|
||||
|
||||
// Discard the lowest 16 bits and convert back to signed result.
|
||||
fix16_t result = (fix16_t)((product_hi << 16) | (product_lo >> 16));
|
||||
if ((inArg0 < 0) != (inArg1 < 0))
|
||||
result = -result;
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
|
||||
static fix16_t fix16_div(fix16_t a, fix16_t b) {
|
||||
// This uses the basic binary restoring division algorithm.
|
||||
// It appears to be faster to do the whole division manually than
|
||||
// trying to compose a 64-bit divide out of 32-bit divisions on
|
||||
// platforms without hardware divide.
|
||||
|
||||
if (b == 0)
|
||||
return (fix16_t)FIX16_MINIMUM;
|
||||
|
||||
uint32_t remainder = (uint32_t)((a >= 0) ? a : (-a));
|
||||
uint32_t divider = (uint32_t)((b >= 0) ? b : (-b));
|
||||
|
||||
uint32_t quotient = 0;
|
||||
uint32_t bit = 0x10000;
|
||||
|
||||
/* The algorithm requires D >= R */
|
||||
while (divider < remainder) {
|
||||
divider <<= 1;
|
||||
bit <<= 1;
|
||||
}
|
||||
|
||||
#ifndef FIXMATH_NO_OVERFLOW
|
||||
if (!bit)
|
||||
return (fix16_t)FIX16_OVERFLOW;
|
||||
#endif
|
||||
|
||||
if (divider & 0x80000000) {
|
||||
// Perform one step manually to avoid overflows later.
|
||||
// We know that divider's bottom bit is 0 here.
|
||||
if (remainder >= divider) {
|
||||
quotient |= bit;
|
||||
remainder -= divider;
|
||||
}
|
||||
divider >>= 1;
|
||||
bit >>= 1;
|
||||
}
|
||||
|
||||
/* Main division loop */
|
||||
while (bit && remainder) {
|
||||
if (remainder >= divider) {
|
||||
quotient |= bit;
|
||||
remainder -= divider;
|
||||
}
|
||||
|
||||
remainder <<= 1;
|
||||
bit >>= 1;
|
||||
}
|
||||
|
||||
#ifndef FIXMATH_NO_ROUNDING
|
||||
if (remainder >= divider) {
|
||||
quotient++;
|
||||
}
|
||||
#endif
|
||||
|
||||
fix16_t result = (fix16_t)quotient;
|
||||
|
||||
/* Figure out the sign of result */
|
||||
if ((a < 0) != (b < 0)) {
|
||||
#ifndef FIXMATH_NO_OVERFLOW
|
||||
if (result == FIX16_MINIMUM)
|
||||
return (fix16_t)FIX16_OVERFLOW;
|
||||
#endif
|
||||
|
||||
result = -result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static fix16_t fix16_sqrt(fix16_t x) {
|
||||
// It is assumed that x is not negative
|
||||
|
||||
uint32_t num = (uint32_t)x;
|
||||
uint32_t result = 0;
|
||||
uint32_t bit;
|
||||
uint8_t n;
|
||||
|
||||
bit = (uint32_t)1 << 30;
|
||||
while (bit > num)
|
||||
bit >>= 2;
|
||||
|
||||
// The main part is executed twice, in order to avoid
|
||||
// using 64 bit values in computations.
|
||||
for (n = 0; n < 2; n++) {
|
||||
// First we get the top 24 bits of the answer.
|
||||
while (bit) {
|
||||
if (num >= result + bit) {
|
||||
num -= result + bit;
|
||||
result = (result >> 1) + bit;
|
||||
} else {
|
||||
result = (result >> 1);
|
||||
}
|
||||
bit >>= 2;
|
||||
}
|
||||
|
||||
if (n == 0) {
|
||||
// Then process it again to get the lowest 8 bits.
|
||||
if (num > 65535) {
|
||||
// The remainder 'num' is too large to be shifted left
|
||||
// by 16, so we have to add 1 to result manually and
|
||||
// adjust 'num' accordingly.
|
||||
// num = a - (result + 0.5)^2
|
||||
// = num + result^2 - (result + 0.5)^2
|
||||
// = num - result - 0.5
|
||||
num -= result;
|
||||
num = (num << 16) - 0x8000;
|
||||
result = (result << 16) + 0x8000;
|
||||
} else {
|
||||
num <<= 16;
|
||||
result <<= 16;
|
||||
}
|
||||
|
||||
bit = 1 << 14;
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef FIXMATH_NO_ROUNDING
|
||||
// Finally, if next bit would have been 1, round the result upwards.
|
||||
if (num > result) {
|
||||
result++;
|
||||
}
|
||||
#endif
|
||||
|
||||
return (fix16_t)result;
|
||||
}
|
||||
|
||||
static fix16_t fix16_exp(fix16_t x) {
|
||||
// Function to approximate exp(); optimized more for code size than speed
|
||||
|
||||
// exp(x) for x = +/- {1, 1/8, 1/64, 1/512}
|
||||
#define NUM_EXP_VALUES 4
|
||||
static const fix16_t exp_pos_values[NUM_EXP_VALUES] = {
|
||||
F16(2.7182818), F16(1.1331485), F16(1.0157477), F16(1.0019550)};
|
||||
static const fix16_t exp_neg_values[NUM_EXP_VALUES] = {
|
||||
F16(0.3678794), F16(0.8824969), F16(0.9844964), F16(0.9980488)};
|
||||
const fix16_t* exp_values;
|
||||
|
||||
fix16_t res, arg;
|
||||
uint16_t i;
|
||||
|
||||
if (x >= F16(10.3972))
|
||||
return FIX16_MAXIMUM;
|
||||
if (x <= F16(-11.7835))
|
||||
return 0;
|
||||
|
||||
if (x < 0) {
|
||||
x = -x;
|
||||
exp_values = exp_neg_values;
|
||||
} else {
|
||||
exp_values = exp_pos_values;
|
||||
}
|
||||
|
||||
res = FIX16_ONE;
|
||||
arg = FIX16_ONE;
|
||||
for (i = 0; i < NUM_EXP_VALUES; i++) {
|
||||
while (x >= arg) {
|
||||
res = fix16_mul(res, exp_values[i]);
|
||||
x -= arg;
|
||||
}
|
||||
arg >>= 3;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static void VocAlgorithm__init_instances(VocAlgorithmParams* params);
|
||||
static void
|
||||
VocAlgorithm__mean_variance_estimator__init(VocAlgorithmParams* params);
|
||||
static void VocAlgorithm__mean_variance_estimator___init_instances(
|
||||
VocAlgorithmParams* params);
|
||||
static void VocAlgorithm__mean_variance_estimator__set_parameters(
|
||||
VocAlgorithmParams* params, fix16_t std_initial,
|
||||
fix16_t tau_mean_variance_hours, fix16_t gating_max_duration_minutes);
|
||||
static void
|
||||
VocAlgorithm__mean_variance_estimator__set_states(VocAlgorithmParams* params,
|
||||
fix16_t mean, fix16_t std,
|
||||
fix16_t uptime_gamma);
|
||||
static fix16_t
|
||||
VocAlgorithm__mean_variance_estimator__get_std(VocAlgorithmParams* params);
|
||||
static fix16_t
|
||||
VocAlgorithm__mean_variance_estimator__get_mean(VocAlgorithmParams* params);
|
||||
static void VocAlgorithm__mean_variance_estimator___calculate_gamma(
|
||||
VocAlgorithmParams* params, fix16_t voc_index_from_prior);
|
||||
static void VocAlgorithm__mean_variance_estimator__process(
|
||||
VocAlgorithmParams* params, fix16_t sraw, fix16_t voc_index_from_prior);
|
||||
static void VocAlgorithm__mean_variance_estimator___sigmoid__init(
|
||||
VocAlgorithmParams* params);
|
||||
static void VocAlgorithm__mean_variance_estimator___sigmoid__set_parameters(
|
||||
VocAlgorithmParams* params, fix16_t L, fix16_t X0, fix16_t K);
|
||||
static fix16_t VocAlgorithm__mean_variance_estimator___sigmoid__process(
|
||||
VocAlgorithmParams* params, fix16_t sample);
|
||||
static void VocAlgorithm__mox_model__init(VocAlgorithmParams* params);
|
||||
static void VocAlgorithm__mox_model__set_parameters(VocAlgorithmParams* params,
|
||||
fix16_t SRAW_STD,
|
||||
fix16_t SRAW_MEAN);
|
||||
static fix16_t VocAlgorithm__mox_model__process(VocAlgorithmParams* params,
|
||||
fix16_t sraw);
|
||||
static void VocAlgorithm__sigmoid_scaled__init(VocAlgorithmParams* params);
|
||||
static void
|
||||
VocAlgorithm__sigmoid_scaled__set_parameters(VocAlgorithmParams* params,
|
||||
fix16_t offset);
|
||||
static fix16_t VocAlgorithm__sigmoid_scaled__process(VocAlgorithmParams* params,
|
||||
fix16_t sample);
|
||||
static void VocAlgorithm__adaptive_lowpass__init(VocAlgorithmParams* params);
|
||||
static void
|
||||
VocAlgorithm__adaptive_lowpass__set_parameters(VocAlgorithmParams* params);
|
||||
static fix16_t
|
||||
VocAlgorithm__adaptive_lowpass__process(VocAlgorithmParams* params,
|
||||
fix16_t sample);
|
||||
|
||||
void VocAlgorithm_init(VocAlgorithmParams* params) {
|
||||
|
||||
params->mVoc_Index_Offset = F16(VocAlgorithm_VOC_INDEX_OFFSET_DEFAULT);
|
||||
params->mTau_Mean_Variance_Hours =
|
||||
F16(VocAlgorithm_TAU_MEAN_VARIANCE_HOURS);
|
||||
params->mGating_Max_Duration_Minutes =
|
||||
F16(VocAlgorithm_GATING_MAX_DURATION_MINUTES);
|
||||
params->mSraw_Std_Initial = F16(VocAlgorithm_SRAW_STD_INITIAL);
|
||||
params->mUptime = F16(0.);
|
||||
params->mSraw = F16(0.);
|
||||
params->mVoc_Index = 0;
|
||||
VocAlgorithm__init_instances(params);
|
||||
}
|
||||
|
||||
static void VocAlgorithm__init_instances(VocAlgorithmParams* params) {
|
||||
|
||||
VocAlgorithm__mean_variance_estimator__init(params);
|
||||
VocAlgorithm__mean_variance_estimator__set_parameters(
|
||||
params, params->mSraw_Std_Initial, params->mTau_Mean_Variance_Hours,
|
||||
params->mGating_Max_Duration_Minutes);
|
||||
VocAlgorithm__mox_model__init(params);
|
||||
VocAlgorithm__mox_model__set_parameters(
|
||||
params, VocAlgorithm__mean_variance_estimator__get_std(params),
|
||||
VocAlgorithm__mean_variance_estimator__get_mean(params));
|
||||
VocAlgorithm__sigmoid_scaled__init(params);
|
||||
VocAlgorithm__sigmoid_scaled__set_parameters(params,
|
||||
params->mVoc_Index_Offset);
|
||||
VocAlgorithm__adaptive_lowpass__init(params);
|
||||
VocAlgorithm__adaptive_lowpass__set_parameters(params);
|
||||
}
|
||||
|
||||
void VocAlgorithm_get_states(VocAlgorithmParams* params, int32_t* state0,
|
||||
int32_t* state1) {
|
||||
|
||||
*state0 = VocAlgorithm__mean_variance_estimator__get_mean(params);
|
||||
*state1 = VocAlgorithm__mean_variance_estimator__get_std(params);
|
||||
return;
|
||||
}
|
||||
|
||||
void VocAlgorithm_set_states(VocAlgorithmParams* params, int32_t state0,
|
||||
int32_t state1) {
|
||||
|
||||
VocAlgorithm__mean_variance_estimator__set_states(
|
||||
params, state0, state1, F16(VocAlgorithm_PERSISTENCE_UPTIME_GAMMA));
|
||||
params->mSraw = state0;
|
||||
}
|
||||
|
||||
void VocAlgorithm_set_tuning_parameters(VocAlgorithmParams* params,
|
||||
int32_t voc_index_offset,
|
||||
int32_t learning_time_hours,
|
||||
int32_t gating_max_duration_minutes,
|
||||
int32_t std_initial) {
|
||||
|
||||
params->mVoc_Index_Offset = (fix16_from_int(voc_index_offset));
|
||||
params->mTau_Mean_Variance_Hours = (fix16_from_int(learning_time_hours));
|
||||
params->mGating_Max_Duration_Minutes =
|
||||
(fix16_from_int(gating_max_duration_minutes));
|
||||
params->mSraw_Std_Initial = (fix16_from_int(std_initial));
|
||||
VocAlgorithm__init_instances(params);
|
||||
}
|
||||
|
||||
void VocAlgorithm_process(VocAlgorithmParams* params, int32_t sraw,
|
||||
int32_t* voc_index) {
|
||||
|
||||
if ((params->mUptime <= F16(VocAlgorithm_INITIAL_BLACKOUT))) {
|
||||
params->mUptime =
|
||||
(params->mUptime + F16(VocAlgorithm_SAMPLING_INTERVAL));
|
||||
} else {
|
||||
if (((sraw > 0) && (sraw < 65000))) {
|
||||
if ((sraw < 20001)) {
|
||||
sraw = 20001;
|
||||
} else if ((sraw > 52767)) {
|
||||
sraw = 52767;
|
||||
}
|
||||
params->mSraw = (fix16_from_int((sraw - 20000)));
|
||||
}
|
||||
params->mVoc_Index =
|
||||
VocAlgorithm__mox_model__process(params, params->mSraw);
|
||||
params->mVoc_Index =
|
||||
VocAlgorithm__sigmoid_scaled__process(params, params->mVoc_Index);
|
||||
params->mVoc_Index =
|
||||
VocAlgorithm__adaptive_lowpass__process(params, params->mVoc_Index);
|
||||
if ((params->mVoc_Index < F16(0.5))) {
|
||||
params->mVoc_Index = F16(0.5);
|
||||
}
|
||||
if ((params->mSraw > F16(0.))) {
|
||||
VocAlgorithm__mean_variance_estimator__process(
|
||||
params, params->mSraw, params->mVoc_Index);
|
||||
VocAlgorithm__mox_model__set_parameters(
|
||||
params, VocAlgorithm__mean_variance_estimator__get_std(params),
|
||||
VocAlgorithm__mean_variance_estimator__get_mean(params));
|
||||
}
|
||||
}
|
||||
*voc_index = (fix16_cast_to_int((params->mVoc_Index + F16(0.5))));
|
||||
return;
|
||||
}
|
||||
|
||||
static void
|
||||
VocAlgorithm__mean_variance_estimator__init(VocAlgorithmParams* params) {
|
||||
|
||||
VocAlgorithm__mean_variance_estimator__set_parameters(params, F16(0.),
|
||||
F16(0.), F16(0.));
|
||||
VocAlgorithm__mean_variance_estimator___init_instances(params);
|
||||
}
|
||||
|
||||
static void VocAlgorithm__mean_variance_estimator___init_instances(
|
||||
VocAlgorithmParams* params) {
|
||||
|
||||
VocAlgorithm__mean_variance_estimator___sigmoid__init(params);
|
||||
}
|
||||
|
||||
static void VocAlgorithm__mean_variance_estimator__set_parameters(
|
||||
VocAlgorithmParams* params, fix16_t std_initial,
|
||||
fix16_t tau_mean_variance_hours, fix16_t gating_max_duration_minutes) {
|
||||
|
||||
params->m_Mean_Variance_Estimator__Gating_Max_Duration_Minutes =
|
||||
gating_max_duration_minutes;
|
||||
params->m_Mean_Variance_Estimator___Initialized = false;
|
||||
params->m_Mean_Variance_Estimator___Mean = F16(0.);
|
||||
params->m_Mean_Variance_Estimator___Sraw_Offset = F16(0.);
|
||||
params->m_Mean_Variance_Estimator___Std = std_initial;
|
||||
params->m_Mean_Variance_Estimator___Gamma =
|
||||
(fix16_div(F16((VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING *
|
||||
(VocAlgorithm_SAMPLING_INTERVAL / 3600.))),
|
||||
(tau_mean_variance_hours +
|
||||
F16((VocAlgorithm_SAMPLING_INTERVAL / 3600.)))));
|
||||
params->m_Mean_Variance_Estimator___Gamma_Initial_Mean =
|
||||
F16(((VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING *
|
||||
VocAlgorithm_SAMPLING_INTERVAL) /
|
||||
(VocAlgorithm_TAU_INITIAL_MEAN + VocAlgorithm_SAMPLING_INTERVAL)));
|
||||
params->m_Mean_Variance_Estimator___Gamma_Initial_Variance = F16(
|
||||
((VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING *
|
||||
VocAlgorithm_SAMPLING_INTERVAL) /
|
||||
(VocAlgorithm_TAU_INITIAL_VARIANCE + VocAlgorithm_SAMPLING_INTERVAL)));
|
||||
params->m_Mean_Variance_Estimator__Gamma_Mean = F16(0.);
|
||||
params->m_Mean_Variance_Estimator__Gamma_Variance = F16(0.);
|
||||
params->m_Mean_Variance_Estimator___Uptime_Gamma = F16(0.);
|
||||
params->m_Mean_Variance_Estimator___Uptime_Gating = F16(0.);
|
||||
params->m_Mean_Variance_Estimator___Gating_Duration_Minutes = F16(0.);
|
||||
}
|
||||
|
||||
static void
|
||||
VocAlgorithm__mean_variance_estimator__set_states(VocAlgorithmParams* params,
|
||||
fix16_t mean, fix16_t std,
|
||||
fix16_t 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 fix16_t
|
||||
VocAlgorithm__mean_variance_estimator__get_std(VocAlgorithmParams* params) {
|
||||
|
||||
return params->m_Mean_Variance_Estimator___Std;
|
||||
}
|
||||
|
||||
static fix16_t
|
||||
VocAlgorithm__mean_variance_estimator__get_mean(VocAlgorithmParams* params) {
|
||||
|
||||
return (params->m_Mean_Variance_Estimator___Mean +
|
||||
params->m_Mean_Variance_Estimator___Sraw_Offset);
|
||||
}
|
||||
|
||||
static void VocAlgorithm__mean_variance_estimator___calculate_gamma(
|
||||
VocAlgorithmParams* params, fix16_t voc_index_from_prior) {
|
||||
|
||||
fix16_t uptime_limit;
|
||||
fix16_t sigmoid_gamma_mean;
|
||||
fix16_t gamma_mean;
|
||||
fix16_t gating_threshold_mean;
|
||||
fix16_t sigmoid_gating_mean;
|
||||
fix16_t sigmoid_gamma_variance;
|
||||
fix16_t gamma_variance;
|
||||
fix16_t gating_threshold_variance;
|
||||
fix16_t sigmoid_gating_variance;
|
||||
|
||||
uptime_limit = F16((VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__FIX16_MAX -
|
||||
VocAlgorithm_SAMPLING_INTERVAL));
|
||||
if ((params->m_Mean_Variance_Estimator___Uptime_Gamma < uptime_limit)) {
|
||||
params->m_Mean_Variance_Estimator___Uptime_Gamma =
|
||||
(params->m_Mean_Variance_Estimator___Uptime_Gamma +
|
||||
F16(VocAlgorithm_SAMPLING_INTERVAL));
|
||||
}
|
||||
if ((params->m_Mean_Variance_Estimator___Uptime_Gating < uptime_limit)) {
|
||||
params->m_Mean_Variance_Estimator___Uptime_Gating =
|
||||
(params->m_Mean_Variance_Estimator___Uptime_Gating +
|
||||
F16(VocAlgorithm_SAMPLING_INTERVAL));
|
||||
}
|
||||
VocAlgorithm__mean_variance_estimator___sigmoid__set_parameters(
|
||||
params, F16(1.), F16(VocAlgorithm_INIT_DURATION_MEAN),
|
||||
F16(VocAlgorithm_INIT_TRANSITION_MEAN));
|
||||
sigmoid_gamma_mean =
|
||||
VocAlgorithm__mean_variance_estimator___sigmoid__process(
|
||||
params, params->m_Mean_Variance_Estimator___Uptime_Gamma);
|
||||
gamma_mean =
|
||||
(params->m_Mean_Variance_Estimator___Gamma +
|
||||
(fix16_mul((params->m_Mean_Variance_Estimator___Gamma_Initial_Mean -
|
||||
params->m_Mean_Variance_Estimator___Gamma),
|
||||
sigmoid_gamma_mean)));
|
||||
gating_threshold_mean =
|
||||
(F16(VocAlgorithm_GATING_THRESHOLD) +
|
||||
(fix16_mul(
|
||||
F16((VocAlgorithm_GATING_THRESHOLD_INITIAL -
|
||||
VocAlgorithm_GATING_THRESHOLD)),
|
||||
VocAlgorithm__mean_variance_estimator___sigmoid__process(
|
||||
params, params->m_Mean_Variance_Estimator___Uptime_Gating))));
|
||||
VocAlgorithm__mean_variance_estimator___sigmoid__set_parameters(
|
||||
params, F16(1.), gating_threshold_mean,
|
||||
F16(VocAlgorithm_GATING_THRESHOLD_TRANSITION));
|
||||
sigmoid_gating_mean =
|
||||
VocAlgorithm__mean_variance_estimator___sigmoid__process(
|
||||
params, voc_index_from_prior);
|
||||
params->m_Mean_Variance_Estimator__Gamma_Mean =
|
||||
(fix16_mul(sigmoid_gating_mean, gamma_mean));
|
||||
VocAlgorithm__mean_variance_estimator___sigmoid__set_parameters(
|
||||
params, F16(1.), F16(VocAlgorithm_INIT_DURATION_VARIANCE),
|
||||
F16(VocAlgorithm_INIT_TRANSITION_VARIANCE));
|
||||
sigmoid_gamma_variance =
|
||||
VocAlgorithm__mean_variance_estimator___sigmoid__process(
|
||||
params, params->m_Mean_Variance_Estimator___Uptime_Gamma);
|
||||
gamma_variance =
|
||||
(params->m_Mean_Variance_Estimator___Gamma +
|
||||
(fix16_mul(
|
||||
(params->m_Mean_Variance_Estimator___Gamma_Initial_Variance -
|
||||
params->m_Mean_Variance_Estimator___Gamma),
|
||||
(sigmoid_gamma_variance - sigmoid_gamma_mean))));
|
||||
gating_threshold_variance =
|
||||
(F16(VocAlgorithm_GATING_THRESHOLD) +
|
||||
(fix16_mul(
|
||||
F16((VocAlgorithm_GATING_THRESHOLD_INITIAL -
|
||||
VocAlgorithm_GATING_THRESHOLD)),
|
||||
VocAlgorithm__mean_variance_estimator___sigmoid__process(
|
||||
params, params->m_Mean_Variance_Estimator___Uptime_Gating))));
|
||||
VocAlgorithm__mean_variance_estimator___sigmoid__set_parameters(
|
||||
params, F16(1.), gating_threshold_variance,
|
||||
F16(VocAlgorithm_GATING_THRESHOLD_TRANSITION));
|
||||
sigmoid_gating_variance =
|
||||
VocAlgorithm__mean_variance_estimator___sigmoid__process(
|
||||
params, voc_index_from_prior);
|
||||
params->m_Mean_Variance_Estimator__Gamma_Variance =
|
||||
(fix16_mul(sigmoid_gating_variance, gamma_variance));
|
||||
params->m_Mean_Variance_Estimator___Gating_Duration_Minutes =
|
||||
(params->m_Mean_Variance_Estimator___Gating_Duration_Minutes +
|
||||
(fix16_mul(F16((VocAlgorithm_SAMPLING_INTERVAL / 60.)),
|
||||
((fix16_mul((F16(1.) - sigmoid_gating_mean),
|
||||
F16((1. + VocAlgorithm_GATING_MAX_RATIO)))) -
|
||||
F16(VocAlgorithm_GATING_MAX_RATIO)))));
|
||||
if ((params->m_Mean_Variance_Estimator___Gating_Duration_Minutes <
|
||||
F16(0.))) {
|
||||
params->m_Mean_Variance_Estimator___Gating_Duration_Minutes = F16(0.);
|
||||
}
|
||||
if ((params->m_Mean_Variance_Estimator___Gating_Duration_Minutes >
|
||||
params->m_Mean_Variance_Estimator__Gating_Max_Duration_Minutes)) {
|
||||
params->m_Mean_Variance_Estimator___Uptime_Gating = F16(0.);
|
||||
}
|
||||
}
|
||||
|
||||
static void VocAlgorithm__mean_variance_estimator__process(
|
||||
VocAlgorithmParams* params, fix16_t sraw, fix16_t voc_index_from_prior) {
|
||||
|
||||
fix16_t delta_sgp;
|
||||
fix16_t c;
|
||||
fix16_t 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 = F16(0.);
|
||||
} else {
|
||||
if (((params->m_Mean_Variance_Estimator___Mean >= F16(100.)) ||
|
||||
(params->m_Mean_Variance_Estimator___Mean <= F16(-100.)))) {
|
||||
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 = F16(0.);
|
||||
}
|
||||
sraw = (sraw - params->m_Mean_Variance_Estimator___Sraw_Offset);
|
||||
VocAlgorithm__mean_variance_estimator___calculate_gamma(
|
||||
params, voc_index_from_prior);
|
||||
delta_sgp = (fix16_div(
|
||||
(sraw - params->m_Mean_Variance_Estimator___Mean),
|
||||
F16(VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING)));
|
||||
if ((delta_sgp < F16(0.))) {
|
||||
c = (params->m_Mean_Variance_Estimator___Std - delta_sgp);
|
||||
} else {
|
||||
c = (params->m_Mean_Variance_Estimator___Std + delta_sgp);
|
||||
}
|
||||
additional_scaling = F16(1.);
|
||||
if ((c > F16(1440.))) {
|
||||
additional_scaling = F16(4.);
|
||||
}
|
||||
params->m_Mean_Variance_Estimator___Std = (fix16_mul(
|
||||
fix16_sqrt((fix16_mul(
|
||||
additional_scaling,
|
||||
(F16(VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING) -
|
||||
params->m_Mean_Variance_Estimator__Gamma_Variance)))),
|
||||
fix16_sqrt((
|
||||
(fix16_mul(
|
||||
params->m_Mean_Variance_Estimator___Std,
|
||||
(fix16_div(
|
||||
params->m_Mean_Variance_Estimator___Std,
|
||||
(fix16_mul(
|
||||
F16(VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING),
|
||||
additional_scaling)))))) +
|
||||
(fix16_mul(
|
||||
(fix16_div(
|
||||
(fix16_mul(
|
||||
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 +
|
||||
(fix16_mul(params->m_Mean_Variance_Estimator__Gamma_Mean,
|
||||
delta_sgp)));
|
||||
}
|
||||
}
|
||||
|
||||
static void VocAlgorithm__mean_variance_estimator___sigmoid__init(
|
||||
VocAlgorithmParams* params) {
|
||||
|
||||
VocAlgorithm__mean_variance_estimator___sigmoid__set_parameters(
|
||||
params, F16(0.), F16(0.), F16(0.));
|
||||
}
|
||||
|
||||
static void VocAlgorithm__mean_variance_estimator___sigmoid__set_parameters(
|
||||
VocAlgorithmParams* params, fix16_t L, fix16_t X0, fix16_t K) {
|
||||
|
||||
params->m_Mean_Variance_Estimator___Sigmoid__L = L;
|
||||
params->m_Mean_Variance_Estimator___Sigmoid__K = K;
|
||||
params->m_Mean_Variance_Estimator___Sigmoid__X0 = X0;
|
||||
}
|
||||
|
||||
static fix16_t VocAlgorithm__mean_variance_estimator___sigmoid__process(
|
||||
VocAlgorithmParams* params, fix16_t sample) {
|
||||
|
||||
fix16_t x;
|
||||
|
||||
x = (fix16_mul(params->m_Mean_Variance_Estimator___Sigmoid__K,
|
||||
(sample - params->m_Mean_Variance_Estimator___Sigmoid__X0)));
|
||||
if ((x < F16(-50.))) {
|
||||
return params->m_Mean_Variance_Estimator___Sigmoid__L;
|
||||
} else if ((x > F16(50.))) {
|
||||
return F16(0.);
|
||||
} else {
|
||||
return (fix16_div(params->m_Mean_Variance_Estimator___Sigmoid__L,
|
||||
(F16(1.) + fix16_exp(x))));
|
||||
}
|
||||
}
|
||||
|
||||
static void VocAlgorithm__mox_model__init(VocAlgorithmParams* params) {
|
||||
|
||||
VocAlgorithm__mox_model__set_parameters(params, F16(1.), F16(0.));
|
||||
}
|
||||
|
||||
static void VocAlgorithm__mox_model__set_parameters(VocAlgorithmParams* params,
|
||||
fix16_t SRAW_STD,
|
||||
fix16_t SRAW_MEAN) {
|
||||
|
||||
params->m_Mox_Model__Sraw_Std = SRAW_STD;
|
||||
params->m_Mox_Model__Sraw_Mean = SRAW_MEAN;
|
||||
}
|
||||
|
||||
static fix16_t VocAlgorithm__mox_model__process(VocAlgorithmParams* params,
|
||||
fix16_t sraw) {
|
||||
|
||||
return (fix16_mul((fix16_div((sraw - params->m_Mox_Model__Sraw_Mean),
|
||||
(-(params->m_Mox_Model__Sraw_Std +
|
||||
F16(VocAlgorithm_SRAW_STD_BONUS))))),
|
||||
F16(VocAlgorithm_VOC_INDEX_GAIN)));
|
||||
}
|
||||
|
||||
static void VocAlgorithm__sigmoid_scaled__init(VocAlgorithmParams* params) {
|
||||
|
||||
VocAlgorithm__sigmoid_scaled__set_parameters(params, F16(0.));
|
||||
}
|
||||
|
||||
static void
|
||||
VocAlgorithm__sigmoid_scaled__set_parameters(VocAlgorithmParams* params,
|
||||
fix16_t offset) {
|
||||
|
||||
params->m_Sigmoid_Scaled__Offset = offset;
|
||||
}
|
||||
|
||||
static fix16_t VocAlgorithm__sigmoid_scaled__process(VocAlgorithmParams* params,
|
||||
fix16_t sample) {
|
||||
|
||||
fix16_t x;
|
||||
fix16_t shift;
|
||||
|
||||
x = (fix16_mul(F16(VocAlgorithm_SIGMOID_K),
|
||||
(sample - F16(VocAlgorithm_SIGMOID_X0))));
|
||||
if ((x < F16(-50.))) {
|
||||
return F16(VocAlgorithm_SIGMOID_L);
|
||||
} else if ((x > F16(50.))) {
|
||||
return F16(0.);
|
||||
} else {
|
||||
if ((sample >= F16(0.))) {
|
||||
shift = (fix16_div(
|
||||
(F16(VocAlgorithm_SIGMOID_L) -
|
||||
(fix16_mul(F16(5.), params->m_Sigmoid_Scaled__Offset))),
|
||||
F16(4.)));
|
||||
return ((fix16_div((F16(VocAlgorithm_SIGMOID_L) + shift),
|
||||
(F16(1.) + fix16_exp(x)))) -
|
||||
shift);
|
||||
} else {
|
||||
return (fix16_mul(
|
||||
(fix16_div(params->m_Sigmoid_Scaled__Offset,
|
||||
F16(VocAlgorithm_VOC_INDEX_OFFSET_DEFAULT))),
|
||||
(fix16_div(F16(VocAlgorithm_SIGMOID_L),
|
||||
(F16(1.) + fix16_exp(x))))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void VocAlgorithm__adaptive_lowpass__init(VocAlgorithmParams* params) {
|
||||
|
||||
VocAlgorithm__adaptive_lowpass__set_parameters(params);
|
||||
}
|
||||
|
||||
static void
|
||||
VocAlgorithm__adaptive_lowpass__set_parameters(VocAlgorithmParams* params) {
|
||||
|
||||
params->m_Adaptive_Lowpass__A1 =
|
||||
F16((VocAlgorithm_SAMPLING_INTERVAL /
|
||||
(VocAlgorithm_LP_TAU_FAST + VocAlgorithm_SAMPLING_INTERVAL)));
|
||||
params->m_Adaptive_Lowpass__A2 =
|
||||
F16((VocAlgorithm_SAMPLING_INTERVAL /
|
||||
(VocAlgorithm_LP_TAU_SLOW + VocAlgorithm_SAMPLING_INTERVAL)));
|
||||
params->m_Adaptive_Lowpass___Initialized = false;
|
||||
}
|
||||
|
||||
static fix16_t
|
||||
VocAlgorithm__adaptive_lowpass__process(VocAlgorithmParams* params,
|
||||
fix16_t sample) {
|
||||
|
||||
fix16_t abs_delta;
|
||||
fix16_t F1;
|
||||
fix16_t tau_a;
|
||||
fix16_t 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 =
|
||||
((fix16_mul((F16(1.) - params->m_Adaptive_Lowpass__A1),
|
||||
params->m_Adaptive_Lowpass___X1)) +
|
||||
(fix16_mul(params->m_Adaptive_Lowpass__A1, sample)));
|
||||
params->m_Adaptive_Lowpass___X2 =
|
||||
((fix16_mul((F16(1.) - params->m_Adaptive_Lowpass__A2),
|
||||
params->m_Adaptive_Lowpass___X2)) +
|
||||
(fix16_mul(params->m_Adaptive_Lowpass__A2, sample)));
|
||||
abs_delta =
|
||||
(params->m_Adaptive_Lowpass___X1 - params->m_Adaptive_Lowpass___X2);
|
||||
if ((abs_delta < F16(0.))) {
|
||||
abs_delta = (-abs_delta);
|
||||
}
|
||||
F1 = fix16_exp((fix16_mul(F16(VocAlgorithm_LP_ALPHA), abs_delta)));
|
||||
tau_a =
|
||||
((fix16_mul(F16((VocAlgorithm_LP_TAU_SLOW - VocAlgorithm_LP_TAU_FAST)),
|
||||
F1)) +
|
||||
F16(VocAlgorithm_LP_TAU_FAST));
|
||||
a3 = (fix16_div(F16(VocAlgorithm_SAMPLING_INTERVAL),
|
||||
(F16(VocAlgorithm_SAMPLING_INTERVAL) + tau_a)));
|
||||
params->m_Adaptive_Lowpass___X3 =
|
||||
((fix16_mul((F16(1.) - a3), params->m_Adaptive_Lowpass___X3)) +
|
||||
(fix16_mul(a3, sample)));
|
||||
return params->m_Adaptive_Lowpass___X3;
|
||||
}
|
|
@ -0,0 +1,182 @@
|
|||
/*
|
||||
* Copyright (c) 2020, 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 VOCALGORITHM_H_
|
||||
#define VOCALGORITHM_H_
|
||||
|
||||
#include "sensirion_arch_config.h"
|
||||
|
||||
/* The fixed point arithmetic parts of this code were originally created by
|
||||
* https://github.com/PetteriAimonen/libfixmath
|
||||
*/
|
||||
|
||||
typedef int32_t fix16_t;
|
||||
|
||||
#define F16(x) \
|
||||
((fix16_t)(((x) >= 0) ? ((x)*65536.0 + 0.5) : ((x)*65536.0 - 0.5)))
|
||||
|
||||
#define VocAlgorithm_SAMPLING_INTERVAL (1.)
|
||||
#define VocAlgorithm_INITIAL_BLACKOUT (45.)
|
||||
#define VocAlgorithm_VOC_INDEX_GAIN (230.)
|
||||
#define VocAlgorithm_SRAW_STD_INITIAL (50.)
|
||||
#define VocAlgorithm_SRAW_STD_BONUS (220.)
|
||||
#define VocAlgorithm_TAU_MEAN_VARIANCE_HOURS (12.)
|
||||
#define VocAlgorithm_TAU_INITIAL_MEAN (20.)
|
||||
#define VocAlgorithm_INIT_DURATION_MEAN ((3600. * 0.75))
|
||||
#define VocAlgorithm_INIT_TRANSITION_MEAN (0.01)
|
||||
#define VocAlgorithm_TAU_INITIAL_VARIANCE (2500.)
|
||||
#define VocAlgorithm_INIT_DURATION_VARIANCE ((3600. * 1.45))
|
||||
#define VocAlgorithm_INIT_TRANSITION_VARIANCE (0.01)
|
||||
#define VocAlgorithm_GATING_THRESHOLD (340.)
|
||||
#define VocAlgorithm_GATING_THRESHOLD_INITIAL (510.)
|
||||
#define VocAlgorithm_GATING_THRESHOLD_TRANSITION (0.09)
|
||||
#define VocAlgorithm_GATING_MAX_DURATION_MINUTES ((60. * 3.))
|
||||
#define VocAlgorithm_GATING_MAX_RATIO (0.3)
|
||||
#define VocAlgorithm_SIGMOID_L (500.)
|
||||
#define VocAlgorithm_SIGMOID_K (-0.0065)
|
||||
#define VocAlgorithm_SIGMOID_X0 (213.)
|
||||
#define VocAlgorithm_VOC_INDEX_OFFSET_DEFAULT (100.)
|
||||
#define VocAlgorithm_LP_TAU_FAST (20.0)
|
||||
#define VocAlgorithm_LP_TAU_SLOW (500.0)
|
||||
#define VocAlgorithm_LP_ALPHA (-0.2)
|
||||
#define VocAlgorithm_PERSISTENCE_UPTIME_GAMMA ((3. * 3600.))
|
||||
#define VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__GAMMA_SCALING (64.)
|
||||
#define VocAlgorithm_MEAN_VARIANCE_ESTIMATOR__FIX16_MAX (32767.)
|
||||
|
||||
/**
|
||||
* Struct to hold all the states of the VOC algorithm.
|
||||
*/
|
||||
typedef struct {
|
||||
fix16_t mVoc_Index_Offset;
|
||||
fix16_t mTau_Mean_Variance_Hours;
|
||||
fix16_t mGating_Max_Duration_Minutes;
|
||||
fix16_t mSraw_Std_Initial;
|
||||
fix16_t mUptime;
|
||||
fix16_t mSraw;
|
||||
fix16_t mVoc_Index;
|
||||
fix16_t m_Mean_Variance_Estimator__Gating_Max_Duration_Minutes;
|
||||
bool m_Mean_Variance_Estimator___Initialized;
|
||||
fix16_t m_Mean_Variance_Estimator___Mean;
|
||||
fix16_t m_Mean_Variance_Estimator___Sraw_Offset;
|
||||
fix16_t m_Mean_Variance_Estimator___Std;
|
||||
fix16_t m_Mean_Variance_Estimator___Gamma;
|
||||
fix16_t m_Mean_Variance_Estimator___Gamma_Initial_Mean;
|
||||
fix16_t m_Mean_Variance_Estimator___Gamma_Initial_Variance;
|
||||
fix16_t m_Mean_Variance_Estimator__Gamma_Mean;
|
||||
fix16_t m_Mean_Variance_Estimator__Gamma_Variance;
|
||||
fix16_t m_Mean_Variance_Estimator___Uptime_Gamma;
|
||||
fix16_t m_Mean_Variance_Estimator___Uptime_Gating;
|
||||
fix16_t m_Mean_Variance_Estimator___Gating_Duration_Minutes;
|
||||
fix16_t m_Mean_Variance_Estimator___Sigmoid__L;
|
||||
fix16_t m_Mean_Variance_Estimator___Sigmoid__K;
|
||||
fix16_t m_Mean_Variance_Estimator___Sigmoid__X0;
|
||||
fix16_t m_Mox_Model__Sraw_Std;
|
||||
fix16_t m_Mox_Model__Sraw_Mean;
|
||||
fix16_t m_Sigmoid_Scaled__Offset;
|
||||
fix16_t m_Adaptive_Lowpass__A1;
|
||||
fix16_t m_Adaptive_Lowpass__A2;
|
||||
bool m_Adaptive_Lowpass___Initialized;
|
||||
fix16_t m_Adaptive_Lowpass___X1;
|
||||
fix16_t m_Adaptive_Lowpass___X2;
|
||||
fix16_t m_Adaptive_Lowpass___X3;
|
||||
} VocAlgorithmParams;
|
||||
|
||||
/**
|
||||
* Initialize the VOC algorithm parameters. Call this once at the beginning or
|
||||
* whenever the sensor stopped measurements.
|
||||
* @param params Pointer to the VocAlgorithmParams struct
|
||||
*/
|
||||
void VocAlgorithm_init(VocAlgorithmParams *params);
|
||||
|
||||
/**
|
||||
* Get current algorithm states. Retrieved values can be used in
|
||||
* VocAlgorithm_set_states() to resume operation after a short interruption,
|
||||
* skipping initial learning phase. This feature can only be used after at least
|
||||
* 3 hours of continuous operation.
|
||||
* @param params Pointer to the VocAlgorithmParams struct
|
||||
* @param state0 State0 to be stored
|
||||
* @param state1 State1 to be stored
|
||||
*/
|
||||
void VocAlgorithm_get_states(VocAlgorithmParams *params, int32_t *state0,
|
||||
int32_t *state1);
|
||||
|
||||
/**
|
||||
* Set previously retrieved algorithm states to resume operation after a short
|
||||
* interruption, skipping initial learning phase. This feature should not be
|
||||
* used after inerruptions of more than 10 minutes. Call this once after
|
||||
* VocAlgorithm_init() and the optional VocAlgorithm_set_tuning_parameters(), if
|
||||
* desired. Otherwise, the algorithm will start with initial learning phase.
|
||||
* @param params Pointer to the VocAlgorithmParams struct
|
||||
* @param state0 State0 to be restored
|
||||
* @param state1 State1 to be restored
|
||||
*/
|
||||
void VocAlgorithm_set_states(VocAlgorithmParams *params, int32_t state0,
|
||||
int32_t state1);
|
||||
|
||||
/**
|
||||
* Set parameters to customize the VOC algorithm. Call this once after
|
||||
* VocAlgorithm_init(), if desired. Otherwise, the default values will be used.
|
||||
*
|
||||
* @param params Pointer to the VocAlgorithmParams struct
|
||||
* @param voc_index_offset VOC index representing typical (average)
|
||||
* conditions. Range 1..250, default 100
|
||||
* @param learning_time_hours Time constant of long-term estimator.
|
||||
* Past events will be forgotten after about
|
||||
* twice the learning time.
|
||||
* Range 1..72 [hours], default 12 [hours]
|
||||
* @param gating_max_duration_minutes Maximum duration of gating (freeze of
|
||||
* estimator during high VOC index signal).
|
||||
* 0 (no gating) or range 1..720 [minutes],
|
||||
* default 180 [minutes]
|
||||
* @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..500, default 50
|
||||
*/
|
||||
void VocAlgorithm_set_tuning_parameters(VocAlgorithmParams *params,
|
||||
int32_t voc_index_offset,
|
||||
int32_t learning_time_hours,
|
||||
int32_t gating_max_duration_minutes,
|
||||
int32_t std_initial);
|
||||
|
||||
/**
|
||||
* Calculate the VOC index value from the raw sensor value.
|
||||
*
|
||||
* @param params Pointer to the VocAlgorithmParams struct
|
||||
* @param sraw Raw value from the SGP40 sensor
|
||||
* @param voc_index Calculated VOC index value from the raw sensor value. Zero
|
||||
* during initial blackout period and 1..500 afterwards
|
||||
*/
|
||||
void VocAlgorithm_process(VocAlgorithmParams *params, int32_t sraw,
|
||||
int32_t *voc_index);
|
||||
|
||||
#endif /* VOCALGORITHM_H_ */
|
|
@ -230,6 +230,7 @@
|
|||
#define D_JSON_RESETTABLE_TOTAL_ACTIVE "ResetTotalActive"
|
||||
#define D_JSON_SIGNALSTRENGTH "SignalStrength"
|
||||
#define D_JSON_CHIPTEMPERATURE "ChipTemperature"
|
||||
#define D_JSON_RAW "Raw"
|
||||
|
||||
#define D_RSLT_ENERGY "ENERGY"
|
||||
#define D_RSLT_HASS_STATE "HASS_STATE"
|
||||
|
|
|
@ -96,6 +96,7 @@
|
|||
//#define USE_TSL2591 // [I2cDriver40] Enable TSL2591 sensor (I2C address 0x29) using library Adafruit_TSL2591 (+1k6 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_SGP40 // [I2cDriver69] Enable SGP40 sensor (I2C address 0x59) (+1k4 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_APDS9960 // [I2cDriver21] Enable APDS9960 Proximity Sensor (I2C address 0x39). Disables SHT and VEML6070 (+4k7 code)
|
||||
|
|
|
@ -354,6 +354,7 @@
|
|||
//#define USE_TSL2591 // [I2cDriver40] Enable TSL2591 sensor (I2C address 0x29) using library Adafruit_TSL2591 (+1k6 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_SGP40 // [I2cDriver69] Enable SGP40 sensor (I2C address 0x59) (+1k4 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_APDS9960 // [I2cDriver21] Enable APDS9960 Proximity Sensor (I2C address 0x39). Disables SHT and VEML6070 (+4k7 code)
|
||||
|
@ -562,6 +563,7 @@
|
|||
//#define USE_TSL2591 // [I2cDriver40] Enable TSL2591 sensor (I2C address 0x29) using library Adafruit_TSL2591 (+1k6 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_SGP40 // [I2cDriver69] Enable SGP40 sensor (I2C address 0x59) (+1k4 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_APDS9960 // [I2cDriver21] Enable APDS9960 Proximity Sensor (I2C address 0x39). Disables SHT and VEML6070 (+4k7 code)
|
||||
|
|
|
@ -602,6 +602,7 @@
|
|||
// #define USE_MGS // [I2cDriver17] Enable Xadow and Grove Mutichannel Gas sensor using library Multichannel_Gas_Sensor (+10k code)
|
||||
#define MGS_SENSOR_ADDR 0x04 // Default Mutichannel Gas sensor i2c address
|
||||
// #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_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_APDS9960 // [I2cDriver21] Enable APDS9960 Proximity Sensor (I2C address 0x39). Disables SHT and VEML6070 (+4k7 code)
|
||||
|
|
|
@ -825,6 +825,9 @@ void ResponseAppendFeatures(void)
|
|||
|
||||
static uint32_t feature9 = 0x00000000;
|
||||
if (!feature9) { // Only fill this once
|
||||
#if defined(USE_I2C) && defined(USE_SGP40)
|
||||
feature9 |= 0x00000001; // xsns_98_sgp40.ino
|
||||
#endif
|
||||
// feature9 |= 0x00000001;
|
||||
// feature9 |= 0x00000002;
|
||||
// feature9 |= 0x00000004;
|
||||
|
|
|
@ -0,0 +1,170 @@
|
|||
/*
|
||||
xsns_98_sgp40.ino - SGP40 gas and air quality sensor support for Tasmota
|
||||
|
||||
Copyright (C) 2021 Theo Arends
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
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_SGP40
|
||||
/*********************************************************************************************\
|
||||
* SGP40 - Gas (TVOC - Total Volatile Organic Compounds) and Air Quality (CO2)
|
||||
*
|
||||
* Source: Gerhard Mutz and Adafruit Industries
|
||||
* Adaption for TASMOTA: Jean-Pierre Deschamps
|
||||
*
|
||||
* I2C Address: 0x59
|
||||
\*********************************************************************************************/
|
||||
|
||||
#define XSNS_98 98
|
||||
#define XI2C_69 69 // See I2CDEVICES.md
|
||||
|
||||
#define SGP40_ADDRESS 0x59
|
||||
|
||||
#include "Adafruit_SGP40.h"
|
||||
Adafruit_SGP40 sgp40;
|
||||
|
||||
bool sgp40_type = false;
|
||||
bool sgp40_ready = false;
|
||||
float sgp40_abshum;
|
||||
uint16_t raw_base;
|
||||
int32_t voc_index;
|
||||
|
||||
/********************************************************************************************/
|
||||
|
||||
void sgp40_Init(void)
|
||||
{
|
||||
if (!I2cSetDevice(SGP40_ADDRESS)) { return; }
|
||||
|
||||
if (sgp40.begin()) {
|
||||
sgp40_type = true;
|
||||
// AddLog(LOG_LEVEL_DEBUG, PSTR("SGP: Serialnumber 0x%04X-0x%04X-0x%04X"), sgp40.serialnumber[0], sgp40.serialnumber[1], sgp40.serialnumber[2]);
|
||||
I2cSetActiveFound(SGP40_ADDRESS, "SGP40");
|
||||
}
|
||||
}
|
||||
|
||||
//#define POW_FUNC pow
|
||||
#define POW_FUNC FastPrecisePow
|
||||
|
||||
float sgp40_AbsoluteHumidity(float temperature, float humidity) {
|
||||
//taken from https://carnotcycle.wordpress.com/2012/08/04/how-to-convert-relative-humidity-to-absolute-humidity/
|
||||
//precision is about 0.1°C in range -30 to 35°C
|
||||
//August-Roche-Magnus 6.1094 exp(17.625 x T)/(T + 243.04)
|
||||
//Buck (1981) 6.1121 exp(17.502 x T)/(T + 240.97)
|
||||
//reference https://www.eas.ualberta.ca/jdwilson/EAS372_13/Vomel_CIRES_satvpformulae.html
|
||||
float temp = NAN;
|
||||
const float mw = 18.01534f; // molar mass of water g/mol
|
||||
const float r = 8.31447215f; // Universal gas constant J/mol/K
|
||||
|
||||
if (isnan(temperature) || isnan(humidity) ) {
|
||||
return NAN;
|
||||
}
|
||||
|
||||
temp = POW_FUNC(2.718281828f, (17.67f * temperature) / (temperature + 243.5f));
|
||||
|
||||
//return (6.112 * temp * humidity * 2.1674) / (273.15 + temperature); //simplified version
|
||||
return (6.112f * temp * humidity * mw) / ((273.15f + temperature) * r); //long version
|
||||
}
|
||||
|
||||
#define SAVE_PERIOD 30
|
||||
|
||||
void Sgp40Update(void) // Perform every second to ensure proper operation of the baseline compensation algorithm
|
||||
{
|
||||
sgp40_ready = false;
|
||||
if (TasmotaGlobal.global_update && (TasmotaGlobal.humidity > 0) && !isnan(TasmotaGlobal.temperature_celsius)) {
|
||||
// abs hum in mg/m3
|
||||
sgp40_abshum = sgp40_AbsoluteHumidity(TasmotaGlobal.temperature_celsius, TasmotaGlobal.humidity);
|
||||
}
|
||||
sgp40_ready = true;
|
||||
|
||||
// these should normally be stored permanently and used for fast restart
|
||||
if (!(TasmotaGlobal.uptime%SAVE_PERIOD)) {
|
||||
// store settings every N seconds
|
||||
raw_base = sgp40.measureRaw(TasmotaGlobal.temperature_celsius, TasmotaGlobal.humidity);
|
||||
voc_index = sgp40.measureVocIndex(TasmotaGlobal.temperature_celsius, TasmotaGlobal.humidity);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_WEBSERVER
|
||||
const char HTTP_SNS_SGP40[] PROGMEM =
|
||||
"{s}SGP40 " D_JSON_RAW "{m}%d " "{e}" // {s} = <tr><th>, {m} = </th><td>, {e} = </td></tr>
|
||||
"{s}SGP40 " D_AIR_QUALITY "{m}%d " "{e}";
|
||||
const char HTTP_SNS_AHUM40[] PROGMEM = "{s}SGP40 Abs Humidity{m}%s g/m3{e}";
|
||||
#endif
|
||||
|
||||
#define D_JSON_AHUM "aHumidity"
|
||||
|
||||
void Sgp40Show(bool json)
|
||||
{
|
||||
if (sgp40_ready) {
|
||||
char abs_hum[33];
|
||||
bool ahum_available = TasmotaGlobal.global_update && (TasmotaGlobal.humidity > 0) && !isnan(TasmotaGlobal.temperature_celsius);
|
||||
if (ahum_available) {
|
||||
// has humidity + temperature
|
||||
dtostrfd(sgp40_abshum,4,abs_hum);
|
||||
}
|
||||
if (json) {
|
||||
ResponseAppend_P(PSTR(",\"SGP40\":{\"" D_JSON_RAW "\":%d,\"" D_JSON_AIRQUALITY "\":%d"), raw_base, voc_index);
|
||||
if (ahum_available) {
|
||||
ResponseAppend_P(PSTR(",\"" D_JSON_AHUM "\":%s"),abs_hum);
|
||||
}
|
||||
ResponseJsonEnd();
|
||||
#ifdef USE_DOMOTICZ
|
||||
if (0 == TasmotaGlobal.tele_period) DomoticzSensor(DZ_AIRQUALITY, raw_base);
|
||||
#endif // USE_DOMOTICZ
|
||||
#ifdef USE_WEBSERVER
|
||||
} else {
|
||||
WSContentSend_PD(HTTP_SNS_SGP40, raw_base, voc_index);
|
||||
if (ahum_available) {
|
||||
WSContentSend_PD(HTTP_SNS_AHUM40, abs_hum);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*********************************************************************************************\
|
||||
* Interface
|
||||
\*********************************************************************************************/
|
||||
|
||||
bool Xsns98(uint8_t function)
|
||||
{
|
||||
if (!I2cEnabled(XI2C_69)) { return false; }
|
||||
|
||||
bool result = false;
|
||||
|
||||
if (FUNC_INIT == function) {
|
||||
sgp40_Init();
|
||||
}
|
||||
else if (sgp40_type) {
|
||||
switch (function) {
|
||||
case FUNC_EVERY_SECOND:
|
||||
Sgp40Update();
|
||||
break;
|
||||
case FUNC_JSON_APPEND:
|
||||
Sgp40Show(1);
|
||||
break;
|
||||
#ifdef USE_WEBSERVER
|
||||
case FUNC_WEB_SENSOR:
|
||||
Sgp40Show(0);
|
||||
break;
|
||||
#endif // USE_WEBSERVER
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif // USE_SGP40
|
||||
#endif // USE_I2C
|
|
@ -236,7 +236,7 @@ a_features = [[
|
|||
"USE_BH1750","USE_VEML6070","USE_ADS1115_I2CDEV","USE_ADS1115",
|
||||
"USE_INA219","USE_SHT3X","USE_MHZ19","USE_TSL2561",
|
||||
"USE_SENSEAIR","USE_PMS5003","USE_MGS","USE_NOVA_SDS",
|
||||
"USE_SGP30","USE_SR04","USE_SDM120","USE_SI1145",
|
||||
"USE_SGP30","USE_SGP40","USE_SR04","USE_SDM120","USE_SI1145",
|
||||
"USE_SDM630","USE_LM75AD","USE_APDS9960","USE_TM1638"
|
||||
],[
|
||||
"USE_MCP230xx","USE_MPR121","USE_CCS811","USE_MPU6050",
|
||||
|
|
Loading…
Reference in New Issue