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:
zolliker 2018-12-19 15:35:02 +01:00 committed by Enrico Faulhaber
parent ef27fd1b54
commit feba5a1400
3 changed files with 68 additions and 30 deletions

View File

@ -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>'

View File

@ -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,
)

View File

@ -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)