Skip to content
Snippets Groups Projects
bsec.c 13.1 KiB
Newer Older
/* Adapted from  bsec_iot_example.c and bsec_iot_ulp_plus_example.c */

#include "card10.h"
#include "bosch.h"
schneider's avatar
schneider committed
#include "bsec_integration.h"
#include "ble/ess.h"

#include "epicardium.h"
#include "modules/modules.h"
#include "os/config.h"

#include "FreeRTOS.h"
#include "task.h"

#include "max32665.h"
#include "gcr_regs.h"
#include <stdio.h>

TaskHandle_t bsec_task_id;
static int64_t last_bme680_timestamp;
static bool debug;
static struct bsec_sensor_data last_bsec_data;
schneider's avatar
schneider committed
#define ULP 0
// From generic_18v_3s_4d/bsec_serialized_configurations_iaq.c
static const uint8_t bsec_config_generic_18v_3s_4d[454] = {
	0,   8,   4,   1,   61,  0,   0,   0,   0,   0,   0,   0,   174, 1,
	0,   0,   48,  0,   1,   0,   0,   192, 168, 71,  64,  49,  119, 76,
	0,   0,   225, 68,  137, 65,  0,   191, 205, 204, 204, 190, 0,   0,
	64,  191, 225, 122, 148, 190, 0,   0,   0,   0,   216, 85,  0,   100,
	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,   7,   240, 150, 61,  0,
	0,   0,   0,   0,   0,   0,   0,   28,  124, 225, 61,  52,  128, 215,
	63,  0,   0,   160, 64,  0,   0,   0,   0,   0,   0,   0,   0,   205,
	204, 12,  62,  103, 213, 39,  62,  230, 63,  76,  192, 0,   0,   0,
	0,   0,   0,   0,   0,   145, 237, 60,  191, 251, 58,  64,  63,  177,
	80,  131, 64,  0,   0,   0,   0,   0,   0,   0,   0,   93,  254, 227,
	62,  54,  60,  133, 191, 0,   0,   64,  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,   83,  141, 0,   0
/*!
 * @brief           Capture the system time in microseconds
 *
 * @return          system_current_time    current system timestamp in microseconds
 */
static int64_t get_timestamp_us()
	int64_t tick = xTaskGetTickCount();
schneider's avatar
schneider committed
	return tick * 1000;
}

/*!
 * @brief           Handling of the ready outputs
 *
 * @param[in]       timestamp       time in nanoseconds
 * @param[in]       iaq             IAQ signal
 * @param[in]       iaq_accuracy    accuracy of IAQ signal
 * @param[in]       temperature     temperature signal
 * @param[in]       humidity        humidity signal
 * @param[in]       pressure        pressure signal
 * @param[in]       raw_temperature raw temperature signal
 * @param[in]       raw_humidity    raw humidity signal
 * @param[in]       gas             raw gas sensor signal
 * @param[in]       bsec_status     value returned by the bsec_do_steps() call
 *
 * @return          none
 */
static void output_ready(
schneider's avatar
schneider committed
	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
) {
schneider's avatar
schneider committed
	last_bsec_data.temperature               = temperature;
	last_bsec_data.humidity                  = humidity;
	last_bsec_data.pressure                  = pressure / 100.;
	last_bsec_data.gas_resistance            = gas;
	last_bsec_data.timestamp                 = timestamp;
	last_bsec_data.accuracy                  = iaq_accuracy;
	last_bsec_data.indoor_air_quality        = iaq;
	last_bsec_data.static_indoor_air_quality = static_iaq;
schneider's avatar
schneider committed
	last_bsec_data.co2_equivalent            = co2_equivalent;
	last_bsec_data.breath_voc_equivalent     = breath_voc_equivalent;
schneider's avatar
schneider committed
	last_bme680_timestamp = timestamp;
	bleESS_update_from_bsec_data(&last_bsec_data);

schneider's avatar
schneider committed
	if (debug) {
schneider's avatar
schneider committed
		LOG_INFO(
			"bsec",
			"time[ms]: %u, IAQ: %u, IAQ ACC[0-3]: %u, T[.1C]: %u, Hum[.1%%]: %u, P[Pa]: %u, Raw T[.1C]: %u, Raw Hum[.1%%]: %u, Gas[Ohm]: %u, Static IAQ: %u, CO2[ppm]: %u, Breath VOC[ppb]: %u",
			(unsigned int)(timestamp / 1e6),
			(unsigned int)(iaq),
			(unsigned int)(iaq_accuracy),
			(unsigned int)(temperature * 10),
			(unsigned int)(humidity * 10),
			(unsigned int)(pressure),
			(unsigned int)(raw_temperature * 10),
			(unsigned int)(raw_humidity * 10),
			(unsigned int)(gas),
			(unsigned int)(static_iaq),
			(unsigned int)(co2_equivalent),
			(unsigned int)(breath_voc_equivalent * 1e3)
		);
schneider's avatar
schneider committed
int epic_bsec_read_sensors(struct bsec_sensor_data *data)
{
schneider's avatar
schneider committed
	if (data == NULL) {
schneider's avatar
schneider committed
		return -EFAULT;
	}
schneider's avatar
schneider committed
	if (!bsec_task_active) {
schneider's avatar
schneider committed
		return -ENODEV;
	}

	/* TODO: could also return -EINVAL */
	while (last_bme680_timestamp == 0)
		vTaskDelay(pdMS_TO_TICKS(10));

	*data = last_bsec_data;
	return 0;
}

static uint32_t bsec_load(char *path, uint8_t *buffer, uint32_t n_buffer)
schneider's avatar
schneider committed
	uint32_t len = 0;
	int fd, res;
schneider's avatar
schneider committed
	LOG_DEBUG("bsec", "load %s %lu", path, n_buffer);

	if ((fd = epic_file_open(path, "r")) < 0) {
schneider's avatar
schneider committed
		LOG_DEBUG("bsec", "Open failed");
schneider's avatar
schneider committed
		return 0;
	}
schneider's avatar
schneider committed
	uint32_t header;
	if ((res = epic_file_read(fd, &header, sizeof(header))) !=
	    sizeof(header)) {
schneider's avatar
schneider committed
		LOG_WARN("bsec", "Header failed");
schneider's avatar
schneider committed
		goto done;
	}
schneider's avatar
schneider committed
	if (header > n_buffer) {
schneider's avatar
schneider committed
		LOG_WARN("bsec", "Too large");
schneider's avatar
schneider committed
		goto done;
	}
	if (epic_file_read(fd, buffer, header) != (int)header) {
schneider's avatar
schneider committed
		LOG_WARN("bsec", "Read failed");
schneider's avatar
schneider committed
		goto done;
	}
schneider's avatar
schneider committed
	len = header;
schneider's avatar
schneider committed
	LOG_DEBUG("bsec", "Success");
schneider's avatar
schneider committed
done:
	epic_file_close(fd);
schneider's avatar
schneider committed
	return len;
}
/*!
 * @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
 */
static uint32_t state_load(uint8_t *state_buffer, uint32_t n_buffer)
schneider's avatar
schneider committed
	return bsec_load("bsec_iaq.state", state_buffer, n_buffer);
}

/*!
 * @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
 */
static void state_save(const uint8_t *state_buffer, uint32_t length)
schneider's avatar
schneider committed
	int fd, res;
schneider's avatar
schneider committed
	LOG_DEBUG("bsec", "state_save %d", (int)length);

	if ((fd = epic_file_open("bsec_iaq.state", "w")) < 0) {
schneider's avatar
schneider committed
		LOG_WARN("bsec", "Open failed");
schneider's avatar
schneider committed
		return;
	}
schneider's avatar
schneider committed
	uint32_t header = length;
	if ((res = epic_file_write(fd, &header, sizeof(header))) !=
	    sizeof(header)) {
schneider's avatar
schneider committed
		LOG_WARN("bsec", "Header failed");
schneider's avatar
schneider committed
		goto done;
	}
	if (epic_file_write(fd, state_buffer, header) != (int)header) {
schneider's avatar
schneider committed
		LOG_WARN("bsec", "Write failed");
schneider's avatar
schneider committed
		goto done;
	}
schneider's avatar
schneider committed
	LOG_DEBUG("bsec", "stack high: %lu", uxTaskGetStackHighWaterMark(NULL));
schneider's avatar
schneider committed
done:
	epic_file_close(fd);
}
/*!
 * @brief           Delete the library state from non-volatile memory
 *
 * @return          none
 */
static void state_delete(void)
{
	LOG_DEBUG("bsec", "state_delete");

	epic_file_unlink("bsec_iaq.state");
}
static int8_t
i2c_write(uint8_t addr, uint8_t reg, uint8_t *p_buf, uint16_t size)
{
	int8_t ret;
	hwlock_acquire(HWLOCK_I2C);
	ret = card10_bosch_i2c_write(addr, reg, p_buf, size);
	hwlock_release(HWLOCK_I2C);
	return ret;
}

static int8_t i2c_read(uint8_t addr, uint8_t reg, uint8_t *p_buf, uint16_t size)
{
	int8_t ret;
	hwlock_acquire(HWLOCK_I2C);
	ret = card10_bosch_i2c_read(addr, reg, p_buf, size);
	hwlock_release(HWLOCK_I2C);
	return ret;
}

static void delay(uint32_t msec)
{
	if (xTaskGetSchedulerState() == taskSCHEDULER_NOT_STARTED) {
		/* We need to fall back to hardware waits if not running
		 * in a task context */
		card10_bosch_delay(msec);
	} else {
		vTaskDelay(pdMS_TO_TICKS(msec));
	}
}

/*!
 * @brief           Load library config from non-volatile memory
 *
 * @param[in,out]   config_buffer    buffer to hold the loaded state string
 * @param[in]       n_buffer        size of the allocated state buffer
 *
 * @return          number of bytes copied to config_buffer
 */
static uint32_t config_load(uint8_t *config_buffer, uint32_t n_buffer)
	uint32_t len = bsec_load("bsec_iaq.config", config_buffer, n_buffer);

	if (len == 0) {
schneider's avatar
schneider committed
		LOG_INFO("bsec", "Using default bsec_config_generic_18v_3s_4d");
		len = sizeof(bsec_config_generic_18v_3s_4d);
		memcpy(config_buffer, bsec_config_generic_18v_3s_4d, len);
	}

	return len;
schneider's avatar
schneider committed
#if ULP
void ulp_plus_trigger_iaq()
schneider's avatar
schneider committed
	/* We call bsec_update_subscription() in order to instruct BSEC to perform an extra measurement at the next
     * possible time slot
     */

schneider's avatar
schneider committed
	bsec_sensor_configuration_t requested_virtual_sensors[1];
	uint8_t n_requested_virtual_sensors = 1;
	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;

	/* To trigger a ULP plus, we request the IAQ virtual sensor with a specific sample rate code */
	requested_virtual_sensors[0].sensor_id = BSEC_OUTPUT_IAQ;
	requested_virtual_sensors[0].sample_rate =
		BSEC_SAMPLE_RATE_ULP_MEASUREMENT_ON_DEMAND;

	/* 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
	);
schneider's avatar
schneider committed
	/* The status code would tell is if the request was accepted. It will be rejected if the sensor is not already in
     * ULP mode, or if the time difference between requests is too short, for example. */
}
#endif

bool bsec_active(void)
{
}

int bsec_read_bme680(struct bme680_sensor_data *data)
{
		return BME680_E_COM_FAIL;
	}
schneider's avatar
schneider committed
	if (data == NULL) {
	while (last_bme680_timestamp == 0)
		vTaskDelay(pdMS_TO_TICKS(10));
schneider's avatar
schneider committed
	data->temperature    = last_bsec_data.temperature;
	data->humidity       = last_bsec_data.humidity;
	data->pressure       = last_bsec_data.pressure;
	data->gas_resistance = last_bsec_data.gas_resistance;

/**
 * Checks config and activates the BSEC libary if requested.
 *
 * Initializes the BSEC library before starting the task to
 * reduce the stack size needed for the task by at least 250 bytes
schneider's avatar
schneider committed
	return_values_init ret;
schneider's avatar
schneider committed
#if ULP
	float sample_rate = BSEC_SAMPLE_RATE_ULP;
#else
schneider's avatar
schneider committed
	float sample_rate = BSEC_SAMPLE_RATE_LP;
#endif
schneider's avatar
schneider committed

	if (!config_get_boolean_with_default("bsec_enable", false)) {
		return -1;
	}

	debug = config_get_boolean_with_default("bsec_debug", false);
		config_get_integer_with_default("bsec_offset", -22) / 10.;
	if (temperature_offset != 0.0) {
schneider's avatar
schneider committed
		LOG_INFO(
schneider's avatar
schneider committed
			"BSEC Temp offset %d/10 K",
			(int)(temperature_offset * 10)
		);
	/* Puts AT LEAST 2 * #BSEC_MAX_PROPERTY_BLOB_SIZE = 2 * 454 = 908 bytes onto the stack */
	ret = bsec_iot_init(
		sample_rate,
		i2c_write,
		i2c_read,
		delay,
		state_load,
		config_load
	if (ret.bsec_status == BSEC_E_CONFIG_VERSIONMISMATCH) {
		/* BSEC version changed and old state is not compatible anymore */
		/* If the config is also not valid anymore, the user will have
		 * to fix that. */
		state_delete();
		ret = bsec_iot_init(
			sample_rate,
			-temperature_offset,
			i2c_write,
			i2c_read,
			delay,
			state_load,
			config_load
		);
	}

schneider's avatar
schneider committed
	if (ret.bme680_status) {
		LOG_WARN("bsec", "bme680 init failed: %d", ret.bme680_status);
		/* Could not initialize BME680 */
schneider's avatar
schneider committed
	} else if (ret.bsec_status) {
		LOG_WARN("bsec", "bsec init failed: %d", ret.bsec_status);
		/* Could not initialize BSEC library */
schneider's avatar
schneider committed
	}
	return 0;
}

void vBSECTask(void *pvParameters)
{
	bsec_task_active = true;
	bsec_task_id     = xTaskGetCurrentTaskHandle();

#if ULP
	/* State is saved every 100 samples, which means every 100 * 300 secs = 500 minutes  */
	const int stat_save_interval = 100;
#else
	/* State is saved every 10.000 samples, which means every 10.000 * 3 secs = 500 minutes  */
	const int stat_save_interval = 10000;
#endif
schneider's avatar
schneider committed
	/* Call to endless loop function which reads and processes data based on sensor settings */
	/* Puts AT LEAST 2 * BSEC_MAX_STATE_BLOB_SIZE + 8 * sizeof(bsec_input_t) =
	 * 2 * 139 + 8 * 20 = 438 bytes onto the stack */
schneider's avatar
schneider committed
	bsec_iot_loop(
		delay,
		get_timestamp_us,
		output_ready,
		state_save,
		stat_save_interval
	);