text: """
      Slixmpp: The Slick 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 Slixmpp.
  
      See the file LICENSE for copying permission.
  """
  
  import datetime
  import logging
  
  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, fields=None):
          if not fields:
              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.
  
  
          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.
  
          """
          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")
  
      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.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
  
      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 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
  
  