|
| 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 |
0 commit comments