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:
2021-01-19 17:20:53 +01:00
parent 2d310bc612
commit bc33933a1a
35 changed files with 655 additions and 275 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 = {}