From 6266930a4d466881b3269c9ee763e92013a61ae3 Mon Sep 17 00:00:00 2001 From: Rui Marinho Date: Sun, 2 Aug 2020 07:12:38 +0100 Subject: [PATCH] sns: Add BME680 (#2295) Add support for BME680 using libalgobsec proprietary algorithms for precise Indoor Air Quality (IAQ) measurement. Unlike traditional CO2 sensors - and good ones are expensive - it measures nearly all VOCs compounds in the air (plus other gases) and compensates those measurements with its built-in temperature and humidity sensors to determine indoor air quality. Co-authored-by: Max Prokhorov --- README.md | 1 + code/espurna/board.cpp | 3 + code/espurna/config/arduino.h | 1 + code/espurna/config/dependencies.h | 9 + code/espurna/config/general.h | 4 +- code/espurna/config/sensors.h | 43 ++- code/espurna/config/types.h | 7 +- code/espurna/debug.cpp | 2 +- code/espurna/sensor.cpp | 44 ++- code/espurna/sensors/BME680Sensor.h | 344 ++++++++++++++++++++++ code/espurna/sensors/BaseSensor.h | 3 +- code/platformio.ini | 3 +- code/scripts/espurna_utils/__init__.py | 2 + code/scripts/espurna_utils/ldscripts.py | 1 - code/scripts/espurna_utils/libalgobsec.py | 69 +++++ code/scripts/pio_main.py | 4 + code/test/build/sensor.h | 1 + 17 files changed, 524 insertions(+), 17 deletions(-) create mode 100644 code/espurna/sensors/BME680Sensor.h create mode 100644 code/scripts/espurna_utils/libalgobsec.py diff --git a/README.md b/README.md index 28fb504740..89bd8842c3 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,7 @@ Since November 2018, Max Prokhorov (**@mcspr**) is also actively working as a co * Environment * **DHT11 / DHT12 / DHT22 / DHT21 / AM2301 / Itead's SI7021** * **BMP180**, **BMP280** and **BME280** pressure, humidity (BME280) and temperature (BMP280 & BME280) sensor by Bosch + * **BME680** pressure, humidity, temperature and gas sensor by Bosch with support for Bosch's proprietary library BSEC for accurate Indoor Air Quality (IAQ) monitoring (⚠️ [learn more](https://github.com/xoseperez/espurna/wiki/Sensors#bme680-environmental-sensor)) * **TMP35** and **TMP36** analog temperature sensors * **MAX6675** temperature sensor * **NTC** temperature sensors diff --git a/code/espurna/board.cpp b/code/espurna/board.cpp index ef6952ab0d..18b04952c6 100644 --- a/code/espurna/board.cpp +++ b/code/espurna/board.cpp @@ -203,6 +203,9 @@ PROGMEM const char espurna_sensors[] = #if BMX280_SUPPORT "BMX280 " #endif + #if BME680_SUPPORT + "BME680 " + #endif #if CSE7766_SUPPORT "CSE7766 " #endif diff --git a/code/espurna/config/arduino.h b/code/espurna/config/arduino.h index 276b4ad3cd..f37b0f879d 100644 --- a/code/espurna/config/arduino.h +++ b/code/espurna/config/arduino.h @@ -229,6 +229,7 @@ //#define BH1750_SUPPORT 1 //#define BMP180_SUPPORT 1 //#define BMX280_SUPPORT 1 +//#define BME680_SUPPORT 1 //#define CSE7766_SUPPORT 1 //#define DALLAS_SUPPORT 1 //#define DHT_SUPPORT 1 diff --git a/code/espurna/config/dependencies.h b/code/espurna/config/dependencies.h index 16ef0ca187..4d82d48c96 100644 --- a/code/espurna/config/dependencies.h +++ b/code/espurna/config/dependencies.h @@ -221,3 +221,12 @@ #warning "MQTT_MAX_PACKET_SIZE should be set in `build_flags = ...` of the environment! Default value is used instead." #endif #endif + +//------------------------------------------------------------------------------ +// Disable BME680 support if using Core version 2.3.0 due to memory constraints. + +#if BME680_SUPPORT && defined(ARDUINO_ESP8266_RELEASE_2_3_0) +#warning "BME680_SUPPORT is not available when using Arduino Core 2.3.0 due to memory constraints. Please use Arduino Core 2.6.3+ instead (or set `platform = ${common.platform_latest}` for the latest version)." +#undef BME680_SUPPORT +#define BME680_SUPPORT 0 +#endif diff --git a/code/espurna/config/general.h b/code/espurna/config/general.h index 74278c0303..58a3b20928 100644 --- a/code/espurna/config/general.h +++ b/code/espurna/config/general.h @@ -667,7 +667,7 @@ #endif // ref: https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/kconfig.html#config-lwip-esp-gratuitous-arp -// ref: https://github.com/xoseperez/espurna/pull/1877#issuecomment-525612546 +// ref: https://github.com/xoseperez/espurna/pull/1877#issuecomment-525612546 // // Broadcast gratuitous ARP periodically to update ARP tables on the AP and all devices on the same network. // Helps to solve compatibility issues when ESP fails to timely reply to ARP requests, causing the device's ARP table entry to expire. @@ -1028,7 +1028,7 @@ #endif #ifndef MQTT_SECURE_CLIENT_MFLN -#define MQTT_SECURE_CLIENT_MFLN SECURE_CLIENT_MFLN // Use global MFLN setting by default +#define MQTT_SECURE_CLIENT_MFLN SECURE_CLIENT_MFLN // Use global MFLN setting by default #endif #ifndef MQTT_SECURE_CLIENT_INCLUDE_CA diff --git a/code/espurna/config/sensors.h b/code/espurna/config/sensors.h index 239b97bb81..13a5f1d7de 100644 --- a/code/espurna/config/sensors.h +++ b/code/espurna/config/sensors.h @@ -782,39 +782,39 @@ // LDR sensor // Enable support by passing LDR_SUPPORT=1 build flag //------------------------------------------------------------------------------ - + #ifndef LDR_SUPPORT #define LDR_SUPPORT 0 #endif - + #ifndef LDR_SAMPLES #define LDR_SAMPLES 10 // Number of samples #endif - + #ifndef LDR_DELAY #define LDR_DELAY 0 // Delay between samples in micros #endif - + #ifndef LDR_TYPE #define LDR_TYPE LDR_GL5528 #endif - + #ifndef LDR_ON_GROUND #define LDR_ON_GROUND true #endif - + #ifndef LDR_RESISTOR #define LDR_RESISTOR 10000 // Resistance #endif - + #ifndef LDR_MULTIPLICATION #define LDR_MULTIPLICATION 32017200 #endif - + #ifndef LDR_POWER #define LDR_POWER 1.5832 #endif - + //------------------------------------------------------------------------------ // MHZ19 CO2 sensor // Enable support by passing MHZ19_SUPPORT=1 build flag @@ -1303,6 +1303,29 @@ #define SI1145_ADDRESS 0x60 #endif +//------------------------------------------------------------------------------ +// BME680 +// Enable support by passing BME680_SUPPORT=1 build flag +//------------------------------------------------------------------------------ + +#ifndef BME680_SUPPORT +#define BME680_SUPPORT 0 +#endif + +#ifndef BME680_I2C_ADDRESS +#define BME680_I2C_ADDRESS 0x00 // 0x00 means auto +#endif + +#ifndef BME680_BSEC_CONFIG +#define BME680_BSEC_CONFIG BME680_BSEC_CONFIG_GENERIC_33V_3S_4D // BSEC config config value. By default, 3.3V as supply voltage, +#endif // 3 seconds as maximum time between bsec_sensor_control` calls + // and 4 days as the time considered for background calibration. + +#ifndef BME680_STATE_SAVE_INTERVAL +#define BME680_STATE_SAVE_INTERVAL 0 // How frequently (in milliseconds) should state be stored in +#endif // non-volatile memory. A common value would be every 6h or + // 360 * 60 * 1000 milliseconds. By default, this is disabled. + // ----------------------------------------------------------------------------- // ADC // ----------------------------------------------------------------------------- @@ -1356,6 +1379,7 @@ BH1750_SUPPORT || \ BMP180_SUPPORT || \ BMX280_SUPPORT || \ + BME680_SUPPORT || \ EMON_ADC121_SUPPORT || \ EMON_ADS1X15_SUPPORT || \ SHT3X_I2C_SUPPORT || \ @@ -1391,6 +1415,7 @@ ANALOG_SUPPORT || \ BH1750_SUPPORT || \ BMP180_SUPPORT || \ + BME680_SUPPORT || \ BMX280_SUPPORT || \ CSE7766_SUPPORT || \ DALLAS_SUPPORT || \ diff --git a/code/espurna/config/types.h b/code/espurna/config/types.h index 6f18a541ad..935557588c 100644 --- a/code/espurna/config/types.h +++ b/code/espurna/config/types.h @@ -333,6 +333,7 @@ #define SENSOR_SI1145_ID 39 #define SENSOR_HDC1080_ID 40 #define SENSOR_PZEM004TV30_ID 41 +#define SENSOR_BME680_ID 42 //-------------------------------------------------------------------------------- // Magnitudes @@ -372,8 +373,12 @@ #define MAGNITUDE_RESISTANCE 30 #define MAGNITUDE_PH 31 #define MAGNITUDE_FREQUENCY 32 +#define MAGNITUDE_IAQ 33 +#define MAGNITUDE_IAQ_ACCURACY 34 +#define MAGNITUDE_IAQ_STATIC 35 +#define MAGNITUDE_VOC 36 -#define MAGNITUDE_MAX 33 +#define MAGNITUDE_MAX 38 #define SENSOR_ERROR_OK 0 // No error #define SENSOR_ERROR_OUT_OF_RANGE 1 // Result out of sensor range diff --git a/code/espurna/debug.cpp b/code/espurna/debug.cpp index 3818cf09f8..74e9cb3eba 100644 --- a/code/espurna/debug.cpp +++ b/code/espurna/debug.cpp @@ -328,7 +328,7 @@ DebugLogMode convert(const String& value) { void debugConfigureBoot() { static_assert( - std::is_same::type>::value, + std::is_same::type>::value, "should be able to match DebugLogMode with int" ); diff --git a/code/espurna/sensor.cpp b/code/espurna/sensor.cpp index ae74e09b65..7b044769ec 100644 --- a/code/espurna/sensor.cpp +++ b/code/espurna/sensor.cpp @@ -59,6 +59,10 @@ Copyright (C) 2016-2019 by Xose Pérez #include "sensors/BMX280Sensor.h" #endif +#if BME680_SUPPORT + #include "sensors/BME680Sensor.h" +#endif + #if CSE7766_SUPPORT #include "sensors/CSE7766Sensor.h" #endif @@ -515,6 +519,8 @@ sensor_magnitude_t::sensor_magnitude_t(unsigned char slot, unsigned char index_l ++_counts[type]; switch (type) { + case MAGNITUDE_IAQ: + case MAGNITUDE_IAQ_STATIC: case MAGNITUDE_ENERGY: filter = new LastFilter(); break; @@ -641,6 +647,18 @@ String magnitudeTopic(unsigned char type) { case MAGNITUDE_CO2: result = F("co2"); break; + case MAGNITUDE_VOC: + result = F("voc"); + break; + case MAGNITUDE_IAQ: + result = F("iaq"); + break; + case MAGNITUDE_IAQ_ACCURACY: + result = F("iaq_accuracy"); + break; + case MAGNITUDE_IAQ_STATIC: + result = F("iaq_static"); + break; case MAGNITUDE_LUX: result = F("lux"); break; @@ -927,6 +945,10 @@ const char * const _magnitudeSettingsPrefix(unsigned char type) { case MAGNITUDE_PM2dot5: return "pm1dot5"; case MAGNITUDE_PM10: return "pm10"; case MAGNITUDE_CO2: return "co2"; + case MAGNITUDE_VOC: return "voc"; + case MAGNITUDE_IAQ: return "iaq"; + case MAGNITUDE_IAQ_ACCURACY: return "iaqAccuracy"; + case MAGNITUDE_IAQ_STATIC: return "iaqStatic"; case MAGNITUDE_LUX: return "lux"; case MAGNITUDE_UVA: return "uva"; case MAGNITUDE_UVB: return "uvb"; @@ -1155,6 +1177,18 @@ String magnitudeName(unsigned char type) { case MAGNITUDE_CO2: result = F("CO2"); break; + case MAGNITUDE_VOC: + result = F("VOC"); + break; + case MAGNITUDE_IAQ_STATIC: + result = F("IAQ (Static)"); + break; + case MAGNITUDE_IAQ: + result = F("IAQ"); + break; + case MAGNITUDE_IAQ_ACCURACY: + result = F("IAQ Accuracy"); + break; case MAGNITUDE_LUX: result = F("Lux"); break; @@ -1570,6 +1604,14 @@ void _sensorLoad() { } #endif + #if BME680_SUPPORT + { + BME680Sensor * sensor = new BME680Sensor(); + sensor->setAddress(BME680_I2C_ADDRESS); + _sensors.push_back(sensor); + } + #endif + #if CSE7766_SUPPORT { CSE7766Sensor * sensor = new CSE7766Sensor(); @@ -1986,7 +2028,7 @@ void _sensorLoad() { _sensors.push_back(sensor); } #endif - + #if T6613_SUPPORT { T6613Sensor * sensor = new T6613Sensor(); diff --git a/code/espurna/sensors/BME680Sensor.h b/code/espurna/sensors/BME680Sensor.h new file mode 100644 index 0000000000..668154e602 --- /dev/null +++ b/code/espurna/sensors/BME680Sensor.h @@ -0,0 +1,344 @@ +// ----------------------------------------------------------------------------- +// BME680 Sensor over I2C +// Copyright (C) 2020 by Rui Marinho +// +// The BSEC software binaries and includes are only available for use after accepting its software +// license agreement. By enabling this sensor integration, you are agreeing to the terms of the license +// agreement available at the following URL: +// +// https://ae-bst.resource.bosch.com/media/_tech/media/bsec/2017-07-17_ClickThrough_License_Terms_Environmentalib_SW_CLEAN.pdf +// +// The Arduino wrapper and BME680 Sensor API used for this integration are licensed under the following terms: +// +// Copyright (c) 2020 Bosch Sensortec GmbH. All rights reserved. +// +// BSD-3-Clause +// +// 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. +// +// For more details, please refer to https://github.com/BoschSensortec/BSEC-Arduino-library. +// ----------------------------------------------------------------------------- + +#if SENSOR_SUPPORT && BME680_SUPPORT + +#pragma once + +#include +#include + +#include "I2CSensor.h" + +// Available configuration modes based on parameters: +// voltage / maximum time between sensor calls / time considered +// for background calibration. +#define BME680_BSEC_CONFIG_GENERIC_18V_3S_4D 0 +#define BME680_BSEC_CONFIG_GENERIC_18V_3S_28D 1 +#define BME680_BSEC_CONFIG_GENERIC_18V_300S_4D 2 +#define BME680_BSEC_CONFIG_GENERIC_18V_300S_28D 3 +#define BME680_BSEC_CONFIG_GENERIC_33V_3S_4D 4 +#define BME680_BSEC_CONFIG_GENERIC_33V_3S_28D 5 +#define BME680_BSEC_CONFIG_GENERIC_33V_300S_4D 6 +#define BME680_BSEC_CONFIG_GENERIC_33V_300S_28D 7 + +const uint8_t bsec_config_iaq[] = { + #if BME680_BSEC_CONFIG == BME680_BSEC_CONFIG_GENERIC_18V_3S_4D + #include + #elif BME680_BSEC_CONFIG == BME680_BSEC_CONFIG_GENERIC_18V_3S_28D + #include + #elif BME680_BSEC_CONFIG == BME680_BSEC_CONFIG_GENERIC_18V_300S_4D + #include + #elif BME680_BSEC_CONFIG == BME680_BSEC_CONFIG_GENERIC_18V_300S_28D + #include + #elif BME680_BSEC_CONFIG == BME680_BSEC_CONFIG_GENERIC_33V_3S_4D + #include + #elif BME680_BSEC_CONFIG == BME680_BSEC_CONFIG_GENERIC_33V_3S_28D + #include + #elif BME680_BSEC_CONFIG == BME680_BSEC_CONFIG_GENERIC_33V_300S_4D + #include + #elif BME680_BSEC_CONFIG == BME680_BSEC_CONFIG_GENERIC_33V_300S_28D + #include + #endif +}; + +class BME680Sensor : public I2CSensor<> { + + public: + + // --------------------------------------------------------------------- + // Public + // --------------------------------------------------------------------- + + BME680Sensor() { + _sensor_id = SENSOR_BME680_ID; + _count = 9; + } + + // --------------------------------------------------------------------- + // Sensor API + // --------------------------------------------------------------------- + + void begin() { + if (!_dirty) { + return; + } + + // I2C auto-discover + unsigned char addresses[] = {BME680_I2C_ADDR_PRIMARY, BME680_I2C_ADDR_SECONDARY}; + _address = _begin_i2c(_address, sizeof(addresses), addresses); + if (_address == 0) return; + + iaqSensor.begin(_address, Wire); + + #if SENSOR_DEBUG + DEBUG_MSG("[BME680] BSEC library version v%u.%u.%u.%u\n", + iaqSensor.version.major, + iaqSensor.version.minor, + iaqSensor.version.major_bugfix, + iaqSensor.version.minor_bugfix + ); + #endif + + if (!_isSensorOk()) { + _error = SENSOR_ERROR_OTHER; + return; + } + + iaqSensor.setConfig(bsec_config_iaq); + + _loadState(); + + float sampleRate; + + // BSEC configuration with 300s allows for the sensor to sleep for 300s + // on the ULP mode in order to minimize power consumption. + if (BME680_BSEC_CONFIG == BME680_BSEC_CONFIG_GENERIC_18V_300S_4D || + BME680_BSEC_CONFIG == BME680_BSEC_CONFIG_GENERIC_18V_300S_4D || + BME680_BSEC_CONFIG == BME680_BSEC_CONFIG_GENERIC_18V_300S_4D) { + sampleRate = BSEC_SAMPLE_RATE_ULP; + } else { + sampleRate = BSEC_SAMPLE_RATE_LP; + } + + iaqSensor.updateSubscription(sensorList, 12, sampleRate); + + if (!_isSensorOk()) { + _error = SENSOR_ERROR_OTHER; + return; + } + + _ready = true; + _dirty = false; + } + + // Descriptive name of the sensor + String description() { + char buffer[21]; + snprintf(buffer, sizeof(buffer), "BME680 @ I2C (0x%02X)", _address); + return String(buffer); + } + + // Type for slot # index + unsigned char type(unsigned char index) { + if (index == 0) return MAGNITUDE_TEMPERATURE; + if (index == 1) return MAGNITUDE_HUMIDITY; + if (index == 2) return MAGNITUDE_PRESSURE; + if (index == 3) return MAGNITUDE_RESISTANCE; + if (index == 4) return MAGNITUDE_IAQ_ACCURACY; + if (index == 5) return MAGNITUDE_IAQ; + if (index == 6) return MAGNITUDE_IAQ_STATIC; + if (index == 7) return MAGNITUDE_CO2; + if (index == 8) return MAGNITUDE_VOC; + + return MAGNITUDE_NONE; + } + + void _loadState() { + String storedState = getSetting("bsecState"); + + if (storedState.length() == 0) { + #if SENSOR_DEBUG + DEBUG_MSG("[BME680] Previous state not found\n"); + #endif + + return; + } + + hexDecode(storedState.c_str(), storedState.length(), _bsecState, sizeof(_bsecState)); + + iaqSensor.setState(_bsecState); + + #if SENSOR_DEBUG + DEBUG_MSG("[BME680] Loaded previous state %s\n", storedState.c_str()); + #endif + } + + void _saveState() { + if (BME680_STATE_SAVE_INTERVAL == 0) { + return; + } + + static unsigned long last_millis = 0; + + if (_iaqAccuracy < 3 || (millis() - last_millis < BME680_STATE_SAVE_INTERVAL)) { + return; + } + + iaqSensor.getState(_bsecState); + + char storedState[BSEC_MAX_STATE_BLOB_SIZE * 2 + 1] = {0}; + hexEncode(_bsecState, BSEC_MAX_STATE_BLOB_SIZE, storedState, sizeof(storedState)); + + setSetting("bsecState", storedState); + + last_millis = millis(); + } + + // The maximum allowed time between two `bsec_sensor_control` calls depends on + // configuration profile `bsec_config_iaq` below. + void tick() { + _error = SENSOR_ERROR_OK; + + if (!_isSensorOk()) { + _error = SENSOR_ERROR_OTHER; + return; + } + + if (iaqSensor.run()) { + _rawTemperature = iaqSensor.rawTemperature; + _rawHumidity = iaqSensor.rawHumidity; + _temperature = iaqSensor.temperature; + _humidity = iaqSensor.humidity; + _pressure = iaqSensor.pressure / 100; + _gasResistance = iaqSensor.gasResistance; + _iaqAccuracy = iaqSensor.iaqAccuracy; + _iaq = iaqSensor.iaq; + _iaqStatic = iaqSensor.staticIaq; + _co2Equivalent = iaqSensor.co2Equivalent; + _breathVocEquivalent = iaqSensor.breathVocEquivalent; + + _saveState(); + } + } + + // Current value for slot # index + double value(unsigned char index) { + if (index == 0) return _temperature; + if (index == 1) return _humidity; + if (index == 2) return _pressure; + if (index == 3) return _gasResistance; + if (index == 4) return _iaqAccuracy; + if (index == 5) return _iaq; + if (index == 6) return _iaqStatic; + if (index == 7) return _co2Equivalent; + if (index == 8) return _breathVocEquivalent; + + return 0; + } + + protected: + + bool _isSensorOk() { + if (iaqSensor.status == BSEC_OK && iaqSensor.bme680Status == BME680_OK) { + return true; + } + + #if SENSOR_DEBUG + if (iaqSensor.status != BSEC_OK) { + if (iaqSensor.status < BSEC_OK) { + DEBUG_MSG("[BME680] BSEC error code %d\n", iaqSensor.status); + } else { + DEBUG_MSG("[BME680] BSEC warning code %d\n", iaqSensor.status); + } + } + #endif + + #if SENSOR_DEBUG + if (iaqSensor.bme680Status != BME680_OK) { + if (iaqSensor.bme680Status < BME680_OK) { + DEBUG_MSG("[BME680] Error code %d\n", iaqSensor.bme680Status); + } else { + DEBUG_MSG("[BME680] Warning code %d\n", iaqSensor.bme680Status); + } + } + #endif + + return false; + } + + bsec_virtual_sensor_t sensorList[12] = { + BSEC_OUTPUT_RAW_TEMPERATURE, // Unscaled (raw) temperature (ºC). + + BSEC_OUTPUT_RAW_PRESSURE, // Unscaled (raw) pressure (Pa). + + BSEC_OUTPUT_RAW_HUMIDITY, // Unscaled (raw) relative humidity (%). + + BSEC_OUTPUT_RAW_GAS, // Gas resistance (Ohm). The resistance value changes according to the + // VOC concentration (the higher the concentration of reducing VOCs, + // the lower the resistance and vice versa). + + BSEC_OUTPUT_IAQ, // Scaled Indoor Air Quality based on the recent sensor history, ideal + // for mobile applications (e.g. carry-on devices). The scale ranges from + // 0 (clean air) to 500 (heavily polluted air). The automatic background + // calibration process ensures that consistent IAQ performance is achieved + // after certain of days (depending on BSEC configuration - 4d or 28d). + + BSEC_OUTPUT_STATIC_IAQ, // Unscaled Indoor Air Quality, optimized for stationary applications + // (e.g. fixed indoor devices). + BSEC_OUTPUT_CO2_EQUIVALENT, // Estimate of CO2 measured in the air. + BSEC_OUTPUT_BREATH_VOC_EQUIVALENT, // Breath VOC represents the most important compounds in an exhaled + // breath of healthy humans. + + BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE, // Temperature compensated for the influence of sensor heater (ºC). + + BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY, // Relative humidity compensated for the influence of sensor heater (%). + + BSEC_OUTPUT_STABILIZATION_STATUS, // Indicates initial stabilization status of the gas sensor element: + // ongoing (0) or finished (1). + + BSEC_OUTPUT_RUN_IN_STATUS, // Indicates power-on stabilization status of the gas sensor element: + // ongoing (0) or finished (1). + }; + + float _breathVocEquivalent = 0.0f; + float _co2Equivalent = 0.0f; + float _gasResistance = 0.0f; + float _humidity = 0.0f; + float _iaq = 0.0f; + float _pressure = 0.0f; + float _rawHumidity = 0.0f; + float _rawTemperature = 0.0f; + float _temperature = 0.0f; + uint8_t _bsecState[BSEC_MAX_STATE_BLOB_SIZE] = {0}; + uint8_t _iaqAccuracy = 0; + float _iaqStatic = 0; + + Bsec iaqSensor; + +}; + +#endif // SENSOR_SUPPORT && BME680_SUPPORT diff --git a/code/espurna/sensors/BaseSensor.h b/code/espurna/sensors/BaseSensor.h index daceebbef3..982cb4d383 100644 --- a/code/espurna/sensors/BaseSensor.h +++ b/code/espurna/sensors/BaseSensor.h @@ -117,9 +117,10 @@ class BaseSensor { case MAGNITUDE_PM1dot0: case MAGNITUDE_PM2dot5: return sensor::Unit::MicrogrammPerCubicMeter; + case MAGNITUDE_CO: case MAGNITUDE_CO2: case MAGNITUDE_NO2: - case MAGNITUDE_CO: + case MAGNITUDE_VOC: return sensor::Unit::PartsPerMillion; case MAGNITUDE_LUX: return sensor::Unit::Lux; diff --git a/code/platformio.ini b/code/platformio.ini index 2bede276fb..02d67718a4 100644 --- a/code/platformio.ini +++ b/code/platformio.ini @@ -155,6 +155,7 @@ lib_deps = https://github.com/mcleng/MAX6675-Library#2.0.1 https://github.com/ThingPulse/esp8266-oled-ssd1306#3398c97 Adafruit SI1145 Library@~1.1.1 + https://github.com/BoschSensortec/BSEC-Arduino-library.git#c5503e0 # ------------------------------------------------------------------------------ # COMMON ENVIRONMENT SETTINGS: @@ -252,7 +253,7 @@ board_build.ldscript = ${common.ldscript_1m} platform = ${common.platform_latest} board = ${common.board_2m} platform_packages = - ${common.git_platform_packages} + ${common.git_platform_packages} board_build.ldscript = ${common.ldscript_2m} [env:esp8266-4m-git-base] diff --git a/code/scripts/espurna_utils/__init__.py b/code/scripts/espurna_utils/__init__.py index e4cb7d8d0d..37fe11c35c 100644 --- a/code/scripts/espurna_utils/__init__.py +++ b/code/scripts/espurna_utils/__init__.py @@ -22,6 +22,7 @@ from .checks import check_cppcheck, check_printsize from .float_support import remove_float_support from .ldscripts import ldscripts_inject_libpath +from .libalgobsec import libalgobsec_inject_patcher from .lwip import lwip_inject_patcher from .postmortem import dummy_ets_printf from .git import app_inject_revision @@ -33,6 +34,7 @@ "check_printsize", "remove_float_support", "ldscripts_inject_libpath", + "libalgobsec_inject_patcher", "lwip_inject_patcher", "dummy_ets_printf", "app_inject_revision", diff --git a/code/scripts/espurna_utils/ldscripts.py b/code/scripts/espurna_utils/ldscripts.py index 0b2f5176e9..35433e1370 100644 --- a/code/scripts/espurna_utils/ldscripts.py +++ b/code/scripts/espurna_utils/ldscripts.py @@ -1,6 +1,5 @@ import os - def ldscripts_inject_libpath(env): platform = env.PioPlatform() diff --git a/code/scripts/espurna_utils/libalgobsec.py b/code/scripts/espurna_utils/libalgobsec.py new file mode 100644 index 0000000000..9b1cbf25d7 --- /dev/null +++ b/code/scripts/espurna_utils/libalgobsec.py @@ -0,0 +1,69 @@ +import os +import sys + + +def libalgobsec_inject_patcher(env): + libalgobsec_builder = next( + ( + builder + for builder in env.GetLibBuilders() + if builder.name == "BSEC Software Library" + ), + None, + ) + + if libalgobsec_builder is None: + return + + def process_archive(target, source, env): + import subprocess + + # Allows `import espurna_utils` for external scripts, where we might not be within scons runtime + from SCons.Script import Delete, Mkdir + + tmpdir = env.get( + "ESPURNA_LIBALGOBSEC_PATCHER_TMPDIR", + os.path.join(str(target[0].get_dir()), "_tmpdir"), + ) + + env.Execute(Mkdir(tmpdir)) + + # XXX: $AR does not support output argument for the extraction + # always switch into tmpdir when running commands + def run(cmd): + sys.stdout.write(" ".join(cmd)) + sys.stdout.write("\n") + subprocess.check_call(cmd, cwd=tmpdir) + + run([env.subst("$AR"), "x", source[0].abspath]) + + names = [] + for infilename in os.listdir(tmpdir): + newname = infilename + if not infilename.endswith(".c.o"): + newname = infilename.replace(".o", ".c.o") + os.rename(os.path.join(tmpdir, infilename), os.path.join(tmpdir, newname)) + names.append(newname) + + pack_cmd = [env.subst("$AR"), "cr", target[0].abspath] + pack_cmd.extend(names) + run(pack_cmd) + + env.Execute(Delete(tmpdir)) + + # Instead of replacing the file in-place, link with the patched version + libalgobsec_dir = os.path.join(libalgobsec_builder.src_dir, "esp8266") + + target = env.File( + "libalgobsec.a", directory=env.subst("$BUILD_DIR/libalgobsec_patched") + ) + source = env.File("libalgobsec.a", directory=libalgobsec_dir) + + command = env.Command(target, source, process_archive) + patcher = env.Alias("patch-libalgobsec", command) + + env.Append(LIBPATH=[target.get_dir()]) + env.Append(LIBS=["algobsec"]) + + env.Depends("$BUILD_DIR/${PROGNAME}.elf", patcher) + diff --git a/code/scripts/pio_main.py b/code/scripts/pio_main.py index 2113e75c10..8218e61f2e 100644 --- a/code/scripts/pio_main.py +++ b/code/scripts/pio_main.py @@ -13,6 +13,7 @@ check_printsize, remove_float_support, ldscripts_inject_libpath, + libalgobsec_inject_patcher, lwip_inject_patcher, app_inject_revision, dummy_ets_printf, @@ -48,6 +49,9 @@ "$BUILD_DIR/FrameworkArduino/core_esp8266_postmortem.cpp.o", dummy_ets_printf ) +# place bsec's libalgobsec.a sections in the flash to avoid "section ‘.text' will not fit in region 'iram1_0_seg'" error +libalgobsec_inject_patcher(env) + # patch lwip1 sources conditionally: # https://github.com/xoseperez/espurna/issues/1610 lwip_inject_patcher(env) diff --git a/code/test/build/sensor.h b/code/test/build/sensor.h index 59ca771fd4..231bac9c7a 100644 --- a/code/test/build/sensor.h +++ b/code/test/build/sensor.h @@ -3,6 +3,7 @@ #define BH1750_SUPPORT 1 #define BMP180_SUPPORT 1 #define BMX280_SUPPORT 1 +#define BME680_SUPPORT 1 #define DALLAS_SUPPORT 1 #define DHT_SUPPORT 1 #define DIGITAL_SUPPORT 1