Skip to content
Snippets Groups Projects
bsec.c 12.4 KiB
Newer Older
  • Learn to ignore specific revisions
  • /* 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.h"
    
    #include "config.h"
    
    #include "modules/log.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] = {
    	4,   7,   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,   63,  205, 204, 204, 62,  0,   0,
    	64,  63,  205, 204, 204, 62,  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,   52,  233, 0,   0
    };
    
    /*!
     * @brief           Capture the system time in microseconds
     *
     * @return          system_current_time    current system timestamp in microseconds
     */
    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
     */
    
    schneider's avatar
    schneider committed
    void output_ready(
    	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
     */
    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
     */
    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);
    }
    
    
    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
     */
    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
    
    
    schneider's avatar
    schneider committed
    	bool bsec_enabled =
    
    schneider's avatar
    schneider committed
    		config_get_boolean_with_default("bsec_enable", false);
    
    schneider's avatar
    schneider committed
    	if (!bsec_enabled) {
    
    		return -1;
    	}
    
    	debug = config_get_boolean_with_default("bsec_debug", false);
    
    
    	float temperature_offset =
    		config_get_integer_with_default("bsec_offset", 0) / 10.;
    	if (temperature_offset != 0.0) {
    
    schneider's avatar
    schneider committed
    		LOG_INFO(
    			"besec",
    			"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
    
    schneider's avatar
    schneider committed
    	if (ret.bme680_status) {
    
    schneider's avatar
    schneider committed
    		LOG_WARN("bsec", "bme680 init failed");
    
    		/* Could not intialize BME680 */
    
    schneider's avatar
    schneider committed
    	} else if (ret.bsec_status) {
    
    schneider's avatar
    schneider committed
    		LOG_WARN("bsec", "bsec init failed");
    
    schneider's avatar
    schneider committed
    		/* Could not intialize 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
    	);