diff --git a/microenv.yaml b/microenv.yaml index cbd6bb3..e8a9e08 100644 --- a/microenv.yaml +++ b/microenv.yaml @@ -1,7 +1,7 @@ --- ############################################################################### -# SuperSensor v2.0 ESPHome configuration +# MicroEnv v1.0 ESPHome configuration ############################################################################### # # Copyright (C) 2025 Joshua M. Boniface @@ -85,37 +85,62 @@ i2c: scan: true sensor: - - platform: sgp30 - address: 0x58 - eco2: - name: "SGP30 eCO2" - id: sgp30_eco2 - accuracy_decimals: 1 - icon: mdi:molecule-co2 + - platform: sgp4x + voc: + name: "SGP41 VOC Index" + id: sgp41_voc_index + accuracy_decimals: 0 + icon: mdi:molecule filters: - - sliding_window_moving_average: - window_size: 20 - send_every: 1 - tvoc: - name: "SGP30 TVOC" - id: sgp30_tvoc - accuracy_decimals: 1 + - sliding_window_moving_average: # We take a reading every 15 seconds, but calculate the sliding + window_size: 12 # average over 12 readings i.e. 60 seconds/1 minute to normalize + send_every: 3 # brief spikes while still sending a value every 15 seconds. + nox: + name: "SGP41 NOx Index" + id: sgp41_nox_index + accuracy_decimals: 0 icon: mdi:molecule filters: - sliding_window_moving_average: - window_size: 20 - send_every: 1 - eco2_baseline: - name: "SGP30 Baseline eCO2" - id: sgp30_baseline_ec02 - tvoc_baseline: - name: "SGP30 Baseline TVOC" - id: sgp30_baseline_tvoc + window_size: 12 + send_every: 3 compensation: temperature_source: sht45_temperature humidity_source: sht45_humidity - store_baseline: yes - update_interval: 15s + store_baseline: true + update_interval: 5s + + - platform: template + name: "SGP41 TVOC (µg/m³)" + id: sgp41_tvoc_ugm3 + lambda: |- + float i = id(sgp41_voc_index).state; + if (i < 1) return NAN; + float tvoc = (log(501.0 - i) - 6.24) * -878.53; + return tvoc; + unit_of_measurement: "µg/m³" + accuracy_decimals: 0 + + - platform: template + name: "SGP41 TVOC (ppb)" + id: sgp41_tvoc_ppb + lambda: |- + float tvoc_ugm3 = id(sgp41_tvoc_ugm3).state; + float tvoc_ppm = tvoc_ugm3 * 0.436; // ppb estimated using isobutylene MW (56.1 g/mol) + return tvoc_ppm; + unit_of_measurement: "ppb" + accuracy_decimals: 0 + + - platform: template + name: "SGP41 eCO2 (approx ppm)" + id: sgp41_eco2_ppm + lambda: |- + float tvoc_ppb = id(sgp41_tvoc_ppb).state; + float eco2_ppm = 400.0 + 1.5 * tvoc_ppb; + if (eco2_ppm > 2000) eco2_ppm = 2000; + return eco2_ppm; + unit_of_measurement: "ppm" + accuracy_decimals: 0 - platform: sht4x temperature: @@ -125,7 +150,7 @@ sensor: filters: - offset: !lambda return id(temperature_offset); - sliding_window_moving_average: - window_size: 20 + window_size: 4 send_every: 1 humidity: name: "SHT45 Relative Humidity" @@ -134,7 +159,7 @@ sensor: filters: - offset: !lambda return id(humidity_offset); - sliding_window_moving_average: - window_size: 20 + window_size: 4 send_every: 1 heater_max_duty: 0.0 update_interval: 15s @@ -159,50 +184,6 @@ sensor: return (b * alpha) / (a - alpha); update_interval: 15s - # IAQ Index (1-5, 5=Great)) - - platform: template - name: "IAQ Index" - icon: mdi:air-purifier - id: iaq_index - lambda: |- - int tvoc = id(sgp30_tvoc).state; - int eco2 = id(sgp30_eco2).state; - if (tvoc > 2200 || eco2 > 2000) return 1; // Bad - if (tvoc > 660 || eco2 > 1200) return 2; // Poor - if (tvoc > 220 || eco2 > 800) return 3; // Fair - if (tvoc > 65 || eco2 > 500) return 4; // Good - return 5; // Great - update_interval: 15s - - # Room Health Score (1-4, 4=Optimal) - - platform: template - name: "Room Health Score" - icon: mdi:home-thermometer - id: room_health - lambda: |- - float temp = id(sht45_temperature).state; - float rh = id(sht45_humidity).state; - int iaq = id(iaq_index).state; - - bool temp_ok = (temp >= 18 && temp <= 24); - bool hum_ok = (rh >= 30 && rh <= 70); - bool iaq_ok = (iaq >= 4); - - int conditions_met = 0; - if (temp_ok) conditions_met++; - if (hum_ok) conditions_met++; - if (iaq_ok) conditions_met++; - - if (iaq_ok && temp_ok && hum_ok) { - return 4; // Optimal: All conditions met and IAQ is excellent/good - } else if (iaq >= 3 && conditions_met >= 2) { - return 3; // Fair: IAQ is moderate and at least 2 conditions met - } else if (iaq >= 2 && conditions_met >= 1) { - return 2; // Poor: IAQ is poor and at least 1 condition met - } else { - return 1; // Bad: All conditions failed or IAQ is unhealthy - } - - platform: wifi_signal name: "WiFi Signal" update_interval: 60s @@ -228,57 +209,6 @@ text_sensor: mac_address: name: "WiFi MAC Address" - # VOC Level - - platform: template - name: "VOC Level" - icon: mdi:molecule - lambda: |- - int tvoc = id(sgp30_tvoc).state; - if (tvoc < 65) return {"Great"}; - if (tvoc < 220) return {"Good"}; - if (tvoc < 660) return {"Fair"}; - if (tvoc < 2200) return {"Poor"}; - return {"Bad"}; - update_interval: 15s - - # CO2 Level - - platform: template - name: "CO2 Level" - icon: mdi:molecule-co2 - lambda: |- - int eco2 = id(sgp30_eco2).state; - if (eco2 < 500) return {"Great"}; - if (eco2 < 800) return {"Good"}; - if (eco2 < 1200) return {"Fair"}; - if (eco2 < 2000) return {"Poor"}; - return {"Bad"}; - update_interval: 15s - - # IAQ Classification - - platform: template - name: "IAQ Classification" - icon: mdi:air-purifier - lambda: |- - int iaq = id(iaq_index).state; - if (iaq == 5) return {"Great"}; - if (iaq == 4) return {"Good"}; - if (iaq == 3) return {"Fair"}; - if (iaq == 2) return {"Poor"}; - return {"Bad"}; - update_interval: 15s - - # Room Health - - platform: template - name: "Room Health" - icon: mdi:home-thermometer - lambda: |- - int score = id(room_health).state; - if (score == 4) return {"Optimal"}; - if (score == 3) return {"Fair"}; - if (score == 2) return {"Poor"}; - return {"Bad"}; - update_interval: 15s - button: - platform: restart name: "ESP32 Restart"