rearrange uart code and add co2 monitoring (on same uart as aqi) plus cleanup more...
authorjweigele <jweigele@local>
Tue, 19 Mar 2024 18:56:11 +0000 (11:56 -0700)
committerjweigele <jweigele@local>
Tue, 19 Mar 2024 18:56:11 +0000 (11:56 -0700)
aqi/main/Kconfig
aqi/main/aqi.c
aqi/main/aqi.h

index f75c64e487d0c2f5a331a952515531790a68e73d..e4fbdd34cdd4c77c16b4940c8fa63d9a701bf9aa 100644 (file)
@@ -21,16 +21,45 @@ config LIGHT_SLEEP_ENABLED
     help
       Not very well tested yet
 
-config UART_ENABLED
-    bool "Actually fetch and report the AQI over UART"
-    default y
+config AQI_ENABLED
+    bool "Fetch and report AQI from UART?"
+    default n
+    help
+      Gets passed and used later for includes
+
+config AQI_TX_PIN
+    int "UART TX pin for AQI"
+    default 22
     help
       Gets passed and used later for includes
-config ZIG_ENABLED
-    bool "Are we using zigbee pairing and reporting"
+
+config AQI_RX_PIN
+    int "UART RX pin for AQI"
+    default 25
+    help
+      Gets passed and used later for includes
+
+config CO2_ENABLED
+    bool "Fetch and report CO2 from UART?"
     default n
     help
       Gets passed and used later for includes
+
+config CO2_TX_PIN
+    int "UART TX pin for CO2"
+    default 22
+    help
+      Gets passed and used later for includes
+
+config CO2_RX_PIN
+    int "UART RX pin for CO2"
+    default 25
+    help
+      Gets passed and used later for includes
+
+
+
+
 config WIFI_ENABLED
     bool "Are we using wifi association and MQTT"
     default n
index 22949ae42e94d851204ba2c1f58c41b24a9c479a..4cead69cdbb29be62696082a03a169adf50032d0 100644 (file)
@@ -41,11 +41,14 @@ typedef struct pwm_data_s {
 
 // 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
@@ -129,7 +132,7 @@ static uint8_t ot_mac[6];
 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
@@ -599,6 +602,8 @@ aqi_data_t new_pm_average(aqi_data_t new_val){
 
 }
 
+#ifdef CONFIG_AQI_ENABLED
+
 void init_pmbuffer(){
     for (int i = 0; i < PMBUFFERSIZE; i++){
         pmbuffer[i].pm10 = -1.0;
@@ -607,7 +612,25 @@ void init_pmbuffer(){
 }
 
 // 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,
@@ -619,9 +642,78 @@ void init_uart(void) {
     // 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
@@ -746,11 +838,17 @@ static void send_report_summary(report_data_t report_data){
     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);
@@ -1251,13 +1349,13 @@ short temp_avg = 0;
     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;
@@ -1290,15 +1388,15 @@ short temp_avg = 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);
             
@@ -1340,8 +1438,17 @@ short temp_avg = 0;
         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
@@ -1405,8 +1512,8 @@ short temp_avg = 0;
 
     }
     // 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
 }
 
@@ -1640,10 +1747,13 @@ void app_main(void)
     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
index 207030905e4d4d52e1ce11513485552554aac2c5..0f2c25d21802dc831eff5e4afe4d807ac4c2fa53 100644 (file)
@@ -1,10 +1,5 @@
 #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.
@@ -111,6 +106,13 @@ static const char *TAG = "aqi";
 #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
 
@@ -122,30 +124,11 @@ static const char *TAG = "aqi";
 #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
 
@@ -180,56 +163,3 @@ static void mqtt_reinit();
 #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