Skip to content

Commit 89f8a20

Browse files
author
alberto
committed
added backward compatible example
1 parent 025b26a commit 89f8a20

File tree

8 files changed

+256
-0
lines changed

8 files changed

+256
-0
lines changed

README.md

+6
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,12 @@ The other process create an approximate and an exact time subscribers.
101101

102102
One process sets its own parameters. The other reads them.
103103

104+
#### Backward compatibility
105+
106+
[**README**](simple_backward_compatible)
107+
108+
Allow nodes compiled against different versions of a message interface to communicate.
109+
104110

105111
#### Secure ROS2
106112

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
cmake_minimum_required(VERSION 3.5)
2+
project(simple_backward_compatible)
3+
4+
## Comment the following line if you want to build V1 of the code to test backward compatibility
5+
#set(BUILD_V2 true)
6+
7+
# Default to C++14
8+
if(NOT CMAKE_CXX_STANDARD)
9+
set(CMAKE_CXX_STANDARD 14)
10+
endif()
11+
12+
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
13+
add_compile_options(-Wall -Wextra -Wpedantic)
14+
endif()
15+
16+
find_package(ament_cmake REQUIRED)
17+
find_package(rosidl_default_generators REQUIRED)
18+
find_package(rclcpp REQUIRED)
19+
20+
if (BUILD_V2)
21+
add_definitions(-DBUILD_V2)
22+
set (INTERFACES_PATH "interfaces_v2")
23+
else()
24+
set (INTERFACES_PATH "interfaces_v1")
25+
endif()
26+
27+
set(dependencies
28+
rclcpp
29+
)
30+
31+
message(" -------------------> BUILDING ${INTERFACES_PATH}")
32+
33+
rosidl_generate_interfaces(${PROJECT_NAME}
34+
${INTERFACES_PATH}/msg/OptionalFields.msg
35+
)
36+
37+
add_executable(publisher_main src/publisher_main.cpp)
38+
ament_target_dependencies(publisher_main ${dependencies})
39+
rosidl_target_interfaces(publisher_main ${PROJECT_NAME} "rosidl_typesupport_cpp")
40+
41+
add_executable(subscriber_main src/subscriber_main.cpp)
42+
ament_target_dependencies(subscriber_main ${dependencies})
43+
rosidl_target_interfaces(subscriber_main ${PROJECT_NAME} "rosidl_typesupport_cpp")
44+
45+
install(TARGETS publisher_main subscriber_main
46+
DESTINATION lib/${PROJECT_NAME}
47+
)
48+
49+
ament_package()

simple_backward_compatible/README.md

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Simple Backward Compatibility
2+
3+
4+
This package is meant to be used to demonstrate how to create messages with optional fields and how to exploit them for achieving backward compatibility.
5+
6+
Long story short, usually when you add a new field to a message interface and then you try to send these updated messages to a subscriber which has been compiled against an old version of the message library (i.e. without the new field) a memory error will arise.
7+
8+
9+
ROS2 introduces optional fields which are dynamically allocated by the subscriber.
10+
11+
12+
## How to use it
13+
14+
Let's start by creating a workspace and compile two simple publisher and subscriber nodes.
15+
16+
```
17+
mkdir -p ws_v1/src
18+
cd ws_v1/src
19+
ln -s <PATH_TO_THIS_REPO>/simple_backward_compatible .
20+
cd ..
21+
colcon build
22+
```
23+
24+
The nodes created in this workspace will communicate using [this message interface](interfaces_v1/msg/Optional_fields.msg) which we will call v1.
25+
26+
You can immediately run the subscriber node.
27+
28+
```
29+
source install/local_setup.sh
30+
ros2 run simple_backward_compatible subscriber_main
31+
```
32+
33+
Now, while the subscriber is still running, open a new terminal and create a second workspace.
34+
35+
```
36+
mkdir -p ws_v2/src
37+
cd ws_v2/src
38+
ln -s <PATH_TO_THIS_REPO>/simple_backward_compatible .
39+
cd ..
40+
```
41+
42+
However this time, before compiling, let's make a quick change.
43+
Open with a text editor `CMakeLists.txt` and uncomment line #5 (the one saying `#set(BUILD_V2 true)`).
44+
Then build.
45+
46+
```
47+
colcon build
48+
```
49+
50+
You have just told the compiler to use a different version of the message interface, which you can find [here](interfaces_v2/msg/Optional_fields.msg) and we will call it v2.
51+
52+
As you can see, the new messages have some additional fields characterized by variable length arrays.
53+
You can note from the [publisher code](src/publisher_main.cpp) that the fields of the updated message are being properly filled with values.
54+
55+
Now run a publisher node from this second workspace.
56+
57+
```
58+
source install/local_setup.sh
59+
ros2 run simple_backward_compatible publisher_main
60+
```
61+
62+
You will note that the two nodes are communicating properly, even if compiled against two different versions of the message interfaces.
63+
64+
**NOTE**: if you try to run a v2 subscriber and a v1 publisher you will still get a memory error.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
string data
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
string data
2+
bool[<=1] optional_boolean
3+
int32[] optional_vector
+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?xml version="1.0"?>
2+
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
3+
<package format="3">
4+
<name>simple_backward_compatible</name>
5+
<version>0.0.1</version>
6+
<description>Examples of sending backward compatible messages to an old subscriber</description>
7+
<maintainer email="[email protected]">Alberto Soragna</maintainer>
8+
<license>MIT</license>
9+
<author>Alberto Soragna</author>
10+
11+
<buildtool_depend>ament_cmake</buildtool_depend>
12+
<buildtool_depend>rosidl_default_generators</buildtool_depend>
13+
14+
<build_depend>rclcpp</build_depend>
15+
16+
<exec_depend>rclcpp</exec_depend>
17+
<exec_depend>rosidl_default_runtime</exec_depend>
18+
19+
<member_of_group>rosidl_interface_packages</member_of_group>
20+
21+
<export>
22+
<build_type>ament_cmake</build_type>
23+
</export>
24+
</package>
25+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#include <memory>
2+
#include <iostream>
3+
#include <thread>
4+
#include <sstream>
5+
6+
#include "rclcpp/rclcpp.hpp"
7+
#include "simple_backward_compatible/msg/optional_fields.hpp"
8+
9+
using namespace std::chrono_literals;
10+
using OptionalFieldsMsg = simple_backward_compatible::msg::OptionalFields;
11+
12+
class SimplePublisherNode : public rclcpp::Node {
13+
14+
public:
15+
16+
SimplePublisherNode() : Node("publisher_node")
17+
{
18+
_publisher = this->create_publisher<OptionalFieldsMsg>("my_topic");
19+
20+
auto period = 500ms;
21+
_timer = this->create_wall_timer(period, std::bind(&SimplePublisherNode::publish, this));
22+
_count = 0;
23+
}
24+
25+
void publish()
26+
{
27+
OptionalFieldsMsg::SharedPtr message(new OptionalFieldsMsg());
28+
29+
_count ++;
30+
message->data = "Hello, world! " + std::to_string(_count);
31+
#ifdef BUILD_V2
32+
message->optional_boolean.push_back(true);
33+
message->optional_vector = { 10, 20, 30 };
34+
#endif
35+
36+
_publisher->publish(message);
37+
38+
#ifdef BUILD_V2
39+
RCLCPP_INFO(this->get_logger(), "Publishing VERSION2: '%s'", message->data.c_str());
40+
#else
41+
RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message->data.c_str());
42+
#endif
43+
}
44+
45+
private:
46+
rclcpp::Publisher<OptionalFieldsMsg>::SharedPtr _publisher;
47+
rclcpp::TimerBase::SharedPtr _timer;
48+
int _count;
49+
};
50+
51+
52+
int main(int argc, char ** argv)
53+
{
54+
rclcpp::init(argc, argv);
55+
56+
std::shared_ptr<SimplePublisherNode> node = std::make_shared<SimplePublisherNode>();
57+
58+
rclcpp::spin(node);
59+
60+
rclcpp::shutdown();
61+
return 0;
62+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#include <memory>
2+
#include <iostream>
3+
#include <thread>
4+
#include <sstream>
5+
6+
#include "rclcpp/rclcpp.hpp"
7+
#include "simple_backward_compatible/msg/optional_fields.hpp"
8+
9+
using OptionalFieldsMsg = simple_backward_compatible::msg::OptionalFields;
10+
11+
class SimpleSubscriberNode : public rclcpp::Node {
12+
13+
public:
14+
15+
SimpleSubscriberNode() : Node("subscriber_node")
16+
{
17+
_subscriber = this->create_subscription<OptionalFieldsMsg>("my_topic",
18+
std::bind(&SimpleSubscriberNode::simple_callback, this, std::placeholders::_1));
19+
}
20+
21+
22+
private:
23+
24+
void simple_callback(const OptionalFieldsMsg::SharedPtr msg)
25+
{
26+
RCLCPP_INFO(this->get_logger(), "Received msg: '%s'", msg->data.c_str());
27+
}
28+
29+
rclcpp::Subscription<OptionalFieldsMsg>::SharedPtr _subscriber;
30+
};
31+
32+
33+
34+
int main(int argc, char ** argv)
35+
{
36+
rclcpp::init(argc, argv);
37+
38+
std::shared_ptr<SimpleSubscriberNode> node = std::make_shared<SimpleSubscriberNode>();
39+
40+
rclcpp::spin(node);
41+
42+
rclcpp::shutdown();
43+
return 0;
44+
}
45+
46+

0 commit comments

Comments
 (0)