diff --git a/secop/metaclass.py b/secop/metaclass.py index 82e95d2..07e5066 100644 --- a/secop/metaclass.py +++ b/secop/metaclass.py @@ -206,5 +206,10 @@ class ModuleMeta(type): if attrname[3:] not in newtype.accessibles: raise ProgrammingError('%r: command %r has to be specified ' 'explicitly!' % (name, attrname[3:])) + + # provide properties to 'filter' out the parameters/commands + newtype.parameters = dict((k,v) for k,v in newtype.accessibles.items() if isinstance(v, Parameter)) + newtype.commands = dict((k,v) for k,v in newtype.accessibles.items() if isinstance(v, Command)) + attrs['__constructed__'] = True return newtype diff --git a/secop/modules.py b/secop/modules.py index 1ab5b71..00ec523 100644 --- a/secop/modules.py +++ b/secop/modules.py @@ -74,7 +74,7 @@ class Module(object): } # properties, parameter and commands are auto-merged upon subclassing - parameters = {} + accessibles = {} commands = {} # reference to the dispatcher (used for sending async updates) @@ -246,7 +246,7 @@ class Readable(Module): DISABLED = 500, UNKNOWN = 0, ) - parameters = { + accessibles = { 'value': Parameter('current value of the Module', readonly=True, default=0., datatype=FloatRange(), unit='', poll=True, @@ -313,7 +313,7 @@ class Writable(Readable): providing a settable 'target' parameter to those of a Readable """ - parameters = { + accessibles = { 'target': Parameter('target value of the Module', default=0., readonly=False, datatype=FloatRange(), ), diff --git a/secop_demo/modules.py b/secop_demo/modules.py index b17d4ba..bb66945 100644 --- a/secop_demo/modules.py +++ b/secop_demo/modules.py @@ -35,7 +35,7 @@ from secop.modules import Drivable, Override, Parameter, Readable class Switch(Drivable): """switch it on or off.... """ - parameters = { + accessibles = { 'value': Override('current state (on or off)', datatype=EnumType(on=1, off=0), default=0, ), @@ -77,7 +77,7 @@ class Switch(Drivable): return self.Status.BUSY, info def _update(self): - started = self.accessibles['target'].timestamp + started = self.parameters['target'].timestamp info = '' if self.target > self.value: info = 'waiting for ON' @@ -97,7 +97,7 @@ class Switch(Drivable): class MagneticField(Drivable): """a liquid magnet """ - parameters = { + accessibles = { 'value': Override('current field in T', unit='T', datatype=FloatRange(-15, 15), default=0, ), @@ -117,7 +117,7 @@ class MagneticField(Drivable): datatype=StringType(), export=False, ), } - Status = Enum(Drivable.Status, PERSIST=101, PREPARE=301, RAMP=302, FINISH=303) + Status = Enum(Drivable.Status, PERSIST=101, PREPARE=301, RAMPING=302, FINISH=303) overrides = { 'status' : Override(datatype=TupleOf(EnumType(Status), StringType())), } @@ -148,7 +148,7 @@ class MagneticField(Drivable): elif self._state == self._state.enum.switch_off: return (self.Status.FINISH, self._state.name) elif self._state == self._state.enum.ramp: - return (self.Status.RAMP, self._state.name) + return (self.Status.RAMPING, self._state.name) return (self.Status.ERROR, self._state.name) def _thread(self): @@ -197,7 +197,7 @@ class MagneticField(Drivable): class CoilTemp(Readable): """a coil temperature """ - parameters = { + accessibles = { 'value': Override('Coil temperatur', unit='K', datatype=FloatRange(), default=0, ), @@ -213,7 +213,7 @@ class CoilTemp(Readable): class SampleTemp(Drivable): """a sample temperature """ - parameters = { + accessibles = { 'value': Override('Sample temperature', unit='K', datatype=FloatRange(), default=10, ), @@ -260,7 +260,7 @@ class Label(Readable): of several subdevices. used for demoing connections between modules. """ - parameters = { + accessibles = { 'system': Parameter("Name of the magnet system", datatype=StringType, export=False, ), @@ -305,7 +305,7 @@ class Label(Readable): class DatatypesTest(Readable): """for demoing all datatypes """ - parameters = { + accessibles = { 'enum': Parameter('enum', datatype=EnumType(boo=None, faar=None, z=9), readonly=False, default=1), 'tupleof': Parameter('tuple of int, float and str', @@ -327,7 +327,7 @@ class DatatypesTest(Readable): class ArrayTest(Readable): - parameters = { + accessibles = { "x": Parameter('value', datatype=ArrayOf(FloatRange(), 0, 100000), default = 100000 * [0]), } diff --git a/secop_demo/test.py b/secop_demo/test.py index d3422d7..a03fbf7 100644 --- a/secop_demo/test.py +++ b/secop_demo/test.py @@ -54,7 +54,7 @@ class Heater(Drivable): class name indicates it to be some heating element, but the implementation may do anything """ - parameters = { + accessibles = { 'maxheaterpower': Parameter('maximum allowed heater power', datatype=FloatRange(0, 100), unit='W', ), @@ -73,7 +73,7 @@ class Temp(Drivable): class name indicates it to be some temperature controller, but the implementation may do anything """ - parameters = { + accessibles = { 'sensor': Parameter( "Sensor number or calibration id", datatype=StringType( diff --git a/secop_ess/epics.py b/secop_ess/epics.py index bcf4ebd..3590899 100644 --- a/secop_ess/epics.py +++ b/secop_ess/epics.py @@ -59,7 +59,7 @@ except ImportError: class EpicsReadable(Readable): """EpicsDrivable handles a Drivable interfacing to EPICS v4""" # Commmon parameter for all EPICS devices - parameters = { + accessibles = { 'value': Parameter('EPICS generic value', datatype=FloatRange(), default=300.0,), @@ -119,7 +119,7 @@ class EpicsReadable(Readable): class EpicsDrivable(Drivable): """EpicsDrivable handles a Drivable interfacing to EPICS v4""" # Commmon parameter for all EPICS devices - parameters = { + accessibles = { 'target': Parameter('EPICS generic target', datatype=FloatRange(), default=300.0, readonly=False), 'value': Parameter('EPICS generic value', datatype=FloatRange(), @@ -192,7 +192,7 @@ class EpicsDrivable(Drivable): class EpicsTempCtrl(EpicsDrivable): - parameters = { + accessibles = { # TODO: restrict possible values with oneof datatype 'heaterrange': Parameter('Heater range', datatype=StringType(), default='Off', readonly=False,), diff --git a/secop_mlz/amagnet.py b/secop_mlz/amagnet.py index b65c761..3f6c9d5 100644 --- a/secop_mlz/amagnet.py +++ b/secop_mlz/amagnet.py @@ -48,7 +48,7 @@ class GarfieldMagnet(SequencerMixin, Drivable): the symmetry setting selects which. """ - parameters = { + accessibles = { 'subdev_currentsource': Parameter('(bipolar) Powersupply', datatype=StringType(), readonly=True, export=False), 'subdev_enable': Parameter('Switch to set for on/off', datatype=StringType(), readonly=True, export=False), 'subdev_polswitch': Parameter('Switch to set for polarity', datatype=StringType(), readonly=True, export=False), diff --git a/secop_mlz/entangle.py b/secop_mlz/entangle.py index c2d0f7b..43d6491 100644 --- a/secop_mlz/entangle.py +++ b/secop_mlz/entangle.py @@ -163,7 +163,7 @@ class PyTangoDevice(Module): execution and attribute operations with logging and exception mapping. """ - parameters = { + accessibles = { 'comtries': Parameter('Maximum retries for communication', datatype=IntRange(1, 100), default=3, readonly=False, group='communication'), @@ -430,7 +430,7 @@ class AnalogOutput(PyTangoDevice, Drivable): controllers, ... """ - parameters = { + accessibles = { 'userlimits': Parameter('User defined limits of device value', datatype=TupleOf(FloatRange(), FloatRange()), default=(float('-Inf'), float('+Inf')), @@ -608,7 +608,7 @@ class Actuator(AnalogOutput): """ # for secop: support the speed and ramp parameters - parameters = { + accessibles = { 'speed': Parameter('The speed of changing the value', unit='main/s', readonly=False, datatype=FloatRange(0), ), @@ -648,7 +648,7 @@ class Motor(Actuator): It has the ability to move a real object from one place to another place. """ - parameters = { + accessibles = { 'refpos': Parameter('Reference position', datatype=FloatRange(), unit='main', ), @@ -688,7 +688,7 @@ class TemperatureController(Actuator): """A temperature control loop device. """ - parameters = { + accessibles = { 'p': Parameter('Proportional control Parameter', datatype=FloatRange(), readonly=False, group='pid', ), @@ -763,7 +763,7 @@ class PowerSupply(Actuator): """A power supply (voltage and current) device. """ - parameters = { + accessibles = { 'ramp': Parameter('Current/voltage ramp', unit='main/min', datatype=FloatRange(), readonly=False, poll=30,), 'voltage': Parameter('Actual voltage', unit='V', @@ -801,7 +801,7 @@ class NamedDigitalInput(DigitalInput): """A DigitalInput with numeric values mapped to names. """ - parameters = { + accessibles = { 'mapping': Parameter('A dictionary mapping state names to integers', datatype=StringType(), export=False), # XXX:!!! } @@ -824,7 +824,7 @@ class PartialDigitalInput(NamedDigitalInput): bit width accessed. """ - parameters = { + accessibles = { 'startbit': Parameter('Number of the first bit', datatype=IntRange(0), default=0), 'bitwidth': Parameter('Number of bits', @@ -868,7 +868,7 @@ class NamedDigitalOutput(DigitalOutput): """A DigitalOutput with numeric values mapped to names. """ - parameters = { + accessibles = { 'mapping': Parameter('A dictionary mapping state names to integers', datatype=StringType(), export=False), } @@ -894,7 +894,7 @@ class PartialDigitalOutput(NamedDigitalOutput): bit width accessed. """ - parameters = { + accessibles = { 'startbit': Parameter('Number of the first bit', datatype=IntRange(0), default=0), 'bitwidth': Parameter('Number of bits', @@ -925,7 +925,7 @@ class StringIO(PyTangoDevice, Module): receives strings. """ - parameters = { + accessibles = { 'bustimeout': Parameter('Communication timeout', datatype=FloatRange(), readonly=False, unit='s', group='communication'), diff --git a/test/test_modules.py b/test/test_modules.py index e8c349a..bb7d74f 100644 --- a/test/test_modules.py +++ b/test/test_modules.py @@ -23,7 +23,7 @@ from __future__ import division, print_function # no fixtures needed -import pytest +#import pytest from secop.datatypes import BoolType, EnumType, FloatRange from secop.metaclass import ModuleMeta @@ -138,10 +138,6 @@ def test_ModuleMeta(): assert acs is not None else: # do not check object or mixin acs = {} - with pytest.raises(AttributeError): - assert baseclass.commands - with pytest.raises(AttributeError): - assert baseclass.parameters for n, o in acs.items(): # check that class accessibles are not reused as instance accessibles assert o not in params_found