diff --git a/.gitignore b/.gitignore index 41278df..8c1d213 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ setuptools-* dist build eggs +.eggs parts bin var diff --git a/Adafruit_IO/_version.py b/Adafruit_IO/_version.py index 239bf2b..528787c 100644 --- a/Adafruit_IO/_version.py +++ b/Adafruit_IO/_version.py @@ -1 +1 @@ -__version__ = "2.8.2" +__version__ = "3.0.0" diff --git a/Adafruit_IO/client.py b/Adafruit_IO/client.py index 3f92326..4349712 100644 --- a/Adafruit_IO/client.py +++ b/Adafruit_IO/client.py @@ -22,7 +22,14 @@ from time import struct_time import json import platform -import pkg_resources +try: + from importlib.metadata import version as package_version # Python 3.8+ +except ImportError: + try: + from importlib_metadata import version as package_version # Backport for <3.8 + except ImportError: # pragma: no cover - fallback for older Python + package_version = None + import pkg_resources import re from urllib.parse import urlparse from urllib.parse import parse_qs @@ -35,8 +42,11 @@ DEFAULT_PAGE_LIMIT = 100 -# set outgoing version, pulled from setup.py -version = pkg_resources.require("Adafruit_IO")[0].version +# set outgoing version, pulled from package metadata +if package_version is not None: + version = package_version("Adafruit_IO") +else: + version = pkg_resources.require("Adafruit_IO")[0].version default_headers = { 'User-Agent': 'AdafruitIO-Python/{0} ({1}, {2} {3})'.format(version, platform.platform(), @@ -232,6 +242,66 @@ def receive_weather(self, weather_id=None): weather_path = "integrations/weather" return self._get(weather_path) + def create_weather(self, weather_record): + """Create a new weather record. + + :param dict weather_record: Weather record to create. e.g. + ``` + weather_record = { + 'location': "40.726190,-74.005360", + 'name': 'New York City, NY' # must be unique (per user) + } + ``` + """ + path = "integrations/weather" + return self._post(path, weather_record) + + def delete_weather(self, weather_id): + """Delete a weather record. + + :param int weather_id: ID of the weather record to delete. + """ + path = "integrations/weather/{0}".format(weather_id) + self._delete(path) + + def receive_air_quality(self, airq_location_id=None, forecast=None): + """Adafruit IO Air Quality Service + + :param int airq_location_id: optional ID for retrieving a specified air quality record. + :param string forecast: Can be "current", "forecast_today", or "forecast_tomorrow". + """ + if airq_location_id: + if forecast: + path = "integrations/air_quality/{0}/{1}".format(airq_location_id, forecast) + else: + path = "integrations/air_quality/{0}".format(airq_location_id) + else: + path = "integrations/air_quality" + return self._get(path) + + def create_air_quality(self, air_quality_record): + """Create a new air quality record. + + :param dict air_quality_record: Air quality record to create. e.g. + ``` + air_quality_record = { + "location": "50.2423591, -5.4001148", + "name": "Godrevy Lighthouse, Cornwall", # must be unique (per user) + "provider": "open_meteo" # 'airnow' [US] or 'open_meteo' [Global] + } + ``` + """ + path = "integrations/air_quality" + return self._post(path, air_quality_record) + + def delete_air_quality(self, air_quality_id): + """Delete an air quality record. + + :param int air_quality_id: ID of the air quality record to delete. + """ + path = "integrations/air_quality/{0}".format(air_quality_id) + self._delete(path) + def receive_random(self, randomizer_id=None): """Access to Adafruit IO's Random Data service. diff --git a/Adafruit_IO/mqtt_client.py b/Adafruit_IO/mqtt_client.py index 198b4d6..9dd944e 100644 --- a/Adafruit_IO/mqtt_client.py +++ b/Adafruit_IO/mqtt_client.py @@ -19,6 +19,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import logging +import re import paho.mqtt.client as mqtt import sys @@ -35,6 +36,25 @@ "forecast_hours_24", "forecast_days_1", "forecast_days_2", "forecast_days_5",] + +def validate_feed_key(feed_key): + """Validates a provided feed key against Adafruit IO's system rules. + https://learn.adafruit.com/naming-things-in-adafruit-io/the-two-feed-identifiers + + :param str feed_key: The feed key to validate. + :raises ValueError: If the feed key is too long. + :raises TypeError: If the feed key contains invalid characters or is empty. + """ + if len(feed_key) > 128: # validate feed key length + raise ValueError("Feed key must be 128 characters or less.") + if not bool( + re.match(r"^[a-zA-Z0-9-]+((\/|\.)[a-zA-Z0-9-]+)?$", feed_key) + ): # validate key naming scheme + raise TypeError( + "Feed key must contain English letters, numbers, dash, and a period or a forward slash." + ) + + class MQTTClient(object): """Interface for publishing and subscribing to feed changes on Adafruit IO using the MQTT protocol. @@ -121,6 +141,9 @@ def _mqtt_message(self, client, userdata, msg): elif parsed_topic[2] == 'weather': topic = parsed_topic[4] payload = '' if msg.payload is None else msg.payload.decode('utf-8') + elif parsed_topic[2] == 'air_quality': + topic = parsed_topic[3] + 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') @@ -198,21 +221,22 @@ def loop(self, timeout_sec=1.0): """ self._client.loop(timeout=timeout_sec) - def subscribe(self, feed_id, feed_user=None, qos=0): + def subscribe(self, feed_key, 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. + the on_message function will be called with the feed_key and new value. - :param str feed_id: The key of the feed to subscribe to. + :param str feed_key: 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. """ + validate_feed_key(feed_key) 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, qos=qos)) + (res, mid) = self._client.subscribe('{0}/feeds/{1}'.format(feed_user, feed_key), qos=qos) else: - (res, mid) = self._client.subscribe('{0}/feeds/{1}'.format(self._username, feed_id), qos=qos) + (res, mid) = self._client.subscribe('{0}/feeds/{1}'.format(self._username, feed_key), qos=qos) return res, mid def subscribe_group(self, group_id, qos=0): @@ -239,8 +263,19 @@ def subscribe_randomizer(self, 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 - :param string type: type of forecast data requested + :param string type: type of forecast data requested. Valid 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 """ if forecast_type in forecast_types: self._client.subscribe('{0}/integration/weather/{1}/{2}'.format(self._username, weather_id, forecast_type)) @@ -248,6 +283,16 @@ def subscribe_weather(self, weather_id, forecast_type): raise TypeError("Invalid Forecast Type Specified.") return + def subscribe_air_quality(self, airq_location_id, forecast='current'): + """Subscribe to Adafruit IO Air Quality Service + :param int airq_location_id: air quality record you want data for + :param string forecast: Can be "current", "forecast_today", or "forecast_tomorrow". + """ + if forecast in forecast_types: + self._client.subscribe('{0}/integration/air_quality/{1}/{2}'.format(self._username, airq_location_id, forecast)) + 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 updated, the on_message function will be called and publish a new value: @@ -264,43 +309,82 @@ def subscribe_time(self, time): raise TypeError('Invalid Time Feed Specified.') return - def unsubscribe(self, feed_id=None, group_id=None): + def unsubscribe(self, feed_key=None, group_id=None): """Unsubscribes from a specified MQTT topic. Note: this does not prevent publishing to a topic, it will unsubscribe from receiving messages via on_message. """ - if feed_id is not None: - self._client.unsubscribe('{0}/feeds/{1}'.format(self._username, feed_id)) + if feed_key is not None: + validate_feed_key(feed_key) + self._client.unsubscribe('{0}/feeds/{1}'.format(self._username, feed_key)) elif group_id is not None: self._client.unsubscribe('{0}/groups/{1}'.format(self._username, group_id)) else: raise TypeError('Invalid topic type specified.') return - def receive(self, feed_id): + def unsubscribe_randomizer(self, randomizer_id): + """Unsubscribe from a specified random data stream. + :param int randomizer_id: ID of the random word record to unsubscribe from. + """ + self._client.unsubscribe('{0}/integration/words/{1}'.format(self._username, randomizer_id)) + + def unsubscribe_weather(self, weather_id, forecast_type): + """Unsubscribe from Adafruit IO Weather + :param int weather_id: weather record to unsubscribe from + :param string type: type of forecast data + """ + if forecast_type in forecast_types: + self._client.unsubscribe('{0}/integration/weather/{1}/{2}'.format(self._username, weather_id, forecast_type)) + else: + raise TypeError("Invalid Forecast Type Specified.") + + def unsubscribe_time(self, time): + """Unsubscribe from Adafruit IO time feeds. + """ + if time == 'millis' or time == 'seconds': + self._client.unsubscribe('time/{0}'.format(time)) + elif time == 'iso': + self._client.unsubscribe('time/ISO-8601') + else: + raise TypeError('Invalid Time Feed Specified.') + return + + def unsubscribe_air_quality(self, airq_location_id, forecast='current'): + """Unsubscribe from Adafruit IO Air Quality Service + :param int airq_location_id: air quality record to unsubscribe from + :param string forecast: Can be "current", "forecast_today", or "forecast_tomorrow". + """ + if forecast in forecast_types: + self._client.unsubscribe('{0}/integration/air_quality/{1}/{2}'.format(self._username, airq_location_id, forecast)) + else: + raise TypeError("Invalid Forecast Type Specified.") + + def receive(self, feed_key): """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 + :param string feed_key: The key of the feed to retrieve the value from. """ - (res, self._pub_mid) = self._client.publish('{0}/feeds/{1}/get'.format(self._username, feed_id), + validate_feed_key(feed_key) + (res, self._pub_mid) = self._client.publish('{0}/feeds/{1}/get'.format(self._username, feed_key), payload='') - def publish(self, feed_id, value=None, group_id=None, feed_user=None): + def publish(self, feed_key, value=None, group_id=None, feed_user=None): """Publish a value to a specified feed. Params: - - feed_id: The id of the feed to update. + - feed_key: The key of the feed to update. - value: The new value to publish to the feed. - (optional) group_id: The id of the group to update. - (optional) feed_user: The feed owner's username. Used for Sharing Feeds. """ + validate_feed_key(feed_key) if feed_user is not None: # shared feed - (res, self._pub_mid) = self._client.publish('{0}/feeds/{1}'.format(feed_user, feed_id), + (res, self._pub_mid) = self._client.publish('{0}/feeds/{1}'.format(feed_user, feed_key), payload=value) elif group_id is not None: # group-specified feed - self._client.publish('{0}/feeds/{1}.{2}'.format(self._username, group_id, feed_id), + self._client.publish('{0}/feeds/{1}.{2}'.format(self._username, group_id, feed_key), payload=value) else: # regular feed - (res, self._pub_mid) = self._client.publish('{0}/feeds/{1}'.format(self._username, feed_id), + (res, self._pub_mid) = self._client.publish('{0}/feeds/{1}'.format(self._username, feed_key), payload=value) diff --git a/README.rst b/README.rst index 94f93ed..8122159 100644 --- a/README.rst +++ b/README.rst @@ -61,6 +61,27 @@ Usage Documentation for this project is `available on the ReadTheDocs `_. +Service Integrations +==================== + +The client includes REST and MQTT helpers for Adafruit IO integrations: + +- Time +- Random Data +- Weather (requires IO+ subscription) +- Air Quality (requires IO+ subscription) + +Integration API details are documented in ``docs/integrations.rst``. + +Service integration examples are included in: + +- ``examples/api/weather.py`` +- ``examples/api/weather_create_delete.py`` +- ``examples/api/air_quality.py`` +- ``examples/api/air_quality_create_delete.py`` +- ``examples/mqtt/mqtt_weather.py`` +- ``examples/mqtt/mqtt_air_quality.py`` + Contributing ============ diff --git a/docs/index.rst b/docs/index.rst index d081ccd..cb060e9 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -25,6 +25,7 @@ Table of Contents feed-sharing data groups + integrations diff --git a/docs/integrations.rst b/docs/integrations.rst new file mode 100644 index 0000000..9ce88f6 --- /dev/null +++ b/docs/integrations.rst @@ -0,0 +1,119 @@ +Service Integrations +-------------------- +Adafruit IO provides integration services that can be used from both the REST client +and MQTT client. + +Time Service +~~~~~~~~~~~~ +REST +^^^^ +Use ``receive_time(timezone=None)`` to retrieve time data as a Python ``struct_time``. + +MQTT +^^^^ +Use: + +- ``subscribe_time(time)`` +- ``unsubscribe_time(time)`` + +Where ``time`` is one of: + +- ``millis`` +- ``seconds`` +- ``iso`` + +Example: + +.. literalinclude:: ../examples/mqtt/mqtt_time.py + +Random Data Service +~~~~~~~~~~~~~~~~~~~ +REST +^^^^ +Use ``receive_random(randomizer_id=None)`` to list randomizers or read one by ID. + +MQTT +^^^^ +Use: + +- ``subscribe_randomizer(randomizer_id)`` +- ``unsubscribe_randomizer(randomizer_id)`` + +Example: + +.. literalinclude:: ../examples/api/random_data.py + +Weather Service +~~~~~~~~~~~~~~~ +REST +^^^^ +Use: + +- ``receive_weather(weather_id=None)`` +- ``create_weather(weather_record)`` +- ``delete_weather(weather_id)`` + +Examples: + +.. literalinclude:: ../examples/api/weather.py + +.. literalinclude:: ../examples/api/weather_create_delete.py + +MQTT +^^^^ +Use: + +- ``subscribe_weather(weather_id, forecast_type)`` +- ``unsubscribe_weather(weather_id, forecast_type)`` + +Where ``forecast_type`` is one of: + +- ``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`` + +Example: + +.. literalinclude:: ../examples/mqtt/mqtt_weather.py + +Air Quality Service +~~~~~~~~~~~~~~~~~~~ +Note: Air Quality service access requires an IO+ subscription. + +REST +^^^^ +Use: + +- ``receive_air_quality(airq_location_id=None, forecast=None)`` +- ``create_air_quality(air_quality_record)`` +- ``delete_air_quality(air_quality_id)`` + +Where ``forecast`` is optional and can be one of: + +- ``current`` +- ``forecast_today`` +- ``forecast_tomorrow`` + +Examples: + +.. literalinclude:: ../examples/api/air_quality.py + +.. literalinclude:: ../examples/api/air_quality_create_delete.py + +MQTT +^^^^ +Use: + +- ``subscribe_air_quality(airq_location_id, forecast='current')`` +- ``unsubscribe_air_quality(airq_location_id, forecast='current')`` + +Example: + +.. literalinclude:: ../examples/mqtt/mqtt_air_quality.py diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 70bb731..5271c3e 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -18,7 +18,9 @@ Here's a short example of how to send a new value to a feed (creating the feed i data = aio.receive('Foo') print('Received value: {0}'.format(data.value)) -If you want to be notified of feed changes immediately without polling, consider using the MQTT client. See the ``examples/mqtt_client.py`` for an example of using the MQTT client. +If you want to be notified of feed changes immediately without polling, consider using the MQTT client. See ``examples/mqtt/mqtt_client_class.py`` for an example of using the MQTT client. + +For Adafruit IO service integrations (Time, Random Data, Weather, and Air Quality), see :doc:`integrations`. Basic Client Usage ------------------- diff --git a/examples/api/air_quality.py b/examples/api/air_quality.py new file mode 100644 index 0000000..223168a --- /dev/null +++ b/examples/api/air_quality.py @@ -0,0 +1,56 @@ +""" +'air_quality.py' +================================================ +Adafruit IO Air Quality Service +with Adafruit IO API (requires IO+ subscription) + +Author(s): Tyeth Gundry for Adafruit Industries +""" +import json +import os +from Adafruit_IO import Client + +ADAFRUIT_IO_USERNAME = os.getenv("ADAFRUIT_IO_USERNAME", "YOUR_IO_USERNAME") +ADAFRUIT_IO_KEY = os.getenv("ADAFRUIT_IO_KEY", "YOUR_IO_KEY") + +aio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY) + +# ID of the Air Quality record to retrieve +# You can find this ID by visiting your Air Quality integration page on Adafruit IO +AQ_ID = None # Replace with your Air Quality record ID + +CREATED_DEMO_RECORD = False # will be used to track deletion +if AQ_ID is None: + # fetch the list of air quality records to find the first ID + print('No Air Quality record ID specified. Fetching list of records...') + air_quality_records = aio.receive_air_quality() + if air_quality_records: + AQ_ID = air_quality_records[0]['id'] + print('Using Air Quality record ID: {0}'.format(AQ_ID)) + else: + # attempt to create one if none exist (requires IO+ subscription) + try: + aq_record = { + "location": "40.726190,-74.005360", + "name": "New York City, NY", # must be unique in your account + "provider": "open_meteo" # 'airnow' [US] or 'open_meteo' [Global] + } + created_record = aio.create_air_quality(aq_record) + AQ_ID = created_record['id'] + CREATED_DEMO_RECORD = True + print('Created Air Quality record with ID: {0}'.format(AQ_ID)) + except Exception as e: + print('Failed to create Air Quality record: {0}'.format(e)) + exit(1) + +print('Retrieving Air Quality Data for Air Quality record ID: {0}'.format(AQ_ID)) +air_quality = aio.receive_air_quality(AQ_ID) +print(json.dumps(air_quality, indent=2)) + +# Clean up the demo record if we created one +if CREATED_DEMO_RECORD: + try: + aio.delete_air_quality(AQ_ID) + print('\nDeleted demo Air Quality record with ID: {0}'.format(AQ_ID)) + except Exception as e: + print('Failed to delete demo Air Quality record: {0}'.format(e)) \ No newline at end of file diff --git a/examples/api/air_quality_create_delete.py b/examples/api/air_quality_create_delete.py new file mode 100644 index 0000000..c880ed5 --- /dev/null +++ b/examples/api/air_quality_create_delete.py @@ -0,0 +1,34 @@ +""" +'air_quality_create_delete.py' +================================================ +Create and Delete Air Quality Records +with Adafruit IO API + +Author(s): Brent Rubell for Adafruit Industries +""" +import os +from Adafruit_IO import Client, RequestError + +ADAFRUIT_IO_USERNAME = os.getenv("ADAFRUIT_IO_USERNAME", "YOUR_IO_USERNAME") +ADAFRUIT_IO_KEY = os.getenv("ADAFRUIT_IO_KEY", "YOUR_IO_KEY") + +aio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY) + +# Create a new air quality record +print('Creating new air quality record...') +aq_record = { + 'location': "40.726190,-74.005360", + 'name': 'New York City, NY', + 'provider': 'open_meteo' # 'airnow' [US only] or 'open_meteo' [Global] +} +try: + created_record = aio.create_air_quality(aq_record) + print('Air Quality record created with ID: {0}'.format(created_record['id'])) + print(created_record) + + # Delete the air quality record + print('\nDeleting air quality record...') + aio.delete_air_quality(created_record['id']) + print('Air Quality record deleted.') +except RequestError as e: + print('Error: {0}'.format(e)) diff --git a/examples/api/data.py b/examples/api/data.py index 63df954..4805e31 100644 --- a/examples/api/data.py +++ b/examples/api/data.py @@ -2,18 +2,21 @@ # API client. # Author: Tony Dicola, Justin Cooper -# Import Adafruit IO REST client. -from Adafruit_IO import Client, Feed, Data, RequestError +# Import standard python modules. import datetime +import os -# 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' +# Import Adafruit IO REST client. +from Adafruit_IO import Client, Feed, Data, RequestError # Set to your Adafruit IO username. # (go to https://accounts.adafruit.com to find your username) -ADAFRUIT_IO_USERNAME = 'YOUR_AIO_USERNAME' +ADAFRUIT_IO_USERNAME = os.getenv('ADAFRUIT_IO_USERNAME', 'YOUR_AIO_USERNAME') + +# 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 = os.getenv('ADAFRUIT_IO_KEY', 'YOUR_AIO_KEY') # Create an instance of the REST client. aio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY) diff --git a/examples/api/feeds.py b/examples/api/feeds.py index 848bdda..1047498 100644 --- a/examples/api/feeds.py +++ b/examples/api/feeds.py @@ -2,18 +2,20 @@ # API client. # Author: Tony Dicola, Justin Cooper, Brent Rubell +# Import standard python modules. +import os + # Import Adafruit IO REST client. from Adafruit_IO import Client, Feed -import json - -# 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' +ADAFRUIT_IO_USERNAME = os.getenv('ADAFRUIT_IO_USERNAME', 'YOUR_AIO_USERNAME') + +# 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 = os.getenv('ADAFRUIT_IO_KEY', 'YOUR_AIO_KEY') # Create an instance of the REST client. aio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY) diff --git a/examples/api/location.py b/examples/api/location.py index 3e5846a..b2cf2cf 100644 --- a/examples/api/location.py +++ b/examples/api/location.py @@ -7,17 +7,20 @@ Author(s): Brent Rubell """ +# Import standard python modules. +import os + # 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' +ADAFRUIT_IO_USERNAME = os.getenv('ADAFRUIT_IO_USERNAME', 'YOUR_AIO_USERNAME') + +# 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 = os.getenv('ADAFRUIT_IO_KEY', 'YOUR_AIO_KEY') # Create an instance of the REST client. aio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY) diff --git a/examples/api/random_data.py b/examples/api/random_data.py index 719e026..edda90c 100644 --- a/examples/api/random_data.py +++ b/examples/api/random_data.py @@ -6,14 +6,20 @@ Author(s): Brent Rubell for Adafruit Industries """ -# Import JSON for forecast parsing +# Import standard python modules import json +import os # Import Adafruit IO REST client. -from Adafruit_IO import Client, Feed, RequestError +from Adafruit_IO import Client + +# Set to your Adafruit IO username. +# (go to https://accounts.adafruit.com to find your username) +ADAFRUIT_IO_USERNAME = os.getenv('ADAFRUIT_IO_USERNAME', 'YOUR_IO_USERNAME') # Set to your Adafruit IO key. -ADAFRUIT_IO_USERNAME = 'YOUR_IO_USERNAME' -ADAFRUIT_IO_KEY = 'YOUR_IO_KEY' +# Remember, your key is a secret, +# so make sure **not** to publish it when you publish this code! +ADAFRUIT_IO_KEY = os.getenv('ADAFRUIT_IO_KEY', 'YOUR_IO_KEY') # Create an instance of the REST client. aio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY) diff --git a/examples/api/simple.py b/examples/api/simple.py index 2f7de33..2ec5326 100644 --- a/examples/api/simple.py +++ b/examples/api/simple.py @@ -2,17 +2,20 @@ # API client. # Author: Tony DiCola +# Import standard python modules. +import os + # Import Adafruit IO REST client. from Adafruit_IO import Client, RequestError, Feed -# 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' +ADAFRUIT_IO_USERNAME = os.getenv('ADAFRUIT_IO_USERNAME', 'YOUR_AIO_USERNAME') + +# 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 = os.getenv('ADAFRUIT_IO_KEY', 'YOUR_AIO_KEY') # Create an instance of the REST client. aio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY) diff --git a/examples/api/weather.py b/examples/api/weather.py index 3e4bc5c..7307821 100644 --- a/examples/api/weather.py +++ b/examples/api/weather.py @@ -6,14 +6,22 @@ Author(s): Brent Rubell for Adafruit Industries """ -# Import JSON for forecast parsing + +# Import JSON for forecast parsing, os for environment variables. import json +import os + # Import Adafruit IO REST client. -from Adafruit_IO import Client, Feed, RequestError +from Adafruit_IO import Client + +# Set to your Adafruit IO username. +# (go to https://accounts.adafruit.com to find your username) +ADAFRUIT_IO_USERNAME = os.getenv("ADAFRUIT_IO_USERNAME", "YOUR_IO_USERNAME") # Set to your Adafruit IO key. -ADAFRUIT_IO_USERNAME = 'YOUR_IO_USERNAME' -ADAFRUIT_IO_KEY = 'YOUR_IO_PASSWORD' +# Remember, your key is a secret, +# so make sure **not** to publish it when you publish this code! +ADAFRUIT_IO_KEY = os.getenv("ADAFRUIT_IO_KEY", "YOUR_IO_PASSWORD") # Create an instance of the REST client. aio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY) @@ -24,18 +32,28 @@ 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'])) +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'])) +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 +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"], + ) +) diff --git a/examples/api/weather_create_delete.py b/examples/api/weather_create_delete.py new file mode 100644 index 0000000..22140f3 --- /dev/null +++ b/examples/api/weather_create_delete.py @@ -0,0 +1,35 @@ +""" +'weather_create_delete.py' +================================================ +Create and Delete Weather Records +with Adafruit IO API + +Author(s): Brent Rubell for Adafruit Industries +""" +import os +from Adafruit_IO import Client, RequestError + +ADAFRUIT_IO_USERNAME = os.getenv("ADAFRUIT_IO_USERNAME", "YOUR_IO_USERNAME") +ADAFRUIT_IO_KEY = os.getenv("ADAFRUIT_IO_KEY", "YOUR_IO_KEY") + +aio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY) + +# Create a new weather record +print('Creating new weather record...') +weather_record = { + 'location': "40.726190,-74.005360", + 'name': 'New York City, NY' +} +try: + created_record = aio.create_weather(weather_record) + print('Weather record created with ID: {0}'.format(created_record['id'])) + # Print the created record details (JSON, but compact form) + print("Record details:") + print(created_record, end='\n\n') + + # Delete the weather record + print('Deleting weather record...') + aio.delete_weather(created_record['id']) + print('Weather record deleted.') +except RequestError as e: + print('Error: {0}'.format(e)) diff --git a/examples/basics/analog_in.py b/examples/basics/analog_in.py index 14f73f4..5b0e75f 100644 --- a/examples/basics/analog_in.py +++ b/examples/basics/analog_in.py @@ -13,6 +13,7 @@ (https://github.com/adafruit/Adafruit_CircuitPython_MCP3xxx) """ # Import standard python modules +import os import time # import Adafruit Blinka @@ -27,14 +28,14 @@ from adafruit_mcp3xxx.mcp3008 import MCP3008 from adafruit_mcp3xxx.analog_in import AnalogIn -# 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' +ADAFRUIT_IO_USERNAME = os.getenv('ADAFRUIT_IO_USERNAME', 'YOUR_AIO_USERNAME') + +# 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 = os.getenv('ADAFRUIT_IO_KEY', 'YOUR_AIO_KEY') # Create an instance of the REST client aio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY) diff --git a/examples/basics/analog_output.py b/examples/basics/analog_output.py index a1d2994..f04a1e8 100644 --- a/examples/basics/analog_output.py +++ b/examples/basics/analog_output.py @@ -22,6 +22,7 @@ """ # import system libraries +import os import time # import Adafruit Blinka @@ -34,14 +35,14 @@ # 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_IO_KEY' - # Set to your Adafruit IO username. # (go to https://accounts.adafruit.com to find your username) -ADAFRUIT_IO_USERNAME = 'YOUR_IO_USERNAME' +ADAFRUIT_IO_USERNAME = os.getenv('ADAFRUIT_IO_USERNAME', 'YOUR_IO_USERNAME') + +# 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 = os.getenv('ADAFRUIT_IO_KEY', 'YOUR_IO_KEY') # Create the I2C bus interface. i2c_bus = I2C(SCL, SDA) diff --git a/examples/basics/dashboard.py b/examples/basics/dashboard.py index 8a5bc61..a2619bd 100644 --- a/examples/basics/dashboard.py +++ b/examples/basics/dashboard.py @@ -5,18 +5,19 @@ Author(s): Doug Zobel """ +import os 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 = '' +ADAFRUIT_IO_USERNAME = os.getenv('ADAFRUIT_IO_USERNAME', '') + +# 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 = os.getenv('ADAFRUIT_IO_KEY', '') # Create an instance of the REST client. aio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY) @@ -27,7 +28,7 @@ # Fetch group info (group.id needed when adding feeds to blocks) group = aio.groups("default") -# Create a new dasbhoard named 'Example Dashboard' +# Create a new dashboard named 'Example Dashboard' dashboard = aio.create_dashboard(Dashboard(name="Example Dashboard")) # Create a line_chart @@ -36,6 +37,7 @@ properties = { "gridLines": True, "historyHours": "2"}, + # block_feeds expects a numeric feed_id, not the feed key block_feeds = [{ "group_id": group.id, "feed_id": feed.id diff --git a/examples/basics/digital_in.py b/examples/basics/digital_in.py index c4a40c0..d83cd19 100644 --- a/examples/basics/digital_in.py +++ b/examples/basics/digital_in.py @@ -7,6 +7,7 @@ Author(s): Brent Rubell, Todd Treece """ # Import standard python modules +import os import time # import Adafruit Blinka @@ -16,14 +17,14 @@ # 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' +ADAFRUIT_IO_USERNAME = os.getenv('ADAFRUIT_IO_USERNAME', 'YOUR_AIO_USERNAME') + +# 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 = os.getenv('ADAFRUIT_IO_KEY', 'YOUR_AIO_KEY') # Create an instance of the REST client. aio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY) diff --git a/examples/basics/digital_out.py b/examples/basics/digital_out.py index 5da4f99..4773693 100644 --- a/examples/basics/digital_out.py +++ b/examples/basics/digital_out.py @@ -7,6 +7,7 @@ Author(s): Brent Rubell, Todd Treece """ # Import standard python modules +import os import time # import Adafruit Blinka @@ -16,14 +17,14 @@ # 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' +ADAFRUIT_IO_USERNAME = os.getenv('ADAFRUIT_IO_USERNAME', 'YOUR_AIO_USERNAME') + +# 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 = os.getenv('ADAFRUIT_IO_KEY', 'YOUR_AIO_KEY') # Create an instance of the REST client. aio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY) @@ -42,7 +43,7 @@ while True: try: data = aio.receive(digital.key) - except RequestError as re: + except RequestError: pass # feed with no data will return 404 if int(data.value) == 1: print('received <- ON\n') diff --git a/examples/basics/location.py b/examples/basics/location.py index c772a9c..a4688d0 100644 --- a/examples/basics/location.py +++ b/examples/basics/location.py @@ -7,19 +7,20 @@ Author(s): Brent Rubell, Todd Treece """ # Import standard python modules +import os import time # 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' +ADAFRUIT_IO_USERNAME = os.getenv('ADAFRUIT_IO_USERNAME', 'YOUR_AIO_USERNAME') + +# 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 = os.getenv('ADAFRUIT_IO_KEY', 'YOUR_AIO_KEY') # Create an instance of the REST client. aio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY) diff --git a/examples/basics/neopixel.py b/examples/basics/neopixel.py index 5c6b241..b74067b 100644 --- a/examples/basics/neopixel.py +++ b/examples/basics/neopixel.py @@ -20,6 +20,7 @@ - Adafruit_CircuitPython_NeoPixel (https://github.com/adafruit/Adafruit_CircuitPython_NeoPixel) """ +import os import time import board import neopixel @@ -39,14 +40,14 @@ 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' +ADAFRUIT_IO_USERNAME = os.getenv('ADAFRUIT_IO_USERNAME', 'YOUR_AIO_USERNAME') + +# 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 = os.getenv('ADAFRUIT_IO_KEY', 'YOUR_AIO_KEY') # Create an instance of the REST client. aio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY) diff --git a/examples/basics/pi_camera.py b/examples/basics/pi_camera.py index 4fd26ec..c8e18ce 100644 --- a/examples/basics/pi_camera.py +++ b/examples/basics/pi_camera.py @@ -6,12 +6,12 @@ Adafruit IO feed. """ # import standard python modules -import time import base64 import os +import time # import Adafruit IO REST client -from Adafruit_IO import Client, Feed, RequestError +from Adafruit_IO import Client # import raspberry pi camera module import picamera @@ -19,14 +19,14 @@ # camera capture interval, in seconds CAMERA_INTERVAL = 3 -# 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' +ADAFRUIT_IO_USERNAME = os.getenv('ADAFRUIT_IO_USERNAME', 'YOUR_AIO_USERNAME') + +# 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 = os.getenv('ADAFRUIT_IO_KEY', 'YOUR_AIO_KEY') # Create an instance of the REST client aio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY) @@ -56,7 +56,8 @@ try: aio.send(picam_feed.key, image_string) print('Picture sent to Adafruit IO') - except: + except Exception as e: print('Sending to Adafruit IO Failed...') + print('Error:', e) time.sleep(CAMERA_INTERVAL) diff --git a/examples/basics/publish.py b/examples/basics/publish.py index 6498ee3..47984f7 100644 --- a/examples/basics/publish.py +++ b/examples/basics/publish.py @@ -6,6 +6,7 @@ Author(s): Brent Rubell, Todd Treece for Adafruit Industries """ # Import standard python modules +import os import time # Import Adafruit IO REST client. @@ -14,14 +15,14 @@ # holds the count for the feed run_count = 0 -# 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' +ADAFRUIT_IO_USERNAME = os.getenv('ADAFRUIT_IO_USERNAME', 'YOUR_AIO_USERNAME') + +# 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 = os.getenv('ADAFRUIT_IO_KEY', 'YOUR_AIO_KEY') # Create an instance of the REST client. aio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY) diff --git a/examples/basics/rgb_led.py b/examples/basics/rgb_led.py index 06781bf..9ae06cf 100644 --- a/examples/basics/rgb_led.py +++ b/examples/basics/rgb_led.py @@ -22,6 +22,7 @@ (https://github.com/adafruit/Adafruit_CircuitPython_PCA9685) """ # import system libraries +import os import time # import Adafruit Blinka @@ -39,14 +40,14 @@ GREEN_PIN = 5 BLUE_PIN = 4 -# 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' +ADAFRUIT_IO_USERNAME = os.getenv('ADAFRUIT_IO_USERNAME', 'YOUR_AIO_USERNAME') + +# 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 = os.getenv('ADAFRUIT_IO_KEY', 'YOUR_AIO_KEY') # Create an instance of the REST client. aio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY) diff --git a/examples/basics/servo.py b/examples/basics/servo.py index 54ee95f..f1fe086 100644 --- a/examples/basics/servo.py +++ b/examples/basics/servo.py @@ -24,6 +24,7 @@ """ # import system libraries +import os import time # import Adafruit Blinka @@ -41,12 +42,12 @@ # Set to your Adafruit IO username. # (go to https://accounts.adafruit.com to find your username) -ADAFRUIT_IO_USERNAME = 'YOUR_AIO_USERNAME' +ADAFRUIT_IO_USERNAME = os.getenv('ADAFRUIT_IO_USERNAME', 'YOUR_AIO_USERNAME') # 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' +# so make sure **not** to publish it when you publish this code! +ADAFRUIT_IO_KEY = os.getenv('ADAFRUIT_IO_KEY', 'YOUR_AIO_KEY') # Create an instance of the REST client. aio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY) diff --git a/examples/basics/subscribe.py b/examples/basics/subscribe.py index f9661f6..f80d153 100644 --- a/examples/basics/subscribe.py +++ b/examples/basics/subscribe.py @@ -6,22 +6,23 @@ Author(s): Brent Rubell, Todd Treece for Adafruit Industries """ # Import standard python modules. +import os import sys # This example uses the MQTTClient instead of the REST 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 = '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' +ADAFRUIT_IO_USERNAME = os.getenv('ADAFRUIT_IO_USERNAME', 'YOUR_AIO_USERNAME') + +# 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 = os.getenv('ADAFRUIT_IO_KEY', 'YOUR_AIO_KEY') -# Set to the ID of the feed to subscribe to for updates. -FEED_ID = 'counter' +# Set to the key of the feed to subscribe to for updates. +FEED_KEY = 'counter' # Define callback functions which will be called when certain events happen. def connected(client): @@ -31,20 +32,20 @@ def connected(client): can make calls against it easily. """ # Subscribe to changes on a feed named Counter. - print('Subscribing to Feed {0}'.format(FEED_ID)) - client.subscribe(FEED_ID) + print('Subscribing to Feed {0}'.format(FEED_KEY)) + client.subscribe(FEED_KEY) print('Waiting for feed data...') def disconnected(client): """Disconnected function will be called when the client disconnects.""" sys.exit(1) -def message(client, feed_id, payload): +def message(client, feed_key, 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 feed_key parameter identifies the feed, and the payload parameter has the new value. """ - print('Feed {0} received new value: {1}'.format(feed_id, payload)) + print('Feed {0} received new value: {1}'.format(feed_key, payload)) # Create an MQTT client instance. diff --git a/examples/basics/temp_humidity.py b/examples/basics/temp_humidity.py index 618d5f6..3d76894 100644 --- a/examples/basics/temp_humidity.py +++ b/examples/basics/temp_humidity.py @@ -15,6 +15,7 @@ """ # import standard python modules. +import os import time # import adafruit-blinka modules @@ -32,14 +33,14 @@ # Time between sensor reads, in seconds READ_TIMEOUT = 60 -# 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' +ADAFRUIT_IO_USERNAME = os.getenv('ADAFRUIT_IO_USERNAME', 'YOUR_AIO_USERNAME') + +# 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 = os.getenv('ADAFRUIT_IO_KEY', 'YOUR_AIO_KEY') # Create an instance of the REST client. aio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY) @@ -71,7 +72,7 @@ print('Temp={0:0.1f}*F'.format(temperature)) else: print('Temp={0:0.1f}*C'.format(temperature)) - print('Humidity={1:0.1f}%'.format(humidity)) + print('Humidity={0:0.1f}%'.format(humidity)) # Format sensor data as string for sending to Adafruit IO temperature = '%.2f'%(temperature) humidity = '%.2f'%(humidity) diff --git a/examples/basics/time.py b/examples/basics/time.py index a6204c8..a29349c 100644 --- a/examples/basics/time.py +++ b/examples/basics/time.py @@ -9,17 +9,20 @@ Author: Brent Rubell """ -# Import Adafruit IO REST client. -from Adafruit_IO import Client, Feed, Data, RequestError +# Import standard python modules. +import os -# 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' +# Import Adafruit IO REST client. +from Adafruit_IO import Client # Set to your Adafruit IO username. # (go to https://accounts.adafruit.com to find your username) -ADAFRUIT_IO_USERNAME = 'YOUR_AIO_USERNAME' +ADAFRUIT_IO_USERNAME = os.getenv('ADAFRUIT_IO_USERNAME', 'YOUR_AIO_USERNAME') + +# 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 = os.getenv('ADAFRUIT_IO_KEY', 'YOUR_AIO_KEY') # Create an instance of the REST client. aio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY) diff --git a/examples/basics/type-conversion.py b/examples/basics/type-conversion.py index 4ccb729..86a6647 100644 --- a/examples/basics/type-conversion.py +++ b/examples/basics/type-conversion.py @@ -8,17 +8,20 @@ Author(s): Brent Rubell, Todd Treece for Adafruit Industries """ +# import standard python modules. +import os + # 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' +ADAFRUIT_IO_USERNAME = os.getenv('ADAFRUIT_IO_USERNAME', 'YOUR_AIO_USERNAME') + +# 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 = os.getenv('ADAFRUIT_IO_KEY', 'YOUR_AIO_KEY') aio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY) diff --git a/examples/mqtt/mqtt_air_quality.py b/examples/mqtt/mqtt_air_quality.py new file mode 100644 index 0000000..8d2a3a2 --- /dev/null +++ b/examples/mqtt/mqtt_air_quality.py @@ -0,0 +1,42 @@ +""" +Example of using the Adafruit IO MQTT Client +for subscribing to the Adafruit IO Air Quality Service +Note: This feature is avaliable for IO Plus Subscribers ONLY + +API Documentation: https://io.adafruit.com/services/air_quality + +Author: Brent Rubell for Adafruit Industries +""" +import json +import os +import sys +from Adafruit_IO import MQTTClient + +ADAFRUIT_IO_USERNAME = os.getenv('ADAFRUIT_IO_USERNAME', 'USER') +ADAFRUIT_IO_KEY = os.getenv('ADAFRUIT_IO_KEY', 'KEY') + +# Set to ID of the air quality record to subscribe to for updates +aq_id = 1234 + +def connected(client): + print('Connected to Adafruit IO! Listening for Air Quality changes...') + # Takes an optional forecast: current, forecast_today, forecast_tomorrow + client.subscribe_air_quality(aq_id, "current") + +def disconnected(client): + print('Disconnected from Adafruit IO!') + sys.exit(1) + +def message(client, topic, payload): + print('Air Quality Update!') + print('Air Quality record ID: {0}'.format(topic)) + print(payload) + +client = MQTTClient(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY) + +client.on_connect = connected +client.on_disconnect = disconnected +client.on_message = message + +client.connect() +client.loop_blocking() diff --git a/examples/mqtt/mqtt_client_class.py b/examples/mqtt/mqtt_client_class.py index e7a16ff..583c7ad 100644 --- a/examples/mqtt/mqtt_client_class.py +++ b/examples/mqtt/mqtt_client_class.py @@ -2,6 +2,7 @@ # Author: Tony DiCola # Import standard python modules. +import os import random import sys import time @@ -9,14 +10,14 @@ # 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 = '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' +ADAFRUIT_IO_USERNAME = os.getenv('ADAFRUIT_IO_USERNAME', 'YOUR_AIO_USERNAME') + +# 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 = os.getenv('ADAFRUIT_IO_KEY', 'YOUR_AIO_KEY') # Define callback functions which will be called when certain events happen. @@ -34,11 +35,11 @@ def disconnected(client): print('Disconnected from Adafruit IO!') sys.exit(1) -def message(client, feed_id, payload): +def message(client, feed_key, 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 feed_key parameter identifies the feed, and the payload parameter has # the new value. - print('Feed {0} received new value: {1}'.format(feed_id, payload)) + print('Feed {0} received new value: {1}'.format(feed_key, payload)) # Create an MQTT client instance. diff --git a/examples/mqtt/mqtt_groups_pubsub.py b/examples/mqtt/mqtt_groups_pubsub.py index cf69963..27241ed 100644 --- a/examples/mqtt/mqtt_groups_pubsub.py +++ b/examples/mqtt/mqtt_groups_pubsub.py @@ -4,6 +4,7 @@ # Author: Brent Rubell for Adafruit Industries, 2018 # Import standard python modules. +import os import random import sys import time @@ -11,14 +12,14 @@ # 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 = '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' +ADAFRUIT_IO_USERNAME = os.getenv('ADAFRUIT_IO_USERNAME', 'YOUR_AIO_USERNAME') + +# 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 = os.getenv('ADAFRUIT_IO_KEY', 'YOUR_AIO_KEY') # Group Name group_name = 'grouptest' diff --git a/examples/mqtt/mqtt_shared_feeds.py b/examples/mqtt/mqtt_shared_feeds.py index 23b2f16..bdc3cc2 100644 --- a/examples/mqtt/mqtt_shared_feeds.py +++ b/examples/mqtt/mqtt_shared_feeds.py @@ -9,6 +9,7 @@ """ # Import standard python modules. +import os import sys import time import random @@ -16,14 +17,14 @@ # 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 = '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' +ADAFRUIT_IO_USERNAME = os.getenv('ADAFRUIT_IO_USERNAME', 'YOUR_AIO_USERNAME') + +# 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 = os.getenv('ADAFRUIT_IO_KEY', 'YOUR_AIO_KEY') # Shared IO Feed # Make sure you have read AND write access to this feed to publish. @@ -45,12 +46,12 @@ def disconnected(client): print('Disconnected from Adafruit IO!') sys.exit(1) -def message(client, feed_id, payload): +def message(client, feed_key, 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 feed_key parameter identifies the feed, and the payload parameter has the new value. """ - print('Feed {0} received new value: {1}'.format(feed_id, payload)) + print('Feed {0} received new value: {1}'.format(feed_key, payload)) # Create an MQTT client instance. diff --git a/examples/mqtt/mqtt_subscribe.py b/examples/mqtt/mqtt_subscribe.py index e3b2999..0b92471 100644 --- a/examples/mqtt/mqtt_subscribe.py +++ b/examples/mqtt/mqtt_subscribe.py @@ -3,22 +3,24 @@ # username, and feed to subscribe to for changes. # Import standard python modules. +import os 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 = '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' +ADAFRUIT_IO_USERNAME = os.getenv('ADAFRUIT_IO_USERNAME', 'YOUR_AIO_USERNAME') + +# 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 = os.getenv('ADAFRUIT_IO_KEY', 'YOUR_AIO_KEY') -# Set to the ID of the feed to subscribe to for updates. -FEED_ID = 'DemoFeed' +# Set to the key of the feed to subscribe to for updates. +# Create this feed in your Adafruit IO account and then publish some values +FEED_KEY = 'DemoFeed' # Define callback functions which will be called when certain events happen. @@ -27,24 +29,28 @@ 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 as {0}! Listening for {1} changes...".format( + ADAFRUIT_IO_USERNAME, FEED_KEY + ) + ) # Subscribe to changes on a feed named DemoFeed. - client.subscribe(FEED_ID) + client.subscribe(FEED_KEY) 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])) + print('Subscribed to {0} with QoS {1}'.format(FEED_KEY, granted_qos[0])) 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): +def message(client, feed_key, 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 feed_key parameter identifies the feed, and the payload parameter has # the new value. - print('Feed {0} received new value: {1}'.format(feed_id, payload)) + print('Feed {0} received new value: {1}'.format(feed_key, payload)) # Create an MQTT client instance. diff --git a/examples/mqtt/mqtt_time.py b/examples/mqtt/mqtt_time.py index 19875d3..b643458 100644 --- a/examples/mqtt/mqtt_time.py +++ b/examples/mqtt/mqtt_time.py @@ -8,31 +8,32 @@ """ # Import standard python modules. +import os import sys import time # 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 = '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' +ADAFRUIT_IO_USERNAME = os.getenv('ADAFRUIT_IO_USERNAME', 'YOUR_AIO_USERNAME') + +# 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 = os.getenv('ADAFRUIT_IO_KEY', 'YOUR_AIO_KEY') 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): +def message(client, feed_key, 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 feed_key parameter identifies the feed, and the payload parameter has # the new value. - print('\t Feed {0} received new value: {1}'.format(feed_id, payload)) + print('\t Feed {0} received new value: {1}'.format(feed_key, payload)) # Create a SECURE MQTT client instance diff --git a/examples/mqtt/mqtt_weather.py b/examples/mqtt/mqtt_weather.py index 9d148d4..244e64d 100644 --- a/examples/mqtt/mqtt_weather.py +++ b/examples/mqtt/mqtt_weather.py @@ -9,20 +9,21 @@ """ # Import standard python modules. -import sys import json +import os +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' +ADAFRUIT_IO_USERNAME = os.getenv('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 = os.getenv('ADAFRUIT_IO_KEY', 'KEY') # Set to ID of the forecast to subscribe to for updates forecast_id = 2153 diff --git a/tests/test_mqtt_client.py b/tests/test_mqtt_client.py index fc611e9..6186bc0 100644 --- a/tests/test_mqtt_client.py +++ b/tests/test_mqtt_client.py @@ -22,6 +22,7 @@ import unittest from Adafruit_IO import MQTTClient +from Adafruit_IO.mqtt_client import validate_feed_key import base @@ -122,4 +123,124 @@ def on_message(mqtt_client, feed, payload): client.loop() time.sleep(0) # Verify one update message with payload is received. - self.assertListEqual(messages, [('testfeed', '42')]) \ No newline at end of file + self.assertListEqual(messages, [('testfeed', '42')]) + + +class TestFeedKeyValidation(unittest.TestCase): + """Tests for feed key validation.""" + + def test_valid_feed_keys(self): + """Test that valid feed keys pass validation.""" + valid_keys = [ + 'testfeed', + 'test-feed', + 'test123', + 'TESTFEED', + 'Test-Feed-123', + 'group.feed', + 'group/feed', + 'my-group.my-feed', + 'my-group/my-feed', + ] + for key in valid_keys: + try: + validate_feed_key(key) + except (ValueError, TypeError) as e: + self.fail(f"Valid feed key '{key}' raised exception: {e}") + + def test_empty_feed_key(self): + """Test that empty string raises TypeError due to invalid pattern.""" + with self.assertRaises(TypeError) as context: + validate_feed_key('') + self.assertIn('Feed key must contain', str(context.exception)) + + def test_feed_key_too_long(self): + """Test that feed keys longer than 128 characters raise ValueError.""" + long_key = 'a' * 129 + with self.assertRaises(ValueError) as context: + validate_feed_key(long_key) + self.assertIn('less than 128 characters', str(context.exception)) + + def test_feed_key_invalid_characters(self): + """Test that feed keys with invalid characters raise TypeError.""" + invalid_keys = [ + 'test feed', # space + 'test_feed', # underscore + 'test@feed', # special character + 'test!feed', # special character + 'test#feed', # special character + 'test$feed', # special character + ] + for key in invalid_keys: + with self.assertRaises(TypeError) as context: + validate_feed_key(key) + self.assertIn('Feed key must contain', str(context.exception)) + + def test_feed_key_invalid_patterns(self): + """Test that feed keys with invalid patterns raise TypeError.""" + invalid_keys = [ + '.testfeed', # starts with period + '/testfeed', # starts with slash + 'test.', # ends with period + 'test/', # ends with slash + 'test..feed', # double period + 'test//feed', # double slash + 'test./feed', # period followed by slash + 'test/.feed', # slash followed by period + ] + for key in invalid_keys: + with self.assertRaises(TypeError) as context: + validate_feed_key(key) + self.assertIn('Feed key must contain', str(context.exception)) + + +class TestMQTTClientFeedKeyValidation(unittest.TestCase): + """Tests for feed key validation in MQTT client methods.""" + + def test_subscribe_with_empty_feed_key(self): + """Test that subscribe() raises TypeError with empty feed key.""" + client = MQTTClient('testuser', 'testkey') + with self.assertRaises(TypeError): + client.subscribe('') + + def test_subscribe_with_invalid_feed_key(self): + """Test that subscribe() raises TypeError with invalid feed key.""" + client = MQTTClient('testuser', 'testkey') + with self.assertRaises(TypeError): + client.subscribe('invalid feed') + + def test_publish_with_empty_feed_key(self): + """Test that publish() raises TypeError with empty feed key.""" + client = MQTTClient('testuser', 'testkey') + with self.assertRaises(TypeError): + client.publish('', 42) + + def test_publish_with_invalid_feed_key(self): + """Test that publish() raises TypeError with invalid feed key.""" + client = MQTTClient('testuser', 'testkey') + with self.assertRaises(TypeError): + client.publish('invalid_feed', 42) + + def test_unsubscribe_with_empty_feed_key(self): + """Test that unsubscribe() raises TypeError with empty feed key.""" + client = MQTTClient('testuser', 'testkey') + with self.assertRaises(TypeError): + client.unsubscribe(feed_key='') + + def test_unsubscribe_with_invalid_feed_key(self): + """Test that unsubscribe() raises TypeError with invalid feed key.""" + client = MQTTClient('testuser', 'testkey') + with self.assertRaises(TypeError): + client.unsubscribe(feed_key='test feed') + + def test_receive_with_empty_feed_key(self): + """Test that receive() raises TypeError with empty feed key.""" + client = MQTTClient('testuser', 'testkey') + with self.assertRaises(TypeError): + client.receive('') + + def test_receive_with_invalid_feed_key(self): + """Test that receive() raises TypeError with invalid feed key.""" + client = MQTTClient('testuser', 'testkey') + with self.assertRaises(TypeError): + client.receive('test@feed') \ No newline at end of file