sTodo-m5paper-client/libraries/bsec2/src/bsec2.cpp
2025-06-30 20:47:33 +02:00

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