demo lakeshore with simulation

- example for lakeshore tutorial
- lakeshore simulator (merge with frappy_psils370sim)
- rename stringio-server to sim-server

Change-Id: I33a9c75ea268349573f8a8387910921e19f242eb
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30516
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
This commit is contained in:
zolliker 2023-01-30 14:39:33 +01:00
parent c8f30582a5
commit 05cf4a791a
2 changed files with 135 additions and 50 deletions

View File

@ -34,15 +34,17 @@ Use cases, mainly for test purposes:
""" """
import sys import sys
import argparse
from os import path from os import path
import asyncore import asyncore
import socket import socket
import ast import time
# Add import path for inplace usage # Add import path for inplace usage
sys.path.insert(0, path.abspath(path.join(path.dirname(__file__), '..'))) sys.path.insert(0, path.abspath(path.join(path.dirname(__file__), '..')))
from frappy.lib import get_class, formatException from frappy.lib import get_class, formatException, mkthread
class LineHandler(asyncore.dispatcher_with_send): class LineHandler(asyncore.dispatcher_with_send):
@ -51,6 +53,9 @@ class LineHandler(asyncore.dispatcher_with_send):
asyncore.dispatcher_with_send.__init__(self, sock) asyncore.dispatcher_with_send.__init__(self, sock)
self.crlf = 0 self.crlf = 0
def handle_line(self, line):
raise NotImplementedError
def handle_read(self): def handle_read(self):
data = self.recv(8192) data = self.recv(8192)
if data: if data:
@ -74,20 +79,22 @@ class LineHandler(asyncore.dispatcher_with_send):
class LineServer(asyncore.dispatcher): class LineServer(asyncore.dispatcher):
def __init__(self, host, port, lineHandlerClass): def __init__(self, port, line_handler_cls, handler_args):
asyncore.dispatcher.__init__(self) asyncore.dispatcher.__init__(self)
self.create_socket(socket.AF_INET, socket.SOCK_STREAM) self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.set_reuse_addr() self.set_reuse_addr()
self.bind((host, port)) self.bind(('0.0.0.0', port))
self.listen(5) self.listen(5)
self.lineHandlerClass = lineHandlerClass print('accept connections at port', port)
self.line_handler_cls = line_handler_cls
self.handler_args = handler_args
def handle_accept(self): def handle_accept(self):
pair = self.accept() pair = self.accept()
if pair is not None: if pair is not None:
sock, addr = pair sock, addr = pair
print("Incoming connection from %s" % repr(addr)) print("Incoming connection from %s" % repr(addr))
self.lineHandlerClass(sock) self.line_handler_cls(sock, self.handler_args)
def loop(self): def loop(self):
asyncore.loop() asyncore.loop()
@ -108,47 +115,73 @@ class Server(LineServer):
class Handler(LineHandler): class Handler(LineHandler):
def __init__(self, sock, handler_args):
super().__init__(sock)
self.module = handler_args['module']
self.verbose = handler_args['verbose']
def handle_line(self, line): def handle_line(self, line):
try: try:
reply = module.do_communicate(line.strip()) reply = self.module.communicate(line.strip())
if verbose: if self.verbose:
print('%-40s | %s' % (line, reply)) print('%-40s | %s' % (line, reply))
except Exception: except Exception:
print(formatException(verbose=True)) print(formatException(verbose=True))
return
self.send_line(reply) self.send_line(reply)
class Logger: class Logger:
def debug(self, *args): def debug(self, *args):
print(*args)
info = exception = debug
opts = {'description': 'simulator'}
args = []
for arg in sys.argv[1:]:
k, sep, v = arg.partition('=')
if not k:
args.append(v)
try:
v = ast.literal_eval(v)
except Exception:
pass pass
opts[k] = v
verbose = opts.pop('verbose', False)
opts['cls'] = 'frappy_psi.ls370sim.Ls370Sim'
opts['port'] = 4567
if len(args) > 2:
raise ValueError('do not know about: %s' % ' '.join(args[2:]))
if len(args) == 2:
opts['port'] = int(args[1])
if len(args) > 0:
opts['cls'] = args[0]
args.append(opts)
cls = opts.pop('cls') def log(self, level, *args):
port = opts.pop('port') pass
srv = Server('localhost', int(port), Handler)
module = get_class(cls)(cls, Logger(), opts, srv) def info(self, *args):
module.earlyInit() print(*args)
srv.loop()
exception = error = warn = info
def parse_argv(argv):
parser = argparse.ArgumentParser(description="Simulate HW with a serial interface")
parser.add_argument("-v", "--verbose",
help="output full communication",
action='store_true', default=False)
parser.add_argument("cls",
type=str,
help="simulator class.\n",)
parser.add_argument('-p',
'--port',
action='store',
help='server port or uri',
default=2089)
return parser.parse_args(argv)
def poller(pollfunc):
while True:
time.sleep(1.0)
pollfunc()
def main(argv=None):
if argv is None:
argv = sys.argv
args = parse_argv(argv[1:])
opts = {'description': 'simulator'}
handler_args = {'verbose': args.verbose}
srv = Server(int(args.port), Handler, handler_args)
module = get_class(args.cls)(args.cls, Logger(), opts, srv)
handler_args['module'] = module
module.earlyInit()
mkthread(poller, module.doPoll)
srv.loop()
if __name__ == '__main__':
sys.exit(main(sys.argv))

View File

@ -18,7 +18,11 @@
# Module authors: # Module authors:
# Markus Zolliker <markus.zolliker@psi.ch> # Markus Zolliker <markus.zolliker@psi.ch>
# ***************************************************************************** # *****************************************************************************
"""a very simple simulator for a LakeShore Model 370""" """a very simple simulator for a LakeShore
Model 370: parameters are stored, but no cryo simulation
Model 336: heat exchanger on channel A with loop 1, sample sensor on channel B
"""
from frappy.modules import Communicator from frappy.modules import Communicator
@ -36,40 +40,88 @@ class Ls370Sim(Communicator):
('SCAN?', '3,1'), ('SCAN?', '3,1'),
('*OPC?', '1'), ('*OPC?', '1'),
] ]
pollinterval = 1
CHANNELS = list(range(1, 17))
data = ()
def earlyInit(self): def earlyInit(self):
super().earlyInit() super().earlyInit()
self._data = dict(self.OTHER_COMMANDS) self.data = dict(self.OTHER_COMMANDS)
for fmt, v in self.CHANNEL_COMMANDS: for fmt, v in self.CHANNEL_COMMANDS:
for chan in range(1,17): for chan in self.CHANNELS:
self._data[fmt % chan] = v self.data[fmt % chan] = v
def communicate(self, command): def doPoll(self):
self.comLog('> %s' % command) super().doPoll()
# simulation part, time independent self.simulate()
for channel in range(1,17):
def simulate(self):
# not really a simulation. just for testing RDGST
for channel in self.CHANNELS:
_, _, _, _, excoff = self._data['RDGRNG?%d' % channel].split(',') _, _, _, _, excoff = self._data['RDGRNG?%d' % channel].split(',')
if excoff == '1': if excoff == '1':
self._data['RDGST?%d' % channel] = '6' self._data['RDGST?%d' % channel] = '6'
else: else:
self._data['RDGST?%d' % channel] = '0' self._data['RDGST?%d' % channel] = '0'
def communicate(self, command):
self.comLog('> %s' % command)
chunks = command.split(';') chunks = command.split(';')
reply = [] reply = []
for chunk in chunks: for chunk in chunks:
if '?' in chunk: if '?' in chunk:
reply.append(self._data[chunk]) chunk = chunk.replace('? ', '?')
reply.append(self.data[chunk])
else: else:
for nqarg in (1,0): for nqarg in (1, 0):
if nqarg == 0: if nqarg == 0:
qcmd, arg = chunk.split(' ', 1) qcmd, arg = chunk.split(' ', 1)
qcmd += '?' qcmd += '?'
else: else:
qcmd, arg = chunk.split(',', nqarg) qcmd, arg = chunk.split(',', nqarg)
qcmd = qcmd.replace(' ', '?', 1) qcmd = qcmd.replace(' ', '?', 1)
if qcmd in self._data: if qcmd in self.data:
self._data[qcmd] = arg self.data[qcmd] = arg
break break
reply = ';'.join(reply) reply = ';'.join(reply)
self.comLog('< %s' % reply) self.comLog('< %s' % reply)
return reply return reply
class Ls336Sim(Ls370Sim):
CHANNEL_COMMANDS = [
('KRDG?%s', '295.0'),
('RDGST?%s', '0'),
]
OTHER_COMMANDS = [
('*IDN?', 'LSCI,MODEL370,370184,05302003'),
('RANGE?1', '0'),
('SETP?1', '0'),
('CLIMIT?1', ''),
('CSET?1', ''),
('CMODE?1', ''),
('*OPC?', '1'),
]
CHANNELS = 'ABCD'
vti = 295
sample = 295
def simulate(self):
# simple temperature control on channel A:
range_ = int(self.data['RANGE?1'])
setp = float(self.data['SETP?1'])
if range_:
# heater on: approach setpoint with 20 sec time constant
self.vti = max(self.vti - 0.1, self.vti + (setp - self.vti) * 0.05)
else:
# heater off 0.1/sec cool down
self.vti = max(1.5, self.vti - 0.1)
# sample approaching setpoint with 10 sec time constant, but with some
# systematic heat loss towards 150 K
self.sample = self.sample + (self.vti + (150 - self.vti) * 0.01 - self.sample) * 0.1
self.data['KRDG?A'] = str(round(self.vti, 3))
self.data['KRDG?B'] = str(round(self.sample, 3))