Skip to content

Commit 345e2ac

Browse files
committed
Update configs for current models
Switches to keep LED disabled and adjust brightness Switch to upload to AirGradient Dashboard Switches to enable/disable Automatic Baseline Calibration on S8 CO2 sensor
1 parent b24faa8 commit 345e2ac

File tree

4 files changed

+441
-302
lines changed

4 files changed

+441
-302
lines changed

airgradient-basic.yaml

Lines changed: 150 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
substitutions:
44
devicename: "ag-basic"
55
upper_devicename: "AG Basic"
6+
ag_esphome_config_version: 0.1.1
67

78
# Instructions: https://esphome.io/guides/getting_started_hassio.html
89
esphome:
@@ -14,24 +15,48 @@ esp8266:
1415
board: d1_mini
1516

1617
# Enable logging
18+
# https://esphome.io/components/logger.html
1719
logger:
20+
logs:
21+
component: ERROR # Hiding warning messages about component taking a long time https://github.com/esphome/issues/issues/4717
1822

1923
# Enable Home Assistant API
2024
api:
21-
password: ""
2225

2326
ota:
24-
password: ""
2527

2628
wifi:
27-
# https://esphome.io/components/wifi.html
28-
ssid: !secret wifi_ssid
29-
password: !secret wifi_password
3029
# Enable fallback hotspot (captive portal) in case wifi connection fails
31-
# Defaults to device name with no password
32-
ap: {}
30+
ap:
31+
32+
# The captive portal is a fallback mechanism for when connecting to the configured WiFi fails.
33+
# https://esphome.io/components/captive_portal.html
3334
captive_portal:
3435

36+
http_request:
37+
# Used to support POST request to send data to AirGradient
38+
# https://esphome.io/components/http_request.html
39+
40+
switch:
41+
- platform: safe_mode
42+
# Create a switch for safe_mode in order to flash the device
43+
# Solution from this thread:
44+
# https://community.home-assistant.io/t/esphome-flashing-over-wifi-does-not-work/357352/1
45+
name: "Flash Mode (Safe Mode)"
46+
icon: "mdi:cellphone-arrow-down"
47+
48+
- platform: template
49+
name: "Display Temperature in °F"
50+
icon: "mdi:thermometer"
51+
id: display_in_f
52+
restore_mode: RESTORE_DEFAULT_ON
53+
optimistic: True
54+
55+
- platform: template
56+
name: "Upload to AirGradient Dashboard"
57+
id: upload_airgradient
58+
restore_mode: RESTORE_DEFAULT_ON
59+
optimistic: True
3560

3661
uart:
3762
# https://esphome.io/components/uart.html#uart
@@ -51,73 +76,92 @@ i2c:
5176

5277
sensor:
5378
- platform: pmsx003
54-
# https://esphome.io/components/sensor/pmsx003.html
79+
# Default interval of updating every second, but using an average over the last 30 seconds/readings
80+
# PMS5003 https://esphome.io/components/sensor/pmsx003.html
5581
type: PMSX003
82+
uart_id: pms5003_uart
5683
pm_2_5:
57-
name: "PM <2.5µm Concentration"
84+
name: "PM 2.5"
5885
id: pm_2_5
59-
on_value:
60-
lambda: |-
61-
// https://en.wikipedia.org/wiki/Air_quality_index#Computing_the_AQI
62-
// Borrowed from https://github.com/kylemanna/sniffer/blob/master/esphome/sniffer_common.yaml
63-
if (id(pm_2_5).state <= 12.0) {
64-
// good
65-
id(pm_2_5_aqi).publish_state((50.0 - 0.0) / (12.0 - 0.0) * (id(pm_2_5).state - 0.0) + 0.0);
66-
} else if (id(pm_2_5).state <= 35.4) {
67-
// moderate
68-
id(pm_2_5_aqi).publish_state((100.0 - 51.0) / (35.4 - 12.1) * (id(pm_2_5).state - 12.1) + 51.0);
69-
} else if (id(pm_2_5).state <= 55.4) {
70-
// usg
71-
id(pm_2_5_aqi).publish_state((150.0 - 101.0) / (55.4 - 35.5) * (id(pm_2_5).state - 35.5) + 101.0);
72-
} else if (id(pm_2_5).state <= 150.4) {
73-
// unhealthy
74-
id(pm_2_5_aqi).publish_state((200.0 - 151.0) / (150.4 - 55.5) * (id(pm_2_5).state - 55.5) + 151.0);
75-
} else if (id(pm_2_5).state <= 250.4) {
76-
// very unhealthy
77-
id(pm_2_5_aqi).publish_state((300.0 - 201.0) / (250.4 - 150.5) * (id(pm_2_5).state - 150.5) + 201.0);
78-
} else if (id(pm_2_5).state <= 350.4) {
79-
// hazardous
80-
id(pm_2_5_aqi).publish_state((400.0 - 301.0) / (350.4 - 250.5) * (id(pm_2_5).state - 250.5) + 301.0);
81-
} else if (id(pm_2_5).state <= 500.4) {
82-
// hazardous 2
83-
id(pm_2_5_aqi).publish_state((500.0 - 401.0) / (500.4 - 350.5) * (id(pm_2_5).state - 350.5) + 401.0);
84-
} else {
85-
id(pm_2_5_aqi).publish_state(500);
86-
}
86+
filters:
87+
- sliding_window_moving_average:
88+
window_size: 30
89+
send_every: 30
8790
pm_1_0:
88-
name: "PM <1.0µm Concentration"
91+
name: "PM 1.0"
8992
id: pm_1_0
93+
filters:
94+
- sliding_window_moving_average:
95+
window_size: 30
96+
send_every: 30
9097
pm_10_0:
91-
name: "PM <10.0µm Concentration"
98+
name: "PM 10.0"
9299
id: pm_10_0
100+
filters:
101+
- sliding_window_moving_average:
102+
window_size: 30
103+
send_every: 30
93104
pm_0_3um:
94-
name: "PM >0.3µm Count"
105+
name: "PM 0.3"
95106
id: pm_0_3um
96-
update_interval: 2min
97-
uart_id: pms5003_uart
107+
filters:
108+
- sliding_window_moving_average:
109+
window_size: 30
110+
send_every: 30
98111

99112
- platform: template
100-
name: "PM <2.5 AQI"
113+
# Depends on another sensor providing an ID of pm_2_5 such as a pms5003
114+
name: "PM 2.5 AQI"
115+
id: pm_2_5_aqi
116+
update_interval: 5 min
101117
unit_of_measurement: "AQI"
102118
icon: "mdi:air-filter"
103119
accuracy_decimals: 0
104-
id: pm_2_5_aqi
120+
filters:
121+
- skip_initial: 10 # Need valid data from PM 2.5 sensor before able to calculate
122+
lambda: |-
123+
// https://en.wikipedia.org/wiki/Air_quality_index#Computing_the_AQI
124+
// Borrowed from https://github.com/kylemanna/sniffer/blob/master/esphome/sniffer_common.yaml
125+
if (id(pm_2_5).state <= 12.0) {
126+
// good
127+
return((50.0 - 0.0) / (12.0 - 0.0) * (id(pm_2_5).state - 0.0) + 0.0);
128+
} else if (id(pm_2_5).state <= 35.4) {
129+
// moderate
130+
return((100.0 - 51.0) / (35.4 - 12.1) * (id(pm_2_5).state - 12.1) + 51.0);
131+
} else if (id(pm_2_5).state <= 55.4) {
132+
// usg
133+
return((150.0 - 101.0) / (55.4 - 35.5) * (id(pm_2_5).state - 35.5) + 101.0);
134+
} else if (id(pm_2_5).state <= 150.4) {
135+
// unhealthy
136+
return((200.0 - 151.0) / (150.4 - 55.5) * (id(pm_2_5).state - 55.5) + 151.0);
137+
} else if (id(pm_2_5).state <= 250.4) {
138+
// very unhealthy
139+
return((300.0 - 201.0) / (250.4 - 150.5) * (id(pm_2_5).state - 150.5) + 201.0);
140+
} else if (id(pm_2_5).state <= 350.4) {
141+
// hazardous
142+
return((400.0 - 301.0) / (350.4 - 250.5) * (id(pm_2_5).state - 250.5) + 301.0);
143+
} else if (id(pm_2_5).state <= 500.4) {
144+
// hazardous 2
145+
return((500.0 - 401.0) / (500.4 - 350.5) * (id(pm_2_5).state - 350.5) + 401.0);
146+
} else {
147+
return(500);
148+
}
105149
106150
- platform: senseair
107-
# https://esphome.io/components/sensor/senseair.html
151+
# SenseAir S8 https://esphome.io/components/sensor/senseair.html
152+
# https://senseair.com/products/size-counts/s8-lp/
108153
co2:
109-
name: "SenseAir S8 CO2 Value"
154+
name: "SenseAir S8 CO2"
110155
id: co2
111156
filters:
112157
- skip_initial: 2
113158
- clamp:
114159
min_value: 400 # 419 as of 2023-06 https://gml.noaa.gov/ccgg/trends/global.html
115-
update_interval: 30s
116160
id: senseair_s8
117161
uart_id: senseair_s8_uart
118162

119163
# - platform: sht4x
120-
# # SHT41 https://esphome.io/components/sensor/sht4x.html
164+
# # SHT40 https://esphome.io/components/sensor/sht4x.html
121165
# temperature:
122166
# name: "Temperature"
123167
# id: temp
@@ -135,34 +179,17 @@ sensor:
135179
name: "Humidity"
136180
id: humidity
137181
address: 0x44
138-
heater_enabled: false # Only enable if in conditions that may have high condensation
139182

140183
- platform: wifi_signal
141184
name: "WiFi Signal"
142185
id: wifi_dbm
143186
update_interval: 60s
144187

145188
- platform: uptime
146-
name: Uptime Sensor
189+
name: "Uptime"
147190
id: device_uptime
148191
update_interval: 10s
149192

150-
# - platform: sgp30
151-
# # https://esphome.io/components/sensor/sgp30.html
152-
# # Unable to get to work when OLED shield is also connected
153-
# eco2:
154-
# name: "eCO2"
155-
# id: eco2
156-
# accuracy_decimals: 1
157-
# tvoc:
158-
# name: "TVOC"
159-
# id: tvoc
160-
# accuracy_decimals: 1
161-
# update_interval: 30s
162-
163-
- platform: uptime
164-
name: Uptime Sensor
165-
id: airgradient_basement_uptime
166193

167194
font:
168195
# Font to use on the display
@@ -201,14 +228,20 @@ display:
201228
lambda: |-
202229
it.print(0, 0, id(font1), "HUM");
203230
it.printf(64, 24, id(font1), TextAlign::TOP_RIGHT, "%.0f%%",id(humidity).state);
204-
# - id: display_eco2
205-
# lambda: |-
206-
# it.print(0, 0, id(font1), "eCO2");
207-
# it.printf(64, 24, id(font1), TextAlign::TOP_RIGHT, "%.0f",id(eco2).state);
208-
# - id: display_tvoc
209-
# lambda: |-
210-
# it.print(0, 0, id(font1), "TVOC");
211-
# it.printf(64, 24, id(font1), TextAlign::TOP_RIGHT, "%.0f",id(tvoc).state);
231+
- id: boot
232+
lambda: |-
233+
it.printf(0, 0, id(font1), TextAlign::TOP_RIGHT, "%s", get_mac_address().substr(6,11).c_str());
234+
on_page_change:
235+
to: boot
236+
then:
237+
- if:
238+
# Skip the boot page after initial boot
239+
condition:
240+
lambda: 'return id(device_uptime).state > 30;'
241+
then:
242+
- display.page.show_next: oled_display
243+
- component.update: oled_display
244+
212245

213246
button:
214247
# https://github.com/esphome/issues/issues/2444
@@ -220,47 +253,65 @@ button:
220253
- senseair.background_calibration: senseair_s8
221254
- delay: 70s
222255
- senseair.background_calibration_result: senseair_s8
256+
223257
- platform: template
224258
name: SenseAir S8 Enable Automatic Calibration
225259
id: senseair_s8_enable_calibrate_button
226260
on_press:
227261
then:
228262
- senseair.abc_enable: senseair_s8
263+
229264
- platform: template
230265
name: SenseAir S8 Disable Automatic Calibration
231266
id: senseair_s8_disable_calibrate_button
232267
on_press:
233268
then:
234269
- senseair.abc_disable: senseair_s8
235270

236-
http_request:
237-
# Used to support POST request to send data to AirGradient
238-
# https://esphome.io/components/http_request.html
239-
271+
- platform: template
272+
name: SenseAir S8 Show Calibration Interval
273+
id: senseair_s8_show_calibrate_interval
274+
on_press:
275+
then:
276+
- senseair.abc_get_period: senseair_s8
240277

241278
interval:
242-
- interval: 3s
243-
# Cycle through page on display
279+
- interval: 5s
280+
# Automatically switch to the next page every five seconds
244281
then:
245-
- display.page.show_next: oled_display
246-
- component.update: oled_display
282+
- if:
283+
# Show boot screen for first 10 seconds with serial number and config version
284+
condition:
285+
lambda: 'return id(device_uptime).state < 10;'
286+
then:
287+
- display.page.show: boot
288+
- lambda: id(device_uptime).set_update_interval(1);
289+
else:
290+
# Change page on display
291+
- display.page.show_next: oled_display
292+
- component.update: oled_display
247293

248-
- interval: 300s
294+
- interval: 2.5min
249295
# Send data to AirGradient API server
250296
then:
251-
- http_request.post:
252-
# AirGradient URL with the last 3 bytes of the MAC address in Hex format all lower case
253-
url: !lambda |-
254-
return "http://hw.airgradient.com/sensors/airgradient:" + get_mac_address().substr(6,11) + "/measures";
255-
headers:
256-
Content-Type: application/json
257-
# "!lambda return to_string(id(pm_2_5).state);" Converts sensor output from double to string
258-
json:
259-
wifi: !lambda return to_string(id(wifi_dbm).state);
260-
pm01: !lambda return to_string(id(pm_1_0).state);
261-
pm02: !lambda return to_string(id(pm_2_5).state);
262-
pm10: !lambda return to_string(id(pm_10_0).state);
263-
pm003_count: !lambda return to_string(id(pm_0_3um).state);
264-
rco2: !lambda return to_string(id(co2).state);
265-
atmp: !lambda return to_string(id(temp).state);
266-
rhum: !lambda return to_string(id(humidity).state);
297+
if:
298+
condition:
299+
switch.is_on: upload_airgradient
300+
then:
301+
- http_request.post:
302+
# https://api.airgradient.com/public/docs/api/v1/
303+
# AirGradient URL with the MAC address all lower case
304+
url: !lambda |-
305+
return "http://hw.airgradient.com/sensors/airgradient:" + get_mac_address().substr(6,11) + "/measures";
306+
headers:
307+
Content-Type: application/json
308+
# "!lambda return to_string(id(pm_2_5).state);" Converts sensor output from double to string
309+
json:
310+
wifi: !lambda return to_string(id(wifi_dbm).state);
311+
pm01: !lambda return to_string(id(pm_1_0).state);
312+
pm02: !lambda return to_string(id(pm_2_5).state);
313+
pm10: !lambda return to_string(id(pm_10_0).state);
314+
pm003_count: !lambda return to_string(id(pm_0_3um).state);
315+
rco2: !lambda return to_string(id(co2).state);
316+
atmp: !lambda return to_string(id(temp).state);
317+
rhum: !lambda return to_string(id(humidity).state);

airgradient-one.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -508,7 +508,6 @@ interval:
508508
- display.page.show_next: oled_display
509509
- component.update: oled_display
510510

511-
512511
- interval: 2.5min
513512
# Send data to AirGradient API server
514513
then:

0 commit comments

Comments
 (0)