up to date with develop/mlz

Change-Id: I5ea71bc99a2f0dffc3dbe37e1119eb188ef8a3f0
This commit is contained in:
zolliker 2023-05-31 14:27:36 +02:00
parent c5d429346d
commit 9a6421a54f
8 changed files with 149 additions and 160 deletions

View File

@ -9,7 +9,7 @@
# Add <file or directory> to the black list. It should be a base name, not a # Add <file or directory> to the black list. It should be a base name, not a
# path. You may set this option multiple times. # path. You may set this option multiple times.
ignore = .git ignore = .git,resources_qt5.py,resources_qt6.py
# Pickle collected data for later comparisons. # Pickle collected data for later comparisons.
persistent=yes persistent=yes
@ -38,27 +38,21 @@ confidence=
# multiple time. # multiple time.
disable=missing-docstring disable=missing-docstring
,locally-disabled ,locally-disabled
,locally-enabled
,fixme ,fixme
,no-member ,no-member
,bad-whitespace
,wrong-import-position ,wrong-import-position
,ungrouped-imports ,ungrouped-imports
,import-self ,import-self
,bad-continuation
,protected-access ,protected-access
,unused-argument ,unused-argument
,duplicate-code ,duplicate-code
,attribute-defined-outside-init ,attribute-defined-outside-init
,access-member-before-definition ,access-member-before-definition
,no-self-use
,broad-except ,broad-except
,unneeded-not ,unneeded-not
,unidiomatic-typecheck ,unidiomatic-typecheck
,undefined-loop-variable ,undefined-loop-variable
,redefined-variable-type ,consider-using-f-string
,deprecated-lambda
[REPORTS] [REPORTS]
# Set the output format. Available formats are text, parseable, colorized, msvs # Set the output format. Available formats are text, parseable, colorized, msvs
@ -67,10 +61,6 @@ disable=missing-docstring
msg-template={path}:{line}: [{msg_id}({symbol}), {obj}] {msg} msg-template={path}:{line}: [{msg_id}({symbol}), {obj}] {msg}
# Put messages in a separate file for each module / package specified on the
# command line instead of printing them on stdout. Reports (if any) will be
# written in a file name "pylint_global.[txt|html]".
files-output=no
# Tells whether to display a full report or only the messages # Tells whether to display a full report or only the messages
reports=no reports=no
@ -93,14 +83,11 @@ dummy-variables-rgx=_|dummy
# List of additional names supposed to be defined in builtins. Remember that # List of additional names supposed to be defined in builtins. Remember that
# you should avoid to define new builtins when possible. # you should avoid to define new builtins when possible.
additional-builtins= additional-builtins=Node,Mod,Param,Command,Group
[BASIC] [BASIC]
# List of builtins function names that should not be used, separated by a comma
#bad-functions=map,filter,apply,input
bad-functions=apply,input
# Regular expression which should only match correct module names # Regular expression which should only match correct module names
module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9_]+))$ module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9_]+))$
@ -155,12 +142,6 @@ notes=FIXME,XXX,TODO
# Maximum number of characters on a single line. # Maximum number of characters on a single line.
max-line-length=132 max-line-length=132
# List of optional constructs for which whitespace checking is disabled. `dict-
# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}.
# `trailing-comma` allows a space between comma and closing bracket: (a, ).
# `empty-line` allows space-only lines.
no-space-check=trailing-comma,dict-separator
# Maximum number of lines in a module # Maximum number of lines in a module
max-module-lines=1000 max-module-lines=1000

View File

@ -60,4 +60,3 @@ changes are done, eventually a sync step should happen:
cp -r secop /Volumes/PPMSData/zolliker/frappy/secop cp -r secop /Volumes/PPMSData/zolliker/frappy/secop
it may be that additional folder have to copied ... it may be that additional folder have to copied ...

View File

@ -31,7 +31,6 @@ sys.path.insert(0, path.abspath(path.join(path.dirname(__file__), '..')))
import logging import logging
from mlzlog import ColoredConsoleHandler from mlzlog import ColoredConsoleHandler
from frappy.gui.qt import QApplication from frappy.gui.qt import QApplication
from frappy.gui.cfg_editor.mainwindow import MainWindow from frappy.gui.cfg_editor.mainwindow import MainWindow

38
frappy-server.spec Normal file
View File

@ -0,0 +1,38 @@
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(['bin\\frappy-server'],
pathex=['.'],
binaries=[],
datas=[],
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=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
[],
exclude_binaries=True,
name='frappy-server',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=True )
coll = COLLECT(exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='frappy-server')

View File

@ -22,6 +22,7 @@
from frappy.core import Readable, Parameter, FloatRange, TupleOf, \ from frappy.core import Readable, Parameter, FloatRange, TupleOf, \
HasIO, StringIO, IntRange, BoolType, Writable, EnumType HasIO, StringIO, IntRange, BoolType, Writable, EnumType
from frappy.errors import RangeError
class SR_IO(StringIO): class SR_IO(StringIO):
@ -30,46 +31,65 @@ class SR_IO(StringIO):
def communicate(self, cmd): # remove dash from terminator def communicate(self, cmd): # remove dash from terminator
reply = super().communicate(cmd) reply = super().communicate(cmd)
status = self._conn.readbytes(2, 0.1) # get the 2 status bytes status = self._conn.readbytes(2, timeout=0.1) # get the 2 status bytes
return reply + ';%d;%d' % tuple(status) return reply + ';%d;%d' % tuple(status)
class Ametek(StringIO, HasIO): class XY(HasIO, Readable):
ioClass = SR_IO
def comm(self, cmd):
reply, status, overload = self.communicate(cmd).split(b';')
if overload != b'0':
self.status = (self.Status.WARN, f'overload {overload}')
self.status = (self.Status.IDLE, '')
return reply
class XY(Ametek, Readable):
value = Parameter('X, Y', datatype=TupleOf(FloatRange(unit='V'), FloatRange(unit='V'))) value = Parameter('X, Y', datatype=TupleOf(FloatRange(unit='V'), FloatRange(unit='V')))
vmode = Parameter('control mode', EnumType(both_grounded=0, A=1, B=2, A_B_diff=3), readonly=False) vmode = Parameter('control mode', EnumType(both_grounded=0, A=1, B=2, A_B_diff=3), readonly=False)
range = Parameter('sensitivity value', FloatRange(0.00, 1), unit='V', default=1) range = Parameter('sensitivity value', FloatRange(0.00, 1), unit='V', default=1)
autosen_on = Parameter('is auto sensitivity on', BoolType(), readonly=False) autosen_on = Parameter('is auto sensitivity on', BoolType(), readonly=False)
noise_control = Parameter('noise control mode', BoolType(), readonly=False) noise_control = Parameter('is noise control mode on', BoolType(), readonly=False)
phase = Parameter('reference phase control', FloatRange(-360, 360), unit='deg', readonly=False) phase = Parameter('reference phase control', FloatRange(-360, 360), unit='deg', readonly=False)
frequency = Parameter('oscill. frequen. control', FloatRange(0.001, 250e3), unit='Hz', readonly=False,
group='frequency')
amplitude = Parameter('oscill. amplit. control', FloatRange(0.00, 5), unit='V_rms', readonly=False)
#filter = Parameter('line frequency filter', unit='Hz')
sen_range = {name: value + 1 for value, name in enumerate( sen_range = {name: value + 1 for value, name in enumerate(
['2nV', '5nV', '10nV', '20nV', '50nV', '100nV', '200nV', '500nV', '1uV', ['2nV', '5nV', '10nV', '20nV', '50nV', '100nV', '200nV', '500nV', '1uV',
'2uV', '5uV', '10uV', '20uV', '50uV', '100uV', '200uV', '500uV', '1mV', '2uV', '5uV', '10uV', '20uV', '50uV', '100uV', '200uV', '500uV', '1mV',
'2mV', '5mV', '10mV', '20mV', '50mV', '100mV', '200mV', '500mV', '1V'] '2mV', '5mV', '10mV', '20mV', '50mV', '100mV', '200mV', '500mV', '1V']
)} )}
irange = Parameter('sensitivity index', EnumType('sensitivity index range', sen_range), readonly=False) irange = Parameter('sensitivity index', EnumType('sensitivity index range', sen_range), readonly=False)
time_const = {value: name for value, name in enumerate( time_const = {name: value for value, name in enumerate(
[('10us', 'N/A'), ('20us', 'N/A'), ('50us', 'N/A'), ('100us', 'N/A'), ['10us', '20us', '50us', '100us', '200us', '500us', '1ms', '2ms', '5ms', '10ms',
('200us', 'N/A'), ('500us', '500us'), ('1ms', '1ms'), ('2ms', '2ms'), '20ms', '50ms', '100ms', '200ms', '500ms', '1s', '2s', '5s', '10s', '20s', '50s',
('5ms', '5ms'), ('10ms', '10ms'), ('20ms', 'N/A'), ('50ms', 'N/A'), '100s', '200s', '500s', '1ks', '10ks', '20ks', '50ks', '100ks']
('100ms', 'N/A'), ('200ms', 'N/A'), ('500ms', 'N/A'), ('1s', 'N/A'),
('2s', 'N/A'), ('5s', 'N/A'), ('10s', 'N/A'), ('20s', 'N/A'), ('50s', 'N/A'),
('100s', 'N/A'), ('200s', 'N/A'), ('500s', 'N/A'), ('1ks', 'N/A'),
('10ks', 'N/A'), ('20ks', 'N/A'), ('50ks', 'N/A'), ('100ks', 'N/A')]
)} )}
tc = Parameter('time const. value', FloatRange(0.00005, 100000), unit='s', readonly=False)
itc = Parameter('time const. index', EnumType('time const. index range', time_const), readonly=False) itc = Parameter('time const. index', EnumType('time const. index range', time_const), readonly=False)
ioClass = SR_IO
def comparison(self, curr_value, new_value, value_dict):
c_ind = None # closest index
c_diff = None # closets difference
for index, value in value_dict.items():
if c_diff is None or diff < c_diff:
c_ind = index
c_diff = c_diff
if abs(curr_value - new_value) < c_diff:
return c_ind
else:
for index, value in value_dict.items():
diff = abs(new_value - value)
if c_diff is None or diff < c_diff:
c_ind = index
c_diff = diff
return c_ind
def comm(self, cmd):
reply, status, overload = self.communicate(cmd).split(';')
if overload != '0':
self.status = (self.Status.WARN, f'overload {overload}')
self.status = (self.Status.IDLE, '')
return reply
def read_vmode(self): def read_vmode(self):
return self.comm('VMODE') return self.comm('VMODE')
@ -97,25 +117,11 @@ class XY(Ametek, Readable):
return self.comm('SEN.') # range value return self.comm('SEN.') # range value
def write_range(self): def write_range(self):
self.comm(f'IMODE {0}') self.comm(f'IMODE 0')
curr_value = self.read_range() curr_value = self.read_range()
new_value = self.value new_value = self.value
c_ind = None # closest parameters c_ind = self.comparison(curr_value, new_value, self.sen_range)
c_diff = None return self.comm(f'SEN {c_ind}')
for index, value in self.sen_range.items():
diff = abs(curr_value - value)
if c_diff is None or diff < c_diff:
c_ind = index
c_diff = diff
if abs(curr_value - new_value) < c_diff:
return self.comm(f'SEN {c_ind}')
else:
for index, value in self.sen_range.items():
diff = abs(new_value - value)
if c_diff is None or diff < c_diff:
c_ind = index
c_diff = diff
return self.comm(f'SEN {c_ind}')
def read_noise_control(self): def read_noise_control(self):
return self.comm('NOISEMODE') return self.comm('NOISEMODE')
@ -126,15 +132,24 @@ class XY(Ametek, Readable):
def read_tc(self): def read_tc(self):
return self.comm('TC.') return self.comm('TC.')
def read_itc(self): def write_tc(self):
return self.comm(f'TC') pass
# def write_tc(self, itc): def read_itc(self):
# if self.noise_control == 0: return self.comm('TC')
# self.itc = self.
def write_itc(self, new_itc):
curr_value = self.read_itc()
new_value = self.time_const[self.itc]
c_ind = self.comparison(curr_value, new_value, self.time_const)
if abs(curr_value - new_value) < c_diff:
if self.read_noise_control() == 1 and (5e-4 <= self.time_const[new_itc] <= 1e-2):
raise RangeError('not allowed with noisemode=1')
return self.comm(f'TC {new_itc}')
def read_value(self): def read_value(self):
reply = self.comm('XY.').split(',') reply = self.comm('XY.').split(b',')
x = float(reply[0]) x = float(reply[0])
y = float(reply[1]) y = float(reply[1])
return x, y return x, y
@ -142,68 +157,28 @@ class XY(Ametek, Readable):
def write_value(self, value): def write_value(self, value):
return self.comm(f'XY {value}') return self.comm(f'XY {value}')
def read_frequency(self):
class Frequency(XY, Writable):
value = Parameter('oscill. frequen. control', FloatRange(0.001, 250e3), unit='Hz', readonly=False)
target = Parameter('target frequency', FloatRange(0.001, 250e3), unit='Hz', readonly=False)
def read_value(self):
return self.comm('OF.') return self.comm('OF.')
def write_target(self,): def write_frequency(self, frequency):
target = self.target() frequency = self.frequency
return self.comm(f'OF. {target}') return self.comm(f'OF. {frequency}')
def read_amplitude(self):
class Amplitude(XY, Writable):
value = Parameter('oscill. amplit. control', FloatRange(0.00, 5), unit='V_rms', readonly=False)
target = Parameter('target amplit.', FloatRange(0.00, 5), unit='V_rms', readonly=False)
# unify the following
# dac = Parameter('output DAC channel value', datatype=TupleOf(IntRange(1, 4), FloatRange(0.0, 5000, unit='mV')),
# readonly=False, initwrite=True, default=(3,0))
# dac = Parameter('output DAC channel value', FloatRange(-10000, 10000, unit='mV'),
# readonly=False, initwrite=True, default=0)
# oscillator amplitude module
def read_value(self):
return self.comm('OA.') return self.comm('OA.')
def write_target(self): def write_amplitude(self, amplitude):
target = self.target() return self.comm(f'OA. {amplitude}')
return self.comm(f'OA. {target}')
# external output DAC
# def read_dac(self):
# # reply = self.comm('DAC %g' % channel) # failed to add the DAC channel you want to control
# reply = self.comm('DAC 3') # stack to channel 3
# return reply
# def write_dac(self, value):
# # self.comm('DAC %g %g' % channel % value)
# self.comm('DAC 3 %g' % value)
# return value
# phase and autophase # phase and autophase
def read_phase(self): def read_phase(self):
reply = self.comm('REFP.') return self.comm('REFP.')
return reply
def write_phase(self, value): def write_phase(self, value):
self.comm(f'REFP {round(1000 * value)}') self.comm(f'REFP {round(1000 * value)}')
self.read_phase() return self.read_phase()
return value
def aphase(self): def aphase(self):
"""auto phase""" """auto phase"""
self.read_phase() self.read_phase()
return self.comm('AQN') return self.comm('AQN')
# class Comp(Ametek, Readable):
# enablePoll = False
# value = Parameter(datatype=FloatRange(unit='V'))
#
#
# class arg(Ametek, Readable):
# enablePoll = False
# value = Parameter(datatype=FloatRange(unit=''))

View File

@ -20,10 +20,10 @@
# ***************************************************************************** # *****************************************************************************
import math import math
from frappy.core import Readable, Parameter, IntRange, EnumType, FloatRange, \ from frappy.core import Readable, Parameter, IntRange, FloatRange, \
StringIO, HasIO, StringType, Property, Writable, Drivable, IDLE, ERROR, \ StringIO, HasIO, StringType, Property, Writable, Drivable, IDLE, ERROR, \
Attached, StructOf, WARN, Done, BoolType, Enum StructOf, WARN, Done, BoolType, Enum
from frappy.errors import RangeError
from frappy_psi.convergence import HasConvergence from frappy_psi.convergence import HasConvergence
from frappy.mixins import HasOutputModule, HasControlledBy from frappy.mixins import HasOutputModule, HasControlledBy
@ -233,7 +233,7 @@ class HeaterOutput336(HeaterOutput):
else: else:
self._range = 1 self._range = 1
user_current = max_current * math.sqrt(100) user_current = max_current * math.sqrt(100)
self.set_par(f'HTRSET {self.loop}', <1 or 2>, 0, user_current, 1) self.set_par(f'HTRSET {self.loop}', 1 if self.resistance < 50 else 2, 0, user_current, 1)
max_power = max_current ** 2 * self.resistance max_power = max_current ** 2 * self.resistance
self._max_power = max_power self._max_power = max_power
self.set_range() self.set_range()

View File

@ -86,6 +86,29 @@ class ThermFishIO(StringIO):
class SensorA10(HasIO, Readable): class SensorA10(HasIO, Readable):
ioClass = ThermFishIO ioClass = ThermFishIO
value = Parameter('internal temperature', unit='degC') value = Parameter('internal temperature', unit='degC')
status_messages = [
(ERROR, 'high tempr. cutout fault', 2, 0),
(ERROR, 'high RA tempr. fault', 2, 1),
(ERROR, 'high temperature fixed fault', 3, 7),
(ERROR, 'low temperature fixed fault', 3, 6),
(ERROR, 'high temperature fault', 3, 5),
(ERROR, 'low temperature fault', 3, 4),
(ERROR, 'low level fault', 3, 3),
(ERROR, 'circulator fault', 4, 5),
(ERROR, 'high press. cutout', 5, 2),
(ERROR, 'motor overloaded', 5, 1),
(ERROR, 'pump speed fault', 5, 0),
(WARN, 'open internal sensor', 1, 7),
(WARN, 'shorted internal sensor', 1, 6),
(WARN, 'high temperature warn', 3, 2),
(WARN, 'low temperature warn', 3, 1),
(WARN, 'low level warn', 3, 0),
(IDLE, 'max. heating', 5, 5),
(IDLE, 'heating', 5, 6),
(IDLE, 'cooling', 5, 4),
(IDLE, 'max cooling', 5, 3),
(IDLE, '', 4, 3),
]
def get_par(self, cmd): def get_par(self, cmd):
""" """
@ -112,37 +135,11 @@ class SensorA10(HasIO, Readable):
result_str = self.communicate('RUFS') # read unit fault status result_str = self.communicate('RUFS') # read unit fault status
values_str = result_str.strip().split() values_str = result_str.strip().split()
values_int = [int(val) for val in values_str] values_int = [int(val) for val in values_str]
v1, v2, v3, v4, v5 = values_int #[:5]
status_messages = [ for status_type, status_msg, vi, bit in self.status_messages:
(ERROR, 'high tempr. cutout fault', 2, 0),
(ERROR, 'high RA tempr. fault', 2, 1),
(ERROR, 'high temperature fixed fault', 3, 7),
(ERROR, 'low temperature fixed fault', 3, 6),
(ERROR, 'high temperature fault', 3, 5),
(ERROR, 'low temperature fault', 3, 4),
(ERROR, 'low level fault', 3, 3),
(ERROR, 'circulator fault', 4, 5),
(ERROR, 'high press. cutout', 5, 2),
(ERROR, 'motor overloaded', 5, 1),
(ERROR, 'pump speed fault', 5, 0),
(WARN, 'open internal sensor', 1, 7),
(WARN, 'shorted internal sensor', 1, 6),
(WARN, 'high temperature warn', 3, 2),
(WARN, 'low temperature warn', 3, 1),
(WARN, 'low level warn', 3, 0),
(IDLE, 'max. heating', 5, 5),
(IDLE, 'heating', 5, 6),
(IDLE, 'cooling', 5, 4),
(IDLE, 'max cooling', 5, 3),
(IDLE, '', 4, 3),
]
for status_type, status_msg, vi, bit in status_messages:
if values_int[vi-1] & (1 << bit): if values_int[vi-1] & (1 << bit):
print(status_type, status_msg, vi, bit)
return status_type, status_msg return status_type, status_msg
return WARN, 'circulation off' return WARN, 'circulation off'
class TemperatureLoopA10(SensorA10, Drivable): class TemperatureLoopA10(SensorA10, Drivable):

View File

@ -29,28 +29,28 @@ from os import listdir, path
from setuptools import find_packages, setup from setuptools import find_packages, setup
import secop.version import frappy.version
scripts = glob(path.join('bin', 'secop-*')) scripts = glob(path.join('bin', 'frappy-*'))
uidir = path.join(path.dirname(__file__), 'secop', 'gui', 'ui') uidir = path.join(path.dirname(__file__), 'frappy', 'gui', 'ui')
uis = [path.join('gui', 'ui', entry) for entry in listdir(uidir)] uis = [path.join('gui', 'ui', entry) for entry in listdir(uidir)]
setup( setup(
name='secop-core', name='frappy-core',
version=secop.version.get_version(), version=frappy.version.get_version(),
license='GPL', license='GPL',
author='Enrico Faulhaber', author='Enrico Faulhaber',
author_email='enrico.faulhaber@frm2.tum.de', author_email='enrico.faulhaber@frm2.tum.de',
description='SECoP Playground core system', description='SECoP Playground core system',
packages=find_packages(exclude=['test']), packages=find_packages(exclude=['test']),
package_data={'secop': ['RELEASE-VERSION'] + uis}, package_data={'frappy': ['RELEASE-VERSION'] + uis},
data_files=[ data_files=[
('/lib/systemd/system-generators', ['etc/secop-generator']), ('/lib/systemd/system-generators', ['etc/frappy-generator']),
('/lib/systemd/system', ['etc/secop@.service', ('/lib/systemd/system', ['etc/frappy@.service',
'etc/secop.target', 'etc/frappy.target',
]), ]),
('/var/log/secop', []), ('/var/log/frappy', []),
], ],
scripts=scripts, scripts=scripts,
classifiers=[ classifiers=[