一. Windows系统下fastDDS使用

  1. 官网下载fastDDS,已经是编译好的二级制安装文件,安装好之后,对应的fastDDS gen和需要的环境变量已经自动装好了。
  2. 直接按照网上教程,先写一个idl后缀文件,用来设置传输的数据类型
1
struct HelloSeven { string sevenData; };
  1. 然后运行:fastddsgen -example CMake HelloSevenPubSubMain.idl,不出意外,报错了

    报错1

    查了一下,cl.exe文件已经装好了,应该是cl.exe的环境变量没设置好,搜了一下,github上有一样的问题,答案里有解决方案。可以通过vs里的命令行(工具—>命令行—>开发者命令工具)来执行这个指令,成功,生成了一系列文件。

    VS命令行工具.png

  2. 这代表已经用fastddsgen工具生成了工程文件了,下一步就是编译这个工程文件。摸索了好几次,还是用最原始的方法,就是先拿cmake软件make一下这个工程,报错,报的错是系统没有openssl的环境变量,查了一下,系统没有装openssl,去官网下载,开始装了个light版,不行,必须装完整版。装完之后,添加三个环境变量OPENSSL_ROOT_DIR OPENSSL_CRYPTO_LIBRARY OPENSSL_INCLUDE_DIR
    再编译,通过了。
    参考代码如下

1
mkdir build cd build cmake ..
  1. 中间cmake --build时,还有报错,“MSBuild version 17.6.3+07e294721 for .NET Framework
    MSBUILD : error MSB1009: 项目文件不存在。”在系统变量path中添加msbuild的路径“C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild”就好了。

  2. 用vs打开工程,生成解决方案,生成了一个exe文件,叫HelloSevenPubSubMain.exe和一个lib文件。

  3. 运行示例项目,用这个exe文件给上不同的参数,分别运行publisher和subscriber,成功

    publisher.png

    subscriber.png

2. 用docker运行fastdds

在Windows上安装docker-desktop。然后去下载fastdds的image,fastdds官网有,下载下来是一个tar文件。然后把这个image载入进docker中,在tar文件所在文件夹运行:

1
docker load -i "ubuntu-fastdds <FastDDS-Version>.tar"

双引号里是tar文件的名称,改成实际文件的名称。
这时候去docker-desktop里面去看,可以看到image里已经有了这个image了。然后就是用这个image启动一个container,运行指令

1
docker run -it --rm --network=host --ipc=host <docker-image>

其中的用实际image名替换,可以去docker-desktop里去复制,

复制image名

这样就启动了这个fastdds的image。network和host的两个参数,是为了容器之间共享内存,可以使得容器之间通过共享内存通讯,详见官方说明1.1. Leveraging Fast DDS SHM in Docker deployments — Fast DDS 2.13.1 documentation (eprosima.com)
再根据官网指示,运行一个image内部自带的例程

1
root@docker-desktop:/usr/local/eprosima/fastrtps/examples/cpp/dds/HelloWorldExample/bin# tmux new-session "./DDSHelloWorldExample publisher 0 1000" \; split-window "./DDSHelloWorldExample subscriber" \; select-layout even-vertical

效果如下

例程运行效果

这是在同一个container中subscriber和publisher同时运行并通讯。
接着测试了开启两个container分别做publisher和subscriber,也成功进行了通讯。
例程参考1.2. Fast DDS Image — Fast DDS 2.13.1 documentation (eprosima.com)

FastDDS共享内存shm模式

按照官方教程1.1. Leveraging Fast DDS SHM in Docker deployments — Fast DDS 2.13.2 documentation (eprosima.com),测试有docker端参与的共享内存方式下的dds通讯。
用官方的这个docker配置方法

1
docker run -it --rm --network=host --ipc=host --name cont1 <image name>

启动俩容器之后,运行共享内存专用测试程序HelloWorldExampleSharedMem,俩容器正常收发,通讯正常。
用–ipc=shareable和–ipc=container:cont1方式启动俩互相共享内存的容器后,

1
docker run -it --rm --network=host --ipc=shareable --name cont1 <image name> docker run -it --rm --network=host --ipc=container:cont1 <image name>

共享内存的dds程序可以跑通,俩容器正常收发。
之前启动容器时,没有加–network=host,跑这个共享内存的fastdds就不通。看来跟网络还有关系,所以必须网络配置成host模式。在上边链接里的官方教程中也有相关说明。
两个容器的共享内存通讯通了,但是测试容器和windows共享内存跑FastDDS还是不行,应该是因为Windows和docker容器中间还隔了一个wsl,并没有共享内存。

两容器通讯

在默认bridge的网络模式下启动两个容器,hello worldexample运行,两容器通讯成功。

容器和wsl2的互联互通

  1. 在默认bridge网络模式下启动容器,然后在wsl2上启动DDSHelloWorldExample,作为publisher,在容器上运行subscriber,无法连通。
  2. 用host网络模式下启动容器,然后测试与wsl2进行dds通讯,还是无法联通。

linux下docker 容器运行fastDDS

ubuntu的宿主机运行一个FastDDS的docker容器,容器网络设置为host模式,即启动时候用

1
docker run -it --rm --network=host --ipc=host <docker-image>

在局域网另一台的Windows机中运行helloworldexample程序,一边发布,一边订阅,容器中程序成功实现同Windows系统通讯。注意要关闭防火墙,或者单独设置防火墙规则。

三.用ros常用的一组msg文件构建fastDDS的发布与订阅c++工程

拿到如下的接口数据结构描述文件夹

1
F:. │ CMakeLists.txt │ darknetf.txt │ package.xml │ └─msg DeadZoneOccupancyGrid.msg GirdNormalInfo.msg GirdNormalInfo2.msg LeaderVehicle.msg LqEntryState.msg LqOccupancyGrid.msg NoObstacleFlag.msg OccupancyGridInfo.msg PercepVehicleState.msg TargetGlobal.msg TargetGlobalInfo.msg TargetLocal.msg TargetLocalInfo.msg UnderWaterOccupancyGrid.msg VehiclePose.msg WaterOccupancyGrid.msg

数据是嵌套构造的,我们需要的最外层数据是WaterOccupancyGrid.msg定义的数据。从msg文件生成idl可以通过ros2系统packages构建过程附带完成,也可以通过构建脚本手动生成,在chat某某T的协助写,编写了python脚本,将msg文件批量转换为了idl文件。

1
import os # 定义类型映射关系 type_mapping = { 'bool': 'boolean', 'int8': 'int8', 'uint8': 'uint8', 'int16': 'int16', 'uint16': 'uint16', 'int32': 'int32', 'uint32': 'uint32', 'int64': 'int64', 'uint64': 'uint64', 'float32': 'float', 'float64': 'double', 'string': 'string' } def convert_msg_type_to_idl_type(msg_type): """将ROS 2的消息类型转换为IDL类型""" if msg_type in type_mapping: return type_mapping[msg_type] elif msg_type.endswith("[]"): # 处理数组 base_type = msg_type[:-2] return f"sequence<{type_mapping.get(base_type, base_type)}>" else: return msg_type # 自定义类型保持不变 def convert_msg_file_to_idl(msg_file, idl_file): """将一个.msg文件转换为.idl文件""" print(f"Converting {msg_file} to {idl_file}") with open(msg_file, 'r', encoding='utf-8') as msg_f, open(idl_file, 'w', encoding='utf-8') as idl_f: msg_name = os.path.splitext(os.path.basename(msg_file))[0] # 写入IDL文件的头 idl_f.write(f"module {msg_name} {{\n") idl_f.write(" struct Msg {\n") # 读取每一行并转换为IDL格式 for line in msg_f: line = line.strip() if not line or line.startswith('#'): # 忽略空行和注释 continue parts = line.split() if len(parts) < 2: print(f"Skipping invalid line: {line}") continue msg_type, msg_field = parts[0], parts[1] idl_type = convert_msg_type_to_idl_type(msg_type) idl_f.write(f" {idl_type} {msg_field};\n") # 写入结构体和模块结束符 idl_f.write(" };\n") idl_f.write("};\n") print(f"Finished writing {idl_file}") def convert_all_msg_files_to_idl(msg_dir, idl_dir): """将一个目录下的所有.msg文件转换为.idl文件""" if not os.path.exists(idl_dir): os.makedirs(idl_dir) for root, dirs, files in os.walk(msg_dir): for file in files: if file.endswith('.msg'): msg_file = os.path.join(root, file) idl_file = os.path.join(idl_dir, file.replace('.msg', '.idl')) print(f"Converting {msg_file} to {idl_file}") convert_msg_file_to_idl(msg_file, idl_file) if __name__ == "__main__": # 设置msg文件目录和生成的idl文件目录 msg_directory = './msg' # 你的.msg文件目录 idl_directory = './idl' # 输出.idl文件的目录 # 执行转换 convert_all_msg_files_to_idl(msg_directory, idl_directory)

执行完转换之后,还需要解决数据结构在不同idl文件之间嵌套的问题,本文第一章节的内容是针对单个idl文件的工程生成,需要进行针对性的修正工作,才能针对多idl文件生成工程。

  1. 要将嵌套引用的idl文件include进idl文件中,然后要保证同一个工程的module名相同,struct名区分,
1
#include "GirdNormalInfo.idl" //结构体的第一个元素GirdNormalInfo是自定义数据格式,需要include该结构体所在的GirdNormalInfo.idl文件 module WaterOccupancyGrid { struct GirdNormalInfo2 { GirdNormalInfo info; uint16 us_height; uint16 uc_target_type; }; };
  1. fastddsgen指令使用时,需要将所有用到的idl文件全部放入指令中
1
fastddsgen -example CMake GirdNormalInfo.idl UniHeader.idl OccupancyGridInfo.idl VehiclePose.idl GirdNormalInfo2.idl WaterOccupancyGrid.idl

这样就生成了发布和订阅的工程源文件,再通过本文第一章节的编译生成过程,就能实现发布和订阅功能了。中间有一些小问题,调试后发布和订阅功能正常。