Search code examples
pythonserial-portraspberry-pipyserialobd-ii

pySerial reading data from AT commands


I'm having trouble reading the response from a RS232 OBD2 interface via pySerial. The code successfully enters the data, as I can see from a direct parallel terminal screen, but fails to read and print the response, regardless of the response.

Right now the code is not capable of printing the response in neither versions of Python. The code looks something like this :

from serial import * # I also tried using /from serial import Serial
import time
ser = Serial("/dev/rfcomm1", 38400, timeout=1)
#print ('Starting up, formatting responses')
#ser.write("ATZ\r"),
#ser.write("ATSP0\r"),
#ser.write("ATS1\r"),
#ser.write("ATL1\r"),
#ser.write("ATH1\r"),
#ser.write("ATF1\r")
#time.sleep(1)
#print ('We have lift-off !')
if ser.inWaiting() > 0:
    ser.flushInput()
#ser.timeout = 1.
time.sleep(1)
#print (raw_data)
ser.write("AT RV\r") #The response should be something like 13.5V, but nothing
ser.timeout = 1.
msg = ser.read(size=1024)
print msg
ser.close()

I left only the AT RV command because while I'm working on it I sent the text formatting commands to ease the job. Right now when I send it it just gives me a blank line (although the terminal which is running on the same machine displays the desired output)

There are no errors in the code, and the commands go through and are responded to by the interface, and I can see that in another live term, but nothing appears when running the Python code. What should I do ?


Solution

  • You should read after writing, not before.

    # before writing anything, ensure there is nothing in the buffer
    if ser.inWaiting() > 0:
        ser.flushInput()
    
    # set the timeout to something reasonable, e.g., 1 s
    ser.timeout = 1.
    
    # send the commands:
    ser.write("ATZ\r")
    # ...
    
    # read the response, guess a length that is more than the message
    msg = ser.read(1024)
    print msg
    
    # send more commands
    # read more responses
    # ...
    

    The point here is that there is no way to know when the response has been received. This code waits for one second after each command sent, unless more than 1024 bytes arrive during that time. There are more clever algorithms, but let's try with this one, first.

    If you want to do something more complicated with the serial line, have a look at the pexpect module.


    Some thoughts debugging python serial problems

    Serial communication problems are sometimes a bit sticky to solve. pySerial is a reliable library, but as different platforms have different types of serial API, there are a lot of details. Things have not become any easier by the removal of physical serial ports, as the USB converters bring an extra layer into the game. Bluetooth converters are even worse.

    The best way to debug the physical layer is to have some monitor hardware with two serial ports tapped into the serial lines. This kind of sniffer helps to isolate the problem to either end of the connection. Unfortunately, such sniffers are very rarely at hand when needed.

    The next best thing is to short the RD and TD (RXD, TXD) pins of the serial line. This way all data will be echoed. If the data is received as sent, the physical connection is good. One thing to take care is handshaking. If you do not know what you are doing, disable all flow control (xon/xoff, rts/cts, dtr/dsr. pySerial disables these all if otherwise instructed.

    In the case of the question above the physical connection is ok, as another piece of software demonstrates that the data is sent and understood by the other device. (Seeing that something is sent does not prove anything, as that information does not go through the physical layer, but seeing something produced by another device is received proves that the physical connection is ok.)

    Now we know the data comes into the operating system, but pySerial does not see it. Or then our code is still somehow bad (no, it shouldn't, but...)

    Let us suspect own own code and try someone else's code. This can be run from command prompt:

    python -m serial.tools.miniterm /dev/rfcomm1 38400
    

    Now we have a terminal which can be used to manually send/receive data form the other party. If the behaviour can be repeated (sends ok, data is received into the system, but not shown on the terminal) with this, then the problem is probably not in our code.

    The next step then is to try:

    sudo python -m serial.tools.miniterm /dev/rfcomm1 38400
    

    In principle access right problems lead to situations where we can receive but not send. But it does not harm to test this, because odd rights cause odd problems.

    pySerialhas a handy function readline which should read one line at a time from the serial line. This is often what is wanted. However, in this specific case the lines seem to end with \r instead of \n. The same may be repeated elsewhere in code, so with special data special care is needed. (The simple "read with timeout" is safe but slow in this sense.) This is discussed in: pySerial 2.6: specify end-of-line in readline()

    The same issue plagues all terminal programs. For the pySerial miniterm, see its documentation (command-line option --cr).

    If there are timeouts, they can and should be made longer for debugging purposes. A one-second timeout may be changed into a ten-second timeout to make sure the other device has ample time to answer.