add sim-server again based on socketserver
- fix ls370test config file + fix issues with frappy_psi.ls370res + add frappy_psi.ls370sim Change-Id: Ie61e3ea01c4b9c7c1286426504e50acf9413a8ba Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/34957 Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch> Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
This commit is contained in:
226
bin/sim-server
226
bin/sim-server
@ -22,22 +22,35 @@
|
||||
|
||||
Usage:
|
||||
|
||||
bin/stringio-server <communciator> <server port>
|
||||
bin/sim-server <communicator class> -p <server port> [-o <option1>=<value> <option2>=<value>]
|
||||
|
||||
open a server on <server port> to communicate with the string based <communicator> over TCP/IP.
|
||||
|
||||
Use cases, mainly for test purposes:
|
||||
- as a T, if the hardware allows only one connection, and more than one is needed
|
||||
- relay to a communicator not using TCP/IP, if Frappy should run on an other host
|
||||
|
||||
- relay to a hardware simulation written as a communicator
|
||||
|
||||
> bin/sim-server frappy_psi.ls370sim.Ls370Sim
|
||||
|
||||
- relay to a communicator not using TCP/IP, if Frappy should run on an other host
|
||||
|
||||
> bin/sim-server frappy.io.StringIO -o uri=serial:///dev/tty...
|
||||
|
||||
- as a T, if the hardware allows only one connection, and more than one is needed:
|
||||
|
||||
> bin/sim-server frappy.io.StringIO -o uri=tcp://<host>:<port>
|
||||
|
||||
typically using communicator class frappy.io.StringIO
|
||||
"""
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
import asyncore
|
||||
import socket
|
||||
import time
|
||||
import os
|
||||
from ast import literal_eval
|
||||
from socketserver import BaseRequestHandler, ThreadingTCPServer
|
||||
|
||||
# Add import path for inplace usage
|
||||
sys.path.insert(0, str(Path(__file__).absolute().parents[1]))
|
||||
@ -45,92 +58,6 @@ sys.path.insert(0, str(Path(__file__).absolute().parents[1]))
|
||||
from frappy.lib import get_class, formatException, mkthread
|
||||
|
||||
|
||||
class LineHandler(asyncore.dispatcher_with_send):
|
||||
|
||||
def __init__(self, sock):
|
||||
self.buffer = b""
|
||||
asyncore.dispatcher_with_send.__init__(self, sock)
|
||||
self.crlf = 0
|
||||
|
||||
def handle_line(self, line):
|
||||
raise NotImplementedError
|
||||
|
||||
def handle_read(self):
|
||||
data = self.recv(8192)
|
||||
if data:
|
||||
parts = data.split(b"\n")
|
||||
if len(parts) == 1:
|
||||
self.buffer += data
|
||||
else:
|
||||
self.handle_line((self.buffer + parts[0]).decode('latin_1'))
|
||||
for part in parts[1:-1]:
|
||||
if part[-1] == b"\r":
|
||||
self.crlf = True
|
||||
part = part[:-1]
|
||||
else:
|
||||
self.crlf = False
|
||||
self.handle_line(part.decode('latin_1'))
|
||||
self.buffer = parts[-1]
|
||||
|
||||
def send_line(self, line):
|
||||
self.send((line + ("\r\n" if self.crlf else "\n")).encode('latin_1'))
|
||||
|
||||
|
||||
class LineServer(asyncore.dispatcher):
|
||||
|
||||
def __init__(self, port, line_handler_cls, handler_args):
|
||||
asyncore.dispatcher.__init__(self)
|
||||
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.set_reuse_addr()
|
||||
self.bind(('0.0.0.0', port))
|
||||
self.listen(5)
|
||||
print('accept connections at port', port)
|
||||
self.line_handler_cls = line_handler_cls
|
||||
self.handler_args = handler_args
|
||||
|
||||
def handle_accept(self):
|
||||
pair = self.accept()
|
||||
if pair is not None:
|
||||
sock, addr = pair
|
||||
print("Incoming connection from %s" % repr(addr))
|
||||
self.line_handler_cls(sock, self.handler_args)
|
||||
|
||||
def loop(self):
|
||||
asyncore.loop()
|
||||
|
||||
|
||||
class Server(LineServer):
|
||||
|
||||
class Dispatcher:
|
||||
def announce_update(self, *_):
|
||||
pass
|
||||
|
||||
def announce_update_error(self, *_):
|
||||
pass
|
||||
|
||||
def __init__(self, *args, **kwds):
|
||||
super().__init__(*args, **kwds)
|
||||
self.secnode = None
|
||||
self.dispatcher = self.Dispatcher()
|
||||
|
||||
|
||||
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):
|
||||
try:
|
||||
reply = self.module.communicate(line.strip())
|
||||
if self.verbose:
|
||||
print('%-40s | %s' % (line, reply))
|
||||
except Exception:
|
||||
print(formatException(verbose=True))
|
||||
return
|
||||
self.send_line(reply)
|
||||
|
||||
|
||||
class Logger:
|
||||
def debug(self, *args):
|
||||
pass
|
||||
@ -144,43 +71,126 @@ class Logger:
|
||||
exception = error = warn = info
|
||||
|
||||
|
||||
class TcpRequestHandler(BaseRequestHandler):
|
||||
def setup(self):
|
||||
print(f'connection opened from {self.client_address}')
|
||||
self.running = True
|
||||
self.request.settimeout(1)
|
||||
self.data = b''
|
||||
|
||||
def finish(self):
|
||||
"""called when handle() terminates, i.e. the socket closed"""
|
||||
# close socket
|
||||
try:
|
||||
self.request.shutdown(socket.SHUT_RDWR)
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
print(f'connection closed from {self.client_address}')
|
||||
self.request.close()
|
||||
|
||||
def poller(self):
|
||||
while True:
|
||||
time.sleep(1.0)
|
||||
self.module.doPoll()
|
||||
|
||||
def handle(self):
|
||||
"""handle a new connection"""
|
||||
# do a copy of the options, as they are consumed
|
||||
self.module = self.server.modulecls(
|
||||
'mod', Logger(), dict(self.server.options), self.server)
|
||||
self.module.earlyInit()
|
||||
|
||||
mkthread(self.poller)
|
||||
while self.running:
|
||||
try:
|
||||
newdata = self.request.recv(1024)
|
||||
if not newdata:
|
||||
return
|
||||
except socket.timeout:
|
||||
# no new data during read, continue
|
||||
continue
|
||||
self.data += newdata
|
||||
while self.running:
|
||||
message, sep, self.data = self.data.partition(b'\n')
|
||||
if not sep:
|
||||
break
|
||||
cmd = message.decode('latin-1')
|
||||
try:
|
||||
reply = self.module.communicate(cmd.strip())
|
||||
if self.server.verbose:
|
||||
print('%-40s | %s' % (cmd, reply))
|
||||
except Exception:
|
||||
print(formatException(verbose=True))
|
||||
return
|
||||
outdata = reply.encode('latin-1') + b'\n'
|
||||
try:
|
||||
self.request.sendall(outdata)
|
||||
except Exception as e:
|
||||
print(repr(e))
|
||||
self.running = False
|
||||
|
||||
|
||||
class Server(ThreadingTCPServer):
|
||||
allow_reuse_address = os.name != 'nt' # False on Windows systems
|
||||
|
||||
class Dispatcher:
|
||||
def announce_update(self, *_):
|
||||
pass
|
||||
|
||||
def announce_update_error(self, *_):
|
||||
pass
|
||||
|
||||
def __init__(self, port, modulecls, options, verbose=False):
|
||||
super().__init__(('', port), TcpRequestHandler,
|
||||
bind_and_activate=True)
|
||||
self.secnode = None
|
||||
self.dispatcher = self.Dispatcher()
|
||||
self.verbose = verbose
|
||||
self.modulecls = get_class(modulecls)
|
||||
self.options = options
|
||||
print(f'started sim-server listening on port {port}')
|
||||
|
||||
|
||||
def parse_argv(argv):
|
||||
parser = argparse.ArgumentParser(description="Simulate HW with a serial interface")
|
||||
parser = argparse.ArgumentParser(description="Relay to a communicator (simulated HW or other)")
|
||||
parser.add_argument("-v", "--verbose",
|
||||
help="output full communication",
|
||||
action='store_true', default=False)
|
||||
parser.add_argument("cls",
|
||||
type=str,
|
||||
help="simulator class.\n",)
|
||||
help="communicator class.\n",)
|
||||
parser.add_argument('-p',
|
||||
'--port',
|
||||
action='store',
|
||||
help='server port or uri',
|
||||
default=2089)
|
||||
parser.add_argument('-o',
|
||||
'--options',
|
||||
action='store',
|
||||
nargs='*',
|
||||
help='options in the form key=value',
|
||||
default=None)
|
||||
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()
|
||||
options = {'description': ''}
|
||||
for item in args.options or ():
|
||||
key, eq, value = item.partition('=')
|
||||
if not eq:
|
||||
raise ValueError(f"missing '=' in {item}")
|
||||
try:
|
||||
value = literal_eval(value)
|
||||
except Exception:
|
||||
pass
|
||||
options[key] = value
|
||||
srv = Server(int(args.port), args.cls, options, args.verbose)
|
||||
srv.serve_forever()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
Reference in New Issue
Block a user