1 Commits

Author SHA1 Message Date
aa64a48ec5 merged changes for lakeshore and ccu4 2025-03-06 17:26:51 +01:00
110 changed files with 1803 additions and 5806 deletions

View File

@ -24,14 +24,12 @@
import sys import sys
import argparse import argparse
import socket
from pathlib import Path from pathlib import Path
# Add import path for inplace usage # Add import path for inplace usage
sys.path.insert(0, str(Path(__file__).absolute().parents[1])) sys.path.insert(0, str(Path(__file__).absolute().parents[1]))
from frappy.client.interactive import init, run, clientenv, interact from frappy.client.interactive import init, run, clientenv, interact
from frappy.protocol.discovery import scan
def parseArgv(argv): def parseArgv(argv):
@ -39,9 +37,6 @@ def parseArgv(argv):
parser.add_argument('-i', '--include', parser.add_argument('-i', '--include',
help='file to execute after connecting to the clients', metavar='file', help='file to execute after connecting to the clients', metavar='file',
type=Path, action='append', default=[]) type=Path, action='append', default=[])
parser.add_argument('-s', '--scan',
help='hosts to scan for (-s subnet for all nodes in subnet)',
action='append', default=[])
parser.add_argument('-o', '--only-execute', parser.add_argument('-o', '--only-execute',
help='Do not go into interactive mode after executing files. \ help='Do not go into interactive mode after executing files. \
Has no effect without --include.', action='store_true') Has no effect without --include.', action='store_true')
@ -51,38 +46,9 @@ def parseArgv(argv):
return parser.parse_args(argv) return parser.parse_args(argv)
def own_ip():
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.settimeout(0)
try:
# doesn't even have to be reachable
s.connect(('10.254.254.254', 1))
return s.getsockname()[0]
except Exception:
return '127.0.0.1'
finally:
s.close()
args = parseArgv(sys.argv[1:]) args = parseArgv(sys.argv[1:])
nodes = args.node success = init(*args.node)
hosts = args.scan
if not nodes and not hosts:
hosts = ['localhost']
if hosts:
answers = []
for host in hosts:
ans = scan()
if host == 'subnet': # all in subnet
answers.extend(ans)
else: # filter by ip
ip = socket.gethostbyname(host)
if ip == '127.0.0.1':
ip = own_ip()
answers.extend(a for a in ans if a.address == ip)
nodes.extend(f'{h.hostname}:{h.port}' for h in answers)
success = init(*nodes)
run_error = '' run_error = ''
file_success = False file_success = False

View File

@ -23,12 +23,12 @@
import sys import sys
from pathlib import Path from pathlib import Path
from frappy.lib import generalConfig
from frappy.logging import logger
# Add import path for inplace usage # Add import path for inplace usage
sys.path.insert(0, str(Path(__file__).absolute().parents[1])) sys.path.insert(0, str(Path(__file__).absolute().parents[1]))
from frappy.lib import generalConfig
from frappy.logging import logger
from frappy.client.interactive import Console from frappy.client.interactive import Console
from frappy.playground import play, USAGE from frappy.playground import play, USAGE

View File

@ -23,36 +23,106 @@
"""SEC node autodiscovery tool.""" """SEC node autodiscovery tool."""
import argparse import argparse
import json
import os
import select
import socket
import sys import sys
from frappy.protocol.discovery import scan, listen from collections import namedtuple
from time import time as currenttime
UDP_PORT = 10767
Answer = namedtuple('Answer',
'address, port, equipment_id, firmware, description')
def decode(msg, addr):
msg = msg.decode('utf-8')
try:
data = json.loads(msg)
except Exception:
return None
if not isinstance(data, dict):
return None
if data.get('SECoP') != 'node':
return None
try:
eq_id = data['equipment_id']
fw = data['firmware']
desc = data['description']
port = data['port']
except KeyError:
return None
addr, _scanport = addr
return Answer(addr, port, eq_id, fw, desc)
def print_answer(answer, *, short=False): def print_answer(answer, *, short=False):
if short: if short:
# NOTE: keep this easily parseable! # NOTE: keep this easily parseable!
print(f'{answer.equipment_id} {answer.hostname}:{answer.port}') print(f'{answer.equipment_id} {answer.address}:{answer.port}')
return return
numeric = f' ({answer.address})' if answer.address == answer.hostname else '' print(f'Found {answer.equipment_id} at {answer.address}:')
print(f'Found {answer.equipment_id} at {answer.hostname}{numeric}:')
print(f' Port: {answer.port}') print(f' Port: {answer.port}')
print(f' Firmware: {answer.firmware}') print(f' Firmware: {answer.firmware}')
desc = answer.description.replace('\n', '\n ') desc = answer.description.replace('\n', '\n ')
print(f' Node description: {desc}') print(f' Node description: {desc}')
print('-' * 80) print()
def scan(max_wait=1.0):
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
# send a general broadcast
try:
s.sendto(json.dumps(dict(SECoP='discover')).encode('utf-8'),
('255.255.255.255', UDP_PORT))
except OSError as e:
print('could not send the broadcast:', e)
# we still keep listening for self-announcements
start = currenttime()
seen = set()
while currenttime() < start + max_wait:
res = select.select([s], [], [], 0.1)
if res[0]:
try:
msg, addr = s.recvfrom(1024)
except socket.error: # pragma: no cover
continue
answer = decode(msg, addr)
if answer is None:
continue
if (answer.address, answer.equipment_id, answer.port) in seen:
continue
seen.add((answer.address, answer.equipment_id, answer.port))
yield answer
def listen(*, short=False):
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
if os.name == 'nt':
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
else:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
s.bind(('0.0.0.0', UDP_PORT))
while True:
try:
msg, addr = s.recvfrom(1024)
except KeyboardInterrupt:
break
answer = decode(msg, addr)
if answer:
print_answer(answer, short=short)
if __name__ == '__main__': if __name__ == '__main__':
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('-l', '--listen', action='store_true', parser.add_argument('-l', '--listen', action='store_true',
help='Keep listening after the broadcast.') help='Print short info. '
parser.add_argument('-s', '--short', action='store_true', 'Keep listening after the broadcast.')
help='Print short info (always on when listen).')
args = parser.parse_args(sys.argv[1:]) args = parser.parse_args(sys.argv[1:])
short = args.listen or args.short
if not short:
print('-' * 80)
for answer in scan(): for answer in scan():
print_answer(answer, short=short) print_answer(answer, short=args.listen)
if args.listen: if args.listen:
for answer in listen(): listen(short=args.listen)
print_answer(short=short)

View File

@ -1,53 +0,0 @@
#!/usr/bin/env python3
import sys
from pathlib import Path
# Add import path for inplace usage
sys.path.insert(0, str(Path(__file__).absolute().parents[1]))
from frappy.client.interactive import Client
from frappy_psi.iqplot import Plot
import numpy as np
import matplotlib.pyplot as plt
if len(sys.argv) < 2:
print('Usage: peus-plot <maxY>')
def get_modules(name):
return list(filter(None, (globals().get(name % i) for i in range(10))))
secnode = Client('pc13252:5000')
time_size = {'time', 'size'}
int_mods = [u] + get_modules('roi%d')
t_rois = get_modules('roi%d')
i_rois = get_modules('roi%di')
q_rois = get_modules('roi%dq')
maxx = None
if len(sys.argv) > 1:
maxy = float(sys.argv[1])
if len(sys.argv) > 2:
maxx = float(sys.argv[2])
else:
maxy = 0.02
iqplot = Plot(maxy, maxx)
for i in range(99):
pass
try:
while True:
curves = np.array(u.get_curves())
iqplot.plot(curves,
rois=[(r.time - r.size * 0.5, r.time + r.size * 0.5) for r in int_mods],
average=([r.time for r in t_rois],
[r.value for r in i_rois],
[r.value for r in q_rois]))
if not iqplot.pause(0.5):
break
except KeyboardInterrupt:
iqplot.close()

View File

@ -1,65 +0,0 @@
#!/usr/bin/env python3
import sys
from pathlib import Path
# Add import path for inplace usage
sys.path.insert(0, str(Path(__file__).absolute().parents[1]))
from frappy.client.interactive import Client
import numpy as np
import matplotlib.pyplot as plt
from frappy_psi.iqplot import Pause
if len(sys.argv) < 2:
print("""
Usage:
us-plot <end> [<start> [<npoints>]]
end: end of window [ns]
start: start of window [n2], default: 0
npoints: number fo points (default 1000)
""")
sys.exit(0)
Client('pc13252:5000')
def plot(array, ax, style, xs):
xaxis = np.arange(len(array)) * xs
return ax.plot(xaxis, array, style)[0]
def update(array, line, xs):
xaxis = np.arange(len(array)) * xs
line.set_data(np.array([xaxis, array]))
def on_close(event):
sys.exit(0)
start = 0
end = float(sys.argv[1])
npoints = 1000
if len(sys.argv) > 2:
start = float(sys.argv[2])
if len(sys.argv) > 3:
npoints = float(sys.argv[3])
fig, ax = plt.subplots(figsize=(15,3))
pause = Pause(fig)
try:
get_signal = iq.get_signal
print('plotting RUS signal')
except NameError:
get_signal = u.get_signal
print('plotting PE signal')
xs, signal = get_signal(start, end, npoints)
lines = [plot(s, ax, '-', xs) for s in signal]
while pause(0.5):
plt.draw()
xs, signal = get_signal(start, end, npoints)
for line, sig in zip(lines, signal):
update(sig, line, xs)

67
cfg/PEUS.py Normal file
View File

@ -0,0 +1,67 @@
Node(equipment_id = 'pe_ultrasound.psi.ch',
description = 'pulse echo ultra sound setup',
interface = 'tcp://5000',
)
Mod('f',
cls = 'frappy_psi.ultrasound.Frequency',
description = 'ultrasound frequency and acquisition loop',
uri = 'serial:///dev/ttyS1',
pars = 'pars',
pollinterval = 0.1,
time = 900, # start time
size = 5000,
freq = 1.17568e+06,
basefreq = 4.14902e+07,
control = False,
rusmode = False,
amp = 5.0,
nr = 1000, #500 #300 #100 #50 #30 #10 #5 #3 #1 #1000 #500 #300 #100 #50 #30 #10 #5 #3 #1 #500
sr = 32768, #16384
plot = True,
maxstep = 100000,
bw = 10E6, #butter worth filter bandwidth
maxy = 0.7, # y scale for plot
curves = 'curves', # module to transmit curves:
)
Mod('curves',
cls = 'frappy_psi.ultrasound.Curves',
description = 't, I, Q and pulse arrays for plot',
)
Mod('delay',
cls = 'frappy__psi.dg645.Delay',
description = 'delay line with 2 channels',
uri = 'serial:///dev/ttyS2',
on1 = 1e-9,
on2 = 1E-9,
off1 = 400e-9,
off2 = 600e-9,
)
Mod('pars',
cls = 'frappy_psi.ultrasound.Pars',
description = 'SEA parameters',
)
def roi(nr, time=None, size=300):
Mod(f'roi{nr}',
cls = 'frappy_psi.ultrasound.Roi',
description = f'I/Q of region {nr}',
main = 'f',
time=time or 4000,
size=size,
enable=time is not None,
)
roi(0, 2450) # you may add size as argument if not default
roi(1, 5950)
roi(2, 9475)
roi(3, 12900)
roi(4, 16100)
roi(5) # disabled
roi(6)
roi(7)
roi(8)
roi(9)

View File

@ -1,87 +0,0 @@
Node('PEUS.psi.ch',
'ultrasound, pulse_echo configuration',
interface='5000',
)
Mod('u',
'frappy_psi.ultrasound.PulseEcho',
'ultrasound acquisition loop',
freq='f',
# pollinterval=0.1,
time=900.0,
size=5000.0,
nr=500,
sr=32768,
bw=1e7,
)
Mod('fio',
'frappy_psi.ultrasound.FreqStringIO', '',
uri='serial:///dev/ttyS1?baudrate=57600',
)
Mod('f',
'frappy_psi.ultrasound.Frequency',
'writable for frequency',
output='R', # L for LF (bnc), R for RF (type N)
io='fio',
amp=0.5, # VPP
)
Mod('fdif',
'frappy_psi.ultrasound.FrequencyDif',
'writable for frequency minus base frequency',
freq='f',
base=41490200.0,
)
# Mod('curves',
# 'frappy_psi.ultrasound.Curves',
# 't, I, Q and pulse arrays for plot',
# )
def roi(name, time, size, components='iqpa', enable=True, control=False, freq=None, **kwds):
description = 'I/Q of region {name}'
if freq:
kwds.update(cls='frappy_psi.ultrasound.ControlRoi',
description=f'{description} as control loop',
freq=freq, **kwds)
else:
kwds.update(cls='frappy_psi.ultrasound.Roi',
description=description, **kwds)
kwds.update({c: name + c for c in components})
Mod(name,
main='u',
time=time,
size=size,
enable=enable,
**kwds,
)
for c in components:
Mod(name + c,
'frappy.modules.Readable',
f'{name}{c} component',
)
# control loop
roi('roi0', 2450, 300, freq='f', maxstep=100000, minstep=4000)
# other rois
roi('roi1', 5950, 300)
roi('roi2', 9475, 300)
roi('roi3', 12900, 300)
#roi('roi4', 400, 30, False)
#roi('roi5', 400, 30, False)
#roi('roi6', 400, 30, False)
#roi('roi7', 400, 30, False)
#roi('roi8', 400, 30, False)
#roi('roi9', 400, 30, False)
Mod('delay',
'frappy_psi.dg645.Delay',
'delay line with 2 channels',
uri='serial:///dev/ttyS2',
on1=1e-09,
on2=1e-09,
off1=4e-07,
off2=6e-07,
)

62
cfg/RUS.py Normal file
View File

@ -0,0 +1,62 @@
Node(equipment_id = 'r_ultrasound.psi.ch',
description = 'resonant ultra sound setup',
interface = 'tcp://5000',
)
Mod('f',
cls = 'frappy_psi.ultrasound.Frequency',
description = 'ultrasound frequency and acquisition loop',
uri = 'serial:///dev/ttyS1',
pars = 'pars',
pollinterval = 0.1,
time = 900, # start time
size = 5000,
freq = 1.e+03,
basefreq = 1.E+3,
control = False,
rusmode = False,
amp = 2.5,
nr = 1, #500 #300 #100 #50 #30 #10 #5 #3 #1 #1000 #500 #300 #100 #50 #30 #10 #5 #3 #1 #500
sr = 1E8, #16384
plot = True,
maxstep = 100000,
bw = 10E6, #butter worth filter bandwidth
maxy = 0.7, # y scale for plot
curves = 'curves', # module to transmit curves:
)
Mod('curves',
cls = 'frappy_psi.ultrasound.Curves',
description = 't, I, Q and pulse arrays for plot',
)
Mod('roi0',
cls = 'frappy_psi.ultrasound.Roi',
description = 'I/Q of region in the control loop',
time = 300, # this is the center of roi:
size = 5000,
main = f,
)
Mod('roi1',
cls = 'frappy_psi.ultrasound.Roi',
description = 'I/Q of region 1',
time = 100, # this is the center of roi:
size = 300,
main = f,
)
Mod('delay',
cls = 'frappy__psi.dg645.Delay',
description = 'delay line with 2 channels',
uri = 'serial:///dev/ttyS2',
on1 = 1e-9,
on2 = 1E-9,
off1 = 400e-9,
off2 = 600e-9,
)
Mod('pars',
cls = 'frappy_psi.ultrasound.Pars',
description = 'SEA parameters',
)

View File

@ -1,39 +0,0 @@
Node(equipment_id = 'r_ultrasound.psi.ch',
description = 'resonant ultra sound setup',
interface = 'tcp://5000',
)
Mod('iq',
cls = 'frappy_psi.ultrasound.RUS',
description = 'ultrasound iq mesurement',
imod = 'i',
qmod = 'q',
freq='f',
input_range=10, # VPP
input_delay = 0,
periods = 163,
)
Mod('freqio',
'frappy_psi.ultrasound.FreqStringIO',
' ',
uri = 'serial:///dev/ttyS1?baudrate=57600',
)
Mod('f',
cls = 'frappy_psi.ultrasound.Frequency',
description = 'ultrasound frequency',
io='freqio',
output='L', # L for LF (bnc), R for RF (type N)
target=10000,
)
Mod('i',
cls='frappy.modules.Readable',
description='I component',
)
Mod('q',
cls='frappy.modules.Readable',
description='Q component',
)

15
cfg/addons/ah2700_cfg.py Executable file → Normal file
View File

@ -2,21 +2,8 @@ Node('ah2700.frappy.psi.ch',
'Andeen Hagerlin 2700 Capacitance Bridge', 'Andeen Hagerlin 2700 Capacitance Bridge',
) )
Mod('cap_io',
'frappy_psi.ah2700.Ah2700IO',
'',
uri='linse-976d-ts:3006',
)
Mod('cap', Mod('cap',
'frappy_psi.ah2700.Capacitance', 'frappy_psi.ah2700.Capacitance',
'capacitance', 'capacitance',
io = 'cap_io', uri='dil4-ts.psi.ch:3008',
)
Mod('loss',
'frappy_psi.parmod.Par',
'loss parameter',
read='cap.loss',
unit='deg',
) )

View File

@ -1,28 +0,0 @@
Node('srs830.ppms.psi.ch',
'',
interface='tcp://5000',
)
Mod('b',
'frappy_psi.SR830.XY',
'signal from Stanford Rasearch lockin',
uri='linse-976d-ts:3002',
)
Mod('bx',
'frappy_psi.parmod.Comp',
'x-comp',
read='b.value[0]',
unit='V',
)
Mod('by',
'frappy_psi.parmod.Comp',
'y-comp',
read='b.value[1]',
unit='V',
)
Mod('bf',
'frappy_psi.parmod.Par',
'lockin frequency',
read='b.freq',
unit='Hz',
)

View File

@ -1,337 +0,0 @@
# by ID (independent of plug location)
turbo_uri = '/dev/serial/by-id/usb-FTDI_FT232R_USB_UART_A601PCGF-if00-port0'
press_uri = '/dev/serial/by-id/usb-FTDI_FT232R_USB_UART_AH07445U-if00-port0'
itc_uri = '/dev/serial/by-id/usb-Prolific_Technology_Inc._USB-Serial_Controller_D-if00-port0'
lsc_uri = '192.168.1.2:7777'
# by plug location:
#turbo_uri='/dev/serial/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.1:1.0-port0'
#press_uri = '/dev/serial/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.2:1.0-port0'
#itc_uri = '/dev/serial/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.3:1.0-port0'
# over USB (does not work anymore)
#lsc_uri='serial:///dev/ttyACM1?baudrate=57600+parity=odd+bytesize=7+stopbits=1',
Node('dil5_logo.psi.ch',
'dil5 logo test',
interface='tcp://5000',
secondary = ['ws://8010']
)
Mod('logo',
'frappy_psi.logo.IO',
'',
ip_address = "192.168.0.3",
tcap_client = 0x3000,
tsap_server = 0x2000
)
Mod('V1',
'frappy_psi.logo.DigitalActuator',
'Valves',
io = 'logo',
feedback_addr ="V1025.0",
output_addr ="V1064.3"
)
Mod('V2',
'frappy_psi.logo.DigitalActuator',
'dil bypass',
io = 'logo',
feedback_addr ="V1024.2",
output_addr ="V1064.0",
)
Mod('V4',
'frappy_psi.logo.DigitalActuator',
'compressor to dump',
io = 'logo',
# feedback_addr ="V1024.5", # not verified
output_addr ="V1064.7",
target_addr ="V404.1",
)
Mod('V5',
'frappy_psi.logo.DigitalActuator',
'compressor input',
io = 'logo',
feedback_addr ="V1024.4",
output_addr ="V1064.2",
)
Mod('V9',
'frappy_psi.logo.DelayedActuator',
'dump output',
io = 'logo',
delay_addr = 'VW24',
feedback_addr ="V1024.3",
output_addr ="V1064.5",
target_addr ="V404.3",
)
Mod('forepump',
'frappy_psi.logo.DigitalActuator',
'forepump',
io = 'logo',
output_addr ="V1064.6",
target_addr ="V404.4",
)
Mod('compressor',
'frappy_psi.logo.DigitalActuator',
'',
io = 'logo',
output_addr ="V1064.4",
target_addr ="V404.2",
)
Mod('p2',
'frappy_psi.logo.Value',
'pressure after compressor',
io = 'logo',
addr ="VW0",
value = Param(unit='mbar'),
)
Mod('p1',
'frappy_psi.logo.Value',
'dump pressure',
io = 'logo',
addr ="VW28",
value = Param(unit='mbar'),
)
Mod('p5',
'frappy_psi.logo.Value',
'pressure after forepump',
io = 'logo',
addr ="VW4",
value = Param(unit='mbar'),
)
Mod('airpressure',
'frappy_psi.logo.DigitalValue',
'Airpressure state',
io = 'logo',
addr ="V1024.7",
)
Mod('io_ls273',
'frappy_psi.ls372.StringIO',
'io for Ls372',
uri=lsc_uri,
)
Mod('sw',
'frappy_psi.ls372.Switcher',
'channel switcher',
io = 'io_ls273',
)
Mod('T_mix',
'frappy_psi.ls372.TemperatureLoop',
'mix temperature chan 5',
channel = 5,
switcher = 'sw',
)
Mod('T_ivc',
'frappy_psi.ls372.TemperatureLoop',
'mix temperature chan 2',
channel = 2,
switcher = 'sw',
)
Mod('T_still',
'frappy_psi.ls372.TemperatureLoop',
'mix temperature chan 3',
channel = 3,
switcher = 'sw',
)
Mod('T_sorb',
'frappy_psi.ls372.TemperatureLoop',
'mix temperature chan 1',
channel = 1,
switcher = 'sw',
)
Mod('T_cp',
'frappy_psi.ls372.TemperatureLoop',
'mix temperature chan 4',
channel = 4,
switcher = 'sw',
)
Mod('io_pfeiffer',
'frappy_psi.pfeiffer_new.PfeifferProtocol',
'',
uri=f'serial://{press_uri}?baudrate=9600+parity=none+bytesize=8+stopbits=1',
)
Mod('io_turbo',
'frappy_psi.pfeiffer_new.PfeifferProtocol',
'',
uri=f'serial://{turbo_uri}?baudrate=9600+parity=none+bytesize=8+stopbits=1',
)
Mod('p3',
'frappy_psi.pfeiffer_new.RPT200',
'Pressure in HPa',
io = 'io_pfeiffer',
address= 2,
)
Mod('p4',
'frappy_psi.pfeiffer_new.RPT200',
'Pressure in HPa',
io = 'io_pfeiffer',
address= 4
)
Mod('turbopump',
'frappy_psi.pfeiffer_new.TCP400',
'Pfeiffer Turbopump',
io = 'io_turbo',
address= 1
)
Mod('MV10',
'frappy_psi.manual_valves.ManualValve',
'Manual Valve MV10'
)
Mod('MV13',
'frappy_psi.manual_valves.ManualValve',
'Manual Valve MV13'
)
Mod('MV8',
'frappy_psi.manual_valves.ManualValve',
'Manual Valve MV8'
)
Mod('MVB',
'frappy_psi.manual_valves.ManualValve',
'Manual Valve MVB'
)
Mod('MV2',
'frappy_psi.manual_valves.ManualValve',
'Manual Valve MV2'
)
Mod('MV1',
'frappy_psi.manual_valves.ManualValve',
'Manual Valve MV1'
)
Mod('MV3a',
'frappy_psi.manual_valves.ManualValve',
'Manual Valve MV3a'
)
Mod('MV3b',
'frappy_psi.manual_valves.ManualValve',
'Manual Valve MV3b'
)
Mod('GV1',
'frappy_psi.manual_valves.ManualValve',
'Manual Valve GV1'
)
Mod('GV2',
'frappy_psi.manual_valves.ManualValve',
'Manual Valve GV2'
)
Mod('MV14',
'frappy_psi.manual_valves.ManualValve',
'Manual Valve MV14'
)
Mod('MV12',
'frappy_psi.manual_valves.ManualValve',
'Manual Valve MV12'
)
Mod('MV11',
'frappy_psi.manual_valves.ManualValve',
'Manual Valve MV11'
)
Mod('MV9',
'frappy_psi.manual_valves.ManualValve',
'Manual Valve MV9'
)
Mod('itc',
'frappy_psi.mercury.IO',
'connection to MercuryiTC',
uri=f'serial://{itc_uri}?baudrate=115200+parity=none+bytesize=8+stopbits=1',
)
Mod('T_still_wup',
'frappy_psi.mercury.TemperatureLoop',
'still warmup temperature',
slot='MB1.T1',
io='itc',
)
Mod('T_one_K',
'frappy_psi.mercury.TemperatureLoop',
'1 K plate warmup temperature',
slot='DB5.T1',
io='itc',
)
Mod('T_mix_wup',
'frappy_psi.mercury.TemperatureLoop',
'mix. chamber warmup temperature',
slot='DB6.T1',
io='itc',
)
Mod('T_ivc_wup',
'frappy_psi.mercury.TemperatureLoop',
'IVC warmup temperature',
slot='DB7.T1',
io='itc',
)
Mod('T_cond',
'frappy_psi.mercury.TemperatureLoop',
'condenser temperature',
slot='DB8.T1',
io='itc',
)
Mod('safety',
'frappy_psi.dilution.Interlock',
'interlock mechanism',
io='logo',
dil='dil',
)
Mod('dil',
'frappy_psi.dilution.DIL5',
'dilution state machine and parameters',
condenseline_pressure = "p2",
condense_valve = "V9",
dump_valve = "V4",
forepump = "forepump",
compressor = "compressor",
turbopump = "turbopump",
condenseline_valve = "V1",
circuitshort_valve = "V2",
still_pressure = "p4",
still_pressure_turbo = "p3",
#ls372 = "res1",
dump_pressure = "p1",
condensing_p_low = 1200,
condensing_p_high = 1500,
)

View File

@ -1,136 +0,0 @@
Node('test.config.frappy.demo',
'''short description of the testing sec-node
This description for the node can 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://5000',
)
Mod('attachtest',
'frappy_demo.test.WithAtt',
'test attached',
att = 'LN2',
)
Mod('pinata',
'frappy_demo.test.Pin',
'scan test',
)
Mod('recursive',
'frappy_demo.test.RecPin',
'scan test',
)
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',
)
Mod('Decision',
'frappy_demo.test.Mapped',
'Random value from configured property choices. Config accepts anything ' \
'that can be converted to a list',
choices = ['Yes', 'Maybe', 'No'],
)
Mod('c',
'frappy_demo.test.Commands',
'a command test',
)
Mod('cryo',
'frappy_demo.cryo.Cryostat',
'A simulated cc cryostat with heat-load, specific heat for the sample and a '
'temperature dependent 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'),
)
Mod('heatswitch',
'frappy_demo.modules.Switch',
'Heatswitch for `mf` device',
switch_on_time = 5,
switch_off_time = 10,
)
Mod('bool',
'frappy_demo.modules.BoolWritable',
'boolean writable test',
)
Mod('lscom',
'frappy_psi.ls370sim.Ls370Sim',
'simulated serial communicator to a LS 370',
visibility = 3
)
Mod('sw',
'frappy_psi.ls370res.Switcher',
'channel switcher for Lsc controller',
io = 'lscom',
)
Mod('a',
'frappy_psi.ls370res.ResChannel',
'resistivity',
channel = 1,
switcher = 'sw',
)
Mod('b',
'frappy_psi.ls370res.ResChannel',
'resistivity',
channel = 3,
switcher = 'sw',
)

View File

@ -1,100 +0,0 @@
Node('fi2.psi.ch',
'vacuum furnace ILL Type',
'tcp://5000',
)
Mod('htr_io',
'frappy_psi.tdkpower.IO',
'powersupply communicator',
uri = 'serial:///dev/ttyUSB0',
)
Mod('htr',
'frappy_psi.tdkpower.Power',
'heater power',
io= 'htr_io',
)
Mod('out',
'frappy_psi.tdkpower.Output',
'heater output',
io = 'htr_io',
maxvolt = 5,
maxcurrent = 25,
)
Mod('relais',
'frappy_psi.ionopimax.DigitalOutput',
'relais for power output',
addr = 'o2',
)
Mod('T_main',
'frappy_psi.ionopimax.CurrentInput',
'sample temperature',
addr = 'ai4',
valuerange = (0, 1372),
value = Param(unit='degC'),
)
Mod('T_extra',
'frappy_psi.ionopimax.CurrentInput',
'extra temperature',
addr = 'ai3',
valuerange = (0, 1372),
value = Param(unit='degC'),
)
Mod('T_htr',
'frappy_psi.ionopimax.CurrentInput',
'heater temperature',
addr = 'ai2',
valuerange = (0, 1372),
value = Param(unit='degC'),
)
Mod('T_wall',
'frappy_psi.ionopimax.VoltageInput',
'furnace wall temperature',
addr = 'av2',
rawrange = (0, 1.5),
valuerange = (0, 150),
value = Param(unit='degC'),
)
Mod('T',
'frappy_psi.picontrol.PI',
'controlled Temperature',
input = 'T_htr',
output = 'out',
relais = 'relais',
p = 2,
i = 0.01,
)
Mod('interlocks',
'frappy_psi.furnace.Interlocks',
'interlock parameters',
input = 'T_htr',
wall_T = 'T_wall',
vacuum = 'p',
relais = 'relais',
control = 'T',
wall_limit = 50,
vacuum_limit = 0.1,
)
Mod('p_io',
'frappy_psi.pfeiffer.IO',
'pressure io',
uri='serial:///dev/ttyUSBlower',
)
Mod('p',
'frappy_psi.pfeiffer.Pressure',
'pressure reading',
io = 'p_io',
)

View File

@ -1,117 +0,0 @@
Node('fi.psi.ch',
'ILL furnace',
'tcp://5000',
)
Mod('T_main',
'frappy_psi.furnace.PRtransmitter',
'sample temperature',
addr='ai2',
valuerange=(0, 2300),
value=Param(unit='degC'),
)
Mod('T_extra',
'frappy_psi.furnace.PRtransmitter',
'extra temperature',
addr='ai1',
valuerange=(0, 2300),
value=Param(unit='degC'),
)
Mod('T_wall',
'frappy_psi.ionopimax.VoltageInput',
'furnace wall temperature',
addr='av2',
rawrange=(0, 1.5),
valuerange=(0, 150),
value=Param(unit='degC'),
)
Mod('T3',
'frappy_psi.furnace.PRtransmitter',
'extra temperature',
addr='ai3',
valuerange=(0, 1372),
value=Param(unit='degC'),
)
Mod('T4',
'frappy_psi.furnace.PRtransmitter',
'extra temperature',
addr='ai4',
valuerange=(0, 1372),
value=Param(unit='degC'),
)
Mod('T',
'frappy_psi.picontrol.PI',
'controlled Temperature',
input_module='T_main',
output_module='htr',
value = Param(unit='degC'),
output_min = 0,
output_max = 100,
# relais='relais',
p=0.1,
i=0.01,
)
Mod('htr_io',
'frappy_psi.tdkpower.IO',
'powersupply communicator',
uri='serial:///dev/ttyUSB0?baudrate=9600',
)
Mod('htr_power',
'frappy_psi.tdkpower.Power',
'heater power',
io='htr_io',
)
Mod('htr',
'frappy_psi.furnace.TdkOutput',
'heater output',
io='htr_io',
maxvolt=8,
maxcurrent=200,
)
Mod('flowswitch',
'frappy_psi.ionopimax.DigitalInput',
'flow switch',
addr='dt2',
true_level='low',
)
Mod('interlocks',
'frappy_psi.furnace.Interlocks',
'interlock parameters',
main_T='T_main',
extra_T='T_extra',
wall_T='T_wall',
vacuum='p',
control='T',
htr='htr',
flowswitch='flowswitch',
wall_limit=50,
main_T_limit = 1400,
extra_T_limit = 1400,
vacuum_limit=0.01,
)
Mod('p',
'frappy_psi.furnace.PKRgauge',
'pressure reading',
addr = 'av1',
rawrange = (1.82, 8.6),
valuerange = (5e-9, 1000),
value = Param(unit='mbar'),
)
Mod('vso',
'frappy_psi.ionopimax.VoltagePower',
'voltage power output',
target = 24,
export = False,
)

View File

@ -1,130 +0,0 @@
Node('fs.psi.ch',
'small vacuum furnace',
'tcp://5000',
)
Mod('T',
'frappy_psi.picontrol.PI2',
'controlled Temperature on sample (2nd loop)',
input = 'T_sample',
output = 'T_reg',
relais = 'relais',
p = 1.2,
i = 0.005,
)
Mod('T_reg',
'frappy_psi.picontrol.PI',
'controlled Temperature on heater',
input = 'T_htr',
output = 't_out',
relais = 'relais',
p = 1,
i = 0.003,
)
Mod('p_reg',
'frappy_psi.picontrol.PI',
'controlled pressure',
input = 'p',
output = 'p_out',
relais = 'relais',
p = 1,
i = 0.005,
)
Mod('T_htr',
'frappy_psi.ionopimax.CurrentInput',
'heater temperature',
addr = 'ai4',
valuerange = (0, 1372),
value = Param(unit='degC'),
)
Mod('T_sample',
'frappy_psi.ionopimax.CurrentInput',
'sample temperature',
addr = 'ai3',
valuerange = (0, 1372),
value = Param(unit='degC'),
)
Mod('T_extra',
'frappy_psi.ionopimax.CurrentInput',
'extra temperature',
addr = 'ai2',
valuerange = (0, 1372),
value = Param(unit='degC'),
)
Mod('T_wall',
'frappy_psi.ionopimax.VoltageInput',
'furnace wall temperature',
addr = 'av2',
rawrange = (0, 1.5),
valuerange = (0, 150),
value = Param(unit='degC'),
)
Mod('htr_io',
'frappy_psi.bkpower.IO',
'powersupply communicator',
uri = 'serial:///dev/ttyUSBupper',
)
Mod('htr',
'frappy_psi.bkpower.Power',
'heater power',
io= 'htr_io',
)
Mod('t_out',
'frappy_psi.bkpower.Output',
'heater output',
p_value = 'p_out',
io = 'htr_io',
maxvolt = 50,
maxcurrent = 2,
)
Mod('relais',
'frappy_psi.ionopimax.DigitalOutput',
'relais for power output',
addr = 'o2',
)
Mod('interlocks',
'frappy_psi.furnace.Interlocks',
'interlock parameters',
input = 'T_htr',
wall_T = 'T_wall',
htr_T = 'T_htr',
main_T = 'T_sample',
extra_T = 'T_extra',
vacuum = 'p',
relais = 'relais',
control = 'T',
wall_limit = 100,
vacuum_limit = 0.1,
)
Mod('p',
'frappy_psi.ionopimax.LogVoltageInput',
'pressure reading',
addr = 'av1',
rawrange = (1.82, 8.6),
valuerange = (5e-9, 1000),
value = Param(unit='mbar'),
)
Mod('vso',
'frappy_psi.ionopimax.VoltagePower',
'voltage power output',
target = 24,
export = False,
)

View File

@ -4,4 +4,4 @@ logdir = ./log
piddir = ./pid piddir = ./pid
confdir = ./cfg confdir = ./cfg
comlog = True comlog = True
omit_unchanged_within = 60

View File

@ -6,8 +6,7 @@ Node('LscSIM.psi.ch',
Mod('io', Mod('io',
'frappy_psi.ls370res.StringIO', 'frappy_psi.ls370res.StringIO',
'io for Ls370', 'io for Ls370',
# uri = 'localhost:2089', uri = 'localhost:2089',
uri = 'linse-976d-ts:3007',
) )
Mod('sw', Mod('sw',
'frappy_psi.ls370res.Switcher', 'frappy_psi.ls370res.Switcher',
@ -18,7 +17,7 @@ Mod('res1',
'frappy_psi.ls370res.ResChannel', 'frappy_psi.ls370res.ResChannel',
'resistivity chan 1', 'resistivity chan 1',
vexc = '2mV', vexc = '2mV',
channel = 2, channel = 1,
switcher = 'sw', switcher = 'sw',
) )
Mod('res2', Mod('res2',

View File

@ -1,21 +0,0 @@
Node('haake2.config.sea.psi.ch',
'Haake thermostat + Eurotherm controller',
)
Mod('sea_main',
'frappy_psi.sea.SeaClient',
'main sea connection for haakeuro.config',
config = 'haake2.config',
service = 'main',
)
Mod('th',
'frappy_psi.sea.SeaDrivable', '',
meaning = ('temperature', 10),
io = 'sea_main',
sea_object = 'th',
extra_modules=['t2'],
)
Mod('ts',
'frappy_psi.sea.SeaReadable', '',
io='sea_main',
single_module='th.t2',
)

View File

@ -1,5 +1,5 @@
Node('haake.config.sea.psi.ch', Node('haakeuro.config.sea.psi.ch',
'Haake thermostat', 'Haake thermostat + Eurotherm controller',
) )
Mod('sea_main', Mod('sea_main',
'frappy_psi.sea.SeaClient', 'frappy_psi.sea.SeaClient',
@ -13,5 +13,9 @@ Mod('th',
io = 'sea_main', io = 'sea_main',
sea_object = 'th', sea_object = 'th',
extra_modules=['t2'], extra_modules=['t2'],
value=Param(unit='degC'), )
Mod('ts',
'frappy_psi.sea.SeaReadable', '',
io='sea_main',
single_module='th.t2',
) )

View File

@ -10,12 +10,11 @@ Mod('sea_main',
) )
Mod('tt', Mod('tt',
'frappy_psi.sea.LscDrivable', '', 'frappy_psi.sea.SeaDrivable', '',
io='sea_main', io='sea_main',
meaning=['temperature_regulation', 27], meaning=['temperature_regulation', 27],
sea_object='tt', sea_object='tt',
sensor_path='tm', rel_paths=['tm', '.', 'set', 'dblctrl'],
set_path='set',
) )
Mod('cc', Mod('cc',

View File

@ -10,12 +10,11 @@ Mod('sea_main',
) )
Mod('tt', Mod('tt',
'frappy_psi.sea.LscDrivable', '', 'frappy_psi.sea.SeaDrivable', '',
io='sea_main', io='sea_main',
meaning=['temperature_regulation', 27], meaning=['temperature_regulation', 27],
sea_object='tt', sea_object='tt',
sensor_path='tm', rel_paths=['tm', '.', 'set', 'dblctrl'],
set_path='set',
) )
Mod('cc', Mod('cc',

View File

@ -10,12 +10,11 @@ Mod('sea_main',
) )
Mod('tt', Mod('tt',
'frappy_psi.sea.LscDrivable', '', 'frappy_psi.sea.SeaDrivable', '',
io='sea_main', io='sea_main',
meaning=['temperature_regulation', 27], meaning=['temperature_regulation', 27],
sea_object='tt', sea_object='tt',
sensor_path='tm', rel_paths=['tm', '.', 'set', 'dblctrl'],
set_path='set',
) )
Mod('cc', Mod('cc',

View File

@ -10,12 +10,11 @@ Mod('sea_main',
) )
Mod('tt', Mod('tt',
'frappy_psi.sea.LscDrivable', '', 'frappy_psi.sea.SeaDrivable', '',
io='sea_main', io='sea_main',
meaning=['temperature_regulation', 27], meaning=['temperature_regulation', 27],
sea_object='tt', sea_object='tt',
sensor_path='tm', rel_paths=['tm', '.', 'set', 'dblctrl'],
set_path='set',
) )
Mod('cc', Mod('cc',

View File

@ -10,12 +10,11 @@ Mod('sea_main',
) )
Mod('tt', Mod('tt',
'frappy_psi.sea.LscDrivable', '', 'frappy_psi.sea.SeaDrivable', '',
io='sea_main', io='sea_main',
meaning=['temperature_regulation', 27], meaning=['temperature_regulation', 27],
sea_object='tt', sea_object='tt',
sensor_path='tm', rel_paths=['tm', '.', 'set', 'dblctrl'],
set_path='set',
) )
Mod('cc', Mod('cc',

View File

@ -10,12 +10,11 @@ Mod('sea_main',
) )
Mod('tt', Mod('tt',
'frappy_psi.sea.LscDrivable', '', 'frappy_psi.sea.SeaDrivable', '',
io='sea_main', io='sea_main',
meaning=['temperature_regulation', 27], meaning=['temperature_regulation', 27],
sea_object='tt', sea_object='tt',
sensor_path='tm', rel_paths=['tm', '.', 'set', 'dblctrl'],
set_path='set',
) )
Mod('pauto', Mod('pauto',

View File

@ -14,8 +14,7 @@ Mod('tt',
io='sea_main', io='sea_main',
meaning=['temperature_regulation', 27], meaning=['temperature_regulation', 27],
sea_object='tt', sea_object='tt',
rel_paths=['main', '.', 'set'], rel_paths=['tt', 'set'],
value=Param(unit='K'),
) )
Mod('T_ccr', Mod('T_ccr',
@ -23,7 +22,6 @@ Mod('T_ccr',
io='sea_main', io='sea_main',
sea_object='tt', sea_object='tt',
rel_paths=['ccr'], rel_paths=['ccr'],
value=Param(unit='K'),
) )
Mod('jtccr', Mod('jtccr',
@ -103,35 +101,30 @@ Mod('p1',
'frappy_psi.sea.SeaReadable', '', 'frappy_psi.sea.SeaReadable', '',
io='sea_main', io='sea_main',
sea_object='p1', sea_object='p1',
value=Param(unit='mbar'),
) )
Mod('p2', Mod('p2',
'frappy_psi.sea.SeaReadable', '', 'frappy_psi.sea.SeaReadable', '',
io='sea_main', io='sea_main',
sea_object='p2', sea_object='p2',
value=Param(unit='mbar'),
) )
Mod('p3', Mod('p3',
'frappy_psi.sea.SeaReadable', '', 'frappy_psi.sea.SeaReadable', '',
io='sea_main', io='sea_main',
sea_object='p3', sea_object='p3',
value=Param(unit='mbar'),
) )
Mod('p4', Mod('p4',
'frappy_psi.sea.SeaReadable', '', 'frappy_psi.sea.SeaReadable', '',
io='sea_main', io='sea_main',
sea_object='p4', sea_object='p4',
value=Param(unit='mbar'),
) )
Mod('pressreg', Mod('pressreg',
'frappy_psi.sea.SeaReadable', '', 'frappy_psi.sea.SeaReadable', '',
io='sea_main', io='sea_main',
sea_object='pressreg', sea_object='pressreg',
value=Param(unit='mbar'),
) )
Mod('epc', Mod('epc',

View File

@ -10,12 +10,11 @@ Mod('sea_main',
) )
Mod('tt', Mod('tt',
'frappy_psi.sea.LscDrivable', '', 'frappy_psi.sea.SeaDrivable', '',
io='sea_main', io='sea_main',
meaning=['temperature_regulation', 27], meaning=['temperature_regulation', 27],
sea_object='tt', sea_object='tt',
sensor_path='tm', rel_paths=['tm', '.', 'set', 'dblctrl'],
set_path='set',
) )
Mod('cc', Mod('cc',

View File

@ -10,12 +10,11 @@ Mod('sea_main',
) )
Mod('tt', Mod('tt',
'frappy_psi.sea.LscDrivable', '', 'frappy_psi.sea.SeaDrivable', '',
io='sea_main', io='sea_main',
meaning=['temperature_regulation', 27], meaning=['temperature_regulation', 27],
sea_object='tt', sea_object='tt',
sensor_path='tm', rel_paths=['tm', '.', 'set', 'dblctrl'],
set_path='set',
) )
Mod('cc', Mod('cc',

View File

@ -10,12 +10,11 @@ Mod('sea_main',
) )
Mod('tt', Mod('tt',
'frappy_psi.sea.LscDrivable', '', 'frappy_psi.sea.SeaDrivable', '',
io='sea_main', io='sea_main',
meaning=['temperature_regulation', 27], meaning=['temperature_regulation', 27],
sea_object='tt', sea_object='tt',
sensor_path='tm', rel_paths=['tm', '.', 'set', 'dblctrl'],
set_path='set',
) )
Mod('cc', Mod('cc',

View File

@ -8,12 +8,11 @@ Mod('sea_main',
service = 'main', service = 'main',
) )
Mod('tt', Mod('tt',
'frappy_psi.sea.LscDrivable', '', 'frappy_psi.sea.SeaDrivable', '',
io='sea_main', io = 'sea_main',
meaning=['temperature_regulation', 27], meaning=['temperature_regulation', 27],
sea_object='tt', sea_object = 'tt',
sensor_path='tm', rel_paths=['tm', '.', 'set', 'dblctrl'],
set_path='set',
) )
Mod('cc', Mod('cc',
'frappy_psi.sea.SeaReadable', '', 'frappy_psi.sea.SeaReadable', '',

View File

@ -10,12 +10,11 @@ Mod('sea_main',
) )
Mod('tt', Mod('tt',
'frappy_psi.sea.LscDrivable', '', 'frappy_psi.sea.SeaDrivable', '',
io='sea_main', io='sea_main',
meaning=['temperature_regulation', 27], meaning=['temperature_regulation', 27],
sea_object='tt', sea_object='tt',
sensor_path='tm', rel_paths=['tm', '.', 'set', 'dblctrl'],
set_path='set',
) )
Mod('cc', Mod('cc',

View File

@ -10,12 +10,11 @@ Mod('sea_main',
) )
Mod('tt', Mod('tt',
'frappy_psi.sea.LscDrivable', '', 'frappy_psi.sea.SeaDrivable', '',
io='sea_main', io='sea_main',
meaning=['temperature_regulation', 27], meaning=['temperature_regulation', 27],
sea_object='tt', sea_object='tt',
sensor_path='tm', rel_paths=['tm', '.', 'set', 'dblctrl'],
set_path='set',
) )
Mod('th', Mod('th',

View File

@ -10,13 +10,12 @@ Mod('sea_main',
) )
Mod('tt', Mod('tt',
'frappy_psi.sea.LscDrivable', '', 'frappy_psi.sea.SeaDrivable', '',
io='sea_main', io='sea_main',
meaning=['temperature_regulation', 27], meaning=['temperature_regulation', 27],
sea_object='tt', sea_object='tt',
sensor_path='tm', rel_paths=['tm', '.', 'set', 'dblctrl'],
set_path='set', )
)
Mod('th', Mod('th',
'frappy_psi.sea.SeaReadable', 'frappy_psi.sea.SeaReadable',

View File

@ -15,12 +15,11 @@ Mod('sea_main',
#) #)
Mod('tt', Mod('tt',
'frappy_psi.sea.LscDrivable', '', 'frappy_psi.sea.SeaDrivable', '',
io='sea_main',
meaning=['temperature_regulation', 27], meaning=['temperature_regulation', 27],
io='sea_main',
sea_object='tt', sea_object='tt',
sensor_path='tm', rel_paths=['tm', '.', 'set', 'dblctrl'],
set_path='set',
) )
Mod('th', Mod('th',

View File

@ -10,12 +10,11 @@ Mod('sea_main',
) )
Mod('tt', Mod('tt',
'frappy_psi.sea.LscDrivable', '', 'frappy_psi.sea.SeaDrivable', '',
io='sea_main', io='sea_main',
meaning=['temperature_regulation', 27], meaning=['temperature_regulation', 27],
sea_object='tt', sea_object='tt',
sensor_path='tm', rel_paths=['tm', '.', 'set', 'dblctrl'],
set_path='set',
) )
Mod('cc', Mod('cc',

228
cfg/main/mb11std_cfg.py Normal file
View File

@ -0,0 +1,228 @@
Node('mb11.psi.ch',
'MB11 11 Tesla - 100 mm cryomagnet',
)
Mod('itc1',
'frappy_psi.mercury.IO',
'ITC for heat exchanger and pressures',
uri='mb11-ts:3001',
)
Mod('itc2',
'frappy_psi.mercury.IO',
'ITC for neck and nv heaters',
uri='mb11-ts:3002',
)
Mod('ips',
'frappy_psi.mercury.IO',
'IPS for magnet and levels',
uri='mb11-ts:3003',
)
Mod('T_stat',
'frappy_psi.mercury.TemperatureAutoFlow',
'static heat exchanger temperature',
meaning=['temperature_regulation', 27],
output_module='htr_stat',
needle_valve='p_stat',
slot='DB6.T1',
io='itc1',
tolerance=0.1,
flowpars=((1,5), (2, 20)),
)
Mod('htr_stat',
'frappy_psi.mercury.HeaterOutput',
'static heat exchanger heater',
slot='DB1.H1',
io='itc1',
)
Mod('ts',
'frappy_psi.mercury.TemperatureLoop',
'sample temperature',
output_module='htr_sample',
slot='MB1.T1',
io='itc1',
tolerance=1.0,
visibility='expert',
)
Mod('htr_sample',
'frappy_psi.mercury.HeaterOutput',
'sample stick heater power',
slot='MB0.H1',
io='itc1',
)
Mod('p_stat',
'frappy_psi.mercury.PressureLoop',
'static needle valve pressure',
output_module='pos_stat',
settling_time=60.0,
slot='DB5.P1',
io='itc1',
tolerance=1.0,
value=Param(
unit='mbar_flow',
),
)
Mod('pos_stat',
'frappy_psi.mercury.ValvePos',
'static needle valve position',
slot='DB5.P1,DB3.G1',
io='itc1',
)
Mod('T_dyn',
'frappy_psi.mercury.TemperatureAutoFlow',
'dynamic heat exchanger temperature',
output_module='htr_dyn',
needle_valve='p_dyn',
slot='DB7.T1',
io='itc1',
tolerance=0.1,
)
Mod('htr_dyn',
'frappy_psi.mercury.HeaterOutput',
'dynamic heat exchanger heater',
slot='DB2.H1',
io='itc1',
)
Mod('p_dyn',
'frappy_psi.mercury.PressureLoop',
'dynamic needle valve pressure',
output_module='pos_dyn',
settling_time=60.0,
slot='DB8.P1',
io='itc1',
tolerance=1.0,
value=Param(
unit='mbar_flow',
),
)
Mod('pos_dyn',
'frappy_psi.mercury.ValvePos',
'dynamic needle valve position',
slot='DB8.P1,DB4.G1',
io='itc1',
)
Mod('mf',
'frappy_psi.ips_mercury.Field',
'magnetic field',
slot='GRPZ',
io='ips',
tolerance=0.001,
wait_stable_field=60.0,
target=Param(
max=11.0,
),
persistent_limit=11.1,
)
Mod('lev',
'frappy_psi.mercury.HeLevel',
'LHe level',
slot='DB1.L1',
io='ips',
)
Mod('n2lev',
'frappy_psi.mercury.N2Level',
'LN2 level',
slot='DB1.L1',
io='ips',
)
Mod('T_neck1',
'frappy_psi.mercury.TemperatureLoop',
'neck heater 1 temperature',
output_module='htr_neck1',
slot='MB1.T1',
io='itc2',
tolerance=1.0,
)
Mod('htr_neck1',
'frappy_psi.mercury.HeaterOutput',
'neck heater 1 power',
slot='MB0.H1',
io='itc2',
)
Mod('T_neck2',
'frappy_psi.mercury.TemperatureLoop',
'neck heater 2 temperature',
output_module='htr_neck2',
slot='DB6.T1',
io='itc2',
tolerance=1.0,
)
Mod('htr_neck2',
'frappy_psi.mercury.HeaterOutput',
'neck heater 2 power',
slot='DB1.H1',
io='itc2',
)
Mod('T_nvs',
'frappy_psi.mercury.TemperatureLoop',
'static needle valve temperature',
output_module='htr_nvs',
slot='DB7.T1',
io='itc2',
tolerance=0.1,
)
Mod('htr_nvs',
'frappy_psi.mercury.HeaterOutput',
'static needle valve heater power',
slot='DB2.H1',
io='itc2',
)
Mod('T_nvd',
'frappy_psi.mercury.TemperatureLoop',
'dynamic needle valve heater temperature',
output_module='htr_nvd',
slot='DB8.T1',
io='itc2',
tolerance=0.1,
)
Mod('htr_nvd',
'frappy_psi.mercury.HeaterOutput',
'dynamic needle valve heater power',
slot='DB3.H1',
io='itc2',
)
Mod('T_coil',
'frappy_psi.mercury.TemperatureSensor',
'coil temperature',
slot='MB1.T1',
io='ips',
)
Mod('om_io',
'frappy_psi.phytron.PhytronIO',
'dom motor IO',
uri='mb11-ts.psi.ch:3004',
)
Mod('om',
'frappy_psi.phytron.Motor',
'stick rotation, typically used for omega',
io='om_io',
target_min=-360,
target_max=360,
encoder_mode='NO',
target=Param(min=-360, max=360),
)

View File

@ -10,12 +10,11 @@ Mod('sea_main',
) )
Mod('tt', Mod('tt',
'frappy_psi.sea.LscDrivable', '', 'frappy_psi.sea.SeaDrivable', '',
io='sea_main', io='sea_main',
meaning=['temperature_regulation', 27], meaning=['temperature_regulation', 27],
sea_object='tt', sea_object='tt',
sensor_path='tm', rel_paths=['tm', '.', 'set', 'dblctrl'],
set_path='set',
) )
Mod('cc', Mod('cc',

View File

@ -10,12 +10,11 @@ Mod('sea_main',
) )
Mod('tt', Mod('tt',
'frappy_psi.sea.LscDrivable', '', 'frappy_psi.sea.SeaDrivable', '',
io='sea_main', io='sea_main',
meaning=['temperature_regulation', 27], meaning=['temperature_regulation', 27],
sea_object='tt', sea_object='tt',
sensor_path='tm', rel_paths=['tm', '.', 'set', 'dblctrl'],
set_path='set',
) )
Mod('cc', Mod('cc',

View File

@ -10,12 +10,11 @@ Mod('sea_main',
) )
Mod('tt', Mod('tt',
'frappy_psi.sea.LscDrivable', '', 'frappy_psi.sea.SeaDrivable', '',
io='sea_main', io='sea_main',
meaning=['temperature_regulation', 27], meaning=['temperature_regulation', 27],
sea_object='tt', sea_object='tt',
sensor_path='tm', rel_paths=['tm', '.', 'set', 'dblctrl'],
set_path='set',
) )
Mod('cc', Mod('cc',

View File

@ -10,12 +10,11 @@ Mod('sea_main',
) )
Mod('tt', Mod('tt',
'frappy_psi.sea.LscDrivable', '', 'frappy_psi.sea.SeaDrivable', '',
io='sea_main', io='sea_main',
meaning=['temperature_regulation', 27], meaning=['temperature_regulation', 27],
sea_object='tt', sea_object='tt',
sensor_path='tm', rel_paths=['tm', '.', 'set', 'dblctrl'],
set_path='set',
) )
Mod('cc', Mod('cc',

View File

@ -10,12 +10,11 @@ Mod('sea_main',
) )
Mod('tt', Mod('tt',
'frappy_psi.sea.LscDrivable', '', 'frappy_psi.sea.SeaDrivable', '',
io='sea_main', io = 'sea_main',
meaning=['temperature_regulation', 27], meaning=['temperature_regulation', 27],
sea_object='tt', sea_object = 'tt',
sensor_path='tm', rel_paths=['tm', '.', 'set', 'dblctrl'],
set_path='set',
) )
Mod('cc', Mod('cc',

View File

@ -7,11 +7,11 @@ Node('ori7test.psi.ch',
rack = Rack(Mod) rack = Rack(Mod)
rack.lakeshore() with rack.lakeshore() as ls:
rack.sensor('Ts', channel='C', calcurve='x186350') ls.sensor('Ts', channel='C', calcurve='x186350')
rack.loop('T', channel='B', calcurve='x174786', output_module='htr', target=10) ls.loop('T', channel='B', calcurve='x174786')
rack.heater('htr', output_no=1, max_heater='100W', resistance=25) ls.heater('htr', '100W', 100)
rack.he() rack.ccu(he=True, n2=True)
rack.n2()
rack.flow(min_open_pulse=0.03) rack.hepump()

View File

@ -10,12 +10,11 @@ Mod('sea_main',
) )
Mod('tt', Mod('tt',
'frappy_psi.sea.LscDrivable', '', 'frappy_psi.sea.SeaDrivable', '',
io='sea_main', io='sea_main',
meaning=['temperature_regulation', 27], meaning=['temperature_regulation', 27],
sea_object='tt', sea_object='tt',
sensor_path='tm', rel_paths=['tm', '.', 'set', 'dblctrl'],
set_path='set',
) )
Mod('cc', Mod('cc',

View File

@ -170,20 +170,18 @@ Mod('htr_nvd',
# Motor controller is not yet available! # Motor controller is not yet available!
# #
''' #Mod('om_io',
Mod('om_io', # 'frappy_psi.phytron.PhytronIO',
'frappy_psi.phytron.PhytronIO', # 'dom motor IO',
'dom motor IO', # uri='mb11-ts.psi.ch:3004',
uri='mb11-ts.psi.ch:3004', #)
)
Mod('om', #Mod('om',
'frappy_psi.phytron.Motor', # 'frappy_psi.phytron.Motor',
'stick rotation, typically used for omega', # 'stick rotation, typically used for omega',
io='om_io', # io='om_io',
target_min=-180, # target_min=-180,
target_max=360, # target_max=360,
encoder_mode='NO', # encoder_mode='NO',
target=Param(min=-180, max=360) # target=Param(min=-180, max=360)
) #)
'''

View File

@ -292,7 +292,7 @@
{"path": "V3A", "type": "int", "readonly": false, "cmd": "dil V3A", "visibility": 3}, {"path": "V3A", "type": "int", "readonly": false, "cmd": "dil V3A", "visibility": 3},
{"path": "Roots", "type": "int", "readonly": false, "cmd": "dil Roots", "visibility": 3}, {"path": "Roots", "type": "int", "readonly": false, "cmd": "dil Roots", "visibility": 3},
{"path": "Aux", "type": "int", "readonly": false, "cmd": "dil Aux", "visibility": 3}, {"path": "Aux", "type": "int", "readonly": false, "cmd": "dil Aux", "visibility": 3},
{"path": "He3", "type": "int", "readonly": false, "cmd": "dil He3"}, {"path": "He3", "type": "int", "readonly": false, "cmd": "dil He3", "visibility": 3},
{"path": "closedelay", "type": "float", "readonly": false, "cmd": "dil closedelay", "visibility": 3}, {"path": "closedelay", "type": "float", "readonly": false, "cmd": "dil closedelay", "visibility": 3},
{"path": "extVersion", "type": "int", "readonly": false, "cmd": "dil extVersion", "visibility": 3}, {"path": "extVersion", "type": "int", "readonly": false, "cmd": "dil extVersion", "visibility": 3},
{"path": "pumpoff", "type": "int"}, {"path": "pumpoff", "type": "int"},

View File

@ -292,7 +292,7 @@
{"path": "V3A", "type": "int", "readonly": false, "cmd": "dil V3A", "visibility": 3}, {"path": "V3A", "type": "int", "readonly": false, "cmd": "dil V3A", "visibility": 3},
{"path": "Roots", "type": "int", "readonly": false, "cmd": "dil Roots", "visibility": 3}, {"path": "Roots", "type": "int", "readonly": false, "cmd": "dil Roots", "visibility": 3},
{"path": "Aux", "type": "int", "readonly": false, "cmd": "dil Aux", "visibility": 3}, {"path": "Aux", "type": "int", "readonly": false, "cmd": "dil Aux", "visibility": 3},
{"path": "He3", "type": "int", "readonly": false, "cmd": "dil He3"}, {"path": "He3", "type": "int", "readonly": false, "cmd": "dil He3", "visibility": 3},
{"path": "closedelay", "type": "float", "readonly": false, "cmd": "dil closedelay", "visibility": 3}, {"path": "closedelay", "type": "float", "readonly": false, "cmd": "dil closedelay", "visibility": 3},
{"path": "extVersion", "type": "int", "readonly": false, "cmd": "dil extVersion", "visibility": 3}, {"path": "extVersion", "type": "int", "readonly": false, "cmd": "dil extVersion", "visibility": 3},
{"path": "pumpoff", "type": "int"}, {"path": "pumpoff", "type": "int"},

View File

@ -292,7 +292,7 @@
{"path": "V3A", "type": "int", "readonly": false, "cmd": "dil V3A", "visibility": 3}, {"path": "V3A", "type": "int", "readonly": false, "cmd": "dil V3A", "visibility": 3},
{"path": "Roots", "type": "int", "readonly": false, "cmd": "dil Roots", "visibility": 3}, {"path": "Roots", "type": "int", "readonly": false, "cmd": "dil Roots", "visibility": 3},
{"path": "Aux", "type": "int", "readonly": false, "cmd": "dil Aux", "visibility": 3}, {"path": "Aux", "type": "int", "readonly": false, "cmd": "dil Aux", "visibility": 3},
{"path": "He3", "type": "int", "readonly": false, "cmd": "dil He3"}, {"path": "He3", "type": "int", "readonly": false, "cmd": "dil He3", "visibility": 3},
{"path": "closedelay", "type": "float", "readonly": false, "cmd": "dil closedelay", "visibility": 3}, {"path": "closedelay", "type": "float", "readonly": false, "cmd": "dil closedelay", "visibility": 3},
{"path": "extVersion", "type": "int", "readonly": false, "cmd": "dil extVersion", "visibility": 3}, {"path": "extVersion", "type": "int", "readonly": false, "cmd": "dil extVersion", "visibility": 3},
{"path": "pumpoff", "type": "int"}, {"path": "pumpoff", "type": "int"},

View File

@ -3,7 +3,7 @@
{"path": "unit", "type": "text", "readonly": false, "cmd": "th unit", "visibility": 3}, {"path": "unit", "type": "text", "readonly": false, "cmd": "th unit", "visibility": 3},
{"path": "t2", "type": "float"}, {"path": "t2", "type": "float"},
{"path": "set", "type": "float"}, {"path": "set", "type": "float"},
{"path": "pumprunning", "type": "int", "readonly": false, "cmd": "th pumprunning"}, {"path": "running", "type": "int", "readonly": false, "cmd": "th running", "visibility": 3},
{"path": "extcontrol", "type": "int", "readonly": false, "cmd": "th extcontrol", "visibility": 3}, {"path": "extcontrol", "type": "int", "readonly": false, "cmd": "th extcontrol", "visibility": 3},
{"path": "relais", "type": "int", "visibility": 3}, {"path": "relais", "type": "int", "visibility": 3},
{"path": "overtemp", "type": "int", "visibility": 3}, {"path": "overtemp", "type": "int", "visibility": 3},

View File

@ -1,160 +0,0 @@
{"th": {"base": "/th", "params": [
{"path": "", "type": "float", "readonly": false, "cmd": "run th", "kids": 26},
{"path": "unit", "type": "text", "readonly": false, "cmd": "th unit", "visibility": 3},
{"path": "t2", "type": "float"},
{"path": "set", "type": "float"},
{"path": "pumprunning", "type": "int", "readonly": false, "cmd": "th pumprunning"},
{"path": "extcontrol", "type": "int", "readonly": false, "cmd": "th extcontrol", "visibility": 3},
{"path": "relais", "type": "int", "visibility": 3},
{"path": "overtemp", "type": "int", "visibility": 3},
{"path": "lowlevel", "type": "int", "visibility": 3},
{"path": "pumpalarm", "type": "int", "visibility": 3},
{"path": "externalarm", "type": "int", "visibility": 3},
{"path": "coolalarm", "type": "int", "visibility": 3},
{"path": "sensor1alarm", "type": "int", "visibility": 3},
{"path": "sensor2alarm", "type": "int", "visibility": 3},
{"path": "reset", "type": "int", "readonly": false, "cmd": "th reset", "visibility": 3},
{"path": "with2sensors", "type": "int", "readonly": false, "cmd": "th with2sensors", "visibility": 3},
{"path": "upperLimit", "type": "float", "readonly": false, "cmd": "th upperLimit"},
{"path": "lowerLimit", "type": "float", "readonly": false, "cmd": "th lowerLimit"},
{"path": "tolerance", "type": "float", "readonly": false, "cmd": "th tolerance"},
{"path": "maxwait", "type": "int", "readonly": false, "cmd": "th maxwait"},
{"path": "settle", "type": "int", "readonly": false, "cmd": "th settle"},
{"path": "targetValue", "type": "float"},
{"path": "is_running", "type": "int", "visibility": 3},
{"path": "verbose", "type": "int", "readonly": false, "cmd": "th verbose", "visibility": 3},
{"path": "driver", "type": "text", "visibility": 3},
{"path": "creationCmd", "type": "text", "visibility": 3},
{"path": "status", "type": "text", "readonly": false, "cmd": "th status"}]},
"te": {"base": "/te", "params": [
{"path": "", "type": "float", "readonly": false, "cmd": "run te", "kids": 30},
{"path": "unit", "type": "text", "readonly": false, "cmd": "te unit", "visibility": 3},
{"path": "mode", "type": "int", "readonly": false, "cmd": "te mode"},
{"path": "model", "type": "text", "visibility": 3},
{"path": "pbPow", "type": "float", "visibility": 3},
{"path": "pbMin", "type": "float", "visibility": 3},
{"path": "pbScl", "type": "float", "visibility": 3},
{"path": "output", "type": "float"},
{"path": "position", "type": "float", "readonly": false, "cmd": "te position"},
{"path": "asymmetry", "type": "float", "readonly": false, "cmd": "te asymmetry", "visibility": 3},
{"path": "range", "type": "float", "readonly": false, "cmd": "te range", "visibility": 3},
{"path": "set", "type": "float", "readonly": false, "cmd": "te set"},
{"path": "rdonly", "type": "int", "readonly": false, "cmd": "te rdonly", "visibility": 3},
{"path": "task", "type": "text", "readonly": false, "cmd": "te task"},
{"path": "upperLimit", "type": "float", "readonly": false, "cmd": "te upperLimit"},
{"path": "lowerLimit", "type": "float", "readonly": false, "cmd": "te lowerLimit", "visibility": 3},
{"path": "tolerance", "type": "float", "readonly": false, "cmd": "te tolerance"},
{"path": "maxwait", "type": "int", "readonly": false, "cmd": "te maxwait"},
{"path": "settle", "type": "int", "readonly": false, "cmd": "te settle"},
{"path": "targetValue", "type": "float"},
{"path": "is_running", "type": "int", "visibility": 3},
{"path": "verbose", "type": "int", "readonly": false, "cmd": "te verbose", "visibility": 3},
{"path": "driver", "type": "text", "visibility": 3},
{"path": "creationCmd", "type": "text", "visibility": 3},
{"path": "status", "type": "text", "readonly": false, "cmd": "te status"},
{"path": "pb", "type": "float", "readonly": false, "cmd": "te pb"},
{"path": "ti", "type": "float", "readonly": false, "cmd": "te ti"},
{"path": "td", "type": "float", "readonly": false, "cmd": "te td"},
{"path": "manual", "type": "float", "readonly": false, "cmd": "te manual"},
{"path": "rate", "type": "float", "readonly": false, "cmd": "te rate"},
{"path": "workset", "type": "float", "readonly": false, "cmd": "te workset"}]},
"cc": {"base": "/cc", "params": [
{"path": "", "type": "bool", "kids": 96},
{"path": "send", "type": "text", "readonly": false, "cmd": "cc send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3},
{"path": "autodevice", "type": "bool", "readonly": false, "cmd": "cc autodevice"},
{"path": "fav", "type": "bool", "readonly": false, "cmd": "cc fav"},
{"path": "f", "type": "float", "visibility": 3},
{"path": "fs", "type": "enum", "enum": {"ok": 0, "no_sens": 1}, "readonly": false, "cmd": "cc fs", "visibility": 3},
{"path": "mav", "type": "bool", "readonly": false, "cmd": "cc mav"},
{"path": "fm", "type": "enum", "enum": {"idle": 0, "opening": 1, "closing": 2, "opened": 3, "closed": 4, "no_motor": 5}, "visibility": 3},
{"path": "fa", "type": "enum", "enum": {"fixed": 0, "controlled": 1, "automatic": 2, "offline": 3}, "readonly": false, "cmd": "cc fa", "visibility": 3},
{"path": "mp", "type": "float", "readonly": false, "cmd": "cc mp", "visibility": 3},
{"path": "msp", "type": "float", "visibility": 3},
{"path": "mmp", "type": "float", "visibility": 3},
{"path": "mc", "type": "float", "readonly": false, "cmd": "cc mc", "visibility": 3},
{"path": "mfc", "type": "float", "readonly": false, "cmd": "cc mfc", "visibility": 3},
{"path": "moc", "type": "float", "readonly": false, "cmd": "cc moc", "visibility": 3},
{"path": "mtc", "type": "float", "readonly": false, "cmd": "cc mtc", "visibility": 3},
{"path": "mtl", "type": "float", "visibility": 3},
{"path": "mft", "type": "float", "readonly": false, "cmd": "cc mft", "visibility": 3},
{"path": "mt", "type": "float", "visibility": 3},
{"path": "mo", "type": "float", "visibility": 3},
{"path": "mcr", "type": "float", "visibility": 3},
{"path": "mot", "type": "float", "visibility": 3},
{"path": "mw", "type": "float", "readonly": false, "cmd": "cc mw", "description": "correction pulse after automatic open", "visibility": 3},
{"path": "hav", "type": "enum", "type": "enum", "enum": {"none": 0, "int": 1, "ext": 2}, "readonly": false, "cmd": "cc hav"},
{"path": "h", "type": "float", "visibility": 3},
{"path": "hr", "type": "float", "visibility": 3},
{"path": "hc", "type": "float", "visibility": 3},
{"path": "hu", "type": "float", "visibility": 3},
{"path": "hh", "type": "float", "readonly": false, "cmd": "cc hh", "visibility": 3},
{"path": "hl", "type": "float", "readonly": false, "cmd": "cc hl", "visibility": 3},
{"path": "htf", "type": "float", "readonly": false, "cmd": "cc htf", "description": "meas. period in fast mode", "visibility": 3},
{"path": "hts", "type": "float", "readonly": false, "cmd": "cc hts", "description": "meas. period in slow mode", "visibility": 3},
{"path": "hd", "type": "float", "readonly": false, "cmd": "cc hd", "visibility": 3},
{"path": "hwr", "type": "float", "readonly": false, "cmd": "cc hwr", "visibility": 3},
{"path": "hem", "type": "float", "readonly": false, "cmd": "cc hem", "description": "sensor length in mm from top to empty pos.", "visibility": 3},
{"path": "hfu", "type": "float", "readonly": false, "cmd": "cc hfu", "description": "sensor length in mm from top to full pos.", "visibility": 3},
{"path": "hcd", "type": "enum", "enum": {"stop": 0, "fill": 1, "off": 2, "auto": 3, "manual": 7}, "readonly": false, "cmd": "cc hcd", "visibility": 3},
{"path": "hv", "type": "enum", "enum": {"fill_valve_off": 0, "filling": 1, "no_fill_valve": 2, "timeout": 3, "timeout1": 4}, "visibility": 3},
{"path": "hsf", "type": "enum", "enum": {"sens_ok": 0, "sens_warm": 1, "no_sens": 2, "timeout": 3, "not_yet_read": 4, "disabled": 5}, "visibility": 3},
{"path": "ha", "type": "bool", "readonly": false, "cmd": "cc ha", "visibility": 3},
{"path": "hm", "type": "bool", "visibility": 3},
{"path": "hf", "type": "enum", "enum": {"slow": 0, "fast": 1}, "readonly": false, "cmd": "cc hf", "visibility": 3},
{"path": "hbe", "type": "bool", "readonly": false, "cmd": "cc hbe", "visibility": 3},
{"path": "hmf", "type": "float", "visibility": 3},
{"path": "hms", "type": "float", "visibility": 3},
{"path": "hit", "type": "float", "readonly": false, "cmd": "cc hit", "visibility": 3},
{"path": "hft", "type": "int", "readonly": false, "cmd": "cc hft", "visibility": 3},
{"path": "hea", "type": "enum", "enum": {"0": 0, "1": 1, "6": 6}, "readonly": false, "cmd": "cc hea"},
{"path": "hch", "type": "int", "readonly": false, "cmd": "cc hch", "visibility": 3},
{"path": "hwr0", "type": "float", "readonly": false, "cmd": "cc hwr0", "visibility": 3},
{"path": "hem0", "type": "float", "readonly": false, "cmd": "cc hem0", "description": "sensor length in mm from top to empty pos.", "visibility": 3},
{"path": "hfu0", "type": "float", "readonly": false, "cmd": "cc hfu0", "description": "sensor length in mm from top to full pos.", "visibility": 3},
{"path": "hd0", "type": "float", "readonly": false, "cmd": "cc hd0", "description": "external sensor drive current (mA)", "visibility": 3},
{"path": "h0", "type": "float", "visibility": 3},
{"path": "hs0", "type": "enum", "enum": {"sens_ok": 0, "sens_warm": 1, "no_sens": 2, "timeout": 3, "not_yet_read": 4, "disabled": 5}, "visibility": 3},
{"path": "h1", "type": "float", "visibility": 3},
{"path": "hs1", "type": "enum", "enum": {"sens_ok": 0, "sens_warm": 1, "no_sens": 2, "timeout": 3, "not_yet_read": 4, "disabled": 5}, "visibility": 3},
{"path": "h2", "type": "float", "visibility": 3},
{"path": "hs2", "type": "enum", "enum": {"sens_ok": 0, "sens_warm": 1, "no_sens": 2, "timeout": 3, "not_yet_read": 4, "disabled": 5}, "visibility": 3},
{"path": "h3", "type": "float", "visibility": 3},
{"path": "hs3", "type": "enum", "enum": {"sens_ok": 0, "sens_warm": 1, "no_sens": 2, "timeout": 3, "not_yet_read": 4, "disabled": 5}, "visibility": 3},
{"path": "h4", "type": "float", "visibility": 3},
{"path": "hs4", "type": "enum", "enum": {"sens_ok": 0, "sens_warm": 1, "no_sens": 2, "timeout": 3, "not_yet_read": 4, "disabled": 5}, "visibility": 3},
{"path": "h5", "type": "float", "visibility": 3},
{"path": "hs5", "type": "enum", "enum": {"sens_ok": 0, "sens_warm": 1, "no_sens": 2, "timeout": 3, "not_yet_read": 4, "disabled": 5}, "visibility": 3},
{"path": "hfb", "type": "float", "visibility": 3},
{"path": "nav", "type": "enum", "type": "enum", "enum": {"none": 0, "int": 1, "ext": 2}, "readonly": false, "cmd": "cc nav"},
{"path": "nu", "type": "float", "visibility": 3},
{"path": "nl", "type": "float", "visibility": 3},
{"path": "nth", "type": "float", "readonly": false, "cmd": "cc nth", "visibility": 3},
{"path": "ntc", "type": "float", "readonly": false, "cmd": "cc ntc", "visibility": 3},
{"path": "ntm", "type": "float", "readonly": false, "cmd": "cc ntm", "visibility": 3},
{"path": "ns", "type": "enum", "enum": {"sens_ok": 0, "no_sens": 1, "short_circuit": 2, "upside_down": 3, "sens_warm": 4, "empty": 5}, "visibility": 3},
{"path": "na", "type": "bool", "readonly": false, "cmd": "cc na", "visibility": 3},
{"path": "nv", "type": "enum", "enum": {"fill_valve_off": 0, "filling": 1, "no_fill_valve": 2, "timeout": 3, "timeout1": 4, "boost": 5}, "visibility": 3},
{"path": "nc", "type": "enum", "enum": {"stop": 0, "fill": 1, "off": 2, "auto": 3}, "readonly": false, "cmd": "cc nc", "visibility": 3},
{"path": "nfb", "type": "float", "visibility": 3},
{"path": "cda", "type": "float"},
{"path": "cdb", "type": "float"},
{"path": "cba", "type": "float"},
{"path": "cbb", "type": "float"},
{"path": "cvs", "type": "int"},
{"path": "csp", "type": "int"},
{"path": "cdv", "type": "text", "readonly": false, "cmd": "cc cdv"},
{"path": "cic", "type": "text", "readonly": false, "cmd": "cc cic"},
{"path": "cin", "type": "text"},
{"path": "cds", "type": "enum", "enum": {"local": 0, "remote": 1, "loading": 2, "by_code": 3, "by_touch": 4}, "readonly": false, "cmd": "cc cds"},
{"path": "timing", "type": "bool", "readonly": false, "cmd": "cc timing"},
{"path": "tc", "type": "float", "visibility": 3},
{"path": "tn", "type": "float", "visibility": 3},
{"path": "th", "type": "float", "visibility": 3},
{"path": "tf", "type": "float", "visibility": 3},
{"path": "tm", "type": "float", "visibility": 3},
{"path": "tv", "type": "float", "visibility": 3},
{"path": "tq", "type": "float", "visibility": 3},
{"path": "bdl", "type": "float", "readonly": false, "cmd": "cc bdl"}]}}

View File

@ -1,50 +1,50 @@
{"tt": {"base": "/tt", "params": [ {"tt": {"base": "/tt", "params": [
{"path": "", "type": "float", "readonly": false, "cmd": "run tt", "description": "tt", "kids": 18}, {"path": "", "type": "float", "readonly": false, "cmd": "run tt", "description": "tt", "kids": 18},
{"path": "send", "type": "text", "readonly": false, "cmd": "tt send", "visibility": 3}, {"path": "send", "type": "text", "readonly": false, "cmd": "tt send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3}, {"path": "status", "type": "text", "readonly": false, "cmd": "run tt", "visibility": 3},
{"path": "is_running", "type": "int", "readonly": false, "cmd": "tt is_running", "visibility": 3}, {"path": "is_running", "type": "int", "readonly": false, "cmd": "tt is_running", "visibility": 3},
{"path": "mainloop", "type": "text", "readonly": false, "cmd": "tt mainloop", "visibility": 3}, {"path": "mainloop", "type": "text", "readonly": false, "cmd": "tt mainloop", "visibility": 3},
{"path": "target", "type": "float"}, {"path": "target", "type": "float", "readonly": false, "cmd": "run tt"},
{"path": "running", "type": "int"}, {"path": "running", "type": "int", "readonly": false, "cmd": "run tt"},
{"path": "tolerance", "type": "float", "readonly": false, "cmd": "tt tolerance"}, {"path": "tolerance", "type": "float", "readonly": false, "cmd": "tt tolerance"},
{"path": "maxwait", "type": "float", "readonly": false, "cmd": "tt maxwait"}, {"path": "maxwait", "type": "float", "readonly": false, "cmd": "tt maxwait"},
{"path": "settle", "type": "float", "readonly": false, "cmd": "tt settle"}, {"path": "settle", "type": "float", "readonly": false, "cmd": "tt settle"},
{"path": "log", "type": "text", "readonly": false, "cmd": "tt log", "visibility": 3, "kids": 4}, {"path": "log", "type": "text", "readonly": false, "cmd": "tt log", "visibility": 3, "kids": 4},
{"path": "log/mean", "type": "float", "visibility": 3}, {"path": "log/mean", "type": "float", "readonly": false, "cmd": "run tt", "visibility": 3},
{"path": "log/m2", "type": "float", "visibility": 3}, {"path": "log/m2", "type": "float", "readonly": false, "cmd": "run tt", "visibility": 3},
{"path": "log/stddev", "type": "float", "visibility": 3}, {"path": "log/stddev", "type": "float", "readonly": false, "cmd": "run tt", "visibility": 3},
{"path": "log/n", "type": "float", "visibility": 3}, {"path": "log/n", "type": "float", "readonly": false, "cmd": "run tt", "visibility": 3},
{"path": "dblctrl", "type": "bool", "readonly": false, "cmd": "tt dblctrl", "kids": 9}, {"path": "dblctrl", "type": "bool", "readonly": false, "cmd": "tt dblctrl", "kids": 9},
{"path": "dblctrl/tshift", "type": "float", "readonly": false, "cmd": "tt dblctrl/tshift"}, {"path": "dblctrl/tshift", "type": "float", "readonly": false, "cmd": "tt dblctrl/tshift"},
{"path": "dblctrl/mode", "type": "enum", "enum": {"disabled": -1, "inactive": 0, "stable": 1, "up": 2, "down": 3}, "readonly": false, "cmd": "tt dblctrl/mode"}, {"path": "dblctrl/mode", "type": "enum", "enum": {"disabled": -1, "inactive": 0, "stable": 1, "up": 2, "down": 3}, "readonly": false, "cmd": "tt dblctrl/mode"},
{"path": "dblctrl/shift_up", "type": "float"}, {"path": "dblctrl/shift_up", "type": "float", "readonly": false, "cmd": "run tt"},
{"path": "dblctrl/shift_lo", "type": "float"}, {"path": "dblctrl/shift_lo", "type": "float", "readonly": false, "cmd": "run tt"},
{"path": "dblctrl/t_min", "type": "float"}, {"path": "dblctrl/t_min", "type": "float", "readonly": false, "cmd": "run tt"},
{"path": "dblctrl/t_max", "type": "float"}, {"path": "dblctrl/t_max", "type": "float", "readonly": false, "cmd": "run tt"},
{"path": "dblctrl/int2", "type": "float", "readonly": false, "cmd": "tt dblctrl/int2"}, {"path": "dblctrl/int2", "type": "float", "readonly": false, "cmd": "tt dblctrl/int2"},
{"path": "dblctrl/prop_up", "type": "float", "readonly": false, "cmd": "tt dblctrl/prop_up"}, {"path": "dblctrl/prop_up", "type": "float", "readonly": false, "cmd": "tt dblctrl/prop_up"},
{"path": "dblctrl/prop_lo", "type": "float", "readonly": false, "cmd": "tt dblctrl/prop_lo"}, {"path": "dblctrl/prop_lo", "type": "float", "readonly": false, "cmd": "tt dblctrl/prop_lo"},
{"path": "tm", "type": "float", "kids": 4}, {"path": "tm", "type": "float", "readonly": false, "cmd": "run tt", "kids": 4},
{"path": "tm/curve", "type": "text", "readonly": false, "cmd": "tt tm/curve", "kids": 1}, {"path": "tm/curve", "type": "text", "readonly": false, "cmd": "tt tm/curve", "kids": 1},
{"path": "tm/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tt tm/curve/points", "visibility": 3}, {"path": "tm/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tt tm/curve/points", "visibility": 3},
{"path": "tm/alarm", "type": "float", "readonly": false, "cmd": "tt tm/alarm"}, {"path": "tm/alarm", "type": "float", "readonly": false, "cmd": "tt tm/alarm"},
{"path": "tm/stddev", "type": "float"}, {"path": "tm/stddev", "type": "float", "readonly": false, "cmd": "run tt"},
{"path": "tm/raw", "type": "float"}, {"path": "tm/raw", "type": "float", "readonly": false, "cmd": "run tt"},
{"path": "ts", "type": "float", "kids": 4}, {"path": "ts", "type": "float", "readonly": false, "cmd": "run tt", "kids": 4},
{"path": "ts/curve", "type": "text", "readonly": false, "cmd": "tt ts/curve", "kids": 1}, {"path": "ts/curve", "type": "text", "readonly": false, "cmd": "tt ts/curve", "kids": 1},
{"path": "ts/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tt ts/curve/points", "visibility": 3}, {"path": "ts/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tt ts/curve/points", "visibility": 3},
{"path": "ts/alarm", "type": "float", "readonly": false, "cmd": "tt ts/alarm"}, {"path": "ts/alarm", "type": "float", "readonly": false, "cmd": "tt ts/alarm"},
{"path": "ts/stddev", "type": "float"}, {"path": "ts/stddev", "type": "float", "readonly": false, "cmd": "run tt"},
{"path": "ts/raw", "type": "float"}, {"path": "ts/raw", "type": "float", "readonly": false, "cmd": "run tt"},
{"path": "ts_2", "type": "float", "visibility": 3, "kids": 4}, {"path": "ts_2", "type": "float", "readonly": false, "cmd": "run tt", "kids": 4},
{"path": "ts_2/curve", "type": "text", "readonly": false, "cmd": "tt ts_2/curve", "visibility": 3, "kids": 1}, {"path": "ts_2/curve", "type": "text", "readonly": false, "cmd": "tt ts_2/curve", "kids": 1},
{"path": "ts_2/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tt ts_2/curve/points", "visibility": 3}, {"path": "ts_2/curve/points", "type": "floatvarar", "readonly": false, "cmd": "tt ts_2/curve/points", "visibility": 3},
{"path": "ts_2/alarm", "type": "float", "readonly": false, "cmd": "tt ts_2/alarm", "visibility": 3}, {"path": "ts_2/alarm", "type": "float", "readonly": false, "cmd": "tt ts_2/alarm"},
{"path": "ts_2/stddev", "type": "float", "visibility": 3}, {"path": "ts_2/stddev", "type": "float", "readonly": false, "cmd": "run tt"},
{"path": "ts_2/raw", "type": "float", "visibility": 3}, {"path": "ts_2/raw", "type": "float", "readonly": false, "cmd": "run tt"},
{"path": "set", "type": "float", "readonly": false, "cmd": "tt set", "kids": 18}, {"path": "set", "type": "float", "readonly": false, "cmd": "tt set", "kids": 18},
{"path": "set/mode", "type": "enum", "enum": {"disabled": -1, "off": 0, "controlling": 1, "manual": 2}, "readonly": false, "cmd": "tt set/mode"}, {"path": "set/mode", "type": "enum", "enum": {"disabled": -1, "off": 0, "controlling": 1, "manual": 2}, "readonly": false, "cmd": "tt set/mode"},
{"path": "set/reg", "type": "float"}, {"path": "set/reg", "type": "float", "readonly": false, "cmd": "run tt"},
{"path": "set/ramp", "type": "float", "readonly": false, "cmd": "tt set/ramp", "description": "maximum ramp in K/min (0: ramp off)"}, {"path": "set/ramp", "type": "float", "readonly": false, "cmd": "tt set/ramp", "description": "maximum ramp in K/min (0: ramp off)"},
{"path": "set/wramp", "type": "float", "readonly": false, "cmd": "tt set/wramp"}, {"path": "set/wramp", "type": "float", "readonly": false, "cmd": "tt set/wramp"},
{"path": "set/smooth", "type": "float", "readonly": false, "cmd": "tt set/smooth", "description": "smooth time (minutes)"}, {"path": "set/smooth", "type": "float", "readonly": false, "cmd": "tt set/smooth", "description": "smooth time (minutes)"},
@ -53,17 +53,17 @@
{"path": "set/resist", "type": "float", "readonly": false, "cmd": "tt set/resist"}, {"path": "set/resist", "type": "float", "readonly": false, "cmd": "tt set/resist"},
{"path": "set/maxheater", "type": "text", "readonly": false, "cmd": "tt set/maxheater", "description": "maximum heater limit, units should be given without space: W, mW, A, mA"}, {"path": "set/maxheater", "type": "text", "readonly": false, "cmd": "tt set/maxheater", "description": "maximum heater limit, units should be given without space: W, mW, A, mA"},
{"path": "set/linearpower", "type": "float", "readonly": false, "cmd": "tt set/linearpower", "description": "when not 0, it is the maximum effective power, and the power is linear to the heater output"}, {"path": "set/linearpower", "type": "float", "readonly": false, "cmd": "tt set/linearpower", "description": "when not 0, it is the maximum effective power, and the power is linear to the heater output"},
{"path": "set/maxpowerlim", "type": "float", "description": "the maximum power limit (before any booster or converter)"}, {"path": "set/maxpowerlim", "type": "float", "readonly": false, "cmd": "run tt", "description": "the maximum power limit (before any booster or converter)"},
{"path": "set/maxpower", "type": "float", "readonly": false, "cmd": "tt set/maxpower", "description": "maximum power [W]"}, {"path": "set/maxpower", "type": "float", "readonly": false, "cmd": "tt set/maxpower", "description": "maximum power [W]"},
{"path": "set/maxcurrent", "type": "float", "description": "the maximum current before any booster or converter"}, {"path": "set/maxcurrent", "type": "float", "readonly": false, "cmd": "run tt", "description": "the maximum current before any booster or converter"},
{"path": "set/manualpower", "type": "float", "readonly": false, "cmd": "tt set/manualpower"}, {"path": "set/manualpower", "type": "float", "readonly": false, "cmd": "tt set/manualpower"},
{"path": "set/power", "type": "float"}, {"path": "set/power", "type": "float", "readonly": false, "cmd": "run tt"},
{"path": "set/prop", "type": "float", "readonly": false, "cmd": "tt set/prop", "description": "bigger means more gain"}, {"path": "set/prop", "type": "float", "readonly": false, "cmd": "tt set/prop", "description": "bigger means more gain"},
{"path": "set/integ", "type": "float", "readonly": false, "cmd": "tt set/integ", "description": "bigger means faster"}, {"path": "set/integ", "type": "float", "readonly": false, "cmd": "tt set/integ", "description": "bigger means faster"},
{"path": "set/deriv", "type": "float", "readonly": false, "cmd": "tt set/deriv"}, {"path": "set/deriv", "type": "float", "readonly": false, "cmd": "tt set/deriv"},
{"path": "setsamp", "type": "float", "readonly": false, "cmd": "tt setsamp", "kids": 18}, {"path": "setsamp", "type": "float", "readonly": false, "cmd": "tt setsamp", "kids": 18},
{"path": "setsamp/mode", "type": "enum", "enum": {"disabled": -1, "off": 0, "controlling": 1, "manual": 2}, "readonly": false, "cmd": "tt setsamp/mode"}, {"path": "setsamp/mode", "type": "enum", "enum": {"disabled": -1, "off": 0, "controlling": 1, "manual": 2}, "readonly": false, "cmd": "tt setsamp/mode"},
{"path": "setsamp/reg", "type": "float"}, {"path": "setsamp/reg", "type": "float", "readonly": false, "cmd": "run tt"},
{"path": "setsamp/ramp", "type": "float", "readonly": false, "cmd": "tt setsamp/ramp", "description": "maximum ramp in K/min (0: ramp off)"}, {"path": "setsamp/ramp", "type": "float", "readonly": false, "cmd": "tt setsamp/ramp", "description": "maximum ramp in K/min (0: ramp off)"},
{"path": "setsamp/wramp", "type": "float", "readonly": false, "cmd": "tt setsamp/wramp"}, {"path": "setsamp/wramp", "type": "float", "readonly": false, "cmd": "tt setsamp/wramp"},
{"path": "setsamp/smooth", "type": "float", "readonly": false, "cmd": "tt setsamp/smooth", "description": "smooth time (minutes)"}, {"path": "setsamp/smooth", "type": "float", "readonly": false, "cmd": "tt setsamp/smooth", "description": "smooth time (minutes)"},
@ -72,16 +72,16 @@
{"path": "setsamp/resist", "type": "float", "readonly": false, "cmd": "tt setsamp/resist"}, {"path": "setsamp/resist", "type": "float", "readonly": false, "cmd": "tt setsamp/resist"},
{"path": "setsamp/maxheater", "type": "text", "readonly": false, "cmd": "tt setsamp/maxheater", "description": "maximum heater limit, units should be given without space: W, mW, A, mA"}, {"path": "setsamp/maxheater", "type": "text", "readonly": false, "cmd": "tt setsamp/maxheater", "description": "maximum heater limit, units should be given without space: W, mW, A, mA"},
{"path": "setsamp/linearpower", "type": "float", "readonly": false, "cmd": "tt setsamp/linearpower", "description": "when not 0, it is the maximum effective power, and the power is linear to the heater output"}, {"path": "setsamp/linearpower", "type": "float", "readonly": false, "cmd": "tt setsamp/linearpower", "description": "when not 0, it is the maximum effective power, and the power is linear to the heater output"},
{"path": "setsamp/maxpowerlim", "type": "float", "description": "the maximum power limit (before any booster or converter)"}, {"path": "setsamp/maxpowerlim", "type": "float", "readonly": false, "cmd": "run tt", "description": "the maximum power limit (before any booster or converter)"},
{"path": "setsamp/maxpower", "type": "float", "readonly": false, "cmd": "tt setsamp/maxpower", "description": "maximum power [W]"}, {"path": "setsamp/maxpower", "type": "float", "readonly": false, "cmd": "tt setsamp/maxpower", "description": "maximum power [W]"},
{"path": "setsamp/maxcurrent", "type": "float", "description": "the maximum current before any booster or converter"}, {"path": "setsamp/maxcurrent", "type": "float", "readonly": false, "cmd": "run tt", "description": "the maximum current before any booster or converter"},
{"path": "setsamp/manualpower", "type": "float", "readonly": false, "cmd": "tt setsamp/manualpower"}, {"path": "setsamp/manualpower", "type": "float", "readonly": false, "cmd": "tt setsamp/manualpower"},
{"path": "setsamp/power", "type": "float"}, {"path": "setsamp/power", "type": "float", "readonly": false, "cmd": "run tt"},
{"path": "setsamp/prop", "type": "float", "readonly": false, "cmd": "tt setsamp/prop", "description": "bigger means more gain"}, {"path": "setsamp/prop", "type": "float", "readonly": false, "cmd": "tt setsamp/prop", "description": "bigger means more gain"},
{"path": "setsamp/integ", "type": "float", "readonly": false, "cmd": "tt setsamp/integ", "description": "bigger means faster"}, {"path": "setsamp/integ", "type": "float", "readonly": false, "cmd": "tt setsamp/integ", "description": "bigger means faster"},
{"path": "setsamp/deriv", "type": "float", "readonly": false, "cmd": "tt setsamp/deriv"}, {"path": "setsamp/deriv", "type": "float", "readonly": false, "cmd": "tt setsamp/deriv"},
{"path": "display", "type": "text", "readonly": false, "cmd": "tt display"}, {"path": "display", "type": "text", "readonly": false, "cmd": "tt display"},
{"path": "remote", "type": "bool"}]}, {"path": "remote", "type": "bool", "readonly": false, "cmd": "run tt"}]},
"cc": {"base": "/cc", "params": [ "cc": {"base": "/cc", "params": [
{"path": "", "type": "bool", "kids": 96}, {"path": "", "type": "bool", "kids": 96},
@ -108,7 +108,7 @@
{"path": "mcr", "type": "float"}, {"path": "mcr", "type": "float"},
{"path": "mot", "type": "float"}, {"path": "mot", "type": "float"},
{"path": "mw", "type": "float", "readonly": false, "cmd": "cc mw", "description": "correction pulse after automatic open"}, {"path": "mw", "type": "float", "readonly": false, "cmd": "cc mw", "description": "correction pulse after automatic open"},
{"path": "hav", "type": "enum", "enum": {"none": 0, "int": 1, "ext": 2}, "readonly": false, "cmd": "cc hav"}, {"path": "hav", "type": "enum", "type": "enum", "enum": {"none": 0, "int": 1, "ext": 2}, "readonly": false, "cmd": "cc hav"},
{"path": "h", "type": "float"}, {"path": "h", "type": "float"},
{"path": "hr", "type": "float"}, {"path": "hr", "type": "float"},
{"path": "hc", "type": "float"}, {"path": "hc", "type": "float"},
@ -132,26 +132,26 @@
{"path": "hms", "type": "float"}, {"path": "hms", "type": "float"},
{"path": "hit", "type": "float", "readonly": false, "cmd": "cc hit"}, {"path": "hit", "type": "float", "readonly": false, "cmd": "cc hit"},
{"path": "hft", "type": "int", "readonly": false, "cmd": "cc hft"}, {"path": "hft", "type": "int", "readonly": false, "cmd": "cc hft"},
{"path": "hea", "type": "enum", "enum": {"0": 0, "1": 1, "6": 6}, "readonly": false, "cmd": "cc hea"}, {"path": "hea", "type": "enum", "enum": {"0": 0, "1": 1, "6": 2}, "readonly": false, "cmd": "cc hea"},
{"path": "hch", "type": "int", "readonly": false, "cmd": "cc hch"}, {"path": "hch", "type": "int", "readonly": false, "cmd": "cc hch", "visibility": 3},
{"path": "hwr0", "type": "float", "readonly": false, "cmd": "cc hwr0"}, {"path": "hwr0", "type": "float", "readonly": false, "cmd": "cc hwr0", "visibility": 3},
{"path": "hem0", "type": "float", "readonly": false, "cmd": "cc hem0", "description": "sensor length in mm from top to empty pos."}, {"path": "hem0", "type": "float", "readonly": false, "cmd": "cc hem0", "description": "sensor length in mm from top to empty pos.", "visibility": 3},
{"path": "hfu0", "type": "float", "readonly": false, "cmd": "cc hfu0", "description": "sensor length in mm from top to full pos."}, {"path": "hfu0", "type": "float", "readonly": false, "cmd": "cc hfu0", "description": "sensor length in mm from top to full pos.", "visibility": 3},
{"path": "hd0", "type": "float", "readonly": false, "cmd": "cc hd0", "description": "external sensor drive current (mA)"}, {"path": "hd0", "type": "float", "readonly": false, "cmd": "cc hd0", "description": "external sensor drive current (mA)", "visibility": 3},
{"path": "h0", "type": "float"}, {"path": "h0", "type": "float", "visibility": 3},
{"path": "hs0", "type": "enum", "enum": {"sens_ok": 0, "sens_warm": 1, "no_sens": 2, "timeout": 3, "not_yet_read": 4, "disabled": 5}}, {"path": "hs0", "type": "enum", "enum": {"sens_ok": 0, "sens_warm": 1, "no_sens": 2, "timeout": 3, "not_yet_read": 4, "disabled": 5}, "visibility": 3},
{"path": "h1", "type": "float"}, {"path": "h1", "type": "float", "visibility": 3},
{"path": "hs1", "type": "enum", "enum": {"sens_ok": 0, "sens_warm": 1, "no_sens": 2, "timeout": 3, "not_yet_read": 4, "disabled": 5}}, {"path": "hs1", "type": "enum", "enum": {"sens_ok": 0, "sens_warm": 1, "no_sens": 2, "timeout": 3, "not_yet_read": 4, "disabled": 5}, "visibility": 3},
{"path": "h2", "type": "float"}, {"path": "h2", "type": "float", "visibility": 3},
{"path": "hs2", "type": "enum", "enum": {"sens_ok": 0, "sens_warm": 1, "no_sens": 2, "timeout": 3, "not_yet_read": 4, "disabled": 5}}, {"path": "hs2", "type": "enum", "enum": {"sens_ok": 0, "sens_warm": 1, "no_sens": 2, "timeout": 3, "not_yet_read": 4, "disabled": 5}, "visibility": 3},
{"path": "h3", "type": "float"}, {"path": "h3", "type": "float", "visibility": 3},
{"path": "hs3", "type": "enum", "enum": {"sens_ok": 0, "sens_warm": 1, "no_sens": 2, "timeout": 3, "not_yet_read": 4, "disabled": 5}}, {"path": "hs3", "type": "enum", "enum": {"sens_ok": 0, "sens_warm": 1, "no_sens": 2, "timeout": 3, "not_yet_read": 4, "disabled": 5}, "visibility": 3},
{"path": "h4", "type": "float"}, {"path": "h4", "type": "float", "visibility": 3},
{"path": "hs4", "type": "enum", "enum": {"sens_ok": 0, "sens_warm": 1, "no_sens": 2, "timeout": 3, "not_yet_read": 4, "disabled": 5}}, {"path": "hs4", "type": "enum", "enum": {"sens_ok": 0, "sens_warm": 1, "no_sens": 2, "timeout": 3, "not_yet_read": 4, "disabled": 5}, "visibility": 3},
{"path": "h5", "type": "float"}, {"path": "h5", "type": "float", "visibility": 3},
{"path": "hs5", "type": "enum", "enum": {"sens_ok": 0, "sens_warm": 1, "no_sens": 2, "timeout": 3, "not_yet_read": 4, "disabled": 5}}, {"path": "hs5", "type": "enum", "enum": {"sens_ok": 0, "sens_warm": 1, "no_sens": 2, "timeout": 3, "not_yet_read": 4, "disabled": 5}, "visibility": 3},
{"path": "hfb", "type": "float"}, {"path": "hfb", "type": "float"},
{"path": "nav", "type": "bool", "readonly": false, "cmd": "cc nav"}, {"path": "nav", "type": "enum", "type": "enum", "enum": {"none": 0, "int": 1, "ext": 2}, "readonly": false, "cmd": "cc nav"},
{"path": "nu", "type": "float"}, {"path": "nu", "type": "float"},
{"path": "nl", "type": "float"}, {"path": "nl", "type": "float"},
{"path": "nth", "type": "float", "readonly": false, "cmd": "cc nth"}, {"path": "nth", "type": "float", "readonly": false, "cmd": "cc nth"},
@ -183,16 +183,15 @@
{"path": "bdl", "type": "float", "readonly": false, "cmd": "cc bdl"}]}, {"path": "bdl", "type": "float", "readonly": false, "cmd": "cc bdl"}]},
"nv": {"base": "/nv", "params": [ "nv": {"base": "/nv", "params": [
{"path": "", "type": "enum", "enum": {"fixed": 0, "controlled": 1, "automatic": 2, "close": 3, "open": 4}, "readonly": false, "cmd": "nv", "kids": 12}, {"path": "", "type": "enum", "enum": {"fixed": 0, "controlled": 1, "automatic": 2, "close": 3, "open": 4}, "readonly": false, "cmd": "nv", "kids": 11},
{"path": "send", "type": "text", "readonly": false, "cmd": "nv send", "visibility": 3}, {"path": "send", "type": "text", "readonly": false, "cmd": "nv send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3}, {"path": "status", "type": "text", "visibility": 3},
{"path": "motstat", "type": "enum", "enum": {"idle": 0, "opening": 1, "closing": 2, "opened": 3, "closed": 4, "no_motor": 5}}, {"path": "motstat", "type": "enum", "enum": {"idle": 0, "opening": 1, "closing": 2, "opened": 3, "closed": 4, "no_motor": 5}},
{"path": "flow", "type": "float"}, {"path": "flow", "type": "float"},
{"path": "set", "type": "float", "readonly": false, "cmd": "nv set"}, {"path": "set", "type": "float", "readonly": false, "cmd": "nv set"},
{"path": "flowmax", "type": "float", "readonly": false, "cmd": "nv flowmax"}, {"path": "flowmax", "type": "float", "readonly": false, "cmd": "nv flowmax"},
{"path": "flowp", "type": "float", "description": "flow calculated from pressure before pump"}, {"path": "flowp", "type": "float"},
{"path": "span", "type": "float"}, {"path": "span", "type": "float"},
{"path": "use_pressure", "type": "bool", "readonly": false, "cmd": "nv use_pressure", "description": "use pressure instead of flow meter for control"},
{"path": "ctrl", "type": "none", "kids": 13}, {"path": "ctrl", "type": "none", "kids": 13},
{"path": "ctrl/regtext", "type": "text"}, {"path": "ctrl/regtext", "type": "text"},
{"path": "ctrl/prop_o", "type": "float", "readonly": false, "cmd": "nv ctrl/prop_o", "description": "prop [sec/mbar] when opening. above 4 mbar a 10 times lower value is used"}, {"path": "ctrl/prop_o", "type": "float", "readonly": false, "cmd": "nv ctrl/prop_o", "description": "prop [sec/mbar] when opening. above 4 mbar a 10 times lower value is used"},
@ -236,34 +235,15 @@
{"path": "calib/ln_per_min_per_mbar", "type": "float", "readonly": false, "cmd": "nv calib/ln_per_min_per_mbar"}, {"path": "calib/ln_per_min_per_mbar", "type": "float", "readonly": false, "cmd": "nv calib/ln_per_min_per_mbar"},
{"path": "calib/mbar_offset", "type": "float", "readonly": false, "cmd": "nv calib/mbar_offset"}]}, {"path": "calib/mbar_offset", "type": "float", "readonly": false, "cmd": "nv calib/mbar_offset"}]},
"hefill": {"base": "/hefill", "params": [
{"path": "", "type": "enum", "enum": {"watching": 0, "filling": 1, "inactive": 2, "manualfill": 3}, "readonly": false, "cmd": "hefill", "kids": 16},
{"path": "send", "type": "text", "readonly": false, "cmd": "hefill send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3},
{"path": "state", "type": "text"},
{"path": "readpath", "type": "text", "readonly": false, "cmd": "hefill readpath", "visibility": 3},
{"path": "lowlevel", "type": "float", "readonly": false, "cmd": "hefill lowlevel"},
{"path": "highlevel", "type": "float", "readonly": false, "cmd": "hefill highlevel"},
{"path": "smooth", "type": "float"},
{"path": "minfillminutes", "type": "float", "readonly": false, "cmd": "hefill minfillminutes"},
{"path": "maxfillminutes", "type": "float", "readonly": false, "cmd": "hefill maxfillminutes"},
{"path": "minholdhours", "type": "float", "readonly": false, "cmd": "hefill minholdhours"},
{"path": "maxholdhours", "type": "float", "readonly": false, "cmd": "hefill maxholdhours"},
{"path": "tolerance", "type": "float", "readonly": false, "cmd": "hefill tolerance"},
{"path": "badreadingminutes", "type": "float", "readonly": false, "cmd": "hefill badreadingminutes"},
{"path": "tubecoolingminutes", "type": "float", "readonly": false, "cmd": "hefill tubecoolingminutes"},
{"path": "vessellimit", "type": "float", "readonly": false, "cmd": "hefill vessellimit"},
{"path": "vext", "type": "float"}]},
"hepump": {"base": "/hepump", "params": [ "hepump": {"base": "/hepump", "params": [
{"path": "", "type": "enum", "enum": {"neodry": 8, "xds35_auto": 0, "xds35_manual": 1, "sv65": 2, "other": 3, "no": -1}, "readonly": false, "cmd": "hepump", "description": "xds35: scroll pump, sv65: leybold", "kids": 10}, {"path": "", "type": "enum", "enum": {"neodry": 8, "xds35_auto": 0, "xds35_manual": 1, "sv65": 2, "other": 3, "no": -1}, "readonly": false, "cmd": "hepump", "description": "xds35: scroll pump, sv65: leybold", "kids": 10},
{"path": "send", "type": "text", "readonly": false, "cmd": "hepump send", "visibility": 3}, {"path": "send", "type": "text", "readonly": false, "cmd": "hepump send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3}, {"path": "status", "type": "text", "visibility": 3},
{"path": "running", "type": "bool", "readonly": false, "cmd": "hepump running"}, {"path": "running", "type": "bool", "readonly": false, "cmd": "hepump running"},
{"path": "eco", "type": "bool", "readonly": false, "cmd": "hepump eco", "visibility": 3}, {"path": "eco", "type": "bool", "readonly": false, "cmd": "hepump eco"},
{"path": "auto", "type": "bool", "readonly": false, "cmd": "hepump auto", "visibility": 3}, {"path": "auto", "type": "bool", "readonly": false, "cmd": "hepump auto"},
{"path": "valve", "type": "enum", "enum": {"closed": 0, "closing": 1, "opening": 2, "opened": 3, "undefined": 4}, "readonly": false, "cmd": "hepump valve"}, {"path": "valve", "type": "enum", "enum": {"closed": 0, "closing": 1, "opening": 2, "opened": 3, "undefined": 4}, "readonly": false, "cmd": "hepump valve"},
{"path": "eco_t_lim", "type": "float", "readonly": false, "cmd": "hepump eco_t_lim", "description": "switch off eco mode when T_set < eco_t_lim and T < eco_t_lim * 2", "visibility": 3}, {"path": "eco_t_lim", "type": "float", "readonly": false, "cmd": "hepump eco_t_lim", "description": "switch off eco mode when T_set < eco_t_lim and T < eco_t_lim * 2"},
{"path": "calib", "type": "float", "readonly": false, "cmd": "hepump calib", "visibility": 3}, {"path": "calib", "type": "float", "readonly": false, "cmd": "hepump calib", "visibility": 3},
{"path": "health", "type": "float"}]}, {"path": "health", "type": "float"}]},
@ -311,11 +291,11 @@
{"path": "save", "type": "bool", "readonly": false, "cmd": "nvflow save", "description": "unchecked: current calib is not saved. set checked: save calib"}]}, {"path": "save", "type": "bool", "readonly": false, "cmd": "nvflow save", "description": "unchecked: current calib is not saved. set checked: save calib"}]},
"ln2fill": {"base": "/ln2fill", "params": [ "ln2fill": {"base": "/ln2fill", "params": [
{"path": "", "type": "enum", "enum": {"watching": 0, "filling": 1, "inactive": 2, "manualfill": 3}, "readonly": false, "cmd": "ln2fill", "kids": 14}, {"path": "", "type": "enum", "enum": {"watching": 0, "fill": 1, "inactive": 2}, "readonly": false, "cmd": "ln2fill", "kids": 14},
{"path": "send", "type": "text", "readonly": false, "cmd": "ln2fill send", "visibility": 3}, {"path": "send", "type": "text", "readonly": false, "cmd": "ln2fill send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3}, {"path": "status", "type": "text", "visibility": 3},
{"path": "state", "type": "text"}, {"path": "state", "type": "text"},
{"path": "readpath", "type": "text", "readonly": false, "cmd": "ln2fill readpath", "visibility": 3}, {"path": "readlevel", "type": "text", "readonly": false, "cmd": "ln2fill readlevel", "visibility": 3},
{"path": "lowlevel", "type": "float", "readonly": false, "cmd": "ln2fill lowlevel"}, {"path": "lowlevel", "type": "float", "readonly": false, "cmd": "ln2fill lowlevel"},
{"path": "highlevel", "type": "float", "readonly": false, "cmd": "ln2fill highlevel"}, {"path": "highlevel", "type": "float", "readonly": false, "cmd": "ln2fill highlevel"},
{"path": "smooth", "type": "float"}, {"path": "smooth", "type": "float"},
@ -327,33 +307,52 @@
{"path": "badreadingminutes", "type": "float", "readonly": false, "cmd": "ln2fill badreadingminutes"}, {"path": "badreadingminutes", "type": "float", "readonly": false, "cmd": "ln2fill badreadingminutes"},
{"path": "tubecoolingminutes", "type": "float", "readonly": false, "cmd": "ln2fill tubecoolingminutes"}]}, {"path": "tubecoolingminutes", "type": "float", "readonly": false, "cmd": "ln2fill tubecoolingminutes"}]},
"hefill": {"base": "/hefill", "params": [
{"path": "", "type": "enum", "enum": {"watching": 0, "fill": 1, "inactive": 2}, "readonly": false, "cmd": "hefill", "kids": 16},
{"path": "send", "type": "text", "readonly": false, "cmd": "hefill send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3},
{"path": "state", "type": "text"},
{"path": "readlevel", "type": "text", "readonly": false, "cmd": "hefill readlevel", "visibility": 3},
{"path": "lowlevel", "type": "float", "readonly": false, "cmd": "hefill lowlevel"},
{"path": "highlevel", "type": "float", "readonly": false, "cmd": "hefill highlevel"},
{"path": "smooth", "type": "float"},
{"path": "minfillminutes", "type": "float", "readonly": false, "cmd": "hefill minfillminutes"},
{"path": "maxfillminutes", "type": "float", "readonly": false, "cmd": "hefill maxfillminutes"},
{"path": "minholdhours", "type": "float", "readonly": false, "cmd": "hefill minholdhours"},
{"path": "maxholdhours", "type": "float", "readonly": false, "cmd": "hefill maxholdhours"},
{"path": "tolerance", "type": "float", "readonly": false, "cmd": "hefill tolerance"},
{"path": "badreadingminutes", "type": "float", "readonly": false, "cmd": "hefill badreadingminutes"},
{"path": "tubecoolingminutes", "type": "float", "readonly": false, "cmd": "hefill tubecoolingminutes"},
{"path": "vessellimit", "type": "float", "readonly": false, "cmd": "hefill vessellimit"},
{"path": "vext", "type": "float"}]},
"mf": {"base": "/mf", "params": [ "mf": {"base": "/mf", "params": [
{"path": "", "type": "float", "readonly": false, "cmd": "run mf", "kids": 26}, {"path": "", "type": "float", "readonly": false, "cmd": "run mf", "kids": 26},
{"path": "persmode", "type": "int", "readonly": false, "cmd": "mf persmode"}, {"path": "persmode", "type": "int", "readonly": false, "cmd": "mf persmode"},
{"path": "perswitch", "type": "int"}, {"path": "perswitch", "type": "int", "readonly": false, "cmd": "run mf"},
{"path": "nowait", "type": "int", "readonly": false, "cmd": "mf nowait"}, {"path": "nowait", "type": "int", "readonly": false, "cmd": "mf nowait"},
{"path": "maxlimit", "type": "float", "visibility": 3}, {"path": "maxlimit", "type": "float", "readonly": false, "cmd": "run mf", "visibility": 3},
{"path": "limit", "type": "float", "readonly": false, "cmd": "mf limit"}, {"path": "limit", "type": "float", "readonly": false, "cmd": "mf limit"},
{"path": "ramp", "type": "float", "readonly": false, "cmd": "mf ramp"}, {"path": "ramp", "type": "float", "readonly": false, "cmd": "mf ramp"},
{"path": "perscurrent", "type": "float", "readonly": false, "cmd": "mf perscurrent"}, {"path": "perscurrent", "type": "float", "readonly": false, "cmd": "mf perscurrent"},
{"path": "perslimit", "type": "float", "readonly": false, "cmd": "mf perslimit"}, {"path": "perslimit", "type": "float", "readonly": false, "cmd": "mf perslimit"},
{"path": "perswait", "type": "int", "readonly": false, "cmd": "mf perswait"}, {"path": "perswait", "type": "int", "readonly": false, "cmd": "mf perswait"},
{"path": "persdelay", "type": "int", "readonly": false, "cmd": "mf persdelay"}, {"path": "persdelay", "type": "int", "readonly": false, "cmd": "mf persdelay"},
{"path": "current", "type": "float"}, {"path": "current", "type": "float", "readonly": false, "cmd": "run mf"},
{"path": "measured", "type": "float"}, {"path": "measured", "type": "float", "readonly": false, "cmd": "run mf"},
{"path": "voltage", "type": "float"}, {"path": "voltage", "type": "float", "readonly": false, "cmd": "run mf"},
{"path": "lastfield", "type": "float", "visibility": 3}, {"path": "lastfield", "type": "float", "readonly": false, "cmd": "run mf", "visibility": 3},
{"path": "ampRamp", "type": "float", "visibility": 3}, {"path": "ampRamp", "type": "float", "readonly": false, "cmd": "run mf", "visibility": 3},
{"path": "inductance", "type": "float", "visibility": 3}, {"path": "inductance", "type": "float", "readonly": false, "cmd": "run mf", "visibility": 3},
{"path": "trainedTo", "type": "float", "readonly": false, "cmd": "mf trainedTo"}, {"path": "trainedTo", "type": "float", "readonly": false, "cmd": "mf trainedTo"},
{"path": "trainMode", "type": "int"}, {"path": "trainMode", "type": "int", "readonly": false, "cmd": "run mf"},
{"path": "external", "type": "int", "readonly": false, "cmd": "mf external"}, {"path": "external", "type": "int", "readonly": false, "cmd": "mf external"},
{"path": "startScript", "type": "text", "readonly": false, "cmd": "mf startScript", "visibility": 3}, {"path": "startScript", "type": "text", "readonly": false, "cmd": "mf startScript", "visibility": 3},
{"path": "is_running", "type": "int", "readonly": false, "cmd": "mf is_running", "visibility": 3}, {"path": "is_running", "type": "int", "readonly": false, "cmd": "run mf", "visibility": 3},
{"path": "verbose", "type": "int", "readonly": false, "cmd": "mf verbose", "visibility": 3}, {"path": "verbose", "type": "int", "readonly": false, "cmd": "mf verbose", "visibility": 3},
{"path": "driver", "type": "text", "visibility": 3}, {"path": "driver", "type": "text", "readonly": false, "cmd": "run mf", "visibility": 3},
{"path": "creationCmd", "type": "text", "visibility": 3}, {"path": "creationCmd", "type": "text", "readonly": false, "cmd": "run mf", "visibility": 3},
{"path": "targetValue", "type": "float"}, {"path": "targetValue", "type": "float", "readonly": false, "cmd": "run mf"},
{"path": "status", "type": "text", "readonly": false, "cmd": "mf status", "visibility": 3}]}, {"path": "status", "type": "text", "readonly": false, "cmd": "mf status", "visibility": 3}]},
"lev": {"base": "/lev", "params": [ "lev": {"base": "/lev", "params": [
@ -363,22 +362,7 @@
{"path": "mode", "type": "enum", "enum": {"slow": 0, "fast (switches to slow automatically after filling)": 1}, "readonly": false, "cmd": "lev mode"}, {"path": "mode", "type": "enum", "enum": {"slow": 0, "fast (switches to slow automatically after filling)": 1}, "readonly": false, "cmd": "lev mode"},
{"path": "n2", "type": "float"}]}, {"path": "n2", "type": "float"}]},
"table": {"base": "/table", "params": [ "prep0": {"base": "/prep0", "params": [
{"path": "", "type": "none", "kids": 17}, {"path": "", "type": "text", "readonly": false, "cmd": "prep0", "kids": 2},
{"path": "send", "type": "text", "readonly": false, "cmd": "table send", "visibility": 3}, {"path": "send", "type": "text", "readonly": false, "cmd": "prep0 send", "visibility": 3},
{"path": "status", "type": "text", "visibility": 3}, {"path": "status", "type": "text", "visibility": 3}]}}
{"path": "fix_tt_set_prop", "type": "bool", "readonly": false, "cmd": "table fix_tt_set_prop"},
{"path": "val_tt_set_prop", "type": "float"},
{"path": "tbl_tt_set_prop", "type": "text", "readonly": false, "cmd": "table tbl_tt_set_prop", "description": "enter value pair separated with colon T1:par1 T2:par2 ..."},
{"path": "fix_tt_set_integ", "type": "bool", "readonly": false, "cmd": "table fix_tt_set_integ"},
{"path": "val_tt_set_integ", "type": "float"},
{"path": "tbl_tt_set_integ", "type": "text", "readonly": false, "cmd": "table tbl_tt_set_integ", "description": "enter value pair separated with colon T1:par1 T2:par2 ..."},
{"path": "fix_tt_dblctrl_int2", "type": "bool", "readonly": false, "cmd": "table fix_tt_dblctrl_int2"},
{"path": "val_tt_dblctrl_int2", "type": "float"},
{"path": "tbl_tt_dblctrl_int2", "type": "text", "readonly": false, "cmd": "table tbl_tt_dblctrl_int2", "description": "enter value pair separated with colon T1:par1 T2:par2 ..."},
{"path": "fix_tt_dblctrl_prop_up", "type": "bool", "readonly": false, "cmd": "table fix_tt_dblctrl_prop_up"},
{"path": "val_tt_dblctrl_prop_up", "type": "float"},
{"path": "tbl_tt_dblctrl_prop_up", "type": "text", "readonly": false, "cmd": "table tbl_tt_dblctrl_prop_up", "description": "enter value pair separated with colon T1:par1 T2:par2 ..."},
{"path": "fix_tt_dblctrl_prop_lo", "type": "bool", "readonly": false, "cmd": "table fix_tt_dblctrl_prop_lo"},
{"path": "val_tt_dblctrl_prop_lo", "type": "float"},
{"path": "tbl_tt_dblctrl_prop_lo", "type": "text", "readonly": false, "cmd": "table tbl_tt_dblctrl_prop_lo", "description": "enter value pair separated with colon T1:par1 T2:par2 ..."}]}}

View File

@ -1,33 +1,3 @@
import os
Node('mb11.stick.sea.psi.ch', Node('mb11.stick.sea.psi.ch',
'MB11 standard sample stick (do not use)', 'MB11 standard sample stick (do not use)',
) )
frappy_main_port = os.environ.get('FRAPPY_MAIN_PORT', 0)
Mod('itc1_',
'frappy.core.Proxy',
'itc1 on main frappy server',
remote_class = 'frappy_psi.mercury.IO',
uri = f'tcp://localhost:{frappy_main_port}',
module='itc1',
# export = False,
)
Mod('T_sample',
'frappy_psi.mercury.TemperatureLoop',
'T at sample stick sensor',
meaning=['temperature', 30],
io='itc1_',
slot='MB1.T1',
)
Mod('htr_sample',
'frappy_psi.mercury.HeaterOutput',
'sample stick heater power',
slot='MB0.H1',
io='itc1_',
)

View File

@ -88,13 +88,16 @@ Mod('interlocks',
vacuum_limit = 0.1, vacuum_limit = 0.1,
) )
Mod('p_io',
'frappy_psi.pfeiffer.IO',
'pressure io',
uri='serial:///dev/ttyUSBlower',
)
Mod('p', Mod('p',
'frappy_psi.ionopimax.LogVoltageInput', 'frappy_psi.pfeiffer.Pressure',
'pressure reading', 'pressure reading',
addr = 'av1', io = 'p_io',
rawrange = (1.8, 8.6),
valuerange = (1e-7, 1000),
value = Param(unit='mbar'),
) )

144
debian/changelog vendored
View File

@ -1,48 +1,4 @@
frappy-core (0.20.5) stable; urgency=medium frappy-core (0.20.4) jammy; urgency=medium
[ Markus Zolliker ]
* add sim-server again based on socketserver
* fix bug when overriding a property with bare value
* frappy.server bug fix: server name must not be a list
* frappy.server: use server name for SecNode name
* frappy.server: remove comment about opts in SecNode/Dispatcher
* follow up change for 'better order of accessibles' (34904)
* better message when a parameter is overridden by an invalid value
* pylint: increase max number of positional arguments
* an error on a write must not send an error update
* fix bug in change 35001 (better error message)
* make UPD listener work when 'tcp://' is omitted on interface
* config: do not override equipment_id with name
* equipment_id for merged configs and routed nodes
* core: alternative approach for optional accessibles
* core: simplify test for methods names
[ Georg Brandl ]
* debian: update compat
* remove wrong <weight> from fonts on Qt6
[ Markus Zolliker ]
* config: validate value and default of parameters
* config: Mod() should return config dict
* stop poller threads on shutdown
* fix overriding Parameter with value
* improve error messages on module creation
* make sure unexported modules are initialized
* change to new visibility spec
* follow-up change to 35931: make Proxy a Module
[ Konstantin Kholostov ]
* installer: add recipe to build macOS app bundle
[ Markus Zolliker ]
* frappy.client.SecopClient: fix setParameterFromString
* frappy_psi/ls370res: various bug fixes
* client: add SecopClient.execCommandFromString
* frappy.client.interactive: improve updates while driving
-- Markus Zolliker <jenkins@frm2.tum.de> Mon, 12 May 2025 14:03:22 +0200
frappy-core (0.20.4) stable; urgency=medium
[ Georg Brandl ] [ Georg Brandl ]
* remove unused file * remove unused file
@ -61,7 +17,7 @@ frappy-core (0.20.4) stable; urgency=medium
-- Georg Brandl <jenkins@frm2.tum.de> Thu, 14 Nov 2024 14:43:54 +0100 -- Georg Brandl <jenkins@frm2.tum.de> Thu, 14 Nov 2024 14:43:54 +0100
frappy-core (0.20.3) stable; urgency=medium frappy-core (0.20.3) jammy; urgency=medium
[ Georg Brandl ] [ Georg Brandl ]
* fixup test for cfg_editor utils to run from non-checkout, and fix names, and remove example code * fixup test for cfg_editor utils to run from non-checkout, and fix names, and remove example code
@ -71,7 +27,7 @@ frappy-core (0.20.3) stable; urgency=medium
-- Georg Brandl <jenkins@frm2.tum.de> Thu, 07 Nov 2024 10:57:11 +0100 -- Georg Brandl <jenkins@frm2.tum.de> Thu, 07 Nov 2024 10:57:11 +0100
frappy-core (0.20.2) stable; urgency=medium frappy-core (0.20.2) jammy; urgency=medium
[ Georg Brandl ] [ Georg Brandl ]
* pylint: do not try to infer too much * pylint: do not try to infer too much
@ -117,7 +73,7 @@ frappy-core (0.20.2) stable; urgency=medium
-- Georg Brandl <jenkins@frm2.tum.de> Wed, 06 Nov 2024 10:40:26 +0100 -- Georg Brandl <jenkins@frm2.tum.de> Wed, 06 Nov 2024 10:40:26 +0100
frappy-core (0.20.1) stable; urgency=medium frappy-core (0.20.1) jammy; urgency=medium
* gui: do not add a console logger when there is no sys.stdout * gui: do not add a console logger when there is no sys.stdout
* remove unused test class * remove unused test class
@ -127,7 +83,7 @@ frappy-core (0.20.1) stable; urgency=medium
-- Georg Brandl <jenkins@frm2.tum.de> Thu, 17 Oct 2024 16:31:27 +0200 -- Georg Brandl <jenkins@frm2.tum.de> Thu, 17 Oct 2024 16:31:27 +0200
frappy-core (0.20.0) stable; urgency=medium frappy-core (0.20.0) jammy; urgency=medium
[ Alexander Zaft ] [ Alexander Zaft ]
* bin: remove make_doc * bin: remove make_doc
@ -172,7 +128,7 @@ frappy-core (0.20.0) stable; urgency=medium
-- Alexander Zaft <jenkins@frm2.tum.de> Thu, 17 Oct 2024 14:24:29 +0200 -- Alexander Zaft <jenkins@frm2.tum.de> Thu, 17 Oct 2024 14:24:29 +0200
frappy-core (0.19.10) stable; urgency=medium frappy-core (0.19.10) jammy; urgency=medium
[ Alexander Zaft ] [ Alexander Zaft ]
* debian: let frappy-core replace frappy-demo * debian: let frappy-core replace frappy-demo
@ -182,25 +138,25 @@ frappy-core (0.19.10) stable; urgency=medium
-- Alexander Zaft <jenkins@frm2.tum.de> Wed, 07 Aug 2024 17:00:06 +0200 -- Alexander Zaft <jenkins@frm2.tum.de> Wed, 07 Aug 2024 17:00:06 +0200
frappy-core (0.19.9) stable; urgency=medium frappy-core (0.19.9) jammy; urgency=medium
* debian: fix missing install dir * debian: fix missing install dir
-- Georg Brandl <jenkins@frm2.tum.de> Tue, 06 Aug 2024 16:02:50 +0200 -- Georg Brandl <jenkins@frm2.tum.de> Tue, 06 Aug 2024 16:02:50 +0200
frappy-core (0.19.8) stable; urgency=medium frappy-core (0.19.8) jammy; urgency=medium
* debian: move demo into core * debian: move demo into core
-- Georg Brandl <jenkins@frm2.tum.de> Tue, 06 Aug 2024 15:58:20 +0200 -- Georg Brandl <jenkins@frm2.tum.de> Tue, 06 Aug 2024 15:58:20 +0200
frappy-core (0.19.7) stable; urgency=medium frappy-core (0.19.7) jammy; urgency=medium
* lib: GeneralConfig fix missing keys logic * lib: GeneralConfig fix missing keys logic
-- Alexander Zaft <jenkins@frm2.tum.de> Tue, 06 Aug 2024 15:04:07 +0200 -- Alexander Zaft <jenkins@frm2.tum.de> Tue, 06 Aug 2024 15:04:07 +0200
frappy-core (0.19.6) stable; urgency=medium frappy-core (0.19.6) jammy; urgency=medium
[ Jens Krüger ] [ Jens Krüger ]
* SINQ/SEA: Fix import error due to None value * SINQ/SEA: Fix import error due to None value
@ -214,7 +170,7 @@ frappy-core (0.19.6) stable; urgency=medium
-- Jens Krüger <jenkins@frm2.tum.de> Tue, 06 Aug 2024 13:56:51 +0200 -- Jens Krüger <jenkins@frm2.tum.de> Tue, 06 Aug 2024 13:56:51 +0200
frappy-core (0.19.5) stable; urgency=medium frappy-core (0.19.5) jammy; urgency=medium
* client: fix how to raise error on wrong ident * client: fix how to raise error on wrong ident
* add missing requirements to setup.py * add missing requirements to setup.py
@ -223,13 +179,13 @@ frappy-core (0.19.5) stable; urgency=medium
-- Alexander Zaft <jenkins@frm2.tum.de> Mon, 05 Aug 2024 09:30:53 +0200 -- Alexander Zaft <jenkins@frm2.tum.de> Mon, 05 Aug 2024 09:30:53 +0200
frappy-core (0.19.4) stable; urgency=medium frappy-core (0.19.4) jammy; urgency=medium
* actually exclude cfg-editor * actually exclude cfg-editor
-- Georg Brandl <jenkins@frm2.tum.de> Fri, 26 Jul 2024 11:46:10 +0200 -- Georg Brandl <jenkins@frm2.tum.de> Fri, 26 Jul 2024 11:46:10 +0200
frappy-core (0.19.3) stable; urgency=medium frappy-core (0.19.3) jammy; urgency=medium
[ Markus Zolliker ] [ Markus Zolliker ]
* frappy_psi.extparams.StructParam: fix doc + simplify * frappy_psi.extparams.StructParam: fix doc + simplify
@ -249,7 +205,7 @@ frappy-core (0.19.3) stable; urgency=medium
-- Markus Zolliker <jenkins@frm2.tum.de> Fri, 26 Jul 2024 08:36:43 +0200 -- Markus Zolliker <jenkins@frm2.tum.de> Fri, 26 Jul 2024 08:36:43 +0200
frappy-core (0.19.2) stable; urgency=medium frappy-core (0.19.2) jammy; urgency=medium
[ l_samenv ] [ l_samenv ]
* fix missing update after error on parameter * fix missing update after error on parameter
@ -274,7 +230,7 @@ frappy-core (0.19.2) stable; urgency=medium
-- l_samenv <jenkins@frm2.tum.de> Tue, 18 Jun 2024 15:21:43 +0200 -- l_samenv <jenkins@frm2.tum.de> Tue, 18 Jun 2024 15:21:43 +0200
frappy-core (0.19.1) stable; urgency=medium frappy-core (0.19.1) jammy; urgency=medium
[ Markus Zolliker ] [ Markus Zolliker ]
* SecopClient.online must be True while activating * SecopClient.online must be True while activating
@ -286,7 +242,7 @@ frappy-core (0.19.1) stable; urgency=medium
-- Markus Zolliker <jenkins@frm2.tum.de> Fri, 07 Jun 2024 16:50:33 +0200 -- Markus Zolliker <jenkins@frm2.tum.de> Fri, 07 Jun 2024 16:50:33 +0200
frappy-core (0.19.0) stable; urgency=medium frappy-core (0.19.0) jammy; urgency=medium
[ Markus Zolliker ] [ Markus Zolliker ]
* simulation: extra_params might be a list * simulation: extra_params might be a list
@ -342,14 +298,14 @@ frappy-core (0.19.0) stable; urgency=medium
-- Markus Zolliker <jenkins@frm2.tum.de> Thu, 16 May 2024 11:31:25 +0200 -- Markus Zolliker <jenkins@frm2.tum.de> Thu, 16 May 2024 11:31:25 +0200
frappy-core (0.18.1) stable; urgency=medium frappy-core (0.18.1) focal; urgency=medium
* mlz: Zapf fix unit handling and small errors * mlz: Zapf fix unit handling and small errors
* mlz: entangle fix limit check * mlz: entangle fix limit check
-- Alexander Zaft <jenkins@frm2.tum.de> Wed, 24 Jan 2024 14:59:21 +0100 -- Alexander Zaft <jenkins@frm2.tum.de> Wed, 24 Jan 2024 14:59:21 +0100
frappy-core (0.18.0) stable; urgency=medium frappy-core (0.18.0) focal; urgency=medium
[ Alexander Zaft ] [ Alexander Zaft ]
* Add shutdownModule function * Add shutdownModule function
@ -460,7 +416,7 @@ frappy-core (0.18.0) stable; urgency=medium
-- Alexander Zaft <jenkins@frm2.tum.de> Wed, 17 Jan 2024 12:35:00 +0100 -- Alexander Zaft <jenkins@frm2.tum.de> Wed, 17 Jan 2024 12:35:00 +0100
frappy-core (0.17.13) stable; urgency=medium frappy-core (0.17.13) focal; urgency=medium
[ Alexander Zaft ] [ Alexander Zaft ]
* add egg-info to gitignore * add egg-info to gitignore
@ -481,7 +437,7 @@ frappy-core (0.17.13) stable; urgency=medium
-- Alexander Zaft <jenkins@frm2.tum.de> Tue, 20 Jun 2023 14:38:00 +0200 -- Alexander Zaft <jenkins@frm2.tum.de> Tue, 20 Jun 2023 14:38:00 +0200
frappy-core (0.17.12) stable; urgency=medium frappy-core (0.17.12) focal; urgency=medium
[ Alexander Zaft ] [ Alexander Zaft ]
* Warn about duplicate module definitions in a file * Warn about duplicate module definitions in a file
@ -506,7 +462,7 @@ frappy-core (0.17.12) stable; urgency=medium
-- Alexander Zaft <jenkins@frm2.tum.de> Tue, 13 Jun 2023 06:51:27 +0200 -- Alexander Zaft <jenkins@frm2.tum.de> Tue, 13 Jun 2023 06:51:27 +0200
frappy-core (0.17.11) stable; urgency=medium frappy-core (0.17.11) focal; urgency=medium
[ Alexander Zaft ] [ Alexander Zaft ]
* Add __format__ to EnumMember * Add __format__ to EnumMember
@ -579,7 +535,7 @@ frappy-core (0.17.11) stable; urgency=medium
-- Alexander Zaft <jenkins@frm2.tum.de> Thu, 25 May 2023 09:38:24 +0200 -- Alexander Zaft <jenkins@frm2.tum.de> Thu, 25 May 2023 09:38:24 +0200
frappy-core (0.17.10) stable; urgency=medium frappy-core (0.17.10) focal; urgency=medium
* Change leftover %-logging calls to lazy * Change leftover %-logging calls to lazy
* Convert formatting automatically to f-strings * Convert formatting automatically to f-strings
@ -591,25 +547,25 @@ frappy-core (0.17.10) stable; urgency=medium
-- Alexander Zaft <jenkins@frm2.tum.de> Wed, 19 Apr 2023 14:32:52 +0200 -- Alexander Zaft <jenkins@frm2.tum.de> Wed, 19 Apr 2023 14:32:52 +0200
frappy-core (0.17.9) stable; urgency=medium frappy-core (0.17.9) focal; urgency=medium
* interactive client: avoid messing up the input line * interactive client: avoid messing up the input line
-- Markus Zolliker <jenkins@frm2.tum.de> Tue, 11 Apr 2023 16:09:03 +0200 -- Markus Zolliker <jenkins@frm2.tum.de> Tue, 11 Apr 2023 16:09:03 +0200
frappy-core (0.17.8) stable; urgency=medium frappy-core (0.17.8) focal; urgency=medium
* Debian: Fix typo * Debian: Fix typo
-- Jens Krüger <jenkins@frm2.tum.de> Wed, 05 Apr 2023 07:20:25 +0200 -- Jens Krüger <jenkins@frm2.tum.de> Wed, 05 Apr 2023 07:20:25 +0200
frappy-core (0.17.7) stable; urgency=medium frappy-core (0.17.7) focal; urgency=medium
* Debian: add pyqtgraph dependency * Debian: add pyqtgraph dependency
-- Jens Krüger <jenkins@frm2.tum.de> Wed, 05 Apr 2023 07:07:24 +0200 -- Jens Krüger <jenkins@frm2.tum.de> Wed, 05 Apr 2023 07:07:24 +0200
frappy-core (0.17.6) stable; urgency=medium frappy-core (0.17.6) focal; urgency=medium
[ Alexander Zaft ] [ Alexander Zaft ]
* gui: show parameter properties again * gui: show parameter properties again
@ -629,25 +585,25 @@ frappy-core (0.17.6) stable; urgency=medium
-- Alexander Zaft <jenkins@frm2.tum.de> Tue, 04 Apr 2023 08:42:26 +0200 -- Alexander Zaft <jenkins@frm2.tum.de> Tue, 04 Apr 2023 08:42:26 +0200
frappy-core (0.17.5) stable; urgency=medium frappy-core (0.17.5) focal; urgency=medium
* Fix generator * Fix generator
-- Alexander Zaft <jenkins@frm2.tum.de> Wed, 22 Mar 2023 12:32:06 +0100 -- Alexander Zaft <jenkins@frm2.tum.de> Wed, 22 Mar 2023 12:32:06 +0100
frappy-core (0.17.4) stable; urgency=medium frappy-core (0.17.4) focal; urgency=medium
* Fix entangle integration bugs * Fix entangle integration bugs
-- Alexander Zaft <jenkins@frm2.tum.de> Wed, 22 Mar 2023 11:44:34 +0100 -- Alexander Zaft <jenkins@frm2.tum.de> Wed, 22 Mar 2023 11:44:34 +0100
frappy-core (0.17.3) stable; urgency=medium frappy-core (0.17.3) focal; urgency=medium
* UNRELEASED * UNRELEASED
-- Alexander Zaft <jenkins@frm2.tum.de> Tue, 21 Mar 2023 15:55:09 +0100 -- Alexander Zaft <jenkins@frm2.tum.de> Tue, 21 Mar 2023 15:55:09 +0100
frappy-core (0.17.2) stable; urgency=medium frappy-core (0.17.2) focal; urgency=medium
[ Alexander Zaft ] [ Alexander Zaft ]
* Fix Simulation and Proxy * Fix Simulation and Proxy
@ -784,7 +740,7 @@ frappy-core (0.17.2) stable; urgency=medium
-- Alexander Zaft <jenkins@frm2.tum.de> Tue, 21 Mar 2023 15:49:06 +0100 -- Alexander Zaft <jenkins@frm2.tum.de> Tue, 21 Mar 2023 15:49:06 +0100
frappy-core (0.17.1) stable; urgency=medium frappy-core (0.17.1) focal; urgency=medium
[ Georg Brandl ] [ Georg Brandl ]
* gitignore: ignore demo PID file * gitignore: ignore demo PID file
@ -803,7 +759,7 @@ frappy-core (0.17.1) stable; urgency=medium
-- Georg Brandl <jenkins@frm2.tum.de> Tue, 21 Feb 2023 17:44:56 +0100 -- Georg Brandl <jenkins@frm2.tum.de> Tue, 21 Feb 2023 17:44:56 +0100
frappy-core (0.17.0) stable; urgency=medium frappy-core (0.17.0) focal; urgency=medium
[ Alexander Zaft ] [ Alexander Zaft ]
* Rework GUI. * Rework GUI.
@ -814,37 +770,37 @@ frappy-core (0.17.0) stable; urgency=medium
-- Alexander Zaft <jenkins@frm2.tum.de> Tue, 21 Feb 2023 13:52:17 +0100 -- Alexander Zaft <jenkins@frm2.tum.de> Tue, 21 Feb 2023 13:52:17 +0100
frappy-core (0.16.1) stable; urgency=medium frappy-core (0.16.1) focal; urgency=medium
* UNRELEASED * UNRELEASED
-- Georg Brandl <jenkins@frm2.tum.de> Tue, 21 Feb 2023 08:44:28 +0100 -- Georg Brandl <jenkins@frm2.tum.de> Tue, 21 Feb 2023 08:44:28 +0100
frappy-core (0.16.4) stable; urgency=medium frappy-core (0.16.4) focal; urgency=medium
* UNRELEASED * UNRELEASED
-- Georg Brandl <jenkins@frm2.tum.de> Tue, 21 Feb 2023 08:09:20 +0100 -- Georg Brandl <jenkins@frm2.tum.de> Tue, 21 Feb 2023 08:09:20 +0100
frappy-core (0.16.3) stable; urgency=medium frappy-core (0.16.3) focal; urgency=medium
* UNRELEASED * UNRELEASED
-- Georg Brandl <jenkins@frm2.tum.de> Tue, 21 Feb 2023 08:00:15 +0100 -- Georg Brandl <jenkins@frm2.tum.de> Tue, 21 Feb 2023 08:00:15 +0100
frappy-core (0.16.2) stable; urgency=medium frappy-core (0.16.2) focal; urgency=medium
* gui: move icon resources for the cfg editor to its subdirectory * gui: move icon resources for the cfg editor to its subdirectory
-- Georg Brandl <jenkins@frm2.tum.de> Tue, 21 Feb 2023 07:50:13 +0100 -- Georg Brandl <jenkins@frm2.tum.de> Tue, 21 Feb 2023 07:50:13 +0100
frappy-core (0.16.1) stable; urgency=medium frappy-core (0.16.1) focal; urgency=medium
* add frappy-cli to package * add frappy-cli to package
-- Enrico Faulhaber <jenkins@frm2.tum.de> Mon, 20 Feb 2023 17:17:23 +0100 -- Enrico Faulhaber <jenkins@frm2.tum.de> Mon, 20 Feb 2023 17:17:23 +0100
frappy-core (0.16.0) stable; urgency=medium frappy-core (0.16.0) focal; urgency=medium
[ Enrico Faulhaber ] [ Enrico Faulhaber ]
* fix sorce package name * fix sorce package name
@ -906,7 +862,7 @@ frappy-core (0.16.0) stable; urgency=medium
-- Enrico Faulhaber <jenkins@frm2.tum.de> Mon, 20 Feb 2023 16:15:10 +0100 -- Enrico Faulhaber <jenkins@frm2.tum.de> Mon, 20 Feb 2023 16:15:10 +0100
frappy-core (0.15.0) stable; urgency=medium frappy-core (0.15.0) focal; urgency=medium
[ Björn Pedersen ] [ Björn Pedersen ]
* Remove iohandler left-overs from docs * Remove iohandler left-overs from docs
@ -936,7 +892,7 @@ frappy-core (0.15.0) stable; urgency=medium
-- Björn Pedersen <jenkins@frm2.tum.de> Thu, 10 Nov 2022 14:46:01 +0100 -- Björn Pedersen <jenkins@frm2.tum.de> Thu, 10 Nov 2022 14:46:01 +0100
secop-core (0.14.3) stable; urgency=medium secop-core (0.14.3) focal; urgency=medium
[ Enrico Faulhaber ] [ Enrico Faulhaber ]
* change repo to secop/frappy * change repo to secop/frappy
@ -952,13 +908,13 @@ secop-core (0.14.3) stable; urgency=medium
-- Enrico Faulhaber <jenkins@frm2.tum.de> Thu, 03 Nov 2022 13:51:52 +0100 -- Enrico Faulhaber <jenkins@frm2.tum.de> Thu, 03 Nov 2022 13:51:52 +0100
secop-core (0.14.2) stable; urgency=medium secop-core (0.14.2) focal; urgency=medium
* systemd generator: adapt to changed config API * systemd generator: adapt to changed config API
-- Georg Brandl <jenkins@frm2.tum.de> Thu, 20 Oct 2022 15:38:45 +0200 -- Georg Brandl <jenkins@frm2.tum.de> Thu, 20 Oct 2022 15:38:45 +0200
secop-core (0.14.1) stable; urgency=medium secop-core (0.14.1) focal; urgency=medium
[ Markus Zolliker ] [ Markus Zolliker ]
* secop_psi.entangle.AnalogInput: fix main value * secop_psi.entangle.AnalogInput: fix main value
@ -970,7 +926,7 @@ secop-core (0.14.1) stable; urgency=medium
-- Markus Zolliker <jenkins@frm2.tum.de> Thu, 20 Oct 2022 14:04:07 +0200 -- Markus Zolliker <jenkins@frm2.tum.de> Thu, 20 Oct 2022 14:04:07 +0200
secop-core (0.14.0) stable; urgency=medium secop-core (0.14.0) focal; urgency=medium
* add simple interactive python client * add simple interactive python client
* fix undefined status in softcal * fix undefined status in softcal
@ -984,7 +940,7 @@ secop-core (0.14.0) stable; urgency=medium
-- Markus Zolliker <jenkins@frm2.tum.de> Wed, 19 Oct 2022 11:31:50 +0200 -- Markus Zolliker <jenkins@frm2.tum.de> Wed, 19 Oct 2022 11:31:50 +0200
secop-core (0.13.1) stable; urgency=medium secop-core (0.13.1) focal; urgency=medium
[ Markus Zolliker ] [ Markus Zolliker ]
* an enum with value 0 should be interpreted as False * an enum with value 0 should be interpreted as False
@ -995,7 +951,7 @@ secop-core (0.13.1) stable; urgency=medium
-- Markus Zolliker <jenkins@jenkins02.admin.frm2.tum.de> Tue, 02 Aug 2022 15:31:52 +0200 -- Markus Zolliker <jenkins@jenkins02.admin.frm2.tum.de> Tue, 02 Aug 2022 15:31:52 +0200
secop-core (0.13.0) stable; urgency=medium secop-core (0.13.0) focal; urgency=medium
[ Georg Brandl ] [ Georg Brandl ]
* debian: fix email addresses in changelog * debian: fix email addresses in changelog
@ -1058,13 +1014,13 @@ secop-core (0.13.0) stable; urgency=medium
-- Georg Brandl <jenkins@frm2.tum.de> Tue, 02 Aug 2022 09:47:06 +0200 -- Georg Brandl <jenkins@frm2.tum.de> Tue, 02 Aug 2022 09:47:06 +0200
secop-core (0.12.4) stable; urgency=medium secop-core (0.12.4) focal; urgency=medium
* fix command inheritance * fix command inheritance
-- Markus Zolliker <jenkins@jenkins01.admin.frm2.tum.de> Thu, 11 Nov 2021 16:21:19 +0100 -- Markus Zolliker <jenkins@jenkins01.admin.frm2.tum.de> Thu, 11 Nov 2021 16:21:19 +0100
secop-core (0.12.3) stable; urgency=medium secop-core (0.12.3) focal; urgency=medium
[ Georg Brandl ] [ Georg Brandl ]
* Makefile: fix docker image * Makefile: fix docker image
@ -1087,7 +1043,7 @@ secop-core (0.12.3) stable; urgency=medium
-- Georg Brandl <jenkins@jenkins01.admin.frm2.tum.de> Wed, 10 Nov 2021 16:33:19 +0100 -- Georg Brandl <jenkins@jenkins01.admin.frm2.tum.de> Wed, 10 Nov 2021 16:33:19 +0100
secop-core (0.12.2) stable; urgency=medium secop-core (0.12.2) focal; urgency=medium
[ Markus Zolliker ] [ Markus Zolliker ]
* fix issue with new syntax in simulation * fix issue with new syntax in simulation
@ -1099,13 +1055,13 @@ secop-core (0.12.2) stable; urgency=medium
-- Markus Zolliker <jenkins@jenkins01.admin.frm2.tum.de> Tue, 18 May 2021 10:29:17 +0200 -- Markus Zolliker <jenkins@jenkins01.admin.frm2.tum.de> Tue, 18 May 2021 10:29:17 +0200
secop-core (0.12.1) stable; urgency=medium secop-core (0.12.1) focal; urgency=medium
* remove secop-console from debian *.install file * remove secop-console from debian *.install file
-- Enrico Faulhaber <jenkins@jenkins02.admin.frm2.tum.de> Tue, 04 May 2021 09:42:53 +0200 -- Enrico Faulhaber <jenkins@jenkins02.admin.frm2.tum.de> Tue, 04 May 2021 09:42:53 +0200
secop-core (0.12.0) stable; urgency=medium secop-core (0.12.0) focal; urgency=medium
[ Markus Zolliker ] [ Markus Zolliker ]
* make datatypes immutable * make datatypes immutable

1
debian/compat vendored Normal file
View File

@ -0,0 +1 @@
11

4
debian/control vendored
View File

@ -2,7 +2,7 @@ Source: frappy-core
Section: contrib/misc Section: contrib/misc
Priority: optional Priority: optional
Maintainer: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de> Maintainer: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
Build-Depends: debhelper-compat (= 13), Build-Depends: debhelper (>= 11~),
dh-python, dh-python,
python3 (>=3.6), python3 (>=3.6),
python3-all, python3-all,
@ -20,7 +20,7 @@ Build-Depends: debhelper-compat (= 13),
git, git,
markdown, markdown,
python3-daemon python3-daemon
Standards-Version: 4.6.2 Standards-Version: 4.1.4
X-Python3-Version: >= 3.6 X-Python3-Version: >= 3.6
Package: frappy-core Package: frappy-core

View File

@ -1,75 +0,0 @@
# *****************************************************************************
#
# 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>
#
# *****************************************************************************
from frappy.core import Parameter, Property
from frappy.datatypes import ValueType
class AddrParam(Parameter):
"""parameter with an address field
instead of implementing read_<param> and write_<param>, just implement
addressed_read and addressed_write.
"""
addr = Property('address', ValueType())
class AddrMixin:
"""mixin for addressed parameters
in case a read_<param> and/or write_<param> are not implemented,
they are created with a call to addressed_read and/or addressed_write
"""
def __init_subclass__(cls):
for aname, aobj in list(cls.__dict__.items()):
if isinstance(aobj, AddrParam):
methodname = f'read_{aname}'
if not hasattr(cls, methodname):
def rfunc(self, pname=aname):
return self.addressed_read(self.accessibles[pname])
setattr(cls, methodname, rfunc)
if not aobj.readonly:
methodname = f'write_{aname}'
if not hasattr(cls, methodname):
def wfunc(self, value, pname=aname):
return self.addressed_write(self.accessibles[pname], value)
setattr(cls, methodname, wfunc)
super().__init_subclass__()
def addressed_read(self, pobj):
"""addressed read
:param pobj: the AddrParam
:return: the value read
"""
return getattr(self, pobj.name)
def addressed_write(self, pobj, value):
"""addressed write
:param pobj: the AddrParam
:param value: the value to be written
:return: the value written or None
"""

View File

@ -209,20 +209,16 @@ class ProxyClient:
# caches (module, parameter) = value, timestamp, readerror (internal names!) # caches (module, parameter) = value, timestamp, readerror (internal names!)
self.cache = Cache() # dict returning Cache.undefined for missing keys self.cache = Cache() # dict returning Cache.undefined for missing keys
def register_callback(self, key, *args, callimmediately=True, **kwds): def register_callback(self, key, *args, callimmediately=None, **kwds):
"""register callback functions """register callback functions
several callbacks might be registered within one call. - key might be either:
ProxyClient.CALLBACK_NAMES contains all names of valid callbacks
:param key: might be either:
1) None: general callback (all callbacks) 1) None: general callback (all callbacks)
2) <module name>: callbacks related to a module (not called for 'unhandledMessage') 2) <module name>: callbacks related to a module (not called for 'unhandledMessage')
3) (<module name>, <parameter name>): callback for specified parameter 3) (<module name>, <parameter name>): callback for specified parameter (only called for 'updateEvent')
(only called for 'updateEvent' and 'updateItem') - all the following arguments are callback functions. The callback name may be
:param args: callback functions. the callback name is taken from the the __name__ attribute of the function given by the keyword, or, for non-keyworded arguments it is taken from the
:param callimmediately: True (default): call immediately for updateItem and updateEvent callbacks __name__ attribute of the function
:param kwds: callback functions. the callback name is taken from the key
""" """
for cbfunc in args: for cbfunc in args:
kwds[cbfunc.__name__] = cbfunc kwds[cbfunc.__name__] = cbfunc
@ -230,8 +226,8 @@ class ProxyClient:
if cbname not in self.CALLBACK_NAMES: if cbname not in self.CALLBACK_NAMES:
raise TypeError(f"unknown callback: {', '.join(kwds)}") raise TypeError(f"unknown callback: {', '.join(kwds)}")
# call immediately for some callback types # immediately call for some callback types
if cbname in ('updateItem', 'updateEvent') and callimmediately: if cbname in ('updateItem', 'updateEvent') and callimmediately is not False:
if key is None: # case generic callback if key is None: # case generic callback
cbargs = [(m, p, d) for (m, p), d in self.cache.items()] cbargs = [(m, p, d) for (m, p), d in self.cache.items()]
else: else:
@ -738,7 +734,7 @@ class SecopClient(ProxyClient):
""" """
self.connect() # make sure we are connected self.connect() # make sure we are connected
datatype = self.modules[module]['parameters'][parameter]['datatype'] datatype = self.modules[module]['parameters'][parameter]['datatype']
value = datatype.export_value(datatype.from_string(formatted)) value = datatype.from_string(formatted)
self.request(WRITEREQUEST, self.identifier[module, parameter], value) self.request(WRITEREQUEST, self.identifier[module, parameter], value)
return self.cache[module, parameter] return self.cache[module, parameter]
@ -757,28 +753,6 @@ class SecopClient(ProxyClient):
data = datatype.import_value(data) data = datatype.import_value(data)
return data, qualifiers return data, qualifiers
def execCommandFromString(self, module, command, formatted_argument=''):
"""call command from string argument
return data as CacheItem which allows to get
- result.value # the python value
- result.formatted() # a string (incl. units)
- result.timestamp
"""
self.connect()
datatype = self.modules[module]['commands'][command]['datatype'].argument
if datatype:
argument = datatype.from_string(formatted_argument)
else:
if formatted_argument:
raise WrongTypeError('command has no argument')
argument = None
# pylint: disable=unsubscriptable-object
data, qualifiers = self.request(COMMANDREQUEST, self.identifier[module, command], argument)[2]
datatype = self.modules[module]['commands'][command]['datatype'].result
value = datatype.import_value(data) if datatype else None
return CacheItem(value, qualifiers.get('t'), None, datatype)
def updateValue(self, module, param, value, timestamp, readerror): def updateValue(self, module, param, value, timestamp, readerror):
datatype = self.modules[module]['parameters'][param]['datatype'] datatype = self.modules[module]['parameters'][param]['datatype']
if readerror: if readerror:

View File

@ -143,7 +143,7 @@ class Module:
def _isBusy(self): def _isBusy(self):
return self.status[0] // 100 == StatusType.BUSY // 100 return self.status[0] // 100 == StatusType.BUSY // 100
def _status_update(self, m, p, status, t, e): def _status_value_update(self, m, p, status, t, e):
if self._is_driving and not self._isBusy(): if self._is_driving and not self._isBusy():
self._is_driving = False self._is_driving = False
self._driving_event.set() self._driving_event.set()
@ -216,11 +216,10 @@ class Module:
def __call__(self, target=None): def __call__(self, target=None):
if target is None: if target is None:
return self.read() return self.read()
watch_params = ['value', 'status'] for pname in 'value', 'status':
for pname in watch_params:
self._secnode.register_callback((self._name, pname), self._secnode.register_callback((self._name, pname),
updateEvent=self._watch_parameter, callimmediately=False,
callimmediately=False) updateEvent=self._watch_parameter)
self.target = target # this sets self._is_driving self.target = target # this sets self._is_driving
@ -240,10 +239,11 @@ class Module:
pass pass
clientenv.raise_with_short_traceback(e) clientenv.raise_with_short_traceback(e)
finally: finally:
# self._watch_parameter(self._name, 'status')
self._secnode.readParameter(self._name, 'value') self._secnode.readParameter(self._name, 'value')
for pname in watch_params: # self._watch_parameter(self._name, 'value', forced=True)
self._secnode.unregister_callback((self._name, pname), self._secnode.unregister_callback((self._name, 'value'), updateEvent=self._watch_parameter)
updateEvent=self._watch_parameter) self._secnode.unregister_callback((self._name, 'status'), updateEvent=self._watch_parameter)
return self.value return self.value
def __repr__(self): def __repr__(self):
@ -418,7 +418,8 @@ class Client(SecopClient):
attrs[cname] = Command(cname, modname, self) attrs[cname] = Command(cname, modname, self)
mobj = type(f'M_{modname}', (Module,), attrs)(modname, self) mobj = type(f'M_{modname}', (Module,), attrs)(modname, self)
if 'status' in mobj._parameters: if 'status' in mobj._parameters:
self.register_callback((modname, 'status'), updateEvent=mobj._status_update) self.register_callback((modname, 'status'), updateEvent=mobj._status_value_update)
self.register_callback((modname, 'value'), updateEvent=mobj._status_value_update)
clientenv.namespace[modname] = mobj clientenv.namespace[modname] = mobj
if removed_modules: if removed_modules:
self.log.info('removed modules: %s', ' '.join(removed_modules)) self.log.info('removed modules: %s', ' '.join(removed_modules))
@ -449,7 +450,7 @@ def run(filepath):
"__file__": filepath, "__file__": filepath,
"__name__": "__main__", "__name__": "__main__",
}) })
with open(filepath, 'rb') as file: with filepath.open('rb') as file:
# pylint: disable=exec-used # pylint: disable=exec-used
exec(compile(file.read(), filepath, 'exec'), clientenv.namespace, None) exec(compile(file.read(), filepath, 'exec'), clientenv.namespace, None)

View File

@ -16,13 +16,13 @@
# #
# Module authors: # Module authors:
# Alexander Zaft <a.zaft@fz-juelich.de> # Alexander Zaft <a.zaft@fz-juelich.de>
# Markus Zolliker <markus.zolliker@psi.ch>
# #
# ***************************************************************************** # *****************************************************************************
import os import os
from pathlib import Path from pathlib import Path
import re import re
from collections import Counter
from frappy.errors import ConfigError from frappy.errors import ConfigError
from frappy.lib import generalConfig from frappy.lib import generalConfig
@ -88,50 +88,17 @@ class Mod(dict):
for member in members: for member in members:
self[member]['group'] = group self[member]['group'] = group
def override(self, **kwds):
name = self['name']
warnings = []
for key, ovr in kwds.items():
if isinstance(ovr, Group):
warnings.append(f'ignore Group when overriding module {name}')
continue
param = self.get(key)
if param is None:
self[key] = ovr if isinstance(ovr, Param) else Param(ovr)
continue
if isinstance(param, Param):
if isinstance(ovr, Param):
param.update(ovr)
else:
param['value'] = ovr
else: # description or cls
self[key] = ovr
return warnings
class Collector: class Collector:
def __init__(self): def __init__(self, cls):
self.modules = {} self.list = []
self.warnings = [] self.cls = cls
def add(self, *args, **kwds): def add(self, *args, **kwds):
mod = Mod(*args, **kwds) self.list.append(self.cls(*args, **kwds))
name = mod.pop('name')
if name in self.modules:
self.warnings.append(f'duplicate module {name} overrides previous')
self.modules[name] = mod
return mod
def override(self, name, **kwds): def append(self, mod):
"""override properties/parameters of previously configured modules self.list.append(mod)
this is useful together with 'include'
"""
mod = self.modules.get(name)
if mod is None:
self.warnings.append(f'try to override nonexisting module {name}')
return
self.warnings.extend(mod.override(**kwds))
class NodeCollector: class NodeCollector:
@ -144,16 +111,14 @@ class NodeCollector:
else: else:
raise ConfigError('Only one Node is allowed per file!') raise ConfigError('Only one Node is allowed per file!')
def override(self, **kwds):
if self.node is None:
raise ConfigError('node must be defined before overriding')
self.node.update(kwds)
class Config(dict): class Config(dict):
def __init__(self, node, modules): def __init__(self, node, modules):
super().__init__(node=node.node, **modules.modules) super().__init__(
self.module_names = set(modules.modules) 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() self.ambiguous = set()
def merge_modules(self, other): def merge_modules(self, other):
@ -169,35 +134,25 @@ class Config(dict):
mod['original_id'] = equipment_id mod['original_id'] = equipment_id
class Include:
def __init__(self, namespace, log):
self.namespace = namespace
self.log = log
def __call__(self, cfgfile):
filename = to_config_path(cfgfile, self.log, '')
# pylint: disable=exec-used
exec(compile(filename.read_bytes(), filename, 'exec'), self.namespace)
def process_file(filename, log): def process_file(filename, log):
config_text = filename.read_bytes() config_text = filename.read_bytes()
node = NodeCollector() node = NodeCollector()
mods = Collector() mods = Collector(Mod)
ns = {'Node': node.add, 'Mod': mods.add, 'Param': Param, 'Command': Param, 'Group': Group, ns = {'Node': node.add, 'Mod': mods.add, 'Param': Param, 'Command': Param, 'Group': Group}
'override': mods.override, 'overrideNode': node.override}
ns['include'] = Include(ns, log)
# pylint: disable=exec-used # pylint: disable=exec-used
exec(compile(config_text, filename, 'exec'), ns) exec(compile(config_text, filename, 'exec'), ns)
if mods.warnings: # check for duplicates in the file itself. Between files comes later
log.warning('warnings in %s', filename) duplicates = [name for name, count in Counter([mod['name']
for text in mods.warnings: for mod in mods.list]).items() if count > 1]
log.warning(text) if duplicates:
log.warning('Duplicate module name in file \'%s\': %s',
filename, ','.join(duplicates))
return Config(node, mods) return Config(node, mods)
def to_config_path(cfgfile, log, check_end='_cfg.py'): def to_config_path(cfgfile, log):
candidates = [cfgfile + e for e in ['_cfg.py', '.py', '']] candidates = [cfgfile + e for e in ['_cfg.py', '.py', '']]
if os.sep in cfgfile: # specified as full path if os.sep in cfgfile: # specified as full path
file = Path(cfgfile) if Path(cfgfile).exists() else None file = Path(cfgfile) if Path(cfgfile).exists() else None
@ -211,8 +166,8 @@ def to_config_path(cfgfile, log, check_end='_cfg.py'):
file = None file = None
if file is None: if file is None:
raise ConfigError(f"Couldn't find cfg file {cfgfile!r} in {generalConfig.confdir}") raise ConfigError(f"Couldn't find cfg file {cfgfile!r} in {generalConfig.confdir}")
if not file.name.endswith(check_end): if not file.name.endswith('_cfg.py'):
log.warning("Config files should end in %r: %s", check_end, file.name) log.warning("Config files should end in '_cfg.py': %s", file.name)
log.debug('Using config file %s for %s', file, cfgfile) log.debug('Using config file %s for %s', file, cfgfile)
return file return file
@ -240,8 +195,6 @@ def load_config(cfgfiles, log):
config.merge_modules(cfg) config.merge_modules(cfg)
else: else:
config = cfg config = cfg
if config.get('node') is None:
raise ConfigError(f'missing Node in {filename}')
if config.ambiguous: if config.ambiguous:
log.warning('ambiguous sections in %s: %r', log.warning('ambiguous sections in %s: %r',

View File

@ -27,6 +27,7 @@
<property name="font"> <property name="font">
<font> <font>
<pointsize>18</pointsize> <pointsize>18</pointsize>
<weight>75</weight>
<bold>true</bold> <bold>true</bold>
</font> </font>
</property> </property>

View File

@ -21,6 +21,7 @@
<property name="font"> <property name="font">
<font> <font>
<pointsize>12</pointsize> <pointsize>12</pointsize>
<weight>75</weight>
<bold>true</bold> <bold>true</bold>
<underline>true</underline> <underline>true</underline>
</font> </font>

View File

@ -60,6 +60,7 @@ class HasAccessibles(HasProperties):
(so the dispatcher will get notified of changed values) (so the dispatcher will get notified of changed values)
""" """
isWrapped = False isWrapped = False
checkedMethods = set()
@classmethod @classmethod
def __init_subclass__(cls): # pylint: disable=too-many-branches def __init_subclass__(cls): # pylint: disable=too-many-branches
@ -113,8 +114,8 @@ class HasAccessibles(HasProperties):
wrapped_name = '_' + cls.__name__ wrapped_name = '_' + cls.__name__
for pname, pobj in accessibles.items(): for pname, pobj in accessibles.items():
# wrap of reading/writing funcs # wrap of reading/writing funcs
if not isinstance(pobj, Parameter) or pobj.optional: if not isinstance(pobj, Parameter):
# nothing to do for Commands and optional parameters # nothing to do for Commands
continue continue
rname = 'read_' + pname rname = 'read_' + pname
@ -198,15 +199,16 @@ class HasAccessibles(HasProperties):
new_wfunc.__module__ = cls.__module__ new_wfunc.__module__ = cls.__module__
cls.wrappedAttributes[wname] = new_wfunc cls.wrappedAttributes[wname] = new_wfunc
cls.checkedMethods.update(cls.wrappedAttributes)
# check for programming errors # check for programming errors
for attrname, func in cls.__dict__.items(): for attrname in dir(cls):
prefix, _, pname = attrname.partition('_') prefix, _, pname = attrname.partition('_')
if not pname: if not pname:
continue continue
if prefix == 'do': if prefix == 'do':
raise ProgrammingError(f'{cls.__name__!r}: old style command {attrname!r} not supported anymore') raise ProgrammingError(f'{cls.__name__!r}: old style command {attrname!r} not supported anymore')
if (prefix in ('read', 'write') and attrname not in cls.wrappedAttributes if prefix in ('read', 'write') and attrname not in cls.checkedMethods:
and not hasattr(func, 'poll')): # may be a handler, which always has a poll attribute
raise ProgrammingError(f'{cls.__name__}.{attrname} defined, but {pname!r} is no parameter') raise ProgrammingError(f'{cls.__name__}.{attrname} defined, but {pname!r} is no parameter')
try: try:
@ -323,7 +325,6 @@ class Module(HasAccessibles):
pollInfo = None pollInfo = None
triggerPoll = None # trigger event for polls. used on io modules and modules without io triggerPoll = None # trigger event for polls. used on io modules and modules without io
__poller = None # the poller thread, if used
def __init__(self, name, logger, cfgdict, srv): def __init__(self, name, logger, cfgdict, srv):
# remember the secnode for interacting with other modules and the # remember the secnode for interacting with other modules and the
@ -389,8 +390,6 @@ class Module(HasAccessibles):
accessibles = self.accessibles accessibles = self.accessibles
self.accessibles = {} self.accessibles = {}
for aname, aobj in accessibles.items(): for aname, aobj in accessibles.items():
if aobj.optional:
continue
# make a copy of the Parameter/Command object # make a copy of the Parameter/Command object
aobj = aobj.copy() aobj = aobj.copy()
acfg = cfgdict.pop(aname, None) acfg = cfgdict.pop(aname, None)
@ -451,12 +450,9 @@ class Module(HasAccessibles):
self.parameters[name] = accessible self.parameters[name] = accessible
if isinstance(accessible, Command): if isinstance(accessible, Command):
self.commands[name] = accessible self.commands[name] = accessible
if cfg is not None: if cfg:
try: try:
for propname, propvalue in cfg.items(): for propname, propvalue in cfg.items():
if propname in {'value', 'default', 'constant'}:
# these properties have ValueType(), but should be checked for datatype
accessible.datatype(cfg[propname])
accessible.setProperty(propname, propvalue) accessible.setProperty(propname, propvalue)
except KeyError: except KeyError:
self.errors.append(f"'{name}' has no property '{propname}'") self.errors.append(f"'{name}' has no property '{propname}'")
@ -526,7 +522,9 @@ class Module(HasAccessibles):
if validate: if validate:
value = pobj.datatype(value) value = pobj.datatype(value)
except Exception as e: except Exception as e:
err = e # allow to assign an exception to trigger an error_update message
err = value if isinstance(value, Exception) else e
changed = False
else: else:
changed = pobj.value != value or pobj.readerror changed = pobj.value != value or pobj.readerror
# store the value even in case of error # store the value even in case of error
@ -613,7 +611,7 @@ class Module(HasAccessibles):
# we do not need self.errors any longer. should we delete it? # we do not need self.errors any longer. should we delete it?
# del self.errors # del self.errors
if self.polledModules: if self.polledModules:
self.__poller = mkthread(self.__pollThread, self.polledModules, start_events.get_trigger()) mkthread(self.__pollThread, self.polledModules, start_events.get_trigger())
self.startModuleDone = True self.startModuleDone = True
def initialReads(self): def initialReads(self):
@ -626,28 +624,8 @@ class Module(HasAccessibles):
all parameters are polled once all parameters are polled once
""" """
def stopPollThread(self):
"""trigger the poll thread to stop
this is called on shutdown
"""
if self.__poller:
self.polledModules.clear()
self.triggerPoll.set()
def joinPollThread(self, timeout):
"""wait for poll thread to finish
if the wait time exceeds <timeout> seconds, return and log a warning
"""
if self.__poller:
self.stopPollThread()
self.__poller.join(timeout)
if self.__poller.is_alive():
self.log.warning('can not stop poller')
def shutdownModule(self): def shutdownModule(self):
"""called when the server shuts down """called when the sever shuts down
any cleanup-work should be performed here, like closing threads and any cleanup-work should be performed here, like closing threads and
saving data. saving data.
@ -679,10 +657,13 @@ class Module(HasAccessibles):
self.pollInfo.pending_errors.discard(rfunc.__name__) self.pollInfo.pending_errors.discard(rfunc.__name__)
except Exception as e: except Exception as e:
if getattr(e, 'report_error', True): if getattr(e, 'report_error', True):
self.log.debug('error in %r', rfunc)
name = rfunc.__name__ name = rfunc.__name__
self.pollInfo.pending_errors.add(name) # trigger o.k. message after error is resolved self.pollInfo.pending_errors.add(name) # trigger o.k. message after error is resolved
if isinstance(e, SECoPError): if isinstance(e, SECoPError):
e.raising_methods.append(name) if name == 'doPoll':
# otherwise the method is already appended in rfunc
e.raising_methods.append(f'{self.name}.{name}')
if e.silent: if e.silent:
self.log.debug('%s', e.format(False)) self.log.debug('%s', e.format(False))
else: else:
@ -690,7 +671,7 @@ class Module(HasAccessibles):
if raise_com_failed and isinstance(e, CommunicationFailedError): if raise_com_failed and isinstance(e, CommunicationFailedError):
raise raise
else: else:
# not a SECoPError: this is proabably a programming error # not a SECoPError: this is probably a programming error
# we want to log the traceback # we want to log the traceback
self.log.error('%s', formatException()) self.log.error('%s', formatException())
@ -750,14 +731,13 @@ class Module(HasAccessibles):
if not polled_modules: # no polls needed - exit thread if not polled_modules: # no polls needed - exit thread
return return
to_poll = () to_poll = ()
while modules: # modules will be cleared on shutdown while True:
now = time.time() now = time.time()
wait_time = 999 wait_time = 999
for mobj in modules: for mobj in modules:
pinfo = mobj.pollInfo pinfo = mobj.pollInfo
if pinfo: wait_time = min(pinfo.last_main + pinfo.interval - now, wait_time,
wait_time = min(pinfo.last_main + pinfo.interval - now, wait_time, pinfo.last_slow + mobj.slowinterval - now)
pinfo.last_slow + mobj.slowinterval - now)
if wait_time > 0 and not to_poll: if wait_time > 0 and not to_poll:
# nothing to do # nothing to do
self.triggerPoll.wait(wait_time) self.triggerPoll.wait(wait_time)
@ -766,7 +746,7 @@ class Module(HasAccessibles):
# call doPoll of all modules where due # call doPoll of all modules where due
for mobj in modules: for mobj in modules:
pinfo = mobj.pollInfo pinfo = mobj.pollInfo
if pinfo and now > pinfo.last_main + pinfo.interval: if now > pinfo.last_main + pinfo.interval:
try: try:
pinfo.last_main = (now // pinfo.interval) * pinfo.interval pinfo.last_main = (now // pinfo.interval) * pinfo.interval
except ZeroDivisionError: except ZeroDivisionError:
@ -786,7 +766,7 @@ class Module(HasAccessibles):
# collect due slow polls # collect due slow polls
for mobj in modules: for mobj in modules:
pinfo = mobj.pollInfo pinfo = mobj.pollInfo
if pinfo and now > pinfo.last_slow + mobj.slowinterval: if now > pinfo.last_slow + mobj.slowinterval:
to_poll.extend(pinfo.polled_parameters) to_poll.extend(pinfo.polled_parameters)
pinfo.last_slow = (now // mobj.slowinterval) * mobj.slowinterval pinfo.last_slow = (now // mobj.slowinterval) * mobj.slowinterval
if to_poll: if to_poll:

View File

@ -68,8 +68,8 @@ class Writable(Readable):
target_dt.compatible(value_dt) target_dt.compatible(value_dt)
except Exception: except Exception:
if type(value_dt) == type(target_dt): if type(value_dt) == type(target_dt):
raise ConfigError(f'{name}: the target range extends beyond the value range') from None raise ConfigError('the target range extends beyond the value range') from None
raise ProgrammingError(f'{name}: the datatypes of target and value are not compatible') from None raise ProgrammingError('the datatypes of target and value are not compatible') from None
class Drivable(Writable): class Drivable(Writable):

View File

@ -47,7 +47,6 @@ class Accessible(HasProperties):
""" """
ownProperties = None ownProperties = None
optional = False
def init(self, kwds): def init(self, kwds):
# do not use self.propertyValues.update here, as no invalid values should be # do not use self.propertyValues.update here, as no invalid values should be
@ -97,8 +96,6 @@ class Accessible(HasProperties):
props = [] props = []
for k, v in sorted(self.propertyValues.items()): for k, v in sorted(self.propertyValues.items()):
props.append(f'{k}={v!r}') props.append(f'{k}={v!r}')
if self.optional:
props.append('optional=True')
return f"{self.__class__.__name__}({', '.join(props)})" return f"{self.__class__.__name__}({', '.join(props)})"
def fixExport(self): def fixExport(self):
@ -194,9 +191,8 @@ class Parameter(Accessible):
readerror = None readerror = None
omit_unchanged_within = 0 omit_unchanged_within = 0
def __init__(self, description=None, datatype=None, inherit=True, optional=False, **kwds): def __init__(self, description=None, datatype=None, inherit=True, **kwds):
super().__init__() super().__init__()
self.optional = optional
if 'poll' in kwds and generalConfig.tolerate_poll_property: if 'poll' in kwds and generalConfig.tolerate_poll_property:
kwds.pop('poll') kwds.pop('poll')
if datatype is None: if datatype is None:
@ -230,16 +226,10 @@ class Parameter(Accessible):
def __get__(self, instance, owner): def __get__(self, instance, owner):
if instance is None: if instance is None:
return self return self
try: return instance.parameters[self.name].value
return instance.parameters[self.name].value
except KeyError:
raise ProgrammingError(f'optional parameter {self.name} is not implemented') from None
def __set__(self, obj, value): def __set__(self, obj, value):
try: obj.announceUpdate(self.name, value)
obj.announceUpdate(self.name, value)
except KeyError:
raise ProgrammingError(f'optional parameter {self.name} is not implemented') from None
def __set_name__(self, owner, name): def __set_name__(self, owner, name):
self.name = name self.name = name
@ -316,7 +306,7 @@ class Parameter(Accessible):
if modobj: if modobj:
if self.update_unchanged == -1: if self.update_unchanged == -1:
t = modobj.omit_unchanged_within t = modobj.omit_unchanged_within
self.omit_unchanged_within = float(generalConfig.omit_unchanged_within) if t is None else t self.omit_unchanged_within = generalConfig.omit_unchanged_within if t is None else t
else: else:
self.omit_unchanged_within = float(self.update_unchanged) self.omit_unchanged_within = float(self.update_unchanged)
@ -376,6 +366,9 @@ class Command(Accessible):
* True: exported, name automatic. * True: exported, name automatic.
* a string: exported with custom name''', OrType(BoolType(), StringType()), * a string: exported with custom name''', OrType(BoolType(), StringType()),
export=False, default=True) export=False, default=True)
# optional = Property(
# '[internal] is the command optional to implement? (vs. mandatory)', BoolType(),
# export=False, default=False, settable=False)
datatype = Property( datatype = Property(
"datatype of the command, auto generated from 'argument' and 'result'", "datatype of the command, auto generated from 'argument' and 'result'",
DataTypeType(), extname='datainfo', export='always') DataTypeType(), extname='datainfo', export='always')
@ -391,9 +384,8 @@ class Command(Accessible):
func = None func = None
def __init__(self, argument=False, *, result=None, inherit=True, optional=False, **kwds): def __init__(self, argument=False, *, result=None, inherit=True, **kwds):
super().__init__() super().__init__()
self.optional = optional
if 'datatype' in kwds: if 'datatype' in kwds:
# self.init will complain about invalid keywords except 'datatype', as this is a property # self.init will complain about invalid keywords except 'datatype', as this is a property
raise ProgrammingError("Command() got an invalid keyword 'datatype'") raise ProgrammingError("Command() got an invalid keyword 'datatype'")
@ -419,8 +411,8 @@ class Command(Accessible):
def __set_name__(self, owner, name): def __set_name__(self, owner, name):
self.name = name self.name = name
if self.func is None and not self.optional: if self.func is None:
raise ProgrammingError(f'Command {owner.__name__}.{name} must be optional or used as a method decorator') raise ProgrammingError(f'Command {owner.__name__}.{name} must be used as a method decorator')
self.fixExport() self.fixExport()
self.datatype = CommandType(self.argument, self.result) self.datatype = CommandType(self.argument, self.result)

View File

@ -94,7 +94,6 @@ logger = MainLogger()
class Playground(Server): class Playground(Server):
def __init__(self, **kwds): # pylint: disable=super-init-not-called def __init__(self, **kwds): # pylint: disable=super-init-not-called
self.name = 'playground'
for modname, cfg in kwds.items(): for modname, cfg in kwds.items():
cfg.setdefault('description', modname) cfg.setdefault('description', modname)
self.log = logger.log self.log = logger.log

View File

@ -131,16 +131,14 @@ class HasProperties(HasDescriptors):
properties = {} properties = {}
# using cls.__bases__ and base.propertyDict for this would fail on some multiple inheritance cases # using cls.__bases__ and base.propertyDict for this would fail on some multiple inheritance cases
for base in reversed(cls.__mro__): for base in reversed(cls.__mro__):
for key, value in base.__dict__.items(): properties.update({k: v for k, v in base.__dict__.items() if isinstance(v, Property)})
if isinstance(value, Property):
properties[key] = value
elif isinstance(value, HasProperties): # value is a Parameter. allow to override
properties.pop(key, None)
cls.propertyDict = properties cls.propertyDict = properties
# treat overriding properties with bare values # treat overriding properties with bare values
for pn, po in list(properties.items()): for pn, po in list(properties.items()):
value = getattr(cls, pn, po) value = getattr(cls, pn, po)
if not isinstance(value, Property): # attribute may be a bare value if isinstance(value, HasProperties): # value is a Parameter, allow override
properties.pop(pn)
elif not isinstance(value, Property): # attribute may be a bare value
po = po.copy() po = po.copy()
try: try:
# try to apply bare value to Property # try to apply bare value to Property

View File

@ -23,9 +23,6 @@
import os import os
import json import json
import socket import socket
import select
from time import monotonic
from collections import namedtuple
from frappy.lib import closeSocket from frappy.lib import closeSocket
from frappy.protocol.interface.tcp import format_address from frappy.protocol.interface.tcp import format_address
@ -35,79 +32,6 @@ UDP_PORT = 10767
MAX_MESSAGE_LEN = 508 MAX_MESSAGE_LEN = 508
Answer = namedtuple('Answer',
'address, hostname, port, equipment_id, firmware, description')
def decode(msg, addr):
msg = msg.decode('utf-8')
try:
data = json.loads(msg)
except Exception:
return None
if not isinstance(data, dict):
return None
if data.get('SECoP') != 'node':
return None
try:
eq_id = data['equipment_id']
fw = data['firmware']
desc = data['description']
port = data['port']
except KeyError:
return None
try:
hostname = socket.gethostbyaddr(addr[0])[0]
except Exception:
hostname = addr[0]
return Answer(addr[0], hostname, port, eq_id, fw, desc)
def scan(max_wait=1.0):
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
# send a general broadcast
try:
s.sendto(json.dumps(dict(SECoP='discover')).encode('utf-8'),
('255.255.255.255', UDP_PORT))
except OSError as e:
print('could not send the broadcast:', e)
# we still keep listening for self-announcements
seen = set()
start = monotonic()
while monotonic() < start + max_wait:
res = select.select([s], [], [], 0.1)
if res[0]:
try:
msg, addr = s.recvfrom(1024)
except socket.error: # pragma: no cover
continue
answer = decode(msg, addr)
if answer is None:
continue
if (answer.address, answer.equipment_id, answer.port) in seen:
continue
seen.add((answer.address, answer.equipment_id, answer.port))
yield answer
def listen():
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
if os.name == 'nt':
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
else:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
s.bind(('0.0.0.0', UDP_PORT))
while True:
try:
msg, addr = s.recvfrom(1024)
except KeyboardInterrupt:
break
answer = decode(msg, addr)
if answer:
yield answer
class UDPListener: class UDPListener:
def __init__(self, equipment_id, description, ifaces, logger, *, def __init__(self, equipment_id, description, ifaces, logger, *,
startup_broadcast=True): startup_broadcast=True):

View File

@ -265,9 +265,9 @@ class Dispatcher:
modulename, exportedname = specifier, None modulename, exportedname = specifier, None
if ':' in specifier: if ':' in specifier:
modulename, exportedname = specifier.split(':', 1) modulename, exportedname = specifier.split(':', 1)
moduleobj = self.secnode.get_module(modulename) if modulename not in self.secnode.export:
if moduleobj is None or not moduleobj.export:
raise NoSuchModuleError(f'Module {modulename!r} does not exist') raise NoSuchModuleError(f'Module {modulename!r} does not exist')
moduleobj = self.secnode.get_module(modulename)
if exportedname is not None: if exportedname is not None:
pname = moduleobj.accessiblename2attr.get(exportedname, True) pname = moduleobj.accessiblename2attr.get(exportedname, True)
if pname and pname not in moduleobj.accessibles: if pname and pname not in moduleobj.accessibles:
@ -281,7 +281,7 @@ class Dispatcher:
else: else:
# activate all modules # activate all modules
self._active_connections.add(conn) self._active_connections.add(conn)
modules = [(m, None) for m in self.secnode.get_exported_modules()] modules = [(m, None) for m in self.secnode.export]
# send updates for all subscribed values. # send updates for all subscribed values.
# note: The initial poll already happend before the server is active # note: The initial poll already happend before the server is active

View File

@ -33,7 +33,7 @@ from frappy.io import HasIO
DISCONNECTED = Readable.Status.ERROR, 'disconnected' DISCONNECTED = Readable.Status.ERROR, 'disconnected'
class Proxy(HasIO, Module): class ProxyModule(HasIO, Module):
module = Property('remote module name', datatype=StringType(), default='') module = Property('remote module name', datatype=StringType(), default='')
status = Parameter('connection status', Readable.status.datatype) # add status even when not a Readable status = Parameter('connection status', Readable.status.datatype) # add status even when not a Readable
@ -42,17 +42,6 @@ class Proxy(HasIO, Module):
_secnode = None _secnode = None
enablePoll = False enablePoll = False
def __new__(cls, name, logger, cfgdict, srv):
"""create a Proxy class based on remote_class"""
remote_class = cfgdict.pop('remote_class')
if isinstance(remote_class, dict):
remote_class = remote_class['value']
if 'description' not in cfgdict:
cfgdict['description'] = (f"remote module {cfgdict.get('module', name)} "
f"on {cfgdict.get('io', {'value:': '?'})['value']}")
proxycls = proxy_class(remote_class)
return super().__new__(proxycls, name, logger, cfgdict, srv)
def ioClass(self, name, logger, opts, srv): def ioClass(self, name, logger, opts, srv):
opts['description'] = f"secnode {opts.get('module', name)} on {opts['uri']}" opts['description'] = f"secnode {opts.get('module', name)} on {opts['uri']}"
return SecNode(name, logger, opts, srv) return SecNode(name, logger, opts, srv)
@ -142,19 +131,19 @@ class Proxy(HasIO, Module):
pass # skip pass # skip
class ProxyReadable(Proxy, Readable): class ProxyReadable(ProxyModule, Readable):
pass pass
class ProxyWritable(Proxy, Writable): class ProxyWritable(ProxyModule, Writable):
pass pass
class ProxyDrivable(Proxy, Drivable): class ProxyDrivable(ProxyModule, Drivable):
pass pass
PROXY_CLASSES = [ProxyDrivable, ProxyWritable, ProxyReadable, Proxy] PROXY_CLASSES = [ProxyDrivable, ProxyWritable, ProxyReadable, ProxyModule]
class SecNode(Module): class SecNode(Module):
@ -180,7 +169,7 @@ def proxy_class(remote_class, name=None):
"""create a proxy class based on the definition of remote class """create a proxy class based on the definition of remote class
remote class is <import path>.<class name> of a class used on the remote node remote class is <import path>.<class name> of a class used on the remote node
if name is not given, <class name> is used if name is not given, 'Proxy' + <class name> is used
""" """
if isinstance(remote_class, type) and issubclass(remote_class, Module): if isinstance(remote_class, type) and issubclass(remote_class, Module):
rcls = remote_class rcls = remote_class
@ -240,3 +229,18 @@ def proxy_class(remote_class, name=None):
raise ConfigError(f'do not now about {aobj!r} in {remote_class}.accessibles') raise ConfigError(f'do not now about {aobj!r} in {remote_class}.accessibles')
return type(name+"_", (proxycls,), attrs) return type(name+"_", (proxycls,), attrs)
def Proxy(name, logger, cfgdict, srv):
"""create a Proxy object based on remote_class
title cased as it acts like a class
"""
remote_class = cfgdict.pop('remote_class')
if isinstance(remote_class, dict):
remote_class = remote_class['value']
if 'description' not in cfgdict:
cfgdict['description'] = f"remote module {cfgdict.get('module', name)} on {cfgdict.get('io', {'value:': '?'})['value']}"
return proxy_class(remote_class)(name, logger, cfgdict, srv)

View File

@ -102,6 +102,7 @@ class Handler:
"""create the wrapped read_* or write_* methods""" """create the wrapped read_* or write_* methods"""
# at this point, this 'method_names' entry is no longer used -> delete # at this point, this 'method_names' entry is no longer used -> delete
self.method_names.discard((self.func.__module__, self.func.__qualname__)) self.method_names.discard((self.func.__module__, self.func.__qualname__))
owner.checkedMethods.add(name)
for key in self.keys: for key in self.keys:
wrapped = self.wrap(key) wrapped = self.wrap(key)
method_name = self.prefix + key method_name = self.prefix + key

View File

@ -19,7 +19,6 @@
# #
# ***************************************************************************** # *****************************************************************************
import time
import traceback import traceback
from collections import OrderedDict from collections import OrderedDict
@ -27,7 +26,6 @@ from frappy.dynamic import Pinata
from frappy.errors import ConfigError, NoSuchModuleError, NoSuchParameterError from frappy.errors import ConfigError, NoSuchModuleError, NoSuchParameterError
from frappy.lib import get_class from frappy.lib import get_class
from frappy.version import get_version from frappy.version import get_version
from frappy.modules import Module
class SecNode: class SecNode:
@ -44,6 +42,8 @@ class SecNode:
self.nodeprops = {} self.nodeprops = {}
# map ALL modulename -> moduleobj # map ALL modulename -> moduleobj
self.modules = {} self.modules = {}
# list of EXPORTED modules
self.export = []
self.log = logger self.log = logger
self.srv = srv self.srv = srv
# set of modules that failed creation # set of modules that failed creation
@ -130,9 +130,6 @@ class SecNode:
# creation has failed already once, do not try again # creation has failed already once, do not try again
return None return None
cls = classname cls = classname
if not issubclass(cls, Module):
self.errors.append(f'{cls.__name__} is not a Module')
return None
except Exception as e: except Exception as e:
if str(e) == 'no such class': if str(e) == 'no such class':
self.errors.append(f'{classname} not found') self.errors.append(f'{classname} not found')
@ -191,62 +188,60 @@ class SecNode:
modname, len(pinata_modules)) modname, len(pinata_modules))
todos.extend(pinata_modules) todos.extend(pinata_modules)
def export_accessibles(self, modobj): def export_accessibles(self, modulename):
self.log.debug('export_accessibles(%r)', modobj.name) self.log.debug('export_accessibles(%r)', modulename)
# omit export=False params! if modulename in self.export:
res = OrderedDict() # omit export=False params!
for aobj in modobj.accessibles.values(): res = OrderedDict()
if aobj.export: for aobj in self.get_module(modulename).accessibles.values():
res[aobj.export] = aobj.for_export() if aobj.export:
self.log.debug('list accessibles for module %s -> %r', res[aobj.export] = aobj.for_export()
modobj.name, res) self.log.debug('list accessibles for module %s -> %r',
return res modulename, res)
return res
def build_descriptive_data(self): self.log.debug('-> module is not to be exported!')
modules = {} return OrderedDict()
result = {'modules': modules}
for modulename in self.modules:
modobj = self.get_module(modulename)
if not modobj.export:
continue
# some of these need rework !
mod_desc = {'accessibles': self.export_accessibles(modobj)}
mod_desc.update(modobj.exportProperties())
mod_desc.pop('export', None)
modules[modulename] = mod_desc
result['equipment_id'] = self.equipment_id
result['firmware'] = 'FRAPPY ' + get_version()
result['description'] = self.nodeprops['description']
for prop, propvalue in self.nodeprops.items():
if prop.startswith('_'):
result[prop] = propvalue
self.descriptive_data = result
def get_descriptive_data(self, specifier): def get_descriptive_data(self, specifier):
"""returns a python object which upon serialisation results in the """returns a python object which upon serialisation results in the
descriptive data""" descriptive data"""
specifier = specifier or '' specifier = specifier or ''
modules = {}
result = {'modules': modules}
for modulename in self.export:
module = self.get_module(modulename)
if not module.export:
continue
# some of these need rework !
mod_desc = {'accessibles': self.export_accessibles(modulename)}
mod_desc.update(module.exportProperties())
mod_desc.pop('export', False)
modules[modulename] = mod_desc
modname, _, pname = specifier.partition(':') modname, _, pname = specifier.partition(':')
modules = self.descriptive_data['modules']
if modname in modules: # extension to SECoP standard: description of a single module if modname in modules: # extension to SECoP standard: description of a single module
result = modules[modname] result = modules[modname]
if pname in result['accessibles']: # extension to SECoP standard: description of a single accessible if pname in result['accessibles']: # extension to SECoP standard: description of a single accessible
# command is also accepted # command is also accepted
return result['accessibles'][pname] result = result['accessibles'][pname]
if pname: elif pname:
raise NoSuchParameterError(f'Module {modname!r} ' raise NoSuchParameterError(f'Module {modname!r} '
f'has no parameter {pname!r}') f'has no parameter {pname!r}')
return result elif not modname or modname == '.':
if not modname or modname == '.': result['equipment_id'] = self.equipment_id
return self.descriptive_data result['firmware'] = 'FRAPPY ' + get_version()
raise NoSuchModuleError(f'Module {modname!r} does not exist') result['description'] = self.nodeprops['description']
for prop, propvalue in self.nodeprops.items():
def get_exported_modules(self): if prop.startswith('_'):
return [m for m, o in self.modules.items() if o.export] result[prop] = propvalue
else:
raise NoSuchModuleError(f'Module {modname!r} does not exist')
return result
def add_module(self, module, modulename): def add_module(self, module, modulename):
"""Adds a named module object to this SecNode.""" """Adds a named module object to this SecNode."""
self.modules[modulename] = module self.modules[modulename] = module
if module.export:
self.export.append(modulename)
# def remove_module(self, modulename_or_obj): # def remove_module(self, modulename_or_obj):
# moduleobj = self.get_module(modulename_or_obj) # moduleobj = self.get_module(modulename_or_obj)
@ -260,15 +255,6 @@ class SecNode:
def shutdown_modules(self): def shutdown_modules(self):
"""Call 'shutdownModule' for all modules.""" """Call 'shutdownModule' for all modules."""
# stop pollers
for mod in self.modules.values():
mod.stopPollThread()
# do not yet join here, as we want to wait in parallel
now = time.time()
deadline = now + 0.5 # should be long enough for most read functions to finish
for mod in self.modules.values():
mod.joinPollThread(max(0, deadline - now))
now = time.time()
for name in self._getSortedModules(): for name in self._getSortedModules():
self.modules[name].shutdownModule() self.modules[name].shutdownModule()

View File

@ -289,6 +289,7 @@ class Server:
If there are errors that occur, they will be collected and emitted If there are errors that occur, they will be collected and emitted
together in the end. together in the end.
""" """
errors = []
opts = dict(self.node_cfg) opts = dict(self.node_cfg)
cls = get_class(opts.pop('cls')) cls = get_class(opts.pop('cls'))
self.secnode = SecNode(self.name, self.log.getChild('secnode'), opts, self) self.secnode = SecNode(self.name, self.log.getChild('secnode'), opts, self)
@ -300,9 +301,10 @@ class Server:
self.secnode.add_secnode_property(k, opts.pop(k)) self.secnode.add_secnode_property(k, opts.pop(k))
self.secnode.create_modules() self.secnode.create_modules()
# initialize modules by calling self.secnode.get_module for all of them # initialize all modules by getting them with Dispatcher.get_module,
# this is done in build_descriptive_data even for unexported modules # which is done in the get_descriptive data
self.secnode.build_descriptive_data() # TODO: caching, to not make this extra work
self.secnode.get_descriptive_data('')
# =========== All modules are initialized =========== # =========== All modules are initialized ===========
# all errors from initialization process # all errors from initialization process

View File

@ -142,5 +142,4 @@ class SimDrivable(SimReadable, Drivable):
@Command @Command
def stop(self): def stop(self):
"""set target to value"""
self.target = self.value self.target = self.value

View File

@ -215,10 +215,7 @@ class HasStates:
self.read_status() self.read_status()
if fast_poll: if fast_poll:
sm.reset_fast_poll = True sm.reset_fast_poll = True
if fast_poll is True: self.setFastPoll(True)
self.setFastPoll(True)
else:
self.setFastPoll(True, fast_poll)
self.pollInfo.trigger(True) # trigger poller self.pollInfo.trigger(True) # trigger poller
def stop_machine(self, stopped_status=(IDLE, 'stopped')): def stop_machine(self, stopped_status=(IDLE, 'stopped')):

View File

@ -161,7 +161,7 @@ class Cryostat(CryoBase):
by setting the current setpoint as new target""" by setting the current setpoint as new target"""
# XXX: discussion: take setpoint or current value ??? # XXX: discussion: take setpoint or current value ???
self.write_target(self.setpoint if self.mode == 'ramp' else self.value) self.write_target(self.setpoint)
# #
# calculation helpers # calculation helpers

View File

@ -28,7 +28,7 @@ import time
from frappy.datatypes import ArrayOf, BoolType, EnumType, \ from frappy.datatypes import ArrayOf, BoolType, EnumType, \
FloatRange, IntRange, StringType, StructOf, TupleOf FloatRange, IntRange, StringType, StructOf, TupleOf
from frappy.lib.enum import Enum from frappy.lib.enum import Enum
from frappy.modules import Drivable, Readable, Writable, Attached from frappy.modules import Drivable, Readable, Attached
from frappy.modules import Parameter as SECoP_Parameter from frappy.modules import Parameter as SECoP_Parameter
from frappy.properties import Property from frappy.properties import Property
@ -99,14 +99,6 @@ class Switch(Drivable):
self.log.info(info) self.log.info(info)
class BoolWritable(Writable):
value = Parameter('boolean', BoolType())
target = Parameter('boolean', BoolType())
def write_target(self, value):
self.value = value
class MagneticField(Drivable): class MagneticField(Drivable):
"""a liquid magnet """a liquid magnet
""" """

View File

@ -22,13 +22,11 @@
import random import random
from frappy.datatypes import FloatRange, StringType, ValueType, TupleOf, StructOf, ArrayOf, StatusType, BoolType from frappy.datatypes import FloatRange, StringType, ValueType, TupleOf, StructOf, ArrayOf
from frappy.modules import Communicator, Drivable, Parameter, Property, Readable, Module, Attached from frappy.modules import Communicator, Drivable, Parameter, Property, Readable, Module, Attached
from frappy.params import Command from frappy.params import Command
from frappy.dynamic import Pinata from frappy.dynamic import Pinata
from frappy.errors import RangeError, HardwareError from frappy.errors import RangeError, HardwareError
from frappy.core import IDLE, WARN, ERROR, DISABLED
class Pin(Pinata): class Pin(Pinata):
def scanModules(self): def scanModules(self):
@ -107,27 +105,13 @@ class Temp(Drivable):
readonly=False, readonly=False,
unit='K', unit='K',
) )
enabled = Parameter('enable', BoolType(), default=True, readonly=False)
status = Parameter(datatype=StatusType(Readable, 'DISABLED'))
_status = IDLE, ''
def read_value(self): def read_value(self):
value = round(100 * random.random(), 1) return round(100 * random.random(), 1)
if value > 75:
self._status = ERROR, 'sensor break'
elif value > 50:
self._status = WARN, 'out of calibrated range'
else:
self._status = IDLE, ''
self.read_status()
return value
def write_target(self, target): def write_target(self, target):
pass pass
def read_status(self):
return self._status if self.enabled else (DISABLED, 'disabled')
class Lower(Communicator): class Lower(Communicator):
"""Communicator returning a lowercase version of the request""" """Communicator returning a lowercase version of the request"""

View File

@ -1,397 +1,313 @@
# ***************************************************************************** # *****************************************************************************
# This program is free software; you can redistribute it and/or modify it under # 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 # 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 # Foundation; either version 2 of the License, or (at your option) any later
# version. # version.
# #
# This program is distributed in the hope that it will be useful, but WITHOUT # 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 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details. # details.
# #
# You should have received a copy of the GNU General Public License along with # 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., # this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# #
# Module authors: # Module authors:
# Damaris Tartarotti Maimone # Damaris Tartarotti Maimone
# Markus Zolliker <markus.zolliker@psi.ch> # Markus Zolliker <markus.zolliker@psi.ch>
# ***************************************************************************** # *****************************************************************************
"""Wrapper for the ADQ data acquisition card for ultrasound""" """Wrapper for the ADQ data acquisition card for ultrasound"""
import sys import sys
import atexit import atexit
import signal import signal
import time import time
import numpy as np import numpy as np
import ctypes as ct import ctypes as ct
from scipy.signal import butter, filtfilt from scipy.signal import butter, filtfilt
# For different trigger modes # For different trigger modes
SW_TRIG = 1 SW_TRIG = 1
# The following external trigger does not work if the level of the trigger is very close to 0.5V. # The following external trigger does not work if the level of the trigger is very close to 0.5V.
# Now we have it close to 3V, and it works # Now we have it close to 3V, and it works
EXT_TRIG_1 = 2 EXT_TRIG_1 = 2
EXT_TRIG_2 = 7 EXT_TRIG_2 = 7
EXT_TRIG_3 = 8 EXT_TRIG_3 = 8
LVL_TRIG = 3 LVL_TRIG = 3
INT_TRIG = 4 INT_TRIG = 4
LVL_FALLING = 0 LVL_FALLING = 0
LVL_RISING = 1 LVL_RISING = 1
ADQ_CLOCK_INT_INTREF = 0 # internal clock source ADQ_CLOCK_INT_INTREF = 0 # internal clock source
ADQ_CLOCK_EXT_REF = 1 # internal clock source, external reference ADQ_CLOCK_EXT_REF = 1 # internal clock source, external reference
ADQ_CLOCK_EXT_CLOCK = 2 # External clock source ADQ_CLOCK_EXT_CLOCK = 2 # External clock source
ADQ_TRANSFER_MODE_NORMAL = 0x00 ADQ_TRANSFER_MODE_NORMAL = 0x00
ADQ_CHANNELS_MASK = 0x3 ADQ_CHANNELS_MASK = 0x3
GHz = 1e9 GHz = 1e9
RMS_TO_VPP = 2 * np.sqrt(2)
class Adq:
class Timer: sample_rate = 2 * GHz
def __init__(self): max_number_of_channels = 2
self.data = [(time.time(), 'start')] ndecimate = 50 # decimation ratio (2GHz / 40 MHz)
number_of_records = 1
def __call__(self, text=''): samples_per_record = 16384
now = time.time() bw_cutoff = 10E6
prev = self.data[-1][0] trigger = EXT_TRIG_1
self.data.append((now, text)) adq_num = 1
return now - prev UNDEFINED = -1
IDLE = 0
def summary(self): BUSY = 1
return ' '.join(f'{txt} {tim:.3f}' for tim, txt in self.data[1:]) READY = 2
status = UNDEFINED
def show(self): data = None
first = prev = self.data[0][0]
print('---', first) def __init__(self):
for tim, txt in self.data[1:]: global ADQAPI
print(f'{(tim - first) * 1000:9.3f} {(tim - prev) * 1000:9.3f} ms {txt}') ADQAPI = ct.cdll.LoadLibrary("libadq.so.0")
prev = tim
ADQAPI.ADQAPI_GetRevision()
class Adq: # Manually set return type from some ADQAPI functions
sample_rate = 2 * GHz ADQAPI.CreateADQControlUnit.restype = ct.c_void_p
max_number_of_channels = 2 ADQAPI.ADQ_GetRevision.restype = ct.c_void_p
ndecimate = 50 # decimation ratio (2GHz / 40 MHz) ADQAPI.ADQ_GetPtrStream.restype = ct.POINTER(ct.c_int16)
number_of_records = 1 ADQAPI.ADQControlUnit_FindDevices.argtypes = [ct.c_void_p]
samples_per_record = 16384 # Create ADQControlUnit
bw_cutoff = 10E6 self.adq_cu = ct.c_void_p(ADQAPI.CreateADQControlUnit())
trigger = EXT_TRIG_1 ADQAPI.ADQControlUnit_EnableErrorTrace(self.adq_cu, 3, '.')
adq_num = 1
data = None # Find ADQ devices
busy = False ADQAPI.ADQControlUnit_FindDevices(self.adq_cu)
n_of_adq = ADQAPI.ADQControlUnit_NofADQ(self.adq_cu)
def __init__(self): if n_of_adq != 1:
global ADQAPI raise RuntimeError('number of ADQs must be 1, not %d' % n_of_adq)
ADQAPI = ct.cdll.LoadLibrary("libadq.so.0")
rev = ADQAPI.ADQ_GetRevision(self.adq_cu, self.adq_num)
ADQAPI.ADQAPI_GetRevision() revision = ct.cast(rev, ct.POINTER(ct.c_int))
print('\nConnected to ADQ #1')
# Manually set return type from some ADQAPI functions # Print revision information
ADQAPI.CreateADQControlUnit.restype = ct.c_void_p print('FPGA Revision: {}'.format(revision[0]))
ADQAPI.ADQ_GetRevision.restype = ct.c_void_p if revision[1]:
ADQAPI.ADQ_GetPtrStream.restype = ct.POINTER(ct.c_int16) print('Local copy')
ADQAPI.ADQControlUnit_FindDevices.argtypes = [ct.c_void_p] else:
# Create ADQControlUnit print('SVN Managed')
self.adq_cu = ct.c_void_p(ADQAPI.CreateADQControlUnit()) if revision[2]:
ADQAPI.ADQControlUnit_EnableErrorTrace(self.adq_cu, 3, '.') print('Mixed Revision')
else:
# Find ADQ devices print('SVN Updated')
ADQAPI.ADQControlUnit_FindDevices(self.adq_cu) print('')
n_of_adq = ADQAPI.ADQControlUnit_NofADQ(self.adq_cu)
if n_of_adq != 1: ADQAPI.ADQ_SetClockSource(self.adq_cu, self.adq_num, ADQ_CLOCK_EXT_REF)
print('number of ADQs must be 1, not %d' % n_of_adq)
print('it seems the ADQ was not properly closed') ##########################
print('please try again or reboot') # Test pattern
sys.exit(0) # ADQAPI.ADQ_SetTestPatternMode(self.adq_cu, self.adq_num, 4)
atexit.register(self.deletecu) ##########################
signal.signal(signal.SIGTERM, lambda *_: sys.exit(0)) # Sample skip
# ADQAPI.ADQ_SetSampleSkip(self.adq_cu, self.adq_num, 1)
rev = ADQAPI.ADQ_GetRevision(self.adq_cu, self.adq_num) ##########################
revision = ct.cast(rev, ct.POINTER(ct.c_int))
out = [f'Connected to ADQ #1, FPGA Revision: {revision[0]}'] # set trigger mode
if revision[1]: if not ADQAPI.ADQ_SetTriggerMode(self.adq_cu, self.adq_num, self.trigger):
out.append('Local copy') raise RuntimeError('ADQ_SetTriggerMode failed.')
else: if self.trigger == LVL_TRIG:
if revision[2]: if not ADQAPI.ADQ_SetLvlTrigLevel(self.adq_cu, self.adq_num, -100):
out.append('SVN Managed - Mixed Revision') raise RuntimeError('ADQ_SetLvlTrigLevel failed.')
else: if not ADQAPI.ADQ_SetTrigLevelResetValue(self.adq_cu, self.adq_num, 1000):
out.append('SVN Updated') raise RuntimeError('ADQ_SetTrigLevelResetValue failed.')
print(', '.join(out)) if not ADQAPI.ADQ_SetLvlTrigChannel(self.adq_cu, self.adq_num, 1):
ADQAPI.ADQ_SetClockSource(self.adq_cu, self.adq_num, ADQ_CLOCK_EXT_REF) raise RuntimeError('ADQ_SetLvlTrigChannel failed.')
if not ADQAPI.ADQ_SetLvlTrigEdge(self.adq_cu, self.adq_num, LVL_RISING):
########################## raise RuntimeError('ADQ_SetLvlTrigEdge failed.')
# Test pattern elif self.trigger == EXT_TRIG_1:
# ADQAPI.ADQ_SetTestPatternMode(self.adq_cu, self.adq_num, 4) if not ADQAPI.ADQ_SetExternTrigEdge(self.adq_cu, self.adq_num, 2):
########################## raise RuntimeError('ADQ_SetLvlTrigEdge failed.')
# Sample skip # if not ADQAPI.ADQ_SetTriggerThresholdVoltage(self.adq_cu, self.adq_num, trigger, ct.c_double(0.2)):
# ADQAPI.ADQ_SetSampleSkip(self.adq_cu, self.adq_num, 1) # raise RuntimeError('SetTriggerThresholdVoltage failed.')
########################## print("CHANNEL:"+str(ct.c_int(ADQAPI.ADQ_GetLvlTrigChannel(self.adq_cu, self.adq_num))))
atexit.register(self.deletecu)
# set trigger mode signal.signal(signal.SIGTERM, lambda *_: sys.exit(0))
if not ADQAPI.ADQ_SetTriggerMode(self.adq_cu, self.adq_num, self.trigger):
raise RuntimeError('ADQ_SetTriggerMode failed.') def init(self, samples_per_record=None, number_of_records=None):
if self.trigger == LVL_TRIG: """initialize dimensions"""
if not ADQAPI.ADQ_SetLvlTrigLevel(self.adq_cu, self.adq_num, -100): if samples_per_record:
raise RuntimeError('ADQ_SetLvlTrigLevel failed.') self.samples_per_record = samples_per_record
if not ADQAPI.ADQ_SetTrigLevelResetValue(self.adq_cu, self.adq_num, 1000): if number_of_records:
raise RuntimeError('ADQ_SetTrigLevelResetValue failed.') self.number_of_records = number_of_records
if not ADQAPI.ADQ_SetLvlTrigChannel(self.adq_cu, self.adq_num, 1): # Setup target buffers for data
raise RuntimeError('ADQ_SetLvlTrigChannel failed.') self.target_buffers = (ct.POINTER(ct.c_int16 * self.samples_per_record * self.number_of_records)
if not ADQAPI.ADQ_SetLvlTrigEdge(self.adq_cu, self.adq_num, LVL_RISING): * self.max_number_of_channels)()
raise RuntimeError('ADQ_SetLvlTrigEdge failed.') for bufp in self.target_buffers:
elif self.trigger == EXT_TRIG_1: bufp.contents = (ct.c_int16 * self.samples_per_record * self.number_of_records)()
if not ADQAPI.ADQ_SetExternTrigEdge(self.adq_cu, self.adq_num, 2):
raise RuntimeError('ADQ_SetLvlTrigEdge failed.') def deletecu(self):
# if not ADQAPI.ADQ_SetTriggerThresholdVoltage(self.adq_cu, self.adq_num, trigger, ct.c_double(0.2)): # Only disarm trigger after data is collected
# raise RuntimeError('SetTriggerThresholdVoltage failed.') ADQAPI.ADQ_DisarmTrigger(self.adq_cu, self.adq_num)
# proabably the folloiwng is wrong. ADQAPI.ADQ_MultiRecordClose(self.adq_cu, self.adq_num)
# print("CHANNEL:" + str(ct.c_int(ADQAPI.ADQ_GetLvlTrigChannel(self.adq_cu, self.adq_num)))) # Delete ADQControlunit
ADQAPI.DeleteADQControlUnit(self.adq_cu)
def init(self, samples_per_record=None, number_of_records=None):
"""initialize dimensions and store result object""" def start(self):
if samples_per_record: # Start acquisition
self.samples_per_record = samples_per_record ADQAPI.ADQ_MultiRecordSetup(self.adq_cu, self.adq_num,
if number_of_records: self.number_of_records,
self.number_of_records = number_of_records self.samples_per_record)
# Setup target buffers for data
self.target_buffers = (ct.POINTER(ct.c_int16 * self.samples_per_record * self.number_of_records) ADQAPI.ADQ_DisarmTrigger(self.adq_cu, self.adq_num)
* self.max_number_of_channels)() ADQAPI.ADQ_ArmTrigger(self.adq_cu, self.adq_num)
for bufp in self.target_buffers: self.status = self.BUSY
bufp.contents = (ct.c_int16 * self.samples_per_record * self.number_of_records)()
def get_status(self):
def deletecu(self): """check if ADQ card is busy"""
cu = self.__dict__.pop('adq_cu', None) if self.status == self.BUSY:
if cu is None: if ADQAPI.ADQ_GetAcquiredAll(self.adq_cu, self.adq_num):
return self.status = self.READY
print('shut down ADQ') else:
# Only disarm trigger after data is collected if self.trigger == SW_TRIG:
ADQAPI.ADQ_DisarmTrigger(cu, self.adq_num) ADQAPI.ADQ_SWTrig(self.adq_cu, self.adq_num)
ADQAPI.ADQ_MultiRecordClose(cu, self.adq_num) return self.status
# Delete ADQControlunit
ADQAPI.DeleteADQControlUnit(cu) def get_data(self, dataclass, **kwds):
print('ADQ closed') """when ready, get raw data from card, else return cached data
def start(self, data): return
# Start acquisition """
ADQAPI.ADQ_MultiRecordSetup(self.adq_cu, self.adq_num, if self.get_status() == self.READY:
self.number_of_records, # Get data from ADQ
self.samples_per_record) if not ADQAPI.ADQ_GetData(self.adq_cu, self.adq_num, self.target_buffers,
self.samples_per_record * self.number_of_records, 2,
ADQAPI.ADQ_DisarmTrigger(self.adq_cu, self.adq_num) 0, self.number_of_records, ADQ_CHANNELS_MASK,
ADQAPI.ADQ_ArmTrigger(self.adq_cu, self.adq_num) 0, self.samples_per_record, ADQ_TRANSFER_MODE_NORMAL):
self.data = data raise RuntimeError('no success from ADQ_GetDATA')
self.data = dataclass(self, **kwds)
def get_data(self): self.status = self.IDLE
"""get new data if available""" if self.status == self.UNDEFINED:
ready = False raise RuntimeError('no data available yet')
data = self.data return self.data
if not data:
self.busy = False
return None # no new data class PEdata:
def __init__(self, adq):
if ADQAPI.ADQ_GetAcquiredAll(self.adq_cu, self.adq_num): self.sample_rate = adq.sample_rate
ready = True self.samp_freq = self.sample_rate / GHz
data.timer('ready') self.number_of_records = adq.number_of_records
else: data = []
if self.trigger == SW_TRIG: for ch in range(2):
ADQAPI.ADQ_SWTrig(self.adq_cu, self.adq_num) onedim = np.frombuffer(adq.target_buffers[ch].contents, dtype=np.int16)
if not ready: data.append(onedim.reshape(adq.number_of_records, adq.samples_per_record) / float(2**14)) # 14 bits ADC
self.busy = True # Now this is an array with all records, but the time is artificial
return None self.data = data
self.data = None
t = time.time() def sinW(self, sig, freq, ti, tf):
# Get data from ADQ # sig: signal array
if not ADQAPI.ADQ_GetData( # freq
self.adq_cu, self.adq_num, self.target_buffers, # ti, tf: initial and end time
self.samples_per_record * self.number_of_records, 2, si = int(ti * self.samp_freq)
0, self.number_of_records, ADQ_CHANNELS_MASK, nperiods = freq * (tf - ti)
0, self.samples_per_record, ADQ_TRANSFER_MODE_NORMAL): n = int(round(max(2, int(nperiods)) / nperiods * (tf-ti) * self.samp_freq))
raise RuntimeError('no success from ADQ_GetDATA') self.nperiods = n
data.retrieve(self) t = np.arange(si, len(sig)) / self.samp_freq
return data t = t[:n]
self.pulselen = n / self.samp_freq
sig = sig[si:si+n]
class PEdata: a = 2*np.sum(sig*np.cos(2*np.pi*freq*t))/len(sig)
def __init__(self, adq): b = 2*np.sum(sig*np.sin(2*np.pi*freq*t))/len(sig)
self.sample_rate = adq.sample_rate return a, b
self.samp_freq = self.sample_rate / GHz
self.number_of_records = adq.number_of_records def mix(self, sigin, sigout, freq, ti, tf):
self.timer = Timer() # sigin, sigout: signal array, incomping, output
# freq
def retrieve(self, adq): # ti, tf: initial and end time of sigin
data = [] a, b = self.sinW(sigin, freq, ti, tf)
rawsignal = [] amp = np.sqrt(a**2 + b**2)
for ch in range(2): a, b = a/amp, b/amp
onedim = np.frombuffer(adq.target_buffers[ch].contents, dtype=np.int16) # si = int(ti * self.samp_freq)
rawsignal.append(onedim[:adq.samples_per_record]) t = np.arange(len(sigout)) / self.samp_freq
# convert 16 bit int to a value in the range -1 .. 1 wave1 = sigout * (a * np.cos(2*np.pi*freq*t) + b * np.sin(2*np.pi*freq*t))
data.append(onedim.reshape(adq.number_of_records, adq.samples_per_record) / float(2 ** 15)) wave2 = sigout * (a * np.sin(2*np.pi*freq*t) - b * np.cos(2*np.pi*freq*t))
# Now this is an array with all records, but the time is artificial return wave1, wave2
self.data = data
self.rawsignal = rawsignal def averageiq(self, data, freq, ti, tf):
self.timer('retrieved') """Average over records"""
iorq = np.array([self.mix(data[0][i], data[1][i], freq, ti, tf) for i in range(self.number_of_records)])
def sinW(self, sig, freq, ti, tf): return iorq.sum(axis=0) / self.number_of_records
# sig: signal array
# freq def filtro(self, iorq, cutoff):
# ti, tf: initial and end time # butter lowpass
si = int(ti * self.samp_freq) nyq = 0.5 * self.sample_rate
nperiods = freq * (tf - ti) normal_cutoff = cutoff / nyq
n = int(round(max(2, int(nperiods)) / nperiods * (tf-ti) * self.samp_freq)) order = 5
self.nperiods = n b, a = butter(order, normal_cutoff, btype='low', analog=False)
t = np.arange(si, len(sig)) / self.samp_freq iqf = [filtfilt(b, a, iorq[i]) for i in np.arange(len(iorq))]
t = t[:n] return iqf
self.pulselen = n / self.samp_freq
sig = sig[si:si+n] def box(self, iorq, ti, tf):
a = 2*np.sum(sig*np.cos(2*np.pi*freq*t))/len(sig) si = int(self.samp_freq * ti)
b = 2*np.sum(sig*np.sin(2*np.pi*freq*t))/len(sig) sf = int(self.samp_freq * tf)
return a, b bxa = [sum(iorq[i][si:sf])/(sf-si) for i in np.arange(len(iorq))]
return bxa
def mix(self, sigin, sigout, freq, ti, tf):
# sigin, sigout: signal array, incomping, output def gates_and_curves(self, freq, pulse, roi, bw_cutoff):
# freq """return iq values of rois and prepare plottable curves for iq"""
# ti, tf: initial and end time of sigin self.ndecimate = int(round(self.sample_rate / freq))
a, b = self.sinW(sigin, freq, ti, tf) # times = []
amp = np.sqrt(a**2 + b**2) # times.append(('aviq', time.time()))
a, b = a/amp, b/amp iq = self.averageiq(self.data, freq / GHz, *pulse)
# si = int(ti * self.samp_freq) # times.append(('filtro', time.time()))
t = np.arange(len(sigout)) / self.samp_freq iqf = self.filtro(iq, bw_cutoff)
wave1 = sigout * (a * np.cos(2*np.pi*freq*t) + b * np.sin(2*np.pi*freq*t)) m = len(iqf[0]) // self.ndecimate
wave2 = sigout * (a * np.sin(2*np.pi*freq*t) - b * np.cos(2*np.pi*freq*t)) ll = m * self.ndecimate
return wave1, wave2 iqf = [iqfx[0:ll] for iqfx in iqf]
# times.append(('iqdec', time.time()))
def averageiq(self, data, freq, ti, tf): iqd = np.average(np.resize(iqf, (2, m, self.ndecimate)), axis=2)
"""Average over records""" t_axis = np.arange(m) * self.ndecimate / self.samp_freq
iorq = np.array([self.mix(data[0][i], data[1][i], freq, ti, tf) for i in range(self.number_of_records)]) pulsig = np.abs(self.data[0][0])
return iorq.sum(axis=0) / self.number_of_records # times.append(('pulsig', time.time()))
pulsig = np.average(np.resize(pulsig, (m, self.ndecimate)), axis=1)
def filtro(self, iorq, cutoff): self.curves = (t_axis, iqd[0], iqd[1], pulsig)
# butter lowpass # print(times)
nyq = 0.5 * self.sample_rate return [self.box(iqf, *r) for r in roi]
normal_cutoff = cutoff / nyq
order = 5
b, a = butter(order, normal_cutoff, btype='low', analog=False) class RUSdata:
iqf = [filtfilt(b, a, iorq[i]) for i in np.arange(len(iorq))] def __init__(self, adq, freq, periods):
return iqf self.sample_rate = adq.sample_rate
self.freq = freq
def box(self, iorq, ti, tf): self.periods = periods
si = int(self.samp_freq * ti) self.samples_per_record = adq.samples_per_record
sf = int(self.samp_freq * tf) input_signal = np.frombuffer(adq.target_buffers[0].contents, dtype=np.int16)
bxa = [sum(iorq[i][si:sf])/(sf-si) for i in np.arange(len(iorq))] output_signal = np.frombuffer(adq.target_buffers[1].contents, dtype=np.int16)
return bxa complex_sinusoid = np.exp(1j * 2 * np.pi * self.freq / self.sample_rate * np.arange(len(input_signal)))
self.input_mixed = input_signal * complex_sinusoid
def gates_and_curves(self, freq, pulse, roi, bw_cutoff): self.output_mixed = output_signal * complex_sinusoid
"""return iq values of rois and prepare plottable curves for iq""" self.input_mean = self.input_mixed.mean()
self.timer('gates') self.output_mean = self.output_mixed.mean()
try: self.iq = self.output_mean / self.input_mean
self.ndecimate = int(round(self.sample_rate / freq))
except TypeError as e: def get_reduced(self, mixed):
raise TypeError(f'{self.sample_rate}/{freq} {e}') """get reduced array and normalize"""
iq = self.averageiq(self.data, freq / GHz, *pulse) nper = self.samples_per_record // self.periods
self.timer('aviq') mean = mixed.mean()
iqf = self.filtro(iq, bw_cutoff) return mixed[:self.period * nper].reshape((-1, nper)).mean(axis=0) / mean
self.timer('filtro')
m = max(1, len(iqf[0]) // self.ndecimate) def calc_quality(self):
ll = m * self.ndecimate """get signal quality info
iqf = [iqfx[0:ll] for iqfx in iqf]
self.timer('iqf') quality info (small values indicate good quality):
iqd = np.average(np.resize(iqf, (2, m, self.ndecimate)), axis=2) - input_std and output_std:
self.timer('avg') the imaginary part indicates deviations in phase
t_axis = np.arange(m) * self.ndecimate / self.samp_freq the real part indicates deviations in amplitude
pulsig = np.abs(self.data[0][0]) - input_slope and output_slope:
self.timer('pulsig') the imaginary part indicates a turning phase (rad/sec)
pulsig = np.average(np.resize(pulsig, (m, self.ndecimate)), axis=1) the real part indicates changes in amplitude (0.01 ~= 1%/sec)
result = ([self.box(iqf, *r) for r in roi], # gates """
(t_axis, iqd[0], iqd[1], pulsig)) # curves reduced = self.get_reduced(self.input_mixed)
self.timer('result') self.input_stdev = reduced.std()
# self.timer.show()
# ns = len(self.rawsignal[0]) * self.number_of_records reduced = self.get_reduced(self.output_mixed)
# print(f'{ns} {ns / 2e6} ms') timeaxis = np.arange(len(reduced)) * self.sample_rate / self.freq
return result self.output_slope = np.polyfit(timeaxis, reduced, 1)[0]
class Namespace:
"""holds channel or other data"""
def __init__(self, **kwds):
self.__dict__.update(**kwds)
class RUSdata:
def __init__(self, adq, freq, periods, delay_samples):
self.sample_rate = adq.sample_rate
self.freq = freq
self.periods = periods
self.delay_samples = delay_samples
self.samples_per_record = adq.samples_per_record
self.inp = Namespace(idx=0, name='input')
self.out = Namespace(idx=1, name='output')
self.channels = (self.inp, self.out)
self.timer = Timer()
def retrieve(self, adq):
self.timer('start retrieve')
npts = self.samples_per_record - self.delay_samples
nbin = max(1, npts // (self.periods * 60)) # for performance reasons, do the binning first
nreduced = npts // nbin
ft = 2 * np.pi * self.freq * nbin / self.sample_rate * np.arange(nreduced)
self.timer('create time axis')
# complex_sinusoid = np.exp(1j * ft) # do not use this, below is 33 % faster
complex_sinusoid = 1j * np.sin(ft) + np.cos(ft)
self.timer('sinusoid')
rawsignal = [] # for raw plot
for chan in self.channels: # looping over input and output
# although the ADC is only 14 bit it is represented as unsigend 16 bit numbers,
# and due to some calculations (calibration) the last 2 bits are not zero
beg = self.delay_samples
isignal = np.frombuffer(adq.target_buffers[chan.idx].contents, dtype=np.int16)[beg:beg+nreduced * nbin]
self.timer('isignal')
reduced = isignal.reshape((-1, nbin)).mean(axis=1) # this converts also int16 to float
self.timer('reduce')
rawsignal.append(reduced)
chan.signal = signal = reduced * 2 ** -16 # in V -> peak to peak 1 V ~ +- 0.5 V
self.timer('divide')
# calculate RMS * sqrt(2) -> peak sinus amplitude.
# may be higher than the input range by a factor 1.4 when heavily clipped
chan.amplitude = np.sqrt((signal ** 2).mean()) * RMS_TO_VPP
self.timer('amp')
chan.mixed = signal * complex_sinusoid
self.timer('mix')
chan.mean = chan.mixed.mean()
self.timer('mean')
self.rawsignal = rawsignal
if self.inp.mean:
self.iq = self.out.mean / self.inp.mean
else:
self.iq = 0
def get_quality(self):
"""get signal quality info
quality info (small values indicate good quality):
- input_stddev:
the imaginary part indicates deviations in phase
the real part indicates deviations in amplitude
- output_slope:
the imaginary part indicates a turning phase (rad/sec)
the real part indicates changes in amplitude (0.01 ~= 1%/sec)
"""
self.timer('get_quality')
npts = len(self.channels[0].signal)
nper = npts // self.periods
for chan in self.channels:
mean = chan.mixed.mean()
chan.reduced = chan.mixed[:self.periods * nper].reshape((-1, nper)).mean(axis=1) / mean
timeaxis = np.arange(len(self.out.reduced)) * self.sample_rate / self.freq
result = Namespace(
input_stddev=self.inp.reduced.std(),
output_slope=np.polyfit(timeaxis, self.out.reduced, 1)[0])
self.timer('got_quality')
self.timer.show()
ns = len(self.rawsignal[0])
print(f'{ns} {ns / 2e6} ms')
return result

View File

@ -1,131 +0,0 @@
# *****************************************************************************
#
# 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>
#
# *****************************************************************************
import time
from frappy.core import Attached, Readable, Writable, Parameter, Command, \
IDLE, BUSY, DISABLED, ERROR
from frappy.datatypes import FloatRange, StatusType, TupleOf, EnumType
from frappy.states import HasStates, Retry, status_code
class AutoFill(HasStates, Readable):
level = Attached(Readable)
valve = Attached(Writable)
status = Parameter(datatype=StatusType(Readable, 'BUSY'))
mode = Parameter('auto mode', EnumType(disabled=0, auto=30), readonly=False)
fill_level = Parameter('low threshold triggering start filling',
FloatRange(unit='%'), readonly=False)
full_level = Parameter('high threshold triggering stop filling',
FloatRange(unit='%'), readonly=False)
fill_minutes_range = Parameter('range of possible fill rate',
TupleOf(FloatRange(unit='min'), FloatRange(unit='min')),
readonly=False)
hold_hours_range = Parameter('range of possible consumption rate',
TupleOf(FloatRange(unit='h'), FloatRange(unit='h')),
readonly=False)
fill_delay = Parameter('delay for cooling the transfer line',
FloatRange(unit='min'), readonly=False)
def read_status(self):
if self.mode == 'DISABLED':
return DISABLED, ''
vstatus = self.valve.status
if vstatus[0] // 100 != IDLE // 100:
self.stop_machine(vstatus)
return vstatus
status = self.level.read_status(self)
if status[0] // 100 == IDLE // 100:
return HasStates.read_status(self)
self.stop_machine(status)
return status
def write_mode(self, mode):
if mode == 'DISABLED':
self.stop_machine((DISABLED, ''))
elif mode == 'AUTO':
self.start_machine(self.watching)
return mode
@status_code(BUSY)
def watching(self, state):
if state.init:
self.valve.write_target(0)
delta = state.delta(10)
raw = self.level.value
if raw > self.value:
self.value -= delta / (3600 * self.hold_hours_range[1])
elif raw < self.value:
self.value -= delta / (3600 * self.hold_hours_range[0])
else:
self.value = raw
if self.value < self.fill_level:
return self.precooling
return Retry
@status_code(BUSY)
def precooling(self, state):
if state.init:
state.fillstart = state.now
self.valve.write_target(1)
delta = state.delta(1)
raw = self.level.value
if raw > self.value:
self.value += delta / (60 * self.fill_minutes_range[0])
elif raw < self.value:
self.value -= delta / (60 * self.fill_minutes_range[0])
else:
self.value = raw
if self.value > self.full_level:
return self.watching
if state.now > state.fillstart + self.fill_delay * 60:
return self.filling
return Retry
@status_code(BUSY)
def filling(self, state):
delta = state.delta(1)
raw = self.level.value
if raw > self.value:
self.value += delta / (60 * self.fill_minutes_range[0])
elif raw < self.value:
self.value += delta / (60 * self.fill_minutes_range[1])
else:
self.value = raw
if self.value > self.full_level:
return self.watching
return Retry
def on_cleanup(self, state):
try:
self.valve.write_target(0)
except Exception:
pass
super().on_cleanup()
@Command()
def fill(self):
self.mode = 'AUTO'
self.start_machine(self.precooling, fillstart=time.time())
@Command()
def stop(self):
self.start_machine(self.watching)

View File

@ -66,9 +66,8 @@ class Power(HasIO, Readable):
class Output(HasIO, Writable): class Output(HasIO, Writable):
value = Parameter(datatype=FloatRange(0,100,unit='%'), default=0) value = Parameter(datatype=FloatRange(0,100,unit='%'))
target = Parameter(datatype=FloatRange(0,100,unit='%')) target = Parameter(datatype=FloatRange(0,100,unit='%'))
p_value = Parameter(datatype=FloatRange(0,100,unit='%'), default=0)
maxvolt = Parameter('voltage at 100%',datatype=FloatRange(0,60,unit='V'),default=50,readonly=False) maxvolt = Parameter('voltage at 100%',datatype=FloatRange(0,60,unit='V'),default=50,readonly=False)
maxcurrent = Parameter('current at 100%',datatype=FloatRange(0,5,unit='A'),default=2,readonly=False) maxcurrent = Parameter('current at 100%',datatype=FloatRange(0,5,unit='A'),default=2,readonly=False)
output_enable = Parameter('control on/off', BoolType(), readonly=False) output_enable = Parameter('control on/off', BoolType(), readonly=False)
@ -79,10 +78,8 @@ class Output(HasIO, Writable):
def write_target(self, target): def write_target(self, target):
self.write_output_enable(target != 0) self.write_output_enable(target != 0)
self.communicate(f'VOLT{round(max(8,(target)**0.5 * self.maxvolt)):03d}') self.communicate(f'VOLT{round(max(8,target*self.maxvolt/10)):03d}')
self.communicate(f'CURR{round((target)**0.5* 10 * self.maxcurrent):03d}') self.communicate(f'CURR{round(target*self.maxcurrent):03d}')
#self.communicate(f'VOLT{round(max(8,target*self.maxvolt/10)):03d}')
#self.communicate(f'CURR{round(target*self.maxcurrent):03d}')
self.value = target self.value = target
def write_output_enable(self, value): def write_output_enable(self, value):

View File

@ -1,128 +0,0 @@
# *****************************************************************************
#
# 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:
# M. Zolliker <markus.zolliker@psi.ch>
#
# *****************************************************************************
from frappy.core import Attached, Command, EnumType, FloatRange, \
Drivable, Parameter, BUSY, IDLE, ERROR
class Valve(Drivable):
motor = Attached(Drivable) # refers to motor module
value = Parameter('valve state',
EnumType(closed=0, open=1, undefined=2),
default=2)
status = Parameter() # inherit properties from Drivable
target = Parameter('valve target',
EnumType(closed=0, open=1),
readonly=False)
# TODO: convert to properties after tests
open_pos = Parameter('target position for open state', FloatRange(), readonly=False, default=80)
mid_pos = Parameter('position for changing speed', FloatRange(), readonly=False, default=5)
fast_speed = Parameter('normal speed', FloatRange(), readonly=False, default=40)
slow_speed = Parameter('reduced speed', FloatRange(), readonly=False, default=10)
__motor_target = None
__status = IDLE, ''
__value = 'undefined'
__drivestate = 0 # 2 when driving to intermediate target or on retry, 1 when driving to final target, 0 when idle
def doPoll(self):
mot = self.motor
motpos = mot.read_value()
scode, stext = mot.read_status()
drivestate = self.__drivestate
if scode >= ERROR:
if self.__drivestate and self.__remaining_tries > 0:
drivestate = 2
self.__remaining_tries -= 1
mot.reset()
mot.write_speed(self.slow_speed)
self.__status = BUSY, f'retry {self._action}'
else:
self.__status = ERROR, f'valve motor: {stext}'
elif scode < BUSY:
if self.__motor_target is not None and mot.target != self.__motor_target:
self.__status = ERROR, 'motor was driven directly'
elif drivestate == 2:
self.goto(self.target)
drivestate = 1
else:
if -3 < motpos < 3:
self.__value = 'closed'
self.__status = IDLE, ''
elif self.open_pos * 0.5 < motpos < self.open_pos * 1.5:
self.__value = 'open'
self.__status = IDLE, ''
else:
self.__status = ERROR, 'undefined'
if self.__drivestate and not self.isBusy(self.__status):
drivestate = 0
self.__motor_target = None
self.setFastPoll(False)
self.__drivestate = drivestate
self.read_status()
self.read_value()
def read_status(self):
return self.__status
def read_value(self):
if self.read_status()[0] >= BUSY:
return 'undefined'
return self.__value
def goto(self, target):
"""go to open, closed or intermediate position
the intermediate position is targeted when a speed change is needed
return 2 when a retry is needed, 1 else
"""
mot = self.motor
if target: # 'open'
self._action = 'opening'
if True or mot.value > self.mid_pos:
mot.write_speed(self.fast_speed)
self.__motor_target = mot.write_target(self.open_pos)
return 1
mot.write_speed(self.slow_speed)
self.__motor_target = mot.write_target(self.mid_pos)
return 2
self._action = 'closing'
if mot.value > self.mid_pos * 2:
mot.write_speed(self.fast_speed)
self.__motor_target = mot.write_target(self.mid_pos)
return 2
mot.write_speed(self.slow_speed)
self.__motor_target = mot.write_target(0)
return 1
def write_target(self, target):
self.__remaining_tries = 5
self.__drivestate = self.goto(target)
self.__status = BUSY, self._action
self.read_status()
self.read_value()
self.setFastPoll(True)
@Command() # python decorator to mark it as a command
def stop(self):
"""stop the motor -> value might get undefined"""
self.__drivestate = 0
self.motor.stop()

View File

@ -1,144 +1,125 @@
import os import os
from glob import glob
from pathlib import Path
from configparser import ConfigParser from configparser import ConfigParser
from frappy.errors import ConfigError
class Rack: class Lsc:
configbase = Path('/home/l_samenv/.config/frappy_instruments') def __init__(self, modfactory, ls_uri, ls_ioname='lsio', ls_devname='ls', ls_model='336', **kwds):
self.modfactory = Mod = modfactory
def __init__(self, modfactory, **kwds): self.model = ls_model
self.modfactory = modfactory self.ioname = ls_ioname
instpath = self.configbase / os.environ['Instrument'] self.devname = ls_devname
sections = {} self.io = Mod(self.ioname, cls=f'frappy_psi.lakeshore.IO{self.model}',
self.config = {} description='comm. to lakeshore in cc rack',
files = glob(str(instpath / '*.ini')) uri=ls_uri)
for filename in files: self.dev = Mod(self.devname, cls=f'frappy_psi.lakeshore.Device{self.model}',
parser = ConfigParser() description='lakeshore in cc rack', io=self.ioname, curve_handling=True)
parser.optionxform = str self.loops = {}
parser.read([filename]) self.outputs = {}
for section in parser.sections():
prev = sections.get(section)
if prev:
raise ConfigError(f'duplicate {section} section in {filename} and {prev}')
sections[section] = filename
self.config.update(parser.items(section))
if 'rack' not in sections:
raise ConfigError(f'no rack found in {instpath}')
self.props = {} # dict (<property>, <method>) of value
self.mods = {} # dict (<property>, <method>) of list of <cfg>
self.ccu_uri = {}
def set_props(self, mod, **kwds):
for prop, method in kwds.items():
value = self.props.get((prop, method))
if value is None:
# add mod to the list of cfgs to be fixed
self.mods.setdefault((prop, method), []).append(mod)
else:
# set prop in current module
if not mod.get(prop): # do not override given and not empty property
mod[prop] = value
def fix_props(self, method, **kwds):
for prop, value in kwds.items():
if (prop, method) in self.props:
raise ConfigError(f'duplicate call to {method}()')
self.props[prop, method] = value
# set property in modules to be fixed
for mod in self.mods.get((prop, method), ()):
mod[prop] = value
def lakeshore(self, ls_uri=None, io='ls_io', dev='ls', model='336', **kwds):
Mod = self.modfactory
self.fix_props('lakeshore', io=io, device=dev)
self.ls_model = model
self.ls_dev = dev
ls_uri = ls_uri or self.config.get('ls_uri')
Mod(io, cls=f'frappy_psi.lakeshore.IO{self.ls_model}',
description='comm. to lakeshore in cc rack', uri=ls_uri)
self.dev = Mod(dev, cls=f'frappy_psi.lakeshore.Device{self.ls_model}',
description='lakeshore in cc rack', io=io, curve_handling=True)
def sensor(self, name, channel, calcurve, **kwds): def sensor(self, name, channel, calcurve, **kwds):
Mod = self.modfactory Mod = self.modfactory
kwds.setdefault('cls', f'frappy_psi.lakeshore.Sensor{self.ls_model}') kwds.setdefault('cls', f'frappy_psi.lakeshore.Sensor{self.model}')
kwds.setdefault('description', f'T sensor {name}') kwds.setdefault('description', f'T sensor {name}')
mod = Mod(name, channel=channel, calcurve=calcurve, return Mod(name, channel=channel, calcurve=calcurve,
device=self.ls_dev, **kwds) io=self.ioname, device=self.devname, **kwds)
self.set_props(mod, io='lakeshore', dev='lakeshore')
def loop(self, name, channel, calcurve, output_module, **kwds): def loop(self, name, channel, calcurve, **kwds):
Mod = self.modfactory Mod = self.modfactory
kwds.setdefault('cls', f'frappy_psi.lakeshore.Loop{self.ls_model}') kwds.setdefault('cls', f'frappy_psi.lakeshore.Loop{self.model}')
kwds.setdefault('description', f'T loop {name}') kwds.setdefault('description', f'T loop {name}')
Mod(name, channel=channel, calcurve=calcurve, output_module=output_module, mod = Mod(name, channel=channel, calcurve=calcurve,
device=self.ls_dev, **kwds) io=self.ioname, device=self.devname, **kwds)
self.fix_props(f'heater({output_module})', description=f'heater for {name}') self.loops[name] = mod
return mod
def heater(self, name, output_no, max_heater, resistance, **kwds): def heater(self, name, max_heater, resistance, output_no=1, **kwds):
Mod = self.modfactory Mod = self.modfactory
if output_no == 1: if output_no == 1:
kwds.setdefault('cls', f'frappy_psi.lakeshore.MainOutput{self.ls_model}') kwds.setdefault('cls', f'frappy_psi.lakeshore.MainOutput{self.model}')
elif output_no == 2: elif output_no == 2:
kwds.setdefault('cls', f'frappy_psi.lakeshore.SecondaryOutput{self.ls_model}') kwds.setdefault('cls', f'frappy_psi.lakeshore.SecondaryOutput{self.model}')
else: else:
return return
kwds.setdefault('description', '') kwds.setdefault('description', '')
mod = Mod(name, max_heater=max_heater, resistance=resistance, **kwds) mod = Mod(name, max_heater=max_heater, resistance=resistance,
self.set_props(mod, io='lakeshore', device='lakeshore', description=f'heater({name})') io=self.ioname, device=self.devname, **kwds)
self.outputs[name] = mod
return mod
def ccu(self, name=None, ccu_uri=None, ccu_io='ccu_io', args_for_io=None, **kwds): def __enter__(self):
if args_for_io is None: return self
args_for_io, kwds = kwds, {}
prev_uri = self.ccu_uri.get(ccu_io)
ccu_uri = ccu_uri or self.config.get('ccu_uri')
if prev_uri:
if prev_uri == ccu_uri:
return kwds # already configured
raise ConfigError(f'rack.{name or "ccu"}: ccu_uri {prev_uri} does not match {ccu_uri}')
self.ccu_uri[ccu_io] = ccu_uri
self.modfactory(ccu_io, 'frappy_psi.ccu4.IO', 'comm. to CCU4', uri=ccu_uri, **args_for_io)
return kwds
def he(self, name='He_lev', ccu_io='ccu_io', **kwds): def __exit__(self, exc_type, exc_val, exc_tb):
self.ccu('he', ccu_io=ccu_io, args_for_io={}, **kwds) outmodules = dict(self.outputs)
self.modfactory(name, cls='frappy_psi.ccu4.HeLevel', for name, loop in self.loops.items():
description='the He Level', io=ccu_io, **kwds) outname = loop.get('output_module')
if outname:
out = outmodules.pop(outname, None)
if not out:
raise KeyError(f'{outname} is not a output module in this lakeshore')
else:
if not outmodules:
raise KeyError(f'{name} needs an output module on this lakeshore')
outname = list(outmodules)[0]
out = outmodules.pop(outname)
loop['output_module'] = outname
if not out['description']:
out['description'] = f'heater for {outname}'
def n2(self, name='N2_lev', valve='N2_valve', upper='N2_upper', lower='N2_lower', ccu_io='ccu_io', **kwds):
self.ccu('n2', ccu_io=ccu_io, args_for_io={}, **kwds)
Mod = self.modfactory
Mod(name, cls='frappy_psi.ccu4.N2Level',
description='the N2 Level', io=ccu_io,
valve=valve, upper=upper, lower=lower)
Mod(valve, cls='frappy_psi.ccu4.N2FillValve',
description='LN2 fill valve', io=ccu_io)
Mod(upper, cls='frappy_psi.ccu4.N2TempSensor',
description='upper LN2 sensor')
Mod(lower, cls='frappy_psi.ccu4.N2TempSensor',
description='lower LN2 sensor')
def flow(self, hepump_uri=None, hepump_type=None, hepump_io='hepump_io', class CCU:
hepump='hepump', hepump_mot='hepump_mot', hepump_valve='hepump_valve', def __init__(self, Mod, ccu_uri, ccu_ioname='ccuio', ccu_devname='ccu', he=None, n2=None, **kwds):
flow_sensor='flow_sensor', pump_pressure='pump_pressure', nv='nv', self.ioname = ccu_ioname
ccu_io='ccu_io', **kwds): self.devname = ccu_devname
"""creates needle valve and pump access if available""" Mod(self.ioname, 'frappy_psi.ccu4.CCU4IO',
kwds = self.ccu('flow', ccu_io=ccu_io, args_for_io={}, **kwds) 'comm. to CCU4', uri=ccu_uri)
Mod = self.modfactory if he:
hepump_type = hepump_type or self.config.get('hepump_type', 'no') if not isinstance(he, str): # e.g. True
Mod(nv, 'frappy_psi.ccu4.NeedleValveFlow', 'flow from flow sensor or pump pressure', he = 'He_lev'
flow_sensor=flow_sensor, pressure=pump_pressure, io=ccu_io, **kwds) Mod(he, cls='frappy_psi.ccu4.HeLevel',
Mod(pump_pressure, 'frappy_psi.ccu4.Pressure', 'He pump pressure', io=ccu_io) description='the He Level', io=self.ioname)
if hepump_type == 'no': if n2:
print('no pump, no flow meter - using flow from pressure alone') if isinstance(n2, str):
return n2 = n2.split(',')
hepump_uri = hepump_uri or self.config['hepump_uri'] else: # e.g. True
Mod(hepump_io, 'frappy.io.BytesIO', 'He pump connection', uri=hepump_uri) n2 = []
Mod(hepump, 'frappy_psi.hepump.HePump', 'He pump', pump_type=hepump_type, n2, valve, upper, lower = n2 + ['N2_lev', 'N2_valve', 'N2_upper', 'N2_lower'][len(n2):]
valvemotor=hepump_mot, valve=hepump_valve, flow=nv) print(n2, valve, upper, lower)
Mod(hepump_mot, 'frappy_psi.hepump.Motor', 'He pump valve motor', io=hepump_io, maxcurrent=2.8) Mod(n2, cls='frappy_psi.ccu4.N2Level',
Mod(hepump_valve, 'frappy_psi.butterflyvalve.Valve', 'He pump valve', motor=hepump_mot) description='the N2 Level', io=self.ioname,
Mod(flow_sensor, 'frappy_psi.sensirion.FlowSensor', 'Flow Sensor', io=hepump_io, nsamples=160) valve=valve, upper=upper, lower=lower)
Mod(valve, cls='frappy_psi.ccu4.N2FillValve',
description='LN2 fill valve', io=self.ioname)
Mod(upper, cls='frappy_psi.ccu4.N2TempSensor',
description='upper LN2 sensor')
Mod(lower, cls='frappy_psi.ccu4.N2TempSensor',
description='lower LN2 sensor')
class HePump:
def __init__(self, Mod, hepump_uri, hepump_io='hepump_io', hemotname='hepump_mot', **kwds):
Mod(hepump_io, 'frappy_psi.trinamic.BytesIO', 'He pump connection', uri=hepump_uri)
Mod(hemotname, 'frappy_psi.trinamic.Motor', 'He pump valve motor', io=hepump_io)
class Rack:
rackfile = '/home/l_samenv/.config/racks.ini'
def __init__(self, modfactory, **kwds):
self.modfactory = modfactory
parser = ConfigParser()
parser.optionxform = str
parser.read([self.rackfile])
kwds.update(parser.items(os.environ['Instrument']))
self.kwds = kwds
def lakeshore(self):
return Lsc(self.modfactory, **self.kwds)
def ccu(self, **kwds):
kwds.update(self.kwds)
return CCU(self.modfactory, **kwds)
def hepump(self):
return HePump(self.modfactory, **self.kwds)

View File

@ -22,24 +22,21 @@
"""drivers for CCU4, the cryostat control unit at SINQ""" """drivers for CCU4, the cryostat control unit at SINQ"""
import time import time
import math import math
import numpy as np
from frappy.lib.enum import Enum from frappy.lib.enum import Enum
from frappy.lib import clamp, formatExtendedTraceback
from frappy.lib.interpolation import Interpolation
# the most common Frappy classes can be imported from frappy.core # the most common Frappy classes can be imported from frappy.core
from frappy.core import HasIO, Parameter, Command, Readable, Writable, Drivable, \ from frappy.core import HasIO, Parameter, Command, Readable, Writable, Drivable, \
Property, StringIO, BUSY, IDLE, WARN, ERROR, DISABLED, Attached, nopoll Property, StringIO, BUSY, IDLE, WARN, ERROR, DISABLED, Attached
from frappy.datatypes import BoolType, EnumType, FloatRange, StructOf, \ from frappy.datatypes import BoolType, EnumType, FloatRange, StructOf, \
StatusType, IntRange, StringType, TupleOf, ArrayOf StatusType, IntRange, StringType, TupleOf
from frappy.errors import CommunicationFailedError from frappy.errors import CommunicationFailedError
from frappy.states import HasStates, status_code, Retry from frappy.states import HasStates, status_code, Retry
M = Enum(idle=0, opening=1, closing=2, opened=3, closed=4, no_motor=5) M = Enum(idle=0, opening=1, closing=2, opened=3, closed=5, no_motor=6)
A = Enum(disabled=0, manual=1, auto=2) A = Enum(disabled=0, manual=1, auto=2)
class IO(StringIO): class CCU4IO(StringIO):
"""communication with CCU4""" """communication with CCU4"""
# for completeness: (not needed, as it is the default) # for completeness: (not needed, as it is the default)
end_of_line = '\n' end_of_line = '\n'
@ -47,8 +44,8 @@ class IO(StringIO):
identification = [('cid', r'cid=CCU4.*')] identification = [('cid', r'cid=CCU4.*')]
class Base(HasIO): class CCU4Base(HasIO):
ioClass = IO ioClass = CCU4IO
def command(self, **kwds): def command(self, **kwds):
"""send a command and get the response """send a command and get the response
@ -82,7 +79,7 @@ class Base(HasIO):
return result return result
class HeLevel(Base, Readable): class HeLevel(CCU4Base, Readable):
"""He Level channel of CCU4""" """He Level channel of CCU4"""
value = Parameter(unit='%') value = Parameter(unit='%')
@ -124,10 +121,10 @@ class HeLevel(Base, Readable):
return self.command(hfu=value) return self.command(hfu=value)
class Valve(Base, Writable): class Valve(CCU4Base, Writable):
value = Parameter('relay state', BoolType()) value = Parameter('relay state', BoolType())
target = Parameter('relay target', BoolType()) target = Parameter('relay target', BoolType())
ioClass = IO ioClass = CCU4IO
STATE_MAP = {0: (0, (IDLE, 'off')), STATE_MAP = {0: (0, (IDLE, 'off')),
1: (1, (IDLE, 'on')), 1: (1, (IDLE, 'on')),
2: (0, (ERROR, 'no valve')), 2: (0, (ERROR, 'no valve')),
@ -176,7 +173,7 @@ class N2TempSensor(Readable):
value = Parameter('LN2 T sensor', FloatRange(unit='K'), default=0) value = Parameter('LN2 T sensor', FloatRange(unit='K'), default=0)
class N2Level(Base, Readable): class N2Level(CCU4Base, Readable):
valve = Attached(Writable, mandatory=False) valve = Attached(Writable, mandatory=False)
lower = Attached(Readable, mandatory=False) lower = Attached(Readable, mandatory=False)
upper = Attached(Readable, mandatory=False) upper = Attached(Readable, mandatory=False)
@ -186,9 +183,9 @@ class N2Level(Base, Readable):
mode = Parameter('auto mode', EnumType(A), readonly=False, default=A.manual) mode = Parameter('auto mode', EnumType(A), readonly=False, default=A.manual)
threshold = Parameter('threshold triggering start/stop filling', threshold = Parameter('threshold triggering start/stop filling',
FloatRange(unit='K'), readonly=False, default=90) FloatRange(unit='K'), readonly=False)
cool_delay = Parameter('max. minutes needed to cool the lower sensor', cool_delay = Parameter('max. minutes needed to cool the lower sensor',
FloatRange(unit='s'), readonly=False, default=30) FloatRange(unit='s'), readonly=False)
fill_timeout = Parameter('max. minutes needed to fill', fill_timeout = Parameter('max. minutes needed to fill',
FloatRange(unit='s'), readonly=False) FloatRange(unit='s'), readonly=False)
names = Property('''names of attached modules names = Property('''names of attached modules
@ -288,515 +285,189 @@ class N2Level(Base, Readable):
self.command(nc=0) self.command(nc=0)
class N2LevelGuess(N2Level): class FlowPressure(CCU4Base, Readable):
"""guess the current level from hold time"""
value = Parameter('estimated level', FloatRange(unit='%'), default=20)
fill_time = Parameter('min fill time - for raw level indicator',
FloatRange(unit='s'), default=600)
hold_time = Parameter('min hold time - for raw level indicator',
FloatRange(unit='s'), default=24 * 3600)
_full_since = None
_empty_since = None
_fill_state = '' # may also be 'empty', 'full' or 'unknown'
_lower = 0
_upper = 0
def read_status(self):
status = super().read_status()
if status == (IDLE, ''):
return IDLE, self._fill_state
return status
def read_value(self):
# read sensors
now = time.time()
lower, upper = self.command(nl=float, nu=float)
if self.lower:
self.lower.value = lower
if self.upper:
self.upper.value = upper
if upper < self.threshold:
self._full_since = now
if self._empty_since is not None:
self.fill_time = now - self._empty_since
self._empty_since = None
self._fill_state = 'full'
return 100
if lower < self.threshold:
if self._empty_since is None:
if self._full_since is None:
self._fill_state = 'unknown'
return 20
delay = now - self._full_since
value = max(10, 100 * 1 - delay / self.hold_time)
if value < 99:
self._fill_state = ''
return value
delay = now - self._empty_since - self.cool_delay
value = min(90, 100 * max(0, delay / self.fill_time))
if value >= 10:
self._fill_state = ''
return value
if self._full_since is not None:
self.hold_time = now - self._full_since
self._full_since = None
self.log.info('lower %g upper %g threshold %g', lower, upper, self.threshold)
self._empty_since = now
self._fill_state = 'empty'
return 0
class HasFilter:
__value1 = None
__value = None
__last = None
def filter(self, filter_time, value):
now = time.time()
if self.__value is None:
self.__last = now
self.__value1 = value
self.__value = value
weight = (now - self.__last) / filter_time
self.__value1 += weight * (value - self.__value)
self.__value += weight * (self.__value1 - self.__value)
self.__last = now
return self.__value
class Pressure(HasFilter, Base, Readable):
value = Parameter(unit='mbar') value = Parameter(unit='mbar')
mbar_offset = Parameter('offset in mbar', FloatRange(unit='mbar'), default=0.8, readonly=False) mbar_offset = Parameter(unit='mbar', default=0.8, readonly=False)
filter_time = Parameter('filter time', FloatRange(unit='sec'), readonly=False, default=3)
pollinterval = Parameter(default=0.25) pollinterval = Parameter(default=0.25)
def read_value(self): def read_value(self):
return self.filter(self.filter_time, self.command(f=float)) - self.mbar_offset return self.filter(self.command(f=float)) - self.mbar_offset
def Table(miny=None, maxy=None): class NeedleValve(HasStates, CCU4Base, Drivable):
return ArrayOf(TupleOf(FloatRange(), FloatRange(miny, maxy))) flow = Attached(Readable, mandatory=False)
flow_pressure = Attached(Readable, mandatory=False)
class NeedleValveFlow(HasStates, Base, Drivable):
flow_sensor = Attached(Readable, mandatory=False)
pressure = Attached(Pressure, mandatory=False)
use_pressure = Parameter('flag (use pressure instead of flow meter)', BoolType(),
readonly=False, default=False)
lnm_per_mbar = Parameter('scale factor', FloatRange(unit='lnm/mbar'), readonly=False, default=0.6)
pump_type = Parameter('pump type', EnumType(unknown=0, neodry=1, xds35=2, sv65=3),
readonly=False, value=0)
value = Parameter(unit='ln/min') value = Parameter(unit='ln/min')
target = Parameter(unit='ln/min') target = Parameter(unit='ln/min')
motor_state = Parameter('motor_state', EnumType(M), default=0) lnm_per_mbar = Parameter(unit='ln/min/mbar', default=0.6, readonly=False)
speed = Parameter('speed moving time / passed time', FloatRange()) use_pressure = Parameter('use flow from pressure', BoolType(),
tolerance = Parameter('tolerance', Table(0), value=[(2,0.1),(4,0.4)], readonly=False) default=False, readonly=False)
prop_open = Parameter('proportional term for opening', Table(0), readonly=False, value=[(1,0.05)]) motor_state = Parameter('motor_state', EnumType(M))
prop_close = Parameter('proportional term for closing', Table(0), readonly=False, value=[(1,0.02)]) tolerance = Parameter('tolerance', FloatRange(0), value=0.25, readonly=False)
tolerance2 = Parameter('tolerance limit above 2 lnm', FloatRange(0), value=0.5, readonly=False)
prop = Parameter('proportional term', FloatRange(unit='s/lnm'), readonly=False)
deriv = Parameter('min progress time constant', FloatRange(unit='s'), deriv = Parameter('min progress time constant', FloatRange(unit='s'),
default=30, readonly=False) default=30, readonly=False)
control_active = Parameter('control active flag', BoolType(), readonly=False, default=1) settle = Parameter('time within tolerance before getting quiet', FloatRange(unit='s'),
min_open_pulse = Parameter('minimal open step', FloatRange(0, unit='s'), default=30, readonly=False)
readonly=False, default=0.02) step_factor = Parameter('factor (no progress time) / (min step size)', FloatRange(), default=300)
min_close_pulse = Parameter('minimal close step', FloatRange(0, unit='s'), control_active = Parameter('control active flag', BoolType(), readonly=False)
readonly=False, default=0.0) pollinterval = Parameter(default=1)
flow_closed = Parameter('flow when needle valve is closed', FloatRange(unit='ln/min'),
readonly=False, default=0.0)
# raw_open_step = Parameter('step after direction change', FloatRange(unit='s'), readonly=False, default=0.12)
# raw_close_step = Parameter('step after direction change', FloatRange(unit='s'), readonly=False, default=0.04)
FLOW_SCALE = {'unknown': 1, 'neodry': 0.55, 'xds35': 0.6, 'sv65': 0.9}
pollinterval = Parameter(datatype=FloatRange(1, unit='s'), default=5)
_last_dirchange = 0
_ref_time = 0 _ref_time = 0
_ref_dif = 0 _ref_dif = 0
_dir = 0 _last_cycle = 0
_rawdir = 0 _last_progress = 0
_step = 0 _step = 0
_speed_sum = 0
_last_era = 0
_value = None
def doPoll(self):
# poll at least every sec, but update value only
# every pollinterval and status when changed
if not self.pollInfo.fast_flag:
self.pollInfo.interval = min(1, self.pollinterval) # reduce internal poll interval
self._value = self.get_value()
self._last.append(self._value)
del self._last[0:-300]
self.read_motor_state()
era = time.time() // self.pollinterval
if era != self._last_era:
self.speed = self._speed_sum / self.pollinterval
self._speed_sum = 0
self.value = self._value
self._last_era = era
self.read_status()
self.cycle_machine()
def get_value(self):
p = self.pressure.read_value() * self.lnm_per_mbar
f = self.flow_sensor.read_value()
return p if self.use_pressure else f
def initModule(self): def initModule(self):
self._last = []
if self.pressure:
self.pressure.addCallback('value', self.update_from_pressure)
if self.flow_sensor:
self.flow_sensor.addCallback('value', self.update_from_flow)
super().initModule() super().initModule()
if self.flow_pressure:
self.flow_pressure.addCallback('value', self.update_flow_pressure)
if self.flow:
self.flow.addCallback('value', self.update_flow)
self.write_tolerance(self.tolerance)
def update_from_flow(self, value): def write_tolerance(self, tolerance):
if not self.use_pressure: if hasattr(self.flow_pressure, 'tolerance'):
self._value = value self.flow_pressure.tolerance = tolerance / self.lnm_per_mbar
if hasattr(self.flow, 'tolerance'):
def update_from_pressure(self, value): self.flow.tolerance = tolerance
if self.use_pressure:
self._value = value * self.lnm_per_mbar
# self.cycle_machine()
def read_value(self):
self._value = self.get_value()
return self._value
def read_use_pressure(self): def read_use_pressure(self):
if self.pressure: if self.flow_pressure:
if self.flow_sensor: if self.flow:
return self.use_pressure return self.use_pressure
return True return True
return False return False
def update_flow(self, value):
if not self.use_pressure:
self.value = value
self.cycle_machine()
def update_flow_pressure(self, value):
if self.use_pressure:
self.value = value * self.lnm_per_mbar
self.cycle_machine()
def write_target(self, value): def write_target(self, value):
self.log.debug('change target') self.start_machine(self.controlling, in_tol_time=0,
self.start_machine(self.change_target, target=value, try_close=True) ref_time=0, ref_dif=0, prev_dif=0)
def write_pump_type(self, value):
self.pressure_scale = self.FLOW_SCALE[value.name]
def write_prop_open(self, value):
self._prop_open = Interpolation(value)
return self._prop_open
def write_prop_close(self, value):
self._prop_close = Interpolation(value)
return self._prop_close
def write_tolerance(self, value):
self._tolerance = Interpolation(value)
return self._tolerance
@status_code(BUSY) @status_code(BUSY)
def change_target(self, sm): def unblock_from_open(self, state):
self.target = sm.target self.motor_state = self.command(fm=int)
sm.last_progress = sm.now if self.motor_state == 'opened':
sm.ref_time = 0 self.command(mp=-60)
sm.ref_dif = 0 return Retry
sm.last_pulse_time = 0 if self.motor_state == 'closing':
sm.no_progress_pulse = (0.1, -0.05) return Retry
self.log.debug('target %s value %s', self.target, self._value) if self.motor_state == 'closed':
tol = self._tolerance(self.target) if self.value > max(1, self.target):
if abs(self.target - self._value) < tol: return Retry
self.log.debug('go to at_target') state.flow_before = self.value
return self.at_target state.wiggle = 1
self.log.debug('go to controlling') state.start_wiggle = state.now
self.command(mp=60)
return self.unblock_open
return self.approaching
@status_code(BUSY)
def unblock_open(self, state):
self.motor_state = self.command(fm=int)
if self.value < state.flow_before:
state.flow_before_open = self.value
elif self.value > state.flow_before + 1:
state.wiggle = -state.wiggle / 2
self.command(mp=state.wiggle)
state.start_wiggle = state.now
return self.unblock_close
if self.motor_state == 'opening':
return Retry
if self.motor_state == 'idle':
self.command(mp=state.wiggle)
return Retry
if self.motor_state == 'opened':
if state.now < state.start_wiggle + 20:
return Retry
return self.final_status(ERROR, 'can not open')
return self.controlling return self.controlling
def _dif_medians(self):
return np.array([self.target - np.median(self._last[-m:]) for m in (1, 5, 12, 30, 60)])
@status_code(BUSY) @status_code(BUSY)
def controlling(self, sm): def unblock_close(self, state):
tol = self._tolerance(self.target) self.motor_state = self.command(fm=int)
dif = self._dif_medians() if self.value > state.flow_before:
if sm.init: state.flow_before_open = self.value
self.log.debug('restart controlling') elif self.value < state.flow_before - 1:
direction = math.copysign(1, dif[1]) if state.wiggle < self.prop * 2:
if direction != self._dir: return self.final_status(IDLE, '')
self.log.debug('new dir %g dif=%g', direction, dif[1]) state.wiggle = -state.wiggle / 2
self._dir = direction self.command(mp=state.wiggle)
self._last_dirchange = sm.now state.start_wiggle = state.now
sm.ref_dif = abs(dif[1]) return self.unblock_open
sm.ref_time = sm.now if self.motor_state == 'closing':
difdir = dif * self._dir # negative when overshoot happend
# difdif = dif - self._prev_dif
# self._prev_dif = dif
expected_dif = sm.ref_dif * math.exp((sm.ref_time - sm.now) / self.deriv)
if np.all(difdir < tol):
if np.all(difdir < -tol):
self.log.debug('overshoot %r', dif)
return self.controlling
if not np.any(difdir < -tol):
# within tolerance
self.log.debug('at target %r tol %g', dif, tol)
return self.at_target
if np.all(difdir > expected_dif):
# not enough progress
if sm.now > sm.last_progress + self.deriv:
lim = self.flow_closed + 0.5
if sm.try_close and self._value <= lim - tol and self.target >= lim + tol:
sm.try_close = False
self.command(mp=-60)
sm.after_close = self.open_until_flow_increase
self.log.debug('go to closing / open_until_flow_increase')
return self.closing
if sm.no_progress_pulse:
pulse = abs(sm.no_progress_pulse[self._dir < 0]) * self._dir
self.log.debug('not enough progress %g %r', pulse, sm.try_close)
self.pulse(pulse)
sm.last_progress = sm.now
if sm.now < sm.last_pulse_time + 2.5:
return Retry
# TODO: check motor state for closed / opened ?
difd = min(difdir[:2])
sm.last_pulse_time = sm.now
if self._dir > 0:
minstep = self.min_open_pulse
prop = self._prop_open(self._value)
else:
minstep = self.min_close_pulse
prop = self._prop_close(self._value)
if difd > 0:
if prop * tol > minstep:
# step outside tol is already minstep
step = difd * prop
else:
if difd > tol:
step = (minstep + (difd - tol) * prop)
else:
step = minstep * difd / tol
step *= self._dir
self.log.debug('MP %g dif=%g tol=%g', step, difd * self._dir, tol)
self.command(mp=step)
self._speed_sum += step
return Retry return Retry
# still approaching if self.motor_state == 'idle':
difmax = max(difdir) self.command(mp=state.wiggle)
if difmax < expected_dif: return Retry
sm.ref_time = sm.now if self.motor_state == 'closed':
sm.ref_dif = difmax if state.now < state.start_wiggle + 20:
# self.log.info('new ref %g', sm.ref_dif) return Retry
sm.last_progress = sm.now return self.final_status(ERROR, 'can not close')
return Retry # progressing: no pulse needed return self.final_status(WARN, 'unblock interrupted')
def _tolerance(self):
return min(self.tolerance * min(1, self.value / 2), self.tolerance2)
@status_code(IDLE) @status_code(IDLE)
def at_target(self, sm): def at_target(self, state):
tol = self._tolerance(self.target) dif = self.target - self.value
dif = self._dif_medians() if abs(dif) > self._tolerance():
if np.all(dif > tol) or np.all(dif < -tol): state.in_tol_time = 0
self.log.debug('unstable %r %g', dif, tol)
return self.unstable return self.unstable
return Retry return Retry
@status_code(IDLE, 'unstable') @status_code(IDLE, 'unstable')
def unstable(self, sm): def unstable(self, state):
sm.no_progress_pulse = None return self.controlling(state)
return self.controlling(sm)
def read_motor_state(self): @status_code(BUSY)
return self.command(fm=int) def controlling(self, state):
delta = state.delta(0)
@Command dif = self.target - self.value
def close(self): difdif = dif - state.prev_dif
"""close valve fully""" state.prev_dif = dif
self.command(mp=-60)
self.motor_state = self.command(fm=int) self.motor_state = self.command(fm=int)
self.start_machine(self.closing, after_close=None, fast_poll=0.1) if self.motor_state == 'closed':
if dif < 0 or difdif < 0:
return Retry
return self.unblock_from_open
elif self.motor_state == 'opened': # trigger also when flow too high?
if dif > 0 or difdif > 0:
return Retry
self.command(mp=-60)
return self.unblock_from_open
@status_code(BUSY) tolerance = self._tolerance()
def closing(self, sm): if abs(dif) < tolerance:
if sm.init: state.in_tol_time += delta
sm.start_time = sm.now if state.in_tol_time > self.settle:
self._speed_sum -= sm.delta() return self.at_target
self.read_motor_state()
if self.motor_state == M.closing:
return Retry return Retry
if self.motor_state == M.closed: expected_dif = state.ref_dif * math.exp((state.now - state.ref_time) / self.deriv)
if sm.after_close: if abs(dif) < expected_dif:
return sm.after_close if abs(dif) < expected_dif / 1.25:
return self.final_status(IDLE, 'closed') state.ref_time = state.now
if sm.now < sm.start_time + 1: state.ref_dif = abs(dif) * 1.25
state.last_progress = state.now
return Retry # progress is fast enough
state.ref_time = state.now
state.ref_dif = abs(dif)
state.step += dif * delta * self.prop
if abs(state.step) < (state.now - state.last_progress) / self.step_factor:
# wait until step size is big enough
return Retry return Retry
return self.final_status(IDLE, 'fixed') self.command(mp=state.step)
@Command
def open(self):
"""open valve fully"""
self.command(mp=60)
self.read_motor_state()
self.start_machine(self.opening, threshold=None)
@status_code(BUSY)
def opening(self, sm):
if sm.init:
sm.start_time = sm.now
self._speed_sum += sm.dleta()
self.read_motor_state()
if self.motor_state == M.opening:
return Retry
if self.motor_state == M.opened:
return self.final_status(IDLE, 'opened')
if sm.now < sm.start_time + 1:
return Retry
return self.final_status(IDLE, 'fixed')
@Command
def close_test(self):
"""close and then try to open until the flow starts to increase
save a
"""
self.command(mp=-60)
self.start_machine(self.closing, fast_poll=0.1, after_close=self.open_until_flow_increase, target=0)
@status_code(BUSY)
def open_until_flow_increase(self, sm):
if sm.init:
p = self.command(f=float)
sm.threshold = 0.5
sm.prev = [p]
sm.ref = p
sm.cnt = 0
sm.low_flow = 0
self.read_motor_state()
if self.motor_state == M.opening:
return Retry
if self.motor_state == M.opened:
return self.final_status(IDLE, 'opened')
press, measured = self.command(f=float, mmp=float)
sm.prev.append(press)
if press > sm.ref + 0.2:
sm.cnt += 1
if sm.cnt > 5 or press > sm.ref + 0.5:
self.flow_closed = sm.low_flow
self.log.debug('flow increased %g', press)
if sm.target == 0:
sm.target = sm.low_flow + 0.5
return self.change_target
self.log.debug('wait count %g', press)
return Retry
sm.low_flow = self.value
sm.cnt = 0
last5 = sm.prev[-5:]
median = sorted(last5)[len(last5) // 2]
if press > median:
# avoid to pulse again after an even small increase
self.log.debug('wait %g', press)
return Retry
sm.ref = min(sm.prev[0], median)
if measured:
self._speed_sum += measured
if measured < 0.1:
sm.threshold = round(sm.threshold * 1.1, 2)
elif measured > 0.3:
sm.threshold = round(sm.threshold * 0.9, 2)
self.log.debug('measured %g new threshold %g press %g', measured, sm.threshold, press)
else:
self._speed_sum += 1
self.log.debug('full pulse')
sm.cnt = 0
self.command(mft=sm.ref + sm.threshold, mp=1)
return Retry return Retry
@Command(FloatRange())
def pulse(self, value):
"""perform a motor pulse"""
self.command(mp=value)
self._speed_sum += value
if value > 0:
self.motor_state = M.opening
return self.opening
self.motor_state = M.closing
return self.closing
@Command()
def autopar(self):
"""adjust automatically needle valve parameters"""
self.close()
self.start_machine(self.auto_wait, open_pulse=0.1, close_pulse=0.05,
minflow=self.read_value(), last=None)
return self.auto_wait
def is_stable(self, sm, n, tol=0.01):
"""wait for a stable flow
n: size of buffer
tol: a tolerance
"""
if sm.last is None:
sm.last = []
sm.cnt = 0
v = self.read_value()
sm.last.append(v)
del sm.last[:-n]
dif = v - sm.last[0]
if dif < -tol:
sm.cnt -= 1
elif dif > tol:
sm.cnt += 1
else:
sm.cnt -= clamp(-1, sm.cnt, 1)
if len(sm.last) < n:
return False
return abs(sm.cnt) < n // 2
def is_unstable(self, sm, n, tol=0.01):
"""wait for a stable flow
return 0, -1 or 1
"""
if sm.last is None:
sm.last = []
sm.cnt = 0
v = self.read_value()
prevmax = max(sm.last)
prevmin = min(sm.last)
sm.last.append(v)
del sm.last[:-n]
self.log.debug('unstable %g >? %g <? %g', v, prevmax, prevmin)
if v > prevmax + tol:
return 1
if v < prevmin - tol:
return -1
return 0
@status_code(BUSY)
def auto_wait(self, sm):
stable = self.is_stable(sm, 5, 0.01)
if self._value < sm.minflow:
sm.minflow = self._value
if self.read_motor_state() == M.closing or not stable:
return Retry
return self.auto_open
@status_code(BUSY)
def auto_open(self, sm):
stable = self.is_unstable(sm, 5, 0.1)
if stable > 0:
sm.start_time = sm.now
sm.flow_before = sm.last[-1]
self.pulse(sm.open_pulse)
return self.auto_close
if sm.delta(sm.open_pulse * 2) is not None:
self.pulse(sm.open_pulse)
return Retry
@status_code(BUSY)
def auto_open_stable(self, sm):
if self.is_stable(sm, 5, 0.01):
return Retry
return self.auto_close
@status_code(BUSY)
def auto_close(self, sm):
if not self.is_stable(sm, 10, 0.01):
return Retry
self.log.debug('before %g pulse %g, flowstep %g', sm.flow_before, sm.open_pulse, sm.last[-1] - sm.flow_before)
self.close()
return self.final_status(IDLE, '')

View File

@ -1,451 +0,0 @@
# *****************************************************************************
#
# 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:
# Andrea Plank <andrea.plank@psi.ch>
#
# *****************************************************************************
import time
from frappy.core import Readable, Drivable, Parameter, Attached, FloatRange, \
Command, IDLE, BUSY, WARN, ERROR, Property
from frappy.datatypes import EnumType, IntRange, BoolType, StructOf, StringType
from frappy.states import Retry, Finish, status_code, HasStates
from frappy.lib.enum import Enum
from frappy.errors import ImpossibleError, HardwareError
from frappy.addrparam import AddrParam, AddrMixin
from frappy.lib import formatStatusBits
from frappy.persistent import PersistentMixin, PersistentParam
from frappy_psi.logo import LogoMixin, DigitalActuator
T = Enum( # target states
off = 0,
sorbpumped = 2,
condense = 5,
remove = 7,
remove_and_sorbpump = 9,
remove_and_condense = 10,
manual = 11,
test = 12,
)
V = Enum(T, # value status inherits from target status
sorbpumping=1,
condensing=4,
circulating=6,
removing=8,
)
class Dilution(HasStates, Drivable):
condenseline_pressure = Attached()
condense_valve = Attached()
dump_valve = Attached()
forepump = Attached()
compressor = Attached(mandatory=False)
turbopump = Attached(mandatory=False)
condenseline_valve = Attached()
circuitshort_valve = Attached()
still_pressure = Attached()
still_pressure_turbo = Attached(mandatory=False)
value = Parameter('current state', EnumType(T), default=0)
target = Parameter('target state', EnumType(T), default=0)
sorbpumped = Parameter('sorb pump done', BoolType(), default=False)
dump_pressure = Attached()
#ls372 = Attached()
condensing_p_low = Parameter('Lower limit for condenseline pressure',
FloatRange(unit='mbar'), readonly=False, default=1200)
condensing_p_high = Parameter('Higher limit for condenseline pressure',
FloatRange(unit='mbar'), readonly=False, default=1500)
dump_target = Parameter('low dump pressure limit indicating end of condensation phase',
FloatRange(unit='mbar * min'), readonly=False, default=100)
pulse_factor = Parameter('factor for calculating V9 pulse length',
FloatRange(unit='mbar'), readonly=False, default=20)
end_condense_pressure = Parameter('low condense pressure indicating end of condensation phase',
FloatRange(unit='mbar'), readonly=False, default=500)
turbo_condense_pressure = Parameter('low condense pressure before turbo start',
FloatRange(unit='mbar'), readonly=False, default=900)
safe_turbo_pressure = Parameter('low still pressure before turbo start',
FloatRange(unit='mbar'), readonly=False, default=10)
turbo_off_speed = Parameter('speed to wait for after switching turbo off',
FloatRange(unit='Hz'), readonly=False, default=200)
end_remove_turbo_pressure = Parameter('pressure reached before end of remove (before turbo)',
FloatRange(unit='mbar'), readonly=False, default=1e-4)
end_remove_pressure = Parameter('pressure reached before end of remove (before fore pump)',
FloatRange(unit='mbar'), readonly=False, default=0.02)
st = StringType()
valve_set = StructOf(close=st, open=st, check_open=st, check_closed=st)
condense_valves = Parameter('valve to act when condensing', valve_set)
valves_after_remove = Parameter('valve to act after remove', valve_set)
check_after_remove = Parameter('check for manual valves after remove', valve_set)
_start_time = 0
init = True
_warn_manual_work = None
def write_target(self, target):
"""
if (target == Targetstates.SORBPUMP):
if self.value == target:
return self.target
self.start_machine(self.sorbpump)
self.value = Targetstates.SORBPUMP
return self.value
"""
self.log.info('start %s', target.name)
if self.value == target:
return target # not sure if this is correct. may be a step wants to be repeated?
try:
self.start_machine(getattr(self, target.name, None))
except Exception as e:
self.log.exception('error')
self.log.info('started %s', target.name)
return target
"""
@status_code(BUSY, 'sorbpump state')
def sorbpump(self, state):
#Heizt Tsorb auf und wartet ab.
if self.init:
self.ls372.write_target(40) #Setze Tsorb auf 40K
self.start_time = self.now
self.init = false
return Retry
if self.now - self.start_time < 2400: # 40 Minuten warten
return Retry
self.ls372.write_target(0)
if self.ls372.read_value() > 10: # Warten bis Tsorb unter 10K
return Retry
return self.condense
"""
@status_code(BUSY, 'start test')
def test(self, state):
"""Nur zum testen, ob UI funktioniert"""
self.init = False
if state.init:
state._start = state.now
return self.wait_test
@status_code(BUSY)
def wait_test(self, state):
if state.now < state.start + 20:
return Retry
return self.final_status(IDLE, 'end test')
@status_code(BUSY)
def condense(self, state):
"""Führt das Kondensationsverfahren durch."""
if state.init:
# self.value = V.condensing
pumpstate = self.forepump.read_value()
if self.turbopump:
self.turbopump.write_target(0)
self.handle_valves(**self.condense_valves)
self._start_time = state.now
if not pumpstate: # wait longer for starting fore pump
self._start_time += 10
return Retry
if self.wait_valves():
return Retry
self.check_valve_result()
return self.condensing
@status_code(BUSY)
def condensing(self, state):
pdump = self.dump_pressure.value # or self.dump_pressure.read_value() ?
pcond = self.condenseline_pressure.read_value()
v9 = self.condense_valve.read_value()
if v9:
if pcond > self.condensing_p_high:
self.log.info('shut V9')
self.condense_valve.write_target(0)
elif pcond < self.condensing_p_low and state.now > self._start_time + 5:
pulse_time = 60 * self.pulse_factor / pdump
if pulse_time > 59:
self.log.info('open V9')
self.condense_value.write_target(1)
else:
self.log.info('V9 pulse %r', pulse_time)
self._start_time = state.now
self.condense_valve.pulse(pulse_time)
if pdump > self.dump_target:
return Retry
self.condense_valve.write_target(1)
if self.turbopump is not None:
return self.condense_wait_before_turbo_start
return self.wait_for_condense_line_pressure
@status_code(BUSY, 'condense (wait before starting turbo)')
def condense_wait_before_turbo_start(self, state):
if (self.condenseline_pressure.read_value() > self.turbo_condense_pressure
and self.still_pressure.read_value() > self.safe_turbo_pressure):
return Retry
self.turbopump.write_target(1)
return self.wait_for_condense_line_pressure
@status_code(BUSY)
def wait_for_condense_line_pressure(self, state):
if self.condenseline_pressure.read_value() > self.end_condense_pressure:
return Retry
self.condense_valve.write_target(0)
return self.circulate
@status_code(BUSY)
def circulate(self, state):
"""Zirkuliert die Mischung."""
if state.init:
self.handle_valves(**self.condense_valves)
if self.wait_valves():
return Retry
self.check_valve_result()
self.value = V.circulating
return Finish
@status_code(BUSY, 'remove (wait for turbo shut down)')
def remove(self, state):
"""Entfernt die Mischung."""
if state.init:
self.handle_valves(**self.remove_valves)
if self.turbopump is not None:
self._start_time = state.now
self.turbopump.write_target(0)
return Retry
if self.turbopump is not None:
# if (state.now - self._start_time < self.turbo_off_delay or
if self.turbopump.read_speed() > self.turbo_off_speed:
return Retry
self.circuitshort_valve.write_target(1)
if self.turbopump is not None:
return self.remove_wait_for_still_pressure
return self.remove_endsequence
@status_code(BUSY, 'remove (wait for still pressure low)')
def remove_wait_for_still_pressure(self, state):
if self.still_pressure.read_value() > self.safe_turbo_pressure:
return Retry
self.turbopump.write_target(1)
return self.remove_endsequence
@status_code(BUSY)
def remove_endsequence(self, state):
if (self.still_pressure_turbo and
self.still_pressure_turbo.read_value() > self.end_remove_turbo_pressure):
return Retry
if self.still_pressure.read_value() > self.end_remove_pressure:
return Retry
self.circuitshort_valve.write_target(0)
self.dump_valve.write_target(0)
if self.compressor is not None:
self.compressor.write_target(0)
return self.close_valves_after_remove
@status_code(BUSY)
def close_valves_after_remove(self, state):
if state.init:
self.handle_valves(**self.valves_after_remove)
self.turbopump.write_target(0)
if self.wait_valves():
return Retry
self.check_valve_result()
self._warn_manual_work = True
return self.final_status(WARN, 'please check manual valves')
def read_status(self):
status = super().read_status()
if status[0] < ERROR and self._warn_manual_work:
try:
self.handle_valves(**self.check_after_remove)
self._warn_manual_work = False
except ImpossibleError:
return WARN, f'please close manual valves {",".join(self._valves_failed[False])}'
return status
def handle_valves(self, check_closed=(), check_open=(), close=(), open=()):
"""check ot set given valves
raises ImpossibleError, when checks fails
"""
self._valves_to_wait_for = {}
self._valves_failed = {True: [], False: []}
for flag, valves in enumerate([check_closed, check_open]):
for vname in valves.split():
if self.secNode.modules[vname].read_value() != flag:
self._valves_failed[flag].append(vname)
for flag, valves in enumerate([close, open]):
for vname in valves.split():
valve = self.secNode.modules[vname]
valve.write_target(flag)
if valve.isBusy():
self._valves_to_wait_for[vname] = (valve, flag)
elif valve.read_value() != flag:
self._valves_failed[flag].append(vname)
def wait_valves(self):
busy = False
for vname, (valve, flag) in dict(self._valves_to_wait_for.items()):
statuscode = valve.read_status()[0]
if statuscode == BUSY:
busy = True
continue
if valve.read_value() == flag and statuscode == IDLE:
self._valves_to_wait_for.pop(vname)
else:
self._valves_failed[flag].append(vname)
return busy
def check_valve_result(self):
result = []
for flag, valves in self._valves_failed.items():
if valves:
result.append(f"{','.join(valves)} not {'open' if flag else 'closed'}")
if result:
raise ImpossibleError(f"failed: {', '.join(result)}")
class DIL5(Dilution):
condense_valves = {
'close': 'V2 V4 V9',
'check_closed': 'MV10 MV13 MV8 MVB MV2',
'check_open': 'MV1 MV3a MV3b GV1 MV9 MV11 MV12 MV14',
'open': 'V1 V5 compressor forepump',
}
remove_valves = {
'close': 'V1 V2 V9',
'check_closed': 'MV10 MV13 MV8 MVB MV2',
'check_open': 'MV1 MV3a MV3b GV1 MV9 MV11 MV12 MV14',
'open': 'V4 V5 compressor forepump',
}
valves_after_remove = {
'close': 'V1 V2 V4 V5 V9',
'check_closed': 'MV10 MV13 MV8 MVB MV2',
'open': '',
'check_open': '',
}
check_after_remove = {
'close': '',
'check_closed': 'MV1 MV9 MV10 MV11 MV12',
'open': '',
'check_open': '',
}
class Interlock(LogoMixin, AddrMixin, Readable):
dil = Attached()
value = AddrParam('interlock state (bitmap)',
IntRange(0, 31), addr='V414', readonly=False)
p5lim = AddrParam('safety limit on p5 to protect forepump',
FloatRange(), value=1300, addr='VW16 VW18', readonly=False)
p2lim = AddrParam('safety limit on p2 to protect compressor',
FloatRange(), value=4000, addr='VW8 VW10', readonly=False)
p1lim = AddrParam('safety limit to protect dump',
FloatRange(), value=1300, addr='VW12 VW14', readonly=False)
p2max = AddrParam('limit pn p2 for mechanism to put mix to dump',
FloatRange(), value=3000, addr='VW20 VW22', readonly=False)
conditions = { # starting with bit 1
'off (p5>p5lim)': {'forepump': False},
'off (p2>p2lim)': {'compressor': False},
'off (p1>p2lim)': {'forepump': False, 'compressor': False},
'open (p2>p2max)': {'V4': True}}
reset_param = Property('addr for reset', StringType(), default='V418.1')
_mismatch = None
_prefix = ''
_actuators = None
def initModule(self):
super().initModule()
self._actuators = {}
for actions in self.conditions.values():
for modname in actions:
if modname not in self._actuators:
self._actuators[modname] = self.secNode.modules[modname]
def doPoll(self):
self.read_status() # this includes read_value
def initialReads(self):
super().initialReads()
self.reset()
@Command
def reset(self):
"""reset the interlock"""
self._prefix = ''
self.set_vm_value(self.reset_param, 1)
for actuator in self._actuators.values():
actuator.reset_fault()
if self.read_value() != 0:
raise HardwareError('can not clear status byte')
self.set_vm_value(self.reset_param, 0)
self.read_status() # update status (this may trigger ERROR again)
def read_status(self):
if self._mismatch is None: # init
self._mismatch = set()
bits = self.read_value()
if bits:
self.dil.stop()
keys = formatStatusBits(bits, self.conditions, 1)
statustext = []
for key in keys:
actions = self.conditions[key]
statustext.append(f"{' and '.join(actions)} {key}")
for module, value in actions.items():
modobj = self._actuators[module]
if modobj.target != value:
self._prefix = 'switched '
modobj.set_fault(value, f'switched {key}')
return ERROR, f"{self._prefix}{', '.join(statustext)}"
if self._mismatch:
return ERROR, f"mismatch on values for {', '.join(self._mismatch)}"
return IDLE, ''
def addressed_read(self, pobj):
values = [self.get_vm_value(a) for a in pobj.addr.split()]
if any(v != values[0] for v in values):
self._mismatch.add(pobj.name)
self.read_status()
else:
self._mismatch.discard(pobj.name)
return values[0]
def addressed_write(self, pobj, value):
for addr in pobj.addr.split():
self.set_vm_value(addr, value)
self.read_status()

View File

@ -56,7 +56,7 @@ class Drums(Writable):
self._pos = 0 self._pos = 0
for i, action in enumerate(self.pattern[self._pos:]): for i, action in enumerate(self.pattern[self._pos:]):
upper = action.upper() upper = action.upper()
relais = self.actions.get(upper) relais = self.actions.get(action.upper())
if relais: if relais:
relais.write_target(upper == action) # True when capital letter relais.write_target(upper == action) # True when capital letter
else: else:

View File

@ -17,104 +17,41 @@
# Markus Zolliker <markus.zolliker@psi.ch> # Markus Zolliker <markus.zolliker@psi.ch>
# ***************************************************************************** # *****************************************************************************
"""interlocks for furnace""" """interlocks for furnance"""
import time
from frappy.core import Module, Writable, Attached, Parameter, FloatRange, Readable,\ from frappy.core import Module, Writable, Attached, Parameter, FloatRange, Readable,\
BoolType, ERROR, IDLE BoolType, ERROR, IDLE
from frappy.errors import ImpossibleError
from frappy.mixins import HasControlledBy
from frappy_psi.picontrol import PImixin from frappy_psi.picontrol import PImixin
from frappy_psi.convergence import HasConvergence
from frappy_psi.ionopimax import CurrentInput, LogVoltageInput
import frappy_psi.tdkpower as tdkpower
import frappy_psi.bkpower as bkpower
class Interlocks(Writable): class Interlocks(Module):
value = Parameter('interlock o.k.', BoolType(), default=True) input = Attached(Readable, 'the input module')
target = Parameter('set to true to confirm', BoolType(), readonly=False) vacuum = Attached (Readable, 'the vacuum pressure')
input = Attached(Readable, 'the input module', mandatory=False) # TODO: remove wall_T = Attached (Readable, 'the wall temperature')
vacuum = Attached(Readable, 'the vacuum pressure', mandatory=False)
wall_T = Attached(Readable, 'the wall temperature', mandatory=False)
htr_T = Attached(Readable, 'the heater temperature', mandatory=False)
main_T = Attached(Readable, 'the main temperature')
extra_T = Attached(Readable, 'the extra temperature')
control = Attached(Module, 'the control module') control = Attached(Module, 'the control module')
htr = Attached(Module, 'the heater module', mandatory=False) relais = Attached(Writable, 'the interlock relais')
relais = Attached(Writable, 'the interlock relais', mandatory=False)
flowswitch = Attached(Readable, 'the flow switch', mandatory=False)
wall_limit = Parameter('maximum wall temperature', FloatRange(0, unit='degC'), wall_limit = Parameter('maximum wall temperature', FloatRange(0, unit='degC'),
default = 50, readonly = False) default = 50, readonly = False)
vacuum_limit = Parameter('maximum vacuum pressure', FloatRange(0, unit='mbar'), vacuum_limit = Parameter('maximum vacuum pressure', FloatRange(0, unit='mbar'),
default = 0.1, readonly = False) default = 0.1, readonly = False)
htr_T_limit = Parameter('maximum htr temperature', FloatRange(0, unit='degC'),
default = 530, readonly = False) def doPoll(self):
main_T_limit = Parameter('maximum main temperature', FloatRange(0, unit='degC'), super().doPoll()
default = 530, readonly = False) if self.input.status[0] >= ERROR:
extra_T_limit = Parameter('maximum extra temperature', FloatRange(0, unit='degC'), self.control.status = self.input.status
default = 530, readonly = False) elif self.vacuum.value > self.vacuum_limit:
self.control.status = ERROR, 'bad vacuum'
_off_reason = None # reason triggering interlock elif self.wall_T.value > self.wall_limit:
_conditions = '' # summary of reasons why locked now self.control.status = ERROR, 'wall overheat'
else:
def initModule(self): return
super().initModule() self.control.write_control_active(False)
self._sensor_checks = [ self.relais.write_target(False)
(self.wall_T, 'wall_limit'),
(self.main_T, 'main_T_limit'),
(self.extra_T, 'extra_T_limit'),
(self.htr_T, 'htr_T_limit'),
(self.vacuum, 'vacuum_limit'),
]
def write_target(self, value):
if value:
self.read_status()
if self._conditions:
raise ImpossibleError('not ready to start')
self._off_reason = None
self.value = True
elif self.value:
self.switch_off()
self._off_reason = 'switched off'
self.value = False
self.read_status()
def switch_off(self):
if self.value:
self._off_reason = self._conditions
self.value = False
if self.control.control_active:
self.log.error('switch control off %r', self.control.status)
self.control.write_control_active(False)
self.control.status = ERROR, self._conditions
if self.htr and self.htr.target:
self.htr.write_target(0)
if self.relais and (self.relais.value or self.relais.target):
self.relais.write_target(False)
def read_status(self):
conditions = []
if self.flowswitch and self.flowswitch.value == 0:
conditions.append('no cooling water')
for sensor, limitname in self._sensor_checks:
if sensor is None:
continue
if sensor.value > getattr(self, limitname):
conditions.append(f'above {sensor.name} limit')
if sensor.status[0] >= ERROR:
conditions.append(f'error at {sensor.name}: {sensor.status[1]}')
break
self._conditions = ', '.join(conditions)
if conditions and (self.control.control_active or self.htr.target):
self.switch_off()
if self.value:
return IDLE, '; '.join(conditions)
return ERROR, self._off_reason
class PI(HasConvergence, PImixin): class PI(PImixin, Writable):
input_module = Attached(Readable, 'the input module') input = Attached(Readable, 'the input module')
relais = Attached(Writable, 'the interlock relais', mandatory=False) relais = Attached(Writable, 'the interlock relais', mandatory=False)
def read_value(self): def read_value(self):
@ -124,23 +61,3 @@ class PI(HasConvergence, PImixin):
super().write_target(value) super().write_target(value)
if self.relais: if self.relais:
self.relais.write_target(1) self.relais.write_target(1)
class TdkOutput(HasControlledBy, tdkpower.Output):
pass
class BkOutput(HasControlledBy, bkpower.Output):
pass
class PRtransmitter(CurrentInput):
rawrange = (0.004, 0.02)
extendedrange = (0.0036, 0.021)
class PKRgauge(LogVoltageInput):
rawrange = (1.82, 8.6)
valuerange = (5e-9, 1000)
extendedrange = (0.5, 9.5)
value = Parameter(unit='mbar')

View File

@ -1,65 +0,0 @@
# *****************************************************************************
#
# 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>
#
# *****************************************************************************
from frappy.core import BoolType, FloatRange, Parameter, Readable, Writable, Attached, EnumType, nopoll
from frappy_psi.trinamic import Motor
from frappy_psi.ccu4 import Pressure, NeedleValveFlow
class ValveMotor(Motor):
has_inputs = True
class HePump(Writable):
valvemotor = Attached(Motor)
valve = Attached(Writable)
value = Parameter(datatype=BoolType())
target = Parameter(datatype=BoolType())
pump_type = NeedleValveFlow.pump_type
eco_mode = Parameter('eco mode', BoolType(), readonly=False)
has_feedback = Parameter('feedback works', BoolType(), readonly=False, default=True)
def write_target(self, value):
self.valvemotor.write_output0(value)
def read_target(self):
return self.valvemotor.read_output0()
def read_value(self):
if self.has_feedback:
return not self.valvemotor.read_input3()
return self.target
def read_eco_mode(self):
if self.pump_type == 'xds35':
return self.valvemotor.read_output1()
return False
def write_eco_mode(self, value):
if self.pump_type == 'xds35':
return self.valvemotor.write_output1(value)
# else silently ignore

View File

@ -18,117 +18,55 @@
# Jael Celia Lorenzana <jael-celia.lorenzana@psi.ch> # Jael Celia Lorenzana <jael-celia.lorenzana@psi.ch>
# ***************************************************************************** # *****************************************************************************
"""support for iono pi max from Sfera Labs from frappy.core import Readable, Writable, Parameter, BoolType, StringType,\
FloatRange, Property, TupleOf, ERROR, IDLE
supports also the smaller model iono pi
"""
from math import log from math import log
from pathlib import Path
from frappy.core import Readable, Writable, Parameter, Property, ERROR, IDLE, WARN
from frappy.errors import ConfigError, OutOfRangeError, ProgrammingError
from frappy.datatypes import BoolType, EnumType, FloatRange, NoneOr, StringType, TupleOf
class Base: class Base:
addr = Property('address', StringType()) addr = Property('address', StringType())
_devpath = None
_devclass = None
_status = IDLE, ''
def initModule(self):
super().initModule()
self.log.info('initModule %r', self.name)
candidates = list(Path('/sys/class').glob(f'ionopi*/*/{self.addr}'))
if not candidates:
raise ConfigError(f'can not find path for {self.addr}')
if len(candidates) > 1:
raise ProgrammingError(f"ambiguous paths {','.join(candidates)}")
self._devpath = candidates[0].parent
self._devclass = candidates[0].parent.name
def read(self, addr, scale=None): def read(self, addr, scale=None):
with open(self._devpath / addr) as f: with open(f'/sys/class/ionopimax/{self.devclass}/{addr}') as f:
result = f.read() result = f.read()
if scale: if scale:
return float(result) / scale return float(result) / scale
return result.strip() return result
def write(self, addr, value, scale=None): def write(self, addr, value, scale=None):
value = str(round(value * scale)) if scale else str(value) value = str(round(value * scale)) if scale else str(value)
with open(self._devpath / addr, 'w') as f: with open(f'/sys/class/ionopimax/{self.devclass}/{addr}', 'w') as f:
f.write(value) f.write(value)
def read_status(self):
return self._status
class DigitalInput(Base, Readable): class DigitalInput(Base, Readable):
value = Parameter('input state', BoolType()) value = Parameter('input state', BoolType())
true_level = Property('level representing True', EnumType(low=0, high=1), default=1) devclass = 'digital_in'
def initModule(self):
super().initModule()
if self._devclass == 'digital_io':
self.write(f'{self.addr}_mode', 'inp')
def read_value(self): def read_value(self):
return self.read(self.addr, 1) == self.true_level return self.read(self.addr, 1)
class DigitalOutput(DigitalInput, Writable): class DigitalOutput(DigitalInput, Writable):
target = Parameter('output state', BoolType(), readonly=False) target = Parameter('output state', BoolType(), readonly=False)
devclass = 'digital_out'
def read_value(self):
reply = self.read(self.addr)
try:
self._status = IDLE, ''
value = int(reply)
except ValueError:
if reply == 'S':
if self.addr.startswith('oc'):
self._status = ERROR, 'short circuit'
else:
self._status = ERROR, 'fault while closed'
value = 0
else:
self._status = ERROR, 'fault while open'
value = 1
self.read_status()
return value == self.true_level
def write_target(self, value): def write_target(self, value):
self.write(self.addr, value == self.true_level, 1) self.write(self.addr, value, 1)
class AnalogInput(Base, Readable): class AnalogInput(Base, Readable):
value = Parameter('analog value', FloatRange()) value = Parameter('analog value', FloatRange())
rawrange = Property('raw range (electronic)', TupleOf(FloatRange(),FloatRange())) rawrange = Property('raw range(electronic)', TupleOf(FloatRange(),FloatRange()))
valuerange = Property('value range (physical)', TupleOf(FloatRange(),FloatRange())) valuerange = Property('value range(physical)', TupleOf(FloatRange(),FloatRange()))
extendedrange = Property('range outside calibrated range, but not sensor fault', devclass = 'analog_in'
NoneOr(TupleOf(FloatRange(), FloatRange())), default=None)
def initModule(self):
super().initModule()
dt = self.parameters['value'].datatype
dt.min, dt.max = self.valuerange
def read_value(self): def read_value(self):
x0, x1 = self.rawrange x0, x1 = self.rawrange
y0, y1 = self.valuerange y0, y1 = self.valuerange
self.x = self.read(self.addr, self.scale) self.x = self.read(self.addr, self.scale)
self.read_status()
if self.status[0] == ERROR:
raise OutOfRangeError('sensor fault')
return y0 + (y1 - y0) * (self.x - x0) / (x1 - x0) return y0 + (y1 - y0) * (self.x - x0) / (x1 - x0)
def read_status(self):
if self.rawrange[0] <= self.x <= self.rawrange[1]:
return IDLE, ''
if self.extendedrange is None or self.extendedrange[0] <= self.x <= self.extendedrange[1]:
return WARN, 'out of range'
return ERROR, 'sensor fault'
class VoltageInput(AnalogInput): class VoltageInput(AnalogInput):
scale = 1e5 scale = 1e5
@ -144,24 +82,30 @@ class LogVoltageInput(VoltageInput):
x0, x1 = self.rawrange x0, x1 = self.rawrange
y0, y1 = self.valuerange y0, y1 = self.valuerange
self.x = self.read(self.addr, self.scale) self.x = self.read(self.addr, self.scale)
self.read_status() a = (x1-x0)/log(y1/y0,10)
if self.status[0] == ERROR:
raise OutOfRangeError('sensor fault')
a = (x1-x0)/log(y1/y0, 10)
return 10**((self.x-x1)/a)*y1 return 10**((self.x-x1)/a)*y1
class CurrentInput(AnalogInput): class CurrentInput(AnalogInput):
scale = 1e6 scale = 1e6
rawrange = (0.004, 0.02) rawrange = (0.004,0.02)
def initModule(self): def initModule(self):
super().initModule() super().initModule()
self.write(f'{self.addr}_mode', 'U') self.write(f'{self.addr}_mode','U')
def read_value(self):
result = super().read_value()
if self.x > 0.021:
self.status = ERROR, 'sensor broken'
else:
self.status = IDLE, ''
return result
class AnalogOutput(AnalogInput, Writable): class AnalogOutput(AnalogInput, Writable):
target = Parameter('outputvalue', FloatRange()) target = Parameter('outputvalue', FloatRange())
devclass = 'analog_out'
def write_target(self, value): def write_target(self, value):
x0, x1 = self.rawrange x0, x1 = self.rawrange
@ -179,18 +123,3 @@ class VoltageOutput(AnalogOutput):
self.write(f'{self.addr}_mode', 'V') self.write(f'{self.addr}_mode', 'V')
self.write(f'{self.addr}', '0') self.write(f'{self.addr}', '0')
self.write(f'{self.addr}_enabled', '1') self.write(f'{self.addr}_enabled', '1')
class VoltagePower(Base, Writable):
target = Parameter(datatype=FloatRange(0, 24.5, unit='V'), default=12)
addr = 'vso'
def write_target(self, value):
if value:
self.log.info('write vso %r', value)
self.write(self.addr, value, 1000)
self.write(f'{self.addr}_enabled', 1)
else:
self.write(f'{self.addr}_enabled', 0)

View File

@ -1,65 +1,16 @@
# ***************************************************************************** """
# Created on Tue Feb 4 11:07:56 2020
# 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 @author: tartarotti_d-adm
# 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:
# Damaris Tartarotti Maimone
# Markus Zolliker <markus.zolliker@psi.ch>
#
# *****************************************************************************
"""support for ultrasound plot clients"""
import numpy as np import numpy as np
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
# disable the behaviour of raising the window to the front each time it is updated
plt.rcParams["figure.raise_window"] = False
NAN = float('nan')
class Pause:
"""allows to leave the plot loop when the window is closed
Usage:
pause = Pause(fig)
# do initial plots
plt.show()
while pause(0.5):
# do plot updates
plt.draw()
"""
def __init__(self, fig):
fig.canvas.mpl_connect('close_event', self.on_close)
self.running = True
def on_close(self, event):
self.running = False
def __call__(self, interval):
try:
plt.pause(interval)
except Exception:
pass
return self.running
def rect(x1, x2, y1, y2): def rect(x1, x2, y1, y2):
return np.array([[x1,x2,x2,x1,x1],[y1,y1,y2,y2,y1]]) return np.array([[x1,x2,x2,x1,x1],[y1,y1,y2,y2,y1]])
NAN = float('nan')
def rects(intervals, y12): def rects(intervals, y12):
result = [rect(*intervals[0], *y12)] result = [rect(*intervals[0], *y12)]
@ -68,19 +19,13 @@ def rects(intervals, y12):
result.append(rect(*x12, *y12)) result.append(rect(*x12, *y12))
return np.concatenate(result, axis=1) return np.concatenate(result, axis=1)
class Plot: class Plot:
def __init__(self, maxy, maxx=None): def __init__(self, maxy):
self.lines = {} self.lines = {}
self.yaxis = ((-2 * maxy, maxy), (-maxy, 2 * maxy)) self.yaxis = ((-2 * maxy, maxy), (-maxy, 2 * maxy))
self.maxx = maxx
self.first = True self.first = True
self.fig = None self.fig = None
def pause(self, interval):
"""will be overridden when figure is created"""
return False
def set_line(self, iax, name, data, fmt, **kwds): def set_line(self, iax, name, data, fmt, **kwds):
""" """
plot or update a line plot or update a line
@ -123,9 +68,8 @@ class Plot:
if self.first: if self.first:
plt.ion() plt.ion()
self.fig, axleft = plt.subplots(figsize=(15,7)) self.fig, axleft = plt.subplots(figsize=(15,7))
self.pause = Pause(self.fig)
plt.title("I/Q", fontsize=14) plt.title("I/Q", fontsize=14)
axleft.set_xlim(0, self.maxx or curves[0][-1]) axleft.set_xlim(0, curves[0][-1])
self.ax = [axleft, axleft.twinx()] self.ax = [axleft, axleft.twinx()]
self.ax[0].axhline(y=0, color='#cccccc') # show x-axis line self.ax[0].axhline(y=0, color='#cccccc') # show x-axis line
self.ax[1].axhline(y=0, color='#cccccc') self.ax[1].axhline(y=0, color='#cccccc')
@ -151,8 +95,7 @@ class Plot:
plt.tight_layout() plt.tight_layout()
finally: finally:
self.first = False self.first = False
plt.draw() plt.draw()
# TODO: do not know why this is needed:
self.fig.canvas.draw() self.fig.canvas.draw()
self.fig.canvas.flush_events() self.fig.canvas.flush_events()

View File

@ -39,13 +39,6 @@ from frappy.extparams import StructParam
from frappy_psi.calcurve import CalCurve from frappy_psi.calcurve import CalCurve
def string_to_num(string):
try:
return int(string)
except ValueError:
return float(string)
class IO(StringIO): class IO(StringIO):
"""IO classes of most LakeShore models will inherit from this""" """IO classes of most LakeShore models will inherit from this"""
end_of_line = '\n' end_of_line = '\n'
@ -104,9 +97,8 @@ class HasLscIO(HasIO):
if not args: if not args:
self.communicate(f'{msghead};*OPC?') self.communicate(f'{msghead};*OPC?')
return None return None
# when an argument is given as integer, it might be that this argument might be a float converters = [type(a) for a in args]
converters = [string_to_num if isinstance(a, int) else type(a) for a in args] values = [a if issubclass(c, str) else f'{a:g}'
values = [a if isinstance(a, str) else f'{a:g}'
for c, a in zip(converters, args)] for c, a in zip(converters, args)]
if ' ' in msghead: if ' ' in msghead:
query = msghead.replace(' ', '? ', 1) query = msghead.replace(' ', '? ', 1)
@ -847,7 +839,7 @@ class MainOutput(Output):
} }
_htr_range = 1 _htr_range = 1
heater_ranges = {5 - i: 10 ** -i for i in range(5)} heater_ranges = {5 - i: 10 ** - i for i in range(5)}
sorted_factors = sorted((v, i) for i, v in heater_ranges.items()) sorted_factors = sorted((v, i) for i, v in heater_ranges.items())
def read_status(self): def read_status(self):
@ -855,9 +847,6 @@ class MainOutput(Output):
return self.HTRST_MAP[st] return self.HTRST_MAP[st]
def configure(self): def configure(self):
if self._desired_max_power is None:
self.log.info(f'max_heater {self.writeDict} {self.max_heater}')
self.write_max_heater(self.max_heater)
self._htr_range = irng = self.get_best_power_idx(self._desired_max_power) self._htr_range = irng = self.get_best_power_idx(self._desired_max_power)
user_current = max(0.1, min(self.imax, 2 * math.sqrt(self._desired_max_power / user_current = max(0.1, min(self.imax, 2 * math.sqrt(self._desired_max_power /
self.heater_ranges[irng] / self.resistance))) self.heater_ranges[irng] / self.resistance)))
@ -1191,7 +1180,7 @@ class MainOutput336(MainOutput):
imax = 2 imax = 2
vmax = 50 vmax = 50
# 3 ranges only # 3 ranges only
heater_ranges = {3 - i: 10 ** -i for i in range(3)} heater_ranges = {i: 10 ** (3 - i) for i in range(5)}
sorted_factors = sorted((v, i) for i, v in heater_ranges.items()) sorted_factors = sorted((v, i) for i, v in heater_ranges.items())

View File

@ -1,413 +0,0 @@
# *****************************************************************************
#
# 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
#
#
#
# *****************************************************************************
import sys
from time import monotonic
from ast import literal_eval
import snap7
from frappy.core import Attached, Command, Readable, Parameter, FloatRange, HasIO, Property, StringType, \
IDLE, BUSY, WARN, ERROR, Writable, Drivable, BoolType, IntRange, Communicator, StatusType
from frappy.errors import CommunicationFailedError, ConfigError
from threading import RLock
class IO(Communicator):
tcap_client = Property('tcap_client', IntRange())
tsap_server = Property('tcap_server', IntRange())
ip_address = Property('numeric ip address', StringType())
_plc = None
_last_try = 0
def initModule(self):
self._lock = RLock()
super().initModule()
def _init(self):
if monotonic() < self._last_try + 10:
raise CommunicationFailedError('logo PLC not reachable')
self._plc = snap7.logo.Logo()
sys.stderr = open('/dev/null', 'w') # suppress output of snap7
try:
self._plc.connect(self.ip_address, self.tcap_client, self.tsap_server)
if self._plc.get_connected():
return
except Exception:
pass
finally:
sys.stderr = sys.stdout
self._plc = None
self._last_try = monotonic()
raise CommunicationFailedError('logo PLC not reachable')
def communicate(self, cmd):
with self._lock:
if not self._plc:
self._init()
cmd = cmd.split(maxsplit=1)
if len(cmd) == 2:
self.comLog('> %s %s', cmd[0], cmd[1])
self._plc.write(cmd[0], literal_eval(cmd[1]))
self.comLog('< OK')
try:
self.comLog('> %s', cmd[0])
reply = self._plc.read(cmd[0])
self.comLog('< %s', reply)
return str(reply)
except Exception as e:
if self._plc:
self.comLog('? %r', e)
self.log.exception('error in plc read')
self._plc = None
raise
class LogoMixin(HasIO):
ioclass = IO
def get_vm_value(self, vm_address):
return literal_eval(self.io.communicate(vm_address))
def set_vm_value(self, vm_address, value):
return literal_eval(self.io.communicate(f'{vm_address} {round(value)}'))
class DigitalActuator(LogoMixin, Writable):
"""output with or without feedback"""
output_addr = Property('VM address output', datatype=StringType(), default='')
target_addr = Property('VM address target', datatype=StringType(), default='')
feedback_addr = Property('VM address feedback', datatype=StringType(), default='')
target = Parameter('target', datatype=BoolType())
value = Parameter('feedback or output', datatype=BoolType())
_input = 'output'
_value_addr = None
_target_addr = None
_fault = None
def doPoll(self):
self.read_status() # this calls also read_value
def checkProperties(self):
super().checkProperties()
if self.feedback_addr:
self._input = 'feedback'
self._value_addr = self.feedback_addr
else:
self._input = 'output'
self._value_addr = self.output_addr
self._target_addr = self.target_addr or self.output_addr
if self._target_addr and self._value_addr:
self._check_feedback = self.feedback_addr and self.output_addr
return
raise ConfigError('need either output_addr or both feedback_addr and target_addr')
def initialReads(self):
super().initialReads()
self.target = self.read_value()
def set_fault(self, value, statustext):
"""on a fault condition, set target to value
and status to statustext
"""
self.write_target(value)
self._fault = statustext
self.read_status()
def reset_fault(self):
"""reset fault condition"""
self._fault = None
self.read_status()
def read_value(self):
return self.get_vm_value(self._value_addr)
def write_target(self, target):
self._fault = None
self.set_vm_value(self._target_addr, target)
value = self.read_value()
if value != target and self.feedback_addr:
# retry only if we have a feedback and the feedback did not change yet
for i in range(20):
if self.read_value() == target:
self.log.debug('tried %d times', i)
break
self.set_vm_value(self._target_addr, target)
def read_status(self):
if self._fault:
return ERROR, self._fault
value = self.read_value()
if value != self.target:
return ERROR, 'value and target do not match'
if self._check_feedback:
if value != self.get_vm_value(self._check_feedback):
return ERROR, f'feedback does not match output'
if self.feedback_addr:
return IDLE, 'feedback confirmed'
return IDLE, ''
class DelayedActuator(DigitalActuator, Drivable):
delay_addr = Property('address of delay value', StringType())
_pulse_start = 0
_pulse_end = 0
_fault = None
def read_status(self):
if self._fault:
return ERROR, self._fault
value = self.read_value()
fberror = None
if self._pulse_start:
now = monotonic()
if now < self._pulse_start + 1:
value = 1
elif now < self._pulse_end - 1:
if not value:
self._pulse_start = 0
return WARN, f'{self._input} is not on during pulse - due to interlock?'
if value:
if now < self._pulse_end + 1:
return BUSY, 'pulsing'
self.log.warn('pulse timeout')
self.set_vm_value(self._target_addr, 0)
self._pulse_start = 0
self.set_vm_value(self.delay_addr, 0)
elif self._check_feedback and value != self.get_vm_value(self._check_feedback):
fberror = ERROR, f'feedback does not match output'
if value != self.target:
return ERROR, 'value does not match target'
self.setFastPoll(False)
if fberror:
return fberror
if self.feedback_addr:
return IDLE, 'feedback confirmed'
return IDLE, ''
def write_target(self, value):
self._pulse_start = 0
if not value:
self.set_vm_value(self.delay_addr, 0)
return super().write_target(value)
@Command(argument=FloatRange(0))
def pulse(self, delay):
"""open for delay seconds"""
self.set_vm_value(self.delay_addr, delay)
self.set_vm_value(self._target_addr, 1)
self.set_vm_value(self._target_addr, 0)
self.setFastPoll(True, 0.5)
self.status = BUSY, 'pulsing'
now = monotonic()
self._pulse_start = now
self._pulse_end = now + delay
class Value(LogoMixin, Readable):
addr = Property('VM address', datatype=StringType())
def read_value(self):
return self.get_vm_value(self.addr)
def read_status(self):
return IDLE, ''
class DigitalValue(Value):
value = Parameter('airpressure state', datatype=BoolType())
# TODO: the following classes are too specific, they have to be moved
class Pressure(LogoMixin, Drivable):
vm_address = Property('VM address', datatype=StringType())
value = Parameter('pressure', datatype=FloatRange(unit='mbar'))
# pollinterval = 0.5
def read_value(self):
return self.get_vm_value(self.vm_address)
def read_status(self):
return IDLE, ''
class Airpressure(LogoMixin, Readable):
vm_address = Property('VM address', datatype=StringType())
value = Parameter('airpressure state', datatype=BoolType())
# pollinterval = 0.5
def read_value(self):
if (self.get_vm_value(self.vm_address) > 500):
return 1
else:
return 0
def read_status(self):
return IDLE, ''
class Valve(LogoMixin, Drivable):
vm_address_input = Property('VM address input', datatype=StringType())
vm_address_output = Property('VM address output', datatype=StringType())
target = Parameter('Valve target', datatype=BoolType())
value = Parameter('Value state', datatype=BoolType())
_remaining_tries = None
def read_value(self):
return self.get_vm_value(self.vm_address_input)
def write_target(self, target):
self.set_vm_value(self.vm_address_output, target)
self._remaining_tries = 5
self.status = BUSY, 'switching'
self.setFastPoll(True, 0.5)
def read_status(self):
self.log.debug('read_status')
value = self.read_value()
self.log.debug('value %d target %d', value, self.target)
if value != self.target:
if self._remaining_tries is None:
self.target = self.read_value()
return IDLE, ''
self._remaining_tries -= 1
if self._remaining_tries < 0:
self.setFastPoll(False)
return ERROR, 'too many tries to switch'
self.set_vm_value(self.vm_address_output, self.target)
return BUSY, 'switching (try again)'
self.setFastPoll(False)
return IDLE, ''
class FluidMachines(LogoMixin, Drivable):
vm_address_output = Property('VM address output', datatype=StringType())
target = Parameter('Valve target', datatype=BoolType())
value = Parameter('Valve state', datatype=BoolType())
def read_value(self):
return self.get_vm_value(self.vm_address_output)
def write_target(self, target):
return self.set_vm_value(self.vm_address_output, target)
def read_status(self):
return IDLE, ''
class TempSensor(LogoMixin, Readable):
vm_address = Property('VM address', datatype=StringType())
value = Parameter('resistance', datatype=FloatRange(unit='Ohm'))
def read_value(self):
return self.get_vm_value(self.vm_address)
def read_status(self):
return IDLE, ''
class HeaterParam(LogoMixin, Writable):
vm_address = Property('VM address output', datatype=StringType())
target = Parameter('Heater target', datatype=IntRange())
value = Parameter('Heater Param', datatype=IntRange())
def read_value(self):
return self.get_vm_value(self.vm_address)
def write_target(self, target):
return self.set_vm_value(self.vm_address, target)
def read_status(self):
return IDLE, ''
class controlHeater(LogoMixin, Writable):
vm_address = Property('VM address on switch', datatype=StringType())
target = Parameter('Heater state', datatype=BoolType())
value = Parameter('Heater state', datatype=BoolType())
def read_value(self):
return self.get_vm_value(self.vm_address_on)
def write_target(self, target):
if (target):
return self.set_vm_value(self.vm_address, True)
else:
return self.set_vm_value(self.vm_address, False)
def read_status(self):
return IDLE, ''
class safetyfeatureState(LogoMixin, Readable):
vm_address = Property('VM address state', datatype=StringType())
value = Parameter('safety Feature state', datatype=BoolType())
def read_value(self):
return self.get_vm_value(self.vm_address)
def read_status(self):
return IDLE, ''
class safetyfeatureParam(LogoMixin, Writable):
vm_address = Property('VM address output', datatype=StringType())
target = Parameter('safety Feature target', datatype=IntRange())
value = Parameter('safety Feature Param', datatype=IntRange())
def read_value(self):
return self.get_vm_value(self.vm_address)
def write_target(self, target):
return self.set_vm_value(self.vm_address, target)
def read_status(self):
return IDLE, ''
class comparatorgekoppeltParam(LogoMixin, Writable):
vm_address_1 = Property('VM address output', datatype=StringType())
vm_address_2 = Property('VM address output', datatype=StringType())
target = Parameter('safety Feature target', datatype=IntRange())
value = Parameter('safety Feature Param', datatype=IntRange())
def read_value(self):
return self.get_vm_value(self.vm_address_1)
def write_target(self, target):
self.set_vm_value(self.vm_address_1, target)
return self.set_vm_value(self.vm_address_2, target)
def read_status(self):
return IDLE, ''

View File

@ -68,8 +68,8 @@ class LakeShoreIO(HasIO):
args.insert(0, cmd) args.insert(0, cmd)
else: else:
args[0] = cmd + args[0] args[0] = cmd + args[0]
tail = ','.join(args) head = ','.join(args)
head = cmd.replace(' ', '?') tail = cmd.replace(' ', '?')
reply = self.io.communicate(f'{head};{tail}') reply = self.io.communicate(f'{head};{tail}')
return parse_result(reply) return parse_result(reply)
@ -166,7 +166,6 @@ class Switcher(LakeShoreIO, ChannelSwitcher):
def set_active_channel(self, chan): def set_active_channel(self, chan):
self.set_param('SCAN ', chan.channel, 0) self.set_param('SCAN ', chan.channel, 0)
self.value = chan.channel
chan._last_range_change = time.monotonic() chan._last_range_change = time.monotonic()
self.set_delays(chan) self.set_delays(chan)
@ -279,12 +278,7 @@ class ResChannel(LakeShoreIO, Channel):
vexc = 0 if excoff or iscur else exc vexc = 0 if excoff or iscur else exc
if (rng, iexc, vexc) != (self.range, self.iexc, self.vexc): if (rng, iexc, vexc) != (self.range, self.iexc, self.vexc):
self._last_range_change = time.monotonic() self._last_range_change = time.monotonic()
try: self.range, self.iexc, self.vexc = rng, iexc, vexc
self.range, self.iexc, self.vexc = rng, iexc, vexc
except Exception:
# avoid raising errors on disabled channel
if self.enabled:
raise
@CommonWriteHandler(rdgrng_params) @CommonWriteHandler(rdgrng_params)
def write_rdgrng(self, change): def write_rdgrng(self, change):

View File

@ -1,32 +0,0 @@
# *****************************************************************************
#
# 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:
# Andrea Plank <andrea.plank@psi.ch>
#
# *****************************************************************************
from frappy.core import Parameter, BoolType, Writable
from frappy.persistent import PersistentMixin, PersistentParam
class ManualValve(PersistentMixin, Writable):
target = PersistentParam('Valve target', datatype=BoolType(), persistent='auto')
value = Parameter('Valve state', datatype=BoolType())
def write_target(self, target):
self.value = target
return self.value

View File

@ -375,7 +375,6 @@ class HeaterOutput(HasInput, Writable):
class HeaterUpdate(HeaterOutput): class HeaterUpdate(HeaterOutput):
kind = 'HTR,TEMP' kind = 'HTR,TEMP'
target = 0 # switch off loop on startup
def update_target(self, module, value): def update_target(self, module, value):
self.change(f'DEV::TEMP:LOOP:ENAB', False, off_on) self.change(f'DEV::TEMP:LOOP:ENAB', False, off_on)

View File

@ -21,12 +21,12 @@
"""modules to access parameters""" """modules to access parameters"""
import re
from frappy.core import Drivable, EnumType, IDLE, Attached, StringType, Property, \ from frappy.core import Drivable, EnumType, IDLE, Attached, StringType, Property, \
Parameter, BoolType, FloatRange, Readable, ERROR, nopoll Parameter, BoolType, FloatRange, Readable, ERROR, nopoll
from frappy.errors import ConfigError from frappy.errors import ConfigError
from frappy_psi.convergence import HasConvergence from frappy_psi.convergence import HasConvergence
from frappy_psi.mixins import HasRamp from frappy_psi.mixins import HasRamp
from frappy.lib import merge_status
class Par(Readable): class Par(Readable):
@ -255,41 +255,3 @@ class SwitchDriv(HasConvergence, Drivable):
self.log.info('target=%g (%s)', target, this.name) self.log.info('target=%g (%s)', target, this.name)
this.write_target(target1) this.write_target(target1)
return target return target
INDEX = re.compile(r'(.*)\[(.*)\]')
class Comp(Readable):
value = Parameter(datatype=FloatRange(unit='$'))
read = Attached(description='<module>.<parameter> for read')
unit = Property('main unit', StringType())
_parname = None
_index = None
def setProperty(self, key, value):
if key == 'read':
value, param = value.split('.')
match = INDEX.match(param)
if match:
self._param, i = match.groups()
self._index = int(i)
else:
self._param = param
super().setProperty(key, value)
def checkProperties(self):
self.applyMainUnit(self.unit)
if self._param == self.name:
raise ConfigError('illegal recursive read module')
super().checkProperties()
def read_value(self):
par = getattr(self.read, self._param)
if self._index is None:
return par
return par[self._index]
def read_status(self):
return IDLE, ''

View File

@ -1,177 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Mon Apr 29 09:24:07 2024
@author: andreaplank
"""
from frappy.core import Readable, Parameter, FloatRange, BoolType, StringIO, HasIO, \
Property, StringType, Drivable, IntRange, IDLE, BUSY, ERROR, nopoll
from frappy.errors import CommunicationFailedError
class PfeifferProtocol(StringIO):
end_of_line = '\r'
@staticmethod
def calculate_crc(data):
crc = sum(ord(ch) for ch in data) % 256
return f'{crc:03d}'
@staticmethod
def check_crc(data):
if data[-3:] != PfeifferProtocol.calculate_crc(data[:-3]):
raise CommunicationFailedError('Bad crc')
def communicate(self, cmd):
cmd += self.calculate_crc(cmd)
reply = super().communicate(cmd)
self.check_crc(reply)
return reply[:-3]
class PfeifferMixin(HasIO):
ioClass = PfeifferProtocol
address= Property('Addresse', datatype= IntRange())
def data_request_u_expo_new(self, parameter_nr):
reply = self.communicate(f'{self.address:03d}00{parameter_nr:03d}02=?')
assert int(reply[5:8]) == parameter_nr
assert int(reply[0:3]) == self.address
try:
exponent = int(reply[14:16])-23
except ValueError:
raise CommunicationFailedError(f'got {reply[10:16]}')
return float(f'{reply[10:14]}e{exponent}')
def data_request_old_boolean(self, parameter_nr):
reply = self.communicate(f'{self.address:03d}00{parameter_nr:03d}02=?')
assert int(reply[5:8]) == parameter_nr, f"Parameter number mismatch: expected {parameter_nr}, got {int(reply[5:8])}"
assert int(reply[0:3]) == self.address, f"Address mismatch: expected {self.address}, got {int(reply[0:3])}"
if reply[12] == "1":
value = True
elif reply[12] == "0":
value = False
else:
raise CommunicationFailedError(f'got {reply[10:16]}')
return value
def data_request_u_real(self, parameter_nr):
reply = self.communicate(f'{self.address:03d}00{parameter_nr:03d}02=?')
assert int(reply[5:8]) == parameter_nr
assert int(reply[0:3]) == self.address
try:
value = float(reply[10:16])/100
except ValueError:
raise CommunicationFailedError(f'got {reply[10:16]}')
return value
def data_request_u_int(self, parameter_nr):
reply = self.communicate(f'{self.address:03d}00{parameter_nr:03d}02=?')
if reply[8] == "0":
reply_length = int(reply[9])
else:
reply_length = int(reply[8:10])
try:
if reply[10 : 10 + reply_length] == "000000":
value = 0
else:
value = float(reply[10 : 10 + reply_length].lstrip("0"))
except ValueError:
raise CommunicationFailedError(f'got {reply[10:16]}')
return value
def data_request_string(self, parameter_nr):
reply = self.communicate(f'{self.address:03d}00{parameter_nr:03d}02=?')
assert int(reply[5:8]) == parameter_nr
assert int(reply[0:3]) == self.address
return str(reply[10:16])
def control_old_boolean(self, parameter_nr, target):
if target:
val = 1
else:
val = 0
cmd = f'{self.address:03d}10{parameter_nr:03d}06{str(val)*6}'
reply = self.communicate(cmd)
assert cmd == reply, f'got {reply} instead of {cmd} '
try:
if reply[11] == "1":
value = 1
else:
value = 0
except ValueError:
raise CommunicationFailedError(f'got {reply[10:16]}')
return value
class RPT200(PfeifferMixin, Readable):
value = Parameter('Pressure', FloatRange(unit='mbar'))
def read_value(self):
return self.data_request_u_expo_new(740)
def read_status(self):
errtxt = self.data_request_string(303)
if errtxt == "000000":
return IDLE, ''
else:
return ERROR, errtxt
class TCP400(PfeifferMixin, Drivable, Readable):
speed= Parameter('Rotational speed', FloatRange(unit='Hz'), readonly = False)
target= Parameter('Pumping station', BoolType())
current= Parameter('Current consumption', FloatRange(unit='%'))
value = Parameter('Turbopump state', BoolType())
temp = Parameter('temp', FloatRange(unit='C'))
def read_temp (self):
return self.data_request_u_int(326)
def read_speed(self):
return self.data_request_u_int(309)
def read_value(self):
return self.data_request_old_boolean(10)
def read_current(self):
return self.data_request_u_real(310)
def write_target(self, target):
return self.control_old_boolean(10, target)
def read_target(self):
return self.data_request_old_boolean(10)
def read_status(self):
if not self.data_request_old_boolean(306):
if self.target:
return BUSY, 'ramping up'
else:
return IDLE, ''
else:
return IDLE, 'at targetspeed'

View File

@ -166,7 +166,7 @@ class Motor(HasOffset, HasStates, PersistentMixin, HasIO, Drivable):
def write_target(self, value): def write_target(self, value):
self.read_alive_time() self.read_alive_time()
if self._blocking_error: if self._blocking_error:
self.status = ERROR, '<motor>.clear_errors() needed after ' + self._blocking_error self.status = ERROR, 'clear_errors needed after ' + self._blocking_error
raise HardwareError(self.status[1]) raise HardwareError(self.status[1])
self.saveParameters() self.saveParameters()
self.start_machine(self.starting, target=value) self.start_machine(self.starting, target=value)

View File

@ -48,8 +48,8 @@ example cfg:
Mod('T_softloop', Mod('T_softloop',
'frappy_psi.picontrol.PI', 'frappy_psi.picontrol.PI',
'softloop controlled Temperature mixing chamber', 'softloop controlled Temperature mixing chamber',
input_module = 'ts', input = 'ts',
output_module = 'htr_mix', output = 'htr_mix',
control_active = 1, control_active = 1,
output_max = 80000, output_max = 80000,
p = 2E6, p = 2E6,
@ -60,10 +60,10 @@ example cfg:
import time import time
import math import math
from frappy.core import Readable, Writable, Parameter, Attached, IDLE, Property from frappy.core import Readable, Writable, Parameter, Attached, IDLE
from frappy.lib import clamp from frappy.lib import clamp
from frappy.datatypes import LimitsType, EnumType, BoolType, FloatRange from frappy.datatypes import LimitsType, EnumType, BoolType, FloatRange
from frappy.newmixins import HasOutputModule from frappy.mixins import HasOutputModule
from frappy_psi.convergence import HasConvergence from frappy_psi.convergence import HasConvergence
@ -71,27 +71,32 @@ class PImixin(HasOutputModule, Writable):
p = Parameter('proportional term', FloatRange(0), readonly=False) p = Parameter('proportional term', FloatRange(0), readonly=False)
i = Parameter('integral term', FloatRange(0), readonly=False) i = Parameter('integral term', FloatRange(0), readonly=False)
# output_module is inherited # output_module is inherited
output_range = Property('legacy output range', LimitsType(FloatRange()), default=(0,0)) output_range = Parameter('min output',
output_min = Parameter('min output', FloatRange(), default=0, readonly=False) LimitsType(FloatRange()), default=(0, 0), readonly=False)
output_max = Parameter('max output', FloatRange(), default=0, readonly=False)
output_func = Parameter('output function', output_func = Parameter('output function',
EnumType(lin=0, square=1), readonly=False, default=0) EnumType(lin=0, square=1), readonly=False, default=0)
value = Parameter(unit='K') value = Parameter(unit='K')
_lastdiff = None _lastdiff = None
_lasttime = 0 _lasttime = 0
_get_range = None # a function get output range from output_module _clamp_limits = None
_overflow = 0
def initModule(self):
super().initModule()
if self.output_range != (0, 0): # legacy !
self.output_min, self.output_max = self.output_range
def doPoll(self): def doPoll(self):
super().doPoll() super().doPoll()
if self._clamp_limits is None:
out = self.output_module
if hasattr(out, 'max_target'):
if hasattr(self, 'min_target'):
self._clamp_limits = lambda v, o=out: clamp(v, o.read_min_target(), o.read_max_target())
else:
self._clamp_limits = lambda v, o=out: clamp(v, 0, o.read_max_target())
elif hasattr(out, 'limit'): # mercury.HeaterOutput
self._clamp_limits = lambda v, o=out: clamp(v, 0, o.read_limit())
else:
self._clamp_limits = lambda v: v
if self.output_range == (0.0, 0.0):
self.output_range = (0, self._clamp_limits(float('inf')))
if not self.control_active: if not self.control_active:
return return
out = self.output_module
self.status = IDLE, 'controlling' self.status = IDLE, 'controlling'
now = time.time() now = time.time()
deltat = clamp(0, now-self._lasttime, 10) deltat = clamp(0, now-self._lasttime, 10)
@ -101,51 +106,17 @@ class PImixin(HasOutputModule, Writable):
self._lastdiff = diff self._lastdiff = diff
deltadiff = diff - self._lastdiff deltadiff = diff - self._lastdiff
self._lastdiff = diff self._lastdiff = diff
output, omin, omax = self._cvt2int(out.target)
output += self._overflow + self.p * deltadiff + self.i * deltat * diff
if output < omin:
self._overflow = max(omin - omax, output - omin)
output = omin
elif output > omax:
self._overflow = min(omax - omin, output - omax)
output = omax
else:
self._overflow = 0
out.update_target(self.name, self._cvt2ext(output))
def cvt2int_square(self, output):
return (math.sqrt(max(0, clamp(x, *self._get_range()))) for x in (output, self.output_min, self.output_max))
def cvt2ext_square(self, output):
return output ** 2
def cvt2int_lin(self, output):
return (clamp(x, *self._get_range()) for x in (output, self.output_min, self.output_max))
def cvt2ext_lin(self, output):
return output
def write_output_func(self, value):
out = self.output_module out = self.output_module
if hasattr(out, 'max_target'): output = out.target
if hasattr(self, 'min_target'): if self.output_func == 'square':
self._get_range = lambda o=out: (o.read_min_target(), o.read_max_target()) output = math.sqrt(max(0, output))
else: output += self.p * deltadiff + self.i * deltat * diff
self._get_range = lambda o=out: (0, o.read_max_target()) if self.output_func == 'square':
elif hasattr(out, 'limit'): # mercury.HeaterOutput output = output ** 2
self._get_range = lambda o=out: (0, o.read_limit()) output = self._clamp_limits(output)
else: out.update_target(self.name, clamp(output, *self.output_range))
if self.output_min == self.output_max == 0:
self.output_max = 1
self._get_range = lambda o=self: (o.output_min, o.output_max)
if self.output_min == self.output_max == 0:
self.output_min, self.output_max = self._get_range()
self.output_func = value
self._cvt2int = getattr(self, f'cvt2int_{self.output_func.name}')
self._cvt2ext = getattr(self, f'cvt2ext_{self.output_func.name}')
def write_control_active(self, value): def write_control_active(self, value):
super().write_control_active(value)
if not value: if not value:
self.output_module.write_target(0) self.output_module.write_target(0)
@ -154,6 +125,61 @@ class PImixin(HasOutputModule, Writable):
self.activate_control() self.activate_control()
# quick fix by Marek:
class PIobsolete(Writable):
"""temporary, but working version from Marek"""
input = Attached(Readable, 'the input module')
output = Attached(Writable, 'the output module')
output_max = Parameter('max output value', FloatRange(0), readonly=False)
p = Parameter('proportional term', FloatRange(0), readonly=False)
i = Parameter('integral term', FloatRange(0), readonly=False)
control_active = Parameter('control flag', BoolType(), readonly=False, default=False)
value = Parameter(unit='K')
tlim = Parameter('max Temperature', FloatRange(0), readonly=False)
_lastdiff = None
_lasttime = 0
_lastvalue = 0
def doPoll(self):
super().doPoll()
if not self.control_active:
return
self.value = self.input.value
self.status = IDLE, 'controlling'
now = time.time()
deltat = min(10.0, now-self._lasttime)
self._lasttime = now
if self.value != self._lastvalue:
diff = self.target - self.value # calculate the difference to target
self._lastvalue = self.value
# else ? (diff is undefined!)
if self.value > self.tlim:
self.write_control_active(False)
return
if self._lastdiff is None:
self._lastdiff = diff
deltadiff = diff - self._lastdiff # calculate the change in deltaT
self._lastdiff = diff
output = self.output.target
output += self.p * deltadiff + self.i * deltat * diff
if output > self.output_max:
output = self.output_max
elif output < 0:
output = 0
self.output.write_target(output)
def write_control_active(self, value):
if not value:
self.output.write_target(0)
def write_target(self, value):
self.control_active = True
# proposal for replacing above PI class, inheriting from PImixin
# additional features:
# - is a Drivable, using the convergence criteria from HasConvergence
# - tries to determine the output limits automatically
# unchecked! # unchecked!
class PI(HasConvergence, PImixin): class PI(HasConvergence, PImixin):
@ -164,18 +190,3 @@ class PI(HasConvergence, PImixin):
def read_status(self): def read_status(self):
return self.input_module.status return self.input_module.status
class PI2(PI):
maxovershoot = Parameter('max. overshoot', FloatRange(0, 100, unit='%'), readonly=False, default=20)
def doPoll(self):
self.output_max = self.target * (1 + 0.01 * self.maxovershoot)
self.output_min = self.target * (1 - 0.01 * self.maxovershoot)
super().doPoll()
def write_target(self, target):
if not self.control_active:
self.output.write_target(target)
super().write_target(target)

View File

@ -213,10 +213,7 @@ class SeaClient(ProxyClient, Module):
if not self._connected.is_set(): if not self._connected.is_set():
if self._connect_thread is None: if self._connect_thread is None:
# let doPoll do the reconnect # let doPoll do the reconnect
if self.pollInfo: # is None in playground self.pollInfo.trigger(True)
self.pollInfo.trigger(True)
else:
self.log.info('sea_main.doPoll() will connect')
raise ConnectionClosed('disconnected - reconnect is tried later') raise ConnectionClosed('disconnected - reconnect is tried later')
return self.raw_request(command, quiet) return self.raw_request(command, quiet)
@ -560,12 +557,10 @@ class SeaModule(Module):
else: else:
cls.paramFilter(result, paramdesc) cls.paramFilter(result, paramdesc)
cfgdict.pop('rel_paths', None) cfgdict.pop('rel_paths', None)
params = [] params = sum(result.values(), [])
for key, plist in result.items():
params.extend(plist)
if is_running: # take this at end if is_running: # take this at end
params.append(is_running) params.append(is_running)
# this needs some care in the configuration: the main value must be the first!
main_value = params[0] main_value = params[0]
if issubclass(cls, Readable): if issubclass(cls, Readable):
if 'key' in main_value: if 'key' in main_value:
@ -667,24 +662,20 @@ class SeaModule(Module):
path2param.setdefault(hdbpath, []).append((name, key)) path2param.setdefault(hdbpath, []).append((name, key))
attributes[key] = pobj attributes[key] = pobj
def rfunc(self, cmd=f'hval {base}/{path}'):
reply = self.io.query(cmd, True)
try:
reply = float(reply)
except ValueError:
pass
# an updateEvent will be handled before above returns
return reply
rfunc.poll = False
if key != 'status' and key is not None: if key != 'status' and key is not None:
rfunc = getattr(cls, f'read_{key}', None) attributes['read_' + key] = rfunc
# do not override existing read method
if rfunc is None:
def rfunc(self, cmd=f'hval {base}/{path}'): if not readonly:
reply = self.io.query(cmd, True)
try:
reply = float(reply)
except ValueError:
pass
# an updateEvent will be handled before above returns
return reply
rfunc.poll = False
attributes['read_' + key] = rfunc
if not readonly and key:
def wfunc(self, value, datatype=datatype, command=paramdesc['cmd']): def wfunc(self, value, datatype=datatype, command=paramdesc['cmd']):
value = datatype.export_value(value) value = datatype.export_value(value)
@ -720,15 +711,9 @@ class SeaModule(Module):
@classmethod @classmethod
def paramFilter(cls, result, paramdesc): def paramFilter(cls, result, paramdesc):
sub = paramdesc['path'].split('/', 1) sub = paramdesc['path'].split('/', 1)
sublist = result.get(sub[0] or '.') sublist = result.get(sub[0])
if sublist is None: if sublist is None:
sublist = result.get('.') return False
if sublist is None:
return False
if len(sub) > 1 or 'kids' in paramdesc:
# avoid params from subtrees
return False
# this is a top parameter without kids
sublist.append(paramdesc) sublist.append(paramdesc)
return True return True
@ -860,20 +845,9 @@ class SeaDrivable(SeaReadable, Drivable):
class LscDrivable(SeaDrivable): class LscDrivable(SeaDrivable):
def __new__(cls, name, logger, cfgdict, srv): def __new__(cls, name, logger, cfgdict, srv):
sensor_path = pop_cfg(cfgdict, 'sensor_path', 'tm') cfgdict['rel_paths'] = [pop_cfg(cfgdict, 'sensor_path', 'tm'), '.',
set_path = pop_cfg(cfgdict, 'set_path', 'set') pop_cfg(cfgdict, 'set_path', 'set'), 'dblctrl']
cfgdict['rel_paths'] = ['.', sensor_path, set_path , 'dblctrl'] return super().__new__(cls, name, logger, cfgdict, srv)
mobj = super().__new__(cls, name, logger, cfgdict, srv)
mobj._sensor_path = sensor_path
def ufunc(value, timestamp, readerror, self=mobj):
if not self.dblctrl:
self.update_value(value, timestamp, readerror)
self.announceUpdate(self._sensor_path, value, readerror, timestamp)
setattr(mobj, f'update_{sensor_path}', ufunc)
mobj._read_value_error = False
return mobj
@classmethod @classmethod
def paramFilter(cls, result, paramdesc): def paramFilter(cls, result, paramdesc):
@ -884,17 +858,3 @@ class LscDrivable(SeaDrivable):
result['.'].append(paramdesc) result['.'].append(paramdesc)
return True return True
return False return False
def update_value(self, value, timestamp, readerror):
self._read_value_error = readerror
if self.dblctrl and not readerror:
super().update_value(value, timestamp, readerror)
def read_value(self):
if self.io.syncio:
if self.dblctrl and not self._read_value_error:
reply = self.io.query('hval tt', True)
else:
reply = self.io.query(f'hval tt/{self._sensor_path}', True)
return float(reply)
return self.value

Some files were not shown because too many files have changed in this diff Show More