// overall report data structure, contains all the variables we'll parse and send to mqtt
typedef struct report_data_s {
-#ifdef CONFIG_UART_ENABLED
+#ifdef CONFIG_AQI_ENABLED
uint16_t pm10;
uint16_t pm25;
float aqi;
#endif
+#ifdef CONFIG_CO2_ENABLED
+ int16_t co2_ppm;
+#endif
#ifdef CONFIG_TEMP_ENABLED
uint16_t temperature;
#endif
static uint8_t otExt[8];
#endif
#endif
-#ifdef CONFIG_UART_ENABLED
+#ifdef CONFIG_AQI_ENABLED
static aqi_data_t cur_pm = {
.pm10 = 0,
.pm25 = 0
}
+#ifdef CONFIG_AQI_ENABLED
+
void init_pmbuffer(){
for (int i = 0; i < PMBUFFERSIZE; i++){
pmbuffer[i].pm10 = -1.0;
}
// hardcoded given the aqi device we expect to find
-void init_uart(void) {
+void init_uart_aqi(void) {
+ 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_DEFAULT,
+ };
+ // We won't use a buffer for sending data.
+ uart_driver_install(UART_NUM_1, RX_BUF_SIZE * 2, 0, 0, NULL, 0);
+ uart_param_config(UART_NUM_1, &uart_config);
+ uart_set_pin(UART_NUM_1, CONFIG_AQI_TX_PIN, CONFIG_AQI_RX_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
+}
+#endif
+
+#ifdef CONFIG_CO2_ENABLED
+// hardcoded given the co2 device we expect to find
+void init_uart_co2(void) {
const uart_config_t uart_config = {
.baud_rate = 9600,
.data_bits = UART_DATA_8_BITS,
// We won't use a buffer for sending data.
uart_driver_install(UART_NUM_1, RX_BUF_SIZE * 2, 0, 0, NULL, 0);
uart_param_config(UART_NUM_1, &uart_config);
- uart_set_pin(UART_NUM_1, UART_TX_GPIO, UART_RX_GPIO, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
+ uart_set_pin(UART_NUM_1, CONFIG_CO2_TX_PIN, CONFIG_CO2_RX_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
+}
+
+bool co2_validate_checksum(uint8_t* data){
+ uint8_t checksum = 0;
+ // yeah, this starts at 1 and skips the first byte
+ for (int i = 1; i < 8; i++ ){
+ checksum += data[i];
+ }
+ checksum = 0xff - checksum;
+ checksum += 1;
+ if (checksum == data[8] ){
+ ESP_LOGI(TAG, "validated CO2 data checksum!\n");
+ return true;
+ } else {
+ ESP_LOGE(TAG, "incorrect CO2 data checksum %02x != %02x\n", checksum, data[8]);
+ return false;
+ }
+}
+
+
+// returns a signed 16 bit int, which should be fine because the detector
+// can never go above 10k anyway so we have like 20k headroom for the sign bit
+int16_t co2_parse_sensor_data(uint8_t* data){
+ // first validate checksum
+ if (co2_validate_checksum(data) ){
+ // need to parse according to manufacturer formula
+ uint8_t high = data[2];
+ uint8_t low = data[3];
+ int16_t retval = high * 256 + low;
+ return retval;
+ } else {
+ return -1;
+ }
+
+
+}
+
+void co2_send_fetch(void){
+ // Write fetch command to co2 sensor
+ uint8_t fetch_cmd[] = CO2_FETCH;
+ uart_write_bytes(UART_NUM_1, (void*)fetch_cmd, 9);
+}
+
+int16_t co2_read_sensor(void){
+ // we should only get back 9 bytes, but give a bigger buffer for error detection of garbage
+ uint8_t* co2_data = (uint8_t*) malloc(128);
+ size_t length = 0;
+ co2_send_fetch();
+
+ ESP_ERROR_CHECK(uart_get_buffered_data_len(UART_NUM_1, (size_t*)&length));
+
+ // assuming we have queried the sensor, at this point we should expect 9 bytes
+ const int rxBytes = uart_read_bytes(UART_NUM_1, co2_data, length, 128);
+ if (rxBytes != 9 ){
+ ESP_LOGE(TAG, "read wrong number of bytes back from UART %d, ignoring!\n", length);
+ free(co2_data);
+ return -1;
+ }
+
+
+ int16_t retval = co2_parse_sensor_data(co2_data);
+ free(co2_data);
+ return retval;
+ // we read the correct number of bytes in response, now need to parse and return
+ return co2_parse_sensor_data(co2_data);
}
+
+#endif
+
+
// this task sends identifying information over mqtt, sleeps an hour, then repeats
//
// if it doesn't go out for some reason it's just stored in a queue until it is fetched
cJSON *root;
root = cJSON_CreateObject();
cJSON_AddStringToObject(root, "location", CONFIG_LOCATION);
-#ifdef CONFIG_UART_ENABLED
+#ifdef CONFIG_AQI_ENABLED
cJSON_AddNumberToObject(root, "pm10", ((float)report_data.pm10/100));
cJSON_AddNumberToObject(root, "pm25", ((float)report_data.pm25/100));
cJSON_AddNumberToObject(root, "aqi", report_data.aqi);
#endif
+#ifdef CONFIG_CO2_ENABLED
+ // -1 is what we use for errors, 0 seems to be the device just isn't yet init
+ if (report_data.co2_ppm != -1 && report_data.co2_ppm != 0){
+ cJSON_AddNumberToObject(root, "co2", report_data.co2_ppm);
+ }
+#endif
// both set as we go through the normal loop, "nice to have" information on device operation
cJSON_AddNumberToObject(root, "uptime", report_data.uptime);
cJSON_AddNumberToObject(root, "internal", report_data.internal_temperature);
init_temp();
#endif
-#ifdef CONFIG_UART_ENABLED
+#ifdef CONFIG_AQI_ENABLED
uint16_t set_pm10;
uint16_t set_pm25;
float cur_aqi_pm25 = 0.0;
float cur_aqi_pm10 = 0.0;
int length = 0;
- uint8_t* data = (uint8_t*) malloc(RX_BUF_SIZE+1);
+ uint8_t* aqi_data = (uint8_t*) malloc(RX_BUF_SIZE+1);
#endif
int64_t cur_uptime = 0;
int64_t next_sleep_uptime = 0;
xSemaphoreGive(report_data.mutex);
-#ifdef CONFIG_UART_ENABLED
+#ifdef CONFIG_AQI_ENABLED
// aqi fetch/calculation/report
ESP_ERROR_CHECK(uart_get_buffered_data_len(UART_NUM_1, (size_t*)&length));
- const int rxBytes = uart_read_bytes(UART_NUM_1, data, length, 1000);
+ const int rxBytes = uart_read_bytes(UART_NUM_1, aqi_data, length, 1000);
if (rxBytes > 0) {
- data[rxBytes] = 0;
- //ESP_LOGI(TAG, "Read %d bytes: '%s'", rxBytes, data);
- //ESP_LOG_BUFFER_HEXDUMP(TAG, data, rxBytes, ESP_LOG_INFO);
- aqi_data_t cur_avg = get_average_pm25(data, rxBytes);
+ aqi_data[rxBytes] = 0;
+ //ESP_LOGI(TAG, "Read %d bytes: '%s'", rxBytes, aqi_data);
+ //ESP_LOG_BUFFER_HEXDUMP(TAG, aqi_data, rxBytes, ESP_LOG_INFO);
+ aqi_data_t cur_avg = get_average_pm25(aqi_data, rxBytes);
ESP_LOGI(TAG, "average from last 10 seconds pm10: %f", cur_avg.pm10);
ESP_LOGI(TAG, "average from last 10 seconds pm25: %f", cur_avg.pm25);
light_driver_set_color_RGB(current_colors[0], current_colors[1], current_colors[2]);
#endif
-#endif // end UART section
-
+#endif // end AQI UART section
+#ifdef CONFIG_CO2_ENABLED
+ int16_t co2_ppm = co2_read_sensor();
+ if (co2_ppm != -1 ){
+ xSemaphoreTake(report_data.mutex, portMAX_DELAY);
+ report_data.co2_ppm = co2_ppm;
+ xSemaphoreGive(report_data.mutex);
+ }
+
+#endif // end CO2 UART section
+
temp_avg = 0;
#ifdef CONFIG_TEMP_ENABLED
// Temperature fetch/calculation/report
}
// only called if we break out of the loop somehow (right now, never)
-#ifdef CONFIG_UART_ENABLED
- free(data);
+#ifdef CONFIG_AQI_ENABLED
+ free(aqi_data);
#endif
}
light_driver_init(LIGHT_DEFAULT_OFF);
adjust_color_lookup_brightness(AQI_INDICATOR_BRIGHTNESS);
#endif
-#ifdef CONFIG_UART_ENABLED
- init_uart();
+#ifdef CONFIG_AQI_ENABLED
+ init_uart_aqi();
init_pmbuffer();
#endif
+#ifdef CONFIG_CO2_ENABLED
+ init_uart_co2();
+#endif
#ifdef CONFIG_TEMP_ENABLED
init_tempbuffer();
#endif
#include "esp_timer.h"
-#ifdef CONFIG_ZIG_ENABLED
-#include "esp_zigbee_core.h"
-#include "ha/esp_zigbee_ha_standard.h"
-#endif
-
// just used for erasing partition, so we want
// to include it regardless of whether zb
// is enabled now.
#define MAX_TEMP_VALUE 12500
#endif
+#ifdef CONFIG_CO2_ENABLED
+// 8 bytes + 1 checksum, defined by manufacturer
+// we should expect 9 bytes in return
+#define CO2_FETCH {0xFF, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79}
+#endif
+
+
#define MAX_PM10 500
#define MAX_PM25 1000
#endif
#define MAXDEVS 1
-#define UART_RX_GPIO (GPIO_NUM_22)
-#define UART_TX_GPIO (GPIO_NUM_25)
// ten second loop
#define SLEEP_MS 10000
#define SQUELCH_MS 5000
-
-
-// zigbee cluster reporting stuff
-#ifdef CONFIG_ZIG_ENABLED
-#define PM25CLUSTER 0xFFFE
-#define PM10CLUSTER 0xFFFE
-//0x042A
-#define PM10MEASURED 0x0000
-#define PM25MEASURED 0x0001
-
-#define UPTIMECLUSTER 0xFFFD
-#define UPTIMEID 0x0000
-
-#endif
-
-
-
#define PMBUFFERSIZE 60
#define TEMPBUFFERSIZE 10
#define DS18B20_COPY_SCRATCHPAD 0x48
#define DS18B20_RECALL_EE 0xb8
#define DS18B20_READ_POWER_SUPPLY 0xb4
-
-
-
-#ifdef CONFIG_ZIG_ENABLED
-/* Zigbee configuration */
-#define INSTALLCODE_POLICY_ENABLE false /* enable the install code policy for security */
-#define ED_AGING_TIMEOUT ESP_ZB_ED_AGING_TIMEOUT_64MIN
-// router or endpoint
-
-
-// auto chooses the correct configuration based upon your menuconfig selection
-#ifdef CONFIG_ZB_ZCZR
-#define ZB_TYPE ESP_ZB_DEVICE_TYPE_ROUTER
-#define MAX_CHILDREN 10
-#define ESP_ZB_ZED_CONFIG() \
- { \
- .esp_zb_role = ZB_TYPE, \
- .install_code_policy = INSTALLCODE_POLICY_ENABLE, \
- .nwk_cfg.zczr_cfg = { \
- .max_children = MAX_CHILDREN, \
- }, \
- }
-
-#elif CONFIG_ZB_ZED
-#define ZB_TYPE ESP_ZB_DEVICE_TYPE_ED
-#define ESP_ZB_ZED_CONFIG() \
- { \
- .esp_zb_role = ZB_TYPE, \
- .install_code_policy = INSTALLCODE_POLICY_ENABLE, \
- .nwk_cfg.zed_cfg = { \
- .ed_timeout = ED_AGING_TIMEOUT, \
- .keep_alive = ED_KEEP_ALIVE, \
- }, \
- }
-
-#endif
-
-#define ED_KEEP_ALIVE 3000 /* 3000 millisecond */
-#define HA_ESP_ENDPOINT 1
-#define ESP_ZB_PRIMARY_CHANNEL_MASK ESP_ZB_TRANSCEIVER_ALL_CHANNELS_MASK /* Zigbee primary channel mask use in the example */
-//specifically search channel 15, which is what I'm on
-//#define ESP_ZB_PRIMARY_CHANNEL_MASK 1l<<15
-
-#define ESP_ZB_DEFAULT_RADIO_CONFIG() \
- { \
- .radio_mode = RADIO_MODE_NATIVE, \
- }
-
-#define ESP_ZB_DEFAULT_HOST_CONFIG() \
- { \
- .host_connection_mode = HOST_CONNECTION_MODE_NONE, \
- }
-#endif