add StringIO.writeline, improve StringIO.multicomm
- StringIO.writeline sends a command and does not expect a reply - StringIO.multicomm and BytesIO.multicomm is improved in order to insert individual delays in between lines and individual noreply flags + fix a bug in tutorial_t_control + improve readability of frappy.lib.classdoc.indent_description Change-Id: I9dea113e19147684ec41aca5267a79816bbf202c Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/32267 Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de> Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de> Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
This commit is contained in:
parent
fd0e762d18
commit
158477792f
@ -405,7 +405,7 @@ Appendix 2: Extract from the LakeShore Manual
|
||||
Reply <range> *term*
|
||||
**Operation Complete Query**
|
||||
----------------------------------------------
|
||||
Command *OPC?
|
||||
Command \*OPC?
|
||||
Reply 1
|
||||
Description in Frappy, we append this command to request in order
|
||||
to generate a reply
|
||||
|
108
frappy/io.py
108
frappy/io.py
@ -25,17 +25,17 @@ other future extensions of AsynConn
|
||||
"""
|
||||
|
||||
import re
|
||||
import time
|
||||
import threading
|
||||
import time
|
||||
|
||||
from frappy.lib.asynconn import AsynConn, ConnectionClosed
|
||||
from frappy.datatypes import ArrayOf, BLOBType, BoolType, FloatRange, IntRange, \
|
||||
StringType, TupleOf, ValueType
|
||||
from frappy.errors import CommunicationFailedError, ConfigError, ProgrammingError, \
|
||||
SilentCommunicationFailedError as SilentError
|
||||
from frappy.modules import Attached, Command, \
|
||||
Communicator, Module, Parameter, Property
|
||||
from frappy.datatypes import ArrayOf, BLOBType, BoolType, FloatRange, \
|
||||
IntRange, StringType, StructOf, TupleOf, ValueType
|
||||
from frappy.errors import CommunicationFailedError, ConfigError, \
|
||||
ProgrammingError, SilentCommunicationFailedError as SilentError
|
||||
from frappy.lib import generalConfig
|
||||
from frappy.lib.asynconn import AsynConn, ConnectionClosed
|
||||
from frappy.modules import Attached, Command, Communicator, Module, \
|
||||
Parameter, Property
|
||||
|
||||
generalConfig.set_default('legacy_hasiodev', False)
|
||||
|
||||
@ -76,8 +76,11 @@ class HasIO(Module):
|
||||
def communicate(self, *args):
|
||||
return self.io.communicate(*args)
|
||||
|
||||
def multicomm(self, *args):
|
||||
return self.io.multicomm(*args)
|
||||
def writeline(self, *args):
|
||||
return self.io.writeline(*args)
|
||||
|
||||
def multicomm(self, *args, **kwds):
|
||||
return self.io.multicomm(*args, **kwds)
|
||||
|
||||
|
||||
class HasIodev(HasIO):
|
||||
@ -287,7 +290,7 @@ class StringIO(IOBase):
|
||||
f' does not match {regexp!r}')
|
||||
|
||||
@Command(StringType(), result=StringType())
|
||||
def communicate(self, command):
|
||||
def communicate(self, command, noreply=False):
|
||||
"""send a command and receive a reply
|
||||
|
||||
using end_of_line, encoding and self._lock
|
||||
@ -314,6 +317,8 @@ class StringIO(IOBase):
|
||||
self.comLog('garbage: %r', garbage)
|
||||
self._conn.send(cmd + self._eol_write)
|
||||
self.comLog('> %s', cmd.decode(self.encoding))
|
||||
if noreply:
|
||||
return None
|
||||
reply = self._conn.readline(self.timeout)
|
||||
except ConnectionClosed:
|
||||
self.closeConnection()
|
||||
@ -329,13 +334,69 @@ class StringIO(IOBase):
|
||||
self.log.error(self._last_error)
|
||||
raise SilentError(repr(e)) from e
|
||||
|
||||
@Command(ArrayOf(StringType()), result=ArrayOf(StringType()))
|
||||
def multicomm(self, commands):
|
||||
"""communicate multiple request/replies in one row"""
|
||||
@Command(StringType())
|
||||
def writeline(self, command):
|
||||
"""send a command without needing a reply
|
||||
|
||||
For keeping a request-reply scheme it is recommended to overwrite
|
||||
this method to append a query on the same line, for example:
|
||||
|
||||
.. code::
|
||||
|
||||
def writeline(self, command):
|
||||
self.communicate(command + ';*OPC?')
|
||||
|
||||
or to add an additional query which is returning always a reply, e.g.:
|
||||
|
||||
.. code::
|
||||
|
||||
def writeline(self, command):
|
||||
with self._lock: # important!
|
||||
self.communicate(command, noreply=True)
|
||||
self.communicate('*OPC?')
|
||||
|
||||
The first version is preferred when the hardware allows to join several
|
||||
commands by a separator.
|
||||
"""
|
||||
self.communicate(command, noreply=True)
|
||||
|
||||
@Command(ArrayOf(TupleOf(StringType(), BoolType(), FloatRange(0, unit='s'))),
|
||||
result=ArrayOf(StringType()))
|
||||
def multicomm(self, requests):
|
||||
"""communicate multiple request/replies in one go
|
||||
|
||||
:param requests: a sequence of tuple of (command, request_expected, delay)
|
||||
if called internally, a sequence of strings (command) is also accepted
|
||||
:return: list of replies
|
||||
|
||||
This method may be rarely used, it is intended when the hardware needs
|
||||
that several commands are not intercepted by an other client or by the poller,
|
||||
for example selecting a channel before reading it. Or when wait times different
|
||||
from 'wait_before' have to be specified.
|
||||
|
||||
These cases may also handled by adding an additional method to the IO class.
|
||||
This could also be a custom SECoP command.
|
||||
Or, in the case where all useful commands in this IO class need it,
|
||||
:meth:`communicate` may be overridden.
|
||||
|
||||
This method should be used in the following cases:
|
||||
|
||||
1) you want to use a generic communicator covering above use cases over SECoP.
|
||||
2) you do not want to subclass the IO class.
|
||||
"""
|
||||
replies = []
|
||||
with self._lock:
|
||||
for cmd in commands:
|
||||
replies.append(self.communicate(cmd))
|
||||
for request in requests:
|
||||
if isinstance(request, str):
|
||||
cmd, expect_reply, delay = request, True, 0
|
||||
else:
|
||||
cmd, expect_reply, delay = request
|
||||
if expect_reply:
|
||||
replies.append(self.communicate(cmd))
|
||||
else:
|
||||
self.writeline(cmd)
|
||||
if delay:
|
||||
time.sleep(delay)
|
||||
return replies
|
||||
|
||||
|
||||
@ -426,13 +487,20 @@ class BytesIO(IOBase):
|
||||
self.log.error(self._last_error)
|
||||
raise SilentError(repr(e)) from e
|
||||
|
||||
@Command((ArrayOf(TupleOf(BLOBType(), IntRange(0)))), result=ArrayOf(BLOBType()))
|
||||
@Command(StructOf(requests=ArrayOf(TupleOf(BLOBType(), IntRange(0), FloatRange(0, unit='s')))),
|
||||
result=ArrayOf(BLOBType()))
|
||||
def multicomm(self, requests):
|
||||
"""communicate multiple request/replies in one row"""
|
||||
"""communicate multiple request/replies in one go
|
||||
|
||||
:param requests: sequence of tuple (<command>, <expected reply length>, <delay>)
|
||||
:return: list of replies
|
||||
"""
|
||||
replies = []
|
||||
with self._lock:
|
||||
for request in requests:
|
||||
replies.append(self.communicate(*request))
|
||||
for cmd, replylen, delay in requests:
|
||||
replies.append(self.communicate(cmd, replylen))
|
||||
if delay:
|
||||
time.sleep(delay)
|
||||
return replies
|
||||
|
||||
def readBytes(self, nbytes):
|
||||
|
@ -27,7 +27,8 @@ from frappy.modules import Command, HasProperties, Module, Parameter, Property
|
||||
|
||||
def indent_description(p):
|
||||
"""indent lines except first one"""
|
||||
return indent(p.description, ' ').replace(' ', '', 1)
|
||||
space = ' ' * 6
|
||||
return indent(p.description, space).replace(space, '', 1)
|
||||
|
||||
|
||||
def fmt_param(name, param):
|
||||
|
98
test/test_io.py
Normal file
98
test/test_io.py
Normal file
@ -0,0 +1,98 @@
|
||||
# *****************************************************************************
|
||||
#
|
||||
# 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>
|
||||
#
|
||||
# *****************************************************************************
|
||||
|
||||
|
||||
import time
|
||||
import pytest
|
||||
from frappy.io import StringIO
|
||||
|
||||
|
||||
class Time:
|
||||
def __init__(self, items):
|
||||
self.items = items
|
||||
|
||||
def sleep(self, seconds):
|
||||
self.items.append(seconds)
|
||||
|
||||
|
||||
class IO(StringIO):
|
||||
def __init__(self):
|
||||
self.items = []
|
||||
self.propertyValues = {}
|
||||
self.earlyInit()
|
||||
|
||||
def communicate(self, command, noreply=False):
|
||||
self.items.append(command)
|
||||
return command.upper()
|
||||
|
||||
|
||||
class AppendedIO(IO):
|
||||
def communicate(self, command, noreply=False):
|
||||
self.items.append(command)
|
||||
if noreply:
|
||||
assert not '?' in command
|
||||
return None
|
||||
assert '?' in command
|
||||
return '1'
|
||||
|
||||
def writeline(self, command):
|
||||
assert self.communicate(command + ';*OPC?') == '1'
|
||||
|
||||
|
||||
class CompositeIO(AppendedIO):
|
||||
def writeline(self, command):
|
||||
# the following is not possible, as multicomm is recursively calling writeline:
|
||||
# self.multicomm([(command, False, 0), ('*OPC?', True, 0)])
|
||||
# anyway, above code is less nice
|
||||
with self._lock:
|
||||
self.communicate(command, noreply=True)
|
||||
self.communicate('*OPC?')
|
||||
|
||||
|
||||
def test_writeline_pure():
|
||||
io = IO()
|
||||
assert io.writeline('noreply') is None
|
||||
assert io.items == ['noreply']
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'ioclass, cmds', [
|
||||
(AppendedIO, ['SETP 1,1;*OPC?']),
|
||||
(CompositeIO, ['SETP 1,1', '*OPC?']),
|
||||
])
|
||||
def test_writeline_extended(ioclass, cmds):
|
||||
io = ioclass()
|
||||
io.writeline('SETP 1,1')
|
||||
assert io.items == cmds
|
||||
|
||||
|
||||
def test_multicomm_simple():
|
||||
io = IO()
|
||||
assert io.multicomm(['first', 'second']) == ['FIRST', 'SECOND']
|
||||
assert io.items == ['first', 'second']
|
||||
|
||||
|
||||
def test_multicomm_with_delay(monkeypatch):
|
||||
io = IO()
|
||||
tm = Time(io.items)
|
||||
monkeypatch.setattr(time, 'sleep', tm.sleep)
|
||||
assert io.multicomm([('noreply', False, 1), ('reply', True, 2)]) == ['REPLY']
|
||||
assert io.items == ['noreply', 1, 'reply', 2]
|
Loading…
x
Reference in New Issue
Block a user