Compare commits
1 Commits
wip
...
hot_lakesh
Author | SHA1 | Date | |
---|---|---|---|
aa64a48ec5 |
@ -24,14 +24,12 @@
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
import socket
|
||||
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 init, run, clientenv, interact
|
||||
from frappy.protocol.discovery import scan
|
||||
|
||||
|
||||
def parseArgv(argv):
|
||||
@ -39,9 +37,6 @@ def parseArgv(argv):
|
||||
parser.add_argument('-i', '--include',
|
||||
help='file to execute after connecting to the clients', metavar='file',
|
||||
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',
|
||||
help='Do not go into interactive mode after executing files. \
|
||||
Has no effect without --include.', action='store_true')
|
||||
@ -51,38 +46,9 @@ def parseArgv(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:])
|
||||
|
||||
nodes = 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)
|
||||
success = init(*args.node)
|
||||
|
||||
run_error = ''
|
||||
file_success = False
|
||||
|
@ -23,12 +23,12 @@
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from frappy.lib import generalConfig
|
||||
from frappy.logging import logger
|
||||
|
||||
# Add import path for inplace usage
|
||||
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.playground import play, USAGE
|
||||
|
||||
|
@ -23,36 +23,106 @@
|
||||
"""SEC node autodiscovery tool."""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import select
|
||||
import socket
|
||||
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):
|
||||
if short:
|
||||
# NOTE: keep this easily parseable!
|
||||
print(f'{answer.equipment_id} {answer.hostname}:{answer.port}')
|
||||
print(f'{answer.equipment_id} {answer.address}:{answer.port}')
|
||||
return
|
||||
numeric = f' ({answer.address})' if answer.address == answer.hostname else ''
|
||||
print(f'Found {answer.equipment_id} at {answer.hostname}{numeric}:')
|
||||
print(f'Found {answer.equipment_id} at {answer.address}:')
|
||||
print(f' Port: {answer.port}')
|
||||
print(f' Firmware: {answer.firmware}')
|
||||
desc = answer.description.replace('\n', '\n ')
|
||||
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__':
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('-l', '--listen', action='store_true',
|
||||
help='Keep listening after the broadcast.')
|
||||
parser.add_argument('-s', '--short', action='store_true',
|
||||
help='Print short info (always on when listen).')
|
||||
help='Print short info. '
|
||||
'Keep listening after the broadcast.')
|
||||
args = parser.parse_args(sys.argv[1:])
|
||||
short = args.listen or args.short
|
||||
if not short:
|
||||
print('-' * 80)
|
||||
for answer in scan():
|
||||
print_answer(answer, short=short)
|
||||
print_answer(answer, short=args.listen)
|
||||
if args.listen:
|
||||
for answer in listen():
|
||||
print_answer(short=short)
|
||||
listen(short=args.listen)
|
||||
|
@ -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()
|
65
bin/us-plot
65
bin/us-plot
@ -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
67
cfg/PEUS.py
Normal 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)
|
@ -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
62
cfg/RUS.py
Normal 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',
|
||||
)
|
@ -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
15
cfg/addons/ah2700_cfg.py
Executable file → Normal file
@ -2,21 +2,8 @@ Node('ah2700.frappy.psi.ch',
|
||||
'Andeen Hagerlin 2700 Capacitance Bridge',
|
||||
)
|
||||
|
||||
Mod('cap_io',
|
||||
'frappy_psi.ah2700.Ah2700IO',
|
||||
'',
|
||||
uri='linse-976d-ts:3006',
|
||||
)
|
||||
|
||||
Mod('cap',
|
||||
'frappy_psi.ah2700.Capacitance',
|
||||
'capacitance',
|
||||
io = 'cap_io',
|
||||
)
|
||||
|
||||
Mod('loss',
|
||||
'frappy_psi.parmod.Par',
|
||||
'loss parameter',
|
||||
read='cap.loss',
|
||||
unit='deg',
|
||||
uri='dil4-ts.psi.ch:3008',
|
||||
)
|
||||
|
@ -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',
|
||||
)
|
||||
|
337
cfg/dil5_cfg.py
337
cfg/dil5_cfg.py
@ -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,
|
||||
)
|
||||
|
136
cfg/dummy_cfg.py
136
cfg/dummy_cfg.py
@ -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',
|
||||
)
|
100
cfg/fi2_cfg.py
100
cfg/fi2_cfg.py
@ -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',
|
||||
)
|
||||
|
117
cfg/fi_cfg.py
117
cfg/fi_cfg.py
@ -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,
|
||||
)
|
130
cfg/fs_cfg.py
130
cfg/fs_cfg.py
@ -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,
|
||||
)
|
@ -4,4 +4,4 @@ logdir = ./log
|
||||
piddir = ./pid
|
||||
confdir = ./cfg
|
||||
comlog = True
|
||||
omit_unchanged_within = 60
|
||||
|
||||
|
@ -6,8 +6,7 @@ Node('LscSIM.psi.ch',
|
||||
Mod('io',
|
||||
'frappy_psi.ls370res.StringIO',
|
||||
'io for Ls370',
|
||||
# uri = 'localhost:2089',
|
||||
uri = 'linse-976d-ts:3007',
|
||||
uri = 'localhost:2089',
|
||||
)
|
||||
Mod('sw',
|
||||
'frappy_psi.ls370res.Switcher',
|
||||
@ -18,7 +17,7 @@ Mod('res1',
|
||||
'frappy_psi.ls370res.ResChannel',
|
||||
'resistivity chan 1',
|
||||
vexc = '2mV',
|
||||
channel = 2,
|
||||
channel = 1,
|
||||
switcher = 'sw',
|
||||
)
|
||||
Mod('res2',
|
||||
|
@ -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',
|
||||
)
|
@ -1,5 +1,5 @@
|
||||
Node('haake.config.sea.psi.ch',
|
||||
'Haake thermostat',
|
||||
Node('haakeuro.config.sea.psi.ch',
|
||||
'Haake thermostat + Eurotherm controller',
|
||||
)
|
||||
Mod('sea_main',
|
||||
'frappy_psi.sea.SeaClient',
|
||||
@ -13,5 +13,9 @@ Mod('th',
|
||||
io = 'sea_main',
|
||||
sea_object = 'th',
|
||||
extra_modules=['t2'],
|
||||
value=Param(unit='degC'),
|
||||
)
|
||||
Mod('ts',
|
||||
'frappy_psi.sea.SeaReadable', '',
|
||||
io='sea_main',
|
||||
single_module='th.t2',
|
||||
)
|
||||
|
@ -10,12 +10,11 @@ Mod('sea_main',
|
||||
)
|
||||
|
||||
Mod('tt',
|
||||
'frappy_psi.sea.LscDrivable', '',
|
||||
'frappy_psi.sea.SeaDrivable', '',
|
||||
io='sea_main',
|
||||
meaning=['temperature_regulation', 27],
|
||||
sea_object='tt',
|
||||
sensor_path='tm',
|
||||
set_path='set',
|
||||
rel_paths=['tm', '.', 'set', 'dblctrl'],
|
||||
)
|
||||
|
||||
Mod('cc',
|
||||
|
@ -10,12 +10,11 @@ Mod('sea_main',
|
||||
)
|
||||
|
||||
Mod('tt',
|
||||
'frappy_psi.sea.LscDrivable', '',
|
||||
'frappy_psi.sea.SeaDrivable', '',
|
||||
io='sea_main',
|
||||
meaning=['temperature_regulation', 27],
|
||||
sea_object='tt',
|
||||
sensor_path='tm',
|
||||
set_path='set',
|
||||
rel_paths=['tm', '.', 'set', 'dblctrl'],
|
||||
)
|
||||
|
||||
Mod('cc',
|
||||
|
@ -10,12 +10,11 @@ Mod('sea_main',
|
||||
)
|
||||
|
||||
Mod('tt',
|
||||
'frappy_psi.sea.LscDrivable', '',
|
||||
'frappy_psi.sea.SeaDrivable', '',
|
||||
io='sea_main',
|
||||
meaning=['temperature_regulation', 27],
|
||||
sea_object='tt',
|
||||
sensor_path='tm',
|
||||
set_path='set',
|
||||
rel_paths=['tm', '.', 'set', 'dblctrl'],
|
||||
)
|
||||
|
||||
Mod('cc',
|
||||
|
@ -10,12 +10,11 @@ Mod('sea_main',
|
||||
)
|
||||
|
||||
Mod('tt',
|
||||
'frappy_psi.sea.LscDrivable', '',
|
||||
'frappy_psi.sea.SeaDrivable', '',
|
||||
io='sea_main',
|
||||
meaning=['temperature_regulation', 27],
|
||||
sea_object='tt',
|
||||
sensor_path='tm',
|
||||
set_path='set',
|
||||
rel_paths=['tm', '.', 'set', 'dblctrl'],
|
||||
)
|
||||
|
||||
Mod('cc',
|
||||
|
@ -10,12 +10,11 @@ Mod('sea_main',
|
||||
)
|
||||
|
||||
Mod('tt',
|
||||
'frappy_psi.sea.LscDrivable', '',
|
||||
'frappy_psi.sea.SeaDrivable', '',
|
||||
io='sea_main',
|
||||
meaning=['temperature_regulation', 27],
|
||||
sea_object='tt',
|
||||
sensor_path='tm',
|
||||
set_path='set',
|
||||
rel_paths=['tm', '.', 'set', 'dblctrl'],
|
||||
)
|
||||
|
||||
Mod('cc',
|
||||
|
@ -10,12 +10,11 @@ Mod('sea_main',
|
||||
)
|
||||
|
||||
Mod('tt',
|
||||
'frappy_psi.sea.LscDrivable', '',
|
||||
'frappy_psi.sea.SeaDrivable', '',
|
||||
io='sea_main',
|
||||
meaning=['temperature_regulation', 27],
|
||||
sea_object='tt',
|
||||
sensor_path='tm',
|
||||
set_path='set',
|
||||
rel_paths=['tm', '.', 'set', 'dblctrl'],
|
||||
)
|
||||
|
||||
Mod('pauto',
|
||||
|
@ -14,8 +14,7 @@ Mod('tt',
|
||||
io='sea_main',
|
||||
meaning=['temperature_regulation', 27],
|
||||
sea_object='tt',
|
||||
rel_paths=['main', '.', 'set'],
|
||||
value=Param(unit='K'),
|
||||
rel_paths=['tt', 'set'],
|
||||
)
|
||||
|
||||
Mod('T_ccr',
|
||||
@ -23,7 +22,6 @@ Mod('T_ccr',
|
||||
io='sea_main',
|
||||
sea_object='tt',
|
||||
rel_paths=['ccr'],
|
||||
value=Param(unit='K'),
|
||||
)
|
||||
|
||||
Mod('jtccr',
|
||||
@ -103,35 +101,30 @@ Mod('p1',
|
||||
'frappy_psi.sea.SeaReadable', '',
|
||||
io='sea_main',
|
||||
sea_object='p1',
|
||||
value=Param(unit='mbar'),
|
||||
)
|
||||
|
||||
Mod('p2',
|
||||
'frappy_psi.sea.SeaReadable', '',
|
||||
io='sea_main',
|
||||
sea_object='p2',
|
||||
value=Param(unit='mbar'),
|
||||
)
|
||||
|
||||
Mod('p3',
|
||||
'frappy_psi.sea.SeaReadable', '',
|
||||
io='sea_main',
|
||||
sea_object='p3',
|
||||
value=Param(unit='mbar'),
|
||||
)
|
||||
|
||||
Mod('p4',
|
||||
'frappy_psi.sea.SeaReadable', '',
|
||||
io='sea_main',
|
||||
sea_object='p4',
|
||||
value=Param(unit='mbar'),
|
||||
)
|
||||
|
||||
Mod('pressreg',
|
||||
'frappy_psi.sea.SeaReadable', '',
|
||||
io='sea_main',
|
||||
sea_object='pressreg',
|
||||
value=Param(unit='mbar'),
|
||||
)
|
||||
|
||||
Mod('epc',
|
||||
|
@ -10,12 +10,11 @@ Mod('sea_main',
|
||||
)
|
||||
|
||||
Mod('tt',
|
||||
'frappy_psi.sea.LscDrivable', '',
|
||||
'frappy_psi.sea.SeaDrivable', '',
|
||||
io='sea_main',
|
||||
meaning=['temperature_regulation', 27],
|
||||
sea_object='tt',
|
||||
sensor_path='tm',
|
||||
set_path='set',
|
||||
rel_paths=['tm', '.', 'set', 'dblctrl'],
|
||||
)
|
||||
|
||||
Mod('cc',
|
||||
|
@ -10,12 +10,11 @@ Mod('sea_main',
|
||||
)
|
||||
|
||||
Mod('tt',
|
||||
'frappy_psi.sea.LscDrivable', '',
|
||||
'frappy_psi.sea.SeaDrivable', '',
|
||||
io='sea_main',
|
||||
meaning=['temperature_regulation', 27],
|
||||
sea_object='tt',
|
||||
sensor_path='tm',
|
||||
set_path='set',
|
||||
rel_paths=['tm', '.', 'set', 'dblctrl'],
|
||||
)
|
||||
|
||||
Mod('cc',
|
||||
|
@ -10,12 +10,11 @@ Mod('sea_main',
|
||||
)
|
||||
|
||||
Mod('tt',
|
||||
'frappy_psi.sea.LscDrivable', '',
|
||||
'frappy_psi.sea.SeaDrivable', '',
|
||||
io='sea_main',
|
||||
meaning=['temperature_regulation', 27],
|
||||
sea_object='tt',
|
||||
sensor_path='tm',
|
||||
set_path='set',
|
||||
rel_paths=['tm', '.', 'set', 'dblctrl'],
|
||||
)
|
||||
|
||||
Mod('cc',
|
||||
|
@ -8,12 +8,11 @@ Mod('sea_main',
|
||||
service = 'main',
|
||||
)
|
||||
Mod('tt',
|
||||
'frappy_psi.sea.LscDrivable', '',
|
||||
io='sea_main',
|
||||
'frappy_psi.sea.SeaDrivable', '',
|
||||
io = 'sea_main',
|
||||
meaning=['temperature_regulation', 27],
|
||||
sea_object='tt',
|
||||
sensor_path='tm',
|
||||
set_path='set',
|
||||
sea_object = 'tt',
|
||||
rel_paths=['tm', '.', 'set', 'dblctrl'],
|
||||
)
|
||||
Mod('cc',
|
||||
'frappy_psi.sea.SeaReadable', '',
|
||||
|
@ -10,12 +10,11 @@ Mod('sea_main',
|
||||
)
|
||||
|
||||
Mod('tt',
|
||||
'frappy_psi.sea.LscDrivable', '',
|
||||
'frappy_psi.sea.SeaDrivable', '',
|
||||
io='sea_main',
|
||||
meaning=['temperature_regulation', 27],
|
||||
sea_object='tt',
|
||||
sensor_path='tm',
|
||||
set_path='set',
|
||||
rel_paths=['tm', '.', 'set', 'dblctrl'],
|
||||
)
|
||||
|
||||
Mod('cc',
|
||||
|
@ -10,12 +10,11 @@ Mod('sea_main',
|
||||
)
|
||||
|
||||
Mod('tt',
|
||||
'frappy_psi.sea.LscDrivable', '',
|
||||
'frappy_psi.sea.SeaDrivable', '',
|
||||
io='sea_main',
|
||||
meaning=['temperature_regulation', 27],
|
||||
sea_object='tt',
|
||||
sensor_path='tm',
|
||||
set_path='set',
|
||||
rel_paths=['tm', '.', 'set', 'dblctrl'],
|
||||
)
|
||||
|
||||
Mod('th',
|
||||
|
@ -10,13 +10,12 @@ Mod('sea_main',
|
||||
)
|
||||
|
||||
Mod('tt',
|
||||
'frappy_psi.sea.LscDrivable', '',
|
||||
'frappy_psi.sea.SeaDrivable', '',
|
||||
io='sea_main',
|
||||
meaning=['temperature_regulation', 27],
|
||||
sea_object='tt',
|
||||
sensor_path='tm',
|
||||
set_path='set',
|
||||
)
|
||||
rel_paths=['tm', '.', 'set', 'dblctrl'],
|
||||
)
|
||||
|
||||
Mod('th',
|
||||
'frappy_psi.sea.SeaReadable',
|
||||
|
@ -15,12 +15,11 @@ Mod('sea_main',
|
||||
#)
|
||||
|
||||
Mod('tt',
|
||||
'frappy_psi.sea.LscDrivable', '',
|
||||
io='sea_main',
|
||||
'frappy_psi.sea.SeaDrivable', '',
|
||||
meaning=['temperature_regulation', 27],
|
||||
io='sea_main',
|
||||
sea_object='tt',
|
||||
sensor_path='tm',
|
||||
set_path='set',
|
||||
rel_paths=['tm', '.', 'set', 'dblctrl'],
|
||||
)
|
||||
|
||||
Mod('th',
|
||||
|
@ -10,12 +10,11 @@ Mod('sea_main',
|
||||
)
|
||||
|
||||
Mod('tt',
|
||||
'frappy_psi.sea.LscDrivable', '',
|
||||
'frappy_psi.sea.SeaDrivable', '',
|
||||
io='sea_main',
|
||||
meaning=['temperature_regulation', 27],
|
||||
sea_object='tt',
|
||||
sensor_path='tm',
|
||||
set_path='set',
|
||||
rel_paths=['tm', '.', 'set', 'dblctrl'],
|
||||
)
|
||||
|
||||
Mod('cc',
|
||||
|
228
cfg/main/mb11std_cfg.py
Normal file
228
cfg/main/mb11std_cfg.py
Normal 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),
|
||||
)
|
@ -10,12 +10,11 @@ Mod('sea_main',
|
||||
)
|
||||
|
||||
Mod('tt',
|
||||
'frappy_psi.sea.LscDrivable', '',
|
||||
'frappy_psi.sea.SeaDrivable', '',
|
||||
io='sea_main',
|
||||
meaning=['temperature_regulation', 27],
|
||||
sea_object='tt',
|
||||
sensor_path='tm',
|
||||
set_path='set',
|
||||
rel_paths=['tm', '.', 'set', 'dblctrl'],
|
||||
)
|
||||
|
||||
Mod('cc',
|
||||
|
@ -10,12 +10,11 @@ Mod('sea_main',
|
||||
)
|
||||
|
||||
Mod('tt',
|
||||
'frappy_psi.sea.LscDrivable', '',
|
||||
'frappy_psi.sea.SeaDrivable', '',
|
||||
io='sea_main',
|
||||
meaning=['temperature_regulation', 27],
|
||||
sea_object='tt',
|
||||
sensor_path='tm',
|
||||
set_path='set',
|
||||
rel_paths=['tm', '.', 'set', 'dblctrl'],
|
||||
)
|
||||
|
||||
Mod('cc',
|
||||
|
@ -10,12 +10,11 @@ Mod('sea_main',
|
||||
)
|
||||
|
||||
Mod('tt',
|
||||
'frappy_psi.sea.LscDrivable', '',
|
||||
'frappy_psi.sea.SeaDrivable', '',
|
||||
io='sea_main',
|
||||
meaning=['temperature_regulation', 27],
|
||||
sea_object='tt',
|
||||
sensor_path='tm',
|
||||
set_path='set',
|
||||
rel_paths=['tm', '.', 'set', 'dblctrl'],
|
||||
)
|
||||
|
||||
Mod('cc',
|
||||
|
@ -10,12 +10,11 @@ Mod('sea_main',
|
||||
)
|
||||
|
||||
Mod('tt',
|
||||
'frappy_psi.sea.LscDrivable', '',
|
||||
'frappy_psi.sea.SeaDrivable', '',
|
||||
io='sea_main',
|
||||
meaning=['temperature_regulation', 27],
|
||||
sea_object='tt',
|
||||
sensor_path='tm',
|
||||
set_path='set',
|
||||
rel_paths=['tm', '.', 'set', 'dblctrl'],
|
||||
)
|
||||
|
||||
Mod('cc',
|
||||
|
@ -10,12 +10,11 @@ Mod('sea_main',
|
||||
)
|
||||
|
||||
Mod('tt',
|
||||
'frappy_psi.sea.LscDrivable', '',
|
||||
io='sea_main',
|
||||
'frappy_psi.sea.SeaDrivable', '',
|
||||
io = 'sea_main',
|
||||
meaning=['temperature_regulation', 27],
|
||||
sea_object='tt',
|
||||
sensor_path='tm',
|
||||
set_path='set',
|
||||
sea_object = 'tt',
|
||||
rel_paths=['tm', '.', 'set', 'dblctrl'],
|
||||
)
|
||||
|
||||
Mod('cc',
|
||||
|
@ -7,11 +7,11 @@ Node('ori7test.psi.ch',
|
||||
|
||||
rack = Rack(Mod)
|
||||
|
||||
rack.lakeshore()
|
||||
rack.sensor('Ts', channel='C', calcurve='x186350')
|
||||
rack.loop('T', channel='B', calcurve='x174786', output_module='htr', target=10)
|
||||
rack.heater('htr', output_no=1, max_heater='100W', resistance=25)
|
||||
with rack.lakeshore() as ls:
|
||||
ls.sensor('Ts', channel='C', calcurve='x186350')
|
||||
ls.loop('T', channel='B', calcurve='x174786')
|
||||
ls.heater('htr', '100W', 100)
|
||||
|
||||
rack.he()
|
||||
rack.n2()
|
||||
rack.flow(min_open_pulse=0.03)
|
||||
rack.ccu(he=True, n2=True)
|
||||
|
||||
rack.hepump()
|
||||
|
@ -10,12 +10,11 @@ Mod('sea_main',
|
||||
)
|
||||
|
||||
Mod('tt',
|
||||
'frappy_psi.sea.LscDrivable', '',
|
||||
'frappy_psi.sea.SeaDrivable', '',
|
||||
io='sea_main',
|
||||
meaning=['temperature_regulation', 27],
|
||||
sea_object='tt',
|
||||
sensor_path='tm',
|
||||
set_path='set',
|
||||
rel_paths=['tm', '.', 'set', 'dblctrl'],
|
||||
)
|
||||
|
||||
Mod('cc',
|
||||
|
@ -170,20 +170,18 @@ Mod('htr_nvd',
|
||||
|
||||
# Motor controller is not yet available!
|
||||
#
|
||||
'''
|
||||
Mod('om_io',
|
||||
'frappy_psi.phytron.PhytronIO',
|
||||
'dom motor IO',
|
||||
uri='mb11-ts.psi.ch:3004',
|
||||
)
|
||||
#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=-180,
|
||||
target_max=360,
|
||||
encoder_mode='NO',
|
||||
target=Param(min=-180, max=360)
|
||||
)
|
||||
'''
|
||||
#Mod('om',
|
||||
# 'frappy_psi.phytron.Motor',
|
||||
# 'stick rotation, typically used for omega',
|
||||
# io='om_io',
|
||||
# target_min=-180,
|
||||
# target_max=360,
|
||||
# encoder_mode='NO',
|
||||
# target=Param(min=-180, max=360)
|
||||
#)
|
||||
|
@ -292,7 +292,7 @@
|
||||
{"path": "V3A", "type": "int", "readonly": false, "cmd": "dil V3A", "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": "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": "extVersion", "type": "int", "readonly": false, "cmd": "dil extVersion", "visibility": 3},
|
||||
{"path": "pumpoff", "type": "int"},
|
||||
|
@ -292,7 +292,7 @@
|
||||
{"path": "V3A", "type": "int", "readonly": false, "cmd": "dil V3A", "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": "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": "extVersion", "type": "int", "readonly": false, "cmd": "dil extVersion", "visibility": 3},
|
||||
{"path": "pumpoff", "type": "int"},
|
||||
|
@ -292,7 +292,7 @@
|
||||
{"path": "V3A", "type": "int", "readonly": false, "cmd": "dil V3A", "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": "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": "extVersion", "type": "int", "readonly": false, "cmd": "dil extVersion", "visibility": 3},
|
||||
{"path": "pumpoff", "type": "int"},
|
||||
|
@ -3,7 +3,7 @@
|
||||
{"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": "running", "type": "int", "readonly": false, "cmd": "th running", "visibility": 3},
|
||||
{"path": "extcontrol", "type": "int", "readonly": false, "cmd": "th extcontrol", "visibility": 3},
|
||||
{"path": "relais", "type": "int", "visibility": 3},
|
||||
{"path": "overtemp", "type": "int", "visibility": 3},
|
||||
|
@ -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"}]}}
|
@ -1,50 +1,50 @@
|
||||
{"tt": {"base": "/tt", "params": [
|
||||
{"path": "", "type": "float", "readonly": false, "cmd": "run tt", "description": "tt", "kids": 18},
|
||||
{"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": "mainloop", "type": "text", "readonly": false, "cmd": "tt mainloop", "visibility": 3},
|
||||
{"path": "target", "type": "float"},
|
||||
{"path": "running", "type": "int"},
|
||||
{"path": "target", "type": "float", "readonly": false, "cmd": "run tt"},
|
||||
{"path": "running", "type": "int", "readonly": false, "cmd": "run tt"},
|
||||
{"path": "tolerance", "type": "float", "readonly": false, "cmd": "tt tolerance"},
|
||||
{"path": "maxwait", "type": "float", "readonly": false, "cmd": "tt maxwait"},
|
||||
{"path": "settle", "type": "float", "readonly": false, "cmd": "tt settle"},
|
||||
{"path": "log", "type": "text", "readonly": false, "cmd": "tt log", "visibility": 3, "kids": 4},
|
||||
{"path": "log/mean", "type": "float", "visibility": 3},
|
||||
{"path": "log/m2", "type": "float", "visibility": 3},
|
||||
{"path": "log/stddev", "type": "float", "visibility": 3},
|
||||
{"path": "log/n", "type": "float", "visibility": 3},
|
||||
{"path": "log/mean", "type": "float", "readonly": false, "cmd": "run tt", "visibility": 3},
|
||||
{"path": "log/m2", "type": "float", "readonly": false, "cmd": "run tt", "visibility": 3},
|
||||
{"path": "log/stddev", "type": "float", "readonly": false, "cmd": "run tt", "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/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/shift_up", "type": "float"},
|
||||
{"path": "dblctrl/shift_lo", "type": "float"},
|
||||
{"path": "dblctrl/t_min", "type": "float"},
|
||||
{"path": "dblctrl/t_max", "type": "float"},
|
||||
{"path": "dblctrl/shift_up", "type": "float", "readonly": false, "cmd": "run tt"},
|
||||
{"path": "dblctrl/shift_lo", "type": "float", "readonly": false, "cmd": "run tt"},
|
||||
{"path": "dblctrl/t_min", "type": "float", "readonly": false, "cmd": "run tt"},
|
||||
{"path": "dblctrl/t_max", "type": "float", "readonly": false, "cmd": "run tt"},
|
||||
{"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_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/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/stddev", "type": "float"},
|
||||
{"path": "tm/raw", "type": "float"},
|
||||
{"path": "ts", "type": "float", "kids": 4},
|
||||
{"path": "tm/stddev", "type": "float", "readonly": false, "cmd": "run tt"},
|
||||
{"path": "tm/raw", "type": "float", "readonly": false, "cmd": "run tt"},
|
||||
{"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/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/stddev", "type": "float"},
|
||||
{"path": "ts/raw", "type": "float"},
|
||||
{"path": "ts_2", "type": "float", "visibility": 3, "kids": 4},
|
||||
{"path": "ts_2/curve", "type": "text", "readonly": false, "cmd": "tt ts_2/curve", "visibility": 3, "kids": 1},
|
||||
{"path": "ts/stddev", "type": "float", "readonly": false, "cmd": "run tt"},
|
||||
{"path": "ts/raw", "type": "float", "readonly": false, "cmd": "run tt"},
|
||||
{"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", "kids": 1},
|
||||
{"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/stddev", "type": "float", "visibility": 3},
|
||||
{"path": "ts_2/raw", "type": "float", "visibility": 3},
|
||||
{"path": "ts_2/alarm", "type": "float", "readonly": false, "cmd": "tt ts_2/alarm"},
|
||||
{"path": "ts_2/stddev", "type": "float", "readonly": false, "cmd": "run tt"},
|
||||
{"path": "ts_2/raw", "type": "float", "readonly": false, "cmd": "run tt"},
|
||||
{"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/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/wramp", "type": "float", "readonly": false, "cmd": "tt set/wramp"},
|
||||
{"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/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/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/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/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/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": "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/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/wramp", "type": "float", "readonly": false, "cmd": "tt setsamp/wramp"},
|
||||
{"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/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/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/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/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/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": "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": [
|
||||
{"path": "", "type": "bool", "kids": 96},
|
||||
@ -108,7 +108,7 @@
|
||||
{"path": "mcr", "type": "float"},
|
||||
{"path": "mot", "type": "float"},
|
||||
{"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": "hr", "type": "float"},
|
||||
{"path": "hc", "type": "float"},
|
||||
@ -132,26 +132,26 @@
|
||||
{"path": "hms", "type": "float"},
|
||||
{"path": "hit", "type": "float", "readonly": false, "cmd": "cc hit"},
|
||||
{"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": "hch", "type": "int", "readonly": false, "cmd": "cc hch"},
|
||||
{"path": "hwr0", "type": "float", "readonly": false, "cmd": "cc hwr0"},
|
||||
{"path": "hem0", "type": "float", "readonly": false, "cmd": "cc hem0", "description": "sensor length in mm from top to empty pos."},
|
||||
{"path": "hfu0", "type": "float", "readonly": false, "cmd": "cc hfu0", "description": "sensor length in mm from top to full pos."},
|
||||
{"path": "hd0", "type": "float", "readonly": false, "cmd": "cc hd0", "description": "external sensor drive current (mA)"},
|
||||
{"path": "h0", "type": "float"},
|
||||
{"path": "hs0", "type": "enum", "enum": {"sens_ok": 0, "sens_warm": 1, "no_sens": 2, "timeout": 3, "not_yet_read": 4, "disabled": 5}},
|
||||
{"path": "h1", "type": "float"},
|
||||
{"path": "hs1", "type": "enum", "enum": {"sens_ok": 0, "sens_warm": 1, "no_sens": 2, "timeout": 3, "not_yet_read": 4, "disabled": 5}},
|
||||
{"path": "h2", "type": "float"},
|
||||
{"path": "hs2", "type": "enum", "enum": {"sens_ok": 0, "sens_warm": 1, "no_sens": 2, "timeout": 3, "not_yet_read": 4, "disabled": 5}},
|
||||
{"path": "h3", "type": "float"},
|
||||
{"path": "hs3", "type": "enum", "enum": {"sens_ok": 0, "sens_warm": 1, "no_sens": 2, "timeout": 3, "not_yet_read": 4, "disabled": 5}},
|
||||
{"path": "h4", "type": "float"},
|
||||
{"path": "hs4", "type": "enum", "enum": {"sens_ok": 0, "sens_warm": 1, "no_sens": 2, "timeout": 3, "not_yet_read": 4, "disabled": 5}},
|
||||
{"path": "h5", "type": "float"},
|
||||
{"path": "hs5", "type": "enum", "enum": {"sens_ok": 0, "sens_warm": 1, "no_sens": 2, "timeout": 3, "not_yet_read": 4, "disabled": 5}},
|
||||
{"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", "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"},
|
||||
{"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": "nl", "type": "float"},
|
||||
{"path": "nth", "type": "float", "readonly": false, "cmd": "cc nth"},
|
||||
@ -183,16 +183,15 @@
|
||||
{"path": "bdl", "type": "float", "readonly": false, "cmd": "cc bdl"}]},
|
||||
|
||||
"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": "status", "type": "text", "visibility": 3},
|
||||
{"path": "motstat", "type": "enum", "enum": {"idle": 0, "opening": 1, "closing": 2, "opened": 3, "closed": 4, "no_motor": 5}},
|
||||
{"path": "flow", "type": "float"},
|
||||
{"path": "set", "type": "float", "readonly": false, "cmd": "nv set"},
|
||||
{"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": "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/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"},
|
||||
@ -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/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": [
|
||||
{"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": "status", "type": "text", "visibility": 3},
|
||||
{"path": "running", "type": "bool", "readonly": false, "cmd": "hepump running"},
|
||||
{"path": "eco", "type": "bool", "readonly": false, "cmd": "hepump eco", "visibility": 3},
|
||||
{"path": "auto", "type": "bool", "readonly": false, "cmd": "hepump auto", "visibility": 3},
|
||||
{"path": "eco", "type": "bool", "readonly": false, "cmd": "hepump eco"},
|
||||
{"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": "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": "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"}]},
|
||||
|
||||
"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": "status", "type": "text", "visibility": 3},
|
||||
{"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": "highlevel", "type": "float", "readonly": false, "cmd": "ln2fill highlevel"},
|
||||
{"path": "smooth", "type": "float"},
|
||||
@ -327,33 +307,52 @@
|
||||
{"path": "badreadingminutes", "type": "float", "readonly": false, "cmd": "ln2fill badreadingminutes"},
|
||||
{"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": [
|
||||
{"path": "", "type": "float", "readonly": false, "cmd": "run mf", "kids": 26},
|
||||
{"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": "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": "ramp", "type": "float", "readonly": false, "cmd": "mf ramp"},
|
||||
{"path": "perscurrent", "type": "float", "readonly": false, "cmd": "mf perscurrent"},
|
||||
{"path": "perslimit", "type": "float", "readonly": false, "cmd": "mf perslimit"},
|
||||
{"path": "perswait", "type": "int", "readonly": false, "cmd": "mf perswait"},
|
||||
{"path": "persdelay", "type": "int", "readonly": false, "cmd": "mf persdelay"},
|
||||
{"path": "current", "type": "float"},
|
||||
{"path": "measured", "type": "float"},
|
||||
{"path": "voltage", "type": "float"},
|
||||
{"path": "lastfield", "type": "float", "visibility": 3},
|
||||
{"path": "ampRamp", "type": "float", "visibility": 3},
|
||||
{"path": "inductance", "type": "float", "visibility": 3},
|
||||
{"path": "current", "type": "float", "readonly": false, "cmd": "run mf"},
|
||||
{"path": "measured", "type": "float", "readonly": false, "cmd": "run mf"},
|
||||
{"path": "voltage", "type": "float", "readonly": false, "cmd": "run mf"},
|
||||
{"path": "lastfield", "type": "float", "readonly": false, "cmd": "run mf", "visibility": 3},
|
||||
{"path": "ampRamp", "type": "float", "readonly": false, "cmd": "run mf", "visibility": 3},
|
||||
{"path": "inductance", "type": "float", "readonly": false, "cmd": "run mf", "visibility": 3},
|
||||
{"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": "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": "driver", "type": "text", "visibility": 3},
|
||||
{"path": "creationCmd", "type": "text", "visibility": 3},
|
||||
{"path": "targetValue", "type": "float"},
|
||||
{"path": "driver", "type": "text", "readonly": false, "cmd": "run mf", "visibility": 3},
|
||||
{"path": "creationCmd", "type": "text", "readonly": false, "cmd": "run mf", "visibility": 3},
|
||||
{"path": "targetValue", "type": "float", "readonly": false, "cmd": "run mf"},
|
||||
{"path": "status", "type": "text", "readonly": false, "cmd": "mf status", "visibility": 3}]},
|
||||
|
||||
"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": "n2", "type": "float"}]},
|
||||
|
||||
"table": {"base": "/table", "params": [
|
||||
{"path": "", "type": "none", "kids": 17},
|
||||
{"path": "send", "type": "text", "readonly": false, "cmd": "table send", "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 ..."}]}}
|
||||
"prep0": {"base": "/prep0", "params": [
|
||||
{"path": "", "type": "text", "readonly": false, "cmd": "prep0", "kids": 2},
|
||||
{"path": "send", "type": "text", "readonly": false, "cmd": "prep0 send", "visibility": 3},
|
||||
{"path": "status", "type": "text", "visibility": 3}]}}
|
||||
|
@ -1,33 +1,3 @@
|
||||
import os
|
||||
|
||||
Node('mb11.stick.sea.psi.ch',
|
||||
'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_',
|
||||
)
|
||||
|
||||
|
@ -88,13 +88,16 @@ Mod('interlocks',
|
||||
vacuum_limit = 0.1,
|
||||
)
|
||||
|
||||
Mod('p_io',
|
||||
'frappy_psi.pfeiffer.IO',
|
||||
'pressure io',
|
||||
uri='serial:///dev/ttyUSBlower',
|
||||
)
|
||||
|
||||
Mod('p',
|
||||
'frappy_psi.ionopimax.LogVoltageInput',
|
||||
'frappy_psi.pfeiffer.Pressure',
|
||||
'pressure reading',
|
||||
addr = 'av1',
|
||||
rawrange = (1.8, 8.6),
|
||||
valuerange = (1e-7, 1000),
|
||||
value = Param(unit='mbar'),
|
||||
io = 'p_io',
|
||||
)
|
||||
|
||||
|
||||
|
144
debian/changelog
vendored
144
debian/changelog
vendored
@ -1,48 +1,4 @@
|
||||
frappy-core (0.20.5) stable; 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
|
||||
frappy-core (0.20.4) jammy; urgency=medium
|
||||
|
||||
[ Georg Brandl ]
|
||||
* 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
|
||||
|
||||
frappy-core (0.20.3) stable; urgency=medium
|
||||
frappy-core (0.20.3) jammy; urgency=medium
|
||||
|
||||
[ Georg Brandl ]
|
||||
* 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
|
||||
|
||||
frappy-core (0.20.2) stable; urgency=medium
|
||||
frappy-core (0.20.2) jammy; urgency=medium
|
||||
|
||||
[ Georg Brandl ]
|
||||
* 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
|
||||
|
||||
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
|
||||
* 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
|
||||
|
||||
frappy-core (0.20.0) stable; urgency=medium
|
||||
frappy-core (0.20.0) jammy; urgency=medium
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* 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
|
||||
|
||||
frappy-core (0.19.10) stable; urgency=medium
|
||||
frappy-core (0.19.10) jammy; urgency=medium
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* 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
|
||||
|
||||
frappy-core (0.19.9) stable; urgency=medium
|
||||
frappy-core (0.19.9) jammy; urgency=medium
|
||||
|
||||
* debian: fix missing install dir
|
||||
|
||||
-- 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
|
||||
|
||||
-- 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
|
||||
|
||||
-- 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 ]
|
||||
* 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
|
||||
|
||||
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
|
||||
* 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
|
||||
|
||||
frappy-core (0.19.4) stable; urgency=medium
|
||||
frappy-core (0.19.4) jammy; urgency=medium
|
||||
|
||||
* actually exclude cfg-editor
|
||||
|
||||
-- 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 ]
|
||||
* 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
|
||||
|
||||
frappy-core (0.19.2) stable; urgency=medium
|
||||
frappy-core (0.19.2) jammy; urgency=medium
|
||||
|
||||
[ l_samenv ]
|
||||
* 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
|
||||
|
||||
frappy-core (0.19.1) stable; urgency=medium
|
||||
frappy-core (0.19.1) jammy; urgency=medium
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* 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
|
||||
|
||||
frappy-core (0.19.0) stable; urgency=medium
|
||||
frappy-core (0.19.0) jammy; urgency=medium
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* 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
|
||||
|
||||
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: entangle fix limit check
|
||||
|
||||
-- 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 ]
|
||||
* 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
|
||||
|
||||
frappy-core (0.17.13) stable; urgency=medium
|
||||
frappy-core (0.17.13) focal; urgency=medium
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* 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
|
||||
|
||||
frappy-core (0.17.12) stable; urgency=medium
|
||||
frappy-core (0.17.12) focal; urgency=medium
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* 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
|
||||
|
||||
frappy-core (0.17.11) stable; urgency=medium
|
||||
frappy-core (0.17.11) focal; urgency=medium
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* 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
|
||||
|
||||
frappy-core (0.17.10) stable; urgency=medium
|
||||
frappy-core (0.17.10) focal; urgency=medium
|
||||
|
||||
* Change leftover %-logging calls to lazy
|
||||
* 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
|
||||
|
||||
frappy-core (0.17.9) stable; urgency=medium
|
||||
frappy-core (0.17.9) focal; urgency=medium
|
||||
|
||||
* interactive client: avoid messing up the input line
|
||||
|
||||
-- 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
|
||||
|
||||
-- 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
|
||||
|
||||
-- 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 ]
|
||||
* 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
|
||||
|
||||
frappy-core (0.17.5) stable; urgency=medium
|
||||
frappy-core (0.17.5) focal; urgency=medium
|
||||
|
||||
* Fix generator
|
||||
|
||||
-- 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
|
||||
|
||||
-- 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
|
||||
|
||||
-- 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 ]
|
||||
* 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
|
||||
|
||||
frappy-core (0.17.1) stable; urgency=medium
|
||||
frappy-core (0.17.1) focal; urgency=medium
|
||||
|
||||
[ Georg Brandl ]
|
||||
* 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
|
||||
|
||||
frappy-core (0.17.0) stable; urgency=medium
|
||||
frappy-core (0.17.0) focal; urgency=medium
|
||||
|
||||
[ Alexander Zaft ]
|
||||
* 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
|
||||
|
||||
frappy-core (0.16.1) stable; urgency=medium
|
||||
frappy-core (0.16.1) focal; urgency=medium
|
||||
|
||||
* UNRELEASED
|
||||
|
||||
-- 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
|
||||
|
||||
-- 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
|
||||
|
||||
-- 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
|
||||
|
||||
-- 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
|
||||
|
||||
-- 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 ]
|
||||
* 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
|
||||
|
||||
frappy-core (0.15.0) stable; urgency=medium
|
||||
frappy-core (0.15.0) focal; urgency=medium
|
||||
|
||||
[ Björn Pedersen ]
|
||||
* 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
|
||||
|
||||
secop-core (0.14.3) stable; urgency=medium
|
||||
secop-core (0.14.3) focal; urgency=medium
|
||||
|
||||
[ Enrico Faulhaber ]
|
||||
* 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
|
||||
|
||||
secop-core (0.14.2) stable; urgency=medium
|
||||
secop-core (0.14.2) focal; urgency=medium
|
||||
|
||||
* systemd generator: adapt to changed config API
|
||||
|
||||
-- Georg Brandl <jenkins@frm2.tum.de> Thu, 20 Oct 2022 15:38:45 +0200
|
||||
|
||||
secop-core (0.14.1) stable; urgency=medium
|
||||
secop-core (0.14.1) focal; urgency=medium
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* 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
|
||||
|
||||
secop-core (0.14.0) stable; urgency=medium
|
||||
secop-core (0.14.0) focal; urgency=medium
|
||||
|
||||
* add simple interactive python client
|
||||
* 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
|
||||
|
||||
secop-core (0.13.1) stable; urgency=medium
|
||||
secop-core (0.13.1) focal; urgency=medium
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* 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
|
||||
|
||||
secop-core (0.13.0) stable; urgency=medium
|
||||
secop-core (0.13.0) focal; urgency=medium
|
||||
|
||||
[ Georg Brandl ]
|
||||
* 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
|
||||
|
||||
secop-core (0.12.4) stable; urgency=medium
|
||||
secop-core (0.12.4) focal; urgency=medium
|
||||
|
||||
* fix command inheritance
|
||||
|
||||
-- Markus Zolliker <jenkins@jenkins01.admin.frm2.tum.de> Thu, 11 Nov 2021 16:21:19 +0100
|
||||
|
||||
secop-core (0.12.3) stable; urgency=medium
|
||||
secop-core (0.12.3) focal; urgency=medium
|
||||
|
||||
[ Georg Brandl ]
|
||||
* 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
|
||||
|
||||
secop-core (0.12.2) stable; urgency=medium
|
||||
secop-core (0.12.2) focal; urgency=medium
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* 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
|
||||
|
||||
secop-core (0.12.1) stable; urgency=medium
|
||||
secop-core (0.12.1) focal; urgency=medium
|
||||
|
||||
* remove secop-console from debian *.install file
|
||||
|
||||
-- Enrico Faulhaber <jenkins@jenkins02.admin.frm2.tum.de> Tue, 04 May 2021 09:42:53 +0200
|
||||
|
||||
secop-core (0.12.0) stable; urgency=medium
|
||||
secop-core (0.12.0) focal; urgency=medium
|
||||
|
||||
[ Markus Zolliker ]
|
||||
* make datatypes immutable
|
||||
|
1
debian/compat
vendored
Normal file
1
debian/compat
vendored
Normal file
@ -0,0 +1 @@
|
||||
11
|
4
debian/control
vendored
4
debian/control
vendored
@ -2,7 +2,7 @@ Source: frappy-core
|
||||
Section: contrib/misc
|
||||
Priority: optional
|
||||
Maintainer: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
Build-Depends: debhelper (>= 11~),
|
||||
dh-python,
|
||||
python3 (>=3.6),
|
||||
python3-all,
|
||||
@ -20,7 +20,7 @@ Build-Depends: debhelper-compat (= 13),
|
||||
git,
|
||||
markdown,
|
||||
python3-daemon
|
||||
Standards-Version: 4.6.2
|
||||
Standards-Version: 4.1.4
|
||||
X-Python3-Version: >= 3.6
|
||||
|
||||
Package: frappy-core
|
||||
|
@ -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
|
||||
"""
|
@ -209,20 +209,16 @@ class ProxyClient:
|
||||
# caches (module, parameter) = value, timestamp, readerror (internal names!)
|
||||
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
|
||||
|
||||
several callbacks might be registered within one call.
|
||||
ProxyClient.CALLBACK_NAMES contains all names of valid callbacks
|
||||
|
||||
:param key: might be either:
|
||||
- key might be either:
|
||||
1) None: general callback (all callbacks)
|
||||
2) <module name>: callbacks related to a module (not called for 'unhandledMessage')
|
||||
3) (<module name>, <parameter name>): callback for specified parameter
|
||||
(only called for 'updateEvent' and 'updateItem')
|
||||
:param args: callback functions. the callback name is taken from the the __name__ attribute of the function
|
||||
:param callimmediately: True (default): call immediately for updateItem and updateEvent callbacks
|
||||
:param kwds: callback functions. the callback name is taken from the key
|
||||
3) (<module name>, <parameter name>): callback for specified parameter (only called for 'updateEvent')
|
||||
- all the following arguments are callback functions. The callback name may be
|
||||
given by the keyword, or, for non-keyworded arguments it is taken from the
|
||||
__name__ attribute of the function
|
||||
"""
|
||||
for cbfunc in args:
|
||||
kwds[cbfunc.__name__] = cbfunc
|
||||
@ -230,8 +226,8 @@ class ProxyClient:
|
||||
if cbname not in self.CALLBACK_NAMES:
|
||||
raise TypeError(f"unknown callback: {', '.join(kwds)}")
|
||||
|
||||
# call immediately for some callback types
|
||||
if cbname in ('updateItem', 'updateEvent') and callimmediately:
|
||||
# immediately call for some callback types
|
||||
if cbname in ('updateItem', 'updateEvent') and callimmediately is not False:
|
||||
if key is None: # case generic callback
|
||||
cbargs = [(m, p, d) for (m, p), d in self.cache.items()]
|
||||
else:
|
||||
@ -738,7 +734,7 @@ class SecopClient(ProxyClient):
|
||||
"""
|
||||
self.connect() # make sure we are connected
|
||||
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)
|
||||
return self.cache[module, parameter]
|
||||
|
||||
@ -757,28 +753,6 @@ class SecopClient(ProxyClient):
|
||||
data = datatype.import_value(data)
|
||||
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):
|
||||
datatype = self.modules[module]['parameters'][param]['datatype']
|
||||
if readerror:
|
||||
|
@ -143,7 +143,7 @@ class Module:
|
||||
def _isBusy(self):
|
||||
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():
|
||||
self._is_driving = False
|
||||
self._driving_event.set()
|
||||
@ -216,11 +216,10 @@ class Module:
|
||||
def __call__(self, target=None):
|
||||
if target is None:
|
||||
return self.read()
|
||||
watch_params = ['value', 'status']
|
||||
for pname in watch_params:
|
||||
for pname in 'value', 'status':
|
||||
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
|
||||
|
||||
@ -240,10 +239,11 @@ class Module:
|
||||
pass
|
||||
clientenv.raise_with_short_traceback(e)
|
||||
finally:
|
||||
# self._watch_parameter(self._name, 'status')
|
||||
self._secnode.readParameter(self._name, 'value')
|
||||
for pname in watch_params:
|
||||
self._secnode.unregister_callback((self._name, pname),
|
||||
updateEvent=self._watch_parameter)
|
||||
# self._watch_parameter(self._name, 'value', forced=True)
|
||||
self._secnode.unregister_callback((self._name, 'value'), updateEvent=self._watch_parameter)
|
||||
self._secnode.unregister_callback((self._name, 'status'), updateEvent=self._watch_parameter)
|
||||
return self.value
|
||||
|
||||
def __repr__(self):
|
||||
@ -418,7 +418,8 @@ class Client(SecopClient):
|
||||
attrs[cname] = Command(cname, modname, self)
|
||||
mobj = type(f'M_{modname}', (Module,), attrs)(modname, self)
|
||||
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
|
||||
if removed_modules:
|
||||
self.log.info('removed modules: %s', ' '.join(removed_modules))
|
||||
@ -449,7 +450,7 @@ def run(filepath):
|
||||
"__file__": filepath,
|
||||
"__name__": "__main__",
|
||||
})
|
||||
with open(filepath, 'rb') as file:
|
||||
with filepath.open('rb') as file:
|
||||
# pylint: disable=exec-used
|
||||
exec(compile(file.read(), filepath, 'exec'), clientenv.namespace, None)
|
||||
|
||||
|
@ -16,13 +16,13 @@
|
||||
#
|
||||
# Module authors:
|
||||
# Alexander Zaft <a.zaft@fz-juelich.de>
|
||||
# Markus Zolliker <markus.zolliker@psi.ch>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
from collections import Counter
|
||||
|
||||
from frappy.errors import ConfigError
|
||||
from frappy.lib import generalConfig
|
||||
@ -88,50 +88,17 @@ class Mod(dict):
|
||||
for member in members:
|
||||
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:
|
||||
def __init__(self):
|
||||
self.modules = {}
|
||||
self.warnings = []
|
||||
def __init__(self, cls):
|
||||
self.list = []
|
||||
self.cls = cls
|
||||
|
||||
def add(self, *args, **kwds):
|
||||
mod = Mod(*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
|
||||
self.list.append(self.cls(*args, **kwds))
|
||||
|
||||
def override(self, name, **kwds):
|
||||
"""override properties/parameters of previously configured modules
|
||||
|
||||
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))
|
||||
def append(self, mod):
|
||||
self.list.append(mod)
|
||||
|
||||
|
||||
class NodeCollector:
|
||||
@ -144,16 +111,14 @@ class NodeCollector:
|
||||
else:
|
||||
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):
|
||||
def __init__(self, node, modules):
|
||||
super().__init__(node=node.node, **modules.modules)
|
||||
self.module_names = set(modules.modules)
|
||||
super().__init__(
|
||||
node=node.node,
|
||||
**{mod['name']: mod for mod in modules.list}
|
||||
)
|
||||
self.module_names = {mod.pop('name') for mod in modules.list}
|
||||
self.ambiguous = set()
|
||||
|
||||
def merge_modules(self, other):
|
||||
@ -169,35 +134,25 @@ class Config(dict):
|
||||
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):
|
||||
config_text = filename.read_bytes()
|
||||
node = NodeCollector()
|
||||
mods = Collector()
|
||||
ns = {'Node': node.add, 'Mod': mods.add, 'Param': Param, 'Command': Param, 'Group': Group,
|
||||
'override': mods.override, 'overrideNode': node.override}
|
||||
ns['include'] = Include(ns, log)
|
||||
mods = Collector(Mod)
|
||||
ns = {'Node': node.add, 'Mod': mods.add, 'Param': Param, 'Command': Param, 'Group': Group}
|
||||
|
||||
# pylint: disable=exec-used
|
||||
exec(compile(config_text, filename, 'exec'), ns)
|
||||
|
||||
if mods.warnings:
|
||||
log.warning('warnings in %s', filename)
|
||||
for text in mods.warnings:
|
||||
log.warning(text)
|
||||
# check for duplicates in the file itself. Between files comes later
|
||||
duplicates = [name for name, count in Counter([mod['name']
|
||||
for mod in mods.list]).items() if count > 1]
|
||||
if duplicates:
|
||||
log.warning('Duplicate module name in file \'%s\': %s',
|
||||
filename, ','.join(duplicates))
|
||||
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', '']]
|
||||
if os.sep in cfgfile: # specified as full path
|
||||
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
|
||||
if file is None:
|
||||
raise ConfigError(f"Couldn't find cfg file {cfgfile!r} in {generalConfig.confdir}")
|
||||
if not file.name.endswith(check_end):
|
||||
log.warning("Config files should end in %r: %s", check_end, file.name)
|
||||
if not file.name.endswith('_cfg.py'):
|
||||
log.warning("Config files should end in '_cfg.py': %s", file.name)
|
||||
log.debug('Using config file %s for %s', file, cfgfile)
|
||||
return file
|
||||
|
||||
@ -240,8 +195,6 @@ def load_config(cfgfiles, log):
|
||||
config.merge_modules(cfg)
|
||||
else:
|
||||
config = cfg
|
||||
if config.get('node') is None:
|
||||
raise ConfigError(f'missing Node in {filename}')
|
||||
|
||||
if config.ambiguous:
|
||||
log.warning('ambiguous sections in %s: %r',
|
||||
|
@ -27,6 +27,7 @@
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>18</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
|
@ -21,6 +21,7 @@
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>12</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
<underline>true</underline>
|
||||
</font>
|
||||
|
@ -60,6 +60,7 @@ class HasAccessibles(HasProperties):
|
||||
(so the dispatcher will get notified of changed values)
|
||||
"""
|
||||
isWrapped = False
|
||||
checkedMethods = set()
|
||||
|
||||
@classmethod
|
||||
def __init_subclass__(cls): # pylint: disable=too-many-branches
|
||||
@ -113,8 +114,8 @@ class HasAccessibles(HasProperties):
|
||||
wrapped_name = '_' + cls.__name__
|
||||
for pname, pobj in accessibles.items():
|
||||
# wrap of reading/writing funcs
|
||||
if not isinstance(pobj, Parameter) or pobj.optional:
|
||||
# nothing to do for Commands and optional parameters
|
||||
if not isinstance(pobj, Parameter):
|
||||
# nothing to do for Commands
|
||||
continue
|
||||
|
||||
rname = 'read_' + pname
|
||||
@ -198,15 +199,16 @@ class HasAccessibles(HasProperties):
|
||||
new_wfunc.__module__ = cls.__module__
|
||||
cls.wrappedAttributes[wname] = new_wfunc
|
||||
|
||||
cls.checkedMethods.update(cls.wrappedAttributes)
|
||||
|
||||
# check for programming errors
|
||||
for attrname, func in cls.__dict__.items():
|
||||
for attrname in dir(cls):
|
||||
prefix, _, pname = attrname.partition('_')
|
||||
if not pname:
|
||||
continue
|
||||
if prefix == 'do':
|
||||
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
|
||||
and not hasattr(func, 'poll')): # may be a handler, which always has a poll attribute
|
||||
if prefix in ('read', 'write') and attrname not in cls.checkedMethods:
|
||||
raise ProgrammingError(f'{cls.__name__}.{attrname} defined, but {pname!r} is no parameter')
|
||||
|
||||
try:
|
||||
@ -323,7 +325,6 @@ class Module(HasAccessibles):
|
||||
|
||||
pollInfo = None
|
||||
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):
|
||||
# remember the secnode for interacting with other modules and the
|
||||
@ -389,8 +390,6 @@ class Module(HasAccessibles):
|
||||
accessibles = self.accessibles
|
||||
self.accessibles = {}
|
||||
for aname, aobj in accessibles.items():
|
||||
if aobj.optional:
|
||||
continue
|
||||
# make a copy of the Parameter/Command object
|
||||
aobj = aobj.copy()
|
||||
acfg = cfgdict.pop(aname, None)
|
||||
@ -451,12 +450,9 @@ class Module(HasAccessibles):
|
||||
self.parameters[name] = accessible
|
||||
if isinstance(accessible, Command):
|
||||
self.commands[name] = accessible
|
||||
if cfg is not None:
|
||||
if cfg:
|
||||
try:
|
||||
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)
|
||||
except KeyError:
|
||||
self.errors.append(f"'{name}' has no property '{propname}'")
|
||||
@ -526,7 +522,9 @@ class Module(HasAccessibles):
|
||||
if validate:
|
||||
value = pobj.datatype(value)
|
||||
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:
|
||||
changed = pobj.value != value or pobj.readerror
|
||||
# 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?
|
||||
# del self.errors
|
||||
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
|
||||
|
||||
def initialReads(self):
|
||||
@ -626,28 +624,8 @@ class Module(HasAccessibles):
|
||||
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):
|
||||
"""called when the server shuts down
|
||||
"""called when the sever shuts down
|
||||
|
||||
any cleanup-work should be performed here, like closing threads and
|
||||
saving data.
|
||||
@ -679,10 +657,13 @@ class Module(HasAccessibles):
|
||||
self.pollInfo.pending_errors.discard(rfunc.__name__)
|
||||
except Exception as e:
|
||||
if getattr(e, 'report_error', True):
|
||||
self.log.debug('error in %r', rfunc)
|
||||
name = rfunc.__name__
|
||||
self.pollInfo.pending_errors.add(name) # trigger o.k. message after error is resolved
|
||||
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:
|
||||
self.log.debug('%s', e.format(False))
|
||||
else:
|
||||
@ -690,7 +671,7 @@ class Module(HasAccessibles):
|
||||
if raise_com_failed and isinstance(e, CommunicationFailedError):
|
||||
raise
|
||||
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
|
||||
self.log.error('%s', formatException())
|
||||
|
||||
@ -750,12 +731,11 @@ class Module(HasAccessibles):
|
||||
if not polled_modules: # no polls needed - exit thread
|
||||
return
|
||||
to_poll = ()
|
||||
while modules: # modules will be cleared on shutdown
|
||||
while True:
|
||||
now = time.time()
|
||||
wait_time = 999
|
||||
for mobj in modules:
|
||||
pinfo = mobj.pollInfo
|
||||
if pinfo:
|
||||
wait_time = min(pinfo.last_main + pinfo.interval - now, wait_time,
|
||||
pinfo.last_slow + mobj.slowinterval - now)
|
||||
if wait_time > 0 and not to_poll:
|
||||
@ -766,7 +746,7 @@ class Module(HasAccessibles):
|
||||
# call doPoll of all modules where due
|
||||
for mobj in modules:
|
||||
pinfo = mobj.pollInfo
|
||||
if pinfo and now > pinfo.last_main + pinfo.interval:
|
||||
if now > pinfo.last_main + pinfo.interval:
|
||||
try:
|
||||
pinfo.last_main = (now // pinfo.interval) * pinfo.interval
|
||||
except ZeroDivisionError:
|
||||
@ -786,7 +766,7 @@ class Module(HasAccessibles):
|
||||
# collect due slow polls
|
||||
for mobj in modules:
|
||||
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)
|
||||
pinfo.last_slow = (now // mobj.slowinterval) * mobj.slowinterval
|
||||
if to_poll:
|
||||
|
@ -68,8 +68,8 @@ class Writable(Readable):
|
||||
target_dt.compatible(value_dt)
|
||||
except Exception:
|
||||
if type(value_dt) == type(target_dt):
|
||||
raise ConfigError(f'{name}: 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 ConfigError('the target range extends beyond the value range') from None
|
||||
raise ProgrammingError('the datatypes of target and value are not compatible') from None
|
||||
|
||||
|
||||
class Drivable(Writable):
|
||||
|
@ -47,7 +47,6 @@ class Accessible(HasProperties):
|
||||
"""
|
||||
|
||||
ownProperties = None
|
||||
optional = False
|
||||
|
||||
def init(self, kwds):
|
||||
# do not use self.propertyValues.update here, as no invalid values should be
|
||||
@ -97,8 +96,6 @@ class Accessible(HasProperties):
|
||||
props = []
|
||||
for k, v in sorted(self.propertyValues.items()):
|
||||
props.append(f'{k}={v!r}')
|
||||
if self.optional:
|
||||
props.append('optional=True')
|
||||
return f"{self.__class__.__name__}({', '.join(props)})"
|
||||
|
||||
def fixExport(self):
|
||||
@ -194,9 +191,8 @@ class Parameter(Accessible):
|
||||
readerror = None
|
||||
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__()
|
||||
self.optional = optional
|
||||
if 'poll' in kwds and generalConfig.tolerate_poll_property:
|
||||
kwds.pop('poll')
|
||||
if datatype is None:
|
||||
@ -230,16 +226,10 @@ class Parameter(Accessible):
|
||||
def __get__(self, instance, owner):
|
||||
if instance is None:
|
||||
return self
|
||||
try:
|
||||
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):
|
||||
try:
|
||||
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):
|
||||
self.name = name
|
||||
@ -316,7 +306,7 @@ class Parameter(Accessible):
|
||||
if modobj:
|
||||
if self.update_unchanged == -1:
|
||||
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:
|
||||
self.omit_unchanged_within = float(self.update_unchanged)
|
||||
|
||||
@ -376,6 +366,9 @@ class Command(Accessible):
|
||||
* True: exported, name automatic.
|
||||
* a string: exported with custom name''', OrType(BoolType(), StringType()),
|
||||
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 of the command, auto generated from 'argument' and 'result'",
|
||||
DataTypeType(), extname='datainfo', export='always')
|
||||
@ -391,9 +384,8 @@ class Command(Accessible):
|
||||
|
||||
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__()
|
||||
self.optional = optional
|
||||
if 'datatype' in kwds:
|
||||
# self.init will complain about invalid keywords except 'datatype', as this is a property
|
||||
raise ProgrammingError("Command() got an invalid keyword 'datatype'")
|
||||
@ -419,8 +411,8 @@ class Command(Accessible):
|
||||
|
||||
def __set_name__(self, owner, name):
|
||||
self.name = name
|
||||
if self.func is None and not self.optional:
|
||||
raise ProgrammingError(f'Command {owner.__name__}.{name} must be optional or used as a method decorator')
|
||||
if self.func is None:
|
||||
raise ProgrammingError(f'Command {owner.__name__}.{name} must be used as a method decorator')
|
||||
|
||||
self.fixExport()
|
||||
self.datatype = CommandType(self.argument, self.result)
|
||||
|
@ -94,7 +94,6 @@ logger = MainLogger()
|
||||
|
||||
class Playground(Server):
|
||||
def __init__(self, **kwds): # pylint: disable=super-init-not-called
|
||||
self.name = 'playground'
|
||||
for modname, cfg in kwds.items():
|
||||
cfg.setdefault('description', modname)
|
||||
self.log = logger.log
|
||||
|
@ -131,16 +131,14 @@ class HasProperties(HasDescriptors):
|
||||
properties = {}
|
||||
# using cls.__bases__ and base.propertyDict for this would fail on some multiple inheritance cases
|
||||
for base in reversed(cls.__mro__):
|
||||
for key, value in base.__dict__.items():
|
||||
if isinstance(value, Property):
|
||||
properties[key] = value
|
||||
elif isinstance(value, HasProperties): # value is a Parameter. allow to override
|
||||
properties.pop(key, None)
|
||||
properties.update({k: v for k, v in base.__dict__.items() if isinstance(v, Property)})
|
||||
cls.propertyDict = properties
|
||||
# treat overriding properties with bare values
|
||||
for pn, po in list(properties.items()):
|
||||
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()
|
||||
try:
|
||||
# try to apply bare value to Property
|
||||
|
@ -23,9 +23,6 @@
|
||||
import os
|
||||
import json
|
||||
import socket
|
||||
import select
|
||||
from time import monotonic
|
||||
from collections import namedtuple
|
||||
|
||||
from frappy.lib import closeSocket
|
||||
from frappy.protocol.interface.tcp import format_address
|
||||
@ -35,79 +32,6 @@ UDP_PORT = 10767
|
||||
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:
|
||||
def __init__(self, equipment_id, description, ifaces, logger, *,
|
||||
startup_broadcast=True):
|
||||
|
@ -265,9 +265,9 @@ class Dispatcher:
|
||||
modulename, exportedname = specifier, None
|
||||
if ':' in specifier:
|
||||
modulename, exportedname = specifier.split(':', 1)
|
||||
moduleobj = self.secnode.get_module(modulename)
|
||||
if moduleobj is None or not moduleobj.export:
|
||||
if modulename not in self.secnode.export:
|
||||
raise NoSuchModuleError(f'Module {modulename!r} does not exist')
|
||||
moduleobj = self.secnode.get_module(modulename)
|
||||
if exportedname is not None:
|
||||
pname = moduleobj.accessiblename2attr.get(exportedname, True)
|
||||
if pname and pname not in moduleobj.accessibles:
|
||||
@ -281,7 +281,7 @@ class Dispatcher:
|
||||
else:
|
||||
# activate all modules
|
||||
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.
|
||||
# note: The initial poll already happend before the server is active
|
||||
|
@ -33,7 +33,7 @@ from frappy.io import HasIO
|
||||
DISCONNECTED = Readable.Status.ERROR, 'disconnected'
|
||||
|
||||
|
||||
class Proxy(HasIO, Module):
|
||||
class ProxyModule(HasIO, Module):
|
||||
module = Property('remote module name', datatype=StringType(), default='')
|
||||
status = Parameter('connection status', Readable.status.datatype) # add status even when not a Readable
|
||||
|
||||
@ -42,17 +42,6 @@ class Proxy(HasIO, Module):
|
||||
_secnode = None
|
||||
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):
|
||||
opts['description'] = f"secnode {opts.get('module', name)} on {opts['uri']}"
|
||||
return SecNode(name, logger, opts, srv)
|
||||
@ -142,19 +131,19 @@ class Proxy(HasIO, Module):
|
||||
pass # skip
|
||||
|
||||
|
||||
class ProxyReadable(Proxy, Readable):
|
||||
class ProxyReadable(ProxyModule, Readable):
|
||||
pass
|
||||
|
||||
|
||||
class ProxyWritable(Proxy, Writable):
|
||||
class ProxyWritable(ProxyModule, Writable):
|
||||
pass
|
||||
|
||||
|
||||
class ProxyDrivable(Proxy, Drivable):
|
||||
class ProxyDrivable(ProxyModule, Drivable):
|
||||
pass
|
||||
|
||||
|
||||
PROXY_CLASSES = [ProxyDrivable, ProxyWritable, ProxyReadable, Proxy]
|
||||
PROXY_CLASSES = [ProxyDrivable, ProxyWritable, ProxyReadable, ProxyModule]
|
||||
|
||||
|
||||
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
|
||||
|
||||
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):
|
||||
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')
|
||||
|
||||
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)
|
||||
|
@ -102,6 +102,7 @@ class Handler:
|
||||
"""create the wrapped read_* or write_* methods"""
|
||||
# at this point, this 'method_names' entry is no longer used -> delete
|
||||
self.method_names.discard((self.func.__module__, self.func.__qualname__))
|
||||
owner.checkedMethods.add(name)
|
||||
for key in self.keys:
|
||||
wrapped = self.wrap(key)
|
||||
method_name = self.prefix + key
|
||||
|
@ -19,7 +19,6 @@
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
import time
|
||||
import traceback
|
||||
from collections import OrderedDict
|
||||
|
||||
@ -27,7 +26,6 @@ from frappy.dynamic import Pinata
|
||||
from frappy.errors import ConfigError, NoSuchModuleError, NoSuchParameterError
|
||||
from frappy.lib import get_class
|
||||
from frappy.version import get_version
|
||||
from frappy.modules import Module
|
||||
|
||||
|
||||
class SecNode:
|
||||
@ -44,6 +42,8 @@ class SecNode:
|
||||
self.nodeprops = {}
|
||||
# map ALL modulename -> moduleobj
|
||||
self.modules = {}
|
||||
# list of EXPORTED modules
|
||||
self.export = []
|
||||
self.log = logger
|
||||
self.srv = srv
|
||||
# set of modules that failed creation
|
||||
@ -130,9 +130,6 @@ class SecNode:
|
||||
# creation has failed already once, do not try again
|
||||
return None
|
||||
cls = classname
|
||||
if not issubclass(cls, Module):
|
||||
self.errors.append(f'{cls.__name__} is not a Module')
|
||||
return None
|
||||
except Exception as e:
|
||||
if str(e) == 'no such class':
|
||||
self.errors.append(f'{classname} not found')
|
||||
@ -191,62 +188,60 @@ class SecNode:
|
||||
modname, len(pinata_modules))
|
||||
todos.extend(pinata_modules)
|
||||
|
||||
def export_accessibles(self, modobj):
|
||||
self.log.debug('export_accessibles(%r)', modobj.name)
|
||||
def export_accessibles(self, modulename):
|
||||
self.log.debug('export_accessibles(%r)', modulename)
|
||||
if modulename in self.export:
|
||||
# omit export=False params!
|
||||
res = OrderedDict()
|
||||
for aobj in modobj.accessibles.values():
|
||||
for aobj in self.get_module(modulename).accessibles.values():
|
||||
if aobj.export:
|
||||
res[aobj.export] = aobj.for_export()
|
||||
self.log.debug('list accessibles for module %s -> %r',
|
||||
modobj.name, res)
|
||||
modulename, res)
|
||||
return res
|
||||
self.log.debug('-> module is not to be exported!')
|
||||
return OrderedDict()
|
||||
|
||||
def build_descriptive_data(self):
|
||||
def get_descriptive_data(self, specifier):
|
||||
"""returns a python object which upon serialisation results in the
|
||||
descriptive data"""
|
||||
specifier = specifier or ''
|
||||
modules = {}
|
||||
result = {'modules': modules}
|
||||
for modulename in self.modules:
|
||||
modobj = self.get_module(modulename)
|
||||
if not modobj.export:
|
||||
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(modobj)}
|
||||
mod_desc.update(modobj.exportProperties())
|
||||
mod_desc.pop('export', None)
|
||||
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(':')
|
||||
if modname in modules: # extension to SECoP standard: description of a single module
|
||||
result = modules[modname]
|
||||
if pname in result['accessibles']: # extension to SECoP standard: description of a single accessible
|
||||
# command is also accepted
|
||||
result = result['accessibles'][pname]
|
||||
elif pname:
|
||||
raise NoSuchParameterError(f'Module {modname!r} '
|
||||
f'has no parameter {pname!r}')
|
||||
elif not modname or modname == '.':
|
||||
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):
|
||||
"""returns a python object which upon serialisation results in the
|
||||
descriptive data"""
|
||||
specifier = specifier or ''
|
||||
modname, _, pname = specifier.partition(':')
|
||||
modules = self.descriptive_data['modules']
|
||||
if modname in modules: # extension to SECoP standard: description of a single module
|
||||
result = modules[modname]
|
||||
if pname in result['accessibles']: # extension to SECoP standard: description of a single accessible
|
||||
# command is also accepted
|
||||
return result['accessibles'][pname]
|
||||
if pname:
|
||||
raise NoSuchParameterError(f'Module {modname!r} '
|
||||
f'has no parameter {pname!r}')
|
||||
return result
|
||||
if not modname or modname == '.':
|
||||
return self.descriptive_data
|
||||
else:
|
||||
raise NoSuchModuleError(f'Module {modname!r} does not exist')
|
||||
|
||||
def get_exported_modules(self):
|
||||
return [m for m, o in self.modules.items() if o.export]
|
||||
return result
|
||||
|
||||
def add_module(self, module, modulename):
|
||||
"""Adds a named module object to this SecNode."""
|
||||
self.modules[modulename] = module
|
||||
if module.export:
|
||||
self.export.append(modulename)
|
||||
|
||||
# def remove_module(self, modulename_or_obj):
|
||||
# moduleobj = self.get_module(modulename_or_obj)
|
||||
@ -260,15 +255,6 @@ class SecNode:
|
||||
|
||||
def shutdown_modules(self):
|
||||
"""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():
|
||||
self.modules[name].shutdownModule()
|
||||
|
||||
|
@ -289,6 +289,7 @@ class Server:
|
||||
If there are errors that occur, they will be collected and emitted
|
||||
together in the end.
|
||||
"""
|
||||
errors = []
|
||||
opts = dict(self.node_cfg)
|
||||
cls = get_class(opts.pop('cls'))
|
||||
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.create_modules()
|
||||
# initialize modules by calling self.secnode.get_module for all of them
|
||||
# this is done in build_descriptive_data even for unexported modules
|
||||
self.secnode.build_descriptive_data()
|
||||
# initialize all modules by getting them with Dispatcher.get_module,
|
||||
# which is done in the get_descriptive data
|
||||
# TODO: caching, to not make this extra work
|
||||
self.secnode.get_descriptive_data('')
|
||||
# =========== All modules are initialized ===========
|
||||
|
||||
# all errors from initialization process
|
||||
|
@ -142,5 +142,4 @@ class SimDrivable(SimReadable, Drivable):
|
||||
|
||||
@Command
|
||||
def stop(self):
|
||||
"""set target to value"""
|
||||
self.target = self.value
|
||||
|
@ -215,10 +215,7 @@ class HasStates:
|
||||
self.read_status()
|
||||
if fast_poll:
|
||||
sm.reset_fast_poll = True
|
||||
if fast_poll is True:
|
||||
self.setFastPoll(True)
|
||||
else:
|
||||
self.setFastPoll(True, fast_poll)
|
||||
self.pollInfo.trigger(True) # trigger poller
|
||||
|
||||
def stop_machine(self, stopped_status=(IDLE, 'stopped')):
|
||||
|
@ -161,7 +161,7 @@ class Cryostat(CryoBase):
|
||||
|
||||
by setting the current setpoint as new target"""
|
||||
# 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
|
||||
|
@ -28,7 +28,7 @@ import time
|
||||
from frappy.datatypes import ArrayOf, BoolType, EnumType, \
|
||||
FloatRange, IntRange, StringType, StructOf, TupleOf
|
||||
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.properties import Property
|
||||
|
||||
@ -99,14 +99,6 @@ class Switch(Drivable):
|
||||
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):
|
||||
"""a liquid magnet
|
||||
"""
|
||||
|
@ -22,13 +22,11 @@
|
||||
|
||||
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.params import Command
|
||||
from frappy.dynamic import Pinata
|
||||
from frappy.errors import RangeError, HardwareError
|
||||
from frappy.core import IDLE, WARN, ERROR, DISABLED
|
||||
|
||||
|
||||
class Pin(Pinata):
|
||||
def scanModules(self):
|
||||
@ -107,27 +105,13 @@ class Temp(Drivable):
|
||||
readonly=False,
|
||||
unit='K',
|
||||
)
|
||||
enabled = Parameter('enable', BoolType(), default=True, readonly=False)
|
||||
status = Parameter(datatype=StatusType(Readable, 'DISABLED'))
|
||||
_status = IDLE, ''
|
||||
|
||||
def read_value(self):
|
||||
value = 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
|
||||
return round(100 * random.random(), 1)
|
||||
|
||||
def write_target(self, target):
|
||||
pass
|
||||
|
||||
def read_status(self):
|
||||
return self._status if self.enabled else (DISABLED, 'disabled')
|
||||
|
||||
|
||||
class Lower(Communicator):
|
||||
"""Communicator returning a lowercase version of the request"""
|
||||
|
@ -47,28 +47,6 @@ ADQ_TRANSFER_MODE_NORMAL = 0x00
|
||||
ADQ_CHANNELS_MASK = 0x3
|
||||
|
||||
GHz = 1e9
|
||||
RMS_TO_VPP = 2 * np.sqrt(2)
|
||||
|
||||
|
||||
class Timer:
|
||||
def __init__(self):
|
||||
self.data = [(time.time(), 'start')]
|
||||
|
||||
def __call__(self, text=''):
|
||||
now = time.time()
|
||||
prev = self.data[-1][0]
|
||||
self.data.append((now, text))
|
||||
return now - prev
|
||||
|
||||
def summary(self):
|
||||
return ' '.join(f'{txt} {tim:.3f}' for tim, txt in self.data[1:])
|
||||
|
||||
def show(self):
|
||||
first = prev = self.data[0][0]
|
||||
print('---', first)
|
||||
for tim, txt in self.data[1:]:
|
||||
print(f'{(tim - first) * 1000:9.3f} {(tim - prev) * 1000:9.3f} ms {txt}')
|
||||
prev = tim
|
||||
|
||||
|
||||
class Adq:
|
||||
@ -80,8 +58,12 @@ class Adq:
|
||||
bw_cutoff = 10E6
|
||||
trigger = EXT_TRIG_1
|
||||
adq_num = 1
|
||||
UNDEFINED = -1
|
||||
IDLE = 0
|
||||
BUSY = 1
|
||||
READY = 2
|
||||
status = UNDEFINED
|
||||
data = None
|
||||
busy = False
|
||||
|
||||
def __init__(self):
|
||||
global ADQAPI
|
||||
@ -102,24 +84,23 @@ class Adq:
|
||||
ADQAPI.ADQControlUnit_FindDevices(self.adq_cu)
|
||||
n_of_adq = ADQAPI.ADQControlUnit_NofADQ(self.adq_cu)
|
||||
if n_of_adq != 1:
|
||||
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')
|
||||
sys.exit(0)
|
||||
atexit.register(self.deletecu)
|
||||
signal.signal(signal.SIGTERM, lambda *_: sys.exit(0))
|
||||
raise RuntimeError('number of ADQs must be 1, not %d' % n_of_adq)
|
||||
|
||||
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]}']
|
||||
print('\nConnected to ADQ #1')
|
||||
# Print revision information
|
||||
print('FPGA Revision: {}'.format(revision[0]))
|
||||
if revision[1]:
|
||||
out.append('Local copy')
|
||||
print('Local copy')
|
||||
else:
|
||||
print('SVN Managed')
|
||||
if revision[2]:
|
||||
out.append('SVN Managed - Mixed Revision')
|
||||
print('Mixed Revision')
|
||||
else:
|
||||
out.append('SVN Updated')
|
||||
print(', '.join(out))
|
||||
print('SVN Updated')
|
||||
print('')
|
||||
|
||||
ADQAPI.ADQ_SetClockSource(self.adq_cu, self.adq_num, ADQ_CLOCK_EXT_REF)
|
||||
|
||||
##########################
|
||||
@ -147,11 +128,12 @@ class Adq:
|
||||
raise RuntimeError('ADQ_SetLvlTrigEdge failed.')
|
||||
# if not ADQAPI.ADQ_SetTriggerThresholdVoltage(self.adq_cu, self.adq_num, trigger, ct.c_double(0.2)):
|
||||
# raise RuntimeError('SetTriggerThresholdVoltage failed.')
|
||||
# proabably the folloiwng is wrong.
|
||||
# print("CHANNEL:" + str(ct.c_int(ADQAPI.ADQ_GetLvlTrigChannel(self.adq_cu, self.adq_num))))
|
||||
print("CHANNEL:"+str(ct.c_int(ADQAPI.ADQ_GetLvlTrigChannel(self.adq_cu, self.adq_num))))
|
||||
atexit.register(self.deletecu)
|
||||
signal.signal(signal.SIGTERM, lambda *_: sys.exit(0))
|
||||
|
||||
def init(self, samples_per_record=None, number_of_records=None):
|
||||
"""initialize dimensions and store result object"""
|
||||
"""initialize dimensions"""
|
||||
if samples_per_record:
|
||||
self.samples_per_record = samples_per_record
|
||||
if number_of_records:
|
||||
@ -163,18 +145,13 @@ class Adq:
|
||||
bufp.contents = (ct.c_int16 * self.samples_per_record * self.number_of_records)()
|
||||
|
||||
def deletecu(self):
|
||||
cu = self.__dict__.pop('adq_cu', None)
|
||||
if cu is None:
|
||||
return
|
||||
print('shut down ADQ')
|
||||
# Only disarm trigger after data is collected
|
||||
ADQAPI.ADQ_DisarmTrigger(cu, self.adq_num)
|
||||
ADQAPI.ADQ_MultiRecordClose(cu, self.adq_num)
|
||||
ADQAPI.ADQ_DisarmTrigger(self.adq_cu, self.adq_num)
|
||||
ADQAPI.ADQ_MultiRecordClose(self.adq_cu, self.adq_num)
|
||||
# Delete ADQControlunit
|
||||
ADQAPI.DeleteADQControlUnit(cu)
|
||||
print('ADQ closed')
|
||||
ADQAPI.DeleteADQControlUnit(self.adq_cu)
|
||||
|
||||
def start(self, data):
|
||||
def start(self):
|
||||
# Start acquisition
|
||||
ADQAPI.ADQ_MultiRecordSetup(self.adq_cu, self.adq_num,
|
||||
self.number_of_records,
|
||||
@ -182,36 +159,35 @@ class Adq:
|
||||
|
||||
ADQAPI.ADQ_DisarmTrigger(self.adq_cu, self.adq_num)
|
||||
ADQAPI.ADQ_ArmTrigger(self.adq_cu, self.adq_num)
|
||||
self.data = data
|
||||
|
||||
def get_data(self):
|
||||
"""get new data if available"""
|
||||
ready = False
|
||||
data = self.data
|
||||
if not data:
|
||||
self.busy = False
|
||||
return None # no new data
|
||||
self.status = self.BUSY
|
||||
|
||||
def get_status(self):
|
||||
"""check if ADQ card is busy"""
|
||||
if self.status == self.BUSY:
|
||||
if ADQAPI.ADQ_GetAcquiredAll(self.adq_cu, self.adq_num):
|
||||
ready = True
|
||||
data.timer('ready')
|
||||
self.status = self.READY
|
||||
else:
|
||||
if self.trigger == SW_TRIG:
|
||||
ADQAPI.ADQ_SWTrig(self.adq_cu, self.adq_num)
|
||||
if not ready:
|
||||
self.busy = True
|
||||
return None
|
||||
self.data = None
|
||||
t = time.time()
|
||||
return self.status
|
||||
|
||||
def get_data(self, dataclass, **kwds):
|
||||
"""when ready, get raw data from card, else return cached data
|
||||
|
||||
return
|
||||
"""
|
||||
if self.get_status() == self.READY:
|
||||
# Get data from ADQ
|
||||
if not ADQAPI.ADQ_GetData(
|
||||
self.adq_cu, self.adq_num, self.target_buffers,
|
||||
if not ADQAPI.ADQ_GetData(self.adq_cu, self.adq_num, self.target_buffers,
|
||||
self.samples_per_record * self.number_of_records, 2,
|
||||
0, self.number_of_records, ADQ_CHANNELS_MASK,
|
||||
0, self.samples_per_record, ADQ_TRANSFER_MODE_NORMAL):
|
||||
raise RuntimeError('no success from ADQ_GetDATA')
|
||||
data.retrieve(self)
|
||||
return data
|
||||
self.data = dataclass(self, **kwds)
|
||||
self.status = self.IDLE
|
||||
if self.status == self.UNDEFINED:
|
||||
raise RuntimeError('no data available yet')
|
||||
return self.data
|
||||
|
||||
|
||||
class PEdata:
|
||||
@ -219,20 +195,12 @@ class PEdata:
|
||||
self.sample_rate = adq.sample_rate
|
||||
self.samp_freq = self.sample_rate / GHz
|
||||
self.number_of_records = adq.number_of_records
|
||||
self.timer = Timer()
|
||||
|
||||
def retrieve(self, adq):
|
||||
data = []
|
||||
rawsignal = []
|
||||
for ch in range(2):
|
||||
onedim = np.frombuffer(adq.target_buffers[ch].contents, dtype=np.int16)
|
||||
rawsignal.append(onedim[:adq.samples_per_record])
|
||||
# convert 16 bit int to a value in the range -1 .. 1
|
||||
data.append(onedim.reshape(adq.number_of_records, adq.samples_per_record) / float(2 ** 15))
|
||||
data.append(onedim.reshape(adq.number_of_records, adq.samples_per_record) / float(2**14)) # 14 bits ADC
|
||||
# Now this is an array with all records, but the time is artificial
|
||||
self.data = data
|
||||
self.rawsignal = rawsignal
|
||||
self.timer('retrieved')
|
||||
|
||||
def sinW(self, sig, freq, ti, tf):
|
||||
# sig: signal array
|
||||
@ -285,113 +253,61 @@ class PEdata:
|
||||
|
||||
def gates_and_curves(self, freq, pulse, roi, bw_cutoff):
|
||||
"""return iq values of rois and prepare plottable curves for iq"""
|
||||
self.timer('gates')
|
||||
try:
|
||||
self.ndecimate = int(round(self.sample_rate / freq))
|
||||
except TypeError as e:
|
||||
raise TypeError(f'{self.sample_rate}/{freq} {e}')
|
||||
# times = []
|
||||
# times.append(('aviq', time.time()))
|
||||
iq = self.averageiq(self.data, freq / GHz, *pulse)
|
||||
self.timer('aviq')
|
||||
# times.append(('filtro', time.time()))
|
||||
iqf = self.filtro(iq, bw_cutoff)
|
||||
self.timer('filtro')
|
||||
m = max(1, len(iqf[0]) // self.ndecimate)
|
||||
m = len(iqf[0]) // self.ndecimate
|
||||
ll = m * self.ndecimate
|
||||
iqf = [iqfx[0:ll] for iqfx in iqf]
|
||||
self.timer('iqf')
|
||||
# times.append(('iqdec', time.time()))
|
||||
iqd = np.average(np.resize(iqf, (2, m, self.ndecimate)), axis=2)
|
||||
self.timer('avg')
|
||||
t_axis = np.arange(m) * self.ndecimate / self.samp_freq
|
||||
pulsig = np.abs(self.data[0][0])
|
||||
self.timer('pulsig')
|
||||
# times.append(('pulsig', time.time()))
|
||||
pulsig = np.average(np.resize(pulsig, (m, self.ndecimate)), axis=1)
|
||||
result = ([self.box(iqf, *r) for r in roi], # gates
|
||||
(t_axis, iqd[0], iqd[1], pulsig)) # curves
|
||||
self.timer('result')
|
||||
# self.timer.show()
|
||||
# ns = len(self.rawsignal[0]) * self.number_of_records
|
||||
# print(f'{ns} {ns / 2e6} ms')
|
||||
return result
|
||||
|
||||
|
||||
class Namespace:
|
||||
"""holds channel or other data"""
|
||||
def __init__(self, **kwds):
|
||||
self.__dict__.update(**kwds)
|
||||
self.curves = (t_axis, iqd[0], iqd[1], pulsig)
|
||||
# print(times)
|
||||
return [self.box(iqf, *r) for r in roi]
|
||||
|
||||
|
||||
class RUSdata:
|
||||
def __init__(self, adq, freq, periods, delay_samples):
|
||||
def __init__(self, adq, freq, periods):
|
||||
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()
|
||||
input_signal = np.frombuffer(adq.target_buffers[0].contents, dtype=np.int16)
|
||||
output_signal = np.frombuffer(adq.target_buffers[1].contents, dtype=np.int16)
|
||||
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
|
||||
self.output_mixed = output_signal * complex_sinusoid
|
||||
self.input_mean = self.input_mixed.mean()
|
||||
self.output_mean = self.output_mixed.mean()
|
||||
self.iq = self.output_mean / self.input_mean
|
||||
|
||||
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')
|
||||
def get_reduced(self, mixed):
|
||||
"""get reduced array and normalize"""
|
||||
nper = self.samples_per_record // self.periods
|
||||
mean = mixed.mean()
|
||||
return mixed[:self.period * nper].reshape((-1, nper)).mean(axis=0) / mean
|
||||
|
||||
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):
|
||||
def calc_quality(self):
|
||||
"""get signal quality info
|
||||
|
||||
quality info (small values indicate good quality):
|
||||
- input_stddev:
|
||||
- input_std and output_std:
|
||||
the imaginary part indicates deviations in phase
|
||||
the real part indicates deviations in amplitude
|
||||
- output_slope:
|
||||
- input_slope and 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
|
||||
reduced = self.get_reduced(self.input_mixed)
|
||||
self.input_stdev = reduced.std()
|
||||
|
||||
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
|
||||
reduced = self.get_reduced(self.output_mixed)
|
||||
timeaxis = np.arange(len(reduced)) * self.sample_rate / self.freq
|
||||
self.output_slope = np.polyfit(timeaxis, reduced, 1)[0]
|
||||
|
@ -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)
|
@ -66,9 +66,8 @@ class Power(HasIO, Readable):
|
||||
|
||||
|
||||
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='%'))
|
||||
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)
|
||||
maxcurrent = Parameter('current at 100%',datatype=FloatRange(0,5,unit='A'),default=2,readonly=False)
|
||||
output_enable = Parameter('control on/off', BoolType(), readonly=False)
|
||||
@ -79,10 +78,8 @@ class Output(HasIO, Writable):
|
||||
|
||||
def write_target(self, target):
|
||||
self.write_output_enable(target != 0)
|
||||
self.communicate(f'VOLT{round(max(8,(target)**0.5 * self.maxvolt)):03d}')
|
||||
self.communicate(f'CURR{round((target)**0.5* 10 * 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.communicate(f'VOLT{round(max(8,target*self.maxvolt/10)):03d}')
|
||||
self.communicate(f'CURR{round(target*self.maxcurrent):03d}')
|
||||
self.value = target
|
||||
|
||||
def write_output_enable(self, value):
|
||||
|
@ -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()
|
@ -1,144 +1,125 @@
|
||||
import os
|
||||
from glob import glob
|
||||
from pathlib import Path
|
||||
from configparser import ConfigParser
|
||||
from frappy.errors import ConfigError
|
||||
|
||||
|
||||
class Rack:
|
||||
configbase = Path('/home/l_samenv/.config/frappy_instruments')
|
||||
|
||||
def __init__(self, modfactory, **kwds):
|
||||
self.modfactory = modfactory
|
||||
instpath = self.configbase / os.environ['Instrument']
|
||||
sections = {}
|
||||
self.config = {}
|
||||
files = glob(str(instpath / '*.ini'))
|
||||
for filename in files:
|
||||
parser = ConfigParser()
|
||||
parser.optionxform = str
|
||||
parser.read([filename])
|
||||
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)
|
||||
class Lsc:
|
||||
def __init__(self, modfactory, ls_uri, ls_ioname='lsio', ls_devname='ls', ls_model='336', **kwds):
|
||||
self.modfactory = Mod = modfactory
|
||||
self.model = ls_model
|
||||
self.ioname = ls_ioname
|
||||
self.devname = ls_devname
|
||||
self.io = Mod(self.ioname, cls=f'frappy_psi.lakeshore.IO{self.model}',
|
||||
description='comm. to lakeshore in cc rack',
|
||||
uri=ls_uri)
|
||||
self.dev = Mod(self.devname, cls=f'frappy_psi.lakeshore.Device{self.model}',
|
||||
description='lakeshore in cc rack', io=self.ioname, curve_handling=True)
|
||||
self.loops = {}
|
||||
self.outputs = {}
|
||||
|
||||
def sensor(self, name, channel, calcurve, **kwds):
|
||||
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}')
|
||||
mod = Mod(name, channel=channel, calcurve=calcurve,
|
||||
device=self.ls_dev, **kwds)
|
||||
self.set_props(mod, io='lakeshore', dev='lakeshore')
|
||||
return Mod(name, channel=channel, calcurve=calcurve,
|
||||
io=self.ioname, device=self.devname, **kwds)
|
||||
|
||||
def loop(self, name, channel, calcurve, output_module, **kwds):
|
||||
def loop(self, name, channel, calcurve, **kwds):
|
||||
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}')
|
||||
Mod(name, channel=channel, calcurve=calcurve, output_module=output_module,
|
||||
device=self.ls_dev, **kwds)
|
||||
self.fix_props(f'heater({output_module})', description=f'heater for {name}')
|
||||
mod = Mod(name, channel=channel, calcurve=calcurve,
|
||||
io=self.ioname, device=self.devname, **kwds)
|
||||
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
|
||||
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:
|
||||
kwds.setdefault('cls', f'frappy_psi.lakeshore.SecondaryOutput{self.ls_model}')
|
||||
kwds.setdefault('cls', f'frappy_psi.lakeshore.SecondaryOutput{self.model}')
|
||||
else:
|
||||
return
|
||||
kwds.setdefault('description', '')
|
||||
mod = Mod(name, max_heater=max_heater, resistance=resistance, **kwds)
|
||||
self.set_props(mod, io='lakeshore', device='lakeshore', description=f'heater({name})')
|
||||
mod = Mod(name, max_heater=max_heater, resistance=resistance,
|
||||
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):
|
||||
if args_for_io is None:
|
||||
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 __enter__(self):
|
||||
return self
|
||||
|
||||
def he(self, name='He_lev', ccu_io='ccu_io', **kwds):
|
||||
self.ccu('he', ccu_io=ccu_io, args_for_io={}, **kwds)
|
||||
self.modfactory(name, cls='frappy_psi.ccu4.HeLevel',
|
||||
description='the He Level', io=ccu_io, **kwds)
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
outmodules = dict(self.outputs)
|
||||
for name, loop in self.loops.items():
|
||||
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,
|
||||
|
||||
class CCU:
|
||||
def __init__(self, Mod, ccu_uri, ccu_ioname='ccuio', ccu_devname='ccu', he=None, n2=None, **kwds):
|
||||
self.ioname = ccu_ioname
|
||||
self.devname = ccu_devname
|
||||
Mod(self.ioname, 'frappy_psi.ccu4.CCU4IO',
|
||||
'comm. to CCU4', uri=ccu_uri)
|
||||
if he:
|
||||
if not isinstance(he, str): # e.g. True
|
||||
he = 'He_lev'
|
||||
Mod(he, cls='frappy_psi.ccu4.HeLevel',
|
||||
description='the He Level', io=self.ioname)
|
||||
if n2:
|
||||
if isinstance(n2, str):
|
||||
n2 = n2.split(',')
|
||||
else: # e.g. True
|
||||
n2 = []
|
||||
n2, valve, upper, lower = n2 + ['N2_lev', 'N2_valve', 'N2_upper', 'N2_lower'][len(n2):]
|
||||
print(n2, valve, upper, lower)
|
||||
Mod(n2, cls='frappy_psi.ccu4.N2Level',
|
||||
description='the N2 Level', io=self.ioname,
|
||||
valve=valve, upper=upper, lower=lower)
|
||||
Mod(valve, cls='frappy_psi.ccu4.N2FillValve',
|
||||
description='LN2 fill valve', io=ccu_io)
|
||||
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')
|
||||
|
||||
def flow(self, hepump_uri=None, hepump_type=None, hepump_io='hepump_io',
|
||||
hepump='hepump', hepump_mot='hepump_mot', hepump_valve='hepump_valve',
|
||||
flow_sensor='flow_sensor', pump_pressure='pump_pressure', nv='nv',
|
||||
ccu_io='ccu_io', **kwds):
|
||||
"""creates needle valve and pump access if available"""
|
||||
kwds = self.ccu('flow', ccu_io=ccu_io, args_for_io={}, **kwds)
|
||||
Mod = self.modfactory
|
||||
hepump_type = hepump_type or self.config.get('hepump_type', 'no')
|
||||
Mod(nv, 'frappy_psi.ccu4.NeedleValveFlow', 'flow from flow sensor or pump pressure',
|
||||
flow_sensor=flow_sensor, pressure=pump_pressure, io=ccu_io, **kwds)
|
||||
Mod(pump_pressure, 'frappy_psi.ccu4.Pressure', 'He pump pressure', io=ccu_io)
|
||||
if hepump_type == 'no':
|
||||
print('no pump, no flow meter - using flow from pressure alone')
|
||||
return
|
||||
hepump_uri = hepump_uri or self.config['hepump_uri']
|
||||
Mod(hepump_io, 'frappy.io.BytesIO', 'He pump connection', uri=hepump_uri)
|
||||
Mod(hepump, 'frappy_psi.hepump.HePump', 'He pump', pump_type=hepump_type,
|
||||
valvemotor=hepump_mot, valve=hepump_valve, flow=nv)
|
||||
Mod(hepump_mot, 'frappy_psi.hepump.Motor', 'He pump valve motor', io=hepump_io, maxcurrent=2.8)
|
||||
Mod(hepump_valve, 'frappy_psi.butterflyvalve.Valve', 'He pump valve', motor=hepump_mot)
|
||||
Mod(flow_sensor, 'frappy_psi.sensirion.FlowSensor', 'Flow Sensor', io=hepump_io, nsamples=160)
|
||||
|
||||
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)
|
||||
|
||||
|
@ -22,24 +22,21 @@
|
||||
"""drivers for CCU4, the cryostat control unit at SINQ"""
|
||||
import time
|
||||
import math
|
||||
import numpy as np
|
||||
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
|
||||
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, \
|
||||
StatusType, IntRange, StringType, TupleOf, ArrayOf
|
||||
StatusType, IntRange, StringType, TupleOf
|
||||
from frappy.errors import CommunicationFailedError
|
||||
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)
|
||||
|
||||
|
||||
class IO(StringIO):
|
||||
class CCU4IO(StringIO):
|
||||
"""communication with CCU4"""
|
||||
# for completeness: (not needed, as it is the default)
|
||||
end_of_line = '\n'
|
||||
@ -47,8 +44,8 @@ class IO(StringIO):
|
||||
identification = [('cid', r'cid=CCU4.*')]
|
||||
|
||||
|
||||
class Base(HasIO):
|
||||
ioClass = IO
|
||||
class CCU4Base(HasIO):
|
||||
ioClass = CCU4IO
|
||||
|
||||
def command(self, **kwds):
|
||||
"""send a command and get the response
|
||||
@ -82,7 +79,7 @@ class Base(HasIO):
|
||||
return result
|
||||
|
||||
|
||||
class HeLevel(Base, Readable):
|
||||
class HeLevel(CCU4Base, Readable):
|
||||
"""He Level channel of CCU4"""
|
||||
|
||||
value = Parameter(unit='%')
|
||||
@ -124,10 +121,10 @@ class HeLevel(Base, Readable):
|
||||
return self.command(hfu=value)
|
||||
|
||||
|
||||
class Valve(Base, Writable):
|
||||
class Valve(CCU4Base, Writable):
|
||||
value = Parameter('relay state', BoolType())
|
||||
target = Parameter('relay target', BoolType())
|
||||
ioClass = IO
|
||||
ioClass = CCU4IO
|
||||
STATE_MAP = {0: (0, (IDLE, 'off')),
|
||||
1: (1, (IDLE, 'on')),
|
||||
2: (0, (ERROR, 'no valve')),
|
||||
@ -176,7 +173,7 @@ class N2TempSensor(Readable):
|
||||
value = Parameter('LN2 T sensor', FloatRange(unit='K'), default=0)
|
||||
|
||||
|
||||
class N2Level(Base, Readable):
|
||||
class N2Level(CCU4Base, Readable):
|
||||
valve = Attached(Writable, mandatory=False)
|
||||
lower = 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)
|
||||
|
||||
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',
|
||||
FloatRange(unit='s'), readonly=False, default=30)
|
||||
FloatRange(unit='s'), readonly=False)
|
||||
fill_timeout = Parameter('max. minutes needed to fill',
|
||||
FloatRange(unit='s'), readonly=False)
|
||||
names = Property('''names of attached modules
|
||||
@ -288,515 +285,189 @@ class N2Level(Base, Readable):
|
||||
self.command(nc=0)
|
||||
|
||||
|
||||
class N2LevelGuess(N2Level):
|
||||
"""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):
|
||||
class FlowPressure(CCU4Base, Readable):
|
||||
value = Parameter(unit='mbar')
|
||||
mbar_offset = Parameter('offset in mbar', FloatRange(unit='mbar'), default=0.8, readonly=False)
|
||||
filter_time = Parameter('filter time', FloatRange(unit='sec'), readonly=False, default=3)
|
||||
mbar_offset = Parameter(unit='mbar', default=0.8, readonly=False)
|
||||
pollinterval = Parameter(default=0.25)
|
||||
|
||||
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):
|
||||
return ArrayOf(TupleOf(FloatRange(), FloatRange(miny, maxy)))
|
||||
|
||||
|
||||
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)
|
||||
class NeedleValve(HasStates, CCU4Base, Drivable):
|
||||
flow = Attached(Readable, mandatory=False)
|
||||
flow_pressure = Attached(Readable, mandatory=False)
|
||||
|
||||
value = Parameter(unit='ln/min')
|
||||
target = Parameter(unit='ln/min')
|
||||
|
||||
motor_state = Parameter('motor_state', EnumType(M), default=0)
|
||||
speed = Parameter('speed moving time / passed time', FloatRange())
|
||||
tolerance = Parameter('tolerance', Table(0), value=[(2,0.1),(4,0.4)], readonly=False)
|
||||
prop_open = Parameter('proportional term for opening', Table(0), readonly=False, value=[(1,0.05)])
|
||||
prop_close = Parameter('proportional term for closing', Table(0), readonly=False, value=[(1,0.02)])
|
||||
lnm_per_mbar = Parameter(unit='ln/min/mbar', default=0.6, readonly=False)
|
||||
use_pressure = Parameter('use flow from pressure', BoolType(),
|
||||
default=False, readonly=False)
|
||||
motor_state = Parameter('motor_state', EnumType(M))
|
||||
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'),
|
||||
default=30, readonly=False)
|
||||
control_active = Parameter('control active flag', BoolType(), readonly=False, default=1)
|
||||
min_open_pulse = Parameter('minimal open step', FloatRange(0, unit='s'),
|
||||
readonly=False, default=0.02)
|
||||
min_close_pulse = Parameter('minimal close step', FloatRange(0, unit='s'),
|
||||
readonly=False, default=0.0)
|
||||
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
|
||||
settle = Parameter('time within tolerance before getting quiet', FloatRange(unit='s'),
|
||||
default=30, readonly=False)
|
||||
step_factor = Parameter('factor (no progress time) / (min step size)', FloatRange(), default=300)
|
||||
control_active = Parameter('control active flag', BoolType(), readonly=False)
|
||||
pollinterval = Parameter(default=1)
|
||||
_ref_time = 0
|
||||
_ref_dif = 0
|
||||
_dir = 0
|
||||
_rawdir = 0
|
||||
_last_cycle = 0
|
||||
_last_progress = 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):
|
||||
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()
|
||||
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):
|
||||
if not self.use_pressure:
|
||||
self._value = value
|
||||
|
||||
def update_from_pressure(self, value):
|
||||
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 write_tolerance(self, tolerance):
|
||||
if hasattr(self.flow_pressure, 'tolerance'):
|
||||
self.flow_pressure.tolerance = tolerance / self.lnm_per_mbar
|
||||
if hasattr(self.flow, 'tolerance'):
|
||||
self.flow.tolerance = tolerance
|
||||
|
||||
def read_use_pressure(self):
|
||||
if self.pressure:
|
||||
if self.flow_sensor:
|
||||
if self.flow_pressure:
|
||||
if self.flow:
|
||||
return self.use_pressure
|
||||
return True
|
||||
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):
|
||||
self.log.debug('change target')
|
||||
self.start_machine(self.change_target, target=value, try_close=True)
|
||||
|
||||
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
|
||||
self.start_machine(self.controlling, in_tol_time=0,
|
||||
ref_time=0, ref_dif=0, prev_dif=0)
|
||||
|
||||
@status_code(BUSY)
|
||||
def change_target(self, sm):
|
||||
self.target = sm.target
|
||||
sm.last_progress = sm.now
|
||||
sm.ref_time = 0
|
||||
sm.ref_dif = 0
|
||||
sm.last_pulse_time = 0
|
||||
sm.no_progress_pulse = (0.1, -0.05)
|
||||
self.log.debug('target %s value %s', self.target, self._value)
|
||||
tol = self._tolerance(self.target)
|
||||
if abs(self.target - self._value) < tol:
|
||||
self.log.debug('go to at_target')
|
||||
return self.at_target
|
||||
self.log.debug('go to 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)
|
||||
def controlling(self, sm):
|
||||
tol = self._tolerance(self.target)
|
||||
dif = self._dif_medians()
|
||||
if sm.init:
|
||||
self.log.debug('restart controlling')
|
||||
direction = math.copysign(1, dif[1])
|
||||
if direction != self._dir:
|
||||
self.log.debug('new dir %g dif=%g', direction, dif[1])
|
||||
self._dir = direction
|
||||
self._last_dirchange = sm.now
|
||||
sm.ref_dif = abs(dif[1])
|
||||
sm.ref_time = sm.now
|
||||
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
|
||||
def unblock_from_open(self, state):
|
||||
self.motor_state = self.command(fm=int)
|
||||
if self.motor_state == 'opened':
|
||||
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
|
||||
if self.motor_state == 'closing':
|
||||
return Retry
|
||||
# still approaching
|
||||
difmax = max(difdir)
|
||||
if difmax < expected_dif:
|
||||
sm.ref_time = sm.now
|
||||
sm.ref_dif = difmax
|
||||
# self.log.info('new ref %g', sm.ref_dif)
|
||||
sm.last_progress = sm.now
|
||||
return Retry # progressing: no pulse needed
|
||||
if self.motor_state == 'closed':
|
||||
if self.value > max(1, self.target):
|
||||
return Retry
|
||||
state.flow_before = self.value
|
||||
state.wiggle = 1
|
||||
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
|
||||
|
||||
@status_code(BUSY)
|
||||
def unblock_close(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:
|
||||
if state.wiggle < self.prop * 2:
|
||||
return self.final_status(IDLE, '')
|
||||
state.wiggle = -state.wiggle / 2
|
||||
self.command(mp=state.wiggle)
|
||||
state.start_wiggle = state.now
|
||||
return self.unblock_open
|
||||
if self.motor_state == 'closing':
|
||||
return Retry
|
||||
if self.motor_state == 'idle':
|
||||
self.command(mp=state.wiggle)
|
||||
return Retry
|
||||
if self.motor_state == 'closed':
|
||||
if state.now < state.start_wiggle + 20:
|
||||
return Retry
|
||||
return self.final_status(ERROR, 'can not close')
|
||||
return self.final_status(WARN, 'unblock interrupted')
|
||||
|
||||
def _tolerance(self):
|
||||
return min(self.tolerance * min(1, self.value / 2), self.tolerance2)
|
||||
|
||||
@status_code(IDLE)
|
||||
def at_target(self, sm):
|
||||
tol = self._tolerance(self.target)
|
||||
dif = self._dif_medians()
|
||||
if np.all(dif > tol) or np.all(dif < -tol):
|
||||
self.log.debug('unstable %r %g', dif, tol)
|
||||
def at_target(self, state):
|
||||
dif = self.target - self.value
|
||||
if abs(dif) > self._tolerance():
|
||||
state.in_tol_time = 0
|
||||
return self.unstable
|
||||
return Retry
|
||||
|
||||
@status_code(IDLE, 'unstable')
|
||||
def unstable(self, sm):
|
||||
sm.no_progress_pulse = None
|
||||
return self.controlling(sm)
|
||||
def unstable(self, state):
|
||||
return self.controlling(state)
|
||||
|
||||
def read_motor_state(self):
|
||||
return self.command(fm=int)
|
||||
|
||||
@Command
|
||||
def close(self):
|
||||
"""close valve fully"""
|
||||
self.command(mp=-60)
|
||||
@status_code(BUSY)
|
||||
def controlling(self, state):
|
||||
delta = state.delta(0)
|
||||
dif = self.target - self.value
|
||||
difdif = dif - state.prev_dif
|
||||
state.prev_dif = dif
|
||||
self.motor_state = self.command(fm=int)
|
||||
self.start_machine(self.closing, after_close=None, fast_poll=0.1)
|
||||
|
||||
@status_code(BUSY)
|
||||
def closing(self, sm):
|
||||
if sm.init:
|
||||
sm.start_time = sm.now
|
||||
self._speed_sum -= sm.delta()
|
||||
self.read_motor_state()
|
||||
if self.motor_state == M.closing:
|
||||
if self.motor_state == 'closed':
|
||||
if dif < 0 or difdif < 0:
|
||||
return Retry
|
||||
if self.motor_state == M.closed:
|
||||
if sm.after_close:
|
||||
return sm.after_close
|
||||
return self.final_status(IDLE, 'closed')
|
||||
if sm.now < sm.start_time + 1:
|
||||
return self.unblock_from_open
|
||||
elif self.motor_state == 'opened': # trigger also when flow too high?
|
||||
if dif > 0 or difdif > 0:
|
||||
return Retry
|
||||
return self.final_status(IDLE, 'fixed')
|
||||
|
||||
@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)
|
||||
return self.unblock_from_open
|
||||
|
||||
@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:
|
||||
tolerance = self._tolerance()
|
||||
if abs(dif) < tolerance:
|
||||
state.in_tol_time += delta
|
||||
if state.in_tol_time > self.settle:
|
||||
return self.at_target
|
||||
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)
|
||||
expected_dif = state.ref_dif * math.exp((state.now - state.ref_time) / self.deriv)
|
||||
if abs(dif) < expected_dif:
|
||||
if abs(dif) < expected_dif / 1.25:
|
||||
state.ref_time = state.now
|
||||
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
|
||||
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)
|
||||
self.command(mp=state.step)
|
||||
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
|
||||
|
||||
@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, '')
|
||||
|
@ -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()
|
||||
|
||||
|
@ -56,7 +56,7 @@ class Drums(Writable):
|
||||
self._pos = 0
|
||||
for i, action in enumerate(self.pattern[self._pos:]):
|
||||
upper = action.upper()
|
||||
relais = self.actions.get(upper)
|
||||
relais = self.actions.get(action.upper())
|
||||
if relais:
|
||||
relais.write_target(upper == action) # True when capital letter
|
||||
else:
|
||||
|
@ -17,104 +17,41 @@
|
||||
# Markus Zolliker <markus.zolliker@psi.ch>
|
||||
# *****************************************************************************
|
||||
|
||||
"""interlocks for furnace"""
|
||||
"""interlocks for furnance"""
|
||||
|
||||
import time
|
||||
from frappy.core import Module, Writable, Attached, Parameter, FloatRange, Readable,\
|
||||
BoolType, ERROR, IDLE
|
||||
from frappy.errors import ImpossibleError
|
||||
from frappy.mixins import HasControlledBy
|
||||
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):
|
||||
value = Parameter('interlock o.k.', BoolType(), default=True)
|
||||
target = Parameter('set to true to confirm', BoolType(), readonly=False)
|
||||
input = Attached(Readable, 'the input module', mandatory=False) # TODO: remove
|
||||
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')
|
||||
class Interlocks(Module):
|
||||
input = Attached(Readable, 'the input module')
|
||||
vacuum = Attached (Readable, 'the vacuum pressure')
|
||||
wall_T = Attached (Readable, 'the wall temperature')
|
||||
control = Attached(Module, 'the control module')
|
||||
htr = Attached(Module, 'the heater module', mandatory=False)
|
||||
relais = Attached(Writable, 'the interlock relais', mandatory=False)
|
||||
flowswitch = Attached(Readable, 'the flow switch', mandatory=False)
|
||||
relais = Attached(Writable, 'the interlock relais')
|
||||
wall_limit = Parameter('maximum wall temperature', FloatRange(0, unit='degC'),
|
||||
default = 50, readonly = False)
|
||||
vacuum_limit = Parameter('maximum vacuum pressure', FloatRange(0, unit='mbar'),
|
||||
default = 0.1, readonly = False)
|
||||
htr_T_limit = Parameter('maximum htr temperature', FloatRange(0, unit='degC'),
|
||||
default = 530, readonly = False)
|
||||
main_T_limit = Parameter('maximum main temperature', FloatRange(0, unit='degC'),
|
||||
default = 530, readonly = False)
|
||||
extra_T_limit = Parameter('maximum extra temperature', FloatRange(0, unit='degC'),
|
||||
default = 530, readonly = False)
|
||||
|
||||
_off_reason = None # reason triggering interlock
|
||||
_conditions = '' # summary of reasons why locked now
|
||||
|
||||
def initModule(self):
|
||||
super().initModule()
|
||||
self._sensor_checks = [
|
||||
(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)
|
||||
def doPoll(self):
|
||||
super().doPoll()
|
||||
if self.input.status[0] >= ERROR:
|
||||
self.control.status = self.input.status
|
||||
elif self.vacuum.value > self.vacuum_limit:
|
||||
self.control.status = ERROR, 'bad vacuum'
|
||||
elif self.wall_T.value > self.wall_limit:
|
||||
self.control.status = ERROR, 'wall overheat'
|
||||
else:
|
||||
return
|
||||
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):
|
||||
input_module = Attached(Readable, 'the input module')
|
||||
class PI(PImixin, Writable):
|
||||
input = Attached(Readable, 'the input module')
|
||||
relais = Attached(Writable, 'the interlock relais', mandatory=False)
|
||||
|
||||
def read_value(self):
|
||||
@ -124,23 +61,3 @@ class PI(HasConvergence, PImixin):
|
||||
super().write_target(value)
|
||||
if self.relais:
|
||||
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')
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -18,117 +18,55 @@
|
||||
# Jael Celia Lorenzana <jael-celia.lorenzana@psi.ch>
|
||||
# *****************************************************************************
|
||||
|
||||
"""support for iono pi max from Sfera Labs
|
||||
|
||||
supports also the smaller model iono pi
|
||||
"""
|
||||
|
||||
from frappy.core import Readable, Writable, Parameter, BoolType, StringType,\
|
||||
FloatRange, Property, TupleOf, ERROR, IDLE
|
||||
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:
|
||||
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):
|
||||
with open(self._devpath / addr) as f:
|
||||
with open(f'/sys/class/ionopimax/{self.devclass}/{addr}') as f:
|
||||
result = f.read()
|
||||
if scale:
|
||||
return float(result) / scale
|
||||
return result.strip()
|
||||
return result
|
||||
|
||||
def write(self, addr, value, scale=None):
|
||||
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)
|
||||
|
||||
def read_status(self):
|
||||
return self._status
|
||||
|
||||
|
||||
class DigitalInput(Base, Readable):
|
||||
value = Parameter('input state', BoolType())
|
||||
true_level = Property('level representing True', EnumType(low=0, high=1), default=1)
|
||||
|
||||
def initModule(self):
|
||||
super().initModule()
|
||||
if self._devclass == 'digital_io':
|
||||
self.write(f'{self.addr}_mode', 'inp')
|
||||
devclass = 'digital_in'
|
||||
|
||||
def read_value(self):
|
||||
return self.read(self.addr, 1) == self.true_level
|
||||
return self.read(self.addr, 1)
|
||||
|
||||
|
||||
class DigitalOutput(DigitalInput, Writable):
|
||||
target = Parameter('output state', BoolType(), readonly=False)
|
||||
|
||||
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
|
||||
devclass = 'digital_out'
|
||||
|
||||
def write_target(self, value):
|
||||
self.write(self.addr, value == self.true_level, 1)
|
||||
self.write(self.addr, value, 1)
|
||||
|
||||
|
||||
class AnalogInput(Base, Readable):
|
||||
value = Parameter('analog value', FloatRange())
|
||||
rawrange = Property('raw range (electronic)', TupleOf(FloatRange(),FloatRange()))
|
||||
valuerange = Property('value range (physical)', TupleOf(FloatRange(),FloatRange()))
|
||||
extendedrange = Property('range outside calibrated range, but not sensor fault',
|
||||
NoneOr(TupleOf(FloatRange(), FloatRange())), default=None)
|
||||
|
||||
def initModule(self):
|
||||
super().initModule()
|
||||
dt = self.parameters['value'].datatype
|
||||
dt.min, dt.max = self.valuerange
|
||||
rawrange = Property('raw range(electronic)', TupleOf(FloatRange(),FloatRange()))
|
||||
valuerange = Property('value range(physical)', TupleOf(FloatRange(),FloatRange()))
|
||||
devclass = 'analog_in'
|
||||
|
||||
def read_value(self):
|
||||
x0, x1 = self.rawrange
|
||||
y0, y1 = self.valuerange
|
||||
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)
|
||||
|
||||
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):
|
||||
scale = 1e5
|
||||
@ -144,24 +82,30 @@ class LogVoltageInput(VoltageInput):
|
||||
x0, x1 = self.rawrange
|
||||
y0, y1 = self.valuerange
|
||||
self.x = self.read(self.addr, self.scale)
|
||||
self.read_status()
|
||||
if self.status[0] == ERROR:
|
||||
raise OutOfRangeError('sensor fault')
|
||||
a = (x1-x0)/log(y1/y0, 10)
|
||||
a = (x1-x0)/log(y1/y0,10)
|
||||
return 10**((self.x-x1)/a)*y1
|
||||
|
||||
|
||||
class CurrentInput(AnalogInput):
|
||||
scale = 1e6
|
||||
rawrange = (0.004, 0.02)
|
||||
rawrange = (0.004,0.02)
|
||||
|
||||
def initModule(self):
|
||||
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):
|
||||
target = Parameter('outputvalue', FloatRange())
|
||||
devclass = 'analog_out'
|
||||
|
||||
def write_target(self, value):
|
||||
x0, x1 = self.rawrange
|
||||
@ -179,18 +123,3 @@ class VoltageOutput(AnalogOutput):
|
||||
self.write(f'{self.addr}_mode', 'V')
|
||||
self.write(f'{self.addr}', '0')
|
||||
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)
|
||||
|
||||
|
||||
|
@ -1,65 +1,16 @@
|
||||
# *****************************************************************************
|
||||
#
|
||||
# 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:
|
||||
# Damaris Tartarotti Maimone
|
||||
# Markus Zolliker <markus.zolliker@psi.ch>
|
||||
#
|
||||
# *****************************************************************************
|
||||
"""support for ultrasound plot clients"""
|
||||
"""
|
||||
Created on Tue Feb 4 11:07:56 2020
|
||||
|
||||
@author: tartarotti_d-adm
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
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):
|
||||
return np.array([[x1,x2,x2,x1,x1],[y1,y1,y2,y2,y1]])
|
||||
|
||||
NAN = float('nan')
|
||||
|
||||
def rects(intervals, y12):
|
||||
result = [rect(*intervals[0], *y12)]
|
||||
@ -68,19 +19,13 @@ def rects(intervals, y12):
|
||||
result.append(rect(*x12, *y12))
|
||||
return np.concatenate(result, axis=1)
|
||||
|
||||
|
||||
class Plot:
|
||||
def __init__(self, maxy, maxx=None):
|
||||
def __init__(self, maxy):
|
||||
self.lines = {}
|
||||
self.yaxis = ((-2 * maxy, maxy), (-maxy, 2 * maxy))
|
||||
self.maxx = maxx
|
||||
self.first = True
|
||||
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):
|
||||
"""
|
||||
plot or update a line
|
||||
@ -123,9 +68,8 @@ class Plot:
|
||||
if self.first:
|
||||
plt.ion()
|
||||
self.fig, axleft = plt.subplots(figsize=(15,7))
|
||||
self.pause = Pause(self.fig)
|
||||
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[0].axhline(y=0, color='#cccccc') # show x-axis line
|
||||
self.ax[1].axhline(y=0, color='#cccccc')
|
||||
@ -153,6 +97,5 @@ class Plot:
|
||||
self.first = False
|
||||
|
||||
plt.draw()
|
||||
# TODO: do not know why this is needed:
|
||||
self.fig.canvas.draw()
|
||||
self.fig.canvas.flush_events()
|
||||
|
@ -39,13 +39,6 @@ from frappy.extparams import StructParam
|
||||
from frappy_psi.calcurve import CalCurve
|
||||
|
||||
|
||||
def string_to_num(string):
|
||||
try:
|
||||
return int(string)
|
||||
except ValueError:
|
||||
return float(string)
|
||||
|
||||
|
||||
class IO(StringIO):
|
||||
"""IO classes of most LakeShore models will inherit from this"""
|
||||
end_of_line = '\n'
|
||||
@ -104,9 +97,8 @@ class HasLscIO(HasIO):
|
||||
if not args:
|
||||
self.communicate(f'{msghead};*OPC?')
|
||||
return None
|
||||
# when an argument is given as integer, it might be that this argument might be a float
|
||||
converters = [string_to_num if isinstance(a, int) else type(a) for a in args]
|
||||
values = [a if isinstance(a, str) else f'{a:g}'
|
||||
converters = [type(a) for a in args]
|
||||
values = [a if issubclass(c, str) else f'{a:g}'
|
||||
for c, a in zip(converters, args)]
|
||||
if ' ' in msghead:
|
||||
query = msghead.replace(' ', '? ', 1)
|
||||
@ -847,7 +839,7 @@ class MainOutput(Output):
|
||||
}
|
||||
|
||||
_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())
|
||||
|
||||
def read_status(self):
|
||||
@ -855,9 +847,6 @@ class MainOutput(Output):
|
||||
return self.HTRST_MAP[st]
|
||||
|
||||
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)
|
||||
user_current = max(0.1, min(self.imax, 2 * math.sqrt(self._desired_max_power /
|
||||
self.heater_ranges[irng] / self.resistance)))
|
||||
@ -1191,7 +1180,7 @@ class MainOutput336(MainOutput):
|
||||
imax = 2
|
||||
vmax = 50
|
||||
# 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())
|
||||
|
||||
|
||||
|
@ -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, ''
|
||||
|
@ -68,8 +68,8 @@ class LakeShoreIO(HasIO):
|
||||
args.insert(0, cmd)
|
||||
else:
|
||||
args[0] = cmd + args[0]
|
||||
tail = ','.join(args)
|
||||
head = cmd.replace(' ', '?')
|
||||
head = ','.join(args)
|
||||
tail = cmd.replace(' ', '?')
|
||||
reply = self.io.communicate(f'{head};{tail}')
|
||||
return parse_result(reply)
|
||||
|
||||
@ -166,7 +166,6 @@ class Switcher(LakeShoreIO, ChannelSwitcher):
|
||||
|
||||
def set_active_channel(self, chan):
|
||||
self.set_param('SCAN ', chan.channel, 0)
|
||||
self.value = chan.channel
|
||||
chan._last_range_change = time.monotonic()
|
||||
self.set_delays(chan)
|
||||
|
||||
@ -279,12 +278,7 @@ class ResChannel(LakeShoreIO, Channel):
|
||||
vexc = 0 if excoff or iscur else exc
|
||||
if (rng, iexc, vexc) != (self.range, self.iexc, self.vexc):
|
||||
self._last_range_change = time.monotonic()
|
||||
try:
|
||||
self.range, self.iexc, self.vexc = rng, iexc, vexc
|
||||
except Exception:
|
||||
# avoid raising errors on disabled channel
|
||||
if self.enabled:
|
||||
raise
|
||||
|
||||
@CommonWriteHandler(rdgrng_params)
|
||||
def write_rdgrng(self, change):
|
||||
|
@ -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
|
@ -375,7 +375,6 @@ class HeaterOutput(HasInput, Writable):
|
||||
|
||||
class HeaterUpdate(HeaterOutput):
|
||||
kind = 'HTR,TEMP'
|
||||
target = 0 # switch off loop on startup
|
||||
|
||||
def update_target(self, module, value):
|
||||
self.change(f'DEV::TEMP:LOOP:ENAB', False, off_on)
|
||||
|
@ -21,12 +21,12 @@
|
||||
|
||||
"""modules to access parameters"""
|
||||
|
||||
import re
|
||||
from frappy.core import Drivable, EnumType, IDLE, Attached, StringType, Property, \
|
||||
Parameter, BoolType, FloatRange, Readable, ERROR, nopoll
|
||||
from frappy.errors import ConfigError
|
||||
from frappy_psi.convergence import HasConvergence
|
||||
from frappy_psi.mixins import HasRamp
|
||||
from frappy.lib import merge_status
|
||||
|
||||
|
||||
class Par(Readable):
|
||||
@ -255,41 +255,3 @@ class SwitchDriv(HasConvergence, Drivable):
|
||||
self.log.info('target=%g (%s)', target, this.name)
|
||||
this.write_target(target1)
|
||||
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, ''
|
||||
|
@ -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'
|
@ -166,7 +166,7 @@ class Motor(HasOffset, HasStates, PersistentMixin, HasIO, Drivable):
|
||||
def write_target(self, value):
|
||||
self.read_alive_time()
|
||||
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])
|
||||
self.saveParameters()
|
||||
self.start_machine(self.starting, target=value)
|
||||
|
@ -48,8 +48,8 @@ example cfg:
|
||||
Mod('T_softloop',
|
||||
'frappy_psi.picontrol.PI',
|
||||
'softloop controlled Temperature mixing chamber',
|
||||
input_module = 'ts',
|
||||
output_module = 'htr_mix',
|
||||
input = 'ts',
|
||||
output = 'htr_mix',
|
||||
control_active = 1,
|
||||
output_max = 80000,
|
||||
p = 2E6,
|
||||
@ -60,10 +60,10 @@ example cfg:
|
||||
|
||||
import time
|
||||
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.datatypes import LimitsType, EnumType, BoolType, FloatRange
|
||||
from frappy.newmixins import HasOutputModule
|
||||
from frappy.mixins import HasOutputModule
|
||||
from frappy_psi.convergence import HasConvergence
|
||||
|
||||
|
||||
@ -71,27 +71,32 @@ class PImixin(HasOutputModule, Writable):
|
||||
p = Parameter('proportional term', FloatRange(0), readonly=False)
|
||||
i = Parameter('integral term', FloatRange(0), readonly=False)
|
||||
# output_module is inherited
|
||||
output_range = Property('legacy output range', LimitsType(FloatRange()), default=(0,0))
|
||||
output_min = Parameter('min output', FloatRange(), default=0, readonly=False)
|
||||
output_max = Parameter('max output', FloatRange(), default=0, readonly=False)
|
||||
output_range = Parameter('min output',
|
||||
LimitsType(FloatRange()), default=(0, 0), readonly=False)
|
||||
output_func = Parameter('output function',
|
||||
EnumType(lin=0, square=1), readonly=False, default=0)
|
||||
value = Parameter(unit='K')
|
||||
_lastdiff = None
|
||||
_lasttime = 0
|
||||
_get_range = None # a function get output range from output_module
|
||||
_overflow = 0
|
||||
|
||||
def initModule(self):
|
||||
super().initModule()
|
||||
if self.output_range != (0, 0): # legacy !
|
||||
self.output_min, self.output_max = self.output_range
|
||||
_clamp_limits = None
|
||||
|
||||
def doPoll(self):
|
||||
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:
|
||||
return
|
||||
out = self.output_module
|
||||
self.status = IDLE, 'controlling'
|
||||
now = time.time()
|
||||
deltat = clamp(0, now-self._lasttime, 10)
|
||||
@ -101,51 +106,17 @@ class PImixin(HasOutputModule, Writable):
|
||||
self._lastdiff = diff
|
||||
deltadiff = diff - self._lastdiff
|
||||
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
|
||||
if hasattr(out, 'max_target'):
|
||||
if hasattr(self, 'min_target'):
|
||||
self._get_range = lambda o=out: (o.read_min_target(), o.read_max_target())
|
||||
else:
|
||||
self._get_range = lambda o=out: (0, o.read_max_target())
|
||||
elif hasattr(out, 'limit'): # mercury.HeaterOutput
|
||||
self._get_range = lambda o=out: (0, o.read_limit())
|
||||
else:
|
||||
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}')
|
||||
output = out.target
|
||||
if self.output_func == 'square':
|
||||
output = math.sqrt(max(0, output))
|
||||
output += self.p * deltadiff + self.i * deltat * diff
|
||||
if self.output_func == 'square':
|
||||
output = output ** 2
|
||||
output = self._clamp_limits(output)
|
||||
out.update_target(self.name, clamp(output, *self.output_range))
|
||||
|
||||
def write_control_active(self, value):
|
||||
super().write_control_active(value)
|
||||
if not value:
|
||||
self.output_module.write_target(0)
|
||||
|
||||
@ -154,6 +125,61 @@ class PImixin(HasOutputModule, Writable):
|
||||
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!
|
||||
|
||||
class PI(HasConvergence, PImixin):
|
||||
@ -164,18 +190,3 @@ class PI(HasConvergence, PImixin):
|
||||
|
||||
def read_status(self):
|
||||
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)
|
||||
|
||||
|
@ -213,10 +213,7 @@ class SeaClient(ProxyClient, Module):
|
||||
if not self._connected.is_set():
|
||||
if self._connect_thread is None:
|
||||
# let doPoll do the reconnect
|
||||
if self.pollInfo: # is None in playground
|
||||
self.pollInfo.trigger(True)
|
||||
else:
|
||||
self.log.info('sea_main.doPoll() will connect')
|
||||
raise ConnectionClosed('disconnected - reconnect is tried later')
|
||||
return self.raw_request(command, quiet)
|
||||
|
||||
@ -560,12 +557,10 @@ class SeaModule(Module):
|
||||
else:
|
||||
cls.paramFilter(result, paramdesc)
|
||||
cfgdict.pop('rel_paths', None)
|
||||
params = []
|
||||
for key, plist in result.items():
|
||||
params.extend(plist)
|
||||
params = sum(result.values(), [])
|
||||
if is_running: # take this at end
|
||||
params.append(is_running)
|
||||
# this needs some care in the configuration: the main value must be the first!
|
||||
|
||||
main_value = params[0]
|
||||
if issubclass(cls, Readable):
|
||||
if 'key' in main_value:
|
||||
@ -667,11 +662,6 @@ class SeaModule(Module):
|
||||
path2param.setdefault(hdbpath, []).append((name, key))
|
||||
attributes[key] = pobj
|
||||
|
||||
if key != 'status' and key is not None:
|
||||
rfunc = getattr(cls, f'read_{key}', None)
|
||||
# do not override existing read method
|
||||
if rfunc is None:
|
||||
|
||||
def rfunc(self, cmd=f'hval {base}/{path}'):
|
||||
reply = self.io.query(cmd, True)
|
||||
try:
|
||||
@ -682,9 +672,10 @@ class SeaModule(Module):
|
||||
return reply
|
||||
|
||||
rfunc.poll = False
|
||||
if key != 'status' and key is not None:
|
||||
attributes['read_' + key] = rfunc
|
||||
|
||||
if not readonly and key:
|
||||
if not readonly:
|
||||
|
||||
def wfunc(self, value, datatype=datatype, command=paramdesc['cmd']):
|
||||
value = datatype.export_value(value)
|
||||
@ -720,15 +711,9 @@ class SeaModule(Module):
|
||||
@classmethod
|
||||
def paramFilter(cls, result, paramdesc):
|
||||
sub = paramdesc['path'].split('/', 1)
|
||||
sublist = result.get(sub[0] or '.')
|
||||
if sublist is None:
|
||||
sublist = result.get('.')
|
||||
sublist = result.get(sub[0])
|
||||
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)
|
||||
return True
|
||||
|
||||
@ -860,20 +845,9 @@ class SeaDrivable(SeaReadable, Drivable):
|
||||
|
||||
class LscDrivable(SeaDrivable):
|
||||
def __new__(cls, name, logger, cfgdict, srv):
|
||||
sensor_path = pop_cfg(cfgdict, 'sensor_path', 'tm')
|
||||
set_path = pop_cfg(cfgdict, 'set_path', 'set')
|
||||
cfgdict['rel_paths'] = ['.', sensor_path, set_path , 'dblctrl']
|
||||
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
|
||||
cfgdict['rel_paths'] = [pop_cfg(cfgdict, 'sensor_path', 'tm'), '.',
|
||||
pop_cfg(cfgdict, 'set_path', 'set'), 'dblctrl']
|
||||
return super().__new__(cls, name, logger, cfgdict, srv)
|
||||
|
||||
@classmethod
|
||||
def paramFilter(cls, result, paramdesc):
|
||||
@ -884,17 +858,3 @@ class LscDrivable(SeaDrivable):
|
||||
result['.'].append(paramdesc)
|
||||
return True
|
||||
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
Reference in New Issue
Block a user