Posts: 6,789
Threads: 20
Joined: Feb 2020
Is the response to read_status is 4 bytes (0xAA, ??, ??, checksum) or 3 bytes (0xAA, status, checksum)?
A failed read appears to raise an exception. According to pyserial documentation that should not happen UNLESS the port is closed or there are multiple devices trying to read the serial port. You should catch the exception and test if the serial port is still open.
Posts: 33
Threads: 7
Joined: Oct 2020
I'm running this code on a Raspberry Pi 4. If the program fails to close the port when it ends properly, the next time it runs it think there are more devices accessing the port.
Also, the documentation says to expect a status of AA 01 01 00 AC when MP3 has finished playing but the return appears to be 00 00 00, but the program is coded to accept the 00 00 00 to end.
I added print(" port ", ser.isOpen) in the read routine but how would I check to see if it thinks more devices are attempting to use the port?
What I saw after running this the second time is important! Shutting the RPi down and starting it fresh, I ran the program the first time with the print("port Open? ", isOpen) and the program ran and ended well printing the 00 00 00 return after the MP3 finished playing only. Makes me believe that the chip on the MP3 board can process only one thing at a time.
But the next observance is the real problem. When I ran the program again with no changes, I got the errors indicating that the device disconnected or multiple access on port. See output below.
Something needs to be closed or reset but at the end of the program is the ser.close() statement.
Output: pi@raspberrypi:~ $ sudo python3 SerialTest3.py
Command = [170, 7, 2, 0, 4, 183]
Command = [170, 1, 0, 171]
port Open? <bound method SerialBase.isOpen of Serial<id=0xb66e7bb0, open=True>(port='/dev/ttyS0', baudrate=9600, bytesize=8, parity='N', stopbits=1, timeout=1, xonxoff=False, rtscts=False, dsrdtr=False)>
Status = b'\xaa'
Command = [170, 1, 0, 171]
port Open? <bound method SerialBase.isOpen of Serial<id=0xb66e7bb0, open=True>(port='/dev/ttyS0', baudrate=9600, bytesize=8, parity='N', stopbits=1, timeout=1, xonxoff=False, rtscts=False, dsrdtr=False)>
Status = b''
Command = [170, 1, 0, 171]
port Open? <bound method SerialBase.isOpen of Serial<id=0xb66e7bb0, open=True>(port='/dev/ttyS0', baudrate=9600, bytesize=8, parity='N', stopbits=1, timeout=1, xonxoff=False, rtscts=False, dsrdtr=False)>
Status = b''
Command = [170, 1, 0, 171]
port Open? <bound method SerialBase.isOpen of Serial<id=0xb66e7bb0, open=True>(port='/dev/ttyS0', baudrate=9600, bytesize=8, parity='N', stopbits=1, timeout=1, xonxoff=False, rtscts=False, dsrdtr=False)>
Status = b''
Command = [170, 1, 0, 171]
port Open? <bound method SerialBase.isOpen of Serial<id=0xb66e7bb0, open=True>(port='/dev/ttyS0', baudrate=9600, bytesize=8, parity='N', stopbits=1, timeout=1, xonxoff=False, rtscts=False, dsrdtr=False)>
Status = b''
Command = [170, 1, 0, 171]
port Open? <bound method SerialBase.isOpen of Serial<id=0xb66e7bb0, open=True>(port='/dev/ttyS0', baudrate=9600, bytesize=8, parity='N', stopbits=1, timeout=1, xonxoff=False, rtscts=False, dsrdtr=False)>
Status = b''
Command = [170, 1, 0, 171]
port Open? <bound method SerialBase.isOpen of Serial<id=0xb66e7bb0, open=True>(port='/dev/ttyS0', baudrate=9600, bytesize=8, parity='N', stopbits=1, timeout=1, xonxoff=False, rtscts=False, dsrdtr=False)>
Status = b'\x00\x00\x00'
pi@raspberrypi:~ $ sudo python3 SerialTest3.py
Command = [170, 7, 2, 0, 4, 183]
Command = [170, 1, 0, 171]
port Open? <bound method SerialBase.isOpen of Serial<id=0xb664ebb0, open=True>(port='/dev/ttyS0', baudrate=9600, bytesize=8, parity='N', stopbits=1, timeout=1, xonxoff=False, rtscts=False, dsrdtr=False)>
Traceback (most recent call last):
File "/usr/lib/python3/dist-packages/serial/serialposix.py", line 501, in read
'device reports readiness to read but returned no data '
serial.serialutil.SerialException: device reports readiness to read but returned no data (device disconnected or multiple access on port?)
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "SerialTest3.py", line 37, in <module>
playing = read_status(ser)
File "SerialTest3.py", line 19, in read_status
return port.read(3)
File "/usr/lib/python3/dist-packages/serial/serialposix.py", line 509, in read
raise SerialException('read failed: {}'.format(e))
serial.serialutil.SerialException: read failed: device reports readiness to read but returned no data (device disconnected or multiple access on port?)
Posts: 6,789
Threads: 20
Joined: Feb 2020
Jul-13-2021, 02:35 PM
(This post was last modified: Jul-13-2021, 04:18 PM by deanhystad.)
The read_status command has to read 5 bytes to get 3 status bytes. In the code below I added a check for the start of message character (often referred to as a sentinel) and a checsum check.
"""Sends a message via serial & receives back information via serial."""
import serial
import time
PLAYING_DONE = bytearray([0, 0, 0])
def write_command(port, command):
"""Write command to serial port"""
command = [170] + command # Command packet starts with 0xAA
command.append(sum(command) % 256) # Append checksum
print(command)
port.write(bytearray(command))
def read_status(port):
"""Read status? Returns 3L bytearray"""
write_command(port, [1, 0])
bytes = port.read(5)
print('Reply ', bytes)
if bytes is None:
raise ValueError('Read timeout')
if bytes[0] != 0xAA:
raise ValueError('Serial sentinel error')
if bytes[4] != (sum(bytes[:-1]) % 256):
raise ValueError('Serial read checksum error')
return bytes[1:-1]
def play_track(port, track_info):
"""Start playing track. Track is??"""
write_command(port, [7] + track_info)
with serial.Serial(
'COM3:',
baudrate=9600,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
bytesize=serial.EIGHTBITS,
timeout=1) as ser:
# Start of serial context
play_track(ser, [2, 0, 4]) # Track info?
playing = None
while playing != PLAYING_DONE:
time.sleep(1)
playing = read_status(ser)
print(f'Status = {playing}')
# end of serial context
print("I am done") To prevent leaving the serial port open the code uses a context manager. "with serial.Serial(...)" creates a context for the serial port. When the program exits this context (the indented instructions below the "with") the serial port is automatically closed. It doesn't matter if the context exits because the program is done using the serial port or if there is an exception.
Posts: 33
Threads: 7
Joined: Oct 2020
Again, it started playing the MP3 requested but then got a run error. See below:
The Internet suggests leading the statement with try: and following up with except and pass.
What do you think?
Output: $ sudo python3 SerialTest4.py
[170, 7, 2, 0, 4, 183]
[170, 1, 0, 171]
Traceback (most recent call last):
File "SerialTest4.py", line 41, in <module>
playing = read_status(ser)
File "SerialTest4.py", line 20, in read_status
raise ValueError('Serial sentinel error')
ValueError: Serial sentinel error
Posts: 33
Threads: 7
Joined: Oct 2020
Not sure if this is a patch or fix, but I started increasing the timeout parameter in the serial open statement and now, it doesn't give the error
serial.serialutil.SerialException: read failed: device reports readiness to read but returned no data (device disconnected or multiple access on port?)
I went up to 20 before it stopped giving an error, started at 2. I suppose, that the chip on the MP3 board doesn't support a very fast serial port.
This just stopped the above error. I still have work to do to get the whole return code back. At this point, all I get back are zeros, no leading or trailing information like the documentation suggests.
Posts: 33
Threads: 7
Joined: Oct 2020
Deanhystad,
With your help and others, I was able to come up with something that works consistently well.
Thanks to all!
Seems there were several issues. One was timing. The timeout parameter in the open statement needed to be long enough for the MP3 board to compile the answer to the query. Another issue was timing between the request and the response. 3rd was to not ask for the read till the inWaiting was equal to the length of the response.
Also, I had trouble when I kept the serial port open through the whole program. Opening and closing probably causes a bunch of overhead but worked best for me. I tried only opening once in the program after I was successful opening and closing many times and it failed with time out issues.
Please consider this issue closed.
Here is the resultant code:
"""Sends a message via serial & receives back information via serial."""
import serial
import time
to = 30 #<-- timeout value
PLAYING_DONE = bytearray([170, 1, 1, 0, 172])
def write_command(port, command):
"""Write command to serial port"""
command = [170] + command # Command packet starts with 0xAA
command.append(sum(command) % 256) # Append checksum
# print('Command = ', command) # For debugging purposes
port.write(bytearray(command))
def read_status(port):
"""Read status? Returns bytearray"""
write_command(port, [1, 0])
time.sleep(0.5)
if port.inWaiting() > 4:
return port.read(5)
def play_track(port, track_info):
"""Start playing track. Track is??"""
write_command(port, [7] + track_info)
ser = serial.Serial(
'/dev/ttyS0', # Serial port on my computer
baudrate=9600,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
bytesize=serial.EIGHTBITS,
timeout=to) # timeout for read
play_track(ser, [2, 0, 9]) # Track info?
ser.close()
playing = None
while playing != PLAYING_DONE:
ser = serial.Serial(
'/dev/ttyS0', # Serial port on my computer
baudrate=9600,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
bytesize=serial.EIGHTBITS,
timeout=to) # timeout for read
playing = read_status(ser)
# print(f'Return = {playing}')
ser.close()
ser.close()
print("Done and closed")
|