From c010dcf1dd35d7d51b6640c1e6bd7a6ad6453372 Mon Sep 17 00:00:00 2001 From: brentru Date: Thu, 11 Jul 2019 10:51:59 -0400 Subject: [PATCH 01/35] start adding mqtt client to init --- adafruit_io/adafruit_io.py | 40 +++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/adafruit_io/adafruit_io.py b/adafruit_io/adafruit_io.py index 0b77e58..8f1234b 100755 --- a/adafruit_io/adafruit_io.py +++ b/adafruit_io/adafruit_io.py @@ -35,11 +35,11 @@ * Adafruit CircuitPython firmware for the supported boards: https://github.com/adafruit/circuitpython/releases -* Adafruit ESP32SPI or ESP_ATcontrol library: - https://github.com/adafruit/Adafruit_CircuitPython_ESP32SPI - https://github.com/adafruit/Adafruit_CircuitPython_ESP_ATcontrol +* Adafruit CircuitPython MiniMQTT: + https://github.com/adafruit/Adafruit_CircuitPython_MiniMQTT """ from time import struct_time +from adafruit_minimqtt import MQTT from adafruit_io.adafruit_io_errors import AdafruitIO_RequestError, AdafruitIO_ThrottleError __version__ = "0.0.0-auto.0" @@ -49,17 +49,39 @@ 'User-Agent': 'AIO-CircuitPython/{0}'.format(__version__) } -class RESTClient(): +class MQTT(): """ - REST Client for interacting with the Adafruit IO API. + Client for interacting with the Adafruit IO MQTT API + :param str aio_username: Adafruit.io account username. + :param str aio_key: Adafruit.io active key. + :param ESP32SPI network_interface: Network interface hardware, ESP32SPI object. + :param bool secure: Enables SSL/TLS connection. """ - def __init__(self, adafruit_io_username, adafruit_io_key, wifi_manager): - """ - Creates an instance of the Adafruit IO REST Client. + def __init__(self, aio_username, aio_key, network_interface, socket, secure=True): + self._user = aio_username + self._key = aio_key + # Network interface hardware detection + if hasattr(network_interface, '_gpio0'): + self._esp = network_interface + self._client = MQTT(socket, + 'io.adafruit.com', + username = self._user, + password = self._key, + esp = esp, + is_ssl = secure) + else: + raise TypeError('This library requires network interface hardware.') + + +class RESTClient(): + """ + Client for interacting with the Adafruit IO HTTP API. :param str adafruit_io_username: Adafruit IO Username :param str adafruit_io_key: Adafruit IO Key :param wifi_manager: WiFiManager object from ESPSPI_WiFiManager or ESPAT_WiFiManager - """ + + """ + def __init__(self, adafruit_io_username, adafruit_io_key, wifi_manager): self.username = adafruit_io_username self.key = adafruit_io_key wifi_type = str(type(wifi_manager)) From 0412871e2796952789edd6fe7e83e2d26dfcdf81 Mon Sep 17 00:00:00 2001 From: brentru Date: Thu, 11 Jul 2019 11:25:32 -0400 Subject: [PATCH 02/35] add on_connect, on_disconnect, on_message, client-level callbacks --- adafruit_io/adafruit_io.py | 61 +++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/adafruit_io/adafruit_io.py b/adafruit_io/adafruit_io.py index 8f1234b..a3aa27f 100755 --- a/adafruit_io/adafruit_io.py +++ b/adafruit_io/adafruit_io.py @@ -71,7 +71,66 @@ def __init__(self, aio_username, aio_key, network_interface, socket, secure=True is_ssl = secure) else: raise TypeError('This library requires network interface hardware.') - + # User-defined MQTT callback methods need to be init'd to none + self.on_connect = None + self.on_disconnect = None + self.on_message = None + self.on_subscribe = None + # MQTT event callbacks + self._client.on_connect = self._on_connect_mqtt + self._client.on_disconnect = self._on_disconnect_mqtt + self._client.on_message = self._on_message_mqtt + self._logger = None + if hasattr(self._client, '_logger'): + self._logger = True + self._client.set_logger_level('DEBUG') + self._connected = False + + def _on_connect_mqtt(self, client, userdata, flags, rc): + """Runs when the on_connect callback is run from user-code. + """ + if self._logger: + self._client._logger.debug('Client called on_connect.') + if rc == 0: + self._connected = True + print('Connected to Adafruit IO!') + else: + raise AdafruitIO_MQTTError(rc) + # Call the user-defined on_connect callback if defined + if self.on_connect is not None: + self.on_connect(self) + + def _on_disconnect_mqtt(self, client, userdata, rc): + """Runs when the on_disconnect callback is run from + user-code. + """ + if self._logger: + self._client._logger.debug('Client called on_disconnect') + self._connected = False + # Call the user-defined on_disconnect callblack if defined + if self.on_disconnect is not None: + self.on_disconnect(self) + + def _on_message_mqtt(self, client, topic, message): + """Runs when the on_message callback is run from user-code. + Performs parsing based on username/feed/feed-key. + """ + if self._logger: + self._client._logger.debug('Client called on_message.') + print('MSG: ', message) + if self.on_message is not None: + parsed_feed = message.topic.split('/') + feed = parsed_feed[2] + message = '' if message is None else message.decode('utf-8') + else: + raise ValueError('Define an on_message method before calling this callback.') + self.on_message(self, feed, message) + + + + + + class RESTClient(): """ From 2422872462acbc7f22c7db007b5335416aae7298 Mon Sep 17 00:00:00 2001 From: brentru Date: Thu, 11 Jul 2019 13:15:31 -0400 Subject: [PATCH 03/35] add sub/pub, refactor to reflect latest minimqtt --- adafruit_io/adafruit_io.py | 39 ++++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/adafruit_io/adafruit_io.py b/adafruit_io/adafruit_io.py index a3aa27f..f95b4cc 100755 --- a/adafruit_io/adafruit_io.py +++ b/adafruit_io/adafruit_io.py @@ -40,7 +40,7 @@ """ from time import struct_time from adafruit_minimqtt import MQTT -from adafruit_io.adafruit_io_errors import AdafruitIO_RequestError, AdafruitIO_ThrottleError +#from adafruit_io.adafruit_io_errors import AdafruitIO_RequestError, AdafruitIO_ThrottleError __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Adafruit_IO.git" @@ -54,23 +54,18 @@ class MQTT(): Client for interacting with the Adafruit IO MQTT API :param str aio_username: Adafruit.io account username. :param str aio_key: Adafruit.io active key. - :param ESP32SPI network_interface: Network interface hardware, ESP32SPI object. + :param network_manager: NetworkManager object, such as WiFiManager from ESPSPI_WiFiManager. :param bool secure: Enables SSL/TLS connection. """ - def __init__(self, aio_username, aio_key, network_interface, socket, secure=True): + def __init__(self, aio_username, aio_key, network_manager, socket, secure=True): self._user = aio_username self._key = aio_key # Network interface hardware detection - if hasattr(network_interface, '_gpio0'): - self._esp = network_interface - self._client = MQTT(socket, - 'io.adafruit.com', - username = self._user, - password = self._key, - esp = esp, - is_ssl = secure) + wifi_type = str(type(wifi_manager)) + if ('ESPSPI_WiFiManager' in wifi_type or 'ESPAT_WiFiManager' in wifi_type): + self.wifi = wifi_manager else: - raise TypeError('This library requires network interface hardware.') + raise TypeError("This library requires a NetworkManager object.") # User-defined MQTT callback methods need to be init'd to none self.on_connect = None self.on_disconnect = None @@ -85,7 +80,27 @@ def __init__(self, aio_username, aio_key, network_interface, socket, secure=True self._logger = True self._client.set_logger_level('DEBUG') self._connected = False + + @property + def is_connected(self): + """Returns True if class is connected to Adafruit IO.""" + return self._connected + def connect(self): + """Connects to Adafruit IO, must be called before any + other API methods are called. + """ + if self._connected: + return + self._client.connect() + + def disconnect(self): + """Disconnects from Adafruit IO. + """ + if self._connected: + self._client.disconnect() + + def _on_connect_mqtt(self, client, userdata, flags, rc): """Runs when the on_connect callback is run from user-code. """ From e8155fcfd5b2d4108944f306a5ac3925c49ecc08 Mon Sep 17 00:00:00 2001 From: brentru Date: Thu, 11 Jul 2019 15:58:35 -0400 Subject: [PATCH 04/35] parse messages properly within on_message --- adafruit_io/adafruit_io.py | 185 +++++++++++++++++++++++++++++++++---- 1 file changed, 165 insertions(+), 20 deletions(-) diff --git a/adafruit_io/adafruit_io.py b/adafruit_io/adafruit_io.py index f95b4cc..416acf8 100755 --- a/adafruit_io/adafruit_io.py +++ b/adafruit_io/adafruit_io.py @@ -39,7 +39,7 @@ https://github.com/adafruit/Adafruit_CircuitPython_MiniMQTT """ from time import struct_time -from adafruit_minimqtt import MQTT +from adafruit_minimqtt import MQTT as MQTTClient #from adafruit_io.adafruit_io_errors import AdafruitIO_RequestError, AdafruitIO_ThrottleError __version__ = "0.0.0-auto.0" @@ -51,7 +51,8 @@ class MQTT(): """ - Client for interacting with the Adafruit IO MQTT API + Client for interacting with the Adafruit IO MQTT API. The client establishes + a secure connection to Adafruit IO by default. :param str aio_username: Adafruit.io account username. :param str aio_key: Adafruit.io active key. :param network_manager: NetworkManager object, such as WiFiManager from ESPSPI_WiFiManager. @@ -61,34 +62,44 @@ def __init__(self, aio_username, aio_key, network_manager, socket, secure=True): self._user = aio_username self._key = aio_key # Network interface hardware detection - wifi_type = str(type(wifi_manager)) - if ('ESPSPI_WiFiManager' in wifi_type or 'ESPAT_WiFiManager' in wifi_type): - self.wifi = wifi_manager + manager_type = str(type(network_manager)) + if ('ESPSPI_WiFiManager' in manager_type or 'ESPAT_WiFiManager' in manager_type): + self._network_manager = network_manager else: raise TypeError("This library requires a NetworkManager object.") + # Set up a MiniMQTT Client + self._client = MQTTClient(socket, + 'io.adafruit.com', + port=8883, + username=self._user, + password=self._key, + network_manager=self._network_manager) # User-defined MQTT callback methods need to be init'd to none self.on_connect = None self.on_disconnect = None self.on_message = None self.on_subscribe = None + self.on_unsubscribe = None # MQTT event callbacks self._client.on_connect = self._on_connect_mqtt self._client.on_disconnect = self._on_disconnect_mqtt self._client.on_message = self._on_message_mqtt - self._logger = None - if hasattr(self._client, '_logger'): + self._logger = False + if self._client._logger is not None: self._logger = True self._client.set_logger_level('DEBUG') self._connected = False @property def is_connected(self): - """Returns True if class is connected to Adafruit IO.""" + """Returns True if is connected to the to Adafruit IO + MQTT Broker. + """ return self._connected def connect(self): - """Connects to Adafruit IO, must be called before any - other API methods are called. + """Connects to the Adafruit IO MQTT Broker. + Must be called before any other API methods are called. """ if self._connected: return @@ -100,9 +111,8 @@ def disconnect(self): if self._connected: self._client.disconnect() - def _on_connect_mqtt(self, client, userdata, flags, rc): - """Runs when the on_connect callback is run from user-code. + """Runs when the on_connect callback is run from code. """ if self._logger: self._client._logger.debug('Client called on_connect.') @@ -117,7 +127,7 @@ def _on_connect_mqtt(self, client, userdata, flags, rc): def _on_disconnect_mqtt(self, client, userdata, rc): """Runs when the on_disconnect callback is run from - user-code. + code. """ if self._logger: self._client._logger.debug('Client called on_disconnect') @@ -127,25 +137,160 @@ def _on_disconnect_mqtt(self, client, userdata, rc): self.on_disconnect(self) def _on_message_mqtt(self, client, topic, message): - """Runs when the on_message callback is run from user-code. + """Runs when the on_message callback is run from code. Performs parsing based on username/feed/feed-key. """ if self._logger: self._client._logger.debug('Client called on_message.') - print('MSG: ', message) if self.on_message is not None: - parsed_feed = message.topic.split('/') - feed = parsed_feed[2] - message = '' if message is None else message.decode('utf-8') + # parse the feed/group name + topic_name = topic.split('/') + topic_name = topic_name[2] + # parse the message + message = '' if message is None else message else: - raise ValueError('Define an on_message method before calling this callback.') - self.on_message(self, feed, message) + raise ValueError('You must define an on_message method before calling this callback.') + self.on_message(self, topic_name, message) + + def loop(self): + """Manually process messages from Adafruit IO. + Use this method to check incoming subscription messages. + """ + self._client.loop() + + def loop_blocking(self): + """Starts a blocking loop and to processes messages + from Adafruit IO. Code below this call will not run. + """ + self._client.loop_forever() + + # Subscription + def subscribe(self, feed_key=None, group_key=None, shared_user=None): + """Subscribes to an Adafruit IO feed or group. + Can also subscribe to someone else's feed. + :param str feed_key: Adafruit IO Feed key. + :param str group_key: Adafruit IO Group key. + :param str shared_user: Owner of the Adafruit IO feed, required for shared feeds. + + Example of subscribing to an Adafruit IO Feed named 'temperature': + + .. code-block:: python + + client.subscribe('temperature') + Example of subscribing to two Adafruit IO feeds: `temperature` + and `humidity` + + .. code-block:: python + + client.subscribe([('temperature'), ('humidity')]) + """ + if shared_user is not None and feed_key is not None: + self._client.subscribe('{0}/feeds/{1}'.format(shared_user, feed_key)) + elif group_key is not None: + self._client.subscribe('{0}/groups/{1}'.format(self._user, feed_key)) + elif feed_key is not None: + self._client.subscribe('{0}/feeds/{1}'.format(self._user, feed_key)) + else: + raise AdafruitIO_MQTTError('Must provide a feed_key or group_key.') + def subscribe(self, feed_key=None, group_key=None, shared_user=None): + """Unsubscribes from an Adafruit IO feed or group. + Can also subscribe to someone else's feed. + :param str feed_key: Adafruit IO Feed key. + :param str group_key: Adafruit IO Group key. + :param str shared_user: Owner of the Adafruit IO feed, required for shared feeds. + + Example of unsubscribing from an Adafruit IO Feed named 'temperature': + .. code-block:: python + client.unsubscribe('temperature') + Example of unsubscribing to two Adafruit IO feeds: `temperature` + and `humidity` + .. code-block:: python + + client.unsubscribe([('temperature'), ('humidity')]) + """ + if shared_user is not None and feed_key is not None: + self._client.subscribe('{0}/feeds/{1}'.format(shared_user, feed_key)) + elif group_key is not None: + self._client.subscribe('{0}/groups/{1}'.format(self._user, feed_key)) + elif feed_key is not None: + self._client.subscribe('{0}/feeds/{1}'.format(self._user, feed_key)) + else: + raise AdafruitIO_MQTTError('Must provide a feed_key or group_key.') + + # Publishing + def publish(self, feed_key, data, shared_user=None, is_group=False): + """Publishes to an An Adafruit IO Feed. + :param str feed_key: Adafruit IO Feed key. + :param str data: Data to publish to the feed or group. + :param int data: Data to publish to the feed or group. + :param float data: Data to publish to the feed or group. + :param str shared_user: Owner of the Adafruit IO feed, required for + feed sharing. + :param bool is_group: Set True if publishing to an Adafruit IO Group. + + Example of publishing an integer to Adafruit IO on feed 'temperature'. + ..code-block:: python + + client.publish('temperature', 30) + + Example of publishing a floating point value to Adafruit IO on feed 'temperature'. + ..code-block:: python + + client.publish('temperature', 3.14) + + Example of publishing a string to Adafruit IO on feed 'temperature'. + ..code-block:: python + + client.publish('temperature, 'thirty degrees') + + Example of publishing an integer to Adafruit IO on group 'weatherstation'. + ..code-block:: python + + client.publish('weatherstation', 12, is_group=True) + + Example of publishing to a shared Adafruit IO feed. + ..code-block:: python + + client.publish('temperature', shared_user='myfriend') + + """ + if is_group: + self._client.publish('{0}/groups/{1}'.format(self._user, feed_key), data) + return + elif shared_user is not None: + self._client.publish('{0}/feeds/{1}'.format(shared_user, feed_key), data) + else: + self._client.publish('{0}/feeds/{1}'.format(self._user, feed_key), data) + + + def publish_multiple(self, feeds_and_data, timeout=5, is_group=False): + """Publishes multiple data points to multiple feeds or groups. + :param str feeds_and_data: List of tuples containing topic strings and data values. + :param int timeout: Delay between publishing data points to Adafruit IO. + + Example of publishing multiple data points to Adafruit IO: + ..code-block:: python + + client.publish_multiple([('DemoFeed', value), ('testfeed', value)]) + + """ + if isinstance(feeds_and_data, list): + feed_data = [] + for t, d in feeds_and_data: + feed_data.append((t, q)) + else: + raise AdafruitIO_MQTTError('This method accepts a list of tuples.') + for t, d in feed_data: + if is_group: + self._client.publish('{0}/feeds/{1}'.format(self._user, feed_key), data) + else: + self._client.publish('{0}/groups/{1}'.format(self._user, feed_key), data) class RESTClient(): """ From 11ad65e8006ed6f64ad1a744fc64171cb4effebe Mon Sep 17 00:00:00 2001 From: brentru Date: Thu, 11 Jul 2019 16:07:53 -0400 Subject: [PATCH 05/35] cleanup publish_multiple a bit.. --- adafruit_io/adafruit_io.py | 61 +++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/adafruit_io/adafruit_io.py b/adafruit_io/adafruit_io.py index 416acf8..8802abb 100755 --- a/adafruit_io/adafruit_io.py +++ b/adafruit_io/adafruit_io.py @@ -38,7 +38,7 @@ * Adafruit CircuitPython MiniMQTT: https://github.com/adafruit/Adafruit_CircuitPython_MiniMQTT """ -from time import struct_time +import time from adafruit_minimqtt import MQTT as MQTTClient #from adafruit_io.adafruit_io_errors import AdafruitIO_RequestError, AdafruitIO_ThrottleError @@ -194,7 +194,7 @@ def subscribe(self, feed_key=None, group_key=None, shared_user=None): else: raise AdafruitIO_MQTTError('Must provide a feed_key or group_key.') - def subscribe(self, feed_key=None, group_key=None, shared_user=None): + def unsubscribe(self, feed_key=None, group_key=None, shared_user=None): """Unsubscribes from an Adafruit IO feed or group. Can also subscribe to someone else's feed. :param str feed_key: Adafruit IO Feed key. @@ -215,15 +215,40 @@ def subscribe(self, feed_key=None, group_key=None, shared_user=None): client.unsubscribe([('temperature'), ('humidity')]) """ if shared_user is not None and feed_key is not None: - self._client.subscribe('{0}/feeds/{1}'.format(shared_user, feed_key)) + self._client.unsubscribe('{0}/feeds/{1}'.format(shared_user, feed_key)) elif group_key is not None: - self._client.subscribe('{0}/groups/{1}'.format(self._user, feed_key)) + self._client.unsubscribe('{0}/groups/{1}'.format(self._user, feed_key)) elif feed_key is not None: - self._client.subscribe('{0}/feeds/{1}'.format(self._user, feed_key)) + self._client.unsubscribe('{0}/feeds/{1}'.format(self._user, feed_key)) else: raise AdafruitIO_MQTTError('Must provide a feed_key or group_key.') # Publishing + def publish_multiple(self, feeds_and_data, timeout=3, is_group=False): + """Publishes multiple data points to multiple feeds or groups. + :param str feeds_and_data: List of tuples containing topic strings and data values. + :param int timeout: Delay between publishing data points to Adafruit IO. + + Example of publishing multiple data points to Adafruit IO: + ..code-block:: python + + client.publish_multiple([('DemoFeed', value), ('testfeed', value)]) + + """ + if isinstance(feeds_and_data, list): + feed_data = [] + for t, d in feeds_and_data: + feed_data.append((t, d)) + else: + raise AdafruitIO_MQTTError('This method accepts a list of tuples.') + for t, d in feed_data: + if is_group: + self.publish(t, d, is_group=True) + else: + print('publishing: {0} to {1}'.format(d, t)) + self.publish(t, d) + time.sleep(timeout) + def publish(self, feed_key, data, shared_user=None, is_group=False): """Publishes to an An Adafruit IO Feed. :param str feed_key: Adafruit IO Feed key. @@ -268,30 +293,6 @@ def publish(self, feed_key, data, shared_user=None, is_group=False): else: self._client.publish('{0}/feeds/{1}'.format(self._user, feed_key), data) - - def publish_multiple(self, feeds_and_data, timeout=5, is_group=False): - """Publishes multiple data points to multiple feeds or groups. - :param str feeds_and_data: List of tuples containing topic strings and data values. - :param int timeout: Delay between publishing data points to Adafruit IO. - - Example of publishing multiple data points to Adafruit IO: - ..code-block:: python - - client.publish_multiple([('DemoFeed', value), ('testfeed', value)]) - - """ - if isinstance(feeds_and_data, list): - feed_data = [] - for t, d in feeds_and_data: - feed_data.append((t, q)) - else: - raise AdafruitIO_MQTTError('This method accepts a list of tuples.') - for t, d in feed_data: - if is_group: - self._client.publish('{0}/feeds/{1}'.format(self._user, feed_key), data) - else: - self._client.publish('{0}/groups/{1}'.format(self._user, feed_key), data) - class RESTClient(): """ Client for interacting with the Adafruit IO HTTP API. @@ -514,5 +515,5 @@ def receive_time(self): """ path = self._compose_path('integrations/time/struct.json') time = self._get(path) - return struct_time((time['year'], time['mon'], time['mday'], time['hour'], + return time.struct_time((time['year'], time['mon'], time['mday'], time['hour'], time['min'], time['sec'], time['wday'], time['yday'], time['isdst'])) From fff6baa3fc9881b8fa8a77989e5a07b783bbe61f Mon Sep 17 00:00:00 2001 From: brentru Date: Thu, 11 Jul 2019 16:20:17 -0400 Subject: [PATCH 06/35] add subpub example code! --- .../adafruit_io_mqtt_subscribe_publish.py | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100755 examples/adafruit_io_mqtt_subscribe_publish.py diff --git a/examples/adafruit_io_mqtt_subscribe_publish.py b/examples/adafruit_io_mqtt_subscribe_publish.py new file mode 100755 index 0000000..43830b5 --- /dev/null +++ b/examples/adafruit_io_mqtt_subscribe_publish.py @@ -0,0 +1,119 @@ +# Example of using the Adafruit IO CircuitPython MQTT client +# to subscribe to an Adafruit IO feed and publish random data +# to be received by the feed. +# +# Example by Tony DiCola for Adafruit Industries +# Modified by Brent Rubell for Adafruit Industries, 2019 +import time +from random import randint +import board +import neopixel +import busio +from digitalio import DigitalInOut + +# Import WiFi configuration +from adafruit_esp32spi import adafruit_esp32spi +from adafruit_esp32spi import adafruit_esp32spi_wifimanager +import adafruit_esp32spi.adafruit_esp32spi_socket as socket + +# Import the Adafruit IO MQTT Class +from adafruit_io.adafruit_io import MQTT + +### WiFi ### + +# 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 + +# If you are using a board with pre-defined ESP32 Pins: +esp32_cs = DigitalInOut(board.ESP_CS) +esp32_ready = DigitalInOut(board.ESP_BUSY) +esp32_reset = DigitalInOut(board.ESP_RESET) + +# If you have an externally connected ESP32: +# esp32_cs = DigitalInOut(board.D9) +# esp32_ready = DigitalInOut(board.D10) +# esp32_reset = DigitalInOut(board.D5) + +spi = busio.SPI(board.SCK, board.MOSI, board.MISO) +esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset) +"""Use below for Most Boards""" +status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2) # Uncomment for Most Boards +"""Uncomment below for ItsyBitsy M4""" +# status_light = dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1, brightness=0.2) +# Uncomment below for an externally defined RGB LED +# import adafruit_rgbled +# from adafruit_esp32spi import PWMOut +# RED_LED = PWMOut.PWMOut(esp, 26) +# GREEN_LED = PWMOut.PWMOut(esp, 27) +# BLUE_LED = PWMOut.PWMOut(esp, 25) +# status_light = adafruit_rgbled.RGBLED(RED_LED, BLUE_LED, GREEN_LED) +wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light) + +# Define callback functions which will be called when certain events happen. +def connected(client): + # Connected function will be called when the client is connected to Adafruit IO. + # This is a good place to subscribe to feed changes. The client parameter + # passed to this function is the Adafruit IO MQTT client so you can make + # calls against it easily. + print('Connected to Adafruit IO! Listening for DemoFeed changes...') + # Subscribe to changes on a feed named DemoFeed. + client.subscribe('DemoFeed') + +def disconnected(client): + # Disconnected function will be called when the client disconnects. + print('Disconnected from Adafruit IO!') + sys.exit(1) + +def message(client, feed_id, payload): + # Message function will be called when a subscribed feed has a new value. + # The feed_id parameter identifies the feed, and the payload parameter has + # the new value. + print('Feed {0} received new value: {1}'.format(feed_id, payload)) + +# Connect to WiFi +wifi.connect() + +# Create an Adafruit IO MQTT client. +client = MQTT(secrets['aio_user'], + secrets['aio_password'], + wifi, + socket) + +# Setup the callback functions defined above. +client.on_connect = connected +client.on_disconnect = disconnected +client.on_message = message + +# Connect to the Adafruit IO server. +client.connect() + +# Now the program needs to use a client loop function to ensure messages are +# sent and received. There are a two options for driving the message loop: + +# You can pump the message loop yourself by periodically calling +# the client loop function. Notice how the loop below changes to call loop +# continuously while still sending a new message every 10 seconds. This is a +# good option if you don't want to or can't have a thread pumping the message +# loop in the background. +last = 0 +print('Publishing a new message every 10 seconds (press Ctrl-C to quit)...') +while True: + # Explicitly pump the message loop. + client.loop() + # Send a new message every 10 seconds. + if (time.monotonic() - last) >= 5: + value = randint(0, 100) + print('Publishing {0} to DemoFeed.'.format(value)) + client.publish('DemoFeed', value) + last = time.monotonic() + +# Or you can just call loop_blocking. This will run a message loop +# forever, so your program will not get past the loop_blocking call. This is +# good for simple programs which only listen to events. For more complex programs +# you probably need to have a background thread loop or explicit message loop like +# the two previous examples above. +# client.loop_blocking() From fed02981c3a7ae54aaef93543b6643dca8bb6e31 Mon Sep 17 00:00:00 2001 From: brentru Date: Thu, 11 Jul 2019 16:49:46 -0400 Subject: [PATCH 07/35] add weather subscription method and example --- adafruit_io/adafruit_io.py | 23 +++- examples/adafruit_io_mqtt_weather.py | 161 +++++++++++++++++++++++++++ 2 files changed, 180 insertions(+), 4 deletions(-) create mode 100755 examples/adafruit_io_mqtt_weather.py diff --git a/adafruit_io/adafruit_io.py b/adafruit_io/adafruit_io.py index 8802abb..898fa53 100755 --- a/adafruit_io/adafruit_io.py +++ b/adafruit_io/adafruit_io.py @@ -49,6 +49,12 @@ 'User-Agent': 'AIO-CircuitPython/{0}'.format(__version__) } +forecast_types = ["current", "forecast_minutes_5", + "forecast_minutes_30", "forecast_hours_1", + "forecast_hours_2", "forecast_hours_6", + "forecast_hours_24", "forecast_days_1", + "forecast_days_2", "forecast_days_5",] + class MQTT(): """ Client for interacting with the Adafruit IO MQTT API. The client establishes @@ -164,7 +170,7 @@ def loop_blocking(self): """ self._client.loop_forever() - # Subscription + # Subscriptions def subscribe(self, feed_key=None, group_key=None, shared_user=None): """Subscribes to an Adafruit IO feed or group. Can also subscribe to someone else's feed. @@ -194,6 +200,15 @@ def subscribe(self, feed_key=None, group_key=None, shared_user=None): else: raise AdafruitIO_MQTTError('Must provide a feed_key or group_key.') + + def subscribe_weather(self, integration_id, forecast_type): + """Subscribes to a weather forecast using the Adafruit IO PLUS weather + service. This feature is only avaliable to Adafruit IO PLUS subscribers. + :param int integration_id: Weather record you want data for. + :param str forecast_type: Forecast data you'd like to recieve. + """ + self._client.subscribe('{0}/integration/weather/{1}/{2}'.format(self._user, integration_id, forecast_type)) + def unsubscribe(self, feed_key=None, group_key=None, shared_user=None): """Unsubscribes from an Adafruit IO feed or group. Can also subscribe to someone else's feed. @@ -228,11 +243,12 @@ def publish_multiple(self, feeds_and_data, timeout=3, is_group=False): """Publishes multiple data points to multiple feeds or groups. :param str feeds_and_data: List of tuples containing topic strings and data values. :param int timeout: Delay between publishing data points to Adafruit IO. + :param bool is_group: Set to True if you're publishing to a group. - Example of publishing multiple data points to Adafruit IO: + Example of publishing multiple data points on different feeds to Adafruit IO: ..code-block:: python - client.publish_multiple([('DemoFeed', value), ('testfeed', value)]) + client.publish_multiple([('humidity', 24.5), ('temperature', 54)]) """ if isinstance(feeds_and_data, list): @@ -245,7 +261,6 @@ def publish_multiple(self, feeds_and_data, timeout=3, is_group=False): if is_group: self.publish(t, d, is_group=True) else: - print('publishing: {0} to {1}'.format(d, t)) self.publish(t, d) time.sleep(timeout) diff --git a/examples/adafruit_io_mqtt_weather.py b/examples/adafruit_io_mqtt_weather.py new file mode 100755 index 0000000..71624ab --- /dev/null +++ b/examples/adafruit_io_mqtt_weather.py @@ -0,0 +1,161 @@ +# Example of using the Adafruit IO CircuitPython MQTT Client +# for subscribe to a weather forecast provided by the +# Adafruit IO Weather Service (IO Plus subscribers ONLY). +# This example uses ESP32SPI to connect over WiFi +# +# by Brent Rubell for Adafruit Industries, 2019 +import time +from random import randint +import board +import neopixel +import busio +from digitalio import DigitalInOut + +# Import WiFi configuration +from adafruit_esp32spi import adafruit_esp32spi +from adafruit_esp32spi import adafruit_esp32spi_wifimanager +import adafruit_esp32spi.adafruit_esp32spi_socket as socket + +# Import the Adafruit IO MQTT Class +from adafruit_io.adafruit_io import MQTT + +### WiFi ### + +# 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 + +# If you are using a board with pre-defined ESP32 Pins: +esp32_cs = DigitalInOut(board.ESP_CS) +esp32_ready = DigitalInOut(board.ESP_BUSY) +esp32_reset = DigitalInOut(board.ESP_RESET) + +# If you have an externally connected ESP32: +# esp32_cs = DigitalInOut(board.D9) +# esp32_ready = DigitalInOut(board.D10) +# esp32_reset = DigitalInOut(board.D5) + +spi = busio.SPI(board.SCK, board.MOSI, board.MISO) +esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset) +"""Use below for Most Boards""" +status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2) # Uncomment for Most Boards +"""Uncomment below for ItsyBitsy M4""" +# status_light = dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1, brightness=0.2) +# Uncomment below for an externally defined RGB LED +# import adafruit_rgbled +# from adafruit_esp32spi import PWMOut +# RED_LED = PWMOut.PWMOut(esp, 26) +# GREEN_LED = PWMOut.PWMOut(esp, 27) +# BLUE_LED = PWMOut.PWMOut(esp, 25) +# status_light = adafruit_rgbled.RGBLED(RED_LED, BLUE_LED, GREEN_LED) +wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light) + +# Set to ID of the forecast to subscribe to for updates +forecast_id = 2127 + +# Set to the ID of the feed to subscribe to for updates. +""" +Valid forecast types are: +current +forecast_minutes_5 +forecast_minutes_30 +forecast_hours_1 +forecast_hours_2 +forecast_hours_6 +forecast_hours_24 +forecast_days_1 +forecast_days_2 +forecast_days_5 +""" +# Subscribe to the current forecast +forecast_today = 'current' +# Subscribe to tomorrow's forecast +forecast_two_days = 'forecast_days_2' +# Subscribe to forecast in 5 days +forecast_in_5_days = 'forecast_days_5' + +# Define callback functions which will be called when certain events happen. +# pylint: disable=redefined-outer-name +def connected(client): + # Connected function will be called when the client is connected to Adafruit IO. + # This is a good place to subscribe to feed changes. The client parameter + # passed to this function is the Adafruit IO MQTT client so you can make + # calls against it easily. + print('Connected to Adafruit IO! Listening to forecast: {0}...'.format(forecast_id)) + # Subscribe to changes on the current forecast. + client.subscribe_weather(forecast_id, forecast_today) + + # Subscribe to changes on tomorrow's forecast. + client.subscribe_weather(forecast_id, forecast_two_days) + + # Subscribe to changes on forecast in 5 days. + client.subscribe_weather(forecast_id, forecast_in_5_days) + +# pylint: disable=unused-argument +def disconnected(client): + # Disconnected function will be called when the client disconnects. + print('Disconnected from Adafruit IO!') + sys.exit(1) + +# pylint: disable=unused-argument +def message(client, topic, payload): + """Message function will be called when any subscribed forecast has an update. + Weather data is updated at most once every 20 minutes. + """ + print('NEW MESSAGE: ', topic, payload) + # forecast based on mqtt topic + if topic == 'current': + # Print out today's forecast + today_forecast = payload + print('\nCurrent Forecast') + parseForecast(today_forecast) + elif topic == 'forecast_days_2': + # Print out tomorrow's forecast + two_day_forecast = payload + print('\nWeather in Two Days') + parseForecast(two_day_forecast) + elif topic == 'forecast_days_5': + # Print out forecast in 5 days + five_day_forecast = payload + print('\nWeather in 5 Days') + parseForecast(five_day_forecast) + +def parseForecast(forecast_data): + """Parses and prints incoming forecast data + """ + # incoming data is a utf-8 string, encode it as a json object + forecast = json.loads(forecast_data) + # Print out the forecast + try: + print('It is {0} and {1}F.'.format(forecast['summary'], forecast['temperature'])) + except KeyError: + # future weather forecasts return a high and low temperature, instead of 'temperature' + print('It will be {0} with a high of {1}F and a low of {2}F.'.format( + forecast['summary'], forecast['temperatureLow'], forecast['temperatureHigh'])) + print('with humidity of {0}%, wind speed of {1}mph, and {2}% chance of precipitation.'.format( + forecast['humidity'], forecast['windSpeed'], forecast['precipProbability'])) + +# Connect to WiFi +wifi.connect() + +# Initialize a new Adafruit IO WiFi MQTT client. +client = MQTT(secrets['aio_user'], + secrets['aio_password'], + wifi, + socket) + +# Setup the callback functions defined above. +client.on_connect = connected +client.on_disconnect = disconnected +client.on_message = message + +# Connect to the Adafruit IO server. +client.connect() + +# Start a message loop that blocks forever waiting for MQTT messages to be +# received. Note there are other options for running the event loop like doing +# so in a background thread--see the mqtt_client.py example to learn more. +client.loop_blocking() From b615e845d8709b2b999f6ac7499963eef33bfd35 Mon Sep 17 00:00:00 2001 From: brentru Date: Thu, 11 Jul 2019 17:00:10 -0400 Subject: [PATCH 08/35] add methods to subscribe to randomizer service api, throttling feed --- adafruit_io/adafruit_io.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/adafruit_io/adafruit_io.py b/adafruit_io/adafruit_io.py index 898fa53..742cbae 100755 --- a/adafruit_io/adafruit_io.py +++ b/adafruit_io/adafruit_io.py @@ -200,6 +200,18 @@ def subscribe(self, feed_key=None, group_key=None, shared_user=None): else: raise AdafruitIO_MQTTError('Must provide a feed_key or group_key.') + def subscribe_throttling(self): + """Subscribes to the username/throttle feed to + notify you if your Adafruit IO rate limit has been + exceeded. + """ + self._client.subscribe('%s/throttle'%self._user) + + def subscribe_randomizer(self, randomizer_id): + """Subscribes to a random data stream created by the Adafruit IO Words service. + :param int randomizer_id: Random word record you want data for. + """ + self._client.subscribe('{0}/integration/words/{1}'.format(self._user, randomizer_id)) def subscribe_weather(self, integration_id, forecast_type): """Subscribes to a weather forecast using the Adafruit IO PLUS weather From 2306cbf7fdcb431cfd342a76e123940cec90788b Mon Sep 17 00:00:00 2001 From: brentru Date: Tue, 16 Jul 2019 13:57:54 -0400 Subject: [PATCH 09/35] add /get retain method, modify on_message_mqtt to correctly parse a group --- adafruit_io/adafruit_io.py | 46 ++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/adafruit_io/adafruit_io.py b/adafruit_io/adafruit_io.py index 742cbae..ebd1fa3 100755 --- a/adafruit_io/adafruit_io.py +++ b/adafruit_io/adafruit_io.py @@ -145,15 +145,27 @@ def _on_disconnect_mqtt(self, client, userdata, rc): def _on_message_mqtt(self, client, topic, message): """Runs when the on_message callback is run from code. Performs parsing based on username/feed/feed-key. + :param MQTT client: A MQTT Client Instance. + :param str topic: MQTT topic response from Adafruit IO. + :param str message: MQTT message data response from Adafruit IO. """ if self._logger: self._client._logger.debug('Client called on_message.') if self.on_message is not None: - # parse the feed/group name + # Parse the MQTT topic string topic_name = topic.split('/') - topic_name = topic_name[2] - # parse the message - message = '' if message is None else message + if topic_name[1] == "groups": + print(message) + message = eval(message) + for feed in message['feeds']: + topic_name = feed # TODO: change this to topic_name + message = message['feeds'][topic] + print(topic, message) + topic_name = topic + else: + topic_name = topic_name[2] + # parse the message + message = '' if message is None else message else: raise ValueError('You must define an on_message method before calling this callback.') self.on_message(self, topic_name, message) @@ -169,7 +181,7 @@ def loop_blocking(self): from Adafruit IO. Code below this call will not run. """ self._client.loop_forever() - + # Subscriptions def subscribe(self, feed_key=None, group_key=None, shared_user=None): """Subscribes to an Adafruit IO feed or group. @@ -194,26 +206,27 @@ def subscribe(self, feed_key=None, group_key=None, shared_user=None): if shared_user is not None and feed_key is not None: self._client.subscribe('{0}/feeds/{1}'.format(shared_user, feed_key)) elif group_key is not None: - self._client.subscribe('{0}/groups/{1}'.format(self._user, feed_key)) + print('subscribing to group...') + self._client.subscribe('{0}/groups/{1}'.format(self._user, group_key)) elif feed_key is not None: self._client.subscribe('{0}/feeds/{1}'.format(self._user, feed_key)) else: raise AdafruitIO_MQTTError('Must provide a feed_key or group_key.') - def subscribe_throttling(self): - """Subscribes to the username/throttle feed to - notify you if your Adafruit IO rate limit has been - exceeded. + def subscribe_to_throttling(self): + """Subscribes to the throttle feed to notify you + if your Adafruit IO rate limit has been exceeded. + https://io.adafruit.com/api/docs/mqtt.html#mqtt-api-rate-limiting """ self._client.subscribe('%s/throttle'%self._user) - def subscribe_randomizer(self, randomizer_id): + def subscribe_to_randomizer(self, randomizer_id): """Subscribes to a random data stream created by the Adafruit IO Words service. :param int randomizer_id: Random word record you want data for. """ self._client.subscribe('{0}/integration/words/{1}'.format(self._user, randomizer_id)) - def subscribe_weather(self, integration_id, forecast_type): + def subscribe_to_weather(self, integration_id, forecast_type): """Subscribes to a weather forecast using the Adafruit IO PLUS weather service. This feature is only avaliable to Adafruit IO PLUS subscribers. :param int integration_id: Weather record you want data for. @@ -240,6 +253,7 @@ def unsubscribe(self, feed_key=None, group_key=None, shared_user=None): .. code-block:: python client.unsubscribe([('temperature'), ('humidity')]) + """ if shared_user is not None and feed_key is not None: self._client.unsubscribe('{0}/feeds/{1}'.format(shared_user, feed_key)) @@ -320,6 +334,14 @@ def publish(self, feed_key, data, shared_user=None, is_group=False): else: self._client.publish('{0}/feeds/{1}'.format(self._user, feed_key), data) + def get(self, feed_key): + """Calling this method will make Adafruit IO publish the most recent + value on feed_key. + https://io.adafruit.com/api/docs/mqtt.html#retained-values + :param str feed_key: Adafruit IO Feed key. + """ + self._client.publish('{0}/feeds{1}/get'.format(self._user, feed_key), '\0') + class RESTClient(): """ Client for interacting with the Adafruit IO HTTP API. From febfdb765cd87fe8d9fe1187282c1f5218f7face Mon Sep 17 00:00:00 2001 From: brentru Date: Tue, 16 Jul 2019 16:12:22 -0400 Subject: [PATCH 10/35] start refactoring - get rid of MMQTT dep, it shouldnt be called from the lib if someone wants two clients --- adafruit_io/adafruit_io.py | 76 +++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 43 deletions(-) diff --git a/adafruit_io/adafruit_io.py b/adafruit_io/adafruit_io.py index ebd1fa3..173d500 100755 --- a/adafruit_io/adafruit_io.py +++ b/adafruit_io/adafruit_io.py @@ -39,7 +39,6 @@ https://github.com/adafruit/Adafruit_CircuitPython_MiniMQTT """ import time -from adafruit_minimqtt import MQTT as MQTTClient #from adafruit_io.adafruit_io_errors import AdafruitIO_RequestError, AdafruitIO_ThrottleError __version__ = "0.0.0-auto.0" @@ -49,37 +48,20 @@ 'User-Agent': 'AIO-CircuitPython/{0}'.format(__version__) } -forecast_types = ["current", "forecast_minutes_5", - "forecast_minutes_30", "forecast_hours_1", - "forecast_hours_2", "forecast_hours_6", - "forecast_hours_24", "forecast_days_1", - "forecast_days_2", "forecast_days_5",] - -class MQTT(): +class MQTT_API(): """ Client for interacting with the Adafruit IO MQTT API. The client establishes a secure connection to Adafruit IO by default. - :param str aio_username: Adafruit.io account username. - :param str aio_key: Adafruit.io active key. - :param network_manager: NetworkManager object, such as WiFiManager from ESPSPI_WiFiManager. - :param bool secure: Enables SSL/TLS connection. + :param MiniMQTT mqtt_client: MiniMQTT Client object. + :param bool secure: Enables a secure SSL/TLS connection with Adafruit IO. """ - def __init__(self, aio_username, aio_key, network_manager, socket, secure=True): + def __init__(self, mqtt_client, secure=True): self._user = aio_username self._key = aio_key - # Network interface hardware detection - manager_type = str(type(network_manager)) - if ('ESPSPI_WiFiManager' in manager_type or 'ESPAT_WiFiManager' in manager_type): - self._network_manager = network_manager - else: - raise TypeError("This library requires a NetworkManager object.") - # Set up a MiniMQTT Client - self._client = MQTTClient(socket, - 'io.adafruit.com', - port=8883, - username=self._user, - password=self._key, - network_manager=self._network_manager) + # MiniMQTT Object + print(type(mqtt_client)) + print('MQTT CLIENT: ', mqtt_client) + self._client = mqtt_client # User-defined MQTT callback methods need to be init'd to none self.on_connect = None self.on_disconnect = None @@ -98,9 +80,7 @@ def __init__(self, aio_username, aio_key, network_manager, socket, secure=True): @property def is_connected(self): - """Returns True if is connected to the to Adafruit IO - MQTT Broker. - """ + """Returns if connected to Adafruit IO MQTT Broker.""" return self._connected def connect(self): @@ -142,12 +122,12 @@ def _on_disconnect_mqtt(self, client, userdata, rc): if self.on_disconnect is not None: self.on_disconnect(self) - def _on_message_mqtt(self, client, topic, message): + def _on_message_mqtt(self, client, topic, payload): """Runs when the on_message callback is run from code. - Performs parsing based on username/feed/feed-key. + Parses incoming data from special Adafruit IO feeds. :param MQTT client: A MQTT Client Instance. :param str topic: MQTT topic response from Adafruit IO. - :param str message: MQTT message data response from Adafruit IO. + :param str payload: MQTT payload data response from Adafruit IO. """ if self._logger: self._client._logger.debug('Client called on_message.') @@ -155,17 +135,22 @@ def _on_message_mqtt(self, client, topic, message): # Parse the MQTT topic string topic_name = topic.split('/') if topic_name[1] == "groups": - print(message) - message = eval(message) - for feed in message['feeds']: - topic_name = feed # TODO: change this to topic_name - message = message['feeds'][topic] - print(topic, message) - topic_name = topic + # Adafruit IO Group Feed Parsing + # May have rx'd more than one feed - parse them. + feeds = [] + messages = [] + payload = eval(payload) + for feed in payload['feeds']: + feeds.append(feed) + for msg in feeds: + payload = payload['feeds'][msg] + messages.append(payload) + topic_name = feeds + message = messages else: topic_name = topic_name[2] - # parse the message - message = '' if message is None else message + # parse the payload + message = '' if payload is None else message else: raise ValueError('You must define an on_message method before calling this callback.') self.on_message(self, topic_name, message) @@ -214,12 +199,17 @@ def subscribe(self, feed_key=None, group_key=None, shared_user=None): raise AdafruitIO_MQTTError('Must provide a feed_key or group_key.') def subscribe_to_throttling(self): - """Subscribes to the throttle feed to notify you - if your Adafruit IO rate limit has been exceeded. + """Subscribes to your personal Adafruit IO /throttle feed. https://io.adafruit.com/api/docs/mqtt.html#mqtt-api-rate-limiting """ self._client.subscribe('%s/throttle'%self._user) + def subscribe_to_errors(self): + """Subscribes to your personal Adafruit IO /errors feed. + Notifies you of errors relating to publish/subscribe calls. + """ + self._client.subscribe('%s/errors'%self._user) + def subscribe_to_randomizer(self, randomizer_id): """Subscribes to a random data stream created by the Adafruit IO Words service. :param int randomizer_id: Random word record you want data for. From 3b5d0a2d677cbb662859d92084be10a880f2814e Mon Sep 17 00:00:00 2001 From: brentru Date: Tue, 16 Jul 2019 17:27:53 -0400 Subject: [PATCH 11/35] refactor to use an external mqtt client, not init one. --- adafruit_io/adafruit_io.py | 39 ++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/adafruit_io/adafruit_io.py b/adafruit_io/adafruit_io.py index 173d500..8c1cd55 100755 --- a/adafruit_io/adafruit_io.py +++ b/adafruit_io/adafruit_io.py @@ -39,7 +39,7 @@ https://github.com/adafruit/Adafruit_CircuitPython_MiniMQTT """ import time -#from adafruit_io.adafruit_io_errors import AdafruitIO_RequestError, AdafruitIO_ThrottleError +from adafruit_io.adafruit_io_errors import AdafruitIO_RequestError, AdafruitIO_ThrottleError, AdafruitIO_MQTTError __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Adafruit_IO.git" @@ -48,7 +48,7 @@ 'User-Agent': 'AIO-CircuitPython/{0}'.format(__version__) } -class MQTT_API(): +class IO_MQTT(): """ Client for interacting with the Adafruit IO MQTT API. The client establishes a secure connection to Adafruit IO by default. @@ -56,13 +56,14 @@ class MQTT_API(): :param bool secure: Enables a secure SSL/TLS connection with Adafruit IO. """ def __init__(self, mqtt_client, secure=True): - self._user = aio_username - self._key = aio_key # MiniMQTT Object - print(type(mqtt_client)) - print('MQTT CLIENT: ', mqtt_client) - self._client = mqtt_client - # User-defined MQTT callback methods need to be init'd to none + mqtt_client_type = str(type(mqtt_client)) + if 'MQTT' in mqtt_client_type: + self._client = mqtt_client + else: + raise TypeError("This class requires a MiniMQTT client.") + self._user = self._client._user + # User-defined MQTT callback methods must be init'd to None self.on_connect = None self.on_disconnect = None self.on_message = None @@ -78,25 +79,27 @@ def __init__(self, mqtt_client, secure=True): self._client.set_logger_level('DEBUG') self._connected = False - @property - def is_connected(self): - """Returns if connected to Adafruit IO MQTT Broker.""" - return self._connected - def connect(self): """Connects to the Adafruit IO MQTT Broker. Must be called before any other API methods are called. """ - if self._connected: - return - self._client.connect() - + try: + self._client.connect() + except error as err: + AdafruitIO_MQTTError(err) + return + def disconnect(self): """Disconnects from Adafruit IO. """ if self._connected: self._client.disconnect() + @property + def is_connected(self): + """Returns if connected to Adafruit IO MQTT Broker.""" + return self._client.is_connected + def _on_connect_mqtt(self, client, userdata, flags, rc): """Runs when the on_connect callback is run from code. """ @@ -188,10 +191,10 @@ def subscribe(self, feed_key=None, group_key=None, shared_user=None): client.subscribe([('temperature'), ('humidity')]) """ + print('sub called!') if shared_user is not None and feed_key is not None: self._client.subscribe('{0}/feeds/{1}'.format(shared_user, feed_key)) elif group_key is not None: - print('subscribing to group...') self._client.subscribe('{0}/groups/{1}'.format(self._user, group_key)) elif feed_key is not None: self._client.subscribe('{0}/feeds/{1}'.format(self._user, feed_key)) From 2d51477db050b8ebe2b3594173e9ab5928599d43 Mon Sep 17 00:00:00 2001 From: brentru Date: Tue, 16 Jul 2019 17:29:03 -0400 Subject: [PATCH 12/35] add a mqtterror class --- adafruit_io/adafruit_io_errors.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/adafruit_io/adafruit_io_errors.py b/adafruit_io/adafruit_io_errors.py index 9e9ecea..c9a361b 100755 --- a/adafruit_io/adafruit_io_errors.py +++ b/adafruit_io/adafruit_io_errors.py @@ -39,3 +39,8 @@ def __init__(self, response): error = response_content['error'] super(AdafruitIO_RequestError, self).__init__("Adafruit IO Error {0}: {1}" .format(response.status_code, error)) + +class AdafruitIO_MQTTError(Exception): + """Adafruit IO MQTT error class""" + def __init__(self, response): + super(AdafruitIO_ThrottleError, self).__init__('Adafruit IO MQTT Error: {0}'.format(response)) From 1ffa360a9d2d00b97957b07f20ca3f941dcb7a81 Mon Sep 17 00:00:00 2001 From: brentru Date: Tue, 16 Jul 2019 17:57:08 -0400 Subject: [PATCH 13/35] add special time topics, on_message parsing --- adafruit_io/adafruit_io.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/adafruit_io/adafruit_io.py b/adafruit_io/adafruit_io.py index 8c1cd55..802713f 100755 --- a/adafruit_io/adafruit_io.py +++ b/adafruit_io/adafruit_io.py @@ -107,7 +107,6 @@ def _on_connect_mqtt(self, client, userdata, flags, rc): self._client._logger.debug('Client called on_connect.') if rc == 0: self._connected = True - print('Connected to Adafruit IO!') else: raise AdafruitIO_MQTTError(rc) # Call the user-defined on_connect callback if defined @@ -138,8 +137,7 @@ def _on_message_mqtt(self, client, topic, payload): # Parse the MQTT topic string topic_name = topic.split('/') if topic_name[1] == "groups": - # Adafruit IO Group Feed Parsing - # May have rx'd more than one feed - parse them. + # Adafruit IO Group Feed(s) feeds = [] messages = [] payload = eval(payload) @@ -150,9 +148,13 @@ def _on_message_mqtt(self, client, topic, payload): messages.append(payload) topic_name = feeds message = messages + elif topic_name[0] == "time": + # Adafruit IO Time Topic + topic_name = topic_name[1] + message = payload else: + # Standard Adafruit IO Feed topic_name = topic_name[2] - # parse the payload message = '' if payload is None else message else: raise ValueError('You must define an on_message method before calling this callback.') @@ -191,7 +193,6 @@ def subscribe(self, feed_key=None, group_key=None, shared_user=None): client.subscribe([('temperature'), ('humidity')]) """ - print('sub called!') if shared_user is not None and feed_key is not None: self._client.subscribe('{0}/feeds/{1}'.format(shared_user, feed_key)) elif group_key is not None: @@ -227,6 +228,19 @@ def subscribe_to_weather(self, integration_id, forecast_type): """ self._client.subscribe('{0}/integration/weather/{1}/{2}'.format(self._user, integration_id, forecast_type)) + def subscribe_to_time(self, time_type): + """Adafruit IO provides some built-in MQTT topics for getting the current server time. + :param str time_type: Current Adafruit IO server time. Can be `seconds`, `millis`, or `iso`. + Information about these topics can be found on the Adafruit IO MQTT API Docs.: + https://io.adafruit.com/api/docs/mqtt.html#time-topics + """ + if time_type == 'seconds' or time_type == 'millis': + self._client.subscribe('time/'+time_type) + elif time_type == 'iso': + self._client.subscribe('time/ISO-8601') + else: + raise TypeError('Invalid time feed type specified') + def unsubscribe(self, feed_key=None, group_key=None, shared_user=None): """Unsubscribes from an Adafruit IO feed or group. Can also subscribe to someone else's feed. From b87ca4437aa39d146bd7aeba96811ac621931226 Mon Sep 17 00:00:00 2001 From: brentru Date: Tue, 16 Jul 2019 18:10:42 -0400 Subject: [PATCH 14/35] pylint a bunch --- adafruit_io/adafruit_io.py | 58 +++++++++++++++++-------------- adafruit_io/adafruit_io_errors.py | 2 +- 2 files changed, 33 insertions(+), 27 deletions(-) diff --git a/adafruit_io/adafruit_io.py b/adafruit_io/adafruit_io.py index 802713f..0d36b69 100755 --- a/adafruit_io/adafruit_io.py +++ b/adafruit_io/adafruit_io.py @@ -39,7 +39,8 @@ https://github.com/adafruit/Adafruit_CircuitPython_MiniMQTT """ import time -from adafruit_io.adafruit_io_errors import AdafruitIO_RequestError, AdafruitIO_ThrottleError, AdafruitIO_MQTTError +from adafruit_io.adafruit_io_errors import AdafruitIO_RequestError, AdafruitIO_ThrottleError +from adafruit_io.adafruit_io_errors import AdafruitIO_MQTTError __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Adafruit_IO.git" @@ -53,9 +54,9 @@ class IO_MQTT(): Client for interacting with the Adafruit IO MQTT API. The client establishes a secure connection to Adafruit IO by default. :param MiniMQTT mqtt_client: MiniMQTT Client object. - :param bool secure: Enables a secure SSL/TLS connection with Adafruit IO. """ - def __init__(self, mqtt_client, secure=True): + # pylint: disable=protected-access + def __init__(self, mqtt_client): # MiniMQTT Object mqtt_client_type = str(type(mqtt_client)) if 'MQTT' in mqtt_client_type: @@ -85,9 +86,8 @@ def connect(self): """ try: self._client.connect() - except error as err: - AdafruitIO_MQTTError(err) - return + except error as e: + AdafruitIO_MQTTError(e) def disconnect(self): """Disconnects from Adafruit IO. @@ -100,20 +100,22 @@ def is_connected(self): """Returns if connected to Adafruit IO MQTT Broker.""" return self._client.is_connected - def _on_connect_mqtt(self, client, userdata, flags, rc): + # pylint: disable=not-callable + def _on_connect_mqtt(self, client, userdata, flags, return_code): """Runs when the on_connect callback is run from code. """ if self._logger: self._client._logger.debug('Client called on_connect.') - if rc == 0: + if return_code == 0: self._connected = True else: - raise AdafruitIO_MQTTError(rc) + raise AdafruitIO_MQTTError(return_code) # Call the user-defined on_connect callback if defined if self.on_connect is not None: self.on_connect(self) - def _on_disconnect_mqtt(self, client, userdata, rc): + # pylint: disable=not-callable + def _on_disconnect_mqtt(self, client, userdata, return_code): """Runs when the on_disconnect callback is run from code. """ @@ -123,7 +125,8 @@ def _on_disconnect_mqtt(self, client, userdata, rc): # Call the user-defined on_disconnect callblack if defined if self.on_disconnect is not None: self.on_disconnect(self) - + + # pylint: disable=not-callable def _on_message_mqtt(self, client, topic, payload): """Runs when the on_message callback is run from code. Parses incoming data from special Adafruit IO feeds. @@ -140,6 +143,7 @@ def _on_message_mqtt(self, client, topic, payload): # Adafruit IO Group Feed(s) feeds = [] messages = [] + # TODO: Remove eval here... payload = eval(payload) for feed in payload['feeds']: feeds.append(feed) @@ -159,13 +163,13 @@ def _on_message_mqtt(self, client, topic, payload): else: raise ValueError('You must define an on_message method before calling this callback.') self.on_message(self, topic_name, message) - + def loop(self): """Manually process messages from Adafruit IO. Use this method to check incoming subscription messages. """ self._client.loop() - + def loop_blocking(self): """Starts a blocking loop and to processes messages from Adafruit IO. Code below this call will not run. @@ -179,7 +183,7 @@ def subscribe(self, feed_key=None, group_key=None, shared_user=None): :param str feed_key: Adafruit IO Feed key. :param str group_key: Adafruit IO Group key. :param str shared_user: Owner of the Adafruit IO feed, required for shared feeds. - + Example of subscribing to an Adafruit IO Feed named 'temperature': .. code-block:: python @@ -220,13 +224,14 @@ def subscribe_to_randomizer(self, randomizer_id): """ self._client.subscribe('{0}/integration/words/{1}'.format(self._user, randomizer_id)) - def subscribe_to_weather(self, integration_id, forecast_type): + def subscribe_to_weather(self, weather_record, forecast): """Subscribes to a weather forecast using the Adafruit IO PLUS weather service. This feature is only avaliable to Adafruit IO PLUS subscribers. - :param int integration_id: Weather record you want data for. - :param str forecast_type: Forecast data you'd like to recieve. + :param int weather_record: Weather record you want data for. + :param str forecast: Forecast data you'd like to recieve. """ - self._client.subscribe('{0}/integration/weather/{1}/{2}'.format(self._user, integration_id, forecast_type)) + self._client.subscribe('{0}/integration/weather/{1}/{2}'.format(self._user, + weather_record, forecast)) def subscribe_to_time(self, time_type): """Adafruit IO provides some built-in MQTT topics for getting the current server time. @@ -234,7 +239,7 @@ def subscribe_to_time(self, time_type): Information about these topics can be found on the Adafruit IO MQTT API Docs.: https://io.adafruit.com/api/docs/mqtt.html#time-topics """ - if time_type == 'seconds' or time_type == 'millis': + if 'seconds' or 'millis' in time_type: self._client.subscribe('time/'+time_type) elif time_type == 'iso': self._client.subscribe('time/ISO-8601') @@ -247,7 +252,7 @@ def unsubscribe(self, feed_key=None, group_key=None, shared_user=None): :param str feed_key: Adafruit IO Feed key. :param str group_key: Adafruit IO Group key. :param str shared_user: Owner of the Adafruit IO feed, required for shared feeds. - + Example of unsubscribing from an Adafruit IO Feed named 'temperature': .. code-block:: python @@ -280,7 +285,7 @@ def publish_multiple(self, feeds_and_data, timeout=3, is_group=False): Example of publishing multiple data points on different feeds to Adafruit IO: ..code-block:: python - + client.publish_multiple([('humidity', 24.5), ('temperature', 54)]) """ @@ -311,12 +316,12 @@ def publish(self, feed_key, data, shared_user=None, is_group=False): ..code-block:: python client.publish('temperature', 30) - + Example of publishing a floating point value to Adafruit IO on feed 'temperature'. ..code-block:: python client.publish('temperature', 3.14) - + Example of publishing a string to Adafruit IO on feed 'temperature'. ..code-block:: python @@ -336,7 +341,7 @@ def publish(self, feed_key, data, shared_user=None, is_group=False): if is_group: self._client.publish('{0}/groups/{1}'.format(self._user, feed_key), data) return - elif shared_user is not None: + if shared_user is not None: self._client.publish('{0}/feeds/{1}'.format(shared_user, feed_key), data) else: self._client.publish('{0}/feeds/{1}'.format(self._user, feed_key), data) @@ -571,5 +576,6 @@ def receive_time(self): """ path = self._compose_path('integrations/time/struct.json') time = self._get(path) - return time.struct_time((time['year'], time['mon'], time['mday'], time['hour'], - time['min'], time['sec'], time['wday'], time['yday'], time['isdst'])) + return time.struct_time((time['year'], time['mon'], time['mday'], + time['hour'], time['min'], time['sec'], + time['wday'], time['yday'], time['isdst'])) diff --git a/adafruit_io/adafruit_io_errors.py b/adafruit_io/adafruit_io_errors.py index c9a361b..68c95af 100755 --- a/adafruit_io/adafruit_io_errors.py +++ b/adafruit_io/adafruit_io_errors.py @@ -43,4 +43,4 @@ def __init__(self, response): class AdafruitIO_MQTTError(Exception): """Adafruit IO MQTT error class""" def __init__(self, response): - super(AdafruitIO_ThrottleError, self).__init__('Adafruit IO MQTT Error: {0}'.format(response)) + super(AdafruitIO_MQTTError, self).__init__('MQTT Error: {0}'.format(response)) From efeb8dfb388ba8076bc72def87b0e59b76cdf680 Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 17 Jul 2019 10:26:05 -0400 Subject: [PATCH 15/35] Black errors --- adafruit_io/adafruit_io_errors.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/adafruit_io/adafruit_io_errors.py b/adafruit_io/adafruit_io_errors.py index 68c95af..10e0389 100755 --- a/adafruit_io/adafruit_io_errors.py +++ b/adafruit_io/adafruit_io_errors.py @@ -26,21 +26,30 @@ * Author(s): Brent Rubell """ + class AdafruitIO_ThrottleError(Exception): """Adafruit IO request error class for rate-limiting""" + def __init__(self): - super(AdafruitIO_ThrottleError, self).__init__("Number of Adafruit IO Requests exceeded! \ - Please try again in 30 seconds..") + super(AdafruitIO_ThrottleError, self).__init__( + "Number of Adafruit IO Requests exceeded! \ + Please try again in 30 seconds.." + ) + class AdafruitIO_RequestError(Exception): """Adafruit IO request error class""" + def __init__(self, response): response_content = response.json() - error = response_content['error'] - super(AdafruitIO_RequestError, self).__init__("Adafruit IO Error {0}: {1}" - .format(response.status_code, error)) + error = response_content["error"] + super(AdafruitIO_RequestError, self).__init__( + "Adafruit IO Error {0}: {1}".format(response.status_code, error) + ) + class AdafruitIO_MQTTError(Exception): """Adafruit IO MQTT error class""" + def __init__(self, response): - super(AdafruitIO_MQTTError, self).__init__('MQTT Error: {0}'.format(response)) + super(AdafruitIO_MQTTError, self).__init__("MQTT Error: {0}".format(response)) From d67fcc8cffc07e7cf7badd7d242e495af7b95856 Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 17 Jul 2019 11:09:43 -0400 Subject: [PATCH 16/35] replace IO REST with IO HTTP to match IO MQTT client name, remove deps --- adafruit_io/adafruit_io.py | 170 ++++++++++-------- examples/adafruit_io_simpletest_analog_in.py | 8 +- examples/adafruit_io_simpletest_data.py | 8 +- .../adafruit_io_simpletest_digital_out.py | 8 +- examples/adafruit_io_simpletest_esp_at.py | 8 +- examples/adafruit_io_simpletest_feeds.py | 8 +- examples/adafruit_io_simpletest_groups.py | 8 +- examples/adafruit_io_simpletest_metadata.py | 8 +- examples/adafruit_io_simpletest_randomizer.py | 8 +- .../adafruit_io_simpletest_temperature.py | 8 +- examples/adafruit_io_simpletest_weather.py | 8 +- 11 files changed, 138 insertions(+), 112 deletions(-) diff --git a/adafruit_io/adafruit_io.py b/adafruit_io/adafruit_io.py index 0d36b69..2c6b518 100755 --- a/adafruit_io/adafruit_io.py +++ b/adafruit_io/adafruit_io.py @@ -23,7 +23,7 @@ `adafruit_io` ================================================================================ -A CircuitPython/Python library for communicating with Adafruit IO over WiFi +A CircuitPython library for communicating with Adafruit IO. * Author(s): Brent Rubell for Adafruit Industries @@ -34,35 +34,38 @@ * Adafruit CircuitPython firmware for the supported boards: https://github.com/adafruit/circuitpython/releases - -* Adafruit CircuitPython MiniMQTT: - https://github.com/adafruit/Adafruit_CircuitPython_MiniMQTT """ import time -from adafruit_io.adafruit_io_errors import AdafruitIO_RequestError, AdafruitIO_ThrottleError -from adafruit_io.adafruit_io_errors import AdafruitIO_MQTTError +from adafruit_io.adafruit_io_errors import ( + AdafruitIO_RequestError, + AdafruitIO_ThrottleError, + AdafruitIO_MQTTError, +) __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Adafruit_IO.git" -CLIENT_HEADERS = { - 'User-Agent': 'AIO-CircuitPython/{0}'.format(__version__) -} +CLIENT_HEADERS = {"User-Agent": "AIO-CircuitPython/{0}".format(__version__)} + -class IO_MQTT(): +class IO_MQTT: """ - Client for interacting with the Adafruit IO MQTT API. The client establishes - a secure connection to Adafruit IO by default. + Client for interacting with the Adafruit IO MQTT API. + https://io.adafruit.com/api/docs/mqtt.html#adafruit-io-mqtt-api + :param MiniMQTT mqtt_client: MiniMQTT Client object. """ + # pylint: disable=protected-access def __init__(self, mqtt_client): # MiniMQTT Object mqtt_client_type = str(type(mqtt_client)) - if 'MQTT' in mqtt_client_type: + if "MQTT" in mqtt_client_type: self._client = mqtt_client else: - raise TypeError("This class requires a MiniMQTT client.") + raise TypeError( + "This class requires a MiniMQTT client object, please create one." + ) self._user = self._client._user # User-defined MQTT callback methods must be init'd to None self.on_connect = None @@ -77,7 +80,7 @@ def __init__(self, mqtt_client): self._logger = False if self._client._logger is not None: self._logger = True - self._client.set_logger_level('DEBUG') + self._client.set_logger_level("DEBUG") self._connected = False def connect(self): @@ -105,7 +108,7 @@ def _on_connect_mqtt(self, client, userdata, flags, return_code): """Runs when the on_connect callback is run from code. """ if self._logger: - self._client._logger.debug('Client called on_connect.') + self._client._logger.debug("Client called on_connect.") if return_code == 0: self._connected = True else: @@ -120,7 +123,7 @@ def _on_disconnect_mqtt(self, client, userdata, return_code): code. """ if self._logger: - self._client._logger.debug('Client called on_disconnect') + self._client._logger.debug("Client called on_disconnect") self._connected = False # Call the user-defined on_disconnect callblack if defined if self.on_disconnect is not None: @@ -135,20 +138,20 @@ def _on_message_mqtt(self, client, topic, payload): :param str payload: MQTT payload data response from Adafruit IO. """ if self._logger: - self._client._logger.debug('Client called on_message.') + self._client._logger.debug("Client called on_message.") if self.on_message is not None: # Parse the MQTT topic string - topic_name = topic.split('/') + topic_name = topic.split("/") if topic_name[1] == "groups": # Adafruit IO Group Feed(s) feeds = [] messages = [] # TODO: Remove eval here... payload = eval(payload) - for feed in payload['feeds']: + for feed in payload["feeds"]: feeds.append(feed) for msg in feeds: - payload = payload['feeds'][msg] + payload = payload["feeds"][msg] messages.append(payload) topic_name = feeds message = messages @@ -159,9 +162,11 @@ def _on_message_mqtt(self, client, topic, payload): else: # Standard Adafruit IO Feed topic_name = topic_name[2] - message = '' if payload is None else message + message = "" if payload is None else message else: - raise ValueError('You must define an on_message method before calling this callback.') + raise ValueError( + "You must define an on_message method before calling this callback." + ) self.on_message(self, topic_name, message) def loop(self): @@ -198,31 +203,33 @@ def subscribe(self, feed_key=None, group_key=None, shared_user=None): client.subscribe([('temperature'), ('humidity')]) """ if shared_user is not None and feed_key is not None: - self._client.subscribe('{0}/feeds/{1}'.format(shared_user, feed_key)) + self._client.subscribe("{0}/feeds/{1}".format(shared_user, feed_key)) elif group_key is not None: - self._client.subscribe('{0}/groups/{1}'.format(self._user, group_key)) + self._client.subscribe("{0}/groups/{1}".format(self._user, group_key)) elif feed_key is not None: - self._client.subscribe('{0}/feeds/{1}'.format(self._user, feed_key)) + self._client.subscribe("{0}/feeds/{1}".format(self._user, feed_key)) else: - raise AdafruitIO_MQTTError('Must provide a feed_key or group_key.') + raise AdafruitIO_MQTTError("Must provide a feed_key or group_key.") def subscribe_to_throttling(self): """Subscribes to your personal Adafruit IO /throttle feed. https://io.adafruit.com/api/docs/mqtt.html#mqtt-api-rate-limiting """ - self._client.subscribe('%s/throttle'%self._user) + self._client.subscribe("%s/throttle" % self._user) def subscribe_to_errors(self): """Subscribes to your personal Adafruit IO /errors feed. Notifies you of errors relating to publish/subscribe calls. """ - self._client.subscribe('%s/errors'%self._user) + self._client.subscribe("%s/errors" % self._user) def subscribe_to_randomizer(self, randomizer_id): """Subscribes to a random data stream created by the Adafruit IO Words service. :param int randomizer_id: Random word record you want data for. """ - self._client.subscribe('{0}/integration/words/{1}'.format(self._user, randomizer_id)) + self._client.subscribe( + "{0}/integration/words/{1}".format(self._user, randomizer_id) + ) def subscribe_to_weather(self, weather_record, forecast): """Subscribes to a weather forecast using the Adafruit IO PLUS weather @@ -230,8 +237,11 @@ def subscribe_to_weather(self, weather_record, forecast): :param int weather_record: Weather record you want data for. :param str forecast: Forecast data you'd like to recieve. """ - self._client.subscribe('{0}/integration/weather/{1}/{2}'.format(self._user, - weather_record, forecast)) + self._client.subscribe( + "{0}/integration/weather/{1}/{2}".format( + self._user, weather_record, forecast + ) + ) def subscribe_to_time(self, time_type): """Adafruit IO provides some built-in MQTT topics for getting the current server time. @@ -239,12 +249,12 @@ def subscribe_to_time(self, time_type): Information about these topics can be found on the Adafruit IO MQTT API Docs.: https://io.adafruit.com/api/docs/mqtt.html#time-topics """ - if 'seconds' or 'millis' in time_type: - self._client.subscribe('time/'+time_type) - elif time_type == 'iso': - self._client.subscribe('time/ISO-8601') + if "seconds" or "millis" in time_type: + self._client.subscribe("time/" + time_type) + elif time_type == "iso": + self._client.subscribe("time/ISO-8601") else: - raise TypeError('Invalid time feed type specified') + raise TypeError("Invalid time feed type specified") def unsubscribe(self, feed_key=None, group_key=None, shared_user=None): """Unsubscribes from an Adafruit IO feed or group. @@ -268,13 +278,13 @@ def unsubscribe(self, feed_key=None, group_key=None, shared_user=None): """ if shared_user is not None and feed_key is not None: - self._client.unsubscribe('{0}/feeds/{1}'.format(shared_user, feed_key)) + self._client.unsubscribe("{0}/feeds/{1}".format(shared_user, feed_key)) elif group_key is not None: - self._client.unsubscribe('{0}/groups/{1}'.format(self._user, feed_key)) + self._client.unsubscribe("{0}/groups/{1}".format(self._user, feed_key)) elif feed_key is not None: - self._client.unsubscribe('{0}/feeds/{1}'.format(self._user, feed_key)) + self._client.unsubscribe("{0}/feeds/{1}".format(self._user, feed_key)) else: - raise AdafruitIO_MQTTError('Must provide a feed_key or group_key.') + raise AdafruitIO_MQTTError("Must provide a feed_key or group_key.") # Publishing def publish_multiple(self, feeds_and_data, timeout=3, is_group=False): @@ -294,7 +304,7 @@ def publish_multiple(self, feeds_and_data, timeout=3, is_group=False): for t, d in feeds_and_data: feed_data.append((t, d)) else: - raise AdafruitIO_MQTTError('This method accepts a list of tuples.') + raise AdafruitIO_MQTTError("This method accepts a list of tuples.") for t, d in feed_data: if is_group: self.publish(t, d, is_group=True) @@ -339,12 +349,12 @@ def publish(self, feed_key, data, shared_user=None, is_group=False): """ if is_group: - self._client.publish('{0}/groups/{1}'.format(self._user, feed_key), data) + self._client.publish("{0}/groups/{1}".format(self._user, feed_key), data) return if shared_user is not None: - self._client.publish('{0}/feeds/{1}'.format(shared_user, feed_key), data) + self._client.publish("{0}/feeds/{1}".format(shared_user, feed_key), data) else: - self._client.publish('{0}/feeds/{1}'.format(self._user, feed_key), data) + self._client.publish("{0}/feeds/{1}".format(self._user, feed_key), data) def get(self, feed_key): """Calling this method will make Adafruit IO publish the most recent @@ -352,27 +362,31 @@ def get(self, feed_key): https://io.adafruit.com/api/docs/mqtt.html#retained-values :param str feed_key: Adafruit IO Feed key. """ - self._client.publish('{0}/feeds{1}/get'.format(self._user, feed_key), '\0') + self._client.publish("{0}/feeds{1}/get".format(self._user, feed_key), "\0") + -class RESTClient(): +class IO_HTTP: """ Client for interacting with the Adafruit IO HTTP API. + https://io.adafruit.com/api/docs/#adafruit-io-http-api :param str adafruit_io_username: Adafruit IO Username :param str adafruit_io_key: Adafruit IO Key :param wifi_manager: WiFiManager object from ESPSPI_WiFiManager or ESPAT_WiFiManager """ + def __init__(self, adafruit_io_username, adafruit_io_key, wifi_manager): self.username = adafruit_io_username self.key = adafruit_io_key wifi_type = str(type(wifi_manager)) - if ('ESPSPI_WiFiManager' in wifi_type or 'ESPAT_WiFiManager' in wifi_type): + if "ESPSPI_WiFiManager" in wifi_type or "ESPAT_WiFiManager" in wifi_type: self.wifi = wifi_manager else: raise TypeError("This library requires a WiFiManager object.") - self._aio_headers = [{"X-AIO-KEY":self.key, - "Content-Type":'application/json'}, - {"X-AIO-KEY":self.key,}] + self._aio_headers = [ + {"X-AIO-KEY": self.key, "Content-Type": "application/json"}, + {"X-AIO-KEY": self.key}, + ] @staticmethod def _create_headers(io_headers): @@ -387,9 +401,14 @@ def _create_data(data, metadata): """Creates JSON data payload """ if metadata is not None: - return {'value':data, 'lat':metadata['lat'], 'lon':metadata['lon'], - 'ele':metadata['ele'], 'created_at':metadata['created_at']} - return {'value':data} + return { + "value": data, + "lat": metadata["lat"], + "lon": metadata["lon"], + "ele": metadata["ele"], + "created_at": metadata["created_at"], + } + return {"value": data} @staticmethod def _handle_error(response): @@ -417,9 +436,8 @@ def _post(self, path, payload): :param json payload: JSON data to send to Adafruit IO """ response = self.wifi.post( - path, - json=payload, - headers=self._create_headers(self._aio_headers[0])) + path, json=payload, headers=self._create_headers(self._aio_headers[0]) + ) self._handle_error(response) return response.json() @@ -429,8 +447,8 @@ def _get(self, path): :param str path: Formatted Adafruit IO URL from _compose_path """ response = self.wifi.get( - path, - headers=self._create_headers(self._aio_headers[1])) + path, headers=self._create_headers(self._aio_headers[1]) + ) self._handle_error(response) return response.json() @@ -440,8 +458,8 @@ def _delete(self, path): :param str path: Formatted Adafruit IO URL from _compose_path """ response = self.wifi.delete( - path, - headers=self._create_headers(self._aio_headers[0])) + path, headers=self._create_headers(self._aio_headers[0]) + ) self._handle_error(response) return response.json() @@ -458,8 +476,8 @@ def send_data(self, feed_key, data, metadata=None, precision=None): if precision: try: data = round(data, precision) - except NotImplementedError: # received a non-float value - raise NotImplementedError('Precision requires a floating point value') + except NotImplementedError: # received a non-float value + raise NotImplementedError("Precision requires a floating point value") payload = self._create_data(data, metadata) self._post(path, payload) @@ -488,7 +506,7 @@ def add_feed_to_group(self, group_key, feed_key): :param str feed_key: Feed to add to the group """ path = self._compose_path("groups/{0}/add".format(group_key)) - payload = {'feed_key':feed_key} + payload = {"feed_key": feed_key} return self._post(path, payload) def create_new_group(self, group_key, group_description): @@ -498,7 +516,7 @@ def create_new_group(self, group_key, group_description): :param str group_description: Brief summary about the group """ path = self._compose_path("groups") - payload = {'name':group_key, 'description':group_description} + payload = {"name": group_key, "description": group_description} return self._post(path, payload) def delete_group(self, group_key): @@ -538,9 +556,7 @@ def create_new_feed(self, feed_key, feed_desc=None, feed_license=None): :param str feed_license: Optional feed license """ path = self._compose_path("feeds") - payload = {'name':feed_key, - 'description':feed_desc, - 'license':feed_license} + payload = {"name": feed_key, "description": feed_desc, "license": feed_license} return self._post(path, payload) def delete_feed(self, feed_key): @@ -574,8 +590,18 @@ def receive_time(self): Returns a struct_time from the Adafruit IO Server based on the device's IP address. https://circuitpython.readthedocs.io/en/latest/shared-bindings/time/__init__.html#time.struct_time """ - path = self._compose_path('integrations/time/struct.json') + path = self._compose_path("integrations/time/struct.json") time = self._get(path) - return time.struct_time((time['year'], time['mon'], time['mday'], - time['hour'], time['min'], time['sec'], - time['wday'], time['yday'], time['isdst'])) + return time.struct_time( + ( + time["year"], + time["mon"], + time["mday"], + time["hour"], + time["min"], + time["sec"], + time["wday"], + time["yday"], + time["isdst"], + ) + ) diff --git a/examples/adafruit_io_simpletest_analog_in.py b/examples/adafruit_io_simpletest_analog_in.py index 99ce01a..74bad6c 100644 --- a/examples/adafruit_io_simpletest_analog_in.py +++ b/examples/adafruit_io_simpletest_analog_in.py @@ -12,8 +12,8 @@ # Import NeoPixel Library import neopixel -# Import Adafruit IO REST Client -from adafruit_io.adafruit_io import RESTClient, AdafruitIO_RequestError +# Import Adafruit IO HTTP Client +from adafruit_io.adafruit_io import IO_HTTP, AdafruitIO_RequestError # Delay between polling and sending light sensor data, in seconds SENSOR_DELAY = 30 @@ -48,8 +48,8 @@ aio_username = secrets['aio_username'] aio_key = secrets['aio_key'] -# Create an instance of the Adafruit IO REST client -io = RESTClient(aio_username, aio_key, wifi) +# Create an instance of the Adafruit IO HTTP client +io = IO_HTTP(aio_username, aio_key, wifi) try: # Get the 'light' feed from Adafruit IO diff --git a/examples/adafruit_io_simpletest_data.py b/examples/adafruit_io_simpletest_data.py index af55dfd..eaa9277 100644 --- a/examples/adafruit_io_simpletest_data.py +++ b/examples/adafruit_io_simpletest_data.py @@ -12,8 +12,8 @@ # Import NeoPixel Library import neopixel -# Import Adafruit IO REST Client -from adafruit_io.adafruit_io import RESTClient, AdafruitIO_RequestError +# Import Adafruit IO HTTP Client +from adafruit_io.adafruit_io import IO_HTTP, AdafruitIO_RequestError # Get wifi details and more from a secrets.py file try: @@ -45,8 +45,8 @@ aio_username = secrets['aio_username'] aio_key = secrets['aio_key'] -# Create an instance of the Adafruit IO REST client -io = RESTClient(aio_username, aio_key, wifi) +# Create an instance of the Adafruit IO HTTP client +io = IO_HTTP(aio_username, aio_key, wifi) try: # Get the 'temperature' feed from Adafruit IO diff --git a/examples/adafruit_io_simpletest_digital_out.py b/examples/adafruit_io_simpletest_digital_out.py index d1a9f3f..b370db3 100644 --- a/examples/adafruit_io_simpletest_digital_out.py +++ b/examples/adafruit_io_simpletest_digital_out.py @@ -13,8 +13,8 @@ # Import NeoPixel Library import neopixel -# Import Adafruit IO REST Client -from adafruit_io.adafruit_io import RESTClient, AdafruitIO_RequestError +# Import Adafruit IO HTTP Client +from adafruit_io.adafruit_io import IO_HTTP, AdafruitIO_RequestError # Get wifi details and more from a secrets.py file try: @@ -46,8 +46,8 @@ aio_username = secrets['aio_username'] aio_key = secrets['aio_key'] -# Create an instance of the Adafruit IO REST client -io = RESTClient(aio_username, aio_key, wifi) +# Create an instance of the Adafruit IO HTTP client +io = IO_HTTP(aio_username, aio_key, wifi) try: # Get the 'digital' feed from Adafruit IO diff --git a/examples/adafruit_io_simpletest_esp_at.py b/examples/adafruit_io_simpletest_esp_at.py index f75eb29..c5b92d4 100644 --- a/examples/adafruit_io_simpletest_esp_at.py +++ b/examples/adafruit_io_simpletest_esp_at.py @@ -10,8 +10,8 @@ import busio from digitalio import DigitalInOut -# Import Adafruit IO REST Client -from adafruit_io.adafruit_io import RESTClient, AdafruitIO_RequestError +# Import Adafruit IO HTTP Client +from adafruit_io.adafruit_io import IO_HTTP, AdafruitIO_RequestError # ESP32 AT from adafruit_espatcontrol import adafruit_espatcontrol, adafruit_espatcontrol_wifimanager @@ -62,8 +62,8 @@ aio_username = secrets['aio_username'] aio_key = secrets['aio_key'] -# Create an instance of the Adafruit IO REST client -io = RESTClient(aio_username, aio_key, wifi) +# Create an instance of the Adafruit IO HTTP client +io = IO_HTTP(aio_username, aio_key, wifi) try: # Get the 'temperature' feed from Adafruit IO diff --git a/examples/adafruit_io_simpletest_feeds.py b/examples/adafruit_io_simpletest_feeds.py index b7d6bc5..f4c4969 100644 --- a/examples/adafruit_io_simpletest_feeds.py +++ b/examples/adafruit_io_simpletest_feeds.py @@ -11,8 +11,8 @@ # Import NeoPixel Library import neopixel -# Import Adafruit IO REST Client -from adafruit_io.adafruit_io import RESTClient +# Import Adafruit IO HTTP Client +from adafruit_io.adafruit_io import IO_HTTP # Get wifi details and more from a secrets.py file try: @@ -44,8 +44,8 @@ aio_username = secrets['aio_username'] aio_key = secrets['aio_key'] -# Create an instance of the Adafruit IO REST client -io = RESTClient(aio_username, aio_key, wifi) +# Create an instance of the Adafruit IO HTTP client +io = IO_HTTP(aio_username, aio_key, wifi) # Create a new 'circuitpython' feed with a description print('Creating new Adafruit IO feed...') diff --git a/examples/adafruit_io_simpletest_groups.py b/examples/adafruit_io_simpletest_groups.py index ded81f2..32f8d25 100644 --- a/examples/adafruit_io_simpletest_groups.py +++ b/examples/adafruit_io_simpletest_groups.py @@ -11,8 +11,8 @@ # Import NeoPixel Library import neopixel -# Import Adafruit IO REST Client -from adafruit_io.adafruit_io import RESTClient +# Import Adafruit IO HTTP Client +from adafruit_io.adafruit_io import IO_HTTP # Get wifi details and more from a secrets.py file try: @@ -43,8 +43,8 @@ aio_username = secrets['aio_username'] aio_key = secrets['aio_key'] -# Create an instance of the Adafruit IO REST client -io = RESTClient(aio_username, aio_key, wifi) +# Create an instance of the Adafruit IO HTTP client +io = IO_HTTP(aio_username, aio_key, wifi) # Create a new group print('Creating a new Adafruit IO Group...') diff --git a/examples/adafruit_io_simpletest_metadata.py b/examples/adafruit_io_simpletest_metadata.py index b6f7650..2b0c5f4 100644 --- a/examples/adafruit_io_simpletest_metadata.py +++ b/examples/adafruit_io_simpletest_metadata.py @@ -12,8 +12,8 @@ # Import NeoPixel Library import neopixel -# Import Adafruit IO REST Client -from adafruit_io.adafruit_io import RESTClient, AdafruitIO_RequestError +# Import Adafruit IO HTTP Client +from adafruit_io.adafruit_io import IO_HTTP, AdafruitIO_RequestError # Get wifi details and more from a secrets.py file try: @@ -45,8 +45,8 @@ aio_username = secrets['aio_username'] aio_key = secrets['aio_key'] -# Create an instance of the Adafruit IO REST client -io = RESTClient(aio_username, aio_key, wifi) +# Create an instance of the Adafruit IO HTTP client +io = IO_HTTP(aio_username, aio_key, wifi) try: # Get the 'location' feed from Adafruit IO diff --git a/examples/adafruit_io_simpletest_randomizer.py b/examples/adafruit_io_simpletest_randomizer.py index b2ea801..2717a18 100644 --- a/examples/adafruit_io_simpletest_randomizer.py +++ b/examples/adafruit_io_simpletest_randomizer.py @@ -13,8 +13,8 @@ # Import NeoPixel Library import neopixel -# Import Adafruit IO REST Client -from adafruit_io.adafruit_io import RESTClient +# Import Adafruit IO HTTP Client +from adafruit_io.adafruit_io import IO_HTTP # Get wifi details and more from a secrets.py file try: @@ -46,8 +46,8 @@ aio_username = secrets['aio_username'] aio_key = secrets['aio_key'] -# Create an instance of the Adafruit IO REST client -io = RESTClient(aio_username, aio_key, wifi) +# Create an instance of the Adafruit IO HTTP client +io = IO_HTTP(aio_username, aio_key, wifi) # Random Data ID # (to obtain this value, visit diff --git a/examples/adafruit_io_simpletest_temperature.py b/examples/adafruit_io_simpletest_temperature.py index f75bb84..d5085cc 100644 --- a/examples/adafruit_io_simpletest_temperature.py +++ b/examples/adafruit_io_simpletest_temperature.py @@ -17,8 +17,8 @@ # Import NeoPixel Library import neopixel -# Import Adafruit IO REST Client -from adafruit_io.adafruit_io import RESTClient, AdafruitIO_RequestError +# Import Adafruit IO HTTP Client +from adafruit_io.adafruit_io import IO_HTTP, AdafruitIO_RequestError # Import ADT7410 Library import adafruit_adt7410 @@ -53,8 +53,8 @@ aio_username = secrets['aio_username'] aio_key = secrets['aio_key'] -# Create an instance of the Adafruit IO REST client -io = RESTClient(aio_username, aio_key, wifi) +# Create an instance of the Adafruit IO HTTP client +io = IO_HTTP(aio_username, aio_key, wifi) try: # Get the 'temperature' feed from Adafruit IO diff --git a/examples/adafruit_io_simpletest_weather.py b/examples/adafruit_io_simpletest_weather.py index 8d131bf..3182584 100644 --- a/examples/adafruit_io_simpletest_weather.py +++ b/examples/adafruit_io_simpletest_weather.py @@ -14,8 +14,8 @@ # Import NeoPixel Library import neopixel -# Import Adafruit IO REST Client -from adafruit_io.adafruit_io import RESTClient +# Import Adafruit IO HTTP Client +from adafruit_io.adafruit_io import IO_HTTP # Get wifi details and more from a secrets.py file try: @@ -47,8 +47,8 @@ aio_username = secrets['aio_username'] aio_key = secrets['aio_key'] -# Create an instance of the Adafruit IO REST client -io = RESTClient(aio_username, aio_key, wifi) +# Create an instance of the Adafruit IO HTTP client +io = IO_HTTP(aio_username, aio_key, wifi) # Weather Location ID # (to obtain this value, visit From 1f87bdd29e1e3897eef844e35dd7e5d935e6cd34 Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 17 Jul 2019 13:03:39 -0400 Subject: [PATCH 17/35] add test file and modified unittest --- examples/tests/adafruit_io_http_tester.py | 142 +++++++++++++ examples/tests/unittest.py | 233 ++++++++++++++++++++++ 2 files changed, 375 insertions(+) create mode 100644 examples/tests/adafruit_io_http_tester.py create mode 100755 examples/tests/unittest.py diff --git a/examples/tests/adafruit_io_http_tester.py b/examples/tests/adafruit_io_http_tester.py new file mode 100644 index 0000000..8a9a99d --- /dev/null +++ b/examples/tests/adafruit_io_http_tester.py @@ -0,0 +1,142 @@ +""" +CircuitPython_AdafruitIO IO_HTTP Tester +-------------------------------------------------- + +Tests Adafruit IO CircuitPython HTTP method +coverage with a WiFi CircuitPython device. + +* Author(s): Brent Rubell for Adafruit Industries +""" +from random import randint, uniform +import time +import board +import busio +from digitalio import DigitalInOut +from adafruit_esp32spi import adafruit_esp32spi, adafruit_esp32spi_wifimanager +import neopixel +from adafruit_io.adafruit_io import IO_HTTP, AdafruitIO_RequestError + +# REQUIRES MicroPython's UnitTest +# https://github.com/micropython/micropython-lib/tree/master/unittest +import unittest + +# 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 + +# ESP32 Setup +esp32_cs = DigitalInOut(board.ESP_CS) +esp32_ready = DigitalInOut(board.ESP_BUSY) +esp32_reset = DigitalInOut(board.ESP_RESET) +spi = busio.SPI(board.SCK, board.MOSI, board.MISO) +esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset) +status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2) +wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light) + +# Set your Adafruit IO Username and Key in secrets.py +# (visit io.adafruit.com if you need to create an account, +# or if you need your Adafruit IO key.) +aio_username = secrets["aio_user"] +aio_key = secrets["aio_password"] + + +class Test_IO_HTTP(unittest.TestCase): + + # Tests for Adafruit IO Authentication + def test_set_user_key(self): + """__init__ constructor + correctly exposes provided credentials. + """ + username = "adabot" + key = "mho" + io = IO_HTTP(username, key, wifi) + self.assertEqual(username, io.username) + self.assertEqual(key, io.key) + + def test_incorrect_user_pass_action(self): + """Incorrect credentials provided to __init__ + should raise a RequestError. + """ + username = "adabot" + key = "mho" + io = IO_HTTP(username, key, wifi) + with self.assertRaises(AdafruitIO_RequestError): + test_feed = io.get_feed("errorfeed") + pass + + # Tests for Adafruit IO Data Methods + def test_txrx(self): + """Sends a random integer value to a feed and receives it back. + """ + # Create an Adafruit IO HTTP Client + io = IO_HTTP(aio_username, aio_key, wifi) + try: + test_feed = io.get_feed("testfeed") + except AdafruitIO_RequestError: + test_feed = io.create_new_feed("testfeed") + tx_data = randint(1, 100) + # send the value + io.send_data(test_feed["key"], tx_data) + # and get it back... + rx_data = io.receive_data(test_feed["key"]) + self.assertEqual(int(rx_data["value"]), tx_data) + + def test_send_location_data(self): + """Sets location metadata. + send_data + """ + # Create an Adafruit IO HTTP Client + io = IO_HTTP(aio_username, aio_key, wifi) + io.delete_feed('testfeed') + test_feed = io.create_new_feed('testfeed') + # value + value = randint(1, 100) + # Set up metadata associated with value + metadata = {'lat': uniform(1, 100), + 'lon': uniform(1, 100), + 'ele': 10, + 'created_at': None} + io.send_data(test_feed['key'], value, metadata) + rx_data = io.receive_data(test_feed['key']) + self.assertEqual(int(rx_data['value']), value) + self.assertAlmostEqual(float(rx_data['lat']), metadata['lat']) + self.assertAlmostEqual(float(rx_data['lon']), metadata['lon']) + self.assertAlmostEqual(float(rx_data['ele']), metadata['ele']) + + # Test for Adafruit IO Feed Methods + def test_create_feed(self): + """Test creating a new feed. + """ + # Create an Adafruit IO HTTP Client + io = IO_HTTP(aio_username, aio_key, wifi) + io.delete_feed('testfeed') + test_feed = io.create_new_feed('testfeed') + self.assertEqual(test_feed['name'], 'testfeed') + + def test_delete_feed(self): + """delete_feed by feed key + """ + # Create an Adafruit IO HTTP Client + io = IO_HTTP(aio_username, aio_key, wifi) + io.delete_feed('testfeed') + with self.assertRaises(AdafruitIO_RequestError): + io.receive_data('testfeed'['key']) + pass + + def test_delete_nonexistent_feed(self): + """delete nonexistent feed by feed key + """ + # Create an Adafruit IO HTTP Client + io = IO_HTTP(aio_username, aio_key, wifi) + io.delete_feed('testfeed') + with self.assertRaises(AdafruitIO_RequestError): + io.delete_feed['testfeed'] + + +if __name__ == "__main__": + # Pass the NetworkManager Object to UnitTest.py + unittest.get_wifi(wifi) + unittest.main() diff --git a/examples/tests/unittest.py b/examples/tests/unittest.py new file mode 100755 index 0000000..cd3fe29 --- /dev/null +++ b/examples/tests/unittest.py @@ -0,0 +1,233 @@ +# UnitTest.py +# https://github.com/micropython/micropython-lib/blob/master/unittest/unittest.py +# Modified for handling ESP32SPI module connectivity + +class SkipTest(Exception): + pass + + +class AssertRaisesContext: + + def __init__(self, exc): + self.expected = exc + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, tb): + if exc_type is None: + assert False, "%r not raised" % self.expected + if issubclass(exc_type, self.expected): + return True + return False + + +class TestCase: + + def fail(self, msg=''): + assert False, msg + + def assertEqual(self, x, y, msg=''): + if not msg: + msg = "%r vs (expected) %r" % (x, y) + assert x == y, msg + + def assertNotEqual(self, x, y, msg=''): + if not msg: + msg = "%r not expected to be equal %r" % (x, y) + assert x != y, msg + + def assertAlmostEqual(self, x, y, places=None, msg='', delta=None): + if x == y: + return + if delta is not None and places is not None: + raise TypeError("specify delta or places not both") + + if delta is not None: + if abs(x - y) <= delta: + return + if not msg: + msg = '%r != %r within %r delta' % (x, y, delta) + else: + if places is None: + places = 7 + if round(abs(y-x), places) == 0: + return + if not msg: + msg = '%r != %r within %r places' % (x, y, places) + + assert False, msg + + def assertNotAlmostEqual(self, x, y, places=None, msg='', delta=None): + if delta is not None and places is not None: + raise TypeError("specify delta or places not both") + + if delta is not None: + if not (x == y) and abs(x - y) > delta: + return + if not msg: + msg = '%r == %r within %r delta' % (x, y, delta) + else: + if places is None: + places = 7 + if not (x == y) and round(abs(y-x), places) != 0: + return + if not msg: + msg = '%r == %r within %r places' % (x, y, places) + + assert False, msg + + def assertIs(self, x, y, msg=''): + if not msg: + msg = "%r is not %r" % (x, y) + assert x is y, msg + + def assertIsNot(self, x, y, msg=''): + if not msg: + msg = "%r is %r" % (x, y) + assert x is not y, msg + + def assertIsNone(self, x, msg=''): + if not msg: + msg = "%r is not None" % x + assert x is None, msg + + def assertIsNotNone(self, x, msg=''): + if not msg: + msg = "%r is None" % x + assert x is not None, msg + + def assertTrue(self, x, msg=''): + if not msg: + msg = "Expected %r to be True" % x + assert x, msg + + def assertFalse(self, x, msg=''): + if not msg: + msg = "Expected %r to be False" % x + assert not x, msg + + def assertIn(self, x, y, msg=''): + if not msg: + msg = "Expected %r to be in %r" % (x, y) + assert x in y, msg + + def assertIsInstance(self, x, y, msg=''): + assert isinstance(x, y), msg + + def assertRaises(self, exc, func=None, *args, **kwargs): + if func is None: + return AssertRaisesContext(exc) + + try: + func(*args, **kwargs) + assert False, "%r not raised" % exc + except Exception as e: + if isinstance(e, exc): + return + raise + + + +def skip(msg): + def _decor(fun): + # We just replace original fun with _inner + def _inner(self): + raise SkipTest(msg) + return _inner + return _decor + +def skipIf(cond, msg): + if not cond: + return lambda x: x + return skip(msg) + +def skipUnless(cond, msg): + if cond: + return lambda x: x + return skip(msg) + + +class TestSuite: + def __init__(self): + self.tests = [] + def addTest(self, cls): + self.tests.append(cls) + +class TestRunner: + def run(self, suite): + res = TestResult() + for c in suite.tests: + run_class(c, res) + + print("Ran %d tests\n" % res.testsRun) + if res.failuresNum > 0 or res.errorsNum > 0: + print("FAILED (failures=%d, errors=%d)" % (res.failuresNum, res.errorsNum)) + else: + msg = "OK" + if res.skippedNum > 0: + msg += " (%d skipped)" % res.skippedNum + print(msg) + + return res + +class TestResult: + def __init__(self): + self.errorsNum = 0 + self.failuresNum = 0 + self.skippedNum = 0 + self.testsRun = 0 + + def wasSuccessful(self): + return self.errorsNum == 0 and self.failuresNum == 0 + +# TODO: Uncompliant +def run_class(c, test_result): + o = c() + set_up = getattr(o, "setUp", lambda: None) + tear_down = getattr(o, "tearDown", lambda: None) + for name in dir(o): + if name.startswith("test"): + print("%s (%s) ..." % (name, c.__qualname__), end="") + m = getattr(o, name) + set_up() + try: + test_result.testsRun += 1 + m() + print(" ok") + except SkipTest as e: + print(" skipped:", e.args[0]) + test_result.skippedNum += 1 + except (RuntimeError, ValueError) as e: + print('Failed to get data from ESP32, retrying...\n') + wifi.reset() + pass + except: + print(" FAIL") + test_result.failuresNum += 1 + # Uncomment to investigate failure in detail + raise + continue + finally: + tear_down() + +def get_wifi(wifi_module): + wifi = wifi_module + return wifi + +def main(module="__main__"): + def test_cases(m): + for tn in dir(m): + c = getattr(m, tn) + if isinstance(c, object) and isinstance(c, type) and issubclass(c, TestCase): + yield c + + m = __import__(module) + suite = TestSuite() + for c in test_cases(m): + suite.addTest(c) + runner = TestRunner() + result = runner.run(suite) + # Terminate with non zero return code in case of failures + if result.failuresNum > 0: + raise ValueError('Failures: ', result.failuresNum) \ No newline at end of file From 730953cce2bbc3e6116c3f505e18a9b7f31dd0fa Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 17 Jul 2019 13:08:07 -0400 Subject: [PATCH 18/35] reorganize tests --- examples/{ => http}/adafruit_io_simpletest_analog_in.py | 0 examples/{ => http}/adafruit_io_simpletest_data.py | 0 examples/{ => http}/adafruit_io_simpletest_digital_out.py | 0 examples/{ => http}/adafruit_io_simpletest_esp_at.py | 0 examples/{ => http}/adafruit_io_simpletest_feeds.py | 0 examples/{ => http}/adafruit_io_simpletest_groups.py | 0 examples/{ => http}/adafruit_io_simpletest_metadata.py | 0 examples/{ => http}/adafruit_io_simpletest_randomizer.py | 0 examples/{ => http}/adafruit_io_simpletest_temperature.py | 0 examples/{ => http}/adafruit_io_simpletest_weather.py | 0 examples/{ => mqtt}/adafruit_io_mqtt_subscribe_publish.py | 0 examples/{ => mqtt}/adafruit_io_mqtt_weather.py | 0 12 files changed, 0 insertions(+), 0 deletions(-) rename examples/{ => http}/adafruit_io_simpletest_analog_in.py (100%) rename examples/{ => http}/adafruit_io_simpletest_data.py (100%) rename examples/{ => http}/adafruit_io_simpletest_digital_out.py (100%) rename examples/{ => http}/adafruit_io_simpletest_esp_at.py (100%) rename examples/{ => http}/adafruit_io_simpletest_feeds.py (100%) rename examples/{ => http}/adafruit_io_simpletest_groups.py (100%) rename examples/{ => http}/adafruit_io_simpletest_metadata.py (100%) rename examples/{ => http}/adafruit_io_simpletest_randomizer.py (100%) rename examples/{ => http}/adafruit_io_simpletest_temperature.py (100%) rename examples/{ => http}/adafruit_io_simpletest_weather.py (100%) rename examples/{ => mqtt}/adafruit_io_mqtt_subscribe_publish.py (100%) rename examples/{ => mqtt}/adafruit_io_mqtt_weather.py (100%) diff --git a/examples/adafruit_io_simpletest_analog_in.py b/examples/http/adafruit_io_simpletest_analog_in.py similarity index 100% rename from examples/adafruit_io_simpletest_analog_in.py rename to examples/http/adafruit_io_simpletest_analog_in.py diff --git a/examples/adafruit_io_simpletest_data.py b/examples/http/adafruit_io_simpletest_data.py similarity index 100% rename from examples/adafruit_io_simpletest_data.py rename to examples/http/adafruit_io_simpletest_data.py diff --git a/examples/adafruit_io_simpletest_digital_out.py b/examples/http/adafruit_io_simpletest_digital_out.py similarity index 100% rename from examples/adafruit_io_simpletest_digital_out.py rename to examples/http/adafruit_io_simpletest_digital_out.py diff --git a/examples/adafruit_io_simpletest_esp_at.py b/examples/http/adafruit_io_simpletest_esp_at.py similarity index 100% rename from examples/adafruit_io_simpletest_esp_at.py rename to examples/http/adafruit_io_simpletest_esp_at.py diff --git a/examples/adafruit_io_simpletest_feeds.py b/examples/http/adafruit_io_simpletest_feeds.py similarity index 100% rename from examples/adafruit_io_simpletest_feeds.py rename to examples/http/adafruit_io_simpletest_feeds.py diff --git a/examples/adafruit_io_simpletest_groups.py b/examples/http/adafruit_io_simpletest_groups.py similarity index 100% rename from examples/adafruit_io_simpletest_groups.py rename to examples/http/adafruit_io_simpletest_groups.py diff --git a/examples/adafruit_io_simpletest_metadata.py b/examples/http/adafruit_io_simpletest_metadata.py similarity index 100% rename from examples/adafruit_io_simpletest_metadata.py rename to examples/http/adafruit_io_simpletest_metadata.py diff --git a/examples/adafruit_io_simpletest_randomizer.py b/examples/http/adafruit_io_simpletest_randomizer.py similarity index 100% rename from examples/adafruit_io_simpletest_randomizer.py rename to examples/http/adafruit_io_simpletest_randomizer.py diff --git a/examples/adafruit_io_simpletest_temperature.py b/examples/http/adafruit_io_simpletest_temperature.py similarity index 100% rename from examples/adafruit_io_simpletest_temperature.py rename to examples/http/adafruit_io_simpletest_temperature.py diff --git a/examples/adafruit_io_simpletest_weather.py b/examples/http/adafruit_io_simpletest_weather.py similarity index 100% rename from examples/adafruit_io_simpletest_weather.py rename to examples/http/adafruit_io_simpletest_weather.py diff --git a/examples/adafruit_io_mqtt_subscribe_publish.py b/examples/mqtt/adafruit_io_mqtt_subscribe_publish.py similarity index 100% rename from examples/adafruit_io_mqtt_subscribe_publish.py rename to examples/mqtt/adafruit_io_mqtt_subscribe_publish.py diff --git a/examples/adafruit_io_mqtt_weather.py b/examples/mqtt/adafruit_io_mqtt_weather.py similarity index 100% rename from examples/adafruit_io_mqtt_weather.py rename to examples/mqtt/adafruit_io_mqtt_weather.py From 61681efb9f35b8b36efbb206cfae544ed9215840 Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 17 Jul 2019 13:13:01 -0400 Subject: [PATCH 19/35] add new travis location, remove unnecessary simpletest --- .travis.yml | 3 ++- ...uit_io_simpletest_analog_in.py => adafruit_io_analog_in.py} | 0 ...io_simpletest_digital_out.py => adafruit_io_digital_out.py} | 0 ...{adafruit_io_simpletest_esp_at.py => adafruit_io_esp_at.py} | 0 .../{adafruit_io_simpletest_feeds.py => adafruit_io_feeds.py} | 0 ...{adafruit_io_simpletest_groups.py => adafruit_io_groups.py} | 0 ...fruit_io_simpletest_metadata.py => adafruit_io_metadata.py} | 0 ...t_io_simpletest_randomizer.py => adafruit_io_randomizer.py} | 0 ...dafruit_io_simpletest_data.py => adafruit_io_simpletest.py} | 0 ...io_simpletest_temperature.py => adafruit_io_temperature.py} | 0 ...dafruit_io_simpletest_weather.py => adafruit_io_weather.py} | 0 11 files changed, 2 insertions(+), 1 deletion(-) rename examples/http/{adafruit_io_simpletest_analog_in.py => adafruit_io_analog_in.py} (100%) rename examples/http/{adafruit_io_simpletest_digital_out.py => adafruit_io_digital_out.py} (100%) rename examples/http/{adafruit_io_simpletest_esp_at.py => adafruit_io_esp_at.py} (100%) rename examples/http/{adafruit_io_simpletest_feeds.py => adafruit_io_feeds.py} (100%) rename examples/http/{adafruit_io_simpletest_groups.py => adafruit_io_groups.py} (100%) rename examples/http/{adafruit_io_simpletest_metadata.py => adafruit_io_metadata.py} (100%) rename examples/http/{adafruit_io_simpletest_randomizer.py => adafruit_io_randomizer.py} (100%) rename examples/http/{adafruit_io_simpletest_data.py => adafruit_io_simpletest.py} (100%) rename examples/http/{adafruit_io_simpletest_temperature.py => adafruit_io_temperature.py} (100%) rename examples/http/{adafruit_io_simpletest_weather.py => adafruit_io_weather.py} (100%) diff --git a/.travis.yml b/.travis.yml index 4c0ccb6..2b581f8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -43,6 +43,7 @@ install: script: - pylint adafruit_io/*.py - - ([[ ! -d "examples" ]] || pylint --disable=missing-docstring,invalid-name,bad-whitespace examples/*.py) + - ([[ ! -d "examples" ]] || pylint --disable=missing-docstring,invalid-name,bad-whitespace examples/http/*.py) + - ([[ ! -d "examples" ]] || pylint --disable=missing-docstring,invalid-name,bad-whitespace examples/mqtt/*.py) - circuitpython-build-bundles --filename_prefix adafruit-circuitpython-adafruit_io --library_location . - cd docs && sphinx-build -E -W -b html . _build/html && cd .. diff --git a/examples/http/adafruit_io_simpletest_analog_in.py b/examples/http/adafruit_io_analog_in.py similarity index 100% rename from examples/http/adafruit_io_simpletest_analog_in.py rename to examples/http/adafruit_io_analog_in.py diff --git a/examples/http/adafruit_io_simpletest_digital_out.py b/examples/http/adafruit_io_digital_out.py similarity index 100% rename from examples/http/adafruit_io_simpletest_digital_out.py rename to examples/http/adafruit_io_digital_out.py diff --git a/examples/http/adafruit_io_simpletest_esp_at.py b/examples/http/adafruit_io_esp_at.py similarity index 100% rename from examples/http/adafruit_io_simpletest_esp_at.py rename to examples/http/adafruit_io_esp_at.py diff --git a/examples/http/adafruit_io_simpletest_feeds.py b/examples/http/adafruit_io_feeds.py similarity index 100% rename from examples/http/adafruit_io_simpletest_feeds.py rename to examples/http/adafruit_io_feeds.py diff --git a/examples/http/adafruit_io_simpletest_groups.py b/examples/http/adafruit_io_groups.py similarity index 100% rename from examples/http/adafruit_io_simpletest_groups.py rename to examples/http/adafruit_io_groups.py diff --git a/examples/http/adafruit_io_simpletest_metadata.py b/examples/http/adafruit_io_metadata.py similarity index 100% rename from examples/http/adafruit_io_simpletest_metadata.py rename to examples/http/adafruit_io_metadata.py diff --git a/examples/http/adafruit_io_simpletest_randomizer.py b/examples/http/adafruit_io_randomizer.py similarity index 100% rename from examples/http/adafruit_io_simpletest_randomizer.py rename to examples/http/adafruit_io_randomizer.py diff --git a/examples/http/adafruit_io_simpletest_data.py b/examples/http/adafruit_io_simpletest.py similarity index 100% rename from examples/http/adafruit_io_simpletest_data.py rename to examples/http/adafruit_io_simpletest.py diff --git a/examples/http/adafruit_io_simpletest_temperature.py b/examples/http/adafruit_io_temperature.py similarity index 100% rename from examples/http/adafruit_io_simpletest_temperature.py rename to examples/http/adafruit_io_temperature.py diff --git a/examples/http/adafruit_io_simpletest_weather.py b/examples/http/adafruit_io_weather.py similarity index 100% rename from examples/http/adafruit_io_simpletest_weather.py rename to examples/http/adafruit_io_weather.py From b6830b3fb361f9a41ee66d9a974e00c3399b56be Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 17 Jul 2019 13:36:26 -0400 Subject: [PATCH 20/35] remove old simpletests with invalid method signatures, etc --- ...lish.py => adafruit_io_mqtt_simpletest.py} | 70 ++++---- examples/mqtt/adafruit_io_mqtt_weather.py | 161 ------------------ 2 files changed, 34 insertions(+), 197 deletions(-) rename examples/mqtt/{adafruit_io_mqtt_subscribe_publish.py => adafruit_io_mqtt_simpletest.py} (61%) delete mode 100755 examples/mqtt/adafruit_io_mqtt_weather.py diff --git a/examples/mqtt/adafruit_io_mqtt_subscribe_publish.py b/examples/mqtt/adafruit_io_mqtt_simpletest.py similarity index 61% rename from examples/mqtt/adafruit_io_mqtt_subscribe_publish.py rename to examples/mqtt/adafruit_io_mqtt_simpletest.py index 43830b5..19a97eb 100755 --- a/examples/mqtt/adafruit_io_mqtt_subscribe_publish.py +++ b/examples/mqtt/adafruit_io_mqtt_simpletest.py @@ -10,14 +10,12 @@ import neopixel import busio from digitalio import DigitalInOut - -# Import WiFi configuration from adafruit_esp32spi import adafruit_esp32spi from adafruit_esp32spi import adafruit_esp32spi_wifimanager import adafruit_esp32spi.adafruit_esp32spi_socket as socket -# Import the Adafruit IO MQTT Class -from adafruit_io.adafruit_io import MQTT +from adafruit_minimqtt import MQTT +from adafruit_io.adafruit_io import IO_MQTT ### WiFi ### @@ -41,7 +39,9 @@ spi = busio.SPI(board.SCK, board.MOSI, board.MISO) esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset) """Use below for Most Boards""" -status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2) # Uncomment for Most Boards +status_light = neopixel.NeoPixel( + board.NEOPIXEL, 1, brightness=0.2 +) # Uncomment for Most Boards """Uncomment below for ItsyBitsy M4""" # status_light = dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1, brightness=0.2) # Uncomment below for an externally defined RGB LED @@ -59,61 +59,59 @@ def connected(client): # This is a good place to subscribe to feed changes. The client parameter # passed to this function is the Adafruit IO MQTT client so you can make # calls against it easily. - print('Connected to Adafruit IO! Listening for DemoFeed changes...') + print("Connected to Adafruit IO! Listening for DemoFeed changes...") # Subscribe to changes on a feed named DemoFeed. - client.subscribe('DemoFeed') + client.subscribe("DemoFeed") + def disconnected(client): # Disconnected function will be called when the client disconnects. - print('Disconnected from Adafruit IO!') - sys.exit(1) + print("Disconnected from Adafruit IO!") + def message(client, feed_id, payload): # Message function will be called when a subscribed feed has a new value. # The feed_id parameter identifies the feed, and the payload parameter has # the new value. - print('Feed {0} received new value: {1}'.format(feed_id, payload)) + print("Feed {0} received new value: {1}".format(feed_id, payload)) + # Connect to WiFi wifi.connect() -# Create an Adafruit IO MQTT client. -client = MQTT(secrets['aio_user'], - secrets['aio_password'], - wifi, - socket) +# Initialize a new MQTT Client +client = MQTT( + socket=socket, + broker="io.adafruit.com", + username=secrets["aio_user"], + password=secrets["aio_key"], + network_manager=wifi, +) # Setup the callback functions defined above. -client.on_connect = connected +client.on_connect = connected client.on_disconnect = disconnected -client.on_message = message +client.on_message = message -# Connect to the Adafruit IO server. -client.connect() +# Initialize an Adafruit IO MQTT Client +io = IO_MQTT(client) -# Now the program needs to use a client loop function to ensure messages are -# sent and received. There are a two options for driving the message loop: +# Connect to Adafruit IO +io.connect() -# You can pump the message loop yourself by periodically calling -# the client loop function. Notice how the loop below changes to call loop -# continuously while still sending a new message every 10 seconds. This is a -# good option if you don't want to or can't have a thread pumping the message -# loop in the background. +# You can call the message loop every X seconds last = 0 -print('Publishing a new message every 10 seconds (press Ctrl-C to quit)...') +print("Publishing a new message every 10 seconds...") while True: # Explicitly pump the message loop. - client.loop() + io.loop() # Send a new message every 10 seconds. if (time.monotonic() - last) >= 5: value = randint(0, 100) - print('Publishing {0} to DemoFeed.'.format(value)) - client.publish('DemoFeed', value) + print("Publishing {0} to DemoFeed.".format(value)) + io.publish("DemoFeed", value) last = time.monotonic() -# Or you can just call loop_blocking. This will run a message loop -# forever, so your program will not get past the loop_blocking call. This is -# good for simple programs which only listen to events. For more complex programs -# you probably need to have a background thread loop or explicit message loop like -# the two previous examples above. -# client.loop_blocking() +# You can also call loop_blocking if you only want to receive values. +# NOTE: If uncommented, no code below this line will run. +# io.loop_blocking() diff --git a/examples/mqtt/adafruit_io_mqtt_weather.py b/examples/mqtt/adafruit_io_mqtt_weather.py deleted file mode 100755 index 71624ab..0000000 --- a/examples/mqtt/adafruit_io_mqtt_weather.py +++ /dev/null @@ -1,161 +0,0 @@ -# Example of using the Adafruit IO CircuitPython MQTT Client -# for subscribe to a weather forecast provided by the -# Adafruit IO Weather Service (IO Plus subscribers ONLY). -# This example uses ESP32SPI to connect over WiFi -# -# by Brent Rubell for Adafruit Industries, 2019 -import time -from random import randint -import board -import neopixel -import busio -from digitalio import DigitalInOut - -# Import WiFi configuration -from adafruit_esp32spi import adafruit_esp32spi -from adafruit_esp32spi import adafruit_esp32spi_wifimanager -import adafruit_esp32spi.adafruit_esp32spi_socket as socket - -# Import the Adafruit IO MQTT Class -from adafruit_io.adafruit_io import MQTT - -### WiFi ### - -# 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 - -# If you are using a board with pre-defined ESP32 Pins: -esp32_cs = DigitalInOut(board.ESP_CS) -esp32_ready = DigitalInOut(board.ESP_BUSY) -esp32_reset = DigitalInOut(board.ESP_RESET) - -# If you have an externally connected ESP32: -# esp32_cs = DigitalInOut(board.D9) -# esp32_ready = DigitalInOut(board.D10) -# esp32_reset = DigitalInOut(board.D5) - -spi = busio.SPI(board.SCK, board.MOSI, board.MISO) -esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset) -"""Use below for Most Boards""" -status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2) # Uncomment for Most Boards -"""Uncomment below for ItsyBitsy M4""" -# status_light = dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1, brightness=0.2) -# Uncomment below for an externally defined RGB LED -# import adafruit_rgbled -# from adafruit_esp32spi import PWMOut -# RED_LED = PWMOut.PWMOut(esp, 26) -# GREEN_LED = PWMOut.PWMOut(esp, 27) -# BLUE_LED = PWMOut.PWMOut(esp, 25) -# status_light = adafruit_rgbled.RGBLED(RED_LED, BLUE_LED, GREEN_LED) -wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light) - -# Set to ID of the forecast to subscribe to for updates -forecast_id = 2127 - -# Set to the ID of the feed to subscribe to for updates. -""" -Valid forecast types are: -current -forecast_minutes_5 -forecast_minutes_30 -forecast_hours_1 -forecast_hours_2 -forecast_hours_6 -forecast_hours_24 -forecast_days_1 -forecast_days_2 -forecast_days_5 -""" -# Subscribe to the current forecast -forecast_today = 'current' -# Subscribe to tomorrow's forecast -forecast_two_days = 'forecast_days_2' -# Subscribe to forecast in 5 days -forecast_in_5_days = 'forecast_days_5' - -# Define callback functions which will be called when certain events happen. -# pylint: disable=redefined-outer-name -def connected(client): - # Connected function will be called when the client is connected to Adafruit IO. - # This is a good place to subscribe to feed changes. The client parameter - # passed to this function is the Adafruit IO MQTT client so you can make - # calls against it easily. - print('Connected to Adafruit IO! Listening to forecast: {0}...'.format(forecast_id)) - # Subscribe to changes on the current forecast. - client.subscribe_weather(forecast_id, forecast_today) - - # Subscribe to changes on tomorrow's forecast. - client.subscribe_weather(forecast_id, forecast_two_days) - - # Subscribe to changes on forecast in 5 days. - client.subscribe_weather(forecast_id, forecast_in_5_days) - -# pylint: disable=unused-argument -def disconnected(client): - # Disconnected function will be called when the client disconnects. - print('Disconnected from Adafruit IO!') - sys.exit(1) - -# pylint: disable=unused-argument -def message(client, topic, payload): - """Message function will be called when any subscribed forecast has an update. - Weather data is updated at most once every 20 minutes. - """ - print('NEW MESSAGE: ', topic, payload) - # forecast based on mqtt topic - if topic == 'current': - # Print out today's forecast - today_forecast = payload - print('\nCurrent Forecast') - parseForecast(today_forecast) - elif topic == 'forecast_days_2': - # Print out tomorrow's forecast - two_day_forecast = payload - print('\nWeather in Two Days') - parseForecast(two_day_forecast) - elif topic == 'forecast_days_5': - # Print out forecast in 5 days - five_day_forecast = payload - print('\nWeather in 5 Days') - parseForecast(five_day_forecast) - -def parseForecast(forecast_data): - """Parses and prints incoming forecast data - """ - # incoming data is a utf-8 string, encode it as a json object - forecast = json.loads(forecast_data) - # Print out the forecast - try: - print('It is {0} and {1}F.'.format(forecast['summary'], forecast['temperature'])) - except KeyError: - # future weather forecasts return a high and low temperature, instead of 'temperature' - print('It will be {0} with a high of {1}F and a low of {2}F.'.format( - forecast['summary'], forecast['temperatureLow'], forecast['temperatureHigh'])) - print('with humidity of {0}%, wind speed of {1}mph, and {2}% chance of precipitation.'.format( - forecast['humidity'], forecast['windSpeed'], forecast['precipProbability'])) - -# Connect to WiFi -wifi.connect() - -# Initialize a new Adafruit IO WiFi MQTT client. -client = MQTT(secrets['aio_user'], - secrets['aio_password'], - wifi, - socket) - -# Setup the callback functions defined above. -client.on_connect = connected -client.on_disconnect = disconnected -client.on_message = message - -# Connect to the Adafruit IO server. -client.connect() - -# Start a message loop that blocks forever waiting for MQTT messages to be -# received. Note there are other options for running the event loop like doing -# so in a background thread--see the mqtt_client.py example to learn more. -client.loop_blocking() From 581dcbc0342e4e384f223adbeb8737ae67974355 Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 17 Jul 2019 13:47:51 -0400 Subject: [PATCH 21/35] require username, reimport struct time... --- adafruit_io/adafruit_io.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/adafruit_io/adafruit_io.py b/adafruit_io/adafruit_io.py index 2c6b518..fcc3289 100755 --- a/adafruit_io/adafruit_io.py +++ b/adafruit_io/adafruit_io.py @@ -35,7 +35,7 @@ * Adafruit CircuitPython firmware for the supported boards: https://github.com/adafruit/circuitpython/releases """ -import time +from time import struct_time from adafruit_io.adafruit_io_errors import ( AdafruitIO_RequestError, AdafruitIO_ThrottleError, @@ -66,7 +66,13 @@ def __init__(self, mqtt_client): raise TypeError( "This class requires a MiniMQTT client object, please create one." ) - self._user = self._client._user + # Adafruit IO Auth. requires a username + try: + self._user = self._client._user + except: + raise TypeError( + "Adafruit IO requires a username, please set one in MiniMQTT" + ) # User-defined MQTT callback methods must be init'd to None self.on_connect = None self.on_disconnect = None @@ -142,6 +148,7 @@ def _on_message_mqtt(self, client, topic, payload): if self.on_message is not None: # Parse the MQTT topic string topic_name = topic.split("/") + print(topic_name) if topic_name[1] == "groups": # Adafruit IO Group Feed(s) feeds = [] @@ -162,7 +169,7 @@ def _on_message_mqtt(self, client, topic, payload): else: # Standard Adafruit IO Feed topic_name = topic_name[2] - message = "" if payload is None else message + message = payload else: raise ValueError( "You must define an on_message method before calling this callback." @@ -592,7 +599,7 @@ def receive_time(self): """ path = self._compose_path("integrations/time/struct.json") time = self._get(path) - return time.struct_time( + return struct_time( ( time["year"], time["mon"], From 566b6cfa820ca961bb8afa68282010fd280e4150 Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 17 Jul 2019 14:04:31 -0400 Subject: [PATCH 22/35] add time topic example --- examples/mqtt/adafruit_io_mqtt_simpletest.py | 16 +-- examples/mqtt/adafruit_io_mqtt_time.py | 117 +++++++++++++++++++ 2 files changed, 126 insertions(+), 7 deletions(-) create mode 100755 examples/mqtt/adafruit_io_mqtt_time.py diff --git a/examples/mqtt/adafruit_io_mqtt_simpletest.py b/examples/mqtt/adafruit_io_mqtt_simpletest.py index 19a97eb..e0c9df1 100755 --- a/examples/mqtt/adafruit_io_mqtt_simpletest.py +++ b/examples/mqtt/adafruit_io_mqtt_simpletest.py @@ -79,27 +79,28 @@ def message(client, feed_id, payload): # Connect to WiFi wifi.connect() -# Initialize a new MQTT Client +# Initialize a new MQTT Client object client = MQTT( socket=socket, broker="io.adafruit.com", username=secrets["aio_user"], password=secrets["aio_key"], network_manager=wifi, + log=True, ) -# Setup the callback functions defined above. -client.on_connect = connected -client.on_disconnect = disconnected -client.on_message = message - # Initialize an Adafruit IO MQTT Client io = IO_MQTT(client) +# Connect the callback methods defined above to Adafruit IO +io.on_connect = connected +io.on_disconnect = disconnected +io.on_message = message + # Connect to Adafruit IO io.connect() -# You can call the message loop every X seconds +# Below is an example of manually publishing a new value to Adafruit IO. last = 0 print("Publishing a new message every 10 seconds...") while True: @@ -112,6 +113,7 @@ def message(client, feed_id, payload): io.publish("DemoFeed", value) last = time.monotonic() + # You can also call loop_blocking if you only want to receive values. # NOTE: If uncommented, no code below this line will run. # io.loop_blocking() diff --git a/examples/mqtt/adafruit_io_mqtt_time.py b/examples/mqtt/adafruit_io_mqtt_time.py new file mode 100755 index 0000000..7aa4492 --- /dev/null +++ b/examples/mqtt/adafruit_io_mqtt_time.py @@ -0,0 +1,117 @@ +# Adafruit IO provides some built-in MQTT topics +# for obtaining the current server time, if you don't have +# access to a RTC module. + +import time +from random import randint +import board +import neopixel +import busio +from digitalio import DigitalInOut +from adafruit_esp32spi import adafruit_esp32spi +from adafruit_esp32spi import adafruit_esp32spi_wifimanager +import adafruit_esp32spi.adafruit_esp32spi_socket as socket + +from adafruit_minimqtt import MQTT +from adafruit_io.adafruit_io import IO_MQTT + +### WiFi ### + +# 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 + +# If you are using a board with pre-defined ESP32 Pins: +esp32_cs = DigitalInOut(board.ESP_CS) +esp32_ready = DigitalInOut(board.ESP_BUSY) +esp32_reset = DigitalInOut(board.ESP_RESET) + +# If you have an externally connected ESP32: +# esp32_cs = DigitalInOut(board.D9) +# esp32_ready = DigitalInOut(board.D10) +# esp32_reset = DigitalInOut(board.D5) + +spi = busio.SPI(board.SCK, board.MOSI, board.MISO) +esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset) +"""Use below for Most Boards""" +status_light = neopixel.NeoPixel( + board.NEOPIXEL, 1, brightness=0.2 +) # Uncomment for Most Boards +"""Uncomment below for ItsyBitsy M4""" +# status_light = dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1, brightness=0.2) +# Uncomment below for an externally defined RGB LED +# import adafruit_rgbled +# from adafruit_esp32spi import PWMOut +# RED_LED = PWMOut.PWMOut(esp, 26) +# GREEN_LED = PWMOut.PWMOut(esp, 27) +# BLUE_LED = PWMOut.PWMOut(esp, 25) +# status_light = adafruit_rgbled.RGBLED(RED_LED, BLUE_LED, GREEN_LED) +wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light) + +# Define callback functions which will be called when certain events happen. +def connected(client): + # Connected function will be called when the client is connected to Adafruit IO. + # This is a good place to subscribe to feed changes. The client parameter + # passed to this function is the Adafruit IO MQTT client so you can make + # calls against it easily. + print("Connected to Adafruit IO!") + + # Subscribe to time/seconds topic + # https://io.adafruit.com/api/docs/mqtt.html#time-seconds + io.subscribe_to_time('seconds') + + # Subscribe to time/millis topic + # https://io.adafruit.com/api/docs/mqtt.html#time-millis + io.subscribe_to_time('millis') + + # Subscribe to time/ISO-8601 topic + # https://io.adafruit.com/api/docs/mqtt.html#time-iso-8601 + io.subscribe_to_time('iso') + + # Subscribe to time/hours topic + # NOTE: This topic only publishes once every hour. + # https://io.adafruit.com/api/docs/mqtt.html#adafruit-io-monitor + io.subscribe_to_time('hours') + + +def disconnected(client): + # Disconnected function will be called when the client disconnects. + print("Disconnected from Adafruit IO!") + + +def message(client, feed_id, payload): + # Message function will be called when a subscribed feed has a new value. + # The feed_id parameter identifies the feed, and the payload parameter has + # the new value. + print("Feed {0} received new value: {1}".format(feed_id, payload)) + + +# Connect to WiFi +wifi.connect() + +# Initialize a new MQTT Client object +client = MQTT( + socket=socket, + broker="io.adafruit.com", + username=secrets["aio_user"], + password=secrets["aio_key"], + network_manager=wifi, + log=True +) + +# Initialize an Adafruit IO MQTT Client +io = IO_MQTT(client) + +# Connect the callback methods defined above to Adafruit IO +io.on_connect = connected +io.on_disconnect = disconnected +io.on_message = message + +# Connect to Adafruit IO +io.connect() + +# Listen forever... +io.loop_blocking() From 7cc98d2750ac3170a91382f1c45c876a74cab8ea Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 17 Jul 2019 15:58:46 -0400 Subject: [PATCH 23/35] Adding publishing location/metadata via csv topic, add example --- adafruit_io/adafruit_io.py | 21 +++- examples/mqtt/adafruit_io_mqtt_location.py | 114 +++++++++++++++++++++ 2 files changed, 131 insertions(+), 4 deletions(-) create mode 100755 examples/mqtt/adafruit_io_mqtt_location.py diff --git a/adafruit_io/adafruit_io.py b/adafruit_io/adafruit_io.py index fcc3289..f06021d 100755 --- a/adafruit_io/adafruit_io.py +++ b/adafruit_io/adafruit_io.py @@ -148,7 +148,6 @@ def _on_message_mqtt(self, client, topic, payload): if self.on_message is not None: # Parse the MQTT topic string topic_name = topic.split("/") - print(topic_name) if topic_name[1] == "groups": # Adafruit IO Group Feed(s) feeds = [] @@ -256,7 +255,7 @@ def subscribe_to_time(self, time_type): Information about these topics can be found on the Adafruit IO MQTT API Docs.: https://io.adafruit.com/api/docs/mqtt.html#time-topics """ - if "seconds" or "millis" in time_type: + if "seconds" or "millis" or "hours" in time_type: self._client.subscribe("time/" + time_type) elif time_type == "iso": self._client.subscribe("time/ISO-8601") @@ -319,12 +318,13 @@ def publish_multiple(self, feeds_and_data, timeout=3, is_group=False): self.publish(t, d) time.sleep(timeout) - def publish(self, feed_key, data, shared_user=None, is_group=False): + def publish(self, feed_key, data, metadata = None, shared_user=None, is_group=False): """Publishes to an An Adafruit IO Feed. :param str feed_key: Adafruit IO Feed key. :param str data: Data to publish to the feed or group. :param int data: Data to publish to the feed or group. :param float data: Data to publish to the feed or group. + :param str metadata: Optional metadata associated with the data. :param str shared_user: Owner of the Adafruit IO feed, required for feed sharing. :param bool is_group: Set True if publishing to an Adafruit IO Group. @@ -353,16 +353,29 @@ def publish(self, feed_key, data, shared_user=None, is_group=False): ..code-block:: python client.publish('temperature', shared_user='myfriend') + + Example of publishing a value along with locational metadata to a feed. + ..code-block:: python + + data = 42 + # format: "lat, lon, ele" + metadata = "40.726190, -74.005334, -6" + io.publish("location-feed", data, metadata) """ if is_group: self._client.publish("{0}/groups/{1}".format(self._user, feed_key), data) - return if shared_user is not None: self._client.publish("{0}/feeds/{1}".format(shared_user, feed_key), data) + if metadata is not None: + if isinstance(data, int or float): + data = str(data) + csv_string = data + "," + metadata + self._client.publish("{0}/feeds/{1}/csv".format(self._user, feed_key), csv_string) else: self._client.publish("{0}/feeds/{1}".format(self._user, feed_key), data) + def get(self, feed_key): """Calling this method will make Adafruit IO publish the most recent value on feed_key. diff --git a/examples/mqtt/adafruit_io_mqtt_location.py b/examples/mqtt/adafruit_io_mqtt_location.py new file mode 100755 index 0000000..8a6290c --- /dev/null +++ b/examples/mqtt/adafruit_io_mqtt_location.py @@ -0,0 +1,114 @@ +# Example of tagging data with location values +# and sending it to an Adafruit IO feed. + +from random import randint +import board +import neopixel +import busio +from digitalio import DigitalInOut +from adafruit_esp32spi import adafruit_esp32spi +from adafruit_esp32spi import adafruit_esp32spi_wifimanager +import adafruit_esp32spi.adafruit_esp32spi_socket as socket + +from adafruit_minimqtt import MQTT +from adafruit_io.adafruit_io import IO_MQTT + +### WiFi ### + +# 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 + +# If you are using a board with pre-defined ESP32 Pins: +esp32_cs = DigitalInOut(board.ESP_CS) +esp32_ready = DigitalInOut(board.ESP_BUSY) +esp32_reset = DigitalInOut(board.ESP_RESET) + +# If you have an externally connected ESP32: +# esp32_cs = DigitalInOut(board.D9) +# esp32_ready = DigitalInOut(board.D10) +# esp32_reset = DigitalInOut(board.D5) + +spi = busio.SPI(board.SCK, board.MOSI, board.MISO) +esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset) +"""Use below for Most Boards""" +status_light = neopixel.NeoPixel( + board.NEOPIXEL, 1, brightness=0.2 +) # Uncomment for Most Boards +"""Uncomment below for ItsyBitsy M4""" +# status_light = dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1, brightness=0.2) +# Uncomment below for an externally defined RGB LED +# import adafruit_rgbled +# from adafruit_esp32spi import PWMOut +# RED_LED = PWMOut.PWMOut(esp, 26) +# GREEN_LED = PWMOut.PWMOut(esp, 27) +# BLUE_LED = PWMOut.PWMOut(esp, 25) +# status_light = adafruit_rgbled.RGBLED(RED_LED, BLUE_LED, GREEN_LED) +wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light) + +# Define callback functions which will be called when certain events happen. +def connected(client): + # Connected function will be called when the client is connected to Adafruit IO. + # This is a good place to subscribe to feed changes. The client parameter + # passed to this function is the Adafruit IO MQTT client so you can make + # calls against it easily. + print("Connected to Adafruit IO!") + + # Subscribe to a location feed! + io.subscribe("location") + + +def disconnected(client): + # Disconnected function will be called when the client disconnects. + print("Disconnected from Adafruit IO!") + + +def message(client, feed_id, payload): + # Message function will be called when a subscribed feed has a new value. + # The feed_id parameter identifies the feed, and the payload parameter has + # the new value. + print("Feed {0} received new value: {1}".format(feed_id, payload)) + + +# Connect to WiFi +wifi.connect() + +# Initialize a new MQTT Client object +client = MQTT( + socket=socket, + broker="io.adafruit.com", + username=secrets["aio_user"], + password=secrets["aio_key"], + network_manager=wifi, + log=True, +) + +# Initialize an Adafruit IO MQTT Client +io = IO_MQTT(client) + +# Connect the callback methods defined above to Adafruit IO +io.on_connect = connected +io.on_disconnect = disconnected +io.on_message = message + +# Connect to Adafruit IO +io.connect() + +# Set data +data_value = 42 + +# Set up metadata associated with data_value +# lat, lon, ele +metadata = "40.726190, -74.005334, -6" + +# Send data and location metadata to the 'location' feed +print("Sending data and location metadata to IO...") +io.publish("location", data_value, metadata) +print("Data sent!") + + +# Listen forever... +io.loop_blocking() From bf18c6b865a73ea9088be0abb3ebf6f2b5caf92d Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 17 Jul 2019 16:35:06 -0400 Subject: [PATCH 24/35] add group example --- examples/mqtt/adafruit_io_groups.py | 117 ++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100755 examples/mqtt/adafruit_io_groups.py diff --git a/examples/mqtt/adafruit_io_groups.py b/examples/mqtt/adafruit_io_groups.py new file mode 100755 index 0000000..1dbf477 --- /dev/null +++ b/examples/mqtt/adafruit_io_groups.py @@ -0,0 +1,117 @@ +# Subscribing to an Adafruit IO Group +# and Publishing to the feeds in the group +import time +from random import randint + +import adafruit_esp32spi.adafruit_esp32spi_socket as socket +import board +import busio +import neopixel +from adafruit_esp32spi import adafruit_esp32spi, adafruit_esp32spi_wifimanager +from adafruit_io.adafruit_io import IO_MQTT +from adafruit_minimqtt import MQTT +from digitalio import DigitalInOut + +### WiFi ### + +# 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 + +# If you are using a board with pre-defined ESP32 Pins: +esp32_cs = DigitalInOut(board.ESP_CS) +esp32_ready = DigitalInOut(board.ESP_BUSY) +esp32_reset = DigitalInOut(board.ESP_RESET) + +# If you have an externally connected ESP32: +# esp32_cs = DigitalInOut(board.D9) +# esp32_ready = DigitalInOut(board.D10) +# esp32_reset = DigitalInOut(board.D5) + +spi = busio.SPI(board.SCK, board.MOSI, board.MISO) +esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset) +"""Use below for Most Boards""" +status_light = neopixel.NeoPixel( + board.NEOPIXEL, 1, brightness=0.2 +) # Uncomment for Most Boards +"""Uncomment below for ItsyBitsy M4""" +# status_light = dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1, brightness=0.2) +# Uncomment below for an externally defined RGB LED +# import adafruit_rgbled +# from adafruit_esp32spi import PWMOut +# RED_LED = PWMOut.PWMOut(esp, 26) +# GREEN_LED = PWMOut.PWMOut(esp, 27) +# BLUE_LED = PWMOut.PWMOut(esp, 25) +# status_light = adafruit_rgbled.RGBLED(RED_LED, BLUE_LED, GREEN_LED) +wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light) + + +# Define callback functions which will be called when certain events happen. +def connected(client): + # Connected function will be called when the client is connected to Adafruit IO. + # This is a good place to subscribe to feed changes. The client parameter + # passed to this function is the Adafruit IO MQTT client so you can make + # calls against it easily. + print("Connected to Adafruit IO!") + + # Subscribe to Group + io.subscribe(group_key=group_name) + + +def disconnected(client): + # Disconnected function will be called when the client disconnects. + print("Disconnected from Adafruit IO!") + + +def message(client, feed_id, payload): + # Message function will be called when a subscribed feed has a new value. + # The feed_id parameter identifies the feed, and the payload parameter has + # the new value. + print("Feed {0} received new value: {1}".format(feed_id, payload)) + + +# Connect to WiFi +wifi.connect() + +# Initialize a new MQTT Client object +client = MQTT( + socket=socket, + broker="io.adafruit.com", + username=secrets["aio_user"], + password=secrets["aio_key"], + network_manager=wifi +) + +# Initialize an Adafruit IO MQTT Client +io = IO_MQTT(client) + +# Connect the callback methods defined above to Adafruit IO +io.on_connect = connected +io.on_disconnect = disconnected +io.on_message = message + +# Group name +group_name = "weatherstation" + +# Feeds within the group +temp_feed = "brubell/feeds/weatherstation.temperature" +humid_feed = "brubell/feeds/weatherstation.humidity" + +# Connect to Adafruit IO +io.connect() + +print("Publishing new messages to group feeds every 5 seconds...") + +while True: + io.loop() + temp_reading = randint(0, 100) + print("Publishing value {0} to feed: {1}".format(temp_reading, temp_feed)) + client.publish(temp_feed, temp_reading) + + humid_reading = randint(0, 100) + print("Publishing value {0} to feed: {1}".format(humid_reading, humid_feed)) + client.publish(humid_feed, humid_reading) + time.sleep(5) From 188e90b1cdb72a71ad879053e6fa140b5f3704f6 Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 17 Jul 2019 16:44:47 -0400 Subject: [PATCH 25/35] linted! --- adafruit_io/adafruit_io.py | 54 ++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/adafruit_io/adafruit_io.py b/adafruit_io/adafruit_io.py index f06021d..04dbab0 100755 --- a/adafruit_io/adafruit_io.py +++ b/adafruit_io/adafruit_io.py @@ -35,7 +35,7 @@ * Adafruit CircuitPython firmware for the supported boards: https://github.com/adafruit/circuitpython/releases """ -from time import struct_time +import time from adafruit_io.adafruit_io_errors import ( AdafruitIO_RequestError, AdafruitIO_ThrottleError, @@ -95,8 +95,8 @@ def connect(self): """ try: self._client.connect() - except error as e: - AdafruitIO_MQTTError(e) + except: + raise AdafruitIO_MQTTError("Unable to connect to Adafruit IO.") def disconnect(self): """Disconnects from Adafruit IO. @@ -109,7 +109,7 @@ def is_connected(self): """Returns if connected to Adafruit IO MQTT Broker.""" return self._client.is_connected - # pylint: disable=not-callable + # pylint: disable=not-callable, unused-argument def _on_connect_mqtt(self, client, userdata, flags, return_code): """Runs when the on_connect callback is run from code. """ @@ -123,7 +123,7 @@ def _on_connect_mqtt(self, client, userdata, flags, return_code): if self.on_connect is not None: self.on_connect(self) - # pylint: disable=not-callable + # pylint: disable=not-callable, unused-argument def _on_disconnect_mqtt(self, client, userdata, return_code): """Runs when the on_disconnect callback is run from code. @@ -307,18 +307,19 @@ def publish_multiple(self, feeds_and_data, timeout=3, is_group=False): """ if isinstance(feeds_and_data, list): feed_data = [] - for t, d in feeds_and_data: - feed_data.append((t, d)) + for topic, data in feeds_and_data: + feed_data.append((topic, data)) else: raise AdafruitIO_MQTTError("This method accepts a list of tuples.") - for t, d in feed_data: + for topic, data in feed_data: if is_group: - self.publish(t, d, is_group=True) + self.publish(topic, data, is_group=True) else: - self.publish(t, d) + self.publish(topic, data) time.sleep(timeout) - def publish(self, feed_key, data, metadata = None, shared_user=None, is_group=False): + # pylint: disable=too-many-arguments + def publish(self, feed_key, data, metadata=None, shared_user=None, is_group=False): """Publishes to an An Adafruit IO Feed. :param str feed_key: Adafruit IO Feed key. :param str data: Data to publish to the feed or group. @@ -343,7 +344,7 @@ def publish(self, feed_key, data, metadata = None, shared_user=None, is_group=Fa ..code-block:: python client.publish('temperature, 'thirty degrees') - + Example of publishing an integer to Adafruit IO on group 'weatherstation'. ..code-block:: python @@ -353,7 +354,7 @@ def publish(self, feed_key, data, metadata = None, shared_user=None, is_group=Fa ..code-block:: python client.publish('temperature', shared_user='myfriend') - + Example of publishing a value along with locational metadata to a feed. ..code-block:: python @@ -371,11 +372,12 @@ def publish(self, feed_key, data, metadata = None, shared_user=None, is_group=Fa if isinstance(data, int or float): data = str(data) csv_string = data + "," + metadata - self._client.publish("{0}/feeds/{1}/csv".format(self._user, feed_key), csv_string) + self._client.publish( + "{0}/feeds/{1}/csv".format(self._user, feed_key), csv_string + ) else: self._client.publish("{0}/feeds/{1}".format(self._user, feed_key), data) - def get(self, feed_key): """Calling this method will make Adafruit IO publish the most recent value on feed_key. @@ -611,17 +613,17 @@ def receive_time(self): https://circuitpython.readthedocs.io/en/latest/shared-bindings/time/__init__.html#time.struct_time """ path = self._compose_path("integrations/time/struct.json") - time = self._get(path) - return struct_time( + time_struct = self._get(path) + return time.struct_time( ( - time["year"], - time["mon"], - time["mday"], - time["hour"], - time["min"], - time["sec"], - time["wday"], - time["yday"], - time["isdst"], + time_struct["year"], + time_struct["mon"], + time_struct["mday"], + time_struct["hour"], + time_struct["min"], + time_struct["sec"], + time_struct["wday"], + time_struct["yday"], + time_struct["isdst"], ) ) From 52c81675961feb7a4ccee08467ab5d1f4aa1ce63 Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 17 Jul 2019 16:54:05 -0400 Subject: [PATCH 26/35] removal of bad eval() --- adafruit_io/adafruit_io.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/adafruit_io/adafruit_io.py b/adafruit_io/adafruit_io.py index 04dbab0..0d0fe81 100755 --- a/adafruit_io/adafruit_io.py +++ b/adafruit_io/adafruit_io.py @@ -36,6 +36,7 @@ https://github.com/adafruit/circuitpython/releases """ import time +import json from adafruit_io.adafruit_io_errors import ( AdafruitIO_RequestError, AdafruitIO_ThrottleError, @@ -152,8 +153,8 @@ def _on_message_mqtt(self, client, topic, payload): # Adafruit IO Group Feed(s) feeds = [] messages = [] - # TODO: Remove eval here... - payload = eval(payload) + # Conversion of incoming group to a json response + payload = json.loads(payload) for feed in payload["feeds"]: feeds.append(feed) for msg in feeds: @@ -177,7 +178,14 @@ def _on_message_mqtt(self, client, topic, payload): def loop(self): """Manually process messages from Adafruit IO. - Use this method to check incoming subscription messages. + Call this method to check incoming subscription messages. + + Example usage of polling the message queue using loop. + + ..code-block:: python + + while True: + io.loop() """ self._client.loop() From a2772c9d98768249ec87a2cabed2883b22d19ddb Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 17 Jul 2019 16:55:18 -0400 Subject: [PATCH 27/35] remove old unittests --- examples/tests/adafruit_io_http_tester.py | 142 ------------- examples/tests/unittest.py | 233 ---------------------- 2 files changed, 375 deletions(-) delete mode 100644 examples/tests/adafruit_io_http_tester.py delete mode 100755 examples/tests/unittest.py diff --git a/examples/tests/adafruit_io_http_tester.py b/examples/tests/adafruit_io_http_tester.py deleted file mode 100644 index 8a9a99d..0000000 --- a/examples/tests/adafruit_io_http_tester.py +++ /dev/null @@ -1,142 +0,0 @@ -""" -CircuitPython_AdafruitIO IO_HTTP Tester --------------------------------------------------- - -Tests Adafruit IO CircuitPython HTTP method -coverage with a WiFi CircuitPython device. - -* Author(s): Brent Rubell for Adafruit Industries -""" -from random import randint, uniform -import time -import board -import busio -from digitalio import DigitalInOut -from adafruit_esp32spi import adafruit_esp32spi, adafruit_esp32spi_wifimanager -import neopixel -from adafruit_io.adafruit_io import IO_HTTP, AdafruitIO_RequestError - -# REQUIRES MicroPython's UnitTest -# https://github.com/micropython/micropython-lib/tree/master/unittest -import unittest - -# 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 - -# ESP32 Setup -esp32_cs = DigitalInOut(board.ESP_CS) -esp32_ready = DigitalInOut(board.ESP_BUSY) -esp32_reset = DigitalInOut(board.ESP_RESET) -spi = busio.SPI(board.SCK, board.MOSI, board.MISO) -esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset) -status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2) -wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light) - -# Set your Adafruit IO Username and Key in secrets.py -# (visit io.adafruit.com if you need to create an account, -# or if you need your Adafruit IO key.) -aio_username = secrets["aio_user"] -aio_key = secrets["aio_password"] - - -class Test_IO_HTTP(unittest.TestCase): - - # Tests for Adafruit IO Authentication - def test_set_user_key(self): - """__init__ constructor - correctly exposes provided credentials. - """ - username = "adabot" - key = "mho" - io = IO_HTTP(username, key, wifi) - self.assertEqual(username, io.username) - self.assertEqual(key, io.key) - - def test_incorrect_user_pass_action(self): - """Incorrect credentials provided to __init__ - should raise a RequestError. - """ - username = "adabot" - key = "mho" - io = IO_HTTP(username, key, wifi) - with self.assertRaises(AdafruitIO_RequestError): - test_feed = io.get_feed("errorfeed") - pass - - # Tests for Adafruit IO Data Methods - def test_txrx(self): - """Sends a random integer value to a feed and receives it back. - """ - # Create an Adafruit IO HTTP Client - io = IO_HTTP(aio_username, aio_key, wifi) - try: - test_feed = io.get_feed("testfeed") - except AdafruitIO_RequestError: - test_feed = io.create_new_feed("testfeed") - tx_data = randint(1, 100) - # send the value - io.send_data(test_feed["key"], tx_data) - # and get it back... - rx_data = io.receive_data(test_feed["key"]) - self.assertEqual(int(rx_data["value"]), tx_data) - - def test_send_location_data(self): - """Sets location metadata. - send_data - """ - # Create an Adafruit IO HTTP Client - io = IO_HTTP(aio_username, aio_key, wifi) - io.delete_feed('testfeed') - test_feed = io.create_new_feed('testfeed') - # value - value = randint(1, 100) - # Set up metadata associated with value - metadata = {'lat': uniform(1, 100), - 'lon': uniform(1, 100), - 'ele': 10, - 'created_at': None} - io.send_data(test_feed['key'], value, metadata) - rx_data = io.receive_data(test_feed['key']) - self.assertEqual(int(rx_data['value']), value) - self.assertAlmostEqual(float(rx_data['lat']), metadata['lat']) - self.assertAlmostEqual(float(rx_data['lon']), metadata['lon']) - self.assertAlmostEqual(float(rx_data['ele']), metadata['ele']) - - # Test for Adafruit IO Feed Methods - def test_create_feed(self): - """Test creating a new feed. - """ - # Create an Adafruit IO HTTP Client - io = IO_HTTP(aio_username, aio_key, wifi) - io.delete_feed('testfeed') - test_feed = io.create_new_feed('testfeed') - self.assertEqual(test_feed['name'], 'testfeed') - - def test_delete_feed(self): - """delete_feed by feed key - """ - # Create an Adafruit IO HTTP Client - io = IO_HTTP(aio_username, aio_key, wifi) - io.delete_feed('testfeed') - with self.assertRaises(AdafruitIO_RequestError): - io.receive_data('testfeed'['key']) - pass - - def test_delete_nonexistent_feed(self): - """delete nonexistent feed by feed key - """ - # Create an Adafruit IO HTTP Client - io = IO_HTTP(aio_username, aio_key, wifi) - io.delete_feed('testfeed') - with self.assertRaises(AdafruitIO_RequestError): - io.delete_feed['testfeed'] - - -if __name__ == "__main__": - # Pass the NetworkManager Object to UnitTest.py - unittest.get_wifi(wifi) - unittest.main() diff --git a/examples/tests/unittest.py b/examples/tests/unittest.py deleted file mode 100755 index cd3fe29..0000000 --- a/examples/tests/unittest.py +++ /dev/null @@ -1,233 +0,0 @@ -# UnitTest.py -# https://github.com/micropython/micropython-lib/blob/master/unittest/unittest.py -# Modified for handling ESP32SPI module connectivity - -class SkipTest(Exception): - pass - - -class AssertRaisesContext: - - def __init__(self, exc): - self.expected = exc - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_value, tb): - if exc_type is None: - assert False, "%r not raised" % self.expected - if issubclass(exc_type, self.expected): - return True - return False - - -class TestCase: - - def fail(self, msg=''): - assert False, msg - - def assertEqual(self, x, y, msg=''): - if not msg: - msg = "%r vs (expected) %r" % (x, y) - assert x == y, msg - - def assertNotEqual(self, x, y, msg=''): - if not msg: - msg = "%r not expected to be equal %r" % (x, y) - assert x != y, msg - - def assertAlmostEqual(self, x, y, places=None, msg='', delta=None): - if x == y: - return - if delta is not None and places is not None: - raise TypeError("specify delta or places not both") - - if delta is not None: - if abs(x - y) <= delta: - return - if not msg: - msg = '%r != %r within %r delta' % (x, y, delta) - else: - if places is None: - places = 7 - if round(abs(y-x), places) == 0: - return - if not msg: - msg = '%r != %r within %r places' % (x, y, places) - - assert False, msg - - def assertNotAlmostEqual(self, x, y, places=None, msg='', delta=None): - if delta is not None and places is not None: - raise TypeError("specify delta or places not both") - - if delta is not None: - if not (x == y) and abs(x - y) > delta: - return - if not msg: - msg = '%r == %r within %r delta' % (x, y, delta) - else: - if places is None: - places = 7 - if not (x == y) and round(abs(y-x), places) != 0: - return - if not msg: - msg = '%r == %r within %r places' % (x, y, places) - - assert False, msg - - def assertIs(self, x, y, msg=''): - if not msg: - msg = "%r is not %r" % (x, y) - assert x is y, msg - - def assertIsNot(self, x, y, msg=''): - if not msg: - msg = "%r is %r" % (x, y) - assert x is not y, msg - - def assertIsNone(self, x, msg=''): - if not msg: - msg = "%r is not None" % x - assert x is None, msg - - def assertIsNotNone(self, x, msg=''): - if not msg: - msg = "%r is None" % x - assert x is not None, msg - - def assertTrue(self, x, msg=''): - if not msg: - msg = "Expected %r to be True" % x - assert x, msg - - def assertFalse(self, x, msg=''): - if not msg: - msg = "Expected %r to be False" % x - assert not x, msg - - def assertIn(self, x, y, msg=''): - if not msg: - msg = "Expected %r to be in %r" % (x, y) - assert x in y, msg - - def assertIsInstance(self, x, y, msg=''): - assert isinstance(x, y), msg - - def assertRaises(self, exc, func=None, *args, **kwargs): - if func is None: - return AssertRaisesContext(exc) - - try: - func(*args, **kwargs) - assert False, "%r not raised" % exc - except Exception as e: - if isinstance(e, exc): - return - raise - - - -def skip(msg): - def _decor(fun): - # We just replace original fun with _inner - def _inner(self): - raise SkipTest(msg) - return _inner - return _decor - -def skipIf(cond, msg): - if not cond: - return lambda x: x - return skip(msg) - -def skipUnless(cond, msg): - if cond: - return lambda x: x - return skip(msg) - - -class TestSuite: - def __init__(self): - self.tests = [] - def addTest(self, cls): - self.tests.append(cls) - -class TestRunner: - def run(self, suite): - res = TestResult() - for c in suite.tests: - run_class(c, res) - - print("Ran %d tests\n" % res.testsRun) - if res.failuresNum > 0 or res.errorsNum > 0: - print("FAILED (failures=%d, errors=%d)" % (res.failuresNum, res.errorsNum)) - else: - msg = "OK" - if res.skippedNum > 0: - msg += " (%d skipped)" % res.skippedNum - print(msg) - - return res - -class TestResult: - def __init__(self): - self.errorsNum = 0 - self.failuresNum = 0 - self.skippedNum = 0 - self.testsRun = 0 - - def wasSuccessful(self): - return self.errorsNum == 0 and self.failuresNum == 0 - -# TODO: Uncompliant -def run_class(c, test_result): - o = c() - set_up = getattr(o, "setUp", lambda: None) - tear_down = getattr(o, "tearDown", lambda: None) - for name in dir(o): - if name.startswith("test"): - print("%s (%s) ..." % (name, c.__qualname__), end="") - m = getattr(o, name) - set_up() - try: - test_result.testsRun += 1 - m() - print(" ok") - except SkipTest as e: - print(" skipped:", e.args[0]) - test_result.skippedNum += 1 - except (RuntimeError, ValueError) as e: - print('Failed to get data from ESP32, retrying...\n') - wifi.reset() - pass - except: - print(" FAIL") - test_result.failuresNum += 1 - # Uncomment to investigate failure in detail - raise - continue - finally: - tear_down() - -def get_wifi(wifi_module): - wifi = wifi_module - return wifi - -def main(module="__main__"): - def test_cases(m): - for tn in dir(m): - c = getattr(m, tn) - if isinstance(c, object) and isinstance(c, type) and issubclass(c, TestCase): - yield c - - m = __import__(module) - suite = TestSuite() - for c in test_cases(m): - suite.addTest(c) - runner = TestRunner() - result = runner.run(suite) - # Terminate with non zero return code in case of failures - if result.failuresNum > 0: - raise ValueError('Failures: ', result.failuresNum) \ No newline at end of file From 99f3d0ae92dda673a918ee91cc35922e9a7fd7e0 Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 17 Jul 2019 17:06:36 -0400 Subject: [PATCH 28/35] fix exit method, dc on exit --- adafruit_io/adafruit_io.py | 56 +++++++++++++++++++++++++------------- 1 file changed, 37 insertions(+), 19 deletions(-) diff --git a/adafruit_io/adafruit_io.py b/adafruit_io/adafruit_io.py index 0d0fe81..8689af2 100755 --- a/adafruit_io/adafruit_io.py +++ b/adafruit_io/adafruit_io.py @@ -59,7 +59,7 @@ class IO_MQTT: # pylint: disable=protected-access def __init__(self, mqtt_client): - # MiniMQTT Object + # Check for MiniMQTT client mqtt_client_type = str(type(mqtt_client)) if "MQTT" in mqtt_client_type: self._client = mqtt_client @@ -67,7 +67,7 @@ def __init__(self, mqtt_client): raise TypeError( "This class requires a MiniMQTT client object, please create one." ) - # Adafruit IO Auth. requires a username + # MiniMQTT's username kwarg is optional, IO requires a username try: self._user = self._client._user except: @@ -85,11 +85,18 @@ def __init__(self, mqtt_client): self._client.on_disconnect = self._on_disconnect_mqtt self._client.on_message = self._on_message_mqtt self._logger = False + # Write to the MiniMQTT logger, if avaliable. if self._client._logger is not None: self._logger = True self._client.set_logger_level("DEBUG") self._connected = False + def __enter__(self): + return self + + def __exit__(self, exception_type, exception_value, traceback): + self.disconnect() + def connect(self): """Connects to the Adafruit IO MQTT Broker. Must be called before any other API methods are called. @@ -100,7 +107,7 @@ def connect(self): raise AdafruitIO_MQTTError("Unable to connect to Adafruit IO.") def disconnect(self): - """Disconnects from Adafruit IO. + """Disconnects from Adafruit IO MQTT Broker. """ if self._connected: self._client.disconnect() @@ -112,7 +119,7 @@ def is_connected(self): # pylint: disable=not-callable, unused-argument def _on_connect_mqtt(self, client, userdata, flags, return_code): - """Runs when the on_connect callback is run from code. + """Runs when the client calls on_connect. """ if self._logger: self._client._logger.debug("Client called on_connect.") @@ -126,8 +133,7 @@ def _on_connect_mqtt(self, client, userdata, flags, return_code): # pylint: disable=not-callable, unused-argument def _on_disconnect_mqtt(self, client, userdata, return_code): - """Runs when the on_disconnect callback is run from - code. + """Runs when the client calls on_disconnect. """ if self._logger: self._client._logger.debug("Client called on_disconnect") @@ -138,8 +144,8 @@ def _on_disconnect_mqtt(self, client, userdata, return_code): # pylint: disable=not-callable def _on_message_mqtt(self, client, topic, payload): - """Runs when the on_message callback is run from code. - Parses incoming data from special Adafruit IO feeds. + """Runs when the client calls on_message. Parses and returns + incoming data from Adafruit IO feeds. :param MQTT client: A MQTT Client Instance. :param str topic: MQTT topic response from Adafruit IO. :param str payload: MQTT payload data response from Adafruit IO. @@ -197,7 +203,7 @@ def loop_blocking(self): # Subscriptions def subscribe(self, feed_key=None, group_key=None, shared_user=None): - """Subscribes to an Adafruit IO feed or group. + """Subscribes to your Adafruit IO feed or group. Can also subscribe to someone else's feed. :param str feed_key: Adafruit IO Feed key. :param str group_key: Adafruit IO Group key. @@ -226,13 +232,13 @@ def subscribe(self, feed_key=None, group_key=None, shared_user=None): raise AdafruitIO_MQTTError("Must provide a feed_key or group_key.") def subscribe_to_throttling(self): - """Subscribes to your personal Adafruit IO /throttle feed. + """Subscribes to your personal Adafruit IO /throttle topic. https://io.adafruit.com/api/docs/mqtt.html#mqtt-api-rate-limiting """ self._client.subscribe("%s/throttle" % self._user) def subscribe_to_errors(self): - """Subscribes to your personal Adafruit IO /errors feed. + """Subscribes to your personal Adafruit IO /errors topic. Notifies you of errors relating to publish/subscribe calls. """ self._client.subscribe("%s/errors" % self._user) @@ -277,19 +283,25 @@ def unsubscribe(self, feed_key=None, group_key=None, shared_user=None): :param str group_key: Adafruit IO Group key. :param str shared_user: Owner of the Adafruit IO feed, required for shared feeds. - Example of unsubscribing from an Adafruit IO Feed named 'temperature': + Example of unsubscribing from a Feed: .. code-block:: python client.unsubscribe('temperature') - Example of unsubscribing to two Adafruit IO feeds: `temperature` + Example of unsubscribing from two feeds: `temperature` and `humidity` .. code-block:: python client.unsubscribe([('temperature'), ('humidity')]) + Example of unsubscribing from a shared feed. + + .. code-block:: python + + client.unsubscribe('temperature', shared_user='adabot') + """ if shared_user is not None and feed_key is not None: self._client.unsubscribe("{0}/feeds/{1}".format(shared_user, feed_key)) @@ -302,9 +314,10 @@ def unsubscribe(self, feed_key=None, group_key=None, shared_user=None): # Publishing def publish_multiple(self, feeds_and_data, timeout=3, is_group=False): - """Publishes multiple data points to multiple feeds or groups. + """Publishes multiple data points to multiple feeds or groups with a variable + timeout. :param str feeds_and_data: List of tuples containing topic strings and data values. - :param int timeout: Delay between publishing data points to Adafruit IO. + :param int timeout: Delay between publishing data points to Adafruit IO, in seconds. :param bool is_group: Set to True if you're publishing to a group. Example of publishing multiple data points on different feeds to Adafruit IO: @@ -343,22 +356,22 @@ def publish(self, feed_key, data, metadata=None, shared_user=None, is_group=Fals client.publish('temperature', 30) - Example of publishing a floating point value to Adafruit IO on feed 'temperature'. + Example of publishing a floating point value to feed 'temperature'. ..code-block:: python client.publish('temperature', 3.14) - Example of publishing a string to Adafruit IO on feed 'temperature'. + Example of publishing a string to feed 'temperature'. ..code-block:: python client.publish('temperature, 'thirty degrees') - Example of publishing an integer to Adafruit IO on group 'weatherstation'. + Example of publishing an integer to group 'weatherstation'. ..code-block:: python client.publish('weatherstation', 12, is_group=True) - Example of publishing to a shared Adafruit IO feed. + Example of publishing to a shared feed. ..code-block:: python client.publish('temperature', shared_user='myfriend') @@ -391,6 +404,11 @@ def get(self, feed_key): value on feed_key. https://io.adafruit.com/api/docs/mqtt.html#retained-values :param str feed_key: Adafruit IO Feed key. + + Example of obtaining a recently published value on a feed: + ..code-block:: python + + io.get('temperature') """ self._client.publish("{0}/feeds{1}/get".format(self._user, feed_key), "\0") From fa225cbf0a3df827a8caa6276e8c71bc39511846 Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 17 Jul 2019 17:32:48 -0400 Subject: [PATCH 29/35] pylint and rename all mqtt examples, drop mqtt prefix as they're in the mqtt folder already --- examples/mqtt/adafruit_io_groups.py | 22 ++++++++++--------- ...tt_location.py => adafruit_io_location.py} | 12 +++++----- ...impletest.py => adafruit_io_simpletest.py} | 21 +++++++++--------- ...it_io_mqtt_time.py => adafruit_io_time.py} | 16 ++++++-------- 4 files changed, 36 insertions(+), 35 deletions(-) rename examples/mqtt/{adafruit_io_mqtt_location.py => adafruit_io_location.py} (94%) rename examples/mqtt/{adafruit_io_mqtt_simpletest.py => adafruit_io_simpletest.py} (95%) rename examples/mqtt/{adafruit_io_mqtt_time.py => adafruit_io_time.py} (95%) diff --git a/examples/mqtt/adafruit_io_groups.py b/examples/mqtt/adafruit_io_groups.py index 1dbf477..e6b6675 100755 --- a/examples/mqtt/adafruit_io_groups.py +++ b/examples/mqtt/adafruit_io_groups.py @@ -3,11 +3,12 @@ import time from random import randint -import adafruit_esp32spi.adafruit_esp32spi_socket as socket import board import busio import neopixel -from adafruit_esp32spi import adafruit_esp32spi, adafruit_esp32spi_wifimanager +from adafruit_esp32spi import adafruit_esp32spi +from adafruit_esp32spi import adafruit_esp32spi_wifimanager +import adafruit_esp32spi.adafruit_esp32spi_socket as socket from adafruit_io.adafruit_io import IO_MQTT from adafruit_minimqtt import MQTT from digitalio import DigitalInOut @@ -50,6 +51,7 @@ # Define callback functions which will be called when certain events happen. +# pylint: disable=unused-argument def connected(client): # Connected function will be called when the client is connected to Adafruit IO. # This is a good place to subscribe to feed changes. The client parameter @@ -60,12 +62,12 @@ def connected(client): # Subscribe to Group io.subscribe(group_key=group_name) - +# pylint: disable=unused-argument def disconnected(client): # Disconnected function will be called when the client disconnects. print("Disconnected from Adafruit IO!") - +# pylint: disable=unused-argument def message(client, feed_id, payload): # Message function will be called when a subscribed feed has a new value. # The feed_id parameter identifies the feed, and the payload parameter has @@ -77,7 +79,7 @@ def message(client, feed_id, payload): wifi.connect() # Initialize a new MQTT Client object -client = MQTT( +mqtt_client = MQTT( socket=socket, broker="io.adafruit.com", username=secrets["aio_user"], @@ -86,7 +88,7 @@ def message(client, feed_id, payload): ) # Initialize an Adafruit IO MQTT Client -io = IO_MQTT(client) +io = IO_MQTT(mqtt_client) # Connect the callback methods defined above to Adafruit IO io.on_connect = connected @@ -97,8 +99,8 @@ def message(client, feed_id, payload): group_name = "weatherstation" # Feeds within the group -temp_feed = "brubell/feeds/weatherstation.temperature" -humid_feed = "brubell/feeds/weatherstation.humidity" +temp_feed = "weatherstation.temperature" +humid_feed = "weatherstation.humidity" # Connect to Adafruit IO io.connect() @@ -109,9 +111,9 @@ def message(client, feed_id, payload): io.loop() temp_reading = randint(0, 100) print("Publishing value {0} to feed: {1}".format(temp_reading, temp_feed)) - client.publish(temp_feed, temp_reading) + io.publish(temp_feed, temp_reading) humid_reading = randint(0, 100) print("Publishing value {0} to feed: {1}".format(humid_reading, humid_feed)) - client.publish(humid_feed, humid_reading) + io.publish(humid_feed, humid_reading) time.sleep(5) diff --git a/examples/mqtt/adafruit_io_mqtt_location.py b/examples/mqtt/adafruit_io_location.py similarity index 94% rename from examples/mqtt/adafruit_io_mqtt_location.py rename to examples/mqtt/adafruit_io_location.py index 8a6290c..38b5d37 100755 --- a/examples/mqtt/adafruit_io_mqtt_location.py +++ b/examples/mqtt/adafruit_io_location.py @@ -1,7 +1,6 @@ # Example of tagging data with location values # and sending it to an Adafruit IO feed. -from random import randint import board import neopixel import busio @@ -50,6 +49,7 @@ wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light) # Define callback functions which will be called when certain events happen. +# pylint: disable=unused-argument def connected(client): # Connected function will be called when the client is connected to Adafruit IO. # This is a good place to subscribe to feed changes. The client parameter @@ -57,15 +57,15 @@ def connected(client): # calls against it easily. print("Connected to Adafruit IO!") - # Subscribe to a location feed! + # Subscribe to a feed named location io.subscribe("location") - +# pylint: disable=unused-argument def disconnected(client): # Disconnected function will be called when the client disconnects. print("Disconnected from Adafruit IO!") - +# pylint: disable=unused-argument def message(client, feed_id, payload): # Message function will be called when a subscribed feed has a new value. # The feed_id parameter identifies the feed, and the payload parameter has @@ -77,7 +77,7 @@ def message(client, feed_id, payload): wifi.connect() # Initialize a new MQTT Client object -client = MQTT( +mqtt_client = MQTT( socket=socket, broker="io.adafruit.com", username=secrets["aio_user"], @@ -87,7 +87,7 @@ def message(client, feed_id, payload): ) # Initialize an Adafruit IO MQTT Client -io = IO_MQTT(client) +io = IO_MQTT(mqtt_client) # Connect the callback methods defined above to Adafruit IO io.on_connect = connected diff --git a/examples/mqtt/adafruit_io_mqtt_simpletest.py b/examples/mqtt/adafruit_io_simpletest.py similarity index 95% rename from examples/mqtt/adafruit_io_mqtt_simpletest.py rename to examples/mqtt/adafruit_io_simpletest.py index e0c9df1..a60e5ec 100755 --- a/examples/mqtt/adafruit_io_mqtt_simpletest.py +++ b/examples/mqtt/adafruit_io_simpletest.py @@ -6,16 +6,17 @@ # Modified by Brent Rubell for Adafruit Industries, 2019 import time from random import randint + + import board -import neopixel import busio -from digitalio import DigitalInOut +import neopixel from adafruit_esp32spi import adafruit_esp32spi from adafruit_esp32spi import adafruit_esp32spi_wifimanager import adafruit_esp32spi.adafruit_esp32spi_socket as socket - -from adafruit_minimqtt import MQTT from adafruit_io.adafruit_io import IO_MQTT +from adafruit_minimqtt import MQTT +from digitalio import DigitalInOut ### WiFi ### @@ -54,6 +55,7 @@ wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light) # Define callback functions which will be called when certain events happen. +# pylint: disable=unused-argument def connected(client): # Connected function will be called when the client is connected to Adafruit IO. # This is a good place to subscribe to feed changes. The client parameter @@ -63,12 +65,12 @@ def connected(client): # Subscribe to changes on a feed named DemoFeed. client.subscribe("DemoFeed") - +# pylint: disable=unused-argument def disconnected(client): # Disconnected function will be called when the client disconnects. print("Disconnected from Adafruit IO!") - +# pylint: disable=unused-argument def message(client, feed_id, payload): # Message function will be called when a subscribed feed has a new value. # The feed_id parameter identifies the feed, and the payload parameter has @@ -80,17 +82,16 @@ def message(client, feed_id, payload): wifi.connect() # Initialize a new MQTT Client object -client = MQTT( +mqtt_client = MQTT( socket=socket, broker="io.adafruit.com", username=secrets["aio_user"], password=secrets["aio_key"], - network_manager=wifi, - log=True, + network_manager=wifi ) # Initialize an Adafruit IO MQTT Client -io = IO_MQTT(client) +io = IO_MQTT(mqtt_client) # Connect the callback methods defined above to Adafruit IO io.on_connect = connected diff --git a/examples/mqtt/adafruit_io_mqtt_time.py b/examples/mqtt/adafruit_io_time.py similarity index 95% rename from examples/mqtt/adafruit_io_mqtt_time.py rename to examples/mqtt/adafruit_io_time.py index 7aa4492..72d5e68 100755 --- a/examples/mqtt/adafruit_io_mqtt_time.py +++ b/examples/mqtt/adafruit_io_time.py @@ -1,9 +1,7 @@ # Adafruit IO provides some built-in MQTT topics # for obtaining the current server time, if you don't have -# access to a RTC module. +# access to a RTC module. -import time -from random import randint import board import neopixel import busio @@ -52,13 +50,14 @@ wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light) # Define callback functions which will be called when certain events happen. +# pylint: disable=unused-argument def connected(client): # Connected function will be called when the client is connected to Adafruit IO. # This is a good place to subscribe to feed changes. The client parameter # passed to this function is the Adafruit IO MQTT client so you can make # calls against it easily. print("Connected to Adafruit IO!") - + # Subscribe to time/seconds topic # https://io.adafruit.com/api/docs/mqtt.html#time-seconds io.subscribe_to_time('seconds') @@ -76,12 +75,12 @@ def connected(client): # https://io.adafruit.com/api/docs/mqtt.html#adafruit-io-monitor io.subscribe_to_time('hours') - +# pylint: disable=unused-argument def disconnected(client): # Disconnected function will be called when the client disconnects. print("Disconnected from Adafruit IO!") - +# pylint: disable=unused-argument def message(client, feed_id, payload): # Message function will be called when a subscribed feed has a new value. # The feed_id parameter identifies the feed, and the payload parameter has @@ -93,17 +92,16 @@ def message(client, feed_id, payload): wifi.connect() # Initialize a new MQTT Client object -client = MQTT( +mqtt_client = MQTT( socket=socket, broker="io.adafruit.com", username=secrets["aio_user"], password=secrets["aio_key"], network_manager=wifi, - log=True ) # Initialize an Adafruit IO MQTT Client -io = IO_MQTT(client) +io = IO_MQTT(mqtt_client) # Connect the callback methods defined above to Adafruit IO io.on_connect = connected From 1abb2d8486d9bbe1d03caae47dffa74b315c4177 Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 17 Jul 2019 17:44:45 -0400 Subject: [PATCH 30/35] pylint examples again --- examples/mqtt/adafruit_io_groups.py | 4 ++-- examples/mqtt/adafruit_io_location.py | 3 ++- examples/mqtt/adafruit_io_simpletest.py | 2 +- examples/mqtt/adafruit_io_time.py | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/examples/mqtt/adafruit_io_groups.py b/examples/mqtt/adafruit_io_groups.py index e6b6675..a1a567a 100755 --- a/examples/mqtt/adafruit_io_groups.py +++ b/examples/mqtt/adafruit_io_groups.py @@ -5,13 +5,13 @@ import board import busio -import neopixel from adafruit_esp32spi import adafruit_esp32spi from adafruit_esp32spi import adafruit_esp32spi_wifimanager import adafruit_esp32spi.adafruit_esp32spi_socket as socket +from digitalio import DigitalInOut +import neopixel from adafruit_io.adafruit_io import IO_MQTT from adafruit_minimqtt import MQTT -from digitalio import DigitalInOut ### WiFi ### diff --git a/examples/mqtt/adafruit_io_location.py b/examples/mqtt/adafruit_io_location.py index 38b5d37..8e69b34 100755 --- a/examples/mqtt/adafruit_io_location.py +++ b/examples/mqtt/adafruit_io_location.py @@ -2,12 +2,13 @@ # and sending it to an Adafruit IO feed. import board -import neopixel import busio from digitalio import DigitalInOut from adafruit_esp32spi import adafruit_esp32spi from adafruit_esp32spi import adafruit_esp32spi_wifimanager import adafruit_esp32spi.adafruit_esp32spi_socket as socket +import neopixel + from adafruit_minimqtt import MQTT from adafruit_io.adafruit_io import IO_MQTT diff --git a/examples/mqtt/adafruit_io_simpletest.py b/examples/mqtt/adafruit_io_simpletest.py index a60e5ec..bfd1562 100755 --- a/examples/mqtt/adafruit_io_simpletest.py +++ b/examples/mqtt/adafruit_io_simpletest.py @@ -10,10 +10,10 @@ import board import busio -import neopixel from adafruit_esp32spi import adafruit_esp32spi from adafruit_esp32spi import adafruit_esp32spi_wifimanager import adafruit_esp32spi.adafruit_esp32spi_socket as socket +import neopixel from adafruit_io.adafruit_io import IO_MQTT from adafruit_minimqtt import MQTT from digitalio import DigitalInOut diff --git a/examples/mqtt/adafruit_io_time.py b/examples/mqtt/adafruit_io_time.py index 72d5e68..a07b117 100755 --- a/examples/mqtt/adafruit_io_time.py +++ b/examples/mqtt/adafruit_io_time.py @@ -3,12 +3,12 @@ # access to a RTC module. import board -import neopixel import busio from digitalio import DigitalInOut from adafruit_esp32spi import adafruit_esp32spi from adafruit_esp32spi import adafruit_esp32spi_wifimanager import adafruit_esp32spi.adafruit_esp32spi_socket as socket +import neopixel from adafruit_minimqtt import MQTT from adafruit_io.adafruit_io import IO_MQTT From 1f5d2baa108fc91c9bb9a98f2c6ff24b6643f842 Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 17 Jul 2019 17:50:20 -0400 Subject: [PATCH 31/35] fixup sphinx --- docs/examples.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/examples.rst b/docs/examples.rst index db69245..6b609f6 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -3,6 +3,6 @@ Simple test Ensure your device works with this simple test. -.. literalinclude:: ../examples/adafruit_io_simpletest_data.py - :caption: examples/adafruit_io_simpletest_data.py +.. literalinclude:: ../examples/http/adafruit_io_simpletest.py + :caption: examples/http/adafruit_io_simpletest.py :linenos: From 74f074cac35fbed5a37843de9c50f21cb4d3152f Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 17 Jul 2019 17:52:27 -0400 Subject: [PATCH 32/35] fix autodoc in file --- adafruit_io/adafruit_io.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/adafruit_io/adafruit_io.py b/adafruit_io/adafruit_io.py index 8689af2..ca9dd5a 100755 --- a/adafruit_io/adafruit_io.py +++ b/adafruit_io/adafruit_io.py @@ -209,14 +209,14 @@ def subscribe(self, feed_key=None, group_key=None, shared_user=None): :param str group_key: Adafruit IO Group key. :param str shared_user: Owner of the Adafruit IO feed, required for shared feeds. - Example of subscribing to an Adafruit IO Feed named 'temperature': + Example of subscribing to an Adafruit IO Feed named 'temperature'. .. code-block:: python client.subscribe('temperature') - Example of subscribing to two Adafruit IO feeds: `temperature` - and `humidity` + Example of subscribing to two Adafruit IO feeds: 'temperature' + and 'humidity'. .. code-block:: python @@ -265,7 +265,7 @@ def subscribe_to_weather(self, weather_record, forecast): def subscribe_to_time(self, time_type): """Adafruit IO provides some built-in MQTT topics for getting the current server time. - :param str time_type: Current Adafruit IO server time. Can be `seconds`, `millis`, or `iso`. + :param str time_type: Current Adafruit IO server time. Can be 'seconds', 'millis', or 'iso'. Information about these topics can be found on the Adafruit IO MQTT API Docs.: https://io.adafruit.com/api/docs/mqtt.html#time-topics """ @@ -283,14 +283,14 @@ def unsubscribe(self, feed_key=None, group_key=None, shared_user=None): :param str group_key: Adafruit IO Group key. :param str shared_user: Owner of the Adafruit IO feed, required for shared feeds. - Example of unsubscribing from a Feed: + Example of unsubscribing from a feed. .. code-block:: python client.unsubscribe('temperature') - Example of unsubscribing from two feeds: `temperature` - and `humidity` + Example of unsubscribing from two feeds: 'temperature' + and 'humidity' .. code-block:: python @@ -316,6 +316,7 @@ def unsubscribe(self, feed_key=None, group_key=None, shared_user=None): def publish_multiple(self, feeds_and_data, timeout=3, is_group=False): """Publishes multiple data points to multiple feeds or groups with a variable timeout. + :param str feeds_and_data: List of tuples containing topic strings and data values. :param int timeout: Delay between publishing data points to Adafruit IO, in seconds. :param bool is_group: Set to True if you're publishing to a group. @@ -342,6 +343,7 @@ def publish_multiple(self, feeds_and_data, timeout=3, is_group=False): # pylint: disable=too-many-arguments def publish(self, feed_key, data, metadata=None, shared_user=None, is_group=False): """Publishes to an An Adafruit IO Feed. + :param str feed_key: Adafruit IO Feed key. :param str data: Data to publish to the feed or group. :param int data: Data to publish to the feed or group. @@ -417,6 +419,7 @@ class IO_HTTP: """ Client for interacting with the Adafruit IO HTTP API. https://io.adafruit.com/api/docs/#adafruit-io-http-api + :param str adafruit_io_username: Adafruit IO Username :param str adafruit_io_key: Adafruit IO Key :param wifi_manager: WiFiManager object from ESPSPI_WiFiManager or ESPAT_WiFiManager From 7fc15a413a6178e66a1b4100f7b7f851b5820ae5 Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 17 Jul 2019 17:54:08 -0400 Subject: [PATCH 33/35] lint --- examples/mqtt/adafruit_io_simpletest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/mqtt/adafruit_io_simpletest.py b/examples/mqtt/adafruit_io_simpletest.py index bfd1562..b7cd68b 100755 --- a/examples/mqtt/adafruit_io_simpletest.py +++ b/examples/mqtt/adafruit_io_simpletest.py @@ -13,10 +13,10 @@ from adafruit_esp32spi import adafruit_esp32spi from adafruit_esp32spi import adafruit_esp32spi_wifimanager import adafruit_esp32spi.adafruit_esp32spi_socket as socket +from digitalio import DigitalInOut import neopixel from adafruit_io.adafruit_io import IO_MQTT from adafruit_minimqtt import MQTT -from digitalio import DigitalInOut ### WiFi ### From eb1475934df4cea2304418cf2447a698e19733d9 Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 17 Jul 2019 17:58:57 -0400 Subject: [PATCH 34/35] drop log from example --- examples/mqtt/adafruit_io_location.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/mqtt/adafruit_io_location.py b/examples/mqtt/adafruit_io_location.py index 8e69b34..76c0a07 100755 --- a/examples/mqtt/adafruit_io_location.py +++ b/examples/mqtt/adafruit_io_location.py @@ -83,8 +83,7 @@ def message(client, feed_id, payload): broker="io.adafruit.com", username=secrets["aio_user"], password=secrets["aio_key"], - network_manager=wifi, - log=True, + network_manager=wifi ) # Initialize an Adafruit IO MQTT Client From 5cc883983c8639aeffac1b364950e548c0ec6ad3 Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 17 Jul 2019 18:06:40 -0400 Subject: [PATCH 35/35] there isnt a dep for esp32spi, drop it. add new examples to readme --- README.rst | 36 ++---------------------------------- 1 file changed, 2 insertions(+), 34 deletions(-) diff --git a/README.rst b/README.rst index 52d2850..a6644bd 100644 --- a/README.rst +++ b/README.rst @@ -23,12 +23,6 @@ This driver depends on: * `Adafruit CircuitPython `_ -You'll also need a library to communicate with an ESP32 as a coprocessor using a WiFiManager object. This library supports connecting an ESP32 using either SPI or UART. - -* SPI: `Adafruit CircuitPython ESP32SPI `_ - -* UART: `Adafruit CircuitPython ESP_ATcontrol `_ - Please ensure all dependencies are available on the CircuitPython filesystem. This is easily achieved by downloading `the Adafruit library and driver bundle `_. @@ -36,35 +30,9 @@ This is easily achieved by downloading Usage Example ============= -Create an Adafruit IO Client object - -.. code-block:: python - - io = RESTClient(aio_username, aio_key, wifi) - -Sending data to an Adafruit IO feed - -.. code-block:: python - - io.send_data(feed, data) - -Receiving data from an Adafruit IO feed - -.. code-block:: python - - data = io.receive_data(feed) - -Creating a new feed named circuitpython with a description - -.. code-block:: python - - feed = io.create_new_feed('circuitpython', 'an Adafruit IO CircuitPython feed') - -Listing the record of a specified feed: +Usage examples for the Adafruit IO HTTP API are within the examples/http folder. -.. code-block:: python - - feed = io.get_feed('circuitpython') +Usage examples for the Adafruit IO MQTT API are within the examples/mqtt folder. Contributing ============