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()
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
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
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.
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.
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?
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.
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
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.