frappy/secop/lib/asynconn.py
Markus Zolliker 9825b9c135 new generic secop client
new client intended as base class for all clients

- based on Ennos secop client in nicos ([WIP] provide secop client)
- self healing connection singletons
- extension for other than TCP is foreseen (by extending new uri schemes)
- extensible name mangling
- seperate rx and tx threads supporting events
- internal cache
- extensible error handling
- callback for unhandled messages
- callback for descriptive data change
- callback for node stat change (connected, disconnected)
- a short close down and reconnect without change in descriptive data
  does not disturb the client side

works with secop-gui (change follows), planned to be used for Frappy
internal secop proxy and as a replacement for secop.client.baseclient.Client
in the nicos secop device.

-> secop/client/baseclient.py to be removed after planned changes

moved secop/client/__init__.py to secop/client/console.py because secop.client
would be the natural place to put the new base class.

Change-Id: I1a7b1f1ded2221a8f9fcdd52f9cc7414e8fbe035
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/22218
Tested-by: JenkinsCodeReview <bjoern_pedersen@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
2020-01-30 08:51:47 +01:00

141 lines
4.1 KiB
Python

# -*- coding: utf-8 -*-
# *****************************************************************************
#
# 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:
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
# Markus Zolliker <markus.zolliker@psi.ch>
#
# *****************************************************************************
"""asynchonous connections
generic class for byte oriented communication
includes implementation for TCP connections
"""
import socket
import time
from secop.lib import parseHostPort, tcpSocket, closeSocket
class ConnectionClosed(ConnectionError):
pass
class AsynConn:
timeout = 1 # inter byte timeout
SCHEME_MAP = {}
connection = None # is not None, if connected
defaultport = None
def __new__(cls, uri):
scheme = uri.split('://')[0]
iocls = cls.SCHEME_MAP.get(scheme, None)
if not iocls:
# try tcp, if scheme not given
try:
host_port = parseHostPort(uri, cls.defaultport)
except (ValueError, TypeError, AssertionError):
raise ValueError('invalid uri: %s' % uri)
iocls = cls.SCHEME_MAP['tcp']
uri = 'tcp://%s:%d' % host_port
return object.__new__(iocls)
def __init__(self, *args):
self._rxbuffer = b''
def __del__(self):
self.disconnect()
@classmethod
def register_scheme(cls, scheme):
cls.SCHEME_MAP[scheme] = cls
def disconnect(self):
raise NotImplementedError
def send(self, data):
"""send data (bytes!)
tries to send all data"""
raise NotImplementedError
def recv(self):
"""return bytes received within timeout
in contrast to socket.recv:
- returns b'' on timeout
- raises ConnectionClosed if the other end has disconnected
"""
raise NotImplementedError
def readline(self, timeout=None):
"""read one line
return either a complete line or None in case of timeout
the timeout argument may increase, but not decrease the default timeout
"""
if timeout:
end = time.time() + timeout
while b'\n' not in self._rxbuffer:
data = self.recv()
if not data:
if timeout:
if time.time() < end:
continue
raise TimeoutError('timeout in readline')
return None
self._rxbuffer += data
line, self._rxbuffer = self._rxbuffer.split(b'\n', 1)
return line
def writeline(self, line):
self.send(line + b'\n')
class AsynTcp(AsynConn):
def __init__(self, uri):
super().__init__()
self.uri = uri
if uri.startswith('tcp://'):
# should be the case always
uri = uri[6:]
self.connection = tcpSocket(uri, self.defaultport, self.timeout)
def disconnect(self):
if self.connection:
closeSocket(self.connection)
self.connection = None
def send(self, data):
"""send data (bytes!)"""
self.connection.sendall(data)
def recv(self):
"""return bytes received within 1 sec"""
try:
data = self.connection.recv(8192)
if data:
return data
except socket.timeout:
# timeout while waiting
return b''
raise ConnectionClosed() # marks end of connection
AsynTcp.register_scheme('tcp')