Add CO2 PWM sensor reads and clean up dependencies
authorjweigele <jweigele@local>
Wed, 20 Mar 2024 05:32:19 +0000 (22:32 -0700)
committerjweigele <jweigele@local>
Wed, 20 Mar 2024 05:32:19 +0000 (22:32 -0700)
 * CO2 PWM counts for 10 seconds and then averages PPM values by formula
   using one pin
 * Kconfig now has a bunch of depends to clean up cruft on the screen
 * Move some variables around to prevent issues with unused warnings

aqi/dependencies.lock
aqi/main/Kconfig
aqi/main/aqi.c
aqi/main/aqi.h

index fd71409715f791b2accabad79d6dacdf67193102..3705fbd52555a6875cabd8f3679e307e17ac26d7 100644 (file)
@@ -9,7 +9,7 @@ dependencies:
     component_hash: null
     source:
       type: idf
-    version: 5.2.0
+    version: 5.2.1
 manifest_hash: fe84d3cba19d4dc50bbd18a9bed2d19eaa863b4be82668f06a4725d7b974e0c1
 target: esp32h2
 version: 1.0.0
index e4fbdd34cdd4c77c16b4940c8fa63d9a701bf9aa..be793409dec8059e05a9a72d620e0d3e329209fb 100644 (file)
@@ -8,6 +8,7 @@ config TEMP_ENABLED
 config TEMP_PIN
     int "If using temperature, on which GPIO?"
     default 4
+    depends on TEMP_ENABLED
     help
       Gets passed and used later for includes
 config EEPY_DEVICE
@@ -17,6 +18,7 @@ config EEPY_DEVICE
       Working on this
 config LIGHT_SLEEP_ENABLED
     bool "Actually go into light sleep between polls?"
+    depends on EEPY_DEVICE
     default n
     help
       Not very well tested yet
@@ -39,7 +41,16 @@ config AQI_RX_PIN
     help
       Gets passed and used later for includes
 
-config CO2_ENABLED
+config CO2_PWM_ENABLED
+    bool "Fetch and report CO2 from PWM?"
+    default n
+
+config CO2_PWM_PIN
+    int "PWM RX pin for CO2"
+    depends on CO2_PWM_ENABLED
+    default 0
+
+config CO2_UART_ENABLED
     bool "Fetch and report CO2 from UART?"
     default n
     help
@@ -48,18 +59,17 @@ config CO2_ENABLED
 config CO2_TX_PIN
     int "UART TX pin for CO2"
     default 22
+    depends on CO2_UART_ENABLED
     help
       Gets passed and used later for includes
 
 config CO2_RX_PIN
     int "UART RX pin for CO2"
     default 25
+    depends on CO2_UART_ENABLED
     help
       Gets passed and used later for includes
 
-
-
-
 config WIFI_ENABLED
     bool "Are we using wifi association and MQTT"
     default n
@@ -82,6 +92,7 @@ config MOTION_FIRST_ENABLED
       Gets passed and used later for includes
 config MOTION_FIRST_PIN
     int "If using motion, on which GPIO?"
+    depends on MOTION_FIRST_ENABLED
     default 22
     help
       Gets passed and used later for includes
@@ -93,6 +104,7 @@ config MOTION_SECOND_ENABLED
       Gets passed and used later for includes
 config MOTION_SECOND_PIN
     int "If using motion, on which GPIO?"
+    depends on MOTION_SECOND_ENABLED
     default 22
     help
       Gets passed and used later for includes
@@ -110,33 +122,41 @@ config PWM_ENABLED
 config PWM_TOPIC
     string "Topic to listen on for PWM data"
     default "esp32/pwm/default"
+    depends on PWM_ENABLED
 
 config PWM_TARGET_PERCENT
     int "Target percentage for PWM"
+    depends on PWM_ENABLED
     default 50
 
 config PWM_R1_GPIO
     int "Red 1 GPIO LED, -1 for disabled"
+    depends on PWM_ENABLED
     default -1
 
 config PWM_G1_GPIO
     int "Green 1 GPIO LED, -1 for disabled"
+    depends on PWM_ENABLED
     default -1
 
 config PWM_B1_GPIO
     int "Blue 1 GPIO LED, -1 for disabled"
+    depends on PWM_ENABLED
     default -1
 
 config PWM_R2_GPIO
     int "Red 2 GPIO LED, -1 for disabled"
+    depends on PWM_ENABLED
     default -1
 
 config PWM_G2_GPIO
     int "Green 2 GPIO LED, -1 for disabled"
+    depends on PWM_ENABLED
     default -1
 
 config PWM_B2_GPIO
     int "Blue 2 GPIO LED, -1 for disabled"
+    depends on PWM_ENABLED
     default -1
 
 
index 4cead69cdbb29be62696082a03a169adf50032d0..0dfae010a98fde1395679c27284114287f98956f 100644 (file)
@@ -46,7 +46,7 @@ typedef struct report_data_s {
   uint16_t pm25;
   float aqi;
 #endif
-#ifdef CONFIG_CO2_ENABLED
+#if defined(CONFIG_CO2_UART_ENABLED) || defined(CONFIG_CO2_PWM_ENABLED)
   int16_t co2_ppm;
 #endif
 #ifdef CONFIG_TEMP_ENABLED
@@ -62,7 +62,6 @@ typedef struct report_data_s {
 } report_data_t;
 // end report data
 
-
 static temperature_sensor_handle_t temp_handle;
 static QueueHandle_t event_queue;
 static QueueHandle_t send_queue;
@@ -628,7 +627,7 @@ void init_uart_aqi(void) {
 }
 #endif
 
-#ifdef CONFIG_CO2_ENABLED
+#ifdef CONFIG_CO2_UART_ENABLED
 // hardcoded given the co2 device we expect to find
 void init_uart_co2(void) {
     const uart_config_t uart_config = {
@@ -709,9 +708,80 @@ int16_t co2_read_sensor(void){
     // we read the correct number of bytes in response, now need to parse and return
     return co2_parse_sensor_data(co2_data);
 }
+#endif // CONFIG_CO2_UART_ENABLED
 
 
-#endif
+#ifdef CONFIG_CO2_PWM_ENABLED
+static void co2_pwm_task(void* discard){
+    TickType_t xLastWakeTime;
+    const TickType_t xFrequency = pdMS_TO_TICKS(10); //1/portTICK_PERIOD_MS;
+
+    // Initialise the xLastWakeTime variable with the current time.
+    xLastWakeTime = xTaskGetTickCount();
+    int counter = 0;
+    int total_high = 0;
+    int total_low = 0;
+    int total_ppm = 0;
+    int level = 0;
+
+    //zero-initialize the config structure.
+    gpio_config_t io_conf = {};
+
+    // GPIO interrupt on either rising or falling edge
+    io_conf.intr_type = GPIO_INTR_DISABLE; 
+    // bit mask of the pins, set to nothing at the start
+    io_conf.pin_bit_mask = 0;
+    io_conf.pin_bit_mask |= (1ULL << CONFIG_CO2_PWM_PIN );
+
+    //set as input mode
+    io_conf.mode = GPIO_MODE_INPUT;
+    // enable pull-down mode
+    io_conf.pull_down_en = 1;
+    io_conf.pull_up_en = 0;
+    // hysteresis separates the thresholds for high/low so we don't have a lot of toggling in between
+    io_conf.hys_ctrl_mode = GPIO_HYS_SOFT_ENABLE;
+    gpio_config(&io_conf);
+
+     
+    // this will loop every 10 seconds and slur out a little less than 10 1004ms periods
+    // but that's okay, since we expect CO2 to mostly stay the same and anyway this will be an average of the cycle
+    // the _important_ bit is just to be consistent on delay, so using vTaskDelayUntil helps there
+    //
+    // see MH-Z19B datasheet for more details
+    for( ;; ){
+        // each period is 1004ms, so we'll just count up to that to keep the formula the same
+        if (counter > 1003){
+            // formula from the datasheet
+            total_ppm = (5000*(total_high-2))/(total_high+total_low-4);
+            // doesn't really matter when we start the counting loop, so just block until we have the semaphore
+            // (should only be unavailable if another task is updating, or more likely we're generating the report)
+            xSemaphoreTake(report_data.mutex, portMAX_DELAY);
+            report_data.co2_ppm = total_ppm;
+            xSemaphoreGive(report_data.mutex);            
+            
+            total_high = 0;
+            total_low = 0;
+            counter = 0;
+        }
+
+        // get the 
+        level = gpio_get_level(CONFIG_CO2_PWM_PIN);
+
+        if (level == 1){
+            total_high += 1;
+        } else {
+            total_low += 1;
+        }
+
+        // Wait for the next cycle.
+        vTaskDelayUntil( &xLastWakeTime, xFrequency );
+        counter+=1;
+
+        // Perform action here.
+    }
+}
+
+#endif // CONFIG_CO2_PWM_ENABLED
 
 
 // this task sends identifying information over mqtt, sleeps an hour, then repeats
@@ -843,7 +913,8 @@ static void send_report_summary(report_data_t report_data){
     cJSON_AddNumberToObject(root, "pm25", ((float)report_data.pm25/100));
     cJSON_AddNumberToObject(root, "aqi", report_data.aqi);
 #endif    
-#ifdef CONFIG_CO2_ENABLED
+#if defined(CONFIG_CO2_UART_ENABLED) || defined(CONFIG_CO2_PWM_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);
@@ -1343,15 +1414,13 @@ static void motion_task(void* discard){
 
 static void monitoring_task(void* discard)
 {
-short temp_avg = 0;
 
 #ifdef CONFIG_TEMP_ENABLED
+    short temp_avg = 0;
     init_temp();
 #endif
 
 #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;
@@ -1439,17 +1508,15 @@ short temp_avg = 0;
 #endif
         
 #endif // end AQI UART section
-#ifdef CONFIG_CO2_ENABLED
+#ifdef CONFIG_CO2_UART_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
         temp_avg = new_temp_average(get_temp());
@@ -1751,9 +1818,12 @@ void app_main(void)
     init_uart_aqi();    
     init_pmbuffer();
 #endif
-#ifdef CONFIG_CO2_ENABLED
+#ifdef CONFIG_CO2_UART_ENABLED
     init_uart_co2();
 #endif
+#ifdef CONFIG_CO2_PWM_ENABLED
+    xTaskCreate(co2_pwm_task, "co2_pwm_task", 4096, NULL, 2, NULL);
+#endif
 #ifdef CONFIG_TEMP_ENABLED
     init_tempbuffer();
 #endif
index 0f2c25d21802dc831eff5e4afe4d807ac4c2fa53..06087f078c0483a93d6f10808d2c6fb93d840494 100644 (file)
@@ -106,7 +106,7 @@ static const char *TAG = "aqi";
 #define MAX_TEMP_VALUE 12500
 #endif
 
-#ifdef CONFIG_CO2_ENABLED
+#ifdef CONFIG_CO2_UART_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}