Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 5 additions & 14 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ jobs:

- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive

- name: Set up ROS 2 Jazzy
uses: ros-tooling/setup-ros@v0.7
Expand All @@ -45,17 +43,15 @@ jobs:
source /opt/ros/jazzy/setup.bash
colcon build --symlink-install \
--cmake-args -DCMAKE_BUILD_TYPE=Release \
--event-handlers console_direct+ \
--packages-ignore test_dynmsg dynmsg_demo
--event-handlers console_direct+

- name: Run linters (clang-format, clang-tidy, etc.)
run: |
source /opt/ros/jazzy/setup.bash
source install/setup.bash
colcon test --return-code-on-test-failure \
--ctest-args -L linter \
--event-handlers console_direct+ \
--packages-ignore test_dynmsg dynmsg_demo
--event-handlers console_direct+

- name: Run unit and integration tests
timeout-minutes: 15
Expand All @@ -64,8 +60,7 @@ jobs:
source install/setup.bash
colcon test --return-code-on-test-failure \
--ctest-args -LE linter \
--event-handlers console_direct+ \
--packages-ignore test_dynmsg dynmsg_demo
--event-handlers console_direct+

- name: Show test results
if: always()
Expand Down Expand Up @@ -97,8 +92,6 @@ jobs:

- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive

- name: Set up ROS 2 Jazzy
uses: ros-tooling/setup-ros@v0.7
Expand All @@ -118,17 +111,15 @@ jobs:
source /opt/ros/jazzy/setup.bash
colcon build --symlink-install \
--cmake-args -DCMAKE_BUILD_TYPE=Debug -DENABLE_COVERAGE=ON \
--event-handlers console_direct+ \
--packages-ignore test_dynmsg dynmsg_demo
--event-handlers console_direct+

- name: Run unit and integration tests for coverage
run: |
source /opt/ros/jazzy/setup.bash
source install/setup.bash
colcon test \
--ctest-args -LE linter \
--event-handlers console_direct+ \
--packages-ignore test_dynmsg dynmsg_demo
--event-handlers console_direct+

- name: Generate coverage report
run: |
Expand Down
3 changes: 0 additions & 3 deletions .gitmodules

This file was deleted.

1 change: 0 additions & 1 deletion src/dynamic_message_introspection
Submodule dynamic_message_introspection deleted from b38e36
21 changes: 9 additions & 12 deletions src/ros2_medkit_gateway/src/operation_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <sstream>

#include "ros2_medkit_serialization/json_serializer.hpp"
#include "ros2_medkit_serialization/message_cleanup.hpp"
#include "ros2_medkit_serialization/serialization_error.hpp"
#include "ros2_medkit_serialization/service_action_types.hpp"
#include "ros2_medkit_serialization/type_cache.hpp"
Expand Down Expand Up @@ -142,7 +143,6 @@ ServiceCallResult OperationManager::call_service(const std::string & service_pat

// Convert JSON to ROS message (deserialized form, not CDR)
// Note: GenericClient expects void* pointing to deserialized message structure
rcutils_allocator_t allocator = rcutils_get_default_allocator();
RosMessage_Cpp ros_request = serializer_->from_json(request_type, request_data);

RCLCPP_INFO(node_->get_logger(), "Calling service: %s (type: %s)", service_path.c_str(), service_type.c_str());
Expand All @@ -157,15 +157,15 @@ ServiceCallResult OperationManager::call_service(const std::string & service_pat
if (future_status != std::future_status::ready) {
// Clean up pending request on timeout
client->remove_pending_request(future_and_id.request_id);
dynmsg::cpp::ros_message_destroy_with_allocator(&ros_request, &allocator);
ros2_medkit_serialization::destroy_ros_message(&ros_request);
result.success = false;
result.error_message =
"Service call timed out (" + std::to_string(service_call_timeout_sec_) + "s): " + service_path;
return result;
}

// Clean up request message after sending
dynmsg::cpp::ros_message_destroy_with_allocator(&ros_request, &allocator);
ros2_medkit_serialization::destroy_ros_message(&ros_request);

// Step 7: Get response and deserialize
auto response_ptr = future_and_id.get();
Expand Down Expand Up @@ -412,7 +412,6 @@ ActionSendGoalResult OperationManager::send_action_goal(const std::string & acti

// Convert JSON to ROS message (deserialized form, not CDR)
// Note: GenericClient expects void* pointing to deserialized message structure
rcutils_allocator_t allocator = rcutils_get_default_allocator();
RosMessage_Cpp ros_request = serializer_->from_json(request_type, send_goal_request);

RCLCPP_INFO(node_->get_logger(), "Sending action goal: %s (type: %s)", action_path.c_str(), action_type.c_str());
Expand All @@ -426,13 +425,13 @@ ActionSendGoalResult OperationManager::send_action_goal(const std::string & acti

if (future_status != std::future_status::ready) {
clients.send_goal_client->remove_pending_request(future_and_id.request_id);
dynmsg::cpp::ros_message_destroy_with_allocator(&ros_request, &allocator);
ros2_medkit_serialization::destroy_ros_message(&ros_request);
result.error_message = "Send goal timed out";
return result;
}

// Clean up request message after sending
dynmsg::cpp::ros_message_destroy_with_allocator(&ros_request, &allocator);
ros2_medkit_serialization::destroy_ros_message(&ros_request);

// Step 6: Deserialize response
auto response_ptr = future_and_id.get();
Expand Down Expand Up @@ -551,7 +550,6 @@ ActionCancelResult OperationManager::cancel_action_goal(const std::string & acti

// Convert JSON to ROS message (deserialized form, not CDR)
// Note: GenericClient expects void* pointing to deserialized message structure
rcutils_allocator_t allocator = rcutils_get_default_allocator();
RosMessage_Cpp ros_request = serializer_->from_json("action_msgs/srv/CancelGoal_Request", cancel_request);

RCLCPP_INFO(node_->get_logger(), "Canceling action goal: %s (goal_id: %s)", action_path.c_str(), goal_id.c_str());
Expand All @@ -562,11 +560,11 @@ ActionCancelResult OperationManager::cancel_action_goal(const std::string & acti
auto future_status = future_and_id.wait_for(std::chrono::seconds(5));
if (future_status != std::future_status::ready) {
clients.cancel_goal_client->remove_pending_request(future_and_id.request_id);
dynmsg::cpp::ros_message_destroy_with_allocator(&ros_request, &allocator);
ros2_medkit_serialization::destroy_ros_message(&ros_request);
result.error_message = "Cancel request timed out";
return result;
}
dynmsg::cpp::ros_message_destroy_with_allocator(&ros_request, &allocator);
ros2_medkit_serialization::destroy_ros_message(&ros_request);

auto response_ptr = future_and_id.get();
if (response_ptr == nullptr) {
Expand Down Expand Up @@ -646,7 +644,6 @@ ActionGetResultResult OperationManager::get_action_result(const std::string & ac

// Convert JSON to ROS message (deserialized form, not CDR)
// Note: GenericClient expects void* pointing to deserialized message structure
rcutils_allocator_t allocator = rcutils_get_default_allocator();
RosMessage_Cpp ros_request = serializer_->from_json(request_type, get_result_request);

RCLCPP_INFO(node_->get_logger(), "Getting action result: %s (goal_id: %s)", action_path.c_str(), goal_id.c_str());
Expand All @@ -657,11 +654,11 @@ ActionGetResultResult OperationManager::get_action_result(const std::string & ac
auto future_status = future_and_id.wait_for(std::chrono::seconds(service_call_timeout_sec_));
if (future_status != std::future_status::ready) {
clients.get_result_client->remove_pending_request(future_and_id.request_id);
dynmsg::cpp::ros_message_destroy_with_allocator(&ros_request, &allocator);
ros2_medkit_serialization::destroy_ros_message(&ros_request);
result.error_message = "Get result timed out";
return result;
}
dynmsg::cpp::ros_message_destroy_with_allocator(&ros_request, &allocator);
ros2_medkit_serialization::destroy_ros_message(&ros_request);

auto response_ptr = future_and_id.get();
if (response_ptr == nullptr) {
Expand Down
56 changes: 42 additions & 14 deletions src/ros2_medkit_serialization/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ endif()
# Find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(dynmsg REQUIRED)
find_package(rcutils REQUIRED)
find_package(rosidl_runtime_c REQUIRED)
find_package(rosidl_typesupport_introspection_c REQUIRED)
find_package(rosidl_typesupport_introspection_cpp REQUIRED)
find_package(rosidl_runtime_cpp REQUIRED)
find_package(rcpputils REQUIRED)
Expand All @@ -33,6 +35,14 @@ add_library(${PROJECT_NAME}
src/json_serializer.cpp
src/type_cache.cpp
src/service_action_types.cpp
src/message_cleanup.cpp
# Vendored dynmsg sources (C++ API only)
src/vendored/dynmsg/typesupport.cpp
src/vendored/dynmsg/message_reading_cpp.cpp
src/vendored/dynmsg/msg_parser_cpp.cpp
src/vendored/dynmsg/string_utils.cpp
src/vendored/dynmsg/vector_utils.cpp
src/vendored/dynmsg/yaml_utils.cpp
)

target_include_directories(${PROJECT_NAME} PUBLIC
Expand All @@ -42,7 +52,9 @@ target_include_directories(${PROJECT_NAME} PUBLIC

ament_target_dependencies(${PROJECT_NAME}
rclcpp
dynmsg
rcutils
rosidl_runtime_c
rosidl_typesupport_introspection_c
rosidl_typesupport_introspection_cpp
rosidl_runtime_cpp
rcpputils
Expand All @@ -64,7 +76,9 @@ endif()
ament_export_targets(${PROJECT_NAME}Targets HAS_LIBRARY_TARGET)
ament_export_dependencies(
rclcpp
dynmsg
rcutils
rosidl_runtime_c
rosidl_typesupport_introspection_c
rosidl_typesupport_introspection_cpp
rosidl_runtime_cpp
rcpputils
Expand Down Expand Up @@ -94,26 +108,40 @@ if(BUILD_TESTING)
find_package(sensor_msgs REQUIRED)
find_package(test_msgs REQUIRED)

# Use custom clang-format and clang-tidy configs from repo root
set(ament_cmake_clang_format_CONFIG_FILE "${CMAKE_CURRENT_SOURCE_DIR}/../../.clang-format")
set(ament_cmake_clang_tidy_CONFIG_FILE "${CMAKE_CURRENT_SOURCE_DIR}/../../.clang-tidy")

# Limit clang-tidy to only report issues from our source files
set(ament_cmake_clang_tidy_HEADER_FILTER "^${CMAKE_CURRENT_SOURCE_DIR}/(include|src|test)/")

# Exclude linters that conflict with clang-format
# Exclude conflicting linters and those we configure manually
list(APPEND AMENT_LINT_AUTO_EXCLUDE
ament_cmake_uncrustify
ament_cmake_cpplint
ament_cmake_clang_tidy
ament_cmake_clang_format
)
ament_lint_auto_find_test_dependencies()

# Configure clang-tidy manually with increased timeout
# Configure clang-format manually for non-vendored files only
find_package(ament_cmake_clang_format REQUIRED)
set(_clang_format_config "${CMAKE_CURRENT_SOURCE_DIR}/../../.clang-format")
ament_clang_format(
CONFIG_FILE "${_clang_format_config}"
"include/ros2_medkit_serialization/json_serializer.hpp"
"include/ros2_medkit_serialization/message_cleanup.hpp"
"include/ros2_medkit_serialization/serialization_error.hpp"
"include/ros2_medkit_serialization/service_action_types.hpp"
"include/ros2_medkit_serialization/type_cache.hpp"
"src/json_serializer.cpp"
"src/message_cleanup.cpp"
"src/service_action_types.cpp"
"src/type_cache.cpp"
"test/test_json_serializer.cpp"
"test/test_service_action_types.cpp"
"test/test_type_cache.cpp"
)

# Configure clang-tidy manually for non-vendored files only
set(_clang_tidy_config "${CMAKE_CURRENT_SOURCE_DIR}/../../.clang-tidy")
ament_clang_tidy(
"${CMAKE_CURRENT_BINARY_DIR}"
CONFIG_FILE "${ament_cmake_clang_tidy_CONFIG_FILE}"
HEADER_FILTER "${ament_cmake_clang_tidy_HEADER_FILTER}"
CONFIG_FILE "${_clang_tidy_config}"
HEADER_FILTER "^${CMAKE_CURRENT_SOURCE_DIR}/include/ros2_medkit_serialization/[^v].*"
TIMEOUT 300
)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright 2026 Selfpatch
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef ROS2_MEDKIT_SERIALIZATION__MESSAGE_CLEANUP_HPP_
#define ROS2_MEDKIT_SERIALIZATION__MESSAGE_CLEANUP_HPP_

#include "ros2_medkit_serialization/vendored/dynmsg/typesupport.hpp"

namespace ros2_medkit_serialization {

/// Destroy a dynamically allocated ROS message created by JsonSerializer::from_json()
///
/// This function properly cleans up messages allocated via the serialization library.
/// It calls the message's finalization function and deallocates the memory.
///
/// @param ros_msg Pointer to the RosMessage_Cpp structure to destroy.
/// If nullptr or if ros_msg->data is nullptr, this is a no-op.
void destroy_ros_message(RosMessage_Cpp * ros_msg);

} // namespace ros2_medkit_serialization

#endif // ROS2_MEDKIT_SERIALIZATION__MESSAGE_CLEANUP_HPP_
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
#include <unordered_map>
#include <utility>

#include "dynmsg/typesupport.hpp"
#include "ros2_medkit_serialization/vendored/dynmsg/typesupport.hpp"

namespace ros2_medkit_serialization {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright 2021 Christophe Bedard
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef ROS2_MEDKIT_SERIALIZATION__VENDORED__DYNMSG__CONFIG_HPP_
#define ROS2_MEDKIT_SERIALIZATION__VENDORED__DYNMSG__CONFIG_HPP_

// Vendored from dynmsg - configuration hardcoded for ros2_medkit_serialization

// If DYNMSG_VALUE_ONLY is defined, the member's value will be used directly, e.g.
// frame_id: "my_frame"
// instead of providing the type name as well as the value, e.g.
// frame_id:
// type: "string"
// value: "my_frame"
// This also means that, with DYNMSG_VALUE_ONLY, the resulting
// YAML object can be converted back into a message.
#define DYNMSG_VALUE_ONLY

// [u]int8_t is handled/parsed as a char by yaml-cpp, so force to an intermediate/other type.
// We convert them to string for msg->YAML and then back from string for YAML->msg.
// https://github.com/jbeder/yaml-cpp/issues/201
// (does not appear to be fixed in 0.6.3 or 0.7.0)
#define DYNMSG_YAML_CPP_BAD_INT8_HANDLING

// If DYNMSG_PARSER_DEBUG is defined, some debugging-related stuff will be printed.
// #define DYNMSG_PARSER_DEBUG

#ifdef DYNMSG_PARSER_DEBUG
# define DYNMSG_DEBUG(code) code
#else
# define DYNMSG_DEBUG(code) ((void) (0))
#endif // DYNMSG_PARSER_DEBUG

#endif // ROS2_MEDKIT_SERIALIZATION__VENDORED__DYNMSG__CONFIG_HPP_
Loading