Skip to content

Commit 4afd277

Browse files
christophebedardemersonknappgbiggs
authored
Add support for C++ messages, make conversion symmetrical, and refactor (#15)
* Export dynmsg_demo_library Signed-off-by: Christophe Bedard <[email protected]> * Split out 'pure' introspection lib Signed-off-by: Christophe Bedard <[email protected]> * Fix use of FetchContent*(yaml-cpp) so that it observes custom options And disable warnings. Signed-off-by: Christophe Bedard <[email protected]> * Use yaml_cpp_vendor for dynmsg Signed-off-by: Christophe Bedard <[email protected]> * Use yaml_cpp_vendor for dynmsg_demo Signed-off-by: Christophe Bedard <[email protected]> * Fix missing <cmath> header and float comparison in test_read_msg_buffer Signed-off-by: Christophe Bedard <[email protected]> * WIP: Use an rcutils allocator Signed-off-by: Christophe Bedard <[email protected]> * Add DYNMSG_VALUE_ONLY preprocessor option Signed-off-by: Christophe Bedard <[email protected]> * Work around yaml-cpp [u]int8_t handling issue Signed-off-by: Christophe Bedard <[email protected]> * Add C++ version of get_type_info() Signed-off-by: Christophe Bedard <[email protected]> * Add test for get_type_info*() Signed-off-by: Christophe Bedard <[email protected]> * Add vector utils file with get_vector_size() Signed-off-by: Christophe Bedard <[email protected]> * Add C++ support for yaml<->msg conversion and cleanup Signed-off-by: Christophe Bedard <[email protected]> * Cleanup dynmsg Signed-off-by: Christophe Bedard <[email protected]> * Expose dynmsg configs as CMake options Signed-off-by: Christophe Bedard <[email protected]> * Add dynmsg::yaml_to_string util function Signed-off-by: Christophe Bedard <[email protected]> * Add test_dynmsg package with message conversion tests Signed-off-by: Christophe Bedard <[email protected]> * Add simple examples in test_dynmsg Signed-off-by: Christophe Bedard <[email protected]> * Add simple root README Signed-off-by: Christophe Bedard <[email protected]> * Address review feedback Signed-off-by: Christophe Bedard <[email protected]> * Fix build error and gcc warnings Signed-off-by: Emerson Knapp <[email protected]> * Fix duplicate include Signed-off-by: Christophe Bedard <[email protected]> * Address review feedback Signed-off-by: Christophe Bedard <[email protected]> * Remove superfluous extern C functions Signed-off-by: Christophe Bedard <[email protected]> * Rename yaml_to_rosmsg_typeinfo to yaml_and_typeinfo_to_rosmsg Signed-off-by: Christophe Bedard <[email protected]> * Rename ros_message_init_typeinfo to ros_message_with_typeinfo_init Signed-off-by: Christophe Bedard <[email protected]> * Rename ros_message_destroy_ to ros_message_destroy_with_allocator Signed-off-by: Christophe Bedard <[email protected]> * Add dynmsg::cpp::ros_message_destroy_with_allocator and cleanup Signed-off-by: Christophe Bedard <[email protected]> * Define dynmsg_ret_t Signed-off-by: Christophe Bedard <[email protected]> * Fix lint errors Signed-off-by: Christophe Bedard <[email protected]> * Update dynmsg_demo tests to support config defines and workarounds Signed-off-by: Christophe Bedard <[email protected]> * Fix CMakeLists.txt linting error Signed-off-by: Geoffrey Biggs <[email protected]> Co-authored-by: Emerson Knapp <[email protected]> Co-authored-by: Geoffrey Biggs <[email protected]>
1 parent 53ded20 commit 4afd277

39 files changed

+4477
-717
lines changed

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# dynamic_message_introspection
2+
3+
This repo demonstrates how to do introspection of C and C++ messages.
4+
The [`dynmsg` package](./dynmsg) can convert ROS 2 messages into a YAML representation, and convert a YAML representation into ROS 2 messages.
5+
6+
The initial development of this code (with support for C messages) was graciously supported by [Rocos (@rocos.io)](https://www.rocos.io/).
7+
C++ messages support was added by [Christophe Bédard](https://github.com/christophebedard).
8+
9+
The [`dynmsg_demo` package](./dynmsg_demo) contains a demonstration of how to do dynamic introspection of messages using the `rcl` message API.
10+
11+
## Examples
12+
13+
For a C message, see [`conversion.c`](./test_dynmsg/examples/conversion_c.cpp).
14+
For a C++ message, see [`conversion.cpp`](./test_dynmsg/examples/conversion_cpp.cpp).
15+
16+
For more examples, see the [message conversion tests for `dynmsg`](./test_dynmsg/test/test_conversion.cpp).

dynmsg/CMakeLists.txt

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
cmake_minimum_required(VERSION 3.5)
2+
project(dynmsg)
3+
4+
# Default to C99
5+
if(NOT CMAKE_C_STANDARD)
6+
set(CMAKE_C_STANDARD 99)
7+
endif()
8+
9+
# Default to C++14
10+
if(NOT CMAKE_CXX_STANDARD)
11+
set(CMAKE_CXX_STANDARD 14)
12+
endif()
13+
14+
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
15+
# -fPIC for building a static lib (yaml-cpp) that later gets linked into a dynamic lib
16+
add_compile_options(-Wall -Wextra -Wpedantic -fPIC)
17+
set(FLAG_NO_WARNINGS "-w")
18+
endif()
19+
20+
# find dependencies
21+
find_package(ament_cmake REQUIRED)
22+
find_package(rcutils REQUIRED)
23+
find_package(rosidl_runtime_c REQUIRED)
24+
find_package(rosidl_runtime_cpp REQUIRED)
25+
find_package(rosidl_typesupport_introspection_c REQUIRED)
26+
find_package(rosidl_typesupport_introspection_cpp REQUIRED)
27+
find_package(yaml_cpp_vendor REQUIRED)
28+
29+
# See config.hpp.in
30+
option(DYNMSG_VALUE_ONLY "Write message member value directly instead default+value" ON)
31+
option(DYNMSG_YAML_CPP_BAD_INT8_HANDLING "Work around buggy [u]int8_t handling by yaml-cpp" ON)
32+
option(DYNMSG_PARSER_DEBUG "Enable debugging-related logs for YAML->msg conversion" OFF)
33+
configure_file(include/${PROJECT_NAME}/config.hpp.in include/${PROJECT_NAME}/config.hpp)
34+
35+
add_library(dynmsg STATIC
36+
src/msg_parser_c.cpp
37+
src/msg_parser_cpp.cpp
38+
src/message_reading_c.cpp
39+
src/message_reading_cpp.cpp
40+
src/typesupport.cpp
41+
src/vector_utils.cpp
42+
src/string_utils.cpp
43+
src/yaml_utils.cpp
44+
)
45+
ament_target_dependencies(dynmsg
46+
rcutils
47+
rosidl_runtime_c
48+
rosidl_runtime_cpp
49+
rosidl_typesupport_introspection_c
50+
rosidl_typesupport_introspection_cpp
51+
yaml_cpp_vendor
52+
)
53+
target_include_directories(dynmsg PUBLIC
54+
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
55+
"$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include>"
56+
"$<INSTALL_INTERFACE:include>"
57+
)
58+
ament_export_include_directories(include)
59+
ament_export_libraries(dynmsg)
60+
ament_export_targets(dynmsg HAS_LIBRARY_TARGET)
61+
ament_export_dependencies(rcutils)
62+
ament_export_dependencies(rosidl_runtime_c)
63+
ament_export_dependencies(rosidl_runtime_cpp)
64+
ament_export_dependencies(rosidl_typesupport_introspection_c)
65+
ament_export_dependencies(rosidl_typesupport_introspection_cpp)
66+
ament_export_dependencies(yaml_cpp_vendor)
67+
68+
install(
69+
DIRECTORY include/ ${CMAKE_CURRENT_BINARY_DIR}/include/
70+
DESTINATION include
71+
PATTERN "*.in" EXCLUDE
72+
)
73+
install(
74+
TARGETS dynmsg
75+
EXPORT dynmsg
76+
LIBRARY DESTINATION lib
77+
ARCHIVE DESTINATION lib
78+
RUNTIME DESTINATION bin
79+
INCLUDES DESTINATION include
80+
)
81+
82+
if(BUILD_TESTING)
83+
find_package(ament_lint_auto REQUIRED)
84+
ament_lint_auto_find_test_dependencies()
85+
86+
find_package(ament_cmake_gtest REQUIRED)
87+
find_package(std_msgs REQUIRED)
88+
89+
ament_add_gtest(wide_strings test/test_wide_strings.cpp)
90+
target_link_libraries(wide_strings dynmsg)
91+
92+
ament_add_gtest(test_vector_utils test/test_vector_utils.cpp)
93+
target_link_libraries(test_vector_utils dynmsg)
94+
95+
ament_add_gtest(test_typesupport test/test_typesupport.cpp)
96+
target_link_libraries(test_typesupport dynmsg)
97+
ament_target_dependencies(test_typesupport std_msgs)
98+
endif()
99+
100+
ament_package()

dynmsg/include/dynmsg/config.hpp.in

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright 2021 Christophe Bedard
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef DYNMSG__CONFIG_HPP_
16+
#define DYNMSG__CONFIG_HPP_
17+
18+
// If DYNMSG_VALUE_ONLY is defined, the member's value will be used directly, e.g.
19+
// frame_id: "my_frame"
20+
// instead of providing the type name as well as the value, e.g.
21+
// frame_id:
22+
// type: "string"
23+
// value: "my_frame"
24+
// This also means that, with DYNMSG_VALUE_ONLY, the resulting
25+
// YAML object can be converted back into a message.
26+
#cmakedefine DYNMSG_VALUE_ONLY
27+
28+
// [u]int8_t is handled/parsed as a char by yaml-cpp, so force to an intermediate/other type.
29+
// We convert them to string for msg->YAML and then back from string for YAML->msg.
30+
// https://github.com/jbeder/yaml-cpp/issues/201
31+
// (does not appear to be fixed in 0.6.3 or 0.7.0)
32+
#cmakedefine DYNMSG_YAML_CPP_BAD_INT8_HANDLING
33+
34+
// If DYNMSG_PARSER_DEBUG is defined, some debugging-related stuff will be printed.
35+
#cmakedefine DYNMSG_PARSER_DEBUG
36+
37+
#ifdef DYNMSG_PARSER_DEBUG
38+
# define DYNMSG_DEBUG(code) code
39+
#else
40+
# define DYNMSG_DEBUG(code) ((void) (0))
41+
#endif // DYNMSG_PARSER_DEBUG
42+
43+
#endif // DYNMSG__CONFIG_HPP_
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright 2020 Open Source Robotics Foundation, Inc.
2+
// Copyright 2021 Christophe Bedard
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
#ifndef DYNMSG__MESSAGE_READING_HPP_
17+
#define DYNMSG__MESSAGE_READING_HPP_
18+
19+
#include <yaml-cpp/yaml.h>
20+
21+
#include "dynmsg/typesupport.hpp"
22+
23+
namespace dynmsg
24+
{
25+
26+
namespace c
27+
{
28+
29+
/// Parse a ROS message stored in a raw buffer into a YAML representation.
30+
/**
31+
* The "message" argument contains both a pointer to the raw buffer and a pointer to the ROS type's
32+
* introspection information as retrieved from an introspection type support library. (See the
33+
* "get_type_info" function in typesupport_utils.hpp for loading introspection type support.)
34+
*
35+
* This function will use the provided introspection information to read the binary data out of the
36+
* ROS message-containing raw buffer and convert it into a YAML representation. The YAML
37+
* representation is a tree structure, with each node in the tree being a field in the message.
38+
* Each field is represented by two values: the ROS type of the field, in a textual representation,
39+
* and the value. For an example of the YAML structure, run the CLI tool and echo a topic; the
40+
* resulting YAML printed to the terminal is the structure used.
41+
*/
42+
YAML::Node message_to_yaml(const RosMessage & message);
43+
44+
} // namespace c
45+
46+
namespace cpp
47+
{
48+
49+
/// C++ version of dynmsg::c::message_to_yaml().
50+
/**
51+
* \see dynmsg::c::message_to_yaml()
52+
*/
53+
YAML::Node message_to_yaml(const RosMessage_Cpp & message);
54+
55+
} // namespace cpp
56+
57+
} // namespace dynmsg
58+
59+
#endif // DYNMSG__MESSAGE_READING_HPP_

dynmsg/include/dynmsg/msg_parser.hpp

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Copyright 2020 Open Source Robotics Foundation, Inc.
2+
// Copyright 2021 Christophe Bedard
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
#ifndef DYNMSG__MSG_PARSER_HPP_
17+
#define DYNMSG__MSG_PARSER_HPP_
18+
19+
#include <string>
20+
21+
#include "rcutils/allocator.h"
22+
23+
#include "typesupport.hpp"
24+
25+
namespace dynmsg
26+
{
27+
28+
namespace c
29+
{
30+
31+
/// Parse a YAML representation of a message into a ROS message and store it in a raw bytes buffer.
32+
/**
33+
* The introspection information is used to convert the YAML representation into the correct binary
34+
* representation for the given ROS message.
35+
*
36+
* It is an error for the YAML representation to contain a field that is not in the ROS message.
37+
* It is not an error for a field of the ROS message to not be specified in the YAML
38+
* representation; that field will be left uninitialised.
39+
*/
40+
RosMessage yaml_to_rosmsg(const InterfaceTypeName & interface_type, const std::string & yaml_str);
41+
42+
/// Version of yaml_to_rosmsg() with TypeInfo provided directly, and an allocator.
43+
/**
44+
* \see dynmsg::c::yaml_to_rosmsg()
45+
*/
46+
RosMessage yaml_and_typeinfo_to_rosmsg(
47+
const TypeInfo * type_info,
48+
const std::string & yaml_str,
49+
rcutils_allocator_t * allocator);
50+
51+
} // namespace c
52+
53+
namespace cpp
54+
{
55+
56+
/// C++ version of dynmsg::c::yaml_to_rosmsg().
57+
/**
58+
* \see dynmsg::c::yaml_to_rosmsg()
59+
*/
60+
RosMessage_Cpp yaml_to_rosmsg(
61+
const InterfaceTypeName & interface_type,
62+
const std::string & yaml_str);
63+
64+
/// C++ version of dynmsg::c::yaml_and_typeinfo_to_rosmsg().
65+
/**
66+
* \see dynmsg::c::yaml_and_typeinfo_to_rosmsg()
67+
*/
68+
RosMessage_Cpp yaml_and_typeinfo_to_rosmsg(
69+
const TypeInfo_Cpp * type_info,
70+
const std::string & yaml_str,
71+
rcutils_allocator_t * allocator);
72+
73+
/// Version of dynmsg::cpp::yaml_and_typeinfo_to_rosmsg() using an existing empty message.
74+
/**
75+
* Takes a pointer to an existing empty message.
76+
*
77+
* \see dynmsg::cpp::yaml_and_typeinfo_to_rosmsg()
78+
*/
79+
void yaml_and_typeinfo_to_rosmsg(
80+
const TypeInfo_Cpp * type_info,
81+
const std::string & yaml_str,
82+
void * ros_message);
83+
84+
} // namespace cpp
85+
86+
} // namespace dynmsg
87+
88+
#endif // DYNMSG__MSG_PARSER_HPP_

dynmsg_demo/include/dynmsg_demo/string_utils.hpp renamed to dynmsg/include/dynmsg/string_utils.hpp

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,18 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
#ifndef DYNMSG_DEMO_INCLUDE_DYNMSG_DEMO_STRING_UTILS_HPP_
16-
#define DYNMSG_DEMO_INCLUDE_DYNMSG_DEMO_STRING_UTILS_HPP_
15+
#ifndef DYNMSG__STRING_UTILS_HPP_
16+
#define DYNMSG__STRING_UTILS_HPP_
1717

1818
#include <string>
1919

2020
extern "C"
2121
{
22+
/// Convert a std::string (8-bit characters) to a std::u16string (16-bit characters).
23+
std::u16string string_to_u16string(const std::string & input);
2224

23-
// Convert a std::string (8-bit characters) to a std::u16string (16-bit characters)
24-
std::u16string string_to_u16string(const std::string& input);
25-
// Convert a std::u16string (16-bit characters) to a std::string (8-bit characters)
26-
std::string u16string_to_string(const std::u16string& input);
25+
/// Convert a std::u16string (16-bit characters) to a std::string (8-bit characters).
26+
std::string u16string_to_string(const std::u16string & input);
27+
} // extern "C"
2728

28-
}; // extern "C"
29-
#endif // DYNMSG_DEMO_INCLUDE_DYNMSG_DEMO_STRING_UTILS_HPP_
29+
#endif // DYNMSG__STRING_UTILS_HPP_

dynmsg/include/dynmsg/types.h

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright 2020 Open Source Robotics Foundation, Inc.
2+
// Copyright 2021 Christophe Bedard
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
#ifndef DYNMSG__TYPES_H_
17+
#define DYNMSG__TYPES_H_
18+
19+
#include <rcutils/types/rcutils_ret.h>
20+
21+
/// The type that holds a dynmsg return code.
22+
typedef rcutils_ret_t dynmsg_ret_t;
23+
24+
/// Success return code.
25+
#define DYNMSG_RET_OK RCUTILS_RET_OK
26+
/// Unspecified error return code.
27+
#define DYNMSG_RET_ERROR RCUTILS_RET_ERROR
28+
29+
#endif // DYNMSG__TYPES_H_

0 commit comments

Comments
 (0)