Home

Awesome

#PyCmdMessenger

Python class for communication with an arduino using the CmdMessenger serial communication library. It sends and recieves messages, automatically converting python data types to arduino types and vice versa.

This project is not affiliated with the CmdMessenger project, though it obviously builds off of their excellent work.

Installation:

To test the library:

Compatibility

Dependencies

pyserial should be installed automatically by pip or the installaion script. For CmdMessenger, please follow the directions on their site. Copies of the CmdMessenter 4.0 main .cpp and .h files are included in the PyCmdMessenger repo in the test/arduino and examples/arduino directories.

##Example code

A typical CmdMessenger message has the following structure:

Cmd Id, param 1, [...] , param N;

The PyCmdMessenger class constructs/parses these strings, as well as sending them over the serial connection via pyserial.

To ensure stable communication with PyCmdMessenger:

A basic example is shown below. These files are in the examples directory.

###Arduino


/* -----------------------------------------------------------------------------
 * Example .ino file for arduino, compiled with CmdMessenger.h and
 * CmdMessenger.cpp in the sketch directory. 
 *----------------------------------------------------------------------------*/

#include "CmdMessenger.h"

/* Define available CmdMessenger commands */
enum {
    who_are_you,
    my_name_is,
    sum_two_ints,
    sum_is,
    error,
};

/* Initialize CmdMessenger -- this should match PyCmdMessenger instance */
const int BAUD_RATE = 9600;
CmdMessenger c = CmdMessenger(Serial,',',';','/');

/* Create callback functions to deal with incoming messages */

/* callback */
void on_who_are_you(void){
    c.sendCmd(my_name_is,"Bob");
}

/* callback */
void on_sum_two_ints(void){
   
    /* Grab two integers */
    int value1 = c.readBinArg<int>();
    int value2 = c.readBinArg<int>();

    /* Send result back */ 
    c.sendBinCmd(sum_is,value1 + value2);

}

/* callback */
void on_unknown_command(void){
    c.sendCmd(error,"Command without callback.");
}

/* Attach callbacks for CmdMessenger commands */
void attach_callbacks(void) { 
  
    c.attach(who_are_you,on_who_are_you);
    c.attach(sum_two_ints,on_sum_two_ints);
    c.attach(on_unknown_command);
}

void setup() {
    Serial.begin(BAUD_RATE);
    attach_callbacks();    
}

void loop() {
    c.feedinSerialData();
}

Python


# ------------------------------------------------------------------------------
# Python program using the library to interface with the arduino sketch above.
# ------------------------------------------------------------------------------

import PyCmdMessenger

# Initialize an ArduinoBoard instance.  This is where you specify baud rate and
# serial timeout.  If you are using a non ATmega328 board, you might also need
# to set the data sizes (bytes for integers, longs, floats, and doubles).  
arduino = PyCmdMessenger.ArduinoBoard("/dev/ttyACM0",baud_rate=9600)

# List of command names (and formats for their associated arguments). These must
# be in the same order as in the sketch.
commands = [["who_are_you",""],
            ["my_name_is","s"],
            ["sum_two_ints","ii"],
            ["sum_is","i"],
            ["error","s"]]

# Initialize the messenger
c = PyCmdMessenger.CmdMessenger(arduino,commands)

# Send
c.send("who_are_you")
# Receive. Should give ["my_name_is",["Bob"],TIME_RECIEVED]
msg = c.receive()
print(msg)

# Send with multiple parameters
c.send("sum_two_ints",4,1)
msg = c.receive()

# should give ["sum_is",[5],TIME_RECEIVED]
print(msg)

##Format arguments

The format for each argument sent with a command (or received with a command) is determined by the command_formats list passed to the CmdMessenger class (see example above). Alternatively, it can be specified by the keyword arg_formats passed directly to the send or receive methods. The format specification is in the table below. If a given command returns a single float value, the format string for that command would be "f". If it returns five floats, the format string would be "fffff". The types can be mixed and matched at will. "si??f" would specify a command that sends or receives five arguments that are a string, integer, bool, bool, and float. If no argument is associated with a command, an empty string ("") or None can be used for the format.

###Format reference table

formatarduino typePython TypeArduino receiveArduino send
"i"intintint value = c.readBinArg<int>();c.sendBinCmd(COMMAND_NAME,value);
"b"byteintint value = c.readBinArg<byte>();c.sendBinCmd(COMMAND_NAME,value);
"I"unsigned intintunsigned int value = c.readBinArg<unsigned int>();c.sendBinCmd(COMMAND_NAME,value);
"l"longintlong value = c.readBinArg<long>();c.sendBinCmd(COMMAND_NAME,value);
"L"unsigned longintunsigned long value = c.readBinArg<unsigned long>();c.sendBinCmd(COMMAND_NAME,value);
"f"floatfloatfloat value = c.readBinArg<float>();c.sendBinCmd(COMMAND_NAME,value);
"d"doublefloatdouble value = c.readBinArg<double>();c.sendBinCmd(COMMAND_NAME,value);
"?"boolboolbool value = c.readBinArg<bool>();c.sendBinCmd(COMMAND_NAME,value);
"c"charstr or bytes, length = 1char value = c.readBinArg<char>();c.sendBinCmd(COMMAND_NAME,value);
"s"char[]str or byteschar value[SIZE] = c.readStringArg();c.sendCmd(COMMAND_NAME,value);

PyCmdMessenger takes care of type conversion before anything is sent over the serial connection. For example, if the user sends an integer as an "f" (float), PyCmdMessenger will run float(value) in python before passing it. It will warn the user for destructive conversions (say, a float to an integer). It will throw a ValueError if the conversion cannot be done (e.g. the string 'ABC' to integer). It will throw an OverflowError if the passed value cannot be accomodated in the specififed arduino data type (say, by passing an integer greater than 32767 to a 2-byte integer, or a negative number to an unsigned int). The sizes for each arduino type are determined by the XXX_bytes attributes of the ArduinoBoard class.

With the exception of strings, all data are passed in binary format. This both minimizes the number of bits sent and makes sure the sent values are accurate. (While you can technically send a float as a string to the arduino, then convert it to a float via atof, this is extremely unreliable.)

PyCmdMessenger will also automatically escape separators in strings, both on sending and receiving. For example, the default field separator is , an dthe default escape character is /. If the user sends the string Hello, my name is Bob., PyCmdMessenger will convert this to Hello/, my name is Bob. CmdMessenger on the arduino will strip out the escape character when received. The same behavior should hold for recieving from the arduino.

##Testing

The test directory has an arduino sketch (in pingpong_arduino) that can be compiled and loaded onto an arudino, as well as a python test script, pingpong_test.py. This will send a wide range of values for every data type back and forth to the arduino, reporting success and failure.

##Known Issues

##Quick reference for CmdMessenger on arduino side For more details, see the CmdMessenger project page.

###Receiving

/* c is an instance of CmdMessenger (see example sketch above) */
/* ------- For all types except strings (replace TYPE appropriately) --------*/
int value = c.readBinArg<TYPE>();

/* ----- For strings (replace BUFFER_SIZE with maximum string length) ------ */
char string[BUFFER_SIZE] = c.readStringArg();

###Sending

/* COMMAND_NAME must be enumerated at the top of the sketch.  c is an instance
 * of CmdMessenger (see example sketch above) */

/* ------------------- For all types except strings ------------------------*/

// Send single value
c.sendBinCmd(COMMAND_NAME,value);

// Send multiple values via a single command
c.sendCmdStart(COMMAND_NAME);
c.sendCmdBinArg(value1);
c.sendCmdBinArg(value2);
// ...
// ...
c.sendCmdEnd();

/* ------------------------- For strings ------------------------------------- */
// Send single string 
c.sendCmd(COMMAND_NAME,string);

// Send multiple strings via a single command
c.sendCmdStart(COMMAND_NAME);
c.sendCmdArg(string1);
c.sendCmdArg(string2);
// ...
// ...
c.sendCmdEnd();

##Python Classes

ArduinoBoard 
    Class for connecting to an Arduino board over USB using PyCmdMessenger.  
    The board holds the serial handle (which, in turn, holds the device name,
    baud rate, and timeout) and the board parameters (size of data types in 
    bytes, etc.).  The default parameters are for an ArduinoUno board.

    Static methods
    --------------
    __init__(self, device, baud_rate=9600, timeout=1.0, settle_time=2.0, enable_dtr=False, int_bytes=2, long_bytes=4, float_bytes=4, double_bytes=4)
        Serial connection parameters:
            
            device: serial device (e.g. /dev/ttyACM0)
            baud_rate: baud rate set in the compiled sketch
            timeout: timeout for serial reading and writing
            settle_time: how long to wait before trying to access serial port
            enable_dtr: use DTR (set to False to prevent arduino reset on connect)

        Board input parameters:
            int_bytes: number of bytes to store an integer
            long_bytes: number of bytes to store a long
            float_bytes: number of bytes to store a float
            double_bytes: number of bytes to store a double

        These can be looked up here:
            https://www.arduino.cc/en/Reference/HomePage (under data types)

        The default parameters work for ATMega328p boards.

    close(self)
        Close serial connection.

    read(self)
        Wrap serial read method.

    readline(self)
        Wrap serial readline method.

    write(self, msg)
        Wrap serial write method.

    Instance variables
    ------------------
    baud_rate

    comm

    device

    double_bytes

    float_bytes

    int_bytes

    int_max

    int_min

    long_bytes

    long_max

    long_min

    settle_time

    timeout

    unsigned_int_max

    unsigned_int_min

    unsigned_long_max

    unsigned_long_min

CmdMessenger 
    Basic interface for interfacing over a serial connection to an arduino 
    using the CmdMessenger library.

    Static methods
    --------------
    __init__(self, board_instance, commands, field_separator=',', command_separator=';', escape_separator='/', warnings=True)
        Input:
        board_instance:
            instance of ArduinoBoard initialized with correct serial 
            connection (points to correct serial with correct baud rate) and
            correct board parameters (float bytes, etc.)

        commands:
            a list or tuple of commands specified in the arduino .ino file
            *in the same order* they are listed there.  commands should be
            a list of lists, where the first element in the list specifies
            the command name and the second the formats for the arguments.
            (e.g. commands = [["who_are_you",""],["my_name_is","s"]])

        field_separator:
            character that separates fields within a message
            Default: ","

        command_separator:
            character that separates messages (commands) from each other
            Default: ";" 

        escape_separator:
            escape character to allow separators within messages.
            Default: "/"

        warnings:
            warnings for user
            Default: True

        The separators and escape_separator should match what's
        in the arduino code that initializes the CmdMessenger.  The default
        separator values match the default values as of CmdMessenger 4.0.

    receive(self, arg_formats=None)
        Recieve commands coming off the serial port. 

        arg_formats is an optimal keyword that specifies the formats to use to
        parse incoming arguments.  If specified here, arg_formats supercedes
        the formats specified on initialization.

    send(self, cmd, *args)
        Send a command (which may or may not have associated arguments) to an 
        arduino using the CmdMessage protocol.  The command and any parameters
        should be passed as direct arguments to send.  

        arg_formats is an optional string that specifies the formats to use for
        each argument when passed to the arduino. If specified here,
        arg_formats supercedes formats specified on initialization.

    Instance variables
    ------------------
    board

    command_separator

    commands

    escape_separator

    field_separator

    give_warnings