diff --git a/include/bme680mems.h b/include/bme680mems.h index ad66c7cb..5532f0cc 100644 --- a/include/bme680mems.h +++ b/include/bme680mems.h @@ -3,30 +3,52 @@ #include "globals.h" #include -#include "bsec_integration.h" #include "irqhandler.h" - -extern const uint8_t bsec_config_iaq[454]; +#include "../lib/Bosch-BSEC/src/bsec.h" extern bmeStatus_t bme_status; // Make struct for storing gps data globally available extern TaskHandle_t BmeTask; +// --- Bosch BSEC library configuration --- +// 3,3V supply voltage; 3s max time between sensor_control calls; 4 days +// calibration. Change this const if not applicable for your application (see +// BME680 datasheet) +const uint8_t bsec_config_iaq[454] = { + 1, 7, 4, 1, 61, 0, 0, 0, 0, 0, 0, 0, 174, 1, 0, + 0, 48, 0, 1, 0, 137, 65, 0, 63, 205, 204, 204, 62, 0, 0, + 64, 63, 205, 204, 204, 62, 0, 0, 225, 68, 0, 192, 168, 71, 64, + 49, 119, 76, 0, 0, 0, 0, 0, 80, 5, 95, 0, 0, 0, 0, + 0, 0, 0, 0, 28, 0, 2, 0, 0, 244, 1, 225, 0, 25, 0, + 0, 128, 64, 0, 0, 32, 65, 144, 1, 0, 0, 112, 65, 0, 0, + 0, 63, 16, 0, 3, 0, 10, 215, 163, 60, 10, 215, 35, 59, 10, + 215, 35, 59, 9, 0, 5, 0, 0, 0, 0, 0, 1, 88, 0, 9, + 0, 229, 208, 34, 62, 0, 0, 0, 0, 0, 0, 0, 0, 218, 27, + 156, 62, 225, 11, 67, 64, 0, 0, 160, 64, 0, 0, 0, 0, 0, + 0, 0, 0, 94, 75, 72, 189, 93, 254, 159, 64, 66, 62, 160, 191, + 0, 0, 0, 0, 0, 0, 0, 0, 33, 31, 180, 190, 138, 176, 97, + 64, 65, 241, 99, 190, 0, 0, 0, 0, 0, 0, 0, 0, 167, 121, + 71, 61, 165, 189, 41, 192, 184, 30, 189, 64, 12, 0, 10, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 229, 0, 254, 0, 2, 1, 5, 48, + 117, 100, 0, 44, 1, 112, 23, 151, 7, 132, 3, 197, 0, 92, 4, + 144, 1, 64, 1, 64, 1, 144, 1, 48, 117, 48, 117, 48, 117, 48, + 117, 100, 0, 100, 0, 100, 0, 48, 117, 48, 117, 48, 117, 100, 0, + 100, 0, 48, 117, 48, 117, 100, 0, 100, 0, 100, 0, 100, 0, 48, + 117, 48, 117, 48, 117, 100, 0, 100, 0, 100, 0, 48, 117, 48, 117, + 100, 0, 100, 0, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, + 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, + 44, 1, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, + 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, + 112, 23, 112, 23, 112, 23, 112, 23, 112, 23, 112, 23, 112, 23, 112, + 23, 112, 23, 112, 23, 112, 23, 112, 23, 112, 23, 112, 23, 255, 255, + 255, 255, 255, 255, 255, 255, 220, 5, 220, 5, 220, 5, 255, 255, 255, + 255, 255, 255, 220, 5, 220, 5, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 44, 1, 0, 0, 0, 0, + 239, 79, 0, 0}; + int bme_init(); void bme_loop(void *pvParameters); -int8_t i2c_read(uint8_t dev_id, uint8_t reg_addr, uint8_t *reg_data, - uint16_t len); -int8_t i2c_write(uint8_t dev_id, uint8_t reg_addr, uint8_t *reg_data, - uint16_t len); -void output_ready(int64_t timestamp, float iaq, uint8_t iaq_accuracy, - float temperature, float humidity, float pressure, - float raw_temperature, float raw_humidity, float gas, - bsec_library_return_t bsec_status, float static_iaq, - float co2_equivalent, float breath_voc_equivalent); -uint32_t state_load(uint8_t *state_buffer, uint32_t n_buffer); -void state_save(const uint8_t *state_buffer, uint32_t length); -uint32_t config_load(uint8_t *config_buffer, uint32_t n_buffer); -void user_delay_ms(uint32_t period); -int64_t get_timestamp_us(); +int checkIaqSensorStatus(void); #endif \ No newline at end of file diff --git a/include/globals.h b/include/globals.h index 8c429048..7fda0d29 100644 --- a/include/globals.h +++ b/include/globals.h @@ -89,6 +89,7 @@ extern uint8_t volatile channel; // wifi channel rotation counter extern uint16_t volatile macs_total, macs_wifi, macs_ble, batt_voltage; // display values extern hw_timer_t *channelSwitch, *sendCycle, *displaytimer; +extern SemaphoreHandle_t I2Caccess; extern std::set, Mallocator> macs; extern std::array::iterator it; diff --git a/lib/Bosch-BSEC/bsec_integration.c b/lib/Bosch-BSEC/bsec_integration.c deleted file mode 100644 index 0dc5dc2d..00000000 --- a/lib/Bosch-BSEC/bsec_integration.c +++ /dev/null @@ -1,559 +0,0 @@ -/* - * Copyright (C) 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_integration.c - * - * @brief - * Private part of the example for using of BSEC library. - */ - -/*! - * @addtogroup bsec_examples BSEC Examples - * @brief BSEC usage examples - * @{*/ - -/**********************************************************************************************************************/ -/* header files */ -/**********************************************************************************************************************/ - -#include -#include -#include - -#include "bsec_integration.h" - -/**********************************************************************************************************************/ -/* local macro definitions */ -/**********************************************************************************************************************/ - -#define NUM_USED_OUTPUTS 8 - -/**********************************************************************************************************************/ -/* global variable declarations */ -/**********************************************************************************************************************/ - -/* Global sensor APIs data structure */ -static struct bme680_dev bme680_g; - -/* Global temperature offset to be subtracted */ -static float bme680_temperature_offset_g = 0.0f; - -/**********************************************************************************************************************/ -/* functions */ -/**********************************************************************************************************************/ - -/*! - * @brief Virtual sensor subscription - * Please call this function before processing of data using bsec_do_steps function - * - * @param[in] sample_rate mode to be used (either BSEC_SAMPLE_RATE_ULP or BSEC_SAMPLE_RATE_LP) - * - * @return subscription result, zero when successful - */ -static bsec_library_return_t bme680_bsec_update_subscription(float sample_rate) -{ - bsec_sensor_configuration_t requested_virtual_sensors[NUM_USED_OUTPUTS]; - uint8_t n_requested_virtual_sensors = NUM_USED_OUTPUTS; - - bsec_sensor_configuration_t required_sensor_settings[BSEC_MAX_PHYSICAL_SENSOR]; - uint8_t n_required_sensor_settings = BSEC_MAX_PHYSICAL_SENSOR; - - bsec_library_return_t status = BSEC_OK; - - /* note: Virtual sensors as desired to be added here */ - requested_virtual_sensors[0].sensor_id = BSEC_OUTPUT_IAQ; - requested_virtual_sensors[0].sample_rate = sample_rate; - requested_virtual_sensors[1].sensor_id = BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE; - requested_virtual_sensors[1].sample_rate = sample_rate; - requested_virtual_sensors[2].sensor_id = BSEC_OUTPUT_RAW_PRESSURE; - requested_virtual_sensors[2].sample_rate = sample_rate; - requested_virtual_sensors[3].sensor_id = BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY; - requested_virtual_sensors[3].sample_rate = sample_rate; - requested_virtual_sensors[4].sensor_id = BSEC_OUTPUT_RAW_GAS; - requested_virtual_sensors[4].sample_rate = sample_rate; - requested_virtual_sensors[5].sensor_id = BSEC_OUTPUT_RAW_TEMPERATURE; - requested_virtual_sensors[5].sample_rate = sample_rate; - requested_virtual_sensors[6].sensor_id = BSEC_OUTPUT_RAW_HUMIDITY; - requested_virtual_sensors[6].sample_rate = sample_rate; - requested_virtual_sensors[7].sensor_id = BSEC_OUTPUT_STATIC_IAQ; - requested_virtual_sensors[7].sample_rate = sample_rate; - - /* Call bsec_update_subscription() to enable/disable the requested virtual sensors */ - status = bsec_update_subscription(requested_virtual_sensors, n_requested_virtual_sensors, required_sensor_settings, - &n_required_sensor_settings); - - return status; -} - -/*! - * @brief Initialize the BME680 sensor and the BSEC library - * - * @param[in] sample_rate mode to be used (either BSEC_SAMPLE_RATE_ULP or BSEC_SAMPLE_RATE_LP) - * @param[in] temperature_offset device-specific temperature offset (due to self-heating) - * @param[in] bus_write pointer to the bus writing function - * @param[in] bus_read pointer to the bus reading function - * @param[in] sleep pointer to the system specific sleep function - * @param[in] state_load pointer to the system-specific state load function - * @param[in] config_load pointer to the system-specific config load function - * - * @return zero if successful, negative otherwise - */ -return_values_init bsec_iot_init(float sample_rate, float temperature_offset, bme680_com_fptr_t bus_write, - bme680_com_fptr_t bus_read, sleep_fct sleep, state_load_fct state_load, config_load_fct config_load) -{ - return_values_init ret = {BME680_OK, BSEC_OK}; - bsec_library_return_t bsec_status = BSEC_OK; - - uint8_t bsec_state[BSEC_MAX_PROPERTY_BLOB_SIZE] = {0}; - uint8_t bsec_config[BSEC_MAX_PROPERTY_BLOB_SIZE] = {0}; - uint8_t work_buffer[BSEC_MAX_PROPERTY_BLOB_SIZE] = {0}; - int bsec_state_len, bsec_config_len; - - /* Fixed I2C configuration */ - bme680_g.dev_id = BME680_I2C_ADDR_PRIMARY; - bme680_g.intf = BME680_I2C_INTF; - /* User configurable I2C configuration */ - bme680_g.write = bus_write; - bme680_g.read = bus_read; - bme680_g.delay_ms = sleep; - - /* Initialize BME680 API */ - ret.bme680_status = bme680_init(&bme680_g); - if (ret.bme680_status != BME680_OK) - { - return ret; - } - - /* Initialize BSEC library */ - ret.bsec_status = bsec_init(); - if (ret.bsec_status != BSEC_OK) - { - return ret; - } - - /* Load library config, if available */ - bsec_config_len = config_load(bsec_config, sizeof(bsec_config)); - if (bsec_config_len != 0) - { - ret.bsec_status = bsec_set_configuration(bsec_config, bsec_config_len, work_buffer, sizeof(work_buffer)); - if (ret.bsec_status != BSEC_OK) - { - return ret; - } - } - - /* Load previous library state, if available */ - bsec_state_len = state_load(bsec_state, sizeof(bsec_state)); - if (bsec_state_len != 0) - { - ret.bsec_status = bsec_set_state(bsec_state, bsec_state_len, work_buffer, sizeof(work_buffer)); - if (ret.bsec_status != BSEC_OK) - { - return ret; - } - } - - /* Set temperature offset */ - bme680_temperature_offset_g = temperature_offset; - - /* Call to the function which sets the library with subscription information */ - ret.bsec_status = bme680_bsec_update_subscription(sample_rate); - if (ret.bsec_status != BSEC_OK) - { - return ret; - } - - return ret; -} - -/*! - * @brief Trigger the measurement based on sensor settings - * - * @param[in] sensor_settings settings of the BME680 sensor adopted by sensor control function - * @param[in] sleep pointer to the system specific sleep function - * - * @return none - */ -static void bme680_bsec_trigger_measurement(bsec_bme_settings_t *sensor_settings, sleep_fct sleep) -{ - uint16_t meas_period; - uint8_t set_required_settings; - int8_t bme680_status = BME680_OK; - - /* Check if a forced-mode measurement should be triggered now */ - if (sensor_settings->trigger_measurement) - { - /* Set sensor configuration */ - - bme680_g.tph_sett.os_hum = sensor_settings->humidity_oversampling; - bme680_g.tph_sett.os_pres = sensor_settings->pressure_oversampling; - bme680_g.tph_sett.os_temp = sensor_settings->temperature_oversampling; - bme680_g.gas_sett.run_gas = sensor_settings->run_gas; - bme680_g.gas_sett.heatr_temp = sensor_settings->heater_temperature; /* degree Celsius */ - bme680_g.gas_sett.heatr_dur = sensor_settings->heating_duration; /* milliseconds */ - - /* Select the power mode */ - /* Must be set before writing the sensor configuration */ - bme680_g.power_mode = BME680_FORCED_MODE; - /* Set the required sensor settings needed */ - set_required_settings = BME680_OST_SEL | BME680_OSP_SEL | BME680_OSH_SEL | BME680_GAS_SENSOR_SEL; - - /* Set the desired sensor configuration */ - bme680_status = bme680_set_sensor_settings(set_required_settings, &bme680_g); - - /* Set power mode as forced mode and trigger forced mode measurement */ - bme680_status = bme680_set_sensor_mode(&bme680_g); - - /* Get the total measurement duration so as to sleep or wait till the measurement is complete */ - bme680_get_profile_dur(&meas_period, &bme680_g); - - /* Delay till the measurement is ready. Timestamp resolution in ms */ - sleep((uint32_t)meas_period); - } - - /* Call the API to get current operation mode of the sensor */ - bme680_status = bme680_get_sensor_mode(&bme680_g); - /* When the measurement is completed and data is ready for reading, the sensor must be in BME680_SLEEP_MODE. - * Read operation mode to check whether measurement is completely done and wait until the sensor is no more - * in BME680_FORCED_MODE. */ - while (bme680_g.power_mode == BME680_FORCED_MODE) - { - /* sleep for 5 ms */ - sleep(5); - bme680_status = bme680_get_sensor_mode(&bme680_g); - } -} - -/*! - * @brief Read the data from registers and populate the inputs structure to be passed to do_steps function - * - * @param[in] time_stamp_trigger settings of the sensor returned from sensor control function - * @param[in] inputs input structure containing the information on sensors to be passed to do_steps - * @param[in] num_bsec_inputs number of inputs to be passed to do_steps - * @param[in] bsec_process_data process data variable returned from sensor_control - * - * @return none - */ -static void bme680_bsec_read_data(int64_t time_stamp_trigger, bsec_input_t *inputs, uint8_t *num_bsec_inputs, - int32_t bsec_process_data) -{ - static struct bme680_field_data data; - int8_t bme680_status = BME680_OK; - - /* We only have to read data if the previous call the bsec_sensor_control() actually asked for it */ - if (bsec_process_data) - { - bme680_status = bme680_get_sensor_data(&data, &bme680_g); - - if (data.status & BME680_NEW_DATA_MSK) - { - /* Pressure to be processed by BSEC */ - if (bsec_process_data & BSEC_PROCESS_PRESSURE) - { - /* Place presssure sample into input struct */ - inputs[*num_bsec_inputs].sensor_id = BSEC_INPUT_PRESSURE; - inputs[*num_bsec_inputs].signal = data.pressure; - inputs[*num_bsec_inputs].time_stamp = time_stamp_trigger; - (*num_bsec_inputs)++; - } - /* Temperature to be processed by BSEC */ - if (bsec_process_data & BSEC_PROCESS_TEMPERATURE) - { - /* Place temperature sample into input struct */ - inputs[*num_bsec_inputs].sensor_id = BSEC_INPUT_TEMPERATURE; - #ifdef BME680_FLOAT_POINT_COMPENSATION - inputs[*num_bsec_inputs].signal = data.temperature; - #else - inputs[*num_bsec_inputs].signal = data.temperature / 100.0f; - #endif - inputs[*num_bsec_inputs].time_stamp = time_stamp_trigger; - (*num_bsec_inputs)++; - - /* Also add optional heatsource input which will be subtracted from the temperature reading to - * compensate for device-specific self-heating (supported in BSEC IAQ solution)*/ - inputs[*num_bsec_inputs].sensor_id = BSEC_INPUT_HEATSOURCE; - inputs[*num_bsec_inputs].signal = bme680_temperature_offset_g; - inputs[*num_bsec_inputs].time_stamp = time_stamp_trigger; - (*num_bsec_inputs)++; - } - /* Humidity to be processed by BSEC */ - if (bsec_process_data & BSEC_PROCESS_HUMIDITY) - { - /* Place humidity sample into input struct */ - inputs[*num_bsec_inputs].sensor_id = BSEC_INPUT_HUMIDITY; - #ifdef BME680_FLOAT_POINT_COMPENSATION - inputs[*num_bsec_inputs].signal = data.humidity; - #else - inputs[*num_bsec_inputs].signal = data.humidity / 1000.0f; - #endif - inputs[*num_bsec_inputs].time_stamp = time_stamp_trigger; - (*num_bsec_inputs)++; - } - /* Gas to be processed by BSEC */ - if (bsec_process_data & BSEC_PROCESS_GAS) - { - /* Check whether gas_valid flag is set */ - if(data.status & BME680_GASM_VALID_MSK) - { - /* Place sample into input struct */ - inputs[*num_bsec_inputs].sensor_id = BSEC_INPUT_GASRESISTOR; - inputs[*num_bsec_inputs].signal = data.gas_resistance; - inputs[*num_bsec_inputs].time_stamp = time_stamp_trigger; - (*num_bsec_inputs)++; - } - } - } - } -} - -/*! - * @brief This function is written to process the sensor data for the requested virtual sensors - * - * @param[in] bsec_inputs input structure containing the information on sensors to be passed to do_steps - * @param[in] num_bsec_inputs number of inputs to be passed to do_steps - * @param[in] output_ready pointer to the function processing obtained BSEC outputs - * - * @return none - */ -static void bme680_bsec_process_data(bsec_input_t *bsec_inputs, uint8_t num_bsec_inputs, output_ready_fct output_ready) -{ - /* Output buffer set to the maximum virtual sensor outputs supported */ - bsec_output_t bsec_outputs[BSEC_NUMBER_OUTPUTS]; - uint8_t num_bsec_outputs = 0; - uint8_t index = 0; - - bsec_library_return_t bsec_status = BSEC_OK; - - int64_t timestamp = 0; - float iaq = 0.0f; - uint8_t iaq_accuracy = 0; - float temp = 0.0f; - float raw_temp = 0.0f; - float raw_pressure = 0.0f; - float humidity = 0.0f; - float raw_humidity = 0.0f; - float raw_gas = 0.0f; - float static_iaq = 0.0f; - uint8_t static_iaq_accuracy = 0; - float co2_equivalent = 0.0f; - uint8_t co2_accuracy = 0; - float breath_voc_equivalent = 0.0f; - uint8_t breath_voc_accuracy = 0; - float comp_gas_value = 0.0f; - uint8_t comp_gas_accuracy = 0; - float gas_percentage = 0.0f; - uint8_t gas_percentage_acccuracy = 0; - - /* Check if something should be processed by BSEC */ - if (num_bsec_inputs > 0) - { - /* Set number of outputs to the size of the allocated buffer */ - /* BSEC_NUMBER_OUTPUTS to be defined */ - num_bsec_outputs = BSEC_NUMBER_OUTPUTS; - - /* Perform processing of the data by BSEC - Note: - * The number of outputs you get depends on what you asked for during bsec_update_subscription(). This is - handled under bme680_bsec_update_subscription() function in this example file. - * The number of actual outputs that are returned is written to num_bsec_outputs. */ - bsec_status = bsec_do_steps(bsec_inputs, num_bsec_inputs, bsec_outputs, &num_bsec_outputs); - - /* Iterate through the outputs and extract the relevant ones. */ - for (index = 0; index < num_bsec_outputs; index++) - { - switch (bsec_outputs[index].sensor_id) - { - case BSEC_OUTPUT_IAQ: - iaq = bsec_outputs[index].signal; - iaq_accuracy = bsec_outputs[index].accuracy; - break; - case BSEC_OUTPUT_STATIC_IAQ: - static_iaq = bsec_outputs[index].signal; - static_iaq_accuracy = bsec_outputs[index].accuracy; - break; - case BSEC_OUTPUT_CO2_EQUIVALENT: - co2_equivalent = bsec_outputs[index].signal; - co2_accuracy = bsec_outputs[index].accuracy; - break; - case BSEC_OUTPUT_BREATH_VOC_EQUIVALENT: - breath_voc_equivalent = bsec_outputs[index].signal; - breath_voc_accuracy = bsec_outputs[index].accuracy; - break; - case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE: - temp = bsec_outputs[index].signal; - break; - case BSEC_OUTPUT_RAW_PRESSURE: - raw_pressure = bsec_outputs[index].signal; - break; - case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY: - humidity = bsec_outputs[index].signal; - break; - case BSEC_OUTPUT_RAW_GAS: - raw_gas = bsec_outputs[index].signal; - break; - case BSEC_OUTPUT_RAW_TEMPERATURE: - raw_temp = bsec_outputs[index].signal; - break; - case BSEC_OUTPUT_RAW_HUMIDITY: - raw_humidity = bsec_outputs[index].signal; - break; - case BSEC_OUTPUT_COMPENSATED_GAS: - comp_gas_value = bsec_outputs[index].signal; - comp_gas_accuracy = bsec_outputs[index].accuracy; - break; - case BSEC_OUTPUT_GAS_PERCENTAGE: - gas_percentage = bsec_outputs[index].signal; - gas_percentage_acccuracy = bsec_outputs[index].accuracy; - break; - default: - continue; - } - - /* Assume that all the returned timestamps are the same */ - timestamp = bsec_outputs[index].time_stamp; - } - - /* Pass the extracted outputs to the user provided output_ready() function. */ - output_ready(timestamp, iaq, iaq_accuracy, temp, humidity, raw_pressure, raw_temp, - raw_humidity, raw_gas, bsec_status, static_iaq, co2_equivalent, breath_voc_equivalent); - } -} - -/*! - * @brief Runs the main (endless) loop that queries sensor settings, applies them, and processes the measured data - * - * @param[in] sleep pointer to the system specific sleep function - * @param[in] get_timestamp_us pointer to the system specific timestamp derivation function - * @param[in] output_ready pointer to the function processing obtained BSEC outputs - * @param[in] state_save pointer to the system-specific state save function - * @param[in] save_intvl interval at which BSEC state should be saved (in samples) - * - * @return none - */ -void bsec_iot_loop(sleep_fct sleep, get_timestamp_us_fct get_timestamp_us, output_ready_fct output_ready, - state_save_fct state_save, uint32_t save_intvl) -{ - /* Timestamp variables */ - int64_t time_stamp = 0; - int64_t time_stamp_interval_ms = 0; - - /* Allocate enough memory for up to BSEC_MAX_PHYSICAL_SENSOR physical inputs*/ - bsec_input_t bsec_inputs[BSEC_MAX_PHYSICAL_SENSOR]; - - /* Number of inputs to BSEC */ - uint8_t num_bsec_inputs = 0; - - /* BSEC sensor settings struct */ - bsec_bme_settings_t sensor_settings; - - /* Save state variables */ - uint8_t bsec_state[BSEC_MAX_STATE_BLOB_SIZE]; - uint8_t work_buffer[BSEC_MAX_STATE_BLOB_SIZE]; - uint32_t bsec_state_len = 0; - uint32_t n_samples = 0; - - bsec_library_return_t bsec_status = BSEC_OK; - - while (1) - { - /* get the timestamp in nanoseconds before calling bsec_sensor_control() */ - time_stamp = get_timestamp_us() * 1000; - - /* Retrieve sensor settings to be used in this time instant by calling bsec_sensor_control */ - bsec_sensor_control(time_stamp, &sensor_settings); - - /* Trigger a measurement if necessary */ - bme680_bsec_trigger_measurement(&sensor_settings, sleep); - - /* Read data from last measurement */ - num_bsec_inputs = 0; - bme680_bsec_read_data(time_stamp, bsec_inputs, &num_bsec_inputs, sensor_settings.process_data); - - /* Time to invoke BSEC to perform the actual processing */ - bme680_bsec_process_data(bsec_inputs, num_bsec_inputs, output_ready); - - /* Increment sample counter */ - n_samples++; - - /* Retrieve and store state if the passed save_intvl */ - if (n_samples >= save_intvl) - { - bsec_status = bsec_get_state(0, bsec_state, sizeof(bsec_state), work_buffer, sizeof(work_buffer), &bsec_state_len); - if (bsec_status == BSEC_OK) - { - state_save(bsec_state, bsec_state_len); - } - n_samples = 0; - } - - - /* Compute how long we can sleep until we need to call bsec_sensor_control() next */ - /* Time_stamp is converted from microseconds to nanoseconds first and then the difference to milliseconds */ - time_stamp_interval_ms = (sensor_settings.next_call - get_timestamp_us() * 1000) / 1000000; - if (time_stamp_interval_ms > 0) - { - sleep((uint32_t)time_stamp_interval_ms); - } - } -} - -/*! @}*/ - diff --git a/lib/Bosch-BSEC/bsec_integration.h b/lib/Bosch-BSEC/bsec_integration.h deleted file mode 100644 index 5155a001..00000000 --- a/lib/Bosch-BSEC/bsec_integration.h +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright (C) 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_integration.h - * - * @brief - * Contains BSEC integration API - */ - -/*! - * @addtogroup bsec_examples BSEC Examples - * @brief BSEC usage examples - * @{*/ - -#ifndef __BSEC_INTEGRATION_H__ -#define __BSEC_INTEGRATION_H__ - -#ifdef __cplusplus -extern "C" -{ -#endif - -/**********************************************************************************************************************/ -/* header files */ -/**********************************************************************************************************************/ - -/* Use the following bme680 driver: https://github.com/BoschSensortec/BME680_driver/releases/tag/bme680_v3.5.1 */ -#include "bme680.h" -/* BSEC header files are available in the inc/ folder of the release package */ -#include "bsec_interface.h" -#include "bsec_datatypes.h" - - -/**********************************************************************************************************************/ -/* type definitions */ -/**********************************************************************************************************************/ - -/* function pointer to the system specific sleep function */ -typedef void (*sleep_fct)(uint32_t t_ms); - -/* function pointer to the system specific timestamp derivation function */ -typedef int64_t (*get_timestamp_us_fct)(); - -/* function pointer to the function processing obtained BSEC outputs */ -typedef void (*output_ready_fct)(int64_t timestamp, float iaq, uint8_t iaq_accuracy, float temperature, float humidity, - float pressure, float raw_temperature, float raw_humidity, float gas, bsec_library_return_t bsec_status, - float static_iaq, float co2_equivalent, float breath_voc_equivalent); - -/* function pointer to the function loading a previous BSEC state from NVM */ -typedef uint32_t (*state_load_fct)(uint8_t *state_buffer, uint32_t n_buffer); - -/* function pointer to the function saving BSEC state to NVM */ -typedef void (*state_save_fct)(const uint8_t *state_buffer, uint32_t length); - -/* function pointer to the function loading the BSEC configuration string from NVM */ -typedef uint32_t (*config_load_fct)(uint8_t *state_buffer, uint32_t n_buffer); - -/* structure definitions */ - -/* Structure with the return value from bsec_iot_init() */ -typedef struct{ - /*! Result of API execution status */ - int8_t bme680_status; - /*! Result of BSEC library */ - bsec_library_return_t bsec_status; -}return_values_init; -/**********************************************************************************************************************/ -/* function declarations */ -/**********************************************************************************************************************/ - -/*! - * @brief Initialize the BME680 sensor and the BSEC library - * - * @param[in] sample_rate mode to be used (either BSEC_SAMPLE_RATE_ULP or BSEC_SAMPLE_RATE_LP) - * @param[in] temperature_offset device-specific temperature offset (due to self-heating) - * @param[in] bus_write pointer to the bus writing function - * @param[in] bus_read pointer to the bus reading function - * @param[in] sleep pointer to the system-specific sleep function - * @param[in] state_load pointer to the system-specific state load function - * - * @return zero if successful, negative otherwise - */ -return_values_init bsec_iot_init(float sample_rate, float temperature_offset, bme680_com_fptr_t bus_write, bme680_com_fptr_t bus_read, - sleep_fct sleep, state_load_fct state_load, config_load_fct config_load); - -/*! - * @brief Runs the main (endless) loop that queries sensor settings, applies them, and processes the measured data - * - * @param[in] sleep pointer to the system-specific sleep function - * @param[in] get_timestamp_us pointer to the system-specific timestamp derivation function - * @param[in] output_ready pointer to the function processing obtained BSEC outputs - * @param[in] state_save pointer to the system-specific state save function - * @param[in] save_intvl interval at which BSEC state should be saved (in samples) - * - * @return return_values_init struct with the result of the API and the BSEC library - */ -void bsec_iot_loop(sleep_fct sleep, get_timestamp_us_fct get_timestamp_us, output_ready_fct output_ready, - state_save_fct state_save, uint32_t save_intvl); - -#ifdef __cplusplus -} -#endif - -#endif /* __BSEC_INTEGRATION_H__ */ - -/*! @}*/ - diff --git a/lib/Bosch-BSEC/README.md b/lib/Bosch-BSEC/src/bme680/README.md similarity index 100% rename from lib/Bosch-BSEC/README.md rename to lib/Bosch-BSEC/src/bme680/README.md diff --git a/lib/Bosch-BSEC/bme680.c b/lib/Bosch-BSEC/src/bme680/bme680.c similarity index 100% rename from lib/Bosch-BSEC/bme680.c rename to lib/Bosch-BSEC/src/bme680/bme680.c diff --git a/lib/Bosch-BSEC/bme680.h b/lib/Bosch-BSEC/src/bme680/bme680.h similarity index 100% rename from lib/Bosch-BSEC/bme680.h rename to lib/Bosch-BSEC/src/bme680/bme680.h diff --git a/lib/Bosch-BSEC/bme680_defs.h b/lib/Bosch-BSEC/src/bme680/bme680_defs.h similarity index 99% rename from lib/Bosch-BSEC/bme680_defs.h rename to lib/Bosch-BSEC/src/bme680/bme680_defs.h index 495edfe0..79fd7782 100644 --- a/lib/Bosch-BSEC/bme680_defs.h +++ b/lib/Bosch-BSEC/src/bme680/bme680_defs.h @@ -103,7 +103,7 @@ /** BME680 configuration macros */ /** Enable or un-comment the macro to provide floating point data output */ #ifndef BME680_FLOAT_POINT_COMPENSATION -/* #define BME680_FLOAT_POINT_COMPENSATION */ +//#define BME680_FLOAT_POINT_COMPENSATION #endif /** BME680 General config */ diff --git a/lib/Bosch-BSEC/src/bsec.cpp b/lib/Bosch-BSEC/src/bsec.cpp new file mode 100644 index 00000000..cf85d77f --- /dev/null +++ b/lib/Bosch-BSEC/src/bsec.cpp @@ -0,0 +1,498 @@ +/** + * Copyright (C) 2017 - 2018 Bosch Sensortec GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * Neither the name of the copyright holder nor the names of the + * 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 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 + * + * The information provided is believed to be accurate and reliable. + * The copyright holder 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 the copyright holder. + * + * @file bsec.cpp + * @date 31 Jan 2018 + * @version 1.0 + * + */ + +#include "bsec.h" + +TwoWire *Bsec::wireObj = NULL; +SPIClass *Bsec::spiObj = NULL; + +/** + * @brief Constructor + */ +Bsec::Bsec() { + nextCall = 0; + version.major = 0; + version.minor = 0; + version.major_bugfix = 0; + version.minor_bugfix = 0; + millisOverflowCounter = 0; + lastTime = 0; + bme680Status = BME680_OK; + outputTimestamp = 0; + _tempOffset = 0.0f; + status = BSEC_OK; + zeroOutputs(); +} + +/** + * @brief Function to initialize the BSEC library and the BME680 sensor + */ +void Bsec::begin(uint8_t devId, enum bme680_intf intf, bme680_com_fptr_t read, + bme680_com_fptr_t write, bme680_delay_fptr_t idleTask) { + _bme680.dev_id = devId; + _bme680.intf = intf; + _bme680.read = read; + _bme680.write = write; + _bme680.delay_ms = idleTask; + _bme680.amb_temp = 25; + _bme680.power_mode = BME680_FORCED_MODE; + + beginCommon(); +} + +/** + * @brief Function to initialize the BSEC library and the BME680 sensor + */ +void Bsec::begin(uint8_t i2cAddr, TwoWire &i2c) { + _bme680.dev_id = i2cAddr; + _bme680.intf = BME680_I2C_INTF; + _bme680.read = Bsec::i2cRead; + _bme680.write = Bsec::i2cWrite; + _bme680.delay_ms = Bsec::delay_ms; + _bme680.amb_temp = 25; + _bme680.power_mode = BME680_FORCED_MODE; + + Bsec::wireObj = &i2c; + Bsec::wireObj->begin(); + + beginCommon(); +} + +/** + * @brief Function to initialize the BSEC library and the BME680 sensor + */ +void Bsec::begin(uint8_t chipSelect, SPIClass &spi) { + _bme680.dev_id = chipSelect; + _bme680.intf = BME680_SPI_INTF; + _bme680.read = Bsec::spiTransfer; + _bme680.write = Bsec::spiTransfer; + _bme680.delay_ms = Bsec::delay_ms; + _bme680.amb_temp = 25; + _bme680.power_mode = BME680_FORCED_MODE; + + pinMode(chipSelect, OUTPUT); + digitalWrite(chipSelect, HIGH); + Bsec::spiObj = &spi; + Bsec::spiObj->begin(); + + beginCommon(); +} + +/** + * @brief Common code for the begin function + */ +void Bsec::beginCommon(void) { + status = bsec_init(); + + getVersion(); + + bme680Status = bme680_init(&_bme680); +} + +/** + * @brief Function that sets the desired sensors and the sample rates + */ +void Bsec::updateSubscription(bsec_virtual_sensor_t sensorList[], + uint8_t nSensors, float sampleRate) { + bsec_sensor_configuration_t virtualSensors[BSEC_NUMBER_OUTPUTS], + sensorSettings[BSEC_MAX_PHYSICAL_SENSOR]; + uint8_t nVirtualSensors = 0, nSensorSettings = BSEC_MAX_PHYSICAL_SENSOR; + + for (uint8_t i = 0; i < nSensors; i++) { + virtualSensors[nVirtualSensors].sensor_id = sensorList[i]; + virtualSensors[nVirtualSensors].sample_rate = sampleRate; + nVirtualSensors++; + } + + status = bsec_update_subscription(virtualSensors, nVirtualSensors, + sensorSettings, &nSensorSettings); + return; +} + +/** + * @brief Callback from the user to trigger reading of data from the BME680, + * process and store outputs + */ +bool Bsec::run(void) { + bool newData = false; + /* Check if the time has arrived to call do_steps() */ + int64_t callTimeMs = getTimeMs(); + + if (callTimeMs >= nextCall) { + + bsec_bme_settings_t bme680Settings; + + int64_t callTimeNs = callTimeMs * INT64_C(1000000); + + status = bsec_sensor_control(callTimeNs, &bme680Settings); + if (status < BSEC_OK) + return false; + + nextCall = + bme680Settings.next_call / INT64_C(1000000); // Convert from ns to ms + + bme680Status = setBme680Config(bme680Settings); + if (bme680Status != BME680_OK) { + return false; + } + + bme680Status = bme680_set_sensor_mode(&_bme680); + if (bme680Status != BME680_OK) { + return false; + } + + /* Wait for measurement to complete */ + uint16_t meas_dur = 0; + + bme680_get_profile_dur(&meas_dur, &_bme680); + delay_ms(meas_dur); + + newData = readProcessData(callTimeNs, bme680Settings); + } + + return newData; +} + +/** + * @brief Function to get the state of the algorithm to save to non-volatile + * memory + */ +void Bsec::getState(uint8_t *state) { + uint8_t workBuffer[BSEC_MAX_STATE_BLOB_SIZE]; + uint32_t n_serialized_state = BSEC_MAX_STATE_BLOB_SIZE; + status = bsec_get_state(0, state, BSEC_MAX_STATE_BLOB_SIZE, workBuffer, + BSEC_MAX_STATE_BLOB_SIZE, &n_serialized_state); +} + +/** + * @brief Function to set the state of the algorithm from non-volatile memory + */ +void Bsec::setState(uint8_t *state) { + uint8_t workBuffer[BSEC_MAX_STATE_BLOB_SIZE]; + + status = bsec_set_state(state, BSEC_MAX_STATE_BLOB_SIZE, workBuffer, + BSEC_MAX_STATE_BLOB_SIZE); +} + +/** + * @brief Function to set the configuration of the algorithm from memory + */ +void Bsec::setConfig(const uint8_t *state) { + uint8_t workBuffer[BSEC_MAX_PROPERTY_BLOB_SIZE]; + + status = bsec_set_configuration(state, BSEC_MAX_PROPERTY_BLOB_SIZE, + workBuffer, sizeof(workBuffer)); +} + +/* Private functions */ + +/** + * @brief Get the version of the BSEC library + */ +void Bsec::getVersion(void) { bsec_get_version(&version); } + +/** + * @brief Read data from the BME680 and process it + */ +bool Bsec::readProcessData(int64_t currTimeNs, + bsec_bme_settings_t bme680Settings) { + bme680Status = bme680_get_sensor_data(&_data, &_bme680); + if (bme680Status != BME680_OK) { + return false; + } + + bsec_input_t inputs[BSEC_MAX_PHYSICAL_SENSOR]; // Temp, Pres, Hum & Gas + uint8_t nInputs = 0, nOutputs = 0; + + if (_data.status & BME680_NEW_DATA_MSK) { + if (bme680Settings.process_data & BSEC_PROCESS_TEMPERATURE) { + inputs[nInputs].sensor_id = BSEC_INPUT_TEMPERATURE; +#ifdef BME680_FLOAT_POINT_COMPENSATION + inputs[nInputs].signal = _data.temperature; +#else + inputs[nInputs].signal = _data.temperature / 100.0f; +#endif + inputs[nInputs].time_stamp = currTimeNs; + nInputs++; + /* Temperature offset from the real temperature due to external heat + * sources */ + inputs[nInputs].sensor_id = BSEC_INPUT_HEATSOURCE; + inputs[nInputs].signal = _tempOffset; + inputs[nInputs].time_stamp = currTimeNs; + nInputs++; + } + if (bme680Settings.process_data & BSEC_PROCESS_HUMIDITY) { + inputs[nInputs].sensor_id = BSEC_INPUT_HUMIDITY; +#ifdef BME680_FLOAT_POINT_COMPENSATION + inputs[nInputs].signal = _data.humidity; +#else + inputs[nInputs].signal = _data.humidity / 1000.0f; +#endif + inputs[nInputs].time_stamp = currTimeNs; + nInputs++; + } + if (bme680Settings.process_data & BSEC_PROCESS_PRESSURE) { + inputs[nInputs].sensor_id = BSEC_INPUT_PRESSURE; + inputs[nInputs].signal = _data.pressure; + inputs[nInputs].time_stamp = currTimeNs; + nInputs++; + } + if (bme680Settings.process_data & BSEC_PROCESS_GAS) { + inputs[nInputs].sensor_id = BSEC_INPUT_GASRESISTOR; + inputs[nInputs].signal = _data.gas_resistance; + inputs[nInputs].time_stamp = currTimeNs; + nInputs++; + } + } + + if (nInputs > 0) { + nOutputs = BSEC_NUMBER_OUTPUTS; + bsec_output_t _outputs[BSEC_NUMBER_OUTPUTS]; + + status = bsec_do_steps(inputs, nInputs, _outputs, &nOutputs); + if (status != BSEC_OK) + return false; + + zeroOutputs(); + + if (nOutputs > 0) { + 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: + iaqEstimate = _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; + } + } + return true; + } + } + + return false; +} + +/** + * @brief Set the BME680 sensor's configuration + */ +int8_t Bsec::setBme680Config(bsec_bme_settings_t bme680Settings) { + _bme680.gas_sett.run_gas = bme680Settings.run_gas; + _bme680.tph_sett.os_hum = bme680Settings.humidity_oversampling; + _bme680.tph_sett.os_temp = bme680Settings.temperature_oversampling; + _bme680.tph_sett.os_pres = bme680Settings.pressure_oversampling; + _bme680.gas_sett.heatr_temp = bme680Settings.heater_temperature; + _bme680.gas_sett.heatr_dur = bme680Settings.heating_duration; + uint16_t desired_settings = BME680_OST_SEL | BME680_OSP_SEL | BME680_OSH_SEL | + BME680_FILTER_SEL | BME680_GAS_SENSOR_SEL; + return bme680_set_sensor_settings(desired_settings, &_bme680); +} + +/** + * @brief Function to zero the outputs + */ +void Bsec::zeroOutputs(void) { + temperature = 0.0f; + pressure = 0.0f; + humidity = 0.0f; + gasResistance = 0.0f; + rawTemperature = 0.0f; + rawHumidity = 0.0f; + stabStatus = 0.0f; + runInStatus = 0.0f; + iaqEstimate = 0.0f; + iaqAccuracy = 0; + staticIaq = 0.0f; + staticIaqAccuracy = 0; + co2Equivalent = 0.0f; + co2Accuracy = 0; + breathVocEquivalent = 0.0f; + breathVocAccuracy = 0; + compGasValue = 0.0f; + compGasAccuracy = 0; + gasPercentage = 0.0f; + gasPercentageAcccuracy = 0; +} + +/** + * @brief Function to calculate an int64_t timestamp in milliseconds + */ +int64_t Bsec::getTimeMs(void) { + int64_t timeMs = millis(); + + if (lastTime > timeMs) { // An overflow occured + lastTime = timeMs; + millisOverflowCounter++; + } + + return timeMs + (millisOverflowCounter * 0xFFFFFFFF); +} + +/** + @brief Task that delays for a ms period of time + */ +void Bsec::delay_ms(uint32_t period) { + // Wait for a period amount of ms + // The system may simply idle, sleep or even perform background tasks + delay(period); +} + +/** + @brief Callback function for reading registers over I2C + */ +int8_t Bsec::i2cRead(uint8_t devId, uint8_t regAddr, uint8_t *regData, + uint16_t length) { + uint16_t i; + int8_t rslt = 0; + if (Bsec::wireObj) { + Bsec::wireObj->beginTransmission(devId); + Bsec::wireObj->write(regAddr); + rslt = Bsec::wireObj->endTransmission(); + Bsec::wireObj->requestFrom((int)devId, (int)length); + for (i = 0; (i < length) && Bsec::wireObj->available(); i++) { + regData[i] = Bsec::wireObj->read(); + } + } else { + rslt = -1; + } + return rslt; +} + +/** + * @brief Callback function for writing registers over I2C + */ +int8_t Bsec::i2cWrite(uint8_t devId, uint8_t regAddr, uint8_t *regData, + uint16_t length) { + uint16_t i; + int8_t rslt = 0; + if (Bsec::wireObj) { + Bsec::wireObj->beginTransmission(devId); + Bsec::wireObj->write(regAddr); + for (i = 0; i < length; i++) { + Bsec::wireObj->write(regData[i]); + } + rslt = Bsec::wireObj->endTransmission(); + } else { + rslt = -1; + } + + return rslt; +} + +/** + * @brief Callback function for reading and writing registers over SPI + */ +int8_t Bsec::spiTransfer(uint8_t devId, uint8_t regAddr, uint8_t *regData, + uint16_t length) { + int8_t rslt = 0; + if (Bsec::spiObj) { + Bsec::spiObj->beginTransaction( + SPISettings(4000000, MSBFIRST, SPI_MODE0)); // Can be upto 10MHz + + digitalWrite(devId, LOW); + + Bsec::spiObj->transfer( + regAddr); // Write the register address, ignore the return + for (uint16_t i = 0; i < length; i++) + regData[i] = Bsec::spiObj->transfer(regData[i]); + + digitalWrite(devId, HIGH); + Bsec::spiObj->endTransaction(); + } else { + rslt = -1; + } + + return rslt; + ; +} \ No newline at end of file diff --git a/lib/Bosch-BSEC/src/bsec.h b/lib/Bosch-BSEC/src/bsec.h new file mode 100644 index 00000000..bf2c0a61 --- /dev/null +++ b/lib/Bosch-BSEC/src/bsec.h @@ -0,0 +1,230 @@ +/** + * Copyright (C) 2017 - 2018 Bosch Sensortec GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * Neither the name of the copyright holder nor the names of the + * 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 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 + * + * The information provided is believed to be accurate and reliable. + * The copyright holder 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 the copyright holder. + * + * @file bsec.h + * @date 31 Jan 2018 + * @version 1.0 + * + */ + +#ifndef BSEC_CLASS_H +#define BSEC_CLASS_H + +/* Includes */ +#include "Arduino.h" +#include "Wire.h" +#include "SPI.h" +#include "inc/bsec_datatypes.h" +#include "inc/bsec_interface.h" +#include "bme680/bme680.h" + +/* BSEC class definition */ +class Bsec +{ +public: + /* Public variables */ + bsec_version_t version; // Stores the version of the BSEC algorithm + int64_t nextCall; // Stores the time when the algorithm has to be called next in ms + int8_t bme680Status; // Placeholder for the BME680 driver's error codes + bsec_library_return_t status; + float iaqEstimate, rawTemperature, pressure, rawHumidity, gasResistance, stabStatus, runInStatus, temperature, humidity, + staticIaq, co2Equivalent, breathVocEquivalent, compGasValue, gasPercentage; + uint8_t iaqAccuracy, staticIaqAccuracy, co2Accuracy, breathVocAccuracy, compGasAccuracy, gasPercentageAcccuracy; + int64_t outputTimestamp; // Timestamp in ms of the output + static TwoWire *wireObj; + static SPIClass *spiObj; + + /* Public APIs */ + /** + * @brief Constructor + */ + Bsec(); + + /** + * @brief Function to initialize the BSEC library and the BME680 sensor + * @param devId : Device identifier parameter for the read/write interface functions + * @param intf : Physical communication interface + * @param read : Pointer to the read function + * @param write : Pointer to the write function + * @param idleTask : Pointer to the idling task + */ + void begin(uint8_t devId, enum bme680_intf intf, bme680_com_fptr_t read, bme680_com_fptr_t write, bme680_delay_fptr_t idleTask); + + /** + * @brief Function to initialize the BSEC library and the BME680 sensor + * @param i2cAddr : I2C address + * @param i2c : Pointer to the TwoWire object + */ + void begin(uint8_t i2cAddr, TwoWire &i2c); + + /** + * @brief Function to initialize the BSEC library and the BME680 sensor + * @param chipSelect : SPI chip select + * @param spi : Pointer to the SPIClass object + */ + void begin(uint8_t chipSelect, SPIClass &spi); + + /** + * @brief Function that sets the desired sensors and the sample rates + * @param sensorList : The list of output sensors + * @param nSensors : Number of outputs requested + * @param sampleRate : The sample rate of requested sensors + */ + void updateSubscription(bsec_virtual_sensor_t sensorList[], uint8_t nSensors, float sampleRate = BSEC_SAMPLE_RATE_ULP); + + /** + * @brief Callback from the user to trigger reading of data from the BME680, process and store outputs + * @return true if there are new outputs. false otherwise + */ + bool run(void); + + /** + * @brief Function to get the state of the algorithm to save to non-volatile memory + * @param state : Pointer to a memory location that contains the state + */ + void getState(uint8_t *state); + + /** + * @brief Function to set the state of the algorithm from non-volatile memory + * @param state : Pointer to a memory location that contains the state + */ + void setState(uint8_t *state); + + /** + * @brief Function to set the configuration of the algorithm from memory + * @param state : Pointer to a memory location that contains the configuration + */ + void setConfig(const uint8_t *config); + + /** + * @brief Function to set the temperature offset + * @param tempOffset : Temperature offset in degree Celsius + */ + void setTemperatureOffset(float tempOffset) + { + _tempOffset = tempOffset; + } + + + /** + * @brief Function to calculate an int64_t timestamp in milliseconds + */ + int64_t getTimeMs(void); + + /** + * @brief Task that delays for a ms period of time + * @param period : Period of time in ms + */ + static void delay_ms(uint32_t period); + + /** + * @brief Callback function for reading registers over I2C + * @param devId : Library agnostic parameter to identify the device to communicate with + * @param regAddr : Register address + * @param regData : Pointer to the array containing the data to be read + * @param length : Length of the array of data + * @return Zero for success, non-zero otherwise + */ + static int8_t i2cRead(uint8_t devId, uint8_t regAddr, uint8_t *regData, uint16_t length); + + /** + * @brief Callback function for writing registers over I2C + * @param devId : Library agnostic parameter to identify the device to communicate with + * @param regAddr : Register address + * @param regData : Pointer to the array containing the data to be written + * @param length : Length of the array of data + * @return Zero for success, non-zero otherwise + */ + static int8_t i2cWrite(uint8_t devId, uint8_t regAddr, uint8_t *regData, uint16_t length); + + /** + * @brief Callback function for reading and writing registers over SPI + * @param devId : Library agnostic parameter to identify the device to communicate with + * @param regAddr : Register address + * @param regData : Pointer to the array containing the data to be read or written + * @param length : Length of the array of data + * @return Zero for success, non-zero otherwise + */ + static int8_t spiTransfer(uint8_t devId, uint8_t regAddr, uint8_t *regData, uint16_t length); + +private: + /* Private variables */ + struct bme680_dev _bme680; + struct bme680_field_data _data; + float _tempOffset; + // Global variables to help create a millisecond timestamp that doesn't overflow every 51 days. + // If it overflows, it will have a negative value. Something that should never happen. + uint32_t millisOverflowCounter; + uint32_t lastTime; + + /* Private APIs */ + /** + * @brief Get the version of the BSEC library + */ + void getVersion(void); + + /** + * @brief Read data from the BME680 and process it + * @param currTimeNs: Current time in ns + * @param bme680Settings: BME680 sensor's settings + * @return true if there are new outputs. false otherwise + */ + bool readProcessData(int64_t currTimeNs, bsec_bme_settings_t bme680Settings); + + /** + * @brief Set the BME680 sensor's configuration + * @param bme680Settings: Settings to configure the BME680 + * @return BME680 return code. BME680_OK for success, failure otherwise + */ + int8_t setBme680Config(bsec_bme_settings_t bme680Settings); + + /** + * @brief Common code for the begin function + */ + void beginCommon(void); + + /** + * @brief Function to zero the outputs + */ + void zeroOutputs(void); +}; + +#endif diff --git a/lib/Bosch-BSEC/libalgobsec.a b/lib/Bosch-BSEC/src/esp32/libalgobsec.a similarity index 100% rename from lib/Bosch-BSEC/libalgobsec.a rename to lib/Bosch-BSEC/src/esp32/libalgobsec.a diff --git a/lib/Bosch-BSEC/bsec_datatypes.h b/lib/Bosch-BSEC/src/inc/bsec_datatypes.h similarity index 100% rename from lib/Bosch-BSEC/bsec_datatypes.h rename to lib/Bosch-BSEC/src/inc/bsec_datatypes.h diff --git a/lib/Bosch-BSEC/bsec_interface.h b/lib/Bosch-BSEC/src/inc/bsec_interface.h similarity index 100% rename from lib/Bosch-BSEC/bsec_interface.h rename to lib/Bosch-BSEC/src/inc/bsec_interface.h diff --git a/platformio.ini b/platformio.ini index f8aa7392..ecbbf0de 100644 --- a/platformio.ini +++ b/platformio.ini @@ -6,7 +6,7 @@ ; ---> SELECT TARGET PLATFORM HERE! <--- [platformio] -env_default = generic +;env_default = generic ;env_default = ebox ;env_default = eboxtube ;env_default = heltec @@ -16,7 +16,7 @@ env_default = generic ;env_default = ttgov21old ;env_default = ttgov21new ;env_default = ttgobeam_old -;env_default = ttgobeam_new +env_default = ttgobeam_new ;env_default = lopy ;env_default = lopy4 ;env_default = fipy @@ -30,10 +30,10 @@ description = Paxcounter is a proof-of-concept ESP32 device for metering passeng [common] ; for release_version use max. 10 chars total, use any decimal format like "a.b.c" -release_version = 1.7.01 +release_version = 1.7.03 ; DEBUG LEVEL: For production run set to 0, otherwise device will leak RAM while running! ; 0=None, 1=Error, 2=Warn, 3=Info, 4=Debug, 5=Verbose -debug_level = 0 +debug_level = 3 ; UPLOAD MODE: select esptool to flash via USB/UART, select custom to upload to cloud for OTA upload_protocol = esptool ;upload_protocol = custom @@ -70,8 +70,8 @@ build_flags_basic = '-DBINTRAY_PACKAGE="${PIOENV}"' '-DPROGVERSION="${common.release_version}"' build_flags_sensors = - -Llib/Bosch-BSEC - -llibalgobsec.a + -Llib/Bosch-BSEC/src/esp32/ + -lalgobsec build_flags_all = ${common.build_flags_basic} ${common.build_flags_sensors} diff --git a/src/bme680mems.cpp b/src/bme680mems.cpp index 08795f31..58570dda 100644 --- a/src/bme680mems.cpp +++ b/src/bme680mems.cpp @@ -8,82 +8,85 @@ static const char TAG[] = "main"; bmeStatus_t bme_status; TaskHandle_t BmeTask; -float bme_offset = (float)BME_TEMP_OFFSET; +Bsec iaqSensor; -// --- Bosch BSEC library configuration --- -// 3,3V supply voltage; 3s max time between sensor_control calls; 4 days -// calibration. Change this const if not applicable for your application (see -// BME680 datasheet) -const uint8_t bsec_config_iaq[454] = { - 1, 7, 4, 1, 61, 0, 0, 0, 0, 0, 0, 0, 174, 1, 0, - 0, 48, 0, 1, 0, 137, 65, 0, 63, 205, 204, 204, 62, 0, 0, - 64, 63, 205, 204, 204, 62, 0, 0, 225, 68, 0, 192, 168, 71, 64, - 49, 119, 76, 0, 0, 0, 0, 0, 80, 5, 95, 0, 0, 0, 0, - 0, 0, 0, 0, 28, 0, 2, 0, 0, 244, 1, 225, 0, 25, 0, - 0, 128, 64, 0, 0, 32, 65, 144, 1, 0, 0, 112, 65, 0, 0, - 0, 63, 16, 0, 3, 0, 10, 215, 163, 60, 10, 215, 35, 59, 10, - 215, 35, 59, 9, 0, 5, 0, 0, 0, 0, 0, 1, 88, 0, 9, - 0, 229, 208, 34, 62, 0, 0, 0, 0, 0, 0, 0, 0, 218, 27, - 156, 62, 225, 11, 67, 64, 0, 0, 160, 64, 0, 0, 0, 0, 0, - 0, 0, 0, 94, 75, 72, 189, 93, 254, 159, 64, 66, 62, 160, 191, - 0, 0, 0, 0, 0, 0, 0, 0, 33, 31, 180, 190, 138, 176, 97, - 64, 65, 241, 99, 190, 0, 0, 0, 0, 0, 0, 0, 0, 167, 121, - 71, 61, 165, 189, 41, 192, 184, 30, 189, 64, 12, 0, 10, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 229, 0, 254, 0, 2, 1, 5, 48, - 117, 100, 0, 44, 1, 112, 23, 151, 7, 132, 3, 197, 0, 92, 4, - 144, 1, 64, 1, 64, 1, 144, 1, 48, 117, 48, 117, 48, 117, 48, - 117, 100, 0, 100, 0, 100, 0, 48, 117, 48, 117, 48, 117, 100, 0, - 100, 0, 48, 117, 48, 117, 100, 0, 100, 0, 100, 0, 100, 0, 48, - 117, 48, 117, 48, 117, 100, 0, 100, 0, 100, 0, 48, 117, 48, 117, - 100, 0, 100, 0, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, - 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, - 44, 1, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, - 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, - 112, 23, 112, 23, 112, 23, 112, 23, 112, 23, 112, 23, 112, 23, 112, - 23, 112, 23, 112, 23, 112, 23, 112, 23, 112, 23, 112, 23, 255, 255, - 255, 255, 255, 255, 255, 255, 220, 5, 220, 5, 220, 5, 255, 255, 255, - 255, 255, 255, 220, 5, 220, 5, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 44, 1, 0, 0, 0, 0, - 239, 79, 0, 0}; +bsec_virtual_sensor_t sensorList[10] = { + BSEC_OUTPUT_RAW_TEMPERATURE, + BSEC_OUTPUT_RAW_PRESSURE, + BSEC_OUTPUT_RAW_HUMIDITY, + BSEC_OUTPUT_RAW_GAS, + BSEC_OUTPUT_IAQ, + BSEC_OUTPUT_STATIC_IAQ, + BSEC_OUTPUT_CO2_EQUIVALENT, + BSEC_OUTPUT_BREATH_VOC_EQUIVALENT, + BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE, + BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY, +}; // initialize BME680 sensor int bme_init(void) { - // struct bme680_dev gas_sensor; - Wire.begin(HAS_BME, 400000); // I2C connect to BME680 sensor with 400 KHz + // block i2c bus access + if (xSemaphoreTake(I2Caccess, (DISPLAYREFRESH_MS / portTICK_PERIOD_MS)) == + pdTRUE) { - // Call to the function which initializes the BSEC library - // Switch on low-power mode and provide no temperature offset + Wire.begin(HAS_BME); + iaqSensor.begin(BME_ADDR, Wire); - return_values_init ret = - bsec_iot_init(BSEC_SAMPLE_RATE_LP, bme_offset, i2c_write, i2c_read, - user_delay_ms, state_load, config_load); + ESP_LOGI(TAG, "BSEC v%d.%d.%d.%d", iaqSensor.version.major, + iaqSensor.version.minor, iaqSensor.version.major_bugfix, + iaqSensor.version.minor_bugfix); + + iaqSensor.setConfig(bsec_config_iaq); + + if (checkIaqSensorStatus()) + ESP_LOGI(TAG, "BME680 sensor found and initialized"); + else { + ESP_LOGE(TAG, "BME680 sensor not found"); + return 1; + } + + iaqSensor.setTemperatureOffset((float)BME_TEMP_OFFSET); + iaqSensor.updateSubscription(sensorList, 10, BSEC_SAMPLE_RATE_LP); + + if (checkIaqSensorStatus()) + ESP_LOGI(TAG, "BSEC subscription succesful"); + else { + ESP_LOGE(TAG, "BSEC subscription error"); + return 1; + } + + xSemaphoreGive(I2Caccess); // release i2c bus access - if ((int)ret.bme680_status) { - ESP_LOGE(TAG, "Could not initialize BME680, error %d", - (int)ret.bme680_status); - } else if ((int)ret.bsec_status) { - ESP_LOGE(TAG, "Could not initialize BSEC library, error %d", - (int)ret.bsec_status); } else { - ESP_LOGI(TAG, "BME680 sensor found and initialized"); + ESP_LOGE(TAG, "I2c bus busy - BME680 initialization error"); return 1; } - return 0; -} -void output_ready(int64_t timestamp, float iaq, uint8_t iaq_accuracy, - float temperature, float humidity, float pressure, - float raw_temperature, float raw_humidity, float gas, - bsec_library_return_t bsec_status, float static_iaq, - float co2_equivalent, float breath_voc_equivalent) { +} // bme_init() - bme_status.temperature = temperature; - bme_status.humidity = humidity; - bme_status.pressure = (pressure / 100.0); // conversion Pa -> hPa - bme_status.iaq = iaq; -} +// Helper function definitions +int checkIaqSensorStatus(void) { + int rslt = 1; // true = 1 = no error, false = 0 = error + + if (iaqSensor.status != BSEC_OK) { + rslt = 0; + if (iaqSensor.status < BSEC_OK) + ESP_LOGE(TAG, "BSEC error %d", iaqSensor.status); + else + ESP_LOGW(TAG, "BSEC warning %d", iaqSensor.status); + } + + if (iaqSensor.bme680Status != BME680_OK) { + rslt = 0; + if (iaqSensor.bme680Status < BME680_OK) + ESP_LOGE(TAG, "BME680 error %d", iaqSensor.bme680Status); + else + ESP_LOGW(TAG, "BME680 warning %d", iaqSensor.bme680Status); + } + + return rslt; +} // checkIaqSensorStatus() // loop function which reads and processes data based on sensor settings void bme_loop(void *pvParameters) { @@ -91,84 +94,31 @@ void bme_loop(void *pvParameters) { configASSERT(((uint32_t)pvParameters) == 1); // FreeRTOS check #ifdef HAS_BME - // State is saved every 10.000 samples, which means every 10.000 * 3 secs = - // 500 minutes - bsec_iot_loop(user_delay_ms, get_timestamp_us, output_ready, state_save, - 10000); + while (checkIaqSensorStatus()) { + + // block i2c bus access + if (xSemaphoreTake(I2Caccess, (DISPLAYREFRESH_MS / portTICK_PERIOD_MS)) == + pdTRUE) { + + if (iaqSensor.run()) { // If new data is available + bme_status.raw_temperature = iaqSensor.rawTemperature; + bme_status.raw_humidity = iaqSensor.rawHumidity; + bme_status.temperature = iaqSensor.temperature; + bme_status.humidity = iaqSensor.humidity; + bme_status.pressure = + (iaqSensor.pressure / 100.0); // conversion Pa -> hPa + bme_status.iaq = iaqSensor.iaqEstimate; + bme_status.iaq_accuracy = iaqSensor.iaqAccuracy; + bme_status.gas = iaqSensor.gasResistance; + } + + xSemaphoreGive(I2Caccess); // release i2c bus access + } + } #endif + ESP_LOGE(TAG, "BME task ended"); vTaskDelete(BmeTask); // should never be reached + } // bme_loop() -int8_t i2c_read(uint8_t dev_id, uint8_t reg_addr, uint8_t *reg_data, - uint16_t len) { - int8_t rslt = 0; - Wire.beginTransmission(dev_id); - Wire.write(reg_addr); - rslt = Wire.endTransmission(false); - - Wire.requestFrom((int)dev_id, (int)len); - for (uint16_t i = 0; (i < len) && Wire.available(); i++) { - reg_data[i] = Wire.read(); - } - // return 0 for success, non-zero for failure - return rslt; -} - -int8_t i2c_write(uint8_t dev_id, uint8_t reg_addr, uint8_t *reg_data, - uint16_t len) { - Wire.beginTransmission(dev_id); - Wire.write(reg_addr); - for (uint16_t i = 0; i < len; i++) { - Wire.write(reg_data[i]); - } - // return 0 for success, non-zero for failure - return Wire.endTransmission(true); -} - -/*! - * @brief Load previous library state from non-volatile memory - * - * @param[in,out] state_buffer buffer to hold the loaded state string - * @param[in] n_buffer size of the allocated state buffer - * - * @return number of bytes copied to state_buffer - */ -uint32_t state_load(uint8_t *state_buffer, uint32_t n_buffer) { - // ... - // Load a previous library state from non-volatile memory, if available. - // - // Return zero if loading was unsuccessful or no state was available, - // otherwise return length of loaded state string. - // ... - return 0; -} - -/*! - * @brief Save library state to non-volatile memory - * - * @param[in] state_buffer buffer holding the state to be stored - * @param[in] length length of the state string to be stored - * - * @return none - */ -void state_save(const uint8_t *state_buffer, uint32_t length) { - // ... - // Save the string some form of non-volatile memory, if possible. - // ... -} - -uint32_t config_load(uint8_t *config_buffer, uint32_t n_buffer) { - - // Load a library config from non-volatile memory, if available. - // Return zero if loading was unsuccessful or no config was available, - // otherwise return length of loaded config string. - - memcpy(config_buffer, bsec_config_iaq, sizeof(bsec_config_iaq)); - return sizeof(bsec_config_iaq); -} - -void user_delay_ms(uint32_t period) { delay(period); } - -int64_t get_timestamp_us() { return (int64_t)millis() * 1000; } - #endif // HAS_BME \ No newline at end of file diff --git a/src/cyclic.cpp b/src/cyclic.cpp index 80e16f1f..0ce0c657 100644 --- a/src/cyclic.cpp +++ b/src/cyclic.cpp @@ -52,7 +52,12 @@ void doHousekeeping() { // read battery voltage into global variable #ifdef HAS_BATTERY_PROBE batt_voltage = read_voltage(); - ESP_LOGI(TAG, "Measured Voltage: %dmV", batt_voltage); + ESP_LOGI(TAG, "Voltage: %dmV", batt_voltage); +#endif + +// display BME sensor data if present +#ifdef HAS_BME + ESP_LOGI(TAG, "BME680 Temp: %.2f°C | IAQ: %.2f", bme_status.temperature, bme_status.iaq); #endif // check free heap memory diff --git a/src/display.cpp b/src/display.cpp index 7427562d..861328fd 100644 --- a/src/display.cpp +++ b/src/display.cpp @@ -98,96 +98,104 @@ void init_display(const char *Productname, const char *Version) { void refreshtheDisplay() { - // set display on/off according to current device configuration - if (DisplayState != cfg.screenon) { - DisplayState = cfg.screenon; - u8x8.setPowerSave(!cfg.screenon); - } + // block i2c bus access + if (xSemaphoreTake(I2Caccess, (DISPLAYREFRESH_MS / portTICK_PERIOD_MS)) == + pdTRUE) { - // if display is switched off we don't refresh it and save time - if (!DisplayState) - return; + // set display on/off according to current device configuration + if (DisplayState != cfg.screenon) { + DisplayState = cfg.screenon; + u8x8.setPowerSave(!cfg.screenon); + } - uint8_t msgWaiting; - char buff[16]; // 16 chars line buffer + // if display is switched off we don't refresh it and save time + if (!DisplayState) + return; - // update counter (lines 0-1) - snprintf( - buff, sizeof(buff), "PAX:%-4d", - (int)macs.size()); // convert 16-bit MAC counter to decimal counter value - u8x8.draw2x2String(0, 0, - buff); // display number on unique macs total Wifi + BLE + uint8_t msgWaiting; + char buff[16]; // 16 chars line buffer + + // update counter (lines 0-1) + snprintf( + buff, sizeof(buff), "PAX:%-4d", + (int) + macs.size()); // convert 16-bit MAC counter to decimal counter value + u8x8.draw2x2String(0, 0, + buff); // display number on unique macs total Wifi + BLE // update Battery status (line 2) #ifdef HAS_BATTERY_PROBE - u8x8.setCursor(0, 2); - u8x8.printf("B:%.1fV", batt_voltage / 1000.0); + u8x8.setCursor(0, 2); + u8x8.printf("B:%.1fV", batt_voltage / 1000.0); #endif // update GPS status (line 2) #ifdef HAS_GPS - u8x8.setCursor(9, 2); - if (!gps.location.isValid()) // if no fix then display Sats value inverse - { - u8x8.setInverseFont(1); - u8x8.printf("Sats:%.2d", gps.satellites.value()); - u8x8.setInverseFont(0); - } else - u8x8.printf("Sats:%.2d", gps.satellites.value()); + u8x8.setCursor(9, 2); + if (!gps.location.isValid()) // if no fix then display Sats value inverse + { + u8x8.setInverseFont(1); + u8x8.printf("Sats:%.2d", gps.satellites.value()); + u8x8.setInverseFont(0); + } else + u8x8.printf("Sats:%.2d", gps.satellites.value()); #endif - // update bluetooth counter + LoRa SF (line 3) + // update bluetooth counter + LoRa SF (line 3) #ifdef BLECOUNTER - u8x8.setCursor(0, 3); - if (cfg.blescan) - u8x8.printf("BLTH:%-4d", macs_ble); - else - u8x8.printf("%s", "BLTH:off"); + u8x8.setCursor(0, 3); + if (cfg.blescan) + u8x8.printf("BLTH:%-4d", macs_ble); + else + u8x8.printf("%s", "BLTH:off"); #endif #ifdef HAS_LORA - u8x8.setCursor(11, 3); - u8x8.printf("SF:"); - if (cfg.adrmode) // if ADR=on then display SF value inverse - u8x8.setInverseFont(1); - u8x8.printf("%c%c", lora_datarate[LMIC.datarate * 2], - lora_datarate[LMIC.datarate * 2 + 1]); - if (cfg.adrmode) // switch off inverse if it was turned on - u8x8.setInverseFont(0); + u8x8.setCursor(11, 3); + u8x8.printf("SF:"); + if (cfg.adrmode) // if ADR=on then display SF value inverse + u8x8.setInverseFont(1); + u8x8.printf("%c%c", lora_datarate[LMIC.datarate * 2], + lora_datarate[LMIC.datarate * 2 + 1]); + if (cfg.adrmode) // switch off inverse if it was turned on + u8x8.setInverseFont(0); #endif // HAS_LORA - // update wifi counter + channel display (line 4) - u8x8.setCursor(0, 4); - u8x8.printf("WIFI:%-4d", macs_wifi); - u8x8.setCursor(11, 4); - u8x8.printf("ch:%02d", channel); + // update wifi counter + channel display (line 4) + u8x8.setCursor(0, 4); + u8x8.printf("WIFI:%-4d", macs_wifi); + u8x8.setCursor(11, 4); + u8x8.printf("ch:%02d", channel); - // update RSSI limiter status & free memory display (line 5) - u8x8.setCursor(0, 5); - u8x8.printf(!cfg.rssilimit ? "RLIM:off " : "RLIM:%-4d", cfg.rssilimit); - u8x8.setCursor(10, 5); - u8x8.printf("%4dKB", getFreeRAM() / 1024); + // update RSSI limiter status & free memory display (line 5) + u8x8.setCursor(0, 5); + u8x8.printf(!cfg.rssilimit ? "RLIM:off " : "RLIM:%-4d", cfg.rssilimit); + u8x8.setCursor(10, 5); + u8x8.printf("%4dKB", getFreeRAM() / 1024); #ifdef HAS_LORA - // update LoRa status display (line 6) - u8x8.setCursor(0, 6); - u8x8.printf("%-16s", display_line6); + // update LoRa status display (line 6) + u8x8.setCursor(0, 6); + u8x8.printf("%-16s", display_line6); - // update LMiC event display (line 7) - u8x8.setCursor(0, 7); - u8x8.printf("%-14s", display_line7); + // update LMiC event display (line 7) + u8x8.setCursor(0, 7); + u8x8.printf("%-14s", display_line7); - // update LoRa send queue display (line 7) - msgWaiting = uxQueueMessagesWaiting(LoraSendQueue); - if (msgWaiting) { - sprintf(buff, "%2d", msgWaiting); - u8x8.setCursor(14, 7); - u8x8.printf("%-2s", msgWaiting == SEND_QUEUE_SIZE ? "<>" : buff); - } else - u8x8.printf(" "); + // update LoRa send queue display (line 7) + msgWaiting = uxQueueMessagesWaiting(LoraSendQueue); + if (msgWaiting) { + sprintf(buff, "%2d", msgWaiting); + u8x8.setCursor(14, 7); + u8x8.printf("%-2s", msgWaiting == SEND_QUEUE_SIZE ? "<>" : buff); + } else + u8x8.printf(" "); #endif // HAS_LORA + xSemaphoreGive(I2Caccess); // release i2c bus access + } + } // refreshDisplay() #endif // HAS_DISPLAY \ No newline at end of file diff --git a/src/hal/generic.h b/src/hal/generic.h index ad88536f..c4e6deb9 100644 --- a/src/hal/generic.h +++ b/src/hal/generic.h @@ -17,8 +17,8 @@ // enable only if device has these sensors, otherwise comment these lines // BME680 sensor on I2C bus -// don't forget to connect SDIO of BME680 to GND for selecting i2c addr 0x76 #define HAS_BME GPIO_NUM_21, GPIO_NUM_22 // SDA, SCL +#define BME_ADDR BME680_I2C_ADDR_PRIMARY // connect SDIO of BME680 to GND // user defined sensors //#define HAS_SENSORS 1 // comment out if device has user defined sensors diff --git a/src/hal/octopus32.h b/src/hal/octopus32.h index a63b399b..288c286b 100644 --- a/src/hal/octopus32.h +++ b/src/hal/octopus32.h @@ -11,11 +11,9 @@ // disable brownout detection (avoid unexpected reset on some boards) #define DISABLE_BROWNOUT 1 // comment out if you want to keep brownout feature -// enable only if device has these sensors, otherwise comment these lines -// BME680 sensor on I2C bus // Octopus32 has a pre-populated BME680 on i2c addr 0x76 #define HAS_BME GPIO_NUM_23, GPIO_NUM_22 // SDA, SCL -//#define HAS_BME 0x76 +#define BME_ADDR BME680_I2C_ADDR_PRIMARY // connect SDIO of BME680 to GND // user defined sensors //#define HAS_SENSORS 1 // comment out if device has user defined sensors diff --git a/src/hal/ttgobeam_new.h b/src/hal/ttgobeam_new.h index 1a4552a9..d177e033 100644 --- a/src/hal/ttgobeam_new.h +++ b/src/hal/ttgobeam_new.h @@ -8,22 +8,21 @@ // Hardware related definitions for TTGO T-Beam board // // pinouts taken from http://tinymicros.com/wiki/TTGO_T-Beam -// + // enable only if device has these sensors, otherwise comment these lines // BME680 sensor on I2C bus -// don't forget to connect SDIO of BME680 to GND for selecting i2c addr 0x76 -// -//#define HAS_BME GPIO_NUM_21, GPIO_NUM_22 // SDA, SCL +#define HAS_BME GPIO_NUM_21, GPIO_NUM_22 // SDA, SCL +#define BME_ADDR BME680_I2C_ADDR_PRIMARY // connect SDIO of BME680 to GND #define HAS_LED GPIO_NUM_14 // on board green LED // user defined sensors //#define HAS_SENSORS 1 // comment out if device has user defined sensors -//#define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C -//#define MY_OLED_SDA (21) -//#define MY_OLED_SCL (22) -//#define MY_OLED_RST (NOT_A_PIN) +#define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C +#define MY_OLED_SDA (21) +#define MY_OLED_SCL (22) +#define MY_OLED_RST U8X8_PIN_NONE //#define DISPLAY_FLIP 1 // use if display is rotated #define HAS_LORA 1 // comment out if device shall not send data via LoRa diff --git a/src/hal/ttgobeam_old.h b/src/hal/ttgobeam_old.h index f8e530fc..f6fd4531 100644 --- a/src/hal/ttgobeam_old.h +++ b/src/hal/ttgobeam_old.h @@ -22,7 +22,7 @@ //#define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C //#define MY_OLED_SDA (21) //#define MY_OLED_SCL (22) -//#define MY_OLED_RST (NOT_A_PIN) +//#define MY_OLED_RST U8X8_PIN_NONE //#define DISPLAY_FLIP 1 // use if display is rotated #define HAS_LORA 1 // comment out if device shall not send data via LoRa diff --git a/src/main.cpp b/src/main.cpp index 37d71674..29aa32cb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -35,11 +35,14 @@ IDLE 0 0 ESP32 arduino scheduler -> runs wifi sniffer looptask 1 1 arduino core -> runs the LMIC LoRa stack irqhandler 1 1 executes tasks triggered by irq gpsloop 1 2 reads data from GPS via serial or i2c -bmeloop 1 0 reads data from BME sensor via i2c +bmeloop 1 1 reads data from BME sensor via i2c IDLE 1 0 ESP32 arduino scheduler Low priority numbers denote low priority tasks. +Tasks using i2c bus all must have same priority, because using mutex semaphore +(irqhandler, bmeloop) + ESP32 hardware timers ========================== 0 Trigger display refresh @@ -60,6 +63,7 @@ uint16_t volatile macs_total = 0, macs_wifi = 0, macs_ble = 0, hw_timer_t *channelSwitch = NULL, *sendCycle = NULL, *homeCycle = NULL, *displaytimer = NULL; // irq tasks TaskHandle_t irqHandlerTask, wifiSwitchTask; +SemaphoreHandle_t I2Caccess; // container holding unique MAC address hashes with Memory Alloctor using PSRAM, // if present @@ -78,6 +82,14 @@ void setup() { char features[100] = ""; + if (I2Caccess == NULL) // Check that semaphore has not already been created + { + I2Caccess = xSemaphoreCreateMutex(); // Create a mutex semaphore we will use + // to manage the i2c bus + if ((I2Caccess) != NULL) + xSemaphoreGive((I2Caccess)); // Flag the i2c bus available for use + } + // disable brownout detection #ifdef DISABLE_BROWNOUT // register with brownout is at address DR_REG_RTCCNTL_BASE + 0xd4 @@ -343,11 +355,11 @@ void setup() { "bmeloop", // name of task 4096, // stack size of task (void *)1, // parameter of the task - 0, // priority of the task + //0, // priority of the task + 1, // priority of the task &BmeTask, // task handle 1); // CPU core } - delay(2000); // time for initializing i2c sensor #endif // start timer triggered interrupts