diff --git a/README.md b/README.md index c3bf7ad..5d3f870 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,232 @@ -#gateway-python-sdk +# gateway-python-sdk -Python SDK for DevIoT gateway service +### Python SDK for DevIoT service ([https://deviot.cisco.com/](https://deviot.cisco.com/)) + +You can use this SDK to register devices to DevIoT and sync up data and actions between the devices and DevIoT. + +## Table of contents + +* [Getting Started](#getting-started) +* [API](#api) + +## Requirement +1. [Python](https://www.python.org/downloads/): This SDK is written in Python. We support both Python 2 and 3. +2. [paho-mqtt](https://eclipse.org/paho/clients/python/): This SDK uses this library to build an MQTT client + +## Usage +1. You can use SDK to register your own sensors and systems to DevIoT. +2. Use [Starter-kit](https://wwwin-github.cisco.com/DevIoT/gateway-python-starter-kit) to run sample codes + +## Term + +- **Gateway**: 'Gateway' is a device connected to DevIoT like a Raspberry Pi board or a mobile phone +- **Thing**: 'Thing' is a sensor or a module in a gateway like a light sensor, LED, or GPS. A thing is an icon in DevIoT. A gateway can have several things. +- **Property**: 'Property' is a variable measured by a thing. For instance, Latitude and longitude are properties of a GPS. A thing can have several properties. +- **Action**: 'Action' is a command for a module. For example, turning on and off LED is two different actions. A thing can have several actions. + +## Getting Started +#### 0) Check the version of python (terminal) +Before using this SDK, you need to check the version of python. The master branch is written in **Python 2**. You can check it with the following command. +``` +python --version +``` + +If your python version is 3.x, you need to switch to 'python3' branch in the next step. + + +#### 1) Set up SDK package (terminal) +Clone SDK git repository +``` +git clone https://github.com/ailuropoda0/gateway-python-sdk.git +cd gateway-python-sdk +``` +(**ONLY for Python 3 users**) Switch to Python 3 +``` +git fetch origin python3:python3 +git checkout python3 +``` + +Install SDK package on python +``` +python setup.py install +``` + +#### 2) Connect gateway (python code) +You can refer the example code in [gateway-python-starter-kit](https://wwwin-github.cisco.com/DevIoT/gateway-python-starter-kit). + +Import SDK +``` +from cisco_deviot.gateway import Gateway +from cisco_deviot.thing import Thing, Property, PropertyType +``` +Construct a Gateway object +``` +account = "your_id@cisco.com" # enter your DevIoT account +app = Gateway(name="gateway_name", account=account) +``` + +Construct a Thing instance +``` +thing = Thing("thing-id", "thing-name", "thing-kind") +``` +A Thing instance can have both *properties* and *actions*. But you can only use it as *either* an input component *or* an output component in each project. + +In DevIoT project page, click the 'Connect' button and connect the thing component with a rules engine by double-clicking them. The type of the component is determined by which component is first double-clicked between the thing component (input) and the rules engine (output). + +(1) Input: Add a property to the thing +``` +property = Property("variable_name", PropertyType.INT, 0) +thing.add_property(property); +``` + +(2) Output: Add an action to the thing +``` +thing.add_action("sameple_action") +def custom_function(): ## define your action function + print("action") + +thing.sameple_action = custom_function +``` + +Register the sensor to the gateway +``` +app.register(thing) +``` + +Connect the gateway to the server +``` +app.start() +``` + +#### 3) disconnect gateway (python code) +disconnect gateway +``` +app.stop() +``` +  +## API +### Gateway +#### Constructor +``` +Gateway(name, deviot_server="deviot.cisco.com", connector_server="deviot.cisco.com:18883", kind="device", account="") +``` +The Gateway() constructor takes the following arguments: +**name** +The unique name of a gateway +**deviot_server** +The address for the DevIoT server. The default value is 'deviot.cisco.com'. It does not need to add the protocol. The default protocol is HTTPS(secure connection). +**connector_server** +The address for the MQTT server. The default value is 'deviot.cisco.com:18883'. It does not need to add the protcol. The default protocol is TCP(unsecure connection) and the default port number is 1883. +**kind** +The kind of a gateway +**account** +Your DevIoT account. you also can use empty string, it will let all account get the data of your gateway. +#### register() +``` +register(*things) +``` +The register() function adds things to the gateway. The thing should not have been already registered. +**thing** +A *Thing* instance to register +#### load() +``` +load(filename, class_directory=None) +``` +The load() function registers things from an JSON file named [filename] and the custom Thing-sub classes inside [class_directory]. +**filename** +The JSON file having information of things. filename should include its extension. The way to write this JSON file is described in **gateway-python-starter-kit**. +**class_directory** +The directory which has custom Thing-sub class. If the Thing-sub class is defined in the same directory, class_directory should be omitted or be None. + +#### deregister() +``` +deregister(*things) +``` +The deregister() function deletes things from the gateway. +**\*things** +A *Thing* instance to deregister +#### update_thing() +``` +update_thing(thing, **new_value) +``` +The update_thing() function updates the values of a thing. +**thing** +The thing to be updated. It can be the string id of a thing or a Thing instance. +**\*\*new_value** +The keyword arguments for the updated values. The key is the name of properties and the value is the new value. + +#### start() +``` +start() +``` +Connect the things of the gateway to the DevIoT server and the MQTT server +#### stop() +``` +stop() +``` +Disconnect the gateway from the DevIoT server and the MQTT server. +### Thing +#### Constructor +``` +Thing(id, name, kind=None) +``` +The Thing() constructor takes the following arguments: +**id** +the unique id of a thing +**name** +the display name of a thing in DevIot +**kind** +the kind of a thing + +#### add_property() +``` +add_property(*thing_properties) +``` +The add_property() adds properties to a Thing instance. When a string is used for arguments, new Property instance named the string is added to the Thing instance. +**\*thing_properties** +The list of Property instances or the string value of properties' name. +#### add_action() +``` +add_action(*thing_actions) +``` +The add_action() adds actions to a Thing instance. When a string is used for arguments, new Action instance named the string is added to the Thing instance. +**\*thing_properties** +The list of Property instances or the string value of actions' name. + +### Property +#### Constructor +``` +Property(name, type=PropertyType.INT, value=None, range=None, unit=None, description=None) +``` +The Property() constructor takes the following arguments: +**name** +The name of a property. It should be unique in a thing. +**type** +The variable type of a property. There are 4 types: int, bool, string, color. You can use PropertyType.INT, PropertyType.BOOL, PropertyType.STRING, PropertyType.COLOR after importing 'PropertyType' class from 'cisco_deviot.thing'. +**value** +The value of a property. +**range** +The range of a property's value. +**unit** +The unit of a property. It is string value. +**description** +The description for a property. It is shown at the page of each thing. + +### Action +#### Constructor +``` +Action(name, **kwargs) +``` +The Action() constructor takes the following arguments: +**name** +The name of Action. The method with the same name should be defined in a class or an instance. So the name must not overlap with other methods of Thing class like *add_property* and *add_action, and the name cannot contain space ' '. +**\*\*kwargs** +The keyword arguments for properties in an action. Key is the name of a property and the value of the property. It is not mandatory. + +#### add_parameter() +``` +add_parameter(action_parameter) +``` +The *add_parameter* adds properties to an Action instance. +**action_parameter** +A Property instance to be added diff --git a/cisco_deviot.egg-info/PKG-INFO b/cisco_deviot.egg-info/PKG-INFO index e2ef1fb..e2a6a12 100644 --- a/cisco_deviot.egg-info/PKG-INFO +++ b/cisco_deviot.egg-info/PKG-INFO @@ -6,9 +6,228 @@ Home-page: https://github.com/CiscoDevIoT/gateway-python-sdk Author: Haihua Xiao Author-email: hhxiao@gmail.com License: Apache 2.0 -Description: #gateway-python-sdk +Description: # gateway-python-sdk - Python SDK for DevIoT gateway service + ### Python SDK for DevIoT service ([https://deviot.cisco.com/](https://deviot.cisco.com/)) + + You can use this SDK to register devices to DevIoT and sync up data and actions between the devices and DevIoT. + + ## Table of contents + + * [Getting Started](#getting-started) + * [API](#api) + + ## Requirement + 1. [Python 2.7](https://www.python.org/downloads/): This SDK is based on the Python 2.7.3 + 2. [paho-mqtt](https://eclipse.org/paho/clients/python/): This SDK uses this library to build a MQTT client + + ## Usage + 1. You can use SDK to register your own sensors and systems to DevIoT. + 2. Use [Starter-ki](https://wwwin-github.cisco.com/DevIoT/gateway-python-starter-kit) to run sample codes + + ## Term + + - **Gateway**: 'Gateway' is a device connected to DevIoT like a Raspberry Pi board or a mobile phone + - **Thing**: 'Thing' is a sensor or a module in a gateway like a light sensor, LED, or GPS. A thing is an icon in DevIoT. A gateway can have several things. + - **Property**: 'Property' is a variable measured by a thing. For instance, Latitude and longitude are properties of a GPS. A thing can have several properties. + - **Action**: 'Action' is a command for a module. For example, turning on and off LED are two different actions. A thing can have several actions. + + ## Getting Started + #### 0) Check the version of python (terminal) + In order to use this SDK, the version of python should be 2.x. You can check it with the following command. + ``` + python --version + ``` + + #### 1) Set up SDK package (terminal) + Clone SDK git repository + ``` + git clone https://github.com/ailuropoda0/gateway-python-sdk.git + cd gateway-python-sdk + ``` + install sdk package on python 2 + ``` + python setup.py install + ``` + + #### 2) Connect gateway (python code) + You can refer the example code in [gateway-python-starter-kit](https://wwwin-github.cisco.com/DevIoT/gateway-python-starter-kit). + + Import SDK + ``` + from cisco_deviot.gateway import Gateway + from cisco_deviot.thing import Thing, Property, PropertyType + ``` + Construct a Gateway object + ``` + account = "your_id@cisco.com" # enter your DevIoT account + app = Gateway(name="gateway_name", account=account) + ``` + + Contruct a thing instance + ``` + thing = Thing("thing-id", "thing-name", "thing-kind") + ``` + You can only use Thing as either an input component(sensor) or an output component. + If your thing has both properties and actions, it acts as an input component. + + (1) Input: Add a property to the thing + ``` + property = Property("variable_name", PropertyType.INT, 0) + thing.add_property(property); + ``` + + (2) Output: Add an action to the thing + ``` + thing.add_action("sameple_action") + def custom_function(): ## define your action function + print("action") + + thing.sameple_action = custom_function + ``` + + Register the sensor to the gateway + ``` + app.register(thing) + ``` + + Connect the gateway to the server + ``` + app.start() + ``` + + #### 3) disconnect gateway (python code) + disconnect gateway + ``` + app.stop() + ``` +   + ## API + ### Gateway + #### Constructor + ``` + Gateway(name, deviot_server="deviot.cisco.com", connector_server="deviot.cisco.com:18883", kind="device", account="") + ``` + The Gateway() constructor takes the following arguments: + **name** + The unique name of a gateway + **deviot_server** + The address for the DevIoT server. The default value is 'deviot.cisco.com'. It does not need to add the protocol. The default protocol is HTTPS(secure connection). + **connector_server** + The address for the MQTT server. The default value is 'deviot.cisco.com:18883'. It does not need to add the protcol. The default protocol is TCP(unsecure connection) and the default port number is 1883. + **kind** + The kind of a gateway + **account** + Your DevIoT account. you also can use empty string, it will let all account get the data of your gateway. + #### register() + ``` + register(*things) + ``` + The register() function adds things to the gateway. The thing should not have been already registered. + **thing** + A *Thing* instance to register + #### load() + ``` + load(filename, class_directory=None) + ``` + The load() function registers things from an JSON file named [filename] and the custom Thing-sub classes inside [class_directory]. + **filename** + The JSON file having information of things. filename should include its extension. The way to write this JSON file is described in **gateway-python-starter-kit**. + **class_directory** + The directory which has custom Thing-sub class. If the Thing-sub class is defined in the same directory, class_directory should be omitted or be None. + + #### deregister() + ``` + deregister(*things) + ``` + The deregister() function deletes things from the gateway. + **\*things** + A *Thing* instance to deregister + #### update_thing() + ``` + update_thing(thing, **new_value) + ``` + The update_thing() function updates the values of a thing. + **thing** + The thing to be updated. It can be the string id of a thing or a Thing instance. + **\*\*new_value** + The keyword arguments for the updated values. The key is the name of properties and the value is the new value. + + #### start() + ``` + start() + ``` + Connect the things of the gateway to the DevIoT server and the MQTT server + #### stop() + ``` + stop() + ``` + Disconnect the gateway from the DevIoT server and the MQTT server. + ### Thing + #### Constructor + ``` + Thing(id, name, kind=None) + ``` + The Thing() constructor takes the following arguments: + **id** + the unique id of a thing + **name** + the display name of a thing in DevIot + **kind** + the kind of a thing + + #### add_property() + ``` + add_property(*thing_properties) + ``` + The add_property() adds properties to a Thing instance. When a string is used for arguments, new Property instance named the string is added to the Thing instance. + **\*thing_properties** + The list of Property instances or the string value of properties' name. + #### add_action() + ``` + add_action(*thing_actions) + ``` + The add_action() adds actions to a Thing instance. When a string is used for arguments, new Action instance named the string is added to the Thing instance. + **\*thing_properties** + The list of Property instances or the string value of actions' name. + + ### Property + #### Constructor + ``` + Property(name, type=PropertyType.INT, value=None, range=None, unit=None, description=None) + ``` + The Property() constructor takes the following arguments: + **name** + The name of a property. It should be unique in a thing. + **type** + The variable type of a property. There are 4 types: int, bool, string, color. You can use PropertyType.INT, PropertyType.BOOL, PropertyType.STRING, PropertyType.COLOR after importing 'PropertyType' class from 'cisco_deviot.thing'. + **value** + The value of a property. + **range** + The range of a property's value. + **unit** + The unit of a property. It is string value. + **description** + The description for a property. It is shown at the page of each thing. + + ### Action + #### Constructor + ``` + Action(name, **kwargs) + ``` + The Action() constructor takes the following arguments: + **name** + The name of Action. The method with the same name should be defined in a class or an instance. So the name must not overlap with other methods of Thing class like *add_property* and *add_action, and the name cannot contain space ' '. + **\*\*kwargs** + The keyword arguments for properties in an action. Key is the name of a property and the value of the property. It is not mandatory. + + #### add_parameter() + ``` + add_parameter(action_parameter) + ``` + The *add_parameter* adds properties to an Action instance. + **action_parameter** + A Property instance to be added Keywords: cisco devnet deviot gateway Platform: UNKNOWN diff --git a/cisco_deviot/__init__.py b/cisco_deviot/__init__.py index fce2632..b52d65e 100644 --- a/cisco_deviot/__init__.py +++ b/cisco_deviot/__init__.py @@ -30,8 +30,8 @@ def get_logger(name, level=logging.INFO): logger = get_logger("DevIoT") -print """ +print(""" ___ ________ ______ / _ \___ _ __/ _/ __ \/_ __/ / // / -_) |/ // // /_/ / / / -/____/\__/|___/___/\____/ /_/""" +/____/\__/|___/___/\____/ /_/""") diff --git a/cisco_deviot/gateway.py b/cisco_deviot/gateway.py index 9286688..9ebef58 100644 --- a/cisco_deviot/gateway.py +++ b/cisco_deviot/gateway.py @@ -15,28 +15,42 @@ import threading import time import json +import importlib +import socket +import collections from urlparse import urlparse from cisco_deviot import logger -from cisco_deviot.mqtt_connector import MqttConnector +from mqtt_connector import MqttConnector +from thing import Thing +from enum import EnumMeta -MODE_HTTP_PULL = 0 -MODE_HTTP_PUSH = 1 -MODE_MQTT = 2 +class Mode(EnumMeta): + HTTP_PULL = 0 + HTTP_PUSH = 1 + MQTT = 2 class Gateway: - def __init__(self, name, deviot_server, connector_server, kind="device", account=""): + def __init__(self, name, deviot_server="deviot.cisco.com", connector_server="deviot.cisco.com:18883", kind="device", account=""): name = name.replace("-", "_") self.name = name self.kind = kind self.owner = account + if urlparse(deviot_server).scheme == '': # the default protocol for a DevIot server is HTTPS + deviot_server = "https://" + deviot_server self.deviot_server = urlparse(deviot_server) - self.mode = MODE_MQTT + if self.deviot_server.scheme == 'http': + self.__connection = httplib.HTTPConnection(self.deviot_server.netloc) + else: + self.__connection = httplib.HTTPSConnection(self.deviot_server.netloc) + self.mode = Mode.MQTT self.things = {} self.__registration_started = False - self.__registered = 0 + self.__registered = False + if urlparse(connector_server).scheme == '': # the default protocol of MQTT is TCP + connector_server = "tcp://" + connector_server self.connector = MqttConnector(self, connector_server) self.host = self.connector.host self.port = self.connector.port @@ -48,7 +62,7 @@ def __registration_function(self): time.sleep(2) if self.__registration_started: self.__register() - time.sleep(8) + time.sleep(18) def __del_none(self, d): if isinstance(d, dict): @@ -62,44 +76,57 @@ def __del_none(self, d): self.__del_none(x) return d + # register the gateway to DevIoT server using HTTP(S) def __register(self): - import collections - model = collections.OrderedDict([("name", self.name), - ("kind", self.kind), - ("mode", self.mode), - ("owner", self.owner), - ("host", self.host), - ("port", self.port), - ("data", self.data), - ("action", self.action), - ("sensors", [thing.get_model() for thing in self.things.values()])]) - json_string = json.dumps(self.__del_none(model), sort_keys=False) try: - conn = httplib.HTTPConnection(self.deviot_server.netloc) - conn.request("POST", "/api/v1/gateways", json_string, {'Content-Type': 'application/json'}) - response = conn.getresponse() + self.__connection.request("POST", "/api/v1/gateways", self.get_model(), {'Content-Type': 'application/json'}) + response = self.__connection.getresponse() + response.read() code = int(response.status) if code < 200 or code > 300: - if self.__registered != 1: - logger.error("failed to register gateway {name} to {server}: {c}-{e}".format(name=self, - server=self.deviot_server.netloc, - c=code, - e=response.reason)) - self.__registered = 1 + logger.error("failed to register gateway {name} to {server}: {c}-{e}".format(name=self, + server=self.deviot_server.netloc, + c=code, + e=response.reason)) + self.__registered = False elif not self.__registered: - if self.__registered != 2: - logger.info("registered gateway {name} to {server}".format(name=self, - server=self.deviot_server.netloc)) - self.__registered = 2 + logger.info("registered gateway {name} to {server}".format(name=self, + server=self.deviot_server.netloc)) + self.__registered = True except IOError as e: - if self.__registered != 1: - logger.error("failed to register gateway {name} to {server}: {e}".format(name=self, - server=self.deviot_server.netloc, - e=e)) - self.__registered = 1 + logger.error("failed to register gateway {name} to {server}: {e}".format(name=self, + server=self.deviot_server.netloc, + e=e)) + self.__registered = False + # deregister the gateway from DevIoT server + def __deregister(self): + if self.__registered: + try: + self.__connection.request("DELETE", "/api/v1/gateways/" + self.name) + response = self.__connection.getresponse() + response.read() + code = int(response.status) + if code < 200 or code > 300: + logger.error("failed to deregister gateway {name} from {server}: {c}-{e}".format(name=self, + server=self.deviot_server.netloc, + c=code, + e=response.reason)) + self.__registered = False + elif self.__registered: + logger.info("deregistered gateway {name} from {server}".format(name=self, + server=self.deviot_server.netloc)) + self.__registered = False + except IOError as e: + logger.error("failed to deregister gateway {name} to {server}: {e}".format(name=self, + server=self.deviot_server.netloc, + e=e)) + else: + logger.info("gateway {name} has already been deregistered from {server}".format(name=self, + server=self.deviot_server.netloc)) + def start(self): - if self.__registration_started: + if self.is_started(): logger.warn("gateway service {name} already started".format(name=self)) else: self.__registration_started = True @@ -110,26 +137,73 @@ def start(self): logger.info("gateway service {name} started".format(name=self)) def stop(self): - if self.__registration_started: + if self.is_started(): self.__registration_started = False + self.__deregister() self.connector.stop() logger.info("gateway service {name} stopped".format(name=self)) else: logger.warn("gateway service {name} already stopped".format(name=self)) - def register(self, thing): - if thing.id in self.things: - logger.warn("thing {thing} is already registered".format(thing=thing)) - else: - self.things[thing.id] = thing - logger.info("thing {thing} registered".format(thing=thing)) + def is_started(self): + return self.__registration_started - def deregister(self, thing): - if thing.id in self.things: - del self.things[thing.id] - logger.info("thing {thing} deregistered".format(thing=thing)) - else: - logger.warn("thing {thing} is not registered".format(thing=thing)) + # register things to the gateway + def register(self, *things): + for thing in things: + if not isinstance(thing, Thing): + logger.error("Invalid thing {thing}, only Thing instance is supported".format(thing=thing)) + elif thing.id in self.things: + logger.warn("thing {thing} is already registered".format(thing=thing)) + else: + self.things[thing.id] = thing + logger.info("thing {thing} registered".format(thing=thing)) + + # load JSON file 'filename' and register instances of custom Thing class which are defined in 'class_directory' + def load(self, filename, class_directory=None): + try: + with open(filename) as json_data: + things = json.load(json_data) + except IOError: + logger.error("cannot open such file: {file}".format(file=filename)) + return + + for thing in things: + stype = str(thing.get("type", "thing")) + name = str(thing.get("name", "")) + if name == "": + name = socket.gethostname() + "'s " + stype + logger.warn("There is no sensor name") + sid = name + "_" + str(int(time.time())) + if stype == 'thing': + c = Thing + else: + try: + class_name = stype if (class_directory is None) else (class_directory + "." + stype) + m = importlib.import_module(class_name) + c = getattr(m, stype.capitalize()) + except: + logger.error("error to import the class {name}".format(name=class_name)) + continue + instance = c(sid, name) + if "options" in thing: + instance.options = thing["options"] + self.register(instance) + + # deregister things from the gateway + def deregister(self, *things): + for thing in things: + if not isinstance(thing, Thing): + logger.error("Invalid thing {thing}, only Thing instance is supported".format(thing=thing)) + continue + if thing.id in self.things: + del self.things[thing.id] + logger.info("thing {thing} deregistered".format(thing=thing)) + else: + logger.warn("thing {thing} is not registered".format(thing=thing)) + + def get_data(self): + return {key: value.get_data() for (key, value) in self.things.items()} def send_data(self, data): if self.connector.is_connected: @@ -137,6 +211,21 @@ def send_data(self, data): else: logger.warn("{connector} not connected yet".format(connector=self.connector)) + def update_thing(self, thing, **new_values): + if isinstance(thing, str): + thing_id = thing + elif isinstance(thing, Thing): + thing_id = thing.id + else: + logger.error("Invalid thing {thing}, only string and Thing are supported".format(thing=thing)) + return + if thing_id in self.things: + thing = self.things[thing_id] + thing.update_property(**new_values) + else: + logger.warn("There is no thing with id {id}".format(id=thing_id)) + + # args: payload of the subscribed MQTT message def call_action(self, args): tid = None if "id" in args: @@ -158,7 +247,7 @@ def call_action(self, args): action = args.pop("action") try: t.call_action(action, args) - except Exception, error: + except Exception as error: logger.error("failed to call {tid}.{action}({args}): {error}".format(tid=tid, action=action, args=args, @@ -166,5 +255,17 @@ def call_action(self, args): else: logger.warn("thing {thing} not registered".format(thing=tid)) + def get_model(self): + model = collections.OrderedDict([("name", self.name), + ("kind", self.kind), + ("mode", self.mode), + ("owner", self.owner), + ("host", self.host), + ("port", self.port), + ("data", self.data), + ("action", self.action), + ("sensors", [thing.get_model() for thing in self.things.values()])]) + return json.dumps(self.__del_none(model), sort_keys=False) + def __str__(self): return "{name}({kind})".format(name=self.name, kind=self.kind) diff --git a/cisco_deviot/mqtt_connector.py b/cisco_deviot/mqtt_connector.py index 04e89ca..67146ec 100644 --- a/cisco_deviot/mqtt_connector.py +++ b/cisco_deviot/mqtt_connector.py @@ -12,6 +12,7 @@ import json +import threading from urlparse import urlparse import time @@ -28,6 +29,7 @@ def __init__(self, gateway, mqtt_server): self.client.on_disconnect = self.__on_disconnect__ self.client.on_message = self.__on_message__ self.__connected = False + self.__connection_start = False ns = gateway.owner.replace("@", "_").replace(".", "_").replace("/", "_") if ns == "": ns = "_" @@ -36,18 +38,36 @@ def __init__(self, gateway, mqtt_server): surl = urlparse(mqtt_server) self.host = surl.hostname self.port = surl.port + if self.port is None: # The default MQTT port number is 1883 + self.port = 1883 self.data = "/deviot/{ns}/{name}/data".format(name=name, ns=ns) self.action = "/deviot/{ns}/{name}/action".format(name=name, ns=ns) + def __publish_function(self): + while self.__connection_start: + if self.is_connected(): + data = self.gateway.get_data() + if len(data) != 0: + self.publish(data) + time.sleep(0.5) + def start(self): self.client.connect_async(self.host, self.port, 60) self.client.loop_start() + self.__connection_start = True + thread = threading.Thread(target=MqttConnector.__publish_function, args=(self,)) + thread.daemon = True + thread.start() logger.info("connecting to {server} ...".format(server=self)) def stop(self): + self.__connection_start = False self.client.disconnect() def __on_connect__(self, client, userdata, flags, rc): + if rc != 0: + logger.error("mqtt connection bad returned code={code}".format(code=rc)) + self.__reconnect(2) self.client.subscribe(self.action) self.__connected = True logger.info("{server}{topic} connected".format(server=self, topic=self.action)) @@ -55,8 +75,10 @@ def __on_connect__(self, client, userdata, flags, rc): def __on_disconnect__(self, client, userdata, rc): logger.warn("{server} disconnected".format(server=self)) self.__connected = False - backoff = 2 - while not self.__connected: + self.__reconnect(2) + + def __reconnect(self, backoff): + while not self.is_connected() and self.__connection_start: logger.info("reconnecting to {server} in {sec} seconds ...".format(server=self, sec=backoff)) time.sleep(backoff) backoff = min(128, backoff * 2) @@ -74,7 +96,7 @@ def __on_message__(self, client, userdata, msg): try: args = json.loads(message) self.gateway.call_action(args) - except Exception, error: + except Exception as error: logger.error("failed to process message {message}: {error}".format(message=message, error=error)) def publish(self, data): diff --git a/cisco_deviot/thing.py b/cisco_deviot/thing.py index 831da68..ea8df5c 100644 --- a/cisco_deviot/thing.py +++ b/cisco_deviot/thing.py @@ -13,29 +13,33 @@ import collections from cisco_deviot import logger - -PropertyTypeInt = 0 -PropertyTypeString = 1 -PropertyTypeBool = 2 -PropertyTypeColor = 3 - +from enum import EnumMeta def default_value_for_type(stype): - if stype == PropertyTypeInt: + if stype == PropertyType.INT: return 0 - if stype == PropertyTypeBool: + if stype == PropertyType.BOOL: return False - if stype == PropertyTypeString: + if stype == PropertyType.STRING: return "" - if stype == PropertyTypeColor: + if stype == PropertyType.COLOR: return "FFFFFF" return None +class PropertyType(EnumMeta): + INT = 0 + STRING = 1 + BOOL = 2 + COLOR = 3 + + class Property: - def __init__(self, name, type=0, value=None, range=None, unit=None, description=None): + def __init__(self, name, type=PropertyType.INT, value=None, range=None, unit=None, description=None): self.name = name self.type = type + if value is None: + value = default_value_for_type(type) self.value = value self.range = range self.unit = unit @@ -49,6 +53,9 @@ def get_model(self): ("unit", self.unit), ("description", self.description)]) + def __str__(self): + return "{name}:{value}{unit}".format(name=self.name, value=self.value, unit=self.unit) + class Action: def __init__(self, name, **kwargs): @@ -88,18 +95,36 @@ def get_model(self): def __str__(self): return "{id}.{name}({kind})".format(id=self.id, name=self.name, kind=self.kind) + def get_data(self): + return {prop.name: prop.value for prop in self.properties} + + # check the property, of which name is 'prop', is in properies + def _has_property(self, prop): + return (prop in [property.name for property in self.properties]) + def add_property(self, *thing_properties): - for prop in thing_properties: - if isinstance(prop, str): - self.properties.append(Property(prop, PropertyTypeInt)) - elif isinstance(prop, Property): - self.properties.append(prop) + for property in thing_properties: + if isinstance(property, str): + added_property = Property(property) + elif isinstance(property, Property): + added_property = property else: - logger.error( - "invalid property {property}, only string and Property are supported".format(property=prop)) + logger.error("invalid property {property}, only string and Property are supported".format(property=property)) + continue + if self._has_property(added_property.name): + logger.error("Invalid property name {property}, already registered.".format(property=added_property.name)) + break + self.properties.append(property) return self + def update_property(self, **new_properties): + for new_prop_name in new_properties: + for existing_prop in self.properties: + if new_prop_name == existing_prop.name: + existing_prop.value = new_properties[new_prop_name] + break + def add_action(self, *thing_actions): for act in thing_actions: if isinstance(act, str): @@ -107,9 +132,10 @@ def add_action(self, *thing_actions): elif isinstance(act, Action): self.actions.append(act) else: - logger.error("invalid property {property}, only string and Property are supported".format(property=act)) + logger.error("invalid action {action}, only string and Action are supported".format(action=act)) return self + # method: action name def call_action(self, method, args): action_method = getattr(self, method) matches = list(a for a in self.actions if a.name == method) diff --git a/sample_code_for_GrovePi_sensor.py b/sample_code_for_GrovePi_sensor.py new file mode 100644 index 0000000..c38e64e --- /dev/null +++ b/sample_code_for_GrovePi_sensor.py @@ -0,0 +1,74 @@ +import time +import traceback +from grovepi import grovepi + +from cisco_deviot.gateway import Gateway +from cisco_deviot.thing import Thing +from cisco_deviot.thing import Property +from cisco_deviot import constants + +pin = {"A0":14, "A1":15, "A2":16, "D2":2, "D3":3, "D4":4, "D5":5, "D6":6, "D7":7, "D8":8} + +# connect each sensor to a pin +light_sensor_pin = pin["A2"] +led_pin = pin["D3"] +ultrasonic_sensor_pin = pin["D4"] +temperature_sensor_pin = pin["D7"] +button_sensor_pin = pin["D8"] + +grovepi.pinMode(light_sensor_pin, "INPUT") +grovepi.pinMode(led_pin, "OUTPUT") +grovepi.pinMode(ultrasonic_sensor_pin, "INPUT") +grovepi.pinMode(temperature_sensor_pin, "INPUT") +grovepi.pinMode(button_sensor_pin, "INPUT") + + +# turn on/off the led when receive action from DevIot +# action name will be 'on' or 'off' +def trigger_grove_led(action): + print('led get action:' + action) + if action == 'on': + return lambda:grovepi.digitalWrite(led, 1) + else: + return lambda:grovepi.digitalWrite(led, 0) + +account = 'your_id@cisco.com' +app = Gateway('grovepi', 'deviot.cisco.com', 'deviot.cisco.com:18883', account) + +# the parameters of a Thing constructor are: id, display name, kind +light_sensor = Thing('grove_light', 'GroveLight', 'light') +light_sensor.add_property('light') +ultrasonic_sensor = Thing('grove_distance', 'GroveDistance', 'distance') +ultrasonic_sensor.add_property('distance') +temperature_sensor = Thing('grove_temp_hum', 'GroveTempHumd', 'temperature') +temperature_sensor.add_property('temperature', 'humidity') +button_sensor = Thing('grove_button', 'GroveButton', 'button') +button_sensor.add_property(Property('value', constants.PROPERTY_TYPE_BOOL)) +led = Thing('grove_led', "GroveLED", led) +led.add_action('on', 'off') +led.on = trigger_grove_led('on') +led.off = trigger_grove_led('off') + +app.register(light_sensor, ultrasonic_sensor, temperature_sensor, button_sensor, led) + +app.start() + +while True: + try: + light_value = grovepi.analogRead(light_sensor_pin) + [temperature_value, humidity_value] = grovepi.dht(temperature_sensor_pin, 0) + distance_value = grovepi.ultrasonicRead(ultrasonic_sensor_pin) + button_value = grovepi.digitalRead(button_sensor_pin) + + light_sensor.update_property(light=light_value) + app.update_thing(light_sensor, light=light_value) + app.update_thing(temperature_sensor, temperature=temperature_value, humidity=humidity_value) + app.update_thing(ultrasonic_sensor, distance=distance_value) + app.update_thing(button_sensor, value=button_value) + + time.sleep(0.3) + except: + traceback.print_exc() + break + +app.stop() \ No newline at end of file diff --git a/setup.py b/setup.py index 6632732..20a681d 100644 --- a/setup.py +++ b/setup.py @@ -20,13 +20,13 @@ name = "cisco_deviot", version = "0.1.2", description = "Gateway SDK for Cisco DevIoT", - long_description=long_description, - url="https://github.com/CiscoDevIoT/gateway-python-sdk", + long_description = long_description, + url = "https://github.com/CiscoDevIoT/gateway-python-sdk", author = "Haihua Xiao", author_email = "hhxiao@gmail.com", license = "Apache 2.0", # See https://pypi.python.org/pypi?%3Aaction=list_classifiers - classifiers=[ + classifiers = [ # How mature is this project? Common values are # 3 - Alpha # 4 - Beta @@ -50,8 +50,8 @@ 'Programming Language :: Python :: 3.5', ], # What does your project relate to? - keywords='cisco devnet deviot gateway', + keywords = 'cisco devnet deviot gateway', zip_safe = False, packages = find_packages(), - install_requires=['paho-mqtt'], - ) + install_requires = ['paho-mqtt'], +)