diff --git a/docs/_static/images/00_ui_view.png b/docs/_static/images/00_ui_view.png new file mode 100644 index 00000000..52c4f044 Binary files /dev/null and b/docs/_static/images/00_ui_view.png differ diff --git a/docs/_static/images/01a_connection_dialog.png b/docs/_static/images/01a_connection_dialog.png new file mode 100644 index 00000000..ddf45aa8 Binary files /dev/null and b/docs/_static/images/01a_connection_dialog.png differ diff --git a/docs/_static/images/01b_connection_dialog_filled.png b/docs/_static/images/01b_connection_dialog_filled.png new file mode 100644 index 00000000..cf89a30c Binary files /dev/null and b/docs/_static/images/01b_connection_dialog_filled.png differ diff --git a/docs/_static/images/02_entity_tree_collapsed.png b/docs/_static/images/02_entity_tree_collapsed.png new file mode 100644 index 00000000..92fb52de Binary files /dev/null and b/docs/_static/images/02_entity_tree_collapsed.png differ diff --git a/docs/_static/images/03_entity_tree_expanded.png b/docs/_static/images/03_entity_tree_expanded.png new file mode 100644 index 00000000..ef5bc40e Binary files /dev/null and b/docs/_static/images/03_entity_tree_expanded.png differ diff --git a/docs/_static/images/04_app_entity_detail.png b/docs/_static/images/04_app_entity_detail.png new file mode 100644 index 00000000..e695b077 Binary files /dev/null and b/docs/_static/images/04_app_entity_detail.png differ diff --git a/docs/_static/images/05_data_view.png b/docs/_static/images/05_data_view.png new file mode 100644 index 00000000..75ad7603 Binary files /dev/null and b/docs/_static/images/05_data_view.png differ diff --git a/docs/_static/images/06_topic_data_view.png b/docs/_static/images/06_topic_data_view.png new file mode 100644 index 00000000..195fefb0 Binary files /dev/null and b/docs/_static/images/06_topic_data_view.png differ diff --git a/docs/_static/images/07_operations_panel.png b/docs/_static/images/07_operations_panel.png new file mode 100644 index 00000000..87ad30d4 Binary files /dev/null and b/docs/_static/images/07_operations_panel.png differ diff --git a/docs/_static/images/08_operations_execution.png b/docs/_static/images/08_operations_execution.png new file mode 100644 index 00000000..e2770ea1 Binary files /dev/null and b/docs/_static/images/08_operations_execution.png differ diff --git a/docs/_static/images/09_configurations_list.png b/docs/_static/images/09_configurations_list.png new file mode 100644 index 00000000..9d949046 Binary files /dev/null and b/docs/_static/images/09_configurations_list.png differ diff --git a/docs/_static/images/10_configuration_edit.png b/docs/_static/images/10_configuration_edit.png new file mode 100644 index 00000000..95bcc483 Binary files /dev/null and b/docs/_static/images/10_configuration_edit.png differ diff --git a/docs/_static/images/12_curl_health.png b/docs/_static/images/12_curl_health.png new file mode 100644 index 00000000..79402e8f Binary files /dev/null and b/docs/_static/images/12_curl_health.png differ diff --git a/docs/_static/images/13_curl_areas_turtlebot3.png b/docs/_static/images/13_curl_areas_turtlebot3.png new file mode 100644 index 00000000..177294f7 Binary files /dev/null and b/docs/_static/images/13_curl_areas_turtlebot3.png differ diff --git a/docs/_static/images/14_curl_topic_odom.png b/docs/_static/images/14_curl_topic_odom.png new file mode 100644 index 00000000..c5697280 Binary files /dev/null and b/docs/_static/images/14_curl_topic_odom.png differ diff --git a/docs/_static/images/15_postman_collection.png b/docs/_static/images/15_postman_collection.png new file mode 100644 index 00000000..087acb79 Binary files /dev/null and b/docs/_static/images/15_postman_collection.png differ diff --git a/docs/_static/images/16_turtlebot_gazebo.png b/docs/_static/images/16_turtlebot_gazebo.png new file mode 100644 index 00000000..221eb1c4 Binary files /dev/null and b/docs/_static/images/16_turtlebot_gazebo.png differ diff --git a/docs/_static/images/17_turtlebot_run_demo_terminal.png b/docs/_static/images/17_turtlebot_run_demo_terminal.png new file mode 100644 index 00000000..027d2c0d Binary files /dev/null and b/docs/_static/images/17_turtlebot_run_demo_terminal.png differ diff --git a/docs/_static/images/18_faults_injected_dashboard.png b/docs/_static/images/18_faults_injected_dashboard.png new file mode 100644 index 00000000..5d66c5ca Binary files /dev/null and b/docs/_static/images/18_faults_injected_dashboard.png differ diff --git a/docs/_static/images/19_faults_injected_app_view.png b/docs/_static/images/19_faults_injected_app_view.png new file mode 100644 index 00000000..2d9df50e Binary files /dev/null and b/docs/_static/images/19_faults_injected_app_view.png differ diff --git a/docs/_static/images/20_sensor_demo_run_terminal.png b/docs/_static/images/20_sensor_demo_run_terminal.png new file mode 100644 index 00000000..9d7acd21 Binary files /dev/null and b/docs/_static/images/20_sensor_demo_run_terminal.png differ diff --git a/docs/_static/images/21_sensor_demo_ui_view.png b/docs/_static/images/21_sensor_demo_ui_view.png new file mode 100644 index 00000000..cf0bea69 Binary files /dev/null and b/docs/_static/images/21_sensor_demo_ui_view.png differ diff --git a/docs/api/cpp.rst b/docs/api/cpp.rst new file mode 100644 index 00000000..6e539761 --- /dev/null +++ b/docs/api/cpp.rst @@ -0,0 +1,104 @@ +C++ API Reference +================= + +This section contains the automatically generated API documentation for the ros2_medkit C++ codebase. + +.. note:: + + This documentation is generated from source code comments using Doxygen and Breathe. + Run the "Docs: Build Doxygen" VS Code task or ``doxygen Doxyfile`` in the docs/ directory + before building Sphinx locally. In CI, Doxygen XML is generated automatically. + +ros2_medkit_gateway +------------------- + +The HTTP/REST gateway that exposes ROS 2 graph via SOVD-compatible API. + +Classes +~~~~~~~ + +.. doxygenclass:: ros2_medkit_gateway::GatewayNode + :members: + +.. doxygenclass:: ros2_medkit_gateway::DiscoveryManager + :members: + +.. doxygenclass:: ros2_medkit_gateway::DataAccessManager + :members: + +.. doxygenclass:: ros2_medkit_gateway::OperationManager + :members: + +.. doxygenclass:: ros2_medkit_gateway::ConfigurationManager + :members: + +Data Models +~~~~~~~~~~~ + +.. doxygenstruct:: ros2_medkit_gateway::QosProfile + :members: + +.. doxygenstruct:: ros2_medkit_gateway::TopicEndpoint + :members: + +.. doxygenstruct:: ros2_medkit_gateway::TopicConnection + :members: + +.. doxygenstruct:: ros2_medkit_gateway::Area + :members: + +.. doxygenstruct:: ros2_medkit_gateway::Component + :members: + +ros2_medkit_fault_manager +------------------------- + +Central fault storage and management node. + +.. doxygenclass:: ros2_medkit_fault_manager::FaultManagerNode + :members: + +.. doxygenclass:: ros2_medkit_fault_manager::FaultStorage + :members: + +.. doxygenclass:: ros2_medkit_fault_manager::InMemoryFaultStorage + :members: + +ros2_medkit_fault_reporter +-------------------------- + +Client library for reporting faults to the fault manager. + +.. doxygenclass:: ros2_medkit_fault_reporter::FaultReporter + :members: + +.. doxygenclass:: ros2_medkit_fault_reporter::LocalFilter + :members: + +ros2_medkit_diagnostic_bridge +----------------------------- + +Bridge node that converts ROS 2 /diagnostics messages to FaultManager faults. + +.. doxygenclass:: ros2_medkit_diagnostic_bridge::DiagnosticBridgeNode + :members: + +ros2_medkit_serialization +------------------------- + +Runtime JSON ↔ ROS 2 message serialization library. + +.. doxygenclass:: ros2_medkit_serialization::JsonSerializer + :members: + +.. doxygenclass:: ros2_medkit_serialization::TypeCache + :members: + +.. doxygenclass:: ros2_medkit_serialization::SerializationError + :members: + +.. doxygenclass:: ros2_medkit_serialization::TypeNotFoundError + :members: + +.. doxygenclass:: ros2_medkit_serialization::JsonConversionError + :members: diff --git a/docs/api/index.rst b/docs/api/index.rst index 325d6c51..c99579db 100644 --- a/docs/api/index.rst +++ b/docs/api/index.rst @@ -1,103 +1,31 @@ -C++ API Reference -================= +API Reference +============= -This section contains the automatically generated API documentation for the ros2_medkit C++ codebase. +This section contains API documentation for ros2_medkit. -.. note:: +.. toctree:: + :maxdepth: 2 - This documentation is generated from source code comments using Doxygen and Breathe. - The Doxygen XML is generated during the documentation build process. + rest + messages + cpp -ros2_medkit_gateway -------------------- - -The HTTP/REST gateway that exposes ROS 2 graph via SOVD-compatible API. - -Classes -~~~~~~~ - -.. doxygenclass:: ros2_medkit_gateway::GatewayNode - :members: - -.. doxygenclass:: ros2_medkit_gateway::DiscoveryManager - :members: - -.. doxygenclass:: ros2_medkit_gateway::DataAccessManager - :members: - -.. doxygenclass:: ros2_medkit_gateway::OperationManager - :members: - -.. doxygenclass:: ros2_medkit_gateway::ConfigurationManager - :members: - -Data Models -~~~~~~~~~~~ - -.. doxygenstruct:: ros2_medkit_gateway::QosProfile - :members: - -.. doxygenstruct:: ros2_medkit_gateway::TopicEndpoint - :members: - -.. doxygenstruct:: ros2_medkit_gateway::TopicConnection - :members: - -.. doxygenstruct:: ros2_medkit_gateway::Area - :members: - -.. doxygenstruct:: ros2_medkit_gateway::Component - :members: - -ros2_medkit_fault_manager -------------------------- +REST API +-------- -Central fault storage and management node. +:doc:`rest` + Complete REST API reference with request/response schemas, error codes, + and usage examples. -.. doxygenclass:: ros2_medkit_fault_manager::FaultManagerNode - :members: - -.. doxygenclass:: ros2_medkit_fault_manager::FaultStorage - :members: - -.. doxygenclass:: ros2_medkit_fault_manager::InMemoryFaultStorage - :members: - -ros2_medkit_fault_reporter --------------------------- - -Client library for reporting faults to the fault manager. - -.. doxygenclass:: ros2_medkit_fault_reporter::FaultReporter - :members: - -.. doxygenclass:: ros2_medkit_fault_reporter::LocalFilter - :members: - -ros2_medkit_diagnostic_bridge ------------------------------ - -Bridge node that converts ROS 2 /diagnostics messages to FaultManager faults. - -.. doxygenclass:: ros2_medkit_diagnostic_bridge::DiagnosticBridgeNode - :members: - -ros2_medkit_serialization -------------------------- - -Runtime JSON ↔ ROS 2 message serialization library. - -.. doxygenclass:: ros2_medkit_serialization::JsonSerializer - :members: - -.. doxygenclass:: ros2_medkit_serialization::TypeCache - :members: +Message Definitions +------------------- -.. doxygenclass:: ros2_medkit_serialization::SerializationError - :members: +:doc:`messages` + ROS 2 message and service interfaces for fault reporting, querying, + and event notifications. -.. doxygenclass:: ros2_medkit_serialization::TypeNotFoundError - :members: +C++ API +------- -.. doxygenclass:: ros2_medkit_serialization::JsonConversionError - :members: +:doc:`cpp` + Doxygen-generated C++ class reference for extending ros2_medkit. diff --git a/docs/api/messages.rst b/docs/api/messages.rst new file mode 100644 index 00000000..80b0ef88 --- /dev/null +++ b/docs/api/messages.rst @@ -0,0 +1,269 @@ +Message Definitions +=================== + +This page documents the ROS 2 message and service interfaces provided by +``ros2_medkit_msgs``. These interfaces are used for fault reporting, querying, +and real-time event notifications. + +.. contents:: Table of Contents + :local: + :depth: 2 + +Messages +-------- + +Fault.msg +~~~~~~~~~ + +Core fault data model representing an aggregated fault condition. + +.. code-block:: text + + # Global fault identifier (e.g., "MOTOR_OVERHEAT", "SENSOR_FAILURE_001") + string fault_code + + # Fault severity level (use SEVERITY_* constants) + uint8 severity + + # Human-readable description of the fault condition + string description + + # Timestamp when this fault was first reported + builtin_interfaces/Time first_occurred + + # Timestamp when this fault was last reported + builtin_interfaces/Time last_occurred + + # Total number of FAILED events aggregated across all sources + uint32 occurrence_count + + # Current fault status (PREFAILED, PREPASSED, CONFIRMED, HEALED, CLEARED) + string status + + # List of source identifiers that have reported this fault + string[] reporting_sources + +**Severity Constants:** + +.. list-table:: + :header-rows: 1 + :widths: 25 10 65 + + * - Constant + - Value + - Description + * - ``SEVERITY_INFO`` + - 0 + - Informational message, no action required + * - ``SEVERITY_WARN`` + - 1 + - Warning, may require attention + * - ``SEVERITY_ERROR`` + - 2 + - Error, impacts functionality + * - ``SEVERITY_CRITICAL`` + - 3 + - Critical error, requires immediate attention. Bypasses debounce. + +**Status Constants:** + +.. list-table:: + :header-rows: 1 + :widths: 20 80 + + * - Status + - Description + * - ``PREFAILED`` + - Debounce counter < 0 but above threshold. Fault detected but not confirmed. + * - ``PREPASSED`` + - Debounce counter > 0 but below threshold. Trending towards resolution. + * - ``CONFIRMED`` + - Debounce counter <= confirmation threshold. Fault active and verified. + * - ``HEALED`` + - Debounce counter >= healing threshold. Resolved by PASSED events. + * - ``CLEARED`` + - Manually acknowledged via ClearFault service. + +**Debounce Lifecycle:** + +.. code-block:: text + + PREFAILED ←────────────────→ PREPASSED + │ (counter crosses 0) │ + ▼ ▼ + CONFIRMED HEALED + │ (retained) + ▼ + CLEARED (manual) + +FaultEvent.msg +~~~~~~~~~~~~~~ + +Real-time fault event notifications published on ``/fault_manager/events``. + +.. code-block:: text + + # Event type (fault_confirmed, fault_cleared, fault_updated) + string event_type + + # The fault data (current state after the event) + Fault fault + + # Timestamp when this event was generated + builtin_interfaces/Time timestamp + + # Symptom codes auto-cleared with root cause (correlation feature) + string[] auto_cleared_codes + +**Event Types:** + +.. list-table:: + :header-rows: 1 + :widths: 25 75 + + * - Event + - Description + * - ``fault_confirmed`` + - Fault transitioned from PREFAILED to CONFIRMED + * - ``fault_cleared`` + - Fault cleared via ClearFault service + * - ``fault_updated`` + - Fault data changed without status transition (e.g., new occurrence) + +MutedFaultInfo.msg +~~~~~~~~~~~~~~~~~~ + +Information about correlated (muted) symptom faults. + +.. code-block:: text + + string fault_code # The muted symptom's fault code + string root_cause_code # Root cause that triggered muting + string rule_id # Correlation rule ID that matched + uint32 delay_ms # Time delay from root cause [ms] + +ClusterInfo.msg +~~~~~~~~~~~~~~~ + +Auto-detected fault cluster information. + +.. code-block:: text + + string cluster_id # Unique cluster ID + string rule_id # Correlation rule ID + string rule_name # Human-readable rule name + string label # Cluster label (e.g., "Communication Storm") + string representative_code # Primary fault code for display + string representative_severity # Severity of representative fault + string[] fault_codes # All fault codes in cluster + uint32 count # Number of faults + builtin_interfaces/Time first_at # First fault timestamp + builtin_interfaces/Time last_at # Last fault timestamp + +Services +-------- + +ReportFault.srv +~~~~~~~~~~~~~~~ + +Report a fault event to the FaultManager. + +**Request:** + +.. code-block:: text + + string fault_code # Global fault identifier (UPPER_SNAKE_CASE) + uint8 event_type # EVENT_FAILED (0) or EVENT_PASSED (1) + uint8 severity # Fault.SEVERITY_* constant (for FAILED events) + string description # Human-readable description + string source_id # Fully qualified node name (e.g., "/powertrain/temp_sensor") + +**Response:** + +.. code-block:: text + + bool accepted # True if event was accepted + +**Example Usage:** + +.. code-block:: cpp + + #include "ros2_medkit_msgs/srv/report_fault.hpp" + + auto request = std::make_shared(); + request->fault_code = "MOTOR_OVERHEAT"; + request->event_type = ros2_medkit_msgs::srv::ReportFault::Request::EVENT_FAILED; + request->severity = ros2_medkit_msgs::msg::Fault::SEVERITY_ERROR; + request->description = "Motor temperature exceeded 85°C"; + request->source_id = get_fully_qualified_name(); + + auto result = client->async_send_request(request); + +ClearFault.srv +~~~~~~~~~~~~~~ + +Clear/acknowledge a fault. + +**Request:** + +.. code-block:: text + + string fault_code # Fault code to clear + +**Response:** + +.. code-block:: text + + bool success # True if fault was found and cleared + string message # Status message or error description + string[] auto_cleared_codes # Symptoms auto-cleared with root cause + +GetFaults.srv +~~~~~~~~~~~~~ + +Query faults from the FaultManager with optional filtering. + +**Request:** + +.. code-block:: text + + bool filter_by_severity # Whether to filter by severity + uint8 severity # Severity to filter by (if filter_by_severity=true) + string[] statuses # Status filter (empty = CONFIRMED only) + bool include_muted # Include correlated symptoms + bool include_clusters # Include cluster information + +**Response:** + +.. code-block:: text + + Fault[] faults # Matching faults + uint32 muted_count # Total muted faults + MutedFaultInfo[] muted_faults # Muted fault details (if requested) + uint32 cluster_count # Total clusters + ClusterInfo[] clusters # Cluster details (if requested) + +**Example: Query all confirmed and pre-failed faults:** + +.. code-block:: cpp + + auto request = std::make_shared(); + request->filter_by_severity = false; + request->statuses = {"CONFIRMED", "PREFAILED"}; + + auto result = client->async_send_request(request); + +GetSnapshots.srv +~~~~~~~~~~~~~~~~ + +Retrieve diagnostic snapshots captured at fault occurrence time. + +See :doc:`/tutorials/snapshots` for detailed usage. + +See Also +-------- + +- :doc:`/design/ros2_medkit_fault_reporter/index` - How to report faults from your nodes +- :doc:`/tutorials/fault-correlation` - Configure fault correlation rules +- :doc:`/tutorials/snapshots` - Diagnostic snapshot capture +- :doc:`/design/ros2_medkit_fault_manager/index` - FaultManager design documentation diff --git a/docs/api/rest.rst b/docs/api/rest.rst new file mode 100644 index 00000000..b05dba06 --- /dev/null +++ b/docs/api/rest.rst @@ -0,0 +1,542 @@ +REST API Reference +================== + +The ros2_medkit gateway exposes a RESTful API for interacting with ROS 2 systems. +All endpoints are prefixed with ``/api/v1``. + +.. note:: + + Entity endpoints (``/components``, ``/apps``) share the same handler implementations. + The examples use ``/components`` but the same patterns apply to ``/apps``. + +.. contents:: Table of Contents + :local: + :depth: 2 + +Server Capabilities +------------------- + +``GET /api/v1/`` + Get server capabilities and entry points. + + **Example Response:** + + .. code-block:: json + + { + "api_version": "1.0.0", + "gateway_version": "0.1.0", + "endpoints": [ + {"path": "/areas", "supported_methods": ["GET"]}, + {"path": "/components", "supported_methods": ["GET"]}, + {"path": "/apps", "supported_methods": ["GET"]} + ] + } + +``GET /api/v1/version-info`` + Get gateway version and status information. + +``GET /api/v1/health`` + Health check endpoint. Returns HTTP 200 if gateway is operational. + +Discovery Endpoints +------------------- + +Areas +~~~~~ + +``GET /api/v1/areas`` + List all areas (logical/physical groupings). + + **Example Response:** + + .. code-block:: json + + { + "items": [ + { + "id": "powertrain", + "name": "Powertrain", + "self": "/api/v1/areas/powertrain" + } + ] + } + +``GET /api/v1/areas/{area_id}`` + Get area capabilities and metadata. + +``GET /api/v1/areas/{area_id}/contains`` + List components contained in this area. + +``GET /api/v1/areas/{area_id}/components`` + List components in a specific area. + +Components +~~~~~~~~~~ + +``GET /api/v1/components`` + List all components with their operations and capabilities. + + **Example Response:** + + .. code-block:: json + + { + "items": [ + { + "id": "temp_sensor", + "name": "temp_sensor", + "self": "/api/v1/components/temp_sensor", + "area": "powertrain", + "resource_collections": ["data", "operations", "configurations", "faults"] + } + ] + } + +``GET /api/v1/components/{component_id}`` + Get component capabilities including available resource collections. + +``GET /api/v1/components/{component_id}/hosts`` + List apps hosted on this component (SOVD 7.6.2.4). + +``GET /api/v1/components/{component_id}/depends-on`` + List component dependencies. + +Apps +~~~~ + +``GET /api/v1/apps`` + List all apps discovered by the gateway. + + The set of apps is populated either from the static manifest (manifest or hybrid mode) + or via heuristic runtime discovery of ROS 2 nodes (see :doc:`/tutorials/heuristic-apps`). + This endpoint may return an empty list if no apps are discovered or if app discovery is + disabled in the gateway configuration. + +``GET /api/v1/apps/{app_id}`` + Get capabilities for a single discovered app. + +Functions +~~~~~~~~~ + +``GET /api/v1/functions`` + List all functions (requires manifest mode or hybrid mode). + +``GET /api/v1/functions/{function_id}`` + Get function capabilities. + +``GET /api/v1/functions/{function_id}/hosts`` + List apps that host this function. + +Data Endpoints +-------------- + +Read and publish data from ROS 2 topics. + +``GET /api/v1/components/{id}/data`` + Read all topic data from an entity. + + **Example Response:** + + .. code-block:: json + + { + "items": [ + { + "name": "temperature", + "data_id": "powertrain%2Fengine%2Ftemperature", + "type": "std_msgs/msg/Float64", + "value": {"data": 85.5}, + "timestamp": "2025-01-15T10:30:00Z" + } + ], + "x-medkit": { + "entity_id": "temp_sensor", + "total_count": 1 + } + } + +``GET /api/v1/components/{id}/data/{topic_path}`` + Read specific topic data. Topic path is URL-encoded (``/`` → ``%2F``). + + **Example:** + + .. code-block:: bash + + curl http://localhost:8080/api/v1/components/temp_sensor/data/powertrain%2Fengine%2Ftemperature + +``PUT /api/v1/components/{id}/data/{topic_path}`` + Publish to a topic. + + - **Content-Type:** application/json + - **200:** Message published successfully + - **400:** Invalid message format + - **401:** Unauthorized (when auth enabled) + + **Example:** + + .. code-block:: bash + + curl -X PUT http://localhost:8080/api/v1/components/brake_actuator/data/chassis%2Fbrakes%2Fcommand \ + -H "Content-Type: application/json" \ + -d '{"data": 50.0}' + +Operations Endpoints +-------------------- + +Execute ROS 2 services and actions. + +List Operations +~~~~~~~~~~~~~~~ + +``GET /api/v1/components/{id}/operations`` + List all operations (services and actions) for an entity. + + **Example Response:** + + .. code-block:: json + + { + "items": [ + { + "id": "calibrate", + "name": "calibrate", + "type": "service", + "service_type": "std_srvs/srv/Trigger", + "schema": { + "request": {}, + "response": {"success": "bool", "message": "string"} + } + }, + { + "id": "long_calibration", + "name": "long_calibration", + "type": "action", + "action_type": "example_interfaces/action/Fibonacci", + "schema": { + "goal": {"order": "int32"}, + "result": {"sequence": "int32[]"}, + "feedback": {"partial_sequence": "int32[]"} + } + } + ], + "x-medkit": { + "entity_id": "calibration", + "total_count": 2 + } + } + +``GET /api/v1/components/{id}/operations/{operation_id}`` + Get operation details and schema. + +Execute Operations +~~~~~~~~~~~~~~~~~~ + +``POST /api/v1/components/{id}/operations/{operation_id}/executions`` + Execute an operation (service call or action goal). + + - **Content-Type:** application/json + - **200:** Service call completed (sync) + - **202:** Action goal accepted (async) + - **400:** Invalid input + - **404:** Operation not found + + **Service Example (synchronous):** + + .. code-block:: bash + + curl -X POST http://localhost:8080/api/v1/components/calibration/operations/calibrate/executions \ + -H "Content-Type: application/json" \ + -d '{}' + + **Action Example (asynchronous):** + + .. code-block:: bash + + curl -X POST http://localhost:8080/api/v1/components/calibration/operations/long_calibration/executions \ + -H "Content-Type: application/json" \ + -d '{"order": 10}' + + **Action Response (202 Accepted):** + + .. code-block:: json + + { + "id": "abc123-def456", + "status": "running" + } + +``GET /api/v1/components/{id}/operations/{operation_id}/executions`` + List all executions for an operation. + +``GET /api/v1/components/{id}/operations/{operation_id}/executions/{execution_id}`` + Get execution status and result. + + **Example Response (completed action):** + + .. code-block:: json + + { + "execution_id": "abc123-def456", + "status": "succeeded", + "result": {"sequence": [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]}, + "feedback": [ + {"partial_sequence": [0, 1]}, + {"partial_sequence": [0, 1, 1, 2, 3]} + ] + } + +``DELETE /api/v1/components/{id}/operations/{operation_id}/executions/{execution_id}`` + Cancel a running execution. + + - **204:** Execution cancelled + - **404:** Execution not found + +Configurations Endpoints +------------------------ + +Manage ROS 2 node parameters. + +``GET /api/v1/components/{id}/configurations`` + List all parameters for an entity. + + **Example Response:** + + .. code-block:: json + + { + "items": [ + { + "name": "publish_rate", + "value": 10.0, + "type": "double", + "description": "Publishing rate in Hz" + }, + { + "name": "sensor_id", + "value": "sensor_001", + "type": "string" + } + ], + "x-medkit": { + "entity_id": "temp_sensor", + "total_count": 2 + } + } + +``GET /api/v1/components/{id}/configurations/{param_name}`` + Get a specific parameter value. + +``PUT /api/v1/components/{id}/configurations/{param_name}`` + Set a parameter value. + + - **Content-Type:** application/json + - **200:** Parameter updated + - **400:** Invalid value + - **404:** Parameter not found + + **Example:** + + .. code-block:: bash + + curl -X PUT http://localhost:8080/api/v1/components/temp_sensor/configurations/publish_rate \ + -H "Content-Type: application/json" \ + -d '{"value": 20.0}' + +``DELETE /api/v1/components/{id}/configurations/{param_name}`` + Reset parameter to default value. + +``DELETE /api/v1/components/{id}/configurations`` + Reset all parameters to default values. + +Faults Endpoints +---------------- + +Query and manage faults. + +.. note:: + + Faults are reported by ROS 2 nodes via the FaultReporter library, not via REST API. + The gateway queries faults from the ros2_medkit_fault_manager node. + +``GET /api/v1/components/{id}/faults`` + List all faults for an entity. + + **Example Response:** + + .. code-block:: json + + { + "items": [ + { + "fault_code": "LIDAR_RANGE_INVALID", + "severity": "ERROR", + "message": "Invalid range configuration: min_range > max_range", + "timestamp": "2025-01-15T10:30:00Z", + "source": "lidar_driver" + } + ], + "x-medkit": { + "entity_id": "lidar_sensor", + "total_count": 1 + } + } + +``GET /api/v1/components/{id}/faults/{fault_code}`` + Get specific fault details. + +``DELETE /api/v1/components/{id}/faults/{fault_code}`` + Clear a fault. + + - **200:** Fault cleared + - **404:** Fault not found + +Authentication Endpoints +------------------------ + +JWT-based authentication with Role-Based Access Control (RBAC). + +.. seealso:: + + :doc:`/tutorials/authentication` for configuration details. + +``POST /api/v1/auth/authorize`` + Authenticate with client credentials. + + **Request:** + + .. code-block:: json + + { + "grant_type": "client_credentials", + "client_id": "admin", + "client_secret": "admin_secret_key" + } + + **Response:** + + .. code-block:: json + + { + "access_token": "eyJhbGciOiJIUzI1NiIs...", + "token_type": "Bearer", + "expires_in": 3600, + "refresh_token": "dGhpcyBpcyBhIHJlZnJlc2g...", + "scope": "admin" + } + +``POST /api/v1/auth/token`` + Refresh access token. + + **Request:** + + .. code-block:: json + + { + "grant_type": "refresh_token", + "refresh_token": "dGhpcyBpcyBhIHJlZnJlc2g..." + } + +``POST /api/v1/auth/revoke`` + Revoke a token. + + **Request:** + + .. code-block:: json + + {"token": "dGhpcyBpcyBhIHJlZnJlc2g..."} + +Error Responses +--------------- + +All error responses follow a consistent format: + +.. code-block:: json + + { + "error": { + "code": "ERR_ENTITY_NOT_FOUND", + "message": "Entity not found", + "details": { + "entity_id": "unknown_component" + } + } + } + +Common Error Codes +~~~~~~~~~~~~~~~~~~ + +.. list-table:: + :header-rows: 1 + :widths: 30 15 55 + + * - Error Code + - HTTP Status + - Description + * - ``ERR_ENTITY_NOT_FOUND`` + - 404 + - The requested entity does not exist + * - ``ERR_RESOURCE_NOT_FOUND`` + - 404 + - The requested resource (topic, service, parameter) does not exist + * - ``ERR_INVALID_INPUT`` + - 400 + - Invalid request body or parameters + * - ``ERR_INVALID_ENTITY_ID`` + - 400 + - Entity ID contains invalid characters + * - ``ERR_OPERATION_FAILED`` + - 500 + - Operation failed during execution + * - ``ERR_TIMEOUT`` + - 504 + - Operation timed out + * - ``ERR_UNAUTHORIZED`` + - 401 + - Authentication required or token invalid + * - ``ERR_FORBIDDEN`` + - 403 + - Insufficient permissions for this operation + +URL Encoding +------------ + +Topic and parameter paths containing ``/`` must be URL-encoded: + +.. list-table:: + :header-rows: 1 + :widths: 50 50 + + * - Original Path + - URL Encoded + * - ``/powertrain/engine/temperature`` + - ``powertrain%2Fengine%2Ftemperature`` + * - ``/chassis/brakes/command`` + - ``chassis%2Fbrakes%2Fcommand`` + +SOVD Compliance +--------------- + +The gateway implements a subset of the SOVD (Service-Oriented Vehicle Diagnostics) specification: + +**SOVD-Compliant Endpoints:** + +- Discovery (``/areas``, ``/components``, ``/apps``, ``/functions``) +- Data access (``/data``) +- Operations (``/operations``, ``/executions``) +- Configurations (``/configurations``) +- Faults (``/faults``) + +**ros2_medkit Extensions:** + +- ``/health`` - Health check endpoint +- ``/version-info`` - Gateway version information +- ``/manifest/status`` - Manifest discovery status +- SSE fault streaming - Real-time fault notifications + +See Also +-------- + +- :doc:`/tutorials/authentication` - Configure authentication +- :doc:`/config/server` - Server configuration options +- `Postman Collection `_ - Interactive API testing diff --git a/docs/changelog.rst b/docs/changelog.rst new file mode 100644 index 00000000..8debaaa7 --- /dev/null +++ b/docs/changelog.rst @@ -0,0 +1,113 @@ +Changelog +========= + +All notable changes to ros2_medkit are documented in this file. + +The format is based on `Keep a Changelog `_, +and this project adheres to `Semantic Versioning `_. + +[0.1.0] - 2026-02-01 +-------------------- + +First public release of ros2_medkit. + +Added +~~~~~ + +**Gateway (ros2_medkit_gateway)** + +- REST API gateway exposing ROS 2 graph via SOVD-compatible endpoints +- Discovery endpoints: ``/areas``, ``/components``, ``/apps``, ``/functions`` +- Data access: Read topic data, publish to topics +- Operations: Call ROS 2 services and actions with execution tracking +- Configurations: Read/write/reset ROS 2 node parameters +- Faults: Query and clear faults from fault manager +- Three discovery modes: runtime_only, hybrid, manifest_only +- Manifest-based discovery with YAML system definitions +- Heuristic app detection in runtime mode +- JWT authentication with RBAC (viewer, operator, configurator, admin roles) +- TLS/HTTPS support with configurable TLS 1.2/1.3 +- CORS configuration for browser clients +- SSE (Server-Sent Events) for real-time fault notifications +- Health check endpoint + +**Fault Manager (ros2_medkit_fault_manager)** + +- Centralized fault storage and management node +- ROS 2 services: ``report_fault``, ``get_faults``, ``clear_fault`` +- AUTOSAR DEM-style debounce lifecycle (PREFAILED → CONFIRMED → HEALED → CLEARED) +- Fault aggregation from multiple sources +- Severity escalation +- In-memory storage with thread-safe implementation + +**Fault Reporter (ros2_medkit_fault_reporter)** + +- Client library for reporting faults from ROS 2 nodes +- Local filtering with configurable threshold and time window +- Fire-and-forget async service calls +- High-severity bypass for immediate fault reporting + +**Diagnostic Bridge (ros2_medkit_diagnostic_bridge)** + +- Bridge node converting ``/diagnostics`` messages to fault manager faults +- Configurable severity mapping from diagnostic status levels +- Support for diagnostic arrays with multiple status entries + +**Serialization (ros2_medkit_serialization)** + +- Runtime JSON ↔ ROS 2 message serialization +- Dynamic message introspection without compile-time type knowledge +- Support for all ROS 2 built-in types, arrays, nested messages +- Type caching for performance + +**Messages (ros2_medkit_msgs)** + +- ``Fault.msg``: Fault status message with severity, timestamps, sources +- ``FaultEvent.msg``: Fault event for subscriptions +- ``ReportFault.srv``: Service for reporting faults +- ``GetFaults.srv``: Service for querying faults with filters +- ``ClearFault.srv``: Service for clearing faults + +**Documentation** + +- Sphinx documentation with Doxygen integration +- Getting Started tutorial +- REST API reference +- Configuration reference (server, discovery, manifest) +- Authentication and HTTPS tutorials +- Docker deployment guide +- Companion project tutorials (web-ui, mcp-server) + +**Tooling** + +- Postman collection for API testing +- VS Code tasks for build/test/launch +- Development container configuration +- GitHub Actions CI/CD pipeline + +Companion Projects +~~~~~~~~~~~~~~~~~~ + +- `sovd_web_ui `_: Web interface for entity browsing +- `ros2_medkit_mcp `_: MCP server for LLM integration + +SOVD Compliance +~~~~~~~~~~~~~~~ + +This release implements a subset of the SOVD (Service-Oriented Vehicle Diagnostics) +specification adapted for ROS 2: + +- Core discovery endpoints (areas, components) +- Extended discovery (apps, functions) via manifest mode +- Data access (read, write) +- Operations (services, actions with executions) +- Configurations (parameters) +- Faults (query, clear) + +Not yet implemented: + +- Bulk data transfer +- Software updates +- Locks +- Triggers +- Communication logs diff --git a/docs/conf.py b/docs/conf.py index 83605896..159768f9 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -42,6 +42,7 @@ "sphinxcontrib.plantuml", "sphinx_design", "breathe", + "sphinx_copybutton", ] # -- Options for Breathe (Doxygen integration) ------------------------------- diff --git a/docs/config/diagnostic-bridge.rst b/docs/config/diagnostic-bridge.rst new file mode 100644 index 00000000..67fbbb6b --- /dev/null +++ b/docs/config/diagnostic-bridge.rst @@ -0,0 +1,201 @@ +Diagnostic Bridge Configuration +================================ + +The ``ros2_medkit_diagnostic_bridge`` node converts standard ROS 2 ``/diagnostics`` messages +to fault events, providing a migration path for existing diagnostic infrastructure. + +.. contents:: Table of Contents + :local: + :depth: 2 + +Overview +-------- + +The diagnostic bridge: + +1. Subscribes to ``/diagnostics`` (or custom topic) +2. Maps ``DiagnosticStatus`` names to fault codes +3. Reports faults to the FaultManager via ``ReportFault`` service + +**Status Mapping:** + +.. list-table:: + :header-rows: 1 + :widths: 25 25 50 + + * - Diagnostic Level + - Fault Event + - Severity + * - OK + - PASSED + - (healing event) + * - WARN + - FAILED + - SEVERITY_WARN (1) + * - ERROR + - FAILED + - SEVERITY_ERROR (2) + * - STALE + - FAILED + - SEVERITY_ERROR (2) + +Parameters +---------- + +.. code-block:: yaml + + diagnostic_bridge: + ros__parameters: + diagnostics_topic: "/diagnostics" # Topic to subscribe to + auto_generate_codes: true # Auto-generate fault codes from names + +.. list-table:: + :header-rows: 1 + :widths: 30 15 55 + + * - Parameter + - Default + - Description + * - ``diagnostics_topic`` + - ``/diagnostics`` + - Topic to subscribe for ``DiagnosticArray`` messages. + * - ``auto_generate_codes`` + - ``true`` + - Automatically generate fault codes from diagnostic names when no explicit + mapping exists. + +Custom Fault Code Mappings +-------------------------- + +Map specific diagnostic names to custom fault codes using the ``name_to_code`` parameter prefix: + +.. code-block:: yaml + + diagnostic_bridge: + ros__parameters: + diagnostics_topic: "/diagnostics" + auto_generate_codes: true + name_to_code: + motor_temp: "MOTOR_OVERHEAT" + battery_level: "LOW_BATTERY" + camera_driver: "CAMERA_FAILURE" + +Or via command line: + +.. code-block:: bash + + ros2 run ros2_medkit_diagnostic_bridge diagnostic_bridge \ + --ros-args \ + -p "name_to_code.motor_temp:=MOTOR_OVERHEAT" \ + -p "name_to_code.battery_level:=LOW_BATTERY" + +Auto-Generated Fault Codes +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When ``auto_generate_codes: true`` and no explicit mapping exists, fault codes are +generated from the diagnostic name: + +1. Convert to uppercase +2. Replace spaces, slashes, and dashes with underscores +3. Remove non-alphanumeric characters (except underscore) +4. Prepend ``DIAG_`` prefix + +**Examples:** + +.. list-table:: + :header-rows: 1 + :widths: 50 50 + + * - Diagnostic Name + - Generated Fault Code + * - ``motor_temp`` + - ``DIAG_MOTOR_TEMP`` + * - ``/camera/driver`` + - ``DIAG_CAMERA_DRIVER`` + * - ``Battery Level Monitor`` + - ``DIAG_BATTERY_LEVEL_MONITOR`` + +Launch File Configuration +------------------------- + +Example launch file: + +.. code-block:: python + + from launch import LaunchDescription + from launch_ros.actions import Node + + def generate_launch_description(): + return LaunchDescription([ + Node( + package='ros2_medkit_diagnostic_bridge', + executable='diagnostic_bridge_node', + name='diagnostic_bridge', + parameters=[{ + 'diagnostics_topic': '/diagnostics', + 'auto_generate_codes': True, + 'name_to_code': { + 'motor_temp': 'MOTOR_OVERHEAT', + 'battery_level': 'LOW_BATTERY', + } + }], + ), + ]) + +Integration with FaultManager +----------------------------- + +The diagnostic bridge requires a running FaultManager to report faults. Ensure the +FaultManager is started before the bridge: + +.. code-block:: bash + + # Terminal 1: Start FaultManager + ros2 run ros2_medkit_fault_manager fault_manager_node + + # Terminal 2: Start Diagnostic Bridge + ros2 run ros2_medkit_diagnostic_bridge diagnostic_bridge_node + +Or use a combined launch file: + +.. code-block:: python + + from launch import LaunchDescription + from launch_ros.actions import Node + + def generate_launch_description(): + return LaunchDescription([ + Node( + package='ros2_medkit_fault_manager', + executable='fault_manager_node', + name='fault_manager', + ), + Node( + package='ros2_medkit_diagnostic_bridge', + executable='diagnostic_bridge_node', + name='diagnostic_bridge', + ), + ]) + +Migration Strategy +------------------ + +For transitioning from standard ROS 2 diagnostics to direct fault reporting: + +1. **Phase 1**: Deploy diagnostic bridge alongside existing diagnostics infrastructure +2. **Phase 2**: Create explicit mappings for important diagnostics +3. **Phase 3**: Migrate critical nodes to direct FaultReporter usage +4. **Phase 4**: Disable auto_generate_codes, rely only on explicit mappings +5. **Phase 5**: Remove diagnostic bridge when all nodes use FaultReporter + +.. seealso:: + + :doc:`/design/ros2_medkit_fault_reporter/index` - Direct fault reporting with FaultReporter + +See Also +-------- + +- :doc:`fault-manager` - FaultManager configuration +- :doc:`/design/ros2_medkit_fault_reporter/index` - Direct fault reporting guide +- :doc:`/api/messages` - ReportFault service definition +- :doc:`/design/ros2_medkit_diagnostic_bridge/index` - Bridge architecture diff --git a/docs/config/fault-manager.rst b/docs/config/fault-manager.rst new file mode 100644 index 00000000..b4a2f733 --- /dev/null +++ b/docs/config/fault-manager.rst @@ -0,0 +1,262 @@ +Fault Manager Configuration +=========================== + +The ``ros2_medkit_fault_manager`` node aggregates and manages faults from multiple sources. +This page documents all configuration parameters. + +.. contents:: Table of Contents + :local: + :depth: 2 + +Basic Configuration +------------------- + +Storage +~~~~~~~ + +.. code-block:: yaml + + fault_manager: + ros__parameters: + storage_type: "sqlite" # Storage backend: "sqlite" or "memory" + database_path: "/var/lib/ros2_medkit/faults.db" # Path for sqlite storage + +.. list-table:: + :header-rows: 1 + :widths: 25 15 60 + + * - Parameter + - Default + - Description + * - ``storage_type`` + - ``sqlite`` + - Storage backend. ``sqlite`` persists faults to disk, ``memory`` keeps in RAM only. + * - ``database_path`` + - ``/var/lib/ros2_medkit/faults.db`` + - File path for SQLite database. Directory must exist and be writable. + +Debounce Settings +~~~~~~~~~~~~~~~~~ + +The fault manager uses AUTOSAR DEM-style debounce filtering to prevent fault flapping. + +.. code-block:: yaml + + fault_manager: + ros__parameters: + confirmation_threshold: -1 # Counter threshold to confirm fault + healing_enabled: false # Enable auto-healing via PASSED events + healing_threshold: 3 # Counter threshold to heal fault + auto_confirm_after_sec: 0.0 # Auto-confirm timeout (0 = disabled) + +.. list-table:: + :header-rows: 1 + :widths: 30 12 58 + + * - Parameter + - Default + - Description + * - ``confirmation_threshold`` + - ``-1`` + - Number of FAILED events to confirm fault. Negative values mean more events needed. + Use ``-3`` to require 3 FAILED events before confirmation. + * - ``healing_enabled`` + - ``false`` + - When true, PASSED events can heal confirmed faults. + * - ``healing_threshold`` + - ``3`` + - Number of PASSED events to transition from CONFIRMED to HEALED. + * - ``auto_confirm_after_sec`` + - ``0.0`` + - Auto-confirm prefailed faults after this duration. Set to 0 to disable. + +.. tip:: + + For immediate fault confirmation (no debounce), set ``confirmation_threshold: 0``. + Faults with ``SEVERITY_CRITICAL`` always bypass debounce regardless of this setting. + +Snapshot Configuration +---------------------- + +Snapshots capture diagnostic data when faults occur. + +Basic Snapshot Settings +~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: yaml + + fault_manager: + ros__parameters: + snapshots: + enabled: true # Enable snapshot capture + background_capture: false # Capture in background thread + timeout_sec: 1.0 # Timeout for topic sampling + max_message_size: 65536 # Max message size in bytes (64KB) + default_topics: [] # Topics to capture for all faults + config_file: "" # Path to YAML config file + +.. list-table:: + :header-rows: 1 + :widths: 30 15 55 + + * - Parameter + - Default + - Description + * - ``snapshots.enabled`` + - ``true`` + - Master switch to enable/disable snapshot capture. + * - ``snapshots.background_capture`` + - ``false`` + - Capture snapshots in background thread (non-blocking). + * - ``snapshots.timeout_sec`` + - ``1.0`` + - Timeout for sampling each topic. + * - ``snapshots.max_message_size`` + - ``65536`` + - Maximum message size to capture (bytes). Larger messages are truncated. + * - ``snapshots.default_topics`` + - ``[]`` + - List of topics to capture for all faults. + * - ``snapshots.config_file`` + - ``""`` + - Path to YAML file with fault-specific snapshot configurations. + +Rosbag Recording +~~~~~~~~~~~~~~~~ + +Capture continuous rosbag recordings around fault events. + +.. code-block:: yaml + + fault_manager: + ros__parameters: + snapshots: + rosbag: + enabled: false # Enable rosbag recording + duration_sec: 5.0 # Pre-fault buffer duration + duration_after_sec: 1.0 # Post-fault recording duration + topics: "config" # Topic selection: "config", "all", or "none" + include_topics: [] # Additional topics to include + exclude_topics: [] # Topics to exclude + lazy_start: false # Start recording on first fault + format: "sqlite3" # Storage format + storage_path: "" # Custom storage path + max_bag_size_mb: 50 # Max size per bag file + max_total_storage_mb: 500 # Max total storage + auto_cleanup: true # Auto-delete old bags + +.. list-table:: + :header-rows: 1 + :widths: 35 15 50 + + * - Parameter + - Default + - Description + * - ``rosbag.enabled`` + - ``false`` + - Enable rosbag recording for snapshots. + * - ``rosbag.duration_sec`` + - ``5.0`` + - Duration of pre-fault circular buffer. + * - ``rosbag.duration_after_sec`` + - ``1.0`` + - How long to record after fault. + * - ``rosbag.topics`` + - ``config`` + - Topic selection mode: ``config`` (per-fault), ``all``, or ``none``. + * - ``rosbag.lazy_start`` + - ``false`` + - Start recording only when first fault occurs. + * - ``rosbag.max_bag_size_mb`` + - ``50`` + - Maximum size per rosbag file (MB). + * - ``rosbag.max_total_storage_mb`` + - ``500`` + - Maximum total storage for all rosbags (MB). + * - ``rosbag.auto_cleanup`` + - ``true`` + - Automatically delete oldest rosbags when storage limit reached. + +.. seealso:: + + :doc:`/tutorials/snapshots` for detailed snapshot configuration examples. + +Correlation Configuration +------------------------- + +Fault correlation identifies root causes and filters symptom faults. + +.. code-block:: yaml + + fault_manager: + ros__parameters: + correlation: + config_file: "/path/to/correlation_rules.yaml" + cleanup_interval_sec: 5.0 # Interval for cleanup tasks + +.. list-table:: + :header-rows: 1 + :widths: 35 15 50 + + * - Parameter + - Default + - Description + * - ``correlation.config_file`` + - ``""`` + - Path to YAML file defining correlation rules. + * - ``correlation.cleanup_interval_sec`` + - ``5.0`` + - Interval for running correlation cleanup tasks. + +.. seealso:: + + :doc:`/tutorials/fault-correlation` for correlation rule syntax and examples. + +Complete Example +---------------- + +.. code-block:: yaml + + fault_manager: + ros__parameters: + # Storage + storage_type: "sqlite" + database_path: "/var/lib/ros2_medkit/faults.db" + + # Debounce (require 3 FAILED events to confirm) + confirmation_threshold: -3 + healing_enabled: true + healing_threshold: 3 + auto_confirm_after_sec: 30.0 + + # Snapshots + snapshots: + enabled: true + background_capture: true + timeout_sec: 2.0 + max_message_size: 131072 + default_topics: + - /diagnostics + - /rosout + config_file: "/etc/ros2_medkit/snapshot_config.yaml" + rosbag: + enabled: true + duration_sec: 10.0 + duration_after_sec: 2.0 + topics: "config" + max_bag_size_mb: 100 + max_total_storage_mb: 1000 + auto_cleanup: true + + # Correlation + correlation: + config_file: "/etc/ros2_medkit/correlation_rules.yaml" + cleanup_interval_sec: 10.0 + +See Also +-------- + +- :doc:`/tutorials/snapshots` - Diagnostic snapshot configuration +- :doc:`/tutorials/fault-correlation` - Fault correlation rules +- :doc:`/api/messages` - Message definitions (Fault.msg, FaultEvent.msg) +- :doc:`/design/ros2_medkit_fault_manager/index` - FaultManager architecture diff --git a/docs/config/index.rst b/docs/config/index.rst index c2c08656..28df9d5b 100644 --- a/docs/config/index.rst +++ b/docs/config/index.rst @@ -6,8 +6,18 @@ This section contains configuration references for ros2_medkit. .. toctree:: :maxdepth: 2 + server discovery-options manifest-schema + fault-manager + diagnostic-bridge + +Server Configuration +-------------------- + +:doc:`server` + REST server settings including network binding, TLS/HTTPS, CORS, + data access tuning, and performance options. Discovery Options ----------------- @@ -22,3 +32,17 @@ Manifest Configuration :doc:`manifest-schema` Complete YAML schema reference for SOVD system manifests. Defines areas, components, apps, and functions for your ROS 2 system. + +Fault Manager +------------- + +:doc:`fault-manager` + FaultManager node configuration: storage, debounce thresholds, snapshot + capture, rosbag recording, and correlation settings. + +Diagnostic Bridge +----------------- + +:doc:`diagnostic-bridge` + Diagnostic bridge configuration for converting standard ROS 2 diagnostics + to fault events. Includes custom fault code mappings. diff --git a/docs/config/manifest-schema.rst b/docs/config/manifest-schema.rst index 2d77423f..1aa09b79 100644 --- a/docs/config/manifest-schema.rst +++ b/docs/config/manifest-schema.rst @@ -205,7 +205,8 @@ Components ---------- Components represent hardware or virtual entities (ECUs, sensors, controllers). -In runtime-only mode, components are derived from ROS 2 nodes. +In runtime-only mode, synthetic components are created per namespace to group Apps (nodes). +In manifest mode, components are explicitly defined and Apps are linked to them. Schema ~~~~~~ diff --git a/docs/config/server.rst b/docs/config/server.rst new file mode 100644 index 00000000..cd8f2afc --- /dev/null +++ b/docs/config/server.rst @@ -0,0 +1,211 @@ +Server Configuration +==================== + +This reference describes all server-related configuration options for the +ros2_medkit gateway. + +Quick Start +----------- + +The gateway can be configured via: + +1. **Command line**: ``--ros-args -p server.port:=9000`` +2. **Launch files**: ``parameters=[{'server.port': 9000}]`` +3. **YAML file**: See ``src/ros2_medkit_gateway/config/gateway_params.yaml`` + +Network Settings +---------------- + +.. list-table:: + :header-rows: 1 + :widths: 25 15 15 45 + + * - Parameter + - Type + - Default + - Description + * - ``server.host`` + - string + - ``"127.0.0.1"`` + - Host to bind the REST server. Use ``"0.0.0.0"`` for Docker or network access. + * - ``server.port`` + - int + - ``8080`` + - Port for REST API. Valid range: 1024-65535. + +Example: + +.. code-block:: bash + + # Expose on all interfaces (Docker/network) + ros2 run ros2_medkit_gateway gateway_node --ros-args \ + -p server.host:=0.0.0.0 \ + -p server.port:=8080 + +TLS/HTTPS Configuration +----------------------- + +.. list-table:: + :header-rows: 1 + :widths: 25 15 15 45 + + * - Parameter + - Type + - Default + - Description + * - ``server.tls.enabled`` + - bool + - ``false`` + - Enable HTTPS using OpenSSL. + * - ``server.tls.cert_file`` + - string + - ``""`` + - Path to PEM-encoded certificate file. + * - ``server.tls.key_file`` + - string + - ``""`` + - Path to PEM-encoded private key file. Restrict permissions (chmod 600). + * - ``server.tls.ca_file`` + - string + - ``""`` + - Path to CA certificate file (reserved for mutual TLS). + * - ``server.tls.min_version`` + - string + - ``"1.2"`` + - Minimum TLS version: ``"1.2"`` (compatible) or ``"1.3"`` (secure). + +Example: + +.. code-block:: yaml + + ros2_medkit_gateway: + ros__parameters: + server: + tls: + enabled: true + cert_file: "/etc/ros2_medkit/certs/cert.pem" + key_file: "/etc/ros2_medkit/certs/key.pem" + min_version: "1.2" + +See :doc:`/tutorials/https` for a complete HTTPS setup tutorial. + +CORS Configuration +------------------ + +Cross-Origin Resource Sharing (CORS) settings for browser-based clients. + +.. list-table:: + :header-rows: 1 + :widths: 30 15 20 35 + + * - Parameter + - Type + - Default + - Description + * - ``cors.allowed_origins`` + - list + - ``[""]`` + - List of allowed origins. Use ``["*"]`` for all (not recommended). + * - ``cors.allowed_methods`` + - list + - ``["GET", "PUT", "OPTIONS"]`` + - Allowed HTTP methods. + * - ``cors.allowed_headers`` + - list + - ``["Content-Type", "Accept"]`` + - Allowed headers in requests. + * - ``cors.allow_credentials`` + - bool + - ``false`` + - Allow credentials (cookies, auth headers). + * - ``cors.max_age_seconds`` + - int + - ``86400`` + - Preflight response cache duration (24 hours). + +Example for development with sovd_web_ui: + +.. code-block:: yaml + + ros2_medkit_gateway: + ros__parameters: + cors: + allowed_origins: ["http://localhost:5173"] + allowed_methods: ["GET", "PUT", "POST", "DELETE", "OPTIONS"] + allowed_headers: ["Content-Type", "Accept", "Authorization"] + allow_credentials: true + +Data Access Settings +-------------------- + +.. list-table:: + :header-rows: 1 + :widths: 35 10 10 45 + + * - Parameter + - Type + - Default + - Description + * - ``max_parallel_topic_samples`` + - int + - ``10`` + - Max concurrent topic samples. Higher values use more resources. Range: 1-50. + * - ``topic_sample_timeout_sec`` + - float + - ``1.0`` + - Timeout for sampling topics with active publishers. Range: 0.1-30.0. + +Performance Tuning +------------------ + +.. list-table:: + :header-rows: 1 + :widths: 30 10 15 45 + + * - Parameter + - Type + - Default + - Description + * - ``refresh_interval_ms`` + - int + - ``10000`` + - Cache refresh interval. How often to discover ROS 2 nodes. Range: 100-60000 (0.1s-60s). + +Lower values provide faster updates but increase CPU usage. + +.. note:: + + The gateway uses native rclcpp APIs for all ROS 2 interactions—no ROS 2 CLI + dependencies. Topic discovery, sampling, publishing, service calls, and + action operations are implemented in pure C++ using ros2_medkit_serialization. + +Complete Example +---------------- + +.. code-block:: yaml + + ros2_medkit_gateway: + ros__parameters: + server: + host: "0.0.0.0" + port: 8080 + tls: + enabled: false + + refresh_interval_ms: 5000 + max_parallel_topic_samples: 30 + topic_sample_timeout_sec: 3.0 + + cors: + allowed_origins: ["http://localhost:5173", "https://dashboard.example.com"] + allowed_methods: ["GET", "PUT", "POST", "DELETE", "OPTIONS"] + allowed_headers: ["Content-Type", "Accept", "Authorization"] + allow_credentials: true + max_age_seconds: 86400 + +See Also +-------- + +- :doc:`/tutorials/authentication` - JWT authentication setup +- :doc:`/tutorials/https` - HTTPS configuration +- :doc:`discovery-options` - Discovery and entity mapping options diff --git a/docs/getting_started.rst b/docs/getting_started.rst index e5b36d4d..1c944ca4 100644 --- a/docs/getting_started.rst +++ b/docs/getting_started.rst @@ -55,17 +55,81 @@ You should see: ros2 launch ros2_medkit_gateway demo_nodes.launch.py -This launches automotive demo nodes (temperature sensors, brake actuators, etc.) -that we'll use to explore the API. +This launches automotive demo nodes that we'll use to explore the API. + +.. list-table:: Demo Nodes Created by demo_nodes.launch.py + :header-rows: 1 + :widths: 25 25 30 20 + + * - Node Name + - Entity ID + - Namespace + - Description + * - temp_sensor + - powertrain_engine_component.temp_sensor + - /powertrain/engine + - Engine temperature sensor + * - rpm_sensor + - powertrain_engine_component.rpm_sensor + - /powertrain/engine + - Engine RPM sensor + * - calibration + - powertrain_engine_component.calibration + - /powertrain/engine + - Calibration service (sync) + * - long_calibration + - powertrain_engine_component.long_calibration + - /powertrain/engine + - Long calibration action (async) + * - pressure_sensor + - chassis_brakes_component.pressure_sensor + - /chassis/brakes + - Brake pressure sensor + * - actuator + - chassis_brakes_component.actuator + - /chassis/brakes + - Brake actuator + * - status_sensor + - body_door_front_left_component.status_sensor + - /body/door/front_left + - Door status sensor + * - controller + - body_lights_component.controller + - /body/lights + - Light controller + * - lidar_sensor + - perception_lidar_component.lidar_sensor + - /perception/lidar + - LiDAR sensor with faults + +.. note:: + + In runtime-only discovery mode, entity IDs are derived from the namespace path. + Use the ``/components`` endpoint to discover actual component IDs. **Terminal 3 - (Optional) Start fault manager:** .. code-block:: bash - ros2 run ros2_medkit_fault_manager fault_manager_node + mkdir -p $HOME/.ros2_medkit + ros2 run ros2_medkit_fault_manager fault_manager_node --ros-args -p database_path:=$HOME/.ros2_medkit/faults.db Required if you want to test the Faults API. +.. note:: + + The ``~/.ros2_medkit/`` directory must exist before starting the fault manager. + SQLite will create the database file automatically. + +.. admonition:: ✅ Checkpoint + :class: tip + + At this point you should have: + + - Gateway running on http://localhost:8080 + - Demo nodes publishing data + - Terminal 1 showing: ``[gateway_node]: REST server started successfully`` + Step 2: Explore the API ----------------------- @@ -77,12 +141,6 @@ The gateway exposes all endpoints under ``/api/v1``. Let's explore! curl http://localhost:8080/api/v1/health -Response: - -.. code-block:: json - - {"status": "healthy"} - **Get gateway capabilities:** .. code-block:: bash @@ -91,13 +149,37 @@ Response: Response shows available endpoints and version info. +.. admonition:: ✅ Checkpoint + :class: tip + + Health check should return: ``{"status": "healthy", "timestamp": ...}`` + + If you see connection refused, verify gateway is running. + Step 3: Discover Areas and Components ------------------------------------- -ros2_medkit organizes ROS 2 nodes into a hierarchy: +ros2_medkit organizes ROS 2 nodes into a SOVD-aligned entity hierarchy: + +- **Areas** — Logical/physical domains (e.g., ``/powertrain``, ``/chassis``) +- **Components** — Hardware or virtual units that group Apps +- **Apps** — Individual ROS 2 nodes +- **Functions** — Cross-cutting capabilities (requires manifest mode) + +.. note:: + + **Discovery Modes** + + - **Runtime-only** (default): Each ROS 2 namespace becomes an Area, and + ROS 2 nodes within it are exposed as Apps. Synthetic Components are + created to group these Apps by namespace. + - **Hybrid**: Manifest defines Areas/Components/Apps/Functions, runtime + links them to live ROS 2 nodes. + - **Manifest-only**: Only manifest-declared entities are exposed. -- **Areas** - Physical or logical domains (e.g., powertrain, chassis) -- **Components** - Individual nodes within areas + See :doc:`tutorials/manifest-discovery` for details on manifest mode. + + In this tutorial, we use runtime-only mode with ``demo_nodes.launch.py``. **List all areas:** @@ -105,15 +187,7 @@ ros2_medkit organizes ROS 2 nodes into a hierarchy: curl http://localhost:8080/api/v1/areas -Response: - -.. code-block:: json - - [ - {"id": "powertrain", "namespace": "/powertrain", "type": "Area"}, - {"id": "chassis", "namespace": "/chassis", "type": "Area"}, - {"id": "body", "namespace": "/body", "type": "Area"} - ] +With ``demo_nodes.launch.py``, you'll see areas like ``powertrain``, ``chassis``, and ``body``. **List all components:** @@ -130,36 +204,114 @@ Response: Step 4: Read Sensor Data ------------------------ -The data endpoints let you read topic data from components. +The data endpoints let you read topic data from apps. -**Read all data from a component:** +**Read all data from an app:** .. code-block:: bash - curl http://localhost:8080/api/v1/components/temp_sensor/data + curl http://localhost:8080/api/v1/apps/temp_sensor/data -Response: +Response structure (showing one topic): .. code-block:: json - [ - { - "topic": "/powertrain/engine/temperature", - "timestamp": 1704067200000000000, - "data": {"temperature": 85.5, "variance": 0.0} + { + "items": [ + { + "category": "currentData", + "id": "/powertrain/engine/temperature", + "name": "/powertrain/engine/temperature", + "x-medkit": { + "ros2": { + "direction": "publish", + "topic": "/powertrain/engine/temperature", + "type": "sensor_msgs/msg/Temperature" + }, + "type_info": { + "default_value": { + "header": {}, + "temperature": 0, + "variance": 0 + }, + "schema": { + "properties": { + "header": {}, + "temperature": {"type": "number"}, + "variance": {"type": "number"} + }, + "type": "object" + } + } + } + } + ], + "x-medkit": { + "entity_id": "temp_sensor", + "total_count": 3 } - ] + } + +Each data item includes: + +- ``category``: Type of data (``currentData``) +- ``id`` and ``name``: ROS 2 topic path +- ``x-medkit.ros2``: Topic metadata (direction, type) +- ``x-medkit.type_info.schema``: JSON Schema for the message type +- ``x-medkit.type_info.default_value``: Default message structure **Read a specific topic:** .. code-block:: bash - curl http://localhost:8080/api/v1/components/temp_sensor/data/powertrain%2Fengine%2Ftemperature + curl http://localhost:8080/api/v1/apps/temp_sensor/data/powertrain%2Fengine%2Ftemperature + +Response with live data: + +.. code-block:: json + + { + "data": { + "header": { + "frame_id": "engine", + "stamp": {"sec": 1769955040, "nanosec": 286555163} + }, + "temperature": 93.5, + "variance": 0.5 + }, + "id": "/powertrain/engine/temperature", + "x-medkit": { + "entity_id": "temp_sensor", + "timestamp": 1769955039964403368, + "ros2": { + "topic": "/powertrain/engine/temperature", + "type": "sensor_msgs/msg/Temperature" + }, + "publisher_count": 1, + "subscriber_count": 0, + "status": "data" + } + } + +Notice: + +- ``data``: The actual message content from ROS 2 topic +- ``x-medkit.timestamp``: Gateway capture time (nanoseconds since epoch) +- ``publisher_count`` / ``subscriber_count``: Number of publishers/subscribers on this topic .. note:: Topic paths use URL encoding: ``/`` becomes ``%2F`` +.. admonition:: ✅ Checkpoint + :class: tip + + You should see: + + - Areas like ``powertrain``, ``chassis``, ``body`` + - Components with ``temp_sensor``, ``brake_actuator``, etc. + - Live topic data with actual sensor readings + Step 5: Call Services and Actions ---------------------------------- @@ -169,7 +321,7 @@ The operations endpoints let you call ROS 2 services and actions. .. code-block:: bash - curl http://localhost:8080/api/v1/components/calibration/operations + curl http://localhost:8080/api/v1/apps/calibration/operations **Call a service (synchronous execution):** @@ -177,24 +329,23 @@ Services return immediately with status ``200 OK``: .. code-block:: bash - curl -X POST http://localhost:8080/api/v1/components/calibration/operations/calibrate/executions \ + curl -X POST http://localhost:8080/api/v1/apps/calibration/operations/calibrate/executions \ -H "Content-Type: application/json" \ -d '{}' -Response: +Response (200 OK): .. code-block:: json { - "id": "exec-12345", - "status": "completed", - "capability": "execute", - "x-medkit": { - "kind": "service", - "response": {"success": true, "message": "Calibration triggered"} + "parameters": { + "success": true, + "message": "Engine calibrated successfully (count: 1)" } } +The ``parameters`` field contains the service response data directly. + **Send an action goal (asynchronous execution):** Actions return ``202 Accepted`` immediately with an execution ID for polling: @@ -237,19 +388,19 @@ The configurations endpoints expose ROS 2 parameters. .. code-block:: bash - curl http://localhost:8080/api/v1/components/temp_sensor/configurations + curl http://localhost:8080/api/v1/apps/temp_sensor/configurations **Get a specific parameter:** .. code-block:: bash - curl http://localhost:8080/api/v1/components/temp_sensor/configurations/publish_rate + curl http://localhost:8080/api/v1/apps/temp_sensor/configurations/publish_rate **Set a parameter value:** .. code-block:: bash - curl -X PUT http://localhost:8080/api/v1/components/temp_sensor/configurations/publish_rate \ + curl -X PUT http://localhost:8080/api/v1/apps/temp_sensor/configurations/publish_rate \ -H "Content-Type: application/json" \ -d '{"value": 5.0}' @@ -257,7 +408,7 @@ The configurations endpoints expose ROS 2 parameters. .. code-block:: bash - curl -X DELETE http://localhost:8080/api/v1/components/temp_sensor/configurations/publish_rate + curl -X DELETE http://localhost:8080/api/v1/apps/temp_sensor/configurations/publish_rate Step 7: Monitor Faults ---------------------- @@ -276,27 +427,70 @@ Step 7: Monitor Faults .. code-block:: bash - curl http://localhost:8080/api/v1/components/lidar_sensor/faults + curl http://localhost:8080/api/v1/apps/lidar_sensor/faults **Clear a fault:** .. code-block:: bash - curl -X DELETE http://localhost:8080/api/v1/components/lidar_sensor/faults/LIDAR_CALIBRATION_REQUIRED + curl -X DELETE http://localhost:8080/api/v1/apps/lidar_sensor/faults/LIDAR_CALIBRATION_REQUIRED + +.. admonition:: ✅ Checkpoint + :class: tip + + At this point you've successfully: + + - Discovered the ROS 2 system structure + - Read sensor data via REST API + - Called services and managed actions + - Managed node parameters + - Queried and cleared faults + + 🎉 You're ready to explore the web UI and advanced features! Using with Web UI ----------------- -A companion web UI is available in the `sovd_web_ui `_ repository: +A companion web UI is available for visual entity browsing: .. code-block:: bash - git clone https://github.com/selfpatch/sovd_web_ui.git - cd sovd_web_ui - npm install - npm run dev + docker pull ghcr.io/selfpatch/sovd_web_ui:latest + docker run -p 3000:80 ghcr.io/selfpatch/sovd_web_ui:latest + +Open http://localhost:3000 and connect to the gateway at http://localhost:8080. -Open http://localhost:5173 and connect to the gateway at http://localhost:8080. +See :doc:`tutorials/web-ui` for more details. + +Using with LLMs (MCP) +--------------------- + +Connect your LLM to the gateway using ros2_medkit_mcp: + +**Option 1: Docker (recommended)** + +.. code-block:: bash + + # Pull and run HTTP server on port 8765 + docker run -p 8765:8765 \ + -e ROS2_MEDKIT_BASE_URL=http://host.docker.internal:8080/api/v1 \ + ghcr.io/selfpatch/ros2_medkit_mcp:latest + + # Or run with stdio transport + docker run -i \ + -e ROS2_MEDKIT_BASE_URL=http://host.docker.internal:8080/api/v1 \ + ghcr.io/selfpatch/ros2_medkit_mcp:latest stdio + +**Option 2: Poetry (for development)** + +.. code-block:: bash + + git clone https://github.com/selfpatch/ros2_medkit_mcp.git + cd ros2_medkit_mcp + poetry install + poetry run ros2-medkit-mcp-stdio + +See :doc:`tutorials/mcp-server` for Claude Desktop and VS Code integration. Using with Postman ------------------ @@ -307,12 +501,36 @@ For interactive API testing, import our Postman collection: 2. Import ``postman/environments/local.postman_environment.json`` 3. Select "ROS 2 Medkit Gateway - Local" environment +.. figure:: /_static/images/15_postman_collection.png + :alt: Postman collection + :align: center + :width: 600px + + Postman collection with organized endpoint folders. + See ``postman/README.md`` for detailed instructions. Next Steps ---------- +**Configuration:** + +- :doc:`config/server` - Server, CORS, and TLS settings +- :doc:`config/discovery-options` - Discovery mode configuration + +**Tutorials:** + - :doc:`tutorials/authentication` - Enable JWT authentication - :doc:`tutorials/https` - Configure TLS/HTTPS +- :doc:`tutorials/manifest-discovery` - Use manifests for stable entity IDs - :doc:`tutorials/docker` - Deploy with Docker + +**Companion Projects:** + +- :doc:`tutorials/web-ui` - Visual entity browser +- :doc:`tutorials/mcp-server` - LLM integration via MCP + +**Reference:** + +- :doc:`api/rest` - Complete REST API reference - :doc:`design/ros2_medkit_gateway/index` - Architecture deep-dive diff --git a/docs/glossary.rst b/docs/glossary.rst new file mode 100644 index 00000000..4b120241 --- /dev/null +++ b/docs/glossary.rst @@ -0,0 +1,102 @@ +Glossary +======== + +This glossary defines key terms used throughout ros2_medkit documentation. + +.. glossary:: + :sorted: + + Area + A logical or physical grouping of components, representing a vehicle + subsystem or domain. Examples: ``powertrain``, ``chassis``, ``body``. + In runtime-only mode, areas are derived from ROS 2 namespace prefixes. + + See: :doc:`tutorials/manifest-discovery` + + App + A software application, typically mapping to a single ROS 2 node. + Apps are hosted on Components and can participate in Functions. + Available in manifest mode and hybrid mode. + + See: :doc:`tutorials/heuristic-apps` + + Component + A hardware or virtual unit that hosts Apps. In runtime-only mode, + components are synthetic groups created by namespace aggregation. + In manifest mode, components are explicitly defined. + + Configuration + A ROS 2 node parameter exposed via the ``/configurations`` endpoint. + Configurations can be read, modified, and reset to defaults. + + See: :doc:`api/rest` + + Data + Topic data from ROS 2 publishers, exposed via the ``/data`` endpoint. + Data can be read (sampled) and written (published). + + Discovery Mode + The method used to map ROS 2 graph entities to SOVD entities: + + - **runtime_only**: ROS 2 graph introspection (default) + - **hybrid**: Manifest + runtime linking (recommended) + - **manifest_only**: Only manifest-declared entities + + See: :doc:`config/discovery-options` + + Entity + A generic term for any SOVD object: Area, Component, App, or Function. + Each entity has a unique ID, name, and set of resource collections. + + Execution + An instance of an operation being called. For services, executions + complete immediately. For actions, executions are asynchronous and + can be polled for status or cancelled. + + Fault + An error condition reported by a ROS 2 node to the fault manager. + Faults have a code, severity, message, and timestamp. + + See: :doc:`design/ros2_medkit_fault_reporter/index` + + Function + A high-level capability that spans multiple Apps. Functions aggregate + data and operations from their host Apps. Available in manifest mode. + + Gateway + The ros2_medkit_gateway node that provides the REST API. It discovers + ROS 2 entities, handles HTTP requests, and manages executions. + + Manifest + A YAML file that declares the system structure (areas, components, + apps, functions) with stable IDs and semantic metadata. + + See: :doc:`config/manifest-schema` + + MCP (Model Context Protocol) + A protocol for connecting LLMs to external tools. ros2_medkit_mcp + provides MCP tools that wrap the gateway REST API. + + See: :doc:`tutorials/mcp-server` + + Operation + A callable action or service exposed via the ``/operations`` endpoint. + Operations are invoked by creating executions. + + Resource Collection + A category of resources available on an entity: ``data``, ``operations``, + ``configurations``, or ``faults``. Not all entities support all collections. + + Runtime Linking + In hybrid mode, the process of matching manifest-declared Apps to + running ROS 2 nodes based on their ``ros_binding`` configuration. + + SOVD + Service-Oriented Vehicle Diagnostics — an ASAM standard for + automotive diagnostics over IP networks. ros2_medkit implements + a subset of SOVD adapted for ROS 2 systems. + + Synthetic Component + A Component automatically created in runtime-only mode by grouping + ROS 2 nodes that share a namespace. Configured via + ``discovery.runtime.create_synthetic_components``. diff --git a/docs/index.rst b/docs/index.rst index 5ad2d49e..9eb29992 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -15,7 +15,7 @@ endpoints for data access, operations, configurations, and fault management. .. note:: - Version 0.1.0 - First public release still under construction... + Version 0.1.0 - First public release. Quick Links ----------- @@ -46,6 +46,40 @@ Quick Links Solutions for common issues and FAQ. +Quick Reference +--------------- + +Common commands for quick access: + +.. code-block:: bash + + # Check gateway health + curl http://localhost:8080/api/v1/health + + # List all areas + curl http://localhost:8080/api/v1/areas + + # List all components + curl http://localhost:8080/api/v1/components + + # List all apps + curl http://localhost:8080/api/v1/apps + + # List all functions + curl http://localhost:8080/api/v1/functions + + # Get data from an entity (area, component, app, or function) + curl http://localhost:8080/api/v1/{entity-type}/{entity-id}/data + + # List operations for an entity (area, component, app, or function) + curl http://localhost:8080/api/v1/{entity-type}/{entity-id}/operations + + # Get configurations (parameters) + curl http://localhost:8080/api/v1/{entity-type}/{entity-id}/configurations + + # List faults + curl http://localhost:8080/api/v1/faults + Community --------- @@ -88,4 +122,11 @@ Community requirements/index +.. toctree:: + :maxdepth: 1 + :caption: Reference + + glossary + changelog + diff --git a/docs/installation.rst b/docs/installation.rst index a6b14777..a97b63e5 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -15,7 +15,7 @@ System Requirements * - Operating System - Ubuntu 24.04 LTS (Noble Numbat) * - ROS 2 Distribution - - Jazzy Jalisco + - Jazzy * - C++ Compiler - GCC 13+ (C++17 support required) * - CMake @@ -26,38 +26,8 @@ System Requirements Prerequisites ------------- -1. **Install ROS 2 Jazzy** - - Follow the official ROS 2 installation guide: - https://docs.ros.org/en/jazzy/Installation/Ubuntu-Install-Debs.html - - .. code-block:: bash - - # Add ROS 2 apt repository - sudo apt update && sudo apt install -y software-properties-common - sudo add-apt-repository universe - sudo apt update && sudo apt install curl -y - sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key \ - -o /usr/share/keyrings/ros-archive-keyring.gpg - echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] \ - http://packages.ros.org/ros2/ubuntu $(. /etc/os-release && echo $UBUNTU_CODENAME) main" | \ - sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null - - # Install ROS 2 Jazzy Desktop - sudo apt update - sudo apt install ros-jazzy-desktop ros-dev-tools - -2. **Source ROS 2 environment** - - .. code-block:: bash - - source /opt/ros/jazzy/setup.bash - - Add this to your ``~/.bashrc`` for persistence: - - .. code-block:: bash - - echo "source /opt/ros/jazzy/setup.bash" >> ~/.bashrc +**ROS 2 Jazzy** must be installed and sourced. Follow the official installation guide: +https://docs.ros.org/en/jazzy/Installation/Ubuntu-Install-Debs.html Installation from Source ------------------------ @@ -73,15 +43,19 @@ ros2_medkit is currently distributed as source code. Binary packages will be ava 2. **Clone the repository** + Clone directly into the ``src`` directory: + .. code-block:: bash - git clone --recurse-submodules https://github.com/selfpatch/ros2_medkit.git - cd ros2_medkit + git clone --recurse-submodules https://github.com/selfpatch/ros2_medkit.git . + + This places packages (``ros2_medkit_gateway``, ``ros2_medkit_fault_manager``, etc.) directly under ``~/ros2_medkit_ws/src/``. If you already cloned without submodules, initialize them: .. code-block:: bash + cd ~/ros2_medkit_ws/src git submodule update --init --recursive 3. **Install dependencies** @@ -134,7 +108,7 @@ You should see output like: .. code-block:: json - {"status": "healthy"} + {"status": "healthy", "timestamp": "..."} Docker Installation ------------------- diff --git a/docs/pyproject.toml b/docs/pyproject.toml index 6a63f8dd..0f0f1520 100644 --- a/docs/pyproject.toml +++ b/docs/pyproject.toml @@ -18,6 +18,7 @@ dependencies = [ "matplotlib", "sphinx-design", "breathe", + "sphinx-copybutton", ] [project.optional-dependencies] diff --git a/docs/requirements/coverage.rst b/docs/requirements/coverage.rst index 677e7475..4554112f 100644 --- a/docs/requirements/coverage.rst +++ b/docs/requirements/coverage.rst @@ -61,10 +61,10 @@ Coverage by Category :columns: id, title, status, verifies_back :style: table - .. tab-item:: Configurations + .. tab-item:: Configuration .. needtable:: - :filter: type == 'req' and "Configurations" in tags + :filter: type == 'req' and "Configuration" in tags :columns: id, title, status, verifies_back :style: table @@ -75,10 +75,80 @@ Coverage by Category :columns: id, title, status, verifies_back :style: table - .. tab-item:: Authentication + .. tab-item:: Auth .. needtable:: - :filter: type == 'req' and "Authentication" in tags + :filter: type == 'req' and "Auth" in tags + :columns: id, title, status, verifies_back + :style: table + + .. tab-item:: Subscriptions + + .. needtable:: + :filter: type == 'req' and "Subscriptions" in tags + :columns: id, title, status, verifies_back + :style: table + + .. tab-item:: DataSets + + .. needtable:: + :filter: type == 'req' and "DataSets" in tags + :columns: id, title, status, verifies_back + :style: table + + .. tab-item:: Scripts + + .. needtable:: + :filter: type == 'req' and "Scripts" in tags + :columns: id, title, status, verifies_back + :style: table + + .. tab-item:: Modes + + .. needtable:: + :filter: type == 'req' and "Modes" in tags + :columns: id, title, status, verifies_back + :style: table + + .. tab-item:: ClearData + + .. needtable:: + :filter: type == 'req' and "ClearData" in tags + :columns: id, title, status, verifies_back + :style: table + + .. tab-item:: Logs + + .. needtable:: + :filter: type == 'req' and "Logs" in tags + :columns: id, title, status, verifies_back + :style: table + + .. tab-item:: CommLogs + + .. needtable:: + :filter: type == 'req' and "CommLogs" in tags + :columns: id, title, status, verifies_back + :style: table + + .. tab-item:: BulkData + + .. needtable:: + :filter: type == 'req' and "BulkData" in tags + :columns: id, title, status, verifies_back + :style: table + + .. tab-item:: Lifecycle + + .. needtable:: + :filter: type == 'req' and "Lifecycle" in tags + :columns: id, title, status, verifies_back + :style: table + + .. tab-item:: Updates + + .. needtable:: + :filter: type == 'req' and "Updates" in tags :columns: id, title, status, verifies_back :style: table diff --git a/docs/requirements/specs/auth.rst b/docs/requirements/specs/auth.rst index 68873fda..784dcf68 100644 --- a/docs/requirements/specs/auth.rst +++ b/docs/requirements/specs/auth.rst @@ -3,14 +3,14 @@ Auth .. req:: POST /authorize :id: REQ_INTEROP_086 - :status: open + :status: verified :tags: Auth The endpoint shall perform an authorization flow and issue authorization information based on client credentials. .. req:: POST /token :id: REQ_INTEROP_087 - :status: open + :status: verified :tags: Auth The endpoint shall issue or refresh an access token that authorizes subsequent SOVD API calls. diff --git a/docs/requirements/specs/configuration.rst b/docs/requirements/specs/configuration.rst index 17a6b811..df5b9185 100644 --- a/docs/requirements/specs/configuration.rst +++ b/docs/requirements/specs/configuration.rst @@ -3,35 +3,35 @@ Configuration .. req:: GET /{entity}/configurations :id: REQ_INTEROP_048 - :status: open + :status: verified :tags: Configuration The endpoint shall list configuration snapshots or items stored on the addressed entity. .. req:: GET /{entity}/configurations/{id} :id: REQ_INTEROP_049 - :status: open + :status: verified :tags: Configuration The endpoint shall return the content of the addressed configuration snapshot or item. .. req:: PUT /{entity}/configurations/{id} :id: REQ_INTEROP_050 - :status: open + :status: verified :tags: Configuration The endpoint shall write configuration data for the addressed configuration resource on the entity, applying the provided content. .. req:: DELETE /{entity}/configurations :id: REQ_INTEROP_051 - :status: open + :status: verified :tags: Configuration The endpoint shall reset all configuration resources of the addressed entity to their default values, if permitted. .. req:: DELETE /{entity}/configurations/{id} :id: REQ_INTEROP_052 - :status: open + :status: verified :tags: Configuration The endpoint shall reset the addressed configuration resource to its default value, if permitted. diff --git a/docs/requirements/specs/data.rst b/docs/requirements/specs/data.rst index c62cc6be..4bd00e5f 100644 --- a/docs/requirements/specs/data.rst +++ b/docs/requirements/specs/data.rst @@ -17,21 +17,21 @@ Data .. req:: GET /{entity}/data :id: REQ_INTEROP_018 - :status: open + :status: verified :tags: Data The endpoint shall return a snapshot of selected data items for the addressed entity. .. req:: GET /{entity}/data/{data-id} :id: REQ_INTEROP_019 - :status: open + :status: verified :tags: Data The endpoint shall read the current value of the addressed data item from the entity. .. req:: PUT /{entity}/data/{data-id} :id: REQ_INTEROP_020 - :status: open + :status: verified :tags: Data The endpoint shall write a new value to the addressed data item on the entity, if it is writable. diff --git a/docs/requirements/specs/datasets.rst b/docs/requirements/specs/datasets.rst index c96c0595..05e5aec8 100644 --- a/docs/requirements/specs/datasets.rst +++ b/docs/requirements/specs/datasets.rst @@ -3,28 +3,28 @@ DataSets .. req:: GET /{entity}/data-lists :id: REQ_INTEROP_021 - :status: open + :status: verified :tags: DataSets The endpoint shall list all defined data sets (data-lists) available on the addressed entity. .. req:: POST /{entity}/data-lists :id: REQ_INTEROP_022 - :status: open + :status: verified :tags: DataSets The endpoint shall create a new temporary data set definition used to read multiple data items with a single request on the addressed entity. .. req:: GET /{entity}/data-lists/{id} :id: REQ_INTEROP_023 - :status: open + :status: verified :tags: DataSets The endpoint shall return the current values of all data items referenced by the addressed data set (data-list). .. req:: DELETE /{entity}/data-lists/{id} :id: REQ_INTEROP_024 - :status: open + :status: verified :tags: DataSets The endpoint shall delete the addressed data set definition from the entity. diff --git a/docs/requirements/specs/discovery.rst b/docs/requirements/specs/discovery.rst index 95c89ba1..12fa1978 100644 --- a/docs/requirements/specs/discovery.rst +++ b/docs/requirements/specs/discovery.rst @@ -3,7 +3,7 @@ Discovery .. req:: GET /version-info :id: REQ_INTEROP_001 - :status: open + :status: verified :tags: Discovery The endpoint shall provide version information for the SOVD server and its implementation. @@ -17,35 +17,35 @@ Discovery .. req:: GET /{entity-collection} :id: REQ_INTEROP_003 - :status: open + :status: verified :tags: Discovery The endpoint shall list all entities of the requested collection together with their basic metadata. .. req:: GET /areas/{id}/subareas :id: REQ_INTEROP_004 - :status: open + :status: verified :tags: Discovery The endpoint shall return the list of subareas that are contained in the addressed area. .. req:: GET /components/{id}/subcomponents :id: REQ_INTEROP_005 - :status: open + :status: verified :tags: Discovery The endpoint shall return the list of subcomponents that are logically contained in the addressed component. .. req:: GET /areas/{id}/contains :id: REQ_INTEROP_006 - :status: open + :status: verified :tags: Discovery The endpoint shall return all entities that are contained in the addressed area. .. req:: GET /components/{id}/hosts :id: REQ_INTEROP_007 - :status: open + :status: verified :tags: Discovery The endpoint shall list all applications that are hosted on the addressed component. @@ -59,21 +59,21 @@ Discovery .. req:: GET /apps/{id}/depends-on :id: REQ_INTEROP_009 - :status: open + :status: verified :tags: Discovery The endpoint shall return the list of other entities that the addressed application depends on. .. req:: GET / :id: REQ_INTEROP_010 - :status: open + :status: verified :tags: Discovery The endpoint shall provide a summary of the SOVD server capabilities and entry points. .. req:: tags=... (query) :id: REQ_INTEROP_011 - :status: open + :status: verified :tags: Discovery The server shall support tag-based query parameters that filter discovery responses by tags. diff --git a/docs/requirements/specs/faults.rst b/docs/requirements/specs/faults.rst index 1f752561..aba1c74b 100644 --- a/docs/requirements/specs/faults.rst +++ b/docs/requirements/specs/faults.rst @@ -3,35 +3,35 @@ Faults .. req:: GET /{entity}/faults :id: REQ_INTEROP_012 - :status: open + :status: verified :tags: Faults The endpoint shall return the list of diagnostic fault entries stored for the addressed entity, possibly including active and stored faults. .. req:: GET /{entity}/faults/{code} :id: REQ_INTEROP_013 - :status: open + :status: verified :tags: Faults The endpoint shall return detailed information for the addressed diagnostic fault code. .. req:: DELETE /{entity}/faults :id: REQ_INTEROP_014 - :status: open + :status: verified :tags: Faults The endpoint shall clear all diagnostic fault entries stored for the addressed entity, if permitted. .. req:: DELETE /{entity}/faults/{code} :id: REQ_INTEROP_015 - :status: open + :status: verified :tags: Faults The endpoint shall clear the addressed diagnostic fault code for the entity, if permitted. .. req:: GET /{entity}/faults/{code}/snapshots :id: REQ_INTEROP_088 - :status: open + :status: verified :tags: Faults, Snapshots The endpoint shall return topic data snapshots captured when the addressed fault transitioned to CONFIRMED status, enabling post-mortem debugging of system state at the time of fault occurrence. diff --git a/docs/requirements/specs/operations.rst b/docs/requirements/specs/operations.rst index d417a2c2..210fd9bc 100644 --- a/docs/requirements/specs/operations.rst +++ b/docs/requirements/specs/operations.rst @@ -20,7 +20,7 @@ The API supports: .. req:: GET /{entity}/operations :id: REQ_INTEROP_033 - :status: open + :status: verified :tags: Operations The endpoint shall list all supported operations that can be executed on the addressed entity. @@ -36,7 +36,7 @@ The API supports: .. req:: GET /{entity}/operations/{op-id} :id: REQ_INTEROP_034 - :status: open + :status: verified :tags: Operations The endpoint shall return the definition and metadata of the addressed operation. @@ -48,7 +48,7 @@ The API supports: .. req:: POST /{entity}/operations/{op-id}/executions :id: REQ_INTEROP_035 - :status: open + :status: verified :tags: Operations The endpoint shall start a new execution of the addressed operation on the entity. @@ -70,7 +70,7 @@ The API supports: .. req:: GET /{entity}/operations/{op-id}/executions :id: REQ_INTEROP_036 - :status: open + :status: verified :tags: Operations The endpoint shall list active and past executions of the addressed operation. @@ -81,7 +81,7 @@ The API supports: .. req:: GET /{entity}/operations/{op-id}/executions/{exec-id} :id: REQ_INTEROP_037 - :status: open + :status: verified :tags: Operations The endpoint shall return the current status and any result details of the addressed operation execution. @@ -95,7 +95,7 @@ The API supports: .. req:: PUT /{entity}/operations/{op-id}/executions/{exec-id} :id: REQ_INTEROP_038 - :status: open + :status: verified :tags: Operations The endpoint shall control the addressed operation execution (e.g. execute, freeze, reset, stop) @@ -120,7 +120,7 @@ The API supports: .. req:: DELETE /{entity}/operations/{op-id}/executions/{exec-id} :id: REQ_INTEROP_039 - :status: open + :status: verified :tags: Operations The endpoint shall terminate the addressed operation execution (if still running) and remove diff --git a/docs/troubleshooting.rst b/docs/troubleshooting.rst index d3ebc8eb..4a73ce2b 100644 --- a/docs/troubleshooting.rst +++ b/docs/troubleshooting.rst @@ -23,19 +23,6 @@ Solution: Initialize and update rosdep: sudo rosdep init # Only needed once rosdep update -**Build fails with C++17 errors** - -.. code-block:: text - - error: 'expected' is not a member of 'std' - -Solution: Ensure you have GCC 13 or newer: - -.. code-block:: bash - - gcc --version # Should show 13.x or higher - sudo apt install gcc-13 g++-13 - **Cannot find ros2_medkit packages** .. code-block:: bash @@ -205,8 +192,8 @@ Optimize with: .. code-block:: yaml - max_parallel_topic_samples: 20 # Default is 10 - topic_sample_timeout_sec: 1.0 # Default is 3.0 + max_parallel_topic_samples: 20 # Increase from default 10 + topic_sample_timeout_sec: 0.5 # Decrease from default 1.0 **High CPU usage** @@ -214,7 +201,7 @@ Reduce cache refresh rate: .. code-block:: yaml - refresh_interval_ms: 10000 # 10 seconds instead of default 2 + refresh_interval_ms: 30000 # 30 seconds instead of default 10s FAQ --- diff --git a/docs/tutorials/custom_areas.rst b/docs/tutorials/custom_areas.rst index 62c2c466..1d0a96b7 100644 --- a/docs/tutorials/custom_areas.rst +++ b/docs/tutorials/custom_areas.rst @@ -18,10 +18,11 @@ ros2_medkit uses a hierarchical entity model inspired by SOVD System └── Areas (logical/physical domains) - └── Components (nodes, devices) - ├── Data (topics) - ├── Operations (services, actions) - └── Configurations (parameters) + └── Components (logical/physical units) + └── Apps (individual ROS 2 nodes) + ├── Data (topics) + ├── Operations (services, actions) + └── Configurations (parameters) **Areas** represent logical or physical domains of your robot: @@ -30,36 +31,49 @@ ros2_medkit uses a hierarchical entity model inspired by SOVD - ``/manipulation`` - Arm and gripper control - ``/safety`` - Emergency stops and safety systems -**Components** are individual software or hardware units: +**Components** are logical groupings of Apps: -- ROS 2 nodes -- Virtual components from topic namespaces +- Defined in manifest files (explicit grouping with semantic IDs) +- Or auto-created as **synthetic components** per namespace in runtime mode -Default Namespace Mapping -------------------------- +**Apps** are individual ROS 2 nodes (the actual running processes) -By default, ros2_medkit extracts Areas from the first namespace level: +Default Entity Mapping (Runtime Mode) +------------------------------------- + +In runtime-only mode with synthetic components enabled (default), +ros2_medkit creates this hierarchy: .. list-table:: - :widths: 50 25 25 + :widths: 40 20 20 20 :header-rows: 1 * - Node FQN - Area - - Component - * - ``/perception/camera/driver`` + - Component (Synthetic) + - App + * - ``/perception/camera`` + - perception + - perception_component + - camera + * - ``/perception/lidar`` - perception - - driver + - perception_component + - lidar * - ``/nav2/controller`` - nav2 + - nav2_component - controller - * - ``/arm/joint_trajectory_controller`` - - arm - - joint_trajectory_controller * - ``/my_node`` (no namespace) - root + - root_component - my_node +.. note:: + + In **manifest mode**, you define Components explicitly with semantic IDs. + See :doc:`manifest-discovery` for details. + Designing Your Namespace Structure ---------------------------------- @@ -178,8 +192,8 @@ Implementing in Launch Files Topic-Based Discovery --------------------- -ros2_medkit also discovers components from topics without nodes. -This is useful for: +ros2_medkit can also create Components for topic namespaces that don't +have any ROS 2 nodes. This is useful for: - Hardware bridges that publish topics directly - Simulation tools (Isaac Sim, Gazebo with custom plugins) @@ -189,7 +203,8 @@ This is useful for: 1. Gateway scans all topics 2. Extracts unique namespace prefixes -3. Creates virtual components for namespaces without nodes +3. Creates synthetic Components for namespaces without running nodes +4. Creates virtual Apps representing the topic source **Example:** Isaac Sim publishes ``/carter1/odom``, ``/carter1/cmd_vel`` diff --git a/docs/tutorials/demos/demo-sensor.rst b/docs/tutorials/demos/demo-sensor.rst new file mode 100644 index 00000000..b2b758ee --- /dev/null +++ b/docs/tutorials/demos/demo-sensor.rst @@ -0,0 +1,244 @@ +Sensor Diagnostics Demo +======================= + +This tutorial walks through the **sensor_diagnostics** demo — a lightweight +demonstration of ros2_medkit's monitoring, configuration, and fault detection +capabilities. + +.. contents:: Table of Contents + :local: + :depth: 2 + +Overview +-------- + +The sensor diagnostics demo showcases ros2_medkit with simulated sensor nodes: + +- **LiDAR Simulator** — 2D laser scanner with fault injection +- **Camera Simulator** — RGB camera with noise and brightness control +- **IMU Simulator** — 9-DOF inertial measurement unit +- **GPS Simulator** — GPS receiver with position drift +- **Anomaly Detector** — Monitors sensor data for faults + +**Key Features:** + +- Runs anywhere — no Gazebo, no GPU required +- Fast startup — seconds vs minutes +- Docker-based deployment with web UI included +- Dual fault reporting paths (legacy diagnostics + modern direct) +- Runtime fault injection via REST API + +Prerequisites +------------- + +- Docker and Docker Compose installed +- Git (to clone the demo repository) + +Starting the Demo +----------------- + +Clone the demo repository and run the startup script: + +.. code-block:: bash + + git clone https://github.com/selfpatch/selfpatch_demos.git + cd selfpatch_demos/demos/sensor_diagnostics + + # Start the demo (daemon mode) + ./run-demo.sh + +.. figure:: /_static/images/20_sensor_demo_run_terminal.png + :alt: Demo startup terminal output + :align: center + :width: 600px + + Terminal showing demo services starting up. + +The script will build and start Docker containers with: + +- ros2_medkit gateway (REST API on port 8080) +- sovd_web_ui (Web interface on port 3000) +- Simulated sensor nodes (lidar, camera, imu, gps) +- Anomaly detector for fault monitoring +- Diagnostic bridge for legacy fault reporting + +**Startup Options:** + +.. code-block:: bash + + ./run-demo.sh --attached # Run in foreground with logs + ./run-demo.sh --update # Pull latest images + ./run-demo.sh --no-cache # Build without cache + +Exploring the Demo +------------------ + +Open the web UI at http://localhost:3000 and connect to the gateway at +http://localhost:8080 and api/v1 + +.. figure:: /_static/images/21_sensor_demo_ui_view.png + :alt: Sensor demo in web UI + :align: center + :width: 600px + + Web UI showing sensor demo entity hierarchy. + +The demo exposes entities organized by namespace: + +- ``/sensors`` — Sensor simulator nodes (lidar, camera, imu, gps) +- ``/processing`` — Anomaly detector +- ``/bridge`` — Diagnostic bridge +- ``/diagnostics`` — ros2_medkit gateway + +Interactive API exploration: + +.. code-block:: bash + + # Run the interactive check script + ./check-demo.sh + +Reading Sensor Data +------------------- + +Navigate to a sensor app in the web UI and explore the **data** folder to see published topics. + +Query sensor data via REST API: + +.. code-block:: bash + + # Get LiDAR scan + curl http://localhost:8080/api/v1/apps/lidar-sim/data/scan | jq '.ranges[:5]' + + # Get IMU data + curl http://localhost:8080/api/v1/apps/imu-sim/data/imu | jq '.linear_acceleration' + + # Get GPS fix + curl http://localhost:8080/api/v1/apps/gps-sim/data/fix | jq '{lat: .latitude, lon: .longitude}' + + # Get camera image info + curl http://localhost:8080/api/v1/apps/camera-sim/data/image | jq '{width, height, encoding}' + +Managing Configurations +----------------------- + +Click on the **configurations** folder in the web UI to see ROS 2 parameters for each sensor. + +View and modify sensor parameters: + +.. code-block:: bash + + # List all LiDAR configurations + curl http://localhost:8080/api/v1/apps/lidar-sim/configurations | jq + + # Get specific parameter + curl http://localhost:8080/api/v1/apps/lidar-sim/configurations/noise_stddev | jq + + # Change scan rate + curl -X PUT http://localhost:8080/api/v1/apps/lidar-sim/configurations/scan_rate \ + -H "Content-Type: application/json" \ + -d '{"value": 20.0}' + +**Key Parameters:** + +- ``scan_rate`` / ``rate`` — Publishing frequency (Hz) +- ``noise_stddev`` / ``noise_level`` — Sensor noise magnitude +- ``drift_rate`` — Gradual sensor drift +- ``failure_probability`` — Probability of sensor timeout +- ``inject_nan`` / ``inject_black_frames`` — Fault injection flags + +Fault Injection +--------------- + +The demo starts with **normal sensor operation** (no faults). You can inject +faults at runtime using provided scripts: + +**Available fault injection scripts:** + +.. code-block:: bash + + # Inject high noise (triggers legacy diagnostics path) + ./inject-noise.sh + + # Inject sensor timeouts + ./inject-failure.sh + + # Inject NaN values in sensor data + ./inject-nan.sh + + # Inject sensor drift + ./inject-drift.sh + + # Restore normal operation + ./restore-normal.sh + +**View active faults:** + +.. code-block:: bash + + # List all system faults + curl http://localhost:8080/api/v1/faults + + # Get faults for specific sensor + curl http://localhost:8080/api/v1/apps/lidar-sim/faults + +**Manual fault injection via API:** + +You can also inject faults by setting parameters directly: + +.. code-block:: bash + + # Increase noise level + curl -X PUT http://localhost:8080/api/v1/apps/lidar-sim/configurations/noise_stddev \ + -H "Content-Type: application/json" -d '{"value": 0.5}' + + # Enable NaN injection + curl -X PUT http://localhost:8080/api/v1/apps/imu-sim/configurations/inject_nan \ + -H "Content-Type: application/json" -d '{"value": true}' + + # Increase failure probability + curl -X PUT http://localhost:8080/api/v1/apps/gps-sim/configurations/failure_probability \ + -H "Content-Type: application/json" -d '{"value": 0.3}' + +Stopping the Demo +----------------- + +To stop all services: + +.. code-block:: bash + + ./stop-demo.sh + +Or manually: + +.. code-block:: bash + + docker compose down + +Architecture +------------ + +The demo implements the following architecture: + +.. code-block:: text + + Sensor Diagnostics Demo + ├── /sensors # Simulated sensor nodes + │ ├── lidar_sim # 2D LiDAR (Legacy path) + │ ├── camera_sim # RGB camera (Legacy path) + │ ├── imu_sim # 9-DOF IMU (Modern path) + │ └── gps_sim # GPS receiver (Modern path) + ├── /processing # Data processing + │ └── anomaly_detector # Fault detection + ├── /bridge # Diagnostic conversion + │ └── diagnostic_bridge # /diagnostics → FaultManager + └── /diagnostics # Monitoring + └── ros2_medkit_gateway # REST API gateway + +See Also +-------- + +- :doc:`/getting_started` — Basic gateway setup +- :doc:`/api/rest` — REST API reference +- :doc:`/config/fault-manager` — Fault Manager configuration +- :doc:`demo-turtlebot3` — TurtleBot3 simulation demo +- `selfpatch_demos `_ — Demo repository diff --git a/docs/tutorials/demos/demo-turtlebot3.rst b/docs/tutorials/demos/demo-turtlebot3.rst new file mode 100644 index 00000000..36dffe29 --- /dev/null +++ b/docs/tutorials/demos/demo-turtlebot3.rst @@ -0,0 +1,232 @@ +TurtleBot3 Demo +=============== + +This tutorial shows ros2_medkit integration with a TurtleBot3 simulation +running Gazebo and Nav2 navigation stack. + +.. contents:: Table of Contents + :local: + :depth: 2 + +Overview +-------- + +This Docker-based demo showcases ros2_medkit with TurtleBot3 Waffle: + +- **TurtleBot3 Simulation** — Gazebo world with TurtleBot3 Waffle robot +- **Nav2 Navigation** — Full navigation stack with SLAM +- **ros2_medkit Gateway** — REST API exposing robot capabilities +- **Web UI** — Visual entity browser + +**Key Features:** + +- Complete navigation system with goal sending +- Sensor data access (LiDAR, odometry, camera) +- Fault injection scenarios (localization, navigation failures) +- Docker-based — no local ROS 2 installation needed + +Prerequisites +------------- + +- Docker and Docker Compose installed +- Git (to clone the demo repository) + +Starting the Demo +----------------- + +Clone the demo repository and run the startup script: + +.. code-block:: bash + + git clone https://github.com/selfpatch/selfpatch_demos.git + cd selfpatch_demos/demos/turtlebot3_integration + + # Start the demo (daemon mode) + ./run-demo.sh + +.. figure:: /_static/images/17_turtlebot_run_demo_terminal.png + :alt: Demo startup terminal output + :align: center + :width: 600px + + Terminal showing demo services starting up. + +The script will build and start Docker containers with: + +- Gazebo simulation with TurtleBot3 Waffle +- Nav2 navigation stack +- ros2_medkit gateway (REST API on port 8080) +- sovd_web_ui (Web interface on port 3000) + +**Startup Options:** + +.. code-block:: bash + + ./run-demo.sh --attached # Run in foreground with logs + ./run-demo.sh --update # Pull latest images + ./run-demo.sh --no-cache # Build without cache + +Exploring the System +-------------------- + +Open the web UI at http://localhost:3000 and connect to the gateway at +http://localhost:8080 and api/v1 + +.. figure:: /_static/images/16_turtlebot_gazebo.png + :alt: TurtleBot3 in Gazebo + :align: center + :width: 600px + + TurtleBot3 Waffle in Gazebo simulation. + +The demo uses a **manifest-based configuration** to organize entities into logical areas: + +- **robot** — TurtleBot3 hardware and drivers +- **navigation** — Nav2 navigation stack +- **diagnostics** — ros2_medkit gateway and fault management +- **bridge** — Legacy diagnostics bridge + +Querying via API: + +.. code-block:: bash + + curl http://localhost:8080/api/v1/areas | jq + +.. figure:: /_static/images/13_curl_areas_turtlebot3.png + :alt: Areas response + :align: center + :width: 600px + + Areas discovered from TurtleBot3 system. + +Interactive API exploration: + +.. code-block:: bash + + # Run the interactive check script + ./check-entities.sh + +Reading Sensor Data +------------------- + +Navigate to an app in the web UI and explore the **data** folder to see published topics. + +Query data via REST API: + +.. code-block:: bash + + # List all apps + curl http://localhost:8080/api/v1/apps | jq + + # Get specific topic from AMCL localization + curl http://localhost:8080/api/v1/apps/amcl/data | jq + + # Get specific topic from controller server + curl http://localhost:8080/api/v1/apps/controller-server/data | jq + +.. figure:: /_static/images/06_topic_data_view.png + :alt: Topic data view + :align: center + :width: 600px + + Viewing topic data in the web UI. + +Sending Navigation Goals +------------------------- + +Use the provided script to send navigation goals: + +.. code-block:: bash + + # Send a navigation goal + ./send-nav-goal.sh x y yaw + +This sends a goal to Nav2 and monitors its progress through the gateway API. + +You can also interact with the navigation stack via API: + +.. code-block:: bash + + # List operations on BT Navigator + curl http://localhost:8080/api/v1/apps/bt-navigator/operations | jq + + # List operations on Controller Server + curl http://localhost:8080/api/v1/apps/controller-server/operations | jq + +Managing Parameters +------------------- + +Click on the **configurations** folder in the web UI to see ROS 2 parameters. + +View and modify parameters: + +.. code-block:: bash + + # List all configurations for AMCL + curl http://localhost:8080/api/v1/apps/amcl/configurations | jq + + # Get specific parameter + curl http://localhost:8080/api/v1/apps/amcl/configurations/use_sim_time | jq + + # Change parameter value + curl -X PUT http://localhost:8080/api/v1/apps/amcl/configurations/use_sim_time \ + -H "Content-Type: application/json" \ + -d '{"value": false}' + +Fault Injection +--------------- + +The demo includes fault injection scripts to test diagnostic capabilities: + +**Available fault injection scripts:** + +.. code-block:: bash + + # Inject localization failure + ./inject-localization-failure.sh + + # Inject navigation failure + ./inject-nav-failure.sh + + # Restore normal operation + ./restore-normal.sh + +**View active faults:** + +.. code-block:: bash + + # Check faults script + ./check-faults.sh + + # Or query via API + curl http://localhost:8080/api/v1/faults + +.. figure:: /_static/images/18_faults_injected_dashboard.png + :alt: Faults dashboard + :align: center + :width: 600px + + Dashboard showing active faults. + +.. figure:: /_static/images/19_faults_injected_app_view.png + :alt: Faults in app view + :align: center + :width: 600px + + Fault details in the entity view. + +Stopping the Demo +----------------- + +To stop all services: + +.. code-block:: bash + + ./stop-demo.sh + +See Also +-------- + +- :doc:`demo-sensor` — Built-in sensor diagnostics demo +- :doc:`/tutorials/docker` — Docker deployment guide +- `selfpatch_demos `_ — Integration demos repository diff --git a/docs/tutorials/demos/index.rst b/docs/tutorials/demos/index.rst new file mode 100644 index 00000000..037ebd46 --- /dev/null +++ b/docs/tutorials/demos/index.rst @@ -0,0 +1,59 @@ +Demos Overview +============== + +This section provides walkthroughs for running ros2_medkit with demo systems +of increasing complexity. + +.. toctree:: + :maxdepth: 1 + + demo-sensor + demo-turtlebot3 + +Learning Path +------------- + +The demos are organized by complexity — start simple and progress to more +advanced scenarios: + +**Start here:** :doc:`/getting_started` — **Quick tutorial** ⚡ + + Learn the basics with built-in demo nodes. Perfect for exploring the gateway + API without external dependencies. + + - ✅ No Docker required + - ✅ Seconds to start + - ✅ Simple automotive sensors + - ✅ Interactive curl examples + +Then explore these demos: + +1. :doc:`demo-sensor` — **Lightweight diagnostics** 🔬 + + Docker-based sensor simulation with comprehensive fault detection and + multiple reporting paths. + + - 🐳 Docker Compose deployment + - ✅ No Gazebo or GPU needed + - 🔧 Runtime fault injection scripts + - 📊 Dual reporting mechanisms + +2. :doc:`demo-turtlebot3` — **Full robot simulation** 🤖 + + Complete robotics stack with navigation, visualization, and realistic + sensor data. + + - 🎮 Gazebo simulation + - 🗺️ Nav2 navigation + - 🚀 Production-like complexity + - 🖥️ GPU recommended + +Choose Your Demo +---------------- + +**I want to...** + +- 🚀 **Get started quickly** → :doc:`/getting_started` +- 🔍 **Learn fault management** → :doc:`demo-sensor` +- 🤖 **Test with realistic robotics** → :doc:`demo-turtlebot3` +- 📚 **Understand the full API** → Start with :doc:`/getting_started`, then explore both demos diff --git a/docs/tutorials/docker.rst b/docs/tutorials/docker.rst index 916e93f5..16d5d8a4 100644 --- a/docs/tutorials/docker.rst +++ b/docs/tutorials/docker.rst @@ -35,7 +35,14 @@ This starts: - ros2_medkit_gateway - `sovd_web_ui `_ (Web UI) -Access the UI at http://localhost:5173 +Access the UI at http://localhost:3000 (Docker container maps port 80 to 3000). + +.. note:: + + **Port confusion:** + + - **Development** (``npm run dev``): http://localhost:5173 (Vite dev server) + - **Docker/Production**: http://localhost:3000 when using ``-p 3000:80`` Building the Gateway Image -------------------------- diff --git a/docs/tutorials/heuristic-apps.rst b/docs/tutorials/heuristic-apps.rst index b6c22f17..45765b14 100644 --- a/docs/tutorials/heuristic-apps.rst +++ b/docs/tutorials/heuristic-apps.rst @@ -143,11 +143,14 @@ When ``true`` (default), the gateway creates synthetic Components to group Apps: listings from all hosted Apps for convenience, but execution must target the specific App that owns the operation. -When ``false``, each node is its own Component (no grouping): +When ``false``, no synthetic Components are created (Apps-only mode): .. code-block:: bash curl http://localhost:8080/api/v1/components + # Returns: [] (empty - no synthetic components) + + curl http://localhost:8080/api/v1/apps # Returns: [{"id": "lidar_driver"}, {"id": "camera_node"}] grouping_strategy @@ -234,9 +237,9 @@ grouped entities: The ``source`` field indicates how the component was discovered: -- ``synthetic``: Grouped from multiple nodes -- ``node``: Direct node-to-component mapping (legacy mode) -- ``topic``: From topic-only namespace +- ``synthetic``: Auto-created from namespace grouping in runtime mode +- ``topic``: Created from topic-only namespace (no running nodes) +- ``manifest``: Explicitly defined in manifest file (see :doc:`manifest-discovery`) Migrating to Manifest Discovery ------------------------------- diff --git a/docs/tutorials/index.rst b/docs/tutorials/index.rst index d0a6ac56..281b6b3d 100644 --- a/docs/tutorials/index.rst +++ b/docs/tutorials/index.rst @@ -6,6 +6,7 @@ Step-by-step guides for common use cases with ros2_medkit. .. toctree:: :maxdepth: 1 + demos/index heuristic-apps manifest-discovery migration-to-manifest @@ -17,6 +18,15 @@ Step-by-step guides for common use cases with ros2_medkit. devcontainer integration custom_areas + web-ui + mcp-server + +Demos +----- + +:doc:`demos/index` + Walkthroughs for running ros2_medkit with demo systems including + the built-in sensor demo and TurtleBot3 simulation. Discovery Tutorials ------------------- @@ -57,6 +67,15 @@ Basic Tutorials :doc:`devcontainer` Set up a VS Code development container for ros2_medkit. +Companion Projects +------------------ + +:doc:`web-ui` + sovd_web_ui — A web interface for browsing SOVD entity trees. + +:doc:`mcp-server` + ros2_medkit_mcp — Connect LLMs to your ROS 2 system via MCP protocol. + Advanced Tutorials ------------------ diff --git a/docs/tutorials/integration.rst b/docs/tutorials/integration.rst index 5ae69a0c..859e8bc4 100644 --- a/docs/tutorials/integration.rst +++ b/docs/tutorials/integration.rst @@ -34,8 +34,9 @@ Just run the gateway alongside your nodes: The gateway will automatically discover: -- All running nodes → Components -- Node namespaces → Areas +- All running nodes → Apps +- Top-level namespaces → Areas +- Namespace groupings → Synthetic Components - Topics, services, actions → Data, Operations - Parameters → Configurations @@ -165,7 +166,8 @@ Any topic under your node's namespace is automatically exposed via the Data API: .. code-block:: cpp // These topics will be accessible via: - // GET /api/v1/components/my_node/data + // GET /api/v1/apps/{app_id}/data + // (or /api/v1/components/{component_id}/data in synthetic mode) pub_status_ = create_publisher("status", 10); pub_diagnostics_ = create_publisher("diagnostics", 10); @@ -184,12 +186,12 @@ Services and actions under your namespace become Operations: .. code-block:: cpp - // Service: POST /api/v1/components/my_node/operations/reset/executions + // Service: POST /api/v1/apps/{app_id}/operations/reset/executions srv_reset_ = create_service( "reset", std::bind(&MyNode::handle_reset, this, _1, _2)); - // Action: POST /api/v1/components/my_node/operations/calibrate/executions + // Action: POST /api/v1/apps/{app_id}/operations/calibrate/executions action_calibrate_ = rclcpp_action::create_server( this, "calibrate", std::bind(&MyNode::handle_goal, this, _1, _2), @@ -204,7 +206,7 @@ ROS 2 parameters become Configurations automatically: .. code-block:: cpp // These parameters will be accessible via: - // GET/PUT /api/v1/components/my_node/configurations/update_rate + // GET/PUT /api/v1/apps/{app_id}/configurations/{param_name} declare_parameter("update_rate", 10.0); declare_parameter("enabled", true); diff --git a/docs/tutorials/manifest-discovery.rst b/docs/tutorials/manifest-discovery.rst index a625192d..021be5d3 100644 --- a/docs/tutorials/manifest-discovery.rst +++ b/docs/tutorials/manifest-discovery.rst @@ -41,11 +41,11 @@ Discovery Modes Comparison - ✅ Yes - ❌ No * - Custom areas - - ❌ No + - ❌ No (derived from namespaces) - ✅ Yes - ✅ Yes * - Apps entity type - - ❌ No + - ✅ Yes (ROS nodes → Apps) - ✅ Yes - ✅ Yes * - Functions entity type @@ -385,34 +385,34 @@ Manifest mode adds the following endpoints: - Since * - ``GET /apps`` - List all apps - - 0.2.0 + - 0.1.0 * - ``GET /apps/{id}`` - Get app capabilities - - 0.2.0 + - 0.1.0 * - ``GET /apps/{id}/data`` - Get app topic data - - 0.2.0 + - 0.1.0 * - ``GET /apps/{id}/operations`` - List app operations - - 0.2.0 + - 0.1.0 * - ``GET /apps/{id}/configurations`` - List app configurations - - 0.2.0 + - 0.1.0 * - ``GET /functions`` - List all functions - - 0.2.0 + - 0.1.0 * - ``GET /functions/{id}`` - Get function capabilities - - 0.2.0 + - 0.1.0 * - ``GET /functions/{id}/hosts`` - List function host apps - - 0.2.0 + - 0.1.0 * - ``GET /functions/{id}/data`` - Aggregated data from hosts - - 0.2.0 + - 0.1.0 * - ``GET /functions/{id}/operations`` - Aggregated operations - - 0.2.0 + - 0.1.0 Next Steps ---------- diff --git a/docs/tutorials/mcp-server.rst b/docs/tutorials/mcp-server.rst new file mode 100644 index 00000000..b0e7b030 --- /dev/null +++ b/docs/tutorials/mcp-server.rst @@ -0,0 +1,262 @@ +MCP Server for LLM Integration +============================== + +ros2_medkit_mcp provides MCP (Model Context Protocol) tools for connecting +Large Language Models to your ROS 2 system via the ros2_medkit gateway. + +.. contents:: Table of Contents + :local: + :depth: 2 + +Overview +-------- + +The MCP server enables: + +- **Natural language queries** about robot state +- **LLM-assisted diagnostics** and troubleshooting +- **Autonomous agents** for robot monitoring and control + +The server wraps the gateway's HTTP API, making it accessible to LLMs through +standardized MCP tools. + +Quick Start +----------- + +Prerequisites +~~~~~~~~~~~~~ + +- Python 3.11+ +- Poetry +- A running ros2_medkit gateway + +Installation +~~~~~~~~~~~~ + +.. code-block:: bash + + git clone https://github.com/selfpatch/ros2_medkit_mcp.git + cd ros2_medkit_mcp + poetry install + +Running the Server +~~~~~~~~~~~~~~~~~~ + +**stdio transport** (for Claude Desktop, VS Code): + +.. code-block:: bash + + poetry run ros2-medkit-mcp-stdio + +**HTTP transport** (for remote access): + +.. code-block:: bash + + poetry run ros2-medkit-mcp-http --host 0.0.0.0 --port 8765 + +Configuration +------------- + +The server is configured via environment variables: + +.. list-table:: + :header-rows: 1 + :widths: 30 30 40 + + * - Variable + - Default + - Description + * - ``ROS2_MEDKIT_BASE_URL`` + - ``http://localhost:8080/api/v1`` + - Base URL of the gateway API + * - ``ROS2_MEDKIT_BEARER_TOKEN`` + - *(none)* + - Optional JWT token for authentication + * - ``ROS2_MEDKIT_TIMEOUT_S`` + - ``30`` + - HTTP request timeout in seconds + +Claude Desktop Integration +-------------------------- + +Add to your ``claude_desktop_config.json``: + +.. code-block:: json + + { + "mcpServers": { + "ros2_medkit": { + "command": "poetry", + "args": ["run", "ros2-medkit-mcp-stdio"], + "cwd": "/path/to/ros2_medkit_mcp", + "env": { + "ROS2_MEDKIT_BASE_URL": "http://localhost:8080/api/v1" + } + } + } + } + +VS Code Integration +------------------- + +Create ``.vscode/mcp.json`` in your workspace: + +**Option 1: stdio transport (local)** + +.. code-block:: json + + { + "servers": { + "ros2_medkit": { + "type": "stdio", + "command": "poetry", + "args": ["run", "ros2-medkit-mcp-stdio"], + "cwd": "/path/to/ros2_medkit_mcp", + "env": { + "ROS2_MEDKIT_BASE_URL": "http://localhost:8080/api/v1" + } + } + } + } + +**Option 2: HTTP transport (Docker/remote)** + +.. code-block:: json + + { + "servers": { + "ros2_medkit": { + "type": "sse", + "url": "http://localhost:8765/mcp" + } + } + } + +Docker Usage +------------ + +.. code-block:: bash + + # Run HTTP server + docker run -p 8765:8765 \ + -e ROS2_MEDKIT_BASE_URL=http://host.docker.internal:8080/api/v1 \ + ghcr.io/selfpatch/ros2_medkit_mcp:latest + + # Run with stdio transport + docker run -i ghcr.io/selfpatch/ros2_medkit_mcp:latest stdio + +MCP Tools Reference +------------------- + +Discovery Tools +~~~~~~~~~~~~~~~ + +.. list-table:: + :header-rows: 1 + :widths: 30 70 + + * - Tool + - Description + * - ``sovd_version`` + - Get SOVD API version information + * - ``sovd_entities_list`` + - List all entities (areas, components, apps, functions) + * - ``sovd_entities_get`` + - Get a specific entity by ID + * - ``sovd_area_components`` + - List components within an area + +Faults Tools +~~~~~~~~~~~~ + +.. list-table:: + :header-rows: 1 + :widths: 30 70 + + * - Tool + - Description + * - ``sovd_faults_list`` + - List faults for a component + * - ``sovd_faults_get`` + - Get specific fault details + * - ``sovd_faults_clear`` + - Clear/acknowledge a fault + +Data Tools +~~~~~~~~~~ + +.. list-table:: + :header-rows: 1 + :widths: 30 70 + + * - Tool + - Description + * - ``sovd_entity_data`` + - Read all topic data from an entity + * - ``sovd_entity_topic_data`` + - Read data from a specific topic + * - ``sovd_publish_topic`` + - Publish data to a topic + +Operations Tools +~~~~~~~~~~~~~~~~ + +.. list-table:: + :header-rows: 1 + :widths: 30 70 + + * - Tool + - Description + * - ``sovd_list_operations`` + - List operations (services/actions) for a component + * - ``sovd_create_execution`` + - Call a service or send an action goal + * - ``sovd_get_execution`` + - Get action execution status + * - ``sovd_list_executions`` + - List all executions for an operation + * - ``sovd_cancel_execution`` + - Cancel a running action + +Configurations Tools +~~~~~~~~~~~~~~~~~~~~ + +.. list-table:: + :header-rows: 1 + :widths: 30 70 + + * - Tool + - Description + * - ``sovd_list_configurations`` + - List parameters for a component + * - ``sovd_get_configuration`` + - Get a parameter value + * - ``sovd_set_configuration`` + - Set a parameter value + * - ``sovd_delete_configuration`` + - Reset parameter to default + * - ``sovd_delete_all_configurations`` + - Reset all parameters + +Example Prompts +--------------- + +Once configured, try these prompts with your LLM: + +- *"List all components in the system"* +- *"What faults are reported by the lidar sensor?"* +- *"Show me the current temperature reading"* +- *"Call the calibrate service on the camera component"* +- *"What parameters does the sensor node have?"* + +Repository +---------- + +https://github.com/selfpatch/ros2_medkit_mcp + +See Also +-------- + +- :doc:`/getting_started` — Basic gateway setup +- :doc:`/tutorials/authentication` — Configure JWT authentication +- :doc:`/api/rest` — REST API reference diff --git a/docs/tutorials/migration-to-manifest.rst b/docs/tutorials/migration-to-manifest.rst index e2404bc1..33e353f3 100644 --- a/docs/tutorials/migration-to-manifest.rst +++ b/docs/tutorials/migration-to-manifest.rst @@ -13,8 +13,9 @@ Overview **Runtime-only mode** (default) automatically discovers: -- ROS 2 nodes → Components -- ROS 2 namespaces → Areas +- ROS 2 nodes → Apps +- Namespace groupings → Synthetic Components +- Top-level namespaces → Areas - Topics, services, actions → Data, operations **Hybrid mode** adds: @@ -184,7 +185,7 @@ Example app mapping: - id: lidar-driver name: "LiDAR Driver" category: "driver" - component: lidar-sensor + is_located_on: lidar-sensor ros_binding: node_name: ld08_driver @@ -192,7 +193,7 @@ Example app mapping: - id: amcl-node name: "AMCL Localization" category: "localization" - component: main-computer + is_located_on: main-computer depends_on: - lidar-driver ros_binding: @@ -202,7 +203,7 @@ Example app mapping: - id: planner-server name: "Planner Server" category: "navigation" - component: main-computer + is_located_on: main-computer depends_on: - amcl-node ros_binding: diff --git a/docs/tutorials/web-ui.rst b/docs/tutorials/web-ui.rst new file mode 100644 index 00000000..f17fa569 --- /dev/null +++ b/docs/tutorials/web-ui.rst @@ -0,0 +1,236 @@ +Web UI (sovd_web_ui) +==================== + +sovd_web_ui is a lightweight web application for browsing SOVD entity trees. +It connects to the ros2_medkit gateway and visualizes the entity hierarchy. + +.. figure:: /_static/images/00_ui_view.png + :alt: sovd_web_ui main interface + :align: center + :width: 600px + + The sovd_web_ui interface showing entity tree, detail panel, and data view. + +.. contents:: Table of Contents + :local: + :depth: 2 + +Features +-------- + +- **Server Connection Dialog** — Enter the URL of your gateway +- **Entity Tree Sidebar** — Browse the hierarchical structure with lazy-loading +- **Entity Detail Panel** — View raw JSON details of any selected entity +- **Data/Operations/Configurations** — Virtual folders for each entity + +Quick Start +----------- + +Using Docker +~~~~~~~~~~~~ + +.. code-block:: bash + + # Pull from GitHub Container Registry + docker pull ghcr.io/selfpatch/sovd_web_ui:latest + docker run -p 3000:80 ghcr.io/selfpatch/sovd_web_ui:latest + +Then open http://localhost:3000 in your browser. + +From Source +~~~~~~~~~~~ + +.. code-block:: bash + + # Clone the repository + git clone https://github.com/selfpatch/sovd_web_ui.git + cd sovd_web_ui + + # Install dependencies + npm install + + # Build for production + npm run build + + # Start development server (port 5173) + npm run dev + +Connecting to ros2_medkit +------------------------- + +1. Open the web UI in your browser — you'll see the connection dialog: + + .. figure:: /_static/images/01a_connection_dialog.png + :alt: Connection dialog + :align: center + :width: 600px + + Connection dialog prompting for gateway URL. + +2. Enter the gateway URL (e.g., ``http://localhost:8080``) and base endpoint (e.g., ``api/v1``): + + .. figure:: /_static/images/01b_connection_dialog_filled.png + :alt: Connection dialog with URL + :align: center + :width: 600px + + Enter your gateway URL and click Connect. + +3. Browse the entity tree in the left sidebar: + + .. figure:: /_static/images/02_entity_tree_collapsed.png + :alt: Entity tree collapsed + :align: center + :width: 600px + + Entity tree showing areas and components. + +4. Expand nodes to see the hierarchy: + + .. figure:: /_static/images/03_entity_tree_expanded.png + :alt: Entity tree expanded + :align: center + :width: 600px + + Expanded tree showing apps and virtual folders. + +5. Click on any entity to view its details: + + .. figure:: /_static/images/04_app_entity_detail.png + :alt: Entity detail panel + :align: center + :width: 600px + + Detail panel showing entity metadata and capabilities. + +.. tip:: + + If the gateway runs on a different host, ensure CORS is configured. + See :doc:`/config/server` for CORS settings. + +Using the Interface +------------------- + +Data View +~~~~~~~~~ + +Click on the **data** folder under any entity to see available topics: + +.. figure:: /_static/images/05_data_view.png + :alt: Data view + :align: center + :width: 600px + + List of topics published by an entity. + +Click on a specific topic to see its current value: + +.. figure:: /_static/images/06_topic_data_view.png + :alt: Topic data view + :align: center + :width: 600px + + Real-time topic data with JSON visualization. + +Operations +~~~~~~~~~~ + +The **operations** folder shows available services and actions: + +.. figure:: /_static/images/07_operations_panel.png + :alt: Operations panel + :align: center + :width: 600px + + List of operations (services and actions) for an entity. + +Execute an operation and see the result: + +.. figure:: /_static/images/08_operations_execution.png + :alt: Operation execution + :align: center + :width: 600px + + Operation execution with request/response display. + +Configurations +~~~~~~~~~~~~~~ + +The **configurations** folder exposes ROS 2 node parameters: + +.. figure:: /_static/images/09_configurations_list.png + :alt: Configurations list + :align: center + :width: 600px + + List of parameters with current values. + +Edit parameters directly in the UI: + +.. figure:: /_static/images/10_configuration_edit.png + :alt: Configuration edit + :align: center + :width: 600px + + Inline parameter editing with type validation. + +Docker Compose Example +---------------------- + +Run both gateway and web UI together: + +.. code-block:: yaml + + # docker-compose.yml + version: '3.8' + services: + gateway: + image: ros:jazzy + command: > + bash -c "source /opt/ros/jazzy/setup.bash && + ros2 launch ros2_medkit_gateway gateway.launch.py server_host:=0.0.0.0" + ports: + - "8080:8080" + network_mode: host + + web_ui: + image: ghcr.io/selfpatch/sovd_web_ui:latest + ports: + - "80:80" + +Docker Image Tags +----------------- + +Images are published to GitHub Container Registry: + +.. list-table:: + :header-rows: 1 + :widths: 40 60 + + * - Trigger + - Image Tags + * - Push/merge to ``main`` + - ``latest``, ``sha-`` + * - Git tag ``v1.2.3`` + - ``1.2.3``, ``1.2``, ``1``, ``sha-`` + +Tech Stack +---------- + +- **React 19** — UI framework +- **TypeScript** — Type safety +- **Vite** — Build tool +- **TailwindCSS 4** — Styling +- **shadcn/ui** — UI components +- **Zustand** — State management + +Repository +---------- + +https://github.com/selfpatch/sovd_web_ui + +See Also +-------- + +- :doc:`/getting_started` — Basic gateway setup +- :doc:`/config/server` — Configure CORS for web UI access diff --git a/scripts/generate_verification.py b/scripts/generate_verification.py index 5dc0d0c1..7947ca84 100755 --- a/scripts/generate_verification.py +++ b/scripts/generate_verification.py @@ -10,6 +10,7 @@ WORKSPACE_DIR = SCRIPT_DIR.parent SRC_DIR = WORKSPACE_DIR / "src" OUTPUT_FILE = WORKSPACE_DIR / "docs/requirements/verification.rst" +REQUIREMENTS_SPECS_DIR = WORKSPACE_DIR / "docs/requirements/specs" def parse_cpp_file(file_path): @@ -229,6 +230,89 @@ def generate_rst(tests): return "\n".join(lines) +def update_requirement_status(verified_reqs): + """Update status in requirement spec files from 'open' to 'verified' for verified requirements.""" + if not REQUIREMENTS_SPECS_DIR.exists(): + print(f"Requirements specs directory {REQUIREMENTS_SPECS_DIR} does not exist.") + return + + updated_files = [] + total_updated_reqs = 0 + + for spec_file in REQUIREMENTS_SPECS_DIR.glob("*.rst"): + if spec_file.name == "index.rst": + continue + + with open(spec_file, "r", encoding="utf-8") as f: + content = f.read() + + modified = False + lines = content.split("\n") + new_lines = [] + + i = 0 + while i < len(lines): + line = lines[i] + + # Check if this starts a new .. req:: block + if re.match(r'\.\.\s+req::', line): + # Collect all lines belonging to this req block (indented lines) + req_block_lines = [line] + i += 1 + + # Collect indented lines that belong to this block + while i < len(lines): + next_line = lines[i] + # Check if line is indented (part of directive) or empty + if re.match(r'^\s+:', next_line) or (next_line.strip() == '' and i + 1 < len(lines) and re.match(r'^\s+:', lines[i + 1])): + req_block_lines.append(next_line) + i += 1 + elif next_line.strip() == '': + req_block_lines.append(next_line) + i += 1 + else: + break + + # Now analyze the req block to find :id: and :status: + req_id = None + status_line_idx = None + current_status = None + + for j, block_line in enumerate(req_block_lines): + id_match = re.match(r'\s*:id:\s+(REQ_\w+)', block_line) + if id_match: + req_id = id_match.group(1) + + status_match = re.match(r'(\s*):status:\s+(\w+)\s*$', block_line) + if status_match: + status_line_idx = j + current_status = status_match.group(2) + + # If we found a verified requirement with open status, update it + if req_id and req_id in verified_reqs and status_line_idx is not None and current_status == 'open': + indent = re.match(r'(\s*)', req_block_lines[status_line_idx]).group(1) + req_block_lines[status_line_idx] = f"{indent}:status: verified" + modified = True + total_updated_reqs += 1 + + new_lines.extend(req_block_lines) + else: + new_lines.append(line) + i += 1 + + if modified: + with open(spec_file, "w", encoding="utf-8") as f: + f.write("\n".join(new_lines)) + updated_files.append(spec_file.name) + + if updated_files: + print(f"Updated {total_updated_reqs} requirement(s) to 'verified' status in {len(updated_files)} file(s):") + for filename in updated_files: + print(f" - {filename}") + else: + print("No requirement status updates needed.") + + def main(): """Scan tests and generate verification.rst.""" all_tests = [] @@ -247,12 +331,22 @@ def main(): # Sort by ID for consistency all_tests.sort(key=lambda x: x["id"]) + # Collect all verified requirement IDs + verified_reqs = set() + for test in all_tests: + verified_reqs.update(test["verifies"]) + + # Generate verification.rst rst_content = generate_rst(all_tests) with open(OUTPUT_FILE, "w", encoding="utf-8") as f: f.write(rst_content) print("Generated " + str(OUTPUT_FILE) + " with " + str(len(all_tests)) + " tests.") + print(f"Found {len(verified_reqs)} verified requirement(s): {sorted(verified_reqs)}") + + # Update requirement statuses + update_requirement_status(verified_reqs) if __name__ == "__main__": diff --git a/src/ros2_medkit_gateway/design/index.rst b/src/ros2_medkit_gateway/design/index.rst index 3cb777f6..ea0ad6ae 100644 --- a/src/ros2_medkit_gateway/design/index.rst +++ b/src/ros2_medkit_gateway/design/index.rst @@ -253,11 +253,11 @@ Main Components - Manages periodic cleanup of old action goals (60s interval) 2. **DiscoveryManager** - Discovers ROS 2 entities and maps them to the SOVD hierarchy - - Discovers Areas from node namespaces - - Discovers Components (synthetic groups or node-based, configurable) - - Discovers Apps from ROS 2 nodes (when heuristic discovery enabled) + - Discovers Areas from node namespaces or manifest definitions + - Discovers Components (synthetic groups from runtime, or explicit from manifest) + - Discovers Apps from ROS 2 nodes (individual running processes) - Discovers Services and Actions using native rclcpp APIs - - Attaches operations (services/actions) to their parent components + - Attaches operations (services/actions) to their parent Apps and Components - Uses pluggable strategy pattern: Runtime, Manifest, or Hybrid - Uses O(n+m) algorithm with hash maps for efficient service/action attachment @@ -326,9 +326,9 @@ Main Components - Thread-safe and stateless design 9. **Data Models** - Entity representations - - ``Area`` - Physical or logical domain (namespace) - - ``Component`` - Hardware/software component with attached operations; can be ``node``, ``synthetic``, or ``topic`` based - - ``App`` - Software application (ROS 2 node); linked to parent Component + - ``Area`` - Physical or logical domain (namespace grouping) + - ``Component`` - Logical grouping of Apps; can be ``synthetic`` (auto-created from namespace), ``topic`` (from topic-only namespace), or ``manifest`` (explicitly defined) + - ``App`` - Software application (ROS 2 node); individual running process linked to parent Component - ``ServiceInfo`` - Service metadata (path, name, type) - ``ActionInfo`` - Action metadata (path, name, type) - ``EntityCache`` - Thread-safe cache of discovered entities (areas, components, apps) diff --git a/src/ros2_medkit_gateway/src/http/rest_server.cpp b/src/ros2_medkit_gateway/src/http/rest_server.cpp index 5e9c5226..3460d35c 100644 --- a/src/ros2_medkit_gateway/src/http/rest_server.cpp +++ b/src/ros2_medkit_gateway/src/http/rest_server.cpp @@ -578,6 +578,11 @@ void RESTServer::setup_routes() { discovery_handlers_->handle_get_area(req, res); }); + // TODO(#158): Dual-path design issue - in runtime-only mode, these routes accept both + // component IDs and app IDs. Semantically, /components should only accept + // synthetic component IDs, not individual ROS 2 node IDs. Consider enforcing + // proper entity type validation in a future refactor. + // // Component topic data (specific topic) - register before general route // Use (.+) for topic_name to accept slashes from percent-encoded URLs (%2F -> /) srv->Get((api_path("/components") + R"(/([^/]+)/data/(.+)$)"), diff --git a/src/ros2_medkit_gateway/test/test_integration.test.py b/src/ros2_medkit_gateway/test/test_integration.test.py index 8e459701..ef19dca0 100644 --- a/src/ros2_medkit_gateway/test/test_integration.test.py +++ b/src/ros2_medkit_gateway/test/test_integration.test.py @@ -1866,7 +1866,7 @@ def test_45_list_configurations(self): """ Test GET /apps/{app_id}/configurations lists all parameters. - @verifies REQ_INTEROP_023 + @verifies REQ_INTEROP_048 """ response = requests.get( f'{self.BASE_URL}/apps/temp_sensor/configurations', @@ -1903,7 +1903,7 @@ def test_46_get_configuration(self): """ Test GET /apps/{app_id}/configurations/{param_name} gets parameter. - @verifies REQ_INTEROP_023 + @verifies REQ_INTEROP_049 """ response = requests.get( f'{self.BASE_URL}/apps/temp_sensor/configurations/publish_rate', @@ -1936,7 +1936,7 @@ def test_47_set_configuration(self): """ Test PUT /apps/{app_id}/configurations/{param_name} sets parameter. - @verifies REQ_INTEROP_024 + @verifies REQ_INTEROP_050 """ # Set a new value using SOVD "data" field response = requests.put( @@ -1990,7 +1990,7 @@ def test_48_delete_configuration_resets_to_default(self): The DELETE method resets the parameter to its default value. It first changes the value, then resets it, then verifies the reset. - @verifies REQ_INTEROP_025 + @verifies REQ_INTEROP_052 """ # First, change the value from default using SOVD "data" field set_response = requests.put( @@ -2023,7 +2023,7 @@ def test_49_configurations_nonexistent_app(self): """ Test GET /apps/{app_id}/configurations returns 404 for unknown app. - @verifies REQ_INTEROP_023 + @verifies REQ_INTEROP_048 """ response = requests.get( f'{self.BASE_URL}/apps/nonexistent_app/configurations', @@ -2044,7 +2044,7 @@ def test_50_configuration_nonexistent_parameter(self): """ Test GET configurations/{param_name} returns 404 for unknown param. - @verifies REQ_INTEROP_023 + @verifies REQ_INTEROP_049 """ response = requests.get( f'{self.BASE_URL}/apps/temp_sensor/configurations/nonexistent_param', @@ -2065,7 +2065,7 @@ def test_51_set_configuration_missing_value(self): """ Test PUT configurations/{param_name} returns 400 when value missing. - @verifies REQ_INTEROP_024 + @verifies REQ_INTEROP_050 """ response = requests.put( f'{self.BASE_URL}/apps/temp_sensor/configurations/min_temp', @@ -2085,7 +2085,7 @@ def test_52_root_endpoint_includes_configurations(self): """ Test that root endpoint lists configurations endpoints and capability. - @verifies REQ_INTEROP_023 + @verifies REQ_INTEROP_048 """ data = self._get_json('/') @@ -2104,11 +2104,7 @@ def test_52_root_endpoint_includes_configurations(self): # ========== Operation Schema Tests (test_53-54) ========== def test_53_service_operation_has_type_info_schema(self): - """ - Test that service operations include type_info with request/response schemas. - - @verifies REQ_INTEROP_025 - """ + """Test that service operations include type_info with request/response schemas.""" # Get operations directly from the operations endpoint ops_data = self._get_json('/apps/calibration/operations') self.assertIn('items', ops_data, 'Operations endpoint should return items') @@ -2147,11 +2143,7 @@ def test_53_service_operation_has_type_info_schema(self): print(f'✓ Service operation type_info test passed: {type_info}') def test_54_action_operation_has_type_info_schema(self): - """ - Test that action operations include type_info with goal/result/feedback schemas. - - @verifies REQ_INTEROP_025 - """ + """Test that action operations include type_info with goal/result/feedback schemas.""" # Get operations directly from the operations endpoint ops_data = self._get_json('/apps/long_calibration/operations') self.assertIn('items', ops_data, 'Operations endpoint should return items')