一次成功流水账-安装LCM 多机通信 lcm-spy数据实时可视化

这篇博客介绍了如何在Ubuntu 16.04及更高版本上安装LCM库,并通过CMake构建工程。首先,详细列出了安装LCM及其依赖的步骤,接着展示了如何生成并使用LCM消息类型。然后,提供了发送、接收和读取LCM日志的示例代码。通过编译和运行这些示例,可以在同一网络环境下实现数据的发送和接收。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

主要步骤:

1. 安装LCM

2. 使用CMake组织工程并编译

3. 报错处理,Check your routing tables and firewall settings

4. LCM多机通讯要点

5. 配置和使用lcm-spy的实时数据可视化功能

1. 安装LCM

1. 安装依赖,使用的是Ubuntu16.04的环境,后续验证ubuntu 20.04,该教程同样可以跑通。

# 必选
sudo apt install build-essential libglib2.0-dev cmake
# 可选,可根据使用的语言选择安装
sudo apt install default-jdk python-all-dev liblua5.1-dev golang doxygen

2. 直接使用指令安装(Installing LCM — LCM documentation

sudo apt install liblcm-dev

或源码安装:下载、编译并安装

1.3版本是长期支持版本,经过了验证,可以选择此版本。离线下载源代码GitHub - lcm-proj/lcm: Lightweight Communications and Marshalling

下载后,先运行./bootstrap.sh生成configure文件,然后依次运行./configre  ;    make   ;  make install指令,即可成功安装。

# 拷贝源码主分支
git clone https://github.com/lcm-proj/lcm.git
#或1.3lts分支都尝试一下
# git clone --branch v1.3-lts https://github.com/lcm-proj/lcm.git

cd lcm
# 编译并安装
mkdir build && cd build
cmake ..
make -j4
sudo make install

2. 使用CMake组织工程并跑通通信示例

生成头文件:

创建 lcm_test 文件夹,新建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_test 文件夹,运行

lcm-gen -x example_t.lcm

命令生成消息类型,成功运行后会生成 exlcm 文件夹,文件夹中有example_t.hpp的头文件。若没有识别lcm-gen命令,则需要检查是否成功安装 lcm,或是否将 lcm 编译结果添加进环境变量。

创建lcm_send.cpplcm_receive.cppread_log.cppexample_t.lcmCMakeLists.txt五个文件,文件内容如下。

lcm_send.cpp:

#include <lcm/lcm-cpp.hpp>

#include "exlcm/example_t.hpp"

int main(int argc, char ** argv)
{
    lcm::LCM lcm("udpm://239.255.76.67:7667?ttl=1");
    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 from computer1";
    my_data.enabled = true;
    lcm.publish("EXAMPLE", &my_data);
    return 0;
}

lcm_receive.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("udpm://239.255.76.67:7667?ttl=1");
    if(!lcm.good())
        return 1;
    Handler handlerObject;
    lcm.subscribe("EXAMPLE", &Handler::handleMessage, &handlerObject);
    while(0 == lcm.handle());
    return 0;
}

read_log.cpp:

#include <stdio.h>

#include <lcm/lcm-cpp.hpp>

#include "exlcm/example_t.hpp"

int main(int argc, char **argv)
{
    if (argc < 2) {
        fprintf(stderr, "usage: read_log <logfile>\n");
        return 1;
    }

    // Open the log file.
    lcm::LogFile log(argv[1], "r");
    if (!log.good()) {
        perror("LogFile");
        fprintf(stderr, "couldn't open log file %s\n", argv[1]);
        return 1;
    }

    while (1) {
        // Read a log event.
        const lcm::LogEvent *event = log.readNextEvent();
        if (!event)
            break;

        // Only process messages on the EXAMPLE channel.
        if (event->channel != "EXAMPLE")
            continue;

        // Try to decode the message.
        exlcm::example_t msg;
        if (msg.decode(event->data, 0, event->datalen) != event->datalen)
            continue;

        // Decode success!  Print out the message contents.
        printf("Message:\n");
        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 (int 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);
    }

    // Log file is closed automatically when the log variable goes out of
    // scope.

    printf("done\n");
    return 0;
}

2. 使用CMake组织工程并编译

CMakeLists.txt:

cmake_minimum_required(VERSION 3.1)

project(lcm_test)

find_package(lcm REQUIRED)

add_executable(lcm_receive lcm_receive.cpp)

add_executable(lcm_send lcm_send.cpp)

add_executable(read_log read_log.cpp)

target_link_libraries(lcm_receive lcm)

target_link_libraries(lcm_send lcm)

target_link_libraries(read_log lcm)

编译并运行,在lcm_test 文件夹下新建build文件夹,在build文件夹中执行

cmake ..
make

生成三个可执行文件,lcm_receive,lcm_send, read_log.

先执行./lcm_receive;再./lcm_send,即可以收到发的内容。 

 

python版本例程,参考官网例程,注意事项,在lcm对象初始化时,应传入指定 URL,如

lc = lcm.LCM('udpm://239.255.76.67:7667?ttl=1')

否则可能导致接收端无法收到数据。反之,发送端同理。

import lcm
import time
from exlcm import pred_har

def publish_message():
    lc = lcm.LCM('udpm://239.255.76.67:7667?ttl=1')

    msg = pred_har()
    msg.timestamp=0.07
    msg.motion_mode=1

    lc.publish("pred_har_msg", msg.encode())
    print("msg published on pred_har_msg!")

if __name__ == '__main__':
    while True:
        publish_message()
        time.sleep(1)

3. 报错处理

如果在运行lcm_receive时出现以下报错信息:

LCM self test failed!!
Check your routing tables and firewall settings

a. 按照指示,关闭防火墙

查看防火墙状态

sudo ufw status

或者

systemctl status ufw

systemctl status ufw.service

如果是激活状态,则关闭防火墙,使用stop关闭后再用disable(有效)

先用stop命令关闭防火墙

systemctl stop ufw

查看状态,已经是inactive(dead)

然后再用disable开机禁用

sudo ufw disable

此时防火墙应该就是已经关闭,且不会再自动开启了。

使用以下两个指令先开启防火墙,然后开机自启防火墙

sudo start ufw
systemctl ufw enable

b. 尝试运行以下两行指令

sudo ifconfig lo multicast 
sudo route add -net 224.0.0.0 netmask 240.0.0.0 dev lo

用 sudo ifconfig lo multicast 命令时,你正在对本地回环接口(lo)进行配置,以启用多播(multicast)功能。

试图将多播(multicast)地址范围 224.0.0.0/4 添加到本地回环接口(lo)的路由表中,这通常不是正确的做法。该做法在多机器有时会碰到问题,如果此方法还不行,那就是其他问题。

如果是windows系统,还需要进行一些其他设置,如下所示

解决Ubuntu无法ping通Windows,但Windows能ping通Ubuntu_如果ubuntu 不能ping通windows,需要将开启windows共享-CSDN博客

4. LCM多机通讯要点

LCM多机通讯要点_lcm 路由表-CSDN博客

同一局域网下多机通信

  • 多节点的udp地址相同,比如程序中lcm::LCM lcm("udpm://239.255.76.67:7667?ttl=1");
  • 话题名称相同
  • 为每台机器A,B,C,…都使能UDP多播

Step1 查看网卡名称

$ ifconfig #查看用于通讯的网卡设备名字,如无线网卡wlp0s20f3

Step2 运行下面两条命令来显式使能 UDP 多播和添加路由表

sudo ifconfig eno2 multicast
sudo route add -net 224.0.0.0 netmask 240.0.0.0 dev eno2

重要
每次重启后体添加的路由会失效,建议写一个bash脚本,每次都运行一下Step2的route设置
使用ntp对多台主机进行时间,可以增加lcm时间戳同步性。 参考主从机时间同步
其他细节
在进行多设备通讯时,需要将ttl设置为大于0的值,LCM默认ttl=0,默认只在本地回环进行通讯。同时,需要target IP 设定为支持组播(Multicast)的IP 分段:(224.0.0.0-239.255.255.255)

5. 配置和使用lcm-spy的实时数据可视化功能

数据实时可视化数据效果如下:

a. 软件包安装和配置,安装JAVA

安装JAVA8.0(lcm只支持8.0版本)

sudo apt-get install openjdk-8-jdk

若安装了多个版本的JAVA,使用以下命令切换到version 8 :

sudo update-alternatives --config java

b. lcm-spy 的.jar类型的生成

lcm-spy 是LCM配套的数据可视化工具。可以通过lcm-spy监视lcm数据发送频率、数据量、数据结构,以及实时画图。

.jar类型文件的生成,可以使用如下make_types.sh脚本

#!/bin/bash
GREEN='\033[0;32m'
NC='\033[0m' # No Color

echo -e "${GREEN} Starting LCM type generation...${NC}"

cd ./lcm-types
# Clean
rm */*.jar
rm */*.java
rm */*.hpp
rm */*.class
rm */*.py
rm */*.pyc

# Make
lcm-gen -jxp *.lcm
cp /usr/local/share/java/lcm.jar .
javac -cp lcm.jar */*.java
jar cf my_types.jar */*.class
mkdir -p java
mv my_types.jar java
mv lcm.jar java
mkdir -p cpp
mv *.hpp cpp

mkdir -p python
mv *.py python

FILES=$(ls */*.class)
echo ${FILES} > file_list.txt


echo -e "${GREEN} Done with LCM type generation${NC}"

我的目录结构如下,将sensor_data_t.lcm类型文件,放置在与脚本同级目录下的lcm-types中

gdp@gdp:~/motorplot/lcm$ tree
.
├── launch_lcm_spy.sh
├── lcm-types
│   ├── cpp
│   │   ├── imu_data_t.hpp
│   │   └── sensor_data_t.hpp
│   ├── file_list.txt
│   ├── java
│   │   ├── lcm.jar
│   │   └── my_types.jar
│   ├── lcmtypes
│   │   ├── imu_data_t.class
│   │   ├── imu_data_t.java
│   │   ├── sensor_data_t.class
│   │   └── sensor_data_t.java
│   ├── python
│   │   ├── imu_data_t.py
│   │   └── sensor_data_t.py
│   └── sensor_data_t.lcm
└── make_types.sh

5 directories, 14 files

然后运行 gdp@gdp:~/motorplot/lcm$

./make_types.sh

之后,检查java目录下,是否生成了my_types.jar的文件,如果存在,则生成成功,执行下一步。

c. lcm-spy 的使用

启动lcm-spy之前,需要设置为java的lcm-type 的.jar 路径,依据上一步目录存放脚本launch_lcm_spy.sh

launch_lcm_spy.sh:

#!/bin/bash
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
cd ${DIR}/lcm-types/java
export CLASSPATH=${DIR}/lcm-types/java/my_types.jar
pwd
lcm-spy

启动脚本,即可看到可视化窗口

gdp@gdp:~/motorplot/lcm$ 

clear && ./launch_lcm_spy.sh

双击即可看到各个数据的实时显示。

### Ubuntu系统中安装LCM库 #### 准备工作 为了确保LCM能够顺利编译和运行,在Ubuntu环境中需先安装必要的依赖包。这包括构建工具和其他可能需要的软件开发套件。 ```bash $ sudo apt update $ sudo apt install build-essential ``` 上述命令更新了本地APT缓存并安装了`build-essential`,这是用于编译C/C++程序所需的基础工具集[^1]。 #### 下载LCM源代码 对于特定版本如1.4.0版,可以从官方GitHub仓库或其他可信资源下载对应标签(tag)下的压缩包或克隆整个项目到本地机器上: ```bash git clone https://github.com/lcm-proj/lcm.git -b v1.4.0 lcm-1.4.0 cd lcm-1.4.0 ``` 这里通过Git获取指定版本号(v1.4.0)对应的LCM源码,并切换至解压后的目录内准备后续操作[^4]。 #### 编译与安装过程 由于LCM 1.4.0及以上不再支持传统的`./configure`脚本配置流程,因此采用现代CMake构建系统来进行编译设置: ```bash mkdir build && cd build cmake .. sudo make install ``` 创建名为`build`的新子文件夹作为输出路径;执行`cmake ..`指令读取父级目录中的CMakeLists.txt文件完成环境初始化;最后利用`make`加上超级用户权限(`sudo`)调用`install`目标实现最终部署动作。 #### 环境变量调整 为了让操作系统识别新加入的共享库位置以及便于其他应用程序定位LCM组件,建议编辑/etc/ld.so.conf.d/lcm.conf并将实际安装地址写入其中: ```bash export LCM_INSTALL_DIR=/usr/local/lib echo "$LCM_INSTALL_DIR" | sudo tee /etc/ld.so.conf.d/lcm.conf > /dev/null sudo ldconfig ``` 此段脚本定义了一个指向默认安装路径(`/usr/local/lib`)的环境变量,并将其记录下来以便动态加载器可以找到这些新增加的库文件。随后刷新全局链接器缓存使更改生效[^3]。 另外还需要让pkg-config机制知晓LCM的存在从而简化第三方应用集成时所需的参数传递: ```bash export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig ``` 这条语句扩展了现有的`PKG_CONFIG_PATH`以包含LCM提供的`.pc`元数据描述文档所在之处。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值