Skip to content

Commit

Permalink
Add updated Fortebit and Polaris libraries
Browse files Browse the repository at this point in the history
  • Loading branch information
ppescher committed Oct 18, 2019
1 parent 2418423 commit 4ff1a14
Show file tree
Hide file tree
Showing 7 changed files with 1,039 additions and 0 deletions.
142 changes: 142 additions & 0 deletions fortebit/iot/http_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import requests
import json
import threading

debug = False


def print_d(*args):
if debug:
print(*args)


class HttpClient():

def __init__(self, endpoint, device_token, ctx=None):
self.token = device_token
self.ctx = ctx
self.connected = False
if ctx is not None:
self.endpoint = "https://" + endpoint
else:
self.endpoint = "http://" + endpoint

def connect(self):
try:
res = requests.get(self._attributes_url() + '/updates?timeout=1000', ctx=self.ctx)
if res.status == 200 or res.status == 408:
self.connected = True
return True
except Exception as e:
print_d("connect", e)
self.connected = False
return False
print_d("connect failed", res.status)
self.connected = False
return False

def is_connected(self):
return self.connected

def on_rpc_request(self, id, method, params):
pass

def _do_loop(self):
while True:
try:
rpc = self._get_rpc_request()
if rpc is not None:
self.on_rpc_request(rpc['id'], rpc['method'], rpc['params'])
except Exception as e:
print_d("http loop", e)
sleep(5000)

def loop(self):
thread(self._do_loop)

def _telemetry_url(self):
return self.endpoint + "/api/v1/" + self.token + "/telemetry"

def _attributes_url(self):
return self.endpoint + "/api/v1/" + self.token + "/attributes"

def _rpc_url(self, id=None):
if id is not None:
return self.endpoint + "/api/v1/" + self.token + "/rpc/" + str(id)
return self.endpoint + "/api/v1/" + self.token + "/rpc"

def publish_telemetry(self, values, ts=None):
if ts is not None:
values = {'values': values, 'ts': ts}
try:
res = requests.post(self._telemetry_url(), json=values, ctx=self.ctx)
except Exception as e:
print_d(e)
return False
if res.status != 200:
return False
return True

def publish_attributes(self, client):
try:
res = requests.post(self._attributes_url(), json=client, ctx=self.ctx)
except Exception as e:
print_d(e)
return False
if res.status != 200:
return False
return True

def get_attributes(self, client, shared=None, timeout=20000):
obj = {}
if client and isinstance(client, PSTRING):
obj['clientKeys'] = client
else:
client = None
if shared and isinstance(shared, PSTRING):
obj['sharedKeys'] = shared
else:
shared = None
try:
res = requests.get(self._attributes_url(), params=obj, ctx=self.ctx)
if res.status != 200:
return None
obj = json.loads(res.content)
except Exception as e:
print_d(e)
return None
if obj is None:
return None
if 'client' in obj:
client = obj['client']
else:
client = None
if 'shared' in obj:
shared = obj['shared']
else:
shared = None
return (client, shared)

def _get_rpc_request(self, timeout=20000):
obj = {'timeout': timeout}
try:
res = requests.get(self._rpc_url(), params=obj, ctx=self.ctx)
if res.status != 200:
return None
obj = json.loads(res.content)
self.connected = True
return obj
except Exception as e:
print_d(e)
self.connected = False
return None

def publish_rpc_reply(self, id, result):
try:
res = requests.post(self._rpc_url(id), json=result, ctx=self.ctx)
except Exception as e:
print_d(e)
return False
if res.status != 200:
return False
return True
131 changes: 131 additions & 0 deletions fortebit/iot/iot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
"""
.. module:: iot
********************
Fortebit IoT Library
********************
The Zerynth Fortebit IoT Library can be used to ease the connection to the `Fortebit IoT Cloud <https://fortebit.tech/cloud/>`_.
It makes your device act as a Fortebit IoT Device that can be monitored and controlled on the Fortebit IoT Cloud dashboard.
The device always send and receive data in the JSON format.
"""


class Device():
"""
================
The Device class
================
.. class:: Device(device_token, client, ctx=None)
Create a Device instance representing a Fortebit IoT device.
The device is provisioned by the :samp:`device_token`, obtained from the Fortebit dashboard upon the creation of a new device.
The :samp:`client` parameter is a class that provides the implementation of the low level details for the connection.
It can be one of :samp:`MqttClient` in the :samp:`mqtt_client` module, or :samp:`HttpClient` in the :samp:`http_client` module.
The optional :samp:`ctx` parameter is an initialized secure socket context.
"""

def __init__(self, device_token, client, ctx=None):
self.device_token = device_token
self.endpoint = "cloud.fortebit.tech"
self.client = client(self.endpoint, device_token, ctx)

def listen_rpc(self, callback):
"""
.. method:: listen_rpc(callback)
Listen to incoming RPC requests that get reported to the specified :samp:`callback` function,
called as *callback(id, method, params)*:
* :samp:`id` is the request identifier (number)
* :samp:`method` is the method identifier of the RPC (Remote Procedure Call)
* :samp:`params` is a dictionary containing the RPC method arguments (or parameters)
Call :func:`send_rpc_reply` to provide the result of the RPC request.
"""
self.client.on_rpc_request = callback

def connect(self, retry=7):
"""
.. method:: connect()
Setup a connection to the Fortebit Cloud. It can raise an exception in case of error.
"""
if self.client.connect():
return True
t = 3000
while (retry > 0):
sleep(t)
if self.client.connect():
return True
# increase (double) the delay between attempts (up to 1 minute)
if t < 30000:
t *= 2
retry -= 1
return False

def is_connected(self):
"""
.. method:: is_connected()
Returns the status of the connection to the Fortebit Cloud (reconnections are automatic).
"""
return self.client.is_connected()

def run(self):
"""
.. method:: run()
Starts the device by executing the underlying client. It can start a new thread depending on the type of client (Mqtt vs Http)
"""
self.client.loop()

def publish_telemetry(self, values, ts=None):
"""
.. method:: publish_telemetry(values, ts)
Publish :samp:`values` (dictionary) to the device telemetry, with optional timestamp :samp:`ts` (epoch in milliseconds).
Return a boolean, *False* if the message cannot be sent.
"""
return self.client.publish_telemetry(values, ts)

def publish_attributes(self, attributes):
"""
.. method:: publish_attributes(attributes)
Publish :samp:`attributes` (dictionary) to the device *client* attributes.
Return a boolean, *False* if the message cannot be sent.
"""
return self.client.publish_attributes(attributes)

def get_attributes(self, client, shared=None, timeout=10000):
"""
.. method:: get_attributes(client, shared, timeout)
Obtain the specified :samp:`client` and/or :samp:`shared` attributes from the device.
Return a dictionary, *None* if the data could not be received.
"""
return self.client.get_attributes(client, shared, timeout)

def send_rpc_reply(self, id, result):
"""
.. method:: send_rpc_reply(id, result)
Publish :samp:`result` (dictionary) as a reply to the RPC request with identifier :samp:`id`.
Return a boolean, *False* if the message cannot be sent.
"""
return self.client.send_rpc_reply(id, result)
Loading

0 comments on commit 4ff1a14

Please sign in to comment.