Bump to 1.3.1
[slixmpp.git] / sleekxmpp / plugins / xep_0323 / device.py
blob0bc20327a9e82ac9efb893d610449e3b84cec178
1 """
2 SleekXMPP: The Sleek XMPP Library
3 Implementation of xeps for Internet of Things
4 http://wiki.xmpp.org/web/Tech_pages/IoT_systems
5 Copyright (C) 2013 Sustainable Innovation, Joachim.lindborg@sust.se, bjorn.westrom@consoden.se
6 This file is part of SleekXMPP.
8 See the file LICENSE for copying permission.
9 """
11 import datetime
12 import logging
14 class Device(object):
15 """
16 Example implementation of a device readout object.
17 Is registered in the XEP_0323.register_node call
18 The device object may be any custom implementation to support
19 specific devices, but it must implement the functions:
20 has_field
21 request_fields
22 """
24 def __init__(self, nodeId, fields={}):
25 self.nodeId = nodeId
26 self.fields = fields # see fields described below
27 # {'type':'numeric',
28 # 'name':'myname',
29 # 'value': 42,
30 # 'unit':'Z'}];
31 self.timestamp_data = {}
32 self.momentary_data = {}
33 self.momentary_timestamp = ""
34 logging.debug("Device object started nodeId %s",nodeId)
36 def has_field(self, field):
37 """
38 Returns true if the supplied field name exists in this device.
40 Arguments:
41 field -- The field name
42 """
43 if field in self.fields.keys():
44 return True;
45 return False;
47 def refresh(self, fields):
48 """
49 override method to do the refresh work
50 refresh values from hardware or other
51 """
52 pass
55 def request_fields(self, fields, flags, session, callback):
56 """
57 Starts a data readout. Verifies the requested fields,
58 refreshes the data (if needed) and calls the callback
59 with requested data.
62 Arguments:
63 fields -- List of field names to readout
64 flags -- [optional] data classifier flags for the field, e.g. momentary
65 Formatted as a dictionary like { "flag name": "flag value" ... }
66 session -- Session id, only used in the callback as identifier
67 callback -- Callback function to call when data is available.
69 The callback function must support the following arguments:
71 session -- Session id, as supplied in the request_fields call
72 nodeId -- Identifier for this device
73 result -- The current result status of the readout. Valid values are:
74 "error" - Readout failed.
75 "fields" - Contains readout data.
76 "done" - Indicates that the readout is complete. May contain
77 readout data.
78 timestamp_block -- [optional] Only applies when result != "error"
79 The readout data. Structured as a dictionary:
81 timestamp: timestamp for this datablock,
82 fields: list of field dictionary (one per readout field).
83 readout field dictionary format:
85 type: The field type (numeric, boolean, dateTime, timeSpan, string, enum)
86 name: The field name
87 value: The field value
88 unit: The unit of the field. Only applies to type numeric.
89 dataType: The datatype of the field. Only applies to type enum.
90 flags: [optional] data classifier flags for the field, e.g. momentary
91 Formatted as a dictionary like { "flag name": "flag value" ... }
94 error_msg -- [optional] Only applies when result == "error".
95 Error details when a request failed.
97 """
98 logging.debug("request_fields called looking for fields %s",fields)
99 if len(fields) > 0:
100 # Check availiability
101 for f in fields:
102 if f not in self.fields.keys():
103 self._send_reject(session, callback)
104 return False;
105 else:
106 # Request all fields
107 fields = self.fields.keys();
110 # Refresh data from device
111 # ...
112 logging.debug("about to refresh device fields %s",fields)
113 self.refresh(fields)
115 if "momentary" in flags and flags['momentary'] == "true" or \
116 "all" in flags and flags['all'] == "true":
117 ts_block = {};
118 timestamp = "";
120 if len(self.momentary_timestamp) > 0:
121 timestamp = self.momentary_timestamp;
122 else:
123 timestamp = self._get_timestamp();
125 field_block = [];
126 for f in self.momentary_data:
127 if f in fields:
128 field_block.append({"name": f,
129 "type": self.fields[f]["type"],
130 "unit": self.fields[f]["unit"],
131 "dataType": self.fields[f]["dataType"],
132 "value": self.momentary_data[f]["value"],
133 "flags": self.momentary_data[f]["flags"]});
134 ts_block["timestamp"] = timestamp;
135 ts_block["fields"] = field_block;
137 callback(session, result="done", nodeId=self.nodeId, timestamp_block=ts_block);
138 return
140 from_flag = self._datetime_flag_parser(flags, 'from')
141 to_flag = self._datetime_flag_parser(flags, 'to')
143 for ts in sorted(self.timestamp_data.keys()):
144 tsdt = datetime.datetime.strptime(ts, "%Y-%m-%dT%H:%M:%S")
145 if not from_flag is None:
146 if tsdt < from_flag:
147 #print (str(tsdt) + " < " + str(from_flag))
148 continue
149 if not to_flag is None:
150 if tsdt > to_flag:
151 #print (str(tsdt) + " > " + str(to_flag))
152 continue
154 ts_block = {};
155 field_block = [];
157 for f in self.timestamp_data[ts]:
158 if f in fields:
159 field_block.append({"name": f,
160 "type": self.fields[f]["type"],
161 "unit": self.fields[f]["unit"],
162 "dataType": self.fields[f]["dataType"],
163 "value": self.timestamp_data[ts][f]["value"],
164 "flags": self.timestamp_data[ts][f]["flags"]});
166 ts_block["timestamp"] = ts;
167 ts_block["fields"] = field_block;
168 callback(session, result="fields", nodeId=self.nodeId, timestamp_block=ts_block);
169 callback(session, result="done", nodeId=self.nodeId, timestamp_block=None);
171 def _datetime_flag_parser(self, flags, flagname):
172 if not flagname in flags:
173 return None
175 dt = None
176 try:
177 dt = datetime.datetime.strptime(flags[flagname], "%Y-%m-%dT%H:%M:%S")
178 except ValueError:
179 # Badly formatted datetime, ignore it
180 pass
181 return dt
184 def _get_timestamp(self):
186 Generates a properly formatted timestamp of current time
188 return datetime.datetime.now().replace(microsecond=0).isoformat()
190 def _send_reject(self, session, callback):
192 Sends a reject to the caller
194 Arguments:
195 session -- Session id, see definition in request_fields function
196 callback -- Callback function, see definition in request_fields function
198 callback(session, result="error", nodeId=self.nodeId, timestamp_block=None, error_msg="Reject");
200 def _add_field(self, name, typename, unit=None, dataType=None):
202 Adds a field to the device
204 Arguments:
205 name -- Name of the field
206 typename -- Type of the field (numeric, boolean, dateTime, timeSpan, string, enum)
207 unit -- [optional] only applies to "numeric". Unit for the field.
208 dataType -- [optional] only applies to "enum". Datatype for the field.
210 self.fields[name] = {"type": typename, "unit": unit, "dataType": dataType};
212 def _add_field_timestamp_data(self, name, timestamp, value, flags=None):
214 Adds timestamped data to a field
216 Arguments:
217 name -- Name of the field
218 timestamp -- Timestamp for the data (string)
219 value -- Field value at the timestamp
220 flags -- [optional] data classifier flags for the field, e.g. momentary
221 Formatted as a dictionary like { "flag name": "flag value" ... }
223 if not name in self.fields.keys():
224 return False;
225 if not timestamp in self.timestamp_data:
226 self.timestamp_data[timestamp] = {};
228 self.timestamp_data[timestamp][name] = {"value": value, "flags": flags};
229 return True;
231 def _add_field_momentary_data(self, name, value, flags=None):
233 Sets momentary data to a field
235 Arguments:
236 name -- Name of the field
237 value -- Field value at the timestamp
238 flags -- [optional] data classifier flags for the field, e.g. momentary
239 Formatted as a dictionary like { "flag name": "flag value" ... }
241 if name not in self.fields:
242 return False;
243 if flags is None:
244 flags = {};
246 flags["momentary"] = "true"
247 self.momentary_data[name] = {"value": value, "flags": flags};
248 return True;
250 def _set_momentary_timestamp(self, timestamp):
252 This function is only for unit testing to produce predictable results.
254 self.momentary_timestamp = timestamp;