Interfacing with Python

I’m trying to interpret joystick inputs on a PC using PyGame and send those inputs over serial using PySerial. I’ve got code that sends the inputs whenever the joystick moves, but unfortunately I’d like to keep 8 bits for the sake of precision - so how do I let the Arduino know when the data is starting and ending? Should I do something like reserve ‘00’ and ‘FF’ in the Python code? I’m scratching my head over here…

Python code (arduino code is rather trivial at the moment):

import serial  
import pygame
  
# allow multiple joysticks  
joy = []  
# Arduino USB port address (COM3)  
usbport = 2 
# define usb serial connection to Arduino  
ser = serial.Serial(usbport, 115200)
    
# handle joystick event  
def handleJoyEvent(e):  
    if e.type == pygame.JOYAXISMOTION:  
        axis = "unknown"  
        if (e.dict['axis'] == 0):  
            axis = "X"  
        if (e.dict['axis'] == 1):  
            axis = "Y"  
        if (e.dict['axis'] == 2):  
            axis = "Throttle"  
        if (e.dict['axis'] == 3):  
            axis = "Z"  
        if (axis != "unknown"):  
            str = "Axis: %s; Value: %f" % (axis, e.dict['value'])  
            # uncomment to debug  
            #output(str, e.dict['joy'])  
            # Send Servo Data 
            if (axis == "X"):  
                pos = e.dict['value']  
                # convert joystick position to servo increment, 0-180  
                move = round(pos * 90, 0)  
                if (move < 0):  
                    servoX = int(90 - abs(move))  
                else:  
                    servoX = int(move + 90)  
                # convert position to ASCII character  
                servoPositionX = chr(servoX)
                # Send X pos to Arduino
                ser.write(servoPositionX)
                # uncomment to debug  
                print "X:",servoX
                
            if (axis == "Y"):  
                pos = e.dict['value']  
                # convert joystick position to servo increment, 0-180  
                move = round(pos * 90, 0)  
                if (move < 0):  
                    servoY = int(90 - abs(move))  
                else:  
                    servoY = int(move + 90)  
                # convert position to ASCII character  
                servoPositionY = chr(servoY)
                #send to Arduino over serial connection  
                ser.write(servoPositionY)
                # uncomment to debug  
                print "Y:",servoY
                
            if (axis == "Throttle"):  
                pos = e.dict['value']
                move = round((pos+1)*128, 0)
                if (move > 255):
                    move = 255
                if (move < 0):  
                    servoT = int(abs(move))
                else:
                    servoT = int(move)
                
                # convert joystick position to servo increment, 0-180    
                # convert position to ASCII character  
                servoPositionT = chr(servoT)  
                #send to Arduino over serial connection  
                ser.write(servoPositionT)
                # uncomment to debug  
                print "T:",servoT
  
    elif e.type == pygame.JOYBUTTONDOWN:  
        str = "Button: %d" % (e.dict['button'])  
        # uncomment to debug  
        #output(str, e.dict['joy'])  
        # Button 0 (trigger) to quit  
        if (e.dict['button'] == 0):  
            print "Bye!\n"  
            ser.close()  
            quit()  
    else:  
        pass

so how do I let the Arduino know when the data is starting and ending?

There needs to be something unique in the header. So the simplest way would be to send the data as ASCII hex characters and have the header not one of these. Of course there is an overhead with this.

Another way that is not as secure is to have a header at the start and a check sum at the end. In that way if one of the data bytes gets mistaken for the header then the byte that is supposed to be the check sum will not check out.

This is less of an implementation question but more a reflection on my lack of programming knowledge, but say I take the simple route, and say I want to send "255" to the Arduino. The Arduino's serial buffer will have 32,35,35 stored in it. How do I piece those together and form an int value?

To build on that, is there an existing protocol to send 'header','checksum',32,35,35 or something along those lines? I'm not looking to re-invent the wheel here, and it seems like someone would have already done it on the Arduino - without adding the full overhead of TCP protocol.

Thanks for your help!

and say I want to send "255" to the Arduino. The Arduino's serial buffer will have 32,35,35 stored in it.

This is only true if you send the data as a decimal string, you could just write the byte. Those numbers 32,35,35 are in hex and to avoid confusion when writing about them you should say:- 0x32,0x35,0x35

How do I piece those together and form an int value?

assume b1 = 0x32, b2 = 0x35, b3 = 0x35 then

val = b1 * 100 + b2 * 10 + b3

it seems like someone would have already done it on the Arduino

No doubt some one has, but it is so trivial. I have seen several examples posted by PaulS in reply to identical questions.

without adding the full overhead of TCP protocol.

Now you are just being silly.

This is less of an implementation question but more a reflection on my lack of programming knowledge, but say I take the simple route, and say I want to send “255” to the Arduino. The Arduino’s serial buffer will have 32,35,35 stored in it. How do I piece those together and form an int value?

The Arduino’s serial buffer will not contain 32, 35, 35. It will contain 0x32, 0x35, 0x35, or more importantly it will contain ‘2’, ‘5’, ‘5’. There is a subtle but important distinction. That is, I know what ‘2’ means without looking. What the heck does 32 (or 0x32) mean? Without looking, what does 0x54 mean?

There is nothing you can do with the data in the serial buffer except read it.

When you do, there are several ways to convert the string to a number. The easiest is to store the characters read in an array, adding a NULL after each character, then pass the string to atoi() when you have reached the end of the packet.

The hardest part, of course, is knowing when you have reached the end of the packet. Running out of serial data to read does NOT signify the end of the packet.

If you add start- and end-of-packet markers to the string being sent (<255>, for example), you can use code like this to read the whole packet.

#define SOP '<'
#define EOP '>'

bool started = false;
bool ended = false;

char inData[80];
byte index;

void setup()
{
   Serial.begin(57600);
   // Other stuff...
}

void loop()
{
  // Read all serial data available, as fast as possible
  while(Serial.available() > 0)
  {
    char inChar = Serial.read();
    if(inChar == SOP)
    {
       index = 0;
       inData[index] = '\0';
       started = true;
       ended = false;
    }
    else if(inChar == EOP)
    {
       ended = true;
       break;
    }
    else
    {
      if(index < 79)
      {
        inData[index] = inChar;
        index++;
        inData[index] = '\0';
      }
    }
  }

  // We are here either because all pending serial
  // data has been read OR because an end of
  // packet marker arrived. Which is it?
  if(started && ended)
  {
    // The end of packet marker arrived. Process the packet

    // Reset for the next packet
    started = false;
    ended = false;
    index = 0;
    inData[index] = '\0';
  }
}

Where it says “Process the packet”, inData can be sent to atoi(), of the packet consists of just one numeric value.

If it contains multiple delimiter separated values (255, 14, 38), then the strtok() function (NOT strtok_r()) will prove useful.

Thanks, Paul! That's exactly what I was looking for! Question though, if I use the string token library, do I need to add any headers, etc. so that the Arduino compiler will recognize the function? Or is it built in?

Mike, there was only one sentence of your post that helped answer my question. What's the point of posting if all you're going to say is "gosh, I'd help but it's just so trivial"?

What's the point of posting if all you're going to say is "gosh, I'd help but it's just so trivial"?

To show you that it is not an impenetrable problem and if you just get off your back side and ween yourself off the dependent culture of asking for an example of every little detail of code you might just learn something as it is an easy thing to do. I guess I misjudged you.

Question though, if I use the string token library, do I need to add any headers, etc. so that the Arduino compiler will recognize the function? Or is it built in?

The IDE adds some header files that include some other header files that contain the strtok() prototype.

Hey guys I have Python controlling 4 servos on my bread board. Does anyone have experience converting the output from pins 2-x to a mono chord, so i can plug it into the trainer port of my rc transmitter (flysky th9x)?

thanks for your help!

Keith

Grumpy_Mike:

What's the point of posting if all you're going to say is "gosh, I'd help but it's just so trivial"?

To show you that it is not an impenetrable problem and if you just get off your back side and ween yourself off the dependent culture of asking for an example of every little detail of code you might just learn something as it is an easy thing to do. I guess I misjudged you.

Grumpy,

In asking the question, in no way did I ask you to bend over backward and write me a page of code - I didn't even ask for an example! If you look at my post, ALL I asked was if there was a way of doing it without re-inventing the wheel. More specifically, if there was a simple protocol that didn't have all of the overhead of ACKs, FINs and what-have-you found in more common protocols. If you knew of a good library that someone made, or if you knew of a similar post, you could have posted that. If you truly wanted me to figure it out for myself, you could have saved yourself the time of typing "No doubt some one has, but it is so trivial" and not done anything at all.

Forums exist so that people can ask questions, post, and learn (although not necessarily in that order). The success of the Arduino is built on the fact that I don't have to write code to print characters to my LCD - I can just grab a library, and work on more important code. Is it such a sin for me to ask if there's something out there that someone has already done?

Please don't assume someone is lazy because they asked if something already existed - it doesn't necessarily mean they didn't spend hours and hours trying to figure it out, it probably just means they want to know if there's a tried-and-true method out there.

@PerunaPete - be careful who you pick on here. Grumpy_Mike offers a lot of very good advice, and has years of experience behind that advice, and has developed a lot of practical applications using the Arduino.

That he took the time to respond to your question, even if you didn't find the answer completely satisfactory, is to be applauded, not belittled.

There is often not enough information in the original post to indicate the background, experience level, etc. of the poster. When one chooses to answer a question, there is a need to strike a balance between providing a hint and providing the full blown solution.

If the answer contains a hint only, and that hint is not sufficient, a response like "I follow what you are saying, but, could you elaborate on that a bit?" is far better than saying "Well, that wasn't what I was looking for!".

If you need more than a hint, it is best to say so, or to respond to the hint only reply asking what that might look like in code.

If the reply contains a full solution, you might be insulted, or the solution might not really mean anything to you, so you haven't learned a thing.

Striking a balance is difficult. Criticizing someone that tried is not the way to win friends here.

Sorry, I overreacted, I mostly took offense at being told to "get off my backside" after hours of work.

This should probably go in a programming section, but I didn’t really want to start a thread. Anyhow, I tried taking a very basic approach where the host sends a packet with <[Variable][Value]>. At the moment I intend to just spam 3 variables (X,Y,Z), so I took this approach to give myself a simple baseline in the hopes that any errors would quickly get overwritten.

After hitting the start of frame ‘<’, the program is supposed to fill the remaining data into the array, terminating with a null character when it hits the end of frame ‘>’. (very similar to the example)

Where my code differs (and I’m assuming where I screwed up) is inside the brkdwn() function which takes the original array and defines the global variable designated by the packet. The function will interpret the first character as the desired variable, then start adding the following characters to a temporary array. It will stop adding characters into the temporary array until it hits the null terminator added by the EOF function. Another null terminator is added so that the char array will be interpreted as a string (realized that was unnecessary as I typed…), then turned to an int by atoi().

In my initial trials, the Arduino would set all three variables to the same packet’s value (by an extra ‘\0’ somewhere), although now I seem to have broken it completely as it won’t accept any values.

Simple python code for testing:

import serial

# Arduino USB port address (COM3)  
usbport = 2 
# define usb serial connection to Arduino  
ser = serial.Serial(usbport, 115200)

while True:
    n=raw_input('Please enter Arduino Byte\':')
    ser.write(n)
    if n.strip()=='stop':
        ser.close()
        break

Code on the Arduino:

#include <LiquidCrystal.h>
LiquidCrystal lcd(22,24,26,28,30,32,34,36,38,40,42);

#define SOP '<'
#define EOP '>'

bool started = false;
bool ended = false;

byte input = 0;
int LCDcursor =0;
boolean frun =true;

int X=0;
int Y=0;
int Z=0;

char inData[80];
byte index;

void setup()
{
   // initialize LCD
  lcd.begin(16, 2);
  lcd.setCursor(0,0);
  // initialize serial:
  Serial.begin(115200);
}
//Breaks packet down from first char (assumed to be a variable) and
//the following string
void brkdwn(char inData[80],int L)
{
  char a;
  char temp[L];
  int i=0;	
  a = inData[0];	
  switch (a)	
  {		
    case 'X':		
      while(inData[i+1] != '\0')		
      {			
        temp[i]=inData[i+1];			
	i++;		
      }		
      temp[i+1]='\0';		
      X = atoi(temp);		
    case 'Y':
      while(inData[i+1] != '\0')		
      {			
        temp[i]=inData[i+1];			
	i++;		
      }		
      temp[i+1]='\0';		
      Y = atoi(temp);	
    case 'Z':	
      while(inData[i+1] != '\0')		
      {			
        temp[i]=inData[i+1];			
	i++;		
      }		
      temp[i+1]='\0';		
      Z = atoi(temp);
    default:
      Serial.println("invalid input!");
      lcd.setCursor(8,1);
      lcd.print("ERR");	
  }	
}
void loop()
{
  //Print Vars
  lcd.setCursor(0,0);
  lcd.print(X);
  Serial.println("X:");
  Serial.println(X);
  lcd.setCursor(8,0);
  lcd.print(Y);
  Serial.println("Y:");
  Serial.println(Y);
  lcd.setCursor(0,1);
  lcd.print(Z);
  Serial.println("Z:");
  Serial.println(Z);
  
  // Read all serial data available, as fast as possible
  while(Serial.available() > 0)
  {
    char inChar = Serial.read();
    if(inChar == SOP)
    {
       index = 0;
       started = true;
       ended = false;
    }
    else if(inChar == EOP)
    {
       ended = true;
       inData[index] = '\0';
       break;
    }
    else
    {
      if(index < 79)
      {
        inData[index] = inChar;
        index++;
      }
    }
  }

  // We are here either because all pending serial
  // data has been read OR because an end of
  // packet marker arrived. Which is it?
  if(started && ended)
  {
    brkdwn(inData, 8);
    started = false;
    ended = false;
    index = 0;
  }
}

You could combine all the joypad inputs into a single character unless your using analog input.
The single character use binary to send the 4 directions and button hits.
ie:

Nothing hit Ascii @ 01000000
Up Ascii A 01000001
Down Ascii B 01000010
Left Ascii D 01000100
Right Ascii H 01001000
Button A Ascii P 01010000
Button B Ascii ` 01100000

So Right + Up + Button A = 01011001 or Ascii Y

Sending a single character should be easier and faster.

But it looks like you are using Analog. :o So disregard that. :slight_smile:

Maybe you could put a print in your Py code to see what your sending, you could be passing number from 128 to -128 for byte sized data.
Your Py code may be flooding the serial because even zero movement is sending data to the serial, you need to add the <> codes so you can see where one packet stops and another starts,
also your arduino code wont be adding the /0 as it doest find a > that’s if it manages to escape the while(Serial.available() > 0)

    case 'X':		
      while(inData[i+1] != '\0')		
      {			
        temp[i]=inData[i+1];			
	i++;		
      }		
      temp[i+1]='\0';		
      X = atoi(temp);

Lets take a look at one example. Supposed that Python transmitted “<X10, Y20, Z30>”. If that is the case, inData will contain “X10, Y20, Z30”. a will be assigned the value ‘X’, so the first case will be triggered.

I hope that you are already shaking your head, saying “that isn’t going to work…”.

So, let’s suppose that Python transmitted just “”. If that is the case, inData will contain “X10”. a will be assigned the value ‘X’, so the first case will be triggered.

i is set to 0.
inData[1] is not NULL, so temp[0] is set to ‘1’, and i is incremented to 1.
inData[2] is not NULL, so temp[1] is set to ‘0’, and i is incremented to 2.
inData[3] is NULL, so the while loop ends and temp[3] is set to NULL.

So, what is in temp[2]?

To clarify, when I said I would just spam the variable values, I meant that the end-state Python code would send <X[value]> <Y[value]> <Z[value]> and so forth.

It should also be worth mentioning (in case someone else would like to build on what’s in the thread) that the Python code will send data if the joystick values CHANGE. For instance, if the joystick is sitting still, nothing would be transmitted (ideally). In reality, the values will most likely be hovering between one value and another, so you would see over and over, even with no intended user input.

I’m not an expert on how the processor would actually behave, but at the time it seemed logical to only update variables if a new one came along. Like everything else, there will probably be enough errors from non-ideal packets that I’ll need to implement a more sophisticated protocol, but hopefully I can get this working first.

Thanks for the catch, Paul! When I get home we’ll see if that’s all there is to it.

Fixed! In addition to mis-addressing my array, I also forgot break statements. Finished (aside from some general cleanup) code for Arduino/Python below.

Python Code:

import serial  
import pygame
  
# allow multiple joysticks  
joy = []  
# Arduino USB port address (COM3)  
usbport = 2 
# define usb serial connection to Arduino  
ser = serial.Serial(usbport, 115200)
    
# handle joystick event  
def handleJoyEvent(e):  
    if e.type == pygame.JOYAXISMOTION:  
        axis = "unknown"  
        if (e.dict['axis'] == 0):  
            axis = "X"  
        if (e.dict['axis'] == 1):  
            axis = "Y"  
        if (e.dict['axis'] == 2):  
            axis = "Throttle"  
        if (e.dict['axis'] == 3):  
            axis = "Z"  
        if (axis != "unknown"):  
            str = "Axis: %s; Value: %f" % (axis, e.dict['value'])  
            # uncomment to debug  
            #output(str, e.dict['joy'])  
            # Send Servo Data 
            if (axis == "X"):  
                pos = e.dict['value']  
                # convert joystick position to servo increment, 0-180  
                move = round(pos * 90, 0)  
                if (move < 0):  
                    servoX = int(90 - abs(move))  
                else:  
                    servoX = int(move + 90)  
                # convert position to ASCII character  
                servoPositionX = chr(servoX)
                # Send X pos to Arduino
                ser.write('<')
                ser.write('X')
                ser.write(servoX)
                ser.write('>')
                # uncomment to debug  
                print "X:",servoX
                
            if (axis == "Y"):  
                pos = e.dict['value']  
                # convert joystick position to servo increment, 0-180  
                move = round(pos * 90, 0)  
                if (move < 0):  
                    servoY = int(90 - abs(move))  
                else:  
                    servoY = int(move + 90)  
                #send to Arduino over serial connection
                ser.write('<')
                ser.write('Y')
                ser.write(servoY)
                ser.write('>')
                # uncomment to debug  
                print "Y:",servoY
                
            if (axis == "Throttle"):  
                pos = e.dict['value']
                move = round((pos+1)*128, 0)
                if (move > 255):
                    move = 255
                if (move < 0):  
                    servoT = int(abs(move))
                else:
                    servoT = int(move)
                
                #send to Arduino over serial connection
                ser.write('<')
                ser.write('Z')
                ser.write(servoT)
                ser.write('>')
                # uncomment to debug  
                print "T:",servoT
  
    elif e.type == pygame.JOYBUTTONDOWN:  
        str = "Button: %d" % (e.dict['button'])  
        # uncomment to debug  
        #output(str, e.dict['joy'])  
        # Button 0 (trigger) to quit  
        if (e.dict['button'] == 0):  
            print "Bye!\n"  
            ser.close()  
            quit()  
    else:  
        pass  
  
# print the joystick position  
def output(line, stick):  
    print "Joystick: %d; %s" % (stick, line)  
  
# wait for joystick input  
def joystickControl():  
    while True:  
        e = pygame.event.wait()  
        if (e.type == pygame.JOYAXISMOTION or e.type == pygame.JOYBUTTONDOWN):  
            handleJoyEvent(e)  
  
# main method  
def main():  
    # initialize pygame  
    pygame.joystick.init()  
    pygame.display.init()  
    if not pygame.joystick.get_count():  
        print "\nPlease connect a joystick and run again.\n"  
        quit()  
    print "\n%d joystick(s) detected." % pygame.joystick.get_count()  
    for i in range(pygame.joystick.get_count()):  
        myjoy = pygame.joystick.Joystick(i)  
        myjoy.init()  
        joy.append(myjoy)  
        print "Joystick %d: " % (i) + joy[i].get_name()  
    print "Depress trigger (button 0) to quit.\n"
    # run joystick listener loop  
    joystickControl()  
  
# allow use as a module or standalone script  
if __name__ == "__main__":  
    main()

Arduino:

#define SOP '<'
#define EOP '>'

bool started = false;
bool ended = false;

byte input = 0;
int LCDcursor =0;
boolean frun =true;

int X=0;
int Y=0;
int Z=0;

char inData[80];
byte index;

void setup()
{
   // initialize LCD
  lcd.begin(16, 2);
  lcd.setCursor(0,0);
  // initialize serial:
  Serial.begin(115200);
}
void brkdwn(char inData[80],int L)
{
  char a;
  char temp[L];
  int i=0;	
  a = inData[0];	
  switch (a)	
  {		
    case 'X':		
      while(inData[i+1] != '\0')		
      {			
        temp[i]=inData[i+1];			
	i++;		
      }		
      temp[i]='\0';		
      X = atoi(temp);
      break;		
    case 'Y':
      while(inData[i+1] != '\0')		
      {			
        temp[i]=inData[i+1];			
	i++;		
      }		
      temp[i]='\0';		
      Y = atoi(temp);
      break;	
    case 'Z':	
      while(inData[i+1] != '\0')		
      {			
        temp[i]=inData[i+1];			
	i++;		
      }		
      temp[i]='\0';		
      Z = atoi(temp);
      break;
    default:
      Serial.println("invalid input!");
      lcd.setCursor(8,1);
      lcd.print("ERR");	
  }	
}
void loop()
{
  //Print Vars
  //Serial.print("Buffer:");
  //Serial.println(Serial.available());
  lcd.setCursor(0,0);
  lcd.print(X);
  //Serial.println("X:");
  //Serial.println(X);
  lcd.setCursor(8,0);
  lcd.print(Y);
  //Serial.println("Y:");
  //Serial.println(Y);
  lcd.setCursor(0,1);
  lcd.print(Z);
  //Serial.println("Z:");
  //Serial.println(Z);
  
  // Read all serial data available, as fast as possible
  while(Serial.available() > 0)
  {
    char inChar = Serial.read();
    if(inChar == SOP)
    {
       index = 0;
       started = true;
       ended = false;
    }
    else if(inChar == EOP)
    {
       ended = true;
       inData[index] = '\0';
       break;
    }
    else
    {
      if(index < 79)
      {
        inData[index] = inChar;
        index++;
      }
    }
  }
  if(started && ended)
  {
    brkdwn(inData, 8);
    started = false;
    ended = false;
    index = 0;
  }
}