From 42df192722437f4eca97686506e6b30a2b8acb2f Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 26 Dec 2018 12:34:36 -0500 Subject: [PATCH 001/116] adding RTD documentation to all methods for client lib --- Adafruit_IO/client.py | 102 ++++++++++++++++++++++-------------------- 1 file changed, 54 insertions(+), 48 deletions(-) diff --git a/Adafruit_IO/client.py b/Adafruit_IO/client.py index e74ae10..76d603b 100644 --- a/Adafruit_IO/client.py +++ b/Adafruit_IO/client.py @@ -116,6 +116,8 @@ def send_data(self, feed, value): specified value to the feed identified by either name, key, or ID. Returns a Data instance with details about the newly appended row of data. Note that send_data now operates the same as append. + :param string feed: Name/Key/ID of Adafruit IO feed. + :param string value: Value to send. """ return self.create_data(feed, Data(value=value)) @@ -126,6 +128,8 @@ def send_batch_data(self, feed, data_list): ID, feed key, or feed name. Data must be an instance of the Data class with at least a value property set on it. Returns a Data instance with details about the newly appended row of data. + :param string feed: Name/Key/ID of Adafruit IO feed. + :param Data data_list: Multiple data values. """ path = "feeds/{0}/data/batch".format(feed) data_dict = type(data_list)((data._asdict() for data in data_list)) @@ -136,58 +140,57 @@ def append(self, feed, value): specified value to the feed identified by either name, key, or ID. Returns a Data instance with details about the newly appended row of data. Note that unlike send the feed should exist before calling append. + :param string feed: Name/Key/ID of Adafruit IO feed. + :param string value: Value to append to feed. """ return self.create_data(feed, Data(value=value)) - def send_location_data(self, feed, value, lat, lon, ele): - """Sends locational data to a feed - - args: - - lat: latitude - - lon: logitude - - ele: elevation - - (optional) value: value to send to the feed + def send_location_data(self, feed, lat, lon, ele, value=None): + """Sends locational data to a feed. + :param string feed: Name/Key/ID of Adafruit IO feed. + :param int lat: Latitude. + :param int lon: Longitude. + :param int ele: Elevation. + :param int value: Optional value to send, defaults to None. """ - return self.create_data(feed, Data(value = value,lat=lat, lon=lon, ele=ele)) + return self.create_data(feed, Data(value=value,lat=lat, lon=lon, ele=ele)) def receive_time(self, time): """Returns the time from the Adafruit IO server. - - args: - - time (string): millis, seconds, ISO-8601 + :param string time: Time to be returned: `millis`, `seconds`, `ISO-8601`. """ timepath = "time/{0}".format(time) return self._get(timepath, is_time=True) def receive(self, feed): - """Retrieve the most recent value for the specified feed. Feed can be a - feed ID, feed key, or feed name. Returns a Data instance whose value - property holds the retrieved value. + """Retrieve the most recent value for the specified feed. Returns a Data + instance whose value property holds the retrieved value. + :param string feed: Name/Key/ID of Adafruit IO feed. """ path = "feeds/{0}/data/last".format(feed) return Data.from_dict(self._get(path)) def receive_next(self, feed): - """Retrieve the next unread value from the specified feed. Feed can be - a feed ID, feed key, or feed name. Returns a Data instance whose value - property holds the retrieved value. + """Retrieve the next unread value from the specified feed. Returns a Data + instance whose value property holds the retrieved value. + :param string feed: Name/Key/ID of Adafruit IO feed. """ path = "feeds/{0}/data/next".format(feed) return Data.from_dict(self._get(path)) def receive_previous(self, feed): - """Retrieve the previous unread value from the specified feed. Feed can - be a feed ID, feed key, or feed name. Returns a Data instance whose - value property holds the retrieved value. + """Retrieve the previous unread value from the specified feed. Returns a + Data instance whose value property holds the retrieved value. + :param string feed: Name/Key/ID of Adafruit IO feed. """ path = "feeds/{0}/data/previous".format(feed) return Data.from_dict(self._get(path)) def data(self, feed, data_id=None): - """Retrieve data from a feed. Feed can be a feed ID, feed key, or feed - name. Data_id is an optional id for a single data value to retrieve. - If data_id is not specified then all the data for the feed will be - returned in an array. + """Retrieve data from a feed. If data_id is not specified then all the data + for the feed will be returned in an array. + :param string feed: Name/Key/ID of Adafruit IO feed. + :param string data_id: ID of the piece of data to delete. """ if data_id is None: path = "feeds/{0}/data".format(feed) @@ -197,41 +200,46 @@ def data(self, feed, data_id=None): return Data.from_dict(self._get(path)) def create_data(self, feed, data): - """Create a new row of data in the specified feed. Feed can be a feed - ID, feed key, or feed name. Data must be an instance of the Data class - with at least a value property set on it. Returns a Data instance with - details about the newly appended row of data. + """Create a new row of data in the specified feed. + Returns a Data instance with details about the newly + appended row of data. + :param string feed: Name/Key/ID of Adafruit IO feed. + :param Data data: Instance of the Data class. Must have a value property set. """ path = "feeds/{0}/data".format(feed) return Data.from_dict(self._post(path, data._asdict())) def delete(self, feed, data_id): - """Delete data from a feed. Feed can be a feed ID, feed key, or feed - name. Data_id must be the ID of the piece of data to delete. + """Delete data from a feed. + :param string feed: Name/Key/ID of Adafruit IO feed. + :param string data_id: ID of the piece of data to delete. """ path = "feeds/{0}/data/{1}".format(feed, data_id) self._delete(path) def toRed(self, data): - """Hex color feed to red channel. + """Hex color feed to red channel. + :param int data: Color value, in hexadecimal. """ return ((int(data[1], 16))*16) + int(data[2], 16) def toGreen(self, data): """Hex color feed to green channel. + :param int data: Color value, in hexadecimal. """ return (int(data[3], 16) * 16) + int(data[4], 16) def toBlue(self, data): - """Hex color feed to blue channel. + """Hex color feed to blue channel. + :param int data: Color value, in hexadecimal. """ return (int(data[5], 16) * 16) + int(data[6], 16) - # Feed functionality. + # feed functionality. def feeds(self, feed=None): """Retrieve a list of all feeds, or the specified feed. If feed is not - specified a list of all feeds will be returned. If feed is specified it - can be a feed name, key, or ID and the requested feed will be returned. + specified a list of all feeds will be returned. + :param string feed: Name/Key/ID of Adafruit IO feed, defaults to None. """ if feed is None: path = "feeds" @@ -241,25 +249,23 @@ def feeds(self, feed=None): return Feed.from_dict(self._get(path)) def create_feed(self, feed): - """Create the specified feed. Feed should be an instance of the Feed - type with at least the name property set. + """Create the specified feed. + :param string feed: Name/Key/ID of Adafruit IO feed. """ path = "feeds/" return Feed.from_dict(self._post(path, {"feed": feed._asdict()})) def delete_feed(self, feed): - """Delete the specified feed. Feed can be a feed ID, feed key, or feed - name. + """Delete the specified feed. + :param string feed: Name/Key/ID of Adafruit IO feed. """ path = "feeds/{0}".format(feed) self._delete(path) # Group functionality. def groups(self, group=None): - """Retrieve a list of all groups, or the specified group. If group is - not specified a list of all groups will be returned. If group is - specified it can be a group name, key, or ID and the requested group - will be returned. + """Retrieve a list of all groups, or the specified group. + :param string group: Name/Key/ID of Adafruit IO Group. Defaults to None. """ if group is None: path = "groups/" @@ -269,15 +275,15 @@ def groups(self, group=None): return Group.from_dict(self._get(path)) def create_group(self, group): - """Create the specified group. Group should be an instance of the Group - type with at least the name and feeds property set. + """Create the specified group. + :param string group: Name/Key/ID of Adafruit IO Group. """ path = "groups/" return Group.from_dict(self._post(path, group._asdict())) def delete_group(self, group): - """Delete the specified group. Group can be a group ID, group key, or - group name. + """Delete the specified group. + :param string group: Name/Key/ID of Adafruit IO Group. """ path = "groups/{0}".format(group) self._delete(path) \ No newline at end of file From e68de95345578418632558b7e4b1073893c192c8 Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 26 Dec 2018 12:39:35 -0500 Subject: [PATCH 002/116] updating send_lococation_data test --- tests/test_client.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/test_client.py b/tests/test_client.py index fd75b27..29e35cf 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -23,7 +23,7 @@ class TestClient(base.IOTestCase): # If your IP isn't put on the list of non-throttled IPs, uncomment the # function below to waste time between tests to prevent throttling. #def tearDown(self): - # time.sleep(30.0) + time.sleep(30.0) # Helper Methods def get_client(self): @@ -132,15 +132,14 @@ def test_create_data(self): def test_location_data(self): aio = self.get_client() self.ensure_feed_deleted(aio, 'testlocfeed') - test_feed = aio.create_feed(Feed(name="testlocfeed")) - aio.send_location_data(test_feed.key, 0, 40, -74, 6) + test_feed = aio.create_feed(Feed(name='testlocfeed')) + aio.send_location_data(test_feed.key, 40, -74, 6, 0) data = aio.receive(test_feed.key) - self.assertEqual(int(data.value), 0) + self.assertEqual(int(data.value), 0.0) self.assertEqual(float(data.lat), 40.0) self.assertEqual(float(data.lon), -74.0) self.assertEqual(float(data.ele), 6.0) - # Test Feed Functionality def test_append_by_feed_name(self): io = self.get_client() From 209c0f3160f4bc9c56f15cb82a3b25ab918fbc07 Mon Sep 17 00:00:00 2001 From: brentru Date: Sun, 30 Dec 2018 20:57:10 -0500 Subject: [PATCH 003/116] update examples for breaking change within send_location_data method, bump version --- Adafruit_IO/_version.py | 2 +- examples/api/location.py | 2 +- examples/basics/location.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Adafruit_IO/_version.py b/Adafruit_IO/_version.py index ff87090..ab999f0 100644 --- a/Adafruit_IO/_version.py +++ b/Adafruit_IO/_version.py @@ -1 +1 @@ -__version__ = "2.0.17" \ No newline at end of file +__version__ = "2.0.18" \ No newline at end of file diff --git a/examples/api/location.py b/examples/api/location.py index 7bec293..08f5fdb 100644 --- a/examples/api/location.py +++ b/examples/api/location.py @@ -33,4 +33,4 @@ ele = 6 # elevation above sea level (meters) # Send location data to Adafruit IO -aio.send_location_data(location.key, value, lat, lon, ele) +aio.send_location_data(location.key, lat, lon, ele, value) diff --git a/examples/basics/location.py b/examples/basics/location.py index 24066f1..b110e9a 100644 --- a/examples/basics/location.py +++ b/examples/basics/location.py @@ -49,7 +49,7 @@ print('\tLon: ', lon) print('\tEle: ', ele) # Send location data to Adafruit IO - aio.send_location_data(location.key, value, lat, lon, ele) + aio.send_location_data(location.key, lat, lon, ele, value) # shift all values (for test/demo purposes) value += 1 lat -= 0.01 From fbbfe8ffae49fec6b7f7850eae2d09ff4752d99a Mon Sep 17 00:00:00 2001 From: brentru Date: Tue, 8 Jan 2019 12:49:40 -0500 Subject: [PATCH 004/116] add darkskies api to io python client --- Adafruit_IO/client.py | 13 ++++++++++--- examples/basics/darkskies.py | 24 ++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 examples/basics/darkskies.py diff --git a/Adafruit_IO/client.py b/Adafruit_IO/client.py index 76d603b..6809371 100644 --- a/Adafruit_IO/client.py +++ b/Adafruit_IO/client.py @@ -61,10 +61,10 @@ def __init__(self, username, key, proxies=None, base_url='https://io.adafruit.co self.base_url = base_url.rstrip('/') def _compose_url(self, path, is_time=None): - if not is_time: + if is_time: # return a call to https://io.adafruit.com/api/v2/time/{unit} + return '{0}/api/{1}/{2}'.format(self.base_url, self.api_version, path) + else: return '{0}/api/{1}/{2}/{3}'.format(self.base_url, self.api_version, self.username, path) - else: # return a call to https://io.adafruit.com/api/v2/time/{unit} - return '{0}/api/{1}/{2}'.format(self.base_url, self.api_version, path) def _handle_error(self, response): @@ -161,6 +161,13 @@ def receive_time(self, time): """ timepath = "time/{0}".format(time) return self._get(timepath, is_time=True) + + def receive_weather(self, weather_id): + """Get the specified weather record with current weather and all available forecast information. + :param int id: ID for forecast + """ + weatherpath = "integrations/weather/{0}".format(weather_id) + return self._get(weatherpath) def receive(self, feed): """Retrieve the most recent value for the specified feed. Returns a Data diff --git a/examples/basics/darkskies.py b/examples/basics/darkskies.py new file mode 100644 index 0000000..0fa5b8c --- /dev/null +++ b/examples/basics/darkskies.py @@ -0,0 +1,24 @@ +""" +Example of getting a darkskies forecast +from Adafruit IO. +""" +# import Adafruit IO REST client. +from Adafruit_IO import Client, Feed, RequestError + +# Set to your Adafruit IO key. +# Remember, your key is a secret, +# so make sure not to publish it when you publish this code! +ADAFRUIT_IO_KEY = 'PASS' + +# Set to your Adafruit IO username. +# (go to https://accounts.adafruit.com to find your username) +ADAFRUIT_IO_USERNAME = 'USER' + +# Create an instance of the REST client. +aio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY) + + + +forecast = aio.receive_weather(2153) + +print(forecast) \ No newline at end of file From b6debfc9f1a1981d995f7ac83fc2a87e8078f0e1 Mon Sep 17 00:00:00 2001 From: brentru Date: Tue, 8 Jan 2019 12:57:56 -0500 Subject: [PATCH 005/116] make weather_id an optional param, update example --- Adafruit_IO/client.py | 11 ++-- examples/basics/{darkskies.py => darksky.py} | 21 +++++--- examples/basics/digital_in.py | 54 -------------------- 3 files changed, 20 insertions(+), 66 deletions(-) rename examples/basics/{darkskies.py => darksky.py} (55%) delete mode 100644 examples/basics/digital_in.py diff --git a/Adafruit_IO/client.py b/Adafruit_IO/client.py index 6809371..bd1ceb2 100644 --- a/Adafruit_IO/client.py +++ b/Adafruit_IO/client.py @@ -162,11 +162,14 @@ def receive_time(self, time): timepath = "time/{0}".format(time) return self._get(timepath, is_time=True) - def receive_weather(self, weather_id): - """Get the specified weather record with current weather and all available forecast information. - :param int id: ID for forecast + def receive_weather(self, weather_id=None): + """Adafruit IO Weather Service, Powered by Dark Sky + :param int id: optional ID for retrieving a specified weather record. """ - weatherpath = "integrations/weather/{0}".format(weather_id) + if weather_id: + weatherpath = "integrations/weather/{0}".format(weather_id) + else: + weatherpath = "integrations/weather" return self._get(weatherpath) def receive(self, feed): diff --git a/examples/basics/darkskies.py b/examples/basics/darksky.py similarity index 55% rename from examples/basics/darkskies.py rename to examples/basics/darksky.py index 0fa5b8c..9270576 100644 --- a/examples/basics/darkskies.py +++ b/examples/basics/darksky.py @@ -1,24 +1,29 @@ """ -Example of getting a darkskies forecast -from Adafruit IO. +Example of getting a forecast from Adafruit IO +Note: This functionality is for IO PLUS users ONLY. + +Author: Brent Rubell for Adafruit Industries """ # import Adafruit IO REST client. from Adafruit_IO import Client, Feed, RequestError +# Set to your Adafruit IO username. +# (go to https://accounts.adafruit.com to find your username) +ADAFRUIT_IO_USERNAME = 'USER' + # Set to your Adafruit IO key. # Remember, your key is a secret, # so make sure not to publish it when you publish this code! -ADAFRUIT_IO_KEY = 'PASS' +ADAFRUIT_IO_KEY = 'PASSWORD' -# Set to your Adafruit IO username. -# (go to https://accounts.adafruit.com to find your username) -ADAFRUIT_IO_USERNAME = 'USER' # Create an instance of the REST client. aio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY) +print('Get all weather integration records without their current forecast values.') +records = aio.receive_weather() +print(records) - +print('Get the specified weather record with current weather and all available forecast information.') forecast = aio.receive_weather(2153) - print(forecast) \ No newline at end of file diff --git a/examples/basics/digital_in.py b/examples/basics/digital_in.py deleted file mode 100644 index d2fdb4e..0000000 --- a/examples/basics/digital_in.py +++ /dev/null @@ -1,54 +0,0 @@ -""" -'digital_in.py' -================================== -Example of sending button values -to an Adafruit IO feed. - -Author(s): Brent Rubell, Todd Treece -""" -# Import standard python modules -import time - -# import Adafruit Blinka -import board -import digitalio - -# import Adafruit IO REST client. -from Adafruit_IO import Client, Feed, RequestError - -# Set to your Adafruit IO key. -# Remember, your key is a secret, -# so make sure not to publish it when you publish this code! -ADAFRUIT_IO_KEY = 'YOUR_AIO_KEY' - -# Set to your Adafruit IO username. -# (go to https://accounts.adafruit.com to find your username) -ADAFRUIT_IO_USERNAME = 'YOUR_AIO_USERNAME' - -# Create an instance of the REST client. -aio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY) - -try: # if we have a 'digital' feed - digital = aio.feeds('digital') -except RequestError: # create a digital feed - feed = Feed(name="digital") - digital = aio.create_feed(feed) - -# button set up -button = digitalio.DigitalInOut(board.D12) -button.direction = digitalio.Direction.INPUT -button.pull = digitalio.Pull.UP -button_current = 0 - - -while True: - if not button.value: - button_current = 1 - else: - button_current = 0 - - print('Button -> ', button_current) - aio.send(digital.key, button_current) - - # avoid timeout from adafruit io - time.sleep(1) From b1ae23c78bbd25eef689343e9e94b625358ff8a2 Mon Sep 17 00:00:00 2001 From: brentru Date: Tue, 8 Jan 2019 12:59:36 -0500 Subject: [PATCH 006/116] bump version --- Adafruit_IO/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Adafruit_IO/_version.py b/Adafruit_IO/_version.py index ab999f0..00b64fe 100644 --- a/Adafruit_IO/_version.py +++ b/Adafruit_IO/_version.py @@ -1 +1 @@ -__version__ = "2.0.18" \ No newline at end of file +__version__ = "2.0.19" \ No newline at end of file From f00bdcd06673eabfee23dad04f6545a5178ae43d Mon Sep 17 00:00:00 2001 From: brentru Date: Tue, 8 Jan 2019 13:15:31 -0500 Subject: [PATCH 007/116] adding MQTT API integration --- Adafruit_IO/mqtt_client.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Adafruit_IO/mqtt_client.py b/Adafruit_IO/mqtt_client.py index c032dae..99bebe3 100644 --- a/Adafruit_IO/mqtt_client.py +++ b/Adafruit_IO/mqtt_client.py @@ -197,6 +197,13 @@ def subscribe_group(self, group_id): """ self._client.subscribe('{0}/groups/{1}'.format(self._username, group_id)) + def subscribe_weather(self, weather_id, forecast_type): + """Subscribe to Adafruit IO Weather + :param int weather_id: weather record you want data for + :param string type: type of forecast data requested + """ + self._client.subscribe('{0}/integration/weather/{1}/{2}'.format(self._username, weather_id, forecast_type)) + def subscribe_time(self, time): """Subscribe to changes on the Adafruit IO time feeds. When the feed is updated, the on_message function will be called and publish a new value: From c6b9dfcca8957a4bc45b4d31eaf0782fa281447b Mon Sep 17 00:00:00 2001 From: brentru Date: Tue, 8 Jan 2019 13:18:57 -0500 Subject: [PATCH 008/116] adding example of MQTT Weather API --- examples/mqtt/mqtt_weather.py | 79 +++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 examples/mqtt/mqtt_weather.py diff --git a/examples/mqtt/mqtt_weather.py b/examples/mqtt/mqtt_weather.py new file mode 100644 index 0000000..334dfc2 --- /dev/null +++ b/examples/mqtt/mqtt_weather.py @@ -0,0 +1,79 @@ +""" +Example of using the Adafruit IO MQTT Client +for subscribing to the Adafruit IO Weather Service +Note: This feature is avaliable for IO Plus Subscribers ONLY + +Author: Brent Rubell for Adafruit Industries +""" + +# Import standard python modules. +import sys + +# Import Adafruit IO MQTT client. +from Adafruit_IO import MQTTClient + +# Set to your Adafruit IO key. +# Remember, your key is a secret, +# so make sure not to publish it when you publish this code! +ADAFRUIT_IO_KEY = 'KEY' + +# Set to your Adafruit IO username. +# (go to https://accounts.adafruit.com to find your username) +ADAFRUIT_IO_USERNAME = 'USER' + +# Set to ID of the forecast to subscribe to for updates. +forecast_id = 1234 + +# 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 +""" +forecast_type = 'current' + +# 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 {0} changes...'.format(FEED_ID)) + # Subscribe to changes on a feed named DemoFeed. + client.subscribe_weather(forecast_id, forecast_type) + +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('Weather Subscription {0} received new value: {1}'.format(forecast_id, payload)) + + +# Create an MQTT client instance. +client = MQTTClient(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY) + +# 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 3f7a8d53f48f5591482066a6fb1e848d97a6ac33 Mon Sep 17 00:00:00 2001 From: brentru Date: Tue, 8 Jan 2019 13:20:35 -0500 Subject: [PATCH 009/116] update prints for forecast --- examples/mqtt/mqtt_weather.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/mqtt/mqtt_weather.py b/examples/mqtt/mqtt_weather.py index 334dfc2..06826a2 100644 --- a/examples/mqtt/mqtt_weather.py +++ b/examples/mqtt/mqtt_weather.py @@ -21,7 +21,7 @@ # (go to https://accounts.adafruit.com to find your username) ADAFRUIT_IO_USERNAME = 'USER' -# Set to ID of the forecast to subscribe to for updates. +# Set to ID of the forecast to subscribe to for updates forecast_id = 1234 # Set to the ID of the feed to subscribe to for updates. @@ -46,7 +46,7 @@ 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 {0} changes...'.format(FEED_ID)) + print('Connected to Adafruit IO! Listening to forecast: {0}...'.format(forecast_id)) # Subscribe to changes on a feed named DemoFeed. client.subscribe_weather(forecast_id, forecast_type) From 6752497e58153bd3a30ea9ab4866602de6a43d46 Mon Sep 17 00:00:00 2001 From: brentru Date: Tue, 8 Jan 2019 13:36:52 -0500 Subject: [PATCH 010/116] enforce strict forecast types, raise error --- Adafruit_IO/mqtt_client.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Adafruit_IO/mqtt_client.py b/Adafruit_IO/mqtt_client.py index 99bebe3..3134dff 100644 --- a/Adafruit_IO/mqtt_client.py +++ b/Adafruit_IO/mqtt_client.py @@ -29,6 +29,11 @@ logger = logging.getLogger(__name__) +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 MQTTClient(object): """Interface for publishing and subscribing to feed changes on Adafruit IO @@ -202,7 +207,10 @@ def subscribe_weather(self, weather_id, forecast_type): :param int weather_id: weather record you want data for :param string type: type of forecast data requested """ - self._client.subscribe('{0}/integration/weather/{1}/{2}'.format(self._username, weather_id, forecast_type)) + if forecast_type in forecast_types: + self._client.subscribe('{0}/integration/weather/{1}/{2}'.format(self._username, weather_id, forecast_type)) + else: + raise TypeError("Invalid Forecast Type Specified.") def subscribe_time(self, time): """Subscribe to changes on the Adafruit IO time feeds. When the feed is From e0904c66d78b6bb2aeb0f5e887419c67f5171e1b Mon Sep 17 00:00:00 2001 From: brentru Date: Tue, 8 Jan 2019 14:37:47 -0500 Subject: [PATCH 011/116] add forecast topic to mqtt message parser --- Adafruit_IO/mqtt_client.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Adafruit_IO/mqtt_client.py b/Adafruit_IO/mqtt_client.py index 3134dff..3194f85 100644 --- a/Adafruit_IO/mqtt_client.py +++ b/Adafruit_IO/mqtt_client.py @@ -109,7 +109,10 @@ def _mqtt_message(self, client, userdata, msg): """ parsed_topic = msg.topic.split('/') if self.on_message is not None: - topic = parsed_topic[2] + topic = parsed_topic[1] + payload = '' if msg.payload is None else msg.payload.decode('utf-8') + elif self.on_message is not None and parsed_topic[2] == 'weather': + topic = parsed_topic[4] payload = '' if msg.payload is None else msg.payload.decode('utf-8') elif self.on_message is not None and parsed_topic[0] == 'time': topic = parsed_topic[0] @@ -211,6 +214,7 @@ def subscribe_weather(self, weather_id, forecast_type): self._client.subscribe('{0}/integration/weather/{1}/{2}'.format(self._username, weather_id, forecast_type)) else: raise TypeError("Invalid Forecast Type Specified.") + return def subscribe_time(self, time): """Subscribe to changes on the Adafruit IO time feeds. When the feed is From aca9f67f5a84e08bd9fbb030ad81d6d9207c2a24 Mon Sep 17 00:00:00 2001 From: brentru Date: Tue, 8 Jan 2019 14:48:47 -0500 Subject: [PATCH 012/116] redo parser --- Adafruit_IO/mqtt_client.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Adafruit_IO/mqtt_client.py b/Adafruit_IO/mqtt_client.py index 3194f85..30df9c5 100644 --- a/Adafruit_IO/mqtt_client.py +++ b/Adafruit_IO/mqtt_client.py @@ -108,10 +108,9 @@ def _mqtt_message(self, client, userdata, msg): assume topic looks like `username/topic/id` """ parsed_topic = msg.topic.split('/') - if self.on_message is not None: - topic = parsed_topic[1] - payload = '' if msg.payload is None else msg.payload.decode('utf-8') - elif self.on_message is not None and parsed_topic[2] == 'weather': + print(parsed_topic) # BR: Remove this + if self.on_message is not None and parsed_topic[2] == 'weather': + print('Weather Subscription') topic = parsed_topic[4] payload = '' if msg.payload is None else msg.payload.decode('utf-8') elif self.on_message is not None and parsed_topic[0] == 'time': @@ -120,6 +119,9 @@ def _mqtt_message(self, client, userdata, msg): elif self.on_message is not None and parsed_topic[1] == 'groups': topic = parsed_topic[3] payload = msg.payload.decode('utf-8') + else: + topic = parsed_topic[1] + payload = '' if msg.payload is None else msg.payload.decode('utf-8') self.on_message(self, topic, payload) def _mqtt_subscribe(client, userdata, mid, granted_qos): From 7ada7633040cb310b90a694882e509cccffc4477 Mon Sep 17 00:00:00 2001 From: brentru Date: Tue, 8 Jan 2019 14:52:02 -0500 Subject: [PATCH 013/116] finish parser, tested --- Adafruit_IO/mqtt_client.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Adafruit_IO/mqtt_client.py b/Adafruit_IO/mqtt_client.py index 30df9c5..2e2b158 100644 --- a/Adafruit_IO/mqtt_client.py +++ b/Adafruit_IO/mqtt_client.py @@ -108,10 +108,8 @@ def _mqtt_message(self, client, userdata, msg): assume topic looks like `username/topic/id` """ parsed_topic = msg.topic.split('/') - print(parsed_topic) # BR: Remove this if self.on_message is not None and parsed_topic[2] == 'weather': - print('Weather Subscription') - topic = parsed_topic[4] + topic = parsed_topic[4] # parse out the forecast type payload = '' if msg.payload is None else msg.payload.decode('utf-8') elif self.on_message is not None and parsed_topic[0] == 'time': topic = parsed_topic[0] From 64c42de144857c8a43cbd57af27e05b4da67bef6 Mon Sep 17 00:00:00 2001 From: brentru Date: Tue, 8 Jan 2019 15:01:11 -0500 Subject: [PATCH 014/116] update example to show 1, 2, 5 day forecasts --- Adafruit_IO/mqtt_client.py | 2 +- examples/mqtt/mqtt_weather.py | 42 ++++++++++++++++++++++++++--------- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/Adafruit_IO/mqtt_client.py b/Adafruit_IO/mqtt_client.py index 2e2b158..34d1147 100644 --- a/Adafruit_IO/mqtt_client.py +++ b/Adafruit_IO/mqtt_client.py @@ -117,7 +117,7 @@ def _mqtt_message(self, client, userdata, msg): elif self.on_message is not None and parsed_topic[1] == 'groups': topic = parsed_topic[3] payload = msg.payload.decode('utf-8') - else: + else: # default topic topic = parsed_topic[1] payload = '' if msg.payload is None else msg.payload.decode('utf-8') self.on_message(self, topic, payload) diff --git a/examples/mqtt/mqtt_weather.py b/examples/mqtt/mqtt_weather.py index 06826a2..0f0fb41 100644 --- a/examples/mqtt/mqtt_weather.py +++ b/examples/mqtt/mqtt_weather.py @@ -22,7 +22,7 @@ ADAFRUIT_IO_USERNAME = 'USER' # Set to ID of the forecast to subscribe to for updates -forecast_id = 1234 +forecast_id = 2153 # Set to the ID of the feed to subscribe to for updates. """ @@ -38,7 +38,12 @@ forecast_days_2 forecast_days_5 """ -forecast_type = 'current' +# Subscribe to the current forecast +forecast_today = 'current' +# Subscribe to tomorrow's forecast +forecast_tomorrow = '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. def connected(client): @@ -47,20 +52,37 @@ def connected(client): # 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 a feed named DemoFeed. - client.subscribe_weather(forecast_id, forecast_type) + # 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_tomorrow) + + # Subscribe to changes on forecast in 5 days. + client.subscribe_weather(forecast_id, forecast_in_5_days) 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('Weather Subscription {0} received new value: {1}'.format(forecast_id, payload)) - +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. + """ + # Raw data from feed + print(topic) + print(payload) + # parse based on topic + if topic is 'current': + # Print out today's forecast + print('Current Forecast: {0}'.format(payload)) + elif topic is 'forecast_days_2': + # print out tomorrow's forecast + print('Forecast Tomorrow: {0}'.format(payload)) + elif topic is 'forecast_days_5': + # print out forecast in 5 days + print('Forecast in 5 Days: {0}'.format(payload)) # Create an MQTT client instance. client = MQTTClient(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY) From 8914f59a310b5c724641c9354a81815737ea68c8 Mon Sep 17 00:00:00 2001 From: brentru Date: Tue, 8 Jan 2019 15:34:25 -0500 Subject: [PATCH 015/116] parsing 3 forecasts --- examples/mqtt/mqtt_weather.py | 42 +++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/examples/mqtt/mqtt_weather.py b/examples/mqtt/mqtt_weather.py index 0f0fb41..74dea4c 100644 --- a/examples/mqtt/mqtt_weather.py +++ b/examples/mqtt/mqtt_weather.py @@ -8,6 +8,7 @@ # Import standard python modules. import sys +import json # Import Adafruit IO MQTT client. from Adafruit_IO import MQTTClient @@ -41,7 +42,7 @@ # Subscribe to the current forecast forecast_today = 'current' # Subscribe to tomorrow's forecast -forecast_tomorrow = 'forecast_days_2' +forecast_two_days = 'forecast_days_2' # Subscribe to forecast in 5 days forecast_in_5_days = 'forecast_days_5' @@ -56,7 +57,7 @@ def connected(client): client.subscribe_weather(forecast_id, forecast_today) # Subscribe to changes on tomorrow's forecast. - client.subscribe_weather(forecast_id, forecast_tomorrow) + 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) @@ -70,19 +71,32 @@ 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. """ - # Raw data from feed - print(topic) - print(payload) - # parse based on topic - if topic is 'current': + # forecast based on mqtt topic + if topic == 'current': # Print out today's forecast - print('Current Forecast: {0}'.format(payload)) - elif topic is 'forecast_days_2': - # print out tomorrow's forecast - print('Forecast Tomorrow: {0}'.format(payload)) - elif topic is 'forecast_days_5': - # print out forecast in 5 days - print('Forecast in 5 Days: {0}'.format(payload)) + 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 incoming forecast data + """ + # incoming data is a utf-8 string, encode it as a json object + forecast = json.loads(forecast_data) + print(forecast) + print('It is {0} and {1} F.'.format(forecast['summary'], forecast['temperature'])) + print('with a humidity of {0}% and a wind speed of {1}mph.'.format(forecast['humidity'], forecast['windSpeed'])) + print('There is a {0}% chance of precipitation. It feels like {1} F'.format(forecast['precipProbability'], forecast['apparentTemperature'])) # Create an MQTT client instance. client = MQTTClient(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY) From 25538f923960733aff166892c50ffd09ef492eae Mon Sep 17 00:00:00 2001 From: brentru Date: Tue, 8 Jan 2019 15:43:30 -0500 Subject: [PATCH 016/116] show all 3 forecasts nicely --- examples/mqtt/mqtt_weather.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/examples/mqtt/mqtt_weather.py b/examples/mqtt/mqtt_weather.py index 74dea4c..87e9c4f 100644 --- a/examples/mqtt/mqtt_weather.py +++ b/examples/mqtt/mqtt_weather.py @@ -3,6 +3,8 @@ for subscribing to the Adafruit IO Weather Service Note: This feature is avaliable for IO Plus Subscribers ONLY +API Documentation: https://io.adafruit.com/services/weather + Author: Brent Rubell for Adafruit Industries """ @@ -93,10 +95,13 @@ def parseForecast(forecast_data): """ # incoming data is a utf-8 string, encode it as a json object forecast = json.loads(forecast_data) - print(forecast) - print('It is {0} and {1} F.'.format(forecast['summary'], forecast['temperature'])) - print('with a humidity of {0}% and a wind speed of {1}mph.'.format(forecast['humidity'], forecast['windSpeed'])) - print('There is a {0}% chance of precipitation. It feels like {1} F'.format(forecast['precipProbability'], forecast['apparentTemperature'])) + try: + print('It is {0} and of {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 a humidity of {0}% and a wind speed of {1}mph.'.format(forecast['humidity'], forecast['windSpeed'])) + print('There is a {0}% chance of precipitation.'.format(forecast['precipProbability'])) # Create an MQTT client instance. client = MQTTClient(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY) From b2b8b18de96830f3242a9a5ecf40d4372eafbed3 Mon Sep 17 00:00:00 2001 From: brentru Date: Tue, 8 Jan 2019 15:55:52 -0500 Subject: [PATCH 017/116] same line on example --- examples/mqtt/mqtt_weather.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/examples/mqtt/mqtt_weather.py b/examples/mqtt/mqtt_weather.py index 87e9c4f..80a3866 100644 --- a/examples/mqtt/mqtt_weather.py +++ b/examples/mqtt/mqtt_weather.py @@ -91,17 +91,18 @@ def message(client, topic, payload): parseForecast(five_day_forecast) def parseForecast(forecast_data): - """Parses incoming 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 of {1}F.'.format(forecast['summary'], forecast['temperature'])) + 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 a humidity of {0}% and a wind speed of {1}mph.'.format(forecast['humidity'], forecast['windSpeed'])) - print('There is a {0}% chance of precipitation.'.format(forecast['precipProbability'])) + print('with a humidity of {0}%, a wind speed of {1}mph, and a {2}% chance of precipitation.'.format(forecast['humidity'], forecast['windSpeed'], forecast['precipProbability'])) # Create an MQTT client instance. client = MQTTClient(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY) @@ -117,4 +118,4 @@ def parseForecast(forecast_data): # 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() +client.loop_blocking() \ No newline at end of file From a85047b8b057641595ea9dbe93be4893405a2426 Mon Sep 17 00:00:00 2001 From: brentru Date: Tue, 8 Jan 2019 16:31:53 -0500 Subject: [PATCH 018/116] correct parsing of default topic --- Adafruit_IO/mqtt_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Adafruit_IO/mqtt_client.py b/Adafruit_IO/mqtt_client.py index 34d1147..046669a 100644 --- a/Adafruit_IO/mqtt_client.py +++ b/Adafruit_IO/mqtt_client.py @@ -118,7 +118,7 @@ def _mqtt_message(self, client, userdata, msg): topic = parsed_topic[3] payload = msg.payload.decode('utf-8') else: # default topic - topic = parsed_topic[1] + topic = parsed_topic[2] payload = '' if msg.payload is None else msg.payload.decode('utf-8') self.on_message(self, topic, payload) From abad94df4c0ab5715a3470771f7c50b67c1f3442 Mon Sep 17 00:00:00 2001 From: brentru Date: Tue, 8 Jan 2019 17:13:45 -0500 Subject: [PATCH 019/116] pylint example --- examples/basics/darksky.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/basics/darksky.py b/examples/basics/darksky.py index 9270576..cd83a8a 100644 --- a/examples/basics/darksky.py +++ b/examples/basics/darksky.py @@ -9,12 +9,12 @@ # Set to your Adafruit IO username. # (go to https://accounts.adafruit.com to find your username) -ADAFRUIT_IO_USERNAME = 'USER' +ADAFRUIT_IO_USERNAME = 'adafruitiotester' # Set to your Adafruit IO key. # Remember, your key is a secret, # so make sure not to publish it when you publish this code! -ADAFRUIT_IO_KEY = 'PASSWORD' +ADAFRUIT_IO_KEY = '1a6995af427747d7bd4a72c97af1d05a' # Create an instance of the REST client. From dc54320ec36fee7e51c4ed7c4ba809f2ae8d3f26 Mon Sep 17 00:00:00 2001 From: brentru Date: Tue, 8 Jan 2019 17:40:58 -0500 Subject: [PATCH 020/116] update weather, remove broken example --- examples/basics/darksky.py | 29 ------------------- examples/mqtt/mqtt_weather.py | 54 +++++++++++++++++++---------------- 2 files changed, 29 insertions(+), 54 deletions(-) delete mode 100644 examples/basics/darksky.py diff --git a/examples/basics/darksky.py b/examples/basics/darksky.py deleted file mode 100644 index cd83a8a..0000000 --- a/examples/basics/darksky.py +++ /dev/null @@ -1,29 +0,0 @@ -""" -Example of getting a forecast from Adafruit IO -Note: This functionality is for IO PLUS users ONLY. - -Author: Brent Rubell for Adafruit Industries -""" -# import Adafruit IO REST client. -from Adafruit_IO import Client, Feed, RequestError - -# Set to your Adafruit IO username. -# (go to https://accounts.adafruit.com to find your username) -ADAFRUIT_IO_USERNAME = 'adafruitiotester' - -# Set to your Adafruit IO key. -# Remember, your key is a secret, -# so make sure not to publish it when you publish this code! -ADAFRUIT_IO_KEY = '1a6995af427747d7bd4a72c97af1d05a' - - -# Create an instance of the REST client. -aio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY) - -print('Get all weather integration records without their current forecast values.') -records = aio.receive_weather() -print(records) - -print('Get the specified weather record with current weather and all available forecast information.') -forecast = aio.receive_weather(2153) -print(forecast) \ No newline at end of file diff --git a/examples/mqtt/mqtt_weather.py b/examples/mqtt/mqtt_weather.py index 80a3866..9d148d4 100644 --- a/examples/mqtt/mqtt_weather.py +++ b/examples/mqtt/mqtt_weather.py @@ -49,6 +49,7 @@ 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 @@ -64,45 +65,48 @@ def connected(client): # 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. """ # forecast based on mqtt topic if topic == 'current': - # Print out today's forecast - today_forecast = payload - print('\nCurrent Forecast') - parseForecast(today_forecast) + # 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) + # 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) + # 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 a humidity of {0}%, a wind speed of {1}mph, and a {2}% chance of precipitation.'.format(forecast['humidity'], forecast['windSpeed'], forecast['precipProbability'])) + """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'])) # Create an MQTT client instance. client = MQTTClient(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY) @@ -118,4 +122,4 @@ def parseForecast(forecast_data): # 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() \ No newline at end of file +client.loop_blocking() From 2dfaa712ac70a2cdc48f8c089c01ee803b45477b Mon Sep 17 00:00:00 2001 From: brentru Date: Mon, 14 Jan 2019 13:46:41 -0500 Subject: [PATCH 021/116] add example of accessing darksky forecast using the IO Weather Service and Adafruit IO API --- examples/api/weather.py | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 examples/api/weather.py diff --git a/examples/api/weather.py b/examples/api/weather.py new file mode 100644 index 0000000..3e4bc5c --- /dev/null +++ b/examples/api/weather.py @@ -0,0 +1,41 @@ +""" +'weather.py' +================================================ +Dark Sky Hyperlocal for IO Plus +with Adafruit IO API + +Author(s): Brent Rubell for Adafruit Industries +""" +# Import JSON for forecast parsing +import json +# Import Adafruit IO REST client. +from Adafruit_IO import Client, Feed, RequestError + +# Set to your Adafruit IO key. +ADAFRUIT_IO_USERNAME = 'YOUR_IO_USERNAME' +ADAFRUIT_IO_KEY = 'YOUR_IO_PASSWORD' + +# Create an instance of the REST client. +aio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY) + +# Grab the weather JSON +weather = aio.receive_weather(1234) +weather = json.dumps(weather) +forecast = json.loads(weather) + +# Parse the current forecast +current = forecast['current'] +print('Current Forecast') +print('It is {0} and {1}.'.format(current['summary'], current['temperature'])) + +# Parse the two day forecast +forecast_days_2 = forecast['forecast_days_2'] +print('\nWeather in Two Days') +print('It will be {0} with a high of {1}F and a low of {2}F.'.format( + forecast_days_2['summary'], forecast_days_2['temperatureLow'], forecast_days_2['temperatureHigh'])) + +# Parse the five day forecast +forecast_days_5 = forecast['forecast_days_5'] +print('\nWeather in Five Days') +print('It will be {0} with a high of {1}F and a low of {2}F.'.format( + forecast_days_5['summary'], forecast_days_5['temperatureLow'], forecast_days_5['temperatureHigh'])) \ No newline at end of file From 02634fb588172130e0fc83d24d6890c0c2fac024 Mon Sep 17 00:00:00 2001 From: brentru Date: Tue, 15 Jan 2019 14:21:40 -0500 Subject: [PATCH 022/116] add method for receiving random data --- Adafruit_IO/client.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/Adafruit_IO/client.py b/Adafruit_IO/client.py index bd1ceb2..c4d5fb2 100644 --- a/Adafruit_IO/client.py +++ b/Adafruit_IO/client.py @@ -167,10 +167,21 @@ def receive_weather(self, weather_id=None): :param int id: optional ID for retrieving a specified weather record. """ if weather_id: - weatherpath = "integrations/weather/{0}".format(weather_id) + weather_path = "integrations/weather/{0}".format(weather_id) else: - weatherpath = "integrations/weather" - return self._get(weatherpath) + weather_path = "integrations/weather" + return self._get(weather_path) + + def receive_random(self, id=None): + """Access to Adafruit IO's Random Data + service. + :param int id: optional ID for retrieving a specified randomizer. + """ + if id: + random_path = "integrations/words/{0}".format(id) + else: + random_path = "integrations/words" + return self._get(random_path) def receive(self, feed): """Retrieve the most recent value for the specified feed. Returns a Data From 32661c2b056f0abb43b7564715cc86081e8bad59 Mon Sep 17 00:00:00 2001 From: brentru Date: Tue, 15 Jan 2019 14:33:51 -0500 Subject: [PATCH 023/116] adding example of getting random data by id --- examples/api/random_data.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 examples/api/random_data.py diff --git a/examples/api/random_data.py b/examples/api/random_data.py new file mode 100644 index 0000000..96a190e --- /dev/null +++ b/examples/api/random_data.py @@ -0,0 +1,28 @@ +""" +'random_data.py' +================================================ +Example for accessing the Adafruit IO Random +Data Service. + +Author(s): Brent Rubell for Adafruit Industries +""" +# Import JSON for forecast parsing +import json +# Import Adafruit IO REST client. +from Adafruit_IO import Client, Feed, RequestError + +# Set to your Adafruit IO key. +ADAFRUIT_IO_USERNAME = 'USER' +ADAFRUIT_IO_KEY = 'KEY' + +# Create an instance of the REST client. +aio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY) + +generator_id = 1461 + +# Get the specified randomizer record with its current value and related details. +random_data = aio.receive_random(generator_id) +# Parse the API response +data = json.dumps(random_data) +data = json.loads(data) +print('Random Data: {0}'.format(data['value'])) \ No newline at end of file From d1b1ac8bc335a10211142909f1e842af60c8f4c3 Mon Sep 17 00:00:00 2001 From: brentru Date: Tue, 15 Jan 2019 15:20:30 -0500 Subject: [PATCH 024/116] add subscribe_randomizer and mqtt example --- Adafruit_IO/mqtt_client.py | 11 +++++++++++ examples/api/random_data.py | 4 ++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Adafruit_IO/mqtt_client.py b/Adafruit_IO/mqtt_client.py index 046669a..22cda92 100644 --- a/Adafruit_IO/mqtt_client.py +++ b/Adafruit_IO/mqtt_client.py @@ -205,6 +205,17 @@ def subscribe_group(self, group_id): """ self._client.subscribe('{0}/groups/{1}'.format(self._username, group_id)) + def subscribe_randomizer(self, randomizer_id): + """Subscribe to changes on a specified random data stream from + Adafruit IO's random data service. + + MQTT random word subscriptions will publish data once per minute to + every client that is subscribed to the same topic. + + :param int randomizer_id: ID of the random word record you want data for. + """ + self._client.subscribe('{0}/integration/words/{1}'.format(self._username, randomizer_id)) + def subscribe_weather(self, weather_id, forecast_type): """Subscribe to Adafruit IO Weather :param int weather_id: weather record you want data for diff --git a/examples/api/random_data.py b/examples/api/random_data.py index 96a190e..a1da2b3 100644 --- a/examples/api/random_data.py +++ b/examples/api/random_data.py @@ -12,8 +12,8 @@ from Adafruit_IO import Client, Feed, RequestError # Set to your Adafruit IO key. -ADAFRUIT_IO_USERNAME = 'USER' -ADAFRUIT_IO_KEY = 'KEY' +ADAFRUIT_IO_USERNAME = 'brubell' +ADAFRUIT_IO_KEY = '6ec4b31bd2c54a09be911e0c1909b7ab' # Create an instance of the REST client. aio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY) From b4ef4feb176617adc8b3962956b1b067e03cc360 Mon Sep 17 00:00:00 2001 From: brentru Date: Tue, 15 Jan 2019 15:27:18 -0500 Subject: [PATCH 025/116] update version to 2.1 --- Adafruit_IO/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Adafruit_IO/_version.py b/Adafruit_IO/_version.py index 00b64fe..259fe5f 100644 --- a/Adafruit_IO/_version.py +++ b/Adafruit_IO/_version.py @@ -1 +1 @@ -__version__ = "2.0.19" \ No newline at end of file +__version__ = "2.1" \ No newline at end of file From fc9dcbfc8a0afc4b5cdb4e3e0cd90c6b636f291f Mon Sep 17 00:00:00 2001 From: brentru Date: Fri, 18 Jan 2019 12:30:17 -0500 Subject: [PATCH 026/116] add binder launch badge to readme --- README.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.rst b/README.rst index 6d6e2ae..3aa5731 100644 --- a/README.rst +++ b/README.rst @@ -13,6 +13,9 @@ Adafruit IO Python :target: https://travis-ci.org/adafruit/io-client-python :alt: Build Status +.. image:: https://img.shields.io/badge/Try%20out-Adafruit%20IO%20Python-579ACA.svg?logo=data:image/png;base64,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 + :target: https://mybinder.org/v2/gh/brentru/adafruit_io_python_jupyter/master?filepath=adafruit-io-python-tutorial.ipynb + .. image:: https://cdn-learn.adafruit.com/assets/assets/000/057/153/original/adafruit_io_iopython.png?1530802073 A Python client and examples for use with `io.adafruit.com `_. From e1d7e4f2c1b87644d58ae5a9769318d1ad25622c Mon Sep 17 00:00:00 2001 From: brentru Date: Fri, 25 Jan 2019 17:18:07 -0500 Subject: [PATCH 027/116] adafruit-io-client -> adafruit-io-python --- README.rst | 2 +- docs/conf.py | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/README.rst b/README.rst index 3aa5731..0ef9dea 100644 --- a/README.rst +++ b/README.rst @@ -18,7 +18,7 @@ Adafruit IO Python .. image:: https://cdn-learn.adafruit.com/assets/assets/000/057/153/original/adafruit_io_iopython.png?1530802073 -A Python client and examples for use with `io.adafruit.com `_. +A Python library and examples for use with `io.adafruit.com `_. Compatible with Python Versions 3.4+ diff --git a/docs/conf.py b/docs/conf.py index 0c56a44..848ccc3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -28,8 +28,8 @@ master_doc = 'index' # General information about the project. -project = u'io-client-python' -copyright = u'2018 Adafruit Industries' +project = u'adafruit-io-python' +copyright = u'2019 Adafruit Industries' author = u'Adafruit Industries' # The version info for the project you're documenting, acts as replacement for @@ -37,9 +37,9 @@ # built documents. # # The short X.Y version. -version = u'2.0.1' +version = u'2.1.0' # The full version, including alpha/beta/rc tags. -release = u'2.0.1' +release = u'2.1.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -129,7 +129,7 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'io-client-pythonLibrary.tex', u'io-client-python Library Documentation', + (master_doc, 'adafruit-io-pythonLibrary.tex', u'adafruit-io-python Library Documentation', author, 'manual'), ] @@ -138,7 +138,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - (master_doc, 'io-client-pythonlibrary', u'io-client-python Library Documentation', + (master_doc, 'adafruit-io-pythonlibrary', u'adafruit-io-python Library Documentation', [author], 1) ] @@ -148,7 +148,7 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'io-client-pythonLibrary', u' io-client-python Library Documentation', - author, 'io-client-pythonLibrary', 'One line description of project.', + (master_doc, 'adafruit-io-pythonLibrary', u' adafruit-io-python Library Documentation', + author, 'adafruit-io-pythonLibrary', 'One line description of project.', 'Miscellaneous'), ] From 649f8da20492bb3f85bc87e6e62dcdc6fa52e6d7 Mon Sep 17 00:00:00 2001 From: brentru Date: Fri, 25 Jan 2019 17:53:32 -0500 Subject: [PATCH 028/116] remove references to io client --- LICENSE.md | 2 +- setup.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index c841ffc..5ccd0cd 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,4 +1,4 @@ -Copyright (c) 2014-2018 Adafruit +Copyright (c) 2014-2019 Adafruit Author: Justin Cooper and Tony DiCola MIT License diff --git a/setup.py b/setup.py index f848d1e..09087f3 100644 --- a/setup.py +++ b/setup.py @@ -50,7 +50,7 @@ long_description = open('README.rst').read(), long_description_content_type='text/x-rst', - url = 'https://github.com/adafruit/io-client-python', + url = 'https://github.com/adafruit/Adafruit_IO_Python', author = 'Adafruit Industries', author_email = 'adafruitio@adafruit.com', @@ -64,6 +64,6 @@ packages = ['Adafruit_IO'], py_modules = ['ez_setup'], - keywords = 'adafruitio io python circuitpython raspberrypi hardware MQTT', + keywords = 'adafruitio io python circuitpython raspberrypi hardware MQTT REST', classifiers = classifiers ) From c3cdd7b349b154f7cfaf31fe20153d70b8374b35 Mon Sep 17 00:00:00 2001 From: brentru Date: Tue, 12 Feb 2019 17:45:11 -0500 Subject: [PATCH 029/116] add mqtt get to client, bump version --- Adafruit_IO/_version.py | 2 +- Adafruit_IO/mqtt_client.py | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Adafruit_IO/_version.py b/Adafruit_IO/_version.py index 259fe5f..b842790 100644 --- a/Adafruit_IO/_version.py +++ b/Adafruit_IO/_version.py @@ -1 +1 @@ -__version__ = "2.1" \ No newline at end of file +__version__ = "2.1.1" \ No newline at end of file diff --git a/Adafruit_IO/mqtt_client.py b/Adafruit_IO/mqtt_client.py index 22cda92..8272ff1 100644 --- a/Adafruit_IO/mqtt_client.py +++ b/Adafruit_IO/mqtt_client.py @@ -256,6 +256,15 @@ def unsubscribe(self, feed_id=None, group_id=None): raise TypeError('Invalid topic type specified.') return + def receive(self, feed_id): + """Receive the last published value from a specified feed. + + :param string feed_id: The ID of the feed to update. + :parm string value: The new value to publish to the feed + """ + (res, self._pub_mid) = self._client.publish('{0}/feeds/{1}/get'.format(self._username, feed_id), + payload='') + def publish(self, feed_id, value=None, group_id=None, feed_user=None): """Publish a value to a specified feed. From b9d2f62bc09144d6f2759bf16e46830de3e7815a Mon Sep 17 00:00:00 2001 From: brentru Date: Thu, 21 Feb 2019 10:25:07 -0500 Subject: [PATCH 030/116] update environmental_monitor.py sketch to reflect Adafruit_CircuitPython_SGP30 lib --- examples/basics/environmental_monitor.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/examples/basics/environmental_monitor.py b/examples/basics/environmental_monitor.py index 0296ac8..144010d 100644 --- a/examples/basics/environmental_monitor.py +++ b/examples/basics/environmental_monitor.py @@ -33,7 +33,7 @@ import board import busio -# import sensor libraries +# import CircuitPython sensor libraries import adafruit_sgp30 import adafruit_veml6070 import adafruit_bme280 @@ -91,12 +91,11 @@ def sample_VEML(): uv_raw = uv.read return uv_raw - while True: print('Reading sensors...') # Read SGP30. - eCO2_data = sgp30.co2eq - tvoc_data = sgp30.tvoc + eCO2_data = sgp30.eCO2 + tvoc_data = sgp30.TVOC # Read VEML6070. uv_data = sample_VEML() From 70e010c16a21b595a2d0d3badc79f4e6d8d499f0 Mon Sep 17 00:00:00 2001 From: brentru Date: Thu, 21 Feb 2019 10:31:35 -0500 Subject: [PATCH 031/116] bump version for release --- Adafruit_IO/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Adafruit_IO/_version.py b/Adafruit_IO/_version.py index b842790..b62a3e5 100644 --- a/Adafruit_IO/_version.py +++ b/Adafruit_IO/_version.py @@ -1 +1 @@ -__version__ = "2.1.1" \ No newline at end of file +__version__ = "2.1.2" \ No newline at end of file From 0c7845a2d4b1a049ac3125900cf7f72cbd152cc2 Mon Sep 17 00:00:00 2001 From: brentru Date: Fri, 22 Feb 2019 10:28:44 -0500 Subject: [PATCH 032/116] remove api version kwarg --- Adafruit_IO/_version.py | 2 +- Adafruit_IO/client.py | 15 +++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/Adafruit_IO/_version.py b/Adafruit_IO/_version.py index b62a3e5..bc674be 100644 --- a/Adafruit_IO/_version.py +++ b/Adafruit_IO/_version.py @@ -1 +1 @@ -__version__ = "2.1.2" \ No newline at end of file +__version__ = "2.1.3" \ No newline at end of file diff --git a/Adafruit_IO/client.py b/Adafruit_IO/client.py index c4d5fb2..9e5604e 100644 --- a/Adafruit_IO/client.py +++ b/Adafruit_IO/client.py @@ -42,29 +42,28 @@ class Client(object): REST API. Use this client class to send, receive, and enumerate feed data. """ - def __init__(self, username, key, proxies=None, base_url='https://io.adafruit.com', api_version = 'v2'): + def __init__(self, username, key, proxies=None, base_url='https://io.adafruit.com'): """Create an instance of the Adafruit IO REST API client. Key must be provided and set to your Adafruit IO access key value. Optionaly - provide a proxies dict in the format used by the requests library, a - base_url to point at a different Adafruit IO service (the default is - the production Adafruit IO service over SSL), and a api_version to - add support for future API versions. + provide a proxies dict in the format used by the requests library, + and a base_url to point at a different Adafruit IO service + (the default is the production Adafruit IO service over SSL). """ self.username = username self.key = key self.proxies = proxies - self.api_version = api_version # self.logger = logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s') # Save URL without trailing slash as it will be added later when # constructing the path. self.base_url = base_url.rstrip('/') + def _compose_url(self, path, is_time=None): if is_time: # return a call to https://io.adafruit.com/api/v2/time/{unit} - return '{0}/api/{1}/{2}'.format(self.base_url, self.api_version, path) + return '{0}/api/{1}/{2}'.format(self.base_url, 'v2', path) else: - return '{0}/api/{1}/{2}/{3}'.format(self.base_url, self.api_version, self.username, path) + return '{0}/api/{1}/{2}/{3}'.format(self.base_url, 'v2', self.username, path) def _handle_error(self, response): From afa3de3cffab4492e8ccd9668c13720d90fb9bf6 Mon Sep 17 00:00:00 2001 From: brentru Date: Mon, 25 Feb 2019 10:32:50 -0500 Subject: [PATCH 033/116] remove `api_version` kwarg from get_client in tests --- tests/test_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_client.py b/tests/test_client.py index 29e35cf..6a7c60d 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -28,7 +28,7 @@ class TestClient(base.IOTestCase): # Helper Methods def get_client(self): # Construct an Adafruit IO REST client and return it. - return Client(self.get_test_username(), self.get_test_key(), proxies=PROXIES, base_url=BASE_URL, api_version = "v2") + return Client(self.get_test_username(), self.get_test_key(), proxies=PROXIES, base_url=BASE_URL) def ensure_feed_deleted(self, client, feed): # Delete the specified feed if it exists. From e8f22ad13b1420ab3804d08d6a7211e614402ce5 Mon Sep 17 00:00:00 2001 From: brentru Date: Mon, 25 Feb 2019 12:43:03 -0500 Subject: [PATCH 034/116] remove python client ref from readme --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 0ef9dea..4ce0470 100644 --- a/README.rst +++ b/README.rst @@ -9,8 +9,8 @@ Adafruit IO Python :target: https://discord.gg/nBQh6qu :alt: Chat -.. image:: https://travis-ci.org/adafruit/io-client-python.svg?branch=master - :target: https://travis-ci.org/adafruit/io-client-python +.. image:: https://travis-ci.com/adafruit/Adafruit_IO_Python.svg?branch=master + :target: https://travis-ci.com/adafruit/Adafruit_IO_Python :alt: Build Status .. image:: https://img.shields.io/badge/Try%20out-Adafruit%20IO%20Python-579ACA.svg?logo=data:image/png;base64,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 From ab83808026bdb81896e05c8c4e34494081db05a5 Mon Sep 17 00:00:00 2001 From: brentrubell Date: Mon, 25 Feb 2019 12:48:09 -0500 Subject: [PATCH 035/116] Update _version.py removal kwarg may break some clients, major version for breaking changes. --- Adafruit_IO/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Adafruit_IO/_version.py b/Adafruit_IO/_version.py index bc674be..d3c8880 100644 --- a/Adafruit_IO/_version.py +++ b/Adafruit_IO/_version.py @@ -1 +1 @@ -__version__ = "2.1.3" \ No newline at end of file +__version__ = "2.2" From 770bb968f87358505a77a3b9f227a81cfd073703 Mon Sep 17 00:00:00 2001 From: brentru Date: Tue, 5 Mar 2019 11:29:51 -0500 Subject: [PATCH 036/116] add metadata, precision kwargs to send_data, add create_payload staticmethod --- Adafruit_IO/client.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/Adafruit_IO/client.py b/Adafruit_IO/client.py index 9e5604e..acaac52 100644 --- a/Adafruit_IO/client.py +++ b/Adafruit_IO/client.py @@ -109,16 +109,34 @@ def _delete(self, path): proxies=self.proxies) self._handle_error(response) + @staticmethod + def _create_payload(value, metadata): + """ + """ + if metadata is not None: + payload = Data(value=value,lat=metadata['lat'], lon=metadata['lon'], + ele=metadata['ele'], created_at=metadata['created_at']) + return payload + return Data(value=value) + # Data functionality. - def send_data(self, feed, value): + def send_data(self, feed, value, metadata=None, precision=None): """Helper function to simplify adding a value to a feed. Will append the specified value to the feed identified by either name, key, or ID. Returns a Data instance with details about the newly appended row of data. Note that send_data now operates the same as append. :param string feed: Name/Key/ID of Adafruit IO feed. :param string value: Value to send. + :param dict metadata: Optional metadata associated with the value. + :param int precision: Optional amount of precision points to send. """ - return self.create_data(feed, Data(value=value)) + if precision: + try: + value = round(value, precision) + except NotImplementedError: + raise NotImplementedError("Using the precision kwarg requires a floating point value") + payload = self._create_payload(value, metadata) + return self.create_data(feed, payload) send = send_data From d2bb729ddbe57f5d427bf5dcfe535d9c5382d4b1 Mon Sep 17 00:00:00 2001 From: brentru Date: Tue, 5 Mar 2019 11:35:54 -0500 Subject: [PATCH 037/116] update example for new send_data --- Adafruit_IO/client.py | 28 +++++++--------------------- examples/api/location.py | 26 +++++++++++++++----------- 2 files changed, 22 insertions(+), 32 deletions(-) diff --git a/Adafruit_IO/client.py b/Adafruit_IO/client.py index acaac52..4817d04 100644 --- a/Adafruit_IO/client.py +++ b/Adafruit_IO/client.py @@ -58,6 +58,13 @@ def __init__(self, username, key, proxies=None, base_url='https://io.adafruit.co # constructing the path. self.base_url = base_url.rstrip('/') + @staticmethod + def _create_payload(value, metadata): + if metadata is not None: + payload = Data(value=value,lat=metadata['lat'], lon=metadata['lon'], + ele=metadata['ele'], created_at=metadata['created_at']) + return payload + return Data(value=value) def _compose_url(self, path, is_time=None): if is_time: # return a call to https://io.adafruit.com/api/v2/time/{unit} @@ -65,7 +72,6 @@ def _compose_url(self, path, is_time=None): else: return '{0}/api/{1}/{2}/{3}'.format(self.base_url, 'v2', self.username, path) - def _handle_error(self, response): # Throttling Error if response.status_code == 429: @@ -109,16 +115,6 @@ def _delete(self, path): proxies=self.proxies) self._handle_error(response) - @staticmethod - def _create_payload(value, metadata): - """ - """ - if metadata is not None: - payload = Data(value=value,lat=metadata['lat'], lon=metadata['lon'], - ele=metadata['ele'], created_at=metadata['created_at']) - return payload - return Data(value=value) - # Data functionality. def send_data(self, feed, value, metadata=None, precision=None): """Helper function to simplify adding a value to a feed. Will append the @@ -162,16 +158,6 @@ def append(self, feed, value): """ return self.create_data(feed, Data(value=value)) - def send_location_data(self, feed, lat, lon, ele, value=None): - """Sends locational data to a feed. - :param string feed: Name/Key/ID of Adafruit IO feed. - :param int lat: Latitude. - :param int lon: Longitude. - :param int ele: Elevation. - :param int value: Optional value to send, defaults to None. - """ - return self.create_data(feed, Data(value=value,lat=lat, lon=lon, ele=ele)) - def receive_time(self, time): """Returns the time from the Adafruit IO server. :param string time: Time to be returned: `millis`, `seconds`, `ISO-8601`. diff --git a/examples/api/location.py b/examples/api/location.py index 08f5fdb..3e5846a 100644 --- a/examples/api/location.py +++ b/examples/api/location.py @@ -1,9 +1,8 @@ """ 'location.py' ================================== -Example of sending location over an -Adafruit IO feed to a Map Dashboard -block +Example of sending metadata +associated with a data point. Author(s): Brent Rubell """ @@ -12,9 +11,14 @@ from Adafruit_IO import Client, Feed, RequestError # Set to your Adafruit IO key. -ADAFRUIT_IO_USERNAME = 'YOUR_AIO_USERNAME' +# Remember, your key is a secret, +# so make sure not to publish it when you publish this code! ADAFRUIT_IO_KEY = 'YOUR_AIO_KEY' +# Set to your Adafruit IO username. +# (go to https://accounts.adafruit.com to find your username) +ADAFRUIT_IO_USERNAME = 'YOUR_AIO_USERNAME' + # Create an instance of the REST client. aio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY) @@ -25,12 +29,12 @@ feed = Feed(name="location") location = aio.create_feed(feed) - -# Top Secret Adafruit HQ Location -value = 1 -lat = 40.726190 -lon = -74.005334 -ele = 6 # elevation above sea level (meters) +value = 42 +# Set metadata associated with value +metadata = {'lat': 40.726190, + 'lon': -74.005334, + 'ele': -6, + 'created_at': None} # Send location data to Adafruit IO -aio.send_location_data(location.key, lat, lon, ele, value) +aio.send_data(location.key, value, metadata) From 528b2a0edd7393162c52ee298eb96045086aa740 Mon Sep 17 00:00:00 2001 From: brentru Date: Tue, 5 Mar 2019 12:02:46 -0500 Subject: [PATCH 038/116] major pylinting --- Adafruit_IO/client.py | 44 +++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/Adafruit_IO/client.py b/Adafruit_IO/client.py index 4817d04..f2039e8 100644 --- a/Adafruit_IO/client.py +++ b/Adafruit_IO/client.py @@ -61,18 +61,13 @@ def __init__(self, username, key, proxies=None, base_url='https://io.adafruit.co @staticmethod def _create_payload(value, metadata): if metadata is not None: - payload = Data(value=value,lat=metadata['lat'], lon=metadata['lon'], - ele=metadata['ele'], created_at=metadata['created_at']) + payload = Data(value=value, lat=metadata['lat'], lon=metadata['lon'], + ele=metadata['ele'], created_at=metadata['created_at']) return payload return Data(value=value) - def _compose_url(self, path, is_time=None): - if is_time: # return a call to https://io.adafruit.com/api/v2/time/{unit} - return '{0}/api/{1}/{2}'.format(self.base_url, 'v2', path) - else: - return '{0}/api/{1}/{2}/{3}'.format(self.base_url, 'v2', self.username, path) - - def _handle_error(self, response): + @staticmethod + def _handle_error(response): # Throttling Error if response.status_code == 429: raise ThrottlingError() @@ -84,6 +79,11 @@ def _handle_error(self, response): raise RequestError(response) # Else do nothing if there was no error. + def _compose_url(self, path, is_time=None): + if is_time: # return a call to https://io.adafruit.com/api/v2/time/{unit} + return '{0}/api/{1}/{2}'.format(self.base_url, 'v2', path) + return '{0}/api/{1}/{2}/{3}'.format(self.base_url, 'v2', self.username, path) + def _headers(self, given): headers = default_headers.copy() headers.update(given) @@ -111,7 +111,7 @@ def _post(self, path, data): def _delete(self, path): response = requests.delete(self._compose_url(path), headers=self._headers({'X-AIO-Key': self.key, - 'Content-Type': 'application/json'}), + 'Content-Type': 'application/json'}), proxies=self.proxies) self._handle_error(response) @@ -130,7 +130,7 @@ def send_data(self, feed, value, metadata=None, precision=None): try: value = round(value, precision) except NotImplementedError: - raise NotImplementedError("Using the precision kwarg requires a floating point value") + raise NotImplementedError("Using the precision kwarg requires a float value") payload = self._create_payload(value, metadata) return self.create_data(feed, payload) @@ -164,26 +164,26 @@ def receive_time(self, time): """ timepath = "time/{0}".format(time) return self._get(timepath, is_time=True) - + def receive_weather(self, weather_id=None): """Adafruit IO Weather Service, Powered by Dark Sky :param int id: optional ID for retrieving a specified weather record. """ if weather_id: - weather_path = "integrations/weather/{0}".format(weather_id) + weather_path = "integrations/weather/{0}".format(weather_id) else: - weather_path = "integrations/weather" + weather_path = "integrations/weather" return self._get(weather_path) - + def receive_random(self, id=None): """Access to Adafruit IO's Random Data service. :param int id: optional ID for retrieving a specified randomizer. """ if id: - random_path = "integrations/words/{0}".format(id) + random_path = "integrations/words/{0}".format(id) else: - random_path = "integrations/words" + random_path = "integrations/words" return self._get(random_path) def receive(self, feed): @@ -195,7 +195,7 @@ def receive(self, feed): return Data.from_dict(self._get(path)) def receive_next(self, feed): - """Retrieve the next unread value from the specified feed. Returns a Data + """Retrieve the next unread value from the specified feed. Returns a Data instance whose value property holds the retrieved value. :param string feed: Name/Key/ID of Adafruit IO feed. """ @@ -225,10 +225,10 @@ def data(self, feed, data_id=None): def create_data(self, feed, data): """Create a new row of data in the specified feed. - Returns a Data instance with details about the newly + Returns a Data instance with details about the newly appended row of data. :param string feed: Name/Key/ID of Adafruit IO feed. - :param Data data: Instance of the Data class. Must have a value property set. + :param Data data: Instance of the Data class. Must have a value property set. """ path = "feeds/{0}/data".format(feed) return Data.from_dict(self._post(path, data._asdict())) @@ -248,7 +248,7 @@ def toRed(self, data): return ((int(data[1], 16))*16) + int(data[2], 16) def toGreen(self, data): - """Hex color feed to green channel. + """Hex color feed to green channel. :param int data: Color value, in hexadecimal. """ return (int(data[3], 16) * 16) + int(data[4], 16) @@ -310,4 +310,4 @@ def delete_group(self, group): :param string group: Name/Key/ID of Adafruit IO Group. """ path = "groups/{0}".format(group) - self._delete(path) \ No newline at end of file + self._delete(path) From 3fd82542b342aadd9510c4cd34e5cd83979105b6 Mon Sep 17 00:00:00 2001 From: brentru Date: Tue, 5 Mar 2019 12:07:08 -0500 Subject: [PATCH 039/116] continue linting and optimizing the lib! --- Adafruit_IO/client.py | 59 +++++++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/Adafruit_IO/client.py b/Adafruit_IO/client.py index f2039e8..be570ba 100644 --- a/Adafruit_IO/client.py +++ b/Adafruit_IO/client.py @@ -19,8 +19,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import json -import pkg_resources import platform +import pkg_resources # import logging import requests @@ -58,6 +58,33 @@ def __init__(self, username, key, proxies=None, base_url='https://io.adafruit.co # constructing the path. self.base_url = base_url.rstrip('/') + @staticmethod + def to_red(data): + """Hex color feed to red channel. + :param int data: Color value, in hexadecimal. + """ + return ((int(data[1], 16))*16) + int(data[2], 16) + + @staticmethod + def to_green(data): + """Hex color feed to green channel. + :param int data: Color value, in hexadecimal. + """ + return (int(data[3], 16) * 16) + int(data[4], 16) + + @staticmethod + def to_blue(data): + """Hex color feed to blue channel. + :param int data: Color value, in hexadecimal. + """ + return (int(data[5], 16) * 16) + int(data[6], 16) + + @staticmethod + def _headers(given): + headers = default_headers.copy() + headers.update(given) + return headers + @staticmethod def _create_payload(value, metadata): if metadata is not None: @@ -84,11 +111,6 @@ def _compose_url(self, path, is_time=None): return '{0}/api/{1}/{2}'.format(self.base_url, 'v2', path) return '{0}/api/{1}/{2}/{3}'.format(self.base_url, 'v2', self.username, path) - def _headers(self, given): - headers = default_headers.copy() - headers.update(given) - return headers - def _get(self, path, is_time=None): response = requests.get(self._compose_url(path, is_time), headers=self._headers({'X-AIO-Key': self.key}), @@ -96,8 +118,7 @@ def _get(self, path, is_time=None): self._handle_error(response) if not is_time: return response.json() - else: # time doesn't need to serialize into json, just return text - return response.text + return response.text def _post(self, path, data): response = requests.post(self._compose_url(path), @@ -111,8 +132,8 @@ def _post(self, path, data): def _delete(self, path): response = requests.delete(self._compose_url(path), headers=self._headers({'X-AIO-Key': self.key, - 'Content-Type': 'application/json'}), - proxies=self.proxies) + 'Content-Type': 'application/json'}), + proxies=self.proxies) self._handle_error(response) # Data functionality. @@ -241,24 +262,6 @@ def delete(self, feed, data_id): path = "feeds/{0}/data/{1}".format(feed, data_id) self._delete(path) - def toRed(self, data): - """Hex color feed to red channel. - :param int data: Color value, in hexadecimal. - """ - return ((int(data[1], 16))*16) + int(data[2], 16) - - def toGreen(self, data): - """Hex color feed to green channel. - :param int data: Color value, in hexadecimal. - """ - return (int(data[3], 16) * 16) + int(data[4], 16) - - def toBlue(self, data): - """Hex color feed to blue channel. - :param int data: Color value, in hexadecimal. - """ - return (int(data[5], 16) * 16) + int(data[6], 16) - # feed functionality. def feeds(self, feed=None): """Retrieve a list of all feeds, or the specified feed. If feed is not From ad9ac1d2e091689f92cf22868a8f0d8beb4b219f Mon Sep 17 00:00:00 2001 From: brentru Date: Tue, 5 Mar 2019 12:12:10 -0500 Subject: [PATCH 040/116] lint lint lint --- Adafruit_IO/client.py | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/Adafruit_IO/client.py b/Adafruit_IO/client.py index be570ba..17fdc93 100644 --- a/Adafruit_IO/client.py +++ b/Adafruit_IO/client.py @@ -133,7 +133,7 @@ def _delete(self, path): response = requests.delete(self._compose_url(path), headers=self._headers({'X-AIO-Key': self.key, 'Content-Type': 'application/json'}), - proxies=self.proxies) + proxies=self.proxies) self._handle_error(response) # Data functionality. @@ -196,13 +196,13 @@ def receive_weather(self, weather_id=None): weather_path = "integrations/weather" return self._get(weather_path) - def receive_random(self, id=None): + def receive_random(self, randomizer_id=None): """Access to Adafruit IO's Random Data service. - :param int id: optional ID for retrieving a specified randomizer. + :param int randomizer_id: optional ID for retrieving a specified randomizer. """ - if id: - random_path = "integrations/words/{0}".format(id) + if randomizer_id: + random_path = "integrations/words/{0}".format(randomizer_id) else: random_path = "integrations/words" return self._get(random_path) @@ -240,9 +240,8 @@ def data(self, feed, data_id=None): if data_id is None: path = "feeds/{0}/data".format(feed) return list(map(Data.from_dict, self._get(path))) - else: - path = "feeds/{0}/data/{1}".format(feed, data_id) - return Data.from_dict(self._get(path)) + path = "feeds/{0}/data/{1}".format(feed, data_id) + return Data.from_dict(self._get(path)) def create_data(self, feed, data): """Create a new row of data in the specified feed. @@ -271,9 +270,8 @@ def feeds(self, feed=None): if feed is None: path = "feeds" return list(map(Feed.from_dict, self._get(path))) - else: - path = "feeds/{0}".format(feed) - return Feed.from_dict(self._get(path)) + path = "feeds/{0}".format(feed) + return Feed.from_dict(self._get(path)) def create_feed(self, feed): """Create the specified feed. @@ -297,9 +295,8 @@ def groups(self, group=None): if group is None: path = "groups/" return list(map(Group.from_dict, self._get(path))) - else: - path = "groups/{0}".format(group) - return Group.from_dict(self._get(path)) + path = "groups/{0}".format(group) + return Group.from_dict(self._get(path)) def create_group(self, group): """Create the specified group. From 54ff53067a0d4be4a8cf060960a37e9ec648ba54 Mon Sep 17 00:00:00 2001 From: brentru Date: Tue, 5 Mar 2019 12:13:50 -0500 Subject: [PATCH 041/116] more --- Adafruit_IO/client.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Adafruit_IO/client.py b/Adafruit_IO/client.py index 17fdc93..73543fb 100644 --- a/Adafruit_IO/client.py +++ b/Adafruit_IO/client.py @@ -52,7 +52,8 @@ def __init__(self, username, key, proxies=None, base_url='https://io.adafruit.co self.username = username self.key = key self.proxies = proxies - # self.logger = logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s') + # self.logger = logging.basicConfig(level=logging.DEBUG, + # format='%(asctime)s - %(levelname)s - %(message)s') # Save URL without trailing slash as it will be added later when # constructing the path. From f5d3ff6331f407b470642d373ce6dd5ff79a9c71 Mon Sep 17 00:00:00 2001 From: brentru Date: Tue, 5 Mar 2019 12:24:37 -0500 Subject: [PATCH 042/116] update location unittest, bump version major --- Adafruit_IO/_version.py | 2 +- tests/test_client.py | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Adafruit_IO/_version.py b/Adafruit_IO/_version.py index d3c8880..202075f 100644 --- a/Adafruit_IO/_version.py +++ b/Adafruit_IO/_version.py @@ -1 +1 @@ -__version__ = "2.2" +__version__ = "2.3" diff --git a/tests/test_client.py b/tests/test_client.py index 6a7c60d..a83b581 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -133,7 +133,11 @@ def test_location_data(self): aio = self.get_client() self.ensure_feed_deleted(aio, 'testlocfeed') test_feed = aio.create_feed(Feed(name='testlocfeed')) - aio.send_location_data(test_feed.key, 40, -74, 6, 0) + metadata = {'lat': 40.726190, + 'lon': -74.005334, + 'ele': -6, + 'created_at': None} + aio.send_data(test_feed.key, 40, metadata) data = aio.receive(test_feed.key) self.assertEqual(int(data.value), 0.0) self.assertEqual(float(data.lat), 40.0) From bbb00f8416723f508fdf2ae7b098f3b6e8f082b3 Mon Sep 17 00:00:00 2001 From: brentru Date: Tue, 5 Mar 2019 13:01:39 -0500 Subject: [PATCH 043/116] fixup test --- tests/test_client.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_client.py b/tests/test_client.py index a83b581..a26677a 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -139,10 +139,10 @@ def test_location_data(self): 'created_at': None} aio.send_data(test_feed.key, 40, metadata) data = aio.receive(test_feed.key) - self.assertEqual(int(data.value), 0.0) - self.assertEqual(float(data.lat), 40.0) - self.assertEqual(float(data.lon), -74.0) - self.assertEqual(float(data.ele), 6.0) + self.assertEqual(int(data.value), 40) + self.assertEqual(float(data.lat), 40.726190) + self.assertEqual(float(data.lon), -74.005334) + self.assertEqual(float(data.ele), -6.0) # Test Feed Functionality def test_append_by_feed_name(self): From 623ea17b4f5e4d319e482f15ad1a0f3b744e371d Mon Sep 17 00:00:00 2001 From: brentru Date: Fri, 8 Mar 2019 11:13:14 -0500 Subject: [PATCH 044/116] fix on_message topic parsing, time example, bump minor version --- Adafruit_IO/_version.py | 2 +- Adafruit_IO/mqtt_client.py | 29 ++++++++++++++++------------- examples/mqtt/mqtt_time.py | 23 ++++++++++------------- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/Adafruit_IO/_version.py b/Adafruit_IO/_version.py index 202075f..3a5935a 100644 --- a/Adafruit_IO/_version.py +++ b/Adafruit_IO/_version.py @@ -1 +1 @@ -__version__ = "2.3" +__version__ = "2.3.1" diff --git a/Adafruit_IO/mqtt_client.py b/Adafruit_IO/mqtt_client.py index 8272ff1..d70118f 100644 --- a/Adafruit_IO/mqtt_client.py +++ b/Adafruit_IO/mqtt_client.py @@ -103,23 +103,26 @@ def _mqtt_disconnect(self, client, userdata, rc): self.on_disconnect(self) def _mqtt_message(self, client, userdata, msg): - logger.debug('Client on_message called.') """Parse out the topic and call on_message callback assume topic looks like `username/topic/id` """ + logger.debug('Client on_message called.') parsed_topic = msg.topic.split('/') - if self.on_message is not None and parsed_topic[2] == 'weather': - topic = parsed_topic[4] # parse out the forecast type - payload = '' if msg.payload is None else msg.payload.decode('utf-8') - elif self.on_message is not None and parsed_topic[0] == 'time': - topic = parsed_topic[0] - payload = msg.payload.decode('utf-8') - elif self.on_message is not None and parsed_topic[1] == 'groups': - topic = parsed_topic[3] - payload = msg.payload.decode('utf-8') - else: # default topic - topic = parsed_topic[2] - payload = '' if msg.payload is None else msg.payload.decode('utf-8') + if self.on_message is not None: + if parsed_topic[0] == 'time': + topic = parsed_topic[0] + payload = msg.payload.decode('utf-8') + elif parsed_topic[1] == 'groups': + topic = parsed_topic[3] + payload = msg.payload.decode('utf-8') + elif parsed_topic[2] == 'weather': + topic = parsed_topic[4] + payload = '' if msg.payload is None else msg.payload.decode('utf-8') + else: + topic = parsed_topic[2] + payload = '' if msg.payload is None else msg.payload.decode('utf-8') + else: + raise ValueError('on_message not defined') self.on_message(self, topic, payload) def _mqtt_subscribe(client, userdata, mid, granted_qos): diff --git a/examples/mqtt/mqtt_time.py b/examples/mqtt/mqtt_time.py index e91f820..19875d3 100644 --- a/examples/mqtt/mqtt_time.py +++ b/examples/mqtt/mqtt_time.py @@ -48,17 +48,14 @@ def message(client, feed_id, payload): # Connect to the Adafruit IO server. client.connect() -# time per loop -loop_time = 2 +# Subscribe to the time feeds +print('* Subscribing to time/seconds') +client.subscribe_time('seconds') -client.loop_background() -while True: - print('* Subscribing to /time/seconds') - client.subscribe_time('seconds') - time.sleep(loop_time) - print('* Subscribing to /time/millis') - client.subscribe_time('millis') - time.sleep(loop_time) - print('* Subscribing to iso-8601') - client.subscribe_time('iso') - time.sleep(loop_time) +print('* Subscribing to time/millis') +client.subscribe_time('millis') + +print('* Subscribing to time/ISO-8601') +client.subscribe_time('iso') + +client.loop_blocking() From 37663ab3ed5219027274527de2c998ca517ec7e5 Mon Sep 17 00:00:00 2001 From: brentru Date: Tue, 12 Mar 2019 12:56:02 -0400 Subject: [PATCH 045/116] add new time endpoint, return time as a struct_time, remove plaintext http get responses, update example to remove topics --- Adafruit_IO/client.py | 25 ++++++++++----------- examples/basics/{time-topics.py => time.py} | 25 ++++++++------------- 2 files changed, 21 insertions(+), 29 deletions(-) rename examples/basics/{time-topics.py => time.py} (55%) diff --git a/Adafruit_IO/client.py b/Adafruit_IO/client.py index 73543fb..515789a 100644 --- a/Adafruit_IO/client.py +++ b/Adafruit_IO/client.py @@ -18,6 +18,7 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +from time import struct_time import json import platform import pkg_resources @@ -107,19 +108,15 @@ def _handle_error(response): raise RequestError(response) # Else do nothing if there was no error. - def _compose_url(self, path, is_time=None): - if is_time: # return a call to https://io.adafruit.com/api/v2/time/{unit} - return '{0}/api/{1}/{2}'.format(self.base_url, 'v2', path) + def _compose_url(self, path): return '{0}/api/{1}/{2}/{3}'.format(self.base_url, 'v2', self.username, path) - def _get(self, path, is_time=None): - response = requests.get(self._compose_url(path, is_time), + def _get(self, path): + response = requests.get(self._compose_url(path), headers=self._headers({'X-AIO-Key': self.key}), proxies=self.proxies) self._handle_error(response) - if not is_time: - return response.json() - return response.text + return response.json() def _post(self, path, data): response = requests.post(self._compose_url(path), @@ -180,12 +177,14 @@ def append(self, feed, value): """ return self.create_data(feed, Data(value=value)) - def receive_time(self, time): - """Returns the time from the Adafruit IO server. - :param string time: Time to be returned: `millis`, `seconds`, `ISO-8601`. + def receive_time(self): + """Returns a struct_time from the Adafruit IO Server based on the device's IP address. + https://docs.python.org/3.7/library/time.html#time.struct_time """ - timepath = "time/{0}".format(time) - return self._get(timepath, is_time=True) + path = 'integrations/time/struct.json' + time = self._get(path) + return struct_time((time['year'], time['mon'], time['mday'], time['hour'], + time['min'], time['sec'], time['wday'], time['yday'], time['isdst'])) def receive_weather(self, weather_id=None): """Adafruit IO Weather Service, Powered by Dark Sky diff --git a/examples/basics/time-topics.py b/examples/basics/time.py similarity index 55% rename from examples/basics/time-topics.py rename to examples/basics/time.py index 3fd487a..a6204c8 100644 --- a/examples/basics/time-topics.py +++ b/examples/basics/time.py @@ -1,10 +1,11 @@ """ -`time-topics.py` -==================================== +`time.py` +========================================== Don't have a RTC handy and need accurate time measurements? -Let Adafruit IO serve real-time values! +Let Adafruit IO serve up real-time values +based off your device's IP-address! Author: Brent Rubell """ @@ -23,16 +24,8 @@ # Create an instance of the REST client. aio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY) -print('---Adafruit IO REST API Time Helpers---') - -print('Seconds: aio.receive_time(seconds)') -secs_val = aio.receive_time('seconds') -print('\t' + secs_val) - -print('Milliseconds: aio.receive_time(millis)') -ms_val = aio.receive_time('millis') -print('\t' + ms_val) - -print('ISO-8601: aio.receive_time(ISO-8601)') -iso_val = aio.receive_time('ISO-8601') -print('\t' + iso_val) \ No newline at end of file +# Get the time from Adafruit IO +time = aio.receive_time() +# Time is returned as a `struct_time` +# https://docs.python.org/3.7/library/time.html#time.struct_time +print(time) \ No newline at end of file From d47c911c2d5d24e36e8479251f599dcc869a688b Mon Sep 17 00:00:00 2001 From: brentru Date: Tue, 12 Mar 2019 13:03:10 -0400 Subject: [PATCH 046/116] bump version --- Adafruit_IO/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Adafruit_IO/_version.py b/Adafruit_IO/_version.py index 3a5935a..ef6497d 100644 --- a/Adafruit_IO/_version.py +++ b/Adafruit_IO/_version.py @@ -1 +1 @@ -__version__ = "2.3.1" +__version__ = "2.3.2" From 3c7a0f1d97ba7305c6e08fe1de6d425e9c62e8a5 Mon Sep 17 00:00:00 2001 From: brentru Date: Tue, 12 Mar 2019 13:30:28 -0400 Subject: [PATCH 047/116] adding unittest for time api --- tests/test_client.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/test_client.py b/tests/test_client.py index a26677a..5f4737c 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -80,6 +80,8 @@ def test_send_batch_data(self): self.assertEqual(int(data.value), 42) def test_receive_next(self): + """receive_next + """ io = self.get_client() self.ensure_feed_deleted(io, 'testfeed') test_feed = io.create_feed(Feed(name="testfeed")) @@ -88,6 +90,8 @@ def test_receive_next(self): self.assertEqual(int(data.value), 1) def test_receive_previous(self): + """receive_previous + """ io = self.get_client() self.ensure_feed_deleted(io, 'testfeed') test_feed = io.create_feed(Feed(name="testfeed")) @@ -101,6 +105,8 @@ def test_receive_previous(self): self.assertEqual(int(data.value), 2) def test_data_on_feed_returns_all_data(self): + """send_data + """ io = self.get_client() self.ensure_feed_deleted(io, 'testfeed') test_feed = io.create_feed(Feed(name="testfeed")) @@ -112,6 +118,8 @@ def test_data_on_feed_returns_all_data(self): self.assertEqual(int(result[1].value), 1) def test_data_on_feed_and_data_id_returns_data(self): + """send_data + """ io = self.get_client() self.ensure_feed_deleted(io, 'testfeed') test_feed = io.create_feed(Feed(name="testfeed")) @@ -121,6 +129,8 @@ def test_data_on_feed_and_data_id_returns_data(self): self.assertEqual(int(data.value), int(result.value)) def test_create_data(self): + """create_data + """ aio = self.get_client() self.ensure_feed_deleted(aio, 'testfeed') test_feed = aio.create_feed(Feed(name="testfeed")) @@ -130,6 +140,8 @@ def test_create_data(self): self.assertEqual(int(result.value), 42) def test_location_data(self): + """receive_location + """ aio = self.get_client() self.ensure_feed_deleted(aio, 'testlocfeed') test_feed = aio.create_feed(Feed(name='testlocfeed')) @@ -144,6 +156,16 @@ def test_location_data(self): self.assertEqual(float(data.lon), -74.005334) self.assertEqual(float(data.ele), -6.0) + def test_time_data(self): + """receive_time + """ + aio = self.get_client() + time = aio.receive_time() + # Check that each value is rx'd properly + # (should never be None type) + for time_data in time: + self.assertIsNotNone(time_data) + # Test Feed Functionality def test_append_by_feed_name(self): io = self.get_client() From f49d8c13371e04a078de41f8844f3d814b0aa633 Mon Sep 17 00:00:00 2001 From: brentru Date: Fri, 5 Apr 2019 13:01:07 -0400 Subject: [PATCH 048/116] update jupyter badge in readme --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 4ce0470..1580674 100644 --- a/README.rst +++ b/README.rst @@ -14,7 +14,7 @@ Adafruit IO Python :alt: Build Status .. image:: https://img.shields.io/badge/Try%20out-Adafruit%20IO%20Python-579ACA.svg?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFkAAABZCAMAAABi1XidAAAB8lBMVEX///9XmsrmZYH1olJXmsr1olJXmsrmZYH1olJXmsr1olJXmsrmZYH1olL1olJXmsr1olJXmsrmZYH1olL1olJXmsrmZYH1olJXmsr1olL1olJXmsrmZYH1olL1olJXmsrmZYH1olL1olL0nFf1olJXmsrmZYH1olJXmsq8dZb1olJXmsrmZYH1olJXmspXmspXmsr1olL1olJXmsrmZYH1olJXmsr1olL1olJXmsrmZYH1olL1olLeaIVXmsrmZYH1olL1olL1olJXmsrmZYH1olLna31Xmsr1olJXmsr1olJXmsrmZYH1olLqoVr1olJXmsr1olJXmsrmZYH1olL1olKkfaPobXvviGabgadXmsqThKuofKHmZ4Dobnr1olJXmsr1olJXmspXmsr1olJXmsrfZ4TuhWn1olL1olJXmsqBi7X1olJXmspZmslbmMhbmsdemsVfl8ZgmsNim8Jpk8F0m7R4m7F5nLB6jbh7jbiDirOEibOGnKaMhq+PnaCVg6qWg6qegKaff6WhnpKofKGtnomxeZy3noG6dZi+n3vCcpPDcpPGn3bLb4/Mb47UbIrVa4rYoGjdaIbeaIXhoWHmZYHobXvpcHjqdHXreHLroVrsfG/uhGnuh2bwj2Hxk17yl1vzmljzm1j0nlX1olL3AJXWAAAAbXRSTlMAEBAQHx8gICAuLjAwMDw9PUBAQEpQUFBXV1hgYGBkcHBwcXl8gICAgoiIkJCQlJicnJ2goKCmqK+wsLC4usDAwMjP0NDQ1NbW3Nzg4ODi5+3v8PDw8/T09PX29vb39/f5+fr7+/z8/Pz9/v7+zczCxgAABC5JREFUeAHN1ul3k0UUBvCb1CTVpmpaitAGSLSpSuKCLWpbTKNJFGlcSMAFF63iUmRccNG6gLbuxkXU66JAUef/9LSpmXnyLr3T5AO/rzl5zj137p136BISy44fKJXuGN/d19PUfYeO67Znqtf2KH33Id1psXoFdW30sPZ1sMvs2D060AHqws4FHeJojLZqnw53cmfvg+XR8mC0OEjuxrXEkX5ydeVJLVIlV0e10PXk5k7dYeHu7Cj1j+49uKg7uLU61tGLw1lq27ugQYlclHC4bgv7VQ+TAyj5Zc/UjsPvs1sd5cWryWObtvWT2EPa4rtnWW3JkpjggEpbOsPr7F7EyNewtpBIslA7p43HCsnwooXTEc3UmPmCNn5lrqTJxy6nRmcavGZVt/3Da2pD5NHvsOHJCrdc1G2r3DITpU7yic7w/7Rxnjc0kt5GC4djiv2Sz3Fb2iEZg41/ddsFDoyuYrIkmFehz0HR2thPgQqMyQYb2OtB0WxsZ3BeG3+wpRb1vzl2UYBog8FfGhttFKjtAclnZYrRo9ryG9uG/FZQU4AEg8ZE9LjGMzTmqKXPLnlWVnIlQQTvxJf8ip7VgjZjyVPrjw1te5otM7RmP7xm+sK2Gv9I8Gi++BRbEkR9EBw8zRUcKxwp73xkaLiqQb+kGduJTNHG72zcW9LoJgqQxpP3/Tj//c3yB0tqzaml05/+orHLksVO+95kX7/7qgJvnjlrfr2Ggsyx0eoy9uPzN5SPd86aXggOsEKW2Prz7du3VID3/tzs/sSRs2w7ovVHKtjrX2pd7ZMlTxAYfBAL9jiDwfLkq55Tm7ifhMlTGPyCAs7RFRhn47JnlcB9RM5T97ASuZXIcVNuUDIndpDbdsfrqsOppeXl5Y+XVKdjFCTh+zGaVuj0d9zy05PPK3QzBamxdwtTCrzyg/2Rvf2EstUjordGwa/kx9mSJLr8mLLtCW8HHGJc2R5hS219IiF6PnTusOqcMl57gm0Z8kanKMAQg0qSyuZfn7zItsbGyO9QlnxY0eCuD1XL2ys/MsrQhltE7Ug0uFOzufJFE2PxBo/YAx8XPPdDwWN0MrDRYIZF0mSMKCNHgaIVFoBbNoLJ7tEQDKxGF0kcLQimojCZopv0OkNOyWCCg9XMVAi7ARJzQdM2QUh0gmBozjc3Skg6dSBRqDGYSUOu66Zg+I2fNZs/M3/f/Grl/XnyF1Gw3VKCez0PN5IUfFLqvgUN4C0qNqYs5YhPL+aVZYDE4IpUk57oSFnJm4FyCqqOE0jhY2SMyLFoo56zyo6becOS5UVDdj7Vih0zp+tcMhwRpBeLyqtIjlJKAIZSbI8SGSF3k0pA3mR5tHuwPFoa7N7reoq2bqCsAk1HqCu5uvI1n6JuRXI+S1Mco54YmYTwcn6Aeic+kssXi8XpXC4V3t7/ADuTNKaQJdScAAAAAElFTkSuQmCC - :target: https://mybinder.org/v2/gh/brentru/adafruit_io_python_jupyter/master?filepath=adafruit-io-python-tutorial.ipynb + :target: https://mybinder.org/v2/gh/adafruit/adafruit_io_python_jupyter/master?filepath=adafruit-io-python-tutorial.ipynb .. image:: https://cdn-learn.adafruit.com/assets/assets/000/057/153/original/adafruit_io_iopython.png?1530802073 From 5c35fd03d2344be82dcd1db9d93b4f7c343ed251 Mon Sep 17 00:00:00 2001 From: Barbudor Date: Wed, 17 Apr 2019 23:05:48 +0200 Subject: [PATCH 049/116] Fixes `location.py`sample for wrong/outdate API --- examples/basics/location.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/basics/location.py b/examples/basics/location.py index b110e9a..c772a9c 100644 --- a/examples/basics/location.py +++ b/examples/basics/location.py @@ -49,7 +49,8 @@ print('\tLon: ', lon) print('\tEle: ', ele) # Send location data to Adafruit IO - aio.send_location_data(location.key, lat, lon, ele, value) + metadata = { 'lat':lat, 'lon':lon, 'ele':ele, 'created_at':time.asctime(time.gmtime()) } + aio.send_data(location.key,value,metadata) # shift all values (for test/demo purposes) value += 1 lat -= 0.01 From 6e0a33f1c83badfe4b4176df9a515b8b3d059c56 Mon Sep 17 00:00:00 2001 From: brentru Date: Mon, 8 Jul 2019 10:30:59 -0400 Subject: [PATCH 050/116] fix methods, tocolor->to_color --- examples/basics/rgb_led.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/basics/rgb_led.py b/examples/basics/rgb_led.py index e3bf6c6..06781bf 100644 --- a/examples/basics/rgb_led.py +++ b/examples/basics/rgb_led.py @@ -78,11 +78,11 @@ def map_range(x, in_min, in_max, out_min, out_max): if color_val != prev_color: # print rgb values and hex value print('Received Color: ') - red = aio.toRed(color_val.value) + red = aio.to_red(color_val.value) print('\t - R: ', red) - green = aio.toGreen(color_val.value) + green = aio.to_green(color_val.value) print('\t - G: ', green) - blue = aio.toBlue(color_val.value) + blue = aio.to_blue(color_val.value) print('\t - B: ', blue) print('\t - HEX: ', color_val.value) # map color values (0-255) to 16-bit values for the pca From 5813ae3e5e3b683cb4048245de5fbc99b347ba31 Mon Sep 17 00:00:00 2001 From: brentrubell Date: Wed, 10 Jul 2019 10:43:25 -0400 Subject: [PATCH 051/116] Bump version for release --- Adafruit_IO/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Adafruit_IO/_version.py b/Adafruit_IO/_version.py index ef6497d..3d67cd6 100644 --- a/Adafruit_IO/_version.py +++ b/Adafruit_IO/_version.py @@ -1 +1 @@ -__version__ = "2.3.2" +__version__ = "2.4.0" From b022ea28860b557dc6ab1a5b80223c095dd54a40 Mon Sep 17 00:00:00 2001 From: brentru Date: Fri, 26 Jul 2019 14:36:39 -0400 Subject: [PATCH 052/116] add back removed digital in.. --- examples/basics/digital_in.py | 54 +++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 examples/basics/digital_in.py diff --git a/examples/basics/digital_in.py b/examples/basics/digital_in.py new file mode 100644 index 0000000..c4a40c0 --- /dev/null +++ b/examples/basics/digital_in.py @@ -0,0 +1,54 @@ +""" +'digital_in.py' +================================== +Example of sending button values +to an Adafruit IO feed. + +Author(s): Brent Rubell, Todd Treece +""" +# Import standard python modules +import time + +# import Adafruit Blinka +import board +import digitalio + +# import Adafruit IO REST client. +from Adafruit_IO import Client, Feed, RequestError + +# Set to your Adafruit IO key. +# Remember, your key is a secret, +# so make sure not to publish it when you publish this code! +ADAFRUIT_IO_KEY = 'YOUR_AIO_KEY' + +# Set to your Adafruit IO username. +# (go to https://accounts.adafruit.com to find your username) +ADAFRUIT_IO_USERNAME = 'YOUR_AIO_USERNAME' + +# Create an instance of the REST client. +aio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY) + +try: # if we have a 'digital' feed + digital = aio.feeds('digital') +except RequestError: # create a digital feed + feed = Feed(name="digital") + digital = aio.create_feed(feed) + +# button set up +button = digitalio.DigitalInOut(board.D12) +button.direction = digitalio.Direction.INPUT +button.pull = digitalio.Pull.UP +button_current = 0 + + +while True: + if not button.value: + button_current = 1 + else: + button_current = 0 + + print('Button -> ', button_current) + aio.send(digital.key, button_current) + + # avoid timeout from adafruit io + time.sleep(1) \ No newline at end of file From 5efd5a82b8bf7313d4199075ac1e6db9917dfc80 Mon Sep 17 00:00:00 2001 From: brentru Date: Fri, 26 Jul 2019 14:38:34 -0400 Subject: [PATCH 053/116] bump patch --- Adafruit_IO/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Adafruit_IO/_version.py b/Adafruit_IO/_version.py index 3d67cd6..54499df 100644 --- a/Adafruit_IO/_version.py +++ b/Adafruit_IO/_version.py @@ -1 +1 @@ -__version__ = "2.4.0" +__version__ = "2.4.1" From e4916b6e1d8a83c9e3a079d4f1cf235d459e83bd Mon Sep 17 00:00:00 2001 From: brentru Date: Thu, 19 Dec 2019 11:50:43 -0500 Subject: [PATCH 054/116] fix sub callback, qos --- Adafruit_IO/_version.py | 2 +- Adafruit_IO/mqtt_client.py | 56 +++++++++++++++++++++++++------------- 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/Adafruit_IO/_version.py b/Adafruit_IO/_version.py index 54499df..3d67cd6 100644 --- a/Adafruit_IO/_version.py +++ b/Adafruit_IO/_version.py @@ -1 +1 @@ -__version__ = "2.4.1" +__version__ = "2.4.0" diff --git a/Adafruit_IO/mqtt_client.py b/Adafruit_IO/mqtt_client.py index d70118f..e565433 100644 --- a/Adafruit_IO/mqtt_client.py +++ b/Adafruit_IO/mqtt_client.py @@ -1,5 +1,5 @@ -# Copyright (c) 2014 Adafruit Industries -# Author: Tony DiCola +# Copyright (c) 2020 Adafruit Industries +# Author: Tony DiCola, Brent Rubell # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -43,9 +43,10 @@ class MQTTClient(object): def __init__(self, username, key, service_host='io.adafruit.com', secure=True): """Create instance of MQTT client. - :param username: Adafruit.IO Username for your account. - :param key: Adafruit IO access key (AIO Key) for your account. - :param secure: (optional, boolean) Switches secure/insecure connections + :param username: Adafruit.IO Username for your account. + :param key: Adafruit IO access key (AIO Key) for your account. + :param secure: (optional, boolean) Switches secure/insecure connections + """ self._username = username self._service_host = service_host @@ -70,6 +71,7 @@ def __init__(self, username, key, service_host='io.adafruit.com', secure=True): self._client.on_connect = self._mqtt_connect self._client.on_disconnect = self._mqtt_disconnect self._client.on_message = self._mqtt_message + self._client.on_subscribe = self._mqtt_subscribe self._connected = False @@ -95,7 +97,7 @@ def _mqtt_disconnect(self, client, userdata, rc): # log the RC as an error. Continue on to call any disconnect handler # so clients can potentially recover gracefully. if rc != 0: - print("Unexpected disconnection.") + print('Unexpected disconnection.') raise MQTTError(rc) print('Disconnected from Adafruit IO!') # Call the on_disconnect callback if available. @@ -105,6 +107,7 @@ def _mqtt_disconnect(self, client, userdata, rc): def _mqtt_message(self, client, userdata, msg): """Parse out the topic and call on_message callback assume topic looks like `username/topic/id` + """ logger.debug('Client on_message called.') parsed_topic = msg.topic.split('/') @@ -124,15 +127,19 @@ def _mqtt_message(self, client, userdata, msg): else: raise ValueError('on_message not defined') self.on_message(self, topic, payload) - - def _mqtt_subscribe(client, userdata, mid, granted_qos): + + def _mqtt_subscribe(self, client, userdata, mid, granted_qos): """Called when broker responds to a subscribe request.""" + logger.debug('Client called on_subscribe') + if self.on_subscribe is not None: + self.on_subscribe(self, userdata, mid, granted_qos) def connect(self, **kwargs): """Connect to the Adafruit.IO service. Must be called before any loop or publish operations are called. Will raise an exception if a connection cannot be made. Optional keyword arguments will be passed to paho-mqtt client connect function. + """ # Skip calling connect if already connected. if self._connected: @@ -145,6 +152,7 @@ def connect(self, **kwargs): def is_connected(self): """Returns True if connected to Adafruit.IO and False if not connected. + """ return self._connected @@ -157,9 +165,9 @@ def loop_background(self, stop=None): """Starts a background thread to listen for messages from Adafruit.IO and call the appropriate callbacks when feed events occur. Will return immediately and will not block execution. Should only be called once. - - Params: - - stop: boolean, stops the execution of the background loop. + + :param bool stop: Stops the execution of the background loop. + """ if stop: self._client.loop_stop() @@ -174,6 +182,7 @@ def loop_blocking(self): listen and respond to Adafruit.IO feed events. If you need to do other processing, consider using the loop_background function to run a loop in the background. + """ self._client.loop_forever() @@ -185,28 +194,36 @@ def loop(self, timeout_sec=1.0): The optional timeout_sec parameter specifies at most how long to block execution waiting for messages when this function is called. The default is one second. + """ self._client.loop(timeout=timeout_sec) - def subscribe(self, feed_id, feed_user=None): + def subscribe(self, feed_id, feed_user=None, qos=0): """Subscribe to changes on the specified feed. When the feed is updated the on_message function will be called with the feed_id and new value. - Params: - - feed_id: The id of the feed to subscribe to. - - feed_user (optional): The user id of the feed. Used for feed sharing functionality. + :param str feed_id: The key of the feed to subscribe to. + :param str feed_user: Optional, identifies feed owner. Used for feed sharing. + :param int qos: The QoS to use when subscribing. Defaults to 0. + """ + if qos > 1: + raise MQTTError("Adafruit IO only supports a QoS level of 0 or 1.") if feed_user is not None: - (res, mid) = self._client.subscribe('{0}/feeds/{1}'.format(feed_user, feed_id)) + (res, mid) = self._client.subscribe('{0}/feeds/{1}'.format(feed_user, feed_id, qos=qos)) else: - (res, mid) = self._client.subscribe('{0}/feeds/{1}'.format(self._username, feed_id)) + (res, mid) = self._client.subscribe('{0}/feeds/{1}'.format(self._username, feed_id), qos=qos) return res, mid - def subscribe_group(self, group_id): + def subscribe_group(self, group_id, qos=0): """Subscribe to changes on the specified group. When the group is updated the on_message function will be called with the group_id and the new value. + + :param str feed_id: The key of the feed to subscribe to. + :param int qos: The QoS to use when subscribing. Defaults to 0. + """ - self._client.subscribe('{0}/groups/{1}'.format(self._username, group_id)) + self._client.subscribe('{0}/groups/{1}'.format(self._username, group_id), qos=qos) def subscribe_randomizer(self, randomizer_id): """Subscribe to changes on a specified random data stream from @@ -216,6 +233,7 @@ def subscribe_randomizer(self, randomizer_id): every client that is subscribed to the same topic. :param int randomizer_id: ID of the random word record you want data for. + """ self._client.subscribe('{0}/integration/words/{1}'.format(self._username, randomizer_id)) From 611823bcfba3cc12e53e10749d9b5831ae4aa381 Mon Sep 17 00:00:00 2001 From: brentru Date: Thu, 19 Dec 2019 11:53:05 -0500 Subject: [PATCH 055/116] add example --- examples/mqtt/mqtt_subscribe.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/examples/mqtt/mqtt_subscribe.py b/examples/mqtt/mqtt_subscribe.py index fcda0ab..e3b2999 100644 --- a/examples/mqtt/mqtt_subscribe.py +++ b/examples/mqtt/mqtt_subscribe.py @@ -31,6 +31,10 @@ def connected(client): # Subscribe to changes on a feed named DemoFeed. client.subscribe(FEED_ID) +def subscribe(client, userdata, mid, granted_qos): + # This method is called when the client subscribes to a new feed. + print('Subscribed to {0} with QoS {1}'.format(FEED_ID, granted_qos[0])) + def disconnected(client): # Disconnected function will be called when the client disconnects. print('Disconnected from Adafruit IO!') @@ -50,6 +54,7 @@ def message(client, feed_id, payload): client.on_connect = connected client.on_disconnect = disconnected client.on_message = message +client.on_subscribe = subscribe # Connect to the Adafruit IO server. client.connect() From 062628b168d33a4c98cc3ab533acccb3b48d7614 Mon Sep 17 00:00:00 2001 From: cv65 Date: Thu, 28 May 2020 21:29:41 -0400 Subject: [PATCH 056/116] Update README.rst Add a note about the prerequisite packages needed before pip3 can install adafruit-io --- README.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.rst b/README.rst index 1580674..946cda6 100644 --- a/README.rst +++ b/README.rst @@ -35,6 +35,13 @@ If you have `PIP `_ installed (typica This will automatically install the Adafruit IO Python client code for your Python scripts to use. You might want to examine the examples folder in this GitHub repository to see examples of usage. +If the above command fails, you may first need to install prerequisites: + +.. code-block:: shell + + pip3 install setuptools + pip3 install wheel + Manual Installation ~~~~~~~~~~~~~~~~~~~ From 646e445a125a0b3be690a50c9916ec5fb9c3abbc Mon Sep 17 00:00:00 2001 From: Phil Hord Date: Fri, 5 Jun 2020 14:27:15 -0700 Subject: [PATCH 057/116] Fix syntax error in Groups documentation --- docs/groups.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/groups.rst b/docs/groups.rst index b24901e..2a931de 100644 --- a/docs/groups.rst +++ b/docs/groups.rst @@ -19,7 +19,7 @@ Create a group by constructing a Group instance with at least a name specified, # Send the group for IO to create: # The returned object will contain all the details about the created group. - group = aio.create_group(group + group = aio.create_group(group) Group Retrieval From ce3347249bb29c41ee581820ea61770485457c16 Mon Sep 17 00:00:00 2001 From: Flavio Fernandes Date: Thu, 27 Aug 2020 16:40:12 -0400 Subject: [PATCH 058/116] [mqtt_client]: Fix docstring in subscribe_group Fix param in the doc string of subscribe_group to use proper name. Trivial fix --- Adafruit_IO/mqtt_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Adafruit_IO/mqtt_client.py b/Adafruit_IO/mqtt_client.py index e565433..b6af726 100644 --- a/Adafruit_IO/mqtt_client.py +++ b/Adafruit_IO/mqtt_client.py @@ -219,7 +219,7 @@ def subscribe_group(self, group_id, qos=0): """Subscribe to changes on the specified group. When the group is updated the on_message function will be called with the group_id and the new value. - :param str feed_id: The key of the feed to subscribe to. + :param str group_id: The id of the group to subscribe to. :param int qos: The QoS to use when subscribing. Defaults to 0. """ From d7d8b274e31a369b17d0b5e74c97efb28cfef68a Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 23 Sep 2020 15:46:06 -0400 Subject: [PATCH 059/116] add unit test workflow --- .github/workflows/build.yml | 14 ++++++++++++++ .travis.yml | 23 ----------------------- 2 files changed, 14 insertions(+), 23 deletions(-) create mode 100644 .github/workflows/build.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..fee47b8 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,14 @@ +name: Build-CI + +on: [pull_request, push] + +name: Test +on: [push] +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Prepare repo + uses: actions/checkout@master + - name: Test + uses: onichandame/python-test-action@master \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index a6b8eba..0000000 --- a/.travis.yml +++ /dev/null @@ -1,23 +0,0 @@ -language: python -dist: trusty -sudo: required -python: -- '3.6' -cache: - pip: true -install: -- python3 setup.py install -- pip3 install pylint Sphinx sphinx-rtd-theme -- pip3 install . -script: -- cd docs && sphinx-build -E -W -b html . _build/html && cd .. -- cd tests/ -- python3 -m unittest discover -- cd .. -deploy: - provider: pypi - user: adafruit-travis - password: - secure: B23uQWyBD9nE4zP+D8n3c8P9h2XsOdJWelVIwBoOL/E8LAqQ60xKRhYo+dbYFj/4L0xPsYxVoF8xdZCu+1rjXvFU6Eot9pgna7DBGzu8Qwzq7CS0Hq014+XhZR+36T9YAOl6Ehr7JR3XMCf51FGfq15myPvKFkCMjxpvxIlVsqg= - on: - tags: true From ce36a9965025e56739298eb700044b1270290fda Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 23 Sep 2020 15:47:12 -0400 Subject: [PATCH 060/116] fix redefinition of name --- .github/workflows/build.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fee47b8..5ed966f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,8 +2,6 @@ name: Build-CI on: [pull_request, push] -name: Test -on: [push] jobs: test: runs-on: ubuntu-latest From e77aa786039daf158db8ce41c6befa13036c463e Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 23 Sep 2020 15:51:15 -0400 Subject: [PATCH 061/116] remove actions ci --- .github/workflows/build.yml | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5ed966f..1c8d2f6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,10 +3,16 @@ name: Build-CI on: [pull_request, push] jobs: - test: + build: runs-on: ubuntu-latest + steps: - - name: Prepare repo - uses: actions/checkout@master - - name: Test - uses: onichandame/python-test-action@master \ No newline at end of file + - uses: actions/checkout@v2 + - name: Set up Python 3.8 + uses: actions/setup-python@v2 + with: + python-version: 3.8 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi \ No newline at end of file From 2b7083d50cb1394a1d1d51d49ff6ad93d1bb0986 Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 23 Sep 2020 15:53:14 -0400 Subject: [PATCH 062/116] install library --- .github/workflows/build.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1c8d2f6..5414a97 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,11 +8,13 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Set up Python 3.8 - uses: actions/setup-python@v2 + - name: Set up Python 3.6 + uses: actions/setup-python@v1 with: - python-version: 3.8 + python-version: 3.6 - name: Install dependencies run: | python -m pip install --upgrade pip - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi \ No newline at end of file + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Install library + run: python3 setup.py install \ No newline at end of file From e148ec564c2a6d11e93aedf91a2f324005178cf6 Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 23 Sep 2020 15:56:02 -0400 Subject: [PATCH 063/116] unittest --- .github/workflows/build.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5414a97..310d2dd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,4 +17,8 @@ jobs: python -m pip install --upgrade pip if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Install library - run: python3 setup.py install \ No newline at end of file + run: python3 setup.py install + - name: Run all unittests + run: | + cd tests/ + python3 -m unittest discover \ No newline at end of file From 6706193718906abf2475e255f68906ad66e2f4b0 Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 23 Sep 2020 16:01:15 -0400 Subject: [PATCH 064/116] swap io user/key to secrets environmental vars --- tests/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/base.py b/tests/base.py index 31d2ceb..501eca8 100644 --- a/tests/base.py +++ b/tests/base.py @@ -28,7 +28,7 @@ def get_test_key(self): """Return the AIO key specified in the ADAFRUIT_IO_KEY environment variable, or raise an exception if it doesn't exist. """ - key = '68163f6f6ee24475b5edb0ed1f77f80a' + key = os.environ[CI_IO_KEY] if key is None: raise RuntimeError("ADAFRUIT_IO_KEY environment variable must be " \ "set with valid Adafruit IO key to run this test!") @@ -38,7 +38,7 @@ def get_test_username(self): """Return the AIO username specified in the ADAFRUIT_IO_USERNAME environment variable, or raise an exception if it doesn't exist. """ - username = 'travisiotester' + username = os.environ[CI_IO_USERNAME] if username is None: raise RuntimeError("ADAFRUIT_IO_USERNAME environment variable must be " \ "set with valid Adafruit IO username to run this test!") From 25e52c0313fbe2f483223515a11906cb286520ec Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 23 Sep 2020 16:08:56 -0400 Subject: [PATCH 065/116] export env vars --- .github/workflows/build.yml | 4 ++++ tests/base.py | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 310d2dd..cb8dd06 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,6 +18,10 @@ jobs: if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Install library run: python3 setup.py install + - name: Secret + env: + secret_io_key: ${{ secrets.CI_IO_KEY } + secret_io_user: ${{ secrets.CI_IO_USERNAME } - name: Run all unittests run: | cd tests/ diff --git a/tests/base.py b/tests/base.py index 501eca8..0673b2a 100644 --- a/tests/base.py +++ b/tests/base.py @@ -28,7 +28,7 @@ def get_test_key(self): """Return the AIO key specified in the ADAFRUIT_IO_KEY environment variable, or raise an exception if it doesn't exist. """ - key = os.environ[CI_IO_KEY] + key = secrets.[CI_IO_KEY] if key is None: raise RuntimeError("ADAFRUIT_IO_KEY environment variable must be " \ "set with valid Adafruit IO key to run this test!") @@ -38,7 +38,7 @@ def get_test_username(self): """Return the AIO username specified in the ADAFRUIT_IO_USERNAME environment variable, or raise an exception if it doesn't exist. """ - username = os.environ[CI_IO_USERNAME] + username = secrets.[CI_IO_USERNAME] if username is None: raise RuntimeError("ADAFRUIT_IO_USERNAME environment variable must be " \ "set with valid Adafruit IO username to run this test!") From dbf52b2547672581d7025958ccbbdf5c6f1f06b4 Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 23 Sep 2020 16:10:34 -0400 Subject: [PATCH 066/116] link in base.py --- .github/workflows/build.yml | 5 ++--- tests/base.py | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cb8dd06..7c5ad07 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,7 +22,6 @@ jobs: env: secret_io_key: ${{ secrets.CI_IO_KEY } secret_io_user: ${{ secrets.CI_IO_USERNAME } + run: cd tests/ - name: Run all unittests - run: | - cd tests/ - python3 -m unittest discover \ No newline at end of file + run: python3 -m unittest discover \ No newline at end of file diff --git a/tests/base.py b/tests/base.py index 0673b2a..69bbca5 100644 --- a/tests/base.py +++ b/tests/base.py @@ -28,7 +28,7 @@ def get_test_key(self): """Return the AIO key specified in the ADAFRUIT_IO_KEY environment variable, or raise an exception if it doesn't exist. """ - key = secrets.[CI_IO_KEY] + key = os.environ[secret_io_key] if key is None: raise RuntimeError("ADAFRUIT_IO_KEY environment variable must be " \ "set with valid Adafruit IO key to run this test!") @@ -38,7 +38,7 @@ def get_test_username(self): """Return the AIO username specified in the ADAFRUIT_IO_USERNAME environment variable, or raise an exception if it doesn't exist. """ - username = secrets.[CI_IO_USERNAME] + username = os.environ[secret_io_user] if username is None: raise RuntimeError("ADAFRUIT_IO_USERNAME environment variable must be " \ "set with valid Adafruit IO username to run this test!") From 162e9482498d844eaa286633c74612a659f70df6 Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 23 Sep 2020 16:11:37 -0400 Subject: [PATCH 067/116] remove tab --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7c5ad07..bb7d58f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,6 +22,6 @@ jobs: env: secret_io_key: ${{ secrets.CI_IO_KEY } secret_io_user: ${{ secrets.CI_IO_USERNAME } - run: cd tests/ + run: cd tests/ - name: Run all unittests run: python3 -m unittest discover \ No newline at end of file From 5cd1cf368787beee1a7d3ac762ada3c393c3a320 Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 23 Sep 2020 16:12:18 -0400 Subject: [PATCH 068/116] close escaped {} sequence --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bb7d58f..92ece7b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,8 +20,8 @@ jobs: run: python3 setup.py install - name: Secret env: - secret_io_key: ${{ secrets.CI_IO_KEY } - secret_io_user: ${{ secrets.CI_IO_USERNAME } + secret_io_key: ${{ secrets.CI_IO_KEY }} + secret_io_user: ${{ secrets.CI_IO_USERNAME }} run: cd tests/ - name: Run all unittests run: python3 -m unittest discover \ No newline at end of file From 404665dc8393f61813f41b16a2a2330ca19bd191 Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 23 Sep 2020 16:13:17 -0400 Subject: [PATCH 069/116] point at tests --- .github/workflows/build.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 92ece7b..71f7ca3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,4 +24,6 @@ jobs: secret_io_user: ${{ secrets.CI_IO_USERNAME }} run: cd tests/ - name: Run all unittests - run: python3 -m unittest discover \ No newline at end of file + run: | + cd tests/ + python3 -m unittest discover \ No newline at end of file From 1ae0addb1d903774ee4811168a199d44c73573c8 Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 23 Sep 2020 16:16:20 -0400 Subject: [PATCH 070/116] try running with env in same step --- .github/workflows/build.yml | 6 ++---- tests/base.py | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 71f7ca3..90afa2d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,12 +18,10 @@ jobs: if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Install library run: python3 setup.py install - - name: Secret + - name: Run all unittests env: secret_io_key: ${{ secrets.CI_IO_KEY }} secret_io_user: ${{ secrets.CI_IO_USERNAME }} - run: cd tests/ - - name: Run all unittests run: | cd tests/ - python3 -m unittest discover \ No newline at end of file + AIO_KEY=secret_io_key AIO_USER=secret_io_user python -m unittest discover diff --git a/tests/base.py b/tests/base.py index 69bbca5..a212225 100644 --- a/tests/base.py +++ b/tests/base.py @@ -28,7 +28,7 @@ def get_test_key(self): """Return the AIO key specified in the ADAFRUIT_IO_KEY environment variable, or raise an exception if it doesn't exist. """ - key = os.environ[secret_io_key] + key = os.environ[AIO_KEY] if key is None: raise RuntimeError("ADAFRUIT_IO_KEY environment variable must be " \ "set with valid Adafruit IO key to run this test!") @@ -38,7 +38,7 @@ def get_test_username(self): """Return the AIO username specified in the ADAFRUIT_IO_USERNAME environment variable, or raise an exception if it doesn't exist. """ - username = os.environ[secret_io_user] + username = os.environ[AIO_USER] if username is None: raise RuntimeError("ADAFRUIT_IO_USERNAME environment variable must be " \ "set with valid Adafruit IO username to run this test!") From 2f58c6879851eb6d7bdef318432a9840aa16ec37 Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 23 Sep 2020 16:20:31 -0400 Subject: [PATCH 071/116] grok io key directly from script --- .github/workflows/build.yml | 6 +++--- tests/base.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 90afa2d..b9f8cd8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,8 +20,8 @@ jobs: run: python3 setup.py install - name: Run all unittests env: - secret_io_key: ${{ secrets.CI_IO_KEY }} - secret_io_user: ${{ secrets.CI_IO_USERNAME }} + SECRET_IO_KEY: ${{ secrets.CI_IO_KEY }} + SECRET_IO_USER: ${{ secrets.CI_IO_USERNAME }} run: | cd tests/ - AIO_KEY=secret_io_key AIO_USER=secret_io_user python -m unittest discover + python -m unittest discover diff --git a/tests/base.py b/tests/base.py index a212225..020c662 100644 --- a/tests/base.py +++ b/tests/base.py @@ -28,7 +28,7 @@ def get_test_key(self): """Return the AIO key specified in the ADAFRUIT_IO_KEY environment variable, or raise an exception if it doesn't exist. """ - key = os.environ[AIO_KEY] + key = os.environ[SECRET_IO_KEY] if key is None: raise RuntimeError("ADAFRUIT_IO_KEY environment variable must be " \ "set with valid Adafruit IO key to run this test!") @@ -38,7 +38,7 @@ def get_test_username(self): """Return the AIO username specified in the ADAFRUIT_IO_USERNAME environment variable, or raise an exception if it doesn't exist. """ - username = os.environ[AIO_USER] + username = os.environ[SECRET_IO_USER] if username is None: raise RuntimeError("ADAFRUIT_IO_USERNAME environment variable must be " \ "set with valid Adafruit IO username to run this test!") From 466f06de900bcba0df148d174a073b1fdbbc35de Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 23 Sep 2020 16:31:13 -0400 Subject: [PATCH 072/116] modified base to take in envar --- .github/workflows/build.yml | 2 +- tests/base.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b9f8cd8..f360d56 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,4 +24,4 @@ jobs: SECRET_IO_USER: ${{ secrets.CI_IO_USERNAME }} run: | cd tests/ - python -m unittest discover + ADAFRUIT_IO_KEY="$SECRET_IO_KEY" ADAFRUIT_IO_USERNAME="$SECRET_IO_USER" python unittest discover diff --git a/tests/base.py b/tests/base.py index 020c662..ac51fab 100644 --- a/tests/base.py +++ b/tests/base.py @@ -28,7 +28,7 @@ def get_test_key(self): """Return the AIO key specified in the ADAFRUIT_IO_KEY environment variable, or raise an exception if it doesn't exist. """ - key = os.environ[SECRET_IO_KEY] + key = os.environ.get('ADAFRUIT_IO_KEY', None) if key is None: raise RuntimeError("ADAFRUIT_IO_KEY environment variable must be " \ "set with valid Adafruit IO key to run this test!") @@ -38,7 +38,7 @@ def get_test_username(self): """Return the AIO username specified in the ADAFRUIT_IO_USERNAME environment variable, or raise an exception if it doesn't exist. """ - username = os.environ[SECRET_IO_USER] + username = os.environ.get('ADAFRUIT_IO_USERNAME', None) if username is None: raise RuntimeError("ADAFRUIT_IO_USERNAME environment variable must be " \ "set with valid Adafruit IO username to run this test!") From 669f57ad4d2be085fe9f2ee350a3e4c990c5056b Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 23 Sep 2020 16:33:41 -0400 Subject: [PATCH 073/116] add -m flag --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f360d56..474339f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,4 +24,4 @@ jobs: SECRET_IO_USER: ${{ secrets.CI_IO_USERNAME }} run: | cd tests/ - ADAFRUIT_IO_KEY="$SECRET_IO_KEY" ADAFRUIT_IO_USERNAME="$SECRET_IO_USER" python unittest discover + ADAFRUIT_IO_KEY=$SECRET_IO_KEY ADAFRUIT_IO_USERNAME=$SECRET_IO_USER python -m unittest discover From a134411fb02cd60b7568f1e6be3a444b1520a1f4 Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 23 Sep 2020 16:42:08 -0400 Subject: [PATCH 074/116] add sphinx docu build --- .github/workflows/build.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 474339f..ac73401 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,10 +18,16 @@ jobs: if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Install library run: python3 setup.py install + - name: Install sphinx + run: pip3 install pylint Sphinx sphinx-rtd-theme - name: Run all unittests env: SECRET_IO_KEY: ${{ secrets.CI_IO_KEY }} SECRET_IO_USER: ${{ secrets.CI_IO_USERNAME }} run: | cd tests/ - ADAFRUIT_IO_KEY=$SECRET_IO_KEY ADAFRUIT_IO_USERNAME=$SECRET_IO_USER python -m unittest discover + ADAFRUIT_IO_KEY="$SECRET_IO_KEY" ADAFRUIT_IO_USERNAME="$SECRET_IO_USER" python -m unittest discover + cd .. + - name: Generate documentation + run: | + cd docs && sphinx-build -E -W -b html . _build/html && cd .. From f22fdbbe3bf6c0e138fe586b2f6eb63940e205e3 Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 23 Sep 2020 16:50:46 -0400 Subject: [PATCH 075/116] add release workflow --- .github/workflows/release.yml | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..27f5fee --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,33 @@ +name: Release CI + +on: + release: + types: [published] + +jobs: + upload-pypi: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Check For setup.py + id: need-pypi + run: | + echo ::set-output name=setup-py::$( find . -wholename './setup.py' ) + - name: Set up Python + if: contains(steps.need-pypi.outputs.setup-py, 'setup.py') + uses: actions/setup-python@v1 + with: + python-version: '3.x' + - name: Install dependencies + if: contains(steps.need-pypi.outputs.setup-py, 'setup.py') + run: | + python -m pip install --upgrade pip + pip install setuptools wheel twine + - name: Build and publish + if: contains(steps.need-pypi.outputs.setup-py, 'setup.py') + env: + TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} + TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + run: | + python setup.py sdist + twine upload dist/* \ No newline at end of file From d5edfd3bd3c331aba202a92a84042dbc8c146c59 Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 23 Sep 2020 16:53:45 -0400 Subject: [PATCH 076/116] replace travisCI badge with actions! --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 1580674..c53dc38 100644 --- a/README.rst +++ b/README.rst @@ -9,8 +9,8 @@ Adafruit IO Python :target: https://discord.gg/nBQh6qu :alt: Chat -.. image:: https://travis-ci.com/adafruit/Adafruit_IO_Python.svg?branch=master - :target: https://travis-ci.com/adafruit/Adafruit_IO_Python +.. image:: https://github.com/adafruit/Adafruit_IO_Python/workflows/Build%20CI/badge.svg + :target: https://github.com/adafruit/Adafruit_IO_Python/actions :alt: Build Status .. image:: https://img.shields.io/badge/Try%20out-Adafruit%20IO%20Python-579ACA.svg?logo=data:image/png;base64,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 From fc69311d3781656382491d5bb2f9bf0ad48e5db6 Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 4 Nov 2020 10:48:43 -0500 Subject: [PATCH 077/116] update VEML6070 function sig --- examples/basics/environmental_monitor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/basics/environmental_monitor.py b/examples/basics/environmental_monitor.py index 144010d..21be64e 100644 --- a/examples/basics/environmental_monitor.py +++ b/examples/basics/environmental_monitor.py @@ -87,8 +87,8 @@ # Sample VEML6070 def sample_VEML(): - for j in range(10): - uv_raw = uv.read + for _ in range(10): + uv_raw = uv.uv_raw return uv_raw while True: From 843b065aa84b8c8c0b9a29a08e13aa4d91a8ac96 Mon Sep 17 00:00:00 2001 From: Mike Causer Date: Tue, 8 Dec 2020 01:30:53 +1100 Subject: [PATCH 078/116] Fix expired Discord link --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 0e3cbfc..edb4bfa 100644 --- a/README.rst +++ b/README.rst @@ -6,7 +6,7 @@ Adafruit IO Python :alt: Documentation Status .. image:: https://img.shields.io/discord/327254708534116352.svg - :target: https://discord.gg/nBQh6qu + :target: https://adafru.it/discord :alt: Chat .. image:: https://github.com/adafruit/Adafruit_IO_Python/workflows/Build%20CI/badge.svg From f24166d0635178ee31621fb64e7b33b3ea84a0d4 Mon Sep 17 00:00:00 2001 From: brentru Date: Fri, 11 Dec 2020 15:42:59 -0500 Subject: [PATCH 079/116] update workflow --- .github/workflows/build.yml | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ac73401..b7fad9e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,26 +8,32 @@ jobs: steps: - uses: actions/checkout@v2 + - name: Set up Python 3.6 uses: actions/setup-python@v1 with: python-version: 3.6 + - name: Install dependencies run: | python -m pip install --upgrade pip if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Install library run: python3 setup.py install + - name: Install sphinx run: pip3 install pylint Sphinx sphinx-rtd-theme - - name: Run all unittests - env: - SECRET_IO_KEY: ${{ secrets.CI_IO_KEY }} - SECRET_IO_USER: ${{ secrets.CI_IO_USERNAME }} + + - name: Run unittests + with: # Set the secret as an input + SECRET_IO_KEY: ${{ secrets.CI_IO_KEY }} + SECRET_IO_USER: ${{ secrets.CI_IO_USERNAME }} run: | cd tests/ - ADAFRUIT_IO_KEY="$SECRET_IO_KEY" ADAFRUIT_IO_USERNAME="$SECRET_IO_USER" python -m unittest discover + ADAFRUIT_IO_KEY=$SECRET_IO_KEY ADAFRUIT_IO_USERNAME=$SECRET_IO_USER python -m unittest discover cd .. + - name: Generate documentation run: | cd docs && sphinx-build -E -W -b html . _build/html && cd .. From dd73f604eba12ca3797d7900d8aa5e3482d3c82d Mon Sep 17 00:00:00 2001 From: brentru Date: Fri, 11 Dec 2020 15:45:01 -0500 Subject: [PATCH 080/116] de-indent --- .github/workflows/build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b7fad9e..e027b55 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -26,9 +26,9 @@ jobs: run: pip3 install pylint Sphinx sphinx-rtd-theme - name: Run unittests - with: # Set the secret as an input - SECRET_IO_KEY: ${{ secrets.CI_IO_KEY }} - SECRET_IO_USER: ${{ secrets.CI_IO_USERNAME }} + env: + SECRET_IO_KEY: ${{ secrets.CI_IO_KEY }} + SECRET_IO_USER: ${{ secrets.CI_IO_USERNAME }} run: | cd tests/ ADAFRUIT_IO_KEY=$SECRET_IO_KEY ADAFRUIT_IO_USERNAME=$SECRET_IO_USER python -m unittest discover From 4b2a3846793834c1fadf87376f9af171b89cc10f Mon Sep 17 00:00:00 2001 From: brentru Date: Mon, 21 Dec 2020 11:51:45 -0500 Subject: [PATCH 081/116] addressing https://github.com/adafruit/Adafruit_IO_Python/issues/104 --- Adafruit_IO/client.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Adafruit_IO/client.py b/Adafruit_IO/client.py index 515789a..7ffe7d3 100644 --- a/Adafruit_IO/client.py +++ b/Adafruit_IO/client.py @@ -273,11 +273,15 @@ def feeds(self, feed=None): path = "feeds/{0}".format(feed) return Feed.from_dict(self._get(path)) - def create_feed(self, feed): + def create_feed(self, feed, group_key=None): """Create the specified feed. - :param string feed: Name/Key/ID of Adafruit IO feed. + :param string feed: Key of Adafruit IO feed. + :param group_key group: Group to place new feed in. """ path = "feeds/" + if group_key is not None: # create feed in a group + path="/groups/%s/feeds"%group_key + return Feed.from_dict(self._post(path, {"feed": feed._asdict()})) return Feed.from_dict(self._post(path, {"feed": feed._asdict()})) def delete_feed(self, feed): From 6f413d5a76a54884006b27372deeb39c03f162b2 Mon Sep 17 00:00:00 2001 From: brentru Date: Mon, 21 Dec 2020 11:52:39 -0500 Subject: [PATCH 082/116] add usage example --- examples/api/feeds.py | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/examples/api/feeds.py b/examples/api/feeds.py index 4a9885b..1f41b1c 100644 --- a/examples/api/feeds.py +++ b/examples/api/feeds.py @@ -1,6 +1,6 @@ # Simple example of sending and receiving values from Adafruit IO with the REST # API client. -# Author: Tony Dicola, Justin Cooper +# Author: Tony Dicola, Justin Cooper, Brent Rubell # Import Adafruit IO REST client. from Adafruit_IO import Client, Feed @@ -19,18 +19,28 @@ aio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY) # List all of your feeds +print("Obtaining user's feeds...") feeds = aio.feeds() -print(feeds) +print('Feeds: ', feeds) # Create a new feed +print("Creating new feed...") feed = Feed(name="PythonFeed") response = aio.create_feed(feed) -print(response) - -# List a specific feed -feed = aio.feeds(response.key) -print(feed) - +print("New feed: ", response) # Delete a feed aio.delete_feed(response.key) + +# Create feed in a group +feed = Feed(name="PythonGroupFeed") +group_key = "example" +print("Creating feed in group %s"%group_key) +response = aio.create_feed(feed, group_key) +print("New feed: ", response) + +print(response.key) + +# Delete a feed within a group +print("Deleting feed within group %s"%group_key) +aio.delete_feed(response.key) \ No newline at end of file From 839e41b494aaefcca047705b3bcbc384f23322de Mon Sep 17 00:00:00 2001 From: brentru Date: Mon, 21 Dec 2020 11:59:08 -0500 Subject: [PATCH 083/116] add unittest --- examples/api/feeds.py | 2 -- tests/test_client.py | 16 ++++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/examples/api/feeds.py b/examples/api/feeds.py index 1f41b1c..848bdda 100644 --- a/examples/api/feeds.py +++ b/examples/api/feeds.py @@ -39,8 +39,6 @@ response = aio.create_feed(feed, group_key) print("New feed: ", response) -print(response.key) - # Delete a feed within a group print("Deleting feed within group %s"%group_key) aio.delete_feed(response.key) \ No newline at end of file diff --git a/tests/test_client.py b/tests/test_client.py index 5f4737c..e8ceb72 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -188,6 +188,22 @@ def test_create_feed(self): result = io.create_feed(feed) self.assertEqual(result.name, 'testfeed') + def test_create_feed_in_group(self): + """Tests creating a feed within a group. + + """ + io = self.get_client() + self.ensure_feed_deleted(io, 'testfeed') + self.ensure_group_deleted(io, 'testgroup') + + group = io.create_group(Group(name='testgroup')) + feed = Feed(name='testfeed') + result = io.create_feed(feed, "testgroup") + self.assertEqual(result.key, "testfeed.testgroup") + + io.delete_feed(result.key) + io.delete_group('testgroup') + def test_feeds_returns_all_feeds(self): io = self.get_client() self.ensure_feed_deleted(io, 'testfeed') From 923d00f8e357d716f96e6d7dfc1688c103217157 Mon Sep 17 00:00:00 2001 From: brentru Date: Mon, 21 Dec 2020 12:01:31 -0500 Subject: [PATCH 084/116] bump ver --- Adafruit_IO/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Adafruit_IO/_version.py b/Adafruit_IO/_version.py index 3d67cd6..50062f8 100644 --- a/Adafruit_IO/_version.py +++ b/Adafruit_IO/_version.py @@ -1 +1 @@ -__version__ = "2.4.0" +__version__ = "2.5.0" From 2c7331585f8b8f89e58366158aea2202a45d974d Mon Sep 17 00:00:00 2001 From: brentru Date: Mon, 21 Dec 2020 12:02:09 -0500 Subject: [PATCH 085/116] reflect test expected --- tests/test_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_client.py b/tests/test_client.py index e8ceb72..c037411 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -199,7 +199,7 @@ def test_create_feed_in_group(self): group = io.create_group(Group(name='testgroup')) feed = Feed(name='testfeed') result = io.create_feed(feed, "testgroup") - self.assertEqual(result.key, "testfeed.testgroup") + self.assertEqual(result.key, "testgroup.testfeed") io.delete_feed(result.key) io.delete_group('testgroup') From e94e2d4bb31cc0cb884cc35b0aad1d2ba6aa3cb5 Mon Sep 17 00:00:00 2001 From: Luke McNinch Date: Tue, 23 Feb 2021 21:04:35 -0500 Subject: [PATCH 086/116] Add params keyword argument to _get method to enable use of URL parameters. --- Adafruit_IO/client.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Adafruit_IO/client.py b/Adafruit_IO/client.py index 7ffe7d3..e51036c 100644 --- a/Adafruit_IO/client.py +++ b/Adafruit_IO/client.py @@ -111,10 +111,11 @@ def _handle_error(response): def _compose_url(self, path): return '{0}/api/{1}/{2}/{3}'.format(self.base_url, 'v2', self.username, path) - def _get(self, path): + def _get(self, path, params=None): response = requests.get(self._compose_url(path), headers=self._headers({'X-AIO-Key': self.key}), - proxies=self.proxies) + proxies=self.proxies, + params=params) self._handle_error(response) return response.json() From e9afd5b317efe3e890606d3d7ce2fc95d3f4866c Mon Sep 17 00:00:00 2001 From: Luke McNinch Date: Fri, 26 Feb 2021 09:51:14 -0500 Subject: [PATCH 087/116] Implement a "max_results" parameter for the data method, to allow for retrieval of more than 1000 data points. This implementation is subject to an existing bug in the pagination link header in the API that will break when and if that bug is fixed. --- Adafruit_IO/client.py | 58 ++++++++++++++++++++++++++++++++++++++----- docs/data.rst | 13 ++++++++++ tests/test_client.py | 8 +++--- 3 files changed, 69 insertions(+), 10 deletions(-) diff --git a/Adafruit_IO/client.py b/Adafruit_IO/client.py index e51036c..b7fe210 100644 --- a/Adafruit_IO/client.py +++ b/Adafruit_IO/client.py @@ -22,6 +22,9 @@ import json import platform import pkg_resources +import re +from urllib.parse import urlparse +from urllib.parse import parse_qs # import logging import requests @@ -29,6 +32,8 @@ from .errors import RequestError, ThrottlingError from .model import Data, Feed, Group +API_PAGE_LIMIT = 1000 + # set outgoing version, pulled from setup.py version = pkg_resources.require("Adafruit_IO")[0].version default_headers = { @@ -60,6 +65,9 @@ def __init__(self, username, key, proxies=None, base_url='https://io.adafruit.co # constructing the path. self.base_url = base_url.rstrip('/') + # Store the last response of a get or post + self._last_response = None + @staticmethod def to_red(data): """Hex color feed to red channel. @@ -116,6 +124,7 @@ def _get(self, path, params=None): headers=self._headers({'X-AIO-Key': self.key}), proxies=self.proxies, params=params) + self._last_response = response self._handle_error(response) return response.json() @@ -125,6 +134,7 @@ def _post(self, path, data): 'Content-Type': 'application/json'}), proxies=self.proxies, data=json.dumps(data)) + self._last_response = response self._handle_error(response) return response.json() @@ -133,6 +143,7 @@ def _delete(self, path): headers=self._headers({'X-AIO-Key': self.key, 'Content-Type': 'application/json'}), proxies=self.proxies) + self._last_response = response self._handle_error(response) # Data functionality. @@ -232,17 +243,52 @@ def receive_previous(self, feed): path = "feeds/{0}/data/previous".format(feed) return Data.from_dict(self._get(path)) - def data(self, feed, data_id=None): + def data(self, feed, data_id=None, max_results=API_PAGE_LIMIT): """Retrieve data from a feed. If data_id is not specified then all the data for the feed will be returned in an array. :param string feed: Name/Key/ID of Adafruit IO feed. :param string data_id: ID of the piece of data to delete. + :param int max_results: The maximum number of results to return. To + return all data, set to None. """ - if data_id is None: - path = "feeds/{0}/data".format(feed) - return list(map(Data.from_dict, self._get(path))) - path = "feeds/{0}/data/{1}".format(feed, data_id) - return Data.from_dict(self._get(path)) + if data_id: + path = "feeds/{0}/data/{1}".format(feed, data_id) + return Data.from_dict(self._get(path)) + + params = {'limit': max_results} if max_results else None + data = [] + path = "feeds/{0}/data".format(feed) + while True: + data.extend(list(map(Data.from_dict, self._get(path, + params=params)))) + nlink = self.get_next_link() + if not nlink: + break + # Parse the link for the query parameters + params = parse_qs(urlparse(nlink).query) + if max_results: + if len(data) >= max_results: + break + params['limit'] = max_results - len(data) + return data + + def get_next_link(self): + """Parse the `next` page URL in the pagination Link header. + + This is necessary because of a bug in the API's implementation of the + link header. If that bug is fixed, the link would be accesible by + response.links['next']['url'] and this method would be broken. + + :return: The url for the next page of data + :rtype: str + """ + if not self._last_response: + return + link_header = self._last_response.headers['link'] + res = re.search('rel="next", <(.+?)>', link_header) + if not res: + return + return res.groups()[0] def create_data(self, feed, data): """Create a new row of data in the specified feed. diff --git a/docs/data.rst b/docs/data.rst index ed76bf9..d162082 100644 --- a/docs/data.rst +++ b/docs/data.rst @@ -33,6 +33,19 @@ You can get all of the data for a feed by using the ``data(feed)`` method. The r for d in data: print('Data value: {0}'.format(d.value)) +By default, the maximum number of data points returned is 1000. This limit can be changed by using the max_results parameter. + +.. code-block:: python + + # Get less than the default number of data points + data = aio.data('Test', max_results=100) + + # Get more than the default number of data points + data = aio.data('Test', max_results=2000) + + # Get all of the points + data = aio.data('Test', max_results=None) + You can also get a specific value by ID by using the ``feeds(feed, data_id)`` method. This will return a single piece of feed data with the provided data ID if it exists in the feed. The returned object will be an instance of the Data class. diff --git a/tests/test_client.py b/tests/test_client.py index c037411..adfb646 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -22,8 +22,8 @@ class TestClient(base.IOTestCase): # If your IP isn't put on the list of non-throttled IPs, uncomment the # function below to waste time between tests to prevent throttling. - #def tearDown(self): - time.sleep(30.0) + def tearDown(self): + time.sleep(30.0) # Helper Methods def get_client(self): @@ -48,7 +48,7 @@ def ensure_group_deleted(self, client, group): def empty_feed(self, client, feed): # Remove all the data from a specified feed (but don't delete the feed). - data = client.data(feed) + data = client.data(feed, max_results=None) for d in data: client.delete(feed, d.id) @@ -138,7 +138,7 @@ def test_create_data(self): data = Data(value=42) result = aio.create_data('testfeed', data) self.assertEqual(int(result.value), 42) - + def test_location_data(self): """receive_location """ From 3d267e97df31a26642bb41e0bbefac3197061659 Mon Sep 17 00:00:00 2001 From: Luke McNinch Date: Thu, 15 Apr 2021 22:09:10 -0400 Subject: [PATCH 088/116] Eliminate while True: by querying the feed count if max_results is None (caller is requesting all data). --- Adafruit_IO/client.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Adafruit_IO/client.py b/Adafruit_IO/client.py index b7fe210..1b31720 100644 --- a/Adafruit_IO/client.py +++ b/Adafruit_IO/client.py @@ -251,6 +251,9 @@ def data(self, feed, data_id=None, max_results=API_PAGE_LIMIT): :param int max_results: The maximum number of results to return. To return all data, set to None. """ + if max_results is None: + res = self._get(f'feeds/{feed}/details') + max_results = res['details']['data']['count'] if data_id: path = "feeds/{0}/data/{1}".format(feed, data_id) return Data.from_dict(self._get(path)) @@ -258,7 +261,7 @@ def data(self, feed, data_id=None, max_results=API_PAGE_LIMIT): params = {'limit': max_results} if max_results else None data = [] path = "feeds/{0}/data".format(feed) - while True: + while len(data) < max_results: data.extend(list(map(Data.from_dict, self._get(path, params=params)))) nlink = self.get_next_link() @@ -267,8 +270,6 @@ def data(self, feed, data_id=None, max_results=API_PAGE_LIMIT): # Parse the link for the query parameters params = parse_qs(urlparse(nlink).query) if max_results: - if len(data) >= max_results: - break params['limit'] = max_results - len(data) return data From d04765517640b1ebf2357b24f444e58981106892 Mon Sep 17 00:00:00 2001 From: Brent Rubell Date: Mon, 8 Nov 2021 10:16:23 -0500 Subject: [PATCH 089/116] Delete environmental_monitor.py --- examples/basics/environmental_monitor.py | 133 ----------------------- 1 file changed, 133 deletions(-) delete mode 100644 examples/basics/environmental_monitor.py diff --git a/examples/basics/environmental_monitor.py b/examples/basics/environmental_monitor.py deleted file mode 100644 index 21be64e..0000000 --- a/examples/basics/environmental_monitor.py +++ /dev/null @@ -1,133 +0,0 @@ -""" -'environmental_monitor.py' -=============================================================================== -Example of sending I2C sensor data -from multiple sensors to Adafruit IO. - -Tutorial Link: https://learn.adafruit.com/adafruit-io-air-quality-monitor - -Adafruit invests time and resources providing this open source code. -Please support Adafruit and open source hardware by purchasing -products from Adafruit! - -Author(s): Brent Rubell for Adafruit Industries -Copyright (c) 2018 Adafruit Industries -Licensed under the MIT license. -All text above must be included in any redistribution. - -Dependencies: - - Adafruit_Blinka (CircuitPython, on Pi.) - (https://github.com/adafruit/Adafruit_Blinka) - - Adafruit_CircuitPython_SGP30. - (https://github.com/adafruit/Adafruit_CircuitPython_SGP30) - - Adafruit_CircuitPython_VEML6070. - (https://github.com/adafruit/Adafruit_CircuitPython_VEML6070) - - Adafruit_CircuitPython_BME280. - (https://github.com/adafruit/Adafruit_CircuitPython_BME280) -""" - -# Import standard python modules -import time - -# import Adafruit Blinka -import board -import busio - -# import CircuitPython sensor libraries -import adafruit_sgp30 -import adafruit_veml6070 -import adafruit_bme280 - -# import Adafruit IO REST client -from Adafruit_IO import Client, Feed, RequestError - -# loop timeout, in seconds. -LOOP_DELAY = 10 - -# Set to your Adafruit IO key. -# Remember, your key is a secret, -# so make sure not to publish it when you publish this code! -ADAFRUIT_IO_KEY = 'YOUR_AIO_KEY' - -# Set to your Adafruit IO username. -# (go to https://accounts.adafruit.com to find your username) -ADAFRUIT_IO_USERNAME = 'YOUR_AIO_USERNAME' - -# Create an instance of the REST client -aio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY) - -try: # if we already have the feeds, assign them. - tvoc_feed = aio.feeds('tvoc') - eCO2_feed = aio.feeds('eco2') - uv_feed = aio.feeds('uv') - temperature_feed = aio.feeds('temperature') - humidity_feed = aio.feeds('humidity') - pressure_feed = aio.feeds('pressure') - altitude_feed = aio.feeds('altitude') -except RequestError: # if we don't, create and assign them. - tvoc_feed = aio.create_feed(Feed(name='tvoc')) - eCO2_feed = aio.create_feed(Feed(name='eco2')) - uv_feed = aio.create_feed(Feed(name='uv')) - temperature_feed = aio.create_feed(Feed(name='temperature')) - humidity_feed = aio.create_feed(Feed(name='humidity')) - pressure_feed = aio.create_feed(Feed(name='pressure')) - altitude_feed = aio.create_feed(Feed(name='altitude')) - -# Create busio I2C -i2c = busio.I2C(board.SCL, board.SDA, frequency=100000) -# Create VEML6070 object. -uv = adafruit_veml6070.VEML6070(i2c) -# Create BME280 object. -bme280 = adafruit_bme280.Adafruit_BME280_I2C(i2c) -bme280.sea_level_pressure = 1013.25 -# Create SGP30 object using I2C. -sgp30 = adafruit_sgp30.Adafruit_SGP30(i2c) -sgp30.iaq_init() -sgp30.set_iaq_baseline(0x8973, 0x8aae) - -# Sample VEML6070 -def sample_VEML(): - for _ in range(10): - uv_raw = uv.uv_raw - return uv_raw - -while True: - print('Reading sensors...') - # Read SGP30. - eCO2_data = sgp30.eCO2 - tvoc_data = sgp30.TVOC - - # Read VEML6070. - uv_data = sample_VEML() - - # Read BME280. - temp_data = bme280.temperature - # convert temperature (C->F) - temp_data = int(temp_data) * 1.8 + 32 - humid_data = bme280.humidity - pressure_data = bme280.pressure - alt_data = bme280.altitude - - print('sending data to adafruit io...') - # Send SGP30 Data to Adafruit IO. - print('eCO2:', eCO2_data) - aio.send(eCO2_feed.key, eCO2_data) - print('tvoc:', tvoc_data) - aio.send(tvoc_feed.key, tvoc_data) - time.sleep(2) - # Send VEML6070 Data to Adafruit IO. - print('UV Level: ', uv_data) - aio.send(uv_feed.key, uv_data) - time.sleep(2) - # Send BME280 Data to Adafruit IO. - print('Temperature: %0.1f C' % temp_data) - aio.send(temperature_feed.key, temp_data) - print("Humidity: %0.1f %%" % humid_data) - aio.send(humidity_feed.key, int(humid_data)) - time.sleep(2) - print("Pressure: %0.1f hPa" % pressure_data) - aio.send(pressure_feed.key, int(pressure_data)) - print("Altitude = %0.2f meters" % alt_data) - aio.send(altitude_feed.key, int(alt_data)) - # avoid timeout from adafruit io - time.sleep(LOOP_DELAY * 60) From 11bddacc67a15f7daa327fbcdf7ae85d010fae9f Mon Sep 17 00:00:00 2001 From: Doug Zobel Date: Tue, 16 Nov 2021 12:06:01 -0600 Subject: [PATCH 090/116] Add support for dashboards, blocks and layouts --- Adafruit_IO/__init__.py | 4 +- Adafruit_IO/client.py | 78 +++++++++++++++++++++++++- Adafruit_IO/model.py | 44 ++++++++++++++- examples/basics/dashboard.py | 88 +++++++++++++++++++++++++++++ tests/test_client.py | 105 ++++++++++++++++++++++++++++++++++- tests/test_model.py | 39 ++++++++++++- 6 files changed, 348 insertions(+), 10 deletions(-) create mode 100644 examples/basics/dashboard.py diff --git a/Adafruit_IO/__init__.py b/Adafruit_IO/__init__.py index fedf663..e34eb3d 100644 --- a/Adafruit_IO/__init__.py +++ b/Adafruit_IO/__init__.py @@ -21,5 +21,5 @@ from .client import Client from .mqtt_client import MQTTClient from .errors import AdafruitIOError, RequestError, ThrottlingError, MQTTError -from .model import Data, Feed, Group -from ._version import __version__ \ No newline at end of file +from .model import Data, Feed, Group, Dashboard, Block, Layout +from ._version import __version__ diff --git a/Adafruit_IO/client.py b/Adafruit_IO/client.py index 7ffe7d3..3912b50 100644 --- a/Adafruit_IO/client.py +++ b/Adafruit_IO/client.py @@ -27,7 +27,7 @@ import requests from .errors import RequestError, ThrottlingError -from .model import Data, Feed, Group +from .model import Data, Feed, Group, Dashboard, Block, Layout # set outgoing version, pulled from setup.py version = pkg_resources.require("Adafruit_IO")[0].version @@ -278,11 +278,13 @@ def create_feed(self, feed, group_key=None): :param string feed: Key of Adafruit IO feed. :param group_key group: Group to place new feed in. """ + f = feed._asdict() + del f['id'] # Don't pass id on create call path = "feeds/" if group_key is not None: # create feed in a group path="/groups/%s/feeds"%group_key - return Feed.from_dict(self._post(path, {"feed": feed._asdict()})) - return Feed.from_dict(self._post(path, {"feed": feed._asdict()})) + return Feed.from_dict(self._post(path, {"feed": f})) + return Feed.from_dict(self._post(path, {"feed": f})) def delete_feed(self, feed): """Delete the specified feed. @@ -315,3 +317,73 @@ def delete_group(self, group): """ path = "groups/{0}".format(group) self._delete(path) + + # Dashboard functionality. + def dashboards(self, dashboard=None): + """Retrieve a list of all dashboards, or the specified dashboard. + :param string dashboard: Key of Adafruit IO Dashboard. Defaults to None. + """ + if dashboard is None: + path = "dashboards/" + return list(map(Dashboard.from_dict, self._get(path))) + path = "dashboards/{0}".format(dashboard) + return Dashboard.from_dict(self._get(path)) + + def create_dashboard(self, dashboard): + """Create the specified dashboard. + :param Dashboard dashboard: Dashboard object to create + """ + path = "dashboards/" + return Dashboard.from_dict(self._post(path, dashboard._asdict())) + + def delete_dashboard(self, dashboard): + """Delete the specified dashboard. + :param string dashboard: Key of Adafruit IO Dashboard. + """ + path = "dashboards/{0}".format(dashboard) + self._delete(path) + + # Block functionality. + def blocks(self, dashboard, block=None): + """Retrieve a list of all blocks from a dashboard, or the specified block. + :param string dashboard: Key of Adafruit IO Dashboard. + :param string block: id of Adafruit IO Block. Defaults to None. + """ + if block is None: + path = "dashboards/{0}/blocks".format(dashboard) + return list(map(Block.from_dict, self._get(path))) + path = "dashboards/{0}/blocks/{1}".format(dashboard, block) + return Block.from_dict(self._get(path)) + + def create_block(self, dashboard, block): + """Create the specified block under the specified dashboard. + :param string dashboard: Key of Adafruit IO Dashboard. + :param Block block: Block object to create under dashboard + """ + path = "dashboards/{0}/blocks".format(dashboard) + return Block.from_dict(self._post(path, block._asdict())) + + def delete_block(self, dashboard, block): + """Delete the specified block. + :param string dashboard: Key of Adafruit IO Dashboard. + :param string block: id of Adafruit IO Block. + """ + path = "dashboards/{0}/blocks/{1}".format(dashboard, block) + self._delete(path) + + # Layout functionality. + def layouts(self, dashboard): + """Retrieve the layouts array from a dashboard + :param string dashboard: key of Adafruit IO Dashboard. + """ + path = "dashboards/{0}".format(dashboard) + dashboard = self._get(path) + return Layout.from_dict(dashboard['layouts']) + + def update_layout(self, dashboard, layout): + """Update the layout of the specified dashboard. + :param string dashboard: Key of Adafruit IO Dashboard. + :param Layout layout: Layout object to update under dashboard + """ + path = "dashboards/{0}/update_layouts".format(dashboard) + return Layout.from_dict(self._post(path, {'layouts': layout._asdict()})) diff --git a/Adafruit_IO/model.py b/Adafruit_IO/model.py index 04bb0c8..f0e4621 100644 --- a/Adafruit_IO/model.py +++ b/Adafruit_IO/model.py @@ -43,6 +43,7 @@ FEED_FIELDS = [ 'name', 'key', + 'id', 'description', 'unit_type', 'unit_symbol', @@ -61,6 +62,26 @@ 'properties', 'name' ] +DASHBOARD_FIELDS = [ 'name', + 'key', + 'description', + 'show_header', + 'color_mode', + 'block_borders', + 'header_image_url', + 'blocks' ] + +BLOCK_FIELDS = [ 'name', + 'id', + 'visual_type', + 'properties', + 'block_feeds' ] + +LAYOUT_FIELDS = ['xl', + 'lg', + 'md', + 'sm', + 'xs' ] # These are very simple data model classes that are based on namedtuple. This is # to keep the classes simple and prevent any confusion around updating data @@ -71,15 +92,24 @@ Data = namedtuple('Data', DATA_FIELDS) Feed = namedtuple('Feed', FEED_FIELDS) Group = namedtuple('Group', GROUP_FIELDS) - +Dashboard = namedtuple('Dashboard', DASHBOARD_FIELDS) +Block = namedtuple('Block', BLOCK_FIELDS) +Layout = namedtuple('Layout', LAYOUT_FIELDS) # Magic incantation to make all parameters to the initializers optional with a # default value of None. Group.__new__.__defaults__ = tuple(None for x in GROUP_FIELDS) Data.__new__.__defaults__ = tuple(None for x in DATA_FIELDS) +Layout.__new__.__defaults__ = tuple(None for x in LAYOUT_FIELDS) + +# explicitly set dashboard values so that 'color_mode' is 'dark' +Dashboard.__new__.__defaults__ = (None, None, None, False, "dark", True, None, None) + +# explicitly set block values so 'properties' is a dictionary +Block.__new__.__defaults__ = (None, None, None, {}, None) # explicitly set feed values -Feed.__new__.__defaults__ = (None, None, None, None, None, 'ON', 'Private', None, None, None) +Feed.__new__.__defaults__ = (None, None, None, None, None, None, 'ON', 'Private', None, None, None) # Define methods to convert from dicts to the data types. def _from_dict(cls, data): @@ -103,7 +133,17 @@ def _group_from_dict(cls, data): return cls(**params) +def _dashboard_from_dict(cls, data): + params = {x: data.get(x, None) for x in cls._fields} + # Parse the blocks if they're provided and generate block instances. + params['blocks'] = tuple(map(Feed.from_dict, data.get('blocks', []))) + return cls(**params) + + # Now add the from_dict class methods defined above to the data types. Data.from_dict = classmethod(_from_dict) Feed.from_dict = classmethod(_feed_from_dict) Group.from_dict = classmethod(_group_from_dict) +Dashboard.from_dict = classmethod(_dashboard_from_dict) +Block.from_dict = classmethod(_from_dict) +Layout.from_dict = classmethod(_from_dict) diff --git a/examples/basics/dashboard.py b/examples/basics/dashboard.py new file mode 100644 index 0000000..8a5bc61 --- /dev/null +++ b/examples/basics/dashboard.py @@ -0,0 +1,88 @@ +""" +'dashboard.py' +========================================= +Creates a dashboard with 3 blocks and feed it data + +Author(s): Doug Zobel +""" +from time import sleep +from random import randrange +from Adafruit_IO import Client, Feed, Block, Dashboard, Layout + +# Set to your Adafruit IO key. +# Remember, your key is a secret, +# so make sure not to publish it when you publish this code! +ADAFRUIT_IO_USERNAME = '' + +# Set to your Adafruit IO username. +# (go to https://accounts.adafruit.com to find your username) +ADAFRUIT_IO_KEY = '' + +# Create an instance of the REST client. +aio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY) + +# Create a new feed named 'Dashboard Data' under the default group +feed = aio.create_feed(Feed(name="Dashboard Data"), "default") + +# Fetch group info (group.id needed when adding feeds to blocks) +group = aio.groups("default") + +# Create a new dasbhoard named 'Example Dashboard' +dashboard = aio.create_dashboard(Dashboard(name="Example Dashboard")) + +# Create a line_chart +linechart = Block(name="Linechart Data", + visual_type = 'line_chart', + properties = { + "gridLines": True, + "historyHours": "2"}, + block_feeds = [{ + "group_id": group.id, + "feed_id": feed.id + }]) +linechart = aio.create_block(dashboard.key, linechart) + +# Create a gauge +gauge = Block(name="Gauge Data", + visual_type = 'gauge', + block_feeds = [{ + "group_id": group.id, + "feed_id": feed.id + }]) +gauge = aio.create_block(dashboard.key, gauge) + +# Create a text stream +stream = Block(name="Stream Data", + visual_type = 'stream', + properties = { + "fontSize": "12", + "fontColor": "#63de00", + "showGroupName": "no"}, + block_feeds = [{ + "group_id": group.id, + "feed_id": feed.id + }]) +stream = aio.create_block(dashboard.key, stream) + +# Update the large layout to: +# |----------------| +# | Line Chart | +# |----------------| +# | Gauge | Stream | +# |----------------| +layout = Layout(lg = [ + {'x': 0, 'y': 0, 'w': 16, 'h': 4, 'i': str(linechart.id)}, + {'x': 0, 'y': 4, 'w': 8, 'h': 4, 'i': str(gauge.id)}, + {'x': 8, 'y': 4, 'w': 8, 'h': 4, 'i': str(stream.id)}]) +aio.update_layout(dashboard.key, layout) + +print("Dashboard created at: " + + "https://io.adafruit.com/{0}/dashboards/{1}".format(ADAFRUIT_IO_USERNAME, + dashboard.key)) +# Now send some data +value = 0 +while True: + value = (value + randrange(0, 10)) % 100 + print('sending data: ', value) + aio.send_data(feed.key, value) + sleep(3) diff --git a/tests/test_client.py b/tests/test_client.py index c037411..b25b24d 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -3,7 +3,7 @@ import time import unittest -from Adafruit_IO import Client, Data, Feed, Group, RequestError +from Adafruit_IO import Client, Data, Feed, Group, Dashboard, Block, Layout, RequestError import base @@ -46,6 +46,22 @@ def ensure_group_deleted(self, client, group): # Swallow the error if the group doesn't exist. pass + def ensure_dashboard_deleted(self, client, dashboard): + # Delete the specified dashboard if it exists. + try: + client.delete_dashboard(dashboard) + except RequestError: + # Swallow the error if the dashboard doesn't exist. + pass + + def ensure_block_deleted(self, client, dashboard, block): + # Delete the specified block if it exists. + try: + client.delete_block(dashboard, block) + except RequestError: + # Swallow the error if the block doesn't exist. + pass + def empty_feed(self, client, feed): # Remove all the data from a specified feed (but don't delete the feed). data = client.data(feed) @@ -269,3 +285,90 @@ def test_receive_group_by_key(self): group = io.create_group(Group(name='grouprx')) response = io.groups(group.key) self.assertEqual(response.key, 'grouprx') + + # Test Dashboard Functionality + def test_dashboard_create_dashboard(self): + io = self.get_client() + self.ensure_dashboard_deleted(io, 'dashtest') + response = io.create_dashboard(Dashboard(name='dashtest')) + self.assertEqual(response.name, 'dashtest') + + def test_dashboard_returns_all_dashboards(self): + io = self.get_client() + self.ensure_dashboard_deleted(io, 'dashtest') + dashboard = io.create_dashboard(Dashboard(name='dashtest')) + response = io.dashboards() + self.assertGreaterEqual(len(response), 1) + + def test_dashboard_returns_requested_feed(self): + io = self.get_client() + self.ensure_dashboard_deleted(io, 'dashtest') + dashboard = io.create_dashboard(Dashboard(name='dashtest')) + response = io.dashboards('dashtest') + self.assertEqual(response.name, 'dashtest') + + # Test Block Functionality + def test_block_create_block(self): + io = self.get_client() + self.ensure_block_deleted(io, 'dashtest', 'blocktest') + self.ensure_dashboard_deleted(io, 'dashtest') + dash = io.create_dashboard(Dashboard(name='dashtest')) + block = io.create_block(dash.key, Block(name='blocktest', + visual_type = 'line_chart')) + self.assertEqual(block.name, 'blocktest') + io.delete_block(dash.key, block.id) + io.delete_dashboard(dash.key) + + def test_dashboard_returns_all_blocks(self): + io = self.get_client() + self.ensure_block_deleted(io, 'dashtest', 'blocktest') + self.ensure_dashboard_deleted(io, 'dashtest') + dash = io.create_dashboard(Dashboard(name='dashtest')) + block = io.create_block(dash.key, Block(name='blocktest', + visual_type = 'line_chart')) + response = io.blocks(dash.key) + self.assertEqual(len(response), 1) + io.delete_block(dash.key, block.id) + io.delete_dashboard(dash.key) + + def test_dashboard_returns_requested_block(self): + io = self.get_client() + self.ensure_block_deleted(io, 'dashtest', 'blocktest') + self.ensure_dashboard_deleted(io, 'dashtest') + dash = io.create_dashboard(Dashboard(name='dashtest')) + block = io.create_block(dash.key, Block(name='blocktest', + visual_type = 'line_chart')) + response = io.blocks(dash.key, block.id) + self.assertEqual(response.name, 'blocktest') + io.delete_block(dash.key, block.id) + io.delete_dashboard(dash.key) + + # Test Layout Functionality + def test_layout_returns_all_layouts(self): + io = self.get_client() + self.ensure_block_deleted(io, 'dashtest', 'blocktest') + self.ensure_dashboard_deleted(io, 'dashtest') + dash = io.create_dashboard(Dashboard(name='dashtest')) + block = io.create_block(dash.key, Block(name='blocktest', + visual_type = 'line_chart')) + response = io.layouts(dash.key) + self.assertEqual(len(response), 5) # 5 layouts: xs, sm, md, lg, xl + self.assertEqual(len(response.lg), 1) + io.delete_block(dash.key, block.id) + io.delete_dashboard(dash.key) + + def test_layout_update_layout(self): + io = self.get_client() + self.ensure_block_deleted(io, 'dashtest', 'blocktest') + self.ensure_dashboard_deleted(io, 'dashtest') + dash = io.create_dashboard(Dashboard(name='dashtest')) + block = io.create_block(dash.key, Block(name='blocktest', + visual_type = 'line_chart')) + layout = Layout(lg = [ + {'x': 0, 'y': 0, 'w': 16, 'h': 4, 'i': str(block.id)}]) + io.update_layout(dash.key, layout) + response = io.layouts(dash.key) + self.assertEqual(len(response.lg), 1) + self.assertEqual(response.lg[0]['w'], 16) + io.delete_block(dash.key, block.id) + io.delete_dashboard(dash.key) diff --git a/tests/test_model.py b/tests/test_model.py index 623bd99..02b105e 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -18,7 +18,7 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from Adafruit_IO import Data, Feed, Group +from Adafruit_IO import Data, Feed, Group, Dashboard, Block, Layout import base @@ -45,11 +45,12 @@ def test_data_properties_are_optional(self): def test_feeds_have_explicitly_set_values(self): """ Let's make sure feeds are explicitly set from within the model: - Feed.__new__.__defaults__ = (None, None, None, None, None, 'ON', 'Private', None, None, None) + Feed.__new__.__defaults__ = (None, None, None, None, None, None, 'ON', 'Private', None, None, None) """ feed = Feed(name='foo') self.assertEqual(feed.name, 'foo') self.assertIsNone(feed.key) + self.assertIsNone(feed.id) self.assertIsNone(feed.description) self.assertIsNone(feed.unit_type) self.assertIsNone(feed.unit_symbol) @@ -69,6 +70,40 @@ def test_group_properties_are_optional(self): self.assertIsNone(group.feeds) self.assertIsNone(group.properties) + """ Let's make sure feeds are explicitly set from within the model: + Dashboard.__new__.__defaults__ = (None, None, None, False, "dark", True, None, None) + + """ + def test_dashboard_have_explicitly_set_values(self): + dashboard = Dashboard(name="foo") + self.assertEqual(dashboard.name, 'foo') + self.assertIsNone(dashboard.key) + self.assertIsNone(dashboard.description) + self.assertFalse(dashboard.show_header) + self.assertEqual(dashboard.color_mode, 'dark') + self.assertTrue(dashboard.block_borders) + self.assertIsNone(dashboard.header_image_url) + self.assertIsNone(dashboard.blocks) + + """ Let's make sure feeds are explicitly set from within the model: + Block.__new__.__defaults__ = (None, None, None {}, None) + """ + def test_block_have_explicitly_set_values(self): + block = Block(name="foo") + self.assertEqual(block.name, 'foo') + self.assertIsNone(block.id) + self.assertIsNone(block.visual_type) + self.assertEqual(type(block.properties), dict) + self.assertEqual(len(block.properties), 0) + self.assertIsNone(block.block_feeds) + + def test_layout_properties_are_optional(self): + layout = Layout() + self.assertIsNone(layout.xl) + self.assertIsNone(layout.lg) + self.assertIsNone(layout.md) + self.assertIsNone(layout.sm) + self.assertIsNone(layout.xs) def test_from_dict_ignores_unknown_items(self): data = Data.from_dict({'value': 'foo', 'feed_id': 10, 'unknown_param': 42}) From 09c021b0f5a2481cac0104459b33e82e761cc113 Mon Sep 17 00:00:00 2001 From: Doug Zobel Date: Thu, 18 Nov 2021 09:38:43 -0600 Subject: [PATCH 091/116] Fix Dashboard parsing of Block objects --- Adafruit_IO/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Adafruit_IO/model.py b/Adafruit_IO/model.py index f0e4621..51d5633 100644 --- a/Adafruit_IO/model.py +++ b/Adafruit_IO/model.py @@ -136,7 +136,7 @@ def _group_from_dict(cls, data): def _dashboard_from_dict(cls, data): params = {x: data.get(x, None) for x in cls._fields} # Parse the blocks if they're provided and generate block instances. - params['blocks'] = tuple(map(Feed.from_dict, data.get('blocks', []))) + params['blocks'] = tuple(map(Block.from_dict, data.get('blocks', []))) return cls(**params) From c5d1975fcc65d71e70264bc7f8e731c347c563e4 Mon Sep 17 00:00:00 2001 From: Luke McNinch Date: Mon, 22 Nov 2021 20:19:47 -0500 Subject: [PATCH 092/116] Address issue 137: receive_time parses week day (wday) as one day off of what is expected by Python. Implement a method to parse the dict returned by the server and correct the week day. --- Adafruit_IO/client.py | 17 ++++++++++++++--- tests/test_client.py | 42 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 52 insertions(+), 7 deletions(-) diff --git a/Adafruit_IO/client.py b/Adafruit_IO/client.py index 7ffe7d3..fb1cb41 100644 --- a/Adafruit_IO/client.py +++ b/Adafruit_IO/client.py @@ -18,6 +18,7 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +import time from time import struct_time import json import platform @@ -182,9 +183,19 @@ def receive_time(self): https://docs.python.org/3.7/library/time.html#time.struct_time """ path = 'integrations/time/struct.json' - time = self._get(path) - return struct_time((time['year'], time['mon'], time['mday'], time['hour'], - time['min'], time['sec'], time['wday'], time['yday'], time['isdst'])) + return self._parse_time_struct(self._get(path)) + + @staticmethod + def _parse_time_struct(time_dict: dict) -> time.struct_time: + """Parse the time data returned by the server and return a time_struct + + Corrects for the weekday returned by the server in Sunday=0 format + (Python expects Monday=0) + """ + wday = (time_dict['wday'] - 1) % 7 + return struct_time((time_dict['year'], time_dict['mon'], time_dict['mday'], + time_dict['hour'], time_dict['min'], time_dict['sec'], + wday, time_dict['yday'], time_dict['isdst'])) def receive_weather(self, weather_id=None): """Adafruit IO Weather Service, Powered by Dark Sky diff --git a/tests/test_client.py b/tests/test_client.py index c037411..c1190bb 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -23,7 +23,7 @@ class TestClient(base.IOTestCase): # If your IP isn't put on the list of non-throttled IPs, uncomment the # function below to waste time between tests to prevent throttling. #def tearDown(self): - time.sleep(30.0) + # time.sleep(30.0) # Helper Methods def get_client(self): @@ -138,7 +138,7 @@ def test_create_data(self): data = Data(value=42) result = aio.create_data('testfeed', data) self.assertEqual(int(result.value), 42) - + def test_location_data(self): """receive_location """ @@ -160,11 +160,41 @@ def test_time_data(self): """receive_time """ aio = self.get_client() - time = aio.receive_time() + server_time = aio.receive_time() # Check that each value is rx'd properly # (should never be None type) - for time_data in time: + for time_data in server_time: self.assertIsNotNone(time_data) + # Check that the week day was interpreted properly + adjusted_time = time.localtime(time.mktime(server_time)) + self.assertEqual(server_time.tm_wday, adjusted_time.tm_wday) + + def test_parse_time_struct(self): + """Ensure the _parse_time_struct method properly handles all 7 + week days. Particularly important to make sure Sunday is 6, + not -1""" + # Zero time is a dictionary as would be provided by server + # (wday is one higher than it should be) + zero_time = {'year': 1970, + 'mon': 1, + 'mday': 1, + 'hour': 0, + 'min': 0, + 'sec': 0, + 'wday': 4, + 'yday': 1, + 'isdst': 0} + + # Create a good struct for each day of the week and make sure + # the server-style dictionary is parsed correctly + for k in range(7): + real_struct = time.gmtime(k * 86400) + d = zero_time.copy() + d['mday'] += k + d['wday'] += k + d['yday'] += k + newd = Client._parse_time_struct(d) + self.assertEqual(newd.tm_wday, real_struct.tm_wday) # Test Feed Functionality def test_append_by_feed_name(self): @@ -269,3 +299,7 @@ def test_receive_group_by_key(self): group = io.create_group(Group(name='grouprx')) response = io.groups(group.key) self.assertEqual(response.key, 'grouprx') + + +if __name__ == "__main__": + unittest.main() From 6587c92bb6fd124c36f9fabe66a2325aeea8dd97 Mon Sep 17 00:00:00 2001 From: Brent Rubell Date: Mon, 29 Nov 2021 10:28:49 -0500 Subject: [PATCH 093/116] Update build.yml https://dev.to/petrsvihlik/using-environment-protection-rules-to-secure-secrets-when-building-external-forks-with-pullrequesttarget-hci --- .github/workflows/build.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e027b55..17e93f9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,9 +3,20 @@ name: Build-CI on: [pull_request, push] jobs: + + approve: # First step + runs-on: ubuntu-latest + + steps: + - name: Approve + run: echo For security reasons, all pull requests to this repository need to be approved first before running any automated CI. + build: runs-on: ubuntu-latest + needs: [approve] # Require the first step to finish + environment: + name: IO steps: - uses: actions/checkout@v2 From c636eda6eaff496e13c6c01f8d8b199deac93555 Mon Sep 17 00:00:00 2001 From: ALfuhrmann Date: Mon, 17 Jan 2022 15:00:44 +0100 Subject: [PATCH 094/116] copy-and-paste error in batch sending The "Send Batch Data" did not actually contain an "aio.send_batch_data" call. Fixed it. --- docs/data.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/data.rst b/docs/data.rst index ed76bf9..f59d0e4 100644 --- a/docs/data.rst +++ b/docs/data.rst @@ -79,7 +79,9 @@ Data can be created after you create a feed, by using the ``send_batch_data(feed # Create a data items in the 'Test' feed. data_list = [Data(value=10), Data(value=11)] - aio.create_data('Test', data) + # send batch data + aio.send_batch_data(temperature.key, data_list) + Receive Data From b53348282cde3951ea9dda5f69f01b0cde35fafb Mon Sep 17 00:00:00 2001 From: Luke McNinch Date: Mon, 17 Jan 2022 10:36:08 -0500 Subject: [PATCH 095/116] Lower default page limit from 1000 to 100. --- Adafruit_IO/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Adafruit_IO/client.py b/Adafruit_IO/client.py index cf46a03..08ffe6c 100644 --- a/Adafruit_IO/client.py +++ b/Adafruit_IO/client.py @@ -33,7 +33,7 @@ from .errors import RequestError, ThrottlingError from .model import Data, Feed, Group, Dashboard, Block, Layout -API_PAGE_LIMIT = 1000 +DEFAULT_PAGE_LIMIT = 100 # set outgoing version, pulled from setup.py version = pkg_resources.require("Adafruit_IO")[0].version @@ -254,7 +254,7 @@ def receive_previous(self, feed): path = "feeds/{0}/data/previous".format(feed) return Data.from_dict(self._get(path)) - def data(self, feed, data_id=None, max_results=API_PAGE_LIMIT): + def data(self, feed, data_id=None, max_results=DEFAULT_PAGE_LIMIT): """Retrieve data from a feed. If data_id is not specified then all the data for the feed will be returned in an array. :param string feed: Name/Key/ID of Adafruit IO feed. From d54398372adf4e0f17f7b22281d6aa1253380782 Mon Sep 17 00:00:00 2001 From: Alec Delaney <89490472+tekktrik@users.noreply.github.com> Date: Sun, 14 Aug 2022 22:54:04 -0400 Subject: [PATCH 096/116] Fix CI badge image --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index edb4bfa..04aaccd 100644 --- a/README.rst +++ b/README.rst @@ -9,7 +9,7 @@ Adafruit IO Python :target: https://adafru.it/discord :alt: Chat -.. image:: https://github.com/adafruit/Adafruit_IO_Python/workflows/Build%20CI/badge.svg +.. image:: https://github.com/adafruit/Adafruit_IO_Python/workflows/Build-CI/badge.svg :target: https://github.com/adafruit/Adafruit_IO_Python/actions :alt: Build Status From 3e8c8b3de1beb8db7ebd61ac2e616864a9e05a0c Mon Sep 17 00:00:00 2001 From: Pjurek3 Date: Sun, 20 Nov 2022 16:01:43 -0800 Subject: [PATCH 097/116] Update feeds.rst #146 - updated Feeds documentation for correct `Client` interface --- docs/feeds.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/feeds.rst b/docs/feeds.rst index 06adf5b..75cc525 100644 --- a/docs/feeds.rst +++ b/docs/feeds.rst @@ -11,7 +11,7 @@ Create a feed by constructing a Feed instance with at least a name specified, an # Import library and create instance of REST client. from Adafruit_IO import Client, Feed - aio = Client('YOUR ADAFRUIT IO KEY') + aio = Client('YOUR ADAFRUIT IO USERNAME', 'YOUR ADAFRUIT IO KEY') # Create Feed object with name 'Foo'. feed = Feed(name='Foo') @@ -30,7 +30,7 @@ You can get a list of your feeds by using the ``feeds()`` method which will retu # Import library and create instance of REST client. from Adafruit_IO import Client - aio = Client('YOUR ADAFRUIT IO KEY') + aio = Client('YOUR ADAFRUIT IO USERNAME', 'YOUR ADAFRUIT IO KEY') # Get list of feeds. feeds = aio.feeds() @@ -45,7 +45,7 @@ Alternatively you can retrieve the metadata for a single feed by calling ``feeds # Import library and create instance of REST client. from Adafruit_IO import Client - aio = Client('YOUR ADAFRUIT IO KEY') + aio = Client('YOUR ADAFRUIT IO USERNAME', 'YOUR ADAFRUIT IO KEY') # Get feed 'Foo' feed = aio.feeds('Foo') From 3fa2cf48c4d53e59292e101d2e50d83918650371 Mon Sep 17 00:00:00 2001 From: brentru Date: Mon, 6 Nov 2023 18:34:15 -0500 Subject: [PATCH 098/116] update example --- examples/basics/temp_humidity.py | 71 ++++++++++++++++++++------------ 1 file changed, 45 insertions(+), 26 deletions(-) diff --git a/examples/basics/temp_humidity.py b/examples/basics/temp_humidity.py index a4fa881..618d5f6 100644 --- a/examples/basics/temp_humidity.py +++ b/examples/basics/temp_humidity.py @@ -1,8 +1,7 @@ """ 'temp_humidity.py' ================================== -Example of sending analog sensor -values to an Adafruit IO feed. +Example of sending temperature and humidity data to Adafruit IO Author(s): Brent Rubell @@ -11,24 +10,27 @@ Dependencies: - Adafruit IO Python Client (https://github.com/adafruit/io-client-python) - - Adafruit_Python_DHT - (https://github.com/adafruit/Adafruit_Python_DHT) + - Adafruit_CircuitPython_AHTx0 + (https://github.com/adafruit/Adafruit_CircuitPython_AHTx0) """ # import standard python modules. import time -# import adafruit dht library. -import Adafruit_DHT +# import adafruit-blinka modules +import board # import Adafruit IO REST client. -from Adafruit_IO import Client, Feed +from Adafruit_IO import Client, Feed, RequestError -# Delay in-between sensor readings, in seconds. -DHT_READ_TIMEOUT = 5 +# Import AHTx0 library +import adafruit_ahtx0 -# Pin connected to DHT22 data pin -DHT_DATA_PIN = 26 +# Set true to send tempertaure data in degrees fahrenheit ('f')? +USE_DEGREES_F = False + +# Time between sensor reads, in seconds +READ_TIMEOUT = 60 # Set to your Adafruit IO key. # Remember, your key is a secret, @@ -42,23 +44,40 @@ # Create an instance of the REST client. aio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY) -# Set up Adafruit IO Feeds. -temperature_feed = aio.feeds('temperature') -humidity_feed = aio.feeds('humidity') +# Assign a temperature feed, if one exists already +try: + temperature_feed = aio.feeds('temperature') +except RequestError: # Doesn't exist, create a new feed + feed_temp = Feed(name="temperature") + temperature_feed = aio.create_feed(feed_temp) + +# Assign a humidity feed, if one exists already +try: + humidity_feed = aio.feeds('humidity') +except RequestError: # Doesn't exist, create a new feed + feed_humid = Feed(name="humidity") + humidity_feed = aio.create_feed(feed_humid) -# Set up DHT22 Sensor. -dht22_sensor = Adafruit_DHT.DHT22 +# Initialize the board's default I2C bus +i2c = board.I2C() # uses board.SCL and board.SDA +# Initialize AHT20 using the default address (0x38) and the board's default i2c bus +sensor = adafruit_ahtx0.AHTx0(i2c) while True: - humidity, temperature = Adafruit_DHT.read_retry(dht22_sensor, DHT_DATA_PIN) - if humidity is not None and temperature is not None: - print('Temp={0:0.1f}*C Humidity={1:0.1f}%'.format(temperature, humidity)) - # Send humidity and temperature feeds to Adafruit IO - temperature = '%.2f'%(temperature) - humidity = '%.2f'%(humidity) - aio.send(temperature_feed.key, str(temperature)) - aio.send(humidity_feed.key, str(humidity)) + temperature = sensor.temperature + humidity = sensor.relative_humidity + if USE_DEGREES_F: + temperature = temperature * 9.0 / 5.0 + 32.0 + print('Temp={0:0.1f}*F'.format(temperature)) else: - print('Failed to get DHT22 Reading, trying again in ', DHT_READ_TIMEOUT, 'seconds') + print('Temp={0:0.1f}*C'.format(temperature)) + print('Humidity={1:0.1f}%'.format(humidity)) + # Format sensor data as string for sending to Adafruit IO + temperature = '%.2f'%(temperature) + humidity = '%.2f'%(humidity) + # Send humidity and temperature data to Adafruit IO + aio.send(temperature_feed.key, str(temperature)) + aio.send(humidity_feed.key, str(humidity)) + # Timeout to avoid flooding Adafruit IO - time.sleep(DHT_READ_TIMEOUT) + time.sleep(READ_TIMEOUT) From 13a875809a1a4f82e555e5bdd3924aeaa0218270 Mon Sep 17 00:00:00 2001 From: brentru Date: Mon, 6 Nov 2023 18:40:17 -0500 Subject: [PATCH 099/116] use new action --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 17e93f9..7a86ba8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,7 +21,7 @@ jobs: - uses: actions/checkout@v2 - name: Set up Python 3.6 - uses: actions/setup-python@v1 + uses: actions/setup-python@v4 with: python-version: 3.6 From 05ebb1ce24a5081f9e5fb83100ea0e4d2f0f8ae2 Mon Sep 17 00:00:00 2001 From: brentru Date: Mon, 6 Nov 2023 18:41:22 -0500 Subject: [PATCH 100/116] drop 3.6 req, EOL --- .github/workflows/build.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7a86ba8..cbc5916 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,10 +20,8 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Set up Python 3.6 + - name: Set up Python uses: actions/setup-python@v4 - with: - python-version: 3.6 - name: Install dependencies run: | From 787150567f00e6e701243a6f8a13dbc2f6006ffa Mon Sep 17 00:00:00 2001 From: brentru Date: Mon, 6 Nov 2023 18:42:40 -0500 Subject: [PATCH 101/116] use latest python ver --- .github/workflows/build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cbc5916..cbeaa1a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,6 +22,8 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 + with: + python-version: '3.10' - name: Install dependencies run: | From 777a96102956fc660812588690d032887f6c1244 Mon Sep 17 00:00:00 2001 From: brentru Date: Mon, 6 Nov 2023 18:49:29 -0500 Subject: [PATCH 102/116] use english configuration --- docs/conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 848ccc3..42d0e3a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -29,7 +29,7 @@ # General information about the project. project = u'adafruit-io-python' -copyright = u'2019 Adafruit Industries' +copyright = u'2023 Adafruit Industries' author = u'Adafruit Industries' # The version info for the project you're documenting, acts as replacement for @@ -46,7 +46,7 @@ # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = "en" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. From f95539a412bb7533839752c471a32fda69e3a128 Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 8 Nov 2023 15:57:58 -0500 Subject: [PATCH 103/116] echo for debug --- .github/workflows/build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cbeaa1a..e5565b6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -41,6 +41,8 @@ jobs: SECRET_IO_KEY: ${{ secrets.CI_IO_KEY }} SECRET_IO_USER: ${{ secrets.CI_IO_USERNAME }} run: | + echo "Secret key length: ${#SECRET_IO_KEY}" + echo "Secret username length: ${#SECRET_IO_USER}" cd tests/ ADAFRUIT_IO_KEY=$SECRET_IO_KEY ADAFRUIT_IO_USERNAME=$SECRET_IO_USER python -m unittest discover cd .. From 0d1146d2ee7bfb929c8abb46ab9477802059bdae Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 8 Nov 2023 16:02:03 -0500 Subject: [PATCH 104/116] target? --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e5565b6..eb9a5f1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,6 +1,6 @@ name: Build-CI -on: [pull_request, push] +on: [pull_request_target, push] jobs: From 15e1f44e7a2a867698efb0470367c46d682bc65b Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 8 Nov 2023 16:07:02 -0500 Subject: [PATCH 105/116] only build on master push --- .github/workflows/build.yml | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index eb9a5f1..5c410bd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,20 +1,14 @@ name: Build-CI -on: [pull_request_target, push] +on: + push: + branches: + - master jobs: - approve: # First step - runs-on: ubuntu-latest - - steps: - - name: Approve - run: echo For security reasons, all pull requests to this repository need to be approved first before running any automated CI. - build: runs-on: ubuntu-latest - - needs: [approve] # Require the first step to finish environment: name: IO steps: From 31372ce8779c81bc794a6a88bfcf2df7e2d6cffe Mon Sep 17 00:00:00 2001 From: brentru Date: Wed, 8 Nov 2023 16:43:41 -0500 Subject: [PATCH 106/116] add neopixel example --- examples/basics/neopixel.py | 69 +++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 examples/basics/neopixel.py diff --git a/examples/basics/neopixel.py b/examples/basics/neopixel.py new file mode 100644 index 0000000..5c6b241 --- /dev/null +++ b/examples/basics/neopixel.py @@ -0,0 +1,69 @@ +""" +`rgb_led.py` +======================================================================= +Control a NeoPixel RGB LED using Adafruit IO and Python + +Tutorial Link: https://learn.adafruit.com/adafruit-io-basics-color + +Adafruit invests time and resources providing this open source code. +Please support Adafruit and open source hardware by purchasing +products from Adafruit! + +Author(s): Brent Rubell for Adafruit Industries +Copyright (c) 2023 Adafruit Industries +Licensed under the MIT license. +All text above must be included in any redistribution. + +Dependencies: + - Adafruit_Blinka + (https://github.com/adafruit/Adafruit_Blinka) + - Adafruit_CircuitPython_NeoPixel + (https://github.com/adafruit/Adafruit_CircuitPython_NeoPixel) +""" +import time +import board +import neopixel +from Adafruit_IO import Client, Feed, RequestError + +# Choose an open pin connected to the Data In of the NeoPixel strip, i.e. board.D18 +# NeoPixels must be connected to D10, D12, D18 or D21 to work. +pixel_pin = board.D18 + +# The number of NeoPixels +num_pixels = 1 + +# The order of the pixel colors - RGB or GRB. Some NeoPixels have red and green reversed! +ORDER = neopixel.GRB + +pixels = neopixel.NeoPixel( + pixel_pin, num_pixels, brightness=0.2, auto_write=False, pixel_order=ORDER +) + +# Set to your Adafruit IO key. +# Remember, your key is a secret, +# so make sure not to publish it when you publish this code! +ADAFRUIT_IO_KEY = 'YOUR_AIO_KEY' + +# Set to your Adafruit IO username. +# (go to https://accounts.adafruit.com to find your username) +ADAFRUIT_IO_USERNAME = 'YOUR_AIO_USERNAME' + +# Create an instance of the REST client. +aio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY) + +try: # if we have a 'color' feed + color = aio.feeds('color') +except RequestError: # create an `color` feed + feed = Feed(name='color') + color = aio.create_feed(feed) + +while True: + # get the value of the Adafruit IO `color` feed + color_val = aio.receive(color.key) + # Print hex value + print('Received Color HEX: ', color_val) + pixels.fill(color_val.value) + pixels.show() + + # let's sleep/wait so we don't flood adafruit io's servers with requests + time.sleep(3) From bae611bb49f06012f32ab9ea3e45d35ed1d52e2c Mon Sep 17 00:00:00 2001 From: Brent Rubell Date: Mon, 27 Nov 2023 10:23:12 -0500 Subject: [PATCH 107/116] Update random_data.py --- examples/api/random_data.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/api/random_data.py b/examples/api/random_data.py index a1da2b3..719e026 100644 --- a/examples/api/random_data.py +++ b/examples/api/random_data.py @@ -12,8 +12,8 @@ from Adafruit_IO import Client, Feed, RequestError # Set to your Adafruit IO key. -ADAFRUIT_IO_USERNAME = 'brubell' -ADAFRUIT_IO_KEY = '6ec4b31bd2c54a09be911e0c1909b7ab' +ADAFRUIT_IO_USERNAME = 'YOUR_IO_USERNAME' +ADAFRUIT_IO_KEY = 'YOUR_IO_KEY' # Create an instance of the REST client. aio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY) @@ -25,4 +25,4 @@ # Parse the API response data = json.dumps(random_data) data = json.loads(data) -print('Random Data: {0}'.format(data['value'])) \ No newline at end of file +print('Random Data: {0}'.format(data['value'])) From 1d996325c30b8de9baa4319210c4f94a9936f6cf Mon Sep 17 00:00:00 2001 From: Tyeth Gundry Date: Tue, 5 Dec 2023 17:21:26 +0000 Subject: [PATCH 108/116] Update _version.py - fix pip --- Adafruit_IO/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Adafruit_IO/_version.py b/Adafruit_IO/_version.py index 50062f8..7a38ae0 100644 --- a/Adafruit_IO/_version.py +++ b/Adafruit_IO/_version.py @@ -1 +1 @@ -__version__ = "2.5.0" +__version__ = "2.7.1" From 51117a4ce0a7dd213b36b4006732344b270f5c1e Mon Sep 17 00:00:00 2001 From: tyeth Date: Wed, 14 Feb 2024 16:20:06 +0000 Subject: [PATCH 109/116] Specify CallbackAPIVersion.VERSION1 --- Adafruit_IO/mqtt_client.py | 2 +- examples/mqtt/mqtt_viewall.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Adafruit_IO/mqtt_client.py b/Adafruit_IO/mqtt_client.py index b6af726..4a16b20 100644 --- a/Adafruit_IO/mqtt_client.py +++ b/Adafruit_IO/mqtt_client.py @@ -60,7 +60,7 @@ def __init__(self, username, key, service_host='io.adafruit.com', secure=True): self.on_message = None self.on_subscribe = None # Initialize MQTT client. - self._client = mqtt.Client() + self._client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION1) if secure: self._client.tls_set_context() self._secure = True diff --git a/examples/mqtt/mqtt_viewall.py b/examples/mqtt/mqtt_viewall.py index 64392be..36c2250 100644 --- a/examples/mqtt/mqtt_viewall.py +++ b/examples/mqtt/mqtt_viewall.py @@ -50,12 +50,12 @@ def on_connect(client, userdata, flags, rc): def on_disconnect(client, userdata, rc): print('Disconnected!') -def on_message(client, userdata, msg, retain): +def on_message(client, userdata, msg): print('Received on {0}: {1}'.format(msg.topic, msg.payload.decode('utf-8'))) # Create MQTT client and connect to Adafruit IO. -client = mqtt.Client() +client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION1) client.username_pw_set(USERNAME, KEY) client.on_connect = on_connect client.on_disconnect = on_disconnect From 29bed98b60dc3bbc170f9e1c94c4d4eb657318d2 Mon Sep 17 00:00:00 2001 From: tyeth Date: Wed, 14 Feb 2024 16:26:10 +0000 Subject: [PATCH 110/116] Bump version to 2.7.2 --- Adafruit_IO/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Adafruit_IO/_version.py b/Adafruit_IO/_version.py index 7a38ae0..7f90850 100644 --- a/Adafruit_IO/_version.py +++ b/Adafruit_IO/_version.py @@ -1 +1 @@ -__version__ = "2.7.1" +__version__ = "2.7.2" From a0cb9f613c5204f5f7e9762fbf2cfd55eb5eb3de Mon Sep 17 00:00:00 2001 From: tyeth Date: Wed, 14 Feb 2024 18:34:15 +0000 Subject: [PATCH 111/116] Update error codes + shared_feeds example --- Adafruit_IO/errors.py | 7 +++++-- examples/mqtt/mqtt_shared_feeds.py | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Adafruit_IO/errors.py b/Adafruit_IO/errors.py index ba6d88f..52e1590 100644 --- a/Adafruit_IO/errors.py +++ b/Adafruit_IO/errors.py @@ -20,8 +20,11 @@ # SOFTWARE. import json, requests +from paho.mqtt.client import error_string -# MQTT RC Error Types +# MQTT RC Error Types *** OBSOLETE *** +# See error_string() in client.py and enums.py from paho instead +# https://github.com/eclipse/paho.mqtt.python/blob/4eeb431f5ae72b42474cec42641fca1daa91c4b0/src/paho/mqtt/client.py#L291-L404 MQTT_ERRORS = [ 'Connection successful', 'Incorrect protocol version', 'Invalid Client ID', @@ -63,6 +66,6 @@ class MQTTError(Exception): """Handles connection attempt failed errors. """ def __init__(self, response): - error = MQTT_ERRORS[response] + error = error_string(response) super(MQTTError, self).__init__(error) pass \ No newline at end of file diff --git a/examples/mqtt/mqtt_shared_feeds.py b/examples/mqtt/mqtt_shared_feeds.py index 38b6667..23b2f16 100644 --- a/examples/mqtt/mqtt_shared_feeds.py +++ b/examples/mqtt/mqtt_shared_feeds.py @@ -70,5 +70,5 @@ def message(client, feed_id, payload): while True: value = random.randint(0, 100) print('Publishing {0} to {1}.'.format(value, IO_FEED)) - client.publish(IO_FEED, value, IO_FEED_USERNAME) + client.publish(IO_FEED, value, feed_user=IO_FEED_USERNAME) time.sleep(10) From 2737781f4c2cced380342dba94357694dc694d5a Mon Sep 17 00:00:00 2001 From: tyeth Date: Fri, 16 Feb 2024 12:40:37 +0000 Subject: [PATCH 112/116] PR Feedback: label client v1, clarify paho client in example/viewall --- Adafruit_IO/errors.py | 10 ---------- Adafruit_IO/mqtt_client.py | 2 +- examples/mqtt/mqtt_viewall.py | 2 +- 3 files changed, 2 insertions(+), 12 deletions(-) diff --git a/Adafruit_IO/errors.py b/Adafruit_IO/errors.py index 52e1590..52b3fd7 100644 --- a/Adafruit_IO/errors.py +++ b/Adafruit_IO/errors.py @@ -22,16 +22,6 @@ import json, requests from paho.mqtt.client import error_string -# MQTT RC Error Types *** OBSOLETE *** -# See error_string() in client.py and enums.py from paho instead -# https://github.com/eclipse/paho.mqtt.python/blob/4eeb431f5ae72b42474cec42641fca1daa91c4b0/src/paho/mqtt/client.py#L291-L404 -MQTT_ERRORS = [ 'Connection successful', - 'Incorrect protocol version', - 'Invalid Client ID', - 'Server unavailable ', - 'Bad username or password', - 'Not authorized' ] - class AdafruitIOError(Exception): """Base class for all Adafruit IO request failures.""" pass diff --git a/Adafruit_IO/mqtt_client.py b/Adafruit_IO/mqtt_client.py index 4a16b20..198b4d6 100644 --- a/Adafruit_IO/mqtt_client.py +++ b/Adafruit_IO/mqtt_client.py @@ -59,7 +59,7 @@ def __init__(self, username, key, service_host='io.adafruit.com', secure=True): self.on_disconnect = None self.on_message = None self.on_subscribe = None - # Initialize MQTT client. + # Initialize v1 MQTT client. self._client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION1) if secure: self._client.tls_set_context() diff --git a/examples/mqtt/mqtt_viewall.py b/examples/mqtt/mqtt_viewall.py index 36c2250..31c3a9e 100644 --- a/examples/mqtt/mqtt_viewall.py +++ b/examples/mqtt/mqtt_viewall.py @@ -54,7 +54,7 @@ def on_message(client, userdata, msg): print('Received on {0}: {1}'.format(msg.topic, msg.payload.decode('utf-8'))) -# Create MQTT client and connect to Adafruit IO. +# Create Paho v1 MQTT client and connect to Adafruit IO. client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION1) client.username_pw_set(USERNAME, KEY) client.on_connect = on_connect From 4e16f2b070788c62a843cb3f1ca9107e5ff92fe9 Mon Sep 17 00:00:00 2001 From: Tyeth Gundry Date: Wed, 18 Sep 2024 12:50:41 +0100 Subject: [PATCH 113/116] Update README.rst - python version 3.6 minimum A user pointed out they cannot run the library on python 3.5, and it appears that 3.6 will be the new minimum python version. Fixes #156 --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 04aaccd..94f93ed 100644 --- a/README.rst +++ b/README.rst @@ -20,7 +20,7 @@ Adafruit IO Python A Python library and examples for use with `io.adafruit.com `_. -Compatible with Python Versions 3.4+ +Compatible with Python Versions 3.6+ Installation ================ From 84577df587e25fd047b043723bcfde88a0e7f4fe Mon Sep 17 00:00:00 2001 From: tyeth Date: Wed, 18 Sep 2024 17:43:47 +0100 Subject: [PATCH 114/116] Add timezone parameter to receive_time + UTC for test --- Adafruit_IO/client.py | 40 +++++++++++++++++++++++++++++++++++++--- tests/test_client.py | 2 +- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/Adafruit_IO/client.py b/Adafruit_IO/client.py index 08ffe6c..3f92326 100644 --- a/Adafruit_IO/client.py +++ b/Adafruit_IO/client.py @@ -72,6 +72,7 @@ def __init__(self, username, key, proxies=None, base_url='https://io.adafruit.co @staticmethod def to_red(data): """Hex color feed to red channel. + :param int data: Color value, in hexadecimal. """ return ((int(data[1], 16))*16) + int(data[2], 16) @@ -79,6 +80,7 @@ def to_red(data): @staticmethod def to_green(data): """Hex color feed to green channel. + :param int data: Color value, in hexadecimal. """ return (int(data[3], 16) * 16) + int(data[4], 16) @@ -86,6 +88,7 @@ def to_green(data): @staticmethod def to_blue(data): """Hex color feed to blue channel. + :param int data: Color value, in hexadecimal. """ return (int(data[5], 16) * 16) + int(data[6], 16) @@ -153,6 +156,7 @@ def send_data(self, feed, value, metadata=None, precision=None): specified value to the feed identified by either name, key, or ID. Returns a Data instance with details about the newly appended row of data. Note that send_data now operates the same as append. + :param string feed: Name/Key/ID of Adafruit IO feed. :param string value: Value to send. :param dict metadata: Optional metadata associated with the value. @@ -173,6 +177,7 @@ def send_batch_data(self, feed, data_list): ID, feed key, or feed name. Data must be an instance of the Data class with at least a value property set on it. Returns a Data instance with details about the newly appended row of data. + :param string feed: Name/Key/ID of Adafruit IO feed. :param Data data_list: Multiple data values. """ @@ -185,21 +190,28 @@ def append(self, feed, value): specified value to the feed identified by either name, key, or ID. Returns a Data instance with details about the newly appended row of data. Note that unlike send the feed should exist before calling append. + :param string feed: Name/Key/ID of Adafruit IO feed. :param string value: Value to append to feed. """ return self.create_data(feed, Data(value=value)) - def receive_time(self): - """Returns a struct_time from the Adafruit IO Server based on the device's IP address. + def receive_time(self, timezone=None): + """Returns a struct_time from the Adafruit IO Server based on requested + timezone, or automatically based on the device's IP address. https://docs.python.org/3.7/library/time.html#time.struct_time + + :param string timezone: Optional timezone to return the time in. + See https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List """ path = 'integrations/time/struct.json' + if timezone: + path += f'?tz={timezone}' return self._parse_time_struct(self._get(path)) @staticmethod def _parse_time_struct(time_dict: dict) -> time.struct_time: - """Parse the time data returned by the server and return a time_struct + """Parse the time data returned by the server and return a time_struct Corrects for the weekday returned by the server in Sunday=0 format (Python expects Monday=0) @@ -211,6 +223,7 @@ def _parse_time_struct(time_dict: dict) -> time.struct_time: def receive_weather(self, weather_id=None): """Adafruit IO Weather Service, Powered by Dark Sky + :param int id: optional ID for retrieving a specified weather record. """ if weather_id: @@ -222,6 +235,7 @@ def receive_weather(self, weather_id=None): def receive_random(self, randomizer_id=None): """Access to Adafruit IO's Random Data service. + :param int randomizer_id: optional ID for retrieving a specified randomizer. """ if randomizer_id: @@ -233,6 +247,7 @@ def receive_random(self, randomizer_id=None): def receive(self, feed): """Retrieve the most recent value for the specified feed. Returns a Data instance whose value property holds the retrieved value. + :param string feed: Name/Key/ID of Adafruit IO feed. """ path = "feeds/{0}/data/last".format(feed) @@ -241,6 +256,7 @@ def receive(self, feed): def receive_next(self, feed): """Retrieve the next unread value from the specified feed. Returns a Data instance whose value property holds the retrieved value. + :param string feed: Name/Key/ID of Adafruit IO feed. """ path = "feeds/{0}/data/next".format(feed) @@ -249,6 +265,7 @@ def receive_next(self, feed): def receive_previous(self, feed): """Retrieve the previous unread value from the specified feed. Returns a Data instance whose value property holds the retrieved value. + :param string feed: Name/Key/ID of Adafruit IO feed. """ path = "feeds/{0}/data/previous".format(feed) @@ -257,6 +274,7 @@ def receive_previous(self, feed): def data(self, feed, data_id=None, max_results=DEFAULT_PAGE_LIMIT): """Retrieve data from a feed. If data_id is not specified then all the data for the feed will be returned in an array. + :param string feed: Name/Key/ID of Adafruit IO feed. :param string data_id: ID of the piece of data to delete. :param int max_results: The maximum number of results to return. To @@ -306,6 +324,7 @@ def create_data(self, feed, data): """Create a new row of data in the specified feed. Returns a Data instance with details about the newly appended row of data. + :param string feed: Name/Key/ID of Adafruit IO feed. :param Data data: Instance of the Data class. Must have a value property set. """ @@ -314,6 +333,7 @@ def create_data(self, feed, data): def delete(self, feed, data_id): """Delete data from a feed. + :param string feed: Name/Key/ID of Adafruit IO feed. :param string data_id: ID of the piece of data to delete. """ @@ -324,6 +344,7 @@ def delete(self, feed, data_id): def feeds(self, feed=None): """Retrieve a list of all feeds, or the specified feed. If feed is not specified a list of all feeds will be returned. + :param string feed: Name/Key/ID of Adafruit IO feed, defaults to None. """ if feed is None: @@ -334,6 +355,7 @@ def feeds(self, feed=None): def create_feed(self, feed, group_key=None): """Create the specified feed. + :param string feed: Key of Adafruit IO feed. :param group_key group: Group to place new feed in. """ @@ -347,6 +369,7 @@ def create_feed(self, feed, group_key=None): def delete_feed(self, feed): """Delete the specified feed. + :param string feed: Name/Key/ID of Adafruit IO feed. """ path = "feeds/{0}".format(feed) @@ -355,6 +378,7 @@ def delete_feed(self, feed): # Group functionality. def groups(self, group=None): """Retrieve a list of all groups, or the specified group. + :param string group: Name/Key/ID of Adafruit IO Group. Defaults to None. """ if group is None: @@ -365,6 +389,7 @@ def groups(self, group=None): def create_group(self, group): """Create the specified group. + :param string group: Name/Key/ID of Adafruit IO Group. """ path = "groups/" @@ -372,6 +397,7 @@ def create_group(self, group): def delete_group(self, group): """Delete the specified group. + :param string group: Name/Key/ID of Adafruit IO Group. """ path = "groups/{0}".format(group) @@ -380,6 +406,7 @@ def delete_group(self, group): # Dashboard functionality. def dashboards(self, dashboard=None): """Retrieve a list of all dashboards, or the specified dashboard. + :param string dashboard: Key of Adafruit IO Dashboard. Defaults to None. """ if dashboard is None: @@ -390,6 +417,7 @@ def dashboards(self, dashboard=None): def create_dashboard(self, dashboard): """Create the specified dashboard. + :param Dashboard dashboard: Dashboard object to create """ path = "dashboards/" @@ -397,6 +425,7 @@ def create_dashboard(self, dashboard): def delete_dashboard(self, dashboard): """Delete the specified dashboard. + :param string dashboard: Key of Adafruit IO Dashboard. """ path = "dashboards/{0}".format(dashboard) @@ -405,6 +434,7 @@ def delete_dashboard(self, dashboard): # Block functionality. def blocks(self, dashboard, block=None): """Retrieve a list of all blocks from a dashboard, or the specified block. + :param string dashboard: Key of Adafruit IO Dashboard. :param string block: id of Adafruit IO Block. Defaults to None. """ @@ -416,6 +446,7 @@ def blocks(self, dashboard, block=None): def create_block(self, dashboard, block): """Create the specified block under the specified dashboard. + :param string dashboard: Key of Adafruit IO Dashboard. :param Block block: Block object to create under dashboard """ @@ -424,6 +455,7 @@ def create_block(self, dashboard, block): def delete_block(self, dashboard, block): """Delete the specified block. + :param string dashboard: Key of Adafruit IO Dashboard. :param string block: id of Adafruit IO Block. """ @@ -433,6 +465,7 @@ def delete_block(self, dashboard, block): # Layout functionality. def layouts(self, dashboard): """Retrieve the layouts array from a dashboard + :param string dashboard: key of Adafruit IO Dashboard. """ path = "dashboards/{0}".format(dashboard) @@ -441,6 +474,7 @@ def layouts(self, dashboard): def update_layout(self, dashboard, layout): """Update the layout of the specified dashboard. + :param string dashboard: Key of Adafruit IO Dashboard. :param Layout layout: Layout object to update under dashboard """ diff --git a/tests/test_client.py b/tests/test_client.py index 90acd5b..7714909 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -176,7 +176,7 @@ def test_time_data(self): """receive_time """ aio = self.get_client() - server_time = aio.receive_time() + server_time = aio.receive_time(timezone='UTC') # Check that each value is rx'd properly # (should never be None type) for time_data in server_time: From 3b008d356dae78808ccc0a5cc373d7fd6f500616 Mon Sep 17 00:00:00 2001 From: Tyeth Gundry Date: Thu, 26 Sep 2024 15:52:51 +0100 Subject: [PATCH 115/116] Update _version.py - bump version to 2.8.0 --- Adafruit_IO/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Adafruit_IO/_version.py b/Adafruit_IO/_version.py index 7f90850..892994a 100644 --- a/Adafruit_IO/_version.py +++ b/Adafruit_IO/_version.py @@ -1 +1 @@ -__version__ = "2.7.2" +__version__ = "2.8.0" From 302fcca696e5b73253e8934ea6858be6433ca664 Mon Sep 17 00:00:00 2001 From: tyeth Date: Fri, 29 Nov 2024 23:39:56 +0000 Subject: [PATCH 116/116] Addresses issue raised in #159 --- examples/basics/digital_out.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/basics/digital_out.py b/examples/basics/digital_out.py index e92d3b6..5da4f99 100644 --- a/examples/basics/digital_out.py +++ b/examples/basics/digital_out.py @@ -40,7 +40,10 @@ while True: - data = aio.receive(digital.key) + try: + data = aio.receive(digital.key) + except RequestError as re: + pass # feed with no data will return 404 if int(data.value) == 1: print('received <- ON\n') elif int(data.value) == 0: