/** * 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; ; }