464 lines
13 KiB
C++
464 lines
13 KiB
C++
/**
|
|
* Copyright (c) 2021 Bosch Sensortec GmbH. All rights reserved.
|
|
*
|
|
* BSD-3-Clause
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
*
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* 3. Neither the name of the copyright holder nor the names of its
|
|
* contributors may be used to endorse or promote products derived from
|
|
* this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
|
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
|
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
* @file bsec2.cpp
|
|
* @date 18 July 2024
|
|
* @version 2.1.5
|
|
*
|
|
*/
|
|
|
|
#include "bsec2.h"
|
|
|
|
static uint8_t workBuffer[BSEC_MAX_WORKBUFFER_SIZE];
|
|
|
|
/**
|
|
* @brief Constructor of Bsec2 class
|
|
*/
|
|
Bsec2::Bsec2(void)
|
|
{
|
|
ovfCounter = 0;
|
|
lastMillis = 0;
|
|
status = BSEC_OK;
|
|
extTempOffset = 0.0f;
|
|
opMode = BME68X_SLEEP_MODE;
|
|
newDataCallback = nullptr;
|
|
bsecInstance = nullptr;
|
|
|
|
memset(&version, 0, sizeof(version));
|
|
memset(&bmeConf, 0, sizeof(bmeConf));
|
|
memset(&outputs, 0, sizeof(outputs));
|
|
}
|
|
|
|
/**
|
|
* @brief Function to initialize the sensor based on custom callbacks
|
|
*/
|
|
bool Bsec2::begin(bme68xIntf intf, bme68x_read_fptr_t read, bme68x_write_fptr_t write,
|
|
bme68x_delay_us_fptr_t idleTask, void *intfPtr, unsigned long (*millis)())
|
|
{
|
|
bsecMillis = millis;
|
|
sensor.begin(intf, read, write, idleTask, intfPtr);
|
|
|
|
if (sensor.checkStatus() == BME68X_ERROR)
|
|
return false;
|
|
|
|
return beginCommon();
|
|
}
|
|
|
|
#ifdef ARDUINO
|
|
/**
|
|
* @brief Function to initialize the sensor based on custom callbacks
|
|
*/
|
|
bool Bsec2::begin(bme68xIntf intf, bme68x_read_fptr_t read, bme68x_write_fptr_t write,
|
|
bme68x_delay_us_fptr_t idleTask, void *intfPtr)
|
|
{
|
|
bsecMillis = millis;
|
|
sensor.begin(intf, read, write, idleTask, intfPtr);
|
|
|
|
if (sensor.checkStatus() == BME68X_ERROR)
|
|
return false;
|
|
|
|
return beginCommon();
|
|
}
|
|
|
|
/**
|
|
* @brief Function to initialize the sensor based on the Wire library
|
|
*/
|
|
bool Bsec2::begin(uint8_t i2cAddr, TwoWire &i2c, bme68x_delay_us_fptr_t idleTask)
|
|
{
|
|
bsecMillis = millis;
|
|
sensor.begin(i2cAddr, i2c, idleTask);
|
|
|
|
if (sensor.checkStatus() == BME68X_ERROR)
|
|
return false;
|
|
|
|
return beginCommon();
|
|
}
|
|
|
|
/**
|
|
* @brief Function to initialize the sensor based on the SPI library
|
|
*/
|
|
bool Bsec2::begin(uint8_t chipSelect, SPIClass &spi, bme68x_delay_us_fptr_t idleTask)
|
|
{
|
|
bsecMillis = millis;
|
|
sensor.begin(chipSelect, spi, idleTask);
|
|
|
|
if (sensor.checkStatus() == BME68X_ERROR)
|
|
return false;
|
|
|
|
return beginCommon();
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* @brief Function to request/subscribe for desired virtual outputs with the supported sample rates
|
|
*/
|
|
bool Bsec2::updateSubscription(bsecSensor sensorList[], uint8_t nSensors, float sampleRate)
|
|
{
|
|
bsec_sensor_configuration_t virtualSensors[BSEC_NUMBER_OUTPUTS], sensorSettings[BSEC_MAX_PHYSICAL_SENSOR];
|
|
uint8_t nSensorSettings = BSEC_MAX_PHYSICAL_SENSOR;
|
|
|
|
for (uint8_t i = 0; i < nSensors; i++)
|
|
{
|
|
virtualSensors[i].sensor_id = sensorList[i];
|
|
virtualSensors[i].sample_rate = sampleRate;
|
|
}
|
|
|
|
/* Subscribe to library virtual sensors outputs */
|
|
status = bsec_update_subscription_m(bsecInstance, virtualSensors, nSensors, sensorSettings, &nSensorSettings);
|
|
if (status != BSEC_OK)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @brief Callback from the user to read data from the BME68X using parallel mode/forced mode, process and store outputs
|
|
*/
|
|
bool Bsec2::run(void)
|
|
{
|
|
uint8_t nFieldsLeft = 0;
|
|
bme68xData data;
|
|
int64_t currTimeNs = getTimeMs() * INT64_C(1000000);
|
|
opMode = bmeConf.op_mode;
|
|
|
|
if (currTimeNs >= bmeConf.next_call)
|
|
{
|
|
/* Provides the information about the current sensor configuration that is
|
|
necessary to fulfill the input requirements, eg: operation mode, timestamp
|
|
at which the sensor data shall be fetched etc */
|
|
status = bsec_sensor_control_m(bsecInstance ,currTimeNs, &bmeConf);
|
|
if (status != BSEC_OK)
|
|
return false;
|
|
|
|
switch (bmeConf.op_mode)
|
|
{
|
|
case BME68X_FORCED_MODE:
|
|
setBme68xConfigForced();
|
|
break;
|
|
case BME68X_PARALLEL_MODE:
|
|
if (opMode != bmeConf.op_mode)
|
|
{
|
|
setBme68xConfigParallel();
|
|
}
|
|
break;
|
|
|
|
case BME68X_SLEEP_MODE:
|
|
if (opMode != bmeConf.op_mode)
|
|
{
|
|
sensor.setOpMode(BME68X_SLEEP_MODE);
|
|
opMode = BME68X_SLEEP_MODE;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (sensor.checkStatus() == BME68X_ERROR)
|
|
return false;
|
|
|
|
if (bmeConf.trigger_measurement && bmeConf.op_mode != BME68X_SLEEP_MODE)
|
|
{
|
|
if (sensor.fetchData())
|
|
{
|
|
do
|
|
{
|
|
nFieldsLeft = sensor.getData(data);
|
|
/* check for valid gas data */
|
|
if (data.status & BME68X_GASM_VALID_MSK)
|
|
{
|
|
/* Convert sensor raw pressure unit from pascal to hecto pascal */
|
|
data.pressure *= 0.01f;
|
|
|
|
if (!processData(currTimeNs, data))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
} while (nFieldsLeft);
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @brief Function to get the state of the algorithm to save to non-volatile memory
|
|
*/
|
|
bool Bsec2::getState(uint8_t *state)
|
|
{
|
|
uint32_t n_serialized_state = BSEC_MAX_STATE_BLOB_SIZE;
|
|
|
|
status = bsec_get_state_m(bsecInstance, 0, state, BSEC_MAX_STATE_BLOB_SIZE, workBuffer, BSEC_MAX_WORKBUFFER_SIZE,
|
|
&n_serialized_state);
|
|
if (status != BSEC_OK)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @brief Function to set the state of the algorithm from non-volatile memory
|
|
*/
|
|
bool Bsec2::setState(uint8_t *state)
|
|
{
|
|
status = bsec_set_state_m(bsecInstance, state, BSEC_MAX_STATE_BLOB_SIZE, workBuffer, BSEC_MAX_WORKBUFFER_SIZE);
|
|
if (status != BSEC_OK)
|
|
return false;
|
|
|
|
memset(&bmeConf, 0, sizeof(bmeConf));
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @brief Function to retrieve the current library configuration
|
|
*/
|
|
bool Bsec2::getConfig(uint8_t *config)
|
|
{
|
|
uint32_t n_serialized_settings = 0;
|
|
|
|
status = bsec_get_configuration_m(bsecInstance, 0, config, BSEC_MAX_PROPERTY_BLOB_SIZE, workBuffer, BSEC_MAX_WORKBUFFER_SIZE, &n_serialized_settings);
|
|
if (status != BSEC_OK)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @brief Function to set the configuration of the algorithm from memory
|
|
*/
|
|
bool Bsec2::setConfig(const uint8_t *config)
|
|
{
|
|
status = bsec_set_configuration_m(bsecInstance, config, BSEC_MAX_PROPERTY_BLOB_SIZE, workBuffer, BSEC_MAX_WORKBUFFER_SIZE);
|
|
if (status != BSEC_OK)
|
|
return false;
|
|
|
|
memset(&bmeConf, 0, sizeof(bmeConf));
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @brief Function to calculate an int64_t timestamp in milliseconds
|
|
*/
|
|
int64_t Bsec2::getTimeMs(void)
|
|
{
|
|
int64_t timeMs = bsecMillis();
|
|
|
|
if (lastMillis > timeMs) /* An overflow occurred */
|
|
{
|
|
ovfCounter++;
|
|
}
|
|
|
|
lastMillis = timeMs;
|
|
|
|
return timeMs + (ovfCounter * INT64_C(0xFFFFFFFF));
|
|
}
|
|
|
|
/**
|
|
* @brief Function to assign the memory block to the bsec instance
|
|
*/
|
|
void Bsec2::allocateMemory(uint8_t (&memBlock)[BSEC_INSTANCE_SIZE])
|
|
{
|
|
/* allocating memory for the bsec instance */
|
|
bsecInstance = memBlock;
|
|
}
|
|
|
|
/**
|
|
* @brief Function to de-allocate the dynamically allocated memory
|
|
*/
|
|
void Bsec2::clearMemory(void)
|
|
{
|
|
delete[] bsecInstance;
|
|
}
|
|
|
|
/* Private functions */
|
|
|
|
/**
|
|
* @brief Reads data from the BME68X sensor and process it
|
|
*/
|
|
bool Bsec2::processData(int64_t currTimeNs, const bme68xData &data)
|
|
{
|
|
bsec_input_t inputs[BSEC_MAX_PHYSICAL_SENSOR]; /* Temp, Pres, Hum & Gas */
|
|
uint8_t nInputs = 0;
|
|
/* Checks all the required sensor inputs, required for the BSEC library for the requested outputs */
|
|
if (BSEC_CHECK_INPUT(bmeConf.process_data, BSEC_INPUT_HEATSOURCE))
|
|
{
|
|
inputs[nInputs].sensor_id = BSEC_INPUT_HEATSOURCE;
|
|
inputs[nInputs].signal = extTempOffset;
|
|
inputs[nInputs].time_stamp = currTimeNs;
|
|
nInputs++;
|
|
}
|
|
if (BSEC_CHECK_INPUT(bmeConf.process_data, BSEC_INPUT_TEMPERATURE))
|
|
{
|
|
#ifdef BME68X_USE_FPU
|
|
inputs[nInputs].signal = data.temperature;
|
|
#else
|
|
inputs[nInputs].signal = data.temperature / 100.0f;
|
|
#endif
|
|
inputs[nInputs].sensor_id = BSEC_INPUT_TEMPERATURE;
|
|
inputs[nInputs].time_stamp = currTimeNs;
|
|
nInputs++;
|
|
}
|
|
if (BSEC_CHECK_INPUT(bmeConf.process_data, BSEC_INPUT_HUMIDITY))
|
|
{
|
|
#ifdef BME68X_USE_FPU
|
|
inputs[nInputs].signal = data.humidity;
|
|
#else
|
|
inputs[nInputs].signal = data.humidity / 1000.0f;
|
|
#endif
|
|
inputs[nInputs].sensor_id = BSEC_INPUT_HUMIDITY;
|
|
inputs[nInputs].time_stamp = currTimeNs;
|
|
nInputs++;
|
|
}
|
|
if (BSEC_CHECK_INPUT(bmeConf.process_data, BSEC_INPUT_PRESSURE))
|
|
{
|
|
inputs[nInputs].sensor_id = BSEC_INPUT_PRESSURE;
|
|
inputs[nInputs].signal = data.pressure;
|
|
inputs[nInputs].time_stamp = currTimeNs;
|
|
nInputs++;
|
|
}
|
|
if (BSEC_CHECK_INPUT(bmeConf.process_data, BSEC_INPUT_GASRESISTOR) &&
|
|
(data.status & BME68X_GASM_VALID_MSK))
|
|
{
|
|
inputs[nInputs].sensor_id = BSEC_INPUT_GASRESISTOR;
|
|
inputs[nInputs].signal = data.gas_resistance;
|
|
inputs[nInputs].time_stamp = currTimeNs;
|
|
nInputs++;
|
|
}
|
|
if (BSEC_CHECK_INPUT(bmeConf.process_data, BSEC_INPUT_PROFILE_PART) &&
|
|
(data.status & BME68X_GASM_VALID_MSK))
|
|
{
|
|
inputs[nInputs].sensor_id = BSEC_INPUT_PROFILE_PART;
|
|
inputs[nInputs].signal = (opMode == BME68X_FORCED_MODE) ? 0 : data.gas_index;
|
|
inputs[nInputs].time_stamp = currTimeNs;
|
|
nInputs++;
|
|
}
|
|
|
|
if (nInputs > 0)
|
|
{
|
|
|
|
outputs.nOutputs = BSEC_NUMBER_OUTPUTS;
|
|
memset(outputs.output, 0, sizeof(outputs.output));
|
|
|
|
/* Processing of the input signals and returning of output samples is performed by bsec_do_steps() */
|
|
status = bsec_do_steps_m(bsecInstance, inputs, nInputs, outputs.output, &outputs.nOutputs);
|
|
|
|
if (status != BSEC_OK)
|
|
return false;
|
|
|
|
if(newDataCallback)
|
|
newDataCallback(data, outputs, *this);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @brief Common code for the begin function
|
|
*/
|
|
bool Bsec2::beginCommon()
|
|
{
|
|
if (!bsecInstance)
|
|
{
|
|
/* allocate memory for the instance if not allocated */
|
|
bsecInstance = new uint8_t[bsec_get_instance_size_m()];
|
|
}
|
|
|
|
if (BSEC_INSTANCE_SIZE < bsec_get_instance_size_m())
|
|
{
|
|
status = BSEC_E_INSUFFICIENT_INSTANCE_SIZE;
|
|
return false;
|
|
}
|
|
status = bsec_init_m(bsecInstance);
|
|
if (status != BSEC_OK)
|
|
return false;
|
|
|
|
status = bsec_get_version_m(bsecInstance, &version);
|
|
if (status != BSEC_OK)
|
|
return false;
|
|
|
|
memset(&bmeConf, 0, sizeof(bmeConf));
|
|
memset(&outputs, 0, sizeof(outputs));
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @brief Set the BME68X sensor configuration to forced mode
|
|
*/
|
|
void Bsec2::setBme68xConfigForced(void)
|
|
{
|
|
/* Set the filter, odr, temperature, pressure and humidity settings */
|
|
sensor.setTPH(bmeConf.temperature_oversampling, bmeConf.pressure_oversampling, bmeConf.humidity_oversampling);
|
|
|
|
if (sensor.checkStatus() == BME68X_ERROR)
|
|
return;
|
|
|
|
sensor.setHeaterProf(bmeConf.heater_temperature, bmeConf.heater_duration);
|
|
|
|
if (sensor.checkStatus() == BME68X_ERROR)
|
|
return;
|
|
|
|
sensor.setOpMode(BME68X_FORCED_MODE);
|
|
if (sensor.checkStatus() == BME68X_ERROR)
|
|
return;
|
|
|
|
opMode = BME68X_FORCED_MODE;
|
|
}
|
|
|
|
/**
|
|
* @brief Set the BME68X sensor configuration to parallel mode
|
|
*/
|
|
void Bsec2::setBme68xConfigParallel(void)
|
|
{
|
|
uint16_t sharedHeaterDur = 0;
|
|
|
|
/* Set the filter, odr, temperature, pressure and humidity settings */
|
|
sensor.setTPH(bmeConf.temperature_oversampling, bmeConf.pressure_oversampling, bmeConf.humidity_oversampling);
|
|
|
|
if (sensor.checkStatus() == BME68X_ERROR)
|
|
return;
|
|
|
|
sharedHeaterDur = BSEC_TOTAL_HEAT_DUR - (sensor.getMeasDur(BME68X_PARALLEL_MODE) / INT64_C(1000));
|
|
|
|
sensor.setHeaterProf(bmeConf.heater_temperature_profile, bmeConf.heater_duration_profile, sharedHeaterDur,
|
|
bmeConf.heater_profile_len);
|
|
|
|
if (sensor.checkStatus() == BME68X_ERROR)
|
|
return;
|
|
|
|
sensor.setOpMode(BME68X_PARALLEL_MODE);
|
|
|
|
if (sensor.checkStatus() == BME68X_ERROR)
|
|
return;
|
|
|
|
opMode = BME68X_PARALLEL_MODE;
|
|
}
|