You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
296 lines
10 KiB
296 lines
10 KiB
//#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
|
|
|
|
#include <esp_log.h>
|
|
#include <esp_err.h>
|
|
#include <freertos/FreeRTOS.h>
|
|
#include <freertos/task.h>
|
|
#include <driver/i2c.h>
|
|
#include <sys/time.h>
|
|
#include "voc_sensor.h"
|
|
#include "i2c_utils.h"
|
|
#include "data_report.h"
|
|
|
|
static const char *TAG = "voc";
|
|
|
|
#define VOC_I2C_NUM I2C_NUM_0
|
|
#define VOC_I2C_TO_MS 1000
|
|
#define VOC_SEMA_TO_MS 2000
|
|
|
|
// Config overrides for BSEC
|
|
#define BME68X_PERIOD_POLL UINT32_C(5000)
|
|
|
|
#include "bme68x.h"
|
|
#include "bme68x_defs.h"
|
|
#include "bsec2.h"
|
|
#include "periph_init.h"
|
|
|
|
struct sensor_itf {
|
|
uint8_t dev_addr;
|
|
};
|
|
|
|
static struct sensor_itf gas_sensor_intf = {};
|
|
static struct bme68x_dev gas_sensor = {};
|
|
static struct bsec2 gas_sensor_bsec = {};
|
|
|
|
void bsec2_errormsg(const char *msg) {
|
|
ESP_LOGE("BSEC", "%s", msg);
|
|
}
|
|
|
|
uint32_t bsec2_timestamp_millis() {
|
|
#if 0
|
|
struct timeval tv;
|
|
gettimeofday(&tv, NULL);
|
|
return tv.tv_sec*1000 + (tv.tv_usec / 1000000);
|
|
#else
|
|
return pdTICKS_TO_MS(xTaskGetTickCount()); // This is no-op when using 1ms tick
|
|
#endif
|
|
}
|
|
|
|
static void user_delay_us(uint32_t period, void *intf_ptr) {
|
|
(void) intf_ptr;
|
|
|
|
uint32_t millis = period / 1000;
|
|
uint32_t usec = period % 1000;
|
|
|
|
#if configTICK_RATE_HZ != 1000
|
|
uint32_t remainder = millis % pdTICKS_TO_MS(1);
|
|
millis -= remainder;
|
|
usec += remainder * 1000;
|
|
#endif
|
|
|
|
if (millis) {
|
|
vTaskDelay(pdMS_TO_TICKS(millis));
|
|
}
|
|
if (usec) {
|
|
ets_delay_us(usec);
|
|
}
|
|
}
|
|
|
|
static BME68X_INTF_RET_TYPE user_i2c_read(uint8_t reg_addr, uint8_t *reg_data, uint32_t length, void *intf_ptr) {
|
|
ESP_LOGD(TAG, "BME680 i2c read");
|
|
if (length == 0) {
|
|
return BME68X_OK;
|
|
}
|
|
struct sensor_itf *itf = intf_ptr;
|
|
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
|
i2c_reg_read(cmd, itf->dev_addr, reg_addr, reg_data, length);
|
|
esp_err_t ret = i2c_master_cmd_begin(VOC_I2C_NUM, cmd, pdMS_TO_TICKS(VOC_I2C_TO_MS));
|
|
i2c_cmd_link_delete(cmd);
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGE(TAG, "I2C read error: %s, devaddr %x, reg %d, len %d", esp_err_to_name(ret), itf->dev_addr, reg_addr, length);
|
|
}
|
|
ESP_LOG_BUFFER_HEXDUMP(TAG, reg_data, length, ESP_LOG_DEBUG);
|
|
return ret == ESP_OK ? BME68X_OK : BME68X_E_COM_FAIL;
|
|
}
|
|
|
|
static BME68X_INTF_RET_TYPE user_i2c_write(uint8_t reg_addr, const uint8_t *reg_data, uint32_t length, void *intf_ptr) {
|
|
ESP_LOGD(TAG, "BME680 i2c write");
|
|
if (length == 0) {
|
|
return BME68X_OK;
|
|
}
|
|
struct sensor_itf *itf = intf_ptr;
|
|
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
|
i2c_reg_write(cmd, itf->dev_addr, reg_addr, reg_data, length);
|
|
esp_err_t ret = i2c_master_cmd_begin(VOC_I2C_NUM, cmd, pdMS_TO_TICKS(VOC_I2C_TO_MS));
|
|
i2c_cmd_link_delete(cmd);
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGE(TAG, "I2C write error: %s, devaddr %x, reg %d, len %d", esp_err_to_name(ret), itf->dev_addr, reg_addr, length);
|
|
}
|
|
return ret == ESP_OK ? BME68X_OK : BME68X_E_COM_FAIL;
|
|
}
|
|
|
|
static esp_err_t voc_init(void) {
|
|
int8_t rslt;
|
|
gas_sensor_intf.dev_addr = BME68X_I2C_ADDR_LOW;
|
|
gas_sensor.intf_ptr = &gas_sensor_intf;
|
|
gas_sensor.amb_temp = 25; // TODO set this from senseair!
|
|
gas_sensor.intf = BME68X_I2C_INTF;
|
|
gas_sensor.read = user_i2c_read;
|
|
gas_sensor.write = user_i2c_write;
|
|
gas_sensor.delay_us = user_delay_us;
|
|
|
|
for (int retry = 1; retry <= 3; retry++) {
|
|
ESP_LOGD(TAG, "BME680 initializing (try %d)", retry);
|
|
rslt = bme68x_init(&gas_sensor);
|
|
if (rslt == BME68X_OK) {
|
|
break;
|
|
}
|
|
}
|
|
if (rslt != BME68X_OK) {
|
|
ESP_LOGE(TAG, "Error from bme680_init: %d", rslt);
|
|
return ESP_FAIL;
|
|
}
|
|
ESP_LOGI(TAG, "BME680 init OK");
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
static void new_data_callback(
|
|
const struct bme68x_data *data,
|
|
const bsecOutputs *outputs,
|
|
const struct bsec2 *bsec
|
|
) {
|
|
if (!outputs->nOutputs) {
|
|
return;
|
|
}
|
|
|
|
// TODO do something with this
|
|
|
|
struct data_report my_report = {};
|
|
|
|
ESP_LOGI(TAG, "BSEC outputs: timestamp = %d", (int) (outputs->output[0].time_stamp / INT64_C(1000000)));
|
|
for (uint8_t i = 0; i < outputs->nOutputs; i++) {
|
|
const bsecData output = outputs->output[i];
|
|
switch (output.sensor_id) {
|
|
case BSEC_OUTPUT_IAQ:
|
|
ESP_LOGI(TAG, "\tIAQ = %f, accuracy %d", output.signal, (int) output.accuracy);
|
|
my_report.iaq = output.signal;
|
|
my_report.iaq_ready = (3 == (int) output.accuracy); // calibration finished
|
|
break;
|
|
case BSEC_OUTPUT_STATIC_IAQ:
|
|
ESP_LOGI(TAG, "\tSTATIC_IAQ = %f, accuracy %d", output.signal, (int) output.accuracy);
|
|
my_report.iaq_static = output.signal;
|
|
break;
|
|
case BSEC_OUTPUT_CO2_EQUIVALENT:
|
|
ESP_LOGI(TAG, "\tCO2_EQUIVALENT = %f, accuracy %d", output.signal, (int) output.accuracy);
|
|
my_report.iaq_co2_ppm_equiv = output.signal;
|
|
break;
|
|
case BSEC_OUTPUT_BREATH_VOC_EQUIVALENT:
|
|
ESP_LOGI(TAG, "\tBREATH_VOC_EQUIVALENT = %f, accuracy %d", output.signal, (int) output.accuracy);
|
|
my_report.iaq_voc_ppm_equiv = output.signal;
|
|
break;
|
|
case BSEC_OUTPUT_RAW_PRESSURE:
|
|
ESP_LOGI(TAG, "\tRAW_PRESSURE = %f", output.signal);
|
|
my_report.pressure = output.signal;
|
|
my_report.thpg_ready = true;
|
|
break;
|
|
case BSEC_OUTPUT_RAW_TEMPERATURE:
|
|
ESP_LOGI(TAG, "\tRAW_TEMPERATURE = %f", output.signal);
|
|
my_report.temperature_raw = output.signal;
|
|
my_report.thpg_ready = true;
|
|
break;
|
|
case BSEC_OUTPUT_RAW_HUMIDITY:
|
|
ESP_LOGI(TAG, "\tRAW_HUMIDITY = %f", output.signal);
|
|
my_report.humidity_raw = output.signal;
|
|
my_report.thpg_ready = true;
|
|
break;
|
|
case BSEC_OUTPUT_RAW_GAS:
|
|
ESP_LOGI(TAG, "\tRAW_GAS = %f", output.signal);
|
|
my_report.gas_raw = output.signal;
|
|
my_report.thpg_ready = true;
|
|
break;
|
|
case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE:
|
|
ESP_LOGI(TAG, "\tSENSOR_HEAT_COMPENSATED_TEMPERATURE = %f", output.signal);
|
|
my_report.temperature = output.signal;
|
|
my_report.thpg_ready = true;
|
|
break;
|
|
case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY:
|
|
ESP_LOGI(TAG, "\tSENSOR_HEAT_COMPENSATED_HUMIDITY = %f", output.signal);
|
|
my_report.humidity = output.signal;
|
|
my_report.thpg_ready = true;
|
|
break;
|
|
case BSEC_OUTPUT_STABILIZATION_STATUS:
|
|
ESP_LOGI(TAG, "\tSTABILIZATION_STATUS status = %d", (int) output.signal);
|
|
break;
|
|
case BSEC_OUTPUT_RUN_IN_STATUS:
|
|
ESP_LOGI(TAG, "\tRUN_IN_STATUS = %d", (int) output.signal);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (pdPASS == xSemaphoreTake(g_mux_data_report, pdMS_TO_TICKS(1000))) {
|
|
if (my_report.thpg_ready) {
|
|
g_data_report.thpg_ready = true;
|
|
g_data_report.thpg_timestamp = xTaskGetTickCount();
|
|
g_data_report.humidity = my_report.humidity;
|
|
g_data_report.humidity_raw = my_report.humidity_raw;
|
|
g_data_report.temperature = my_report.temperature;
|
|
g_data_report.temperature_raw = my_report.temperature_raw;
|
|
g_data_report.pressure = my_report.pressure;
|
|
g_data_report.gas_raw = my_report.gas_raw;
|
|
} else {
|
|
g_data_report.thpg_ready = false;
|
|
}
|
|
|
|
if (my_report.iaq_ready) {
|
|
g_data_report.iaq_ready = true;
|
|
g_data_report.iaq_timestamp = xTaskGetTickCount();
|
|
g_data_report.iaq = my_report.iaq;
|
|
g_data_report.iaq_static = my_report.iaq_static;
|
|
g_data_report.iaq_co2_ppm_equiv = my_report.iaq_co2_ppm_equiv;
|
|
g_data_report.iaq_voc_ppm_equiv = my_report.iaq_voc_ppm_equiv;
|
|
} else {
|
|
g_data_report.iaq_ready = false;
|
|
}
|
|
|
|
xSemaphoreGive(g_mux_data_report);
|
|
}
|
|
}
|
|
|
|
void voc_read_task(void *param) {
|
|
ESP_LOGI(TAG, "VOC sensor init");
|
|
|
|
if (ESP_OK != voc_init()) {
|
|
ESP_LOGE(TAG, "Fail to init sensor!");
|
|
goto abort;
|
|
}
|
|
|
|
int rv = bsec2_init(&gas_sensor_bsec, &gas_sensor);
|
|
if (rv != 0) {
|
|
ESP_LOGE(TAG, "Error in bsec init: %d", rv);
|
|
goto abort;
|
|
}
|
|
|
|
bsecSensor sensorList[] = {
|
|
BSEC_OUTPUT_IAQ,
|
|
BSEC_OUTPUT_STATIC_IAQ,
|
|
BSEC_OUTPUT_CO2_EQUIVALENT,
|
|
BSEC_OUTPUT_BREATH_VOC_EQUIVALENT,
|
|
|
|
BSEC_OUTPUT_RAW_TEMPERATURE,
|
|
BSEC_OUTPUT_RAW_HUMIDITY,
|
|
BSEC_OUTPUT_RAW_PRESSURE,
|
|
BSEC_OUTPUT_RAW_GAS,
|
|
|
|
BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE,
|
|
BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY,
|
|
|
|
BSEC_OUTPUT_STABILIZATION_STATUS,
|
|
BSEC_OUTPUT_RUN_IN_STATUS,
|
|
};
|
|
|
|
rv = bsec2_updateSubscription(&gas_sensor_bsec,
|
|
sensorList,
|
|
sizeof(sensorList) / sizeof(bsecSensor),
|
|
BSEC_SAMPLE_RATE_LP);
|
|
|
|
if (false == rv) {
|
|
ESP_LOGE(TAG, "Fail to update bsec subscriptions!");
|
|
goto abort;
|
|
}
|
|
|
|
bsec2_attachCallback(&gas_sensor_bsec, new_data_callback);
|
|
|
|
ESP_LOGI(TAG, "BSEC library version %d.%d.%d.%d",
|
|
gas_sensor_bsec.version.major,
|
|
gas_sensor_bsec.version.minor,
|
|
gas_sensor_bsec.version.major_bugfix,
|
|
gas_sensor_bsec.version.minor_bugfix);
|
|
|
|
// TODO add bsec state persistence to NVS at some landmark intervals, e.g. 1 day
|
|
// TODO periodic updating of sensor ambient temp
|
|
|
|
while (1) {
|
|
rv = bsec2_run(&gas_sensor_bsec);
|
|
if (false == rv) {
|
|
ESP_LOGE(TAG, "Error in bsec run!");
|
|
}
|
|
vTaskDelay(pdMS_TO_TICKS(100));
|
|
}
|
|
|
|
abort:
|
|
ESP_LOGE(TAG, "VOC task ends");
|
|
vTaskDelete(NULL);
|
|
}
|
|
|