Compare commits

...

6 Commits

  1. 18
      LICENSE.txt
  2. 55
      README.md
  3. 30
      main/co2_sensor.c
  4. 6
      main/modbus_fn.c
  5. 5
      main/periph_init.c

@ -0,0 +1,18 @@
Out of abundance of caution, all code in this project should be considered available for private educational use only,
with no redistribution permitted in any form. Some files are available as MIT.
Ask for clarification if needed: ondra@ondrovo.com
~~ Licensing for individual components ~~
Console
proprietary (c) VZLU 2021, developed for VZLUSAT2 with CSP parts removed in this version.
Inspired by the esp32 console, including a modified 3rd party Linenoise library, and argtable3.
BSEC
by Bosch, see the bme680 lib for details
Web templating, telnet protocol, tcp socket server and the actual metering app
(c) Ondřej Hruška 2018-2022, these components are MIT.

@ -0,0 +1,55 @@
# ESP air quality sensor
## Function
- CO2 ppm
- air temperature
- relative humidity
- gas sensor VOC equivalent PPM
- gas sensor CO2 equivalent PPM
### Data reading
The module prints actual readings to the debug console.
Data is accessible as JSON on a REST endpoint /sample, port 80
Example:
```
{"temp":22.80,"temp_r":22.86,"hum":45.33,"hum_r":45.15,"pres":98203.7,"gas_r":583406.1,"co2":620.00}
```
More data is included once BSEC2 (Bosch proprietary BME680 client / fusion library) finishes its calibration. See the `websrv.c` file for details.
### Workarounds & reliability features
The used CO2 sensor has buggy ABC algorithm that sometimes underflows. Further, I2C is very much broken on the sample used, so I use the Modbus interface instead.
Automatic restart and calibration restore is implemented for it.
BSEC2 state persistence is implemented, but dubious if functional due to the millisecond timestamp logic. Air quality reports usually start coming after a couple hours of run time with varying ambient gas concentration.
### Configuration
Config via console on the serial port (115200-8-N-1), or telnet (port 22).
WiFi can be configured and persisted, including static IP. WiFi scan is not implemented.
## Hardware
- BME680 in I2C mode
- SenseAir Sunrise (buggy early revision, will need updating for newer sensors)
## Pinout
- I2C_SDA0 16 - i2c for BME680
- I2C_SCL0 17
- I2C_SDA1 5 (senseair sunrise uart TX)
- I2C_SCL1 18 (senseair sunrise uart RX)
- CO2_NRDY 23
- CO2_COMSEL 13 (high=uart)
- CO2_EN 15 sunrise restart pin, works like nReset
## Licensing
See the LICENSE file.

@ -28,14 +28,25 @@ static int mb_write_and_read(uint8_t *buffer, int num, size_t resplen) {
return 0;
}
// sometimes there's trash in the buffer, get rid of it
int discarded = 0;
uint8_t junk;
do {
discarded = uart_read_bytes(CO2_UART_NUM, &junk, 1, pdMS_TO_TICKS(2));
} while(discarded);
ESP_LOGD(TAG, "Sending");
ESP_LOG_BUFFER_HEXDUMP(TAG, buffer, num, ESP_LOG_DEBUG);
uart_write_bytes(CO2_UART_NUM, buffer, num);
ESP_LOGD(TAG, "Expect resp of %d bytes", resplen);
num = uart_read_bytes(CO2_UART_NUM, buffer, resplen, pdMS_TO_TICKS(500));
num = uart_read_bytes(CO2_UART_NUM, buffer, resplen, pdMS_TO_TICKS(TIMEOUT_MS));
ESP_LOGD(TAG, "Received %d bytes", num);
ESP_LOG_BUFFER_HEXDUMP(TAG, buffer, num, ESP_LOG_DEBUG);
if (num == 0) {
ESP_LOGE(TAG, "No modbus response!");
}
return num;
}
@ -50,7 +61,7 @@ static void write_saved_calib_to_sensor() {
/* Write calib */
resplen = 0;
num = mb_build_fc16(buffer, 128, &resplen, 104, 4, g_Settings.co2_calib, 5);
num = mb_build_fc16(buffer, 128, &resplen, CO2_ADDR, 4, g_Settings.co2_calib, 5);
num = mb_write_and_read(buffer, num, resplen);
resp = mb_parse_fc16(buffer, num);
if (resp < 0) {
@ -65,7 +76,7 @@ void co2_restart(bool restore_calib) {
ESP_LOGI(TAG, "Restarting CO2 sensor");
gpio_set_level(CONFIG_PIN_CO2_EN, 0);
vTaskDelay(pdMS_TO_TICKS(100));
vTaskDelay(pdMS_TO_TICKS(250));
gpio_set_level(CONFIG_PIN_CO2_EN, 1);
vTaskDelay(pdMS_TO_TICKS(50));
@ -81,6 +92,8 @@ static bool ppm_looks_valid(uint16_t ppm) {
void co2_read_task(void *param) {
(void) param;
// 68 04 0a 00 00 00 00 00 00 03 b5 03 f2 dd c3
vTaskDelay(pdMS_TO_TICKS(500));
// TODO
@ -107,14 +120,14 @@ void co2_read_task(void *param) {
ppm = 0;
vTaskDelay(read_cycle_time_ticks);
if (num_fails > 10) {
if (num_fails > 5) {
ESP_LOGW(TAG, "Too many CO2 fails, restarting sensor");
co2_restart(true);
num_fails = 0;
}
resplen = 0;
num = mb_build_fc4(buffer, 128, &resplen, 104, 0, 5);
num = mb_build_fc4(buffer, 128, &resplen, CO2_ADDR, 0, 5);
num = mb_write_and_read(buffer, num, resplen);
qty = mb_parse_fc4(buffer, num, values, 32);
if (qty < 0) {
@ -175,7 +188,10 @@ void co2_read_task(void *param) {
if (!ppm_looks_valid(ppm)) {
ESP_LOGW(TAG, "CO2 measures nonsense!");
co2_restart(true);
if (ppm != 0) {
// ppm=0 is OK, it means the ppm wasn't measured yet. just ignore it.
co2_restart(true);
}
} else {
// CO2 measurement looks OK
@ -187,7 +203,7 @@ void co2_read_task(void *param) {
last_calib_persist = tickNow;
resplen = 0;
num = mb_build_fc3(buffer, 128, &resplen, 104, 4, 5);
num = mb_build_fc3(buffer, 128, &resplen, CO2_ADDR, 4, 5);
num = mb_write_and_read(buffer, num, resplen);
qty = mb_parse_fc3(buffer, num, values, 32);
if (qty < 0) {

@ -1,3 +1,6 @@
//#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
#include <assert.h>
#include "modbus_fn.h"
#include "modbus_crc.h"
@ -9,6 +12,9 @@ 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];
if (real != given) {
ESP_LOGE(TAG, "CRC computed of %d bytes: %04x, given %04x", len-2, real, given);
}
return real == given;
}

@ -103,7 +103,10 @@ esp_err_t periph_init() {
}
gpio_set_level(CONFIG_PIN_CO2_COMSEL, 1); // low=I2C
gpio_set_level(CONFIG_PIN_CO2_EN, 1); // active high
gpio_set_level(CONFIG_PIN_CO2_EN, 0);
vTaskDelay(pdMS_TO_TICKS(250));
gpio_set_level(CONFIG_PIN_CO2_EN, 1);
}
// input

Loading…
Cancel
Save