Merge pull request #3 from fritzy/develop

Merge to fritzy_master
This commit is contained in:
Robert Robinson 2015-09-18 13:30:30 -06:00
commit 5fc14de32e
73 changed files with 2409 additions and 1214 deletions

1
.gitignore vendored
View File

@ -12,3 +12,4 @@ sleekxmpp.egg-info/
*~
.baboon/
.DS_STORE
.idea/

10
.travis.yml Normal file
View File

@ -0,0 +1,10 @@
language: python
python:
- "2.6"
- "2.7"
- "3.2"
- "3.3"
- "3.4"
install:
- "pip install ."
script: testall.py

View File

@ -3,7 +3,7 @@ SleekXMPP
SleekXMPP is an MIT licensed XMPP library for Python 2.6/3.1+,
and is featured in examples in
`XMPP: The Definitive Guide <http://oreilly.com/catalog/9780596521271>`_
`XMPP: The Definitive Guide <http://oreilly.com/catalog/9780596521271>`_
by Kevin Smith, Remko Tronçon, and Peter Saint-Andre. If you've arrived
here from reading the Definitive Guide, please see the notes on updating
the examples to the latest version of SleekXMPP.
@ -52,7 +52,7 @@ The latest source code for SleekXMPP may be found on `Github
Installing DNSPython
---------------------
--------------------
If you are using Python3 and wish to use dnspython, you will have to checkout and
install the ``python3`` branch::
@ -144,7 +144,7 @@ SleekXMPP projects::
if __name__ == '__main__':
# Ideally use optparse or argparse to get JID,
# Ideally use optparse or argparse to get JID,
# password, and log level.
logging.basicConfig(level=logging.DEBUG,
@ -158,15 +158,15 @@ SleekXMPP projects::
Credits
-------
**Main Author:** Nathan Fritz
`fritzy@netflint.net <xmpp:fritzy@netflint.net?message>`_,
`fritzy@netflint.net <xmpp:fritzy@netflint.net?message>`_,
`@fritzy <http://twitter.com/fritzy>`_
Nathan is also the author of XMPPHP and `Seesmic-AS3-XMPP
<http://code.google.com/p/seesmic-as3-xmpp/>`_, and a former member of
<http://code.google.com/p/seesmic-as3-xmpp/>`_, and a former member of
the XMPP Council.
**Co-Author:** Lance Stout
`lancestout@gmail.com <xmpp:lancestout@gmail.com?message>`_,
`lancestout@gmail.com <xmpp:lancestout@gmail.com?message>`_,
`@lancestout <http://twitter.com/lancestout>`_
**Contributors:**

View File

@ -161,7 +161,7 @@ item itself, and the JID and node that will own the item.
In this case, the owning JID and node are provided with the
parameters ``ijid`` and ``node``.
Peforming Disco Queries
Performing Disco Queries
-----------------------
The methods ``get_info()`` and ``get_items()`` are used to query remote JIDs
and their nodes for disco information. Since these methods are wrappers for

View File

@ -11,18 +11,10 @@
See the file LICENSE for copying permission.
"""
import os
import sys
# This can be used when you are in a test environment and need to make paths right
sys.path=['/Users/jocke/Dropbox/06_dev/SleekXMPP']+sys.path
import getpass
import logging
import unittest
import distutils.core
import datetime
import sys
from glob import glob
from os.path import splitext, basename, join as pjoin
from optparse import OptionParser
from urllib import urlopen
@ -39,8 +31,6 @@ else:
from sleekxmpp.plugins.xep_0323.device import Device
#from sleekxmpp.exceptions import IqError, IqTimeout
class IoT_TestDevice(sleekxmpp.ClientXMPP):
"""
@ -179,13 +169,13 @@ if __name__ == '__main__':
# node=opts.nodeid,
# jid=xmpp.boundjid.full)
myDevice = TheDevice(opts.nodeid);
myDevice = TheDevice(opts.nodeid)
# myDevice._add_field(name="Relay", typename="numeric", unit="Bool");
myDevice._add_field(name="Temperature", typename="numeric", unit="C");
myDevice._add_field(name="Temperature", typename="numeric", unit="C")
myDevice._set_momentary_timestamp("2013-03-07T16:24:30")
myDevice._add_field_momentary_data("Temperature", "23.4", flags={"automaticReadout": "true"});
myDevice._add_field_momentary_data("Temperature", "23.4", flags={"automaticReadout": "true"})
xmpp['xep_0323'].register_node(nodeId=opts.nodeid, device=myDevice, commTimeout=10);
xmpp['xep_0323'].register_node(nodeId=opts.nodeid, device=myDevice, commTimeout=10)
xmpp.beClientOrServer(server=True)
while not(xmpp.testForRelease()):
xmpp.connect()

View File

@ -94,7 +94,7 @@ class Disco(sleekxmpp.ClientXMPP):
info = self['xep_0030'].get_info(jid=self.target_jid,
node=self.target_node,
block=True)
elif self.get in self.items_types:
if self.get in self.items_types:
# The same applies from above. Listen for the
# disco_items event or pass a callback function
# if you need to process a non-blocking request.

101
examples/http_over_xmpp.py Normal file
View File

@ -0,0 +1,101 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
SleekXMPP: The Sleek XMPP Library
Implementation of HTTP over XMPP transport
http://xmpp.org/extensions/xep-0332.html
Copyright (C) 2015 Riptide IO, sangeeth@riptideio.com
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp import ClientXMPP
from optparse import OptionParser
import logging
import getpass
class HTTPOverXMPPClient(ClientXMPP):
def __init__(self, jid, password):
ClientXMPP.__init__(self, jid, password)
self.register_plugin('xep_0332') # HTTP over XMPP Transport
self.add_event_handler(
'session_start', self.session_start, threaded=True
)
self.add_event_handler('http_request', self.http_request_received)
self.add_event_handler('http_response', self.http_response_received)
def http_request_received(self, iq):
pass
def http_response_received(self, iq):
print 'HTTP Response Received : ', iq
print 'From : ', iq['from']
print 'To : ', iq['to']
print 'Type : ', iq['type']
print 'Headers : ', iq['resp']['headers']
print 'Code : ', iq['resp']['code']
print 'Message : ', iq['resp']['message']
print 'Data : ', iq['resp']['data']
def session_start(self, event):
# TODO: Fill in the blanks
self['xep_0332'].send_request(
to='?', method='?', resource='?', headers={}
)
self.disconnect()
if __name__ == '__main__':
#
# NOTE: To run this example, fill up the blanks in session_start() and
# use the following command.
#
# ./http_over_xmpp.py -J <jid> -P <pwd> -i <ip> -p <port> [-v]
#
parser = OptionParser()
# Output verbosity options.
parser.add_option(
'-v', '--verbose', help='set logging to DEBUG', action='store_const',
dest='loglevel', const=logging.DEBUG, default=logging.ERROR
)
# JID and password options.
parser.add_option('-J', '--jid', dest='jid', help='JID')
parser.add_option('-P', '--password', dest='password', help='Password')
# XMPP server ip and port options.
parser.add_option(
'-i', '--ipaddr', dest='ipaddr',
help='IP Address of the XMPP server', default=None
)
parser.add_option(
'-p', '--port', dest='port',
help='Port of the XMPP server', default=None
)
opts, args = parser.parse_args()
# Setup logging.
logging.basicConfig(level=opts.loglevel,
format='%(levelname)-8s %(message)s')
if opts.jid is None:
opts.jid = raw_input('Username: ')
if opts.password is None:
opts.password = getpass.getpass('Password: ')
xmpp = HTTPOverXMPPClient(opts.jid, opts.password)
if xmpp.connect((opts.ipaddr, int(opts.port))):
print 'Connected!'
xmpp.process(block=True)
else:
print 'Not connected!'
print 'Goodbye....'

View File

@ -113,7 +113,7 @@ def on_session2(event):
new_xmpp.update_roster(jid,
name = item['name'],
groups = item['groups'])
new_xmpp.disconnect()
new_xmpp.disconnect()
new_xmpp.add_event_handler('session_start', on_session2)
if new_xmpp.connect():

View File

@ -68,7 +68,7 @@ class RosterBrowser(sleekxmpp.ClientXMPP):
try:
self.get_roster()
except IqError as err:
print('Error: %' % err.iq['error']['condition'])
print('Error: %s' % err.iq['error']['condition'])
except IqTimeout:
print('Error: Request timed out')
self.send_presence()

View File

@ -63,7 +63,7 @@ class AvatarSetter(sleekxmpp.ClientXMPP):
avatar_file = None
try:
avatar_file = open(os.path.expanduser(self.filepath))
avatar_file = open(os.path.expanduser(self.filepath), 'rb')
except IOError:
print('Could not find file: %s' % self.filepath)
return self.disconnect()

View File

@ -93,6 +93,7 @@ packages = [ 'sleekxmpp',
'sleekxmpp/plugins/xep_0108',
'sleekxmpp/plugins/xep_0115',
'sleekxmpp/plugins/xep_0118',
'sleekxmpp/plugins/xep_0122',
'sleekxmpp/plugins/xep_0128',
'sleekxmpp/plugins/xep_0131',
'sleekxmpp/plugins/xep_0152',
@ -123,6 +124,8 @@ packages = [ 'sleekxmpp',
'sleekxmpp/plugins/xep_0323/stanza',
'sleekxmpp/plugins/xep_0325',
'sleekxmpp/plugins/xep_0325/stanza',
'sleekxmpp/plugins/xep_0332',
'sleekxmpp/plugins/xep_0332/stanza',
'sleekxmpp/plugins/google',
'sleekxmpp/plugins/google/gmail',
'sleekxmpp/plugins/google/auth',

View File

@ -25,7 +25,6 @@ from sleekxmpp.exceptions import IqError, IqTimeout
from sleekxmpp.stanza import Message, Presence, Iq, StreamError
from sleekxmpp.stanza.roster import Roster
from sleekxmpp.stanza.nick import Nick
from sleekxmpp.stanza.htmlim import HTMLIM
from sleekxmpp.xmlstream import XMLStream, JID
from sleekxmpp.xmlstream import ET, register_stanza_plugin
@ -56,8 +55,8 @@ class BaseXMPP(XMLStream):
is used during initialization.
"""
def __init__(self, jid='', default_ns='jabber:client'):
XMLStream.__init__(self)
def __init__(self, jid='', default_ns='jabber:client', **kwargs):
XMLStream.__init__(self, **kwargs)
self.default_ns = default_ns
self.stream_ns = 'http://etherx.jabber.org/streams'
@ -245,7 +244,7 @@ class BaseXMPP(XMLStream):
self.plugin[name].post_inited = True
return XMLStream.process(self, *args, **kwargs)
def register_plugin(self, plugin, pconfig={}, module=None):
def register_plugin(self, plugin, pconfig=None, module=None):
"""Register and configure a plugin for use in this stream.
:param plugin: The name of the plugin class. Plugin names must

View File

@ -52,7 +52,6 @@ class ClientXMPP(BaseXMPP):
:param jid: The JID of the XMPP user account.
:param password: The password for the XMPP user account.
:param ssl: **Deprecated.**
:param plugin_config: A dictionary of plugin configurations.
:param plugin_whitelist: A list of approved plugins that
will be loaded when calling
@ -60,9 +59,15 @@ class ClientXMPP(BaseXMPP):
:param escape_quotes: **Deprecated.**
"""
def __init__(self, jid, password, plugin_config={}, plugin_whitelist=[],
escape_quotes=True, sasl_mech=None, lang='en'):
BaseXMPP.__init__(self, jid, 'jabber:client')
def __init__(self, jid, password, plugin_config=None,
plugin_whitelist=None, escape_quotes=True, sasl_mech=None,
lang='en', **kwargs):
if not plugin_whitelist:
plugin_whitelist = []
if not plugin_config:
plugin_config = {}
BaseXMPP.__init__(self, jid, 'jabber:client', **kwargs)
self.escape_quotes = escape_quotes
self.plugin_config = plugin_config

View File

@ -49,8 +49,13 @@ class ComponentXMPP(BaseXMPP):
Defaults to ``False``.
"""
def __init__(self, jid, secret, host=None, port=None,
plugin_config={}, plugin_whitelist=[], use_jc_ns=False):
def __init__(self, jid, secret, host=None, port=None, plugin_config=None, plugin_whitelist=None, use_jc_ns=False):
if not plugin_whitelist:
plugin_whitelist = []
if not plugin_config:
plugin_config = {}
if use_jc_ns:
default_ns = 'jabber:client'
else:

View File

@ -187,14 +187,14 @@ class FeatureMechanisms(BasePlugin):
except sasl.SASLCancelled:
self.attempted_mechs.add(self.mech.name)
self._send_auth()
except sasl.SASLFailed:
self.attempted_mechs.add(self.mech.name)
self._send_auth()
except sasl.SASLMutualAuthFailed:
log.error("Mutual authentication failed! " + \
"A security breach is possible.")
self.attempted_mechs.add(self.mech.name)
self.xmpp.disconnect()
except sasl.SASLFailed:
self.attempted_mechs.add(self.mech.name)
self._send_auth()
else:
resp.send(now=True)
@ -207,13 +207,13 @@ class FeatureMechanisms(BasePlugin):
resp['value'] = self.mech.process(stanza['value'])
except sasl.SASLCancelled:
self.stanza.Abort(self.xmpp).send()
except sasl.SASLFailed:
self.stanza.Abort(self.xmpp).send()
except sasl.SASLMutualAuthFailed:
log.error("Mutual authentication failed! " + \
"A security breach is possible.")
self.attempted_mechs.add(self.mech.name)
self.xmpp.disconnect()
except sasl.SASLFailed:
self.stanza.Abort(self.xmpp).send()
else:
if resp.get_value() == '':
resp.del_value()

View File

@ -72,19 +72,18 @@ JID_CACHE_LOCK = threading.Lock()
JID_CACHE_MAX_SIZE = 1024
def _cache(key, parts, locked):
JID_CACHE[key] = (parts, locked)
if len(JID_CACHE) > JID_CACHE_MAX_SIZE:
with JID_CACHE_LOCK:
while len(JID_CACHE) > JID_CACHE_MAX_SIZE:
found = None
for key, item in JID_CACHE.items():
if not item[1]: # if not locked
found = key
break
if not found: # more than MAX_SIZE locked
# warn?
with JID_CACHE_LOCK:
JID_CACHE[key] = (parts, locked)
while len(JID_CACHE) > JID_CACHE_MAX_SIZE:
found = None
for key, item in JID_CACHE.items():
if not item[1]: # if not locked
found = key
break
del JID_CACHE[found]
if not found: # more than MAX_SIZE locked
# warn?
break
del JID_CACHE[found]
# pylint: disable=c0103
#: The nodeprep profile of stringprep used to validate the local,
@ -528,10 +527,6 @@ class JID(object):
def username(self):
return self._jid[0] or ''
@property
def bare(self):
return _format_jid(self._jid[0], self._jid[1])
@property
def server(self):
return self._jid[1] or ''
@ -556,7 +551,6 @@ class JID(object):
def bare(self):
return _format_jid(self._jid[0], self._jid[1])
@resource.setter
def resource(self, value):
self._jid = JID(self, resource=value)._jid

View File

@ -47,6 +47,7 @@ __all__ = [
'xep_0108', # User Activity
'xep_0115', # Entity Capabilities
'xep_0118', # User Tune
'xep_0122', # Data Forms Validation
'xep_0128', # Extended Service Discovery
'xep_0131', # Standard Headers and Internet Metadata
'xep_0133', # Service Administration
@ -83,4 +84,5 @@ __all__ = [
'xep_0319', # Last User Interaction in Presence
'xep_0323', # IoT Systems Sensor Data
'xep_0325', # IoT Systems Control
'xep_0332', # HTTP Over XMPP Transport
]

View File

@ -24,7 +24,7 @@ class GoogleAuth(ElementBase):
print('setting up google extension')
def get_client_uses_full_bind_result(self):
return self.parent()._get_attr(self.disovery_attr) == 'true'
return self.parent()._get_attr(self.discovery_attr) == 'true'
def set_client_uses_full_bind_result(self, value):
print('>>>', value)

View File

@ -74,8 +74,8 @@ class Gmail(BasePlugin):
return resp
def _update_last_results(self, iq, callback=None):
self._last_result_time = data['gmail_messages']['result_time']
threads = data['gmail_messages']['threads']
self._last_result_time = iq['gmail_messages']['result_time']
threads = iq['gmail_messages']['threads']
if threads:
self._last_result_tid = threads[0]['tid']
if callback:

View File

@ -52,7 +52,7 @@ class Item(ElementBase):
def get_source(self):
return JID(self._get_attr('source', ''))
def set_source(self):
def set_source(self, value):
self._set_attr('source', str(value))

View File

@ -6,8 +6,6 @@
See the file LICENSE for copying permission.
"""
import logging
from sleekxmpp.stanza import Iq
from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.xmlstream.matcher import StanzaPath

View File

@ -13,8 +13,9 @@ class FormField(ElementBase):
namespace = 'jabber:x:data'
name = 'field'
plugin_attrib = 'field'
plugin_multi_attrib = 'fields'
interfaces = set(('answer', 'desc', 'required', 'value',
'options', 'label', 'type', 'var'))
'label', 'type', 'var'))
sub_interfaces = set(('desc',))
plugin_tag_map = {}
plugin_attrib_map = {}
@ -165,6 +166,7 @@ class FieldOption(ElementBase):
plugin_attrib = 'option'
interfaces = set(('label', 'value'))
sub_interfaces = set(('value',))
plugin_multi_attrib = 'options'
FormField.addOption = FormField.add_option

View File

@ -9,7 +9,7 @@
import copy
import logging
from sleekxmpp.thirdparty import OrderedDict
from sleekxmpp.thirdparty import OrderedDict, OrderedSet
from sleekxmpp.xmlstream import ElementBase, ET
from sleekxmpp.plugins.xep_0004.stanza import FormField
@ -22,8 +22,7 @@ class Form(ElementBase):
namespace = 'jabber:x:data'
name = 'x'
plugin_attrib = 'form'
interfaces = set(('fields', 'instructions', 'items',
'reported', 'title', 'type', 'values'))
interfaces = OrderedSet(('instructions', 'reported', 'title', 'type', 'items', ))
sub_interfaces = set(('title',))
form_types = set(('cancel', 'form', 'result', 'submit'))
@ -43,12 +42,12 @@ class Form(ElementBase):
@property
def field(self):
return self['fields']
return self.get_fields()
def set_type(self, ftype):
self._set_attr('type', ftype)
if ftype == 'submit':
fields = self['fields']
fields = self.get_fields()
for var in fields:
field = fields[var]
del field['type']
@ -74,7 +73,8 @@ class Form(ElementBase):
field['desc'] = desc
field['required'] = required
if options is not None:
field['options'] = options
for option in options:
field.add_option(**option)
else:
del field['type']
self.append(field)
@ -151,7 +151,6 @@ class Form(ElementBase):
return fields
def get_instructions(self):
instructions = ''
instsXML = self.xml.findall('{%s}instructions' % self.namespace)
return "\n".join([instXML.text for instXML in instsXML])
@ -170,7 +169,7 @@ class Form(ElementBase):
def get_reported(self):
fields = OrderedDict()
xml = self.xml.findall('{%s}reported/{%s}field' % (self.namespace,
FormField.namespace))
FormField.namespace))
for field in xml:
field = FormField(xml=field)
fields[field['var']] = field
@ -178,7 +177,7 @@ class Form(ElementBase):
def get_values(self):
values = OrderedDict()
fields = self['fields']
fields = self.get_fields()
for var in fields:
values[var] = fields[var]['value']
return values
@ -195,7 +194,14 @@ class Form(ElementBase):
fields = fields.items()
for var, field in fields:
field['var'] = var
self.add_field(**field)
self.add_field(
var = field.get('var'),
label = field.get('label'),
desc = field.get('desc'),
required = field.get('required'),
value = field.get('value'),
options = field.get('options'),
type = field.get('type'))
def set_instructions(self, instructions):
del self['instructions']
@ -213,17 +219,33 @@ class Form(ElementBase):
self.add_item(item)
def set_reported(self, reported):
"""
This either needs a dictionary or dictionaries or a dictionary of form fields.
:param reported:
:return:
"""
for var in reported:
field = reported[var]
field['var'] = var
self.add_reported(var, **field)
if isinstance(field, dict):
self.add_reported(**field)
else:
reported = self.xml.find('{%s}reported' % self.namespace)
if reported is None:
reported = ET.Element('{%s}reported' % self.namespace)
self.xml.append(reported)
fieldXML = ET.Element('{%s}field' % FormField.namespace)
reported.append(fieldXML)
new_field = FormField(xml=fieldXML)
new_field.values = field.values
def set_values(self, values):
fields = self['fields']
fields = self.get_fields()
for field in values:
if field not in fields:
if field not in self.get_fields():
fields[field] = self.add_field(var=field)
fields[field]['value'] = values[field]
self.get_fields()[field]['value'] = values[field]
def merge(self, other):
new = copy.copy(self)

View File

@ -6,7 +6,7 @@
See the file LICENSE for copying permission.
"""
from binding import py2xml, xml2py, xml2fault, fault2xml
from sleekxmpp.plugins.xep_0009.binding import py2xml, xml2py, xml2fault, fault2xml
from threading import RLock
import abc
import inspect
@ -18,6 +18,45 @@ import traceback
log = logging.getLogger(__name__)
# Define a function _isstr() to check if an object is a string in a way
# compatible with Python 2 and Python 3 (basestring does not exists in Python 3).
try:
basestring # This evaluation will throw an exception if basestring does not exists (Python 3).
def _isstr(obj):
return isinstance(obj, basestring)
except NameError:
def _isstr(obj):
return isinstance(obj, str)
# Class decorator to declare a metaclass to a class in a way compatible with Python 2 and 3.
# This decorator is copied from 'six' (https://bitbucket.org/gutworth/six):
#
# Copyright (c) 2010-2015 Benjamin Peterson
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
def _add_metaclass(metaclass):
def wrapper(cls):
orig_vars = cls.__dict__.copy()
slots = orig_vars.get('__slots__')
if slots is not None:
if isinstance(slots, str):
slots = [slots]
for slots_var in slots:
orig_vars.pop(slots_var)
orig_vars.pop('__dict__', None)
orig_vars.pop('__weakref__', None)
return metaclass(cls.__name__, cls.__bases__, orig_vars)
return wrapper
def _intercept(method, name, public):
def _resolver(instance, *args, **kwargs):
log.debug("Locally calling %s.%s with arguments %s.", instance.FQN(), method.__name__, args)
@ -68,7 +107,7 @@ def remote(function_argument, public = True):
if hasattr(function_argument, '__call__'):
return _intercept(function_argument, None, public)
else:
if not isinstance(function_argument, basestring):
if not _isstr(function_argument):
if not isinstance(function_argument, bool):
raise Exception('Expected an RPC method name or visibility modifier!')
else:
@ -222,12 +261,11 @@ class TimeoutException(Exception):
pass
@_add_metaclass(abc.ABCMeta)
class Callback(object):
'''
A base class for callback handlers.
'''
__metaclass__ = abc.ABCMeta
@abc.abstractproperty
def set_value(self, value):
@ -291,7 +329,7 @@ class Future(Callback):
self._event.set()
@_add_metaclass(abc.ABCMeta)
class Endpoint(object):
'''
The Endpoint class is an abstract base class for all objects
@ -303,8 +341,6 @@ class Endpoint(object):
which specifies which object an RPC call refers to. It is the
first part in a RPC method name '<fqn>.<method>'.
'''
__metaclass__ = abc.ABCMeta
def __init__(self, session, target_jid):
'''
@ -491,7 +527,7 @@ class RemoteSession(object):
def _find_key(self, dict, value):
"""return the key of dictionary dic given the value"""
search = [k for k, v in dict.iteritems() if v == value]
search = [k for k, v in dict.items() if v == value]
if len(search) == 0:
return None
else:
@ -547,7 +583,7 @@ class RemoteSession(object):
result = handler_cls(*args, **kwargs)
Endpoint.__init__(result, self, self._client.boundjid.full)
method_dict = result.get_methods()
for method_name, method in method_dict.iteritems():
for method_name, method in method_dict.items():
#!!! self._client.plugin['xep_0009'].register_call(result.FQN(), method, method_name)
self._register_call(result.FQN(), method, method_name)
self._register_acl(result.FQN(), acl)
@ -569,11 +605,11 @@ class RemoteSession(object):
self._register_callback(pid, callback)
iq.send()
def close(self):
def close(self, wait=False):
'''
Closes this session.
'''
self._client.disconnect(False)
self._client.disconnect(wait=wait)
self._session_close_callback()
def _on_jabber_rpc_method_call(self, iq):
@ -697,7 +733,8 @@ class Remote(object):
if(client.boundjid.bare in cls._sessions):
raise RemoteException("There already is a session associated with these credentials!")
else:
cls._sessions[client.boundjid.bare] = client;
cls._sessions[client.boundjid.bare] = client
def _session_close_callback():
with Remote._lock:
del cls._sessions[client.boundjid.bare]

View File

@ -61,7 +61,7 @@ class XEP_0009(BasePlugin):
iq.enable('rpc_query')
iq['rpc_query']['method_call']['method_name'] = pmethod
iq['rpc_query']['method_call']['params'] = params
return iq;
return iq
def make_iq_method_response(self, pid, pto, params):
iq = self.xmpp.makeIqResult(pid)
@ -93,7 +93,7 @@ class XEP_0009(BasePlugin):
def _item_not_found(self, iq):
payload = iq.get_payload()
iq.reply().error().set_payload(payload);
iq.reply().error().set_payload(payload)
iq['error']['code'] = '404'
iq['error']['type'] = 'cancel'
iq['error']['condition'] = 'item-not-found'

View File

@ -324,7 +324,7 @@ class XEP_0030(BasePlugin):
callback -- Optional callback to execute when a reply is
received instead of blocking and waiting for
the reply.
timeout_callback -- Optional callback to execute when no result
timeout_callback -- Optional callback to execute when no result
has been received in timeout seconds.
"""
if local is None:
@ -408,7 +408,7 @@ class XEP_0030(BasePlugin):
iterator -- If True, return a result set iterator using
the XEP-0059 plugin, if the plugin is loaded.
Otherwise the parameter is ignored.
timeout_callback -- Optional callback to execute when no result
timeout_callback -- Optional callback to execute when no result
has been received in timeout seconds.
"""
if local or local is None and jid is None:
@ -604,7 +604,7 @@ class XEP_0030(BasePlugin):
"""
self.api['del_features'](jid, node, None, kwargs)
def _run_node_handler(self, htype, jid, node=None, ifrom=None, data={}):
def _run_node_handler(self, htype, jid, node=None, ifrom=None, data=None):
"""
Execute the most specific node handler for the given
JID/node combination.
@ -615,6 +615,9 @@ class XEP_0030(BasePlugin):
node -- The node requested.
data -- Optional, custom data to pass to the handler.
"""
if not data:
data = {}
return self.api[htype](jid, node, ifrom, data)
def _handle_disco_info(self, iq):

View File

@ -397,6 +397,16 @@ class XEP_0045(BasePlugin):
return None
return self.rooms[room].keys()
def getUsersByAffiliation(cls, room, affiliation='member', ifrom=None):
if affiliation not in ('outcast', 'member', 'admin', 'owner', 'none'):
raise TypeError
query = ET.Element('{http://jabber.org/protocol/muc#admin}query')
item = ET.Element('{http://jabber.org/protocol/muc#admin}item', {'affiliation': affiliation})
query.append(item)
iq = cls.xmpp.Iq(sto=room, sfrom=ifrom, stype='get')
iq.append(query)
return iq.send()
xep_0045 = XEP_0045
register_plugin(XEP_0045)

View File

@ -128,7 +128,8 @@ class Telephone(ElementBase):
def setup(self, xml=None):
super(Telephone, self).setup(xml=xml)
self._set_sub_text('NUMBER', '', keep=True)
## this blanks out numbers received from server
##self._set_sub_text('NUMBER', '', keep=True)
def set_number(self, value):
self._set_sub_text('NUMBER', value, keep=True)

View File

@ -206,7 +206,7 @@ class XEP_0065(base_plugin):
# Though this should not be neccessary remove the closed session anyway
with self._sessions_lock:
if sid in self._sessions:
log.warn(('SOCKS5 session with sid = "%s" was not ' +
log.warn(('SOCKS5 session with sid = "%s" was not ' +
'removed from _sessions by sock.close()') % sid)
del self._sessions[sid]
@ -240,9 +240,9 @@ class XEP_0065(base_plugin):
# The hostname MUST be SHA1(SID + Requester JID + Target JID)
# where the output is hexadecimal-encoded (not binary).
digest = sha1()
digest.update(sid)
digest.update(str(requester))
digest.update(str(target))
digest.update(sid.encode('utf-8'))
digest.update(str(requester).encode('utf-8'))
digest.update(str(target).encode('utf-8'))
dest = digest.hexdigest()

View File

@ -8,7 +8,7 @@
from base64 import b64encode, b64decode
from sleekxmpp.util import bytes
from sleekxmpp.util import bytes as sbytes
from sleekxmpp.xmlstream import ET, ElementBase, register_stanza_plugin
@ -20,12 +20,15 @@ class Data(ElementBase):
def get_value(self):
if self.xml.text:
return b64decode(bytes(self.xml.text))
return b64decode(sbytes(self.xml.text))
return ''
def set_value(self, value):
if value:
self.xml.text = b64encode(bytes(value))
self.xml.text = b64encode(sbytes(value))
# Python3 base64 encoded is bytes and needs to be decoded to string
if isinstance(self.xml.text, bytes):
self.xml.text = self.xml.text.decode()
else:
self.xml.text = ''

View File

@ -47,6 +47,7 @@ class XEP_0096(BasePlugin):
data['size'] = size
data['date'] = date
data['desc'] = desc
data['hash'] = hash
if allow_ranged:
data.enable('range')

View File

@ -0,0 +1,11 @@
from sleekxmpp.plugins.base import register_plugin
from sleekxmpp.plugins.xep_0122.stanza import FormValidation
from sleekxmpp.plugins.xep_0122.data_validation import XEP_0122
register_plugin(XEP_0122)
# Retain some backwards compatibility
xep_0122 = XEP_0122

View File

@ -0,0 +1,19 @@
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.plugins import BasePlugin
from sleekxmpp.plugins.xep_0004 import stanza
from sleekxmpp.plugins.xep_0004.stanza import FormField
from sleekxmpp.plugins.xep_0122.stanza import FormValidation
class XEP_0122(BasePlugin):
"""
XEP-0004: Data Forms
"""
name = 'xep_0122'
description = 'XEP-0122: Data Forms Validation'
dependencies = set(['xep_0004'])
stanza = stanza
def plugin_init(self):
register_stanza_plugin(FormField, FormValidation)

View File

@ -0,0 +1,94 @@
from sleekxmpp.xmlstream import ElementBase, ET
class FormValidation(ElementBase):
"""
Validation values for form fields.
Example:
<field var='evt.date' type='text-single' label='Event Date/Time'>
<validate xmlns='http://jabber.org/protocol/xdata-validate'
datatype='xs:dateTime'/>
<value>2003-10-06T11:22:00-07:00</value>
</field>
Questions:
Should this look at the datatype value and convert the range values as appropriate?
Should this stanza provide a pass/fail for a value from the field, or convert field value to datatype?
"""
namespace = 'http://jabber.org/protocol/xdata-validate'
name = 'validate'
plugin_attrib = 'validate'
interfaces = {'datatype', 'basic', 'open', 'range', 'regex', }
sub_interfaces = {'basic', 'open', 'range', 'regex', }
plugin_attrib_map = {}
plugin_tag_map = {}
def _add_field(self, name):
self.remove_all()
item_xml = ET.Element('{%s}%s' % (self.namespace, name))
self.xml.append(item_xml)
return item_xml
def set_basic(self, value):
if value:
self._add_field('basic')
else:
del self['basic']
def set_open(self, value):
if value:
self._add_field('open')
else:
del self['open']
def set_regex(self, regex):
if regex:
_regex = self._add_field('regex')
_regex.text = regex
else:
del self['regex']
def set_range(self, value, minimum=None, maximum=None):
if value:
_range = self._add_field('range')
_range.attrib['min'] = str(minimum)
_range.attrib['max'] = str(maximum)
else:
del self['range']
def remove_all(self, except_tag=None):
for a in self.sub_interfaces:
if a != except_tag:
del self[a]
def get_basic(self):
present = self.xml.find('{%s}basic' % self.namespace)
return present is not None
def get_open(self):
present = self.xml.find('{%s}open' % self.namespace)
return present is not None
def get_regex(self):
present = self.xml.find('{%s}regex' % self.namespace)
if present is not None:
return present.text
return False
def get_range(self):
present = self.xml.find('{%s}range' % self.namespace)
if present is not None:
attributes = present.attrib
return_value = dict()
if 'min' in attributes:
return_value['minimum'] = attributes['min']
if 'max' in attributes:
return_value['maximum'] = attributes['max']
return return_value
return False

View File

@ -0,0 +1,148 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2011 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
import logging
import socket
import zlib
from sleekxmpp.thirdparty.suelta.util import bytes
from sleekxmpp.stanza import StreamFeatures
from sleekxmpp.xmlstream import RestartStream, register_stanza_plugin, ElementBase, StanzaBase
from sleekxmpp.xmlstream.matcher import *
from sleekxmpp.xmlstream.handler import *
from sleekxmpp.plugins import BasePlugin, register_plugin
log = logging.getLogger(__name__)
class Compression(ElementBase):
name = 'compression'
namespace = 'http://jabber.org/features/compress'
interfaces = set(('methods',))
plugin_attrib = 'compression'
plugin_tag_map = {}
plugin_attrib_map = {}
def get_methods(self):
methods = []
for method in self.xml.findall('{%s}method' % self.namespace):
methods.append(method.text)
return methods
class Compress(StanzaBase):
name = 'compress'
namespace = 'http://jabber.org/protocol/compress'
interfaces = set(('method',))
sub_interfaces = interfaces
plugin_attrib = 'compress'
plugin_tag_map = {}
plugin_attrib_map = {}
def setup(self, xml):
StanzaBase.setup(self, xml)
self.xml.tag = self.tag_name()
class Compressed(StanzaBase):
name = 'compressed'
namespace = 'http://jabber.org/protocol/compress'
interfaces = set()
plugin_tag_map = {}
plugin_attrib_map = {}
def setup(self, xml):
StanzaBase.setup(self, xml)
self.xml.tag = self.tag_name()
class ZlibSocket(object):
def __init__(self, socketobj):
self.__socket = socketobj
self.compressor = zlib.compressobj()
self.decompressor = zlib.decompressobj(zlib.MAX_WBITS)
def __getattr__(self, name):
return getattr(self.__socket, name)
def send(self, data):
sentlen = len(data)
data = self.compressor.compress(data)
data += self.compressor.flush(zlib.Z_SYNC_FLUSH)
log.debug(b'>>> (compressed)' + (data.encode("hex")))
#return self.__socket.send(data)
sentactuallen = self.__socket.send(data)
assert(sentactuallen == len(data))
return sentlen
def recv(self, *args, **kwargs):
data = self.__socket.recv(*args, **kwargs)
log.debug(b'<<< (compressed)' + data.encode("hex"))
return self.decompressor.decompress(self.decompressor.unconsumed_tail + data)
class XEP_0138(BasePlugin):
"""
XEP-0138: Compression
"""
name = "xep_0138"
description = "XEP-0138: Compression"
dependencies = set(["xep_0030"])
def plugin_init(self):
self.xep = '0138'
self.description = 'Stream Compression (Generic)'
self.compression_methods = {'zlib': True}
register_stanza_plugin(StreamFeatures, Compression)
self.xmpp.register_stanza(Compress)
self.xmpp.register_stanza(Compressed)
self.xmpp.register_handler(
Callback('Compressed',
StanzaPath('compressed'),
self._handle_compressed,
instream=True))
self.xmpp.register_feature('compression',
self._handle_compression,
restart=True,
order=self.config.get('order', 5))
def register_compression_method(self, name, handler):
self.compression_methods[name] = handler
def _handle_compression(self, features):
for method in features['compression']['methods']:
if method in self.compression_methods:
log.info('Attempting to use %s compression' % method)
c = Compress(self.xmpp)
c['method'] = method
c.send(now=True)
return True
return False
def _handle_compressed(self, stanza):
self.xmpp.features.add('compression')
log.debug('Stream Compressed!')
compressed_socket = ZlibSocket(self.xmpp.socket)
self.xmpp.set_socket(compressed_socket)
raise RestartStream()
def _handle_failure(self, stanza):
pass
xep_0138 = XEP_0138
register_plugin(XEP_0138)

View File

@ -72,9 +72,10 @@ class XEP_0202(BasePlugin):
Arguments:
iq -- The Iq time request stanza.
"""
iq.reply()
iq['entity_time']['time'] = self.local_time(iq['to'])
iq.send()
if iq['type'] == 'get':
iq.reply()
iq['entity_time']['time'] = self.local_time(iq['to'])
iq.send()
def get_entity_time(self, to, ifrom=None, **iqargs):
"""

View File

@ -10,7 +10,7 @@ from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin
class Certs(ElementBase):
name = 'query'
name = 'items'
namespace = 'urn:xmpp:saslcert:1'
plugin_attrib = 'sasl_certs'
interfaces = set()

View File

@ -13,15 +13,18 @@ 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
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={}):
def __init__(self, nodeId, fields=None):
if not fields:
fields = {}
self.nodeId = nodeId
self.fields = fields # see fields described below
# {'type':'numeric',
@ -38,19 +41,19 @@ class Device(object):
Returns true if the supplied field name exists in this device.
Arguments:
field -- The field name
field -- The field name
"""
if field in self.fields.keys():
return True;
return False;
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):
"""
@ -65,7 +68,7 @@ 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:
session -- Session id, as supplied in the request_fields call
@ -73,11 +76,11 @@ class Device(object):
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
"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:
@ -89,10 +92,10 @@ class Device(object):
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.
Error details when a request failed.
"""
logging.debug("request_fields called looking for fields %s",fields)
@ -101,10 +104,10 @@ class Device(object):
for f in fields:
if f not in self.fields.keys():
self._send_reject(session, callback)
return False;
return False
else:
# Request all fields
fields = self.fields.keys();
fields = self.fields.keys()
# Refresh data from device
@ -114,27 +117,27 @@ class Device(object):
if "momentary" in flags and flags['momentary'] == "true" or \
"all" in flags and flags['all'] == "true":
ts_block = {};
timestamp = "";
ts_block = {}
timestamp = ""
if len(self.momentary_timestamp) > 0:
timestamp = self.momentary_timestamp;
timestamp = self.momentary_timestamp
else:
timestamp = self._get_timestamp();
timestamp = self._get_timestamp()
field_block = [];
field_block = []
for f in self.momentary_data:
if f in fields:
field_block.append({"name": f,
"type": self.fields[f]["type"],
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;
"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);
callback(session, result="done", nodeId=self.nodeId, timestamp_block=ts_block)
return
from_flag = self._datetime_flag_parser(flags, 'from')
@ -142,36 +145,36 @@ class Device(object):
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:
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:
if not to_flag is None:
if tsdt > to_flag:
#print (str(tsdt) + " > " + str(to_flag))
continue
ts_block = {};
field_block = [];
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"],
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"]});
"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);
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")
@ -195,7 +198,7 @@ class Device(object):
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):
"""
@ -207,7 +210,7 @@ class Device(object):
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):
"""
@ -221,12 +224,12 @@ class Device(object):
Formatted as a dictionary like { "flag name": "flag value" ... }
"""
if not name in self.fields.keys():
return False;
return False
if not timestamp in self.timestamp_data:
self.timestamp_data[timestamp] = {};
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):
"""
@ -239,17 +242,17 @@ class Device(object):
Formatted as a dictionary like { "flag name": "flag value" ... }
"""
if name not in self.fields:
return False;
return False
if flags is None:
flags = {};
flags = {}
flags["momentary"] = "true"
self.momentary_data[name] = {"value": value, "flags": flags};
return 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;
self.momentary_timestamp = timestamp

View File

@ -15,7 +15,6 @@ from threading import Thread, Lock, Timer
from sleekxmpp.plugins.xep_0323.timerreset import TimerReset
from sleekxmpp.xmlstream import JID
from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.xmlstream.matcher import StanzaPath
from sleekxmpp.plugins.base import BasePlugin
@ -29,12 +28,12 @@ log = logging.getLogger(__name__)
class XEP_0323(BasePlugin):
"""
XEP-0323: IoT Sensor Data
XEP-0323: IoT Sensor Data
This XEP provides the underlying architecture, basic operations and data
structures for sensor data communication over XMPP networks. It includes
a hardware abstraction model, removing any technical detail implemented
a hardware abstraction model, removing any technical detail implemented
in underlying technologies.
Also see <http://xmpp.org/extensions/xep-0323.html>
@ -55,10 +54,10 @@ class XEP_0323(BasePlugin):
Sensordata Event:Rejected -- Received a reject from sensor for a request
Sensordata Event:Cancelled -- Received a cancel confirm from sensor
Sensordata Event:Fields -- Received fields from sensor for a request
This may be triggered multiple times since
This may be triggered multiple times since
the sensor can split up its response in
multiple messages.
Sensordata Event:Failure -- Received a failure indication from sensor
Sensordata Event:Failure -- Received a failure indication from sensor
for a request. Typically a comm timeout.
Attributes:
@ -69,7 +68,7 @@ class XEP_0323(BasePlugin):
relevant to a request's session. This dictionary is used
both by the client and sensor side. On client side, seqnr
is used as key, while on sensor side, a session_id is used
as key. This ensures that the two will not collide, so
as key. This ensures that the two will not collide, so
one instance can be both client and sensor.
Sensor side
-----------
@ -89,12 +88,12 @@ class XEP_0323(BasePlugin):
Sensor side
-----------
register_node -- Register a sensor as available from this XMPP
register_node -- Register a sensor as available from this XMPP
instance.
Client side
-----------
request_data -- Initiates a request for data from one or more
request_data -- Initiates a request for data from one or more
sensors. Non-blocking, a callback function will
be called when data is available.
@ -102,13 +101,12 @@ class XEP_0323(BasePlugin):
name = 'xep_0323'
description = 'XEP-0323 Internet of Things - Sensor Data'
dependencies = set(['xep_0030'])
dependencies = set(['xep_0030'])
stanza = stanza
default_config = {
'threaded': True
# 'session_db': None
}
def plugin_init(self):
@ -155,17 +153,17 @@ class XEP_0323(BasePlugin):
self._handle_event_started))
# Server side dicts
self.nodes = {};
self.sessions = {};
self.nodes = {}
self.sessions = {}
self.last_seqnr = 0;
self.seqnr_lock = Lock();
self.last_seqnr = 0
self.seqnr_lock = Lock()
## For testning only
## For testing only
self.test_authenticated_from = ""
def post_init(self):
""" Init complete. Register our features in Serivce discovery. """
""" Init complete. Register our features in Service discovery. """
BasePlugin.post_init(self)
self.xmpp['xep_0030'].add_feature(Sensordata.namespace)
self.xmpp['xep_0030'].set_items(node=Sensordata.namespace, items=tuple())
@ -182,7 +180,7 @@ class XEP_0323(BasePlugin):
def plugin_end(self):
""" Stop the XEP-0323 plugin """
self.sessions.clear();
self.sessions.clear()
self.xmpp.remove_handler('Sensordata Event:Req')
self.xmpp.remove_handler('Sensordata Event:Accepted')
self.xmpp.remove_handler('Sensordata Event:Rejected')
@ -198,9 +196,9 @@ class XEP_0323(BasePlugin):
def register_node(self, nodeId, device, commTimeout, sourceId=None, cacheType=None):
"""
Register a sensor/device as available for serving of data through this XMPP
instance.
instance.
The device object may by any custom implementation to support
The device object may by any custom implementation to support
specific devices, but it must implement the functions:
has_field
request_fields
@ -212,25 +210,25 @@ class XEP_0323(BasePlugin):
commTimeout -- Time in seconds to wait between each callback from device during
a data readout. Float.
sourceId -- [optional] identifying the data source controlling the device
cacheType -- [optional] narrowing down the search to a specific kind of node
cacheType -- [optional] narrowing down the search to a specific kind of node
"""
self.nodes[nodeId] = {"device": device,
self.nodes[nodeId] = {"device": device,
"commTimeout": commTimeout,
"sourceId": sourceId,
"cacheType": cacheType};
"sourceId": sourceId,
"cacheType": cacheType}
def _set_authenticated(self, auth=''):
""" Internal testing function """
self.test_authenticated_from = auth;
self.test_authenticated_from = auth
def _handle_event_req(self, iq):
"""
Event handler for reception of an Iq with req - this is a request.
Verifies that
Verifies that
- all the requested nodes are available
- at least one of the requested fields is available from at least
- at least one of the requested fields is available from at least
one of the nodes
If the request passes verification, an accept response is sent, and
@ -238,42 +236,42 @@ class XEP_0323(BasePlugin):
If the verification fails, a reject message is sent.
"""
seqnr = iq['req']['seqnr'];
error_msg = '';
req_ok = True;
seqnr = iq['req']['seqnr']
error_msg = ''
req_ok = True
# Authentication
if len(self.test_authenticated_from) > 0 and not iq['from'] == self.test_authenticated_from:
# Invalid authentication
req_ok = False;
error_msg = "Access denied";
req_ok = False
error_msg = "Access denied"
# Nodes
process_nodes = [];
process_nodes = []
if len(iq['req']['nodes']) > 0:
for n in iq['req']['nodes']:
if not n['nodeId'] in self.nodes:
req_ok = False;
error_msg = "Invalid nodeId " + n['nodeId'];
process_nodes = [n['nodeId'] for n in iq['req']['nodes']];
req_ok = False
error_msg = "Invalid nodeId " + n['nodeId']
process_nodes = [n['nodeId'] for n in iq['req']['nodes']]
else:
process_nodes = self.nodes.keys();
process_nodes = self.nodes.keys()
# Fields - if we just find one we are happy, otherwise we reject
process_fields = [];
process_fields = []
if len(iq['req']['fields']) > 0:
found = False
for f in iq['req']['fields']:
for node in self.nodes:
if self.nodes[node]["device"].has_field(f['name']):
found = True;
break;
found = True
break
if not found:
req_ok = False;
error_msg = "Invalid field " + f['name'];
process_fields = [f['name'] for n in iq['req']['fields']];
req_ok = False
error_msg = "Invalid field " + f['name']
process_fields = [f['name'] for n in iq['req']['fields']]
req_flags = iq['req']._get_flags();
req_flags = iq['req']._get_flags()
request_delay_sec = None
if 'when' in req_flags:
@ -283,7 +281,7 @@ class XEP_0323(BasePlugin):
try:
dt = datetime.datetime.strptime(req_flags['when'], "%Y-%m-%dT%H:%M:%S")
except ValueError:
req_ok = False;
req_ok = False
error_msg = "Invalid datetime in 'when' flag, please use ISO format (i.e. 2013-04-05T15:00:03)."
if not dt is None:
@ -292,51 +290,47 @@ class XEP_0323(BasePlugin):
dtdiff = dt - dtnow
request_delay_sec = dtdiff.seconds + dtdiff.days * 24 * 3600
if request_delay_sec <= 0:
req_ok = False;
error_msg = "Invalid datetime in 'when' flag, cannot set a time in the past. Current time: " + dtnow.isoformat();
req_ok = False
error_msg = "Invalid datetime in 'when' flag, cannot set a time in the past. Current time: " + dtnow.isoformat()
if req_ok:
session = self._new_session();
self.sessions[session] = {"from": iq['from'], "to": iq['to'], "seqnr": seqnr};
self.sessions[session]["commTimers"] = {};
self.sessions[session]["nodeDone"] = {};
session = self._new_session()
self.sessions[session] = {"from": iq['from'], "to": iq['to'], "seqnr": seqnr}
self.sessions[session]["commTimers"] = {}
self.sessions[session]["nodeDone"] = {}
#print("added session: " + str(self.sessions))
iq.reply();
iq['accepted']['seqnr'] = seqnr;
iq.reply()
iq['accepted']['seqnr'] = seqnr
if not request_delay_sec is None:
iq['accepted']['queued'] = "true"
iq.send(block=False);
iq.send(block=False)
self.sessions[session]["node_list"] = process_nodes;
self.sessions[session]["node_list"] = process_nodes
if not request_delay_sec is None:
# Delay request to requested time
timer = Timer(request_delay_sec, self._event_delayed_req, args=(session, process_fields, req_flags))
self.sessions[session]["commTimers"]["delaytimer"] = timer;
timer.start();
self.sessions[session]["commTimers"]["delaytimer"] = timer
timer.start()
return
if self.threaded:
#print("starting thread")
tr_req = Thread(target=self._threaded_node_request, args=(session, process_fields, req_flags))
tr_req.start()
#print("started thread")
else:
self._threaded_node_request(session, process_fields, req_flags);
self._threaded_node_request(session, process_fields, req_flags)
else:
iq.reply();
iq['type'] = 'error';
iq['rejected']['seqnr'] = seqnr;
iq['rejected']['error'] = error_msg;
iq.send(block=False);
iq.reply()
iq['type'] = 'error'
iq['rejected']['seqnr'] = seqnr
iq['rejected']['error'] = error_msg
iq.send(block=False)
def _threaded_node_request(self, session, process_fields, flags):
"""
"""
Helper function to handle the device readouts in a separate thread.
Arguments:
session -- The request session id
process_fields -- The fields to request from the devices
@ -344,41 +338,39 @@ class XEP_0323(BasePlugin):
Formatted as a dictionary like { "flag name": "flag value" ... }
"""
for node in self.sessions[session]["node_list"]:
self.sessions[session]["nodeDone"][node] = False;
self.sessions[session]["nodeDone"][node] = False
for node in self.sessions[session]["node_list"]:
timer = TimerReset(self.nodes[node]['commTimeout'], self._event_comm_timeout, args=(session, node));
self.sessions[session]["commTimers"][node] = timer;
#print("Starting timer " + str(timer) + ", timeout: " + str(self.nodes[node]['commTimeout']))
timer.start();
self.nodes[node]['device'].request_fields(process_fields, flags=flags, session=session, callback=self._device_field_request_callback);
timer = TimerReset(self.nodes[node]['commTimeout'], self._event_comm_timeout, args=(session, node))
self.sessions[session]["commTimers"][node] = timer
timer.start()
self.nodes[node]['device'].request_fields(process_fields, flags=flags, session=session, callback=self._device_field_request_callback)
def _event_comm_timeout(self, session, nodeId):
"""
"""
Triggered if any of the readout operations timeout.
Sends a failure message back to the client, stops communicating
with the failing device.
Arguments:
session -- The request session id
nodeId -- The id of the device which timed out
"""
msg = self.xmpp.Message();
msg['from'] = self.sessions[session]['to'];
msg['to'] = self.sessions[session]['from'];
msg['failure']['seqnr'] = self.sessions[session]['seqnr'];
msg['failure']['error']['text'] = "Timeout";
msg['failure']['error']['nodeId'] = nodeId;
msg['failure']['error']['timestamp'] = datetime.datetime.now().replace(microsecond=0).isoformat();
msg = self.xmpp.Message()
msg['from'] = self.sessions[session]['to']
msg['to'] = self.sessions[session]['from']
msg['failure']['seqnr'] = self.sessions[session]['seqnr']
msg['failure']['error']['text'] = "Timeout"
msg['failure']['error']['nodeId'] = nodeId
msg['failure']['error']['timestamp'] = datetime.datetime.now().replace(microsecond=0).isoformat()
# Drop communication with this device and check if we are done
self.sessions[session]["nodeDone"][nodeId] = True;
self.sessions[session]["nodeDone"][nodeId] = True
if (self._all_nodes_done(session)):
msg['failure']['done'] = 'true';
msg.send();
msg['failure']['done'] = 'true'
msg.send()
# The session is complete, delete it
#print("del session " + session + " due to timeout")
del self.sessions[session];
del self.sessions[session]
def _event_delayed_req(self, session, process_fields, req_flags):
"""
@ -390,47 +382,47 @@ class XEP_0323(BasePlugin):
flags -- [optional] flags to pass to the devices, e.g. momentary
Formatted as a dictionary like { "flag name": "flag value" ... }
"""
msg = self.xmpp.Message();
msg['from'] = self.sessions[session]['to'];
msg['to'] = self.sessions[session]['from'];
msg['started']['seqnr'] = self.sessions[session]['seqnr'];
msg.send();
msg = self.xmpp.Message()
msg['from'] = self.sessions[session]['to']
msg['to'] = self.sessions[session]['from']
msg['started']['seqnr'] = self.sessions[session]['seqnr']
msg.send()
if self.threaded:
tr_req = Thread(target=self._threaded_node_request, args=(session, process_fields, req_flags))
tr_req.start()
else:
self._threaded_node_request(session, process_fields, req_flags);
self._threaded_node_request(session, process_fields, req_flags)
def _all_nodes_done(self, session):
"""
Checks wheter all devices are done replying to the readout.
"""
Checks whether all devices are done replying to the readout.
Arguments:
session -- The request session id
"""
for n in self.sessions[session]["nodeDone"]:
if not self.sessions[session]["nodeDone"][n]:
return False;
return True;
return False
return True
def _device_field_request_callback(self, session, nodeId, result, timestamp_block, error_msg=None):
"""
"""
Callback function called by the devices when they have any additional data.
Composes a message with the data and sends it back to the client, and resets
Composes a message with the data and sends it back to the client, and resets
the timeout timer for the device.
Arguments:
session -- The request session id
nodeId -- The device id which initiated the callback
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
"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:
@ -442,109 +434,107 @@ class XEP_0323(BasePlugin):
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 not session in self.sessions:
# This can happend if a session was deleted, like in a cancellation. Just drop the data.
# This can happen if a session was deleted, like in a cancellation. Just drop the data.
return
if result == "error":
self.sessions[session]["commTimers"][nodeId].cancel();
self.sessions[session]["commTimers"][nodeId].cancel()
msg = self.xmpp.Message();
msg['from'] = self.sessions[session]['to'];
msg['to'] = self.sessions[session]['from'];
msg['failure']['seqnr'] = self.sessions[session]['seqnr'];
msg['failure']['error']['text'] = error_msg;
msg['failure']['error']['nodeId'] = nodeId;
msg['failure']['error']['timestamp'] = datetime.datetime.now().replace(microsecond=0).isoformat();
msg = self.xmpp.Message()
msg['from'] = self.sessions[session]['to']
msg['to'] = self.sessions[session]['from']
msg['failure']['seqnr'] = self.sessions[session]['seqnr']
msg['failure']['error']['text'] = error_msg
msg['failure']['error']['nodeId'] = nodeId
msg['failure']['error']['timestamp'] = datetime.datetime.now().replace(microsecond=0).isoformat()
# Drop communication with this device and check if we are done
self.sessions[session]["nodeDone"][nodeId] = True;
self.sessions[session]["nodeDone"][nodeId] = True
if (self._all_nodes_done(session)):
msg['failure']['done'] = 'true';
msg['failure']['done'] = 'true'
# The session is complete, delete it
# print("del session " + session + " due to error")
del self.sessions[session];
msg.send();
del self.sessions[session]
msg.send()
else:
msg = self.xmpp.Message();
msg['from'] = self.sessions[session]['to'];
msg['to'] = self.sessions[session]['from'];
msg['fields']['seqnr'] = self.sessions[session]['seqnr'];
msg = self.xmpp.Message()
msg['from'] = self.sessions[session]['to']
msg['to'] = self.sessions[session]['from']
msg['fields']['seqnr'] = self.sessions[session]['seqnr']
if timestamp_block is not None and len(timestamp_block) > 0:
node = msg['fields'].add_node(nodeId);
ts = node.add_timestamp(timestamp_block["timestamp"]);
node = msg['fields'].add_node(nodeId)
ts = node.add_timestamp(timestamp_block["timestamp"])
for f in timestamp_block["fields"]:
data = ts.add_data( typename=f['type'],
name=f['name'],
value=f['value'],
unit=f['unit'],
dataType=f['dataType'],
flags=f['flags']);
data = ts.add_data( typename=f['type'],
name=f['name'],
value=f['value'],
unit=f['unit'],
dataType=f['dataType'],
flags=f['flags'])
if result == "done":
self.sessions[session]["commTimers"][nodeId].cancel();
self.sessions[session]["nodeDone"][nodeId] = True;
msg['fields']['done'] = 'true';
self.sessions[session]["commTimers"][nodeId].cancel()
self.sessions[session]["nodeDone"][nodeId] = True
if (self._all_nodes_done(session)):
# The session is complete, delete it
# print("del session " + session + " due to complete")
del self.sessions[session];
del self.sessions[session]
msg['fields']['done'] = 'true'
else:
# Restart comm timer
self.sessions[session]["commTimers"][nodeId].reset();
self.sessions[session]["commTimers"][nodeId].reset()
msg.send();
msg.send()
def _handle_event_cancel(self, iq):
""" Received Iq with cancel - this is a cancel request.
""" Received Iq with cancel - this is a cancel request.
Delete the session and confirm. """
seqnr = iq['cancel']['seqnr'];
seqnr = iq['cancel']['seqnr']
# Find the session
for s in self.sessions:
if self.sessions[s]['from'] == iq['from'] and self.sessions[s]['to'] == iq['to'] and self.sessions[s]['seqnr'] == seqnr:
# found it. Cancel all timers
for n in self.sessions[s]["commTimers"]:
self.sessions[s]["commTimers"][n].cancel();
self.sessions[s]["commTimers"][n].cancel()
# Confirm
iq.reply();
iq['type'] = 'result';
iq['cancelled']['seqnr'] = seqnr;
iq.send(block=False);
iq.reply()
iq['type'] = 'result'
iq['cancelled']['seqnr'] = seqnr
iq.send(block=False)
# Delete session
del self.sessions[s]
return
# Could not find session, send reject
iq.reply();
iq['type'] = 'error';
iq['rejected']['seqnr'] = seqnr;
iq['rejected']['error'] = "Cancel request received, no matching request is active.";
iq.send(block=False);
iq.reply()
iq['type'] = 'error'
iq['rejected']['seqnr'] = seqnr
iq['rejected']['error'] = "Cancel request received, no matching request is active."
iq.send(block=False)
# =================================================================
# =================================================================
# Client side (data retriever) API
def request_data(self, from_jid, to_jid, callback, nodeIds=None, fields=None, flags=None):
"""
Called on the client side to initiade a data readout.
"""
Called on the client side to initiate a data readout.
Composes a message with the request and sends it to the device(s).
Does not block, the callback will be called when data is available.
Arguments:
from_jid -- The jid of the requester
to_jid -- The jid of the device(s)
callback -- The callback function to call when data is availble.
callback -- The callback function to call when data is available.
The callback function must support the following arguments:
from_jid -- The jid of the responding device(s)
@ -565,7 +555,7 @@ class XEP_0323(BasePlugin):
The timestamp of data in this callback. One callback will only
contain data from one timestamp.
fields -- [optional] Mandatory when result == "fields".
List of field dictionaries representing the readout data.
List of field dictionaries representing the readout data.
Dictionary format:
{
typename: The field type (numeric, boolean, dateTime, timeSpan, string, enum)
@ -575,11 +565,11 @@ class XEP_0323(BasePlugin):
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] Mandatory when result == "rejected" or "failure".
Details about why the request is rejected or failed.
"rejected" means that the request is stopped, but note that the
Details about why the request is rejected or failed.
"rejected" means that the request is stopped, but note that the
request will continue even after a "failure". "failure" only means
that communication was stopped to that specific device, other
device(s) (if any) will continue their readout.
@ -593,131 +583,130 @@ class XEP_0323(BasePlugin):
session -- Session identifier. Client can use this as a reference to cancel
the request.
"""
iq = self.xmpp.Iq();
iq['from'] = from_jid;
iq['to'] = to_jid;
iq['type'] = "get";
seqnr = self._get_new_seqnr();
iq['id'] = seqnr;
iq['req']['seqnr'] = seqnr;
iq = self.xmpp.Iq()
iq['from'] = from_jid
iq['to'] = to_jid
iq['type'] = "get"
seqnr = self._get_new_seqnr()
iq['id'] = seqnr
iq['req']['seqnr'] = seqnr
if nodeIds is not None:
for nodeId in nodeIds:
iq['req'].add_node(nodeId);
iq['req'].add_node(nodeId)
if fields is not None:
for field in fields:
iq['req'].add_field(field);
iq['req'].add_field(field)
iq['req']._set_flags(flags);
iq['req']._set_flags(flags)
self.sessions[seqnr] = {"from": iq['from'], "to": iq['to'], "seqnr": seqnr, "callback": callback};
iq.send(block=False);
self.sessions[seqnr] = {"from": iq['from'], "to": iq['to'], "seqnr": seqnr, "callback": callback}
iq.send(block=False)
return seqnr;
return seqnr
def cancel_request(self, session):
"""
"""
Called on the client side to cancel a request for data readout.
Composes a message with the cancellation and sends it to the device(s).
Does not block, the callback will be called when cancellation is
Does not block, the callback will be called when cancellation is
confirmed.
Arguments:
session -- The session id of the request to cancel
"""
seqnr = session
iq = self.xmpp.Iq();
iq = self.xmpp.Iq()
iq['from'] = self.sessions[seqnr]['from']
iq['to'] = self.sessions[seqnr]['to'];
iq['type'] = "get";
iq['id'] = seqnr;
iq['cancel']['seqnr'] = seqnr;
iq.send(block=False);
iq['to'] = self.sessions[seqnr]['to']
iq['type'] = "get"
iq['id'] = seqnr
iq['cancel']['seqnr'] = seqnr
iq.send(block=False)
def _get_new_seqnr(self):
""" Returns a unique sequence number (unique across threads) """
self.seqnr_lock.acquire();
self.last_seqnr = self.last_seqnr + 1;
self.seqnr_lock.release();
return str(self.last_seqnr);
self.seqnr_lock.acquire()
self.last_seqnr += 1
self.seqnr_lock.release()
return str(self.last_seqnr)
def _handle_event_accepted(self, iq):
""" Received Iq with accepted - request was accepted """
seqnr = iq['accepted']['seqnr'];
seqnr = iq['accepted']['seqnr']
result = "accepted"
if iq['accepted']['queued'] == 'true':
result = "queued"
callback = self.sessions[seqnr]["callback"];
callback(from_jid=iq['from'], result=result);
callback = self.sessions[seqnr]["callback"]
callback(from_jid=iq['from'], result=result)
def _handle_event_rejected(self, iq):
""" Received Iq with rejected - this is a reject.
""" Received Iq with rejected - this is a reject.
Delete the session. """
seqnr = iq['rejected']['seqnr'];
callback = self.sessions[seqnr]["callback"];
callback(from_jid=iq['from'], result="rejected", error_msg=iq['rejected']['error']);
seqnr = iq['rejected']['seqnr']
callback = self.sessions[seqnr]["callback"]
callback(from_jid=iq['from'], result="rejected", error_msg=iq['rejected']['error'])
# Session terminated
del self.sessions[seqnr];
del self.sessions[seqnr]
def _handle_event_cancelled(self, iq):
"""
Received Iq with cancelled - this is a cancel confirm.
Delete the session.
"""
#print("Got cancelled")
seqnr = iq['cancelled']['seqnr'];
callback = self.sessions[seqnr]["callback"];
callback(from_jid=iq['from'], result="cancelled");
Received Iq with cancelled - this is a cancel confirm.
Delete the session.
"""
seqnr = iq['cancelled']['seqnr']
callback = self.sessions[seqnr]["callback"]
callback(from_jid=iq['from'], result="cancelled")
# Session cancelled
del self.sessions[seqnr];
del self.sessions[seqnr]
def _handle_event_fields(self, msg):
"""
Received Msg with fields - this is a data reponse to a request.
"""
Received Msg with fields - this is a data response to a request.
If this is the last data block, issue a "done" callback.
"""
seqnr = msg['fields']['seqnr'];
callback = self.sessions[seqnr]["callback"];
seqnr = msg['fields']['seqnr']
callback = self.sessions[seqnr]["callback"]
for node in msg['fields']['nodes']:
for ts in node['timestamps']:
fields = [];
fields = []
for d in ts['datas']:
field_block = {};
field_block["name"] = d['name'];
field_block["typename"] = d._get_typename();
field_block["value"] = d['value'];
field_block = {}
field_block["name"] = d['name']
field_block["typename"] = d._get_typename()
field_block["value"] = d['value']
if not d['unit'] == "": field_block["unit"] = d['unit'];
if not d['dataType'] == "": field_block["dataType"] = d['dataType'];
flags = d._get_flags();
flags = d._get_flags()
if not len(flags) == 0:
field_block["flags"] = flags;
fields.append(field_block);
field_block["flags"] = flags
fields.append(field_block)
callback(from_jid=msg['from'], result="fields", nodeId=node['nodeId'], timestamp=ts['value'], fields=fields)
callback(from_jid=msg['from'], result="fields", nodeId=node['nodeId'], timestamp=ts['value'], fields=fields);
if msg['fields']['done'] == "true":
callback(from_jid=msg['from'], result="done");
callback(from_jid=msg['from'], result="done")
# Session done
del self.sessions[seqnr];
del self.sessions[seqnr]
def _handle_event_failure(self, msg):
"""
Received Msg with failure - our request failed
Delete the session.
"""
seqnr = msg['failure']['seqnr'];
callback = self.sessions[seqnr]["callback"];
callback(from_jid=msg['from'], result="failure", nodeId=msg['failure']['error']['nodeId'], timestamp=msg['failure']['error']['timestamp'], error_msg=msg['failure']['error']['text']);
Received Msg with failure - our request failed
Delete the session.
"""
seqnr = msg['failure']['seqnr']
callback = self.sessions[seqnr]["callback"]
callback(from_jid=msg['from'], result="failure", nodeId=msg['failure']['error']['nodeId'], timestamp=msg['failure']['error']['timestamp'], error_msg=msg['failure']['error']['text'])
# Session failed
del self.sessions[seqnr];
del self.sessions[seqnr]
def _handle_event_started(self, msg):
"""
Received Msg with started - our request was queued and is now started.
"""
seqnr = msg['started']['seqnr'];
callback = self.sessions[seqnr]["callback"];
callback(from_jid=msg['from'], result="started");
Received Msg with started - our request was queued and is now started.
"""
seqnr = msg['started']['seqnr']
callback = self.sessions[seqnr]["callback"]
callback(from_jid=msg['from'], result="started")

View File

@ -20,14 +20,14 @@ class Sensordata(ElementBase):
interfaces = set(tuple())
class FieldTypes():
"""
"""
All field types are optional booleans that default to False
"""
field_types = set([ 'momentary','peak','status','computed','identity','historicalSecond','historicalMinute','historicalHour', \
'historicalDay','historicalWeek','historicalMonth','historicalQuarter','historicalYear','historicalOther'])
class FieldStatus():
"""
"""
All field statuses are optional booleans that default to False
"""
field_status = set([ 'missing','automaticEstimate','manualEstimate','manualReadout','automaticReadout','timeOffset','warning','error', \
@ -38,12 +38,12 @@ class Request(ElementBase):
name = 'req'
plugin_attrib = name
interfaces = set(['seqnr','nodes','fields','serviceToken','deviceToken','userToken','from','to','when','historical','all'])
interfaces.update(FieldTypes.field_types);
_flags = set(['serviceToken','deviceToken','userToken','from','to','when','historical','all']);
_flags.update(FieldTypes.field_types);
interfaces.update(FieldTypes.field_types)
_flags = set(['serviceToken','deviceToken','userToken','from','to','when','historical','all'])
_flags.update(FieldTypes.field_types)
def __init__(self, xml=None, parent=None):
ElementBase.__init__(self, xml, parent);
ElementBase.__init__(self, xml, parent)
self._nodes = set()
self._fields = set()
@ -64,27 +64,27 @@ class Request(ElementBase):
def _get_flags(self):
"""
Helper function for getting of flags. Returns all flags in
dictionary format: { "flag name": "flag value" ... }
Helper function for getting of flags. Returns all flags in
dictionary format: { "flag name": "flag value" ... }
"""
flags = {};
flags = {}
for f in self._flags:
if not self[f] == "":
flags[f] = self[f];
return flags;
flags[f] = self[f]
return flags
def _set_flags(self, flags):
"""
Helper function for setting of flags.
Helper function for setting of flags.
Arguments:
flags -- Flags in dictionary format: { "flag name": "flag value" ... }
flags -- Flags in dictionary format: { "flag name": "flag value" ... }
"""
for f in self._flags:
if flags is not None and f in flags:
self[f] = flags[f];
self[f] = flags[f]
else:
self[f] = None;
self[f] = None
def add_node(self, nodeId, sourceId=None, cacheType=None):
"""
@ -94,7 +94,7 @@ class Request(ElementBase):
Arguments:
nodeId -- The ID for the node.
sourceId -- [optional] identifying the data source controlling the device
cacheType -- [optional] narrowing down the search to a specific kind of node
cacheType -- [optional] narrowing down the search to a specific kind of node
"""
if nodeId not in self._nodes:
self._nodes.add((nodeId))
@ -269,7 +269,7 @@ class Error(ElementBase):
:param value: string
"""
self.xml.text = value;
self.xml.text = value
return self
def del_text(self):
@ -292,7 +292,7 @@ class Fields(ElementBase):
interfaces = set(['seqnr','done','nodes'])
def __init__(self, xml=None, parent=None):
ElementBase.__init__(self, xml, parent);
ElementBase.__init__(self, xml, parent)
self._nodes = set()
def setup(self, xml=None):
@ -318,7 +318,7 @@ class Fields(ElementBase):
Arguments:
nodeId -- The ID for the node.
sourceId -- [optional] identifying the data source controlling the device
cacheType -- [optional] narrowing down the search to a specific kind of node
cacheType -- [optional] narrowing down the search to a specific kind of node
"""
if nodeId not in self._nodes:
self._nodes.add((nodeId))
@ -392,7 +392,7 @@ class FieldsNode(ElementBase):
interfaces = set(['nodeId','sourceId','cacheType','timestamps'])
def __init__(self, xml=None, parent=None):
ElementBase.__init__(self, xml, parent);
ElementBase.__init__(self, xml, parent)
self._timestamps = set()
def setup(self, xml=None):
@ -411,7 +411,7 @@ class FieldsNode(ElementBase):
def add_timestamp(self, timestamp, substanzas=None):
"""
Add a new timestamp element.
Add a new timestamp element.
Arguments:
timestamp -- The timestamp in ISO format.
@ -423,7 +423,7 @@ class FieldsNode(ElementBase):
ts = Timestamp(parent=self)
ts['value'] = timestamp
if not substanzas is None:
ts.set_datas(substanzas);
ts.set_datas(substanzas)
#print("add_timestamp with substanzas: " + str(substanzas))
self.iterables.append(ts)
#print(str(id(self)) + " added_timestamp: " + str(id(ts)))
@ -485,7 +485,7 @@ class FieldsNode(ElementBase):
self.iterables.remove(timestamp)
class Field(ElementBase):
"""
"""
Field element in response Timestamp. This is a base class,
all instances of fields added to Timestamp must be of types:
DataNumeric
@ -494,17 +494,17 @@ class Field(ElementBase):
DataDateTime
DataTimeSpan
DataEnum
"""
"""
namespace = 'urn:xmpp:iot:sensordata'
name = 'field'
plugin_attrib = name
interfaces = set(['name','module','stringIds']);
interfaces.update(FieldTypes.field_types);
interfaces.update(FieldStatus.field_status);
interfaces = set(['name','module','stringIds'])
interfaces.update(FieldTypes.field_types)
interfaces.update(FieldStatus.field_status)
_flags = set();
_flags.update(FieldTypes.field_types);
_flags.update(FieldStatus.field_status);
_flags = set()
_flags.update(FieldTypes.field_types)
_flags.update(FieldStatus.field_status)
def set_stringIds(self, value):
"""Verifies stringIds according to regexp from specification XMPP-0323.
@ -514,7 +514,7 @@ class Field(ElementBase):
pattern = re.compile("^\d+([|]\w+([.]\w+)*([|][^,]*)?)?(,\d+([|]\w+([.]\w+)*([|][^,]*)?)?)*$")
if pattern.match(value) is not None:
self.xml.stringIds = value;
self.xml.stringIds = value
else:
# Bad content, add nothing
pass
@ -523,30 +523,30 @@ class Field(ElementBase):
def _get_flags(self):
"""
Helper function for getting of flags. Returns all flags in
dictionary format: { "flag name": "flag value" ... }
Helper function for getting of flags. Returns all flags in
dictionary format: { "flag name": "flag value" ... }
"""
flags = {};
flags = {}
for f in self._flags:
if not self[f] == "":
flags[f] = self[f];
return flags;
flags[f] = self[f]
return flags
def _set_flags(self, flags):
"""
Helper function for setting of flags.
Helper function for setting of flags.
Arguments:
flags -- Flags in dictionary format: { "flag name": "flag value" ... }
flags -- Flags in dictionary format: { "flag name": "flag value" ... }
"""
for f in self._flags:
if flags is not None and f in flags:
self[f] = flags[f];
self[f] = flags[f]
else:
self[f] = None;
self[f] = None
def _get_typename(self):
return "invalid type, use subclasses!";
return "invalid type, use subclasses!"
class Timestamp(ElementBase):
@ -557,7 +557,7 @@ class Timestamp(ElementBase):
interfaces = set(['value','datas'])
def __init__(self, xml=None, parent=None):
ElementBase.__init__(self, xml, parent);
ElementBase.__init__(self, xml, parent)
self._datas = set()
def setup(self, xml=None):
@ -576,7 +576,7 @@ class Timestamp(ElementBase):
def add_data(self, typename, name, value, module=None, stringIds=None, unit=None, dataType=None, flags=None):
"""
Add a new data element.
Add a new data element.
Arguments:
typename -- The type of data element (numeric, string, boolean, dateTime, timeSpan or enum)
@ -587,29 +587,29 @@ class Timestamp(ElementBase):
dataType -- [optional] The dataType. Only applicable for type enum
"""
if name not in self._datas:
dataObj = None;
dataObj = None
if typename == "numeric":
dataObj = DataNumeric(parent=self);
dataObj['unit'] = unit;
dataObj = DataNumeric(parent=self)
dataObj['unit'] = unit
elif typename == "string":
dataObj = DataString(parent=self);
dataObj = DataString(parent=self)
elif typename == "boolean":
dataObj = DataBoolean(parent=self);
dataObj = DataBoolean(parent=self)
elif typename == "dateTime":
dataObj = DataDateTime(parent=self);
dataObj = DataDateTime(parent=self)
elif typename == "timeSpan":
dataObj = DataTimeSpan(parent=self);
dataObj = DataTimeSpan(parent=self)
elif typename == "enum":
dataObj = DataEnum(parent=self);
dataObj['dataType'] = dataType;
dataObj = DataEnum(parent=self)
dataObj['dataType'] = dataType
dataObj['name'] = name;
dataObj['value'] = value;
dataObj['module'] = module;
dataObj['stringIds'] = stringIds;
dataObj['name'] = name
dataObj['value'] = value
dataObj['module'] = module
dataObj['stringIds'] = stringIds
if flags is not None:
dataObj._set_flags(flags);
dataObj._set_flags(flags)
self._datas.add(name)
self.iterables.append(dataObj)
@ -661,87 +661,87 @@ class Timestamp(ElementBase):
self.iterables.remove(data)
class DataNumeric(Field):
"""
Field data of type numeric.
Note that the value is expressed as a string.
"""
Field data of type numeric.
Note that the value is expressed as a string.
"""
namespace = 'urn:xmpp:iot:sensordata'
name = 'numeric'
plugin_attrib = name
interfaces = set(['value', 'unit']);
interfaces.update(Field.interfaces);
interfaces = set(['value', 'unit'])
interfaces.update(Field.interfaces)
def _get_typename(self):
return "numeric"
return "numeric"
class DataString(Field):
"""
Field data of type string
"""
Field data of type string
"""
namespace = 'urn:xmpp:iot:sensordata'
name = 'string'
plugin_attrib = name
interfaces = set(['value']);
interfaces.update(Field.interfaces);
interfaces = set(['value'])
interfaces.update(Field.interfaces)
def _get_typename(self):
return "string"
return "string"
class DataBoolean(Field):
"""
"""
Field data of type boolean.
Note that the value is expressed as a string.
Note that the value is expressed as a string.
"""
namespace = 'urn:xmpp:iot:sensordata'
name = 'boolean'
plugin_attrib = name
interfaces = set(['value']);
interfaces.update(Field.interfaces);
interfaces = set(['value'])
interfaces.update(Field.interfaces)
def _get_typename(self):
return "boolean"
return "boolean"
class DataDateTime(Field):
"""
"""
Field data of type dateTime.
Note that the value is expressed as a string.
Note that the value is expressed as a string.
"""
namespace = 'urn:xmpp:iot:sensordata'
name = 'dateTime'
plugin_attrib = name
interfaces = set(['value']);
interfaces.update(Field.interfaces);
interfaces = set(['value'])
interfaces.update(Field.interfaces)
def _get_typename(self):
return "dateTime"
return "dateTime"
class DataTimeSpan(Field):
"""
"""
Field data of type timeSpan.
Note that the value is expressed as a string.
Note that the value is expressed as a string.
"""
namespace = 'urn:xmpp:iot:sensordata'
name = 'timeSpan'
plugin_attrib = name
interfaces = set(['value']);
interfaces.update(Field.interfaces);
interfaces = set(['value'])
interfaces.update(Field.interfaces)
def _get_typename(self):
return "timeSpan"
return "timeSpan"
class DataEnum(Field):
"""
"""
Field data of type enum.
Note that the value is expressed as a string.
Note that the value is expressed as a string.
"""
namespace = 'urn:xmpp:iot:sensordata'
name = 'enum'
plugin_attrib = name
interfaces = set(['value', 'dataType']);
interfaces.update(Field.interfaces);
interfaces = set(['value', 'dataType'])
interfaces.update(Field.interfaces)
def _get_typename(self):
return "enum"
return "enum"
class Done(ElementBase):
""" Done element used to signal that all data has been transferred """

View File

@ -23,7 +23,12 @@ class _TimerReset(Thread):
t.cancel() # stop the timer's action if it's still waiting
"""
def __init__(self, interval, function, args=[], kwargs={}):
def __init__(self, interval, function, args=None, kwargs=None):
if not kwargs:
kwargs = {}
if not args:
args = []
Thread.__init__(self)
self.interval = interval
self.function = function

View File

@ -12,7 +12,6 @@ import logging
import time
from threading import Thread, Timer, Lock
from sleekxmpp.xmlstream import JID
from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.xmlstream.matcher import StanzaPath
from sleekxmpp.plugins.base import BasePlugin
@ -26,16 +25,16 @@ log = logging.getLogger(__name__)
class XEP_0325(BasePlugin):
"""
XEP-0325: IoT Control
XEP-0325: IoT Control
Actuators are devices in sensor networks that can be controlled through
the network and act with the outside world. In sensor networks and
Internet of Things applications, actuators make it possible to automate
real-world processes.
This plugin implements a mechanism whereby actuators can be controlled
in XMPP-based sensor networks, making it possible to integrate sensors
and actuators of different brands, makes and models into larger
Actuators are devices in sensor networks that can be controlled through
the network and act with the outside world. In sensor networks and
Internet of Things applications, actuators make it possible to automate
real-world processes.
This plugin implements a mechanism whereby actuators can be controlled
in XMPP-based sensor networks, making it possible to integrate sensors
and actuators of different brands, makes and models into larger
Internet of Things applications.
Also see <http://xmpp.org/extensions/xep-0325.html>
@ -52,9 +51,9 @@ class XEP_0325(BasePlugin):
Client side
-----------
Control Event:SetResponse -- Received a response to a
Control Event:SetResponse -- Received a response to a
control request, type result
Control Event:SetResponseError -- Received a response to a
Control Event:SetResponseError -- Received a response to a
control request, type error
Attributes:
@ -65,7 +64,7 @@ class XEP_0325(BasePlugin):
relevant to a request's session. This dictionary is used
both by the client and sensor side. On client side, seqnr
is used as key, while on sensor side, a session_id is used
as key. This ensures that the two will not collide, so
as key. This ensures that the two will not collide, so
one instance can be both client and sensor.
Sensor side
-----------
@ -85,15 +84,15 @@ class XEP_0325(BasePlugin):
Sensor side
-----------
register_node -- Register a sensor as available from this XMPP
register_node -- Register a sensor as available from this XMPP
instance.
Client side
-----------
set_request -- Initiates a control request to modify data in
set_request -- Initiates a control request to modify data in
sensor(s). Non-blocking, a callback function will
be called when the sensor has responded.
set_command -- Initiates a control command to modify data in
set_command -- Initiates a control command to modify data in
sensor(s). Non-blocking. The sensor(s) will not
respond regardless of the result of the command,
so no callback is made.
@ -102,7 +101,7 @@ class XEP_0325(BasePlugin):
name = 'xep_0325'
description = 'XEP-0325 Internet of Things - Control'
dependencies = set(['xep_0030'])
dependencies = set(['xep_0030'])
stanza = stanza
@ -135,11 +134,11 @@ class XEP_0325(BasePlugin):
self._handle_set_response))
# Server side dicts
self.nodes = {};
self.sessions = {};
self.nodes = {}
self.sessions = {}
self.last_seqnr = 0;
self.seqnr_lock = Lock();
self.last_seqnr = 0
self.seqnr_lock = Lock()
## For testning only
self.test_authenticated_from = ""
@ -156,13 +155,13 @@ class XEP_0325(BasePlugin):
def plugin_end(self):
""" Stop the XEP-0325 plugin """
self.sessions.clear();
self.sessions.clear()
self.xmpp.remove_handler('Control Event:DirectSet')
self.xmpp.remove_handler('Control Event:SetReq')
self.xmpp.remove_handler('Control Event:SetResponse')
self.xmpp.remove_handler('Control Event:SetResponseError')
self.xmpp['xep_0030'].del_feature(feature=Control.namespace)
self.xmpp['xep_0030'].set_items(node=Control.namespace, items=tuple());
self.xmpp['xep_0030'].set_items(node=Control.namespace, items=tuple())
# =================================================================
@ -170,10 +169,10 @@ class XEP_0325(BasePlugin):
def register_node(self, nodeId, device, commTimeout, sourceId=None, cacheType=None):
"""
Register a sensor/device as available for control requests/commands
through this XMPP instance.
Register a sensor/device as available for control requests/commands
through this XMPP instance.
The device object may by any custom implementation to support
The device object may by any custom implementation to support
specific devices, but it must implement the functions:
has_control_field
set_control_fields
@ -185,30 +184,30 @@ class XEP_0325(BasePlugin):
commTimeout -- Time in seconds to wait between each callback from device during
a data readout. Float.
sourceId -- [optional] identifying the data source controlling the device
cacheType -- [optional] narrowing down the search to a specific kind of node
cacheType -- [optional] narrowing down the search to a specific kind of node
"""
self.nodes[nodeId] = {"device": device,
self.nodes[nodeId] = {"device": device,
"commTimeout": commTimeout,
"sourceId": sourceId,
"cacheType": cacheType};
"sourceId": sourceId,
"cacheType": cacheType}
def _set_authenticated(self, auth=''):
""" Internal testing function """
self.test_authenticated_from = auth;
self.test_authenticated_from = auth
def _get_new_seqnr(self):
""" Returns a unique sequence number (unique across threads) """
self.seqnr_lock.acquire();
self.last_seqnr = self.last_seqnr + 1;
self.seqnr_lock.release();
return str(self.last_seqnr);
self.seqnr_lock.acquire()
self.last_seqnr += 1
self.seqnr_lock.release()
return str(self.last_seqnr)
def _handle_set_req(self, iq):
"""
Event handler for reception of an Iq with set req - this is a
Event handler for reception of an Iq with set req - this is a
control request.
Verifies that
Verifies that
- all the requested nodes are available
(if no nodes are specified in the request, assume all nodes)
- all the control fields are available from all requested nodes
@ -216,80 +215,79 @@ class XEP_0325(BasePlugin):
If the request passes verification, the control request is passed
to the devices (in a separate thread).
If the verification fails, a setResponse with error indication
If the verification fails, a setResponse with error indication
is sent.
"""
error_msg = '';
req_ok = True;
missing_node = None;
missing_field = None;
error_msg = ''
req_ok = True
missing_node = None
missing_field = None
# Authentication
if len(self.test_authenticated_from) > 0 and not iq['from'] == self.test_authenticated_from:
# Invalid authentication
req_ok = False;
error_msg = "Access denied";
req_ok = False
error_msg = "Access denied"
# Nodes
process_nodes = [];
if len(iq['set']['nodes']) > 0:
for n in iq['set']['nodes']:
if not n['nodeId'] in self.nodes:
req_ok = False;
missing_node = n['nodeId'];
error_msg = "Invalid nodeId " + n['nodeId'];
process_nodes = [n['nodeId'] for n in iq['set']['nodes']];
req_ok = False
missing_node = n['nodeId']
error_msg = "Invalid nodeId " + n['nodeId']
process_nodes = [n['nodeId'] for n in iq['set']['nodes']]
else:
process_nodes = self.nodes.keys();
process_nodes = self.nodes.keys()
# Fields - for control we need to find all in all devices, otherwise we reject
process_fields = [];
process_fields = []
if len(iq['set']['datas']) > 0:
for f in iq['set']['datas']:
for node in self.nodes:
if not self.nodes[node]["device"].has_control_field(f['name'], f._get_typename()):
req_ok = False;
missing_field = f['name'];
error_msg = "Invalid field " + f['name'];
break;
process_fields = [(f['name'], f._get_typename(), f['value']) for f in iq['set']['datas']];
req_ok = False
missing_field = f['name']
error_msg = "Invalid field " + f['name']
break
process_fields = [(f['name'], f._get_typename(), f['value']) for f in iq['set']['datas']]
if req_ok:
session = self._new_session();
self.sessions[session] = {"from": iq['from'], "to": iq['to'], "seqnr": iq['id']};
self.sessions[session]["commTimers"] = {};
self.sessions[session]["nodeDone"] = {};
session = self._new_session()
self.sessions[session] = {"from": iq['from'], "to": iq['to'], "seqnr": iq['id']}
self.sessions[session]["commTimers"] = {}
self.sessions[session]["nodeDone"] = {}
# Flag that a reply is exected when we are done
self.sessions[session]["reply"] = True;
self.sessions[session]["reply"] = True
self.sessions[session]["node_list"] = process_nodes;
self.sessions[session]["node_list"] = process_nodes
if self.threaded:
#print("starting thread")
tr_req = Thread(target=self._threaded_node_request, args=(session, process_fields))
tr_req.start()
#print("started thread")
else:
self._threaded_node_request(session, process_fields);
self._threaded_node_request(session, process_fields)
else:
iq.reply();
iq['type'] = 'error';
iq['setResponse']['responseCode'] = "NotFound";
iq.reply()
iq['type'] = 'error'
iq['setResponse']['responseCode'] = "NotFound"
if missing_node is not None:
iq['setResponse'].add_node(missing_node);
iq['setResponse'].add_node(missing_node)
if missing_field is not None:
iq['setResponse'].add_data(missing_field);
iq['setResponse']['error']['var'] = "Output";
iq['setResponse']['error']['text'] = error_msg;
iq.send(block=False);
iq['setResponse'].add_data(missing_field)
iq['setResponse']['error']['var'] = "Output"
iq['setResponse']['error']['text'] = error_msg
iq.send(block=False)
def _handle_direct_set(self, msg):
"""
Event handler for reception of a Message with set command - this is a
Event handler for reception of a Message with set command - this is a
direct control command.
Verifies that
Verifies that
- all the requested nodes are available
(if no nodes are specified in the request, assume all nodes)
- all the control fields are available from all requested nodes
@ -299,73 +297,72 @@ class XEP_0325(BasePlugin):
to the devices (in a separate thread).
If the verification fails, do nothing.
"""
req_ok = True;
req_ok = True
# Nodes
process_nodes = [];
if len(msg['set']['nodes']) > 0:
for n in msg['set']['nodes']:
if not n['nodeId'] in self.nodes:
req_ok = False;
error_msg = "Invalid nodeId " + n['nodeId'];
process_nodes = [n['nodeId'] for n in msg['set']['nodes']];
req_ok = False
error_msg = "Invalid nodeId " + n['nodeId']
process_nodes = [n['nodeId'] for n in msg['set']['nodes']]
else:
process_nodes = self.nodes.keys();
process_nodes = self.nodes.keys()
# Fields - for control we need to find all in all devices, otherwise we reject
process_fields = [];
process_fields = []
if len(msg['set']['datas']) > 0:
for f in msg['set']['datas']:
for node in self.nodes:
if not self.nodes[node]["device"].has_control_field(f['name'], f._get_typename()):
req_ok = False;
missing_field = f['name'];
error_msg = "Invalid field " + f['name'];
break;
process_fields = [(f['name'], f._get_typename(), f['value']) for f in msg['set']['datas']];
req_ok = False
missing_field = f['name']
error_msg = "Invalid field " + f['name']
break
process_fields = [(f['name'], f._get_typename(), f['value']) for f in msg['set']['datas']]
if req_ok:
session = self._new_session();
self.sessions[session] = {"from": msg['from'], "to": msg['to']};
self.sessions[session]["commTimers"] = {};
self.sessions[session]["nodeDone"] = {};
self.sessions[session]["reply"] = False;
session = self._new_session()
self.sessions[session] = {"from": msg['from'], "to": msg['to']}
self.sessions[session]["commTimers"] = {}
self.sessions[session]["nodeDone"] = {}
self.sessions[session]["reply"] = False
self.sessions[session]["node_list"] = process_nodes;
self.sessions[session]["node_list"] = process_nodes
if self.threaded:
#print("starting thread")
tr_req = Thread(target=self._threaded_node_request, args=(session, process_fields))
tr_req.start()
#print("started thread")
else:
self._threaded_node_request(session, process_fields);
self._threaded_node_request(session, process_fields)
def _threaded_node_request(self, session, process_fields):
"""
"""
Helper function to handle the device control in a separate thread.
Arguments:
session -- The request session id
process_fields -- The fields to set in the devices. List of tuple format:
(name, datatype, value)
"""
for node in self.sessions[session]["node_list"]:
self.sessions[session]["nodeDone"][node] = False;
self.sessions[session]["nodeDone"][node] = False
for node in self.sessions[session]["node_list"]:
timer = Timer(self.nodes[node]['commTimeout'], self._event_comm_timeout, args=(session, node));
self.sessions[session]["commTimers"][node] = timer;
timer.start();
self.nodes[node]['device'].set_control_fields(process_fields, session=session, callback=self._device_set_command_callback);
timer = Timer(self.nodes[node]['commTimeout'], self._event_comm_timeout, args=(session, node))
self.sessions[session]["commTimers"][node] = timer
timer.start()
self.nodes[node]['device'].set_control_fields(process_fields, session=session, callback=self._device_set_command_callback)
def _event_comm_timeout(self, session, nodeId):
"""
"""
Triggered if any of the control operations timeout.
Stop communicating with the failing device.
If the control command was an Iq request, sends a failure
message back to the client.
If the control command was an Iq request, sends a failure
message back to the client.
Arguments:
session -- The request session id
nodeId -- The id of the device which timed out
@ -373,51 +370,51 @@ class XEP_0325(BasePlugin):
if self.sessions[session]["reply"]:
# Reply is exected when we are done
iq = self.xmpp.Iq();
iq['from'] = self.sessions[session]['to'];
iq['to'] = self.sessions[session]['from'];
iq['type'] = "error";
iq['id'] = self.sessions[session]['seqnr'];
iq['setResponse']['responseCode'] = "OtherError";
iq['setResponse'].add_node(nodeId);
iq['setResponse']['error']['var'] = "Output";
iq['setResponse']['error']['text'] = "Timeout.";
iq.send(block=False);
iq = self.xmpp.Iq()
iq['from'] = self.sessions[session]['to']
iq['to'] = self.sessions[session]['from']
iq['type'] = "error"
iq['id'] = self.sessions[session]['seqnr']
iq['setResponse']['responseCode'] = "OtherError"
iq['setResponse'].add_node(nodeId)
iq['setResponse']['error']['var'] = "Output"
iq['setResponse']['error']['text'] = "Timeout."
iq.send(block=False)
## TODO - should we send one timeout per node??
# Drop communication with this device and check if we are done
self.sessions[session]["nodeDone"][nodeId] = True;
self.sessions[session]["nodeDone"][nodeId] = True
if (self._all_nodes_done(session)):
# The session is complete, delete it
del self.sessions[session];
del self.sessions[session]
def _all_nodes_done(self, session):
"""
"""
Checks wheter all devices are done replying to the control command.
Arguments:
session -- The request session id
"""
for n in self.sessions[session]["nodeDone"]:
if not self.sessions[session]["nodeDone"][n]:
return False;
return True;
return False
return True
def _device_set_command_callback(self, session, nodeId, result, error_field=None, error_msg=None):
"""
Callback function called by the devices when the control command is
"""
Callback function called by the devices when the control command is
complete or failed.
If needed, composes a message with the result and sends it back to the
If needed, composes a message with the result and sends it back to the
client.
Arguments:
session -- The request session id
nodeId -- The device id which initiated the callback
result -- The current result status of the control command. Valid values are:
"error" - Set fields failed.
"ok" - All fields were set.
error_field -- [optional] Only applies when result == "error"
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.
@ -428,62 +425,62 @@ class XEP_0325(BasePlugin):
return
if result == "error":
self.sessions[session]["commTimers"][nodeId].cancel();
self.sessions[session]["commTimers"][nodeId].cancel()
if self.sessions[session]["reply"]:
# Reply is exected when we are done
iq = self.xmpp.Iq();
iq['from'] = self.sessions[session]['to'];
iq['to'] = self.sessions[session]['from'];
iq['type'] = "error";
iq['id'] = self.sessions[session]['seqnr'];
iq['setResponse']['responseCode'] = "OtherError";
iq['setResponse'].add_node(nodeId);
iq = self.xmpp.Iq()
iq['from'] = self.sessions[session]['to']
iq['to'] = self.sessions[session]['from']
iq['type'] = "error"
iq['id'] = self.sessions[session]['seqnr']
iq['setResponse']['responseCode'] = "OtherError"
iq['setResponse'].add_node(nodeId)
if error_field is not None:
iq['setResponse'].add_data(error_field);
iq['setResponse']['error']['var'] = error_field;
iq['setResponse']['error']['text'] = error_msg;
iq.send(block=False);
iq['setResponse'].add_data(error_field)
iq['setResponse']['error']['var'] = error_field
iq['setResponse']['error']['text'] = error_msg
iq.send(block=False)
# Drop communication with this device and check if we are done
self.sessions[session]["nodeDone"][nodeId] = True;
self.sessions[session]["nodeDone"][nodeId] = True
if (self._all_nodes_done(session)):
# The session is complete, delete it
del self.sessions[session];
del self.sessions[session]
else:
self.sessions[session]["commTimers"][nodeId].cancel();
self.sessions[session]["commTimers"][nodeId].cancel()
self.sessions[session]["nodeDone"][nodeId] = True;
self.sessions[session]["nodeDone"][nodeId] = True
if (self._all_nodes_done(session)):
if self.sessions[session]["reply"]:
# Reply is exected when we are done
iq = self.xmpp.Iq();
iq['from'] = self.sessions[session]['to'];
iq['to'] = self.sessions[session]['from'];
iq['type'] = "result";
iq['id'] = self.sessions[session]['seqnr'];
iq['setResponse']['responseCode'] = "OK";
iq.send(block=False);
iq = self.xmpp.Iq()
iq['from'] = self.sessions[session]['to']
iq['to'] = self.sessions[session]['from']
iq['type'] = "result"
iq['id'] = self.sessions[session]['seqnr']
iq['setResponse']['responseCode'] = "OK"
iq.send(block=False)
# The session is complete, delete it
del self.sessions[session];
del self.sessions[session]
# =================================================================
# Client side (data controller) API
def set_request(self, from_jid, to_jid, callback, fields, nodeIds=None):
"""
"""
Called on the client side to initiade a control request.
Composes a message with the request and sends it to the device(s).
Does not block, the callback will be called when the device(s)
Does not block, the callback will be called when the device(s)
has responded.
Arguments:
from_jid -- The jid of the requester
to_jid -- The jid of the device(s)
callback -- The callback function to call when data is availble.
callback -- The callback function to call when data is availble.
The callback function must support the following arguments:
from_jid -- The jid of the responding device(s)
@ -494,46 +491,46 @@ class XEP_0325(BasePlugin):
"Locked" - Field(s) is locked and cannot
be changed at the moment.
"NotImplemented" - Request feature not implemented.
"FormError" - Error while setting with
"FormError" - Error while setting with
a form (not implemented).
"OtherError" - Indicates other types of
errors, such as timeout.
"OtherError" - Indicates other types of
errors, such as timeout.
Details in the error_msg.
nodeId -- [optional] Only applicable when result == "error"
List of node Ids of failing device(s).
fields -- [optional] Only applicable when result == "error"
nodeId -- [optional] Only applicable when result == "error"
List of node Ids of failing device(s).
fields -- [optional] Only applicable when result == "error"
List of fields that failed.[optional] Mandatory when result == "rejected" or "failure".
error_msg -- Details about why the request failed.
error_msg -- Details about why the request failed.
fields -- Fields to set. List of tuple format: (name, typename, value).
nodeIds -- [optional] Limits the request to the node Ids in this list.
"""
iq = self.xmpp.Iq();
iq['from'] = from_jid;
iq['to'] = to_jid;
seqnr = self._get_new_seqnr();
iq['id'] = seqnr;
iq['type'] = "set";
iq = self.xmpp.Iq()
iq['from'] = from_jid
iq['to'] = to_jid
seqnr = self._get_new_seqnr()
iq['id'] = seqnr
iq['type'] = "set"
if nodeIds is not None:
for nodeId in nodeIds:
iq['set'].add_node(nodeId);
iq['set'].add_node(nodeId)
if fields is not None:
for name, typename, value in fields:
iq['set'].add_data(name=name, typename=typename, value=value);
iq['set'].add_data(name=name, typename=typename, value=value)
self.sessions[seqnr] = {"from": iq['from'], "to": iq['to'], "callback": callback};
iq.send(block=False);
self.sessions[seqnr] = {"from": iq['from'], "to": iq['to'], "callback": callback}
iq.send(block=False)
def set_command(self, from_jid, to_jid, fields, nodeIds=None):
"""
"""
Called on the client side to initiade a control command.
Composes a message with the set commandand sends it to the device(s).
Does not block. Device(s) will not respond, regardless of result.
Arguments:
from_jid -- The jid of the requester
to_jid -- The jid of the device(s)
@ -541,34 +538,32 @@ class XEP_0325(BasePlugin):
fields -- Fields to set. List of tuple format: (name, typename, value).
nodeIds -- [optional] Limits the request to the node Ids in this list.
"""
msg = self.xmpp.Message();
msg['from'] = from_jid;
msg['to'] = to_jid;
msg['type'] = "set";
msg = self.xmpp.Message()
msg['from'] = from_jid
msg['to'] = to_jid
msg['type'] = "set"
if nodeIds is not None:
for nodeId in nodeIds:
msg['set'].add_node(nodeId);
msg['set'].add_node(nodeId)
if fields is not None:
for name, typename, value in fields:
msg['set'].add_data(name, typename, value);
msg['set'].add_data(name, typename, value)
# We won't get any reply, so don't create a session
msg.send();
msg.send()
def _handle_set_response(self, iq):
""" Received response from device(s) """
#print("ooh")
seqnr = iq['id'];
from_jid = str(iq['from']);
result = iq['setResponse']['responseCode'];
nodeIds = [n['name'] for n in iq['setResponse']['nodes']];
fields = [f['name'] for f in iq['setResponse']['datas']];
error_msg = None;
seqnr = iq['id']
from_jid = str(iq['from'])
result = iq['setResponse']['responseCode']
nodeIds = [n['name'] for n in iq['setResponse']['nodes']]
fields = [f['name'] for f in iq['setResponse']['datas']]
error_msg = None
if not iq['setResponse'].find('error') is None and not iq['setResponse']['error']['text'] == "":
error_msg = iq['setResponse']['error']['text'];
error_msg = iq['setResponse']['error']['text']
callback = self.sessions[seqnr]["callback"];
callback(from_jid=from_jid, result=result, nodeIds=nodeIds, fields=fields, error_msg=error_msg);
callback = self.sessions[seqnr]["callback"]
callback(from_jid=from_jid, result=result, nodeIds=nodeIds, fields=fields, error_msg=error_msg)

View File

@ -13,16 +13,16 @@ import datetime
class Device(object):
"""
Example implementation of a device control object.
The device object may by any custom implementation to support
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 = {};
self.nodeId = nodeId
self.control_fields = {}
def has_control_field(self, field, typename):
"""
@ -30,12 +30,12 @@ class Device(object):
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;
return True
return False
def set_control_fields(self, fields, session, callback):
"""
@ -43,22 +43,22 @@ class Device(object):
sets the data and (if needed) and calls the callback.
Arguments:
fields -- List of control fields in tuple format:
fields -- List of control fields in tuple format:
(name, typename, value)
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:
session -- Session id, as supplied in the
session -- Session id, as supplied in the
request_fields call
nodeId -- Identifier for this device
result -- The current result status of the readout.
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
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.
@ -69,12 +69,12 @@ class Device(object):
for name, typename, value in fields:
if not self.has_control_field(name, typename):
self._send_control_reject(session, name, "NotFound", callback)
return False;
return False
for name, typename, value in fields:
self._set_field_value(name, value)
callback(session, result="ok", nodeId=self.nodeId);
callback(session, result="ok", nodeId=self.nodeId)
return True
def _send_control_reject(self, session, field, message, callback):
@ -82,12 +82,12 @@ class Device(object):
Sends a reject to the caller
Arguments:
session -- Session id, see definition in
session -- Session id, see definition in
set_control_fields function
callback -- Callback function, see definition in
callback -- Callback function, see definition in
set_control_fields function
"""
callback(session, result="error", nodeId=self.nodeId, error_field=field, error_msg=message);
callback(session, result="error", nodeId=self.nodeId, error_field=field, error_msg=message)
def _add_control_field(self, name, typename, value):
"""
@ -95,12 +95,12 @@ class Device(object):
Arguments:
name -- Name of the field
typename -- Type of the field, one of:
(boolean, color, string, date, dateTime,
typename -- Type of the field, one of:
(boolean, color, string, date, dateTime,
double, duration, int, long, time)
value -- Field value
"""
self.control_fields[name] = {"type": typename, "value": value};
self.control_fields[name] = {"type": typename, "value": value}
def _set_field_value(self, name, value):
"""
@ -111,7 +111,7 @@ class Device(object):
value -- New value for the field
"""
if name in self.control_fields:
self.control_fields[name]["value"] = value;
self.control_fields[name]["value"] = value
def _get_field_value(self, name):
"""
@ -121,5 +121,5 @@ class Device(object):
name -- Name of the field
"""
if name in self.control_fields:
return self.control_fields[name]["value"];
return None;
return self.control_fields[name]["value"]
return None

View File

@ -26,7 +26,7 @@ class ControlSet(ElementBase):
interfaces = set(['nodes','datas'])
def __init__(self, xml=None, parent=None):
ElementBase.__init__(self, xml, parent);
ElementBase.__init__(self, xml, parent)
self._nodes = set()
self._datas = set()
@ -53,7 +53,7 @@ class ControlSet(ElementBase):
Arguments:
nodeId -- The ID for the node.
sourceId -- [optional] identifying the data source controlling the device
cacheType -- [optional] narrowing down the search to a specific kind of node
cacheType -- [optional] narrowing down the search to a specific kind of node
"""
if nodeId not in self._nodes:
self._nodes.add((nodeId))
@ -117,40 +117,40 @@ class ControlSet(ElementBase):
def add_data(self, name, typename, value):
"""
Add a new data element.
Add a new data element.
Arguments:
name -- The name of the data element
typename -- The type of data element
(boolean, color, string, date, dateTime,
typename -- The type of data element
(boolean, color, string, date, dateTime,
double, duration, int, long, time)
value -- The value of the data element
"""
if name not in self._datas:
dataObj = None;
dataObj = None
if typename == "boolean":
dataObj = BooleanParameter(parent=self);
dataObj = BooleanParameter(parent=self)
elif typename == "color":
dataObj = ColorParameter(parent=self);
dataObj = ColorParameter(parent=self)
elif typename == "string":
dataObj = StringParameter(parent=self);
dataObj = StringParameter(parent=self)
elif typename == "date":
dataObj = DateParameter(parent=self);
dataObj = DateParameter(parent=self)
elif typename == "dateTime":
dataObj = DateTimeParameter(parent=self);
dataObj = DateTimeParameter(parent=self)
elif typename == "double":
dataObj = DoubleParameter(parent=self);
dataObj = DoubleParameter(parent=self)
elif typename == "duration":
dataObj = DurationParameter(parent=self);
dataObj = DurationParameter(parent=self)
elif typename == "int":
dataObj = IntParameter(parent=self);
dataObj = IntParameter(parent=self)
elif typename == "long":
dataObj = LongParameter(parent=self);
dataObj = LongParameter(parent=self)
elif typename == "time":
dataObj = TimeParameter(parent=self);
dataObj = TimeParameter(parent=self)
dataObj['name'] = name;
dataObj['value'] = value;
dataObj['name'] = name
dataObj['value'] = value
self._datas.add(name)
self.iterables.append(dataObj)
@ -217,7 +217,7 @@ class ControlSetResponse(ElementBase):
interfaces = set(['responseCode'])
def __init__(self, xml=None, parent=None):
ElementBase.__init__(self, xml, parent);
ElementBase.__init__(self, xml, parent)
self._nodes = set()
self._datas = set()
@ -244,7 +244,7 @@ class ControlSetResponse(ElementBase):
Arguments:
nodeId -- The ID for the node.
sourceId -- [optional] identifying the data source controlling the device
cacheType -- [optional] narrowing down the search to a specific kind of node
cacheType -- [optional] narrowing down the search to a specific kind of node
"""
if nodeId not in self._nodes:
self._nodes.add(nodeId)
@ -308,7 +308,7 @@ class ControlSetResponse(ElementBase):
def add_data(self, name):
"""
Add a new ResponseParameter element.
Add a new ResponseParameter element.
Arguments:
name -- Name of the parameter
@ -316,7 +316,7 @@ class ControlSetResponse(ElementBase):
if name not in self._datas:
self._datas.add(name)
data = ResponseParameter(parent=self)
data['name'] = name;
data['name'] = name
self.iterables.append(data)
return data
return None
@ -383,26 +383,26 @@ class Error(ElementBase):
value -- string
"""
self.xml.text = value;
self.xml.text = value
return self
def del_text(self):
"""Remove the contents inside the XML tag."""
self.xml.text = ""
return self
return self
class ResponseParameter(ElementBase):
"""
Parameter element in ControlSetResponse.
"""
"""
Parameter element in ControlSetResponse.
"""
namespace = 'urn:xmpp:iot:control'
name = 'parameter'
plugin_attrib = name
interfaces = set(['name']);
interfaces = set(['name'])
class BaseParameter(ElementBase):
"""
"""
Parameter element in SetCommand. This is a base class,
all instances of parameters added to SetCommand must be of types:
BooleanParameter
@ -415,90 +415,91 @@ class BaseParameter(ElementBase):
IntParameter
LongParameter
TimeParameter
"""
"""
namespace = 'urn:xmpp:iot:control'
name = 'baseParameter'
plugin_attrib = name
interfaces = set(['name','value']);
interfaces = set(['name','value'])
def _get_typename(self):
return self.name;
return self.name
class BooleanParameter(BaseParameter):
"""
Field data of type boolean.
Note that the value is expressed as a string.
"""
Field data of type boolean.
Note that the value is expressed as a string.
"""
name = 'boolean'
plugin_attrib = name
class ColorParameter(BaseParameter):
"""
Field data of type color.
Note that the value is expressed as a string.
"""
Field data of type color.
Note that the value is expressed as a string.
"""
name = 'color'
plugin_attrib = name
class StringParameter(BaseParameter):
"""
Field data of type string.
"""
Field data of type string.
"""
name = 'string'
plugin_attrib = name
class DateParameter(BaseParameter):
"""
Field data of type date.
Note that the value is expressed as a string.
"""
Field data of type date.
Note that the value is expressed as a string.
"""
name = 'date'
plugin_attrib = name
class DateTimeParameter(BaseParameter):
"""
Field data of type dateTime.
Note that the value is expressed as a string.
"""
Field data of type dateTime.
Note that the value is expressed as a string.
"""
name = 'dateTime'
plugin_attrib = name
class DoubleParameter(BaseParameter):
"""
Field data of type double.
Note that the value is expressed as a string.
"""
Field data of type double.
Note that the value is expressed as a string.
"""
name = 'double'
plugin_attrib = name
class DurationParameter(BaseParameter):
"""
Field data of type duration.
Note that the value is expressed as a string.
"""
Field data of type duration.
Note that the value is expressed as a string.
"""
name = 'duration'
plugin_attrib = name
class IntParameter(BaseParameter):
"""
Field data of type int.
Note that the value is expressed as a string.
"""
Field data of type int.
Note that the value is expressed as a string.
"""
name = 'int'
plugin_attrib = name
class LongParameter(BaseParameter):
"""
Field data of type long (64-bit int).
Note that the value is expressed as a string.
"""
Field data of type long (64-bit int).
Note that the value is expressed as a string.
"""
name = 'long'
plugin_attrib = name
class TimeParameter(BaseParameter):
"""
Field data of type time.
Note that the value is expressed as a string.
"""
Field data of type time.
Note that the value is expressed as a string.
"""
name = 'time'
plugin_attrib = name

View File

@ -0,0 +1,17 @@
"""
SleekXMPP: The Sleek XMPP Library
Implementation of HTTP over XMPP transport
http://xmpp.org/extensions/xep-0332.html
Copyright (C) 2015 Riptide IO, sangeeth@riptideio.com
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.plugins.base import register_plugin
from sleekxmpp.plugins.xep_0332 import stanza
from sleekxmpp.plugins.xep_0332.http import XEP_0332
register_plugin(XEP_0332)

View File

@ -0,0 +1,159 @@
"""
SleekXMPP: The Sleek XMPP Library
Implementation of HTTP over XMPP transport
http://xmpp.org/extensions/xep-0332.html
Copyright (C) 2015 Riptide IO, sangeeth@riptideio.com
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
import logging
from sleekxmpp import Iq
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.xmlstream.matcher import StanzaPath
from sleekxmpp.plugins.base import BasePlugin
from sleekxmpp.plugins.xep_0332.stanza import (
HTTPRequest, HTTPResponse, HTTPData
)
from sleekxmpp.plugins.xep_0131.stanza import Headers
log = logging.getLogger(__name__)
class XEP_0332(BasePlugin):
"""
XEP-0332: HTTP over XMPP transport
"""
name = 'xep_0332'
description = 'XEP-0332: HTTP over XMPP transport'
#: xep_0047 not included.
#: xep_0001, 0137 and 0166 are missing
dependencies = set(['xep_0030', 'xep_0131'])
#: TODO: Do we really need to mention the supported_headers?!
default_config = {
'supported_headers': set([
'Content-Length', 'Transfer-Encoding', 'DateTime',
'Accept-Charset', 'Location', 'Content-ID', 'Description',
'Content-Language', 'Content-Transfer-Encoding', 'Timestamp',
'Expires', 'User-Agent', 'Host', 'Proxy-Authorization', 'Date',
'WWW-Authenticate', 'Accept-Encoding', 'Server', 'Error-Info',
'Identifier', 'Content-Location', 'Content-Encoding', 'Distribute',
'Accept', 'Proxy-Authenticate', 'ETag', 'Expect', 'Content-Type'
])
}
def plugin_init(self):
self.xmpp.register_handler(
Callback(
'HTTP Request',
StanzaPath('iq/http-req'),
self._handle_request
)
)
self.xmpp.register_handler(
Callback(
'HTTP Response',
StanzaPath('iq/http-resp'),
self._handle_response
)
)
register_stanza_plugin(Iq, HTTPRequest, iterable=True)
register_stanza_plugin(Iq, HTTPResponse, iterable=True)
register_stanza_plugin(HTTPRequest, Headers, iterable=True)
register_stanza_plugin(HTTPRequest, HTTPData, iterable=True)
register_stanza_plugin(HTTPResponse, Headers, iterable=True)
register_stanza_plugin(HTTPResponse, HTTPData, iterable=True)
# TODO: Should we register any api's here? self.api.register()
def plugin_end(self):
self.xmpp.remove_handler('HTTP Request')
self.xmpp.remove_handler('HTTP Response')
self.xmpp['xep_0030'].del_feature('urn:xmpp:http')
for header in self.supported_headers:
self.xmpp['xep_0030'].del_feature(
feature='%s#%s' % (Headers.namespace, header)
)
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature('urn:xmpp:http')
for header in self.supported_headers:
self.xmpp['xep_0030'].add_feature(
'%s#%s' % (Headers.namespace, header)
)
# TODO: Do we need to add the supported headers to xep_0131?
# self.xmpp['xep_0131'].supported_headers.add(header)
def _handle_request(self, iq):
self.xmpp.event('http_request', iq)
def _handle_response(self, iq):
self.xmpp.event('http_response', iq)
def send_request(self, to=None, method=None, resource=None, headers=None,
data=None, **kwargs):
iq = self.xmpp.Iq()
iq['from'] = self.xmpp.boundjid
iq['to'] = to
iq['type'] = 'set'
iq['http-req']['headers'] = headers
iq['http-req']['method'] = method
iq['http-req']['resource'] = resource
iq['http-req']['version'] = '1.1' # TODO: set this implicitly
if 'id' in kwargs:
iq['id'] = kwargs["id"]
if data is not None:
iq['http-req']['data'] = data
return iq.send(
timeout=kwargs.get('timeout', None),
block=kwargs.get('block', True),
callback=kwargs.get('callback', None),
timeout_callback=kwargs.get('timeout_callback', None)
)
def send_response(self, to=None, code=None, message=None, headers=None,
data=None, **kwargs):
iq = self.xmpp.Iq()
iq['from'] = self.xmpp.boundjid
iq['to'] = to
iq['type'] = 'result'
iq['http-resp']['headers'] = headers
iq['http-resp']['code'] = code
iq['http-resp']['message'] = message
iq['http-resp']['version'] = '1.1' # TODO: set this implicitly
if 'id' in kwargs:
iq['id'] = kwargs["id"]
if data is not None:
iq['http-resp']['data'] = data
return iq.send(
timeout=kwargs.get('timeout', None),
block=kwargs.get('block', True),
callback=kwargs.get('callback', None),
timeout_callback=kwargs.get('timeout_callback', None)
)
def send_error(self, to=None, ecode='500', etype='wait',
econd='internal-server-error', **kwargs):
iq = self.xmpp.Iq()
iq['from'] = self.xmpp.boundjid
iq['to'] = to
iq['type'] = 'error'
iq['error']['code'] = ecode
iq['error']['type'] = etype
iq['error']['condition'] = econd
if 'id' in kwargs:
iq['id'] = kwargs["id"]
return iq.send(
timeout=kwargs.get('timeout', None),
block=kwargs.get('block', True),
callback=kwargs.get('callback', None),
timeout_callback=kwargs.get('timeout_callback', None)
)

View File

@ -0,0 +1,13 @@
"""
SleekXMPP: The Sleek XMPP Library
Implementation of HTTP over XMPP transport
http://xmpp.org/extensions/xep-0332.html
Copyright (C) 2015 Riptide IO, sangeeth@riptideio.com
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.plugins.xep_0332.stanza.request import HTTPRequest
from sleekxmpp.plugins.xep_0332.stanza.response import HTTPResponse
from sleekxmpp.plugins.xep_0332.stanza.data import HTTPData

View File

@ -0,0 +1,30 @@
"""
SleekXMPP: The Sleek XMPP Library
Implementation of HTTP over XMPP transport
http://xmpp.org/extensions/xep-0332.html
Copyright (C) 2015 Riptide IO, sangeeth@riptideio.com
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.xmlstream import ElementBase
class HTTPData(ElementBase):
"""
The data element.
"""
name = 'data'
namespace = 'urn:xmpp:http'
interfaces = set(['data'])
plugin_attrib = 'data'
is_extension = True
def get_data(self, encoding='text'):
data = self._get_sub_text(encoding, None)
return str(data) if data is not None else data
def set_data(self, data, encoding='text'):
self._set_sub_text(encoding, text=data)

View File

@ -0,0 +1,71 @@
"""
SleekXMPP: The Sleek XMPP Library
Implementation of HTTP over XMPP transport
http://xmpp.org/extensions/xep-0332.html
Copyright (C) 2015 Riptide IO, sangeeth@riptideio.com
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.xmlstream import ElementBase
class HTTPRequest(ElementBase):
"""
All HTTP communication is done using the `Request`/`Response` paradigm.
Each HTTP Request is made sending an `iq` stanza containing a `req`
element to the server. Each `iq` stanza sent is of type `set`.
Examples:
<iq type='set' from='a@b.com/browser' to='x@y.com' id='1'>
<req xmlns='urn:xmpp:http'
method='GET'
resource='/api/users'
version='1.1'>
<headers xmlns='http://jabber.org/protocol/shim'>
<header name='Host'>b.com</header>
</headers>
</req>
</iq>
<iq type='set' from='a@b.com/browser' to='x@y.com' id='2'>
<req xmlns='urn:xmpp:http'
method='PUT'
resource='/api/users'
version='1.1'>
<headers xmlns='http://jabber.org/protocol/shim'>
<header name='Host'>b.com</header>
<header name='Content-Type'>text/html</header>
<header name='Content-Length'>...</header>
</headers>
<data>
<text>...</text>
</data>
</req>
</iq>
"""
name = 'request'
namespace = 'urn:xmpp:http'
interfaces = set(['method', 'resource', 'version'])
plugin_attrib = 'http-req'
def get_method(self):
return self._get_attr('method', None)
def set_method(self, method):
self._set_attr('method', method)
def get_resource(self):
return self._get_attr('resource', None)
def set_resource(self, resource):
self._set_attr('resource', resource)
def get_version(self):
return self._get_attr('version', None)
def set_version(self, version='1.1'):
self._set_attr('version', version)

View File

@ -0,0 +1,66 @@
"""
SleekXMPP: The Sleek XMPP Library
Implementation of HTTP over XMPP transport
http://xmpp.org/extensions/xep-0332.html
Copyright (C) 2015 Riptide IO, sangeeth@riptideio.com
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.xmlstream import ElementBase
class HTTPResponse(ElementBase):
"""
When the HTTP Server responds, it does so by sending an `iq` stanza
response (type=`result`) back to the client containing the `resp` element.
Since response are asynchronous, and since multiple requests may be active
at the same time, responses may be returned in a different order than the
in which the original requests were made.
Examples:
<iq type='result'
from='httpserver@clayster.com'
to='httpclient@clayster.com/browser' id='2'>
<resp xmlns='urn:xmpp:http'
version='1.1'
statusCode='200'
statusMessage='OK'>
<headers xmlns='http://jabber.org/protocol/shim'>
<header name='Date'>Fri, 03 May 2013 16:39:54GMT-4</header>
<header name='Server'>Clayster</header>
<header name='Content-Type'>text/turtle</header>
<header name='Content-Length'>...</header>
<header name='Connection'>Close</header>
</headers>
<data>
<text>
...
</text>
</data>
</resp>
</iq>
"""
name = 'response'
namespace = 'urn:xmpp:http'
interfaces = set(['code', 'message', 'version'])
plugin_attrib = 'http-resp'
def get_code(self):
code = self._get_attr('statusCode', None)
return int(code) if code is not None else code
def set_code(self, code):
self._set_attr('statusCode', str(code))
def get_message(self):
return self._get_attr('statusMessage', '')
def set_message(self, message):
self._set_attr('statusMessage', message)
def set_version(self, version='1.1'):
self._set_attr('version', version)

View File

@ -237,8 +237,7 @@ class RosterNode(object):
if not self.xmpp.is_component:
return self.update(jid, subscription='remove')
def update(self, jid, name=None, subscription=None, groups=[],
block=True, timeout=None, callback=None):
def update(self, jid, name=None, subscription=None, groups=None, block=True, timeout=None, callback=None):
"""
Update a JID's subscription information.
@ -258,6 +257,9 @@ class RosterNode(object):
Will be executed when the roster is received.
Implies block=False.
"""
if not groups:
groups = []
self[jid]['name'] = name
self[jid]['groups'] = groups
self[jid].save()

View File

@ -6,8 +6,7 @@
See the file LICENSE for copying permission.
"""
from sleekxmpp.xmlstream import ElementBase
from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase
class AtomEntry(ElementBase):
@ -22,5 +21,23 @@ class AtomEntry(ElementBase):
namespace = 'http://www.w3.org/2005/Atom'
name = 'entry'
plugin_attrib = 'entry'
interfaces = set(('title', 'summary'))
sub_interfaces = set(('title', 'summary'))
interfaces = set(('title', 'summary', 'id', 'published', 'updated'))
sub_interfaces = set(('title', 'summary', 'id', 'published',
'updated'))
class AtomAuthor(ElementBase):
"""
An Atom author.
Stanza Interface:
name -- The printable author name
uri -- The bare jid of the author
"""
name = 'author'
plugin_attrib = 'author'
interfaces = set(('name', 'uri'))
sub_interfaces = set(('name', 'uri'))
register_stanza_plugin(AtomEntry, AtomAuthor)

View File

@ -60,7 +60,9 @@ class RootStanza(StanzaBase):
self.send()
elif isinstance(e, XMPPError):
# We raised this deliberately
keep_id = self['id']
self.reply(clear=e.clear)
self['id'] = keep_id
self['error']['condition'] = e.condition
self['error']['text'] = e.text
self['error']['type'] = e.etype
@ -72,7 +74,9 @@ class RootStanza(StanzaBase):
self.send()
else:
# We probably didn't raise this on purpose, so send an error stanza
keep_id = self['id']
self.reply()
self['id'] = keep_id
self['error']['condition'] = 'undefined-condition'
self['error']['text'] = "SleekXMPP got into trouble."
self['error']['type'] = 'cancel'

View File

@ -288,11 +288,8 @@ class SleekTest(unittest.TestCase):
if self.xmpp:
self.xmpp.socket.disconnect_error()
def stream_start(self, mode='client', skip=True, header=None,
socket='mock', jid='tester@localhost',
password='test', server='localhost',
port=5222, sasl_mech=None,
plugins=None, plugin_config={}):
def stream_start(self, mode='client', skip=True, header=None, socket='mock', jid='tester@localhost',
password='test', server='localhost', port=5222, sasl_mech=None, plugins=None, plugin_config=None):
"""
Initialize an XMPP client or component using a dummy XML stream.
@ -315,6 +312,9 @@ class SleekTest(unittest.TestCase):
plugins -- List of plugins to register. By default, all plugins
are loaded.
"""
if not plugin_config:
plugin_config = {}
if mode == 'client':
self.xmpp = ClientXMPP(jid, password,
sasl_mech=sasl_mech,
@ -425,8 +425,7 @@ class SleekTest(unittest.TestCase):
parts.append('xmlns="%s"' % default_ns)
return header % ' '.join(parts)
def recv(self, data, defaults=[], method='exact',
use_values=True, timeout=1):
def recv(self, data, defaults=None, method='exact', use_values=True, timeout=1):
"""
Pass data to the dummy XMPP client as if it came from an XMPP server.
@ -447,6 +446,9 @@ class SleekTest(unittest.TestCase):
timeout -- Time to wait in seconds for data to be received by
a live connection.
"""
if not defaults:
defaults = []
if self.xmpp.socket.is_live:
# we are working with a live connection, so we should
# verify what has been received instead of simulating

View File

@ -10,3 +10,4 @@ except:
from sleekxmpp.thirdparty import socks
from sleekxmpp.thirdparty.mini_dateutil import tzutc, tzoffset, parse_iso
from sleekxmpp.thirdparty.orderedset import OrderedSet

89
sleekxmpp/thirdparty/orderedset.py vendored Normal file
View File

@ -0,0 +1,89 @@
# Copyright (c) 2009 Raymond Hettinger
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
import collections
class OrderedSet(collections.MutableSet):
def __init__(self, iterable=None):
self.end = end = []
end += [None, end, end] # sentinel node for doubly linked list
self.map = {} # key --> [key, prev, next]
if iterable is not None:
self |= iterable
def __len__(self):
return len(self.map)
def __contains__(self, key):
return key in self.map
def add(self, key):
if key not in self.map:
end = self.end
curr = end[1]
curr[2] = end[1] = self.map[key] = [key, curr, end]
def discard(self, key):
if key in self.map:
key, prev, next = self.map.pop(key)
prev[2] = next
next[1] = prev
def __iter__(self):
end = self.end
curr = end[2]
while curr is not end:
yield curr[0]
curr = curr[2]
def __reversed__(self):
end = self.end
curr = end[1]
while curr is not end:
yield curr[0]
curr = curr[1]
def pop(self, last=True):
if not self:
raise KeyError('set is empty')
key = self.end[1][0] if last else self.end[2][0]
self.discard(key)
return key
def __repr__(self):
if not self:
return '%s()' % (self.__class__.__name__,)
return '%s(%r)' % (self.__class__.__name__, list(self))
def __eq__(self, other):
if isinstance(other, OrderedSet):
return len(self) == len(other) and list(self) == list(other)
return set(self) == set(other)
if __name__ == '__main__':
s = OrderedSet('abracadaba')
t = OrderedSet('simsalabim')
print(s | t)
print(s & t)
print(s - t)

View File

@ -28,6 +28,9 @@ OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE.
This module provides a standard socket-like interface for Python
for tunneling connections through SOCKS proxies.
"""
"""
Minor modifications made by Christopher Gilbert (http://motomastyle.com/)
for use in PyLoris (http://pyloris.sourceforge.net/)
@ -35,10 +38,13 @@ for use in PyLoris (http://pyloris.sourceforge.net/)
Minor modifications made by Mario Vilas (http://breakingcode.wordpress.com/)
mainly to merge bug fixes found in Sourceforge
Minor modifications made by Eugene Dementiev (http://www.dementiev.eu/)
"""
import socket
import struct
import sys
PROXY_TYPE_SOCKS4 = 1
PROXY_TYPE_SOCKS5 = 2
@ -208,12 +214,12 @@ class socksocket(socket.socket):
if self.__proxy[3]:
# Resolve remotely
ipaddr = None
req = req + chr(0x03).encode() + chr(len(destaddr)).encode() + destaddr
req = req + chr(0x03).encode() + chr(len(destaddr)).encode() + destaddr.encode()
else:
# Resolve locally
ipaddr = socket.inet_aton(socket.gethostbyname(destaddr))
req = req + chr(0x01).encode() + ipaddr
req = req + struct.pack(">H", destport)
req += struct.pack(">H", destport)
self.sendall(req)
# Get the response
resp = self.__recvall(4)
@ -282,7 +288,7 @@ class socksocket(socket.socket):
# The username parameter is considered userid for SOCKS4
if self.__proxy[4] != None:
req = req + self.__proxy[4]
req = req + chr(0x00).encode()
req += chr(0x00).encode()
# DNS name if remote resolving is required
# NOTE: This is actually an extension to the SOCKS4 protocol
# called SOCKS4A and may not be supported in all cases.
@ -323,7 +329,10 @@ class socksocket(socket.socket):
# We read the response until we get the string "\r\n\r\n"
resp = self.recv(1)
while resp.find("\r\n\r\n".encode()) == -1:
resp = resp + self.recv(1)
recv = self.recv(1)
if not recv:
raise GeneralProxyError((1, _generalerrors[1]))
resp = resp + recv
# We just need the first line to check if the connection
# was successful
statusline = resp.splitlines()[0].split(" ".encode(), 2)

View File

@ -34,7 +34,7 @@ class StateMachine(object):
self.lock.release()
def transition(self, from_state, to_state, wait=0.0, func=None, args=[], kwargs={}):
def transition(self, from_state, to_state, wait=0.0, func=None, args=None, kwargs=None):
'''
Transition from the given `from_state` to the given `to_state`.
This method will return `True` if the state machine is now in `to_state`. It
@ -65,15 +65,23 @@ class StateMachine(object):
values for `args` and `kwargs` are provided, they are expanded and passed like so:
`func( *args, **kwargs )`.
'''
if not args:
args = []
if not kwargs:
kwargs = {}
return self.transition_any((from_state,), to_state, wait=wait,
func=func, args=args, kwargs=kwargs)
def transition_any(self, from_states, to_state, wait=0.0, func=None, args=[], kwargs={}):
def transition_any(self, from_states, to_state, wait=0.0, func=None, args=None, kwargs=None):
'''
Transition from any of the given `from_states` to the given `to_state`.
'''
if not args:
args = []
if not kwargs:
kwargs = {}
if not isinstance(from_states, (tuple, list, set)):
raise ValueError("from_states should be a list, tuple, or set")

View File

@ -32,12 +32,17 @@ def _gevent_threads_enabled():
if _gevent_threads_enabled():
import gevent.queue as queue
Queue = queue.JoinableQueue
_queue = queue.JoinableQueue
else:
try:
import queue
except ImportError:
import Queue as queue
Queue = queue.Queue
_queue = queue.Queue
class Queue(_queue):
def put(self, item, block=True, timeout=None):
if _queue.full(self):
_queue.get(self)
return _queue.put(self, item, block, timeout)
QueueEmpty = queue.Empty

View File

@ -223,17 +223,16 @@ class SCRAM(Mech):
return self.hash(text).digest()
def saslname(self, value):
escaped = b''
for char in bytes(value):
if char == b',':
escaped += b'=2C'
elif char == b'=':
escaped += b'=3D'
value = value.decode("utf-8")
escaped = []
for char in value:
if char == ',':
escaped += '=2C'
elif char == '=':
escaped += '=3D'
else:
if isinstance(char, int):
char = chr(char)
escaped += bytes(char)
return escaped
escaped += char
return "".join(escaped).encode("utf-8")
def parse(self, challenge):
items = {}

View File

@ -9,5 +9,5 @@
# We don't want to have to import the entire library
# just to get the version info for setup.py
__version__ = '1.3.1'
__version_info__ = (1, 3, 1, '', 0)
__version__ = '1.4.0'
__version_info__ = (1, 4, 0, '', 0)

View File

@ -181,4 +181,4 @@ def verify(expected, raw_cert):
return True
raise CertificateError(
'Could not match certficate against hostname: %s' % expected)
'Could not match certificate against hostname: %s' % expected)

View File

@ -19,6 +19,7 @@ import logging
import weakref
from xml.etree import cElementTree as ET
from sleekxmpp.util import safedict
from sleekxmpp.xmlstream import JID
from sleekxmpp.xmlstream.tostring import tostring
from sleekxmpp.thirdparty import OrderedDict
@ -562,10 +563,13 @@ class ElementBase(object):
.. versionadded:: 1.0-Beta1
"""
values = {}
values = OrderedDict()
values['lang'] = self['lang']
for interface in self.interfaces:
values[interface] = self[interface]
if isinstance(self[interface], JID):
values[interface] = self[interface].jid
else:
values[interface] = self[interface]
if interface in self.lang_interfaces:
values['%s|*' % interface] = self['%s|*' % interface]
for plugin, stanza in self.plugins.items():
@ -676,6 +680,8 @@ class ElementBase(object):
if lang and attrib in self.lang_interfaces:
kwargs['lang'] = lang
kwargs = safedict(kwargs)
if attrib == 'substanzas':
return self.iterables
elif attrib in self.interfaces or attrib == 'lang':
@ -752,6 +758,8 @@ class ElementBase(object):
if lang and attrib in self.lang_interfaces:
kwargs['lang'] = lang
kwargs = safedict(kwargs)
if attrib in self.interfaces or attrib == 'lang':
if value is not None:
set_method = "set_%s" % attrib.lower()
@ -838,6 +846,8 @@ class ElementBase(object):
if lang and attrib in self.lang_interfaces:
kwargs['lang'] = lang
kwargs = safedict(kwargs)
if attrib in self.interfaces or attrib == 'lang':
del_method = "del_%s" % attrib.lower()
del_method2 = "del%s" % attrib.title()

View File

@ -114,7 +114,8 @@ class XMLStream(object):
:param int port: The port to use for the connection. Defaults to 0.
"""
def __init__(self, socket=None, host='', port=0):
def __init__(self, socket=None, host='', port=0, certfile=None,
keyfile=None, ca_certs=None, **kwargs):
#: Most XMPP servers support TLSv1, but OpenFire in particular
#: does not work well with it. For OpenFire, set
#: :attr:`ssl_version` to use ``SSLv23``::
@ -136,16 +137,16 @@ class XMLStream(object):
#:
#: On Mac OS X, certificates in the system keyring will
#: be consulted, even if they are not in the provided file.
self.ca_certs = None
self.ca_certs = ca_certs
#: Path to a file containing a client certificate to use for
#: authenticating via SASL EXTERNAL. If set, there must also
#: be a corresponding `:attr:keyfile` value.
self.certfile = None
self.certfile = certfile
#: Path to a file containing the private key for the selected
#: client certificate to use for authenticating via SASL EXTERNAL.
self.keyfile = None
self.keyfile = keyfile
self._der_cert = None
@ -291,7 +292,7 @@ class XMLStream(object):
self.event_queue = Queue()
#: A queue of string data to be sent over the stream.
self.send_queue = Queue()
self.send_queue = Queue(maxsize=256)
self.send_queue_lock = threading.Lock()
self.send_lock = threading.RLock()
@ -460,9 +461,11 @@ class XMLStream(object):
def _connect(self, reattempt=True):
self.scheduler.remove('Session timeout check')
if self.reconnect_delay is None or not reattempt:
if self.reconnect_delay is None:
delay = 1.0
else:
self.reconnect_delay = delay
if reattempt:
delay = min(self.reconnect_delay * 2, self.reconnect_max_delay)
delay = random.normalvariate(delay, delay * 0.1)
log.debug('Waiting %s seconds before connecting.', delay)
@ -523,7 +526,8 @@ class XMLStream(object):
'keyfile': self.keyfile,
'ca_certs': self.ca_certs,
'cert_reqs': cert_policy,
'do_handshake_on_connect': False
'do_handshake_on_connect': False,
"ssl_version": self.ssl_version
})
if sys.version_info >= (2, 7):
@ -847,13 +851,14 @@ class XMLStream(object):
'keyfile': self.keyfile,
'ca_certs': self.ca_certs,
'cert_reqs': cert_policy,
'do_handshake_on_connect': False
'do_handshake_on_connect': False,
"ssl_version": self.ssl_version
})
if sys.version_info >= (2, 7):
ssl_args['ciphers'] = self.ciphers
ssl_socket = ssl.wrap_socket(self.socket, **ssl_args);
ssl_socket = ssl.wrap_socket(self.socket, **ssl_args)
if hasattr(self.socket, 'socket'):
# We are using a testing socket, so preserve the top
@ -938,12 +943,13 @@ class XMLStream(object):
self.whitespace_keepalive_interval = 300
"""
self.schedule('Whitespace Keepalive',
self.whitespace_keepalive_interval,
self.send_raw,
args=(' ',),
kwargs={'now': True},
repeat=True)
if self.whitespace_keepalive:
self.schedule('Whitespace Keepalive',
self.whitespace_keepalive_interval,
self.send_raw,
args=(' ',),
kwargs={'now': True},
repeat=True)
def _remove_schedules(self, event):
"""Remove whitespace keepalive and certificate expiration schedules."""
@ -1148,7 +1154,7 @@ class XMLStream(object):
"""
return len(self.__event_handlers.get(name, []))
def event(self, name, data={}, direct=False):
def event(self, name, data=None, direct=False):
"""Manually trigger a custom event.
:param name: The name of the event to trigger.
@ -1159,6 +1165,9 @@ class XMLStream(object):
event queue. All event handlers will run in the
same thread.
"""
if not data:
data = {}
log.debug("Event triggered: " + name)
handlers = self.__event_handlers.get(name, [])
@ -1318,9 +1327,6 @@ class XMLStream(object):
try:
sent += self.socket.send(data[sent:])
count += 1
except Socket.error as serr:
if serr.errno != errno.EINTR:
raise
except ssl.SSLError as serr:
if tries >= self.ssl_retry_max:
log.debug('SSL error: max retries reached')
@ -1335,6 +1341,9 @@ class XMLStream(object):
if not self.stop.is_set():
time.sleep(self.ssl_retry_delay)
tries += 1
except Socket.error as serr:
if serr.errno != errno.EINTR:
raise
if count > 1:
log.debug('SENT: %d chunks', count)
except (Socket.error, ssl.SSLError) as serr:
@ -1744,9 +1753,6 @@ class XMLStream(object):
try:
sent += self.socket.send(enc_data[sent:])
count += 1
except Socket.error as serr:
if serr.errno != errno.EINTR:
raise
except ssl.SSLError as serr:
if tries >= self.ssl_retry_max:
log.debug('SSL error: max retries reached')
@ -1759,6 +1765,9 @@ class XMLStream(object):
if not self.stop.is_set():
time.sleep(self.ssl_retry_delay)
tries += 1
except Socket.error as serr:
if serr.errno != errno.EINTR:
raise
if count > 1:
log.debug('SENT: %d chunks', count)
self.send_queue.task_done()

View File

@ -385,7 +385,7 @@ class TestElementBase(SleekTest):
interfaces = set(('bar', 'baz'))
def setBar(self, value):
self._set_sub_text("path/to/only/bar", value);
self._set_sub_text("path/to/only/bar", value)
def getBar(self):
return self._get_sub_text("path/to/only/bar")
@ -394,7 +394,7 @@ class TestElementBase(SleekTest):
self._del_sub("path/to/only/bar")
def setBaz(self, value):
self._set_sub_text("path/to/just/baz", value);
self._set_sub_text("path/to/just/baz", value)
def getBaz(self):
return self._get_sub_text("path/to/just/baz")

View File

@ -11,8 +11,8 @@ class TestDataForms(SleekTest):
def setUp(self):
register_stanza_plugin(Message, xep_0004.Form)
register_stanza_plugin(xep_0004.Form, xep_0004.FormField)
register_stanza_plugin(xep_0004.FormField, xep_0004.FieldOption)
register_stanza_plugin(xep_0004.Form, xep_0004.FormField, iterable=True)
register_stanza_plugin(xep_0004.FormField, xep_0004.FieldOption, iterable=True)
def testMultipleInstructions(self):
"""Testing using multiple instructions elements in a data form."""
@ -68,7 +68,7 @@ class TestDataForms(SleekTest):
'value': 'cool'},
{'label': 'Urgh!',
'value': 'urgh'}]}
form['fields'] = fields
form.set_fields(fields)
self.check(msg, """
@ -141,13 +141,13 @@ class TestDataForms(SleekTest):
'value': 'cool'},
{'label': 'Urgh!',
'value': 'urgh'}]}
form['fields'] = fields
form.set_fields(fields)
form['type'] = 'submit'
form['values'] = {'f1': 'username',
form.set_values({'f1': 'username',
'f2': 'hunter2',
'f3': 'A long\nmultiline\nmessage',
'f4': 'cool'}
'f4': 'cool'})
self.check(form, """
<x xmlns="jabber:x:data" type="submit">
@ -189,7 +189,7 @@ class TestDataForms(SleekTest):
'value': 'cool'},
{'label': 'Urgh!',
'value': 'urgh'}]}
form['fields'] = fields
form.set_fields(fields)
form['type'] = 'cancel'
@ -197,5 +197,52 @@ class TestDataForms(SleekTest):
<x xmlns="jabber:x:data" type="cancel" />
""")
def testReported(self):
msg = self.Message()
form = msg['form']
form['type'] = 'result'
form.add_reported(var='f1', ftype='text-single', label='Username')
form.add_item({'f1': 'username@example.org'})
self.check(msg, """
<message>
<x xmlns="jabber:x:data" type="result">
<reported>
<field var="f1" type="text-single" label="Username" />
</reported>
<item>
<field var="f1">
<value>username@example.org</value>
</field>
</item>
</x>
</message>
""")
def testSetReported(self):
msg = self.Message()
form = msg['form']
form['type'] = 'result'
reported = {'f1': {
'var': 'f1',
'type': 'text-single',
'label': 'Username'
}}
form.set_reported(reported)
self.check(msg, """
<message>
<x xmlns="jabber:x:data" type="result">
<reported>
<field var="f1" type="text-single" label="Username" />
</reported>
</x>
</message>
""")
suite = unittest.TestLoader().loadTestsFromTestCase(TestDataForms)

View File

@ -0,0 +1,189 @@
import unittest
from sleekxmpp import Message
from sleekxmpp.test import SleekTest
import sleekxmpp.plugins.xep_0004 as xep_0004
import sleekxmpp.plugins.xep_0122 as xep_0122
from sleekxmpp.xmlstream import register_stanza_plugin
class TestDataForms(SleekTest):
def setUp(self):
register_stanza_plugin(Message, xep_0004.Form)
register_stanza_plugin(xep_0004.Form, xep_0004.FormField, iterable=True)
register_stanza_plugin(xep_0004.FormField, xep_0004.FieldOption, iterable=True)
register_stanza_plugin(xep_0004.FormField, xep_0122.FormValidation)
def test_basic_validation(self):
"""Testing basic validation setting and getting."""
msg = self.Message()
form = msg['form']
field = form.addField(var='f1',
ftype='text-single',
label='Text',
desc='A text field',
required=True,
value='Some text!')
validation = field['validate']
validation['datatype'] = 'xs:string'
validation.set_basic(True)
self.check(msg, """
<message>
<x xmlns="jabber:x:data" type="form">
<field var="f1" type="text-single" label="Text">
<desc>A text field</desc>
<required />
<value>Some text!</value>
<validate xmlns="http://jabber.org/protocol/xdata-validate" datatype="xs:string">
<basic/>
</validate>
</field>
</x>
</message>
""")
self.assertTrue(validation.get_basic())
self.assertFalse(validation.get_open())
self.assertFalse(validation.get_range())
self.assertFalse(validation.get_regex())
def test_open_validation(self):
"""Testing open validation setting and getting."""
msg = self.Message()
form = msg['form']
field = form.addField(var='f1',
ftype='text-single',
label='Text',
desc='A text field',
required=True,
value='Some text!')
validation = field['validate']
validation.set_open(True)
self.check(msg, """
<message>
<x xmlns="jabber:x:data" type="form">
<field var="f1" type="text-single" label="Text">
<desc>A text field</desc>
<required />
<value>Some text!</value>
<validate xmlns="http://jabber.org/protocol/xdata-validate">
<open />
</validate>
</field>
</x>
</message>
""")
self.assertFalse(validation.get_basic())
self.assertTrue(validation.get_open())
self.assertFalse(validation.get_range())
self.assertFalse(validation.get_regex())
def test_regex_validation(self):
"""Testing regex validation setting and getting."""
msg = self.Message()
form = msg['form']
field = form.addField(var='f1',
ftype='text-single',
label='Text',
desc='A text field',
required=True,
value='Some text!')
regex_value = '[0-9]+'
validation = field['validate']
validation.set_regex(regex_value)
self.check(msg, """
<message>
<x xmlns="jabber:x:data" type="form">
<field var="f1" type="text-single" label="Text">
<desc>A text field</desc>
<required />
<value>Some text!</value>
<validate xmlns="http://jabber.org/protocol/xdata-validate">
<regex>[0-9]+</regex>
</validate>
</field>
</x>
</message>
""")
self.assertFalse(validation.get_basic())
self.assertFalse(validation.get_open())
self.assertFalse(validation.get_range())
self.assertTrue(validation.get_regex())
self.assertEqual(regex_value, validation.get_regex())
def test_range_validation(self):
"""Testing range validation setting and getting."""
msg = self.Message()
form = msg['form']
field = form.addField(var='f1',
ftype='text-single',
label='Text',
desc='A text field',
required=True,
value='Some text!')
validation = field['validate']
validation.set_range(True, minimum=0, maximum=10)
self.check(msg, """
<message>
<x xmlns="jabber:x:data" type="form">
<field var="f1" type="text-single" label="Text">
<desc>A text field</desc>
<required />
<value>Some text!</value>
<validate xmlns="http://jabber.org/protocol/xdata-validate">
<range min="0" max="10" />
</validate>
</field>
</x>
</message>
""")
self.assertDictEqual(dict(minimum=str(0), maximum=str(10)), validation.get_range())
def test_reported_field_validation(self):
"""
Testing adding validation to the field when it's stored in the reported.
:return:
"""
msg = self.Message()
form = msg['form']
field = form.addReported(var='f1', ftype='text-single', label='Text')
validation = field['validate']
validation.set_basic(True)
form.addItem({'f1': 'Some text!'})
self.check(msg, """
<message>
<x xmlns="jabber:x:data" type="form">
<reported>
<field var="f1" type="text-single" label="Text">
<validate xmlns="http://jabber.org/protocol/xdata-validate">
<basic />
</validate>
</field>
</reported>
<item>
<field var="f1">
<value>Some text!</value>
</field>
</item>
</x>
</message>
""")
suite = unittest.TestLoader().loadTestsFromTestCase(TestDataForms)

View File

@ -6,7 +6,7 @@ import sleekxmpp.plugins.xep_0323 as xep_0323
namespace='sn'
class TestSensorDataStanzas(SleekTest):
def setUp(self):
pass
@ -59,8 +59,8 @@ class TestSensorDataStanzas(SleekTest):
iq['req']['momentary'] = 'true'
iq['req'].add_node("Device02", "Source02", "CacheType");
iq['req'].add_node("Device44");
iq['req'].add_node("Device02", "Source02", "CacheType")
iq['req'].add_node("Device44")
self.check(iq,"""
<iq type='get'
@ -75,7 +75,7 @@ class TestSensorDataStanzas(SleekTest):
"""
)
iq['req'].del_node("Device02");
iq['req'].del_node("Device02")
self.check(iq,"""
<iq type='get'
@ -89,7 +89,7 @@ class TestSensorDataStanzas(SleekTest):
"""
)
iq['req'].del_nodes();
iq['req'].del_nodes()
self.check(iq,"""
<iq type='get'
@ -115,8 +115,8 @@ class TestSensorDataStanzas(SleekTest):
iq['req']['momentary'] = 'true'
iq['req'].add_field("Top temperature");
iq['req'].add_field("Bottom temperature");
iq['req'].add_field("Top temperature")
iq['req'].add_field("Bottom temperature")
self.check(iq,"""
<iq type='get'
@ -171,7 +171,7 @@ class TestSensorDataStanzas(SleekTest):
iq['accepted']['seqnr'] = '2'
self.check(iq,"""
<iq type='result'
<iq type='result'
from='device@clayster.com'
to='master@clayster.com/amr'
id='2'>
@ -193,7 +193,7 @@ class TestSensorDataStanzas(SleekTest):
iq['rejected']['error'] = 'Access denied.'
self.check(iq,"""
<iq type='error'
<iq type='error'
from='device@clayster.com'
to='master@clayster.com/amr'
id='4'>
@ -237,12 +237,12 @@ class TestSensorDataStanzas(SleekTest):
msg['to'] = 'master@clayster.com/amr'
msg['fields']['seqnr'] = '1'
node = msg['fields'].add_node("Device02");
ts = node.add_timestamp("2013-03-07T16:24:30");
node = msg['fields'].add_node("Device02")
ts = node.add_timestamp("2013-03-07T16:24:30")
data = ts.add_data(typename="numeric", name="Temperature", value="-12.42", unit='K');
data['momentary'] = 'true';
data['automaticReadout'] = 'true';
data = ts.add_data(typename="numeric", name="Temperature", value="-12.42", unit='K')
data['momentary'] = 'true'
data['automaticReadout'] = 'true'
self.check(msg,"""
<message from='device@clayster.com'
@ -250,7 +250,7 @@ class TestSensorDataStanzas(SleekTest):
<fields xmlns='urn:xmpp:iot:sensordata' seqnr='1'>
<node nodeId='Device02'>
<timestamp value='2013-03-07T16:24:30'>
<numeric name='Temperature' momentary='true' automaticReadout='true' value='-12.42' unit='K'/>
<numeric name='Temperature' momentary='true' automaticReadout='true' value='-12.42' unit='K'/>
</timestamp>
</node>
</fields>
@ -258,10 +258,9 @@ class TestSensorDataStanzas(SleekTest):
"""
)
node = msg['fields'].add_node("EmptyDevice");
node = msg['fields'].add_node("Device04");
ts = node.add_timestamp("EmptyTimestamp");
node = msg['fields'].add_node("EmptyDevice")
node = msg['fields'].add_node("Device04")
ts = node.add_timestamp("EmptyTimestamp")
self.check(msg,"""
<message from='device@clayster.com'
@ -269,7 +268,7 @@ class TestSensorDataStanzas(SleekTest):
<fields xmlns='urn:xmpp:iot:sensordata' seqnr='1'>
<node nodeId='Device02'>
<timestamp value='2013-03-07T16:24:30'>
<numeric name='Temperature' momentary='true' automaticReadout='true' value='-12.42' unit='K'/>
<numeric name='Temperature' momentary='true' automaticReadout='true' value='-12.42' unit='K'/>
</timestamp>
</node>
<node nodeId='EmptyDevice'/>
@ -281,32 +280,32 @@ class TestSensorDataStanzas(SleekTest):
"""
)
node = msg['fields'].add_node("Device77");
ts = node.add_timestamp("2013-05-03T12:00:01");
data = ts.add_data(typename="numeric", name="Temperature", value="-12.42", unit='K');
data['historicalDay'] = 'true';
data = ts.add_data(typename="numeric", name="Speed", value="312.42", unit='km/h');
data['historicalWeek'] = 'false';
data = ts.add_data(typename="string", name="Temperature name", value="Bottom oil");
data['historicalMonth'] = 'true';
data = ts.add_data(typename="string", name="Speed name", value="Top speed");
data['historicalQuarter'] = 'false';
data = ts.add_data(typename="dateTime", name="T1", value="1979-01-01T00:00:00");
data['historicalYear'] = 'true';
data = ts.add_data(typename="dateTime", name="T2", value="2000-01-01T01:02:03");
data['historicalOther'] = 'false';
data = ts.add_data(typename="timeSpan", name="TS1", value="P5Y");
data['missing'] = 'true';
data = ts.add_data(typename="timeSpan", name="TS2", value="PT2M1S");
data['manualEstimate'] = 'false';
data = ts.add_data(typename="enum", name="top color", value="red", dataType="string");
data['invoiced'] = 'true';
data = ts.add_data(typename="enum", name="bottom color", value="black", dataType="string");
data['powerFailure'] = 'false';
data = ts.add_data(typename="boolean", name="Temperature real", value="false");
data['historicalDay'] = 'true';
data = ts.add_data(typename="boolean", name="Speed real", value="true");
data['historicalWeek'] = 'false';
node = msg['fields'].add_node("Device77")
ts = node.add_timestamp("2013-05-03T12:00:01")
data = ts.add_data(typename="numeric", name="Temperature", value="-12.42", unit='K')
data['historicalDay'] = 'true'
data = ts.add_data(typename="numeric", name="Speed", value="312.42", unit='km/h')
data['historicalWeek'] = 'false'
data = ts.add_data(typename="string", name="Temperature name", value="Bottom oil")
data['historicalMonth'] = 'true'
data = ts.add_data(typename="string", name="Speed name", value="Top speed")
data['historicalQuarter'] = 'false'
data = ts.add_data(typename="dateTime", name="T1", value="1979-01-01T00:00:00")
data['historicalYear'] = 'true'
data = ts.add_data(typename="dateTime", name="T2", value="2000-01-01T01:02:03")
data['historicalOther'] = 'false'
data = ts.add_data(typename="timeSpan", name="TS1", value="P5Y")
data['missing'] = 'true'
data = ts.add_data(typename="timeSpan", name="TS2", value="PT2M1S")
data['manualEstimate'] = 'false'
data = ts.add_data(typename="enum", name="top color", value="red", dataType="string")
data['invoiced'] = 'true'
data = ts.add_data(typename="enum", name="bottom color", value="black", dataType="string")
data['powerFailure'] = 'false'
data = ts.add_data(typename="boolean", name="Temperature real", value="false")
data['historicalDay'] = 'true'
data = ts.add_data(typename="boolean", name="Speed real", value="true")
data['historicalWeek'] = 'false'
self.check(msg,"""
<message from='device@clayster.com'
@ -314,7 +313,7 @@ class TestSensorDataStanzas(SleekTest):
<fields xmlns='urn:xmpp:iot:sensordata' seqnr='1'>
<node nodeId='Device02'>
<timestamp value='2013-03-07T16:24:30'>
<numeric name='Temperature' momentary='true' automaticReadout='true' value='-12.42' unit='K'/>
<numeric name='Temperature' momentary='true' automaticReadout='true' value='-12.42' unit='K'/>
</timestamp>
</node>
<node nodeId='EmptyDevice'/>
@ -323,18 +322,18 @@ class TestSensorDataStanzas(SleekTest):
</node>
<node nodeId='Device77'>
<timestamp value='2013-05-03T12:00:01'>
<numeric name='Temperature' historicalDay='true' value='-12.42' unit='K'/>
<numeric name='Speed' historicalWeek='false' value='312.42' unit='km/h'/>
<string name='Temperature name' historicalMonth='true' value='Bottom oil'/>
<string name='Speed name' historicalQuarter='false' value='Top speed'/>
<dateTime name='T1' historicalYear='true' value='1979-01-01T00:00:00'/>
<dateTime name='T2' historicalOther='false' value='2000-01-01T01:02:03'/>
<timeSpan name='TS1' missing='true' value='P5Y'/>
<timeSpan name='TS2' manualEstimate='false' value='PT2M1S'/>
<enum name='top color' invoiced='true' value='red' dataType='string'/>
<enum name='bottom color' powerFailure='false' value='black' dataType='string'/>
<boolean name='Temperature real' historicalDay='true' value='false'/>
<boolean name='Speed real' historicalWeek='false' value='true'/>
<numeric name='Temperature' historicalDay='true' value='-12.42' unit='K'/>
<numeric name='Speed' historicalWeek='false' value='312.42' unit='km/h'/>
<string name='Temperature name' historicalMonth='true' value='Bottom oil'/>
<string name='Speed name' historicalQuarter='false' value='Top speed'/>
<dateTime name='T1' historicalYear='true' value='1979-01-01T00:00:00'/>
<dateTime name='T2' historicalOther='false' value='2000-01-01T01:02:03'/>
<timeSpan name='TS1' missing='true' value='P5Y'/>
<timeSpan name='TS2' manualEstimate='false' value='PT2M1S'/>
<enum name='top color' invoiced='true' value='red' dataType='string'/>
<enum name='bottom color' powerFailure='false' value='black' dataType='string'/>
<boolean name='Temperature real' historicalDay='true' value='false'/>
<boolean name='Speed real' historicalWeek='false' value='true'/>
</timestamp>
</node>
</fields>
@ -342,21 +341,19 @@ class TestSensorDataStanzas(SleekTest):
"""
)
def testTimestamp(self):
msg = self.Message();
msg = self.Message()
msg['from'] = 'device@clayster.com'
msg['to'] = 'master@clayster.com/amr'
msg['fields']['seqnr'] = '1'
node = msg['fields'].add_node("Device02");
node = msg['fields'].add_node("Device03");
ts = node.add_timestamp("2013-03-07T16:24:30");
ts = node.add_timestamp("2013-03-07T16:24:31");
node = msg['fields'].add_node("Device02")
node = msg['fields'].add_node("Device03")
ts = node.add_timestamp("2013-03-07T16:24:30")
ts = node.add_timestamp("2013-03-07T16:24:31")
self.check(msg,"""
<message from='device@clayster.com'
@ -386,8 +383,8 @@ class TestSensorDataStanzas(SleekTest):
self.check(msg,emptyStringIdXML)
msg['fields']['stringIds'] = "1"
self.check(msg,emptyStringIdXML)
suite = unittest.TestLoader().loadTestsFromTestCase(TestSensorDataStanzas)

View File

@ -15,7 +15,7 @@ import sleekxmpp.plugins.xep_0325 as xep_0325
namespace='sn'
class TestControlStanzas(SleekTest):
def setUp(self):
pass
@ -29,8 +29,8 @@ class TestControlStanzas(SleekTest):
iq['from'] = 'master@clayster.com/amr'
iq['to'] = 'device@clayster.com'
iq['id'] = '1'
iq['set'].add_node("Device02", "Source02", "MyCacheType");
iq['set'].add_node("Device15");
iq['set'].add_node("Device02", "Source02", "MyCacheType")
iq['set'].add_node("Device15")
iq['set'].add_data("Tjohej", "boolean", "true")
self.check(iq,"""
@ -47,7 +47,7 @@ class TestControlStanzas(SleekTest):
"""
)
iq['set'].del_node("Device02");
iq['set'].del_node("Device02")
self.check(iq,"""
<iq type='set'
@ -62,7 +62,7 @@ class TestControlStanzas(SleekTest):
"""
)
iq['set'].del_nodes();
iq['set'].del_nodes()
self.check(iq,"""
<iq type='set'
@ -84,12 +84,12 @@ class TestControlStanzas(SleekTest):
msg = self.Message()
msg['from'] = 'master@clayster.com/amr'
msg['to'] = 'device@clayster.com'
msg['set'].add_node("Device02");
msg['set'].add_node("Device15");
msg['set'].add_node("Device02")
msg['set'].add_node("Device15")
msg['set'].add_data("Tjohej", "boolean", "true")
self.check(msg,"""
<message
<message
from='master@clayster.com/amr'
to='device@clayster.com'>
<set xmlns='urn:xmpp:iot:control'>
@ -111,7 +111,7 @@ class TestControlStanzas(SleekTest):
iq['from'] = 'master@clayster.com/amr'
iq['to'] = 'device@clayster.com'
iq['id'] = '8'
iq['setResponse']['responseCode'] = "OK";
iq['setResponse']['responseCode'] = "OK"
self.check(iq,"""
<iq type='result'
@ -128,10 +128,9 @@ class TestControlStanzas(SleekTest):
iq['from'] = 'master@clayster.com/amr'
iq['to'] = 'device@clayster.com'
iq['id'] = '9'
iq['setResponse']['responseCode'] = "OtherError";
iq['setResponse']['error']['var'] = "Output";
iq['setResponse']['error']['text'] = "Test of other error.!";
iq['setResponse']['responseCode'] = "OtherError"
iq['setResponse']['error']['var'] = "Output"
iq['setResponse']['error']['text'] = "Test of other error.!"
self.check(iq,"""
<iq type='error'
@ -150,11 +149,10 @@ class TestControlStanzas(SleekTest):
iq['from'] = 'master@clayster.com/amr'
iq['to'] = 'device@clayster.com'
iq['id'] = '9'
iq['setResponse']['responseCode'] = "NotFound";
iq['setResponse'].add_node("Device17", "Source09");
iq['setResponse'].add_node("Device18", "Source09");
iq['setResponse'].add_data("Tjohopp");
iq['setResponse']['responseCode'] = "NotFound"
iq['setResponse'].add_node("Device17", "Source09")
iq['setResponse'].add_node("Device18", "Source09")
iq['setResponse'].add_data("Tjohopp")
self.check(iq,"""
<iq type='error'
@ -179,38 +177,38 @@ class TestControlStanzas(SleekTest):
iq['from'] = 'master@clayster.com/amr'
iq['to'] = 'device@clayster.com'
iq['id'] = '1'
iq['set'].add_node("Device02", "Source02", "MyCacheType");
iq['set'].add_node("Device15");
iq['set'].add_node("Device02", "Source02", "MyCacheType")
iq['set'].add_node("Device15")
iq['set'].add_data("Tjohej", "boolean", "true");
iq['set'].add_data("Tjohej2", "boolean", "false");
iq['set'].add_data("Tjohej", "boolean", "true")
iq['set'].add_data("Tjohej2", "boolean", "false")
iq['set'].add_data("TjohejC", "color", "FF00FF");
iq['set'].add_data("TjohejC2", "color", "00FF00");
iq['set'].add_data("TjohejC", "color", "FF00FF")
iq['set'].add_data("TjohejC2", "color", "00FF00")
iq['set'].add_data("TjohejS", "string", "String1");
iq['set'].add_data("TjohejS2", "string", "String2");
iq['set'].add_data("TjohejS", "string", "String1")
iq['set'].add_data("TjohejS2", "string", "String2")
iq['set'].add_data("TjohejDate", "date", "2012-01-01");
iq['set'].add_data("TjohejDate2", "date", "1900-12-03");
iq['set'].add_data("TjohejDate", "date", "2012-01-01")
iq['set'].add_data("TjohejDate2", "date", "1900-12-03")
iq['set'].add_data("TjohejDateT4", "dateTime", "1900-12-03 12:30");
iq['set'].add_data("TjohejDateT2", "dateTime", "1900-12-03 11:22");
iq['set'].add_data("TjohejDateT4", "dateTime", "1900-12-03 12:30")
iq['set'].add_data("TjohejDateT2", "dateTime", "1900-12-03 11:22")
iq['set'].add_data("TjohejDouble2", "double", "200.22");
iq['set'].add_data("TjohejDouble3", "double", "-12232131.3333");
iq['set'].add_data("TjohejDouble2", "double", "200.22")
iq['set'].add_data("TjohejDouble3", "double", "-12232131.3333")
iq['set'].add_data("TjohejDur", "duration", "P5Y");
iq['set'].add_data("TjohejDur2", "duration", "PT2M1S");
iq['set'].add_data("TjohejDur", "duration", "P5Y")
iq['set'].add_data("TjohejDur2", "duration", "PT2M1S")
iq['set'].add_data("TjohejInt", "int", "1");
iq['set'].add_data("TjohejInt2", "int", "-42");
iq['set'].add_data("TjohejInt", "int", "1")
iq['set'].add_data("TjohejInt2", "int", "-42")
iq['set'].add_data("TjohejLong", "long", "123456789098");
iq['set'].add_data("TjohejLong2", "long", "-90983243827489374");
iq['set'].add_data("TjohejLong", "long", "123456789098")
iq['set'].add_data("TjohejLong2", "long", "-90983243827489374")
iq['set'].add_data("TjohejTime", "time", "23:59");
iq['set'].add_data("TjohejTime2", "time", "12:00");
iq['set'].add_data("TjohejTime", "time", "23:59")
iq['set'].add_data("TjohejTime2", "time", "12:00")
self.check(iq,"""
<iq type='set'
@ -244,5 +242,5 @@ class TestControlStanzas(SleekTest):
</iq>
"""
)
suite = unittest.TestLoader().loadTestsFromTestCase(TestControlStanzas)

View File

@ -119,7 +119,7 @@ class TestAdHocCommands(SleekTest):
def handle_command(iq, session):
def handle_form(form, session):
results.append(form['values']['foo'])
results.append(form.get_values()['foo'])
form = self.xmpp['xep_0004'].makeForm('form')
form.addField(var='foo', ftype='text-single', label='Foo')
@ -191,10 +191,10 @@ class TestAdHocCommands(SleekTest):
def handle_command(iq, session):
def handle_step2(form, session):
results.append(form['values']['bar'])
results.append(form.get_values()['bar'])
def handle_step1(form, session):
results.append(form['values']['foo'])
results.append(form.get_values()['foo'])
form = self.xmpp['xep_0004'].makeForm('form')
form.addField(var='bar', ftype='text-single', label='Bar')
@ -426,7 +426,7 @@ class TestAdHocCommands(SleekTest):
def handle_form(forms, session):
for form in forms:
results.append(form['values']['FORM_TYPE'])
results.append(form.get_values()['FORM_TYPE'])
form1 = self.xmpp['xep_0004'].makeForm('form')
form1.addField(var='FORM_TYPE', ftype='hidden', value='form_1')

View File

@ -19,7 +19,7 @@ class TestStreamSensorData(SleekTest):
pass
def _time_now(self):
return datetime.datetime.now().replace(microsecond=0).isoformat();
return datetime.datetime.now().replace(microsecond=0).isoformat()
def tearDown(self):
self.stream_close()
@ -29,12 +29,12 @@ class TestStreamSensorData(SleekTest):
plugins=['xep_0030',
'xep_0323'])
myDevice = Device("Device22");
myDevice._add_field(name="Temperature", typename="numeric", unit="°C");
myDevice = Device("Device22")
myDevice._add_field(name="Temperature", typename="numeric", unit="°C")
myDevice._set_momentary_timestamp("2013-03-07T16:24:30")
myDevice._add_field_momentary_data("Temperature", "23.4", flags={"automaticReadout": "true"});
myDevice._add_field_momentary_data("Temperature", "23.4", flags={"automaticReadout": "true"})
self.xmpp['xep_0323'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5);
self.xmpp['xep_0323'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5)
self.recv("""
<iq type='get'
@ -46,7 +46,7 @@ class TestStreamSensorData(SleekTest):
""")
self.send("""
<iq type='result'
<iq type='result'
from='device@clayster.com'
to='master@clayster.com/amr'
id='1'>
@ -60,11 +60,11 @@ class TestStreamSensorData(SleekTest):
<fields xmlns='urn:xmpp:iot:sensordata' seqnr='1' done='true'>
<node nodeId='Device22'>
<timestamp value='2013-03-07T16:24:30'>
<numeric name='Temperature' momentary='true' automaticReadout='true' value='23.4' unit='°C'/>
<numeric name='Temperature' momentary='true' automaticReadout='true' value='23.4' unit='°C'/>
</timestamp>
</node>
</fields>
</message>
</message>
""")
def testRequestRejectAuth(self):
@ -73,7 +73,7 @@ class TestStreamSensorData(SleekTest):
plugins=['xep_0030',
'xep_0323'])
self.xmpp['xep_0323']._set_authenticated("darth@deathstar.com");
self.xmpp['xep_0323']._set_authenticated("darth@deathstar.com")
self.recv("""
<iq type='get'
@ -85,7 +85,7 @@ class TestStreamSensorData(SleekTest):
""")
self.send("""
<iq type='error'
<iq type='error'
from='device@clayster.com'
to='master@clayster.com/amr'
id='4'>
@ -101,8 +101,8 @@ class TestStreamSensorData(SleekTest):
plugins=['xep_0030',
'xep_0323'])
myDevice = Device("Device44");
self.xmpp['xep_0323'].register_node('Device44', myDevice, commTimeout=0.5);
myDevice = Device("Device44")
self.xmpp['xep_0323'].register_node('Device44', myDevice, commTimeout=0.5)
print("."),
@ -118,7 +118,7 @@ class TestStreamSensorData(SleekTest):
""")
self.send("""
<iq type='error'
<iq type='error'
from='device@clayster.com'
to='master@clayster.com/amr'
id='77'>
@ -142,7 +142,7 @@ class TestStreamSensorData(SleekTest):
""")
self.send("""
<iq type='result'
<iq type='result'
from='device@clayster.com'
to='master@clayster.com/amr'
id='8'>
@ -157,11 +157,11 @@ class TestStreamSensorData(SleekTest):
plugins=['xep_0030',
'xep_0323'])
myDevice = Device("Device44");
myDevice._add_field(name='Voltage', typename="numeric", unit="V");
myDevice._add_field_timestamp_data(name="Voltage", value="230.4", timestamp="2000-01-01T00:01:02", flags={"invoiced": "true"});
myDevice = Device("Device44")
myDevice._add_field(name='Voltage', typename="numeric", unit="V")
myDevice._add_field_timestamp_data(name="Voltage", value="230.4", timestamp="2000-01-01T00:01:02", flags={"invoiced": "true"})
self.xmpp['xep_0323'].register_node('Device44', myDevice, commTimeout=0.5);
self.xmpp['xep_0323'].register_node('Device44', myDevice, commTimeout=0.5)
print("."),
@ -177,7 +177,7 @@ class TestStreamSensorData(SleekTest):
""")
self.send("""
<iq type='error'
<iq type='error'
from='device@clayster.com'
to='master@clayster.com/amr'
id='7'>
@ -201,7 +201,7 @@ class TestStreamSensorData(SleekTest):
""")
self.send("""
<iq type='result'
<iq type='result'
from='device@clayster.com'
to='master@clayster.com/amr'
id='8'>
@ -215,11 +215,11 @@ class TestStreamSensorData(SleekTest):
<fields xmlns='urn:xmpp:iot:sensordata' seqnr='7'>
<node nodeId='Device44'>
<timestamp value='2000-01-01T00:01:02'>
<numeric name='Voltage' invoiced='true' value='230.4' unit='V'/>
<numeric name='Voltage' invoiced='true' value='230.4' unit='V'/>
</timestamp>
</node>
</fields>
</message>
</message>
""")
self.send("""
@ -227,7 +227,7 @@ class TestStreamSensorData(SleekTest):
to='master@clayster.com/amr'>
<fields xmlns='urn:xmpp:iot:sensordata' seqnr='7' done='true'>
</fields>
</message>
</message>
""")
def testRequestMultiTimestampSingleField(self):
@ -236,15 +236,15 @@ class TestStreamSensorData(SleekTest):
plugins=['xep_0030',
'xep_0323'])
myDevice = Device("Device44");
myDevice._add_field(name='Voltage', typename="numeric", unit="V");
myDevice._add_field_timestamp_data(name="Voltage", value="230.4", timestamp="2000-01-01T00:01:02", flags={"invoiced": "true"});
myDevice._add_field(name='Current', typename="numeric", unit="A");
myDevice._add_field(name='Height', typename="string");
myDevice._add_field_timestamp_data(name="Voltage", value="230.6", timestamp="2000-01-01T01:01:02");
myDevice._add_field_timestamp_data(name="Height", value="115 m", timestamp="2000-01-01T01:01:02", flags={"invoiced": "true"});
myDevice = Device("Device44")
myDevice._add_field(name='Voltage', typename="numeric", unit="V")
myDevice._add_field_timestamp_data(name="Voltage", value="230.4", timestamp="2000-01-01T00:01:02", flags={"invoiced": "true"})
myDevice._add_field(name='Current', typename="numeric", unit="A")
myDevice._add_field(name='Height', typename="string")
myDevice._add_field_timestamp_data(name="Voltage", value="230.6", timestamp="2000-01-01T01:01:02")
myDevice._add_field_timestamp_data(name="Height", value="115 m", timestamp="2000-01-01T01:01:02", flags={"invoiced": "true"})
self.xmpp['xep_0323'].register_node('Device44', myDevice, commTimeout=0.5);
self.xmpp['xep_0323'].register_node('Device44', myDevice, commTimeout=0.5)
print("."),
@ -260,7 +260,7 @@ class TestStreamSensorData(SleekTest):
""")
self.send("""
<iq type='result'
<iq type='result'
from='device@clayster.com'
to='master@clayster.com/amr'
id='8'>
@ -274,11 +274,11 @@ class TestStreamSensorData(SleekTest):
<fields xmlns='urn:xmpp:iot:sensordata' seqnr='7'>
<node nodeId='Device44'>
<timestamp value='2000-01-01T00:01:02'>
<numeric name='Voltage' invoiced='true' value='230.4' unit='V'/>
<numeric name='Voltage' invoiced='true' value='230.4' unit='V'/>
</timestamp>
</node>
</fields>
</message>
</message>
""")
self.send("""
@ -287,11 +287,11 @@ class TestStreamSensorData(SleekTest):
<fields xmlns='urn:xmpp:iot:sensordata' seqnr='7'>
<node nodeId='Device44'>
<timestamp value='2000-01-01T01:01:02'>
<numeric name='Voltage' value='230.6' unit='V'/>
<numeric name='Voltage' value='230.6' unit='V'/>
</timestamp>
</node>
</fields>
</message>
</message>
""")
self.send("""
@ -299,7 +299,7 @@ class TestStreamSensorData(SleekTest):
to='master@clayster.com/amr'>
<fields xmlns='urn:xmpp:iot:sensordata' seqnr='7' done='true'>
</fields>
</message>
</message>
""")
def testRequestMultiTimestampAllFields(self):
@ -308,15 +308,15 @@ class TestStreamSensorData(SleekTest):
plugins=['xep_0030',
'xep_0323'])
myDevice = Device("Device44");
myDevice._add_field(name='Voltage', typename="numeric", unit="V");
myDevice._add_field_timestamp_data(name="Voltage", value="230.4", timestamp="2000-01-01T00:01:02", flags={"invoiced": "true"});
myDevice._add_field(name='Current', typename="numeric", unit="A");
myDevice._add_field(name='Height', typename="string");
myDevice._add_field_timestamp_data(name="Voltage", value="230.6", timestamp="2000-01-01T01:01:02");
myDevice._add_field_timestamp_data(name="Height", value="115 m", timestamp="2000-01-01T01:01:02", flags={"invoiced": "true"});
myDevice = Device("Device44")
myDevice._add_field(name='Voltage', typename="numeric", unit="V")
myDevice._add_field_timestamp_data(name="Voltage", value="230.4", timestamp="2000-01-01T00:01:02", flags={"invoiced": "true"})
myDevice._add_field(name='Current', typename="numeric", unit="A")
myDevice._add_field(name='Height', typename="string")
myDevice._add_field_timestamp_data(name="Voltage", value="230.6", timestamp="2000-01-01T01:01:02")
myDevice._add_field_timestamp_data(name="Height", value="115 m", timestamp="2000-01-01T01:01:02", flags={"invoiced": "true"})
self.xmpp['xep_0323'].register_node('Device44', myDevice, commTimeout=0.5);
self.xmpp['xep_0323'].register_node('Device44', myDevice, commTimeout=0.5)
print("."),
@ -330,7 +330,7 @@ class TestStreamSensorData(SleekTest):
""")
self.send("""
<iq type='result'
<iq type='result'
from='device@clayster.com'
to='master@clayster.com/amr'
id='8'>
@ -344,11 +344,11 @@ class TestStreamSensorData(SleekTest):
<fields xmlns='urn:xmpp:iot:sensordata' seqnr='7'>
<node nodeId='Device44'>
<timestamp value='2000-01-01T00:01:02'>
<numeric name='Voltage' invoiced='true' value='230.4' unit='V'/>
<numeric name='Voltage' invoiced='true' value='230.4' unit='V'/>
</timestamp>
</node>
</fields>
</message>
</message>
""")
self.send("""
@ -357,12 +357,12 @@ class TestStreamSensorData(SleekTest):
<fields xmlns='urn:xmpp:iot:sensordata' seqnr='7'>
<node nodeId='Device44'>
<timestamp value='2000-01-01T01:01:02'>
<numeric name='Voltage' value='230.6' unit='V'/>
<string name='Height' invoiced='true' value='115 m'/>
<numeric name='Voltage' value='230.6' unit='V'/>
<string name='Height' invoiced='true' value='115 m'/>
</timestamp>
</node>
</fields>
</message>
</message>
""")
self.send("""
@ -370,7 +370,7 @@ class TestStreamSensorData(SleekTest):
to='master@clayster.com/amr'>
<fields xmlns='urn:xmpp:iot:sensordata' seqnr='7' done='true'>
</fields>
</message>
</message>
""")
def testRequestAPI(self):
@ -379,7 +379,7 @@ class TestStreamSensorData(SleekTest):
plugins=['xep_0030',
'xep_0323'])
self.xmpp['xep_0323'].request_data(from_jid="tester@localhost", to_jid="you@google.com", callback=None);
self.xmpp['xep_0323'].request_data(from_jid="tester@localhost", to_jid="you@google.com", callback=None)
self.send("""
<iq type='get'
@ -390,7 +390,7 @@ class TestStreamSensorData(SleekTest):
</iq>
""")
self.xmpp['xep_0323'].request_data(from_jid="tester@localhost", to_jid="you@google.com", nodeIds=['Device33', 'Device22'], callback=None);
self.xmpp['xep_0323'].request_data(from_jid="tester@localhost", to_jid="you@google.com", nodeIds=['Device33', 'Device22'], callback=None)
self.send("""
<iq type='get'
@ -404,7 +404,7 @@ class TestStreamSensorData(SleekTest):
</iq>
""")
self.xmpp['xep_0323'].request_data(from_jid="tester@localhost", to_jid="you@google.com", fields=['Temperature', 'Voltage'], callback=None);
self.xmpp['xep_0323'].request_data(from_jid="tester@localhost", to_jid="you@google.com", fields=['Temperature', 'Voltage'], callback=None)
self.send("""
<iq type='get'
@ -424,13 +424,13 @@ class TestStreamSensorData(SleekTest):
plugins=['xep_0030',
'xep_0323'])
results = [];
results = []
def my_callback(from_jid, result, nodeId=None, timestamp=None, fields=None, error_msg=None):
if (result == "rejected") and (error_msg == "Invalid device Device22"):
results.append("rejected");
results.append("rejected")
self.xmpp['xep_0323'].request_data(from_jid="tester@localhost", to_jid="you@google.com", nodeIds=['Device33', 'Device22'], callback=my_callback);
self.xmpp['xep_0323'].request_data(from_jid="tester@localhost", to_jid="you@google.com", nodeIds=['Device33', 'Device22'], callback=my_callback)
self.send("""
<iq type='get'
@ -445,20 +445,20 @@ class TestStreamSensorData(SleekTest):
""")
self.recv("""
<iq type='error'
<iq type='error'
from='you@google.com'
to='tester@localhost'
id='1'>
<rejected xmlns='urn:xmpp:iot:sensordata' seqnr='1'>
<error>Invalid device Device22</error>
</rejected>
</iq>
</iq>
""")
time.sleep(.1)
self.failUnless(results == ["rejected"],
"Rejected callback was not properly executed");
self.failUnless(results == ["rejected"],
"Rejected callback was not properly executed")
def testRequestAcceptedAPI(self):
@ -466,12 +466,12 @@ class TestStreamSensorData(SleekTest):
plugins=['xep_0030',
'xep_0323'])
results = [];
results = []
def my_callback(from_jid, result, nodeId=None, timestamp=None, fields=None, error_msg=None):
results.append(result);
results.append(result)
self.xmpp['xep_0323'].request_data(from_jid="tester@localhost", to_jid="you@google.com", nodeIds=['Device33', 'Device22'], callback=my_callback);
self.xmpp['xep_0323'].request_data(from_jid="tester@localhost", to_jid="you@google.com", nodeIds=['Device33', 'Device22'], callback=my_callback)
self.send("""
<iq type='get'
@ -486,18 +486,18 @@ class TestStreamSensorData(SleekTest):
""")
self.recv("""
<iq type='result'
<iq type='result'
from='you@google.com'
to='tester@localhost'
id='1'>
<accepted xmlns='urn:xmpp:iot:sensordata' seqnr='1'/>
</iq>
</iq>
""")
time.sleep(.1)
self.failUnless(results == ["accepted"],
"Accepted callback was not properly executed");
self.failUnless(results == ["accepted"],
"Accepted callback was not properly executed")
def testRequestFieldsAPI(self):
@ -505,25 +505,25 @@ class TestStreamSensorData(SleekTest):
plugins=['xep_0030',
'xep_0323'])
results = [];
callback_data = {};
results = []
callback_data = {}
def my_callback(from_jid, result, nodeId=None, timestamp=None, fields=None, error_msg=None):
results.append(result);
results.append(result)
if result == "fields":
callback_data["nodeId"] = nodeId;
callback_data["timestamp"] = timestamp;
callback_data["error_msg"] = error_msg;
callback_data["nodeId"] = nodeId
callback_data["timestamp"] = timestamp
callback_data["error_msg"] = error_msg
for f in fields:
callback_data["field_" + f['name']] = f;
callback_data["field_" + f['name']] = f
t1= threading.Thread(name="request_data",
target=self.xmpp['xep_0323'].request_data,
kwargs={"from_jid": "tester@localhost",
"to_jid": "you@google.com",
"nodeIds": ['Device33'],
"callback": my_callback});
t1.start();
kwargs={"from_jid": "tester@localhost",
"to_jid": "you@google.com",
"nodeIds": ['Device33'],
"callback": my_callback})
t1.start()
#self.xmpp['xep_0323'].request_data(from_jid="tester@localhost", to_jid="you@google.com", nodeIds=['Device33'], callback=my_callback);
self.send("""
@ -538,12 +538,12 @@ class TestStreamSensorData(SleekTest):
""")
self.recv("""
<iq type='result'
<iq type='result'
from='you@google.com'
to='tester@localhost'
id='1'>
<accepted xmlns='urn:xmpp:iot:sensordata' seqnr='1'/>
</iq>
</iq>
""")
self.recv("""
@ -552,42 +552,42 @@ class TestStreamSensorData(SleekTest):
<fields xmlns='urn:xmpp:iot:sensordata' seqnr='1'>
<node nodeId='Device33'>
<timestamp value='2000-01-01T00:01:02'>
<numeric name='Voltage' invoiced='true' value='230.4' unit='V'/>
<boolean name='TestBool' value='true'/>
<numeric name='Voltage' invoiced='true' value='230.4' unit='V'/>
<boolean name='TestBool' value='true'/>
</timestamp>
</node>
</fields>
</message>
</message>
""")
self.recv("""
<message from='you@google.com'
to='tester@localhost'>
<fields xmlns='urn:xmpp:iot:sensordata' seqnr='1' done='true'/>
</message>
</message>
""")
t1.join();
t1.join()
time.sleep(.5)
self.failUnlessEqual(results, ["accepted","fields","done"]);
self.failUnlessEqual(results, ["accepted","fields","done"])
# self.assertIn("nodeId", callback_data);
self.assertTrue("nodeId" in callback_data)
self.failUnlessEqual(callback_data["nodeId"], "Device33");
self.failUnlessEqual(callback_data["nodeId"], "Device33")
# self.assertIn("timestamp", callback_data);
self.assertTrue("timestamp" in callback_data);
self.failUnlessEqual(callback_data["timestamp"], "2000-01-01T00:01:02");
self.assertTrue("timestamp" in callback_data)
self.failUnlessEqual(callback_data["timestamp"], "2000-01-01T00:01:02")
#self.assertIn("field_Voltage", callback_data);
self.assertTrue("field_Voltage" in callback_data);
self.failUnlessEqual(callback_data["field_Voltage"], {"name": "Voltage", "value": "230.4", "typename": "numeric", "unit": "V", "flags": {"invoiced": "true"}});
self.assertTrue("field_Voltage" in callback_data)
self.failUnlessEqual(callback_data["field_Voltage"], {"name": "Voltage", "value": "230.4", "typename": "numeric", "unit": "V", "flags": {"invoiced": "true"}})
#self.assertIn("field_TestBool", callback_data);
self.assertTrue("field_TestBool" in callback_data);
self.failUnlessEqual(callback_data["field_TestBool"], {"name": "TestBool", "value": "true", "typename": "boolean" });
self.assertTrue("field_TestBool" in callback_data)
self.failUnlessEqual(callback_data["field_TestBool"], {"name": "TestBool", "value": "true", "typename": "boolean" })
def testServiceDiscoveryClient(self):
self.stream_start(mode='client',
plugins=['xep_0030',
'xep_0323']);
'xep_0323'])
self.recv("""
<iq type='get'
@ -605,14 +605,14 @@ class TestStreamSensorData(SleekTest):
<query xmlns='http://jabber.org/protocol/disco#info'>
<identity category='client' type='bot'/>
<feature var='urn:xmpp:iot:sensordata'/>
</query>
</iq>
</query>
</iq>
""")
def testServiceDiscoveryComponent(self):
self.stream_start(mode='component',
plugins=['xep_0030',
'xep_0323']);
'xep_0323'])
self.recv("""
<iq type='get'
@ -631,8 +631,8 @@ class TestStreamSensorData(SleekTest):
<query xmlns='http://jabber.org/protocol/disco#info'>
<identity category='component' type='generic'/>
<feature var='urn:xmpp:iot:sensordata'/>
</query>
</iq>
</query>
</iq>
""")
def testRequestTimeout(self):
@ -641,23 +641,23 @@ class TestStreamSensorData(SleekTest):
plugins=['xep_0030',
'xep_0323'])
results = [];
callback_data = {};
results = []
callback_data = {}
def my_callback(from_jid, result, nodeId=None, timestamp=None, error_msg=None):
results.append(result);
results.append(result)
if result == "failure":
callback_data["nodeId"] = nodeId;
callback_data["timestamp"] = timestamp;
callback_data["error_msg"] = error_msg;
callback_data["nodeId"] = nodeId
callback_data["timestamp"] = timestamp
callback_data["error_msg"] = error_msg
t1= threading.Thread(name="request_data",
target=self.xmpp['xep_0323'].request_data,
kwargs={"from_jid": "tester@localhost",
"to_jid": "you@google.com",
"nodeIds": ['Device33'],
"callback": my_callback});
t1.start();
kwargs={"from_jid": "tester@localhost",
"to_jid": "you@google.com",
"nodeIds": ['Device33'],
"callback": my_callback})
t1.start()
self.send("""
<iq type='get'
@ -671,12 +671,12 @@ class TestStreamSensorData(SleekTest):
""")
self.recv("""
<iq type='result'
<iq type='result'
from='you@google.com'
to='tester@localhost'
id='1'>
<accepted xmlns='urn:xmpp:iot:sensordata' seqnr='1'/>
</iq>
</iq>
""")
self.recv("""
@ -688,31 +688,31 @@ class TestStreamSensorData(SleekTest):
</message>
""")
t1.join();
t1.join()
time.sleep(.5)
self.failUnlessEqual(results, ["accepted","failure"]);
self.failUnlessEqual(results, ["accepted","failure"])
# self.assertIn("nodeId", callback_data);
self.assertTrue("nodeId" in callback_data);
self.failUnlessEqual(callback_data["nodeId"], "Device33");
self.assertTrue("nodeId" in callback_data)
self.failUnlessEqual(callback_data["nodeId"], "Device33")
# self.assertIn("timestamp", callback_data);
self.assertTrue("timestamp" in callback_data);
self.failUnlessEqual(callback_data["timestamp"], "2013-03-07T17:13:30");
self.assertTrue("timestamp" in callback_data)
self.failUnlessEqual(callback_data["timestamp"], "2013-03-07T17:13:30")
# self.assertIn("error_msg", callback_data);
self.assertTrue("error_msg" in callback_data);
self.failUnlessEqual(callback_data["error_msg"], "Timeout.");
self.assertTrue("error_msg" in callback_data)
self.failUnlessEqual(callback_data["error_msg"], "Timeout.")
def testDelayedRequest(self):
self.stream_start(mode='component',
plugins=['xep_0030',
'xep_0323'])
myDevice = Device("Device22");
myDevice._add_field(name="Temperature", typename="numeric", unit="°C");
myDevice = Device("Device22")
myDevice._add_field(name="Temperature", typename="numeric", unit="°C")
myDevice._set_momentary_timestamp("2013-03-07T16:24:30")
myDevice._add_field_momentary_data("Temperature", "23.4", flags={"automaticReadout": "true"});
myDevice._add_field_momentary_data("Temperature", "23.4", flags={"automaticReadout": "true"})
self.xmpp['xep_0323'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5);
self.xmpp['xep_0323'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5)
dtnow = datetime.datetime.now()
ts_2sec = datetime.timedelta(0,2)
@ -729,7 +729,7 @@ class TestStreamSensorData(SleekTest):
""")
self.send("""
<iq type='result'
<iq type='result'
from='device@clayster.com'
to='master@clayster.com/amr'
id='1'>
@ -743,7 +743,7 @@ class TestStreamSensorData(SleekTest):
<message from='device@clayster.com'
to='master@clayster.com/amr'>
<started xmlns='urn:xmpp:iot:sensordata' seqnr='1' />
</message>
</message>
""")
self.send("""
@ -752,11 +752,11 @@ class TestStreamSensorData(SleekTest):
<fields xmlns='urn:xmpp:iot:sensordata' seqnr='1' done='true'>
<node nodeId='Device22'>
<timestamp value='2013-03-07T16:24:30'>
<numeric name='Temperature' momentary='true' automaticReadout='true' value='23.4' unit='°C'/>
<numeric name='Temperature' momentary='true' automaticReadout='true' value='23.4' unit='°C'/>
</timestamp>
</node>
</fields>
</message>
</message>
""")
def testDelayedRequestFail(self):
@ -764,12 +764,12 @@ class TestStreamSensorData(SleekTest):
plugins=['xep_0030',
'xep_0323'])
myDevice = Device("Device22");
myDevice._add_field(name="Temperature", typename="numeric", unit="°C");
myDevice = Device("Device22")
myDevice._add_field(name="Temperature", typename="numeric", unit="°C")
myDevice._set_momentary_timestamp("2013-03-07T16:24:30")
myDevice._add_field_momentary_data("Temperature", "23.4", flags={"automaticReadout": "true"});
myDevice._add_field_momentary_data("Temperature", "23.4", flags={"automaticReadout": "true"})
self.xmpp['xep_0323'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5);
self.xmpp['xep_0323'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5)
dtnow = datetime.datetime.now()
ts_2sec = datetime.timedelta(0,2)
@ -792,7 +792,7 @@ class TestStreamSensorData(SleekTest):
xml_stanza['rejected']['error'] = error_text
self._filtered_stanza_check("""
<iq type='error'
<iq type='error'
from='device@clayster.com'
to='master@clayster.com/amr'
id='1'>
@ -825,13 +825,13 @@ class TestStreamSensorData(SleekTest):
plugins=['xep_0030',
'xep_0323'])
myDevice = Device("Device44");
myDevice._add_field(name='Voltage', typename="numeric", unit="V");
myDevice._add_field_timestamp_data(name="Voltage", value="230.1", timestamp="2000-01-01T00:01:02", flags={"invoiced": "true"});
myDevice._add_field_timestamp_data(name="Voltage", value="230.2", timestamp="2000-02-01T00:01:02", flags={"invoiced": "true"});
myDevice._add_field_timestamp_data(name="Voltage", value="230.3", timestamp="2000-03-01T00:01:02", flags={"invoiced": "true"});
myDevice = Device("Device44")
myDevice._add_field(name='Voltage', typename="numeric", unit="V")
myDevice._add_field_timestamp_data(name="Voltage", value="230.1", timestamp="2000-01-01T00:01:02", flags={"invoiced": "true"})
myDevice._add_field_timestamp_data(name="Voltage", value="230.2", timestamp="2000-02-01T00:01:02", flags={"invoiced": "true"})
myDevice._add_field_timestamp_data(name="Voltage", value="230.3", timestamp="2000-03-01T00:01:02", flags={"invoiced": "true"})
self.xmpp['xep_0323'].register_node('Device44', myDevice, commTimeout=0.5);
self.xmpp['xep_0323'].register_node('Device44', myDevice, commTimeout=0.5)
print("."),
@ -847,7 +847,7 @@ class TestStreamSensorData(SleekTest):
""")
self.send("""
<iq type='result'
<iq type='result'
from='device@clayster.com'
to='master@clayster.com/amr'
id='6'>
@ -861,11 +861,11 @@ class TestStreamSensorData(SleekTest):
<fields xmlns='urn:xmpp:iot:sensordata' seqnr='6'>
<node nodeId='Device44'>
<timestamp value='2000-02-01T00:01:02'>
<numeric name='Voltage' invoiced='true' value='230.2' unit='V'/>
<numeric name='Voltage' invoiced='true' value='230.2' unit='V'/>
</timestamp>
</node>
</fields>
</message>
</message>
""")
self.send("""
@ -874,11 +874,11 @@ class TestStreamSensorData(SleekTest):
<fields xmlns='urn:xmpp:iot:sensordata' seqnr='6'>
<node nodeId='Device44'>
<timestamp value='2000-03-01T00:01:02'>
<numeric name='Voltage' invoiced='true' value='230.3' unit='V'/>
<numeric name='Voltage' invoiced='true' value='230.3' unit='V'/>
</timestamp>
</node>
</fields>
</message>
</message>
""")
self.send("""
@ -886,7 +886,7 @@ class TestStreamSensorData(SleekTest):
to='master@clayster.com/amr'>
<fields xmlns='urn:xmpp:iot:sensordata' seqnr='6' done='true'>
</fields>
</message>
</message>
""")
def testRequestFieldTo(self):
@ -895,13 +895,13 @@ class TestStreamSensorData(SleekTest):
plugins=['xep_0030',
'xep_0323'])
myDevice = Device("Device44");
myDevice._add_field(name='Voltage', typename="numeric", unit="V");
myDevice._add_field_timestamp_data(name="Voltage", value="230.1", timestamp="2000-01-01T00:01:02", flags={"invoiced": "true"});
myDevice._add_field_timestamp_data(name="Voltage", value="230.2", timestamp="2000-02-01T00:01:02", flags={"invoiced": "true"});
myDevice._add_field_timestamp_data(name="Voltage", value="230.3", timestamp="2000-03-01T00:01:02", flags={"invoiced": "true"});
myDevice = Device("Device44")
myDevice._add_field(name='Voltage', typename="numeric", unit="V")
myDevice._add_field_timestamp_data(name="Voltage", value="230.1", timestamp="2000-01-01T00:01:02", flags={"invoiced": "true"})
myDevice._add_field_timestamp_data(name="Voltage", value="230.2", timestamp="2000-02-01T00:01:02", flags={"invoiced": "true"})
myDevice._add_field_timestamp_data(name="Voltage", value="230.3", timestamp="2000-03-01T00:01:02", flags={"invoiced": "true"})
self.xmpp['xep_0323'].register_node('Device44', myDevice, commTimeout=0.5);
self.xmpp['xep_0323'].register_node('Device44', myDevice, commTimeout=0.5)
print("."),
@ -917,7 +917,7 @@ class TestStreamSensorData(SleekTest):
""")
self.send("""
<iq type='result'
<iq type='result'
from='device@clayster.com'
to='master@clayster.com/amr'
id='6'>
@ -931,11 +931,11 @@ class TestStreamSensorData(SleekTest):
<fields xmlns='urn:xmpp:iot:sensordata' seqnr='6'>
<node nodeId='Device44'>
<timestamp value='2000-01-01T00:01:02'>
<numeric name='Voltage' invoiced='true' value='230.1' unit='V'/>
<numeric name='Voltage' invoiced='true' value='230.1' unit='V'/>
</timestamp>
</node>
</fields>
</message>
</message>
""")
self.send("""
@ -944,11 +944,11 @@ class TestStreamSensorData(SleekTest):
<fields xmlns='urn:xmpp:iot:sensordata' seqnr='6'>
<node nodeId='Device44'>
<timestamp value='2000-02-01T00:01:02'>
<numeric name='Voltage' invoiced='true' value='230.2' unit='V'/>
<numeric name='Voltage' invoiced='true' value='230.2' unit='V'/>
</timestamp>
</node>
</fields>
</message>
</message>
""")
self.send("""
@ -956,7 +956,7 @@ class TestStreamSensorData(SleekTest):
to='master@clayster.com/amr'>
<fields xmlns='urn:xmpp:iot:sensordata' seqnr='6' done='true'>
</fields>
</message>
</message>
""")
def testRequestFieldFromTo(self):
@ -965,13 +965,13 @@ class TestStreamSensorData(SleekTest):
plugins=['xep_0030',
'xep_0323'])
myDevice = Device("Device44");
myDevice._add_field(name='Voltage', typename="numeric", unit="V");
myDevice._add_field_timestamp_data(name="Voltage", value="230.1", timestamp="2000-01-01T00:01:02", flags={"invoiced": "true"});
myDevice._add_field_timestamp_data(name="Voltage", value="230.2", timestamp="2000-02-01T00:01:02", flags={"invoiced": "true"});
myDevice._add_field_timestamp_data(name="Voltage", value="230.3", timestamp="2000-03-01T00:01:02", flags={"invoiced": "true"});
myDevice = Device("Device44")
myDevice._add_field(name='Voltage', typename="numeric", unit="V")
myDevice._add_field_timestamp_data(name="Voltage", value="230.1", timestamp="2000-01-01T00:01:02", flags={"invoiced": "true"})
myDevice._add_field_timestamp_data(name="Voltage", value="230.2", timestamp="2000-02-01T00:01:02", flags={"invoiced": "true"})
myDevice._add_field_timestamp_data(name="Voltage", value="230.3", timestamp="2000-03-01T00:01:02", flags={"invoiced": "true"})
self.xmpp['xep_0323'].register_node('Device44', myDevice, commTimeout=0.5);
self.xmpp['xep_0323'].register_node('Device44', myDevice, commTimeout=0.5)
print("."),
@ -987,7 +987,7 @@ class TestStreamSensorData(SleekTest):
""")
self.send("""
<iq type='result'
<iq type='result'
from='device@clayster.com'
to='master@clayster.com/amr'
id='6'>
@ -1001,11 +1001,11 @@ class TestStreamSensorData(SleekTest):
<fields xmlns='urn:xmpp:iot:sensordata' seqnr='6'>
<node nodeId='Device44'>
<timestamp value='2000-02-01T00:01:02'>
<numeric name='Voltage' invoiced='true' value='230.2' unit='V'/>
<numeric name='Voltage' invoiced='true' value='230.2' unit='V'/>
</timestamp>
</node>
</fields>
</message>
</message>
""")
self.send("""
@ -1013,7 +1013,7 @@ class TestStreamSensorData(SleekTest):
to='master@clayster.com/amr'>
<fields xmlns='urn:xmpp:iot:sensordata' seqnr='6' done='true'>
</fields>
</message>
</message>
""")
def testDelayedRequestClient(self):
@ -1021,25 +1021,25 @@ class TestStreamSensorData(SleekTest):
plugins=['xep_0030',
'xep_0323'])
results = [];
callback_data = {};
results = []
callback_data = {}
def my_callback(from_jid, result, nodeId=None, timestamp=None, fields=None, error_msg=None):
results.append(result);
results.append(result)
if result == "fields":
callback_data["nodeId"] = nodeId;
callback_data["timestamp"] = timestamp;
callback_data["error_msg"] = error_msg;
callback_data["nodeId"] = nodeId
callback_data["timestamp"] = timestamp
callback_data["error_msg"] = error_msg
for f in fields:
callback_data["field_" + f['name']] = f;
callback_data["field_" + f['name']] = f
t1= threading.Thread(name="request_data",
target=self.xmpp['xep_0323'].request_data,
kwargs={"from_jid": "tester@localhost",
"to_jid": "you@google.com",
"nodeIds": ['Device33'],
"callback": my_callback});
t1.start();
kwargs={"from_jid": "tester@localhost",
"to_jid": "you@google.com",
"nodeIds": ['Device33'],
"callback": my_callback})
t1.start()
#self.xmpp['xep_0323'].request_data(from_jid="tester@localhost", to_jid="you@google.com", nodeIds=['Device33'], callback=my_callback);
self.send("""
@ -1054,20 +1054,20 @@ class TestStreamSensorData(SleekTest):
""")
self.recv("""
<iq type='result'
<iq type='result'
from='you@google.com'
to='tester@localhost'
id='1'>
<accepted xmlns='urn:xmpp:iot:sensordata' seqnr='1' queued='true'/>
</iq>
</iq>
""")
self.recv("""
<message from='device@clayster.com'
to='master@clayster.com/amr'>
<started xmlns='urn:xmpp:iot:sensordata' seqnr='1' />
</message>
""")
</message>
""")
self.recv("""
<message from='you@google.com'
@ -1075,37 +1075,37 @@ class TestStreamSensorData(SleekTest):
<fields xmlns='urn:xmpp:iot:sensordata' seqnr='1'>
<node nodeId='Device33'>
<timestamp value='2000-01-01T00:01:02'>
<numeric name='Voltage' invoiced='true' value='230.4' unit='V'/>
<boolean name='TestBool' value='true'/>
<numeric name='Voltage' invoiced='true' value='230.4' unit='V'/>
<boolean name='TestBool' value='true'/>
</timestamp>
</node>
</fields>
</message>
</message>
""")
self.recv("""
<message from='you@google.com'
to='tester@localhost'>
<fields xmlns='urn:xmpp:iot:sensordata' seqnr='1' done='true'/>
</message>
</message>
""")
t1.join();
t1.join()
time.sleep(.5)
self.failUnlessEqual(results, ["queued","started","fields","done"]);
self.failUnlessEqual(results, ["queued","started","fields","done"])
# self.assertIn("nodeId", callback_data);
self.assertTrue("nodeId" in callback_data);
self.failUnlessEqual(callback_data["nodeId"], "Device33");
self.assertTrue("nodeId" in callback_data)
self.failUnlessEqual(callback_data["nodeId"], "Device33")
# self.assertIn("timestamp", callback_data);
self.assertTrue("timestamp" in callback_data);
self.failUnlessEqual(callback_data["timestamp"], "2000-01-01T00:01:02");
self.assertTrue("timestamp" in callback_data)
self.failUnlessEqual(callback_data["timestamp"], "2000-01-01T00:01:02")
# self.assertIn("field_Voltage", callback_data);
self.assertTrue("field_Voltage" in callback_data);
self.failUnlessEqual(callback_data["field_Voltage"], {"name": "Voltage", "value": "230.4", "typename": "numeric", "unit": "V", "flags": {"invoiced": "true"}});
self.assertTrue("field_Voltage" in callback_data)
self.failUnlessEqual(callback_data["field_Voltage"], {"name": "Voltage", "value": "230.4", "typename": "numeric", "unit": "V", "flags": {"invoiced": "true"}})
# self.assertIn("field_TestBool", callback_data);
self.assertTrue("field_TestBool" in callback_data);
self.failUnlessEqual(callback_data["field_TestBool"], {"name": "TestBool", "value": "true", "typename": "boolean" });
self.assertTrue("field_TestBool" in callback_data)
self.failUnlessEqual(callback_data["field_TestBool"], {"name": "TestBool", "value": "true", "typename": "boolean" })
def testRequestFieldsCancelAPI(self):
@ -1114,12 +1114,12 @@ class TestStreamSensorData(SleekTest):
plugins=['xep_0030',
'xep_0323'])
results = [];
results = []
def my_callback(from_jid, result, nodeId=None, timestamp=None, fields=None, error_msg=None):
results.append(result);
results.append(result)
session = self.xmpp['xep_0323'].request_data(from_jid="tester@localhost", to_jid="you@google.com", nodeIds=['Device33'], callback=my_callback);
session = self.xmpp['xep_0323'].request_data(from_jid="tester@localhost", to_jid="you@google.com", nodeIds=['Device33'], callback=my_callback)
self.send("""
<iq type='get'
@ -1133,15 +1133,15 @@ class TestStreamSensorData(SleekTest):
""")
self.recv("""
<iq type='result'
<iq type='result'
from='you@google.com'
to='tester@localhost'
id='1'>
<accepted xmlns='urn:xmpp:iot:sensordata' seqnr='1'/>
</iq>
</iq>
""")
self.xmpp['xep_0323'].cancel_request(session=session);
self.xmpp['xep_0323'].cancel_request(session=session)
self.send("""
<iq type='get'
@ -1163,19 +1163,19 @@ class TestStreamSensorData(SleekTest):
time.sleep(.5)
self.failUnlessEqual(results, ["accepted","cancelled"]);
self.failUnlessEqual(results, ["accepted","cancelled"])
def testDelayedRequestCancel(self):
self.stream_start(mode='component',
plugins=['xep_0030',
'xep_0323'])
myDevice = Device("Device22");
myDevice._add_field(name="Temperature", typename="numeric", unit="°C");
myDevice = Device("Device22")
myDevice._add_field(name="Temperature", typename="numeric", unit="°C")
myDevice._set_momentary_timestamp("2013-03-07T16:24:30")
myDevice._add_field_momentary_data("Temperature", "23.4", flags={"automaticReadout": "true"});
myDevice._add_field_momentary_data("Temperature", "23.4", flags={"automaticReadout": "true"})
self.xmpp['xep_0323'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5);
self.xmpp['xep_0323'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5)
dtnow = datetime.datetime.now()
ts_2sec = datetime.timedelta(0,2)
@ -1192,7 +1192,7 @@ class TestStreamSensorData(SleekTest):
""")
self.send("""
<iq type='result'
<iq type='result'
from='device@clayster.com'
to='master@clayster.com/amr'
id='1'>

View File

@ -28,7 +28,7 @@ class TestStreamControl(SleekTest):
pass
def _time_now(self):
return datetime.datetime.now().replace(microsecond=0).isoformat();
return datetime.datetime.now().replace(microsecond=0).isoformat()
def tearDown(self):
self.stream_close()
@ -38,10 +38,10 @@ class TestStreamControl(SleekTest):
plugins=['xep_0030',
'xep_0325'])
myDevice = Device("Device22");
myDevice._add_control_field(name="Temperature", typename="int", value="15");
myDevice = Device("Device22")
myDevice._add_control_field(name="Temperature", typename="int", value="15")
self.xmpp['xep_0325'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5);
self.xmpp['xep_0325'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5)
self.recv("""
<iq type='set'
@ -60,26 +60,26 @@ class TestStreamControl(SleekTest):
to='master@clayster.com/amr'
id='1'>
<setResponse xmlns='urn:xmpp:iot:control' responseCode="OK" />
</iq>
</iq>
""")
self.assertEqual(myDevice._get_field_value("Temperature"), "17");
self.assertEqual(myDevice._get_field_value("Temperature"), "17")
def testRequestSetMulti(self):
self.stream_start(mode='component',
plugins=['xep_0030',
'xep_0325'])
myDevice = Device("Device22");
myDevice._add_control_field(name="Temperature", typename="int", value="15");
myDevice._add_control_field(name="Startup", typename="date", value="2013-01-03");
myDevice = Device("Device22")
myDevice._add_control_field(name="Temperature", typename="int", value="15")
myDevice._add_control_field(name="Startup", typename="date", value="2013-01-03")
myDevice2 = Device("Device23");
myDevice2._add_control_field(name="Temperature", typename="int", value="19");
myDevice2._add_control_field(name="Startup", typename="date", value="2013-01-09");
myDevice2 = Device("Device23")
myDevice2._add_control_field(name="Temperature", typename="int", value="19")
myDevice2._add_control_field(name="Startup", typename="date", value="2013-01-09")
self.xmpp['xep_0325'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5);
self.xmpp['xep_0325'].register_node(nodeId="Device23", device=myDevice2, commTimeout=0.5);
self.xmpp['xep_0325'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5)
self.xmpp['xep_0325'].register_node(nodeId="Device23", device=myDevice2, commTimeout=0.5)
self.recv("""
<iq type='set'
@ -99,11 +99,11 @@ class TestStreamControl(SleekTest):
to='master@clayster.com/amr'
id='1'>
<setResponse xmlns='urn:xmpp:iot:control' responseCode="OK" />
</iq>
</iq>
""")
self.assertEqual(myDevice._get_field_value("Temperature"), "17");
self.assertEqual(myDevice2._get_field_value("Temperature"), "19");
self.assertEqual(myDevice._get_field_value("Temperature"), "17")
self.assertEqual(myDevice2._get_field_value("Temperature"), "19")
self.recv("""
<iq type='set'
@ -125,23 +125,23 @@ class TestStreamControl(SleekTest):
to='master@clayster.com/amr'
id='2'>
<setResponse xmlns='urn:xmpp:iot:control' responseCode="OK" />
</iq>
</iq>
""")
self.assertEqual(myDevice._get_field_value("Temperature"), "20");
self.assertEqual(myDevice2._get_field_value("Temperature"), "20");
self.assertEqual(myDevice._get_field_value("Startup"), "2013-02-01");
self.assertEqual(myDevice2._get_field_value("Startup"), "2013-02-01");
self.assertEqual(myDevice._get_field_value("Temperature"), "20")
self.assertEqual(myDevice2._get_field_value("Temperature"), "20")
self.assertEqual(myDevice._get_field_value("Startup"), "2013-02-01")
self.assertEqual(myDevice2._get_field_value("Startup"), "2013-02-01")
def testRequestSetFail(self):
self.stream_start(mode='component',
plugins=['xep_0030',
'xep_0325'])
myDevice = Device("Device23");
myDevice._add_control_field(name="Temperature", typename="int", value="15");
myDevice = Device("Device23")
myDevice._add_control_field(name="Temperature", typename="int", value="15")
self.xmpp['xep_0325'].register_node(nodeId="Device23", device=myDevice, commTimeout=0.5);
self.xmpp['xep_0325'].register_node(nodeId="Device23", device=myDevice, commTimeout=0.5)
self.recv("""
<iq type='set'
@ -163,24 +163,24 @@ class TestStreamControl(SleekTest):
<parameter name='Voltage' />
<error var='Output'>Invalid field Voltage</error>
</setResponse>
</iq>
</iq>
""")
self.assertEqual(myDevice._get_field_value("Temperature"), "15");
self.assertFalse(myDevice.has_control_field("Voltage", "int"));
self.assertEqual(myDevice._get_field_value("Temperature"), "15")
self.assertFalse(myDevice.has_control_field("Voltage", "int"))
def testDirectSetOk(self):
self.stream_start(mode='component',
plugins=['xep_0030',
'xep_0325'])
myDevice = Device("Device22");
myDevice._add_control_field(name="Temperature", typename="int", value="15");
myDevice = Device("Device22")
myDevice._add_control_field(name="Temperature", typename="int", value="15")
self.xmpp['xep_0325'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5);
self.xmpp['xep_0325'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5)
self.recv("""
<message
<message
from='master@clayster.com/amr'
to='device@clayster.com'>
<set xmlns='urn:xmpp:iot:control'>
@ -191,20 +191,20 @@ class TestStreamControl(SleekTest):
time.sleep(.5)
self.assertEqual(myDevice._get_field_value("Temperature"), "17");
self.assertEqual(myDevice._get_field_value("Temperature"), "17")
def testDirectSetFail(self):
self.stream_start(mode='component',
plugins=['xep_0030',
'xep_0325'])
myDevice = Device("Device22");
myDevice._add_control_field(name="Temperature", typename="int", value="15");
myDevice = Device("Device22")
myDevice._add_control_field(name="Temperature", typename="int", value="15")
self.xmpp['xep_0325'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5);
self.xmpp['xep_0325'].register_node(nodeId="Device22", device=myDevice, commTimeout=0.5)
self.recv("""
<message
<message
from='master@clayster.com/amr'
to='device@clayster.com'>
<set xmlns='urn:xmpp:iot:control'>
@ -215,8 +215,8 @@ class TestStreamControl(SleekTest):
time.sleep(.5)
self.assertEqual(myDevice._get_field_value("Temperature"), "15");
self.assertFalse(myDevice.has_control_field("Voltage", "int"));
self.assertEqual(myDevice._get_field_value("Temperature"), "15")
self.assertFalse(myDevice.has_control_field("Voltage", "int"))
def testRequestSetOkAPI(self):
@ -225,16 +225,16 @@ class TestStreamControl(SleekTest):
plugins=['xep_0030',
'xep_0325'])
results = [];
results = []
def my_callback(from_jid, result, nodeIds=None, fields=None, error_msg=None):
results.append(result);
results.append(result)
fields = []
fields.append(("Temperature", "double", "20.5"))
fields.append(("TemperatureAlarmSetting", "string", "High"))
self.xmpp['xep_0325'].set_request(from_jid="tester@localhost", to_jid="you@google.com", fields=fields, nodeIds=['Device33', 'Device22'], callback=my_callback);
self.xmpp['xep_0325'].set_request(from_jid="tester@localhost", to_jid="you@google.com", fields=fields, nodeIds=['Device33', 'Device22'], callback=my_callback)
self.send("""
<iq type='set'
@ -256,12 +256,12 @@ class TestStreamControl(SleekTest):
to='tester@localhost'
id='1'>
<setResponse xmlns='urn:xmpp:iot:control' responseCode="OK" />
</iq>
</iq>
""")
time.sleep(.5)
self.assertEqual(results, ["OK"]);
self.assertEqual(results, ["OK"])
def testRequestSetErrorAPI(self):
@ -269,16 +269,16 @@ class TestStreamControl(SleekTest):
plugins=['xep_0030',
'xep_0325'])
results = [];
results = []
def my_callback(from_jid, result, nodeIds=None, fields=None, error_msg=None):
results.append(result);
results.append(result)
fields = []
fields.append(("Temperature", "double", "20.5"))
fields.append(("TemperatureAlarmSetting", "string", "High"))
self.xmpp['xep_0325'].set_request(from_jid="tester@localhost", to_jid="you@google.com", fields=fields, nodeIds=['Device33', 'Device22'], callback=my_callback);
self.xmpp['xep_0325'].set_request(from_jid="tester@localhost", to_jid="you@google.com", fields=fields, nodeIds=['Device33', 'Device22'], callback=my_callback)
self.send("""
<iq type='set'
@ -302,17 +302,17 @@ class TestStreamControl(SleekTest):
<setResponse xmlns='urn:xmpp:iot:control' responseCode="OtherError" >
<error var='Temperature'>Sensor error</error>
</setResponse>
</iq>
</iq>
""")
time.sleep(.5)
self.assertEqual(results, ["OtherError"]);
self.assertEqual(results, ["OtherError"])
def testServiceDiscoveryClient(self):
self.stream_start(mode='client',
plugins=['xep_0030',
'xep_0325']);
'xep_0325'])
self.recv("""
<iq type='get'
@ -330,14 +330,14 @@ class TestStreamControl(SleekTest):
<query xmlns='http://jabber.org/protocol/disco#info'>
<identity category='client' type='bot'/>
<feature var='urn:xmpp:iot:control'/>
</query>
</iq>
</query>
</iq>
""")
def testServiceDiscoveryComponent(self):
self.stream_start(mode='component',
plugins=['xep_0030',
'xep_0325']);
'xep_0325'])
self.recv("""
<iq type='get'
@ -356,8 +356,8 @@ class TestStreamControl(SleekTest):
<query xmlns='http://jabber.org/protocol/disco#info'>
<identity category='component' type='generic'/>
<feature var='urn:xmpp:iot:control'/>
</query>
</iq>
</query>
</iq>
""")

View File

@ -1,5 +1,5 @@
[tox]
envlist = py26,py27,py31,py32,py33
envlist = py26,py27,py34
[testenv]
deps = nose
commands = nosetests --where=tests --exclude=live -i sleektest.py