Skip to content

Commit

Permalink
Add --log-level to ros2 bag play and record (#1625)
Browse files Browse the repository at this point in the history
* Add log level parameter to rosbag play and record

Signed-off-by: Roman Sokolkov <rsokolkov@gmail.com>

* Fix warning with typecast and add test coverage for default constructors

Signed-off-by: Michael Orlov <michael.orlov@apex.ai>

---------

Signed-off-by: Roman Sokolkov <rsokolkov@gmail.com>
Signed-off-by: Michael Orlov <michael.orlov@apex.ai>
Co-authored-by: Michael Orlov <michael.orlov@apex.ai>
  • Loading branch information
r7vme and MichaelOrlov authored May 10, 2024
1 parent 7d48046 commit 5a06430
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 8 deletions.
6 changes: 5 additions & 1 deletion ros2bag/ros2bag/verb/play.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@ def add_arguments(self, parser, cli_name): # noqa: D102
help='Determine the source of the service requests to be replayed. This option only '
'makes sense if the "--publish-service-requests" option is set. By default,'
' the service requests replaying from recorded service introspection message.')
parser.add_argument(
'--log-level', type=str, default='info',
choices=['debug', 'info', 'warn', 'error', 'fatal'],
help='Logging level.')

def get_playback_until_from_arg_group(self, playback_until_sec, playback_until_nsec) -> int:
nano_scale = 1000 * 1000 * 1000
Expand Down Expand Up @@ -236,7 +240,7 @@ def main(self, *, args): # noqa: D102
else:
play_options.service_requests_source = ServiceRequestsSource.CLIENT_INTROSPECTION

player = Player()
player = Player(args.log_level)
try:
player.play(storage_options, play_options)
except KeyboardInterrupt:
Expand Down
6 changes: 5 additions & 1 deletion ros2bag/ros2bag/verb/record.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,10 @@ def add_recorder_arguments(parser: ArgumentParser) -> None:
help='Enable snapshot mode. Messages will not be written to the bagfile until '
'the "/rosbag2_recorder/snapshot" service is called. e.g. \n '
'ros2 service call /rosbag2_recorder/snapshot rosbag2_interfaces/Snapshot')
parser.add_argument(
'--log-level', type=str, default='info',
choices=['debug', 'info', 'warn', 'error', 'fatal'],
help='Logging level.')

# Storage configuration
add_writer_storage_plugin_extensions(parser)
Expand Down Expand Up @@ -339,7 +343,7 @@ def main(self, *, args): # noqa: D102
record_options.use_sim_time = args.use_sim_time
record_options.disable_keyboard_controls = args.disable_keyboard_controls

recorder = Recorder()
recorder = Recorder(args.log_level)

try:
recorder.record(storage_options, record_options, args.node_name)
Expand Down
7 changes: 7 additions & 0 deletions rosbag2_py/rosbag2_py/_transport.pyi
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from typing import Any, ClassVar, List

from typing import overload
import datetime
import rosbag2_py._storage

Expand Down Expand Up @@ -32,7 +33,10 @@ class PlayOptions:
def __init__(self) -> None: ...

class Player:
@overload
def __init__(self) -> None: ...
@overload
def __init__(self, arg0: str) -> None: ...
def burst(self, storage_options: rosbag2_py._storage.StorageOptions, play_options: PlayOptions, num_messages: int) -> None: ...
def cancel(self, *args, **kwargs) -> Any: ...
def play(self, storage_options: rosbag2_py._storage.StorageOptions, play_options: PlayOptions) -> None: ...
Expand Down Expand Up @@ -66,7 +70,10 @@ class RecordOptions:
def __init__(self) -> None: ...

class Recorder:
@overload
def __init__(self) -> None: ...
@overload
def __init__(self, arg0: str) -> None: ...
def cancel(self, *args, **kwargs) -> Any: ...
def record(self, storage_options: rosbag2_py._storage.StorageOptions, record_options: RecordOptions, node_name: str = ...) -> None: ...

Expand Down
47 changes: 41 additions & 6 deletions rosbag2_py/src/rosbag2_py/_transport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

#include <algorithm>
#include <csignal>
#include <chrono>
#include <memory>
Expand All @@ -37,6 +38,36 @@ typedef std::unordered_map<std::string, rclcpp::QoS> QoSMap;
namespace
{

class Arguments
{
public:
explicit Arguments(const std::vector<std::string> & args)
: arguments_(args)
{
std::for_each(
arguments_.begin(), arguments_.end(),
[this](const std::string & arg) {
pointers_.push_back(const_cast<char *>(arg.c_str()));
}
);
pointers_.push_back(nullptr);
}

char ** argv()
{
return arguments_.empty() ? nullptr : pointers_.data();
}

[[nodiscard]] int argc() const
{
return static_cast<int>(arguments_.size());
}

private:
std::vector<std::string> arguments_;
std::vector<char *> pointers_;
};

rclcpp::QoS qos_from_handle(const py::handle source)
{
PyObject * raw_obj = PyObject_CallMethod(source.ptr(), "get_c_qos_profile", "");
Expand Down Expand Up @@ -132,9 +163,10 @@ class Player
public:
using SignalHandlerType = void (*)(int);

Player()
explicit Player(const std::string & log_level = "info")
{
rclcpp::init(0, nullptr);
Arguments arguments({"--ros-args", "--log-level", log_level});
rclcpp::init(arguments.argc(), arguments.argv());
}

virtual ~Player()
Expand Down Expand Up @@ -291,9 +323,10 @@ class Recorder
{
public:
using SignalHandlerType = void (*)(int);
Recorder()
explicit Recorder(const std::string & log_level = "info")
{
rclcpp::init(0, nullptr);
Arguments arguments({"--ros-args", "--log-level", log_level});
rclcpp::init(arguments.argc(), arguments.argv());
}

virtual ~Recorder()
Expand Down Expand Up @@ -555,7 +588,8 @@ PYBIND11_MODULE(_transport, m) {
;

py::class_<rosbag2_py::Player>(m, "Player")
.def(py::init())
.def(py::init<>())
.def(py::init<const std::string &>())
.def("play", &rosbag2_py::Player::play, py::arg("storage_options"), py::arg("play_options"))
.def(
"burst", &rosbag2_py::Player::burst, py::arg("storage_options"), py::arg("play_options"),
Expand All @@ -564,7 +598,8 @@ PYBIND11_MODULE(_transport, m) {
;

py::class_<rosbag2_py::Recorder>(m, "Recorder")
.def(py::init())
.def(py::init<>())
.def(py::init<const std::string &>())
.def(
"record", &rosbag2_py::Recorder::record, py::arg("storage_options"), py::arg("record_options"),
py::arg("node_name") = "rosbag2_recorder")
Expand Down
20 changes: 20 additions & 0 deletions rosbag2_py/test/test_transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,26 @@ def test_options_qos_conversion():
assert record_options.topic_qos_profile_overrides == simple_overrides


def test_player_log_level():
rosbag2_py.Player() # Test for default constructor
valid_log_level = 'debug'
rosbag2_py.Player(valid_log_level)

invalid_log_level = 'xxx'
with pytest.raises(RuntimeError):
rosbag2_py.Player(invalid_log_level)


def test_recoder_log_level():
rosbag2_py.Recorder() # Test for default constructor
valid_log_level = 'debug'
rosbag2_py.Recorder(valid_log_level)

invalid_log_level = 'xxx'
with pytest.raises(RuntimeError):
rosbag2_py.Recorder(invalid_log_level)


@pytest.mark.parametrize('storage_id', TESTED_STORAGE_IDS)
def test_record_cancel(tmp_path, storage_id):
bag_path = tmp_path / 'test_record_cancel'
Expand Down

0 comments on commit 5a06430

Please sign in to comment.