Introduction
I've recently been building an omni directional robot, which i'm documenting here. Since I don't want this article to become too long. I'm going to split the documentation across a number of articles.
In the first article I'm going to discuss the comms protocol used to communicate with the robot. You can see a picture and video of the robot in action below:
Background
I've worked on a number of projects around the Arduino platform in the past which required Serial communication. I often came up against a hurdle when using Serial comms once the messages sent to the Arduino became more complex. I began looking into solutions to this problem and soon came across CmdMessenger on the Arduino playground.
From the libraries homepage:
Quote:
CmdMessenger is a messaging library for the Arduino Platform (and .NET/Mono platform). It uses the serial port as transport layer. To use CmdMessenger, we define a list of command identifiers, then attach callback / handler functions for received messages.
The library was a great solution the Serial comms problem I was trying to solve. However the .Net implementation of the protocol didn't meet my requirements. The existing .Net library was tightly coupled to using a serial port for the transport layer. Since many devices such as the Raspberry Pi and Arduino Yun now support WiFi, I wanted to be able to send the messages over TCP/IP.
The messages sent over TCP/IP could can then be proxied to the Arduino using a simple Python script. See diagram below:
I'm using a python script called tcp_serial_redirect.py which is run as a daemon process on the Linux device. I won't cover the steps involved in configuring the device in this article though. Before the Arduino Yun was released i was using a Raspberry Pi as the Linux device coupled with a Arduino Uno.
Protocol
The protocol defined by CmdMessenger is simple and very flexible. The format of the protocol is as follows:
String Escape parameters
Since the strings could contain a Parameter or Command separator the protocol must support escaping of these special characters. The special characters are escaped by suffixing them with the escape characters. By default the escape characters is a back slash. For example:
- 5,Hello,World; - Would appear as one command with three parameters.
- 5,Hello\,World; - Would appear as one command with two parameters.
This is all handled by the library and therefore the client does not need to know these details.
Architecture
The following class diagram shows the basic overview of the library:
CmdMessenger
CmdMessenger is the main entry point into the library.
- Start - opens the connection and starts processing incoming commands.
- Stop - closes the connection and stops reading incoming commands.
- Send - Sends an ISendCommand and blocks until a response is received.
- SendAsync - Sends an ISendCommand and returns a Taks<IRecievedCommand>.
- Register - allows a method or ICommandObserver to subscribe to incoming commands with a specified ID.
IReceivedCommand
IReceivedCommand provides an interface for reading incoming commands.
- ReadInt16 - reads a 16 bit integer from the command.
- ReadBool - reads a boolean from the command where 1, 0 represents true, false respectively.
- ReadInt32 - reads a 32 bit integer from the command.
- ReadString - reads a string from the command.
ReceivedCommand
ReceivedCommand is the concrete implementation of IReceivedCommand. There shouldn't be a need for clients to implement the interface.
ISendCommand
ISendCommand provides an interface for sending outgoing commands.
SendCommand
SendCommand provides a concrete implementation of the the ISendCommand interface. The class can be used by clients directly or the library can be extended by creating command which derive from this class.
ICmdComms
ICmdComms provides a common interface for transport layers.
CmdComms
Provides a common implementation for reading commands from a stream. Other stream based transport layers can derive from this base class. Two implementation of the ICmdComms are provided with the library:
- SerialCmdClient
- TcpCmdClient
Simple demo
The source code contains a simple demo application which demonstrates sending a command and handling its response. The demo shows the following steps:
- CmdMessenger is first instantiated along with a transport layer.
- A command handler is registered to handle the response command.
- The start method is called to to open the connection to the transport layer and start processing the commands.
- A command is sent to the CmdMessenger server and waits until the server responds with a CommandID of 1.
var cmdClient = new CmdMessenger.CmdMessenger(new TcpCmdClient("127.0.0.1", 5000));
cmdClient.Register(1, r => Console.WriteLine("Response received"));
cmdClient.Start();
while (Console.ReadLine() != "x")
{
cmdClient.Send(new SendCommand(0, 1));
}
Commands can be uni or bi directional. If the send command defines an AckCommandID the send command will block until a response is received or the time-out is reached.
cmdClient.Send(new SendCommand(0, 1);
cmdClient.Send(new SendCommand(0);
The library supports C#5 with the SendAsync methods.
Task<IReceivedCommand> received = cmdClient.SendAsync(new SendCommand(0, 1);
await received;
Robot Controller Application
The source code contains a WPF application for controlling the robot. This application takes things a little further by adding an extra layer of abstraction:
There are four components which make up the Robot Controller:
- CmdMessenger - The CmdMessenger library.
- PiBot.Common - A class library which adds a layer of abstraction on top of CmdMessenger.
- ControlPad - A Custom Control (I might document this in a subsequent article)
- PiBot.Gui - The main application.
PiBot.Common - Robot Commands
PiBot.Common contains a number of commands. I plan to add sensors and a pan/tilt camera to the robot at some stage. I havent added these features yet and therefore I have only fully implemented a few commands.
- SetMotorSpeed
- SendMotorSpeed
PitBot.Gui - Application
The Robot Controller application is simple to use:
- Select "Address = 127.0.0.1, Port 5000" from the combo box.
- Click connect, the controller will wait until a connection can be established.
- Once a connection is established the control pad will be enabled allowing you to click the control directions.
Since you won't have my robot to hand I've created a test harness which can be used in conjunction with the robot controller application.
Arduino Code
The following is a code dump of the Arduino code.
Key Points
I'm using four motor controllers from VEX robotics to drive the robot Motor Controller 29. The motor controllers require a PWM signal in the range of 1ms to 2ms with 1ms being full reverse and 2ms full forward, 1.5 ms is neutral. From testing I was able to use the Arduino Servo library to generate the desired PWM signal. I found an angle of 40 gave me full reverse and 140 gave me full forward, therefore the range of available speeds is 0 - 100.
#include <CmdMessenger.h>
#include <Base64.h>
#include <Streaming.h>
#include <Servo.h>
char field_separator = ',';
char command_separator = ';';
CmdMessenger cmdMessenger = CmdMessenger(Serial, field_separator, command_separator);
Servo servo1;
Servo servo2;
Servo servo3;
Servo servo4;
enum
{
GetMotorSpeed = 1,
GetMotorSpeedResponse,
SetMotorSpeed,
SendMotorSpeed,
GetPanTiltPos,
GetPanTiltPosResponse,
SetPanTiltPos,
SendPanTiltPos,
GetSensorValue,
GetSensorValueResponse,
SetSensorValue,
SendSensorValue,
Fault
};
messengerCallbackFunction messengerCallbacks[] =
{
HandleGetMotorSpeed,
unknownCmd,
HandleSetMotorSpeed,
unknownCmd,
NULL
};
void HandleGetMotorSpeed()
{
int leftSpeedF = servo1.read() - 40;
int rightSpeedF = servo2.read() - 40;
int leftSpeedR = servo3.read() - 40;
int rightSpeedR = servo4.read() - 40;
String response = "Send pan tilt pos" + String(leftSpeedF) + "," + String(rightSpeedF) +
"," + String(leftSpeedR) + "," + String(leftSpeedR);
char* stdStr = new char[response.length() + 1];
response.toCharArray(stdStr,response.length());
cmdMessenger.sendCmd(GetMotorSpeedResponse, "Get mototr speed");
}
void HandleSetMotorSpeed()
{
int leftSpeedF = cmdMessenger.readInt();
int rightSpeedF = cmdMessenger.readInt();
int leftSpeedR = cmdMessenger.readInt();
int rightSpeedR = cmdMessenger.readInt();
cmdMessenger.sendCmd(SendMotorSpeed, "Send motor speed");
servo1.write(40 + leftSpeedF);
servo2.write(40 + rightSpeedF);
servo3.write(40 + leftSpeedR);
servo4.write(40 + rightSpeedR);
}
void unknownCmd()
{
cmdMessenger.sendCmd(Fault,"Unknown command");
}
void attach_callbacks(messengerCallbackFunction* callbacks)
{
int i = 0;
int offset = 1;
while(callbacks[i])
{
cmdMessenger.attach(offset+i, callbacks[i]);
i++;
}
}
void setup()
{
Serial.begin(57600);
servo1.attach(8);
servo2.attach(9);
servo3.attach(10);
servo4.attach(11);
cmdMessenger.print_LF_CR();
cmdMessenger.attach(unknownCmd);
attach_callbacks(messengerCallbacks);
}
void loop()
{
cmdMessenger.feedinSerialData();
}
Points of Interest
Support For Binary Form (efficient)
The original C# implementation of the CmdMessenger library supports sending messages in binary format. I decided not to implemented this feature in my library. I think the advantage of protcol is the fact that messages can be sent using a simple serial or telnet session. Messages can be sent to a device using simple ASCII commands. Implementing this feature would complicate the library. The main purpose for sending messages in binary would be to reduce latency which is not the main purpose of this library.
Source Code
If you would like to view the libraries source code and demo applications the code can be found on my Bit Bucket site.
https://bitbucket.org/chrism233/pibot
The git repository address is as follows:
https://chrism233@bitbucket.org/chrism233/pibot.git
History
Date | Changes |
25/08/2014 | Initial release. |