diff --git a/secop/modules.py b/secop/modules.py index 027add8..d750fb7 100644 --- a/secop/modules.py +++ b/secop/modules.py @@ -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 '. = ' diff --git a/secop/params.py b/secop/params.py index 6d22a03..334a14e 100644 --- a/secop/params.py +++ b/secop/params.py @@ -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, +) diff --git a/secop/protocol/dispatcher.py b/secop/protocol/dispatcher.py index f92a4c0..1633bec 100644 --- a/secop/protocol/dispatcher.py +++ b/secop/protocol/dispatcher.py @@ -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)