Commit a2423b84 authored by Lance Stout's avatar Lance Stout

Get the IoT plugins to pass tests on Py3

parent 49acdac7
......@@ -12,51 +12,51 @@ import datetime
import logging
class Device(object):
"""
Example implementation of a device readout 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, fields={}):
self.nodeId = nodeId
self.fields = fields # see fields described below
# {'type':'numeric',
# 'name':'myname',
# 'value': 42,
# 'unit':'Z'}];
self.timestamp_data = {}
self.momentary_data = {}
self.momentary_timestamp = ""
logging.debug("Device object started nodeId %s",nodeId)
def has_field(self, field):
"""
Returns true if the supplied field name exists in this device.
"""
def __init__(self, nodeId, fields={}):
self.nodeId = nodeId
self.fields = fields # see fields described below
# {'type':'numeric',
# 'name':'myname',
# 'value': 42,
# 'unit':'Z'}];
self.timestamp_data = {}
self.momentary_data = {}
self.momentary_timestamp = ""
logging.debug("Device object started nodeId %s",nodeId)
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.keys():
return True;
return False;
def refresh(self, fields):
"""
override method to do the refresh work
refresh values from hardware or other
"""
pass
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.
field -- The field name
"""
if field in self.fields.keys():
return True;
return False;
def refresh(self, fields):
"""
override method to do the refresh work
refresh values from hardware or other
"""
pass
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:
......@@ -65,153 +65,153 @@ class Device(object):
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:
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:
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"
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
{
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_msg -- [optional] Only applies when result == "error".
Error details when a request failed.
"""
logging.debug("request_fields called looking for fields %s",fields)
if len(fields) > 0:
# Check availiability
for f in fields:
if f not in self.fields.keys():
self._send_reject(session, callback)
return False;
else:
# Request all fields
fields = self.fields.keys();
# Refresh data from device
# ...
logging.debug("about to refresh device fields %s",fields)
self.refresh(fields)
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
"""
logging.debug("request_fields called looking for fields %s",fields)
if len(fields) > 0:
# Check availiability
for f in fields:
if f not in self.fields.keys():
self._send_reject(session, callback)
return False;
else:
# Request all fields
fields = self.fields.keys();
# Refresh data from device
# ...
logging.debug("about to refresh device fields %s",fields)
self.refresh(fields)
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");
"""
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
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};
"""
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
def _add_field_timestamp_data(self, name, timestamp, value, flags=None):
"""
Adds timestamped data to a field
Arguments:
name -- Name of the field
......@@ -219,37 +219,37 @@ class Device(object):
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.keys():
return False;
if not timestamp in self.timestamp_data:
self.timestamp_data[timestamp] = {};
"""
if not name in self.fields.keys():
return False;
if not timestamp in self.timestamp_data:
self.timestamp_data[timestamp] = {};
self.timestamp_data[timestamp][name] = {"value": value, "flags": flags};
return True;
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
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 not self.fields.has_key(name):
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;
"""
if name not in self.fields:
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;
......@@ -124,10 +124,10 @@ class Request(ElementBase):
def get_nodes(self):
"""Return all nodes."""
nodes = set()
nodes = []
for node in self['substanzas']:
if isinstance(node, RequestNode):
nodes.add(node)
nodes.append(node)
return nodes
def set_nodes(self, nodes):
......@@ -190,10 +190,10 @@ class Request(ElementBase):
def get_fields(self):
"""Return all fields."""
fields = set()
fields = []
for field in self['substanzas']:
if isinstance(field, RequestField):
fields.add(field)
fields.append(field)
return fields
def set_fields(self, fields):
......@@ -351,10 +351,10 @@ class Fields(ElementBase):
def get_nodes(self):
"""Return all nodes."""
nodes = set()
nodes = []
for node in self['substanzas']:
if isinstance(node, FieldsNode):
nodes.add(node)
nodes.append(node)
return nodes
def set_nodes(self, nodes):
......@@ -450,10 +450,10 @@ class FieldsNode(ElementBase):
def get_timestamps(self):
"""Return all timestamps."""
#print(str(id(self)) + " get_timestamps: ")
timestamps = set()
timestamps = []
for timestamp in self['substanzas']:
if isinstance(timestamp, Timestamp):
timestamps.add(timestamp)
timestamps.append(timestamp)
return timestamps
def set_timestamps(self, timestamps):
......@@ -634,10 +634,10 @@ class Timestamp(ElementBase):
def get_datas(self):
""" Return all data elements. """
datas = set()
datas = []
for data in self['substanzas']:
if isinstance(data, Field):
datas.add(data)
datas.append(data)
return datas
def set_datas(self, datas):
......
......@@ -11,36 +11,36 @@
import datetime
class Device(object):
"""
Example implementation of a device control object.
"""
Example implementation of a device control object.
The device object may by any custom implementation to support
specific devices, but it must implement the functions:
has_control_field
set_control_fields
"""
"""
def __init__(self, nodeId):
self.nodeId = nodeId;
self.control_fields = {};
def __init__(self, nodeId):
self.nodeId = nodeId;
self.control_fields = {};
def has_control_field(self, field, typename):
"""
Returns true if the supplied field name exists
and the type matches for control in this device.
def has_control_field(self, field, typename):
"""
Returns true if the supplied field name exists
and the type matches for control in this device.
Arguments:
field -- The field name
field -- The field name
typename -- The expected type
"""
if field in self.control_fields and self.control_fields[field]["type"] == typename:
return True;
return False;
"""
if field in self.control_fields and self.control_fields[field]["type"] == typename:
return True;
return False;
def set_control_fields(self, fields, session, callback):
"""
Starts a control setting procedure. Verifies the fields,
sets the data and (if needed) and calls the callback.
def set_control_fields(self, fields, session, callback):
"""
Starts a control setting procedure. Verifies the fields,
sets the data and (if needed) and calls the callback.
Arguments:
fields -- List of control fields in tuple format:
......@@ -48,50 +48,50 @@ class Device(object):
session -- Session id, only used in the callback as identifier
callback -- Callback function to call when control set is complete.
The callback function must support the following arguments:
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:
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" - Set fields failed.
"ok" - All fields were set.
error_field -- [optional] Only applies when result == "error"
The field name that failed
(usually means it is missing)
error_msg -- [optional] Only applies when result == "error".
Error details when a request failed.
"""
if len(fields) > 0:
# Check availiability
for name, typename, value in fields:
if not self.has_control_field(name, typename):
self._send_control_reject(session, name, "NotFound", callback)
return False;
for name, typename, value in fields:
self._set_field_value(name, value)
callback(session, result="ok", nodeId=self.nodeId);
return True
def _send_control_reject(self, session, field, message, callback):
"""
Sends a reject to the caller
error_field -- [optional] Only applies when result == "error"
The field name that failed
(usually means it is missing)
error_msg -- [optional] Only applies when result == "error".
Error details when a request failed.
"""
if len(fields) > 0:
# Check availiability
for name, typename, value in fields:
if not self.has_control_field(name, typename):
self._send_control_reject(session, name, "NotFound", callback)
return False;
for name, typename, value in fields:
self._set_field_value(name, value)
callback(session, result="ok", nodeId=self.nodeId);
return True
def _send_control_reject(self, session, field, message, callback):
"""
Sends a reject to the caller
Arguments:
session -- Session id, see definition in
set_control_fields function
callback -- Callback function, see definition in
set_control_fields function
"""