implemented co2 and web access

modbus
Ondřej Hruška 2 years ago
parent fb71f94a17
commit 557b074527
  1. 2
      main/CMakeLists.txt
  2. 4
      main/app_main.c
  3. 133
      main/co2_sensor.c
  4. 12
      main/co2_sensor.h
  5. 4
      main/data_report.c
  6. 35
      main/data_report.h
  7. 18
      main/periph_init.c
  8. 4
      main/periph_init.h
  9. 64
      main/voc_sensor.c
  10. 42
      main/web/websrv.c

@ -6,6 +6,8 @@ idf_component_register(SRCS
utils.c
wifi_conn.c
voc_sensor.c
co2_sensor.c
data_report.c
periph_init.c
i2c_utils.c
console/console_ioimpl.c

@ -22,6 +22,7 @@
#include "periph_init.h"
#include "voc_sensor.h"
#include "tasks.h"
#include "co2_sensor.h"
static const char *TAG = "main";
@ -56,7 +57,8 @@ void app_main(void) {
periph_init();
xTaskCreate(voc_read_task, "VOC", 4096, NULL, PRIO_NORMAL, NULL);
xTaskCreatePinnedToCore(voc_read_task, "VOC", 4096, NULL, PRIO_NORMAL, NULL, 1);
xTaskCreatePinnedToCore(co2_read_task, "CO2", 4096, NULL, PRIO_NORMAL, NULL, 1);
console_init(NULL);
register_console_commands();

@ -0,0 +1,133 @@
#include "co2_sensor.h"
#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 "periph_init.h"
#include "data_report.h"
static const char *TAG = "co2";
#define CO2_ADDR 104
#define CO2_I2C_NUM I2C_NUM_0
#define TIMEOUT_MS 1000
static esp_err_t do_reg_read(uint8_t reg_addr, uint8_t *reg_data, uint32_t length, uint32_t timeout_ms) {
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_reg_read(cmd, CO2_ADDR, reg_addr, reg_data, length);
esp_err_t ret = i2c_master_cmd_begin(CO2_I2C_NUM, cmd, pdMS_TO_TICKS(TIMEOUT_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), CO2_ADDR, reg_addr, length);
}
ESP_LOG_BUFFER_HEXDUMP(TAG, reg_data, length, ESP_LOG_DEBUG);
return ret;
}
bool wake_up() {
esp_err_t suc;
#define TRY(x) suc=x; if(suc!=ESP_OK) return suc;
// for (int i = 0; i < 10; i++) {
uint8_t dummy;
i2c_cmd_handle_t chain = i2c_cmd_link_create();
TRY(i2c_master_start(chain));
TRY(i2c_master_write_byte(chain, (CO2_ADDR << 1) | I2C_MASTER_WRITE, false));
TRY(i2c_master_write_byte(chain, 0x30, false));
TRY(i2c_master_start(chain));
TRY(i2c_master_write_byte(chain, (CO2_ADDR << 1) | I2C_MASTER_READ, false)); // TODO expect ack?
TRY(i2c_master_read(chain, &dummy, 1, I2C_MASTER_LAST_NACK));
TRY(i2c_master_stop(chain));
/*esp_err_t ret = */ i2c_master_cmd_begin(CO2_I2C_NUM, chain, pdMS_TO_TICKS(10));
i2c_cmd_link_delete(chain);
// if (ret == ESP_OK) {
vTaskDelay(pdMS_TO_TICKS(14));
return true;
// }
//// }
// ESP_LOGE(TAG, "Fail to wake up");
// return false;
#undef TRY
}
static esp_err_t reg_read(uint8_t reg_addr, uint8_t *reg_data, uint32_t length, uint32_t timeout_ms) {
BaseType_t suc = xSemaphoreTake(g_mux_i2c, pdMS_TO_TICKS(500));
if (suc != pdPASS) {
ESP_LOGE(TAG, "Sema fail");
return ESP_ERR_TIMEOUT;
}
if (!wake_up()) {
xSemaphoreGive(g_mux_i2c);
return ESP_ERR_TIMEOUT;
}
esp_err_t rv = do_reg_read(reg_addr, reg_data, length, timeout_ms);
xSemaphoreGive(g_mux_i2c);
ets_delay_us(12000);
return rv;
}
static esp_err_t do_reg_write(uint8_t reg_addr, const uint8_t *reg_data, uint32_t length, uint32_t timeout_ms) {
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_reg_write(cmd, CO2_ADDR, reg_addr, reg_data, length);
esp_err_t ret = i2c_master_cmd_begin(CO2_I2C_NUM, cmd, pdMS_TO_TICKS(timeout_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), CO2_ADDR, reg_addr, length);
}
return ret;
}
static esp_err_t reg_write(uint8_t reg_addr, const uint8_t *reg_data, uint32_t length, uint32_t timeout_ms) {
BaseType_t suc = xSemaphoreTake(g_mux_i2c, pdMS_TO_TICKS(500));
if (suc != pdPASS) {
ESP_LOGE(TAG, "Sema fail");
return ESP_ERR_TIMEOUT;
}
if (!wake_up()) {
xSemaphoreGive(g_mux_i2c);
return ESP_ERR_TIMEOUT;
}
esp_err_t rv = do_reg_write(reg_addr, reg_data, length, timeout_ms);
xSemaphoreGive(g_mux_i2c);
vTaskDelay(pdMS_TO_TICKS(12));
return rv;
}
void co2_read_task(void *param) {
// i2c is protected by a semaphore inside the driver, no need to worry about it here
// continuous is the default
vTaskDelay(pdMS_TO_TICKS(35));
{
uint8_t pld[] = {0x00, 0x07, 0x00, 0x05};
reg_write(0x96, pld, 4, TIMEOUT_MS);
}
while (1) {
vTaskDelay(pdMS_TO_TICKS(7000));
uint8_t data[4] = {};
if (ESP_OK == reg_read(0x06, data, 4, TIMEOUT_MS)) {
uint16_t filtered = (data[0] << 8) | data[1];
uint16_t unfiltered = (data[2] << 8) | data[3];
ESP_LOGI(TAG, "CO2 ppm %d, raw %d", filtered, unfiltered);
if (pdPASS == xSemaphoreTake(g_mux_data_report, pdMS_TO_TICKS(1000))) {
if (filtered > 400 && filtered < 5000) {
g_data_report.co2_ppm = (float) filtered;
g_data_report.co2_ready = true;
} else {
g_data_report.co2_ready = false;
}
}
xSemaphoreGive(g_mux_data_report);
}
}
}

@ -0,0 +1,12 @@
/**
* TODO file description
*
* Created on 2021/12/12.
*/
#ifndef ESPNODE_CO2_SENSOR_H
#define ESPNODE_CO2_SENSOR_H
void co2_read_task(void *param);
#endif //ESPNODE_CO2_SENSOR_H

@ -0,0 +1,4 @@
#include "data_report.h"
SemaphoreHandle_t g_mux_data_report;
struct data_report g_data_report = {};

@ -0,0 +1,35 @@
/**
* TODO file description
*
* Created on 2021/12/13.
*/
#ifndef ESPNODE_DATA_REPORT_H
#define ESPNODE_DATA_REPORT_H
#include <stdint-gcc.h>
#include <stdbool.h>
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
struct data_report {
bool iaq_ready;
float iaq;
float iaq_static;
float iaq_co2_ppm_equiv;
float iaq_voc_ppm_equiv;
bool thpg_ready;
float temperature;
float pressure;
float humidity;
float gasr;
bool co2_ready;
float co2_ppm;
};
extern SemaphoreHandle_t g_mux_data_report;
extern struct data_report g_data_report;
#endif //ESPNODE_DATA_REPORT_H

@ -1,12 +1,21 @@
#include <esp_log.h>
#include "periph_init.h"
#include "driver/i2c.h"
#include "data_report.h"
static const char *TAG = "periph_init";
SemaphoreHandle_t g_mux_i2c;
esp_err_t periph_init() {
esp_err_t rv;
g_mux_i2c = xSemaphoreCreateMutex();
assert(g_mux_i2c);
g_mux_data_report = xSemaphoreCreateMutex(); // XXX move elsewhere
assert(g_mux_data_report);
ESP_LOGI(TAG, "Init I2C");
int i2c_master_port = I2C_NUM_0;
@ -29,5 +38,14 @@ esp_err_t periph_init() {
return rv;
}
gpio_config_t gconf = {
.pin_bit_mask = (1 << CONFIG_PIN_SENSEAIR_NRDY),
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE, // TODO
};
gpio_config(&gconf);
return ESP_OK;
}

@ -8,6 +8,10 @@
#define ESPNODE_PERIPH_INIT_H
#include <esp_err.h>
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
extern SemaphoreHandle_t g_mux_i2c;
esp_err_t periph_init();

@ -1,7 +1,6 @@
//#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
#include <esp_log.h>
#include <time.h>
#include <esp_err.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
@ -9,11 +8,12 @@
#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 250
#define VOC_I2C_TO_MS 1000
// Config overrides for BSEC
#define BME68X_PERIOD_POLL UINT32_C(5000)
@ -21,6 +21,7 @@ static const char *TAG = "voc";
#include "bme68x.h"
#include "bme68x_defs.h"
#include "bsec2.h"
#include "periph_init.h"
struct sensor_itf {
uint8_t dev_addr;
@ -69,6 +70,11 @@ static BME68X_INTF_RET_TYPE user_i2c_read(uint8_t reg_addr, uint8_t *reg_data, u
if (length == 0) {
return BME68X_OK;
}
BaseType_t suc = xSemaphoreTake(g_mux_i2c, pdMS_TO_TICKS(500));
if (suc != pdPASS) {
ESP_LOGE(TAG, "Sema fail");
return -1;
}
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);
@ -78,6 +84,7 @@ static BME68X_INTF_RET_TYPE user_i2c_read(uint8_t reg_addr, uint8_t *reg_data, u
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);
xSemaphoreGive(g_mux_i2c);
return ret == ESP_OK ? BME68X_OK : BME68X_E_COM_FAIL;
}
@ -86,6 +93,11 @@ static BME68X_INTF_RET_TYPE user_i2c_write(uint8_t reg_addr, const uint8_t *reg_
if (length == 0) {
return BME68X_OK;
}
BaseType_t suc = xSemaphoreTake(g_mux_i2c, pdMS_TO_TICKS(500));
if (suc != pdPASS) {
ESP_LOGE(TAG, "Sema fail");
return -1;
}
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);
@ -94,6 +106,7 @@ static BME68X_INTF_RET_TYPE user_i2c_write(uint8_t reg_addr, const uint8_t *reg_
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);
}
xSemaphoreGive(g_mux_i2c);
return ret == ESP_OK ? BME68X_OK : BME68X_E_COM_FAIL;
}
@ -133,39 +146,48 @@ static void new_data_callback(
// 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);
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);
break;
case BSEC_OUTPUT_RAW_TEMPERATURE:
ESP_LOGI(TAG, "\tRAW_TEMPERATURE = %f", output.signal);
my_report.iaq_voc_ppm_equiv = output.signal;
break;
case BSEC_OUTPUT_RAW_PRESSURE:
ESP_LOGI(TAG, "\tRAW_PRESSURE = %f", output.signal);
break;
case BSEC_OUTPUT_RAW_HUMIDITY:
ESP_LOGI(TAG, "\tRAW_HUMIDITY = %f", output.signal);
my_report.pressure = output.signal;
my_report.thpg_ready = true;
break;
case BSEC_OUTPUT_RAW_GAS:
ESP_LOGI(TAG, "\tRAW_GAS = %f", output.signal);
my_report.gasr = 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);
@ -177,6 +199,30 @@ static void new_data_callback(
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.humidity = my_report.humidity;
g_data_report.temperature = my_report.temperature;
g_data_report.pressure = my_report.pressure;
g_data_report.gasr = my_report.gasr;
} else {
g_data_report.thpg_ready = false;
}
if (my_report.iaq_ready) {
g_data_report.iaq_ready = true;
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) {
@ -199,9 +245,7 @@ void voc_read_task(void *param) {
BSEC_OUTPUT_CO2_EQUIVALENT,
BSEC_OUTPUT_BREATH_VOC_EQUIVALENT,
BSEC_OUTPUT_RAW_TEMPERATURE, // XXX probably useless, we can get these directly from the data struct
BSEC_OUTPUT_RAW_PRESSURE,
BSEC_OUTPUT_RAW_HUMIDITY,
BSEC_OUTPUT_RAW_GAS,
BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE,

@ -10,6 +10,7 @@
#include "utils.h"
#include "www_files_enum.h"
#include "data_report.h"
static const char *TAG = "websrv";
@ -81,6 +82,42 @@ static esp_err_t handler_reboot(httpd_req_t *req) {
esp_restart();
}
static esp_err_t handler_sample(httpd_req_t *req) {
char *msg = malloc(256);
if (!msg) return ESP_ERR_NO_MEM;
// TODO use cJSON
char *wp = msg;
wp += sprintf(wp, "{");
bool need_comma = false;
if (g_data_report.iaq_ready) {
wp += sprintf(wp, "\"iaq\":%.2f,\"iaq_s\":%.2f,\"iaq_co2\":%.2f,\"iaq_voc\":%.2f",
g_data_report.iaq,g_data_report.iaq_static, g_data_report.iaq_co2_ppm_equiv, g_data_report.iaq_co2_ppm_equiv);
need_comma = true;
}
if (g_data_report.thpg_ready) {
wp += sprintf(wp, "%s\"temp\":%.2f,\"hum\":%.2f,\"pres\":%.1f,\"gasr\":%.1f",
need_comma?",":"",
g_data_report.temperature,g_data_report.humidity, g_data_report.pressure, g_data_report.gasr);
need_comma = true;
}
if (g_data_report.co2_ready) {
wp += sprintf(wp, "%s\"co2\":%.2f",
need_comma?",":"",
g_data_report.co2_ppm);
}
sprintf(wp, "}");
esp_err_t rv = httpd_resp_send(req, msg, -1);
free(msg);
return rv;
}
/* An HTTP GET handler */
static esp_err_t handler_staticfiles(httpd_req_t *r) {
const struct embedded_file_info *file;
@ -133,6 +170,11 @@ static const httpd_uri_t routes[] = {
.method = HTTP_GET,
.handler = handler_reboot,
},
{
.uri = "/sample",
.method = HTTP_GET,
.handler = handler_sample,
},
{
.uri = "*", // any file except protected (e.g. not HTML, PEM etc)
.method = HTTP_GET,

Loading…
Cancel
Save