I'm trying to track the position, along one axes, of an object using a motion tracker and send to arduino to be displayed using a linear motor.
In other words if the object is moved up the probe of the linear motor will be extended, in the other case will be retracted.
The problem is that when I integrate the serial communication of arduino in the tracking program that read the position from the motion tracker, the latency of the communication is way to high.
For some preliminary test i would like to have the latency to be, at least, less than a second.
The code in python is related to another function called NatNetClient.py that setup the communication with the motive tracker system.
If someone is familiar with this kind of program for tracking could kindly suggest me the best way to integrate a communication with arduino ?
I'm using an arduino Uno.
I couldn't attach the entire python program, so here is a snippet of the communication (based on the one provided by Robin2 here )
def receiveRigidBodyFrame( id, position, rotation ):
values=MapPointMotionTracker(position) #mapping the values from the motion tracker to a scale 0-50 for the linear motor
Values.x_t1=values[1]*Values.alpha+(1-Values.alpha)*Values.x_tp #exponential filtering of the data
pos=[]
Values.x_tp=Values.x_t1
if (Values.x_t1>=50):
pos.append("<Linear,"+str(50)+">")
elif (Values.x_t1<=0):
pos.append("<Linear,"+str(0)+">")
else:
pos.append("<Linear,"+str(int(round(Values.x_t1)))+">")
print( "POSITION FROM THE MOTION TRACKER : ", position[1], "POSITION SMOOTHED", Values.x_t1, "VALUE NOT SMOOTHED", values[1])
if(round(values[1])==Values.positionOld):
runTest(pos)
I have a project that uses essentially the same code and Python receives messages instantly - and lots of them if I let my Arduino program run fast.
You need to measure the time in different parts of your program to determine where the sloth is. IMHO you should not have any calculations in the function that receives messages. Do the calculations after the message has been received.
sterretje:
Why do you use a Serial.println(">") and not a Serial.print(">")? The former just sends extra stuff that should be ignored by the Python side.
i agree, it does not matter to Python. However if, for debugging purposes, you show the output on the Arduino Serial Monitor the linefeed makes things tidier.
Robin2:
I have a project that uses essentially the same code and Python receives messages instantly - and lots of them if I let my Arduino program run fast.
Thank you for your prompt answer and for providing a new example. I will integrate it in the program and see if something changes.
Probably the latency is due to the way the program of the motion tracking call the communication with arduino.
The python function that I shared is called continuously, and creates a "pos" list every time for a single position and calls "runTest". It is possible that using your program in this way (instead of creating only a list of multiple values) can generate some delay?
Also when testing I noticed that the movement that arduino does in the setup, are overshoot when is reset from the python script. I doesn't make much sense to me, in your knowledge is it possible ?
Robin2:
IMHO you should not have any calculations in the function that receives messages. Do the calculations after the message has been received.
Are you talking about the arduino script or in the python one ?
berto906:
Also when testing I noticed that the movement that arduino does in the setup, are overshoot when is reset from the python script. I doesn't make much sense to me, in your knowledge is it possible ?
Not sure what you're trying to say.
Seeing the word reset, the Uno is reset when you open the serial port. If you open the port continuously, you will have plenty resets of the Arduino. In general, it can be prevented by not touching DTR when you open the port; no idea how to do so in Python.
Seeing the word reset, the Uno is reset when you open the serial port. If you open the port continuously, you will have plenty resets of the Arduino. In general, it can be prevented by not touching DTR when you open the port; no idea how to do so in Python.
Sorry, my bad, I wasn't clear enough. When I run the python script it resets the arduino, the same way if I pushed the reset button.
Anyway this does not matter anymore, it didn't shown that behavior again. Probably I changed accidentally the value.
berto906:
The python function that I shared is called continuously, and creates a "pos" list every time for a single position and calls "runTest". It is possible that using your program in this way (instead of creating only a list of multiple values) can generate some delay?
I don't understand. I thought the purpose of the Python code was to receive data from the Arduino and. if so, I would only expect it to update when a new message is received.
Also when testing I noticed that the movement that arduino does in the setup, are overshoot when is reset from the python script. I doesn't make much sense to me, in your knowledge is it possible ?
When a PC program opens the serial port it normally causes the Arduino to reset. Your program should allow time for that and then keep the serial port open.
Are you talking about the arduino script or in the python one ?
Both, if appropriate. A "receive" function should do nothing but receive.
Robin2:
I don't understand. I thought the purpose of the Python code was to receive data from the Arduino and. if so, I would only expect it to update when a new message is received.
The objective is to send to arduino a number that correspond with the position of the object tracked with the motion tracker.
During a bit of troubleshooting today I found out that neither the program to retrieve the position or the communication with arduino created delay by itself. Although togheter they caused the position to be read with a big delay.
But using the script you shared on the second answer, to send the position to arduino solved the problem.
Now I have no delay in the communication
I seem to be late to the party, but it's honestly faster and more reliable to interface Python with your Arduino using compatible libraries such as pySerialTransfer and SerialTransfer.h.
pySerialTransfer is pip-installable and cross-platform compatible. SerialTransfer.h runs on the Arduino platform and can be installed through the Arduino IDE's Libraries Manager.
Both of these libraries have highly efficient and robust packetizing/parsing algorithms with easy to use APIs.
Example Python Script:
Note that while this script may seem daunting, it's meant to show the flexibility of the library while your particular script doesn't need to be as complex.
import time
from pySerialTransfer import pySerialTransfer as txfer
if __name__ == '__main__':
try:
link = txfer.SerialTransfer('COM17')
link.open()
time.sleep(2) # allow some time for the Arduino to completely reset
while True:
send_size = 0
###################################################################
# Send a list
###################################################################
list_ = [1, 3]
list_size = link.tx_obj(list_)
send_size += list_size
###################################################################
# Send a string
###################################################################
str_ = 'hello'
str_size = link.tx_obj(str_, send_size) - send_size
send_size += str_size
###################################################################
# Send a float
###################################################################
float_ = 5.234
float_size = link.tx_obj(float_, send_size) - send_size
send_size += float_size
###################################################################
# Transmit all the data to send in a single packet
###################################################################
link.send(send_size)
###################################################################
# Wait for a response and report any errors while receiving packets
###################################################################
while not link.available():
if link.status < 0:
if link.status == -1:
print('ERROR: CRC_ERROR')
elif link.status == -2:
print('ERROR: PAYLOAD_ERROR')
elif link.status == -3:
print('ERROR: STOP_BYTE_ERROR')
###################################################################
# Parse response list
###################################################################
rec_list_ = link.rx_obj(obj_type=type(list_),
obj_byte_size=list_size,
list_format='i')
###################################################################
# Parse response string
###################################################################
rec_str_ = link.rx_obj(obj_type=type(str_),
obj_byte_size=str_size,
start_pos=list_size)
###################################################################
# Parse response float
###################################################################
rec_float_ = link.rx_obj(obj_type=type(float_),
obj_byte_size=float_size,
start_pos=(list_size + str_size))
###################################################################
# Display the received data
###################################################################
print('SENT: {} {} {}'.format(list_, str_, float_))
print('RCVD: {} {} {}'.format(rec_list_, rec_str_, rec_float_))
print(' ')
except KeyboardInterrupt:
link.close()
except:
import traceback
traceback.print_exc()
link.close()
Example Arduino Sketch:
#include "SerialTransfer.h"
SerialTransfer myTransfer;
void setup()
{
Serial.begin(115200);
myTransfer.begin(Serial);
}
void loop()
{
if(myTransfer.available())
{
// send all received data back to Python
for(uint16_t i=0; i < myTransfer.bytesRead; i++)
myTransfer.txBuff[i] = myTransfer.rxBuff[i];
myTransfer.sendData(myTransfer.bytesRead);
}
}
On the Arduino side, you can use myTransfer.txObj() and myTransfer.rxObj() to copy values to the library's RX buffer and parse multi-byte variables out of the library's TX buffer.
Power_Broker:
I seem to be late to the party, but it's honestly faster and more reliable to interface Python with your Arduino using compatible libraries such as [...]
Never too late! I was just trying to improve the program. Now works really good for my need, but after 20 seconds or so, it starts slowing down and flooding the data all together, every now and then.
I need just a script to send a number (int) to arduino continuously with an interval as little as possible. I'm not that used to parsing or with the library you have mentioned. There is some more material I could use to implement it in my program? Especially for the arduino side?
berto906:
Now works really good for my need, but after 20 seconds or so, it starts slowing down and flooding the data all together, every now and then.
I need just a script to send a number (int) to arduino continuously with an interval as little as possible.
It sounds as if you are sending data more frequently than the Arduino is capable of handling. You need to match the rate at which you send data with the speed at which the receiver can process the incoming data.
Please post the latest version of your Arduino program (the complete program) and tell us how often the Python program sends messages.
Robin2:
It sounds as if you are sending data more frequently than the Arduino is capable of handling. You need to match the rate at which you send data with the speed at which the receiver can process the incoming data.
Please post the latest version of your Arduino program (the complete program) and tell us how often the Python program sends messages.
...R
The python program send data every 0.07 seconds, and send a string. Arduino then convert this string in to an int and move a linear actuator into the position.
All the millis along the program is due to the fact that the linear motor has no encoder, is a way to keep track of the position of the probe.
Power_Broker:
I seem to be late to the party, but it's honestly faster and more reliable to interface Python with your Arduino using compatible libraries such as [...]
I have tried the examples you sent, but on the python terminal nothing is printed. It doesn't output any errors, but it doesn't work either.
berto906:
The python program send data every 0.07 seconds, and send a string. Arduino then convert this string in to an int and move a linear actuator into the position.
#include <Wire.h>
#include <Adafruit_ADS1015.h>
#include <Servo.h>
Adafruit_ADS1115 ads1115(0x48);
Servo Squeezer; //name of the servo motor
//unsigned long initMillis; //time at which the linear actuator has started moving
unsigned long posMillis; //millis for the start of the movement of the linear actuator
unsigned long initMillis; //time at which the linear actuator has started moving
unsigned long currentMillis;
unsigned long stepMillis; //time of the precedent counted movement of the linear actuator
boolean newposition=false; //bool for a new position
boolean moving=false; //bool for the motion of the probe
boolean extend=false;
boolean retract=false;
float posC=0; // the actual position of the actuator
int flag =0; //variable for LinearActuation Function
int oldPos=0; //variable to store the position
int position=0; //required position
//int PWM1=3; //Pin to control the DC motor
int PWML1=5;//PWM OF THE LINEAR MOTOR (RED)
int PWML2=6;//GND OF THE LINEAR MOTOR (BLACK)
int Spin=9;//squeezer pin
int NewMotorValue= 0;
int16_t results; //variable for the force sensor
//variable needed for communication
const byte numChars = 64;
char receivedChars[numChars];
boolean newData = false;
byte ledPin = 13; // the onboard LED
const unsigned long period =138; //number in millisecond corresponding to 1 mm
boolean toend=false;
//===========
void setup() {
Serial.begin(115200);
//pinMode(PWM1,OUTPUT);
pinMode(PWML1,OUTPUT);
pinMode(PWML2,OUTPUT);
ads1115.begin();
Squeezer.attach(9); //the pin the servo motor is attached to has to be PWM
// flash LEDs so we know we are alive
//for (byte n = 0; n < numLEDs; n++) {
// pinMode(ledPin[n], OUTPUT);
// digitalWrite(ledPin[n], HIGH);
//}
digitalWrite(PWML2,HIGH);
digitalWrite(PWML1,LOW);
Serial.println("<Retracting the probe... please wait>");
delay(8000); // delay() is OK in setup as it only happens once
digitalWrite(PWML2,LOW);
posC=25; // i position the probe to the middle point to avoid initial overshoot
Serial.println("<Moving the probe to middle point... please wait>");
digitalWrite(PWML1,HIGH);
currentMillis=millis();
while (millis()-currentMillis<=25*period){
digitalWrite(PWML1,HIGH);
}
digitalWrite(PWML1,LOW);
Serial.println("<Arduino is ready>");
initMillis=millis();
}
//==========
void loop() {
currentMillis = millis();
//Position();
recvWithStartEndMarkers();
updateMotorSpeed(); //this order is important, this has to stay between this two functions
replyToPython();
readForceSensor();
Squeezing();
updateLinear(); //has to be called continuously to stop the probe
UpdatePosLin();
}
//=============
void Position(){ //used for debugging
if (currentMillis-posMillis>100)
{
Serial.print("POSITION REQUIRED : ");
//receivedChars =random(0,50);
//postion=random(0,50)
Serial.println(position);
newposition=true;
posMillis=millis();
}
}
//=============
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;
}
/*if (newData==true){
newData=false;
Serial.println("It'a all false now");
}*/
}
//=============
void updateMotorSpeed() {
// this illustrates using different inputs to call different functions
//if (strcmp(messageFromPC, "Motor1") == 0) {
// updateMotor1();
//}
//Serial.println("inside the control of motor");
if (newData == true) {
position=atoi(receivedChars);
newposition=true;
updateLinear();
}
}
//=============
void updateLinear(){ // given a position (receivedChars) from 0 to 50 have to move the linear motor accordingly
if((int(posC)!=position)&&(newposition)){
int buf=posC-position;
//oldPos=position;//store the position that has to be reached
//Serial.println("There is a new position received ");
//Serial.print("LENGTH OF MOVEMENT : ");
//Serial.println(buf);
if(buf<0){
digitalWrite(PWML2,LOW);
digitalWrite(PWML1,HIGH);
//Serial.println("MI ESTENDO");
initMillis=currentMillis;//initMillis can actually be deleted
stepMillis=initMillis;
//endMillis=initMillis+period*abs(buf); //time at which the probe has to stop used in the previous version, can be used to doulbe check
newposition=false;
moving=true;
extend=true;
retract=false;
}
if(buf>0){
digitalWrite(PWML2,HIGH);
digitalWrite(PWML1,LOW);
//Serial.println("MI ACCORCIO");
initMillis=currentMillis;
stepMillis=initMillis;
//endMillis=initMillis+period*abs(buf); //time at which the probe has to stop
newposition=false;
moving=true;
retract=true;
extend=false;
}
}
//if((currentMillis>=endMillis)&&(moving)){
if(int(posC)==position){
digitalWrite(PWML2,LOW);
digitalWrite(PWML1,LOW);
newposition=false;
moving=false;
extend=false;
retract=false;
//pos=oldPos;
//Serial.print("STOPPED THE PROBE, THE POSITION IS: ");
//Serial.println(posC);
}
}
//============
void UpdatePosLin(){
currentMillis=millis();// to test what changes to have it here instead of before the function call in the loop
float increment;
if((int(posC)!=position)&&(moving)){ //moving condition is sufficient probably
if(extend){
increment= ((float)currentMillis-(float)stepMillis)/period;
posC=posC+increment; // the position is going toward 50
stepMillis=currentMillis;
}
if(retract){
increment= ((float)currentMillis-(float)stepMillis)/period;
posC=posC-increment; //the position is going toward 0
stepMillis=currentMillis;
}
//Serial.print("AGGIORNO LA POSIZIONE CON IL CALCOLOLO: ");
//Serial.println(posC);
//Serial.print("L'incremento vale : ");
//Serial.println(increment);
}
}
//============
void readForceSensor(){
results = ads1115.readADC_Differential_0_1()/5.33;//the fraction is used for calibration purposes (same number on the amplifier and the arduino)
//Serial.print("Differential: "); Serial.print(results); Serial.print("("); Serial.print(results * 3); Serial.println("mV)");
}
void Squeezing(){ //given the value of the force sensor map the value on the range of the servo and moves it
int val = map(results, 0, 600, 0, 180); //the first two numbers should be the range of the force sensor
Squeezer.write(val);
}
Robin2:
I would like to see one or two examples of what is actually sent.
It is not clear to me what do you mean by "what is actually sent".
The function that compute the position of the object looks like this :
if(time.time()-Values.prevTime>0.05): #this regulates the delay beetween two data trasmission to arduino
Values.x_tp=Values.x_t1
Values.prevTime=time.time()
if (Values.x_t1>=50)and(50!=Values.positionOld):
sendToArduino(str(50))
Values.positionOld=50
elif (Values.x_t1<=0)and(0!=Values.positionOld): #to guarantee not to enter here twice
sendToArduino(str(0))
Values.positionOld=0
else:
if(round(values[1],8)!=round(Values.positionOld,8)):
sendToArduino(str(int(round(Values.x_t1))))
Values.positionOld=(round(Values.x_t1,6))# update the position values for the new iteration
Values.x_t1 is a float number ranging (usually) from 0 to 50, but with the two if condition inside I ensure that only 0-50 int values are sent.
The function sendToArduino is the same as in the example you shared:
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