- 这几天写了lcm发、收、录、读的小工具,之前都用过发和收,这次主要还是录和读,就是把接收到的lcm消息保存下来,再通过另外一个读的程序把数据读出来,用于离线分析等。
- LCM(Lightweight Communications and
Marshalling)在实时系统的数据传递中经常被使用。它具有高带宽、低延迟的特点,支持多种编程语言,API使用方便。官方文档提供了基本的使用说明,以及函数的具体代表。
发送消息
step1:定义结构体
1、将要发送的数据,定义在一个结构体中,写在以 .lcm 结尾的文件中。比如:
struct example_t
{
int64_t timestamp;
double position[3];
double orientation[4];
int32_t num_ranges;
int16_t ranges[num_ranges];
string name;
boolean enabled;
}
其中,数据类型需要使用lcm支持的类型,如下:
定义好数据结构后,生成对应语言使用的头文件。使用如下指令:
没有问题的话就会生成example_t.hpp,这个文件是不允许修改的。
step2:LCM初始化
数据结构的头文件生成后,在主程序代码里#include进来
#include <lcm/lcm-cpp.hpp>
#include “example_t.hpp"
定义lcm的对象并初始化
lcm::LCM lcm;
if(!lcm.good())
return 1;
初始化干了什么呢?我们进去看一下:
inline bool
LCM::good() const
{
return this->lcm != NULL;
}
好像也没干什么?
step3:赋值并发送
在程序中定义一个数据结构的对象,并在相应的位置进行赋值
example_t my_data;
my_data.timestamp = 0;
my_data.position[0] = 1;
my_data.position[1] = 2;
my_data.position[2] = 3;
发送
lcm.publish("EXAMPLE", &my_data);
其中"EXAMPLE"为通道名,可以自由定义,接收的时候也要指定,二者保持一致才能收到。
发送的数据较多时,可以定义多个数据结构,相应会有多个lcm生成的头文件。用不同的通道名来区分他们。发送时也可灵活选择对哪些通道的数据进行发送。
至此,lcm发送的方式就结束了,还是很简洁的。整个发送代码如下:
#include <lcm/lcm-cpp.hpp>
#include "exlcm/example_t.hpp"
int main(int argc, char ** argv)
{
lcm::LCM lcm;
if(!lcm.good())
return 1;
exlcm::example_t my_data;
my_data.timestamp = 0;
my_data.position[0] = 1;
my_data.position[1] = 2;
my_data.position[2] = 3;
lcm.publish("EXAMPLE", &my_data);
return 0;
}
接收消息
消息接收主要利用lcm.subscribe()函数,函数定义如下:
LCM::subscribe(const std::string& channel,
void (MessageHandlerClass::*handlerMethod)(const ReceiveBuffer* rbuf, const std::string& channel, const MessageType* msg),
MessageHandlerClass* handler)
上面需要3个参数,分别是(通道名、回调函数、句柄对象)。
subscribe()还有另一种重载形式,其中回调函数只要2个参数,不用绑定具体的类对象。
LCM::subscribe(const std::string& channel,
void (MessageHandlerClass::*handlerMethod)(const ReceiveBuffer* rbuf, const std::string& channel),
MessageHandlerClass* handler)
step1:LCM初始化
#include <stdio.h>
#include <lcm/lcm-cpp.hpp>
#include "exlcm/example_t.hpp"
int main(int argc, char** argv)
{
lcm::LCM lcm;
if(!lcm.good())
return 1;
}
step2:定义句柄及回调函数
句柄类可以如下定义,其中handleMessage即为subscribe时使用的回调函数:
class Handler
{
public:
~Handler() {}
void handleMessage(const lcm::ReceiveBuffer* rbuf,
const std::string& chan,
const exlcm::example_t* msg)
{
int i;
printf("Received message on channel \"%s\":\n", chan.c_str());
printf(" timestamp = %lld\n", (long long)msg->timestamp);
printf(" position = (%f, %f, %f)\n",
msg->position[0], msg->position[1], msg->position[2]);
printf("\n");
}
};
上面代码将接收到的内容打印出来,可以看到,使用了之前定义的消息类型example_t
step3:接收消息
定义句柄对象,调用subscribe()函数接收。其中lcm.handle()
Handler handlerObject;
lcm.subscribe("EXAMPLE", &Handler::handleMessage, &handlerObject);
while(0 == lcm.handle());
LCM自动解码消息,再传给回调函数,回调函数可以识别消息类型。因为回调函数在lcm.handle()方法中调度,所以不需要并发执行,这些都在一个单线程中完成。
调用lcm.handle()非常重要,函数会保持阻塞直到有任务需要做。
完整的接收代码如下,代码只会接收通道名为”EXAMPLE"的消息。
1 #include <stdio.h>
2 #include <lcm/lcm-cpp.hpp>
3 #include "exlcm/example_t.hpp"
4
5 class Handler
6 {
7 public:
8 ~Handler() {}
9
10 void handleMessage(const lcm::ReceiveBuffer* rbuf,
11 const std::string& chan,
12 const exlcm::example_t* msg)
13 {
14 int i;
15 printf("Received message on channel \"%s\":\n", chan.c_str());
16 printf(" timestamp = %lld\n", (long long)msg->timestamp);
17 printf(" position = (%f, %f, %f)\n",
18 msg->position[0], msg->position[1], msg->position[2]);
28 }
29 };
30
31 int main(int argc, char** argv)
32 {
33 lcm::LCM lcm;
34 if(!lcm.good())
35 return 1;
36
37 Handler handlerObject;
38 lcm.subscribe("EXAMPLE", &Handler::handleMessage, &handlerObject);
39
40 while(0 == lcm.handle());
41
42 return 0;
43 }
一个lcm对象可以有无限个接收器,如果有多个通道的消息需要接收,可以
lcm.subscribe("AAAA", &Handler::handleMessage, &handlerObject);
lcm.subscribe("BBBB", &Handler::handleMessage, &handlerObject);
或者用下面方式接收所有通道
lcm.subscribe(".*", &Handler::handleMessage, &handlerObject);