Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ find_package(pluginlib REQUIRED)
find_package(Boost REQUIRED COMPONENTS system)
find_package(PkgConfig REQUIRED)
pkg_check_modules(ZSTD REQUIRED libzstd)
find_package(zmqpp_vendor REQUIRED)

if(BUILD_TESTING)
find_package(launch_testing_ament_cmake REQUIRED)
Expand All @@ -37,6 +38,10 @@ if(BUILD_TESTING)
test/test_tcp.py
TIMEOUT 2 # Sets a timeout for the test in seconds
)
add_launch_test(
test/test_zmq.py
TIMEOUT 10 # Sets a timeout for the test in seconds
)
endif()

include_directories(include)
Expand All @@ -55,6 +60,10 @@ add_library(tcp_interface SHARED
src/network_interfaces/tcp_interface.cpp
)

add_library(zmq_interface SHARED
src/network_interfaces/zmq_interface.cpp
)

target_link_libraries(network_bridge PUBLIC
${std_msgs_TARGETS}
${tf2_msgs_TARGETS}
Expand All @@ -76,6 +85,28 @@ target_link_libraries(tcp_interface PUBLIC
${Boost_LIBRARIES}
)

if(DEFINED zmqpp_vendor_INCLUDE_DIRS)
target_include_directories(zmq_interface PUBLIC
${zmqpp_vendor_INCLUDE_DIRS}
)
endif()

if(DEFINED zmqpp_vendor_TARGETS)
target_link_libraries(zmq_interface PUBLIC
rclcpp::rclcpp
pluginlib::pluginlib
${zmqpp_vendor_TARGETS}
)
endif()

if(DEFINED zmqpp_vendor_LIBRARIES)
target_link_libraries(zmq_interface PUBLIC
rclcpp::rclcpp
pluginlib::pluginlib
${zmqpp_vendor_LIBRARIES}
)
endif()

pluginlib_export_plugin_description_file(network_bridge network_interface_plugins.xml)

install(TARGETS
Expand All @@ -86,6 +117,7 @@ install(TARGETS
install(TARGETS
udp_interface
tcp_interface
zmq_interface
DESTINATION lib/
)

Expand Down
52 changes: 49 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
# Network Bridge

[![CI](https://github.com/brow1633/network_bridge/actions/workflows/CI.yml/badge.svg)](https://github.com/brow1633/network_bridge/actions/workflows/CI.yml)

**Network Bridge** is a lightweight ROS2 node designed for robust communication between robotic systems over arbitrary network protocols. Supporting UDP and TCP protocols out of the box, this packages seamlessly bridges ROS2 topics across networks, facilitating effective remote communications between a base station and robotic systems, or between multiple robotic systems.
**Network Bridge** is a lightweight ROS2 node designed for robust communication between robotic systems over arbitrary network protocols. Supporting UDP, TCP, and ZMQ protocols out of the box, this packages seamlessly bridges ROS2 topics across networks, facilitating effective remote communications between a base station and robotic systems, or between multiple robotic systems.

## Installation

### Installation via apt

Install with:

```
sudo apt install ros-<distro>-network-bridge
```

### Building from Source

Simply clone the repository into your ROS2 workspace and build with `colcon build`.

## Usage

### Demo

#### TCP

```
ros2 launch network_bridge tcp.launch.py

Expand All @@ -26,20 +33,37 @@ ros2 topic echo /tcp2/MyDefaultTopic
```

#### UDP

```
ros2 launch network_bridge udp.launch.py

ros2 topic pub /udp1/MyDefaultTopic std_msgs/msg/String "data: 'Hello World'"

ros2 topic echo /udp2/MyDefaultTopic
```

#### ZMQ

```
ros2 launch network_bridge zmq.launch.py

ros2 topic pub /zmq1/MyDefaultTopic std_msgs/msg/String "data: 'Hello World'"

ros2 topic echo /zmq2/MyDefaultTopic
```

### Configuration

Simply setup the network interface parameters and list your desired topics to get started. If you are using UDP over cellular data, it is recommended to setup a VPN to facilitate connection. Also, please note that **no encryption** occurs within this package. Currently, if you would like encryption, you must use a VPN.

See `config/Udp1.yaml` for a description of all parameters, as well as the TCP example configuration files.

#### Minimal Example

The following configuration examples demonstrate a robot sending a message on `/gps/fix` over UDP to a basestation that will then re-publish the message. This works seamlessly on all message types, so long as they are built and sourced on both ends of the transmission.

#### Robot

```
/udp_sender:
ros__parameters:
Expand All @@ -52,7 +76,9 @@ The following configuration examples demonstrate a robot sending a message on `/
topics:
- "/gps/fix"
```

#### Base Station

```
/udp_receiver:
ros__parameters:
Expand All @@ -62,11 +88,14 @@ The following configuration examples demonstrate a robot sending a message on `/
remote_address: "192.168.1.2"
send_port: 5001
```

#### Special case: TF

The TF topic `/tf` or `/tf_static` are handled as a special cases. The subscriber side will listen to all TF messages, accumulate them
(similarly to a TF buffer) and send all of them at the specified rate. The behavior can be disabled or forced using the `is_tf` configuration.

If some TFs need to be excluded or if the list of TFs to include is finite, one can use the include and exclude regex parameters. A transform is matched (hence excluded or included) if either the `frame_id` or `child_frame_id` are matching a pattern.

```
/udp_sender:
ros__parameters:
Expand All @@ -92,21 +121,37 @@ If some TFs need to be excluded or if the list of TFs to include is finite, one
```

### Choice of protocol

- **UDP**: Use UDP for low-latency, high-throughput communications, where occasional data loss is tolerable. Ideal for real-time telemetry data like sensor streams.
- **TCP**: Opt for TCP when data integrity and reliability are critical. This ensures that control commands and state transitions are reliably delivered, though with potentially higher latency.
- **ZMQ**: Use ZMQ for reliable, high-performance messaging patterns. It provides a more robust and flexible communication architecture compared to raw TCP/UDP, abstracting away complex socket management.

#### ZMQ Communication Patterns

Network protocols are implemented as pluginlib plugins, allowing the creation of arbitrary interfaces using the abstract class `include/network_interfaces/network_interface_base.hpp`. Any interface that can send and receive bytes could theoretically be implemented, including protocols that go beyond point-to-point communication, such as ZMQ. Please consider opening a pull request if you implement a new network interface.
ZMQ supports multiple messaging patterns. This package currently supports two, configurable via the `pattern` parameter:

- **PUB/SUB** (`pattern: pub_sub`): One publisher broadcasts messages to multiple subscribers. Best for one-to-many data distribution (e.g., sensor streams to multiple consumers). Subscribers receive only the topics they subscribe to; late-joining subscribers miss messages sent before connection.

- **PUSH/PULL** (`pattern: push_pull`): One pusher sends messages to a pool of pullers in a round-robin fashion. Best for load-balanced pipelines where each message must be processed by exactly one consumer. Provides back-pressure and queuing, unlike PUB/SUB.

### Network Protocol Implementation

Network protocols are implemented as pluginlib plugins, allowing the creation of arbitrary interfaces using the abstract class `include/network_interfaces/network_interface_base.hpp`. Any interface that can send and receive bytes could theoretically be implemented, including protocols that go beyond point-to-point communication. Please consider opening a pull request if you implement a new network interface.

### Tuning

This node can be launched with logger level DEBUG, which provides useful information for tuning the compression, rate and stale message parameters. For each message that is sent, the receiving side will output the number of bytes received, the decompressed size in bytes and the transmission delay.

### Contributing

Thank you for considering contributing!

#### Code Formatting

Python code is formatted with `black`, and C++ is formatted with `uncrustify`.

#### Pre-commit hooks

To ease the friction of linting, there are pre-commit hooks that you can install:

```bash
Expand All @@ -120,4 +165,5 @@ pre-commit install # Run on commit automatically
which will reformat code automatically when you commit changes.

## Acknowledgements
This package was developed for use in the Indy Autonomous Challenge by the Purdue AI Racing team. Inspiration was taken from mqtt_client (https://github.com/ika-rwth-aachen/mqtt_client/).

This package was developed for use in the Indy Autonomous Challenge by the Purdue AI Racing team. Inspiration was taken from mqtt_client (<https://github.com/ika-rwth-aachen/mqtt_client/>).
20 changes: 20 additions & 0 deletions config/Zmq1.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Configuration for the ZMQ Server (PUSH/PULL or PUB/SUB)
/**/zmq_bridge_server:
ros__parameters:
network_interface: "network_bridge::ZmqInterface"

ZmqInterface:
role: "server"
# pattern: "pub_sub" # Options: pub_sub (default) or push_pull
port: 5555

default_rate: 10.0
default_zstd_level: 3
publish_stale_data: False

# The server subscribes to this topic and pushes it to ZMQ
topics:
- "/test_topic"

subscribe_namespace: "/zmq1"
publish_namespace: "/zmq1"
20 changes: 20 additions & 0 deletions config/Zmq1PushPull.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Configuration for the ZMQ Server (PUSH/PULL or PUB/SUB)
/**/zmq_bridge_server_push:
ros__parameters:
network_interface: "network_bridge::ZmqInterface"

ZmqInterface:
role: "server"
pattern: "push_pull" # Options: pub_sub (default) or push_pull
port: 5556

default_rate: 10.0
default_zstd_level: 3
publish_stale_data: False

# The server subscribes to this topic and pushes it to ZMQ
topics:
- "/test_topic"

subscribe_namespace: "/zmq1/push_pull"
publish_namespace: "/zmq1/push_pull"
17 changes: 17 additions & 0 deletions config/Zmq2.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Configuration for the ZMQ Client (PUSH/PULL or PUB/SUB)
/**/zmq_bridge_client:
ros__parameters:
network_interface: "network_bridge::ZmqInterface"

ZmqInterface:
role: "client"
# pattern: "pub_sub" # Options: pub_sub (default) or push_pull
remote_address: "127.0.0.1"
port: 5555

default_rate: 10.0
default_zstd_level: 3
publish_stale_data: False

subscribe_namespace: "/zmq2"
publish_namespace: "/zmq2"
17 changes: 17 additions & 0 deletions config/Zmq2PushPull.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Configuration for the ZMQ Client (PUSH/PULL or PUB/SUB)
/**/zmq_bridge_client_pull:
ros__parameters:
network_interface: "network_bridge::ZmqInterface"

ZmqInterface:
role: "client"
pattern: "push_pull" # Options: pub_sub (default) or push_pull
remote_address: "127.0.0.1"
port: 5556

default_rate: 10.0
default_zstd_level: 3
publish_stale_data: False

subscribe_namespace: "/zmq2/push_pull"
publish_namespace: "/zmq2/push_pull"
99 changes: 99 additions & 0 deletions include/network_interfaces/zmq_interface.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
==============================================================================
MIT License

Copyright (c) 2024 Ethan M Brown
Copyright (c) 2026 PAL Robotics

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
==============================================================================
*/

#pragma once

#include <atomic>
#include <string>
#include <thread>
#include <vector>

#include <zmqpp/zmqpp.hpp>

#include "network_interfaces/network_interface_base.hpp"

namespace network_bridge
{

/**
* @class ZmqInterface
* @brief Represents a ZMQ network interface.
*
* The ZmqInterface class is a concrete implementation of the NetworkInterface
* abstract class. It provides functionality for opening, closing, receiving and
* writing data to a ZMQ interface. It also handles receiving data
* asynchronously and provides error handling capabilities.
*/
class ZmqInterface : public NetworkInterface
{
public:
ZmqInterface()
: NetworkInterface()
{
ready_ = false;
failed_ = false;
}

virtual ~ZmqInterface() {close();}

protected:
/**
* @brief Initializes interface by loading parameters.
*
* Called from NetworkInterface::initialize()
*/
void initialize_() override;

public:
bool has_failed() const override;
bool is_ready() const override;
void open() override;
void close() override;
void write(const std::vector<uint8_t> & data) override;

protected:
void load_parameters();
void setup_server();
void setup_client();
void receive_thread();

private:
zmqpp::context context_;
std::shared_ptr<zmqpp::socket> socket_;

std::string role_;
std::string pattern_;
std::string remote_address_;
int port_;
std::atomic<bool> ready_;
std::atomic<bool> failed_;
std::atomic<bool> shutting_down_;

std::thread packet_thread_;
};

} // namespace network_bridge
Loading
Loading