7

我有一个串行设备,我正在尝试从中读取输入。我给它发送了一个字符串“ID\r”,它返回“ID XX\r”(其中\r 是ASCII 回车,十六进制0x0d)。

由于不再支持 serial.readline 上的 eol 选项,因此我使用 TextIOWrapper 从串行端口读取并一次返回一行。

我的问题是,它不是在看到回车后立即返回我的字符串,而是等到我打开串口时设置的超时时间的两倍。我希望它在读取整行后立即返回字符串,因为我可能有数百个这样的命令要发送到设备,而且我不想每次都等待超时。如果我将超时设置为 0,那么我根本不会得到任何输出(可能是因为我的脚本在设备有机会输出任何内容之前停止等待),如果我将超时设置为无,脚本将永远阻塞。

这是一个简单的测试脚本:

import serial
import io
import time

ser = serial.Serial("/dev/ttyUSB0", baudrate=9600,
                    bytesize=8, parity='N', stopbits=1,
                    xonxoff=0, rtscts=1, timeout=5)

sio = io.TextIOWrapper(io.BufferedRWPair(ser, ser),
                       newline=None)


sio.write(unicode("ID\r"))
sio.flush()

print "reading..."

x = sio.readline()

print len(x)
print x

该脚本从它说“正在读取”到打印从串行端口读取的“ID XX”字符串总是需要 10 秒。

我确定设备正在输出回车,因为我使用 strace 来观看读取:

select(4, [3], [], [], {5, 0})          = 1 (in [3], left {4, 991704})
read(3, "I", 8192)                      = 1
select(4, [3], [], [], {5, 0})          = 1 (in [3], left {4, 999267})
read(3, "D", 8191)                      = 1
select(4, [3], [], [], {5, 0})          = 1 (in [3], left {4, 999420})
read(3, " ", 8190)                      = 1
select(4, [3], [], [], {5, 0})          = 1 (in [3], left {4, 999321})
read(3, "X", 8189)                      = 1
select(4, [3], [], [], {5, 0})          = 1 (in [3], left {4, 999355})
read(3, "X", 8188)                      = 1
select(4, [3], [], [], {5, 0})          = 1 (in [3], left {4, 999171})
read(3, "\r", 8187)                     = 1
select(4, [3], [], [], {5, 0})          = 0 (Timeout)
select(4, [3], [], [], {5, 0})          = 0 (Timeout)

您可以看到导致 10 秒延迟的 2 个 select() 超时,但您也可以清楚地看到正在读取的回车。我尝试将换行参数设置为“无”和“”(应该自动允许 \r、\n 和 \r\n)和“\r”,但每次的结果都相同。

我还尝试将 BufferedRWPair() 调用中的 buffer_size 设置为“1”,以防止它缓冲输入,但这并没有什么区别。

知道我做错了什么吗?

如果我不能让这个工作,我的下一步将是使用 serial.read() 一次读取一个字符并做我自己的行缓冲,但我想尝试用 textiowrapper 以“正确”的方式来做第一的。

4

4 回答 4

8

今天浪费了几个小时。事实证明,io.BufferedReader读取直到它填满了它的缓冲区,然后将缓冲区传递给io.TextIOWrapper. 默认缓冲区大小为 8192,因此根据您的设备,这可能需要一段时间。

正确的示例代码应该是:

# buffer size is 1 byte, so directly passed to TextIOWrapper
sio = io.TextIOWrapper(io.BufferedRWPair(ser, ser, 1), encoding='ascii')
print sio.readline()[:-1]
于 2012-09-25T10:39:24.443 回答
3

警告:我在 Mac 上使用 Python 3.4,因此您的使用范围可能会有所不同,尽管我相信将 TextIOWrapper 向后移植到 Python 2.7,Python 2.7(和其他操作系统)中的情况与我在下面描述的情况基本相同。

主要问题是 io.TextIOWrapper 本身使用缓冲机制,由未记录的 _CHUNK_SIZE 属性控制。这很不愉快。所以你有两个选择:

  1. 尝试使用超时。这是pyserial 文档页面上readline文档中所暗示的内容。但是,如果您使用较大的值(如您所做的那样),当没有足够的数据来填充 TextIOWrapper 的缓冲区时,您的代码将阻塞,直到达到超时。这就是您本质上所经历的(我没有追究为什么您必须等待超时值的两倍,但我认为这可以通过查看 TextIOWrapper 的实现来解决,最终与您的问题无关)。
  2. 第二种选择是将 _CHUNK_SIZE 更改为 1。在您的情况下,只需添加该行

    sio._CHUNK_SIZE = 1
    

    初始化 sio 后立即添加到您的代码中。这可能会产生令人不快的效果,即 TextIOWrapper 本身的缓冲将被关闭(这用于输入的增量解码)。如果性能不是问题,这是最简单的解决方案。如果性能是一个问题,您可以设置一个较低的超时值,而不是影响 _CHUNK_SIZE。但是,在这种情况下,请准备从 readline() 获取一个空字符串(如果设备向您发送一个空行,它将作为 '\n' 出现,因此可以将它与您将获得的空字符串区分开来读取用完分配的时间)。

您的代码还有另一个问题:当 sio 将被删除时,ser 的 close 方法将被调用两次,这将导致您的程序即将完成时出现异常(至少如果我尝试您的代码时会发生这种情况在我的电脑上)。您应该(似乎)创建 serial.Serial 的实例并将它们传递给 BufferedRWPair。

我还创建了一个基于 TextIOWrapper 的包装器类,如果有兴趣,我也可以发布它,只是我不想用一些额外的代码乱扔响应,严格来说,这是不需要的。

PS:与此同时,我在 Ubuntu 上试验了代码。在我的 Mac 上,我认为不需要将 io.BufferedRWPair 的缓冲区大小设置为 1,但在 Ubuntu 上,除了将 _CHUNK_SIZE 设置为 1 之外,我还必须这样做。

于 2015-01-12T02:20:22.140 回答
0

如果没有实际看到它,这将很难调试。但是看看你是否可以使用我的 tty 模块。

http://code.google.com/p/pycopia/source/browse/trunk/aid/pycopia/tty.py

尝试其中的 SerialPort 对象。我已经成功地使用它与串行仪器进行交互,其中“那个其他串行模块”有很多类似于你描述的问题。这也可以告诉您 FIFO 中是否有数据。

现在让我看看情况如何。

于 2012-04-19T07:08:41.743 回答
0

感谢 Keith 的代码,但我想让这段代码有点可移植,所以我想坚持使用默认的“串行”包。

另外,由于我还在学习 Python,所以我想尝试学习如何以预期的方式使用 TextIOWrapper。

我放弃了尝试使 serial.readline() 工作,所以现在我将只使用一个简单的“readLine”函数一次读取一个字符并查找回车终止符。虽然如果我遇到更多的连续古怪,我可能会重新使用你的代码。

谢谢!

def readLine(ser):
    str = ""
    while 1:
        ch = ser.read()
        if(ch == '\r' or ch == ''):  
            break
        str += ch

    #"print "str = " + str

    return str
于 2012-04-20T14:37:19.867 回答