CmdMessenger





5.00/5 (11投票s)
一篇关于全向 Arduino Yun 机器人和 CmdMessenger 的替代 .Net 实现的文章。
引言
我最近一直在构建一个全向机器人,并在此处记录。由于我不想让这篇文章过长,我将把文档分成几篇文章。
在第一篇文章中,我将讨论用于与机器人通信的通信协议。您可以在下面看到机器人的图片和视频:
背景
我过去曾参与过许多需要串行通信的 Arduino 平台项目。当发送给 Arduino 的消息变得更复杂时,我经常在使用串行通信时遇到障碍。我开始寻找解决这个问题的方法,很快就在 Arduino playground 上发现了 CmdMessenger。
来自库主页
引用CmdMessenger 是一个用于 Arduino 平台(和 .NET/Mono 平台)的消息库。它使用串口作为传输层。要使用 CmdMessenger,我们定义一个命令标识符列表,然后为收到的消息附加回调/处理函数。
该库是我试图解决的串行通信问题的绝佳解决方案。然而,该协议的 .Net 实现不符合我的要求。现有的 .Net 库与使用串口作为传输层紧密耦合。由于 Raspberry Pi 和 Arduino Yun 等许多设备现在都支持 WiFi,我希望能够通过 TCP/IP 发送消息。
然后可以使用一个简单的 Python 脚本将通过 TCP/IP 发送的消息代理到 Arduino。请参阅下面的图表。
我正在使用一个名为 tcp_serial_redirect.py 的 Python 脚本,它作为守护进程在 Linux 设备上运行。我不会在本文中介绍配置设备的步骤。在 Arduino Yun 发布之前,我使用的是 Raspberry Pi 作为 Linux 设备,并与 Arduino Uno 配合使用。
协议
CmdMessenger 定义的协议简单且非常灵活。协议的格式如下:
字符串转义参数
由于字符串可能包含参数或命令分隔符,因此协议必须支持这些特殊字符的转义。特殊字符通过在其后面加上转义字符来转义。默认情况下,转义字符是反斜杠。例如:
- 5,Hello,World; - 将显示为一个带有三个参数的命令。
- 5,Hello\,World; - 将显示为一个带有两个参数的命令。
所有这些都由库处理,因此客户端无需了解这些细节。
架构
以下类图显示了库的基本概述。
CmdMessenger
CmdMessenger 是库的主要入口点。
- Start - 打开连接并开始处理传入命令。
- Stop - 关闭连接并停止读取传入命令。
- Send - 发送 ISendCommand 并阻塞直到收到响应。
- SendAsync - 发送 ISendCommand 并返回 Taks<IRecievedCommand>。
- Register - 允许方法或 ICommandObserver 订阅具有指定 ID 的传入命令。
IReceivedCommand
IReceivedCommand 提供了一个用于读取传入命令的接口。
- ReadInt16 - 从命令中读取一个 16 位整数。
- ReadBool - 从命令中读取一个布尔值,其中 1, 0 分别表示 true, false。
- ReadInt32 - 从命令中读取一个 32 位整数。
- ReadString - 从命令中读取一个字符串。
ReceivedCommand
ReceivedCommand 是 IReceivedCommand 的具体实现。客户端无需实现该接口。
ISendCommand
ISendCommand 提供了一个用于发送传出命令的接口。
SendCommand
SendCommand 提供了 ISendCommand 接口的具体实现。客户端可以直接使用该类,或者通过创建派生自该类的命令来扩展库。
ICmdComms
ICmdComms 为传输层提供了一个通用接口。
CmdComms
提供了从流中读取命令的通用实现。其他基于流的传输层可以从该基类派生。库提供了 ICmdComms 的两个实现:
- SerialCmdClient
- TcpCmdClient
简单演示
源代码包含一个简单的演示应用程序,它演示了发送命令和处理其响应。演示显示了以下步骤:
- CmdMessenger 首先与传输层一起实例化。
- 注册一个命令处理程序来处理响应命令。
- 调用 start 方法以打开与传输层的连接并开始处理命令。
- 向 CmdMessenger 服务器发送一个命令,并等待直到服务器响应 CommandID 为 1。
// Create an instance of CmdMessenger and provide an implementation of the transport layer.
var cmdClient = new CmdMessenger.CmdMessenger(new TcpCmdClient("127.0.0.1", 5000));
// Register one or more command handlers.
cmdClient.Register(1, r => Console.WriteLine("Response received"));
// Open the connection and begin processing incoming commands.
cmdClient.Start();
while (Console.ReadLine() != "x")
{
// Send a simple command with no arguments.
cmdClient.Send(new SendCommand(0, 1));
}
命令可以是单向或双向的。如果发送命令定义了 AckCommandID,则发送命令将阻塞,直到收到响应或达到超时。
// Will block until the device responds with a commandID of 1
cmdClient.Send(new SendCommand(0, 1);
// Will return imedietly since no AckCommandID has been defiened.
cmdClient.Send(new SendCommand(0);
该库支持 C#5 的 SendAsync 方法。
Task<IReceivedCommand> received = cmdClient.SendAsync(new SendCommand(0, 1);
// Do some other work
// Wait for the response
await received;
机器人控制器应用程序
源代码包含一个用于控制机器人的 WPF 应用程序。该应用程序通过添加额外的抽象层进一步扩展了功能。
机器人控制器由四个组件组成:
- CmdMessenger - CmdMessenger 库。
- PiBot.Common - 一个类库,在 CmdMessenger 之上添加了一个抽象层。
- ControlPad - 一个自定义控件(我可能会在后续文章中记录它)。
- PiBot.Gui - 主应用程序。
PiBot.Common - 机器人命令
PiBot.Common 包含许多命令。我计划在某个阶段为机器人添加传感器和云台摄像机。我还没有添加这些功能,因此我只完整实现了几个命令。
- SetMotorSpeed
- SendMotorSpeed
PitBot.Gui - 应用程序
机器人控制器应用程序易于使用。
- 从组合框中选择“地址 = 127.0.0.1,端口 5000”。
- 点击连接,控制器将等待直到建立连接。
- 建立连接后,控制板将启用,允许您点击控制方向。
由于您没有我的机器人,我创建了一个可以与机器人控制器应用程序一起使用的测试工具。
Arduino 代码
以下是 Arduino 代码的代码转储。
要点
我正在使用 VEX 机器人公司的四个电机控制器来驱动机器人 Motor Controller 29。电机控制器需要 1ms 到 2ms 范围的 PWM 信号,其中 1ms 为全反转,2ms 为全正转,1.5ms 为中性。通过测试,我能够使用 Arduino Servo 库生成所需的 PWM 信号。我发现角度 40 给我全反转,角度 140 给我全正转,因此可用速度范围是 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
{
//Request the device to send it's motor speed.
GetMotorSpeed = 1,
// Response message to a GetMotorSpeed message.
GetMotorSpeedResponse,
// Sets the motor speed on the device.
SetMotorSpeed,
// Message send when an external device changes the motor speed.
SendMotorSpeed,
// Request the current pan tilt position from the device.
GetPanTiltPos,
// Response message to a GetPanTiltPos message.
GetPanTiltPosResponse,
// Set the pan tilt position on the device.
SetPanTiltPos,
// Message sent when an external device modifies the pan tilt position.
SendPanTiltPos,
// Get the current value for a specified sensor.
GetSensorValue,
// Response message to a GetSensorValue.
GetSensorValueResponse,
//Set sensor value
SetSensorValue,
// Message sent when a sensor value changes.
SendSensorValue,
// A fault has occurred.
Fault
};
// Commands we send from the PC and want to receive on the Arduino
// We must define a callback function in our Arduino program for each entry in the list.
messengerCallbackFunction messengerCallbacks[] =
{
HandleGetMotorSpeed,
unknownCmd,
HandleSetMotorSpeed,
unknownCmd,
NULL
};
// ---------------------- C A L L B A C K M E T H O D S ----------------------------------
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);
}
// ----------------------- D E F A U L T C A L L B A C K S --------------------------------------
void unknownCmd()
{
// Default response for unknown commands and corrupt messages
cmdMessenger.sendCmd(Fault,"Unknown command");
}
// ------------------ E N D C A L L B A C K M E T H O D S ------------------
// ------------------ S E T U P ----------------------------------------------
void attach_callbacks(messengerCallbackFunction* callbacks)
{
int i = 0;
int offset = 1;
while(callbacks[i])
{
cmdMessenger.attach(offset+i, callbacks[i]);
i++;
}
}
void setup()
{
// Listen on the serial connection for messages from pc
Serial.begin(57600);
servo1.attach(8);
servo2.attach(9);
servo3.attach(10);
servo4.attach(11);
// Pront line break between messages.
cmdMessenger.print_LF_CR();
cmdMessenger.attach(unknownCmd);
attach_callbacks(messengerCallbacks);
}
// ------------------ E N D S E T U P ----------------------------------
void loop()
{
cmdMessenger.feedinSerialData();
}
关注点
支持二进制格式 (高效)
CmdMessenger 库的原始 C# 实现支持以二进制格式发送消息。我决定不在我的库中实现此功能。我认为协议的优点是消息可以使用简单的串行或 telnet 会话发送。消息可以使用简单的 ASCII 命令发送到设备。实现此功能会使库复杂化。以二进制形式发送消息的主要目的是减少延迟,这不是此库的主要目的。
源代码
如果您想查看库的源代码和演示应用程序,可以在我的 Bit Bucket 网站上找到代码。
https://bitbucket.org/chrism233/pibot
git 存储库地址如下:
https://chrism233@bitbucket.org/chrism233/pibot.git
历史
日期 | 变更 |
---|---|
25/08/2014 | 初始发布。 |