Skip to content

Commit 9a1900d

Browse files
authored
Merge pull request #666 from dastels/iot_air_sensor
Add IoT environmental sensor code
2 parents 9358ed7 + 74aa3b9 commit 9a1900d

5 files changed

Lines changed: 526 additions & 0 deletions

File tree

IoT_Environment_Sensor/aio.py

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
"""
2+
IoT environmental sensor node.
3+
4+
Adafruit invests time and resources providing this open source code.
5+
Please support Adafruit and open source hardware by purchasing
6+
products from Adafruit!
7+
8+
Written by Dave Astels for Adafruit Industries
9+
Copyright (c) 2019 Adafruit Industries
10+
Licensed under the MIT license.
11+
12+
All text above must be included in any redistribution.
13+
"""
14+
15+
import time
16+
import gc
17+
import board
18+
import busio
19+
from digitalio import DigitalInOut
20+
from adafruit_esp32spi import adafruit_esp32spi
21+
import adafruit_esp32spi.adafruit_esp32spi_requests as requests
22+
import adafruit_logging as logging
23+
import rtc
24+
25+
logger = logging.getLogger('main')
26+
27+
TIME_SERVICE = "https://io.adafruit.com/api/v2/%s/integrations/time/strftime?x-aio-key=%s"
28+
# our strftime is %Y-%m-%d %H:%M:%S.%L %j %u %z %Z see http://strftime.net/ for decoding details
29+
# See https://apidock.com/ruby/DateTime/strftime for full options
30+
TIME_SERVICE_STRFTIME = '&fmt=%25Y-%25m-%25d+%25H%3A%25M%3A%25S.%25L+%25j+%25u+%25z+%25Z'
31+
32+
33+
34+
# Get wifi details and more from a settings.py file
35+
try:
36+
from secrets import secrets
37+
except ImportError:
38+
logger.critical('WiFi settings are kept in settings.py, please add them there!')
39+
raise
40+
41+
class AIO(object):
42+
43+
def __init__(self):
44+
esp32_cs = DigitalInOut(board.D10)
45+
esp32_ready = DigitalInOut(board.D9)
46+
esp32_reset = DigitalInOut(board.D6)
47+
48+
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
49+
self._esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
50+
51+
if self._esp.status == adafruit_esp32spi.WL_IDLE_STATUS:
52+
logger.debug('ESP32 found and in idle mode')
53+
logger.info('Firmware vers. %s', self._esp.firmware_version)
54+
logger.info('MAC addr: %s', ':'.join([hex(i)[2:4] for i in self._esp.MAC_address]))
55+
56+
requests.set_interface(self._esp)
57+
58+
59+
def connect(self):
60+
logger.debug("Connecting...")
61+
while not self._esp.is_connected:
62+
try:
63+
self._esp.connect_AP(secrets['ssid'], secrets['password'])
64+
except RuntimeError as e:
65+
logger.error("could not connect to AP, retrying: %s", e)
66+
continue
67+
68+
def post(self, feed, payload):
69+
api_url = 'https://io.adafruit.com/api/v2/{0}/feeds/{1}/data'.format(secrets['aio_username'], feed)# pylint: disable=line-too-long
70+
logger.info('POSTing to %s', api_url)
71+
logger.info('payload: %s', str(payload))
72+
auth_header = {'X-AIO-KEY':secrets['aio_key']}
73+
self.connect()
74+
r = None
75+
tries = 0
76+
while True:
77+
if tries == 5:
78+
return False
79+
tries += 1
80+
try:
81+
r = requests.post(api_url, headers=auth_header, json=payload)
82+
logger.info('Status: %d', r.status_code)
83+
if r.status_code == 200:
84+
logger.debug('Headers: %s', str(r.headers))
85+
logger.debug('Text: %s', str(r.json()))
86+
else:
87+
logger.debug('Text: %s', str(r.json()))
88+
break
89+
except RuntimeError as err:
90+
logger.error('Error posting: %s', str(err))
91+
logger.info('Resetting and reconnecting')
92+
self._esp.reset()
93+
self.connect()
94+
r.close()
95+
return r.status_code == 200
96+
97+
# pylint:disable=too-many-locals
98+
def refresh_local_time(self):
99+
# pylint: disable=line-too-long
100+
"""Fetch and "set" the local time of this microcontroller to the local time at the location, using an internet time API.
101+
Copied from adafruit_pyportal
102+
:param str location: Your city and country, e.g. ``"New York, US"``.
103+
"""
104+
# pylint: enable=line-too-long
105+
api_url = None
106+
try:
107+
aio_username = secrets['aio_username']
108+
aio_key = secrets['aio_key']
109+
except KeyError:
110+
raise KeyError("\n\nOur time service requires a login/password to rate-limit. Please register for a free adafruit.io account and place the user/key in your secrets file under 'aio_username' and 'aio_key'")# pylint: disable=line-too-long
111+
112+
location = secrets['timezone']
113+
if location:
114+
logger.debug('Getting time for timezone %s', location)
115+
api_url = (TIME_SERVICE + "&tz=%s") % (aio_username, aio_key, location)
116+
else: # we'll try to figure it out from the IP address
117+
logger.debug("Getting time from IP address")
118+
api_url = TIME_SERVICE % (aio_username, aio_key)
119+
api_url += TIME_SERVICE_STRFTIME
120+
logger.debug('Requesting time from %s', api_url)
121+
try:
122+
self.connect()
123+
response = requests.get(api_url)
124+
logger.debug('Time reply: %s', response.text)
125+
times = response.text.split(' ')
126+
the_date = times[0]
127+
the_time = times[1]
128+
year_day = int(times[2])
129+
week_day = int(times[3])
130+
is_dst = None # no way to know yet
131+
except KeyError:
132+
raise KeyError("Was unable to lookup the time, try setting secrets['timezone'] according to http://worldtimeapi.org/timezones") # pylint: disable=line-too-long
133+
year, month, mday = [int(x) for x in the_date.split('-')]
134+
the_time = the_time.split('.')[0]
135+
hours, minutes, seconds = [int(x) for x in the_time.split(':')]
136+
now = time.struct_time((year, month, mday, hours, minutes, seconds, week_day, year_day,
137+
is_dst))
138+
rtc.RTC().datetime = now
139+
logger.debug('Fetched time: %s', str(now))
140+
141+
# now clean up
142+
response.close()
143+
response = None
144+
gc.collect()
145+
return now
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
"""
2+
IoT environmental sensor node.
3+
4+
Adafruit invests time and resources providing this open source code.
5+
Please support Adafruit and open source hardware by purchasing
6+
products from Adafruit!
7+
8+
Written by Dave Astels for Adafruit Industries
9+
Copyright (c) 2019 Adafruit Industries
10+
Licensed under the MIT license.
11+
12+
All text above must be included in any redistribution.
13+
"""
14+
15+
import adafruit_logging as logging
16+
17+
try:
18+
import struct
19+
except ImportError:
20+
import ustruct as struct
21+
22+
logger = logging.getLogger('main')
23+
24+
class AirQualitySensor (object):
25+
26+
def __init__(self, uart):
27+
self._uart = uart
28+
self._buffer = []
29+
self._pm10_standard = 0
30+
self._pm25_standard = 0
31+
self._pm100_standard = 0
32+
self._pm10_env = 0
33+
self._pm25_env = 0
34+
self._pm100_env = 0
35+
self._particles_03um = 0
36+
self._particles_05um = 0
37+
self._particles_10um = 0
38+
self._particles_25um = 0
39+
self._particles_50um = 0
40+
self._particles_100um = 0
41+
42+
def read(self):
43+
data = self._uart.read(32) # read up to 32 bytes
44+
data = list(data)
45+
46+
self._buffer += data
47+
48+
while self._buffer and self._buffer[0] != 0x42:
49+
self._buffer.pop(0)
50+
51+
if len(self._buffer) > 200:
52+
self._buffer = [] # avoid an overrun if all bad data
53+
return False
54+
if len(self._buffer) < 32:
55+
return False
56+
57+
if self._buffer[1] != 0x4d:
58+
self._buffer.pop(0)
59+
return False
60+
61+
frame_len = struct.unpack(">H", bytes(self._buffer[2:4]))[0]
62+
if frame_len != 28:
63+
self._buffer = []
64+
return False
65+
66+
logger.debug('buffer length: %d', len(self._buffer) - 4)
67+
frame = struct.unpack(">HHHHHHHHHHHHHH", bytes(self._buffer[4:32]))
68+
69+
self._pm10_standard, self._pm25_standard, self._pm100_standard, self._pm10_env, \
70+
self._pm25_env, self._pm100_env, self._particles_03um, self._particles_05um, \
71+
self._particles_10um, self._particles_25um, self._particles_50um, \
72+
self._particles_100um, _, checksum = frame
73+
74+
check = sum(self._buffer[0:30])
75+
76+
if check != checksum:
77+
self._buffer = []
78+
return False
79+
80+
return True
81+
82+
@property
83+
def pm10_standard(self):
84+
return self._pm10_standard
85+
86+
@property
87+
def pm25_standard(self):
88+
return self._pm25_standard
89+
90+
@property
91+
def pm100_standard(self):
92+
return self._pm100_standard
93+
94+
@property
95+
def pm10_env(self):
96+
return self._pm10_env
97+
98+
@property
99+
def pm25_env(self):
100+
return self._pm25_env
101+
102+
@property
103+
def pm100_env(self):
104+
return self._pm100_env
105+
106+
@property
107+
def particles_03um(self):
108+
return self._particles_03um
109+
110+
@property
111+
def particles_05um(self):
112+
return self._particles_05um
113+
114+
@property
115+
def particles_10um(self):
116+
return self._particles_10um
117+
118+
@property
119+
def particles_25um(self):
120+
return self._particles_25um
121+
122+
@property
123+
def particles_50um(self):
124+
return self._particles_50um
125+
126+
@property
127+
def particles_100um(self):
128+
return self._particles_100um

0 commit comments

Comments
 (0)