Search code examples
pythonarduinoserial-portdecode

Struggling when processing data from Serial Port (Arduino) with Python


I'm trying to move my cursor with a joystick connected to an Arduino Uno through a Python script since this board doesn't have a library for moving mouse as the Arduino Leonardo has. Therefore I need help with the issue I present below. You can see how the code works and were the issue is the comments of my code. Please, I'll thank a solution.

import serial
import pyautogui
import time
#I import the libraries.
arduino_port="/dev/ttyACM0" #Serial port for comunication(ubuntu).
baud=9600#frecuency

ser=serial.Serial(arduino_port, baud)# Connect to /dev/ttyACM0 at 9600 bauds
print("Connected to the arduino port at ", baud, "bauds") # Confirm connection

while True:#I'm using a while because I want it to run while the program is running, forever.
    data=ser.readline().decode('utf-8').rstrip() # Decode the data printed by arduino in the serial port.
    xyValues=data.split(",")# creates an array like this: ['x','y']
    final_xyValues=[]# declaration of a new array
    for i in xyValues:
        final_xyValues.append(int(i))
    #When the loop has finished, I have this array: [x, y], where x and y are integers.
    print(final_xyValues)
    #to check the problem uncomment the code below:
    """
    if len(final_xyValues)==2:
        pyautogui.moveRel(final_xyValues[0], final_xyValues[1])
        time.sleep(0.2)
    """

"""
Here is where I need help. I want to move the cursor with the values that
the program is constantly receiving and converting to an array called
final_xyValues. The array final_xyValues's first value is the x axis and the
second the y axis.

Take in count that the variable data is a string like this: '405,678', which is
transform to an array with two integers as values. Now, you must know that the values
of final_xyValues are from an analog input of a joystick connected to an Arduino Uno
and read in from the serial port. I want to move the cursor with the analog inputs of the
joystick.

ISSUE: when I uncomment the code from line 21 to 25 the first values of the joystick x and y
position in that moment are ok but after that the data continues identical to the first values
read. The values are correctly and constantly been updated if I comment this part of the code,
if I move the joystick the data changes.
"""

You can see the issue with the values of the joystick in the last comment of the python code below. Where it says 'ISSUE'. I've been looking for a solution several days and nothing worked.

#define VRX_PIN  A0 // Arduino pin connected to VRX pin
#define VRY_PIN  A1 // Arduino pin connected to VRY pin

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

void loop() {
  int xValue = analogRead(VRX_PIN);//Read the analog value of A0 pin, x axis.
  int yValue = analogRead(VRY_PIN);//Read the analog value of A1 pin, y axis.
  // read analog X and Y analog values
  Serial.print(xValue);
  Serial.print(",");
  Serial.println(yValue);
  //Result: 'x,y'
}

Solution

  • @michael-butscher wrote a good comment:

    Just guessing: The PC only reads five values per second (due to the sleep) but the Arduino sends much more so they are buffered and old ones are processed by PC. Solutions: Create a thread on PC side which reads the values constantly and provides only the most current one to the main thread after each sleep. Or you reduce the Arduino output by a sleep on this side.

    @michael-butscher is illuminating the fact that in a brief one second of time, you may have one hundred lines of 0,0 or 511,510, or whichever happens to be the analog voltage reading. E.g.,

    511,510
    511,510
    511,510
    511,510
    511,510
    511,510
    511,510
    511,510
    511,510
    511,510
    511,510
    511,510
    511,510
    ...
    

    This is because the Arduino loops very quickly, and will quickly flood your pyserial input buffer with the same value.

    Your Python loop, on the other hand, loops very slowly (compared to the Arduino). So it will be taking its time consuming each line, one by one, once every 0.2 seconds. So it's just going to be stuck at the beginning of your Serial input, reading these many repeated initial values.

    You don't need to remove the time.sleep(0.2) delay. I agree, the improved readability when the Python loop is slowed down to a manageable speed is helpful.

    A simple solution in my opinion is, on the Python side, to clear the serial input buffer when you're ready to read a new measurement reported by the Arduino.

    I recommend you try the following infinite while loop:

    while True:
    
        # Clear entire serial input buffer
        ser.reset_input_buffer()
    
        # Read and ignore the next line.
        # This solves the issue that if you start reading now, you 
        # may be starting in the middle of a line. So we'll discard
        # this possibly incomplete line.
        ser.readline()
    
        data=ser.readline().decode('utf-8').rstrip()
        print(data)
    
        # 'data' is now guaranteed to be a most recent, complete
        # line of measurement data from the Arduino.
    
        time.sleep(0.2)
    
    

    You should notice the output now always showing the latest analog measurements reported by the Arduino. Once this is confirmed, you can do the raw data line parsing you were doing earlier (cast to int, checking array size, etc.).