added 'export_as' for parameters and commands
the export argument of an accessible allows now to specify an other external name than the attribute used internally. export=True (default, use defined name, or prefix with '_' when name not in predefined list) export=False (do not export) export=<any string> (special cases only) Change-Id: I6c6669cd502d9d6fd3aa40091673e5554fd961bd Reviewed-on: https://forge.frm2.tum.de/review/19664 Tested-by: JenkinsCodeReview <bjoern_pedersen@frm2.tum.de> Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
This commit is contained in:
parent
ef27fd1b54
commit
feba5a1400
@ -28,12 +28,12 @@ import time
|
||||
|
||||
from secop.datatypes import (EnumType, FloatRange, StringType, TupleOf,
|
||||
get_datatype)
|
||||
from secop.errors import ConfigError
|
||||
from secop.errors import ConfigError, ProgrammingError
|
||||
from secop.lib import (formatException, formatExtendedStack, mkthread,
|
||||
unset_value)
|
||||
from secop.lib.enum import Enum
|
||||
from secop.metaclass import ModuleMeta, add_metaclass
|
||||
from secop.params import Command, Override, Parameter
|
||||
from secop.params import Command, Override, Parameter, PREDEFINED_ACCESSIBLES
|
||||
|
||||
# XXX: connect with 'protocol'-Modules.
|
||||
# Idea: every Module defined herein is also a 'protocol'-Module,
|
||||
@ -41,8 +41,6 @@ from secop.params import Command, Override, Parameter
|
||||
# from these base classes (how to do this?)
|
||||
|
||||
|
||||
|
||||
|
||||
@add_metaclass(ModuleMeta)
|
||||
class Module(object):
|
||||
"""Basic Module
|
||||
@ -74,6 +72,7 @@ class Module(object):
|
||||
'visibility': None, # XXX: ????
|
||||
# what else?
|
||||
}
|
||||
|
||||
# properties, parameter and commands are auto-merged upon subclassing
|
||||
parameters = {}
|
||||
commands = {}
|
||||
@ -126,11 +125,27 @@ class Module(object):
|
||||
# they need to be individual per instance since we use them also
|
||||
# to cache the current value + qualifiers...
|
||||
accessibles = {}
|
||||
for k, v in self.accessibles.items():
|
||||
# conversion from exported names to internal attribute names
|
||||
accessiblename2attr = {}
|
||||
for aname, aobj in self.accessibles.items():
|
||||
# make a copy of the Parameter/Command object
|
||||
accessibles[k] = v.copy()
|
||||
aobj = aobj.copy()
|
||||
if aobj.export:
|
||||
if aobj.export is True:
|
||||
predefined_obj = PREDEFINED_ACCESSIBLES.get(aname, None)
|
||||
if predefined_obj:
|
||||
if isinstance(aobj, predefined_obj):
|
||||
aobj.export = aname
|
||||
else:
|
||||
raise ProgrammingError("can not use '%s' as name of a %s" %
|
||||
(aname, aobj.__class__.__name__))
|
||||
else: # create custom parameter
|
||||
aobj.export = '_' + aname
|
||||
accessiblename2attr[aobj.export] = aname
|
||||
accessibles[aname] = aobj
|
||||
# do not re-use self.accessibles as this is the same for all instances
|
||||
self.accessibles = accessibles
|
||||
self.accessiblename2attr = accessiblename2attr
|
||||
|
||||
# 2) check and apply parameter_properties
|
||||
# specified as '<paramname>.<propertyname> = <propertyvalue>'
|
||||
|
@ -29,6 +29,7 @@ from secop.lib import unset_value
|
||||
|
||||
EVENT_ONLY_ON_CHANGED_VALUES = False
|
||||
|
||||
|
||||
class CountedObj(object):
|
||||
ctr = [0]
|
||||
def __init__(self):
|
||||
@ -36,6 +37,7 @@ class CountedObj(object):
|
||||
cl[0] += 1
|
||||
self.ctr = cl[0]
|
||||
|
||||
|
||||
class Accessible(CountedObj):
|
||||
'''abstract base class for Parameter and Command'''
|
||||
|
||||
@ -73,6 +75,7 @@ class Accessible(CountedObj):
|
||||
raise ProgrammingError('can not overrride property name %s' % name)
|
||||
cls.valid_properties[name] = external
|
||||
|
||||
|
||||
class Parameter(Accessible):
|
||||
"""storage for Parameter settings + value + qualifiers
|
||||
|
||||
@ -219,3 +222,23 @@ class Command(Accessible):
|
||||
def for_export(self):
|
||||
# used for serialisation only
|
||||
return self.exported_properties()
|
||||
|
||||
# list of predefined accessibles with their type
|
||||
PREDEFINED_ACCESSIBLES = dict(
|
||||
value = Parameter,
|
||||
status = Parameter,
|
||||
target = Parameter,
|
||||
pollinterval = Parameter,
|
||||
ramp = Parameter,
|
||||
user_ramp = Parameter,
|
||||
setpoint = Parameter,
|
||||
time_to_target = Parameter,
|
||||
unit = Parameter, # reserved name
|
||||
loglevel = Parameter, # reserved name
|
||||
mode = Parameter, # reserved name
|
||||
stop = Command,
|
||||
reset = Command,
|
||||
go = Command,
|
||||
abort = Command,
|
||||
shutdown = Command,
|
||||
)
|
||||
|
@ -100,23 +100,19 @@ class Dispatcher(object):
|
||||
def announce_update(self, moduleobj, pname, pobj):
|
||||
"""called by modules param setters to notify subscribers of new values
|
||||
"""
|
||||
msg = (EVENTREPLY, u'%s:%s' % (moduleobj.name, pname), [pobj.export_value(), dict(t=pobj.timestamp)])
|
||||
# argument pname is no longer used here - should we remove it?
|
||||
msg = (EVENTREPLY, u'%s:%s' % (moduleobj.name, pobj.export),
|
||||
[pobj.export_value(), dict(t=pobj.timestamp)])
|
||||
self.broadcast_event(msg)
|
||||
|
||||
def subscribe(self, conn, modulename, pname=None):
|
||||
eventname = modulename
|
||||
if pname:
|
||||
eventname = u'%s:%s' % (modulename, pname)
|
||||
def subscribe(self, conn, eventname):
|
||||
self._subscriptions.setdefault(eventname, set()).add(conn)
|
||||
|
||||
def unsubscribe(self, conn, modulename, pname=None):
|
||||
if pname:
|
||||
eventname = u'%s:%s' % (modulename, pname)
|
||||
else:
|
||||
eventname = modulename
|
||||
def unsubscribe(self, conn, eventname):
|
||||
if not ':' in eventname:
|
||||
# also remove 'more specific' subscriptions
|
||||
for k, v in self._subscriptions.items():
|
||||
if k.startswith(u'%s:' % modulename):
|
||||
if k.startswith(u'%s:' % eventname):
|
||||
v.discard(conn)
|
||||
if eventname in self._subscriptions:
|
||||
self._subscriptions[eventname].discard(conn)
|
||||
@ -166,9 +162,9 @@ class Dispatcher(object):
|
||||
if modulename in self._export:
|
||||
# omit export=False params!
|
||||
res = []
|
||||
for aname, aobj in self.get_module(modulename).accessibles.items():
|
||||
for aobj in self.get_module(modulename).accessibles.values():
|
||||
if aobj.export:
|
||||
res.append([aname, aobj.for_export()])
|
||||
res.append([aobj.export, aobj.for_export()])
|
||||
self.log.debug(u'list accessibles for module %s -> %r' %
|
||||
(modulename, res))
|
||||
return res
|
||||
@ -218,11 +214,12 @@ class Dispatcher(object):
|
||||
# XXX: pipe through cmdspec.datatype.result ?
|
||||
return res, dict(t=currenttime())
|
||||
|
||||
def _setParameterValue(self, modulename, pname, value):
|
||||
def _setParameterValue(self, modulename, exportedname, value):
|
||||
moduleobj = self.get_module(modulename)
|
||||
if moduleobj is None:
|
||||
raise NoSuchModuleError('Module does not exist on this SEC-Node!')
|
||||
|
||||
pname = moduleobj.accessiblename2attr.get(exportedname, None)
|
||||
pobj = moduleobj.accessibles.get(pname, None)
|
||||
if pobj is None or not isinstance(pobj, Parameter):
|
||||
raise NoSuchParameterError('Module has no such parameter on this SEC-Node!')
|
||||
@ -239,11 +236,12 @@ class Dispatcher(object):
|
||||
return pobj.export_value(), dict(t=pobj.timestamp)
|
||||
return pobj.export_value(), {}
|
||||
|
||||
def _getParameterValue(self, modulename, pname):
|
||||
def _getParameterValue(self, modulename, exportedname):
|
||||
moduleobj = self.get_module(modulename)
|
||||
if moduleobj is None:
|
||||
raise NoSuchModuleError('Module does not exist on this SEC-Node!')
|
||||
|
||||
pname = moduleobj.accessiblename2attr.get(exportedname, None)
|
||||
pobj = moduleobj.accessibles.get(pname, None)
|
||||
if pobj is None or not isinstance(pobj, Parameter):
|
||||
raise NoSuchParameterError('Module has no such parameter on this SEC-Node!')
|
||||
@ -324,16 +322,18 @@ class Dispatcher(object):
|
||||
if data:
|
||||
raise ProtocolError('activate request don\'t take data!')
|
||||
if specifier:
|
||||
modulename, pname = specifier, None
|
||||
modulename, exportedname = specifier, None
|
||||
if ':' in specifier:
|
||||
modulename, pname = specifier.split(u':', 1)
|
||||
modulename, exportedname = specifier.split(u':', 1)
|
||||
if modulename not in self._export:
|
||||
raise NoSuchModuleError('Module does not exist on this SEC-Node!')
|
||||
if pname and pname not in self.get_module(modulename).accessibles:
|
||||
moduleobj = self.get_module(modulename)
|
||||
pname = moduleobj.accessiblename2attr.get(exportedname, True)
|
||||
if pname and pname not in moduleobj.accessibles:
|
||||
# what if we try to subscribe a command here ???
|
||||
raise NoSuchParameterError('Module has no such parameter on this SEC-Node!')
|
||||
# activate only ONE module
|
||||
self.subscribe(conn, modulename, pname)
|
||||
# activate only ONE item (module or module:parameter)
|
||||
self.subscribe(conn, specifier)
|
||||
modules = [(modulename, pname)]
|
||||
else:
|
||||
# activate all modules
|
||||
@ -346,17 +346,17 @@ class Dispatcher(object):
|
||||
moduleobj = self._modules.get(modulename, None)
|
||||
if pname:
|
||||
pobj = moduleobj.accessibles[pname]
|
||||
updmsg = (EVENTREPLY, u'%s:%s' % (modulename, pname),
|
||||
updmsg = (EVENTREPLY, u'%s:%s' % (modulename, pobj.export),
|
||||
[pobj.export_value(), dict(t=pobj.timestamp)])
|
||||
conn.queue_async_reply(updmsg)
|
||||
continue
|
||||
for pname, pobj in moduleobj.accessibles.items(): # pylint: disable=redefined-outer-name
|
||||
for pobj in moduleobj.accessibles.values():
|
||||
if not isinstance(pobj, Parameter):
|
||||
continue
|
||||
if not pobj.export: # XXX: handle export_as cases!
|
||||
if not pobj.export:
|
||||
continue
|
||||
# can not use announce_update here, as this will send to all clients
|
||||
updmsg = (EVENTREPLY, u'%s:%s' % (modulename, pname),
|
||||
updmsg = (EVENTREPLY, u'%s:%s' % (modulename, pobj.export),
|
||||
[pobj.export_value(), dict(t=pobj.timestamp)])
|
||||
conn.queue_async_reply(updmsg)
|
||||
return (ENABLEEVENTSREPLY, specifier, None) if specifier else (ENABLEEVENTSREPLY, None, None)
|
||||
|
Loading…
x
Reference in New Issue
Block a user