Skip to content

Commit

Permalink
Merge pull request #93 from brentru/enhance-send-data
Browse files Browse the repository at this point in the history
Enhanced send_data method
  • Loading branch information
brentru authored Mar 5, 2019
2 parents ab83808 + bbb00f8 commit e614f56
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 84 deletions.
2 changes: 1 addition & 1 deletion Adafruit_IO/_version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "2.2"
__version__ = "2.3"
139 changes: 72 additions & 67 deletions Adafruit_IO/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import json
import pkg_resources
import platform
import pkg_resources
# import logging

import requests
Expand Down Expand Up @@ -52,21 +52,50 @@ def __init__(self, username, key, proxies=None, base_url='https://io.adafruit.co
self.username = username
self.key = key
self.proxies = proxies
# self.logger = logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
# self.logger = logging.basicConfig(level=logging.DEBUG,
# format='%(asctime)s - %(levelname)s - %(message)s')

# Save URL without trailing slash as it will be added later when
# constructing the path.
self.base_url = base_url.rstrip('/')

@staticmethod
def to_red(data):
"""Hex color feed to red channel.
:param int data: Color value, in hexadecimal.
"""
return ((int(data[1], 16))*16) + int(data[2], 16)

def _compose_url(self, path, is_time=None):
if is_time: # return a call to https://io.adafruit.com/api/v2/time/{unit}
return '{0}/api/{1}/{2}'.format(self.base_url, 'v2', path)
else:
return '{0}/api/{1}/{2}/{3}'.format(self.base_url, 'v2', self.username, path)
@staticmethod
def to_green(data):
"""Hex color feed to green channel.
:param int data: Color value, in hexadecimal.
"""
return (int(data[3], 16) * 16) + int(data[4], 16)

@staticmethod
def to_blue(data):
"""Hex color feed to blue channel.
:param int data: Color value, in hexadecimal.
"""
return (int(data[5], 16) * 16) + int(data[6], 16)

@staticmethod
def _headers(given):
headers = default_headers.copy()
headers.update(given)
return headers

@staticmethod
def _create_payload(value, metadata):
if metadata is not None:
payload = Data(value=value, lat=metadata['lat'], lon=metadata['lon'],
ele=metadata['ele'], created_at=metadata['created_at'])
return payload
return Data(value=value)

def _handle_error(self, response):
@staticmethod
def _handle_error(response):
# Throttling Error
if response.status_code == 429:
raise ThrottlingError()
Expand All @@ -78,10 +107,10 @@ def _handle_error(self, response):
raise RequestError(response)
# Else do nothing if there was no error.

def _headers(self, given):
headers = default_headers.copy()
headers.update(given)
return headers
def _compose_url(self, path, is_time=None):
if is_time: # return a call to https://io.adafruit.com/api/v2/time/{unit}
return '{0}/api/{1}/{2}'.format(self.base_url, 'v2', path)
return '{0}/api/{1}/{2}/{3}'.format(self.base_url, 'v2', self.username, path)

def _get(self, path, is_time=None):
response = requests.get(self._compose_url(path, is_time),
Expand All @@ -90,8 +119,7 @@ def _get(self, path, is_time=None):
self._handle_error(response)
if not is_time:
return response.json()
else: # time doesn't need to serialize into json, just return text
return response.text
return response.text

def _post(self, path, data):
response = requests.post(self._compose_url(path),
Expand All @@ -105,20 +133,28 @@ def _post(self, path, data):
def _delete(self, path):
response = requests.delete(self._compose_url(path),
headers=self._headers({'X-AIO-Key': self.key,
'Content-Type': 'application/json'}),
'Content-Type': 'application/json'}),
proxies=self.proxies)
self._handle_error(response)

# Data functionality.
def send_data(self, feed, value):
def send_data(self, feed, value, metadata=None, precision=None):
"""Helper function to simplify adding a value to a feed. Will append the
specified value to the feed identified by either name, key, or ID.
Returns a Data instance with details about the newly appended row of data.
Note that send_data now operates the same as append.
:param string feed: Name/Key/ID of Adafruit IO feed.
:param string value: Value to send.
:param dict metadata: Optional metadata associated with the value.
:param int precision: Optional amount of precision points to send.
"""
return self.create_data(feed, Data(value=value))
if precision:
try:
value = round(value, precision)
except NotImplementedError:
raise NotImplementedError("Using the precision kwarg requires a float value")
payload = self._create_payload(value, metadata)
return self.create_data(feed, payload)

send = send_data

Expand All @@ -144,42 +180,32 @@ def append(self, feed, value):
"""
return self.create_data(feed, Data(value=value))

def send_location_data(self, feed, lat, lon, ele, value=None):
"""Sends locational data to a feed.
:param string feed: Name/Key/ID of Adafruit IO feed.
:param int lat: Latitude.
:param int lon: Longitude.
:param int ele: Elevation.
:param int value: Optional value to send, defaults to None.
"""
return self.create_data(feed, Data(value=value,lat=lat, lon=lon, ele=ele))

def receive_time(self, time):
"""Returns the time from the Adafruit IO server.
:param string time: Time to be returned: `millis`, `seconds`, `ISO-8601`.
"""
timepath = "time/{0}".format(time)
return self._get(timepath, is_time=True)

def receive_weather(self, weather_id=None):
"""Adafruit IO Weather Service, Powered by Dark Sky
:param int id: optional ID for retrieving a specified weather record.
"""
if weather_id:
weather_path = "integrations/weather/{0}".format(weather_id)
weather_path = "integrations/weather/{0}".format(weather_id)
else:
weather_path = "integrations/weather"
weather_path = "integrations/weather"
return self._get(weather_path)
def receive_random(self, id=None):

def receive_random(self, randomizer_id=None):
"""Access to Adafruit IO's Random Data
service.
:param int id: optional ID for retrieving a specified randomizer.
:param int randomizer_id: optional ID for retrieving a specified randomizer.
"""
if id:
random_path = "integrations/words/{0}".format(id)
if randomizer_id:
random_path = "integrations/words/{0}".format(randomizer_id)
else:
random_path = "integrations/words"
random_path = "integrations/words"
return self._get(random_path)

def receive(self, feed):
Expand All @@ -191,7 +217,7 @@ def receive(self, feed):
return Data.from_dict(self._get(path))

def receive_next(self, feed):
"""Retrieve the next unread value from the specified feed. Returns a Data
"""Retrieve the next unread value from the specified feed. Returns a Data
instance whose value property holds the retrieved value.
:param string feed: Name/Key/ID of Adafruit IO feed.
"""
Expand All @@ -215,16 +241,15 @@ def data(self, feed, data_id=None):
if data_id is None:
path = "feeds/{0}/data".format(feed)
return list(map(Data.from_dict, self._get(path)))
else:
path = "feeds/{0}/data/{1}".format(feed, data_id)
return Data.from_dict(self._get(path))
path = "feeds/{0}/data/{1}".format(feed, data_id)
return Data.from_dict(self._get(path))

def create_data(self, feed, data):
"""Create a new row of data in the specified feed.
Returns a Data instance with details about the newly
Returns a Data instance with details about the newly
appended row of data.
:param string feed: Name/Key/ID of Adafruit IO feed.
:param Data data: Instance of the Data class. Must have a value property set.
:param Data data: Instance of the Data class. Must have a value property set.
"""
path = "feeds/{0}/data".format(feed)
return Data.from_dict(self._post(path, data._asdict()))
Expand All @@ -237,24 +262,6 @@ def delete(self, feed, data_id):
path = "feeds/{0}/data/{1}".format(feed, data_id)
self._delete(path)

def toRed(self, data):
"""Hex color feed to red channel.
:param int data: Color value, in hexadecimal.
"""
return ((int(data[1], 16))*16) + int(data[2], 16)

def toGreen(self, data):
"""Hex color feed to green channel.
:param int data: Color value, in hexadecimal.
"""
return (int(data[3], 16) * 16) + int(data[4], 16)

def toBlue(self, data):
"""Hex color feed to blue channel.
:param int data: Color value, in hexadecimal.
"""
return (int(data[5], 16) * 16) + int(data[6], 16)

# feed functionality.
def feeds(self, feed=None):
"""Retrieve a list of all feeds, or the specified feed. If feed is not
Expand All @@ -264,9 +271,8 @@ def feeds(self, feed=None):
if feed is None:
path = "feeds"
return list(map(Feed.from_dict, self._get(path)))
else:
path = "feeds/{0}".format(feed)
return Feed.from_dict(self._get(path))
path = "feeds/{0}".format(feed)
return Feed.from_dict(self._get(path))

def create_feed(self, feed):
"""Create the specified feed.
Expand All @@ -290,9 +296,8 @@ def groups(self, group=None):
if group is None:
path = "groups/"
return list(map(Group.from_dict, self._get(path)))
else:
path = "groups/{0}".format(group)
return Group.from_dict(self._get(path))
path = "groups/{0}".format(group)
return Group.from_dict(self._get(path))

def create_group(self, group):
"""Create the specified group.
Expand All @@ -306,4 +311,4 @@ def delete_group(self, group):
:param string group: Name/Key/ID of Adafruit IO Group.
"""
path = "groups/{0}".format(group)
self._delete(path)
self._delete(path)
26 changes: 15 additions & 11 deletions examples/api/location.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
"""
'location.py'
==================================
Example of sending location over an
Adafruit IO feed to a Map Dashboard
block
Example of sending metadata
associated with a data point.
Author(s): Brent Rubell
"""
Expand All @@ -12,9 +11,14 @@
from Adafruit_IO import Client, Feed, RequestError

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

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

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

Expand All @@ -25,12 +29,12 @@
feed = Feed(name="location")
location = aio.create_feed(feed)


# Top Secret Adafruit HQ Location
value = 1
lat = 40.726190
lon = -74.005334
ele = 6 # elevation above sea level (meters)
value = 42
# Set metadata associated with value
metadata = {'lat': 40.726190,
'lon': -74.005334,
'ele': -6,
'created_at': None}

# Send location data to Adafruit IO
aio.send_location_data(location.key, lat, lon, ele, value)
aio.send_data(location.key, value, metadata)
14 changes: 9 additions & 5 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,12 +133,16 @@ def test_location_data(self):
aio = self.get_client()
self.ensure_feed_deleted(aio, 'testlocfeed')
test_feed = aio.create_feed(Feed(name='testlocfeed'))
aio.send_location_data(test_feed.key, 40, -74, 6, 0)
metadata = {'lat': 40.726190,
'lon': -74.005334,
'ele': -6,
'created_at': None}
aio.send_data(test_feed.key, 40, metadata)
data = aio.receive(test_feed.key)
self.assertEqual(int(data.value), 0.0)
self.assertEqual(float(data.lat), 40.0)
self.assertEqual(float(data.lon), -74.0)
self.assertEqual(float(data.ele), 6.0)
self.assertEqual(int(data.value), 40)
self.assertEqual(float(data.lat), 40.726190)
self.assertEqual(float(data.lon), -74.005334)
self.assertEqual(float(data.ele), -6.0)

# Test Feed Functionality
def test_append_by_feed_name(self):
Expand Down

0 comments on commit e614f56

Please sign in to comment.