Report both pm25 *and* pm10, and average temperature across time
authorjweigele <jweigele@local>
Thu, 13 Jul 2023 17:30:02 +0000 (10:30 -0700)
committerjweigele <jweigele@local>
Thu, 13 Jul 2023 17:30:02 +0000 (10:30 -0700)
 * add pm10 as a discrete value, and create an overall struct to hold
   both data types (pmbuffer) and average
 * make the temp list an actual ring buffer too, for use in
   flattening/averaging out temperature values like is currently done on
   pico
 * memmem for searching a value worked better, and the strstr variant
   was buggy for whatever reason

aqi/main/aqi.c
aqi/main/aqi.h

index 6c28780ecc66c737195807856fd493a8ab6d69d9..27d0cf6027b4b0d2a773e7e50f1d1bafb75a7ba8 100644 (file)
@@ -34,14 +34,26 @@ typedef struct zdo_info_ctx_s {
     esp_zb_ieee_addr_t long_addr;
 } zdo_info_user_ctx_t;
 
-static short temp_list[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+typedef struct aqi_data_s {
+    float pm10;
+    float pm25;
+} aqi_data_t;
+
+static int tempbuffer[TEMPBUFFERSIZE];
+static int tempbufferindex = 0;
+
+//= {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
 static const char *TAG = "aqi";
 static OW ow;
 static bool zig_connected = false;
-static uint16_t cur_pm25 = 0;
+static aqi_data_t cur_pm = {
+    .pm10 = 0,
+    .pm25 = 0
+};
 
-static float pm25buffer[PM25BUFFERSIZE];
-static int pm25bufferindex = 0;
+static aqi_data_t pmbuffer[PMBUFFERSIZE];
+static int pmbufferindex = 0;
 
 /********************* Define functions **************************/
 static void bdb_start_top_level_commissioning_cb(uint8_t mode_mask)
@@ -64,35 +76,93 @@ bool init_temp(){
     return err;
 }
 
+void print_pmdata(aqi_data_t data){
+    ESP_LOGI(TAG, "pmdata pm10: %f, pm25: %f", data.pm10, data.pm25);
+}
 
 // add new value to ringbuffer and return average of all, downcast to uint16_t
-uint16_t new_pm25_average(float new_val){
-    pm25buffer[pm25bufferindex++] = new_val;
+aqi_data_t new_pm_average(aqi_data_t new_val){
+    pmbuffer[pmbufferindex++] = new_val;
+    aqi_data_t retval = {
+        .pm10 = 0,
+        .pm25 = 0,
+    };
     // rollover
-    if (pm25bufferindex == PM25BUFFERSIZE){
-        pm25bufferindex = 0;
+    if (pmbufferindex == PMBUFFERSIZE){
+        pmbufferindex = 0;
     }
 
-    float total = 0;
+    float pm10_total = 0;
+    float pm25_total = 0;
     int count = 0;
-    for (int i = 0; i < PM25BUFFERSIZE; i++ ){
+    for (int i = 0; i < PMBUFFERSIZE; i++ ){
         // ignore initial values
-        if ( pm25buffer[i] != -1.0 ){
-            total += pm25buffer[i];
+        if ( pmbuffer[i].pm10 != -1.0 ){
+            pm10_total += pmbuffer[i].pm10;
+        }
+        if ( pmbuffer[i].pm25 != -1.0 ){
+            pm25_total += pmbuffer[i].pm25;
+            // assuming both fields have the same "valid" count
+            count++;
+        }
+        
+        
+    }
+    // multiply * 100 for greater precision once sent
+    if (count != 0 ){
+        retval.pm10 = (pm10_total*100/count);
+        retval.pm25 = (pm25_total*100/count);
+    }
+
+    ESP_LOGI(TAG, "returning average pmdata to send: pm10 %f pm25 %f", retval.pm10, retval.pm25);
+    return retval;
+
+}
+
+int new_temp_average(short new_val){
+    tempbuffer[tempbufferindex++] = new_val;
+    // rollover
+    if (tempbufferindex == TEMPBUFFERSIZE){
+        tempbufferindex = 0;
+    }
+
+    int total = 0;
+    int count = 0;
+    for (int i = 0; i < TEMPBUFFERSIZE; i++ ){
+        // ignore invalid
+        if ( tempbuffer[i] != 0x8000 ){
+            total += tempbuffer[i];
             count++;
         }
     }
-    uint16_t retval = (uint16_t)(total*100/count);
-    ESP_LOGI(TAG, "returning average pm25 to send: %d", retval);
+    int retval;
+    // we should only get here if all values are added are 0x8000 (aka no read)
+    // but it's still important to fill out just so we don't crash
+    // mainly, when ds18b20 is not connected or sensing properly
+    if (count == 0){
+        retval = 0x8000;
+    } else {
+        retval = (int)(total/count);
+    }
+    ESP_LOGI(TAG, "returning average temp to send: %d", retval);
     return retval;
 
 }
 
-void init_pm25buffer(){
-    for (int i = 0; i < PM25BUFFERSIZE; i++){
-        pm25buffer[i] = -1.0;
+
+void init_pmbuffer(){
+    for (int i = 0; i < PMBUFFERSIZE; i++){
+        pmbuffer[i].pm10 = -1.0;
+        pmbuffer[i].pm25 = -1.0;
     }
 }
+
+void init_tempbuffer(){
+    for (int i = 0; i < TEMPBUFFERSIZE; i++){
+        tempbuffer[i] = 0x8000;
+    }
+}
+
 void init_uart(void) {
     const uart_config_t uart_config = {
         .baud_rate = 9600,
@@ -154,7 +224,7 @@ static short get_temp(){
     }
 }
 
-static void send_report(uint8_t report_type){
+static void send_report(int report_cluster, int report_attribute){
     ESP_LOGI(TAG, "sending report here");
     esp_zb_zcl_report_attr_cmd_t des;
 
@@ -169,23 +239,11 @@ static void send_report(uint8_t report_type){
     //ESP_ZB_APS_ADDR_MODE_16_GROUP_ENDP_NOT_PRESENT;
     //des.address_mode = ESP_ZB_APS_ADDR_MODE_64_ENDP_PRESENT;
     
-    if (report_type == TEMP_REPORT){
-        des.clusterID = ESP_ZB_ZCL_CLUSTER_ID_TEMP_MEASUREMENT;
-        des.attributeID = ESP_ZB_ZCL_ATTR_TEMP_MEASUREMENT_VALUE_ID;
-    } else if (report_type == PM25_REPORT){
-        des.clusterID = PM25CLUSTER;
-        des.attributeID = PM25MEASURED;
-    } else {
-        ESP_LOGW(TAG, "undefined report type, just returning");
-        return;
-    }
+    des.clusterID = report_cluster;
+    des.attributeID = report_attribute;
     des.cluster_role=ESP_ZB_ZCL_CLUSTER_SERVER_ROLE;   
     ESP_ERROR_CHECK(esp_zb_zcl_report_attr_cmd_req(&des));
-    if (report_type == TEMP_REPORT){    
-        ESP_LOGI(TAG, "sent temp report");
-    } else if (report_type == PM25_REPORT){
-        ESP_LOGI(TAG, "sent pm25 report");
-    }
+    ESP_LOGI(TAG, "sent report");
 
 }
 
@@ -201,83 +259,114 @@ bool verify_checksum(uint8_t* data){
     return data[30] == check_high && data[31] == check_low;
 }
 
-float get_average_pm25(uint8_t* data, int data_size){
-    int total = 0;
+aqi_data_t get_average_pm25(uint8_t* data, int data_size){
+    int pm10_total = 0;
+    int pm25_total = 0;
     int count = 0;
+    aqi_data_t retval = {
+        .pm10 = -1.0,
+        .pm25 = -1.0,
+    };    
+    //ESP_LOG_BUFFER_HEXDUMP(TAG, data, data_size, ESP_LOG_INFO);
     char search_prefix[2] = {0x42, 0x4d};
-    uint8_t* first_entry = (uint8_t*)strstr((char*)data, search_prefix);
+    uint8_t* first_entry = (uint8_t*)memmem((void*)data, data_size, search_prefix, 2);
     int offset = first_entry - data;
+    if (first_entry == NULL){
+        ESP_LOGW(TAG, "invalid first entry, returning invalid data");
+        return retval;
+    }
     ESP_LOGI(TAG, "first entry found at %p (offset %d)", (uint8_t*)first_entry, offset);
     uint8_t* cur_entry = first_entry;
-    int pm25;
+    int pm10, pm25;
     while (cur_entry + 32 < data + data_size){
         //printf("doing one iteration\n");
         //ESP_LOG_BUFFER_HEXDUMP(TAG, cur_entry, 32, ESP_LOG_INFO);
         if (verify_checksum(cur_entry)){
             pm25 = (data[12] << 8) + (data[13]);
+            pm10 = (data[10] << 8) + (data[11]);
             //ESP_LOGI(TAG, "pm25 val: %d", pm25);
             count++;
-            total += pm25;
+            pm25_total += pm25;
+            pm10_total += pm10;
         } else {
             ESP_LOGW(TAG, "checksum failed for uart, skipping entry");
         }
         cur_entry += 32;
     }
-    if (count == 0){
-        return 0.0;
-    } else {
-        return ((float)total)/count;
+    if (count != 0){
+        retval.pm10 = ((float)pm10_total)/count;
+        retval.pm25 = ((float)pm25_total)/count;
     }
+    print_pmdata(retval);
+    return retval;
 }
 
+
 static void monitoring_task(void* discard)
 {
     bool tempsetup = init_temp();
     //bool tempsetup = true;
 
     uint8_t* data = (uint8_t*) malloc(RX_BUF_SIZE+1);
+    short temp_avg = 0x8000;
     int length = 0;
     for(;;){
         ESP_LOGI(TAG, "free heap: %"PRIu32, esp_get_free_heap_size());
         //temp_list[0] += 100;
-       if (tempsetup){
-               temp_list[0] = get_temp();
-       } else {
-               ESP_LOGI(TAG, "temp not setup, skipping update");
-       }
-        ESP_LOGI(TAG, "current: %d", temp_list[0]);
 
        if (zig_connected == true){
+
+            // 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, 100);
             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);
-                float cur_avg = get_average_pm25(data, rxBytes);
-                ESP_LOGI(TAG, "average from last 10 seconds pm25: %f", cur_avg);
+                aqi_data_t cur_avg = get_average_pm25(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);
+                
                 ESP_LOGI(TAG, "setting attrib value and sending, I guess");
-                cur_pm25 = new_pm25_average(cur_avg);
+                cur_pm = new_pm_average(cur_avg);
+                uint16_t set_pm10 = (uint16_t)cur_pm.pm10;
+                uint16_t set_pm25 = (uint16_t)cur_pm.pm25;                
+                esp_zb_zcl_set_attribute_val(HA_ESP_TEMP_ENDPOINT, PM25CLUSTER, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE,
+                                PM10MEASURED, &set_pm10, false);
                 esp_zb_zcl_set_attribute_val(HA_ESP_TEMP_ENDPOINT, PM25CLUSTER, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE,
-                                PM25MEASURED, &cur_pm25, false);
-                send_report(PM25_REPORT);
+                                PM25MEASURED, &set_pm25, false);
+
+                send_report(PM10CLUSTER, PM10MEASURED);
+                send_report(PM25CLUSTER, PM25MEASURED);
 
             } else {
                 ESP_LOGI(TAG, "Nothing seen from uart");
             }
 
-               ESP_LOGI(TAG, "setting attrib value and sending, I guess");
-               esp_zb_zcl_set_attribute_val(HA_ESP_TEMP_ENDPOINT, ESP_ZB_ZCL_CLUSTER_ID_TEMP_MEASUREMENT, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE,
-                               ESP_ZB_ZCL_ATTR_TEMP_MEASUREMENT_VALUE_ID, &temp_list[0], false);
-               send_report(TEMP_REPORT);
-               // reg sleep for 1 second to clear out backlog, then light sleep
-               //ESP_LOGI(TAG, "light sleep starts in a second");
-               vTaskDelay( pdMS_TO_TICKS(10000) );
-               //ESP_ERROR_CHECK(esp_sleep_enable_timer_wakeup(9000000));
-               //ESP_ERROR_CHECK(esp_light_sleep_start());
+            // Temperature fetch/calculation/report
+            if (tempsetup){
+                    temp_avg = new_temp_average(get_temp());
+            } else {
+                    ESP_LOGI(TAG, "temp not setup, skipping update");
+            }
+            ESP_LOGI(TAG, "current: %d", temp_avg);
+
+            ESP_LOGI(TAG, "setting attrib value and sending, I guess");
+            esp_zb_zcl_set_attribute_val(HA_ESP_TEMP_ENDPOINT, ESP_ZB_ZCL_CLUSTER_ID_TEMP_MEASUREMENT, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE,
+                            ESP_ZB_ZCL_ATTR_TEMP_MEASUREMENT_VALUE_ID, &temp_avg, false);
+            send_report(ESP_ZB_ZCL_CLUSTER_ID_TEMP_MEASUREMENT, ESP_ZB_ZCL_ATTR_TEMP_MEASUREMENT_VALUE_ID);
 
-               // now we've woken up I guess?
-               ESP_LOGI(TAG, "I have worken up after sleep, looping around");
+
+            // loop around and sleep
+            //
+            // reg sleep for 1 second to clear out backlog, then light sleep
+            //ESP_LOGI(TAG, "light sleep starts in a second");
+            vTaskDelay( pdMS_TO_TICKS(10000) );
+            //ESP_ERROR_CHECK(esp_sleep_enable_timer_wakeup(9000000));
+            //ESP_ERROR_CHECK(esp_light_sleep_start());
+
+            // now we've woken up I guess?
+            ESP_LOGI(TAG, "I have worken up after sleep, looping around");
                
        } else {
                ESP_LOGI(TAG, "zig not connected, skipping temp report (and staying awake)");
@@ -291,11 +380,6 @@ static void monitoring_task(void* discard)
     //esp_zb_scheduler_alarm((esp_zb_callback_t)monitoring_task, parm, 10000);
 }
 
-static void esp_zb_read_resp_cb(esp_zb_zcl_status_t status, uint16_t cluster_id, uint16_t attr_id, esp_zb_zcl_attr_type_t attr_type, void *value)
-{
-    ESP_LOGI(TAG, "Switch got read attribute response with status:%d,cluster_id:0x%x,attr_id:0x%x,value:%d,attr_type:0x%x", status, cluster_id, attr_id, *(uint8_t *)value, attr_type);
-}
-
 void esp_zb_app_signal_handler(esp_zb_app_signal_t *signal_struct)
 {
     uint32_t *p_sg_p       = signal_struct->p_app_signal;
@@ -372,9 +456,6 @@ static void esp_zb_task(void *pvParameters)
     uint8_t power_source[] = {0x01};
 
 
-    for (int i= 0; i < 10; i++ ){
-        temp_list[i] = 0x8000;
-    }
     /* basic cluster create with fully customized */
     esp_zb_attribute_list_t *esp_zb_basic_cluster = esp_zb_zcl_attr_list_create(ESP_ZB_ZCL_CLUSTER_ID_BASIC);
     esp_zb_basic_cluster_add_attr(esp_zb_basic_cluster, ESP_ZB_ZCL_ATTR_BASIC_POWER_SOURCE_ID, power_source);
@@ -400,22 +481,23 @@ static void esp_zb_task(void *pvParameters)
 
     short min_temp = -5500;
     short max_temp = 12500;
+    uint16_t initial_pm10 = 0;
+    uint16_t initial_pm25 = 0;
     // temperature cluster
     esp_zb_attribute_list_t *esp_zb_temp_cluster = esp_zb_zcl_attr_list_create(ESP_ZB_ZCL_CLUSTER_ID_TEMP_MEASUREMENT);
-    esp_zb_temperature_meas_cluster_add_attr(esp_zb_temp_cluster, ESP_ZB_ZCL_ATTR_TEMP_MEASUREMENT_VALUE_ID, &temp_list[0]);
+    esp_zb_temperature_meas_cluster_add_attr(esp_zb_temp_cluster, ESP_ZB_ZCL_ATTR_TEMP_MEASUREMENT_VALUE_ID, &tempbuffer[0]);
     esp_zb_temperature_meas_cluster_add_attr(esp_zb_temp_cluster, ESP_ZB_ZCL_ATTR_TEMP_MEASUREMENT_MIN_VALUE_ID, &min_temp);
     esp_zb_temperature_meas_cluster_add_attr(esp_zb_temp_cluster, ESP_ZB_ZCL_ATTR_TEMP_MEASUREMENT_MAX_VALUE_ID, &max_temp);
 
 
     esp_zb_attribute_list_t *esp_zb_pm25_cluster = esp_zb_zcl_attr_list_create(PM25CLUSTER);
-    ESP_ERROR_CHECK(esp_zb_custom_cluster_add_custom_attr(esp_zb_pm25_cluster, PM25MEASURED, ESP_ZB_ZCL_ATTR_TYPE_U16, ESP_ZB_ZCL_ATTR_ACCESS_READ_ONLY | ESP_ZB_ZCL_ATTR_ACCESS_REPORTING, &cur_pm25));
+    ESP_ERROR_CHECK(esp_zb_custom_cluster_add_custom_attr(esp_zb_pm25_cluster, PM25MEASURED, ESP_ZB_ZCL_ATTR_TYPE_U16, ESP_ZB_ZCL_ATTR_ACCESS_READ_ONLY | ESP_ZB_ZCL_ATTR_ACCESS_REPORTING, &initial_pm25));
+    ESP_ERROR_CHECK(esp_zb_custom_cluster_add_custom_attr(esp_zb_pm25_cluster, PM10MEASURED, ESP_ZB_ZCL_ATTR_TYPE_U16, ESP_ZB_ZCL_ATTR_ACCESS_READ_ONLY | ESP_ZB_ZCL_ATTR_ACCESS_REPORTING, &initial_pm10));
+    
 
     /* create cluster lists for this endpoint */
     esp_zb_cluster_list_t *esp_zb_cluster_list = esp_zb_zcl_cluster_list_create();
     esp_zb_cluster_list_add_basic_cluster(esp_zb_cluster_list, esp_zb_basic_cluster, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
-
-    // add all the clusters to our main cluster list
-    //esp_zb_cluster_list_update_basic_cluster(esp_zb_cluster_list, esp_zb_basic_cluster_create(NULL), ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
     esp_zb_cluster_list_add_identify_cluster(esp_zb_cluster_list, esp_zb_identify_cluster, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
     esp_zb_cluster_list_add_temperature_meas_cluster(esp_zb_cluster_list, esp_zb_temp_cluster, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);    
     ESP_ERROR_CHECK(esp_zb_cluster_list_add_custom_cluster(esp_zb_cluster_list, esp_zb_pm25_cluster, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE));
@@ -426,16 +508,6 @@ static void esp_zb_task(void *pvParameters)
     esp_zb_ep_list_add_ep(esp_zb_ep_list, esp_zb_cluster_list, HA_ESP_TEMP_ENDPOINT, ESP_ZB_AF_HA_PROFILE_ID, ESP_ZB_HA_ON_OFF_OUTPUT_DEVICE_ID);
     esp_zb_device_register(esp_zb_ep_list);
 
-    // set first pm25 val
-    /*esp_zb_zcl_set_attribute_val(HA_ESP_TEMP_ENDPOINT, PM25CLUSTER, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE,
-                                    PM25MEASURED, &cur_pm25, false);*/
-
-    // set first temp value
-    esp_zb_zcl_set_attribute_val(HA_ESP_TEMP_ENDPOINT, ESP_ZB_ZCL_CLUSTER_ID_TEMP_MEASUREMENT, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE,
-                                    ESP_ZB_ZCL_ATTR_TEMP_MEASUREMENT_VALUE_ID, &temp_list[0], false); 
-
-    //esp_zb_device_add_set_attr_value_cb(attr_cb);
-    esp_zb_add_read_attr_resp_cb(HA_ESP_TEMP_ENDPOINT, esp_zb_read_resp_cb);
     ESP_ERROR_CHECK(esp_zb_start(false));
 
     //ESP_ERROR_CHECK(esp_zb_secur_ic_set(ESP_ZB_IC_TYPE_128, (uint8_t*)curic));
@@ -452,7 +524,8 @@ void app_main(void)
     };
     ESP_ERROR_CHECK(nvs_flash_init());
     init_uart();
-    init_pm25buffer();
+    init_pmbuffer();
+    init_tempbuffer();
     /* load Zigbee light_bulb platform config to initialization */
     ESP_ERROR_CHECK(esp_zb_platform_config(&config));
     /* hardware related and device init */
index 62b336d420faa74507f412590c7f0fa5f02fd37e..6c04ac7601ee13e21f014bc79152f3bc531c986c 100644 (file)
 #define UART_RX_GPIO (GPIO_NUM_26)
 #define UART_TX_GPIO (GPIO_NUM_27)
 #define PM25CLUSTER 0xFFFE
+#define PM10CLUSTER 0xFFFE
 //0x042A
-#define PM25MEASURED 0x0000
+#define PM10MEASURED 0x0000
+#define PM25MEASURED 0x0001
 
 #define TEMP_REPORT 0x1
 #define PM25_REPORT 0x2
 
 
-#define PM25BUFFERSIZE 6
+#define PMBUFFERSIZE 6
+#define TEMPBUFFERSIZE 10
 
 static const int RX_BUF_SIZE = 1024;