Skip to content

Commit ba36baf

Browse files
author
brentru
committed
add AQI code, non-internet-connected, no averaging over hours
1 parent 342671c commit ba36baf

File tree

1 file changed

+108
-0
lines changed

1 file changed

+108
-0
lines changed

Adafruit_IO_Air_Quality/code.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import time
2+
import board
3+
import busio
4+
from digitalio import DigitalInOut, Direction, Pull
5+
from simpleio import map_range
6+
import adafruit_pm25
7+
import adafruit_bme280
8+
9+
10+
# Connect to a PM2.5 sensor over UART
11+
uart = busio.UART(board.TX, board.RX, baudrate=9600)
12+
pm25 = adafruit_pm25.PM25_UART(uart)
13+
14+
# Connect to a BME280 sensor over I2C
15+
i2c = busio.I2C(board.SCL, board.SDA)
16+
bme280 = adafruit_bme280.Adafruit_BME280_I2C(i2c)
17+
18+
def calculate_aqi(pm_sensor_reading):
19+
"""Returns a calculated air quality index (AQI)
20+
and category as a tuple.
21+
NOTE: The AQI returned by this function should ideally be measured
22+
using the 24-hour concentration average. Calculating a AQI without
23+
averaging will result in higher AQI values than expected.
24+
:param float pm_sensor_reading: Particulate matter sensor value.
25+
26+
"""
27+
# Check sensor reading using EPA breakpoint (Clow-Chigh)
28+
if 0.0 <= pm_sensor_reading <= 12.0:
29+
# AQI calculation using EPA breakpoints (Ilow-IHigh)
30+
aqi = map_range(int(pm_sensor_reading), 0, 12, 0, 50)
31+
aqi_condition = "Good"
32+
elif 12.1 <= pm_sensor_reading <= 35.4:
33+
aqi = map_range(int(pm_sensor_reading), 12, 35, 51, 100)
34+
aqi_condition = "Moderate"
35+
elif 35.5 <= pm_sensor_reading <= 55.4:
36+
aqi = map_range(int(pm_sensor_reading), 36, 55, 101, 150)
37+
aqi_condition = "Unhealthy for Sensitive Groups"
38+
elif 55.5 <= pm_sensor_reading <= 150.4:
39+
aqi = map_range(int(pm_sensor_reading), 56, 150, 151, 200)
40+
aqi_condition = "Unhealthy"
41+
elif 150.5 <= pm_sensor_reading <= 250.4:
42+
aqi = map_range(int(pm_sensor_reading), 151, 250, 201, 300)
43+
aqi_condition = "Very Unhealthy"
44+
elif 250.5 <= pm_sensor_reading <= 350.4:
45+
aqi = map_range(int(pm_sensor_reading), 251, 350, 301, 400)
46+
aqi_condition = "Hazardous"
47+
elif 350.5 <= pm_sensor_reading <= 500.4:
48+
aqi = map_range(int(pm_sensor_reading), 351, 500, 401, 500)
49+
aqi_condition = "Hazardous"
50+
else:
51+
print("Invalid PM2.5 concentration")
52+
aqi = -1
53+
aqi_condition = None
54+
return aqi, aqi_condition
55+
56+
def sample_aq_sensor():
57+
"""Samples PM2.5 sensor
58+
over a 2.3 second sample rate.
59+
60+
"""
61+
aq_reading = 0
62+
aq_samples = []
63+
64+
# initial timestamp
65+
time_start = time.monotonic()
66+
# sample pm2.5 sensor over 2.3 sec sample rate
67+
while (time.monotonic() - time_start <= 2.3):
68+
try:
69+
aqdata = pm25.read()
70+
aq_samples.append(aqdata["particles 25um"])
71+
except RuntimeError:
72+
print("Unable to read from sensor, retrying...")
73+
continue
74+
# pm sensor output rate of 1s
75+
time.sleep(1)
76+
# average sample reading / # samples
77+
for sample in range(len(aq_samples)):
78+
aq_reading += aq_samples[sample]
79+
aq_reading = aq_reading / len(aq_samples)
80+
aq_samples.clear()
81+
return aq_reading
82+
83+
def read_bme280(is_celsius=False):
84+
"""Returns temperature and humidity
85+
from BME280 environmental sensor, as a tuple.
86+
87+
:param bool is_celsius: Returns temperature in degrees celsius
88+
if True, otherwise fahrenheit.
89+
"""
90+
humidity = bme280.humidity
91+
temperature = bme280.temperature
92+
if not is_celsius:
93+
temperature = temperature * 1.8 + 32
94+
return temperature, humidity
95+
96+
97+
98+
while True:
99+
# TODO: Sample every 10min.
100+
# Air Quality
101+
aqi_reading = sample_aq_sensor()
102+
aqi_index, aqi_condition = calculate_aqi(aqi_reading)
103+
print("AQI: %d"%aqi_index)
104+
print("Condition: %s"%aqi_condition)
105+
# temp and humidity
106+
temperature, humidity = read_bme280()
107+
print("Temperature: %0.1f F" % temperature)
108+
print("Humidity: %0.1f %%" % humidity)

0 commit comments

Comments
 (0)