diff --git a/docker/compose.yml b/docker/compose.yml index e2a06f2..f786a71 100644 --- a/docker/compose.yml +++ b/docker/compose.yml @@ -12,4 +12,5 @@ services: && ros2 run sas_common sas_object_test_node && ros2 run sas_common sas_object_client_manager_test_node && ros2 run sas_common test_python_wrapper.py + && ros2 run sas_common test_object_client_manager_wrapper.py && ros2 run sas_common sas_simulator_test_node" \ No newline at end of file diff --git a/sas_common/__init__.py b/sas_common/__init__.py index e1163ea..9b20bbd 100644 --- a/sas_common/__init__.py +++ b/sas_common/__init__.py @@ -28,12 +28,13 @@ compiled extension module :mod:`sas_common._sas_common` for convenience. Exports: -- rclcpp_Init: Initialize rclcpp +- rclcpp_init: Initialize rclcpp - rclcpp_Node: Node wrapper exposed from the C++ extension - rclcpp_spin_some: Spin the node briefly - rclcpp_shutdown: Shutdown rclcpp - ObjectClient, SimulatorClient: C++ client wrappers for object/simulator control +- ObjectClientManager: Manager for multiple ObjectClient instances """ -from sas_common._sas_common import rclcpp_init, rclcpp_Node, rclcpp_spin_some, rclcpp_shutdown, ObjectClient, SimulatorClient +from sas_common._sas_common import rclcpp_init, rclcpp_Node, rclcpp_spin_some, rclcpp_shutdown, ObjectClient, SimulatorClient, ObjectClientManager diff --git a/scripts/test_object_client_manager_wrapper.py b/scripts/test_object_client_manager_wrapper.py new file mode 100644 index 0000000..06c260a --- /dev/null +++ b/scripts/test_object_client_manager_wrapper.py @@ -0,0 +1,75 @@ +#!/usr/bin/python3 +""" +# Copyright (c) 2026 Murilo Marques Marinho +# +# This file is part of sas_common. +# +# sas_common is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# sas_common is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with sas_common. If not, see . +# +# ####################################################################################### +# +# Author: Murilo M. Marinho, email: murilomarinho@ieee.org +# +# ####################################################################################### +""" +"""Test script for the ObjectClientManager Python bindings. + +This script demonstrates usage of the ObjectClientManager exposed via +the sas_common Python package. It initializes rclcpp, creates a node, +instantiates an ObjectClientManager, and exercises add/remove/get/list operations. + +Run with: python3 test_object_client_manager_wrapper.py +""" +from dqrobotics import * +from sas_common import rclcpp_init, rclcpp_Node, rclcpp_spin_some, rclcpp_shutdown, ObjectClientManager + +def main(): + """Run a basic smoke-test for the ObjectClientManager Python bindings.""" + try: + rclcpp_init() + node = rclcpp_Node("sas_common_object_client_manager_test_node") + + mgr = ObjectClientManager(node) + print("Initial size:", mgr.size()) + + mgr.add_client("arm") + mgr.add_client("hand") + print("Size after adding arm and hand:", mgr.size()) + + print("Has arm:", mgr.has_client("arm")) + print("Has hand:", mgr.has_client("hand")) + print("Has gripper:", mgr.has_client("gripper")) + print("Client names:", mgr.get_client_names()) + + arm = mgr.get_client("arm") + print("Arm enabled:", arm.is_enabled()) + print("Arm topic prefix:", arm.get_topic_prefix()) + + x = DQ([1]) + arm.send_pose(x) + + removed = mgr.remove_client("hand") + print("Removed hand:", removed) + print("Size after removal:", mgr.size()) + print("Client names after removal:", mgr.get_client_names()) + + rclcpp_spin_some(node) + rclcpp_shutdown() + except KeyboardInterrupt: + print("Interrupted by user") + except Exception as e: + print("Unhandled exception:", e) + +if __name__ == '__main__': + main() diff --git a/src/sas_common_py.cpp b/src/sas_common_py.cpp index 98c8dc4..fa51f80 100644 --- a/src/sas_common_py.cpp +++ b/src/sas_common_py.cpp @@ -22,13 +22,16 @@ # # ################################################################*/ #include +#include #include #include +#include #include namespace py = pybind11; using OC = sas::ObjectClient; +using OCM = sas::ObjectClientManager; using SC = sas::SimulatorClient; PYBIND11_MODULE(_sas_common, m) { @@ -65,4 +68,28 @@ PYBIND11_MODULE(_sas_common, m) { .def("is_enabled",&SC::is_enabled,"Returns true if the SimulatorClient is enabled.") .def("get_topic_prefix",&SC::get_topic_prefix, "Return the topic/service prefix configured for this client."); + + py::class_(m, "ObjectClientManager") + .def(py::init&>(), + py::arg("node"), + "Construct an ObjectClientManager that manages multiple ObjectClient instances.") + .def("add_client", &OCM::add_client, + py::arg("name"), + "Add (or replace) an ObjectClient identified by name.") + .def("remove_client", &OCM::remove_client, + py::arg("name"), + "Remove an ObjectClient by name. Returns True if found and removed.") + .def("get_client", [](OCM& self, const std::string& name) -> OC& { + return self.get_client(name); + }, + py::arg("name"), + py::return_value_policy::reference_internal, + "Return a reference to the managed ObjectClient named `name`.") + .def("has_client", &OCM::has_client, + py::arg("name"), + "Return True if a client with the given name exists.") + .def("get_client_names", &OCM::get_client_names, + "Return a list of all managed client names.") + .def("size", &OCM::size, + "Return the number of managed clients."); }