stp
stp

Reputation: 11

Why is serial read(1) continuing to wait until the timeout after having received a character?

The following code communicates with hardware through the serial interface. The communication is successful (the hardware actuates and correct positions are reported), except that the read command always takes roughly s.timeout seconds, almost regardless of the s.timeout setting (s.timeout must be at least ~3s).

I would like to know how this code might be changed so that the read command will return immediately after having received just one character.

import serial # installed as pyserial
import time, io

try:
    # Best guesses for an undocumented serial device
    s = serial.Serial()
    s.port = 'COM1'
    s.baudrate = 9600
    s.timeout = 5
    s.open()

    # Device begins 18s init routine when serial communication established
    sio = io.TextIOWrapper(io.BufferedRWPair(s,s))
    time.sleep(18)
    start = time.time()
    dat = sio.read(1)
    print('Initial position is %s (%0.3fs reply)' % (dat, time.time()-start))

    # Move device from initial position to Position 2
    sio.write('2')
    sio.flush()
    start = time.time()
    dat = sio.read(1)
    print('New position is %s (%0.3fs reply)' % (dat, time.time()-start))

finally:
    del sio
    s.close()

Output:

Initial position is 0 (5.008s reply)
Moved to position 2 (5.003s reply)

Upvotes: 1

Views: 673

Answers (2)

stp
stp

Reputation: 11

I'm able to get the expected behavior from read(1) when I use the serial object directly instead of going through TextIOWrapper, as shown in the code below. time.sleep() is no longer necessary but I've kept it so that the code output can be compared to that of the original post.

I'm using Python 3.7.3, and the documentation for io.TextIOWrapper, which inherits from io.TextIOBase, doesn't explicitly state that read(N) stops blocking after N characters are read, although I think it's reasonable to assume that it should. Actually, the docs seem to suggest that read(size=1) is the correct implementation, although calling read in this way causes a "read() takes no keyword arguments" error.

I'm satisfied with using the serial object directly, but hopefully someone can answer the N-character question for others who might need TextIOWrapper.

import time, serial

try:
    s = serial.Serial()
    s.port = 'COM1'
    s.baudrate = 9600
    s.timeout = 20
    s.open()

    time.sleep(18)
    start = time.time()
    dat = s.read(1)
    print('Initial position is %s (%0.3fs reply)' % \
          (dat.decode('utf-8'), time.time()-start))

    s.write('2'.encode())
    s.flush()
    start = time.time()
    dat = s.read(1)
    print('New position is %s (%0.3fs reply)' % \
          (dat.decode('utf-8'), time.time()-start))

finally:
    s.close()

Output:

Initial position is 0 (0.000s reply)
New position is 2 (2.887s reply)

Upvotes: 0

TarkaDaal
TarkaDaal

Reputation: 19595

There are two things that look strange to me in this code.

  1. The serial port is being wrapped with a buffer, but if you want to control hardware on a character-by-character basis, that might not be what you want.
  2. BufferedRWPair should be used to logically combine two simplex streams into a duplex stream. However, the Serial class is already duplex.

I would try removing the TextIOWrapper and BufferedRWPair entirely, and directly call the read and write methods of the Serial class.

Upvotes: 1

Related Questions