devices.esphome.io
AirGradient-One
AirGradient-One
Device Type: sensorElectrical Standard: usBoard: esp32
The AirGradient ONE monitor measures PM, CO2, TVOC, NOx, temperature and relative humidity from AirGradient.
If you have multiple of these devices, you will likely need to make each sensor name unique across the boards (e.g. "AirGradient Temperature 1"), so there aren't naming conflicts.
Basic Configuration
# AirGradient ONE - Board v9# https://www.airgradient.com/open-airgradient/instructions/overview/## This configuration was blatantly yoinked from:# https://github.com/MallocArray/airgradient_esphome/blob/main/airgradient-one.yaml
# Needs ESPHome 2023.7.0 or later
# Reference for substitutions: https://github.com/ajfriesen/ESPHome-AirGradient/blob/main/air-gradient-pro-diy.yamlsubstitutions: devicename: !secret name ag_esphome_config_version: 0.1.0 led_strip_brightness: "25%"
esphome: name: "${devicename}"
esp32: board: esp32-c3-devkitm-1
# Disable logging# https://esphome.io/components/logger.htmllogger: baud_rate: 0 # Must disable serial logging as ESP32-C3 only has 2 hardware UART and both are in use logs: component: ERROR # Hiding warning messages about component taking a long time https://github.com/esphome/issues/issues/4717
# Enable Home Assistant APIapi: encryption: key: !secret api_encryption_key
ota: password: !secret ota_password
wifi: ssid: !secret wifi_ssid password: !secret wifi_password ap:
web_server: port: 9926 version: 1
# Create a switch for safe_mode in order to flash the device# Solution from this thread:# https://community.home-assistant.io/t/esphome-flashing-over-wifi-does-not-work/357352/1switch: - platform: safe_mode name: "Flash Mode (Safe Mode)" icon: "mdi:cellphone-arrow-down" - platform: template name: "Display Temperature in °F" icon: "mdi:thermometer" id: display_in_f restore_mode: RESTORE_DEFAULT_ON optimistic: True
uart: # https://esphome.io/components/uart.html#uart - rx_pin: GPIO0 # Pin 12 tx_pin: GPIO1 # Pin 13 baud_rate: 9600 id: senseair_s8_uart
- rx_pin: GPIO20 # Pin 30 or RX tx_pin: GPIO21 # Pin 31, or TX baud_rate: 9600 id: pms5003_uart
i2c: # https://esphome.io/components/i2c.html sda: GPIO7 scl: GPIO6 frequency: 400kHz # 400kHz eliminates warnings about components taking a long time other than SGP40 component: https://github.com/esphome/issues/issues/4717
sensor: - platform: pmsx003 # PMS5003 https://esphome.io/components/sensor/pmsx003.html type: PMSX003 uart_id: pms5003_uart pm_2_5: name: "PM 2.5" id: pm_2_5 on_value: 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 id(pm_2_5_aqi).publish_state((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 id(pm_2_5_aqi).publish_state((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 id(pm_2_5_aqi).publish_state((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 id(pm_2_5_aqi).publish_state((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 id(pm_2_5_aqi).publish_state((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 id(pm_2_5_aqi).publish_state((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 id(pm_2_5_aqi).publish_state((500.0 - 401.0) / (500.4 - 350.5) * (id(pm_2_5).state - 350.5) + 401.0); } else { id(pm_2_5_aqi).publish_state(500); } pm_1_0: name: "PM 1.0" id: pm_1_0 pm_10_0: name: "PM 10.0" id: pm_10_0 pm_0_3um: name: "PM 0.3" id: pm_0_3um update_interval: 2min
- platform: template name: "PM 2.5 AQI" unit_of_measurement: "AQI" icon: "mdi:air-filter" accuracy_decimals: 0 id: pm_2_5_aqi
- platform: senseair # SenseAir S8 https://esphome.io/components/sensor/senseair.html # https://senseair.com/products/size-counts/s8-lp/ co2: name: "SenseAir S8 CO2" id: co2 filters: - skip_initial: 2 - clamp: min_value: 400 # 419 as of 2023-06 https://gml.noaa.gov/ccgg/trends/global.html on_value: - if: condition: lambda: 'return id(co2).state < 800;' then: - light.turn_on: id: led_strip brightness: "${led_strip_brightness}" red: 0% green: 100% blue: 0% - if: condition: lambda: 'return id(co2).state >= 800 && id(co2).state < 1000;' then: - light.turn_on: id: led_strip brightness: "${led_strip_brightness}" red: 100% green: 100% blue: 0% - if: condition: lambda: 'return id(co2).state >= 1000 && id(co2).state < 1500;' then: - light.turn_on: id: led_strip brightness: "${led_strip_brightness}" red: 100% green: 50% blue: 0% - if: condition: lambda: 'return id(co2).state >= 1500 && id(co2).state < 2000;' then: - light.turn_on: id: led_strip brightness: "${led_strip_brightness}" red: 100% green: 0% blue: 0% - if: condition: lambda: 'return id(co2).state >= 2000 && id(co2).state < 3000;' then: - light.turn_on: id: led_strip brightness: "${led_strip_brightness}" red: 60% green: 0% blue: 60% - if: condition: lambda: 'return id(co2).state >= 3000 && id(co2).state < 10000;' then: - light.turn_on: id: led_strip brightness: "${led_strip_brightness}" red: 40% green: 0% blue: 0% id: senseair_s8 uart_id: senseair_s8_uart
- platform: sht4x # SHT40 https://esphome.io/components/sensor/sht4x.html temperature: name: "Temperature" id: temp humidity: name: "Humidity" id: humidity address: 0x44
- platform: wifi_signal name: "WiFi Signal" id: wifi_dbm update_interval: 60s
- platform: uptime name: "Uptime" id: device_uptime update_interval: 10s
- platform: sgp4x # SGP41 https://esphome.io/components/sensor/sgp4x.html voc: name: "VOC Index" id: voc nox: name: "NOx Index" id: nox compensation: # Remove this block if no temp/humidity sensor present for compensation temperature_source: temp humidity_source: humidity
font: # Font to use on the display # Open Source font Liberation Sans by Red Hat # https://www.dafont.com/liberation-sans.font # - file: "./fonts/liberation_sans/LiberationSans-Regular.ttf" - file: type: gfonts family: Poppins weight: light id: poppins_light size: 14 glyphs: '!"%()+=,-_.:°0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz/µ³' - file: type: gfonts family: Poppins weight: light id: poppins_light_12 size: 12 glyphs: '!"%()+=,-_.:°0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz/µ³' - file: "gfonts://Ubuntu Mono" id: ubuntu size: 22 glyphs: '!"%()+=,-_.:°0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz/µ³'
display: # https://esphome.io/components/display/ssd1306.html # Formatting reference: https://www.tutorialspoint.com/c_standard_library/c_function_printf.htm - platform: ssd1306_i2c model: "SH1106 128x64" id: oled_display address: 0x3C # rotation: 180° pages: - id: summary1 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: |- 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: boot lambda: |- it.printf(0, 0, id(poppins_light), "ID:"); it.printf(128, 0, id(poppins_light), TextAlign::TOP_RIGHT, "%s", get_mac_address().c_str()); it.printf(0, 21, id(poppins_light), "Config Ver: $ag_esphome_config_version"); it.printf(0, 42, id(poppins_light), "$devicename");
on_page_change: to: boot then: - if: # Skip the boot page after initial boot condition: lambda: 'return id(device_uptime).state > 30;' then: - display.page.show_next: oled_display - component.update: oled_display
button: # https://github.com/esphome/issues/issues/2444 - platform: template name: SenseAir S8 Calibration id: senseair_s8_calibrate_button on_press: then: - senseair.background_calibration: senseair_s8 - delay: 70s - senseair.background_calibration_result: senseair_s8 - platform: template name: SenseAir S8 Enable Automatic Calibration id: senseair_s8_enable_calibrate_button on_press: then: - senseair.abc_enable: senseair_s8 - platform: template name: SenseAir S8 Disable Automatic Calibration id: senseair_s8_disable_calibrate_button on_press: then: - senseair.abc_disable: senseair_s8
output: - platform: gpio # Watchdog to reboot if no activity id: watchdog pin: GPIO2
light: # https://esphome.io/components/light/esp32_rmt_led_strip.html - platform: esp32_rmt_led_strip color_correct: [50%,50%,50%] rgb_order: GRB pin: GPIO10 # Pin 16 num_leds: 11 rmt_channel: 0 chipset: ws2812 name: "LED Strip" id: led_strip
interval: - interval: 30s # Notify watchdog device is still alive then: - output.turn_on: watchdog - delay: 20ms - output.turn_off: watchdog
- interval: 5s # Automatically switch to the next page every five seconds then: - if: # Show boot screen for first 10 seconds with serial number and config version condition: lambda: 'return id(device_uptime).state < 10;' then: - display.page.show: boot - lambda: id(device_uptime).set_update_interval(1); else: # Change page on display - display.page.show_next: oled_display - component.update: oled_display