diff --git a/BUILDS.md b/BUILDS.md index 12802b096..e5dadfaa6 100644 --- a/BUILDS.md +++ b/BUILDS.md @@ -118,6 +118,7 @@ Note: `minimal` variant is not listed as it shouldn't be used outside of the [up | USE_MGS | - | - / x | - | x | - | - | | USE_SGP30 | - | - / x | - | x | - | - | | USE_SGP40 | - | - / x | - | x | - | - | +| USE_SEN5X | - | - / x | - | x | - | - | | USE_SI1145 | - | - / - | - | - | - | - | | USE_LM75AD | - | - / x | - | x | - | - | | USE_APDS9960 | - | - / - | - | - | - | - | diff --git a/CODE_OWNERS.md b/CODE_OWNERS.md index 37851c86d..9e5b35411 100644 --- a/CODE_OWNERS.md +++ b/CODE_OWNERS.md @@ -198,6 +198,7 @@ In addition to @arendst the following code is mainly owned by: | xsns_100_ina3221 | @barbudor | xsns_101_hmc5883l | Andreas Achtzehn | xsns_102_ld2410 | @arendst +| xsns_103_sen5x | @tyeth | | | Libraries | | | diff --git a/I2CDEVICES.md b/I2CDEVICES.md index 171b04fd7..28818c562 100644 --- a/I2CDEVICES.md +++ b/I2CDEVICES.md @@ -111,3 +111,4 @@ Index | Define | Driver | Device | Address(es) | Description 73 | USE_HMC5883L | xsns_101 | HMC5883L | 0x1E | 3-channels Magnetic Field Sensor 74 | USE_DISPLAY_TM1650 | xdsp_20 | TM1650 | 0x24 - 0x27, 0x34 - 0x37 | Four-digit seven-segment LED controller 75 | USE_PCA9632 | xdrv_64 | PCA9632 | 0x60 | 4-channel 4-bit pwm driver + 76 | USE_SEN5X | xsns_103 | SEN5X | 0x69 | Gas (VOC/NOx index) and air quality (PPM <1,<2.5,<4,<10) diff --git a/lib/lib_i2c/Sensirion_Core/.clang-format b/lib/lib_i2c/Sensirion_Core/.clang-format new file mode 100644 index 000000000..047f2adf1 --- /dev/null +++ b/lib/lib_i2c/Sensirion_Core/.clang-format @@ -0,0 +1,14 @@ +--- +Language: Cpp +BasedOnStyle: LLVM +IndentWidth: 4 +AlignAfterOpenBracket: Align +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: false +IndentCaseLabels: true +SpacesBeforeTrailingComments: 2 +PointerAlignment: Left +AlignEscapedNewlines: Left +ForEachMacros: ['TEST_GROUP', 'TEST'] +... diff --git a/lib/lib_i2c/Sensirion_Core/CHANGELOG.rst b/lib/lib_i2c/Sensirion_Core/CHANGELOG.rst new file mode 100644 index 000000000..8d7aa66ff --- /dev/null +++ b/lib/lib_i2c/Sensirion_Core/CHANGELOG.rst @@ -0,0 +1,161 @@ +Changelog +========= + +All notable changes to this project will be documented in this file. + +The format is based on `Keep a Changelog `_ +and this project adheres to `Semantic Versioning `_. + +`Unreleased`_ +------------- + + +`0.6.0`_ 2022-06-22 +------------------- + +- Fix compiler warnings in SensirionErrors.cpp +- Allow drivers to choose CRC function + +`0.5.3`_ 2021-10-19 +------------------- + +- Add support for sensor specific errors +- Update keywords.txt + + +`0.5.2`_ 2021-08-03 +------------------- + +Fixed +..... + +- Fix CRC insertion in ``SensirionI2CTxFrame`` when more then one parameter + is sent to the sensor. + +`0.5.1`_ 2021-07-08 +------------------- + +Changed +....... + +- Adjusted deprecation warnings + +`0.5.0`_ 2021-07-07 +------------------- + +Added +..... + +- Enable SensirionTxFrame to incorporate Uint8 and Uint16 commands + + +`0.4.3`_ 2021-02-12 +------------------- + +Added +..... + +- Added ``const`` modifier to functions which process MOSI array data. + +`0.4.2`_ 2021-01-29 +------------------- + +Changed +....... + +- Renamed the library header from ``SensirionCoreArduinoLibrary.h`` to ``SensirionCore.h``. + We keep the old header for legacy support. + +`0.4.1`_ 2021-01-28 +------------------- + +Fixed +..... + +- Properly handle I2C write errors + + +`0.4.0`_ 2021-01-20 +------------------- + +Added +..... + +- Documentation for all functions. + +Breaking +........ + +- Change interface of ``errorToString()`` function to include length of the + provided buffer. + +Removed +....... + +- Removed ``reset()`` function from ``SensirionI2CTxFrame`` since the + functionality is not needed. + + +`0.3.0`_ 2021-01-13 +------------------- + +Added +..... + +- Core implementation for I2C communication. This includes a RX and TX frame + and a I2C communication class. + +Changed +....... + +- SHDLC and I2C RX frame inherit from a RX frame base class. +- ESP8266 test board from esp8266:esp8266:arduino to esp8266:esp8266:generic. +- Sorted errors into general, SHDLC and I2C errors. +- Replace C style casts with ``static_cast``. + + +`0.2.0`_ 2021-01-11 +------------------- + +Added +..... + +- Explanation what SHDLC is in README. +- ``SensirionErrors.h`` to ``SensirionCoreArduinoLibrary.h``. +- ``sendAndReceiveFrame()`` function to ``SensirionShdlcCommunication``. This + function combines ``sendFrame()`` and ``receiveFrame()`` into one function and + adds additional error checking. + +Changed +....... + +- Rename DeviceError to ExecutionError. +- Move check for execution error after the whole frame is read and checksum is + checked. This prevents that a wrong checksum can't be displayed as an + execution error. + +Removed +....... + +- ``reset()`` function from ``SensirionShdlcTxFrame`` and ``SensirionShdlcRxFrame``, + since one can just create a new frame object which has the same effect. + +`0.1.0`_ 2021-01-07 +------------------- + +- Initial release + + +.. _Unreleased: https://github.com/Sensirion/arduino-core/compare/0.6.0...main +.. _0.6.0: https://github.com/Sensirion/arduino-core/compare/0.6.0...0.5.3 +.. _0.5.3: https://github.com/Sensirion/arduino-core/compare/0.5.2...0.5.3 +.. _0.5.2: https://github.com/Sensirion/arduino-core/compare/0.5.1...0.5.2 +.. _0.5.1: https://github.com/Sensirion/arduino-core/compare/0.5.0...0.5.1 +.. _0.5.0: https://github.com/Sensirion/arduino-core/compare/0.4.3...0.5.0 +.. _0.4.3: https://github.com/Sensirion/arduino-core/compare/0.4.2...0.4.3 +.. _0.4.2: https://github.com/Sensirion/arduino-core/compare/0.4.1...0.4.2 +.. _0.4.1: https://github.com/Sensirion/arduino-core/compare/0.4.0...0.4.1 +.. _0.4.0: https://github.com/Sensirion/arduino-core/compare/0.3.0...0.4.0 +.. _0.3.0: https://github.com/Sensirion/arduino-core/compare/0.2.0...0.3.0 +.. _0.2.0: https://github.com/Sensirion/arduino-core/compare/0.1.0...0.2.0 +.. _0.1.0: https://github.com/Sensirion/arduino-core/releases/tag/0.1.0 diff --git a/lib/lib_i2c/Sensirion_Core/LICENSE b/lib/lib_i2c/Sensirion_Core/LICENSE new file mode 100644 index 000000000..fad1acd6e --- /dev/null +++ b/lib/lib_i2c/Sensirion_Core/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +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: + +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. diff --git a/lib/lib_i2c/Sensirion_Core/README.md b/lib/lib_i2c/Sensirion_Core/README.md new file mode 100644 index 000000000..1a8cf2c71 --- /dev/null +++ b/lib/lib_i2c/Sensirion_Core/README.md @@ -0,0 +1,139 @@ + +# Sensirion Arduino Core Library + +This library provides SHDLC and I2C protocol implementations for Sensirion +sensors. There shouldn't be a reason to use it directly, but is required by the +sensor driver libraries provided here: + +- [SCD4x](https://github.com/Sensirion/arduino-i2c-scd4x) +- [SVM40-I2C](https://github.com/Sensirion/arduino-i2c-svm40) +- [SVM40-UART](https://github.com/Sensirion/arduino-uart-svm40) +- [SFA3x-I2C](https://github.com/Sensirion/arduino-i2c-sfa3x) +- [SFA3x-UART](https://github.com/Sensirion/arduino-uart-sfa3x) + +# More Drivers + +Not looking for Arduino drivers? Check out our other drivers here: + +- [Embedded](https://github.com/Sensirion/info#repositories) +- [Python](https://github.com/Sensirion/info#python-drivers) + +# Usage + +## SHDLC + +SHDLC (Sensirion High-Level Data Link Control) is a byte-oriented master-slave +communication protocol based on [ISO +HDLC](https://en.wikipedia.org/wiki/High-Level_Data_Link_Control). It is used +to control some of Sensirion’s devices (for example mass flow controllers). The +detailed protocol documentation is not publicly available (yet). If you need +it, please contact our [customer +support](https://www.sensirion.com/en/about-us/contact/). + +This library provides the following classes for communication with Sensirion +Sensors using the SHDLC protocol. +- `SensirionShdlcTxFrame` +- `SensirionShdlcRxFrame` +- `SensirionShdlcCommunication` + +### Example Usage +First initialize an instance of `SensirionShdlcTxFrame` and +`SensirionShdlcRxFrame` with a properly sized buffer. A good worst case +estimation for the buffer size is `2 * (n+6)` where `n` is the number of bytes +you want to send. After that you can build your frame by first calling +`begin()`. Information about the correct COMMAND and ADDRESS can be found on +the data sheet of your sensor. Then you can add data to the frame by using +different add member functions. See the code below for examples. After adding +your data finish the frame by calling `finish()`. + +To send this frame to the sensor you first need to initialize the correct +Stream object (Serial,UART,...) to talk to your sensor. Don't forget to also +call the `.begin()` function with the right configuration. Then call the static +function `sendAndReceiveFrame()` from `SensirionShdlcCommunication` as shown +below. You need to replace `STREAMOBJECT` with the initialized Stream object of +your choice. Additionally you need to provide a timeout for to receive data +back, consult the data sheet of your sensor for information on the best timeout +value. + +You can decode the frame by using the different get members to convert the +received data to desired data types. + +All functions return a error code if an error occurs during execution and zero +otherwise. + +```cpp +uint8_t txBuffer[256]; +uint8_t rxBuffer[256]; + +SensirionShdlcTxFrame txFrame(txBuffer, 256); +SensirionShdlcRxFrame rxFrame(rxBuffer, 256); + +txFrame.begin(COMMAND, ADDRESS, DATALENGTH); + +txFrame.addUInt8(UINT8); +txFrame.addUInt32(UINT32); + +txFrame.finish(); + +SensirionShdlcCommunication::sendAndReceiveFrame(STREAMOBJECT, txFrame, rxFrame, TIMEOUT); + +rxFrame.getUInt16(UINT16); +rxFrame.getFloat(FLOAT); + +``` + +## I2C + +This library provides the following classes for communication with Sensirion +Sensors using the I2C protocol. +- `SensirionI2cTxFrame` +- `SensirionI2cRxFrame` +- `SensirionI2cCommunication` + +### Example Usage + +First initialize an instance of `SensirionI2CTxFrame` and `SensirionI2CRxFrame` +with a buffer sized the amount of data to read times 1.5. This is needed to +account for the CRC which is added after every second byte. After that you can +build your frame by first calling `addCommand()` to add the command at the +beginning of the frame. Information about the different COMMANDs can be found +on the data sheet of your sensor. Then you can add data to the frame by using +different add member functions. See the code below for examples. + +To send this frame to the sensor you first need to initialize a Wire object. +Don't forget to also call the `.begin()` function with the right configuration. +Then call the static function `sendFrame()` form `SensirionI2CCommunication` as +shown below. You can find the ADDRESS on the data sheet of the sensor. You also +need to replace `WIREOBJECT` with the initialized Wire object. Then wait the in +the data sheet documented `READ_DELAY` before receiving the reply from the +sensor by calling `receiveFrame()` with the same Wire object. + +You then can decode the frame by using the different get members to convert the +received data to desired data types. + +All functions return a error code if an error occurs during execution and zero +otherwise. + +```cpp +uint8_t txBuffer[256]; +uint8_t rxBuffer[256]; + +SensirionShdlcTxFrame txFrame(txBuffer, 256); +SensirionShdlcRxFrame rxFrame(rxBuffer, 256); + +txFrame.addCommand(COMMAND); + +txFrame.addUInt8(UINT8); +txFrame.addUInt32(UINT32); + +SensirionShdlcCommunication::sendFrame(ADDRESS, txFrame, WIREOBJECT); + +delay(READ_DELAY); + +SensirionShdlcCommunication::receiveFrame(ADDRESS, rxFrame, WIREOBJECT); + +rxFrame.getUInt16(UINT16); +rxFrame.getFloat(FLOAT); + +``` diff --git a/lib/lib_i2c/Sensirion_Core/examples/AllCommandsI2c/AllCommandsI2c.ino b/lib/lib_i2c/Sensirion_Core/examples/AllCommandsI2c/AllCommandsI2c.ino new file mode 100644 index 000000000..49b10d868 --- /dev/null +++ b/lib/lib_i2c/Sensirion_Core/examples/AllCommandsI2c/AllCommandsI2c.ino @@ -0,0 +1,62 @@ +#include +#include +#include + +uint8_t txBuffer[256]; +uint8_t rxBuffer[256]; + +SensirionI2CTxFrame txFrame(txBuffer, 256); +SensirionI2CRxFrame rxFrame(rxBuffer, 256); + +void setup() { + Wire.begin(); +} + +void loop() { + uint16_t mockCommand = 42; + uint16_t error = txFrame.addCommand(mockCommand); + + uint32_t mockUInt32 = 42; + error |= txFrame.addUInt32(mockUInt32); + + int32_t mockInt32 = 42; + error |= txFrame.addInt32(mockInt32); + + uint16_t mockUInt16 = 42; + error |= txFrame.addUInt16(mockUInt16); + + int16_t mockInt16 = 42; + error |= txFrame.addInt16(mockInt16); + + uint8_t mockUInt8 = 42; + error |= txFrame.addUInt8(mockUInt8); + + int8_t mockInt8 = 42; + error |= txFrame.addInt8(mockInt8); + + float mockFloat = 42.0f; + error |= txFrame.addFloat(mockFloat); + + bool mockBool = true; + error |= txFrame.addBool(mockBool); + + uint8_t mockBytes[] = {42, 42, 42, 42}; + error |= txFrame.addBytes(mockBytes, 4); + + uint8_t mockAddress = 42; + + error |= SensirionI2CCommunication::sendFrame(mockAddress, txFrame, Wire); + + size_t mockNumBytes = 42; + error |= SensirionI2CCommunication::receiveFrame(mockAddress, mockNumBytes, + rxFrame, Wire); + + error |= rxFrame.getUInt32(mockUInt32); + error |= rxFrame.getInt32(mockInt32); + error |= rxFrame.getUInt16(mockUInt16); + error |= rxFrame.getInt16(mockInt16); + error |= rxFrame.getUInt8(mockUInt8); + error |= rxFrame.getInt8(mockInt8); + error |= rxFrame.getFloat(mockFloat); + error |= rxFrame.getBytes(mockBytes, 4); +} diff --git a/lib/lib_i2c/Sensirion_Core/examples/AllCommandsShdlc/AllCommandsShdlc.ino b/lib/lib_i2c/Sensirion_Core/examples/AllCommandsShdlc/AllCommandsShdlc.ino new file mode 100644 index 000000000..2ff07bb94 --- /dev/null +++ b/lib/lib_i2c/Sensirion_Core/examples/AllCommandsShdlc/AllCommandsShdlc.ino @@ -0,0 +1,73 @@ +#include +#include + +uint8_t txBuffer[256]; +uint8_t rxBuffer[256]; + +SensirionShdlcTxFrame txFrame(txBuffer, 256); +SensirionShdlcRxFrame rxFrame(rxBuffer, 256); + +void setup() { + Serial.begin(115200); +} + +void loop() { + uint8_t mockCommand = 42; + uint8_t mockAddress = 42; + uint8_t mockDataLength = 42; + uint16_t error = txFrame.begin(mockCommand, mockAddress, mockDataLength); + + uint32_t mockUInt32 = 42; + error |= txFrame.addUInt32(mockUInt32); + + int32_t mockInt32 = 42; + error |= txFrame.addInt32(mockInt32); + + uint16_t mockUInt16 = 42; + error |= txFrame.addUInt16(mockUInt16); + + int16_t mockInt16 = 42; + error |= txFrame.addInt16(mockInt16); + + uint8_t mockUInt8 = 42; + error |= txFrame.addUInt8(mockUInt8); + + int8_t mockInt8 = 42; + error |= txFrame.addInt8(mockInt8); + + float mockFloat = 42.0f; + error |= txFrame.addFloat(mockFloat); + + bool mockBool = true; + error |= txFrame.addBool(mockBool); + + uint8_t mockBytes[] = {42, 42, 42, 42}; + error |= txFrame.addBytes(mockBytes, 4); + + error |= txFrame.finish(); + + error |= SensirionShdlcCommunication::sendFrame(txFrame, Serial); + + error |= SensirionShdlcCommunication::sendAndReceiveFrame( + Serial, txFrame, rxFrame, 10000000); + + error |= + SensirionShdlcCommunication::receiveFrame(rxFrame, Serial, 1000000); + + error |= rxFrame.getUInt32(mockUInt32); + error |= rxFrame.getInt32(mockInt32); + error |= rxFrame.getUInt16(mockUInt16); + error |= rxFrame.getInt16(mockInt16); + error |= rxFrame.getUInt8(mockUInt8); + error |= rxFrame.getInt8(mockInt8); + error |= rxFrame.getFloat(mockFloat); + error |= rxFrame.getBytes(mockBytes, 4); + + mockCommand = rxFrame.getCommand(); + mockAddress = rxFrame.getAddress(); + mockDataLength = rxFrame.getDataLength(); + uint8_t mockState = rxFrame.getState(); + if (mockState) { + // There is an error in the device. + } +} diff --git a/lib/lib_i2c/Sensirion_Core/keywords.txt b/lib/lib_i2c/Sensirion_Core/keywords.txt new file mode 100644 index 000000000..910b12264 --- /dev/null +++ b/lib/lib_i2c/Sensirion_Core/keywords.txt @@ -0,0 +1,56 @@ +####################################### +# Syntax Coloring Map +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### +SensirionShdlcCommunication KEYWORD1 +SensirionShdlcRxFrame KEYWORD1 +SensirionShdlcTxFrame KEYWORD1 +SensirionI2CTxFrame KEYWORD1 +SensirionI2CRxFrame KEYWORD1 +SensirionI2CCommunication KEYWORD1 + +# SensirionErrors.h +HighLevelError KEYWORD1 +LowLevelError KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### +sendFrame KEYWORD2 +receiveFrame KEYWORD2 +sendAndReceiveFrame KEYWORD2 +addUInt32 KEYWORD2 +addInt32 KEYWORD2 +addUInt16 KEYWORD2 +addInt16 KEYWORD2 +addUInt8 KEYWORD2 +addInt8 KEYWORD2 +addFloat KEYWORD2 +addBytes KEYWORD2 +addBool KEYWORD2 +addCommand KEYWORD2 +begin KEYWORD2 +finish KEYWORD2 +reset KEYWORD2 +getUInt32 KEYWORD2 +getInt32 KEYWORD2 +getUInt16 KEYWORD2 +getInt16 KEYWORD2 +getUInt8 KEYWORD2 +getInt8 KEYWORD2 +getFloat KEYWORD2 +getBytes KEYWORD2 +getCommand KEYWORD2 +getAddress KEYWORD2 +getDataLength KEYWORD2 +getState KEYWORD2 + +# SensirionErrors.h +errorToString KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### diff --git a/lib/lib_i2c/Sensirion_Core/library.properties b/lib/lib_i2c/Sensirion_Core/library.properties new file mode 100644 index 000000000..9c5934fa1 --- /dev/null +++ b/lib/lib_i2c/Sensirion_Core/library.properties @@ -0,0 +1,9 @@ +name=Sensirion Core +version=0.6.0 +author=Sensirion +maintainer=Sensirion +sentence=Library containing code base for Sensirion Sensor Libraries. +paragraph=All Libraries for Sensirion Sensors use this library as a code base. In this library the Sensirion specific parts for I2C and UART communication are implemented. It provides dynamic frame construction, checksum calculation and buffer handling. +category=Communication +url=https://github.com/Sensirion/arduino-core/ +includes=SensirionCore.h diff --git a/lib/lib_i2c/Sensirion_Core/src/SensirionCore.h b/lib/lib_i2c/Sensirion_Core/src/SensirionCore.h new file mode 100644 index 000000000..9222b61dd --- /dev/null +++ b/lib/lib_i2c/Sensirion_Core/src/SensirionCore.h @@ -0,0 +1,46 @@ +/* + * 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 _SENSIRION_CORE_H_ +#define _SENSIRION_CORE_H_ + +#include "SensirionCrc.h" +#include "SensirionErrors.h" +#include "SensirionRxFrame.h" + +#include "SensirionShdlcCommunication.h" +#include "SensirionShdlcRxFrame.h" +#include "SensirionShdlcTxFrame.h" + +#include "SensirionI2CCommunication.h" +#include "SensirionI2CRxFrame.h" +#include "SensirionI2CTxFrame.h" + +#endif /* _SENSIRION_CORE_H_ */ diff --git a/lib/lib_i2c/Sensirion_Core/src/SensirionCoreArduinoLibrary.h b/lib/lib_i2c/Sensirion_Core/src/SensirionCoreArduinoLibrary.h new file mode 100644 index 000000000..fcfeb6dca --- /dev/null +++ b/lib/lib_i2c/Sensirion_Core/src/SensirionCoreArduinoLibrary.h @@ -0,0 +1,51 @@ +/* + * + * THIS IS A LEGACY FILE AND WILL BE REMOVED SOON. + * + * 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 _SENSIRION_CORE_ARDUINO_LIBRARY_H_ +#define _SENSIRION_CORE_ARDUINO_LIBRARY_H_ + +#pragma GCC warning \ + "Legacy file SensirionCoreArdunioLibrary.h included. Please include SensirionCore.h instead." + +#include "SensirionErrors.h" +#include "SensirionRxFrame.h" + +#include "SensirionShdlcCommunication.h" +#include "SensirionShdlcRxFrame.h" +#include "SensirionShdlcTxFrame.h" + +#include "SensirionI2CCommunication.h" +#include "SensirionI2CRxFrame.h" +#include "SensirionI2CTxFrame.h" + +#endif /* _SENSIRION_CORE_ARDUION_LIBRARY_H_ */ diff --git a/lib/lib_i2c/Sensirion_Core/src/SensirionCrc.cpp b/lib/lib_i2c/Sensirion_Core/src/SensirionCrc.cpp new file mode 100644 index 000000000..25b8234aa --- /dev/null +++ b/lib/lib_i2c/Sensirion_Core/src/SensirionCrc.cpp @@ -0,0 +1,64 @@ +/* + * 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 "SensirionCrc.h" + +uint8_t generateCRCGeneric(const uint8_t* data, size_t count, uint8_t init, + uint8_t polynomial) { + uint8_t crc = init; + + /* calculates 8-Bit checksum with given polynomial */ + for (size_t current_byte = 0; current_byte < count; ++current_byte) { + crc ^= (data[current_byte]); + for (uint8_t crc_bit = 8; crc_bit > 0; --crc_bit) { + if (crc & 0x80) + crc = (crc << 1) ^ polynomial; + else + crc = (crc << 1); + } + } + return crc; +} + +uint8_t generateCRC31_ff(const uint8_t* data, size_t count) { + return generateCRCGeneric(data, count, 0xff, 0x31); +} + +uint8_t generateCRC31_00(const uint8_t* data, size_t count) { + return generateCRCGeneric(data, count, 0x00, 0x31); +} + +uint8_t generateCRC(const uint8_t* data, size_t count, CrcPolynomial type) { + if (CRC31_00 == type) { + return generateCRC31_00(data, count); + } + return generateCRC31_ff(data, count); +} diff --git a/lib/lib_i2c/Sensirion_Core/src/SensirionCrc.h b/lib/lib_i2c/Sensirion_Core/src/SensirionCrc.h new file mode 100644 index 000000000..904fc9286 --- /dev/null +++ b/lib/lib_i2c/Sensirion_Core/src/SensirionCrc.h @@ -0,0 +1,59 @@ +/* + * 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 _SENSIRION_CRC_H_ +#define _SENSIRION_CRC_H_ + +#include +#include + +enum CrcPolynomial : uint8_t { + CRC31_00 = 0x0, + CRC31_ff = 0x1, +}; + +uint8_t generateCRCGeneric(const uint8_t* data, size_t count, uint8_t init, + uint8_t polynomial); + +uint8_t generateCRC31_ff(const uint8_t* data, size_t count); + +uint8_t generateCRC31_00(const uint8_t* data, size_t count); + +/** + * @brief Generate a crc for data given a polynomial type + * + * @param data data to calculate CRC for + * @param count the array size of data + * @param poly CRC polynomal to use + */ +uint8_t generateCRC(const uint8_t* data, size_t count, CrcPolynomial type); + +#endif /* _SENSIRION_CRC_H_ */ diff --git a/lib/lib_i2c/Sensirion_Core/src/SensirionErrors.cpp b/lib/lib_i2c/Sensirion_Core/src/SensirionErrors.cpp new file mode 100644 index 000000000..b0d7ffe62 --- /dev/null +++ b/lib/lib_i2c/Sensirion_Core/src/SensirionErrors.cpp @@ -0,0 +1,148 @@ +/* + * 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. + */ +#include "SensirionErrors.h" + +#include +#include +#include + +void errorToString(uint16_t error, char errorMessage[], + size_t errorMessageSize) { + + uint16_t highLevelError = error & 0xFF00; + uint16_t lowLevelError = error & 0x00FF; + + if (error & HighLevelError::SensorSpecificError) { + snprintf(errorMessage, errorMessageSize, "Sensor specific error: 0x%2x", + lowLevelError); + return; + } + + switch (highLevelError) { + case HighLevelError::NoError: + if (!error) { + strncpy(errorMessage, "No error", errorMessageSize); + return; + } + break; + case HighLevelError::WriteError: + switch (lowLevelError) { + case LowLevelError::SerialWriteError: + strncpy(errorMessage, "Error writing to serial", + errorMessageSize); + return; + case LowLevelError::InternalBufferSizeError: + strncpy(errorMessage, + "Data too long to fit in transmit buffer", + errorMessageSize); + return; + case LowLevelError::I2cAddressNack: + strncpy(errorMessage, + "Received NACK on transmit of address", + errorMessageSize); + return; + case LowLevelError::I2cDataNack: + strncpy(errorMessage, "Received NACK on transmit of data", + errorMessageSize); + return; + case LowLevelError::I2cOtherError: + strncpy(errorMessage, "Error writing to I2C bus", + errorMessageSize); + return; + } + break; + case HighLevelError::ReadError: + switch (lowLevelError) { + case LowLevelError::NonemptyFrameError: + strncpy(errorMessage, "Frame already contains data", + errorMessageSize); + return; + case LowLevelError::TimeoutError: + strncpy(errorMessage, "Timeout while reading data", + errorMessageSize); + return; + case LowLevelError::ChecksumError: + strncpy(errorMessage, "Checksum is wrong", + errorMessageSize); + return; + case LowLevelError::CRCError: + strncpy(errorMessage, "Wrong CRC found", errorMessageSize); + return; + case LowLevelError::WrongNumberBytesError: + strncpy(errorMessage, "Number of bytes not a multiple of 3", + errorMessageSize); + return; + case LowLevelError::NotEnoughDataError: + strncpy(errorMessage, "Not enough data received", + errorMessageSize); + return; + case LowLevelError::InternalBufferSizeError: + strncpy(errorMessage, "Internal I2C buffer too small", + errorMessageSize); + return; + } + break; + case HighLevelError::ExecutionError: { + char format[] = "Execution error, status register: 0x%x"; + snprintf(errorMessage, errorMessageSize, format, lowLevelError); + return; + } + case HighLevelError::TxFrameError: + switch (lowLevelError) { + case LowLevelError::BufferSizeError: + strncpy(errorMessage, "Not enough space in buffer", + errorMessageSize); + return; + } + break; + case HighLevelError::RxFrameError: + switch (lowLevelError) { + case LowLevelError::BufferSizeError: + strncpy(errorMessage, "Not enough space in buffer", + errorMessageSize); + return; + case LowLevelError::NoDataError: + strncpy(errorMessage, "No more data in frame", + errorMessageSize); + return; + case LowLevelError::RxAddressError: + strncpy(errorMessage, "Wrong address in return frame", + errorMessageSize); + return; + case LowLevelError::RxCommandError: + strncpy(errorMessage, "Wrong command in return frame", + errorMessageSize); + return; + } + } + strncpy(errorMessage, "Error processing error", errorMessageSize); + return; +} diff --git a/lib/lib_i2c/Sensirion_Core/src/SensirionErrors.h b/lib/lib_i2c/Sensirion_Core/src/SensirionErrors.h new file mode 100644 index 000000000..268d9754f --- /dev/null +++ b/lib/lib_i2c/Sensirion_Core/src/SensirionErrors.h @@ -0,0 +1,87 @@ +/* + * 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 _SENSIRION_ERRORS_H_ +#define _SENSIRION_ERRORS_H_ + +#include +#include + +enum HighLevelError : uint16_t { + // general errors + NoError = 0, + WriteError = 0x0100, + ReadError = 0x0200, + TxFrameError = 0x0300, + RxFrameError = 0x0400, + // shdlc errors + ExecutionError = 0x0500, + // i2c errors + + // Sensor specific errors. All errors higher than that are depending on the + // sensor used. + SensorSpecificError = 0x8000, +}; + +enum LowLevelError : uint16_t { + // general errors + NonemptyFrameError, + NoDataError, + BufferSizeError, + // shdlc errors + StopByteError, + ChecksumError, + TimeoutError, + RxCommandError, + RxAddressError, + SerialWriteError, + // i2c errors + WrongNumberBytesError, + CRCError, + I2cAddressNack, + I2cDataNack, + I2cOtherError, + NotEnoughDataError, + InternalBufferSizeError, +}; + +/** + * errorToString() - Convert error code to a human readable error message + * + * @param error Error code to be converted. + * @param errorMessage String where the error text can be + * stored. + * @param errorMessageSize Size in bytes of the string buffer for the error + * message. + */ +void errorToString(uint16_t error, char errorMessage[], + size_t errorMessageSize); + +#endif /* _SENSIRION_ERRORS_H_ */ diff --git a/lib/lib_i2c/Sensirion_Core/src/SensirionI2CCommunication.cpp b/lib/lib_i2c/Sensirion_Core/src/SensirionI2CCommunication.cpp new file mode 100644 index 000000000..2d8b7acab --- /dev/null +++ b/lib/lib_i2c/Sensirion_Core/src/SensirionI2CCommunication.cpp @@ -0,0 +1,119 @@ +/* + * 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. + */ +#include "SensirionI2CCommunication.h" + +#include +#include + +#include "Arduino.h" +#include "SensirionCrc.h" +#include "SensirionErrors.h" +#include "SensirionI2CRxFrame.h" +#include "SensirionI2CTxFrame.h" + +static void clearRxBuffer(TwoWire& i2cBus) { + while (i2cBus.available()) { + (void)i2cBus.read(); + } +} + +uint16_t SensirionI2CCommunication::sendFrame(uint8_t address, + SensirionI2CTxFrame& frame, + TwoWire& i2cBus) { + i2cBus.beginTransmission(address); + size_t writtenBytes = i2cBus.write(frame._buffer, frame._index); + uint8_t i2c_error = i2cBus.endTransmission(); + if (writtenBytes != frame._index) { + return WriteError | I2cOtherError; + } + // translate Arduino errors, see + // https://www.arduino.cc/en/Reference/WireEndTransmission + switch (i2c_error) { + case 0: + return NoError; + case 1: + return WriteError | InternalBufferSizeError; + case 2: + return WriteError | I2cAddressNack; + case 3: + return WriteError | I2cDataNack; + default: + return WriteError | I2cOtherError; + } +} + +uint16_t SensirionI2CCommunication::receiveFrame(uint8_t address, + size_t numBytes, + SensirionI2CRxFrame& frame, + TwoWire& i2cBus, + CrcPolynomial poly) { + size_t readAmount; + size_t i = 0; + +#ifdef I2C_BUFFER_LENGTH + const uint8_t sizeBuffer = + (static_cast(I2C_BUFFER_LENGTH) / static_cast(3)) * 3; +#elif defined(BUFFER_LENGTH) + const uint8_t sizeBuffer = + (static_cast(BUFFER_LENGTH) / static_cast(3)) * 3; +#else + const uint8_t sizeBuffer = 30; +#endif + + if (numBytes % 3) { + return ReadError | WrongNumberBytesError; + } + if ((numBytes / 3) * 2 > frame._bufferSize) { + return ReadError | BufferSizeError; + } + if (numBytes > sizeBuffer) { + return ReadError | InternalBufferSizeError; + } + + readAmount = i2cBus.requestFrom(address, static_cast(numBytes), + static_cast(true)); + if (numBytes != readAmount) { + return ReadError | NotEnoughDataError; + } + do { + frame._buffer[i++] = i2cBus.read(); + frame._buffer[i++] = i2cBus.read(); + uint8_t actualCRC = i2cBus.read(); + uint8_t expectedCRC = generateCRC(&frame._buffer[i - 2], 2, poly); + if (actualCRC != expectedCRC) { + clearRxBuffer(i2cBus); + return ReadError | CRCError; + } + readAmount -= 3; + } while (readAmount > 0); + frame._numBytes = i; + return NoError; +} diff --git a/lib/lib_i2c/Sensirion_Core/src/SensirionI2CCommunication.h b/lib/lib_i2c/Sensirion_Core/src/SensirionI2CCommunication.h new file mode 100644 index 000000000..98aead75a --- /dev/null +++ b/lib/lib_i2c/Sensirion_Core/src/SensirionI2CCommunication.h @@ -0,0 +1,83 @@ +/* + * 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 SENSIRION_I2C_COMMUNICATION_H_ +#define SENSIRION_I2C_COMMUNICATION_H_ + +#include +#include + +#include "Arduino.h" +#include "Wire.h" + +#include "SensirionI2CRxFrame.h" +#include "SensirionI2CTxFrame.h" + +class SensirionI2CTxFrame; +class SensirionI2CRxFrame; + +/* + * SensirionI2CCommunication - Class which is responsible for the communication + * via a I2C bus. It provides functionality to send and receive frames from a + * Sensirion sensor. The data is sent and received in a SensirionI2cTxFrame or + * SensirionI2cRxFrame respectively. + */ +class SensirionI2CCommunication { + public: + /** + * sendFrame() - Sends frame to sensor + * + * @param address I2C address of the sensor. + * @param frame Tx frame object containing a finished frame to send to + * the sensor. + * @param i2cBus TwoWire object to communicate with the sensor. + * + * @return NoError on success, an error code otherwise + */ + static uint16_t sendFrame(uint8_t address, SensirionI2CTxFrame& frame, + TwoWire& i2cBus); + + /** + * receiveFrame() - Receive Frame from sensor + * + * @param address I2C address of the sensor. + * @param numBytes Number of bytes to receive. + * @param frame Rx frame to store the received data in. + * @param i2cBus TwoWire object to communicate with the sensor. + * @param poly CRC polynomal to use. Defaults to 0x31 with init 0xFF + * + * @return NoError on success, an error code otherwise + */ + static uint16_t receiveFrame(uint8_t address, size_t numBytes, + SensirionI2CRxFrame& frame, TwoWire& i2cBus, + CrcPolynomial poly = CRC31_ff); +}; + +#endif /* SENSIRION_I2C_COMMUNICATION_H_ */ diff --git a/lib/lib_i2c/Sensirion_Core/src/SensirionI2CRxFrame.h b/lib/lib_i2c/Sensirion_Core/src/SensirionI2CRxFrame.h new file mode 100644 index 000000000..e15cfa28b --- /dev/null +++ b/lib/lib_i2c/Sensirion_Core/src/SensirionI2CRxFrame.h @@ -0,0 +1,64 @@ +/* + * 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 SENSIRION_I2C_RX_FRAME_H_ +#define SENSIRION_I2C_RX_FRAME_H_ + +#include +#include + +#include "SensirionI2CCommunication.h" +#include "SensirionRxFrame.h" + +/** + * SenirionI2CRxFrame - Class which decodes the through I2C received data into + * common data types. It contains a buffer which is filled by the + * SensirionI2CCommunication class. By calling the different decode function + * inherited from the SensirionRxFrame base class the raw data can be decoded + * into different data types. + */ +class SensirionI2CRxFrame : public SensirionRxFrame { + + friend class SensirionI2CCommunication; + + public: + /** + * Constructor + * + * @param buffer Buffer in which the receive frame will be + * stored. + * @param bufferSize Number of bytes in the buffer for the receive frame. + * + */ + SensirionI2CRxFrame(uint8_t buffer[], size_t bufferSize) + : SensirionRxFrame(buffer, bufferSize){}; +}; + +#endif /* SENSIRION_I2C_RX_FRAME_H_ */ diff --git a/lib/lib_i2c/Sensirion_Core/src/SensirionI2CTxFrame.cpp b/lib/lib_i2c/Sensirion_Core/src/SensirionI2CTxFrame.cpp new file mode 100644 index 000000000..67999f86c --- /dev/null +++ b/lib/lib_i2c/Sensirion_Core/src/SensirionI2CTxFrame.cpp @@ -0,0 +1,144 @@ +/* + * 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, + * 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 + * 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 "SensirionI2CTxFrame.h" + +#include +#include + +#include "SensirionCrc.h" +#include "SensirionErrors.h" + +SensirionI2CTxFrame::SensirionI2CTxFrame(uint8_t buffer[], size_t bufferSize, + size_t numCommandBytes, + CrcPolynomial poly) + : _buffer(buffer), _bufferSize(bufferSize), _index(numCommandBytes), + _numCommandBytes(numCommandBytes), _polynomial_type(poly) { +} + +SensirionI2CTxFrame::SensirionI2CTxFrame(uint8_t buffer[], size_t bufferSize, + CrcPolynomial poly) + : SensirionI2CTxFrame(buffer, bufferSize, 2, poly) { +} + +SensirionI2CTxFrame SensirionI2CTxFrame::createWithUInt8Command( + uint8_t command, uint8_t buffer[], size_t bufferSize, CrcPolynomial poly) { + SensirionI2CTxFrame instance = + SensirionI2CTxFrame(buffer, bufferSize, 1, poly); + instance._buffer[0] = command; + return instance; +} + +SensirionI2CTxFrame SensirionI2CTxFrame::createWithUInt16Command( + uint16_t command, uint8_t buffer[], size_t bufferSize, CrcPolynomial poly) { + SensirionI2CTxFrame instance = + SensirionI2CTxFrame(buffer, bufferSize, 2, poly); + instance._buffer[0] = static_cast((command & 0xFF00) >> 8); + instance._buffer[1] = static_cast((command & 0x00FF) >> 0); + return instance; +} + +uint16_t SensirionI2CTxFrame::addCommand(uint16_t command) { + if (_bufferSize < 2) { + return TxFrameError | BufferSizeError; + } + _buffer[0] = static_cast((command & 0xFF00) >> 8); + _buffer[1] = static_cast((command & 0x00FF) >> 0); + return NoError; +} + +uint16_t SensirionI2CTxFrame::addUInt32(uint32_t data) { + uint16_t error = _addByte(static_cast((data & 0xFF000000) >> 24)); + error |= _addByte(static_cast((data & 0x00FF0000) >> 16)); + error |= _addByte(static_cast((data & 0x0000FF00) >> 8)); + error |= _addByte(static_cast((data & 0x000000FF) >> 0)); + return error; +} + +uint16_t SensirionI2CTxFrame::addInt32(int32_t data) { + return addUInt32(static_cast(data)); +} + +uint16_t SensirionI2CTxFrame::addUInt16(uint16_t data) { + uint16_t error = _addByte(static_cast((data & 0xFF00) >> 8)); + error |= _addByte(static_cast((data & 0x00FF) >> 0)); + return error; +} + +uint16_t SensirionI2CTxFrame::addInt16(int16_t data) { + return addUInt16(static_cast(data)); +} + +uint16_t SensirionI2CTxFrame::addUInt8(uint8_t data) { + return _addByte(data); +} + +uint16_t SensirionI2CTxFrame::addInt8(int8_t data) { + return _addByte(static_cast(data)); +} + +uint16_t SensirionI2CTxFrame::addBool(bool data) { + return _addByte(static_cast(data)); +} + +uint16_t SensirionI2CTxFrame::addFloat(float data) { + union { + uint32_t uInt32Data; + float floatData; + } convert; + + convert.floatData = data; + return addUInt32(convert.uInt32Data); +} + +uint16_t SensirionI2CTxFrame::addBytes(const uint8_t data[], + size_t dataLength) { + uint16_t error = 0; + for (size_t i = 0; i < dataLength; i++) { + error |= _addByte(data[i]); + } + return error; +} + +uint16_t SensirionI2CTxFrame::_addByte(uint8_t data) { + if (_bufferSize <= _index) { + return TxFrameError | BufferSizeError; + } + _buffer[_index++] = data; + if ((_index - _numCommandBytes) % 3 == 2) { + if (_bufferSize <= _index) { + return TxFrameError | BufferSizeError; + } + uint8_t crc = generateCRC(&_buffer[_index - 2], 2, _polynomial_type); + _buffer[_index++] = crc; + } + return NoError; +} diff --git a/lib/lib_i2c/Sensirion_Core/src/SensirionI2CTxFrame.h b/lib/lib_i2c/Sensirion_Core/src/SensirionI2CTxFrame.h new file mode 100644 index 000000000..3e5cee3df --- /dev/null +++ b/lib/lib_i2c/Sensirion_Core/src/SensirionI2CTxFrame.h @@ -0,0 +1,197 @@ +/* + * 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 SENSIRION_I2C_TX_FRAME_H_ +#define SENSIRION_I2C_TX_FRAME_H_ + +#include +#include + +#include "SensirionCrc.h" +#include "SensirionI2CCommunication.h" + +/* + * SensirionI2CTxFrame - Class which helps to build a correct I2C frame for + * Sensirion Sensors. The different addDatatype() functions add the frame data + * and the addCommand() function writes the command at the beginning. Using + * these functions one can easily construct a I2C frame for Sensirion sensors. + */ +class SensirionI2CTxFrame { + + friend class SensirionI2CCommunication; + + public: + /** + * Factory to create a SensirionI2CTxFrame using a UInt8 command. + * + * @param command Command to add to the send frame. + * @param buffer Buffer in which the send frame will be stored. + * @param bufferSize Number of bytes in the buffer for the send frame. + * @param poly CRC polynomal to use. Defaults to 0x31 with init 0xFF + * + * @return the constructed SensirionI2CTxFrame. + */ + static SensirionI2CTxFrame + createWithUInt8Command(uint8_t command, uint8_t buffer[], size_t bufferSize, + CrcPolynomial poly = CRC31_ff); + + /** + * Factory to create a SensirionI2CTxFrame using a UInt16 command. + * + * @param command Command to add to the send frame. + * @param buffer Buffer in which the send frame will be stored. + * @param bufferSize Number of bytes in the buffer for the send frame. + * @param poly CRC polynomal to use. Defaults to 0x31 with init 0xFF + * + * @return the constructed SensirionI2CTxFrame. + */ + static SensirionI2CTxFrame + createWithUInt16Command(uint16_t command, uint8_t buffer[], + size_t bufferSize, CrcPolynomial poly = CRC31_ff); + + /** + * Constructor + * + * @param buffer Buffer in which the send frame will be stored. + * @param bufferSize Number of bytes in the buffer for the send frame. + * @param poly CRC polynomal to use. Defaults to 0x31 with init 0xFF + * + * @deprecated Use createWithUInt16Command() instead + */ + SensirionI2CTxFrame(uint8_t buffer[], size_t bufferSize, + CrcPolynomial poly = CRC31_ff); + + /** + * addCommand() - Add command to the send frame. + * + * @param command Command to add to the send frame. + * + * @return NoError on success, an error code otherwise + * + * @deprecated Use createWithUInt16Command() instead + */ + uint16_t addCommand(uint16_t command); + + /** + * addUInt32() - Add unsigned 32bit integer to the send frame. + * + * @param data Unsigned 32bit integer to add to the send frame. + * + * @return NoError on success, an error code otherwise + */ + uint16_t addUInt32(uint32_t data); + + /** + * addInt32() - Add signed 32bit integer to the send frame. + * + * @param data Signed 32bit integer to add to the send frame. + * + * @return NoError on success, an error code otherwise + */ + uint16_t addInt32(int32_t data); + + /** + * addUInt16() - Add unsigned 16bit integer to the send frame. + * + * @param data Unsigned 16bit integer to add to the send frame. + * + * @return NoError on success, an error code otherwise + */ + uint16_t addUInt16(uint16_t data); + + /** + * addInt16() - Add signed 16bit integer to the send frame. + * + * @param data Signed 16bit integer to add to the send frame. + * + * @return NoError on success, an error code otherwise + */ + uint16_t addInt16(int16_t data); + + /** + * addUInt8() - Add unsigned 8bit integer to the send frame. + * + * @param data Unsigned 8bit integer to add to the send frame. + * + * @return NoError on success, an error code otherwise + */ + uint16_t addUInt8(uint8_t data); + + /** + * addInt8() - Add signed 8bit integer to the send frame. + * + * @param data Signed 8bit integer to add to the send frame. + * + * @return NoError on success, an error code otherwise + */ + uint16_t addInt8(int8_t data); + + /** + * addBool() - Add boolean to the send frame. + * + * @param data Boolean to add to the send frame. + * + * @return NoError on success, an error code otherwise + */ + uint16_t addBool(bool data); + + /** + * addFloat() - Add float to the send frame. + * + * @param data Float to add to the send frame. + * + * @return NoError on success, an error code otherwise + */ + uint16_t addFloat(float data); + + /** + * addBytes() - Add byte array to the send frame. + * + * @param data Byte array to add to the send frame. + * @param dataLength Number of bytes to add to the send frame. + * + * @return NoError on success, an error code otherwise + */ + uint16_t addBytes(const uint8_t data[], size_t dataLength); + + private: + SensirionI2CTxFrame(uint8_t buffer[], size_t bufferSize, + size_t numCommandBytes, CrcPolynomial poly = CRC31_ff); + + uint16_t _addByte(uint8_t data); + + uint8_t* _buffer; + size_t _bufferSize; + size_t _index; + size_t _numCommandBytes; + CrcPolynomial _polynomial_type; +}; + +#endif /* SENSIRION_I2C_TX_FRAME_H_ */ diff --git a/lib/lib_i2c/Sensirion_Core/src/SensirionRxFrame.cpp b/lib/lib_i2c/Sensirion_Core/src/SensirionRxFrame.cpp new file mode 100644 index 000000000..c653c250d --- /dev/null +++ b/lib/lib_i2c/Sensirion_Core/src/SensirionRxFrame.cpp @@ -0,0 +1,129 @@ +/* + * 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. + */ + +#include "SensirionRxFrame.h" + +#include +#include + +#include "SensirionErrors.h" + +SensirionRxFrame::SensirionRxFrame(uint8_t buffer[], size_t bufferSize) + : _buffer(buffer), _bufferSize(bufferSize), _index(0), _numBytes(0) { +} + +uint16_t SensirionRxFrame::getUInt32(uint32_t& data) { + if (_numBytes < 4) { + return RxFrameError | NoDataError; + } + data = static_cast(_buffer[_index++]) << 24; + data |= static_cast(_buffer[_index++]) << 16; + data |= static_cast(_buffer[_index++]) << 8; + data |= static_cast(_buffer[_index++]); + _numBytes -= 4; + return NoError; +} + +uint16_t SensirionRxFrame::getInt32(int32_t& data) { + uint32_t ret; + uint16_t error = getUInt32(ret); + data = static_cast(ret); + return error; +} + +uint16_t SensirionRxFrame::getUInt16(uint16_t& data) { + if (_numBytes < 2) { + return RxFrameError | NoDataError; + } + data = static_cast(_buffer[_index++]) << 8; + data |= static_cast(_buffer[_index++]); + _numBytes -= 2; + return NoError; +} + +uint16_t SensirionRxFrame::getInt16(int16_t& data) { + uint16_t ret; + uint16_t error = getUInt16(ret); + data = static_cast(ret); + return error; +} + +uint16_t SensirionRxFrame::getUInt8(uint8_t& data) { + if (_numBytes < 1) { + return RxFrameError | NoDataError; + } + data = _buffer[_index++]; + _numBytes -= 1; + return NoError; +} + +uint16_t SensirionRxFrame::getInt8(int8_t& data) { + if (_numBytes < 1) { + return RxFrameError | NoDataError; + } + data = static_cast(_buffer[_index++]); + _numBytes -= 1; + return NoError; +} + +uint16_t SensirionRxFrame::getBool(bool& data) { + if (_numBytes < 1) { + return RxFrameError | NoDataError; + } + data = static_cast(_buffer[_index++]); + _numBytes -= 1; + return NoError; +} + +uint16_t SensirionRxFrame::getFloat(float& data) { + union { + uint32_t uInt32Data; + float floatData; + } convert; + uint16_t error = getUInt32(convert.uInt32Data); + data = convert.floatData; + return error; +} + +uint16_t SensirionRxFrame::getBytes(uint8_t data[], size_t maxBytes) { + if (_numBytes < 1) { + return RxFrameError | NoDataError; + } + size_t readAmount = maxBytes; + if (_numBytes < maxBytes) { + readAmount = _numBytes; + } + for (size_t i = 0; i < readAmount; i++) { + data[i] = _buffer[_index++]; + } + _numBytes -= readAmount; + return NoError; +} diff --git a/lib/lib_i2c/Sensirion_Core/src/SensirionRxFrame.h b/lib/lib_i2c/Sensirion_Core/src/SensirionRxFrame.h new file mode 100644 index 000000000..a4384fb6c --- /dev/null +++ b/lib/lib_i2c/Sensirion_Core/src/SensirionRxFrame.h @@ -0,0 +1,147 @@ +/* + * 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 SENSIRION_RX_FRAME_H_ +#define SENSIRION_RX_FRAME_H_ + +#include +#include + +/** + * SenirionRxFrame - Base class for SensirionShdlcRxFrame and + * SensirionI2cRxFrame. It decodes received data into common data types. The + * data is contained in a buffer which is filled by the one of the two + * communication classes. By calling the different decode function the raw data + * can be decoded into different data types. + */ +class SensirionRxFrame { + + friend class SensirionI2CCommunication; + friend class SensirionShdlcCommunication; + + public: + /** + * Constructor + * + * @param buffer Buffer in which the receive frame will be stored. + * @param bufferSize Number of bytes in the buffer for the receive frame. + */ + SensirionRxFrame(uint8_t buffer[], size_t bufferSize); + + /** + * getUInt32() - Get unsigned 32bit integer from the received data. + * + * @param data Memory to store unsigned 32bit integer in. + * + * @return NoError on success, an error code otherwise + */ + uint16_t getUInt32(uint32_t& data); + + /** + * getInt32() - Get signed 32bit integer from the received data. + * + * @param data Memory to store signed 32bit integer in. + * + * @return NoError on success, an error code otherwise + */ + uint16_t getInt32(int32_t& data); + + /** + * getUInt16() - Get unsigned 16bit integer from the received data. + * + * @param data Memory to store unsigned 16bit integer in. + * + * @return NoError on success, an error code otherwise + */ + uint16_t getUInt16(uint16_t& data); + + /** + * getInt16() - Get signed 16bit integer from the received data. + * + * @param data Memory to store signed 16bit integer in. + * + * @return NoError on success, an error code otherwise + */ + uint16_t getInt16(int16_t& data); + + /** + * getUInt8() - Get unsigned 8bit integer from the received data. + * + * @param data Memory to store unsigned 8bit integer in. + * + * @return NoError on success, an error code otherwise + */ + uint16_t getUInt8(uint8_t& data); + + /** + * getInt8() - Get signed 8bit integer from the received data. + * + * @param data Memory to store signed 8bit integer in. + * + * @return NoError on success, an error code otherwise + */ + uint16_t getInt8(int8_t& data); + + /** + * getBool() - Get Boolean from the received data. + * + * @param data Memory to store boolean in. + * + * @return NoError on success, an error code otherwise + */ + uint16_t getBool(bool& data); + + /** + * getFloat() - Get float from the received data. + * + * @param data Memory to store float in. + * + * @return NoError on success, an error code otherwise + */ + uint16_t getFloat(float& data); + + /** + * getBytes() - Get an array of bytes from the received data. + * + * @param data Buffer to store the bytes in. + * @param maxBytes Maximal amount of bytes to read from the received data. + * + * @return NoError on success, an error code otherwise + */ + uint16_t getBytes(uint8_t data[], size_t maxBytes); + + private: + uint8_t* _buffer = 0; + size_t _bufferSize = 0; + size_t _index = 0; + size_t _numBytes = 0; +}; + +#endif /* SENSIRION_RX_FRAME_H_ */ diff --git a/lib/lib_i2c/Sensirion_Core/src/SensirionShdlcCommunication.cpp b/lib/lib_i2c/Sensirion_Core/src/SensirionShdlcCommunication.cpp new file mode 100644 index 000000000..300546ee8 --- /dev/null +++ b/lib/lib_i2c/Sensirion_Core/src/SensirionShdlcCommunication.cpp @@ -0,0 +1,184 @@ +/* + * 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. + */ +#include "SensirionShdlcCommunication.h" + +#include +#include + +#include "Arduino.h" +#include "SensirionErrors.h" +#include "SensirionShdlcRxFrame.h" +#include "SensirionShdlcTxFrame.h" + +static uint16_t readByte(uint8_t& data, Stream& serial, unsigned long startTime, + unsigned long timeoutMicros) { + do { + if (micros() - startTime > timeoutMicros) { + return ReadError | TimeoutError; + } + } while (!serial.available()); + data = serial.read(); + return NoError; +} + +static uint16_t unstuffByte(uint8_t& data, Stream& serial, + unsigned long startTime, + unsigned long timeoutMicros) { + uint16_t error = readByte(data, serial, startTime, timeoutMicros); + if (error) { + return error; + } + if (data == 0x7d) { + error = readByte(data, serial, startTime, timeoutMicros); + if (error) { + return error; + } + data = data ^ (1 << 5); + } + return NoError; +} + +uint16_t SensirionShdlcCommunication::sendFrame(SensirionShdlcTxFrame& frame, + Stream& serial) { + size_t writtenBytes = serial.write(&frame._buffer[0], frame._index); + if (writtenBytes != frame._index) { + return WriteError | SerialWriteError; + } + return NoError; +} + +uint16_t SensirionShdlcCommunication::receiveFrame( + SensirionShdlcRxFrame& frame, Stream& serial, unsigned long timeoutMicros) { + unsigned long startTime = micros(); + uint16_t error; + uint8_t dataLength; + uint8_t current = 0; + + if (frame._numBytes) { + return ReadError | NonemptyFrameError; + } + + // Wait for start byte and ignore all other bytes in case a partial frame + // is still in the receive buffer due to a previous error. + do { + error = readByte(current, serial, startTime, timeoutMicros); + if (error) { + return error; + } + } while (current != 0x7e); + + // Handle a repeated start byte which may happen + do { + error = unstuffByte(current, serial, startTime, timeoutMicros); + if (error) { + return error; + } + } while (current == 0x7e); + + frame._address = current; + error = unstuffByte(frame._command, serial, startTime, timeoutMicros); + if (error) { + return error; + } + error = unstuffByte(frame._state, serial, startTime, timeoutMicros); + if (error) { + return error; + } + error = unstuffByte(dataLength, serial, startTime, timeoutMicros); + if (error) { + return error; + } + + uint8_t checksum = + frame._address + frame._command + frame._state + dataLength; + + if (dataLength > frame._bufferSize) { + return RxFrameError | BufferSizeError; + } + + size_t i = 0; + while (i < dataLength) { + error = unstuffByte(current, serial, startTime, timeoutMicros); + if (error) { + return error; + } + frame._buffer[i] = current; + checksum += current; + i++; + } + + uint8_t expectedChecksum = ~checksum; + uint8_t actualChecksum; + error = unstuffByte(actualChecksum, serial, startTime, timeoutMicros); + if (error) { + return error; + } + if (expectedChecksum != actualChecksum) { + return ReadError | ChecksumError; + } + + uint8_t stop; + error = readByte(stop, serial, startTime, timeoutMicros); + if (error) { + return error; + } + if (stop != 0x7e) { + return ReadError | StopByteError; + } + if (frame._state & 0x7F) { + return ExecutionError | frame._state; + } + frame._dataLength = dataLength; + frame._numBytes = dataLength; + return NoError; +} + +uint16_t SensirionShdlcCommunication::sendAndReceiveFrame( + Stream& serial, SensirionShdlcTxFrame& txFrame, + SensirionShdlcRxFrame& rxFrame, unsigned long rxTimeoutMicros) { + uint16_t error; + error = SensirionShdlcCommunication::sendFrame(txFrame, serial); + if (error) { + return error; + } + error = SensirionShdlcCommunication::receiveFrame(rxFrame, serial, + rxTimeoutMicros); + if (error) { + return error; + } + if (rxFrame.getCommand() != txFrame.getCommand()) { + return RxFrameError | RxCommandError; + } + if (rxFrame.getAddress() != txFrame.getAddress()) { + return RxFrameError | RxAddressError; + } + return NoError; +} diff --git a/lib/lib_i2c/Sensirion_Core/src/SensirionShdlcCommunication.h b/lib/lib_i2c/Sensirion_Core/src/SensirionShdlcCommunication.h new file mode 100644 index 000000000..9d2dc35b1 --- /dev/null +++ b/lib/lib_i2c/Sensirion_Core/src/SensirionShdlcCommunication.h @@ -0,0 +1,95 @@ +/* + * 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 SENSIRION_SHDLC_COMMUNICATION_H_ +#define SENSIRION_SHDLC_COMMUNICATION_H_ + +#include +#include + +#include "Arduino.h" + +#include "SensirionShdlcRxFrame.h" +#include "SensirionShdlcTxFrame.h" + +class SensirionShdlcTxFrame; +class SensirionShdlcRxFrame; + +/* + * SensirionShdlcCommunication - Class which is responsible for the + * communication via a UART (Serial) interface. It provides functionality to + * send and receive frames from a Sensirion sensor. The data is sent and + * received in a SensirionShdlcTxFrame or SensirionShdlcRxFrame respectively. + */ +class SensirionShdlcCommunication { + + public: + /** + * sendFrame() - Sends frame to sensor + * + * @param frame Tx frame object containing a finished frame to send to the + * sensor. + * @param serial Stream object to communicate with the sensor. + * + * @return NoError on success, an error code otherwise + */ + static uint16_t sendFrame(SensirionShdlcTxFrame& frame, Stream& serial); + + /** + * receiveFrame() - Receive Frame from sensor + * + * @param frame Rx frame to store the received data in. + * @param serial Stream object to communicate with the sensor. + * @param timeoutMicros Timeout in micro seconds for the receive operation. + * + * @return NoError on success, an error code otherwise + */ + static uint16_t receiveFrame(SensirionShdlcRxFrame& frame, Stream& serial, + unsigned long timeoutMicros); + + /** + * sendAndReceiveFrame() - Send and receive a frame from sensor. + * + * @param serial Stream object to communicate with the sensor. + * @param txFrame Tx frame object containing a finished frame to + * send to the sensor. + * @param rxFrame Rx frame to store the received data in. + * @param rxTimeoutMicros Timeout in micro seconds for the receive + * operation. + * + * @return NoError on success, an error code otherwise + */ + static uint16_t sendAndReceiveFrame(Stream& serial, + SensirionShdlcTxFrame& txFrame, + SensirionShdlcRxFrame& rxFrame, + unsigned long rxTimeoutMicros); +}; + +#endif /* SENSIRION_SHDLC_COMMUNICATION_H_ */ diff --git a/lib/lib_i2c/Sensirion_Core/src/SensirionShdlcRxFrame.h b/lib/lib_i2c/Sensirion_Core/src/SensirionShdlcRxFrame.h new file mode 100644 index 000000000..d4bca0e2a --- /dev/null +++ b/lib/lib_i2c/Sensirion_Core/src/SensirionShdlcRxFrame.h @@ -0,0 +1,86 @@ +/* + * 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 SENSIRION_SHDLC_RX_FRAME_H_ +#define SENSIRION_SHDLC_RX_FRAME_H_ + +#include +#include + +#include "SensirionRxFrame.h" +#include "SensirionShdlcCommunication.h" + +/** + * SenirionShdlcRxFrame - Class which decodes the through UART received data + * into common data types. It contains a buffer which is filled by the + * SensirionShdlcCommunication class. By calling the different decode function + * inherited from the SensirionRxFrame base class the raw data can be decoded + * into different data types. In addition to that it also stores the four + * header bytes defined by the SHDLC protocol state, command, address, + * datalength. These bytes can be read out by the corresponding getter method. + */ +class SensirionShdlcRxFrame : public SensirionRxFrame { + + friend class SensirionShdlcCommunication; + + public: + /** + * Constructor + * + * @param buffer Buffer in which the receive frame will be stored. + * @param bufferSize Number of bytes in the buffer for the receive frame. + */ + SensirionShdlcRxFrame(uint8_t buffer[], size_t bufferSize) + : SensirionRxFrame(buffer, bufferSize){}; + + uint8_t getAddress(void) const { + return _address; + }; + + uint8_t getCommand(void) const { + return _command; + }; + + uint8_t getState(void) const { + return _state; + }; + + uint8_t getDataLength(void) const { + return _dataLength; + }; + + private: + uint8_t _address = 0; + uint8_t _command = 0; + uint8_t _state = 0; + uint8_t _dataLength = 0; +}; + +#endif /* SENSIRION_SHDLC_RX_FRAME_H_ */ diff --git a/lib/lib_i2c/Sensirion_Core/src/SensirionShdlcTxFrame.cpp b/lib/lib_i2c/Sensirion_Core/src/SensirionShdlcTxFrame.cpp new file mode 100644 index 000000000..fc8c4c994 --- /dev/null +++ b/lib/lib_i2c/Sensirion_Core/src/SensirionShdlcTxFrame.cpp @@ -0,0 +1,130 @@ +/* + * 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, + * 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 + * 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 "SensirionShdlcTxFrame.h" + +#include +#include + +#include "SensirionErrors.h" + +uint16_t SensirionShdlcTxFrame::begin(uint8_t command, uint8_t address, + uint8_t dataLength) { + _buffer[_index++] = 0x7e; + uint16_t error = addUInt8(address); + error |= addUInt8(command); + error |= addUInt8(dataLength); + _command = command; + _address = address; + return error; +} + +uint16_t SensirionShdlcTxFrame::finish(void) { + uint16_t error = addUInt8(~_checksum); + if (error) { + return error; + } + if (_index + 1 > _bufferSize) { + return TxFrameError | BufferSizeError; + } + _buffer[_index++] = 0x7e; + _isFinished = true; + return NoError; +} + +uint16_t SensirionShdlcTxFrame::addUInt32(uint32_t data) { + uint16_t error = addUInt8(static_cast((data & 0xFF000000) >> 24)); + error |= addUInt8(static_cast((data & 0x00FF0000) >> 16)); + error |= addUInt8(static_cast((data & 0x0000FF00) >> 8)); + error |= addUInt8(static_cast((data & 0x000000FF) >> 0)); + return error; +} + +uint16_t SensirionShdlcTxFrame::addInt32(int32_t data) { + return addUInt32(static_cast(data)); +} + +uint16_t SensirionShdlcTxFrame::addUInt16(uint16_t data) { + uint16_t error = addUInt8(static_cast((data & 0xFF00) >> 8)); + error |= addUInt8(static_cast((data & 0x00FF) >> 0)); + return error; +} + +uint16_t SensirionShdlcTxFrame::addInt16(int16_t data) { + return addUInt16(static_cast(data)); +} + +uint16_t SensirionShdlcTxFrame::addUInt8(uint8_t data) { + if (_index + 2 > _bufferSize) { + return TxFrameError | BufferSizeError; + } + switch (data) { + case 0x11: + case 0x13: + case 0x7d: + case 0x7e: + // byte stuffing is done by inserting 0x7d and inverting bit 5 + _buffer[_index++] = 0x7d; + _buffer[_index++] = data ^ (1 << 5); + break; + default: + _buffer[_index++] = data; + } + _checksum += data; + return NoError; +} + +uint16_t SensirionShdlcTxFrame::addInt8(int8_t data) { + return addUInt8(static_cast(data)); +} + +uint16_t SensirionShdlcTxFrame::addBool(bool data) { + return addUInt8(static_cast(data)); +} + +uint16_t SensirionShdlcTxFrame::addFloat(float data) { + union { + uint32_t uInt32Data; + float floatData; + } convert; + + convert.floatData = data; + return addUInt32(convert.uInt32Data); +} + +uint16_t SensirionShdlcTxFrame::addBytes(const uint8_t data[], + size_t dataLength) { + uint16_t error = 0; + for (size_t i = 0; i < dataLength; i++) { + error |= addUInt8(data[i]); + } + return error; +} diff --git a/lib/lib_i2c/Sensirion_Core/src/SensirionShdlcTxFrame.h b/lib/lib_i2c/Sensirion_Core/src/SensirionShdlcTxFrame.h new file mode 100644 index 000000000..eb2dbc6e7 --- /dev/null +++ b/lib/lib_i2c/Sensirion_Core/src/SensirionShdlcTxFrame.h @@ -0,0 +1,184 @@ +/* + * 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 SENSIRION_SHDLC_TX_FRAME_H_ +#define SENSIRION_SHDLC_TX_FRAME_H_ + +#include +#include + +#include "SensirionShdlcCommunication.h" + +/* + * SensirionShdlcTxFrame - Class which helps to build a correct SHDLC frame. + * The begin() functions writes the header. The different addDatatype() + * functions add the frame data and the finish() function writes the tail. + * Using these functions one can easily construct a SHDLC frame. + */ +class SensirionShdlcTxFrame { + + friend class SensirionShdlcCommunication; + + public: + /** + * Constructor + * + * @param buffer Buffer in which the send frame will be stored. + * @param bufferSize Number of bytes in the buffer for the send frame. + */ + SensirionShdlcTxFrame(uint8_t buffer[], size_t bufferSize) + : _buffer(buffer), _bufferSize(bufferSize) { + } + + /** + * begin() - Begin frame and write header. + * + * @note This function needs to be called before calling other + * data add functions to write the header at the beginning. + * + * @param command Command byte to add to the send frame. + * @param address Address byte to add to the send frame. + * @param dataLength Data length byte to add to the send frame. + * + * @return NoError on success, an error code otherwise + */ + uint16_t begin(uint8_t command, uint8_t address, uint8_t dataLength); + + /** + * finish() - Finish frame and write tail. + * + * @note This function needs to be called last, after adding all + * data to frame and before sending the frame to the sensor. + * + * @return NoError on success, an error code otherwise + */ + uint16_t finish(void); + + /** + * addUInt32() - Add unsigned 32bit integer to the send frame. + * + * @param data Unsigned 32bit integer to add to the send frame. + * + * @return NoError on success, an error code otherwise + */ + uint16_t addUInt32(uint32_t data); + + /** + * addInt32() - Add signed 32bit integer to the send frame. + * + * @param data Signed 32bit integer to add to the send frame. + * + * @return NoError on success, an error code otherwise + */ + uint16_t addInt32(int32_t data); + + /** + * addUInt16() - Add unsigned 16bit integer to the send frame. + * + * @param data Unsigned 16bit integer to add to the send frame. + * + * @return NoError on success, an error code otherwise + */ + uint16_t addUInt16(uint16_t data); + + /** + * addInt16() - Add signed 16bit integer to the send frame. + * + * @param data Signed 16bit integer to add to the send frame. + * + * @return NoError on success, an error code otherwise + */ + uint16_t addInt16(int16_t data); + + /** + * addUInt8() - Add unsigned 8bit integer to the send frame. + * + * @param data Unsigned 8bit integer to add to the send frame. + * + * @return NoError on success, an error code otherwise + */ + uint16_t addUInt8(uint8_t data); + + /** + * addInt8() - Add signed 8bit integer to the send frame. + * + * @param data Signed 8bit integer to add to the send frame. + * + * @return NoError on success, an error code otherwise + */ + uint16_t addInt8(int8_t data); + + /** + * addBool() - Add boolean to the send frame. + * + * @param data Boolean to add to the send frame. + * + * @return NoError on success, an error code otherwise + */ + uint16_t addBool(bool data); + + /** + * addFloat() - Add float to the send frame. + * + * @param data Float to add to the send frame. + * + * @return NoError on success, an error code otherwise + */ + uint16_t addFloat(float data); + + /** + * addBytes() - Add byte array to the send frame. + * + * @param data Byte array to add to the send frame. + * @param dataLength Number of bytes to add to the send frame. + * + * @return NoError on success, an error code otherwise + */ + uint16_t addBytes(const uint8_t data[], size_t dataLength); + + uint8_t getCommand(void) const { + return _command; + }; + + uint8_t getAddress(void) const { + return _address; + } + + private: + uint8_t* _buffer; + size_t _bufferSize; + size_t _index = 0; + uint8_t _checksum = 0; + bool _isFinished = false; + uint8_t _command = 0; + uint8_t _address = 0; +}; + +#endif /* SENSIRION_SHDLC_TX_FRAME_H_ */ diff --git a/lib/lib_i2c/Sensirion_Core/tests/compile_test.py b/lib/lib_i2c/Sensirion_Core/tests/compile_test.py new file mode 100644 index 000000000..81c543a68 --- /dev/null +++ b/lib/lib_i2c/Sensirion_Core/tests/compile_test.py @@ -0,0 +1,45 @@ +#! /usr/bin/env python3 +import subprocess +import json +import argparse +import sys +import logging + + +def main(): + parser = argparse.ArgumentParser() + parser.description = u'Compile test a sketch for all available boards' + parser.add_argument(u'-s', u'--sketch', dest=u'sketch', + required=True, help=u'Path to sketch') + args = parser.parse_args() + test_all_boards(args.sketch) + + +def test_all_boards(sketch): + logging.basicConfig(level=logging.INFO, + format='%(asctime)s [%(levelname)s] %(message)s') + log = logging.getLogger('arduino-compile-test') + process = subprocess.run("arduino-cli board listall --format json".split(), + stdout=subprocess.PIPE) + board_list_json = process.stdout.decode('utf-8') + board_list = json.loads(board_list_json) + test_list = ["arduino:samd:mkrzero", "arduino:avr:mega", + "arduino:avr:nano", "arduino:avr:uno", + "esp32:esp32:esp32", "esp8266:esp8266:generic"] + for board in test_list: + if board in (b['fqbn'] for b in board_list['boards']): + log.info('Test compilation for board {}'.format(board)) + command = 'arduino-cli compile --libraries="." --warnings all'\ + ' --fqbn {board} {sketch}'.format(board=board, + sketch=sketch) + process = subprocess.run(command.split(), stdout=subprocess.PIPE) + if process.returncode: + log.error(process.stdout.decode('utf-8')) + sys.exit(process.returncode) + else: + log.error('Board not installed: {}'.format(board)) + sys.exit(-1) + + +if __name__ == '__main__': + main() diff --git a/lib/lib_i2c/Sensirion_Core/tests/run_cppcheck.sh b/lib/lib_i2c/Sensirion_Core/tests/run_cppcheck.sh new file mode 100644 index 000000000..60d620b1e --- /dev/null +++ b/lib/lib_i2c/Sensirion_Core/tests/run_cppcheck.sh @@ -0,0 +1,3 @@ +#!/bin/bash +set -euxo pipefail +cppcheck --std=c++11 --language=c++ --error-exitcode=1 --enable=warning,style,performance,portability src/* diff --git a/lib/lib_i2c/Sensirion_Core/tests/syntax_check.sh b/lib/lib_i2c/Sensirion_Core/tests/syntax_check.sh new file mode 100644 index 000000000..53aeaba2c --- /dev/null +++ b/lib/lib_i2c/Sensirion_Core/tests/syntax_check.sh @@ -0,0 +1,5 @@ +#! /bin/bash +set -euxo pipefail +editorconfig-checker +flake8 +find . -type f -iregex ".*\.\(c\|h\|cpp\|ino\)" -exec clang-format-6.0 -i -style=file {} \; && git diff --exit-code diff --git a/lib/lib_i2c/Sensirion_I2C_SEN5X/CHANGELOG.md b/lib/lib_i2c/Sensirion_I2C_SEN5X/CHANGELOG.md new file mode 100644 index 000000000..a1d8044bc --- /dev/null +++ b/lib/lib_i2c/Sensirion_I2C_SEN5X/CHANGELOG.md @@ -0,0 +1,20 @@ +# 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). + +## [Unreleased] + + +## [0.2.0] - 2022-03-30 + +Add support for SEN50 + +## [0.1.0] - 2022-01-05 + +Initial release + +[0.2.0]: https://github.com/Sensirion/embedded-i2c-sen5x/compare/0.1.0...0.2.0 +[0.1.0]: https://github.com/Sensirion/arduino-i2c-sen5x/releases/tag/0.1.0 + diff --git a/lib/lib_i2c/Sensirion_I2C_SEN5X/LICENSE b/lib/lib_i2c/Sensirion_I2C_SEN5X/LICENSE new file mode 100644 index 000000000..ff20c41df --- /dev/null +++ b/lib/lib_i2c/Sensirion_I2C_SEN5X/LICENSE @@ -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. diff --git a/lib/lib_i2c/Sensirion_I2C_SEN5X/README.md b/lib/lib_i2c/Sensirion_I2C_SEN5X/README.md new file mode 100644 index 000000000..5165ad7c3 --- /dev/null +++ b/lib/lib_i2c/Sensirion_I2C_SEN5X/README.md @@ -0,0 +1,97 @@ + +# Sensirion I2C SEN5X Arduino Library + +This is the Sensirion SEN5X library for Arduino using the +modules I2C interface. + +
+ +## Supported sensors + +- SEN50 (only particulate matter signals available) +- SEN54 (no NOx signal available) +- SEN55 (full feature set) + +# 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 SEN5X 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 SEN5X Sensor board can be found in the + data sheet. + + | *SEN5X* | *Arduino* | *Jumper Wire* | + | ------- | ----------- | ------------- | + | VCC | 5V | Red | + | GND | GND | Black | + | SDA | SDA | Green | + | SCL | SCL | Yellow | + | SEL | GND for I2C | Blue | + +
+ + | *Pin* | *Name* | *Description* | *Comments* | + | ----- | ------ | ------------------------------- | -------------------------------- | + | 1 | VCC | Supply Voltage | 5V ±10% | + | 2 | GND | Ground | + | 3 | SDA | I2C: Serial data input / output | TTL 5V and LVTTL 3.3V compatible | + | 4 | SCL | I2C: Serial clock input | TTL 5V and LVTTL 3.3V compatible | + | 5 | SEL | Interface select | Pull to GND to select I2C | + | 6 | NC | Do not connect | + +2. Open the `exampleUsage` sample project within the Arduino IDE + + File => Examples => Sensirion I2C SEN5X => 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). diff --git a/lib/lib_i2c/Sensirion_I2C_SEN5X/examples/exampleUsage/exampleUsage.ino b/lib/lib_i2c/Sensirion_I2C_SEN5X/examples/exampleUsage/exampleUsage.ino new file mode 100644 index 000000000..c92a10374 --- /dev/null +++ b/lib/lib_i2c/Sensirion_I2C_SEN5X/examples/exampleUsage/exampleUsage.ino @@ -0,0 +1,249 @@ + +/* + * I2C-Generator: 0.3.0 + * Yaml Version: 2.1.3 + * Template Version: 0.7.0-112-g190ecaa + */ +/* + * 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 +#include +#include + +// The used commands use up to 48 bytes. On some Arduino's the default buffer +// space is not large enough +#define MAXBUF_REQUIREMENT 48 + +#if (defined(I2C_BUFFER_LENGTH) && \ + (I2C_BUFFER_LENGTH >= MAXBUF_REQUIREMENT)) || \ + (defined(BUFFER_LENGTH) && BUFFER_LENGTH >= MAXBUF_REQUIREMENT) +#define USE_PRODUCT_INFO +#endif + +SensirionI2CSen5x sen5x; + +void printModuleVersions() { + uint16_t error; + char errorMessage[256]; + + unsigned char productName[32]; + uint8_t productNameSize = 32; + + error = sen5x.getProductName(productName, productNameSize); + + if (error) { + Serial.print("Error trying to execute getProductName(): "); + errorToString(error, errorMessage, 256); + Serial.println(errorMessage); + } else { + Serial.print("ProductName:"); + Serial.println((char*)productName); + } + + uint8_t firmwareMajor; + uint8_t firmwareMinor; + bool firmwareDebug; + uint8_t hardwareMajor; + uint8_t hardwareMinor; + uint8_t protocolMajor; + uint8_t protocolMinor; + + error = sen5x.getVersion(firmwareMajor, firmwareMinor, firmwareDebug, + hardwareMajor, hardwareMinor, protocolMajor, + protocolMinor); + if (error) { + Serial.print("Error trying to execute getVersion(): "); + errorToString(error, errorMessage, 256); + Serial.println(errorMessage); + } else { + Serial.print("Firmware: "); + Serial.print(firmwareMajor); + Serial.print("."); + Serial.print(firmwareMinor); + Serial.print(", "); + + Serial.print("Hardware: "); + Serial.print(hardwareMajor); + Serial.print("."); + Serial.println(hardwareMinor); + } +} + +void printSerialNumber() { + uint16_t error; + char errorMessage[256]; + unsigned char serialNumber[32]; + uint8_t serialNumberSize = 32; + + error = sen5x.getSerialNumber(serialNumber, serialNumberSize); + if (error) { + Serial.print("Error trying to execute getSerialNumber(): "); + errorToString(error, errorMessage, 256); + Serial.println(errorMessage); + } else { + Serial.print("SerialNumber:"); + Serial.println((char*)serialNumber); + } +} + +void setup() { + + Serial.begin(115200); + while (!Serial) { + delay(100); + } + + Wire.begin(); + + sen5x.begin(Wire); + + uint16_t error; + char errorMessage[256]; + error = sen5x.deviceReset(); + if (error) { + Serial.print("Error trying to execute deviceReset(): "); + errorToString(error, errorMessage, 256); + Serial.println(errorMessage); + } + +// Print SEN55 module information if i2c buffers are large enough +#ifdef USE_PRODUCT_INFO + printSerialNumber(); + printModuleVersions(); +#endif + + // set a temperature offset in degrees celsius + // Note: supported by SEN54 and SEN55 sensors + // By default, the temperature and humidity outputs from the sensor + // are compensated for the modules self-heating. If the module is + // designed into a device, the temperature compensation might need + // to be adapted to incorporate the change in thermal coupling and + // self-heating of other device components. + // + // A guide to achieve optimal performance, including references + // to mechanical design-in examples can be found in the app note + // “SEN5x – Temperature Compensation Instruction” at www.sensirion.com. + // Please refer to those application notes for further information + // on the advanced compensation settings used + // in `setTemperatureOffsetParameters`, `setWarmStartParameter` and + // `setRhtAccelerationMode`. + // + // Adjust tempOffset to account for additional temperature offsets + // exceeding the SEN module's self heating. + float tempOffset = 0.0; + error = sen5x.setTemperatureOffsetSimple(tempOffset); + if (error) { + Serial.print("Error trying to execute setTemperatureOffsetSimple(): "); + errorToString(error, errorMessage, 256); + Serial.println(errorMessage); + } else { + Serial.print("Temperature Offset set to "); + Serial.print(tempOffset); + Serial.println(" deg. Celsius (SEN54/SEN55 only"); + } + + // Start Measurement + error = sen5x.startMeasurement(); + if (error) { + Serial.print("Error trying to execute startMeasurement(): "); + errorToString(error, errorMessage, 256); + Serial.println(errorMessage); + } +} + +void loop() { + uint16_t error; + char errorMessage[256]; + + delay(1000); + + // Read Measurement + float massConcentrationPm1p0; + float massConcentrationPm2p5; + float massConcentrationPm4p0; + float massConcentrationPm10p0; + float ambientHumidity; + float ambientTemperature; + float vocIndex; + float noxIndex; + + error = sen5x.readMeasuredValues( + massConcentrationPm1p0, massConcentrationPm2p5, massConcentrationPm4p0, + massConcentrationPm10p0, ambientHumidity, ambientTemperature, vocIndex, + noxIndex); + + if (error) { + Serial.print("Error trying to execute readMeasuredValues(): "); + errorToString(error, errorMessage, 256); + Serial.println(errorMessage); + } else { + Serial.print("MassConcentrationPm1p0:"); + Serial.print(massConcentrationPm1p0); + Serial.print("\t"); + Serial.print("MassConcentrationPm2p5:"); + Serial.print(massConcentrationPm2p5); + Serial.print("\t"); + Serial.print("MassConcentrationPm4p0:"); + Serial.print(massConcentrationPm4p0); + Serial.print("\t"); + Serial.print("MassConcentrationPm10p0:"); + Serial.print(massConcentrationPm10p0); + Serial.print("\t"); + Serial.print("AmbientHumidity:"); + if (isnan(ambientHumidity)) { + Serial.print("n/a"); + } else { + Serial.print(ambientHumidity); + } + Serial.print("\t"); + Serial.print("AmbientTemperature:"); + if (isnan(ambientTemperature)) { + Serial.print("n/a"); + } else { + Serial.print(ambientTemperature); + } + Serial.print("\t"); + Serial.print("VocIndex:"); + if (isnan(vocIndex)) { + Serial.print("n/a"); + } else { + Serial.print(vocIndex); + } + Serial.print("\t"); + Serial.print("NoxIndex:"); + if (isnan(noxIndex)) { + Serial.println("n/a"); + } else { + Serial.println(noxIndex); + } + } +} diff --git a/lib/lib_i2c/Sensirion_I2C_SEN5X/images/SEN5X_pinout.png b/lib/lib_i2c/Sensirion_I2C_SEN5X/images/SEN5X_pinout.png new file mode 100644 index 000000000..c42395c2f Binary files /dev/null and b/lib/lib_i2c/Sensirion_I2C_SEN5X/images/SEN5X_pinout.png differ diff --git a/lib/lib_i2c/Sensirion_I2C_SEN5X/images/SEN5x.png b/lib/lib_i2c/Sensirion_I2C_SEN5X/images/SEN5x.png new file mode 100644 index 000000000..32bb95b01 Binary files /dev/null and b/lib/lib_i2c/Sensirion_I2C_SEN5X/images/SEN5x.png differ diff --git a/lib/lib_i2c/Sensirion_I2C_SEN5X/keywords.txt b/lib/lib_i2c/Sensirion_I2C_SEN5X/keywords.txt new file mode 100644 index 000000000..295031081 --- /dev/null +++ b/lib/lib_i2c/Sensirion_I2C_SEN5X/keywords.txt @@ -0,0 +1,55 @@ +####################################### +# Syntax Coloring Map +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +SensirionI2CSen5x KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### +startMeasurement KEYWORD2 +startMeasurementWithoutPm KEYWORD2 +stopMeasurement KEYWORD2 +readDataReady KEYWORD2 +readMeasuredValues KEYWORD2 +readMeasuredValuesAsIntegers KEYWORD2 +readMeasuredValuesSen50 KEYWORD2 +readMeasuredRawValues KEYWORD2 +readMeasuredPmValues KEYWORD2 +readMeasuredPmValuesAsIntegers KEYWORD2 +startFanCleaning KEYWORD2 +setTemperatureOffsetSimple KEYWORD2 +getTemperatureOffsetSimple KEYWORD2 +setTemperatureOffsetParameters KEYWORD2 +getTemperatureOffsetParameters KEYWORD2 +setWarmStartParameter KEYWORD2 +getWarmStartParameter KEYWORD2 +setVocAlgorithmTuningParameters KEYWORD2 +getVocAlgorithmTuningParameters KEYWORD2 +setNoxAlgorithmTuningParameters KEYWORD2 +getNoxAlgorithmTuningParameters KEYWORD2 +setRhtAccelerationMode KEYWORD2 +getRhtAccelerationMode KEYWORD2 +setVocAlgorithmState KEYWORD2 +getVocAlgorithmState KEYWORD2 +setFanAutoCleaningInterval KEYWORD2 +getFanAutoCleaningInterval KEYWORD2 +getProductName KEYWORD2 +getSerialNumber KEYWORD2 +getVersion KEYWORD2 +readDeviceStatus KEYWORD2 +readAndClearDeviceStatus KEYWORD2 +deviceReset KEYWORD2 +####################################### +# Instances (KEYWORD2) +####################################### + +sen5x KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### diff --git a/lib/lib_i2c/Sensirion_I2C_SEN5X/library.properties b/lib/lib_i2c/Sensirion_I2C_SEN5X/library.properties new file mode 100644 index 000000000..ea4a9000b --- /dev/null +++ b/lib/lib_i2c/Sensirion_I2C_SEN5X/library.properties @@ -0,0 +1,10 @@ +name=Sensirion I2C SEN5X +version=0.2.0 +author=Sensirion +maintainer=Sensirion +sentence=Library for the SEN5X sensor family by Sensirion +paragraph=Enables you to use the SEN50, SEN54 and SEN55 via I2C. +url=https://github.com/Sensirion/arduino-i2c-sen5x +category=Sensors +depends=Sensirion Core +includes=SensirionI2CSen5x.h diff --git a/lib/lib_i2c/Sensirion_I2C_SEN5X/src/SensirionI2CSen5x.cpp b/lib/lib_i2c/Sensirion_I2C_SEN5X/src/SensirionI2CSen5x.cpp new file mode 100644 index 000000000..3008e5226 --- /dev/null +++ b/lib/lib_i2c/Sensirion_I2C_SEN5X/src/SensirionI2CSen5x.cpp @@ -0,0 +1,880 @@ +/* + * THIS FILE IS AUTOMATICALLY GENERATED + * + * I2C-Generator: 0.3.0 + * Yaml Version: 2.1.3 + * Template Version: 0.7.0-112-g190ecaa + */ +/* + * 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 "SensirionI2CSen5x.h" +#include "Arduino.h" +#include "SensirionCore.h" +#include +#include + +#define SEN5X_I2C_ADDRESS 0x69 +#define UINT_INVALID 0xFFFF +#define INT_INVALID 0x7FFF + +SensirionI2CSen5x::SensirionI2CSen5x() { +} + +void SensirionI2CSen5x::begin(TwoWire& i2cBus) { + _i2cBus = &i2cBus; +} + +uint16_t SensirionI2CSen5x::startMeasurement() { + uint16_t error = 0; + uint8_t buffer[2]; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x21, buffer, 2); + + error = SensirionI2CCommunication::sendFrame(SEN5X_I2C_ADDRESS, txFrame, + *_i2cBus); + delay(50); + return error; +} + +uint16_t SensirionI2CSen5x::startMeasurementWithoutPm() { + uint16_t error = 0; + uint8_t buffer[2]; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x37, buffer, 2); + + error = SensirionI2CCommunication::sendFrame(SEN5X_I2C_ADDRESS, txFrame, + *_i2cBus); + delay(50); + return error; +} + +uint16_t SensirionI2CSen5x::stopMeasurement() { + uint16_t error = 0; + uint8_t buffer[2]; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x104, buffer, 2); + + error = SensirionI2CCommunication::sendFrame(SEN5X_I2C_ADDRESS, txFrame, + *_i2cBus); + delay(200); + return error; +} + +uint16_t SensirionI2CSen5x::readDataReady(bool& dataReady) { + uint16_t error = 0; + uint8_t buffer[3]; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x202, buffer, 3); + + error = SensirionI2CCommunication::sendFrame(SEN5X_I2C_ADDRESS, txFrame, + *_i2cBus); + if (error) { + return error; + } + + delay(20); + + SensirionI2CRxFrame rxFrame(buffer, 3); + error = SensirionI2CCommunication::receiveFrame(SEN5X_I2C_ADDRESS, 3, + rxFrame, *_i2cBus); + if (error) { + return error; + } + + uint8_t padding; + error |= rxFrame.getUInt8(padding); // remove padding + error |= rxFrame.getBool(dataReady); + return error; +} + +uint16_t SensirionI2CSen5x::readMeasuredValues( + float& massConcentrationPm1p0, float& massConcentrationPm2p5, + float& massConcentrationPm4p0, float& massConcentrationPm10p0, + float& ambientHumidity, float& ambientTemperature, float& vocIndex, + float& noxIndex) { + + uint16_t error = 0; + uint16_t massConcentrationPm1p0Int; + uint16_t massConcentrationPm2p5Int; + uint16_t massConcentrationPm4p0Int; + uint16_t massConcentrationPm10p0Int; + int16_t ambientHumidityInt; + int16_t ambientTemperatureInt; + int16_t vocIndexInt; + int16_t noxIndexInt; + + error = readMeasuredValuesAsIntegers( + massConcentrationPm1p0Int, massConcentrationPm2p5Int, + massConcentrationPm4p0Int, massConcentrationPm10p0Int, + ambientHumidityInt, ambientTemperatureInt, vocIndexInt, noxIndexInt); + + if (error) { + return error; + } + + massConcentrationPm1p0 = massConcentrationPm1p0Int == UINT_INVALID + ? NAN + : massConcentrationPm1p0Int / 10.0f; + massConcentrationPm2p5 = massConcentrationPm2p5Int == UINT_INVALID + ? NAN + : massConcentrationPm2p5Int / 10.0f; + massConcentrationPm4p0 = massConcentrationPm4p0Int == UINT_INVALID + ? NAN + : massConcentrationPm4p0Int / 10.0f; + massConcentrationPm10p0 = massConcentrationPm10p0Int == UINT_INVALID + ? NAN + : massConcentrationPm10p0Int / 10.0f; + ambientHumidity = + ambientHumidityInt == INT_INVALID ? NAN : ambientHumidityInt / 100.0f; + ambientTemperature = ambientTemperatureInt == INT_INVALID + ? NAN + : ambientTemperatureInt / 200.0f; + vocIndex = vocIndexInt == INT_INVALID ? NAN : vocIndexInt / 10.0f; + noxIndex = noxIndexInt == INT_INVALID ? NAN : noxIndexInt / 10.0f; + + return NoError; +} + +uint16_t SensirionI2CSen5x::readMeasuredValuesAsIntegers( + uint16_t& massConcentrationPm1p0, uint16_t& massConcentrationPm2p5, + uint16_t& massConcentrationPm4p0, uint16_t& massConcentrationPm10p0, + int16_t& ambientHumidity, int16_t& ambientTemperature, int16_t& vocIndex, + int16_t& noxIndex) { + uint16_t error = 0; + uint8_t buffer[24]; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x3C4, buffer, 24); + + error = SensirionI2CCommunication::sendFrame(SEN5X_I2C_ADDRESS, txFrame, + *_i2cBus); + if (error) { + return error; + } + + delay(20); + + SensirionI2CRxFrame rxFrame(buffer, 24); + error = SensirionI2CCommunication::receiveFrame(SEN5X_I2C_ADDRESS, 24, + rxFrame, *_i2cBus); + if (error) { + return error; + } + + error |= rxFrame.getUInt16(massConcentrationPm1p0); + error |= rxFrame.getUInt16(massConcentrationPm2p5); + error |= rxFrame.getUInt16(massConcentrationPm4p0); + error |= rxFrame.getUInt16(massConcentrationPm10p0); + error |= rxFrame.getInt16(ambientHumidity); + error |= rxFrame.getInt16(ambientTemperature); + error |= rxFrame.getInt16(vocIndex); + error |= rxFrame.getInt16(noxIndex); + return error; +} + +uint16_t SensirionI2CSen5x::readMeasuredRawValues(int16_t& rawHumidity, + int16_t& rawTemperature, + uint16_t& rawVoc, + uint16_t& rawNox) { + uint16_t error = 0; + uint8_t buffer[12]; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x3D2, buffer, 12); + + error = SensirionI2CCommunication::sendFrame(SEN5X_I2C_ADDRESS, txFrame, + *_i2cBus); + if (error) { + return error; + } + + delay(20); + + SensirionI2CRxFrame rxFrame(buffer, 12); + error = SensirionI2CCommunication::receiveFrame(SEN5X_I2C_ADDRESS, 12, + rxFrame, *_i2cBus); + if (error) { + return error; + } + + error |= rxFrame.getInt16(rawHumidity); + error |= rxFrame.getInt16(rawTemperature); + error |= rxFrame.getUInt16(rawVoc); + error |= rxFrame.getUInt16(rawNox); + return error; +} + +uint16_t SensirionI2CSen5x::readMeasuredValuesSen50( + float& massConcentrationPm1p0, float& massConcentrationPm2p5, + float& massConcentrationPm4p0, float& massConcentrationPm10p0) { + + uint16_t error = 0; + float ambientHumidityDummy; + float ambientTemperatureDummy; + float vocIndexDummy; + float noxIndexDummy; + error = readMeasuredValues(massConcentrationPm1p0, massConcentrationPm2p5, + massConcentrationPm4p0, massConcentrationPm10p0, + ambientHumidityDummy, ambientTemperatureDummy, + vocIndexDummy, noxIndexDummy); + return error; +} + +uint16_t SensirionI2CSen5x::readMeasuredPmValues( + float& massConcentrationPm1p0, float& massConcentrationPm2p5, + float& massConcentrationPm4p0, float& massConcentrationPm10p0, + float& numberConcentrationPm0p5, float& numberConcentrationPm1p0, + float& numberConcentrationPm2p5, float& numberConcentrationPm4p0, + float& numberConcentrationPm10p0, float& typicalParticleSize) { + + uint16_t error = 0; + uint16_t massConcentrationPm1p0Int; + uint16_t massConcentrationPm2p5Int; + uint16_t massConcentrationPm4p0Int; + uint16_t massConcentrationPm10p0Int; + uint16_t numberConcentrationPm0p5Int; + uint16_t numberConcentrationPm1p0Int; + uint16_t numberConcentrationPm2p5Int; + uint16_t numberConcentrationPm4p0Int; + uint16_t numberConcentrationPm10p0Int; + uint16_t typicalParticleSizeInt; + + error = readMeasuredPmValuesAsIntegers( + massConcentrationPm1p0Int, massConcentrationPm2p5Int, + massConcentrationPm4p0Int, massConcentrationPm10p0Int, + numberConcentrationPm0p5Int, numberConcentrationPm1p0Int, + numberConcentrationPm2p5Int, numberConcentrationPm4p0Int, + numberConcentrationPm10p0Int, typicalParticleSizeInt); + + if (error) { + return error; + } + + massConcentrationPm1p0 = massConcentrationPm1p0Int == UINT_INVALID + ? NAN + : massConcentrationPm1p0Int / 10.0f; + massConcentrationPm2p5 = massConcentrationPm2p5Int == UINT_INVALID + ? NAN + : massConcentrationPm2p5Int / 10.0f; + massConcentrationPm4p0 = massConcentrationPm4p0Int == UINT_INVALID + ? NAN + : massConcentrationPm4p0Int / 10.0f; + massConcentrationPm10p0 = massConcentrationPm10p0Int == UINT_INVALID + ? NAN + : massConcentrationPm10p0Int / 10.0f; + numberConcentrationPm0p5 = numberConcentrationPm0p5Int == UINT_INVALID + ? NAN + : numberConcentrationPm0p5Int / 10.0f; + numberConcentrationPm1p0 = numberConcentrationPm1p0Int == UINT_INVALID + ? NAN + : numberConcentrationPm1p0Int / 10.0f; + numberConcentrationPm2p5 = numberConcentrationPm2p5Int == UINT_INVALID + ? NAN + : numberConcentrationPm2p5Int / 10.0f; + numberConcentrationPm4p0 = numberConcentrationPm4p0Int == UINT_INVALID + ? NAN + : numberConcentrationPm4p0Int / 10.0f; + numberConcentrationPm10p0 = numberConcentrationPm10p0Int == UINT_INVALID + ? NAN + : numberConcentrationPm10p0Int / 10.0f; + typicalParticleSize = typicalParticleSizeInt == UINT_INVALID + ? NAN + : typicalParticleSizeInt / 1000.0f; + + return NoError; +} + +uint16_t SensirionI2CSen5x::readMeasuredPmValuesAsIntegers( + uint16_t& massConcentrationPm1p0, uint16_t& massConcentrationPm2p5, + uint16_t& massConcentrationPm4p0, uint16_t& massConcentrationPm10p0, + uint16_t& numberConcentrationPm0p5, uint16_t& numberConcentrationPm1p0, + uint16_t& numberConcentrationPm2p5, uint16_t& numberConcentrationPm4p0, + uint16_t& numberConcentrationPm10p0, uint16_t& typicalParticleSize) { + uint16_t error = 0; + uint8_t buffer[30]; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x413, buffer, 30); + + error = SensirionI2CCommunication::sendFrame(SEN5X_I2C_ADDRESS, txFrame, + *_i2cBus); + if (error) { + return error; + } + + delay(20); + + SensirionI2CRxFrame rxFrame(buffer, 30); + error = SensirionI2CCommunication::receiveFrame(SEN5X_I2C_ADDRESS, 30, + rxFrame, *_i2cBus); + if (error) { + return error; + } + + error |= rxFrame.getUInt16(massConcentrationPm1p0); + error |= rxFrame.getUInt16(massConcentrationPm2p5); + error |= rxFrame.getUInt16(massConcentrationPm4p0); + error |= rxFrame.getUInt16(massConcentrationPm10p0); + error |= rxFrame.getUInt16(numberConcentrationPm0p5); + error |= rxFrame.getUInt16(numberConcentrationPm1p0); + error |= rxFrame.getUInt16(numberConcentrationPm2p5); + error |= rxFrame.getUInt16(numberConcentrationPm4p0); + error |= rxFrame.getUInt16(numberConcentrationPm10p0); + error |= rxFrame.getUInt16(typicalParticleSize); + return error; +} + +uint16_t SensirionI2CSen5x::startFanCleaning() { + uint16_t error = 0; + uint8_t buffer[2]; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x5607, buffer, 2); + + error = SensirionI2CCommunication::sendFrame(SEN5X_I2C_ADDRESS, txFrame, + *_i2cBus); + delay(20); + return error; +} + +uint16_t SensirionI2CSen5x::setTemperatureOffsetSimple(float tempOffset) { + int16_t defaultSlope = 0; + uint16_t defaultTimeConstant = 0; + int16_t tempOffsetTicks = static_cast(tempOffset * 200); + return setTemperatureOffsetParameters(tempOffsetTicks, defaultSlope, + defaultTimeConstant); +} + +uint16_t SensirionI2CSen5x::getTemperatureOffsetSimple(float& tempOffset) { + int16_t tempOffsetTicks; + int16_t slope; + uint16_t timeConstant; + uint16_t error = 0; + + error = + getTemperatureOffsetParameters(tempOffsetTicks, slope, timeConstant); + if (error) { + return error; + } + + tempOffset = static_cast(tempOffsetTicks) / 200.0f; + + return NoError; +} + +uint16_t SensirionI2CSen5x::setTemperatureOffsetParameters( + int16_t tempOffset, int16_t slope, uint16_t timeConstant) { + uint16_t error = 0; + uint8_t buffer[11]; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x60B2, buffer, 11); + + error |= txFrame.addInt16(tempOffset); + error |= txFrame.addInt16(slope); + error |= txFrame.addUInt16(timeConstant); + + if (error) { + return error; + } + + error = SensirionI2CCommunication::sendFrame(SEN5X_I2C_ADDRESS, txFrame, + *_i2cBus); + delay(20); + return error; +} + +uint16_t SensirionI2CSen5x::getTemperatureOffsetParameters( + int16_t& tempOffset, int16_t& slope, uint16_t& timeConstant) { + uint16_t error = 0; + uint8_t buffer[9]; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x60B2, buffer, 9); + + error = SensirionI2CCommunication::sendFrame(SEN5X_I2C_ADDRESS, txFrame, + *_i2cBus); + if (error) { + return error; + } + + delay(20); + + SensirionI2CRxFrame rxFrame(buffer, 9); + error = SensirionI2CCommunication::receiveFrame(SEN5X_I2C_ADDRESS, 9, + rxFrame, *_i2cBus); + if (error) { + return error; + } + + error |= rxFrame.getInt16(tempOffset); + error |= rxFrame.getInt16(slope); + error |= rxFrame.getUInt16(timeConstant); + return error; +} + +uint16_t SensirionI2CSen5x::setWarmStartParameter(uint16_t warmStart) { + uint16_t error = 0; + uint8_t buffer[5]; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x60C6, buffer, 5); + + error |= txFrame.addUInt16(warmStart); + + if (error) { + return error; + } + + error = SensirionI2CCommunication::sendFrame(SEN5X_I2C_ADDRESS, txFrame, + *_i2cBus); + delay(20); + return error; +} + +uint16_t SensirionI2CSen5x::getWarmStartParameter(uint16_t& warmStart) { + uint16_t error = 0; + uint8_t buffer[3]; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x60C6, buffer, 3); + + error = SensirionI2CCommunication::sendFrame(SEN5X_I2C_ADDRESS, txFrame, + *_i2cBus); + if (error) { + return error; + } + + delay(20); + + SensirionI2CRxFrame rxFrame(buffer, 3); + error = SensirionI2CCommunication::receiveFrame(SEN5X_I2C_ADDRESS, 3, + rxFrame, *_i2cBus); + if (error) { + return error; + } + + error |= rxFrame.getUInt16(warmStart); + return error; +} + +uint16_t SensirionI2CSen5x::setVocAlgorithmTuningParameters( + int16_t indexOffset, int16_t learningTimeOffsetHours, + int16_t learningTimeGainHours, int16_t gatingMaxDurationMinutes, + int16_t stdInitial, int16_t gainFactor) { + uint16_t error = 0; + uint8_t buffer[20]; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x60D0, buffer, 20); + + error |= txFrame.addInt16(indexOffset); + error |= txFrame.addInt16(learningTimeOffsetHours); + error |= txFrame.addInt16(learningTimeGainHours); + error |= txFrame.addInt16(gatingMaxDurationMinutes); + error |= txFrame.addInt16(stdInitial); + error |= txFrame.addInt16(gainFactor); + + if (error) { + return error; + } + + error = SensirionI2CCommunication::sendFrame(SEN5X_I2C_ADDRESS, txFrame, + *_i2cBus); + delay(20); + return error; +} + +uint16_t SensirionI2CSen5x::getVocAlgorithmTuningParameters( + int16_t& indexOffset, int16_t& learningTimeOffsetHours, + int16_t& learningTimeGainHours, int16_t& gatingMaxDurationMinutes, + int16_t& stdInitial, int16_t& gainFactor) { + uint16_t error = 0; + uint8_t buffer[18]; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x60D0, buffer, 18); + + error = SensirionI2CCommunication::sendFrame(SEN5X_I2C_ADDRESS, txFrame, + *_i2cBus); + if (error) { + return error; + } + + delay(20); + + SensirionI2CRxFrame rxFrame(buffer, 18); + error = SensirionI2CCommunication::receiveFrame(SEN5X_I2C_ADDRESS, 18, + rxFrame, *_i2cBus); + if (error) { + return error; + } + + error |= rxFrame.getInt16(indexOffset); + error |= rxFrame.getInt16(learningTimeOffsetHours); + error |= rxFrame.getInt16(learningTimeGainHours); + error |= rxFrame.getInt16(gatingMaxDurationMinutes); + error |= rxFrame.getInt16(stdInitial); + error |= rxFrame.getInt16(gainFactor); + return error; +} + +uint16_t SensirionI2CSen5x::setNoxAlgorithmTuningParameters( + int16_t indexOffset, int16_t learningTimeOffsetHours, + int16_t learningTimeGainHours, int16_t gatingMaxDurationMinutes, + int16_t stdInitial, int16_t gainFactor) { + uint16_t error = 0; + uint8_t buffer[20]; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x60E1, buffer, 20); + + error |= txFrame.addInt16(indexOffset); + error |= txFrame.addInt16(learningTimeOffsetHours); + error |= txFrame.addInt16(learningTimeGainHours); + error |= txFrame.addInt16(gatingMaxDurationMinutes); + error |= txFrame.addInt16(stdInitial); + error |= txFrame.addInt16(gainFactor); + + if (error) { + return error; + } + + error = SensirionI2CCommunication::sendFrame(SEN5X_I2C_ADDRESS, txFrame, + *_i2cBus); + delay(20); + return error; +} + +uint16_t SensirionI2CSen5x::getNoxAlgorithmTuningParameters( + int16_t& indexOffset, int16_t& learningTimeOffsetHours, + int16_t& learningTimeGainHours, int16_t& gatingMaxDurationMinutes, + int16_t& stdInitial, int16_t& gainFactor) { + uint16_t error = 0; + uint8_t buffer[18]; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x60E1, buffer, 18); + + error = SensirionI2CCommunication::sendFrame(SEN5X_I2C_ADDRESS, txFrame, + *_i2cBus); + if (error) { + return error; + } + + delay(20); + + SensirionI2CRxFrame rxFrame(buffer, 18); + error = SensirionI2CCommunication::receiveFrame(SEN5X_I2C_ADDRESS, 18, + rxFrame, *_i2cBus); + if (error) { + return error; + } + + error |= rxFrame.getInt16(indexOffset); + error |= rxFrame.getInt16(learningTimeOffsetHours); + error |= rxFrame.getInt16(learningTimeGainHours); + error |= rxFrame.getInt16(gatingMaxDurationMinutes); + error |= rxFrame.getInt16(stdInitial); + error |= rxFrame.getInt16(gainFactor); + return error; +} + +uint16_t SensirionI2CSen5x::setRhtAccelerationMode(uint16_t mode) { + uint16_t error = 0; + uint8_t buffer[5]; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x60F7, buffer, 5); + + error |= txFrame.addUInt16(mode); + + if (error) { + return error; + } + + error = SensirionI2CCommunication::sendFrame(SEN5X_I2C_ADDRESS, txFrame, + *_i2cBus); + delay(20); + return error; +} + +uint16_t SensirionI2CSen5x::getRhtAccelerationMode(uint16_t& mode) { + uint16_t error = 0; + uint8_t buffer[3]; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x60F7, buffer, 3); + + error = SensirionI2CCommunication::sendFrame(SEN5X_I2C_ADDRESS, txFrame, + *_i2cBus); + if (error) { + return error; + } + + delay(20); + + SensirionI2CRxFrame rxFrame(buffer, 3); + error = SensirionI2CCommunication::receiveFrame(SEN5X_I2C_ADDRESS, 3, + rxFrame, *_i2cBus); + if (error) { + return error; + } + + error |= rxFrame.getUInt16(mode); + return error; +} + +uint16_t SensirionI2CSen5x::setVocAlgorithmState(const uint8_t state[], + uint8_t stateSize) { + uint16_t error = 0; + uint8_t buffer[14]; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x6181, buffer, 14); + + error |= txFrame.addBytes(state, stateSize); + + if (error) { + return error; + } + + error = SensirionI2CCommunication::sendFrame(SEN5X_I2C_ADDRESS, txFrame, + *_i2cBus); + delay(20); + return error; +} + +uint16_t SensirionI2CSen5x::getVocAlgorithmState(uint8_t state[], + uint8_t stateSize) { + uint16_t error = 0; + uint8_t buffer[12]; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x6181, buffer, 12); + + error = SensirionI2CCommunication::sendFrame(SEN5X_I2C_ADDRESS, txFrame, + *_i2cBus); + if (error) { + return error; + } + + delay(20); + + SensirionI2CRxFrame rxFrame(buffer, 12); + error = SensirionI2CCommunication::receiveFrame(SEN5X_I2C_ADDRESS, 12, + rxFrame, *_i2cBus); + if (error) { + return error; + } + + error |= rxFrame.getBytes(state, stateSize); + return error; +} + +uint16_t SensirionI2CSen5x::setFanAutoCleaningInterval(uint32_t interval) { + uint16_t error = 0; + uint8_t buffer[8]; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x8004, buffer, 8); + + error |= txFrame.addUInt32(interval); + + if (error) { + return error; + } + + error = SensirionI2CCommunication::sendFrame(SEN5X_I2C_ADDRESS, txFrame, + *_i2cBus); + delay(20); + return error; +} + +uint16_t SensirionI2CSen5x::getFanAutoCleaningInterval(uint32_t& interval) { + uint16_t error = 0; + uint8_t buffer[6]; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0x8004, buffer, 6); + + error = SensirionI2CCommunication::sendFrame(SEN5X_I2C_ADDRESS, txFrame, + *_i2cBus); + if (error) { + return error; + } + + delay(20); + + SensirionI2CRxFrame rxFrame(buffer, 6); + error = SensirionI2CCommunication::receiveFrame(SEN5X_I2C_ADDRESS, 6, + rxFrame, *_i2cBus); + if (error) { + return error; + } + + error |= rxFrame.getUInt32(interval); + return error; +} + +uint16_t SensirionI2CSen5x::getProductName(unsigned char productName[], + uint8_t productNameSize) { + uint16_t error = 0; + uint8_t buffer[48]; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0xD014, buffer, 48); + + error = SensirionI2CCommunication::sendFrame(SEN5X_I2C_ADDRESS, txFrame, + *_i2cBus); + if (error) { + return error; + } + + delay(50); + + SensirionI2CRxFrame rxFrame(buffer, 48); + error = SensirionI2CCommunication::receiveFrame(SEN5X_I2C_ADDRESS, 48, + rxFrame, *_i2cBus); + if (error) { + return error; + } + + error |= rxFrame.getBytes(productName, productNameSize); + return error; +} + +uint16_t SensirionI2CSen5x::getSerialNumber(unsigned char serialNumber[], + uint8_t serialNumberSize) { + uint16_t error = 0; + uint8_t buffer[48]; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0xD033, buffer, 48); + + error = SensirionI2CCommunication::sendFrame(SEN5X_I2C_ADDRESS, txFrame, + *_i2cBus); + if (error) { + return error; + } + + delay(50); + + SensirionI2CRxFrame rxFrame(buffer, 48); + error = SensirionI2CCommunication::receiveFrame(SEN5X_I2C_ADDRESS, 48, + rxFrame, *_i2cBus); + if (error) { + return error; + } + + error |= rxFrame.getBytes(serialNumber, serialNumberSize); + return error; +} + +uint16_t +SensirionI2CSen5x::getVersion(uint8_t& firmwareMajor, uint8_t& firmwareMinor, + bool& firmwareDebug, uint8_t& hardwareMajor, + uint8_t& hardwareMinor, uint8_t& protocolMajor, + uint8_t& protocolMinor) { + uint16_t error = 0; + uint8_t buffer[12]; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0xD100, buffer, 12); + + error = SensirionI2CCommunication::sendFrame(SEN5X_I2C_ADDRESS, txFrame, + *_i2cBus); + if (error) { + return error; + } + + delay(20); + + SensirionI2CRxFrame rxFrame(buffer, 12); + error = SensirionI2CCommunication::receiveFrame(SEN5X_I2C_ADDRESS, 12, + rxFrame, *_i2cBus); + if (error) { + return error; + } + + error |= rxFrame.getUInt8(firmwareMajor); + error |= rxFrame.getUInt8(firmwareMinor); + error |= rxFrame.getBool(firmwareDebug); + error |= rxFrame.getUInt8(hardwareMajor); + error |= rxFrame.getUInt8(hardwareMinor); + error |= rxFrame.getUInt8(protocolMajor); + error |= rxFrame.getUInt8(protocolMinor); + uint8_t padding; + error |= rxFrame.getUInt8(padding); // remove padding + return error; +} + +uint16_t SensirionI2CSen5x::readDeviceStatus(uint32_t& deviceStatus) { + uint16_t error = 0; + uint8_t buffer[6]; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0xD206, buffer, 6); + + error = SensirionI2CCommunication::sendFrame(SEN5X_I2C_ADDRESS, txFrame, + *_i2cBus); + if (error) { + return error; + } + + delay(20); + + SensirionI2CRxFrame rxFrame(buffer, 6); + error = SensirionI2CCommunication::receiveFrame(SEN5X_I2C_ADDRESS, 6, + rxFrame, *_i2cBus); + if (error) { + return error; + } + + error |= rxFrame.getUInt32(deviceStatus); + return error; +} + +uint16_t SensirionI2CSen5x::readAndClearDeviceStatus(uint32_t& deviceStatus) { + uint16_t error = 0; + uint8_t buffer[6]; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0xD210, buffer, 6); + + error = SensirionI2CCommunication::sendFrame(SEN5X_I2C_ADDRESS, txFrame, + *_i2cBus); + if (error) { + return error; + } + + delay(20); + + SensirionI2CRxFrame rxFrame(buffer, 6); + error = SensirionI2CCommunication::receiveFrame(SEN5X_I2C_ADDRESS, 6, + rxFrame, *_i2cBus); + if (error) { + return error; + } + + error |= rxFrame.getUInt32(deviceStatus); + return error; +} + +uint16_t SensirionI2CSen5x::deviceReset() { + uint16_t error = 0; + uint8_t buffer[2]; + SensirionI2CTxFrame txFrame = + SensirionI2CTxFrame::createWithUInt16Command(0xD304, buffer, 2); + + error = SensirionI2CCommunication::sendFrame(SEN5X_I2C_ADDRESS, txFrame, + *_i2cBus); + delay(200); + return error; +} diff --git a/lib/lib_i2c/Sensirion_I2C_SEN5X/src/SensirionI2CSen5x.h b/lib/lib_i2c/Sensirion_I2C_SEN5X/src/SensirionI2CSen5x.h new file mode 100644 index 000000000..a572ff3fe --- /dev/null +++ b/lib/lib_i2c/Sensirion_I2C_SEN5X/src/SensirionI2CSen5x.h @@ -0,0 +1,857 @@ +/* + * THIS FILE IS AUTOMATICALLY GENERATED + * + * I2C-Generator: 0.3.0 + * Yaml Version: 2.1.3 + * Template Version: 0.7.0-112-g190ecaa + */ +/* + * 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 SENSIRIONI2CSEN5X_H +#define SENSIRIONI2CSEN5X_H + +#include + +#include + +class SensirionI2CSen5x { + + public: + SensirionI2CSen5x(); + /** + * begin() - Initializes the SensirionI2CSen5x class. + * + * @param serial Arduino stream object to be communicated with. + * + */ + void begin(TwoWire& i2cBus); + + /** + * startMeasurement() - Starts a continuous measurement. + + * After starting the measurement, it takes some time (~1s) until the first + * measurement results are available. You could poll with the command + * 0x0202 \"Read Data Ready\" to check when the results are ready to read. + * + * This command is only available in idle mode. If the device is already + * in any measure mode, this command has no effect. + * + * @return 0 on success, an error code otherwise + */ + uint16_t startMeasurement(void); + + /** + * startMeasurementWithoutPm() - Starts a continuous measurement without PM. + * Only humidity, temperature, VOC and NOx are available in this mode. Laser + * and fan are switched off to keep power consumption low. + * + * After starting the measurement, it takes some time (~1s) until the first + * measurement results are available. You could poll with the command + * 0x0202 \"Read Data Ready\" to check when the results are ready to read. + * + * This command is only available in idle mode. If the device is already + * in any measure mode, this command has no effect. + * + * Supported sensors: SEN54, SEN55 + * + * @return 0 on success, an error code otherwise + */ + uint16_t startMeasurementWithoutPm(void); + + /** + * stopMeasurement() - Stops the measurement and returns to idle mode. + * + * If the device is already in idle mode, this command has no effect. + * + * @return 0 on success, an error code otherwise + */ + uint16_t stopMeasurement(void); + + /** + * readDataReady() - This command can be used to check if new measurement + * results are ready to read. The data ready flag is automatically reset + * after reading the measurement values with the 0x03.. \"Read Measured + * Values\" commands. + * + * @note During fan (auto-)cleaning, no measurement data is available for + * several seconds and thus this flag will not be set until cleaning has + * finished. So please expect gaps of several seconds at any time if fan + * auto-cleaning is enabled. + * + * @param padding Padding byte, always 0x00. + * + * @param dataReady True (0x01) if data is ready, False (0x00) if not. When + * no measurement is running, False will be returned. + * + * @return 0 on success, an error code otherwise + */ + uint16_t readDataReady(bool& dataReady); + + /** + * readMeasuredValues() - Returns the measured values. + * + * The command 0x0202 \"Read Data Ready\" can be used to check if new + * data is available since the last read operation. If no new data is + * available, the previous values will be returned again. If no data is + * available at all (e.g. measurement not running for at least one + * second), all values will be NAN. + * + * @param massConcentrationPm1p0 PM1.0 [µg/m³] + * Note: If this value is unknown, NAN is returned.* + * + * @param massConcentrationPm2p5 PM2.5 [µg/m³] + * Note: If this value is unknown, NAN is returned.* + * + * @param massConcentrationPm4p0 PM4.0 [µg/m³] + * Note: If this value is unknown, NAN is returned.* + * + * @param massConcentrationPm10p0 PM10.0 [µg/m³] + * Note: If this value is unknown, NAN is returned.* + * + * @param ambientHumidity RH [%] + * Note: If this value is unknown, NAN is returned.* + * + * @param ambientTemperature T [°C] + * Note: If this value is unknown, NAN is returned.* + * + * @param vocIndex VOC Index + * Note: If this value is unknown, NAN is returned.* + * + * @param noxIndex NOx Index + * Note: If this value is unknown, which is true for SEN54, + * NAN is returned. During the first 10..11 seconds after + * power-on or device reset, this value will be NAN as well.* + * + * @return 0 on success, an error code otherwise + */ + uint16_t readMeasuredValues(float& massConcentrationPm1p0, + float& massConcentrationPm2p5, + float& massConcentrationPm4p0, + float& massConcentrationPm10p0, + float& ambientHumidity, + float& ambientTemperature, float& vocIndex, + float& noxIndex); + + /** + * readMeasuredValuesAsIntegers() - Returns the measured values + * without scaling factors applied. + * + * The command 0x0202 \"Read Data Ready\" can be used to check if new + * data is available since the last read operation. If no new data is + * available, the previous values will be returned again. If no data is + * available at all (e.g. measurement not running for at least one + * second), all values will be at their upper limit (0xFFFF for `uint16`, + * 0x7FFF for `int16`). + * + * @param massConcentrationPm1p0 Value is scaled with factor 10: + * PM1.0 [µg/m³] = value / 10 + * Note: If this value is unknown, 0xFFFF is returned.* + * + * @param massConcentrationPm2p5 Value is scaled with factor 10: + * PM2.5 [µg/m³] = value / 10 + * Note: If this value is unknown, 0xFFFF is returned.* + * + * @param massConcentrationPm4p0 Value is scaled with factor 10: + * PM4.0 [µg/m³] = value / 10 + * Note: If this value is unknown, 0xFFFF is returned.* + * + * @param massConcentrationPm10p0 Value is scaled with factor 10: + * PM10.0 [µg/m³] = value / 10 + * Note: If this value is unknown, 0xFFFF is returned.* + * + * @param ambientHumidity Value is scaled with factor 100: + * RH [%] = value /100 + * Note: If this value is unknown, 0x7FFF is returned.* + * + * @param ambientTemperature Value is scaled with factor 200: + * T [°C] = value / 200 + * Note: If this value is unknown, 0x7FFF is returned.* + * + * @param vocIndex Value is scaled with factor 10: VOC Index = value / 10 + * Note: If this value is unknown, 0x7FFF is returned.* + * + * @param noxIndex Value is scaled with factor 10: NOx Index = value / 10 + * Note: If this value is unknown, which is the case for SEN54, + * 0x7FFF is returned. During the first 10..11 seconds after power-on + * or device reset, this value will be 0x7FFF as well.* + * + * @return 0 on success, an error code otherwise + */ + uint16_t readMeasuredValuesAsIntegers(uint16_t& massConcentrationPm1p0, + uint16_t& massConcentrationPm2p5, + uint16_t& massConcentrationPm4p0, + uint16_t& massConcentrationPm10p0, + int16_t& ambientHumidity, + int16_t& ambientTemperature, + int16_t& vocIndex, int16_t& noxIndex); + + /** + * readMeasuredRawValues() - Returns the measured raw values. + * + * The command 0x0202 \"Read Data Ready\" can be used to check if new + * data is available since the last read operation. If no new data is + * available, the previous values will be returned again. If no data + * is available at all (e.g. measurement not running for at least one + * second), all values will be at their upper limit (0xFFFF for `uint16`, + * 0x7FFF for `int16`). + * + * Supported sensors: SEN54 (no NOx), SEN55 + * + * @param rawHumidity Value is scaled with factor 100: RH [%] = value / 100 + * Note: If this value is unknown, 0x7FFF is returned.* + * + * @param rawTemperature Value is scaled with factor 200: + * T [°C] = value / 200 + * Note: If this value is unknown, 0x7FFF is returned.* + * + * @param rawVoc Raw measured VOC ticks without scale factor. + * Note: If this value is unknown, 0xFFFF is returned.* + * + * @param rawNox Raw measured NOx ticks without scale factor. + * Note: If this value is unknown, which is the case for SEN54, + * 0x7FFF is returned. During the first 10..11 seconds after power-on + * or device reset, this value will be 0x7FFF as well.* + * + * @return 0 on success, an error code otherwise + */ + uint16_t readMeasuredRawValues(int16_t& rawHumidity, + int16_t& rawTemperature, uint16_t& rawVoc, + uint16_t& rawNox); + + /** + * readMeasuredValuesSen50() - Returns the measured values for SEN50. + * + * The command 0x0202 \"Read Data Ready\" can be used to check if new + * data is available since the last read operation. If no new data is + * available, the previous values will be returned again. If no data is + * available at all (e.g. measurement not running for at least one + * second), all values will be NAN. + * + * @param massConcentrationPm1p0 PM1.0 [µg/m³] + * Note: If this value is unknown, NAN is returned.* + * + * @param massConcentrationPm2p5 PM2.5 [µg/m³] + * Note: If this value is unknown, NAN is returned.* + * + * @param massConcentrationPm4p0 PM4.0 [µg/m³] + * Note: If this value is unknown, NAN is returned.* + * + * @param massConcentrationPm10p0 PM10.0 [µg/m³] + * Note: If this value is unknown, NAN is returned.* + * + * @return 0 on success, an error code otherwise + */ + uint16_t readMeasuredValuesSen50(float& massConcentrationPm1p0, + float& massConcentrationPm2p5, + float& massConcentrationPm4p0, + float& massConcentrationPm10p0); + + /** + * readMeasuredPmValues() - Returns the measured particulate matter values. + * + * The command 0x0202 \"Read Data Ready\" can be used to check if new + * data is available since the last read operation. If no new data is + * available, the previous values will be returned again. If no data + * is available at all (e.g. measurement not running for at least one + * second), all values will be NAN. + * + * @param massConcentrationPm1p0 PM1.0 [µg/m³] + * Note: If this value is unknown, NAN is returned.* + * + * @param massConcentrationPm2p5 PM2.5 [µg/m³] + * Note: If this value is unknown, NAN is returned.* + * + * @param massConcentrationPm4p0 PM4.0 [µg/m³] + * Note: If this value is unknown, NAN is returned.* + * + * @param massConcentrationPm10p0 PM10.0 [µg/m³] + * Note: If this value is unknown, NAN is returned.* + * + * @param numberConcentrationPm0p5 PM0.5 [#/cm³] + * Note: If this value is unknown, NAN is returned.* + * + * @param numberConcentrationPm1p0 PM1.0 [#/cm³] + * Note: If this value is unknown, NAN is returned.* + * + * @param numberConcentrationPm2p5 PM2.5 [#/cm³] + * Note: If this value is unknown, NAN is returned.* + * + * @param numberConcentrationPm4p0 PM4.0 [#/cm³] + * Note: If this value is unknown, NAN is returned.* + * + * @param numberConcentrationPm10p0 PM10.0 [#/cm³] + * Note: If this value is unknown, NAN is returned.* + * + * @param typicalParticleSize Size [µm] + * Note: If this value is unknown, NAN is returned.* + * + * @return 0 on success, an error code otherwise + */ + uint16_t readMeasuredPmValues( + float& massConcentrationPm1p0, float& massConcentrationPm2p5, + float& massConcentrationPm4p0, float& massConcentrationPm10p0, + float& numberConcentrationPm0p5, float& numberConcentrationPm1p0, + float& numberConcentrationPm2p5, float& numberConcentrationPm4p0, + float& numberConcentrationPm10p0, float& typicalParticleSize); + + /** + * readMeasuredPmValuesAsIntegers() - Returns the measured particulate + * matter values. + * + * The command 0x0202 \"Read Data Ready\" can be used to check if new + * data is available since the last read operation. If no new data is + * available, the previous values will be returned again. If no data + * is available at all (e.g. measurement not running for at least one + * second), all values will be 0xFFFF. + * + * @param massConcentrationPm1p0 Value is scaled with factor 10: + * PM1.0 [µg/m³] = value / 1 + * Note: If this value is unknown, 0xFFFF is returned.* + * + * @param massConcentrationPm2p5 Value is scaled with factor 10: + * PM2.5 [µg/m³] = value / 1 + * Note: If this value is unknown, 0xFFFF is returned.* + * + * @param massConcentrationPm4p0 Value is scaled with factor 10: + * PM4.0 [µg/m³] = value / 1 + * Note: If this value is unknown, 0xFFFF is returned.* + * + * @param massConcentrationPm10p0 Value is scaled with factor 10: + * PM10.0 [µg/m³] = value / 1 + * Note: If this value is unknown, 0xFFFF is returned.* + * + * @param numberConcentrationPm0p5 Value is scaled with factor 10: + * PM0.5 [#/cm³] = value / 1 + * Note: If this value is unknown, 0xFFFF is returned.* + * + * @param numberConcentrationPm1p0 Value is scaled with factor 10: + * PM1.0 [#/cm³] = value / 1 + * Note: If this value is unknown, 0xFFFF is returned.* + * + * @param numberConcentrationPm2p5 Value is scaled with factor 10: + * PM2.5 [#/cm³] = value / 1 + * Note: If this value is unknown, 0xFFFF is returned.* + * + * @param numberConcentrationPm4p0 Value is scaled with factor 10: + * PM4.0 [#/cm³] = value / 1 + * Note: If this value is unknown, 0xFFFF is returned.* + * + * @param numberConcentrationPm10p0 Value is scaled with factor 10: + * PM10.0 [#/cm³] = value / 10 + * Note: If this value is unknown, 0xFFFF is returned.* + * + * @param typicalParticleSize Value is scaled with factor 1000: + * Size [µm] = value / 1000 + * Note: If this value is unknown, 0xFFFF is returned.* + * + * @return 0 on success, an error code otherwise + */ + uint16_t readMeasuredPmValuesAsIntegers( + uint16_t& massConcentrationPm1p0, uint16_t& massConcentrationPm2p5, + uint16_t& massConcentrationPm4p0, uint16_t& massConcentrationPm10p0, + uint16_t& numberConcentrationPm0p5, uint16_t& numberConcentrationPm1p0, + uint16_t& numberConcentrationPm2p5, uint16_t& numberConcentrationPm4p0, + uint16_t& numberConcentrationPm10p0, uint16_t& typicalParticleSize); + + /** + * startFanCleaning() - Starts the fan cleaning manually. The \"data + * ready\"-flag will be cleared immediately and during the next few seconds, + * no new measurement results will be available (old values will be + * returned). Once the cleaning is finished, the \"data ready\"-flag will be + * set and new measurement results will be available. + * + * When executing this command while cleaning is already active, the + * command does nothing. + * + * If you stop the measurement while fan cleaning is active, the cleaning + * will be aborted immediately. + * + * @note This command is only available in measure mode with PM measurement + * enabled, i.e. only if the fan is already running. In any other state, + * this command does nothing. + * + * @return 0 on success, an error code otherwise + */ + uint16_t startFanCleaning(void); + + /** + * setTemperatureOffsetSimple() - Sets the temperature offset parameter + * in degrees celsius for the device, while leaving the other parameters at + * their default setting. + * + * Supported sensors: SEN54, SEN55 + * + * @param tempOffset Constant temperature offset in degrees celsius. + * The default value is 0. + * + * @return 0 on success, an error code otherwise + */ + uint16_t setTemperatureOffsetSimple(float tempOffset); + + /** + * getTemperatureOffsetSimple() - Gets the temperature offset parameter + * in degrees celsius from the device. + * @note The other parameters, such as slope and time constant may differ + * from the default values, if they were previously set using + * `setTemperatureOffsetParameters`. + * + * Supported sensors: SEN54, SEN55 + * + * @param tempOffset Constant temperature offset in degrees celsius. + * + * @return 0 on success, an error code otherwise + */ + uint16_t getTemperatureOffsetSimple(float& tempOffset); + + /** + * setTemperatureOffsetParameters() - Sets the temperature offset parameters + * for the device. + * + * Supported sensors: SEN54, SEN55 + * + * @param tempOffset Constant temperature offset scaled with factor 200 (T + * [°C] = value / 200). The default value is 0. + * + * @param slope Normalized temperature offset slope scaled with factor 10000 + * (applied factor = value / 10000). The default value is 0. + * + * @param timeConstant Time constant [s] how fast the new slope and offset + * will be applied. After the specified value in seconds, 63% of the new + * slope and offset are applied. A time constant of zero means the new + * values will be applied immediately (within the next measure interval of 1 + * second). + * + * @return 0 on success, an error code otherwise + */ + uint16_t setTemperatureOffsetParameters(int16_t tempOffset, int16_t slope, + uint16_t timeConstant); + + /** + * getTemperatureOffsetParameters() - Gets the temperature offset parameters + * from the device. + * + * Supported sensors: SEN54, SEN55 + * + * @param tempOffset Constant temperature offset scaled with factor 200 (T + * [°C] = value / 200). + * + * @param slope Normalized temperature offset slope scaled with factor 10000 + * (applied factor = value / 10000). + * + * @param timeConstant Time constant [s] how fast the slope and offset are + * applied. After the specified value in seconds, 63% of the new slope and + * offset are applied. + * + * @return 0 on success, an error code otherwise + */ + uint16_t getTemperatureOffsetParameters(int16_t& tempOffset, int16_t& slope, + uint16_t& timeConstant); + + /** + * setWarmStartParameter() - Sets the warm start parameter for the device. + * + * Supported sensors: SEN54, SEN55 + * + * @note This parameter can be changed in any state of the device (and the + * getter immediately returns the new value), but it is applied only the + * next time starting a measurement, i.e. when sending a \"Start + * Measurement\" command! So the parameter needs to be set *before* a + * warm-start measurement is started. + * + * @param warmStart Warm start behavior as a value in the range from 0 (cold + * start) to 65535 (warm start). The default value is 0. + * + * @return 0 on success, an error code otherwise + */ + uint16_t setWarmStartParameter(uint16_t warmStart); + + /** + * getWarmStartParameter() - Gets the warm start parameter from the device. + * + * Supported sensors: SEN54, SEN55 + * + * @param warmStart Warm start behavior as a value in the range from 0 (cold + * start) to 65535 (warm start). + * + * @return 0 on success, an error code otherwise + */ + uint16_t getWarmStartParameter(uint16_t& warmStart); + + /** + * setVocAlgorithmTuningParameters() - Sets the tuning parameters of the VOC + * algorithm. + * + * Supported sensors: SEN54, SEN55 + * + * @note This command is available only in idle mode. In measure mode, this + * command has no effect. In addition, it has no effect if at least one + * parameter is outside the specified range. + * + * @param indexOffset VOC index representing typical (average) conditions. + * Allowed values are in range 1..250. The default value is 100. + * + * @param learningTimeOffsetHours Time constant to estimate the VOC + * algorithm offset from the history in hours. Past events will be forgotten + * after about twice the learning time. Allowed values are in range 1..1000. + * The default value is 12 hours. + * + * @param learningTimeGainHours Time constant to estimate the VOC algorithm + * gain from the history in hours. Past events will be forgotten after about + * twice the learning time. Allowed values are in range 1..1000. The default + * value is 12 hours. + * + * @param gatingMaxDurationMinutes Maximum duration of gating in minutes + * (freeze of estimator during high VOC index signal). Set to zero to + * disable the gating. Allowed values are in range 0..3000. The default + * value is 180 minutes. + * + * @param stdInitial Initial estimate for standard deviation. Lower value + * boosts events during initial learning period, but may result in larger + * device-to-device variations. Allowed values are in range 10..5000. The + * default value is 50. + * + * @param gainFactor Gain factor to amplify or to attenuate the VOC index + * output. Allowed values are in range 1..1000. The default value is 230. + * + * @return 0 on success, an error code otherwise + */ + uint16_t setVocAlgorithmTuningParameters(int16_t indexOffset, + int16_t learningTimeOffsetHours, + int16_t learningTimeGainHours, + int16_t gatingMaxDurationMinutes, + int16_t stdInitial, + int16_t gainFactor); + + /** + * getVocAlgorithmTuningParameters() - Gets the currently set tuning + * parameters of the VOC algorithm. + * + * Supported sensors: SEN54, SEN55 + * + * @param indexOffset VOC index representing typical (average) conditions. + * + * @param learningTimeOffsetHours Time constant to estimate the VOC + * algorithm offset from the history in hours. Past events will be forgotten + * after about twice the learning time. + * + * @param learningTimeGainHours Time constant to estimate the VOC algorithm + * gain from the history in hours. Past events will be forgotten after about + * twice the learning time. + * + * @param gatingMaxDurationMinutes Maximum duration of gating in minutes + * (freeze of estimator during high VOC index signal). Zero disables the + * gating. + * + * @param stdInitial Initial estimate for standard deviation. Lower value + * boosts events during initial learning period, but may result in larger + * device-to-device variations. + * + * @param gainFactor Gain factor to amplify or to attenuate the VOC index + * output. + * + * @return 0 on success, an error code otherwise + */ + uint16_t getVocAlgorithmTuningParameters(int16_t& indexOffset, + int16_t& learningTimeOffsetHours, + int16_t& learningTimeGainHours, + int16_t& gatingMaxDurationMinutes, + int16_t& stdInitial, + int16_t& gainFactor); + + /** + * setNoxAlgorithmTuningParameters() - Sets the tuning parameters of the NOx + * algorithm. + * + * Supported sensors: SEN55 + * + * @note This command is available only in idle mode. In measure mode, this + * command has no effect. In addition, it has no effect if at least one + * parameter is outside the specified range. + * + * @param indexOffset NOx index representing typical (average) conditions. + * Allowed values are in range 1..250. The default value is 1. + * + * @param learningTimeOffsetHours Time constant to estimate the NOx + * algorithm offset from the history in hours. Past events will be forgotten + * after about twice the learning time. Allowed values are in range 1..1000. + * The default value is 12 hours. + * + * @param learningTimeGainHours The time constant to estimate the NOx + * algorithm gain from the history has no impact for NOx. This parameter is + * still in place for consistency reasons with the VOC tuning parameters + * command. This parameter must always be set to 12 hours. + * + * @param gatingMaxDurationMinutes Maximum duration of gating in minutes + * (freeze of estimator during high NOx index signal). Set to zero to + * disable the gating. Allowed values are in range 0..3000. The default + * value is 720 minutes. + * + * @param stdInitial The initial estimate for standard deviation parameter + * has no impact for NOx. This parameter is still in place for consistency + * reasons with the VOC tuning parameters command. This parameter must + * always be set to 50. + * + * @param gainFactor Gain factor to amplify or to attenuate the NOx index + * output. Allowed values are in range 1..1000. The default value is 230. + * + * @return 0 on success, an error code otherwise + */ + uint16_t setNoxAlgorithmTuningParameters(int16_t indexOffset, + int16_t learningTimeOffsetHours, + int16_t learningTimeGainHours, + int16_t gatingMaxDurationMinutes, + int16_t stdInitial, + int16_t gainFactor); + + /** + * getNoxAlgorithmTuningParameters() - Gets the currently set tuning + * parameters of the NOx algorithm. + * + * Supported sensors: SEN55 + * + * @param indexOffset NOx index representing typical (average) conditions. + * + * @param learningTimeOffsetHours Time constant to estimate the NOx + * algorithm offset from the history in hours. Past events will be forgotten + * after about twice the learning time. + * + * @param learningTimeGainHours The time constant to estimate the NOx + * algorithm gain from the history has no impact for NOx. This parameter is + * still in place for consistency reasons with the VOC tuning parameters + * command. + * + * @param gatingMaxDurationMinutes Maximum duration of gating in minutes + * (freeze of estimator during high NOx index signal). Zero disables the + * gating. + * + * @param stdInitial The initial estimate for standard deviation has no + * impact for NOx. This parameter is still in place for consistency reasons + * with the VOC tuning parameters command. + * + * @param gainFactor Gain factor to amplify or to attenuate the NOx index + * output. + * + * @return 0 on success, an error code otherwise + */ + uint16_t getNoxAlgorithmTuningParameters(int16_t& indexOffset, + int16_t& learningTimeOffsetHours, + int16_t& learningTimeGainHours, + int16_t& gatingMaxDurationMinutes, + int16_t& stdInitial, + int16_t& gainFactor); + + /** + * setRhtAccelerationMode() - Sets the RH/T acceleration mode. + * + * Supported sensors: SEN54, SEN55 + * + * @note This parameter can be changed in any state of the device (and the + * getter immediately returns the new value), but it is applied only the + * next time starting a measurement, i.e. when sending a \"Start + * Measurement\" command. So the parameter needs to be set *before* a new + * measurement is started. + * + * @param mode The new RH/T acceleration mode. + * + * @return 0 on success, an error code otherwise + */ + uint16_t setRhtAccelerationMode(uint16_t mode); + + /** + * getRhtAccelerationMode() - Gets the RH/T acceleration mode. + * + * Supported sensors: SEN54, SEN55 + * + * @param mode The current RH/T acceleration mode. + * + * @return 0 on success, an error code otherwise + */ + uint16_t getRhtAccelerationMode(uint16_t& mode); + + /** + * setVocAlgorithmState() - Sets the VOC algorithm state previously received + * with the \"Get VOC Algorithm State\" command. + * + * Supported sensors: SEN54, SEN55 + * + * @note This command is only available in idle mode and the state will be + * applied only once when starting the next measurement. Any further + * measurements (i.e. when stopping and restarting the measure mode) will + * reset the state to initial values. In measure mode, this command has no + * effect. + * + * @param state VOC algorithm state to restore. + * + * @return 0 on success, an error code otherwise + */ + uint16_t setVocAlgorithmState(const uint8_t state[], uint8_t stateSize); + + /** + * getVocAlgorithmState() - Gets the current VOC algorithm state. This data + * can be used to restore the state with the \"Set VOC Algorithm State\" + * command after a short power cycle or device reset. + * + * This command can be used either in measure mode or in idle mode + * (which will then return the state at the time when the measurement + * was stopped). In measure mode, the state can be read each measure + * interval to always have the latest state available, even in case of + * a sudden power loss. + * + * Supported sensors: SEN54, SEN55 + * + * @param state Current VOC algorithm state. + * + * @return 0 on success, an error code otherwise + */ + uint16_t getVocAlgorithmState(uint8_t state[], uint8_t stateSize); + + /** + * setFanAutoCleaningInterval() - Sets the fan auto cleaning interval for + * the device. + * + * @param interval Fan auto cleaning interval [s]. Set to zero to disable + * auto cleaning. + * + * @return 0 on success, an error code otherwise + */ + uint16_t setFanAutoCleaningInterval(uint32_t interval); + + /** + * getFanAutoCleaningInterval() - Gets the fan auto cleaning interval from + * the device. + * + * @param interval Fan auto cleaning interval [s]. Zero means auto cleaning + * is disabled. + * + * @return 0 on success, an error code otherwise + */ + uint16_t getFanAutoCleaningInterval(uint32_t& interval); + + /** + * getProductName() - Gets the product name from the device. + * + * @param productName Null-terminated ASCII string containing the product + * name. Up to 32 characters can be read from the device. + * + * @return 0 on success, an error code otherwise + */ + uint16_t getProductName(unsigned char productName[], + uint8_t productNameSize); + + /** + * getSerialNumber() - Gets the serial number from the device. + * + * @param serialNumber Null-terminated ASCII string containing the serial + * number. Up to 32 characters can be read from the device. + * + * @return 0 on success, an error code otherwise + */ + uint16_t getSerialNumber(unsigned char serialNumber[], + uint8_t serialNumberSize); + + /** + * getVersion() - Gets the version information for the hardware, firmware + * and communication protocol. + * + * @param firmwareMajor Firmware major version number. + * + * @param firmwareMinor Firmware minor version number. + * + * @param firmwareDebug Firmware debug state. If the debug state is set, the + * firmware is in development. + * + * @param hardwareMajor Hardware major version number. + * + * @param hardwareMinor Hardware minor version number. + * + * @param protocolMajor Protocol major version number. + * + * @param protocolMinor Protocol minor version number. + * + * @param padding Padding byte, ignore this. + * + * @return 0 on success, an error code otherwise + */ + uint16_t getVersion(uint8_t& firmwareMajor, uint8_t& firmwareMinor, + bool& firmwareDebug, uint8_t& hardwareMajor, + uint8_t& hardwareMinor, uint8_t& protocolMajor, + uint8_t& protocolMinor); + + /** + * readDeviceStatus() - Reads the current device status. + * + * Use this command to get detailed information about the device status. + * The device status is encoded in flags. Each device status flag + * represents a single bit in a 32-bit integer value. If more than one + * error is present, the device status register value is the sum of the + * corresponding flag values. For details about the available flags, + * refer to the device status flags documentation. + * + * @note The status flags of type \"Error\" are sticky, i.e. they are not + * cleared automatically even if the error condition no longer exists. So + * they can only be cleared manually with the command 0xD210 \"Read And + * Clear Device Status\" or with a device reset. All other flags are not + * sticky, i.e. they are cleared automatically if the trigger condition + * disappears. + * + * @param deviceStatus Device status (32 flags as an integer value). For + * details, please refer to the device status flags documentation. + * + * @return 0 on success, an error code otherwise + */ + uint16_t readDeviceStatus(uint32_t& deviceStatus); + + /** + * readAndClearDeviceStatus() - Reads the current device status (like + * command 0xD206 \"Read Device Status\") and afterwards clears all flags. + * + * @param deviceStatus Device status (32 flags as an integer value) + * **before** clearing it. For details, please refer to the device status + * flags documentation. + * + * @return 0 on success, an error code otherwise + */ + uint16_t readAndClearDeviceStatus(uint32_t& deviceStatus); + + /** + * deviceReset() - Executes a reset on the device. This has the same effect + * as a power cycle. + * + * @return 0 on success, an error code otherwise + */ + uint16_t deviceReset(void); + + private: + TwoWire* _i2cBus = nullptr; +}; + +#endif /* SENSIRIONI2CSEN5X_H */ diff --git a/tasmota/include/tasmota_configurations.h b/tasmota/include/tasmota_configurations.h index 1ed060c54..bc572e890 100644 --- a/tasmota/include/tasmota_configurations.h +++ b/tasmota/include/tasmota_configurations.h @@ -98,6 +98,7 @@ #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_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_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) diff --git a/tasmota/include/tasmota_configurations_ESP32.h b/tasmota/include/tasmota_configurations_ESP32.h index 9360a109c..673c70d49 100644 --- a/tasmota/include/tasmota_configurations_ESP32.h +++ b/tasmota/include/tasmota_configurations_ESP32.h @@ -369,6 +369,7 @@ //#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_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_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) @@ -586,6 +587,7 @@ #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_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_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) diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index 6eee06d05..64b040afc 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -619,6 +619,7 @@ #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_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_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) diff --git a/tasmota/tasmota_support/support_features.ino b/tasmota/tasmota_support/support_features.ino index 8f52b111e..c1e816293 100644 --- a/tasmota/tasmota_support/support_features.ino +++ b/tasmota/tasmota_support/support_features.ino @@ -870,8 +870,9 @@ void ResponseAppendFeatures(void) #ifdef USE_TUYAMCUBR feature9 |= 0x00004000; // xdrv_65_tuyamcubr.ino #endif - -// feature9 |= 0x00008000; +#if defined(USE_I2C) && defined(USE_SEN5X) + feature9 |= 0x00008000; // xsns_103_sen5x.ino +#endif // feature9 |= 0x00010000; // feature9 |= 0x00020000; diff --git a/tasmota/tasmota_xsns_sensor/xsns_103_sen5x.ino b/tasmota/tasmota_xsns_sensor/xsns_103_sen5x.ino new file mode 100644 index 000000000..720550137 --- /dev/null +++ b/tasmota/tasmota_xsns_sensor/xsns_103_sen5x.ino @@ -0,0 +1,312 @@ +/* + xsns_103_sen5x.ino - SEN5X gas and air quality sensor support for Tasmota + + Copyright (C) 2022 Tyeth Gundry + + 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 . +*/ + +#ifdef USE_I2C +#ifdef USE_SEN5X +/*********************************************************************************************\ + * SEN5X - Gas (VOC - Volatile Organic Compounds / NOx - Nitrous Oxides) and Particulates (PPM) + * + * Source: Sensirion SEN5X Driver + Example, and Tasmota Driver 98 by Jean-Pierre Deschamps + * Adaption for TASMOTA: Tyeth Gundry + * + * I2C Address: 0x59 +\*********************************************************************************************/ + +#define XSNS_103 103 +#define XI2C_76 76 // See I2CDEVICES.md + +#define SEN5X_ADDRESS 0x69 + +#include +#include +SensirionI2CSen5x *sen5x = nullptr; + +struct SEN5XDATA_s { + bool sen5x_ready; + float abshum; + float massConcentrationPm1p0; + float massConcentrationPm2p5; + float massConcentrationPm4p0; + float massConcentrationPm10p0; + float ambientHumidity; + float ambientTemperature; + float vocIndex; + float noxIndex; +} *SEN5XDATA = nullptr; +/********************************************************************************************/ + +void sen5x_Init(void) +{ + if(!TasmotaGlobal.i2c_enabled){ + DEBUG_SENSOR_LOG(PSTR("I2C Not enabled, so not loading SEN5X driver.")); + return; + } + int usingI2cBus = 0; +#ifdef ESP32 + if (!I2cSetDevice(SEN5X_ADDRESS, 0)) + { + DEBUG_SENSOR_LOG(PSTR("Sensirion SEN5X not found, i2c bus 0")); + if (TasmotaGlobal.i2c_enabled_2 ){ + + if(!I2cSetDevice(SEN5X_ADDRESS, 1)){ + DEBUG_SENSOR_LOG(PSTR("Sensirion SEN5X not found, i2c bus 1")); + return; + } + usingI2cBus = 1; + } + else { + return; + } + } +#else + if (!I2cSetDevice(SEN5X_ADDRESS)) + { + DEBUG_SENSOR_LOG(PSTR("Sensirion SEN5X not found, i2c bus 0")); + return; + } +#endif + if (SEN5XDATA == nullptr) + SEN5XDATA = (SEN5XDATA_s *)calloc(1, sizeof(struct SEN5XDATA_s)); + SEN5XDATA->sen5x_ready = false; + if(sen5x == nullptr) sen5x = new SensirionI2CSen5x(); + if(usingI2cBus==1){ +#ifdef ESP32 + sen5x->begin(Wire1); +#else + sen5x->begin(Wire); +#endif + } + else { + sen5x->begin(Wire); + } + int error_stop = sen5x->deviceReset(); + if (error_stop != 0) + { + DEBUG_SENSOR_LOG(PSTR("Sensirion SEN5X failed to reset device (I2C Bus %d)"), usingI2cBus); + return; + } + // Wait 1 second for sensors to start recording + 100ms for reset command + delay(1100); + int error_start = sen5x->startMeasurement(); + if (error_start != 0) + { + DEBUG_SENSOR_LOG(PSTR("Sensirion SEN5X failed to start measurement (I2C Bus %d)"), usingI2cBus); + return; + } + SEN5XDATA->sen5x_ready = true; + I2cSetActiveFound(SEN5X_ADDRESS, "SEN5X", usingI2cBus); + DEBUG_SENSOR_LOG(PSTR("Sensirion SEN5X found, i2c bus %d"), usingI2cBus); +} + +// #define POW_FUNC pow +#define POW_FUNC FastPrecisePow + +float sen5x_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 SEN5XUpdate(void) // Perform every second to ensure proper operation of the baseline compensation algorithm +{ + uint16_t error; + char errorMessage[256]; + DEBUG_SENSOR_LOG(PSTR("Running readMeasuredValues for SEN5X...")); + + error = sen5x->readMeasuredValues( + SEN5XDATA->massConcentrationPm1p0, SEN5XDATA->massConcentrationPm2p5, SEN5XDATA->massConcentrationPm4p0, + SEN5XDATA->massConcentrationPm10p0, SEN5XDATA->ambientHumidity, SEN5XDATA->ambientTemperature, SEN5XDATA->vocIndex, + SEN5XDATA->noxIndex); + + if (error) + { + AddLog(LOG_LEVEL_DEBUG, PSTR("Failed to retrieve SEN5X readings.")); + #ifdef DEBUG_TASMOTA_SENSOR + DEBUG_SENSOR_LOG(PSTR("Error trying to execute readMeasuredValues(): \n")); + errorToString(error, errorMessage, 256); + DEBUG_SENSOR_LOG(errorMessage); + #endif + } + else + { +#ifdef DEBUG_TASMOTA_SENSOR + DEBUG_SENSOR_LOG(PSTR("SEN5x readings:-")); + DEBUG_SENSOR_LOG(PSTR("MassConcentrationPm1p0: %f\n"), SEN5XDATA->massConcentrationPm1p0); + DEBUG_SENSOR_LOG(PSTR("MassConcentrationPm2p5: %f\n"), SEN5XDATA->massConcentrationPm2p5); + DEBUG_SENSOR_LOG(PSTR("MassConcentrationPm4p0: %f\n"), SEN5XDATA->massConcentrationPm4p0); + DEBUG_SENSOR_LOG(PSTR("MassConcentrationPm10p0: %f\n"), SEN5XDATA->massConcentrationPm10p0); + if (isnan(SEN5XDATA->ambientHumidity)) + { + DEBUG_SENSOR_LOG(PSTR("AmbientHumidity: n/a\n")); + } + else + { + DEBUG_SENSOR_LOG(PSTR("AmbientHumidity: %f\n"), SEN5XDATA->ambientHumidity); + } + + if (isnan(SEN5XDATA->ambientTemperature)) + { + DEBUG_SENSOR_LOG(PSTR("AmbientTemperature: n/a\n")); + } + else + { + DEBUG_SENSOR_LOG(PSTR("AmbientTemperature: %f\n"), SEN5XDATA->ambientTemperature); + } + + if (isnan(SEN5XDATA->vocIndex)) + { + DEBUG_SENSOR_LOG(PSTR("VocIndex: n/a\n")); + } + else + { + DEBUG_SENSOR_LOG(PSTR("VocIndex: %f\n"), SEN5XDATA->vocIndex); + } + + if (isnan(SEN5XDATA->noxIndex)) + { + DEBUG_SENSOR_LOG(PSTR("NoxIndex: n/a\n")); + } + else + { + DEBUG_SENSOR_LOG(PSTR("NoxIndex: %f\n"), SEN5XDATA->noxIndex); + } +#endif + } + if (!isnan(SEN5XDATA->ambientTemperature) && SEN5XDATA->ambientHumidity > 0) { + SEN5XDATA->abshum = sen5x_AbsoluteHumidity(SEN5XDATA->ambientTemperature, SEN5XDATA->ambientHumidity); + DEBUG_SENSOR_LOG(PSTR("AbsoluteHumidity: %f\n"), SEN5XDATA->abshum); + } +} + +#ifdef USE_WEBSERVER +const char HTTP_SNS_SEN5X_UNITS[] PROGMEM = "{s}SEN5X %s{m}%.*f %s{e}"; +const char HTTP_SNS_SEN5X_UNITLESS[] PROGMEM = "{s}SEN5X %s{m}%.*f{e}"; +// {s} = , {m} = , {e} = +const char HTTP_SNS_AHUMSEN5X[] PROGMEM = "{s}SEN5X Abs Humidity{m}%s g/m³{e}"; +#endif + +#define D_JSON_AHUM "aHumidity" + +void SEN5XShow(bool json) +{ + if (SEN5XDATA->sen5x_ready) + { + char sen5x_abs_hum[33]; + bool ahum_available = !isnan(SEN5XDATA->ambientTemperature) && (SEN5XDATA->ambientHumidity > 0); + if (ahum_available) + { + // has humidity + temperature + dtostrfd(SEN5XDATA->abshum, 4, sen5x_abs_hum); + } + if (json) + { + ResponseAppend_P(PSTR(",\"SEN5X\":{")); + ResponseAppend_P(PSTR("\"PM1\":%.1f,"), SEN5XDATA->massConcentrationPm1p0); + ResponseAppend_P(PSTR("\"PM2.5\":%.1f,"), SEN5XDATA->massConcentrationPm2p5); + ResponseAppend_P(PSTR("\"PM4\":%.1f,"), SEN5XDATA->massConcentrationPm4p0); + ResponseAppend_P(PSTR("\"PM10\":%.1f,"), SEN5XDATA->massConcentrationPm10p0); + if (!isnan(SEN5XDATA->noxIndex)) + ResponseAppend_P(PSTR("\"NOx\":%.0f,"), SEN5XDATA->noxIndex); + if (!isnan(SEN5XDATA->vocIndex)) + ResponseAppend_P(PSTR("\"VOC\":%.0f,"), SEN5XDATA->vocIndex); + if (!isnan(SEN5XDATA->ambientTemperature)) + ResponseAppendTHD(SEN5XDATA->ambientTemperature, SEN5XDATA->ambientHumidity); + if (ahum_available) + ResponseAppend_P(PSTR(",\"" D_JSON_AHUM "\":%s"), sen5x_abs_hum); + ResponseJsonEnd(); + } + +#ifdef USE_WEBSERVER + + WSContentSend_PD(HTTP_SNS_SEN5X_UNITS, "PM1", 1, SEN5XDATA->massConcentrationPm1p0, "μg/m³"); + WSContentSend_PD(HTTP_SNS_SEN5X_UNITS, "PM2.5", 1, SEN5XDATA->massConcentrationPm2p5, "μg/m³"); + WSContentSend_PD(HTTP_SNS_SEN5X_UNITS, "PM4", 1, SEN5XDATA->massConcentrationPm4p0, "μg/m³"); + WSContentSend_PD(HTTP_SNS_SEN5X_UNITS, "PM10", 1, SEN5XDATA->massConcentrationPm10p0, "μg/m³"); + if (!isnan(SEN5XDATA->noxIndex)) + WSContentSend_PD(HTTP_SNS_SEN5X_UNITLESS, "NOx", 0, SEN5XDATA->noxIndex); + if (!isnan(SEN5XDATA->vocIndex)) + WSContentSend_PD(HTTP_SNS_SEN5X_UNITLESS, "VOC", 0, SEN5XDATA->vocIndex); + if (!isnan(SEN5XDATA->ambientTemperature)) + WSContentSend_PD(HTTP_SNS_SEN5X_UNITS, "Temperature", 2, SEN5XDATA->ambientTemperature, "°C"); + if (!isnan(SEN5XDATA->ambientHumidity)) + WSContentSend_PD(HTTP_SNS_SEN5X_UNITS, "Humidity", 2, SEN5XDATA->ambientHumidity, "%RH"); + if (ahum_available) + WSContentSend_PD(HTTP_SNS_AHUMSEN5X, sen5x_abs_hum); + +#endif + } +} + +/*********************************************************************************************\ + * Interface +\*********************************************************************************************/ + +bool Xsns103(uint32_t function) +{ + if (!I2cEnabled(XI2C_76)) + { + return false; + } + + bool result = false; + + if (FUNC_INIT == function) + { + sen5x_Init(); + } + else if (SEN5XDATA != nullptr) + { + switch (function) + { + case FUNC_EVERY_SECOND: + SEN5XUpdate(); + break; + case FUNC_JSON_APPEND: + SEN5XShow(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + SEN5XShow(0); + break; +#endif // USE_WEBSERVER + } + } + return result; +} + +#endif // USE_SEN5X +#endif // USE_I2C diff --git a/tools/decode-status.py b/tools/decode-status.py index 843a2d0e8..567d54d90 100755 --- a/tools/decode-status.py +++ b/tools/decode-status.py @@ -290,7 +290,7 @@ a_features = [[ "USE_SGP40","USE_LUXV30B","USE_CANSNIFFER","USE_QMC5883L", "USE_MODBUS_ENERGY","USE_SHELLY_PRO","USE_DALI","USE_BP1658CJ", "USE_DINGTIAN_RELAY","USE_HMC5883L","USE_LD2410","USE_ME007", - "USE_DISPLAY_TM1650","USE_PCA9632","USE_TUYAMCUBR","", + "USE_DISPLAY_TM1650","USE_PCA9632","USE_TUYAMCUBR","USE_SEN5X", "","","","", "","","","", "","","","",