From 006bbf595df85ccf62b24efd6654ce37e01bdfce Mon Sep 17 00:00:00 2001 From: Khuzaymah Bin Haris Date: Sat, 6 Jun 2026 13:45:46 -0600 Subject: [PATCH 01/19] Add normalized diagnostics contract for ground station UI. Define DiagnosticMessage and shared status helpers so the UI can consume mock or future ROS 2 data through one interface. Co-authored-by: Cursor --- ground_station_monitoring_ui/src/__init__.py | 1 + .../src/diagnostics_contract.py | 48 +++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 ground_station_monitoring_ui/src/__init__.py create mode 100644 ground_station_monitoring_ui/src/diagnostics_contract.py diff --git a/ground_station_monitoring_ui/src/__init__.py b/ground_station_monitoring_ui/src/__init__.py new file mode 100644 index 0000000..5ad0570 --- /dev/null +++ b/ground_station_monitoring_ui/src/__init__.py @@ -0,0 +1 @@ +"""WayBionic ground station monitoring prototype package.""" diff --git a/ground_station_monitoring_ui/src/diagnostics_contract.py b/ground_station_monitoring_ui/src/diagnostics_contract.py new file mode 100644 index 0000000..37d9f5b --- /dev/null +++ b/ground_station_monitoring_ui/src/diagnostics_contract.py @@ -0,0 +1,48 @@ +"""Normalized diagnostics contract used by the monitoring UI. + +The UI consumes these objects only. A future ROS 2 subscriber should convert +live diagnostics into this shape before handing them to the dashboard. +""" + +from __future__ import annotations + +from dataclasses import dataclass +from datetime import datetime, timedelta, timezone +from typing import Literal + + +DiagnosticStatus = Literal["OK", "WARN", "FAULT", "STALE"] + + +@dataclass(frozen=True) +class DiagnosticMessage: + signal_name: str + status: DiagnosticStatus + timestamp: str + value: float | str | None + unit: str | None + alert_message: str | None = None + + +def utc_timestamp(seconds_ago: float = 0.0) -> str: + """Return an ISO-8601 UTC timestamp offset from the current time.""" + now = datetime.now(timezone.utc) + if seconds_ago: + now = now - timedelta(seconds=seconds_ago) + return now.isoformat() + + +def seconds_since(timestamp: str) -> float: + """Return age in seconds for an ISO-8601 timestamp.""" + try: + parsed = datetime.fromisoformat(timestamp) + except ValueError: + return 0.0 + + if parsed.tzinfo is None: + parsed = parsed.replace(tzinfo=timezone.utc) + return max(0.0, (datetime.now(timezone.utc) - parsed).total_seconds()) + + +def format_age(timestamp: str) -> str: + return f"{seconds_since(timestamp):.1f}s ago" From 0c48fa561242339845d184ece0858e039d4f9ce9 Mon Sep 17 00:00:00 2001 From: Khuzaymah Bin Haris Date: Sat, 6 Jun 2026 13:45:53 -0600 Subject: [PATCH 02/19] Add mock diagnostics source with normal and fault demo modes. Provide locally generated DiagnosticMessage data so the dashboard can run without ROS 2 or hardware. Co-authored-by: Cursor --- .../src/mock_diagnostics.py | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 ground_station_monitoring_ui/src/mock_diagnostics.py diff --git a/ground_station_monitoring_ui/src/mock_diagnostics.py b/ground_station_monitoring_ui/src/mock_diagnostics.py new file mode 100644 index 0000000..f2388fd --- /dev/null +++ b/ground_station_monitoring_ui/src/mock_diagnostics.py @@ -0,0 +1,63 @@ +"""Locally generated diagnostic data for the prototype dashboard.""" + +from __future__ import annotations + +import math +import time +from dataclasses import dataclass +from typing import Protocol + +from src.diagnostics_contract import DiagnosticMessage, utc_timestamp + + +class DiagnosticsSource(Protocol): + source_name: str + + def get_messages(self) -> list[DiagnosticMessage]: + """Return normalized diagnostic messages for the UI.""" + + +@dataclass +class MockDiagnosticsSource: + """Mock source that can be switched between normal and fault demos.""" + + mode: str = "normal" + source_name: str = "Mock" + + def set_mode(self, mode: str) -> None: + if mode not in {"normal", "fault"}: + raise ValueError(f"Unsupported diagnostics mode: {mode}") + self.mode = mode + + def get_messages(self) -> list[DiagnosticMessage]: + if self.mode == "fault": + return self._fault_messages() + return self._normal_messages() + + def _normal_messages(self) -> list[DiagnosticMessage]: + pulse = math.sin(time.monotonic() / 4.0) + return [ + DiagnosticMessage("board.temperature", "OK", utc_timestamp(0.4), round(42.0 + pulse, 1), "°C"), + DiagnosticMessage("motor.current", "OK", utc_timestamp(0.5), round(0.8 + pulse * 0.05, 2), "A"), + DiagnosticMessage("imu.roll", "OK", utc_timestamp(0.2), round(1.2 + pulse * 0.1, 1), "deg"), + DiagnosticMessage("imu.pitch", "OK", utc_timestamp(0.2), round(-0.4 + pulse * 0.1, 1), "deg"), + DiagnosticMessage("imu.yaw", "OK", utc_timestamp(0.2), round(12.9 + pulse * 0.2, 1), "deg"), + ] + + def _fault_messages(self) -> list[DiagnosticMessage]: + pulse = math.sin(time.monotonic() / 3.0) + return [ + DiagnosticMessage( + "board.temperature", + "FAULT", + utc_timestamp(0.2), + round(82.0 + pulse * 0.5, 1), + "°C", + "High temperature detected", + ), + DiagnosticMessage("motor.current", "OK", utc_timestamp(0.5), 0.8, "A"), + DiagnosticMessage("imu.roll", "OK", utc_timestamp(0.2), 1.2, "deg"), + DiagnosticMessage("imu.pitch", "OK", utc_timestamp(0.2), -0.4, "deg"), + DiagnosticMessage("imu.yaw", "OK", utc_timestamp(0.2), 12.9, "deg"), + DiagnosticMessage("imu.heartbeat", "STALE", utc_timestamp(5.2), None, None, "Sensor timeout"), + ] From 58a7694aa989a378edaedb721b1373cee4c608b2 Mon Sep 17 00:00:00 2001 From: Khuzaymah Bin Haris Date: Sat, 6 Jun 2026 13:46:00 -0600 Subject: [PATCH 03/19] Add dark engineering dashboard theme and status colors. Centralize Qt stylesheet constants for the monitoring prototype panels, tables, and OK/WARN/FAULT/STALE states. Co-authored-by: Cursor --- ground_station_monitoring_ui/src/styles.py | 118 +++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 ground_station_monitoring_ui/src/styles.py diff --git a/ground_station_monitoring_ui/src/styles.py b/ground_station_monitoring_ui/src/styles.py new file mode 100644 index 0000000..239a713 --- /dev/null +++ b/ground_station_monitoring_ui/src/styles.py @@ -0,0 +1,118 @@ +"""Qt styling constants for the WayBionic monitoring prototype.""" + +from __future__ import annotations + + +COLORS = { + "background": "#071019", + "panel": "#0d1722", + "panel_alt": "#101d2a", + "border": "#284052", + "text": "#e8f1f8", + "muted": "#8ea3b1", + "ok": "#3ddc84", + "warn": "#ffb020", + "fault": "#ff4d5e", + "stale": "#9aa4ad", + "blue": "#43a6ff", +} + + +STATUS_COLORS = { + "OK": COLORS["ok"], + "WARN": COLORS["warn"], + "FAULT": COLORS["fault"], + "STALE": COLORS["stale"], +} + + +APP_STYLESHEET = f""" +QMainWindow {{ + background: {COLORS["background"]}; +}} + +QWidget {{ + color: {COLORS["text"]}; + font-family: "Segoe UI", "Inter", Arial, sans-serif; + font-size: 13px; +}} + +QFrame#Panel {{ + background: {COLORS["panel"]}; + border: 1px solid {COLORS["border"]}; + border-radius: 10px; +}} + +QLabel#Title {{ + font-size: 24px; + font-weight: 700; + letter-spacing: 0.5px; +}} + +QLabel#PanelTitle {{ + font-size: 17px; + font-weight: 700; +}} + +QLabel#Muted {{ + color: {COLORS["muted"]}; +}} + +QLabel#StateNormal {{ + background: rgba(61, 220, 132, 0.16); + border: 1px solid {COLORS["ok"]}; + border-radius: 8px; + color: {COLORS["ok"]}; + font-weight: 700; + padding: 8px 12px; +}} + +QLabel#StateFault {{ + background: rgba(255, 77, 94, 0.18); + border: 1px solid {COLORS["fault"]}; + border-radius: 8px; + color: {COLORS["fault"]}; + font-weight: 800; + padding: 8px 12px; +}} + +QPushButton {{ + background: {COLORS["panel_alt"]}; + border: 1px solid {COLORS["border"]}; + border-radius: 8px; + padding: 8px 12px; + font-weight: 600; +}} + +QPushButton:hover {{ + border-color: {COLORS["blue"]}; +}} + +QPushButton:checked {{ + background: rgba(67, 166, 255, 0.18); + border-color: {COLORS["blue"]}; + color: {COLORS["text"]}; +}} + +QTableWidget {{ + background: #09131d; + border: 1px solid {COLORS["border"]}; + border-radius: 8px; + gridline-color: #1f3343; + selection-background-color: rgba(67, 166, 255, 0.25); +}} + +QHeaderView::section {{ + background: {COLORS["panel_alt"]}; + color: {COLORS["muted"]}; + border: none; + border-right: 1px solid {COLORS["border"]}; + padding: 8px; + font-weight: 700; +}} + +QTableWidget::item {{ + padding: 7px; + border-bottom: 1px solid #142535; +}} +""" From 7e6f30d13ea0335fb59c0e9c7a38b43e919c5950 Mon Sep 17 00:00:00 2001 From: Khuzaymah Bin Haris Date: Sat, 6 Jun 2026 13:46:07 -0600 Subject: [PATCH 04/19] Add PySide6 main window for ground station monitoring dashboard. Implement the two-column layout, telemetry table, alerts panel, demo mode controls, and reserved camera/visualization placeholders. Co-authored-by: Cursor --- .../src/main_window.py | 348 ++++++++++++++++++ 1 file changed, 348 insertions(+) create mode 100644 ground_station_monitoring_ui/src/main_window.py diff --git a/ground_station_monitoring_ui/src/main_window.py b/ground_station_monitoring_ui/src/main_window.py new file mode 100644 index 0000000..d044215 --- /dev/null +++ b/ground_station_monitoring_ui/src/main_window.py @@ -0,0 +1,348 @@ +"""Main PySide6 dashboard window for the monitoring prototype.""" + +from __future__ import annotations + +from PySide6.QtCore import Qt, QTimer +from PySide6.QtGui import QColor +from PySide6.QtWidgets import ( + QAbstractItemView, + QButtonGroup, + QFrame, + QGridLayout, + QHBoxLayout, + QHeaderView, + QLabel, + QMainWindow, + QPushButton, + QSizePolicy, + QTableWidget, + QTableWidgetItem, + QVBoxLayout, + QWidget, +) + +from src.diagnostics_contract import DiagnosticMessage, format_age, seconds_since +from src.mock_diagnostics import MockDiagnosticsSource +from src.styles import APP_STYLESHEET, COLORS, STATUS_COLORS + + +class GroundStationMainWindow(QMainWindow): + """Standalone monitoring-first ground station dashboard.""" + + def __init__(self) -> None: + super().__init__() + self.setWindowTitle("WayBionic Ground Station") + self.resize(1360, 820) + + self.diagnostics_source = MockDiagnosticsSource() + self.current_messages: list[DiagnosticMessage] = [] + + self.setStyleSheet(APP_STYLESHEET) + self.setCentralWidget(self._build_central_widget()) + + self.timer = QTimer(self) + self.timer.timeout.connect(self.refresh) + self.timer.start(1000) + self.refresh() + + def _build_central_widget(self) -> QWidget: + root = QWidget() + root_layout = QVBoxLayout(root) + root_layout.setContentsMargins(18, 14, 18, 18) + root_layout.setSpacing(14) + + root_layout.addLayout(self._build_top_bar()) + + body = QHBoxLayout() + body.setSpacing(14) + body.addLayout(self._build_left_column(), 5) + body.addLayout(self._build_right_column(), 6) + root_layout.addLayout(body, 1) + + return root + + def _build_top_bar(self) -> QHBoxLayout: + top_bar = QHBoxLayout() + top_bar.setSpacing(12) + + state_controls = QHBoxLayout() + self.normal_button = QPushButton("Normal Demo") + self.normal_button.setCheckable(True) + self.normal_button.setChecked(True) + self.fault_button = QPushButton("Fault Demo") + self.fault_button.setCheckable(True) + + self.demo_buttons = QButtonGroup(self) + self.demo_buttons.setExclusive(True) + self.demo_buttons.addButton(self.normal_button) + self.demo_buttons.addButton(self.fault_button) + self.normal_button.clicked.connect(lambda: self._set_demo_mode("normal")) + self.fault_button.clicked.connect(lambda: self._set_demo_mode("fault")) + + self.state_label = QLabel("Current State: NORMAL") + self.state_label.setObjectName("StateNormal") + state_controls.addWidget(self.normal_button) + state_controls.addWidget(self.fault_button) + state_controls.addWidget(self.state_label) + + title = QLabel("WayBionic Ground Station") + title.setObjectName("Title") + title.setAlignment(Qt.AlignCenter) + + self.last_updated_label = QLabel("Last updated: --") + self.last_updated_label.setObjectName("Muted") + self.last_updated_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter) + + top_bar.addLayout(state_controls, 2) + top_bar.addWidget(title, 3) + top_bar.addWidget(self.last_updated_label, 2) + return top_bar + + def _build_left_column(self) -> QVBoxLayout: + left = QVBoxLayout() + left.setSpacing(14) + left.addWidget(self._build_system_status_panel(), 2) + left.addWidget(self._build_telemetry_panel(), 5) + left.addWidget(self._build_alerts_panel(), 3) + return left + + def _build_right_column(self) -> QVBoxLayout: + right = QVBoxLayout() + right.setSpacing(14) + right.addWidget( + self._build_placeholder_panel( + "Surgeon Camera View", + "Reserved - no video streaming in this sprint", + ), + 1, + ) + right.addWidget( + self._build_placeholder_panel( + "Robot / Arm Visualization", + "Reserved - monitoring only", + ), + 1, + ) + return right + + def _panel(self) -> QFrame: + panel = QFrame() + panel.setObjectName("Panel") + panel.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + return panel + + def _build_system_status_panel(self) -> QFrame: + panel = self._panel() + layout = QGridLayout(panel) + layout.setContentsMargins(18, 16, 18, 16) + layout.setHorizontalSpacing(18) + layout.setVerticalSpacing(10) + + title = QLabel("System Status") + title.setObjectName("PanelTitle") + layout.addWidget(title, 0, 0, 1, 2) + + self.status_fields: dict[str, QLabel] = {} + rows = [ + ("Diagnostic Source", "Mock"), + ("ROS 2 Connection", "Not connected / Future integration"), + ("Backend Heartbeat", "OK"), + ("UI Mode", "Monitoring only"), + ("Safety Note", "No motor commands sent from this interface"), + ] + for row, (label_text, value_text) in enumerate(rows, start=1): + label = QLabel(label_text) + label.setObjectName("Muted") + value = QLabel(value_text) + value.setWordWrap(True) + layout.addWidget(label, row, 0) + layout.addWidget(value, row, 1) + self.status_fields[label_text] = value + + layout.setColumnStretch(1, 1) + return panel + + def _build_telemetry_panel(self) -> QFrame: + panel = self._panel() + layout = QVBoxLayout(panel) + layout.setContentsMargins(18, 16, 18, 18) + layout.setSpacing(12) + + title = QLabel("Telemetry + Live Values") + title.setObjectName("PanelTitle") + layout.addWidget(title) + + self.telemetry_table = QTableWidget(0, 6) + self.telemetry_table.setHorizontalHeaderLabels( + ["Signal", "Status", "Value", "Unit", "Last Updated", "Message"] + ) + self.telemetry_table.setEditTriggers(QAbstractItemView.NoEditTriggers) + self.telemetry_table.setSelectionBehavior(QAbstractItemView.SelectRows) + self.telemetry_table.setFocusPolicy(Qt.NoFocus) + self.telemetry_table.verticalHeader().setVisible(False) + self.telemetry_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch) + self.telemetry_table.horizontalHeader().setMinimumSectionSize(100) + layout.addWidget(self.telemetry_table, 1) + return panel + + def _build_alerts_panel(self) -> QFrame: + self.alerts_panel = self._panel() + layout = QVBoxLayout(self.alerts_panel) + layout.setContentsMargins(18, 16, 18, 18) + layout.setSpacing(12) + + title_row = QHBoxLayout() + title = QLabel("Current Alerts") + title.setObjectName("PanelTitle") + self.alert_icon = QLabel("") + self.alert_icon.setAlignment(Qt.AlignRight | Qt.AlignVCenter) + self.alert_icon.setStyleSheet(f"font-size: 30px; color: {COLORS['fault']}; font-weight: 800;") + title_row.addWidget(title, 1) + title_row.addWidget(self.alert_icon) + layout.addLayout(title_row) + + self.alerts_container = QVBoxLayout() + self.alerts_container.setSpacing(8) + layout.addLayout(self.alerts_container) + layout.addStretch(1) + return self.alerts_panel + + def _build_placeholder_panel(self, title_text: str, body_text: str) -> QFrame: + panel = self._panel() + layout = QVBoxLayout(panel) + layout.setContentsMargins(24, 22, 24, 22) + layout.setSpacing(12) + + title = QLabel(title_text) + title.setObjectName("PanelTitle") + title.setStyleSheet("font-size: 22px;") + + body = QLabel(body_text) + body.setWordWrap(True) + body.setStyleSheet(f"color: {COLORS['blue']}; font-size: 22px; font-weight: 650;") + + scope = QLabel("Prototype scope: monitoring surface only; no control, video, or simulation backend attached.") + scope.setObjectName("Muted") + scope.setWordWrap(True) + + layout.addWidget(title) + layout.addWidget(body) + layout.addStretch(1) + layout.addWidget(scope) + return panel + + def _set_demo_mode(self, mode: str) -> None: + self.diagnostics_source.set_mode(mode) + self.refresh() + + def refresh(self) -> None: + self.current_messages = self.diagnostics_source.get_messages() + self._update_state() + self._update_system_status() + self._update_table() + self._update_alerts() + + def _update_state(self) -> None: + has_fault = any(message.status in {"FAULT", "STALE"} for message in self.current_messages) + state = "FAULT" if has_fault else "NORMAL" + self.state_label.setText(f"Current State: {state}") + self.state_label.setObjectName("StateFault" if has_fault else "StateNormal") + self.state_label.style().unpolish(self.state_label) + self.state_label.style().polish(self.state_label) + + if self.current_messages: + latest_age = min(seconds_since(message.timestamp) for message in self.current_messages) + self.last_updated_label.setText(f"Last updated: {latest_age:.1f}s ago") + else: + self.last_updated_label.setText("Last updated: --") + + def _update_system_status(self) -> None: + has_stale = any(message.status == "STALE" for message in self.current_messages) + has_fault = any(message.status == "FAULT" for message in self.current_messages) + heartbeat = "STALE" if has_stale else "OK" + heartbeat_color = STATUS_COLORS["STALE"] if has_stale else STATUS_COLORS["OK"] + + self.status_fields["Diagnostic Source"].setText(self.diagnostics_source.source_name) + self.status_fields["Backend Heartbeat"].setText(heartbeat) + self.status_fields["Backend Heartbeat"].setStyleSheet(f"color: {heartbeat_color}; font-weight: 800;") + self.status_fields["Safety Note"].setStyleSheet( + f"color: {COLORS['fault'] if has_fault else COLORS['muted']}; font-weight: 700;" + ) + + def _update_table(self) -> None: + self.telemetry_table.setRowCount(len(self.current_messages)) + + for row, message in enumerate(self.current_messages): + values = [ + message.signal_name, + message.status, + self._format_value(message.value), + message.unit or "-", + format_age(message.timestamp), + message.alert_message or "-", + ] + status_color = QColor(STATUS_COLORS[message.status]) + row_background = QColor("#170d12" if message.status == "FAULT" else "#101016" if message.status == "STALE" else "#09131d") + + for column, value in enumerate(values): + item = QTableWidgetItem(value) + item.setForeground(status_color if column == 1 else QColor(COLORS["text"])) + item.setBackground(row_background) + if column in {1, 2, 3, 4}: + item.setTextAlignment(Qt.AlignCenter) + else: + item.setTextAlignment(Qt.AlignLeft | Qt.AlignVCenter) + if message.status != "OK" and column in {0, 1, 5}: + font = item.font() + font.setBold(True) + item.setFont(font) + self.telemetry_table.setItem(row, column, item) + + def _update_alerts(self) -> None: + self._clear_layout(self.alerts_container) + alerts = [message for message in self.current_messages if message.status != "OK"] + + if not alerts: + self.alert_icon.setText("") + no_alerts = QLabel("No active alerts") + no_alerts.setStyleSheet(f"color: {COLORS['ok']}; font-size: 18px; font-weight: 700;") + self.alerts_container.addWidget(no_alerts) + return + + self.alert_icon.setText("!") + for message in alerts: + label = QLabel(self._alert_text(message)) + label.setWordWrap(True) + label.setStyleSheet( + f""" + background: rgba(255, 77, 94, 0.16); + border: 1px solid {STATUS_COLORS[message.status]}; + border-radius: 8px; + color: {COLORS['text']}; + font-size: 16px; + font-weight: 800; + padding: 10px 12px; + """ + ) + self.alerts_container.addWidget(label) + + def _clear_layout(self, layout: QVBoxLayout) -> None: + while layout.count(): + item = layout.takeAt(0) + widget = item.widget() + if widget is not None: + widget.deleteLater() + + def _alert_text(self, message: DiagnosticMessage) -> str: + if message.signal_name == "board.temperature": + return f"FAULT - Board temperature high: {self._format_value(message.value)}{message.unit or ''}" + if message.signal_name == "imu.heartbeat": + return "STALE - IMU heartbeat timeout" + return f"{message.status} - {message.signal_name}: {message.alert_message or 'Attention required'}" + + def _format_value(self, value: float | str | None) -> str: + if value is None: + return "-" + if isinstance(value, float): + return f"{value:g}" + return str(value) From 48877812ec28eb1dd132bfb368913f438263841d Mon Sep 17 00:00:00 2001 From: Khuzaymah Bin Haris Date: Sat, 6 Jun 2026 13:46:14 -0600 Subject: [PATCH 05/19] Add app entry point, dependencies, and prototype documentation. Wire up the runnable PySide6 launcher and document install/run steps plus the backend diagnostics contract for future ROS 2 integration. Co-authored-by: Cursor --- ground_station_monitoring_ui/README.md | 64 +++++++++++++ ground_station_monitoring_ui/app.py | 20 +++++ .../docs/DIAGNOSTICS_CONTRACT.md | 89 +++++++++++++++++++ ground_station_monitoring_ui/requirements.txt | 1 + 4 files changed, 174 insertions(+) create mode 100644 ground_station_monitoring_ui/README.md create mode 100644 ground_station_monitoring_ui/app.py create mode 100644 ground_station_monitoring_ui/docs/DIAGNOSTICS_CONTRACT.md create mode 100644 ground_station_monitoring_ui/requirements.txt diff --git a/ground_station_monitoring_ui/README.md b/ground_station_monitoring_ui/README.md new file mode 100644 index 0000000..06bd1ef --- /dev/null +++ b/ground_station_monitoring_ui/README.md @@ -0,0 +1,64 @@ +# WayBionic Ground Station Monitoring Interface + +This folder contains a first desktop UI proof of concept for the WayBionic engineering and operations ground station. It is a standalone Python + PySide6/Qt dashboard focused on monitoring diagnostic health and live values. + +The prototype uses locally generated mock diagnostic data immediately. It does not depend on robot hardware, sensors, ROS topics, PCBs, cameras, or the doctor/surgeon controller. + +## Scope + +This app is monitoring-only. + +- It does not send motor commands. +- It does not participate in the real-time safety-critical control loop. +- It does not implement video streaming. +- It does not implement robot control or arm simulation. + +The interface is intended to give operators and engineers an early ground station layout while the live robotics integrations are still being built. + +## UI Regions + +- Top bar: demo state controls, current NORMAL/FAULT state, title, and last-updated indicator. +- System Status: diagnostic source, future ROS 2 connection status, backend heartbeat, UI mode, and safety note. +- Telemetry + Live Values: compact diagnostic table for current readings. +- Current Alerts: derived from any diagnostic signal whose status is not `OK`. +- Surgeon Camera View: reserved placeholder; no video streaming in this sprint. +- Robot / Arm Visualization: reserved placeholder; monitoring-only for now. + +## Install + +From this folder: + +```powershell +python -m venv .venv +.\.venv\Scripts\Activate.ps1 +python -m pip install -r requirements.txt +``` + +On Linux/macOS: + +```bash +python -m venv .venv +source .venv/bin/activate +python -m pip install -r requirements.txt +``` + +## Run + +```powershell +python app.py +``` + +## Demo Modes + +Use the top-left buttons to switch states: + +- `Normal Demo`: all mock diagnostic signals report `OK`, and the alerts panel shows `No active alerts`. +- `Fault Demo`: board temperature reports a high-temperature `FAULT`, IMU heartbeat reports `STALE`, and alerts become visually urgent. + +The dashboard refreshes roughly once per second and updates the telemetry table, alert panel, backend heartbeat, state indicator, and last-updated text from the current diagnostic source. + +## Future ROS 2 Integration + +The UI depends on normalized `DiagnosticMessage` objects, not on mock internals. A future `ROS2DiagnosticsSubscriber` should replace `MockDiagnosticsSource` and convert live ROS 2 diagnostics into the same normalized contract before the data reaches the UI. + +See `docs/DIAGNOSTICS_CONTRACT.md` for the expected data shape and backend integration notes. diff --git a/ground_station_monitoring_ui/app.py b/ground_station_monitoring_ui/app.py new file mode 100644 index 0000000..731d6df --- /dev/null +++ b/ground_station_monitoring_ui/app.py @@ -0,0 +1,20 @@ +"""Entry point for the WayBionic ground station monitoring prototype.""" + +from __future__ import annotations + +import sys + +from PySide6.QtWidgets import QApplication + +from src.main_window import GroundStationMainWindow + + +def main() -> int: + app = QApplication(sys.argv) + window = GroundStationMainWindow() + window.show() + return app.exec() + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/ground_station_monitoring_ui/docs/DIAGNOSTICS_CONTRACT.md b/ground_station_monitoring_ui/docs/DIAGNOSTICS_CONTRACT.md new file mode 100644 index 0000000..01e4e22 --- /dev/null +++ b/ground_station_monitoring_ui/docs/DIAGNOSTICS_CONTRACT.md @@ -0,0 +1,89 @@ +# Diagnostics Contract + +The ground station UI consumes normalized diagnostic messages. Mock data uses this contract today, and future ROS 2 data should be converted into the same shape before reaching the UI. + +## Internal Format + +```python +DiagnosticMessage: + signal_name: str + status: "OK" | "WARN" | "FAULT" | "STALE" + timestamp: str + value: float | str | None + unit: str | None + alert_message: str | None +``` + +## Fields + +- `signal_name`: stable name of the monitored topic or signal, such as `board.temperature`, `motor.current`, `imu.roll`, `imu.pitch`, `imu.yaw`, or `imu.heartbeat`. +- `status`: current health state of the signal. It must map cleanly to `OK`, `WARN`, `FAULT`, or `STALE`. +- `timestamp`: time the diagnostic was generated or last updated. ISO-8601 UTC strings are preferred. +- `value`: current reading, if applicable. +- `unit`: unit for the value, such as `°C`, `A`, `deg`, `V`, or empty/null. +- `alert_message`: human-readable message shown in the alerts panel when status is not `OK`. + +## Example Normal Diagnostics + +```python +[ + DiagnosticMessage("board.temperature", "OK", "2026-06-06T18:30:00.400000+00:00", 42, "°C", None), + DiagnosticMessage("motor.current", "OK", "2026-06-06T18:30:00.500000+00:00", 0.8, "A", None), + DiagnosticMessage("imu.roll", "OK", "2026-06-06T18:30:00.200000+00:00", 1.2, "deg", None), + DiagnosticMessage("imu.pitch", "OK", "2026-06-06T18:30:00.200000+00:00", -0.4, "deg", None), + DiagnosticMessage("imu.yaw", "OK", "2026-06-06T18:30:00.200000+00:00", 12.9, "deg", None), +] +``` + +## Example Fault Diagnostics + +```python +[ + DiagnosticMessage( + "board.temperature", + "FAULT", + "2026-06-06T18:30:00.200000+00:00", + 82, + "°C", + "High temperature detected", + ), + DiagnosticMessage("motor.current", "OK", "2026-06-06T18:30:00.500000+00:00", 0.8, "A", None), + DiagnosticMessage("imu.roll", "OK", "2026-06-06T18:30:00.200000+00:00", 1.2, "deg", None), + DiagnosticMessage("imu.pitch", "OK", "2026-06-06T18:30:00.200000+00:00", -0.4, "deg", None), + DiagnosticMessage("imu.yaw", "OK", "2026-06-06T18:30:00.200000+00:00", 12.9, "deg", None), + DiagnosticMessage( + "imu.heartbeat", + "STALE", + "2026-06-06T18:29:55.200000+00:00", + None, + None, + "Sensor timeout", + ), +] +``` + +## Future ROS 2 Integration + +The current flow is: + +```text +MockDiagnosticsSource -> normalized DiagnosticMessage objects -> UI +``` + +The future flow should be: + +```text +ROS2DiagnosticsSubscriber -> normalized DiagnosticMessage objects -> same UI +``` + +The preferred future topic is `/diagnostics`. The backend may publish diagnostics using ROS 2 diagnostic-style messages, such as diagnostic array/status messages, or another agreed format. + +Whatever live ROS 2 source is used, it should be converted into the normalized `DiagnosticMessage` format before reaching the UI. The UI should not directly depend on ROS message internals. + +## Integration Note For Korede / Backend + +Please publish each monitored signal with a stable signal name, status level, timestamp, current value if applicable, unit, and alert message if applicable. + +The UI expects statuses to map cleanly to `OK`, `WARN`, `FAULT`, or `STALE`. Faults and stale signals should generate visible alerts. + +The UI can initially be tested using mock messages, then connected to live ROS 2 diagnostics once available. diff --git a/ground_station_monitoring_ui/requirements.txt b/ground_station_monitoring_ui/requirements.txt new file mode 100644 index 0000000..7fa34f0 --- /dev/null +++ b/ground_station_monitoring_ui/requirements.txt @@ -0,0 +1 @@ +PySide6 From 1295e1c163ed0ca7de606d5bd43d1cba881f4a16 Mon Sep 17 00:00:00 2001 From: Khuzaymah Bin Haris Date: Sat, 6 Jun 2026 13:52:22 -0600 Subject: [PATCH 06/19] Document languages and frameworks used by the ground station UI. Clarify that the prototype is built with Python 3, PySide6, and Qt Widgets, and note the planned ROS 2 backend integration path. Co-authored-by: Cursor --- ground_station_monitoring_ui/README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ground_station_monitoring_ui/README.md b/ground_station_monitoring_ui/README.md index 06bd1ef..25b6bb0 100644 --- a/ground_station_monitoring_ui/README.md +++ b/ground_station_monitoring_ui/README.md @@ -4,6 +4,18 @@ This folder contains a first desktop UI proof of concept for the WayBionic engin The prototype uses locally generated mock diagnostic data immediately. It does not depend on robot hardware, sensors, ROS topics, PCBs, cameras, or the doctor/surgeon controller. +## Languages and Frameworks + +This prototype is built as a standalone desktop app using: + +- **Python 3**: core application language for the entry point, dashboard logic, mock diagnostics, and normalized diagnostic contract. +- **PySide6**: official Qt for Python bindings used to render the desktop UI. +- **Qt Widgets**: window layout, panels, tables, buttons, timers, and Qt Style Sheets for the dark engineering dashboard theme. + +Current Python dependencies are listed in `requirements.txt`. The only required package today is `PySide6`. + +This sprint does not use ROS 2, rclpy, or web frameworks in the UI layer. Future live integration is expected to add a backend diagnostics subscriber (for example via ROS 2), but the dashboard should continue consuming the same normalized `DiagnosticMessage` format rather than depending directly on ROS message types. + ## Scope This app is monitoring-only. From 0671fe89ca91388a716758167172d6cdc7ac4da5 Mon Sep 17 00:00:00 2001 From: Khuzaymah Bin Haris Date: Sat, 6 Jun 2026 14:52:18 -0600 Subject: [PATCH 07/19] Add waybionic_rviz_plugins ROS 2 package scaffold. Create the isolated ament_cmake package and RViz plugin export metadata for the ground station UI work. Co-authored-by: Cursor --- waybionic_rviz_plugins/CMakeLists.txt | 70 +++++++++++++++++++ waybionic_rviz_plugins/package.xml | 32 +++++++++ waybionic_rviz_plugins/plugin_description.xml | 10 +++ 3 files changed, 112 insertions(+) create mode 100644 waybionic_rviz_plugins/CMakeLists.txt create mode 100644 waybionic_rviz_plugins/package.xml create mode 100644 waybionic_rviz_plugins/plugin_description.xml diff --git a/waybionic_rviz_plugins/CMakeLists.txt b/waybionic_rviz_plugins/CMakeLists.txt new file mode 100644 index 0000000..a193936 --- /dev/null +++ b/waybionic_rviz_plugins/CMakeLists.txt @@ -0,0 +1,70 @@ +cmake_minimum_required(VERSION 3.8) +project(waybionic_rviz_plugins) + +if(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +set(CMAKE_AUTOMOC ON) +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +find_package(ament_cmake REQUIRED) +find_package(diagnostic_msgs REQUIRED) +find_package(pluginlib REQUIRED) +find_package(Qt5 REQUIRED COMPONENTS Widgets) +find_package(rclcpp REQUIRED) +find_package(rviz_common REQUIRED) +find_package(rviz_default_plugins REQUIRED) +find_package(sensor_msgs REQUIRED) + +set(THIS_PACKAGE_INCLUDE_DEPENDS + diagnostic_msgs + pluginlib + rclcpp + rviz_common + rviz_default_plugins + sensor_msgs +) + +add_library(${PROJECT_NAME} SHARED + include/waybionic_rviz_plugins/diagnostics_panel.hpp + src/diagnostics_panel.cpp + src/mock_diagnostics_source.cpp +) + +target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17) +target_include_directories(${PROJECT_NAME} PUBLIC + $ + $ +) +target_link_libraries(${PROJECT_NAME} Qt5::Widgets) +ament_target_dependencies(${PROJECT_NAME} ${THIS_PACKAGE_INCLUDE_DEPENDS}) + +pluginlib_export_plugin_description_file(rviz_common plugin_description.xml) + +install( + TARGETS ${PROJECT_NAME} + EXPORT export_${PROJECT_NAME} + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin +) + +install( + DIRECTORY include/ + DESTINATION include/${PROJECT_NAME} +) + +install( + DIRECTORY config launch docs + DESTINATION share/${PROJECT_NAME} +) + +install( + FILES plugin_description.xml + DESTINATION share/${PROJECT_NAME} +) + +ament_export_targets(export_${PROJECT_NAME} HAS_LIBRARY_TARGET) +ament_export_dependencies(${THIS_PACKAGE_INCLUDE_DEPENDS}) +ament_package() diff --git a/waybionic_rviz_plugins/package.xml b/waybionic_rviz_plugins/package.xml new file mode 100644 index 0000000..334258c --- /dev/null +++ b/waybionic_rviz_plugins/package.xml @@ -0,0 +1,32 @@ + + + waybionic_rviz_plugins + 0.1.0 + RViz2-native ground station panels, layouts, and launch files for WayBionic monitoring. + WayBionic + MIT + + ament_cmake + + diagnostic_msgs + pluginlib + qtbase5-dev + rclcpp + rviz_common + rviz_default_plugins + sensor_msgs + + annin_ar4_moveit_config + annin_ar4_description + joint_state_publisher + launch + launch_ros + robot_state_publisher + rviz2 + xacro + + + ament_cmake + + + diff --git a/waybionic_rviz_plugins/plugin_description.xml b/waybionic_rviz_plugins/plugin_description.xml new file mode 100644 index 0000000..faa00a6 --- /dev/null +++ b/waybionic_rviz_plugins/plugin_description.xml @@ -0,0 +1,10 @@ + + + + WayBionic ground station diagnostics, telemetry, and alerts panel for RViz2. + + + From 4b781f7e6753a67bda986345ba0453f71d6d57d3 Mon Sep 17 00:00:00 2001 From: Khuzaymah Bin Haris Date: Sat, 6 Jun 2026 14:52:25 -0600 Subject: [PATCH 08/19] Add normalized diagnostics contract and mock data source. Define DiagnosticMessage and local Normal/Fault demo diagnostics so the RViz panel can run before live ROS topics are available. Co-authored-by: Cursor --- .../diagnostics_contract.hpp | 52 ++++++++++++ .../mock_diagnostics_source.hpp | 38 +++++++++ .../src/mock_diagnostics_source.cpp | 85 +++++++++++++++++++ 3 files changed, 175 insertions(+) create mode 100644 waybionic_rviz_plugins/include/waybionic_rviz_plugins/diagnostics_contract.hpp create mode 100644 waybionic_rviz_plugins/include/waybionic_rviz_plugins/mock_diagnostics_source.hpp create mode 100644 waybionic_rviz_plugins/src/mock_diagnostics_source.cpp diff --git a/waybionic_rviz_plugins/include/waybionic_rviz_plugins/diagnostics_contract.hpp b/waybionic_rviz_plugins/include/waybionic_rviz_plugins/diagnostics_contract.hpp new file mode 100644 index 0000000..6f8fd26 --- /dev/null +++ b/waybionic_rviz_plugins/include/waybionic_rviz_plugins/diagnostics_contract.hpp @@ -0,0 +1,52 @@ +#ifndef WAYBIONIC_RVIZ_PLUGINS__DIAGNOSTICS_CONTRACT_HPP_ +#define WAYBIONIC_RVIZ_PLUGINS__DIAGNOSTICS_CONTRACT_HPP_ + +#include +#include + +#include + +namespace waybionic_rviz_plugins +{ + +enum class DiagnosticStatus +{ + Ok, + Warn, + Fault, + Stale +}; + +struct DiagnosticMessage +{ + std::string signal_name; + DiagnosticStatus status; + rclcpp::Time timestamp; + std::optional value; + std::optional unit; + std::optional alert_message; +}; + +inline const char * toString(const DiagnosticStatus status) +{ + switch (status) { + case DiagnosticStatus::Ok: + return "OK"; + case DiagnosticStatus::Warn: + return "WARN"; + case DiagnosticStatus::Fault: + return "FAULT"; + case DiagnosticStatus::Stale: + return "STALE"; + } + return "UNKNOWN"; +} + +inline bool isAlertStatus(const DiagnosticStatus status) +{ + return status != DiagnosticStatus::Ok; +} + +} // namespace waybionic_rviz_plugins + +#endif // WAYBIONIC_RVIZ_PLUGINS__DIAGNOSTICS_CONTRACT_HPP_ diff --git a/waybionic_rviz_plugins/include/waybionic_rviz_plugins/mock_diagnostics_source.hpp b/waybionic_rviz_plugins/include/waybionic_rviz_plugins/mock_diagnostics_source.hpp new file mode 100644 index 0000000..593b360 --- /dev/null +++ b/waybionic_rviz_plugins/include/waybionic_rviz_plugins/mock_diagnostics_source.hpp @@ -0,0 +1,38 @@ +#ifndef WAYBIONIC_RVIZ_PLUGINS__MOCK_DIAGNOSTICS_SOURCE_HPP_ +#define WAYBIONIC_RVIZ_PLUGINS__MOCK_DIAGNOSTICS_SOURCE_HPP_ + +#include +#include + +#include + +#include "waybionic_rviz_plugins/diagnostics_contract.hpp" + +namespace waybionic_rviz_plugins +{ + +enum class DemoMode +{ + Normal, + Fault +}; + +class MockDiagnosticsSource +{ +public: + void setMode(DemoMode mode); + DemoMode mode() const; + std::string sourceName() const; + + std::vector messages(const rclcpp::Time & now) const; + +private: + std::vector normalMessages(const rclcpp::Time & now) const; + std::vector faultMessages(const rclcpp::Time & now) const; + + DemoMode mode_{DemoMode::Normal}; +}; + +} // namespace waybionic_rviz_plugins + +#endif // WAYBIONIC_RVIZ_PLUGINS__MOCK_DIAGNOSTICS_SOURCE_HPP_ diff --git a/waybionic_rviz_plugins/src/mock_diagnostics_source.cpp b/waybionic_rviz_plugins/src/mock_diagnostics_source.cpp new file mode 100644 index 0000000..1f8bed2 --- /dev/null +++ b/waybionic_rviz_plugins/src/mock_diagnostics_source.cpp @@ -0,0 +1,85 @@ +#include "waybionic_rviz_plugins/mock_diagnostics_source.hpp" + +#include +#include +#include +#include +#include +#include + +namespace waybionic_rviz_plugins +{ +namespace +{ + +rclcpp::Time secondsAgo(const rclcpp::Time & now, const double seconds) +{ + const auto nanoseconds = static_cast(seconds * 1'000'000'000.0); + return now - rclcpp::Duration::from_nanoseconds(nanoseconds); +} + +std::string formatNumber(const double value, const int precision) +{ + char buffer[32]; + std::snprintf(buffer, sizeof(buffer), "%.*f", precision, value); + return std::string(buffer); +} + +} // namespace + +void MockDiagnosticsSource::setMode(const DemoMode mode) +{ + mode_ = mode; +} + +DemoMode MockDiagnosticsSource::mode() const +{ + return mode_; +} + +std::string MockDiagnosticsSource::sourceName() const +{ + return "Mock"; +} + +std::vector MockDiagnosticsSource::messages(const rclcpp::Time & now) const +{ + if (mode_ == DemoMode::Fault) { + return faultMessages(now); + } + return normalMessages(now); +} + +std::vector MockDiagnosticsSource::normalMessages(const rclcpp::Time & now) const +{ + const double pulse = std::sin(now.seconds() / 4.0); + return { + {"board.temperature", DiagnosticStatus::Ok, secondsAgo(now, 0.4), formatNumber(42.0 + pulse, 1), "C", std::nullopt}, + {"motor.current", DiagnosticStatus::Ok, secondsAgo(now, 0.5), formatNumber(0.8 + pulse * 0.05, 2), "A", std::nullopt}, + {"imu.roll", DiagnosticStatus::Ok, secondsAgo(now, 0.2), formatNumber(1.2 + pulse * 0.1, 1), "deg", std::nullopt}, + {"imu.pitch", DiagnosticStatus::Ok, secondsAgo(now, 0.2), formatNumber(-0.4 + pulse * 0.1, 1), "deg", std::nullopt}, + {"imu.yaw", DiagnosticStatus::Ok, secondsAgo(now, 0.2), formatNumber(12.9 + pulse * 0.2, 1), "deg", std::nullopt}, + }; +} + +std::vector MockDiagnosticsSource::faultMessages(const rclcpp::Time & now) const +{ + const double pulse = std::sin(now.seconds() / 3.0); + return { + { + "board.temperature", + DiagnosticStatus::Fault, + secondsAgo(now, 0.2), + formatNumber(82.0 + pulse * 0.5, 1), + "C", + "High temperature detected", + }, + {"motor.current", DiagnosticStatus::Ok, secondsAgo(now, 0.5), "0.80", "A", std::nullopt}, + {"imu.roll", DiagnosticStatus::Ok, secondsAgo(now, 0.2), "1.2", "deg", std::nullopt}, + {"imu.pitch", DiagnosticStatus::Ok, secondsAgo(now, 0.2), "-0.4", "deg", std::nullopt}, + {"imu.yaw", DiagnosticStatus::Ok, secondsAgo(now, 0.2), "12.9", "deg", std::nullopt}, + {"imu.heartbeat", DiagnosticStatus::Stale, secondsAgo(now, 5.2), std::nullopt, std::nullopt, "Sensor timeout"}, + }; +} + +} // namespace waybionic_rviz_plugins From abd8231d272b53cc8c4c612d336289998ca1db0b Mon Sep 17 00:00:00 2001 From: Khuzaymah Bin Haris Date: Sat, 6 Jun 2026 14:52:32 -0600 Subject: [PATCH 09/19] Add RViz2 diagnostics panel plugin for engineer monitoring. Implement the docked Qt panel with system status, telemetry table, alerts, and Normal/Fault mock demo controls. Co-authored-by: Cursor --- .../diagnostics_panel.hpp | 70 +++ .../src/diagnostics_panel.cpp | 419 ++++++++++++++++++ 2 files changed, 489 insertions(+) create mode 100644 waybionic_rviz_plugins/include/waybionic_rviz_plugins/diagnostics_panel.hpp create mode 100644 waybionic_rviz_plugins/src/diagnostics_panel.cpp diff --git a/waybionic_rviz_plugins/include/waybionic_rviz_plugins/diagnostics_panel.hpp b/waybionic_rviz_plugins/include/waybionic_rviz_plugins/diagnostics_panel.hpp new file mode 100644 index 0000000..73585fc --- /dev/null +++ b/waybionic_rviz_plugins/include/waybionic_rviz_plugins/diagnostics_panel.hpp @@ -0,0 +1,70 @@ +#ifndef WAYBIONIC_RVIZ_PLUGINS__DIAGNOSTICS_PANEL_HPP_ +#define WAYBIONIC_RVIZ_PLUGINS__DIAGNOSTICS_PANEL_HPP_ + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include "waybionic_rviz_plugins/diagnostics_contract.hpp" +#include "waybionic_rviz_plugins/mock_diagnostics_source.hpp" + +class QButtonGroup; +class QPushButton; + +namespace waybionic_rviz_plugins +{ + +class DiagnosticsPanel : public rviz_common::Panel +{ + Q_OBJECT + +public: + explicit DiagnosticsPanel(QWidget * parent = nullptr); + + void onInitialize() override; + +private: + void buildUi(); + void refresh(); + void setDemoMode(DemoMode mode); + void updateSystemStatus(const std::vector & messages, const rclcpp::Time & now); + void updateTelemetryTable(const std::vector & messages, const rclcpp::Time & now); + void updateAlerts(const std::vector & messages); + void clearAlerts(); + + QString statusColor(DiagnosticStatus status) const; + QString rowBackground(DiagnosticStatus status) const; + QString ageText(const rclcpp::Time & timestamp, const rclcpp::Time & now) const; + QString optionalText(const std::optional & value) const; + QString alertText(const DiagnosticMessage & message) const; + + MockDiagnosticsSource diagnostics_source_; + rclcpp::Clock clock_{RCL_SYSTEM_TIME}; + + QTimer * refresh_timer_{nullptr}; + QLabel * state_label_{nullptr}; + QLabel * last_updated_label_{nullptr}; + QLabel * source_label_{nullptr}; + QLabel * ros_connection_label_{nullptr}; + QLabel * heartbeat_label_{nullptr}; + QLabel * ui_mode_label_{nullptr}; + QLabel * safety_label_{nullptr}; + QLabel * alert_icon_label_{nullptr}; + QTableWidget * telemetry_table_{nullptr}; + QVBoxLayout * alerts_layout_{nullptr}; + QPushButton * normal_button_{nullptr}; + QPushButton * fault_button_{nullptr}; + QButtonGroup * demo_button_group_{nullptr}; +}; + +} // namespace waybionic_rviz_plugins + +#endif // WAYBIONIC_RVIZ_PLUGINS__DIAGNOSTICS_PANEL_HPP_ diff --git a/waybionic_rviz_plugins/src/diagnostics_panel.cpp b/waybionic_rviz_plugins/src/diagnostics_panel.cpp new file mode 100644 index 0000000..9707f23 --- /dev/null +++ b/waybionic_rviz_plugins/src/diagnostics_panel.cpp @@ -0,0 +1,419 @@ +#include "waybionic_rviz_plugins/diagnostics_panel.hpp" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace waybionic_rviz_plugins +{ +namespace +{ + +constexpr const char * kPanelStyle = R"( +QWidget { + background-color: #071019; + color: #e8f1f8; + font-family: "Segoe UI", "Ubuntu", sans-serif; + font-size: 12px; +} +QFrame#Card { + background-color: #0d1722; + border: 1px solid #284052; + border-radius: 8px; +} +QLabel#PanelTitle { + font-size: 15px; + font-weight: 700; +} +QLabel#Muted { + color: #8ea3b1; +} +QLabel#StateNormal { + background-color: rgba(61, 220, 132, 0.16); + border: 1px solid #3ddc84; + border-radius: 6px; + color: #3ddc84; + font-weight: 800; + padding: 6px; +} +QLabel#StateFault { + background-color: rgba(255, 77, 94, 0.18); + border: 1px solid #ff4d5e; + border-radius: 6px; + color: #ff4d5e; + font-weight: 800; + padding: 6px; +} +QPushButton { + background-color: #101d2a; + border: 1px solid #284052; + border-radius: 6px; + color: #e8f1f8; + font-weight: 700; + padding: 6px; +} +QPushButton:checked { + background-color: rgba(67, 166, 255, 0.22); + border-color: #43a6ff; +} +QTableWidget { + background-color: #09131d; + border: 1px solid #284052; + gridline-color: #1f3343; +} +QHeaderView::section { + background-color: #101d2a; + color: #8ea3b1; + border: none; + border-right: 1px solid #284052; + font-weight: 700; + padding: 5px; +} +)"; + +QLabel * makeTitle(const QString & text) +{ + auto * label = new QLabel(text); + label->setObjectName("PanelTitle"); + return label; +} + +QLabel * makeMuted(const QString & text) +{ + auto * label = new QLabel(text); + label->setObjectName("Muted"); + return label; +} + +QFrame * makeCard() +{ + auto * card = new QFrame(); + card->setObjectName("Card"); + return card; +} + +} // namespace + +DiagnosticsPanel::DiagnosticsPanel(QWidget * parent) +: rviz_common::Panel(parent) +{ + buildUi(); +} + +void DiagnosticsPanel::onInitialize() +{ + refresh_timer_ = new QTimer(this); + connect(refresh_timer_, &QTimer::timeout, this, [this]() { refresh(); }); + refresh_timer_->start(1000); + refresh(); +} + +void DiagnosticsPanel::buildUi() +{ + setStyleSheet(kPanelStyle); + setMinimumWidth(420); + + auto * root_layout = new QVBoxLayout(this); + root_layout->setContentsMargins(10, 10, 10, 10); + root_layout->setSpacing(10); + + auto * header_card = makeCard(); + auto * header_layout = new QVBoxLayout(header_card); + header_layout->setSpacing(8); + + auto * title = makeTitle("WayBionic Engineering Monitor"); + state_label_ = new QLabel("Current State: NORMAL"); + state_label_->setObjectName("StateNormal"); + last_updated_label_ = makeMuted("Last updated: --"); + + auto * button_row = new QHBoxLayout(); + normal_button_ = new QPushButton("Normal Demo"); + normal_button_->setCheckable(true); + normal_button_->setChecked(true); + fault_button_ = new QPushButton("Fault Demo"); + fault_button_->setCheckable(true); + + demo_button_group_ = new QButtonGroup(this); + demo_button_group_->setExclusive(true); + demo_button_group_->addButton(normal_button_); + demo_button_group_->addButton(fault_button_); + connect(normal_button_, &QPushButton::clicked, this, [this]() { setDemoMode(DemoMode::Normal); }); + connect(fault_button_, &QPushButton::clicked, this, [this]() { setDemoMode(DemoMode::Fault); }); + + button_row->addWidget(normal_button_); + button_row->addWidget(fault_button_); + + header_layout->addWidget(title); + header_layout->addLayout(button_row); + header_layout->addWidget(state_label_); + header_layout->addWidget(last_updated_label_); + root_layout->addWidget(header_card); + + auto * system_card = makeCard(); + auto * system_layout = new QGridLayout(system_card); + system_layout->setVerticalSpacing(6); + system_layout->addWidget(makeTitle("System Status"), 0, 0, 1, 2); + + source_label_ = new QLabel("Mock"); + ros_connection_label_ = new QLabel("Not connected / Future integration"); + heartbeat_label_ = new QLabel("OK"); + ui_mode_label_ = new QLabel("Monitoring only"); + safety_label_ = new QLabel("No motor commands sent from this RViz panel"); + safety_label_->setWordWrap(true); + + const std::vector> rows = { + {"Diagnostic Source", source_label_}, + {"ROS 2 Connection", ros_connection_label_}, + {"Backend Heartbeat", heartbeat_label_}, + {"UI Mode", ui_mode_label_}, + {"Safety Note", safety_label_}, + }; + + int row = 1; + for (const auto & [name, value] : rows) { + system_layout->addWidget(makeMuted(name), row, 0); + system_layout->addWidget(value, row, 1); + ++row; + } + root_layout->addWidget(system_card); + + auto * table_card = makeCard(); + auto * table_layout = new QVBoxLayout(table_card); + table_layout->addWidget(makeTitle("Telemetry + Live Values")); + + telemetry_table_ = new QTableWidget(0, 6); + telemetry_table_->setHorizontalHeaderLabels({"Signal", "Status", "Value", "Unit", "Last Updated", "Message"}); + telemetry_table_->setEditTriggers(QAbstractItemView::NoEditTriggers); + telemetry_table_->setSelectionBehavior(QAbstractItemView::SelectRows); + telemetry_table_->verticalHeader()->setVisible(false); + telemetry_table_->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + telemetry_table_->horizontalHeader()->setMinimumSectionSize(80); + table_layout->addWidget(telemetry_table_); + root_layout->addWidget(table_card, 1); + + auto * alerts_card = makeCard(); + auto * alerts_root = new QVBoxLayout(alerts_card); + auto * alerts_title_row = new QHBoxLayout(); + alerts_title_row->addWidget(makeTitle("Current Alerts"), 1); + alert_icon_label_ = new QLabel(""); + alert_icon_label_->setStyleSheet("color: #ff4d5e; font-size: 28px; font-weight: 900;"); + alerts_title_row->addWidget(alert_icon_label_); + alerts_root->addLayout(alerts_title_row); + + alerts_layout_ = new QVBoxLayout(); + alerts_layout_->setSpacing(6); + alerts_root->addLayout(alerts_layout_); + alerts_root->addStretch(1); + root_layout->addWidget(alerts_card); +} + +void DiagnosticsPanel::refresh() +{ + const auto now = clock_.now(); + const auto messages = diagnostics_source_.messages(now); + updateSystemStatus(messages, now); + updateTelemetryTable(messages, now); + updateAlerts(messages); +} + +void DiagnosticsPanel::setDemoMode(const DemoMode mode) +{ + diagnostics_source_.setMode(mode); + refresh(); +} + +void DiagnosticsPanel::updateSystemStatus( + const std::vector & messages, + const rclcpp::Time & now) +{ + const bool has_alert = + std::any_of(messages.begin(), messages.end(), [](const auto & message) { + return isAlertStatus(message.status); + }); + const bool has_stale = + std::any_of(messages.begin(), messages.end(), [](const auto & message) { + return message.status == DiagnosticStatus::Stale; + }); + + state_label_->setText(has_alert ? "Current State: FAULT" : "Current State: NORMAL"); + state_label_->setObjectName(has_alert ? "StateFault" : "StateNormal"); + state_label_->style()->unpolish(state_label_); + state_label_->style()->polish(state_label_); + + source_label_->setText(QString::fromStdString(diagnostics_source_.sourceName())); + heartbeat_label_->setText(has_stale ? "STALE" : "OK"); + heartbeat_label_->setStyleSheet(QString("color: %1; font-weight: 800;").arg(has_stale ? "#9aa4ad" : "#3ddc84")); + safety_label_->setStyleSheet(QString("color: %1; font-weight: 700;").arg(has_alert ? "#ff4d5e" : "#8ea3b1")); + + if (messages.empty()) { + last_updated_label_->setText("Last updated: --"); + return; + } + + double latest_age = std::numeric_limits::max(); + for (const auto & message : messages) { + latest_age = std::min(latest_age, (now - message.timestamp).seconds()); + } + last_updated_label_->setText(QString("Last updated: %1s ago").arg(latest_age, 0, 'f', 1)); +} + +void DiagnosticsPanel::updateTelemetryTable( + const std::vector & messages, + const rclcpp::Time & now) +{ + telemetry_table_->setRowCount(static_cast(messages.size())); + + for (int row = 0; row < static_cast(messages.size()); ++row) { + const auto & message = messages.at(row); + const QStringList values = { + QString::fromStdString(message.signal_name), + toString(message.status), + optionalText(message.value), + optionalText(message.unit), + ageText(message.timestamp, now), + optionalText(message.alert_message), + }; + + for (int column = 0; column < values.size(); ++column) { + auto * item = new QTableWidgetItem(values.at(column)); + item->setBackground(QColor(rowBackground(message.status))); + item->setForeground(QColor(column == 1 ? statusColor(message.status) : "#e8f1f8")); + if (column == 1 || column == 2 || column == 3 || column == 4) { + item->setTextAlignment(Qt::AlignCenter); + } + if (message.status != DiagnosticStatus::Ok && (column == 0 || column == 1 || column == 5)) { + auto font = item->font(); + font.setBold(true); + item->setFont(font); + } + telemetry_table_->setItem(row, column, item); + } + } +} + +void DiagnosticsPanel::updateAlerts(const std::vector & messages) +{ + clearAlerts(); + + bool has_alert = false; + for (const auto & message : messages) { + if (!isAlertStatus(message.status)) { + continue; + } + + has_alert = true; + auto * label = new QLabel(alertText(message)); + label->setWordWrap(true); + label->setStyleSheet(QString( + "background-color: rgba(255, 77, 94, 0.18);" + "border: 1px solid %1;" + "border-radius: 6px;" + "color: #e8f1f8;" + "font-weight: 800;" + "padding: 8px;").arg(statusColor(message.status))); + alerts_layout_->addWidget(label); + } + + if (!has_alert) { + alert_icon_label_->setText(""); + auto * label = new QLabel("No active alerts"); + label->setStyleSheet("color: #3ddc84; font-size: 15px; font-weight: 800;"); + alerts_layout_->addWidget(label); + return; + } + + alert_icon_label_->setText("!"); +} + +void DiagnosticsPanel::clearAlerts() +{ + while (alerts_layout_->count() > 0) { + auto * item = alerts_layout_->takeAt(0); + if (auto * widget = item->widget()) { + widget->deleteLater(); + } + delete item; + } +} + +QString DiagnosticsPanel::statusColor(const DiagnosticStatus status) const +{ + switch (status) { + case DiagnosticStatus::Ok: + return "#3ddc84"; + case DiagnosticStatus::Warn: + return "#ffb020"; + case DiagnosticStatus::Fault: + return "#ff4d5e"; + case DiagnosticStatus::Stale: + return "#9aa4ad"; + } + return "#e8f1f8"; +} + +QString DiagnosticsPanel::rowBackground(const DiagnosticStatus status) const +{ + switch (status) { + case DiagnosticStatus::Fault: + return "#241018"; + case DiagnosticStatus::Warn: + return "#241d0d"; + case DiagnosticStatus::Stale: + return "#151922"; + case DiagnosticStatus::Ok: + return "#09131d"; + } + return "#09131d"; +} + +QString DiagnosticsPanel::ageText(const rclcpp::Time & timestamp, const rclcpp::Time & now) const +{ + const double age_seconds = std::max(0.0, (now - timestamp).seconds()); + return QString("%1s ago").arg(age_seconds, 0, 'f', 1); +} + +QString DiagnosticsPanel::optionalText(const std::optional & value) const +{ + if (!value.has_value() || value->empty()) { + return "-"; + } + return QString::fromStdString(*value); +} + +QString DiagnosticsPanel::alertText(const DiagnosticMessage & message) const +{ + if (message.signal_name == "board.temperature") { + return QString("FAULT - Board temperature high: %1 %2") + .arg(optionalText(message.value), optionalText(message.unit)); + } + + if (message.signal_name == "imu.heartbeat") { + return "STALE - IMU heartbeat timeout"; + } + + return QString("%1 - %2: %3") + .arg(toString(message.status)) + .arg(QString::fromStdString(message.signal_name)) + .arg(optionalText(message.alert_message)); +} + +} // namespace waybionic_rviz_plugins + +PLUGINLIB_EXPORT_CLASS(waybionic_rviz_plugins::DiagnosticsPanel, rviz_common::Panel) From fff8fd22df2ec0e61435311806939d9931570d56 Mon Sep 17 00:00:00 2001 From: Khuzaymah Bin Haris Date: Sat, 6 Jun 2026 14:52:39 -0600 Subject: [PATCH 10/19] Add doctor and engineer RViz layouts with launch files. Provide camera-only and monitoring-first RViz configs plus passive engineer visualization launch without motor commands. Co-authored-by: Cursor --- .../config/doctor_camera_view.rviz | 85 +++++++++++++ .../config/engineer_monitoring_view.rviz | 113 ++++++++++++++++++ .../launch/doctor_view.launch.py | 22 ++++ .../launch/engineer_view.launch.py | 86 +++++++++++++ 4 files changed, 306 insertions(+) create mode 100644 waybionic_rviz_plugins/config/doctor_camera_view.rviz create mode 100644 waybionic_rviz_plugins/config/engineer_monitoring_view.rviz create mode 100644 waybionic_rviz_plugins/launch/doctor_view.launch.py create mode 100644 waybionic_rviz_plugins/launch/engineer_view.launch.py diff --git a/waybionic_rviz_plugins/config/doctor_camera_view.rviz b/waybionic_rviz_plugins/config/doctor_camera_view.rviz new file mode 100644 index 0000000..422f08e --- /dev/null +++ b/waybionic_rviz_plugins/config/doctor_camera_view.rviz @@ -0,0 +1,85 @@ +Panels: + - Class: rviz_common/Displays + Name: Displays +Visualization Manager: + Class: "" + Displays: + - Class: rviz_default_plugins/Image + Enabled: true + Max Value: 1 + Median window: 5 + Min Value: 0 + Name: Surgeon Primary Camera + Normalize Range: true + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /camera/camera/color/image_raw + Value: true + - Class: rviz_default_plugins/Image + Enabled: false + Max Value: 1 + Median window: 5 + Min Value: 0 + Name: Surgeon Secondary Camera Placeholder + Normalize Range: true + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /surgeon/secondary/image_raw + Value: false + Enabled: true + Global Options: + Background Color: 0; 0; 0 + Fixed Frame: world + Frame Rate: 30 + Name: root + Tools: + - Class: rviz_default_plugins/Interact + Hide Inactive Objects: true + - Class: rviz_default_plugins/MoveCamera + - Class: rviz_default_plugins/Select + Transformation: + Current: + Class: rviz_default_plugins/TF + Value: true + Views: + Current: + Class: rviz_default_plugins/Orbit + Distance: 1 + Enable Stereo Rendering: + Stereo Eye Separation: 0.06 + Stereo Focal Distance: 1 + Swap Stereo Eyes: false + Value: false + Focal Point: + X: 0 + Y: 0 + Z: 0 + Focal Shape Fixed Size: true + Focal Shape Size: 0.05 + Invert Z Axis: false + Name: Current View + Near Clip Distance: 0.01 + Pitch: 0 + Target Frame: + Value: Orbit (rviz) + Yaw: 0 + Saved: ~ +Window Geometry: + Displays: + collapsed: false + Height: 995 + Hide Left Dock: false + Hide Right Dock: true + Surgeon Primary Camera: + collapsed: false + Surgeon Secondary Camera Placeholder: + collapsed: true + Width: 1920 + X: 0 + Y: 0 diff --git a/waybionic_rviz_plugins/config/engineer_monitoring_view.rviz b/waybionic_rviz_plugins/config/engineer_monitoring_view.rviz new file mode 100644 index 0000000..5eae327 --- /dev/null +++ b/waybionic_rviz_plugins/config/engineer_monitoring_view.rviz @@ -0,0 +1,113 @@ +Panels: + - Class: rviz_common/Displays + Name: Displays + - Class: rviz_common/Views + Name: Views + - Class: waybionic_rviz_plugins/DiagnosticsPanel + Name: WayBionic Diagnostics +Visualization Manager: + Class: "" + Displays: + - Alpha: 0.5 + Cell Size: 1 + Class: rviz_default_plugins/Grid + Color: 160; 160; 164 + Enabled: true + Line Style: + Line Width: 0.03 + Value: Lines + Name: Grid + Normal Cell Count: 0 + Offset: + X: 0 + Y: 0 + Z: 0 + Plane: XY + Plane Cell Count: 10 + Reference Frame: + Value: true + - Class: rviz_default_plugins/TF + Enabled: true + Frame Timeout: 15 + Frames: + All Enabled: true + Marker Scale: 0.4 + Name: TF + Show Arrows: true + Show Axes: true + Show Names: false + Tree: + {} + Update Interval: 0 + Value: true + - Alpha: 1 + Class: rviz_default_plugins/RobotModel + Collision Enabled: false + Description Source: Topic + Description Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /robot_description + Enabled: true + Name: Robot Model + Robot Description: robot_description + TF Prefix: "" + Update Interval: 0 + Value: true + Visual Enabled: true + Enabled: true + Global Options: + Background Color: 15; 22; 30 + Fixed Frame: world + Frame Rate: 30 + Name: root + Tools: + - Class: rviz_default_plugins/Interact + Hide Inactive Objects: true + - Class: rviz_default_plugins/MoveCamera + - Class: rviz_default_plugins/Select + - Class: rviz_default_plugins/FocusCamera + - Class: rviz_default_plugins/Measure + Line color: 128; 128; 0 + Transformation: + Current: + Class: rviz_default_plugins/TF + Value: true + Views: + Current: + Class: rviz_default_plugins/Orbit + Distance: 1.8 + Enable Stereo Rendering: + Stereo Eye Separation: 0.06 + Stereo Focal Distance: 1 + Swap Stereo Eyes: false + Value: false + Focal Point: + X: 0 + Y: 0 + Z: 0.2 + Focal Shape Fixed Size: true + Focal Shape Size: 0.05 + Invert Z Axis: false + Name: Current View + Near Clip Distance: 0.01 + Pitch: 0.55 + Target Frame: + Value: Orbit (rviz) + Yaw: 5.4 + Saved: ~ +Window Geometry: + Displays: + collapsed: false + Height: 995 + Hide Left Dock: false + Hide Right Dock: false + Views: + collapsed: true + WayBionic Diagnostics: + collapsed: false + Width: 1920 + X: 0 + Y: 0 diff --git a/waybionic_rviz_plugins/launch/doctor_view.launch.py b/waybionic_rviz_plugins/launch/doctor_view.launch.py new file mode 100644 index 0000000..a8bc3c1 --- /dev/null +++ b/waybionic_rviz_plugins/launch/doctor_view.launch.py @@ -0,0 +1,22 @@ +from launch import LaunchDescription +from launch_ros.actions import Node +from launch_ros.substitutions import FindPackageShare +from launch.substitutions import PathJoinSubstitution + + +def generate_launch_description(): + rviz_config = PathJoinSubstitution([ + FindPackageShare("waybionic_rviz_plugins"), + "config", + "doctor_camera_view.rviz", + ]) + + return LaunchDescription([ + Node( + package="rviz2", + executable="rviz2", + name="waybionic_doctor_rviz", + output="screen", + arguments=["-d", rviz_config], + ), + ]) diff --git a/waybionic_rviz_plugins/launch/engineer_view.launch.py b/waybionic_rviz_plugins/launch/engineer_view.launch.py new file mode 100644 index 0000000..5b29cad --- /dev/null +++ b/waybionic_rviz_plugins/launch/engineer_view.launch.py @@ -0,0 +1,86 @@ +from launch import LaunchDescription +from launch.actions import DeclareLaunchArgument +from launch.substitutions import Command, FindExecutable, LaunchConfiguration, PathJoinSubstitution +from launch_ros.actions import Node +from launch_ros.substitutions import FindPackageShare + + +def generate_launch_description(): + ar_model = LaunchConfiguration("ar_model") + include_gripper = LaunchConfiguration("include_gripper") + tf_prefix = LaunchConfiguration("tf_prefix") + use_sim_time = LaunchConfiguration("use_sim_time") + + rviz_config = PathJoinSubstitution([ + FindPackageShare("waybionic_rviz_plugins"), + "config", + "engineer_monitoring_view.rviz", + ]) + + robot_description_content = Command([ + PathJoinSubstitution([FindExecutable(name="xacro")]), + " ", + PathJoinSubstitution([ + FindPackageShare("annin_ar4_description"), + "urdf", + "ar.urdf.xacro", + ]), + " ", + "ar_model:=", + ar_model, + " ", + "tf_prefix:=", + tf_prefix, + " ", + "include_gripper:=", + include_gripper, + ]) + robot_description = {"robot_description": robot_description_content} + + return LaunchDescription([ + DeclareLaunchArgument( + "ar_model", + default_value="mk3", + choices=["mk1", "mk2", "mk3"], + description="AR4 model to visualize.", + ), + DeclareLaunchArgument( + "include_gripper", + default_value="True", + choices=["True", "False"], + description="Include the gripper in the visualization.", + ), + DeclareLaunchArgument( + "tf_prefix", + default_value="", + description="Optional TF prefix for the robot tree.", + ), + DeclareLaunchArgument( + "use_sim_time", + default_value="False", + choices=["True", "False"], + description="Use simulation time for passive visualization.", + ), + Node( + package="robot_state_publisher", + executable="robot_state_publisher", + name="waybionic_engineer_robot_state_publisher", + output="screen", + parameters=[robot_description, {"use_sim_time": use_sim_time}], + ), + Node( + package="joint_state_publisher", + executable="joint_state_publisher", + name="waybionic_engineer_joint_state_publisher", + output="screen", + parameters=[{"use_sim_time": use_sim_time}], + ), + Node( + package="rviz2", + executable="rviz2", + name="waybionic_engineer_rviz", + output="screen", + arguments=["-d", rviz_config], + parameters=[robot_description, {"use_sim_time": use_sim_time}], + ), + ]) From 80f9f05e84ea52e98639535f3a6fae5eea7a4899 Mon Sep 17 00:00:00 2001 From: Khuzaymah Bin Haris Date: Sat, 6 Jun 2026 14:52:46 -0600 Subject: [PATCH 11/19] Document RViz ground station setup, run steps, and live integration. Add workspace build/run instructions for ar4_ws and describe future diagnostics, camera, and robot visualization integration paths. Co-authored-by: Cursor --- waybionic_rviz_plugins/README.md | 175 ++++++++++++++++++ .../docs/DIAGNOSTICS_CONTRACT.md | 97 ++++++++++ .../docs/GROUND_STATION_RVIZ_UI.md | 157 ++++++++++++++++ 3 files changed, 429 insertions(+) create mode 100644 waybionic_rviz_plugins/README.md create mode 100644 waybionic_rviz_plugins/docs/DIAGNOSTICS_CONTRACT.md create mode 100644 waybionic_rviz_plugins/docs/GROUND_STATION_RVIZ_UI.md diff --git a/waybionic_rviz_plugins/README.md b/waybionic_rviz_plugins/README.md new file mode 100644 index 0000000..359d24d --- /dev/null +++ b/waybionic_rviz_plugins/README.md @@ -0,0 +1,175 @@ +# WayBionic Ground Station UI (RViz2) + +RViz2-native ground station interface for WayBionic. This package provides two operator views inside RViz2: + +- **Doctor / Surgeon Camera View** — camera feeds only, no engineering telemetry. +- **Engineer View** — robot visualization plus diagnostics, telemetry, live values, and alerts. + +This is the V2 direction. The archived standalone PySide6 prototype lives in `ground_station_monitoring_ui/` on earlier branches. + +## Scope + +Monitoring-first only: + +- No motor commands from this package. +- No robot control or safety-critical logic. +- No camera driver implementation in this sprint. +- No RViz source-code modifications. + +## Prerequisites + +- ROS 2 Jazzy on Ubuntu 24.04 (WSL or native Linux). +- An existing colcon workspace with the AR4 packages built, for example `~/ar4_ws`. +- RViz2 and Qt development packages (installed via `rosdep`). + +Tested workflow uses WSL Ubuntu with an `ar4_ws` workspace that already contains `annin_ar4_moveit_config`, `annin_ar4_description`, and related AR4 packages. + +## Add This Package To Your Workspace + +If this repo is checked out on Windows and you build from WSL, symlink the package into your ROS workspace `src/`: + +```bash +ln -s "/mnt/c/Users//OneDrive/Desktop/Uni Work/Clubs/WayBionic/waybionic_ground_station/waybionic_ground_station/waybionic_rviz_plugins" \ + ~/ar4_ws/src/waybionic_rviz_plugins +``` + +If you clone the repo directly inside WSL, copy or symlink `waybionic_rviz_plugins/` into `~/ar4_ws/src/` instead. + +## Build + +From your ROS workspace (same style as the existing AR4 demo): + +```bash +cd ~/ar4_ws +source /opt/ros/jazzy/setup.bash +rosdep install --from-paths src --ignore-src -r -y +colcon build --packages-select waybionic_rviz_plugins --symlink-install +source install/setup.bash +``` + +After editing files in this package, rebuild with the same `colcon build` command and re-source `install/setup.bash`. + +## Run + +### Engineer monitoring view + +```bash +cd ~/ar4_ws +source /opt/ros/jazzy/setup.bash +source install/setup.bash +ros2 launch waybionic_rviz_plugins engineer_view.launch.py +``` + +Optional arguments: + +```bash +ros2 launch waybionic_rviz_plugins engineer_view.launch.py ar_model:=mk3 include_gripper:=True +``` + +This launches: + +- `robot_state_publisher` and `joint_state_publisher` for passive robot visualization. +- RViz2 with `config/engineer_monitoring_view.rviz`. +- The docked `WayBionic Diagnostics` panel with mock Normal/Fault demo modes. + +It does **not** start the hardware driver or send motor commands. + +### Doctor / surgeon camera view + +```bash +cd ~/ar4_ws +source /opt/ros/jazzy/setup.bash +source install/setup.bash +ros2 launch waybionic_rviz_plugins doctor_view.launch.py +``` + +This opens `config/doctor_camera_view.rviz` with image displays only. + +## Demo Modes (Engineer Panel) + +Use the panel buttons: + +- **Normal Demo** — all mock signals `OK`, no active alerts. +- **Fault Demo** — high board temperature `FAULT` and IMU heartbeat `STALE`. + +The panel refreshes about once per second from the current diagnostic source. + +## Future Integration With Live Data + +### Diagnostics (backend / Korede) + +Today: + +```text +MockDiagnosticsSource -> DiagnosticMessage -> DiagnosticsPanel +``` + +Future: + +```text +ROS2 /diagnostics subscriber -> DiagnosticMessage -> same DiagnosticsPanel +``` + +Preferred topic: `/diagnostics` using `diagnostic_msgs/msg/DiagnosticArray` or an agreed WayBionic format. + +Backend should publish stable signal names, status, timestamp, value, unit, and alert message. See `docs/DIAGNOSTICS_CONTRACT.md` for the full normalized contract and mapping guidance. + +Integration steps (later): + +1. Add a `ROS2DiagnosticsSubscriber` (or equivalent) that converts live messages into `DiagnosticMessage`. +2. Replace or supplement `MockDiagnosticsSource` inside the panel based on a parameter such as `use_mock_diagnostics`. +3. Keep all Qt table and alert rendering on the normalized contract — do not bind the UI directly to ROS message fields. +4. Test with mock data first, then connect live `/diagnostics` once the backend publishes it. + +### Surgeon camera feeds + +Current placeholder topics in `config/doctor_camera_view.rviz`: + +- `/camera/camera/color/image_raw` (primary) +- `/surgeon/secondary/image_raw` (disabled placeholder) + +When real camera drivers are available: + +1. Confirm the live `sensor_msgs/Image` topic names. +2. Update `config/doctor_camera_view.rviz` Image display topics. +3. Launch `doctor_view.launch.py` while the camera node is running. +4. No panel plugin changes are required for basic camera viewing. + +### Robot visualization with live joint states + +The engineer launch currently uses `joint_state_publisher` for a passive demo pose. For live hardware or simulation: + +1. Stop relying on the passive joint publisher. +2. Launch your existing arm stack (driver, Gazebo, or MoveIt) so `/joint_states` and TF are published. +3. Keep using `engineer_monitoring_view.rviz` or point RViz at the same config via `rviz_config_file`. +4. The diagnostics panel remains independent of arm control. + +Example reference for an existing AR4 RViz demo: + +```bash +cd ~/ar4_ws +source install/setup.bash +ros2 launch annin_ar4_moveit_config demo.launch.py +``` + +WayBionic engineer monitoring is separate from that launch, but can reuse the same workspace and robot description packages. + +## Package Layout + +```text +waybionic_rviz_plugins/ + README.md + CMakeLists.txt + package.xml + plugin_description.xml + include/waybionic_rviz_plugins/ + src/ + config/ + launch/ + docs/ +``` + +## More Documentation + +- `docs/GROUND_STATION_RVIZ_UI.md` — view details and architecture notes. +- `docs/DIAGNOSTICS_CONTRACT.md` — normalized diagnostic interface for backend integration. diff --git a/waybionic_rviz_plugins/docs/DIAGNOSTICS_CONTRACT.md b/waybionic_rviz_plugins/docs/DIAGNOSTICS_CONTRACT.md new file mode 100644 index 0000000..067dbb0 --- /dev/null +++ b/waybionic_rviz_plugins/docs/DIAGNOSTICS_CONTRACT.md @@ -0,0 +1,97 @@ +# Diagnostics Contract + +The RViz2 diagnostics panel consumes a normalized internal model. Mock diagnostics use this contract now, and future ROS 2 diagnostics should be converted into the same model before updating the UI. + +## Internal C++ Shape + +```cpp +enum class DiagnosticStatus +{ + Ok, + Warn, + Fault, + Stale +}; + +struct DiagnosticMessage +{ + std::string signal_name; + DiagnosticStatus status; + rclcpp::Time timestamp; + std::optional value; + std::optional unit; + std::optional alert_message; +}; +``` + +## Fields + +- `signal_name`: stable name of the monitored signal, such as `board.temperature`, `motor.current`, `imu.roll`, `imu.pitch`, `imu.yaw`, or `imu.heartbeat`. +- `status`: current health state, normalized to `OK`, `WARN`, `FAULT`, or `STALE`. +- `timestamp`: time the diagnostic was generated or last updated. +- `value`: current reading, if applicable. +- `unit`: unit for the value, such as `C`, `A`, `deg`, `V`, or empty/null. +- `alert_message`: human-readable message shown in the alerts panel when status is not `OK`. + +## Example Normal Data + +```text +board.temperature | OK | 42.0 | C | 0.4s ago | - +motor.current | OK | 0.80 | A | 0.5s ago | - +imu.roll | OK | 1.2 | deg | 0.2s ago | - +imu.pitch | OK | -0.4 | deg | 0.2s ago | - +imu.yaw | OK | 12.9 | deg | 0.2s ago | - +``` + +## Example Fault Data + +```text +board.temperature | FAULT | 82.0 | C | 0.2s ago | High temperature detected +imu.heartbeat | STALE | - | - | 5.2s ago | Sensor timeout +``` + +Other rows can remain `OK` while these fault rows generate visible alerts. + +## Future ROS 2 Mapping + +Preferred future topic: + +```text +/diagnostics +``` + +Recommended source format: + +- `diagnostic_msgs/msg/DiagnosticArray` +- or another agreed WayBionic diagnostics message + +Mapping guidance: + +- ROS diagnostic `OK` maps to `DiagnosticStatus::Ok`. +- ROS diagnostic `WARN` maps to `DiagnosticStatus::Warn`. +- ROS diagnostic `ERROR` maps to `DiagnosticStatus::Fault`. +- Missing or old timestamps should map to `DiagnosticStatus::Stale`. +- Diagnostic key/value pairs should be normalized into `value`, `unit`, and `alert_message` before updating the panel. + +The Qt/RViz UI should depend on `DiagnosticMessage`, not raw ROS message internals. This keeps mock data, future backend diagnostics, and any future simulator diagnostics on the same path. + +## Backend Note + +Please publish each monitored signal with: + +- stable signal name +- status level +- timestamp +- current value, if applicable +- unit, if applicable +- alert message, if applicable + +Faults and stale signals should generate visible alerts in the engineer panel. + +## Integration Checklist (Later) + +1. Publish live diagnostics to `/diagnostics` (or agreed topic) with stable signal names. +2. Implement a ROS 2 subscriber that maps incoming messages to `DiagnosticMessage`. +3. Add a panel parameter such as `use_mock_diagnostics:=false` to switch from mock to live data. +4. Verify Normal and Fault scenarios in the engineer RViz panel before operator use. +5. Keep camera and robot visualization integration separate — they do not require changes to the diagnostic contract itself. diff --git a/waybionic_rviz_plugins/docs/GROUND_STATION_RVIZ_UI.md b/waybionic_rviz_plugins/docs/GROUND_STATION_RVIZ_UI.md new file mode 100644 index 0000000..ae03d31 --- /dev/null +++ b/waybionic_rviz_plugins/docs/GROUND_STATION_RVIZ_UI.md @@ -0,0 +1,157 @@ +# WayBionic RViz2 Ground Station UI + +This package contains the RViz2-native direction for the WayBionic ground station. It keeps the operator interface inside RViz2 and avoids modifying RViz source code. + +## Scope + +This is a monitoring-first UI package. + +- It does not send motor commands. +- It does not implement robot control. +- It does not implement safety-critical logic. +- It does not implement camera streaming drivers. +- It does not replace the existing AR4 driver, MoveIt, Gazebo, or firmware packages. + +## Workspace Setup (WSL / Ubuntu) + +The expected development environment is ROS 2 Jazzy on Ubuntu 24.04, using an existing colcon workspace such as `~/ar4_ws`. + +### 1. Add package to workspace + +Symlink from a Windows checkout into WSL: + +```bash +ln -s "/mnt/c/Users//OneDrive/Desktop/Uni Work/Clubs/WayBionic/waybionic_ground_station/waybionic_ground_station/waybionic_rviz_plugins" \ + ~/ar4_ws/src/waybionic_rviz_plugins +``` + +### 2. Build + +```bash +cd ~/ar4_ws +source /opt/ros/jazzy/setup.bash +rosdep install --from-paths src --ignore-src -r -y +colcon build --packages-select waybionic_rviz_plugins --symlink-install +source install/setup.bash +``` + +### 3. Rebuild after code changes + +```bash +cd ~/ar4_ws +source /opt/ros/jazzy/setup.bash +colcon build --packages-select waybionic_rviz_plugins --symlink-install +source install/setup.bash +``` + +## Views + +### Doctor / Surgeon Camera View + +Launch: + +```bash +cd ~/ar4_ws +source /opt/ros/jazzy/setup.bash +source install/setup.bash +ros2 launch waybionic_rviz_plugins doctor_view.launch.py +``` + +This opens RViz2 with `config/doctor_camera_view.rviz`. The layout is intentionally camera-focused and avoids engineering telemetry clutter. + +Current camera topics are placeholders: + +- `/camera/camera/color/image_raw` +- `/surgeon/secondary/image_raw` disabled by default + +Update the RViz config when the real surgeon camera topics are available. + +### Engineer View + +Launch: + +```bash +cd ~/ar4_ws +source /opt/ros/jazzy/setup.bash +source install/setup.bash +ros2 launch waybionic_rviz_plugins engineer_view.launch.py +``` + +This opens RViz2 with `config/engineer_monitoring_view.rviz`. The layout includes: + +- RViz robot visualization using the AR4 description. +- A docked `WayBionic Diagnostics` panel. +- Mock diagnostics with Normal Demo and Fault Demo controls. +- Telemetry/live values and alerts derived from normalized diagnostic messages. + +The engineer launch uses `robot_state_publisher` and `joint_state_publisher` for passive visualization. It does not start the hardware driver and does not send motor commands. + +Useful launch arguments: + +```bash +ros2 launch waybionic_rviz_plugins engineer_view.launch.py ar_model:=mk3 include_gripper:=True +``` + +## Architecture + +```text +Doctor view: + doctor_view.launch.py -> doctor_camera_view.rviz -> RViz Image displays + +Engineer view: + engineer_view.launch.py -> engineer_monitoring_view.rviz + -> RobotModel / TF visualization + -> WayBionic Diagnostics panel + +Diagnostics flow (today): + MockDiagnosticsSource -> DiagnosticMessage -> DiagnosticsPanel + +Diagnostics flow (future): + ROS2 /diagnostics subscriber -> DiagnosticMessage -> same DiagnosticsPanel +``` + +## Future Live Data Integration + +### Diagnostics panel + +1. Backend publishes to `/diagnostics` (or agreed topic). +2. A future subscriber converts `diagnostic_msgs/msg/DiagnosticArray` into `DiagnosticMessage`. +3. The panel UI stays unchanged and still renders tables/alerts from the normalized contract. +4. Add a launch parameter to switch between mock and live sources during bring-up. + +See `DIAGNOSTICS_CONTRACT.md` for field definitions, examples, and ROS mapping guidance. + +### Camera integration + +1. Start the surgeon camera driver/node. +2. Confirm published `sensor_msgs/Image` topic names. +3. Edit `config/doctor_camera_view.rviz` to point Image displays at live topics. +4. Relaunch `doctor_view.launch.py`. + +### Live robot visualization + +1. Launch driver, Gazebo, or MoveIt so `/joint_states` and TF are live. +2. Use engineer RViz config without the passive `joint_state_publisher`, or include MoveIt with `rviz_config_file` override. +3. Keep diagnostics monitoring separate from control commands. + +## Package Contents + +```text +waybionic_rviz_plugins/ + include/waybionic_rviz_plugins/ + diagnostics_contract.hpp + diagnostics_panel.hpp + mock_diagnostics_source.hpp + src/ + diagnostics_panel.cpp + mock_diagnostics_source.cpp + config/ + engineer_monitoring_view.rviz + doctor_camera_view.rviz + launch/ + engineer_view.launch.py + doctor_view.launch.py + docs/ + GROUND_STATION_RVIZ_UI.md + DIAGNOSTICS_CONTRACT.md +``` From c972b34995f5cf07514307c726f1b67903d9741d Mon Sep 17 00:00:00 2001 From: Khuzaymah Bin Haris Date: Sat, 6 Jun 2026 14:58:36 -0600 Subject: [PATCH 12/19] Archive standalone PySide6 prototype folder after RViz2 pivot. Rename ground_station_monitoring_ui to ground_station_monitoring_ui_archived and note that waybionic_rviz_plugins is now the active ground station UI. Co-authored-by: Cursor --- .../README.md | 4 +++- .../app.py | 0 .../docs/DIAGNOSTICS_CONTRACT.md | 0 .../requirements.txt | 0 .../src/__init__.py | 0 .../src/diagnostics_contract.py | 0 .../src/main_window.py | 0 .../src/mock_diagnostics.py | 0 .../src/styles.py | 0 waybionic_rviz_plugins/README.md | 2 +- 10 files changed, 4 insertions(+), 2 deletions(-) rename {ground_station_monitoring_ui => ground_station_monitoring_ui_archived}/README.md (93%) rename {ground_station_monitoring_ui => ground_station_monitoring_ui_archived}/app.py (100%) rename {ground_station_monitoring_ui => ground_station_monitoring_ui_archived}/docs/DIAGNOSTICS_CONTRACT.md (100%) rename {ground_station_monitoring_ui => ground_station_monitoring_ui_archived}/requirements.txt (100%) rename {ground_station_monitoring_ui => ground_station_monitoring_ui_archived}/src/__init__.py (100%) rename {ground_station_monitoring_ui => ground_station_monitoring_ui_archived}/src/diagnostics_contract.py (100%) rename {ground_station_monitoring_ui => ground_station_monitoring_ui_archived}/src/main_window.py (100%) rename {ground_station_monitoring_ui => ground_station_monitoring_ui_archived}/src/mock_diagnostics.py (100%) rename {ground_station_monitoring_ui => ground_station_monitoring_ui_archived}/src/styles.py (100%) diff --git a/ground_station_monitoring_ui/README.md b/ground_station_monitoring_ui_archived/README.md similarity index 93% rename from ground_station_monitoring_ui/README.md rename to ground_station_monitoring_ui_archived/README.md index 25b6bb0..542defa 100644 --- a/ground_station_monitoring_ui/README.md +++ b/ground_station_monitoring_ui_archived/README.md @@ -1,4 +1,6 @@ -# WayBionic Ground Station Monitoring Interface +# WayBionic Ground Station Monitoring Interface (Archived) + +> **Archived:** This standalone PySide6 prototype has been superseded by the RViz2-native ground station UI in `waybionic_rviz_plugins/`. Kept for reference only. This folder contains a first desktop UI proof of concept for the WayBionic engineering and operations ground station. It is a standalone Python + PySide6/Qt dashboard focused on monitoring diagnostic health and live values. diff --git a/ground_station_monitoring_ui/app.py b/ground_station_monitoring_ui_archived/app.py similarity index 100% rename from ground_station_monitoring_ui/app.py rename to ground_station_monitoring_ui_archived/app.py diff --git a/ground_station_monitoring_ui/docs/DIAGNOSTICS_CONTRACT.md b/ground_station_monitoring_ui_archived/docs/DIAGNOSTICS_CONTRACT.md similarity index 100% rename from ground_station_monitoring_ui/docs/DIAGNOSTICS_CONTRACT.md rename to ground_station_monitoring_ui_archived/docs/DIAGNOSTICS_CONTRACT.md diff --git a/ground_station_monitoring_ui/requirements.txt b/ground_station_monitoring_ui_archived/requirements.txt similarity index 100% rename from ground_station_monitoring_ui/requirements.txt rename to ground_station_monitoring_ui_archived/requirements.txt diff --git a/ground_station_monitoring_ui/src/__init__.py b/ground_station_monitoring_ui_archived/src/__init__.py similarity index 100% rename from ground_station_monitoring_ui/src/__init__.py rename to ground_station_monitoring_ui_archived/src/__init__.py diff --git a/ground_station_monitoring_ui/src/diagnostics_contract.py b/ground_station_monitoring_ui_archived/src/diagnostics_contract.py similarity index 100% rename from ground_station_monitoring_ui/src/diagnostics_contract.py rename to ground_station_monitoring_ui_archived/src/diagnostics_contract.py diff --git a/ground_station_monitoring_ui/src/main_window.py b/ground_station_monitoring_ui_archived/src/main_window.py similarity index 100% rename from ground_station_monitoring_ui/src/main_window.py rename to ground_station_monitoring_ui_archived/src/main_window.py diff --git a/ground_station_monitoring_ui/src/mock_diagnostics.py b/ground_station_monitoring_ui_archived/src/mock_diagnostics.py similarity index 100% rename from ground_station_monitoring_ui/src/mock_diagnostics.py rename to ground_station_monitoring_ui_archived/src/mock_diagnostics.py diff --git a/ground_station_monitoring_ui/src/styles.py b/ground_station_monitoring_ui_archived/src/styles.py similarity index 100% rename from ground_station_monitoring_ui/src/styles.py rename to ground_station_monitoring_ui_archived/src/styles.py diff --git a/waybionic_rviz_plugins/README.md b/waybionic_rviz_plugins/README.md index 359d24d..e5dd759 100644 --- a/waybionic_rviz_plugins/README.md +++ b/waybionic_rviz_plugins/README.md @@ -5,7 +5,7 @@ RViz2-native ground station interface for WayBionic. This package provides two o - **Doctor / Surgeon Camera View** — camera feeds only, no engineering telemetry. - **Engineer View** — robot visualization plus diagnostics, telemetry, live values, and alerts. -This is the V2 direction. The archived standalone PySide6 prototype lives in `ground_station_monitoring_ui/` on earlier branches. +This is the V2 direction. The archived standalone PySide6 prototype lives in `ground_station_monitoring_ui_archived/`. ## Scope From 1bf970b0fe8285aec56d5f0c628cab17a6234908 Mon Sep 17 00:00:00 2001 From: Khuzaymah Bin Haris Date: Sat, 13 Jun 2026 14:09:50 -0600 Subject: [PATCH 13/19] Decouple Annin/AR4 from core waybionic_rviz_plugins launch path. Move passive AR4 visualization into an optional engineer_ar4_demo launch/config so the default engineer view stays generic. Co-authored-by: Cursor --- .../config/engineer_ar4_demo.rviz | 115 ++++++++++++++++++ .../launch/engineer_ar4_demo.launch.py | 104 ++++++++++++++++ .../launch/engineer_view.launch.py | 72 +++-------- waybionic_rviz_plugins/package.xml | 5 - 4 files changed, 237 insertions(+), 59 deletions(-) create mode 100644 waybionic_rviz_plugins/config/engineer_ar4_demo.rviz create mode 100644 waybionic_rviz_plugins/launch/engineer_ar4_demo.launch.py diff --git a/waybionic_rviz_plugins/config/engineer_ar4_demo.rviz b/waybionic_rviz_plugins/config/engineer_ar4_demo.rviz new file mode 100644 index 0000000..2debfa5 --- /dev/null +++ b/waybionic_rviz_plugins/config/engineer_ar4_demo.rviz @@ -0,0 +1,115 @@ +Panels: + - Class: rviz_common/Displays + Name: Displays + - Class: rviz_common/Views + Name: Views + - Class: waybionic_rviz_plugins/DiagnosticsPanel + Diagnostics Topic: /diagnostics + Name: WayBionic Diagnostics + Use Mock Diagnostics: true +Visualization Manager: + Class: "" + Displays: + - Alpha: 0.5 + Cell Size: 1 + Class: rviz_default_plugins/Grid + Color: 160; 160; 164 + Enabled: true + Line Style: + Line Width: 0.03 + Value: Lines + Name: Grid + Normal Cell Count: 0 + Offset: + X: 0 + Y: 0 + Z: 0 + Plane: XY + Plane Cell Count: 10 + Reference Frame: + Value: true + - Class: rviz_default_plugins/TF + Enabled: true + Frame Timeout: 15 + Frames: + All Enabled: true + Marker Scale: 0.4 + Name: TF + Show Arrows: true + Show Axes: true + Show Names: false + Tree: + {} + Update Interval: 0 + Value: true + - Alpha: 1 + Class: rviz_default_plugins/RobotModel + Collision Enabled: false + Description Source: Topic + Description Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /robot_description + Enabled: true + Name: Optional AR4 Robot Model + Robot Description: robot_description + TF Prefix: "" + Update Interval: 0 + Value: true + Visual Enabled: true + Enabled: true + Global Options: + Background Color: 15; 22; 30 + Fixed Frame: world + Frame Rate: 30 + Name: root + Tools: + - Class: rviz_default_plugins/Interact + Hide Inactive Objects: true + - Class: rviz_default_plugins/MoveCamera + - Class: rviz_default_plugins/Select + - Class: rviz_default_plugins/FocusCamera + - Class: rviz_default_plugins/Measure + Line color: 128; 128; 0 + Transformation: + Current: + Class: rviz_default_plugins/TF + Value: true + Views: + Current: + Class: rviz_default_plugins/Orbit + Distance: 1.8 + Enable Stereo Rendering: + Stereo Eye Separation: 0.06 + Stereo Focal Distance: 1 + Swap Stereo Eyes: false + Value: false + Focal Point: + X: 0 + Y: 0 + Z: 0.2 + Focal Shape Fixed Size: true + Focal Shape Size: 0.05 + Invert Z Axis: false + Name: Current View + Near Clip Distance: 0.01 + Pitch: 0.55 + Target Frame: + Value: Orbit (rviz) + Yaw: 5.4 + Saved: ~ +Window Geometry: + Displays: + collapsed: false + Height: 995 + Hide Left Dock: false + Hide Right Dock: false + Views: + collapsed: true + WayBionic Diagnostics: + collapsed: false + Width: 1920 + X: 0 + Y: 0 diff --git a/waybionic_rviz_plugins/launch/engineer_ar4_demo.launch.py b/waybionic_rviz_plugins/launch/engineer_ar4_demo.launch.py new file mode 100644 index 0000000..5954b18 --- /dev/null +++ b/waybionic_rviz_plugins/launch/engineer_ar4_demo.launch.py @@ -0,0 +1,104 @@ +from launch import LaunchDescription +from launch.actions import DeclareLaunchArgument +from launch.substitutions import Command, FindExecutable, LaunchConfiguration, PathJoinSubstitution +from launch_ros.actions import Node +from launch_ros.parameter_descriptions import ParameterValue +from launch_ros.substitutions import FindPackageShare + + +def generate_launch_description(): + ar_model = LaunchConfiguration("ar_model") + include_gripper = LaunchConfiguration("include_gripper") + tf_prefix = LaunchConfiguration("tf_prefix") + use_sim_time = LaunchConfiguration("use_sim_time") + use_mock_diagnostics = LaunchConfiguration("use_mock_diagnostics") + diagnostics_topic = LaunchConfiguration("diagnostics_topic") + + rviz_config = PathJoinSubstitution([ + FindPackageShare("waybionic_rviz_plugins"), + "config", + "engineer_ar4_demo.rviz", + ]) + + robot_description_content = Command([ + PathJoinSubstitution([FindExecutable(name="xacro")]), + " ", + PathJoinSubstitution([ + FindPackageShare("annin_ar4_description"), + "urdf", + "ar.urdf.xacro", + ]), + " ", + "ar_model:=", + ar_model, + " ", + "tf_prefix:=", + tf_prefix, + " ", + "include_gripper:=", + include_gripper, + ]) + robot_description = {"robot_description": robot_description_content} + + return LaunchDescription([ + DeclareLaunchArgument( + "use_mock_diagnostics", + default_value="true", + choices=["true", "false"], + description="Use local mock diagnostics validation states instead of live ROS 2 diagnostics.", + ), + DeclareLaunchArgument( + "diagnostics_topic", + default_value="/diagnostics", + description="ROS 2 diagnostic_msgs/msg/DiagnosticArray topic used when use_mock_diagnostics is false.", + ), + DeclareLaunchArgument( + "ar_model", + default_value="mk3", + choices=["mk1", "mk2", "mk3"], + description="AR4 model to visualize for the optional AR4 helper.", + ), + DeclareLaunchArgument( + "include_gripper", + default_value="True", + choices=["True", "False"], + description="Include the gripper in the optional AR4 visualization.", + ), + DeclareLaunchArgument( + "tf_prefix", + default_value="", + description="Optional TF prefix for the robot tree.", + ), + DeclareLaunchArgument( + "use_sim_time", + default_value="false", + choices=["true", "false"], + description="Use simulation time for passive visualization.", + ), + Node( + package="robot_state_publisher", + executable="robot_state_publisher", + name="waybionic_engineer_ar4_robot_state_publisher", + output="screen", + parameters=[robot_description, {"use_sim_time": ParameterValue(use_sim_time, value_type=bool)}], + ), + Node( + package="joint_state_publisher", + executable="joint_state_publisher", + name="waybionic_engineer_ar4_joint_state_publisher", + output="screen", + parameters=[{"use_sim_time": ParameterValue(use_sim_time, value_type=bool)}], + ), + Node( + package="rviz2", + executable="rviz2", + name="waybionic_engineer_ar4_rviz", + output="screen", + arguments=["-d", rviz_config], + parameters=[robot_description, { + "use_sim_time": ParameterValue(use_sim_time, value_type=bool), + "use_mock_diagnostics": ParameterValue(use_mock_diagnostics, value_type=bool), + "diagnostics_topic": ParameterValue(diagnostics_topic, value_type=str), + }], + ), + ]) diff --git a/waybionic_rviz_plugins/launch/engineer_view.launch.py b/waybionic_rviz_plugins/launch/engineer_view.launch.py index 5b29cad..0a02776 100644 --- a/waybionic_rviz_plugins/launch/engineer_view.launch.py +++ b/waybionic_rviz_plugins/launch/engineer_view.launch.py @@ -1,15 +1,15 @@ from launch import LaunchDescription from launch.actions import DeclareLaunchArgument -from launch.substitutions import Command, FindExecutable, LaunchConfiguration, PathJoinSubstitution +from launch.substitutions import LaunchConfiguration, PathJoinSubstitution from launch_ros.actions import Node +from launch_ros.parameter_descriptions import ParameterValue from launch_ros.substitutions import FindPackageShare def generate_launch_description(): - ar_model = LaunchConfiguration("ar_model") - include_gripper = LaunchConfiguration("include_gripper") - tf_prefix = LaunchConfiguration("tf_prefix") use_sim_time = LaunchConfiguration("use_sim_time") + use_mock_diagnostics = LaunchConfiguration("use_mock_diagnostics") + diagnostics_topic = LaunchConfiguration("diagnostics_topic") rviz_config = PathJoinSubstitution([ FindPackageShare("waybionic_rviz_plugins"), @@ -17,70 +17,34 @@ def generate_launch_description(): "engineer_monitoring_view.rviz", ]) - robot_description_content = Command([ - PathJoinSubstitution([FindExecutable(name="xacro")]), - " ", - PathJoinSubstitution([ - FindPackageShare("annin_ar4_description"), - "urdf", - "ar.urdf.xacro", - ]), - " ", - "ar_model:=", - ar_model, - " ", - "tf_prefix:=", - tf_prefix, - " ", - "include_gripper:=", - include_gripper, - ]) - robot_description = {"robot_description": robot_description_content} - return LaunchDescription([ DeclareLaunchArgument( - "ar_model", - default_value="mk3", - choices=["mk1", "mk2", "mk3"], - description="AR4 model to visualize.", + "use_mock_diagnostics", + default_value="true", + choices=["true", "false"], + description="Use local mock diagnostics validation states instead of live ROS 2 diagnostics.", ), DeclareLaunchArgument( - "include_gripper", - default_value="True", - choices=["True", "False"], - description="Include the gripper in the visualization.", - ), - DeclareLaunchArgument( - "tf_prefix", - default_value="", - description="Optional TF prefix for the robot tree.", + "diagnostics_topic", + default_value="/diagnostics", + description="ROS 2 diagnostic_msgs/msg/DiagnosticArray topic used when use_mock_diagnostics is false.", ), DeclareLaunchArgument( "use_sim_time", - default_value="False", - choices=["True", "False"], + default_value="false", + choices=["true", "false"], description="Use simulation time for passive visualization.", ), - Node( - package="robot_state_publisher", - executable="robot_state_publisher", - name="waybionic_engineer_robot_state_publisher", - output="screen", - parameters=[robot_description, {"use_sim_time": use_sim_time}], - ), - Node( - package="joint_state_publisher", - executable="joint_state_publisher", - name="waybionic_engineer_joint_state_publisher", - output="screen", - parameters=[{"use_sim_time": use_sim_time}], - ), Node( package="rviz2", executable="rviz2", name="waybionic_engineer_rviz", output="screen", arguments=["-d", rviz_config], - parameters=[robot_description, {"use_sim_time": use_sim_time}], + parameters=[{ + "use_sim_time": ParameterValue(use_sim_time, value_type=bool), + "use_mock_diagnostics": ParameterValue(use_mock_diagnostics, value_type=bool), + "diagnostics_topic": ParameterValue(diagnostics_topic, value_type=str), + }], ), ]) diff --git a/waybionic_rviz_plugins/package.xml b/waybionic_rviz_plugins/package.xml index 334258c..97a35f0 100644 --- a/waybionic_rviz_plugins/package.xml +++ b/waybionic_rviz_plugins/package.xml @@ -16,14 +16,9 @@ rviz_default_plugins sensor_msgs - annin_ar4_moveit_config - annin_ar4_description - joint_state_publisher launch launch_ros - robot_state_publisher rviz2 - xacro ament_cmake From e9a0e652e9a727f7d186ece68c3d1b28107c9720 Mon Sep 17 00:00:00 2001 From: Khuzaymah Bin Haris Date: Sat, 13 Jun 2026 14:10:16 -0600 Subject: [PATCH 14/19] Add mock/live diagnostics source switching for the engineer panel. Introduce DiagnosticsSource with MockDiagnosticsSource and RosDiagnosticsSource, use_mock_diagnostics launch/config support, and live /diagnostics mapping into DiagnosticMessage. Co-authored-by: Cursor --- waybionic_rviz_plugins/CMakeLists.txt | 3 + .../config/engineer_monitoring_view.rviz | 2 + .../diagnostics_panel.hpp | 25 ++- .../diagnostics_source.hpp | 26 +++ .../mock_diagnostics_source.hpp | 16 +- .../ros_diagnostics_source.hpp | 45 +++++ .../src/diagnostics_panel.cpp | 152 +++++++++++++-- .../src/mock_diagnostics_source.cpp | 11 +- .../src/ros_diagnostics_source.cpp | 174 ++++++++++++++++++ 9 files changed, 426 insertions(+), 28 deletions(-) create mode 100644 waybionic_rviz_plugins/include/waybionic_rviz_plugins/diagnostics_source.hpp create mode 100644 waybionic_rviz_plugins/include/waybionic_rviz_plugins/ros_diagnostics_source.hpp create mode 100644 waybionic_rviz_plugins/src/ros_diagnostics_source.cpp diff --git a/waybionic_rviz_plugins/CMakeLists.txt b/waybionic_rviz_plugins/CMakeLists.txt index a193936..9779b71 100644 --- a/waybionic_rviz_plugins/CMakeLists.txt +++ b/waybionic_rviz_plugins/CMakeLists.txt @@ -27,9 +27,12 @@ set(THIS_PACKAGE_INCLUDE_DEPENDS ) add_library(${PROJECT_NAME} SHARED + include/waybionic_rviz_plugins/diagnostics_source.hpp include/waybionic_rviz_plugins/diagnostics_panel.hpp + include/waybionic_rviz_plugins/ros_diagnostics_source.hpp src/diagnostics_panel.cpp src/mock_diagnostics_source.cpp + src/ros_diagnostics_source.cpp ) target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17) diff --git a/waybionic_rviz_plugins/config/engineer_monitoring_view.rviz b/waybionic_rviz_plugins/config/engineer_monitoring_view.rviz index 5eae327..574c52a 100644 --- a/waybionic_rviz_plugins/config/engineer_monitoring_view.rviz +++ b/waybionic_rviz_plugins/config/engineer_monitoring_view.rviz @@ -4,7 +4,9 @@ Panels: - Class: rviz_common/Views Name: Views - Class: waybionic_rviz_plugins/DiagnosticsPanel + Diagnostics Topic: /diagnostics Name: WayBionic Diagnostics + Use Mock Diagnostics: true Visualization Manager: Class: "" Displays: diff --git a/waybionic_rviz_plugins/include/waybionic_rviz_plugins/diagnostics_panel.hpp b/waybionic_rviz_plugins/include/waybionic_rviz_plugins/diagnostics_panel.hpp index 73585fc..4433841 100644 --- a/waybionic_rviz_plugins/include/waybionic_rviz_plugins/diagnostics_panel.hpp +++ b/waybionic_rviz_plugins/include/waybionic_rviz_plugins/diagnostics_panel.hpp @@ -1,19 +1,24 @@ #ifndef WAYBIONIC_RVIZ_PLUGINS__DIAGNOSTICS_PANEL_HPP_ #define WAYBIONIC_RVIZ_PLUGINS__DIAGNOSTICS_PANEL_HPP_ -#include +#include #include +#include #include +#include #include #include #include #include #include +#include +#include #include #include "waybionic_rviz_plugins/diagnostics_contract.hpp" +#include "waybionic_rviz_plugins/diagnostics_source.hpp" #include "waybionic_rviz_plugins/mock_diagnostics_source.hpp" class QButtonGroup; @@ -30,14 +35,21 @@ class DiagnosticsPanel : public rviz_common::Panel explicit DiagnosticsPanel(QWidget * parent = nullptr); void onInitialize() override; + void save(rviz_common::Config config) const override; + void load(const rviz_common::Config & config) override; private: void buildUi(); + void configureSource(bool use_mock_diagnostics); + bool readUseMockDiagnosticsParameter(bool default_value); + std::string readDiagnosticsTopicParameter(const std::string & default_value); void refresh(); - void setDemoMode(DemoMode mode); + void setMockDiagnosticsState(MockDiagnosticsState mode); + void setUseMockDiagnostics(bool use_mock_diagnostics); void updateSystemStatus(const std::vector & messages, const rclcpp::Time & now); void updateTelemetryTable(const std::vector & messages, const rclcpp::Time & now); void updateAlerts(const std::vector & messages); + void updateSourceControls(); void clearAlerts(); QString statusColor(DiagnosticStatus status) const; @@ -46,10 +58,15 @@ class DiagnosticsPanel : public rviz_common::Panel QString optionalText(const std::optional & value) const; QString alertText(const DiagnosticMessage & message) const; - MockDiagnosticsSource diagnostics_source_; + std::unique_ptr diagnostics_source_; + MockDiagnosticsSource * mock_diagnostics_source_{nullptr}; + rclcpp::Node::SharedPtr rviz_node_; rclcpp::Clock clock_{RCL_SYSTEM_TIME}; + std::string diagnostics_topic_{"/diagnostics"}; + bool use_mock_diagnostics_{true}; QTimer * refresh_timer_{nullptr}; + QCheckBox * use_mock_diagnostics_checkbox_{nullptr}; QLabel * state_label_{nullptr}; QLabel * last_updated_label_{nullptr}; QLabel * source_label_{nullptr}; @@ -62,7 +79,7 @@ class DiagnosticsPanel : public rviz_common::Panel QVBoxLayout * alerts_layout_{nullptr}; QPushButton * normal_button_{nullptr}; QPushButton * fault_button_{nullptr}; - QButtonGroup * demo_button_group_{nullptr}; + QButtonGroup * mock_state_button_group_{nullptr}; }; } // namespace waybionic_rviz_plugins diff --git a/waybionic_rviz_plugins/include/waybionic_rviz_plugins/diagnostics_source.hpp b/waybionic_rviz_plugins/include/waybionic_rviz_plugins/diagnostics_source.hpp new file mode 100644 index 0000000..7114626 --- /dev/null +++ b/waybionic_rviz_plugins/include/waybionic_rviz_plugins/diagnostics_source.hpp @@ -0,0 +1,26 @@ +#ifndef WAYBIONIC_RVIZ_PLUGINS__DIAGNOSTICS_SOURCE_HPP_ +#define WAYBIONIC_RVIZ_PLUGINS__DIAGNOSTICS_SOURCE_HPP_ + +#include +#include + +#include + +#include "waybionic_rviz_plugins/diagnostics_contract.hpp" + +namespace waybionic_rviz_plugins +{ + +class DiagnosticsSource +{ +public: + virtual ~DiagnosticsSource() = default; + + virtual std::string sourceName() const = 0; + virtual std::string connectionStatus(const rclcpp::Time & now) const = 0; + virtual std::vector messages(const rclcpp::Time & now) const = 0; +}; + +} // namespace waybionic_rviz_plugins + +#endif // WAYBIONIC_RVIZ_PLUGINS__DIAGNOSTICS_SOURCE_HPP_ diff --git a/waybionic_rviz_plugins/include/waybionic_rviz_plugins/mock_diagnostics_source.hpp b/waybionic_rviz_plugins/include/waybionic_rviz_plugins/mock_diagnostics_source.hpp index 593b360..ac94245 100644 --- a/waybionic_rviz_plugins/include/waybionic_rviz_plugins/mock_diagnostics_source.hpp +++ b/waybionic_rviz_plugins/include/waybionic_rviz_plugins/mock_diagnostics_source.hpp @@ -7,30 +7,32 @@ #include #include "waybionic_rviz_plugins/diagnostics_contract.hpp" +#include "waybionic_rviz_plugins/diagnostics_source.hpp" namespace waybionic_rviz_plugins { -enum class DemoMode +enum class MockDiagnosticsState { Normal, Fault }; -class MockDiagnosticsSource +class MockDiagnosticsSource : public DiagnosticsSource { public: - void setMode(DemoMode mode); - DemoMode mode() const; - std::string sourceName() const; + void setMode(MockDiagnosticsState mode); + MockDiagnosticsState mode() const; + std::string sourceName() const override; + std::string connectionStatus(const rclcpp::Time & now) const override; - std::vector messages(const rclcpp::Time & now) const; + std::vector messages(const rclcpp::Time & now) const override; private: std::vector normalMessages(const rclcpp::Time & now) const; std::vector faultMessages(const rclcpp::Time & now) const; - DemoMode mode_{DemoMode::Normal}; + MockDiagnosticsState mode_{MockDiagnosticsState::Normal}; }; } // namespace waybionic_rviz_plugins diff --git a/waybionic_rviz_plugins/include/waybionic_rviz_plugins/ros_diagnostics_source.hpp b/waybionic_rviz_plugins/include/waybionic_rviz_plugins/ros_diagnostics_source.hpp new file mode 100644 index 0000000..5101ba3 --- /dev/null +++ b/waybionic_rviz_plugins/include/waybionic_rviz_plugins/ros_diagnostics_source.hpp @@ -0,0 +1,45 @@ +#ifndef WAYBIONIC_RVIZ_PLUGINS__ROS_DIAGNOSTICS_SOURCE_HPP_ +#define WAYBIONIC_RVIZ_PLUGINS__ROS_DIAGNOSTICS_SOURCE_HPP_ + +#include +#include +#include + +#include +#include + +#include "waybionic_rviz_plugins/diagnostics_source.hpp" + +namespace waybionic_rviz_plugins +{ + +class RosDiagnosticsSource : public DiagnosticsSource +{ +public: + explicit RosDiagnosticsSource( + rclcpp::Node::SharedPtr node, + std::string diagnostics_topic = "/diagnostics"); + + std::string sourceName() const override; + std::string connectionStatus(const rclcpp::Time & now) const override; + std::vector messages(const rclcpp::Time & now) const override; + +private: + void diagnosticsCallback(diagnostic_msgs::msg::DiagnosticArray::SharedPtr message); + DiagnosticMessage toDiagnosticMessage( + const diagnostic_msgs::msg::DiagnosticStatus & status, + const rclcpp::Time & timestamp) const; + + rclcpp::Node::SharedPtr node_; + std::string diagnostics_topic_; + rclcpp::Subscription::SharedPtr subscription_; + + mutable std::mutex mutex_; + std::vector latest_messages_; + rclcpp::Time last_received_time_{0, 0, RCL_SYSTEM_TIME}; + bool has_received_{false}; +}; + +} // namespace waybionic_rviz_plugins + +#endif // WAYBIONIC_RVIZ_PLUGINS__ROS_DIAGNOSTICS_SOURCE_HPP_ diff --git a/waybionic_rviz_plugins/src/diagnostics_panel.cpp b/waybionic_rviz_plugins/src/diagnostics_panel.cpp index 9707f23..bc4eefd 100644 --- a/waybionic_rviz_plugins/src/diagnostics_panel.cpp +++ b/waybionic_rviz_plugins/src/diagnostics_panel.cpp @@ -2,24 +2,31 @@ #include #include +#include #include #include #include #include #include +#include #include #include #include #include #include #include +#include #include #include #include #include #include +#include +#include + +#include "waybionic_rviz_plugins/ros_diagnostics_source.hpp" namespace waybionic_rviz_plugins { @@ -115,16 +122,44 @@ DiagnosticsPanel::DiagnosticsPanel(QWidget * parent) : rviz_common::Panel(parent) { buildUi(); + configureSource(use_mock_diagnostics_); } void DiagnosticsPanel::onInitialize() { + if (auto ros_node_abstraction = getDisplayContext()->getRosNodeAbstraction().lock()) { + rviz_node_ = ros_node_abstraction->get_raw_node(); + } + + diagnostics_topic_ = readDiagnosticsTopicParameter(diagnostics_topic_); + configureSource(readUseMockDiagnosticsParameter(use_mock_diagnostics_)); refresh_timer_ = new QTimer(this); connect(refresh_timer_, &QTimer::timeout, this, [this]() { refresh(); }); refresh_timer_->start(1000); refresh(); } +void DiagnosticsPanel::save(rviz_common::Config config) const +{ + rviz_common::Panel::save(config); + config.mapSetValue("Use Mock Diagnostics", use_mock_diagnostics_); + config.mapSetValue("Diagnostics Topic", QString::fromStdString(diagnostics_topic_)); +} + +void DiagnosticsPanel::load(const rviz_common::Config & config) +{ + rviz_common::Panel::load(config); + + bool use_mock_diagnostics = use_mock_diagnostics_; + QString diagnostics_topic; + if (config.mapGetString("Diagnostics Topic", &diagnostics_topic)) { + diagnostics_topic_ = diagnostics_topic.toStdString(); + } + if (config.mapGetBool("Use Mock Diagnostics", &use_mock_diagnostics)) { + configureSource(use_mock_diagnostics); + } +} + void DiagnosticsPanel::buildUi() { setStyleSheet(kPanelStyle); @@ -143,24 +178,37 @@ void DiagnosticsPanel::buildUi() state_label_->setObjectName("StateNormal"); last_updated_label_ = makeMuted("Last updated: --"); + use_mock_diagnostics_checkbox_ = new QCheckBox("Use Mock Diagnostics"); + use_mock_diagnostics_checkbox_->setChecked(use_mock_diagnostics_); + use_mock_diagnostics_checkbox_->setToolTip( + "Checked: local mock validation states. Unchecked: subscribe to live ROS 2 diagnostics."); + connect(use_mock_diagnostics_checkbox_, &QCheckBox::toggled, this, [this](const bool checked) { + setUseMockDiagnostics(checked); + }); + auto * button_row = new QHBoxLayout(); - normal_button_ = new QPushButton("Normal Demo"); + normal_button_ = new QPushButton("Mock Normal"); normal_button_->setCheckable(true); normal_button_->setChecked(true); - fault_button_ = new QPushButton("Fault Demo"); + fault_button_ = new QPushButton("Mock Fault"); fault_button_->setCheckable(true); - demo_button_group_ = new QButtonGroup(this); - demo_button_group_->setExclusive(true); - demo_button_group_->addButton(normal_button_); - demo_button_group_->addButton(fault_button_); - connect(normal_button_, &QPushButton::clicked, this, [this]() { setDemoMode(DemoMode::Normal); }); - connect(fault_button_, &QPushButton::clicked, this, [this]() { setDemoMode(DemoMode::Fault); }); + mock_state_button_group_ = new QButtonGroup(this); + mock_state_button_group_->setExclusive(true); + mock_state_button_group_->addButton(normal_button_); + mock_state_button_group_->addButton(fault_button_); + connect(normal_button_, &QPushButton::clicked, this, [this]() { + setMockDiagnosticsState(MockDiagnosticsState::Normal); + }); + connect(fault_button_, &QPushButton::clicked, this, [this]() { + setMockDiagnosticsState(MockDiagnosticsState::Fault); + }); button_row->addWidget(normal_button_); button_row->addWidget(fault_button_); header_layout->addWidget(title); + header_layout->addWidget(use_mock_diagnostics_checkbox_); header_layout->addLayout(button_row); header_layout->addWidget(state_label_); header_layout->addWidget(last_updated_label_); @@ -172,7 +220,7 @@ void DiagnosticsPanel::buildUi() system_layout->addWidget(makeTitle("System Status"), 0, 0, 1, 2); source_label_ = new QLabel("Mock"); - ros_connection_label_ = new QLabel("Not connected / Future integration"); + ros_connection_label_ = new QLabel("Local mock diagnostics"); heartbeat_label_ = new QLabel("OK"); ui_mode_label_ = new QLabel("Monitoring only"); safety_label_ = new QLabel("No motor commands sent from this RViz panel"); @@ -224,18 +272,70 @@ void DiagnosticsPanel::buildUi() root_layout->addWidget(alerts_card); } +void DiagnosticsPanel::configureSource(const bool use_mock_diagnostics) +{ + use_mock_diagnostics_ = use_mock_diagnostics; + mock_diagnostics_source_ = nullptr; + + if (use_mock_diagnostics_ || !rviz_node_) { + auto mock_source = std::make_unique(); + mock_diagnostics_source_ = mock_source.get(); + diagnostics_source_ = std::move(mock_source); + } else { + diagnostics_source_ = std::make_unique(rviz_node_, diagnostics_topic_); + } + + updateSourceControls(); +} + +bool DiagnosticsPanel::readUseMockDiagnosticsParameter(const bool default_value) +{ + if (!rviz_node_) { + return default_value; + } + + if (!rviz_node_->has_parameter("use_mock_diagnostics")) { + return rviz_node_->declare_parameter("use_mock_diagnostics", default_value); + } + + return rviz_node_->get_parameter("use_mock_diagnostics").as_bool(); +} + +std::string DiagnosticsPanel::readDiagnosticsTopicParameter(const std::string & default_value) +{ + if (!rviz_node_) { + return default_value; + } + + if (!rviz_node_->has_parameter("diagnostics_topic")) { + return rviz_node_->declare_parameter("diagnostics_topic", default_value); + } + + return rviz_node_->get_parameter("diagnostics_topic").as_string(); +} + void DiagnosticsPanel::refresh() { const auto now = clock_.now(); - const auto messages = diagnostics_source_.messages(now); + const auto messages = diagnostics_source_->messages(now); updateSystemStatus(messages, now); updateTelemetryTable(messages, now); updateAlerts(messages); } -void DiagnosticsPanel::setDemoMode(const DemoMode mode) +void DiagnosticsPanel::setMockDiagnosticsState(const MockDiagnosticsState mode) +{ + if (mock_diagnostics_source_ == nullptr) { + return; + } + + mock_diagnostics_source_->setMode(mode); + refresh(); +} + +void DiagnosticsPanel::setUseMockDiagnostics(const bool use_mock_diagnostics) { - diagnostics_source_.setMode(mode); + configureSource(use_mock_diagnostics); refresh(); } @@ -252,12 +352,20 @@ void DiagnosticsPanel::updateSystemStatus( return message.status == DiagnosticStatus::Stale; }); - state_label_->setText(has_alert ? "Current State: FAULT" : "Current State: NORMAL"); + const bool waiting_for_live_diagnostics = + !use_mock_diagnostics_ && messages.size() == 1 && messages.front().signal_name == "diagnostics.topic"; + + if (waiting_for_live_diagnostics) { + state_label_->setText("Current State: WAITING FOR LIVE DIAGNOSTICS"); + } else { + state_label_->setText(has_alert ? "Current State: FAULT" : "Current State: NORMAL"); + } state_label_->setObjectName(has_alert ? "StateFault" : "StateNormal"); state_label_->style()->unpolish(state_label_); state_label_->style()->polish(state_label_); - source_label_->setText(QString::fromStdString(diagnostics_source_.sourceName())); + source_label_->setText(QString::fromStdString(diagnostics_source_->sourceName())); + ros_connection_label_->setText(QString::fromStdString(diagnostics_source_->connectionStatus(now))); heartbeat_label_->setText(has_stale ? "STALE" : "OK"); heartbeat_label_->setStyleSheet(QString("color: %1; font-weight: 800;").arg(has_stale ? "#9aa4ad" : "#3ddc84")); safety_label_->setStyleSheet(QString("color: %1; font-weight: 700;").arg(has_alert ? "#ff4d5e" : "#8ea3b1")); @@ -274,6 +382,22 @@ void DiagnosticsPanel::updateSystemStatus( last_updated_label_->setText(QString("Last updated: %1s ago").arg(latest_age, 0, 'f', 1)); } +void DiagnosticsPanel::updateSourceControls() +{ + if (use_mock_diagnostics_checkbox_ != nullptr) { + const QSignalBlocker blocker(use_mock_diagnostics_checkbox_); + use_mock_diagnostics_checkbox_->setChecked(use_mock_diagnostics_); + } + + const bool mock_enabled = mock_diagnostics_source_ != nullptr; + if (normal_button_ != nullptr) { + normal_button_->setEnabled(mock_enabled); + } + if (fault_button_ != nullptr) { + fault_button_->setEnabled(mock_enabled); + } +} + void DiagnosticsPanel::updateTelemetryTable( const std::vector & messages, const rclcpp::Time & now) diff --git a/waybionic_rviz_plugins/src/mock_diagnostics_source.cpp b/waybionic_rviz_plugins/src/mock_diagnostics_source.cpp index 1f8bed2..54b2e58 100644 --- a/waybionic_rviz_plugins/src/mock_diagnostics_source.cpp +++ b/waybionic_rviz_plugins/src/mock_diagnostics_source.cpp @@ -27,12 +27,12 @@ std::string formatNumber(const double value, const int precision) } // namespace -void MockDiagnosticsSource::setMode(const DemoMode mode) +void MockDiagnosticsSource::setMode(const MockDiagnosticsState mode) { mode_ = mode; } -DemoMode MockDiagnosticsSource::mode() const +MockDiagnosticsState MockDiagnosticsSource::mode() const { return mode_; } @@ -42,9 +42,14 @@ std::string MockDiagnosticsSource::sourceName() const return "Mock"; } +std::string MockDiagnosticsSource::connectionStatus(const rclcpp::Time & /*now*/) const +{ + return "Local mock diagnostics"; +} + std::vector MockDiagnosticsSource::messages(const rclcpp::Time & now) const { - if (mode_ == DemoMode::Fault) { + if (mode_ == MockDiagnosticsState::Fault) { return faultMessages(now); } return normalMessages(now); diff --git a/waybionic_rviz_plugins/src/ros_diagnostics_source.cpp b/waybionic_rviz_plugins/src/ros_diagnostics_source.cpp new file mode 100644 index 0000000..a16898a --- /dev/null +++ b/waybionic_rviz_plugins/src/ros_diagnostics_source.cpp @@ -0,0 +1,174 @@ +#include "waybionic_rviz_plugins/ros_diagnostics_source.hpp" + +#include +#include +#include +#include +#include +#include + +#include + +namespace waybionic_rviz_plugins +{ +namespace +{ + +constexpr double kStaleAfterSeconds = 5.0; + +DiagnosticStatus mapLevel(const unsigned char level) +{ + switch (level) { + case diagnostic_msgs::msg::DiagnosticStatus::OK: + return DiagnosticStatus::Ok; + case diagnostic_msgs::msg::DiagnosticStatus::WARN: + return DiagnosticStatus::Warn; + case diagnostic_msgs::msg::DiagnosticStatus::ERROR: + return DiagnosticStatus::Fault; + case diagnostic_msgs::msg::DiagnosticStatus::STALE: + return DiagnosticStatus::Stale; + default: + return DiagnosticStatus::Warn; + } +} + +std::string lowerCopy(std::string value) +{ + std::transform(value.begin(), value.end(), value.begin(), [](const unsigned char character) { + return static_cast(std::tolower(character)); + }); + return value; +} + +bool hasContent(const std::string & value) +{ + return !value.empty(); +} + +} // namespace + +RosDiagnosticsSource::RosDiagnosticsSource( + rclcpp::Node::SharedPtr node, + std::string diagnostics_topic) +: node_(std::move(node)), + diagnostics_topic_(std::move(diagnostics_topic)) +{ + subscription_ = node_->create_subscription( + diagnostics_topic_, + rclcpp::QoS(10), + [this](diagnostic_msgs::msg::DiagnosticArray::SharedPtr message) { + diagnosticsCallback(std::move(message)); + }); +} + +std::string RosDiagnosticsSource::sourceName() const +{ + return "ROS " + diagnostics_topic_; +} + +std::string RosDiagnosticsSource::connectionStatus(const rclcpp::Time & now) const +{ + std::lock_guard lock(mutex_); + if (!has_received_) { + return "Waiting for " + diagnostics_topic_; + } + + const double age_seconds = std::max(0.0, (now - last_received_time_).seconds()); + if (age_seconds > kStaleAfterSeconds) { + return "No recent messages on " + diagnostics_topic_; + } + + return "Connected to " + diagnostics_topic_; +} + +std::vector RosDiagnosticsSource::messages(const rclcpp::Time & now) const +{ + std::lock_guard lock(mutex_); + if (!has_received_) { + return {{ + "diagnostics.topic", + DiagnosticStatus::Stale, + now, + std::nullopt, + std::nullopt, + "Live diagnostics mode active; waiting for " + diagnostics_topic_ + " messages", + }}; + } + + auto messages = latest_messages_; + const double age_seconds = std::max(0.0, (now - last_received_time_).seconds()); + if (age_seconds <= kStaleAfterSeconds) { + return messages; + } + + for (auto & message : messages) { + if (message.status == DiagnosticStatus::Ok || message.status == DiagnosticStatus::Warn) { + message.status = DiagnosticStatus::Stale; + message.alert_message = "No recent update from " + diagnostics_topic_; + } + } + return messages; +} + +void RosDiagnosticsSource::diagnosticsCallback( + diagnostic_msgs::msg::DiagnosticArray::SharedPtr message) +{ + rclcpp::Clock system_clock(RCL_SYSTEM_TIME); + const auto received_at = system_clock.now(); + rclcpp::Time timestamp(message->header.stamp, RCL_SYSTEM_TIME); + if (timestamp.nanoseconds() == 0) { + timestamp = received_at; + } + + std::vector normalized_messages; + normalized_messages.reserve(message->status.size()); + for (const auto & status : message->status) { + normalized_messages.push_back(toDiagnosticMessage(status, timestamp)); + } + + std::lock_guard lock(mutex_); + latest_messages_ = std::move(normalized_messages); + last_received_time_ = received_at; + has_received_ = true; +} + +DiagnosticMessage RosDiagnosticsSource::toDiagnosticMessage( + const diagnostic_msgs::msg::DiagnosticStatus & status, + const rclcpp::Time & timestamp) const +{ + std::optional value; + std::optional unit; + + for (const auto & key_value : status.values) { + const auto key = lowerCopy(key_value.key); + if (key == "value") { + value = key_value.value; + continue; + } + if (key == "unit") { + unit = key_value.value; + continue; + } + if (!value.has_value() && hasContent(key_value.value)) { + value = key_value.value; + unit = key_value.key; + } + } + + const auto normalized_status = mapLevel(status.level); + std::optional alert_message; + if (hasContent(status.message) && normalized_status != DiagnosticStatus::Ok) { + alert_message = status.message; + } + + return { + status.name, + normalized_status, + timestamp, + value, + unit, + alert_message, + }; +} + +} // namespace waybionic_rviz_plugins From 1eeaf9a7fb3ec97f429a0664ffeb0d6433f1b556 Mon Sep 17 00:00:00 2001 From: Khuzaymah Bin Haris Date: Sat, 13 Jun 2026 14:10:31 -0600 Subject: [PATCH 15/19] Add SurgeonCameraPanel placeholder for doctor/surgeon camera layout. Register the second RViz panel, dock it in doctor_camera_view.rviz, and show placeholder topics with a waiting-for-feed status. Co-authored-by: Cursor --- waybionic_rviz_plugins/CMakeLists.txt | 2 + .../config/doctor_camera_view.rviz | 32 ++-- .../surgeon_camera_panel.hpp | 37 ++++ waybionic_rviz_plugins/plugin_description.xml | 8 + .../src/surgeon_camera_panel.cpp | 163 ++++++++++++++++++ 5 files changed, 232 insertions(+), 10 deletions(-) create mode 100644 waybionic_rviz_plugins/include/waybionic_rviz_plugins/surgeon_camera_panel.hpp create mode 100644 waybionic_rviz_plugins/src/surgeon_camera_panel.cpp diff --git a/waybionic_rviz_plugins/CMakeLists.txt b/waybionic_rviz_plugins/CMakeLists.txt index 9779b71..aadfe53 100644 --- a/waybionic_rviz_plugins/CMakeLists.txt +++ b/waybionic_rviz_plugins/CMakeLists.txt @@ -30,9 +30,11 @@ add_library(${PROJECT_NAME} SHARED include/waybionic_rviz_plugins/diagnostics_source.hpp include/waybionic_rviz_plugins/diagnostics_panel.hpp include/waybionic_rviz_plugins/ros_diagnostics_source.hpp + include/waybionic_rviz_plugins/surgeon_camera_panel.hpp src/diagnostics_panel.cpp src/mock_diagnostics_source.cpp src/ros_diagnostics_source.cpp + src/surgeon_camera_panel.cpp ) target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17) diff --git a/waybionic_rviz_plugins/config/doctor_camera_view.rviz b/waybionic_rviz_plugins/config/doctor_camera_view.rviz index 422f08e..ad9fc4c 100644 --- a/waybionic_rviz_plugins/config/doctor_camera_view.rviz +++ b/waybionic_rviz_plugins/config/doctor_camera_view.rviz @@ -1,6 +1,15 @@ Panels: - Class: rviz_common/Displays + Help Height: 70 Name: Displays + Property Tree Widget: + Expanded: ~ + Splitter Ratio: 0.5 + Tree Height: 280 + - Class: waybionic_rviz_plugins/SurgeonCameraPanel + Name: WayBionic Surgeon Camera + Primary Camera Topic: /camera/camera/color/image_raw + Secondary Camera Topic: /surgeon/secondary/image_raw Visualization Manager: Class: "" Displays: @@ -19,7 +28,7 @@ Visualization Manager: Value: /camera/camera/color/image_raw Value: true - Class: rviz_default_plugins/Image - Enabled: false + Enabled: true Max Value: 1 Median window: 5 Min Value: 0 @@ -31,7 +40,7 @@ Visualization Manager: History Policy: Keep Last Reliability Policy: Reliable Value: /surgeon/secondary/image_raw - Value: false + Value: true Enabled: true Global Options: Background Color: 0; 0; 0 @@ -52,7 +61,7 @@ Visualization Manager: Class: rviz_default_plugins/Orbit Distance: 1 Enable Stereo Rendering: - Stereo Eye Separation: 0.06 + Stereo Eye Separation: 0.05999999865889549 Stereo Focal Distance: 1 Swap Stereo Eyes: false Value: false @@ -61,14 +70,14 @@ Visualization Manager: Y: 0 Z: 0 Focal Shape Fixed Size: true - Focal Shape Size: 0.05 + Focal Shape Size: 0.05000000074505806 Invert Z Axis: false Name: Current View - Near Clip Distance: 0.01 - Pitch: 0 + Near Clip Distance: 0.009999999776482582 + Pitch: 0.28479644656181335 Target Frame: Value: Orbit (rviz) - Yaw: 0 + Yaw: 4.648182392120361 Saved: ~ Window Geometry: Displays: @@ -76,10 +85,13 @@ Window Geometry: Height: 995 Hide Left Dock: false Hide Right Dock: true + QMainWindow State: 000000ff00000000fd0000000100000000000001560000038dfc0200000003fb0000004800530075007200670065006f006e0020005300650063006f006e0064006100720079002000430061006d00650072006100200050006c0061006300650068006f006c006400650072010000003b000000f40000001600fffffffb0000002c00530075007200670065006f006e0020005000720069006d006100720079002000430061006d0065007200610100000135000000f40000001600fffffffb000000100044006900730070006c006100790073010000022f00000199000000c700ffffff000006240000038d00000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 Surgeon Primary Camera: collapsed: false + WayBionic Surgeon Camera: + collapsed: false Surgeon Secondary Camera Placeholder: - collapsed: true + collapsed: false Width: 1920 - X: 0 - Y: 0 + X: -32 + Y: -32 diff --git a/waybionic_rviz_plugins/include/waybionic_rviz_plugins/surgeon_camera_panel.hpp b/waybionic_rviz_plugins/include/waybionic_rviz_plugins/surgeon_camera_panel.hpp new file mode 100644 index 0000000..93ac92d --- /dev/null +++ b/waybionic_rviz_plugins/include/waybionic_rviz_plugins/surgeon_camera_panel.hpp @@ -0,0 +1,37 @@ +#ifndef WAYBIONIC_RVIZ_PLUGINS__SURGEON_CAMERA_PANEL_HPP_ +#define WAYBIONIC_RVIZ_PLUGINS__SURGEON_CAMERA_PANEL_HPP_ + +#include + +#include +#include + +class QLabel; + +namespace waybionic_rviz_plugins +{ + +class SurgeonCameraPanel : public rviz_common::Panel +{ + Q_OBJECT + +public: + explicit SurgeonCameraPanel(QWidget * parent = nullptr); + + void save(rviz_common::Config config) const override; + void load(const rviz_common::Config & config) override; + +private: + void buildUi(); + void refreshLabels(); + + QString primary_topic_{"/camera/camera/color/image_raw"}; + QString secondary_topic_{"/surgeon/secondary/image_raw"}; + + QLabel * primary_topic_label_{nullptr}; + QLabel * secondary_topic_label_{nullptr}; +}; + +} // namespace waybionic_rviz_plugins + +#endif // WAYBIONIC_RVIZ_PLUGINS__SURGEON_CAMERA_PANEL_HPP_ diff --git a/waybionic_rviz_plugins/plugin_description.xml b/waybionic_rviz_plugins/plugin_description.xml index faa00a6..7a5a31b 100644 --- a/waybionic_rviz_plugins/plugin_description.xml +++ b/waybionic_rviz_plugins/plugin_description.xml @@ -7,4 +7,12 @@ WayBionic ground station diagnostics, telemetry, and alerts panel for RViz2. + + + WayBionic surgeon camera placeholder panel for RViz2. + + diff --git a/waybionic_rviz_plugins/src/surgeon_camera_panel.cpp b/waybionic_rviz_plugins/src/surgeon_camera_panel.cpp new file mode 100644 index 0000000..a1d0f73 --- /dev/null +++ b/waybionic_rviz_plugins/src/surgeon_camera_panel.cpp @@ -0,0 +1,163 @@ +#include "waybionic_rviz_plugins/surgeon_camera_panel.hpp" + +#include +#include +#include +#include + +#include + +namespace waybionic_rviz_plugins +{ +namespace +{ + +constexpr const char * kPanelStyle = R"( +QWidget { + background-color: #071019; + color: #e8f1f8; + font-family: "Segoe UI", "Ubuntu", sans-serif; + font-size: 12px; +} +QFrame#Card { + background-color: #0d1722; + border: 1px solid #284052; + border-radius: 8px; +} +QLabel#PanelTitle { + font-size: 15px; + font-weight: 700; +} +QLabel#StatusWaiting { + background-color: rgba(255, 176, 32, 0.16); + border: 1px solid #ffb020; + border-radius: 6px; + color: #ffb020; + font-weight: 800; + padding: 6px; +} +QLabel#Muted { + color: #8ea3b1; +} +)"; + +QLabel * makeTitle(const QString & text) +{ + auto * label = new QLabel(text); + label->setObjectName("PanelTitle"); + return label; +} + +QLabel * makeMuted(const QString & text) +{ + auto * label = new QLabel(text); + label->setObjectName("Muted"); + return label; +} + +QFrame * makeCard() +{ + auto * card = new QFrame(); + card->setObjectName("Card"); + return card; +} + +} // namespace + +SurgeonCameraPanel::SurgeonCameraPanel(QWidget * parent) +: rviz_common::Panel(parent) +{ + buildUi(); + refreshLabels(); +} + +void SurgeonCameraPanel::save(rviz_common::Config config) const +{ + rviz_common::Panel::save(config); + config.mapSetValue("Primary Camera Topic", primary_topic_); + config.mapSetValue("Secondary Camera Topic", secondary_topic_); +} + +void SurgeonCameraPanel::load(const rviz_common::Config & config) +{ + rviz_common::Panel::load(config); + + QString primary_topic; + if (config.mapGetString("Primary Camera Topic", &primary_topic)) { + primary_topic_ = primary_topic; + } + + QString secondary_topic; + if (config.mapGetString("Secondary Camera Topic", &secondary_topic)) { + secondary_topic_ = secondary_topic; + } + + refreshLabels(); +} + +void SurgeonCameraPanel::buildUi() +{ + setStyleSheet(kPanelStyle); + setMinimumWidth(360); + + auto * root_layout = new QVBoxLayout(this); + root_layout->setContentsMargins(10, 10, 10, 10); + root_layout->setSpacing(10); + + auto * header_card = makeCard(); + auto * header_layout = new QVBoxLayout(header_card); + header_layout->setSpacing(8); + + header_layout->addWidget(makeTitle("WayBionic Surgeon Camera Placeholder")); + + auto * status_label = new QLabel("Waiting for camera feed"); + status_label->setObjectName("StatusWaiting"); + header_layout->addWidget(status_label); + + auto * scope_label = makeMuted( + "Placeholder layout only. Camera hardware, drivers, and final ROS topics are not selected yet."); + scope_label->setWordWrap(true); + header_layout->addWidget(scope_label); + + root_layout->addWidget(header_card); + + auto * topics_card = makeCard(); + auto * topics_layout = new QGridLayout(topics_card); + topics_layout->setVerticalSpacing(6); + topics_layout->addWidget(makeTitle("Configured Placeholder Topics"), 0, 0, 1, 2); + + primary_topic_label_ = new QLabel(); + secondary_topic_label_ = new QLabel(); + + topics_layout->addWidget(makeMuted("Primary Camera"), 1, 0); + topics_layout->addWidget(primary_topic_label_, 1, 1); + topics_layout->addWidget(makeMuted("Secondary Camera"), 2, 0); + topics_layout->addWidget(secondary_topic_label_, 2, 1); + root_layout->addWidget(topics_card); + + auto * safety_card = makeCard(); + auto * safety_layout = new QVBoxLayout(safety_card); + safety_layout->addWidget(makeTitle("Scope")); + + auto * safety_label = makeMuted( + "Monitoring-only panel. It does not subscribe to images directly, start camera nodes, or send motor commands."); + safety_label->setWordWrap(true); + safety_layout->addWidget(safety_label); + root_layout->addWidget(safety_card); + + root_layout->addStretch(1); +} + +void SurgeonCameraPanel::refreshLabels() +{ + if (primary_topic_label_ != nullptr) { + primary_topic_label_->setText(primary_topic_); + } + if (secondary_topic_label_ != nullptr) { + secondary_topic_label_->setText(secondary_topic_); + } +} + +} // namespace waybionic_rviz_plugins + +PLUGINLIB_EXPORT_CLASS(waybionic_rviz_plugins::SurgeonCameraPanel, rviz_common::Panel) From 9992aca4361850699f9a64e42ec40556923d237e Mon Sep 17 00:00:00 2001 From: Khuzaymah Bin Haris Date: Sat, 13 Jun 2026 14:10:40 -0600 Subject: [PATCH 16/19] Document generic launch flow, mock/live diagnostics, and review notes. Update package README and docs for the decoupled AR4 helper, SurgeonCameraPanel, and live /diagnostics integration path. Co-authored-by: Cursor --- waybionic_rviz_plugins/README.md | 238 +++++++++--------- .../docs/DIAGNOSTICS_CONTRACT.md | 51 ++-- .../docs/GROUND_STATION_RVIZ_UI.md | 129 ++++------ waybionic_rviz_plugins/docs/PR_NOTES.md | 106 ++++++++ 4 files changed, 316 insertions(+), 208 deletions(-) create mode 100644 waybionic_rviz_plugins/docs/PR_NOTES.md diff --git a/waybionic_rviz_plugins/README.md b/waybionic_rviz_plugins/README.md index e5dd759..5fb9716 100644 --- a/waybionic_rviz_plugins/README.md +++ b/waybionic_rviz_plugins/README.md @@ -1,175 +1,181 @@ -# WayBionic Ground Station UI (RViz2) +# WayBionic RViz2 Diagnostics Plugin -RViz2-native ground station interface for WayBionic. This package provides two operator views inside RViz2: +RViz2-native diagnostics and monitoring UI for WayBionic. This package is a generic foundation plugin: it does not require Annin/AR4 packages for the default engineer or doctor views. -- **Doctor / Surgeon Camera View** — camera feeds only, no engineering telemetry. -- **Engineer View** — robot visualization plus diagnostics, telemetry, live values, and alerts. - -This is the V2 direction. The archived standalone PySide6 prototype lives in `ground_station_monitoring_ui_archived/`. - -## Scope - -Monitoring-first only: +Monitoring-only scope: - No motor commands from this package. - No robot control or safety-critical logic. -- No camera driver implementation in this sprint. -- No RViz source-code modifications. - -## Prerequisites - -- ROS 2 Jazzy on Ubuntu 24.04 (WSL or native Linux). -- An existing colcon workspace with the AR4 packages built, for example `~/ar4_ws`. -- RViz2 and Qt development packages (installed via `rosdep`). - -Tested workflow uses WSL Ubuntu with an `ar4_ws` workspace that already contains `annin_ar4_moveit_config`, `annin_ar4_description`, and related AR4 packages. - -## Add This Package To Your Workspace - -If this repo is checked out on Windows and you build from WSL, symlink the package into your ROS workspace `src/`: +- No camera driver or camera pipeline implementation. +- Mock diagnostics for validation while backend `/diagnostics` is not ready. -```bash -ln -s "/mnt/c/Users//OneDrive/Desktop/Uni Work/Clubs/WayBionic/waybionic_ground_station/waybionic_ground_station/waybionic_rviz_plugins" \ - ~/ar4_ws/src/waybionic_rviz_plugins -``` - -If you clone the repo directly inside WSL, copy or symlink `waybionic_rviz_plugins/` into `~/ar4_ws/src/` instead. - -## Build +## Quickstart -From your ROS workspace (same style as the existing AR4 demo): +Use this package from any ROS 2 Jazzy colcon workspace: ```bash -cd ~/ar4_ws +cd source /opt/ros/jazzy/setup.bash rosdep install --from-paths src --ignore-src -r -y colcon build --packages-select waybionic_rviz_plugins --symlink-install source install/setup.bash +ros2 launch waybionic_rviz_plugins engineer_view.launch.py +ros2 launch waybionic_rviz_plugins doctor_view.launch.py ``` -After editing files in this package, rebuild with the same `colcon build` command and re-source `install/setup.bash`. +## Package Layout -## Run +```text +waybionic_rviz_plugins/ + CMakeLists.txt # Builds the shared RViz plugin library + package.xml # Generic deps only; no Annin/AR4 exec deps + plugin_description.xml # Registers DiagnosticsPanel + SurgeonCameraPanel + include/waybionic_rviz_plugins/ + diagnostics_contract.hpp # Normalized DiagnosticMessage model + diagnostics_source.hpp # DiagnosticsSource interface + diagnostics_panel.hpp # Engineer monitoring panel + mock_diagnostics_source.hpp + ros_diagnostics_source.hpp + surgeon_camera_panel.hpp # Doctor/surgeon placeholder panel + src/ + diagnostics_panel.cpp + mock_diagnostics_source.cpp + ros_diagnostics_source.cpp + surgeon_camera_panel.cpp + config/ + engineer_monitoring_view.rviz # Generic engineer layout (default) + doctor_camera_view.rviz # Doctor/surgeon placeholder layout + engineer_ar4_demo.rviz # Optional AR4 visualization helper + launch/ + engineer_view.launch.py # Generic engineer launch (default) + doctor_view.launch.py # Doctor/surgeon placeholder launch + engineer_ar4_demo.launch.py # Optional AR4 helper only + docs/ + DIAGNOSTICS_CONTRACT.md # Backend /diagnostics mapping contract + GROUND_STATION_RVIZ_UI.md # Extended architecture notes + PR_NOTES.md # Review/PR summary +``` -### Engineer monitoring view +### What each launch/config pair does -```bash -cd ~/ar4_ws -source /opt/ros/jazzy/setup.bash -source install/setup.bash -ros2 launch waybionic_rviz_plugins engineer_view.launch.py -``` +| Launch | RViz config | Purpose | +|--------|-------------|---------| +| `engineer_view.launch.py` | `engineer_monitoring_view.rviz` | Generic engineer monitoring; no AR4 | +| `doctor_view.launch.py` | `doctor_camera_view.rviz` | Surgeon camera placeholder layout | +| `engineer_ar4_demo.launch.py` | `engineer_ar4_demo.rviz` | Optional passive AR4 robot viz | -Optional arguments: +Core plugin dependencies are ROS/RViz/Qt only (`rclcpp`, `rviz_common`, `diagnostic_msgs`, etc.). AR4/Annin packages are required only for the optional `engineer_ar4_demo` helper. -```bash -ros2 launch waybionic_rviz_plugins engineer_view.launch.py ar_model:=mk3 include_gripper:=True -``` +## RViz Panels -This launches: +Both panels appear under **Panels → Add Panel → `waybionic_rviz_plugins`**: -- `robot_state_publisher` and `joint_state_publisher` for passive robot visualization. -- RViz2 with `config/engineer_monitoring_view.rviz`. -- The docked `WayBionic Diagnostics` panel with mock Normal/Fault demo modes. +| Panel | Role | +|-------|------| +| `DiagnosticsPanel` | Engineer monitoring: telemetry table, alerts, mock/live diagnostics | +| `SurgeonCameraPanel` | Doctor/surgeon placeholder: topic names, waiting state, scope notes | -It does **not** start the hardware driver or send motor commands. +Saved launch layouts are still the recommended way to open a clean engineer or doctor view. Panels can also be docked manually in one RViz session. -### Doctor / surgeon camera view +## Engineer View ```bash -cd ~/ar4_ws -source /opt/ros/jazzy/setup.bash -source install/setup.bash -ros2 launch waybionic_rviz_plugins doctor_view.launch.py +ros2 launch waybionic_rviz_plugins engineer_view.launch.py ``` -This opens `config/doctor_camera_view.rviz` with image displays only. - -## Demo Modes (Engineer Panel) +Opens `config/engineer_monitoring_view.rviz` with the docked `WayBionic Diagnostics` panel. Does not launch Annin/AR4 packages or robot publishers. -Use the panel buttons: +### Mock vs live diagnostics -- **Normal Demo** — all mock signals `OK`, no active alerts. -- **Fault Demo** — high board temperature `FAULT` and IMU heartbeat `STALE`. +```bash +ros2 launch waybionic_rviz_plugins engineer_view.launch.py use_mock_diagnostics:=true +ros2 launch waybionic_rviz_plugins engineer_view.launch.py use_mock_diagnostics:=false +ros2 launch waybionic_rviz_plugins engineer_view.launch.py use_mock_diagnostics:=false diagnostics_topic:=/diagnostics +``` -The panel refreshes about once per second from the current diagnostic source. +| Mode | Behavior | +|------|----------| +| Mock (`use_mock_diagnostics:=true`, default) | Uses `MockDiagnosticsSource`; `Mock Normal` / `Mock Fault` controls enabled | +| Live (`use_mock_diagnostics:=false`) | Uses `RosDiagnosticsSource`; subscribes to `diagnostic_msgs/msg/DiagnosticArray`; mock controls disabled | -## Future Integration With Live Data +Live mode with no publisher yet shows a stable waiting state (`Waiting for messages`) instead of fake mock data. -### Diagnostics (backend / Korede) +Panel settings are also saved in `engineer_monitoring_view.rviz` as `Use Mock Diagnostics` and `Diagnostics Topic`. -Today: +### Diagnostics architecture ```text -MockDiagnosticsSource -> DiagnosticMessage -> DiagnosticsPanel +DiagnosticsSource + MockDiagnosticsSource -> DiagnosticMessage -> DiagnosticsPanel + RosDiagnosticsSource -> DiagnosticMessage -> DiagnosticsPanel ``` -Future: +`RosDiagnosticsSource` maps ROS diagnostic levels and fields into the internal `DiagnosticMessage` model before the Qt panel renders them. See `docs/DIAGNOSTICS_CONTRACT.md` for the full mapping Korede/backend should follow. -```text -ROS2 /diagnostics subscriber -> DiagnosticMessage -> same DiagnosticsPanel -``` +## Doctor / Surgeon Placeholder View -Preferred topic: `/diagnostics` using `diagnostic_msgs/msg/DiagnosticArray` or an agreed WayBionic format. - -Backend should publish stable signal names, status, timestamp, value, unit, and alert message. See `docs/DIAGNOSTICS_CONTRACT.md` for the full normalized contract and mapping guidance. - -Integration steps (later): +```bash +ros2 launch waybionic_rviz_plugins doctor_view.launch.py +``` -1. Add a `ROS2DiagnosticsSubscriber` (or equivalent) that converts live messages into `DiagnosticMessage`. -2. Replace or supplement `MockDiagnosticsSource` inside the panel based on a parameter such as `use_mock_diagnostics`. -3. Keep all Qt table and alert rendering on the normalized contract — do not bind the UI directly to ROS message fields. -4. Test with mock data first, then connect live `/diagnostics` once the backend publishes it. +Opens `config/doctor_camera_view.rviz` with: -### Surgeon camera feeds +- RViz Image displays for placeholder camera topics +- Docked `WayBionic Surgeon Camera` panel (`SurgeonCameraPanel`) -Current placeholder topics in `config/doctor_camera_view.rviz`: +Placeholder topics until Gianna/electrical select actual camera hardware: - `/camera/camera/color/image_raw` (primary) -- `/surgeon/secondary/image_raw` (disabled placeholder) +- `/surgeon/secondary/image_raw` (secondary placeholder) -When real camera drivers are available: +Both Image displays are configured in the RViz layout, but no camera feed will appear until a driver publishes those topics. The surgeon panel shows `Waiting for camera feed` and does not subscribe to images directly. -1. Confirm the live `sensor_msgs/Image` topic names. -2. Update `config/doctor_camera_view.rviz` Image display topics. -3. Launch `doctor_view.launch.py` while the camera node is running. -4. No panel plugin changes are required for basic camera viewing. +## Switching Between Views -### Robot visualization with live joint states +Engineer and doctor are separate saved layouts. Options: -The engineer launch currently uses `joint_state_publisher` for a passive demo pose. For live hardware or simulation: +1. Close one window and launch the other launch file. +2. In RViz: **File → Open Config** and load the other layout from the installed package: + - Engineer: `.../share/waybionic_rviz_plugins/config/engineer_monitoring_view.rviz` + - Doctor: `.../share/waybionic_rviz_plugins/config/doctor_camera_view.rviz` +3. Dock `DiagnosticsPanel` or `SurgeonCameraPanel` manually via the Panels menu. -1. Stop relying on the passive joint publisher. -2. Launch your existing arm stack (driver, Gazebo, or MoveIt) so `/joint_states` and TF are published. -3. Keep using `engineer_monitoring_view.rviz` or point RViz at the same config via `rviz_config_file`. -4. The diagnostics panel remains independent of arm control. +## Optional AR4 Visualization Helper -Example reference for an existing AR4 RViz demo: +Not part of the default quickstart. Isolated for prototype review only: ```bash -cd ~/ar4_ws -source install/setup.bash -ros2 launch annin_ar4_moveit_config demo.launch.py +ros2 launch waybionic_rviz_plugins engineer_ar4_demo.launch.py +ros2 launch waybionic_rviz_plugins engineer_ar4_demo.launch.py ar_model:=mk3 include_gripper:=True ``` -WayBionic engineer monitoring is separate from that launch, but can reuse the same workspace and robot description packages. +Requires an AR4/Annin workspace with `annin_ar4_description`, `xacro`, `robot_state_publisher`, and `joint_state_publisher`. These are not core dependencies of `waybionic_rviz_plugins`. -## Package Layout +## Mock-Only vs Live -```text -waybionic_rviz_plugins/ - README.md - CMakeLists.txt - package.xml - plugin_description.xml - include/waybionic_rviz_plugins/ - src/ - config/ - launch/ - docs/ -``` +**Mock-only today (synthetic, for UI validation):** + +- `Mock Normal` / `Mock Fault` controls in the engineer panel +- Synthetic telemetry values in mock mode + +**Live path (ready for backend):** + +- `use_mock_diagnostics:=false` +- `diagnostics_topic:=/diagnostics` (or another agreed topic) +- `RosDiagnosticsSource` in `src/ros_diagnostics_source.cpp` + +**Placeholder only (no real pipeline yet):** + +- Doctor/surgeon camera topics and `SurgeonCameraPanel` waiting state + +## Related Docs + +- `docs/DIAGNOSTICS_CONTRACT.md` — normalized diagnostic model and ROS mapping +- `docs/GROUND_STATION_RVIZ_UI.md` — extended architecture and view notes +- `docs/PR_NOTES.md` — review summary, testing, and PR description source -## More Documentation +## Follow-Ups -- `docs/GROUND_STATION_RVIZ_UI.md` — view details and architecture notes. -- `docs/DIAGNOSTICS_CONTRACT.md` — normalized diagnostic interface for backend integration. +- Test engineer layout against Harold's WayBionic placeholder robot once `origin/rebuild/waybionic-foundation` is merge-ready. +- Connect live camera topic names after hardware selection. +- Validate live `/diagnostics` once Korede/backend publishes stable data. diff --git a/waybionic_rviz_plugins/docs/DIAGNOSTICS_CONTRACT.md b/waybionic_rviz_plugins/docs/DIAGNOSTICS_CONTRACT.md index 067dbb0..918ca1a 100644 --- a/waybionic_rviz_plugins/docs/DIAGNOSTICS_CONTRACT.md +++ b/waybionic_rviz_plugins/docs/DIAGNOSTICS_CONTRACT.md @@ -1,6 +1,6 @@ # Diagnostics Contract -The RViz2 diagnostics panel consumes a normalized internal model. Mock diagnostics use this contract now, and future ROS 2 diagnostics should be converted into the same model before updating the UI. +The RViz2 diagnostics panel consumes a normalized internal model. Mock diagnostics and live ROS 2 diagnostics both convert into this model before updating the UI. ## Internal C++ Shape @@ -52,26 +52,30 @@ imu.heartbeat | STALE | - | - | 5.2s ago | Sensor timeout Other rows can remain `OK` while these fault rows generate visible alerts. -## Future ROS 2 Mapping +## ROS 2 Diagnostics Mapping -Preferred future topic: +Live mode subscribes to `/diagnostics` by default. The topic can be overridden at launch: -```text -/diagnostics +```bash +ros2 launch waybionic_rviz_plugins engineer_view.launch.py use_mock_diagnostics:=false diagnostics_topic:=/diagnostics ``` -Recommended source format: +Message type: - `diagnostic_msgs/msg/DiagnosticArray` -- or another agreed WayBionic diagnostics message Mapping guidance: +- `DiagnosticStatus.name` maps to `signal_name`. - ROS diagnostic `OK` maps to `DiagnosticStatus::Ok`. - ROS diagnostic `WARN` maps to `DiagnosticStatus::Warn`. - ROS diagnostic `ERROR` maps to `DiagnosticStatus::Fault`. -- Missing or old timestamps should map to `DiagnosticStatus::Stale`. -- Diagnostic key/value pairs should be normalized into `value`, `unit`, and `alert_message` before updating the panel. +- ROS diagnostic `STALE` maps to `DiagnosticStatus::Stale`. +- `DiagnosticStatus.message` maps to `alert_message` when the status is not `OK`. +- `DiagnosticStatus.values` may provide `value` and `unit` keys. If those keys are not present, the first non-empty key/value pair is shown as a value with the key as the unit/label. +- `DiagnosticArray.header.stamp` is used as the row timestamp. If it is unset, receive time is used. +- If no live message has arrived, the panel shows `Live diagnostics mode active` and `Waiting for messages` instead of mock data. +- If the latest live message is old, the panel marks rows stale. The Qt/RViz UI should depend on `DiagnosticMessage`, not raw ROS message internals. This keeps mock data, future backend diagnostics, and any future simulator diagnostics on the same path. @@ -88,10 +92,27 @@ Please publish each monitored signal with: Faults and stale signals should generate visible alerts in the engineer panel. -## Integration Checklist (Later) +## Source Switching + +Launch with mock diagnostics: + +```bash +ros2 launch waybionic_rviz_plugins engineer_view.launch.py use_mock_diagnostics:=true +``` + +Launch with live ROS 2 diagnostics: + +```bash +ros2 launch waybionic_rviz_plugins engineer_view.launch.py use_mock_diagnostics:=false +ros2 launch waybionic_rviz_plugins engineer_view.launch.py use_mock_diagnostics:=false diagnostics_topic:=/diagnostics +``` + +Mock mode keeps the `Mock Normal` and `Mock Fault` validation controls enabled. Live mode disables those controls and listens to the configured diagnostics topic. + +## Integration Checklist -1. Publish live diagnostics to `/diagnostics` (or agreed topic) with stable signal names. -2. Implement a ROS 2 subscriber that maps incoming messages to `DiagnosticMessage`. -3. Add a panel parameter such as `use_mock_diagnostics:=false` to switch from mock to live data. -4. Verify Normal and Fault scenarios in the engineer RViz panel before operator use. -5. Keep camera and robot visualization integration separate — they do not require changes to the diagnostic contract itself. +1. Publish live diagnostics to `/diagnostics` or pass the agreed topic as `diagnostics_topic:=`. +2. Verify that each `DiagnosticStatus` includes a stable `name`, useful `message`, and value/unit keys where applicable. +3. Launch the engineer view with `use_mock_diagnostics:=false`. +4. Verify mock normal/fault validation states and live backend diagnostics before operator use. +5. Keep camera and robot visualization integration separate; they do not require changes to the diagnostic contract itself. diff --git a/waybionic_rviz_plugins/docs/GROUND_STATION_RVIZ_UI.md b/waybionic_rviz_plugins/docs/GROUND_STATION_RVIZ_UI.md index ae03d31..18c2297 100644 --- a/waybionic_rviz_plugins/docs/GROUND_STATION_RVIZ_UI.md +++ b/waybionic_rviz_plugins/docs/GROUND_STATION_RVIZ_UI.md @@ -1,6 +1,6 @@ # WayBionic RViz2 Ground Station UI -This package contains the RViz2-native direction for the WayBionic ground station. It keeps the operator interface inside RViz2 and avoids modifying RViz source code. +This package contains the WayBionic RViz2-native diagnostics plugin and monitoring layouts. It keeps the operator interface inside RViz2 and avoids modifying RViz source code. ## Scope @@ -10,129 +10,96 @@ This is a monitoring-first UI package. - It does not implement robot control. - It does not implement safety-critical logic. - It does not implement camera streaming drivers. -- It does not replace the existing AR4 driver, MoveIt, Gazebo, or firmware packages. +- It does not require Annin/AR4 packages for the generic engineer or doctor views. -## Workspace Setup (WSL / Ubuntu) - -The expected development environment is ROS 2 Jazzy on Ubuntu 24.04, using an existing colcon workspace such as `~/ar4_ws`. - -### 1. Add package to workspace - -Symlink from a Windows checkout into WSL: - -```bash -ln -s "/mnt/c/Users//OneDrive/Desktop/Uni Work/Clubs/WayBionic/waybionic_ground_station/waybionic_ground_station/waybionic_rviz_plugins" \ - ~/ar4_ws/src/waybionic_rviz_plugins -``` - -### 2. Build +## Build ```bash -cd ~/ar4_ws +cd source /opt/ros/jazzy/setup.bash rosdep install --from-paths src --ignore-src -r -y colcon build --packages-select waybionic_rviz_plugins --symlink-install source install/setup.bash ``` -### 3. Rebuild after code changes +## Views + +### Engineer Monitoring View ```bash -cd ~/ar4_ws -source /opt/ros/jazzy/setup.bash -colcon build --packages-select waybionic_rviz_plugins --symlink-install -source install/setup.bash +ros2 launch waybionic_rviz_plugins engineer_view.launch.py ``` -## Views +This opens RViz2 with `config/engineer_monitoring_view.rviz`. The layout includes a docked `WayBionic Diagnostics` panel and generic RViz displays. It does not launch AR4 robot description packages, passive joint publishers, hardware drivers, or motor command nodes. -### Doctor / Surgeon Camera View +Diagnostics source modes: -Launch: +```bash +ros2 launch waybionic_rviz_plugins engineer_view.launch.py use_mock_diagnostics:=true +ros2 launch waybionic_rviz_plugins engineer_view.launch.py use_mock_diagnostics:=false +ros2 launch waybionic_rviz_plugins engineer_view.launch.py use_mock_diagnostics:=false diagnostics_topic:=/diagnostics +``` + +- Mock mode uses `MockDiagnosticsSource` and keeps the `Mock Normal` and `Mock Fault` validation controls working. +- Live mode uses `RosDiagnosticsSource`, subscribes to the configured diagnostics topic, and disables mock controls. + +### Doctor / Surgeon Placeholder View ```bash -cd ~/ar4_ws -source /opt/ros/jazzy/setup.bash -source install/setup.bash ros2 launch waybionic_rviz_plugins doctor_view.launch.py ``` This opens RViz2 with `config/doctor_camera_view.rviz`. The layout is intentionally camera-focused and avoids engineering telemetry clutter. -Current camera topics are placeholders: +The layout includes RViz Image displays and the docked `WayBionic Surgeon Camera` panel. Current camera topics are placeholders until Gianna/electrical select the actual camera hardware and ROS topics: - `/camera/camera/color/image_raw` - `/surgeon/secondary/image_raw` disabled by default -Update the RViz config when the real surgeon camera topics are available. - -### Engineer View +The `SurgeonCameraPanel` is a placeholder/status panel only. It shows the configured topics, waits for a future camera feed, and does not start camera nodes or claim a real camera pipeline exists. -Launch: +### Optional AR4 Visualization Helper ```bash -cd ~/ar4_ws -source /opt/ros/jazzy/setup.bash -source install/setup.bash -ros2 launch waybionic_rviz_plugins engineer_view.launch.py +ros2 launch waybionic_rviz_plugins engineer_ar4_demo.launch.py ``` -This opens RViz2 with `config/engineer_monitoring_view.rviz`. The layout includes: - -- RViz robot visualization using the AR4 description. -- A docked `WayBionic Diagnostics` panel. -- Mock diagnostics with Normal Demo and Fault Demo controls. -- Telemetry/live values and alerts derived from normalized diagnostic messages. - -The engineer launch uses `robot_state_publisher` and `joint_state_publisher` for passive visualization. It does not start the hardware driver and does not send motor commands. - -Useful launch arguments: - -```bash -ros2 launch waybionic_rviz_plugins engineer_view.launch.py ar_model:=mk3 include_gripper:=True -``` +This launches passive AR4 robot visualization using `annin_ar4_description`, `xacro`, `robot_state_publisher`, and `joint_state_publisher`, then opens `config/engineer_ar4_demo.rviz`. These are optional visualization dependencies, not core plugin dependencies. ## Architecture ```text Doctor view: doctor_view.launch.py -> doctor_camera_view.rviz -> RViz Image displays + -> WayBionic Surgeon Camera panel -Engineer view: +Generic engineer view: engineer_view.launch.py -> engineer_monitoring_view.rviz - -> RobotModel / TF visualization -> WayBionic Diagnostics panel -Diagnostics flow (today): - MockDiagnosticsSource -> DiagnosticMessage -> DiagnosticsPanel +Optional AR4 visualization helper: + engineer_ar4_demo.launch.py -> engineer_ar4_demo.rviz + -> AR4 robot_description + passive joint states -Diagnostics flow (future): - ROS2 /diagnostics subscriber -> DiagnosticMessage -> same DiagnosticsPanel +Diagnostics flow: + DiagnosticsSource + -> MockDiagnosticsSource -> DiagnosticMessage -> DiagnosticsPanel + -> RosDiagnosticsSource -> DiagnosticMessage -> DiagnosticsPanel ``` -## Future Live Data Integration - -### Diagnostics panel - -1. Backend publishes to `/diagnostics` (or agreed topic). -2. A future subscriber converts `diagnostic_msgs/msg/DiagnosticArray` into `DiagnosticMessage`. -3. The panel UI stays unchanged and still renders tables/alerts from the normalized contract. -4. Add a launch parameter to switch between mock and live sources during bring-up. - -See `DIAGNOSTICS_CONTRACT.md` for field definitions, examples, and ROS mapping guidance. +RViz panel plugins: -### Camera integration +```text +Panels -> Add Panel -> waybionic_rviz_plugins + -> DiagnosticsPanel + -> SurgeonCameraPanel +``` -1. Start the surgeon camera driver/node. -2. Confirm published `sensor_msgs/Image` topic names. -3. Edit `config/doctor_camera_view.rviz` to point Image displays at live topics. -4. Relaunch `doctor_view.launch.py`. +## Live Data Integration -### Live robot visualization +`RosDiagnosticsSource` subscribes to `/diagnostics` by default, or another topic passed as `diagnostics_topic:=`, with `diagnostic_msgs/msg/DiagnosticArray`. It maps each ROS diagnostic status into `DiagnosticMessage`, including status level, name, timestamp, value/unit fields, and alert message. See `DIAGNOSTICS_CONTRACT.md` for the exact mapping. -1. Launch driver, Gazebo, or MoveIt so `/joint_states` and TF are live. -2. Use engineer RViz config without the passive `joint_state_publisher`, or include MoveIt with `rviz_config_file` override. -3. Keep diagnostics monitoring separate from control commands. +The panel remains monitoring-only in both mock and live modes. ## Package Contents @@ -141,17 +108,25 @@ waybionic_rviz_plugins/ include/waybionic_rviz_plugins/ diagnostics_contract.hpp diagnostics_panel.hpp + diagnostics_source.hpp mock_diagnostics_source.hpp + ros_diagnostics_source.hpp + surgeon_camera_panel.hpp src/ diagnostics_panel.cpp mock_diagnostics_source.cpp + ros_diagnostics_source.cpp + surgeon_camera_panel.cpp config/ engineer_monitoring_view.rviz + engineer_ar4_demo.rviz doctor_camera_view.rviz launch/ engineer_view.launch.py + engineer_ar4_demo.launch.py doctor_view.launch.py docs/ - GROUND_STATION_RVIZ_UI.md DIAGNOSTICS_CONTRACT.md + GROUND_STATION_RVIZ_UI.md + PR_NOTES.md ``` diff --git a/waybionic_rviz_plugins/docs/PR_NOTES.md b/waybionic_rviz_plugins/docs/PR_NOTES.md new file mode 100644 index 0000000..1dd762d --- /dev/null +++ b/waybionic_rviz_plugins/docs/PR_NOTES.md @@ -0,0 +1,106 @@ +# PR Notes + +## Summary + +This branch makes `waybionic_rviz_plugins` a generic WayBionic RViz2 diagnostics foundation package. The default engineer launch opens RViz with the diagnostics panel, mock diagnostics are kept for validation, and live mode is ready to consume ROS 2 diagnostics without binding the panel UI to raw ROS messages. + +## Primary Launch Commands + +```bash +colcon build --packages-select waybionic_rviz_plugins --symlink-install +source install/setup.bash +ros2 launch waybionic_rviz_plugins engineer_view.launch.py +ros2 launch waybionic_rviz_plugins doctor_view.launch.py +``` + +Live diagnostics mode: + +```bash +ros2 launch waybionic_rviz_plugins engineer_view.launch.py use_mock_diagnostics:=false diagnostics_topic:=/diagnostics +``` + +## What Works + +- Engineer RViz view with the `WayBionic Diagnostics` panel. +- Mock normal and fault validation states. +- Alert banner/table rendering from normalized `DiagnosticMessage` rows. +- Live diagnostics source path that subscribes to a `diagnostic_msgs/msg/DiagnosticArray` topic. +- Doctor/surgeon placeholder camera layout with a docked `SurgeonCameraPanel`. +- Both RViz panels are registered under `waybionic_rviz_plugins`: `DiagnosticsPanel` and `SurgeonCameraPanel`. + +## Mock Diagnostics Mode + +Mock mode is enabled by default through `use_mock_diagnostics:=true`. It uses synthetic values and keeps `Mock Normal` and `Mock Fault` controls enabled for UI validation while backend `/diagnostics` publishing is not ready. + +## Live `/diagnostics` Path + +`RosDiagnosticsSource` subscribes to `/diagnostics` by default, or another topic passed as `diagnostics_topic:=`, and converts each status into the internal `DiagnosticMessage` model before the panel renders it. If no backend is publishing yet, the panel stays stable and shows that live diagnostics mode is active while it waits for messages. + +Expected mapping: + +- `DiagnosticStatus.name` -> `signal_name` +- `OK` -> `OK` +- `WARN` -> `WARN` +- `ERROR` -> `FAULT` +- `STALE` -> `STALE` +- `DiagnosticStatus.message` -> `alert_message` for non-OK rows +- `DiagnosticStatus.values` -> `value` and `unit` when those keys are present +- `DiagnosticArray.header.stamp`, or receive time when unset, -> `timestamp` + +## Doctor / Surgeon Camera Placeholder View + +`doctor_view.launch.py` opens `config/doctor_camera_view.rviz` with RViz Image displays and the docked `WayBionic Surgeon Camera` placeholder panel. The configured topics are placeholders until Gianna/electrical select the real camera hardware and ROS topics; this package does not claim a camera pipeline exists yet. + +The surgeon camera panel is available from RViz through: + +```text +Panels -> Add Panel -> waybionic_rviz_plugins -> SurgeonCameraPanel +``` + +It shows the placeholder topic names and `Waiting for camera feed`; actual image rendering remains in RViz Image displays once camera topics exist. + +## Optional AR4 Visualization Helper + +Core package dependencies do not include Annin/AR4 packages. The generic launch is `engineer_view.launch.py`; it starts RViz only and does not require AR4 descriptions or passive joint publishers. + +Optional AR4 coupling is isolated in: + +- `launch/engineer_ar4_demo.launch.py` +- `config/engineer_ar4_demo.rviz` + +That helper requires an AR4/Annin workspace with `annin_ar4_description`, `xacro`, `robot_state_publisher`, and `joint_state_publisher` available. + +## Testing Performed + +```bash +source /opt/ros/jazzy/setup.bash +colcon build --packages-select waybionic_rviz_plugins --symlink-install +source install/setup.bash +ros2 launch waybionic_rviz_plugins engineer_view.launch.py --show-args +ros2 launch waybionic_rviz_plugins doctor_view.launch.py --show-args +``` + +Optional AR4 helper, only in a workspace with AR4 dependencies: + +```bash +ros2 launch waybionic_rviz_plugins engineer_ar4_demo.launch.py --show-args +``` + +The doctor view was also launched briefly under a timeout to verify RViz starts and loads the placeholder panel without plugin errors. + +## PR Status + +PR status could not be confirmed from this environment. `gh` is not available in PowerShell, and the WSL check did not return PR data. Use this file as the PR description source if the PR still needs to be opened. + +## Known Limitations + +- Meaningful live rows require Korede/backend to publish stable diagnostics data. +- Stale handling is currently a simple five-second freshness check. +- Doctor view is a placeholder layout only; no real camera hardware or pipeline is implied. +- The UI is monitoring-only and does not send motor commands. + +## Next Steps + +- Confirm final diagnostic signal names and value/unit conventions with backend. +- Connect live camera topic names after hardware selection. +- Add focused runtime validation once a diagnostics publisher is available. From aa50ab9e3ea141071fa96d934d1994196a57f504 Mon Sep 17 00:00:00 2001 From: Khuzaymah Bin Haris Date: Sat, 13 Jun 2026 14:18:51 -0600 Subject: [PATCH 17/19] Add PR review screenshots for engineer and surgeon views. Store mock normal/fault and surgeon placeholder captures under docs/screenshots and reference them at the top of PR_NOTES. Co-authored-by: Cursor --- waybionic_rviz_plugins/docs/PR_NOTES.md | 14 +++++++++++++- .../docs/screenshots/engineer_mock_fault.png | Bin 0 -> 86452 bytes .../docs/screenshots/engineer_mock_normal.png | Bin 0 -> 80895 bytes .../screenshots/surgeon_camera_placeholder.png | Bin 0 -> 76375 bytes 4 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 waybionic_rviz_plugins/docs/screenshots/engineer_mock_fault.png create mode 100644 waybionic_rviz_plugins/docs/screenshots/engineer_mock_normal.png create mode 100644 waybionic_rviz_plugins/docs/screenshots/surgeon_camera_placeholder.png diff --git a/waybionic_rviz_plugins/docs/PR_NOTES.md b/waybionic_rviz_plugins/docs/PR_NOTES.md index 1dd762d..c792ed9 100644 --- a/waybionic_rviz_plugins/docs/PR_NOTES.md +++ b/waybionic_rviz_plugins/docs/PR_NOTES.md @@ -1,5 +1,17 @@ # PR Notes +## Review Screenshots + +These images are included only to help reviewers see the UI quickly. They are not part of the runtime package behavior. + +| Engineer view — mock normal | Engineer view — mock fault | +| --- | --- | +| ![Engineer mock normal](screenshots/engineer_mock_normal.png) | ![Engineer mock fault](screenshots/engineer_mock_fault.png) | + +Doctor/surgeon placeholder panel: + +![Surgeon camera placeholder](screenshots/surgeon_camera_placeholder.png) + ## Summary This branch makes `waybionic_rviz_plugins` a generic WayBionic RViz2 diagnostics foundation package. The default engineer launch opens RViz with the diagnostics panel, mock diagnostics are kept for validation, and live mode is ready to consume ROS 2 diagnostics without binding the panel UI to raw ROS messages. @@ -90,7 +102,7 @@ The doctor view was also launched briefly under a timeout to verify RViz starts ## PR Status -PR status could not be confirmed from this environment. `gh` is not available in PowerShell, and the WSL check did not return PR data. Use this file as the PR description source if the PR still needs to be opened. +Use this file as the PR description source. Review screenshots are at the top for reviewer context only. ## Known Limitations diff --git a/waybionic_rviz_plugins/docs/screenshots/engineer_mock_fault.png b/waybionic_rviz_plugins/docs/screenshots/engineer_mock_fault.png new file mode 100644 index 0000000000000000000000000000000000000000..84ec6ea597a9c45b2d0aed9e6e41e8f8e18cf0da GIT binary patch literal 86452 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ΞUD+^| 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 V&qlcmXDCV5(I0no+uVPQ{{y81sUiRX literal 0 HcmV?d00001 diff --git a/waybionic_rviz_plugins/docs/screenshots/surgeon_camera_placeholder.png b/waybionic_rviz_plugins/docs/screenshots/surgeon_camera_placeholder.png new file mode 100644 index 0000000000000000000000000000000000000000..515b16a2ec2b50302c7927ec3312cdcc2c5f0229 GIT binary patch literal 76375 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

-!7_K5eZd-6YdDiJkL~iM=0P1rgOZqt4CR^9bIN)*xNh |* z^LD#3-a~a(C6(@xAv#4MeFQ7xlnS)eY=1F&FZE_zdX{{*0Ux2Q1NU7iVD@2FrY8%X z3)7U^4LBM^i-KyhA8&Y4FT8oG2{r_bfks%k@=pvr?P#s1ISC?k;kEZ*Yxs2{ZT7;F z1+Bk^L!emE`OxkGK>#`)+dIk2j~cXOkcfl^-GJ`bUsRefJ?2^8(83|D@=9@zPY3FZ zZW&57^~P^5_Ji8Y zMs1}MU;G#0!G7R0n(BaDo| z$~-6|QYkULyr5L!71TAn#GvQIQds&x1VMlD6niTn#aC(eeNWD@AH!aVghtu})yswA z?c=Li9-76?9eBo7x^iCP1d&c8!?XbH{&{yv3h)o{-+f`b~j2#d&8M4zxoB~SY;CF2)2qrsZz_ve?QJD;$Kn#g45&lY|H zx>m)00<`O0mXz^OAUlMbQv%rn^-yHpP@jWZ)%Y0Ed}WF_ItWKc>4nrbg-l9-iC(C2ea_Lh?0pPY#%(qf);RG$dI04{e8r1;R7P)=! zcQnJK8pGf@9iNruGMk7RE?z;l0=+IT3+@Ph(Vu|I5^G^eih%FkK+T2hLL6`-NJ{y; zF%DH&LZmW^$xd^-m!XC)BOi+N^ht!X2Pqg3iA3$mrChd3<8xkdfK##G18(KzUZ*^X zCp0xIr#7r?Bv($M6nC&e*Mb?2T)|v%vCv7yc&C05 zCn?UUQ7FhUG+EInv_4~IQwshG*r5NSw8`}}mBFUn(>d_|&=uiJW8o?|XW|%~gilxi z5tP&}pL?(|ytNgJrsGQ*cG=3ZRnkD4qmamPV3ArWD_GK2@+wz=I=ZGI%})3My;kir zaL!9T^-ajqi6W#t;HjdO`gVh0_gBaob-Xs6i?p;h_V%G;jL?c~qM222`a_r2=j3Cv zpP&s_@(nm|Q3&JS3t7Ba-Yyn)=_u>S`~dJgJ6!Zxw|VQ6(>By7=6Qh!r@_N}6Z0B} zb)j6J`l1Uuz1GWzdXqc)w$nA07b8+O=1~pK2jQ(LajYkOg%YGB+h>o*!Aqb~TG?Yb zH;$)grc5P@LR4dOIPd{8M=yNQBIF{<4gDW2;*;r8)PlKh<@SkmXZe8wOd|^((Fs9% zCtE0|r{%Be;JcC{86p^ote;;vDp%_W!o19&3U^$}_rThEHwwu3i1QcIvn2Y88_m7L zVZAEZHs^5W-|3Ld<@wa2sUUB8!9K;x=vV8>TU4SnW_4I^VRGH${=KcXURgk>d58x8&^C zG{)aqeE1yykm(4q{okhy(8cu}xI%HFj&OuIaoma8b<$cW#^>zxFO@@5k|Xf&wroyM zZbVHwi)N~W5+etRfQs{UW>r;em6gGpC($0R8h`Zcv0}Usp?c* z_j;L?-hxK7{wrLt)b_&Uu!qK?Ibh}? z3io^WPfrX(%GLD9S&dKquQ0@IT3fNFd-%^4{FV>&A>aGa%-^`B7iMqz!yN^NxlfwE zdwmD78Si15;wcB?ja-p!HC%xL5fF^Mb-nWo9Xf(@+68t6)nI#J@^&=3%GjAQmZ4nU z*IVGd1nKS6A9#T#6WW=1Z!Ip28=g0W)#LMyE5(nVy0VwR-E-|6(zKQ27@1e;LDsQS z9Sg9p^EMVPU$lZrJhaB}AiaSkuaQrh*&$pGITi0s^R70vLEn2ea_%Ux~dzSa3<^s!_(wrDV3m!qx_nrOiP5 zh%wf?w^eMtmIg1KT+lT~SLk!SgC!)X{QTN~*S5c@++R&O76w<>j)BiXNQRpNY^C-6 z$Wwy?-e>ym@vA~jGX~%>8E-;ElP+0t^e2NmNA=l5L6q2gVje6vy-rkP2l{!YNJK!0 zz~MlJhXLAil>*?7q{>Q6S5UCw8#a*&mDGHGj`9SzLk81nwVsWlszCoP)ylxQ zlCC67FWkKkivq!e@I?<4L=ck{c!H3_B-?wMddL*L#=za*s&YDurVJIyBxCqnVC51@ z2Zd<9dw0c?M#BP@z2jq$$}*dS=zu3nfieWFL}W_xN!p!1beG-wNvM)w)4huB3=NP6 zd`80^->+tzmMCssN@Cm6#z$J*Eo;FGgMD4htQyrN=wbu$9@kmwN;vR$p_VHnvHefEC=D+ry(1J-(iCt{}a%lK9*#CurkKJu>2G71!FrQ3>h3KuXH%L-{V$lHhk@i_F7}CL>fK4vn<8}A|Ydxv~ZiJOT zNFqJ<2VubB53S^L%rm@KDW^sl1b%i#zaDu zOcCy8`iWbGA%&1kP(A5myH^*dP(R578S(E4i~1*2n-BX2DMLB&p<%AD2!DhM7tC2g z%oj7zuLD^fFXA82xc2v~Sj=fVm|4KvaXd*Ff>JrX+W{Hwl92oJ1} z2Ft17v6U@~!&f*zUX+BJtve*A$Co5lOR>_NixM+i^(jaHA*b>1P_uz1ZB-)ge*z4%4!1rA?=MXS zdkT9zdnwd7>}BXtaNj4f0&T(jgSxC;dzm*XJ>>XTYhhuV_w4FC)+}pcXWCENw~KYm zQu?+}*!U5#%RKDCyF+Yy&>_*ox$voz8%2AYos`I?mF9=RQ?O*S+0bq0x*elNlWZRc_!LhHsZ$T0hv8AH-R)L>K) z_xV%@Y_b-MVXtw}9%9s(KC@~$iq`8>M`y`TL`*hgOuj@UAkg23XA(Kbeq>flWT(`T zPxuhz;8w|yh4&1jLQ;~5aLGzKuNr?YQ*n%CcP!~^csw&yUHu}t(~uQjpN7Y?1=p01 ze6;e3mm`rX&$wlG>wWD<+3c7pb~;cw_Klw#3C5QQRHT^l{cVwVVCEs^gcnuOZ#GlP zXlSxmf4PdkE3c~kpeczP1)H^=^4EXDO&In^T4izd?-C$QK)``I+ zM3eMsm0&oc$ogP3Q97(syI#v3#l2A}@=6mY*aRahwA^f;=O(5$K7&7DCc3f#hc=jb z&0NL!O?`@tFWSO|$?bd&LgwKnx4h!jmC<*($oM!jf79S{Q>Ig6`JVUnX;J&;A+cT@ z<@2ZpN)2&I0_;jh89K{qy0^Xdms!FKhCcxT4HU#hE$!hOL%I!Wxp~e?^{k}Fv z7#Xud9c9^C7CGc60HXr#DxsYnChV7=7Pr-Etoe2qcM+tGS`n?U8?U)Yd z-57Z<=U;)owCyUq6xl>XMEO!FTGe0!RjWGh-vvIiKCl)OdID~PwOc3^$sK3EP0iJ^ zHAsKPK7BQFTxPhJdrC^a%1eE( zlYXL}BDd-FOpf_W8vF%Q%D)!YpG)l55r7U?yo3CVXKr$qK2)wma6Fqxw+5vnrycp2 zJzJo@jh6bPfgNuJa3qrd){`X8#(j~8WL)6xkemV@#qJF>IuufJ2XM>= z++D!y#BUmGZpxd7NH+52)@vLVIUgNxN1RivWS*A$DdT!GPX)5ASF{Xmku599!Fo}9 zM(|$Xnua%#=9Qb^)(?wxDeBt9g>OcFD9V%wz9wsb$OO=d#(&!B#6f3&Yl$RLge;iEZk$b*Jae< z!d|J+$hR@VkHaoDk|V+!0ttQsq^TQWDAAeUdz1)b*OW&-=FZLEt}BJAp-P3%GP(e% zdTd=X(`@+<#3<+=E2vemI7~6ABrl!^U*%`MwqRq^l_z=4Mz=+8zc^SP-RsbgQ{#un&EB2^%2&kGUy33!CAL4B6; zIxv`7Jx@O;IHS2DdW=rb8t5dYTI}3SEYLqb@KNb|$VS2R;~K%1!ib3Q5Tv zhn|=G2+*OoysJl8?p4i!9rWhQoD9Vw0*y-SkrVr_dC9_SFiLA|X8IGrwy6Y1BrBS5 zNR3_)ZdhiZ>vnqDu6nNTn}W!O`l|_GOB`#HAXCKOLB|(t8Tc`m9h1|26H{tiR+w+i&f^rTuT~;s1lkx8*8=Tdu}5bMYm8CDC4-=DSW4wjV=3 zvnTv5u24#oJTzL_ticjbp^dD;zC}bDLD$j%1TK$nd=h$Zw9gjcI{-TJ4obtmLJp>Q z`%D5w^52Nq@7S0W6FJ+&zCkK-D(NM!+6Cs_nh*SIXsAXG*_*+s6$`%#{jg;6NHi+z zOqh1sz6b%80gLALChVAoqFDOeQw9Y$-u$#g6QvU+#%D}@OzgO5vBrTsiFvxblYG#9 zYakJh>k*(_Y2V!f`|QfFxV_}ce{A&7AYYKv_2%hvd+%WC)sbH6jEMHjtVWNQdJcP< z)bh4dTD7KWaveLby$K zwbBOE2_eA=E50R&%1eh)hpQgm0bMHJ*p?&Uys|=z(Rik)?R+lPKUNT?JmPJxaq!eV za-#yd9v}Ec{|F$IUBVckvq(BWWVP+U2#qH!wq~f!b`jwXL)$o*LA}(8hEs)x>a{!x z!%9eF6X=T(uIvIm!rs1{JgrxZQ`~GF(ig1?AF}+4fC{gyfzolf(F%eZ?wz`|nh=K= z$uyX-&WyEE^xKT^&{B(hU-cBY;8umVM^LM8=pRz0{A2hR1-8&Rz(%u4o(8o2T?kD z>tPqc7IqOA540PGi!>`Kd3RiB+HLpkfR%EQ?mIVAIsFNHajV?ij zzA-QI!+3Dcim@Czs5*2y4&aVWP;Y6F?<&Vg&p1-+;;wLkMB8Si_qS4kvJ|y`O+Kw=hel!KcH!6mWGlSjFn6K)k`l3S)P0E+6?i?X9F3YoZYoqY zJAL)WFu)z2-)r^dDHbMPC=ya#=xgg zohO#liuIXu4zXTuCcGl8toz5T|8`n(&=4H@r;~>`va=CCj_>=u{o4=!3#d*^UY2S( zg34cNq=VgjyzPMCP8POEUJ=l(gVe;D@`vlvvedt_+zjq}%a3zhB)xB%e)jlH`?gb1G8xXdz)Y1we|C&ufMJIB zFFj`lilKn8SYHup&xBC0u5zuT?SEV`NSYnwXd31O&{{NSH9VBq5$FC;K(Mw|Zc{ll z5U&1Z0LMGX8b>|;P`!YF6s$bfi{oe#mk_;*g+)7!fF+W6Ebw9n4^+tyjC{(FJ6g6{ z&ImPf(>IQRW5TE4Eqc4Z1XunHv;21SP_+g8179~qR&Ix*x&AJmzMp@IJ3N9{;Fv$K zwv?Fe!W)g=c@!?H=G)>E*%roq<&x^_>1Y{8zkn~n<6_bn!x&V9Lx7EQojA4gj-#{6 zFf?k3#YN;`r=X`Dqy1%k+GJM%rAHb$Njsus1yF$jMIi?MBo}f|05Y8d5WI^ zO3WI!vUffG=6Pkdg*v4ATi~AFvF}T0w){YHz=jk_ZYAE#U_`zkChI}aRfG<_uq^%{ zs`umS2?UfO(ui){!nA&pkZ#oe;Gr?@_{u|Q2a1=!KZ(CB!OWz7WF%_8z_dZT!@~6t z#+KDS?kJsayJ@F8wXZe~0ESADeKQ+xG%O^^eQD9F?yv@fQBwIc)s3J*;S z`d;N2$5+XPO*t~61)ns-WrEcm6?dlEPj}p8YaRDBy5X<$`L~f*^U?Tq<1~ zvSkpeHz-PuHSa?M{_Ys2x4JwHT=$3+N-oU4#8kAOI}xTmz}olJMxw`#__+S-mE!BV zZ1kKDFBvn6RqbG8(|nN&i=Qx0Gbx=^)i)T2rTA=7FXiQa0^S7%Gm=agCGbi)9R;~= z&3V_Bimtz8occtXC!*F*l~FTfLVoXB8RUw5A$Jx>Ct2KAFc*r%wfm-5u048?x?u9d zGyeP-R2v!E9G8HChjN15^|(gSb!7a=zlSyv;cKqyEZ6HiA|?NxE$*LyR@4%|+GFtS zUP@<6_^iVrE+W1??la;L)24~VhsR94=pUcwf!e0Y%XJpj zQjO@Q<|!vkJr%+nGJgVcf_i~xzrzT!`RJzKHMLOc(BS=dQ%(I-M~JDq+*eo$h?YRX zge{RqKiJG9SWLvxab&kGFVL)i64R-vp&x84NAz8@V-1KrsUn>;vw(8JES>m>t7)VW(z(t z2;Lr0LjFjl_bH#=Pdk>&u5zzky)@UuWvO`!=egj z@_?$9V_ln-R|}sxr`HOww?(qEi&dKR!vfB>Vo;+rbx6O+q2}7csj@>&OdxDhq0zFk zSWCeUil!6@_LvCgSm5LL5A>^kY!NS1jLAd^eBGM-<$HYN9^mNNku+AsKXdLn)fgf_P2?_M9?EG?Va1ldm@z%m66XM zdRko;gY#=;=QQPTGJljE<4W(`O*~?(>hg#Vy|B5HI5C?$^434Cr(Q?fHGeHRoZ+*I0w za}~PA_RUyTgo7%Sam|filh&egG-?6)M`7Wg3`t)}!SJFZLH<42-VFBb&WPQhFvq9w z>u6pHT*q&;_h>>b*KU)Dk9`rKP4f7LS#Pz@;+}OsP6wp5HJqm!Sn9jssx8j%4lZ$( zZ@2dFFO>C_oO3EVtTUsE65ArI(_N_Sazt1!*P()2n8&KoRZ_I8(ajPwt7vDhV)iv% z!BDT%Dnp@RkI^6?&<&H7`KStPC2S^2R%qmCn#>EfqW*$!< z_lnP@8|Ku#pSEiSbZOfjzt%#BHX6ix<1BmcIg9w6R&p^t(zItsPc1e{~;|9eRxpsJBXp33PVtBC`1hy#} zQ)))cRO#4Y-YThzG1jVFoKz;GKvSmjd*@`U+8e5=xM`@sW=+E9!{r<*JF{LoN;Jvf zd?9r(U39D2P@*(DtnYy?svkDW%hCxN2iQ*GbUYLuzG=`)B1Ta~jsB#4UqpJjY7qOD z{k&Jy1?wlk>2R{>94_NjO6kXKZP7MH20X>XQxKGj*{l;W@66oNLK$?Ayb>R7&Y3L$ z3Sh8^)8UY_Mh6s3JqKd`Z;81^W_*9;=^ClJXiG`~{>u8}N2svZ>kYrPf3A7N(f^nZ z_!oBIzxaRIG~2p=1qTr5(8iN;U)4U+MY1pfiy2$-L*Lx$cWeN4UuLmqWW8f#ay#@x z)R0NK=fS>O26^l&EVY>t52S-?GD!o`C^wjQ##s3I+exjwZH$}hp~Cd7Ae`rqNvLqe zslxDJQe1WC6eE2DR$Q!%RU{O9I4w9Q?I0hFgF6fgHvx0u7`fn+FbL>T8r}xmb41{k zyM9o#c;IPlq##7^UTj)7cKxAL0x@&%_J%=@JBYAZH&Pk(iKLj+yT{|2pZ7RK)ErmB z>2CUEEasoxXAg4Nos_unuL@l*_MY{MV|iN0ry+~eodWXf+@4l~5g0i(a5CtCV+d{( zzKU}-ja)Q~i50%uDUPF5j1`u-?l2O@j25cbi9Ec5yYiX75-J!n zn?ZanHh2yvI4uS!hFGRS9~>~6NYKn5h+n7LFUyEy+p*acz6uvU)<`}cqTPt!avgU6 zlD4)&cUp$_x7qs-iuYq}x0W6UD(Fr!&bgf;N@aZ)$zG%@BJy{`OH3qLv&!aBWf+_w z)ceRq=AcX}#iQ#$9%ulw49CY&=|r6}M<5fs5(=TgJBfHF3p8BUx}HHSvrwdoxd_IB1-;7Vz4Vw zwnnEm+l|=6i9TVMDy3RjsK&mDrFB!Mgc5|E+UT_(!TFIR{9jXAP-0`%hx+*}Mp4*C z61ceua`~)jr`or?5+Z3bio%tg(j;HmHX&TT#J+fSHN*b;qiz!(GPKIpz`boT=OXdh z4)5I93UZ$A&7~c!mn#^Jc1U0T1Xbo(RUwKSv=)L_svgnIhxcKDC?_Qz@NhY70@ zoTsG<+QNDZ69G+~?RI0|rc3F;l6HKaTM%zl{|s+oqt{z{2 zNN2ktD%Gh(DXt}-<_2|wKz>Gd_VQv+h^-FaGhDt3_jw3psqd8F-=zDcsn71YbJiay zE}j7=P4TzLVqCUXTX6rY7~ftg)sF`;nzsRaSivYb3D3;2E#LPW;0&d*wo{j_Et=#s ztIm5qF>i}~?v8+8k`dsY(ep~1>~OVXBOFJ{lNYDTMfY#A4*c$o#QK%>m!wF_*ZV29 zMK|AX=cwE!ODv+^_G9J|vi{jkB$Ve^E22=!}V#!hGNV+Tj(=r)$R1u}_Lw9OeU81(_l zB|jkosDvg@te5d2XMT9lZuuxlYo)`>9;dYsOSTJ8j>3+MwuH50ceF9v@e_dk#B5LA z=;q}0)u&4(MBbISvpaY8w6ol%5i4m+`M}(?%Y`2l$LotuB>o2NbGT@&J~7EEvJmhx zIcbNVKN97lDf?+O-iyYt#2(suD1sYlv$JCpf`)@tbC>rEmVyo;$(2gqPbKU zr>F=c>a|zNq(uY8k0x*d0Sr#K3<6Mmc!j9G_vNGI39p~5sZeaaEqL(|t@lh0BOqO* zTTF+}Bo(%ZUlYWhNf0Q0_84vZKVxhEhwRHg|8`jKwh&gxE^C-($PXm-3l_oJLtYU_ zmn|INNytnikXJok>?{IL=p4F6`WbR#+ah@XT zr2vqKopm4#$))>rt~9Tb38WlHy=ohwHb_JJ*>sOt+66att)^PDVMX820Vo7ydKMy? zm0ix8Y256w9=~p=(H>QE5W5O|_*U`{3Hf&+ibMzCeElF^HBzvH8@}h3HN&|^cPXJ2 z-Rz+Ok(TW*V(!k5+EpM>M58GKA%$HS+L5m`@}I8Plj+a=a>e`aRsp8uNIA`bZR9Ro zm6UURcCqn?4qK^djfw&fA#Lm1MB7lg&|tq&it43V$`?NYRc|jH$|aru;990H{v&aM zP4o8Mc8+nQQ}g|JTI#iN&aJzJvuf<$PE|~tx+LHJ^JD$K%u1T{&Z6$ewJwi}$Nr3N z_%8fyOaG5{c=?-!EOj~d^x_r$i_@E=lq6BmC1{qBoMzn_m<9qV69vpw4K zlRXN0da7LtxDIE(_)H%3EP4%~>lXB!R{W#ZyWr)J6Iq8q>9JK$v0UehE~H9*k}Exs zne$ELg{-x_Qpk)p<3!19WU5E5B!6%cGLdLMJ8**-1!^BAun?-n*4~*%!@JdrAuV?d zN0c7HSedj0NYdA8g7hB+l7TRS@P%^=gT3YK=+a`lV*Z@yqUl;585?o#sD_I*JF|;; zI&r=_)})^Rjp8Tk881yYmv-v|Jq({7RlKW^-Uw2$>!azEPvIEhOW{PoA!rmlSh#Y6 zRvD)KGhvlHO$Q#5!t7q|#EWOPIWJ4xa}ennnkysX9q(=|=yFpzx@}`)c2XA!)@-RE z(};!p{khV}PLXgFz!sltAletTP>pR|J+8h|{-q5*3A2#v7Bby|Er^11#P=~Z3K_-2 zU%AZx;y?az^d$7!V~Um&Ftb8@T~dZ=*29SeWDH*B^mv`MCzu=YW6!UxJNh)W(p0#7Gy9p-sx+e#ibX6~EPX>43 z@a{3`Y~(kAjO9?J&dYRuPkqSF$+sr@fwt#9Bk~by$SX$r|6@?U#`sW{MPB3Pu>Ugs z@(tp>#zo94P?O!zR0-Yg8MwmKFok|GvRXfSgkNvl zJPO3Dt)p=u`w75Zgi(MIhH{FdMZl1yowFS4%Wji8OYAEl|Fy}tX=7uELc8sLFfmyH zvd4(PgnOyM78ggWQ>+iI);e}CS{&jd!rKgt8F`cxiDGM{ZMQX_e_RcXCh<9&Sv03v z_qLz=tiXswcO>7DlpM2psX{p@F~WJJ>@7FV|3(A3@{2&`?z zcVC2m@EQA%s2Lilq0l`R@o1EYF$lNgA_p?&q5Ue)dZ%Lai6yR@*?_*yP3)2g*AHD= z<=-JAH`;$@ZU5gy@c&p-|JNwCzjz(%mUbLtKnPL^G2_I$o|wzUFI*BzWN7iTwjvLv zd{+=`uY*^>UG^ z&)^g7L(szFd7}$jXKI=e_+;@utURCTt?Sj2QM>C5Y2*04iQURQWw-=gLyRKAC&N|P zkJalfRYkL39NVf-w>!Gf09?*Jh%{cfpj*l?8T>8Pn#?8WsoX#8uYbJ@f-F3g9`ke8 zO0qJmBtI7k7H(0e)ZHN0jL^idc^Sy#Q?hKhw)c*AP zMZJ!%QY;fNbo@i|s1m_Sd;#{q>78fHPg6-r8E)S=-{{3>%XS zyvIW(+i;1_B(|u+i6Sh}?H0XHnXu-Eu3v`0z(Q?5hC@)eRZ8AxWZ3`-aF zu>QI5oV%*TvaoZ^fY^1#FEL{`7%D~uio#C1zO@$q(9sdTV03r|jc&g?M>_lperWx) zI7sxU4&VwrK$sbm!||^q<k!y;b*d+#{Ud}BSU^3*PxbW@EJd&t zUD*SXqwTfO9F39N1~8{Rd->yKh6R3|GykI@^IeqGR_s0BWsoB_W9T=AQt=Y@>Fi5d z)Hp{JK&W-Md zyT~yq8H~qyD$WLj%{F3nKTS5M{7Z+8&%xNfpPEE_8dJ@#tfzQg;)+SMxYcH7d$Ww*TTL44T5WN^*=p^EExK>NQYseWv5S_%G zca-w(63Hg()ubQL-r_@X(RNR)zBM1uoyFq|jt|=Y4%zUeoQWKMf%s9b+-DUgKD-@+$Q2@gIYWN(?NrX|I3jTm`)cyoDnCOb z$5MgS!pQwogF_T46=2jvpcaov0=oM1TW?H)& zMlrt$;@jOH?*IK9seHPfuc8_At3_vvPG;|U9t{0^h!fXzZC5h#=cpb(*4QSsN-p3k zTbds?A-=>TJCEcTi4G9(qRx*OY;!_bhQoVrYJIx6g%gDywgdRwZ*tm}l_$_+u*0mt2;Fg#Sd;0c&DcUu8@2HaJ&fii(_drnbY4LW^pgl0M@Z(Bs`ZA0N%AdhV z1~SP7jfnjFE&XM-wKwO!>Dig+3k(xO$vgdA0{QLMI)h|NGh?kj9O9D8HpHwX^a6+= z__6@UuWSE)nUk+4ZyE<#%jti-43i@(!xdXzYdCKMoCJ|+EvxmO;a|?J^2q@bPFKXH zSRIDHB4`a4sFyb49se1*X_+QBQl50LQ_7GdJFrl?4pTs;tyqC-yW#i&eosfyQYjv- zG$nZQ(p@IQmVCgY;Hz6v!vt-ajDbpsKY!F!kl-o~2=2m2fja^!Jeg0K+5$3fERdGs z*a_tnAh#pqWmZ$=T$myU%N8F{emx0}~DUvF0a_b#iJBN`k6|j1Q{lOBF z_yrStP)J1|_(>8hlrVVBS&(zen``ZR=zr^q{sqZq>acgqT7B_}^pp2_%^?Vt3U5Rv zUTts2B(8B%waIlJ>g4fi4+H=p?5sc}JHP|EWx#u_dJ#62-gbd_~D z0JTH4=5o^tZQ=8Ar#$Gw+qPHaeSo^<*$P_v5n2&J#DGH(S0(_|%h-jmXJf59Kj_yw zH;RdG?O|QGT$64gmK++LfzOB3LQTU>=h@_Hye25mx)4zh*Rr@qS>xsM{)* zqeJp7mk9Ta2;ailHefzaq^+e@4{dl>*fK{N;E~e^PIz>LS0UTLDeqCZqIOp|YbWL!hmH zdKLfeQD~mqv1XhM0S3&y^KRH1H1htvhFp9ST~qVON`lyU2>CK2)j=dNnR{oe$Vrsh zkb?ncwP~0`!52g=QnZ^jSl=?IEmzRIn+tuuY6)kroF59)V#rRy%!2E0 zS@p0e8?$K88^-f!v8z8Ad<`6k-8irXzG`)@_XLMdnt9NsZT8+BE?kF_P?Z}fRjV|O zt0nMe1PS;fuQU1nN;rajLP5~V_~di!%xF70WcN#|h^{WbR!xyb)9F4#P^b1gBg_0o z6#v=j%}6(u(@QnIQgXC)z~(WBq> zdx)z1qhq#IWS7duhJ|A={Pxr?um+V|C~FwiTLcrt$&T=$=NE@WkB+?xRuvKP$;j9{ zQT$1)f(44TD1Hn=Z+RNx9QnrXvJk{V9uu zYitn$%m)H>^cFfT1YFOl=lAgdhl^V+O-(}s0bJ7Gs_g{|?nRVNZ{_}VFU_3lg>Rnt z7O`fNYZEILG#;Z1l`K|)otk$GT_^!?vxj9%SPPx)Y}f(kT zJ)*G7gl6adrR;)piK7C`(rYF!Ls1f}Kuaw@EcX;nMF2Tt2>fMJ#I3;{;(JJtKRWzz z#n%~6$Y+lGi>`n&4*E?&)lR)n3kdZ#KiGohE@jB zE8D`4>;*w?>G#Lw|L+6%?}zx`_Xu>9T1KWlL<1fi4f}W)>5;{>L^!;OfY}o5v{UXq zm@fNcfPaiwyyE8f2jwHbgxoCQPe=SOde}MHUeUT|Y!NLskJyd-iq6EBxkXI18K1Jc zt5(0BYM3K&D3N(5gkRmXwGx?TXW+A_8;$rh7oR35BKlLzP!p-1nqpP&k5rX&-^chr zNE8MLlyOqc2&hvlbjykiQ!7*L2$j2@QMxy@;Ecy>|7OLPWXtuVcxX13Y89!gT(*g0E8I}+ip?~idFM~^B zBTOm0s)4t*yu6AA-zezv<#DT;`@GK4D1*)D=!k=Uh(i)PQ(Lq=lbPZ5nFIp7r)mzv zCKIBimNA&|HCJ|}zUoU`}6?|1Kb|9yWLU1K!8R@JIiRnL5$Ip=RQ@p{q#z(ZvLd(M!X ze&a@V;O1;H-l|(?%Q;*wjqcs?nSz?)@RLZ-mX*=lZf}0^N=FEdx)k8C0L$>zLjdp5 z>Z3=Lw={NP2gWdsiwyjFgMilOa+6HKsKW9-Y$JVsZsc#yJW_NbC-wwEvHTxcR(3YM zA>?%RYxgG)E%Gm#Wk#%L!o8~NKWFyVk*$zLX5#@>m{im9{1ZB9UQ!CUN+xBH2L=SB z{e%tvwVv{q$jG^9AA*I7Hrti3+3<;@lkEE87R5ZN8|lFj&2!ZcW-(cbD=mr+rVf4d zt;E7tSIx#WfERn0A)E%Koyf?Bb57d=ccX{Lq-Y}ThsT^Yu=v@|JXZ(SDgWSVH-tkJ z;J@KV|1)xy&JI1)%r-<>zR&XH+e|KV^QL|G5E@`G13xaVeH~1@P>`8DQ7+{=r)67G zaS*JTEfaOZ10GuLS~G50($$^7?T^`UfW{RLqgZEjAJ}QL@>{?>h4lq{THYNEiv!(&{t2_>wAZ#V#G5yDUZn=~Emd>=C?O?n$Z{h@^GK=eXEzIK~MXS5ia zIx3?t1a}`z$si-C)Q#8JsII<@kYo$;g6|$lxpn;KgZ#RTwNbQ=L@w58dAgZ6?w#5~ zI`&<9>{~ZtF!X*mq<&14*{{4L?0} z$_E8TuoAcQYyQnQa0kgVU~ann6elrmFOpTIOwEU=!JrSQJs5hd?ZhJ_BdmgtHqWqf ze83|=5Tu4)crO%1*(If_D^@7HE-IX!)O~F$b5XG4QiJNsmHI46u$!KX^b4;(h3koX zPhIrIN$ZJse@{`3R_oYUQl=z-l08(lJbU;WqhV`4xZ_{c?nuH@S1u(ISJ&*g&Fw!R4NS>{;bp3@tM z=; zTx{8hOZXT3sEE zip3Y68Hayg9?cek(QkTVpLwnP(T9lUTV6Nis*oV*t>M3zMGM{;g3qCyUjlRgxPk6( zLII;2aiwNcqn>jP#3NsT#{frgj5+qBUe2i8sTwVLYfEnT%=`&|$6g~fO5ZFO z@4e+^(Z9O&@aU>Z9zbH9wq>Cz%sXb%+Gc{uHuIUbT&^p+E)%nJ{I~7J20eTk%=i>B zaGtd&O3Wi2_{c|>6sl#Lf0``u7~nFfyt6FBlip%{vt`5-Hjre*V#W(Qp|N$(ZXQ`+ z!*gqa*nq ziEZMGNq_%nB9g_oJ?x~*30aA-y9`0b(Q5pLSzahmE4+V2~0SbD2t*dWocf^83v?G*sm zMDRq0w{-+5>6q4X*JFp*8T*R5lzO4^2jBNPP0tm?+6K^%gGM?KpKNosIySHKX**`f zuS)Jg+G*A^1rccCVg(r={YBRI-*!31#9dj-^$ONze|X`<=rVn?peYI`5)@}lO;RE-o zc^L->$)(f6@7fJtuA!gR2}?BWjY;<-g%%Xq8ikCcf?kV1WztNVD*�jgcqARe!wS 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 literal 0 HcmV?d00001 From d47fee9c0b904f95444aa78381b94334affa868e Mon Sep 17 00:00:00 2001 From: Khuzaymah Bin Haris Date: Sat, 13 Jun 2026 14:20:17 -0600 Subject: [PATCH 18/19] Note opened PR link in PR_NOTES. Co-authored-by: Cursor --- waybionic_rviz_plugins/docs/PR_NOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/waybionic_rviz_plugins/docs/PR_NOTES.md b/waybionic_rviz_plugins/docs/PR_NOTES.md index c792ed9..7781cc4 100644 --- a/waybionic_rviz_plugins/docs/PR_NOTES.md +++ b/waybionic_rviz_plugins/docs/PR_NOTES.md @@ -102,7 +102,7 @@ The doctor view was also launched briefly under a timeout to verify RViz starts ## PR Status -Use this file as the PR description source. Review screenshots are at the top for reviewer context only. +Opened as https://github.com/Waybionic/waybionic_ground_station/pull/2 against `main`. Review screenshots at the top of this file are for reviewer context only. ## Known Limitations From 773ae3b85fffac9cf777921201a2aa14a9481302 Mon Sep 17 00:00:00 2001 From: Khuzaymah Bin Haris Date: Sun, 21 Jun 2026 21:46:03 -0600 Subject: [PATCH 19/19] Focus PR on RViz diagnostics only and add local test publisher. Remove SurgeonCameraPanel, doctor launch/config, and related docs or screenshots. Add colcon test and lint wiring, replace placeholder maintainer metadata, and add a temporary /diagnostics publisher for mock/live validation while backend publishing is unavailable. Co-authored-by: Cursor --- waybionic_rviz_plugins/CMakeLists.txt | 24 ++- waybionic_rviz_plugins/README.md | 122 ++++++------ .../config/doctor_camera_view.rviz | 97 ---------- .../docs/DIAGNOSTICS_CONTRACT.md | 2 +- .../docs/GROUND_STATION_RVIZ_UI.md | 59 +++--- waybionic_rviz_plugins/docs/PR_NOTES.md | 73 ++++--- .../surgeon_camera_placeholder.png | Bin 76375 -> 0 bytes .../surgeon_camera_panel.hpp | 37 ---- .../launch/doctor_view.launch.py | 22 --- .../temporary_diagnostics_publisher.launch.py | 39 ++++ waybionic_rviz_plugins/package.xml | 11 +- waybionic_rviz_plugins/plugin_description.xml | 8 - .../temporary_diagnostics_publisher.py | 179 ++++++++++++++++++ .../src/surgeon_camera_panel.cpp | 163 ---------------- .../test/test_package_metadata.py | 37 ++++ 15 files changed, 418 insertions(+), 455 deletions(-) delete mode 100644 waybionic_rviz_plugins/config/doctor_camera_view.rviz delete mode 100644 waybionic_rviz_plugins/docs/screenshots/surgeon_camera_placeholder.png delete mode 100644 waybionic_rviz_plugins/include/waybionic_rviz_plugins/surgeon_camera_panel.hpp delete mode 100644 waybionic_rviz_plugins/launch/doctor_view.launch.py create mode 100644 waybionic_rviz_plugins/launch/temporary_diagnostics_publisher.launch.py create mode 100644 waybionic_rviz_plugins/scripts/temporary_diagnostics_publisher.py delete mode 100644 waybionic_rviz_plugins/src/surgeon_camera_panel.cpp create mode 100644 waybionic_rviz_plugins/test/test_package_metadata.py diff --git a/waybionic_rviz_plugins/CMakeLists.txt b/waybionic_rviz_plugins/CMakeLists.txt index aadfe53..2d8ec7f 100644 --- a/waybionic_rviz_plugins/CMakeLists.txt +++ b/waybionic_rviz_plugins/CMakeLists.txt @@ -15,7 +15,6 @@ find_package(Qt5 REQUIRED COMPONENTS Widgets) find_package(rclcpp REQUIRED) find_package(rviz_common REQUIRED) find_package(rviz_default_plugins REQUIRED) -find_package(sensor_msgs REQUIRED) set(THIS_PACKAGE_INCLUDE_DEPENDS diagnostic_msgs @@ -23,18 +22,15 @@ set(THIS_PACKAGE_INCLUDE_DEPENDS rclcpp rviz_common rviz_default_plugins - sensor_msgs ) add_library(${PROJECT_NAME} SHARED include/waybionic_rviz_plugins/diagnostics_source.hpp include/waybionic_rviz_plugins/diagnostics_panel.hpp include/waybionic_rviz_plugins/ros_diagnostics_source.hpp - include/waybionic_rviz_plugins/surgeon_camera_panel.hpp src/diagnostics_panel.cpp src/mock_diagnostics_source.cpp src/ros_diagnostics_source.cpp - src/surgeon_camera_panel.cpp ) target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17) @@ -70,6 +66,26 @@ install( DESTINATION share/${PROJECT_NAME} ) +install( + PROGRAMS scripts/temporary_diagnostics_publisher.py + DESTINATION lib/${PROJECT_NAME} +) + +if(BUILD_TESTING) + find_package(ament_cmake_lint_cmake REQUIRED) + find_package(ament_cmake_pytest REQUIRED) + find_package(ament_cmake_xmllint REQUIRED) + + ament_lint_cmake() + ament_xmllint() + + ament_add_pytest_test( + test_package_metadata + test/test_package_metadata.py + TIMEOUT 60 + ) +endif() + ament_export_targets(export_${PROJECT_NAME} HAS_LIBRARY_TARGET) ament_export_dependencies(${THIS_PACKAGE_INCLUDE_DEPENDS}) ament_package() diff --git a/waybionic_rviz_plugins/README.md b/waybionic_rviz_plugins/README.md index 5fb9716..eaf0153 100644 --- a/waybionic_rviz_plugins/README.md +++ b/waybionic_rviz_plugins/README.md @@ -1,12 +1,12 @@ # WayBionic RViz2 Diagnostics Plugin -RViz2-native diagnostics and monitoring UI for WayBionic. This package is a generic foundation plugin: it does not require Annin/AR4 packages for the default engineer or doctor views. +RViz2-native diagnostics and monitoring UI for WayBionic. This package is diagnostics-only: it provides the engineer monitoring panel, mock/live diagnostics switching, and a temporary `/diagnostics` publisher for local validation. Monitoring-only scope: - No motor commands from this package. - No robot control or safety-critical logic. -- No camera driver or camera pipeline implementation. +- No camera or doctor/surgeon UI in this PR (handled separately later). - Mock diagnostics for validation while backend `/diagnostics` is not ready. ## Quickstart @@ -20,40 +20,47 @@ rosdep install --from-paths src --ignore-src -r -y colcon build --packages-select waybionic_rviz_plugins --symlink-install source install/setup.bash ros2 launch waybionic_rviz_plugins engineer_view.launch.py -ros2 launch waybionic_rviz_plugins doctor_view.launch.py +``` + +Run package tests: + +```bash +colcon test --packages-select waybionic_rviz_plugins +colcon test-result --verbose ``` ## Package Layout ```text waybionic_rviz_plugins/ - CMakeLists.txt # Builds the shared RViz plugin library + CMakeLists.txt # Builds the shared RViz plugin library + tests package.xml # Generic deps only; no Annin/AR4 exec deps - plugin_description.xml # Registers DiagnosticsPanel + SurgeonCameraPanel + plugin_description.xml # Registers DiagnosticsPanel + scripts/ + temporary_diagnostics_publisher.py include/waybionic_rviz_plugins/ diagnostics_contract.hpp # Normalized DiagnosticMessage model diagnostics_source.hpp # DiagnosticsSource interface diagnostics_panel.hpp # Engineer monitoring panel mock_diagnostics_source.hpp ros_diagnostics_source.hpp - surgeon_camera_panel.hpp # Doctor/surgeon placeholder panel src/ diagnostics_panel.cpp mock_diagnostics_source.cpp ros_diagnostics_source.cpp - surgeon_camera_panel.cpp config/ engineer_monitoring_view.rviz # Generic engineer layout (default) - doctor_camera_view.rviz # Doctor/surgeon placeholder layout engineer_ar4_demo.rviz # Optional AR4 visualization helper launch/ - engineer_view.launch.py # Generic engineer launch (default) - doctor_view.launch.py # Doctor/surgeon placeholder launch + engineer_view.launch.py + temporary_diagnostics_publisher.launch.py engineer_ar4_demo.launch.py # Optional AR4 helper only + test/ + test_package_metadata.py docs/ - DIAGNOSTICS_CONTRACT.md # Backend /diagnostics mapping contract - GROUND_STATION_RVIZ_UI.md # Extended architecture notes - PR_NOTES.md # Review/PR summary + DIAGNOSTICS_CONTRACT.md + GROUND_STATION_RVIZ_UI.md + PR_NOTES.md ``` ### What each launch/config pair does @@ -61,21 +68,16 @@ waybionic_rviz_plugins/ | Launch | RViz config | Purpose | |--------|-------------|---------| | `engineer_view.launch.py` | `engineer_monitoring_view.rviz` | Generic engineer monitoring; no AR4 | -| `doctor_view.launch.py` | `doctor_camera_view.rviz` | Surgeon camera placeholder layout | +| `temporary_diagnostics_publisher.launch.py` | — | Temporary `/diagnostics` demo publisher | | `engineer_ar4_demo.launch.py` | `engineer_ar4_demo.rviz` | Optional passive AR4 robot viz | Core plugin dependencies are ROS/RViz/Qt only (`rclcpp`, `rviz_common`, `diagnostic_msgs`, etc.). AR4/Annin packages are required only for the optional `engineer_ar4_demo` helper. -## RViz Panels +## RViz Panel -Both panels appear under **Panels → Add Panel → `waybionic_rviz_plugins`**: +`DiagnosticsPanel` appears under **Panels → Add Panel → `waybionic_rviz_plugins`**. -| Panel | Role | -|-------|------| -| `DiagnosticsPanel` | Engineer monitoring: telemetry table, alerts, mock/live diagnostics | -| `SurgeonCameraPanel` | Doctor/surgeon placeholder: topic names, waiting state, scope notes | - -Saved launch layouts are still the recommended way to open a clean engineer or doctor view. Panels can also be docked manually in one RViz session. +It provides engineer monitoring: telemetry table, alerts, and mock/live diagnostics switching. ## Engineer View @@ -102,43 +104,41 @@ Live mode with no publisher yet shows a stable waiting state (`Waiting for DiagnosticMessage -> DiagnosticsPanel - RosDiagnosticsSource -> DiagnosticMessage -> DiagnosticsPanel -``` - -`RosDiagnosticsSource` maps ROS diagnostic levels and fields into the internal `DiagnosticMessage` model before the Qt panel renders them. See `docs/DIAGNOSTICS_CONTRACT.md` for the full mapping Korede/backend should follow. +### Temporary diagnostics publisher -## Doctor / Surgeon Placeholder View +While Korede/backend publishing is unavailable, use the temporary demo publisher: ```bash -ros2 launch waybionic_rviz_plugins doctor_view.launch.py +ros2 launch waybionic_rviz_plugins temporary_diagnostics_publisher.launch.py +ros2 launch waybionic_rviz_plugins temporary_diagnostics_publisher.launch.py mode:=fault +ros2 launch waybionic_rviz_plugins temporary_diagnostics_publisher.launch.py mode:=stale +ros2 launch waybionic_rviz_plugins temporary_diagnostics_publisher.launch.py mode:=cycle ``` -Opens `config/doctor_camera_view.rviz` with: +Then open live diagnostics in the engineer panel: -- RViz Image displays for placeholder camera topics -- Docked `WayBionic Surgeon Camera` panel (`SurgeonCameraPanel`) - -Placeholder topics until Gianna/electrical select actual camera hardware: +```bash +ros2 launch waybionic_rviz_plugins engineer_view.launch.py use_mock_diagnostics:=false +``` -- `/camera/camera/color/image_raw` (primary) -- `/surgeon/secondary/image_raw` (secondary placeholder) +Modes: -Both Image displays are configured in the RViz layout, but no camera feed will appear until a driver publishes those topics. The surgeon panel shows `Waiting for camera feed` and does not subscribe to images directly. +| Mode | Behavior | +|------|----------| +| `normal` | OK telemetry for board, motor, and IMU signals | +| `fault` | High board temperature + stale IMU heartbeat | +| `stale` | All sample signals published as STALE | +| `cycle` | Rotates through normal, fault, and stale every 5 seconds | -## Switching Between Views +### Diagnostics architecture -Engineer and doctor are separate saved layouts. Options: +```text +DiagnosticsSource + MockDiagnosticsSource -> DiagnosticMessage -> DiagnosticsPanel + RosDiagnosticsSource -> DiagnosticMessage -> DiagnosticsPanel +``` -1. Close one window and launch the other launch file. -2. In RViz: **File → Open Config** and load the other layout from the installed package: - - Engineer: `.../share/waybionic_rviz_plugins/config/engineer_monitoring_view.rviz` - - Doctor: `.../share/waybionic_rviz_plugins/config/doctor_camera_view.rviz` -3. Dock `DiagnosticsPanel` or `SurgeonCameraPanel` manually via the Panels menu. +`RosDiagnosticsSource` maps ROS diagnostic levels and fields into the internal `DiagnosticMessage` model before the Qt panel renders them. See `docs/DIAGNOSTICS_CONTRACT.md` for the full mapping Korede/backend should follow. ## Optional AR4 Visualization Helper @@ -151,31 +151,19 @@ ros2 launch waybionic_rviz_plugins engineer_ar4_demo.launch.py ar_model:=mk3 inc Requires an AR4/Annin workspace with `annin_ar4_description`, `xacro`, `robot_state_publisher`, and `joint_state_publisher`. These are not core dependencies of `waybionic_rviz_plugins`. -## Mock-Only vs Live - -**Mock-only today (synthetic, for UI validation):** - -- `Mock Normal` / `Mock Fault` controls in the engineer panel -- Synthetic telemetry values in mock mode - -**Live path (ready for backend):** - -- `use_mock_diagnostics:=false` -- `diagnostics_topic:=/diagnostics` (or another agreed topic) -- `RosDiagnosticsSource` in `src/ros_diagnostics_source.cpp` - -**Placeholder only (no real pipeline yet):** +## Platform Notes -- Doctor/surgeon camera topics and `SurgeonCameraPanel` waiting state +- Primary validation target is Ubuntu/WSL2 with ROS 2 Jazzy. +- A Mac/RoboStack RViz shutdown crash is not treated as a merge blocker unless it is reproduced on Ubuntu/WSL2. ## Related Docs - `docs/DIAGNOSTICS_CONTRACT.md` — normalized diagnostic model and ROS mapping -- `docs/GROUND_STATION_RVIZ_UI.md` — extended architecture and view notes -- `docs/PR_NOTES.md` — review summary, testing, and PR description source +- `docs/GROUND_STATION_RVIZ_UI.md` — extended architecture notes +- `docs/PR_NOTES.md` — review summary and PR description source ## Follow-Ups - Test engineer layout against Harold's WayBionic placeholder robot once `origin/rebuild/waybionic-foundation` is merge-ready. -- Connect live camera topic names after hardware selection. -- Validate live `/diagnostics` once Korede/backend publishes stable data. +- Validate live `/diagnostics` against Korede/backend once stable publishing is available. +- Camera/doctor low-latency workflow will be handled in a separate PR. diff --git a/waybionic_rviz_plugins/config/doctor_camera_view.rviz b/waybionic_rviz_plugins/config/doctor_camera_view.rviz deleted file mode 100644 index ad9fc4c..0000000 --- a/waybionic_rviz_plugins/config/doctor_camera_view.rviz +++ /dev/null @@ -1,97 +0,0 @@ -Panels: - - Class: rviz_common/Displays - Help Height: 70 - Name: Displays - Property Tree Widget: - Expanded: ~ - Splitter Ratio: 0.5 - Tree Height: 280 - - Class: waybionic_rviz_plugins/SurgeonCameraPanel - Name: WayBionic Surgeon Camera - Primary Camera Topic: /camera/camera/color/image_raw - Secondary Camera Topic: /surgeon/secondary/image_raw -Visualization Manager: - Class: "" - Displays: - - Class: rviz_default_plugins/Image - Enabled: true - Max Value: 1 - Median window: 5 - Min Value: 0 - Name: Surgeon Primary Camera - Normalize Range: true - Topic: - Depth: 5 - Durability Policy: Volatile - History Policy: Keep Last - Reliability Policy: Reliable - Value: /camera/camera/color/image_raw - Value: true - - Class: rviz_default_plugins/Image - Enabled: true - Max Value: 1 - Median window: 5 - Min Value: 0 - Name: Surgeon Secondary Camera Placeholder - Normalize Range: true - Topic: - Depth: 5 - Durability Policy: Volatile - History Policy: Keep Last - Reliability Policy: Reliable - Value: /surgeon/secondary/image_raw - Value: true - Enabled: true - Global Options: - Background Color: 0; 0; 0 - Fixed Frame: world - Frame Rate: 30 - Name: root - Tools: - - Class: rviz_default_plugins/Interact - Hide Inactive Objects: true - - Class: rviz_default_plugins/MoveCamera - - Class: rviz_default_plugins/Select - Transformation: - Current: - Class: rviz_default_plugins/TF - Value: true - Views: - Current: - Class: rviz_default_plugins/Orbit - Distance: 1 - Enable Stereo Rendering: - Stereo Eye Separation: 0.05999999865889549 - Stereo Focal Distance: 1 - Swap Stereo Eyes: false - Value: false - Focal Point: - X: 0 - Y: 0 - Z: 0 - Focal Shape Fixed Size: true - Focal Shape Size: 0.05000000074505806 - Invert Z Axis: false - Name: Current View - Near Clip Distance: 0.009999999776482582 - Pitch: 0.28479644656181335 - Target Frame: - Value: Orbit (rviz) - Yaw: 4.648182392120361 - Saved: ~ -Window Geometry: - Displays: - collapsed: false - Height: 995 - Hide Left Dock: false - Hide Right Dock: true - QMainWindow State: 000000ff00000000fd0000000100000000000001560000038dfc0200000003fb0000004800530075007200670065006f006e0020005300650063006f006e0064006100720079002000430061006d00650072006100200050006c0061006300650068006f006c006400650072010000003b000000f40000001600fffffffb0000002c00530075007200670065006f006e0020005000720069006d006100720079002000430061006d0065007200610100000135000000f40000001600fffffffb000000100044006900730070006c006100790073010000022f00000199000000c700ffffff000006240000038d00000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 - Surgeon Primary Camera: - collapsed: false - WayBionic Surgeon Camera: - collapsed: false - Surgeon Secondary Camera Placeholder: - collapsed: false - Width: 1920 - X: -32 - Y: -32 diff --git a/waybionic_rviz_plugins/docs/DIAGNOSTICS_CONTRACT.md b/waybionic_rviz_plugins/docs/DIAGNOSTICS_CONTRACT.md index 918ca1a..17d5078 100644 --- a/waybionic_rviz_plugins/docs/DIAGNOSTICS_CONTRACT.md +++ b/waybionic_rviz_plugins/docs/DIAGNOSTICS_CONTRACT.md @@ -115,4 +115,4 @@ Mock mode keeps the `Mock Normal` and `Mock Fault` validation controls enabled. 2. Verify that each `DiagnosticStatus` includes a stable `name`, useful `message`, and value/unit keys where applicable. 3. Launch the engineer view with `use_mock_diagnostics:=false`. 4. Verify mock normal/fault validation states and live backend diagnostics before operator use. -5. Keep camera and robot visualization integration separate; they do not require changes to the diagnostic contract itself. +5. Keep robot visualization integration separate; it does not require changes to the diagnostic contract itself. diff --git a/waybionic_rviz_plugins/docs/GROUND_STATION_RVIZ_UI.md b/waybionic_rviz_plugins/docs/GROUND_STATION_RVIZ_UI.md index 18c2297..5865961 100644 --- a/waybionic_rviz_plugins/docs/GROUND_STATION_RVIZ_UI.md +++ b/waybionic_rviz_plugins/docs/GROUND_STATION_RVIZ_UI.md @@ -1,6 +1,8 @@ # WayBionic RViz2 Ground Station UI -This package contains the WayBionic RViz2-native diagnostics plugin and monitoring layouts. It keeps the operator interface inside RViz2 and avoids modifying RViz source code. +This package contains the WayBionic RViz2-native diagnostics plugin and engineer monitoring layout. It keeps the operator interface inside RViz2 and avoids modifying RViz source code. + +This PR is diagnostics-only. Camera/doctor placeholder work was removed and will be handled later as a separate low-latency/VR/camera workflow. ## Scope @@ -9,8 +11,8 @@ This is a monitoring-first UI package. - It does not send motor commands. - It does not implement robot control. - It does not implement safety-critical logic. -- It does not implement camera streaming drivers. -- It does not require Annin/AR4 packages for the generic engineer or doctor views. +- It does not include camera or doctor/surgeon UI. +- It does not require Annin/AR4 packages for the generic engineer view. ## Build @@ -20,11 +22,11 @@ source /opt/ros/jazzy/setup.bash rosdep install --from-paths src --ignore-src -r -y colcon build --packages-select waybionic_rviz_plugins --symlink-install source install/setup.bash +colcon test --packages-select waybionic_rviz_plugins +colcon test-result --verbose ``` -## Views - -### Engineer Monitoring View +## Engineer Monitoring View ```bash ros2 launch waybionic_rviz_plugins engineer_view.launch.py @@ -43,22 +45,20 @@ ros2 launch waybionic_rviz_plugins engineer_view.launch.py use_mock_diagnostics: - Mock mode uses `MockDiagnosticsSource` and keeps the `Mock Normal` and `Mock Fault` validation controls working. - Live mode uses `RosDiagnosticsSource`, subscribes to the configured diagnostics topic, and disables mock controls. -### Doctor / Surgeon Placeholder View +## Temporary Diagnostics Publisher + +A temporary demo publisher is included for local `/diagnostics` validation while Korede/backend publishing is unavailable: ```bash -ros2 launch waybionic_rviz_plugins doctor_view.launch.py +ros2 launch waybionic_rviz_plugins temporary_diagnostics_publisher.launch.py +ros2 launch waybionic_rviz_plugins temporary_diagnostics_publisher.launch.py mode:=cycle ``` -This opens RViz2 with `config/doctor_camera_view.rviz`. The layout is intentionally camera-focused and avoids engineering telemetry clutter. - -The layout includes RViz Image displays and the docked `WayBionic Surgeon Camera` panel. Current camera topics are placeholders until Gianna/electrical select the actual camera hardware and ROS topics: - -- `/camera/camera/color/image_raw` -- `/surgeon/secondary/image_raw` disabled by default +Supported modes: `normal`, `fault`, `stale`, `cycle`. -The `SurgeonCameraPanel` is a placeholder/status panel only. It shows the configured topics, waits for a future camera feed, and does not start camera nodes or claim a real camera pipeline exists. +Sample signals: `board.temperature`, `motor.current`, `imu.roll`, `imu.pitch`, `imu.yaw`, `imu.heartbeat`. -### Optional AR4 Visualization Helper +## Optional AR4 Visualization Helper ```bash ros2 launch waybionic_rviz_plugins engineer_ar4_demo.launch.py @@ -69,14 +69,13 @@ This launches passive AR4 robot visualization using `annin_ar4_description`, `xa ## Architecture ```text -Doctor view: - doctor_view.launch.py -> doctor_camera_view.rviz -> RViz Image displays - -> WayBionic Surgeon Camera panel - Generic engineer view: engineer_view.launch.py -> engineer_monitoring_view.rviz -> WayBionic Diagnostics panel +Temporary backend demo: + temporary_diagnostics_publisher.launch.py -> /diagnostics + Optional AR4 visualization helper: engineer_ar4_demo.launch.py -> engineer_ar4_demo.rviz -> AR4 robot_description + passive joint states @@ -87,12 +86,10 @@ Diagnostics flow: -> RosDiagnosticsSource -> DiagnosticMessage -> DiagnosticsPanel ``` -RViz panel plugins: +RViz panel plugin: ```text -Panels -> Add Panel -> waybionic_rviz_plugins - -> DiagnosticsPanel - -> SurgeonCameraPanel +Panels -> Add Panel -> waybionic_rviz_plugins -> DiagnosticsPanel ``` ## Live Data Integration @@ -101,6 +98,11 @@ Panels -> Add Panel -> waybionic_rviz_plugins The panel remains monitoring-only in both mock and live modes. +## Platform Notes + +- Primary validation target is Ubuntu/WSL2 with ROS 2 Jazzy. +- A Mac/RoboStack RViz shutdown crash is not treated as a merge blocker unless it is reproduced on Ubuntu/WSL2. + ## Package Contents ```text @@ -111,20 +113,21 @@ waybionic_rviz_plugins/ diagnostics_source.hpp mock_diagnostics_source.hpp ros_diagnostics_source.hpp - surgeon_camera_panel.hpp src/ diagnostics_panel.cpp mock_diagnostics_source.cpp ros_diagnostics_source.cpp - surgeon_camera_panel.cpp + scripts/ + temporary_diagnostics_publisher.py config/ engineer_monitoring_view.rviz engineer_ar4_demo.rviz - doctor_camera_view.rviz launch/ engineer_view.launch.py + temporary_diagnostics_publisher.launch.py engineer_ar4_demo.launch.py - doctor_view.launch.py + test/ + test_package_metadata.py docs/ DIAGNOSTICS_CONTRACT.md GROUND_STATION_RVIZ_UI.md diff --git a/waybionic_rviz_plugins/docs/PR_NOTES.md b/waybionic_rviz_plugins/docs/PR_NOTES.md index 7781cc4..a2bfa98 100644 --- a/waybionic_rviz_plugins/docs/PR_NOTES.md +++ b/waybionic_rviz_plugins/docs/PR_NOTES.md @@ -8,13 +8,11 @@ These images are included only to help reviewers see the UI quickly. They are no | --- | --- | | ![Engineer mock normal](screenshots/engineer_mock_normal.png) | ![Engineer mock fault](screenshots/engineer_mock_fault.png) | -Doctor/surgeon placeholder panel: - -![Surgeon camera placeholder](screenshots/surgeon_camera_placeholder.png) - ## Summary -This branch makes `waybionic_rviz_plugins` a generic WayBionic RViz2 diagnostics foundation package. The default engineer launch opens RViz with the diagnostics panel, mock diagnostics are kept for validation, and live mode is ready to consume ROS 2 diagnostics without binding the panel UI to raw ROS messages. +This PR makes `waybionic_rviz_plugins` a focused WayBionic RViz2 diagnostics package. It keeps the engineer monitoring panel, mock/live diagnostics switching, and a temporary `/diagnostics` publisher for local validation. + +Camera/doctor placeholder work was removed from this PR and will be handled separately later. ## Primary Launch Commands @@ -22,23 +20,36 @@ This branch makes `waybionic_rviz_plugins` a generic WayBionic RViz2 diagnostics colcon build --packages-select waybionic_rviz_plugins --symlink-install source install/setup.bash ros2 launch waybionic_rviz_plugins engineer_view.launch.py -ros2 launch waybionic_rviz_plugins doctor_view.launch.py ``` -Live diagnostics mode: +Mock diagnostics (default): + +```bash +ros2 launch waybionic_rviz_plugins engineer_view.launch.py use_mock_diagnostics:=true +``` + +Live diagnostics: ```bash ros2 launch waybionic_rviz_plugins engineer_view.launch.py use_mock_diagnostics:=false diagnostics_topic:=/diagnostics ``` +Temporary backend/demo publisher: + +```bash +ros2 launch waybionic_rviz_plugins temporary_diagnostics_publisher.launch.py +ros2 launch waybionic_rviz_plugins temporary_diagnostics_publisher.launch.py mode:=cycle +``` + ## What Works - Engineer RViz view with the `WayBionic Diagnostics` panel. - Mock normal and fault validation states. - Alert banner/table rendering from normalized `DiagnosticMessage` rows. - Live diagnostics source path that subscribes to a `diagnostic_msgs/msg/DiagnosticArray` topic. -- Doctor/surgeon placeholder camera layout with a docked `SurgeonCameraPanel`. -- Both RViz panels are registered under `waybionic_rviz_plugins`: `DiagnosticsPanel` and `SurgeonCameraPanel`. +- Temporary `/diagnostics` publisher with `normal`, `fault`, `stale`, and `cycle` modes. +- Package metadata and lint/test wiring via `colcon test`. +- Only `DiagnosticsPanel` is registered in `plugin_description.xml`. ## Mock Diagnostics Mode @@ -46,7 +57,7 @@ Mock mode is enabled by default through `use_mock_diagnostics:=true`. It uses sy ## Live `/diagnostics` Path -`RosDiagnosticsSource` subscribes to `/diagnostics` by default, or another topic passed as `diagnostics_topic:=`, and converts each status into the internal `DiagnosticMessage` model before the panel renders it. If no backend is publishing yet, the panel stays stable and shows that live diagnostics mode is active while it waits for messages. +`RosDiagnosticsSource` subscribes to `/diagnostics` by default, or another topic passed as `diagnostics_topic:=`, and converts each status into the internal `DiagnosticMessage` model before the panel renders it. Expected mapping: @@ -59,17 +70,31 @@ Expected mapping: - `DiagnosticStatus.values` -> `value` and `unit` when those keys are present - `DiagnosticArray.header.stamp`, or receive time when unset, -> `timestamp` -## Doctor / Surgeon Camera Placeholder View +## Temporary Diagnostics Publisher + +`scripts/temporary_diagnostics_publisher.py` publishes sample `DiagnosticArray` messages for local testing while Korede/backend publishing is unavailable. + +Modes: -`doctor_view.launch.py` opens `config/doctor_camera_view.rviz` with RViz Image displays and the docked `WayBionic Surgeon Camera` placeholder panel. The configured topics are placeholders until Gianna/electrical select the real camera hardware and ROS topics; this package does not claim a camera pipeline exists yet. +- `normal` — OK telemetry +- `fault` — high board temperature + stale IMU heartbeat +- `stale` — all sample signals STALE +- `cycle` — rotate through normal/fault/stale every 5 seconds -The surgeon camera panel is available from RViz through: +## Lint / Test Wiring -```text -Panels -> Add Panel -> waybionic_rviz_plugins -> SurgeonCameraPanel +```bash +colcon test --packages-select waybionic_rviz_plugins +colcon test-result --verbose ``` -It shows the placeholder topic names and `Waiting for camera feed`; actual image rendering remains in RViz Image displays once camera topics exist. +Tests verify: + +- `DiagnosticsPanel` is registered in `plugin_description.xml` +- `SurgeonCameraPanel` is not registered +- `package.xml` has no Annin/AR4 dependency +- doctor/camera launch/config files are removed +- temporary diagnostics publisher files exist ## Optional AR4 Visualization Helper @@ -80,16 +105,16 @@ Optional AR4 coupling is isolated in: - `launch/engineer_ar4_demo.launch.py` - `config/engineer_ar4_demo.rviz` -That helper requires an AR4/Annin workspace with `annin_ar4_description`, `xacro`, `robot_state_publisher`, and `joint_state_publisher` available. - ## Testing Performed ```bash source /opt/ros/jazzy/setup.bash colcon build --packages-select waybionic_rviz_plugins --symlink-install source install/setup.bash +colcon test --packages-select waybionic_rviz_plugins +colcon test-result --verbose ros2 launch waybionic_rviz_plugins engineer_view.launch.py --show-args -ros2 launch waybionic_rviz_plugins doctor_view.launch.py --show-args +ros2 launch waybionic_rviz_plugins temporary_diagnostics_publisher.launch.py --show-args ``` Optional AR4 helper, only in a workspace with AR4 dependencies: @@ -98,21 +123,19 @@ Optional AR4 helper, only in a workspace with AR4 dependencies: ros2 launch waybionic_rviz_plugins engineer_ar4_demo.launch.py --show-args ``` -The doctor view was also launched briefly under a timeout to verify RViz starts and loads the placeholder panel without plugin errors. - ## PR Status Opened as https://github.com/Waybionic/waybionic_ground_station/pull/2 against `main`. Review screenshots at the top of this file are for reviewer context only. ## Known Limitations -- Meaningful live rows require Korede/backend to publish stable diagnostics data. +- Meaningful live rows still depend on stable backend publishing once Korede is available. - Stale handling is currently a simple five-second freshness check. -- Doctor view is a placeholder layout only; no real camera hardware or pipeline is implied. - The UI is monitoring-only and does not send motor commands. +- Camera/doctor workflow is out of scope for this PR. ## Next Steps - Confirm final diagnostic signal names and value/unit conventions with backend. -- Connect live camera topic names after hardware selection. -- Add focused runtime validation once a diagnostics publisher is available. +- Merge diagnostics foundation, then handle camera/doctor low-latency workflow separately. +- Add focused runtime validation against Korede/backend once publishing is available. diff --git a/waybionic_rviz_plugins/docs/screenshots/surgeon_camera_placeholder.png b/waybionic_rviz_plugins/docs/screenshots/surgeon_camera_placeholder.png deleted file mode 100644 index 515b16a2ec2b50302c7927ec3312cdcc2c5f0229..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 76375 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

-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 |* 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�jgcqARe!wS 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 diff --git a/waybionic_rviz_plugins/include/waybionic_rviz_plugins/surgeon_camera_panel.hpp b/waybionic_rviz_plugins/include/waybionic_rviz_plugins/surgeon_camera_panel.hpp deleted file mode 100644 index 93ac92d..0000000 --- a/waybionic_rviz_plugins/include/waybionic_rviz_plugins/surgeon_camera_panel.hpp +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef WAYBIONIC_RVIZ_PLUGINS__SURGEON_CAMERA_PANEL_HPP_ -#define WAYBIONIC_RVIZ_PLUGINS__SURGEON_CAMERA_PANEL_HPP_ - -#include - -#include -#include - -class QLabel; - -namespace waybionic_rviz_plugins -{ - -class SurgeonCameraPanel : public rviz_common::Panel -{ - Q_OBJECT - -public: - explicit SurgeonCameraPanel(QWidget * parent = nullptr); - - void save(rviz_common::Config config) const override; - void load(const rviz_common::Config & config) override; - -private: - void buildUi(); - void refreshLabels(); - - QString primary_topic_{"/camera/camera/color/image_raw"}; - QString secondary_topic_{"/surgeon/secondary/image_raw"}; - - QLabel * primary_topic_label_{nullptr}; - QLabel * secondary_topic_label_{nullptr}; -}; - -} // namespace waybionic_rviz_plugins - -#endif // WAYBIONIC_RVIZ_PLUGINS__SURGEON_CAMERA_PANEL_HPP_ diff --git a/waybionic_rviz_plugins/launch/doctor_view.launch.py b/waybionic_rviz_plugins/launch/doctor_view.launch.py deleted file mode 100644 index a8bc3c1..0000000 --- a/waybionic_rviz_plugins/launch/doctor_view.launch.py +++ /dev/null @@ -1,22 +0,0 @@ -from launch import LaunchDescription -from launch_ros.actions import Node -from launch_ros.substitutions import FindPackageShare -from launch.substitutions import PathJoinSubstitution - - -def generate_launch_description(): - rviz_config = PathJoinSubstitution([ - FindPackageShare("waybionic_rviz_plugins"), - "config", - "doctor_camera_view.rviz", - ]) - - return LaunchDescription([ - Node( - package="rviz2", - executable="rviz2", - name="waybionic_doctor_rviz", - output="screen", - arguments=["-d", rviz_config], - ), - ]) diff --git a/waybionic_rviz_plugins/launch/temporary_diagnostics_publisher.launch.py b/waybionic_rviz_plugins/launch/temporary_diagnostics_publisher.launch.py new file mode 100644 index 0000000..97b806a --- /dev/null +++ b/waybionic_rviz_plugins/launch/temporary_diagnostics_publisher.launch.py @@ -0,0 +1,39 @@ +from launch import LaunchDescription +from launch.actions import DeclareLaunchArgument +from launch.substitutions import LaunchConfiguration +from launch_ros.actions import Node + + +def generate_launch_description(): + return LaunchDescription([ + DeclareLaunchArgument( + 'mode', + default_value='normal', + choices=['normal', 'fault', 'stale', 'cycle'], + description=( + 'Diagnostics profile to publish: normal, fault, stale, or cycle ' + '(rotate through normal/fault/stale every 5 seconds).' + ), + ), + DeclareLaunchArgument( + 'topic', + default_value='/diagnostics', + description='diagnostic_msgs/msg/DiagnosticArray topic to publish.', + ), + DeclareLaunchArgument( + 'publish_rate_hz', + default_value='2.0', + description='Publish rate in Hz.', + ), + Node( + package='waybionic_rviz_plugins', + executable='temporary_diagnostics_publisher.py', + name='temporary_diagnostics_publisher', + output='screen', + parameters=[{ + 'mode': LaunchConfiguration('mode'), + 'topic': LaunchConfiguration('topic'), + 'publish_rate_hz': LaunchConfiguration('publish_rate_hz'), + }], + ), + ]) diff --git a/waybionic_rviz_plugins/package.xml b/waybionic_rviz_plugins/package.xml index 97a35f0..04ed636 100644 --- a/waybionic_rviz_plugins/package.xml +++ b/waybionic_rviz_plugins/package.xml @@ -2,8 +2,8 @@ waybionic_rviz_plugins 0.1.0 - RViz2-native ground station panels, layouts, and launch files for WayBionic monitoring. - WayBionic + RViz2 diagnostics panel and engineer monitoring layouts for WayBionic. + Khuzaymah Bin Haris MIT ament_cmake @@ -14,12 +14,17 @@ rclcpp rviz_common rviz_default_plugins - sensor_msgs launch launch_ros + rclpy rviz2 + ament_cmake_lint_cmake + ament_cmake_pytest + ament_cmake_xmllint + python3-pytest + ament_cmake diff --git a/waybionic_rviz_plugins/plugin_description.xml b/waybionic_rviz_plugins/plugin_description.xml index 7a5a31b..faa00a6 100644 --- a/waybionic_rviz_plugins/plugin_description.xml +++ b/waybionic_rviz_plugins/plugin_description.xml @@ -7,12 +7,4 @@ WayBionic ground station diagnostics, telemetry, and alerts panel for RViz2. - - - WayBionic surgeon camera placeholder panel for RViz2. - - diff --git a/waybionic_rviz_plugins/scripts/temporary_diagnostics_publisher.py b/waybionic_rviz_plugins/scripts/temporary_diagnostics_publisher.py new file mode 100644 index 0000000..fdfb433 --- /dev/null +++ b/waybionic_rviz_plugins/scripts/temporary_diagnostics_publisher.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python3 +"""Temporary demo publisher for local /diagnostics validation.""" + +import math + +import rclpy +from diagnostic_msgs.msg import DiagnosticArray, DiagnosticStatus, KeyValue +from rclpy.node import Node + +CYCLE_MODES = ('normal', 'fault', 'stale') +CYCLE_PERIOD_SECONDS = 5.0 + + +def make_status(name, level, message='', value=None, unit=None): + status = DiagnosticStatus() + status.name = name + status.level = level + status.message = message + if value is not None: + status.values.append(KeyValue(key='value', value=str(value))) + if unit is not None: + status.values.append(KeyValue(key='unit', value=unit)) + return status + + +class TemporaryDiagnosticsPublisher(Node): + def __init__(self): + super().__init__('temporary_diagnostics_publisher') + self.declare_parameter('mode', 'normal') + self.declare_parameter('topic', '/diagnostics') + self.declare_parameter('publish_rate_hz', 2.0) + + self.mode = self.get_parameter('mode').get_parameter_value().string_value + topic = self.get_parameter('topic').get_parameter_value().string_value + rate_hz = self.get_parameter('publish_rate_hz').get_parameter_value().double_value + + if self.mode not in {'normal', 'fault', 'stale', 'cycle'}: + raise ValueError( + f"Unsupported mode '{self.mode}'. Use normal, fault, stale, or cycle." + ) + + self.publisher_ = self.create_publisher(DiagnosticArray, topic, 10) + period = 1.0 / rate_hz if rate_hz > 0.0 else 0.5 + self.timer_ = self.create_timer(period, self.publish_diagnostics) + self.cycle_start_ = self.get_clock().now() + + self.get_logger().info( + f"Publishing temporary diagnostics to {topic} in '{self.mode}' mode at {rate_hz:.1f} Hz" + ) + + def active_mode(self): + if self.mode != 'cycle': + return self.mode + + elapsed = (self.get_clock().now() - self.cycle_start_).nanoseconds / 1e9 + index = int(elapsed / CYCLE_PERIOD_SECONDS) % len(CYCLE_MODES) + return CYCLE_MODES[index] + + def publish_diagnostics(self): + message = DiagnosticArray() + message.header.stamp = self.get_clock().now().to_msg() + message.status = self.build_statuses(self.active_mode()) + self.publisher_.publish(message) + + def build_statuses(self, mode): + pulse = math.sin(self.get_clock().now().nanoseconds / 4e9) + + if mode == 'normal': + return [ + make_status( + 'board.temperature', + DiagnosticStatus.OK, + value=f'{42.0 + pulse:.1f}', + unit='C', + ), + make_status( + 'motor.current', + DiagnosticStatus.OK, + value=f'{0.8 + pulse * 0.05:.2f}', + unit='A', + ), + make_status( + 'imu.roll', + DiagnosticStatus.OK, + value=f'{1.2 + pulse * 0.1:.1f}', + unit='deg', + ), + make_status( + 'imu.pitch', + DiagnosticStatus.OK, + value=f'{-0.4 + pulse * 0.1:.1f}', + unit='deg', + ), + make_status( + 'imu.yaw', + DiagnosticStatus.OK, + value=f'{12.9 + pulse * 0.2:.1f}', + unit='deg', + ), + ] + + if mode == 'fault': + return [ + make_status( + 'board.temperature', + DiagnosticStatus.ERROR, + 'High temperature detected', + value=f'{82.0 + pulse * 0.5:.1f}', + unit='C', + ), + make_status('motor.current', DiagnosticStatus.OK, value='0.80', unit='A'), + make_status('imu.roll', DiagnosticStatus.OK, value='1.2', unit='deg'), + make_status('imu.pitch', DiagnosticStatus.OK, value='-0.4', unit='deg'), + make_status('imu.yaw', DiagnosticStatus.OK, value='12.9', unit='deg'), + make_status( + 'imu.heartbeat', + DiagnosticStatus.STALE, + 'Sensor timeout', + ), + ] + + return [ + make_status( + 'board.temperature', + DiagnosticStatus.STALE, + 'No recent board telemetry', + value='--', + unit='C', + ), + make_status( + 'motor.current', + DiagnosticStatus.STALE, + 'No recent motor telemetry', + value='--', + unit='A', + ), + make_status( + 'imu.roll', + DiagnosticStatus.STALE, + 'IMU stream stale', + value='--', + unit='deg', + ), + make_status( + 'imu.pitch', + DiagnosticStatus.STALE, + 'IMU stream stale', + value='--', + unit='deg', + ), + make_status( + 'imu.yaw', + DiagnosticStatus.STALE, + 'IMU stream stale', + value='--', + unit='deg', + ), + make_status( + 'imu.heartbeat', + DiagnosticStatus.STALE, + 'Sensor timeout', + ), + ] + + +def main(args=None): + rclpy.init(args=args) + node = TemporaryDiagnosticsPublisher() + try: + rclpy.spin(node) + except KeyboardInterrupt: + pass + finally: + node.destroy_node() + rclpy.shutdown() + + +if __name__ == '__main__': + main() diff --git a/waybionic_rviz_plugins/src/surgeon_camera_panel.cpp b/waybionic_rviz_plugins/src/surgeon_camera_panel.cpp deleted file mode 100644 index a1d0f73..0000000 --- a/waybionic_rviz_plugins/src/surgeon_camera_panel.cpp +++ /dev/null @@ -1,163 +0,0 @@ -#include "waybionic_rviz_plugins/surgeon_camera_panel.hpp" - -#include -#include -#include -#include - -#include - -namespace waybionic_rviz_plugins -{ -namespace -{ - -constexpr const char * kPanelStyle = R"( -QWidget { - background-color: #071019; - color: #e8f1f8; - font-family: "Segoe UI", "Ubuntu", sans-serif; - font-size: 12px; -} -QFrame#Card { - background-color: #0d1722; - border: 1px solid #284052; - border-radius: 8px; -} -QLabel#PanelTitle { - font-size: 15px; - font-weight: 700; -} -QLabel#StatusWaiting { - background-color: rgba(255, 176, 32, 0.16); - border: 1px solid #ffb020; - border-radius: 6px; - color: #ffb020; - font-weight: 800; - padding: 6px; -} -QLabel#Muted { - color: #8ea3b1; -} -)"; - -QLabel * makeTitle(const QString & text) -{ - auto * label = new QLabel(text); - label->setObjectName("PanelTitle"); - return label; -} - -QLabel * makeMuted(const QString & text) -{ - auto * label = new QLabel(text); - label->setObjectName("Muted"); - return label; -} - -QFrame * makeCard() -{ - auto * card = new QFrame(); - card->setObjectName("Card"); - return card; -} - -} // namespace - -SurgeonCameraPanel::SurgeonCameraPanel(QWidget * parent) -: rviz_common::Panel(parent) -{ - buildUi(); - refreshLabels(); -} - -void SurgeonCameraPanel::save(rviz_common::Config config) const -{ - rviz_common::Panel::save(config); - config.mapSetValue("Primary Camera Topic", primary_topic_); - config.mapSetValue("Secondary Camera Topic", secondary_topic_); -} - -void SurgeonCameraPanel::load(const rviz_common::Config & config) -{ - rviz_common::Panel::load(config); - - QString primary_topic; - if (config.mapGetString("Primary Camera Topic", &primary_topic)) { - primary_topic_ = primary_topic; - } - - QString secondary_topic; - if (config.mapGetString("Secondary Camera Topic", &secondary_topic)) { - secondary_topic_ = secondary_topic; - } - - refreshLabels(); -} - -void SurgeonCameraPanel::buildUi() -{ - setStyleSheet(kPanelStyle); - setMinimumWidth(360); - - auto * root_layout = new QVBoxLayout(this); - root_layout->setContentsMargins(10, 10, 10, 10); - root_layout->setSpacing(10); - - auto * header_card = makeCard(); - auto * header_layout = new QVBoxLayout(header_card); - header_layout->setSpacing(8); - - header_layout->addWidget(makeTitle("WayBionic Surgeon Camera Placeholder")); - - auto * status_label = new QLabel("Waiting for camera feed"); - status_label->setObjectName("StatusWaiting"); - header_layout->addWidget(status_label); - - auto * scope_label = makeMuted( - "Placeholder layout only. Camera hardware, drivers, and final ROS topics are not selected yet."); - scope_label->setWordWrap(true); - header_layout->addWidget(scope_label); - - root_layout->addWidget(header_card); - - auto * topics_card = makeCard(); - auto * topics_layout = new QGridLayout(topics_card); - topics_layout->setVerticalSpacing(6); - topics_layout->addWidget(makeTitle("Configured Placeholder Topics"), 0, 0, 1, 2); - - primary_topic_label_ = new QLabel(); - secondary_topic_label_ = new QLabel(); - - topics_layout->addWidget(makeMuted("Primary Camera"), 1, 0); - topics_layout->addWidget(primary_topic_label_, 1, 1); - topics_layout->addWidget(makeMuted("Secondary Camera"), 2, 0); - topics_layout->addWidget(secondary_topic_label_, 2, 1); - root_layout->addWidget(topics_card); - - auto * safety_card = makeCard(); - auto * safety_layout = new QVBoxLayout(safety_card); - safety_layout->addWidget(makeTitle("Scope")); - - auto * safety_label = makeMuted( - "Monitoring-only panel. It does not subscribe to images directly, start camera nodes, or send motor commands."); - safety_label->setWordWrap(true); - safety_layout->addWidget(safety_label); - root_layout->addWidget(safety_card); - - root_layout->addStretch(1); -} - -void SurgeonCameraPanel::refreshLabels() -{ - if (primary_topic_label_ != nullptr) { - primary_topic_label_->setText(primary_topic_); - } - if (secondary_topic_label_ != nullptr) { - secondary_topic_label_->setText(secondary_topic_); - } -} - -} // namespace waybionic_rviz_plugins - -PLUGINLIB_EXPORT_CLASS(waybionic_rviz_plugins::SurgeonCameraPanel, rviz_common::Panel) diff --git a/waybionic_rviz_plugins/test/test_package_metadata.py b/waybionic_rviz_plugins/test/test_package_metadata.py new file mode 100644 index 0000000..4b3b8e4 --- /dev/null +++ b/waybionic_rviz_plugins/test/test_package_metadata.py @@ -0,0 +1,37 @@ +from pathlib import Path + + +PACKAGE_ROOT = Path(__file__).resolve().parent.parent + + +def read_text(relative_path: str) -> str: + return (PACKAGE_ROOT / relative_path).read_text(encoding='utf-8') + + +def test_plugin_xml_registers_diagnostics_panel(): + plugin_xml = read_text('plugin_description.xml') + assert 'waybionic_rviz_plugins/DiagnosticsPanel' in plugin_xml + assert 'DiagnosticsPanel' in plugin_xml + + +def test_plugin_xml_does_not_register_surgeon_panel(): + plugin_xml = read_text('plugin_description.xml') + assert 'SurgeonCameraPanel' not in plugin_xml + + +def test_package_xml_has_no_annin_or_ar4_dependency(): + package_xml = read_text('package.xml').lower() + assert 'annin' not in package_xml + assert 'ar4' not in package_xml + + +def test_doctor_camera_files_removed(): + assert not (PACKAGE_ROOT / 'launch' / 'doctor_view.launch.py').exists() + assert not (PACKAGE_ROOT / 'config' / 'doctor_camera_view.rviz').exists() + assert not (PACKAGE_ROOT / 'include' / 'waybionic_rviz_plugins' / 'surgeon_camera_panel.hpp').exists() + assert not (PACKAGE_ROOT / 'src' / 'surgeon_camera_panel.cpp').exists() + + +def test_temporary_diagnostics_publisher_exists(): + assert (PACKAGE_ROOT / 'scripts' / 'temporary_diagnostics_publisher.py').exists() + assert (PACKAGE_ROOT / 'launch' / 'temporary_diagnostics_publisher.launch.py').exists()