I think I’ve sorted out a functional ESPHome configuration for the outdoor monitor and figured I’d post in case it’s useful for others or if there are any suggested improvements.
Credit and big thanks to @spectrumjade for the patch which allows both UARTs to be used.
Some notable differences when compared to a similar configuration for the DIY Pro:
-
TPL5010 hardware watchdog (GPIO2) must be signaled periodically; if not, hardware reset is observed every ~450s
-
Logger uses
hardware_uart: USB_SERIAL_JTAG
which allows for logging over USB serial while avoiding interference with UART data -
Values from the 2x PMS5003T modules are averaged, though there may be a more elegant way to do this
-
Uses
esp-idf
framework which is recommended for ESP32C3
Build versions (esphome==2023.4.4)
Processing airgradient-outdoor (board: lolin_c3_mini; framework: espidf; platform: platformio/espressif32 @ 5.3.0)
------------------------------------------------------------------------------------------------------------------
HARDWARE: ESP32C3 160MHz, 320KB RAM, 4MB Flash
- framework-espidf @ 3.40404.0 (4.4.4)
- tool-cmake @ 3.16.4
- tool-ninja @ 1.7.1
- toolchain-esp32ulp @ 2.35.0-20220830
- toolchain-riscv32-esp @ 8.4.0+2021r2-patch5
airgradient-outdoor.yaml
substitutions:
devicename: "airgradient-outdoor"
esphome:
name: "${devicename}"
platformio_options:
board_build.flash_mode: dio
esp32:
board: lolin_c3_mini
framework:
type: esp-idf
# Remove once this is merged: https://github.com/esphome/esphome/pull/4762
external_components:
- source: github://spectrumjade/esphome@esp32s2_cdc_fix
components: [ uart ]
refresh: 0s
logger:
hardware_uart: USB_SERIAL_JTAG
level: INFO
api:
ota:
password: !secret ota_update_password
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
switch:
- platform: gpio
id: watchdog_reset
internal: true
pin: GPIO2 # Pin 5
- platform: restart
name: "${devicename} Restart"
- platform: safe_mode
name: "${devicename} Flash Mode (Safe Mode)"
# Momentary button
binary_sensor:
- platform: gpio
id: button
pin: GPIO9 # Pin 23
# Signal hardware watchdog every 60s
time:
- platform: homeassistant
id: ha_time
on_time:
- seconds: 0
minutes: /1
then:
- lambda: !lambda |-
id(watchdog_reset).turn_on();
delay(20);
id(watchdog_reset).turn_off();
i2c:
sda: GPIO7 # Pin 21
scl: GPIO6 # Pin 20
scan: false
uart:
- id: pm1_uart
baud_rate: 9600
rx_pin: GPIO20 # Pin 30 / RXD0
tx_pin: GPIO21 # Pin 31 / TXD0
- id: pm2_uart
baud_rate: 9600
rx_pin: GPIO0 # Pin 12
tx_pin: GPIO1 # Pin 13
light:
- platform: status_led
name: "${devicename} Status LED"
pin: GPIO10 # Pin 16
sensor:
# Diagnostic sensors
- platform: uptime
name: "${devicename} Uptime"
update_interval: 60s
- platform: internal_temperature
name: "${devicename} Internal ESP32 Temperature"
unit_of_measurement: "°F"
update_interval: 60s
filters:
- lambda: return x * (9.0/5.0) + 32.0;
# PM1 raw values
- platform: pmsx003
type: PMS5003T
uart_id: pm1_uart
update_interval: 120s
temperature:
id: pm1_temperature
accuracy_decimals: 1
unit_of_measurement: "°F"
filters:
- lambda: return x * (9.0/5.0) + 32.0;
humidity:
id: pm1_humidity
accuracy_decimals: 1
unit_of_measurement: "%"
pm_1_0:
id: pm1_1_0
pm_2_5:
id: pm1_2_5
pm_10_0:
id: pm1_10_0
pm_0_3um:
id: pm1_0_3um
pm_0_5um:
id: pm1_0_5um
pm_1_0um:
id: pm1_1_0um
pm_2_5um:
id: pm1_2_5um
# PM2 raw values
- platform: pmsx003
type: PMS5003T
uart_id: pm2_uart
update_interval: 120s
temperature:
id: pm2_temperature
accuracy_decimals: 1
unit_of_measurement: "°F"
filters:
- lambda: return x * (9.0/5.0) + 32.0;
humidity:
id: pm2_humidity
accuracy_decimals: 1
unit_of_measurement: "%"
pm_1_0:
id: pm2_1_0
pm_2_5:
id: pm2_2_5
pm_10_0:
id: pm2_10_0
pm_0_3um:
id: pm2_0_3um
pm_0_5um:
id: pm2_0_5um
pm_1_0um:
id: pm2_1_0um
pm_2_5um:
id: pm2_2_5um
# PM1 + PM2 averaged values
- platform: template
id: temperature
name: "${devicename} Temperature"
icon: mdi:home-thermometer-outline
accuracy_decimals: 1
unit_of_measurement: "°F"
lambda: return (id(pm1_temperature).state + id(pm2_temperature).state) / 2;
- platform: template
id: humidity
name: "${devicename} Relative Humidity"
icon: mdi:water-percent
accuracy_decimals: 1
unit_of_measurement: "%"
lambda: return (id(pm1_humidity).state + id(pm2_humidity).state) / 2;
- platform: template
id: pm_1_0
name: "${devicename} PM <1.0µm Concentration"
icon: mdi:smoke
accuracy_decimals: 0
unit_of_measurement: µg/m³
lambda: return (id(pm1_1_0).state + id(pm2_1_0).state) / 2;
- platform: template
id: pm_2_5
name: "${devicename} PM <2.5µm Concentration"
icon: mdi:smoke
accuracy_decimals: 0
unit_of_measurement: µg/m³
lambda: return (id(pm1_2_5).state + id(pm2_2_5).state) / 2;
- platform: template
id: pm_10_0
name: "${devicename} PM <10.0µm Concentration"
icon: mdi:smoke
accuracy_decimals: 0
unit_of_measurement: µg/m³
lambda: return (id(pm1_10_0).state + id(pm2_10_0).state) / 2;
- platform: template
id: pm_0_3um
name: "${devicename} PM >0.3µm Count"
icon: mdi:counter
accuracy_decimals: 0
unit_of_measurement: /dL
lambda: return (id(pm1_0_3um).state + id(pm2_0_3um).state) / 2;
- platform: template
id: pm_0_5um
name: "${devicename} PM >0.5µm Count"
icon: mdi:counter
accuracy_decimals: 0
unit_of_measurement: /dL
lambda: return (id(pm1_0_5um).state + id(pm2_0_5um).state) / 2;
- platform: template
id: pm_1_0um
name: "${devicename} PM >1.0µm Count"
icon: mdi:counter
accuracy_decimals: 0
unit_of_measurement: /dL
lambda: return (id(pm1_1_0um).state + id(pm2_1_0um).state) / 2;
- platform: template
id: pm_2_5um
name: "${devicename} PM >2.5µm Count"
icon: mdi:counter
accuracy_decimals: 0
unit_of_measurement: /dL
lambda: return (id(pm1_2_5um).state + id(pm2_2_5um).state) / 2;