FourierDDS
1. Overview
FourierDDS is the DDS communication SDK used by Fourier robot software. It provides C++ and Python APIs on top of Fast DDS for creating participants, publishing topics, subscribing to topics, and implementing request/response services.
This page is written for application developers using an installed SDK. You do not need the FourierDDS source code to use these APIs.
| Element | Value |
|---|---|
| C++ namespace | fourier_dds |
| Python package | fourierdds_py |
| DDS backend | Fast DDS |
| Default install prefix | /opt/fftai |
| C++ standard | C++17 |
2. Communication Checklist
All participants that communicate with each other must use compatible settings.
| Setting | Requirement |
|---|---|
domain_id | Must match on both sides. |
| Topic or service name | Must match after namespace and ROS-compatible name mapping. |
| Message type | The generated message and PubSubType must match. |
| Namespace | Must match, or be disabled on both sides. |
| ROS compatibility | Must match when communicating with ROS 2-compatible DDS names. |
| Discovery Server | If enabled, all processes must use the same server address list. |
| QoS | Publisher and subscriber QoS must be compatible. |
For local single-machine tests, use an explicit domain and disable namespace and Discovery Server.
3. Verify the Installed 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*
If CMake cannot find fourier_dds, set:
export CMAKE_PREFIX_PATH=/opt/fftai:${CMAKE_PREFIX_PATH}
If the executable cannot find shared libraries at runtime, set:
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
If this check fails, confirm that fourierdds-py, fourierdds_core, Fast DDS, fastcdr, and generated message packages were installed as one compatible release set.
4. C++ SDK
4.1 CMake Setup
Replace MotorCfgState with the message library delivered with your target message package.
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 Core Classes
| Class | Purpose |
|---|---|
fourier_dds::DdsNode | Creates and owns the DDS participant configuration. |
fourier_dds::DdsPublisher<MsgPubSubType> | Publishes typed messages. |
fourier_dds::DdsSubscription<MsgPubSubType> | Subscribes to typed messages and invokes callbacks. |
fourier_dds::DdsClient<ReqPubSubType, RepPubSubType> | Sends service requests and waits for replies. |
fourier_dds::DdsServer<ReqPubSubType, RepPubSubType> | Receives service requests and sends replies. |
4.3 Create a 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
);
For robot-side applications using the installed system configuration, the short form is usually enough:
auto node = std::make_shared<fourier_dds::DdsNode>(0);
4.4 Publish a 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 Subscribe to a 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 Publishing
For plain and bounded message types, a publisher can borrow a message sample before publishing. If the type cannot be loaned, the API falls back to a local message object.
auto msg = pub->borrow_message();
msg->angular_velocity().x(1.0);
msg->linear_acceleration().z(9.81);
bool ok = pub->publish(std::move(msg));
Use pub->can_loan() to check whether the current message type is loan-capable.
5. Python SDK
5.1 Import and Create a 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 Message Type Rule
Use the generated PubSubType class when creating publishers and subscriptions. Use the generated message class when publishing or reading callback data.
from fourier_msgs_pod.msg.ActuatorState import ActuatorState, ActuatorStatePubSubType
msg = ActuatorState()
msg.state_size(3)
value = msg.state_size()
5.3 Publish and Subscribe
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()
Callbacks run in the DDS callback path. Keep callbacks short and move long-running work to an application queue or worker thread.
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 exposes common QoS presets:
pub_qos = PublisherQosProfile.best_effort()
sub_qos = SubscriberQosProfile.best_effort()
pub_qos = PublisherQosProfile.reliable()
sub_qos = SubscriberQosProfile.reliable()
| Field | Description |
|---|---|
reliability | best_effort or reliable. |
durability | volatile or transient_local. |
history_kind | keep_last or keep_all. |
history_depth | DDS history depth. |
deadline_sec / deadline_nanosec | DDS deadline. The default is infinite deadline. |
max_samples_per_callback | Maximum samples drained by one subscription callback. |
When C++ and Python communicate, make sure their QoS settings are compatible.
7. Namespace, ROS Compatibility, and Discovery Server
7.1 Namespace
FourierDDS can automatically prefix topic and service names with a robot namespace. The namespace is usually read from robot configuration or BMS.
| Scenario | Recommendation |
|---|---|
| Local test | Set use_namespace=false in C++ or use_namespace=False in Python. |
| Robot runtime | Use the default namespace behavior unless instructed otherwise. |
| Multi-robot deployment | Make sure both sides intentionally use the same namespace or different namespaces for isolation. |
7.2 ROS Compatibility
ROS-compatible mode maps DDS names to ROS 2-compatible DDS names.
| Entity | ROS-compatible DDS name |
|---|---|
| Topic | rt/<topic> |
| Service request | rq/<service>Request |
| Service response | rr/<service>Reply |
Enable it explicitly when communicating with ROS 2-compatible DDS participants:
export FOURIERDDS_ROS_COMPATIBLE=1
7.3 Discovery Server
If the deployment uses Fast DDS Discovery Server, all processes must use the same address list.
export FOURIERDDS_USE_DISCOVERY_SERVER=1
export FOURIERDDS_DISCOVERY_SERVER="192.168.1.10:11811;192.168.1.11:11812"
To connect ROS 2 CLI tools or PlotJuggler to the same DDS network, source the installed environment wrapper:
source /opt/fftai/fourierdds/shells/discovery_env.sh --ros2
ros2 topic list
source /opt/fftai/fourierdds/shells/discovery_env.sh --plotjuggler
plotjuggler
8. Troubleshooting
8.1 CMake Cannot Find fourier_dds
Set the install prefix before running CMake:
export CMAKE_PREFIX_PATH=/opt/fftai:${CMAKE_PREFIX_PATH}
8.2 Runtime Shared Library Error
Set the runtime library path:
export LD_LIBRARY_PATH=/opt/fftai/fourier_dds_msgs/lib:${LD_LIBRARY_PATH}
8.3 Python _core Import Error
Confirm that fourierdds-py, fourierdds_core, Fast DDS, fastcdr, and generated message packages are from the same compatible release set. If the dynamic linker cannot find shared libraries, set LD_LIBRARY_PATH as shown above.
8.4 No Topic Data Received
Check these items first:
| Check | Description |
|---|---|
domain_id | Must match. |
| Topic name | Must match after namespace and ROS-compatible mapping. |
| Message type | PubSubType must match. |
| Namespace | Confirm whether a robot namespace was added. |
| ROS compatibility | Must match on both sides. |
| Discovery Server | Must use the same server configuration if enabled. |
| QoS | Reliability, durability, and history must be compatible. |
8.5 Service Client Timeout
Confirm that the server is already running and that domain_id, service name, namespace, ROS compatibility, Discovery Server, and request/response types match. For Python, increase wait_for_server_ms if discovery is slow.
response = client.send_request(request, wait_ms=3000, wait_for_server_ms=3000)
8.6 ROS 2 CLI Cannot See Topics
Use the installed wrapper to generate a ROS 2 SUPER_CLIENT profile. Setting only ROS_DISCOVERY_SERVER is usually not enough.
source /opt/fftai/fourierdds/shells/discovery_env.sh --ros2
ros2 topic list
Status: Stable
Last updated: 2026-06-23