Arduino - Python communication wrapper
Description
There are many times you want to connect between Arduino(s) and some Python script. This can be for exmple data collection reasons or coordinating between multiple devices (Robot arms (e.g.: UR5 arms), digital servos (e.g.: Dynamixels), webcams, etc ).
However, for many, using a middleware communication system such as ROS can be a huge learning curve and an unnecessary overhead.
The point for this wrapper is to provide a simple solution to estabilish a reliable two-way communication channel between mulitple arduinos and a central python script.
The aim is to be able to simple messages back and forth without going through the often troublesome process of synchronising messages, opening serial communication channels, etc. The general functionality is limited to keep everything straightforward and simple.
The project is constantly developing so please report bugs or any problems.
Core functionality
- Estabilish a stable communication channel between multiple Arduinos and a single Python script
- Send multiple strings form the Python side to the Arduino side (e.g.: can be used to tell the arduino "stop the motor" while sending the motor demand position)
- Send mulitple messages with specified messages names from the Arduino side to the Python side (e.g.: can be used to transfer multiple sensor values, status messages, timestamps, etc)
- Simplified keyboard keypress reading functionality to test your mechatronic setup
Points to bare in mind
- On both sides, the messages reflect the most recent message. For now, there is no buffer or queue of messages (in order to simplify and increase the robustness the communication channel from edge cases).
Known issues
- Certain combinations of microcontrollers and OS does not work well (see table below)
How to use this tool
Download the release
Download the latest release from here. You can see rough changes from the previous versions in the CHANGELOG.
In the release you will find three files: comms_wrapper.py
, pyCommsLib.cpp
, and pyCommsLib.h
. These three files will be used as "Libraries" to provide the communication wrapper functionality.
Initial setup (Python side)
Copy and paste the comms_wrapper.py
into the same directory as your Python script. In your Python script, import the contents of comms_wrapper.py
by writing from comms_wrapper import *
.
Initial setup (Arduino side)
Copy and paste the pyCommsLib.cpp
and pyCommsLib.h
into the same directory as your Arduino .ino file. If you do this correctly, you should see the two files as two tabs on the Arduino IDE.
How to use the wrapper
To help you get started with this wrapper, look through the commented example code Exmples/
folder (the implementation for the Python and Arduino side is shown the corresponding Exmples/Python/
and Exmples/Arduino/
folders).
You can also find detailed description of the different functions and variables used in the implementation to understand their functions. You can find this at the bottom of this README file.
Python libraries you need to install
Testing and compatability
Arduino Uno | Arduino Nano | Arduino Nano Every | Orange Pip | Teensy 4.0 | |
---|---|---|---|---|---|
Windows 10 | ✔️ | ❌ | ✔️ | ✔️ | ❌ |
Ubuntu 20.04 | ✔️ | ✔️ | ✔️ | ✔️ | ❌ |
Testing has been perfromed on an XPS 15 9000 model.
Function and variable definitions
Here, all of the public functions and variables are defined and explained for usage.
Python side
-
Defining the arduino object:
CallArduino(descriptiveDeviceName, portName, baudrate)
to create your arduino class instance. For every arduino you connect, you need to create a new class instance. Example:myArduino = Arduino("Arduino Uno", "COM4", 115200)
- Parameters:
-
descriptiveDeviceName
: Name given to your arduino controller -
portName
: Port your arduino is connected -
baudrate
: Baudrate set on the Arduino side
-
- Parameters:
-
Connecting to the Arduino:
Call theconnect_and_handshake()
function to connect to your arduino. The connection is two stages - first it connects via the serial port, and then it performs a handshake to be certain the communication is successful- Return:
- Boolean whether the connection + handshake was successful
- Return:
-
Receiveing messages:
Call thereceive_message()
function obtain new messages from the Arduino- Parameters:
-
printOutput
: Set toTrue
to printout the received message. Default isFalse
-
verbose
: Set toTrue
to printout verbose information of the received message. This assumesprintOutput
isTrue
. Default isFalse
-
- Return:
- Boolean whether a valid message was received
- Parameters:
-
Sending messages: Call the
send_message()
function to send messages to the arduino. You can send a single message or multiple messages. Example:myArduino.send_message("stop motor")
,myArduino.send_message([1,2,"stop motor"])
- Parameters:
-
msg
:- Option 1, single message: You can include any type of object as argument, but it will be transformed into a string before sending.
- Option 2, multiple messages: You can specify a list of any objects as argument. Each element in the list will be converted into a string before sending.
-
- Parameters:
-
Debugging:
Call thedebug()
function to debug the communication pipeline. It will show you the messages received from the arduino side, and what message is stored on the arduino (i.e.: tells you if the message you sent from the python side has indeed been receieved by the arduino side).- Parameters:
-
verbose
: Set toTrue
to printout verbose information of the received message. Default isFalse
-
- Parameters:
Arduino side
-
Initializing the communication:
Callinit_python_communication()
withinvoid setup()
to begin the communication with the python side. The arduino code will hang on this command until the python side is activated. -
latest_received_msg():
Calllatest_received_msg()
to fetch the most recent message from the python side. Note: This does not actually read the message from the python side, but rather is an interface the store message on the arduino to be accessed. To actually receive messages, you must callsync()
(see below).- Parameters:
-
int
index
: If specified, it will fetch the message sent on that particular index from the python side. If unspecified, it will fetch the full message.
-
int
- Return:
- String of the received message
- Parameters:
-
Prepare to send a message to python side:
Callload_msg_to_python()
to load the message which will be sent to the python side. You must specify the name of each message and the payload itself. A good way of doing this is shown in the example code. Example:load_msg_to_python(msgName, dataCarrier, size_of_array(msgName));
- Parameters:
-
String*
msgName
: Array of String variables. Here you should specify the name associated with each message -
String*
msgName
: Array of String variables. Here you should specify the message content -
int
numOfMsg
: The number of messages you are sending. This is done automatically by callingsize_of_array(msgName)
-
String*
- Parameters:
-
Send and receive messages:
Callsync()
to send and receive messages over the serial bus. This is where the actualy communiocation takes place and must be called.