0

I'm having a weird issue with pyserial, using Python 3.6.9, running under WSL Ubuntu 18.4.2 LTS

I've set up a simple function to send GCODE commands to a serial port:

def gcode_send(data):
    print("Sending: " + data.strip())
    data = data.strip() + "\n"  # Strip all EOL characters for consistency
    s.write(data.encode())  # Send g-code block to grbl

    grbl_out = s.readline().decode().strip()
    print(grbl_out)

It sort of works, but every command I send is 'held' until the next is sent.

e.g.

  1. I send G0 X0 > the device doesn't react
  2. I send G0 X1 > the device reacts to G0 X0
  3. I send G1 X0 > the device reacts to G0 X1
  4. and so on...

My setup code is:

s = serial.Serial(com, 115200)

s.write("\r\n\r\n".encode())  # Wake up grbl
time.sleep(2)  # Wait for grbl to initialize
s.flushInput()  # Flush startup text in serial input

I can work around the delay for now, but it's quite annoying and I can't find anyone else experiencing the same. Any idea what could be causing this?

XDGFX
  • 41
  • 2
  • 9

2 Answers2

0

There might be a lot of problems here, but rest assured that the pyserial is not causing it. It uses the underlying OS's API to communicate with the UART driver. That being said you first have to test your code with real Linux to see whether WSL is causing it. I.e. whether a Linux and Windows UART buffers are correctly synced. I am sorry that I cannot tell whether a problem is in your code or not because I do not know the device you are using, so I cannot guess what is happening on its end of communication channel. Have in mind that Windows alone can act weirdly in best of circumstances, so, prepare yourself for some frustrations here. Check your motherboard or USB2Serial converter drivers or whatever hw you are using.

Next thing, you should know that sometimes, communication gets confusing if timeouts aren't set. Why? Nobody really knows. So try setting timeouts. Check whether you need software Xon/Xoff turned on or not, and other RS232 parameters that might be required by the device you are communicating with.

Also, see what is going on with s.readline(), I wouldn't personally use it. Timeouts might help or you can use s.read(1024) with timeouts. I do not remember right now, but see whether pyserial supports asynchronous communication. If it does, you can try using it instead of standard blocking mode.

Also, check whether you have to forcefully flush the serial buffer after s.write() or add a sleep after it. It might happen that the device doesn't get the message but the read request is activated. As the device didn't receive the command it doesn't respond. After you send another command, IO buffer is flushed and the previous one is delivered and so forth. Serial communication is fun, but when it hits a snag it can be a real P in the A, believe me.

Ow, a P.S. Check whether the device sends "\r\n\r\n" or "\r\n" only, or "\r" or "\n" in response. s.readline() might get confused. For a start, try putting there two s.readline()s one after another and print out each output. If the device sends double EOL then the one s.readline() is stopping on the empty line and your program receives an empty response, when you send another command s.readline() goes through the buffer and returns a full line that is already there but not read before.

Dalen
  • 4,128
  • 1
  • 17
  • 35
  • Thanks for the extensive reply! A note on the first section - I left this out as I thought it wasn't necessary but the 'device' I'm communicating is `grbl-sim`, which is software that emulates a real Arduino running grbl. As such the com port is just a temporary file, and not a real hardware device. – XDGFX Mar 22 '20 at 12:32
  • I will have a look into timeouts, xon/xoff and anything else I find. Using `s.read(1024)` seems to just hang. I'll look into other timeout options. I'm fairly new to this but I think this is asynchronous, that just means there isn't one clock controlling both devices (which would be the case with a physical Arduino). Maybe you mean full-duplex, in which case it looks like this can only be done with multithreading in Python, with one function reading while another writes. – XDGFX Mar 22 '20 at 12:40
  • What a mess you got yourself in. I wish you luck. :D What are all parties that are here involved in RS232 propagation and buffering would be nice to know. At least the "device" replies with something. :D So you just need to figure out what is going on. I hope that you just need to flush the buffer(s) or that double EOL is a problem. – Dalen Mar 22 '20 at 12:40
  • I've tried adding various s.flush() and 2s sleeps but couldn't get the behaviour to change. – XDGFX Mar 22 '20 at 12:42
  • s.read() is blocking, I.e. if there is no 1024 bytes it will hang, that is why you need a timeout with it. It will return what finds in the buffer and return upon timeout. Then you check for \n manually. – Dalen Mar 22 '20 at 12:43
  • When I do full duplex emulating the blocking communication, I use one thread for reading with a queue using the collections.deque(). In main thread I do writing and waiting for the reply to appear in the queue by using: while 1: try: Q.popleft(); except: sleep(0.001); continue – Dalen Mar 22 '20 at 12:47
  • Ah, I assumed that was the timeout in ms or something ;) Tried with a 1s timeout and same problem. If this helps, using `screen` works perfectly, which is why I assume something with pyserial – XDGFX Mar 22 '20 at 12:48
  • screen knows what it is doing and it is full duplex because you have to type and read at the same time. So, no, it's not pyserial. It's your code. ;) Sorry to disapoint. :D – Dalen Mar 22 '20 at 12:51
  • Checked the docs and responses are "terminated by a return", so presumably just '\r'. The docs also say xon/xoff is not supported – XDGFX Mar 22 '20 at 12:54
  • Fair enough :) I based it off the supplied grbl simple_stream.py, however this was written for Python 2 and I had to adapt it. I might try their alternative streaming method that doesn't wait for responses, and see if that makes any difference. I'll let you know if I figure it out, thanks for the suggestions! :D – XDGFX Mar 22 '20 at 12:57
  • Check then how s.readline() works. I think that in console: help(s.read) will show you the __doc__ string, if not, go digging through pyserial documentation. If it doesn't detect "\r" or something, then: data = s.read(1024).split("\r", 1) is a way to go. Then check len(data) to see whether it has 1 or 2 items. The first is the response, and the second one you save to add to it in the next iteration. – Dalen Mar 22 '20 at 13:04
  • I am going to digg out the console I wrote for screen-like communication via bluetooth with Nokia Symbian smartphones. And if I can shorten it in reasonable time, I'll post it. It's in Python 2 though. – Dalen Mar 22 '20 at 13:07
0

Here it goes. The code promissed in the comment. Big portions of it removed and error checks too.

It is a typing terminal for using PyS60 Python console on Nokia smartphones in the Symbian series via bluetooth. Works fantastically.


from serial import *
from thread import start_new_thread as thread
from time import sleep
import sys, os

# Original code works on Linux too
# The following code for gettin one character from stdin without echoing it on terminal
# has its Linux complement using tricks from Python's stdlib getpass.py module
# I.e. put the terminal in non-blocking mode, turn off echoing and use sys.stdin.read(1)
# Here is Win code only (for brevity):
import msvcrt

def getchar ():
    return msvcrt.getch()

def pause ():
    raw_input("\nPress enter to continue . . .")

port = raw_input("Portname: ")
if os.name=="nt":
    nport = ""
    for x in port:
        if x.isdigit(): nport += x
    port = int(nport)-1

try:
    s = Serial(port, 9600)
except:
    print >> sys.stderr, "Cannot open the port!\nThe program will be closed."
    pause()
    sys.exit(1)

print "Port ready!"
running = 1

def reader():
    while running:
        try:
            msg = s.read()
            # If timeout is set
            while msg=="":
                msg = s.read()
            sys.stdout.write(msg)
        except: sleep(0.001)

thread(reader,())

while 1:
    try: c = getchar()
    except Exception, e:
        running = 0
        print >> sys.stderr, e
        s.write('\r\n\x04')
        break
    if c=='\003' or c=='\x04':
        running = 0
        s.write('\r\n\x04')
        break
    s.write(c)

s.close()
pause()

Dalen
  • 4,128
  • 1
  • 17
  • 35
  • Damn, I couldn't get this to work at all. Even just connecting to serial and sending s.write commands with Python 2.7 yielded nothing - I verified my serial connection was working normally (though with the delay) with my Python 3 code. Not to worry, for this project I can work my way around the issue, at least it's consistent :) Thanks again! – XDGFX Mar 23 '20 at 19:14