PySerial communication with Arduino not working

Hi all,

I've been creating projects with Arduino for years now and have had a lot of fun. Now I'm trying to get my feet wet in some python. My goal in this project is to use openCV's facial detection to track a face in a video and output the location to an Arduino to control a servo. My facial detection python works great and I am able to calculate the center point of the face and store it in centerX and centerY variables. My simple Arduino code takes any value from 0 to 400 (the centerX value range) and maps it from 0 to 180 to control a servo. After some research, I found that you can send serial data from python running on a laptop to an Arduino with PySerial. After figuring out the usage, I implemented it and I can see that the Arduino is receiving the data because of the RX led. The only problem is that it doesn't seem to want to control the servo. I hooked up the rx and tx lines of the Arduino to another Arduino and opened up the serial monitor of the second one so I could make sure that the right info is coming through. All that shows up is random ascii characters. I know that this seems like a baud rate issue but I double checked and every Arduino was set to 9600 and the python program was too. From what I've seen nobody else has had this issue and I'm completely confused why this is happening.

(excuse my code neatness I haven't cleaned it up yet)

# USAGE
# python detect_faces_video.py --prototxt deploy.prototxt.txt --model res10_300x300_ssd_iter_140000.caffemodel

# import the necessary packages
from imutils.video import VideoStream
import numpy as np
import argparse
import imutils
import time
import cv2
import serial

#Serial setup
ser=serial.Serial()    
ser.baudrate=9600
ser.port= '/dev/ttyUSB0'
ser.parity=serial.PARITY_ODD
ser.stopbits=serial.STOPBITS_ONE
ser.bytesize=serial.EIGHTBITS
ser.open()

# construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-p", "--prototxt", required=True,
        help="path to Caffe 'deploy' prototxt file")
ap.add_argument("-m", "--model", required=True,
        help="path to Caffe pre-trained model")
ap.add_argument("-c", "--confidence", type=float, default=0.5,
        help="minimum probability to filter weak detections")
args = vars(ap.parse_args())

# load our serialized model from disk
print("[INFO] loading model...")

net = cv2.dnn.readNetFromCaffe(args["prototxt"], args["model"])

# initialize the video stream and allow the cammera sensor to warmup
print("[INFO] starting video stream...")
vs = VideoStream(src=0).start()
time.sleep(0.5)

# loop over the frames from the video stream
while True:
        # grab the frame from the threaded video stream and resize it
        # to have a maximum width of 400 pixels
        frame = vs.read()
        frame = imutils.resize(frame, width=400)
 
        # grab the frame dimensions and convert it to a blob
        (h, w) = frame.shape[:2]
        blob = cv2.dnn.blobFromImage(cv2.resize(frame, (300, 300)), 1.0,
                (300, 300), (104.0, 177.0, 123.0))
 
        # pass the blob through the network and obtain the detections and
        # predictions
        net.setInput(blob)
        detections = net.forward()

        # loop over the detections
        for i in range(0, detections.shape[2]):
                # extract the confidence (i.e., probability) associated with the
                # prediction
                confidence = detections[0, 0, i, 2]

                # filter out weak detections by ensuring the `confidence` is
                # greater than the minimum confidence
                if confidence < args["confidence"]:
                        continue

                # compute the (x, y)-coordinates of the bounding box for the
                # object
                box = detections[0, 0, i, 3:7] * np.array([w, h, w, h])
                (startX, startY, endX, endY) = box.astype("int")

                # draw the bounding box of the face along with the associated
                # probability

                centerX = ((startX + endX)/2)
                centerY = ((startY + endY)/2)
 
                text2 = "{}".format(int(centerX))
                text = "{:.2f}%".format(confidence * 100)

                y = startY - 10 if startY - 10 > 10 else startY + 10
                cv2.rectangle(frame, (startX, startY), (endX, endY),
                        (0, 0, 255), 2)

                cv2.putText(frame, text2, (startX, y-10),
                        cv2.FONT_HERSHEY_SIMPLEX, 0.45, (0, 0, 255), 2)

                cv2.putText(frame, text, (startX, y+5),
                        cv2.FONT_HERSHEY_SIMPLEX, 0.45, (0, 0, 255), 2)

                cv2.line(frame, (startX, startY), (endX, endY), (0, 0, 255), 2)
                cv2.line(frame, (startX, endY), (endX, startY), (0, 0, 255), 2)

                cv2.circle(frame, (int(centerX), int(centerY)), 8, (0, 255, 0), 2)


                #Serial transmission
                ser.write(centerX)

        # show the output frame
        cv2.imshow("Frame", frame)
        key = cv2.waitKey(1) & 0xFF
 
        # if the `q` key was pressed, break from the loop
        if key == ord("q"):
                break

# do a bit of cleanup
cv2.destroyAllWindows()
vs.stop()
#include <Servo.h>
Servo myservo;
int pos = 0;



void setup()

{

Serial.begin(9600);

myservo.attach(9);
myservo.write(100);
}
void loop() {
  

if (Serial.available()) {
  
  int state = Serial.parseInt();
    

  if (state > 0 && state <= 180) {
    int x = map(state, 0, 400, 0, 180);
    myservo.write(state);
  
    }

  } 

}

Thanks,
Noah

This Simple Python - Arduino demo should get you started.

The Python code should work on Windows if you edit it to use the Windows style of COM ports.

It will be best to send all the values in a single message - for example <123, 789> so that there is no doubt in the Arduino program whether it is getting the Xvalue or the Yvalue.

Also have a look at the examples in Serial Input Basics . It includes a parse example to illustrate how to extract numbers from the received text.

The technique in the 3rd example will be the most reliable. It is what I use for Arduino to Arduino and Arduino to PC communication.

You can send data in a compatible format with code like this (or the equivalent in any other programming language)

Serial.print('<'); // start marker
Serial.print(value1);
Serial.print(','); // comma separator
Serial.print(value2);
Serial.println('>'); // end marker

...R

It is important to realise that all communication to and from Python happens in ASCII. Your code is just sending a number but it is being received in ASCII so you have to convert it from ASCII into an int at the Arduino end.

You could do it all on the Pi if you used one of these Adafruit 16-Channel 12-bit PWM/Servo Driver - I2C interface [PCA9685] : ID 815 : $14.95 : Adafruit Industries, Unique & fun DIY electronics and kits or in fact just used a PCA9685 chip attached to the Pi's I2C pins, to drive the servo. For one or two servos, you can drive them directly from the digital audio lines on the GPIO connector. The HDMI audio would still work.

You can interface Python with Arduinos via USB serial easily using the compatible libraries: 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:

from time import sleep
from pySerialTransfer import pySerialTransfer as txfer

if __name__ == '__main__':
    try:
        link = txfer.SerialTransfer('COM13')
        
        link.open()
        sleep(2) # allow some time for the Arduino to completely reset
    
        link.txBuff[0] = 'h'
        link.txBuff[1] = 'i'
        link.txBuff[2] = '\n'
        
        link.send(3)
        
        while not link.available():
            if link.status < 0:
                print('ERROR: {}'.format(link.status))
            
        print('Response received:')
        
        response = ''
        for index in range(link.bytesRead):
            response += chr(link.rxBuff[index])
        
        print(response)
        link.close()
        
    except KeyboardInterrupt:
        link.close()

Example Arduino Sketch:

#include "SerialTransfer.h"

SerialTransfer myTransfer;

void setup()
{
  Serial.begin(115200);
  Serial1.begin(115200);
  myTransfer.begin(Serial1);
}

void loop()
{
  myTransfer.txBuff[0] = 'h';
  myTransfer.txBuff[1] = 'i';
  myTransfer.txBuff[2] = '\n';
  
  myTransfer.sendData(3);
  delay(100);

  if(myTransfer.available())
  {
    Serial.println("New Data");
    for(byte i = 0; i < myTransfer.bytesRead; i++)
      Serial.write(myTransfer.rxBuff[i]);
    Serial.println();
  }
  else if(myTransfer.status < 0)
  {
    Serial.print("ERROR: ");
    Serial.println(myTransfer.status);
  }
}

My experience with PySerial has not been that great. It seems that it works sometimes and not others. I had the same problem as you once where I just got random stuff on the serial monitor at different baud rates. The baud rates matched but the communication only worked on 115200 baud and not 9600. Try making a simple script that only sends a couple known values and use your arduino to confirm that is working first. Don't wanna chase your tail trying to figure out the arduino side when it could be PySerial giving you problems

You need parity none BTW, like the Arduino setup.

bears0:
My experience with PySerial has not been that great.

I don't recall ever having a problem with it. I regularly use it for communication between this laptop and my Uno and Mega boards at 500,000 baud.

...R

I've used it extensively communicating between RaspPi's and Arduinos (and several other boards and systems)
You are using Python3 aren't you?

Likewise I have never had trouble communicating between the Py serial and an Arduino, done it many times. Just connect the two together using USB then the baud rate is irrelevant.

.. I just remembered it wasn't PySerial that was giving me problems. PySerial was my solution after trying to get serial to work in VisualStudio. I wanted a way to parse an image and send the data to an FPGA controlled LED matrix. Got the code right and even downloaded some open source programs to "borrow" the code. Still was a pain in the butt. It printed the data to a file correctly but not through serial. Sorry for my bad recollection.

Does that mean your code is fixed? Did you see reply #3?