Skip to content
Open
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
9 changes: 7 additions & 2 deletions greenwave_monitor/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ endif()

# find dependencies
find_package(ament_cmake_auto REQUIRED)
find_package(yaml-cpp REQUIRED)
ament_auto_find_build_dependencies()

# Add message_diagnostics.hpp as a header-only library
Expand All @@ -35,8 +36,12 @@ ament_target_dependencies(greenwave_monitor
std_msgs
diagnostic_msgs
greenwave_monitor_interfaces
yaml_cpp_vendor
)
target_link_libraries(greenwave_monitor
message_diagnostics
yaml-cpp
)
target_link_libraries(greenwave_monitor message_diagnostics)

target_include_directories(greenwave_monitor PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
Expand All @@ -60,7 +65,7 @@ install(TARGETS minimal_publisher_node
DESTINATION lib/${PROJECT_NAME})

install(
DIRECTORY launch examples
DIRECTORY launch examples config
DESTINATION share/${PROJECT_NAME}
)

Expand Down
2 changes: 2 additions & 0 deletions greenwave_monitor/config/type_registry.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
has_header:
- stereo_msgs/msg/DisparityImage
Copy link
Collaborator

@sgillen sgillen Oct 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is good but actually I'd prefer to have DisparityImage in the default map. Do you want to add that to this PR? else I can do that as a separate PR.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure thing! I only had this here as an example and for my local testing. How about PoseWithCovarianceStamped since you have PoseStamped already? In my experience at least, it is very common among both opensource and commerical SLAM systems.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both sound great, please add any common types you use to the default known_header_types.

3 changes: 3 additions & 0 deletions greenwave_monitor/include/greenwave_monitor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,14 @@ class GreenwaveMonitor : public rclcpp::Node

bool has_header_from_type(const std::string & type_name);

bool has_header_from_type_registry(const std::string & type_name);

std::chrono::time_point<std::chrono::system_clock>
GetTimestampFromSerializedMessage(
std::shared_ptr<rclcpp::SerializedMessage> serialized_message_ptr,
const std::string & type);

std::string type_registry_path_;
std::map<std::string,
std::unique_ptr<message_diagnostics::MessageDiagnostics>> message_diagnostics_;
std::vector<std::shared_ptr<rclcpp::GenericSubscription>> subscriptions_;
Expand Down
1 change: 1 addition & 0 deletions greenwave_monitor/package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
<depend>sensor_msgs</depend>
<depend>greenwave_monitor_interfaces</depend>
<depend>rclpy</depend>
<depend>yaml_cpp_vendor</depend>

<exec_depend>launch</exec_depend>
<exec_depend>launch_ros</exec_depend>
Expand Down
78 changes: 76 additions & 2 deletions greenwave_monitor/src/greenwave_monitor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

#include "greenwave_monitor.hpp"

#include <sys/stat.h>
#include <yaml-cpp/yaml.h>
#include <algorithm>
#include <cstring>
#include <mutex>
Expand All @@ -26,6 +28,19 @@

using namespace std::chrono_literals;

namespace
{

// Check if a file exists and is a regular file
bool is_valid_file(const std::string & path)
{
struct stat st;
if (stat(path.c_str(), &st) != 0) {return false;}
return static_cast<bool>(st.st_mode & S_IFREG);
}

} // anonymous namespace

GreenwaveMonitor::GreenwaveMonitor(const rclcpp::NodeOptions & options)
: Node("greenwave_monitor", options)
{
Expand All @@ -35,6 +50,19 @@ GreenwaveMonitor::GreenwaveMonitor(const rclcpp::NodeOptions & options)
this->declare_parameter<std::vector<std::string>>("topics", {""});
auto topics = this->get_parameter("topics").as_string_array();

// Declare and get the type registry path parameter
this->declare_parameter<std::string>("type_registry_path", "");
type_registry_path_ = this->get_parameter("type_registry_path").as_string();

// Check if the type registry path is valid
if (!type_registry_path_.empty() && !is_valid_file(type_registry_path_)) {
RCLCPP_WARN(
this->get_logger(), "Type registry path '%s' is not a valid file."
"Falling back to build-in type registry.",
type_registry_path_.c_str());
type_registry_path_.clear();
}

message_diagnostics::MessageDiagnosticsConfig diagnostics_config;
diagnostics_config.enable_all_topic_diagnostics = true;

Expand Down Expand Up @@ -241,19 +269,65 @@ bool GreenwaveMonitor::has_header_from_type(const std::string & type_name)
auto it = known_header_types.find(type_name);
bool has_header = (it != known_header_types.end()) ? it->second : false;

type_has_header_cache[type_name] = has_header;
// In case the type is not in the known list, attempt to read from type registry
if (it == known_header_types.end() && !type_registry_path_.empty()) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do you feel about using type_registry to update known_header_types and leaving the logic the same?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what I initially had implemented, but this has the advantage of being able to add types to the yaml file while it is running. For instance, in case I had an object detection pipeline runnging (v4l2 -> Encoder -> TensorRT -> Decoder) and notice that the detections are coming at 15 Hz while the camera publishes at 30 Hz. It would be practical to be able to add isaac_ros_tensor_list_interfaces/msg/TensorList to the registry while the system is still running and monitoring the Encoder and TensorRT outputs to see where the slow down is happening.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer to keep this as simple as possible load the YAML file once during initialization and merge it directly into known_header_types. If the user needs to add new types just restart the node.

has_header = has_header_from_type_registry(type_name);
}

// Fallback of no header in case of unknown type, log for reference
if (it == known_header_types.end()) {
if (it == known_header_types.end() && !has_header) {
RCLCPP_WARN_ONCE(
this->get_logger(),
"Unknown message type '%s' - assuming no header. Consider adding to registry.",
type_name.c_str());
}

// Cache the result for future lookups
type_has_header_cache[type_name] = has_header;

return has_header;
}

bool GreenwaveMonitor::has_header_from_type_registry(const std::string & type_name)
{
try {
YAML::Node config = YAML::LoadFile(type_registry_path_);
if (config["has_header"]) {
// Check if 'has_header' is a sequence (list)
if (config["has_header"].IsSequence()) {
for (const auto & type_node : config["has_header"]) {
if (type_node.IsScalar()) {
// Check if the type matches
if (type_node.as<std::string>() == type_name) {
return true;
}
} else {
RCLCPP_WARN(
this->get_logger(),
"Found a non-string value in the registry: %s. Skipping.",
type_node.as<std::string>().c_str());
}
Comment on lines +305 to +309
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Attempting to convert a non-scalar node to string at line 308 will throw an exception. Remove the .as<std::string>() call or wrap in try-catch.

Suggested change
RCLCPP_WARN(
this->get_logger(),
"Found a non-string value in the registry: %s. Skipping.",
type_node.as<std::string>().c_str());
}
} else {
RCLCPP_WARN(
this->get_logger(),
"Found a non-string value in the registry. Skipping.");
}

}
} else {
RCLCPP_ERROR(
this->get_logger(),
"'has_header' key is not a sequence in the YAML file.");
}
} else {
RCLCPP_ERROR(
this->get_logger(),
"'has_header' key is not found in the YAML file.");
}
} catch (const YAML::BadFile & e) {
RCLCPP_ERROR(this->get_logger(), "Error reading YAML file: %s", e.what());
} catch (const YAML::ParserException & e) {
RCLCPP_ERROR(this->get_logger(), "Error parsing YAML string: %s", e.what());
} catch (const std::exception & e) {
RCLCPP_ERROR(this->get_logger(), "An unexpected error occurred: %s", e.what());
}
return false;
}

bool GreenwaveMonitor::add_topic(const std::string & topic, std::string & message)
{
// Check if topic already exists
Expand Down