stringio now works with serial connections

also allow SECoP client connections via serial

Change-Id: I10c02532a9f8e9b8f16599b98c439742da6d8f5c
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/22525
Tested-by: JenkinsCodeReview <bjoern_pedersen@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
This commit is contained in:
2020-02-26 11:36:28 +01:00
parent d021a116f1
commit 4bb11e249d
2 changed files with 141 additions and 111 deletions

View File

@@ -24,9 +24,9 @@ implements TCP/IP and is be used as a base for SerialIO
"""
import time
import socket
import threading
import re
from secop.lib.asynconn import AsynConn, ConnectionClosed
from secop.modules import Module, Communicator, Parameter, Command, Property, Attached
from secop.datatypes import StringType, FloatRange, ArrayOf, BoolType, TupleOf
from secop.errors import CommunicationFailedError, CommunicationSilentError
@@ -34,14 +34,9 @@ from secop.poller import REGULAR
from secop.metaclass import Done
class StringIO(Communicator):
"""line oriented communicator
implementation for TCP/IP streams.
other types have to override the following methods:
createConnection, readWithTimeout, writeBytes, closeConnection
self healing is assured by polling the parameter 'is_connected'
"""
properties = {
@@ -76,82 +71,35 @@ class StringIO(Communicator):
_reconnectCallbacks = None
def earlyInit(self):
self._stream = None
self._conn = None
self._lock = threading.RLock()
self._end_of_line = self.end_of_line.encode(self.encoding)
self._connect_error = None
self._last_error = None
def createConnection(self):
"""create connection
in case of success, self.is_connected MUST be set to True by implementors
"""
uri = self.uri
if uri.startswith('tcp://'):
uri = uri[6:]
try:
host, port = uri.split(':')
self._stream = socket.create_connection((host, int(port)), 10)
self.is_connected = True
except (ConnectionRefusedError, socket.gaierror) as e:
raise CommunicationFailedError(str(e))
except Exception as e:
# this is really bad, do not try again
self._connect_error = e
raise
def readWithTimeout(self, timeout):
"""read with timeout
Read bytes available now, or wait at most the specified timeout until some bytes
are available. Throw an error, if disconnected.
If no bytes are available, return b''
to be overwritten for other stream types
"""
if timeout is None or timeout < 0:
raise ValueError('illegal timeout %r' % timeout)
def connectStart(self):
if not self.is_connected:
raise CommunicationSilentError(self._last_error or 'not connected')
self._stream.settimeout(timeout)
try:
reply = self._stream.recv(4096)
if reply:
return reply
except (BlockingIOError, socket.timeout):
return b''
except Exception as e:
self.closeConnection()
raise CommunicationFailedError('disconnected because of %s' % e)
# other end disconnected
self.closeConnection()
raise CommunicationFailedError('other end disconnected')
def writeBytes(self, data):
"""write bytes
to be overwritten for other stream types
"""
self._stream.sendall(data)
uri = self.uri
try:
self._conn = AsynConn(uri, self._end_of_line)
self.is_connected = True
except Exception as e:
# this is really bad, do not try again
self._connect_error = e
raise
for command, regexp in self.identification:
reply = self.do_communicate(command)
if not re.match(regexp, reply):
self.closeConnection()
raise CommunicationFailedError('bad response: %s does not match %s' %
(reply, regexp))
def closeConnection(self):
"""close connection
self.is_connected MUST be set to False by implementors
"""
if not self._stream:
return
self.log.debug('disconnect %s' % self.uri)
try:
self._stream.shutdown(socket.SHUT_RDWR)
except socket.error:
pass
try:
self._stream.close()
except socket.error:
pass
self._stream = None
self._conn.disconnect()
self._conn = None
self.is_connected = False
def read_is_connected(self):
@@ -185,16 +133,6 @@ class StringIO(Communicator):
return False
return self.read_is_connected()
def connectStart(self):
if not self.is_connected:
self.createConnection()
for command, regexp in self.identification:
reply = self.do_communicate(command)
if not re.match(regexp, reply):
self.closeConnection()
raise CommunicationFailedError('bad response: %s does not match %s' %
(reply, regexp))
def registerReconnectCallback(self, name, func):
"""register reconnect callback
@@ -236,25 +174,15 @@ class StringIO(Communicator):
if self.wait_before:
time.sleep(self.wait_before)
if garbage is None: # read garbage only once
garbage = b''
data = self.readWithTimeout(0)
while data:
garbage += data
data = self.readWithTimeout(0)
garbage = self._conn.flush_recv()
if garbage:
self.log.debug('garbage: %s', garbage.decode(self.encoding))
self.writeBytes((cmd + self.end_of_line).encode(self.encoding))
timeout = self.timeout
buffer = b''
data = True
while data:
data = self.readWithTimeout(timeout)
buffer += data
if self._end_of_line in buffer:
break
else:
raise CommunicationFailedError('timeout')
reply = buffer.split(self._end_of_line, 1)[0].decode(self.encoding)
self._conn.send((cmd + self.end_of_line).encode(self.encoding))
try:
reply = self._conn.readline(self.timeout)
except ConnectionClosed:
raise CommunicationFailedError('disconnected')
reply = reply.decode(self.encoding)
self.log.debug('recv: %s', reply)
return reply
except Exception as e: