Slow serial write in Python compared to Arduino IDE serial monitor

Hi all,

This is my first post and the title/subject says it all.

For some background:
I'm writing my Msc thesis and will be tracking and navigating a robot using a ceiling mounted camera. The image processing and navigation is done on my pc and through a USB serial connection (later I want to turn this into wireless) the robot will receive it's commands.

Robot is a mBot from makeblock which is based/uses a arduino uno and can be communicated with as such. I wrote a simple program to drive the robot respectively forward, backward left turn, right turn. The serial commands the sketch expects are: w,a,s or d. So far no problem and when I use the serial monitor from Arduino IDE it works fine. Meaning motors/robot reacts "immediatly" (I don't know if there is a way to easily measure response time?).

But my main program will be written in Python so I want to write serial commands through Python. So I wrote a simple program for this but here arrises the problem. Whenever I write a serial comman using python the robot responds/executes the command with 2 sec delay. I tried adjusting/removing the time.sleep in my code without luck.
I'm using Spyder (Python 3.7) through anaconda as my Python IDE

So my question is: Is there a way to speed this up? Am i doing something wrong?
I already spent some time on the forum looking for solutions but so far none have worked for me.

Here is my sketch

//For communicating with the mBot arduino board
#include "MeMCore.h"

// initializing the motors
MeDCMotor motor1(M1);
MeDCMotor motor2(M2);

//initialize speed
int leftspeed = 0; /* value: between -255 and 255. */
int rightspeed = 0; /* value: between -255 and 255. */
int speed = 100;

//Has to be "defined" for the serial communication input
String command;

void setup() {
  // initialize serial communication:
  Serial.begin(9600);
}

void loop()
{
  if (Serial.available()) {
    command = Serial.readStringUntil('\n');
    if (command.equals("w")) { //forward
      leftspeed = speed;
      rightspeed = speed;
    }
    else if (command.equals("a")) { //left
      leftspeed = 0;
      rightspeed = speed;
    }
    else if (command.equals("s")) { //backward
      leftspeed = -speed;
      rightspeed = -speed;
    }
    else if (command.equals("d")) { //right
      leftspeed = speed;
      rightspeed = 0;
    }
  }
  else {
    Serial.println("Invalid command");
    Serial.println(leftspeed);
    Serial.println(rightspeed);
  }
  motor1.run((M1) == M1 ? -(leftspeed) : (leftspeed));
  motor2.run((M2) == M1 ? -(rightspeed) : (rightspeed));
}

Here is my python code

import serial
import time

# Define the serial port and baud rate.
# Default baud rate for mBot USB is 9600 and 115200 for Bluetooth
# Ensure the 'COM#' is correctly selected for the mBot
ser = serial.Serial('COM5', 9600)

def drivepy():
    user_input = input("\n Type w / a / s / d / quit: ")
    if user_input =="w":
        print("Driving Forward")
        time.sleep(0.1) 
        ser.write(b'w') 
        drivepy()
    elif user_input =="a":
        print("Turn Left")
        time.sleep(0.1)
        ser.write(b'a')
        drivepy()
    elif user_input =="s":
        print("Drive Backwards")
        time.sleep(0.1)
        ser.write(b's')
        drivepy()
    elif user_input =="d":
        print("Turn Right")
        time.sleep(0.1)
        ser.write(b'd')
        drivepy()
    elif user_input =="quit" or user_input == "q":
        print("Program Exiting")
        time.sleep(0.1)
        ser.write(b'L')
        ser.close()
    else:
        print("Invalid input. Type on / off / quit.")
        drivepy()

time.sleep(2) # wait for the serial connection to initialize

drivepy()

Probably I'm missing some basic knowledge here about serial communication in combination with Python. I hope someone can help me

You should NOT be calling your drivepy() function from within itself - the program will just vanish up its own backside so it's not surprising if things are not working well.

And what is the purpose of the time.sleep() ?

def drivepy():
    user_input = input("\n Type w / a / s / d / quit: ")
    if user_input =="w":
        print("Driving Forward")
        time.sleep(0.1) 
        ser.write(b'w') 
        drivepy()

Have a look at this Simple Python - Arduino demo

...R

To be honest the main set-up of this code was copied/used from a tutorial.
Python Arduino Tutorial
How I see it the sleep is meant to prevent latency but I agree it's a bit weird and could be deleted from the code.
I took a look at your link and also came across it before. But it doesn't solve my issue or I don't understand it which is highly possible.

Instead of using my script I also used the following code to test/see/trial error if the problem was within my script. But it made no difference and the time between executing the python command and the robot to react was still 2 seconds.

import serial
import time

ser = serial.Serial('COM5', 9600)

ser.write(b'w')

Am I overlooking something? Or should I use different libraries then Pyserial?
I can't grasp the fact that when I use the arduino IDE serial monitor there is no delay in response at all

Pyserial works very well.

Did you try the code in my Tutorial?

...R

Yes I just tried it and python shows the following response

Serial port COM5 opened  Baudrate 9600
Waiting for Arduino to reset
Arduino is ready
message sent
message sent

After this nothing happens and I have to reconnect the arduino. I think this is supposed to happen correct?
I played with the time interval for sending messages and indeed it turns out pyserial works good and fast.

Your c++ code looks quite advanced to me (I'm just starting) but I think I get the main idea. Your Python I also understand. But what I don't understand what is wrong with my code. I see you use the sendToArduino function, does this influence how fast and reliable python can communicate with the arduino?
I like to learn about these kind of things and program in a proper way.

In my code I tried to keep it as simple as possible and just sending a single character without using any additional function or whatever.

I will try to implement your structure in my code or the other way around and see if it works. But even if it works I'm interested in the mechanics/reasoning why. By trial and error I will probably get somewhere in the next years but that's not the way to go ofcourse

atonbom:
Yes I just tried it and python shows the following response

Serial port COM5 opened  Baudrate 9600

Waiting for Arduino to reset
Arduino is ready
message sent
message sent

That can't happen with my Tutorial programs because neither the Python code nor the Arduino code says "message sent".

Try again using the code from my Tutorial without any changes

...R

In your python program where you open the serial port, you need some time.sleep() in order to allow the Arduino to reset before starting to communicate over the open port.

time.sleep(2) # wait for the serial connection to initialize

cattledog:
In your python program where you open the serial port, you need some time.sleep() in order to allow the Arduino to reset before starting to communicate over the open port.

time.sleep(2) # wait for the serial connection to initialize

Not a python programmer; but it should be possible to prevent the reset (for boards without native USB) by playing with the DTR signal when you open the port.

sterretje:
Not a python programmer; but it should be possible to prevent the reset (for boards without native USB) by playing with the DTR signal when you open the port.

I doubt if the RESET issue is at the heart of the OP's problem.

...R

Robin2:
That can't happen with my Tutorial programs because neither the Python code nor the Arduino code says "message sent".

Try again using the code from my Tutorial without any changes

...R

Okay I tried your code this time without (any) changes, The only thing I did was select the appropiate com port because otherwise the program didn't run. This is the result I got:

Serial port COM5 opened  Baudrate 115200
Waiting for Arduino to reset
Arduino is ready

Then I also changed the baudrate because the default rate for this board is 9600 but it didn't make any difference. I've waited for 5 more min but nothing happened.

Serial port COM5 opened  Baudrate 9600
Waiting for Arduino to reset
Arduino is ready

atonbom:
Okay I tried your code this time without (any) changes, The only thing I did was select the appropiate com port because otherwise the program didn't run. This is the result I got:

Please post the actual Python and Arduino programs that you used to get that result.

...R

Python Code

import serial
import time

startMarker = '<'
endMarker = '>'
dataStarted = False
dataBuf = ""
messageComplete = False

#========================
#========================
    # the functions

def setupSerial(baudRate, serialPortName):
    
    global  serialPort
    
    serialPort = serial.Serial(port= serialPortName, baudrate = baudRate, timeout=0, rtscts=True)

    print("Serial port " + serialPortName + " opened  Baudrate " + str(baudRate))

    waitForArduino()

#========================

def sendToArduino(stringToSend):
    
        # this adds the start- and end-markers before sending
    global startMarker, endMarker, serialPort
    
    stringWithMarkers = (startMarker)
    stringWithMarkers += stringToSend
    stringWithMarkers += (endMarker)

    serialPort.write(stringWithMarkers.encode('utf-8')) # encode needed for Python3


#==================

def recvLikeArduino():

    global startMarker, endMarker, serialPort, dataStarted, dataBuf, messageComplete

    if serialPort.inWaiting() > 0 and messageComplete == False:
        x = serialPort.read().decode("utf-8") # decode needed for Python3
        
        if dataStarted == True:
            if x != endMarker:
                dataBuf = dataBuf + x
            else:
                dataStarted = False
                messageComplete = True
        elif x == startMarker:
            dataBuf = ''
            dataStarted = True
    
    if (messageComplete == True):
        messageComplete = False
        return dataBuf
    else:
        return "XXX"

#==================

def waitForArduino():

    # wait until the Arduino sends 'Arduino is ready' - allows time for Arduino reset
    # it also ensures that any bytes left over from a previous message are discarded
    
    print("Waiting for Arduino to reset")
    
    msg = ""
    while msg.find("Arduino is ready") == -1:
        msg = recvLikeArduino()
        if not (msg == 'XXX'):
            print(msg)



#====================
#====================
    # the program


setupSerial(9600, "COM5")
count = 0
prevTime = time.time()
while True:
            # check for a reply
    arduinoReply = recvLikeArduino()
    if not (arduinoReply == 'XXX'):
        print ("Time %s  Reply %s" %(time.time(), arduinoReply))
        
        # send a message at intervals
    if time.time() - prevTime > 1.0:
        sendToArduino("this is a test " + str(count))
        prevTime = time.time()
        count += 1

Arduino Code

// This is very similar to Example 3 - Receive with start- and end-markers
//    in Serial Input Basics   http://forum.arduino.cc/index.php?topic=396450.0

const byte numChars = 64;
char receivedChars[numChars];

boolean newData = false;

byte ledPin = 13;   // the onboard LED

//===============

void setup() {
    Serial.begin(9600);

    pinMode(ledPin, OUTPUT);
    digitalWrite(ledPin, HIGH);
    delay(200);
    digitalWrite(ledPin, LOW);
    delay(200);
    digitalWrite(ledPin, HIGH);

    Serial.println("<Arduino is ready>");
}

//===============

void loop() {
    recvWithStartEndMarkers();
    replyToPython();
}

//===============

void recvWithStartEndMarkers() {
    static boolean recvInProgress = false;
    static byte ndx = 0;
    char startMarker = '<';
    char endMarker = '>';
    char rc;

    while (Serial.available() > 0 && newData == false) {
        rc = Serial.read();

        if (recvInProgress == true) {
            if (rc != endMarker) {
                receivedChars[ndx] = rc;
                ndx++;
                if (ndx >= numChars) {
                    ndx = numChars - 1;
                }
            }
            else {
                receivedChars[ndx] = '\0'; // terminate the string
                recvInProgress = false;
                ndx = 0;
                newData = true;
            }
        }

        else if (rc == startMarker) {
            recvInProgress = true;
        }
    }
}

//===============

void replyToPython() {
    if (newData == true) {
        Serial.print("<This just in ... ");
        Serial.print(receivedChars);
        Serial.print("   ");
        Serial.print(millis());
        Serial.print('>');
            // change the state of the LED everytime a reply is sent
        digitalWrite(ledPin, ! digitalRead(ledPin));
        newData = false;
    }
}

//===============

Okay I think I fixed the problem more or at least I increased the response time of my robot. I was reading serial input as a string.

command = Serial.readStringUntil('\n');

I changed to code to:

char command = Serial.read();

And now it looks like responds almost instantly. Are there other ways I could increase the speed? Either from Python side or Arduino side? In the Python code I now serial write using the following code

ser.write(b'a')

And I have seen Robin2 and other tutorials and people also use

ser.write('w'.encode('utf-8'))

Does this influence anything or is just coding style?
And @Robin2 do you have any idea why when I run your program it doesn't behave like it should?

Referring to Reply # 11 ...

Very strange. I don't see why it is not producing "This just in ..." every second.

I'll try to get time to test it again myself later today.

...R

The serial port set up is not correct in my testing. When I change the rtscts setting to False or just leave it out and let a default setting happen, I see the code run as intended. I'm sure some study of the Arduino hardware serial core will explain, but from what I can see the Uno I'm testing on does not support hardware flow control.

#serialPort = serial.Serial(port= serialPortName, baudrate = baudRate, timeout=0, rtscts=True) #This fails
serialPort = serial.Serial(port= serialPortName, baudrate = baudRate, timeout=0, rtscts=False)  #This works
#serialPort = serial.Serial(port= serialPortName, baudrate = baudRate, timeout=0) #This works

atonbom:
Python Code

I just copied your Python and Arduino code and tried it on this Linux laptop and it worked just as expected.

...R

I just copied your Python and Arduino code and tried it on this Linux laptop and it worked just as expected.

@Robin2 are you saying that the code with rtscts = True runs successfully in Linux?

What Arduino is on the other end?

In Windows10, with an Arduino Uno, and Python 3.6.5 and IDLE I can not run your demo with the hardware flow control True.

cattledog:
I'm sure some study of the Arduino hardware serial core will explain, but from what I can see the Uno I'm testing on does not support hardware flow control.

The flow control only effects if the Arduino is reset when opening the serial port on a PC or not. That's not part of the Serial library :wink:

In Win10 using a C# or C++ application, the flow control configured in the application does affect that behaviour.

sterretje:
In Win10 using a C# or C++ application, the flow control configured in the application does affect that behaviour.

Thanks for that information. Maybe it would then be reasonable for a Python3 application in Win10 to also be affected by the flow control.

I know that in the past I tried several times to run @Robin2's demonstration programs without success, but I didn't figure out the reason until I began to dig into @atonbom's failure which I confirmed.

Is there's anyone with experience on a Mac running Robin2's python demo pair?
Other windows versions?

It sounds like the demonstration pair run successfully in Linux but fail in Windows 10. Does anyone know why?

cattledog:
@Robin2 are you saying that the code with rtsctc = True runs successfully in Linux?

What Arduino is on the other end?

In Windows10, with an Arduino Uno, and Python 3.6.5 and IDLE I can not run your demo with the hardware flow control True.

Yes

Uno

I don't use Windows.

What do you mean by "IDLE"

If you are saying that Windows requires rtscts=True then I will mention that when I refer to the example in future. And thank you for pointing it out.

EDIT -- Stupid mistake - I intended to write the following (see Reply #20)
If you are saying that Windows requires rtscts=False then I will mention that when I refer to the example in future. And thank you for pointing it out.

...R