Compare commits
1 commit
master
...
kattni-bme
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8560921438 |
21 changed files with 220 additions and 659 deletions
17
.github/workflows/build.yml
vendored
17
.github/workflows/build.yml
vendored
|
|
@ -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 ..
|
||||
|
|
|
|||
|
|
@ -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__
|
||||
|
|
@ -1 +1 @@
|
|||
__version__ = "2.8.0"
|
||||
__version__ = "2.5.0"
|
||||
|
|
|
|||
|
|
@ -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()}))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
================
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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']))
|
||||
|
|
@ -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)
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
133
examples/basics/environmental_monitor.py
Normal file
133
examples/basics/environmental_monitor.py
Normal 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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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})
|
||||
|
|
|
|||
Loading…
Reference in a new issue