Skip to content

Commit e34259a

Browse files
authored
Add support for Open Air Outdoor model O-PPT1 (#22)
* Initial commit for o-ppt1 model * Add compiled binary * Enable temp and humidity * Add temp/humidity correction for Outdoor * Add temp/hum compensation to second PMS5003T
1 parent ec3449e commit e34259a

7 files changed

Lines changed: 282 additions & 9 deletions

airgradient-open-air-o-1ppt.bin

1.1 MB
Binary file not shown.

airgradient-open-air-o-1ppt.yaml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# AirGradient Open Air Outdoor Monitor with dual PMS5003T sensors
2+
# Model: O-1PPT
3+
# https://www.airgradient.com/open-airgradient/instructions/overview/
4+
5+
substitutions:
6+
name: "ag-open-air-o-1ppt"
7+
friendly_name: "AG Open Air O-1PPT"
8+
config_version: 2.0.1
9+
name_add_mac_suffix: "false" # Must have quotes around value
10+
11+
# Enable Home Assistant API
12+
api: # Add encryption key as desired
13+
14+
ota: # Add password as desired
15+
16+
wifi:
17+
# Enable fallback hotspot (captive portal) in case wifi connection fails
18+
ap:
19+
20+
dashboard_import:
21+
package_import_url: github://MallocArray/airgradient_esphome/airgradient-open-air-o-1ppt.yaml
22+
import_full_config: false
23+
24+
packages:
25+
board: github://MallocArray/airgradient_esphome/packages/airgradient_esp32-c3_board.yaml
26+
pm_2.5: github://MallocArray/airgradient_esphome/packages/sensor_pms5003t.yaml
27+
pm_2.5_2: github://MallocArray/airgradient_esphome/packages/sensor_pms5003t_2.yaml
28+
tvoc: github://MallocArray/airgradient_esphome/packages/sensor_sgp41.yaml
29+
airgradient_api: github://MallocArray/airgradient_esphome/packages/airgradient_api_esp32-c3_dual_pms5003t.yaml
30+
hardware_watchdog: github://MallocArray/airgradient_esphome/packages/watchdog.yaml
31+
wifi: github://MallocArray/airgradient_esphome/packages/sensor_wifi.yaml
32+
uptime: github://MallocArray/airgradient_esphome/packages/sensor_uptime.yaml
33+
safe_mode: github://MallocArray/airgradient_esphome/packages/switch_safe_mode.yaml
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Used with the Open Air O-PPT1 with dual PMS5003T sensors
2+
interval:
3+
- interval: 2.5min
4+
# Send data to AirGradient API server
5+
then:
6+
if:
7+
condition:
8+
switch.is_on: upload_airgradient
9+
then:
10+
- http_request.post:
11+
# https://api.airgradient.com/public/docs/api/v1/
12+
# AirGradient URL with the MAC address all lower case
13+
url: !lambda |-
14+
return "http://hw.airgradient.com/sensors/airgradient:" + get_mac_address() + "/measures";
15+
headers:
16+
Content-Type: application/json
17+
# "!lambda return to_string(id(pm_2_5).state);" Converts sensor output from double to string
18+
# Method borrowed from ajfriesen
19+
# https://github.com/ajfriesen/ESPHome-AirGradient/blob/main/air-gradient-open-air.yaml
20+
# https://arduinojson.org/v7/assistant
21+
body: !lambda |
22+
String jsonString;
23+
StaticJsonDocument<1024> doc;
24+
25+
doc["wifi"] = id(wifi_dbm).state;
26+
27+
doc["pm01"] = to_string(id(pm_1_0_avg).state);
28+
doc["pm02"] = to_string(id(pm_2_5_avg).state);
29+
doc["pm10"] = to_string(id(pm_10_0_avg).state);
30+
doc["pm003_count"] = to_string(id(pm_0_3um_avg).state);
31+
doc["atmp"] = to_string(id(temp_avg).state);
32+
doc["rhum"] = to_string(id(humidity_avg).state);
33+
34+
// We don't have access to the boot loop counter in esphome, so just send a 1
35+
// See: https://github.com/esphome/issues/issues/1539
36+
doc["boot"] = "1";
37+
38+
JsonObject channels = doc.createNestedObject("channels");
39+
40+
JsonObject channels_1 = channels.createNestedObject("1");
41+
channels_1["pm01"] = to_string(id(pm_1_0).state);
42+
channels_1["pm02"] = to_string(id(pm_2_5).state);
43+
channels_1["pm10"] = to_string(id(pm_10_0).state);
44+
channels_1["pm003_count"] = to_string(id(pm_0_3um).state);
45+
channels_1["atmp"] = to_string(id(temp).state);
46+
channels_1["rhum"] = to_string(id(humidity).state);
47+
48+
JsonObject channels_2 = channels.createNestedObject("2");
49+
channels_2["pm01"] = to_string(id(pm_1_0_2).state);
50+
channels_2["pm02"] = to_string(id(pm_2_5_2).state);
51+
channels_2["pm10"] = to_string(id(pm_10_0_2).state);
52+
channels_2["pm003_count"] = to_string(id(pm_0_3um_2).state);
53+
channels_2["atmp"] = to_string(id(temp_2).state);
54+
channels_2["rhum"] = to_string(id(humidity_2).state);
55+
56+
// Serialize the JSON document into the string
57+
serializeJson(doc, jsonString);
58+
59+
// Convert String to std::string
60+
std::string stdJsonString(jsonString.c_str());
61+
62+
return stdJsonString;
63+
64+
65+
switch:
66+
- platform: template
67+
name: "Upload to AirGradient Dashboard"
68+
id: upload_airgradient
69+
restore_mode: RESTORE_DEFAULT_ON
70+
optimistic: True
71+
72+
esphome:
73+
on_boot:
74+
priority: 200 # Network connections setup
75+
then:
76+
if:
77+
condition:
78+
switch.is_on: upload_airgradient
79+
then:
80+
- http_request.post:
81+
# Return wifi signal -50 as soon as device boots to show activity on AirGradient Dashboard site
82+
# Using -50 instead of actual value as the wifi_signal sensor has not reported a value at this point in boot process
83+
url: !lambda |-
84+
return "http://hw.airgradient.com/sensors/airgradient:" + get_mac_address() + "/measures";
85+
headers:
86+
Content-Type: application/json
87+
json:
88+
wifi: !lambda return to_string(-50);
89+
90+
http_request:
91+
# Used to support POST request to send data to AirGradient
92+
# https://esphome.io/components/http_request.html

packages/sensor_pms5003t.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,23 @@ sensor:
77
pm_2_5:
88
name: "PM 2.5"
99
id: pm_2_5
10-
device_class: pm25 # Added to report properly to Homekit
10+
device_class: pm25 # Added to report properly to HomeKit
1111
filters:
1212
- sliding_window_moving_average:
1313
window_size: 30
1414
send_every: 30
1515
pm_1_0:
1616
name: "PM 1.0"
1717
id: pm_1_0
18-
device_class: pm1 # Added to report properly to Homekit
18+
device_class: pm1 # Added to report properly to HomeKit
1919
filters:
2020
- sliding_window_moving_average:
2121
window_size: 30
2222
send_every: 30
2323
pm_10_0:
2424
name: "PM 10.0"
2525
id: pm_10_0
26-
device_class: pm10 # Added to report properly to Homekit
26+
device_class: pm10 # Added to report properly to HomeKit
2727
filters:
2828
- sliding_window_moving_average:
2929
window_size: 30

packages/sensor_pms5003t_2.yaml

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
# Used for a second PMS5003T in the same device, such as the Open Air O-1PPT
2+
sensor:
3+
- platform: pmsx003
4+
# Default interval of updating every second, but using an average over the last 30 seconds/readings
5+
# PMS5003T with temperature and humidity https://esphome.io/components/sensor/pmsx003.html
6+
type: PMS5003T # ! Don't forget to change this back to T
7+
uart_id: senseair_s8_uart
8+
pm_2_5:
9+
name: "PM 2.5 (2)"
10+
id: pm_2_5_2
11+
device_class: pm25 # Added to report properly to HomeKit
12+
filters:
13+
- sliding_window_moving_average:
14+
window_size: 30
15+
send_every: 30
16+
pm_1_0:
17+
name: "PM 1.0 (2)"
18+
id: pm_1_0_2
19+
device_class: pm1 # Added to report properly to HomeKit
20+
filters:
21+
- sliding_window_moving_average:
22+
window_size: 30
23+
send_every: 30
24+
pm_10_0:
25+
name: "PM 10.0 (2)"
26+
id: pm_10_0_2
27+
device_class: pm10 # Added to report properly to HomeKit
28+
filters:
29+
- sliding_window_moving_average:
30+
window_size: 30
31+
send_every: 30
32+
pm_0_3um:
33+
name: "PM 0.3 (2)"
34+
id: pm_0_3um_2
35+
filters:
36+
- sliding_window_moving_average:
37+
window_size: 30
38+
send_every: 30
39+
temperature:
40+
name: "Temperature (2)"
41+
id: temp_2
42+
filters:
43+
# https://www.airgradient.com/blog/slr-temperature-example/
44+
# - lambda: return (x - 4.55) / 0.83; # Once the negative number issue fix is merged in https://github.com/esphome/issues/issues/3814
45+
- lambda: !lambda |-
46+
// Fix negative values for now
47+
if (x > 6000) {
48+
return (x - 6553.6 - 4.55) / 0.83;
49+
}
50+
return (x - 4.55) / 0.83;
51+
- sliding_window_moving_average:
52+
window_size: 30
53+
send_every: 30
54+
humidity:
55+
name: "Humidity (2)"
56+
id: humidity_2
57+
filters:
58+
# https://www.airgradient.com/blog/linear-regression-improves-sensor-accuracy/
59+
# - lambda: return x * 1.500574 - 4.76;
60+
# https://forum.airgradient.com/t/open-air-outdoor-air-quality-monitor-humidity-readings-are-off/1171/2
61+
- lambda: return x * 1.3921 - 1.0245;
62+
- sliding_window_moving_average:
63+
window_size: 30
64+
send_every: 30
65+
66+
# Average the readings from both sensors to return a value
67+
- platform: template
68+
id: pm_2_5_avg
69+
name: "PM 2.5 (Average)"
70+
lambda: return (id(pm_2_5).state + id(pm_2_5_2).state) / 2.0;
71+
device_class: pm25
72+
unit_of_measurement: µg/m³
73+
icon: mdi:chemical-weapon
74+
accuracy_decimals: 0
75+
state_class: measurement
76+
update_interval: 1s
77+
filters:
78+
- sliding_window_moving_average:
79+
window_size: 30
80+
81+
- platform: template
82+
id: pm_1_0_avg
83+
name: "PM 1.0 (Average)"
84+
lambda: return (id(pm_1_0).state + id(pm_1_0_2).state) / 2.0;
85+
device_class: pm1
86+
unit_of_measurement: µg/m³
87+
icon: mdi:chemical-weapon
88+
accuracy_decimals: 0
89+
state_class: measurement
90+
update_interval: 1s
91+
filters:
92+
- sliding_window_moving_average:
93+
window_size: 30
94+
95+
- platform: template
96+
id: pm_10_0_avg
97+
name: "PM 10.0 (Average)"
98+
lambda: return (id(pm_10_0).state + id(pm_10_0_2).state) / 2.0;
99+
device_class: pm10
100+
unit_of_measurement: µg/m³
101+
icon: mdi:chemical-weapon
102+
accuracy_decimals: 0
103+
state_class: measurement
104+
update_interval: 1s
105+
filters:
106+
- sliding_window_moving_average:
107+
window_size: 30
108+
109+
- platform: template
110+
id: pm_0_3um_avg
111+
name: "PM 0.3 (Average)"
112+
lambda: return (id(pm_0_3um).state + id(pm_0_3um_2).state) / 2.0;
113+
unit_of_measurement: /dL
114+
icon: mdi:chemical-weapon
115+
accuracy_decimals: 0
116+
state_class: measurement
117+
update_interval: 1s
118+
filters:
119+
- sliding_window_moving_average:
120+
window_size: 30
121+
122+
- platform: template
123+
id: temp_avg
124+
name: "Temperature (Average)"
125+
lambda: return (id(temp).state + id(temp_2).state) / 2.0;
126+
unit_of_measurement: °C
127+
accuracy_decimals: 1
128+
device_class: temperature
129+
state_class: measurement
130+
icon: mdi:thermometer
131+
update_interval: 1s
132+
filters:
133+
- sliding_window_moving_average:
134+
window_size: 30
135+
136+
- platform: template
137+
id: humidity_avg
138+
name: "Humidity (Average)"
139+
lambda: return (id(humidity).state + id(humidity_2).state) / 2.0;
140+
unit_of_measurement: '%'
141+
accuracy_decimals: 1
142+
device_class: humidity
143+
state_class: measurement
144+
icon: mdi:water-percent
145+
update_interval: 1s
146+
filters:
147+
- sliding_window_moving_average:
148+
window_size: 30

packages/sensor_pms5003t_extended_life.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@ sensor:
77
pm_2_5:
88
name: "PM 2.5"
99
id: pm_2_5
10-
device_class: pm25 # Added to report properly to Homekit
10+
device_class: pm25 # Added to report properly to HomeKit
1111
pm_1_0:
1212
name: "PM 1.0"
1313
id: pm_1_0
14-
device_class: pm1 # Added to report properly to Homekit
14+
device_class: pm1 # Added to report properly to HomeKit
1515
pm_10_0:
1616
name: "PM 10.0"
1717
id: pm_10_0
18-
device_class: pm10 # Added to report properly to Homekit
18+
device_class: pm10 # Added to report properly to HomeKit
1919
pm_0_3um:
2020
name: "PM 0.3"
2121
id: pm_0_3um

packages/sensor_pms5003t_uncorrected.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,23 @@ sensor:
77
pm_2_5:
88
name: "PM 2.5"
99
id: pm_2_5
10-
device_class: pm25 # Added to report properly to Homekit
10+
device_class: pm25 # Added to report properly to HomeKit
1111
filters:
1212
- sliding_window_moving_average:
1313
window_size: 30
1414
send_every: 30
1515
pm_1_0:
1616
name: "PM 1.0"
1717
id: pm_1_0
18-
device_class: pm1 # Added to report properly to Homekit
18+
device_class: pm1 # Added to report properly to HomeKit
1919
filters:
2020
- sliding_window_moving_average:
2121
window_size: 30
2222
send_every: 30
2323
pm_10_0:
2424
name: "PM 10.0"
2525
id: pm_10_0
26-
device_class: pm10 # Added to report properly to Homekit
26+
device_class: pm10 # Added to report properly to HomeKit
2727
filters:
2828
- sliding_window_moving_average:
2929
window_size: 30

0 commit comments

Comments
 (0)