Skip to content

Commit 300f57e

Browse files
authored
Merge pull request adafruit#1268 from brentru/aqi-node-guide
Code: Adafruit IO Air Quality Sensor
2 parents 173f524 + 564f465 commit 300f57e

File tree

1 file changed

+190
-0
lines changed

1 file changed

+190
-0
lines changed

Adafruit_IO_Air_Quality/code.py

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
import time
2+
import board
3+
import busio
4+
from digitalio import DigitalInOut
5+
import neopixel
6+
from adafruit_esp32spi import adafruit_esp32spi, adafruit_esp32spi_wifimanager
7+
from adafruit_io.adafruit_io import IO_HTTP
8+
from simpleio import map_range
9+
10+
import adafruit_pm25
11+
import adafruit_bme280
12+
13+
### Configure Sensor ###
14+
# Return BME280 environmental sensor readings in degrees Celsius
15+
USE_CELSIUS = False
16+
# Interval the sensor publishes to Adafruit IO, in minutes
17+
PUBLISH_INTERVAL = 10
18+
19+
### WiFi ###
20+
21+
# Get wifi details and more from a secrets.py file
22+
try:
23+
from secrets import secrets
24+
except ImportError:
25+
print("WiFi secrets are kept in secrets.py, please add them there!")
26+
raise
27+
28+
# AirLift FeatherWing
29+
esp32_cs = DigitalInOut(board.D13)
30+
esp32_reset = DigitalInOut(board.D12)
31+
esp32_ready = DigitalInOut(board.D11)
32+
33+
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
34+
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
35+
status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
36+
wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light)
37+
38+
# Connect to a PM2.5 sensor over UART
39+
uart = busio.UART(board.TX, board.RX, baudrate=9600)
40+
pm25 = adafruit_pm25.PM25_UART(uart)
41+
42+
# Connect to a BME280 sensor over I2C
43+
i2c = busio.I2C(board.SCL, board.SDA)
44+
bme280 = adafruit_bme280.Adafruit_BME280_I2C(i2c)
45+
46+
### Sensor Functions ###
47+
def calculate_aqi(pm_sensor_reading):
48+
"""Returns a calculated air quality index (AQI)
49+
and category as a tuple.
50+
NOTE: The AQI returned by this function should ideally be measured
51+
using the 24-hour concentration average. Calculating a AQI without
52+
averaging will result in higher AQI values than expected.
53+
:param float pm_sensor_reading: Particulate matter sensor value.
54+
55+
"""
56+
# Check sensor reading using EPA breakpoint (Clow-Chigh)
57+
if 0.0 <= pm_sensor_reading <= 12.0:
58+
# AQI calculation using EPA breakpoints (Ilow-IHigh)
59+
aqi_val = map_range(int(pm_sensor_reading), 0, 12, 0, 50)
60+
aqi_cat = "Good"
61+
elif 12.1 <= pm_sensor_reading <= 35.4:
62+
aqi_val = map_range(int(pm_sensor_reading), 12, 35, 51, 100)
63+
aqi_cat = "Moderate"
64+
elif 35.5 <= pm_sensor_reading <= 55.4:
65+
aqi_val = map_range(int(pm_sensor_reading), 36, 55, 101, 150)
66+
aqi_cat = "Unhealthy for Sensitive Groups"
67+
elif 55.5 <= pm_sensor_reading <= 150.4:
68+
aqi_val = map_range(int(pm_sensor_reading), 56, 150, 151, 200)
69+
aqi_cat = "Unhealthy"
70+
elif 150.5 <= pm_sensor_reading <= 250.4:
71+
aqi_val = map_range(int(pm_sensor_reading), 151, 250, 201, 300)
72+
aqi_cat = "Very Unhealthy"
73+
elif 250.5 <= pm_sensor_reading <= 350.4:
74+
aqi_val = map_range(int(pm_sensor_reading), 251, 350, 301, 400)
75+
aqi_cat = "Hazardous"
76+
elif 350.5 <= pm_sensor_reading <= 500.4:
77+
aqi_val = map_range(int(pm_sensor_reading), 351, 500, 401, 500)
78+
aqi_cat = "Hazardous"
79+
else:
80+
print("Invalid PM2.5 concentration")
81+
aqi_val = -1
82+
aqi_cat = None
83+
return aqi_val, aqi_cat
84+
85+
86+
def sample_aq_sensor():
87+
"""Samples PM2.5 sensor
88+
over a 2.3 second sample rate.
89+
90+
"""
91+
aq_reading = 0
92+
aq_samples = []
93+
94+
# initial timestamp
95+
time_start = time.monotonic()
96+
# sample pm2.5 sensor over 2.3 sec sample rate
97+
while time.monotonic() - time_start <= 2.3:
98+
try:
99+
aqdata = pm25.read()
100+
aq_samples.append(aqdata["pm25 env"])
101+
except RuntimeError:
102+
print("Unable to read from sensor, retrying...")
103+
continue
104+
# pm sensor output rate of 1s
105+
time.sleep(1)
106+
# average sample reading / # samples
107+
for sample in range(len(aq_samples)):
108+
aq_reading += aq_samples[sample]
109+
aq_reading = aq_reading / len(aq_samples)
110+
aq_samples.clear()
111+
return aq_reading
112+
113+
114+
def read_bme280(is_celsius=False):
115+
"""Returns temperature and humidity
116+
from BME280 environmental sensor, as a tuple.
117+
118+
:param bool is_celsius: Returns temperature in degrees celsius
119+
if True, otherwise fahrenheit.
120+
"""
121+
humid = bme280.humidity
122+
temp = bme280.temperature
123+
if not is_celsius:
124+
temp = temp * 1.8 + 32
125+
return temperature, humid
126+
127+
128+
# Create an instance of the Adafruit IO HTTP client
129+
io = IO_HTTP(secrets["aio_user"], secrets["aio_key"], wifi)
130+
131+
# Describes feeds used to hold Adafruit IO data
132+
feed_aqi = io.get_feed("air-quality-sensor.aqi")
133+
feed_aqi_category = io.get_feed("air-quality-sensor.category")
134+
feed_humidity = io.get_feed("air-quality-sensor.humidity")
135+
feed_temperature = io.get_feed("air-quality-sensor.temperature")
136+
137+
# Set up location metadata from secrets.py file
138+
location_metadata = (secrets["latitude"], secrets["longitude"], secrets["elevation"])
139+
140+
elapsed_minutes = 0
141+
prv_mins = 0
142+
143+
while True:
144+
try:
145+
print("Fetching time...")
146+
cur_time = io.receive_time()
147+
print("Time fetched OK!")
148+
# Hourly reset
149+
if cur_time.tm_min == 0:
150+
prv_mins = 0
151+
except (ValueError, RuntimeError) as e:
152+
print("Failed to fetch time, retrying\n", e)
153+
wifi.reset()
154+
wifi.connect()
155+
continue
156+
157+
if cur_time.tm_min >= prv_mins:
158+
print("%d min elapsed.." % elapsed_minutes)
159+
prv_mins = cur_time.tm_min
160+
elapsed_minutes += 1
161+
162+
if elapsed_minutes >= PUBLISH_INTERVAL:
163+
print("Sampling AQI...")
164+
aqi_reading = sample_aq_sensor()
165+
aqi, aqi_category = calculate_aqi(aqi_reading)
166+
print("AQI: %d" % aqi)
167+
print("Category: %s" % aqi_category)
168+
169+
# temp and humidity
170+
print("Sampling environmental sensor...")
171+
temperature, humidity = read_bme280(USE_CELSIUS)
172+
print("Temperature: %0.1f F" % temperature)
173+
print("Humidity: %0.1f %%" % humidity)
174+
175+
# Publish all values to Adafruit IO
176+
print("Publishing to Adafruit IO...")
177+
try:
178+
io.send_data(feed_aqi["key"], str(aqi), location_metadata)
179+
io.send_data(feed_aqi_category["key"], aqi_category)
180+
io.send_data(feed_temperature["key"], str(temperature))
181+
io.send_data(feed_humidity["key"], str(humidity))
182+
print("Published!")
183+
except (ValueError, RuntimeError) as e:
184+
print("Failed to send data to IO, retrying\n", e)
185+
wifi.reset()
186+
wifi.connect()
187+
continue
188+
# Reset timer
189+
elapsed_minutes = 0
190+
time.sleep(30)

0 commit comments

Comments
 (0)