Skip to content

Commit cf892ee

Browse files
authored
Add pause/resume options to the bag recorder (ros2#905)
* Add pause/resume options to the bag recorder Signed-off-by: Ivan Santiago Paunovic <[email protected]>
1 parent 27b6a7a commit cf892ee

File tree

7 files changed

+174
-2
lines changed

7 files changed

+174
-2
lines changed

ros2bag/ros2bag/verb/record.py

+4
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,9 @@ def add_arguments(self, parser, cli_name): # noqa: D102
149149
'write:'
150150
' pragmas: [\"<setting_name>\" = <setting_value>]'
151151
'For a list of sqlite3 settings, refer to sqlite3 documentation')
152+
parser.add_argument(
153+
'--start-paused', action='store_true', default=False,
154+
help='Start the recorder in a paused state.')
152155
self._subparser = parser
153156

154157
def main(self, *, args): # noqa: D102
@@ -219,6 +222,7 @@ def main(self, *, args): # noqa: D102
219222
record_options.compression_threads = args.compression_threads
220223
record_options.topic_qos_profile_overrides = qos_profile_overrides
221224
record_options.include_hidden_topics = args.include_hidden_topics
225+
record_options.start_paused = args.start_paused
222226

223227
recorder = Recorder()
224228

rosbag2_py/src/rosbag2_py/_transport.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ PYBIND11_MODULE(_transport, m) {
269269
&RecordOptions::getTopicQoSProfileOverrides,
270270
&RecordOptions::setTopicQoSProfileOverrides)
271271
.def_readwrite("include_hidden_topics", &RecordOptions::include_hidden_topics)
272+
.def_readwrite("start_paused", &RecordOptions::start_paused)
272273
;
273274

274275
py::class_<rosbag2_py::Player>(m, "Player")

rosbag2_tests/test/rosbag2_tests/test_rosbag2_record_end_to_end.cpp

+45
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,51 @@ TEST_F(RecordFixture, record_end_to_end_test) {
180180
EXPECT_THAT(wrong_topic_messages, IsEmpty());
181181
}
182182

183+
TEST_F(RecordFixture, record_end_to_end_test_start_paused) {
184+
auto message = get_messages_strings()[0];
185+
message->string_value = "test";
186+
187+
rosbag2_test_common::PublicationManager pub_manager;
188+
pub_manager.setup_publisher("/test_topic", message, 10);
189+
190+
auto process_handle = start_execution(
191+
"ros2 bag record --max-cache-size 0 --output " + root_bag_path_.string() +
192+
" --start-paused /test_topic");
193+
auto cleanup_process_handle = rcpputils::make_scope_exit(
194+
[process_handle]() {
195+
stop_execution(process_handle);
196+
});
197+
198+
ASSERT_TRUE(pub_manager.wait_for_matched("/test_topic")) <<
199+
"Expected find rosbag subscription";
200+
201+
wait_for_db();
202+
203+
pub_manager.run_publishers();
204+
205+
stop_execution(process_handle);
206+
cleanup_process_handle.cancel();
207+
208+
// TODO(Martin-Idel-SI): Find out how to correctly send a Ctrl-C signal on Windows
209+
// This is necessary as the process is killed hard on Windows and doesn't write a metadata file
210+
#ifdef _WIN32
211+
rosbag2_storage::BagMetadata metadata{};
212+
metadata.version = 1;
213+
metadata.storage_identifier = "sqlite3";
214+
metadata.relative_file_paths = {get_bag_file_path(0).string()};
215+
metadata.duration = std::chrono::nanoseconds(0);
216+
metadata.starting_time =
217+
std::chrono::time_point<std::chrono::high_resolution_clock>(std::chrono::nanoseconds(0));
218+
metadata.message_count = 0;
219+
rosbag2_storage::MetadataIo metadata_io;
220+
metadata_io.write_metadata(root_bag_path_.string(), metadata);
221+
#endif
222+
223+
wait_for_metadata();
224+
auto test_topic_messages = get_messages_for_topic<test_msgs::msg::Strings>("/test_topic");
225+
EXPECT_THAT(test_topic_messages, IsEmpty());
226+
}
227+
183228
TEST_F(RecordFixture, record_end_to_end_exits_gracefully_on_sigterm) {
184229
const std::string topic_name = "/test_sigterm";
185230
auto message = get_messages_strings()[0];

rosbag2_transport/include/rosbag2_transport/record_options.hpp

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <unordered_map>
2121
#include <vector>
2222

23+
#include "keyboard_handler/keyboard_handler.hpp"
2324
#include "rclcpp/rclcpp.hpp"
2425
#include "rosbag2_storage/yaml.hpp"
2526
#include "rosbag2_transport/visibility_control.hpp"
@@ -43,6 +44,7 @@ struct RecordOptions
4344
uint64_t compression_threads = 0;
4445
std::unordered_map<std::string, rclcpp::QoS> topic_qos_profile_overrides{};
4546
bool include_hidden_topics = false;
47+
bool start_paused = false;
4648
};
4749

4850
} // namespace rosbag2_transport

rosbag2_transport/include/rosbag2_transport/recorder.hpp

+35
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
#include <utility>
2424
#include <vector>
2525

26+
#include "keyboard_handler/keyboard_handler.hpp"
27+
2628
#include "rclcpp/node.hpp"
2729
#include "rclcpp/qos.hpp"
2830

@@ -59,6 +61,15 @@ class Recorder : public rclcpp::Node
5961
const std::string & node_name = "rosbag2_recorder",
6062
const rclcpp::NodeOptions & node_options = rclcpp::NodeOptions());
6163

64+
ROSBAG2_TRANSPORT_PUBLIC
65+
Recorder(
66+
std::shared_ptr<rosbag2_cpp::Writer> writer,
67+
std::shared_ptr<KeyboardHandler> keyboard_handler,
68+
const rosbag2_storage::StorageOptions & storage_options,
69+
const rosbag2_transport::RecordOptions & record_options,
70+
const std::string & node_name = "rosbag2_recorder",
71+
const rclcpp::NodeOptions & node_options = rclcpp::NodeOptions());
72+
6273
ROSBAG2_TRANSPORT_PUBLIC
6374
virtual ~Recorder();
6475

@@ -80,6 +91,24 @@ class Recorder : public rclcpp::Node
8091
ROSBAG2_TRANSPORT_PUBLIC
8192
const rosbag2_cpp::Writer & get_writer_handle();
8293

94+
/// Pause the recording.
95+
ROSBAG2_TRANSPORT_PUBLIC
96+
void pause();
97+
98+
/// Resume recording.
99+
ROSBAG2_TRANSPORT_PUBLIC
100+
void resume();
101+
102+
/// Pause if it was recording, continue recording if paused.
103+
ROSBAG2_TRANSPORT_PUBLIC
104+
void toggle_paused();
105+
106+
/// Return the current paused state.
107+
ROSBAG2_TRANSPORT_PUBLIC
108+
bool is_paused();
109+
110+
inline constexpr static const auto kPauseResumeToggleKey = KeyboardHandler::KeyCode::SPACE;
111+
83112
private:
84113
void topics_discovery();
85114

@@ -124,6 +153,12 @@ class Recorder : public rclcpp::Node
124153
std::unordered_map<std::string, rclcpp::QoS> topic_qos_profile_overrides_;
125154
std::unordered_set<std::string> topic_unknown_types_;
126155
rclcpp::Service<rosbag2_interfaces::srv::Snapshot>::SharedPtr srv_snapshot_;
156+
std::atomic<bool> paused_ = false;
157+
// Keyboard handler
158+
std::shared_ptr<KeyboardHandler> keyboard_handler_;
159+
// Toogle paused key callback handle
160+
KeyboardHandler::callback_handle_t toggle_paused_key_callback_handle_ =
161+
KeyboardHandler::invalid_handle;
127162
};
128163

129164
} // namespace rosbag2_transport

rosbag2_transport/src/rosbag2_transport/recorder.cpp

+59-2
Original file line numberDiff line numberDiff line change
@@ -56,16 +56,45 @@ Recorder::Recorder(
5656
const rosbag2_transport::RecordOptions & record_options,
5757
const std::string & node_name,
5858
const rclcpp::NodeOptions & node_options)
59+
: Recorder(
60+
std::move(writer),
61+
std::make_shared<KeyboardHandler>(),
62+
storage_options,
63+
record_options,
64+
node_name,
65+
node_options)
66+
{}
67+
68+
Recorder::Recorder(
69+
std::shared_ptr<rosbag2_cpp::Writer> writer,
70+
std::shared_ptr<KeyboardHandler> keyboard_handler,
71+
const rosbag2_storage::StorageOptions & storage_options,
72+
const rosbag2_transport::RecordOptions & record_options,
73+
const std::string & node_name,
74+
const rclcpp::NodeOptions & node_options)
5975
: rclcpp::Node(node_name, rclcpp::NodeOptions(node_options).start_parameter_event_publisher(false)),
6076
writer_(std::move(writer)),
6177
storage_options_(storage_options),
6278
record_options_(record_options),
63-
stop_discovery_(record_options_.is_discovery_disabled)
79+
stop_discovery_(record_options_.is_discovery_disabled),
80+
paused_(record_options.start_paused),
81+
keyboard_handler_(std::move(keyboard_handler))
6482
{
83+
std::string key_str = enum_key_code_to_str(Recorder::kPauseResumeToggleKey);
84+
toggle_paused_key_callback_handle_ =
85+
keyboard_handler_->add_key_press_callback(
86+
[this](KeyboardHandler::KeyCode /*key_code*/,
87+
KeyboardHandler::KeyModifiers /*key_modifiers*/) {this->toggle_paused();},
88+
Recorder::kPauseResumeToggleKey);
89+
// show instructions
90+
RCLCPP_INFO_STREAM(
91+
get_logger(),
92+
"Press " << key_str << " for pausing/resuming");
6593
}
6694

6795
Recorder::~Recorder()
6896
{
97+
keyboard_handler_->delete_key_press_callback(toggle_paused_key_callback_handle_);
6998
stop_discovery_ = true;
7099
if (discovery_future_.valid()) {
71100
discovery_future_.wait();
@@ -113,6 +142,32 @@ const rosbag2_cpp::Writer & Recorder::get_writer_handle()
113142
return *writer_;
114143
}
115144

145+
void Recorder::pause()
146+
{
147+
paused_.store(true);
148+
RCLCPP_INFO_STREAM(get_logger(), "Pausing recording.");
149+
}
150+
151+
void Recorder::resume()
152+
{
153+
paused_.store(false);
154+
RCLCPP_INFO_STREAM(get_logger(), "Resuming recording.");
155+
}
156+
157+
void Recorder::toggle_paused()
158+
{
159+
if (paused_.load()) {
160+
this->resume();
161+
} else {
162+
this->pause();
163+
}
164+
}
165+
166+
bool Recorder::is_paused()
167+
{
168+
return paused_.load();
169+
}
170+
116171
void Recorder::topics_discovery()
117172
{
118173
while (rclcpp::ok() && stop_discovery_ == false) {
@@ -225,7 +280,9 @@ Recorder::create_subscription(
225280
topic_type,
226281
qos,
227282
[this, topic_name, topic_type](std::shared_ptr<rclcpp::SerializedMessage> message) {
228-
writer_->write(message, topic_name, topic_type, rclcpp::Clock(RCL_SYSTEM_TIME).now());
283+
if (!paused_.load()) {
284+
writer_->write(message, topic_name, topic_type, rclcpp::Clock(RCL_SYSTEM_TIME).now());
285+
}
229286
});
230287
return subscription;
231288
}

rosbag2_transport/test/rosbag2_transport/test_keyboard_controls.cpp

+28
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,13 @@
2626
#include "rosbag2_test_common/subscription_manager.hpp"
2727

2828
#include "rosbag2_transport/player.hpp"
29+
#include "rosbag2_transport/recorder.hpp"
2930

3031
#include "test_msgs/msg/arrays.hpp"
3132
#include "test_msgs/msg/basic_types.hpp"
3233
#include "test_msgs/message_fixtures.hpp"
3334

35+
#include "record_integration_fixture.hpp"
3436
#include "rosbag2_play_test_fixture.hpp"
3537
#include "rosbag2_transport_test_fixture.hpp"
3638
#include "mock_keyboard_handler.hpp"
@@ -177,3 +179,29 @@ TEST_F(RosBag2PlayTestFixture, test_keyboard_controls)
177179
EXPECT_THAT(player->num_played_next, 1);
178180
EXPECT_THAT(player->num_set_rate, 2);
179181
}
182+
183+
TEST_F(RecordIntegrationTestFixture, test_keyboard_controls)
184+
{
185+
auto mock_sequential_writer = std::make_unique<MockSequentialWriter>();
186+
auto writer = std::make_unique<rosbag2_cpp::Writer>(std::move(mock_sequential_writer));
187+
auto keyboard_handler = std::make_shared<MockKeyboardHandler>();
188+
189+
rosbag2_transport::RecordOptions record_options = {true, false, {}, "rmw_format", 100ms};
190+
record_options.start_paused = true;
191+
192+
auto recorder = std::make_shared<Recorder>(
193+
std::move(writer), keyboard_handler, storage_options_,
194+
record_options, "test_count_recorder");
195+
196+
recorder->record();
197+
198+
this->start_async_spin(recorder);
199+
200+
EXPECT_THAT(recorder->is_paused(), true);
201+
keyboard_handler->simulate_key_press(rosbag2_transport::Recorder::kPauseResumeToggleKey);
202+
EXPECT_THAT(recorder->is_paused(), false);
203+
keyboard_handler->simulate_key_press(rosbag2_transport::Recorder::kPauseResumeToggleKey);
204+
EXPECT_THAT(recorder->is_paused(), true);
205+
206+
this->stop_spinning();
207+
}

0 commit comments

Comments
 (0)