update doc
- add properties, parameters and commands to the doc string autoatically - change names to "Frappy" - started tutorial - changed doc structure slightly Change-Id: I87bef91384d138c738d12ddcf3a1de7f758a0973
This commit is contained in:
@@ -36,3 +36,4 @@ from secop.metaclass import Done
|
||||
from secop.iohandler import IOHandler, IOHandlerBase
|
||||
from secop.stringio import StringIO, HasIodev
|
||||
from secop.proxy import SecNode, Proxy, proxy_class
|
||||
from secop.poller import AUTO, REGULAR, SLOW, DYNAMIC
|
||||
|
||||
@@ -53,8 +53,8 @@ UNLIMITED = 1 << 64 # internal limit for integers, is probably high enough for
|
||||
Parser = Parser()
|
||||
|
||||
|
||||
# base class for all DataTypes
|
||||
class DataType(HasProperties):
|
||||
"""base class for all data types"""
|
||||
IS_COMMAND = False
|
||||
unit = ''
|
||||
default = None
|
||||
@@ -157,8 +157,14 @@ class Stub(DataType):
|
||||
|
||||
# SECoP types:
|
||||
|
||||
|
||||
class FloatRange(DataType):
|
||||
"""Restricted float type"""
|
||||
"""(restricted) float type
|
||||
|
||||
:param minval: (property **min**)
|
||||
:param maxval: (property **max**)
|
||||
"""
|
||||
|
||||
properties = {
|
||||
'min': Property('low limit', Stub('FloatRange'), extname='min', default=-sys.float_info.max),
|
||||
'max': Property('high limit', Stub('FloatRange'), extname='max', default=sys.float_info.max),
|
||||
@@ -170,11 +176,11 @@ class FloatRange(DataType):
|
||||
extname='relative_resolution', default=1.2e-7),
|
||||
}
|
||||
|
||||
def __init__(self, minval=None, maxval=None, **kwds):
|
||||
def __init__(self, minval=None, maxval=None, **properties):
|
||||
super().__init__()
|
||||
kwds['min'] = minval if minval is not None else -sys.float_info.max
|
||||
kwds['max'] = maxval if maxval is not None else sys.float_info.max
|
||||
self.set_properties(**kwds)
|
||||
properties['min'] = minval if minval is not None else -sys.float_info.max
|
||||
properties['max'] = maxval if maxval is not None else sys.float_info.max
|
||||
self.set_properties(**properties)
|
||||
|
||||
def checkProperties(self):
|
||||
self.default = 0 if self.min <= 0 <= self.max else self.min
|
||||
@@ -236,7 +242,11 @@ class FloatRange(DataType):
|
||||
|
||||
|
||||
class IntRange(DataType):
|
||||
"""Restricted int type"""
|
||||
"""restricted int type
|
||||
|
||||
:param minval: (property **min**)
|
||||
:param maxval: (property **max**)
|
||||
"""
|
||||
properties = {
|
||||
'min': Property('minimum value', Stub('IntRange', -UNLIMITED, UNLIMITED), extname='min', mandatory=True),
|
||||
'max': Property('maximum value', Stub('IntRange', -UNLIMITED, UNLIMITED), extname='max', mandatory=True),
|
||||
@@ -296,10 +306,14 @@ class IntRange(DataType):
|
||||
|
||||
|
||||
class ScaledInteger(DataType):
|
||||
"""Scaled integer int type
|
||||
"""scaled integer (= fixed resolution float) type
|
||||
|
||||
note: limits are for the scaled value (i.e. the internal value)
|
||||
the scale is only used for calculating to/from transport serialisation"""
|
||||
:param minval: (property **min**)
|
||||
:param maxval: (property **max**)
|
||||
|
||||
note: limits are for the scaled float value
|
||||
the scale is only used for calculating to/from transport serialisation
|
||||
"""
|
||||
properties = {
|
||||
'scale': Property('scale factor', FloatRange(sys.float_info.min), extname='scale', mandatory=True),
|
||||
'min': Property('low limit', FloatRange(), extname='min', mandatory=True),
|
||||
@@ -312,7 +326,7 @@ class ScaledInteger(DataType):
|
||||
extname='relative_resolution', default=1.2e-7),
|
||||
}
|
||||
|
||||
def __init__(self, scale, minval=None, maxval=None, absolute_resolution=None, **kwds):
|
||||
def __init__(self, scale, minval=None, maxval=None, absolute_resolution=None, **properties):
|
||||
super().__init__()
|
||||
scale = float(scale)
|
||||
if absolute_resolution is None:
|
||||
@@ -321,7 +335,7 @@ class ScaledInteger(DataType):
|
||||
min=DEFAULT_MIN_INT * scale if minval is None else float(minval),
|
||||
max=DEFAULT_MAX_INT * scale if maxval is None else float(maxval),
|
||||
absolute_resolution=absolute_resolution,
|
||||
**kwds)
|
||||
**properties)
|
||||
|
||||
def checkProperties(self):
|
||||
self.default = 0 if self.min <= 0 <= self.max else self.min
|
||||
@@ -401,14 +415,20 @@ class ScaledInteger(DataType):
|
||||
|
||||
|
||||
class EnumType(DataType):
|
||||
"""enumeration
|
||||
|
||||
def __init__(self, enum_or_name='', **kwds):
|
||||
:param enum_or_name: the name of the Enum or an Enum to inherit from
|
||||
:param members: members=<members dict>
|
||||
|
||||
other keywords: (additional) members
|
||||
"""
|
||||
def __init__(self, enum_or_name='', **members):
|
||||
super().__init__()
|
||||
if 'members' in kwds:
|
||||
kwds = dict(kwds)
|
||||
kwds.update(kwds['members'])
|
||||
kwds.pop('members')
|
||||
self._enum = Enum(enum_or_name, **kwds)
|
||||
if 'members' in members:
|
||||
members = dict(members)
|
||||
members.update(members['members'])
|
||||
members.pop('members')
|
||||
self._enum = Enum(enum_or_name, **members)
|
||||
self.default = self._enum[self._enum.members[0]]
|
||||
|
||||
def copy(self):
|
||||
@@ -448,6 +468,10 @@ class EnumType(DataType):
|
||||
|
||||
|
||||
class BLOBType(DataType):
|
||||
"""binary large object
|
||||
|
||||
internally treated as bytes
|
||||
"""
|
||||
properties = {
|
||||
'minbytes': Property('minimum number of bytes', IntRange(0), extname='minbytes',
|
||||
default=0),
|
||||
@@ -511,6 +535,9 @@ class BLOBType(DataType):
|
||||
|
||||
|
||||
class StringType(DataType):
|
||||
"""string
|
||||
|
||||
"""
|
||||
properties = {
|
||||
'minchars': Property('minimum number of character points', IntRange(0, UNLIMITED),
|
||||
extname='minchars', default=0),
|
||||
@@ -520,11 +547,11 @@ class StringType(DataType):
|
||||
Stub('BoolType'), extname='isUTF8', default=False),
|
||||
}
|
||||
|
||||
def __init__(self, minchars=0, maxchars=None, **kwds):
|
||||
def __init__(self, minchars=0, maxchars=None, **properties):
|
||||
super().__init__()
|
||||
if maxchars is None:
|
||||
maxchars = minchars or UNLIMITED
|
||||
self.set_properties(minchars=minchars, maxchars=maxchars, **kwds)
|
||||
self.set_properties(minchars=minchars, maxchars=maxchars, **properties)
|
||||
|
||||
def checkProperties(self):
|
||||
self.default = ' ' * self.minchars
|
||||
@@ -602,6 +629,9 @@ class TextType(StringType):
|
||||
|
||||
|
||||
class BoolType(DataType):
|
||||
"""boolean
|
||||
|
||||
"""
|
||||
default = False
|
||||
|
||||
def export_datatype(self):
|
||||
@@ -646,6 +676,9 @@ Stub.fix_datatypes()
|
||||
|
||||
|
||||
class ArrayOf(DataType):
|
||||
"""data structure with fields of homogeneous type
|
||||
|
||||
"""
|
||||
properties = {
|
||||
'minlen': Property('minimum number of elements', IntRange(0), extname='minlen',
|
||||
default=0),
|
||||
@@ -743,6 +776,9 @@ class ArrayOf(DataType):
|
||||
|
||||
|
||||
class TupleOf(DataType):
|
||||
"""data structure with fields of inhomogeneous type
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, *members):
|
||||
super().__init__()
|
||||
@@ -813,7 +849,9 @@ class ImmutableDict(dict):
|
||||
|
||||
|
||||
class StructOf(DataType):
|
||||
"""data structure with named fields
|
||||
|
||||
"""
|
||||
def __init__(self, optional=None, **members):
|
||||
super().__init__()
|
||||
self.members = members
|
||||
@@ -890,6 +928,10 @@ class StructOf(DataType):
|
||||
|
||||
|
||||
class CommandType(DataType):
|
||||
"""command
|
||||
|
||||
a pseudo datatype for commands with arguments and return values
|
||||
"""
|
||||
IS_COMMAND = True
|
||||
|
||||
def __init__(self, argument=None, result=None):
|
||||
@@ -948,8 +990,8 @@ class CommandType(DataType):
|
||||
raise BadValueError('incompatible datatypes')
|
||||
|
||||
|
||||
|
||||
# internally used datatypes (i.e. only for programming the SEC-node)
|
||||
|
||||
class DataTypeType(DataType):
|
||||
def __call__(self, value):
|
||||
"""check if given value (a python obj) is a valid datatype
|
||||
@@ -1111,7 +1153,10 @@ def get_datatype(json, pname=''):
|
||||
"""returns a DataType object from description
|
||||
|
||||
inverse of <DataType>.export_datatype()
|
||||
the pname argument, if given, is used to name EnumTypes from the parameter name
|
||||
|
||||
:param json: the datainfo object as returned from json.loads
|
||||
:param pname: if given, used to name EnumTypes from the parameter name
|
||||
:return: the datatype (instance of DataType)
|
||||
"""
|
||||
if json is None:
|
||||
return json
|
||||
|
||||
@@ -197,20 +197,18 @@ class IOHandler(IOHandlerBase):
|
||||
the same format as the arguments for the change command.
|
||||
Examples: devices from LakeShore, PPMS
|
||||
|
||||
implementing classes may override the following class variables
|
||||
"""
|
||||
CMDARGS = [] # list of properties or parameters to be used for building some of the the query and change commands
|
||||
CMDSEPARATOR = None # if not None, it is possible to join a command and a query with the given separator
|
||||
:param group: the handler group (used for analyze_<group> and change_<group>)
|
||||
:param querycmd: the command for a query, may contain named formats for cmdargs
|
||||
:param replyfmt: the format for reading the reply with some scanf like behaviour
|
||||
:param changecmd: the first part of the change command (without values), may be
|
||||
omitted if no write happens
|
||||
|
||||
"""
|
||||
CMDARGS = [] #: list of properties or parameters to be used for building some of the the query and change commands
|
||||
CMDSEPARATOR = None #: if not None, it is possible to join a command and a query with the given separator
|
||||
|
||||
def __init__(self, group, querycmd, replyfmt, changecmd=None):
|
||||
"""initialize the IO handler
|
||||
|
||||
group: the handler group (used for analyze_<group> and change_<group>)
|
||||
querycmd: the command for a query, may contain named formats for cmdargs
|
||||
replyfmt: the format for reading the reply with some scanf like behaviour
|
||||
changecmd: the first part of the change command (without values), may be
|
||||
omitted if no write happens
|
||||
"""
|
||||
"""initialize the IO handler"""
|
||||
self.group = group
|
||||
self.parameters = set()
|
||||
self._module_class = None
|
||||
@@ -269,7 +267,7 @@ class IOHandler(IOHandlerBase):
|
||||
return self.read
|
||||
|
||||
def read(self, module):
|
||||
"""write values from module"""
|
||||
# read values from module
|
||||
assert module.__class__ == self._module_class
|
||||
try:
|
||||
# do a read of the current hw values
|
||||
@@ -293,7 +291,8 @@ class IOHandler(IOHandlerBase):
|
||||
def get_write_func(self, pname):
|
||||
"""returns the write function passed to the metaclass
|
||||
|
||||
If pre_wfunc is given, it is to be called before change_<group>.
|
||||
:param pname: the parameter name
|
||||
|
||||
May be overriden to return None, if not used
|
||||
"""
|
||||
|
||||
@@ -304,7 +303,7 @@ class IOHandler(IOHandlerBase):
|
||||
return wfunc
|
||||
|
||||
def write(self, module, pname, value):
|
||||
"""write value to the module"""
|
||||
# write value to parameter pname of the module
|
||||
assert module.__class__ == self._module_class
|
||||
force_read = False
|
||||
valuedict = {pname: value}
|
||||
|
||||
@@ -126,7 +126,7 @@ class AsynConn:
|
||||
self._rxbuffer += data
|
||||
|
||||
def readbytes(self, nbytes, timeout=None):
|
||||
"""read one line
|
||||
"""read a fixed number of bytes
|
||||
|
||||
return either <nbytes> bytes or None if not enough data available within 1 sec (self.timeout)
|
||||
if a non-zero timeout is given, a timeout error is raised instead of returning None
|
||||
|
||||
@@ -270,7 +270,10 @@ class Enum(dict):
|
||||
self.name = name
|
||||
|
||||
def __getattr__(self, key):
|
||||
return self[key]
|
||||
try:
|
||||
return self[key]
|
||||
except KeyError as e:
|
||||
raise AttributeError(str(e))
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
if self.name and key != 'name':
|
||||
@@ -286,7 +289,8 @@ class Enum(dict):
|
||||
raise TypeError('Enum %r can not be changed!' % self.name)
|
||||
|
||||
def __repr__(self):
|
||||
return '<Enum %r (%d values)>' % (self.name, len(self)//2)
|
||||
return 'Enum(%r, %s)' % (self.name, ', '.join('%s=%d' % (m.name, m.value) for m in self.members))
|
||||
# return '<Enum %r (%d values)>' % (self.name, len(self)//2)
|
||||
|
||||
def __call__(self, key):
|
||||
return self[key]
|
||||
|
||||
@@ -28,7 +28,7 @@ from collections import OrderedDict
|
||||
from secop.errors import ProgrammingError, BadValueError
|
||||
from secop.params import Command, Override, Parameter
|
||||
from secop.datatypes import EnumType
|
||||
from secop.properties import PropertyMeta
|
||||
from secop.properties import PropertyMeta, add_extra_doc
|
||||
|
||||
|
||||
class Done:
|
||||
@@ -206,6 +206,10 @@ class ModuleMeta(PropertyMeta):
|
||||
raise ProgrammingError('%r: command %r has to be specified '
|
||||
'explicitly!' % (name, attrname[3:]))
|
||||
|
||||
add_extra_doc(newtype, '**parameters**',
|
||||
{k: p for k, p in accessibles.items() if isinstance(p, Parameter)})
|
||||
add_extra_doc(newtype, '**commands**',
|
||||
{k: p for k, p in accessibles.items() if isinstance(p, Command)})
|
||||
attrs['__constructed__'] = True
|
||||
return newtype
|
||||
|
||||
|
||||
@@ -46,19 +46,21 @@ from secop.poller import Poller, BasicPoller
|
||||
|
||||
|
||||
class Module(HasProperties, metaclass=ModuleMeta):
|
||||
"""Basic Module
|
||||
"""basic module
|
||||
|
||||
ALL secop Modules derive from this
|
||||
all SECoP modules derive from this.
|
||||
|
||||
note: within Modules, parameters should only be addressed as self.<pname>
|
||||
i.e. self.value, self.target etc...
|
||||
note: within modules, parameters should only be addressed as ``self.<pname>``
|
||||
i.e. ``self.value``, ``self.target`` etc...
|
||||
these are accessing the cached version.
|
||||
they can also be written to (which auto-calls self.write_<pname> and
|
||||
generate an async update)
|
||||
they can also be written to, generating an async update
|
||||
|
||||
if you want to 'update from the hardware', call self.read_<pname>() instead
|
||||
if you want to 'update from the hardware', call ``self.read_<pname>()`` instead
|
||||
the return value of this method will be used as the new cached value and
|
||||
be an async update sent automatically.
|
||||
|
||||
if you want to 'update the hardware' call ``self.write_<pname>(<new value>)``.
|
||||
The return value of this method will also update the cache.
|
||||
"""
|
||||
# static properties, definitions in derived classes should overwrite earlier ones.
|
||||
# note: properties don't change after startup and are usually filled
|
||||
@@ -78,7 +80,6 @@ class Module(HasProperties, metaclass=ModuleMeta):
|
||||
extname='implementation'),
|
||||
'interface_classes': Property('Offical highest Interface-class of the module', ArrayOf(StringType()),
|
||||
extname='interface_classes'),
|
||||
# what else?
|
||||
}
|
||||
|
||||
# properties, parameters and commands are auto-merged upon subclassing
|
||||
@@ -88,7 +89,7 @@ class Module(HasProperties, metaclass=ModuleMeta):
|
||||
# reference to the dispatcher (used for sending async updates)
|
||||
DISPATCHER = None
|
||||
|
||||
pollerClass = Poller
|
||||
pollerClass = Poller #: default poller used
|
||||
|
||||
def __init__(self, name, logger, cfgdict, srv):
|
||||
# remember the dispatcher object (for the async callbacks)
|
||||
@@ -401,12 +402,7 @@ class Module(HasProperties, metaclass=ModuleMeta):
|
||||
|
||||
|
||||
class Readable(Module):
|
||||
"""Basic readable Module
|
||||
|
||||
providing the readonly parameter 'value' and 'status'
|
||||
|
||||
Also allow configurable polling per 'pollinterval' parameter.
|
||||
"""
|
||||
"""basic readable module"""
|
||||
# pylint: disable=invalid-name
|
||||
Status = Enum('Status',
|
||||
IDLE = 100,
|
||||
@@ -415,7 +411,7 @@ class Readable(Module):
|
||||
ERROR = 400,
|
||||
DISABLED = 0,
|
||||
UNKNOWN = 401,
|
||||
)
|
||||
) #: status codes
|
||||
parameters = {
|
||||
'value': Parameter('current value of the Module', readonly=True,
|
||||
datatype=FloatRange(),
|
||||
@@ -478,10 +474,7 @@ class Readable(Module):
|
||||
|
||||
|
||||
class Writable(Readable):
|
||||
"""Basic Writable Module
|
||||
|
||||
providing a settable 'target' parameter to those of a Readable
|
||||
"""
|
||||
"""basic writable module"""
|
||||
parameters = {
|
||||
'target': Parameter('target value of the Module',
|
||||
default=0, readonly=False, datatype=FloatRange(),
|
||||
@@ -490,13 +483,9 @@ class Writable(Readable):
|
||||
|
||||
|
||||
class Drivable(Writable):
|
||||
"""Basic Drivable Module
|
||||
"""basic drivable module"""
|
||||
|
||||
provides a stop command to interrupt actions.
|
||||
Also status gets extended with a BUSY state indicating a running action.
|
||||
"""
|
||||
|
||||
Status = Enum(Readable.Status, BUSY=300)
|
||||
Status = Enum(Readable.Status, BUSY=300) #: Status codes
|
||||
|
||||
commands = {
|
||||
'stop': Command(
|
||||
@@ -511,11 +500,18 @@ class Drivable(Writable):
|
||||
}
|
||||
|
||||
def isBusy(self, status=None):
|
||||
"""helper function for treating substates of BUSY correctly"""
|
||||
"""check for busy, treating substates correctly
|
||||
|
||||
returns True when busy (also when finalizing)
|
||||
"""
|
||||
return 300 <= (status or self.status)[0] < 400
|
||||
|
||||
def isDriving(self, status=None):
|
||||
"""helper function (finalize is busy, not driving)"""
|
||||
"""check for driving, treating status substates correctly
|
||||
|
||||
returns True when busy, but not finalizing
|
||||
"""
|
||||
""""""
|
||||
return 300 <= (status or self.status)[0] < 390
|
||||
|
||||
# improved polling: may poll faster if module is BUSY
|
||||
@@ -537,13 +533,13 @@ class Drivable(Writable):
|
||||
return fastpoll
|
||||
|
||||
def do_stop(self):
|
||||
"""default implementation of the stop command
|
||||
|
||||
by default does nothing."""
|
||||
# default implementation of the stop command
|
||||
# by default does nothing
|
||||
pass
|
||||
|
||||
|
||||
class Communicator(Module):
|
||||
"""Basic communication Module
|
||||
"""basic communication module
|
||||
|
||||
providing no parameters, but a 'communicate' command.
|
||||
"""
|
||||
@@ -555,8 +551,24 @@ class Communicator(Module):
|
||||
),
|
||||
}
|
||||
|
||||
def do_communicate(self, command):
|
||||
"""communicate command
|
||||
|
||||
:param command: the command to be sent
|
||||
:return: the reply
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class Attached(Property):
|
||||
"""a special property, defining an attached modle
|
||||
|
||||
assign a module name to this property in the cfg file,
|
||||
and the server will create an attribute with this module
|
||||
|
||||
:param attrname: the name of the to be created attribute. if not given
|
||||
the attribute name is the property name prepended by an underscore.
|
||||
"""
|
||||
# we can not put this to properties.py, as it needs datatypes
|
||||
def __init__(self, attrname=None):
|
||||
self.attrname = attrname
|
||||
|
||||
@@ -68,50 +68,41 @@ class Accessible(HasProperties, CountedObj):
|
||||
|
||||
|
||||
class Parameter(Accessible):
|
||||
"""storage for Parameter settings + value + qualifiers
|
||||
|
||||
if readonly is False, the value can be changed (by code, or remote)
|
||||
if no default is given, the parameter MUST be specified in the configfile
|
||||
during startup, value is initialized with the default value or
|
||||
from the config file if specified there
|
||||
|
||||
poll can be:
|
||||
- None: will be converted to True/False if handler is/is not None
|
||||
- False or 0 (never poll this parameter)
|
||||
- True or > 0 (poll this parameter)
|
||||
- the exact meaning depends on the used poller
|
||||
meaning for secop.poller.Poller:
|
||||
- 1 or True (AUTO), converted to SLOW (readonly=False), DYNAMIC('status' and 'value') or REGULAR(else)
|
||||
- 2 (SLOW), polled with lower priority and a multiple of pollperiod
|
||||
- 3 (REGULAR), polled with pollperiod
|
||||
- 4 (DYNAMIC), polled with pollperiod, if not BUSY, else with a fraction of pollperiod
|
||||
meaning for the basicPoller:
|
||||
- True or 1 (poll this every pollinterval)
|
||||
- positive int (poll every N(th) pollinterval)
|
||||
- negative int (normally poll every N(th) pollinterval, if module is busy, poll every pollinterval)
|
||||
note: Drivable (and derived classes) poll with 10 fold frequency if module is busy....
|
||||
"""
|
||||
"""storage for parameter settings + value + qualifiers"""
|
||||
# poll: meaning for the basicPoller:
|
||||
# - True or 1 (poll this every pollinterval)
|
||||
# - positive int (poll every N(th) pollinterval)
|
||||
# - negative int (normally poll every N(th) pollinterval, if module is busy, poll every pollinterval)
|
||||
# note: Drivable (and derived classes) poll with 10 fold frequency if module is busy....
|
||||
|
||||
properties = {
|
||||
'description': Property('Description of the Parameter', TextType(),
|
||||
'description': Property('mandatory description of the parameter', TextType(),
|
||||
extname='description', mandatory=True),
|
||||
'datatype': Property('Datatype of the Parameter', DataTypeType(),
|
||||
'datatype': Property('datatype of the Parameter (SECoP datainfo)', DataTypeType(),
|
||||
extname='datainfo', mandatory=True),
|
||||
'readonly': Property('Is the Parameter readonly? (vs. changeable via SECoP)', BoolType(),
|
||||
'readonly': Property('not changeable via SECoP (default True)', BoolType(),
|
||||
extname='readonly', mandatory=True),
|
||||
'group': Property('Optional parameter group this parameter belongs to', StringType(),
|
||||
'group': Property('optional parameter group this parameter belongs to', StringType(),
|
||||
extname='group', default=''),
|
||||
'visibility': Property('Optional visibility hint', EnumType('visibility', user=1, advanced=2, expert=3),
|
||||
'visibility': Property('optional visibility hint', EnumType('visibility', user=1, advanced=2, expert=3),
|
||||
extname='visibility', default=1),
|
||||
'constant': Property('Optional constant value for constant parameters', ValueType(),
|
||||
'constant': Property('optional constant value for constant parameters', ValueType(),
|
||||
extname='constant', default=None, mandatory=False),
|
||||
'default': Property('Default (startup) value of this parameter if it can not be read from the hardware.',
|
||||
'default': Property('default (startup) value of this parameter if it can not be read from the hardware.',
|
||||
ValueType(), export=False, default=None, mandatory=False),
|
||||
'export': Property('Is this parameter accessible via SECoP? (vs. internal parameter)',
|
||||
'export': Property('[internal] is this parameter accessible via SECoP? (vs. internal parameter)',
|
||||
OrType(BoolType(), StringType()), export=False, default=True),
|
||||
'poll': Property('Polling indicator', NoneOr(IntRange()), export=False, default=None),
|
||||
'needscfg': Property('needs value in config', NoneOr(BoolType()), export=False, default=None),
|
||||
'optional': Property('[Internal] is this parameter optional?', BoolType(), export=False,
|
||||
'poll': Property('[internal] polling indicator, may be:\n' + '\n '.join(['',
|
||||
'* None (omitted): will be converted to True/False if handler is/is not None',
|
||||
'* False or 0 (never poll this parameter)',
|
||||
'* True or 1 (AUTO), converted to SLOW (readonly=False), '
|
||||
'DYNAMIC (*status* and *value*) or REGULAR (else)',
|
||||
'* 2 (SLOW), polled with lower priority and a multiple of pollinterval',
|
||||
'* 3 (REGULAR), polled with pollperiod',
|
||||
'* 4 (DYNAMIC), if BUSY, with a fraction of pollinterval, else polled with pollperiod']),
|
||||
NoneOr(IntRange()), export=False, default=None),
|
||||
'needscfg': Property('[internal] needs value in config', NoneOr(BoolType()), export=False, default=None),
|
||||
'optional': Property('[internal] is this parameter optional?', BoolType(), export=False,
|
||||
settable=False, default=False),
|
||||
'handler': Property('[internal] overload the standard read and write functions',
|
||||
ValueType(), export=False, default=None, mandatory=False, settable=False),
|
||||
@@ -225,7 +216,7 @@ class Override(CountedObj):
|
||||
"""Stores the overrides to be applied to a Parameter
|
||||
|
||||
note: overrides are applied by the metaclass during class creating
|
||||
reorder= True: use position of Override instead of inherited for the order
|
||||
reorder=True: use position of Override instead of inherited for the order
|
||||
"""
|
||||
def __init__(self, description="", datatype=None, *, reorder=False, **kwds):
|
||||
super(Override, self).__init__()
|
||||
@@ -273,21 +264,21 @@ class Command(Accessible):
|
||||
"""storage for Commands settings (description + call signature...)
|
||||
"""
|
||||
properties = {
|
||||
'description': Property('Description of the Command', TextType(),
|
||||
'description': Property('description of the command', TextType(),
|
||||
extname='description', export=True, mandatory=True),
|
||||
'group': Property('Optional command group of the command.', StringType(),
|
||||
'group': Property('optional command group of the command.', StringType(),
|
||||
extname='group', export=True, default=''),
|
||||
'visibility': Property('Optional visibility hint', EnumType('visibility', user=1, advanced=2, expert=3),
|
||||
'visibility': Property('optional visibility hint', EnumType('visibility', user=1, advanced=2, expert=3),
|
||||
extname='visibility', export=True, default=1),
|
||||
'export': Property('[internal] Flag: is the command accessible via SECoP? (vs. pure internal use)',
|
||||
'export': Property('[internal] flag: is the command accessible via SECoP? (vs. pure internal use)',
|
||||
OrType(BoolType(), StringType()), export=False, default=True),
|
||||
'optional': Property('[internal] is the command optional to implement? (vs. mandatory)',
|
||||
BoolType(), export=False, default=False, settable=False),
|
||||
'datatype': Property('[internal] datatype of the command, auto generated from \'argument\' and \'result\'',
|
||||
DataTypeType(), extname='datainfo', mandatory=True),
|
||||
'argument': Property('Datatype of the argument to the command, or None.',
|
||||
'argument': Property('datatype of the argument to the command, or None.',
|
||||
NoneOr(DataTypeType()), export=False, mandatory=True),
|
||||
'result': Property('Datatype of the result from the command, or None.',
|
||||
'result': Property('datatype of the result from the command, or None.',
|
||||
NoneOr(DataTypeType()), export=False, mandatory=True),
|
||||
}
|
||||
|
||||
|
||||
@@ -40,10 +40,10 @@ from secop.lib import mkthread
|
||||
from secop.errors import ProgrammingError
|
||||
|
||||
# poll types:
|
||||
AUTO = 1 # equivalent to True, converted to REGULAR, SLOW or DYNAMIC
|
||||
SLOW = 2
|
||||
REGULAR = 3
|
||||
DYNAMIC = 4
|
||||
AUTO = 1 #: equivalent to True, converted to REGULAR, SLOW or DYNAMIC
|
||||
SLOW = 2 #: polling with low priority and increased poll interval (used by default when readonly=False)
|
||||
REGULAR = 3 #: polling with standard interval (used by default for read only parameters except status and value)
|
||||
DYNAMIC = 4 #: polling with shorter poll interval when BUSY (used by default for status and value)
|
||||
|
||||
|
||||
class PollerBase:
|
||||
|
||||
@@ -30,13 +30,19 @@ from secop.errors import ProgrammingError, ConfigError, BadValueError
|
||||
|
||||
# storage for 'properties of a property'
|
||||
class Property:
|
||||
'''base class holding info about a property
|
||||
"""base class holding info about a property
|
||||
|
||||
properties are only sent to the ECS if export is True, or an extname is set
|
||||
if mandatory is True, they MUST have a value in the cfg file assigned to them.
|
||||
otherwise, this is optional in which case the default value is applied.
|
||||
All values MUST pass the datatype.
|
||||
'''
|
||||
:param description: mandatory
|
||||
:param datatype: the datatype to be accepted. not only to the SECoP datatypes are allowed!
|
||||
also for example ``ValueType()`` (any type!), ``NoneOr(...)``, etc.
|
||||
:param default: a default value. SECoP properties are normally not sent to the ECS,
|
||||
when they match the default
|
||||
:param extname: external name
|
||||
:param export: sent to the ECS when True. defaults to True, when ``extname`` is given
|
||||
:param mandatory: defaults to True, when ``default`` is not given. indicates that it must have a value
|
||||
assigned from the cfg file (or, in case of a module property, it may be assigned as a class attribute)
|
||||
:param settable: settable from the cfg file
|
||||
"""
|
||||
# note: this is intended to be used on base classes.
|
||||
# the VALUES of the properties are on the instances!
|
||||
def __init__(self, description, datatype, default=None, extname='', export=False, mandatory=None, settable=True):
|
||||
@@ -79,6 +85,17 @@ class Properties(OrderedDict):
|
||||
raise ProgrammingError('deleting Properties is not supported!')
|
||||
|
||||
|
||||
def add_extra_doc(cls, title, items):
|
||||
"""add bulleted list to doc string
|
||||
|
||||
using names and description of items
|
||||
"""
|
||||
bulletlist = ['\n - **%s** - %s' % (k, p.description) for k, p in items.items()]
|
||||
if bulletlist:
|
||||
doctext = '%s\n\n%s' % (title, ''.join(bulletlist))
|
||||
cls.__doc__ = (cls.__doc__ or '') + '\n\n %s\n' % doctext
|
||||
|
||||
|
||||
class PropertyMeta(type):
|
||||
"""Metaclass for HasProperties
|
||||
|
||||
@@ -124,6 +141,8 @@ class PropertyMeta(type):
|
||||
raise ProgrammingError('%r: property %r can not be set to %r'
|
||||
% (newtype, k, attrs[k]))
|
||||
setattr(newtype, k, property(getter))
|
||||
|
||||
add_extra_doc(newtype, '**properties**', attrs.get('properties', {})) # only new properties
|
||||
return newtype
|
||||
|
||||
|
||||
|
||||
@@ -49,8 +49,10 @@ class StringIO(Communicator):
|
||||
Property('used encoding', datatype=StringType(),
|
||||
default='ascii', settable=True),
|
||||
'identification':
|
||||
Property('a list of tuples with commands and expected responses as regexp',
|
||||
datatype=ArrayOf(TupleOf(StringType(),StringType())), default=[], export=False),
|
||||
Property('identification\n\n'
|
||||
'a list of tuples with commands and expected responses as regexp, '
|
||||
'to be sent on connect',
|
||||
datatype=ArrayOf(TupleOf(StringType(), StringType())), default=[], export=False),
|
||||
}
|
||||
parameters = {
|
||||
'timeout':
|
||||
@@ -65,7 +67,7 @@ class StringIO(Communicator):
|
||||
commands = {
|
||||
'multicomm':
|
||||
Command('execute multiple commands in one go',
|
||||
argument=ArrayOf(StringType()), result= ArrayOf(StringType()))
|
||||
argument=ArrayOf(StringType()), result=ArrayOf(StringType()))
|
||||
}
|
||||
|
||||
_reconnectCallbacks = None
|
||||
@@ -221,7 +223,7 @@ class HasIodev(Module):
|
||||
"""
|
||||
properties = {
|
||||
'iodev': Attached(),
|
||||
'uri': Property('uri for auto creation of iodev', StringType(), default=''),
|
||||
'uri': Property('uri for automatic creation of the attached communication module', StringType(), default=''),
|
||||
}
|
||||
|
||||
iodevDict = {}
|
||||
|
||||
Reference in New Issue
Block a user