From bd7400b23565705762b8541d011536d8fa300faf Mon Sep 17 00:00:00 2001 From: Jerry Needell Date: Wed, 27 Feb 2019 12:15:50 -0500 Subject: [PATCH 1/3] implement wifimanager --- .../adafruit_espatcontrol_requests.py | 3 +- .../adafruit_espatcontrol_wifimanager.py | 204 ++++++++++++++++ .../adafruit_espatcontrol_wifimanager.py.save | 216 +++++++++++++++++ examples/espatcontrol_aio_post.py | 45 ++-- examples/espatcontrol_cheerlights.py | 84 +++---- examples/espatcontrol_countviewer.py | 2 +- examples/espatcontrol_localtime.py | 88 +++++++ examples/espatcontrol_quoteEPD.py | 226 ------------------ examples/espatcontrol_secrets.py | 4 +- examples/espatcontrol_simpletest.py | 3 + examples/espatcontrol_webclient.py | 14 +- 11 files changed, 582 insertions(+), 307 deletions(-) create mode 100755 adafruit_espatcontrol/adafruit_espatcontrol_wifimanager.py create mode 100755 adafruit_espatcontrol/adafruit_espatcontrol_wifimanager.py.save create mode 100644 examples/espatcontrol_localtime.py delete mode 100644 examples/espatcontrol_quoteEPD.py diff --git a/adafruit_espatcontrol/adafruit_espatcontrol_requests.py b/adafruit_espatcontrol/adafruit_espatcontrol_requests.py index d23af01..4c7ce1a 100644 --- a/adafruit_espatcontrol/adafruit_espatcontrol_requests.py +++ b/adafruit_espatcontrol/adafruit_espatcontrol_requests.py @@ -131,7 +131,6 @@ def request(method, url, data=None, json=None, headers=None, stream=None): sock.write(bytes(data, 'utf-8')) line = sock.readline() - #print(line) line = line.split(None, 2) status = int(line[1]) reason = "" @@ -142,7 +141,6 @@ def request(method, url, data=None, json=None, headers=None, stream=None): if not line or line == b"\r\n": break - #print(line) title, content = line.split(b': ', 1) if title and content: title = str(title.lower(), 'utf-8') @@ -157,6 +155,7 @@ def request(method, url, data=None, json=None, headers=None, stream=None): except OSError: sock.close() + print("how did we get here") raise resp.status_code = status diff --git a/adafruit_espatcontrol/adafruit_espatcontrol_wifimanager.py b/adafruit_espatcontrol/adafruit_espatcontrol_wifimanager.py new file mode 100755 index 0000000..5c0b4d5 --- /dev/null +++ b/adafruit_espatcontrol/adafruit_espatcontrol_wifimanager.py @@ -0,0 +1,204 @@ +# The MIT License (MIT) +# +# Copyright (c) 2019 Melissa LeBlanc-Williams for Adafruit Industries +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +""" +`adafruit_espatcontrol_wifimanager` +================================================================================ + +WiFi Manager for making ESP32 SPI as WiFi much easier + +* Author(s): Melissa LeBlanc-Williams, ladyada +""" + +# pylint: disable=no-name-in-module + +import adafruit_espatcontrol.adafruit_espatcontrol_requests as requests + +class ESPAT_WiFiManager: + """ + A class to help manage the Wifi connection + """ + def __init__(self, esp, secrets, status_pixel, attempts=2): + """ + :param ESP_SPIcontrol esp: The ESP object we are using + :param dict secrets: The WiFi and Adafruit IO secrets dict (See examples) + :param status_pixel: (Optional) The pixel device - A NeoPixel or DotStar (default=None) + :type status_pixel: NeoPixel or DotStar + :param int attempts: (Optional) Failed attempts before resetting the ESP32 (default=2) + """ + # Read the settings + self._esp = esp + self.debug = False + self.secrets = secrets + self.attempts = attempts + requests.set_interface(self._esp) + self.statuspix = status_pixel + self.pixel_status(0) + + def reset(self): + """ + Perform a hard reset on the ESP + """ + if self.debug: + print("Resetting ESP") + self._esp.hard_reset() + + def connect(self): + """ + Attempt to connect to WiFi using the current settings + """ + failure_count = 0 + while not self._esp.is_connected: + try: + if self.debug: + print("Connecting to AP...") + self.pixel_status((100, 0, 0)) + self._esp.connect(self.secrets) + failure_count = 0 + self.pixel_status((0, 100, 0)) + except (ValueError, RuntimeError) as error: + print("Failed to connect, retrying\n", error) + failure_count += 1 + if failure_count >= self.attempts: + failure_count = 0 + self.reset() + continue + + def get(self, url, **kw): + """ + Pass the Get request to requests and update Status NeoPixel + + :param str url: The URL to retrieve data from + :param dict data: (Optional) Form data to submit + :param dict json: (Optional) JSON data to submit. (Data must be None) + :param dict header: (Optional) Header data to include + :param bool stream: (Optional) Whether to stream the Response + :return: The response from the request + :rtype: Response + """ + if not self._esp.is_connected: + self.connect() + self.pixel_status((0, 0, 100)) + return_val = requests.get(url, **kw) + self.pixel_status(0) + return return_val + + def post(self, url, **kw): + """ + Pass the Post request to requests and update Status NeoPixel + + :param str url: The URL to post data to + :param dict data: (Optional) Form data to submit + :param dict json: (Optional) JSON data to submit. (Data must be None) + :param dict header: (Optional) Header data to include + :param bool stream: (Optional) Whether to stream the Response + :return: The response from the request + :rtype: Response + """ + if not self._esp.is_connected: + self.connect() + self.pixel_status((0, 0, 100)) + return_val = requests.post(url, **kw) + return return_val + + def put(self, url, **kw): + """ + Pass the put request to requests and update Status NeoPixel + + :param str url: The URL to PUT data to + :param dict data: (Optional) Form data to submit + :param dict json: (Optional) JSON data to submit. (Data must be None) + :param dict header: (Optional) Header data to include + :param bool stream: (Optional) Whether to stream the Response + :return: The response from the request + :rtype: Response + """ + if not self._esp.is_connected: + self.connect() + self.pixel_status((0, 0, 100)) + return_val = requests.put(url, **kw) + self.pixel_status(0) + return return_val + + def patch(self, url, **kw): + """ + Pass the patch request to requests and update Status NeoPixel + + :param str url: The URL to PUT data to + :param dict data: (Optional) Form data to submit + :param dict json: (Optional) JSON data to submit. (Data must be None) + :param dict header: (Optional) Header data to include + :param bool stream: (Optional) Whether to stream the Response + :return: The response from the request + :rtype: Response + """ + if not self._esp.is_connected: + self.connect() + self.pixel_status((0, 0, 100)) + return_val = requests.patch(url, **kw) + self.pixel_status(0) + return return_val + + def delete(self, url, **kw): + """ + Pass the delete request to requests and update Status NeoPixel + + :param str url: The URL to PUT data to + :param dict data: (Optional) Form data to submit + :param dict json: (Optional) JSON data to submit. (Data must be None) + :param dict header: (Optional) Header data to include + :param bool stream: (Optional) Whether to stream the Response + :return: The response from the request + :rtype: Response + """ + if not self._esp.is_connected: + self.connect() + self.pixel_status((0, 0, 100)) + return_val = requests.delete(url, **kw) + self.pixel_status(0) + return return_val + + def ping(self, host, ttl=250): + """ + Pass the Ping request to the ESP32, update Status NeoPixel, return response time + + :param str host: The hostname or IP address to ping + :param int ttl: (Optional) The Time To Live in milliseconds for the packet (default=250) + :return: The response time in milliseconds + :rtype: int + """ + if not self._esp.is_connected: + self.connect() + self.pixel_status((0, 0, 100)) + response_time = self._esp.ping(host, ttl=ttl) + self.pixel_status(0) + return response_time + + def pixel_status(self, value): + """ + Change Status NeoPixel if it was defined + + :param value: The value to set the Board's Status NeoPixel to + :type value: int or 3-value tuple + """ + if self.statuspix: + self.statuspix.fill(value) diff --git a/adafruit_espatcontrol/adafruit_espatcontrol_wifimanager.py.save b/adafruit_espatcontrol/adafruit_espatcontrol_wifimanager.py.save new file mode 100755 index 0000000..a460ccc --- /dev/null +++ b/adafruit_espatcontrol/adafruit_espatcontrol_wifimanager.py.save @@ -0,0 +1,216 @@ +# The MIT License (MIT) +# +# Copyright (c) 2019 Melissa LeBlanc-Williams for Adafruit Industries +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +""" +`adafruit_espatcontrol_wifimanager` +================================================================================ + +WiFi Manager for making ESP32 AT Control as WiFi much easier + +* Author(s): Melissa LeBlanc-Williams, ladyada, Jerry Needell +""" + +# pylint: disable=no-name-in-module + +import neopixel +import adafruit_espatcontrol +import adafruit_espatcontrol.adafruit_espatcontrol_requests as requests + +class ESPAT_WiFiManager: + """ + A class to help manage the Wifi connection + """ + def __init__(self, esp, secrets, status_neopixel=None, attempts=2): + """ + :param ESP_ATcontrol esp: The ESP object we are using + :param dict secrets: The WiFi and Adafruit IO secrets dict (See examples) + :param int attempts: (Optional) Failed attempts before resetting the ESP32 (default=2) + :param status_neopixel: (Optional) The neopixel pin - Usually board.NEOPIXEL (default=None) + :type status_neopixel: Pin + """ + # Read the settings + self._esp = esp + self.debug = False + self.secrets = secrets + self.attempts = attempts + requests.set_interface(self._esp) + if status_neopixel: + self.neopix = neopixel.NeoPixel(status_neopixel, 1, brightness=0.2) + else: + self.neopix = None + self.neo_status(0) + + def reset(self): + """ + Perform a hard reset on the ESP32 + """ + if self.debug: + print("Resetting ESP32") + self._esp.hard_reset() + + def connect(self): + """ + Attempt to connect to WiFi using the current settings + """ + if self.debug: + if self._esp.status == adafruit_espatcontrol.WL_IDLE_STATUS: + print("ESP32 found and in idle mode") + print("Firmware vers.", self._esp.firmware_version) + print("MAC addr:", [hex(i) for i in self._esp.MAC_address]) + for access_pt in self._esp.scan_networks(): + print("\t%s\t\tRSSI: %d" % (str(access_pt['ssid'], 'utf-8'), access_pt['rssi'])) + failure_count = 0 + while not self._esp.is_connected: + try: + if self.debug: + print("Connecting to AP...") + self.neo_status((100, 0, 0)) + self._esp.connect(self.secrets) + failure_count = 0 + self.neo_status((0, 100, 0)) + except (ValueError, RuntimeError) as error: + print("Failed to connect, retrying\n", error) + failure_count += 1 + if failure_count >= self.attempts: + failure_count = 0 + self.reset() + continue + + def get(self, url, **kw): + """ + Pass the Get request to requests and update Status NeoPixel + + :param str url: The URL to retrieve data from + :param dict data: (Optional) Form data to submit + :param dict json: (Optional) JSON data to submit. (Data must be None) + :param dict header: (Optional) Header data to include + :param bool stream: (Optional) Whether to stream the Response + :return: The response from the request + :rtype: Response + """ + if not self._esp.is_connected: + self.connect() + self.neo_status((0, 0, 100)) + return_val = requests.get(url, **kw) + self.neo_status(0) + return return_val + + def post(self, url, **kw): + """ + Pass the Post request to requests and update Status NeoPixel + + :param str url: The URL to post data to + :param dict data: (Optional) Form data to submit + :param dict json: (Optional) JSON data to submit. (Data must be None) + :param dict header: (Optional) Header data to include + :param bool stream: (Optional) Whether to stream the Response + :return: The response from the request + :rtype: Response + """ + if not self._esp.is_connected: + self.connect() + self.neo_status((0, 0, 100)) + return_val = requests.post(url, **kw) + return return_val + + def put(self, url, **kw): + """ + Pass the put request to requests and update Status NeoPixel + + :param str url: The URL to PUT data to + :param dict data: (Optional) Form data to submit + :param dict json: (Optional) JSON data to submit. (Data must be None) + :param dict header: (Optional) Header data to include + :param bool stream: (Optional) Whether to stream the Response + :return: The response from the request + :rtype: Response + """ + if not self._esp.is_connected: + self.connect() + self.neo_status((0, 0, 100)) + return_val = requests.put(url, **kw) + self.neo_status(0) + return return_val + + def patch(self, url, **kw): + """ + Pass the patch request to requests and update Status NeoPixel + + :param str url: The URL to PUT data to + :param dict data: (Optional) Form data to submit + :param dict json: (Optional) JSON data to submit. (Data must be None) + :param dict header: (Optional) Header data to include + :param bool stream: (Optional) Whether to stream the Response + :return: The response from the request + :rtype: Response + """ + if not self._esp.is_connected: + self.connect() + self.neo_status((0, 0, 100)) + return_val = requests.patch(url, **kw) + self.neo_status(0) + return return_val + + def delete(self, url, **kw): + """ + Pass the delete request to requests and update Status NeoPixel + + :param str url: The URL to PUT data to + :param dict data: (Optional) Form data to submit + :param dict json: (Optional) JSON data to submit. (Data must be None) + :param dict header: (Optional) Header data to include + :param bool stream: (Optional) Whether to stream the Response + :return: The response from the request + :rtype: Response + """ + if not self._esp.is_connected: + self.connect() + self.neo_status((0, 0, 100)) + return_val = requests.delete(url, **kw) + self.neo_status(0) + return return_val + + def ping(self, host, ttl=250): + """ + Pass the Ping request to the ESP32, update Status NeoPixel, return response time + + :param str host: The hostname or IP address to ping + :param int ttl: (Optional) The Time To Live in milliseconds for the packet (default=250) + :return: The response time in milliseconds + :rtype: int + """ + if not self._esp.is_connected: + self.connect() + self.neo_status((0, 0, 100)) + response_time = self._esp.ping(host, ttl=ttl) + self.neo_status(0) + return response_time + + def neo_status(self, value): + """ + Change Status NeoPixel if it was defined + + :param value: The value to set the Board's Status NeoPixel to + :type value: int or 3-value tuple + """ + if self.neopix: + self.neopix.fill(value) diff --git a/examples/espatcontrol_aio_post.py b/examples/espatcontrol_aio_post.py index adf7b5c..b556649 100644 --- a/examples/espatcontrol_aio_post.py +++ b/examples/espatcontrol_aio_post.py @@ -2,8 +2,16 @@ import board import busio from digitalio import DigitalInOut -from adafruit_espatcontrol import adafruit_espatcontrol -from adafruit_espatcontrol import adafruit_espatcontrol_requests as requests + +# ESP32 AT +from adafruit_espatcontrol import adafruit_espatcontrol, adafruit_espatcontrol_wifimanager + +#Use below for Most Boards +import neopixel +status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2) # Uncomment for Most Boards +#Uncomment below for ItsyBitsy M4# +#import adafruit_dotstar as dotstar +#status_light = dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1, brightness=0.2) # Get wifi details and more from a secrets.py file @@ -15,9 +23,9 @@ # With a Metro or Feather M4 +uart = busio.UART(board.TX, board.RX, timeout=0.1) resetpin = DigitalInOut(board.D5) rtspin = DigitalInOut(board.D6) -uart = busio.UART(board.TX, board.RX, timeout=0.1) # With a Particle Argon """ @@ -33,36 +41,31 @@ """ -esp = adafruit_espatcontrol.ESP_ATcontrol(uart, 115200, reset_pin=resetpin, - run_baudrate = 460800, rts_pin=rtspin, debug=True) -print("Resetting ESP module") -esp.hard_reset() -requests.set_interface(esp) +print("ESP AT commands") +esp = adafruit_espatcontrol.ESP_ATcontrol(uart, 115200, + reset_pin=resetpin, rts_pin=rtspin, debug=False) +wifi = adafruit_espatcontrol_wifimanager.ESPAT_WiFiManager(esp, secrets, status_light) -print("Connected to AT software version", esp.get_version()) counter = 0 + while True: try: - # Connect to WiFi if not already - while not esp.is_connected: - print("Connecting...") - esp.connect(secrets) - print("Connected to", esp.remote_AP) - # great, lets get the data print("Posting data...", end='') - data=counter - feed='test' - payload={'value':data} - response=requests.post( + data = counter + feed = 'test' + payload = {'value':data} + response = wifi.post( "https://io.adafruit.com/api/v2/"+secrets['aio_username']+"/feeds/"+feed+"/data", - json=payload,headers={bytes("X-AIO-KEY","utf-8"):bytes(secrets['aio_key'],"utf-8")}) + json=payload, + headers={bytes("X-AIO-KEY", "utf-8"):bytes(secrets['aio_key'], "utf-8")}) print(response.json()) response.close() counter = counter + 1 print("OK") except (ValueError, RuntimeError, adafruit_espatcontrol.OKError) as e: print("Failed to get data, retrying\n", e) + wifi.reset() continue response = None - time.sleep(10) + time.sleep(15) diff --git a/examples/espatcontrol_cheerlights.py b/examples/espatcontrol_cheerlights.py index 5640215..5f014ea 100644 --- a/examples/espatcontrol_cheerlights.py +++ b/examples/espatcontrol_cheerlights.py @@ -1,18 +1,16 @@ -""" -This example will query ThingSpeak channel 1417 "CheerLights" and display the -color on a NeoPixel ring or strip -""" - -import gc import time import board import busio from digitalio import DigitalInOut -from adafruit_espatcontrol import adafruit_espatcontrol -from adafruit_espatcontrol import adafruit_espatcontrol_requests as requests + +# ESP32 SPI +from adafruit_espatcontrol import adafruit_espatcontrol, adafruit_espatcontrol_wifimanager + + import neopixel import adafruit_fancyled.adafruit_fancyled as fancy + # Get wifi details and more from a secrets.py file try: from secrets import secrets @@ -20,18 +18,18 @@ print("WiFi secrets are kept in secrets.py, please add them there!") raise -# CONFIGURATION -TIME_BETWEEN_QUERY = 10 # in seconds -# Cheerlights! -DATA_SOURCE = "http://api.thingspeak.com/channels/1417/feeds.json?results=1" -DATA_LOCATION = ["feeds", 0, "field2"] +#Use below for Most Boards +status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2) # Uncomment for Most Boards +#Uncomment below for ItsyBitsy M4 +#import adafruit_dotstar as dotstar +#status_light = dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1, brightness=0.2) +# With a Metro or Feather M4 uart = busio.UART(board.TX, board.RX, timeout=0.1) resetpin = DigitalInOut(board.D5) rtspin = DigitalInOut(board.D6) - # With a Particle Argon """ RX = board.ESP_TX @@ -46,55 +44,42 @@ """ +print("ESP AT commands") +esp = adafruit_espatcontrol.ESP_ATcontrol(uart, 115200, + reset_pin=resetpin, rts_pin=rtspin, debug=False) +wifi = adafruit_espatcontrol_wifimanager.ESPAT_WiFiManager(esp, secrets, status_light) + -# Create the connection to the co-processor and reset -esp = adafruit_espatcontrol.ESP_ATcontrol(uart, 115200, run_baudrate=460800, - reset_pin=resetpin, - rts_pin=rtspin, debug=True) -esp.hard_reset() +DATA_SOURCE = "https://api.thingspeak.com/channels/1417/feeds.json?results=1" +DATA_LOCATION = ["feeds", 0, "field2"] -requests.set_interface(esp) # neopixels pixels = neopixel.NeoPixel(board.A1, 16, brightness=0.3) pixels.fill(0) -builtin = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.1) -builtin[0] = 0 # we'll save the value in question last_value = value = None -the_time = None -times = 0 while True: try: - while not esp.is_connected: - builtin[0] = (100, 0, 0) - # secrets dictionary must contain 'ssid' and 'password' at a minimum - esp.connect(secrets) - builtin[0] = (0, 100, 0) - # great, lets get the data - print("Retrieving data source...", end='') - builtin[0] = (100, 100, 0) - r = requests.get(DATA_SOURCE) - builtin[0] = (0, 0, 100) - print("Reply is OK!") + print("Fetching json from", DATA_SOURCE) + response = wifi.get(DATA_SOURCE) + print(response.json()) + value = response.json() + for key in DATA_LOCATION: + value = value[key] + print(value) + response.close() except (ValueError, RuntimeError, adafruit_espatcontrol.OKError) as e: print("Failed to get data, retrying\n", e) + wifi.reset() continue - print('-'*40,) - print("Headers: ", r.headers) - print("Text:", r.text) - print('-'*40) - # For mystery reasons, there's two numbers before and after the json data - value = r.json() - for x in DATA_LOCATION: - value = value[x] - builtin[0] = (100, 100, 100) + if not value: continue if last_value != value: - color = int(value[1:],16) + color = int(value[1:], 16) red = color >> 16 & 0xFF green = color >> 8 & 0xFF blue = color& 0xFF @@ -102,10 +87,5 @@ pixels.fill(gamma_corrected) last_value = value - times += 1 - - # normally we wouldn't have to do this, but we get bad fragments - r = None - gc.collect() - print(gc.mem_free()) # pylint: disable=no-member - time.sleep(TIME_BETWEEN_QUERY) + response = None + time.sleep(60) diff --git a/examples/espatcontrol_countviewer.py b/examples/espatcontrol_countviewer.py index 3eb2309..1518c2d 100644 --- a/examples/espatcontrol_countviewer.py +++ b/examples/espatcontrol_countviewer.py @@ -82,7 +82,7 @@ # Create the connection to the co-processor and reset esp = adafruit_espatcontrol.ESP_ATcontrol(uart, 115200, run_baudrate=921600, reset_pin=resetpin, - rts_pin=rtspin, debug=True) + rts_pin=rtspin, debug=False) esp.hard_reset() requests.set_interface(esp) diff --git a/examples/espatcontrol_localtime.py b/examples/espatcontrol_localtime.py new file mode 100644 index 0000000..4c3b167 --- /dev/null +++ b/examples/espatcontrol_localtime.py @@ -0,0 +1,88 @@ +import time +import rtc +import board +import busio +from digitalio import DigitalInOut + +# ESP32 SPI +from adafruit_espatcontrol import adafruit_espatcontrol, adafruit_espatcontrol_wifimanager + +#Use below for Most Boards +import neopixel +status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2) # Uncomment for Most Boards +#Uncomment below for ItsyBitsy M4 +#import adafruit_dotstar as dotstar +#status_light = dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1, brightness=0.2) + + +# Get wifi details and more from a secrets.py file +try: + from secrets import secrets +except ImportError: + print("WiFi secrets are kept in secrets.py, please add them there!") + raise + + + +# With a Metro or Feather M4 +uart = busio.UART(board.TX, board.RX, timeout=0.1) +resetpin = DigitalInOut(board.D5) +rtspin = DigitalInOut(board.D6) + +# With a Particle Argon +""" +RX = board.ESP_TX +TX = board.ESP_RX +resetpin = DigitalInOut(board.ESP_WIFI_EN) +rtspin = DigitalInOut(board.ESP_CTS) +uart = busio.UART(TX, RX, timeout=0.1) +esp_boot = DigitalInOut(board.ESP_BOOT_MODE) +from digitalio import Direction +esp_boot.direction = Direction.OUTPUT +esp_boot.value = True +""" + + +print("ESP AT commands") +esp = adafruit_espatcontrol.ESP_ATcontrol(uart, 115200, + reset_pin=resetpin, rts_pin=rtspin, debug=False) +wifi = adafruit_espatcontrol_wifimanager.ESPAT_WiFiManager(esp, secrets, status_light) + + + +print("ESP32 local time") + +TIME_API = "http://worldtimeapi.org/api/ip" + + +the_rtc = rtc.RTC() + +response = None +while True: + try: + print("Fetching json from", TIME_API) + response = wifi.get(TIME_API) + break + except (ValueError, RuntimeError, adafruit_espatcontrol.OKError) as e: + print("Failed to get data, retrying\n", e) + continue + +json = response.json() +current_time = json['datetime'] +the_date, the_time = current_time.split('T') +year, month, mday = [int(x) for x in the_date.split('-')] +the_time = the_time.split('.')[0] +hours, minutes, seconds = [int(x) for x in the_time.split(':')] + +# We can also fill in these extra nice things +year_day = json['day_of_year'] +week_day = json['day_of_week'] +is_dst = json['dst'] + +now = time.struct_time((year, month, mday, hours, minutes, seconds, week_day, year_day, is_dst)) +print(now) +the_rtc.datetime = now + +while True: + print(time.localtime()) + time.sleep(1) diff --git a/examples/espatcontrol_quoteEPD.py b/examples/espatcontrol_quoteEPD.py deleted file mode 100644 index ea126aa..0000000 --- a/examples/espatcontrol_quoteEPD.py +++ /dev/null @@ -1,226 +0,0 @@ -""" -This example will access an API, grab a number like hackaday skulls, github -stars, price of bitcoin, twitter followers... if you can find something that -spits out JSON data, we can display it! -""" - -import gc -import time -import board -import busio -from digitalio import DigitalInOut -from adafruit_espatcontrol import adafruit_espatcontrol -from adafruit_espatcontrol import adafruit_espatcontrol_requests as requests -try: - import json as json_module -except ImportError: - import ujson as json_module -from adafruit_epd.epd import Adafruit_EPD -from adafruit_epd.il0373 import Adafruit_IL0373 - - -# Get wifi details and more from a secrets.py file -try: - from secrets import secrets -except ImportError: - print("WiFi secrets are kept in secrets.py, please add them there!") - raise - -# CONFIGURATION -TIME_BETWEEN_QUERY = 0 # in seconds -DATA_SOURCE = "https://www.adafruit.com/api/quotes.php" - - -# create the spi device and pins we will need -spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO) -epd_cs = DigitalInOut(board.D10) -epd_dc = DigitalInOut(board.D11) -epd_rst = DigitalInOut(board.D12) -# give them all to our driver -display = Adafruit_IL0373(104, 212, spi, - cs_pin=epd_cs, dc_pin=epd_dc, sramcs_pin=None, - rst_pin=epd_rst, busy_pin=None) -display.rotation = 3 - -uart = busio.UART(board.TX, board.RX, timeout=0.1) -resetpin = DigitalInOut(board.D5) -rtspin = DigitalInOut(board.D6) - - -# With a Particle Argon -""" -RX = board.ESP_TX -TX = board.ESP_RX -resetpin = DigitalInOut(board.ESP_WIFI_EN) -rtspin = DigitalInOut(board.ESP_CTS) -uart = busio.UART(TX, RX, timeout=0.1) -esp_boot = DigitalInOut(board.ESP_BOOT_MODE) -from digitalio import Direction -esp_boot.direction = Direction.OUTPUT -esp_boot.value = True -""" - - -# Create the connection to the co-processor and reset -esp = adafruit_espatcontrol.ESP_ATcontrol(uart, 115200, run_baudrate=115200, - reset_pin=resetpin, - rts_pin=rtspin, debug=True) -esp.hard_reset() - -requests.set_interface(esp) - -# Extract a value from a json string -def get_value(response, location): - """Extract a value from a json object, based on the path in 'location'""" - try: - print("Parsing JSON response...", end='') - json = json_module.loads(response) - print("parsed OK!") - for x in location: - json = json[x] - return json - except ValueError: - print("Failed to parse json, retrying") - return None - -# return a list of lines with wordwrapping -def wrap_nicely(string, max_chars): - words = string.split(' ') - the_lines = [] - the_line = "" - for w in words: - if len(the_line+' '+w) <= max_chars: - the_line += ' '+w - else: - the_lines.append(line) - the_line = ''+w - if the_line: # last line remaining - the_lines.append(line) - return the_lines - -def read_le(s): - # as of this writting, int.from_bytes does not have LE support, DIY! - result = 0 - shift = 0 - for byte in bytearray(s): - result += byte << shift - shift += 8 - return result - -class BMPError(Exception): - pass - -def draw_bmp(filename, x, y): # pylint: disable=too-many-locals, too-many-branches - try: - with open("/" + filename, "rb") as f: - print("File opened") - if f.read(2) != b'BM': # check signature - raise BMPError("Not BitMap file") - - bmpFileSize = read_le(f.read(4)) - f.read(4) # Read & ignore creator bytes - - bmpImageoffset = read_le(f.read(4)) # Start of image data - headerSize = read_le(f.read(4)) - bmpWidth = read_le(f.read(4)) - bmpHeight = read_le(f.read(4)) - flip = True - - print("Size: %d\nImage offset: %d\nHeader size: %d" % - (bmpFileSize, bmpImageoffset, headerSize)) - print("Width: %d\nHeight: %d" % (bmpWidth, bmpHeight)) - - if read_le(f.read(2)) != 1: - raise BMPError("Not singleplane") - bmpDepth = read_le(f.read(2)) # bits per pixel - print("Bit depth: %d" % (bmpDepth)) - if bmpDepth != 24: - raise BMPError("Not 24-bit") - if read_le(f.read(2)) != 0: - raise BMPError("Compressed file") - - print("Image OK! Drawing...") - - rowSize = (bmpWidth * 3 + 3) & ~3 # 32-bit line boundary - - for row in range(bmpHeight): # For each scanline... - if flip: # Bitmap is stored bottom-to-top order (normal BMP) - pos = bmpImageoffset + (bmpHeight - 1 - row) * rowSize - else: # Bitmap is stored top-to-bottom - pos = bmpImageoffset + row * rowSize - - # print ("seek to %d" % pos) - f.seek(pos) - for col in range(bmpWidth): - b, g, r = bytearray(f.read(3)) # BMP files store RGB in BGR - color = None - if r < 0x80 and g < 0x80 and b < 0x80: - color = Adafruit_EPD.BLACK - elif r >= 0x80 and g >= 0x80 and b >= 0x80: - color = Adafruit_EPD.WHITE - elif r >= 0x80: - color = Adafruit_EPD.RED - display.pixel(x+row, y+col, color) - - except OSError as e: - if e.args[0] == 28: - raise OSError("OS Error 28 0.25") - else: - raise OSError("OS Error 0.5") - except BMPError as e: - print("Failed to parse BMP: " + e.args[0]) - -quote = "Eternal vigilance is not only the price of liberty; eternal vigilance is the price of human decency" # pylint: disable=line-too-long -author = "Aldous Huxley" -lines = wrap_nicely(str(quote, 'utf-8'), (210 - 50)//6) -print(lines) -start_x = 10 -start_y = 10 -display.fill(Adafruit_EPD.WHITE) -draw_bmp("lilblinka.bmp", display.width - 75, display.height - 80) -for i,line in enumerate(lines): - display.text(line, start_x, start_y+i*10, Adafruit_EPD.BLACK) -display.text(author, 10, 100-20, Adafruit_EPD.RED) - -display.display() - - -while True: - try: - while not esp.is_connected: - # secrets dictionary must contain 'ssid' and 'password' at a minimum - esp.connect(secrets) - # great, lets get the data - - print("Retrieving data source...", end='') - req = requests.get(DATA_SOURCE) - print("Reply is OK!") - except (ValueError, RuntimeError, adafruit_espatcontrol.OKError) as e: - print("Failed to get data, retrying\n", e) - continue - - body = req.text - print('-'*40, "Size: ", len(body)) - print(str(body, 'utf-8')) - print('-'*40) - quote = get_value(body, [0, "text"]) - author = get_value(body, [0, "author"]) - if not quote or not author: - continue - print(quote, author) - - lines = wrap_nicely(str(quote, 'utf-8'), (display.width - 50)//6) - start_x = 10 - start_y = 10 - display.fill(Adafruit_EPD.WHITE) - draw_bmp("lilblinka.bmp", display.width - 75, display.height - 80) - for i, line in enumerate(lines): - display.text(line, start_x, start_y+i*10, Adafruit_EPD.BLACK) - display.text(author, 10, display.height-20, Adafruit_EPD.RED) - display.display() - - # normally we wouldn't have to do this, but we get bad fragments - req = None - gc.collect() - print(gc.mem_free()) # pylint: disable=no-member - time.sleep(TIME_BETWEEN_QUERY) diff --git a/examples/espatcontrol_secrets.py b/examples/espatcontrol_secrets.py index dd2fea4..68ef07a 100644 --- a/examples/espatcontrol_secrets.py +++ b/examples/espatcontrol_secrets.py @@ -6,6 +6,6 @@ 'password' : 'my password', 'timezone' : -5, # this is offset from UTC 'github_token' : 'abcdefghij0123456789', - 'aio_username' : 'myusername', - 'aio_key' : 'abcdefghij0123456789', + 'adafruit_io_user' : 'myusername', + 'adafruit_io_key' : 'abcdefghij0123456789', } diff --git a/examples/espatcontrol_simpletest.py b/examples/espatcontrol_simpletest.py index 25cf3d7..a9ce228 100644 --- a/examples/espatcontrol_simpletest.py +++ b/examples/espatcontrol_simpletest.py @@ -4,6 +4,8 @@ from digitalio import DigitalInOut from adafruit_espatcontrol import adafruit_espatcontrol + + # Get wifi details and more from a secrets.py file try: from secrets import secrets @@ -48,6 +50,7 @@ print("Connecting...") esp.connect(secrets) print("Connected to AT software version ", esp.version) + print("IP address ", esp.local_ip) first_pass = False print("Pinging 8.8.8.8...", end="") print(esp.ping("8.8.8.8")) diff --git a/examples/espatcontrol_webclient.py b/examples/espatcontrol_webclient.py index a756485..409599e 100644 --- a/examples/espatcontrol_webclient.py +++ b/examples/espatcontrol_webclient.py @@ -2,6 +2,8 @@ import board import busio from digitalio import DigitalInOut + +# ESP32 AT from adafruit_espatcontrol import adafruit_espatcontrol from adafruit_espatcontrol import adafruit_espatcontrol_requests as requests @@ -12,6 +14,7 @@ print("WiFi secrets are kept in secrets.py, please add them there!") raise + # With a Metro or Feather M4 uart = busio.UART(board.TX, board.RX, timeout=0.1) resetpin = DigitalInOut(board.D5) @@ -19,20 +22,25 @@ # With a Particle Argon """ -uart = busio.UART(board.ESP_RX, board.ESP_TX, timeout=0.1) +RX = board.ESP_TX +TX = board.ESP_RX resetpin = DigitalInOut(board.ESP_WIFI_EN) rtspin = DigitalInOut(board.ESP_CTS) +uart = busio.UART(TX, RX, timeout=0.1) esp_boot = DigitalInOut(board.ESP_BOOT_MODE) from digitalio import Direction esp_boot.direction = Direction.OUTPUT esp_boot.value = True """ -URL = "http://wifitest.adafruit.com/testwifi/index.html" -print("ESP AT GET URL", URL) +print("ESP AT commands") esp = adafruit_espatcontrol.ESP_ATcontrol(uart, 115200, reset_pin=resetpin, rts_pin=rtspin, debug=False) + +URL = "http://wifitest.adafruit.com/testwifi/index.html" +print("ESP AT GET URL", URL) + print("Resetting ESP module") esp.hard_reset() From 383f509b88c9c7dae671da783db7bbf962b8e51b Mon Sep 17 00:00:00 2001 From: Jerry Needell Date: Wed, 27 Feb 2019 12:46:17 -0500 Subject: [PATCH 2/3] pylint fic --- examples/espatcontrol_localtime.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/espatcontrol_localtime.py b/examples/espatcontrol_localtime.py index 4c3b167..cd16933 100644 --- a/examples/espatcontrol_localtime.py +++ b/examples/espatcontrol_localtime.py @@ -1,8 +1,8 @@ import time -import rtc import board import busio from digitalio import DigitalInOut +import rtc # ESP32 SPI from adafruit_espatcontrol import adafruit_espatcontrol, adafruit_espatcontrol_wifimanager From c8505f430a68197ee1a8e9c9f7fcaa79c2810c0f Mon Sep 17 00:00:00 2001 From: Jerry Needell Date: Wed, 27 Feb 2019 13:04:40 -0500 Subject: [PATCH 3/3] fixes per review --- .../adafruit_espatcontrol_wifimanager.py | 6 +- .../adafruit_espatcontrol_wifimanager.py.save | 216 ------------------ 2 files changed, 3 insertions(+), 219 deletions(-) delete mode 100755 adafruit_espatcontrol/adafruit_espatcontrol_wifimanager.py.save diff --git a/adafruit_espatcontrol/adafruit_espatcontrol_wifimanager.py b/adafruit_espatcontrol/adafruit_espatcontrol_wifimanager.py index 5c0b4d5..3136801 100755 --- a/adafruit_espatcontrol/adafruit_espatcontrol_wifimanager.py +++ b/adafruit_espatcontrol/adafruit_espatcontrol_wifimanager.py @@ -24,9 +24,9 @@ `adafruit_espatcontrol_wifimanager` ================================================================================ -WiFi Manager for making ESP32 SPI as WiFi much easier +WiFi Manager for making ESP32 AT Control as WiFi much easier -* Author(s): Melissa LeBlanc-Williams, ladyada +* Author(s): Melissa LeBlanc-Williams, ladyada, Jerry Needell """ # pylint: disable=no-name-in-module @@ -37,7 +37,7 @@ class ESPAT_WiFiManager: """ A class to help manage the Wifi connection """ - def __init__(self, esp, secrets, status_pixel, attempts=2): + def __init__(self, esp, secrets, status_pixel=None, attempts=2): """ :param ESP_SPIcontrol esp: The ESP object we are using :param dict secrets: The WiFi and Adafruit IO secrets dict (See examples) diff --git a/adafruit_espatcontrol/adafruit_espatcontrol_wifimanager.py.save b/adafruit_espatcontrol/adafruit_espatcontrol_wifimanager.py.save deleted file mode 100755 index a460ccc..0000000 --- a/adafruit_espatcontrol/adafruit_espatcontrol_wifimanager.py.save +++ /dev/null @@ -1,216 +0,0 @@ -# The MIT License (MIT) -# -# Copyright (c) 2019 Melissa LeBlanc-Williams for Adafruit Industries -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - -""" -`adafruit_espatcontrol_wifimanager` -================================================================================ - -WiFi Manager for making ESP32 AT Control as WiFi much easier - -* Author(s): Melissa LeBlanc-Williams, ladyada, Jerry Needell -""" - -# pylint: disable=no-name-in-module - -import neopixel -import adafruit_espatcontrol -import adafruit_espatcontrol.adafruit_espatcontrol_requests as requests - -class ESPAT_WiFiManager: - """ - A class to help manage the Wifi connection - """ - def __init__(self, esp, secrets, status_neopixel=None, attempts=2): - """ - :param ESP_ATcontrol esp: The ESP object we are using - :param dict secrets: The WiFi and Adafruit IO secrets dict (See examples) - :param int attempts: (Optional) Failed attempts before resetting the ESP32 (default=2) - :param status_neopixel: (Optional) The neopixel pin - Usually board.NEOPIXEL (default=None) - :type status_neopixel: Pin - """ - # Read the settings - self._esp = esp - self.debug = False - self.secrets = secrets - self.attempts = attempts - requests.set_interface(self._esp) - if status_neopixel: - self.neopix = neopixel.NeoPixel(status_neopixel, 1, brightness=0.2) - else: - self.neopix = None - self.neo_status(0) - - def reset(self): - """ - Perform a hard reset on the ESP32 - """ - if self.debug: - print("Resetting ESP32") - self._esp.hard_reset() - - def connect(self): - """ - Attempt to connect to WiFi using the current settings - """ - if self.debug: - if self._esp.status == adafruit_espatcontrol.WL_IDLE_STATUS: - print("ESP32 found and in idle mode") - print("Firmware vers.", self._esp.firmware_version) - print("MAC addr:", [hex(i) for i in self._esp.MAC_address]) - for access_pt in self._esp.scan_networks(): - print("\t%s\t\tRSSI: %d" % (str(access_pt['ssid'], 'utf-8'), access_pt['rssi'])) - failure_count = 0 - while not self._esp.is_connected: - try: - if self.debug: - print("Connecting to AP...") - self.neo_status((100, 0, 0)) - self._esp.connect(self.secrets) - failure_count = 0 - self.neo_status((0, 100, 0)) - except (ValueError, RuntimeError) as error: - print("Failed to connect, retrying\n", error) - failure_count += 1 - if failure_count >= self.attempts: - failure_count = 0 - self.reset() - continue - - def get(self, url, **kw): - """ - Pass the Get request to requests and update Status NeoPixel - - :param str url: The URL to retrieve data from - :param dict data: (Optional) Form data to submit - :param dict json: (Optional) JSON data to submit. (Data must be None) - :param dict header: (Optional) Header data to include - :param bool stream: (Optional) Whether to stream the Response - :return: The response from the request - :rtype: Response - """ - if not self._esp.is_connected: - self.connect() - self.neo_status((0, 0, 100)) - return_val = requests.get(url, **kw) - self.neo_status(0) - return return_val - - def post(self, url, **kw): - """ - Pass the Post request to requests and update Status NeoPixel - - :param str url: The URL to post data to - :param dict data: (Optional) Form data to submit - :param dict json: (Optional) JSON data to submit. (Data must be None) - :param dict header: (Optional) Header data to include - :param bool stream: (Optional) Whether to stream the Response - :return: The response from the request - :rtype: Response - """ - if not self._esp.is_connected: - self.connect() - self.neo_status((0, 0, 100)) - return_val = requests.post(url, **kw) - return return_val - - def put(self, url, **kw): - """ - Pass the put request to requests and update Status NeoPixel - - :param str url: The URL to PUT data to - :param dict data: (Optional) Form data to submit - :param dict json: (Optional) JSON data to submit. (Data must be None) - :param dict header: (Optional) Header data to include - :param bool stream: (Optional) Whether to stream the Response - :return: The response from the request - :rtype: Response - """ - if not self._esp.is_connected: - self.connect() - self.neo_status((0, 0, 100)) - return_val = requests.put(url, **kw) - self.neo_status(0) - return return_val - - def patch(self, url, **kw): - """ - Pass the patch request to requests and update Status NeoPixel - - :param str url: The URL to PUT data to - :param dict data: (Optional) Form data to submit - :param dict json: (Optional) JSON data to submit. (Data must be None) - :param dict header: (Optional) Header data to include - :param bool stream: (Optional) Whether to stream the Response - :return: The response from the request - :rtype: Response - """ - if not self._esp.is_connected: - self.connect() - self.neo_status((0, 0, 100)) - return_val = requests.patch(url, **kw) - self.neo_status(0) - return return_val - - def delete(self, url, **kw): - """ - Pass the delete request to requests and update Status NeoPixel - - :param str url: The URL to PUT data to - :param dict data: (Optional) Form data to submit - :param dict json: (Optional) JSON data to submit. (Data must be None) - :param dict header: (Optional) Header data to include - :param bool stream: (Optional) Whether to stream the Response - :return: The response from the request - :rtype: Response - """ - if not self._esp.is_connected: - self.connect() - self.neo_status((0, 0, 100)) - return_val = requests.delete(url, **kw) - self.neo_status(0) - return return_val - - def ping(self, host, ttl=250): - """ - Pass the Ping request to the ESP32, update Status NeoPixel, return response time - - :param str host: The hostname or IP address to ping - :param int ttl: (Optional) The Time To Live in milliseconds for the packet (default=250) - :return: The response time in milliseconds - :rtype: int - """ - if not self._esp.is_connected: - self.connect() - self.neo_status((0, 0, 100)) - response_time = self._esp.ping(host, ttl=ttl) - self.neo_status(0) - return response_time - - def neo_status(self, value): - """ - Change Status NeoPixel if it was defined - - :param value: The value to set the Board's Status NeoPixel to - :type value: int or 3-value tuple - """ - if self.neopix: - self.neopix.fill(value)