diff --git a/postman/README.md b/postman/README.md index b37b433..edf61a0 100644 --- a/postman/README.md +++ b/postman/README.md @@ -9,6 +9,7 @@ This folder contains Postman collections for testing the ROS 2 Medkit Gateway RE Includes below endpoints: - ✅ GET `/` - Gateway info - ✅ GET `/areas` - List all areas +- ✅ GET `/components` - List all components ## Quick Start @@ -49,6 +50,10 @@ ros2 launch ros2_medkit_gateway demo_nodes.launch.py 6. Click **Send** 7. You should see areas: `[{"id": "powertrain", ...}, {"id": "chassis", ...}, ...]` +8. Click **"GET List Components"** +9. Click **Send** +10. You should see components: `[{"id": "temp_sensor", "namespace": "/powertrain/engine", ...}, ...]` + ## API Variables The environment includes: diff --git a/postman/collections/ros2-medkit-gateway.postman_collection.json b/postman/collections/ros2-medkit-gateway.postman_collection.json index e2b261f..07372af 100644 --- a/postman/collections/ros2-medkit-gateway.postman_collection.json +++ b/postman/collections/ros2-medkit-gateway.postman_collection.json @@ -1,7 +1,7 @@ { "info": { "name": "ROS 2 Medkit Gateway API", - "description": "Minimal collection: GET / and GET /areas endpoints only", + "description": "Collection for ROS 2 Medkit Gateway REST API endpoints", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, "item": [ @@ -43,6 +43,24 @@ "description": "List all discovered areas (powertrain, chassis, body, root)" }, "response": [] + }, + { + "name": "GET List Components", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{base_url}}/components", + "host": [ + "{{base_url}}" + ], + "path": [ + "components" + ] + }, + "description": "List all discovered components across all areas. Returns component metadata including id, namespace, fqn, type, and parent area." + }, + "response": [] } ] } diff --git a/src/ros2_medkit_gateway/README.md b/src/ros2_medkit_gateway/README.md index 9bb2bc3..355cc13 100644 --- a/src/ros2_medkit_gateway/README.md +++ b/src/ros2_medkit_gateway/README.md @@ -14,11 +14,75 @@ The ROS 2 Medkit Gateway exposes ROS 2 system information and data through a RES ## Endpoints -Current available endpoints: +### Discovery Endpoints - `GET /health` - Health check endpoint (returns healthy status) - `GET /` - Gateway status and version information - `GET /areas` - List all discovered areas (powertrain, chassis, body, root) +- `GET /components` - List all discovered components across all areas + +### API Reference + +#### GET /areas + +Lists all discovered areas in the system. + +**Example:** +```bash +curl http://localhost:8080/areas +``` + +**Response:** +```json +[ + { + "id": "powertrain", + "namespace": "/powertrain", + "type": "Area" + }, + { + "id": "chassis", + "namespace": "/chassis", + "type": "Area" + } +] +``` + +#### GET /components + +Lists all discovered components across all areas. + +**Example:** +```bash +curl http://localhost:8080/components +``` + +**Response:** +```json +[ + { + "id": "temp_sensor", + "namespace": "/powertrain/engine", + "fqn": "/powertrain/engine/temp_sensor", + "type": "Component", + "area": "powertrain" + }, + { + "id": "rpm_sensor", + "namespace": "/powertrain/engine", + "fqn": "/powertrain/engine/rpm_sensor", + "type": "Component", + "area": "powertrain" + } +] +``` + +**Response Fields:** +- `id` - Component name (node name) +- `namespace` - ROS 2 namespace where the component is running +- `fqn` - Fully qualified name (namespace + node name) +- `type` - Always "Component" +- `area` - Parent area this component belongs to ## Quick Start diff --git a/src/ros2_medkit_gateway/include/ros2_medkit_gateway/rest_server.hpp b/src/ros2_medkit_gateway/include/ros2_medkit_gateway/rest_server.hpp index 1d476a3..885e228 100644 --- a/src/ros2_medkit_gateway/include/ros2_medkit_gateway/rest_server.hpp +++ b/src/ros2_medkit_gateway/include/ros2_medkit_gateway/rest_server.hpp @@ -40,6 +40,7 @@ class RESTServer { void handle_health(const httplib::Request& req, httplib::Response& res); void handle_root(const httplib::Request& req, httplib::Response& res); void handle_list_areas(const httplib::Request& req, httplib::Response& res); + void handle_list_components(const httplib::Request& req, httplib::Response& res); GatewayNode* node_; std::string host_; diff --git a/src/ros2_medkit_gateway/src/rest_server.cpp b/src/ros2_medkit_gateway/src/rest_server.cpp index db189cd..44eaeb5 100644 --- a/src/ros2_medkit_gateway/src/rest_server.cpp +++ b/src/ros2_medkit_gateway/src/rest_server.cpp @@ -46,6 +46,11 @@ void RESTServer::setup_routes() { server_->Get("/areas", [this](const httplib::Request& req, httplib::Response& res) { handle_list_areas(req, res); }); + + // Components + server_->Get("/components", [this](const httplib::Request& req, httplib::Response& res) { + handle_list_components(req, res); + }); } void RESTServer::start() { @@ -129,4 +134,30 @@ void RESTServer::handle_list_areas(const httplib::Request& req, httplib::Respons } } +void RESTServer::handle_list_components(const httplib::Request& req, httplib::Response& res) { + (void)req; // Unused parameter + + try { + const auto cache = node_->get_entity_cache(); + + json components_json = json::array(); + for (const auto& component : cache.components) { + components_json.push_back(component.to_json()); + } + + res.set_content(components_json.dump(2), "application/json"); + } catch (const std::exception& e) { + res.status = 500; + res.set_content( + json{{"error", "Internal server error"}}.dump(), + "application/json" + ); + RCLCPP_ERROR( + rclcpp::get_logger("rest_server"), + "Error in handle_list_components: %s", + e.what() + ); + } +} + } // namespace ros2_medkit_gateway diff --git a/src/ros2_medkit_gateway/test/test_integration.test.py b/src/ros2_medkit_gateway/test/test_integration.test.py index e0d0ce6..988e0e1 100644 --- a/src/ros2_medkit_gateway/test/test_integration.test.py +++ b/src/ros2_medkit_gateway/test/test_integration.test.py @@ -178,7 +178,31 @@ def test_02_list_areas(self): self.assertIn('root', area_ids) print(f'✓ Areas test passed: {len(areas)} areas discovered') - def test_21_automotive_areas_discovery(self): + def test_03_list_components(self): + """Test GET /components returns all discovered components.""" + components = self._get_json('/components') + self.assertIsInstance(components, list) + # Should have at least 7 demo nodes + gateway node + self.assertGreaterEqual(len(components), 7) + + # Verify response structure - all components should have required fields + for component in components: + self.assertIn('id', component) + self.assertIn('namespace', component) + self.assertIn('fqn', component) + self.assertIn('type', component) + self.assertIn('area', component) + self.assertEqual(component['type'], 'Component') + + # Verify some expected component IDs are present + component_ids = [comp['id'] for comp in components] + self.assertIn('temp_sensor', component_ids) + self.assertIn('rpm_sensor', component_ids) + self.assertIn('pressure_sensor', component_ids) + + print(f'✓ Components test passed: {len(components)} components discovered') + + def test_04_automotive_areas_discovery(self): """Test that automotive areas are properly discovered.""" areas = self._get_json('/areas') area_ids = [area['id'] for area in areas]