diff --git a/secop/client/__init__.py b/secop/client/__init__.py index c1d22b4..37a8054 100644 --- a/secop/client/__init__.py +++ b/secop/client/__init__.py @@ -495,9 +495,12 @@ class SecopClient(ProxyClient): def _set_state(self, online, state=None): # treat reconnecting as online! state = state or self.state - self.callback(None, 'nodeStateChange', online, state) - for mname in self.modules: - self.callback(mname, 'nodeStateChange', online, state) + try: + self.callback(None, 'nodeStateChange', online, state) + for mname in self.modules: + self.callback(mname, 'nodeStateChange', online, state) + except Exception as e: + self.log.error('ERROR in nodeStateCallback %s', e) # set online attribute after callbacks -> callback may check for old state self.online = online self.state = state diff --git a/secop/lib/__init__.py b/secop/lib/__init__.py index 8118f24..96fb073 100644 --- a/secop/lib/__init__.py +++ b/secop/lib/__init__.py @@ -98,14 +98,17 @@ def clamp(_min, value, _max): def get_class(spec): - """loads a class given by string in dotted notaion (as python would do)""" + """loads a class given by string in dotted notation (as python would do)""" modname, classname = spec.rsplit('.', 1) if modname.startswith('secop'): module = importlib.import_module(modname) else: # rarely needed by now.... module = importlib.import_module('secop.' + modname) - return getattr(module, classname) + try: + return getattr(module, classname) + except AttributeError: + raise AttributeError('no such class') from None def mkthread(func, *args, **kwds): diff --git a/secop/modules.py b/secop/modules.py index ae0a6c6..c3d1334 100644 --- a/secop/modules.py +++ b/secop/modules.py @@ -93,10 +93,13 @@ class HasAccessibles(HasProperties): rfunc_handler = pobj.handler.get_read_func(cls, pname) if pobj.handler else None wrapped = hasattr(rfunc, '__wrapped__') if rfunc_handler: - if rfunc and not wrapped: - raise ProgrammingError("parameter '%s' can not have a handler " - "and read_%s" % (pname, pname)) - rfunc = rfunc_handler + if 'read_' + pname in cls.__dict__: + if pname in cls.__dict__: + raise ProgrammingError("parameter '%s' can not have a handler " + "and read_%s" % (pname, pname)) + # read_ overwrites inherited handler + else: + rfunc = rfunc_handler wrapped = False # create wrapper except when read function is already wrapped diff --git a/secop/params.py b/secop/params.py index 831505e..5b0de55 100644 --- a/secop/params.py +++ b/secop/params.py @@ -101,10 +101,10 @@ class Parameter(Accessible): description = Property( 'mandatory description of the parameter', TextType(), - extname='description', mandatory=True) + extname='description', mandatory=True, export='always') datatype = Property( 'datatype of the Parameter (SECoP datainfo)', DataTypeType(), - extname='datainfo', mandatory=True) + extname='datainfo', mandatory=True, export='always') readonly = Property( 'not changeable via SECoP (default True)', BoolType(), extname='readonly', default=True, export='always') @@ -283,7 +283,7 @@ class Command(Accessible): description = Property( 'description of the Command', TextType(), - extname='description', export=True, mandatory=True) + extname='description', export='always', mandatory=True) group = Property( 'optional command group of the command.', StringType(), extname='group', export=True, default='') diff --git a/secop/server.py b/secop/server.py index e22cd13..50a674f 100644 --- a/secop/server.py +++ b/secop/server.py @@ -228,11 +228,15 @@ class Server: errors.append(self.unknown_options(cls, opts)) self.modules = OrderedDict() badclass = None + failed = set() # python modules failed to load self.lastError = None for modname, options in self.module_cfg.items(): opts = dict(options) try: classname = opts.pop('class') + pymodule = classname.rpartition('.')[0] + if pymodule in failed: + continue cls = get_class(classname) modobj = cls(modname, self.log.getChild(modname), opts, self) # all used args should be popped from opts! @@ -242,8 +246,12 @@ class Server: except ConfigError as e: errors.append(str(e)) except Exception as e: - badclass = classname - errors.append('error while loading %s' % badclass) + if str(e) == 'no such class': + errors.append('%s not found' % classname) + else: + failed.add(pymodule) + badclass = classname + errors.append('error importing %s' % pymodule) poll_table = dict() # all objs created, now start them up and interconnect diff --git a/secop_psi/historywriter.py b/secop_psi/historywriter.py index 76b1bea..49a0b7b 100644 --- a/secop_psi/historywriter.py +++ b/secop_psi/historywriter.py @@ -43,9 +43,17 @@ def make_cvt_list(dt, tail=''): else: return [] # ArrayType, BlobType and TextType are ignored: too much data, probably not used result = [] + print('START', dt) for subkey, elmtype in items: + print('MAKE_CVT_LIST', subkey, elmtype) for fun, tail_, opts in make_cvt_list(elmtype, '%s.%s' % (tail, subkey)): - result.append((lambda v, k=subkey, f=fun: f(v[k]), tail_, opts)) + def conv(value, key=subkey, func=fun): + try: + return value[key] + except KeyError: # can not use value.get() because value might be a list + return None + result.append((conv, tail_, opts)) + print('END', result) return result diff --git a/secop_psi/ppms.py b/secop_psi/ppms.py index 238224a..b5a2d5a 100644 --- a/secop_psi/ppms.py +++ b/secop_psi/ppms.py @@ -128,8 +128,9 @@ class Main(Communicator): return data # return data as string -class PpmsBase(HasIodev, Readable): +class PpmsMixin: """common base for all ppms modules""" + iodev = Attached() pollerClass = Poller @@ -139,7 +140,7 @@ class PpmsBase(HasIodev, Readable): # as this pollinterval affects only the polling of settings # it would be confusing to export it. - pollinterval = Parameter(export=False) + pollinterval = Parameter('', FloatRange(), needscfg=False, export=False) def initModule(self): self._iodev.register(self) @@ -172,7 +173,7 @@ class PpmsBase(HasIodev, Readable): self.status = (self.Status.IDLE, '') -class Channel(PpmsBase): +class Channel(PpmsMixin, HasIodev, Readable): """channel base class""" value = Parameter('main value of channels', poll=True) @@ -270,7 +271,7 @@ class BridgeChannel(Channel): return self.no, 0, 0, change.dcflag, change.readingmode, 0 -class Level(PpmsBase): +class Level(PpmsMixin, HasIodev, Readable): """helium level""" level = IOHandler('level', 'LEVEL?', '%g,%d') @@ -293,7 +294,7 @@ class Level(PpmsBase): return dict(value=level, status=(self.Status.IDLE, '')) -class Chamber(PpmsBase, Drivable): +class Chamber(PpmsMixin, HasIodev, Drivable): """sample chamber handling value is an Enum, which is redundant with the status text @@ -368,7 +369,7 @@ class Chamber(PpmsBase, Drivable): return (change.target,) -class Temp(PpmsBase, Drivable): +class Temp(PpmsMixin, HasIodev, Drivable): """temperature""" temp = IOHandler('temp', 'TEMP?', '%g,%g,%d') @@ -553,7 +554,7 @@ class Temp(PpmsBase, Drivable): self._stopped = True -class Field(PpmsBase, Drivable): +class Field(PpmsMixin, HasIodev, Drivable): """magnetic field""" field = IOHandler('field', 'FIELD?', '%g,%g,%d,%d') @@ -562,6 +563,7 @@ class Field(PpmsBase, Drivable): PREPARED=150, PREPARING=340, RAMPING=370, + STABILIZING=380, FINALIZING=390, ) # pylint: disable=invalid-name @@ -584,7 +586,7 @@ class Field(PpmsBase, Drivable): 2: (Status.PREPARING, 'switch warming'), 3: (Status.FINALIZING, 'switch cooling'), 4: (Status.IDLE, 'driven stable'), - 5: (Status.FINALIZING, 'driven final'), + 5: (Status.STABILIZING, 'driven final'), 6: (Status.RAMPING, 'charging'), 7: (Status.RAMPING, 'discharging'), 8: (Status.ERROR, 'current error'), @@ -690,7 +692,7 @@ class Field(PpmsBase, Drivable): self._stopped = True -class Position(PpmsBase, Drivable): +class Position(PpmsMixin, HasIodev, Drivable): """rotator position""" move = IOHandler('move', 'MOVE?', '%g,%g,%g') diff --git a/secop_psi/sea.py b/secop_psi/sea.py index b90a19f..6a97720 100644 --- a/secop_psi/sea.py +++ b/secop_psi/sea.py @@ -359,6 +359,8 @@ class SeaModule(Module): else: pobj = Parameter(**kwds) datatype = pobj.datatype + if name == 'cc' and key == 'value': + print('cc.value: %r %r' % (kwds, pobj)) attributes[key] = pobj if not hasattr(cls, 'read_' + key): def rfunc(self, cmd='hval /sics/%s/%s' % (sea_object, path)): diff --git a/secop_psi/testcmd.py b/secop_psi/testcmd.py index 9fe13ca..3458c1f 100644 --- a/secop_psi/testcmd.py +++ b/secop_psi/testcmd.py @@ -38,12 +38,14 @@ class TestCmd(Module): result=StringType()) def arg(self, *arg): """5 args""" + self.tuple = arg return repr(arg) @Command(argument=StructOf(a=StringType(), b=FloatRange(), c=BoolType(), optional=['b']), result=StringType()) def keyed(self, **arg): """keyworded arg""" + self.struct = arg return repr(arg) @Command(argument=FloatRange(), result=StringType())