11---
2- # Instructions: https://esphome.io/guides/getting_started_hassio.html
2+ # Reference for substitutions: https://github.com/ajfriesen/ESPHome-AirGradient/blob/main/air-gradient-pro-diy.yaml
3+ substitutions :
4+ devicename : " ag-basic"
5+ upper_devicename : " AG Basic"
6+
7+ # Instructions: https://esphome.io/guides/getting_started_hassio.html
38esphome :
4- name : airgradient
5- platform : ESP8266
9+ name : " ${devicename}"
10+ friendly_name : " ${upper_devicename}"
11+ name_add_mac_suffix : true # Set to false if you don't want part of the MAC address in the name
12+
13+ esp8266 :
614 board : d1_mini
715
816# Enable logging
2432 ap : {}
2533captive_portal :
2634
27- # Configuration for AirGradient DIY v2 device
28- # https://www.esphome-devices.com/devices/AirGradient-DIY/
2935
3036uart :
3137 # https://esphome.io/components/uart.html#uart
3541 id : senseair_s8_uart
3642
3743 - rx_pin : D5
44+ tx_pin : D6
3845 baud_rate : 9600
3946 id : pms5003_uart
4047
@@ -44,62 +51,105 @@ i2c:
4451
4552sensor :
4653 - platform : pmsx003
47- # https://esphome.io/components/sensor/pmsx003.html?highlight=pms5003
54+ # https://esphome.io/components/sensor/pmsx003.html
4855 type : PMSX003
49- pm_1_0 :
50- name : " Particulate Matter <1.0µm Concentration"
51- id : pm1
52- filters :
53- - sliding_window_moving_average :
54- window_size : 30
55- send_every : 30
5656 pm_2_5 :
57- name : " Particulate Matter <2.5µm Concentration"
58- id : pm2
59- filters :
60- - sliding_window_moving_average :
61- window_size : 30
62- send_every : 30
57+ name : " PM <2.5µm Concentration"
58+ 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+ }
87+ pm_1_0 :
88+ name : " PM <1.0µm Concentration"
89+ id : pm_1_0
6390 pm_10_0 :
64- name : " Particulate Matter <10.0µm Concentration"
65- id : pm10
66- filters :
67- - sliding_window_moving_average :
68- window_size : 30
69- send_every : 30
91+ name : " PM <10.0µm Concentration"
92+ id : pm_10_0
93+ pm_0_3um :
94+ name : " PM >0.3µm Count "
95+ id : pm_0_3um
96+ update_interval : 2min
7097 uart_id : pms5003_uart
7198
99+ - platform : template
100+ name : " PM <2.5 AQI"
101+ unit_of_measurement : " AQI"
102+ icon : " mdi:air-filter"
103+ accuracy_decimals : 0
104+ id : pm_2_5_aqi
72105
73106 - platform : senseair
74107 # https://esphome.io/components/sensor/senseair.html
75108 co2 :
76109 name : " SenseAir S8 CO2 Value"
77110 id : co2
111+ filters :
112+ - skip_initial : 2
113+ - clamp :
114+ min_value : 400 # 419 as of 2023-06 https://gml.noaa.gov/ccgg/trends/global.html
78115 update_interval : 30s
116+ id : senseair_s8
79117 uart_id : senseair_s8_uart
80118
119+ # - platform: sht4x
120+ # # SHT41 https://esphome.io/components/sensor/sht4x.html
121+ # temperature:
122+ # name: "Temperature"
123+ # id: temp
124+ # humidity:
125+ # name: "Humidity"
126+ # id: humidity
127+ # address: 0x44
81128
82129 - platform : sht3xd
83- # https://esphome.io/components/sensor/sht3xd.html?highlight=sht31
130+ # SHT30 https://esphome.io/components/sensor/sht3xd.html
84131 temperature :
85132 name : " Temperature"
86133 id : temp
87134 humidity :
88135 name : " Humidity"
89136 id : humidity
90137 address : 0x44
91- update_interval : 30s
92-
138+ heater_enabled : false # Only enable if in conditions that may have high condensation
93139
94140 - platform : wifi_signal
95141 name : " WiFi Signal"
96- update_interval : 30s
97- id : airgradient_wifi_signal
142+ id : wifi_dbm
143+ update_interval : 60s
98144
145+ - platform : uptime
146+ name : Uptime Sensor
147+ id : device_uptime
148+ update_interval : 10s
99149
100150 # - platform: sgp30
101151 # # https://esphome.io/components/sensor/sgp30.html
102- # # Unable to get to work when OLED shield is connected
152+ # # Unable to get to work when OLED shield is also connected
103153 # eco2:
104154 # name: "eCO2"
105155 # id: eco2
@@ -110,7 +160,9 @@ sensor:
110160 # accuracy_decimals: 1
111161 # update_interval: 30s
112162
113-
163+ - platform : uptime
164+ name : Uptime Sensor
165+ id : airgradient_basement_uptime
114166
115167font :
116168 # Font to use on the display
@@ -132,7 +184,7 @@ display:
132184 - id : display_pm2
133185 lambda : |-
134186 it.print(0, 0, id(font1), "PM2");
135- it.printf(64, 24, id(font1), TextAlign::TOP_RIGHT, "%.0f",id(pm2 ).state);
187+ it.printf(64, 24, id(font1), TextAlign::TOP_RIGHT, "%.0f",id(pm_2_5 ).state);
136188 - id : display_co2
137189 lambda : |-
138190 it.print(0, 0, id(font1), "CO2");
@@ -158,6 +210,28 @@ display:
158210 # it.print(0, 0, id(font1), "TVOC");
159211 # it.printf(64, 24, id(font1), TextAlign::TOP_RIGHT, "%.0f",id(tvoc).state);
160212
213+ button :
214+ # https://github.com/esphome/issues/issues/2444
215+ - platform : template
216+ name : SenseAir S8 Calibration
217+ id : senseair_s8_calibrate_button
218+ on_press :
219+ then :
220+ - senseair.background_calibration : senseair_s8
221+ - delay : 70s
222+ - senseair.background_calibration_result : senseair_s8
223+ - platform : template
224+ name : SenseAir S8 Enable Automatic Calibration
225+ id : senseair_s8_enable_calibrate_button
226+ on_press :
227+ then :
228+ - senseair.abc_enable : senseair_s8
229+ - platform : template
230+ name : SenseAir S8 Disable Automatic Calibration
231+ id : senseair_s8_disable_calibrate_button
232+ on_press :
233+ then :
234+ - senseair.abc_disable : senseair_s8
161235
162236http_request :
163237 # Used to support POST request to send data to AirGradient
@@ -171,7 +245,7 @@ interval:
171245 - display.page.show_next : oled_display
172246 - component.update : oled_display
173247
174- - interval : 300s # 5 minutes to avoid overloading API
248+ - interval : 300s
175249 # Send data to AirGradient API server
176250 then :
177251 - http_request.post :
@@ -180,12 +254,13 @@ interval:
180254 return "http://hw.airgradient.com/sensors/airgradient:" + get_mac_address().substr(6,11) + "/measures";
181255 headers :
182256 Content-Type : application/json
183- # "!lambda return to_string(id(pm2 ).state);" Converts sensor output from double to string
257+ # "!lambda return to_string(id(pm_2_5 ).state);" Converts sensor output from double to string
184258 json :
185- wifi : id(airgradient_wifi_signal).state
186- pm02 : !lambda return to_string(id(pm2).state);
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);
187264 rco2 : !lambda return to_string(id(co2).state);
188265 atmp : !lambda return to_string(id(temp).state);
189266 rhum : !lambda return to_string(id(humidity).state);
190- # tvoc: !lambda return to_string(id(tvoc).state);
191- verify_ssl : false
0 commit comments