diff --git a/doc/source/_static/custom.css b/doc/source/_static/custom.css index 60a9232..5f508cc 100644 --- a/doc/source/_static/custom.css +++ b/doc/source/_static/custom.css @@ -1,9 +1,10 @@ -/* this is for the sphinx_rtd_theme +/* this is for the sphinx_rtd_theme */ div.wy-nav-content { max-width: 100% !important; } -*/ + +/* this is for the alabaser theme */ div.body { max-width: 100%; } @@ -32,4 +33,19 @@ pre, tt, code { } } +dd { + padding-bottom: 0.5em; +} + +/* make nested bullet lists nicer (ales too much space above inner nested list) */ +.rst-content .section ul li ul { + margin-top: 0px; + margin-bottom: 6px; +} + +/* make some bullet lists more dense (this rule exists in theme.css, but not important)*/ +.wy-plain-list-disc li p:last-child, .rst-content .section ul li p:last-child, .rst-content .toctree-wrapper ul li p:last-child, article ul li p:last-child { + margin-bottom: 0 !important; +} + diff --git a/doc/source/conf.py b/doc/source/conf.py index 70be1b6..052c9bc 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -89,9 +89,10 @@ pygments_style = 'sphinx' # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = True -# sort by source instead of alphabetic -autodoc_member_order = 'bysource' - +autodoc_default_options = { + 'member-order': 'bysource', + 'show-inheritance': True, +} default_role = 'any' # -- Options for HTML output ---------------------------------------------- @@ -99,9 +100,19 @@ default_role = 'any' # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -#import sphinx_rtd_theme -#html_theme = 'sphinx_rtd_theme' -html_theme = 'alabaster' +if False: # alabaster + html_theme = 'alabaster' + # Theme options are theme-specific and customize the look and feel of a theme + # further. For a list of options available for each theme, see the + # documentation. + # + html_theme_options = { + 'page_width': '100%', + 'fixed_sidebar': True, + } +else: + import sphinx_rtd_theme + html_theme = 'sphinx_rtd_theme' # If not None, a 'Last updated on:' timestamp is inserted at every page # bottom, using the given strftime format. @@ -110,14 +121,6 @@ html_theme = 'alabaster' html_last_updated_fmt = '%Y-%m-%d %H:%M' -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# -html_theme_options = { - 'page_width': '100%', - 'fixed_sidebar': True, -} # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, diff --git a/doc/source/facility/demo/cryo.rst b/doc/source/facility/demo/cryo.rst deleted file mode 100644 index ed0b353..0000000 --- a/doc/source/facility/demo/cryo.rst +++ /dev/null @@ -1,6 +0,0 @@ -Demo cryostat -============= - -.. automodule:: secop_demo.cryo - :members: - diff --git a/doc/source/facility/demo/index.rst b/doc/source/facility/demo/index.rst deleted file mode 100644 index 5db5d77..0000000 --- a/doc/source/facility/demo/index.rst +++ /dev/null @@ -1,12 +0,0 @@ -Demo -==== - -Specific sample environments ----------------------------- - -.. toctree:: - :maxdepth: 3 - - cryo - test - diff --git a/doc/source/facility/demo/test.rst b/doc/source/facility/demo/test.rst deleted file mode 100644 index ffdf65f..0000000 --- a/doc/source/facility/demo/test.rst +++ /dev/null @@ -1,6 +0,0 @@ -Test devices -============= - -.. automodule:: secop_demo.test - :members: - diff --git a/doc/source/facility/ess/index.rst b/doc/source/facility/ess/index.rst deleted file mode 100644 index d834df2..0000000 --- a/doc/source/facility/ess/index.rst +++ /dev/null @@ -1,11 +0,0 @@ -ESS -=== - -Frameworks ----------- - -.. toctree:: - :maxdepth: 3 - - epics - diff --git a/doc/source/facility/index.rst b/doc/source/facility/index.rst deleted file mode 100644 index 0b83d17..0000000 --- a/doc/source/facility/index.rst +++ /dev/null @@ -1,10 +0,0 @@ -Facility specific functionalities -================================= - -.. toctree:: - :maxdepth: 3 - - demo/index - mlz/index - ess/index - psi/index diff --git a/doc/source/facility/mlz/amagnet.rst b/doc/source/facility/mlz/amagnet.rst deleted file mode 100644 index 9883bdb..0000000 --- a/doc/source/facility/mlz/amagnet.rst +++ /dev/null @@ -1,6 +0,0 @@ -ANTARES magnet (amagnet) -======================== - -.. automodule:: secop_mlz.amagnet - :members: - diff --git a/doc/source/facility/mlz/entangle.rst b/doc/source/facility/mlz/entangle.rst deleted file mode 100644 index 8ac5a20..0000000 --- a/doc/source/facility/mlz/entangle.rst +++ /dev/null @@ -1,6 +0,0 @@ -Entangle -======== - -.. automodule:: secop_mlz.entangle - :members: - diff --git a/doc/source/facility/mlz/index.rst b/doc/source/facility/mlz/index.rst deleted file mode 100644 index 02f7a42..0000000 --- a/doc/source/facility/mlz/index.rst +++ /dev/null @@ -1,20 +0,0 @@ -MLZ -=== - -Frameworks ----------- - -.. toctree:: - :maxdepth: 3 - - entangle - - -Specific sample environments ----------------------------- - -.. toctree:: - :maxdepth: 3 - - amagnet - diff --git a/doc/source/facility/psi/index.rst b/doc/source/facility/psi/index.rst deleted file mode 100644 index 36b46e1..0000000 --- a/doc/source/facility/psi/index.rst +++ /dev/null @@ -1,10 +0,0 @@ -PSI -=== - -.. toctree:: - :maxdepth: 3 - - ppms - ls370res - - diff --git a/doc/source/facility/psi/ls370res.rst b/doc/source/facility/psi/ls370res.rst deleted file mode 100644 index 81c7e32..0000000 --- a/doc/source/facility/psi/ls370res.rst +++ /dev/null @@ -1,7 +0,0 @@ -LakeShore 370 resistivity -========================= - -.. automodule:: secop_psi.ls370res - :members: - - diff --git a/doc/source/facility/psi/ppms.rst b/doc/source/facility/psi/ppms.rst deleted file mode 100644 index a2e9fac..0000000 --- a/doc/source/facility/psi/ppms.rst +++ /dev/null @@ -1,7 +0,0 @@ -PPMS -==== - -.. automodule:: secop_psi.ppms - :members: - - diff --git a/doc/source/framework.rst b/doc/source/framework.rst deleted file mode 100644 index 4f5b307..0000000 --- a/doc/source/framework.rst +++ /dev/null @@ -1,67 +0,0 @@ -Framework documentation -======================= - - -Module Base Classes -------------------- - -.. autoclass:: secop.core.Module - :members: startModule - -.. autoclass:: secop.core.Readable - :members: pollerClass, Status - -.. autoclass:: secop.core.Writable - -.. autoclass:: secop.core.Drivable - :members: Status, isBusy, isDriving, do_stop - - -Parameters, Commands and Properties ------------------------------------ - -.. autoclass:: secop.core.Parameter -.. autoclass:: secop.core.Command -.. autoclass:: secop.core.Override -.. autoclass:: secop.core.Property -.. autoclass:: secop.core.Attached - - -Datatypes ---------- - -.. autoclass:: secop.core.FloatRange -.. autoclass:: secop.core.IntRange -.. autoclass:: secop.core.BoolType -.. autoclass:: secop.core.ScaledInteger -.. autoclass:: secop.core.EnumType -.. autoclass:: secop.core.StringType -.. autoclass:: secop.core.TupleOf -.. autoclass:: secop.core.ArrayOf -.. autoclass:: secop.core.StructOf -.. autoclass:: secop.core.BLOBType - - -Communication -------------- - -.. autoclass:: secop.core.Communicator - :members: do_communicate - -.. autoclass:: secop.core.StringIO - :members: do_communicate, do_multicomm - -.. autoclass:: secop.core.HasIodev - -.. autoclass:: secop.core.IOHandlerBase - :members: - -.. autoclass:: secop.core.IOHandler - :members: - - -Exception classes ------------------ - -.. automodule:: secop.errors - :members: diff --git a/doc/source/index.rst b/doc/source/index.rst index 970728d..8cb2774 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -1,18 +1,16 @@ -Welcome to the FRAPPY documentation! -==================================== +Frappy Programming Guide +======================== .. toctree:: :maxdepth: 2 - tutorial/tutorial - server - framework - facility/index - - -Indices and tables -================== + introduction + tutorial + reference + secop_psi + secop_demo + secop_mlz + secop_ess * :ref:`genindex` -* :ref:`modindex` -* :ref:`search` + diff --git a/doc/source/introduction.rst b/doc/source/introduction.rst new file mode 100644 index 0000000..2c70b39 --- /dev/null +++ b/doc/source/introduction.rst @@ -0,0 +1,70 @@ +Introduction +============ + +Frappy - a Python Framework for SECoP +------------------------------------- + +*Frappy* is a Python framework for creating Sample Environment Control Nodes (SEC Node) with +a SECoP interface. A *SEC Node* is a service, running usually a computer or microcomputer, +which accesses the hardware over the interfaces given by the manufacturer of the used +electronic devices. It provides access to the data in an abstracted form over the SECoP interface. +`SECoP `_ is a protocol for +communicating with Sample Environment and other mobile devices, specified by a committee of +the `ISSE `_. + +The Frappy framework deals with all the details of the SECoP protocol, so the programmer +can concentrate on the details of accessing the hardware with support for different types +of interfaces (TCP or Serial, ASCII or binary). However, the programmer should be aware of +the basic principle of the SECoP protocol: the hardware abstraction. + + +Hardware Abstraction +-------------------- + +The idea of hardware abstraction is to hide the details of hardware access from the SECoP interface. +A SECoP module is a logical component of an abstract view of the sample environment. +It is one independent value of measurement like a temperature or pressure or a physical output like +a current or voltage. This corresponds roughly to an EPICS channel or a NICOS device. On the +hardware side we may have devices with several channels, like a typical temperature controller, +which will be represented individual SECoP modules. +On the other hand a SECoP channel might be linked with several hardware devices, for example if +you imagine a superconducting magnet controller built of separate electronic devices like a power +supply, switch heater and coil temperature monitor. The latter case does not mean that we have +to hide the details in the SECoP interface. For an expert it might be useful to give at least +read access to hardware specific data by providing them as separate SECoP modules. But the +magnet module should be usable without knowledge of all the inner details. + +A SECoP module has: + +* **properties**: static information describing the module, for example a human readable + *description* of the module or information about the intended *visibility*. +* **parameters**: changing information about the state of a module (for example the *status* + containing information about the state of the module) or modifiable information influencing + the measurement (for example a "ramp" rate). +* **commands**: actions, for example *stop*. + +A SECoP module belongs to an interface class, mainly *Readable* or *Drivable*. A *Readable* +has at least the parameters *value* and *status*, a *Drivable* in addition *target*. *value* is +the main value of the module and is read only. *status* is a tuple (status code, status text), +and *target* is the target value. When the *target* parameter value of a *Drivable* changes, +the status code changes normally to a busy code. As soon as the target value is reached, +the status code changes back to an idle code, if no error occurs. + +**Programmers Hint:** before starting to code, choose carefully the main SECoP modules you want +to provide to the user. + + +Programming a Driver +-------------------- + +Programming a driver means extending one of the base classes like :class:`secop.modules.Readable` +or :class:`secop.modules.Drivable`. The parameters are defined in the dict :py:attr:`parameters`, as a +class attribute of the extended class, using the :class:`secop.params.Parameter` constructor, or in case +of altering the properties of an inherited parameter, :class:`secop.params.Override`. + +Parameters usually need a method :meth:`read_()` +implementing the code to retrieve their value from the hardware. Writeable parameters +(with the argument ``readonly=False``) usually need a method :meth:`write_()` +implementing how they are written to the hardware. Above methods may be omitted, when +there is no interaction with the hardware involved. + diff --git a/doc/source/reference.rst b/doc/source/reference.rst new file mode 100644 index 0000000..7f3d8fb --- /dev/null +++ b/doc/source/reference.rst @@ -0,0 +1,77 @@ +Reference +--------- + +Module Base Classes +................... + +.. autoclass:: secop.modules.Module + :members: earlyInit, initModule, startModule + +.. autoclass:: secop.modules.Readable + :members: pollerClass, Status + +.. autoclass:: secop.modules.Writable + +.. autoclass:: secop.modules.Drivable + :members: Status, isBusy, isDriving, do_stop + + +Parameters, Commands and Properties +................................... + +.. autoclass:: secop.params.Parameter +.. autoclass:: secop.params.Command +.. autoclass:: secop.params.Override +.. autoclass:: secop.properties.Property +.. autoclass:: secop.modules.Attached + :show-inheritance: + + + +Datatypes +......... + +.. autoclass:: secop.datatypes.FloatRange +.. autoclass:: secop.datatypes.IntRange +.. autoclass:: secop.datatypes.BoolType +.. autoclass:: secop.datatypes.ScaledInteger +.. autoclass:: secop.datatypes.EnumType +.. autoclass:: secop.datatypes.StringType +.. autoclass:: secop.datatypes.TupleOf +.. autoclass:: secop.datatypes.ArrayOf +.. autoclass:: secop.datatypes.StructOf +.. autoclass:: secop.datatypes.BLOBType + + + +Communication +............. + +.. autoclass:: secop.modules.Communicator + :show-inheritance: + :members: do_communicate + +.. autoclass:: secop.stringio.StringIO + :show-inheritance: + :members: do_communicate, do_multicomm + +.. autoclass:: secop.stringio.HasIodev + :show-inheritance: + +.. autoclass:: secop.iohandler.IOHandlerBase + :show-inheritance: + :members: + +.. autoclass:: secop.iohandler.IOHandler + :show-inheritance: + :members: + + +Exception classes +.....................-- + +.. automodule:: secop.errors + :members: + +.. include:: server.rst + diff --git a/doc/source/secop_demo.rst b/doc/source/secop_demo.rst new file mode 100644 index 0000000..7273d58 --- /dev/null +++ b/doc/source/secop_demo.rst @@ -0,0 +1,10 @@ +Demo +==== + +.. automodule:: secop_demo.cryo + :show-inheritance: + :members: + +.. automodule:: secop_demo.test + :show-inheritance: + :members: diff --git a/doc/source/facility/ess/epics.rst b/doc/source/secop_ess.rst similarity index 52% rename from doc/source/facility/ess/epics.rst rename to doc/source/secop_ess.rst index a101acd..7f115c7 100644 --- a/doc/source/facility/ess/epics.rst +++ b/doc/source/secop_ess.rst @@ -1,6 +1,9 @@ -EPICS modules -============= +ESS +--- + +EPICS +..... .. automodule:: secop_ess.epics + :show-inheritance: :members: - diff --git a/doc/source/secop_mlz.rst b/doc/source/secop_mlz.rst new file mode 100644 index 0000000..ec275ef --- /dev/null +++ b/doc/source/secop_mlz.rst @@ -0,0 +1,19 @@ +MLZ +--- + +Amagnet (Garfield) +.................. + +.. automodule:: secop_mlz.amagnet + :show-inheritance: + :members: + + +Entangle Framework +.................. + +.. automodule:: secop_mlz.entangle + :show-inheritance: + :members: + + diff --git a/doc/source/secop_psi.rst b/doc/source/secop_psi.rst new file mode 100644 index 0000000..0112651 --- /dev/null +++ b/doc/source/secop_psi.rst @@ -0,0 +1,19 @@ +PSI (SINQ) +---------- + +PPMS +.... + +.. automodule:: secop_psi.ppms + :show-inheritance: + :members: + +LakeShore 370 +............. + +Calibrated sensors and control loop not yet supported. + +.. automodule:: secop_psi.ls370res + :show-inheritance: + :members: + diff --git a/doc/source/server.rst b/doc/source/server.rst index 2e7abb3..e5ed364 100644 --- a/doc/source/server.rst +++ b/doc/source/server.rst @@ -1,8 +1,5 @@ -Configuring and Starting -======================== - Configuration -------------- +............. The configuration consists of a **NODE** section, an **INTERFACE** section and one section per SECoP module. @@ -51,7 +48,7 @@ the SECoP interface. Starting --------- +........ The Frappy server can be started via the **bin/secop-server** script. @@ -73,5 +70,3 @@ The Frappy server can be started via the **bin/secop-server** script. -q, --quiet suppress non-error messages -d, --daemonize run as daemon -t, --test check cfg files only - - diff --git a/doc/source/tutorial.rst b/doc/source/tutorial.rst new file mode 100644 index 0000000..c349f65 --- /dev/null +++ b/doc/source/tutorial.rst @@ -0,0 +1,7 @@ +Tutorial +-------- + +.. toctree:: + :maxdepth: 2 + + tutorial_helevel diff --git a/doc/source/tutorial/tutorial.rst b/doc/source/tutorial_helevel.rst similarity index 66% rename from doc/source/tutorial/tutorial.rst rename to doc/source/tutorial_helevel.rst index f602968..2e1b467 100644 --- a/doc/source/tutorial/tutorial.rst +++ b/doc/source/tutorial_helevel.rst @@ -1,64 +1,10 @@ -Frappy Programming Guide -======================== +HeLevel - a Simple Driver +========================= -Introduction ------------- - -*Frappy* is a Python framework for creating Sample Environment Control Nodes (SEC Node) with -a SECoP interface. A *SEC Node* is a service, running usually a computer or microcomputer, -which accesses the hardware over the interfaces given by the manufacturer of the used -electronic devices. It provides access to the data in an abstracted form over the SECoP interface. -`SECoP `_ is a protocol for -communicating with Sample Environment and other mobile devices, specified by a committee of -the `ISSE `_. - -The Frappy framework deals with all the details of the SECoP protocol, so the programmer -can concentrate on the details of accessing the hardware with support for different types -of interfaces (TCP or Serial, ASCII or binary). However, the programmer should be aware of -the basic principle of the SECoP protocol: the hardware abstraction. - -Hardware Abstraction --------------------- - -The idea of hardware abstraction is to hide the details of hardware access from the SECoP interface. -A SECoP module is a logical component of an abstract view of the sample environment. -It is one independent value of measurement like a temperature or physical output like a current or voltage. -This corresponds roughly to an EPICS channel or a NICOS device. On the hardware side we may have devices -with several channels, like a typical temperature controller, which will be represented individual SECoP modules. -On the other hand a SECoP channel might be linked with several hardware devices, for example if you imagine -a superconducting magnet controller built of seperate electronic devices like a power supply, switch heater -and coil temperature monitor. The latter case does not mean that we have to hide complete the details in the -SECoP interface. For an expert it might be useful to give at least read access to hardware specific data -by providing them as seperate SECoP modules. But the magnet module should be usable without knowledge of -all the inner details. - -A SECoP module has: - -* **properties**: static information describing the module, for example a human readable *description* of - the module or information about the intended *visibiliy*. -* **parameters**: changing information about the state of a module (for example the *status* containing - information about the state of the module )or modifiable information influencing the measurement - (for example a "ramp" rate) -* **commands**: actions, for example *stop* - -A SECoP module belongs to an interface class, mainly *Readable* or *Drivable*. A *Readable* has at least the -parameters *value* and *status*, a *Drivable* in addition *target*. *value* is the main value of the module -and is read only. *status* is a tuple (status code, status text), and *target* is the target value. -When the *target* parameter value of a *Drivable* changes, the status code changes normally to a busy code. -As soon as the target value is reached, the status code changes back to an idle code, if no error occurs. - -**Programmers Hint:** before starting to code, choose carefully the main SECoP modules you have to provide -to the user. - - -Tutorial Example ----------------- -For this tutorial we choose as an example a cryostat with a LakeShore 336 temperature controller, a level -meter and a motorized needle value. Let us start with the level meter, as this is the simplest module. - - -Coding the HeLevel Driver -------------------------- +Coding the Driver +----------------- +For this tutorial we choose as an example a cryostat. Let us start with the helium level meter, +as this is the simplest module. As mentioned in the introduction, we have to code the access to the hardware (driver), and the Frappy framework will deal with the SECoP interface. The code for the driver is located in a subdirectory named after the facility or institute programming the driver in our case *secop_psi*. @@ -79,11 +25,12 @@ CCU4 luckily has a very simple and logical protocol: StringIO, HasIodev - # the class used for communication class CCU4IO(StringIO): + """communication with CCU4""" + # for completeness: (not needed, as it is the default) + end_of_line = '\n' # on connect, we send 'cid' and expect a reply starting with 'CCU4' identification = [('cid', r'CCU4.*')] - end_of_line = '\n' # inheriting the HasIodev mixin creates us the things needed for talking @@ -108,7 +55,11 @@ CCU4 luckily has a very simple and logical protocol: assert name == 'h' # check that we got a reply to our command return txtvalue # the framework will automatically convert the string to a float -This is already a very simple working He Level meter driver. For a next step, we want to improve it: +The class :class:`CCU4`, an extension of (:class:`secop.stringio.StringIO`) serves as +communication class. + +Above is already the code for a very simple working He Level meter driver. For a next step, +we want to improve it: * We should inform the client about errors. That is what the *status* parameter is for. * We want to be able to configure the He Level sensor. @@ -254,3 +205,4 @@ the parameters *empty* and *full* from the client by defining: However, we do not do this here, as it is nice to try out chaning parameters for a test! +**name** *(x)* \ No newline at end of file diff --git a/secop/datatypes.py b/secop/datatypes.py index b34a611..54a2293 100644 --- a/secop/datatypes.py +++ b/secop/datatypes.py @@ -126,6 +126,10 @@ class DataType(HasProperties): """ raise NotImplementedError + def short_doc(self): + """short description for automatic extension of doc strings""" + return None + class Stub(DataType): """incomplete datatype, to be replaced with a proper one later during module load @@ -155,6 +159,10 @@ class Stub(DataType): if isinstance(stub, cls): prop.datatype = globals()[stub.name](*stub.args) + def short_doc(self): + return self.name.replace('Type', '').replace('Range', '').lower() + + # SECoP types: @@ -163,6 +171,7 @@ class FloatRange(DataType): :param minval: (property **min**) :param maxval: (property **max**) + :param properties: any of the properties below """ properties = { @@ -240,6 +249,9 @@ class FloatRange(DataType): other(max(sys.float_info.min, self.min)) other(min(sys.float_info.max, self.max)) + def short_doc(self): + return 'float' + class IntRange(DataType): """restricted int type @@ -304,15 +316,25 @@ class IntRange(DataType): for i in range(self.min, self.max + 1): other(i) + def short_doc(self): + return 'int' + class ScaledInteger(DataType): """scaled integer (= fixed resolution float) type + | In general *ScaledInteger* is needed only in special cases, + e.g. when the a SEC node is running on very limited hardware + without floating point support. + | Please use *FloatRange* instead. + :param minval: (property **min**) :param maxval: (property **max**) + :param properties: any of the properties below - note: limits are for the scaled float value - the scale is only used for calculating to/from transport serialisation + {properties} + :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), @@ -413,14 +435,17 @@ class ScaledInteger(DataType): other(self.min) other(self.max) + def short_doc(self): + return 'float' + class EnumType(DataType): """enumeration :param enum_or_name: the name of the Enum or an Enum to inherit from - :param members: members= + :param members: each argument denotes = - other keywords: (additional) members + exception: use members= to add members from a dict """ def __init__(self, enum_or_name='', **members): super().__init__() @@ -466,6 +491,9 @@ class EnumType(DataType): for m in self._enum.members: other(m) + def short_doc(self): + return 'one of %s' % str(tuple(self._enum.keys())) + class BLOBType(DataType): """binary large object @@ -547,11 +575,11 @@ class StringType(DataType): Stub('BoolType'), extname='isUTF8', default=False), } - def __init__(self, minchars=0, maxchars=None, **properties): + def __init__(self, minchars=0, maxchars=None, isUTF8=False): super().__init__() if maxchars is None: maxchars = minchars or UNLIMITED - self.set_properties(minchars=minchars, maxchars=maxchars, **properties) + self.set_properties(minchars=minchars, maxchars=maxchars, isUTF8=isUTF8) def checkProperties(self): self.default = ' ' * self.minchars @@ -607,12 +635,24 @@ class StringType(DataType): except AttributeError: raise BadValueError('incompatible datatypes') + def short_doc(self): + return 'str' + # TextType is a special StringType intended for longer texts (i.e. embedding \n), # whereas StringType is supposed to not contain '\n' # unfortunately, SECoP makes no distinction here.... # note: content is supposed to follow the format of a git commit message, i.e. a line of text, 2 '\n' + a longer explanation class TextType(StringType): + """special string type, intended for longer texts + + :param maxchars: maximum number of characters + + whereas StringType is supposed to not contain '\n' + unfortunately, SECoP makes no distinction here.... + note: content is supposed to follow the format of a git commit message, + i.e. a line of text, 2 '\n' + a longer explanation + """ def __init__(self, maxchars=None): if maxchars is None: maxchars = UNLIMITED @@ -667,6 +707,9 @@ class BoolType(DataType): other(False) other(True) + def short_doc(self): + return 'bool' + Stub.fix_datatypes() @@ -678,6 +721,7 @@ Stub.fix_datatypes() class ArrayOf(DataType): """data structure with fields of homogeneous type + :param members: the datatype for all elements """ properties = { 'minlen': Property('minimum number of elements', IntRange(0), extname='minlen', @@ -774,10 +818,14 @@ class ArrayOf(DataType): except AttributeError: raise BadValueError('incompatible datatypes') + def short_doc(self): + return 'array of %s' % self.members.short_doc() + class TupleOf(DataType): """data structure with fields of inhomogeneous type + :param members: each argument is a datatype of an element """ def __init__(self, *members): @@ -841,6 +889,9 @@ class TupleOf(DataType): for a, b in zip(self.members, other.members): a.compatible(b) + def short_doc(self): + return 'tuple of (%s)' % ', '.join(m.short_doc() for m in self.members) + class ImmutableDict(dict): def _no(self, *args, **kwds): @@ -851,6 +902,8 @@ class ImmutableDict(dict): class StructOf(DataType): """data structure with named fields + :param optional: (*sequence*) optional members + :param members: each argument denotes = """ def __init__(self, optional=None, **members): super().__init__() @@ -926,11 +979,18 @@ class StructOf(DataType): except (AttributeError, TypeError, KeyError): raise BadValueError('incompatible datatypes') + def short_doc(self): + return 'dict' + class CommandType(DataType): """command a pseudo datatype for commands with arguments and return values + + :param argument: None or the data type of the argument. multiple arguments may be simulated + by TupleOf or StructOf + :param result: None or the data type of the result """ IS_COMMAND = True @@ -989,10 +1049,16 @@ class CommandType(DataType): except AttributeError: raise BadValueError('incompatible datatypes') + def short_doc(self): + argument = self.argument.short_doc() if self.argument else '' + result = ' -> %s' % self.argument.short_doc() if self.result else '' + return '(%s)%s' % (argument, result) # return argument list only + # internally used datatypes (i.e. only for programming the SEC-node) class DataTypeType(DataType): + """DataType type""" def __call__(self, value): """check if given value (a python obj) is a valid datatype @@ -1036,7 +1102,9 @@ class ValueType(DataType): class NoneOr(DataType): - """validates a None or smth. else""" + """validates a None or other + + :param other: the other datatype""" default = None def __init__(self, other): @@ -1051,8 +1119,16 @@ class NoneOr(DataType): return None return self.other.export_value(value) + def short_doc(self): + other = self.other.short_doc() + return '%s or None' % other if other else None + class OrType(DataType): + """validates one of the + + :param types: each argument denotes one allowed type + """ def __init__(self, *types): super().__init__() self.types = types @@ -1066,6 +1142,12 @@ class OrType(DataType): pass raise BadValueError("Invalid Value, must conform to one of %s" % (', '.join((str(t) for t in self.types)))) + def short_doc(self): + types = [t.short_doc() for t in self.types] + if None in types: + return None + return ' or '.join(types) + Int8 = IntRange(-(1 << 7), (1 << 7) - 1) Int16 = IntRange(-(1 << 15), (1 << 15) - 1) @@ -1079,6 +1161,12 @@ UInt64 = IntRange(0, (1 << 64) - 1) # Goodie: Convenience Datatypes for Programming class LimitsType(TupleOf): + """limit (min, max) tuple + + :param members: the type of both members + + checks for min <= max + """ def __init__(self, members): TupleOf.__init__(self, members, members) @@ -1090,7 +1178,13 @@ class LimitsType(TupleOf): class StatusType(TupleOf): - # shorten initialisation and allow acces to status enumMembers from status values + """SECoP status type + + :param enum: the status code enum type + + allows to access enum members directly + """ + def __init__(self, enum): TupleOf.__init__(self, EnumType(enum), StringType()) self.enum = enum diff --git a/secop/lib/classdoc.py b/secop/lib/classdoc.py new file mode 100644 index 0000000..59f3cc6 --- /dev/null +++ b/secop/lib/classdoc.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +# ***************************************************************************** +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Module authors: +# Markus Zolliker +# +# ***************************************************************************** + +from inspect import cleandoc +from textwrap import indent + + +def indent_description(p): + """indent lines except first one""" + return indent(p.description, ' ').replace(' ', '', 1) + + +def append_to_doc(cls, name, title, attrname, newitems, fmtfunc): + """add information about some items to the doc + + :param cls: the class with the doc string to be extended + :param name: the name of the attribute dict to be used + :param title: the title to be used + :param newitems: the set of new items defined for this class + :param fmtfunc: a function returning a formatted item to be displayed, including line feed at end + or an empty string to suppress output for this item + :type fmtfunc: function(key, value) + """ + doc = cleandoc(cls.__doc__ or '') + allitems = getattr(cls, attrname, {}) + fmtdict = {n: fmtfunc(n, p) or ' - **%s** *removed*\n' % n for n, p in allitems.items()} + head, _, tail = doc.partition('{all %s}' % name) + if tail: # take all + inherited = set() + fmted = ''.join(fmtdict.values()) + else: + inherited = {n: p for n, p in allitems.items() if fmtdict.get(n) and n not in newitems} + fmted = ''.join(' ' + v for k, v in fmtdict.items() if k in newitems) + head, _, tail = doc.partition('{%s}' % name) + if not tail: + head, _, tail = doc.partition('{no %s}' % name) + if tail: # add no information + return + # no tag found: append to the end + if fmted: + clsset = set() + for name in inherited: + p = allitems[name] + refcls = cls + for base in cls.__mro__: + dp = getattr(base, attrname, {}).get(name) + if dp: + if dp == p: + refcls = base + else: + break + clsset.add(refcls) + clsset.discard(cls) + if clsset: + fmted += ' - see also %s\n' % (', '.join(':class:`%s.%s`' % (c.__module__, c.__name__) + for c in cls.__mro__ if c in clsset)) + cls.__doc__ = '%s\n\n:%s: %s\n%s' % (head, title, fmted, tail) diff --git a/secop/metaclass.py b/secop/metaclass.py index 8244162..44ae52b 100644 --- a/secop/metaclass.py +++ b/secop/metaclass.py @@ -28,7 +28,8 @@ 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, add_extra_doc +from secop.properties import PropertyMeta +from secop.lib.classdoc import append_to_doc, indent_description class Done: @@ -77,6 +78,9 @@ class ModuleMeta(PropertyMeta): obj = obj.apply(accessibles[key]) accessibles[key] = obj else: + if obj is None: # allow removal of accessibles + accessibles.pop(key, None) + continue if key in accessibles: # for now, accept redefinitions: print("WARNING: module %s: %s should not be redefined" @@ -206,10 +210,33 @@ 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)}) + def fmt_param(name, param): + if not isinstance(param, Parameter): + return '' + desc = indent_description(param) + if '(' in desc[0:2]: + dtinfo = '' + else: + dtinfo = [param.datatype.short_doc(), 'rd' if param.readonly else 'wr', + None if param.export else 'hidden'] + dtinfo = '*(%s)* ' % ', '.join(filter(None, dtinfo)) + return '- **%s** - %s%s\n' % (name, dtinfo, desc) + + def fmt_command(name, command): + if not isinstance(command, Command): + return '' + desc = indent_description(command) + if '(' in desc[0:2]: + dtinfo = '' # note: we expect that desc contains argument list + else: + dtinfo = '*%s*' % command.datatype.short_doc() + ' -%s ' % ('' if command.export else ' *(hidden)*') + return '- **%s**\\ %s%s\n' % (name, dtinfo, desc) + + append_to_doc(newtype, 'parameters', 'SECOP Parameters', + 'accessibles', set(parameters) | set(overrides), fmt_param) + append_to_doc(newtype, 'commands', 'SECOP Commands', + 'accessibles', set(commands) | set(overrides), fmt_command) + attrs['__constructed__'] = True return newtype diff --git a/secop/modules.py b/secop/modules.py index 97a0f85..29582b4 100644 --- a/secop/modules.py +++ b/secop/modules.py @@ -50,17 +50,28 @@ class Module(HasProperties, metaclass=ModuleMeta): all SECoP modules derive from this. - note: within modules, parameters should only be addressed as ``self.`` - i.e. ``self.value``, ``self.target`` etc... - these are accessing the cached version. - they can also be written to, generating an async update + :param name: the modules name + :param logger: a logger instance + :param cfgdict: the dict from this modules section in the config file + :param srv: the server instance - if you want to 'update from the hardware', call ``self.read_()`` instead - the return value of this method will be used as the new cached value and - be an async update sent automatically. + Notes: + + - the programmer normally should not need to reimplement :meth:`__init__` + - within modules, parameters should only be addressed as ``self.``, i.e. ``self.value``, ``self.target`` etc... + + - these are accessing the cached version. + - they can also be written to, generating an async update + + - if you want to 'update from the hardware', call ``self.read_()`` 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_()``. + + - The return value of this method will also update the cache. - if you want to 'update the hardware' call ``self.write_()``. - 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 @@ -69,22 +80,22 @@ class Module(HasProperties, metaclass=ModuleMeta): # note: the names map to a [datatype, value] list, value comes from the cfg file, # datatype is fixed! properties = { - 'export': Property('Flag if this Module is to be exported', BoolType(), default=True, export=False), - 'group': Property('Optional group the Module belongs to', StringType(), default='', extname='group'), - 'description': Property('Description of the module', TextType(), extname='description', mandatory=True), - 'meaning': Property('Optional Meaning indicator', TupleOf(StringType(),IntRange(0,50)), + 'export': Property('flag if this Module is to be exported', BoolType(), default=True, export=False), + 'group': Property('optional group the Module belongs to', StringType(), default='', extname='group'), + 'description': Property('description of the module', TextType(), extname='description', mandatory=True), + 'meaning': Property('dptional Meaning indicator', TupleOf(StringType(),IntRange(0,50)), default=('',0), extname='meaning'), - '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), default='user', extname='visibility'), - 'implementation': Property('Internal name of the implementation class of the module', StringType(), + 'implementation': Property('internal name of the implementation class of the module', StringType(), extname='implementation'), - 'interface_classes': Property('Offical highest Interface-class of the module', ArrayOf(StringType()), + 'interface_classes': Property('offical highest Interface-class of the module', ArrayOf(StringType()), extname='interface_classes'), } # properties, parameters and commands are auto-merged upon subclassing - parameters = {} - commands = {} + parameters = {} #: definition of parameters + commands = {} #: definition of commands # reference to the dispatcher (used for sending async updates) DISPATCHER = None @@ -354,12 +365,31 @@ class Module(HasProperties, metaclass=ModuleMeta): return False def earlyInit(self): - # may be overriden in derived classes to init stuff + """may be overriden in derived classes to init stuff + + after creating the module (no super call needed) + """ self.log.debug('empty %s.earlyInit()' % self.__class__.__name__) def initModule(self): + """may be overriden to do stuff after all modules are intiialized + + no super call needed + """ self.log.debug('empty %s.initModule()' % self.__class__.__name__) + def startModule(self, started_callback): + """runs after init of all modules + + :param started_callback: argument less function to be called when the thread + spawned by startModule has finished its initial work + :return: None or a timeout value, if different from default (30 sec) + + override this method for doing stuff during startup, after all modules are + initialized. do not forget the super call + """ + mkthread(self.writeInitParams, started_callback) + def pollOneParam(self, pname): """poll parameter with proper error handling""" try: @@ -391,15 +421,6 @@ class Module(HasProperties, metaclass=ModuleMeta): if started_callback: started_callback() - def startModule(self, started_callback): - """runs after init of all modules - - started_callback to be called when the thread spawned by startModule - has finished its initial work - might return a timeout value, if different from default - """ - mkthread(self.writeInitParams, started_callback) - class Readable(Module): """basic readable module""" @@ -421,7 +442,7 @@ class Readable(Module): readonly=False, datatype=FloatRange(0.1, 120), ), - 'status': Parameter('current status of the Module', + 'status': Parameter('*(rd, tuple of (Readable.Status, str))* current status of the Module', default=(Status.IDLE, ''), datatype=TupleOf(EnumType(Status), StringType()), readonly=True, poll=True, @@ -496,7 +517,8 @@ class Drivable(Writable): } overrides = { - 'status': Override(datatype=StatusType(Status)), + 'status': Override('*(rd, tuple of (Drivable.Status, str))* current status of the Module', + datatype=StatusType(Status)), } def isBusy(self, status=None): @@ -561,7 +583,7 @@ class Communicator(Module): class Attached(Property): - """a special property, defining an attached modle + """a special property, defining an attached module assign a module name to this property in the cfg file, and the server will create an attribute with this module diff --git a/secop/params.py b/secop/params.py index 76ddb41..d78aeee 100644 --- a/secop/params.py +++ b/secop/params.py @@ -24,6 +24,7 @@ from collections import OrderedDict +from inspect import cleandoc from secop.datatypes import CommandType, DataType, StringType, BoolType, EnumType, DataTypeType, ValueType, OrType, \ NoneOr, TextType, IntRange @@ -88,18 +89,30 @@ class Parameter(Accessible): extname='visibility', default=1), '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.', - ValueType(), export=False, default=None, mandatory=False), - 'export': Property('[internal] is this parameter accessible via SECoP? (vs. internal parameter)', - OrType(BoolType(), StringType()), export=False, default=True), - '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']), + 'default': Property('[internal] default (startup) value of this parameter ' + 'if it can not be read from the hardware.', + ValueType(), export=False, default=None, mandatory=False), + 'export': Property(''' + [internal] export settings + + * False: not accessible via SECoP. + * True: exported, name automatic. + * a string: exported with custom name''', + OrType(BoolType(), StringType()), export=False, default=True), + 'poll': Property(''' + [internal] polling indicator + + may be: + + * 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, @@ -124,7 +137,7 @@ class Parameter(Accessible): raise ProgrammingError( 'datatype MUST be derived from class DataType!') - kwds['description'] = description + kwds['description'] = cleandoc(description) kwds['datatype'] = datatype kwds['readonly'] = kwds.get('readonly', True) # for frappy optional, for SECoP mandatory if unit is not None: # for legacy code only @@ -213,7 +226,7 @@ class Commands(Parameters): class Override(CountedObj): - """Stores the overrides to be applied to a Parameter + """Stores the overrides to be applied to a Parameter or Command note: overrides are applied by the metaclass during class creating reorder=True: use position of Override instead of inherited for the order @@ -224,7 +237,7 @@ class Override(CountedObj): self.reorder = reorder # allow to override description and datatype without keyword if description: - self.kwds['description'] = description + self.kwds['description'] = cleandoc(description) if datatype is not None: self.kwds['datatype'] = datatype # for now, do not use the Override ctr @@ -252,12 +265,10 @@ class Override(CountedObj): props.update(self.kwds) if self.reorder: - #props['ctr'] = self.ctr - return type(obj)(ctr=self.ctr, **props) - return type(obj)(**props) + return type(obj)(**props) + return type(obj)(ctr=self.ctr, **props) raise ProgrammingError( - "Overrides can only be applied to Accessibles, %r is none!" % - obj) + "Overrides can only be applied to Accessibles, %r is none!" % obj) class Command(Accessible): @@ -270,8 +281,13 @@ class Command(Accessible): extname='group', export=True, default=''), '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)', - OrType(BoolType(), StringType()), export=False, default=True), + 'export': Property(''' + [internal] export settings + + - False: not accessible via SECoP. + - True: exported, name automatic. + - a string: exported with custom name''', + 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\'', @@ -283,7 +299,7 @@ class Command(Accessible): } def __init__(self, description, ctr=None, **kwds): - kwds['description'] = description + kwds['description'] = cleandoc(description) kwds['datatype'] = CommandType(kwds.get('argument', None), kwds.get('result', None)) super(Command, self).__init__(**kwds) if ctr is not None: diff --git a/secop/properties.py b/secop/properties.py index 1be6916..a666871 100644 --- a/secop/properties.py +++ b/secop/properties.py @@ -24,8 +24,10 @@ from collections import OrderedDict +from inspect import cleandoc from secop.errors import ProgrammingError, ConfigError, BadValueError +from secop.lib.classdoc import append_to_doc, indent_description # storage for 'properties of a property' @@ -33,7 +35,7 @@ class Property: """base class holding info about a property :param description: mandatory - :param datatype: the datatype to be accepted. not only to the SECoP datatypes are allowed! + :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 @@ -48,7 +50,7 @@ class Property: def __init__(self, description, datatype, default=None, extname='', export=False, mandatory=None, settable=True): if not callable(datatype): raise ValueError('datatype MUST be a valid DataType or a basic_validator') - self.description = description + self.description = cleandoc(description) self.default = datatype.default if default is None else datatype(default) self.datatype = datatype self.extname = extname @@ -85,17 +87,6 @@ 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 @@ -142,7 +133,21 @@ class PropertyMeta(type): % (newtype, k, attrs[k])) setattr(newtype, k, property(getter)) - add_extra_doc(newtype, '**properties**', attrs.get('properties', {})) # only new properties + # add property information to the doc string + def fmt_property(name, prop): + desc = indent_description(prop) + if '(' in desc[0:2]: + dtinfo = '' + else: + dtinfo = [prop.datatype.short_doc(), None if prop.export else 'hidden'] + dtinfo = ', '.join(filter(None, dtinfo)) + if dtinfo: + dtinfo = '*(%s)* ' % dtinfo + return '- **%s** - %s%s\n' % (name, dtinfo, desc) + + append_to_doc(newtype, 'properties', 'SECOP Properties', + 'properties', attrs.get("properties", {}), fmt_property) + return newtype diff --git a/secop/stringio.py b/secop/stringio.py index 5f535d5..3399b15 100644 --- a/secop/stringio.py +++ b/secop/stringio.py @@ -27,7 +27,7 @@ import time import threading import re from secop.lib.asynconn import AsynConn, ConnectionClosed -from secop.modules import Module, Communicator, Parameter, Command, Property, Attached +from secop.modules import Module, Communicator, Parameter, Command, Property, Attached, Override from secop.datatypes import StringType, FloatRange, ArrayOf, BoolType, TupleOf, ValueType from secop.errors import CommunicationFailedError, CommunicationSilentError from secop.poller import REGULAR @@ -65,8 +65,20 @@ class StringIO(Communicator): Parameter('reconnect interval', datatype=FloatRange(0), readonly=False, default=10), } commands = { + 'communicate': + Override(''' + send a command and receive a reply + + - using end_of_line, encoding and self._lock + - for commands without reply, the command must be joined with a query command, + - wait_before is respected for end_of_lines within a command + '''), 'multicomm': - Command('execute multiple commands in one go', + Command(''' + execute multiple commands in one go + + assuring that no other thread calls commands in between + ''', argument=ArrayOf(StringType()), result=ArrayOf(StringType())) } @@ -169,12 +181,6 @@ class StringIO(Communicator): self._reconnectCallbacks.pop(key) def do_communicate(self, command): - """send a command and receive a reply - - using end_of_line, encoding and self._lock - for commands without reply, the command must be joined with a query command, - wait_before is respected for end_of_lines within a command. - """ if not self.is_connected: self.read_is_connected() # try to reconnect try: diff --git a/secop_psi/ppms.py b/secop_psi/ppms.py index a935a86..431abd6 100644 --- a/secop_psi/ppms.py +++ b/secop_psi/ppms.py @@ -136,8 +136,8 @@ class Main(Communicator): class PpmsMixin(HasIodev, Module): """common methods for ppms modules""" - properties = { - 'iodev': Attached(), + parameters = { + 'pollinterval': None, } pollerClass = Poller @@ -186,8 +186,6 @@ class Channel(PpmsMixin, Readable): 'enabled': Parameter('is this channel used?', readonly=False, poll=False, datatype=BoolType(), default=False), - 'pollinterval': - Override(visibility=3), } properties = { 'channel': @@ -210,13 +208,9 @@ class Channel(PpmsMixin, Readable): class UserChannel(Channel): """user channel""" - parameters = { - 'pollinterval': - Override(visibility=3), - } properties = { 'no': - Property('channel number', + Property('*(unused)*', datatype=IntRange(0, 0), export=False, default=0), 'linkenable': Property('name of linked channel for enabling', @@ -243,8 +237,6 @@ class DriverChannel(Channel): 'powerlimit': Parameter('power limit', readonly=False, handler=drvout, datatype=FloatRange(0., 1000., unit='uW')), - 'pollinterval': - Override(visibility=3), } def analyze_drvout(self, no, current, powerlimit): @@ -281,8 +273,6 @@ class BridgeChannel(Channel): 'voltagelimit': Parameter('voltage limit', readonly=False, handler=bridge, datatype=FloatRange(0.0001, 100., unit='mV')), - 'pollinterval': - Override(visibility=3), } def analyze_bridge(self, no, excitation, powerlimit, dcflag, readingmode, voltagelimit): @@ -312,8 +302,6 @@ class Level(PpmsMixin, Readable): parameters = { 'value': Override(datatype=FloatRange(unit='%'), handler=level), 'status': Override(handler=level), - 'pollinterval': - Override(visibility=3), } channel = 'level' @@ -370,8 +358,6 @@ class Chamber(PpmsMixin, Drivable): 'target': Override(description='chamber command', handler=chamber, datatype=EnumType(Operation)), - 'pollinterval': - Override(visibility=3), } STATUS_MAP = { StatusCode.purged_and_sealed: (Status.IDLE, 'purged and sealed'), @@ -435,8 +421,6 @@ class Temp(PpmsMixin, Drivable): 'approachmode': Parameter('how to approach target!', readonly=False, handler=temp, datatype=EnumType(ApproachMode)), - 'pollinterval': - Override(visibility=3), 'timeout': Parameter('drive timeout, in addition to ramp time', readonly=False, datatype=FloatRange(0, unit='sec'), default=3600), @@ -632,8 +616,6 @@ class Field(PpmsMixin, Drivable): 'persistentmode': Parameter('what to do after changing field', readonly=False, handler=field, datatype=EnumType(PersistentMode)), - 'pollinterval': - Override(visibility=3), } STATUS_MAP = { @@ -763,8 +745,6 @@ class Position(PpmsMixin, Drivable): 'speed': Parameter('motor speed', readonly=False, handler=move, datatype=FloatRange(0.8, 12, unit='deg/sec')), - 'pollinterval': - Override(visibility=3), } STATUS_MAP = { 1: (Status.IDLE, 'at target'),