- 這幾天寫了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);