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.
|
||||
disable=missing-docstring
|
||||
,locally-disabled
|
||||
,locally-enabled
|
||||
,fixme
|
||||
,no-member
|
||||
,bad-whitespace
|
||||
,wrong-import-position
|
||||
,ungrouped-imports
|
||||
,import-self
|
||||
,bad-continuation
|
||||
,protected-access
|
||||
,unused-argument
|
||||
,duplicate-code
|
||||
,attribute-defined-outside-init
|
||||
,access-member-before-definition
|
||||
,no-self-use
|
||||
,broad-except
|
||||
,unneeded-not
|
||||
,unidiomatic-typecheck
|
||||
,undefined-loop-variable
|
||||
,redefined-variable-type
|
||||
,deprecated-lambda
|
||||
|
||||
,consider-using-f-string
|
||||
[REPORTS]
|
||||
|
||||
# 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}
|
||||
|
||||
# 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
|
||||
reports=no
|
||||
@ -93,14 +83,11 @@ dummy-variables-rgx=_|dummy
|
||||
|
||||
# List of additional names supposed to be defined in builtins. Remember that
|
||||
# you should avoid to define new builtins when possible.
|
||||
additional-builtins=
|
||||
additional-builtins=Node,Mod,Param,Command,Group
|
||||
|
||||
|
||||
[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
|
||||
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.
|
||||
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
|
||||
max-module-lines=1200
|
||||
max-module-lines=1000
|
||||
|
||||
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
|
||||
# tab).
|
||||
@ -212,13 +193,13 @@ max-locals=50
|
||||
max-returns=10
|
||||
|
||||
# Maximum number of branch for function / method body
|
||||
max-branches=40
|
||||
max-branches=50
|
||||
|
||||
# Maximum number of statements in function / method body
|
||||
max-statements=150
|
||||
|
||||
# Maximum number of parents for a class (see R0901).
|
||||
max-parents=10
|
||||
max-parents=15
|
||||
|
||||
# Maximum number of attributes for a class (see R0902).
|
||||
max-attributes=50
|
||||
|
28
Makefile
28
Makefile
@ -3,12 +3,18 @@
|
||||
|
||||
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:
|
||||
@bin/secop-server -q demo &
|
||||
@bin/secop-server -q test &
|
||||
@bin/secop-server -q cryo &
|
||||
@bin/secop-gui localhost:10767 localhost:10768 localhost:10769
|
||||
@ps aux|grep [s]ecop-server|awk '{print $$2}'|xargs kill
|
||||
@rm -f frappydemo.PID || true
|
||||
@{ bin/frappy-server -q demo & echo $$! >> frappydemo.PID; }
|
||||
@{ bin/frappy-server -q test & echo $$! >> frappydemo.PID; }
|
||||
@{ bin/frappy-server -q cryo & echo $$! >> frappydemo.PID; }
|
||||
@sleep 0.2
|
||||
@bin/frappy-gui localhost:10767 localhost:10768 localhost:10769
|
||||
@cat frappydemo.PID | xargs kill || true
|
||||
@rm frappydemo.PID
|
||||
|
||||
build:
|
||||
python3 setup.py build
|
||||
@ -32,18 +38,18 @@ test-verbose:
|
||||
python3 $(shell which pytest) -v test -s
|
||||
|
||||
test-coverage:
|
||||
python3 $(shell which pytest) -v test --cov=secop
|
||||
python3 $(shell which pytest) -v test --cov=frappy
|
||||
|
||||
doc:
|
||||
$(MAKE) -C doc html
|
||||
|
||||
lint:
|
||||
pylint -j 0 -f colorized -r n --rcfile=.pylintrc secop secop_* test
|
||||
pylint -f colorized -r n --rcfile=.pylintrc frappy frappy_* test
|
||||
|
||||
isort:
|
||||
@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 . -wholename './secop_*.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 './frappy_*.py' -print0 | xargs -0 isort -e -m 2 -w 80 -ns __init__.py
|
||||
|
||||
release-patch:
|
||||
MODE="patch" $(MAKE) release
|
||||
@ -55,8 +61,8 @@ release-major:
|
||||
MODE="major" $(MAKE) 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:
|
||||
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
|
||||
sys.path.insert(0, path.abspath(path.join(path.dirname(__file__), '..')))
|
||||
|
||||
from secop.gui.qt import QApplication
|
||||
from secop.gui.cfg_editor.mainwindow import MainWindow
|
||||
from frappy.gui.qt import QApplication
|
||||
from frappy.gui.cfg_editor.mainwindow import MainWindow
|
||||
|
||||
|
||||
def main(argv=None):
|
@ -26,42 +26,49 @@
|
||||
from __future__ import print_function
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
from os import path
|
||||
|
||||
# Add import path for inplace usage
|
||||
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 secop.gui.mainwindow import MainWindow
|
||||
from frappy.gui.qt import QApplication
|
||||
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):
|
||||
if argv is None:
|
||||
argv = sys.argv
|
||||
|
||||
if '-h' in argv or '--help' in argv:
|
||||
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)
|
||||
args = parseArgv(argv[1:])
|
||||
|
||||
if '-d' in argv or '--debug' in argv:
|
||||
mlzlog.initLogging('gui', 'debug')
|
||||
else:
|
||||
mlzlog.initLogging('gui', 'info')
|
||||
loglevel = logging.DEBUG if args.debug else (logging.ERROR if args.quiet else logging.INFO)
|
||||
logger = logging.getLogger('gui')
|
||||
logger.setLevel(logging.DEBUG)
|
||||
console = ColoredConsoleHandler()
|
||||
console.setLevel(loglevel)
|
||||
logger.addHandler(console)
|
||||
|
||||
app = QApplication(argv)
|
||||
|
||||
hosts = [host for host in argv[1:] if not host.startswith('-')]
|
||||
if not hosts:
|
||||
hosts = ['localhost:10767']
|
||||
win = MainWindow(hosts)
|
||||
win = MainWindow(args.node, logger)
|
||||
win.show()
|
||||
|
||||
return app.exec_()
|
@ -27,13 +27,12 @@ import sys
|
||||
import argparse
|
||||
from os import path
|
||||
|
||||
import mlzlog
|
||||
|
||||
# Add import path for inplace usage
|
||||
sys.path.insert(0, path.abspath(path.join(path.dirname(__file__), '..')))
|
||||
|
||||
from secop.lib import getGeneralConfig
|
||||
from secop.server import Server
|
||||
from frappy.lib import generalConfig
|
||||
from frappy.logging import logger
|
||||
from frappy.server import Server
|
||||
|
||||
|
||||
def parseArgv(argv):
|
||||
@ -60,15 +59,26 @@ def parseArgv(argv):
|
||||
parser.add_argument('-c',
|
||||
'--cfgfiles',
|
||||
action='store',
|
||||
help="comma separated list of cfg files\n"
|
||||
"defaults to <name_of_the_instance>\n"
|
||||
"cfgfiles given without '.cfg' extension are searched in the configuration directory,"
|
||||
help="comma separated list of cfg files,\n"
|
||||
"defaults to <name_of_the_instance>.\n"
|
||||
"cfgfiles given without '.cfg' extension are searched in the configuration directory, "
|
||||
"else they are treated as path names",
|
||||
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',
|
||||
'--test',
|
||||
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)
|
||||
return parser.parse_args(argv)
|
||||
|
||||
@ -80,9 +90,12 @@ def main(argv=None):
|
||||
args = parseArgv(argv[1:])
|
||||
|
||||
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:
|
||||
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
|
||||
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):
|
||||
|
||||
@ -136,7 +136,7 @@ for arg in sys.argv[1:]:
|
||||
pass
|
||||
opts[k] = v
|
||||
verbose = opts.pop('verbose', False)
|
||||
opts['cls'] = 'secop_psi.ls370sim.Ls370Sim'
|
||||
opts['cls'] = 'frappy_psi.ls370sim.Ls370Sim'
|
||||
opts['port'] = 4567
|
||||
if len(args) > 2:
|
||||
raise ValueError('do not know about: %s' % ' '.join(args[2:]))
|
||||
|
@ -7,11 +7,11 @@ bindto = 0.0.0.0
|
||||
bindport = 5000
|
||||
|
||||
[module cap]
|
||||
class = secop_psi.ah2700.Capacitance
|
||||
class = frappy_psi.ah2700.Capacitance
|
||||
description = capacitance
|
||||
uri=ldmse3-ts:3015
|
||||
|
||||
#[module ahcom]
|
||||
#class = secop_psi.ah2700.StringIO
|
||||
#class = frappy_psi.ah2700.StringIO
|
||||
#uri=ldmse3-ts:3015
|
||||
#description = serial communicator to an AH2700
|
||||
|
@ -19,7 +19,7 @@ bindto=0.0.0.0
|
||||
bindport=10767
|
||||
|
||||
[module enable]
|
||||
class=secop_mlz.entangle.NamedDigitalOutput
|
||||
class=frappy_mlz.entangle.NamedDigitalOutput
|
||||
tangodevice='tango://localhost:10000/box/plc/_enable'
|
||||
value.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'
|
||||
|
||||
[module polarity]
|
||||
class=secop_mlz.entangle.NamedDigitalOutput
|
||||
class=frappy_mlz.entangle.NamedDigitalOutput
|
||||
tangodevice=tango://localhost:10000/box/plc/_polarity
|
||||
value.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]
|
||||
class=secop_mlz.entangle.NamedDigitalOutput
|
||||
class=frappy_mlz.entangle.NamedDigitalOutput
|
||||
tangodevice=tango://localhost:10000/box/plc/_symmetric
|
||||
value.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
|
||||
|
||||
[module T1]
|
||||
class=secop_mlz.entangle.AnalogInput
|
||||
class=frappy_mlz.entangle.AnalogInput
|
||||
tangodevice=tango://localhost:10000/box/plc/_t1
|
||||
.description=Temperature1 of the coils system
|
||||
#warnlimits=(0, 50)
|
||||
value.unit='degC'
|
||||
|
||||
[module T2]
|
||||
class=secop_mlz.entangle.AnalogInput
|
||||
class=frappy_mlz.entangle.AnalogInput
|
||||
tangodevice=tango://localhost:10000/box/plc/_t2
|
||||
.description=Temperature2 of the coils system
|
||||
#warnlimits=(0, 50)
|
||||
value.unit='degC'
|
||||
|
||||
[module T3]
|
||||
class=secop_mlz.entangle.AnalogInput
|
||||
class=frappy_mlz.entangle.AnalogInput
|
||||
tangodevice=tango://localhost:10000/box/plc/_t3
|
||||
.description=Temperature3 of the coils system
|
||||
#warnlimits=(0, 50)
|
||||
value.unit='degC'
|
||||
|
||||
[module T4]
|
||||
class=secop_mlz.entangle.AnalogInput
|
||||
class=frappy_mlz.entangle.AnalogInput
|
||||
tangodevice=tango://localhost:10000/box/plc/_t4
|
||||
.description=Temperature4 of the coils system
|
||||
#warnlimits=(0, 50)
|
||||
value.unit='degC'
|
||||
|
||||
[module currentsource]
|
||||
class=secop_mlz.entangle.PowerSupply
|
||||
class=frappy_mlz.entangle.PowerSupply
|
||||
tangodevice=tango://localhost:10000/box/lambda/curr
|
||||
.description=Device for the magnet power supply (current mode)
|
||||
abslimits=(0,200)
|
||||
@ -92,7 +92,7 @@ voltage=10
|
||||
.visibility=advanced
|
||||
|
||||
[module mf]
|
||||
class=secop_mlz.amagnet.GarfieldMagnet
|
||||
class=frappy_mlz.amagnet.GarfieldMagnet
|
||||
.description=magnetic field module, handling polarity switching and stuff
|
||||
subdev_currentsource=currentsource
|
||||
subdev_enable=enable
|
||||
|
32
cfg/ccr.cfg
32
cfg/ccr.cfg
@ -11,7 +11,7 @@ bindto=0.0.0.0
|
||||
bindport=10767
|
||||
|
||||
[module automatik]
|
||||
class=secop_mlz.entangle.NamedDigitalOutput
|
||||
class=frappy_mlz.entangle.NamedDigitalOutput
|
||||
tangodevice=tango://localhost:10000/box/plc/_automatik
|
||||
mapping=dict(Off=0,p1=1,p2=2)
|
||||
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"
|
||||
|
||||
[module compressor]
|
||||
class=secop_mlz.entangle.NamedDigitalOutput
|
||||
class=frappy_mlz.entangle.NamedDigitalOutput
|
||||
tangodevice=tango://localhost:10000/box/plc/_cooler_onoff
|
||||
mapping=dict(Off=0,On=1)
|
||||
description=control the compressor (on/off)
|
||||
|
||||
[module gas]
|
||||
class=secop_mlz.entangle.NamedDigitalOutput
|
||||
class=frappy_mlz.entangle.NamedDigitalOutput
|
||||
tangodevice=tango://localhost:10000/box/plc/_gas_onoff
|
||||
mapping=dict(Off=0,On=1)
|
||||
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
|
||||
|
||||
[module vacuum]
|
||||
class=secop_mlz.entangle.NamedDigitalOutput
|
||||
class=frappy_mlz.entangle.NamedDigitalOutput
|
||||
tangodevice=tango://localhost:10000/box/plc/_vacuum_Onoff
|
||||
mapping=dict(Off=0,On=1)
|
||||
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
|
||||
|
||||
[module p1]
|
||||
class=secop_mlz.entangle.AnalogInput
|
||||
class=frappy_mlz.entangle.AnalogInput
|
||||
tangodevice=tango://localhost:10000/box/plc/_p1
|
||||
value.unit='mbar'
|
||||
description=pressure sensor 1 (linear scale)
|
||||
|
||||
[module p2]
|
||||
class=secop_mlz.entangle.AnalogInput
|
||||
class=frappy_mlz.entangle.AnalogInput
|
||||
tangodevice=tango://localhost:10000/box/plc/_p2
|
||||
value.unit='mbar'
|
||||
description=pressure sensor 2 (selectable curve)
|
||||
|
||||
[module curve_p2]
|
||||
class=secop_mlz.entangle.NamedDigitalInput
|
||||
class=frappy_mlz.entangle.NamedDigitalInput
|
||||
tangodevice=tango://localhost:10000/box/plc/_curve
|
||||
value.default=0
|
||||
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
|
||||
[module T_sample]
|
||||
class=secop_mlz.entangle.Sensor
|
||||
class=frappy_mlz.entangle.Sensor
|
||||
tangodevice=tango://localhost:10000/box/sample/sensora
|
||||
value.unit='K'
|
||||
description=sample temperature
|
||||
|
||||
[module T_stick]
|
||||
class=secop_mlz.entangle.Sensor
|
||||
class=frappy_mlz.entangle.Sensor
|
||||
tangodevice=tango://localhost:10000/box/stick/sensorb
|
||||
value.unit='K'
|
||||
description=temperature at bottom of sample stick
|
||||
|
||||
[module T_coldhead]
|
||||
class=secop_mlz.entangle.Sensor
|
||||
class=frappy_mlz.entangle.Sensor
|
||||
tangodevice=tango://localhost:10000/box/coldhead/sensorc
|
||||
value.unit='K'
|
||||
description=temperature at coldhead
|
||||
|
||||
[module T_tube]
|
||||
class=secop_mlz.entangle.Sensor
|
||||
class=frappy_mlz.entangle.Sensor
|
||||
tangodevice=tango://localhost:10000/box/tube/sensord
|
||||
value.unit='K'
|
||||
description=temperature at thermal coupling tube <-> stick
|
||||
@ -98,7 +98,7 @@ description=temperature at thermal coupling tube <-> stick
|
||||
# regulations
|
||||
|
||||
[module T_stick_regulation]
|
||||
class=secop_mlz.entangle.TemperatureController
|
||||
class=frappy_mlz.entangle.TemperatureController
|
||||
tangodevice=tango://localhost:10000/box/stick/control2
|
||||
heateroutput.default=0
|
||||
description=regulation of stick temperature
|
||||
@ -114,7 +114,7 @@ value.unit='K'
|
||||
|
||||
# OMG! a NamedDigitalOutput, but with float'ints' 0..3
|
||||
[module T_stick_regulation_heaterrange]
|
||||
class=secop_mlz.entangle.AnalogOutput
|
||||
class=frappy_mlz.entangle.AnalogOutput
|
||||
tangodevice=tango://localhost:10000/box/stick/range2
|
||||
precision.default=1
|
||||
abslimits=(0,3)
|
||||
@ -122,7 +122,7 @@ description=heaterrange for stick regulation
|
||||
|
||||
|
||||
[module T_tube_regulation]
|
||||
class=secop_mlz.entangle.TemperatureController
|
||||
class=frappy_mlz.entangle.TemperatureController
|
||||
tangodevice=tango://localhost:10000/box/tube/control1
|
||||
description=regulation of tube temperature
|
||||
heateroutput.default=0
|
||||
@ -138,13 +138,13 @@ value.unit='K'
|
||||
|
||||
# OMG! a NamedDigitalOutput, but with float'ints' 0..3
|
||||
#[module T_tube_regulation_heaterrange]
|
||||
#class=secop_mlz.entangle.AnalogOutput
|
||||
#class=frappy_mlz.entangle.AnalogOutput
|
||||
#tangodevice=tango://localhost:10000/box/tube/range1
|
||||
#precision.default=1
|
||||
#abslimits=(0,3)
|
||||
|
||||
[module T_tube_regulation_heaterrange]
|
||||
class=secop_mlz.entangle.NamedDigitalOutput
|
||||
class=frappy_mlz.entangle.NamedDigitalOutput
|
||||
tangodevice=tango://localhost:10000/box/tube/range1
|
||||
mapping=dict(Off=0, Low=1, Medium=2, High=3)
|
||||
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
|
||||
|
||||
[module tc1]
|
||||
class=secop_demo.modules.CoilTemp
|
||||
class=frappy_demo.modules.CoilTemp
|
||||
sensor="X34598T7"
|
||||
|
||||
[module tc2]
|
||||
class=secop_demo.modules.CoilTemp
|
||||
class=frappy_demo.modules.CoilTemp
|
||||
sensor="X39284Q8'
|
||||
|
||||
|
||||
[module sensor1]
|
||||
class=secop_ess.epics.EpicsReadable
|
||||
class=frappy_ess.epics.EpicsReadable
|
||||
epics_version="v4"
|
||||
.group="Lakeshore336"
|
||||
value_pv="DEV:KRDG1"
|
||||
|
||||
|
||||
[module loop1]
|
||||
class=secop_ess.epics.EpicsTempCtrl
|
||||
class=frappy_ess.epics.EpicsTempCtrl
|
||||
epics_version="v4"
|
||||
.group="Lakeshore336"
|
||||
|
||||
@ -33,14 +33,14 @@ heaterrange_pv="DEV:RANGE_S1"
|
||||
|
||||
|
||||
[module sensor2]
|
||||
class=secop_ess.epics.EpicsReadable
|
||||
class=frappy_ess.epics.EpicsReadable
|
||||
epics_version="v4"
|
||||
.group="Lakeshore336"
|
||||
value_pv="DEV:KRDG2"
|
||||
|
||||
|
||||
[module loop2]
|
||||
class=secop_ess.epics.EpicsTempCtrl
|
||||
class=frappy_ess.epics.EpicsTempCtrl
|
||||
epics_version="v4"
|
||||
.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
|
||||
|
||||
[interface tcp]
|
||||
type = tcp
|
||||
bindto = 0.0.0.0
|
||||
bindport = 5000
|
||||
[INTERFACE]
|
||||
uri = tcp://5000
|
||||
|
||||
[module res]
|
||||
class = secop_psi.ls370res.ResChannel
|
||||
.channel = 3
|
||||
.description = resistivity
|
||||
.main = lsmain
|
||||
.iodev = lscom
|
||||
[lscom]
|
||||
class = frappy_psi.ls370sim.Ls370Sim
|
||||
description = simulated serial communicator to a LS 370
|
||||
visibility = 3
|
||||
|
||||
[module lsmain]
|
||||
class = secop_psi.ls370res.Main
|
||||
.description = main control of Lsc controller
|
||||
.iodev = lscom
|
||||
[sw]
|
||||
class = frappy_psi.ls370res.Switcher
|
||||
description = channel switcher for Lsc controller
|
||||
io = lscom
|
||||
|
||||
[module lscom]
|
||||
class = secop_psi.ls370sim.Ls370Sim
|
||||
.description = simulated serial communicator to a LS 370
|
||||
.visibility = 3
|
||||
[a]
|
||||
class = frappy_psi.ls370res.ResChannel
|
||||
channel = 1
|
||||
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
|
||||
|
||||
[module lsmain]
|
||||
class = secop_psi.ls370res.Main
|
||||
class = frappy_psi.ls370res.Main
|
||||
description = main control of Lsc controller
|
||||
uri = localhost:4567
|
||||
|
||||
[module res]
|
||||
class = secop_psi.ls370res.ResChannel
|
||||
class = frappy_psi.ls370res.ResChannel
|
||||
vexc = '2mV'
|
||||
channel = 3
|
||||
description = resistivity
|
||||
|
128
cfg/ppms.cfg
128
cfg/ppms.cfg
@ -6,119 +6,119 @@ description = PPMS at PSI
|
||||
uri = tcp://5000
|
||||
|
||||
[tt]
|
||||
class = secop_psi.ppms.Temp
|
||||
class = frappy_psi.ppms.Temp
|
||||
description = main temperature
|
||||
iodev = ppms
|
||||
io = ppms
|
||||
|
||||
[mf]
|
||||
class = secop_psi.ppms.Field
|
||||
class = frappy_psi.ppms.Field
|
||||
target.min = -9
|
||||
target.max = 9
|
||||
.description = magnetic field
|
||||
.iodev = ppms
|
||||
description = magnetic field
|
||||
io = ppms
|
||||
|
||||
[pos]
|
||||
class = secop_psi.ppms.Position
|
||||
.description = sample rotator
|
||||
.iodev = ppms
|
||||
class = frappy_psi.ppms.Position
|
||||
description = sample rotator
|
||||
io = ppms
|
||||
|
||||
[lev]
|
||||
class = secop_psi.ppms.Level
|
||||
.description = helium level
|
||||
.iodev = ppms
|
||||
class = frappy_psi.ppms.Level
|
||||
description = helium level
|
||||
io = ppms
|
||||
|
||||
[chamber]
|
||||
class = secop_psi.ppms.Chamber
|
||||
.description = chamber state
|
||||
.iodev = ppms
|
||||
class = frappy_psi.ppms.Chamber
|
||||
description = chamber state
|
||||
io = ppms
|
||||
|
||||
[r1]
|
||||
class = secop_psi.ppms.BridgeChannel
|
||||
.description = resistivity channel 1
|
||||
.no = 1
|
||||
class = frappy_psi.ppms.BridgeChannel
|
||||
description = resistivity channel 1
|
||||
no = 1
|
||||
value.unit = Ohm
|
||||
.iodev = ppms
|
||||
io = ppms
|
||||
|
||||
[r2]
|
||||
class = secop_psi.ppms.BridgeChannel
|
||||
.description = resistivity channel 2
|
||||
.no = 2
|
||||
class = frappy_psi.ppms.BridgeChannel
|
||||
description = resistivity channel 2
|
||||
no = 2
|
||||
value.unit = Ohm
|
||||
.iodev = ppms
|
||||
io = ppms
|
||||
|
||||
[r3]
|
||||
class = secop_psi.ppms.BridgeChannel
|
||||
.description = resistivity channel 3
|
||||
.no = 3
|
||||
class = frappy_psi.ppms.BridgeChannel
|
||||
description = resistivity channel 3
|
||||
no = 3
|
||||
value.unit = Ohm
|
||||
.iodev = ppms
|
||||
io = ppms
|
||||
|
||||
[r4]
|
||||
class = secop_psi.ppms.BridgeChannel
|
||||
.description = resistivity channel 4
|
||||
.no = 4
|
||||
class = frappy_psi.ppms.BridgeChannel
|
||||
description = resistivity channel 4
|
||||
no = 4
|
||||
value.unit = Ohm
|
||||
.iodev = ppms
|
||||
io = ppms
|
||||
|
||||
[i1]
|
||||
class = secop_psi.ppms.Channel
|
||||
.description = current channel 1
|
||||
.no = 1
|
||||
class = frappy_psi.ppms.Channel
|
||||
description = current channel 1
|
||||
no = 1
|
||||
value.unit = uA
|
||||
.iodev = ppms
|
||||
io = ppms
|
||||
|
||||
[i2]
|
||||
class = secop_psi.ppms.Channel
|
||||
.description = current channel 2
|
||||
.no = 2
|
||||
class = frappy_psi.ppms.Channel
|
||||
description = current channel 2
|
||||
no = 2
|
||||
value.unit = uA
|
||||
.iodev = ppms
|
||||
io = ppms
|
||||
|
||||
[i3]
|
||||
class = secop_psi.ppms.Channel
|
||||
.description = current channel 3
|
||||
.no = 3
|
||||
class = frappy_psi.ppms.Channel
|
||||
description = current channel 3
|
||||
no = 3
|
||||
value.unit = uA
|
||||
.iodev = ppms
|
||||
io = ppms
|
||||
|
||||
[i4]
|
||||
class = secop_psi.ppms.Channel
|
||||
.description = current channel 4
|
||||
.no = 4
|
||||
class = frappy_psi.ppms.Channel
|
||||
description = current channel 4
|
||||
no = 4
|
||||
value.unit = uA
|
||||
.iodev = ppms
|
||||
io = ppms
|
||||
|
||||
[v1]
|
||||
class = secop_psi.ppms.DriverChannel
|
||||
.description = voltage channel 1
|
||||
.no = 1
|
||||
class = frappy_psi.ppms.DriverChannel
|
||||
description = voltage channel 1
|
||||
no = 1
|
||||
value.unit = V
|
||||
.iodev = ppms
|
||||
io = ppms
|
||||
|
||||
[v2]
|
||||
class = secop_psi.ppms.DriverChannel
|
||||
.description = voltage channel 2
|
||||
.no = 2
|
||||
class = frappy_psi.ppms.DriverChannel
|
||||
description = voltage channel 2
|
||||
no = 2
|
||||
value.unit = V
|
||||
.iodev = ppms
|
||||
io = ppms
|
||||
|
||||
[tv]
|
||||
class = secop_psi.ppms.UserChannel
|
||||
.description = VTI temperature
|
||||
class = frappy_psi.ppms.UserChannel
|
||||
description = VTI temperature
|
||||
enabled = 1
|
||||
value.unit = K
|
||||
.iodev = ppms
|
||||
io = ppms
|
||||
|
||||
[ts]
|
||||
class = secop_psi.ppms.UserChannel
|
||||
.description = sample temperature
|
||||
class = frappy_psi.ppms.UserChannel
|
||||
description = sample temperature
|
||||
enabled = 1
|
||||
value.unit = K
|
||||
.iodev = ppms
|
||||
io = ppms
|
||||
|
||||
[ppms]
|
||||
class = secop_psi.ppms.Main
|
||||
.description = the main and poller module
|
||||
.class_id = QD.MULTIVU.PPMS.1
|
||||
.visibility = 3
|
||||
class = frappy_psi.ppms.Main
|
||||
description = the main and poller module
|
||||
class_id = QD.MULTIVU.PPMS.1
|
||||
visibility = 3
|
||||
pollinterval = 2
|
||||
|
@ -7,13 +7,13 @@ bindto = 0.0.0.0
|
||||
bindport = 5002
|
||||
|
||||
[module secnode]
|
||||
class = secop.SecNode
|
||||
class = frappy.SecNode
|
||||
description = a SEC node
|
||||
uri = tcp://localhost:5000
|
||||
|
||||
[module mf]
|
||||
class = secop.Proxy
|
||||
remote_class = secop_psi.ppms.Field
|
||||
class = frappy.Proxy
|
||||
remote_class = frappy_psi.ppms.Field
|
||||
description = magnetic field
|
||||
iodev = secnode
|
||||
value.min = -0.1
|
||||
|
@ -10,7 +10,7 @@ bindport=10767
|
||||
|
||||
|
||||
[module sim]
|
||||
class=secop.simulation.SimDrivable
|
||||
class=frappy.simulation.SimDrivable
|
||||
.description=simulation stuff
|
||||
.extra_params=param3,param4,jitter,ramp
|
||||
param3.datatype={"type":"bool"}
|
||||
|
@ -19,14 +19,14 @@ bindto=0.0.0.0
|
||||
bindport=10767
|
||||
|
||||
[module enable]
|
||||
class=secop.simulation.SimWritable
|
||||
class=frappy.simulation.SimWritable
|
||||
value.datatype={"type":"enum", "members":{'On':1,'Off':0}}
|
||||
target.datatype={"type":"enum", "members":{'On':1,'Off':0}}
|
||||
.description='Enables to Output of the Powersupply'
|
||||
.visibility='advanced'
|
||||
|
||||
[module polarity]
|
||||
class=secop.simulation.SimWritable
|
||||
class=frappy.simulation.SimWritable
|
||||
value.datatype={"type":"enum", "members":{'+1':1,'0':0,'-1':-1}}
|
||||
target.datatype={"type":"enum", "members":{'+1':1,'0':0,'-1':-1}}
|
||||
.description=polarity (+/-) switch
|
||||
@ -38,7 +38,7 @@ target.datatype={"type":"enum", "members":{'+1':1,'0':0,'-1':-1}}
|
||||
|
||||
|
||||
[module symmetry]
|
||||
class=secop.simulation.SimWritable
|
||||
class=frappy.simulation.SimWritable
|
||||
value.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
|
||||
@ -48,31 +48,31 @@ target.datatype={"type":"enum", "members":{'symmetric':1,'short':0, 'asymmetric'
|
||||
value.default = 'symmetric'
|
||||
|
||||
[module T1]
|
||||
class=secop.simulation.SimReadable
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Temperature1 of the coils system
|
||||
value.unit='degC'
|
||||
value.default = 23.45
|
||||
|
||||
[module T2]
|
||||
class=secop.simulation.SimReadable
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Temperature2 of the coils system
|
||||
value.unit='degC'
|
||||
value.default = 23.45
|
||||
|
||||
[module T3]
|
||||
class=secop.simulation.SimReadable
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Temperature3 of the coils system
|
||||
value.unit='degC'
|
||||
value.default = 23.45
|
||||
|
||||
[module T4]
|
||||
class=secop.simulation.SimReadable
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Temperature4 of the coils system
|
||||
value.unit='degC'
|
||||
value.default = 23.45
|
||||
|
||||
[module currentsource]
|
||||
class=secop.simulation.SimDrivable
|
||||
class=frappy.simulation.SimDrivable
|
||||
.description=Device for the magnet power supply (current mode)
|
||||
abslimits=(0,200)
|
||||
speed=1
|
||||
@ -98,7 +98,7 @@ window.datatype = {"type":"double", "min":0, "max":120, "unit":"s"}
|
||||
window.default = 10
|
||||
|
||||
[module mf]
|
||||
class=secop_mlz.amagnet.GarfieldMagnet
|
||||
class=frappy_mlz.amagnet.GarfieldMagnet
|
||||
.description=magnetic field module, handling polarity switching and stuff
|
||||
subdev_currentsource=currentsource
|
||||
subdev_enable=enable
|
||||
|
@ -11,7 +11,7 @@ bindto=0.0.0.0
|
||||
bindport=10767
|
||||
|
||||
[module T_cci3he1]
|
||||
class=secop.simulation.SimDrivable
|
||||
class=frappy.simulation.SimDrivable
|
||||
.description=Main temperature control node of cci3he1.
|
||||
.
|
||||
Controls the regulation loop of the ls370.
|
||||
@ -26,7 +26,7 @@ ramp.default=60
|
||||
.meaning=["temperature_regulation",40]
|
||||
|
||||
[module T_cci3he1_A]
|
||||
class=secop.simulation.SimReadable
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=3He pot temperature sensor. Also used for the regulation.
|
||||
.visibility=expert
|
||||
value.default=300
|
||||
@ -34,7 +34,7 @@ value.datatype={"type":"double","unit":"K"}
|
||||
.meaning=["temperature",38]
|
||||
|
||||
[module T_cci3he1_B]
|
||||
class=secop.simulation.SimReadable
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=(optional) sample temperature sensor close to sample.
|
||||
.visibility=user
|
||||
value.default=300
|
||||
@ -42,49 +42,49 @@ value.datatype={"type":"double","unit":"K"}
|
||||
.meaning=["temperature",39]
|
||||
|
||||
[module cci3he1_p1]
|
||||
class=secop.simulation.SimReadable
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Pressure at turbo pump inlet.
|
||||
.visibility=expert
|
||||
value.default=2e-3
|
||||
value.datatype={"type":"double","unit":"mbar"}
|
||||
|
||||
[module cci3he1_p2]
|
||||
class=secop.simulation.SimReadable
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Pressure at turbo pump outlet.
|
||||
.visibility=expert
|
||||
value.default=9.87
|
||||
value.datatype={"type":"double","unit":"mbar"}
|
||||
|
||||
[module cci3he1_p3]
|
||||
class=secop.simulation.SimReadable
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Pressure at compressor inlet.
|
||||
.visibility=expert
|
||||
value.default=19.99
|
||||
value.datatype={"type":"double","unit":"mbar"}
|
||||
|
||||
[module cci3he1_p4]
|
||||
class=secop.simulation.SimReadable
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Pressure at compressor outlet.
|
||||
.visibility=expert
|
||||
value.default=999
|
||||
value.datatype={"type":"double","unit":"mbar"}
|
||||
|
||||
[module cci3he1_p5]
|
||||
class=secop.simulation.SimReadable
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Pressure in dump tank.
|
||||
.visibility=expert
|
||||
value.default=567
|
||||
value.datatype={"type":"double","unit":"mbar"}
|
||||
|
||||
[module cci3he1_p6]
|
||||
class=secop.simulation.SimReadable
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Pressure in the vacuum dewar (ivc).
|
||||
.visibility=expert
|
||||
value.default=1e-3
|
||||
value.datatype={"type":"double","unit":"mbar"}
|
||||
|
||||
[module cci3he1_flow]
|
||||
class=secop.simulation.SimReadable
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Gas Flow (condensing line).
|
||||
.visibility=expert
|
||||
value.default=12.34
|
||||
|
@ -11,7 +11,7 @@ bindto=0.0.0.0
|
||||
bindport=10767
|
||||
|
||||
[module T_ccidu1]
|
||||
class=secop.simulation.SimDrivable
|
||||
class=frappy.simulation.SimDrivable
|
||||
.description=Main temperature control node of ccidu1.
|
||||
.
|
||||
Controls the regulation loop of the ls372.
|
||||
@ -26,7 +26,7 @@ ramp.default=60
|
||||
.meaning=["temperature_regulation",40]
|
||||
|
||||
[module T_ccidu1_A]
|
||||
class=secop.simulation.SimReadable
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=mixing chamber temperature sensor. Also used for the regulation.
|
||||
.visibility=expert
|
||||
value.default=300
|
||||
@ -34,7 +34,7 @@ value.datatype={"type":"double", "unit":"K"}
|
||||
.meaning=["temperature",38]
|
||||
|
||||
[module T_ccidu1_B]
|
||||
class=secop.simulation.SimReadable
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=(optional) sample temperature sensor close to sample.
|
||||
.visibility=user
|
||||
value.default=300
|
||||
@ -42,49 +42,49 @@ value.datatype={"type":"double", "unit":"K"}
|
||||
.meaning=["temperature",39]
|
||||
|
||||
[module ccidu1_pstill]
|
||||
class=secop.simulation.SimReadable
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Pressure at the still/turbo pump inlet.
|
||||
.visibility=expert
|
||||
value.default=999
|
||||
value.datatype={"type":"double", "unit":"mbar"}
|
||||
|
||||
[module ccidu1_pinlet]
|
||||
class=secop.simulation.SimReadable
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Pressure at forepump inlet/turbo pump outlet.
|
||||
.visibility=expert
|
||||
value.default=999
|
||||
value.datatype={"type":"double", "unit":"mbar"}
|
||||
|
||||
[module ccidu1_poutlet]
|
||||
class=secop.simulation.SimReadable
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Pressure at forepump outlet/compressor inlet.
|
||||
.visibility=expert
|
||||
value.default=999
|
||||
value.datatype={"type":"double", "unit":"mbar"}
|
||||
|
||||
[module ccidu1_pkond]
|
||||
class=secop.simulation.SimReadable
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Pressure at condensing line/compressor outlet.
|
||||
.visibility=expert
|
||||
value.default=999
|
||||
value.datatype={"type":"double", "unit":"mbar"}
|
||||
|
||||
[module ccidu1_ptank]
|
||||
class=secop.simulation.SimReadable
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Pressure in dump tank.
|
||||
.visibility=expert
|
||||
value.default=999
|
||||
value.datatype={"type":"double", "unit":"mbar"}
|
||||
|
||||
[module ccidu1_pvac]
|
||||
class=secop.simulation.SimReadable
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Pressure in the vacuum dewar (ivc).
|
||||
.visibility=expert
|
||||
value.default=999
|
||||
value.datatype={"type":"double", "unit":"mbar"}
|
||||
|
||||
[module ccidu1_flow]
|
||||
class=secop.simulation.SimReadable
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Gas Flow (condensing line).
|
||||
.visibility=expert
|
||||
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
|
||||
[module ccidu1_V6]
|
||||
class=secop.simulation.SimDrivable
|
||||
class=frappy.simulation.SimDrivable
|
||||
.description=Needle valve
|
||||
.visibility=expert
|
||||
value.default=99
|
||||
value.datatype={"type":"double", "min":0, "max":100, "unit":"%%"}
|
||||
|
||||
[module ccidu1_V3]
|
||||
class=secop.simulation.SimWritable
|
||||
class=frappy.simulation.SimWritable
|
||||
.description=Dump Valve
|
||||
.visibility=expert
|
||||
value.default="OFF"
|
||||
|
@ -12,7 +12,7 @@ bindto=0.0.0.0
|
||||
bindport=10767
|
||||
|
||||
[module T_ccr12]
|
||||
class=secop.simulation.SimDrivable
|
||||
class=frappy.simulation.SimDrivable
|
||||
.description=Main temperature control node of CCR12.
|
||||
.
|
||||
Switches between regulation on stick and regulation on tube depending on temperature requested.
|
||||
@ -30,7 +30,7 @@ target.default=300
|
||||
.meaning=["temperature_regulation", 20]
|
||||
|
||||
[module T_ccr12_stick]
|
||||
class=secop.simulation.SimDrivable
|
||||
class=frappy.simulation.SimDrivable
|
||||
.description=Temperature regulation for the sample stick in ccr12.
|
||||
.extra_params=ramp
|
||||
ramp.datatype={"type":"double", "min":0,"max":60, "unit":"K/min"}
|
||||
@ -42,7 +42,7 @@ target.default=300
|
||||
.meaning=["temperature_regulation", 15]
|
||||
|
||||
[module T_ccr12_tube]
|
||||
class=secop.simulation.SimDrivable
|
||||
class=frappy.simulation.SimDrivable
|
||||
.description=Temperature regulation for the tube of ccr12.
|
||||
.extra_params=ramp
|
||||
ramp.datatype={"type":"double", "min":0,"max":60, "unit":"K/min"}
|
||||
@ -54,28 +54,28 @@ target.default=300
|
||||
.meaning=["temperature_regulation", 10]
|
||||
|
||||
[module T_ccr12_A]
|
||||
class=secop.simulation.SimReadable
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=(optional) Sample temperature sensor.
|
||||
value.datatype={"type":"double", "unit":"K"}
|
||||
value.default=300
|
||||
.meaning=["temperature", 9]
|
||||
|
||||
[module T_ccr12_B]
|
||||
class=secop.simulation.SimReadable
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=(regulation) temperature sensor on stick.
|
||||
value.datatype={"type":"double", "unit":"K"}
|
||||
value.default=300
|
||||
.meaning=["temperature", 10]
|
||||
|
||||
[module T_ccr12_C]
|
||||
class=secop.simulation.SimReadable
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Temperature at the coldhead.
|
||||
value.datatype={"type":"double", "unit":"K"}
|
||||
value.default=70
|
||||
.meaning=["temperature", 1]
|
||||
|
||||
[module T_ccr12_D]
|
||||
class=secop.simulation.SimReadable
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=(regulation) temperature at coupling to stick.
|
||||
value.datatype={"type":"double", "unit":"K"}
|
||||
value.default=80
|
||||
@ -84,7 +84,7 @@ value.default=80
|
||||
|
||||
|
||||
[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.
|
||||
.visibility=expert
|
||||
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'
|
||||
|
||||
[module ccr12_compressor]
|
||||
class=secop.simulation.SimDrivable
|
||||
class=frappy.simulation.SimDrivable
|
||||
.description=Switches the compressor for the cooling stage on or off.
|
||||
.
|
||||
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'
|
||||
|
||||
[module ccr12_gas_switch]
|
||||
class=secop.simulation.SimWritable
|
||||
class=frappy.simulation.SimWritable
|
||||
.description=Switches the gas inlet on or off.
|
||||
.
|
||||
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'
|
||||
|
||||
[module ccr12_vacuum_switch]
|
||||
class=secop.simulation.SimWritable
|
||||
class=frappy.simulation.SimWritable
|
||||
.description=Switches the vacuum pumping valve on or off.
|
||||
.
|
||||
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'
|
||||
|
||||
[module ccr12_p1]
|
||||
class=secop.simulation.SimReadable
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Default pressure Sensor, linear scale 0..1000mbar
|
||||
value.datatype={"type":"double", "unit":"mbar"}
|
||||
value.default=999
|
||||
|
||||
[module ccr12_p2]
|
||||
class=secop.simulation.SimReadable
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Auxillary pressure Sensor.
|
||||
value.datatype={"type":"double", "unit":"mbar"}
|
||||
value.default=1e-6
|
||||
|
||||
[module ccr12_curve_p2]
|
||||
class=secop.simulation.SimWritable
|
||||
class=frappy.simulation.SimWritable
|
||||
.description=Curve for Aux pressure Sensor p2
|
||||
.visibility=expert
|
||||
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}}
|
||||
|
||||
[module ccr12_p1_limits]
|
||||
class=secop.simulation.SimWritable
|
||||
class=frappy.simulation.SimWritable
|
||||
.description=Limits for pressure regulation in P1.
|
||||
.visibility=expert
|
||||
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]
|
||||
|
||||
[module ccr12_p2_limits]
|
||||
class=secop.simulation.SimWritable
|
||||
class=frappy.simulation.SimWritable
|
||||
.description=Limits for pressure regulation in P2.
|
||||
.visibility=expert
|
||||
value.datatype={"type":"limit","members":{"type":"double", "min":0,"max":1000, "unit":"mbar"}}
|
||||
|
@ -12,7 +12,7 @@ bindto=0.0.0.0
|
||||
bindport=10767
|
||||
|
||||
[module T_ccr12]
|
||||
class=secop.simulation.SimDrivable
|
||||
class=frappy.simulation.SimDrivable
|
||||
.description=Main temperature control node of CCR12.
|
||||
.
|
||||
Switches between regulation on stick and regulation on tube depending on temperature requested.
|
||||
@ -41,7 +41,7 @@ userlimits.readonly=False
|
||||
.meaning=["temperature_regulation", 20]
|
||||
|
||||
[module T_ccr12_A]
|
||||
class=secop.simulation.SimReadable
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=(optional) Sample temperature sensor.
|
||||
.visibility=expert
|
||||
value.datatype={"type":"double", "min":0, "unit":"K"}
|
||||
@ -49,7 +49,7 @@ value.default=300
|
||||
.meaning=["temperature", 9]
|
||||
|
||||
[module T_ccr12_B]
|
||||
class=secop.simulation.SimReadable
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=(regulation) temperature sensor on stick.
|
||||
.visibility=expert
|
||||
value.datatype={"type":"double", "min":0, "unit":"K"}
|
||||
@ -57,7 +57,7 @@ value.default=300
|
||||
.meaning=["temperature", 10]
|
||||
|
||||
[module T_ccr12_C]
|
||||
class=secop.simulation.SimReadable
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Temperature at the coldhead.
|
||||
.visibility=expert
|
||||
value.datatype={"type":"double", "min":0, "unit":"K"}
|
||||
@ -65,7 +65,7 @@ value.default=70
|
||||
.meaning=["temperature", 1]
|
||||
|
||||
[module T_ccr12_D]
|
||||
class=secop.simulation.SimReadable
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=(regulation) temperature at coupling to stick.
|
||||
.visibility=expert
|
||||
value.datatype={"type":"double", "min":0, "unit":"K"}
|
||||
@ -75,7 +75,7 @@ value.default=80
|
||||
|
||||
|
||||
[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.
|
||||
.visibility=user
|
||||
.extra_params=switchpoints, mode
|
||||
@ -91,7 +91,7 @@ value.datatype={"type":"double", "min":0, "max":1000, "unit":"mbar"}
|
||||
value.default = 1e-5
|
||||
|
||||
[module ccr12_compressor]
|
||||
class=secop.simulation.SimDrivable
|
||||
class=frappy.simulation.SimDrivable
|
||||
.description=Switches the compressor for the cooling stage on or off.
|
||||
.
|
||||
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}}
|
||||
|
||||
[module ccr12_gas_switch]
|
||||
class=secop.simulation.SimWritable
|
||||
class=frappy.simulation.SimWritable
|
||||
.description=Switches the gas inlet on or off.
|
||||
.
|
||||
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'
|
||||
|
||||
[module ccr12_vacuum_switch]
|
||||
class=secop.simulation.SimWritable
|
||||
class=frappy.simulation.SimWritable
|
||||
.description=Switches the vacuum pumping valve on or off.
|
||||
.
|
||||
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'
|
||||
|
||||
[module ccr12_p1]
|
||||
class=secop.simulation.SimReadable
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Default pressure Sensor, linear scale 0..1000 mbar
|
||||
.
|
||||
Good candidate for a 'Sensor' Interface class!
|
||||
@ -140,7 +140,7 @@ userlimits.description=current user set limits for the pressure regulation.
|
||||
userlimits.readonly=False
|
||||
|
||||
[module ccr12_p2]
|
||||
class=secop.simulation.SimReadable
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Auxillary pressure Sensor.
|
||||
value.default=1e-6
|
||||
value.unit=mbar
|
||||
|
@ -10,7 +10,7 @@ bindto=0.0.0.0
|
||||
bindport=10767
|
||||
|
||||
[module T_htf02]
|
||||
class=secop.simulation.SimDrivable
|
||||
class=frappy.simulation.SimDrivable
|
||||
.description=Main temperature control node of htf02.
|
||||
.
|
||||
Controls the regulation loop of the Eurotherm.
|
||||
@ -26,7 +26,7 @@ ramp.readonly=False
|
||||
.meaning=["temperature", 10]
|
||||
|
||||
[module htf02_p]
|
||||
class=secop.simulation.SimReadable
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Pressure Sensor at sample space (ivc).
|
||||
value.datatype={"type":"double", "min":0, "unit":"mbar"}
|
||||
value.default=989
|
||||
|
@ -10,7 +10,7 @@ bindto=0.0.0.0
|
||||
bindport=10767
|
||||
|
||||
[module T_stressihtf2]
|
||||
class=secop.simulation.SimDrivable
|
||||
class=frappy.simulation.SimDrivable
|
||||
.description=Main temperature control node of Stressihtf2.
|
||||
value.datatype={"type":"double", "min":0, "unit":"degC"}
|
||||
value.default=20
|
||||
@ -30,7 +30,7 @@ userlimits.readonly=False
|
||||
.meaning=['temperature_regulation', 10]
|
||||
|
||||
[module T_stressihtf2_sample]
|
||||
class=secop.simulation.SimReadable
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=(optional) Sample temperature sensor.
|
||||
.visibility=expert
|
||||
value.default=300
|
||||
@ -38,7 +38,7 @@ value.datatype={"type":"double", "min":0, "unit":"degC"}
|
||||
.meaning=["temperature", 9]
|
||||
|
||||
[module stressihtf2_n2]
|
||||
class=secop.simulation.SimWritable
|
||||
class=frappy.simulation.SimWritable
|
||||
.description=Switches the N2 gas inlet on or off.
|
||||
.visibility=expert
|
||||
value.default='off'
|
||||
@ -47,7 +47,7 @@ target.datatype={"type":"enum", "members":{'off':0,'on':1}}
|
||||
target.default='off'
|
||||
|
||||
[module stressihtf2_he]
|
||||
class=secop.simulation.SimWritable
|
||||
class=frappy.simulation.SimWritable
|
||||
.description=Switches the He gas inlet on or off.
|
||||
.visibility=expert
|
||||
value.default='off'
|
||||
@ -56,7 +56,7 @@ target.datatype={"type":"enum", "members":{'off':0,'on':1}}
|
||||
target.default='off'
|
||||
|
||||
[module stressihtf2_lamps]
|
||||
class=secop.simulation.SimWritable
|
||||
class=frappy.simulation.SimWritable
|
||||
.description=Switches the heating lamps on or off.
|
||||
.visibility=expert
|
||||
value.default='on'
|
||||
@ -65,7 +65,7 @@ target.datatype={"type":"enum", "members":{'off':0,'on':1}}
|
||||
target.default='on'
|
||||
|
||||
[module stressihtf2_water_ok]
|
||||
class=secop.simulation.SimReadable
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Readout of the cooling water state.
|
||||
.visibility=expert
|
||||
value.default='ok'
|
||||
|
@ -10,7 +10,7 @@ bindto=0.0.0.0
|
||||
bindport=10767
|
||||
|
||||
[module T]
|
||||
class=secop.simulation.SimDrivable
|
||||
class=frappy.simulation.SimDrivable
|
||||
.description=Main temperature control node of Stressihtf2.
|
||||
value.datatype={"type":"double", "min":0, "unit":"degC"}
|
||||
value.default=20
|
||||
@ -30,7 +30,7 @@ userlimits.readonly=False
|
||||
.meaning=['temperature_regulation', 10]
|
||||
|
||||
[module T_sample]
|
||||
class=secop.simulation.SimReadable
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=(optional) Sample temperature sensor.
|
||||
.visibility=expert
|
||||
value.default=300
|
||||
@ -38,7 +38,7 @@ value.datatype={"type":"double", "min":0, "unit":"degC"}
|
||||
.meaning=["temperature", 9]
|
||||
|
||||
[module N2]
|
||||
class=secop.simulation.SimWritable
|
||||
class=frappy.simulation.SimWritable
|
||||
.description=Switches the N2 gas inlet on or off.
|
||||
.visibility=expert
|
||||
value.default='off'
|
||||
@ -47,7 +47,7 @@ target.datatype={"type":"enum", "members":{'off':0,'on':1}}
|
||||
target.default='off'
|
||||
|
||||
[module He]
|
||||
class=secop.simulation.SimWritable
|
||||
class=frappy.simulation.SimWritable
|
||||
.description=Switches the He gas inlet on or off.
|
||||
.visibility=expert
|
||||
value.default='off'
|
||||
@ -56,7 +56,7 @@ target.datatype={"type":"enum", "members":{'off':0,'on':1}}
|
||||
target.default='off'
|
||||
|
||||
[module lamps]
|
||||
class=secop.simulation.SimWritable
|
||||
class=frappy.simulation.SimWritable
|
||||
.description=Switches the heating lamps on or off.
|
||||
.visibility=expert
|
||||
value.default='on'
|
||||
@ -65,7 +65,7 @@ target.datatype={"type":"enum", "members":{'off':0,'on':1}}
|
||||
target.default='on'
|
||||
|
||||
[module water_ok]
|
||||
class=secop.simulation.SimReadable
|
||||
class=frappy.simulation.SimReadable
|
||||
.description=Readout of the cooling water state.
|
||||
.visibility=expert
|
||||
value.default='ok'
|
||||
|
@ -1,12 +1,12 @@
|
||||
[r3]
|
||||
class = secop.core.Proxy
|
||||
remote_class = secop.core.Readable
|
||||
class = frappy.core.Proxy
|
||||
remote_class = frappy.core.Readable
|
||||
description = temp sensor on 3He system
|
||||
uri = tcp://pc12694:5000
|
||||
export = False
|
||||
|
||||
[t3]
|
||||
class = secop_psi.softcal.Sensor
|
||||
class = frappy_psi.softcal.Sensor
|
||||
rawsensor = r3
|
||||
calib = X131346
|
||||
value.unit = K
|
||||
|
@ -11,7 +11,7 @@ bindto=0.0.0.0
|
||||
bindport=10767
|
||||
|
||||
[module T]
|
||||
class=secop_mlz.entangle.TemperatureController
|
||||
class=frappy_mlz.entangle.TemperatureController
|
||||
tangodevice=tango://localhost:10000/box/eurotherm/ctrl
|
||||
.description=Main temperature control node of Stressihtf2.
|
||||
value.unit='degC'
|
||||
@ -41,42 +41,42 @@ pid.default=[1,0,0]
|
||||
speed.default=0
|
||||
|
||||
[module T_sample_a]
|
||||
class=secop_mlz.entangle.Sensor
|
||||
class=frappy_mlz.entangle.Sensor
|
||||
tangodevice=tango://localhost:10000/box/eurotherm/sensora
|
||||
.description=Regulation temperature sensor.
|
||||
.visibility=user
|
||||
value.unit='degC'
|
||||
|
||||
[module T_sample_b]
|
||||
class=secop_mlz.entangle.Sensor
|
||||
class=frappy_mlz.entangle.Sensor
|
||||
tangodevice=tango://localhost:10000/box/eurotherm/sensorb
|
||||
.description=(optional) Sample temperature sensor.
|
||||
.visibility=expert
|
||||
value.unit='degC'
|
||||
|
||||
[module N2]
|
||||
class=secop_mlz.entangle.NamedDigitalOutput
|
||||
class=frappy_mlz.entangle.NamedDigitalOutput
|
||||
tangodevice=tango://localhost:10000/box/plc/_gas1
|
||||
.description=Switches the N2 gas inlet on or off.
|
||||
.visibility=expert
|
||||
mapping=dict(off=0,on=1)
|
||||
|
||||
[module He]
|
||||
class=secop_mlz.entangle.NamedDigitalOutput
|
||||
class=frappy_mlz.entangle.NamedDigitalOutput
|
||||
tangodevice=tango://localhost:10000/box/plc/_gas2
|
||||
.description=Switches the He gas inlet on or off.
|
||||
.visibility=expert
|
||||
mapping=dict(off=0,on=1)
|
||||
|
||||
[module lamps]
|
||||
class=secop_mlz.entangle.NamedDigitalOutput
|
||||
class=frappy_mlz.entangle.NamedDigitalOutput
|
||||
tangodevice=tango://localhost:10000/box/plc/_onoff
|
||||
.description=Switches the heating lamps on or off.
|
||||
.visibility=expert
|
||||
mapping=dict(off=0,on=1)
|
||||
|
||||
[module water_ok]
|
||||
class=secop_mlz.entangle.NamedDigitalInput
|
||||
class=frappy_mlz.entangle.NamedDigitalInput
|
||||
tangodevice=tango://localhost:10000/box/plc/_waterok
|
||||
.description=Readout of the cooling water state.
|
||||
.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
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
RUN apt-get update && \
|
||||
@ -11,7 +11,7 @@ RUN apt-get update && \
|
||||
locales \
|
||||
python3 \
|
||||
python3-dev \
|
||||
python3-pytango \
|
||||
python3-tango \
|
||||
python3-venv python3-setuptools \
|
||||
virtualenv
|
||||
|
||||
@ -33,14 +33,15 @@ RUN virtualenv /home/jenkins/tools2 && \
|
||||
rm -rf /home/jenkins/tools2src
|
||||
|
||||
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 && \
|
||||
pip install -U pip wheel setuptools && \
|
||||
pip install -r /home/jenkins/playground/requirements-dev.txt -r /home/jenkins/playground/requirements.txt pylint pytest && \
|
||||
rm -rf /home/jenkins/playground
|
||||
pip install -r /home/jenkins/frappy/requirements-dev.txt -r /home/jenkins/frappy/requirements.txt pylint pytest && \
|
||||
rm -rf /home/jenkins/frappy
|
||||
|
||||
|
||||
FROM base AS docs
|
||||
ARG PYVER=python3
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
USER root
|
||||
@ -50,6 +51,8 @@ RUN apt-get update && \
|
||||
texlive-latex-base \
|
||||
texlive-latex-recommended \
|
||||
texlive-fonts-recommended \
|
||||
texlive-fonts-extra \
|
||||
tex-gyre \
|
||||
texlive-base \
|
||||
texlive-binaries \
|
||||
latexmk
|
||||
|
56
ci/Jenkinsfile
vendored
56
ci/Jenkinsfile
vendored
@ -4,7 +4,7 @@ properties([
|
||||
daysToKeepStr: '',
|
||||
numToKeepStr: '50')),
|
||||
parameters([
|
||||
string(defaultValue: 'sine2020/secop/playground',
|
||||
string(defaultValue: 'secop/frappy',
|
||||
description: '', name: 'GERRIT_PROJECT'),
|
||||
string(defaultValue: 'master',
|
||||
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.txt
|
||||
pip install isort pylint
|
||||
python3 setup.py develop
|
||||
pip install -e .
|
||||
export PYTHONIOENCODING=utf8
|
||||
|
||||
echo "$changedFiles"
|
||||
if [[ -n "$changedFiles" ]]; then
|
||||
set -o pipefail
|
||||
pylint $changedFiles | tee pylint_results.txt
|
||||
isort -df $changedFiles | tee isort_results.txt
|
||||
isort --df $changedFiles | tee isort_results.txt
|
||||
fi
|
||||
"""
|
||||
withCredentials([string(credentialsId: 'GERRITHTTP',
|
||||
variable: 'GERRITHTTP')]) {
|
||||
sh """\
|
||||
#!/bin/bash
|
||||
#!/bin/bash
|
||||
if [ -f pylint_results.txt ] ; then
|
||||
/home/jenkins/tools2/bin/pylint2gerrit
|
||||
mv pylint_results.txt pylint-${pyver}.txt
|
||||
@ -67,19 +67,11 @@ fi
|
||||
} // credentials
|
||||
|
||||
echo "pylint result: $res"
|
||||
this.verifyresult.put('pylint'+pyver, 1)
|
||||
if ( res != 0 ) {
|
||||
currentBuild.result='FAILURE'
|
||||
this.verifyresult.put('pylint'+ pyver, -1)
|
||||
status = 'FAILURE'
|
||||
}
|
||||
|
||||
gerritverificationpublisher([
|
||||
verifyStatusValue: this.verifyresult['pylint'+pyver],
|
||||
verifyStatusCategory: 'pylint ',
|
||||
verifyStatusName: 'pylint-'+pyver,
|
||||
verifyStatusReporter: 'jenkins',
|
||||
verifyStatusRerun: '!recheck'])
|
||||
archiveArtifacts([allowEmptyArchive: true,
|
||||
artifacts: 'pylint-*.txt'])
|
||||
recordIssues([enabledForFailure: true,
|
||||
@ -90,9 +82,6 @@ fi
|
||||
failedTotalAll: 1])
|
||||
|
||||
|
||||
if (status == 'FAILURE') {
|
||||
throw new Exception('Failure in pylint with ' + pyver)
|
||||
}
|
||||
}
|
||||
} // run_pylint
|
||||
|
||||
@ -104,7 +93,6 @@ def run_tests(pyver) {
|
||||
addopts = --junit-xml=pytest.xml --junit-prefix=''' + pyver
|
||||
|
||||
def status = "OK"
|
||||
verifyresult.put(pyver, 0)
|
||||
try {
|
||||
timeout(5) {
|
||||
sh '''\
|
||||
@ -112,28 +100,17 @@ addopts = --junit-xml=pytest.xml --junit-prefix=''' + pyver
|
||||
. /home/jenkins/secopvenv/bin/activate
|
||||
pip install -r requirements-dev.txt
|
||||
pip install -r requirements.txt
|
||||
python3 setup.py develop
|
||||
pip install -e .
|
||||
make test
|
||||
'''
|
||||
verifyresult.put(pyver, 1)
|
||||
}
|
||||
} catch (all) {
|
||||
currentBuild.result = '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,
|
||||
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
|
||||
pip install -r requirements-dev.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') {
|
||||
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
|
||||
git worktree add tmpmaster origin/master
|
||||
cd tmpmaster
|
||||
docker build --target base --tag secop_base:latest ci
|
||||
docker build --target docs --tag secop_docs:latest ci
|
||||
docker build --target base --tag frappy_base:latest ci
|
||||
docker build --target docs --tag frappy_docs:latest ci
|
||||
cd ..
|
||||
rm -rf tmpmaster
|
||||
'''
|
||||
}
|
||||
|
||||
stage('execute tests') {
|
||||
def img = docker.image('secop_base:latest')
|
||||
def docimg = docker.image('secop_docs:latest')
|
||||
def img = docker.image('frappy_base:latest')
|
||||
def docimg = docker.image('frappy_docs:latest')
|
||||
|
||||
parallel 'Test': {
|
||||
img.inside {
|
||||
@ -252,9 +221,10 @@ node("dockerhost") {
|
||||
if (GERRIT_EVENT_TYPE == 'change-merged')
|
||||
{
|
||||
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:
|
||||
|
||||
docker build --target <base|docs> --tag secop_<base|docs>:latest .
|
||||
docker build --target <base|docs> --tag frappy_<base|docs>:latest .
|
||||
|
||||
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)
|
||||
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.
|
||||
|
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
|
||||
|
||||
* fix secop-generator
|
||||
@ -133,7 +393,7 @@ secop-core (0.10.5) unstable; urgency=low
|
||||
|
||||
[ 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
|
||||
|
||||
@ -142,7 +402,7 @@ secop-core (0.10.3) unstable; urgency=low
|
||||
|
||||
[ 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
|
||||
|
||||
@ -153,7 +413,7 @@ secop-core (0.10.2) unstable; urgency=low
|
||||
|
||||
[ 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
|
||||
|
||||
@ -162,7 +422,7 @@ secop-core (0.10.1) unstable; urgency=low
|
||||
|
||||
[ 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
|
||||
|
||||
@ -171,7 +431,7 @@ secop-core (0.10.0) unstable; urgency=low
|
||||
|
||||
[ 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
|
||||
|
||||
@ -198,7 +458,7 @@ secop-core (0.9.0) unstable; urgency=low
|
||||
|
||||
[ 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
|
||||
|
||||
@ -207,7 +467,7 @@ secop-core (0.8.1) unstable; urgency=low
|
||||
|
||||
[ 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
|
||||
|
||||
@ -275,7 +535,7 @@ secop-core (0.8.0) unstable; urgency=low
|
||||
|
||||
[ 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
|
||||
|
||||
@ -311,7 +571,7 @@ secop-core (0.7.0) unstable; urgency=low
|
||||
|
||||
[ 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
|
||||
|
||||
@ -376,7 +636,7 @@ secop-core (0.6.4) unstable; urgency=low
|
||||
|
||||
[ 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
|
||||
|
||||
@ -390,7 +650,7 @@ secop-core (0.6.3) unstable; urgency=low
|
||||
|
||||
[ 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
|
||||
|
||||
@ -429,7 +689,7 @@ secop-core (0.6.2) unstable; urgency=low
|
||||
|
||||
[ 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
|
||||
|
||||
@ -438,7 +698,7 @@ secop-core (0.6.1) unstable; urgency=low
|
||||
|
||||
[ 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
|
||||
|
||||
@ -458,7 +718,7 @@ secop-core (0.6.0) unstable; urgency=low
|
||||
|
||||
[ 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
|
||||
|
||||
@ -470,7 +730,7 @@ secop-core (0.5.0) unstable; urgency=low
|
||||
* fix amagnet
|
||||
* add info about Meeting @PSI
|
||||
* fix typo and include comment from Niklas
|
||||
* playground: give sequencermixin a loopcounter (per step)
|
||||
* give sequencermixin a loopcounter (per step)
|
||||
|
||||
[ Frank Wutzler ]
|
||||
* describe SECoP motivation discussed in meeting 2017-11-27
|
||||
@ -521,7 +781,7 @@ secop-core (0.5.0) unstable; urgency=low
|
||||
|
||||
[ 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
|
||||
|
||||
@ -530,7 +790,7 @@ secop-core (0.4.4) unstable; urgency=low
|
||||
|
||||
[ 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
|
||||
|
||||
@ -539,7 +799,7 @@ secop-core (0.4.3) unstable; urgency=low
|
||||
|
||||
[ 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
|
||||
|
||||
@ -548,7 +808,7 @@ secop-core (0.4.2) unstable; urgency=low
|
||||
|
||||
[ 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
|
||||
|
||||
@ -557,7 +817,7 @@ secop-core (0.4.1) unstable; urgency=low
|
||||
|
||||
[ 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
|
||||
|
||||
@ -567,7 +827,7 @@ secop-core (0.4.0) unstable; urgency=low
|
||||
|
||||
[ 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
|
||||
|
||||
@ -633,7 +893,7 @@ secop-core (0.3.0) unstable; urgency=low
|
||||
|
||||
[ 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
|
||||
|
||||
@ -642,7 +902,7 @@ secop-core (0.2.0) unstable; urgency=low
|
||||
|
||||
[ 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
|
||||
|
||||
@ -651,7 +911,7 @@ secop-core (0.1.1) unstable; urgency=low
|
||||
|
||||
[ 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
|
||||
|
||||
@ -660,7 +920,7 @@ secop-core (0.1.0) unstable; urgency=low
|
||||
|
||||
[ 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
|
||||
|
||||
@ -669,7 +929,7 @@ secop-core (0.0.8) unstable; urgency=low
|
||||
|
||||
[ 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
|
||||
|
||||
@ -678,7 +938,7 @@ secop-core (0.0.7) unstable; urgency=low
|
||||
|
||||
[ 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
|
||||
|
||||
@ -688,7 +948,7 @@ secop-core (0.0.6) unstable; urgency=low
|
||||
|
||||
[ 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
|
||||
|
||||
@ -697,7 +957,7 @@ secop-core (0.0.5) unstable; urgency=low
|
||||
|
||||
[ 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
|
||||
|
||||
@ -706,7 +966,7 @@ secop-core (0.0.4) unstable; urgency=low
|
||||
|
||||
[ 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
|
||||
|
||||
@ -716,7 +976,7 @@ secop-core (0.0.3) unstable; urgency=low
|
||||
|
||||
[ 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
|
||||
|
||||
@ -794,4 +1054,4 @@ secop-core (0.0.2) unstable; urgency=medium
|
||||
|
||||
[ 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
|
||||
Priority: optional
|
||||
Maintainer: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||
@ -23,7 +23,7 @@ Build-Depends: debhelper (>= 11~),
|
||||
Standards-Version: 4.1.4
|
||||
X-Python3-Version: >= 3.6
|
||||
|
||||
Package: secop-core
|
||||
Package: frappy-core
|
||||
Architecture: all
|
||||
Depends: python3 (>= 3.6),
|
||||
${misc:Depends},
|
||||
@ -35,60 +35,72 @@ Depends: python3 (>= 3.6),
|
||||
python3-mlzlog,
|
||||
markdown,
|
||||
python3-daemon
|
||||
Replaces: secop-core (<= 0.14.3)
|
||||
Breaks: secop-core (<= 0.14.3)
|
||||
Description: Frappy SECoP core system
|
||||
contains the core server and client libraries and the server binary
|
||||
as well as the systemd integration
|
||||
|
||||
#Package: secop-doc
|
||||
#Package: frappy-doc
|
||||
#Architecture: all
|
||||
#Section: doc
|
||||
#Depends: ${sphinxdoc:Depends},
|
||||
# ${misc:Depends}
|
||||
#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
|
||||
Depends: secop-core,
|
||||
Depends: frappy-core,
|
||||
${misc:Depends},
|
||||
${python3:Depends},
|
||||
python3-pyqt (>=4)
|
||||
Replaces: secop-gui (<= 0.14.3)
|
||||
Breaks: secop-gui (<= 0.14.3)
|
||||
Description: Frappy SECoP gui client + cfgtool
|
||||
contains the GUI client and the configurator
|
||||
|
||||
Package: secop-demo
|
||||
Package: frappy-demo
|
||||
Architecture: all
|
||||
Depends: secop-core,
|
||||
Depends: frappy-core,
|
||||
${misc: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
|
||||
for demonstration purposes
|
||||
|
||||
Package: secop-ess
|
||||
Package: frappy-ess
|
||||
Architecture: all
|
||||
Depends: secop-core,
|
||||
Depends: frappy-core,
|
||||
${misc: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
|
||||
Modules specific for ESS
|
||||
|
||||
Package: secop-mlz
|
||||
Package: frappy-mlz
|
||||
Architecture: all
|
||||
Depends: secop-core,
|
||||
Depends: frappy-core,
|
||||
${misc:Depends},
|
||||
${python3:Depends},
|
||||
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
|
||||
Modules specific for MLZ
|
||||
|
||||
Package: secop-psi
|
||||
Package: frappy-psi
|
||||
Architecture: all
|
||||
Depends: secop-core,
|
||||
Depends: frappy-core,
|
||||
${misc: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
|
||||
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/
|
||||
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
|
||||
see https://www.github.com/SampleEnvironment/SECoP
|
||||
|
||||
Files: *
|
||||
Copyright: 2016-2019 by the FRAPPY-SECOP contributors (see AUTHORS)
|
||||
Copyright: 2016-2022 by the FRAPPY-SECOP contributors (see AUTHORS)
|
||||
License: GPL-2
|
||||
|
||||
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
|
||||
|
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.
|
||||
#export DH_VERBOSE=1
|
||||
|
||||
export PYBUILD_NAME=secop
|
||||
export PYBUILD_NAME=frappy
|
||||
export PYBUILD_TEST_PYTEST=1
|
||||
|
||||
override_dh_install:
|
||||
rmdir debian/tmp
|
||||
mv debian/python3-secop debian/tmp
|
||||
mv debian/python3-frappy debian/tmp
|
||||
|
||||
dh_install -i -O--buildsystem=pybuild
|
||||
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;
|
||||
}
|
||||
|
||||
/* 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
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# SECoP documentation build configuration file, created by
|
||||
# Frappy documentation build configuration file, created by
|
||||
# sphinx-quickstart on Mon Sep 11 10:58:28 2017.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its
|
||||
@ -27,7 +27,7 @@ from os import path
|
||||
# Add import path for inplace usage
|
||||
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 ------------------------------------------------
|
||||
@ -57,9 +57,9 @@ source_suffix = ['.rst', '.md']
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = 'SECoP'
|
||||
#copyright = '2017, Enrico Faulhaber, Markus Zolliker'
|
||||
copyright = '2017, SECoP Committee'
|
||||
project = 'Frappy'
|
||||
copyright = '2017-2021, Enrico Faulhaber, Markus Zolliker,'
|
||||
#copyright = '2017, SECoP Committee'
|
||||
author = 'Enrico Faulhaber, Markus Zolliker'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
@ -76,7 +76,7 @@ version = release.split('-')[0]
|
||||
#
|
||||
# This is also used if you do content translation via gettext catalogs.
|
||||
# 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
|
||||
# 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.
|
||||
todo_include_todos = True
|
||||
|
||||
autodoc_default_options = {
|
||||
'member-order': 'bysource',
|
||||
'show-inheritance': True,
|
||||
}
|
||||
default_role = 'any'
|
||||
|
||||
# -- Options for HTML output ----------------------------------------------
|
||||
@ -106,11 +110,6 @@ html_theme = 'sphinx_rtd_theme'
|
||||
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,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
@ -136,7 +135,7 @@ html_sidebars = {
|
||||
# -- Options for HTMLHelp output ------------------------------------------
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'SECoPdoc'
|
||||
htmlhelp_basename = 'Frappydoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
@ -163,7 +162,7 @@ latex_elements = {
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
(master_doc, 'SECoP.tex', 'SECoP source documentation',
|
||||
(master_doc, 'Frappy.tex', 'Frappy source documentation',
|
||||
'Enrico Faulhaber, Markus Zolliker', 'manual'),
|
||||
]
|
||||
|
||||
@ -173,7 +172,7 @@ latex_documents = [
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
(master_doc, 'secop', 'SECoP source documentation',
|
||||
(master_doc, 'frappy', 'Frappy source documentation',
|
||||
[author], 1)
|
||||
]
|
||||
|
||||
@ -184,8 +183,8 @@ man_pages = [
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
(master_doc, 'SECoP', 'SECoP source documentation',
|
||||
author, 'SECoP', 'One line description of project.',
|
||||
(master_doc, 'Frappy', 'Frappy source documentation',
|
||||
author, 'Frappy', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
@ -212,4 +211,9 @@ epub_exclude_files = ['search.html']
|
||||
|
||||
|
||||
# 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::
|
||||
:maxdepth: 2
|
||||
|
||||
server/index
|
||||
client/index
|
||||
framework/index
|
||||
gui/index
|
||||
facility/index
|
||||
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
introduction
|
||||
tutorial
|
||||
reference
|
||||
frappy_psi
|
||||
frappy_demo
|
||||
frappy_mlz
|
||||
frappy_ess
|
||||
|
||||
* :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 sys
|
||||
import fnmatch
|
||||
from os import path
|
||||
|
||||
from secop.lib import getGeneralConfig
|
||||
from frappy.lib import generalConfig
|
||||
|
||||
|
||||
def main():
|
||||
normal_dir = sys.argv[1]
|
||||
|
||||
global_config = getGeneralConfig()
|
||||
config_dir = global_config['confdir']
|
||||
generalConfig.init()
|
||||
config_dir = generalConfig['confdir']
|
||||
|
||||
secop_unit = '/lib/systemd/system/secop@.service'
|
||||
wants_dir = normal_dir + '/secop.target.wants'
|
||||
frappy_unit = '/lib/systemd/system/frappy@.service'
|
||||
wants_dir = normal_dir + '/frappy.target.wants'
|
||||
|
||||
all_servers = [base for (base, ext) in
|
||||
map(path.splitext, os.listdir(config_dir)) if ext == '.cfg']
|
||||
all_servers.sort()
|
||||
|
||||
for srv in all_servers:
|
||||
symlink = '%s/secop@%s.service' % (normal_dir, srv)
|
||||
os.symlink(secop_unit, symlink)
|
||||
symlink = '%s/frappy@%s.service' % (normal_dir, srv)
|
||||
os.symlink(frappy_unit, symlink)
|
||||
if not path.isdir(wants_dir):
|
||||
os.mkdir(wants_dir)
|
||||
os.symlink(symlink, '%s/%s' % (wants_dir, path.basename(symlink)))
|
||||
|
||||
# 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__':
|
@ -1,10 +1,10 @@
|
||||
[Unit]
|
||||
Description=SECoP SEC-node: %i
|
||||
Description=FRAPPY SECoP SEC-node: %i
|
||||
After=network-online.service
|
||||
|
||||
[Service]
|
||||
Type=notify
|
||||
ExecStart=/usr/bin/secop-server %I
|
||||
ExecStart=/usr/bin/frappy-server %I
|
||||
Restart=on-abnormal
|
||||
RestartSec=30
|
||||
|
@ -3,12 +3,12 @@
|
||||
block_cipher = None
|
||||
|
||||
|
||||
a = Analysis(['bin\\secop-server'],
|
||||
a = Analysis(['bin\\frappy-server'],
|
||||
pathex=['.'],
|
||||
binaries=[],
|
||||
datas=[],
|
||||
hiddenimports=['secop.protocol', 'secop.protocol.dispatcher', 'secop.protocol.interface', 'secop.protocol.interface.tcp',
|
||||
'secop_psi.ppmssim', 'secop_psi.ppmswindows', 'secop_psi.ppms'],
|
||||
hiddenimports=['frappy.protocol', 'frappy.protocol.dispatcher', 'frappy.protocol.interface', 'frappy.protocol.interface.tcp',
|
||||
'frappy_psi.ppmssim', 'frappy_psi.ppmswindows', 'frappy_psi.ppms'],
|
||||
hookspath=[],
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
@ -22,7 +22,7 @@ exe = EXE(pyz,
|
||||
a.scripts,
|
||||
[],
|
||||
exclude_binaries=True,
|
||||
name='secop-server',
|
||||
name='frappy-server',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
@ -35,4 +35,4 @@ coll = COLLECT(exe,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
name='secop-server')
|
||||
name='frappy-server')
|
@ -18,29 +18,33 @@
|
||||
# Module authors:
|
||||
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||
# Markus Zolliker <markus.zolliker@psi.ch>
|
||||
# Alexander Zaft <a.zaft@fz-juelich.de>
|
||||
#
|
||||
# *****************************************************************************
|
||||
"""general SECoP client"""
|
||||
|
||||
import time
|
||||
import queue
|
||||
import re
|
||||
import json
|
||||
from threading import Event, RLock, current_thread
|
||||
import queue
|
||||
import time
|
||||
from collections import defaultdict
|
||||
from threading import Event, RLock, current_thread
|
||||
|
||||
from secop.lib import mkthread, formatExtendedTraceback, formatExtendedStack
|
||||
from secop.lib.asynconn import AsynConn, ConnectionClosed
|
||||
from secop.datatypes import get_datatype
|
||||
from secop.protocol.interface import encode_msg_frame, decode_msg
|
||||
from secop.protocol.messages import REQUEST2REPLY, ERRORPREFIX, EVENTREPLY, WRITEREQUEST, WRITEREPLY, \
|
||||
READREQUEST, READREPLY, IDENTREQUEST, IDENTPREFIX, ENABLEEVENTSREQUEST, COMMANDREQUEST, \
|
||||
DESCRIPTIONREQUEST, HEARTBEATREQUEST
|
||||
import secop.errors
|
||||
import secop.params
|
||||
import frappy.errors
|
||||
import frappy.params
|
||||
from frappy.datatypes import get_datatype
|
||||
from frappy.lib import mkthread
|
||||
from frappy.lib.asynconn import AsynConn, ConnectionClosed
|
||||
from frappy.protocol.interface import decode_msg, encode_msg_frame
|
||||
from frappy.protocol.messages import COMMANDREQUEST, \
|
||||
DESCRIPTIONREQUEST, ENABLEEVENTSREQUEST, ERRORPREFIX, \
|
||||
EVENTREPLY, HEARTBEATREQUEST, IDENTPREFIX, IDENTREQUEST, \
|
||||
READREPLY, READREQUEST, REQUEST2REPLY, WRITEREPLY, WRITEREQUEST
|
||||
|
||||
# replies to be handled for cache
|
||||
UPDATE_MESSAGES = {EVENTREPLY, READREPLY, WRITEREPLY, ERRORPREFIX + READREQUEST, ERRORPREFIX + EVENTREPLY}
|
||||
|
||||
VERSIONFMT= re.compile(r'^[^,]*?ISSE[^,]*,SECoP,')
|
||||
|
||||
class UNREGISTER:
|
||||
"""a magic value, used a returned value in a callback
|
||||
@ -160,7 +164,6 @@ class ProxyClient:
|
||||
if not cblist:
|
||||
self.callbacks[cbname].pop(key)
|
||||
|
||||
|
||||
def callback(self, key, cbname, *args):
|
||||
"""perform callbacks
|
||||
|
||||
@ -243,9 +246,16 @@ class SecopClient(ProxyClient):
|
||||
self.secop_version = reply.decode('utf-8')
|
||||
else:
|
||||
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' %
|
||||
(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
|
||||
self._running = True
|
||||
self._rxthread = mkthread(self.__rxthread)
|
||||
@ -317,7 +327,7 @@ class SecopClient(ProxyClient):
|
||||
if module_param is not None:
|
||||
if action.startswith(ERRORPREFIX):
|
||||
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
|
||||
else:
|
||||
timestamp = data[1].get('t', None)
|
||||
@ -356,7 +366,7 @@ class SecopClient(ProxyClient):
|
||||
except ConnectionClosed:
|
||||
pass
|
||||
except Exception as e:
|
||||
self.log.error('rxthread ended with %s' % e)
|
||||
self.log.error('rxthread ended with %r', e)
|
||||
self._rxthread = None
|
||||
self.disconnect(False)
|
||||
if self._shutdown:
|
||||
@ -393,7 +403,7 @@ class SecopClient(ProxyClient):
|
||||
if time.time() > self.disconnect_time + self.reconnect_timeout:
|
||||
if self.online: # was recently connected
|
||||
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.warning(formatExtendedTraceback())
|
||||
self._set_state(False)
|
||||
@ -490,17 +500,15 @@ class SecopClient(ProxyClient):
|
||||
|
||||
def _unhandled_message(self, 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):
|
||||
# treat reconnecting 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
|
||||
# remark: reconnecting is treated as 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):
|
||||
"""make a request"""
|
||||
@ -520,7 +528,7 @@ class SecopClient(ProxyClient):
|
||||
action, _, data = entry[2] # pylint: disable=unpacking-non-sequence
|
||||
if action.startswith(ERRORPREFIX):
|
||||
errcls = self.error_map(data[0])
|
||||
raise errcls('on SEC-Node: ' + data[1])
|
||||
raise errcls(data[1])
|
||||
return entry[2] # reply
|
||||
|
||||
def request(self, action, ident=None, data=None):
|
||||
@ -535,7 +543,7 @@ class SecopClient(ProxyClient):
|
||||
"""forced read over connection"""
|
||||
try:
|
||||
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
|
||||
pass
|
||||
return self.cache.get((module, parameter), None)
|
||||
@ -563,7 +571,7 @@ class SecopClient(ProxyClient):
|
||||
argument = datatype.export_value(argument)
|
||||
else:
|
||||
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
|
||||
data, qualifiers = self.request(COMMANDREQUEST, self.identifier[module, command], argument)[2]
|
||||
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
|
||||
|
||||
ERROR_MAP = secop.errors.EXCEPTIONS
|
||||
DEFAULT_EXCEPTION = secop.errors.SECoPError
|
||||
PREDEFINED_NAMES = set(secop.params.PREDEFINED_ACCESSIBLES)
|
||||
ERROR_MAP = frappy.errors.EXCEPTIONS
|
||||
DEFAULT_EXCEPTION = frappy.errors.SECoPError
|
||||
PREDEFINED_NAMES = set(frappy.params.PREDEFINED_ACCESSIBLES)
|
||||
activate = True
|
||||
|
||||
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
|
||||
from secop.datatypes import FloatRange, IntRange, ScaledInteger, \
|
||||
BoolType, EnumType, BLOBType, StringType, TupleOf, ArrayOf, StructOf
|
||||
from secop.lib.enum import Enum
|
||||
from secop.modules import Module, Readable, Writable, Drivable, Communicator, Attached
|
||||
from secop.properties import Property
|
||||
from secop.params import Parameter, Command, Override
|
||||
from secop.metaclass import Done
|
||||
from secop.iohandler import IOHandler, IOHandlerBase
|
||||
from secop.stringio import StringIO, HasIodev
|
||||
from secop.proxy import SecNode, Proxy, proxy_class
|
||||
from frappy.datatypes import ArrayOf, BLOBType, BoolType, EnumType, \
|
||||
FloatRange, IntRange, ScaledInteger, StringType, StructOf, TupleOf
|
||||
from frappy.lib.enum import Enum
|
||||
from frappy.modules import Attached, Communicator, \
|
||||
Done, Drivable, Feature, Module, Readable, Writable, HasAccessibles
|
||||
from frappy.params import Command, Parameter
|
||||
from frappy.properties import Property
|
||||
from frappy.proxy import Proxy, SecNode, proxy_class
|
||||
from frappy.io import HasIO, StringIO, BytesIO, HasIodev # TODO: remove HasIodev (legacy stuff)
|
||||
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."""
|
||||
|
||||
# pylint: disable=abstract-method
|
||||
# pylint: disable=abstract-method, too-many-lines
|
||||
|
||||
|
||||
import sys
|
||||
from base64 import b64decode, b64encode
|
||||
|
||||
from secop.errors import ProgrammingError, ProtocolError, BadValueError, ConfigError
|
||||
from secop.lib import clamp
|
||||
from secop.lib.enum import Enum
|
||||
from secop.parse import Parser
|
||||
from secop.properties import HasProperties, Property
|
||||
from frappy.errors import BadValueError, \
|
||||
ConfigError, ProgrammingError, ProtocolError
|
||||
from frappy.lib import clamp, generalConfig
|
||||
from frappy.lib.enum import Enum
|
||||
from frappy.parse import Parser
|
||||
from frappy.properties import HasProperties, Property
|
||||
|
||||
|
||||
# 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',
|
||||
]
|
||||
generalConfig.set_default('lazy_number_validation', False)
|
||||
|
||||
# *DEFAULT* limits for IntRange/ScaledIntegers transport serialisation
|
||||
DEFAULT_MIN_INT = -16777216
|
||||
@ -53,18 +45,48 @@ UNLIMITED = 1 << 64 # internal limit for integers, is probably high enough for
|
||||
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
|
||||
class DataType(HasProperties):
|
||||
"""base class for all data types"""
|
||||
IS_COMMAND = False
|
||||
unit = ''
|
||||
default = None
|
||||
|
||||
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
|
||||
|
||||
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):
|
||||
"""interprets a given string and returns a validated (internal) value"""
|
||||
# to evaluate values from configfiles, ui, etc...
|
||||
@ -97,11 +119,11 @@ class DataType(HasProperties):
|
||||
def set_properties(self, **kwds):
|
||||
"""init datatype properties"""
|
||||
try:
|
||||
for k,v in kwds.items():
|
||||
for k, v in kwds.items():
|
||||
self.setProperty(k, v)
|
||||
self.checkProperties()
|
||||
except Exception as e:
|
||||
raise ProgrammingError(str(e))
|
||||
raise ProgrammingError(str(e)) from None
|
||||
|
||||
def get_info(self, **kwds):
|
||||
"""prepare dict for export or repr
|
||||
@ -126,16 +148,20 @@ class DataType(HasProperties):
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def set_main_unit(self, unit):
|
||||
"""replace $ in unit by argument"""
|
||||
|
||||
|
||||
class Stub(DataType):
|
||||
"""incomplete datatype, to be replaced with a proper one later during module load
|
||||
|
||||
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__()
|
||||
self.name = datatype_name
|
||||
self.args = args
|
||||
self.kwds = kwds
|
||||
|
||||
def __call__(self, value):
|
||||
"""validate"""
|
||||
@ -150,25 +176,36 @@ class Stub(DataType):
|
||||
"""
|
||||
for dtcls in globals().values():
|
||||
if isinstance(dtcls, type) and issubclass(dtcls, DataType):
|
||||
for prop in dtcls.properties.values():
|
||||
for prop in dtcls.propertyDict.values():
|
||||
stub = prop.datatype
|
||||
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:
|
||||
|
||||
class FloatRange(DataType):
|
||||
"""Restricted float type"""
|
||||
properties = {
|
||||
'min': Property('low limit', Stub('FloatRange'), extname='min', default=-sys.float_info.max),
|
||||
'max': Property('high limit', Stub('FloatRange'), extname='max', default=sys.float_info.max),
|
||||
'unit': Property('physical unit', Stub('StringType'), extname='unit', default=''),
|
||||
'fmtstr': Property('format string', Stub('StringType'), extname='fmtstr', default='%g'),
|
||||
'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),
|
||||
}
|
||||
class FloatRange(HasUnit, DataType):
|
||||
"""(restricted) float type
|
||||
|
||||
:param minval: (property **min**)
|
||||
:param maxval: (property **max**)
|
||||
:param kwds: any of the properties below
|
||||
"""
|
||||
min = Property('low limit', Stub('FloatRange'), extname='min', default=-sys.float_info.max)
|
||||
max = Property('high limit', Stub('FloatRange'), extname='max', default=sys.float_info.max)
|
||||
fmtstr = Property('format string', Stub('StringType'), extname='fmtstr', default='%g')
|
||||
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):
|
||||
super().__init__()
|
||||
@ -186,18 +223,28 @@ class FloatRange(DataType):
|
||||
return self.get_info(type='double')
|
||||
|
||||
def __call__(self, value):
|
||||
try:
|
||||
value += 0.0 # do not accept strings here
|
||||
except Exception:
|
||||
try:
|
||||
value = float(value)
|
||||
except Exception:
|
||||
raise BadValueError('Can not __call__ %r to float' % value)
|
||||
# map +/-infty to +/-max possible number
|
||||
value = clamp(-sys.float_info.max, value, sys.float_info.max)
|
||||
raise BadValueError('can not convert %s to a float' % shortrepr(value)) from None
|
||||
if not generalConfig.lazy_number_validation:
|
||||
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)
|
||||
if self.min - prec <= value <= self.max + prec:
|
||||
return min(max(value, self.min), self.max)
|
||||
raise BadValueError('%.14g should be a float between %.14g and %.14g' %
|
||||
# silently clamp when outside by not more than prec
|
||||
return clamp(self.min, value, self.max)
|
||||
raise BadValueError('%.14g must be between %d and %d' %
|
||||
(value, self.min, self.max))
|
||||
|
||||
def __repr__(self):
|
||||
@ -206,7 +253,7 @@ class FloatRange(DataType):
|
||||
hints['minval'] = hints.pop('min')
|
||||
if 'max' in hints:
|
||||
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):
|
||||
"""returns a python object fit for serialisation"""
|
||||
@ -231,18 +278,20 @@ class FloatRange(DataType):
|
||||
if not isinstance(other, (FloatRange, ScaledInteger)):
|
||||
raise BadValueError('incompatible datatypes')
|
||||
# avoid infinity
|
||||
other(max(sys.float_info.min, self.min))
|
||||
other(min(sys.float_info.max, self.max))
|
||||
other.validate(max(sys.float_info.min, self.min))
|
||||
other.validate(min(sys.float_info.max, self.max))
|
||||
|
||||
|
||||
class IntRange(DataType):
|
||||
"""Restricted int type"""
|
||||
properties = {
|
||||
'min': Property('minimum value', Stub('IntRange', -UNLIMITED, UNLIMITED), extname='min', mandatory=True),
|
||||
'max': Property('maximum value', Stub('IntRange', -UNLIMITED, UNLIMITED), extname='max', mandatory=True),
|
||||
"""restricted int type
|
||||
|
||||
:param minval: (property **min**)
|
||||
: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?
|
||||
# 'unit': Property('physical unit', StringType(), extname='unit', default=''),
|
||||
}
|
||||
# unit = Property('physical unit', StringType(), extname='unit', default='')
|
||||
|
||||
def __init__(self, minval=None, maxval=None):
|
||||
super().__init__()
|
||||
@ -257,18 +306,37 @@ class IntRange(DataType):
|
||||
return self.get_info(type='int')
|
||||
|
||||
def __call__(self, value):
|
||||
try:
|
||||
fvalue = value + 0.0 # do not accept strings here
|
||||
value = int(value)
|
||||
except Exception:
|
||||
try:
|
||||
fvalue = float(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:
|
||||
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):
|
||||
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):
|
||||
"""returns a python object fit for serialisation"""
|
||||
@ -286,38 +354,43 @@ class IntRange(DataType):
|
||||
return '%d' % value
|
||||
|
||||
def compatible(self, other):
|
||||
if isinstance(other, IntRange):
|
||||
other(self.min)
|
||||
other(self.max)
|
||||
if isinstance(other, (IntRange, FloatRange, ScaledInteger)):
|
||||
other.validate(self.min)
|
||||
other.validate(self.max)
|
||||
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):
|
||||
other(i)
|
||||
raise BadValueError('incompatible datatypes')
|
||||
|
||||
|
||||
class ScaledInteger(DataType):
|
||||
"""Scaled integer int type
|
||||
class ScaledInteger(HasUnit, DataType):
|
||||
"""scaled integer (= fixed resolution float) type
|
||||
|
||||
note: limits are for the scaled value (i.e. the internal value)
|
||||
the scale is only used for calculating to/from transport serialisation"""
|
||||
properties = {
|
||||
'scale': Property('scale factor', FloatRange(sys.float_info.min), extname='scale', mandatory=True),
|
||||
'min': Property('low limit', FloatRange(), extname='min', mandatory=True),
|
||||
'max': Property('high limit', FloatRange(), extname='max', mandatory=True),
|
||||
'unit': Property('physical unit', Stub('StringType'), extname='unit', default=''),
|
||||
'fmtstr': Property('format string', Stub('StringType'), extname='fmtstr', default='%g'),
|
||||
'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),
|
||||
}
|
||||
:param minval: (property **min**)
|
||||
:param maxval: (property **max**)
|
||||
:param kwds: any of the properties below
|
||||
|
||||
note: limits are for the scaled float value
|
||||
the scale is only used for calculating to/from transport serialisation
|
||||
"""
|
||||
scale = Property('scale factor', FloatRange(sys.float_info.min), extname='scale', mandatory=True)
|
||||
min = Property('low limit', FloatRange(), extname='min', mandatory=True)
|
||||
max = Property('high limit', FloatRange(), extname='max', mandatory=True)
|
||||
fmtstr = Property('format string', Stub('StringType'), extname='fmtstr', default='%g')
|
||||
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):
|
||||
super().__init__()
|
||||
scale = float(scale)
|
||||
if absolute_resolution is None:
|
||||
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),
|
||||
max=DEFAULT_MAX_INT * scale if maxval is None else float(maxval),
|
||||
absolute_resolution=absolute_resolution,
|
||||
@ -348,35 +421,40 @@ class ScaledInteger(DataType):
|
||||
|
||||
def export_datatype(self):
|
||||
return self.get_info(type='scaled',
|
||||
min = int((self.min + self.scale * 0.5) // self.scale),
|
||||
max = int((self.max + self.scale * 0.5) // self.scale))
|
||||
min=int(round(self.min / self.scale)),
|
||||
max=int(round(self.max / self.scale)))
|
||||
|
||||
def __call__(self, value):
|
||||
try:
|
||||
value += 0.0 # do not accept strings here
|
||||
except Exception:
|
||||
try:
|
||||
value = float(value)
|
||||
except Exception:
|
||||
raise BadValueError('Can not convert %r to float' % value)
|
||||
prec = max(self.scale, abs(value * self.relative_resolution),
|
||||
self.absolute_resolution)
|
||||
if self.min - prec <= value <= self.max + prec:
|
||||
value = min(max(value, self.min), self.max)
|
||||
else:
|
||||
raise BadValueError('%g should be a float between %g and %g' %
|
||||
raise BadValueError('can not convert %s to float' % shortrepr(value)) from None
|
||||
if not generalConfig.lazy_number_validation:
|
||||
raise DiscouragedConversion('automatic string to float conversion no longer supported') from None
|
||||
intval = int(round(value / self.scale))
|
||||
return float(intval * self.scale) # return 'actual' value (which is more discrete than a float)
|
||||
|
||||
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))
|
||||
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):
|
||||
hints = self.get_info(scale=float('%g' % self.scale),
|
||||
min = int((self.min + self.scale * 0.5) // self.scale),
|
||||
max = int((self.max + self.scale * 0.5) // self.scale))
|
||||
min=int(round(self.min / self.scale)),
|
||||
max=int(round(self.max / self.scale)))
|
||||
return 'ScaledInteger(%s)' % (', '.join('%s=%r' % kv for kv in hints.items()))
|
||||
|
||||
def export_value(self, value):
|
||||
"""returns a python object fit for serialisation"""
|
||||
# note: round behaves different in Py2 vs. Py3, so use floor division
|
||||
return int((value + self.scale * 0.5) // self.scale)
|
||||
return int(round(value / self.scale))
|
||||
|
||||
def import_value(self, value):
|
||||
"""returns a python object from serialisation"""
|
||||
@ -396,18 +474,24 @@ class ScaledInteger(DataType):
|
||||
def compatible(self, other):
|
||||
if not isinstance(other, (FloatRange, ScaledInteger)):
|
||||
raise BadValueError('incompatible datatypes')
|
||||
other(self.min)
|
||||
other(self.max)
|
||||
other.validate(self.min)
|
||||
other.validate(self.max)
|
||||
|
||||
|
||||
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__()
|
||||
if 'members' in kwds:
|
||||
kwds = dict(kwds)
|
||||
kwds.update(kwds['members'])
|
||||
kwds.pop('members')
|
||||
if members is not None:
|
||||
kwds.update(members)
|
||||
if isinstance(enum_or_name, str):
|
||||
self._enum = Enum(enum_or_name, kwds) # allow 'self' as name
|
||||
else:
|
||||
self._enum = Enum(enum_or_name, **kwds)
|
||||
self.default = self._enum[self._enum.members[0]]
|
||||
|
||||
@ -416,10 +500,11 @@ class EnumType(DataType):
|
||||
return EnumType(self._enum)
|
||||
|
||||
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):
|
||||
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):
|
||||
"""returns a python object fit for serialisation"""
|
||||
@ -434,7 +519,7 @@ class EnumType(DataType):
|
||||
try:
|
||||
return self._enum[value]
|
||||
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):
|
||||
return self(text)
|
||||
@ -442,18 +527,24 @@ class EnumType(DataType):
|
||||
def format_value(self, value, unit=None):
|
||||
return '%s<%s>' % (self._enum[value].name, self._enum[value].value)
|
||||
|
||||
def set_name(self, name):
|
||||
self._enum.name = name
|
||||
|
||||
def compatible(self, other):
|
||||
for m in self._enum.members:
|
||||
other(m)
|
||||
|
||||
|
||||
class BLOBType(DataType):
|
||||
properties = {
|
||||
'minbytes': Property('minimum number of bytes', IntRange(0), extname='minbytes',
|
||||
default=0),
|
||||
'maxbytes': Property('maximum number of bytes', IntRange(0), extname='maxbytes',
|
||||
mandatory=True),
|
||||
}
|
||||
"""binary large object
|
||||
|
||||
internally treated as bytes
|
||||
"""
|
||||
|
||||
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):
|
||||
super().__init__()
|
||||
@ -476,7 +567,7 @@ class BLOBType(DataType):
|
||||
def __call__(self, value):
|
||||
"""return the validated (internal) value or raise"""
|
||||
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)
|
||||
if size < self.minbytes:
|
||||
raise BadValueError(
|
||||
@ -507,18 +598,20 @@ class BLOBType(DataType):
|
||||
if self.minbytes < other.minbytes or self.maxbytes > other.maxbytes:
|
||||
raise BadValueError('incompatible datatypes')
|
||||
except AttributeError:
|
||||
raise BadValueError('incompatible datatypes')
|
||||
raise BadValueError('incompatible datatypes') from None
|
||||
|
||||
|
||||
class StringType(DataType):
|
||||
properties = {
|
||||
'minchars': Property('minimum number of character points', IntRange(0, UNLIMITED),
|
||||
extname='minchars', default=0),
|
||||
'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),
|
||||
}
|
||||
"""string
|
||||
|
||||
for parameters see properties below
|
||||
"""
|
||||
minchars = Property('minimum number of character points', IntRange(0, UNLIMITED),
|
||||
extname='minchars', default=0)
|
||||
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):
|
||||
super().__init__()
|
||||
@ -539,19 +632,19 @@ class StringType(DataType):
|
||||
def __call__(self, value):
|
||||
"""return the validated (internal) value or raise"""
|
||||
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:
|
||||
try:
|
||||
value.encode('ascii')
|
||||
except UnicodeEncodeError:
|
||||
raise BadValueError('%r contains non-ascii character!' % value)
|
||||
raise BadValueError('%s contains non-ascii character!' % shortrepr(value)) from None
|
||||
size = len(value)
|
||||
if size < self.minchars:
|
||||
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:
|
||||
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:
|
||||
raise BadValueError(
|
||||
'Strings are not allowed to embed a \\0! Use a Blob instead!')
|
||||
@ -578,23 +671,24 @@ class StringType(DataType):
|
||||
self.isUTF8 > other.isUTF8:
|
||||
raise BadValueError('incompatible datatypes')
|
||||
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),
|
||||
# whereas StringType is supposed to not contain '\n'
|
||||
# unfortunately, SECoP makes no distinction here....
|
||||
# note: content is supposed to follow the format of a git commit message, i.e. a line of text, 2 '\n' + a longer explanation
|
||||
# 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):
|
||||
def __init__(self, maxchars=None):
|
||||
if maxchars is None:
|
||||
maxchars = UNLIMITED
|
||||
super(TextType, self).__init__(0, maxchars)
|
||||
super().__init__(0, maxchars)
|
||||
|
||||
def __repr__(self):
|
||||
if self.maxchars == UNLIMITED:
|
||||
return 'TextType()'
|
||||
return 'TextType(%d)' % (self.maxchars)
|
||||
return 'TextType(%d)' % self.maxchars
|
||||
|
||||
def copy(self):
|
||||
# DataType.copy will not work, because it is exported as 'string'
|
||||
@ -602,6 +696,7 @@ class TextType(StringType):
|
||||
|
||||
|
||||
class BoolType(DataType):
|
||||
"""boolean"""
|
||||
default = False
|
||||
|
||||
def export_datatype(self):
|
||||
@ -616,7 +711,7 @@ class BoolType(DataType):
|
||||
return False
|
||||
if value in [1, '1', 'True', 'true', 'yes', 'on', 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):
|
||||
"""returns a python object fit for serialisation"""
|
||||
@ -646,12 +741,14 @@ Stub.fix_datatypes()
|
||||
|
||||
|
||||
class ArrayOf(DataType):
|
||||
properties = {
|
||||
'minlen': Property('minimum number of elements', IntRange(0), extname='minlen',
|
||||
default=0),
|
||||
'maxlen': Property('maximum number of elements', IntRange(0), extname='maxlen',
|
||||
mandatory=True),
|
||||
}
|
||||
"""data structure with fields of homogeneous type
|
||||
|
||||
:param members: the datatype of the elements
|
||||
"""
|
||||
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):
|
||||
super().__init__()
|
||||
@ -682,7 +779,7 @@ class ArrayOf(DataType):
|
||||
|
||||
def setProperty(self, key, value):
|
||||
"""set also properties of members"""
|
||||
if key in self.__class__.properties:
|
||||
if key in self.propertyDict:
|
||||
super().setProperty(key, value)
|
||||
else:
|
||||
self.members.setProperty(key, value)
|
||||
@ -695,21 +792,35 @@ class ArrayOf(DataType):
|
||||
return 'ArrayOf(%s, %s, %s)' % (
|
||||
repr(self.members), self.minlen, self.maxlen)
|
||||
|
||||
def __call__(self, value):
|
||||
"""validate an external representation to an internal one"""
|
||||
if isinstance(value, (tuple, list)):
|
||||
def check_type(self, value):
|
||||
try:
|
||||
# check number of elements
|
||||
if self.minlen is not None and len(value) < self.minlen:
|
||||
raise BadValueError(
|
||||
'Array too small, needs at least %d elements!' %
|
||||
'array too small, needs at least %d elements!' %
|
||||
self.minlen)
|
||||
if self.maxlen is not None and len(value) > self.maxlen:
|
||||
raise BadValueError(
|
||||
'Array too big, holds at most %d elements!' % self.minlen)
|
||||
# apply subtype valiation to all elements and return as list
|
||||
return tuple(self.members(elem) for elem in value)
|
||||
raise BadValueError(
|
||||
'Can not convert %s to ArrayOf DataType!' % repr(value))
|
||||
'array too big, holds at most %d elements!' % self.maxlen)
|
||||
except TypeError:
|
||||
raise BadValueError('%s can not be converted to ArrayOf DataType!'
|
||||
% type(value).__name__) from None
|
||||
|
||||
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):
|
||||
"""returns a python object fit for serialisation"""
|
||||
@ -739,11 +850,17 @@ class ArrayOf(DataType):
|
||||
raise BadValueError('incompatible datatypes')
|
||||
self.members.compatible(other.members)
|
||||
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):
|
||||
"""data structure with fields of inhomogeneous type
|
||||
|
||||
types are given as positional arguments
|
||||
"""
|
||||
def __init__(self, *members):
|
||||
super().__init__()
|
||||
if not members:
|
||||
@ -765,19 +882,30 @@ class TupleOf(DataType):
|
||||
def __repr__(self):
|
||||
return 'TupleOf(%s)' % ', '.join([repr(st) for st in self.members])
|
||||
|
||||
def __call__(self, value):
|
||||
"""return the validated value or raise"""
|
||||
# keep the ordering!
|
||||
def check_type(self, value):
|
||||
try:
|
||||
if len(value) != len(self.members):
|
||||
raise BadValueError(
|
||||
'Illegal number of Arguments! Need %d arguments.' %
|
||||
(len(self.members)))
|
||||
# validate elements and return as list
|
||||
return tuple(sub(elem)
|
||||
for sub, elem in zip(self.members, value))
|
||||
except Exception as exc:
|
||||
raise BadValueError('Can not validate:', str(exc))
|
||||
'tuple needs %d elements' % len(self.members))
|
||||
except TypeError:
|
||||
raise BadValueError('%s can not be converted to TupleOf DataType!'
|
||||
% type(value).__name__) from None
|
||||
|
||||
def __call__(self, value):
|
||||
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):
|
||||
"""returns a python object fit for serialisation"""
|
||||
@ -800,11 +928,15 @@ class TupleOf(DataType):
|
||||
def compatible(self, other):
|
||||
if not isinstance(other, TupleOf):
|
||||
raise BadValueError('incompatible datatypes')
|
||||
if len(self.members) != len(other.members) :
|
||||
if len(self.members) != len(other.members):
|
||||
raise BadValueError('incompatible datatypes')
|
||||
for a, b in zip(self.members, other.members):
|
||||
a.compatible(b)
|
||||
|
||||
def set_main_unit(self, unit):
|
||||
for member in self.members:
|
||||
member.set_main_unit(unit)
|
||||
|
||||
|
||||
class ImmutableDict(dict):
|
||||
def _no(self, *args, **kwds):
|
||||
@ -813,13 +945,19 @@ class ImmutableDict(dict):
|
||||
|
||||
|
||||
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):
|
||||
super().__init__()
|
||||
self.members = members
|
||||
if not members:
|
||||
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()):
|
||||
if not isinstance(subtype, DataType):
|
||||
raise ProgrammingError(
|
||||
@ -828,16 +966,16 @@ class StructOf(DataType):
|
||||
if name not in members:
|
||||
raise ProgrammingError(
|
||||
'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):
|
||||
"""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):
|
||||
res = dict(type='struct', members=dict((n, s.export_datatype())
|
||||
for n, s in list(self.members.items())))
|
||||
if self.optional:
|
||||
if set(self.optional) != set(self.members):
|
||||
res['optional'] = self.optional
|
||||
return res
|
||||
|
||||
@ -847,16 +985,38 @@ class StructOf(DataType):
|
||||
['%s=%s' % (n, repr(st)) for n, st in list(self.members.items())]), opt)
|
||||
|
||||
def __call__(self, value):
|
||||
"""return the validated value or raise"""
|
||||
try:
|
||||
missing = set(self.members) - set(value) - set(self.optional)
|
||||
if missing:
|
||||
raise BadValueError('missing values for keys %r' % list(missing))
|
||||
# validate elements and return as dict
|
||||
if set(dict(value)) != set(self.members):
|
||||
raise BadValueError('member names do not match') from None
|
||||
except TypeError:
|
||||
raise BadValueError('%s can not be converted a StructOf'
|
||||
% type(value).__name__) from None
|
||||
try:
|
||||
return ImmutableDict((str(k), self.members[k](v))
|
||||
for k, v in list(value.items()))
|
||||
except Exception as exc:
|
||||
raise BadValueError('Can not validate %s: %s' % (repr(value), str(exc)))
|
||||
except Exception as e:
|
||||
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):
|
||||
"""returns a python object fit for serialisation"""
|
||||
@ -886,10 +1046,18 @@ class StructOf(DataType):
|
||||
if mandatory:
|
||||
raise BadValueError('incompatible datatypes')
|
||||
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):
|
||||
"""command
|
||||
|
||||
a pseudo datatype for commands with arguments and return values
|
||||
"""
|
||||
IS_COMMAND = True
|
||||
|
||||
def __init__(self, argument=None, result=None):
|
||||
@ -913,14 +1081,12 @@ class CommandType(DataType):
|
||||
return props
|
||||
|
||||
def __repr__(self):
|
||||
argstr = repr(self.argument) if self.argument else ''
|
||||
if self.result is None:
|
||||
return 'CommandType(%s)' % argstr
|
||||
return 'CommandType(%s)->%s' % (argstr, repr(self.result))
|
||||
return 'CommandType(%s)' % (repr(self.argument) if self.argument else '')
|
||||
return 'CommandType(%s, %s)' % (repr(self.argument), repr(self.result))
|
||||
|
||||
def __call__(self, value):
|
||||
"""return the validated argument value or raise"""
|
||||
return self.argument(value)
|
||||
raise ProgrammingError('commands can not be converted to a value')
|
||||
|
||||
def export_value(self, value):
|
||||
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
|
||||
other.result.compatible(self.result)
|
||||
except AttributeError:
|
||||
raise BadValueError('incompatible datatypes')
|
||||
|
||||
raise BadValueError('incompatible datatypes') from None
|
||||
|
||||
|
||||
# internally used datatypes (i.e. only for programming the SEC-node)
|
||||
|
||||
class DataTypeType(DataType):
|
||||
def __call__(self, value):
|
||||
"""check if given value (a python obj) is a valid datatype
|
||||
@ -957,7 +1123,10 @@ class DataTypeType(DataType):
|
||||
returns the value or raises an appropriate exception"""
|
||||
if isinstance(value, DataType):
|
||||
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):
|
||||
"""if needed, reformat value for transport"""
|
||||
@ -992,6 +1161,13 @@ class ValueType(DataType):
|
||||
"""
|
||||
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):
|
||||
"""validates a None or smth. else"""
|
||||
@ -1038,26 +1214,23 @@ UInt64 = IntRange(0, (1 << 64) - 1)
|
||||
# Goodie: Convenience Datatypes for Programming
|
||||
class LimitsType(TupleOf):
|
||||
def __init__(self, members):
|
||||
TupleOf.__init__(self, members, members)
|
||||
super().__init__(members, members)
|
||||
|
||||
def __call__(self, value):
|
||||
limits = TupleOf.__call__(self, value)
|
||||
limits = TupleOf.validate(self, value)
|
||||
if limits[1] < limits[0]:
|
||||
raise BadValueError('Maximum Value %s must be greater than minimum value %s!' % (limits[1], limits[0]))
|
||||
return limits
|
||||
|
||||
|
||||
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):
|
||||
TupleOf.__init__(self, EnumType(enum), StringType())
|
||||
self.enum = enum
|
||||
super().__init__(EnumType(enum), StringType())
|
||||
self._enum = enum
|
||||
|
||||
def __getattr__(self, key):
|
||||
enum = TupleOf.__getattr__(self, 'enum')
|
||||
if hasattr(enum, key):
|
||||
return getattr(enum, key)
|
||||
return TupleOf.__getattr__(self, key)
|
||||
return getattr(self._enum, key)
|
||||
|
||||
|
||||
def floatargs(kwds):
|
||||
@ -1070,37 +1243,26 @@ def floatargs(kwds):
|
||||
DATATYPES = dict(
|
||||
bool = lambda **kwds:
|
||||
BoolType(),
|
||||
|
||||
int = lambda min, max, **kwds:
|
||||
IntRange(minval=min, maxval=max),
|
||||
|
||||
scaled = lambda scale, min, max, **kwds:
|
||||
ScaledInteger(scale=scale, minval=min*scale, maxval=max*scale, **floatargs(kwds)),
|
||||
|
||||
double = lambda min=None, max=None, **kwds:
|
||||
FloatRange(minval=min, maxval=max, **floatargs(kwds)),
|
||||
|
||||
blob = lambda maxbytes, minbytes=0, **kwds:
|
||||
BLOBType(minbytes=minbytes, maxbytes=maxbytes),
|
||||
|
||||
string = lambda minchars=0, maxchars=None, isUTF8=False, **kwds:
|
||||
StringType(minchars=minchars, maxchars=maxchars, isUTF8=isUTF8),
|
||||
|
||||
array = lambda maxlen, members, minlen=0, pname='', **kwds:
|
||||
ArrayOf(get_datatype(members, pname), minlen=minlen, maxlen=maxlen),
|
||||
|
||||
tuple = lambda members, pname='', **kwds:
|
||||
TupleOf(*tuple((get_datatype(t, pname) for t in members))),
|
||||
|
||||
enum = lambda members, pname='', **kwds:
|
||||
EnumType(pname, members=members),
|
||||
|
||||
struct = lambda members, optional=None, pname='', **kwds:
|
||||
StructOf(optional, **dict((n, get_datatype(t, pname)) for n, t in list(members.items()))),
|
||||
|
||||
command = lambda argument=None, result=None, pname='', **kwds:
|
||||
CommandType(get_datatype(argument, pname), get_datatype(result)),
|
||||
|
||||
limit = lambda members, pname='', **kwds:
|
||||
LimitsType(get_datatype(members, pname)),
|
||||
)
|
||||
@ -1111,7 +1273,10 @@ def get_datatype(json, pname=''):
|
||||
"""returns a DataType object from description
|
||||
|
||||
inverse of <DataType>.export_datatype()
|
||||
the pname argument, if given, is used to name EnumTypes from the parameter name
|
||||
|
||||
:param json: the datainfo object as returned from json.loads
|
||||
:param pname: if given, used to name EnumTypes from the parameter name
|
||||
:return: the datatype (instance of DataType)
|
||||
"""
|
||||
if json is None:
|
||||
return json
|
||||
@ -1122,9 +1287,9 @@ def get_datatype(json, pname=''):
|
||||
kwargs = json.copy()
|
||||
base = kwargs.pop('type')
|
||||
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:
|
||||
return DATATYPES[base](pname=pname, **kwargs)
|
||||
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:
|
||||
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||
# Markus Zolliker <markus.zolliker@psi.ch>
|
||||
#
|
||||
# *****************************************************************************
|
||||
"""Define (internal) SECoP Errors"""
|
||||
|
||||
import re
|
||||
from ast import literal_eval
|
||||
|
||||
|
||||
class SECoPError(RuntimeError):
|
||||
silent = False # silent = True indicates that the error is already logged
|
||||
|
||||
def __init__(self, *args, **kwds):
|
||||
RuntimeError.__init__(self)
|
||||
super().__init__()
|
||||
self.args = args
|
||||
for k, v in list(kwds.items()):
|
||||
setattr(self, k, v)
|
||||
|
||||
def __repr__(self):
|
||||
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 = []
|
||||
if args:
|
||||
res.append(args)
|
||||
@ -103,14 +108,6 @@ class CommunicationFailedError(SECoPError):
|
||||
pass
|
||||
|
||||
|
||||
class SilentError(SECoPError):
|
||||
pass
|
||||
|
||||
|
||||
class CommunicationSilentError(SilentError, CommunicationFailedError):
|
||||
name = 'CommunicationFailed'
|
||||
|
||||
|
||||
class IsBusyError(SECoPError):
|
||||
pass
|
||||
|
||||
@ -124,10 +121,29 @@ class DisabledError(SECoPError):
|
||||
|
||||
|
||||
class HardwareError(SECoPError):
|
||||
pass
|
||||
name = 'HardwareError'
|
||||
|
||||
|
||||
FRAPPY_ERROR = re.compile(r'(.*)\(.*\)$')
|
||||
|
||||
|
||||
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)
|
||||
return errcls(text)
|
||||
|
||||
@ -144,7 +160,7 @@ EXCEPTIONS = dict(
|
||||
NoSuchCommand=NoSuchCommandError,
|
||||
CommandFailed=CommandFailedError,
|
||||
CommandRunning=CommandRunningError,
|
||||
Readonly=ReadOnlyError,
|
||||
ReadOnly=ReadOnlyError,
|
||||
BadValue=BadValueError,
|
||||
CommunicationFailed=CommunicationFailedError,
|
||||
HardwareError=HardwareError,
|
||||
@ -152,7 +168,8 @@ EXCEPTIONS = dict(
|
||||
IsError=IsErrorError,
|
||||
Disabled=DisabledError,
|
||||
SyntaxError=ProtocolError,
|
||||
NotImplementedError=NotImplementedError,
|
||||
NotImplemented=NotImplementedError,
|
||||
ProtocolError=ProtocolError,
|
||||
InternalError=InternalError,
|
||||
# internal short versions (candidates for spec)
|
||||
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