一. Windows系统下fastDDS使用
- 官网下载fastDDS,已经是编译好的二级制安装文件,安装好之后,对应的fastDDS gen和需要的环境变量已经自动装好了。
- 直接按照网上教程,先写一个idl后缀文件,用来设置传输的数据类型
struct HelloSeven
{
string sevenData;
};
- 然后运行:
fastddsgen -example CMake HelloSevenPubSubMain.idl
,不出意外,报错了
查了一下,cl.exe文件已经装好了,应该是cl.exe的环境变量没设置好,搜了一下,github上有一样的问题,答案里有解决方案。可以通过vs里的命令行(工具—>命令行—>开发者命令工具)来执行这个指令,成功,生成了一系列文件。
- 这代表已经用fastddsgen工具生成了工程文件了,下一步就是编译这个工程文件。摸索了好几次,还是用最原始的方法,就是先拿cmake软件make一下这个工程,报错,报的错是系统没有openssl的环境变量,查了一下,系统没有装openssl,去官网下载,开始装了个light版,不行,必须装完整版。装完之后,添加三个环境变量
OPENSSL_ROOT_DIR OPENSSL_CRYPTO_LIBRARY OPENSSL_INCLUDE_DIR
再编译,通过了。
参考代码如下
mkdir build
cd build
cmake ..
- 中间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”就好了。 - 用vs打开工程,生成解决方案,生成了一个exe文件,叫HelloSevenPubSubMain.exe和一个lib文件。
-
运行示例项目,用这个exe文件给上不同的参数,分别运行publisher和subscriber,成功
2. 用docker运行fastdds
在Windows上安装docker-desktop。然后去下载fastdds的image,fastdds官网有,下载下来是一个tar文件。然后把这个image载入进docker中,在tar文件所在文件夹运行:
docker load -i "ubuntu-fastdds <FastDDS-Version>.tar"
双引号里是tar文件的名称,改成实际文件的名称。
这时候去docker-desktop里面去看,可以看到image里已经有了这个image了。然后就是用这个image启动一个container,运行指令
docker run -it --rm --network=host --ipc=host <docker-image>
其中的<docker-image>用实际image名替换,可以去docker-desktop里去复制,
再根据官网指示,运行一个image内部自带的例程
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分别做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配置方法
docker run -it --rm --network=host --ipc=host --name cont1 <image name>
启动俩容器之后,运行共享内存专用测试程序HelloWorldExampleSharedMem,俩容器正常收发,通讯正常。
用--ipc=shareable和--ipc=container:cont1方式启动俩互相共享内存的容器后,
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的互联互通
- 在默认bridge网络模式下启动容器,然后在wsl2上启动DDSHelloWorldExample,作为publisher,在容器上运行subscriber,无法连通。
- 用host网络模式下启动容器,然后测试与wsl2进行dds通讯,还是无法联通。
linux下docker 容器运行fastDDS
ubuntu的宿主机运行一个FastDDS的docker容器,容器网络设置为host模式,即启动时候用
docker run -it --rm --network=host --ipc=host <docker-image>
在局域网另一台的Windows机中运行helloworldexample程序,一边发布,一边订阅,容器中程序成功实现同Windows系统通讯。注意要关闭防火墙,或者单独设置防火墙规则。
三.用ros常用的一组msg文件构建fastDDS的发布与订阅c++工程
拿到如下的接口数据结构描述文件夹
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文件。
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文件生成工程。
- 要将嵌套引用的idl文件include进idl文件中,然后要保证同一个工程的module名相同,struct名区分,
#include "GirdNormalInfo.idl" //结构体的第一个元素GirdNormalInfo是自定义数据格式,需要include该结构体所在的GirdNormalInfo.idl文件
module WaterOccupancyGrid {
struct GirdNormalInfo2 {
GirdNormalInfo info;
uint16 us_height;
uint16 uc_target_type;
};
};
- fastddsgen指令使用时,需要将所有用到的idl文件全部放入指令中
fastddsgen -example CMake GirdNormalInfo.idl UniHeader.idl OccupancyGridInfo.idl VehiclePose.idl GirdNormalInfo2.idl WaterOccupancyGrid.idl
这样就生成了发布和订阅的工程源文件,再通过本文第一章节的编译生成过程,就能实现发布和订阅功能了。中间有一些小问题,调试后发布和订阅功能正常。