Pep8 improvements + cleanup
Change-Id: I9052e703b58e93b639c027521b47f693ae853f6e
This commit is contained in:
parent
7320ac1538
commit
78bb3b5f96
@ -32,14 +32,14 @@ basepath = path.abspath(path.join(sys.path[0], '..'))
|
|||||||
etc_path = path.join(basepath, 'etc')
|
etc_path = path.join(basepath, 'etc')
|
||||||
pid_path = path.join(basepath, 'pid')
|
pid_path = path.join(basepath, 'pid')
|
||||||
log_path = path.join(basepath, 'log')
|
log_path = path.join(basepath, 'log')
|
||||||
#sys.path[0] = path.join(basepath, 'src')
|
# sys.path[0] = path.join(basepath, 'src')
|
||||||
sys.path[0] = basepath
|
sys.path[0] = basepath
|
||||||
|
|
||||||
|
# do not move above!
|
||||||
import loggers
|
import loggers
|
||||||
from secop.client import ClientConsole
|
from secop.client import ClientConsole
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def parseArgv(argv):
|
def parseArgv(argv):
|
||||||
parser = argparse.ArgumentParser(description="Connect to a SECoP server")
|
parser = argparse.ArgumentParser(description="Connect to a SECoP server")
|
||||||
loggroup = parser.add_mutually_exclusive_group()
|
loggroup = parser.add_mutually_exclusive_group()
|
||||||
@ -64,7 +64,6 @@ def main(argv=None):
|
|||||||
loglevel = 'debug' if args.verbose else ('error' if args.quiet else 'info')
|
loglevel = 'debug' if args.verbose else ('error' if args.quiet else 'info')
|
||||||
loggers.initLogging('console', loglevel, log_path)
|
loggers.initLogging('console', loglevel, log_path)
|
||||||
|
|
||||||
|
|
||||||
console = ClientConsole(args.name, basepath)
|
console = ClientConsole(args.name, basepath)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -40,7 +40,6 @@ from secop import loggers
|
|||||||
from secop.server import Server
|
from secop.server import Server
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def parseArgv(argv):
|
def parseArgv(argv):
|
||||||
parser = argparse.ArgumentParser(description="Manage a SECoP server")
|
parser = argparse.ArgumentParser(description="Manage a SECoP server")
|
||||||
loggroup = parser.add_mutually_exclusive_group()
|
loggroup = parser.add_mutually_exclusive_group()
|
||||||
|
@ -23,7 +23,8 @@ In replies the SEC-node (in the playground) will always use the correct paramete
|
|||||||
On change-requests the parameter is assumed to be 'target', on trigger-requests it is assumed to be 'value'.
|
On change-requests the parameter is assumed to be 'target', on trigger-requests it is assumed to be 'value'.
|
||||||
Clients should not rely on this and explicitly state the parametername!
|
Clients should not rely on this and explicitly state the parametername!
|
||||||
|
|
||||||
All keywords are defined to be identifiers in the sense, that they are not longer than 63 characters and consist only of letters, digits and underscore and do not start with a digit. (i.e. T_9 is ok, whereas t{9} is not!)
|
All names and keywords are defined to be identifiers in the sense, that they are not longer than 63 characters and consist only of letters, digits and underscore and do not start with a digit. (i.e. T_9 is ok, whereas t{9} is not!)
|
||||||
|
No rule is without exception, there is exactly ONE special case: the identify request consists of the literal string '*IDN?\n' and its answer is formatted like an valid SCPI response for *IDN?.
|
||||||
|
|
||||||
We rely on the underlying transport to not split messages, i.e. all messages are transported as a whole and no message interrupts another.
|
We rely on the underlying transport to not split messages, i.e. all messages are transported as a whole and no message interrupts another.
|
||||||
|
|
||||||
@ -58,7 +59,7 @@ Identify
|
|||||||
--------
|
--------
|
||||||
|
|
||||||
* Request: type A: '*IDN?'
|
* Request: type A: '*IDN?'
|
||||||
* Reply: special: 'Sine2020WP7.1&ISSE, SECoP, V2016-11-30, rc1'
|
* Reply: special: 'SECoP, SECoPTCP, V2016-11-30, rc1'
|
||||||
* queries if SECoP protocol is supported and which version it is
|
* queries if SECoP protocol is supported and which version it is
|
||||||
Format is intentionally choosen to be compatible to SCPI (for this query only).
|
Format is intentionally choosen to be compatible to SCPI (for this query only).
|
||||||
It is NOT intended to transport information about the manufacturer of the hardware, but to identify this as a SECoP device and transfer the protocol version!
|
It is NOT intended to transport information about the manufacturer of the hardware, but to identify this as a SECoP device and transfer the protocol version!
|
||||||
|
@ -36,7 +36,7 @@ import glob
|
|||||||
import signal
|
import signal
|
||||||
|
|
||||||
|
|
||||||
CFG_DIR='/etc/secop'
|
CFG_DIR = '/etc/secop'
|
||||||
PID_DIR = '/var/run'
|
PID_DIR = '/var/run'
|
||||||
SRV_EXEC = 'secop-server'
|
SRV_EXEC = 'secop-server'
|
||||||
|
|
||||||
@ -62,7 +62,7 @@ def parseArgv(argv):
|
|||||||
|
|
||||||
def getServerNames():
|
def getServerNames():
|
||||||
return sorted([os.path.basename(entry)[:-4]
|
return sorted([os.path.basename(entry)[:-4]
|
||||||
for entry in glob.glob(os.path.join(CFG_DIR, '*.cfg'))])
|
for entry in glob.glob(os.path.join(CFG_DIR, '*.cfg'))])
|
||||||
|
|
||||||
|
|
||||||
def getSrvPid(name):
|
def getSrvPid(name):
|
||||||
@ -100,6 +100,7 @@ def determineServerStatus(name):
|
|||||||
else:
|
else:
|
||||||
print('%s: dead' % name)
|
print('%s: dead' % name)
|
||||||
|
|
||||||
|
|
||||||
def main(argv=None):
|
def main(argv=None):
|
||||||
if argv is None:
|
if argv is None:
|
||||||
argv = sys.argv
|
argv = sys.argv
|
||||||
@ -107,7 +108,7 @@ def main(argv=None):
|
|||||||
args = parseArgv(argv[1:])
|
args = parseArgv(argv[1:])
|
||||||
|
|
||||||
actionMap = {
|
actionMap = {
|
||||||
'start' : startServer,
|
'start': startServer,
|
||||||
'stop': stopServer,
|
'stop': stopServer,
|
||||||
'status': determineServerStatus,
|
'status': determineServerStatus,
|
||||||
}
|
}
|
||||||
|
@ -36,9 +36,9 @@ from secop.protocol.framing import FRAMERS
|
|||||||
from secop.protocol.messages import *
|
from secop.protocol.messages import *
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class TCPConnection(object):
|
class TCPConnection(object):
|
||||||
# disguise a TCP connection as serial one
|
# disguise a TCP connection as serial one
|
||||||
|
|
||||||
def __init__(self, host, port):
|
def __init__(self, host, port):
|
||||||
self._host = host
|
self._host = host
|
||||||
self._port = int(port)
|
self._port = int(port)
|
||||||
@ -73,15 +73,17 @@ class TCPConnection(object):
|
|||||||
while '\n' in data:
|
while '\n' in data:
|
||||||
line, data = data.split('\n', 1)
|
line, data = data.split('\n', 1)
|
||||||
try:
|
try:
|
||||||
self._readbuffer.put(line.strip('\r'), block=True, timeout=1)
|
self._readbuffer.put(
|
||||||
|
line.strip('\r'), block=True, timeout=1)
|
||||||
except Queue.Full:
|
except Queue.Full:
|
||||||
self.log.debug('rcv queue full! dropping line: %r' % line)
|
self.log.debug(
|
||||||
|
'rcv queue full! dropping line: %r' % line)
|
||||||
finally:
|
finally:
|
||||||
self._thread = None
|
self._thread = None
|
||||||
|
|
||||||
def readline(self, block=False):
|
def readline(self, block=False):
|
||||||
"""blocks until a full line was read and returns it"""
|
"""blocks until a full line was read and returns it"""
|
||||||
i = 10;
|
i = 10
|
||||||
while i:
|
while i:
|
||||||
try:
|
try:
|
||||||
return self._readbuffer.get(block=True, timeout=1)
|
return self._readbuffer.get(block=True, timeout=1)
|
||||||
@ -109,13 +111,13 @@ class Value(object):
|
|||||||
u = None
|
u = None
|
||||||
e = None
|
e = None
|
||||||
fmtstr = '%s'
|
fmtstr = '%s'
|
||||||
|
|
||||||
def __init__(self, value, qualifiers={}):
|
def __init__(self, value, qualifiers={}):
|
||||||
self.value = value
|
self.value = value
|
||||||
if 't' in qualifiers:
|
if 't' in qualifiers:
|
||||||
self.t = parse_time(qualifiers.pop('t'))
|
self.t = parse_time(qualifiers.pop('t'))
|
||||||
self.__dict__.update(qualifiers)
|
self.__dict__.update(qualifiers)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
r = []
|
r = []
|
||||||
if self.t is not None:
|
if self.t is not None:
|
||||||
@ -160,7 +162,8 @@ class Client(object):
|
|||||||
self.single_shots = dict()
|
self.single_shots = dict()
|
||||||
|
|
||||||
# mapping the modulename to a dict mapping the parameter names to their values
|
# mapping the modulename to a dict mapping the parameter names to their values
|
||||||
# note: the module value is stored as the value of the parameter value of the module
|
# note: the module value is stored as the value of the parameter value
|
||||||
|
# of the module
|
||||||
self.cache = dict()
|
self.cache = dict()
|
||||||
|
|
||||||
self._syncLock = threading.RLock()
|
self._syncLock = threading.RLock()
|
||||||
@ -188,7 +191,7 @@ class Client(object):
|
|||||||
self.secop_id = line
|
self.secop_id = line
|
||||||
continue
|
continue
|
||||||
msgtype, spec, data = self._decode_message(line)
|
msgtype, spec, data = self._decode_message(line)
|
||||||
if msgtype in ('event','changed'):
|
if msgtype in ('event', 'changed'):
|
||||||
# handle async stuff
|
# handle async stuff
|
||||||
self._handle_event(spec, data)
|
self._handle_event(spec, data)
|
||||||
if msgtype != 'event':
|
if msgtype != 'event':
|
||||||
@ -202,7 +205,6 @@ class Client(object):
|
|||||||
else:
|
else:
|
||||||
self.log.error('ignoring unexpected reply %r' % line)
|
self.log.error('ignoring unexpected reply %r' % line)
|
||||||
|
|
||||||
|
|
||||||
def _encode_message(self, requesttype, spec='', data=Ellipsis):
|
def _encode_message(self, requesttype, spec='', data=Ellipsis):
|
||||||
"""encodes the given message to a string
|
"""encodes the given message to a string
|
||||||
"""
|
"""
|
||||||
@ -250,30 +252,37 @@ class Client(object):
|
|||||||
try:
|
try:
|
||||||
mkthread(func, data)
|
mkthread(func, data)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
self.log.exception('Exception in Single-shot Callback!', err)
|
self.log.exception(
|
||||||
|
'Exception in Single-shot Callback!', err)
|
||||||
run.add(func)
|
run.add(func)
|
||||||
self.single_shots[spec].difference_update(run)
|
self.single_shots[spec].difference_update(run)
|
||||||
|
|
||||||
def register_callback(self, module, parameter, cb):
|
def register_callback(self, module, parameter, cb):
|
||||||
self.log.debug('registering callback %r for %s:%s' % (cb, module, parameter))
|
self.log.debug(
|
||||||
|
'registering callback %r for %s:%s' %
|
||||||
|
(cb, module, parameter))
|
||||||
self.callbacks.setdefault('%s:%s' % (module, parameter), set()).add(cb)
|
self.callbacks.setdefault('%s:%s' % (module, parameter), set()).add(cb)
|
||||||
|
|
||||||
def unregister_callback(self, module, parameter, cb):
|
def unregister_callback(self, module, parameter, cb):
|
||||||
self.log.debug('unregistering callback %r for %s:%s' % (cb, module, parameter))
|
self.log.debug(
|
||||||
self.callbacks.setdefault('%s:%s' % (module, parameter), set()).discard(cb)
|
'unregistering callback %r for %s:%s' %
|
||||||
|
(cb, module, parameter))
|
||||||
|
self.callbacks.setdefault('%s:%s' %
|
||||||
|
(module, parameter), set()).discard(cb)
|
||||||
|
|
||||||
def communicate(self, msgtype, spec='', data=Ellipsis):
|
def communicate(self, msgtype, spec='', data=Ellipsis):
|
||||||
# maps each (sync) request to the corresponding reply
|
# maps each (sync) request to the corresponding reply
|
||||||
# XXX: should go to the encoder! and be imported here (or make a translating method)
|
# XXX: should go to the encoder! and be imported here (or make a
|
||||||
|
# translating method)
|
||||||
REPLYMAP = {
|
REPLYMAP = {
|
||||||
"describe": "describing",
|
"describe": "describing",
|
||||||
"do": "done",
|
"do": "done",
|
||||||
"change": "changed",
|
"change": "changed",
|
||||||
"activate": "active",
|
"activate": "active",
|
||||||
"deactivate": "inactive",
|
"deactivate": "inactive",
|
||||||
"*IDN?": "SECoP,",
|
"*IDN?": "SECoP,",
|
||||||
"ping": "ping",
|
"ping": "ping",
|
||||||
}
|
}
|
||||||
if self.stopflag:
|
if self.stopflag:
|
||||||
raise RuntimeError('alreading stopping!')
|
raise RuntimeError('alreading stopping!')
|
||||||
if msgtype == 'poll':
|
if msgtype == 'poll':
|
||||||
@ -282,20 +291,25 @@ class Client(object):
|
|||||||
spec = spec + ':value'
|
spec = spec + ':value'
|
||||||
event = threading.Event()
|
event = threading.Event()
|
||||||
result = ['polled', spec]
|
result = ['polled', spec]
|
||||||
self.single_shots.setdefault(spec, set()).add(lambda d: (result.append(d), event.set()))
|
self.single_shots.setdefault(spec, set()).add(
|
||||||
self.connection.writeline(self._encode_message(msgtype, spec, data))
|
lambda d: (result.append(d), event.set()))
|
||||||
|
self.connection.writeline(
|
||||||
|
self._encode_message(
|
||||||
|
msgtype, spec, data))
|
||||||
if event.wait(10):
|
if event.wait(10):
|
||||||
return tuple(result)
|
return tuple(result)
|
||||||
raise RuntimeError("timeout upon waiting for reply!")
|
raise RuntimeError("timeout upon waiting for reply!")
|
||||||
|
|
||||||
rply = REPLYMAP[msgtype]
|
rply = REPLYMAP[msgtype]
|
||||||
if rply in self.expected_replies:
|
if rply in self.expected_replies:
|
||||||
raise RuntimeError("can not have more than one requests of the same type at the same time!")
|
raise RuntimeError(
|
||||||
|
"can not have more than one requests of the same type at the same time!")
|
||||||
event = threading.Event()
|
event = threading.Event()
|
||||||
self.expected_replies[rply] = [event]
|
self.expected_replies[rply] = [event]
|
||||||
self.connection.writeline(self._encode_message(msgtype, spec, data))
|
self.connection.writeline(self._encode_message(msgtype, spec, data))
|
||||||
if event.wait(10): # wait 10s for reply
|
if event.wait(10): # wait 10s for reply
|
||||||
result = rply, self.expected_replies[rply][1], self.expected_replies[rply][2]
|
result = rply, self.expected_replies[rply][
|
||||||
|
1], self.expected_replies[rply][2]
|
||||||
del self.expected_replies[rply]
|
del self.expected_replies[rply]
|
||||||
return result
|
return result
|
||||||
del self.expected_replies[rply]
|
del self.expected_replies[rply]
|
||||||
@ -316,9 +330,9 @@ class Client(object):
|
|||||||
self._cache.getdefault(device, {})[param] = value
|
self._cache.getdefault(device, {})[param] = value
|
||||||
# XXX: further notification-callbacks needed ???
|
# XXX: further notification-callbacks needed ???
|
||||||
|
|
||||||
|
|
||||||
def startup(self, async=False):
|
def startup(self, async=False):
|
||||||
_, self.equipment_id, self.describing_data = self.communicate('describe')
|
_, self.equipment_id, self.describing_data = self.communicate(
|
||||||
|
'describe')
|
||||||
# always fill our cache
|
# always fill our cache
|
||||||
self.communicate('activate')
|
self.communicate('activate')
|
||||||
# deactivate updates if not wanted
|
# deactivate updates if not wanted
|
||||||
@ -343,8 +357,8 @@ class Client(object):
|
|||||||
return self.describing_data['modules'][module]['commands'].keys()
|
return self.describing_data['modules'][module]['commands'].keys()
|
||||||
|
|
||||||
def getProperties(self, module, parameter):
|
def getProperties(self, module, parameter):
|
||||||
return self.describing_data['modules'][module]['parameters'][parameter].items()
|
return self.describing_data['modules'][
|
||||||
|
module]['parameters'][parameter].items()
|
||||||
|
|
||||||
def syncCommunicate(self, msg):
|
def syncCommunicate(self, msg):
|
||||||
return self.communicate(msg)
|
return self.communicate(msg)
|
||||||
|
|
||||||
|
@ -69,12 +69,12 @@ class PARAM(object):
|
|||||||
|
|
||||||
def as_dict(self):
|
def as_dict(self):
|
||||||
# used for serialisation only
|
# used for serialisation only
|
||||||
return dict(description = self.description,
|
return dict(description=self.description,
|
||||||
unit = self.unit,
|
unit=self.unit,
|
||||||
readonly = self.readonly,
|
readonly=self.readonly,
|
||||||
value = self.value,
|
value=self.value,
|
||||||
timestamp = self.timestamp,
|
timestamp=self.timestamp,
|
||||||
validator = repr(self.validator),
|
validator=repr(self.validator),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -95,13 +95,15 @@ class CMD(object):
|
|||||||
|
|
||||||
def as_dict(self):
|
def as_dict(self):
|
||||||
# used for serialisation only
|
# used for serialisation only
|
||||||
return dict(description = self.description,
|
return dict(description=self.description,
|
||||||
arguments = repr(self.arguments),
|
arguments=repr(self.arguments),
|
||||||
resulttype = repr(self.resulttype),
|
resulttype=repr(self.resulttype),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Meta class
|
# Meta class
|
||||||
# warning: MAGIC!
|
# warning: MAGIC!
|
||||||
|
|
||||||
|
|
||||||
class DeviceMeta(type):
|
class DeviceMeta(type):
|
||||||
|
|
||||||
def __new__(mcs, name, bases, attrs):
|
def __new__(mcs, name, bases, attrs):
|
||||||
@ -183,8 +185,9 @@ class DeviceMeta(type):
|
|||||||
argspec = inspect.getargspec(value)
|
argspec = inspect.getargspec(value)
|
||||||
if argspec[0] and argspec[0][0] == 'self':
|
if argspec[0] and argspec[0][0] == 'self':
|
||||||
del argspec[0][0]
|
del argspec[0][0]
|
||||||
newtype.CMDS[name[2:]] = CMD(getattr(value, '__doc__'),
|
newtype.CMDS[name[2:]] = CMD(
|
||||||
argspec.args, None) # XXX: find resulttype!
|
getattr(value, '__doc__'),
|
||||||
|
argspec.args, None) # XXX: find resulttype!
|
||||||
attrs['__constructed__'] = True
|
attrs['__constructed__'] = True
|
||||||
return newtype
|
return newtype
|
||||||
|
|
||||||
@ -279,7 +282,7 @@ class Readable(Device):
|
|||||||
'baseclass': PARAM('protocol defined interface class',
|
'baseclass': PARAM('protocol defined interface class',
|
||||||
default="Readable", validator=str),
|
default="Readable", validator=str),
|
||||||
'value': PARAM('current value of the device', readonly=True, default=0.),
|
'value': PARAM('current value of the device', readonly=True, default=0.),
|
||||||
'pollinterval': PARAM('sleeptime between polls', readonly=False, default=5, validator=floatrange(1,120),),
|
'pollinterval': PARAM('sleeptime between polls', readonly=False, default=5, validator=floatrange(1, 120),),
|
||||||
'status': PARAM('current status of the device', default=status.OK,
|
'status': PARAM('current status of the device', default=status.OK,
|
||||||
validator=enum(**{'idle': status.OK,
|
validator=enum(**{'idle': status.OK,
|
||||||
'BUSY': status.BUSY,
|
'BUSY': status.BUSY,
|
||||||
@ -303,7 +306,7 @@ class Readable(Device):
|
|||||||
self._pollthread = threading.Thread(target=self._pollThread)
|
self._pollthread = threading.Thread(target=self._pollThread)
|
||||||
self._pollthread.daemon = True
|
self._pollthread.daemon = True
|
||||||
self._pollthread.start()
|
self._pollthread.start()
|
||||||
|
|
||||||
def _pollThread(self):
|
def _pollThread(self):
|
||||||
while True:
|
while True:
|
||||||
time.sleep(self.pollinterval)
|
time.sleep(self.pollinterval)
|
||||||
@ -312,7 +315,8 @@ class Readable(Device):
|
|||||||
rfunc = getattr(self, 'read_%s' % pname, None)
|
rfunc = getattr(self, 'read_%s' % pname, None)
|
||||||
if rfunc:
|
if rfunc:
|
||||||
rfunc()
|
rfunc()
|
||||||
|
|
||||||
|
|
||||||
class Driveable(Readable):
|
class Driveable(Readable):
|
||||||
"""Basic Driveable device
|
"""Basic Driveable device
|
||||||
|
|
||||||
@ -324,5 +328,6 @@ class Driveable(Readable):
|
|||||||
'target': PARAM('target value of the device', default=0.,
|
'target': PARAM('target value of the device', default=0.,
|
||||||
readonly=False),
|
readonly=False),
|
||||||
}
|
}
|
||||||
|
|
||||||
def doStop(self):
|
def doStop(self):
|
||||||
time.sleep(1) # for testing !
|
time.sleep(1) # for testing !
|
||||||
|
@ -37,10 +37,14 @@ class Switch(Driveable):
|
|||||||
'value': PARAM('current state (on or off)',
|
'value': PARAM('current state (on or off)',
|
||||||
validator=enum(on=1, off=0), default=0),
|
validator=enum(on=1, off=0), default=0),
|
||||||
'target': PARAM('wanted state (on or off)',
|
'target': PARAM('wanted state (on or off)',
|
||||||
validator=enum(on=1, off=0), default=0,
|
validator=enum(on=1, off=0),
|
||||||
readonly=False),
|
default=0, readonly=False),
|
||||||
'switch_on_time': PARAM('how long to wait after switching the switch on', validator=floatrange(0, 60), unit='s', default=10, export=False),
|
'switch_on_time': PARAM('seconds to wait after activating the switch',
|
||||||
'switch_off_time': PARAM('how long to wait after switching the switch off', validator=floatrange(0, 60), unit='s', default=10, export=False),
|
validator=floatrange(0, 60), unit='s',
|
||||||
|
default=10, export=False),
|
||||||
|
'switch_off_time': PARAM('cool-down time in seconds',
|
||||||
|
validator=floatrange(0, 60), unit='s',
|
||||||
|
default=10, export=False),
|
||||||
}
|
}
|
||||||
|
|
||||||
def init(self):
|
def init(self):
|
||||||
@ -177,8 +181,8 @@ class SampleTemp(Driveable):
|
|||||||
validator=float, default=10),
|
validator=float, default=10),
|
||||||
'sensor': PARAM("Sensor number or calibration id",
|
'sensor': PARAM("Sensor number or calibration id",
|
||||||
validator=str, readonly=True),
|
validator=str, readonly=True),
|
||||||
'ramp': PARAM('moving speed in K/min',
|
'ramp': PARAM('moving speed in K/min', validator=floatrange(0, 100),
|
||||||
validator=floatrange(0, 100), unit='K/min', default=0.1, readonly=False),
|
unit='K/min', default=0.1, readonly=False),
|
||||||
}
|
}
|
||||||
|
|
||||||
def init(self):
|
def init(self):
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
|
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
|
|
||||||
class attrdict(dict):
|
class attrdict(dict):
|
||||||
"""a normal dict, providing access also via attributes"""
|
"""a normal dict, providing access also via attributes"""
|
||||||
|
|
||||||
|
@ -26,12 +26,17 @@ import re
|
|||||||
import time
|
import time
|
||||||
from datetime import tzinfo, timedelta, datetime
|
from datetime import tzinfo, timedelta, datetime
|
||||||
|
|
||||||
|
# format_time and parse_time could be simplified with external dateutil lib
|
||||||
|
# http://stackoverflow.com/a/15228038
|
||||||
|
|
||||||
# based on http://stackoverflow.com/a/39418771
|
# based on http://stackoverflow.com/a/39418771
|
||||||
|
|
||||||
|
|
||||||
class LocalTimezone(tzinfo):
|
class LocalTimezone(tzinfo):
|
||||||
ZERO = timedelta(0)
|
ZERO = timedelta(0)
|
||||||
STDOFFSET = timedelta(seconds = -time.timezone)
|
STDOFFSET = timedelta(seconds=-time.timezone)
|
||||||
if time.daylight:
|
if time.daylight:
|
||||||
DSTOFFSET = timedelta(seconds = -time.altzone)
|
DSTOFFSET = timedelta(seconds=-time.altzone)
|
||||||
else:
|
else:
|
||||||
DSTOFFSET = STDOFFSET
|
DSTOFFSET = STDOFFSET
|
||||||
|
|
||||||
@ -62,6 +67,7 @@ class LocalTimezone(tzinfo):
|
|||||||
|
|
||||||
LocalTimezone = LocalTimezone()
|
LocalTimezone = LocalTimezone()
|
||||||
|
|
||||||
|
|
||||||
def format_time(timestamp=None):
|
def format_time(timestamp=None):
|
||||||
# get time in UTC
|
# get time in UTC
|
||||||
if timestamp is None:
|
if timestamp is None:
|
||||||
@ -70,15 +76,22 @@ def format_time(timestamp=None):
|
|||||||
d = datetime.fromtimestamp(timestamp, LocalTimezone)
|
d = datetime.fromtimestamp(timestamp, LocalTimezone)
|
||||||
return d.isoformat("T")
|
return d.isoformat("T")
|
||||||
|
|
||||||
|
# Solution based on
|
||||||
|
# https://bugs.python.org/review/15873/diff/16581/Lib/datetime.py#newcode1418Lib/datetime.py:1418
|
||||||
|
|
||||||
|
|
||||||
class Timezone(tzinfo):
|
class Timezone(tzinfo):
|
||||||
|
|
||||||
def __init__(self, offset, name='unknown timezone'):
|
def __init__(self, offset, name='unknown timezone'):
|
||||||
self.offset = offset
|
self.offset = offset
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|
||||||
def tzname(self, dt):
|
def tzname(self, dt):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
def utcoffset(self, dt):
|
def utcoffset(self, dt):
|
||||||
return self.offset
|
return self.offset
|
||||||
|
|
||||||
def dst(self, dt):
|
def dst(self, dt):
|
||||||
return timedelta(0)
|
return timedelta(0)
|
||||||
datetime_re = re.compile(
|
datetime_re = re.compile(
|
||||||
@ -88,6 +101,7 @@ datetime_re = re.compile(
|
|||||||
r'(?P<tzinfo>Z|[+-]\d{2}(?::?\d{2})?)?$'
|
r'(?P<tzinfo>Z|[+-]\d{2}(?::?\d{2})?)?$'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _parse_isostring(isostring):
|
def _parse_isostring(isostring):
|
||||||
"""Parses a string and return a datetime.datetime.
|
"""Parses a string and return a datetime.datetime.
|
||||||
This function supports time zone offsets. When the input contains one,
|
This function supports time zone offsets. When the input contains one,
|
||||||
@ -111,7 +125,10 @@ def _parse_isostring(isostring):
|
|||||||
kw = {k: int(v) for k, v in kw.items() if v is not None}
|
kw = {k: int(v) for k, v in kw.items() if v is not None}
|
||||||
kw['tzinfo'] = _tzinfo
|
kw['tzinfo'] = _tzinfo
|
||||||
return datetime(**kw)
|
return datetime(**kw)
|
||||||
raise ValueError("%s is not a valid ISO8601 string I can parse!" % isostring)
|
raise ValueError(
|
||||||
|
"%s is not a valid ISO8601 string I can parse!" %
|
||||||
|
isostring)
|
||||||
|
|
||||||
|
|
||||||
def parse_time(isostring):
|
def parse_time(isostring):
|
||||||
try:
|
try:
|
||||||
|
@ -36,10 +36,6 @@ Interface to the modules:
|
|||||||
- get_module(modulename) returns the requested module or None
|
- get_module(modulename) returns the requested module or None
|
||||||
- remove_module(modulename_or_obj): removes the module (during shutdown)
|
- remove_module(modulename_or_obj): removes the module (during shutdown)
|
||||||
|
|
||||||
internal stuff which may be called
|
|
||||||
- list_modules(): return a list of modules + descriptive data as dict
|
|
||||||
- list_module_params():
|
|
||||||
return a list of paramnames for this module + descriptive data
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import time
|
import time
|
||||||
@ -50,20 +46,21 @@ from errors import *
|
|||||||
|
|
||||||
|
|
||||||
class Dispatcher(object):
|
class Dispatcher(object):
|
||||||
|
|
||||||
def __init__(self, logger, options):
|
def __init__(self, logger, options):
|
||||||
self.equipment_id = options.pop('equipment_id')
|
self.equipment_id = options.pop('equipment_id')
|
||||||
self.log = logger
|
self.log = logger
|
||||||
# map ALL modulename -> moduleobj
|
# map ALL modulename -> moduleobj
|
||||||
self._dispatcher_modules = {}
|
self._modules = {}
|
||||||
# list of EXPORTED modules
|
# list of EXPORTED modules
|
||||||
self._dispatcher_export = []
|
self._export = []
|
||||||
# list all connections
|
# list all connections
|
||||||
self._dispatcher_connections = []
|
self._connections = []
|
||||||
# active (i.e. broadcast-receiving) connections
|
# active (i.e. broadcast-receiving) connections
|
||||||
self._dispatcher_active_connections = set()
|
self._active_connections = set()
|
||||||
# map eventname -> list of subscribed connections
|
# map eventname -> list of subscribed connections
|
||||||
self._dispatcher_subscriptions = {}
|
self._subscriptions = {}
|
||||||
self._dispatcher_lock = threading.RLock()
|
self._lock = threading.RLock()
|
||||||
|
|
||||||
def handle_request(self, conn, msg):
|
def handle_request(self, conn, msg):
|
||||||
"""handles incoming request
|
"""handles incoming request
|
||||||
@ -72,7 +69,7 @@ class Dispatcher(object):
|
|||||||
"""
|
"""
|
||||||
self.log.debug('Dispatcher: handling msg: %r' % msg)
|
self.log.debug('Dispatcher: handling msg: %r' % msg)
|
||||||
# play thread safe !
|
# play thread safe !
|
||||||
with self._dispatcher_lock:
|
with self._lock:
|
||||||
reply = None
|
reply = None
|
||||||
# generate reply (coded and framed)
|
# generate reply (coded and framed)
|
||||||
msgname = msg.__class__.__name__
|
msgname = msg.__class__.__name__
|
||||||
@ -90,7 +87,7 @@ class Dispatcher(object):
|
|||||||
reply = ErrorMessage(errorclass=err.__class__.__name__,
|
reply = ErrorMessage(errorclass=err.__class__.__name__,
|
||||||
errorinfo=[repr(err), str(msg)])
|
errorinfo=[repr(err), str(msg)])
|
||||||
except (ValueError, TypeError) as err:
|
except (ValueError, TypeError) as err:
|
||||||
# self.log.exception(err)
|
# self.log.exception(err)
|
||||||
reply = ErrorMessage(errorclass='BadValue',
|
reply = ErrorMessage(errorclass='BadValue',
|
||||||
errorinfo=[repr(err), str(msg)])
|
errorinfo=[repr(err), str(msg)])
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
@ -106,118 +103,123 @@ class Dispatcher(object):
|
|||||||
def broadcast_event(self, msg, reallyall=False):
|
def broadcast_event(self, msg, reallyall=False):
|
||||||
"""broadcasts a msg to all active connections"""
|
"""broadcasts a msg to all active connections"""
|
||||||
if reallyall:
|
if reallyall:
|
||||||
listeners = self._dispatcher_connections
|
listeners = self._connections
|
||||||
else:
|
else:
|
||||||
if getattr(msg, 'command', None) is None:
|
if getattr(msg, 'command', None) is None:
|
||||||
eventname = '%s:%s' % (msg.module, msg.parameter if msg.parameter else 'value')
|
eventname = '%s:%s' % (
|
||||||
|
msg.module, msg.parameter if msg.parameter else 'value')
|
||||||
else:
|
else:
|
||||||
eventname = '%s:%s()' % (msg.module, msg.command)
|
eventname = '%s:%s()' % (msg.module, msg.command)
|
||||||
listeners = self._dispatcher_subscriptions.get(eventname, [])
|
listeners = self._subscriptions.get(eventname, [])
|
||||||
listeners += list(self._dispatcher_active_connections)
|
listeners += list(self._active_connections)
|
||||||
for conn in listeners:
|
for conn in listeners:
|
||||||
conn.queue_async_reply(msg)
|
conn.queue_async_reply(msg)
|
||||||
|
|
||||||
def announce_update(self, moduleobj, pname, pobj):
|
def announce_update(self, moduleobj, pname, pobj):
|
||||||
"""called by modules param setters to notify subscribers of new values
|
"""called by modules param setters to notify subscribers of new values
|
||||||
"""
|
"""
|
||||||
msg = Value(moduleobj.name, parameter=pname, value=pobj.value, t=pobj.timestamp)
|
msg = Value(
|
||||||
|
moduleobj.name,
|
||||||
|
parameter=pname,
|
||||||
|
value=pobj.value,
|
||||||
|
t=pobj.timestamp)
|
||||||
self.broadcast_event(msg)
|
self.broadcast_event(msg)
|
||||||
|
|
||||||
def subscribe(self, conn, modulename, pname='value'):
|
def subscribe(self, conn, modulename, pname='value'):
|
||||||
eventname = '%s:%s' % (modulename, pname)
|
eventname = '%s:%s' % (modulename, pname)
|
||||||
self._dispatcher_subscriptions.setdefault(eventname, set()).add(conn)
|
self._subscriptions.setdefault(eventname, set()).add(conn)
|
||||||
|
|
||||||
def unsubscribe(self, conn, modulename, pname='value'):
|
def unsubscribe(self, conn, modulename, pname='value'):
|
||||||
eventname = '%s:%s' % (modulename, pname)
|
eventname = '%s:%s' % (modulename, pname)
|
||||||
if eventname in self._dispatcher_subscriptions:
|
if eventname in self._subscriptions:
|
||||||
self._dispatcher_subscriptions.remove(conn)
|
self._subscriptions.remove(conn)
|
||||||
|
|
||||||
def add_connection(self, conn):
|
def add_connection(self, conn):
|
||||||
"""registers new connection"""
|
"""registers new connection"""
|
||||||
self._dispatcher_connections.append(conn)
|
self._connections.append(conn)
|
||||||
|
|
||||||
def remove_connection(self, conn):
|
def remove_connection(self, conn):
|
||||||
"""removes now longer functional connection"""
|
"""removes now longer functional connection"""
|
||||||
if conn in self._dispatcher_connections:
|
if conn in self._connections:
|
||||||
self._dispatcher_connections.remove(conn)
|
self._connections.remove(conn)
|
||||||
for _evt, conns in self._dispatcher_subscriptions.items():
|
for _evt, conns in self._subscriptions.items():
|
||||||
conns.discard(conn)
|
conns.discard(conn)
|
||||||
|
|
||||||
def activate_connection(self, conn):
|
def activate_connection(self, conn):
|
||||||
self._dispatcher_active_connections.add(conn)
|
self._active_connections.add(conn)
|
||||||
|
|
||||||
def deactivate_connection(self, conn):
|
def deactivate_connection(self, conn):
|
||||||
self._dispatcher_active_connections.discard(conn)
|
self._active_connections.discard(conn)
|
||||||
|
|
||||||
def register_module(self, moduleobj, modulename, export=True):
|
def register_module(self, moduleobj, modulename, export=True):
|
||||||
self.log.debug('registering module %r as %s (export=%r)' %
|
self.log.debug('registering module %r as %s (export=%r)' %
|
||||||
(moduleobj, modulename, export))
|
(moduleobj, modulename, export))
|
||||||
self._dispatcher_modules[modulename] = moduleobj
|
self._modules[modulename] = moduleobj
|
||||||
if export:
|
if export:
|
||||||
self._dispatcher_export.append(modulename)
|
self._export.append(modulename)
|
||||||
|
|
||||||
def get_module(self, modulename):
|
def get_module(self, modulename):
|
||||||
module = self._dispatcher_modules.get(modulename, modulename)
|
module = self._modules.get(modulename, modulename)
|
||||||
return module
|
return module
|
||||||
|
|
||||||
def remove_module(self, modulename_or_obj):
|
def remove_module(self, modulename_or_obj):
|
||||||
moduleobj = self.get_module(modulename_or_obj) or modulename_or_obj
|
moduleobj = self.get_module(modulename_or_obj) or modulename_or_obj
|
||||||
modulename = moduleobj.name
|
modulename = moduleobj.name
|
||||||
if modulename in self._dispatcher_export:
|
if modulename in self._export:
|
||||||
self._dispatcher_export.remove(modulename)
|
self._export.remove(modulename)
|
||||||
self._dispatcher_modules.pop(modulename)
|
self._modules.pop(modulename)
|
||||||
# XXX: also clean _dispatcher_subscriptions
|
# XXX: also clean _subscriptions
|
||||||
|
|
||||||
def list_module_names(self):
|
def list_module_names(self):
|
||||||
# return a copy of our list
|
# return a copy of our list
|
||||||
return self._dispatcher_export[:]
|
return self._export[:]
|
||||||
|
|
||||||
def list_modules(self):
|
|
||||||
dn = []
|
|
||||||
dd = {}
|
|
||||||
for modulename in self._dispatcher_export:
|
|
||||||
dn.append(modulename)
|
|
||||||
module = self.get_module(modulename)
|
|
||||||
descriptive_data = {
|
|
||||||
'class': module.__class__.__name__,
|
|
||||||
#'bases': module.__bases__,
|
|
||||||
'parameters': module.PARAMS.keys(),
|
|
||||||
'commands': module.CMDS.keys(),
|
|
||||||
# XXX: what else?
|
|
||||||
}
|
|
||||||
dd[modulename] = descriptive_data
|
|
||||||
return dn, dd
|
|
||||||
|
|
||||||
def get_descriptive_data(self):
|
|
||||||
# XXX: be lazy and cache this?
|
|
||||||
result = {}
|
|
||||||
for modulename in self._dispatcher_export:
|
|
||||||
module = self.get_module(modulename)
|
|
||||||
dd = {'class' : module.__class__.__name__,
|
|
||||||
'bases' : [b.__name__ for b in module.__class__.__bases__],
|
|
||||||
'parameters': dict((pn,po.as_dict()) for pn,po in module.PARAMS.items()),
|
|
||||||
'commands': dict((cn,co.as_dict()) for cn,co in module.CMDS.items()),
|
|
||||||
'baseclass' : 'Readable',
|
|
||||||
}
|
|
||||||
result.setdefault('modules', {})[modulename] = dd
|
|
||||||
result['equipment_id'] = self.equipment_id
|
|
||||||
# XXX: what else?
|
|
||||||
return result
|
|
||||||
|
|
||||||
def list_module_params(self, modulename):
|
def list_module_params(self, modulename):
|
||||||
self.log.debug('list_module_params(%r)' % modulename)
|
self.log.debug('list_module_params(%r)' % modulename)
|
||||||
if modulename in self._dispatcher_export:
|
if modulename in self._export:
|
||||||
# XXX: omit export=False params!
|
# omit export=False params!
|
||||||
res = {}
|
res = {}
|
||||||
for paramname, param in self.get_module(modulename).PARAMS.items():
|
for paramname, param in self.get_module(modulename).PARAMS.items():
|
||||||
if param.export == True:
|
if param.export:
|
||||||
res[paramname] = param
|
res[paramname] = param.as_dict()
|
||||||
self.log.debug('list params for module %s -> %r' %
|
self.log.debug('list params for module %s -> %r' %
|
||||||
(modulename, res))
|
(modulename, res))
|
||||||
return res
|
return res
|
||||||
self.log.debug('-> module is not to be exported!')
|
self.log.debug('-> module is not to be exported!')
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
def list_module_cmds(self, modulename):
|
||||||
|
self.log.debug('list_module_cmds(%r)' % modulename)
|
||||||
|
if modulename in self._export:
|
||||||
|
# omit export=False params!
|
||||||
|
res = {}
|
||||||
|
for cmdname, cmdobj in self.get_module(modulename).CMDS.items():
|
||||||
|
res[cmdname] = cmdobj.as_dict()
|
||||||
|
self.log.debug('list cmds for module %s -> %r' %
|
||||||
|
(modulename, res))
|
||||||
|
return res
|
||||||
|
self.log.debug('-> module is not to be exported!')
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def get_descriptive_data(self):
|
||||||
|
# XXX: be lazy and cache this?
|
||||||
|
result = {'modules':{}}
|
||||||
|
for modulename in self._export:
|
||||||
|
module = self.get_module(modulename)
|
||||||
|
# some of these need rework !
|
||||||
|
dd = {'class': module.__class__.__name__,
|
||||||
|
'bases': [b.__name__ for b in module.__class__.__bases__],
|
||||||
|
'parameters': self.list_module_params(modulename),
|
||||||
|
'commands': self.list_module_cmds(modulename),
|
||||||
|
'baseclass': 'Readable',
|
||||||
|
}
|
||||||
|
result['modules'][modulename] = dd
|
||||||
|
result['equipment_id'] = self.equipment_id
|
||||||
|
result['firmware'] = 'The SECoP playground'
|
||||||
|
result['version'] = "2016.12"
|
||||||
|
# XXX: what else?
|
||||||
|
return result
|
||||||
|
|
||||||
def _execute_command(self, modulename, command, arguments=None):
|
def _execute_command(self, modulename, command, arguments=None):
|
||||||
if arguments is None:
|
if arguments is None:
|
||||||
arguments = []
|
arguments = []
|
||||||
@ -230,13 +232,22 @@ class Dispatcher(object):
|
|||||||
if cmdspec is None:
|
if cmdspec is None:
|
||||||
raise NoSuchCommandError(module=modulename, command=command)
|
raise NoSuchCommandError(module=modulename, command=command)
|
||||||
if len(cmdspec.arguments) != len(arguments):
|
if len(cmdspec.arguments) != len(arguments):
|
||||||
raise BadValueError(module=modulename, command=command, reason='Wrong number of arguments!')
|
raise BadValueError(
|
||||||
|
module=modulename,
|
||||||
|
command=command,
|
||||||
|
reason='Wrong number of arguments!')
|
||||||
|
|
||||||
# now call func and wrap result as value
|
# now call func and wrap result as value
|
||||||
# note: exceptions are handled in handle_request, not here!
|
# note: exceptions are handled in handle_request, not here!
|
||||||
func = getattr(moduleobj, 'do'+command)
|
func = getattr(moduleobj, 'do' + command)
|
||||||
res = func(*arguments)
|
res = func(*arguments)
|
||||||
res = CommandReply(module=modulename, command=command, result=[res, dict(t=time.time())])
|
res = CommandReply(
|
||||||
|
module=modulename,
|
||||||
|
command=command,
|
||||||
|
result=[
|
||||||
|
res,
|
||||||
|
dict(
|
||||||
|
t=time.time())])
|
||||||
# res = Value(modulename, command=command, value=func(*arguments), t=time.time())
|
# res = Value(modulename, command=command, value=func(*arguments), t=time.time())
|
||||||
return res
|
return res
|
||||||
|
|
||||||
@ -258,8 +269,16 @@ class Dispatcher(object):
|
|||||||
else:
|
else:
|
||||||
setattr(moduleobj, pname, value)
|
setattr(moduleobj, pname, value)
|
||||||
if pobj.timestamp:
|
if pobj.timestamp:
|
||||||
return WriteReply(module=modulename, parameter=pname, value=[pobj.value, dict(t=pobj.timestamp)])
|
return WriteReply(
|
||||||
return WriteReply(module=modulename, parameter=pname, value=[pobj.value, {}])
|
module=modulename, parameter=pname, value=[
|
||||||
|
pobj.value, dict(
|
||||||
|
t=pobj.timestamp)])
|
||||||
|
return WriteReply(
|
||||||
|
module=modulename,
|
||||||
|
parameter=pname,
|
||||||
|
value=[
|
||||||
|
pobj.value,
|
||||||
|
{}])
|
||||||
|
|
||||||
def _getParamValue(self, modulename, pname):
|
def _getParamValue(self, modulename, pname):
|
||||||
moduleobj = self.get_module(modulename)
|
moduleobj = self.get_module(modulename)
|
||||||
@ -276,10 +295,13 @@ class Dispatcher(object):
|
|||||||
# note: exceptions are handled in handle_request, not here!
|
# note: exceptions are handled in handle_request, not here!
|
||||||
readfunc()
|
readfunc()
|
||||||
if pobj.timestamp:
|
if pobj.timestamp:
|
||||||
return Value(modulename, parameter=pname, value=pobj.value, t=pobj.timestamp)
|
return Value(
|
||||||
|
modulename,
|
||||||
|
parameter=pname,
|
||||||
|
value=pobj.value,
|
||||||
|
t=pobj.timestamp)
|
||||||
return Value(modulename, parameter=pname, value=pobj.value)
|
return Value(modulename, parameter=pname, value=pobj.value)
|
||||||
|
|
||||||
|
|
||||||
# now the (defined) handlers for the different requests
|
# now the (defined) handlers for the different requests
|
||||||
def handle_Help(self, conn, msg):
|
def handle_Help(self, conn, msg):
|
||||||
return HelpMessage()
|
return HelpMessage()
|
||||||
@ -289,21 +311,24 @@ class Dispatcher(object):
|
|||||||
|
|
||||||
def handle_Describe(self, conn, msg):
|
def handle_Describe(self, conn, msg):
|
||||||
# XXX:collect descriptive data
|
# XXX:collect descriptive data
|
||||||
return DescribeReply(equipment_id = self.equipment_id, description = self.get_descriptive_data())
|
return DescribeReply(
|
||||||
|
equipment_id=self.equipment_id,
|
||||||
|
description=self.get_descriptive_data())
|
||||||
|
|
||||||
def handle_Poll(self, conn, msg):
|
def handle_Poll(self, conn, msg):
|
||||||
# XXX: trigger polling and force sending event
|
# XXX: trigger polling and force sending event
|
||||||
res = self._getParamValue(msg.module, msg.parameter or 'value')
|
res = self._getParamValue(msg.module, msg.parameter or 'value')
|
||||||
#self.broadcast_event(res)
|
# self.broadcast_event(res)
|
||||||
if conn in self._dispatcher_active_connections:
|
if conn in self._active_connections:
|
||||||
return None # already send to myself
|
return None # already send to myself
|
||||||
return res # send reply to inactive conns
|
return res # send reply to inactive conns
|
||||||
|
|
||||||
def handle_Write(self, conn, msg):
|
def handle_Write(self, conn, msg):
|
||||||
# notify all by sending WriteReply
|
# notify all by sending WriteReply
|
||||||
#msg1 = WriteReply(**msg.as_dict())
|
#msg1 = WriteReply(**msg.as_dict())
|
||||||
#self.broadcast_event(msg1)
|
# self.broadcast_event(msg1)
|
||||||
# try to actually write XXX: should this be done asyncron? we could just return the reply in that case
|
# try to actually write XXX: should this be done asyncron? we could
|
||||||
|
# just return the reply in that case
|
||||||
if msg.parameter:
|
if msg.parameter:
|
||||||
res = self._setParamValue(msg.module, msg.parameter, msg.value)
|
res = self._setParamValue(msg.module, msg.parameter, msg.value)
|
||||||
else:
|
else:
|
||||||
@ -312,21 +337,22 @@ class Dispatcher(object):
|
|||||||
raise ReadonlyError(module=msg.module, parameter=None)
|
raise ReadonlyError(module=msg.module, parameter=None)
|
||||||
res = self._setParamValue(msg.module, 'target', msg.value)
|
res = self._setParamValue(msg.module, 'target', msg.value)
|
||||||
res.parameter = 'target'
|
res.parameter = 'target'
|
||||||
#self.broadcast_event(res)
|
# self.broadcast_event(res)
|
||||||
if conn in self._dispatcher_active_connections:
|
if conn in self._active_connections:
|
||||||
return None # already send to myself
|
return None # already send to myself
|
||||||
return res # send reply to inactive conns
|
return res # send reply to inactive conns
|
||||||
|
|
||||||
def handle_Command(self, conn, msg):
|
def handle_Command(self, conn, msg):
|
||||||
# notify all by sending CommandReply
|
# notify all by sending CommandReply
|
||||||
#msg1 = CommandReply(**msg.as_dict())
|
#msg1 = CommandReply(**msg.as_dict())
|
||||||
#self.broadcast_event(msg1)
|
# self.broadcast_event(msg1)
|
||||||
# XXX: should this be done asyncron? we could just return the reply in that case
|
# XXX: should this be done asyncron? we could just return the reply in
|
||||||
|
# that case
|
||||||
|
|
||||||
# try to actually execute command
|
# try to actually execute command
|
||||||
res = self._execute_command(msg.module, msg.command, msg.arguments)
|
res = self._execute_command(msg.module, msg.command, msg.arguments)
|
||||||
#self.broadcast_event(res)
|
# self.broadcast_event(res)
|
||||||
#if conn in self._dispatcher_active_connections:
|
# if conn in self._active_connections:
|
||||||
# return None # already send to myself
|
# return None # already send to myself
|
||||||
return res # send reply to inactive conns
|
return res # send reply to inactive conns
|
||||||
|
|
||||||
@ -336,7 +362,7 @@ class Dispatcher(object):
|
|||||||
def handle_Activate(self, conn, msg):
|
def handle_Activate(self, conn, msg):
|
||||||
self.activate_connection(conn)
|
self.activate_connection(conn)
|
||||||
# easy approach: poll all values...
|
# easy approach: poll all values...
|
||||||
for modulename, moduleobj in self._dispatcher_modules.items():
|
for modulename, moduleobj in self._modules.items():
|
||||||
for pname, pobj in moduleobj.PARAMS.items():
|
for pname, pobj in moduleobj.PARAMS.items():
|
||||||
# WARNING: THIS READS ALL PARAMS FROM HW!
|
# WARNING: THIS READS ALL PARAMS FROM HW!
|
||||||
# XXX: should we send the cached values instead? (pbj.value)
|
# XXX: should we send the cached values instead? (pbj.value)
|
||||||
@ -349,7 +375,7 @@ class Dispatcher(object):
|
|||||||
res = Value(module=modulename, parameter=pname,
|
res = Value(module=modulename, parameter=pname,
|
||||||
value=pobj.value, t=pobj.timestamp,
|
value=pobj.value, t=pobj.timestamp,
|
||||||
unit=pobj.unit)
|
unit=pobj.unit)
|
||||||
if res.value != Ellipsis: # means we do not have a value at all so skip this
|
if res.value != Ellipsis: # means we do not have a value at all so skip this
|
||||||
self.broadcast_event(res)
|
self.broadcast_event(res)
|
||||||
conn.queue_async_reply(ActivateReply(**msg.as_dict()))
|
conn.queue_async_reply(ActivateReply(**msg.as_dict()))
|
||||||
return None
|
return None
|
||||||
@ -369,6 +395,4 @@ class Dispatcher(object):
|
|||||||
"""
|
"""
|
||||||
self.log.error('IGN: got unhandled request %s' % msg)
|
self.log.error('IGN: got unhandled request %s' % msg)
|
||||||
return ErrorMessage(errorclass="InternalError",
|
return ErrorMessage(errorclass="InternalError",
|
||||||
errorstring = 'Got Unhandled Request %r' % msg)
|
errorstring='Got Unhandled Request %r' % msg)
|
||||||
|
|
||||||
|
|
||||||
|
@ -98,7 +98,7 @@ class DemoEncoder(MessageEncoder):
|
|||||||
MessageEncoder.__init__(self, *args, **kwds)
|
MessageEncoder.__init__(self, *args, **kwds)
|
||||||
self.result = [] # for decoding
|
self.result = [] # for decoding
|
||||||
self.expect_lines = 1
|
self.expect_lines = 1
|
||||||
#self.tests()
|
# self.tests()
|
||||||
|
|
||||||
def encode(self, msg):
|
def encode(self, msg):
|
||||||
"""msg object -> transport layer message"""
|
"""msg object -> transport layer message"""
|
||||||
@ -179,8 +179,13 @@ class DemoEncoder(MessageEncoder):
|
|||||||
return '\n'.join(result)
|
return '\n'.join(result)
|
||||||
|
|
||||||
if isinstance(msg, ErrorMessage):
|
if isinstance(msg, ErrorMessage):
|
||||||
return ('%s %s' % (devspec(msg, 'error %s' %
|
return (
|
||||||
msg.errortype), msg.errorstring)).strip()
|
'%s %s' %
|
||||||
|
(devspec(
|
||||||
|
msg,
|
||||||
|
'error %s' %
|
||||||
|
msg.errortype),
|
||||||
|
msg.errorstring)).strip()
|
||||||
|
|
||||||
return 'Can not handle object %r!' % msg
|
return 'Can not handle object %r!' % msg
|
||||||
|
|
||||||
@ -292,7 +297,11 @@ class DemoEncoder(MessageEncoder):
|
|||||||
# construct messageobj
|
# construct messageobj
|
||||||
if msgtype in MESSAGE:
|
if msgtype in MESSAGE:
|
||||||
return MESSAGE[msgtype](
|
return MESSAGE[msgtype](
|
||||||
devs=devs, pars=pars, props=props, result=result, **mgroups)
|
devs=devs,
|
||||||
|
pars=pars,
|
||||||
|
props=props,
|
||||||
|
result=result,
|
||||||
|
**mgroups)
|
||||||
|
|
||||||
return ErrorMessage(errortype="SyntaxError",
|
return ErrorMessage(errortype="SyntaxError",
|
||||||
errorstring="Can't handle %r" % encoded)
|
errorstring="Can't handle %r" % encoded)
|
||||||
|
@ -34,38 +34,47 @@ import ast
|
|||||||
import re
|
import re
|
||||||
import json
|
import json
|
||||||
|
|
||||||
# each message is like <messagetype> [ \space <messageargs> [ \space <json> ]] \lf
|
# each message is like <messagetype> [ \space <messageargs> [ \space
|
||||||
|
# <json> ]] \lf
|
||||||
|
|
||||||
# note: the regex allow <> for spec for testing only!
|
# note: the regex allow <> for spec for testing only!
|
||||||
DEMO_RE = re.compile(
|
DEMO_RE = re.compile(
|
||||||
r"""^(?P<msgtype>[\*\?\w]+)(?:\s(?P<spec>[\w:<>]+)(?:\s(?P<json>.*))?)?$""", re.X)
|
r"""^(?P<msgtype>[\*\?\w]+)(?:\s(?P<spec>[\w:<>]+)(?:\s(?P<json>.*))?)?$""",
|
||||||
|
re.X)
|
||||||
|
|
||||||
#"""
|
#"""
|
||||||
# messagetypes:
|
# messagetypes:
|
||||||
IDENTREQUEST = '*IDN?' # literal
|
IDENTREQUEST = '*IDN?' # literal
|
||||||
IDENTREPLY = 'SECoP, SECoPTCP, V2016-11-30, rc1' # literal! first part 'SECoP' is fixed!
|
# literal! first part 'SECoP' is fixed!
|
||||||
|
IDENTREPLY = 'SECoP, SECoPTCP, V2016-11-30, rc1'
|
||||||
DESCRIPTIONSREQUEST = 'describe' # literal
|
DESCRIPTIONSREQUEST = 'describe' # literal
|
||||||
DESCRIPTIONREPLY = 'describing' # +<id> +json
|
DESCRIPTIONREPLY = 'describing' # +<id> +json
|
||||||
ENABLEEVENTSREQUEST = 'activate' # literal
|
ENABLEEVENTSREQUEST = 'activate' # literal
|
||||||
ENABLEEVENTSREPLY = 'active' # literal, is end-of-initial-data-transfer
|
ENABLEEVENTSREPLY = 'active' # literal, is end-of-initial-data-transfer
|
||||||
DISABLEEVENTSREQUEST = 'deactivate' # literal
|
DISABLEEVENTSREQUEST = 'deactivate' # literal
|
||||||
DISABLEEVENTSREPLY = 'inactive' # literal
|
DISABLEEVENTSREPLY = 'inactive' # literal
|
||||||
COMMANDREQUEST = 'do' # +module:command +json args (if needed)
|
COMMANDREQUEST = 'do' # +module:command +json args (if needed)
|
||||||
COMMANDREPLY = 'done' # +module:command +json args (if needed) # send after the command finished !
|
# +module:command +json args (if needed) # send after the command finished !
|
||||||
WRITEREQUEST = 'change' # +module[:parameter] +json_value -> NO direct reply, calls TRIGGER internally!
|
COMMANDREPLY = 'done'
|
||||||
WRITEREPLY = 'changed' # +module[:parameter] +json_value # send with the read back value
|
# +module[:parameter] +json_value -> NO direct reply, calls TRIGGER internally!
|
||||||
TRIGGERREQUEST = 'poll' # +module[:parameter] -> NO direct reply, calls TRIGGER internally!
|
WRITEREQUEST = 'change'
|
||||||
|
# +module[:parameter] +json_value # send with the read back value
|
||||||
|
WRITEREPLY = 'changed'
|
||||||
|
# +module[:parameter] -> NO direct reply, calls TRIGGER internally!
|
||||||
|
TRIGGERREQUEST = 'poll'
|
||||||
EVENT = 'event' # +module[:parameter] +json_value (value, qualifiers_as_dict)
|
EVENT = 'event' # +module[:parameter] +json_value (value, qualifiers_as_dict)
|
||||||
HEARTBEATREQUEST = 'ping' # +nonce_without_space
|
HEARTBEATREQUEST = 'ping' # +nonce_without_space
|
||||||
HEARTBEATREPLY = 'pong' # +nonce_without_space
|
HEARTBEATREPLY = 'pong' # +nonce_without_space
|
||||||
ERRORREPLY = 'ERROR' # +errorclass +json_extended_info
|
ERRORREPLY = 'ERROR' # +errorclass +json_extended_info
|
||||||
HELPREQUEST = 'help' # literal
|
HELPREQUEST = 'help' # literal
|
||||||
HELPREPLY = 'helping' # +line number +json_text
|
HELPREPLY = 'helping' # +line number +json_text
|
||||||
ERRORCLASSES = ['NoSuchDevice', 'NoSuchParameter', 'NoSuchCommand',
|
ERRORCLASSES = ['NoSuchDevice', 'NoSuchParameter', 'NoSuchCommand',
|
||||||
'CommandFailed', 'ReadOnly', 'BadValue', 'CommunicationFailed',
|
'CommandFailed', 'ReadOnly', 'BadValue', 'CommunicationFailed',
|
||||||
'IsBusy', 'IsError', 'SyntaxError', 'InternalError',
|
'IsBusy', 'IsError', 'SyntaxError', 'InternalError',
|
||||||
'CommandRunning', 'Disabled',]
|
'CommandRunning', 'Disabled', ]
|
||||||
# note: above strings need to be unique in the sense, that none is/or starts with another
|
# note: above strings need to be unique in the sense, that none is/or
|
||||||
|
# starts with another
|
||||||
|
|
||||||
|
|
||||||
def encode_value_data(vobj):
|
def encode_value_data(vobj):
|
||||||
q = vobj.qualifiers.copy()
|
q = vobj.qualifiers.copy()
|
||||||
@ -73,55 +82,57 @@ def encode_value_data(vobj):
|
|||||||
q['t'] = format_time(q['t'])
|
q['t'] = format_time(q['t'])
|
||||||
return vobj.value, q
|
return vobj.value, q
|
||||||
|
|
||||||
|
|
||||||
class DemoEncoder(MessageEncoder):
|
class DemoEncoder(MessageEncoder):
|
||||||
# map of msg to msgtype string as defined above.
|
# map of msg to msgtype string as defined above.
|
||||||
ENCODEMAP = {
|
ENCODEMAP = {
|
||||||
IdentifyRequest : (IDENTREQUEST,),
|
IdentifyRequest: (IDENTREQUEST,),
|
||||||
IdentifyReply : (IDENTREPLY,),
|
IdentifyReply: (IDENTREPLY,),
|
||||||
DescribeRequest : (DESCRIPTIONSREQUEST,),
|
DescribeRequest: (DESCRIPTIONSREQUEST,),
|
||||||
DescribeReply : (DESCRIPTIONREPLY, 'equipment_id', 'description',),
|
DescribeReply: (DESCRIPTIONREPLY, 'equipment_id', 'description',),
|
||||||
ActivateRequest : (ENABLEEVENTSREQUEST,),
|
ActivateRequest: (ENABLEEVENTSREQUEST,),
|
||||||
ActivateReply : (ENABLEEVENTSREPLY,),
|
ActivateReply: (ENABLEEVENTSREPLY,),
|
||||||
DeactivateRequest: (DISABLEEVENTSREQUEST,),
|
DeactivateRequest: (DISABLEEVENTSREQUEST,),
|
||||||
DeactivateReply : (DISABLEEVENTSREPLY,),
|
DeactivateReply: (DISABLEEVENTSREPLY,),
|
||||||
CommandRequest : (COMMANDREQUEST, lambda msg: "%s:%s" % (msg.module, msg.command), 'arguments',),
|
CommandRequest: (COMMANDREQUEST, lambda msg: "%s:%s" % (msg.module, msg.command), 'arguments',),
|
||||||
CommandReply : (COMMANDREPLY, lambda msg: "%s:%s" % (msg.module, msg.command), 'result',),
|
CommandReply: (COMMANDREPLY, lambda msg: "%s:%s" % (msg.module, msg.command), 'result',),
|
||||||
WriteRequest : (WRITEREQUEST, lambda msg: "%s:%s" % (msg.module, msg.parameter) if msg.parameter else msg.module, 'value',),
|
WriteRequest: (WRITEREQUEST, lambda msg: "%s:%s" % (msg.module, msg.parameter) if msg.parameter else msg.module, 'value',),
|
||||||
WriteReply : (WRITEREPLY, lambda msg: "%s:%s" % (msg.module, msg.parameter) if msg.parameter else msg.module, 'value',),
|
WriteReply: (WRITEREPLY, lambda msg: "%s:%s" % (msg.module, msg.parameter) if msg.parameter else msg.module, 'value',),
|
||||||
PollRequest : (TRIGGERREQUEST, lambda msg: "%s:%s" % (msg.module, msg.parameter) if msg.parameter else msg.module, ),
|
PollRequest: (TRIGGERREQUEST, lambda msg: "%s:%s" % (msg.module, msg.parameter) if msg.parameter else msg.module, ),
|
||||||
HeartbeatRequest : (HEARTBEATREQUEST, 'nonce',),
|
HeartbeatRequest: (HEARTBEATREQUEST, 'nonce',),
|
||||||
HeartbeatReply : (HEARTBEATREPLY, 'nonce',),
|
HeartbeatReply: (HEARTBEATREPLY, 'nonce',),
|
||||||
HelpMessage: (HELPREQUEST, ),
|
HelpMessage: (HELPREQUEST, ),
|
||||||
ErrorMessage : (ERRORREPLY, 'errorclass', 'errorinfo',),
|
ErrorMessage: (ERRORREPLY, 'errorclass', 'errorinfo',),
|
||||||
Value: (EVENT, lambda msg: "%s:%s" % (msg.module, msg.parameter or (msg.command+'()'))
|
Value: (EVENT, lambda msg: "%s:%s" % (msg.module, msg.parameter or (msg.command + '()'))
|
||||||
if msg.parameter or msg.command else msg.module,
|
if msg.parameter or msg.command else msg.module,
|
||||||
encode_value_data,),
|
encode_value_data,),
|
||||||
}
|
}
|
||||||
DECODEMAP = {
|
DECODEMAP = {
|
||||||
IDENTREQUEST : lambda spec, data: IdentifyRequest(),
|
IDENTREQUEST: lambda spec, data: IdentifyRequest(),
|
||||||
IDENTREPLY : lambda spec, data: IdentifyReply(encoded), # handled specially, listed here for completeness
|
# handled specially, listed here for completeness
|
||||||
DESCRIPTIONSREQUEST : lambda spec, data: DescribeRequest(),
|
IDENTREPLY: lambda spec, data: IdentifyReply(encoded),
|
||||||
DESCRIPTIONREPLY : lambda spec, data: DescribeReply(equipment_id=spec[0], description=data),
|
DESCRIPTIONSREQUEST: lambda spec, data: DescribeRequest(),
|
||||||
ENABLEEVENTSREQUEST : lambda spec, data: ActivateRequest(),
|
DESCRIPTIONREPLY: lambda spec, data: DescribeReply(equipment_id=spec[0], description=data),
|
||||||
ENABLEEVENTSREPLY: lambda spec, data:ActivateReply(),
|
ENABLEEVENTSREQUEST: lambda spec, data: ActivateRequest(),
|
||||||
DISABLEEVENTSREQUEST: lambda spec, data:DeactivateRequest(),
|
ENABLEEVENTSREPLY: lambda spec, data: ActivateReply(),
|
||||||
DISABLEEVENTSREPLY: lambda spec, data:DeactivateReply(),
|
DISABLEEVENTSREQUEST: lambda spec, data: DeactivateRequest(),
|
||||||
COMMANDREQUEST: lambda spec, data:CommandRequest(module=spec[0], command=spec[1], arguments=data),
|
DISABLEEVENTSREPLY: lambda spec, data: DeactivateReply(),
|
||||||
|
COMMANDREQUEST: lambda spec, data: CommandRequest(module=spec[0], command=spec[1], arguments=data),
|
||||||
COMMANDREPLY: lambda spec, data: CommandReply(module=spec[0], command=spec[1], result=data),
|
COMMANDREPLY: lambda spec, data: CommandReply(module=spec[0], command=spec[1], result=data),
|
||||||
WRITEREQUEST: lambda spec, data: WriteRequest(module=spec[0], parameter=spec[1], value=data),
|
WRITEREQUEST: lambda spec, data: WriteRequest(module=spec[0], parameter=spec[1], value=data),
|
||||||
WRITEREPLY:lambda spec, data:WriteReply(module=spec[0], parameter=spec[1], value=data),
|
WRITEREPLY: lambda spec, data: WriteReply(module=spec[0], parameter=spec[1], value=data),
|
||||||
TRIGGERREQUEST:lambda spec, data:PollRequest(module=spec[0], parameter=spec[1]),
|
TRIGGERREQUEST: lambda spec, data: PollRequest(module=spec[0], parameter=spec[1]),
|
||||||
HEARTBEATREQUEST:lambda spec, data:HeartbeatRequest(nonce=spec[0]),
|
HEARTBEATREQUEST: lambda spec, data: HeartbeatRequest(nonce=spec[0]),
|
||||||
HEARTBEATREPLY:lambda spec, data:HeartbeatReply(nonce=spec[0]),
|
HEARTBEATREPLY: lambda spec, data: HeartbeatReply(nonce=spec[0]),
|
||||||
HELPREQUEST: lambda spec, data:HelpMessage(),
|
HELPREQUEST: lambda spec, data: HelpMessage(),
|
||||||
# HELPREPLY: lambda spec, data:None, # ignore this
|
# HELPREPLY: lambda spec, data:None, # ignore this
|
||||||
ERRORREPLY:lambda spec, data:ErrorMessage(errorclass=spec[0], errorinfo=data),
|
ERRORREPLY: lambda spec, data: ErrorMessage(errorclass=spec[0], errorinfo=data),
|
||||||
EVENT:lambda spec, data:Value(module=spec[0], parameter=spec[1], value=data[0], qualifiers=data[1] if len(data)>1 else {}),
|
EVENT: lambda spec, data: Value(module=spec[0], parameter=spec[1], value=data[0], qualifiers=data[1] if len(data) > 1 else {}),
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, *args, **kwds):
|
def __init__(self, *args, **kwds):
|
||||||
MessageEncoder.__init__(self, *args, **kwds)
|
MessageEncoder.__init__(self, *args, **kwds)
|
||||||
#self.tests()
|
# self.tests()
|
||||||
|
|
||||||
def encode(self, msg):
|
def encode(self, msg):
|
||||||
"""msg object -> transport layer message"""
|
"""msg object -> transport layer message"""
|
||||||
@ -136,20 +147,21 @@ class DemoEncoder(MessageEncoder):
|
|||||||
'%s <nonce>' to request a heartbeat response
|
'%s <nonce>' to request a heartbeat response
|
||||||
'%s' to activate async updates
|
'%s' to activate async updates
|
||||||
'%s' to deactivate updates
|
'%s' to deactivate updates
|
||||||
""" %(IDENTREQUEST, DESCRIPTIONSREQUEST, TRIGGERREQUEST,
|
""" % (IDENTREQUEST, DESCRIPTIONSREQUEST, TRIGGERREQUEST,
|
||||||
WRITEREQUEST, COMMANDREQUEST, HEARTBEATREQUEST,
|
WRITEREQUEST, COMMANDREQUEST, HEARTBEATREQUEST,
|
||||||
ENABLEEVENTSREQUEST, DISABLEEVENTSREQUEST)
|
ENABLEEVENTSREQUEST, DISABLEEVENTSREQUEST)
|
||||||
return '\n'.join('%s %d %s' %(HELPREPLY, i+1, l.strip()) for i,l in enumerate(text.split('\n')[:-1]))
|
return '\n'.join('%s %d %s' % (HELPREPLY, i + 1, l.strip())
|
||||||
|
for i, l in enumerate(text.split('\n')[:-1]))
|
||||||
for msgcls, parts in self.ENCODEMAP.items():
|
for msgcls, parts in self.ENCODEMAP.items():
|
||||||
if isinstance(msg, msgcls):
|
if isinstance(msg, msgcls):
|
||||||
# resolve lambdas
|
# resolve lambdas
|
||||||
parts = [parts[0]] + [p(msg) if callable(p) else getattr(msg, p) for p in parts[1:]]
|
parts = [parts[0]] + [p(msg) if callable(p)
|
||||||
|
else getattr(msg, p) for p in parts[1:]]
|
||||||
if len(parts) > 1:
|
if len(parts) > 1:
|
||||||
parts[1] = str(parts[1])
|
parts[1] = str(parts[1])
|
||||||
if len(parts) == 3:
|
if len(parts) == 3:
|
||||||
parts[2] = json.dumps(parts[2])
|
parts[2] = json.dumps(parts[2])
|
||||||
return ' '.join(parts)
|
return ' '.join(parts)
|
||||||
|
|
||||||
|
|
||||||
def decode(self, encoded):
|
def decode(self, encoded):
|
||||||
# first check beginning
|
# first check beginning
|
||||||
@ -158,17 +170,17 @@ class DemoEncoder(MessageEncoder):
|
|||||||
print repr(encoded), repr(IDENTREPLY)
|
print repr(encoded), repr(IDENTREPLY)
|
||||||
if encoded == IDENTREPLY: # XXX:better just check the first 2 parts...
|
if encoded == IDENTREPLY: # XXX:better just check the first 2 parts...
|
||||||
return IdentifyReply(version_string=encoded)
|
return IdentifyReply(version_string=encoded)
|
||||||
|
|
||||||
return HelpMessage()
|
return HelpMessage()
|
||||||
return ErrorMessage(errorclass='SyntaxError',
|
return ErrorMessage(errorclass='SyntaxError',
|
||||||
errorinfo='Regex did not match!',
|
errorinfo='Regex did not match!',
|
||||||
is_request=True)
|
is_request=True)
|
||||||
msgtype, msgspec, data = match.groups()
|
msgtype, msgspec, data = match.groups()
|
||||||
if msgspec is None and data:
|
if msgspec is None and data:
|
||||||
return ErrorMessage(errorclass='InternalError',
|
return ErrorMessage(errorclass='InternalError',
|
||||||
errorinfo='Regex matched json, but not spec!',
|
errorinfo='Regex matched json, but not spec!',
|
||||||
is_request=True)
|
is_request=True)
|
||||||
|
|
||||||
if msgtype in self.DECODEMAP:
|
if msgtype in self.DECODEMAP:
|
||||||
if msgspec and ':' in msgspec:
|
if msgspec and ':' in msgspec:
|
||||||
msgspec = msgspec.split(':', 1)
|
msgspec = msgspec.split(':', 1)
|
||||||
@ -181,16 +193,28 @@ class DemoEncoder(MessageEncoder):
|
|||||||
return ErrorMessage(errorclass='BadValue',
|
return ErrorMessage(errorclass='BadValue',
|
||||||
errorinfo=[repr(err), str(encoded)])
|
errorinfo=[repr(err), str(encoded)])
|
||||||
return self.DECODEMAP[msgtype](msgspec, data)
|
return self.DECODEMAP[msgtype](msgspec, data)
|
||||||
return ErrorMessage(errorclass='SyntaxError',
|
return ErrorMessage(
|
||||||
errorinfo='%r: No Such Messagetype defined!' % encoded,
|
errorclass='SyntaxError',
|
||||||
is_request=True)
|
errorinfo='%r: No Such Messagetype defined!' %
|
||||||
|
encoded,
|
||||||
|
is_request=True)
|
||||||
|
|
||||||
def tests(self):
|
def tests(self):
|
||||||
print "---- Testing encoding -----"
|
print "---- Testing encoding -----"
|
||||||
for msgclass, parts in sorted(self.ENCODEMAP.items()):
|
for msgclass, parts in sorted(self.ENCODEMAP.items()):
|
||||||
print msgclass
|
print msgclass
|
||||||
e=self.encode(msgclass(module='<module>',parameter='<paramname>',value=2.718,equipment_id='<id>',description='descriptive data',command='<cmd>',arguments='<arguments>',nonce='<nonce>',errorclass='InternalError',errorinfo='nix'))
|
e = self.encode(
|
||||||
|
msgclass(
|
||||||
|
module='<module>',
|
||||||
|
parameter='<paramname>',
|
||||||
|
value=2.718,
|
||||||
|
equipment_id='<id>',
|
||||||
|
description='descriptive data',
|
||||||
|
command='<cmd>',
|
||||||
|
arguments='<arguments>',
|
||||||
|
nonce='<nonce>',
|
||||||
|
errorclass='InternalError',
|
||||||
|
errorinfo='nix'))
|
||||||
print e
|
print e
|
||||||
print self.decode(e)
|
print self.decode(e)
|
||||||
print
|
print
|
||||||
@ -200,9 +224,8 @@ class DemoEncoder(MessageEncoder):
|
|||||||
if msgtype == EVENT:
|
if msgtype == EVENT:
|
||||||
msg = '%s a:b [3,{"t":193868}]' % msgtype
|
msg = '%s a:b [3,{"t":193868}]' % msgtype
|
||||||
print msg
|
print msg
|
||||||
d=self.decode(msg)
|
d = self.decode(msg)
|
||||||
print d
|
print d
|
||||||
print self.encode(d)
|
print self.encode(d)
|
||||||
print
|
print
|
||||||
print "---- Testing done -----"
|
print "---- Testing done -----"
|
||||||
|
|
||||||
|
@ -35,7 +35,6 @@ except ImportError:
|
|||||||
import pickle
|
import pickle
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class PickleEncoder(MessageEncoder):
|
class PickleEncoder(MessageEncoder):
|
||||||
|
|
||||||
def encode(self, messageobj):
|
def encode(self, messageobj):
|
||||||
|
@ -24,9 +24,10 @@
|
|||||||
|
|
||||||
|
|
||||||
class SECOPError(RuntimeError):
|
class SECOPError(RuntimeError):
|
||||||
|
|
||||||
def __init__(self, *args, **kwds):
|
def __init__(self, *args, **kwds):
|
||||||
self.args = args
|
self.args = args
|
||||||
for k,v in kwds.items():
|
for k, v in kwds.items():
|
||||||
setattr(self, k, v)
|
setattr(self, k, v)
|
||||||
|
|
||||||
|
|
||||||
|
@ -80,5 +80,3 @@ class DemoFramer(Framer):
|
|||||||
def reset(self):
|
def reset(self):
|
||||||
self.data = b''
|
self.data = b''
|
||||||
self.decoded = []
|
self.decoded = []
|
||||||
|
|
||||||
|
|
||||||
|
@ -70,5 +70,3 @@ class RLEFramer(Framer):
|
|||||||
def reset(self):
|
def reset(self):
|
||||||
self.data = b''
|
self.data = b''
|
||||||
self.frames_to_go = 0
|
self.frames_to_go = 0
|
||||||
|
|
||||||
|
|
||||||
|
@ -34,6 +34,7 @@ from secop.protocol.encoding import ENCODERS
|
|||||||
from secop.protocol.framing import FRAMERS
|
from secop.protocol.framing import FRAMERS
|
||||||
from secop.protocol.messages import HelpMessage
|
from secop.protocol.messages import HelpMessage
|
||||||
|
|
||||||
|
|
||||||
class TCPRequestHandler(SocketServer.BaseRequestHandler):
|
class TCPRequestHandler(SocketServer.BaseRequestHandler):
|
||||||
|
|
||||||
def setup(self):
|
def setup(self):
|
||||||
@ -49,7 +50,7 @@ class TCPRequestHandler(SocketServer.BaseRequestHandler):
|
|||||||
clientaddr = self.client_address
|
clientaddr = self.client_address
|
||||||
serverobj = self.server
|
serverobj = self.server
|
||||||
self.log.debug("handling new connection from %s" % repr(clientaddr))
|
self.log.debug("handling new connection from %s" % repr(clientaddr))
|
||||||
|
|
||||||
# notify dispatcher of us
|
# notify dispatcher of us
|
||||||
serverobj.dispatcher.add_connection(self)
|
serverobj.dispatcher.add_connection(self)
|
||||||
|
|
||||||
@ -81,7 +82,7 @@ class TCPRequestHandler(SocketServer.BaseRequestHandler):
|
|||||||
# dispatcher will queue the reply before returning
|
# dispatcher will queue the reply before returning
|
||||||
frames = self.framing.decode(data)
|
frames = self.framing.decode(data)
|
||||||
if frames is not None:
|
if frames is not None:
|
||||||
if not frames: # empty list
|
if not frames: # empty list
|
||||||
self.queue_reply(HelpMessage(MSGTYPE=reply))
|
self.queue_reply(HelpMessage(MSGTYPE=reply))
|
||||||
for frame in frames:
|
for frame in frames:
|
||||||
reply = None
|
reply = None
|
||||||
@ -129,7 +130,8 @@ class TCPServer(SocketServer.ThreadingTCPServer):
|
|||||||
self.encodingCLS = ENCODERS[interfaceopts.pop('encoding', 'pickle')]
|
self.encodingCLS = ENCODERS[interfaceopts.pop('encoding', 'pickle')]
|
||||||
self.log.debug("TCPServer binding to %s:%d" % (bindto, portnum))
|
self.log.debug("TCPServer binding to %s:%d" % (bindto, portnum))
|
||||||
self.log.debug("TCPServer using framing=%s" % self.framingCLS.__name__)
|
self.log.debug("TCPServer using framing=%s" % self.framingCLS.__name__)
|
||||||
self.log.debug("TCPServer using encoding=%s" % self.encodingCLS.__name__)
|
self.log.debug("TCPServer using encoding=%s" %
|
||||||
|
self.encodingCLS.__name__)
|
||||||
SocketServer.ThreadingTCPServer.__init__(self, (bindto, portnum),
|
SocketServer.ThreadingTCPServer.__init__(self, (bindto, portnum),
|
||||||
TCPRequestHandler,
|
TCPRequestHandler,
|
||||||
bind_and_activate=True)
|
bind_and_activate=True)
|
||||||
|
@ -45,18 +45,19 @@ class Message(object):
|
|||||||
|
|
||||||
def as_dict(self):
|
def as_dict(self):
|
||||||
"""returns set parameters as dict"""
|
"""returns set parameters as dict"""
|
||||||
return dict(map(lambda k:(k, getattr(self,k)),self.ARGS))
|
return dict(map(lambda k: (k, getattr(self, k)), self.ARGS))
|
||||||
|
|
||||||
|
|
||||||
class Value(object):
|
class Value(object):
|
||||||
|
|
||||||
def __init__(self, module, parameter=None, command=None, value=Ellipsis, **qualifiers):
|
def __init__(self, module, parameter=None, command=None, value=Ellipsis,
|
||||||
|
**qualifiers):
|
||||||
self.module = module
|
self.module = module
|
||||||
self.parameter = parameter
|
self.parameter = parameter
|
||||||
self.command = command
|
self.command = command
|
||||||
self.value = value
|
self.value = value
|
||||||
self.qualifiers = qualifiers
|
self.qualifiers = qualifiers
|
||||||
self.msgtype = 'update' # 'changed' or 'done'
|
self.msgtype = 'update' # 'changed' or 'done'
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
devspec = self.module
|
devspec = self.module
|
||||||
@ -65,79 +66,96 @@ class Value(object):
|
|||||||
elif self.command:
|
elif self.command:
|
||||||
devspec = '%s:%s()' % (devspec, self.command)
|
devspec = '%s:%s()' % (devspec, self.command)
|
||||||
return '%s:Value(%s)' % (devspec, ', '.join(
|
return '%s:Value(%s)' % (devspec, ', '.join(
|
||||||
[repr(self.value)] + ['%s=%s' % (k, repr(v)) for k, v in self.qualifiers.items()]))
|
[repr(self.value)] +
|
||||||
|
['%s=%s' % (k, repr(v)) for k, v in self.qualifiers.items()]))
|
||||||
|
|
||||||
|
|
||||||
class IdentifyRequest(Message):
|
class IdentifyRequest(Message):
|
||||||
is_request = True
|
is_request = True
|
||||||
|
|
||||||
|
|
||||||
class IdentifyReply(Message):
|
class IdentifyReply(Message):
|
||||||
is_reply = True
|
is_reply = True
|
||||||
version_string = None
|
version_string = None
|
||||||
|
|
||||||
|
|
||||||
class DescribeRequest(Message):
|
class DescribeRequest(Message):
|
||||||
is_request = True
|
is_request = True
|
||||||
|
|
||||||
|
|
||||||
class DescribeReply(Message):
|
class DescribeReply(Message):
|
||||||
is_reply = True
|
is_reply = True
|
||||||
equipment_id = None
|
equipment_id = None
|
||||||
description = None
|
description = None
|
||||||
|
|
||||||
|
|
||||||
class ActivateRequest(Message):
|
class ActivateRequest(Message):
|
||||||
is_request = True
|
is_request = True
|
||||||
|
|
||||||
|
|
||||||
class ActivateReply(Message):
|
class ActivateReply(Message):
|
||||||
is_reply = True
|
is_reply = True
|
||||||
|
|
||||||
|
|
||||||
class DeactivateRequest(Message):
|
class DeactivateRequest(Message):
|
||||||
is_request = True
|
is_request = True
|
||||||
|
|
||||||
|
|
||||||
class DeactivateReply(Message):
|
class DeactivateReply(Message):
|
||||||
is_reply = True
|
is_reply = True
|
||||||
|
|
||||||
|
|
||||||
class CommandRequest(Message):
|
class CommandRequest(Message):
|
||||||
is_request = True
|
is_request = True
|
||||||
command = ''
|
command = ''
|
||||||
arguments = []
|
arguments = []
|
||||||
|
|
||||||
|
|
||||||
class CommandReply(Message):
|
class CommandReply(Message):
|
||||||
is_reply = True
|
is_reply = True
|
||||||
command = ''
|
command = ''
|
||||||
result = None
|
result = None
|
||||||
|
|
||||||
|
|
||||||
class WriteRequest(Message):
|
class WriteRequest(Message):
|
||||||
is_request = True
|
is_request = True
|
||||||
module = None
|
module = None
|
||||||
parameter = None
|
parameter = None
|
||||||
value = None
|
value = None
|
||||||
|
|
||||||
|
|
||||||
class WriteReply(Message):
|
class WriteReply(Message):
|
||||||
is_reply = True
|
is_reply = True
|
||||||
module = None
|
module = None
|
||||||
parameter = None
|
parameter = None
|
||||||
value = None
|
value = None
|
||||||
|
|
||||||
|
|
||||||
class PollRequest(Message):
|
class PollRequest(Message):
|
||||||
is_request = True
|
is_request = True
|
||||||
module = None
|
module = None
|
||||||
parameter = None
|
parameter = None
|
||||||
|
|
||||||
|
|
||||||
class HeartbeatRequest(Message):
|
class HeartbeatRequest(Message):
|
||||||
is_request = True
|
is_request = True
|
||||||
nonce = 'alive'
|
nonce = 'alive'
|
||||||
|
|
||||||
|
|
||||||
class HeartbeatReply(Message):
|
class HeartbeatReply(Message):
|
||||||
is_reply = True
|
is_reply = True
|
||||||
nonce = 'undefined'
|
nonce = 'undefined'
|
||||||
|
|
||||||
|
|
||||||
class EventMessage(Message):
|
class EventMessage(Message):
|
||||||
# use Value directly for Replies !
|
# use Value directly for Replies !
|
||||||
is_reply = True
|
is_reply = True
|
||||||
module = None
|
module = None
|
||||||
parameter = None
|
parameter = None
|
||||||
command = None
|
command = None
|
||||||
value = None # Value object ! (includes qualifiers!)
|
value = None # Value object ! (includes qualifiers!)
|
||||||
|
|
||||||
|
|
||||||
class ErrorMessage(Message):
|
class ErrorMessage(Message):
|
||||||
is_error = True
|
is_error = True
|
||||||
errorclass = 'InternalError'
|
errorclass = 'InternalError'
|
||||||
@ -149,7 +167,6 @@ class HelpMessage(Message):
|
|||||||
is_request = True
|
is_request = True
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
print("Minimal testing of messages....")
|
print("Minimal testing of messages....")
|
||||||
m = Message(MSGTYPE='test', a=1, b=2, c='x')
|
m = Message(MSGTYPE='test', a=1, b=2, c='x')
|
||||||
|
@ -110,8 +110,10 @@ class Value(object):
|
|||||||
devspec = '%s:%s' % (devspec, self.param)
|
devspec = '%s:%s' % (devspec, self.param)
|
||||||
if self.prop:
|
if self.prop:
|
||||||
devspec = '%s:%s' % (devspec, self.prop)
|
devspec = '%s:%s' % (devspec, self.prop)
|
||||||
return '%s:Value(%s)' % (devspec, ', '.join(
|
return '%s:Value(%s)' % (
|
||||||
[repr(self.value)] + ['%s=%r' % (k, v) for k, v in self.qualifiers.items()]))
|
devspec, ', '.join(
|
||||||
|
[repr(self.value)] +
|
||||||
|
['%s=%r' % (k, v) for k, v in self.qualifiers.items()]))
|
||||||
|
|
||||||
|
|
||||||
class ListMessage(Message):
|
class ListMessage(Message):
|
||||||
@ -165,32 +167,59 @@ class HelpMessage(Message):
|
|||||||
|
|
||||||
|
|
||||||
class NoSuchDeviceError(ErrorMessage):
|
class NoSuchDeviceError(ErrorMessage):
|
||||||
|
|
||||||
def __init__(self, *devs):
|
def __init__(self, *devs):
|
||||||
ErrorMessage.__init__(self, devs=devs, errorstring="Device %r does not exist" % devs[0], errortype='NoSuchDevice')
|
ErrorMessage.__init__(
|
||||||
|
self, devs=devs, errorstring="Device %r does not exist" %
|
||||||
|
devs[0], errortype='NoSuchDevice')
|
||||||
|
|
||||||
|
|
||||||
class NoSuchParamError(ErrorMessage):
|
class NoSuchParamError(ErrorMessage):
|
||||||
|
|
||||||
def __init__(self, dev, *params):
|
def __init__(self, dev, *params):
|
||||||
ErrorMessage.__init__(self, devs=(dev,), params=params, errorstring="Device %r has no parameter %r" % (dev, params[0]), errortype='NoSuchParam')
|
ErrorMessage.__init__(
|
||||||
|
self, devs=(dev,),
|
||||||
|
params=params, errorstring="Device %r has no parameter %r" %
|
||||||
|
(dev, params[0]),
|
||||||
|
errortype='NoSuchParam')
|
||||||
|
|
||||||
|
|
||||||
class ParamReadonlyError(ErrorMessage):
|
class ParamReadonlyError(ErrorMessage):
|
||||||
|
|
||||||
def __init__(self, dev, *params):
|
def __init__(self, dev, *params):
|
||||||
ErrorMessage.__init__(self, devs=(dev,), params=params, errorstring="Device %r, parameter %r is not writeable!" % (dev, params[0]), errortype='ParamReadOnly')
|
ErrorMessage.__init__(
|
||||||
|
self, devs=(dev,),
|
||||||
|
params=params,
|
||||||
|
errorstring="Device %r, parameter %r is not writeable!" %
|
||||||
|
(dev, params[0]),
|
||||||
|
errortype='ParamReadOnly')
|
||||||
|
|
||||||
|
|
||||||
class InvalidParamValueError(ErrorMessage):
|
class InvalidParamValueError(ErrorMessage):
|
||||||
|
|
||||||
def __init__(self, dev, param, value, e):
|
def __init__(self, dev, param, value, e):
|
||||||
ErrorMessage.__init__(self, devs=(dev,), params=params, values=(value), errorstring=str(e), errortype='InvalidParamValueError')
|
ErrorMessage.__init__(
|
||||||
|
self, devs=(dev,),
|
||||||
|
params=params, values=(value),
|
||||||
|
errorstring=str(e),
|
||||||
|
errortype='InvalidParamValueError')
|
||||||
|
|
||||||
|
|
||||||
class InternalError(ErrorMessage):
|
class InternalError(ErrorMessage):
|
||||||
|
|
||||||
def __init__(self, err, **kwds):
|
def __init__(self, err, **kwds):
|
||||||
ErrorMessage.__init__(self, errorstring=str(err), errortype='InternalError', **kwds)
|
ErrorMessage.__init__(
|
||||||
|
self, errorstring=str(err),
|
||||||
|
errortype='InternalError', **kwds)
|
||||||
|
|
||||||
|
|
||||||
MESSAGE = dict((cls.MSGTYPE, cls) for cls in [HelpMessage, ErrorMessage, EventMessage, TriggerMessage, UnsubscribeMessage, SubscribeMessage,
|
MESSAGE = dict(
|
||||||
PollMessage, CommandMessage, WriteMessage, ReadMessage, ListMessage])
|
(cls.MSGTYPE, cls)
|
||||||
|
for cls
|
||||||
|
in
|
||||||
|
[HelpMessage, ErrorMessage, EventMessage, TriggerMessage,
|
||||||
|
UnsubscribeMessage, SubscribeMessage, PollMessage, CommandMessage,
|
||||||
|
WriteMessage, ReadMessage, ListMessage])
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
print("Minimal testing of messages....")
|
print("Minimal testing of messages....")
|
||||||
|
@ -85,7 +85,9 @@ class Server(object):
|
|||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
for t in self._threads:
|
for t in self._threads:
|
||||||
if not t.is_alive():
|
if not t.is_alive():
|
||||||
self.log.debug('thread %r died (%d still running)' % (t,len(self._threads)))
|
self.log.debug(
|
||||||
|
'thread %r died (%d still running)' %
|
||||||
|
(t, len(self._threads)))
|
||||||
t.join()
|
t.join()
|
||||||
self._threads.discard(t)
|
self._threads.discard(t)
|
||||||
|
|
||||||
@ -132,7 +134,9 @@ class Server(object):
|
|||||||
if parser.has_option('equipment', 'id'):
|
if parser.has_option('equipment', 'id'):
|
||||||
equipment_id = parser.get('equipment', 'id')
|
equipment_id = parser.get('equipment', 'id')
|
||||||
|
|
||||||
self._dispatcher = self._buildObject('Dispatcher', Dispatcher, dict(equipment_id=equipment_id))
|
self._dispatcher = self._buildObject(
|
||||||
|
'Dispatcher', Dispatcher, dict(
|
||||||
|
equipment_id=equipment_id))
|
||||||
self._processInterfaceOptions(interfaceopts)
|
self._processInterfaceOptions(interfaceopts)
|
||||||
self._processDeviceOptions(deviceopts)
|
self._processDeviceOptions(deviceopts)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user