LCM is a set of libraries and tools for message passing and data marshalling,被广泛使用与无人车通信。其官方文档地址为:http://lcm-proj.github.io/
这里我们不讲安装,我们只讲使用。(安装在文档中都写有)http://lcm-proj.github.io/build_instructions.html
LCM可以将数据进行封装和发送,使用UDP组播的方式发送出去。
首先我们需要有我们自己的数据。LCM给我们提供了一个程序,可以很简单的将我们的结构体转变为LCM需要的格式(这里必须转变,否则LCM将无法发送或封装),这个程序就是lcm-gen
在官方文档上有一个表格,详细记载了lcm-gen的使用方法,注意这里需要使用命令行,linux很简单,windows上需要使用cmd来执行操作。
可以看到lcm-gen程序的参数和使用方法,这里我们只讨论C++,所以使用lcm-gen -x 就可以了。
首先我们需要定义一个我们自己想要的结构体
struct Example
{
int8_t a;
double b;
};
这里只是举个例子,没有使用很复杂的结构体,官方文档上的例子很复杂,也很典型。注意这里,结构体里面的数据类型有些必须是lcm自己定义的数据类型(比如int不能直接使用)。lcm支持的数据类型有这些:
确定我们自己的结构体之后,我们需要按照规则将我们的结构体进行一定的更改才能使用我们的lcm-gen程序。
首先创建一个以.lcm结尾的文件(使用记事本打开),然后写上
package exlcm;
struct Example
{
int8_t a;
double b;
}
注意这里,package 后面表示的是域名,是生成的类所属的namespace,还有就是struct后面不需要加”;”记住一定不要加。接下来就是对我们的l.lcm文件执行lcm-gen程序。
lcm-gen -x l.lcm
然后会生成一个以我们的域名为名称的文件夹,文件夹下有一个以我们结构体名称为名字的.hpp文件,那么这个.hpp文件就是lcm所需要的数据类型,同时也满足我们自身结构体的设计初衷。
打开我们的Exanple.hpp文件:
/** THIS IS AN AUTOMATICALLY GENERATED FILE. DO NOT MODIFY
* BY HAND!!
*
* Generated by lcm-gen
**/
#include
#ifndef __exlcm_Example_hpp__
#define __exlcm_Example_hpp__
namespace exlcm
{
class Example
{
public:
int8_t a;
double b;
public:
/**
* Encode a message into binary form.
*
* @param buf The output buffer.
* @param offset Encoding starts at thie byte offset into @p buf.
* @param maxlen Maximum number of bytes to write. This should generally be
* equal to getEncodedSize().
* @return The number of bytes encoded, or <0 on error.
*/
inline int encode(void *buf, int offset, int maxlen) const;
/**
* Check how many bytes are required to encode this message.
*/
inline int getEncodedSize() const;
/**
* Decode a message from binary form into this instance.
*
* @param buf The buffer containing the encoded message.
* @param offset The byte offset into @p buf where the encoded message starts.
* @param maxlen The maximum number of bytes to reqad while decoding.
* @return The number of bytes decoded, or <0 if an error occured.
*/
inline int decode(const void *buf, int offset, int maxlen);
/**
* Retrieve the 64-bit fingerprint identifying the structure of the message.
* Note that the fingerprint is the same for all instances of the same
* message type, and is a fingerprint on the message type definition, not on
* the message contents.
*/
inline static int64_t getHash();
/**
* Returns "Example"
*/
inline static const char* getTypeName();
// LCM support functions. Users should not call these
inline int _encodeNoHash(void *buf, int offset, int maxlen) const;
inline int _getEncodedSizeNoHash() const;
inline int _decodeNoHash(const void *buf, int offset, int maxlen);
inline static int64_t _computeHash(const __lcm_hash_ptr *p);
};
int Example::encode(void *buf, int offset, int maxlen) const
{
int pos = 0, tlen;
int64_t hash = getHash();
tlen = __int64_t_encode_array(buf, offset + pos, maxlen - pos, &hash, 1);
if(tlen < 0) return tlen; else pos += tlen;
tlen = this->_encodeNoHash(buf, offset + pos, maxlen - pos);
if (tlen < 0) return tlen; else pos += tlen;
return pos;
}
int Example::decode(const void *buf, int offset, int maxlen)
{
int pos = 0, thislen;
int64_t msg_hash;
thislen = __int64_t_decode_array(buf, offset + pos, maxlen - pos, &msg_hash, 1);
if (thislen < 0) return thislen; else pos += thislen;
if (msg_hash != getHash()) return -1;
thislen = this->_decodeNoHash(buf, offset + pos, maxlen - pos);
if (thislen < 0) return thislen; else pos += thislen;
return pos;
}
int Example::getEncodedSize() const
{
return 8 + _getEncodedSizeNoHash();
}
int64_t Example::getHash()
{
static int64_t hash = _computeHash(NULL);
return hash;
}
const char* Example::getTypeName()
{
return "Example";
}
int Example::_encodeNoHash(void *buf, int offset, int maxlen) const
{
int pos = 0, tlen;
tlen = __int8_t_encode_array(buf, offset + pos, maxlen - pos, &this->a, 1);
if(tlen < 0) return tlen; else pos += tlen;
tlen = __double_encode_array(buf, offset + pos, maxlen - pos, &this->b, 1);
if(tlen < 0) return tlen; else pos += tlen;
return pos;
}
int Example::_decodeNoHash(const void *buf, int offset, int maxlen)
{
int pos = 0, tlen;
tlen = __int8_t_decode_array(buf, offset + pos, maxlen - pos, &this->a, 1);
if(tlen < 0) return tlen; else pos += tlen;
tlen = __double_decode_array(buf, offset + pos, maxlen - pos, &this->b, 1);
if(tlen < 0) return tlen; else pos += tlen;
return pos;
}
int Example::_getEncodedSizeNoHash() const
{
int enc_size = 0;
enc_size += __int8_t_encoded_array_size(NULL, 1);
enc_size += __double_encoded_array_size(NULL, 1);
return enc_size;
}
int64_t Example::_computeHash(const __lcm_hash_ptr *)
{
int64_t hash = 0xd4555bd74f10486bLL;
return (hash<<1) + ((hash>>63)&1);
}
}
#endif
这里所有的代码都不需要我们管,我们只需要将它当做我们自己设计的结构体来用就行,通过这个类,LCM就可以将我们的需要和LCM的规范整合到一起来。
lcm-gen程序在lcm文件夹的lcmgen文件夹下。
LCM关于C++的API有四个类,最重要的就是LCM类,其中有很多的成员方法和成员变量,但是其使用时非常简单和方便的。
下面是一个十分简单的例子,也是官方给的例子:
#include
#include "exlcm/Example.hpp"
int main(int argc, char ** argv)
{
lcm::LCM lcm;
if(!lcm.good())
return 1;
exlcm::Example my_data;
my_data.a=5;
my_data.b=5.0;
lcm.publish("EXAMPLE", &my_data);
return 0;
}
当然,这里使用的exlcm::Example类型是我们使用lcm-gen程序自动生成的。
整个程序就是声明一个lcm对象,然后声明一个我们的通过lcm-gen自动生成的数据类型的对象,然后对数据对象进行赋值,然后调用publish函数,这里主要的就是最后的
lcm.publish("EXAMPLE", &my_data);
这句代码将我们赋值好的数据发送出去,这个函数有两个参数,第一个参数为channel name,第二个参数是我们要发送的数据对象的指针。
这里出现了一个channel name这个东西的作用是标识我们的数据,后面说道接收lcm消息的时候,会用到这个channel name(接收channel name为“XXX”的数据,并调用某回调函数)。
LCM的发送是非常简单的,只需要调用此接口就可以了。
数据的接收会有点麻烦,当然LCM将接口封装的很漂亮,官方例子是这样写的
int main(int argc, char** argv)
{
lcm::LCM lcm;
if(!lcm.good())
return 1;
Handler handlerObject;
lcm.subscribe("EXAMPLE", &Handler::handleMessage, &handlerObject);
while(0 == lcm.handle());
return 0;
}
简单的令人讶异的代码。
首先声明一个lcm对象,然后调用subscribe方法,然后while调用handle方法便结束了。
当然肯定不会那么简单,首先看一下Handler类的生命和定义:
class Handler
{
public:
~Handler() {}
void handleMessage(const lcm::ReceiveBuffer* rbuf,
const std::string& chan,
const exlcm::example_t* msg)
{
cout<a<b<
这个就是官方例子中的Handler类,handleMessage方法就是我们接受到消息的回调函数,那么subscribe方法的参数意义不言而喻,第一个是channel name,第二个参数是回调函数,第三个参数是回调函数所在的对象的指针。
channel name很重要,当接受到的消息的channel name与我们的subcribe方法的channel name不一样时,我们的回调函数是不会调用的,channel name就相当于套接字描述符,而subcribe就相当于select中的注册函数。
handle方法内部其实就是一个recvfrom调用,handle方法会阻塞在recvfrom调用,当接受到消息时,结束handle方法,成功返回0,失败返回 -1,所以如果想要一直接收消息,请使用while循环。