From 1f322a8221e79ddc42e00a2efaef7a38a5924519 Mon Sep 17 00:00:00 2001 From: MG-95 Date: Sun, 31 Oct 2021 12:08:02 +0100 Subject: [PATCH] Add BSEC, show IAQ on display --- Makefile | 3 + src/BME68x-Sensor-API | 2 +- src/BSEC/bsec_datatypes.h | 489 +++++++++++++++++++++++++++++++++ src/BSEC/bsec_interface.h | 564 ++++++++++++++++++++++++++++++++++++++ src/BSEC/libalgobsec.a | Bin 0 -> 89672 bytes src/bmeSPI.cxx | 321 ++++++++++++++++------ src/main.cxx | 15 +- 7 files changed, 1289 insertions(+), 105 deletions(-) create mode 100644 src/BSEC/bsec_datatypes.h create mode 100644 src/BSEC/bsec_interface.h create mode 100644 src/BSEC/libalgobsec.a diff --git a/Makefile b/Makefile index a87a7df..65acd3e 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,10 @@ DEVICE := stm32l152rc DEFS += BME68X_DO_NOT_USE_FPU DEFS += FW_USE_RTOS +LDLIBS := src/BSEC/libalgobsec.a + INCDIRS := \ +src/BSEC \ src/BME68x-Sensor-API \ src/oled-driver/ \ src/oled-driver/include \ diff --git a/src/BME68x-Sensor-API b/src/BME68x-Sensor-API index e104fe5..9f9b030 160000 --- a/src/BME68x-Sensor-API +++ b/src/BME68x-Sensor-API @@ -1 +1 @@ -Subproject commit e104fe56e58dfdf4d827f1344587dba7cf45bb01 +Subproject commit 9f9b030c7236aa308252921973ec2abeb679c1bd diff --git a/src/BSEC/bsec_datatypes.h b/src/BSEC/bsec_datatypes.h new file mode 100644 index 0000000..535ed5d --- /dev/null +++ b/src/BSEC/bsec_datatypes.h @@ -0,0 +1,489 @@ +/* + * Copyright (C) 2015, 2016, 2017 Robert Bosch. All Rights Reserved. + * + * Disclaimer + * + * Common: + * Bosch Sensortec products are developed for the consumer goods industry. They may only be used + * within the parameters of the respective valid product data sheet. Bosch Sensortec products are + * provided with the express understanding that there is no warranty of fitness for a particular purpose. + * They are not fit for use in life-sustaining, safety or security sensitive systems or any system or device + * that may lead to bodily harm or property damage if the system or device malfunctions. In addition, + * Bosch Sensortec products are not fit for use in products which interact with motor vehicle systems. + * The resale and/or use of products are at the purchasers own risk and his own responsibility. The + * examination of fitness for the intended use is the sole responsibility of the Purchaser. + * + * The purchaser shall indemnify Bosch Sensortec from all third party claims, including any claims for + * incidental, or consequential damages, arising from any product use not covered by the parameters of + * the respective valid product data sheet or not approved by Bosch Sensortec and reimburse Bosch + * Sensortec for all costs in connection with such claims. + * + * The purchaser must monitor the market for the purchased products, particularly with regard to + * product safety and inform Bosch Sensortec without delay of all security relevant incidents. + * + * Engineering Samples are marked with an asterisk (*) or (e). Samples may vary from the valid + * technical specifications of the product series. They are therefore not intended or fit for resale to third + * parties or for use in end products. Their sole purpose is internal client testing. The testing of an + * engineering sample may in no way replace the testing of a product series. Bosch Sensortec + * assumes no liability for the use of engineering samples. By accepting the engineering samples, the + * Purchaser agrees to indemnify Bosch Sensortec from all claims arising from the use of engineering + * samples. + * + * Special: + * This software module (hereinafter called "Software") and any information on application-sheets + * (hereinafter called "Information") is provided free of charge for the sole purpose to support your + * application work. The Software and Information is subject to the following terms and conditions: + * + * The Software is specifically designed for the exclusive use for Bosch Sensortec products by + * personnel who have special experience and training. Do not use this Software if you do not have the + * proper experience or training. + * + * This Software package is provided `` as is `` and without any expressed or implied warranties, + * including without limitation, the implied warranties of merchantability and fitness for a particular + * purpose. + * + * Bosch Sensortec and their representatives and agents deny any liability for the functional impairment + * of this Software in terms of fitness, performance and safety. Bosch Sensortec and their + * representatives and agents shall not be liable for any direct or indirect damages or injury, except as + * otherwise stipulated in mandatory applicable law. + * + * The Information provided is believed to be accurate and reliable. Bosch Sensortec assumes no + * responsibility for the consequences of use of such Information nor for any infringement of patents or + * other rights of third parties which may result from its use. No license is granted by implication or + * otherwise under any patent or patent rights of Bosch. Specifications mentioned in the Information are + * subject to change without notice. + * + * It is not allowed to deliver the source code of the Software to any third party without permission of + * Bosch Sensortec. + * + */ + + /** + * @file bsec_datatypes.h + * + * @brief + * Contains the data types used by BSEC + * + */ + +#ifndef __BSEC_DATATYPES_H__ +#define __BSEC_DATATYPES_H__ + +#ifdef __cplusplus +extern "C" +{ +#endif + +/*! + * @addtogroup bsec_interface BSEC C Interface + * @{*/ + +#ifdef __KERNEL__ +#include +#endif +#include +#include + +#define BSEC_MAX_WORKBUFFER_SIZE (2048) /*!< Maximum size (in bytes) of the work buffer */ +#define BSEC_MAX_PHYSICAL_SENSOR (8) /*!< Number of physical sensors that need allocated space before calling bsec_update_subscription() */ +#define BSEC_MAX_PROPERTY_BLOB_SIZE (454) /*!< Maximum size (in bytes) of the data blobs returned by bsec_get_configuration() */ +#define BSEC_MAX_STATE_BLOB_SIZE (139) /*!< Maximum size (in bytes) of the data blobs returned by bsec_get_state()*/ +#define BSEC_SAMPLE_RATE_DISABLED (65535.0f) /*!< Sample rate of a disabled sensor */ +#define BSEC_SAMPLE_RATE_ULP (0.0033333f) /*!< Sample rate in case of Ultra Low Power Mode */ +#define BSEC_SAMPLE_RATE_CONTINUOUS (1.0f) /*!< Sample rate in case of Continuous Mode */ +#define BSEC_SAMPLE_RATE_LP (0.33333f) /*!< Sample rate in case of Low Power Mode */ +#define BSEC_SAMPLE_RATE_ULP_MEASUREMENT_ON_DEMAND (0.0f) /*!< Input value used to trigger an extra measurment (ULP plus) */ + +#define BSEC_PROCESS_PRESSURE (1 << (BSEC_INPUT_PRESSURE-1)) /*!< process_data bitfield constant for pressure @sa bsec_bme_settings_t */ +#define BSEC_PROCESS_TEMPERATURE (1 << (BSEC_INPUT_TEMPERATURE-1)) /*!< process_data bitfield constant for temperature @sa bsec_bme_settings_t */ +#define BSEC_PROCESS_HUMIDITY (1 << (BSEC_INPUT_HUMIDITY-1)) /*!< process_data bitfield constant for humidity @sa bsec_bme_settings_t */ +#define BSEC_PROCESS_GAS (1 << (BSEC_INPUT_GASRESISTOR-1)) /*!< process_data bitfield constant for gas sensor @sa bsec_bme_settings_t */ +#define BSEC_NUMBER_OUTPUTS (14) /*!< Number of outputs, depending on solution */ +#define BSEC_OUTPUT_INCLUDED (1210863) /*!< bitfield that indicates which outputs are included in the solution */ + +/*! + * @brief Enumeration for input (physical) sensors. + * + * Used to populate bsec_input_t::sensor_id. It is also used in bsec_sensor_configuration_t::sensor_id structs + * returned in the parameter required_sensor_settings of bsec_update_subscription(). + * + * @sa bsec_sensor_configuration_t @sa bsec_input_t + */ +typedef enum +{ + /** + * @brief Pressure sensor output of BMExxx [Pa] + */ + BSEC_INPUT_PRESSURE = 1, + + /** + * @brief Humidity sensor output of BMExxx [%] + * + * @note Relative humidity strongly depends on the temperature (it is measured at). It may require a conversion to + * the temperature outside of the device. + * + * @sa bsec_virtual_sensor_t + */ + BSEC_INPUT_HUMIDITY = 2, + + /** + * @brief Temperature sensor output of BMExxx [degrees Celsius] + * + * @note The BME680 is factory trimmed, thus the temperature sensor of the BME680 is very accurate. + * The temperature value is a very local measurement value and can be influenced by external heat sources. + * + * @sa bsec_virtual_sensor_t + */ + BSEC_INPUT_TEMPERATURE = 3, + + /** + * @brief Gas sensor resistance output of BMExxx [Ohm] + * + * The resistance value changes due to varying VOC concentrations (the higher the concentration of reducing VOCs, + * the lower the resistance and vice versa). + */ + BSEC_INPUT_GASRESISTOR = 4, /*!< */ + + /** + * @brief Additional input for device heat compensation + * + * IAQ solution: The value is subtracted from ::BSEC_INPUT_TEMPERATURE to compute + * ::BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE. + * + * ALL solution: Generic heat source 1 + * + * @sa bsec_virtual_sensor_t + */ + BSEC_INPUT_HEATSOURCE = 14, + + /** + * @brief Additional input for device heat compensation 8 + * + * Generic heat source 8 + */ + + + /** + * @brief Additional input that disables baseline tracker + * + * 0 - Normal + * 1 - Event 1 + * 2 - Event 2 + */ + BSEC_INPUT_DISABLE_BASELINE_TRACKER = 23, + +} bsec_physical_sensor_t; + +/*! + * @brief Enumeration for output (virtual) sensors + * + * Used to populate bsec_output_t::sensor_id. It is also used in bsec_sensor_configuration_t::sensor_id structs + * passed in the parameter requested_virtual_sensors of bsec_update_subscription(). + * + * @sa bsec_sensor_configuration_t @sa bsec_output_t + */ +typedef enum +{ + /** + * @brief Indoor-air-quality estimate [0-500] + * + * Indoor-air-quality (IAQ) gives an indication of the relative change in ambient TVOCs detected by BME680. + * + * @note The IAQ scale ranges from 0 (clean air) to 500 (heavily polluted air). During operation, algorithms + * automatically calibrate and adapt themselves to the typical environments where the sensor is operated + * (e.g., home, workplace, inside a car, etc.).This automatic background calibration ensures that users experience + * consistent IAQ performance. The calibration process considers the recent measurement history (typ. up to four + * days) to ensure that IAQ=25 corresponds to typical good air and IAQ=250 indicates typical polluted air. + */ + BSEC_OUTPUT_IAQ = 1, + BSEC_OUTPUT_STATIC_IAQ = 2, /*!< Unscaled indoor-air-quality estimate */ + BSEC_OUTPUT_CO2_EQUIVALENT = 3, /*!< co2 equivalent estimate [ppm] */ + BSEC_OUTPUT_BREATH_VOC_EQUIVALENT = 4, /*!< breath VOC concentration estimate [ppm] */ + + /** + * @brief Temperature sensor signal [degrees Celsius] + * + * Temperature directly measured by BME680 in degree Celsius. + * + * @note This value is cross-influenced by the sensor heating and device specific heating. + */ + BSEC_OUTPUT_RAW_TEMPERATURE = 6, + + /** + * @brief Pressure sensor signal [Pa] + * + * Pressure directly measured by the BME680 in Pa. + */ + BSEC_OUTPUT_RAW_PRESSURE = 7, + + /** + * @brief Relative humidity sensor signal [%] + * + * Relative humidity directly measured by the BME680 in %. + * + * @note This value is cross-influenced by the sensor heating and device specific heating. + */ + BSEC_OUTPUT_RAW_HUMIDITY = 8, + + /** + * @brief Gas sensor signal [Ohm] + * + * Gas resistance measured directly by the BME680 in Ohm.The resistance value changes due to varying VOC + * concentrations (the higher the concentration of reducing VOCs, the lower the resistance and vice versa). + */ + BSEC_OUTPUT_RAW_GAS = 9, + + /** + * @brief Gas sensor stabilization status [boolean] + * + * Indicates initial stabilization status of the gas sensor element: stabilization is ongoing (0) or stabilization + * is finished (1). + */ + BSEC_OUTPUT_STABILIZATION_STATUS = 12, + + /** + * @brief Gas sensor run-in status [boolean] + * + * Indicates power-on stabilization status of the gas sensor element: stabilization is ongoing (0) or stabilization + * is finished (1). + */ + BSEC_OUTPUT_RUN_IN_STATUS = 13, + + /** + * @brief Sensor heat compensated temperature [degrees Celsius] + * + * Temperature measured by BME680 which is compensated for the influence of sensor (heater) in degree Celsius. + * The self heating introduced by the heater is depending on the sensor operation mode and the sensor supply voltage. + * + * + * @note IAQ solution: In addition, the temperature output can be compensated by an user defined value + * (::BSEC_INPUT_HEATSOURCE in degrees Celsius), which represents the device specific self-heating. + * + * Thus, the value is calculated as follows: + * * IAQ solution: ```BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE = ::BSEC_INPUT_TEMPERATURE - function(sensor operation mode, sensor supply voltage) - ::BSEC_INPUT_HEATSOURCE``` + * * other solutions: ```::BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE = ::BSEC_INPUT_TEMPERATURE - function(sensor operation mode, sensor supply voltage)``` + * + * The self-heating in operation mode BSEC_SAMPLE_RATE_ULP is negligible. + * The self-heating in operation mode BSEC_SAMPLE_RATE_LP is supported for 1.8V by default (no config file required). If the BME680 sensor supply voltage is 3.3V, the IoT_LP_3_3V.config shall be used. + */ + BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE = 14, + + /** + * @brief Sensor heat compensated humidity [%] + * + * Relative measured by BME680 which is compensated for the influence of sensor (heater) in %. + * + * It converts the ::BSEC_INPUT_HUMIDITY from temperature ::BSEC_INPUT_TEMPERATURE to temperature + * ::BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE. + * + * @note IAQ solution: If ::BSEC_INPUT_HEATSOURCE is used for device specific temperature compensation, it will be + * effective for ::BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY too. + */ + BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY = 15, + + BSEC_OUTPUT_COMPENSATED_GAS = 18, /*!< Reserved internal debug output */ + BSEC_OUTPUT_GAS_PERCENTAGE = 21 /*!< percentage of min and max filtered gas value [%] */ +} bsec_virtual_sensor_t; + +/*! + * @brief Enumeration for function return codes + */ +typedef enum +{ + BSEC_OK = 0, /*!< Function execution successful */ + BSEC_E_DOSTEPS_INVALIDINPUT = -1, /*!< Input (physical) sensor id passed to bsec_do_steps() is not in the valid range or not valid for requested virtual sensor */ + BSEC_E_DOSTEPS_VALUELIMITS = -2, /*!< Value of input (physical) sensor signal passed to bsec_do_steps() is not in the valid range */ + BSEC_E_DOSTEPS_DUPLICATEINPUT = -6, /*!< Duplicate input (physical) sensor ids passed as input to bsec_do_steps() */ + BSEC_I_DOSTEPS_NOOUTPUTSRETURNABLE = 2, /*!< No memory allocated to hold return values from bsec_do_steps(), i.e., n_outputs == 0 */ + BSEC_W_DOSTEPS_EXCESSOUTPUTS = 3, /*!< Not enough memory allocated to hold return values from bsec_do_steps(), i.e., n_outputs < maximum number of requested output (virtual) sensors */ + BSEC_W_DOSTEPS_TSINTRADIFFOUTOFRANGE = 4, /*!< Duplicate timestamps passed to bsec_do_steps() */ + BSEC_E_SU_WRONGDATARATE = -10, /*!< The sample_rate of the requested output (virtual) sensor passed to bsec_update_subscription() is zero */ + BSEC_E_SU_SAMPLERATELIMITS = -12, /*!< The sample_rate of the requested output (virtual) sensor passed to bsec_update_subscription() does not match with the sampling rate allowed for that sensor */ + BSEC_E_SU_DUPLICATEGATE = -13, /*!< Duplicate output (virtual) sensor ids requested through bsec_update_subscription() */ + BSEC_E_SU_INVALIDSAMPLERATE = -14, /*!< The sample_rate of the requested output (virtual) sensor passed to bsec_update_subscription() does not fall within the global minimum and maximum sampling rates */ + BSEC_E_SU_GATECOUNTEXCEEDSARRAY = -15, /*!< Not enough memory allocated to hold returned input (physical) sensor data from bsec_update_subscription(), i.e., n_required_sensor_settings < #BSEC_MAX_PHYSICAL_SENSOR */ + BSEC_E_SU_SAMPLINTVLINTEGERMULT = -16, /*!< The sample_rate of the requested output (virtual) sensor passed to bsec_update_subscription() is not correct */ + BSEC_E_SU_MULTGASSAMPLINTVL = -17, /*!< The sample_rate of the requested output (virtual), which requires the gas sensor, is not equal to the sample_rate that the gas sensor is being operated */ + BSEC_E_SU_HIGHHEATERONDURATION = -18, /*!< The duration of one measurement is longer than the requested sampling interval */ + BSEC_W_SU_UNKNOWNOUTPUTGATE = 10, /*!< Output (virtual) sensor id passed to bsec_update_subscription() is not in the valid range; e.g., n_requested_virtual_sensors > actual number of output (virtual) sensors requested */ + BSEC_W_SU_MODINNOULP = 11, /*!< ULP plus can not be requested in non-ulp mode */ /*MOD_ONLY*/ + BSEC_I_SU_SUBSCRIBEDOUTPUTGATES = 12, /*!< No output (virtual) sensor data were requested via bsec_update_subscription() */ + BSEC_E_PARSE_SECTIONEXCEEDSWORKBUFFER = -32, /*!< n_work_buffer_size passed to bsec_set_[configuration/state]() not sufficient */ + BSEC_E_CONFIG_FAIL = -33, /*!< Configuration failed */ + BSEC_E_CONFIG_VERSIONMISMATCH = -34, /*!< Version encoded in serialized_[settings/state] passed to bsec_set_[configuration/state]() does not match with current version */ + BSEC_E_CONFIG_FEATUREMISMATCH = -35, /*!< Enabled features encoded in serialized_[settings/state] passed to bsec_set_[configuration/state]() does not match with current library implementation */ + BSEC_E_CONFIG_CRCMISMATCH = -36, /*!< serialized_[settings/state] passed to bsec_set_[configuration/state]() is corrupted */ + BSEC_E_CONFIG_EMPTY = -37, /*!< n_serialized_[settings/state] passed to bsec_set_[configuration/state]() is to short to be valid */ + BSEC_E_CONFIG_INSUFFICIENTWORKBUFFER = -38, /*!< Provided work_buffer is not large enough to hold the desired string */ + BSEC_E_CONFIG_INVALIDSTRINGSIZE = -40, /*!< String size encoded in configuration/state strings passed to bsec_set_[configuration/state]() does not match with the actual string size n_serialized_[settings/state] passed to these functions */ + BSEC_E_CONFIG_INSUFFICIENTBUFFER = -41, /*!< String buffer insufficient to hold serialized data from BSEC library */ + BSEC_E_SET_INVALIDCHANNELIDENTIFIER = -100, /*!< Internal error code, size of work buffer in setConfig must be set to BSEC_MAX_WORKBUFFER_SIZE */ + BSEC_E_SET_INVALIDLENGTH = -104, /*!< Internal error code */ + BSEC_W_SC_CALL_TIMING_VIOLATION = 100, /*!< Difference between actual and defined sampling intervals of bsec_sensor_control() greater than allowed */ + BSEC_W_SC_MODEXCEEDULPTIMELIMIT = 101, /*!< ULP plus is not allowed because an ULP measurement just took or will take place */ /*MOD_ONLY*/ + BSEC_W_SC_MODINSUFFICIENTWAITTIME = 102 /*!< ULP plus is not allowed because not sufficient time passed since last ULP plus */ /*MOD_ONLY*/ +} bsec_library_return_t; + +/*! + * @brief Structure containing the version information + * + * Please note that configuration and state strings are coded to a specific version and will not be accepted by other + * versions of BSEC. + * + */ +typedef struct +{ + uint8_t major; /**< @brief Major version */ + uint8_t minor; /**< @brief Minor version */ + uint8_t major_bugfix; /**< @brief Major bug fix version */ + uint8_t minor_bugfix; /**< @brief Minor bug fix version */ +} bsec_version_t; + +/*! + * @brief Structure describing an input sample to the library + * + * Each input sample is provided to BSEC as an element in a struct array of this type. Timestamps must be provided + * in nanosecond resolution. Moreover, duplicate timestamps for subsequent samples are not allowed and will results in + * an error code being returned from bsec_do_steps(). + * + * The meaning unit of the signal field are determined by the bsec_input_t::sensor_id field content. Possible + * bsec_input_t::sensor_id values and and their meaning are described in ::bsec_physical_sensor_t. + * + * @sa bsec_physical_sensor_t + * + */ +typedef struct +{ + /** + * @brief Time stamp in nanosecond resolution [ns] + * + * Timestamps must be provided as non-repeating and increasing values. They can have their 0-points at system start or + * at a defined wall-clock time (e.g., 01-Jan-1970 00:00:00) + */ + int64_t time_stamp; + float signal; /*!< @brief Signal sample in the unit defined for the respective sensor_id @sa bsec_physical_sensor_t */ + uint8_t signal_dimensions; /*!< @brief Signal dimensions (reserved for future use, shall be set to 1) */ + uint8_t sensor_id; /*!< @brief Identifier of physical sensor @sa bsec_physical_sensor_t */ +} bsec_input_t; + +/*! + * @brief Structure describing an output sample of the library + * + * Each output sample is returned from BSEC by populating the element of a struct array of this type. The contents of + * the signal field is defined by the supplied bsec_output_t::sensor_id. Possible output + * bsec_output_t::sensor_id values are defined in ::bsec_virtual_sensor_t. + * + * @sa bsec_virtual_sensor_t + */ +typedef struct +{ + int64_t time_stamp; /*!< @brief Time stamp in nanosecond resolution as provided as input [ns] */ + float signal; /*!< @brief Signal sample in the unit defined for the respective bsec_output_t::sensor_id @sa bsec_virtual_sensor_t */ + uint8_t signal_dimensions; /*!< @brief Signal dimensions (reserved for future use, shall be set to 1) */ + uint8_t sensor_id; /*!< @brief Identifier of virtual sensor @sa bsec_virtual_sensor_t */ + + /** + * @brief Accuracy status 0-3 + * + * Some virtual sensors provide a value in the accuracy field. If this is the case, the meaning of the field is as + * follows: + * + * | Name | Value | Accuracy description | + * |----------------------------|-------|-------------------------------------------------------------------------------------------------------------| + * | UNRELIABLE | 0 | Sensor data is unreliable, the sensor must be calibrated | + * | LOW_ACCURACY | 1 | Low accuracy, sensor should be calibrated | + * | MEDIUM_ACCURACY | 2 | Medium accuracy, sensor calibration may improve performance | + * | HIGH_ACCURACY | 3 | High accuracy | + * + * For example: + * + * - Ambient temperature accuracy is derived from change in the temperature in 1 minute. + * + * | Virtual sensor | Value | Accuracy description | + * |--------------------- |-------|------------------------------------------------------------------------------| + * | Ambient temperature | 0 | The difference in ambient temperature is greater than 4 degree in one minute | + * | | 1 | The difference in ambient temperature is less than 4 degree in one minute | + * | | 2 | The difference in ambient temperature is less than 3 degree in one minute | + * | | 3 | The difference in ambient temperature is less than 2 degree in one minute | + * + * - IAQ accuracy indicator will notify the user when she/he should initiate a calibration process. Calibration is + * performed automatically in the background if the sensor is exposed to clean and polluted air for approximately + * 30 minutes each. + * + * | Virtual sensor | Value | Accuracy description | + * |----------------------------|-------|---------------------------------------------------------------------------------------------------------------------------------------------------------------| + * | IAQ | 0 | Stabilization / run-in ongoing | + * | | 1 | Low accuracy,to reach high accuracy(3),please expose sensor once to good air (e.g. outdoor air) and bad air (e.g. box with exhaled breath) for auto-trimming | + * | | 2 | Medium accuracy: auto-trimming ongoing | + * | | 3 | High accuracy | + */ + uint8_t accuracy; +} bsec_output_t; + +/*! + * @brief Structure describing sample rate of physical/virtual sensors + * + * This structure is used together with bsec_update_subscription() to enable BSEC outputs and to retrieve information + * about the sample rates used for BSEC inputs. + */ +typedef struct +{ + /** + * @brief Sample rate of the virtual or physical sensor in Hertz [Hz] + * + * Only supported sample rates are allowed. + */ + float sample_rate; + + /** + * @brief Identifier of the virtual or physical sensor + * + * The meaning of this field changes depending on whether the structs are as the requested_virtual_sensors argument + * to bsec_update_subscription() or as the required_sensor_settings argument. + * + * | bsec_update_subscription() argument | sensor_id field interpretation | + * |-------------------------------------|--------------------------------| + * | requested_virtual_sensors | ::bsec_virtual_sensor_t | + * | required_sensor_settings | ::bsec_physical_sensor_t | + * + * @sa bsec_physical_sensor_t + * @sa bsec_virtual_sensor_t + */ + uint8_t sensor_id; +} bsec_sensor_configuration_t; + +/*! + * @brief Structure returned by bsec_sensor_control() to configure BMExxx sensor + * + * This structure contains settings that must be used to configure the BMExxx to perform a forced-mode measurement. + * A measurement should only be executed if bsec_bme_settings_t::trigger_measurement is 1. If so, the oversampling + * settings for temperature, humidity, and pressure should be set to the provided settings provided in + * bsec_bme_settings_t::temperature_oversampling, bsec_bme_settings_t::humidity_oversampling, and + * bsec_bme_settings_t::pressure_oversampling, respectively. + * + * In case of bsec_bme_settings_t::run_gas = 1, the gas sensor must be enabled with the provided + * bsec_bme_settings_t::heater_temperature and bsec_bme_settings_t::heating_duration settings. + */ +typedef struct +{ + int64_t next_call; /*!< @brief Time stamp of the next call of the sensor_control*/ + uint32_t process_data; /*!< @brief Bit field describing which data is to be passed to bsec_do_steps() @sa BSEC_PROCESS_* */ + uint16_t heater_temperature; /*!< @brief Heating temperature [degrees Celsius] */ + uint16_t heating_duration; /*!< @brief Heating duration [ms] */ + uint8_t run_gas; /*!< @brief Enable gas measurements [0/1] */ + uint8_t pressure_oversampling; /*!< @brief Pressure oversampling settings [0-5] */ + uint8_t temperature_oversampling; /*!< @brief Temperature oversampling settings [0-5] */ + uint8_t humidity_oversampling; /*!< @brief Humidity oversampling settings [0-5] */ + uint8_t trigger_measurement; /*!< @brief Trigger a forced measurement with these settings now [0/1] */ +} bsec_bme_settings_t; + +/* internal defines and backward compatibility */ +#define BSEC_STRUCT_NAME Bsec /*!< Internal struct name */ + +/*@}*/ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/BSEC/bsec_interface.h b/src/BSEC/bsec_interface.h new file mode 100644 index 0000000..d6c09a3 --- /dev/null +++ b/src/BSEC/bsec_interface.h @@ -0,0 +1,564 @@ +/* + * Copyright (C) 2015, 2016, 2017 Robert Bosch. All Rights Reserved. + * + * Disclaimer + * + * Common: + * Bosch Sensortec products are developed for the consumer goods industry. They may only be used + * within the parameters of the respective valid product data sheet. Bosch Sensortec products are + * provided with the express understanding that there is no warranty of fitness for a particular purpose. + * They are not fit for use in life-sustaining, safety or security sensitive systems or any system or device + * that may lead to bodily harm or property damage if the system or device malfunctions. In addition, + * Bosch Sensortec products are not fit for use in products which interact with motor vehicle systems. + * The resale and/or use of products are at the purchasers own risk and his own responsibility. The + * examination of fitness for the intended use is the sole responsibility of the Purchaser. + * + * The purchaser shall indemnify Bosch Sensortec from all third party claims, including any claims for + * incidental, or consequential damages, arising from any product use not covered by the parameters of + * the respective valid product data sheet or not approved by Bosch Sensortec and reimburse Bosch + * Sensortec for all costs in connection with such claims. + * + * The purchaser must monitor the market for the purchased products, particularly with regard to + * product safety and inform Bosch Sensortec without delay of all security relevant incidents. + * + * Engineering Samples are marked with an asterisk (*) or (e). Samples may vary from the valid + * technical specifications of the product series. They are therefore not intended or fit for resale to third + * parties or for use in end products. Their sole purpose is internal client testing. The testing of an + * engineering sample may in no way replace the testing of a product series. Bosch Sensortec + * assumes no liability for the use of engineering samples. By accepting the engineering samples, the + * Purchaser agrees to indemnify Bosch Sensortec from all claims arising from the use of engineering + * samples. + * + * Special: + * This software module (hereinafter called "Software") and any information on application-sheets + * (hereinafter called "Information") is provided free of charge for the sole purpose to support your + * application work. The Software and Information is subject to the following terms and conditions: + * + * The Software is specifically designed for the exclusive use for Bosch Sensortec products by + * personnel who have special experience and training. Do not use this Software if you do not have the + * proper experience or training. + * + * This Software package is provided `` as is `` and without any expressed or implied warranties, + * including without limitation, the implied warranties of merchantability and fitness for a particular + * purpose. + * + * Bosch Sensortec and their representatives and agents deny any liability for the functional impairment + * of this Software in terms of fitness, performance and safety. Bosch Sensortec and their + * representatives and agents shall not be liable for any direct or indirect damages or injury, except as + * otherwise stipulated in mandatory applicable law. + * + * The Information provided is believed to be accurate and reliable. Bosch Sensortec assumes no + * responsibility for the consequences of use of such Information nor for any infringement of patents or + * other rights of third parties which may result from its use. No license is granted by implication or + * otherwise under any patent or patent rights of Bosch. Specifications mentioned in the Information are + * subject to change without notice. + * + * It is not allowed to deliver the source code of the Software to any third party without permission of + * Bosch Sensortec. + * + */ + /*! + * + * @file bsec_interface.h + * + * @brief + * Contains the API for BSEC + * + */ + + +#ifndef __BSEC_INTERFACE_H__ +#define __BSEC_INTERFACE_H__ + +#include "bsec_datatypes.h" + +#ifdef __cplusplus + extern "C" { +#endif + + + /*! @addtogroup bsec_interface BSEC C Interface + * @brief Interfaces of BSEC signal processing library + * + * ### Interface usage + * + * The following provides a short overview on the typical operation sequence for BSEC. + * + * - Initialization of the library + * + * | Steps | Function | + * |---------------------------------------------------------------------|--------------------------| + * | Initialization of library | bsec_init() | + * | Update configuration settings (optional) | bsec_set_configuration() | + * | Restore the state of the library (optional) | bsec_set_state() | + * + * + * - The following function is called to enable output signals and define their sampling rate / operation mode. + * + * | Steps | Function | + * |---------------------------------------------|----------------------------| + * | Enable library outputs with specified mode | bsec_update_subscription() | + * + * + * - This table describes the main processing loop. + * + * | Steps | Function | + * |-------------------------------------------|----------------------------------| + * | Retrieve sensor settings to be used | bsec_sensor_control() | + * | Configure sensor and trigger measurement | See BME680 API and example codes | + * | Read results from sensor | See BME680 API and example codes | + * | Perform signal processing | bsec_do_steps() | + * + * + * - Before shutting down the system, the current state of BSEC can be retrieved and can then be used during + * re-initialization to continue processing. + * + * | Steps | Function | + * |----------------------------------------|-------------------| + * | To retrieve the current library state | bsec_get_state() | + * + * + * + * ### Configuration and state + * + * Values of variables belonging to a BSEC instance are divided into two groups: + * - Values **not updated by processing** of signals belong to the **configuration group**. If available, BSEC can be + * configured before use with a customer specific configuration string. + * - Values **updated during processing** are member of the **state group**. Saving and restoring of the state of BSEC + * is necessary to maintain previously estimated sensor models and baseline information which is important for best + * performance of the gas sensor outputs. + * + * @note BSEC library consists of adaptive algorithms which models the gas sensor which improves its performance over + * the time. These will be lost if library is initialized due to system reset. In order to avoid this situation + * library state shall be stored in non volatile memory so that it can be loaded after system reset. + * + * + * @{ + */ + + +/*! + * @brief Return the version information of BSEC library + * + * @param [out] bsec_version_p pointer to struct which is to be populated with the version information + * + * @return Zero if successful, otherwise an error code + * + * See also: bsec_version_t + * + \code{.c} + // Example // + bsec_version_t version; + bsec_get_version(&version); + printf("BSEC version: %d.%d.%d.%d",version.major, version.minor, version.major_bugfix, version.minor_bugfix); + + \endcode +*/ + +bsec_library_return_t bsec_get_version(bsec_version_t * bsec_version_p); + + +/*! + * @brief Initialize the library + * + * Initialization and reset of BSEC is performed by calling bsec_init(). Calling this function sets up the relation + * among all internal modules, initializes run-time dependent library states and resets the configuration and state + * of all BSEC signal processing modules to defaults. + * + * Before any further use, the library must be initialized. This ensure that all memory and states are in defined + * conditions prior to processing any data. + * + * @return Zero if successful, otherwise an error code + * + \code{.c} + + // Initialize BSEC library before further use + bsec_init(); + + \endcode +*/ + +bsec_library_return_t bsec_init(void); + +/*! + * @brief Subscribe to library virtual sensors outputs + * + * Use bsec_update_subscription() to instruct BSEC which of the processed output signals are requested at which sample rates. + * See ::bsec_virtual_sensor_t for available library outputs. + * + * Based on the requested virtual sensors outputs, BSEC will provide information about the required physical sensor input signals + * (see ::bsec_physical_sensor_t) with corresponding sample rates. This information is purely informational as bsec_sensor_control() + * will ensure the sensor is operated in the required manner. To disable a virtual sensor, set the sample rate to BSEC_SAMPLE_RATE_DISABLED. + * + * The subscription update using bsec_update_subscription() is apart from the signal processing one of the the most + * important functions. It allows to enable the desired library outputs. The function determines which physical input + * sensor signals are required at which sample rate to produce the virtual output sensor signals requested by the user. + * When this function returns with success, the requested outputs are called subscribed. A very important feature is the + * retaining of already subscribed outputs. Further outputs can be requested or disabled both individually and + * group-wise in addition to already subscribed outputs without changing them unless a change of already subscribed + * outputs is requested. + * + * @note The state of the library concerning the subscribed outputs cannot be retained among reboots. + * + * The interface of bsec_update_subscription() requires the usage of arrays of sensor configuration structures. + * Such a structure has the fields sensor identifier and sample rate. These fields have the properties: + * - Output signals of virtual sensors must be requested using unique identifiers (Member of ::bsec_virtual_sensor_t) + * - Different sets of identifiers are available for inputs of physical sensors and outputs of virtual sensors + * - Identifiers are unique values defined by the library, not from external + * - Sample rates must be provided as value of + * - An allowed sample rate for continuously sampled signals + * - 65535.0f (BSEC_SAMPLE_RATE_DISABLED) to turn off outputs and identify disabled inputs + * + * @note The same sensor identifiers are also used within the functions bsec_do_steps(). + * + * The usage principles of bsec_update_subscription() are: + * - Differential updates (i.e., only asking for outputs that the user would like to change) is supported. + * - Invalid requests of outputs are ignored. Also if one of the requested outputs is unavailable, all the requests + * are ignored. At the same time, a warning is returned. + * - To disable BSEC, all outputs shall be turned off. Only enabled (subscribed) outputs have to be disabled while + * already disabled outputs do not have to be disabled explicitly. + * + * @param[in] requested_virtual_sensors Pointer to array of requested virtual sensor (output) configurations for the library + * @param[in] n_requested_virtual_sensors Number of virtual sensor structs pointed by requested_virtual_sensors + * @param[out] required_sensor_settings Pointer to array of required physical sensor configurations for the library + * @param[in,out] n_required_sensor_settings [in] Size of allocated required_sensor_settings array, [out] number of sensor configurations returned + * + * @return Zero when successful, otherwise an error code + * + * @sa bsec_sensor_configuration_t + * @sa bsec_physical_sensor_t + * @sa bsec_virtual_sensor_t + * + \code{.c} + // Example // + + // Change 3 virtual sensors (switch IAQ and raw temperature -> on / pressure -> off) + bsec_sensor_configuration_t requested_virtual_sensors[3]; + uint8_t n_requested_virtual_sensors = 3; + + requested_virtual_sensors[0].sensor_id = BSEC_OUTPUT_IAQ; + requested_virtual_sensors[0].sample_rate = BSEC_SAMPLE_RATE_ULP; + requested_virtual_sensors[1].sensor_id = BSEC_OUTPUT_RAW_TEMPERATURE; + requested_virtual_sensors[1].sample_rate = BSEC_SAMPLE_RATE_ULP; + requested_virtual_sensors[2].sensor_id = BSEC_OUTPUT_RAW_PRESSURE; + requested_virtual_sensors[2].sample_rate = BSEC_SAMPLE_RATE_DISABLED; + + // Allocate a struct for the returned physical sensor settings + bsec_sensor_configuration_t required_sensor_settings[BSEC_MAX_PHYSICAL_SENSOR]; + uint8_t n_required_sensor_settings = BSEC_MAX_PHYSICAL_SENSOR; + + // Call bsec_update_subscription() to enable/disable the requested virtual sensors + bsec_update_subscription(requested_virtual_sensors, n_requested_virtual_sensors, required_sensor_settings, &n_required_sensor_settings); + \endcode + * + */ +bsec_library_return_t bsec_update_subscription(const bsec_sensor_configuration_t * const requested_virtual_sensors, + const uint8_t n_requested_virtual_sensors, bsec_sensor_configuration_t * required_sensor_settings, + uint8_t * n_required_sensor_settings); + + +/*! + * @brief Main signal processing function of BSEC + * + * + * Processing of the input signals and returning of output samples is performed by bsec_do_steps(). + * - The samples of all library inputs must be passed with unique identifiers representing the input signals from + * physical sensors where the order of these inputs can be chosen arbitrary. However, all input have to be provided + * within the same time period as they are read. A sequential provision to the library might result in undefined + * behavior. + * - The samples of all library outputs are returned with unique identifiers corresponding to the output signals of + * virtual sensors where the order of the returned outputs may be arbitrary. + * - The samples of all input as well as output signals of physical as well as virtual sensors use the same + * representation in memory with the following fields: + * - Sensor identifier: + * - For inputs: required to identify the input signal from a physical sensor + * - For output: overwritten by bsec_do_steps() to identify the returned signal from a virtual sensor + * - Time stamp of the sample + * + * Calling bsec_do_steps() requires the samples of the input signals to be provided along with their time stamp when + * they are recorded and only when they are acquired. Repetition of samples with the same time stamp are ignored and + * result in a warning. Repetition of values of samples which are not acquired anew by a sensor result in deviations + * of the computed output signals. Concerning the returned output samples, an important feature is, that a value is + * returned for an output only when a new occurrence has been computed. A sample of an output signal is returned only + * once. + * + * + * @param[in] inputs Array of input data samples. Each array element represents a sample of a different physical sensor. + * @param[in] n_inputs Number of passed input data structs. + * @param[out] outputs Array of output data samples. Each array element represents a sample of a different virtual sensor. + * @param[in,out] n_outputs [in] Number of allocated output structs, [out] number of outputs returned + * + * @return Zero when successful, otherwise an error code + * + + \code{.c} + // Example // + + // Allocate input and output memory + bsec_input_t input[3]; + uint8_t n_input = 3; + bsec_output_t output[2]; + uint8_t n_output=2; + + bsec_library_return_t status; + + // Populate the input structs, assuming the we have timestamp (ts), + // gas sensor resistance (R), temperature (T), and humidity (rH) available + // as input variables + input[0].sensor_id = BSEC_INPUT_GASRESISTOR; + input[0].signal = R; + input[0].time_stamp= ts; + input[1].sensor_id = BSEC_INPUT_TEMPERATURE; + input[1].signal = T; + input[1].time_stamp= ts; + input[2].sensor_id = BSEC_INPUT_HUMIDITY; + input[2].signal = rH; + input[2].time_stamp= ts; + + + // Invoke main processing BSEC function + status = bsec_do_steps( input, n_input, output, &n_output ); + + // Iterate through the BSEC output data, if the call succeeded + if(status == BSEC_OK) + { + for(int i = 0; i < n_output; i++) + { + switch(output[i].sensor_id) + { + case BSEC_OUTPUT_IAQ: + // Retrieve the IAQ results from output[i].signal + // and do something with the data + break; + case BSEC_OUTPUT_AMBIENT_TEMPERATURE: + // Retrieve the ambient temperature results from output[i].signal + // and do something with the data + break; + + } + } + } + + \endcode + */ + +bsec_library_return_t bsec_do_steps(const bsec_input_t * const inputs, const uint8_t n_inputs, bsec_output_t * outputs, uint8_t * n_outputs); + + +/*! + * @brief Reset a particular virtual sensor output + * + * This function allows specific virtual sensor outputs to be reset. The meaning of "reset" depends on the specific + * output. In case of the IAQ output, reset means zeroing the output to the current ambient conditions. + * + * @param[in] sensor_id Virtual sensor to be reset + * + * @return Zero when successful, otherwise an error code + * + * + \code{.c} + // Example // + bsec_reset_output(BSEC_OUTPUT_IAQ); + + \endcode + */ + +bsec_library_return_t bsec_reset_output(uint8_t sensor_id); + + +/*! + * @brief Update algorithm configuration parameters + * + * BSEC uses a default configuration for the modules and common settings. The initial configuration can be customized + * by bsec_set_configuration(). This is an optional step. + * + * @note A work buffer with sufficient size is required and has to be provided by the function caller to decompose + * the serialization and apply it to the library and its modules. Please use #BSEC_MAX_PROPERTY_BLOB_SIZE for allotting + * the required size. + * + * @param[in] serialized_settings Settings serialized to a binary blob + * @param[in] n_serialized_settings Size of the settings blob + * @param[in,out] work_buffer Work buffer used to parse the blob + * @param[in] n_work_buffer_size Length of the work buffer available for parsing the blob + * + * @return Zero when successful, otherwise an error code + * + \code{.c} + // Example // + + // Allocate variables + uint8_t serialized_settings[BSEC_MAX_PROPERTY_BLOB_SIZE]; + uint32_t n_serialized_settings_max = BSEC_MAX_PROPERTY_BLOB_SIZE; + uint8_t work_buffer[BSEC_MAX_PROPERTY_BLOB_SIZE]; + uint32_t n_work_buffer = BSEC_MAX_PROPERTY_BLOB_SIZE; + + // Here we will load a provided config string into serialized_settings + + // Apply the configuration + bsec_set_configuration(serialized_settings, n_serialized_settings_max, work_buffer, n_work_buffer); + + \endcode + */ + +bsec_library_return_t bsec_set_configuration(const uint8_t * const serialized_settings, + const uint32_t n_serialized_settings, uint8_t * work_buffer, + const uint32_t n_work_buffer_size); + + +/*! + * @brief Restore the internal state of the library + * + * BSEC uses a default state for all signal processing modules and the BSEC module. To ensure optimal performance, + * especially of the gas sensor functionality, it is recommended to retrieve the state using bsec_get_state() + * before unloading the library, storing it in some form of non-volatile memory, and setting it using bsec_set_state() + * before resuming further operation of the library. + * + * @note A work buffer with sufficient size is required and has to be provided by the function caller to decompose the + * serialization and apply it to the library and its modules. Please use #BSEC_MAX_PROPERTY_BLOB_SIZE for allotting the + * required size. + * + * @param[in] serialized_state States serialized to a binary blob + * @param[in] n_serialized_state Size of the state blob + * @param[in,out] work_buffer Work buffer used to parse the blob + * @param[in] n_work_buffer_size Length of the work buffer available for parsing the blob + * + * @return Zero when successful, otherwise an error code + * + \code{.c} + // Example // + + // Allocate variables + uint8_t serialized_state[BSEC_MAX_PROPERTY_BLOB_SIZE]; + uint32_t n_serialized_state = BSEC_MAX_PROPERTY_BLOB_SIZE; + uint8_t work_buffer_state[BSEC_MAX_PROPERTY_BLOB_SIZE]; + uint32_t n_work_buffer_size = BSEC_MAX_PROPERTY_BLOB_SIZE; + + // Here we will load a state string from a previous use of BSEC + + // Apply the previous state to the current BSEC session + bsec_set_state(serialized_state, n_serialized_state, work_buffer_state, n_work_buffer_size); + + \endcode +*/ + +bsec_library_return_t bsec_set_state(const uint8_t * const serialized_state, const uint32_t n_serialized_state, + uint8_t * work_buffer, const uint32_t n_work_buffer_size); + + +/*! + * @brief Retrieve the current library configuration + * + * BSEC allows to retrieve the current configuration using bsec_get_configuration(). Returns a binary blob encoding + * the current configuration parameters of the library in a format compatible with bsec_set_configuration(). + * + * @note The function bsec_get_configuration() is required to be used for debugging purposes only. + * @note A work buffer with sufficient size is required and has to be provided by the function caller to decompose the + * serialization and apply it to the library and its modules. Please use #BSEC_MAX_PROPERTY_BLOB_SIZE for allotting the + * required size. + * + * + * @param[in] config_id Identifier for a specific set of configuration settings to be returned; + * shall be zero to retrieve all configuration settings. + * @param[out] serialized_settings Buffer to hold the serialized config blob + * @param[in] n_serialized_settings_max Maximum available size for the serialized settings + * @param[in,out] work_buffer Work buffer used to parse the binary blob + * @param[in] n_work_buffer Length of the work buffer available for parsing the blob + * @param[out] n_serialized_settings Actual size of the returned serialized configuration blob + * + * @return Zero when successful, otherwise an error code + * + \code{.c} + // Example // + + // Allocate variables + uint8_t serialized_settings[BSEC_MAX_PROPERTY_BLOB_SIZE]; + uint32_t n_serialized_settings_max = BSEC_MAX_PROPERTY_BLOB_SIZE; + uint8_t work_buffer[BSEC_MAX_PROPERTY_BLOB_SIZE]; + uint32_t n_work_buffer = BSEC_MAX_PROPERTY_BLOB_SIZE; + uint32_t n_serialized_settings = 0; + + // Configuration of BSEC algorithm is stored in 'serialized_settings' + bsec_get_configuration(0, serialized_settings, n_serialized_settings_max, work_buffer, n_work_buffer, &n_serialized_settings); + + \endcode + */ + +bsec_library_return_t bsec_get_configuration(const uint8_t config_id, uint8_t * serialized_settings, const uint32_t n_serialized_settings_max, + uint8_t * work_buffer, const uint32_t n_work_buffer, uint32_t * n_serialized_settings); + + +/*! + *@brief Retrieve the current internal library state + * + * BSEC allows to retrieve the current states of all signal processing modules and the BSEC module using + * bsec_get_state(). This allows a restart of the processing after a reboot of the system by calling bsec_set_state(). + * + * @note A work buffer with sufficient size is required and has to be provided by the function caller to decompose the + * serialization and apply it to the library and its modules. Please use #BSEC_MAX_STATE_BLOB_SIZE for allotting the + * required size. + * + * + * @param[in] state_set_id Identifier for a specific set of states to be returned; shall be + * zero to retrieve all states. + * @param[out] serialized_state Buffer to hold the serialized config blob + * @param[in] n_serialized_state_max Maximum available size for the serialized states + * @param[in,out] work_buffer Work buffer used to parse the blob + * @param[in] n_work_buffer Length of the work buffer available for parsing the blob + * @param[out] n_serialized_state Actual size of the returned serialized blob + * + * @return Zero when successful, otherwise an error code + * + \code{.c} + // Example // + + // Allocate variables + uint8_t serialized_state[BSEC_MAX_STATE_BLOB_SIZE]; + uint32_t n_serialized_state_max = BSEC_MAX_STATE_BLOB_SIZE; + uint32_t n_serialized_state = BSEC_MAX_STATE_BLOB_SIZE; + uint8_t work_buffer_state[BSEC_MAX_STATE_BLOB_SIZE]; + uint32_t n_work_buffer_size = BSEC_MAX_STATE_BLOB_SIZE; + + // Algorithm state is stored in 'serialized_state' + bsec_get_state(0, serialized_state, n_serialized_state_max, work_buffer_state, n_work_buffer_size, &n_serialized_state); + + \endcode + */ + +bsec_library_return_t bsec_get_state(const uint8_t state_set_id, uint8_t * serialized_state, + const uint32_t n_serialized_state_max, uint8_t * work_buffer, const uint32_t n_work_buffer, + uint32_t * n_serialized_state); + +/*! + * @brief Retrieve BMExxx sensor instructions + * + * The bsec_sensor_control() interface is a key feature of BSEC, as it allows an easy way for the signal processing + * library to control the operation of the BME sensor. This is important since gas sensor behaviour is mainly + * determined by how the integrated heater is configured. To ensure an easy integration of BSEC into any system, + * bsec_sensor_control() will provide the caller with information about the current sensor configuration that is + * necessary to fulfill the input requirements derived from the current outputs requested via + * bsec_update_subscription(). + * + * In practice the use of this function shall be as follows: + * - Call bsec_sensor_control() which returns a bsec_bme_settings_t struct. + * - Based on the information contained in this struct, the sensor is configured and a forced-mode measurement is + * triggered if requested by bsec_sensor_control(). + * - Once this forced-mode measurement is complete, the signals specified in this struct shall be passed to + * bsec_do_steps() to perform the signal processing. + * - After processing, the process should sleep until the bsec_bme_settings_t::next_call timestamp is reached. + * + * + * @param [in] time_stamp Current timestamp in [ns] + * @param[out] sensor_settings Settings to be passed to API to operate sensor at this time instance + * + * @return Zero when successful, otherwise an error code + */ + +bsec_library_return_t bsec_sensor_control(const int64_t time_stamp, bsec_bme_settings_t *sensor_settings); + +/*@}*/ //BSEC Interface + +#ifdef __cplusplus + } +#endif + +#endif /* __BSEC_INTERFACE_H__ */ diff --git a/src/BSEC/libalgobsec.a b/src/BSEC/libalgobsec.a new file mode 100644 index 0000000000000000000000000000000000000000..51c6242ed2eaea4962337a49cb00fdcc4f642ff7 GIT binary patch literal 89672 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%0AZZG%*awLw0~Ob@oFD9*5(c{v-#^w9;L8 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 RPJ;Z(to$JgeKnA*|6jYXZfO7j literal 0 HcmV?d00001 diff --git a/src/bmeSPI.cxx b/src/bmeSPI.cxx index f223f64..6499172 100644 --- a/src/bmeSPI.cxx +++ b/src/bmeSPI.cxx @@ -8,34 +8,51 @@ #include #include "BME68x-Sensor-API/bme68x.h" +#include "BSEC/bsec_interface.h" #include "oled-driver/Renderer.hpp" extern QueueHandle_t spiMutex; extern void waitForSpiFinished(); extern Renderer renderer; +extern void initDisplay(); + constexpr auto MaximumChars = 22 * 4; char buffer[MaximumChars]; -constexpr auto SPI_DEVICE = &hspi2; -uint8_t txBuffer[512]; +constexpr auto SpiPeripherie = &hspi2; +uint8_t txBuffer[512 + 1]; + +constexpr auto temperatureOffset = 7.0f; struct bme68x_dev bmeSensor; -volatile int8_t res; struct bme68x_conf bmeConf; struct bme68x_heatr_conf bmeHeaterConf; struct bme68x_data bmeData[3]; -uint16_t del_period; -uint32_t time_ms = 0; -uint8_t n_fields; -uint16_t sampleCount = 1; +uint32_t delayInUs; +uint8_t numberOfData; -/* Heater temperature in degree Celsius */ -uint16_t temp_prof[10] = {200, 240, 280, 320, 360, 360, 320, 280, 240, 200}; +constexpr auto ProfileLength = 1; -/* Heating duration in milliseconds */ -uint16_t dur_prof[10] = {100, 100, 100, 100, 100, 100, 100, 100, 100, 100}; +// Heater temperature in degree Celsius +uint16_t temperatureProfile[ProfileLength] = {320}; + +// Heating duration in milliseconds +uint16_t durationProfile[ProfileLength] = {150}; + +constexpr uint8_t numberRequestedVirtualSensors = 4; +bsec_sensor_configuration_t requestedVirtualSensors[numberRequestedVirtualSensors]; + +float iaq, rawTemperature, pressure, rawHumidity, gasResistance, stabStatus, runInStatus, + temperature, humidity, staticIaq, co2Equivalent, breathVocEquivalent, compGasValue, + gasPercentage; + +uint8_t iaqAccuracy, staticIaqAccuracy, co2Accuracy, breathVocAccuracy, compGasAccuracy, + gasPercentageAcccuracy; + +// uint8_t bsecState[BSEC_MAX_STATE_BLOB_SIZE]; +// uint8_t workBuffer[BSEC_MAX_WORKBUFFER_SIZE]; void setChipSelect(bool state) { @@ -49,9 +66,9 @@ BME68X_INTF_RET_TYPE bme68x_spi_read(uint8_t reg_addr, uint8_t *reg_data, uint32 xSemaphoreTake(spiMutex, portMAX_DELAY); setChipSelect(true); - HAL_SPI_Transmit_DMA(SPI_DEVICE, ®_addr, 1); + HAL_SPI_Transmit_DMA(SpiPeripherie, ®_addr, 1); waitForSpiFinished(); - HAL_SPI_Receive_DMA(SPI_DEVICE, reg_data, len); + HAL_SPI_Receive_DMA(SpiPeripherie, reg_data, len); waitForSpiFinished(); setChipSelect(false); @@ -73,7 +90,7 @@ BME68X_INTF_RET_TYPE bme68x_spi_write(uint8_t reg_addr, const uint8_t *reg_data, xSemaphoreTake(spiMutex, portMAX_DELAY); setChipSelect(true); - HAL_SPI_Transmit_DMA(SPI_DEVICE, const_cast(txBuffer), len + 1); + HAL_SPI_Transmit_DMA(SpiPeripherie, const_cast(txBuffer), len + 1); waitForSpiFinished(); setChipSelect(false); @@ -85,7 +102,7 @@ BME68X_INTF_RET_TYPE bme68x_spi_write(uint8_t reg_addr, const uint8_t *reg_data, // Delay function maps void bme68x_delay_us(uint32_t period, void *) { - HAL_Delay(period / 1000); + vTaskDelay(period / 1000); } int8_t bme68x_spi_init(struct bme68x_dev *bme) @@ -94,13 +111,11 @@ int8_t bme68x_spi_init(struct bme68x_dev *bme) if (bme != NULL) { - // printf("SPI Interface\n"); bme->read = bme68x_spi_read; bme->write = bme68x_spi_write; bme->intf = BME68X_SPI_INTF; bme->delay_us = bme68x_delay_us; - bme->intf_ptr = SPI_DEVICE; bme->amb_temp = 25; /* The ambient temperature in deg C is used for defining the heater temperature */ } @@ -112,90 +127,216 @@ int8_t bme68x_spi_init(struct bme68x_dev *bme) return rslt; } -void bme68x_check_rslt(const char *, int8_t) +void bmeSensorInit() { + bme68x_spi_init(&bmeSensor); + bme68x_init(&bmeSensor); + + bme68x_get_conf(&bmeConf, &bmeSensor); + bmeConf.os_hum = BME68X_OS_16X; + bmeConf.os_temp = BME68X_OS_2X; + bmeConf.os_pres = BME68X_OS_1X; + bmeConf.filter = BME68X_FILTER_OFF; + bmeConf.odr = BME68X_ODR_NONE; + bme68x_set_conf(&bmeConf, &bmeSensor); + + bmeHeaterConf.enable = BME68X_ENABLE; + bmeHeaterConf.heatr_temp_prof = temperatureProfile; + bmeHeaterConf.heatr_dur_prof = durationProfile; + bmeHeaterConf.profile_len = ProfileLength; + bme68x_set_heatr_conf(BME68X_SEQUENTIAL_MODE, &bmeHeaterConf, &bmeSensor); + + bme68x_set_op_mode(BME68X_SEQUENTIAL_MODE, &bmeSensor); + + bsec_init(); + + // Change 3 virtual sensors (switch IAQ and raw temperature -> on / pressure -> off + requestedVirtualSensors[0].sensor_id = BSEC_OUTPUT_IAQ; + requestedVirtualSensors[0].sample_rate = BSEC_SAMPLE_RATE_CONTINUOUS; + requestedVirtualSensors[1].sensor_id = BSEC_OUTPUT_CO2_EQUIVALENT; + requestedVirtualSensors[1].sample_rate = BSEC_SAMPLE_RATE_CONTINUOUS; + requestedVirtualSensors[2].sensor_id = BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE; + requestedVirtualSensors[2].sample_rate = BSEC_SAMPLE_RATE_CONTINUOUS; + requestedVirtualSensors[3].sensor_id = BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY; + requestedVirtualSensors[3].sample_rate = BSEC_SAMPLE_RATE_CONTINUOUS; + + // Allocate a struct for the returned physical sensor settings + bsec_sensor_configuration_t requiredSensorSettings[BSEC_MAX_PHYSICAL_SENSOR]; + uint8_t numberRequiredSensorSettings = BSEC_MAX_PHYSICAL_SENSOR; + + // Call bsec_update_subscription() to enable/disable the requested virtual sensors + bsec_update_subscription(requestedVirtualSensors, numberRequestedVirtualSensors, + requiredSensorSettings, &numberRequiredSensorSettings); } void bmeRun() { - res = bme68x_spi_init(&bmeSensor); - bme68x_check_rslt("bme68x_interface_init", res); + delayInUs = bme68x_get_meas_dur(BME68X_SEQUENTIAL_MODE, &bmeConf, &bmeSensor) + + (bmeHeaterConf.heatr_dur_prof[0] * 1000); + vTaskDelay(delayInUs / 1000); - res = bme68x_init(&bmeSensor); - bme68x_check_rslt("bme68x_init", res); - - /* Check if res == BME68X_OK, report or handle if otherwise */ - res = bme68x_get_conf(&bmeConf, &bmeSensor); - bme68x_check_rslt("bme68x_get_conf", res); - - /* Check if res == BME68X_OK, report or handle if otherwise */ - bmeConf.filter = BME68X_FILTER_OFF; - bmeConf.odr = - BME68X_ODR_NONE; /* This parameter defines the sleep duration after each profile */ - bmeConf.os_hum = BME68X_OS_16X; - bmeConf.os_pres = BME68X_OS_1X; - bmeConf.os_temp = BME68X_OS_2X; - res = bme68x_set_conf(&bmeConf, &bmeSensor); - bme68x_check_rslt("bme68x_set_conf", res); - - /* Check if res == BME68X_OK, report or handle if otherwise */ - bmeHeaterConf.enable = BME68X_ENABLE; - bmeHeaterConf.heatr_temp_prof = temp_prof; - bmeHeaterConf.heatr_dur_prof = dur_prof; - bmeHeaterConf.profile_len = 10; - res = bme68x_set_heatr_conf(BME68X_SEQUENTIAL_MODE, &bmeHeaterConf, &bmeSensor); - bme68x_check_rslt("bme68x_set_heatr_conf", res); - - /* Check if res == BME68X_OK, report or handle if otherwise */ - res = bme68x_set_op_mode(BME68X_SEQUENTIAL_MODE, &bmeSensor); - bme68x_check_rslt("bme68x_set_op_mode", res); - - /* Check if res == BME68X_OK, report or handle if otherwise */ - // printf("Sample, TimeStamp(ms), Temperature(deg C), Pressure(Pa), Humidity(%%), Gas " - // "resistance(ohm), Status, Profile index, Measurement index\n"); - while (1) + auto status = bme68x_get_data(BME68X_SEQUENTIAL_MODE, bmeData, &numberOfData, &bmeSensor); + if (status != 0) { - del_period = - bme68x_get_meas_dur(BME68X_SEQUENTIAL_MODE, &bmeConf) + bmeHeaterConf.heatr_dur_prof[0]; - vTaskDelay(del_period); + __asm("bkpt"); + } +} - // time_ms = HAL_GetTick(); +void bsecRun() +{ + /* + auto status = bsec_set_state(state, BSEC_MAX_STATE_BLOB_SIZE, workBuffer, sizeof(workBuffer)); - res = bme68x_get_data(BME68X_SEQUENTIAL_MODE, bmeData, &n_fields, &bmeSensor); - bme68x_check_rslt("bme68x_get_data", res); - - /* Check if res == BME68X_OK, report or handle if otherwise */ - for (uint8_t i = 0; i < n_fields; i++) + if (status == BSEC_OK) + { + for (uint32_t i = 0; i < BSEC_MAX_STATE_BLOB_SIZE; i++) { - renderer.clearAll(); - - snprintf(buffer, MaximumChars, - "%d°C, %luhPa, %lu%%\n%lukOhm, status: 0x%x\ngas_index: %d\nmeas_index: %d", - bmeData[i].temperature / 100, // - bmeData[i].pressure / 100, // - bmeData[i].humidity / 1000, // - bmeData[i].gas_resistance / 1000, // - bmeData[i].status, // - bmeData[i].gas_index, // - bmeData[i].meas_index); - - renderer.print({0, 0}, buffer); - renderer.render(); - /* - snprintf(buffer, MaximumChars, "sampleCount: %u\n, time_ms: %u\n \ - temperature; %d°C\n \ - pressure: %u\n \ - humidity: %u\n \ - gas_resistance: %u\n \ - // status: 0x%x\n \ - //gas_index: %d\n \ - meas_index: %d\n", - sampleCount, time_ms, (bmeData[i].temperature / 100), bmeData[i].pressure, - (bmeData[i].humidity / 1000), bmeData[i].gas_resistance, bmeData[i].status, - bmeData[i].gas_index, bmeData[i].meas_index); - */ + bsecState[i] = state[i]; } + validBsecState = true; + } + */ + + if (!(bmeData[numberOfData - 1].status & BME68X_NEW_DATA_MSK)) + { + __asm("bkpt"); + return; } - return; + bsec_input_t inputs[BSEC_MAX_PHYSICAL_SENSOR]; + uint8_t nInputs = 0, nOutputs = 0; + int64_t currentTimeInNs = xTaskGetTickCount() * int64_t(1000) * int64_t(1000); + + inputs[nInputs].sensor_id = BSEC_INPUT_TEMPERATURE; + inputs[nInputs].signal = bmeData[numberOfData - 1].temperature / 100.0f; + inputs[nInputs].time_stamp = currentTimeInNs; + nInputs++; + + inputs[nInputs].sensor_id = BSEC_INPUT_HUMIDITY; + inputs[nInputs].signal = bmeData[numberOfData - 1].humidity / 1000.0f; + inputs[nInputs].time_stamp = currentTimeInNs; + nInputs++; + + inputs[nInputs].sensor_id = BSEC_INPUT_PRESSURE; + inputs[nInputs].signal = bmeData[numberOfData - 1].pressure; + inputs[nInputs].time_stamp = currentTimeInNs; + nInputs++; + + inputs[nInputs].sensor_id = BSEC_INPUT_GASRESISTOR; + inputs[nInputs].signal = bmeData[numberOfData - 1].gas_resistance; + inputs[nInputs].time_stamp = currentTimeInNs; + nInputs++; + + inputs[nInputs].sensor_id = BSEC_INPUT_HEATSOURCE; + inputs[nInputs].signal = temperatureOffset; + inputs[nInputs].time_stamp = currentTimeInNs; + nInputs++; + + nOutputs = BSEC_NUMBER_OUTPUTS; + bsec_output_t outputs[BSEC_NUMBER_OUTPUTS]; + + auto status = bsec_do_steps(inputs, nInputs, outputs, &nOutputs); + if (status != BSEC_OK) + { + return; + } + + // zeroOutputs(); + + if (nOutputs > 0) + { + auto outputTimestamp = outputs[0].time_stamp / 1000000; /* Convert from ns to ms */ + + for (uint8_t i = 0; i < nOutputs; i++) + { + switch (outputs[i].sensor_id) + { + case BSEC_OUTPUT_IAQ: + iaq = outputs[i].signal; + iaqAccuracy = outputs[i].accuracy; + break; + case BSEC_OUTPUT_STATIC_IAQ: + staticIaq = outputs[i].signal; + staticIaqAccuracy = outputs[i].accuracy; + break; + case BSEC_OUTPUT_CO2_EQUIVALENT: + co2Equivalent = outputs[i].signal; + co2Accuracy = outputs[i].accuracy; + break; + case BSEC_OUTPUT_BREATH_VOC_EQUIVALENT: + breathVocEquivalent = outputs[i].signal; + breathVocAccuracy = outputs[i].accuracy; + break; + case BSEC_OUTPUT_RAW_TEMPERATURE: + rawTemperature = outputs[i].signal; + break; + case BSEC_OUTPUT_RAW_PRESSURE: + pressure = outputs[i].signal; + break; + case BSEC_OUTPUT_RAW_HUMIDITY: + rawHumidity = outputs[i].signal; + break; + case BSEC_OUTPUT_RAW_GAS: + gasResistance = outputs[i].signal; + break; + case BSEC_OUTPUT_STABILIZATION_STATUS: + stabStatus = outputs[i].signal; + break; + case BSEC_OUTPUT_RUN_IN_STATUS: + runInStatus = outputs[i].signal; + break; + case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE: + temperature = outputs[i].signal; + break; + case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY: + humidity = outputs[i].signal; + break; + case BSEC_OUTPUT_COMPENSATED_GAS: + compGasValue = outputs[i].signal; + compGasAccuracy = outputs[i].accuracy; + break; + case BSEC_OUTPUT_GAS_PERCENTAGE: + gasPercentage = outputs[i].signal; + gasPercentageAcccuracy = outputs[i].accuracy; + break; + default: + break; + } + } + } +} + +void printBmeSensorData() +{ + renderer.clearAll(); + + snprintf(buffer, MaximumChars, + "%d°C, %luhPa, %d%%\nIAQ: %d, Accuracy: %d\nCO2: %dppm\n%d, %d, %d - %lukOhm", + static_cast(temperature), // + bmeData[numberOfData - 1].pressure / 100, // + static_cast(humidity), // + static_cast(iaq), // + iaqAccuracy, // + static_cast(co2Equivalent), // + bmeData[numberOfData - 1].status, // + bmeData[numberOfData - 1].gas_index, // + bmeData[numberOfData - 1].gas_wait, // + bmeData[numberOfData - 1].gas_resistance / 1000); + + renderer.print({0, 0}, buffer); + renderer.render(); +} + +//-------------------------------------------------------------------------------------------------- +extern "C" void sensorTask(void *) +{ + initDisplay(); + bmeSensorInit(); + + while (1) + { + bmeRun(); + bsecRun(); + printBmeSensorData(); + } } \ No newline at end of file diff --git a/src/main.cxx b/src/main.cxx index ea4b4c9..e734397 100644 --- a/src/main.cxx +++ b/src/main.cxx @@ -3,13 +3,11 @@ #include "semphr.h" #include "task.h" +// #include "BSEC/bsec_integration.h" #include "SSD1306_SPI.hpp" #include "oled-driver/Display.hpp" #include "oled-driver/Renderer.hpp" -extern TaskHandle_t sensorHandle; -extern void bmeRun(); - // oled display SSD1306_SPI ssdSpiInterface; Display display(ssdSpiInterface); @@ -67,15 +65,4 @@ extern "C" void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *) extern "C" void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *) { notifySpiIsFinished(); -} - -//-------------------------------------------------------------------------------------------------- -extern "C" void sensorTask(void *) -{ - initDisplay(); - - bmeRun(); - while (1) - { - } } \ No newline at end of file