Skip to content

Commit

Permalink
Merge pull request #136 from cj8scrambler/master
Browse files Browse the repository at this point in the history
Add support for dashboards, blocks and layouts
  • Loading branch information
brentru authored Nov 29, 2021
2 parents 0f83289 + ce3d868 commit 92093d4
Show file tree
Hide file tree
Showing 6 changed files with 350 additions and 11 deletions.
4 changes: 2 additions & 2 deletions Adafruit_IO/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,5 @@
from .client import Client
from .mqtt_client import MQTTClient
from .errors import AdafruitIOError, RequestError, ThrottlingError, MQTTError
from .model import Data, Feed, Group
from ._version import __version__
from .model import Data, Feed, Group, Dashboard, Block, Layout
from ._version import __version__
78 changes: 75 additions & 3 deletions Adafruit_IO/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
import requests

from .errors import RequestError, ThrottlingError
from .model import Data, Feed, Group
from .model import Data, Feed, Group, Dashboard, Block, Layout

# set outgoing version, pulled from setup.py
version = pkg_resources.require("Adafruit_IO")[0].version
Expand Down Expand Up @@ -289,11 +289,13 @@ def create_feed(self, feed, group_key=None):
:param string feed: Key of Adafruit IO feed.
:param group_key group: Group to place new feed in.
"""
f = feed._asdict()
del f['id'] # Don't pass id on create call
path = "feeds/"
if group_key is not None: # create feed in a group
path="/groups/%s/feeds"%group_key
return Feed.from_dict(self._post(path, {"feed": feed._asdict()}))
return Feed.from_dict(self._post(path, {"feed": feed._asdict()}))
return Feed.from_dict(self._post(path, {"feed": f}))
return Feed.from_dict(self._post(path, {"feed": f}))

def delete_feed(self, feed):
"""Delete the specified feed.
Expand Down Expand Up @@ -326,3 +328,73 @@ def delete_group(self, group):
"""
path = "groups/{0}".format(group)
self._delete(path)

# Dashboard functionality.
def dashboards(self, dashboard=None):
"""Retrieve a list of all dashboards, or the specified dashboard.
:param string dashboard: Key of Adafruit IO Dashboard. Defaults to None.
"""
if dashboard is None:
path = "dashboards/"
return list(map(Dashboard.from_dict, self._get(path)))
path = "dashboards/{0}".format(dashboard)
return Dashboard.from_dict(self._get(path))

def create_dashboard(self, dashboard):
"""Create the specified dashboard.
:param Dashboard dashboard: Dashboard object to create
"""
path = "dashboards/"
return Dashboard.from_dict(self._post(path, dashboard._asdict()))

def delete_dashboard(self, dashboard):
"""Delete the specified dashboard.
:param string dashboard: Key of Adafruit IO Dashboard.
"""
path = "dashboards/{0}".format(dashboard)
self._delete(path)

# Block functionality.
def blocks(self, dashboard, block=None):
"""Retrieve a list of all blocks from a dashboard, or the specified block.
:param string dashboard: Key of Adafruit IO Dashboard.
:param string block: id of Adafruit IO Block. Defaults to None.
"""
if block is None:
path = "dashboards/{0}/blocks".format(dashboard)
return list(map(Block.from_dict, self._get(path)))
path = "dashboards/{0}/blocks/{1}".format(dashboard, block)
return Block.from_dict(self._get(path))

def create_block(self, dashboard, block):
"""Create the specified block under the specified dashboard.
:param string dashboard: Key of Adafruit IO Dashboard.
:param Block block: Block object to create under dashboard
"""
path = "dashboards/{0}/blocks".format(dashboard)
return Block.from_dict(self._post(path, block._asdict()))

def delete_block(self, dashboard, block):
"""Delete the specified block.
:param string dashboard: Key of Adafruit IO Dashboard.
:param string block: id of Adafruit IO Block.
"""
path = "dashboards/{0}/blocks/{1}".format(dashboard, block)
self._delete(path)

# Layout functionality.
def layouts(self, dashboard):
"""Retrieve the layouts array from a dashboard
:param string dashboard: key of Adafruit IO Dashboard.
"""
path = "dashboards/{0}".format(dashboard)
dashboard = self._get(path)
return Layout.from_dict(dashboard['layouts'])

def update_layout(self, dashboard, layout):
"""Update the layout of the specified dashboard.
:param string dashboard: Key of Adafruit IO Dashboard.
:param Layout layout: Layout object to update under dashboard
"""
path = "dashboards/{0}/update_layouts".format(dashboard)
return Layout.from_dict(self._post(path, {'layouts': layout._asdict()}))
44 changes: 42 additions & 2 deletions Adafruit_IO/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@

FEED_FIELDS = [ 'name',
'key',
'id',
'description',
'unit_type',
'unit_symbol',
Expand All @@ -61,6 +62,26 @@
'properties',
'name' ]

DASHBOARD_FIELDS = [ 'name',
'key',
'description',
'show_header',
'color_mode',
'block_borders',
'header_image_url',
'blocks' ]

BLOCK_FIELDS = [ 'name',
'id',
'visual_type',
'properties',
'block_feeds' ]

LAYOUT_FIELDS = ['xl',
'lg',
'md',
'sm',
'xs' ]

# These are very simple data model classes that are based on namedtuple. This is
# to keep the classes simple and prevent any confusion around updating data
Expand All @@ -71,15 +92,24 @@
Data = namedtuple('Data', DATA_FIELDS)
Feed = namedtuple('Feed', FEED_FIELDS)
Group = namedtuple('Group', GROUP_FIELDS)

Dashboard = namedtuple('Dashboard', DASHBOARD_FIELDS)
Block = namedtuple('Block', BLOCK_FIELDS)
Layout = namedtuple('Layout', LAYOUT_FIELDS)

# Magic incantation to make all parameters to the initializers optional with a
# default value of None.
Group.__new__.__defaults__ = tuple(None for x in GROUP_FIELDS)
Data.__new__.__defaults__ = tuple(None for x in DATA_FIELDS)
Layout.__new__.__defaults__ = tuple(None for x in LAYOUT_FIELDS)

# explicitly set dashboard values so that 'color_mode' is 'dark'
Dashboard.__new__.__defaults__ = (None, None, None, False, "dark", True, None, None)

# explicitly set block values so 'properties' is a dictionary
Block.__new__.__defaults__ = (None, None, None, {}, None)

# explicitly set feed values
Feed.__new__.__defaults__ = (None, None, None, None, None, 'ON', 'Private', None, None, None)
Feed.__new__.__defaults__ = (None, None, None, None, None, None, 'ON', 'Private', None, None, None)

# Define methods to convert from dicts to the data types.
def _from_dict(cls, data):
Expand All @@ -103,7 +133,17 @@ def _group_from_dict(cls, data):
return cls(**params)


def _dashboard_from_dict(cls, data):
params = {x: data.get(x, None) for x in cls._fields}
# Parse the blocks if they're provided and generate block instances.
params['blocks'] = tuple(map(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)
88 changes: 88 additions & 0 deletions examples/basics/dashboard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
"""
'dashboard.py'
=========================================
Creates a dashboard with 3 blocks and feed it data
Author(s): Doug Zobel
"""
from time import sleep
from random import randrange
from Adafruit_IO import Client, Feed, Block, Dashboard, Layout

# Set to your Adafruit IO key.
# Remember, your key is a secret,
# so make sure not to publish it when you publish this code!
ADAFRUIT_IO_USERNAME = ''

# Set to your Adafruit IO username.
# (go to https://accounts.adafruit.com to find your username)
ADAFRUIT_IO_KEY = ''

# Create an instance of the REST client.
aio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY)

# Create a new feed named 'Dashboard Data' under the default group
feed = aio.create_feed(Feed(name="Dashboard Data"), "default")

# Fetch group info (group.id needed when adding feeds to blocks)
group = aio.groups("default")

# Create a new dasbhoard named 'Example Dashboard'
dashboard = aio.create_dashboard(Dashboard(name="Example Dashboard"))

# Create a line_chart
linechart = Block(name="Linechart Data",
visual_type = 'line_chart',
properties = {
"gridLines": True,
"historyHours": "2"},
block_feeds = [{
"group_id": group.id,
"feed_id": feed.id
}])
linechart = aio.create_block(dashboard.key, linechart)

# Create a gauge
gauge = Block(name="Gauge Data",
visual_type = 'gauge',
block_feeds = [{
"group_id": group.id,
"feed_id": feed.id
}])
gauge = aio.create_block(dashboard.key, gauge)

# Create a text stream
stream = Block(name="Stream Data",
visual_type = 'stream',
properties = {
"fontSize": "12",
"fontColor": "#63de00",
"showGroupName": "no"},
block_feeds = [{
"group_id": group.id,
"feed_id": feed.id
}])
stream = aio.create_block(dashboard.key, stream)

# Update the large layout to:
# |----------------|
# | Line Chart |
# |----------------|
# | Gauge | Stream |
# |----------------|
layout = Layout(lg = [
{'x': 0, 'y': 0, 'w': 16, 'h': 4, 'i': str(linechart.id)},
{'x': 0, 'y': 4, 'w': 8, 'h': 4, 'i': str(gauge.id)},
{'x': 8, 'y': 4, 'w': 8, 'h': 4, 'i': str(stream.id)}])
aio.update_layout(dashboard.key, layout)

print("Dashboard created at: " +
"https://io.adafruit.com/{0}/dashboards/{1}".format(ADAFRUIT_IO_USERNAME,
dashboard.key))
# Now send some data
value = 0
while True:
value = (value + randrange(0, 10)) % 100
print('sending data: ', value)
aio.send_data(feed.key, value)
sleep(3)
Loading

0 comments on commit 92093d4

Please sign in to comment.