Compare commits
167 Commits
Author | SHA1 | Date | |
---|---|---|---|
dcd79506a9 | |||
f4e974f46c | |||
![]() |
d889401697 | ||
85295a7d72 | |||
82957c287d | |||
![]() |
05593d80f6 | ||
7a870aa56c | |||
![]() |
09d48ea913 | ||
![]() |
8850edbc2d | ||
59cc981566 | |||
3cc9a75174 | |||
![]() |
52b77ba9e6 | ||
![]() |
db3b190c26 | ||
![]() |
c522c41654 | ||
![]() |
929e41ffff | ||
![]() |
8d99a8c536 | ||
a14c282993 | |||
![]() |
d09634a55d | ||
b17030afa2 | |||
37c9efb27b | |||
![]() |
4167ce7b00 | ||
![]() |
2b7b2267d2 | ||
![]() |
8071c21819 | ||
![]() |
e16ef3ae87 | ||
![]() |
51147d8e09 | ||
![]() |
6909eb8541 | ||
![]() |
5d6b208671 | ||
![]() |
b3eebb6c6a | ||
![]() |
7f166a5b8c | ||
![]() |
c1eb764b09 | ||
![]() |
a9d798fabc | ||
![]() |
a928c95efd | ||
![]() |
7df4584150 | ||
![]() |
b7cebe2cd8 | ||
![]() |
b8b2dafaf8 | ||
![]() |
f66411dded | ||
![]() |
ce4bbec766 | ||
![]() |
355810a887 | ||
![]() |
aa98604f88 | ||
![]() |
b0051ca3f0 | ||
![]() |
92edbb27ea | ||
![]() |
20fc48ddf0 | ||
![]() |
340c031f46 | ||
![]() |
3ac8b4e255 | ||
![]() |
6ffc73a1e1 | ||
![]() |
4ef0b0c01d | ||
![]() |
1fc805a5a2 | ||
05edf98dfe | |||
![]() |
e09c365ea5 | ||
64cb297a06 | |||
eaefa1ce87 | |||
71aaf7187a | |||
0a28192c15 | |||
1b9cac04b1 | |||
2b1986ad8f | |||
270152d503 | |||
1922578dfa | |||
43880346d6 | |||
![]() |
d0794a7803 | ||
![]() |
dc76ac92de | ||
8579368259 | |||
cd90385e6c | |||
![]() |
3acea5f7c7 | ||
![]() |
c564ae392c | ||
1357ead435 | |||
6a0261c728 | |||
d717a481d7 | |||
4c94580cb9 | |||
8eee7ab3b0 | |||
478075c545 | |||
b7d16d2e16 | |||
d3379d5e95 | |||
d6ad5f058d | |||
a35134978a | |||
7891c281e1 | |||
6460e51920 | |||
b40b0e75b1 | |||
af983287e7 | |||
8d23503bbd | |||
bb097ac3ba | |||
16a9550080 | |||
9858973ba1 | |||
e6d6179925 | |||
60c62a340d | |||
3c0c60615a | |||
fda1939324 | |||
8767be2aac | |||
39a3e79eb3 | |||
c1d42f0f02 | |||
3fe44d32b1 | |||
f58ab263e7 | |||
e0fe7e46d1 | |||
b423235c5d | |||
aa82bc580d | |||
c2596a9629 | |||
bf1761bbc4 | |||
99588fc815 | |||
bbc4663266 | |||
c1307cdd03 | |||
b911bc1838 | |||
26a0f2e078 | |||
4f7083bc98 | |||
0909f92e12 | |||
f3450375ce | |||
eb2e8f5f74 | |||
2ef0da68e8 | |||
9b38db7706 | |||
8f7fb1e45b | |||
f13e29aad2 | |||
c5d228ffc4 | |||
071ba38b60 | |||
b29b1e1b36 | |||
c91d726f9d | |||
1d75d192e5 | |||
4bf3acab98 | |||
![]() |
eebc9232cd | ||
![]() |
c16f159599 | ||
796be752b7 | |||
![]() |
b8e8d24b50 | ||
45dd14a72c | |||
ad7cfe4ea0 | |||
47d09e9b08 | |||
d76d79aebb | |||
a64eb7f33b | |||
3d0d779d81 | |||
3b7cc33f64 | |||
41489a4a24 | |||
3140d454ae | |||
7bd166a8a1 | |||
f3978385b4 | |||
bc6a99e11b | |||
6b610f1e25 | |||
9a60de9c1c | |||
![]() |
026e657799 | ||
![]() |
35f08bf4ad | ||
![]() |
76ae75a926 | ||
27ac70b1da | |||
a343b07f1d | |||
09411f36f3 | |||
![]() |
b0f0a48e51 | ||
![]() |
e4261ecfe1 | ||
![]() |
598dd07888 | ||
![]() |
f2b330a3f0 | ||
![]() |
a85201ad7d | ||
![]() |
2cf6e167a8 | ||
![]() |
ffc2c495fb | ||
![]() |
7ea4e3955a | ||
cc1632e07d | |||
![]() |
1e17d0c6b9 | ||
![]() |
e568c665a8 | ||
d0f895ed44 | |||
69f5692951 | |||
980499ba41 | |||
6cde8177d5 | |||
23779c8f8c | |||
899a07aec8 | |||
6a32ecf342 | |||
1464a6bce5 | |||
bb6f692c6b | |||
1a8ddbc696 | |||
ed02131a37 | |||
a19425684c | |||
24cffad4df | |||
05fec236da | |||
b1a88440ef | |||
c9721649a3 | |||
d2c3370a40 |
@ -1,2 +1,2 @@
|
|||||||
SECoP playground for creating specification and testing one implementation.
|
Frappy framework for implementing SEC-nodes (see SECoP protocol on github).
|
||||||
|
|
||||||
|
29
.pylintrc
29
.pylintrc
@ -38,27 +38,21 @@ confidence=
|
|||||||
# multiple time.
|
# multiple time.
|
||||||
disable=missing-docstring
|
disable=missing-docstring
|
||||||
,locally-disabled
|
,locally-disabled
|
||||||
,locally-enabled
|
|
||||||
,fixme
|
,fixme
|
||||||
,no-member
|
,no-member
|
||||||
,bad-whitespace
|
|
||||||
,wrong-import-position
|
,wrong-import-position
|
||||||
,ungrouped-imports
|
,ungrouped-imports
|
||||||
,import-self
|
,import-self
|
||||||
,bad-continuation
|
|
||||||
,protected-access
|
,protected-access
|
||||||
,unused-argument
|
,unused-argument
|
||||||
,duplicate-code
|
,duplicate-code
|
||||||
,attribute-defined-outside-init
|
,attribute-defined-outside-init
|
||||||
,access-member-before-definition
|
,access-member-before-definition
|
||||||
,no-self-use
|
|
||||||
,broad-except
|
,broad-except
|
||||||
,unneeded-not
|
,unneeded-not
|
||||||
,unidiomatic-typecheck
|
,unidiomatic-typecheck
|
||||||
,undefined-loop-variable
|
,undefined-loop-variable
|
||||||
,redefined-variable-type
|
,consider-using-f-string
|
||||||
,deprecated-lambda
|
|
||||||
|
|
||||||
[REPORTS]
|
[REPORTS]
|
||||||
|
|
||||||
# Set the output format. Available formats are text, parseable, colorized, msvs
|
# Set the output format. Available formats are text, parseable, colorized, msvs
|
||||||
@ -67,10 +61,6 @@ disable=missing-docstring
|
|||||||
|
|
||||||
msg-template={path}:{line}: [{msg_id}({symbol}), {obj}] {msg}
|
msg-template={path}:{line}: [{msg_id}({symbol}), {obj}] {msg}
|
||||||
|
|
||||||
# Put messages in a separate file for each module / package specified on the
|
|
||||||
# command line instead of printing them on stdout. Reports (if any) will be
|
|
||||||
# written in a file name "pylint_global.[txt|html]".
|
|
||||||
files-output=no
|
|
||||||
|
|
||||||
# Tells whether to display a full report or only the messages
|
# Tells whether to display a full report or only the messages
|
||||||
reports=no
|
reports=no
|
||||||
@ -93,14 +83,11 @@ dummy-variables-rgx=_|dummy
|
|||||||
|
|
||||||
# List of additional names supposed to be defined in builtins. Remember that
|
# List of additional names supposed to be defined in builtins. Remember that
|
||||||
# you should avoid to define new builtins when possible.
|
# you should avoid to define new builtins when possible.
|
||||||
additional-builtins=
|
additional-builtins=Node,Mod,Param,Command,Group
|
||||||
|
|
||||||
|
|
||||||
[BASIC]
|
[BASIC]
|
||||||
|
|
||||||
# List of builtins function names that should not be used, separated by a comma
|
|
||||||
#bad-functions=map,filter,apply,input
|
|
||||||
bad-functions=apply,input
|
|
||||||
|
|
||||||
# Regular expression which should only match correct module names
|
# Regular expression which should only match correct module names
|
||||||
module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9_]+))$
|
module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9_]+))$
|
||||||
@ -155,14 +142,8 @@ notes=FIXME,XXX,TODO
|
|||||||
# Maximum number of characters on a single line.
|
# Maximum number of characters on a single line.
|
||||||
max-line-length=132
|
max-line-length=132
|
||||||
|
|
||||||
# List of optional constructs for which whitespace checking is disabled. `dict-
|
|
||||||
# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}.
|
|
||||||
# `trailing-comma` allows a space between comma and closing bracket: (a, ).
|
|
||||||
# `empty-line` allows space-only lines.
|
|
||||||
no-space-check=trailing-comma,dict-separator
|
|
||||||
|
|
||||||
# Maximum number of lines in a module
|
# Maximum number of lines in a module
|
||||||
max-module-lines=1200
|
max-module-lines=1000
|
||||||
|
|
||||||
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
|
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
|
||||||
# tab).
|
# tab).
|
||||||
@ -212,13 +193,13 @@ max-locals=50
|
|||||||
max-returns=10
|
max-returns=10
|
||||||
|
|
||||||
# Maximum number of branch for function / method body
|
# Maximum number of branch for function / method body
|
||||||
max-branches=40
|
max-branches=50
|
||||||
|
|
||||||
# Maximum number of statements in function / method body
|
# Maximum number of statements in function / method body
|
||||||
max-statements=150
|
max-statements=150
|
||||||
|
|
||||||
# Maximum number of parents for a class (see R0901).
|
# Maximum number of parents for a class (see R0901).
|
||||||
max-parents=10
|
max-parents=15
|
||||||
|
|
||||||
# Maximum number of attributes for a class (see R0902).
|
# Maximum number of attributes for a class (see R0902).
|
||||||
max-attributes=50
|
max-attributes=50
|
||||||
|
28
Makefile
28
Makefile
@ -3,12 +3,18 @@
|
|||||||
|
|
||||||
all: clean doc
|
all: clean doc
|
||||||
|
|
||||||
|
# Make spawns a new shell for each command.
|
||||||
|
# Save each PID in temporary file
|
||||||
|
# sleep in order for "test" to have started reliably
|
||||||
demo:
|
demo:
|
||||||
@bin/secop-server -q demo &
|
@rm -f frappydemo.PID || true
|
||||||
@bin/secop-server -q test &
|
@{ bin/frappy-server -q demo & echo $$! >> frappydemo.PID; }
|
||||||
@bin/secop-server -q cryo &
|
@{ bin/frappy-server -q test & echo $$! >> frappydemo.PID; }
|
||||||
@bin/secop-gui localhost:10767 localhost:10768 localhost:10769
|
@{ bin/frappy-server -q cryo & echo $$! >> frappydemo.PID; }
|
||||||
@ps aux|grep [s]ecop-server|awk '{print $$2}'|xargs kill
|
@sleep 0.2
|
||||||
|
@bin/frappy-gui localhost:10767 localhost:10768 localhost:10769
|
||||||
|
@cat frappydemo.PID | xargs kill || true
|
||||||
|
@rm frappydemo.PID
|
||||||
|
|
||||||
build:
|
build:
|
||||||
python3 setup.py build
|
python3 setup.py build
|
||||||
@ -32,18 +38,18 @@ test-verbose:
|
|||||||
python3 $(shell which pytest) -v test -s
|
python3 $(shell which pytest) -v test -s
|
||||||
|
|
||||||
test-coverage:
|
test-coverage:
|
||||||
python3 $(shell which pytest) -v test --cov=secop
|
python3 $(shell which pytest) -v test --cov=frappy
|
||||||
|
|
||||||
doc:
|
doc:
|
||||||
$(MAKE) -C doc html
|
$(MAKE) -C doc html
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
pylint -j 0 -f colorized -r n --rcfile=.pylintrc secop secop_* test
|
pylint -f colorized -r n --rcfile=.pylintrc frappy frappy_* test
|
||||||
|
|
||||||
isort:
|
isort:
|
||||||
@find test -name '*.py' -print0 | xargs -0 isort -e -m 2 -w 80 -ns __init__.py
|
@find test -name '*.py' -print0 | xargs -0 isort -e -m 2 -w 80 -ns __init__.py
|
||||||
@find secop -name '*.py' -print0 | xargs -0 isort -e -m 2 -w 80 -ns __init__.py
|
@find frappy -name '*.py' -print0 | xargs -0 isort -e -m 2 -w 80 -ns __init__.py
|
||||||
@find . -wholename './secop_*.py' -print0 | xargs -0 isort -e -m 2 -w 80 -ns __init__.py
|
@find . -wholename './frappy_*.py' -print0 | xargs -0 isort -e -m 2 -w 80 -ns __init__.py
|
||||||
|
|
||||||
release-patch:
|
release-patch:
|
||||||
MODE="patch" $(MAKE) release
|
MODE="patch" $(MAKE) release
|
||||||
@ -55,8 +61,8 @@ release-major:
|
|||||||
MODE="major" $(MAKE) release
|
MODE="major" $(MAKE) release
|
||||||
|
|
||||||
release:
|
release:
|
||||||
ssh jenkinsng.admin.frm2 -p 29417 build -v -s -p GERRIT_PROJECT=$(shell git config --get remote.origin.url | rev | cut -d '/' -f -3 | rev) -p ARCH=all -p MODE=$(MODE) ReleasePipeline
|
ssh jenkins.admin.frm2.tum.de -p 29417 build -v -s -p GERRIT_PROJECT=$(shell git config --get remote.origin.url | rev | cut -d '/' -f -2 | rev) -p ARCH=all -p MODE=$(MODE) ReleasePipeline
|
||||||
|
|
||||||
|
|
||||||
build-pkg:
|
build-pkg:
|
||||||
debocker build --image jenkinsng.admin.frm2:5000/mlzbase/buster
|
debocker build --image docker.ictrl.frm2.tum.de:5443/mlzbase/buster
|
||||||
|
@ -29,8 +29,8 @@ from os import path
|
|||||||
# Add import path for inplace usage
|
# Add import path for inplace usage
|
||||||
sys.path.insert(0, path.abspath(path.join(path.dirname(__file__), '..')))
|
sys.path.insert(0, path.abspath(path.join(path.dirname(__file__), '..')))
|
||||||
|
|
||||||
from secop.gui.qt import QApplication
|
from frappy.gui.qt import QApplication
|
||||||
from secop.gui.cfg_editor.mainwindow import MainWindow
|
from frappy.gui.cfg_editor.mainwindow import MainWindow
|
||||||
|
|
||||||
|
|
||||||
def main(argv=None):
|
def main(argv=None):
|
@ -26,42 +26,49 @@
|
|||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
import argparse
|
||||||
from os import path
|
from os import path
|
||||||
|
|
||||||
# Add import path for inplace usage
|
# Add import path for inplace usage
|
||||||
sys.path.insert(0, path.abspath(path.join(path.dirname(__file__), '..')))
|
sys.path.insert(0, path.abspath(path.join(path.dirname(__file__), '..')))
|
||||||
|
|
||||||
import mlzlog
|
import logging
|
||||||
|
from mlzlog import ColoredConsoleHandler
|
||||||
|
|
||||||
from secop.gui.qt import QApplication
|
from frappy.gui.qt import QApplication
|
||||||
from secop.gui.mainwindow import MainWindow
|
from frappy.gui.mainwindow import MainWindow
|
||||||
|
|
||||||
|
def parseArgv(argv):
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
loggroup = parser.add_mutually_exclusive_group()
|
||||||
|
loggroup.add_argument('-d', '--debug',
|
||||||
|
help='Enable debug output',
|
||||||
|
action='store_true', default=False)
|
||||||
|
loggroup.add_argument('-q', '--quiet',
|
||||||
|
help='Supress everything but errors',
|
||||||
|
action='store_true', default=False)
|
||||||
|
parser.add_argument('node',
|
||||||
|
help='Nodes the Gui should connect to.\n', metavar='host[:port]',
|
||||||
|
nargs='*', type=str, default=['localhost:10767'])
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
def main(argv=None):
|
def main(argv=None):
|
||||||
if argv is None:
|
if argv is None:
|
||||||
argv = sys.argv
|
argv = sys.argv
|
||||||
|
|
||||||
if '-h' in argv or '--help' in argv:
|
args = parseArgv(argv[1:])
|
||||||
print("Usage: secop-gui [-d] [-h] [host:[port]]")
|
|
||||||
print()
|
|
||||||
print("Option GNU long option Meaning")
|
|
||||||
print("-h --help Show this message")
|
|
||||||
print("-d --debug Enable debug output")
|
|
||||||
print()
|
|
||||||
print("if not given, host defaults to 'localhost' and port to 10767")
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
if '-d' in argv or '--debug' in argv:
|
loglevel = logging.DEBUG if args.debug else (logging.ERROR if args.quiet else logging.INFO)
|
||||||
mlzlog.initLogging('gui', 'debug')
|
logger = logging.getLogger('gui')
|
||||||
else:
|
logger.setLevel(logging.DEBUG)
|
||||||
mlzlog.initLogging('gui', 'info')
|
console = ColoredConsoleHandler()
|
||||||
|
console.setLevel(loglevel)
|
||||||
|
logger.addHandler(console)
|
||||||
|
|
||||||
app = QApplication(argv)
|
app = QApplication(argv)
|
||||||
|
|
||||||
hosts = [host for host in argv[1:] if not host.startswith('-')]
|
win = MainWindow(args.node, logger)
|
||||||
if not hosts:
|
|
||||||
hosts = ['localhost:10767']
|
|
||||||
win = MainWindow(hosts)
|
|
||||||
win.show()
|
win.show()
|
||||||
|
|
||||||
return app.exec_()
|
return app.exec_()
|
@ -27,13 +27,12 @@ import sys
|
|||||||
import argparse
|
import argparse
|
||||||
from os import path
|
from os import path
|
||||||
|
|
||||||
import mlzlog
|
|
||||||
|
|
||||||
# Add import path for inplace usage
|
# Add import path for inplace usage
|
||||||
sys.path.insert(0, path.abspath(path.join(path.dirname(__file__), '..')))
|
sys.path.insert(0, path.abspath(path.join(path.dirname(__file__), '..')))
|
||||||
|
|
||||||
from secop.lib import getGeneralConfig
|
from frappy.lib import generalConfig
|
||||||
from secop.server import Server
|
from frappy.logging import logger
|
||||||
|
from frappy.server import Server
|
||||||
|
|
||||||
|
|
||||||
def parseArgv(argv):
|
def parseArgv(argv):
|
||||||
@ -60,15 +59,26 @@ def parseArgv(argv):
|
|||||||
parser.add_argument('-c',
|
parser.add_argument('-c',
|
||||||
'--cfgfiles',
|
'--cfgfiles',
|
||||||
action='store',
|
action='store',
|
||||||
help="comma separated list of cfg files\n"
|
help="comma separated list of cfg files,\n"
|
||||||
"defaults to <name_of_the_instance>\n"
|
"defaults to <name_of_the_instance>.\n"
|
||||||
"cfgfiles given without '.cfg' extension are searched in the configuration directory,"
|
"cfgfiles given without '.cfg' extension are searched in the configuration directory, "
|
||||||
"else they are treated as path names",
|
"else they are treated as path names",
|
||||||
default=None)
|
default=None)
|
||||||
|
parser.add_argument('-g',
|
||||||
|
'--gencfg',
|
||||||
|
action='store',
|
||||||
|
help="full path of general config file,\n"
|
||||||
|
"defaults to env. variable FRAPPY_CONFIG_FILE\n",
|
||||||
|
default=None)
|
||||||
parser.add_argument('-t',
|
parser.add_argument('-t',
|
||||||
'--test',
|
'--test',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help='Check cfg files only',
|
help='check cfg files only',
|
||||||
|
default=False)
|
||||||
|
parser.add_argument('-r',
|
||||||
|
'--relaxed',
|
||||||
|
action='store_true',
|
||||||
|
help='no checking of problematic behaviour',
|
||||||
default=False)
|
default=False)
|
||||||
return parser.parse_args(argv)
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
@ -80,9 +90,12 @@ def main(argv=None):
|
|||||||
args = parseArgv(argv[1:])
|
args = parseArgv(argv[1:])
|
||||||
|
|
||||||
loglevel = 'debug' if args.verbose else ('error' if args.quiet else 'info')
|
loglevel = 'debug' if args.verbose else ('error' if args.quiet else 'info')
|
||||||
mlzlog.initLogging('secop', loglevel, getGeneralConfig()['logdir'])
|
generalConfig.defaults = {k: args.relaxed for k in (
|
||||||
|
'lazy_number_validation', 'disable_value_range_check', 'legacy_hasiodev', 'tolerate_poll_property')}
|
||||||
|
generalConfig.init(args.gencfg)
|
||||||
|
logger.init(loglevel)
|
||||||
|
|
||||||
srv = Server(args.name, mlzlog.log, cfgfiles=args.cfgfiles, interface=args.port, testonly=args.test)
|
srv = Server(args.name, logger.log, cfgfiles=args.cfgfiles, interface=args.port, testonly=args.test)
|
||||||
|
|
||||||
if args.daemonize:
|
if args.daemonize:
|
||||||
srv.start()
|
srv.start()
|
@ -1,76 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# pylint: disable=invalid-name
|
|
||||||
# -*- 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:
|
|
||||||
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
|
||||||
#
|
|
||||||
# *****************************************************************************
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import argparse
|
|
||||||
from os import path
|
|
||||||
|
|
||||||
# Path magic to make python find our stuff.
|
|
||||||
# also remember our basepath (for etc, pid lookup, etc)
|
|
||||||
basepath = path.abspath(path.join(sys.path[0], '..'))
|
|
||||||
etc_path = path.join(basepath, 'etc')
|
|
||||||
pid_path = path.join(basepath, 'pid')
|
|
||||||
log_path = path.join(basepath, 'log')
|
|
||||||
# sys.path[0] = path.join(basepath, 'src')
|
|
||||||
sys.path[0] = basepath
|
|
||||||
|
|
||||||
# do not move above!
|
|
||||||
import mlzlog
|
|
||||||
from secop.client.console import ClientConsole
|
|
||||||
|
|
||||||
|
|
||||||
def parseArgv(argv):
|
|
||||||
parser = argparse.ArgumentParser(description="Connect to a SECoP server")
|
|
||||||
loggroup = parser.add_mutually_exclusive_group()
|
|
||||||
loggroup.add_argument("-v", "--verbose",
|
|
||||||
help="Output lots of diagnostic information",
|
|
||||||
action='store_true', default=False)
|
|
||||||
loggroup.add_argument("-q", "--quiet", help="suppress non-error messages",
|
|
||||||
action='store_true', default=False)
|
|
||||||
parser.add_argument("name",
|
|
||||||
type=str,
|
|
||||||
help="Name of the instance.\n"
|
|
||||||
" Uses etc/name.cfg for configuration\n",)
|
|
||||||
return parser.parse_args()
|
|
||||||
|
|
||||||
|
|
||||||
def main(argv=None):
|
|
||||||
if argv is None:
|
|
||||||
argv = sys.argv
|
|
||||||
|
|
||||||
args = parseArgv(argv[1:])
|
|
||||||
|
|
||||||
loglevel = 'debug' if args.verbose else ('error' if args.quiet else 'info')
|
|
||||||
mlzlog.initLogging('console', loglevel, log_path)
|
|
||||||
|
|
||||||
console = ClientConsole(args.name, basepath)
|
|
||||||
|
|
||||||
try:
|
|
||||||
console.run()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
console.close()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.exit(main(sys.argv))
|
|
@ -42,7 +42,7 @@ import ast
|
|||||||
# Add import path for inplace usage
|
# Add import path for inplace usage
|
||||||
sys.path.insert(0, path.abspath(path.join(path.dirname(__file__), '..')))
|
sys.path.insert(0, path.abspath(path.join(path.dirname(__file__), '..')))
|
||||||
|
|
||||||
from secop.lib import get_class, formatException
|
from frappy.lib import get_class, formatException
|
||||||
|
|
||||||
class LineHandler(asyncore.dispatcher_with_send):
|
class LineHandler(asyncore.dispatcher_with_send):
|
||||||
|
|
||||||
@ -136,7 +136,7 @@ for arg in sys.argv[1:]:
|
|||||||
pass
|
pass
|
||||||
opts[k] = v
|
opts[k] = v
|
||||||
verbose = opts.pop('verbose', False)
|
verbose = opts.pop('verbose', False)
|
||||||
opts['cls'] = 'secop_psi.ls370sim.Ls370Sim'
|
opts['cls'] = 'frappy_psi.ls370sim.Ls370Sim'
|
||||||
opts['port'] = 4567
|
opts['port'] = 4567
|
||||||
if len(args) > 2:
|
if len(args) > 2:
|
||||||
raise ValueError('do not know about: %s' % ' '.join(args[2:]))
|
raise ValueError('do not know about: %s' % ' '.join(args[2:]))
|
||||||
|
@ -7,11 +7,11 @@ bindto = 0.0.0.0
|
|||||||
bindport = 5000
|
bindport = 5000
|
||||||
|
|
||||||
[module cap]
|
[module cap]
|
||||||
class = secop_psi.ah2700.Capacitance
|
class = frappy_psi.ah2700.Capacitance
|
||||||
description = capacitance
|
description = capacitance
|
||||||
uri=ldmse3-ts:3015
|
uri=ldmse3-ts:3015
|
||||||
|
|
||||||
#[module ahcom]
|
#[module ahcom]
|
||||||
#class = secop_psi.ah2700.StringIO
|
#class = frappy_psi.ah2700.StringIO
|
||||||
#uri=ldmse3-ts:3015
|
#uri=ldmse3-ts:3015
|
||||||
#description = serial communicator to an AH2700
|
#description = serial communicator to an AH2700
|
||||||
|
@ -19,7 +19,7 @@ bindto=0.0.0.0
|
|||||||
bindport=10767
|
bindport=10767
|
||||||
|
|
||||||
[module enable]
|
[module enable]
|
||||||
class=secop_mlz.entangle.NamedDigitalOutput
|
class=frappy_mlz.entangle.NamedDigitalOutput
|
||||||
tangodevice='tango://localhost:10000/box/plc/_enable'
|
tangodevice='tango://localhost:10000/box/plc/_enable'
|
||||||
value.datatype=["enum", {'On':1,'Off':0}]
|
value.datatype=["enum", {'On':1,'Off':0}]
|
||||||
target.datatype=["enum", {'On':1,'Off':0}]
|
target.datatype=["enum", {'On':1,'Off':0}]
|
||||||
@ -27,7 +27,7 @@ target.datatype=["enum", {'On':1,'Off':0}]
|
|||||||
.visibility='advanced'
|
.visibility='advanced'
|
||||||
|
|
||||||
[module polarity]
|
[module polarity]
|
||||||
class=secop_mlz.entangle.NamedDigitalOutput
|
class=frappy_mlz.entangle.NamedDigitalOutput
|
||||||
tangodevice=tango://localhost:10000/box/plc/_polarity
|
tangodevice=tango://localhost:10000/box/plc/_polarity
|
||||||
value.datatype=["enum", {'+1':1,'0':0,'-1':-1}]
|
value.datatype=["enum", {'+1':1,'0':0,'-1':-1}]
|
||||||
target.datatype=["enum", {'+1':1,'0':0,'-1':-1}]
|
target.datatype=["enum", {'+1':1,'0':0,'-1':-1}]
|
||||||
@ -41,7 +41,7 @@ comtries=50
|
|||||||
|
|
||||||
|
|
||||||
[module symmetry]
|
[module symmetry]
|
||||||
class=secop_mlz.entangle.NamedDigitalOutput
|
class=frappy_mlz.entangle.NamedDigitalOutput
|
||||||
tangodevice=tango://localhost:10000/box/plc/_symmetric
|
tangodevice=tango://localhost:10000/box/plc/_symmetric
|
||||||
value.datatype=["enum",{'symmetric':1,'short':0, 'asymmetric':-1}]
|
value.datatype=["enum",{'symmetric':1,'short':0, 'asymmetric':-1}]
|
||||||
target.datatype=["enum",{'symmetric':1,'short':0, 'asymmetric':-1}]
|
target.datatype=["enum",{'symmetric':1,'short':0, 'asymmetric':-1}]
|
||||||
@ -51,35 +51,35 @@ target.datatype=["enum",{'symmetric':1,'short':0, 'asymmetric':-1}]
|
|||||||
.visibility=advanced
|
.visibility=advanced
|
||||||
|
|
||||||
[module T1]
|
[module T1]
|
||||||
class=secop_mlz.entangle.AnalogInput
|
class=frappy_mlz.entangle.AnalogInput
|
||||||
tangodevice=tango://localhost:10000/box/plc/_t1
|
tangodevice=tango://localhost:10000/box/plc/_t1
|
||||||
.description=Temperature1 of the coils system
|
.description=Temperature1 of the coils system
|
||||||
#warnlimits=(0, 50)
|
#warnlimits=(0, 50)
|
||||||
value.unit='degC'
|
value.unit='degC'
|
||||||
|
|
||||||
[module T2]
|
[module T2]
|
||||||
class=secop_mlz.entangle.AnalogInput
|
class=frappy_mlz.entangle.AnalogInput
|
||||||
tangodevice=tango://localhost:10000/box/plc/_t2
|
tangodevice=tango://localhost:10000/box/plc/_t2
|
||||||
.description=Temperature2 of the coils system
|
.description=Temperature2 of the coils system
|
||||||
#warnlimits=(0, 50)
|
#warnlimits=(0, 50)
|
||||||
value.unit='degC'
|
value.unit='degC'
|
||||||
|
|
||||||
[module T3]
|
[module T3]
|
||||||
class=secop_mlz.entangle.AnalogInput
|
class=frappy_mlz.entangle.AnalogInput
|
||||||
tangodevice=tango://localhost:10000/box/plc/_t3
|
tangodevice=tango://localhost:10000/box/plc/_t3
|
||||||
.description=Temperature3 of the coils system
|
.description=Temperature3 of the coils system
|
||||||
#warnlimits=(0, 50)
|
#warnlimits=(0, 50)
|
||||||
value.unit='degC'
|
value.unit='degC'
|
||||||
|
|
||||||
[module T4]
|
[module T4]
|
||||||
class=secop_mlz.entangle.AnalogInput
|
class=frappy_mlz.entangle.AnalogInput
|
||||||
tangodevice=tango://localhost:10000/box/plc/_t4
|
tangodevice=tango://localhost:10000/box/plc/_t4
|
||||||
.description=Temperature4 of the coils system
|
.description=Temperature4 of the coils system
|
||||||
#warnlimits=(0, 50)
|
#warnlimits=(0, 50)
|
||||||
value.unit='degC'
|
value.unit='degC'
|
||||||
|
|
||||||
[module currentsource]
|
[module currentsource]
|
||||||
class=secop_mlz.entangle.PowerSupply
|
class=frappy_mlz.entangle.PowerSupply
|
||||||
tangodevice=tango://localhost:10000/box/lambda/curr
|
tangodevice=tango://localhost:10000/box/lambda/curr
|
||||||
.description=Device for the magnet power supply (current mode)
|
.description=Device for the magnet power supply (current mode)
|
||||||
abslimits=(0,200)
|
abslimits=(0,200)
|
||||||
@ -92,7 +92,7 @@ voltage=10
|
|||||||
.visibility=advanced
|
.visibility=advanced
|
||||||
|
|
||||||
[module mf]
|
[module mf]
|
||||||
class=secop_mlz.amagnet.GarfieldMagnet
|
class=frappy_mlz.amagnet.GarfieldMagnet
|
||||||
.description=magnetic field module, handling polarity switching and stuff
|
.description=magnetic field module, handling polarity switching and stuff
|
||||||
subdev_currentsource=currentsource
|
subdev_currentsource=currentsource
|
||||||
subdev_enable=enable
|
subdev_enable=enable
|
||||||
|
32
cfg/ccr.cfg
32
cfg/ccr.cfg
@ -11,7 +11,7 @@ bindto=0.0.0.0
|
|||||||
bindport=10767
|
bindport=10767
|
||||||
|
|
||||||
[module automatik]
|
[module automatik]
|
||||||
class=secop_mlz.entangle.NamedDigitalOutput
|
class=frappy_mlz.entangle.NamedDigitalOutput
|
||||||
tangodevice=tango://localhost:10000/box/plc/_automatik
|
tangodevice=tango://localhost:10000/box/plc/_automatik
|
||||||
mapping=dict(Off=0,p1=1,p2=2)
|
mapping=dict(Off=0,p1=1,p2=2)
|
||||||
description="controls the (simple) pressure regulation
|
description="controls the (simple) pressure regulation
|
||||||
@ -19,13 +19,13 @@ description="controls the (simple) pressure regulation
|
|||||||
selects between off, regulate on p1 or regulate on p2 sensor"
|
selects between off, regulate on p1 or regulate on p2 sensor"
|
||||||
|
|
||||||
[module compressor]
|
[module compressor]
|
||||||
class=secop_mlz.entangle.NamedDigitalOutput
|
class=frappy_mlz.entangle.NamedDigitalOutput
|
||||||
tangodevice=tango://localhost:10000/box/plc/_cooler_onoff
|
tangodevice=tango://localhost:10000/box/plc/_cooler_onoff
|
||||||
mapping=dict(Off=0,On=1)
|
mapping=dict(Off=0,On=1)
|
||||||
description=control the compressor (on/off)
|
description=control the compressor (on/off)
|
||||||
|
|
||||||
[module gas]
|
[module gas]
|
||||||
class=secop_mlz.entangle.NamedDigitalOutput
|
class=frappy_mlz.entangle.NamedDigitalOutput
|
||||||
tangodevice=tango://localhost:10000/box/plc/_gas_onoff
|
tangodevice=tango://localhost:10000/box/plc/_gas_onoff
|
||||||
mapping=dict(Off=0,On=1)
|
mapping=dict(Off=0,On=1)
|
||||||
description=control the gas inlet into the ccr (on/off)
|
description=control the gas inlet into the ccr (on/off)
|
||||||
@ -35,7 +35,7 @@ description=control the gas inlet into the ccr (on/off)
|
|||||||
note: if the pressure regulation is active, it enslave this device
|
note: if the pressure regulation is active, it enslave this device
|
||||||
|
|
||||||
[module vacuum]
|
[module vacuum]
|
||||||
class=secop_mlz.entangle.NamedDigitalOutput
|
class=frappy_mlz.entangle.NamedDigitalOutput
|
||||||
tangodevice=tango://localhost:10000/box/plc/_vacuum_Onoff
|
tangodevice=tango://localhost:10000/box/plc/_vacuum_Onoff
|
||||||
mapping=dict(Off=0,On=1)
|
mapping=dict(Off=0,On=1)
|
||||||
description=control the vacuum inlet into the ccr (on/off)
|
description=control the vacuum inlet into the ccr (on/off)
|
||||||
@ -44,19 +44,19 @@ description=control the vacuum inlet into the ccr (on/off)
|
|||||||
note: if the pressure regulation is active, it enslave this device
|
note: if the pressure regulation is active, it enslave this device
|
||||||
|
|
||||||
[module p1]
|
[module p1]
|
||||||
class=secop_mlz.entangle.AnalogInput
|
class=frappy_mlz.entangle.AnalogInput
|
||||||
tangodevice=tango://localhost:10000/box/plc/_p1
|
tangodevice=tango://localhost:10000/box/plc/_p1
|
||||||
value.unit='mbar'
|
value.unit='mbar'
|
||||||
description=pressure sensor 1 (linear scale)
|
description=pressure sensor 1 (linear scale)
|
||||||
|
|
||||||
[module p2]
|
[module p2]
|
||||||
class=secop_mlz.entangle.AnalogInput
|
class=frappy_mlz.entangle.AnalogInput
|
||||||
tangodevice=tango://localhost:10000/box/plc/_p2
|
tangodevice=tango://localhost:10000/box/plc/_p2
|
||||||
value.unit='mbar'
|
value.unit='mbar'
|
||||||
description=pressure sensor 2 (selectable curve)
|
description=pressure sensor 2 (selectable curve)
|
||||||
|
|
||||||
[module curve_p2]
|
[module curve_p2]
|
||||||
class=secop_mlz.entangle.NamedDigitalInput
|
class=frappy_mlz.entangle.NamedDigitalInput
|
||||||
tangodevice=tango://localhost:10000/box/plc/_curve
|
tangodevice=tango://localhost:10000/box/plc/_curve
|
||||||
value.default=0
|
value.default=0
|
||||||
description=calibration curve for pressure sensor 2
|
description=calibration curve for pressure sensor 2
|
||||||
@ -71,25 +71,25 @@ mapping="{'0-10V':0, '0-1000mbar':1, '1-9V to 0-1 mbar':2,
|
|||||||
|
|
||||||
# sensors
|
# sensors
|
||||||
[module T_sample]
|
[module T_sample]
|
||||||
class=secop_mlz.entangle.Sensor
|
class=frappy_mlz.entangle.Sensor
|
||||||
tangodevice=tango://localhost:10000/box/sample/sensora
|
tangodevice=tango://localhost:10000/box/sample/sensora
|
||||||
value.unit='K'
|
value.unit='K'
|
||||||
description=sample temperature
|
description=sample temperature
|
||||||
|
|
||||||
[module T_stick]
|
[module T_stick]
|
||||||
class=secop_mlz.entangle.Sensor
|
class=frappy_mlz.entangle.Sensor
|
||||||
tangodevice=tango://localhost:10000/box/stick/sensorb
|
tangodevice=tango://localhost:10000/box/stick/sensorb
|
||||||
value.unit='K'
|
value.unit='K'
|
||||||
description=temperature at bottom of sample stick
|
description=temperature at bottom of sample stick
|
||||||
|
|
||||||
[module T_coldhead]
|
[module T_coldhead]
|
||||||
class=secop_mlz.entangle.Sensor
|
class=frappy_mlz.entangle.Sensor
|
||||||
tangodevice=tango://localhost:10000/box/coldhead/sensorc
|
tangodevice=tango://localhost:10000/box/coldhead/sensorc
|
||||||
value.unit='K'
|
value.unit='K'
|
||||||
description=temperature at coldhead
|
description=temperature at coldhead
|
||||||
|
|
||||||
[module T_tube]
|
[module T_tube]
|
||||||
class=secop_mlz.entangle.Sensor
|
class=frappy_mlz.entangle.Sensor
|
||||||
tangodevice=tango://localhost:10000/box/tube/sensord
|
tangodevice=tango://localhost:10000/box/tube/sensord
|
||||||
value.unit='K'
|
value.unit='K'
|
||||||
description=temperature at thermal coupling tube <-> stick
|
description=temperature at thermal coupling tube <-> stick
|
||||||
@ -98,7 +98,7 @@ description=temperature at thermal coupling tube <-> stick
|
|||||||
# regulations
|
# regulations
|
||||||
|
|
||||||
[module T_stick_regulation]
|
[module T_stick_regulation]
|
||||||
class=secop_mlz.entangle.TemperatureController
|
class=frappy_mlz.entangle.TemperatureController
|
||||||
tangodevice=tango://localhost:10000/box/stick/control2
|
tangodevice=tango://localhost:10000/box/stick/control2
|
||||||
heateroutput.default=0
|
heateroutput.default=0
|
||||||
description=regulation of stick temperature
|
description=regulation of stick temperature
|
||||||
@ -114,7 +114,7 @@ value.unit='K'
|
|||||||
|
|
||||||
# OMG! a NamedDigitalOutput, but with float'ints' 0..3
|
# OMG! a NamedDigitalOutput, but with float'ints' 0..3
|
||||||
[module T_stick_regulation_heaterrange]
|
[module T_stick_regulation_heaterrange]
|
||||||
class=secop_mlz.entangle.AnalogOutput
|
class=frappy_mlz.entangle.AnalogOutput
|
||||||
tangodevice=tango://localhost:10000/box/stick/range2
|
tangodevice=tango://localhost:10000/box/stick/range2
|
||||||
precision.default=1
|
precision.default=1
|
||||||
abslimits=(0,3)
|
abslimits=(0,3)
|
||||||
@ -122,7 +122,7 @@ description=heaterrange for stick regulation
|
|||||||
|
|
||||||
|
|
||||||
[module T_tube_regulation]
|
[module T_tube_regulation]
|
||||||
class=secop_mlz.entangle.TemperatureController
|
class=frappy_mlz.entangle.TemperatureController
|
||||||
tangodevice=tango://localhost:10000/box/tube/control1
|
tangodevice=tango://localhost:10000/box/tube/control1
|
||||||
description=regulation of tube temperature
|
description=regulation of tube temperature
|
||||||
heateroutput.default=0
|
heateroutput.default=0
|
||||||
@ -138,13 +138,13 @@ value.unit='K'
|
|||||||
|
|
||||||
# OMG! a NamedDigitalOutput, but with float'ints' 0..3
|
# OMG! a NamedDigitalOutput, but with float'ints' 0..3
|
||||||
#[module T_tube_regulation_heaterrange]
|
#[module T_tube_regulation_heaterrange]
|
||||||
#class=secop_mlz.entangle.AnalogOutput
|
#class=frappy_mlz.entangle.AnalogOutput
|
||||||
#tangodevice=tango://localhost:10000/box/tube/range1
|
#tangodevice=tango://localhost:10000/box/tube/range1
|
||||||
#precision.default=1
|
#precision.default=1
|
||||||
#abslimits=(0,3)
|
#abslimits=(0,3)
|
||||||
|
|
||||||
[module T_tube_regulation_heaterrange]
|
[module T_tube_regulation_heaterrange]
|
||||||
class=secop_mlz.entangle.NamedDigitalOutput
|
class=frappy_mlz.entangle.NamedDigitalOutput
|
||||||
tangodevice=tango://localhost:10000/box/tube/range1
|
tangodevice=tango://localhost:10000/box/tube/range1
|
||||||
mapping=dict(Off=0, Low=1, Medium=2, High=3)
|
mapping=dict(Off=0, Low=1, Medium=2, High=3)
|
||||||
description=heaterrange for tube regulation
|
description=heaterrange for tube regulation
|
||||||
|
50
cfg/cryo.cfg
50
cfg/cryo.cfg
@ -1,50 +0,0 @@
|
|||||||
[node cryo_7]
|
|
||||||
# set SEC-node properties
|
|
||||||
description = short description
|
|
||||||
.
|
|
||||||
This is a very long description providing all the glory details in all the glory details about the stuff we are describing
|
|
||||||
|
|
||||||
|
|
||||||
[interface tcp]
|
|
||||||
type=tcp
|
|
||||||
bindto=0.0.0.0
|
|
||||||
bindport=10769
|
|
||||||
|
|
||||||
[module cryo]
|
|
||||||
# some (non-defaut) module properties
|
|
||||||
.group=very important/stuff
|
|
||||||
.description=A simulated cc cryostat with heat-load, specific heat for the sample
|
|
||||||
and a temperature dependend heat-link between sample and regulation.
|
|
||||||
|
|
||||||
# class of module:
|
|
||||||
class=secop_demo.cryo.Cryostat
|
|
||||||
|
|
||||||
# some parameters
|
|
||||||
jitter=0.1
|
|
||||||
T_start=10.0
|
|
||||||
target=10.0
|
|
||||||
looptime=1
|
|
||||||
ramp=6
|
|
||||||
maxpower=20.0
|
|
||||||
heater=4.1
|
|
||||||
p=40
|
|
||||||
i=10
|
|
||||||
d=2
|
|
||||||
mode=pid
|
|
||||||
tolerance=0.1
|
|
||||||
window=30
|
|
||||||
timeout=900
|
|
||||||
|
|
||||||
# some (non-default) parameter properties
|
|
||||||
pollinterval.export=False
|
|
||||||
|
|
||||||
# some parameter grouping
|
|
||||||
p.group=pid
|
|
||||||
i.group=pid
|
|
||||||
d.group=pid
|
|
||||||
|
|
||||||
value.unit=K
|
|
||||||
|
|
||||||
# test custom properties
|
|
||||||
value.test=customized value
|
|
||||||
|
|
37
cfg/cryo_cfg.py
Normal file
37
cfg/cryo_cfg.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
#####################################################################
|
||||||
|
# Python version of frappy config
|
||||||
|
#####################################################################
|
||||||
|
|
||||||
|
Node('cryo_7.frappy.demo',
|
||||||
|
'short description' \
|
||||||
|
'' \
|
||||||
|
'' \
|
||||||
|
'This is a very long description providing all the glory details in all the ' \
|
||||||
|
'glory details about the stuff we are describing',
|
||||||
|
'tcp://10769',
|
||||||
|
more="blub",
|
||||||
|
)
|
||||||
|
|
||||||
|
Mod('cryo',
|
||||||
|
'frappy_demo.cryo.Cryostat',
|
||||||
|
'A simulated cc cryostat with heat-load, specific heat for the sample and a ' \
|
||||||
|
'temperature dependend heat-link between sample and regulation.',
|
||||||
|
group='very important/stuff',
|
||||||
|
jitter=0.1,
|
||||||
|
T_start=10.0,
|
||||||
|
target=10.0,
|
||||||
|
looptime=1,
|
||||||
|
ramp=6,
|
||||||
|
maxpower=20.0,
|
||||||
|
heater=4.1,
|
||||||
|
mode='pid',
|
||||||
|
tolerance=0.1,
|
||||||
|
window=30,
|
||||||
|
timeout=900,
|
||||||
|
p = Param(40, unit='%/K'), # in case 'default' is the first arg, we can omit 'default='
|
||||||
|
i = 10,
|
||||||
|
d = 2,
|
||||||
|
pid = Group('p', 'i', 'd'),
|
||||||
|
pollinterval = Param(export=False),
|
||||||
|
value = Param(unit = 'K', test = 'customized value'),
|
||||||
|
)
|
43
cfg/demo.cfg
43
cfg/demo.cfg
@ -1,43 +0,0 @@
|
|||||||
[node Equipment_ID_for_demonstration]
|
|
||||||
description = virtual modules to play around with
|
|
||||||
|
|
||||||
[interface tcp]
|
|
||||||
bindto=0.0.0.0
|
|
||||||
bindport=10767
|
|
||||||
|
|
||||||
[module heatswitch]
|
|
||||||
class=secop_demo.modules.Switch
|
|
||||||
switch_on_time=5
|
|
||||||
switch_off_time=10
|
|
||||||
.description="Heatswitch for `mf` device"
|
|
||||||
|
|
||||||
[module mf]
|
|
||||||
class=secop_demo.modules.MagneticField
|
|
||||||
heatswitch = heatswitch
|
|
||||||
.description="simulates some cryomagnet with persistent/non-persistent switching"
|
|
||||||
|
|
||||||
[module ts]
|
|
||||||
class=secop_demo.modules.SampleTemp
|
|
||||||
sensor = 'Q1329V7R3'
|
|
||||||
ramp = 4
|
|
||||||
target = 10
|
|
||||||
value = 10
|
|
||||||
.description = "some temperature"
|
|
||||||
|
|
||||||
[module tc1]
|
|
||||||
class=secop_demo.modules.CoilTemp
|
|
||||||
sensor="X34598T7"
|
|
||||||
.description = "some temperature"
|
|
||||||
|
|
||||||
[module tc2]
|
|
||||||
class=secop_demo.modules.CoilTemp
|
|
||||||
sensor="X39284Q8'
|
|
||||||
.description = "some temperature"
|
|
||||||
|
|
||||||
[module label]
|
|
||||||
class=secop_demo.modules.Label
|
|
||||||
system=Cryomagnet MX15
|
|
||||||
subdev_mf=mf
|
|
||||||
subdev_ts=ts
|
|
||||||
.description = "some label indicating the state of the magnet `mf`."
|
|
||||||
|
|
45
cfg/demo_cfg.py
Normal file
45
cfg/demo_cfg.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
Node('demo.frappy.demo',
|
||||||
|
'Basic demo server for frappy',
|
||||||
|
'tcp://10767',
|
||||||
|
)
|
||||||
|
|
||||||
|
Mod('heatswitch',
|
||||||
|
'frappy_demo.modules.Switch',
|
||||||
|
'Heatswitch for `mf` device',
|
||||||
|
switch_on_time = 5,
|
||||||
|
switch_off_time = 10,
|
||||||
|
)
|
||||||
|
|
||||||
|
Mod('mf',
|
||||||
|
'frappy_demo.modules.MagneticField',
|
||||||
|
'simulates some cryomagnet with persistent/non-persistent switching',
|
||||||
|
heatswitch = 'heatswitch',
|
||||||
|
)
|
||||||
|
|
||||||
|
Mod('ts',
|
||||||
|
'frappy_demo.modules.SampleTemp',
|
||||||
|
'some temperature',
|
||||||
|
sensor = 'Q1329V7R3',
|
||||||
|
ramp = 4,
|
||||||
|
target = 10,
|
||||||
|
value = 10,
|
||||||
|
)
|
||||||
|
|
||||||
|
Mod('tc1',
|
||||||
|
'frappy_demo.modules.CoilTemp',
|
||||||
|
'some temperature',
|
||||||
|
sensor = 'X34598T7',
|
||||||
|
)
|
||||||
|
|
||||||
|
Mod('tc2',
|
||||||
|
'frappy_demo.modules.CoilTemp',
|
||||||
|
'some temperature',
|
||||||
|
sensor = 'X39284Q8',
|
||||||
|
)
|
||||||
|
|
||||||
|
Mod('label',
|
||||||
|
'frappy_demo.modules.Label',
|
||||||
|
'some label indicating the state of the magnet `,f`.',
|
||||||
|
subdev_mf = 'mf',
|
||||||
|
subdev_ts = 'ts',
|
||||||
|
)
|
@ -7,23 +7,23 @@ bindto=0.0.0.0
|
|||||||
bindport=10767
|
bindport=10767
|
||||||
|
|
||||||
[module tc1]
|
[module tc1]
|
||||||
class=secop_demo.modules.CoilTemp
|
class=frappy_demo.modules.CoilTemp
|
||||||
sensor="X34598T7"
|
sensor="X34598T7"
|
||||||
|
|
||||||
[module tc2]
|
[module tc2]
|
||||||
class=secop_demo.modules.CoilTemp
|
class=frappy_demo.modules.CoilTemp
|
||||||
sensor="X39284Q8'
|
sensor="X39284Q8'
|
||||||
|
|
||||||
|
|
||||||
[module sensor1]
|
[module sensor1]
|
||||||
class=secop_ess.epics.EpicsReadable
|
class=frappy_ess.epics.EpicsReadable
|
||||||
epics_version="v4"
|
epics_version="v4"
|
||||||
.group="Lakeshore336"
|
.group="Lakeshore336"
|
||||||
value_pv="DEV:KRDG1"
|
value_pv="DEV:KRDG1"
|
||||||
|
|
||||||
|
|
||||||
[module loop1]
|
[module loop1]
|
||||||
class=secop_ess.epics.EpicsTempCtrl
|
class=frappy_ess.epics.EpicsTempCtrl
|
||||||
epics_version="v4"
|
epics_version="v4"
|
||||||
.group="Lakeshore336"
|
.group="Lakeshore336"
|
||||||
|
|
||||||
@ -33,14 +33,14 @@ heaterrange_pv="DEV:RANGE_S1"
|
|||||||
|
|
||||||
|
|
||||||
[module sensor2]
|
[module sensor2]
|
||||||
class=secop_ess.epics.EpicsReadable
|
class=frappy_ess.epics.EpicsReadable
|
||||||
epics_version="v4"
|
epics_version="v4"
|
||||||
.group="Lakeshore336"
|
.group="Lakeshore336"
|
||||||
value_pv="DEV:KRDG2"
|
value_pv="DEV:KRDG2"
|
||||||
|
|
||||||
|
|
||||||
[module loop2]
|
[module loop2]
|
||||||
class=secop_ess.epics.EpicsTempCtrl
|
class=frappy_ess.epics.EpicsTempCtrl
|
||||||
epics_version="v4"
|
epics_version="v4"
|
||||||
.group="Lakeshore336"
|
.group="Lakeshore336"
|
||||||
|
|
||||||
|
5
cfg/generalConfig.cfg
Normal file
5
cfg/generalConfig.cfg
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
[FRAPPY]
|
||||||
|
# general config for running in git repo
|
||||||
|
logdir = ./log
|
||||||
|
piddir = ./pid
|
||||||
|
confdir = ./cfg
|
@ -1,24 +1,28 @@
|
|||||||
[node LscSIM.psi.ch]
|
[NODE]
|
||||||
|
id = LscSIM.psi.ch
|
||||||
description = Lsc Simulation at PSI
|
description = Lsc Simulation at PSI
|
||||||
|
|
||||||
[interface tcp]
|
[INTERFACE]
|
||||||
type = tcp
|
uri = tcp://5000
|
||||||
bindto = 0.0.0.0
|
|
||||||
bindport = 5000
|
|
||||||
|
|
||||||
[module res]
|
[lscom]
|
||||||
class = secop_psi.ls370res.ResChannel
|
class = frappy_psi.ls370sim.Ls370Sim
|
||||||
.channel = 3
|
description = simulated serial communicator to a LS 370
|
||||||
.description = resistivity
|
visibility = 3
|
||||||
.main = lsmain
|
|
||||||
.iodev = lscom
|
|
||||||
|
|
||||||
[module lsmain]
|
[sw]
|
||||||
class = secop_psi.ls370res.Main
|
class = frappy_psi.ls370res.Switcher
|
||||||
.description = main control of Lsc controller
|
description = channel switcher for Lsc controller
|
||||||
.iodev = lscom
|
io = lscom
|
||||||
|
|
||||||
[module lscom]
|
[a]
|
||||||
class = secop_psi.ls370sim.Ls370Sim
|
class = frappy_psi.ls370res.ResChannel
|
||||||
.description = simulated serial communicator to a LS 370
|
channel = 1
|
||||||
.visibility = 3
|
description = resistivity
|
||||||
|
switcher = sw
|
||||||
|
|
||||||
|
[b]
|
||||||
|
class = frappy_psi.ls370res.ResChannel
|
||||||
|
channel = 3
|
||||||
|
description = resistivity
|
||||||
|
switcher = sw
|
||||||
|
@ -7,12 +7,12 @@ bindto = 0.0.0.0
|
|||||||
bindport = 5000
|
bindport = 5000
|
||||||
|
|
||||||
[module lsmain]
|
[module lsmain]
|
||||||
class = secop_psi.ls370res.Main
|
class = frappy_psi.ls370res.Main
|
||||||
description = main control of Lsc controller
|
description = main control of Lsc controller
|
||||||
uri = localhost:4567
|
uri = localhost:4567
|
||||||
|
|
||||||
[module res]
|
[module res]
|
||||||
class = secop_psi.ls370res.ResChannel
|
class = frappy_psi.ls370res.ResChannel
|
||||||
vexc = '2mV'
|
vexc = '2mV'
|
||||||
channel = 3
|
channel = 3
|
||||||
description = resistivity
|
description = resistivity
|
||||||
|
128
cfg/ppms.cfg
128
cfg/ppms.cfg
@ -6,119 +6,119 @@ description = PPMS at PSI
|
|||||||
uri = tcp://5000
|
uri = tcp://5000
|
||||||
|
|
||||||
[tt]
|
[tt]
|
||||||
class = secop_psi.ppms.Temp
|
class = frappy_psi.ppms.Temp
|
||||||
description = main temperature
|
description = main temperature
|
||||||
iodev = ppms
|
io = ppms
|
||||||
|
|
||||||
[mf]
|
[mf]
|
||||||
class = secop_psi.ppms.Field
|
class = frappy_psi.ppms.Field
|
||||||
target.min = -9
|
target.min = -9
|
||||||
target.max = 9
|
target.max = 9
|
||||||
.description = magnetic field
|
description = magnetic field
|
||||||
.iodev = ppms
|
io = ppms
|
||||||
|
|
||||||
[pos]
|
[pos]
|
||||||
class = secop_psi.ppms.Position
|
class = frappy_psi.ppms.Position
|
||||||
.description = sample rotator
|
description = sample rotator
|
||||||
.iodev = ppms
|
io = ppms
|
||||||
|
|
||||||
[lev]
|
[lev]
|
||||||
class = secop_psi.ppms.Level
|
class = frappy_psi.ppms.Level
|
||||||
.description = helium level
|
description = helium level
|
||||||
.iodev = ppms
|
io = ppms
|
||||||
|
|
||||||
[chamber]
|
[chamber]
|
||||||
class = secop_psi.ppms.Chamber
|
class = frappy_psi.ppms.Chamber
|
||||||
.description = chamber state
|
description = chamber state
|
||||||
.iodev = ppms
|
io = ppms
|
||||||
|
|
||||||
[r1]
|
[r1]
|
||||||
class = secop_psi.ppms.BridgeChannel
|
class = frappy_psi.ppms.BridgeChannel
|
||||||
.description = resistivity channel 1
|
description = resistivity channel 1
|
||||||
.no = 1
|
no = 1
|
||||||
value.unit = Ohm
|
value.unit = Ohm
|
||||||
.iodev = ppms
|
io = ppms
|
||||||
|
|
||||||
[r2]
|
[r2]
|
||||||
class = secop_psi.ppms.BridgeChannel
|
class = frappy_psi.ppms.BridgeChannel
|
||||||
.description = resistivity channel 2
|
description = resistivity channel 2
|
||||||
.no = 2
|
no = 2
|
||||||
value.unit = Ohm
|
value.unit = Ohm
|
||||||
.iodev = ppms
|
io = ppms
|
||||||
|
|
||||||
[r3]
|
[r3]
|
||||||
class = secop_psi.ppms.BridgeChannel
|
class = frappy_psi.ppms.BridgeChannel
|
||||||
.description = resistivity channel 3
|
description = resistivity channel 3
|
||||||
.no = 3
|
no = 3
|
||||||
value.unit = Ohm
|
value.unit = Ohm
|
||||||
.iodev = ppms
|
io = ppms
|
||||||
|
|
||||||
[r4]
|
[r4]
|
||||||
class = secop_psi.ppms.BridgeChannel
|
class = frappy_psi.ppms.BridgeChannel
|
||||||
.description = resistivity channel 4
|
description = resistivity channel 4
|
||||||
.no = 4
|
no = 4
|
||||||
value.unit = Ohm
|
value.unit = Ohm
|
||||||
.iodev = ppms
|
io = ppms
|
||||||
|
|
||||||
[i1]
|
[i1]
|
||||||
class = secop_psi.ppms.Channel
|
class = frappy_psi.ppms.Channel
|
||||||
.description = current channel 1
|
description = current channel 1
|
||||||
.no = 1
|
no = 1
|
||||||
value.unit = uA
|
value.unit = uA
|
||||||
.iodev = ppms
|
io = ppms
|
||||||
|
|
||||||
[i2]
|
[i2]
|
||||||
class = secop_psi.ppms.Channel
|
class = frappy_psi.ppms.Channel
|
||||||
.description = current channel 2
|
description = current channel 2
|
||||||
.no = 2
|
no = 2
|
||||||
value.unit = uA
|
value.unit = uA
|
||||||
.iodev = ppms
|
io = ppms
|
||||||
|
|
||||||
[i3]
|
[i3]
|
||||||
class = secop_psi.ppms.Channel
|
class = frappy_psi.ppms.Channel
|
||||||
.description = current channel 3
|
description = current channel 3
|
||||||
.no = 3
|
no = 3
|
||||||
value.unit = uA
|
value.unit = uA
|
||||||
.iodev = ppms
|
io = ppms
|
||||||
|
|
||||||
[i4]
|
[i4]
|
||||||
class = secop_psi.ppms.Channel
|
class = frappy_psi.ppms.Channel
|
||||||
.description = current channel 4
|
description = current channel 4
|
||||||
.no = 4
|
no = 4
|
||||||
value.unit = uA
|
value.unit = uA
|
||||||
.iodev = ppms
|
io = ppms
|
||||||
|
|
||||||
[v1]
|
[v1]
|
||||||
class = secop_psi.ppms.DriverChannel
|
class = frappy_psi.ppms.DriverChannel
|
||||||
.description = voltage channel 1
|
description = voltage channel 1
|
||||||
.no = 1
|
no = 1
|
||||||
value.unit = V
|
value.unit = V
|
||||||
.iodev = ppms
|
io = ppms
|
||||||
|
|
||||||
[v2]
|
[v2]
|
||||||
class = secop_psi.ppms.DriverChannel
|
class = frappy_psi.ppms.DriverChannel
|
||||||
.description = voltage channel 2
|
description = voltage channel 2
|
||||||
.no = 2
|
no = 2
|
||||||
value.unit = V
|
value.unit = V
|
||||||
.iodev = ppms
|
io = ppms
|
||||||
|
|
||||||
[tv]
|
[tv]
|
||||||
class = secop_psi.ppms.UserChannel
|
class = frappy_psi.ppms.UserChannel
|
||||||
.description = VTI temperature
|
description = VTI temperature
|
||||||
enabled = 1
|
enabled = 1
|
||||||
value.unit = K
|
value.unit = K
|
||||||
.iodev = ppms
|
io = ppms
|
||||||
|
|
||||||
[ts]
|
[ts]
|
||||||
class = secop_psi.ppms.UserChannel
|
class = frappy_psi.ppms.UserChannel
|
||||||
.description = sample temperature
|
description = sample temperature
|
||||||
enabled = 1
|
enabled = 1
|
||||||
value.unit = K
|
value.unit = K
|
||||||
.iodev = ppms
|
io = ppms
|
||||||
|
|
||||||
[ppms]
|
[ppms]
|
||||||
class = secop_psi.ppms.Main
|
class = frappy_psi.ppms.Main
|
||||||
.description = the main and poller module
|
description = the main and poller module
|
||||||
.class_id = QD.MULTIVU.PPMS.1
|
class_id = QD.MULTIVU.PPMS.1
|
||||||
.visibility = 3
|
visibility = 3
|
||||||
pollinterval = 2
|
pollinterval = 2
|
||||||
|
@ -7,13 +7,13 @@ bindto = 0.0.0.0
|
|||||||
bindport = 5002
|
bindport = 5002
|
||||||
|
|
||||||
[module secnode]
|
[module secnode]
|
||||||
class = secop.SecNode
|
class = frappy.SecNode
|
||||||
description = a SEC node
|
description = a SEC node
|
||||||
uri = tcp://localhost:5000
|
uri = tcp://localhost:5000
|
||||||
|
|
||||||
[module mf]
|
[module mf]
|
||||||
class = secop.Proxy
|
class = frappy.Proxy
|
||||||
remote_class = secop_psi.ppms.Field
|
remote_class = frappy_psi.ppms.Field
|
||||||
description = magnetic field
|
description = magnetic field
|
||||||
iodev = secnode
|
iodev = secnode
|
||||||
value.min = -0.1
|
value.min = -0.1
|
||||||
|
@ -10,7 +10,7 @@ bindport=10767
|
|||||||
|
|
||||||
|
|
||||||
[module sim]
|
[module sim]
|
||||||
class=secop.simulation.SimDrivable
|
class=frappy.simulation.SimDrivable
|
||||||
.description=simulation stuff
|
.description=simulation stuff
|
||||||
.extra_params=param3,param4,jitter,ramp
|
.extra_params=param3,param4,jitter,ramp
|
||||||
param3.datatype={"type":"bool"}
|
param3.datatype={"type":"bool"}
|
||||||
|
@ -19,14 +19,14 @@ bindto=0.0.0.0
|
|||||||
bindport=10767
|
bindport=10767
|
||||||
|
|
||||||
[module enable]
|
[module enable]
|
||||||
class=secop.simulation.SimWritable
|
class=frappy.simulation.SimWritable
|
||||||
value.datatype={"type":"enum", "members":{'On':1,'Off':0}}
|
value.datatype={"type":"enum", "members":{'On':1,'Off':0}}
|
||||||
target.datatype={"type":"enum", "members":{'On':1,'Off':0}}
|
target.datatype={"type":"enum", "members":{'On':1,'Off':0}}
|
||||||
.description='Enables to Output of the Powersupply'
|
.description='Enables to Output of the Powersupply'
|
||||||
.visibility='advanced'
|
.visibility='advanced'
|
||||||
|
|
||||||
[module polarity]
|
[module polarity]
|
||||||
class=secop.simulation.SimWritable
|
class=frappy.simulation.SimWritable
|
||||||
value.datatype={"type":"enum", "members":{'+1':1,'0':0,'-1':-1}}
|
value.datatype={"type":"enum", "members":{'+1':1,'0':0,'-1':-1}}
|
||||||
target.datatype={"type":"enum", "members":{'+1':1,'0':0,'-1':-1}}
|
target.datatype={"type":"enum", "members":{'+1':1,'0':0,'-1':-1}}
|
||||||
.description=polarity (+/-) switch
|
.description=polarity (+/-) switch
|
||||||
@ -38,7 +38,7 @@ target.datatype={"type":"enum", "members":{'+1':1,'0':0,'-1':-1}}
|
|||||||
|
|
||||||
|
|
||||||
[module symmetry]
|
[module symmetry]
|
||||||
class=secop.simulation.SimWritable
|
class=frappy.simulation.SimWritable
|
||||||
value.datatype={"type":"enum", "members":{'symmetric':1,'short':0, 'asymmetric':-1}}
|
value.datatype={"type":"enum", "members":{'symmetric':1,'short':0, 'asymmetric':-1}}
|
||||||
target.datatype={"type":"enum", "members":{'symmetric':1,'short':0, 'asymmetric':-1}}
|
target.datatype={"type":"enum", "members":{'symmetric':1,'short':0, 'asymmetric':-1}}
|
||||||
.description=par/ser switch selecting (a)symmetric mode
|
.description=par/ser switch selecting (a)symmetric mode
|
||||||
@ -48,31 +48,31 @@ target.datatype={"type":"enum", "members":{'symmetric':1,'short':0, 'asymmetric'
|
|||||||
value.default = 'symmetric'
|
value.default = 'symmetric'
|
||||||
|
|
||||||
[module T1]
|
[module T1]
|
||||||
class=secop.simulation.SimReadable
|
class=frappy.simulation.SimReadable
|
||||||
.description=Temperature1 of the coils system
|
.description=Temperature1 of the coils system
|
||||||
value.unit='degC'
|
value.unit='degC'
|
||||||
value.default = 23.45
|
value.default = 23.45
|
||||||
|
|
||||||
[module T2]
|
[module T2]
|
||||||
class=secop.simulation.SimReadable
|
class=frappy.simulation.SimReadable
|
||||||
.description=Temperature2 of the coils system
|
.description=Temperature2 of the coils system
|
||||||
value.unit='degC'
|
value.unit='degC'
|
||||||
value.default = 23.45
|
value.default = 23.45
|
||||||
|
|
||||||
[module T3]
|
[module T3]
|
||||||
class=secop.simulation.SimReadable
|
class=frappy.simulation.SimReadable
|
||||||
.description=Temperature3 of the coils system
|
.description=Temperature3 of the coils system
|
||||||
value.unit='degC'
|
value.unit='degC'
|
||||||
value.default = 23.45
|
value.default = 23.45
|
||||||
|
|
||||||
[module T4]
|
[module T4]
|
||||||
class=secop.simulation.SimReadable
|
class=frappy.simulation.SimReadable
|
||||||
.description=Temperature4 of the coils system
|
.description=Temperature4 of the coils system
|
||||||
value.unit='degC'
|
value.unit='degC'
|
||||||
value.default = 23.45
|
value.default = 23.45
|
||||||
|
|
||||||
[module currentsource]
|
[module currentsource]
|
||||||
class=secop.simulation.SimDrivable
|
class=frappy.simulation.SimDrivable
|
||||||
.description=Device for the magnet power supply (current mode)
|
.description=Device for the magnet power supply (current mode)
|
||||||
abslimits=(0,200)
|
abslimits=(0,200)
|
||||||
speed=1
|
speed=1
|
||||||
@ -98,7 +98,7 @@ window.datatype = {"type":"double", "min":0, "max":120, "unit":"s"}
|
|||||||
window.default = 10
|
window.default = 10
|
||||||
|
|
||||||
[module mf]
|
[module mf]
|
||||||
class=secop_mlz.amagnet.GarfieldMagnet
|
class=frappy_mlz.amagnet.GarfieldMagnet
|
||||||
.description=magnetic field module, handling polarity switching and stuff
|
.description=magnetic field module, handling polarity switching and stuff
|
||||||
subdev_currentsource=currentsource
|
subdev_currentsource=currentsource
|
||||||
subdev_enable=enable
|
subdev_enable=enable
|
||||||
|
@ -11,7 +11,7 @@ bindto=0.0.0.0
|
|||||||
bindport=10767
|
bindport=10767
|
||||||
|
|
||||||
[module T_cci3he1]
|
[module T_cci3he1]
|
||||||
class=secop.simulation.SimDrivable
|
class=frappy.simulation.SimDrivable
|
||||||
.description=Main temperature control node of cci3he1.
|
.description=Main temperature control node of cci3he1.
|
||||||
.
|
.
|
||||||
Controls the regulation loop of the ls370.
|
Controls the regulation loop of the ls370.
|
||||||
@ -26,7 +26,7 @@ ramp.default=60
|
|||||||
.meaning=["temperature_regulation",40]
|
.meaning=["temperature_regulation",40]
|
||||||
|
|
||||||
[module T_cci3he1_A]
|
[module T_cci3he1_A]
|
||||||
class=secop.simulation.SimReadable
|
class=frappy.simulation.SimReadable
|
||||||
.description=3He pot temperature sensor. Also used for the regulation.
|
.description=3He pot temperature sensor. Also used for the regulation.
|
||||||
.visibility=expert
|
.visibility=expert
|
||||||
value.default=300
|
value.default=300
|
||||||
@ -34,7 +34,7 @@ value.datatype={"type":"double","unit":"K"}
|
|||||||
.meaning=["temperature",38]
|
.meaning=["temperature",38]
|
||||||
|
|
||||||
[module T_cci3he1_B]
|
[module T_cci3he1_B]
|
||||||
class=secop.simulation.SimReadable
|
class=frappy.simulation.SimReadable
|
||||||
.description=(optional) sample temperature sensor close to sample.
|
.description=(optional) sample temperature sensor close to sample.
|
||||||
.visibility=user
|
.visibility=user
|
||||||
value.default=300
|
value.default=300
|
||||||
@ -42,49 +42,49 @@ value.datatype={"type":"double","unit":"K"}
|
|||||||
.meaning=["temperature",39]
|
.meaning=["temperature",39]
|
||||||
|
|
||||||
[module cci3he1_p1]
|
[module cci3he1_p1]
|
||||||
class=secop.simulation.SimReadable
|
class=frappy.simulation.SimReadable
|
||||||
.description=Pressure at turbo pump inlet.
|
.description=Pressure at turbo pump inlet.
|
||||||
.visibility=expert
|
.visibility=expert
|
||||||
value.default=2e-3
|
value.default=2e-3
|
||||||
value.datatype={"type":"double","unit":"mbar"}
|
value.datatype={"type":"double","unit":"mbar"}
|
||||||
|
|
||||||
[module cci3he1_p2]
|
[module cci3he1_p2]
|
||||||
class=secop.simulation.SimReadable
|
class=frappy.simulation.SimReadable
|
||||||
.description=Pressure at turbo pump outlet.
|
.description=Pressure at turbo pump outlet.
|
||||||
.visibility=expert
|
.visibility=expert
|
||||||
value.default=9.87
|
value.default=9.87
|
||||||
value.datatype={"type":"double","unit":"mbar"}
|
value.datatype={"type":"double","unit":"mbar"}
|
||||||
|
|
||||||
[module cci3he1_p3]
|
[module cci3he1_p3]
|
||||||
class=secop.simulation.SimReadable
|
class=frappy.simulation.SimReadable
|
||||||
.description=Pressure at compressor inlet.
|
.description=Pressure at compressor inlet.
|
||||||
.visibility=expert
|
.visibility=expert
|
||||||
value.default=19.99
|
value.default=19.99
|
||||||
value.datatype={"type":"double","unit":"mbar"}
|
value.datatype={"type":"double","unit":"mbar"}
|
||||||
|
|
||||||
[module cci3he1_p4]
|
[module cci3he1_p4]
|
||||||
class=secop.simulation.SimReadable
|
class=frappy.simulation.SimReadable
|
||||||
.description=Pressure at compressor outlet.
|
.description=Pressure at compressor outlet.
|
||||||
.visibility=expert
|
.visibility=expert
|
||||||
value.default=999
|
value.default=999
|
||||||
value.datatype={"type":"double","unit":"mbar"}
|
value.datatype={"type":"double","unit":"mbar"}
|
||||||
|
|
||||||
[module cci3he1_p5]
|
[module cci3he1_p5]
|
||||||
class=secop.simulation.SimReadable
|
class=frappy.simulation.SimReadable
|
||||||
.description=Pressure in dump tank.
|
.description=Pressure in dump tank.
|
||||||
.visibility=expert
|
.visibility=expert
|
||||||
value.default=567
|
value.default=567
|
||||||
value.datatype={"type":"double","unit":"mbar"}
|
value.datatype={"type":"double","unit":"mbar"}
|
||||||
|
|
||||||
[module cci3he1_p6]
|
[module cci3he1_p6]
|
||||||
class=secop.simulation.SimReadable
|
class=frappy.simulation.SimReadable
|
||||||
.description=Pressure in the vacuum dewar (ivc).
|
.description=Pressure in the vacuum dewar (ivc).
|
||||||
.visibility=expert
|
.visibility=expert
|
||||||
value.default=1e-3
|
value.default=1e-3
|
||||||
value.datatype={"type":"double","unit":"mbar"}
|
value.datatype={"type":"double","unit":"mbar"}
|
||||||
|
|
||||||
[module cci3he1_flow]
|
[module cci3he1_flow]
|
||||||
class=secop.simulation.SimReadable
|
class=frappy.simulation.SimReadable
|
||||||
.description=Gas Flow (condensing line).
|
.description=Gas Flow (condensing line).
|
||||||
.visibility=expert
|
.visibility=expert
|
||||||
value.default=12.34
|
value.default=12.34
|
||||||
|
@ -11,7 +11,7 @@ bindto=0.0.0.0
|
|||||||
bindport=10767
|
bindport=10767
|
||||||
|
|
||||||
[module T_ccidu1]
|
[module T_ccidu1]
|
||||||
class=secop.simulation.SimDrivable
|
class=frappy.simulation.SimDrivable
|
||||||
.description=Main temperature control node of ccidu1.
|
.description=Main temperature control node of ccidu1.
|
||||||
.
|
.
|
||||||
Controls the regulation loop of the ls372.
|
Controls the regulation loop of the ls372.
|
||||||
@ -26,7 +26,7 @@ ramp.default=60
|
|||||||
.meaning=["temperature_regulation",40]
|
.meaning=["temperature_regulation",40]
|
||||||
|
|
||||||
[module T_ccidu1_A]
|
[module T_ccidu1_A]
|
||||||
class=secop.simulation.SimReadable
|
class=frappy.simulation.SimReadable
|
||||||
.description=mixing chamber temperature sensor. Also used for the regulation.
|
.description=mixing chamber temperature sensor. Also used for the regulation.
|
||||||
.visibility=expert
|
.visibility=expert
|
||||||
value.default=300
|
value.default=300
|
||||||
@ -34,7 +34,7 @@ value.datatype={"type":"double", "unit":"K"}
|
|||||||
.meaning=["temperature",38]
|
.meaning=["temperature",38]
|
||||||
|
|
||||||
[module T_ccidu1_B]
|
[module T_ccidu1_B]
|
||||||
class=secop.simulation.SimReadable
|
class=frappy.simulation.SimReadable
|
||||||
.description=(optional) sample temperature sensor close to sample.
|
.description=(optional) sample temperature sensor close to sample.
|
||||||
.visibility=user
|
.visibility=user
|
||||||
value.default=300
|
value.default=300
|
||||||
@ -42,49 +42,49 @@ value.datatype={"type":"double", "unit":"K"}
|
|||||||
.meaning=["temperature",39]
|
.meaning=["temperature",39]
|
||||||
|
|
||||||
[module ccidu1_pstill]
|
[module ccidu1_pstill]
|
||||||
class=secop.simulation.SimReadable
|
class=frappy.simulation.SimReadable
|
||||||
.description=Pressure at the still/turbo pump inlet.
|
.description=Pressure at the still/turbo pump inlet.
|
||||||
.visibility=expert
|
.visibility=expert
|
||||||
value.default=999
|
value.default=999
|
||||||
value.datatype={"type":"double", "unit":"mbar"}
|
value.datatype={"type":"double", "unit":"mbar"}
|
||||||
|
|
||||||
[module ccidu1_pinlet]
|
[module ccidu1_pinlet]
|
||||||
class=secop.simulation.SimReadable
|
class=frappy.simulation.SimReadable
|
||||||
.description=Pressure at forepump inlet/turbo pump outlet.
|
.description=Pressure at forepump inlet/turbo pump outlet.
|
||||||
.visibility=expert
|
.visibility=expert
|
||||||
value.default=999
|
value.default=999
|
||||||
value.datatype={"type":"double", "unit":"mbar"}
|
value.datatype={"type":"double", "unit":"mbar"}
|
||||||
|
|
||||||
[module ccidu1_poutlet]
|
[module ccidu1_poutlet]
|
||||||
class=secop.simulation.SimReadable
|
class=frappy.simulation.SimReadable
|
||||||
.description=Pressure at forepump outlet/compressor inlet.
|
.description=Pressure at forepump outlet/compressor inlet.
|
||||||
.visibility=expert
|
.visibility=expert
|
||||||
value.default=999
|
value.default=999
|
||||||
value.datatype={"type":"double", "unit":"mbar"}
|
value.datatype={"type":"double", "unit":"mbar"}
|
||||||
|
|
||||||
[module ccidu1_pkond]
|
[module ccidu1_pkond]
|
||||||
class=secop.simulation.SimReadable
|
class=frappy.simulation.SimReadable
|
||||||
.description=Pressure at condensing line/compressor outlet.
|
.description=Pressure at condensing line/compressor outlet.
|
||||||
.visibility=expert
|
.visibility=expert
|
||||||
value.default=999
|
value.default=999
|
||||||
value.datatype={"type":"double", "unit":"mbar"}
|
value.datatype={"type":"double", "unit":"mbar"}
|
||||||
|
|
||||||
[module ccidu1_ptank]
|
[module ccidu1_ptank]
|
||||||
class=secop.simulation.SimReadable
|
class=frappy.simulation.SimReadable
|
||||||
.description=Pressure in dump tank.
|
.description=Pressure in dump tank.
|
||||||
.visibility=expert
|
.visibility=expert
|
||||||
value.default=999
|
value.default=999
|
||||||
value.datatype={"type":"double", "unit":"mbar"}
|
value.datatype={"type":"double", "unit":"mbar"}
|
||||||
|
|
||||||
[module ccidu1_pvac]
|
[module ccidu1_pvac]
|
||||||
class=secop.simulation.SimReadable
|
class=frappy.simulation.SimReadable
|
||||||
.description=Pressure in the vacuum dewar (ivc).
|
.description=Pressure in the vacuum dewar (ivc).
|
||||||
.visibility=expert
|
.visibility=expert
|
||||||
value.default=999
|
value.default=999
|
||||||
value.datatype={"type":"double", "unit":"mbar"}
|
value.datatype={"type":"double", "unit":"mbar"}
|
||||||
|
|
||||||
[module ccidu1_flow]
|
[module ccidu1_flow]
|
||||||
class=secop.simulation.SimReadable
|
class=frappy.simulation.SimReadable
|
||||||
.description=Gas Flow (condensing line).
|
.description=Gas Flow (condensing line).
|
||||||
.visibility=expert
|
.visibility=expert
|
||||||
value.default=999
|
value.default=999
|
||||||
@ -92,14 +92,14 @@ value.datatype={"type":"double", "unit":"mbar"}
|
|||||||
|
|
||||||
# note: all valves and switches are missing: use VNC to control them
|
# note: all valves and switches are missing: use VNC to control them
|
||||||
[module ccidu1_V6]
|
[module ccidu1_V6]
|
||||||
class=secop.simulation.SimDrivable
|
class=frappy.simulation.SimDrivable
|
||||||
.description=Needle valve
|
.description=Needle valve
|
||||||
.visibility=expert
|
.visibility=expert
|
||||||
value.default=99
|
value.default=99
|
||||||
value.datatype={"type":"double", "min":0, "max":100, "unit":"%%"}
|
value.datatype={"type":"double", "min":0, "max":100, "unit":"%%"}
|
||||||
|
|
||||||
[module ccidu1_V3]
|
[module ccidu1_V3]
|
||||||
class=secop.simulation.SimWritable
|
class=frappy.simulation.SimWritable
|
||||||
.description=Dump Valve
|
.description=Dump Valve
|
||||||
.visibility=expert
|
.visibility=expert
|
||||||
value.default="OFF"
|
value.default="OFF"
|
||||||
|
@ -12,7 +12,7 @@ bindto=0.0.0.0
|
|||||||
bindport=10767
|
bindport=10767
|
||||||
|
|
||||||
[module T_ccr12]
|
[module T_ccr12]
|
||||||
class=secop.simulation.SimDrivable
|
class=frappy.simulation.SimDrivable
|
||||||
.description=Main temperature control node of CCR12.
|
.description=Main temperature control node of CCR12.
|
||||||
.
|
.
|
||||||
Switches between regulation on stick and regulation on tube depending on temperature requested.
|
Switches between regulation on stick and regulation on tube depending on temperature requested.
|
||||||
@ -30,7 +30,7 @@ target.default=300
|
|||||||
.meaning=["temperature_regulation", 20]
|
.meaning=["temperature_regulation", 20]
|
||||||
|
|
||||||
[module T_ccr12_stick]
|
[module T_ccr12_stick]
|
||||||
class=secop.simulation.SimDrivable
|
class=frappy.simulation.SimDrivable
|
||||||
.description=Temperature regulation for the sample stick in ccr12.
|
.description=Temperature regulation for the sample stick in ccr12.
|
||||||
.extra_params=ramp
|
.extra_params=ramp
|
||||||
ramp.datatype={"type":"double", "min":0,"max":60, "unit":"K/min"}
|
ramp.datatype={"type":"double", "min":0,"max":60, "unit":"K/min"}
|
||||||
@ -42,7 +42,7 @@ target.default=300
|
|||||||
.meaning=["temperature_regulation", 15]
|
.meaning=["temperature_regulation", 15]
|
||||||
|
|
||||||
[module T_ccr12_tube]
|
[module T_ccr12_tube]
|
||||||
class=secop.simulation.SimDrivable
|
class=frappy.simulation.SimDrivable
|
||||||
.description=Temperature regulation for the tube of ccr12.
|
.description=Temperature regulation for the tube of ccr12.
|
||||||
.extra_params=ramp
|
.extra_params=ramp
|
||||||
ramp.datatype={"type":"double", "min":0,"max":60, "unit":"K/min"}
|
ramp.datatype={"type":"double", "min":0,"max":60, "unit":"K/min"}
|
||||||
@ -54,28 +54,28 @@ target.default=300
|
|||||||
.meaning=["temperature_regulation", 10]
|
.meaning=["temperature_regulation", 10]
|
||||||
|
|
||||||
[module T_ccr12_A]
|
[module T_ccr12_A]
|
||||||
class=secop.simulation.SimReadable
|
class=frappy.simulation.SimReadable
|
||||||
.description=(optional) Sample temperature sensor.
|
.description=(optional) Sample temperature sensor.
|
||||||
value.datatype={"type":"double", "unit":"K"}
|
value.datatype={"type":"double", "unit":"K"}
|
||||||
value.default=300
|
value.default=300
|
||||||
.meaning=["temperature", 9]
|
.meaning=["temperature", 9]
|
||||||
|
|
||||||
[module T_ccr12_B]
|
[module T_ccr12_B]
|
||||||
class=secop.simulation.SimReadable
|
class=frappy.simulation.SimReadable
|
||||||
.description=(regulation) temperature sensor on stick.
|
.description=(regulation) temperature sensor on stick.
|
||||||
value.datatype={"type":"double", "unit":"K"}
|
value.datatype={"type":"double", "unit":"K"}
|
||||||
value.default=300
|
value.default=300
|
||||||
.meaning=["temperature", 10]
|
.meaning=["temperature", 10]
|
||||||
|
|
||||||
[module T_ccr12_C]
|
[module T_ccr12_C]
|
||||||
class=secop.simulation.SimReadable
|
class=frappy.simulation.SimReadable
|
||||||
.description=Temperature at the coldhead.
|
.description=Temperature at the coldhead.
|
||||||
value.datatype={"type":"double", "unit":"K"}
|
value.datatype={"type":"double", "unit":"K"}
|
||||||
value.default=70
|
value.default=70
|
||||||
.meaning=["temperature", 1]
|
.meaning=["temperature", 1]
|
||||||
|
|
||||||
[module T_ccr12_D]
|
[module T_ccr12_D]
|
||||||
class=secop.simulation.SimReadable
|
class=frappy.simulation.SimReadable
|
||||||
.description=(regulation) temperature at coupling to stick.
|
.description=(regulation) temperature at coupling to stick.
|
||||||
value.datatype={"type":"double", "unit":"K"}
|
value.datatype={"type":"double", "unit":"K"}
|
||||||
value.default=80
|
value.default=80
|
||||||
@ -84,7 +84,7 @@ value.default=80
|
|||||||
|
|
||||||
|
|
||||||
[module ccr12_pressure_regulate]
|
[module ccr12_pressure_regulate]
|
||||||
class=secop.simulation.SimWritable
|
class=frappy.simulation.SimWritable
|
||||||
.description=Selects on which Sensor the pressure regulation works, or switches it off.
|
.description=Selects on which Sensor the pressure regulation works, or switches it off.
|
||||||
.visibility=expert
|
.visibility=expert
|
||||||
value.datatype={"type":"enum", "members":{'off':0,'p1':1,'p2':2}}
|
value.datatype={"type":"enum", "members":{'off':0,'p1':1,'p2':2}}
|
||||||
@ -93,7 +93,7 @@ target.datatype={"type":"enum", "members":{'off':0,'p1':1,'p2':2}}
|
|||||||
target.default='off'
|
target.default='off'
|
||||||
|
|
||||||
[module ccr12_compressor]
|
[module ccr12_compressor]
|
||||||
class=secop.simulation.SimDrivable
|
class=frappy.simulation.SimDrivable
|
||||||
.description=Switches the compressor for the cooling stage on or off.
|
.description=Switches the compressor for the cooling stage on or off.
|
||||||
.
|
.
|
||||||
Note: This should always be on, except for fast heatup for sample change.
|
Note: This should always be on, except for fast heatup for sample change.
|
||||||
@ -103,7 +103,7 @@ target.datatype={"type":"enum", "members":{'off':0,'on':1}}
|
|||||||
target.default='on'
|
target.default='on'
|
||||||
|
|
||||||
[module ccr12_gas_switch]
|
[module ccr12_gas_switch]
|
||||||
class=secop.simulation.SimWritable
|
class=frappy.simulation.SimWritable
|
||||||
.description=Switches the gas inlet on or off.
|
.description=Switches the gas inlet on or off.
|
||||||
.
|
.
|
||||||
note: in reality this switches itself off after 15min.
|
note: in reality this switches itself off after 15min.
|
||||||
@ -115,7 +115,7 @@ target.datatype={"type":"enum", "members":{'off':0,'on':1}}
|
|||||||
target.default='off'
|
target.default='off'
|
||||||
|
|
||||||
[module ccr12_vacuum_switch]
|
[module ccr12_vacuum_switch]
|
||||||
class=secop.simulation.SimWritable
|
class=frappy.simulation.SimWritable
|
||||||
.description=Switches the vacuum pumping valve on or off.
|
.description=Switches the vacuum pumping valve on or off.
|
||||||
.
|
.
|
||||||
note: in reality this is interlocked with ccr12_gas_switch, only one can be on!
|
note: in reality this is interlocked with ccr12_gas_switch, only one can be on!
|
||||||
@ -126,19 +126,19 @@ target.datatype={"type":"enum", "members":{'off':0,'on':1}}
|
|||||||
target.default='off'
|
target.default='off'
|
||||||
|
|
||||||
[module ccr12_p1]
|
[module ccr12_p1]
|
||||||
class=secop.simulation.SimReadable
|
class=frappy.simulation.SimReadable
|
||||||
.description=Default pressure Sensor, linear scale 0..1000mbar
|
.description=Default pressure Sensor, linear scale 0..1000mbar
|
||||||
value.datatype={"type":"double", "unit":"mbar"}
|
value.datatype={"type":"double", "unit":"mbar"}
|
||||||
value.default=999
|
value.default=999
|
||||||
|
|
||||||
[module ccr12_p2]
|
[module ccr12_p2]
|
||||||
class=secop.simulation.SimReadable
|
class=frappy.simulation.SimReadable
|
||||||
.description=Auxillary pressure Sensor.
|
.description=Auxillary pressure Sensor.
|
||||||
value.datatype={"type":"double", "unit":"mbar"}
|
value.datatype={"type":"double", "unit":"mbar"}
|
||||||
value.default=1e-6
|
value.default=1e-6
|
||||||
|
|
||||||
[module ccr12_curve_p2]
|
[module ccr12_curve_p2]
|
||||||
class=secop.simulation.SimWritable
|
class=frappy.simulation.SimWritable
|
||||||
.description=Curve for Aux pressure Sensor p2
|
.description=Curve for Aux pressure Sensor p2
|
||||||
.visibility=expert
|
.visibility=expert
|
||||||
value.default='TTR100'
|
value.default='TTR100'
|
||||||
@ -146,7 +146,7 @@ value.datatype={"type":"enum", "members":{'0..10V':0,'default':1,'0..9V':2,'DI20
|
|||||||
target.datatype={"type":"enum", "members":{'0..10V':0,'default':1,'0..9V':2,'DI200':3,'DI2000':4,'TTR100':7,'PTR90':8,'PTR225/PTR237':9,'ITR90':10,'ITR100 curve D':11, 'ITR100 curve 2':12, 'ITR100 curve 3':13,'ITR100 curve 4':14,'ITR100 curve 5':15, 'ITR100 curve 6':16, 'ITR100 curve 7':17, 'ITR100 curve 8':18, 'ITR100 curve 9':19, 'ITR100 curve A':20,'CMR361':21, 'CMR362':22, 'CMR363':23, 'CMR364':24, 'CMR365':25}}
|
target.datatype={"type":"enum", "members":{'0..10V':0,'default':1,'0..9V':2,'DI200':3,'DI2000':4,'TTR100':7,'PTR90':8,'PTR225/PTR237':9,'ITR90':10,'ITR100 curve D':11, 'ITR100 curve 2':12, 'ITR100 curve 3':13,'ITR100 curve 4':14,'ITR100 curve 5':15, 'ITR100 curve 6':16, 'ITR100 curve 7':17, 'ITR100 curve 8':18, 'ITR100 curve 9':19, 'ITR100 curve A':20,'CMR361':21, 'CMR362':22, 'CMR363':23, 'CMR364':24, 'CMR365':25}}
|
||||||
|
|
||||||
[module ccr12_p1_limits]
|
[module ccr12_p1_limits]
|
||||||
class=secop.simulation.SimWritable
|
class=frappy.simulation.SimWritable
|
||||||
.description=Limits for pressure regulation in P1.
|
.description=Limits for pressure regulation in P1.
|
||||||
.visibility=expert
|
.visibility=expert
|
||||||
value.datatype={"type":"limit","members":{"type":"double", "min":0,"max":1000, "unit":"mbar"}}
|
value.datatype={"type":"limit","members":{"type":"double", "min":0,"max":1000, "unit":"mbar"}}
|
||||||
@ -155,7 +155,7 @@ target.datatype={"type":"limit","members":{"type":"double", "min":0,"max":1000,
|
|||||||
target.default=[0,10]
|
target.default=[0,10]
|
||||||
|
|
||||||
[module ccr12_p2_limits]
|
[module ccr12_p2_limits]
|
||||||
class=secop.simulation.SimWritable
|
class=frappy.simulation.SimWritable
|
||||||
.description=Limits for pressure regulation in P2.
|
.description=Limits for pressure regulation in P2.
|
||||||
.visibility=expert
|
.visibility=expert
|
||||||
value.datatype={"type":"limit","members":{"type":"double", "min":0,"max":1000, "unit":"mbar"}}
|
value.datatype={"type":"limit","members":{"type":"double", "min":0,"max":1000, "unit":"mbar"}}
|
||||||
|
@ -12,7 +12,7 @@ bindto=0.0.0.0
|
|||||||
bindport=10767
|
bindport=10767
|
||||||
|
|
||||||
[module T_ccr12]
|
[module T_ccr12]
|
||||||
class=secop.simulation.SimDrivable
|
class=frappy.simulation.SimDrivable
|
||||||
.description=Main temperature control node of CCR12.
|
.description=Main temperature control node of CCR12.
|
||||||
.
|
.
|
||||||
Switches between regulation on stick and regulation on tube depending on temperature requested.
|
Switches between regulation on stick and regulation on tube depending on temperature requested.
|
||||||
@ -41,7 +41,7 @@ userlimits.readonly=False
|
|||||||
.meaning=["temperature_regulation", 20]
|
.meaning=["temperature_regulation", 20]
|
||||||
|
|
||||||
[module T_ccr12_A]
|
[module T_ccr12_A]
|
||||||
class=secop.simulation.SimReadable
|
class=frappy.simulation.SimReadable
|
||||||
.description=(optional) Sample temperature sensor.
|
.description=(optional) Sample temperature sensor.
|
||||||
.visibility=expert
|
.visibility=expert
|
||||||
value.datatype={"type":"double", "min":0, "unit":"K"}
|
value.datatype={"type":"double", "min":0, "unit":"K"}
|
||||||
@ -49,7 +49,7 @@ value.default=300
|
|||||||
.meaning=["temperature", 9]
|
.meaning=["temperature", 9]
|
||||||
|
|
||||||
[module T_ccr12_B]
|
[module T_ccr12_B]
|
||||||
class=secop.simulation.SimReadable
|
class=frappy.simulation.SimReadable
|
||||||
.description=(regulation) temperature sensor on stick.
|
.description=(regulation) temperature sensor on stick.
|
||||||
.visibility=expert
|
.visibility=expert
|
||||||
value.datatype={"type":"double", "min":0, "unit":"K"}
|
value.datatype={"type":"double", "min":0, "unit":"K"}
|
||||||
@ -57,7 +57,7 @@ value.default=300
|
|||||||
.meaning=["temperature", 10]
|
.meaning=["temperature", 10]
|
||||||
|
|
||||||
[module T_ccr12_C]
|
[module T_ccr12_C]
|
||||||
class=secop.simulation.SimReadable
|
class=frappy.simulation.SimReadable
|
||||||
.description=Temperature at the coldhead.
|
.description=Temperature at the coldhead.
|
||||||
.visibility=expert
|
.visibility=expert
|
||||||
value.datatype={"type":"double", "min":0, "unit":"K"}
|
value.datatype={"type":"double", "min":0, "unit":"K"}
|
||||||
@ -65,7 +65,7 @@ value.default=70
|
|||||||
.meaning=["temperature", 1]
|
.meaning=["temperature", 1]
|
||||||
|
|
||||||
[module T_ccr12_D]
|
[module T_ccr12_D]
|
||||||
class=secop.simulation.SimReadable
|
class=frappy.simulation.SimReadable
|
||||||
.description=(regulation) temperature at coupling to stick.
|
.description=(regulation) temperature at coupling to stick.
|
||||||
.visibility=expert
|
.visibility=expert
|
||||||
value.datatype={"type":"double", "min":0, "unit":"K"}
|
value.datatype={"type":"double", "min":0, "unit":"K"}
|
||||||
@ -75,7 +75,7 @@ value.default=80
|
|||||||
|
|
||||||
|
|
||||||
[module ccr12_pressure_regulation]
|
[module ccr12_pressure_regulation]
|
||||||
class=secop.simulation.SimReadable
|
class=frappy.simulation.SimReadable
|
||||||
.description=Simple two-point presssure regulation. the mode parameter selects the readout on which to regulate, or 'none' for no regulation.
|
.description=Simple two-point presssure regulation. the mode parameter selects the readout on which to regulate, or 'none' for no regulation.
|
||||||
.visibility=user
|
.visibility=user
|
||||||
.extra_params=switchpoints, mode
|
.extra_params=switchpoints, mode
|
||||||
@ -91,7 +91,7 @@ value.datatype={"type":"double", "min":0, "max":1000, "unit":"mbar"}
|
|||||||
value.default = 1e-5
|
value.default = 1e-5
|
||||||
|
|
||||||
[module ccr12_compressor]
|
[module ccr12_compressor]
|
||||||
class=secop.simulation.SimDrivable
|
class=frappy.simulation.SimDrivable
|
||||||
.description=Switches the compressor for the cooling stage on or off.
|
.description=Switches the compressor for the cooling stage on or off.
|
||||||
.
|
.
|
||||||
Note: This should always be on, except for fast heatup for sample change.
|
Note: This should always be on, except for fast heatup for sample change.
|
||||||
@ -101,7 +101,7 @@ value.default='off'
|
|||||||
target.datatype={"type":"enum", "members":{'off':0,'on':1}}
|
target.datatype={"type":"enum", "members":{'off':0,'on':1}}
|
||||||
|
|
||||||
[module ccr12_gas_switch]
|
[module ccr12_gas_switch]
|
||||||
class=secop.simulation.SimWritable
|
class=frappy.simulation.SimWritable
|
||||||
.description=Switches the gas inlet on or off.
|
.description=Switches the gas inlet on or off.
|
||||||
.
|
.
|
||||||
note: in reality this switches itself off after 15min.
|
note: in reality this switches itself off after 15min.
|
||||||
@ -113,7 +113,7 @@ target.datatype={"type":"enum", "members":{'off':0,'on':1}}
|
|||||||
target.default='off'
|
target.default='off'
|
||||||
|
|
||||||
[module ccr12_vacuum_switch]
|
[module ccr12_vacuum_switch]
|
||||||
class=secop.simulation.SimWritable
|
class=frappy.simulation.SimWritable
|
||||||
.description=Switches the vacuum pumping valve on or off.
|
.description=Switches the vacuum pumping valve on or off.
|
||||||
.
|
.
|
||||||
note: in reality this is interlocked with ccr12_gas_switch, only one can be on!
|
note: in reality this is interlocked with ccr12_gas_switch, only one can be on!
|
||||||
@ -124,7 +124,7 @@ target.datatype={"type":"enum", "members":{'off':0,'on':1}}
|
|||||||
target.default='off'
|
target.default='off'
|
||||||
|
|
||||||
[module ccr12_p1]
|
[module ccr12_p1]
|
||||||
class=secop.simulation.SimReadable
|
class=frappy.simulation.SimReadable
|
||||||
.description=Default pressure Sensor, linear scale 0..1000 mbar
|
.description=Default pressure Sensor, linear scale 0..1000 mbar
|
||||||
.
|
.
|
||||||
Good candidate for a 'Sensor' Interface class!
|
Good candidate for a 'Sensor' Interface class!
|
||||||
@ -140,7 +140,7 @@ userlimits.description=current user set limits for the pressure regulation.
|
|||||||
userlimits.readonly=False
|
userlimits.readonly=False
|
||||||
|
|
||||||
[module ccr12_p2]
|
[module ccr12_p2]
|
||||||
class=secop.simulation.SimReadable
|
class=frappy.simulation.SimReadable
|
||||||
.description=Auxillary pressure Sensor.
|
.description=Auxillary pressure Sensor.
|
||||||
value.default=1e-6
|
value.default=1e-6
|
||||||
value.unit=mbar
|
value.unit=mbar
|
||||||
|
@ -10,7 +10,7 @@ bindto=0.0.0.0
|
|||||||
bindport=10767
|
bindport=10767
|
||||||
|
|
||||||
[module T_htf02]
|
[module T_htf02]
|
||||||
class=secop.simulation.SimDrivable
|
class=frappy.simulation.SimDrivable
|
||||||
.description=Main temperature control node of htf02.
|
.description=Main temperature control node of htf02.
|
||||||
.
|
.
|
||||||
Controls the regulation loop of the Eurotherm.
|
Controls the regulation loop of the Eurotherm.
|
||||||
@ -26,7 +26,7 @@ ramp.readonly=False
|
|||||||
.meaning=["temperature", 10]
|
.meaning=["temperature", 10]
|
||||||
|
|
||||||
[module htf02_p]
|
[module htf02_p]
|
||||||
class=secop.simulation.SimReadable
|
class=frappy.simulation.SimReadable
|
||||||
.description=Pressure Sensor at sample space (ivc).
|
.description=Pressure Sensor at sample space (ivc).
|
||||||
value.datatype={"type":"double", "min":0, "unit":"mbar"}
|
value.datatype={"type":"double", "min":0, "unit":"mbar"}
|
||||||
value.default=989
|
value.default=989
|
||||||
|
@ -10,7 +10,7 @@ bindto=0.0.0.0
|
|||||||
bindport=10767
|
bindport=10767
|
||||||
|
|
||||||
[module T_stressihtf2]
|
[module T_stressihtf2]
|
||||||
class=secop.simulation.SimDrivable
|
class=frappy.simulation.SimDrivable
|
||||||
.description=Main temperature control node of Stressihtf2.
|
.description=Main temperature control node of Stressihtf2.
|
||||||
value.datatype={"type":"double", "min":0, "unit":"degC"}
|
value.datatype={"type":"double", "min":0, "unit":"degC"}
|
||||||
value.default=20
|
value.default=20
|
||||||
@ -30,7 +30,7 @@ userlimits.readonly=False
|
|||||||
.meaning=['temperature_regulation', 10]
|
.meaning=['temperature_regulation', 10]
|
||||||
|
|
||||||
[module T_stressihtf2_sample]
|
[module T_stressihtf2_sample]
|
||||||
class=secop.simulation.SimReadable
|
class=frappy.simulation.SimReadable
|
||||||
.description=(optional) Sample temperature sensor.
|
.description=(optional) Sample temperature sensor.
|
||||||
.visibility=expert
|
.visibility=expert
|
||||||
value.default=300
|
value.default=300
|
||||||
@ -38,7 +38,7 @@ value.datatype={"type":"double", "min":0, "unit":"degC"}
|
|||||||
.meaning=["temperature", 9]
|
.meaning=["temperature", 9]
|
||||||
|
|
||||||
[module stressihtf2_n2]
|
[module stressihtf2_n2]
|
||||||
class=secop.simulation.SimWritable
|
class=frappy.simulation.SimWritable
|
||||||
.description=Switches the N2 gas inlet on or off.
|
.description=Switches the N2 gas inlet on or off.
|
||||||
.visibility=expert
|
.visibility=expert
|
||||||
value.default='off'
|
value.default='off'
|
||||||
@ -47,7 +47,7 @@ target.datatype={"type":"enum", "members":{'off':0,'on':1}}
|
|||||||
target.default='off'
|
target.default='off'
|
||||||
|
|
||||||
[module stressihtf2_he]
|
[module stressihtf2_he]
|
||||||
class=secop.simulation.SimWritable
|
class=frappy.simulation.SimWritable
|
||||||
.description=Switches the He gas inlet on or off.
|
.description=Switches the He gas inlet on or off.
|
||||||
.visibility=expert
|
.visibility=expert
|
||||||
value.default='off'
|
value.default='off'
|
||||||
@ -56,7 +56,7 @@ target.datatype={"type":"enum", "members":{'off':0,'on':1}}
|
|||||||
target.default='off'
|
target.default='off'
|
||||||
|
|
||||||
[module stressihtf2_lamps]
|
[module stressihtf2_lamps]
|
||||||
class=secop.simulation.SimWritable
|
class=frappy.simulation.SimWritable
|
||||||
.description=Switches the heating lamps on or off.
|
.description=Switches the heating lamps on or off.
|
||||||
.visibility=expert
|
.visibility=expert
|
||||||
value.default='on'
|
value.default='on'
|
||||||
@ -65,7 +65,7 @@ target.datatype={"type":"enum", "members":{'off':0,'on':1}}
|
|||||||
target.default='on'
|
target.default='on'
|
||||||
|
|
||||||
[module stressihtf2_water_ok]
|
[module stressihtf2_water_ok]
|
||||||
class=secop.simulation.SimReadable
|
class=frappy.simulation.SimReadable
|
||||||
.description=Readout of the cooling water state.
|
.description=Readout of the cooling water state.
|
||||||
.visibility=expert
|
.visibility=expert
|
||||||
value.default='ok'
|
value.default='ok'
|
||||||
|
@ -10,7 +10,7 @@ bindto=0.0.0.0
|
|||||||
bindport=10767
|
bindport=10767
|
||||||
|
|
||||||
[module T]
|
[module T]
|
||||||
class=secop.simulation.SimDrivable
|
class=frappy.simulation.SimDrivable
|
||||||
.description=Main temperature control node of Stressihtf2.
|
.description=Main temperature control node of Stressihtf2.
|
||||||
value.datatype={"type":"double", "min":0, "unit":"degC"}
|
value.datatype={"type":"double", "min":0, "unit":"degC"}
|
||||||
value.default=20
|
value.default=20
|
||||||
@ -30,7 +30,7 @@ userlimits.readonly=False
|
|||||||
.meaning=['temperature_regulation', 10]
|
.meaning=['temperature_regulation', 10]
|
||||||
|
|
||||||
[module T_sample]
|
[module T_sample]
|
||||||
class=secop.simulation.SimReadable
|
class=frappy.simulation.SimReadable
|
||||||
.description=(optional) Sample temperature sensor.
|
.description=(optional) Sample temperature sensor.
|
||||||
.visibility=expert
|
.visibility=expert
|
||||||
value.default=300
|
value.default=300
|
||||||
@ -38,7 +38,7 @@ value.datatype={"type":"double", "min":0, "unit":"degC"}
|
|||||||
.meaning=["temperature", 9]
|
.meaning=["temperature", 9]
|
||||||
|
|
||||||
[module N2]
|
[module N2]
|
||||||
class=secop.simulation.SimWritable
|
class=frappy.simulation.SimWritable
|
||||||
.description=Switches the N2 gas inlet on or off.
|
.description=Switches the N2 gas inlet on or off.
|
||||||
.visibility=expert
|
.visibility=expert
|
||||||
value.default='off'
|
value.default='off'
|
||||||
@ -47,7 +47,7 @@ target.datatype={"type":"enum", "members":{'off':0,'on':1}}
|
|||||||
target.default='off'
|
target.default='off'
|
||||||
|
|
||||||
[module He]
|
[module He]
|
||||||
class=secop.simulation.SimWritable
|
class=frappy.simulation.SimWritable
|
||||||
.description=Switches the He gas inlet on or off.
|
.description=Switches the He gas inlet on or off.
|
||||||
.visibility=expert
|
.visibility=expert
|
||||||
value.default='off'
|
value.default='off'
|
||||||
@ -56,7 +56,7 @@ target.datatype={"type":"enum", "members":{'off':0,'on':1}}
|
|||||||
target.default='off'
|
target.default='off'
|
||||||
|
|
||||||
[module lamps]
|
[module lamps]
|
||||||
class=secop.simulation.SimWritable
|
class=frappy.simulation.SimWritable
|
||||||
.description=Switches the heating lamps on or off.
|
.description=Switches the heating lamps on or off.
|
||||||
.visibility=expert
|
.visibility=expert
|
||||||
value.default='on'
|
value.default='on'
|
||||||
@ -65,7 +65,7 @@ target.datatype={"type":"enum", "members":{'off':0,'on':1}}
|
|||||||
target.default='on'
|
target.default='on'
|
||||||
|
|
||||||
[module water_ok]
|
[module water_ok]
|
||||||
class=secop.simulation.SimReadable
|
class=frappy.simulation.SimReadable
|
||||||
.description=Readout of the cooling water state.
|
.description=Readout of the cooling water state.
|
||||||
.visibility=expert
|
.visibility=expert
|
||||||
value.default='ok'
|
value.default='ok'
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
[r3]
|
[r3]
|
||||||
class = secop.core.Proxy
|
class = frappy.core.Proxy
|
||||||
remote_class = secop.core.Readable
|
remote_class = frappy.core.Readable
|
||||||
description = temp sensor on 3He system
|
description = temp sensor on 3He system
|
||||||
uri = tcp://pc12694:5000
|
uri = tcp://pc12694:5000
|
||||||
export = False
|
export = False
|
||||||
|
|
||||||
[t3]
|
[t3]
|
||||||
class = secop_psi.softcal.Sensor
|
class = frappy_psi.softcal.Sensor
|
||||||
rawsensor = r3
|
rawsensor = r3
|
||||||
calib = X131346
|
calib = X131346
|
||||||
value.unit = K
|
value.unit = K
|
||||||
|
@ -11,7 +11,7 @@ bindto=0.0.0.0
|
|||||||
bindport=10767
|
bindport=10767
|
||||||
|
|
||||||
[module T]
|
[module T]
|
||||||
class=secop_mlz.entangle.TemperatureController
|
class=frappy_mlz.entangle.TemperatureController
|
||||||
tangodevice=tango://localhost:10000/box/eurotherm/ctrl
|
tangodevice=tango://localhost:10000/box/eurotherm/ctrl
|
||||||
.description=Main temperature control node of Stressihtf2.
|
.description=Main temperature control node of Stressihtf2.
|
||||||
value.unit='degC'
|
value.unit='degC'
|
||||||
@ -41,42 +41,42 @@ pid.default=[1,0,0]
|
|||||||
speed.default=0
|
speed.default=0
|
||||||
|
|
||||||
[module T_sample_a]
|
[module T_sample_a]
|
||||||
class=secop_mlz.entangle.Sensor
|
class=frappy_mlz.entangle.Sensor
|
||||||
tangodevice=tango://localhost:10000/box/eurotherm/sensora
|
tangodevice=tango://localhost:10000/box/eurotherm/sensora
|
||||||
.description=Regulation temperature sensor.
|
.description=Regulation temperature sensor.
|
||||||
.visibility=user
|
.visibility=user
|
||||||
value.unit='degC'
|
value.unit='degC'
|
||||||
|
|
||||||
[module T_sample_b]
|
[module T_sample_b]
|
||||||
class=secop_mlz.entangle.Sensor
|
class=frappy_mlz.entangle.Sensor
|
||||||
tangodevice=tango://localhost:10000/box/eurotherm/sensorb
|
tangodevice=tango://localhost:10000/box/eurotherm/sensorb
|
||||||
.description=(optional) Sample temperature sensor.
|
.description=(optional) Sample temperature sensor.
|
||||||
.visibility=expert
|
.visibility=expert
|
||||||
value.unit='degC'
|
value.unit='degC'
|
||||||
|
|
||||||
[module N2]
|
[module N2]
|
||||||
class=secop_mlz.entangle.NamedDigitalOutput
|
class=frappy_mlz.entangle.NamedDigitalOutput
|
||||||
tangodevice=tango://localhost:10000/box/plc/_gas1
|
tangodevice=tango://localhost:10000/box/plc/_gas1
|
||||||
.description=Switches the N2 gas inlet on or off.
|
.description=Switches the N2 gas inlet on or off.
|
||||||
.visibility=expert
|
.visibility=expert
|
||||||
mapping=dict(off=0,on=1)
|
mapping=dict(off=0,on=1)
|
||||||
|
|
||||||
[module He]
|
[module He]
|
||||||
class=secop_mlz.entangle.NamedDigitalOutput
|
class=frappy_mlz.entangle.NamedDigitalOutput
|
||||||
tangodevice=tango://localhost:10000/box/plc/_gas2
|
tangodevice=tango://localhost:10000/box/plc/_gas2
|
||||||
.description=Switches the He gas inlet on or off.
|
.description=Switches the He gas inlet on or off.
|
||||||
.visibility=expert
|
.visibility=expert
|
||||||
mapping=dict(off=0,on=1)
|
mapping=dict(off=0,on=1)
|
||||||
|
|
||||||
[module lamps]
|
[module lamps]
|
||||||
class=secop_mlz.entangle.NamedDigitalOutput
|
class=frappy_mlz.entangle.NamedDigitalOutput
|
||||||
tangodevice=tango://localhost:10000/box/plc/_onoff
|
tangodevice=tango://localhost:10000/box/plc/_onoff
|
||||||
.description=Switches the heating lamps on or off.
|
.description=Switches the heating lamps on or off.
|
||||||
.visibility=expert
|
.visibility=expert
|
||||||
mapping=dict(off=0,on=1)
|
mapping=dict(off=0,on=1)
|
||||||
|
|
||||||
[module water_ok]
|
[module water_ok]
|
||||||
class=secop_mlz.entangle.NamedDigitalInput
|
class=frappy_mlz.entangle.NamedDigitalInput
|
||||||
tangodevice=tango://localhost:10000/box/plc/_waterok
|
tangodevice=tango://localhost:10000/box/plc/_waterok
|
||||||
.description=Readout of the cooling water state.
|
.description=Readout of the cooling water state.
|
||||||
.visibility=expert
|
.visibility=expert
|
||||||
|
46
cfg/test.cfg
46
cfg/test.cfg
@ -1,46 +0,0 @@
|
|||||||
[node test config]
|
|
||||||
description=description of the testing sec-node
|
|
||||||
.
|
|
||||||
Here should be the long description.
|
|
||||||
It can be very long.
|
|
||||||
.
|
|
||||||
a single . on a line gets stripped so you can make paragraphs.
|
|
||||||
.
|
|
||||||
These texts are supposed to be possibly very long.
|
|
||||||
|
|
||||||
[interface tcp]
|
|
||||||
type=tcp
|
|
||||||
bindto=0.0.0.0
|
|
||||||
bindport=10768
|
|
||||||
|
|
||||||
|
|
||||||
[module LN2]
|
|
||||||
class=secop_demo.test.LN2
|
|
||||||
.description="random value between 0..100%%"
|
|
||||||
value.unit = "%%"
|
|
||||||
|
|
||||||
[module heater]
|
|
||||||
class=secop_demo.test.Heater
|
|
||||||
maxheaterpower=10
|
|
||||||
.description="some heater"
|
|
||||||
|
|
||||||
[module T1]
|
|
||||||
class=secop_demo.test.Temp
|
|
||||||
sensor="X34598T7"
|
|
||||||
.description="some temperature"
|
|
||||||
|
|
||||||
[module T2]
|
|
||||||
class=secop_demo.modules.CoilTemp
|
|
||||||
sensor="X34598T8"
|
|
||||||
.description="some temperature"
|
|
||||||
|
|
||||||
[module T3]
|
|
||||||
class=secop_demo.modules.CoilTemp
|
|
||||||
sensor="X34598T9"
|
|
||||||
.description="some temperature"
|
|
||||||
|
|
||||||
|
|
||||||
[module Lower]
|
|
||||||
class=secop_demo.test.Lower
|
|
||||||
.description="something else"
|
|
||||||
|
|
46
cfg/test_cfg.py
Normal file
46
cfg/test_cfg.py
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
Node('test.config.frappy.demo',
|
||||||
|
'''short description of the testing sec-node
|
||||||
|
|
||||||
|
This description for the Nodecan be as long as you need if you use a multiline string.
|
||||||
|
|
||||||
|
Very long!
|
||||||
|
The needed fields are Equipment id (1st argument), description (this)
|
||||||
|
and the main interface of the node (3rd arg)
|
||||||
|
''',
|
||||||
|
'tcp://10768',
|
||||||
|
)
|
||||||
|
|
||||||
|
Mod('LN2',
|
||||||
|
'frappy_demo.test.LN2',
|
||||||
|
'random value between 0..100%',
|
||||||
|
value = Param(default = 0, unit = '%'),
|
||||||
|
)
|
||||||
|
|
||||||
|
Mod('heater',
|
||||||
|
'frappy_demo.test.Heater',
|
||||||
|
'some heater',
|
||||||
|
maxheaterpower = 10,
|
||||||
|
)
|
||||||
|
|
||||||
|
Mod('T1',
|
||||||
|
'frappy_demo.test.Temp',
|
||||||
|
'some temperature',
|
||||||
|
sensor = 'X34598T7',
|
||||||
|
)
|
||||||
|
|
||||||
|
Mod('T2',
|
||||||
|
'frappy_demo.test.Temp',
|
||||||
|
'some temperature',
|
||||||
|
sensor = 'X34598T8',
|
||||||
|
)
|
||||||
|
|
||||||
|
Mod('T3',
|
||||||
|
'frappy_demo.test.Temp',
|
||||||
|
'some temperature',
|
||||||
|
sensor = 'X34598T9',
|
||||||
|
)
|
||||||
|
|
||||||
|
Mod('Lower',
|
||||||
|
'frappy_demo.test.Lower',
|
||||||
|
'something else',
|
||||||
|
)
|
@ -1,4 +1,4 @@
|
|||||||
FROM python:3-buster AS base
|
FROM python:3.9 AS base
|
||||||
ARG PYVER=python3
|
ARG PYVER=python3
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
@ -11,7 +11,7 @@ RUN apt-get update && \
|
|||||||
locales \
|
locales \
|
||||||
python3 \
|
python3 \
|
||||||
python3-dev \
|
python3-dev \
|
||||||
python3-pytango \
|
python3-tango \
|
||||||
python3-venv python3-setuptools \
|
python3-venv python3-setuptools \
|
||||||
virtualenv
|
virtualenv
|
||||||
|
|
||||||
@ -33,14 +33,15 @@ RUN virtualenv /home/jenkins/tools2 && \
|
|||||||
rm -rf /home/jenkins/tools2src
|
rm -rf /home/jenkins/tools2src
|
||||||
|
|
||||||
RUN virtualenv -p /usr/bin/python3 --system-site-packages /home/jenkins/secopvenv && \
|
RUN virtualenv -p /usr/bin/python3 --system-site-packages /home/jenkins/secopvenv && \
|
||||||
git clone https://forge.frm2.tum.de/review/sine2020/secop/playground /home/jenkins/playground && \
|
git clone https://forge.frm2.tum.de/review/secop/frappy /home/jenkins/frappy && \
|
||||||
. /home/jenkins/secopvenv/bin/activate && \
|
. /home/jenkins/secopvenv/bin/activate && \
|
||||||
pip install -U pip wheel setuptools && \
|
pip install -U pip wheel setuptools && \
|
||||||
pip install -r /home/jenkins/playground/requirements-dev.txt -r /home/jenkins/playground/requirements.txt pylint pytest && \
|
pip install -r /home/jenkins/frappy/requirements-dev.txt -r /home/jenkins/frappy/requirements.txt pylint pytest && \
|
||||||
rm -rf /home/jenkins/playground
|
rm -rf /home/jenkins/frappy
|
||||||
|
|
||||||
|
|
||||||
FROM base AS docs
|
FROM base AS docs
|
||||||
|
ARG PYVER=python3
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
USER root
|
USER root
|
||||||
@ -50,6 +51,8 @@ RUN apt-get update && \
|
|||||||
texlive-latex-base \
|
texlive-latex-base \
|
||||||
texlive-latex-recommended \
|
texlive-latex-recommended \
|
||||||
texlive-fonts-recommended \
|
texlive-fonts-recommended \
|
||||||
|
texlive-fonts-extra \
|
||||||
|
tex-gyre \
|
||||||
texlive-base \
|
texlive-base \
|
||||||
texlive-binaries \
|
texlive-binaries \
|
||||||
latexmk
|
latexmk
|
||||||
|
56
ci/Jenkinsfile
vendored
56
ci/Jenkinsfile
vendored
@ -4,7 +4,7 @@ properties([
|
|||||||
daysToKeepStr: '',
|
daysToKeepStr: '',
|
||||||
numToKeepStr: '50')),
|
numToKeepStr: '50')),
|
||||||
parameters([
|
parameters([
|
||||||
string(defaultValue: 'sine2020/secop/playground',
|
string(defaultValue: 'secop/frappy',
|
||||||
description: '', name: 'GERRIT_PROJECT'),
|
description: '', name: 'GERRIT_PROJECT'),
|
||||||
string(defaultValue: 'master',
|
string(defaultValue: 'master',
|
||||||
description: '', name: 'GERRIT_BRANCH'),
|
description: '', name: 'GERRIT_BRANCH'),
|
||||||
@ -43,20 +43,20 @@ git diff HEAD~1... --name-only --diff-filter=ARCM -- \\*.py
|
|||||||
pip install -r requirements-dev.txt
|
pip install -r requirements-dev.txt
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
pip install isort pylint
|
pip install isort pylint
|
||||||
python3 setup.py develop
|
pip install -e .
|
||||||
export PYTHONIOENCODING=utf8
|
export PYTHONIOENCODING=utf8
|
||||||
|
|
||||||
echo "$changedFiles"
|
echo "$changedFiles"
|
||||||
if [[ -n "$changedFiles" ]]; then
|
if [[ -n "$changedFiles" ]]; then
|
||||||
set -o pipefail
|
set -o pipefail
|
||||||
pylint $changedFiles | tee pylint_results.txt
|
pylint $changedFiles | tee pylint_results.txt
|
||||||
isort -df $changedFiles | tee isort_results.txt
|
isort --df $changedFiles | tee isort_results.txt
|
||||||
fi
|
fi
|
||||||
"""
|
"""
|
||||||
withCredentials([string(credentialsId: 'GERRITHTTP',
|
withCredentials([string(credentialsId: 'GERRITHTTP',
|
||||||
variable: 'GERRITHTTP')]) {
|
variable: 'GERRITHTTP')]) {
|
||||||
sh """\
|
sh """\
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
if [ -f pylint_results.txt ] ; then
|
if [ -f pylint_results.txt ] ; then
|
||||||
/home/jenkins/tools2/bin/pylint2gerrit
|
/home/jenkins/tools2/bin/pylint2gerrit
|
||||||
mv pylint_results.txt pylint-${pyver}.txt
|
mv pylint_results.txt pylint-${pyver}.txt
|
||||||
@ -67,19 +67,11 @@ fi
|
|||||||
} // credentials
|
} // credentials
|
||||||
|
|
||||||
echo "pylint result: $res"
|
echo "pylint result: $res"
|
||||||
this.verifyresult.put('pylint'+pyver, 1)
|
|
||||||
if ( res != 0 ) {
|
if ( res != 0 ) {
|
||||||
currentBuild.result='FAILURE'
|
currentBuild.result='FAILURE'
|
||||||
this.verifyresult.put('pylint'+ pyver, -1)
|
|
||||||
status = 'FAILURE'
|
status = 'FAILURE'
|
||||||
}
|
}
|
||||||
|
|
||||||
gerritverificationpublisher([
|
|
||||||
verifyStatusValue: this.verifyresult['pylint'+pyver],
|
|
||||||
verifyStatusCategory: 'pylint ',
|
|
||||||
verifyStatusName: 'pylint-'+pyver,
|
|
||||||
verifyStatusReporter: 'jenkins',
|
|
||||||
verifyStatusRerun: '!recheck'])
|
|
||||||
archiveArtifacts([allowEmptyArchive: true,
|
archiveArtifacts([allowEmptyArchive: true,
|
||||||
artifacts: 'pylint-*.txt'])
|
artifacts: 'pylint-*.txt'])
|
||||||
recordIssues([enabledForFailure: true,
|
recordIssues([enabledForFailure: true,
|
||||||
@ -90,9 +82,6 @@ fi
|
|||||||
failedTotalAll: 1])
|
failedTotalAll: 1])
|
||||||
|
|
||||||
|
|
||||||
if (status == 'FAILURE') {
|
|
||||||
throw new Exception('Failure in pylint with ' + pyver)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} // run_pylint
|
} // run_pylint
|
||||||
|
|
||||||
@ -104,7 +93,6 @@ def run_tests(pyver) {
|
|||||||
addopts = --junit-xml=pytest.xml --junit-prefix=''' + pyver
|
addopts = --junit-xml=pytest.xml --junit-prefix=''' + pyver
|
||||||
|
|
||||||
def status = "OK"
|
def status = "OK"
|
||||||
verifyresult.put(pyver, 0)
|
|
||||||
try {
|
try {
|
||||||
timeout(5) {
|
timeout(5) {
|
||||||
sh '''\
|
sh '''\
|
||||||
@ -112,28 +100,17 @@ addopts = --junit-xml=pytest.xml --junit-prefix=''' + pyver
|
|||||||
. /home/jenkins/secopvenv/bin/activate
|
. /home/jenkins/secopvenv/bin/activate
|
||||||
pip install -r requirements-dev.txt
|
pip install -r requirements-dev.txt
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
python3 setup.py develop
|
pip install -e .
|
||||||
make test
|
make test
|
||||||
'''
|
'''
|
||||||
verifyresult.put(pyver, 1)
|
|
||||||
}
|
}
|
||||||
} catch (all) {
|
} catch (all) {
|
||||||
currentBuild.result = 'FAILURE'
|
currentBuild.result = 'FAILURE'
|
||||||
status = 'FAILURE'
|
status = 'FAILURE'
|
||||||
verifyresult.put(pyver, -1)
|
|
||||||
}
|
}
|
||||||
gerritverificationpublisher([
|
|
||||||
verifyStatusValue: verifyresult[pyver],
|
|
||||||
verifyStatusCategory: 'test ',
|
|
||||||
verifyStatusName: 'pytest-'+pyver,
|
|
||||||
verifyStatusReporter: 'jenkins',
|
|
||||||
verifyStatusRerun: '!recheck'])
|
|
||||||
|
|
||||||
step([$class: 'JUnitResultArchiver', allowEmptyResults: true,
|
step([$class: 'JUnitResultArchiver', allowEmptyResults: true,
|
||||||
keepLongStdio: true, testResults: 'pytest.xml'])
|
keepLongStdio: true, testResults: 'pytest.xml'])
|
||||||
if (status == 'FAILURE') {
|
|
||||||
throw new Exception('Failure in test with ' + pyver)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,7 +120,7 @@ def run_docs() {
|
|||||||
. /home/jenkins/secopvenv/bin/activate
|
. /home/jenkins/secopvenv/bin/activate
|
||||||
pip install -r requirements-dev.txt
|
pip install -r requirements-dev.txt
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
python3 setup.py develop
|
pip install -e .
|
||||||
'''
|
'''
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,15 +162,7 @@ def run_docs() {
|
|||||||
|
|
||||||
stage('store html doc for build') {
|
stage('store html doc for build') {
|
||||||
publishHTML([allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'doc/_build/html', reportFiles: 'index.html', reportName: 'Built documentation', reportTitles: ''])
|
publishHTML([allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'doc/_build/html', reportFiles: 'index.html', reportName: 'Built documentation', reportTitles: ''])
|
||||||
gerritverificationpublisher([
|
|
||||||
verifyStatusValue: 1,
|
|
||||||
verifyStatusCategory: 'test ',
|
|
||||||
verifyStatusName: 'doc',
|
|
||||||
verifyStatusReporter: 'jenkins',
|
|
||||||
verifyStatusRerun: '@recheck'
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -225,16 +194,16 @@ node("dockerhost") {
|
|||||||
sh '''#!/bin/bash
|
sh '''#!/bin/bash
|
||||||
git worktree add tmpmaster origin/master
|
git worktree add tmpmaster origin/master
|
||||||
cd tmpmaster
|
cd tmpmaster
|
||||||
docker build --target base --tag secop_base:latest ci
|
docker build --target base --tag frappy_base:latest ci
|
||||||
docker build --target docs --tag secop_docs:latest ci
|
docker build --target docs --tag frappy_docs:latest ci
|
||||||
cd ..
|
cd ..
|
||||||
rm -rf tmpmaster
|
rm -rf tmpmaster
|
||||||
'''
|
'''
|
||||||
}
|
}
|
||||||
|
|
||||||
stage('execute tests') {
|
stage('execute tests') {
|
||||||
def img = docker.image('secop_base:latest')
|
def img = docker.image('frappy_base:latest')
|
||||||
def docimg = docker.image('secop_docs:latest')
|
def docimg = docker.image('frappy_docs:latest')
|
||||||
|
|
||||||
parallel 'Test': {
|
parallel 'Test': {
|
||||||
img.inside {
|
img.inside {
|
||||||
@ -252,9 +221,10 @@ node("dockerhost") {
|
|||||||
if (GERRIT_EVENT_TYPE == 'change-merged')
|
if (GERRIT_EVENT_TYPE == 'change-merged')
|
||||||
{
|
{
|
||||||
sh '''
|
sh '''
|
||||||
rsync -rlv doc/_build/* /ictrlsrv/share/public/doc/secop
|
rsync -rlv doc/_build/* /ictrlsrv/share/public/doc/frappy
|
||||||
'''
|
'''
|
||||||
}
|
}
|
||||||
}}
|
}}, failFast: false
|
||||||
}
|
}
|
||||||
|
setGerritReview()
|
||||||
}
|
}
|
||||||
|
@ -10,10 +10,10 @@ The Dockerfile defines two images:
|
|||||||
|
|
||||||
To create the images:
|
To create the images:
|
||||||
|
|
||||||
docker build --target <base|docs> --tag secop_<base|docs>:latest .
|
docker build --target <base|docs> --tag frappy_<base|docs>:latest .
|
||||||
|
|
||||||
To test images interactivly:
|
To test images interactivly:
|
||||||
docker run -u jenkins -i -t secop<base|docs> /bin/bash
|
docker run -u jenkins -i -t frappy<base|docs> /bin/bash
|
||||||
|
|
||||||
The Jenkinsfile uses this Dockerfile (only approved checked-in versions from master)
|
The Jenkinsfile uses this Dockerfile (only approved checked-in versions from master)
|
||||||
to build the images (a rebuild will only happen if the Dockerfile is changed as docker
|
to build the images (a rebuild will only happen if the Dockerfile is changed as docker
|
||||||
|
2
debian/README
vendored
2
debian/README
vendored
@ -1,4 +1,4 @@
|
|||||||
The Debian Package secop-core
|
The Debian Package frappy-core
|
||||||
-----------------------------
|
-----------------------------
|
||||||
|
|
||||||
SECoP is currently under development.
|
SECoP is currently under development.
|
||||||
|
324
debian/changelog
vendored
324
debian/changelog
vendored
@ -1,3 +1,263 @@
|
|||||||
|
frappy-core (0.15.0) focal; urgency=medium
|
||||||
|
|
||||||
|
[ Björn Pedersen ]
|
||||||
|
* Remove iohandler left-overs from docs
|
||||||
|
|
||||||
|
[ Georg Brandl ]
|
||||||
|
* Makefile: fix release target
|
||||||
|
|
||||||
|
[ Alexander Zaft ]
|
||||||
|
* Add requirements-gui.txt and add PyQT5
|
||||||
|
|
||||||
|
[ Christian Felder ]
|
||||||
|
* Fix typo in .description
|
||||||
|
|
||||||
|
[ Alexander Zaft ]
|
||||||
|
* fixed pylint warnings
|
||||||
|
* Rename from secop to frappy
|
||||||
|
* rename debian package files
|
||||||
|
|
||||||
|
[ Björn Pedersen ]
|
||||||
|
* CI build: upgrade base image
|
||||||
|
|
||||||
|
[ Alexander Zaft ]
|
||||||
|
* rename debian files
|
||||||
|
|
||||||
|
[ Björn Pedersen ]
|
||||||
|
* Fix doc warnings/errors
|
||||||
|
|
||||||
|
-- Björn Pedersen <jenkins@frm2.tum.de> Thu, 10 Nov 2022 14:46:01 +0100
|
||||||
|
|
||||||
|
secop-core (0.14.3) focal; urgency=medium
|
||||||
|
|
||||||
|
[ Enrico Faulhaber ]
|
||||||
|
* change repo to secop/frappy
|
||||||
|
|
||||||
|
[ Georg Brandl ]
|
||||||
|
* secop_mlz/amagnet: formatting fixup
|
||||||
|
|
||||||
|
[ Bjoern Pedersen ]
|
||||||
|
* Upgrade for ci
|
||||||
|
|
||||||
|
[ Enrico Faulhaber ]
|
||||||
|
* MLZ/entangle: fix AnalogOutput.read_status()
|
||||||
|
|
||||||
|
-- Enrico Faulhaber <jenkins@frm2.tum.de> Thu, 03 Nov 2022 13:51:52 +0100
|
||||||
|
|
||||||
|
secop-core (0.14.2) focal; urgency=medium
|
||||||
|
|
||||||
|
* systemd generator: adapt to changed config API
|
||||||
|
|
||||||
|
-- Georg Brandl <jenkins@frm2.tum.de> Thu, 20 Oct 2022 15:38:45 +0200
|
||||||
|
|
||||||
|
secop-core (0.14.1) focal; urgency=medium
|
||||||
|
|
||||||
|
[ Markus Zolliker ]
|
||||||
|
* secop_psi.entangle.AnalogInput: fix main value
|
||||||
|
|
||||||
|
[ Georg Brandl ]
|
||||||
|
* gui: clarify needed input for "add sec node" dialog
|
||||||
|
* mlz: avoid error on import due to consistency check
|
||||||
|
* Makefile: fix Jenkins host
|
||||||
|
|
||||||
|
-- Markus Zolliker <jenkins@frm2.tum.de> Thu, 20 Oct 2022 14:04:07 +0200
|
||||||
|
|
||||||
|
secop-core (0.14.0) focal; urgency=medium
|
||||||
|
|
||||||
|
* add simple interactive python client
|
||||||
|
* fix undefined status in softcal
|
||||||
|
* improve HasConvergence mixin
|
||||||
|
* fix bug in persistent.py
|
||||||
|
* fix bug when restarting statemachine
|
||||||
|
* improve general config
|
||||||
|
* improvements on interactive client
|
||||||
|
* apply main unit also in structured types
|
||||||
|
* HasIO: automatic creation of io from uri fails
|
||||||
|
|
||||||
|
-- Markus Zolliker <jenkins@frm2.tum.de> Wed, 19 Oct 2022 11:31:50 +0200
|
||||||
|
|
||||||
|
secop-core (0.13.1) focal; urgency=medium
|
||||||
|
|
||||||
|
[ Markus Zolliker ]
|
||||||
|
* an enum with value 0 should be interpreted as False
|
||||||
|
* make startup faster in case of errors
|
||||||
|
|
||||||
|
[ Enrico Faulhaber ]
|
||||||
|
* secop_mlz: minor rework entangle client
|
||||||
|
|
||||||
|
-- Markus Zolliker <jenkins@jenkins02.admin.frm2.tum.de> Tue, 02 Aug 2022 15:31:52 +0200
|
||||||
|
|
||||||
|
secop-core (0.13.0) focal; urgency=medium
|
||||||
|
|
||||||
|
[ Georg Brandl ]
|
||||||
|
* debian: fix email addresses in changelog
|
||||||
|
|
||||||
|
[ Markus Zolliker ]
|
||||||
|
* various small changes
|
||||||
|
* automatic saving of persistent parameters
|
||||||
|
* add more tests and fixes for command inheritance
|
||||||
|
* entangle.AnalogOutput: fix window mechanism
|
||||||
|
* remote logging (issue 46)
|
||||||
|
* add timeouts to MultiEvents
|
||||||
|
* introduce general config file
|
||||||
|
* improve handling of module init methods
|
||||||
|
* check for bad read_* and write_* methods
|
||||||
|
* change name of read_hw_status method in sequencer mixin
|
||||||
|
* fix doc (stringio - > io)
|
||||||
|
* enhance logging
|
||||||
|
* UniqueObject
|
||||||
|
* ReadHandler and WriteHandler decorators
|
||||||
|
* do not convert string to float
|
||||||
|
* check for problematic value range
|
||||||
|
* unify name and module on Attached property
|
||||||
|
* ppms: replace IOHandler by Read/WriteHandler
|
||||||
|
* fix handling commands
|
||||||
|
* common read/write handlers
|
||||||
|
* implement a state machine
|
||||||
|
* proper return value in handler read_* methods
|
||||||
|
* new poll mechanism
|
||||||
|
* support for fast poll when busy
|
||||||
|
* various small fixes
|
||||||
|
* reset connection on identification
|
||||||
|
* improve softcal
|
||||||
|
* move markdown to requirements-dev.txt
|
||||||
|
* improve k2601b driver
|
||||||
|
* fix and improved Attached
|
||||||
|
* fix error in write wrapper and more
|
||||||
|
* support write_ method on readonly param and more
|
||||||
|
* init generalConfig.defaults only in secop-server
|
||||||
|
* HasConvergence mixin
|
||||||
|
* avoid race conditions in read_*/write_* methods
|
||||||
|
* reintroduced individual init of generalConfig.defaults
|
||||||
|
* fix statemachine
|
||||||
|
* use a common poller thread for modules sharing io
|
||||||
|
* motor valve using trinamic motor
|
||||||
|
* improved trinamic driver
|
||||||
|
* fix error in secop.logging
|
||||||
|
* avoid deadlock in proxy
|
||||||
|
* improve poller error handling
|
||||||
|
* support for OI mercury series
|
||||||
|
* add 'ts' to the ppms simulation
|
||||||
|
* allow a configfile path as single argument to secop-server
|
||||||
|
* fix keithley 2601b after tests
|
||||||
|
* channel switcher for Lakeshore 370 with scanner
|
||||||
|
* feature implementation
|
||||||
|
* allow to convert numpy arrays to ArrayOf
|
||||||
|
* remove IOHandler stuff
|
||||||
|
|
||||||
|
[ Enrico Faulhaber ]
|
||||||
|
* default unit to UTF8
|
||||||
|
|
||||||
|
-- Georg Brandl <jenkins@frm2.tum.de> Tue, 02 Aug 2022 09:47:06 +0200
|
||||||
|
|
||||||
|
secop-core (0.12.4) focal; urgency=medium
|
||||||
|
|
||||||
|
* fix command inheritance
|
||||||
|
|
||||||
|
-- Markus Zolliker <jenkins@jenkins01.admin.frm2.tum.de> Thu, 11 Nov 2021 16:21:19 +0100
|
||||||
|
|
||||||
|
secop-core (0.12.3) focal; urgency=medium
|
||||||
|
|
||||||
|
[ Georg Brandl ]
|
||||||
|
* Makefile: fix docker image
|
||||||
|
|
||||||
|
[ Markus Zolliker ]
|
||||||
|
* various fixes
|
||||||
|
* remove irrelevant comments
|
||||||
|
* introduce BytesIO
|
||||||
|
* GUI fixes
|
||||||
|
* persistent params / trinamic motor
|
||||||
|
* fix Parameter/Command copy method
|
||||||
|
* show first instead of last traceback on multiple errors
|
||||||
|
* fix parameter inheritance
|
||||||
|
* fix property inheritance
|
||||||
|
* fix python 3.5 compatibility
|
||||||
|
* omit updates of unchanged values within short time
|
||||||
|
* improve simulation
|
||||||
|
* automatically register subclasses of AsynConn
|
||||||
|
* fix feature for removing commands
|
||||||
|
|
||||||
|
-- Georg Brandl <jenkins@jenkins01.admin.frm2.tum.de> Wed, 10 Nov 2021 16:33:19 +0100
|
||||||
|
|
||||||
|
secop-core (0.12.2) focal; urgency=medium
|
||||||
|
|
||||||
|
[ Markus Zolliker ]
|
||||||
|
* fix issue with new syntax in simulation
|
||||||
|
* treat specifier of describe message
|
||||||
|
* allow to remove accessibles
|
||||||
|
|
||||||
|
[ Enrico Faulhaber ]
|
||||||
|
* secop_mlz: small fixes
|
||||||
|
|
||||||
|
-- Markus Zolliker <jenkins@jenkins01.admin.frm2.tum.de> Tue, 18 May 2021 10:29:17 +0200
|
||||||
|
|
||||||
|
secop-core (0.12.1) focal; urgency=medium
|
||||||
|
|
||||||
|
* remove secop-console from debian *.install file
|
||||||
|
|
||||||
|
-- Enrico Faulhaber <jenkins@jenkins02.admin.frm2.tum.de> Tue, 04 May 2021 09:42:53 +0200
|
||||||
|
|
||||||
|
secop-core (0.12.0) focal; urgency=medium
|
||||||
|
|
||||||
|
[ Markus Zolliker ]
|
||||||
|
* make datatypes immutable
|
||||||
|
* customizable general config
|
||||||
|
* support for multiple secop servers
|
||||||
|
* secop.asynconn without pyserial
|
||||||
|
* change cfg file format
|
||||||
|
* fix bug in secop.gui.valuewidgets
|
||||||
|
* fix deadlock when reconnecting client
|
||||||
|
* allow class instead of class name in proxy_class
|
||||||
|
* fix pylint command in Makefile
|
||||||
|
* change arguments of stringio-server
|
||||||
|
* router bug fix
|
||||||
|
* introduce update callbacks
|
||||||
|
* improve error handling on client connections
|
||||||
|
* ppms: improve status and temperature
|
||||||
|
* rework tcp server
|
||||||
|
* cosmetics on datatypes.TextType
|
||||||
|
* improve error handling in SecopClient
|
||||||
|
* improve HasIodev
|
||||||
|
* HasIodev bug fix
|
||||||
|
* fix handling of StructOf datatype
|
||||||
|
* more flexible end_of_line in stringio
|
||||||
|
* improvements on PPMS and LS370
|
||||||
|
* add readbytes method to AsynConn
|
||||||
|
* Param(..., initwrite=True) works only with poll=True
|
||||||
|
* fix initwrite behaviour
|
||||||
|
* make order of accessibles work again
|
||||||
|
* main module of LS370 is now drivable
|
||||||
|
* improve softcal
|
||||||
|
* make arguments of Parameter and Override consistent
|
||||||
|
* new syntax for parameter/commands/properties
|
||||||
|
* enhance documentation
|
||||||
|
* removed old style syntax
|
||||||
|
* after running isort
|
||||||
|
* try to follow PEP8
|
||||||
|
* fix inheritance order
|
||||||
|
* remove obsolete code
|
||||||
|
* lookup cfg files in a list of directories
|
||||||
|
* added hook for optional history writer
|
||||||
|
* fixed errors during migration
|
||||||
|
* move historywriter to secop_psi
|
||||||
|
* fix autoscan behaviour in ls370res
|
||||||
|
|
||||||
|
[ l_samenv ]
|
||||||
|
* improve tutorial_helevel
|
||||||
|
* fixed bugs from syntax migration
|
||||||
|
|
||||||
|
[ Markus Zolliker ]
|
||||||
|
* user friendly reporting of config errors
|
||||||
|
|
||||||
|
[ Bjoern Pedersen ]
|
||||||
|
* Jenkisfile: verification
|
||||||
|
* Fixes to Jenkinsfile
|
||||||
|
* No pull for images, they are recreated in the job
|
||||||
|
* Another Jenkisfile error
|
||||||
|
* Correct checks enum
|
||||||
|
|
||||||
|
-- Markus Zolliker <jenkins@jenkins02.admin.frm2.tum.de> Tue, 04 May 2021 08:49:57 +0200
|
||||||
|
|
||||||
secop-core (0.11.6) unstable; urgency=medium
|
secop-core (0.11.6) unstable; urgency=medium
|
||||||
|
|
||||||
* fix secop-generator
|
* fix secop-generator
|
||||||
@ -133,7 +393,7 @@ secop-core (0.10.5) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Tue, 29 Oct 2019 16:33:18 +0100
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Tue, 29 Oct 2019 16:33:18 +0100
|
||||||
|
|
||||||
secop-core (0.10.3) unstable; urgency=low
|
secop-core (0.10.3) unstable; urgency=low
|
||||||
|
|
||||||
@ -142,7 +402,7 @@ secop-core (0.10.3) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Fri, 11 Oct 2019 10:49:43 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Fri, 11 Oct 2019 10:49:43 +0200
|
||||||
|
|
||||||
secop-core (0.10.2) unstable; urgency=low
|
secop-core (0.10.2) unstable; urgency=low
|
||||||
|
|
||||||
@ -153,7 +413,7 @@ secop-core (0.10.2) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Fri, 11 Oct 2019 10:42:58 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Fri, 11 Oct 2019 10:42:58 +0200
|
||||||
|
|
||||||
secop-core (0.10.1) unstable; urgency=low
|
secop-core (0.10.1) unstable; urgency=low
|
||||||
|
|
||||||
@ -162,7 +422,7 @@ secop-core (0.10.1) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Thu, 26 Sep 2019 16:41:10 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Thu, 26 Sep 2019 16:41:10 +0200
|
||||||
|
|
||||||
secop-core (0.10.0) unstable; urgency=low
|
secop-core (0.10.0) unstable; urgency=low
|
||||||
|
|
||||||
@ -171,7 +431,7 @@ secop-core (0.10.0) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Thu, 26 Sep 2019 16:31:14 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Thu, 26 Sep 2019 16:31:14 +0200
|
||||||
|
|
||||||
secop-core (0.9.0) unstable; urgency=low
|
secop-core (0.9.0) unstable; urgency=low
|
||||||
|
|
||||||
@ -198,7 +458,7 @@ secop-core (0.9.0) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Thu, 26 Sep 2019 16:26:07 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Thu, 26 Sep 2019 16:26:07 +0200
|
||||||
|
|
||||||
secop-core (0.8.1) unstable; urgency=low
|
secop-core (0.8.1) unstable; urgency=low
|
||||||
|
|
||||||
@ -207,7 +467,7 @@ secop-core (0.8.1) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Wed, 25 Sep 2019 15:40:44 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Wed, 25 Sep 2019 15:40:44 +0200
|
||||||
|
|
||||||
secop-core (0.8.0) unstable; urgency=low
|
secop-core (0.8.0) unstable; urgency=low
|
||||||
|
|
||||||
@ -275,7 +535,7 @@ secop-core (0.8.0) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Wed, 25 Sep 2019 10:27:51 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Wed, 25 Sep 2019 10:27:51 +0200
|
||||||
|
|
||||||
secop-core (0.7.0) unstable; urgency=low
|
secop-core (0.7.0) unstable; urgency=low
|
||||||
|
|
||||||
@ -311,7 +571,7 @@ secop-core (0.7.0) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Thu, 28 Mar 2019 13:46:08 +0100
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Thu, 28 Mar 2019 13:46:08 +0100
|
||||||
|
|
||||||
secop-core (0.6.4) unstable; urgency=low
|
secop-core (0.6.4) unstable; urgency=low
|
||||||
|
|
||||||
@ -376,7 +636,7 @@ secop-core (0.6.4) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Thu, 20 Dec 2018 16:44:03 +0100
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Thu, 20 Dec 2018 16:44:03 +0100
|
||||||
|
|
||||||
secop-core (0.6.3) unstable; urgency=low
|
secop-core (0.6.3) unstable; urgency=low
|
||||||
|
|
||||||
@ -390,7 +650,7 @@ secop-core (0.6.3) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Fri, 27 Jul 2018 09:31:59 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Fri, 27 Jul 2018 09:31:59 +0200
|
||||||
|
|
||||||
secop-core (0.6.2) unstable; urgency=low
|
secop-core (0.6.2) unstable; urgency=low
|
||||||
|
|
||||||
@ -429,7 +689,7 @@ secop-core (0.6.2) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Wed, 18 Jul 2018 12:06:57 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Wed, 18 Jul 2018 12:06:57 +0200
|
||||||
|
|
||||||
secop-core (0.6.1) unstable; urgency=low
|
secop-core (0.6.1) unstable; urgency=low
|
||||||
|
|
||||||
@ -438,7 +698,7 @@ secop-core (0.6.1) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Thu, 19 Apr 2018 10:24:44 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Thu, 19 Apr 2018 10:24:44 +0200
|
||||||
|
|
||||||
secop-core (0.6.0) unstable; urgency=low
|
secop-core (0.6.0) unstable; urgency=low
|
||||||
|
|
||||||
@ -458,7 +718,7 @@ secop-core (0.6.0) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Tue, 17 Apr 2018 17:38:52 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Tue, 17 Apr 2018 17:38:52 +0200
|
||||||
|
|
||||||
secop-core (0.5.0) unstable; urgency=low
|
secop-core (0.5.0) unstable; urgency=low
|
||||||
|
|
||||||
@ -470,7 +730,7 @@ secop-core (0.5.0) unstable; urgency=low
|
|||||||
* fix amagnet
|
* fix amagnet
|
||||||
* add info about Meeting @PSI
|
* add info about Meeting @PSI
|
||||||
* fix typo and include comment from Niklas
|
* fix typo and include comment from Niklas
|
||||||
* playground: give sequencermixin a loopcounter (per step)
|
* give sequencermixin a loopcounter (per step)
|
||||||
|
|
||||||
[ Frank Wutzler ]
|
[ Frank Wutzler ]
|
||||||
* describe SECoP motivation discussed in meeting 2017-11-27
|
* describe SECoP motivation discussed in meeting 2017-11-27
|
||||||
@ -521,7 +781,7 @@ secop-core (0.5.0) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Tue, 17 Apr 2018 12:45:58 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Tue, 17 Apr 2018 12:45:58 +0200
|
||||||
|
|
||||||
secop-core (0.4.4) unstable; urgency=low
|
secop-core (0.4.4) unstable; urgency=low
|
||||||
|
|
||||||
@ -530,7 +790,7 @@ secop-core (0.4.4) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Sun, 24 Sep 2017 22:25:01 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Sun, 24 Sep 2017 22:25:01 +0200
|
||||||
|
|
||||||
secop-core (0.4.3) unstable; urgency=low
|
secop-core (0.4.3) unstable; urgency=low
|
||||||
|
|
||||||
@ -539,7 +799,7 @@ secop-core (0.4.3) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Fri, 22 Sep 2017 17:29:46 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Fri, 22 Sep 2017 17:29:46 +0200
|
||||||
|
|
||||||
secop-core (0.4.2) unstable; urgency=low
|
secop-core (0.4.2) unstable; urgency=low
|
||||||
|
|
||||||
@ -548,7 +808,7 @@ secop-core (0.4.2) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Fri, 22 Sep 2017 16:37:59 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Fri, 22 Sep 2017 16:37:59 +0200
|
||||||
|
|
||||||
secop-core (0.4.1) unstable; urgency=low
|
secop-core (0.4.1) unstable; urgency=low
|
||||||
|
|
||||||
@ -557,7 +817,7 @@ secop-core (0.4.1) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Fri, 22 Sep 2017 13:25:28 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Fri, 22 Sep 2017 13:25:28 +0200
|
||||||
|
|
||||||
secop-core (0.4.0) unstable; urgency=low
|
secop-core (0.4.0) unstable; urgency=low
|
||||||
|
|
||||||
@ -567,7 +827,7 @@ secop-core (0.4.0) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Fri, 22 Sep 2017 10:33:04 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Fri, 22 Sep 2017 10:33:04 +0200
|
||||||
|
|
||||||
secop-core (0.3.0) unstable; urgency=low
|
secop-core (0.3.0) unstable; urgency=low
|
||||||
|
|
||||||
@ -633,7 +893,7 @@ secop-core (0.3.0) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Mon, 18 Sep 2017 14:18:36 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Mon, 18 Sep 2017 14:18:36 +0200
|
||||||
|
|
||||||
secop-core (0.2.0) unstable; urgency=low
|
secop-core (0.2.0) unstable; urgency=low
|
||||||
|
|
||||||
@ -642,7 +902,7 @@ secop-core (0.2.0) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Thu, 07 Sep 2017 14:55:41 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Thu, 07 Sep 2017 14:55:41 +0200
|
||||||
|
|
||||||
secop-core (0.1.1) unstable; urgency=low
|
secop-core (0.1.1) unstable; urgency=low
|
||||||
|
|
||||||
@ -651,7 +911,7 @@ secop-core (0.1.1) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Thu, 07 Sep 2017 11:02:19 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Thu, 07 Sep 2017 11:02:19 +0200
|
||||||
|
|
||||||
secop-core (0.1.0) unstable; urgency=low
|
secop-core (0.1.0) unstable; urgency=low
|
||||||
|
|
||||||
@ -660,7 +920,7 @@ secop-core (0.1.0) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Thu, 07 Sep 2017 10:50:24 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Thu, 07 Sep 2017 10:50:24 +0200
|
||||||
|
|
||||||
secop-core (0.0.8) unstable; urgency=low
|
secop-core (0.0.8) unstable; urgency=low
|
||||||
|
|
||||||
@ -669,7 +929,7 @@ secop-core (0.0.8) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Tue, 01 Aug 2017 14:13:11 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Tue, 01 Aug 2017 14:13:11 +0200
|
||||||
|
|
||||||
secop-core (0.0.7) unstable; urgency=low
|
secop-core (0.0.7) unstable; urgency=low
|
||||||
|
|
||||||
@ -678,7 +938,7 @@ secop-core (0.0.7) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Tue, 01 Aug 2017 13:52:15 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Tue, 01 Aug 2017 13:52:15 +0200
|
||||||
|
|
||||||
secop-core (0.0.6) unstable; urgency=low
|
secop-core (0.0.6) unstable; urgency=low
|
||||||
|
|
||||||
@ -688,7 +948,7 @@ secop-core (0.0.6) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Tue, 01 Aug 2017 13:39:07 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Tue, 01 Aug 2017 13:39:07 +0200
|
||||||
|
|
||||||
secop-core (0.0.5) unstable; urgency=low
|
secop-core (0.0.5) unstable; urgency=low
|
||||||
|
|
||||||
@ -697,7 +957,7 @@ secop-core (0.0.5) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Tue, 01 Aug 2017 13:11:43 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Tue, 01 Aug 2017 13:11:43 +0200
|
||||||
|
|
||||||
secop-core (0.0.4) unstable; urgency=low
|
secop-core (0.0.4) unstable; urgency=low
|
||||||
|
|
||||||
@ -706,7 +966,7 @@ secop-core (0.0.4) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Thu, 27 Jul 2017 11:39:42 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Thu, 27 Jul 2017 11:39:42 +0200
|
||||||
|
|
||||||
secop-core (0.0.3) unstable; urgency=low
|
secop-core (0.0.3) unstable; urgency=low
|
||||||
|
|
||||||
@ -716,7 +976,7 @@ secop-core (0.0.3) unstable; urgency=low
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Thu, 27 Jul 2017 11:27:28 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Thu, 27 Jul 2017 11:27:28 +0200
|
||||||
|
|
||||||
secop-core (0.0.2) unstable; urgency=medium
|
secop-core (0.0.2) unstable; urgency=medium
|
||||||
|
|
||||||
@ -794,4 +1054,4 @@ secop-core (0.0.2) unstable; urgency=medium
|
|||||||
|
|
||||||
[ Jenkins ]
|
[ Jenkins ]
|
||||||
|
|
||||||
-- Jenkins <jenkins@debuild.taco.frm2> Wed, 19 Jul 2017 11:44:13 +0200
|
-- Jenkins <jenkins@debuild.taco.frm2.tum.de> Wed, 19 Jul 2017 11:44:13 +0200
|
||||||
|
48
debian/control
vendored
48
debian/control
vendored
@ -1,4 +1,4 @@
|
|||||||
Source: secop-core
|
Source: frappy-core
|
||||||
Section: contrib/misc
|
Section: contrib/misc
|
||||||
Priority: optional
|
Priority: optional
|
||||||
Maintainer: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
Maintainer: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||||
@ -23,7 +23,7 @@ Build-Depends: debhelper (>= 11~),
|
|||||||
Standards-Version: 4.1.4
|
Standards-Version: 4.1.4
|
||||||
X-Python3-Version: >= 3.6
|
X-Python3-Version: >= 3.6
|
||||||
|
|
||||||
Package: secop-core
|
Package: frappy-core
|
||||||
Architecture: all
|
Architecture: all
|
||||||
Depends: python3 (>= 3.6),
|
Depends: python3 (>= 3.6),
|
||||||
${misc:Depends},
|
${misc:Depends},
|
||||||
@ -35,60 +35,72 @@ Depends: python3 (>= 3.6),
|
|||||||
python3-mlzlog,
|
python3-mlzlog,
|
||||||
markdown,
|
markdown,
|
||||||
python3-daemon
|
python3-daemon
|
||||||
|
Replaces: secop-core (<= 0.14.3)
|
||||||
|
Breaks: secop-core (<= 0.14.3)
|
||||||
Description: Frappy SECoP core system
|
Description: Frappy SECoP core system
|
||||||
contains the core server and client libraries and the server binary
|
contains the core server and client libraries and the server binary
|
||||||
as well as the systemd integration
|
as well as the systemd integration
|
||||||
|
|
||||||
#Package: secop-doc
|
#Package: frappy-doc
|
||||||
#Architecture: all
|
#Architecture: all
|
||||||
#Section: doc
|
#Section: doc
|
||||||
#Depends: ${sphinxdoc:Depends},
|
#Depends: ${sphinxdoc:Depends},
|
||||||
# ${misc:Depends}
|
# ${misc:Depends}
|
||||||
#Description: Frappy SECoP docu
|
#Description: Frappy SECoP docu
|
||||||
# This is the documentation to all the secop-* packages
|
# This is the documentation to all the frappy-* packages
|
||||||
|
|
||||||
Package: secop-gui
|
Package: frappy-gui
|
||||||
Architecture: all
|
Architecture: all
|
||||||
Depends: secop-core,
|
Depends: frappy-core,
|
||||||
${misc:Depends},
|
${misc:Depends},
|
||||||
${python3:Depends},
|
${python3:Depends},
|
||||||
python3-pyqt (>=4)
|
python3-pyqt (>=4)
|
||||||
|
Replaces: secop-gui (<= 0.14.3)
|
||||||
|
Breaks: secop-gui (<= 0.14.3)
|
||||||
Description: Frappy SECoP gui client + cfgtool
|
Description: Frappy SECoP gui client + cfgtool
|
||||||
contains the GUI client and the configurator
|
contains the GUI client and the configurator
|
||||||
|
|
||||||
Package: secop-demo
|
Package: frappy-demo
|
||||||
Architecture: all
|
Architecture: all
|
||||||
Depends: secop-core,
|
Depends: frappy-core,
|
||||||
${misc:Depends},
|
${misc:Depends},
|
||||||
${python3:Depends}
|
${python3:Depends}
|
||||||
Recommends: secop-gui
|
Replaces: secop-demo (<= 0.14.3)
|
||||||
|
Breaks: secop-demo (<= 0.14.3)
|
||||||
|
Recommends: frappy-gui
|
||||||
Description: SECoP demo files
|
Description: SECoP demo files
|
||||||
for demonstration purposes
|
for demonstration purposes
|
||||||
|
|
||||||
Package: secop-ess
|
Package: frappy-ess
|
||||||
Architecture: all
|
Architecture: all
|
||||||
Depends: secop-core,
|
Depends: frappy-core,
|
||||||
${misc:Depends},
|
${misc:Depends},
|
||||||
${python3:Depends}
|
${python3:Depends}
|
||||||
Recommends: secop-gui
|
Replaces: secop-ess (<= 0.14.3)
|
||||||
|
Breaks: secop-ess (<= 0.14.3)
|
||||||
|
Recommends: frappy-gui
|
||||||
Description: SECoP ess files
|
Description: SECoP ess files
|
||||||
Modules specific for ESS
|
Modules specific for ESS
|
||||||
|
|
||||||
Package: secop-mlz
|
Package: frappy-mlz
|
||||||
Architecture: all
|
Architecture: all
|
||||||
Depends: secop-core,
|
Depends: frappy-core,
|
||||||
${misc:Depends},
|
${misc:Depends},
|
||||||
${python3:Depends},
|
${python3:Depends},
|
||||||
python3-tango (>=9)
|
python3-tango (>=9)
|
||||||
Recommends: secop-gui
|
Replaces: secop-mlz (<= 0.14.3)
|
||||||
|
Breaks: secop-mlz (<= 0.14.3)
|
||||||
|
Recommends: frappy-gui
|
||||||
Description: SECoP mlz files
|
Description: SECoP mlz files
|
||||||
Modules specific for MLZ
|
Modules specific for MLZ
|
||||||
|
|
||||||
Package: secop-psi
|
Package: frappy-psi
|
||||||
Architecture: all
|
Architecture: all
|
||||||
Depends: secop-core,
|
Depends: frappy-core,
|
||||||
${misc:Depends},
|
${misc:Depends},
|
||||||
${python3:Depends}
|
${python3:Depends}
|
||||||
Recommends: secop-gui
|
Replaces: secop-psi (<= 0.14.3)
|
||||||
|
Breaks: secop-psi (<= 0.14.3)
|
||||||
|
Recommends: frappy-gui
|
||||||
Description: SECoP psi files
|
Description: SECoP psi files
|
||||||
Modules specific for PSI
|
Modules specific for PSI
|
||||||
|
6
debian/copyright
vendored
6
debian/copyright
vendored
@ -1,15 +1,15 @@
|
|||||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||||
Upstream-Name: frappy
|
Upstream-Name: frappy
|
||||||
Source: http://forge.frm2.tum.de/cgit/cgit.cgi/frm2/sine2020/secop/playground.git
|
Source: http://forge.frm2.tum.de/cgit/cgit.cgi/secop/frappy.git
|
||||||
Comment: FRAPPY is an implementation of the free SECoP protocol
|
Comment: FRAPPY is an implementation of the free SECoP protocol
|
||||||
see https://www.github.com/SampleEnvironment/SECoP
|
see https://www.github.com/SampleEnvironment/SECoP
|
||||||
|
|
||||||
Files: *
|
Files: *
|
||||||
Copyright: 2016-2019 by the FRAPPY-SECOP contributors (see AUTHORS)
|
Copyright: 2016-2022 by the FRAPPY-SECOP contributors (see AUTHORS)
|
||||||
License: GPL-2
|
License: GPL-2
|
||||||
|
|
||||||
Files: debian/*
|
Files: debian/*
|
||||||
Copyright: 2015-2019 Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
Copyright: 2015-2022 Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||||
License: GPL-2
|
License: GPL-2
|
||||||
|
|
||||||
License: GPL-2
|
License: GPL-2
|
||||||
|
9
debian/frappy-core.install
vendored
Normal file
9
debian/frappy-core.install
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
usr/bin/frappy-server
|
||||||
|
usr/lib/python3.*/dist-packages/frappy/*.py
|
||||||
|
usr/lib/python3.*/dist-packages/frappy/lib
|
||||||
|
usr/lib/python3.*/dist-packages/frappy/client
|
||||||
|
usr/lib/python3.*/dist-packages/frappy/protocol
|
||||||
|
usr/lib/python3.*/dist-packages/frappy_core-*
|
||||||
|
usr/lib/python3.*/dist-packages/frappy/RELEASE-VERSION
|
||||||
|
lib/systemd
|
||||||
|
var/log/frappy
|
2
debian/frappy-demo.install
vendored
Normal file
2
debian/frappy-demo.install
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
usr/lib/python3.*/dist-packages/frappy_demo
|
||||||
|
usr/lib/python3.*/dist-packages/frappy/__pycache__
|
1
debian/frappy-ess.install
vendored
Normal file
1
debian/frappy-ess.install
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
usr/lib/python3.*/dist-packages/frappy_ess
|
3
debian/frappy-gui.install
vendored
Normal file
3
debian/frappy-gui.install
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
usr/bin/frappy-gui
|
||||||
|
usr/bin/frappy-cfg-editor
|
||||||
|
usr/lib/python3.*/dist-packages/frappy/gui
|
1
debian/frappy-mlz.install
vendored
Normal file
1
debian/frappy-mlz.install
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
usr/lib/python3.*/dist-packages/frappy_mlz
|
1
debian/frappy-psi.install
vendored
Normal file
1
debian/frappy-psi.install
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
usr/lib/python3.*/dist-packages/frappy_psi
|
4
debian/rules
vendored
4
debian/rules
vendored
@ -4,12 +4,12 @@
|
|||||||
# Uncomment this to turn on verbose mode.
|
# Uncomment this to turn on verbose mode.
|
||||||
#export DH_VERBOSE=1
|
#export DH_VERBOSE=1
|
||||||
|
|
||||||
export PYBUILD_NAME=secop
|
export PYBUILD_NAME=frappy
|
||||||
export PYBUILD_TEST_PYTEST=1
|
export PYBUILD_TEST_PYTEST=1
|
||||||
|
|
||||||
override_dh_install:
|
override_dh_install:
|
||||||
rmdir debian/tmp
|
rmdir debian/tmp
|
||||||
mv debian/python3-secop debian/tmp
|
mv debian/python3-frappy debian/tmp
|
||||||
|
|
||||||
dh_install -i -O--buildsystem=pybuild
|
dh_install -i -O--buildsystem=pybuild
|
||||||
dh_missing --fail-missing
|
dh_missing --fail-missing
|
||||||
|
10
debian/secop-core.install
vendored
10
debian/secop-core.install
vendored
@ -1,10 +0,0 @@
|
|||||||
usr/bin/secop-server
|
|
||||||
usr/bin/secop-console
|
|
||||||
usr/lib/python3.*/dist-packages/secop/*.py
|
|
||||||
usr/lib/python3.*/dist-packages/secop/lib
|
|
||||||
usr/lib/python3.*/dist-packages/secop/client
|
|
||||||
usr/lib/python3.*/dist-packages/secop/protocol
|
|
||||||
usr/lib/python3.*/dist-packages/secop_core-*
|
|
||||||
usr/lib/python3.*/dist-packages/secop/RELEASE-VERSION
|
|
||||||
lib/systemd
|
|
||||||
var/log/secop
|
|
2
debian/secop-demo.install
vendored
2
debian/secop-demo.install
vendored
@ -1,2 +0,0 @@
|
|||||||
usr/lib/python3.*/dist-packages/secop_demo
|
|
||||||
usr/lib/python3.*/dist-packages/secop/__pycache__
|
|
1
debian/secop-ess.install
vendored
1
debian/secop-ess.install
vendored
@ -1 +0,0 @@
|
|||||||
usr/lib/python3.*/dist-packages/secop_ess
|
|
3
debian/secop-gui.install
vendored
3
debian/secop-gui.install
vendored
@ -1,3 +0,0 @@
|
|||||||
usr/bin/secop-gui
|
|
||||||
usr/bin/secop-cfg-editor
|
|
||||||
usr/lib/python3.*/dist-packages/secop/gui
|
|
1
debian/secop-mlz.install
vendored
1
debian/secop-mlz.install
vendored
@ -1 +0,0 @@
|
|||||||
usr/lib/python3.*/dist-packages/secop_mlz
|
|
1
debian/secop-psi.install
vendored
1
debian/secop-psi.install
vendored
@ -1 +0,0 @@
|
|||||||
usr/lib/python3.*/dist-packages/secop_psi
|
|
@ -2,3 +2,19 @@ div.wy-nav-content
|
|||||||
{
|
{
|
||||||
max-width: 100% !important;
|
max-width: 100% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* overwrite custom font (to save bandwidth not using a custom font) */
|
||||||
|
body {
|
||||||
|
font-family: "proxima-nova", "Helvetica Neue", Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, .rst-content .toctree-wrapper p.caption, h3, h4, h5, h6, legend {
|
||||||
|
font-family: "ff-tisa-web-pro", "Georgia", Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
Client documentation
|
|
||||||
====================
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 2
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- 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.
|
# sphinx-quickstart on Mon Sep 11 10:58:28 2017.
|
||||||
#
|
#
|
||||||
# This file is execfile()d with the current directory set to its
|
# This file is execfile()d with the current directory set to its
|
||||||
@ -27,7 +27,7 @@ from os import path
|
|||||||
# Add import path for inplace usage
|
# Add import path for inplace usage
|
||||||
sys.path.insert(0, path.abspath(path.join(path.dirname(__file__), '..', '..')))
|
sys.path.insert(0, path.abspath(path.join(path.dirname(__file__), '..', '..')))
|
||||||
|
|
||||||
from secop.version import get_version
|
from frappy.version import get_version
|
||||||
|
|
||||||
|
|
||||||
# -- General configuration ------------------------------------------------
|
# -- General configuration ------------------------------------------------
|
||||||
@ -57,9 +57,9 @@ source_suffix = ['.rst', '.md']
|
|||||||
master_doc = 'index'
|
master_doc = 'index'
|
||||||
|
|
||||||
# General information about the project.
|
# General information about the project.
|
||||||
project = 'SECoP'
|
project = 'Frappy'
|
||||||
#copyright = '2017, Enrico Faulhaber, Markus Zolliker'
|
copyright = '2017-2021, Enrico Faulhaber, Markus Zolliker,'
|
||||||
copyright = '2017, SECoP Committee'
|
#copyright = '2017, SECoP Committee'
|
||||||
author = 'Enrico Faulhaber, Markus Zolliker'
|
author = 'Enrico Faulhaber, Markus Zolliker'
|
||||||
|
|
||||||
# The version info for the project you're documenting, acts as replacement for
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
@ -76,7 +76,7 @@ version = release.split('-')[0]
|
|||||||
#
|
#
|
||||||
# This is also used if you do content translation via gettext catalogs.
|
# This is also used if you do content translation via gettext catalogs.
|
||||||
# Usually you set "language" from the command line for these cases.
|
# Usually you set "language" from the command line for these cases.
|
||||||
language = None
|
language = "en"
|
||||||
|
|
||||||
# List of patterns, relative to source directory, that match files and
|
# List of patterns, relative to source directory, that match files and
|
||||||
# directories to ignore when looking for source files.
|
# directories to ignore when looking for source files.
|
||||||
@ -89,6 +89,10 @@ pygments_style = 'sphinx'
|
|||||||
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
||||||
todo_include_todos = True
|
todo_include_todos = True
|
||||||
|
|
||||||
|
autodoc_default_options = {
|
||||||
|
'member-order': 'bysource',
|
||||||
|
'show-inheritance': True,
|
||||||
|
}
|
||||||
default_role = 'any'
|
default_role = 'any'
|
||||||
|
|
||||||
# -- Options for HTML output ----------------------------------------------
|
# -- Options for HTML output ----------------------------------------------
|
||||||
@ -106,11 +110,6 @@ html_theme = 'sphinx_rtd_theme'
|
|||||||
html_last_updated_fmt = '%Y-%m-%d %H:%M'
|
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 = {}
|
|
||||||
|
|
||||||
# Add any paths that contain custom static files (such as style sheets) here,
|
# 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,
|
# relative to this directory. They are copied after the builtin static files,
|
||||||
@ -136,7 +135,7 @@ html_sidebars = {
|
|||||||
# -- Options for HTMLHelp output ------------------------------------------
|
# -- Options for HTMLHelp output ------------------------------------------
|
||||||
|
|
||||||
# Output file base name for HTML help builder.
|
# Output file base name for HTML help builder.
|
||||||
htmlhelp_basename = 'SECoPdoc'
|
htmlhelp_basename = 'Frappydoc'
|
||||||
|
|
||||||
|
|
||||||
# -- Options for LaTeX output ---------------------------------------------
|
# -- Options for LaTeX output ---------------------------------------------
|
||||||
@ -163,7 +162,7 @@ latex_elements = {
|
|||||||
# (source start file, target name, title,
|
# (source start file, target name, title,
|
||||||
# author, documentclass [howto, manual, or own class]).
|
# author, documentclass [howto, manual, or own class]).
|
||||||
latex_documents = [
|
latex_documents = [
|
||||||
(master_doc, 'SECoP.tex', 'SECoP source documentation',
|
(master_doc, 'Frappy.tex', 'Frappy source documentation',
|
||||||
'Enrico Faulhaber, Markus Zolliker', 'manual'),
|
'Enrico Faulhaber, Markus Zolliker', 'manual'),
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -173,7 +172,7 @@ latex_documents = [
|
|||||||
# One entry per manual page. List of tuples
|
# One entry per manual page. List of tuples
|
||||||
# (source start file, name, description, authors, manual section).
|
# (source start file, name, description, authors, manual section).
|
||||||
man_pages = [
|
man_pages = [
|
||||||
(master_doc, 'secop', 'SECoP source documentation',
|
(master_doc, 'frappy', 'Frappy source documentation',
|
||||||
[author], 1)
|
[author], 1)
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -184,8 +183,8 @@ man_pages = [
|
|||||||
# (source start file, target name, title, author,
|
# (source start file, target name, title, author,
|
||||||
# dir menu entry, description, category)
|
# dir menu entry, description, category)
|
||||||
texinfo_documents = [
|
texinfo_documents = [
|
||||||
(master_doc, 'SECoP', 'SECoP source documentation',
|
(master_doc, 'Frappy', 'Frappy source documentation',
|
||||||
author, 'SECoP', 'One line description of project.',
|
author, 'Frappy', 'One line description of project.',
|
||||||
'Miscellaneous'),
|
'Miscellaneous'),
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -212,4 +211,9 @@ epub_exclude_files = ['search.html']
|
|||||||
|
|
||||||
|
|
||||||
# Example configuration for intersphinx: refer to the Python standard library.
|
# Example configuration for intersphinx: refer to the Python standard library.
|
||||||
intersphinx_mapping = {'https://docs.python.org/': None}
|
intersphinx_mapping = {'https://docs.python.org/3/': None}
|
||||||
|
|
||||||
|
from frappy.lib.classdoc import class_doc_handler
|
||||||
|
|
||||||
|
def setup(app):
|
||||||
|
app.connect('autodoc-process-docstring', class_doc_handler)
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
Demo cryostat
|
|
||||||
=============
|
|
||||||
|
|
||||||
.. automodule:: secop_demo.cryo
|
|
||||||
:members:
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
|||||||
Demo
|
|
||||||
====
|
|
||||||
|
|
||||||
Specific sample environments
|
|
||||||
----------------------------
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 3
|
|
||||||
|
|
||||||
cryo
|
|
||||||
test
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
|||||||
Test devices
|
|
||||||
=============
|
|
||||||
|
|
||||||
.. automodule:: secop_demo.test
|
|
||||||
:members:
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
|||||||
EPICS modules
|
|
||||||
=============
|
|
||||||
|
|
||||||
.. automodule:: secop_ess.epics
|
|
||||||
:members:
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
|||||||
ESS
|
|
||||||
===
|
|
||||||
|
|
||||||
Frameworks
|
|
||||||
----------
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 3
|
|
||||||
|
|
||||||
epics
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
|||||||
Facility specific functionalities
|
|
||||||
=================================
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 3
|
|
||||||
|
|
||||||
demo/index
|
|
||||||
mlz/index
|
|
||||||
ess/index
|
|
@ -1,6 +0,0 @@
|
|||||||
ANTARES magnet (amagnet)
|
|
||||||
========================
|
|
||||||
|
|
||||||
.. automodule:: secop_mlz.amagnet
|
|
||||||
:members:
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
|||||||
Entangle
|
|
||||||
========
|
|
||||||
|
|
||||||
.. automodule:: secop_mlz.entangle
|
|
||||||
:members:
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
|||||||
MLZ
|
|
||||||
===
|
|
||||||
|
|
||||||
Frameworks
|
|
||||||
----------
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 3
|
|
||||||
|
|
||||||
entangle
|
|
||||||
|
|
||||||
|
|
||||||
Specific sample environments
|
|
||||||
----------------------------
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 3
|
|
||||||
|
|
||||||
amagnet
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
10
doc/source/frappy_demo.rst
Normal file
10
doc/source/frappy_demo.rst
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
Demo
|
||||||
|
====
|
||||||
|
|
||||||
|
.. automodule:: frappy_demo.cryo
|
||||||
|
:show-inheritance:
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. automodule:: frappy_demo.test
|
||||||
|
:show-inheritance:
|
||||||
|
:members:
|
9
doc/source/frappy_ess.rst
Normal file
9
doc/source/frappy_ess.rst
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
ESS
|
||||||
|
---
|
||||||
|
|
||||||
|
EPICS
|
||||||
|
.....
|
||||||
|
|
||||||
|
.. automodule:: frappy_ess.epics
|
||||||
|
:show-inheritance:
|
||||||
|
:members:
|
19
doc/source/frappy_mlz.rst
Normal file
19
doc/source/frappy_mlz.rst
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
MLZ
|
||||||
|
---
|
||||||
|
|
||||||
|
Amagnet (Garfield)
|
||||||
|
..................
|
||||||
|
|
||||||
|
.. automodule:: frappy_mlz.amagnet
|
||||||
|
:show-inheritance:
|
||||||
|
:members:
|
||||||
|
|
||||||
|
|
||||||
|
Entangle Framework
|
||||||
|
..................
|
||||||
|
|
||||||
|
.. automodule:: frappy_mlz.entangle
|
||||||
|
:show-inheritance:
|
||||||
|
:members:
|
||||||
|
|
||||||
|
|
27
doc/source/frappy_psi.rst
Normal file
27
doc/source/frappy_psi.rst
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
PSI (SINQ)
|
||||||
|
----------
|
||||||
|
|
||||||
|
CCU4 tutorial example
|
||||||
|
.....................
|
||||||
|
|
||||||
|
.. automodule:: frappy_psi.ccu4
|
||||||
|
:show-inheritance:
|
||||||
|
:members:
|
||||||
|
|
||||||
|
|
||||||
|
PPMS
|
||||||
|
....
|
||||||
|
|
||||||
|
.. automodule:: frappy_psi.ppms
|
||||||
|
:show-inheritance:
|
||||||
|
:members:
|
||||||
|
|
||||||
|
LakeShore 370
|
||||||
|
.............
|
||||||
|
|
||||||
|
Calibrated sensors and control loop not yet supported.
|
||||||
|
|
||||||
|
.. automodule:: frappy_psi.ls370res
|
||||||
|
:show-inheritance:
|
||||||
|
:members:
|
||||||
|
|
@ -1,6 +0,0 @@
|
|||||||
Graphical user interface documentation
|
|
||||||
======================================
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 2
|
|
||||||
|
|
@ -1,19 +1,16 @@
|
|||||||
Welcome to FRAPPY documentation!
|
Frappy Programming Guide
|
||||||
================================
|
========================
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
server/index
|
introduction
|
||||||
client/index
|
tutorial
|
||||||
framework/index
|
reference
|
||||||
gui/index
|
frappy_psi
|
||||||
facility/index
|
frappy_demo
|
||||||
|
frappy_mlz
|
||||||
|
frappy_ess
|
||||||
Indices and tables
|
|
||||||
==================
|
|
||||||
|
|
||||||
* :ref:`genindex`
|
* :ref:`genindex`
|
||||||
* :ref:`modindex`
|
|
||||||
* :ref:`search`
|
|
||||||
|
70
doc/source/introduction.rst
Normal file
70
doc/source/introduction.rst
Normal file
@ -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 <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 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:`frappy.modules.Readable`
|
||||||
|
or :class:`frappy.modules.Drivable`. The parameters are defined in the dict :py:attr:`parameters`, as a
|
||||||
|
class attribute of the extended class, using the :class:`frappy.params.Parameter` constructor, or in case
|
||||||
|
of altering the properties of an inherited parameter, :class:`frappy.params.Override`.
|
||||||
|
|
||||||
|
Parameters usually need a method :meth:`read_<name>()`
|
||||||
|
implementing the code to retrieve their value from the hardware. Writeable parameters
|
||||||
|
(with the argument ``readonly=False``) usually need a method :meth:`write_<name>(<value>)`
|
||||||
|
implementing how they are written to the hardware. Above methods may be omitted, when
|
||||||
|
there is no interaction with the hardware involved.
|
||||||
|
|
89
doc/source/reference.rst
Normal file
89
doc/source/reference.rst
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
Reference
|
||||||
|
---------
|
||||||
|
|
||||||
|
Module Base Classes
|
||||||
|
...................
|
||||||
|
|
||||||
|
.. autodata:: frappy.modules.Done
|
||||||
|
|
||||||
|
.. autoclass:: frappy.modules.Module
|
||||||
|
:members: earlyInit, initModule, startModule
|
||||||
|
|
||||||
|
.. autoclass:: frappy.modules.Readable
|
||||||
|
:members: Status
|
||||||
|
|
||||||
|
.. autoclass:: frappy.modules.Writable
|
||||||
|
|
||||||
|
.. autoclass:: frappy.modules.Drivable
|
||||||
|
:members: Status, isBusy, isDriving, stop
|
||||||
|
|
||||||
|
|
||||||
|
Parameters, Commands and Properties
|
||||||
|
...................................
|
||||||
|
|
||||||
|
.. autoclass:: frappy.params.Parameter
|
||||||
|
.. autoclass:: frappy.params.Command
|
||||||
|
.. autoclass:: frappy.properties.Property
|
||||||
|
.. autoclass:: frappy.modules.Attached
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
|
||||||
|
Datatypes
|
||||||
|
.........
|
||||||
|
|
||||||
|
.. autoclass:: frappy.datatypes.FloatRange
|
||||||
|
.. autoclass:: frappy.datatypes.IntRange
|
||||||
|
.. autoclass:: frappy.datatypes.BoolType
|
||||||
|
.. autoclass:: frappy.datatypes.ScaledInteger
|
||||||
|
.. autoclass:: frappy.datatypes.EnumType
|
||||||
|
.. autoclass:: frappy.datatypes.StringType
|
||||||
|
.. autoclass:: frappy.datatypes.TupleOf
|
||||||
|
.. autoclass:: frappy.datatypes.ArrayOf
|
||||||
|
.. autoclass:: frappy.datatypes.StructOf
|
||||||
|
.. autoclass:: frappy.datatypes.BLOBType
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Communication
|
||||||
|
.............
|
||||||
|
|
||||||
|
.. autoclass:: frappy.modules.Communicator
|
||||||
|
:show-inheritance:
|
||||||
|
:members: communicate
|
||||||
|
|
||||||
|
.. autoclass:: frappy.io.StringIO
|
||||||
|
:show-inheritance:
|
||||||
|
:members: communicate, multicomm
|
||||||
|
|
||||||
|
.. autoclass:: frappy.io.BytesIO
|
||||||
|
:show-inheritance:
|
||||||
|
:members: communicate, multicomm
|
||||||
|
|
||||||
|
.. autoclass:: frappy.io.HasIO
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
.. autoclass:: frappy.rwhandler.ReadHandler
|
||||||
|
:show-inheritance:
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: frappy.rwhandler.CommonReadHandler
|
||||||
|
:show-inheritance:
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: frappy.rwhandler.WriteHandler
|
||||||
|
:show-inheritance:
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: frappy.rwhandler.CommonWriteHandler
|
||||||
|
:show-inheritance:
|
||||||
|
:members:
|
||||||
|
|
||||||
|
|
||||||
|
Exception classes
|
||||||
|
.................
|
||||||
|
|
||||||
|
.. automodule:: frappy.errors
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. include:: server.rst
|
||||||
|
|
72
doc/source/server.rst
Normal file
72
doc/source/server.rst
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
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
|
||||||
|
frappy_psi in the python module file ccu4.py:
|
||||||
|
|
||||||
|
.. code::
|
||||||
|
|
||||||
|
[helevel]
|
||||||
|
class = frappy_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/frappy-server** script.
|
||||||
|
|
||||||
|
.. parsed-literal::
|
||||||
|
|
||||||
|
usage: frappy-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
|
|
||||||
|
|
||||||
|
|
7
doc/source/tutorial.rst
Normal file
7
doc/source/tutorial.rst
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
Tutorial
|
||||||
|
--------
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
tutorial_helevel
|
250
doc/source/tutorial_helevel.rst
Normal file
250
doc/source/tutorial_helevel.rst
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
HeLevel - a Simple 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 *frappy_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``
|
||||||
|
|
||||||
|
``frappy_psi/ccu4.py``:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
# the most common Frappy classes can be imported from frappy.core
|
||||||
|
from frappy.core import Readable, Parameter, FloatRange, BoolType, StringIO, HasIO
|
||||||
|
|
||||||
|
|
||||||
|
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'
|
||||||
|
# 'cid' is a CCU4 command returning the current version prefixed with CCU4
|
||||||
|
identification = [('cid', r'CCU4.*')]
|
||||||
|
|
||||||
|
|
||||||
|
# inheriting HasIO allows us to use the communicate method for talking with the hardware
|
||||||
|
# 'Readable' as base class defines the value and status parameters
|
||||||
|
class HeLevel(HasIO, Readable):
|
||||||
|
"""He Level channel of CCU4"""
|
||||||
|
|
||||||
|
# define the communication class for automatic creation of the IO module
|
||||||
|
ioClass = CCU4IO
|
||||||
|
|
||||||
|
# define or alter the parameters
|
||||||
|
# as Readable.value exists already, we give only the modified property 'unit'
|
||||||
|
value = Parameter(unit='%')
|
||||||
|
|
||||||
|
def read_value(self):
|
||||||
|
# method for reading the main value
|
||||||
|
reply = self.communicate('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 float(txtvalue)
|
||||||
|
|
||||||
|
|
||||||
|
The class :class:`frappy_psi.ccu4.CCU4IO`, an extension of (:class:`frappy.stringio.StringIO`)
|
||||||
|
serves as communication class.
|
||||||
|
|
||||||
|
:Note:
|
||||||
|
|
||||||
|
You might wonder why the parameter *value* is declared here as class attribute.
|
||||||
|
In Python, usually class attributes are used to set a default value which might
|
||||||
|
be overwritten in a method. But class attributes can do more, look for Python
|
||||||
|
descriptors or properties if you are interested in details.
|
||||||
|
In Frappy, the *Parameter* class is a descriptor, which does the magic needed for
|
||||||
|
the SECoP interface. Given ``lev`` as an instance of the class ``HeLevel`` above,
|
||||||
|
``lev.value`` will just return its internal cached value.
|
||||||
|
``lev.value = 85.3`` will try to convert to the data type of the parameter,
|
||||||
|
put it to the internal cache and send a messages to the SECoP clients telling
|
||||||
|
that ``lev.value`` has got a new value.
|
||||||
|
For getting a value from the hardware, you have to call ``lev.read_value()``.
|
||||||
|
Frappy has replaced your version of *read_value* with a wrapped one which
|
||||||
|
also takes care to announce the change to the clients.
|
||||||
|
Even when you did not code this method, Frappy adds it silently, so calling
|
||||||
|
``<module>.read_<parameter>`` will be possible for all parameters declared
|
||||||
|
in a module.
|
||||||
|
|
||||||
|
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.
|
||||||
|
* 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. We do not need to declare the status parameter,
|
||||||
|
as it is inherited from *Readable*. But we declare the new parameters *empty_length*,
|
||||||
|
*full_length* and *sample_rate*, and we have to code the communication and convert
|
||||||
|
the status codes from the hardware to the standard SECoP status codes.
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
...
|
||||||
|
# the first two arguments to Parameter are 'description' and 'datatype'
|
||||||
|
# it is highly recommended to define always the physical unit
|
||||||
|
empty_length = Parameter('warm length when empty', FloatRange(0, 2000, unit='mm'),
|
||||||
|
readonly=False)
|
||||||
|
full_length = Parameter('warm length when full', FloatRange(0, 2000, unit='mm'),
|
||||||
|
readonly=False)
|
||||||
|
sample_rate = Parameter('sample rate', EnumType(slow=0, fast=1), readonly=False)
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
Status = Readable.Status
|
||||||
|
|
||||||
|
# conversion of the code from the CCU4 parameter 'hsf'
|
||||||
|
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.communicate('hsf').split('=')
|
||||||
|
assert name == 'hsf'
|
||||||
|
return self.STATUS_MAP(int(txtvalue))
|
||||||
|
|
||||||
|
def read_empty_length(self):
|
||||||
|
name, txtvalue = self.communicate('hem').split('=')
|
||||||
|
assert name == 'hem'
|
||||||
|
return float(txtvalue)
|
||||||
|
|
||||||
|
def write_empty_length(self, value):
|
||||||
|
name, txtvalue = self.communicate('hem=%g' % value).split('=')
|
||||||
|
assert name == 'hem'
|
||||||
|
return float(txtvalue)
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
Here we start to realize, that we will repeat similar code for other parameters,
|
||||||
|
which means it might be worth to create a *query* method, and then the
|
||||||
|
*read_<param>* and *write_<param>* methods will become shorter:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
class HeLevel(Readable):
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
def query(self, cmd):
|
||||||
|
"""send a query and get the response
|
||||||
|
|
||||||
|
:param cmd: the name of the parameter to query or '<parameter>=<value'
|
||||||
|
for changing a parameter
|
||||||
|
:returns: the (new) value of the parameter
|
||||||
|
"""
|
||||||
|
name, txtvalue = self.communicate(cmd).split('=')
|
||||||
|
assert name == cmd.split('=')[0] # check that we got a reply to our command
|
||||||
|
return float(txtvalue)
|
||||||
|
|
||||||
|
def read_value(self):
|
||||||
|
return self.query('h')
|
||||||
|
|
||||||
|
def read_status(self):
|
||||||
|
return self.STATUS_MAP[int(self.query('hsf'))]
|
||||||
|
|
||||||
|
def read_empty_length(self):
|
||||||
|
return self.query('hem')
|
||||||
|
|
||||||
|
def write_empty_length(self, value):
|
||||||
|
return self.query('hem=%g' % value)
|
||||||
|
|
||||||
|
def read_full_length(self):
|
||||||
|
return self.query('hfu')
|
||||||
|
|
||||||
|
def write_full_length(self, value):
|
||||||
|
return self.query('hfu=%g' % value)
|
||||||
|
|
||||||
|
def read_sample_rate(self):
|
||||||
|
return self.query('hf')
|
||||||
|
|
||||||
|
def write_sample_rate(self, value):
|
||||||
|
return self.query('hf=%d' % value)
|
||||||
|
|
||||||
|
|
||||||
|
:Note:
|
||||||
|
|
||||||
|
It make sense to unify *empty_length* and *full_length* to one parameter *calibration*,
|
||||||
|
as a :class:`frappy.datatypes.StructOf` with members *empty_length* and *full_length*:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
calibration = Parameter(
|
||||||
|
'sensor calibration',
|
||||||
|
StructOf(empty_length=FloatRange(0, 2000, unit='mm'),
|
||||||
|
full_length=FloatRange(0, 2000, unit='mm')),
|
||||||
|
readonly=False)
|
||||||
|
|
||||||
|
For simplicity we stay with two float parameters for this tutorial.
|
||||||
|
|
||||||
|
|
||||||
|
The full documentation of the example can be found here: :class:`frappy_psi.ccu4.HeLevel`
|
||||||
|
|
||||||
|
|
||||||
|
Configuration
|
||||||
|
-------------
|
||||||
|
Before we continue coding, we may try out what we have coded and create a configuration file.
|
||||||
|
The directory tree of the Frappy framework contains the code for all drivers, but the
|
||||||
|
configuration file determines, which code will be loaded when a server is started.
|
||||||
|
We choose the name *example_cryo* and create therefore a configuration file
|
||||||
|
*example_cryo.cfg* in the *cfg* subdirectory:
|
||||||
|
|
||||||
|
``cfg/example_cryo.cfg``:
|
||||||
|
|
||||||
|
.. code:: ini
|
||||||
|
|
||||||
|
[NODE]
|
||||||
|
description = this is an example cryostat for the Frappy tutorial
|
||||||
|
id = example_cryo.psi.ch
|
||||||
|
|
||||||
|
[INTERFACE]
|
||||||
|
uri = tcp://5000
|
||||||
|
|
||||||
|
[helev]
|
||||||
|
description = He level of the cryostat He reservoir
|
||||||
|
class = frappy_psi.ccu4.HeLevel
|
||||||
|
uri = linse-moxa-4.psi.ch:3001
|
||||||
|
empty_length = 380
|
||||||
|
full_length = 0
|
||||||
|
|
||||||
|
A configuration file contains several sections with a header enclosed by 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 contains 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 and the values for
|
||||||
|
configuring the He Level sensor. We might also alter parameter properties, for example we may hide
|
||||||
|
the parameters *empty_length* and *full_length* from the client by defining:
|
||||||
|
|
||||||
|
.. code:: ini
|
||||||
|
|
||||||
|
empty_length.export = False
|
||||||
|
full_length.export = False
|
||||||
|
|
||||||
|
However, we do not put this here, as it is nice to try out changing parameters for a test!
|
||||||
|
|
||||||
|
*to be continued*
|
@ -25,33 +25,33 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import fnmatch
|
|
||||||
from os import path
|
from os import path
|
||||||
|
|
||||||
from secop.lib import getGeneralConfig
|
from frappy.lib import generalConfig
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
normal_dir = sys.argv[1]
|
normal_dir = sys.argv[1]
|
||||||
|
|
||||||
global_config = getGeneralConfig()
|
generalConfig.init()
|
||||||
config_dir = global_config['confdir']
|
config_dir = generalConfig['confdir']
|
||||||
|
|
||||||
secop_unit = '/lib/systemd/system/secop@.service'
|
frappy_unit = '/lib/systemd/system/frappy@.service'
|
||||||
wants_dir = normal_dir + '/secop.target.wants'
|
wants_dir = normal_dir + '/frappy.target.wants'
|
||||||
|
|
||||||
all_servers = [base for (base, ext) in
|
all_servers = [base for (base, ext) in
|
||||||
map(path.splitext, os.listdir(config_dir)) if ext == '.cfg']
|
map(path.splitext, os.listdir(config_dir)) if ext == '.cfg']
|
||||||
all_servers.sort()
|
all_servers.sort()
|
||||||
|
|
||||||
for srv in all_servers:
|
for srv in all_servers:
|
||||||
symlink = '%s/secop@%s.service' % (normal_dir, srv)
|
symlink = '%s/frappy@%s.service' % (normal_dir, srv)
|
||||||
os.symlink(secop_unit, symlink)
|
os.symlink(frappy_unit, symlink)
|
||||||
if not path.isdir(wants_dir):
|
if not path.isdir(wants_dir):
|
||||||
os.mkdir(wants_dir)
|
os.mkdir(wants_dir)
|
||||||
os.symlink(symlink, '%s/%s' % (wants_dir, path.basename(symlink)))
|
os.symlink(symlink, '%s/%s' % (wants_dir, path.basename(symlink)))
|
||||||
|
|
||||||
# the stamp file signals successful run of the generator
|
# the stamp file signals successful run of the generator
|
||||||
open(normal_dir + '/secop.stamp', 'w').close()
|
open(normal_dir + '/frappy.stamp', 'w').close()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
@ -1,10 +1,10 @@
|
|||||||
[Unit]
|
[Unit]
|
||||||
Description=SECoP SEC-node: %i
|
Description=FRAPPY SECoP SEC-node: %i
|
||||||
After=network-online.service
|
After=network-online.service
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=notify
|
Type=notify
|
||||||
ExecStart=/usr/bin/secop-server %I
|
ExecStart=/usr/bin/frappy-server %I
|
||||||
Restart=on-abnormal
|
Restart=on-abnormal
|
||||||
RestartSec=30
|
RestartSec=30
|
||||||
|
|
@ -3,12 +3,12 @@
|
|||||||
block_cipher = None
|
block_cipher = None
|
||||||
|
|
||||||
|
|
||||||
a = Analysis(['bin\\secop-server'],
|
a = Analysis(['bin\\frappy-server'],
|
||||||
pathex=['.'],
|
pathex=['.'],
|
||||||
binaries=[],
|
binaries=[],
|
||||||
datas=[],
|
datas=[],
|
||||||
hiddenimports=['secop.protocol', 'secop.protocol.dispatcher', 'secop.protocol.interface', 'secop.protocol.interface.tcp',
|
hiddenimports=['frappy.protocol', 'frappy.protocol.dispatcher', 'frappy.protocol.interface', 'frappy.protocol.interface.tcp',
|
||||||
'secop_psi.ppmssim', 'secop_psi.ppmswindows', 'secop_psi.ppms'],
|
'frappy_psi.ppmssim', 'frappy_psi.ppmswindows', 'frappy_psi.ppms'],
|
||||||
hookspath=[],
|
hookspath=[],
|
||||||
runtime_hooks=[],
|
runtime_hooks=[],
|
||||||
excludes=[],
|
excludes=[],
|
||||||
@ -22,7 +22,7 @@ exe = EXE(pyz,
|
|||||||
a.scripts,
|
a.scripts,
|
||||||
[],
|
[],
|
||||||
exclude_binaries=True,
|
exclude_binaries=True,
|
||||||
name='secop-server',
|
name='frappy-server',
|
||||||
debug=False,
|
debug=False,
|
||||||
bootloader_ignore_signals=False,
|
bootloader_ignore_signals=False,
|
||||||
strip=False,
|
strip=False,
|
||||||
@ -35,4 +35,4 @@ coll = COLLECT(exe,
|
|||||||
strip=False,
|
strip=False,
|
||||||
upx=True,
|
upx=True,
|
||||||
upx_exclude=[],
|
upx_exclude=[],
|
||||||
name='secop-server')
|
name='frappy-server')
|
@ -18,29 +18,33 @@
|
|||||||
# Module authors:
|
# Module authors:
|
||||||
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||||
# Markus Zolliker <markus.zolliker@psi.ch>
|
# Markus Zolliker <markus.zolliker@psi.ch>
|
||||||
|
# Alexander Zaft <a.zaft@fz-juelich.de>
|
||||||
#
|
#
|
||||||
# *****************************************************************************
|
# *****************************************************************************
|
||||||
"""general SECoP client"""
|
"""general SECoP client"""
|
||||||
|
|
||||||
import time
|
import re
|
||||||
import queue
|
|
||||||
import json
|
import json
|
||||||
from threading import Event, RLock, current_thread
|
import queue
|
||||||
|
import time
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
from threading import Event, RLock, current_thread
|
||||||
|
|
||||||
from secop.lib import mkthread, formatExtendedTraceback, formatExtendedStack
|
import frappy.errors
|
||||||
from secop.lib.asynconn import AsynConn, ConnectionClosed
|
import frappy.params
|
||||||
from secop.datatypes import get_datatype
|
from frappy.datatypes import get_datatype
|
||||||
from secop.protocol.interface import encode_msg_frame, decode_msg
|
from frappy.lib import mkthread
|
||||||
from secop.protocol.messages import REQUEST2REPLY, ERRORPREFIX, EVENTREPLY, WRITEREQUEST, WRITEREPLY, \
|
from frappy.lib.asynconn import AsynConn, ConnectionClosed
|
||||||
READREQUEST, READREPLY, IDENTREQUEST, IDENTPREFIX, ENABLEEVENTSREQUEST, COMMANDREQUEST, \
|
from frappy.protocol.interface import decode_msg, encode_msg_frame
|
||||||
DESCRIPTIONREQUEST, HEARTBEATREQUEST
|
from frappy.protocol.messages import COMMANDREQUEST, \
|
||||||
import secop.errors
|
DESCRIPTIONREQUEST, ENABLEEVENTSREQUEST, ERRORPREFIX, \
|
||||||
import secop.params
|
EVENTREPLY, HEARTBEATREQUEST, IDENTPREFIX, IDENTREQUEST, \
|
||||||
|
READREPLY, READREQUEST, REQUEST2REPLY, WRITEREPLY, WRITEREQUEST
|
||||||
|
|
||||||
# replies to be handled for cache
|
# replies to be handled for cache
|
||||||
UPDATE_MESSAGES = {EVENTREPLY, READREPLY, WRITEREPLY, ERRORPREFIX + READREQUEST, ERRORPREFIX + EVENTREPLY}
|
UPDATE_MESSAGES = {EVENTREPLY, READREPLY, WRITEREPLY, ERRORPREFIX + READREQUEST, ERRORPREFIX + EVENTREPLY}
|
||||||
|
|
||||||
|
VERSIONFMT= re.compile(r'^[^,]*?ISSE[^,]*,SECoP,')
|
||||||
|
|
||||||
class UNREGISTER:
|
class UNREGISTER:
|
||||||
"""a magic value, used a returned value in a callback
|
"""a magic value, used a returned value in a callback
|
||||||
@ -160,7 +164,6 @@ class ProxyClient:
|
|||||||
if not cblist:
|
if not cblist:
|
||||||
self.callbacks[cbname].pop(key)
|
self.callbacks[cbname].pop(key)
|
||||||
|
|
||||||
|
|
||||||
def callback(self, key, cbname, *args):
|
def callback(self, key, cbname, *args):
|
||||||
"""perform callbacks
|
"""perform callbacks
|
||||||
|
|
||||||
@ -243,9 +246,16 @@ class SecopClient(ProxyClient):
|
|||||||
self.secop_version = reply.decode('utf-8')
|
self.secop_version = reply.decode('utf-8')
|
||||||
else:
|
else:
|
||||||
raise self.error_map('HardwareError')('no answer to %s' % IDENTREQUEST)
|
raise self.error_map('HardwareError')('no answer to %s' % IDENTREQUEST)
|
||||||
if not self.secop_version.startswith(IDENTPREFIX):
|
|
||||||
|
if not VERSIONFMT.match(self.secop_version):
|
||||||
raise self.error_map('HardwareError')('bad answer to %s: %r' %
|
raise self.error_map('HardwareError')('bad answer to %s: %r' %
|
||||||
(IDENTREQUEST, self.secop_version))
|
(IDENTREQUEST, self.secop_version))
|
||||||
|
# inform that the other party still uses a legacy identifier
|
||||||
|
# see e.g. Frappy Bug #4659 (https://forge.frm2.tum.de/redmine/issues/4659)
|
||||||
|
if not self.secop_version.startswith(IDENTPREFIX):
|
||||||
|
self.log.warning('SEC-Node replied with legacy identify reply: %s'
|
||||||
|
% self.secop_version)
|
||||||
|
|
||||||
# now its safe to do secop stuff
|
# now its safe to do secop stuff
|
||||||
self._running = True
|
self._running = True
|
||||||
self._rxthread = mkthread(self.__rxthread)
|
self._rxthread = mkthread(self.__rxthread)
|
||||||
@ -317,7 +327,7 @@ class SecopClient(ProxyClient):
|
|||||||
if module_param is not None:
|
if module_param is not None:
|
||||||
if action.startswith(ERRORPREFIX):
|
if action.startswith(ERRORPREFIX):
|
||||||
timestamp = data[2].get('t', None)
|
timestamp = data[2].get('t', None)
|
||||||
readerror = secop.errors.make_secop_error(*data[0:2])
|
readerror = frappy.errors.make_secop_error(*data[0:2])
|
||||||
value = None
|
value = None
|
||||||
else:
|
else:
|
||||||
timestamp = data[1].get('t', None)
|
timestamp = data[1].get('t', None)
|
||||||
@ -356,7 +366,7 @@ class SecopClient(ProxyClient):
|
|||||||
except ConnectionClosed:
|
except ConnectionClosed:
|
||||||
pass
|
pass
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.log.error('rxthread ended with %s' % e)
|
self.log.error('rxthread ended with %r', e)
|
||||||
self._rxthread = None
|
self._rxthread = None
|
||||||
self.disconnect(False)
|
self.disconnect(False)
|
||||||
if self._shutdown:
|
if self._shutdown:
|
||||||
@ -393,7 +403,7 @@ class SecopClient(ProxyClient):
|
|||||||
if time.time() > self.disconnect_time + self.reconnect_timeout:
|
if time.time() > self.disconnect_time + self.reconnect_timeout:
|
||||||
if self.online: # was recently connected
|
if self.online: # was recently connected
|
||||||
self.disconnect_time = 0
|
self.disconnect_time = 0
|
||||||
self.log.warning('can not reconnect to %s (%r)' % (self.nodename, e))
|
self.log.warning('can not reconnect to %s (%r)', self.nodename, e)
|
||||||
self.log.info('continue trying to reconnect')
|
self.log.info('continue trying to reconnect')
|
||||||
# self.log.warning(formatExtendedTraceback())
|
# self.log.warning(formatExtendedTraceback())
|
||||||
self._set_state(False)
|
self._set_state(False)
|
||||||
@ -490,17 +500,15 @@ class SecopClient(ProxyClient):
|
|||||||
|
|
||||||
def _unhandled_message(self, action, ident, data):
|
def _unhandled_message(self, action, ident, data):
|
||||||
if not self.callback(None, 'unhandledMessage', action, ident, data):
|
if not self.callback(None, 'unhandledMessage', action, ident, data):
|
||||||
self.log.warning('unhandled message: %s %s %r' % (action, ident, data))
|
self.log.warning('unhandled message: %s %s %r', action, ident, data)
|
||||||
|
|
||||||
def _set_state(self, online, state=None):
|
def _set_state(self, online, state=None):
|
||||||
# treat reconnecting as online!
|
# remark: reconnecting is treated as online
|
||||||
state = state or self.state
|
|
||||||
self.callback(None, 'nodeStateChange', online, state)
|
|
||||||
for mname in self.modules:
|
|
||||||
self.callback(mname, 'nodeStateChange', online, state)
|
|
||||||
# set online attribute after callbacks -> callback may check for old state
|
|
||||||
self.online = online
|
self.online = online
|
||||||
self.state = state
|
self.state = state or self.state
|
||||||
|
self.callback(None, 'nodeStateChange', self.online, self.state)
|
||||||
|
for mname in self.modules:
|
||||||
|
self.callback(mname, 'nodeStateChange', self.online, self.state)
|
||||||
|
|
||||||
def queue_request(self, action, ident=None, data=None):
|
def queue_request(self, action, ident=None, data=None):
|
||||||
"""make a request"""
|
"""make a request"""
|
||||||
@ -520,7 +528,7 @@ class SecopClient(ProxyClient):
|
|||||||
action, _, data = entry[2] # pylint: disable=unpacking-non-sequence
|
action, _, data = entry[2] # pylint: disable=unpacking-non-sequence
|
||||||
if action.startswith(ERRORPREFIX):
|
if action.startswith(ERRORPREFIX):
|
||||||
errcls = self.error_map(data[0])
|
errcls = self.error_map(data[0])
|
||||||
raise errcls('on SEC-Node: ' + data[1])
|
raise errcls(data[1])
|
||||||
return entry[2] # reply
|
return entry[2] # reply
|
||||||
|
|
||||||
def request(self, action, ident=None, data=None):
|
def request(self, action, ident=None, data=None):
|
||||||
@ -535,7 +543,7 @@ class SecopClient(ProxyClient):
|
|||||||
"""forced read over connection"""
|
"""forced read over connection"""
|
||||||
try:
|
try:
|
||||||
self.request(READREQUEST, self.identifier[module, parameter])
|
self.request(READREQUEST, self.identifier[module, parameter])
|
||||||
except secop.errors.SECoPError:
|
except frappy.errors.SECoPError:
|
||||||
# error reply message is already stored as readerror in cache
|
# error reply message is already stored as readerror in cache
|
||||||
pass
|
pass
|
||||||
return self.cache.get((module, parameter), None)
|
return self.cache.get((module, parameter), None)
|
||||||
@ -563,7 +571,7 @@ class SecopClient(ProxyClient):
|
|||||||
argument = datatype.export_value(argument)
|
argument = datatype.export_value(argument)
|
||||||
else:
|
else:
|
||||||
if argument is not None:
|
if argument is not None:
|
||||||
raise secop.errors.BadValueError('command has no argument')
|
raise frappy.errors.BadValueError('command has no argument')
|
||||||
# pylint: disable=unsubscriptable-object
|
# pylint: disable=unsubscriptable-object
|
||||||
data, qualifiers = self.request(COMMANDREQUEST, self.identifier[module, command], argument)[2]
|
data, qualifiers = self.request(COMMANDREQUEST, self.identifier[module, command], argument)[2]
|
||||||
datatype = self.modules[module]['commands'][command]['datatype'].result
|
datatype = self.modules[module]['commands'][command]['datatype'].result
|
||||||
@ -573,9 +581,9 @@ class SecopClient(ProxyClient):
|
|||||||
|
|
||||||
# the following attributes may be/are intended to be overwritten by a subclass
|
# the following attributes may be/are intended to be overwritten by a subclass
|
||||||
|
|
||||||
ERROR_MAP = secop.errors.EXCEPTIONS
|
ERROR_MAP = frappy.errors.EXCEPTIONS
|
||||||
DEFAULT_EXCEPTION = secop.errors.SECoPError
|
DEFAULT_EXCEPTION = frappy.errors.SECoPError
|
||||||
PREDEFINED_NAMES = set(secop.params.PREDEFINED_ACCESSIBLES)
|
PREDEFINED_NAMES = set(frappy.params.PREDEFINED_ACCESSIBLES)
|
||||||
activate = True
|
activate = True
|
||||||
|
|
||||||
def error_map(self, exc):
|
def error_map(self, exc):
|
295
frappy/client/interactive.py
Normal file
295
frappy/client/interactive.py
Normal file
@ -0,0 +1,295 @@
|
|||||||
|
# -*- 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 <markus.zolliker@psi.ch>
|
||||||
|
#
|
||||||
|
# *****************************************************************************
|
||||||
|
"""simple interactive python client"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import re
|
||||||
|
from queue import Queue
|
||||||
|
from frappy.client import SecopClient
|
||||||
|
from frappy.errors import SECoPError
|
||||||
|
from frappy.datatypes import get_datatype
|
||||||
|
|
||||||
|
USAGE = """
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
from frappy.client.interactive import Client
|
||||||
|
|
||||||
|
client = Client('localhost:5000') # start client.
|
||||||
|
# this connects and creates objects for all SECoP modules in the main namespace
|
||||||
|
|
||||||
|
<module> # list all parameters
|
||||||
|
<module>.<param> = <value> # change parameter
|
||||||
|
<module>(<target>) # set target and wait until not busy
|
||||||
|
# 'status' and 'value' changes are shown every 1 sec
|
||||||
|
client.mininterval = 0.2 # change minimal update interval to 0.2 sec (default is 1 second)
|
||||||
|
|
||||||
|
<module>.watch(1) # watch changes of all parameters of a module
|
||||||
|
<module>.watch(0) # remove all watching
|
||||||
|
<module>.watch(status=1, value=1) # add 'status' and 'value' to watched parameters
|
||||||
|
<module>.watch(value=0) # remove 'value' from watched parameters
|
||||||
|
"""
|
||||||
|
|
||||||
|
main = sys.modules['__main__']
|
||||||
|
|
||||||
|
|
||||||
|
class Logger:
|
||||||
|
def __init__(self, loglevel='info'):
|
||||||
|
func = self.noop
|
||||||
|
for lev in 'debug', 'info', 'warning', 'error':
|
||||||
|
if lev == loglevel:
|
||||||
|
func = self.emit
|
||||||
|
setattr(self, lev, func)
|
||||||
|
self._minute = 0
|
||||||
|
|
||||||
|
def emit(self, fmt, *args, **kwds):
|
||||||
|
now = time.time()
|
||||||
|
minute = now // 60
|
||||||
|
if minute != self._minute:
|
||||||
|
self._minute = minute
|
||||||
|
print(time.strftime('--- %H:%M:%S ---', time.localtime(now)))
|
||||||
|
print('%6.3f' % (now % 60.0), str(fmt) % args)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def noop(fmt, *args, **kwds):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class PrettyFloat(float):
|
||||||
|
"""float with a nicer repr:
|
||||||
|
|
||||||
|
- numbers which are close to a fractional decimal number do not have
|
||||||
|
additional annoying digits
|
||||||
|
- always display a decimal point
|
||||||
|
"""
|
||||||
|
def __repr__(self):
|
||||||
|
result = '%.12g' % self
|
||||||
|
if '.' in result or 'e' in result:
|
||||||
|
return result
|
||||||
|
return result + '.'
|
||||||
|
|
||||||
|
|
||||||
|
class Module:
|
||||||
|
_log_pattern = re.compile('.*')
|
||||||
|
|
||||||
|
def __init__(self, name, secnode):
|
||||||
|
self._name = name
|
||||||
|
self._secnode = secnode
|
||||||
|
self._parameters = list(secnode.modules[name]['parameters'])
|
||||||
|
self._commands = list(secnode.modules[name]['commands'])
|
||||||
|
self._running = None
|
||||||
|
self._status = None
|
||||||
|
props = secnode.modules[name]['properties']
|
||||||
|
self._title = '# %s (%s)' % (props.get('implementation', ''), props.get('interface_classes', [''])[0])
|
||||||
|
|
||||||
|
def _one_line(self, pname, minwid=0):
|
||||||
|
"""return <module>.<param> = <value> truncated to one line"""
|
||||||
|
param = getattr(type(self), pname)
|
||||||
|
try:
|
||||||
|
value = getattr(self, pname)
|
||||||
|
r = param.format(value)
|
||||||
|
except Exception as e:
|
||||||
|
r = repr(e)
|
||||||
|
pname = pname.ljust(minwid)
|
||||||
|
vallen = 113 - len(self._name) - len(pname)
|
||||||
|
if len(r) > vallen:
|
||||||
|
r = r[:vallen - 4] + ' ...'
|
||||||
|
return '%s.%s = %s' % (self._name, pname, r)
|
||||||
|
|
||||||
|
def _isBusy(self):
|
||||||
|
return 300 <= self.status[0] < 400
|
||||||
|
|
||||||
|
def _status_value_update(self, m, p, status, t, e):
|
||||||
|
if self._running:
|
||||||
|
try:
|
||||||
|
self._running.put(True)
|
||||||
|
if self._running and not self._isBusy():
|
||||||
|
self._running.put(False)
|
||||||
|
except TypeError: # may happen when _running is removed during above lines
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _watch_parameter(self, m, pname, *args, forced=False, mininterval=0):
|
||||||
|
"""show parameter update"""
|
||||||
|
pobj = getattr(type(self), pname)
|
||||||
|
if not args:
|
||||||
|
args = self._secnode.cache[self._name, pname]
|
||||||
|
value = args[0]
|
||||||
|
now = time.time()
|
||||||
|
if (value != pobj.prev and now >= pobj.prev_time + mininterval) or forced:
|
||||||
|
self._secnode.log.info('%s', self._one_line(pname))
|
||||||
|
pobj.prev = value
|
||||||
|
pobj.prev_time = now
|
||||||
|
|
||||||
|
def watch(self, *args, **kwds):
|
||||||
|
enabled = {}
|
||||||
|
for arg in args:
|
||||||
|
if arg == 1: # or True
|
||||||
|
enabled.update({k: True for k in self._parameters})
|
||||||
|
elif arg == 0: # or False
|
||||||
|
enabled.update({k: False for k in self._parameters})
|
||||||
|
else:
|
||||||
|
enabled.update(arg)
|
||||||
|
enabled.update(kwds)
|
||||||
|
for pname, enable in enabled.items():
|
||||||
|
self._secnode.unregister_callback((self._name, pname), updateEvent=self._watch_parameter)
|
||||||
|
if enable:
|
||||||
|
self._secnode.register_callback((self._name, pname), updateEvent=self._watch_parameter)
|
||||||
|
|
||||||
|
def read(self, pname='value'):
|
||||||
|
value, _, error = self._secnode.readParameter(self._name, pname)
|
||||||
|
if error:
|
||||||
|
raise error
|
||||||
|
return value
|
||||||
|
|
||||||
|
def __call__(self, target=None):
|
||||||
|
if target is None:
|
||||||
|
return self.read()
|
||||||
|
self.target = target # this sets self._running
|
||||||
|
type(self).value.prev = None # show at least one value
|
||||||
|
show_final_value = True
|
||||||
|
try:
|
||||||
|
while self._running.get():
|
||||||
|
self._watch_parameter(self._name, 'value', mininterval=self._secnode.mininterval)
|
||||||
|
self._watch_parameter(self._name, 'status')
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
self._secnode.log.info('-- interrupted --')
|
||||||
|
self._running = None
|
||||||
|
self._watch_parameter(self._name, 'status')
|
||||||
|
self._secnode.readParameter(self._name, 'value')
|
||||||
|
self._watch_parameter(self._name, 'value', forced=show_final_value)
|
||||||
|
return self.value
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
wid = max(len(k) for k in self._parameters)
|
||||||
|
return '%s\n%s\nCommands: %s' % (
|
||||||
|
self._title,
|
||||||
|
'\n'.join(self._one_line(k, wid) for k in self._parameters),
|
||||||
|
', '.join(k + '()' for k in self._commands))
|
||||||
|
|
||||||
|
def logging(self, level='comlog', pattern='.*'):
|
||||||
|
self._log_pattern = re.compile(pattern)
|
||||||
|
self._secnode.request('logging', self._name, level)
|
||||||
|
|
||||||
|
def handle_log_message_(self, data):
|
||||||
|
if self._log_pattern.match(data):
|
||||||
|
self._secnode.log.info('%s: %r', self._name, data)
|
||||||
|
|
||||||
|
|
||||||
|
class Param:
|
||||||
|
def __init__(self, name, datainfo):
|
||||||
|
self.name = name
|
||||||
|
self.prev = None
|
||||||
|
self.prev_time = 0
|
||||||
|
self.datatype = get_datatype(datainfo)
|
||||||
|
|
||||||
|
def __get__(self, obj, owner):
|
||||||
|
if obj is None:
|
||||||
|
return self
|
||||||
|
value, _, error = obj._secnode.cache[obj._name, self.name]
|
||||||
|
if error:
|
||||||
|
raise error
|
||||||
|
return value
|
||||||
|
|
||||||
|
def __set__(self, obj, value):
|
||||||
|
if self.name == 'target':
|
||||||
|
obj._running = Queue()
|
||||||
|
try:
|
||||||
|
obj._secnode.setParameter(obj._name, self.name, value)
|
||||||
|
except SECoPError as e:
|
||||||
|
obj._secnode.log.error(repr(e))
|
||||||
|
|
||||||
|
def format(self, value):
|
||||||
|
return self.datatype.format_value(value)
|
||||||
|
|
||||||
|
|
||||||
|
class Command:
|
||||||
|
def __init__(self, name, modname, secnode):
|
||||||
|
self.name = name
|
||||||
|
self.modname = modname
|
||||||
|
self.exec = secnode.execCommand
|
||||||
|
|
||||||
|
def call(self, *args, **kwds):
|
||||||
|
if kwds:
|
||||||
|
if args:
|
||||||
|
raise TypeError('mixed arguments forbidden')
|
||||||
|
result, _ = self.exec(self.modname, self.name, kwds)
|
||||||
|
else:
|
||||||
|
result, _ = self.exec(self.modname, self.name, args or None)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def __get__(self, obj, owner=None):
|
||||||
|
if obj is None:
|
||||||
|
return self
|
||||||
|
return self.call
|
||||||
|
|
||||||
|
|
||||||
|
class Client(SecopClient):
|
||||||
|
activate = True
|
||||||
|
secnodes = {}
|
||||||
|
mininterval = 1
|
||||||
|
|
||||||
|
def __init__(self, uri, loglevel='info'):
|
||||||
|
# remove previous client:
|
||||||
|
prev = self.secnodes.pop(uri, None)
|
||||||
|
if prev:
|
||||||
|
prev.log.info('remove previous client to %s', uri)
|
||||||
|
for modname in prev.modules:
|
||||||
|
prevnode = getattr(getattr(main, modname, None), '_secnode', None)
|
||||||
|
if prevnode == prev:
|
||||||
|
prev.log.info('remove previous module %s', modname)
|
||||||
|
delattr(main, modname)
|
||||||
|
prev.disconnect()
|
||||||
|
self.secnodes[uri] = self
|
||||||
|
super().__init__(uri, Logger(loglevel))
|
||||||
|
self.connect()
|
||||||
|
for modname, moddesc in self.modules.items():
|
||||||
|
prev = getattr(main, modname, None)
|
||||||
|
if prev is None:
|
||||||
|
self.log.info('create module %s', modname)
|
||||||
|
else:
|
||||||
|
if getattr(prev, '_secnode', None) is None:
|
||||||
|
self.log.error('skip module %s overwriting a global variable' % modname)
|
||||||
|
continue
|
||||||
|
self.log.info('overwrite module %s', modname)
|
||||||
|
attrs = {}
|
||||||
|
for pname, pinfo in moddesc['parameters'].items():
|
||||||
|
attrs[pname] = Param(pname, pinfo['datainfo'])
|
||||||
|
for cname in moddesc['commands']:
|
||||||
|
attrs[cname] = Command(cname, modname, self)
|
||||||
|
mobj = type('M_%s' % modname, (Module,), attrs)(modname, self)
|
||||||
|
if 'status' in mobj._parameters:
|
||||||
|
self.register_callback((modname, 'status'), updateEvent=mobj._status_value_update)
|
||||||
|
self.register_callback((modname, 'value'), updateEvent=mobj._status_value_update)
|
||||||
|
setattr(main, modname, mobj)
|
||||||
|
self.register_callback(None, self.unhandledMessage)
|
||||||
|
self.log.info('%s', USAGE)
|
||||||
|
|
||||||
|
def unhandledMessage(self, action, ident, data):
|
||||||
|
"""handle logging messages"""
|
||||||
|
if action == 'log':
|
||||||
|
modname = ident.split(':')[0]
|
||||||
|
modobj = getattr(main, modname, None)
|
||||||
|
if modobj:
|
||||||
|
modobj.handle_log_message_(data)
|
||||||
|
return
|
||||||
|
self.log.info('module %s not found', modname)
|
||||||
|
self.log.info('unhandled: %s %s %r', action, ident, data)
|
184
frappy/config.py
Normal file
184
frappy/config.py
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
# -*- 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:
|
||||||
|
# Alexander Zaft <a.zaft@fz-juelich.de>
|
||||||
|
#
|
||||||
|
# *****************************************************************************
|
||||||
|
import os
|
||||||
|
|
||||||
|
from frappy.errors import ConfigError
|
||||||
|
from frappy.lib import generalConfig
|
||||||
|
|
||||||
|
class Undef:
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Node(dict):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
equipment_id,
|
||||||
|
description,
|
||||||
|
interface=None,
|
||||||
|
cls='protocol.dispatcher.Dispatcher',
|
||||||
|
omit_unchanged_within=1.1,
|
||||||
|
**kwds
|
||||||
|
):
|
||||||
|
super().__init__(
|
||||||
|
equipment_id=equipment_id,
|
||||||
|
description=description,
|
||||||
|
interface=interface,
|
||||||
|
cls=cls,
|
||||||
|
omit_unchanged_within=omit_unchanged_within,
|
||||||
|
**kwds
|
||||||
|
)
|
||||||
|
|
||||||
|
class Param(dict):
|
||||||
|
def __init__(self, value=Undef, **kwds):
|
||||||
|
if value is not Undef:
|
||||||
|
kwds['value'] = value
|
||||||
|
super().__init__(**kwds)
|
||||||
|
|
||||||
|
class Group(tuple):
|
||||||
|
def __new__(cls, *args):
|
||||||
|
return super().__new__(cls, args)
|
||||||
|
|
||||||
|
class Mod(dict):
|
||||||
|
def __init__(self, name, cls, description, **kwds):
|
||||||
|
super().__init__(
|
||||||
|
name=name,
|
||||||
|
cls=cls,
|
||||||
|
description=description
|
||||||
|
)
|
||||||
|
# Make parameters out of all keywords
|
||||||
|
groups = {}
|
||||||
|
for key, val in kwds.items():
|
||||||
|
if isinstance(val, Param):
|
||||||
|
self[key] = val
|
||||||
|
elif isinstance(val, Group):
|
||||||
|
groups[key] = val
|
||||||
|
else:
|
||||||
|
# shortcut to only set value
|
||||||
|
self[key] = Param(val)
|
||||||
|
for group, members in groups.items():
|
||||||
|
for member in members:
|
||||||
|
self[member]['group'] = group
|
||||||
|
|
||||||
|
class Collector:
|
||||||
|
def __init__(self, cls):
|
||||||
|
self.list = []
|
||||||
|
self.cls = cls
|
||||||
|
|
||||||
|
def add(self, *args, **kwds):
|
||||||
|
self.list.append(self.cls(*args, **kwds))
|
||||||
|
|
||||||
|
def append(self, mod):
|
||||||
|
self.list.append(mod)
|
||||||
|
|
||||||
|
|
||||||
|
class NodeCollector:
|
||||||
|
def __init__(self):
|
||||||
|
self.node = None
|
||||||
|
|
||||||
|
def add(self, *args, **kwds):
|
||||||
|
if self.node is None:
|
||||||
|
self.node = Node(*args, **kwds)
|
||||||
|
else:
|
||||||
|
raise ConfigError('Only one Node is allowed per file!')
|
||||||
|
|
||||||
|
|
||||||
|
class Config(dict):
|
||||||
|
def __init__(self, node, modules):
|
||||||
|
super().__init__(
|
||||||
|
node=node.node,
|
||||||
|
**{mod['name']: mod for mod in modules.list}
|
||||||
|
)
|
||||||
|
self.module_names = {mod.pop('name') for mod in modules.list}
|
||||||
|
self.ambiguous = set()
|
||||||
|
|
||||||
|
def merge_modules(self, other):
|
||||||
|
""" merges only the modules from 'other' into 'self'"""
|
||||||
|
self.ambiguous |= self.module_names & other.module_names
|
||||||
|
for name, mod in other.items():
|
||||||
|
if name == 'node':
|
||||||
|
continue
|
||||||
|
if name not in self.module_names:
|
||||||
|
self.module_names.add(name)
|
||||||
|
self.modules.append(mod)
|
||||||
|
|
||||||
|
|
||||||
|
def process_file(config_text):
|
||||||
|
node = NodeCollector()
|
||||||
|
mods = Collector(Mod)
|
||||||
|
ns = {'Node': node.add, 'Mod': mods.add, 'Param': Param, 'Command': Param, 'Group': Group}
|
||||||
|
|
||||||
|
# pylint: disable=exec-used
|
||||||
|
exec(config_text, ns)
|
||||||
|
return Config(node, mods)
|
||||||
|
|
||||||
|
|
||||||
|
def to_config_path(cfgfile, log):
|
||||||
|
candidates = [cfgfile + e for e in ['_cfg.py', '.py', '']]
|
||||||
|
if os.sep in cfgfile: # specified as full path
|
||||||
|
filename = cfgfile if os.path.exists(cfgfile) else None
|
||||||
|
else:
|
||||||
|
for filename in [os.path.join(d, candidate)
|
||||||
|
for d in generalConfig.confdir.split(os.pathsep)
|
||||||
|
for candidate in candidates]:
|
||||||
|
if os.path.exists(filename):
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
filename = None
|
||||||
|
|
||||||
|
if filename is None:
|
||||||
|
raise ConfigError("Couldn't find cfg file %r in %s"
|
||||||
|
% (cfgfile, generalConfig.confdir))
|
||||||
|
if not filename.endswith('_cfg.py'):
|
||||||
|
log.warning("Config files should end in '_cfg.py': %s", os.path.basename(filename))
|
||||||
|
log.debug('Using config file %s for %s', filename, cfgfile)
|
||||||
|
return filename
|
||||||
|
|
||||||
|
|
||||||
|
def load_config(cfgfiles, log):
|
||||||
|
"""Load config files.
|
||||||
|
|
||||||
|
Only the node-section of the first config file will be returned.
|
||||||
|
The others will be discarded.
|
||||||
|
Arguments
|
||||||
|
- cfgfiles : str
|
||||||
|
Comma separated list of config-files
|
||||||
|
- log : frappy.logging.Mainlogger
|
||||||
|
Logger aquired from frappy.logging
|
||||||
|
Returns
|
||||||
|
- config: Config
|
||||||
|
merged configuration
|
||||||
|
"""
|
||||||
|
config = None
|
||||||
|
for cfgfile in cfgfiles.split(','):
|
||||||
|
filename = to_config_path(cfgfile, log)
|
||||||
|
log.debug('Parsing config file %s...', filename)
|
||||||
|
with open(filename, 'rb') as f:
|
||||||
|
config_text = f.read()
|
||||||
|
cfg = process_file(config_text)
|
||||||
|
if config:
|
||||||
|
config.merge_modules(cfg)
|
||||||
|
else:
|
||||||
|
config = cfg
|
||||||
|
|
||||||
|
if config.ambiguous:
|
||||||
|
log.warning('ambiguous sections in %s: %r',
|
||||||
|
cfgfiles, list(config.ambiguous))
|
||||||
|
return config
|
@ -23,16 +23,23 @@
|
|||||||
#
|
#
|
||||||
# *****************************************************************************
|
# *****************************************************************************
|
||||||
|
|
||||||
# allow to import the most important classes from 'secop'
|
# allow to import the most important classes from 'frappy'
|
||||||
|
|
||||||
# pylint: disable=unused-import
|
# pylint: disable=unused-import
|
||||||
from secop.datatypes import FloatRange, IntRange, ScaledInteger, \
|
from frappy.datatypes import ArrayOf, BLOBType, BoolType, EnumType, \
|
||||||
BoolType, EnumType, BLOBType, StringType, TupleOf, ArrayOf, StructOf
|
FloatRange, IntRange, ScaledInteger, StringType, StructOf, TupleOf
|
||||||
from secop.lib.enum import Enum
|
from frappy.lib.enum import Enum
|
||||||
from secop.modules import Module, Readable, Writable, Drivable, Communicator, Attached
|
from frappy.modules import Attached, Communicator, \
|
||||||
from secop.properties import Property
|
Done, Drivable, Feature, Module, Readable, Writable, HasAccessibles
|
||||||
from secop.params import Parameter, Command, Override
|
from frappy.params import Command, Parameter
|
||||||
from secop.metaclass import Done
|
from frappy.properties import Property
|
||||||
from secop.iohandler import IOHandler, IOHandlerBase
|
from frappy.proxy import Proxy, SecNode, proxy_class
|
||||||
from secop.stringio import StringIO, HasIodev
|
from frappy.io import HasIO, StringIO, BytesIO, HasIodev # TODO: remove HasIodev (legacy stuff)
|
||||||
from secop.proxy import SecNode, Proxy, proxy_class
|
from frappy.persistent import PersistentMixin, PersistentParam
|
||||||
|
from frappy.rwhandler import ReadHandler, WriteHandler, CommonReadHandler, \
|
||||||
|
CommonWriteHandler, nopoll
|
||||||
|
|
||||||
|
ERROR = Drivable.Status.ERROR
|
||||||
|
WARN = Drivable.Status.WARN
|
||||||
|
BUSY = Drivable.Status.BUSY
|
||||||
|
IDLE = Drivable.Status.IDLE
|
@ -22,28 +22,20 @@
|
|||||||
# *****************************************************************************
|
# *****************************************************************************
|
||||||
"""Define validated data types."""
|
"""Define validated data types."""
|
||||||
|
|
||||||
# pylint: disable=abstract-method
|
# pylint: disable=abstract-method, too-many-lines
|
||||||
|
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
from base64 import b64decode, b64encode
|
from base64 import b64decode, b64encode
|
||||||
|
|
||||||
from secop.errors import ProgrammingError, ProtocolError, BadValueError, ConfigError
|
from frappy.errors import BadValueError, \
|
||||||
from secop.lib import clamp
|
ConfigError, ProgrammingError, ProtocolError
|
||||||
from secop.lib.enum import Enum
|
from frappy.lib import clamp, generalConfig
|
||||||
from secop.parse import Parser
|
from frappy.lib.enum import Enum
|
||||||
from secop.properties import HasProperties, Property
|
from frappy.parse import Parser
|
||||||
|
from frappy.properties import HasProperties, Property
|
||||||
|
|
||||||
|
generalConfig.set_default('lazy_number_validation', False)
|
||||||
# Only export these classes for 'from secop.datatypes import *'
|
|
||||||
__all__ = [
|
|
||||||
'DataType', 'get_datatype',
|
|
||||||
'FloatRange', 'IntRange', 'ScaledInteger',
|
|
||||||
'BoolType', 'EnumType',
|
|
||||||
'BLOBType', 'StringType', 'TextType',
|
|
||||||
'TupleOf', 'ArrayOf', 'StructOf',
|
|
||||||
'CommandType', 'StatusType',
|
|
||||||
]
|
|
||||||
|
|
||||||
# *DEFAULT* limits for IntRange/ScaledIntegers transport serialisation
|
# *DEFAULT* limits for IntRange/ScaledIntegers transport serialisation
|
||||||
DEFAULT_MIN_INT = -16777216
|
DEFAULT_MIN_INT = -16777216
|
||||||
@ -53,18 +45,48 @@ UNLIMITED = 1 << 64 # internal limit for integers, is probably high enough for
|
|||||||
Parser = Parser()
|
Parser = Parser()
|
||||||
|
|
||||||
|
|
||||||
|
class DiscouragedConversion(BadValueError):
|
||||||
|
"""the discouraged conversion string - > float happened"""
|
||||||
|
log_message = True
|
||||||
|
|
||||||
|
|
||||||
|
def shortrepr(value):
|
||||||
|
"""shortened repr for error messages
|
||||||
|
|
||||||
|
avoid lengthy error message in case a value is too complex
|
||||||
|
"""
|
||||||
|
r = repr(value)
|
||||||
|
if len(r) > 40:
|
||||||
|
return r[:40] + '...'
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
# base class for all DataTypes
|
# base class for all DataTypes
|
||||||
class DataType(HasProperties):
|
class DataType(HasProperties):
|
||||||
|
"""base class for all data types"""
|
||||||
IS_COMMAND = False
|
IS_COMMAND = False
|
||||||
unit = ''
|
unit = ''
|
||||||
default = None
|
default = None
|
||||||
|
|
||||||
def __call__(self, value):
|
def __call__(self, value):
|
||||||
"""check if given value (a python obj) is valid for this datatype
|
"""convert given value to our datatype and validate
|
||||||
|
|
||||||
returns the value or raises an appropriate exception"""
|
:param value: the value to be converted
|
||||||
|
:return: the converted type
|
||||||
|
|
||||||
|
check if given value (a python obj) is valid for this datatype,
|
||||||
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def validate(self, value, previous=None):
|
||||||
|
"""convert value to datatype and check for limits
|
||||||
|
|
||||||
|
:param value: the value to be converted
|
||||||
|
:param previous: previous value (used for optional struct members)
|
||||||
|
"""
|
||||||
|
# default: no limits to check
|
||||||
|
return self(value)
|
||||||
|
|
||||||
def from_string(self, text):
|
def from_string(self, text):
|
||||||
"""interprets a given string and returns a validated (internal) value"""
|
"""interprets a given string and returns a validated (internal) value"""
|
||||||
# to evaluate values from configfiles, ui, etc...
|
# to evaluate values from configfiles, ui, etc...
|
||||||
@ -97,11 +119,11 @@ class DataType(HasProperties):
|
|||||||
def set_properties(self, **kwds):
|
def set_properties(self, **kwds):
|
||||||
"""init datatype properties"""
|
"""init datatype properties"""
|
||||||
try:
|
try:
|
||||||
for k,v in kwds.items():
|
for k, v in kwds.items():
|
||||||
self.setProperty(k, v)
|
self.setProperty(k, v)
|
||||||
self.checkProperties()
|
self.checkProperties()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ProgrammingError(str(e))
|
raise ProgrammingError(str(e)) from None
|
||||||
|
|
||||||
def get_info(self, **kwds):
|
def get_info(self, **kwds):
|
||||||
"""prepare dict for export or repr
|
"""prepare dict for export or repr
|
||||||
@ -126,16 +148,20 @@ class DataType(HasProperties):
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def set_main_unit(self, unit):
|
||||||
|
"""replace $ in unit by argument"""
|
||||||
|
|
||||||
|
|
||||||
class Stub(DataType):
|
class Stub(DataType):
|
||||||
"""incomplete datatype, to be replaced with a proper one later during module load
|
"""incomplete datatype, to be replaced with a proper one later during module load
|
||||||
|
|
||||||
this workaround because datatypes need properties with datatypes defined later
|
this workaround because datatypes need properties with datatypes defined later
|
||||||
"""
|
"""
|
||||||
def __init__(self, datatype_name, *args):
|
def __init__(self, datatype_name, *args, **kwds):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.name = datatype_name
|
self.name = datatype_name
|
||||||
self.args = args
|
self.args = args
|
||||||
|
self.kwds = kwds
|
||||||
|
|
||||||
def __call__(self, value):
|
def __call__(self, value):
|
||||||
"""validate"""
|
"""validate"""
|
||||||
@ -150,25 +176,36 @@ class Stub(DataType):
|
|||||||
"""
|
"""
|
||||||
for dtcls in globals().values():
|
for dtcls in globals().values():
|
||||||
if isinstance(dtcls, type) and issubclass(dtcls, DataType):
|
if isinstance(dtcls, type) and issubclass(dtcls, DataType):
|
||||||
for prop in dtcls.properties.values():
|
for prop in dtcls.propertyDict.values():
|
||||||
stub = prop.datatype
|
stub = prop.datatype
|
||||||
if isinstance(stub, cls):
|
if isinstance(stub, cls):
|
||||||
prop.datatype = globals()[stub.name](*stub.args)
|
prop.datatype = globals()[stub.name](*stub.args, **stub.kwds)
|
||||||
|
|
||||||
|
|
||||||
|
class HasUnit:
|
||||||
|
unit = Property('physical unit', Stub('StringType', isUTF8=True), extname='unit', default='')
|
||||||
|
|
||||||
|
def set_main_unit(self, unit):
|
||||||
|
if '$' in self.unit:
|
||||||
|
self.setProperty('unit', self.unit.replace('$', unit))
|
||||||
|
|
||||||
|
|
||||||
# SECoP types:
|
# SECoP types:
|
||||||
|
|
||||||
class FloatRange(DataType):
|
class FloatRange(HasUnit, DataType):
|
||||||
"""Restricted float type"""
|
"""(restricted) float type
|
||||||
properties = {
|
|
||||||
'min': Property('low limit', Stub('FloatRange'), extname='min', default=-sys.float_info.max),
|
:param minval: (property **min**)
|
||||||
'max': Property('high limit', Stub('FloatRange'), extname='max', default=sys.float_info.max),
|
:param maxval: (property **max**)
|
||||||
'unit': Property('physical unit', Stub('StringType'), extname='unit', default=''),
|
:param kwds: any of the properties below
|
||||||
'fmtstr': Property('format string', Stub('StringType'), extname='fmtstr', default='%g'),
|
"""
|
||||||
'absolute_resolution': Property('absolute resolution', Stub('FloatRange', 0),
|
min = Property('low limit', Stub('FloatRange'), extname='min', default=-sys.float_info.max)
|
||||||
extname='absolute_resolution', default=0.0),
|
max = Property('high limit', Stub('FloatRange'), extname='max', default=sys.float_info.max)
|
||||||
'relative_resolution': Property('relative resolution', Stub('FloatRange', 0),
|
fmtstr = Property('format string', Stub('StringType'), extname='fmtstr', default='%g')
|
||||||
extname='relative_resolution', default=1.2e-7),
|
absolute_resolution = Property('absolute resolution', Stub('FloatRange', 0),
|
||||||
}
|
extname='absolute_resolution', default=0.0)
|
||||||
|
relative_resolution = Property('relative resolution', Stub('FloatRange', 0),
|
||||||
|
extname='relative_resolution', default=1.2e-7)
|
||||||
|
|
||||||
def __init__(self, minval=None, maxval=None, **kwds):
|
def __init__(self, minval=None, maxval=None, **kwds):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@ -186,18 +223,28 @@ class FloatRange(DataType):
|
|||||||
return self.get_info(type='double')
|
return self.get_info(type='double')
|
||||||
|
|
||||||
def __call__(self, value):
|
def __call__(self, value):
|
||||||
|
try:
|
||||||
|
value += 0.0 # do not accept strings here
|
||||||
|
except Exception:
|
||||||
try:
|
try:
|
||||||
value = float(value)
|
value = float(value)
|
||||||
except Exception:
|
except Exception:
|
||||||
raise BadValueError('Can not __call__ %r to float' % value)
|
raise BadValueError('can not convert %s to a float' % shortrepr(value)) from None
|
||||||
# map +/-infty to +/-max possible number
|
if not generalConfig.lazy_number_validation:
|
||||||
value = clamp(-sys.float_info.max, value, sys.float_info.max)
|
raise DiscouragedConversion('automatic string to float conversion no longer supported') from None
|
||||||
|
|
||||||
# now check the limits
|
# map +/-infty to +/-max possible number
|
||||||
|
return clamp(-sys.float_info.max, value, sys.float_info.max)
|
||||||
|
|
||||||
|
def validate(self, value, previous=None):
|
||||||
|
# convert
|
||||||
|
value = self(value)
|
||||||
|
# check the limits
|
||||||
prec = max(abs(value * self.relative_resolution), self.absolute_resolution)
|
prec = max(abs(value * self.relative_resolution), self.absolute_resolution)
|
||||||
if self.min - prec <= value <= self.max + prec:
|
if self.min - prec <= value <= self.max + prec:
|
||||||
return min(max(value, self.min), self.max)
|
# silently clamp when outside by not more than prec
|
||||||
raise BadValueError('%.14g should be a float between %.14g and %.14g' %
|
return clamp(self.min, value, self.max)
|
||||||
|
raise BadValueError('%.14g must be between %d and %d' %
|
||||||
(value, self.min, self.max))
|
(value, self.min, self.max))
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
@ -206,7 +253,7 @@ class FloatRange(DataType):
|
|||||||
hints['minval'] = hints.pop('min')
|
hints['minval'] = hints.pop('min')
|
||||||
if 'max' in hints:
|
if 'max' in hints:
|
||||||
hints['maxval'] = hints.pop('max')
|
hints['maxval'] = hints.pop('max')
|
||||||
return 'FloatRange(%s)' % (', '.join('%s=%r' % (k,v) for k,v in hints.items()))
|
return 'FloatRange(%s)' % (', '.join('%s=%r' % (k, v) for k, v in hints.items()))
|
||||||
|
|
||||||
def export_value(self, value):
|
def export_value(self, value):
|
||||||
"""returns a python object fit for serialisation"""
|
"""returns a python object fit for serialisation"""
|
||||||
@ -231,18 +278,20 @@ class FloatRange(DataType):
|
|||||||
if not isinstance(other, (FloatRange, ScaledInteger)):
|
if not isinstance(other, (FloatRange, ScaledInteger)):
|
||||||
raise BadValueError('incompatible datatypes')
|
raise BadValueError('incompatible datatypes')
|
||||||
# avoid infinity
|
# avoid infinity
|
||||||
other(max(sys.float_info.min, self.min))
|
other.validate(max(sys.float_info.min, self.min))
|
||||||
other(min(sys.float_info.max, self.max))
|
other.validate(min(sys.float_info.max, self.max))
|
||||||
|
|
||||||
|
|
||||||
class IntRange(DataType):
|
class IntRange(DataType):
|
||||||
"""Restricted int type"""
|
"""restricted int type
|
||||||
properties = {
|
|
||||||
'min': Property('minimum value', Stub('IntRange', -UNLIMITED, UNLIMITED), extname='min', mandatory=True),
|
:param minval: (property **min**)
|
||||||
'max': Property('maximum value', Stub('IntRange', -UNLIMITED, UNLIMITED), extname='max', mandatory=True),
|
:param maxval: (property **max**)
|
||||||
|
"""
|
||||||
|
min = Property('minimum value', Stub('IntRange', -UNLIMITED, UNLIMITED), extname='min', mandatory=True)
|
||||||
|
max = Property('maximum value', Stub('IntRange', -UNLIMITED, UNLIMITED), extname='max', mandatory=True)
|
||||||
# a unit on an int is now allowed in SECoP, but do we need them in Frappy?
|
# a unit on an int is now allowed in SECoP, but do we need them in Frappy?
|
||||||
# 'unit': Property('physical unit', StringType(), extname='unit', default=''),
|
# unit = Property('physical unit', StringType(), extname='unit', default='')
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, minval=None, maxval=None):
|
def __init__(self, minval=None, maxval=None):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@ -257,18 +306,37 @@ class IntRange(DataType):
|
|||||||
return self.get_info(type='int')
|
return self.get_info(type='int')
|
||||||
|
|
||||||
def __call__(self, value):
|
def __call__(self, value):
|
||||||
|
try:
|
||||||
|
fvalue = value + 0.0 # do not accept strings here
|
||||||
|
value = int(value)
|
||||||
|
except Exception:
|
||||||
try:
|
try:
|
||||||
fvalue = float(value)
|
fvalue = float(value)
|
||||||
value = int(value)
|
value = int(value)
|
||||||
if not self.min <= value <= self.max or round(fvalue) != fvalue:
|
|
||||||
raise BadValueError('%r should be an int between %d and %d' %
|
|
||||||
(value, self.min, self.max))
|
|
||||||
return value
|
|
||||||
except Exception:
|
except Exception:
|
||||||
raise BadValueError('Can not convert %r to int' % value)
|
raise BadValueError('can not convert %s to an int' % shortrepr(value)) from None
|
||||||
|
if not generalConfig.lazy_number_validation:
|
||||||
|
raise DiscouragedConversion('automatic string to float conversion no longer supported') from None
|
||||||
|
if round(fvalue) != fvalue:
|
||||||
|
raise BadValueError('%r should be an int')
|
||||||
|
return value
|
||||||
|
|
||||||
|
def validate(self, value, previous=None):
|
||||||
|
# convert
|
||||||
|
value = self(value)
|
||||||
|
# check the limits
|
||||||
|
if self.min <= value <= self.max:
|
||||||
|
return value
|
||||||
|
raise BadValueError('%r must be between %d and %d' %
|
||||||
|
(value, self.min, self.max))
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return 'IntRange(%d, %d)' % (self.min, self.max)
|
args = (self.min, self.max)
|
||||||
|
if args[1] == DEFAULT_MAX_INT:
|
||||||
|
args = args[:1]
|
||||||
|
if args[0] == DEFAULT_MIN_INT:
|
||||||
|
args = ()
|
||||||
|
return 'IntRange%s' % repr(args)
|
||||||
|
|
||||||
def export_value(self, value):
|
def export_value(self, value):
|
||||||
"""returns a python object fit for serialisation"""
|
"""returns a python object fit for serialisation"""
|
||||||
@ -286,38 +354,43 @@ class IntRange(DataType):
|
|||||||
return '%d' % value
|
return '%d' % value
|
||||||
|
|
||||||
def compatible(self, other):
|
def compatible(self, other):
|
||||||
if isinstance(other, IntRange):
|
if isinstance(other, (IntRange, FloatRange, ScaledInteger)):
|
||||||
other(self.min)
|
other.validate(self.min)
|
||||||
other(self.max)
|
other.validate(self.max)
|
||||||
return
|
return
|
||||||
# this will accept some EnumType, BoolType
|
if isinstance(other, (EnumType, BoolType)):
|
||||||
|
# the following loop will not cycle more than the number of Enum elements
|
||||||
for i in range(self.min, self.max + 1):
|
for i in range(self.min, self.max + 1):
|
||||||
other(i)
|
other(i)
|
||||||
|
raise BadValueError('incompatible datatypes')
|
||||||
|
|
||||||
|
|
||||||
class ScaledInteger(DataType):
|
class ScaledInteger(HasUnit, DataType):
|
||||||
"""Scaled integer int type
|
"""scaled integer (= fixed resolution float) type
|
||||||
|
|
||||||
note: limits are for the scaled value (i.e. the internal value)
|
:param minval: (property **min**)
|
||||||
the scale is only used for calculating to/from transport serialisation"""
|
:param maxval: (property **max**)
|
||||||
properties = {
|
:param kwds: any of the properties below
|
||||||
'scale': Property('scale factor', FloatRange(sys.float_info.min), extname='scale', mandatory=True),
|
|
||||||
'min': Property('low limit', FloatRange(), extname='min', mandatory=True),
|
note: limits are for the scaled float value
|
||||||
'max': Property('high limit', FloatRange(), extname='max', mandatory=True),
|
the scale is only used for calculating to/from transport serialisation
|
||||||
'unit': Property('physical unit', Stub('StringType'), extname='unit', default=''),
|
"""
|
||||||
'fmtstr': Property('format string', Stub('StringType'), extname='fmtstr', default='%g'),
|
scale = Property('scale factor', FloatRange(sys.float_info.min), extname='scale', mandatory=True)
|
||||||
'absolute_resolution': Property('absolute resolution', FloatRange(0),
|
min = Property('low limit', FloatRange(), extname='min', mandatory=True)
|
||||||
extname='absolute_resolution', default=0.0),
|
max = Property('high limit', FloatRange(), extname='max', mandatory=True)
|
||||||
'relative_resolution': Property('relative resolution', FloatRange(0),
|
fmtstr = Property('format string', Stub('StringType'), extname='fmtstr', default='%g')
|
||||||
extname='relative_resolution', default=1.2e-7),
|
absolute_resolution = Property('absolute resolution', FloatRange(0),
|
||||||
}
|
extname='absolute_resolution', default=0.0)
|
||||||
|
relative_resolution = Property('relative resolution', FloatRange(0),
|
||||||
|
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, **kwds):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
scale = float(scale)
|
scale = float(scale)
|
||||||
if absolute_resolution is None:
|
if absolute_resolution is None:
|
||||||
absolute_resolution = scale
|
absolute_resolution = scale
|
||||||
self.set_properties(scale=scale,
|
self.set_properties(
|
||||||
|
scale=scale,
|
||||||
min=DEFAULT_MIN_INT * scale if minval is None else float(minval),
|
min=DEFAULT_MIN_INT * scale if minval is None else float(minval),
|
||||||
max=DEFAULT_MAX_INT * scale if maxval is None else float(maxval),
|
max=DEFAULT_MAX_INT * scale if maxval is None else float(maxval),
|
||||||
absolute_resolution=absolute_resolution,
|
absolute_resolution=absolute_resolution,
|
||||||
@ -348,35 +421,40 @@ class ScaledInteger(DataType):
|
|||||||
|
|
||||||
def export_datatype(self):
|
def export_datatype(self):
|
||||||
return self.get_info(type='scaled',
|
return self.get_info(type='scaled',
|
||||||
min = int((self.min + self.scale * 0.5) // self.scale),
|
min=int(round(self.min / self.scale)),
|
||||||
max = int((self.max + self.scale * 0.5) // self.scale))
|
max=int(round(self.max / self.scale)))
|
||||||
|
|
||||||
def __call__(self, value):
|
def __call__(self, value):
|
||||||
|
try:
|
||||||
|
value += 0.0 # do not accept strings here
|
||||||
|
except Exception:
|
||||||
try:
|
try:
|
||||||
value = float(value)
|
value = float(value)
|
||||||
except Exception:
|
except Exception:
|
||||||
raise BadValueError('Can not convert %r to float' % value)
|
raise BadValueError('can not convert %s to float' % shortrepr(value)) from None
|
||||||
prec = max(self.scale, abs(value * self.relative_resolution),
|
if not generalConfig.lazy_number_validation:
|
||||||
self.absolute_resolution)
|
raise DiscouragedConversion('automatic string to float conversion no longer supported') from None
|
||||||
if self.min - prec <= value <= self.max + prec:
|
intval = int(round(value / self.scale))
|
||||||
value = min(max(value, self.min), self.max)
|
return float(intval * self.scale) # return 'actual' value (which is more discrete than a float)
|
||||||
else:
|
|
||||||
raise BadValueError('%g should be a float between %g and %g' %
|
def validate(self, value, previous=None):
|
||||||
|
# convert
|
||||||
|
result = self(value)
|
||||||
|
if self.min - self.scale < value < self.max + self.scale:
|
||||||
|
# silently clamp when outside by not more than self.scale
|
||||||
|
return clamp(self(self.min), result, self(self.max))
|
||||||
|
raise BadValueError('%.14g must be between between %g and %g' %
|
||||||
(value, self.min, self.max))
|
(value, self.min, self.max))
|
||||||
intval = int((value + self.scale * 0.5) // self.scale)
|
|
||||||
value = float(intval * self.scale)
|
|
||||||
return value # return 'actual' value (which is more discrete than a float)
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
hints = self.get_info(scale=float('%g' % self.scale),
|
hints = self.get_info(scale=float('%g' % self.scale),
|
||||||
min = int((self.min + self.scale * 0.5) // self.scale),
|
min=int(round(self.min / self.scale)),
|
||||||
max = int((self.max + self.scale * 0.5) // self.scale))
|
max=int(round(self.max / self.scale)))
|
||||||
return 'ScaledInteger(%s)' % (', '.join('%s=%r' % kv for kv in hints.items()))
|
return 'ScaledInteger(%s)' % (', '.join('%s=%r' % kv for kv in hints.items()))
|
||||||
|
|
||||||
def export_value(self, value):
|
def export_value(self, value):
|
||||||
"""returns a python object fit for serialisation"""
|
"""returns a python object fit for serialisation"""
|
||||||
# note: round behaves different in Py2 vs. Py3, so use floor division
|
return int(round(value / self.scale))
|
||||||
return int((value + self.scale * 0.5) // self.scale)
|
|
||||||
|
|
||||||
def import_value(self, value):
|
def import_value(self, value):
|
||||||
"""returns a python object from serialisation"""
|
"""returns a python object from serialisation"""
|
||||||
@ -396,18 +474,24 @@ class ScaledInteger(DataType):
|
|||||||
def compatible(self, other):
|
def compatible(self, other):
|
||||||
if not isinstance(other, (FloatRange, ScaledInteger)):
|
if not isinstance(other, (FloatRange, ScaledInteger)):
|
||||||
raise BadValueError('incompatible datatypes')
|
raise BadValueError('incompatible datatypes')
|
||||||
other(self.min)
|
other.validate(self.min)
|
||||||
other(self.max)
|
other.validate(self.max)
|
||||||
|
|
||||||
|
|
||||||
class EnumType(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 dict or None when using kwds only
|
||||||
|
:param kwds: (additional) members
|
||||||
|
"""
|
||||||
|
def __init__(self, enum_or_name='', members=None, **kwds):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
if 'members' in kwds:
|
if members is not None:
|
||||||
kwds = dict(kwds)
|
kwds.update(members)
|
||||||
kwds.update(kwds['members'])
|
if isinstance(enum_or_name, str):
|
||||||
kwds.pop('members')
|
self._enum = Enum(enum_or_name, kwds) # allow 'self' as name
|
||||||
|
else:
|
||||||
self._enum = Enum(enum_or_name, **kwds)
|
self._enum = Enum(enum_or_name, **kwds)
|
||||||
self.default = self._enum[self._enum.members[0]]
|
self.default = self._enum[self._enum.members[0]]
|
||||||
|
|
||||||
@ -416,10 +500,11 @@ class EnumType(DataType):
|
|||||||
return EnumType(self._enum)
|
return EnumType(self._enum)
|
||||||
|
|
||||||
def export_datatype(self):
|
def export_datatype(self):
|
||||||
return {'type': 'enum', 'members':dict((m.name, m.value) for m in self._enum.members)}
|
return {'type': 'enum', 'members': dict((m.name, m.value) for m in self._enum.members)}
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "EnumType(%r, %s)" % (self._enum.name, ', '.join('%s=%d' %(m.name, m.value) for m in self._enum.members))
|
return "EnumType(%r, %s)" % (self._enum.name,
|
||||||
|
', '.join('%s=%d' % (m.name, m.value) for m in self._enum.members))
|
||||||
|
|
||||||
def export_value(self, value):
|
def export_value(self, value):
|
||||||
"""returns a python object fit for serialisation"""
|
"""returns a python object fit for serialisation"""
|
||||||
@ -434,7 +519,7 @@ class EnumType(DataType):
|
|||||||
try:
|
try:
|
||||||
return self._enum[value]
|
return self._enum[value]
|
||||||
except (KeyError, TypeError): # TypeError will be raised when value is not hashable
|
except (KeyError, TypeError): # TypeError will be raised when value is not hashable
|
||||||
raise BadValueError('%r is not a member of enum %r' % (value, self._enum))
|
raise BadValueError('%s is not a member of enum %r' % (shortrepr(value), self._enum)) from None
|
||||||
|
|
||||||
def from_string(self, text):
|
def from_string(self, text):
|
||||||
return self(text)
|
return self(text)
|
||||||
@ -442,18 +527,24 @@ class EnumType(DataType):
|
|||||||
def format_value(self, value, unit=None):
|
def format_value(self, value, unit=None):
|
||||||
return '%s<%s>' % (self._enum[value].name, self._enum[value].value)
|
return '%s<%s>' % (self._enum[value].name, self._enum[value].value)
|
||||||
|
|
||||||
|
def set_name(self, name):
|
||||||
|
self._enum.name = name
|
||||||
|
|
||||||
def compatible(self, other):
|
def compatible(self, other):
|
||||||
for m in self._enum.members:
|
for m in self._enum.members:
|
||||||
other(m)
|
other(m)
|
||||||
|
|
||||||
|
|
||||||
class BLOBType(DataType):
|
class BLOBType(DataType):
|
||||||
properties = {
|
"""binary large object
|
||||||
'minbytes': Property('minimum number of bytes', IntRange(0), extname='minbytes',
|
|
||||||
default=0),
|
internally treated as bytes
|
||||||
'maxbytes': Property('maximum number of bytes', IntRange(0), extname='maxbytes',
|
"""
|
||||||
mandatory=True),
|
|
||||||
}
|
minbytes = Property('minimum number of bytes', IntRange(0), extname='minbytes',
|
||||||
|
default=0)
|
||||||
|
maxbytes = Property('maximum number of bytes', IntRange(0), extname='maxbytes',
|
||||||
|
mandatory=True)
|
||||||
|
|
||||||
def __init__(self, minbytes=0, maxbytes=None):
|
def __init__(self, minbytes=0, maxbytes=None):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@ -476,7 +567,7 @@ class BLOBType(DataType):
|
|||||||
def __call__(self, value):
|
def __call__(self, value):
|
||||||
"""return the validated (internal) value or raise"""
|
"""return the validated (internal) value or raise"""
|
||||||
if not isinstance(value, bytes):
|
if not isinstance(value, bytes):
|
||||||
raise BadValueError('%s has the wrong type!' % repr(value))
|
raise BadValueError('%s must be of type bytes' % shortrepr(value))
|
||||||
size = len(value)
|
size = len(value)
|
||||||
if size < self.minbytes:
|
if size < self.minbytes:
|
||||||
raise BadValueError(
|
raise BadValueError(
|
||||||
@ -507,18 +598,20 @@ class BLOBType(DataType):
|
|||||||
if self.minbytes < other.minbytes or self.maxbytes > other.maxbytes:
|
if self.minbytes < other.minbytes or self.maxbytes > other.maxbytes:
|
||||||
raise BadValueError('incompatible datatypes')
|
raise BadValueError('incompatible datatypes')
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
raise BadValueError('incompatible datatypes')
|
raise BadValueError('incompatible datatypes') from None
|
||||||
|
|
||||||
|
|
||||||
class StringType(DataType):
|
class StringType(DataType):
|
||||||
properties = {
|
"""string
|
||||||
'minchars': Property('minimum number of character points', IntRange(0, UNLIMITED),
|
|
||||||
extname='minchars', default=0),
|
for parameters see properties below
|
||||||
'maxchars': Property('maximum number of character points', IntRange(0, UNLIMITED),
|
"""
|
||||||
extname='maxchars', default=UNLIMITED),
|
minchars = Property('minimum number of character points', IntRange(0, UNLIMITED),
|
||||||
'isUTF8': Property('flag telling whether encoding is UTF-8 instead of ASCII',
|
extname='minchars', default=0)
|
||||||
Stub('BoolType'), extname='isUTF8', default=False),
|
maxchars = Property('maximum number of character points', IntRange(0, UNLIMITED),
|
||||||
}
|
extname='maxchars', default=UNLIMITED)
|
||||||
|
isUTF8 = Property('flag telling whether encoding is UTF-8 instead of ASCII',
|
||||||
|
Stub('BoolType'), extname='isUTF8', default=False)
|
||||||
|
|
||||||
def __init__(self, minchars=0, maxchars=None, **kwds):
|
def __init__(self, minchars=0, maxchars=None, **kwds):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@ -539,19 +632,19 @@ class StringType(DataType):
|
|||||||
def __call__(self, value):
|
def __call__(self, value):
|
||||||
"""return the validated (internal) value or raise"""
|
"""return the validated (internal) value or raise"""
|
||||||
if not isinstance(value, str):
|
if not isinstance(value, str):
|
||||||
raise BadValueError('%s has the wrong type!' % repr(value))
|
raise BadValueError('%s has the wrong type!' % shortrepr(value))
|
||||||
if not self.isUTF8:
|
if not self.isUTF8:
|
||||||
try:
|
try:
|
||||||
value.encode('ascii')
|
value.encode('ascii')
|
||||||
except UnicodeEncodeError:
|
except UnicodeEncodeError:
|
||||||
raise BadValueError('%r contains non-ascii character!' % value)
|
raise BadValueError('%s contains non-ascii character!' % shortrepr(value)) from None
|
||||||
size = len(value)
|
size = len(value)
|
||||||
if size < self.minchars:
|
if size < self.minchars:
|
||||||
raise BadValueError(
|
raise BadValueError(
|
||||||
'%r must be at least %d bytes long!' % (value, self.minchars))
|
'%s must be at least %d chars long!' % (shortrepr(value), self.minchars))
|
||||||
if size > self.maxchars:
|
if size > self.maxchars:
|
||||||
raise BadValueError(
|
raise BadValueError(
|
||||||
'%r must be at most %d bytes long!' % (value, self.maxchars))
|
'%s must be at most %d chars long!' % (shortrepr(value), self.maxchars))
|
||||||
if '\0' in value:
|
if '\0' in value:
|
||||||
raise BadValueError(
|
raise BadValueError(
|
||||||
'Strings are not allowed to embed a \\0! Use a Blob instead!')
|
'Strings are not allowed to embed a \\0! Use a Blob instead!')
|
||||||
@ -578,23 +671,24 @@ class StringType(DataType):
|
|||||||
self.isUTF8 > other.isUTF8:
|
self.isUTF8 > other.isUTF8:
|
||||||
raise BadValueError('incompatible datatypes')
|
raise BadValueError('incompatible datatypes')
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
raise BadValueError('incompatible datatypes')
|
raise BadValueError('incompatible datatypes') from None
|
||||||
|
|
||||||
|
|
||||||
# TextType is a special StringType intended for longer texts (i.e. embedding \n),
|
# TextType is a special StringType intended for longer texts (i.e. embedding \n),
|
||||||
# whereas StringType is supposed to not contain '\n'
|
# whereas StringType is supposed to not contain '\n'
|
||||||
# unfortunately, SECoP makes no distinction here....
|
# 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
|
# 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):
|
class TextType(StringType):
|
||||||
def __init__(self, maxchars=None):
|
def __init__(self, maxchars=None):
|
||||||
if maxchars is None:
|
if maxchars is None:
|
||||||
maxchars = UNLIMITED
|
maxchars = UNLIMITED
|
||||||
super(TextType, self).__init__(0, maxchars)
|
super().__init__(0, maxchars)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
if self.maxchars == UNLIMITED:
|
if self.maxchars == UNLIMITED:
|
||||||
return 'TextType()'
|
return 'TextType()'
|
||||||
return 'TextType(%d)' % (self.maxchars)
|
return 'TextType(%d)' % self.maxchars
|
||||||
|
|
||||||
def copy(self):
|
def copy(self):
|
||||||
# DataType.copy will not work, because it is exported as 'string'
|
# DataType.copy will not work, because it is exported as 'string'
|
||||||
@ -602,6 +696,7 @@ class TextType(StringType):
|
|||||||
|
|
||||||
|
|
||||||
class BoolType(DataType):
|
class BoolType(DataType):
|
||||||
|
"""boolean"""
|
||||||
default = False
|
default = False
|
||||||
|
|
||||||
def export_datatype(self):
|
def export_datatype(self):
|
||||||
@ -616,7 +711,7 @@ class BoolType(DataType):
|
|||||||
return False
|
return False
|
||||||
if value in [1, '1', 'True', 'true', 'yes', 'on', True]:
|
if value in [1, '1', 'True', 'true', 'yes', 'on', True]:
|
||||||
return True
|
return True
|
||||||
raise BadValueError('%r is not a boolean value!' % value)
|
raise BadValueError('%s is not a boolean value!' % shortrepr(value))
|
||||||
|
|
||||||
def export_value(self, value):
|
def export_value(self, value):
|
||||||
"""returns a python object fit for serialisation"""
|
"""returns a python object fit for serialisation"""
|
||||||
@ -646,12 +741,14 @@ Stub.fix_datatypes()
|
|||||||
|
|
||||||
|
|
||||||
class ArrayOf(DataType):
|
class ArrayOf(DataType):
|
||||||
properties = {
|
"""data structure with fields of homogeneous type
|
||||||
'minlen': Property('minimum number of elements', IntRange(0), extname='minlen',
|
|
||||||
default=0),
|
:param members: the datatype of the elements
|
||||||
'maxlen': Property('maximum number of elements', IntRange(0), extname='maxlen',
|
"""
|
||||||
mandatory=True),
|
minlen = Property('minimum number of elements', IntRange(0), extname='minlen',
|
||||||
}
|
default=0)
|
||||||
|
maxlen = Property('maximum number of elements', IntRange(0), extname='maxlen',
|
||||||
|
mandatory=True)
|
||||||
|
|
||||||
def __init__(self, members, minlen=0, maxlen=None):
|
def __init__(self, members, minlen=0, maxlen=None):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@ -682,7 +779,7 @@ class ArrayOf(DataType):
|
|||||||
|
|
||||||
def setProperty(self, key, value):
|
def setProperty(self, key, value):
|
||||||
"""set also properties of members"""
|
"""set also properties of members"""
|
||||||
if key in self.__class__.properties:
|
if key in self.propertyDict:
|
||||||
super().setProperty(key, value)
|
super().setProperty(key, value)
|
||||||
else:
|
else:
|
||||||
self.members.setProperty(key, value)
|
self.members.setProperty(key, value)
|
||||||
@ -695,21 +792,35 @@ class ArrayOf(DataType):
|
|||||||
return 'ArrayOf(%s, %s, %s)' % (
|
return 'ArrayOf(%s, %s, %s)' % (
|
||||||
repr(self.members), self.minlen, self.maxlen)
|
repr(self.members), self.minlen, self.maxlen)
|
||||||
|
|
||||||
def __call__(self, value):
|
def check_type(self, value):
|
||||||
"""validate an external representation to an internal one"""
|
try:
|
||||||
if isinstance(value, (tuple, list)):
|
|
||||||
# check number of elements
|
# check number of elements
|
||||||
if self.minlen is not None and len(value) < self.minlen:
|
if self.minlen is not None and len(value) < self.minlen:
|
||||||
raise BadValueError(
|
raise BadValueError(
|
||||||
'Array too small, needs at least %d elements!' %
|
'array too small, needs at least %d elements!' %
|
||||||
self.minlen)
|
self.minlen)
|
||||||
if self.maxlen is not None and len(value) > self.maxlen:
|
if self.maxlen is not None and len(value) > self.maxlen:
|
||||||
raise BadValueError(
|
raise BadValueError(
|
||||||
'Array too big, holds at most %d elements!' % self.minlen)
|
'array too big, holds at most %d elements!' % self.maxlen)
|
||||||
# apply subtype valiation to all elements and return as list
|
except TypeError:
|
||||||
return tuple(self.members(elem) for elem in value)
|
raise BadValueError('%s can not be converted to ArrayOf DataType!'
|
||||||
raise BadValueError(
|
% type(value).__name__) from None
|
||||||
'Can not convert %s to ArrayOf DataType!' % repr(value))
|
|
||||||
|
def __call__(self, value):
|
||||||
|
self.check_type(value)
|
||||||
|
try:
|
||||||
|
return tuple(self.members(v) for v in value)
|
||||||
|
except Exception as e:
|
||||||
|
raise BadValueError('can not convert some array elements') from e
|
||||||
|
|
||||||
|
def validate(self, value, previous=None):
|
||||||
|
self.check_type(value)
|
||||||
|
try:
|
||||||
|
if previous:
|
||||||
|
return tuple(self.members.validate(v, p) for v, p in zip(value, previous))
|
||||||
|
return tuple(self.members.validate(v) for v in value)
|
||||||
|
except Exception as e:
|
||||||
|
raise BadValueError('some array elements are invalid') from e
|
||||||
|
|
||||||
def export_value(self, value):
|
def export_value(self, value):
|
||||||
"""returns a python object fit for serialisation"""
|
"""returns a python object fit for serialisation"""
|
||||||
@ -739,11 +850,17 @@ class ArrayOf(DataType):
|
|||||||
raise BadValueError('incompatible datatypes')
|
raise BadValueError('incompatible datatypes')
|
||||||
self.members.compatible(other.members)
|
self.members.compatible(other.members)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
raise BadValueError('incompatible datatypes')
|
raise BadValueError('incompatible datatypes') from None
|
||||||
|
|
||||||
|
def set_main_unit(self, unit):
|
||||||
|
self.members.set_main_unit(unit)
|
||||||
|
|
||||||
|
|
||||||
class TupleOf(DataType):
|
class TupleOf(DataType):
|
||||||
|
"""data structure with fields of inhomogeneous type
|
||||||
|
|
||||||
|
types are given as positional arguments
|
||||||
|
"""
|
||||||
def __init__(self, *members):
|
def __init__(self, *members):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
if not members:
|
if not members:
|
||||||
@ -765,19 +882,30 @@ class TupleOf(DataType):
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return 'TupleOf(%s)' % ', '.join([repr(st) for st in self.members])
|
return 'TupleOf(%s)' % ', '.join([repr(st) for st in self.members])
|
||||||
|
|
||||||
def __call__(self, value):
|
def check_type(self, value):
|
||||||
"""return the validated value or raise"""
|
|
||||||
# keep the ordering!
|
|
||||||
try:
|
try:
|
||||||
if len(value) != len(self.members):
|
if len(value) != len(self.members):
|
||||||
raise BadValueError(
|
raise BadValueError(
|
||||||
'Illegal number of Arguments! Need %d arguments.' %
|
'tuple needs %d elements' % len(self.members))
|
||||||
(len(self.members)))
|
except TypeError:
|
||||||
# validate elements and return as list
|
raise BadValueError('%s can not be converted to TupleOf DataType!'
|
||||||
return tuple(sub(elem)
|
% type(value).__name__) from None
|
||||||
for sub, elem in zip(self.members, value))
|
|
||||||
except Exception as exc:
|
def __call__(self, value):
|
||||||
raise BadValueError('Can not validate:', str(exc))
|
self.check_type(value)
|
||||||
|
try:
|
||||||
|
return tuple(sub(elem) for sub, elem in zip(self.members, value))
|
||||||
|
except Exception as e:
|
||||||
|
raise BadValueError('can not convert some tuple elements') from e
|
||||||
|
|
||||||
|
def validate(self, value, previous=None):
|
||||||
|
self.check_type(value)
|
||||||
|
try:
|
||||||
|
if previous is None:
|
||||||
|
return tuple(sub.validate(elem) for sub, elem in zip(self.members, value))
|
||||||
|
return tuple(sub.validate(v, p) for sub, v, p in zip(self.members, value, previous))
|
||||||
|
except Exception as e:
|
||||||
|
raise BadValueError('some tuple elements are invalid') from e
|
||||||
|
|
||||||
def export_value(self, value):
|
def export_value(self, value):
|
||||||
"""returns a python object fit for serialisation"""
|
"""returns a python object fit for serialisation"""
|
||||||
@ -800,11 +928,15 @@ class TupleOf(DataType):
|
|||||||
def compatible(self, other):
|
def compatible(self, other):
|
||||||
if not isinstance(other, TupleOf):
|
if not isinstance(other, TupleOf):
|
||||||
raise BadValueError('incompatible datatypes')
|
raise BadValueError('incompatible datatypes')
|
||||||
if len(self.members) != len(other.members) :
|
if len(self.members) != len(other.members):
|
||||||
raise BadValueError('incompatible datatypes')
|
raise BadValueError('incompatible datatypes')
|
||||||
for a, b in zip(self.members, other.members):
|
for a, b in zip(self.members, other.members):
|
||||||
a.compatible(b)
|
a.compatible(b)
|
||||||
|
|
||||||
|
def set_main_unit(self, unit):
|
||||||
|
for member in self.members:
|
||||||
|
member.set_main_unit(unit)
|
||||||
|
|
||||||
|
|
||||||
class ImmutableDict(dict):
|
class ImmutableDict(dict):
|
||||||
def _no(self, *args, **kwds):
|
def _no(self, *args, **kwds):
|
||||||
@ -813,13 +945,19 @@ class ImmutableDict(dict):
|
|||||||
|
|
||||||
|
|
||||||
class StructOf(DataType):
|
class StructOf(DataType):
|
||||||
|
"""data structure with named fields
|
||||||
|
|
||||||
|
:param optional: a list of optional members
|
||||||
|
:param members: names as keys and types as values for all members
|
||||||
|
"""
|
||||||
|
# Remark: assignment of parameters containing partial structs in their datatype
|
||||||
|
# are (and can) not be handled here! This has to be done manually in the write method
|
||||||
def __init__(self, optional=None, **members):
|
def __init__(self, optional=None, **members):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.members = members
|
self.members = members
|
||||||
if not members:
|
if not members:
|
||||||
raise BadValueError('Empty structs are not allowed!')
|
raise BadValueError('Empty structs are not allowed!')
|
||||||
self.optional = list(optional or [])
|
self.optional = list(members if optional is None else optional)
|
||||||
for name, subtype in list(members.items()):
|
for name, subtype in list(members.items()):
|
||||||
if not isinstance(subtype, DataType):
|
if not isinstance(subtype, DataType):
|
||||||
raise ProgrammingError(
|
raise ProgrammingError(
|
||||||
@ -828,16 +966,16 @@ class StructOf(DataType):
|
|||||||
if name not in members:
|
if name not in members:
|
||||||
raise ProgrammingError(
|
raise ProgrammingError(
|
||||||
'Only members of StructOf may be declared as optional!')
|
'Only members of StructOf may be declared as optional!')
|
||||||
self.default = dict((k,el.default) for k, el in members.items())
|
self.default = dict((k, el.default) for k, el in members.items())
|
||||||
|
|
||||||
def copy(self):
|
def copy(self):
|
||||||
"""DataType.copy does not work when members contain enums"""
|
"""DataType.copy does not work when members contain enums"""
|
||||||
return StructOf(self.optional, **{k: v.copy() for k,v in self.members.items()})
|
return StructOf(self.optional, **{k: v.copy() for k, v in self.members.items()})
|
||||||
|
|
||||||
def export_datatype(self):
|
def export_datatype(self):
|
||||||
res = dict(type='struct', members=dict((n, s.export_datatype())
|
res = dict(type='struct', members=dict((n, s.export_datatype())
|
||||||
for n, s in list(self.members.items())))
|
for n, s in list(self.members.items())))
|
||||||
if self.optional:
|
if set(self.optional) != set(self.members):
|
||||||
res['optional'] = self.optional
|
res['optional'] = self.optional
|
||||||
return res
|
return res
|
||||||
|
|
||||||
@ -847,16 +985,38 @@ class StructOf(DataType):
|
|||||||
['%s=%s' % (n, repr(st)) for n, st in list(self.members.items())]), opt)
|
['%s=%s' % (n, repr(st)) for n, st in list(self.members.items())]), opt)
|
||||||
|
|
||||||
def __call__(self, value):
|
def __call__(self, value):
|
||||||
"""return the validated value or raise"""
|
|
||||||
try:
|
try:
|
||||||
missing = set(self.members) - set(value) - set(self.optional)
|
if set(dict(value)) != set(self.members):
|
||||||
if missing:
|
raise BadValueError('member names do not match') from None
|
||||||
raise BadValueError('missing values for keys %r' % list(missing))
|
except TypeError:
|
||||||
# validate elements and return as dict
|
raise BadValueError('%s can not be converted a StructOf'
|
||||||
|
% type(value).__name__) from None
|
||||||
|
try:
|
||||||
return ImmutableDict((str(k), self.members[k](v))
|
return ImmutableDict((str(k), self.members[k](v))
|
||||||
for k, v in list(value.items()))
|
for k, v in list(value.items()))
|
||||||
except Exception as exc:
|
except Exception as e:
|
||||||
raise BadValueError('Can not validate %s: %s' % (repr(value), str(exc)))
|
raise BadValueError('can not convert some struct element') from e
|
||||||
|
|
||||||
|
def validate(self, value, previous=None):
|
||||||
|
try:
|
||||||
|
superfluous = set(dict(value)) - set(self.members)
|
||||||
|
except TypeError:
|
||||||
|
raise BadValueError('%s can not be converted a StructOf'
|
||||||
|
% type(value).__name__) from None
|
||||||
|
if superfluous - set(self.optional):
|
||||||
|
raise BadValueError('struct contains superfluous members: %s' % ', '.join(superfluous))
|
||||||
|
missing = set(self.members) - set(value) - set(self.optional)
|
||||||
|
if missing:
|
||||||
|
raise BadValueError('missing struct elements: %s' % ', '.join(missing))
|
||||||
|
try:
|
||||||
|
if previous is None:
|
||||||
|
return ImmutableDict((str(k), self.members[k].validate(v))
|
||||||
|
for k, v in list(value.items()))
|
||||||
|
result = dict(previous)
|
||||||
|
result.update(((k, self.members[k].validate(v, previous[k])) for k, v in value.items()))
|
||||||
|
return ImmutableDict(result)
|
||||||
|
except Exception as e:
|
||||||
|
raise BadValueError('some struct elements are invalid') from e
|
||||||
|
|
||||||
def export_value(self, value):
|
def export_value(self, value):
|
||||||
"""returns a python object fit for serialisation"""
|
"""returns a python object fit for serialisation"""
|
||||||
@ -886,10 +1046,18 @@ class StructOf(DataType):
|
|||||||
if mandatory:
|
if mandatory:
|
||||||
raise BadValueError('incompatible datatypes')
|
raise BadValueError('incompatible datatypes')
|
||||||
except (AttributeError, TypeError, KeyError):
|
except (AttributeError, TypeError, KeyError):
|
||||||
raise BadValueError('incompatible datatypes')
|
raise BadValueError('incompatible datatypes') from None
|
||||||
|
|
||||||
|
def set_main_unit(self, unit):
|
||||||
|
for member in self.members.values():
|
||||||
|
member.set_main_unit(unit)
|
||||||
|
|
||||||
|
|
||||||
class CommandType(DataType):
|
class CommandType(DataType):
|
||||||
|
"""command
|
||||||
|
|
||||||
|
a pseudo datatype for commands with arguments and return values
|
||||||
|
"""
|
||||||
IS_COMMAND = True
|
IS_COMMAND = True
|
||||||
|
|
||||||
def __init__(self, argument=None, result=None):
|
def __init__(self, argument=None, result=None):
|
||||||
@ -913,14 +1081,12 @@ class CommandType(DataType):
|
|||||||
return props
|
return props
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
argstr = repr(self.argument) if self.argument else ''
|
|
||||||
if self.result is None:
|
if self.result is None:
|
||||||
return 'CommandType(%s)' % argstr
|
return 'CommandType(%s)' % (repr(self.argument) if self.argument else '')
|
||||||
return 'CommandType(%s)->%s' % (argstr, repr(self.result))
|
return 'CommandType(%s, %s)' % (repr(self.argument), repr(self.result))
|
||||||
|
|
||||||
def __call__(self, value):
|
def __call__(self, value):
|
||||||
"""return the validated argument value or raise"""
|
raise ProgrammingError('commands can not be converted to a value')
|
||||||
return self.argument(value)
|
|
||||||
|
|
||||||
def export_value(self, value):
|
def export_value(self, value):
|
||||||
raise ProgrammingError('values of type command can not be transported!')
|
raise ProgrammingError('values of type command can not be transported!')
|
||||||
@ -945,11 +1111,11 @@ class CommandType(DataType):
|
|||||||
if self.result != other.result: # not both are None
|
if self.result != other.result: # not both are None
|
||||||
other.result.compatible(self.result)
|
other.result.compatible(self.result)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
raise BadValueError('incompatible datatypes')
|
raise BadValueError('incompatible datatypes') from None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# internally used datatypes (i.e. only for programming the SEC-node)
|
# internally used datatypes (i.e. only for programming the SEC-node)
|
||||||
|
|
||||||
class DataTypeType(DataType):
|
class DataTypeType(DataType):
|
||||||
def __call__(self, value):
|
def __call__(self, value):
|
||||||
"""check if given value (a python obj) is a valid datatype
|
"""check if given value (a python obj) is a valid datatype
|
||||||
@ -957,7 +1123,10 @@ class DataTypeType(DataType):
|
|||||||
returns the value or raises an appropriate exception"""
|
returns the value or raises an appropriate exception"""
|
||||||
if isinstance(value, DataType):
|
if isinstance(value, DataType):
|
||||||
return value
|
return value
|
||||||
raise ProgrammingError('%r should be a DataType!' % value)
|
try:
|
||||||
|
return get_datatype(value)
|
||||||
|
except Exception as e:
|
||||||
|
raise ProgrammingError(e) from None
|
||||||
|
|
||||||
def export_value(self, value):
|
def export_value(self, value):
|
||||||
"""if needed, reformat value for transport"""
|
"""if needed, reformat value for transport"""
|
||||||
@ -992,6 +1161,13 @@ class ValueType(DataType):
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def setProperty(self, key, value):
|
||||||
|
"""silently ignored
|
||||||
|
|
||||||
|
as ValueType is used for the datatype default, this makes code
|
||||||
|
shorter for cases, where the datatype may not yet be defined
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class NoneOr(DataType):
|
class NoneOr(DataType):
|
||||||
"""validates a None or smth. else"""
|
"""validates a None or smth. else"""
|
||||||
@ -1038,26 +1214,23 @@ UInt64 = IntRange(0, (1 << 64) - 1)
|
|||||||
# Goodie: Convenience Datatypes for Programming
|
# Goodie: Convenience Datatypes for Programming
|
||||||
class LimitsType(TupleOf):
|
class LimitsType(TupleOf):
|
||||||
def __init__(self, members):
|
def __init__(self, members):
|
||||||
TupleOf.__init__(self, members, members)
|
super().__init__(members, members)
|
||||||
|
|
||||||
def __call__(self, value):
|
def __call__(self, value):
|
||||||
limits = TupleOf.__call__(self, value)
|
limits = TupleOf.validate(self, value)
|
||||||
if limits[1] < limits[0]:
|
if limits[1] < limits[0]:
|
||||||
raise BadValueError('Maximum Value %s must be greater than minimum value %s!' % (limits[1], limits[0]))
|
raise BadValueError('Maximum Value %s must be greater than minimum value %s!' % (limits[1], limits[0]))
|
||||||
return limits
|
return limits
|
||||||
|
|
||||||
|
|
||||||
class StatusType(TupleOf):
|
class StatusType(TupleOf):
|
||||||
# shorten initialisation and allow acces to status enumMembers from status values
|
# shorten initialisation and allow access to status enumMembers from status values
|
||||||
def __init__(self, enum):
|
def __init__(self, enum):
|
||||||
TupleOf.__init__(self, EnumType(enum), StringType())
|
super().__init__(EnumType(enum), StringType())
|
||||||
self.enum = enum
|
self._enum = enum
|
||||||
|
|
||||||
def __getattr__(self, key):
|
def __getattr__(self, key):
|
||||||
enum = TupleOf.__getattr__(self, 'enum')
|
return getattr(self._enum, key)
|
||||||
if hasattr(enum, key):
|
|
||||||
return getattr(enum, key)
|
|
||||||
return TupleOf.__getattr__(self, key)
|
|
||||||
|
|
||||||
|
|
||||||
def floatargs(kwds):
|
def floatargs(kwds):
|
||||||
@ -1070,37 +1243,26 @@ def floatargs(kwds):
|
|||||||
DATATYPES = dict(
|
DATATYPES = dict(
|
||||||
bool = lambda **kwds:
|
bool = lambda **kwds:
|
||||||
BoolType(),
|
BoolType(),
|
||||||
|
|
||||||
int = lambda min, max, **kwds:
|
int = lambda min, max, **kwds:
|
||||||
IntRange(minval=min, maxval=max),
|
IntRange(minval=min, maxval=max),
|
||||||
|
|
||||||
scaled = lambda scale, min, max, **kwds:
|
scaled = lambda scale, min, max, **kwds:
|
||||||
ScaledInteger(scale=scale, minval=min*scale, maxval=max*scale, **floatargs(kwds)),
|
ScaledInteger(scale=scale, minval=min*scale, maxval=max*scale, **floatargs(kwds)),
|
||||||
|
|
||||||
double = lambda min=None, max=None, **kwds:
|
double = lambda min=None, max=None, **kwds:
|
||||||
FloatRange(minval=min, maxval=max, **floatargs(kwds)),
|
FloatRange(minval=min, maxval=max, **floatargs(kwds)),
|
||||||
|
|
||||||
blob = lambda maxbytes, minbytes=0, **kwds:
|
blob = lambda maxbytes, minbytes=0, **kwds:
|
||||||
BLOBType(minbytes=minbytes, maxbytes=maxbytes),
|
BLOBType(minbytes=minbytes, maxbytes=maxbytes),
|
||||||
|
|
||||||
string = lambda minchars=0, maxchars=None, isUTF8=False, **kwds:
|
string = lambda minchars=0, maxchars=None, isUTF8=False, **kwds:
|
||||||
StringType(minchars=minchars, maxchars=maxchars, isUTF8=isUTF8),
|
StringType(minchars=minchars, maxchars=maxchars, isUTF8=isUTF8),
|
||||||
|
|
||||||
array = lambda maxlen, members, minlen=0, pname='', **kwds:
|
array = lambda maxlen, members, minlen=0, pname='', **kwds:
|
||||||
ArrayOf(get_datatype(members, pname), minlen=minlen, maxlen=maxlen),
|
ArrayOf(get_datatype(members, pname), minlen=minlen, maxlen=maxlen),
|
||||||
|
|
||||||
tuple = lambda members, pname='', **kwds:
|
tuple = lambda members, pname='', **kwds:
|
||||||
TupleOf(*tuple((get_datatype(t, pname) for t in members))),
|
TupleOf(*tuple((get_datatype(t, pname) for t in members))),
|
||||||
|
|
||||||
enum = lambda members, pname='', **kwds:
|
enum = lambda members, pname='', **kwds:
|
||||||
EnumType(pname, members=members),
|
EnumType(pname, members=members),
|
||||||
|
|
||||||
struct = lambda members, optional=None, pname='', **kwds:
|
struct = lambda members, optional=None, pname='', **kwds:
|
||||||
StructOf(optional, **dict((n, get_datatype(t, pname)) for n, t in list(members.items()))),
|
StructOf(optional, **dict((n, get_datatype(t, pname)) for n, t in list(members.items()))),
|
||||||
|
|
||||||
command = lambda argument=None, result=None, pname='', **kwds:
|
command = lambda argument=None, result=None, pname='', **kwds:
|
||||||
CommandType(get_datatype(argument, pname), get_datatype(result)),
|
CommandType(get_datatype(argument, pname), get_datatype(result)),
|
||||||
|
|
||||||
limit = lambda members, pname='', **kwds:
|
limit = lambda members, pname='', **kwds:
|
||||||
LimitsType(get_datatype(members, pname)),
|
LimitsType(get_datatype(members, pname)),
|
||||||
)
|
)
|
||||||
@ -1111,7 +1273,10 @@ def get_datatype(json, pname=''):
|
|||||||
"""returns a DataType object from description
|
"""returns a DataType object from description
|
||||||
|
|
||||||
inverse of <DataType>.export_datatype()
|
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:
|
if json is None:
|
||||||
return json
|
return json
|
||||||
@ -1122,9 +1287,9 @@ def get_datatype(json, pname=''):
|
|||||||
kwargs = json.copy()
|
kwargs = json.copy()
|
||||||
base = kwargs.pop('type')
|
base = kwargs.pop('type')
|
||||||
except (TypeError, KeyError, AttributeError):
|
except (TypeError, KeyError, AttributeError):
|
||||||
raise BadValueError('a data descriptor must be a dict containing a "type" key, not %r' % json)
|
raise BadValueError('a data descriptor must be a dict containing a "type" key, not %r' % json) from None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return DATATYPES[base](pname=pname, **kwargs)
|
return DATATYPES[base](pname=pname, **kwargs)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise BadValueError('invalid data descriptor: %r (%s)' % (json, str(e)))
|
raise BadValueError('invalid data descriptor: %r (%s)' % (json, str(e))) from None
|
@ -17,23 +17,28 @@
|
|||||||
#
|
#
|
||||||
# Module authors:
|
# Module authors:
|
||||||
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||||
|
# Markus Zolliker <markus.zolliker@psi.ch>
|
||||||
#
|
#
|
||||||
# *****************************************************************************
|
# *****************************************************************************
|
||||||
"""Define (internal) SECoP Errors"""
|
"""Define (internal) SECoP Errors"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
from ast import literal_eval
|
||||||
|
|
||||||
|
|
||||||
class SECoPError(RuntimeError):
|
class SECoPError(RuntimeError):
|
||||||
|
silent = False # silent = True indicates that the error is already logged
|
||||||
|
|
||||||
def __init__(self, *args, **kwds):
|
def __init__(self, *args, **kwds):
|
||||||
RuntimeError.__init__(self)
|
super().__init__()
|
||||||
self.args = args
|
self.args = args
|
||||||
for k, v in list(kwds.items()):
|
for k, v in list(kwds.items()):
|
||||||
setattr(self, k, v)
|
setattr(self, k, v)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
args = ', '.join(map(repr, self.args))
|
args = ', '.join(map(repr, self.args))
|
||||||
kwds = ', '.join(['%s=%r' % i for i in list(self.__dict__.items())])
|
kwds = ', '.join(['%s=%r' % i for i in list(self.__dict__.items())
|
||||||
|
if i[0] != 'silent'])
|
||||||
res = []
|
res = []
|
||||||
if args:
|
if args:
|
||||||
res.append(args)
|
res.append(args)
|
||||||
@ -103,14 +108,6 @@ class CommunicationFailedError(SECoPError):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class SilentError(SECoPError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class CommunicationSilentError(SilentError, CommunicationFailedError):
|
|
||||||
name = 'CommunicationFailed'
|
|
||||||
|
|
||||||
|
|
||||||
class IsBusyError(SECoPError):
|
class IsBusyError(SECoPError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -124,10 +121,29 @@ class DisabledError(SECoPError):
|
|||||||
|
|
||||||
|
|
||||||
class HardwareError(SECoPError):
|
class HardwareError(SECoPError):
|
||||||
pass
|
name = 'HardwareError'
|
||||||
|
|
||||||
|
|
||||||
|
FRAPPY_ERROR = re.compile(r'(.*)\(.*\)$')
|
||||||
|
|
||||||
|
|
||||||
def make_secop_error(name, text):
|
def make_secop_error(name, text):
|
||||||
|
"""create an instance of SECoPError from an error report
|
||||||
|
|
||||||
|
:param name: the error class from the SECoP error report
|
||||||
|
:param text: the second item of a SECoP error report
|
||||||
|
:return: the built instance of SECoPError
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# try to interprete the error text as a repr(<instance of SECoPError>)
|
||||||
|
# as it would be created by a Frappy server
|
||||||
|
cls, textarg = FRAPPY_ERROR.match(text).groups()
|
||||||
|
errcls = locals()[cls]
|
||||||
|
if errcls.name == name:
|
||||||
|
# convert repr(<string>) to <string>
|
||||||
|
text = literal_eval(textarg)
|
||||||
|
except Exception:
|
||||||
|
# probably not a Frappy server, or running a different version
|
||||||
errcls = EXCEPTIONS.get(name, InternalError)
|
errcls = EXCEPTIONS.get(name, InternalError)
|
||||||
return errcls(text)
|
return errcls(text)
|
||||||
|
|
||||||
@ -144,7 +160,7 @@ EXCEPTIONS = dict(
|
|||||||
NoSuchCommand=NoSuchCommandError,
|
NoSuchCommand=NoSuchCommandError,
|
||||||
CommandFailed=CommandFailedError,
|
CommandFailed=CommandFailedError,
|
||||||
CommandRunning=CommandRunningError,
|
CommandRunning=CommandRunningError,
|
||||||
Readonly=ReadOnlyError,
|
ReadOnly=ReadOnlyError,
|
||||||
BadValue=BadValueError,
|
BadValue=BadValueError,
|
||||||
CommunicationFailed=CommunicationFailedError,
|
CommunicationFailed=CommunicationFailedError,
|
||||||
HardwareError=HardwareError,
|
HardwareError=HardwareError,
|
||||||
@ -152,7 +168,8 @@ EXCEPTIONS = dict(
|
|||||||
IsError=IsErrorError,
|
IsError=IsErrorError,
|
||||||
Disabled=DisabledError,
|
Disabled=DisabledError,
|
||||||
SyntaxError=ProtocolError,
|
SyntaxError=ProtocolError,
|
||||||
NotImplementedError=NotImplementedError,
|
NotImplemented=NotImplementedError,
|
||||||
|
ProtocolError=ProtocolError,
|
||||||
InternalError=InternalError,
|
InternalError=InternalError,
|
||||||
# internal short versions (candidates for spec)
|
# internal short versions (candidates for spec)
|
||||||
Protocol=ProtocolError,
|
Protocol=ProtocolError,
|
245
frappy/features.py
Normal file
245
frappy/features.py
Normal file
@ -0,0 +1,245 @@
|
|||||||
|
# -*- 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:
|
||||||
|
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||||
|
# Markus Zolliker <markus.zolliker@psi.ch>
|
||||||
|
#
|
||||||
|
# *****************************************************************************
|
||||||
|
"""Define Mixin Features for real Modules implemented in the server"""
|
||||||
|
|
||||||
|
|
||||||
|
from frappy.datatypes import ArrayOf, BoolType, EnumType, \
|
||||||
|
FloatRange, StringType, StructOf, TupleOf
|
||||||
|
from frappy.core import Command, Done, Drivable, Feature, \
|
||||||
|
Parameter, Property, PersistentParam, Readable
|
||||||
|
from frappy.errors import BadValueError, ConfigError
|
||||||
|
from frappy.lib import clamp
|
||||||
|
|
||||||
|
|
||||||
|
# --- proposals, to be used at SINQ (not agreed as standard yet) ---
|
||||||
|
|
||||||
|
class HasOffset(Feature):
|
||||||
|
"""has an offset parameter
|
||||||
|
|
||||||
|
implementation to be done in the subclass
|
||||||
|
"""
|
||||||
|
offset = PersistentParam('offset (physical value + offset = HW value)',
|
||||||
|
FloatRange(unit='deg'), readonly=False, default=0)
|
||||||
|
|
||||||
|
def write_offset(self, value):
|
||||||
|
self.offset = value
|
||||||
|
if isinstance(self, HasLimits):
|
||||||
|
self.read_limits()
|
||||||
|
if isinstance(self, Readable):
|
||||||
|
self.read_value()
|
||||||
|
if isinstance(self, Drivable):
|
||||||
|
self.read_target()
|
||||||
|
self.saveParameters()
|
||||||
|
return Done
|
||||||
|
|
||||||
|
|
||||||
|
class HasLimits(Feature):
|
||||||
|
"""user limits
|
||||||
|
|
||||||
|
implementation to be done in the subclass
|
||||||
|
|
||||||
|
for a drivable, abslimits is roughly the same as the target datatype limits,
|
||||||
|
except for the offset
|
||||||
|
"""
|
||||||
|
abslimits = Property('abs limits (raw values)', default=(-9e99, 9e99), extname='abslimits', export=True,
|
||||||
|
datatype=TupleOf(FloatRange(unit='deg'), FloatRange(unit='deg')))
|
||||||
|
limits = PersistentParam('user limits', readonly=False, default=(-9e99, 9e99),
|
||||||
|
datatype=TupleOf(FloatRange(unit='deg'), FloatRange(unit='deg')))
|
||||||
|
_limits = None
|
||||||
|
|
||||||
|
def apply_offset(self, sign, *values):
|
||||||
|
if isinstance(self, HasOffset):
|
||||||
|
return tuple(v + sign * self.offset for v in values)
|
||||||
|
return values
|
||||||
|
|
||||||
|
def earlyInit(self):
|
||||||
|
super().earlyInit()
|
||||||
|
# make limits valid
|
||||||
|
_limits = self.apply_offset(1, *self.limits)
|
||||||
|
self._limits = tuple(clamp(self.abslimits[0], v, self.abslimits[1]) for v in _limits)
|
||||||
|
self.read_limits()
|
||||||
|
|
||||||
|
def checkProperties(self):
|
||||||
|
pname = 'target' if isinstance(self, Drivable) else 'value'
|
||||||
|
dt = self.parameters[pname].datatype
|
||||||
|
min_, max_ = self.abslimits
|
||||||
|
t_min, t_max = self.apply_offset(1, dt.min, dt.max)
|
||||||
|
if t_min > max_ or t_max < min_:
|
||||||
|
raise ConfigError('abslimits not within %s range' % pname)
|
||||||
|
self.abslimits = clamp(t_min, min_, t_max), clamp(t_min, max_, t_max)
|
||||||
|
super().checkProperties()
|
||||||
|
|
||||||
|
def read_limits(self):
|
||||||
|
return self.apply_offset(-1, *self._limits)
|
||||||
|
|
||||||
|
def write_limits(self, value):
|
||||||
|
min_, max_ = self.apply_offset(-1, *self.abslimits)
|
||||||
|
if not min_ <= value[0] <= value[1] <= max_:
|
||||||
|
if value[0] > value[1]:
|
||||||
|
raise BadValueError('invalid interval: %r' % value)
|
||||||
|
raise BadValueError('limits not within abs limits [%g, %g]' % (min_, max_))
|
||||||
|
self.limits = value
|
||||||
|
self.saveParameters()
|
||||||
|
return Done
|
||||||
|
|
||||||
|
def check_limits(self, value):
|
||||||
|
"""check if value is valid"""
|
||||||
|
min_, max_ = self.limits
|
||||||
|
if not min_ <= value <= max_:
|
||||||
|
raise BadValueError('limits violation: %g outside [%g, %g]' % (value, min_, max_))
|
||||||
|
|
||||||
|
|
||||||
|
# --- not used, not tested yet ---
|
||||||
|
|
||||||
|
class HAS_PID(Feature):
|
||||||
|
# note: implementors should either use p,i,d or pid, but ECS must be handle both cases
|
||||||
|
# note: if both p,i,d and pid are implemented, it MUST NOT matter which one gets a change, the final result should be the same
|
||||||
|
# note: if there are additional custom accessibles with the same name as an element of the struct, the above applies
|
||||||
|
# note: (i would still but them in the same group, though)
|
||||||
|
# note: if extra elements are implemented in the pid struct they MUST BE
|
||||||
|
# properly described in the description of the pid Parameter
|
||||||
|
|
||||||
|
# parameters
|
||||||
|
use_pid = Parameter('use the pid mode', datatype=EnumType(openloop=0, pid_control=1), )
|
||||||
|
# pylint: disable=invalid-name
|
||||||
|
p = Parameter('proportional part of the regulation', datatype=FloatRange(0), )
|
||||||
|
i = Parameter('(optional) integral part', datatype=FloatRange(0), optional=True)
|
||||||
|
d = Parameter('(optional) derivative part', datatype=FloatRange(0), optional=True)
|
||||||
|
base_output = Parameter('(optional) minimum output value', datatype=FloatRange(0), optional=True)
|
||||||
|
pid = Parameter('(optional) Struct of p,i,d, minimum output value',
|
||||||
|
datatype=StructOf(p=FloatRange(0),
|
||||||
|
i=FloatRange(0),
|
||||||
|
d=FloatRange(0),
|
||||||
|
base_output=FloatRange(0),
|
||||||
|
), optional=True,
|
||||||
|
) # note: struct may be extended with custom elements (names should be prefixed with '_')
|
||||||
|
output = Parameter('(optional) output of pid-control', datatype=FloatRange(0), optional=True, readonly=False)
|
||||||
|
|
||||||
|
|
||||||
|
class Has_PIDTable(HAS_PID):
|
||||||
|
|
||||||
|
# parameters
|
||||||
|
use_pidtable = Parameter('use the zoning mode', datatype=EnumType(fixed_pid=0, zone_mode=1))
|
||||||
|
pidtable = Parameter('Table of pid-values vs. target temperature', datatype=ArrayOf(TupleOf(FloatRange(0),
|
||||||
|
StructOf(p=FloatRange(0),
|
||||||
|
i=FloatRange(0),
|
||||||
|
d=FloatRange(0),
|
||||||
|
_heater_range=FloatRange(0),
|
||||||
|
_base_output=FloatRange(0),),),), optional=True) # struct may include 'heaterrange'
|
||||||
|
|
||||||
|
|
||||||
|
class HAS_Persistent(Feature):
|
||||||
|
#extra_Status {
|
||||||
|
# 'decoupled' : Status.IDLE+1, # to be discussed.
|
||||||
|
# 'coupling' : Status.BUSY+1, # to be discussed.
|
||||||
|
# 'coupled' : Status.BUSY+2, # to be discussed.
|
||||||
|
# 'decoupling' : Status.BUSY+3, # to be discussed.
|
||||||
|
#}
|
||||||
|
|
||||||
|
# parameters
|
||||||
|
persistent_mode = Parameter('Use persistent mode',
|
||||||
|
datatype=EnumType(off=0,on=1),
|
||||||
|
default=0, readonly=False)
|
||||||
|
is_persistent = Parameter('current state of persistence',
|
||||||
|
datatype=BoolType(), optional=True)
|
||||||
|
# stored_value = Parameter('current persistence value, often used as the modules value',
|
||||||
|
# datatype='main', unit='$', optional=True)
|
||||||
|
# driven_value = Parameter('driven value (outside value, syncs with stored_value if non-persistent)',
|
||||||
|
# datatype='main', unit='$' )
|
||||||
|
|
||||||
|
|
||||||
|
class HAS_Tolerance(Feature):
|
||||||
|
# detects IDLE status by checking if the value lies in a given window:
|
||||||
|
# tolerance is the maximum allowed deviation from target, value must lie in this interval
|
||||||
|
# for at least ´timewindow´ seconds.
|
||||||
|
|
||||||
|
# parameters
|
||||||
|
tolerance = Parameter('Half height of the Window',
|
||||||
|
datatype=FloatRange(0), default=1, unit='$')
|
||||||
|
timewindow = Parameter('Length of the timewindow to check',
|
||||||
|
datatype=FloatRange(0), default=30, unit='s',
|
||||||
|
optional=True)
|
||||||
|
|
||||||
|
|
||||||
|
class HAS_Timeout(Feature):
|
||||||
|
|
||||||
|
# parameters
|
||||||
|
timeout = Parameter('timeout for movement',
|
||||||
|
datatype=FloatRange(0), default=0, unit='s')
|
||||||
|
|
||||||
|
|
||||||
|
class HAS_Pause(Feature):
|
||||||
|
# just a proposal, can't agree on it....
|
||||||
|
|
||||||
|
@Command(argument=None, result=None)
|
||||||
|
def pause(self):
|
||||||
|
"""pauses movement"""
|
||||||
|
|
||||||
|
@Command(argument=None, result=None)
|
||||||
|
def go(self):
|
||||||
|
"""continues movement or start a new one if target was change since the last pause"""
|
||||||
|
|
||||||
|
|
||||||
|
class HAS_Ramp(Feature):
|
||||||
|
|
||||||
|
# parameters
|
||||||
|
ramp =Parameter('speed of movement', unit='$/min',
|
||||||
|
datatype=FloatRange(0))
|
||||||
|
use_ramp = Parameter('use the ramping of the setpoint, or jump',
|
||||||
|
datatype=EnumType(disable_ramp=0, use_ramp=1),
|
||||||
|
optional=True)
|
||||||
|
setpoint = Parameter('currently active setpoint',
|
||||||
|
datatype=FloatRange(0), unit='$',
|
||||||
|
readonly=True, )
|
||||||
|
|
||||||
|
|
||||||
|
class HAS_Speed(Feature):
|
||||||
|
|
||||||
|
# parameters
|
||||||
|
speed = Parameter('(maximum) speed of movement (of the main value)',
|
||||||
|
unit='$/s', datatype=FloatRange(0))
|
||||||
|
|
||||||
|
|
||||||
|
class HAS_Accel(HAS_Speed):
|
||||||
|
|
||||||
|
# parameters
|
||||||
|
accel = Parameter('acceleration of movement', unit='$/s^2',
|
||||||
|
datatype=FloatRange(0))
|
||||||
|
decel = Parameter('deceleration of movement', unit='$/s^2',
|
||||||
|
datatype=FloatRange(0), optional=True)
|
||||||
|
|
||||||
|
|
||||||
|
class HAS_MotorCurrents(Feature):
|
||||||
|
|
||||||
|
# parameters
|
||||||
|
movecurrent = Parameter('Current while moving',
|
||||||
|
datatype=FloatRange(0))
|
||||||
|
idlecurrent = Parameter('Current while idle',
|
||||||
|
datatype=FloatRange(0), optional=True)
|
||||||
|
|
||||||
|
|
||||||
|
class HAS_Curve(Feature):
|
||||||
|
# proposed, not yet agreed upon!
|
||||||
|
|
||||||
|
# parameters
|
||||||
|
curve = Parameter('Calibration curve', datatype=StringType(), default='<unset>')
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user