LCM通讯的使用

本文主要介绍LCM通讯的基本使用,内容主要整理自官网

https://lcm-proj.github.io/lcm/index.html

LCM,即Library for Communication and Marshalling,是一组用于消息传递与数据封装的库和工具,它主要的面向对象是要求高带宽、低延迟的实时系统。

它使用publish/subscribe的方式进行进程间通信,和ROS的话题有点类似。

既然涉及进程间通信,那么使用什么编程语言就无关紧要了,即不同编程语言编写的程序都可以互相通信。

LCM主要具有以下特点:

  • 低延迟进程间通信

  • 使用UDP组播的高效广播机制

  • 类型安全的编组/解组

  • 用户友好的日志记录和回放功能

  • 点对点通信

  • 无需守护进程

  • 依赖少

本文介绍LCM的基本使用。本文环境为ubuntu20.04

LCM下载

进入官网,下载安装包

https://github.com/lcm-proj/lcm/releases

image-20240712223302612

环境构建

首先安装LCM的依赖:

sudo apt install build-essential cmake libglib2.0-dev

然后将包放到自己的目录下解压,执行以下步骤:

mkdir build

cd build

cmake ..

make

sudo make install

至此环境构建完成。

简单程序编写

简单体验下LCM的通讯方式,此处使用C++语言,使用官网例程做示范。

  • 选择一个路径创建项目的文件夹

    image-20240712224402425

  • 定义消息类型,创建发布方、接收方,编写CMakeLists文件

    image-20240712224622049

    四个文件的内容如下:

    • example_t.lcm

      package exlcm;
      
      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_publisher.cpp

      #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;
      
          my_data.orientation[0] = 1;
          my_data.orientation[1] = 0;
          my_data.orientation[2] = 0;
          my_data.orientation[3] = 0;
      
          my_data.num_ranges = 15;
          my_data.ranges.resize(my_data.num_ranges);
          for(int i = 0; i < my_data.num_ranges; i++)
              my_data.ranges[i] = i;
      
          my_data.name = "example string";
          my_data.enabled = true;
      
          lcm.publish("EXAMPLE", &my_data);
      
          return 0;
      }
      
    • lcm_receiver.cpp

      
      #include <stdio.h>
      #include <lcm/lcm-cpp.hpp>
      #include "exlcm/example_t.hpp"
      
      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("  orientation = (%f, %f, %f, %f)\n",
                          msg->orientation[0], msg->orientation[1], 
                          msg->orientation[2], msg->orientation[3]);
                  printf("  ranges:");
                  for(i = 0; i < msg->num_ranges; i++)
                      printf(" %d", msg->ranges[i]);
                  printf("\n");
                  printf("  name        = '%s'\n", msg->name.c_str());
                  printf("  enabled     = %d\n", msg->enabled);
              }
      };
      
      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;
      }
      
    • CMakeLists.txt

      cmake_minimum_required(VERSION 3.1)
      
      project(lcm_cpp_example)
      
      find_package(lcm REQUIRED)
      include(${LCM_USE_FILE})
      
      # Put all message definition files in the type directory in one list
      FILE(GLOB example_message_definitions "${CMAKE_CURRENT_LIST_DIR}/../types/*.lcm")
      
      # Generate headers from message definition
      lcm_wrap_types(CPP_HEADERS cpp_headers
        ${example_message_definitions})
      
      # Create library from all the messages
      lcm_add_library(example_messages-cpp CPP ${cpp_headers})
      target_include_directories(example_messages-cpp INTERFACE
        $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>)
      
      # Create executables for the three example programs, linking all of them to our
      # messages library and lcm
      
      add_executable(lcm_receiver "lcm_receiver.cpp")
      lcm_target_link_libraries(lcm_receiver example_messages-cpp ${LCM_NAMESPACE}lcm)
      
      add_executable(lcm_publisher "lcm_publisher.cpp")
      lcm_target_link_libraries(lcm_publisher example_messages-cpp ${LCM_NAMESPACE}lcm)
      

    以上文件中头文件exlcm/example_t.hpp是缺失的,这时我们在目录下执行lcm-gen -x example_t.lcm,则可以看到当前目录下生成了exlcm文件夹,下面自动生成了example_t.hpp

    image-20240712225454876

    example_t.hpp的内容如下:

    /** THIS IS AN AUTOMATICALLY GENERATED FILE.  DO NOT MODIFY
     * BY HAND!!
     *
     * Generated by lcm-gen
     **/
    
    #ifndef __exlcm_example_t_hpp__
    #define __exlcm_example_t_hpp__
    
    #include <lcm/lcm_coretypes.h>
    
    #include <vector>
    #include <string>
    
    namespace exlcm
    {
    
    class example_t
    {
        public:
            int64_t    timestamp;
    
            double     position[3];
    
            double     orientation[4];
    
            int32_t    num_ranges;
    
            std::vector< int16_t > ranges;
    
            std::string name;
    
            int8_t     enabled;
    
        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 read 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_t"
             */
            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 uint64_t _computeHash(const __lcm_hash_ptr *p);
    };
    
    int example_t::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_t::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_t::getEncodedSize() const
    {
        return 8 + _getEncodedSizeNoHash();
    }
    
    int64_t example_t::getHash()
    {
        static int64_t hash = static_cast<int64_t>(_computeHash(NULL));
        return hash;
    }
    
    const char* example_t::getTypeName()
    {
        return "example_t";
    }
    
    int example_t::_encodeNoHash(void *buf, int offset, int maxlen) const
    {
        int pos = 0, tlen;
    
        tlen = __int64_t_encode_array(buf, offset + pos, maxlen - pos, &this->timestamp, 1);
        if(tlen < 0) return tlen; else pos += tlen;
    
        tlen = __double_encode_array(buf, offset + pos, maxlen - pos, &this->position[0], 3);
        if(tlen < 0) return tlen; else pos += tlen;
    
        tlen = __double_encode_array(buf, offset + pos, maxlen - pos, &this->orientation[0], 4);
        if(tlen < 0) return tlen; else pos += tlen;
    
        tlen = __int32_t_encode_array(buf, offset + pos, maxlen - pos, &this->num_ranges, 1);
        if(tlen < 0) return tlen; else pos += tlen;
    
        if(this->num_ranges > 0) {
            tlen = __int16_t_encode_array(buf, offset + pos, maxlen - pos, &this->ranges[0], this->num_ranges);
            if(tlen < 0) return tlen; else pos += tlen;
        }
    
        char* name_cstr = const_cast<char*>(this->name.c_str());
        tlen = __string_encode_array(
            buf, offset + pos, maxlen - pos, &name_cstr, 1);
        if(tlen < 0) return tlen; else pos += tlen;
    
        tlen = __boolean_encode_array(buf, offset + pos, maxlen - pos, &this->enabled, 1);
        if(tlen < 0) return tlen; else pos += tlen;
    
        return pos;
    }
    
    int example_t::_decodeNoHash(const void *buf, int offset, int maxlen)
    {
        int pos = 0, tlen;
    
        tlen = __int64_t_decode_array(buf, offset + pos, maxlen - pos, &this->timestamp, 1);
        if(tlen < 0) return tlen; else pos += tlen;
    
        tlen = __double_decode_array(buf, offset + pos, maxlen - pos, &this->position[0], 3);
        if(tlen < 0) return tlen; else pos += tlen;
    
        tlen = __double_decode_array(buf, offset + pos, maxlen - pos, &this->orientation[0], 4);
        if(tlen < 0) return tlen; else pos += tlen;
    
        tlen = __int32_t_decode_array(buf, offset + pos, maxlen - pos, &this->num_ranges, 1);
        if(tlen < 0) return tlen; else pos += tlen;
    
        if(this->num_ranges) {
            this->ranges.resize(this->num_ranges);
            tlen = __int16_t_decode_array(buf, offset + pos, maxlen - pos, &this->ranges[0], this->num_ranges);
            if(tlen < 0) return tlen; else pos += tlen;
        }
    
        int32_t __name_len__;
        tlen = __int32_t_decode_array(
            buf, offset + pos, maxlen - pos, &__name_len__, 1);
        if(tlen < 0) return tlen; else pos += tlen;
        if(__name_len__ > maxlen - pos) return -1;
        this->name.assign(
            static_cast<const char*>(buf) + offset + pos, __name_len__ - 1);
        pos += __name_len__;
    
        tlen = __boolean_decode_array(buf, offset + pos, maxlen - pos, &this->enabled, 1);
        if(tlen < 0) return tlen; else pos += tlen;
    
        return pos;
    }
    
    int example_t::_getEncodedSizeNoHash() const
    {
        int enc_size = 0;
        enc_size += __int64_t_encoded_array_size(NULL, 1);
        enc_size += __double_encoded_array_size(NULL, 3);
        enc_size += __double_encoded_array_size(NULL, 4);
        enc_size += __int32_t_encoded_array_size(NULL, 1);
        enc_size += __int16_t_encoded_array_size(NULL, this->num_ranges);
        enc_size += this->name.size() + 4 + 1;
        enc_size += __boolean_encoded_array_size(NULL, 1);
        return enc_size;
    }
    
    uint64_t example_t::_computeHash(const __lcm_hash_ptr *)
    {
        uint64_t hash = 0x1baa9e29b0fbaa8bLL;
        return (hash<<1) + ((hash>>63)&1);
    }
    
    }
    
    #endif
    

测试

创建build文件夹,进入,执行以下命令:
cd build

cmake ..

make

可以看到当前目录下生成了对应的可执行文件:

image-20240712225838113

image-20240712230131481

运行lcm_receiver

重新开一个终端运行lcm_publisher,可以看到:

image-20240712230045459

通信成功。

小结

本文主要介绍了机器人中LCM通讯的入门使用。感兴趣的朋友可以去官网进一步学习。

实际上,LCM和ROS的话题通讯方式在使用上十分类似,不过,正如开头所说,LCM的面向对象是要求高带宽、低延迟的实时系统,如机械臂的控制系统;与之相比ROS的通信机制更复杂,通信延时会更高一点,在这一点上LCM比ROS1的表现更好。

但是ROS有更强大的生态社区,在做一些复杂的功能时,ROS有更丰富的工具帮助快速搭建系统,如rvizrqt等。因此二者也是各有优劣。

  • 16
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值