diff --git a/airgradient-basic.yaml b/airgradient-basic.yaml index 16e8a69..11e140b 100644 --- a/airgradient-basic.yaml +++ b/airgradient-basic.yaml @@ -5,7 +5,7 @@ substitutions: name: "ag-basic" friendly_name: "AG Basic" - config_version: 2.0.0 + config_version: 2.0.1 name_add_mac_suffix: "false" # Must have quotes around value # Enable Home Assistant API diff --git a/airgradient-one.yaml b/airgradient-one.yaml index 647516f..336aa6d 100644 --- a/airgradient-one.yaml +++ b/airgradient-one.yaml @@ -4,7 +4,7 @@ substitutions: name: "ag-one" friendly_name: "AG One" - config_version: 2.0.0 + config_version: 2.0.1 name_add_mac_suffix: "false" # Must have quotes around value # Enable Home Assistant API diff --git a/airgradient-pro.yaml b/airgradient-pro.yaml index 0932914..622e4b6 100644 --- a/airgradient-pro.yaml +++ b/airgradient-pro.yaml @@ -4,7 +4,7 @@ substitutions: name: "ag-pro" friendly_name: "AG Pro" - config_version: 2.0.0 + config_version: 2.0.1 name_add_mac_suffix: "false" # Must have quotes around value # Enable Home Assistant API diff --git a/full_config/ag-basic.yaml b/full_config/ag-basic.yaml index 0b1c418..b6388c9 100644 --- a/full_config/ag-basic.yaml +++ b/full_config/ag-basic.yaml @@ -1,7 +1,7 @@ substitutions: name: ag-basic friendly_name: AG Basic - config_version: 2.0.0 + config_version: 2.0.1 name_add_mac_suffix: 'false' esphome: name: ag-basic @@ -559,4 +559,3 @@ wifi: dashboard_import: package_import_url: github://MallocArray/airgradient_esphome/airgradient-basic.yaml import_full_config: false - diff --git a/full_config/ag-one.yaml b/full_config/ag-one.yaml index 2f9cc52..947ee98 100644 --- a/full_config/ag-one.yaml +++ b/full_config/ag-one.yaml @@ -1,7 +1,7 @@ substitutions: name: ag-one friendly_name: AG One - config_version: 2.0.0 + config_version: 2.0.1 name_add_mac_suffix: 'false' esphome: name: ag-one @@ -49,7 +49,7 @@ logger: component: ERROR tx_buffer_size: 512 deassert_rts_dtr: false - hardware_uart: UART0 + hardware_uart: USB_CDC level: DEBUG captive_portal: {} uart: @@ -1423,8 +1423,8 @@ binary_sensor: - logger.log: format: Toggling display betwen C and F level: DEBUG - tag: main args: [] + tag: main - switch.toggle: id: display_in_f invalid_cooldown: 1s @@ -1438,8 +1438,8 @@ binary_sensor: - logger.log: format: Starting manual CO2 calibration level: DEBUG - tag: main args: [] + tag: main - senseair.background_calibration: id: senseair_s8 - delay: 70s @@ -1469,4 +1469,3 @@ wifi: dashboard_import: package_import_url: github://MallocArray/airgradient_esphome/airgradient-one.yaml import_full_config: false - diff --git a/full_config/ag-open-air-o-1ppt.yaml b/full_config/ag-open-air-o-1ppt.yaml new file mode 100644 index 0000000..857e0f2 --- /dev/null +++ b/full_config/ag-open-air-o-1ppt.yaml @@ -0,0 +1,636 @@ +substitutions: + name: ag-open-air-o-1ppt + friendly_name: AG Open Air O-1PPT + config_version: 2.0.1 + name_add_mac_suffix: 'false' +esphome: + name: ag-open-air-o-1ppt + friendly_name: AG Open Air O-1PPT + name_add_mac_suffix: false + project: + name: mallocarray.airgradient + version: 2.0.1 + min_version: 2023.12.0 + on_boot: + - priority: 200.0 + then: + - if: + condition: + switch.is_on: + id: upload_airgradient + then: + - http_request.post: + url: !lambda |- + return "http://hw.airgradient.com/sensors/airgradient:" + get_mac_address() + "/measures"; + headers: + Content-Type: application/json + json: + wifi: !lambda |- + return to_string(-50); + verify_ssl: true + method: POST + build_path: build/ag-open-air-o-1ppt + area: '' + platformio_options: {} + includes: [] + libraries: [] +esp32: + board: esp32-c3-devkitm-1 + flash_size: 4MB + framework: + version: 2.0.5 + source: ~3.20005.0 + platform_version: platformio/espressif32@5.4.0 + type: arduino + variant: ESP32C3 +logger: + baud_rate: 0 + logs: + component: ERROR + tx_buffer_size: 512 + deassert_rts_dtr: false + hardware_uart: USB_CDC + level: DEBUG +captive_portal: {} +uart: +- rx_pin: + number: 0 + mode: + input: true + output: false + open_drain: false + pullup: false + pulldown: false + inverted: false + ignore_strapping_warning: false + drive_strength: 20.0 + tx_pin: + number: 1 + mode: + output: true + input: false + open_drain: false + pullup: false + pulldown: false + inverted: false + ignore_strapping_warning: false + drive_strength: 20.0 + baud_rate: 9600 + id: senseair_s8_uart + rx_buffer_size: 256 + stop_bits: 1 + data_bits: 8 + parity: NONE +- rx_pin: + number: 20 + mode: + input: true + output: false + open_drain: false + pullup: false + pulldown: false + inverted: false + ignore_strapping_warning: false + drive_strength: 20.0 + tx_pin: + number: 21 + mode: + output: true + input: false + open_drain: false + pullup: false + pulldown: false + inverted: false + ignore_strapping_warning: false + drive_strength: 20.0 + baud_rate: 9600 + id: pms5003_uart + rx_buffer_size: 256 + stop_bits: 1 + data_bits: 8 + parity: NONE +i2c: +- sda: 7 + scl: 6 + frequency: 400000.0 + scan: true +button: +- platform: factory_reset + disabled_by_default: true + name: Factory Reset ESP + id: factory_reset_all + icon: mdi:restart-alert + entity_category: config + device_class: restart +sensor: +- platform: pmsx003 + type: PMS5003T + uart_id: pms5003_uart + pm_2_5: + name: PM 2.5 + id: pm_2_5 + device_class: pm25 + filters: + - sliding_window_moving_average: + window_size: 30 + send_every: 30 + send_first_at: 1 + disabled_by_default: false + force_update: false + unit_of_measurement: µg/m³ + icon: mdi:chemical-weapon + accuracy_decimals: 0 + state_class: measurement + pm_1_0: + name: PM 1.0 + id: pm_1_0 + device_class: pm1 + filters: + - sliding_window_moving_average: + window_size: 30 + send_every: 30 + send_first_at: 1 + disabled_by_default: false + force_update: false + unit_of_measurement: µg/m³ + icon: mdi:chemical-weapon + accuracy_decimals: 0 + state_class: measurement + pm_10_0: + name: PM 10.0 + id: pm_10_0 + device_class: pm10 + filters: + - sliding_window_moving_average: + window_size: 30 + send_every: 30 + send_first_at: 1 + disabled_by_default: false + force_update: false + unit_of_measurement: µg/m³ + icon: mdi:chemical-weapon + accuracy_decimals: 0 + state_class: measurement + pm_0_3um: + name: PM 0.3 + id: pm_0_3um + filters: + - sliding_window_moving_average: + window_size: 30 + send_every: 30 + send_first_at: 1 + disabled_by_default: false + force_update: false + unit_of_measurement: /dL + icon: mdi:chemical-weapon + accuracy_decimals: 0 + temperature: + name: Temperature + id: temp + filters: + - lambda: !lambda |- + // Fix negative values for now + if (x > 6000) { + return (x - 6553.6 - 4.55) / 0.83; + } + return (x - 4.55) / 0.83; + - sliding_window_moving_average: + window_size: 30 + send_every: 30 + send_first_at: 1 + disabled_by_default: false + force_update: false + unit_of_measurement: °C + accuracy_decimals: 1 + device_class: temperature + state_class: measurement + humidity: + name: Humidity + id: humidity + filters: + - lambda: !lambda |- + return x * 1.3921 - 1.0245; + - sliding_window_moving_average: + window_size: 30 + send_every: 30 + send_first_at: 1 + disabled_by_default: false + force_update: false + unit_of_measurement: '%' + accuracy_decimals: 1 + device_class: humidity + state_class: measurement + update_interval: 0s +- platform: template + name: PM 2.5 AQI + id: pm_2_5_aqi + update_interval: 5min + unit_of_measurement: AQI + icon: mdi:air-filter + accuracy_decimals: 0 + filters: + - skip_initial: 1 + lambda: !lambda |- + // https://en.wikipedia.org/wiki/Air_quality_index#Computing_the_AQI + // Borrowed from https://github.com/kylemanna/sniffer/blob/master/esphome/sniffer_common.yaml + if (id(pm_2_5).state <= 12.0) { + // good + return((50.0 - 0.0) / (12.0 - 0.0) * (id(pm_2_5).state - 0.0) + 0.0); + } else if (id(pm_2_5).state <= 35.4) { + // moderate + return((100.0 - 51.0) / (35.4 - 12.1) * (id(pm_2_5).state - 12.1) + 51.0); + } else if (id(pm_2_5).state <= 55.4) { + // usg + return((150.0 - 101.0) / (55.4 - 35.5) * (id(pm_2_5).state - 35.5) + 101.0); + } else if (id(pm_2_5).state <= 150.4) { + // unhealthy + return((200.0 - 151.0) / (150.4 - 55.5) * (id(pm_2_5).state - 55.5) + 151.0); + } else if (id(pm_2_5).state <= 250.4) { + // very unhealthy + return((300.0 - 201.0) / (250.4 - 150.5) * (id(pm_2_5).state - 150.5) + 201.0); + } else if (id(pm_2_5).state <= 350.4) { + // hazardous + return((400.0 - 301.0) / (350.4 - 250.5) * (id(pm_2_5).state - 250.5) + 301.0); + } else if (id(pm_2_5).state <= 500.4) { + // hazardous 2 + return((500.0 - 401.0) / (500.4 - 350.5) * (id(pm_2_5).state - 350.5) + 401.0); + } else { + return(500); + } + disabled_by_default: false + force_update: false +- platform: pmsx003 + type: PMS5003T + uart_id: senseair_s8_uart + pm_2_5: + name: PM 2.5 (2) + id: pm_2_5_2 + device_class: pm25 + filters: + - sliding_window_moving_average: + window_size: 30 + send_every: 30 + send_first_at: 1 + disabled_by_default: false + force_update: false + unit_of_measurement: µg/m³ + icon: mdi:chemical-weapon + accuracy_decimals: 0 + state_class: measurement + pm_1_0: + name: PM 1.0 (2) + id: pm_1_0_2 + device_class: pm1 + filters: + - sliding_window_moving_average: + window_size: 30 + send_every: 30 + send_first_at: 1 + disabled_by_default: false + force_update: false + unit_of_measurement: µg/m³ + icon: mdi:chemical-weapon + accuracy_decimals: 0 + state_class: measurement + pm_10_0: + name: PM 10.0 (2) + id: pm_10_0_2 + device_class: pm10 + filters: + - sliding_window_moving_average: + window_size: 30 + send_every: 30 + send_first_at: 1 + disabled_by_default: false + force_update: false + unit_of_measurement: µg/m³ + icon: mdi:chemical-weapon + accuracy_decimals: 0 + state_class: measurement + pm_0_3um: + name: PM 0.3 (2) + id: pm_0_3um_2 + filters: + - sliding_window_moving_average: + window_size: 30 + send_every: 30 + send_first_at: 1 + disabled_by_default: false + force_update: false + unit_of_measurement: /dL + icon: mdi:chemical-weapon + accuracy_decimals: 0 + temperature: + name: Temperature (2) + id: temp_2 + filters: + - lambda: !lambda |- + // Fix negative values for now + if (x > 6000) { + return (x - 6553.6 - 4.55) / 0.83; + } + return (x - 4.55) / 0.83; + - sliding_window_moving_average: + window_size: 30 + send_every: 30 + send_first_at: 1 + disabled_by_default: false + force_update: false + unit_of_measurement: °C + accuracy_decimals: 1 + device_class: temperature + state_class: measurement + humidity: + name: Humidity (2) + id: humidity_2 + filters: + - lambda: !lambda |- + return x * 1.3921 - 1.0245; + - sliding_window_moving_average: + window_size: 30 + send_every: 30 + send_first_at: 1 + disabled_by_default: false + force_update: false + unit_of_measurement: '%' + accuracy_decimals: 1 + device_class: humidity + state_class: measurement + update_interval: 0s +- platform: template + id: pm_2_5_avg + name: PM 2.5 (Average) + lambda: !lambda |- + return (id(pm_2_5).state + id(pm_2_5_2).state) / 2.0; + device_class: pm25 + unit_of_measurement: µg/m³ + icon: mdi:chemical-weapon + accuracy_decimals: 0 + state_class: measurement + update_interval: 1s + filters: + - sliding_window_moving_average: + window_size: 30 + send_every: 15 + send_first_at: 1 + disabled_by_default: false + force_update: false +- platform: template + id: pm_1_0_avg + name: PM 1.0 (Average) + lambda: !lambda |- + return (id(pm_1_0).state + id(pm_1_0_2).state) / 2.0; + device_class: pm1 + unit_of_measurement: µg/m³ + icon: mdi:chemical-weapon + accuracy_decimals: 0 + state_class: measurement + update_interval: 1s + filters: + - sliding_window_moving_average: + window_size: 30 + send_every: 15 + send_first_at: 1 + disabled_by_default: false + force_update: false +- platform: template + id: pm_10_0_avg + name: PM 10.0 (Average) + lambda: !lambda |- + return (id(pm_10_0).state + id(pm_10_0_2).state) / 2.0; + device_class: pm10 + unit_of_measurement: µg/m³ + icon: mdi:chemical-weapon + accuracy_decimals: 0 + state_class: measurement + update_interval: 1s + filters: + - sliding_window_moving_average: + window_size: 30 + send_every: 15 + send_first_at: 1 + disabled_by_default: false + force_update: false +- platform: template + id: pm_0_3um_avg + name: PM 0.3 (Average) + lambda: !lambda |- + return (id(pm_0_3um).state + id(pm_0_3um_2).state) / 2.0; + unit_of_measurement: /dL + icon: mdi:chemical-weapon + accuracy_decimals: 0 + state_class: measurement + update_interval: 1s + filters: + - sliding_window_moving_average: + window_size: 30 + send_every: 15 + send_first_at: 1 + disabled_by_default: false + force_update: false +- platform: template + id: temp_avg + name: Temperature (Average) + lambda: !lambda |- + return (id(temp).state + id(temp_2).state) / 2.0; + unit_of_measurement: °C + accuracy_decimals: 1 + device_class: temperature + state_class: measurement + icon: mdi:thermometer + update_interval: 1s + filters: + - sliding_window_moving_average: + window_size: 30 + send_every: 15 + send_first_at: 1 + disabled_by_default: false + force_update: false +- platform: template + id: humidity_avg + name: Humidity (Average) + lambda: !lambda |- + return (id(humidity).state + id(humidity_2).state) / 2.0; + unit_of_measurement: '%' + accuracy_decimals: 1 + device_class: humidity + state_class: measurement + icon: mdi:water-percent + update_interval: 1s + filters: + - sliding_window_moving_average: + window_size: 30 + send_every: 15 + send_first_at: 1 + disabled_by_default: false + force_update: false +- platform: sgp4x + voc: + name: VOC Index + id: voc + disabled_by_default: false + force_update: false + icon: mdi:radiator + accuracy_decimals: 0 + device_class: aqi + state_class: measurement + nox: + name: NOx Index + id: nox + disabled_by_default: false + force_update: false + icon: mdi:radiator + accuracy_decimals: 0 + device_class: aqi + state_class: measurement + compensation: + temperature_source: temp + humidity_source: humidity + store_baseline: true + update_interval: 60s + address: 0x59 +- platform: wifi_signal + name: WiFi Signal + id: wifi_dbm + update_interval: 60s + disabled_by_default: false + force_update: false + unit_of_measurement: dBm + accuracy_decimals: 0 + device_class: signal_strength + state_class: measurement + entity_category: diagnostic +- platform: uptime + name: Uptime + id: device_uptime + disabled_by_default: false + force_update: false + unit_of_measurement: s + icon: mdi:timer-outline + accuracy_decimals: 0 + device_class: duration + state_class: total_increasing + entity_category: diagnostic + update_interval: 60s +interval: +- interval: 150s + then: + - if: + condition: + switch.is_on: + id: upload_airgradient + then: + - http_request.post: + url: !lambda |- + return "http://hw.airgradient.com/sensors/airgradient:" + get_mac_address() + "/measures"; + headers: + Content-Type: application/json + body: !lambda | + String jsonString; + StaticJsonDocument<1024> doc; + + doc["wifi"] = id(wifi_dbm).state; + + doc["pm01"] = to_string(id(pm_1_0_avg).state); + doc["pm02"] = to_string(id(pm_2_5_avg).state); + doc["pm10"] = to_string(id(pm_10_0_avg).state); + doc["pm003_count"] = to_string(id(pm_0_3um_avg).state); + doc["atmp"] = to_string(id(temp_avg).state); + doc["rhum"] = to_string(id(humidity_avg).state); + + // We don't have access to the boot loop counter in esphome, so just send a 1 + // See: https://github.com/esphome/issues/issues/1539 + doc["boot"] = "1"; + + JsonObject channels = doc.createNestedObject("channels"); + + JsonObject channels_1 = channels.createNestedObject("1"); + channels_1["pm01"] = to_string(id(pm_1_0).state); + channels_1["pm02"] = to_string(id(pm_2_5).state); + channels_1["pm10"] = to_string(id(pm_10_0).state); + channels_1["pm003_count"] = to_string(id(pm_0_3um).state); + channels_1["atmp"] = to_string(id(temp).state); + channels_1["rhum"] = to_string(id(humidity).state); + + JsonObject channels_2 = channels.createNestedObject("2"); + channels_2["pm01"] = to_string(id(pm_1_0_2).state); + channels_2["pm02"] = to_string(id(pm_2_5_2).state); + channels_2["pm10"] = to_string(id(pm_10_0_2).state); + channels_2["pm003_count"] = to_string(id(pm_0_3um_2).state); + channels_2["atmp"] = to_string(id(temp_2).state); + channels_2["rhum"] = to_string(id(humidity_2).state); + + // Serialize the JSON document into the string + serializeJson(doc, jsonString); + + // Convert String to std::string + std::string stdJsonString(jsonString.c_str()); + + return stdJsonString; + verify_ssl: true + method: POST + startup_delay: 0s +- interval: 150s + then: + - output.turn_on: + id: watchdog + - delay: 20ms + - output.turn_off: + id: watchdog + startup_delay: 0s +switch: +- platform: template + name: Upload to AirGradient Dashboard + id: upload_airgradient + restore_mode: RESTORE_DEFAULT_ON + optimistic: true + disabled_by_default: false + assumed_state: false +- platform: safe_mode + name: Flash Mode (Safe Mode) + icon: mdi:cellphone-arrow-down + disabled_by_default: false + restore_mode: ALWAYS_OFF + entity_category: config +http_request: + useragent: ESPHome + follow_redirects: true + redirect_limit: 3 + timeout: 5s +output: +- platform: gpio + id: watchdog + pin: + number: 2 + ignore_strapping_warning: true + mode: + output: true + input: false + open_drain: false + pullup: false + pulldown: false + inverted: false + drive_strength: 20.0 +api: + port: 6053 + password: \033[5m''\033[6m + reboot_timeout: 15min +ota: + safe_mode: true + port: 3232 + reboot_timeout: 5min + num_attempts: 10 +wifi: + ap: + ap_timeout: 1min + domain: .local + reboot_timeout: 15min + power_save_mode: LIGHT + fast_connect: false + passive_scan: false + enable_on_boot: true + use_address: ag-open-air-o-1ppt.local +dashboard_import: + package_import_url: github://MallocArray/airgradient_esphome/airgradient-open-air-o-1ppt.yaml + import_full_config: false + diff --git a/full_config/ag-open-air-o-1pst.yaml b/full_config/ag-open-air-o-1pst.yaml index c655761..88d7672 100644 --- a/full_config/ag-open-air-o-1pst.yaml +++ b/full_config/ag-open-air-o-1pst.yaml @@ -1,7 +1,7 @@ substitutions: name: ag-open-air-o-1pst friendly_name: AG Open Air O-1PST - config_version: 2.0.0 + config_version: 2.0.1 name_add_mac_suffix: 'false' esphome: name: ag-open-air-o-1pst @@ -9,7 +9,7 @@ esphome: name_add_mac_suffix: false project: name: mallocarray.airgradient - version: 2.0.0 + version: 2.0.1 min_version: 2023.12.0 on_boot: - priority: 200.0 @@ -49,7 +49,7 @@ logger: component: ERROR tx_buffer_size: 512 deassert_rts_dtr: false - hardware_uart: UART0 + hardware_uart: USB_CDC level: DEBUG captive_portal: {} uart: @@ -207,6 +207,12 @@ sensor: name: Temperature id: temp filters: + - lambda: !lambda |- + // Fix negative values for now + if (x > 6000) { + return (x - 6553.6 - 4.55) / 0.83; + } + return (x - 4.55) / 0.83; - sliding_window_moving_average: window_size: 30 send_every: 30 @@ -221,6 +227,8 @@ sensor: name: Humidity id: humidity filters: + - lambda: !lambda |- + return x * 1.3921 - 1.0245; - sliding_window_moving_average: window_size: 30 send_every: 30 @@ -240,7 +248,7 @@ sensor: icon: mdi:air-filter accuracy_decimals: 0 filters: - - skip_initial: 10 + - skip_initial: 1 lambda: !lambda |- // https://en.wikipedia.org/wiki/Air_quality_index#Computing_the_AQI // Borrowed from https://github.com/kylemanna/sniffer/blob/master/esphome/sniffer_common.yaml diff --git a/full_config/ag-pro.yaml b/full_config/ag-pro.yaml index b84b833..0b0a288 100644 --- a/full_config/ag-pro.yaml +++ b/full_config/ag-pro.yaml @@ -1,7 +1,7 @@ substitutions: name: ag-pro friendly_name: AG Pro - config_version: 2.0.0 + config_version: 2.0.1 name_add_mac_suffix: 'false' esphome: name: ag-pro @@ -789,9 +789,9 @@ binary_sensor: then: - logger.log: format: Toggling display betwen C and F - tag: main - level: DEBUG args: [] + level: DEBUG + tag: main - switch.toggle: id: display_in_f invalid_cooldown: 1s @@ -804,9 +804,9 @@ binary_sensor: then: - logger.log: format: Starting manual CO2 calibration - tag: main - level: DEBUG args: [] + level: DEBUG + tag: main - senseair.background_calibration: id: senseair_s8 - delay: 70s @@ -837,4 +837,3 @@ wifi: dashboard_import: package_import_url: github://MallocArray/airgradient_esphome/airgradient-pro.yaml import_full_config: false -