Skip to content

Commit aa96aad

Browse files
committed
Add IoT environmental sensor code
1 parent 9358ed7 commit aa96aad

5 files changed

Lines changed: 521 additions & 0 deletions

File tree

IoT_Environment_Sensor/aio.py

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
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 board
17+
import busio
18+
import gc
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)
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+
def refresh_local_time(self):
98+
# pylint: disable=line-too-long
99+
"""Fetch and "set" the local time of this microcontroller to the local time at the location, using an internet time API.
100+
Copied from adafruit_pyportal
101+
:param str location: Your city and country, e.g. ``"New York, US"``.
102+
"""
103+
# pylint: enable=line-too-long
104+
api_url = None
105+
try:
106+
aio_username = secrets['aio_username']
107+
aio_key = secrets['aio_key']
108+
except KeyError:
109+
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
110+
111+
location = secrets['timezone']
112+
if location:
113+
logger.debug('Getting time for timezone %s', location)
114+
api_url = (TIME_SERVICE + "&tz=%s") % (aio_username, aio_key, location)
115+
else: # we'll try to figure it out from the IP address
116+
logger.debug("Getting time from IP address")
117+
api_url = TIME_SERVICE % (aio_username, aio_key)
118+
api_url += TIME_SERVICE_STRFTIME
119+
logger.debug('Requesting time from %s', api_url)
120+
try:
121+
self.connect()
122+
response = requests.get(api_url)
123+
logger.debug('Time reply: %s', response.text)
124+
times = response.text.split(' ')
125+
the_date = times[0]
126+
the_time = times[1]
127+
year_day = int(times[2])
128+
week_day = int(times[3])
129+
is_dst = None # no way to know yet
130+
except KeyError:
131+
raise KeyError("Was unable to lookup the time, try setting secrets['timezone'] according to http://worldtimeapi.org/timezones") # pylint: disable=line-too-long
132+
year, month, mday = [int(x) for x in the_date.split('-')]
133+
the_time = the_time.split('.')[0]
134+
hours, minutes, seconds = [int(x) for x in the_time.split(':')]
135+
now = time.struct_time((year, month, mday, hours, minutes, seconds, week_day, year_day,
136+
is_dst))
137+
rtc.RTC().datetime = now
138+
logger.debug('Fetched time: %s', str(now))
139+
140+
# now clean up
141+
response.close()
142+
response = None
143+
gc.collect()
144+
return now
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
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, self._particles_10um, \
71+
self._particles_25um, self._particles_50um, self._particles_100um, skip, checksum = frame
72+
73+
check = sum(self._buffer[0:30])
74+
75+
if check != checksum:
76+
self._buffer = []
77+
return False
78+
79+
return True
80+
81+
@property
82+
def pm10_standard(self):
83+
return self._pm10_standard
84+
85+
@property
86+
def pm25_standard(self):
87+
return self._pm25_standard
88+
89+
@property
90+
def pm100_standard(self):
91+
return self._pm100_standard
92+
93+
@property
94+
def pm10_env(self):
95+
return self._pm10_env
96+
97+
@property
98+
def pm25_env(self):
99+
return self._pm25_env
100+
101+
@property
102+
def pm100_env(self):
103+
return self._pm100_env
104+
105+
@property
106+
def particles_03um(self):
107+
return self._particles_03um
108+
109+
@property
110+
def particles_05um(self):
111+
return self._particles_05um
112+
113+
@property
114+
def particles_10um(self):
115+
return self._particles_10um
116+
117+
@property
118+
def particles_25um(self):
119+
return self._particles_25um
120+
121+
@property
122+
def particles_50um(self):
123+
return self._particles_50um
124+
125+
@property
126+
def particles_100um(self):
127+
return self._particles_100um

0 commit comments

Comments
 (0)