pySerial inWaiting returns incorrect number of byt

2020-02-06 13:11发布

问题:

I've got a simple program to test serial functionality. My serial device reacts to two inputs. If the user enters 'a', it responds with 'fg'. If the user enters anything other character/byte, it responds with 'z'. If I send 'b' to the serial device, it will return 'z' just fine. When I send 'a', it should return both 'f' and 'g', so two bytes instead of one.

See code below.

#!/usr/bin/env python

import serial

ser = serial.Serial(
    port = '/dev/ttyUSB0',
    baudrate = 9600,
    parity = serial.PARITY_NONE,
    stopbits = serial.STOPBITS_ONE,
    bytesize = serial.EIGHTBITS
    )

ser.write('a')
byteData = ser.read(1) # read one, blocking
moreBytes = ser.inWaiting()

if moreBytes:
    byteData = byteData + ser.read(moreBytes)
    print byteData

print byteData


ser.close()

The output is :

user@ubuntu:~/code/native$ ./serialTesting.py 
f

inWaiting() gives the value of 0, thus it never reads the second byte. If I do a small change to the code, and manually read the two expected bytes, it works fine.

#!/usr/bin/env python

import serial

ser = serial.Serial(
    port = '/dev/ttyUSB0',
    baudrate = 9600,
    parity = serial.PARITY_NONE,
    stopbits = serial.STOPBITS_ONE,
    bytesize = serial.EIGHTBITS
    )

ser.write('a')
byteData = ser.read(2) # read two expected bytes for the result 'fg'

print byteData

ser.close()

The output is as expected:

user@ubuntu:~/code/native$ ./serialTesting.py 
fg

回答1:

There are two decent solutions for this. For either, you'll need to set a timeout like jramirez already suggested:

ser = serial.Serial(
    port = '/dev/ttyUSB0',
    baudrate = 9600,
    parity = serial.PARITY_NONE,
    stopbits = serial.STOPBITS_ONE,
    bytesize = serial.EIGHTBITS,
    timeout=0.5, # IMPORTANT, can be lower or higher
    inter_byte_timeout=0.1 # Alternative
    )

Solution 1: Simple and effective

byteData = ser.read(size=800) #Set size to something high

This will read up to 800 bytes and will take no more time than the timeout you've set. If you've instead set an inter_byte_timeout, read() will wait up to that amount of time for each single byte.

This is a quick solution that will work for cases where you only receive a chunk of data of known maximum size.

Solution 2: The proper way

def read_all(port, chunk_size=200):
    """Read all characters on the serial port and return them."""
    if not port.timeout:
        raise TypeError('Port needs to have a timeout set!')

    read_buffer = b''

    while True:
        # Read in chunks. Each chunk will wait as long as specified by
        # timeout. Increase chunk_size to fail quicker
        byte_chunk = port.read(size=chunk_size)
        read_buffer += byte_chunk
        if not len(byte_chunk) == chunk_size:
            break

    return read_buffer

The code snippet above is licensed under CC0 1.0.

And then, to read:

byteData = read_all(ser)

Basically, this will read your data in chunks and wait every time to see if new characters appeared. If less characters were read in the time set by timeout, the transmission is considered finished.

This solution will always work, even when you receive a lot of data or if you receive it very slowly.



回答2:

it could be because the baudrate is really slow. You are processing the inwaiting() call before the second byte gets to the buffer. When you do ser.read(2) it waits(blocks) until 2 bytes have been received thus why it works. Try setting a timeout of 1 second, that should fix your problem.

ser = serial.Serial(
    port = '/dev/ttyUSB0',
    baudrate = 9600,
    parity = serial.PARITY_NONE,
    stopbits = serial.STOPBITS_ONE,
    bytesize = serial.EIGHTBITS,
    timeout=1 # add this
    )
ser.write('a')
byteData = ser.read(1) # read one, blocking
byteData += ser.read(ser.inWaiting())

print byteData

ser.close()