Skip to content

Commit f7198ee

Browse files
authored
Merge branch 'master' into add/realtime/mutexes
2 parents ca86ba3 + 78f0372 commit f7198ee

File tree

7 files changed

+139
-49
lines changed

7 files changed

+139
-49
lines changed

.github/workflows/downstream-build.yml

Lines changed: 0 additions & 28 deletions
This file was deleted.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Contains a set of tools that can be used from a hard realtime thread, without br
88
## Build status
99
ROS2 Distro | Branch | Build status | Documentation | Released packages
1010
:---------: | :----: | :----------: | :-----------: | :---------------:
11-
**Rolling** | [`master`](https://github.com/ros-controls/realtime_tools/tree/master) | [![Binary Build](https://github.com/ros-controls/realtime_tools/actions/workflows/binary-build.yml/badge.svg?branch=master)](https://github.com/ros-controls/realtime_tools/actions/workflows/binary-build.yml?branch=master) <br /> [![Source Build](https://github.com/ros-controls/realtime_tools/actions/workflows/source-build.yml/badge.svg?branch=master)](https://github.com/ros-controls/realtime_tools/actions/workflows/source-build.yml?branch=master) | [API](http://docs.ros.org/en/rolling/p/realtime_tools/) | [realtime_tools](https://index.ros.org/p/realtime_tools/#rolling)
11+
**Rolling** | [`master`](https://github.com/ros-controls/realtime_tools/tree/master) | [![Binary Build](https://github.com/ros-controls/realtime_tools/actions/workflows/rolling-binary-build.yml/badge.svg?branch=master)](https://github.com/ros-controls/realtime_tools/actions/workflows/rolling-binary-build.yml?branch=master) <br /> [![Source Build](https://github.com/ros-controls/realtime_tools/actions/workflows/rolling-source-build.yml/badge.svg?branch=master)](https://github.com/ros-controls/realtime_tools/actions/workflows/rolling-source-build.yml?branch=master) | [API](http://docs.ros.org/en/rolling/p/realtime_tools/) | [realtime_tools](https://index.ros.org/p/realtime_tools/#rolling)
1212
**Jazzy** | [`master`](https://github.com/ros-controls/realtime_tools/tree/master) | see above | [API](http://docs.ros.org/en/jazzy/p/realtime_tools/) | [realtime_tools](https://index.ros.org/p/realtime_tools/#jazzy)
1313
**Humble** | [`humble`](https://github.com/ros-controls/realtime_tools/tree/humble) | [![Binary Build](https://github.com/ros-controls/realtime_tools/actions/workflows/humble-binary-build.yml/badge.svg?branch=master)](https://github.com/ros-controls/realtime_tools/actions/workflows/humble-binary-build.yml?branch=master) <br /> [![Source Build](https://github.com/ros-controls/realtime_tools/actions/workflows/humble-source-build.yml/badge.svg?branch=master)](https://github.com/ros-controls/realtime_tools/actions/workflows/humble-source-build.yml?branch=master) | [API](http://docs.ros.org/en/humble/p/realtime_tools/) | [realtime_tools](https://index.ros.org/p/realtime_tools/#humble)
1414

include/realtime_tools/realtime_publisher.hpp

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -49,27 +49,39 @@
4949

5050
namespace realtime_tools
5151
{
52-
template <class Msg>
52+
template <class MessageT>
5353
class RealtimePublisher
5454
{
55-
private:
56-
using PublisherSharedPtr = typename rclcpp::Publisher<Msg>::SharedPtr;
57-
5855
public:
56+
/// Provide various typedefs to resemble the rclcpp::Publisher type
57+
using PublisherType = rclcpp::Publisher<MessageT>;
58+
using PublisherSharedPtr = typename rclcpp::Publisher<MessageT>::SharedPtr;
59+
60+
using PublishedType = typename rclcpp::TypeAdapter<MessageT>::custom_type;
61+
using ROSMessageType = typename rclcpp::TypeAdapter<MessageT>::ros_message_type;
62+
63+
RCLCPP_SMART_PTR_DEFINITIONS(RealtimePublisher<MessageT>)
64+
5965
/// The msg_ variable contains the data that will get published on the ROS topic.
60-
Msg msg_;
66+
MessageT msg_;
6167

6268
/** \brief Constructor for the realtime publisher
6369
*
6470
* \param publisher the publisher to wrap
6571
*/
6672
explicit RealtimePublisher(PublisherSharedPtr publisher)
67-
: publisher_(publisher), is_running_(false), keep_running_(true), turn_(LOOP_NOT_STARTED)
73+
: publisher_(publisher), is_running_(false), keep_running_(true), turn_(State::LOOP_NOT_STARTED)
6874
{
6975
thread_ = std::thread(&RealtimePublisher::publishingLoop, this);
7076
}
7177

72-
RealtimePublisher() : is_running_(false), keep_running_(false), turn_(LOOP_NOT_STARTED) {}
78+
[[deprecated(
79+
"Use constructor with rclcpp::Publisher<T>::SharedPtr instead - this class does not make sense "
80+
"without a real publisher")]]
81+
RealtimePublisher()
82+
: is_running_(false), keep_running_(false), turn_(State::LOOP_NOT_STARTED)
83+
{
84+
}
7385

7486
/// Destructor
7587
~RealtimePublisher()
@@ -101,7 +113,7 @@ class RealtimePublisher
101113
bool trylock()
102114
{
103115
if (msg_mutex_.try_lock()) {
104-
if (turn_ == REALTIME) {
116+
if (turn_ == State::REALTIME) {
105117
return true;
106118
} else {
107119
msg_mutex_.unlock();
@@ -112,6 +124,23 @@ class RealtimePublisher
112124
}
113125
}
114126

127+
/** \brief Try to get the data lock from realtime and publish the given message
128+
*
129+
* Tries to gain unique access to msg_ variable. If this succeeds
130+
* update the msg_ variable and call unlockAndPublish
131+
* @return false in case no lock for the realtime variable could be acquired
132+
*/
133+
bool tryPublish(const MessageT & msg)
134+
{
135+
if (!trylock()) {
136+
return false;
137+
}
138+
139+
msg_ = msg;
140+
unlockAndPublish();
141+
return true;
142+
}
143+
115144
/** \brief Unlock the msg_ variable
116145
*
117146
* After a successful trylock and after the data is written to the mgs_
@@ -120,7 +149,7 @@ class RealtimePublisher
120149
*/
121150
void unlockAndPublish()
122151
{
123-
turn_ = NON_REALTIME;
152+
turn_ = State::NON_REALTIME;
124153
unlock();
125154
}
126155

@@ -163,10 +192,10 @@ class RealtimePublisher
163192
void publishingLoop()
164193
{
165194
is_running_ = true;
166-
turn_ = REALTIME;
195+
turn_ = State::REALTIME;
167196

168197
while (keep_running_) {
169-
Msg outgoing;
198+
MessageT outgoing;
170199

171200
// Locks msg_ and copies it
172201

@@ -176,7 +205,7 @@ class RealtimePublisher
176205
lock();
177206
#endif
178207

179-
while (turn_ != NON_REALTIME && keep_running_) {
208+
while (turn_ != State::NON_REALTIME && keep_running_) {
180209
#ifdef NON_POLLING
181210
updated_cond_.wait(lock_);
182211
#else
@@ -186,7 +215,7 @@ class RealtimePublisher
186215
#endif
187216
}
188217
outgoing = msg_;
189-
turn_ = REALTIME;
218+
turn_ = State::REALTIME;
190219

191220
unlock();
192221

@@ -210,12 +239,12 @@ class RealtimePublisher
210239
std::condition_variable updated_cond_;
211240
#endif
212241

213-
enum { REALTIME, NON_REALTIME, LOOP_NOT_STARTED };
214-
std::atomic<int> turn_; // Who's turn is it to use msg_?
242+
enum class State : int { REALTIME, NON_REALTIME, LOOP_NOT_STARTED };
243+
std::atomic<State> turn_; // Who's turn is it to use msg_?
215244
};
216245

217-
template <class Msg>
218-
using RealtimePublisherSharedPtr = std::shared_ptr<RealtimePublisher<Msg>>;
246+
template <class MessageT>
247+
using RealtimePublisherSharedPtr = std::shared_ptr<RealtimePublisher<MessageT>>;
219248

220249
} // namespace realtime_tools
221250
#endif // REALTIME_TOOLS__REALTIME_PUBLISHER_HPP_

src/realtime_clock.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
* Author: Wim Meeussen
3636
*/
3737

38-
#include "realtime_tools/realtime_clock.h"
38+
#include "realtime_tools/realtime_clock.hpp"
3939

4040
#include <chrono>
4141

test/realtime_publisher_tests.cpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,44 @@ TEST(RealtimePublisher, rt_publish)
9191
EXPECT_STREQ(expected_msg, str_callback.msg_.string_value.c_str());
9292
rclcpp::shutdown();
9393
}
94+
95+
TEST(RealtimePublisher, rt_try_publish)
96+
{
97+
rclcpp::init(0, nullptr);
98+
const size_t ATTEMPTS = 10;
99+
const std::chrono::milliseconds DELAY(250);
100+
101+
const char * expected_msg = "Hello World";
102+
auto node = std::make_shared<rclcpp::Node>("construct_move_destruct");
103+
rclcpp::QoS qos(10);
104+
qos.reliable().transient_local();
105+
auto pub = node->create_publisher<StringMsg>("~/rt_publish", qos);
106+
RealtimePublisher<StringMsg> rt_pub(pub);
107+
108+
// try publish a latched message
109+
bool publish_success = false;
110+
for (std::size_t i = 0; i < ATTEMPTS; ++i) {
111+
StringMsg msg;
112+
msg.string_value = expected_msg;
113+
114+
if (rt_pub.tryPublish(msg)) {
115+
publish_success = true;
116+
break;
117+
}
118+
std::this_thread::sleep_for(DELAY);
119+
}
120+
ASSERT_TRUE(publish_success);
121+
122+
// make sure subscriber gets it
123+
StringCallback str_callback;
124+
125+
auto sub = node->create_subscription<StringMsg>(
126+
"~/rt_publish", qos,
127+
std::bind(&StringCallback::callback, &str_callback, std::placeholders::_1));
128+
for (size_t i = 0; i < ATTEMPTS && str_callback.msg_.string_value.empty(); ++i) {
129+
rclcpp::spin_some(node);
130+
std::this_thread::sleep_for(DELAY);
131+
}
132+
EXPECT_STREQ(expected_msg, str_callback.msg_.string_value.c_str());
133+
rclcpp::shutdown();
134+
}

test/realtime_publisher_tests_non_polling.cpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,44 @@ TEST(RealtimePublisherNonPolling, rt_publish)
9393
EXPECT_STREQ(expected_msg, str_callback.msg_.string_value.c_str());
9494
rclcpp::shutdown();
9595
}
96+
97+
TEST(RealtimePublisher, rt_try_publish)
98+
{
99+
rclcpp::init(0, nullptr);
100+
const size_t ATTEMPTS = 10;
101+
const std::chrono::milliseconds DELAY(250);
102+
103+
const char * expected_msg = "Hello World";
104+
auto node = std::make_shared<rclcpp::Node>("construct_move_destruct");
105+
rclcpp::QoS qos(10);
106+
qos.reliable().transient_local();
107+
auto pub = node->create_publisher<StringMsg>("~/rt_publish", qos);
108+
RealtimePublisher<StringMsg> rt_pub(pub);
109+
110+
// try publish a latched message
111+
bool publish_success = false;
112+
for (std::size_t i = 0; i < ATTEMPTS; ++i) {
113+
StringMsg msg;
114+
msg.string_value = expected_msg;
115+
116+
if (rt_pub.tryPublish(msg)) {
117+
publish_success = true;
118+
break;
119+
}
120+
std::this_thread::sleep_for(DELAY);
121+
}
122+
ASSERT_TRUE(publish_success);
123+
124+
// make sure subscriber gets it
125+
StringCallback str_callback;
126+
127+
auto sub = node->create_subscription<StringMsg>(
128+
"~/rt_publish", qos,
129+
std::bind(&StringCallback::callback, &str_callback, std::placeholders::_1));
130+
for (size_t i = 0; i < ATTEMPTS && str_callback.msg_.string_value.empty(); ++i) {
131+
rclcpp::spin_some(node);
132+
std::this_thread::sleep_for(DELAY);
133+
}
134+
EXPECT_STREQ(expected_msg, str_callback.msg_.string_value.c_str());
135+
rclcpp::shutdown();
136+
}

test/test_async_function_handler.cpp

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ TEST_F(AsyncFunctionHandlerTest, check_initialization)
104104

105105
// Once initialized, it should not be possible to initialize again
106106
async_class.get_handler().start_thread();
107+
std::this_thread::sleep_for(std::chrono::milliseconds(10));
107108
auto trigger_status = async_class.trigger();
108109
ASSERT_TRUE(trigger_status.first);
109110
ASSERT_EQ(realtime_tools::return_type::OK, trigger_status.second);
@@ -130,6 +131,7 @@ TEST_F(AsyncFunctionHandlerTest, check_triggering)
130131
// It shouldn't be possible to trigger without starting the thread
131132
ASSERT_THROW(async_class.trigger(), std::runtime_error);
132133
async_class.get_handler().start_thread();
134+
std::this_thread::sleep_for(std::chrono::milliseconds(10));
133135

134136
ASSERT_TRUE(async_class.get_handler().get_thread().joinable());
135137
ASSERT_TRUE(
@@ -174,6 +176,7 @@ TEST_F(AsyncFunctionHandlerTest, trigger_for_several_cycles)
174176
ASSERT_FALSE(async_class.get_handler().is_running());
175177
ASSERT_FALSE(async_class.get_handler().is_stopped());
176178
async_class.get_handler().start_thread();
179+
std::this_thread::sleep_for(std::chrono::milliseconds(10));
177180

178181
EXPECT_EQ(async_class.get_state().id(), lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE);
179182

@@ -193,8 +196,8 @@ TEST_F(AsyncFunctionHandlerTest, trigger_for_several_cycles)
193196
missed_triggers++;
194197
}
195198
}
196-
// Make sure that the failed triggers are less than 0.1%
197-
ASSERT_LT(missed_triggers, static_cast<int>(0.001 * total_cycles))
199+
// Make sure that the failed triggers are less than 0.5%
200+
ASSERT_LT(missed_triggers, static_cast<int>(0.005 * total_cycles))
198201
<< "The missed triggers cannot be more than 0.1%!";
199202
async_class.get_handler().stop_thread();
200203

@@ -216,6 +219,7 @@ TEST_F(AsyncFunctionHandlerTest, test_with_deactivate_and_activate_cycles)
216219
ASSERT_FALSE(async_class.get_handler().is_running());
217220
ASSERT_FALSE(async_class.get_handler().is_stopped());
218221
async_class.get_handler().start_thread();
222+
std::this_thread::sleep_for(std::chrono::milliseconds(10));
219223
ASSERT_TRUE(async_class.get_handler().is_running());
220224
ASSERT_FALSE(async_class.get_handler().is_stopped());
221225
EXPECT_EQ(async_class.get_state().id(), lifecycle_msgs::msg::State::PRIMARY_STATE_INACTIVE);
@@ -285,6 +289,7 @@ TEST_F(AsyncFunctionHandlerTest, check_triggering_with_different_return_state_an
285289
ASSERT_FALSE(
286290
realtime_tools::set_thread_affinity(async_class.get_handler().get_thread(), 0).first);
287291
async_class.get_handler().start_thread();
292+
std::this_thread::sleep_for(std::chrono::milliseconds(10));
288293

289294
ASSERT_TRUE(async_class.get_handler().get_thread().joinable());
290295
ASSERT_TRUE(realtime_tools::set_thread_affinity(async_class.get_handler().get_thread(), 0).first);
@@ -356,6 +361,7 @@ TEST_F(AsyncFunctionHandlerTest, check_exception_handling)
356361
realtime_tools::TestAsyncFunctionHandler async_class;
357362
async_class.initialize();
358363
async_class.get_handler().start_thread();
364+
std::this_thread::sleep_for(std::chrono::milliseconds(10));
359365

360366
EXPECT_EQ(async_class.get_state().id(), lifecycle_msgs::msg::State::PRIMARY_STATE_ACTIVE);
361367
auto trigger_status = async_class.trigger();
@@ -401,6 +407,7 @@ TEST_F(AsyncFunctionHandlerTest, check_exception_handling)
401407

402408
async_class.reset_counter(0);
403409
async_class.get_handler().start_thread();
410+
std::this_thread::sleep_for(std::chrono::milliseconds(10));
404411
trigger_status = async_class.trigger();
405412
ASSERT_TRUE(trigger_status.first);
406413
ASSERT_EQ(realtime_tools::return_type::OK, trigger_status.second);

0 commit comments

Comments
 (0)