FourierDDS
1. 概述
FourierDDS 是 Fourier 机器人软件使用的 DDS 通信 SDK。它基于 Fast DDS 提供 C++ 和 Python API,用于创建 participant、发布 topic、订阅 topic,以及实现 request/response service。
本文面向已经安装 SDK 的应用开发者。使用这些 API 不需要 FourierDDS 源码。
| 元素 | 值 |
|---|---|
| C++ namespace | fourier_dds |
| Python package | fourierdds_py |
| DDS backend | Fast DDS |
| 默认安装前缀 | /opt/fftai |
| C++ 标准 | C++17 |
2. 通信检查清单
所有需要互相通信的 participant 都必须使用兼容配置。
| 配置项 | 要求 |
|---|---|
domain_id | 两端必须一致。 |
| Topic 或 service 名称 | namespace 和 ROS 兼容命名映射后必须一致。 |
| 消息类型 | 生成消息和 PubSubType 必须匹配。 |
| Namespace | 两端必须一致,或两端都关闭。 |
| ROS 兼容 | 与 ROS 2 兼容 DDS 名称通信时必须一致。 |
| Discovery Server | 启用时所有进程必须使用同一组 server 地址。 |
| QoS | Publisher 和 subscriber 的 QoS 必须兼容。 |
本机单机测试时,建议显式设置 domain,并关闭 namespace 和 Discovery Server。
3. 确认 SDK 已安装
3.1 C++ SDK
ls /opt/fftai/lib/cmake/fourier_dds/fourier_ddsConfig.cmake
ls /opt/fftai/fourier_dds_msgs/lib/libfourierdds_core.so*
如果 CMake 找不到 fourier_dds,设置:
export CMAKE_PREFIX_PATH=/opt/fftai:${CMAKE_PREFIX_PATH}
如果可执行程序运行时找不到共享库,设置:
export LD_LIBRARY_PATH=/opt/fftai/fourier_dds_msgs/lib:${LD_LIBRARY_PATH}
3.2 Python SDK
python - <<'PY'
import fourierdds_py
import fourierdds_py._core
import fastdds
from fourier_msgs_pod.msg.ActuatorState import ActuatorStatePubSubType
print("fourierdds_py:", fourierdds_py.__file__)
print("core binding: OK")
print("message package: OK")
PY
如果检查失败,请确认 fourierdds-py、fourierdds_core、Fast DDS、fastcdr 和生成消息包来自同一批兼容版本。
4. C++ SDK
4.1 CMake 接入
将 MotorCfgState 替换成目标消息包交付的消息库名称。
cmake_minimum_required(VERSION 3.13)
project(my_fourierdds_app CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(fourier_dds REQUIRED)
find_package(fastdds 3 REQUIRED)
add_executable(my_app main.cpp)
target_include_directories(my_app PRIVATE ${fourier_dds_INCLUDE_DIRS})
target_link_directories(my_app PRIVATE ${fourier_dds_LIBRARY_DIRS})
target_link_libraries(my_app PRIVATE
fastdds
${fourier_dds_LIBRARIES}
MotorCfgState
)
4.2 核心类
| 类 | 用途 |
|---|---|
fourier_dds::DdsNode | 创建并持有 DDS participant 配置。 |
fourier_dds::DdsPublisher<MsgPubSubType> | 发布强类型消息。 |
fourier_dds::DdsSubscription<MsgPubSubType> | 订阅强类型消息并触发 callback。 |
fourier_dds::DdsClient<ReqPubSubType, RepPubSubType> | 发送 service request 并等待 reply。 |
fourier_dds::DdsServer<ReqPubSubType, RepPubSubType> | 接收 service request 并发送 reply。 |
4.3 创建 Node
auto node = std::make_shared<fourier_dds::DdsNode>(
42, // domain_id
0, // transport mode
false, // is_ros_compatible
false, // use_namespace
false // use_ds
);
对于使用已安装系统配置的机器人侧应用,通常可以使用简写:
auto node = std::make_shared<fourier_dds::DdsNode>(0);
4.4 发布 Topic
#include <chrono>
#include <memory>
#include <thread>
#include <MotorCfgState/MotorCfgStatePubSubTypes.hpp>
#include "fourier_dds/dds_node.hpp"
#include "fourier_dds/dds_publisher.hpp"
int main() {
auto node = std::make_shared<fourier_dds::DdsNode>(42, 0, false, false, false);
auto pub = std::make_shared<
fourier_dds::DdsPublisher<fourier_msgs::msg::MotorCfgGroupStatePubSubType>>(
node,
"hello"
);
fourier_msgs::msg::MotorCfgGroupState msg;
msg.group_name("demo");
msg.pos_kp({0.1, 0.2});
while (true) {
pub->publish(msg);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
4.5 订阅 Topic
#include <chrono>
#include <iostream>
#include <memory>
#include <thread>
#include <MotorCfgState/MotorCfgStatePubSubTypes.hpp>
#include "fourier_dds/dds_node.hpp"
#include "fourier_dds/dds_subscription.hpp"
int main() {
auto node = std::make_shared<fourier_dds::DdsNode>(42, 0, false, false, false);
auto sub = std::make_shared<
fourier_dds::DdsSubscription<fourier_msgs::msg::MotorCfgGroupStatePubSubType>>(
node,
"hello",
[](const fourier_msgs::msg::MotorCfgGroupState& msg) {
std::cout << "group=" << msg.group_name() << std::endl;
}
);
while (true) {
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
4.6 Service Server
#include <chrono>
#include <memory>
#include <thread>
#include <Calculator/Calculator.hpp>
#include <Calculator/CalculatorPubSubTypes.hpp>
#include "fourier_dds/dds_node.hpp"
#include "fourier_dds/dds_server.hpp"
int main() {
auto node = std::make_shared<fourier_dds::DdsNode>(42, 0, false, false, false);
auto server = std::make_shared<
fourier_dds::DdsServer<fourier_msgs::srv::Calculator_RequestPubSubType,
fourier_msgs::srv::Calculator_ResponsePubSubType>>(
node,
"calculator",
[](const fourier_msgs::srv::Calculator_Request& req,
fourier_msgs::srv::Calculator_Response& rep) {
rep.result() = req.x() + req.y();
}
);
server->run();
while (true) {
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
4.7 Service Client
#include <iostream>
#include <memory>
#include <Calculator/Calculator.hpp>
#include <Calculator/CalculatorPubSubTypes.hpp>
#include "fourier_dds/dds_client.hpp"
#include "fourier_dds/dds_node.hpp"
int main() {
auto node = std::make_shared<fourier_dds::DdsNode>(42, 0, false, false, false);
auto client = std::make_shared<
fourier_dds::DdsClient<fourier_msgs::srv::Calculator_RequestPubSubType,
fourier_msgs::srv::Calculator_ResponsePubSubType>>(
node,
"calculator"
);
fourier_msgs::srv::Calculator_Request req;
fourier_msgs::srv::Calculator_Response rep;
req.x() = 1;
req.y() = 2;
bool ok = client->send_request(req, rep, 2000);
if (ok) {
std::cout << "result=" << rep.result() << std::endl;
} else {
std::cout << "timeout" << std::endl;
}
}
4.8 Loaned Message 发布
对于 plain 且 bounded 的消息类型,publisher 可以先 borrow 一个 message sample 再发布。如果类型不支持 loan,接口会自动回退为本地消息对象。
auto msg = pub->borrow_message();
msg->angular_velocity().x(1.0);
msg->linear_acceleration().z(9.81);
bool ok = pub->publish(std::move(msg));
使用 pub->can_loan() 可以检查当前消息类型是否支持 loan。
5. Python SDK
5.1 导入并创建 Node
from fourierdds_py import DDSInterface
from fourierdds_py import PublisherQosProfile, SubscriberQosProfile
dds = DDSInterface(
domain_id=42,
use_namespace=False,
use_ds=False,
is_ros_compatible=False,
)
5.2 消息类型规则
创建 publisher 和 subscription 时使用生成的 PubSubType 类。发布或读取 callback 数据时使用对应的生成消息类。
from fourier_msgs_pod.msg.ActuatorState import ActuatorState, ActuatorStatePubSubType
msg = ActuatorState()
msg.state_size(3)
value = msg.state_size()
5.3 发布与订阅
import time
from fourierdds_py import DDSInterface
from fourierdds_py import PublisherQosProfile, SubscriberQosProfile
from fourier_msgs_pod.msg.ActuatorState import ActuatorState, ActuatorStatePubSubType
dds = DDSInterface(domain_id=42, use_namespace=False, use_ds=False)
def on_msg(msg):
print("received state_size:", msg.state_size())
sub = dds.create_subscription(
ActuatorStatePubSubType,
"actuator_state",
on_msg,
qos_profile=SubscriberQosProfile.best_effort(),
)
pub = dds.create_publisher(
ActuatorStatePubSubType,
"actuator_state",
qos_profile=PublisherQosProfile.best_effort(),
)
time.sleep(1.0)
msg = ActuatorState()
msg.state_size(1)
pub.publish(msg)
time.sleep(1.0)
dds.close()
Callback 运行在 DDS 回调路径中。请保持 callback 简短,将耗时工作放到应用队列或工作线程中。
5.4 Service Server
import time
from fourierdds_py import DDSInterface
from std_srvs.srv import Trigger
dds = DDSInterface(domain_id=42, use_namespace=False, use_ds=False)
def handle_request(request, response):
response.success(True)
response.message("ok")
server = dds.create_service(
Trigger.Trigger_RequestPubSubType,
Trigger.Trigger_ResponsePubSubType,
"demo_service",
handle_request,
)
while True:
time.sleep(1)
5.5 Service Client
from fourierdds_py import DDSInterface
from std_srvs.srv import Trigger
dds = DDSInterface(domain_id=42, use_namespace=False, use_ds=False)
client = dds.create_client(
Trigger.Trigger_RequestPubSubType,
Trigger.Trigger_ResponsePubSubType,
"demo_service",
)
request = Trigger.Trigger_Request()
response = client.send_request(
request,
wait_ms=2000,
wait_for_server_ms=1000,
)
if response is None:
print("timeout")
else:
print("success:", response.success())
print("message:", response.message())
dds.close()
6. QoS Profiles
Python 提供常用 QoS 预设:
pub_qos = PublisherQosProfile.best_effort()
sub_qos = SubscriberQosProfile.best_effort()
pub_qos = PublisherQosProfile.reliable()
sub_qos = SubscriberQosProfile.reliable()
| 字段 | 说明 |
|---|---|
reliability | best_effort 或 reliable。 |
durability | volatile 或 transient_local。 |
history_kind | keep_last 或 keep_all。 |
history_depth | DDS history depth。 |
deadline_sec / deadline_nanosec | DDS deadline。默认是无限 deadline。 |
max_samples_per_callback | 单次 subscription callback 最多处理的样本数。 |
C++ 和 Python 互通时,也要确保 两端 QoS 兼容。
7. Namespace、ROS 兼容与 Discovery Server
7.1 Namespace
FourierDDS 可以自动给 topic 和 service 名称添加机器人 namespace。namespace 通常来自机器人配置或 BMS。
| 场景 | 建议 |
|---|---|
| 本机测试 | C++ 设置 use_namespace=false,Python 设置 use_namespace=False。 |
| 机器人运行时 | 除非另有说明,使用默认 namespace 行为。 |
| 多机器人部署 | 确认双方有意使用相同 namespace,或使用不同 namespace 进行隔离。 |
7.2 ROS 兼容
ROS 兼容模式会将 DDS 名称映射成 ROS 2 兼容 DDS 名称。
| 实体 | ROS 兼容 DDS 名称 |
|---|---|
| Topic | rt/<topic> |
| Service request | rq/<service>Request |
| Service response | rr/<service>Reply |
与 ROS 2 兼容 DDS participant 通信时,显式启用:
export FOURIERDDS_ROS_COMPATIBLE=1
7.3 Discovery Server
如果部署环境使用 Fast DDS Discovery Server,所有进程必须使用同一组地址。
export FOURIERDDS_USE_DISCOVERY_SERVER=1
export FOURIERDDS_DISCOVERY_SERVER="192.168.1.10:11811;192.168.1.11:11812"
如需让 ROS 2 CLI 工具或 PlotJuggler 接入同一个 DDS 网络,source 已安装的环境 wrapper:
source /opt/fftai/fourierdds/shells/discovery_env.sh --ros2
ros2 topic list
source /opt/fftai/fourierdds/shells/discovery_env.sh --plotjuggler
plotjuggler
8. 故障排查
8.1 CMake 找不到 fourier_dds
运行 CMake 前设置安装前缀:
export CMAKE_PREFIX_PATH=/opt/fftai:${CMAKE_PREFIX_PATH}
8.2 运行时报共享库错误
设置运行时库路径:
export LD_LIBRARY_PATH=/opt/fftai/fourier_dds_msgs/lib:${LD_LIBRARY_PATH}
8.3 Python _core 导入错误
确认 fourierdds-py、fourierdds_core、Fast DDS、fastcdr 和生成消息包来自同一批兼容版本。如果动态链接器找不到共享库,按上方方式设置 LD_LIBRARY_PATH。
8.4 收不到 Topic 数据
优先检查这些项:
| 检查项 | 说明 |
|---|---|
domain_id | 必须一致。 |
| Topic 名称 | namespace 和 ROS 兼容映射后必须一致。 |
| 消息类型 | PubSubType 必须匹配。 |
| Namespace | 确认是否自动添加了机器人 namespace。 |
| ROS 兼容 | 两端必须一致。 |
| Discovery Server | 启用时必须使用同一组 server 配置。 |
| QoS | Reliability、durability、history 必须兼容。 |
8.5 Service Client 超时
确认 server 已启动,并且 domain_id、service 名称、namespace、ROS 兼容、Discovery Server、request/response 类型都一致。Python 中如果 discovery 较慢,可以调大 wait_for_server_ms。
response = client.send_request(request, wait_ms=3000, wait_for_server_ms=3000)