Skip to content

Commit 1604da1

Browse files
committed
Initial commit for Open Air with CO2
All sensor included. No support for button presses or LED at this time.
1 parent a8e670e commit 1604da1

File tree

1 file changed

+250
-0
lines changed

1 file changed

+250
-0
lines changed

airgradient-open-air-co2.yaml

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
# AirGradient Open Air Outdoor Monitor with CO2 and TVOC
2+
# Model: O-1PST
3+
# https://www.airgradient.com/open-airgradient/instructions/overview/
4+
5+
# Needs ESPHome 2023.7.0 or later
6+
7+
substitutions:
8+
devicename: "ag-open-air-o-1pst"
9+
upper_devicename: "AG Open Air CO2"
10+
ag_esphome_config_version: 0.1.0
11+
12+
esphome:
13+
name: "${devicename}"
14+
friendly_name: "${upper_devicename}"
15+
name_add_mac_suffix: true # Set to false if you don't want part of the MAC address in the name
16+
on_boot:
17+
priority: 200 # Network connections setup
18+
then:
19+
- http_request.post:
20+
# Return wifi signal -50 as soon as device boots to show activity on AirGradient Dashboard site
21+
# Using -50 instead of actual value as the wifi_signal sensor has not reported a value at this point in boot process
22+
url: !lambda |-
23+
return "http://hw.airgradient.com/sensors/airgradient:" + get_mac_address() + "/measures";
24+
headers:
25+
Content-Type: application/json
26+
json:
27+
wifi: !lambda return to_string(-50);
28+
29+
esp32:
30+
board: esp32-c3-devkitm-1
31+
32+
# Enable logging
33+
# https://esphome.io/components/logger.html
34+
logger:
35+
baud_rate: 0 # Must disable serial logging as it conflicts with pms5003 uart pins and ESP32-C3 only has 2 hardware UART
36+
# hardware_uart: USB_SERIAL_JTAG
37+
logs:
38+
component: ERROR # Hiding warning messages about component taking a long time https://github.com/esphome/issues/issues/4717
39+
40+
# Enable Home Assistant API
41+
api:
42+
43+
ota:
44+
45+
wifi:
46+
ssid: !secret wifi_ssid
47+
password: !secret wifi_password
48+
manual_ip:
49+
static_ip: 192.168.2.120
50+
gateway: 192.168.2.1
51+
subnet: 255.255.255.0
52+
dns1: 192.168.2.1
53+
dns2: 4.2.2.2
54+
# Enable fallback hotspot (captive portal) in case wifi connection fails
55+
ap:
56+
57+
# The captive portal is a fallback mechanism for when connecting to the configured WiFi fails.
58+
# https://esphome.io/components/captive_portal.html
59+
captive_portal:
60+
61+
web_server: # Please note that enabling this component will take up a lot of memory and may decrease stability, especially on ESP8266.
62+
63+
# Create a switch for safe_mode in order to flash the device
64+
# Solution from this thread:
65+
# https://community.home-assistant.io/t/esphome-flashing-over-wifi-does-not-work/357352/1
66+
switch:
67+
- platform: safe_mode
68+
name: "Flash Mode (Safe Mode)"
69+
icon: "mdi:cellphone-arrow-down"
70+
71+
72+
uart:
73+
# https://esphome.io/components/uart.html#uart
74+
- rx_pin: GPIO0 # Pin 12
75+
tx_pin: GPIO1 # Pin 13
76+
baud_rate: 9600
77+
id: senseair_s8_uart
78+
79+
- rx_pin: GPIO20 # Pin 30 or RX
80+
tx_pin: GPIO21 # Pin 31, or TX
81+
baud_rate: 9600
82+
id: pms5003_uart
83+
84+
i2c:
85+
# https://esphome.io/components/i2c.html
86+
sda: GPIO7 # Pin 21
87+
scl: GPIO6 # Pin 20
88+
frequency: 400kHz # 400kHz eliminates warnings about components taking a long time other than SGP40 component: https://github.com/esphome/issues/issues/4717
89+
90+
sensor:
91+
- platform: pmsx003
92+
# PMS5003T with temperature and humidity https://esphome.io/components/sensor/pmsx003.html
93+
type: PMS5003T
94+
uart_id: pms5003_uart
95+
pm_2_5:
96+
name: "PM 2.5"
97+
id: pm_2_5
98+
on_value:
99+
lambda: |-
100+
// https://en.wikipedia.org/wiki/Air_quality_index#Computing_the_AQI
101+
// Borrowed from https://github.com/kylemanna/sniffer/blob/master/esphome/sniffer_common.yaml
102+
if (id(pm_2_5).state <= 12.0) {
103+
// good
104+
id(pm_2_5_aqi).publish_state((50.0 - 0.0) / (12.0 - 0.0) * (id(pm_2_5).state - 0.0) + 0.0);
105+
} else if (id(pm_2_5).state <= 35.4) {
106+
// moderate
107+
id(pm_2_5_aqi).publish_state((100.0 - 51.0) / (35.4 - 12.1) * (id(pm_2_5).state - 12.1) + 51.0);
108+
} else if (id(pm_2_5).state <= 55.4) {
109+
// usg
110+
id(pm_2_5_aqi).publish_state((150.0 - 101.0) / (55.4 - 35.5) * (id(pm_2_5).state - 35.5) + 101.0);
111+
} else if (id(pm_2_5).state <= 150.4) {
112+
// unhealthy
113+
id(pm_2_5_aqi).publish_state((200.0 - 151.0) / (150.4 - 55.5) * (id(pm_2_5).state - 55.5) + 151.0);
114+
} else if (id(pm_2_5).state <= 250.4) {
115+
// very unhealthy
116+
id(pm_2_5_aqi).publish_state((300.0 - 201.0) / (250.4 - 150.5) * (id(pm_2_5).state - 150.5) + 201.0);
117+
} else if (id(pm_2_5).state <= 350.4) {
118+
// hazardous
119+
id(pm_2_5_aqi).publish_state((400.0 - 301.0) / (350.4 - 250.5) * (id(pm_2_5).state - 250.5) + 301.0);
120+
} else if (id(pm_2_5).state <= 500.4) {
121+
// hazardous 2
122+
id(pm_2_5_aqi).publish_state((500.0 - 401.0) / (500.4 - 350.5) * (id(pm_2_5).state - 350.5) + 401.0);
123+
} else {
124+
id(pm_2_5_aqi).publish_state(500);
125+
}
126+
pm_1_0:
127+
name: "PM 1.0"
128+
id: pm_1_0
129+
pm_10_0:
130+
name: "PM 10.0"
131+
id: pm_10_0
132+
pm_0_3um:
133+
name: "PM 0.3"
134+
id: pm_0_3um
135+
temperature:
136+
name: "Temperature"
137+
id: temp
138+
humidity:
139+
name: "Humidity"
140+
id: humidity
141+
update_interval: 2min
142+
143+
- platform: template
144+
name: "PM 2.5 AQI"
145+
unit_of_measurement: "AQI"
146+
icon: "mdi:air-filter"
147+
accuracy_decimals: 0
148+
id: pm_2_5_aqi
149+
150+
- platform: senseair
151+
# SenseAir S8 https://esphome.io/components/sensor/senseair.html
152+
# https://senseair.com/products/size-counts/s8-lp/
153+
co2:
154+
name: "SenseAir S8 CO2"
155+
id: co2
156+
filters:
157+
- skip_initial: 2
158+
- clamp:
159+
min_value: 400 # 419 as of 2023-06 https://gml.noaa.gov/ccgg/trends/global.html
160+
id: senseair_s8
161+
uart_id: senseair_s8_uart
162+
163+
- platform: wifi_signal
164+
name: "WiFi Signal"
165+
id: wifi_dbm
166+
update_interval: 60s
167+
168+
- platform: uptime
169+
name: "Uptime"
170+
id: device_uptime
171+
update_interval: 10s
172+
173+
- platform: sgp4x
174+
# SGP41 https://esphome.io/components/sensor/sgp4x.html
175+
voc:
176+
name: "VOC Index"
177+
id: voc
178+
nox:
179+
name: "NOx Index"
180+
id: nox
181+
compensation: # Remove this block if no temp/humidity sensor present for compensation
182+
temperature_source: temp
183+
humidity_source: humidity
184+
185+
186+
button:
187+
# https://github.com/esphome/issues/issues/2444
188+
- platform: template
189+
name: SenseAir S8 Calibration
190+
id: senseair_s8_calibrate_button
191+
on_press:
192+
then:
193+
- senseair.background_calibration: senseair_s8
194+
- delay: 70s
195+
- senseair.background_calibration_result: senseair_s8
196+
- platform: template
197+
name: SenseAir S8 Enable Automatic Calibration
198+
id: senseair_s8_enable_calibrate_button
199+
on_press:
200+
then:
201+
- senseair.abc_enable: senseair_s8
202+
- platform: template
203+
name: SenseAir S8 Disable Automatic Calibration
204+
id: senseair_s8_disable_calibrate_button
205+
on_press:
206+
then:
207+
- senseair.abc_disable: senseair_s8
208+
209+
output:
210+
- platform: gpio
211+
# Watchdog to reboot if no activity
212+
id: watchdog
213+
pin: GPIO2
214+
215+
http_request:
216+
# Used to support POST request to send data to AirGradient
217+
# https://esphome.io/components/http_request.html
218+
219+
220+
interval:
221+
- interval: 30s
222+
# Notify watchdog device is still alive
223+
then:
224+
- output.turn_on: watchdog
225+
- delay: 20ms
226+
- output.turn_off: watchdog
227+
228+
229+
- interval: 2.5min
230+
# Send data to AirGradient API server
231+
then:
232+
- http_request.post:
233+
# https://api.airgradient.com/public/docs/api/v1/
234+
# AirGradient URL with the MAC address all lower case
235+
url: !lambda |-
236+
return "http://hw.airgradient.com/sensors/airgradient:" + get_mac_address() + "/measures";
237+
headers:
238+
Content-Type: application/json
239+
# "!lambda return to_string(id(pm_2_5).state);" Converts sensor output from double to string
240+
json:
241+
wifi: !lambda return to_string(id(wifi_dbm).state);
242+
pm01: !lambda return to_string(id(pm_1_0).state);
243+
pm02: !lambda return to_string(id(pm_2_5).state);
244+
pm10: !lambda return to_string(id(pm_10_0).state);
245+
pm003_count: !lambda return to_string(id(pm_0_3um).state);
246+
rco2: !lambda return to_string(id(co2).state);
247+
atmp: !lambda return to_string(id(temp).state);
248+
rhum: !lambda return to_string(id(humidity).state);
249+
tvoc_index: !lambda return to_string(id(voc).state);
250+
nox_index: !lambda return to_string(id(nox).state);

0 commit comments

Comments
 (0)