Python <--> Arduino Package

I've got a project in the works that may require a RPi to send/receive datapackets to/from one of my Arduinos. I have some prior experience trying to "brute force" Python to connect to Arduinos before, but this project requires more efficiency and simplicity. Plus, I love libraries.

As a result, I've successfully created a super easy to use Python package that connects your PC to your Arduino with reliable and robust communication.

USB Packet Anatomy:

01111110 11111111 00000000 00000000 00000000 ... 00000000 10000001
|      | |      | |      | |      | |      | | | |      | |______|__Stop byte
|      | |      | |      | |      | |      | | | |______|___________8-bit CRC
|      | |      | |      | |      | |      | |_|____________________Rest of payload
|      | |      | |      | |      | |______|________________________2nd payload byte
|      | |      | |      | |______|_________________________________1st payload byte
|      | |      | |______|__________________________________________# of payload bytes
|      | |______|___________________________________________________COBS Overhead byte
|______|____________________________________________________________Start byte (constant)

To install:
1.) Make sure you have Python installed (any version should do - I'm using 3.7)
2.) Go to your system shell
3.) For Linux, call

sudo pip install pySerialTransfer

For Windows, call

pip install pySerialTransfer

To Use:
The pySerialTransfer package was written based off of the Arduino library SerialTransfer.h (available in the IDE's Libraries Manager), so much of the API is the same. This package is also compatible with Arduinos running sketches using SerialTransfer.h.

To Import:

from pySerialTransfer import pySerialTransfer

To Initialize:

link = pySerialTransfer.SerialTransfer(port_num=15, baud=9600)

The above line creates a SerialTransfer class object named "link" that connects to a USB device on COM15 (for Windows) or /dev/ttyUSB15 (for Linux) at a baud of 9600.

To Send Data:

link.txBuff[0] = 'h'
link.txBuff[1] = 'i'
link.txBuff[2] = '\n'

link.send(3)

The above lines stuff byte values (either chars or integers below the value 256) into the transmit buffer (txBuff) and sends them using the "send()" member function. The argument for "send()" is the total number of payload bytes (from the txBuff array) to transmit to the USB device. You can send up to 255 bytes of payload data per packet!

To Parse Data:

if link.available():
    pass

If a new packet has been successfully parsed, the member function "available()" will return the total number of payload bytes in the new packet. If a new packet has not been successfully/fully parsed yet, "available()" will return 0.

To Catch Error Codes:

if link.status < 0:
    print('ERROR: {}'.format(link.status))

The class property "status" will tell you what failed during the parsing of the current packet. Here are the different status definitions:

CONTINUE        = 2
NEW_DATA        = 1
NO_DATA         = 0
CHECKSUM_ERROR  = -1
PAYLOAD_ERROR   = -2
STOP_BYTE_ERROR = -3

(Note that only status codes < 0 are "error" codes)

To Manipulate Received Bytes:

response = ''
for index in range(link.bytesRead):
    response += chr(link.rxBuff[index])

The above lines assumes the Python script is receiving ASCII chars, but the method for saving/manipulating integer chars is so similar, it doesn't need it's own example. Generally speaking, the "bytesRead" attribute lets you know how many payload bytes were read into the receive buffer (rxBuff) from the last parsed packet. You can then use a loop to access each of these bytes in the "rxBuff" to save to individual variables, do calculations, etc...

To Clean Up The Port:

link.close()

This closes the USB port to the device (i.e. cuts off communication between the Python script and the Arduino)

To Open The Port Manually:

link.open()

Note that the class constructor automatically establishes a port connection to the USB device, but if you need to reopen the port at any time, just call the member function "open()". This will either return True for a successful port opening or a False for a failed port opening attempt.

And here is a completed example Python script:

from pySerialTransfer import pySerialTransfer

if __name__ == '__main__':
    try:
        link = pySerialTransfer.SerialTransfer(13)
    
        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()