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
37 changes: 37 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Repository Guidelines

## Project Structure & Module Organization
This repository contains two ROS 2 packages:
- `greenwave_monitor/`: main package with C++ nodes (`src/`, `include/`), Python dashboard modules (`greenwave_monitor/greenwave_monitor/`), launch files (`launch/`), config (`config/`), examples (`examples/`), and tests (`test/`).
- `greenwave_monitor_interfaces/`: ROS 2 service definitions in `srv/` and interface build metadata.

Supporting files live at the root: `README.md`, `Contributing.md`, `.pre-commit-config.yaml`, and helper scripts in `scripts/`.

## Build, Test, and Development Commands
Use a ROS 2 workspace root (one level above this repo) for build/test commands.
- `colcon build --packages-up-to greenwave_monitor`: build monitor and required interfaces.
- `colcon test --packages-select greenwave_monitor && colcon test-result --verbose`: run and inspect tests.
- `ros2 run greenwave_monitor ncurses_dashboard --demo`: run the dashboard with demo publishers.
- `ros2 launch greenwave_monitor hz.launch.py gw_monitored_topics:='["/topic1", "/topic2"]'`: launch topic monitoring.
- `pre-commit run --all-files`: run formatting, lint, copyright, spelling, and sign-off checks locally.

## Coding Style & Naming Conventions
- C++ targets use C++17 with warnings enabled (`-Wall -Wextra -Wpedantic`).
- Format C/C++ with `uncrustify` using `ament_code_style.cfg` (wired into pre-commit).
- Python style uses `autopep8` and `flake8` (max line length 99).
- Prefer snake_case for Python modules/functions and descriptive ROS node/topic names.
- Keep headers in `include/`, implementations in `src/`, and place tests under `test/test_*.py` or `test/test_*.cpp`.

## Testing Guidelines
The package uses:
- `ament_cmake_pytest` for Python and integration tests.
- `ament_cmake_gtest` for C++ unit tests.
- `ament_lint_auto` for linting/copyright checks.

Name tests with `test_` prefix and keep scope narrow (single behavior per test file where practical). Run all package tests before opening a PR.

## Commit & Pull Request Guidelines
- Follow existing history style: imperative subject line, concise scope (example: `Fix README smoke tests in CI (#26)`).
- Sign off every commit (`git commit -s ...`); unsigned commits are not accepted (DCO requirement).
- PRs should include: purpose, key changes, test evidence (`colcon test` or equivalent), and linked issue(s) when applicable.
- Include screenshots or terminal output snippets for dashboard/UI behavior changes.
37 changes: 15 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,30 +15,13 @@ This diagram shows an overview of the architecture:

![architecture](docs/images/greenwave_diagram.png)

## Diagnostic messages

The diagnostics messages published by greenwave monitor are valid ROS 2 Diagnostics messages, however the dashboard does rely on specific keys to associate the data with the correct topic, and to find frequency and latency data.

In particular, the messages follow conventions from [Isaac ROS NITROS](https://github.com/NVIDIA-ISAAC-ROS/isaac_ros_nitros), which means configured NITROS nodes can be monitored by greenwave monitor frontends without any additional subscriber overhead. For example the drivers from [Isaac ROS NOVA](https://github.com/NVIDIA-ISAAC-ROS/isaac_ros_nova) can be monitored out of the box. Furthermore, you can set `ENABLE_GLOBAL_NITROS_DIAGNOSTICS=1` to configure all NITROS nodes to publish diagnostics (more info [here](https://nvidia-isaac-ros.github.io/repositories_and_packages/isaac_ros_nitros/isaac_ros_nitros/index.html)).

## Latency Measurements

Latency is calculated as the difference between the current system time and the timestamp in the message header. For this calculation to work correctly:

- The message type must have a `std_msgs/Header` field
- The message type must be in the recognized types list (see `has_header_from_type()` in `greenwave_monitor.cpp`)
- The header timestamp must be in epoch time (not boottime)

If any of these conditions are not met, the latency will be reported as **"N/A"** in the dashboard. This typically occurs when:
- The message type doesn't have a header (e.g., `std_msgs/String`, `geometry_msgs/Twist`)
- The message type is not recognized by greenwave monitor
- The header timestamp is in boottime format instead of epoch time

Currently, message types with headers must be manually registered in the `known_header_types` map in `greenwave_monitor.cpp`. Support for automatic detection of arbitrary message types may be added in the future. In the meantime, if you need support for a commonly used message type, please submit an issue or pull request to add it to the registry.
For implementation details and inline integration guidance, see
[`docs/DESIGN_AND_IMPLEMENTATION.md`](docs/DESIGN_AND_IMPLEMENTATION.md).

## Compatibility

Greenwave monitor is a standalone package tested on Humble, Iron, Jazzy, Kilted, and Rolling ROS 2 releases, under Ubuntu 22.04 and Ubuntu 24.04. It does not depend on Isaac ROS.
Greenwave monitor is a standalone package tested on Humble, Iron, Jazzy, Kilted, and Rolling ROS 2 releases, under Ubuntu 22.04 and Ubuntu 24.04. It does not depend on Isaac ROS. It does however play nicely with Isaac ROS NITROS diagnostics, see [`docs/DESIGN_AND_IMPLEMENTATION.md`](docs/DESIGN_AND_IMPLEMENTATION.md) for more.

## Installation

Expand Down Expand Up @@ -73,7 +56,7 @@ ros2 run greenwave_monitor ncurses_dashboard --demo

For users who want an advanced, feature-rich terminal interface, **r2s_gw** is available as a separate package. Built on the excellent [r2s](https://github.com/mjcarroll/r2s) TUI framework and powered by [Textual](https://github.com/textualize/textual/), r2s_gw provides a beautiful, modern interface with enhanced navigation and visualization capabilities.

**r2s_gw** is perfect for interactive development and debugging sessions. For production deployments with many topics or minimal dependency requirements, the lightweight ncurses dashboard above is recommended.
**r2s_gw** is perfect for interactive development and debugging sessions. For deployments with many topics or minimal dependency requirements, the lightweight ncurses dashboard above is recommended.

To use r2s_gw:

Expand Down Expand Up @@ -104,9 +87,19 @@ ros2 run r2s_gw r2s_gw_dashboard -- --demo

### Manual Launch (ros2 topic hz mode)

You can of course also launch the node standalone, or incorporate it into your own launch files.
You can also launch the node standalone, or incorporate it into your own launch files.
If you want to use it as a command line tool, you can do so with the following launch file:

```bash
ros2 launch greenwave_monitor hz.launch.py gw_monitored_topics:='["/topic1", "/topic2"]'
```

### Inline Integration Example Node

For a minimal C++ reference implementation that publishes both data and greenwave diagnostics:

```bash
ros2 run greenwave_monitor example_greenwave_publisher_node
```

`minimal_publisher_node` is still available as a multi-message-type demo publisher.
220 changes: 220 additions & 0 deletions docs/DESIGN_AND_IMPLEMENTATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
# Greenwave Monitor Design And Implementation Guide

This page explains how greenwave monitor is built, and how to inline
`greenwave_diagnostics` directly into your own ROS 2 node.

## Design Goals

- Provide low-overhead runtime topic diagnostics (rate, latency, jitter signals).
- Support both deployment styles:
- centralized monitor node (`greenwave_monitor`)
- inline diagnostics in producer/consumer nodes (`greenwave_diagnostics.hpp`)
- Publish standard `diagnostic_msgs/DiagnosticArray` on `/diagnostics` so multiple frontends can consume it.
- Provide a batteries included terminal based frontend for developers.

## Overall Design

![architecture](images/greenwave_diagram.png)

### 1) Greenwave Diagnostic messages

The diagnostics messages published by greenwave monitor are standard ROS 2 Diagnostics messages (`diagnostic_msgs/DiagnosticArray`), however the dashboard relies on specific keys to associate the data with the correct topic, and to find frequency and latency data. We provide a header only library that creates these greenwave compatible diagnostics messages: `greenwave_monitor/include/greenwave_diagnostics.hpp`

`GreenwaveDiagnostics` tracks:

- node-time interarrival rate (`frame_rate_node`)
- message-time interarrival rate (`frame_rate_msg`) # Using the message's header timestamp
- current delay from realtime (`current_delay_from_realtime_ms`)
- jitter/outlier counters and summary stats
- status transitions (`OK`, `ERROR`, `STALE`) for missed timing expectations

In particular, the messages follow conventions from [Isaac ROS NITROS](https://github.com/NVIDIA-ISAAC-ROS/isaac_ros_nitros), which means configured NITROS nodes can be monitored by greenwave monitor frontends without any additional subscriber overhead. For example the drivers from [Isaac ROS NOVA](https://github.com/NVIDIA-ISAAC-ROS/isaac_ros_nova) can be monitored out of the box. Furthermore, you can set `ENABLE_GLOBAL_NITROS_DIAGNOSTICS=1` to configure all NITROS nodes to publish diagnostics (more info [here](https://nvidia-isaac-ros.github.io/repositories_and_packages/isaac_ros_nitros/isaac_ros_nitros/index.html)).


### 2) Central monitor node (`greenwave_monitor`)

We provide a single node which can subscribe to arbitrary topics and produce greenwave diagnostics for them. You can think of this like a more efficient, more featureful version of ros2 topic hz. Implementation: `greenwave_monitor/src/greenwave_monitor.cpp`

- Starts a generic subscription per monitored topic.
- Reads topic list from:
- `gw_monitored_topics`
- `gw_frequency_monitored_topics.<topic>.expected_frequency`
- `gw_frequency_monitored_topics.<topic>.tolerance`
- Resolves topic type dynamically and uses serialized subscriptions.
- Extracts message timestamps for known header-bearing types.
- Publishes diagnostics once per second in `timer_callback()`.

Provides runtime services:

- `/greenwave_monitor/manage_topic` (`ManageTopic.srv`)
- `/greenwave_monitor/set_expected_frequency` (`SetExpectedFrequency.srv`)

### 3) UI adapters

- `greenwave_monitor/greenwave_monitor/ui_adaptor.py` subscribes to `/diagnostics`.
- Maintains a table of topic->`DiagnosticStatus`
- Frontends render from a single dictionary maintained by the UIadaptor, rather than a stream of diagnostics messages.

## Inline Integration In Your Own Node

The Greenwave Monitor Node is very convenient, and for most use cases it is sufficient. However sometimes the overhead from an extra subscriber is not desirable, or one wants to measure diagnostics directly from the publisher/subscriber. This section details how to integrate greenwave diagnostics inline. Reference pattern: `greenwave_monitor/src/example_greenwave_publisher_node.cpp`

### Step 1: Add the include and member

```cpp
#include "greenwave_diagnostics.hpp"

std::unique_ptr<greenwave_diagnostics::GreenwaveDiagnostics> gw_diag_;
```

### Step 2: Construct diagnostics in your node constructor

```cpp
greenwave_diagnostics::GreenwaveDiagnosticsConfig config;
config.enable_all_topic_diagnostics = true;

gw_diag_ = std::make_unique<greenwave_diagnostics::GreenwaveDiagnostics>(
*this, "/my_topic", config);
```

Optional expected-rate checks (enables jitter/deadline logic):

```cpp
gw_diag_->setExpectedDt(/*expected_hz=*/30.0, /*tolerance_percent=*/10.0);
```

### Step 3: Update diagnostics per message event

Call `updateDiagnostics(msg_timestamp_ns)` whenever you publish or consume.

```cpp
const auto stamp_ns = msg.header.stamp.sec * 1000000000ULL + msg.header.stamp.nanosec;
gw_diag_->updateDiagnostics(stamp_ns);
```

If your message has no header, pass node time:

```cpp
gw_diag_->updateDiagnostics(this->now().nanoseconds());
```

### Step 4: Publish diagnostics periodically

`updateDiagnostics()` does not publish by itself.
Call `publishDiagnostics()` from a timer (for example, 1 Hz):

```cpp
diag_timer_ = this->create_wall_timer(
std::chrono::seconds(1),
[this]() { gw_diag_->publishDiagnostics(); });
```

## Diagnostics Contract (Important For UI Compatibility)

Dashboards expect specific keys inside `DiagnosticStatus.values`, including:

- `frame_rate_node`
- `frame_rate_msg`
- `current_delay_from_realtime_ms`
- `expected_frequency`
- `tolerance`

These are already emitted by `GreenwaveDiagnostics::publishDiagnostics()`. You can write your own publisher for greenwave compatible `/diagnostics`, but we don't guarantee schema stability for now.

## Implementation Notes And Pitfalls

- Message timestamp should be epoch time for latency to be meaningful.
- The central monitor only parses headers for types listed in
`GreenwaveMonitor::has_header_from_type()`; unknown types fall back to no-header behavior.
- `publishDiagnostics()` marks status as `STALE` if no fresh `updateDiagnostics()` happened since the previous publish.
- `setExpectedDt()` requires `expected_hz > 0`; zero disables useful timing checks.

## Where To Look In Code

- Core monitor node: `greenwave_monitor/src/greenwave_monitor.cpp`
- Diagnostics API: `greenwave_monitor/include/greenwave_diagnostics.hpp`
- Inline integration example node: `greenwave_monitor/src/example_greenwave_publisher_node.cpp`
- Demo traffic generator node: `greenwave_monitor/src/minimal_publisher_node.cpp`
- Service definitions:
- `greenwave_monitor_interfaces/srv/ManageTopic.srv`
- `greenwave_monitor_interfaces/srv/SetExpectedFrequency.srv`

## Service API and CLI Usage

The monitor exposes two runtime services for dynamic configuration:

#### `ManageTopic`

Service type: `greenwave_monitor_interfaces/srv/ManageTopic`

Request fields:

- `topic_name` (string)
- `add_topic` (bool, `true` = add, `false` = remove)

Response fields:

- `success` (bool)
- `message` (string)

Examples:

```bash
# Add a topic
ros2 service call /greenwave_monitor/manage_topic \
greenwave_monitor_interfaces/srv/ManageTopic \
"{topic_name: '/topic2', add_topic: true}"

# Remove a topic
ros2 service call /greenwave_monitor/manage_topic \
greenwave_monitor_interfaces/srv/ManageTopic \
"{topic_name: '/topic2', add_topic: false}"
```

#### `SetExpectedFrequency`

Service type: `greenwave_monitor_interfaces/srv/SetExpectedFrequency`

Request fields:

- `topic_name` (string)
- `expected_hz` (float64)
- `tolerance_percent` (float64, e.g. `5.0` = 5%)
- `clear_expected` (bool)
- `add_topic_if_missing` (bool)

Response fields:

- `success` (bool)
- `message` (string)

Examples:

```bash
# Set expected frequency/tolerance
ros2 service call /greenwave_monitor/set_expected_frequency \
greenwave_monitor_interfaces/srv/SetExpectedFrequency \
"{topic_name: '/topic2', expected_hz: 30.0, tolerance_percent: 10.0, add_topic_if_missing: true}"

# Clear expected frequency
ros2 service call /greenwave_monitor/set_expected_frequency \
greenwave_monitor_interfaces/srv/SetExpectedFrequency \
"{topic_name: '/topic2', clear_expected: true}"
```

Note: topic names should include the leading slash (`/topic2`).

## Latency Measurements

Latency is calculated as the difference between the current system time and the timestamp in the message header. For this calculation to work correctly:

- The message type must have a `std_msgs/Header` field
- The message type must be in the recognized types list (see `has_header_from_type()` in `greenwave_monitor.cpp`)
- The header timestamp must be in epoch time (not boottime)

If any of these conditions are not met, the latency will be reported as **"N/A"** in the dashboard. This typically occurs when:
- The message type doesn't have a header (e.g., `std_msgs/String`, `geometry_msgs/Twist`)
- The message type is not recognized by greenwave monitor
- The header timestamp is in boottime format instead of epoch time

Currently, message types with headers must be manually registered in the `known_header_types` map in `greenwave_monitor.cpp`. Support for automatic detection of arbitrary message types may be added in the future. In the meantime, if you need support for a commonly used message type, please submit an issue or pull request to add it to the registry.
37 changes: 0 additions & 37 deletions docs/images/SERVICES.md

This file was deleted.

Loading