diff --git a/airgradient-basic.bin b/airgradient-basic.bin index 4dff7eb..ef8a9b3 100644 Binary files a/airgradient-basic.bin and b/airgradient-basic.bin differ diff --git a/airgradient-one.bin b/airgradient-one.bin index 7b5093d..ecca8a0 100644 Binary files a/airgradient-one.bin and b/airgradient-one.bin differ diff --git a/airgradient-open-air-o-1pst.bin b/airgradient-open-air-o-1pst.bin index 86edef1..6b857ef 100644 Binary files a/airgradient-open-air-o-1pst.bin and b/airgradient-open-air-o-1pst.bin differ diff --git a/airgradient-pro.bin b/airgradient-pro.bin index b0703fc..9e6ae32 100644 Binary files a/airgradient-pro.bin and b/airgradient-pro.bin differ diff --git a/full_config/ag-basic.yaml b/full_config/ag-basic.yaml index d00a8f8..0b1c418 100644 --- a/full_config/ag-basic.yaml +++ b/full_config/ag-basic.yaml @@ -1,549 +1,562 @@ -substitutions: - devicename: ag-basic - friendly_devicename: AG Basic - ag_esphome_config_version: 1.0.0 - add_mac_suffix: 'true' -esphome: - name: ag-basic - friendly_name: AG Basic - name_add_mac_suffix: true - project: - name: mallocarray.airgradient - version: 1.0.0 - min_version: 2023.7.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().substr(6,11) + "/measures"; - headers: - Content-Type: application/json - json: - wifi: !lambda |- - return to_string(-50); - verify_ssl: true - method: POST - build_path: build/ag-basic - area: '' - platformio_options: {} - includes: [] - libraries: [] -esp8266: - board: d1_mini - restore_from_flash: true - framework: - version: 3.0.2 - source: ~3.30002.0 - platform_version: platformio/espressif8266@3.2.0 - early_pin_init: true - board_flash_mode: dout -logger: - logs: - component: ERROR - baud_rate: 115200 - tx_buffer_size: 512 - deassert_rts_dtr: false - hardware_uart: UART0 - level: DEBUG - esp8266_store_log_strings_in_flash: true -captive_portal: {} -uart: -- rx_pin: - number: 2 - mode: - input: true - output: false - open_drain: false - pullup: false - pulldown: false - analog: false - inverted: false - tx_pin: - number: 0 - mode: - output: true - input: false - open_drain: false - pullup: false - pulldown: false - analog: false - inverted: false - baud_rate: 9600 - id: senseair_s8_uart - rx_buffer_size: 256 - stop_bits: 1 - data_bits: 8 - parity: NONE -- rx_pin: - number: 14 - mode: - input: true - output: false - open_drain: false - pullup: false - pulldown: false - analog: false - inverted: false - tx_pin: - number: 12 - mode: - output: true - input: false - open_drain: false - pullup: false - pulldown: false - analog: false - inverted: false - baud_rate: 9600 - id: pms5003_uart - rx_buffer_size: 256 - stop_bits: 1 - data_bits: 8 - parity: NONE -i2c: -- sda: 4 - scl: 5 - frequency: 400000.0 - scan: true -sensor: -- platform: pmsx003 - type: PMSX003 - uart_id: pms5003_uart - pm_2_5: - name: PM 2.5 - id: pm_2_5 - 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 - 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 - 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 - 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: 10 - 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: senseair - co2: - name: CO2 - id: co2 - filters: - - skip_initial: 2 - - clamp: - min_value: 400.0 - max_value: .nan - ignore_out_of_range: false - disabled_by_default: false - force_update: false - unit_of_measurement: ppm - icon: mdi:molecule-co2 - accuracy_decimals: 0 - device_class: carbon_dioxide - state_class: measurement - id: senseair_s8 - uart_id: senseair_s8_uart - update_interval: 60s -- platform: sht3xd - temperature: - name: Temperature - id: temp - 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 - disabled_by_default: false - force_update: false - unit_of_measurement: '%' - accuracy_decimals: 1 - device_class: humidity - state_class: measurement - address: 0x44 - heater_enabled: false - update_interval: 60s -- 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 -button: -- platform: template - name: SenseAir S8 Calibration - id: senseair_s8_calibrate_button - on_press: - - then: - - senseair.background_calibration: - id: senseair_s8 - - delay: 70s - - senseair.background_calibration_result: - id: senseair_s8 - disabled_by_default: false -- platform: template - name: SenseAir S8 Show Calibration Interval - id: senseair_s8_show_calibrate_interval - on_press: - - then: - - senseair.abc_get_period: - id: senseair_s8 - disabled_by_default: false -switch: -- platform: template - name: SenseAir S8 Automatic Background Calibration - id: senseair_s8_abc_switch - restore_mode: RESTORE_DEFAULT_ON - optimistic: true - entity_category: config - turn_on_action: - then: - - senseair.abc_enable: - id: senseair_s8 - turn_off_action: - then: - - senseair.abc_disable: - id: senseair_s8 - disabled_by_default: false - assumed_state: false -- platform: template - name: Display Temperature in °F - icon: mdi:thermometer - id: display_in_f - restore_mode: RESTORE_DEFAULT_ON - optimistic: true - disabled_by_default: false - assumed_state: false -- 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 -font: -- file: - path: ./fonts/liberation_sans/LiberationSans-Regular.ttf - type: local - id: font1 - size: 24 - glyphs: - - ' ' - - '!' - - '"' - - '%' - - ( - - ) - - + - - ',' - - '-' - - . - - / - - '0' - - '1' - - '2' - - '3' - - '4' - - '5' - - '6' - - '7' - - '8' - - '9' - - ':' - - '=' - - A - - B - - C - - D - - E - - F - - G - - H - - I - - J - - K - - L - - M - - N - - O - - P - - Q - - R - - S - - T - - U - - V - - W - - X - - Y - - Z - - _ - - a - - b - - c - - d - - e - - f - - g - - h - - i - - j - - k - - l - - m - - n - - o - - p - - q - - r - - s - - t - - u - - v - - w - - x - - y - - z - - ° -display: -- platform: ssd1306_i2c - model: SSD1306_64X48 - id: oled_display - reset_pin: - number: 16 - mode: - output: true - input: false - open_drain: false - pullup: false - pulldown: false - analog: false - inverted: false - address: 0x3C - pages: - - id: display_pm2 - lambda: !lambda |- - it.print(0, 0, id(font1), "PM2"); - it.printf(64, 24, id(font1), TextAlign::TOP_RIGHT, "%.0f",id(pm_2_5).state); - - id: display_co2 - lambda: !lambda |- - it.print(0, 0, id(font1), "CO2"); - it.printf(64, 24, id(font1), TextAlign::TOP_RIGHT, "%.0f",id(co2).state); - - id: display_tempf - lambda: !lambda |- - it.print(0, 0, id(font1), "°F"); - it.printf(64, 24, id(font1), TextAlign::TOP_RIGHT, "%.1f",id(temp).state*9/5+32); - - id: display_humidity - lambda: !lambda |- - it.print(0, 0, id(font1), "HUM"); - it.printf(64, 24, id(font1), TextAlign::TOP_RIGHT, "%.0f%%",id(humidity).state); - - id: boot - lambda: !lambda |- - it.printf(0, 0, id(font1), TextAlign::TOP_RIGHT, "%s", get_mac_address().substr(6,11).c_str()); - on_page_change: - - to: boot - then: - - if: - condition: - lambda: !lambda |- - return id(device_uptime).state > 30; - then: - - display.page.show_next: - id: oled_display - - component.update: - id: oled_display - auto_clear_enabled: true - brightness: 1.0 - contrast: 1.0 - flip_x: true - flip_y: true - offset_x: 0 - offset_y: 0 - invert: false - update_interval: 1s -interval: -- interval: 5s - then: - - if: - condition: - lambda: !lambda |- - return id(device_uptime).state < 10; - then: - - display.page.show: - id: boot - else: - - display.page.show_next: - id: oled_display - - component.update: - id: oled_display - startup_delay: 0s -- 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().substr(6,11) + "/measures"; - headers: - Content-Type: application/json - json: - wifi: !lambda |- - return to_string(id(wifi_dbm).state); - pm01: !lambda |- - return to_string(id(pm_1_0).state); - pm02: !lambda |- - return to_string(id(pm_2_5).state); - pm10: !lambda |- - return to_string(id(pm_10_0).state); - pm003_count: !lambda |- - return to_string(id(pm_0_3um).state); - rco2: !lambda |- - return to_string(id(co2).state); - atmp: !lambda |- - return to_string(id(temp).state); - rhum: !lambda |- - return to_string(id(humidity).state); - verify_ssl: true - method: POST - startup_delay: 0s -http_request: - useragent: ESPHome - follow_redirects: true - redirect_limit: 3 - timeout: 5s - esp8266_disable_ssl_support: false -api: - port: 6053 - password: \033[5m''\033[6m - reboot_timeout: 15min -ota: - safe_mode: true - port: 8266 - reboot_timeout: 5min - num_attempts: 10 -wifi: - ap: - ap_timeout: 1min - domain: .local - reboot_timeout: 15min - power_save_mode: NONE - fast_connect: false - output_power: 20.0 - passive_scan: false - enable_on_boot: true - use_address: ag-basic.local - +substitutions: + name: ag-basic + friendly_name: AG Basic + config_version: 2.0.0 + name_add_mac_suffix: 'false' +esphome: + name: ag-basic + friendly_name: AG Basic + name_add_mac_suffix: false + project: + name: mallocarray.airgradient + version: 2.0.0 + 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().substr(6,11) + "/measures"; + headers: + Content-Type: application/json + json: + wifi: !lambda |- + return to_string(-50); + verify_ssl: true + method: POST + build_path: build/ag-basic + area: '' + platformio_options: {} + includes: [] + libraries: [] +esp8266: + board: d1_mini + restore_from_flash: true + framework: + version: 3.0.2 + source: ~3.30002.0 + platform_version: platformio/espressif8266@3.2.0 + early_pin_init: true + board_flash_mode: dout +logger: + logs: + component: ERROR + baud_rate: 115200 + tx_buffer_size: 512 + deassert_rts_dtr: false + hardware_uart: UART0 + level: DEBUG + esp8266_store_log_strings_in_flash: true +captive_portal: {} +uart: +- rx_pin: + number: 2 + mode: + input: true + output: false + open_drain: false + pullup: false + pulldown: false + analog: false + inverted: false + tx_pin: + number: 0 + mode: + output: true + input: false + open_drain: false + pullup: false + pulldown: false + analog: false + inverted: false + baud_rate: 9600 + id: senseair_s8_uart + rx_buffer_size: 256 + stop_bits: 1 + data_bits: 8 + parity: NONE +- rx_pin: + number: 14 + mode: + input: true + output: false + open_drain: false + pullup: false + pulldown: false + analog: false + inverted: false + tx_pin: + number: 12 + mode: + output: true + input: false + open_drain: false + pullup: false + pulldown: false + analog: false + inverted: false + baud_rate: 9600 + id: pms5003_uart + rx_buffer_size: 256 + stop_bits: 1 + data_bits: 8 + parity: NONE +i2c: +- sda: 4 + scl: 5 + 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 +- platform: template + name: SenseAir S8 Calibration + id: senseair_s8_calibrate_button + on_press: + - then: + - senseair.background_calibration: + id: senseair_s8 + - delay: 70s + - senseair.background_calibration_result: + id: senseair_s8 + disabled_by_default: false +- platform: template + name: SenseAir S8 Show Calibration Interval + id: senseair_s8_show_calibrate_interval + on_press: + - then: + - senseair.abc_get_period: + id: senseair_s8 + disabled_by_default: false +sensor: +- platform: pmsx003 + type: PMSX003 + 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 + 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: 10 + 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: senseair + co2: + name: CO2 + id: co2 + filters: + - skip_initial: 2 + - clamp: + min_value: 400.0 + max_value: .nan + ignore_out_of_range: false + disabled_by_default: false + force_update: false + unit_of_measurement: ppm + icon: mdi:molecule-co2 + accuracy_decimals: 0 + device_class: carbon_dioxide + state_class: measurement + id: senseair_s8 + uart_id: senseair_s8_uart + update_interval: 60s +- platform: sht3xd + temperature: + name: Temperature + id: temp + 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 + disabled_by_default: false + force_update: false + unit_of_measurement: '%' + accuracy_decimals: 1 + device_class: humidity + state_class: measurement + address: 0x44 + heater_enabled: false + update_interval: 60s +- 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 +switch: +- platform: template + name: SenseAir S8 Automatic Background Calibration + id: senseair_s8_abc_switch + restore_mode: RESTORE_DEFAULT_ON + optimistic: true + entity_category: config + turn_on_action: + then: + - senseair.abc_enable: + id: senseair_s8 + turn_off_action: + then: + - senseair.abc_disable: + id: senseair_s8 + disabled_by_default: false + assumed_state: false +- platform: template + name: Display Temperature in °F + icon: mdi:thermometer + id: display_in_f + restore_mode: RESTORE_DEFAULT_ON + optimistic: true + disabled_by_default: false + assumed_state: false +- platform: template + name: Upload to AirGradient Dashboard + id: upload_airgradient + restore_mode: RESTORE_DEFAULT_OFF + 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 +font: +- file: + path: ./fonts/liberation_sans/LiberationSans-Regular.ttf + type: local + id: font1 + size: 24 + glyphs: + - ' ' + - '!' + - '"' + - '%' + - ( + - ) + - + + - ',' + - '-' + - . + - / + - '0' + - '1' + - '2' + - '3' + - '4' + - '5' + - '6' + - '7' + - '8' + - '9' + - ':' + - '=' + - A + - B + - C + - D + - E + - F + - G + - H + - I + - J + - K + - L + - M + - N + - O + - P + - Q + - R + - S + - T + - U + - V + - W + - X + - Y + - Z + - _ + - a + - b + - c + - d + - e + - f + - g + - h + - i + - j + - k + - l + - m + - n + - o + - p + - q + - r + - s + - t + - u + - v + - w + - x + - y + - z + - ° +display: +- platform: ssd1306_i2c + model: SSD1306_64X48 + id: oled_display + reset_pin: + number: 16 + mode: + output: true + input: false + open_drain: false + pullup: false + pulldown: false + analog: false + inverted: false + address: 0x3C + pages: + - id: display_pm2 + lambda: !lambda |- + it.print(0, 0, id(font1), "PM2"); + it.printf(64, 24, id(font1), TextAlign::TOP_RIGHT, "%.0f",id(pm_2_5).state); + - id: display_co2 + lambda: !lambda |- + it.print(0, 0, id(font1), "CO2"); + it.printf(64, 24, id(font1), TextAlign::TOP_RIGHT, "%.0f",id(co2).state); + - id: display_tempf + lambda: !lambda |- + it.print(0, 0, id(font1), "°F"); + it.printf(64, 24, id(font1), TextAlign::TOP_RIGHT, "%.1f",id(temp).state*9/5+32); + - id: display_humidity + lambda: !lambda |- + it.print(0, 0, id(font1), "HUM"); + it.printf(64, 24, id(font1), TextAlign::TOP_RIGHT, "%.0f%%",id(humidity).state); + - id: boot + lambda: !lambda |- + it.printf(0, 0, id(font1), TextAlign::TOP_RIGHT, "%s", get_mac_address().substr(6,11).c_str()); + on_page_change: + - to: boot + then: + - if: + condition: + lambda: !lambda |- + return id(device_uptime).state > 30; + then: + - display.page.show_next: + id: oled_display + - component.update: + id: oled_display + auto_clear_enabled: true + brightness: 1.0 + contrast: 1.0 + flip_x: true + flip_y: true + offset_x: 0 + offset_y: 0 + invert: false + update_interval: 1s +interval: +- interval: 5s + then: + - if: + condition: + lambda: !lambda |- + return id(device_uptime).state < 10; + then: + - display.page.show: + id: boot + else: + - display.page.show_next: + id: oled_display + - component.update: + id: oled_display + startup_delay: 0s +- 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().substr(6,11) + "/measures"; + headers: + Content-Type: application/json + json: + wifi: !lambda |- + return to_string(id(wifi_dbm).state); + pm01: !lambda |- + return to_string(id(pm_1_0).state); + pm02: !lambda |- + return to_string(id(pm_2_5).state); + pm10: !lambda |- + return to_string(id(pm_10_0).state); + pm003_count: !lambda |- + return to_string(id(pm_0_3um).state); + rco2: !lambda |- + return to_string(id(co2).state); + atmp: !lambda |- + return to_string(id(temp).state); + rhum: !lambda |- + return to_string(id(humidity).state); + verify_ssl: true + method: POST + startup_delay: 0s +http_request: + useragent: ESPHome + follow_redirects: true + redirect_limit: 3 + timeout: 5s + esp8266_disable_ssl_support: false +api: + port: 6053 + password: \033[5m''\033[6m + reboot_timeout: 15min +ota: + safe_mode: true + port: 8266 + reboot_timeout: 5min + num_attempts: 10 +wifi: + ap: + ap_timeout: 1min + domain: .local + reboot_timeout: 15min + power_save_mode: NONE + fast_connect: false + output_power: 20.0 + passive_scan: false + enable_on_boot: true + use_address: ag-basic.local +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 8c0e814..2f9cc52 100644 --- a/full_config/ag-one.yaml +++ b/full_config/ag-one.yaml @@ -1,1442 +1,1472 @@ -substitutions: - devicename: ag-one - friendly_devicename: AG One - ag_esphome_config_version: 1.0.0 - add_mac_suffix: 'true' -esphome: - name: ag-one - friendly_name: AG One - name_add_mac_suffix: true - project: - name: mallocarray.airgradient - version: 1.0.0 - min_version: 2023.7.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-one - 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: UART0 - 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 -sensor: -- platform: pmsx003 - type: PMSX003 - uart_id: pms5003_uart - pm_2_5: - name: PM 2.5 - id: pm_2_5 - 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 - 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 - 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 - 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: 10 - 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: senseair - co2: - name: CO2 - id: co2 - filters: - - skip_initial: 2 - - clamp: - min_value: 400.0 - max_value: .nan - ignore_out_of_range: false - disabled_by_default: false - force_update: false - unit_of_measurement: ppm - icon: mdi:molecule-co2 - accuracy_decimals: 0 - device_class: carbon_dioxide - state_class: measurement - id: senseair_s8 - uart_id: senseair_s8_uart - update_interval: 60s -- platform: sht4x - temperature: - name: Temperature - id: temp - disabled_by_default: false - force_update: false - unit_of_measurement: °C - icon: mdi:thermometer - accuracy_decimals: 2 - device_class: temperature - state_class: measurement - humidity: - name: Humidity - id: humidity - disabled_by_default: false - force_update: false - unit_of_measurement: '%' - icon: mdi:water-percent - accuracy_decimals: 2 - device_class: humidity - state_class: measurement - address: 0x44 - precision: High - heater_power: High - heater_time: Long - heater_max_duty: 0.0 - update_interval: 60s -- 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 -button: -- platform: template - name: SenseAir S8 Calibration - id: senseair_s8_calibrate_button - on_press: - - then: - - senseair.background_calibration: - id: senseair_s8 - - delay: 70s - - senseair.background_calibration_result: - id: senseair_s8 - disabled_by_default: false -- platform: template - name: SenseAir S8 Show Calibration Interval - id: senseair_s8_show_calibrate_interval - on_press: - - then: - - senseair.abc_get_period: - id: senseair_s8 - disabled_by_default: false -switch: -- platform: template - name: SenseAir S8 Automatic Background Calibration - id: senseair_s8_abc_switch - restore_mode: RESTORE_DEFAULT_ON - optimistic: true - entity_category: config - turn_on_action: - then: - - senseair.abc_enable: - id: senseair_s8 - turn_off_action: - then: - - senseair.abc_disable: - id: senseair_s8 - disabled_by_default: false - assumed_state: false -- platform: template - name: Display Temperature in °F - id: display_in_f - restore_mode: RESTORE_DEFAULT_ON - optimistic: true - entity_category: config - icon: mdi:thermometer - disabled_by_default: false - assumed_state: false -- platform: template - name: Display AirGradient Default Page - id: display_ag_default_page - restore_mode: RESTORE_DEFAULT_ON - optimistic: true - entity_category: config - icon: mdi:monitor - disabled_by_default: false - assumed_state: false -- platform: template - name: Display Summary Pages - id: display_summary_pages - restore_mode: RESTORE_DEFAULT_OFF - optimistic: true - entity_category: config - icon: mdi:monitor - disabled_by_default: false - assumed_state: false -- platform: template - name: Display Air Quality Page - id: display_air_quality_page - restore_mode: RESTORE_DEFAULT_OFF - optimistic: true - entity_category: config - icon: mdi:monitor - disabled_by_default: false - assumed_state: false -- platform: template - name: Display Temp/Hum Page - id: display_air_temp_page - restore_mode: RESTORE_DEFAULT_OFF - optimistic: true - entity_category: config - icon: mdi:monitor - disabled_by_default: false - assumed_state: false -- platform: template - name: Display VOC Page - id: display_voc_page - restore_mode: RESTORE_DEFAULT_OFF - optimistic: true - entity_category: config - icon: mdi:monitor - disabled_by_default: false - assumed_state: false -- platform: template - name: Display Combo Page - id: display_combo_page - restore_mode: RESTORE_DEFAULT_OFF - optimistic: true - entity_category: config - icon: mdi:monitor - disabled_by_default: false - assumed_state: false -- platform: template - name: Display Boot Page - id: display_boot_page - restore_mode: ALWAYS_ON - optimistic: true - entity_category: config - icon: mdi:monitor - disabled_by_default: false - assumed_state: false -- platform: template - name: Display Blank Page - id: display_blank_page - restore_mode: RESTORE_DEFAULT_OFF - optimistic: true - entity_category: config - icon: mdi:monitor - disabled_by_default: false - assumed_state: false -- 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 -font: -- file: - family: Open Sans - weight: 400 - italic: false - type: gfonts - id: open_sans_14 - size: 14 - glyphs: - - ' ' - - '!' - - '"' - - '%' - - ( - - ) - - + - - ',' - - '-' - - . - - / - - '0' - - '1' - - '2' - - '3' - - '4' - - '5' - - '6' - - '7' - - '8' - - '9' - - ':' - - '=' - - A - - B - - C - - D - - E - - F - - G - - H - - I - - J - - K - - L - - M - - N - - O - - P - - Q - - R - - S - - T - - U - - V - - W - - X - - Y - - Z - - _ - - a - - b - - c - - d - - e - - f - - g - - h - - i - - j - - k - - l - - m - - n - - o - - p - - q - - r - - s - - t - - u - - v - - w - - x - - y - - z - - ° - - ³ - - µ -- file: - family: Open Sans - weight: 400 - italic: false - type: gfonts - id: open_sans_9 - size: 9 - glyphs: - - ' ' - - '!' - - '"' - - '%' - - ( - - ) - - + - - ',' - - '-' - - . - - / - - '0' - - '1' - - '2' - - '3' - - '4' - - '5' - - '6' - - '7' - - '8' - - '9' - - ':' - - '=' - - A - - B - - C - - D - - E - - F - - G - - H - - I - - J - - K - - L - - M - - N - - O - - P - - Q - - R - - S - - T - - U - - V - - W - - X - - Y - - Z - - _ - - a - - b - - c - - d - - e - - f - - g - - h - - i - - j - - k - - l - - m - - n - - o - - p - - q - - r - - s - - t - - u - - v - - w - - x - - y - - z - - ° - - ³ - - µ -- file: - family: Open Sans - weight: 400 - italic: false - type: gfonts - id: open_sans_20 - size: 20 - glyphs: - - ' ' - - '!' - - '"' - - '%' - - ( - - ) - - + - - ',' - - '-' - - . - - / - - '0' - - '1' - - '2' - - '3' - - '4' - - '5' - - '6' - - '7' - - '8' - - '9' - - ':' - - '=' - - A - - B - - C - - D - - E - - F - - G - - H - - I - - J - - K - - L - - M - - N - - O - - P - - Q - - R - - S - - T - - U - - V - - W - - X - - Y - - Z - - _ - - a - - b - - c - - d - - e - - f - - g - - h - - i - - j - - k - - l - - m - - n - - o - - p - - q - - r - - s - - t - - u - - v - - w - - x - - y - - z - - ° - - ³ - - µ -- file: - family: Poppins - weight: 300 - italic: false - type: gfonts - id: poppins_light - size: 14 - glyphs: - - ' ' - - '!' - - '"' - - '%' - - ( - - ) - - + - - ',' - - '-' - - . - - / - - '0' - - '1' - - '2' - - '3' - - '4' - - '5' - - '6' - - '7' - - '8' - - '9' - - ':' - - '=' - - A - - B - - C - - D - - E - - F - - G - - H - - I - - J - - K - - L - - M - - N - - O - - P - - Q - - R - - S - - T - - U - - V - - W - - X - - Y - - Z - - _ - - a - - b - - c - - d - - e - - f - - g - - h - - i - - j - - k - - l - - m - - n - - o - - p - - q - - r - - s - - t - - u - - v - - w - - x - - y - - z - - ° - - ³ - - µ -- file: - family: Poppins - weight: 300 - italic: false - type: gfonts - id: poppins_light_12 - size: 12 - glyphs: - - ' ' - - '!' - - '"' - - '%' - - ( - - ) - - + - - ',' - - '-' - - . - - / - - '0' - - '1' - - '2' - - '3' - - '4' - - '5' - - '6' - - '7' - - '8' - - '9' - - ':' - - '=' - - A - - B - - C - - D - - E - - F - - G - - H - - I - - J - - K - - L - - M - - N - - O - - P - - Q - - R - - S - - T - - U - - V - - W - - X - - Y - - Z - - _ - - a - - b - - c - - d - - e - - f - - g - - h - - i - - j - - k - - l - - m - - n - - o - - p - - q - - r - - s - - t - - u - - v - - w - - x - - y - - z - - ° - - ³ - - µ -- file: - family: Ubuntu Mono - weight: 400 - italic: false - type: gfonts - id: ubuntu - size: 22 - glyphs: - - ' ' - - '!' - - '"' - - '%' - - ( - - ) - - + - - ',' - - '-' - - . - - / - - '0' - - '1' - - '2' - - '3' - - '4' - - '5' - - '6' - - '7' - - '8' - - '9' - - ':' - - '=' - - A - - B - - C - - D - - E - - F - - G - - H - - I - - J - - K - - L - - M - - N - - O - - P - - Q - - R - - S - - T - - U - - V - - W - - X - - Y - - Z - - _ - - a - - b - - c - - d - - e - - f - - g - - h - - i - - j - - k - - l - - m - - n - - o - - p - - q - - r - - s - - t - - u - - v - - w - - x - - y - - z - - ° - - ³ - - µ -display: -- platform: ssd1306_i2c - model: SH1106_128X64 - id: oled_display - address: 0x3C - pages: - - id: airgradient_default - lambda: !lambda |- - if (id(display_in_f).state) { - it.printf(0, 0, id(open_sans_14), "%.1f°F", id(temp).state*9/5+32); - } else { - it.printf(0, 0, id(open_sans_14), "%.1f°C", id(temp).state); - } - it.printf(128, 0, id(open_sans_14), TextAlign::TOP_RIGHT, "%.1f%%", id(humidity).state); - it.line(0,17,128,17); - it.printf(0,19, id(open_sans_9), "CO2"); - it.printf(0,27, id(open_sans_20), "%.0f", id(co2).state); - it.printf(0,52, id(open_sans_9), "ppm"); - it.line(50,19,50,64); - it.printf(54, 19, id(open_sans_9), "PM2.5"); - it.printf(54, 27, id(open_sans_20), "%.0f", id(pm_2_5).state); - it.printf(54, 52, id(open_sans_9), "µg/m³"); - it.line(100,19,100,64); - it.printf(104,18, id(open_sans_9), "TVOC"); - it.printf(104,29, id(open_sans_9), "%.0f", id(voc).state); - it.printf(104,41, id(open_sans_9), "NOx"); - it.printf(104,52, id(open_sans_9), "%.0f", id(nox).state); - - id: boot - lambda: !lambda |- - it.printf(0, 0, id(open_sans_14), "ID:"); - it.printf(128, 0, id(open_sans_14), TextAlign::TOP_RIGHT, "%s", get_mac_address().c_str()); - it.printf(0, 21, id(open_sans_14), "Config Ver: 1.0.0"); - it.printf(0, 42, id(open_sans_14), "AG One"); - - id: summary1 - lambda: !lambda |- - it.printf(0, 0, id(poppins_light), "CO2:"); - it.printf(128, 0, id(poppins_light), TextAlign::TOP_RIGHT, "%.0f ppm", id(co2).state); - it.printf(0, 16, id(poppins_light), "PM2.5:"); - it.printf(128, 16, id(poppins_light), TextAlign::TOP_RIGHT, "%.0f µg/m³", id(pm_2_5).state); - it.printf(0, 32, id(poppins_light), "Temp:"); - if (id(display_in_f).state) { - it.printf(128, 32, id(poppins_light), TextAlign::TOP_RIGHT, "%.1f°F", id(temp).state*9/5+32); - } else { - it.printf(128, 32, id(poppins_light), TextAlign::TOP_RIGHT, "%.1f°C", id(temp).state); - } - it.printf(0, 48, id(poppins_light), "Humidity:"); - it.printf(128, 48, id(poppins_light), TextAlign::TOP_RIGHT, "%.1f%%", id(humidity).state); - - id: summary2 - lambda: !lambda |- - it.printf(0, 0, id(poppins_light), "CO2:"); - it.printf(128, 0, id(poppins_light), TextAlign::TOP_RIGHT, "%.0f ppm", id(co2).state); - it.printf(0, 16, id(poppins_light), "PM2.5:"); - it.printf(128, 16, id(poppins_light), TextAlign::TOP_RIGHT, "%.0f µg/m³", id(pm_2_5).state); - it.printf(0, 32, id(poppins_light), "VOC:"); - it.printf(128, 32, id(poppins_light), TextAlign::TOP_RIGHT, "%.0f", id(voc).state); - it.printf(0, 48, id(poppins_light), "NOx:"); - it.printf(128, 48, id(poppins_light), TextAlign::TOP_RIGHT, "%.0f", id(nox).state); - - id: air_quality - lambda: !lambda |- - it.printf(0, 6, id(ubuntu), "CO2"); - it.printf(128, 6, id(ubuntu), TextAlign::TOP_RIGHT, "%.0f ppm", id(co2).state); - it.printf(0, 34, id(ubuntu), "PM2"); - it.printf(128, 34, id(ubuntu), TextAlign::TOP_RIGHT, "%.0f µg/m³", id(pm_2_5).state); - - id: air_temp - lambda: !lambda |- - it.printf(0, 6, id(ubuntu), "Temp"); - if (id(display_in_f).state) { - it.printf(128, 6, id(ubuntu), TextAlign::TOP_RIGHT, "%.1f°F", id(temp).state*9/5+32); - } else { - it.printf(128, 6, id(ubuntu), TextAlign::TOP_RIGHT, "%.1f°C", id(temp).state); - } - it.printf(0, 34, id(ubuntu), "Humid"); - it.printf(128, 34, id(ubuntu), TextAlign::TOP_RIGHT, "%.1f%%", id(humidity).state); - - id: air_voc - lambda: !lambda |- - it.printf(0, 6, id(ubuntu), "VOC:"); - it.printf(128, 6, id(ubuntu), TextAlign::TOP_RIGHT, "%.0f", id(voc).state); - it.printf(0, 34, id(ubuntu), "NOx:"); - it.printf(128, 34, id(ubuntu), TextAlign::TOP_RIGHT, "%.0f", id(nox).state); - - id: combo - lambda: !lambda |- - if (id(display_in_f).state) { - it.printf(0, 0, id(poppins_light_12), "%.1f °F", id(temp).state*9/5+32); - } else { - it.printf(0, 0, id(poppins_light_12), "%.1f °C", id(temp).state); - } - it.printf(128, 0, id(poppins_light_12), TextAlign::TOP_RIGHT, "%.1f%%", id(humidity).state); - it.printf(0, 16, id(poppins_light_12), "%.0f µg", id(pm_2_5).state); - it.printf(128, 16, id(poppins_light_12), TextAlign::TOP_RIGHT, "%.0f ppm", id(co2).state); - it.printf(0, 32, id(poppins_light_12), "VOC: %.0f", id(voc).state); - it.printf(128, 32, id(poppins_light_12), TextAlign::TOP_RIGHT, "NOx: %.0f", id(nox).state); - it.printf(0, 48, id(poppins_light_12), "AQI: %.0f", id(pm_2_5_aqi).state); - - id: blank - lambda: !lambda |- - it.printf(0, 0, id(open_sans_14), ""); - on_page_change: - - to: airgradient_default - then: - - if: - condition: - switch.is_off: - id: display_ag_default_page - then: - - display.page.show_next: - id: oled_display - - component.update: - id: oled_display - - to: summary1 - then: - - if: - condition: - switch.is_off: - id: display_summary_pages - then: - - display.page.show_next: - id: oled_display - - component.update: - id: oled_display - - to: summary2 - then: - - if: - condition: - switch.is_off: - id: display_summary_pages - then: - - display.page.show_next: - id: oled_display - - component.update: - id: oled_display - - to: air_quality - then: - - if: - condition: - switch.is_off: - id: display_air_quality_page - then: - - display.page.show_next: - id: oled_display - - component.update: - id: oled_display - - to: air_temp - then: - - if: - condition: - switch.is_off: - id: display_air_temp_page - then: - - display.page.show_next: - id: oled_display - - component.update: - id: oled_display - - to: air_voc - then: - - if: - condition: - switch.is_off: - id: display_voc_page - then: - - display.page.show_next: - id: oled_display - - component.update: - id: oled_display - - to: combo - then: - - if: - condition: - switch.is_off: - id: display_combo_page - then: - - display.page.show_next: - id: oled_display - - component.update: - id: oled_display - - to: boot - then: - - if: - condition: - switch.is_off: - id: display_boot_page - then: - - display.page.show_next: - id: oled_display - - component.update: - id: oled_display - - to: blank - then: - - if: - condition: - switch.is_off: - id: display_blank_page - then: - - display.page.show_next: - id: oled_display - - component.update: - id: oled_display - auto_clear_enabled: true - brightness: 1.0 - contrast: 1.0 - flip_x: true - flip_y: true - offset_x: 0 - offset_y: 0 - invert: false - update_interval: 1s -interval: -- interval: 10s - startup_delay: 1s - then: - - if: - condition: - switch.is_on: - id: display_boot_page - then: - - switch.turn_off: - id: display_boot_page -- interval: 5s - then: - - if: - condition: - switch.is_on: - id: display_boot_page - then: - - display.page.show: - id: boot - else: - - display.page.show_next: - id: oled_display - - component.update: - id: oled_display - startup_delay: 0s -- interval: 1min - then: - - if: - condition: - light.is_on: - id: led_strip - then: - - if: - condition: - lambda: !lambda |- - return id(co2).state < 800; - then: - - light.turn_on: - id: led_strip - brightness: !lambda |- - return id(led_brightness).state / 100.0; - red: 0.0 - green: 1.0 - blue: 0.0 - state: true - - if: - condition: - lambda: !lambda |- - return id(co2).state >= 800 && id(co2).state < 1000; - then: - - light.turn_on: - id: led_strip - brightness: !lambda |- - return id(led_brightness).state / 100.0; - red: 1.0 - green: 1.0 - blue: 0.0 - state: true - - if: - condition: - lambda: !lambda |- - return id(co2).state >= 1000 && id(co2).state < 1500; - then: - - light.turn_on: - id: led_strip - brightness: !lambda |- - return id(led_brightness).state / 100.0; - red: 1.0 - green: 0.7 - blue: 0.0 - state: true - - if: - condition: - lambda: !lambda |- - return id(co2).state >= 1500 && id(co2).state < 2000; - then: - - light.turn_on: - id: led_strip - brightness: !lambda |- - return id(led_brightness).state / 100.0; - red: 1.0 - green: 0.0 - blue: 0.0 - state: true - - if: - condition: - lambda: !lambda |- - return id(co2).state >= 2000 && id(co2).state < 3000; - then: - - light.turn_on: - id: led_strip - brightness: !lambda |- - return id(led_brightness).state / 100.0; - red: 0.6 - green: 0.0 - blue: 0.6 - state: true - - if: - condition: - lambda: !lambda |- - return id(co2).state >= 3000 && id(co2).state < 10000; - then: - - light.turn_on: - id: led_strip - brightness: !lambda |- - return id(led_brightness).state / 100.0; - red: 0.4 - green: 0.0 - blue: 0.0 - state: true - startup_delay: 0s -- 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 - json: - wifi: !lambda |- - return to_string(id(wifi_dbm).state); - pm01: !lambda |- - return to_string(id(pm_1_0).state); - pm02: !lambda |- - return to_string(id(pm_2_5).state); - pm10: !lambda |- - return to_string(id(pm_10_0).state); - pm003_count: !lambda |- - return to_string(id(pm_0_3um).state); - rco2: !lambda |- - return to_string(id(co2).state); - atmp: !lambda |- - return to_string(id(temp).state); - rhum: !lambda |- - return to_string(id(humidity).state); - tvoc_index: !lambda |- - return to_string(id(voc).state); - nox_index: !lambda |- - return to_string(id(nox).state); - 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 -light: -- platform: esp32_rmt_led_strip - rgb_order: GRB - pin: 10 - num_leds: 11 - rmt_channel: 0 - chipset: WS2812 - name: LED Strip - id: led_strip - restore_mode: RESTORE_DEFAULT_OFF - disabled_by_default: false - gamma_correct: 2.8 - default_transition_length: 1s - flash_transition_length: 0s - is_rgbw: false -number: -- platform: template - name: LED Brightness % - icon: mdi:lightbulb - id: led_brightness - min_value: 0.0 - max_value: 100.0 - step: 1.0 - initial_value: 100.0 - optimistic: true - restore_value: true - mode: SLIDER - disabled_by_default: false - update_interval: 60s -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 -binary_sensor: -- platform: gpio - pin: - number: 9 - mode: - input: true - pullup: true - output: false - open_drain: false - pulldown: false - inverted: true - ignore_strapping_warning: true - drive_strength: 20.0 - internal: true - name: Configuration Button - id: config_button - on_multi_click: - - timing: - - state: true - min_length: 0ms - max_length: 1s - - state: false - min_length: 500ms - then: - - logger.log: - format: Toggling display betwen C and F - tag: main - level: DEBUG - args: [] - - switch.toggle: - id: display_in_f - invalid_cooldown: 1s - - timing: - - state: true - min_length: 1s - max_length: 5s - - state: false - min_length: 500ms - then: - - logger.log: - format: Starting manual CO2 calibration - tag: main - level: DEBUG - args: [] - - senseair.background_calibration: - id: senseair_s8 - - delay: 70s - - senseair.background_calibration_result: - id: senseair_s8 - invalid_cooldown: 1s - disabled_by_default: false -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-one.local - +substitutions: + name: ag-one + friendly_name: AG One + config_version: 2.0.0 + name_add_mac_suffix: 'false' +esphome: + name: ag-one + friendly_name: AG One + name_add_mac_suffix: false + project: + name: mallocarray.airgradient + version: 2.0.0 + 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-one + 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: UART0 + 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 +- platform: template + name: SenseAir S8 Calibration + id: senseair_s8_calibrate_button + on_press: + - then: + - senseair.background_calibration: + id: senseair_s8 + - delay: 70s + - senseair.background_calibration_result: + id: senseair_s8 + disabled_by_default: false +- platform: template + name: SenseAir S8 Show Calibration Interval + id: senseair_s8_show_calibrate_interval + on_press: + - then: + - senseair.abc_get_period: + id: senseair_s8 + disabled_by_default: false +sensor: +- platform: pmsx003 + type: PMSX003 + 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 + 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: 10 + 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: senseair + co2: + name: CO2 + id: co2 + filters: + - skip_initial: 2 + - clamp: + min_value: 400.0 + max_value: .nan + ignore_out_of_range: false + disabled_by_default: false + force_update: false + unit_of_measurement: ppm + icon: mdi:molecule-co2 + accuracy_decimals: 0 + device_class: carbon_dioxide + state_class: measurement + id: senseair_s8 + uart_id: senseair_s8_uart + update_interval: 60s +- platform: sht4x + temperature: + name: Temperature + id: temp + disabled_by_default: false + force_update: false + unit_of_measurement: °C + icon: mdi:thermometer + accuracy_decimals: 2 + device_class: temperature + state_class: measurement + humidity: + name: Humidity + id: humidity + disabled_by_default: false + force_update: false + unit_of_measurement: '%' + icon: mdi:water-percent + accuracy_decimals: 2 + device_class: humidity + state_class: measurement + address: 0x44 + precision: High + heater_power: High + heater_time: Long + heater_max_duty: 0.0 + update_interval: 60s +- 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 +switch: +- platform: template + name: SenseAir S8 Automatic Background Calibration + id: senseair_s8_abc_switch + restore_mode: RESTORE_DEFAULT_ON + optimistic: true + entity_category: config + turn_on_action: + then: + - senseair.abc_enable: + id: senseair_s8 + turn_off_action: + then: + - senseair.abc_disable: + id: senseair_s8 + disabled_by_default: false + assumed_state: false +- platform: template + name: Display Temperature in °F + id: display_in_f + restore_mode: RESTORE_DEFAULT_ON + optimistic: true + entity_category: config + icon: mdi:thermometer + disabled_by_default: false + assumed_state: false +- platform: template + name: Display AirGradient Default Page + id: display_ag_default_page + restore_mode: RESTORE_DEFAULT_ON + optimistic: true + entity_category: config + icon: mdi:monitor + disabled_by_default: false + assumed_state: false +- platform: template + name: Display Summary Pages + id: display_summary_pages + restore_mode: RESTORE_DEFAULT_OFF + optimistic: true + entity_category: config + icon: mdi:monitor + disabled_by_default: false + assumed_state: false +- platform: template + name: Display Air Quality Page + id: display_air_quality_page + restore_mode: RESTORE_DEFAULT_OFF + optimistic: true + entity_category: config + icon: mdi:monitor + disabled_by_default: false + assumed_state: false +- platform: template + name: Display Temp/Hum Page + id: display_air_temp_page + restore_mode: RESTORE_DEFAULT_OFF + optimistic: true + entity_category: config + icon: mdi:monitor + disabled_by_default: false + assumed_state: false +- platform: template + name: Display VOC Page + id: display_voc_page + restore_mode: RESTORE_DEFAULT_OFF + optimistic: true + entity_category: config + icon: mdi:monitor + disabled_by_default: false + assumed_state: false +- platform: template + name: Display Combo Page + id: display_combo_page + restore_mode: RESTORE_DEFAULT_OFF + optimistic: true + entity_category: config + icon: mdi:monitor + disabled_by_default: false + assumed_state: false +- platform: template + name: Display Boot Page + id: display_boot_page + restore_mode: ALWAYS_ON + optimistic: true + entity_category: config + icon: mdi:monitor + disabled_by_default: false + assumed_state: false +- platform: template + name: Display Blank Page + id: display_blank_page + restore_mode: RESTORE_DEFAULT_OFF + optimistic: true + entity_category: config + icon: mdi:monitor + disabled_by_default: false + assumed_state: false +- platform: template + name: Upload to AirGradient Dashboard + id: upload_airgradient + restore_mode: RESTORE_DEFAULT_OFF + 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 +font: +- file: + family: Open Sans + weight: 400 + italic: false + type: gfonts + id: open_sans_14 + size: 14 + glyphs: + - ' ' + - '!' + - '"' + - '%' + - ( + - ) + - + + - ',' + - '-' + - . + - / + - '0' + - '1' + - '2' + - '3' + - '4' + - '5' + - '6' + - '7' + - '8' + - '9' + - ':' + - '=' + - A + - B + - C + - D + - E + - F + - G + - H + - I + - J + - K + - L + - M + - N + - O + - P + - Q + - R + - S + - T + - U + - V + - W + - X + - Y + - Z + - _ + - a + - b + - c + - d + - e + - f + - g + - h + - i + - j + - k + - l + - m + - n + - o + - p + - q + - r + - s + - t + - u + - v + - w + - x + - y + - z + - ° + - ³ + - µ +- file: + family: Open Sans + weight: 400 + italic: false + type: gfonts + id: open_sans_9 + size: 9 + glyphs: + - ' ' + - '!' + - '"' + - '%' + - ( + - ) + - + + - ',' + - '-' + - . + - / + - '0' + - '1' + - '2' + - '3' + - '4' + - '5' + - '6' + - '7' + - '8' + - '9' + - ':' + - '=' + - A + - B + - C + - D + - E + - F + - G + - H + - I + - J + - K + - L + - M + - N + - O + - P + - Q + - R + - S + - T + - U + - V + - W + - X + - Y + - Z + - _ + - a + - b + - c + - d + - e + - f + - g + - h + - i + - j + - k + - l + - m + - n + - o + - p + - q + - r + - s + - t + - u + - v + - w + - x + - y + - z + - ° + - ³ + - µ +- file: + family: Open Sans + weight: 400 + italic: false + type: gfonts + id: open_sans_20 + size: 20 + glyphs: + - ' ' + - '!' + - '"' + - '%' + - ( + - ) + - + + - ',' + - '-' + - . + - / + - '0' + - '1' + - '2' + - '3' + - '4' + - '5' + - '6' + - '7' + - '8' + - '9' + - ':' + - '=' + - A + - B + - C + - D + - E + - F + - G + - H + - I + - J + - K + - L + - M + - N + - O + - P + - Q + - R + - S + - T + - U + - V + - W + - X + - Y + - Z + - _ + - a + - b + - c + - d + - e + - f + - g + - h + - i + - j + - k + - l + - m + - n + - o + - p + - q + - r + - s + - t + - u + - v + - w + - x + - y + - z + - ° + - ³ + - µ +- file: + family: Poppins + weight: 300 + italic: false + type: gfonts + id: poppins_light + size: 14 + glyphs: + - ' ' + - '!' + - '"' + - '%' + - ( + - ) + - + + - ',' + - '-' + - . + - / + - '0' + - '1' + - '2' + - '3' + - '4' + - '5' + - '6' + - '7' + - '8' + - '9' + - ':' + - '=' + - A + - B + - C + - D + - E + - F + - G + - H + - I + - J + - K + - L + - M + - N + - O + - P + - Q + - R + - S + - T + - U + - V + - W + - X + - Y + - Z + - _ + - a + - b + - c + - d + - e + - f + - g + - h + - i + - j + - k + - l + - m + - n + - o + - p + - q + - r + - s + - t + - u + - v + - w + - x + - y + - z + - ° + - ³ + - µ +- file: + family: Poppins + weight: 300 + italic: false + type: gfonts + id: poppins_light_12 + size: 12 + glyphs: + - ' ' + - '!' + - '"' + - '%' + - ( + - ) + - + + - ',' + - '-' + - . + - / + - '0' + - '1' + - '2' + - '3' + - '4' + - '5' + - '6' + - '7' + - '8' + - '9' + - ':' + - '=' + - A + - B + - C + - D + - E + - F + - G + - H + - I + - J + - K + - L + - M + - N + - O + - P + - Q + - R + - S + - T + - U + - V + - W + - X + - Y + - Z + - _ + - a + - b + - c + - d + - e + - f + - g + - h + - i + - j + - k + - l + - m + - n + - o + - p + - q + - r + - s + - t + - u + - v + - w + - x + - y + - z + - ° + - ³ + - µ +- file: + family: Ubuntu Mono + weight: 400 + italic: false + type: gfonts + id: ubuntu + size: 22 + glyphs: + - ' ' + - '!' + - '"' + - '%' + - ( + - ) + - + + - ',' + - '-' + - . + - / + - '0' + - '1' + - '2' + - '3' + - '4' + - '5' + - '6' + - '7' + - '8' + - '9' + - ':' + - '=' + - A + - B + - C + - D + - E + - F + - G + - H + - I + - J + - K + - L + - M + - N + - O + - P + - Q + - R + - S + - T + - U + - V + - W + - X + - Y + - Z + - _ + - a + - b + - c + - d + - e + - f + - g + - h + - i + - j + - k + - l + - m + - n + - o + - p + - q + - r + - s + - t + - u + - v + - w + - x + - y + - z + - ° + - ³ + - µ +display: +- platform: ssd1306_i2c + model: SH1106_128X64 + id: oled_display + address: 0x3C + pages: + - id: airgradient_default + lambda: !lambda |- + if (id(display_in_f).state) { + it.printf(0, 0, id(open_sans_14), "%.1f°F", id(temp).state*9/5+32); + } else { + it.printf(0, 0, id(open_sans_14), "%.1f°C", id(temp).state); + } + it.printf(128, 0, id(open_sans_14), TextAlign::TOP_RIGHT, "%.1f%%", id(humidity).state); + it.line(0,17,128,17); + it.printf(0,19, id(open_sans_9), "CO2"); + it.printf(0,27, id(open_sans_20), "%.0f", id(co2).state); + it.printf(0,52, id(open_sans_9), "ppm"); + it.line(50,19,50,64); + it.printf(54, 19, id(open_sans_9), "PM2.5"); + it.printf(54, 27, id(open_sans_20), "%.0f", id(pm_2_5).state); + it.printf(54, 52, id(open_sans_9), "µg/m³"); + it.line(100,19,100,64); + it.printf(104,18, id(open_sans_9), "TVOC"); + it.printf(104,29, id(open_sans_9), "%.0f", id(voc).state); + it.printf(104,41, id(open_sans_9), "NOx"); + it.printf(104,52, id(open_sans_9), "%.0f", id(nox).state); + - id: boot + lambda: !lambda |- + it.printf(0, 0, id(open_sans_14), "ID:"); + it.printf(128, 0, id(open_sans_14), TextAlign::TOP_RIGHT, "%s", get_mac_address().c_str()); + it.printf(0, 21, id(open_sans_14), "Config Ver: 2.0.0"); + it.printf(0, 42, id(open_sans_14), "AG One"); + - id: summary1 + lambda: !lambda |- + it.printf(0, 0, id(poppins_light), "CO2:"); + it.printf(128, 0, id(poppins_light), TextAlign::TOP_RIGHT, "%.0f ppm", id(co2).state); + it.printf(0, 16, id(poppins_light), "PM2.5:"); + it.printf(128, 16, id(poppins_light), TextAlign::TOP_RIGHT, "%.0f µg/m³", id(pm_2_5).state); + it.printf(0, 32, id(poppins_light), "Temp:"); + if (id(display_in_f).state) { + it.printf(128, 32, id(poppins_light), TextAlign::TOP_RIGHT, "%.1f°F", id(temp).state*9/5+32); + } else { + it.printf(128, 32, id(poppins_light), TextAlign::TOP_RIGHT, "%.1f°C", id(temp).state); + } + it.printf(0, 48, id(poppins_light), "Humidity:"); + it.printf(128, 48, id(poppins_light), TextAlign::TOP_RIGHT, "%.1f%%", id(humidity).state); + - id: summary2 + lambda: !lambda |- + it.printf(0, 0, id(poppins_light), "CO2:"); + it.printf(128, 0, id(poppins_light), TextAlign::TOP_RIGHT, "%.0f ppm", id(co2).state); + it.printf(0, 16, id(poppins_light), "PM2.5:"); + it.printf(128, 16, id(poppins_light), TextAlign::TOP_RIGHT, "%.0f µg/m³", id(pm_2_5).state); + it.printf(0, 32, id(poppins_light), "VOC:"); + it.printf(128, 32, id(poppins_light), TextAlign::TOP_RIGHT, "%.0f", id(voc).state); + it.printf(0, 48, id(poppins_light), "NOx:"); + it.printf(128, 48, id(poppins_light), TextAlign::TOP_RIGHT, "%.0f", id(nox).state); + - id: air_quality + lambda: !lambda |- + it.printf(0, 6, id(ubuntu), "CO2"); + it.printf(128, 6, id(ubuntu), TextAlign::TOP_RIGHT, "%.0f ppm", id(co2).state); + it.printf(0, 34, id(ubuntu), "PM2"); + it.printf(128, 34, id(ubuntu), TextAlign::TOP_RIGHT, "%.0f µg/m³", id(pm_2_5).state); + - id: air_temp + lambda: !lambda |- + it.printf(0, 6, id(ubuntu), "Temp"); + if (id(display_in_f).state) { + it.printf(128, 6, id(ubuntu), TextAlign::TOP_RIGHT, "%.1f°F", id(temp).state*9/5+32); + } else { + it.printf(128, 6, id(ubuntu), TextAlign::TOP_RIGHT, "%.1f°C", id(temp).state); + } + it.printf(0, 34, id(ubuntu), "Humid"); + it.printf(128, 34, id(ubuntu), TextAlign::TOP_RIGHT, "%.1f%%", id(humidity).state); + - id: air_voc + lambda: !lambda |- + it.printf(0, 6, id(ubuntu), "VOC:"); + it.printf(128, 6, id(ubuntu), TextAlign::TOP_RIGHT, "%.0f", id(voc).state); + it.printf(0, 34, id(ubuntu), "NOx:"); + it.printf(128, 34, id(ubuntu), TextAlign::TOP_RIGHT, "%.0f", id(nox).state); + - id: combo + lambda: !lambda |- + if (id(display_in_f).state) { + it.printf(0, 0, id(poppins_light_12), "%.1f °F", id(temp).state*9/5+32); + } else { + it.printf(0, 0, id(poppins_light_12), "%.1f °C", id(temp).state); + } + it.printf(128, 0, id(poppins_light_12), TextAlign::TOP_RIGHT, "%.1f%%", id(humidity).state); + it.printf(0, 16, id(poppins_light_12), "%.0f µg", id(pm_2_5).state); + it.printf(128, 16, id(poppins_light_12), TextAlign::TOP_RIGHT, "%.0f ppm", id(co2).state); + it.printf(0, 32, id(poppins_light_12), "VOC: %.0f", id(voc).state); + it.printf(128, 32, id(poppins_light_12), TextAlign::TOP_RIGHT, "NOx: %.0f", id(nox).state); + it.printf(0, 48, id(poppins_light_12), "AQI: %.0f", id(pm_2_5_aqi).state); + - id: blank + lambda: !lambda |- + it.printf(0, 0, id(open_sans_14), ""); + on_page_change: + - to: airgradient_default + then: + - if: + condition: + switch.is_off: + id: display_ag_default_page + then: + - display.page.show_next: + id: oled_display + - component.update: + id: oled_display + - to: summary1 + then: + - if: + condition: + switch.is_off: + id: display_summary_pages + then: + - display.page.show_next: + id: oled_display + - component.update: + id: oled_display + - to: summary2 + then: + - if: + condition: + switch.is_off: + id: display_summary_pages + then: + - display.page.show_next: + id: oled_display + - component.update: + id: oled_display + - to: air_quality + then: + - if: + condition: + switch.is_off: + id: display_air_quality_page + then: + - display.page.show_next: + id: oled_display + - component.update: + id: oled_display + - to: air_temp + then: + - if: + condition: + switch.is_off: + id: display_air_temp_page + then: + - display.page.show_next: + id: oled_display + - component.update: + id: oled_display + - to: air_voc + then: + - if: + condition: + switch.is_off: + id: display_voc_page + then: + - display.page.show_next: + id: oled_display + - component.update: + id: oled_display + - to: combo + then: + - if: + condition: + switch.is_off: + id: display_combo_page + then: + - display.page.show_next: + id: oled_display + - component.update: + id: oled_display + - to: boot + then: + - if: + condition: + switch.is_off: + id: display_boot_page + then: + - display.page.show_next: + id: oled_display + - component.update: + id: oled_display + - to: blank + then: + - if: + condition: + switch.is_off: + id: display_blank_page + then: + - display.page.show_next: + id: oled_display + - component.update: + id: oled_display + auto_clear_enabled: true + brightness: 1.0 + contrast: 1.0 + flip_x: true + flip_y: true + offset_x: 0 + offset_y: 0 + invert: false + update_interval: 1s +interval: +- interval: 10s + startup_delay: 1s + then: + - if: + condition: + switch.is_on: + id: display_boot_page + then: + - switch.turn_off: + id: display_boot_page +- interval: 5s + then: + - if: + condition: + switch.is_on: + id: display_boot_page + then: + - display.page.show: + id: boot + else: + - display.page.show_next: + id: oled_display + - component.update: + id: oled_display + startup_delay: 0s +- interval: 1min + then: + - if: + condition: + light.is_on: + id: led_strip + then: + - if: + condition: + lambda: !lambda |- + return id(co2).state < 800; + then: + - light.turn_on: + id: led_strip + brightness: !lambda |- + return id(led_brightness).state / 100.0; + red: 0.0 + green: 1.0 + blue: 0.0 + state: true + - if: + condition: + lambda: !lambda |- + return id(co2).state >= 800 && id(co2).state < 1000; + then: + - light.turn_on: + id: led_strip + brightness: !lambda |- + return id(led_brightness).state / 100.0; + red: 1.0 + green: 1.0 + blue: 0.0 + state: true + - if: + condition: + lambda: !lambda |- + return id(co2).state >= 1000 && id(co2).state < 1500; + then: + - light.turn_on: + id: led_strip + brightness: !lambda |- + return id(led_brightness).state / 100.0; + red: 1.0 + green: 0.7 + blue: 0.0 + state: true + - if: + condition: + lambda: !lambda |- + return id(co2).state >= 1500 && id(co2).state < 2000; + then: + - light.turn_on: + id: led_strip + brightness: !lambda |- + return id(led_brightness).state / 100.0; + red: 1.0 + green: 0.0 + blue: 0.0 + state: true + - if: + condition: + lambda: !lambda |- + return id(co2).state >= 2000 && id(co2).state < 3000; + then: + - light.turn_on: + id: led_strip + brightness: !lambda |- + return id(led_brightness).state / 100.0; + red: 0.6 + green: 0.0 + blue: 0.6 + state: true + - if: + condition: + lambda: !lambda |- + return id(co2).state >= 3000 && id(co2).state < 10000; + then: + - light.turn_on: + id: led_strip + brightness: !lambda |- + return id(led_brightness).state / 100.0; + red: 0.4 + green: 0.0 + blue: 0.0 + state: true + startup_delay: 0s +- 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 + json: + wifi: !lambda |- + return to_string(id(wifi_dbm).state); + pm01: !lambda |- + return to_string(id(pm_1_0).state); + pm02: !lambda |- + return to_string(id(pm_2_5).state); + pm10: !lambda |- + return to_string(id(pm_10_0).state); + pm003_count: !lambda |- + return to_string(id(pm_0_3um).state); + rco2: !lambda |- + return to_string(id(co2).state); + atmp: !lambda |- + return to_string(id(temp).state); + rhum: !lambda |- + return to_string(id(humidity).state); + tvoc_index: !lambda |- + return to_string(id(voc).state); + nox_index: !lambda |- + return to_string(id(nox).state); + 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 +number: +- platform: template + name: Display Contrast % + icon: mdi:lightbulb + id: display_contrast + min_value: 0.0 + max_value: 100.0 + step: 1.0 + initial_value: 100.0 + optimistic: true + restore_value: true + mode: SLIDER + on_value: + - then: + - lambda: !lambda |- + id(oled_display).set_contrast(id(display_contrast).state / 100.0); + disabled_by_default: false + update_interval: 60s +- platform: template + name: LED Brightness % + icon: mdi:lightbulb + id: led_brightness + min_value: 0.0 + max_value: 100.0 + step: 1.0 + initial_value: 100.0 + optimistic: true + restore_value: true + mode: SLIDER + disabled_by_default: false + update_interval: 60s +light: +- platform: esp32_rmt_led_strip + rgb_order: GRB + pin: 10 + num_leds: 11 + rmt_channel: 0 + chipset: WS2812 + name: LED Strip + id: led_strip + restore_mode: RESTORE_DEFAULT_OFF + disabled_by_default: false + gamma_correct: 2.8 + default_transition_length: 1s + flash_transition_length: 0s + is_rgbw: false +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 +binary_sensor: +- platform: gpio + pin: + number: 9 + mode: + input: true + pullup: true + output: false + open_drain: false + pulldown: false + inverted: true + ignore_strapping_warning: true + drive_strength: 20.0 + internal: true + name: Configuration Button + id: config_button + on_multi_click: + - timing: + - state: true + min_length: 0ms + max_length: 1s + - state: false + min_length: 500ms + then: + - logger.log: + format: Toggling display betwen C and F + level: DEBUG + tag: main + args: [] + - switch.toggle: + id: display_in_f + invalid_cooldown: 1s + - timing: + - state: true + min_length: 1s + max_length: 5s + - state: false + min_length: 500ms + then: + - logger.log: + format: Starting manual CO2 calibration + level: DEBUG + tag: main + args: [] + - senseair.background_calibration: + id: senseair_s8 + - delay: 70s + - senseair.background_calibration_result: + id: senseair_s8 + invalid_cooldown: 1s + disabled_by_default: false +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-one.local +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-1pst.yaml b/full_config/ag-open-air-o-1pst.yaml index 2c156ff..c655761 100644 --- a/full_config/ag-open-air-o-1pst.yaml +++ b/full_config/ag-open-air-o-1pst.yaml @@ -1,444 +1,457 @@ -substitutions: - devicename: ag-open-air-o-1pst - friendly_devicename: AG Open Air O-1PST - ag_esphome_config_version: 1.0.0 - add_mac_suffix: 'true' -esphome: - name: ag-open-air-o-1pst - friendly_name: AG Open Air O-1PST - name_add_mac_suffix: true - project: - name: mallocarray.airgradient - version: 1.0.0 - min_version: 2023.7.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-1pst - 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: UART0 - 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 -sensor: -- platform: pmsx003 - type: PMS5003T - uart_id: pms5003_uart - pm_2_5: - name: PM 2.5 - id: pm_2_5 - 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 - 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 - 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: - - 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: - - 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: 10 - 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: senseair - co2: - name: CO2 - id: co2 - filters: - - skip_initial: 2 - - clamp: - min_value: 400.0 - max_value: .nan - ignore_out_of_range: false - disabled_by_default: false - force_update: false - unit_of_measurement: ppm - icon: mdi:molecule-co2 - accuracy_decimals: 0 - device_class: carbon_dioxide - state_class: measurement - id: senseair_s8 - uart_id: senseair_s8_uart - update_interval: 60s -- 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 -button: -- platform: template - name: SenseAir S8 Calibration - id: senseair_s8_calibrate_button - on_press: - - then: - - senseair.background_calibration: - id: senseair_s8 - - delay: 70s - - senseair.background_calibration_result: - id: senseair_s8 - disabled_by_default: false -- platform: template - name: SenseAir S8 Show Calibration Interval - id: senseair_s8_show_calibrate_interval - on_press: - - then: - - senseair.abc_get_period: - id: senseair_s8 - disabled_by_default: false -switch: -- platform: template - name: SenseAir S8 Automatic Background Calibration - id: senseair_s8_abc_switch - restore_mode: RESTORE_DEFAULT_ON - optimistic: true - entity_category: config - turn_on_action: - then: - - senseair.abc_enable: - id: senseair_s8 - turn_off_action: - then: - - senseair.abc_disable: - id: senseair_s8 - disabled_by_default: false - assumed_state: false -- 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 -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 - json: - wifi: !lambda |- - return to_string(id(wifi_dbm).state); - pm01: !lambda |- - return to_string(id(pm_1_0).state); - pm02: !lambda |- - return to_string(id(pm_2_5).state); - pm10: !lambda |- - return to_string(id(pm_10_0).state); - pm003_count: !lambda |- - return to_string(id(pm_0_3um).state); - rco2: !lambda |- - return to_string(id(co2).state); - atmp: !lambda |- - return to_string(id(temp).state); - rhum: !lambda |- - return to_string(id(humidity).state); - tvoc_index: !lambda |- - return to_string(id(voc).state); - nox_index: !lambda |- - return to_string(id(nox).state); - 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 -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-1pst.local - +substitutions: + name: ag-open-air-o-1pst + friendly_name: AG Open Air O-1PST + config_version: 2.0.0 + name_add_mac_suffix: 'false' +esphome: + name: ag-open-air-o-1pst + friendly_name: AG Open Air O-1PST + name_add_mac_suffix: false + project: + name: mallocarray.airgradient + version: 2.0.0 + 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-1pst + 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: UART0 + 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 +- platform: template + name: SenseAir S8 Calibration + id: senseair_s8_calibrate_button + on_press: + - then: + - senseair.background_calibration: + id: senseair_s8 + - delay: 70s + - senseair.background_calibration_result: + id: senseair_s8 + disabled_by_default: false +- platform: template + name: SenseAir S8 Show Calibration Interval + id: senseair_s8_show_calibrate_interval + on_press: + - then: + - senseair.abc_get_period: + id: senseair_s8 + disabled_by_default: false +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: + - 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: + - 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: 10 + 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: senseair + co2: + name: CO2 + id: co2 + filters: + - skip_initial: 2 + - clamp: + min_value: 400.0 + max_value: .nan + ignore_out_of_range: false + disabled_by_default: false + force_update: false + unit_of_measurement: ppm + icon: mdi:molecule-co2 + accuracy_decimals: 0 + device_class: carbon_dioxide + state_class: measurement + id: senseair_s8 + uart_id: senseair_s8_uart + update_interval: 60s +- 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 +switch: +- platform: template + name: SenseAir S8 Automatic Background Calibration + id: senseair_s8_abc_switch + restore_mode: RESTORE_DEFAULT_ON + optimistic: true + entity_category: config + turn_on_action: + then: + - senseair.abc_enable: + id: senseair_s8 + turn_off_action: + then: + - senseair.abc_disable: + id: senseair_s8 + disabled_by_default: false + assumed_state: false +- platform: template + name: Upload to AirGradient Dashboard + id: upload_airgradient + restore_mode: RESTORE_DEFAULT_OFF + 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 +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 + json: + wifi: !lambda |- + return to_string(id(wifi_dbm).state); + pm01: !lambda |- + return to_string(id(pm_1_0).state); + pm02: !lambda |- + return to_string(id(pm_2_5).state); + pm10: !lambda |- + return to_string(id(pm_10_0).state); + pm003_count: !lambda |- + return to_string(id(pm_0_3um).state); + rco2: !lambda |- + return to_string(id(co2).state); + atmp: !lambda |- + return to_string(id(temp).state); + rhum: !lambda |- + return to_string(id(humidity).state); + tvoc_index: !lambda |- + return to_string(id(voc).state); + nox_index: !lambda |- + return to_string(id(nox).state); + 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 +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-1pst.local +dashboard_import: + package_import_url: github://MallocArray/airgradient_esphome/airgradient-open-air-o-1pst.yaml + import_full_config: false + diff --git a/full_config/ag-pro.yaml b/full_config/ag-pro.yaml index c472d3e..b84b833 100644 --- a/full_config/ag-pro.yaml +++ b/full_config/ag-pro.yaml @@ -1,809 +1,840 @@ -substitutions: - devicename: ag-pro - friendly_devicename: AG Pro - ag_esphome_config_version: 1.0.0 - add_mac_suffix: 'true' -esphome: - name: ag-pro - friendly_name: AG Pro - name_add_mac_suffix: true - project: - name: mallocarray.airgradient - version: 1.0.0 - min_version: 2023.7.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().substr(6,11) + "/measures"; - headers: - Content-Type: application/json - json: - wifi: !lambda |- - return to_string(-50); - verify_ssl: true - method: POST - build_path: build/ag-pro - area: '' - platformio_options: {} - includes: [] - libraries: [] -esp8266: - board: d1_mini - restore_from_flash: true - framework: - version: 3.0.2 - source: ~3.30002.0 - platform_version: platformio/espressif8266@3.2.0 - early_pin_init: true - board_flash_mode: dout -logger: - logs: - component: ERROR - baud_rate: 115200 - tx_buffer_size: 512 - deassert_rts_dtr: false - hardware_uart: UART0 - level: DEBUG - esp8266_store_log_strings_in_flash: true -captive_portal: {} -uart: -- rx_pin: - number: 2 - mode: - input: true - output: false - open_drain: false - pullup: false - pulldown: false - analog: false - inverted: false - tx_pin: - number: 0 - mode: - output: true - input: false - open_drain: false - pullup: false - pulldown: false - analog: false - inverted: false - baud_rate: 9600 - id: senseair_s8_uart - rx_buffer_size: 256 - stop_bits: 1 - data_bits: 8 - parity: NONE -- rx_pin: - number: 14 - mode: - input: true - output: false - open_drain: false - pullup: false - pulldown: false - analog: false - inverted: false - tx_pin: - number: 12 - mode: - output: true - input: false - open_drain: false - pullup: false - pulldown: false - analog: false - inverted: false - baud_rate: 9600 - id: pms5003_uart - rx_buffer_size: 256 - stop_bits: 1 - data_bits: 8 - parity: NONE -i2c: -- sda: 4 - scl: 5 - frequency: 400000.0 - scan: true -sensor: -- platform: pmsx003 - type: PMSX003 - uart_id: pms5003_uart - pm_2_5: - name: PM 2.5 - id: pm_2_5 - 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 - 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 - 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 - 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: 10 - 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: senseair - co2: - name: CO2 - id: co2 - filters: - - skip_initial: 2 - - clamp: - min_value: 400.0 - max_value: .nan - ignore_out_of_range: false - disabled_by_default: false - force_update: false - unit_of_measurement: ppm - icon: mdi:molecule-co2 - accuracy_decimals: 0 - device_class: carbon_dioxide - state_class: measurement - id: senseair_s8 - uart_id: senseair_s8_uart - update_interval: 60s -- platform: sht4x - temperature: - name: Temperature - id: temp - disabled_by_default: false - force_update: false - unit_of_measurement: °C - icon: mdi:thermometer - accuracy_decimals: 2 - device_class: temperature - state_class: measurement - humidity: - name: Humidity - id: humidity - disabled_by_default: false - force_update: false - unit_of_measurement: '%' - icon: mdi:water-percent - accuracy_decimals: 2 - device_class: humidity - state_class: measurement - address: 0x44 - precision: High - heater_power: High - heater_time: Long - heater_max_duty: 0.0 - update_interval: 60s -- 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 -button: -- platform: template - name: SenseAir S8 Calibration - id: senseair_s8_calibrate_button - on_press: - - then: - - senseair.background_calibration: - id: senseair_s8 - - delay: 70s - - senseair.background_calibration_result: - id: senseair_s8 - disabled_by_default: false -- platform: template - name: SenseAir S8 Show Calibration Interval - id: senseair_s8_show_calibrate_interval - on_press: - - then: - - senseair.abc_get_period: - id: senseair_s8 - disabled_by_default: false -switch: -- platform: template - name: SenseAir S8 Automatic Background Calibration - id: senseair_s8_abc_switch - restore_mode: RESTORE_DEFAULT_ON - optimistic: true - entity_category: config - turn_on_action: - then: - - senseair.abc_enable: - id: senseair_s8 - turn_off_action: - then: - - senseair.abc_disable: - id: senseair_s8 - disabled_by_default: false - assumed_state: false -- platform: template - name: Display Temperature in °F - icon: mdi:thermometer - id: display_in_f - restore_mode: RESTORE_DEFAULT_ON - optimistic: true - disabled_by_default: false - assumed_state: false -- 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 -font: -- file: - family: Open Sans - weight: 400 - italic: false - type: gfonts - id: open_sans_14 - size: 14 - glyphs: - - ' ' - - '!' - - '"' - - '%' - - ( - - ) - - + - - ',' - - '-' - - . - - / - - '0' - - '1' - - '2' - - '3' - - '4' - - '5' - - '6' - - '7' - - '8' - - '9' - - ':' - - '=' - - A - - B - - C - - D - - E - - F - - G - - H - - I - - J - - K - - L - - M - - N - - O - - P - - Q - - R - - S - - T - - U - - V - - W - - X - - Y - - Z - - _ - - a - - b - - c - - d - - e - - f - - g - - h - - i - - j - - k - - l - - m - - n - - o - - p - - q - - r - - s - - t - - u - - v - - w - - x - - y - - z - - ° - - ³ - - µ -- file: - family: Open Sans - weight: 400 - italic: false - type: gfonts - id: open_sans_9 - size: 9 - glyphs: - - ' ' - - '!' - - '"' - - '%' - - ( - - ) - - + - - ',' - - '-' - - . - - / - - '0' - - '1' - - '2' - - '3' - - '4' - - '5' - - '6' - - '7' - - '8' - - '9' - - ':' - - '=' - - A - - B - - C - - D - - E - - F - - G - - H - - I - - J - - K - - L - - M - - N - - O - - P - - Q - - R - - S - - T - - U - - V - - W - - X - - Y - - Z - - _ - - a - - b - - c - - d - - e - - f - - g - - h - - i - - j - - k - - l - - m - - n - - o - - p - - q - - r - - s - - t - - u - - v - - w - - x - - y - - z - - ° - - ³ - - µ -- file: - family: Open Sans - weight: 400 - italic: false - type: gfonts - id: open_sans_20 - size: 20 - glyphs: - - ' ' - - '!' - - '"' - - '%' - - ( - - ) - - + - - ',' - - '-' - - . - - / - - '0' - - '1' - - '2' - - '3' - - '4' - - '5' - - '6' - - '7' - - '8' - - '9' - - ':' - - '=' - - A - - B - - C - - D - - E - - F - - G - - H - - I - - J - - K - - L - - M - - N - - O - - P - - Q - - R - - S - - T - - U - - V - - W - - X - - Y - - Z - - _ - - a - - b - - c - - d - - e - - f - - g - - h - - i - - j - - k - - l - - m - - n - - o - - p - - q - - r - - s - - t - - u - - v - - w - - x - - y - - z - - ° - - ³ - - µ -display: -- platform: ssd1306_i2c - model: SH1106_128X64 - id: oled_display - address: 0x3C - pages: - - id: summary - lambda: !lambda |- - if (id(display_in_f).state) { - it.printf(0, 0, id(open_sans_14), "%.1f°F", id(temp).state*9/5+32); - } else { - it.printf(0, 0, id(open_sans_14), "%.1f°C", id(temp).state); - } - it.printf(128, 0, id(open_sans_14), TextAlign::TOP_RIGHT, "%.1f%%", id(humidity).state); - it.line(0,17,128,17); - it.printf(0,19, id(open_sans_9), "CO2"); - it.printf(0,27, id(open_sans_20), "%.0f", id(co2).state); - it.printf(0,52, id(open_sans_9), "ppm"); - it.line(50,19,50,64); - it.printf(54, 19, id(open_sans_9), "PM2.5"); - it.printf(54, 27, id(open_sans_20), "%.0f", id(pm_2_5).state); - it.printf(54, 52, id(open_sans_9), "µg/m³"); - it.line(100,19,100,64); - it.printf(104,18, id(open_sans_9), "TVOC"); - it.printf(104,29, id(open_sans_9), "%.0f", id(voc).state); - it.printf(104,41, id(open_sans_9), "NOx"); - it.printf(104,52, id(open_sans_9), "%.0f", id(nox).state); - - id: boot - lambda: !lambda |- - it.printf(0, 0, id(open_sans_14), "ID:"); - it.printf(128, 0, id(open_sans_14), TextAlign::TOP_RIGHT, "%s", get_mac_address().c_str()); - it.printf(0, 21, id(open_sans_14), "Config Ver: 1.0.0"); - it.printf(0, 42, id(open_sans_14), "AG Pro"); - on_page_change: - - to: boot - then: - - if: - condition: - lambda: !lambda |- - return id(device_uptime).state > 30; - then: - - display.page.show_next: - id: oled_display - - component.update: - id: oled_display - auto_clear_enabled: true - brightness: 1.0 - contrast: 1.0 - flip_x: true - flip_y: true - offset_x: 0 - offset_y: 0 - invert: false - update_interval: 1s -interval: -- interval: 5s - then: - - if: - condition: - lambda: !lambda |- - return id(device_uptime).state < 10; - then: - - display.page.show: - id: boot - else: - - display.page.show_next: - id: oled_display - - component.update: - id: oled_display - startup_delay: 0s -- 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().substr(6,11) + "/measures"; - headers: - Content-Type: application/json - json: - wifi: !lambda |- - return to_string(id(wifi_dbm).state); - pm01: !lambda |- - return to_string(id(pm_1_0).state); - pm02: !lambda |- - return to_string(id(pm_2_5).state); - pm10: !lambda |- - return to_string(id(pm_10_0).state); - pm003_count: !lambda |- - return to_string(id(pm_0_3um).state); - rco2: !lambda |- - return to_string(id(co2).state); - atmp: !lambda |- - return to_string(id(temp).state); - rhum: !lambda |- - return to_string(id(humidity).state); - tvoc_index: !lambda |- - return to_string(id(voc).state); - nox_index: !lambda |- - return to_string(id(nox).state); - verify_ssl: true - method: POST - startup_delay: 0s -http_request: - useragent: ESPHome - follow_redirects: true - redirect_limit: 3 - timeout: 5s - esp8266_disable_ssl_support: false -binary_sensor: -- platform: gpio - pin: - number: 13 - mode: - input: true - pullup: true - output: false - open_drain: false - pulldown: false - analog: false - inverted: true - internal: true - name: Configuration Button - id: config_button - on_multi_click: - - timing: - - state: true - min_length: 0ms - max_length: 1s - - state: false - min_length: 500ms - then: - - logger.log: - format: Toggling display betwen C and F - args: [] - tag: main - level: DEBUG - - switch.toggle: - id: display_in_f - invalid_cooldown: 1s - - timing: - - state: true - min_length: 1s - max_length: 5s - - state: false - min_length: 500ms - then: - - logger.log: - format: Starting manual CO2 calibration - args: [] - tag: main - level: DEBUG - - senseair.background_calibration: - id: senseair_s8 - - delay: 70s - - senseair.background_calibration_result: - id: senseair_s8 - invalid_cooldown: 1s - disabled_by_default: false -api: - port: 6053 - password: \033[5m''\033[6m - reboot_timeout: 15min -ota: - safe_mode: true - port: 8266 - reboot_timeout: 5min - num_attempts: 10 -wifi: - ap: - ap_timeout: 1min - domain: .local - reboot_timeout: 15min - power_save_mode: NONE - fast_connect: false - output_power: 20.0 - passive_scan: false - enable_on_boot: true - use_address: ag-pro.local - +substitutions: + name: ag-pro + friendly_name: AG Pro + config_version: 2.0.0 + name_add_mac_suffix: 'false' +esphome: + name: ag-pro + friendly_name: AG Pro + name_add_mac_suffix: false + project: + name: mallocarray.airgradient + version: 2.0.0 + 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().substr(6,11) + "/measures"; + headers: + Content-Type: application/json + json: + wifi: !lambda |- + return to_string(-50); + verify_ssl: true + method: POST + build_path: build/ag-pro + area: '' + platformio_options: {} + includes: [] + libraries: [] +esp8266: + board: d1_mini + restore_from_flash: true + framework: + version: 3.0.2 + source: ~3.30002.0 + platform_version: platformio/espressif8266@3.2.0 + early_pin_init: true + board_flash_mode: dout +logger: + logs: + component: ERROR + baud_rate: 115200 + tx_buffer_size: 512 + deassert_rts_dtr: false + hardware_uart: UART0 + level: DEBUG + esp8266_store_log_strings_in_flash: true +captive_portal: {} +uart: +- rx_pin: + number: 2 + mode: + input: true + output: false + open_drain: false + pullup: false + pulldown: false + analog: false + inverted: false + tx_pin: + number: 0 + mode: + output: true + input: false + open_drain: false + pullup: false + pulldown: false + analog: false + inverted: false + baud_rate: 9600 + id: senseair_s8_uart + rx_buffer_size: 256 + stop_bits: 1 + data_bits: 8 + parity: NONE +- rx_pin: + number: 14 + mode: + input: true + output: false + open_drain: false + pullup: false + pulldown: false + analog: false + inverted: false + tx_pin: + number: 12 + mode: + output: true + input: false + open_drain: false + pullup: false + pulldown: false + analog: false + inverted: false + baud_rate: 9600 + id: pms5003_uart + rx_buffer_size: 256 + stop_bits: 1 + data_bits: 8 + parity: NONE +i2c: +- sda: 4 + scl: 5 + 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 +- platform: template + name: SenseAir S8 Calibration + id: senseair_s8_calibrate_button + on_press: + - then: + - senseair.background_calibration: + id: senseair_s8 + - delay: 70s + - senseair.background_calibration_result: + id: senseair_s8 + disabled_by_default: false +- platform: template + name: SenseAir S8 Show Calibration Interval + id: senseair_s8_show_calibrate_interval + on_press: + - then: + - senseair.abc_get_period: + id: senseair_s8 + disabled_by_default: false +sensor: +- platform: pmsx003 + type: PMSX003 + 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 + 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: 10 + 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: senseair + co2: + name: CO2 + id: co2 + filters: + - skip_initial: 2 + - clamp: + min_value: 400.0 + max_value: .nan + ignore_out_of_range: false + disabled_by_default: false + force_update: false + unit_of_measurement: ppm + icon: mdi:molecule-co2 + accuracy_decimals: 0 + device_class: carbon_dioxide + state_class: measurement + id: senseair_s8 + uart_id: senseair_s8_uart + update_interval: 60s +- platform: sht4x + temperature: + name: Temperature + id: temp + disabled_by_default: false + force_update: false + unit_of_measurement: °C + icon: mdi:thermometer + accuracy_decimals: 2 + device_class: temperature + state_class: measurement + humidity: + name: Humidity + id: humidity + disabled_by_default: false + force_update: false + unit_of_measurement: '%' + icon: mdi:water-percent + accuracy_decimals: 2 + device_class: humidity + state_class: measurement + address: 0x44 + precision: High + heater_power: High + heater_time: Long + heater_max_duty: 0.0 + update_interval: 60s +- 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 +switch: +- platform: template + name: SenseAir S8 Automatic Background Calibration + id: senseair_s8_abc_switch + restore_mode: RESTORE_DEFAULT_ON + optimistic: true + entity_category: config + turn_on_action: + then: + - senseair.abc_enable: + id: senseair_s8 + turn_off_action: + then: + - senseair.abc_disable: + id: senseair_s8 + disabled_by_default: false + assumed_state: false +- platform: template + name: Display Temperature in °F + icon: mdi:thermometer + id: display_in_f + restore_mode: RESTORE_DEFAULT_ON + optimistic: true + disabled_by_default: false + assumed_state: false +- platform: template + name: Upload to AirGradient Dashboard + id: upload_airgradient + restore_mode: RESTORE_DEFAULT_OFF + 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 +font: +- file: + family: Open Sans + weight: 400 + italic: false + type: gfonts + id: open_sans_14 + size: 14 + glyphs: + - ' ' + - '!' + - '"' + - '%' + - ( + - ) + - + + - ',' + - '-' + - . + - / + - '0' + - '1' + - '2' + - '3' + - '4' + - '5' + - '6' + - '7' + - '8' + - '9' + - ':' + - '=' + - A + - B + - C + - D + - E + - F + - G + - H + - I + - J + - K + - L + - M + - N + - O + - P + - Q + - R + - S + - T + - U + - V + - W + - X + - Y + - Z + - _ + - a + - b + - c + - d + - e + - f + - g + - h + - i + - j + - k + - l + - m + - n + - o + - p + - q + - r + - s + - t + - u + - v + - w + - x + - y + - z + - ° + - ³ + - µ +- file: + family: Open Sans + weight: 400 + italic: false + type: gfonts + id: open_sans_9 + size: 9 + glyphs: + - ' ' + - '!' + - '"' + - '%' + - ( + - ) + - + + - ',' + - '-' + - . + - / + - '0' + - '1' + - '2' + - '3' + - '4' + - '5' + - '6' + - '7' + - '8' + - '9' + - ':' + - '=' + - A + - B + - C + - D + - E + - F + - G + - H + - I + - J + - K + - L + - M + - N + - O + - P + - Q + - R + - S + - T + - U + - V + - W + - X + - Y + - Z + - _ + - a + - b + - c + - d + - e + - f + - g + - h + - i + - j + - k + - l + - m + - n + - o + - p + - q + - r + - s + - t + - u + - v + - w + - x + - y + - z + - ° + - ³ + - µ +- file: + family: Open Sans + weight: 400 + italic: false + type: gfonts + id: open_sans_20 + size: 20 + glyphs: + - ' ' + - '!' + - '"' + - '%' + - ( + - ) + - + + - ',' + - '-' + - . + - / + - '0' + - '1' + - '2' + - '3' + - '4' + - '5' + - '6' + - '7' + - '8' + - '9' + - ':' + - '=' + - A + - B + - C + - D + - E + - F + - G + - H + - I + - J + - K + - L + - M + - N + - O + - P + - Q + - R + - S + - T + - U + - V + - W + - X + - Y + - Z + - _ + - a + - b + - c + - d + - e + - f + - g + - h + - i + - j + - k + - l + - m + - n + - o + - p + - q + - r + - s + - t + - u + - v + - w + - x + - y + - z + - ° + - ³ + - µ +display: +- platform: ssd1306_i2c + model: SH1106_128X64 + id: oled_display + address: 0x3C + pages: + - id: summary + lambda: !lambda |- + if (id(display_in_f).state) { + it.printf(0, 0, id(open_sans_14), "%.1f°F", id(temp).state*9/5+32); + } else { + it.printf(0, 0, id(open_sans_14), "%.1f°C", id(temp).state); + } + it.printf(128, 0, id(open_sans_14), TextAlign::TOP_RIGHT, "%.1f%%", id(humidity).state); + it.line(0,17,128,17); + it.printf(0,19, id(open_sans_9), "CO2"); + it.printf(0,27, id(open_sans_20), "%.0f", id(co2).state); + it.printf(0,52, id(open_sans_9), "ppm"); + it.line(50,19,50,64); + it.printf(54, 19, id(open_sans_9), "PM2.5"); + it.printf(54, 27, id(open_sans_20), "%.0f", id(pm_2_5).state); + it.printf(54, 52, id(open_sans_9), "µg/m³"); + it.line(100,19,100,64); + it.printf(104,18, id(open_sans_9), "TVOC"); + it.printf(104,29, id(open_sans_9), "%.0f", id(voc).state); + it.printf(104,41, id(open_sans_9), "NOx"); + it.printf(104,52, id(open_sans_9), "%.0f", id(nox).state); + - id: boot + lambda: !lambda |- + it.printf(0, 0, id(open_sans_14), "ID:"); + it.printf(128, 0, id(open_sans_14), TextAlign::TOP_RIGHT, "%s", get_mac_address().c_str()); + it.printf(0, 21, id(open_sans_14), "Config Ver: 2.0.0"); + it.printf(0, 42, id(open_sans_14), "AG Pro"); + on_page_change: + - to: boot + then: + - if: + condition: + lambda: !lambda |- + return id(device_uptime).state > 30; + then: + - display.page.show_next: + id: oled_display + - component.update: + id: oled_display + auto_clear_enabled: true + brightness: 1.0 + contrast: 1.0 + flip_x: true + flip_y: true + offset_x: 0 + offset_y: 0 + invert: false + update_interval: 1s +interval: +- interval: 5s + then: + - if: + condition: + lambda: !lambda |- + return id(device_uptime).state < 10; + then: + - display.page.show: + id: boot + else: + - display.page.show_next: + id: oled_display + - component.update: + id: oled_display + startup_delay: 0s +- 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().substr(6,11) + "/measures"; + headers: + Content-Type: application/json + json: + wifi: !lambda |- + return to_string(id(wifi_dbm).state); + pm01: !lambda |- + return to_string(id(pm_1_0).state); + pm02: !lambda |- + return to_string(id(pm_2_5).state); + pm10: !lambda |- + return to_string(id(pm_10_0).state); + pm003_count: !lambda |- + return to_string(id(pm_0_3um).state); + rco2: !lambda |- + return to_string(id(co2).state); + atmp: !lambda |- + return to_string(id(temp).state); + rhum: !lambda |- + return to_string(id(humidity).state); + tvoc_index: !lambda |- + return to_string(id(voc).state); + nox_index: !lambda |- + return to_string(id(nox).state); + verify_ssl: true + method: POST + startup_delay: 0s +number: +- platform: template + name: Display Contrast % + icon: mdi:lightbulb + id: display_contrast + min_value: 0.0 + max_value: 100.0 + step: 1.0 + initial_value: 100.0 + optimistic: true + restore_value: true + mode: SLIDER + on_value: + - then: + - lambda: !lambda |- + id(oled_display).set_contrast(id(display_contrast).state / 100.0); + disabled_by_default: false + update_interval: 60s +http_request: + useragent: ESPHome + follow_redirects: true + redirect_limit: 3 + timeout: 5s + esp8266_disable_ssl_support: false +binary_sensor: +- platform: gpio + pin: + number: 13 + mode: + input: true + pullup: true + output: false + open_drain: false + pulldown: false + analog: false + inverted: true + internal: true + name: Configuration Button + id: config_button + on_multi_click: + - timing: + - state: true + min_length: 0ms + max_length: 1s + - state: false + min_length: 500ms + then: + - logger.log: + format: Toggling display betwen C and F + tag: main + level: DEBUG + args: [] + - switch.toggle: + id: display_in_f + invalid_cooldown: 1s + - timing: + - state: true + min_length: 1s + max_length: 5s + - state: false + min_length: 500ms + then: + - logger.log: + format: Starting manual CO2 calibration + tag: main + level: DEBUG + args: [] + - senseair.background_calibration: + id: senseair_s8 + - delay: 70s + - senseair.background_calibration_result: + id: senseair_s8 + invalid_cooldown: 1s + disabled_by_default: false +api: + port: 6053 + password: \033[5m''\033[6m + reboot_timeout: 15min +ota: + safe_mode: true + port: 8266 + reboot_timeout: 5min + num_attempts: 10 +wifi: + ap: + ap_timeout: 1min + domain: .local + reboot_timeout: 15min + power_save_mode: NONE + fast_connect: false + output_power: 20.0 + passive_scan: false + enable_on_boot: true + use_address: ag-pro.local +dashboard_import: + package_import_url: github://MallocArray/airgradient_esphome/airgradient-pro.yaml + import_full_config: false +