Compare commits

..

1 commit

Author SHA1 Message Date
Kattni
8560921438
Update BME280 import for new lib. 2021-06-16 13:53:29 -04:00
21 changed files with 220 additions and 659 deletions

View file

@ -1,23 +1,18 @@
name: Build-CI
on:
push:
branches:
- master
on: [pull_request, push]
jobs:
build:
runs-on: ubuntu-latest
environment:
name: IO
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v4
- name: Set up Python 3.6
uses: actions/setup-python@v1
with:
python-version: '3.10'
python-version: 3.6
- name: Install dependencies
run: |
@ -35,8 +30,6 @@ 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 ..

View file

@ -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, Dashboard, Block, Layout
from ._version import __version__
from .model import Data, Feed, Group
from ._version import __version__

View file

@ -1 +1 @@
__version__ = "2.8.0"
__version__ = "2.5.0"

View file

@ -18,22 +18,16 @@
# 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
import pkg_resources
import re
from urllib.parse import urlparse
from urllib.parse import parse_qs
# import logging
import requests
from .errors import RequestError, ThrottlingError
from .model import Data, Feed, Group, Dashboard, Block, Layout
DEFAULT_PAGE_LIMIT = 100
from .model import Data, Feed, Group
# set outgoing version, pulled from setup.py
version = pkg_resources.require("Adafruit_IO")[0].version
@ -66,13 +60,9 @@ class Client(object):
# 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.
:param int data: Color value, in hexadecimal.
"""
return ((int(data[1], 16))*16) + int(data[2], 16)
@ -80,7 +70,6 @@ class Client(object):
@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)
@ -88,7 +77,6 @@ class Client(object):
@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)
@ -123,12 +111,10 @@ class Client(object):
def _compose_url(self, path):
return '{0}/api/{1}/{2}/{3}'.format(self.base_url, 'v2', self.username, path)
def _get(self, path, params=None):
def _get(self, path):
response = requests.get(self._compose_url(path),
headers=self._headers({'X-AIO-Key': self.key}),
proxies=self.proxies,
params=params)
self._last_response = response
proxies=self.proxies)
self._handle_error(response)
return response.json()
@ -138,7 +124,6 @@ class Client(object):
'Content-Type': 'application/json'}),
proxies=self.proxies,
data=json.dumps(data))
self._last_response = response
self._handle_error(response)
return response.json()
@ -147,7 +132,6 @@ class Client(object):
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.
@ -156,7 +140,6 @@ class Client(object):
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.
@ -177,7 +160,6 @@ class Client(object):
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.
"""
@ -190,40 +172,22 @@ class Client(object):
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, timezone=None):
"""Returns a struct_time from the Adafruit IO Server based on requested
timezone, or automatically based on the device's IP address.
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
: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
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']))
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
:param int id: optional ID for retrieving a specified weather record.
"""
if weather_id:
@ -235,7 +199,6 @@ class Client(object):
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:
@ -247,7 +210,6 @@ class Client(object):
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)
@ -256,7 +218,6 @@ class Client(object):
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)
@ -265,66 +226,27 @@ class Client(object):
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)
return Data.from_dict(self._get(path))
def data(self, feed, data_id=None, max_results=DEFAULT_PAGE_LIMIT):
def data(self, feed, data_id=None):
"""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 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))
params = {'limit': max_results} if max_results else None
data = []
path = "feeds/{0}/data".format(feed)
while len(data) < max_results:
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:
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]
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))
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.
"""
@ -333,7 +255,6 @@ class Client(object):
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.
"""
@ -344,7 +265,6 @@ class Client(object):
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:
@ -355,21 +275,17 @@ class Client(object):
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.
"""
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": f}))
return Feed.from_dict(self._post(path, {"feed": f}))
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):
"""Delete the specified feed.
:param string feed: Name/Key/ID of Adafruit IO feed.
"""
path = "feeds/{0}".format(feed)
@ -378,7 +294,6 @@ class Client(object):
# 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:
@ -389,7 +304,6 @@ class Client(object):
def create_group(self, group):
"""Create the specified group.
:param string group: Name/Key/ID of Adafruit IO Group.
"""
path = "groups/"
@ -397,86 +311,7 @@ class Client(object):
def delete_group(self, group):
"""Delete the specified group.
:param string group: Name/Key/ID of Adafruit IO 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()}))

View file

@ -20,7 +20,14 @@
# SOFTWARE.
import json, requests
from paho.mqtt.client import error_string
# MQTT RC Error Types
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."""
@ -56,6 +63,6 @@ class MQTTError(Exception):
"""Handles connection attempt failed errors.
"""
def __init__(self, response):
error = error_string(response)
error = MQTT_ERRORS[response]
super(MQTTError, self).__init__(error)
pass

View file

@ -43,7 +43,6 @@ DATA_FIELDS = [ 'created_epoch',
FEED_FIELDS = [ 'name',
'key',
'id',
'description',
'unit_type',
'unit_symbol',
@ -62,26 +61,6 @@ GROUP_FIELDS = [ 'description',
'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
@ -92,24 +71,15 @@ LAYOUT_FIELDS = ['xl',
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, None, 'ON', 'Private', None, None, None)
Feed.__new__.__defaults__ = (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):
@ -133,17 +103,7 @@ 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(Block.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)

View file

@ -59,8 +59,8 @@ class MQTTClient(object):
self.on_disconnect = None
self.on_message = None
self.on_subscribe = None
# Initialize v1 MQTT client.
self._client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION1)
# Initialize MQTT client.
self._client = mqtt.Client()
if secure:
self._client.tls_set_context()
self._secure = True

View file

@ -9,7 +9,7 @@ Adafruit IO Python
:target: https://adafru.it/discord
:alt: Chat
.. image:: https://github.com/adafruit/Adafruit_IO_Python/workflows/Build-CI/badge.svg
.. 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
@ -20,7 +20,7 @@ Adafruit IO Python
A Python library and examples for use with `io.adafruit.com <https://io.adafruit.com>`_.
Compatible with Python Versions 3.6+
Compatible with Python Versions 3.4+
Installation
================

View file

@ -29,7 +29,7 @@ master_doc = 'index'
# General information about the project.
project = u'adafruit-io-python'
copyright = u'2023 Adafruit Industries'
copyright = u'2019 Adafruit Industries'
author = u'Adafruit Industries'
# The version info for the project you're documenting, acts as replacement for
@ -46,7 +46,7 @@ release = u'2.1.0'
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = "en"
language = None
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.

View file

@ -33,19 +33,6 @@ 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.
@ -92,9 +79,7 @@ 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)]
# send batch data
aio.send_batch_data(temperature.key, data_list)
aio.create_data('Test', data)
Receive Data

View file

@ -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 USERNAME', 'YOUR ADAFRUIT IO KEY')
aio = Client('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 USERNAME', 'YOUR ADAFRUIT IO KEY')
aio = Client('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 USERNAME', 'YOUR ADAFRUIT IO KEY')
aio = Client('YOUR ADAFRUIT IO KEY')
# Get feed 'Foo'
feed = aio.feeds('Foo')

View file

@ -12,8 +12,8 @@ import json
from Adafruit_IO import Client, Feed, RequestError
# Set to your Adafruit IO key.
ADAFRUIT_IO_USERNAME = 'YOUR_IO_USERNAME'
ADAFRUIT_IO_KEY = 'YOUR_IO_KEY'
ADAFRUIT_IO_USERNAME = 'brubell'
ADAFRUIT_IO_KEY = '6ec4b31bd2c54a09be911e0c1909b7ab'
# Create an instance of the REST client.
aio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY)
@ -25,4 +25,4 @@ 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']))
print('Random Data: {0}'.format(data['value']))

View file

@ -1,88 +0,0 @@
"""
'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)

View file

@ -40,10 +40,7 @@ led.direction = digitalio.Direction.OUTPUT
while True:
try:
data = aio.receive(digital.key)
except RequestError as re:
pass # feed with no data will return 404
data = aio.receive(digital.key)
if int(data.value) == 1:
print('received <- ON\n')
elif int(data.value) == 0:

View file

@ -0,0 +1,133 @@
"""
'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
from adafruit_bme280 import basic as 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)

View file

@ -1,69 +0,0 @@
"""
`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)

View file

@ -1,7 +1,8 @@
"""
'temp_humidity.py'
==================================
Example of sending temperature and humidity data to Adafruit IO
Example of sending analog sensor
values to an Adafruit IO feed.
Author(s): Brent Rubell
@ -10,27 +11,24 @@ Tutorial Link: Tutorial Link: https://learn.adafruit.com/adafruit-io-basics-temp
Dependencies:
- Adafruit IO Python Client
(https://github.com/adafruit/io-client-python)
- Adafruit_CircuitPython_AHTx0
(https://github.com/adafruit/Adafruit_CircuitPython_AHTx0)
- Adafruit_Python_DHT
(https://github.com/adafruit/Adafruit_Python_DHT)
"""
# import standard python modules.
import time
# import adafruit-blinka modules
import board
# import adafruit dht library.
import Adafruit_DHT
# import Adafruit IO REST client.
from Adafruit_IO import Client, Feed, RequestError
from Adafruit_IO import Client, Feed
# Import AHTx0 library
import adafruit_ahtx0
# Delay in-between sensor readings, in seconds.
DHT_READ_TIMEOUT = 5
# Set true to send tempertaure data in degrees fahrenheit ('f')?
USE_DEGREES_F = False
# Time between sensor reads, in seconds
READ_TIMEOUT = 60
# Pin connected to DHT22 data pin
DHT_DATA_PIN = 26
# Set to your Adafruit IO key.
# Remember, your key is a secret,
@ -44,40 +42,23 @@ ADAFRUIT_IO_USERNAME = 'YOUR_AIO_USERNAME'
# Create an instance of the REST client.
aio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY)
# 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)
# Set up Adafruit IO Feeds.
temperature_feed = aio.feeds('temperature')
humidity_feed = aio.feeds('humidity')
# 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)
# 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)
# Set up DHT22 Sensor.
dht22_sensor = Adafruit_DHT.DHT22
while True:
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))
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))
else:
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))
print('Failed to get DHT22 Reading, trying again in ', DHT_READ_TIMEOUT, 'seconds')
# Timeout to avoid flooding Adafruit IO
time.sleep(READ_TIMEOUT)
time.sleep(DHT_READ_TIMEOUT)

View file

@ -70,5 +70,5 @@ print('Publishing a new message every 10 seconds (press Ctrl-C to quit)...')
while True:
value = random.randint(0, 100)
print('Publishing {0} to {1}.'.format(value, IO_FEED))
client.publish(IO_FEED, value, feed_user=IO_FEED_USERNAME)
client.publish(IO_FEED, value, IO_FEED_USERNAME)
time.sleep(10)

View file

@ -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):
def on_message(client, userdata, msg, retain):
print('Received on {0}: {1}'.format(msg.topic, msg.payload.decode('utf-8')))
# Create Paho v1 MQTT client and connect to Adafruit IO.
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION1)
# Create MQTT client and connect to Adafruit IO.
client = mqtt.Client()
client.username_pw_set(USERNAME, KEY)
client.on_connect = on_connect
client.on_disconnect = on_disconnect

View file

@ -3,7 +3,7 @@
import time
import unittest
from Adafruit_IO import Client, Data, Feed, Group, Dashboard, Block, Layout, RequestError
from Adafruit_IO import Client, Data, Feed, Group, RequestError
import base
@ -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):
@ -46,25 +46,9 @@ class TestClient(base.IOTestCase):
# 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, max_results=None)
data = client.data(feed)
for d in data:
client.delete(feed, d.id)
@ -154,7 +138,7 @@ class TestClient(base.IOTestCase):
data = Data(value=42)
result = aio.create_data('testfeed', data)
self.assertEqual(int(result.value), 42)
def test_location_data(self):
"""receive_location
"""
@ -176,41 +160,11 @@ class TestClient(base.IOTestCase):
"""receive_time
"""
aio = self.get_client()
server_time = aio.receive_time(timezone='UTC')
time = aio.receive_time()
# Check that each value is rx'd properly
# (should never be None type)
for time_data in server_time:
for time_data in 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):
@ -315,95 +269,3 @@ class TestClient(base.IOTestCase):
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)
if __name__ == "__main__":
unittest.main()

View file

@ -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, Dashboard, Block, Layout
from Adafruit_IO import Data, Feed, Group
import base
@ -45,12 +45,11 @@ class TestData(base.IOTestCase):
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, None, 'ON', 'Private', None, None, None)
Feed.__new__.__defaults__ = (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)
@ -70,40 +69,6 @@ class TestData(base.IOTestCase):
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})