Sending float values from Python to Arduino via serial... Stumped

Issue has been resolved. Thanks guys. See post: Sending float values from Python to Arduino via serial... Stumped - #13 by Toasty3000 - Project Guidance - Arduino Forum

Hi all, I've hit a roadblock in my latest project.

Short version: I'm trying to receive two float values via usb serial on my Tinyduino, formatted as follows: <01234.56,10987.65>
I'm trying to get my sketch to read these two floats, and update variables with them. (e.g. var1 = 1st float, and var2 = 2nd float)

I'm really not sure how to do this. I have no experience working with serial communication code, and limited experience with Arduino in general. Learning slowly but surely.

Long (detailed) version:
As I mentioned above, I'm sending two floats via serial com port. I'm trying to create a satellite 'pointer,' so I have a Python program running calculations on telemetry data I input, and that program spits out azimuth and elevation in the form of a stepper motor position (# of steps from 0). I then have it write those positions to Arduino via a preselected com port, and it does this every 5 sec to update the positions.

I found some code here that I tried flexing to work with what I have, but I seem to have failed.
I start up Arduino and my Python program, establish the serial connection, confirm the connection, and then Python starts sending data. The problem I have is that my steppers don't move at all. To confirm the problem was somewhere within the serial connection and/or string parsing, I set the position variables to an arbitrary number from within the sketch, which works.

Here is my Python code:

import serial
import time
import msvcrt
import math
from datetime import datetime
import ephem
import struct


COMPORT = int(input("Port number: "))
arduino = serial.Serial("COM{}".format(COMPORT), 9600, timeout = 1)
arduino.port = "COM{}".format(COMPORT)
print ("Opening Serial port...")
time.sleep(2)
print (arduino.readline())
time.sleep(2)
print ("Initialization complete")

degrees_per_radian = 180.0 / math.pi
 
home = ephem.Observer()
home.lon = '-110.031'   # +E
home.lat = '39.978'      # +N
home.elevation = 80 # meters
 
# Always get the latest ISS TLE data from:
# http://spaceflight.nasa.gov/realdata/sightings/SSapplications/Post/JavaSSOP/orbit/ISS/SVPOST.html
iss = ephem.readtle('ISS',
    '1 25544U 98067A   17119.54718229  .00016717  00000-0  10270-3 0  9015',
    '2 25544  51.6378 280.7424 0005784 109.0002 251.1778 15.53903632 14187'
)
 
while True:
    home.date = datetime.utcnow()
    iss.compute(home)
    print('iss: altitude %4.1f deg, azimuth %5.1f deg' % (iss.alt * degrees_per_radian, iss.az * degrees_per_radian))
    azi = iss.az * degrees_per_radian
    elev = iss.alt * degrees_per_radian
    azipc = azi / 360
    elevabsolute = elev + 90
    elevpc = elevabsolute / 180
    azistp = str(round(azipc * 15753.846153846153846153846153846, 2)).zfill(8)
    elevstp = str(round(elevpc * 7876.923076923076923076923076923, 2)).zfill(8)
    command = '<'
    command += elevstp
    command += ','
    command += azistp
    command += '>'
    arduino.write(b'command')
    print (command)
    print (arduino.readline())
    time.sleep(5)

Here is my Arduino sketch. Unless there's an obvious problem with my Python code, the problem is in here somewhere. Most likely in the segment that's supposed to parse the data.

#include <AccelStepper.h>
#include <string.h>
#include <stdio.h>
#define HALFSTEP 8

// Motor pin definitions
#define motorPin1  8     // IN1 on the ULN2003 driver 1
#define motorPin2  7     // IN2 on the ULN2003 driver 1
#define motorPin3  5     // IN3 on the ULN2003 driver 1
#define motorPin4  6     // IN4 on the ULN2003 driver 1
#define motorPin5  9     // IN1 on the ULN2003 driver 2
#define motorPin6  12     // IN2 on the ULN2003 driver 2
#define motorPin7  11     // IN3 on the ULN2003 driver 2
#define motorPin8  10     // IN4 on the ULN2003 driver 2

// Initialize with pin sequence IN1-IN3-IN2-IN4 for using the AccelStepper with 28BYJ-48
AccelStepper stepper1(HALFSTEP, motorPin1, motorPin3, motorPin2, motorPin4);
AccelStepper stepper2(HALFSTEP, motorPin5, motorPin7, motorPin6, motorPin8);

//#define SOP '<' ////ignore these
//#define EOP '>' ////ignore these
//#define MID ','  ////ignore these
float elevation = 03100.73;
float azimuth = 02502.54;
const byte numChars = 19;
char receivedChars[numChars];
char tempChars[numChars];
char dataFromPC[numChars] = {0};
float elevFromPC = 0.0;
float aziFromPC = 0.0;
boolean newData = false;

void setup() {
  Serial.begin(9600);
  delay(250);
  Serial.write("Arrrre you ready kids?"); //Sends a message to PC to confirm connection
  delay(1000);
  stepper1.setMaxSpeed(1000.0);
  stepper1.setAcceleration(500.0);
  stepper1.setSpeed(500);
  stepper2.setMaxSpeed(1000.0);
  stepper2.setAcceleration(500.0);
  stepper2.setSpeed(500);

}

///// ============== Somewhere below is the problem =============================================
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';
        recvInProgress = false;
        ndx = 0;
        newData = true;
      }
    }
    else if (rc == startMarker) {
      recvInProgress = true;
    }
  }
}

void parseData() {
  char * strtokIndx;
  strtokIndx = strtok(tempChars, ",");
  elevFromPC = atof(strtokIndx);
  strtokIndx = strtok(NULL, ",");
  aziFromPC = atof(strtokIndx);
}

void showParsedData() {
  Serial.println(elevation);
  Serial.println(azimuth);
}
////////////// ========================== Somewhere above is the problem ==============================

void loop() { //   \/ This part of the code works beautifully when I set elevation or azimuth = 12345.67
  elevation = elevFromPC;
  azimuth = aziFromPC;
  stepper1.moveTo(azimuth);
  if (stepper1.distanceToGo() != 0) {
    stepper2.moveTo(azimuth);
  }
  if (stepper1.distanceToGo() == 0) {
    stepper2.moveTo(elevation);
  }
  stepper1.run();
  stepper2.run();
}

Anything will help at this point, even if it's a general pointer in the right direction. I'm lost

You never call recvWithStartEndMarkers in loop().

Ah, I was hopeful that would fix it. Sadly not. Same problem, no movement from the steppers, and still getting a blank message back

This is what you mean by calling it, right? Sorry for the dumb questions. Like I said, I am pretty fresh to Arduino.

void loop() { 
  recvWithStartEndMarkers();
  elevation = elevFromPC;
  azimuth = aziFromPC;
  stepper1.moveTo(azimuth);
  if (stepper1.distanceToGo() != 0) {
    stepper2.moveTo(azimuth);
  }
  if (stepper1.distanceToGo() == 0) {
    stepper2.moveTo(elevation);
  }
  stepper1.run();
  stepper2.run();
}

That was one. Next you also need to call parseData once newData is true.

Look again at how the original code was composed in the (updated) serial input basics thread.

Right! Can't believe I missed that. Added that to loop(), yet I am still getting the same result. Is there anything wrong with the void parseData() segment? I suspect there might be, I am not entirely confident in what I did.

Does it matter if I have the loop() segment at the very end of the sketch?

Does it matter if I have the loop() segment at the very end of the sketch?

No. Please post the latest sketch.

jremington:
No. Please post the latest sketch.

Here it is.

#include <AccelStepper.h>
#include <string.h>
#include <stdio.h>
#define HALFSTEP 8

// Motor pin definitions
#define motorPin1  8     // IN1 on the ULN2003 driver 1
#define motorPin2  7     // IN2 on the ULN2003 driver 1
#define motorPin3  5     // IN3 on the ULN2003 driver 1
#define motorPin4  6     // IN4 on the ULN2003 driver 1
#define motorPin5  9     // IN1 on the ULN2003 driver 2
#define motorPin6  12     // IN2 on the ULN2003 driver 2
#define motorPin7  11     // IN3 on the ULN2003 driver 2
#define motorPin8  10     // IN4 on the ULN2003 driver 2

// Initialize with pin sequence IN1-IN3-IN2-IN4 for using the AccelStepper with 28BYJ-48
AccelStepper stepper1(HALFSTEP, motorPin1, motorPin3, motorPin2, motorPin4);
AccelStepper stepper2(HALFSTEP, motorPin5, motorPin7, motorPin6, motorPin8);

#define SOP '<'
#define EOP '>'
#define MID ','
float elevation = 03100.73;
float azimuth = 02502.54;
const byte numChars = 19;
char receivedChars[numChars];
char tempChars[numChars];
char dataFromPC[numChars] = {0};
float elevFromPC = 0.0;
float aziFromPC = 0.0;
boolean newData = false;

void setup() {
  Serial.begin(9600);
  delay(250);
  Serial.write("Arrrre you ready kids?"); //Sends a message to PC to confirm connection
  delay(1000);
  stepper1.setMaxSpeed(1000.0);
  stepper1.setAcceleration(500.0);
  stepper1.setSpeed(500);
  stepper2.setMaxSpeed(1000.0);
  stepper2.setAcceleration(500.0);
  stepper2.setSpeed(500);

}

///// ============== Somewhere below is the problem =============================================
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';
        recvInProgress = false;
        ndx = 0;
        newData = true;
      }
    }
    else if (rc == startMarker) {
      recvInProgress = true;
    }
  }
}

void parseData() {
  char * strtokIndx;
  strtokIndx = strtok(tempChars, ",");
  elevFromPC = atof(strtokIndx);
  strtokIndx = strtok(NULL, ",");
  aziFromPC = atof(strtokIndx);
}

void showParsedData() {
  Serial.println(elevation);
  Serial.println(azimuth);
}
////////////// ========================== Somewhere above is the problem ==============================

void loop() { //   \/ This part of the code works beautifully when I set elevation or azimuth = 12345.67
  recvWithStartEndMarkers();
  elevation = elevFromPC;
  azimuth = aziFromPC;
  if (newData == true) {
    strcpy(tempChars, receivedChars);
    parseData();
    showParsedData();
    newData = false;
  }
  stepper1.moveTo(azimuth);
  if (stepper1.distanceToGo() != 0) {
    stepper2.moveTo(azimuth);
  }
  if (stepper1.distanceToGo() == 0) {
    stepper2.moveTo(elevation);
  }
  stepper1.run();
  stepper2.run();
}

There is no need for this step, as parseData could just as well work with receivedChars.    strcpy(tempChars, receivedChars);

However, I would leave it in for the time being and put a Serial.println(tempChars) after that line, to make sure that the tempChars string is what you expect and is properly terminated with a zero byte.

Make sure that only valid characters are present in the string presented to atof().

Hm, still getting a blank response, as shown in my python shell output below.

Port number: 10
Opening Serial port...
b'Arrrre you ready kids?'
Initialization complete
iss: altitude -46.0 deg, azimuth  94.8 deg
<01927.06,04146.46>
b''
iss: altitude -46.0 deg, azimuth  94.4 deg
<01927.38,04130.05>
b''
iss: altitude -45.9 deg, azimuth  94.0 deg
<001927.7,04113.65>
b''
iss: altitude -45.9 deg, azimuth  93.6 deg
<01928.03,04097.24>
b''
iss: altitude -45.9 deg, azimuth  93.3 deg
<01928.36,04080.84>
b''

I just had a brainwave. Is it possible the problem is that with Python 3.5+ you can only send bytes over serial, not strings? The 'b' prefix before 'command' (below) converts whatever the command is to bytes. (I may have no clue what I'm talking about, honestly)

arduino.write(b'command')

Toasty3000:
Ah, I was hopeful that would fix it. Sadly not.

Post the complete code from your latest version of the program and I will have a look at it.

Also post the Python program - or a shortened version that just sends the message to the Arduino.

Have you looked at this Python - Arduino demo. Note that it pre-dates Serial Input Basics

...R

Here you go Robin.

Full sketch:

#include <AccelStepper.h>
#include <string.h>
#include <stdio.h>
#define HALFSTEP 8

// Motor pin definitions
#define motorPin1  8     // IN1 on the ULN2003 driver 1
#define motorPin2  7     // IN2 on the ULN2003 driver 1
#define motorPin3  5     // IN3 on the ULN2003 driver 1
#define motorPin4  6     // IN4 on the ULN2003 driver 1
#define motorPin5  9     // IN1 on the ULN2003 driver 2
#define motorPin6  12     // IN2 on the ULN2003 driver 2
#define motorPin7  11     // IN3 on the ULN2003 driver 2
#define motorPin8  10     // IN4 on the ULN2003 driver 2

// Initialize with pin sequence IN1-IN3-IN2-IN4 for using the AccelStepper with 28BYJ-48
AccelStepper stepper1(HALFSTEP, motorPin1, motorPin3, motorPin2, motorPin4);
AccelStepper stepper2(HALFSTEP, motorPin5, motorPin7, motorPin6, motorPin8);

float elevation = 03100.73;
float azimuth = 02502.54;
const byte numChars = 19;
char receivedChars[numChars];
char tempChars[numChars];
char dataFromPC[numChars] = {0};
float elevFromPC = 0.0;
float aziFromPC = 0.0;
boolean newData = false;

void setup() {
  Serial.begin(9600);
  delay(250);
  Serial.write("Arrrre you ready kids?"); //Sends a message to PC to confirm connection
  delay(1000);
  stepper1.setMaxSpeed(1000.0);
  stepper1.setAcceleration(500.0);
  stepper1.setSpeed(500);
  stepper2.setMaxSpeed(1000.0);
  stepper2.setAcceleration(500.0);
  stepper2.setSpeed(500);
}

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';
        recvInProgress = false;
        ndx = 0;
        newData = true;
      }
    }
    else if (rc == startMarker) {
      recvInProgress = true;
    }
  }
}

void parseData() {
  char * strtokIndx;
  strtokIndx = strtok(tempChars, ",");
  elevFromPC = atof(strtokIndx);
  strtokIndx = strtok(NULL, ",");
  aziFromPC = atof(strtokIndx);
}

void showParsedData() {
  Serial.println(elevation);
  Serial.println(azimuth);
}


void loop() { // This part of the code works beautifully when I set elevation or azimuth = 12345.67
  recvWithStartEndMarkers();
  elevation = elevFromPC;
  azimuth = aziFromPC;
  if (newData == true) {
    strcpy(tempChars, receivedChars);
    Serial.println(tempChars);
    parseData();
    showParsedData();
    newData = false;
  }
  stepper1.moveTo(azimuth);
  if (stepper1.distanceToGo() != 0) {
    stepper2.moveTo(azimuth);
  }
  if (stepper1.distanceToGo() == 0) {
    stepper2.moveTo(elevation);
  }
  stepper1.run();
  stepper2.run();
}

Python program: (irrelevant stuff omitted)

COMPORT = int(input("Port number: "))
arduino = serial.Serial("COM{}".format(COMPORT), 9600, timeout = 1)
arduino.port = "COM{}".format(COMPORT)
print ("Opening Serial port...")
time.sleep(2)
print (arduino.readline())
time.sleep(2)
print ("Initialization complete")

######snipped some things here, calculations, etc##
 
while True:
##more snipped##
    command = '<'
    command += elevstp
    command += ','
    command += azistp
    command += '>'
    arduino.write(b'command')
    print (command)
    print (arduino.readline())
    time.sleep(5)
    arduino.write(b'command')

According to my Python interpreter that will send...

>>> b'command'
b'command'

...to your Arduino. Which cannot possibly be correct.

Without that prefix there I get the following error:
TypeError: unicode strings are not supported, please encode to bytes: 'command'

EDIT:
Holy crap, the problem is in my python program! I just tried replacing arduino.write(b'command') with arduino.write(b'<01000.00,04321.00>') and it worked!! I think I may have woken up my roommate, haha.

Okay, so then that means the problem is in the creation of the serial command itself.. I can't believe I automatically ruled that out. doh.

Edit 2:
Okay! Awesome news; thanks to the help from you guys and a lot of googling, I resolved the issue and have the thing running smoothly.

I replaced the line arduino.write(b'command') in my python program with arduino.write(bytes(b'%r' % command))

My satellite tracker appears to be running just as I want it to, and Arduino is even echoing back what it's parsing. I'll update the OP once it'll let me.

Toasty3000:
Holy crap, the problem is in my python program!

Which was why I suggested posting it :slight_smile:

Glad to hear it is working.

It is always a good idea to get the Arduino program to print what it actually receives before you start trying to parse it.

...R

Toasty3000:
I replaced the line arduino.write(b'command') in my python program with arduino.write(bytes(b'%r' % command))

I find this to be more readable...

arduino.write(command.encode('ascii'))

I resolved the issue and have the thing running smoothly.

Excellent!