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:
@ -1,6 +0,0 @@
|
||||
Client documentation
|
||||
====================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# SECoP documentation build configuration file, created by
|
||||
# Frappy documentation build configuration file, created by
|
||||
# sphinx-quickstart on Mon Sep 11 10:58:28 2017.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its
|
||||
@ -57,9 +57,9 @@ source_suffix = ['.rst', '.md']
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = 'SECoP'
|
||||
#copyright = '2017, Enrico Faulhaber, Markus Zolliker'
|
||||
copyright = '2017, SECoP Committee'
|
||||
project = 'Frappy'
|
||||
copyright = '2017-2021, Enrico Faulhaber, Markus Zolliker,'
|
||||
#copyright = '2017, SECoP Committee'
|
||||
author = 'Enrico Faulhaber, Markus Zolliker'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
@ -89,6 +89,9 @@ 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'
|
||||
|
||||
default_role = 'any'
|
||||
|
||||
# -- Options for HTML output ----------------------------------------------
|
||||
@ -136,7 +139,7 @@ html_sidebars = {
|
||||
# -- Options for HTMLHelp output ------------------------------------------
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'SECoPdoc'
|
||||
htmlhelp_basename = 'Frappydoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
@ -163,7 +166,7 @@ latex_elements = {
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
(master_doc, 'SECoP.tex', 'SECoP source documentation',
|
||||
(master_doc, 'Frappy.tex', 'Frappy source documentation',
|
||||
'Enrico Faulhaber, Markus Zolliker', 'manual'),
|
||||
]
|
||||
|
||||
@ -173,7 +176,7 @@ latex_documents = [
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
(master_doc, 'secop', 'SECoP source documentation',
|
||||
(master_doc, 'frappy', 'Frappy source documentation',
|
||||
[author], 1)
|
||||
]
|
||||
|
||||
@ -184,8 +187,8 @@ man_pages = [
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
(master_doc, 'SECoP', 'SECoP source documentation',
|
||||
author, 'SECoP', 'One line description of project.',
|
||||
(master_doc, 'Frappy', 'Frappy source documentation',
|
||||
author, 'Frappy', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
|
@ -7,3 +7,4 @@ Facility specific functionalities
|
||||
demo/index
|
||||
mlz/index
|
||||
ess/index
|
||||
psi/index
|
||||
|
10
doc/source/facility/psi/index.rst
Normal file
10
doc/source/facility/psi/index.rst
Normal file
@ -0,0 +1,10 @@
|
||||
PSI
|
||||
===
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 3
|
||||
|
||||
ppms
|
||||
ls370res
|
||||
|
||||
|
7
doc/source/facility/psi/ls370res.rst
Normal file
7
doc/source/facility/psi/ls370res.rst
Normal file
@ -0,0 +1,7 @@
|
||||
LakeShore 370 resistivity
|
||||
=========================
|
||||
|
||||
.. automodule:: secop_psi.ls370res
|
||||
:members:
|
||||
|
||||
|
7
doc/source/facility/psi/ppms.rst
Normal file
7
doc/source/facility/psi/ppms.rst
Normal file
@ -0,0 +1,7 @@
|
||||
PPMS
|
||||
====
|
||||
|
||||
.. automodule:: secop_psi.ppms
|
||||
:members:
|
||||
|
||||
|
67
doc/source/framework.rst
Normal file
67
doc/source/framework.rst
Normal file
@ -0,0 +1,67 @@
|
||||
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:
|
@ -1,6 +0,0 @@
|
||||
Datatypes
|
||||
=========
|
||||
|
||||
.. automodule:: secop.datatypes
|
||||
:members:
|
||||
|
@ -1,6 +0,0 @@
|
||||
Exception classes
|
||||
=================
|
||||
|
||||
.. automodule:: secop.errors
|
||||
:members:
|
||||
|
@ -1,9 +0,0 @@
|
||||
Framework documentation
|
||||
=======================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
datatypes
|
||||
errors
|
||||
|
@ -1,6 +0,0 @@
|
||||
Graphical user interface documentation
|
||||
======================================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
@ -1,13 +1,12 @@
|
||||
Welcome to FRAPPY documentation!
|
||||
================================
|
||||
Welcome to the FRAPPY documentation!
|
||||
====================================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
server/index
|
||||
client/index
|
||||
framework/index
|
||||
gui/index
|
||||
tutorial/tutorial
|
||||
server
|
||||
framework
|
||||
facility/index
|
||||
|
||||
|
||||
|
77
doc/source/server.rst
Normal file
77
doc/source/server.rst
Normal file
@ -0,0 +1,77 @@
|
||||
Configuring and Starting
|
||||
========================
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
The configuration consists of a **NODE** section, an **INTERFACE** section and one
|
||||
section per SECoP module.
|
||||
|
||||
The **NODE** section contains a description of the SEC node and a globally unique ID of
|
||||
the SEC node. Example:
|
||||
|
||||
.. code::
|
||||
|
||||
[NODE]
|
||||
description = a description of the SEC node
|
||||
id = globally.valid.identifier
|
||||
|
||||
The **INTERFACE** section defines the server interface. Currently only tcp is supported.
|
||||
When the TCP port is given as an argument of the server start script, this section is not
|
||||
needed or ignored. The main information is the port number, in this example 5000:
|
||||
|
||||
.. code::
|
||||
|
||||
[INTERFACE]
|
||||
uri = tcp://5000
|
||||
|
||||
|
||||
All other sections define the SECoP modules. The section name itself is the module name,
|
||||
mandatory fields are **class** and **description**. **class** is a path to the Python class
|
||||
from there the module is instantiated, separated with dots. In the following example the class
|
||||
**HeLevel** used by the **helevel** module can be found in the PSI facility subdirectory
|
||||
secop_psi in the python module file ccu4.py:
|
||||
|
||||
.. code::
|
||||
|
||||
[helevel]
|
||||
class = secop_psi.ccu4.HeLevel
|
||||
description = this is the He level sensor of the main reservoir
|
||||
empty = 380
|
||||
empty.export = False
|
||||
full = 0
|
||||
full.export = False
|
||||
|
||||
It is highly recommended to use all lower case for the module name, as SECoP names have to be
|
||||
unique despite of casing. In addition, parameters, properties and parameter properties might
|
||||
be initialized in this section. In the above example **empty** and **full** are parameters,
|
||||
the resistivity of the He Level sensor at the end of the ranges. In addition, we alter the
|
||||
default property **export** of theses parameters, as we do not want to expose these parameters to
|
||||
the SECoP interface.
|
||||
|
||||
|
||||
Starting
|
||||
--------
|
||||
|
||||
The Frappy server can be started via the **bin/secop-server** script.
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
usage: secop-server [-h] [-v | -q] [-d] name
|
||||
|
||||
Manage a Frappy server
|
||||
|
||||
positional arguments:
|
||||
name name of the instance. Uses etc/name.cfg for configuration
|
||||
|
||||
optional arguments:
|
||||
-c, --cfgfiles config files to be used. Comma separated list.
|
||||
defaults to <name> when omitted
|
||||
-p, --port server port (default: take from cfg file)
|
||||
-h, --help show this help message and exit
|
||||
-v, --verbose output lots of diagnostic information
|
||||
-q, --quiet suppress non-error messages
|
||||
-d, --daemonize run as daemon
|
||||
-t, --test check cfg files only
|
||||
|
||||
|
@ -1,3 +0,0 @@
|
||||
Configuration
|
||||
=============
|
||||
|
@ -1,11 +0,0 @@
|
||||
Server documentation
|
||||
====================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 3
|
||||
|
||||
starting
|
||||
configuration
|
||||
modules
|
||||
protocol/index
|
||||
|
@ -1,6 +0,0 @@
|
||||
Module base classes
|
||||
===================
|
||||
|
||||
.. automodule:: secop.modules
|
||||
:members:
|
||||
|
@ -1,8 +0,0 @@
|
||||
protocol stack
|
||||
==============
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 3
|
||||
|
||||
interface/index
|
||||
|
@ -1,9 +0,0 @@
|
||||
Interfaces
|
||||
==========
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 3
|
||||
|
||||
tcp
|
||||
zmq
|
||||
|
@ -1,6 +0,0 @@
|
||||
TCP
|
||||
===
|
||||
|
||||
.. automodule:: secop.protocol.interface.tcp
|
||||
:members:
|
||||
|
@ -1,6 +0,0 @@
|
||||
ZMQ
|
||||
===
|
||||
|
||||
.. automodule:: secop.protocol.interface.zmq
|
||||
:members:
|
||||
|
@ -1,21 +0,0 @@
|
||||
Starting
|
||||
========
|
||||
|
||||
The SECoP server can be started via the ``bin/secop-server`` script.
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
usage: secop-server [-h] [-v | -q] [-d] name
|
||||
|
||||
Manage a SECoP server
|
||||
|
||||
positional arguments:
|
||||
name Name of the instance. Uses etc/name.cfg for configuration
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
-v, --verbose Output lots of diagnostic information
|
||||
-q, --quiet suppress non-error messages
|
||||
-d, --daemonize Run as daemon
|
||||
|
||||
|
228
doc/source/tutorial/tutorial.rst
Normal file
228
doc/source/tutorial/tutorial.rst
Normal file
@ -0,0 +1,228 @@
|
||||
Frappy Programming Guide
|
||||
========================
|
||||
|
||||
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*](https://github.com/SampleEnvironment/SECoP/tree/master/protocol) is a protocol for communicating with Sample Environment and other mobile devices, specified by a committee of the [ISSE](https://sampleenvironment.org). 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.
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
Before we start coding, we create a configuration file. The frappy framework usually has all present code
|
||||
in the directory tree, and the server is started with the configuration as an argument, determining which
|
||||
modules are to be configured, ans which code is effectively to be used. We choose the name *example_cryo*
|
||||
and create therefore a configuration file *example_cryo.cfg* in the *cfg* subdirectory.
|
||||
|
||||
Let us start with a simple configuration for the level meter only:
|
||||
|
||||
``cfg/example_cryo.cfg``:
|
||||
|
||||
.. code:: ini
|
||||
|
||||
[NODE]
|
||||
description = this is an example cryostat for the Frappy tutorial
|
||||
id = example_cryo.sampleenvironment.org
|
||||
|
||||
[INTERFACE]
|
||||
uri = tcp://5000
|
||||
|
||||
[helev]
|
||||
description = He level of the cryostat He reservoir
|
||||
class = secop_psi.ccu4.HeLevel
|
||||
uri = linse-moxa-4.psi.ch:3001
|
||||
|
||||
# TO BE MOVED
|
||||
[tmain]
|
||||
description = main (heat exchange) temperature
|
||||
class = secop_psi.ls336.ControlledChannel
|
||||
iodev = lsio
|
||||
|
||||
The configuration file contains several section starting with a line in rectangular brackets.
|
||||
|
||||
The *NODE* section describes the main properties of the SEC Node: a description of the node and
|
||||
an id, which should be globally unique.
|
||||
|
||||
The *INTERFACE* section defines the address of the server, usually the only important value here
|
||||
is the TCP port under which the server will be accessible. Currently only tcp is supported.
|
||||
|
||||
All the other sections define the SECoP modules to be used. A module section at least contain a
|
||||
human readable *description*, and the Python *class* used. Other properties or parameter values may
|
||||
follow, in this case the *uri* for the communication with the He level monitor.
|
||||
|
||||
Code the Python Class for the 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*.
|
||||
We create a file named from the electronic device CCU4 we use here for the He level reading.
|
||||
CCU4 luckily has a very simple and logical protocol:
|
||||
|
||||
* ``<name>=<value>\n`` sets the parameter named ``<name>`` to the value ``<value>``
|
||||
* ``<name>\n`` reads the parameter named ``<name>``
|
||||
* in both cases, the reply is ``<name>=<value>\n``
|
||||
|
||||
``secop_psi/ccu4.py``:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# the most common classes can be imported from secop.core
|
||||
from secop.core import Readable, Parameter, Override, FloatRange, BoolType, \
|
||||
StringIO, HasIodev
|
||||
|
||||
# inheriting HasIodev mixin creates us the things needed for talking
|
||||
# with a device by means of the sendRecv method
|
||||
# Readable as a base class defines the value and status parameters
|
||||
|
||||
class HeLevel(HasIodev, Readable):
|
||||
"""He Level channel of CCU4"""
|
||||
|
||||
# define or alter the parameters
|
||||
parameters = {
|
||||
# we are changing the 'unit' parameter property of the inherited 'value'
|
||||
# parameter, therefore 'Override'
|
||||
'value': Override(unit='%'),
|
||||
}
|
||||
# tells us how to communicate. StringIO is using \n as line end, which fits
|
||||
iodevClass = StringIO
|
||||
|
||||
def read_value(self):
|
||||
# method for reading the main value
|
||||
reply = self.sendRecv('h') # send 'h\n' and get the reply 'h=<value>\n'
|
||||
name, txtvalue = reply.split('=')
|
||||
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:
|
||||
|
||||
* We should tell the client, when there is an error. That is what the *status* parameter is for.
|
||||
We do not need to declare the status parameter, as it is inherited from *Readable*.
|
||||
* We want to be able to configure the He Level sensor and we want to be able to switch the
|
||||
Level Monitor to fast reading before we start to fill.
|
||||
|
||||
Let us start to code these additions:
|
||||
|
||||
.. code:: python
|
||||
|
||||
...
|
||||
# define or alter the parameters
|
||||
parameters = {
|
||||
|
||||
...
|
||||
|
||||
# the first two arguments to Parameter are 'description' and 'datatype'
|
||||
# it is highly recommended to define always the physical unit
|
||||
'empty': Parameter('warm length when empty', FloatRange(0, 2000),
|
||||
readonly=False, unit='mm'),
|
||||
'full': Parameter('warm length when full', FloatRange(0, 2000),
|
||||
readonly=False, unit='mm'),
|
||||
'fast': Parameter('fast reading', BoolType(),
|
||||
readonly=False),
|
||||
}
|
||||
|
||||
...
|
||||
|
||||
Status = Readable.Status
|
||||
|
||||
STATUS_MAP = {
|
||||
0: (Status.IDLE, 'sensor ok'),
|
||||
1: (Status.ERROR, 'sensor warm'),
|
||||
2: (Status.ERROR, 'no sensor'),
|
||||
3: (Status.ERROR, 'timeout'),
|
||||
4: (Status.ERROR, 'not yet read'),
|
||||
5: (Status.DISABLED, 'disabled'),
|
||||
}
|
||||
|
||||
def read_status(self):
|
||||
name, txtvalue = self.sendRecv('hsf').split('=')
|
||||
assert name == 'hsf'
|
||||
return self.STATUS_MAP(int(txtvalue))
|
||||
|
||||
def read_emtpy(self):
|
||||
name, txtvalue = self.sendRecv('hem').split('=')
|
||||
assert name == 'hem'
|
||||
return txtvalue
|
||||
|
||||
def write_empty(self, value):
|
||||
name, txtvalue = self.sendRecv('hem=%g' % value).split('=')
|
||||
assert name == 'hem'
|
||||
return txtvalue
|
||||
|
||||
...
|
||||
|
||||
We realize now, that we will repeat similar code for other parameters, which means it might be
|
||||
worth to create our own *_sendRecv* method, and then the *read_<param>* and *write_<param>* methods
|
||||
will become shorter:
|
||||
|
||||
.. code:: python
|
||||
|
||||
...
|
||||
|
||||
def _sendRecv(self, cmd):
|
||||
# method may be used for reading and writing parameters
|
||||
name, txtvalue = self.sendRecv(cmd).split('=')
|
||||
assert name == cmd.split('=')[0] # check that we got a reply to our command
|
||||
return txtvalue # the framework will automatically convert the string to a float
|
||||
|
||||
def read_value(self):
|
||||
return self._sendRecv('h')
|
||||
|
||||
...
|
||||
|
||||
def read_status(self):
|
||||
return self.STATUS_MAP(int(self._sendRecv('hsf')))
|
||||
|
||||
def read_empty(self):
|
||||
return self._sendRecv('hem')
|
||||
|
||||
def write_empty(self, value):
|
||||
return self._sendRecv('hem=%g' % value)
|
||||
|
||||
def read_full(self):
|
||||
return self._sendRecv('hfu')
|
||||
|
||||
def write_full(self, value):
|
||||
return self._sendRecv('hfu=%g' % value)
|
||||
|
||||
def read_fast(self):
|
||||
return self._sendRecv('hf')
|
||||
|
||||
def write_fast(self, value):
|
||||
return self._sendRecv('hf=%s' % value)
|
||||
|
Reference in New Issue
Block a user