主要步骤:

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

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

Logo

永洪科技,致力于打造全球领先的数据技术厂商,具备从数据应用方案咨询、BI、AIGC智能分析、数字孪生、数据资产、数据治理、数据实施的端到端大数据价值服务能力。

更多推荐