trinamic driver and bytesio module
Change-Id: Id634e7514fecab6fd6bc3edf81e25ad41c2bb12f
This commit is contained in:
90
secop/bytesio.py
Normal file
90
secop/bytesio.py
Normal file
@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env 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:
|
||||
# Markus Zolliker <markus.zolliker@psi.ch>
|
||||
# *****************************************************************************
|
||||
"""byte oriented stream communication"""
|
||||
|
||||
import time
|
||||
import re
|
||||
|
||||
from secop.lib.asynconn import AsynConn, ConnectionClosed
|
||||
from secop.modules import Property, Command
|
||||
from secop.stringio import BaseIO
|
||||
from secop.datatypes import BLOBType, IntRange, ArrayOf, TupleOf, StringType
|
||||
from secop.errors import CommunicationFailedError, CommunicationSilentError
|
||||
|
||||
|
||||
HEX_CODE = re.compile(r'[0-9a-fA-F][0-9a-fA-F]$')
|
||||
|
||||
|
||||
class BytesIO(BaseIO):
|
||||
identification = Property(
|
||||
"""identification
|
||||
|
||||
a list of tuples with commands and expected responses, to be sent on connect.
|
||||
commands and responses are whitespace separated items
|
||||
an item is either:
|
||||
- a two digit hexadecimal number (byte value)
|
||||
- a character
|
||||
- ?? indicating ignored bytes in responses
|
||||
""", datatype=ArrayOf(TupleOf(StringType(), StringType())), default=[], export=False)
|
||||
|
||||
def connectStart(self):
|
||||
if not self.is_connected:
|
||||
uri = self.uri
|
||||
self._conn = AsynConn(uri, b'')
|
||||
self.is_connected = True
|
||||
for command, match in self.identification:
|
||||
cmdbytes = bytes([int(c, 16) if HEX_CODE.match(c) else ord(c) for c in command.split()])
|
||||
replypat = [int(c, 16) if HEX_CODE.match(c.replace('??', '-1')) else ord(c) for c in command.split()]
|
||||
reply = self.communicate(cmdbytes, len(replypat))
|
||||
if any(b != c and c != -1 for b, c in zip(reply, replypat)):
|
||||
self.closeConnection()
|
||||
raise CommunicationFailedError('bad response: %r does not match %r' % (command, match))
|
||||
|
||||
@Command((BLOBType(), IntRange(0)), result=BLOBType())
|
||||
def communicate(self, command, nbytes):
|
||||
"""send a command and receive nbytes as reply"""
|
||||
if not self.is_connected:
|
||||
self.read_is_connected() # try to reconnect
|
||||
if not self._conn:
|
||||
raise CommunicationSilentError('can not connect to %r' % self.uri)
|
||||
try:
|
||||
with self._lock:
|
||||
# read garbage and wait before send
|
||||
try:
|
||||
if self.wait_before:
|
||||
time.sleep(self.wait_before)
|
||||
garbage = self._conn.flush_recv()
|
||||
if garbage:
|
||||
self.log.debug('garbage: %r', garbage)
|
||||
self._conn.send(command)
|
||||
self.log.debug('send: %r', command)
|
||||
reply = self._conn.readbytes(nbytes, self.timeout)
|
||||
except ConnectionClosed:
|
||||
self.closeConnection()
|
||||
raise CommunicationFailedError('disconnected')
|
||||
self.log.debug('recv: %r', reply)
|
||||
return reply
|
||||
except Exception as e:
|
||||
if str(e) == self._last_error:
|
||||
raise CommunicationSilentError(str(e))
|
||||
self._last_error = str(e)
|
||||
self.log.error(self._last_error)
|
||||
raise
|
@ -79,8 +79,8 @@ class AsynConn:
|
||||
self.disconnect()
|
||||
|
||||
@classmethod
|
||||
def register_scheme(cls, scheme):
|
||||
cls.SCHEME_MAP[scheme] = cls
|
||||
def __init_subclass__(cls):
|
||||
cls.SCHEME_MAP[cls.scheme] = cls
|
||||
|
||||
def disconnect(self):
|
||||
raise NotImplementedError
|
||||
@ -154,6 +154,8 @@ class AsynConn:
|
||||
|
||||
|
||||
class AsynTcp(AsynConn):
|
||||
scheme = 'tcp'
|
||||
|
||||
def __init__(self, uri, *args, **kwargs):
|
||||
super().__init__(uri, *args, **kwargs)
|
||||
self.uri = uri
|
||||
@ -202,9 +204,6 @@ class AsynTcp(AsynConn):
|
||||
raise ConnectionClosed() # marks end of connection
|
||||
|
||||
|
||||
AsynTcp.register_scheme('tcp')
|
||||
|
||||
|
||||
class AsynSerial(AsynConn):
|
||||
"""a serial connection using pyserial
|
||||
|
||||
@ -221,6 +220,7 @@ class AsynSerial(AsynConn):
|
||||
|
||||
and others (see documentation of serial.Serial)
|
||||
"""
|
||||
scheme = 'serial'
|
||||
PARITY_NAMES = {name[0]: name for name in ['NONE', 'ODD', 'EVEN', 'MASK', 'SPACE']}
|
||||
|
||||
def __init__(self, uri, *args, **kwargs):
|
||||
@ -282,6 +282,3 @@ class AsynSerial(AsynConn):
|
||||
return self.connection.read(n)
|
||||
data = self.connection.read(1)
|
||||
return data + self.connection.read(self.connection.in_waiting)
|
||||
|
||||
|
||||
AsynSerial.register_scheme('serial')
|
||||
|
@ -20,7 +20,8 @@
|
||||
# *****************************************************************************
|
||||
"""Line oriented stream communication
|
||||
|
||||
implements TCP/IP and is be used as a base for SerialIO
|
||||
StringIO: string oriented IO. May be used for TCP/IP as well for serial IO or
|
||||
other future extensions of AsynConn
|
||||
"""
|
||||
|
||||
import re
|
||||
@ -37,66 +38,23 @@ from secop.modules import Attached, Command, \
|
||||
from secop.poller import REGULAR
|
||||
|
||||
|
||||
class StringIO(Communicator):
|
||||
"""line oriented communicator
|
||||
|
||||
self healing is assured by polling the parameter 'is_connected'
|
||||
"""
|
||||
class BaseIO(Communicator):
|
||||
"""base of StringIO and BytesIO"""
|
||||
uri = Property('hostname:portnumber', datatype=StringType())
|
||||
end_of_line = Property('end_of_line character', datatype=ValueType(),
|
||||
default='\n', settable=True)
|
||||
encoding = Property('used encoding', datatype=StringType(),
|
||||
default='ascii', settable=True)
|
||||
identification = Property('''
|
||||
identification
|
||||
|
||||
a list of tuples with commands and expected responses as regexp,
|
||||
to be sent on connect''',
|
||||
datatype=ArrayOf(TupleOf(StringType(), StringType())), default=[], export=False)
|
||||
|
||||
timeout = Parameter('timeout', datatype=FloatRange(0), default=2)
|
||||
wait_before = Parameter('wait time before sending', datatype=FloatRange(), default=0)
|
||||
is_connected = Parameter('connection state', datatype=BoolType(), readonly=False, poll=REGULAR)
|
||||
pollinterval = Parameter('reconnect interval', datatype=FloatRange(0), readonly=False, default=10)
|
||||
|
||||
_reconnectCallbacks = None
|
||||
_conn = None
|
||||
_last_error = None
|
||||
|
||||
def earlyInit(self):
|
||||
self._conn = None
|
||||
self._lock = threading.RLock()
|
||||
eol = self.end_of_line
|
||||
if isinstance(eol, (tuple, list)):
|
||||
if len(eol) not in (1, 2):
|
||||
raise ValueError('invalid end_of_line: %s' % eol)
|
||||
else:
|
||||
eol = [eol]
|
||||
# eol for read and write might be distinct
|
||||
self._eol_read = self._convert_eol(eol[0])
|
||||
if not self._eol_read:
|
||||
raise ValueError('end_of_line for read must not be empty')
|
||||
self._eol_write = self._convert_eol(eol[-1])
|
||||
self._last_error = None
|
||||
|
||||
def _convert_eol(self, value):
|
||||
if isinstance(value, str):
|
||||
return value.encode(self.encoding)
|
||||
if isinstance(value, int):
|
||||
return bytes([value])
|
||||
if isinstance(value, bytes):
|
||||
return value
|
||||
raise ValueError('invalid end_of_line: %s' % repr(value))
|
||||
|
||||
def connectStart(self):
|
||||
if not self.is_connected:
|
||||
uri = self.uri
|
||||
self._conn = AsynConn(uri, self._eol_read)
|
||||
self.is_connected = True
|
||||
for command, regexp in self.identification:
|
||||
reply = self.communicate(command)
|
||||
if not re.match(regexp, reply):
|
||||
self.closeConnection()
|
||||
raise CommunicationFailedError('bad response: %s does not match %s' %
|
||||
(reply, regexp))
|
||||
raise NotImplementedError
|
||||
|
||||
def closeConnection(self):
|
||||
"""close connection
|
||||
@ -158,6 +116,62 @@ class StringIO(Communicator):
|
||||
if removeme:
|
||||
self._reconnectCallbacks.pop(key)
|
||||
|
||||
def communicate(self, command):
|
||||
return NotImplementedError
|
||||
|
||||
|
||||
class StringIO(BaseIO):
|
||||
"""line oriented communicator
|
||||
|
||||
self healing is assured by polling the parameter 'is_connected'
|
||||
"""
|
||||
end_of_line = Property('end_of_line character', datatype=ValueType(),
|
||||
default='\n', settable=True)
|
||||
encoding = Property('used encoding', datatype=StringType(),
|
||||
default='ascii', settable=True)
|
||||
identification = Property('''
|
||||
identification
|
||||
|
||||
a list of tuples with commands and expected responses as regexp,
|
||||
to be sent on connect''',
|
||||
datatype=ArrayOf(TupleOf(StringType(), StringType())), default=[], export=False)
|
||||
|
||||
def _convert_eol(self, value):
|
||||
if isinstance(value, str):
|
||||
return value.encode(self.encoding)
|
||||
if isinstance(value, int):
|
||||
return bytes([value])
|
||||
if isinstance(value, bytes):
|
||||
return value
|
||||
raise ValueError('invalid end_of_line: %s' % repr(value))
|
||||
|
||||
def earlyInit(self):
|
||||
super().earlyInit()
|
||||
eol = self.end_of_line
|
||||
if isinstance(eol, (tuple, list)):
|
||||
if len(eol) not in (1, 2):
|
||||
raise ValueError('invalid end_of_line: %s' % eol)
|
||||
else:
|
||||
eol = [eol]
|
||||
# eol for read and write might be distinct
|
||||
self._eol_read = self._convert_eol(eol[0])
|
||||
if not self._eol_read:
|
||||
raise ValueError('end_of_line for read must not be empty')
|
||||
self._eol_write = self._convert_eol(eol[-1])
|
||||
|
||||
def connectStart(self):
|
||||
if not self.is_connected:
|
||||
uri = self.uri
|
||||
self._conn = AsynConn(uri, self._eol_read)
|
||||
self.is_connected = True
|
||||
for command, regexp in self.identification:
|
||||
reply = self.communicate(command)
|
||||
if not re.match(regexp, reply):
|
||||
self.closeConnection()
|
||||
raise CommunicationFailedError('bad response: %s does not match %s' %
|
||||
(reply, regexp))
|
||||
|
||||
@Command(StringType(), result=StringType())
|
||||
def communicate(self, command):
|
||||
"""send a command and receive a reply
|
||||
|
||||
@ -165,6 +179,7 @@ class StringIO(Communicator):
|
||||
for commands without reply, the command must be joined with a query command,
|
||||
wait_before is respected for end_of_lines within a command.
|
||||
"""
|
||||
command = command.encode(self.encoding)
|
||||
if not self.is_connected:
|
||||
self.read_is_connected() # try to reconnect
|
||||
if not self._conn:
|
||||
@ -173,9 +188,9 @@ class StringIO(Communicator):
|
||||
with self._lock:
|
||||
# read garbage and wait before send
|
||||
if self.wait_before and self._eol_write:
|
||||
cmds = command.encode(self.encoding).split(self._eol_write)
|
||||
cmds = command.split(self._eol_write)
|
||||
else:
|
||||
cmds = [command.encode(self.encoding)]
|
||||
cmds = [command]
|
||||
garbage = None
|
||||
try:
|
||||
for cmd in cmds:
|
||||
@ -186,6 +201,7 @@ class StringIO(Communicator):
|
||||
if garbage:
|
||||
self.log.debug('garbage: %r', garbage)
|
||||
self._conn.send(cmd + self._eol_write)
|
||||
self.log.debug('send: %s', cmd + self._eol_write)
|
||||
reply = self._conn.readline(self.timeout)
|
||||
except ConnectionClosed:
|
||||
self.closeConnection()
|
||||
|
Reference in New Issue
Block a user