From 45689fd8799186fd6be0b308745aef428ab50dcc Mon Sep 17 00:00:00 2001 From: Joachim Lindborg Date: Fri, 30 Aug 2013 02:29:52 +0200 Subject: First implementation of the xep_0323 and xep_325 used in IoT systems. Tests are added for stanza and streams --- sleekxmpp/plugins/xep_0323/device.py | 243 +++++++++++++++++++++++++++++++++++ 1 file changed, 243 insertions(+) create mode 100644 sleekxmpp/plugins/xep_0323/device.py (limited to 'sleekxmpp/plugins/xep_0323/device.py') diff --git a/sleekxmpp/plugins/xep_0323/device.py b/sleekxmpp/plugins/xep_0323/device.py new file mode 100644 index 00000000..8414f5b4 --- /dev/null +++ b/sleekxmpp/plugins/xep_0323/device.py @@ -0,0 +1,243 @@ +""" + SleekXMPP: The Sleek XMPP Library + Implementation of xeps for Internet of Things + http://wiki.xmpp.org/web/Tech_pages/IoT_systems + Copyright (C) 2013 Sustainable Innovation, Joachim.lindborg@sust.se, bjorn.westrom@consoden.se + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + +import datetime + +class Device(object): + """ + Example implementation of a device readout object. + Is registered in the XEP_0323.register_node call + The device object may be any custom implementation to support + specific devices, but it must implement the functions: + has_field + request_fields + """ + + def __init__(self, nodeId): + self.nodeId = nodeId; + self.fields = {}; + # {'type':'numeric', + # 'name':'myname', + # 'value': 42, + # 'unit':'Z'}]; + self.timestamp_data = {}; + self.momentary_data = {}; + self.momentary_timestamp = ""; + + def has_field(self, field): + """ + Returns true if the supplied field name exists in this device. + + Arguments: + field -- The field name + """ + if field in self.fields: + return True; + return False; + + def request_fields(self, fields, flags, session, callback): + """ + Starts a data readout. Verifies the requested fields, + refreshes the data (if needed) and calls the callback + with requested data. + + + Arguments: + fields -- List of field names to readout + flags -- [optional] data classifier flags for the field, e.g. momentary + Formatted as a dictionary like { "flag name": "flag value" ... } + session -- Session id, only used in the callback as identifier + callback -- Callback function to call when data is available. + + The callback function must support the following arguments: + + session -- Session id, as supplied in the request_fields call + nodeId -- Identifier for this device + result -- The current result status of the readout. Valid values are: + "error" - Readout failed. + "fields" - Contains readout data. + "done" - Indicates that the readout is complete. May contain + readout data. + timestamp_block -- [optional] Only applies when result != "error" + The readout data. Structured as a dictionary: + { + timestamp: timestamp for this datablock, + fields: list of field dictionary (one per readout field). + readout field dictionary format: + { + type: The field type (numeric, boolean, dateTime, timeSpan, string, enum) + name: The field name + value: The field value + unit: The unit of the field. Only applies to type numeric. + dataType: The datatype of the field. Only applies to type enum. + flags: [optional] data classifier flags for the field, e.g. momentary + Formatted as a dictionary like { "flag name": "flag value" ... } + } + } + error_msg -- [optional] Only applies when result == "error". + Error details when a request failed. + + """ + + if len(fields) > 0: + # Check availiability + for f in fields: + if f not in self.fields: + self._send_reject(session, callback) + return False; + else: + # Request all fields + fields = self.fields; + + + # Refresh data from device + # ... + + if "momentary" in flags and flags['momentary'] == "true" or \ + "all" in flags and flags['all'] == "true": + ts_block = {}; + timestamp = ""; + + if len(self.momentary_timestamp) > 0: + timestamp = self.momentary_timestamp; + else: + timestamp = self._get_timestamp(); + + field_block = []; + for f in self.momentary_data: + if f in fields: + field_block.append({"name": f, + "type": self.fields[f]["type"], + "unit": self.fields[f]["unit"], + "dataType": self.fields[f]["dataType"], + "value": self.momentary_data[f]["value"], + "flags": self.momentary_data[f]["flags"]}); + ts_block["timestamp"] = timestamp; + ts_block["fields"] = field_block; + + callback(session, result="done", nodeId=self.nodeId, timestamp_block=ts_block); + return + + from_flag = self._datetime_flag_parser(flags, 'from') + to_flag = self._datetime_flag_parser(flags, 'to') + + for ts in sorted(self.timestamp_data.keys()): + tsdt = datetime.datetime.strptime(ts, "%Y-%m-%dT%H:%M:%S") + if not from_flag is None: + if tsdt < from_flag: + #print (str(tsdt) + " < " + str(from_flag)) + continue + if not to_flag is None: + if tsdt > to_flag: + #print (str(tsdt) + " > " + str(to_flag)) + continue + + ts_block = {}; + field_block = []; + + for f in self.timestamp_data[ts]: + if f in fields: + field_block.append({"name": f, + "type": self.fields[f]["type"], + "unit": self.fields[f]["unit"], + "dataType": self.fields[f]["dataType"], + "value": self.timestamp_data[ts][f]["value"], + "flags": self.timestamp_data[ts][f]["flags"]}); + + ts_block["timestamp"] = ts; + ts_block["fields"] = field_block; + callback(session, result="fields", nodeId=self.nodeId, timestamp_block=ts_block); + callback(session, result="done", nodeId=self.nodeId, timestamp_block=None); + + def _datetime_flag_parser(self, flags, flagname): + if not flagname in flags: + return None + + dt = None + try: + dt = datetime.datetime.strptime(flags[flagname], "%Y-%m-%dT%H:%M:%S") + except ValueError: + # Badly formatted datetime, ignore it + pass + return dt + + + def _get_timestamp(self): + """ + Generates a properly formatted timestamp of current time + """ + return datetime.datetime.now().replace(microsecond=0).isoformat() + + def _send_reject(self, session, callback): + """ + Sends a reject to the caller + + Arguments: + session -- Session id, see definition in request_fields function + callback -- Callback function, see definition in request_fields function + """ + callback(session, result="error", nodeId=self.nodeId, timestamp_block=None, error_msg="Reject"); + + def _add_field(self, name, typename, unit=None, dataType=None): + """ + Adds a field to the device + + Arguments: + name -- Name of the field + typename -- Type of the field (numeric, boolean, dateTime, timeSpan, string, enum) + unit -- [optional] only applies to "numeric". Unit for the field. + dataType -- [optional] only applies to "enum". Datatype for the field. + """ + self.fields[name] = {"type": typename, "unit": unit, "dataType": dataType}; + + def _add_field_timestamp_data(self, name, timestamp, value, flags=None): + """ + Adds timestamped data to a field + + Arguments: + name -- Name of the field + timestamp -- Timestamp for the data (string) + value -- Field value at the timestamp + flags -- [optional] data classifier flags for the field, e.g. momentary + Formatted as a dictionary like { "flag name": "flag value" ... } + """ + if not name in self.fields: + return False; + if not timestamp in self.timestamp_data: + self.timestamp_data[timestamp] = {}; + + self.timestamp_data[timestamp][name] = {"value": value, "flags": flags}; + return True; + + def _add_field_momentary_data(self, name, value, flags=None): + """ + Sets momentary data to a field + + Arguments: + name -- Name of the field + value -- Field value at the timestamp + flags -- [optional] data classifier flags for the field, e.g. momentary + Formatted as a dictionary like { "flag name": "flag value" ... } + """ + if self.fields[name] is None: + return False; + if flags is None: + flags = {}; + + flags["momentary"] = "true" + self.momentary_data[name] = {"value": value, "flags": flags}; + return True; + + def _set_momentary_timestamp(self, timestamp): + """ + This function is only for unit testing to produce predictable results. + """ + self.momentary_timestamp = timestamp; + -- cgit v1.2.3