Merge branch 'modbus'

master
Ondřej Hruška 3 years ago
commit 15f09e1d9e
  1. 2
      main/CMakeLists.txt
  2. 12
      main/Kconfig.projbuild
  3. 5
      main/app_main.c
  4. 284
      main/co2_sensor.c
  5. 164
      main/co2_sensor_i2c.c
  6. 6
      main/data_report.h
  7. 98
      main/modbus_crc.c
  8. 37
      main/modbus_crc.h
  9. 167
      main/modbus_fn.c
  10. 85
      main/modbus_fn.h
  11. 64
      main/periph_init.c
  12. 18
      main/settings.c
  13. 5
      main/settings.h
  14. 75
      main/voc_sensor.c
  15. 3
      sdkconfig

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

@ -16,6 +16,18 @@ menu "App Configuration"
config PIN_I2C_SCL1 config PIN_I2C_SCL1
int "I2C0 SCL" int "I2C0 SCL"
default 18 default 18
config PIN_CO2_NRDY
int "CO2 NRDY"
default 23
config PIN_CO2_COMSEL
int "CO2 COMSEL"
default 13
config PIN_CO2_EN
int "CO2 EN"
default 15
endmenu endmenu
menu "Console" menu "Console"

@ -59,9 +59,8 @@ void app_main(void) {
vTaskDelay(pdMS_TO_TICKS(1000)); vTaskDelay(pdMS_TO_TICKS(1000));
xTaskCreatePinnedToCore(voc_read_task, "VOC", 4096, NULL, PRIO_NORMAL, NULL, 1); xTaskCreate(voc_read_task, "VOC", 4096, NULL, PRIO_NORMAL, NULL);
xTaskCreatePinnedToCore(co2_read_task, "CO2", 4096, NULL, PRIO_NORMAL, NULL, 1); xTaskCreate(co2_read_task, "CO2", 4096, NULL, PRIO_NORMAL, NULL);
// xTaskCreate(co2_read_task, "CO2", 4096, NULL, PRIO_NORMAL, NULL);
console_init(NULL); console_init(NULL);
register_console_commands(); register_console_commands();

@ -1,4 +1,4 @@
#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG //#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
#include "co2_sensor.h" #include "co2_sensor.h"
@ -7,147 +7,204 @@
#include <freertos/FreeRTOS.h> #include <freertos/FreeRTOS.h>
#include <freertos/task.h> #include <freertos/task.h>
#include <driver/i2c.h> #include <driver/i2c.h>
#include <sys/time.h>
#include <hal/i2c_hal.h>
#include "voc_sensor.h"
#include "i2c_utils.h"
#include "periph_init.h" #include "periph_init.h"
#include "data_report.h" #include "data_report.h"
#include "modbus_fn.h"
#include "settings.h"
#include <driver/uart.h>
#include <string.h>
#include <stddef.h>
#include <inttypes.h>
static const char *TAG = "co2"; static const char *TAG = "co2";
#define CO2_ADDR 104 #define CO2_ADDR 104
#define CO2_I2C_NUM I2C_NUM_1 #define CO2_UART_NUM 1
#define TIMEOUT_MS 500 #define TIMEOUT_MS 500
// this works, but the bus is fucked up static int mb_write_and_read(uint8_t *buffer, int num, size_t resplen) {
bool wake_up() { if (num < 0) {
esp_err_t suc; ESP_LOGE(TAG, "MB build failed");
#define TRY(x) suc=x; if(suc!=ESP_OK) return suc; return 0;
for (int i = 0; i < 5; i++) { }
ESP_LOGD(TAG, "Wake up try %d", i);
uint8_t dummy;
i2c_cmd_handle_t chain;
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));
i2c_master_cmd_begin(CO2_I2C_NUM, chain, pdMS_TO_TICKS(70));
i2c_cmd_link_delete(chain);
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, true)); // TODO expect ack?
TRY(i2c_master_read(chain, &dummy, 1, I2C_MASTER_LAST_NACK));
TRY(i2c_master_stop(chain));
suc = i2c_master_cmd_begin(CO2_I2C_NUM, chain, pdMS_TO_TICKS(80));
i2c_cmd_link_delete(chain);
if (suc == ESP_OK) {
ESP_LOGD(TAG, "Woken up at try %d", i);
return true;
}
}
ESP_LOGE(TAG, "Fail to wake up");
return false;
#undef TRY
}
ESP_LOGD(TAG, "Sending");
ESP_LOG_BUFFER_HEXDUMP(TAG, buffer, num, ESP_LOG_DEBUG);
uart_write_bytes(CO2_UART_NUM, buffer, num);
static esp_err_t do_reg_read(uint8_t reg_addr, uint8_t *reg_data, uint32_t length, uint32_t timeout_ms) { ESP_LOGD(TAG, "Expect resp of %d bytes", resplen);
i2c_cmd_handle_t cmd = i2c_cmd_link_create(); num = uart_read_bytes(CO2_UART_NUM, buffer, resplen, pdMS_TO_TICKS(500));
i2c_reg_read(cmd, CO2_ADDR, reg_addr, reg_data, length); ESP_LOGD(TAG, "Received %d bytes", num);
esp_err_t ret = i2c_master_cmd_begin(CO2_I2C_NUM, cmd, pdMS_TO_TICKS(TIMEOUT_MS)); ESP_LOG_BUFFER_HEXDUMP(TAG, buffer, num, ESP_LOG_DEBUG);
i2c_cmd_link_delete(cmd); return num;
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;
} }
static esp_err_t reg_read(uint8_t reg_addr, uint8_t *reg_data, uint32_t length, uint32_t timeout_ms) { static void write_saved_calib_to_sensor() {
// BaseType_t suc = xSemaphoreTake(g_mux_i2c, pdMS_TO_TICKS(SEMA_TIMEOUT_MS)); static uint8_t buffer[40];
// if (suc != pdPASS) { size_t resplen;
// ESP_LOGE(TAG, "Sema fail"); int num;
// return ESP_ERR_TIMEOUT; int resp;
// }
if (!wake_up()) { if (g_Settings.co2_calib[0] > 0) {
// xSemaphoreGive(g_mux_i2c); ESP_LOGI(TAG, "Writing saved calib to sensor");
return ESP_ERR_TIMEOUT;
} /* Write calib */
esp_err_t rv = do_reg_read(reg_addr, reg_data, length, timeout_ms); resplen = 0;
// xSemaphoreGive(g_mux_i2c); num = mb_build_fc16(buffer, 128, &resplen, 104, 4, g_Settings.co2_calib, 5);
// ets_delay_us(12000); num = mb_write_and_read(buffer, num, resplen);
return rv; resp = mb_parse_fc16(buffer, num);
if (resp < 0) {
ESP_LOGE(TAG, "Fail to write calib constants!");
}
} else {
ESP_LOGW(TAG, "No saved calib to write to sensor!");
}
} }
static esp_err_t do_reg_write(uint8_t reg_addr, const uint8_t *reg_data, uint32_t length, uint32_t timeout_ms) { void co2_restart(bool restore_calib) {
i2c_cmd_handle_t cmd = i2c_cmd_link_create(); ESP_LOGI(TAG, "Restarting CO2 sensor");
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)); gpio_set_level(CONFIG_PIN_CO2_EN, 0);
i2c_cmd_link_delete(cmd); vTaskDelay(pdMS_TO_TICKS(100));
if (ret != ESP_OK) { gpio_set_level(CONFIG_PIN_CO2_EN, 1);
ESP_LOGE(TAG, "I2C write error: %s, devaddr %x, reg %d, len %d", esp_err_to_name(ret), CO2_ADDR, reg_addr, length); vTaskDelay(pdMS_TO_TICKS(50));
if (restore_calib) {
write_saved_calib_to_sensor();
} }
return ret;
} }
static esp_err_t reg_write(uint8_t reg_addr, const uint8_t *reg_data, uint32_t length, uint32_t timeout_ms) { static bool ppm_looks_valid(uint16_t ppm) {
// BaseType_t suc = xSemaphoreTake(g_mux_i2c, pdMS_TO_TICKS(SEMA_TIMEOUT_MS)); return ppm > 400 && ppm < 5000;
// 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);
// ets_delay_us(12000);
return rv;
} }
void co2_read_task(void *param) { void co2_read_task(void *param) {
// i2c is protected by a semaphore inside the driver, no need to worry about it here (void) param;
// continuous is the default
vTaskDelay(pdMS_TO_TICKS(500)); vTaskDelay(pdMS_TO_TICKS(500));
uint8_t pld[] = {0x00, 0x05, 0x00, 0x05}; // TODO
if (ESP_OK == reg_write(0x96, pld, 4, TIMEOUT_MS)) {
ESP_LOGI(TAG, "CO2 init OK"); static uint8_t buffer[128];
} static uint16_t values[32];
int qty;
size_t resplen;
int num;
bool init_suc = false; write_saved_calib_to_sensor();
vTaskDelay(pdMS_TO_TICKS(250));
const uint32_t read_cycle_time_ticks = 10 * 1000;
const uint32_t calib_persist_time_ticks = 12 * 3600 * 1000;
_Static_assert(configTICK_RATE_HZ == 1000, "1kHz tick");
uint32_t last_calib_persist = xTaskGetTickCount();
ESP_LOGD(TAG, "Calib persist time = %d ticks", calib_persist_time_ticks);
int num_fails = 0;
uint16_t ppm = 0;
while (1) { while (1) {
ppm = 0;
vTaskDelay(read_cycle_time_ticks);
vTaskDelay(pdMS_TO_TICKS(2000)); if (num_fails > 10) {
ESP_LOGW(TAG, "Too many CO2 fails, restarting sensor");
co2_restart(true);
num_fails = 0;
}
uint8_t data[4] = {}; resplen = 0;
num = mb_build_fc4(buffer, 128, &resplen, 104, 0, 5);
num = mb_write_and_read(buffer, num, resplen);
qty = mb_parse_fc4(buffer, num, values, 32);
if (qty < 0) {
num_fails++;
goto next;
}
for (int retry = 0; retry < 3; retry++) { // Read was OK, reset the fail counter
if (ESP_OK == reg_read(0x06, data, 4, TIMEOUT_MS)) { num_fails = 0;
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); uint16_t status = values[0];
bool co2_needs_restart = false;
bool calibration_error = false;
if (status != 0) {
if (status & 0x01) {
ESP_LOGE(TAG, "CO2 fatal error");
co2_needs_restart = true;
}
if (status & 0x02) {
ESP_LOGE(TAG, "CO2 I2C communication error");
}
if (status & 0x04) {
ESP_LOGE(TAG, "CO2 internal I2C operation error");
}
if (status & 0x08) {
ESP_LOGE(TAG, "CO2 calibration error");
calibration_error = true;
}
if (status & 0x10) {
ESP_LOGE(TAG, "CO2 self-diagnostics error");
co2_needs_restart = true;
}
if (status & 0x20) {
ESP_LOGE(TAG, "CO2 out of range error");
// nonsense, this means calibration failed
calibration_error = true;
}
if (status & 0x40) {
ESP_LOGE(TAG, "CO2 memory error");
co2_needs_restart = true;
}
if (status & 0x80) {
ESP_LOGE(TAG, "CO2 external I2C error");
}
if (co2_needs_restart) {
co2_restart(!calibration_error);
if (calibration_error) {
// Prevent persisting values too soon, give it some time to calibrate
last_calib_persist = xTaskGetTickCount();
}
}
goto next;
}
ppm = values[3];
ESP_LOGI(TAG, "CO2 ppm %d", ppm);
if (!ppm_looks_valid(ppm)) {
ESP_LOGW(TAG, "CO2 measures nonsense!");
co2_restart(true);
} else {
// CO2 measurement looks OK
uint32_t tickNow = xTaskGetTickCount();
uint32_t elapsed = tickNow - last_calib_persist;
if (elapsed > calib_persist_time_ticks) {
ESP_LOGI(TAG, "Read & persist CO2 calibration");
last_calib_persist = tickNow;
resplen = 0;
num = mb_build_fc3(buffer, 128, &resplen, 104, 4, 5);
num = mb_write_and_read(buffer, num, resplen);
qty = mb_parse_fc3(buffer, num, values, 32);
if (qty < 0) {
num_fails++;
}
if (qty == 5) {
memcpy(g_Settings.co2_calib, values, 5 * 2);
settings_persist(SETTINGS_co2_calib);
}
}
}
next:
if (pdPASS == xSemaphoreTake(g_mux_data_report, pdMS_TO_TICKS(750))) { if (pdPASS == xSemaphoreTake(g_mux_data_report, pdMS_TO_TICKS(750))) {
if (filtered > 400 && filtered < 5000) { if (ppm_looks_valid(ppm)) {
g_data_report.co2_ppm = (float) filtered; g_data_report.co2_ppm = (float) ppm;
g_data_report.co2_ready = true; g_data_report.co2_ready = true;
g_data_report.co2_timestamp = xTaskGetTickCount(); g_data_report.co2_timestamp = xTaskGetTickCount();
} else { } else {
@ -155,10 +212,5 @@ void co2_read_task(void *param) {
} }
} }
xSemaphoreGive(g_mux_data_report); xSemaphoreGive(g_mux_data_report);
break;
}
vTaskDelay(pdMS_TO_TICKS(50));
}
} }
} }

@ -0,0 +1,164 @@
#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
#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 <hal/i2c_hal.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_1
#define TIMEOUT_MS 500
// this works, but the bus is fucked up
bool wake_up() {
esp_err_t suc;
#define TRY(x) suc=x; if(suc!=ESP_OK) return suc;
for (int i = 0; i < 5; i++) {
ESP_LOGD(TAG, "Wake up try %d", i);
uint8_t dummy;
i2c_cmd_handle_t chain;
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));
i2c_master_cmd_begin(CO2_I2C_NUM, chain, pdMS_TO_TICKS(70));
i2c_cmd_link_delete(chain);
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, true)); // TODO expect ack?
TRY(i2c_master_read(chain, &dummy, 1, I2C_MASTER_LAST_NACK));
TRY(i2c_master_stop(chain));
suc = i2c_master_cmd_begin(CO2_I2C_NUM, chain, pdMS_TO_TICKS(80));
i2c_cmd_link_delete(chain);
if (suc == ESP_OK) {
ESP_LOGD(TAG, "Woken up at try %d", i);
return true;
}
}
ESP_LOGE(TAG, "Fail to wake up");
return false;
#undef TRY
}
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;
}
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(SEMA_TIMEOUT_MS));
// 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(SEMA_TIMEOUT_MS));
// 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);
// ets_delay_us(12000);
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(500));
uint8_t pld[] = {0x00, 0x05, 0x00, 0x05};
if (ESP_OK == reg_write(0x96, pld, 4, TIMEOUT_MS)) {
ESP_LOGI(TAG, "CO2 init OK");
}
bool init_suc = false;
vTaskDelay(pdMS_TO_TICKS(250));
while (1) {
vTaskDelay(pdMS_TO_TICKS(2000));
uint8_t data[4] = {};
for (int retry = 0; retry < 3; retry++) {
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(750))) {
if (filtered > 400 && filtered < 5000) {
g_data_report.co2_ppm = (float) filtered;
g_data_report.co2_ready = true;
g_data_report.co2_timestamp = xTaskGetTickCount();
} else {
g_data_report.co2_ready = false;
}
}
xSemaphoreGive(g_mux_data_report);
break;
}
vTaskDelay(pdMS_TO_TICKS(50));
}
}
}

@ -15,14 +15,14 @@
struct data_report { struct data_report {
bool iaq_ready; bool iaq_ready;
uint32_t iaq_timestamp; uint32_t iaq_timestamp; // timestamp in ticks
float iaq; float iaq;
float iaq_static; float iaq_static;
float iaq_co2_ppm_equiv; float iaq_co2_ppm_equiv;
float iaq_voc_ppm_equiv; float iaq_voc_ppm_equiv;
bool thpg_ready; bool thpg_ready;
uint32_t thpg_timestamp; uint32_t thpg_timestamp; // timestamp in ticks
float temperature; float temperature;
float temperature_raw; float temperature_raw;
float pressure; float pressure;
@ -31,7 +31,7 @@ struct data_report {
float gas_raw; float gas_raw;
bool co2_ready; bool co2_ready;
uint32_t co2_timestamp; uint32_t co2_timestamp; // timestamp in ticks
float co2_ppm; float co2_ppm;
}; };

@ -0,0 +1,98 @@
/*
* FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU.
* Copyright (c) 2006 Christian Walter <wolti@sil.at>
* All rights reserved.
*
* 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. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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: $Id: mbcrc.c,v 1.7 2007/02/18 23:50:27 wolti Exp $
*/
/* ----------------------- Platform includes --------------------------------*/
#include "modbus_crc.h"
#include <stdint.h>
static const uint8_t aucCRCHi[] = {
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40
};
static const uint8_t aucCRCLo[] = {
0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7,
0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E,
0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9,
0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC,
0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32,
0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D,
0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38,
0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF,
0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1,
0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4,
0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB,
0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA,
0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0,
0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97,
0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E,
0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89,
0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83,
0x41, 0x81, 0x80, 0x40
};
uint16_t modbus_crc(const uint8_t *pucFrame, uint16_t usLen)
{
uint8_t ucCRCHi = 0xFF;
uint8_t ucCRCLo = 0xFF;
int iIndex;
while (usLen--) {
iIndex = ucCRCLo ^ *(pucFrame++);
ucCRCLo = (uint8_t) (ucCRCHi ^ aucCRCHi[iIndex]);
ucCRCHi = aucCRCLo[iIndex];
}
// return (uint16_t) (ucCRCHi << 8 | ucCRCLo);
return (uint16_t) (ucCRCLo << 8 | ucCRCHi);
}

@ -0,0 +1,37 @@
/*
* FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU.
* Copyright (c) 2006 Christian Walter <wolti@sil.at>
* All rights reserved.
*
* 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. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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: $Id: mbcrc.h,v 1.5 2006/12/07 22:10:34 wolti Exp $
*/
#ifndef _MODBUS_CRC_H
#define _MODBUS_CRC_H
#include <stdint.h>
uint16_t modbus_crc(const uint8_t *pucFrame, uint16_t usLen);
#endif

@ -0,0 +1,167 @@
#include <assert.h>
#include "modbus_fn.h"
#include "modbus_crc.h"
#include <esp_log.h>
static const char *TAG = "mb";
static bool crc_matches(const uint8_t *buf, size_t len) {
assert(buf);
uint16_t real = modbus_crc(buf, len - 2);
uint16_t given = ((uint16_t) buf[len - 2] << 8) | (uint16_t) buf[len - 1];
return real == given;
}
// Read holding registers
int mb_build_fc3(uint8_t *buf, size_t len, size_t *resplen, uint8_t mbAddr, uint16_t ref, uint16_t qty) {
assert(buf);
assert(resplen);
if (len < 8) {
ESP_LOGE(TAG, "Buf len too short for FC3");
return -1;
}
if (qty > 127) {
ESP_LOGE(TAG, "FC3 qty too high");
return -1;
}
size_t wp = 0;
buf[wp++] = mbAddr;
buf[wp++] = 0x03;
buf[wp++] = (ref & 0xFF00) >> 8;
buf[wp++] = (ref & 0xFF);
buf[wp++] = (qty & 0xFF00) >> 8;
buf[wp++] = (qty & 0xFF);
uint16_t crc = modbus_crc(buf, wp);
buf[wp++] = (crc & 0xFF00) >> 8;
buf[wp++] = (crc & 0xFF);
*resplen = 5 + qty * 2;
return (int) wp;
}
static int mb_parse_fc3_4(const uint8_t *buf, size_t len, uint16_t *dst, size_t capacity, int fcX) {
assert(buf);
if (!crc_matches(buf, len)) {
ESP_LOGE(TAG, "FC%d CRC mismatch!", fcX);
return -1;
}
//11 03 06 AE41 5652 4340 49AD
if (len == 5 && buf[1] == (0x80+fcX)) {
ESP_LOGE(TAG, "FC%d exception: %d", fcX, buf[2]);
return -1;
}
if (buf[1] != fcX) {
ESP_LOGE(TAG, "FC%d decode fail: not FC%d", fcX, fcX);
return -1;
}
int num = buf[2] / 2; // this is the number of bytes
if (capacity < num) {
ESP_LOGE(TAG, "FC%d decode fail: dst too small for %d registers", fcX, num);
return -1;
}
for (int i = 0; i < num; i++) {
*dst++ = (((uint16_t) buf[3 + i * 2]) << 8) | (((uint16_t) buf[3 + i * 2 + 1]));
}
return num;
}
int mb_parse_fc3(const uint8_t *buf, size_t len, uint16_t *dst, size_t capacity) {
return mb_parse_fc3_4(buf, len, dst, capacity, 3);
}
//// Read input registers
int mb_build_fc4(uint8_t *buf, size_t len, size_t *resplen, uint8_t mbAddr, uint16_t ref, uint16_t qty) {
assert(buf);
assert(resplen);
if (len < 8) {
ESP_LOGE(TAG, "Buf len too short for FC4");
return -1;
}
if (qty > 127) {
ESP_LOGE(TAG, "FC3 qty too high");
return -1;
}
size_t wp = 0;
buf[wp++] = mbAddr;
buf[wp++] = 0x04;
buf[wp++] = (ref & 0xFF00) >> 8;
buf[wp++] = (ref & 0xFF);
buf[wp++] = (qty & 0xFF00) >> 8;
buf[wp++] = (qty & 0xFF);
uint16_t crc = modbus_crc(buf, wp);
buf[wp++] = (crc & 0xFF00) >> 8;
buf[wp++] = (crc & 0xFF);
*resplen = 5 + qty * 2;
return (int) wp;
}
int mb_parse_fc4(const uint8_t *buf, size_t len, uint16_t *dst, size_t capacity) {
return mb_parse_fc3_4(buf, len, dst, capacity, 4);
}
// Write holding registers
int mb_build_fc16(uint8_t *buf, size_t len, size_t *resplen, uint8_t mbAddr, uint16_t ref, const uint16_t *data, uint16_t qty) {
assert(buf);
assert(data);
assert(resplen);
if (len < 9 + qty * 2) {
ESP_LOGE(TAG, "Buf len too short for FC16, or bad len");
return -1;
}
if (qty > 127) {
ESP_LOGE(TAG, "FC16 qty too high");
return -1;
}
size_t wp = 0;
buf[wp++] = mbAddr;
buf[wp++] = 0x10;
buf[wp++] = (ref & 0xFF00) >> 8;
buf[wp++] = (ref & 0xFF);
buf[wp++] = (qty & 0xFF00) >> 8;
buf[wp++] = (qty & 0xFF);
buf[wp++] = qty * 2;
for (int i = 0; i < qty; i++) {
buf[wp++] = (data[i] & 0xFF00) >> 8;
buf[wp++] = (data[i] & 0xFF);
}
uint16_t crc = modbus_crc(buf, wp);
buf[wp++] = (crc & 0xFF00) >> 8;
buf[wp++] = (crc & 0xFF);
*resplen = 8;
return (int) wp;
}
bool mb_parse_fc16(const uint8_t *buf, size_t len)
{
assert(buf);
if (!crc_matches(buf, len)) {
ESP_LOGE(TAG, "FC16 CRC mismatch!");
return -1;
}
//11 10 0001 0002 1298
if (len == 5 && buf[1] == 0x90) {
ESP_LOGE(TAG, "FC16 exception: %d", buf[2]);
return -1;
}
if (buf[1] != 16) {
ESP_LOGE(TAG, "FC16 decode fail: not FC16");
return -1;
}
return 0;
}

@ -0,0 +1,85 @@
/**
* TODO file description
*
* Created on 2022/01/02.
*/
#ifndef ESPNODE_MODBUS_FN_H
#define ESPNODE_MODBUS_FN_H
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
/**
* Build "read holding registers" packet
*
* @param buf buf to write to
* @param len buf len
* @param resplen expected response length (if not exception)
* @param mbAddr mb address
* @param ref first register number
* @param qty register quantity
* @return number of buf bytes if success, negative if error
*/
int mb_build_fc3(uint8_t *buf, size_t len, size_t *resplen, uint8_t mbAddr, uint16_t ref, uint16_t qty);
/**
* Parse "read holding registers" response
*
* @param buf buf with the response bytes
* @param len response buf len
* @param dst store results here
* @param capacity capacity of "dst"
* @return num of results, negative if error
*/
int mb_parse_fc3(const uint8_t *buf, size_t len, uint16_t *dst, size_t capacity);
/**
* Build "read input registers" packet
*
* @param buf buf to write to
* @param len buf len
* @param resplen expected response length (if not exception)
* @param mbAddr mb address
* @param ref first register number
* @param qty register quantity
* @return number of buf bytes if success, negative if error
*/
int mb_build_fc4(uint8_t *buf, size_t len, size_t *resplen, uint8_t mbAddr, uint16_t ref, uint16_t qty);
/**
* Parse "read input registers" response
*
* @param buf buf with the response bytes
* @param len response buf len
* @param dst store results here
* @param capacity capacity of "dst"
* @return num of results, negative if error
*/
int mb_parse_fc4(const uint8_t *buf, size_t len, uint16_t *dst, size_t capacity);
/**
* Build "write holding registers" packet
*
* @param buf buf to write to
* @param len buf len
* @param resplen expected response length (if not exception)
* @param mbAddr mb address
* @param ref first register number
* @param data register data to write, as an array
* @param qty register quantity
* @return number of buf bytes if success, negative if error
*/
int mb_build_fc16(uint8_t *buf, size_t len, size_t *resplen, uint8_t mbAddr, uint16_t ref, const uint16_t *data, uint16_t qty);
/**
* Parse "write holding registers" response
*
* @param buf buf with the response bytes
* @param len response buf len
* @return 0 if success, negative if error
*/
bool mb_parse_fc16(const uint8_t *buf, size_t len);
#endif //ESPNODE_MODBUS_FN_H

@ -3,6 +3,8 @@
#include "driver/i2c.h" #include "driver/i2c.h"
#include "data_report.h" #include "data_report.h"
#include <driver/uart.h>
static const char *TAG = "periph_init"; static const char *TAG = "periph_init";
esp_err_t periph_init() { esp_err_t periph_init() {
@ -38,6 +40,7 @@ esp_err_t periph_init() {
// senseair i2c // senseair i2c
{ {
#if 0
int i2c_master_port = I2C_NUM_1; int i2c_master_port = I2C_NUM_1;
i2c_config_t conf2 = { i2c_config_t conf2 = {
.mode = I2C_MODE_MASTER, .mode = I2C_MODE_MASTER,
@ -57,6 +60,67 @@ esp_err_t periph_init() {
ESP_LOGE(TAG, "Err in i2c_driver_install"); ESP_LOGE(TAG, "Err in i2c_driver_install");
return rv; return rv;
} }
#else
int i2c_co2_port = I2C_NUM_1;
const uart_config_t uart_config = {
.baud_rate = 9600,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
.source_clk = UART_SCLK_APB,
//.use_ref_tick = true
};
ESP_ERROR_CHECK(uart_driver_install(i2c_co2_port,
/* rxbuf */ 256, /* txbuf */ 256, /* que */ 0, /* uart que */ NULL, /* alloc flags */ 0));
ESP_ERROR_CHECK( uart_param_config(i2c_co2_port, &uart_config) );
ESP_ERROR_CHECK(uart_set_pin(i2c_co2_port,
// CONFIG_PIN_I2C_SCL1,
// CONFIG_PIN_I2C_SDA1,
CONFIG_PIN_I2C_SDA1,
CONFIG_PIN_I2C_SCL1,
UART_PIN_NO_CHANGE,
UART_PIN_NO_CHANGE));
// Set UART pins(TX, RX, RTS, CTS)
#endif
}
// outputs
{
gpio_config_t gpioconf = {
.pin_bit_mask = (1 << CONFIG_PIN_CO2_COMSEL) | (1 << CONFIG_PIN_CO2_EN),
.mode = GPIO_MODE_OUTPUT,
};
rv = gpio_config(&gpioconf);
if (rv != ESP_OK) {
ESP_LOGE(TAG, "Err in gpio_config");
return rv;
}
gpio_set_level(CONFIG_PIN_CO2_COMSEL, 1); // low=I2C
gpio_set_level(CONFIG_PIN_CO2_EN, 1); // active high
}
// input
{
gpio_config_t gpioconf = {
.pin_bit_mask = (1 << CONFIG_PIN_CO2_NRDY),
.mode = GPIO_MODE_INPUT,
.pull_up_en = 1,
.pull_down_en = 0,
.intr_type = GPIO_INTR_DISABLE,
};
rv = gpio_config(&gpioconf);
if (rv != ESP_OK) {
ESP_LOGE(TAG, "Err in gpio_config");
return rv;
}
} }
return ESP_OK; return ESP_OK;

@ -118,12 +118,20 @@ void settings_load(void)
NVSCHECK(nvs_get_str(storage, "ntp_srv", g_Settings.ntp_srv, &capacity)); NVSCHECK(nvs_get_str(storage, "ntp_srv", g_Settings.ntp_srv, &capacity));
ensure_str_terminated(g_Settings.ntp_srv, NTP_SRV_LEN); ensure_str_terminated(g_Settings.ntp_srv, NTP_SRV_LEN);
} }
{
size_t capacity = 10;
NVSCHECK(nvs_get_blob(storage, "co2_calib", g_Settings.co2_calib, &capacity));
}
{
size_t capacity = BSEC_MAX_STATE_BLOB_SIZE;
NVSCHECK(nvs_get_blob(storage, "bsec_state", g_Settings.bsec_state, &capacity));
}
} }
void settings_persist(enum settings_key_enum what) void settings_persist(enum settings_key_enum what)
{ {
esp_err_t rv; esp_err_t rv;
char name[24]; // char name[24];
#undef NVSCHECK #undef NVSCHECK
#define NVSCHECK(callback) \ #define NVSCHECK(callback) \
@ -152,6 +160,14 @@ void settings_persist(enum settings_key_enum what)
if (what==SETTINGS_ALL || what==SETTINGS_ntp_srv) { if (what==SETTINGS_ALL || what==SETTINGS_ntp_srv) {
NVSCHECK(nvs_set_str(storage, "ntp_srv", g_Settings.ntp_srv)); NVSCHECK(nvs_set_str(storage, "ntp_srv", g_Settings.ntp_srv));
} }
if (what==SETTINGS_ALL || what==SETTINGS_co2_calib) {
NVSCHECK(nvs_set_blob(storage, "co2_calib", (void*) g_Settings.co2_calib, 5*sizeof(uint16_t)));
}
if (what==SETTINGS_ALL || what==SETTINGS_bsec_state) {
NVSCHECK(nvs_set_blob(storage, "bsec_state", (void*) g_Settings.bsec_state, BSEC_MAX_STATE_BLOB_SIZE));
}
} }
uint16_t app_get_bootcount() uint16_t app_get_bootcount()

@ -10,6 +10,7 @@
#include <nvs.h> #include <nvs.h>
#include <driver/uart.h> #include <driver/uart.h>
#include <sdkconfig.h> #include <sdkconfig.h>
#include <bsec2.h>
#define CONSOLE_TELNET_PORT CONFIG_CONSOLE_TELNET_PORT #define CONSOLE_TELNET_PORT CONFIG_CONSOLE_TELNET_PORT
#define CONSOLE_PW_LEN CONFIG_CONSOLE_PW_LEN #define CONSOLE_PW_LEN CONFIG_CONSOLE_PW_LEN
@ -20,6 +21,8 @@ extern nvs_handle g_nvs_storage;
#define NTP_SRV_LEN 32 #define NTP_SRV_LEN 32
#define DEF_NTP_SRV "pool.ntp.org" #define DEF_NTP_SRV "pool.ntp.org"
#define CO2_CALIB_DEF ({0,0,0,0,0})
/* type , name , suffix , defval , generate load/save funcs /* type , name , suffix , defval , generate load/save funcs
* , , save fn - use 'none' if unused to avoid build errors * , , save fn - use 'none' if unused to avoid build errors
* , , , save fn var prefix */ * , , , save fn var prefix */
@ -32,6 +35,8 @@ extern nvs_handle g_nvs_storage;
X(char , console_pw, [CONSOLE_PW_LEN], "" , false , none , ) \ X(char , console_pw, [CONSOLE_PW_LEN], "" , false , none , ) \
X(bool , ntp_enable , , 1 , true , bool , &) \ X(bool , ntp_enable , , 1 , true , bool , &) \
X(char , ntp_srv ,[NTP_SRV_LEN], DEF_NTP_SRV , false, none , ) \ X(char , ntp_srv ,[NTP_SRV_LEN], DEF_NTP_SRV , false, none , ) \
X(uint16_t , co2_calib ,[5], {}, false, none , ) \
X(uint8_t , bsec_state ,[BSEC_MAX_STATE_BLOB_SIZE], {}, false, none , ) \
X(bool , dhcp_wd_enable , , 1 , true , bool , &) \ X(bool , dhcp_wd_enable , , 1 , true , bool , &) \
X(bool , dhcp_enable , , 1 , true , bool , &) \ X(bool , dhcp_enable , , 1 , true , bool , &) \
X(uint32_t , static_ip , , 0 , true , u32 , &) \ X(uint32_t , static_ip , , 0 , true , u32 , &) \

@ -23,6 +23,7 @@ static const char *TAG = "voc";
#include "bme68x_defs.h" #include "bme68x_defs.h"
#include "bsec2.h" #include "bsec2.h"
#include "periph_init.h" #include "periph_init.h"
#include "settings.h"
struct sensor_itf { struct sensor_itf {
uint8_t dev_addr; uint8_t dev_addr;
@ -30,7 +31,7 @@ struct sensor_itf {
static struct sensor_itf gas_sensor_intf = {}; static struct sensor_itf gas_sensor_intf = {};
static struct bme68x_dev gas_sensor = {}; static struct bme68x_dev gas_sensor = {};
static struct bsec2 gas_sensor_bsec = {}; static struct bsec2 hBsec = {};
void bsec2_errormsg(const char *msg) { void bsec2_errormsg(const char *msg) {
ESP_LOGE("BSEC", "%s", msg); ESP_LOGE("BSEC", "%s", msg);
@ -103,7 +104,7 @@ static esp_err_t voc_init(void) {
int8_t rslt; int8_t rslt;
gas_sensor_intf.dev_addr = BME68X_I2C_ADDR_LOW; gas_sensor_intf.dev_addr = BME68X_I2C_ADDR_LOW;
gas_sensor.intf_ptr = &gas_sensor_intf; gas_sensor.intf_ptr = &gas_sensor_intf;
gas_sensor.amb_temp = 25; // TODO set this from senseair! gas_sensor.amb_temp = 25;
gas_sensor.intf = BME68X_I2C_INTF; gas_sensor.intf = BME68X_I2C_INTF;
gas_sensor.read = user_i2c_read; gas_sensor.read = user_i2c_read;
gas_sensor.write = user_i2c_write; gas_sensor.write = user_i2c_write;
@ -134,66 +135,64 @@ static void new_data_callback(
return; return;
} }
// TODO do something with this
struct data_report my_report = {}; struct data_report my_report = {};
ESP_LOGI(TAG, "BSEC outputs: timestamp = %d", (int) (outputs->output[0].time_stamp / INT64_C(1000000))); ESP_LOGD(TAG, "BSEC outputs: timestamp = %d", (int) (outputs->output[0].time_stamp / INT64_C(1000000)));
for (uint8_t i = 0; i < outputs->nOutputs; i++) { for (uint8_t i = 0; i < outputs->nOutputs; i++) {
const bsecData output = outputs->output[i]; const bsecData output = outputs->output[i];
switch (output.sensor_id) { switch (output.sensor_id) {
case BSEC_OUTPUT_IAQ: case BSEC_OUTPUT_IAQ:
ESP_LOGI(TAG, "\tIAQ = %f, accuracy %d", output.signal, (int) output.accuracy); ESP_LOGD(TAG, " IAQ = %f, accuracy %d", output.signal, (int) output.accuracy);
my_report.iaq = output.signal; my_report.iaq = output.signal;
my_report.iaq_ready = (3 == (int) output.accuracy); // calibration finished my_report.iaq_ready = (3 == (int) output.accuracy); // calibration finished
break; break;
case BSEC_OUTPUT_STATIC_IAQ: case BSEC_OUTPUT_STATIC_IAQ:
ESP_LOGI(TAG, "\tSTATIC_IAQ = %f, accuracy %d", output.signal, (int) output.accuracy); ESP_LOGD(TAG, " STATIC_IAQ = %f, accuracy %d", output.signal, (int) output.accuracy);
my_report.iaq_static = output.signal; my_report.iaq_static = output.signal;
break; break;
case BSEC_OUTPUT_CO2_EQUIVALENT: case BSEC_OUTPUT_CO2_EQUIVALENT:
ESP_LOGI(TAG, "\tCO2_EQUIVALENT = %f, accuracy %d", output.signal, (int) output.accuracy); ESP_LOGD(TAG, " CO2_EQUIVALENT = %f, accuracy %d", output.signal, (int) output.accuracy);
my_report.iaq_co2_ppm_equiv = output.signal; my_report.iaq_co2_ppm_equiv = output.signal;
break; break;
case BSEC_OUTPUT_BREATH_VOC_EQUIVALENT: case BSEC_OUTPUT_BREATH_VOC_EQUIVALENT:
ESP_LOGI(TAG, "\tBREATH_VOC_EQUIVALENT = %f, accuracy %d", output.signal, (int) output.accuracy); ESP_LOGD(TAG, " BREATH_VOC_EQUIVALENT = %f, accuracy %d", output.signal, (int) output.accuracy);
my_report.iaq_voc_ppm_equiv = output.signal; my_report.iaq_voc_ppm_equiv = output.signal;
break; break;
case BSEC_OUTPUT_RAW_PRESSURE: case BSEC_OUTPUT_RAW_PRESSURE:
ESP_LOGI(TAG, "\tRAW_PRESSURE = %f", output.signal); ESP_LOGD(TAG, " RAW_PRESSURE = %f", output.signal);
my_report.pressure = output.signal; my_report.pressure = output.signal;
my_report.thpg_ready = true; my_report.thpg_ready = true;
break; break;
case BSEC_OUTPUT_RAW_TEMPERATURE: case BSEC_OUTPUT_RAW_TEMPERATURE:
ESP_LOGI(TAG, "\tRAW_TEMPERATURE = %f", output.signal); ESP_LOGD(TAG, " RAW_TEMPERATURE = %f", output.signal);
my_report.temperature_raw = output.signal; my_report.temperature_raw = output.signal;
my_report.thpg_ready = true; my_report.thpg_ready = true;
break; break;
case BSEC_OUTPUT_RAW_HUMIDITY: case BSEC_OUTPUT_RAW_HUMIDITY:
ESP_LOGI(TAG, "\tRAW_HUMIDITY = %f", output.signal); ESP_LOGD(TAG, " RAW_HUMIDITY = %f", output.signal);
my_report.humidity_raw = output.signal; my_report.humidity_raw = output.signal;
my_report.thpg_ready = true; my_report.thpg_ready = true;
break; break;
case BSEC_OUTPUT_RAW_GAS: case BSEC_OUTPUT_RAW_GAS:
ESP_LOGI(TAG, "\tRAW_GAS = %f", output.signal); ESP_LOGD(TAG, " RAW_GAS = %f", output.signal);
my_report.gas_raw = output.signal; my_report.gas_raw = output.signal;
my_report.thpg_ready = true; my_report.thpg_ready = true;
break; break;
case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE: case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE:
ESP_LOGI(TAG, "\tSENSOR_HEAT_COMPENSATED_TEMPERATURE = %f", output.signal); ESP_LOGD(TAG, " SENSOR_HEAT_COMPENSATED_TEMPERATURE = %f", output.signal);
my_report.temperature = output.signal; my_report.temperature = output.signal;
my_report.thpg_ready = true; my_report.thpg_ready = true;
break; break;
case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY: case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY:
ESP_LOGI(TAG, "\tSENSOR_HEAT_COMPENSATED_HUMIDITY = %f", output.signal); ESP_LOGD(TAG, " SENSOR_HEAT_COMPENSATED_HUMIDITY = %f", output.signal);
my_report.humidity = output.signal; my_report.humidity = output.signal;
my_report.thpg_ready = true; my_report.thpg_ready = true;
break; break;
case BSEC_OUTPUT_STABILIZATION_STATUS: case BSEC_OUTPUT_STABILIZATION_STATUS:
ESP_LOGI(TAG, "\tSTABILIZATION_STATUS status = %d", (int) output.signal); ESP_LOGD(TAG, " STABILIZATION_STATUS status = %d", (int) output.signal);
break; break;
case BSEC_OUTPUT_RUN_IN_STATUS: case BSEC_OUTPUT_RUN_IN_STATUS:
ESP_LOGI(TAG, "\tRUN_IN_STATUS = %d", (int) output.signal); ESP_LOGD(TAG, " RUN_IN_STATUS = %d", (int) output.signal);
break; break;
default: default:
break; break;
@ -202,6 +201,9 @@ static void new_data_callback(
if (pdPASS == xSemaphoreTake(g_mux_data_report, pdMS_TO_TICKS(1000))) { if (pdPASS == xSemaphoreTake(g_mux_data_report, pdMS_TO_TICKS(1000))) {
if (my_report.thpg_ready) { if (my_report.thpg_ready) {
ESP_LOGI(TAG, "%.1f%%r.H, %.1f°C, %.0f Pa, gas %.0fR",
my_report.humidity, my_report.temperature, my_report.pressure, my_report.gas_raw);
g_data_report.thpg_ready = true; g_data_report.thpg_ready = true;
g_data_report.thpg_timestamp = xTaskGetTickCount(); g_data_report.thpg_timestamp = xTaskGetTickCount();
g_data_report.humidity = my_report.humidity; g_data_report.humidity = my_report.humidity;
@ -215,6 +217,11 @@ static void new_data_callback(
} }
if (my_report.iaq_ready) { if (my_report.iaq_ready) {
ESP_LOGI(TAG, "IAQ %.1f, IAQ_s %.1f, CO2eq %.1f ppm, VOCeq %.1f ppm",
my_report.iaq, my_report.iaq_static,
my_report.iaq_co2_ppm_equiv,
my_report.iaq_voc_ppm_equiv);
g_data_report.iaq_ready = true; g_data_report.iaq_ready = true;
g_data_report.iaq_timestamp = xTaskGetTickCount(); g_data_report.iaq_timestamp = xTaskGetTickCount();
g_data_report.iaq = my_report.iaq; g_data_report.iaq = my_report.iaq;
@ -237,7 +244,7 @@ void voc_read_task(void *param) {
goto abort; goto abort;
} }
int rv = bsec2_init(&gas_sensor_bsec, &gas_sensor); int rv = bsec2_init(&hBsec, &gas_sensor);
if (rv != 0) { if (rv != 0) {
ESP_LOGE(TAG, "Error in bsec init: %d", rv); ESP_LOGE(TAG, "Error in bsec init: %d", rv);
goto abort; goto abort;
@ -261,7 +268,7 @@ void voc_read_task(void *param) {
BSEC_OUTPUT_RUN_IN_STATUS, BSEC_OUTPUT_RUN_IN_STATUS,
}; };
rv = bsec2_updateSubscription(&gas_sensor_bsec, rv = bsec2_updateSubscription(&hBsec,
sensorList, sensorList,
sizeof(sensorList) / sizeof(bsecSensor), sizeof(sensorList) / sizeof(bsecSensor),
BSEC_SAMPLE_RATE_LP); BSEC_SAMPLE_RATE_LP);
@ -271,21 +278,35 @@ void voc_read_task(void *param) {
goto abort; goto abort;
} }
bsec2_attachCallback(&gas_sensor_bsec, new_data_callback); bsec2_attachCallback(&hBsec, new_data_callback);
ESP_LOGI(TAG, "BSEC library version %d.%d.%d.%d", ESP_LOGI(TAG, "BSEC library version %d.%d.%d.%d",
gas_sensor_bsec.version.major, hBsec.version.major,
gas_sensor_bsec.version.minor, hBsec.version.minor,
gas_sensor_bsec.version.major_bugfix, hBsec.version.major_bugfix,
gas_sensor_bsec.version.minor_bugfix); hBsec.version.minor_bugfix);
const uint32_t state_persist_time_ticks = 12 * 3600 * 1000;
_Static_assert(configTICK_RATE_HZ == 1000, "1kHz tick");
uint32_t last_state_persist = xTaskGetTickCount();
// TODO add bsec state persistence to NVS at some landmark intervals, e.g. 1 day ESP_LOGI(TAG, "Restore BSEC state");
// TODO periodic updating of sensor ambient temp bsec2_setState(&hBsec, g_Settings.bsec_state);
while (1) { while (1) {
rv = bsec2_run(&gas_sensor_bsec); rv = bsec2_run(&hBsec);
if (false == rv) { if (false == rv) {
ESP_LOGE(TAG, "Error in bsec run!"); ESP_LOGE(TAG, "Error in bsec run!");
} else {
uint32_t tickNow = xTaskGetTickCount();
uint32_t elapsed = tickNow - last_state_persist;
if (elapsed > state_persist_time_ticks) {
ESP_LOGI(TAG, "Read & persist BSEC state");
last_state_persist = tickNow;
if (bsec2_getState(&hBsec, g_Settings.bsec_state)) {
settings_persist(SETTINGS_bsec_state);
}
}
} }
vTaskDelay(pdMS_TO_TICKS(100)); vTaskDelay(pdMS_TO_TICKS(100));
} }

@ -136,6 +136,9 @@ CONFIG_PIN_I2C_SDA0=16
CONFIG_PIN_I2C_SCL0=17 CONFIG_PIN_I2C_SCL0=17
CONFIG_PIN_I2C_SDA1=5 CONFIG_PIN_I2C_SDA1=5
CONFIG_PIN_I2C_SCL1=18 CONFIG_PIN_I2C_SCL1=18
CONFIG_PIN_CO2_NRDY=23
CONFIG_PIN_CO2_COMSEL=13
CONFIG_PIN_CO2_EN=15
# end of Pin mapping # end of Pin mapping
# #

Loading…
Cancel
Save