-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add updated Fortebit and Polaris libraries
- Loading branch information
Showing
7 changed files
with
1,039 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
Oops, something went wrong.