Skip to content

Commit 67fb810

Browse files
authored
Use parameter yaml on startup for configuring greenwave monitor + rename to greenwave diagnostics (#22)
Add support for ROS parameter YAML on startup and rename message diagnostics to greenwave diagnostics to match parameter yaml group name. Look at the example.yaml if you want to see how it is integrated. Fixes a few bugs/makes the system robust also. Specifically #16 .
1 parent 984432a commit 67fb810

18 files changed

+703
-168
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,5 +108,5 @@ You can of course also launch the node standalone, or incorporate it into your o
108108
If you want to use it as a command line tool, you can do so with the following launch file:
109109

110110
```bash
111-
ros2 launch greenwave_monitor hz.launch.py topics:='["/topic1", "/topic2"]'
111+
ros2 launch greenwave_monitor hz.launch.py gw_monitored_topics:='["/topic1", "/topic2"]'
112112
```

greenwave_monitor/CMakeLists.txt

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ endif()
2323
find_package(ament_cmake_auto REQUIRED)
2424
ament_auto_find_build_dependencies()
2525

26-
# Add message_diagnostics.hpp as a header-only library
27-
add_library(message_diagnostics INTERFACE)
28-
target_include_directories(message_diagnostics INTERFACE
26+
# Add greenwave_diagnostics.hpp as a header-only library
27+
add_library(greenwave_diagnostics INTERFACE)
28+
target_include_directories(greenwave_diagnostics INTERFACE
2929
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
3030
$<INSTALL_INTERFACE:include>)
3131

@@ -36,7 +36,7 @@ ament_target_dependencies(greenwave_monitor
3636
diagnostic_msgs
3737
greenwave_monitor_interfaces
3838
)
39-
target_link_libraries(greenwave_monitor message_diagnostics)
39+
target_link_libraries(greenwave_monitor greenwave_diagnostics)
4040

4141
target_include_directories(greenwave_monitor PUBLIC
4242
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
@@ -51,7 +51,7 @@ add_executable(minimal_publisher_node
5151
src/minimal_publisher_node.cpp
5252
src/minimal_publisher_main.cpp)
5353
ament_target_dependencies(minimal_publisher_node rclcpp std_msgs sensor_msgs diagnostic_msgs)
54-
target_link_libraries(minimal_publisher_node message_diagnostics)
54+
target_link_libraries(minimal_publisher_node greenwave_diagnostics)
5555
target_include_directories(minimal_publisher_node PUBLIC
5656
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
5757
$<INSTALL_INTERFACE:include>)
@@ -60,7 +60,7 @@ install(TARGETS minimal_publisher_node
6060
DESTINATION lib/${PROJECT_NAME})
6161

6262
install(
63-
DIRECTORY launch examples
63+
DIRECTORY launch examples config
6464
DESTINATION share/${PROJECT_NAME}
6565
)
6666

@@ -117,21 +117,21 @@ if(BUILD_TESTING)
117117
)
118118

119119
# Add gtests
120-
ament_add_gtest(test_message_diagnostics test/test_message_diagnostics.cpp
120+
ament_add_gtest(test_greenwave_diagnostics test/test_greenwave_diagnostics.cpp
121121
TIMEOUT 60
122122
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
123123
)
124-
ament_target_dependencies(test_message_diagnostics
124+
ament_target_dependencies(test_greenwave_diagnostics
125125
rclcpp
126126
std_msgs
127127
diagnostic_msgs
128128
)
129-
target_link_libraries(test_message_diagnostics message_diagnostics)
130-
target_include_directories(test_message_diagnostics PUBLIC
129+
target_link_libraries(test_greenwave_diagnostics greenwave_diagnostics)
130+
target_include_directories(test_greenwave_diagnostics PUBLIC
131131
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
132132
$<INSTALL_INTERFACE:include>
133133
)
134-
target_compile_features(test_message_diagnostics PUBLIC c_std_99 cxx_std_17)
134+
target_compile_features(test_greenwave_diagnostics PUBLIC c_std_99 cxx_std_17)
135135

136136
ament_add_gtest(test_minimal_publisher
137137
test/test_minimal_publisher.cpp
@@ -144,7 +144,7 @@ if(BUILD_TESTING)
144144
sensor_msgs
145145
diagnostic_msgs
146146
)
147-
target_link_libraries(test_minimal_publisher message_diagnostics)
147+
target_link_libraries(test_minimal_publisher greenwave_diagnostics)
148148
target_include_directories(test_minimal_publisher PUBLIC
149149
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
150150
$<INSTALL_INTERFACE:include>
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES
2+
# Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
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+
# SPDX-License-Identifier: Apache-2.0
17+
# Example configuration for greenwave_monitor
18+
# Defines topics to monitor with their expected frequencies and tolerances
19+
20+
greenwave_monitor:
21+
ros__parameters:
22+
# gw_monitored_topics parameter specifies topics to monitor that do not require expected
23+
# frequencies or tolerances.
24+
gw_monitored_topics: ['/string_topic']
25+
# gw_frequency_monitored_topics parameter specifies the topics to monitor and their
26+
# expected frequencies and tolerances.
27+
gw_frequency_monitored_topics:
28+
/imu_topic:
29+
expected_frequency: 100.0
30+
tolerance: 10.0
31+
/image_topic:
32+
# If an invalid frequency is provided (e.g. 0.0), the topic will be monitored but no
33+
# expected frequency will be set.
34+
expected_frequency: 0.0
35+
# Invalid tolerance values are clamped to 0.0.
36+
tolerance: -10.0

greenwave_monitor/examples/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Node(
2626
name='greenwave_monitor',
2727
output='screen', # or 'log' if you want to add monitoring without terminal output
2828
parameters=[
29-
{'topics': ['/your_topic_1', '/your_topic_2']} # List your topics to monitor
29+
{'gw_monitored_topics': ['/your_topic_1', '/your_topic_2']} # List your topics to monitor
3030
],
3131
),
3232
```

greenwave_monitor/examples/example.launch.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
1+
# Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
22
#
33
# Licensed under the Apache License, Version 2.0 (the "License");
44
# you may not use this file except in compliance with the License.
@@ -12,12 +12,18 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import os
16+
17+
from ament_index_python.packages import get_package_share_directory
1518
from launch import LaunchDescription
1619
from launch.actions import LogInfo
1720
from launch_ros.actions import Node
1821

1922

2023
def generate_launch_description():
24+
pkg_share = get_package_share_directory('greenwave_monitor')
25+
config_file = os.path.join(pkg_share, 'config', 'example.yaml')
26+
2127
return LaunchDescription([
2228
Node(
2329
package='greenwave_monitor',
@@ -51,9 +57,16 @@ def generate_launch_description():
5157
executable='greenwave_monitor',
5258
name='greenwave_monitor',
5359
output='log',
54-
parameters=[
55-
{'topics': ['/imu_topic', '/image_topic', '/string_topic']}
56-
],
60+
# Example of inline parameter settings
61+
# parameters=[{
62+
# 'gw_monitored_topics': ['/string_topic'],
63+
# 'gw_frequency_monitored_topics': {
64+
# '/imu_topic': {'expected_frequency': 100.0, 'tolerance': 10.0},
65+
# '/image_topic': {'expected_frequency': 0.0, 'tolerance': -10.0}
66+
# }
67+
# }],
68+
# Example of using a config file
69+
parameters=[config_file],
5770
),
5871
LogInfo(
5972
msg='Run `ros2 run r2s_gw r2s_gw` in another terminal to see the demo output '

greenwave_monitor/greenwave_monitor/ncurses_frontend.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ def curses_main(stdscr, node):
337337

338338
# Get expected frequency
339339
expected_hz, tolerance = node.ui_adaptor.get_expected_frequency(topic_name)
340-
if expected_hz > 0:
340+
if expected_hz > 0.0:
341341
expected_freq_display = f'{expected_hz:.1f}Hz'.ljust(12)
342342

343343
# Color coding based on status

greenwave_monitor/greenwave_monitor/test_utils.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
import math
2121
import time
22-
from typing import List, Optional, Tuple
22+
from typing import Any, List, Optional, Tuple
2323

2424
from diagnostic_msgs.msg import DiagnosticArray, DiagnosticStatus
2525
from greenwave_monitor_interfaces.srv import ManageTopic, SetExpectedFrequency
@@ -32,9 +32,10 @@
3232
# (message_type, expected_frequency, tolerance_hz)
3333
# NOTE: Tolerances and frequencies are set conservatively for reliable operation
3434
# on slow/loaded CI systems such as the ROS buildfarm. The 30% tolerance standard
35-
# ensures tests pass even under system load.
35+
# ensures tests pass even under system load. Low frequencies (1 Hz) use 50%
36+
# tolerance due to higher timing variability.
3637
TEST_CONFIGURATIONS = [
37-
('imu', 1.0, 0.3),
38+
('imu', 1.0, 0.6),
3839
('imu', 100.0, 30.0),
3940
('imu', 500.0, 150.0),
4041
('image', 10.0, 3.0),
@@ -65,19 +66,17 @@ def create_minimal_publisher(
6566

6667
def create_monitor_node(namespace: str = MONITOR_NODE_NAMESPACE,
6768
node_name: str = MONITOR_NODE_NAME,
68-
topics: List[str] = None):
69+
parameters: List[dict[str, Any]] = None):
6970
"""Create a greenwave_monitor node for testing."""
70-
if topics is None:
71-
topics = ['/test_topic']
71+
if parameters is None:
72+
parameters = [{'gw_monitored_topics': ['/test_topic']}]
7273

7374
return launch_ros.actions.Node(
7475
package='greenwave_monitor',
7576
executable='greenwave_monitor',
7677
name=node_name,
7778
namespace=namespace,
78-
parameters=[{
79-
'topics': topics
80-
}],
79+
parameters=parameters,
8180
output='screen'
8281
)
8382

greenwave_monitor/greenwave_monitor/ui_adaptor.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ class UiDiagnosticData:
5757
5858
"""
5959

60+
expected_frequency: str = '-'
61+
tolerance: str = '-'
6062
pub_rate: str = '-'
6163
msg_rate: str = '-'
6264
latency: str = '-'
@@ -85,6 +87,10 @@ def from_status(cls, status: DiagnosticStatus) -> 'UiDiagnosticData':
8587
data.msg_rate = kv.value
8688
elif kv.key == 'current_delay_from_realtime_ms':
8789
data.latency = kv.value
90+
elif kv.key == 'expected_frequency':
91+
data.expected_frequency = kv.value
92+
elif kv.key == 'tolerance':
93+
data.tolerance = kv.value
8894
return data
8995

9096

@@ -142,7 +148,7 @@ def _extract_topic_name(self, diagnostic_name: str) -> str:
142148
- NITROS: node_name + namespace + "/" + topic (e.g., "my_node/ns/camera/image")
143149
- Greenwave: topic_name only (e.g., "/ns/camera/image")
144150
145-
This is a temporary hack until NITROS migrates to message_diagnostics.hpp.
151+
This is a temporary hack until NITROS migrates to greenwave_diagnostics.hpp.
146152
"""
147153
# If the name starts with '/', it's already just a topic name (Greenwave format)
148154
if diagnostic_name.startswith('/'):
@@ -167,6 +173,16 @@ def _on_diagnostics(self, msg: DiagnosticArray):
167173
# Normalize the topic name to handle both NITROS and Greenwave formats
168174
topic_name = self._extract_topic_name(status.name)
169175
self.ui_diagnostics[topic_name] = ui_data
176+
try:
177+
expected_frequency = float(ui_data.expected_frequency)
178+
tolerance = float(ui_data.tolerance)
179+
if expected_frequency > 0 and tolerance >= 0:
180+
self.expected_frequencies[topic_name] = (expected_frequency, tolerance)
181+
else:
182+
self.expected_frequencies.pop(topic_name, None)
183+
except (ValueError, TypeError):
184+
# Skip updating expected_frequencies if values aren't numeric
185+
self.expected_frequencies.pop(topic_name, None)
170186

171187
def toggle_topic_monitoring(self, topic_name: str):
172188
"""Toggle monitoring for a topic."""

0 commit comments

Comments
 (0)