1

I'm working on a robot and I'd like to somehow send a command using pySerial to the arduino. The command would look like {MOVE, 60, 70} or {REQUEST_DATA}, where I'd have the arduino read in the first value, if it's "MOVE" then it drives some motors with speed 60 and 70, and if it's "REQUEST_DATA" it would respond with some data like battery status, gps location etc.

Sending this as a string of characters and then parsing is really a huge pain! I've tried days (!frustration!) without it working properly. Is there a way to serialize a data structure like {'MOVE', 70, 40}, send the bytes to the arduino and reconstruct into a struct there? (Using struct.pack() maybe? But I don't yet know how to "unpack" in the arduino).

I've looked at serial communication on arduino and people seem to just do it the 'frustrating' way - sending single chars. Plus all talk about sending struct from arduino to python, and not the other way round.

3
  • How is parsing on the receive end a pain? You only have serial comms to work with right?. Can you build a dictionary structure on the arduino end mapping single characters to common commands? Commented Dec 3, 2022 at 19:52
  • Well... I've tried. The situation is a mess with EOF characters, comparing char arrays, converting from char to int and more... not to mention handling when sending/receiving fails. I'd think for such a common task people'd have libraries... Commented Dec 3, 2022 at 21:09
  • Update: I tried ArduinoJson and it "worked". However the sending is so slow (~1s) even with short structs like above... Commented Dec 3, 2022 at 21:10

1 Answer 1

2

There are a number of ways to tackle this problem, and the best solution depends on exactly what data you're sending back and forth.

The simplest solution is to represent commands a single bytes (e.g., M for MOVE or R for REQUEST_DATA), because this way you only need to read a single byte on the arduino side to determine the command. Once you know that, you should know how much additional data you need to read in order to get the necessary parameters.

For example, here's a simple program that understands two commands:

  • A command to move to a given position
  • A command to turn the built-in LED on or off

The code looks like this:

#define CMD_MOVE 'M'
#define CMD_LED 'L'

struct Position {
  int8_t xpos, ypos;
};

struct LEDState {
  byte state;
};

void setup() {
  Serial.begin(9600);
  pinMode(LED_BUILTIN, OUTPUT);

  // We need this so our Python code knows when the arduino is
  // ready to receive data.
  Serial.println("READY");
}

void loop() {
  char cmd;
  size_t nb;

  if (Serial.available()) {
    cmd = Serial.read();
    switch (cmd) {
      case CMD_MOVE:
        struct Position pos;
        nb = Serial.readBytes((char *)&pos, sizeof(struct Position));
        Serial.print("Moving to position ");
        Serial.print(pos.xpos);
        Serial.print(",");
        Serial.println(pos.ypos);
        break;
      case CMD_LED:
        struct LEDState led;
        nb = Serial.readBytes((char *)&led, sizeof(struct LEDState));
        if (led.state) {
          digitalWrite(LED_BUILTIN, HIGH);
        } else {
          digitalWrite(LED_BUILTIN, LOW);
        }
        Serial.print("LED is ");
        Serial.println(led.state ? "on" : "off");

        break;
    }
  }
}

A fragment of Python code that interacts with the above might look like this (assuming that port is a serial.Serial object):

print("waiting for arduino...")
line=b""
while not b"READY" in line:
    line = port.readline()

port.write(struct.pack('bbb', ord('M'), 10, -10))
res = port.readline()
print(res)

for i in range(10):
    port.write(struct.pack('bb', ord('L'), i%2))
    res = port.readline()
    print(res)
    time.sleep(0.5)

port.write(struct.pack('bbb', ord('M'), -10, 10))
res = port.readline()
print(res)

Running the above Python code, with the Arduino code loaded on my Uno, produces:

waiting for arduino...
b'Moving to position -10,10\r\n'
b'LED is off\r\n'
b'LED is on\r\n'
b'LED is off\r\n'
b'LED is on\r\n'
b'LED is off\r\n'
b'LED is on\r\n'
b'LED is off\r\n'
b'LED is on\r\n'
b'LED is off\r\n'
b'LED is on\r\n'
b'Moving to position 10,-10\r\n'

This is simple to implement and doesn't require much in the way of decoding on the Arduino side.

For more complex situations, you may want to investigate more complex serialization solutions: for example, you can send JSON to the arduino and use something like https://arduinojson.org/ to deserialize it on the Arduino side, but that's going to be a much more complex solution.


In most cases, the speed at which this works is going to be limited by the speed of the serial port: the default speed of 9600bps is relatively slow, and you're going to notice that with larger amounts of data. Using higher serial port speeds will make things noticeably faster: I'm too lazy to look up the max. speed supported by the Arduino, but my UNO works at least as fast as 115200bps.

Sign up to request clarification or add additional context in comments.

2 Comments

I tested this solution and it works beautifully... except i can't get negative integers to work. I've tried changing the pack format to 'bii' but some how the y position get messed up (always show 0), but the x position is fine. Any ideas?
Use type int8_t instead of byte for storing the position. Do note that using signed 8-bit values limits you to a range of -127 to 127. We're already using signed bytes in Python because I was using b instead of B in the struct format string. I've updated the code in the answer with this change.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.