/* * Copyright 2020 Daniel Friesel * * SPDX-License-Identifier: BSD-2-Clause */ #include "config.h" #ifdef CONFIG_driver_bme680_bsec_save_state #define STRINGIFY(x) #x #define TOSTRING(x) STRINGIFY(x) #define BSEC_STATE_PATH TOSTRING(CONFIG_driver_bme680_bsec_state_path) #include #endif #include "arch.h" #include "driver/gpio.h" #include "driver/stdout.h" #include "driver/uptime.h" #if defined(CONFIG_meta_driver_hardware_i2c) #include "driver/i2c.h" #elif defined(CONFIG_driver_softi2c) #include "driver/soft_i2c.h" #endif #include "driver/bme680.h" #include "driver/bme680_util.h" #include "driver/bme680-bsec-armv6/bsec_interface.h" const char* accuracy[] = {"(unreliable)", "(calibration required)", "(auto-trim in progress)", ""}; #ifdef CONFIG_driver_bme680_bsec_save_state // For (de)serialization of library state across application restarts uint8_t serialized_state[BSEC_MAX_STATE_BLOB_SIZE]; uint8_t work_buffer[BSEC_MAX_STATE_BLOB_SIZE]; #endif void loop(void) { static bsec_bme_settings_t sensor_settings; static uint16_t iteration = 0; struct bme680_field_data data; bsec_input_t bsec_inputs[BSEC_MAX_PHYSICAL_SENSOR]; bsec_output_t bsec_outputs[BSEC_NUMBER_OUTPUTS]; uint8_t num_bsec_inputs = 0; uint8_t num_bsec_outputs = BSEC_NUMBER_OUTPUTS; int64_t now = uptime.get_us() * 1000; uint32_t now_s = uptime.get_s(); /* * BSEC expects the application to observe precise timing constraints. * After each call to bsec_sensor_control, sensor_settings.next_call is set * to the nanosecond timestamp of the next call. Significant violations * cause bsec_sensor_control to return a BSEC_W_SC_CALL_TIMING_VIOLATION * warning. */ if (now < sensor_settings.next_call) { if (sensor_settings.next_call - now < 1000000000) { // less than one second -> sleep arch.delay_us((sensor_settings.next_call - now) / 1000); now = uptime.get_us() * 1000; } else { // more than one second -> next loop() call is sufficient return; } } /* * Retrieve sensor configuration from BSEC. In our case, it's fairly simple: * all virtual sensors are set to the same sample rate, so we expect * physical sensor readings to be the same for each BSEC call. However, * if we disable virtual sensors, or change their sample rate, this is * no longer the case. * Also, bsec_sensor_control controls sensor_settings.next_call, so we * need to call it anyways. */ bsec_library_return_t status = bsec_sensor_control(now, &sensor_settings); if (status < 0) { kout << "bsec_sensor_control error: " << status << endl; return; } if (status > 0) { kout << "bsec_sensor_control warning: " << status << endl; } /* * bsec_sensor_control tells us whether it needs new sensor data or * not. If so: configure the sensor as indicated and perform a measurement. */ if (sensor_settings.trigger_measurement) { bme680.tph_sett.os_hum = sensor_settings.humidity_oversampling; bme680.tph_sett.os_pres = sensor_settings.pressure_oversampling; bme680.tph_sett.os_temp = sensor_settings.temperature_oversampling; bme680.gas_sett.run_gas = sensor_settings.run_gas; bme680.gas_sett.heatr_temp = sensor_settings.heater_temperature; bme680.gas_sett.heatr_dur = sensor_settings.heating_duration; bme680.power_mode = BME680_FORCED_MODE; bme680.setSensorSettings(BME680_OST_SEL | BME680_OSP_SEL | BME680_OSH_SEL | BME680_GAS_SENSOR_SEL); bme680.setSensorMode(); /* * TODO recent versions of the bme680 open-source driver are able to * calculate the required delay. */ arch.delay_ms(250); do { arch.delay_ms(5); bme680.getSensorMode(); } while (bme680.power_mode == BME680_FORCED_MODE); if (sensor_settings.process_data) { bme680.getSensorData(&data); if (data.status & BME680_NEW_DATA_MSK) { if (sensor_settings.process_data & BSEC_PROCESS_TEMPERATURE) { bsec_inputs[num_bsec_inputs].sensor_id = BSEC_INPUT_TEMPERATURE; bsec_inputs[num_bsec_inputs].signal = data.temperature / 100.0f; bsec_inputs[num_bsec_inputs].time_stamp = now; num_bsec_inputs++; } if (sensor_settings.process_data & BSEC_PROCESS_HUMIDITY) { bsec_inputs[num_bsec_inputs].sensor_id = BSEC_INPUT_HUMIDITY; bsec_inputs[num_bsec_inputs].signal = data.humidity / 1000.0f; bsec_inputs[num_bsec_inputs].time_stamp = now; num_bsec_inputs++; } if (sensor_settings.process_data & BSEC_PROCESS_PRESSURE) { bsec_inputs[num_bsec_inputs].sensor_id = BSEC_INPUT_PRESSURE; bsec_inputs[num_bsec_inputs].signal = data.pressure; bsec_inputs[num_bsec_inputs].time_stamp = now; num_bsec_inputs++; } if (sensor_settings.process_data & BSEC_PROCESS_GAS) { bsec_inputs[num_bsec_inputs].sensor_id = BSEC_INPUT_GASRESISTOR; bsec_inputs[num_bsec_inputs].signal = data.gas_resistance; bsec_inputs[num_bsec_inputs].time_stamp = now; num_bsec_inputs++; } } } if (num_bsec_inputs > 0) { status = bsec_do_steps(bsec_inputs, num_bsec_inputs, bsec_outputs, &num_bsec_outputs); if (status < 0) { kout << "bsec_do_steps error: " << status << endl; return; } if (status > 0) { kout << "bsec_do_steps warning: " << status << endl; } for (uint8_t i = 0; i < num_bsec_outputs; i++) { switch (bsec_outputs[i].sensor_id) { case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE: kout << now_s << " BME680 temperature " << bsec_outputs[i].signal << " °c" << endl; break; case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY: kout << now_s << " BME680 humidity " << bsec_outputs[i].signal << " %" << endl; break; case BSEC_OUTPUT_RAW_PRESSURE: kout << now_s << " BME680 pressure " << bsec_outputs[i].signal / 100 << " hPa" << endl; break; case BSEC_OUTPUT_RAW_GAS: kout << now_s << " BME680 gas resistance " << bsec_outputs[i].signal << " Ω" << endl; break; case BSEC_OUTPUT_IAQ: if (bsec_outputs[i].accuracy > 0) { kout << now_s << " BME680 IAQ: " << bsec_outputs[i].signal << " " << accuracy[bsec_outputs[i].accuracy] << endl; } break; case BSEC_OUTPUT_STABILIZATION_STATUS: if (bsec_outputs[i].signal < 1) { kout << now_s << " BME680 IAQ initial stabilization in progress" << endl; } break; case BSEC_OUTPUT_RUN_IN_STATUS: if (bsec_outputs[i].signal < 1) { kout << now_s << " BME680 IAQ power-on stabilization in progress" << endl; } break; default: continue; } } kout << endl; } } #ifdef CONFIG_driver_bme680_bsec_save_state if (++iteration == 600) { uint32_t serialized_state_size; status = bsec_get_state(0, serialized_state, BSEC_MAX_STATE_BLOB_SIZE, work_buffer, BSEC_MAX_STATE_BLOB_SIZE, &serialized_state_size); if (status < 0) { kout << "bsec_get_state error: " << status << endl; return; } if (status > 0) { kout << "bsec_get_state warning: " << status << endl; } FILE *f = fopen(BSEC_STATE_PATH, "w"); if (f == NULL) { perror("fopen"); return; } if (fwrite(serialized_state, sizeof(uint8_t), serialized_state_size, f) < serialized_state_size) { perror("fwrite"); } if (fclose(f) == EOF) { perror("fclose"); } iteration = 0; } #endif } int main(void) { arch.setup(); gpio.setup(); kout.setup(); #if defined(CONFIG_meta_driver_i2c) while (i2c.setup() != 0) { kout << "I2C setup FAILED" << endl; arch.delay_ms(1000); } kout << "I2C setup OK" << endl; #endif // Set up (open-source) BME680 driver bme680.intf = BME680_I2C_INTF; bme680.read = bme680_i2c_read; bme680.write = bme680_i2c_write; bme680.delay_ms = bme680_delay_ms; int8_t bme680_status = bme680.init(); while (bme680_status != 0) { kout << "BME680 init failed: " << (uint8_t)bme680_status << endl; arch.delay_ms(1000); } kout << "BME680 init OK" << endl; // Initialize proprietary BSEC library bsec_library_return_t bsec_status = bsec_init(); while (bsec_status != BSEC_OK) { kout << "BSEC init failed: " << bsec_status << endl; arch.delay_ms(1000); } kout << "BSEC init OK" << endl; #ifdef CONFIG_driver_bme680_bsec_save_state FILE *f = fopen(BSEC_STATE_PATH, "r"); if (f != NULL) { size_t serialized_state_size = fread(serialized_state, BSEC_MAX_STATE_BLOB_SIZE, sizeof(uint8_t), f); if (serialized_state_size > 0) { bsec_status = bsec_set_state(serialized_state, BSEC_MAX_STATE_BLOB_SIZE, work_buffer, BSEC_MAX_STATE_BLOB_SIZE); if (bsec_status < 0) { kout << "bsec_set_state error: " << bsec_status << endl; } if (bsec_status > 0) { kout << "bsec_set_state warning: " << bsec_status << endl; } } if (fclose(f) == EOF) { perror("fclose"); } } else { // file doesn't exist. that's harmless. perror("fopen"); } #endif /* * Output configuration. The BME680 BSEC library supports several virtual * sensors such as raw temperature, compensated temperature, or IAQ. Each * virtual sensor is calculated based on past observations and the * TPH+Gas readings obtained from the BME680 sinsor. * * Here, we are interested in seven different types of readings. */ bsec_sensor_configuration_t virtual_sensors[7]; unsigned char n_virtual_sensors = 7; /* * bsec_update_subscription writes required sensor settings to the * sensor_configs array. We're not interested in them at the moment. */ bsec_sensor_configuration_t sensor_configs[BSEC_MAX_PHYSICAL_SENSOR]; unsigned char n_sensor_settings = BSEC_MAX_PHYSICAL_SENSOR; /* * Low Power mode -> 1/3 Hz (i.e., one sample every three seconds). */ float sample_rate = BSEC_SAMPLE_RATE_LP; /* * We're interested in the following readings. * See bsec_virtual_sensor_t definition in bsec_datatypes for a list of * supported virtual sensor types ("sensor_ids"). */ virtual_sensors[0].sensor_id = BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE; virtual_sensors[0].sample_rate = sample_rate; virtual_sensors[1].sensor_id = BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY; virtual_sensors[1].sample_rate = sample_rate; virtual_sensors[2].sensor_id = BSEC_OUTPUT_RAW_PRESSURE; virtual_sensors[2].sample_rate = sample_rate; virtual_sensors[3].sensor_id = BSEC_OUTPUT_RAW_GAS; virtual_sensors[3].sample_rate = sample_rate; virtual_sensors[4].sensor_id = BSEC_OUTPUT_IAQ; virtual_sensors[4].sample_rate = sample_rate; virtual_sensors[5].sensor_id = BSEC_OUTPUT_STABILIZATION_STATUS; virtual_sensors[5].sample_rate = sample_rate; virtual_sensors[6].sensor_id = BSEC_OUTPUT_RUN_IN_STATUS; virtual_sensors[6].sample_rate = sample_rate; bsec_status = bsec_update_subscription(virtual_sensors, n_virtual_sensors, sensor_configs, &n_sensor_settings); while (bsec_status != BSEC_OK) { kout << "bsec_update_subscription error: " << bsec_status << endl; arch.delay_ms(1000); } kout << "bsec_update_subscription OK" << endl; arch.idle_loop(); return 0; }