parent
de54399861
commit
9666a9b0aa
@ -0,0 +1,126 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Run a multi-threaded single-client SCPI Server implemented in Python.
|
||||
Using a single-client server is sensible for many SCPI servers
|
||||
where state would need to be shared between the multiple clients
|
||||
and thus access to it would need to be made thread-safe.
|
||||
In most cases, this doesn't make sense. Everything is
|
||||
simply much easier when allowing only one client at a time.
|
||||
The design choice for a multi-threaded server was made in
|
||||
order to be able to actively disconnect additional clients
|
||||
while another one is already connected.
|
||||
Contains code from https://gist.github.com/pklaus/db709c8c1279348e0638
|
||||
"""
|
||||
|
||||
# Make it work on Python 2 and Python 3:
|
||||
try:
|
||||
import socketserver
|
||||
except ImportError:
|
||||
import SocketServer as socketserver
|
||||
import socket, threading
|
||||
import argparse, random, logging
|
||||
from logging import DEBUG, INFO, WARNING, ERROR, CRITICAL
|
||||
import peaktech
|
||||
|
||||
logger = logging.getLogger('scpi-server')
|
||||
|
||||
class CmdTCPServer(socketserver.ThreadingTCPServer):
|
||||
"""
|
||||
A TCP server made to respond to line based commands.
|
||||
"""
|
||||
|
||||
#: newline character(s) to be added to string responses
|
||||
newline = '\n'
|
||||
#: Ctrl-C will cleanly kill all spawned threads
|
||||
daemon_threads = True
|
||||
#: much faster rebinding possible
|
||||
allow_reuse_address = True
|
||||
address_family = socket.AF_INET6
|
||||
|
||||
class CmdRequestHandler(socketserver.StreamRequestHandler):
|
||||
def handle(self):
|
||||
if not self.server.lock.acquire(blocking=False):
|
||||
self.log(DEBUG, 'An additional cliend tried to connect from {client}. Denying...')
|
||||
return
|
||||
self.log(DEBUG, 'Connected to {client}.')
|
||||
try:
|
||||
while True:
|
||||
self.single_cmd()
|
||||
except Disconnected:
|
||||
pass
|
||||
self.log(DEBUG, 'The client {client} closed the connection')
|
||||
finally:
|
||||
self.server.lock.release()
|
||||
def read_cmd(self):
|
||||
return self.rfile.readline().decode('utf-8').strip()
|
||||
def log(self, level, msg, *args, **kwargs):
|
||||
if type(level) == str:
|
||||
level = getattr(logging, level.upper())
|
||||
msg = msg.format(client=self.client_address[0])
|
||||
logger.log(level, msg, *args, **kwargs)
|
||||
def send_reply(self, reply):
|
||||
if type(reply) == str:
|
||||
if self.server.newline: reply += self.server.newline
|
||||
reply = reply.encode('utf-8')
|
||||
self.wfile.write(reply)
|
||||
def single_cmd(self):
|
||||
cmd = self.read_cmd()
|
||||
if not cmd: raise Disconnected
|
||||
self.log(DEBUG, 'Received a cmd: {}'.format(cmd))
|
||||
try:
|
||||
reply = self.server.process(cmd)
|
||||
if reply is not None:
|
||||
self.send_reply(reply)
|
||||
except:
|
||||
self.send_reply('ERR')
|
||||
|
||||
def __init__(self, server_address, name=None):
|
||||
socketserver.TCPServer.__init__(self, server_address, self.CmdRequestHandler)
|
||||
self.lock = threading.Lock()
|
||||
self.name = name if name else "{}:{}".format(*server_address)
|
||||
|
||||
def process(self, cmd):
|
||||
"""
|
||||
Implement this method to handle command processing.
|
||||
For each command, this method will be called.
|
||||
Return a string or bytes as appropriate.
|
||||
If your the message is only a command (not a query), return None.
|
||||
"""
|
||||
raise NotImplemented
|
||||
|
||||
class SCPIServerExample(CmdTCPServer):
|
||||
|
||||
def process(self, cmd):
|
||||
"""
|
||||
This is the method to process each SCPI command
|
||||
received from the client.
|
||||
"""
|
||||
if cmd.startswith('*IDN?'):
|
||||
return self.name
|
||||
if cmd.startswith('READ?'):
|
||||
return str(peaktech.read())
|
||||
#return '{:+.6E}'.format(peaktech.read())
|
||||
#return '{:+.6E}'.format(random.random())
|
||||
else:
|
||||
return 'unknown cmd'
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__.split('\n')[1])
|
||||
parser.add_argument('--port', type=int, default=5025, help='TCP port to listen to.')
|
||||
parser.add_argument('--host', default='::', help='The host / IP address to listen at.')
|
||||
parser.add_argument('--loglevel', default='INFO', help='log level',
|
||||
choices=['CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG'])
|
||||
args = parser.parse_args()
|
||||
logging.basicConfig(format='%(message)s', level=args.loglevel.upper())
|
||||
scpi_server = SCPIServerExample((args.host, args.port))
|
||||
try:
|
||||
scpi_server.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
logger.info('Ctrl-C pressed. Shutting down...')
|
||||
scpi_server.server_close()
|
||||
|
||||
class Disconnected(Exception): pass
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Reference in New Issue