diff --git a/docs/api/rest.rst b/docs/api/rest.rst index 5a658534c..85b5ab64e 100644 --- a/docs/api/rest.rst +++ b/docs/api/rest.rst @@ -359,9 +359,31 @@ Query and manage faults. 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/faults`` + List all faults across the system. + ``GET /api/v1/components/{id}/faults`` List all faults for an entity. + Both endpoints accept an optional ``?status=`` query parameter: + + +-----------------+--------------------------------------------------+ + | Value | Returns | + +=================+==================================================+ + | *(default)* | ``PREFAILED`` + ``CONFIRMED`` (active faults) | + +-----------------+--------------------------------------------------+ + | ``pending`` | ``PREFAILED`` only | + +-----------------+--------------------------------------------------+ + | ``confirmed`` | ``CONFIRMED`` only | + +-----------------+--------------------------------------------------+ + | ``cleared`` | ``CLEARED`` + ``HEALED`` + ``PREPASSED`` | + | | (SOVD "cleared" semantics) | + +-----------------+--------------------------------------------------+ + | ``healed`` | ``HEALED`` + ``PREPASSED`` only | + +-----------------+--------------------------------------------------+ + | ``all`` | All statuses | + +-----------------+--------------------------------------------------+ + **Example Response:** .. code-block:: json diff --git a/postman/collections/ros2-medkit-gateway.postman_collection.json b/postman/collections/ros2-medkit-gateway.postman_collection.json index e012c6221..2a6acb8ad 100644 --- a/postman/collections/ros2-medkit-gateway.postman_collection.json +++ b/postman/collections/ros2-medkit-gateway.postman_collection.json @@ -1122,7 +1122,7 @@ } ] }, - "description": "List all faults across the system including cleared ones. Use status=pending, status=confirmed, status=cleared, or status=all to filter." + "description": "List all faults across the system including cleared ones. Use status=pending, status=confirmed, status=cleared, status=healed, or status=all to filter." }, "response": [] }, @@ -1168,7 +1168,7 @@ } ] }, - "description": "List all faults including cleared ones. Use status=prefailed, status=confirmed, status=cleared, or status=all to filter." + "description": "List all faults including cleared ones. Use status=prefailed, status=confirmed, status=cleared, status=healed, or status=all to filter." }, "response": [] }, diff --git a/src/ros2_medkit_gateway/include/ros2_medkit_gateway/fault_manager.hpp b/src/ros2_medkit_gateway/include/ros2_medkit_gateway/fault_manager.hpp index 28a816d04..69410650b 100644 --- a/src/ros2_medkit_gateway/include/ros2_medkit_gateway/fault_manager.hpp +++ b/src/ros2_medkit_gateway/include/ros2_medkit_gateway/fault_manager.hpp @@ -70,12 +70,13 @@ class FaultManager { /// @param include_prefailed Include PREFAILED status faults (debounce not yet confirmed) /// @param include_confirmed Include CONFIRMED status faults /// @param include_cleared Include CLEARED status faults + /// @param include_healed Include HEALED and PREPASSED status faults /// @param include_muted Include muted faults (correlation symptoms) in response /// @param include_clusters Include cluster info in response /// @return FaultResult with array of faults (and optionally muted_faults and clusters) FaultResult list_faults(const std::string & source_id = "", bool include_prefailed = true, - bool include_confirmed = true, bool include_cleared = false, bool include_muted = false, - bool include_clusters = false); + bool include_confirmed = true, bool include_cleared = false, bool include_healed = false, + bool include_muted = false, bool include_clusters = false); /// Get a specific fault by code with environment data /// @param fault_code Fault identifier diff --git a/src/ros2_medkit_gateway/include/ros2_medkit_gateway/http/http_utils.hpp b/src/ros2_medkit_gateway/include/ros2_medkit_gateway/http/http_utils.hpp index ac0528955..a7c20fd7e 100644 --- a/src/ros2_medkit_gateway/include/ros2_medkit_gateway/http/http_utils.hpp +++ b/src/ros2_medkit_gateway/include/ros2_medkit_gateway/http/http_utils.hpp @@ -90,6 +90,7 @@ struct FaultStatusFilter { bool include_pending = true; bool include_confirmed = true; bool include_cleared = false; + bool include_healed = false; bool is_valid = true; }; @@ -107,6 +108,7 @@ inline FaultStatusFilter parse_fault_status_param(const httplib::Request & req) filter.include_pending = false; filter.include_confirmed = false; filter.include_cleared = false; + filter.include_healed = false; if (status == "pending") { filter.include_pending = true; @@ -114,10 +116,14 @@ inline FaultStatusFilter parse_fault_status_param(const httplib::Request & req) filter.include_confirmed = true; } else if (status == "cleared") { filter.include_cleared = true; + filter.include_healed = true; // SOVD maps both CLEARED and HEALED to "cleared" + } else if (status == "healed") { + filter.include_healed = true; } else if (status == "all") { filter.include_pending = true; filter.include_confirmed = true; filter.include_cleared = true; + filter.include_healed = true; } else { filter.is_valid = false; } diff --git a/src/ros2_medkit_gateway/src/fault_manager.cpp b/src/ros2_medkit_gateway/src/fault_manager.cpp index 91180b902..13d5f2cfd 100644 --- a/src/ros2_medkit_gateway/src/fault_manager.cpp +++ b/src/ros2_medkit_gateway/src/fault_manager.cpp @@ -127,7 +127,8 @@ FaultResult FaultManager::report_fault(const std::string & fault_code, uint8_t s } FaultResult FaultManager::list_faults(const std::string & source_id, bool include_prefailed, bool include_confirmed, - bool include_cleared, bool include_muted, bool include_clusters) { + bool include_cleared, bool include_healed, bool include_muted, + bool include_clusters) { std::lock_guard lock(list_mutex_); FaultResult result; @@ -152,6 +153,10 @@ FaultResult FaultManager::list_faults(const std::string & source_id, bool includ if (include_cleared) { request->statuses.push_back(ros2_medkit_msgs::msg::Fault::STATUS_CLEARED); } + if (include_healed) { + request->statuses.push_back(ros2_medkit_msgs::msg::Fault::STATUS_HEALED); + request->statuses.push_back(ros2_medkit_msgs::msg::Fault::STATUS_PREPASSED); + } // Correlation options request->include_muted = include_muted; diff --git a/src/ros2_medkit_gateway/src/http/handlers/fault_handlers.cpp b/src/ros2_medkit_gateway/src/http/handlers/fault_handlers.cpp index e73f265a4..3313e09b5 100644 --- a/src/ros2_medkit_gateway/src/http/handlers/fault_handlers.cpp +++ b/src/ros2_medkit_gateway/src/http/handlers/fault_handlers.cpp @@ -233,7 +233,7 @@ void FaultHandlers::handle_list_all_faults(const httplib::Request & req, httplib if (!filter.is_valid) { HandlerContext::send_error(res, StatusCode::BadRequest_400, ERR_INVALID_PARAMETER, "Invalid status parameter value", - {{"allowed_values", "pending, confirmed, cleared, all"}, + {{"allowed_values", "pending, confirmed, cleared, healed, all"}, {"parameter", "status"}, {"value", req.get_param_value("status")}}); return; @@ -246,7 +246,7 @@ void FaultHandlers::handle_list_all_faults(const httplib::Request & req, httplib auto fault_mgr = ctx_.node()->get_fault_manager(); // Empty source_id = no filtering, return all faults auto result = fault_mgr->list_faults("", filter.include_pending, filter.include_confirmed, filter.include_cleared, - include_muted, include_clusters); + filter.include_healed, include_muted, include_clusters); if (result.success) { // Format: items array at top level @@ -304,7 +304,7 @@ void FaultHandlers::handle_list_faults(const httplib::Request & req, httplib::Re if (!filter.is_valid) { HandlerContext::send_error(res, StatusCode::BadRequest_400, ERR_INVALID_PARAMETER, "Invalid status parameter value", - {{"allowed_values", "pending, confirmed, cleared, all"}, + {{"allowed_values", "pending, confirmed, cleared, healed, all"}, {"parameter", "status"}, {"value", req.get_param_value("status")}, {entity_info.id_field, entity_id}}); @@ -322,7 +322,7 @@ void FaultHandlers::handle_list_faults(const httplib::Request & req, httplib::Re if (entity_info.type == EntityType::FUNCTION) { // Get all faults (no namespace filter) auto result = fault_mgr->list_faults("", filter.include_pending, filter.include_confirmed, filter.include_cleared, - include_muted, include_clusters); + filter.include_healed, include_muted, include_clusters); if (!result.success) { HandlerContext::send_error(res, StatusCode::ServiceUnavailable_503, ERR_SERVICE_UNAVAILABLE, @@ -363,7 +363,7 @@ void FaultHandlers::handle_list_faults(const httplib::Request & req, httplib::Re if (entity_info.type == EntityType::COMPONENT) { // Get all faults (no namespace filter) auto result = fault_mgr->list_faults("", filter.include_pending, filter.include_confirmed, filter.include_cleared, - include_muted, include_clusters); + filter.include_healed, include_muted, include_clusters); if (!result.success) { HandlerContext::send_error(res, StatusCode::ServiceUnavailable_503, ERR_SERVICE_UNAVAILABLE, @@ -402,8 +402,9 @@ void FaultHandlers::handle_list_faults(const httplib::Request & req, httplib::Re // For other entity types (App, Area), use namespace_path filtering std::string namespace_path = entity_info.namespace_path; - auto result = fault_mgr->list_faults(namespace_path, filter.include_pending, filter.include_confirmed, - filter.include_cleared, include_muted, include_clusters); + auto result = + fault_mgr->list_faults(namespace_path, filter.include_pending, filter.include_confirmed, filter.include_cleared, + filter.include_healed, include_muted, include_clusters); if (result.success) { // Format: items array at top level @@ -578,7 +579,7 @@ void FaultHandlers::handle_clear_all_faults(const httplib::Request & req, httpli // Get all faults for this entity auto fault_mgr = ctx_.node()->get_fault_manager(); - auto faults_result = fault_mgr->list_faults(entity_info.namespace_path, "", ""); + auto faults_result = fault_mgr->list_faults(entity_info.namespace_path); if (!faults_result.success) { HandlerContext::send_error(res, StatusCode::ServiceUnavailable_503, ERR_SERVICE_UNAVAILABLE, @@ -590,8 +591,8 @@ void FaultHandlers::handle_clear_all_faults(const httplib::Request & req, httpli // Clear each fault if (faults_result.data.contains("faults") && faults_result.data["faults"].is_array()) { for (const auto & fault : faults_result.data["faults"]) { - if (fault.contains("faultCode")) { - std::string fault_code = fault["faultCode"].get(); + if (fault.contains("fault_code")) { + std::string fault_code = fault["fault_code"].get(); auto clear_result = fault_mgr->clear_fault(fault_code); if (!clear_result.success) { RCLCPP_WARN(HandlerContext::logger(), "Failed to clear fault '%s' for entity '%s': %s", fault_code.c_str(), diff --git a/src/ros2_medkit_gateway/test/test_integration.test.py b/src/ros2_medkit_gateway/test/test_integration.test.py index 023f3169a..21d41c1da 100644 --- a/src/ros2_medkit_gateway/test/test_integration.test.py +++ b/src/ros2_medkit_gateway/test/test_integration.test.py @@ -2337,8 +2337,8 @@ def test_60_list_all_faults_with_status_filter(self): self.assertIn('x-medkit', data) self.assertIn('count', data['x-medkit']) - # Test other valid status values - for status in ['pending', 'confirmed', 'cleared']: + # Test other valid status values (including healed) + for status in ['pending', 'confirmed', 'cleared', 'healed']: response = requests.get( f'{self.BASE_URL}/faults?status={status}', timeout=10 @@ -2364,6 +2364,7 @@ def test_61_list_faults_invalid_status_returns_400(self): params = data['parameters'] self.assertIn('allowed_values', params) self.assertIn('pending', params['allowed_values']) # Should mention valid values + self.assertIn('healed', params['allowed_values']) # Must include healed self.assertIn('parameter', params) self.assertEqual(params.get('parameter'), 'status') self.assertIn('value', params)