初试LCM

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 <lcm/lcm_coretypes.h>

#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 <lcm/lcm-cpp.hpp>

#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<<msg->a<<endl<<msg->b<<endl;
        }
};

这个就是官方例子中的Handler类,handleMessage方法就是我们接受到消息的回调函数,那么subscribe方法的参数意义不言而喻,第一个是channel name,第二个参数是回调函数,第三个参数是回调函数所在的对象的指针。
channel name很重要,当接受到的消息的channel name与我们的subcribe方法的channel name不一样时,我们的回调函数是不会调用的,channel name就相当于套接字描述符,而subcribe就相当于select中的注册函数。

handle方法内部其实就是一个recvfrom调用,handle方法会阻塞在recvfrom调用,当接受到消息时,结束handle方法,成功返回0,失败返回 -1,所以如果想要一直接收消息,请使用while循环。

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值