34
34
Type ,
35
35
TypedDict ,
36
36
cast ,
37
+ runtime_checkable ,
37
38
)
38
39
39
40
import rclpy
47
48
import rosidl_runtime_py .set_message
48
49
import rosidl_runtime_py .utilities
49
50
from action_msgs .srv import CancelGoal
50
- from rclpy .action import ActionClient
51
+ from rclpy .action import ActionClient , CancelResponse , GoalResponse
51
52
from rclpy .action .client import ClientGoalHandle
53
+ from rclpy .action .server import (
54
+ ActionServer ,
55
+ ServerGoalHandle ,
56
+ default_cancel_callback ,
57
+ default_goal_callback ,
58
+ default_handle_accepted_callback ,
59
+ )
52
60
from rclpy .callback_groups import ReentrantCallbackGroup
53
61
from rclpy .publisher import Publisher
54
62
from rclpy .qos import (
57
65
LivelinessPolicy ,
58
66
QoSProfile ,
59
67
ReliabilityPolicy ,
68
+ qos_profile_action_status_default ,
69
+ qos_profile_services_default ,
60
70
)
71
+ from rclpy .service import Service
61
72
from rclpy .task import Future
62
73
from rclpy .topic_endpoint_info import TopicEndpointInfo
63
74
64
75
from rai .tools .ros .utils import import_message_from_str
65
76
66
77
78
+ @runtime_checkable
67
79
class IROS2Message (Protocol ):
68
- __slots__ : tuple
80
+ __slots__ : list
69
81
70
82
def get_fields_and_field_types (self ) -> dict : ...
71
83
@@ -590,6 +602,7 @@ class ROS2ServiceAPI:
590
602
def __init__ (self , node : rclpy .node .Node ) -> None :
591
603
self .node = node
592
604
self ._logger = node .get_logger ()
605
+ self ._services : Dict [str , Service ] = {}
593
606
594
607
def call_service (
595
608
self ,
@@ -622,6 +635,19 @@ def call_service(
622
635
def get_service_names_and_types (self ) -> List [Tuple [str , List [str ]]]:
623
636
return self .node .get_service_names_and_types ()
624
637
638
+ def create_service (
639
+ self ,
640
+ service_name : str ,
641
+ service_type : str ,
642
+ callback : Callable [[Any , Any ], Any ],
643
+ ** kwargs ,
644
+ ) -> str :
645
+ srv_cls = import_message_from_str (service_type )
646
+ service = self .node .create_service (srv_cls , service_name , callback , ** kwargs )
647
+ handle = str (uuid .uuid4 ())
648
+ self ._services [handle ] = service
649
+ return handle
650
+
625
651
626
652
class ROS2ActionData (TypedDict ):
627
653
action_client : Optional [ActionClient ]
@@ -636,6 +662,7 @@ def __init__(self, node: rclpy.node.Node) -> None:
636
662
self .node = node
637
663
self ._logger = node .get_logger ()
638
664
self .actions : Dict [str , ROS2ActionData ] = {}
665
+ self ._action_servers : Dict [str , ActionServer ] = {}
639
666
self ._callback_executor = ThreadPoolExecutor (max_workers = 10 )
640
667
641
668
def _generate_handle (self ):
@@ -672,6 +699,87 @@ def _safe_callback_wrapper(
672
699
except Exception as e :
673
700
self ._logger .error (f"Error in feedback callback: { str (e )} " )
674
701
702
+ def create_action_server (
703
+ self ,
704
+ action_type : str ,
705
+ action_name : str ,
706
+ execute_callback : Callable [[ServerGoalHandle ], Type [IROS2Message ]],
707
+ * ,
708
+ callback_group : Optional [rclpy .node .CallbackGroup ] = None ,
709
+ goal_callback : Callable [[IROS2Message ], GoalResponse ] = default_goal_callback ,
710
+ handle_accepted_callback : Callable [
711
+ [ServerGoalHandle ], None
712
+ ] = default_handle_accepted_callback ,
713
+ cancel_callback : Callable [
714
+ [IROS2Message ], CancelResponse
715
+ ] = default_cancel_callback ,
716
+ goal_service_qos_profile : QoSProfile = qos_profile_services_default ,
717
+ result_service_qos_profile : QoSProfile = qos_profile_services_default ,
718
+ cancel_service_qos_profile : QoSProfile = qos_profile_services_default ,
719
+ feedback_pub_qos_profile : QoSProfile = QoSProfile (depth = 10 ),
720
+ status_pub_qos_profile : QoSProfile = qos_profile_action_status_default ,
721
+ result_timeout : int = 900 ,
722
+ ) -> str :
723
+ """
724
+ Create an action server.
725
+
726
+ Args:
727
+ action_type: The action message type with namespace
728
+ action_name: The name of the action server
729
+ execute_callback: The callback to execute when a goal is received
730
+ callback_grou: The callback group to use for the action server
731
+ goal_callback: The callback to execute when a goal is received
732
+ handle_accepted_callback: The callback to execute when a goal handle is accepted
733
+ cancel_callback: The callback to execute when a goal is canceled
734
+ goal_service_qos_profile: The QoS profile for the goal service
735
+ result_service_qos_profile: The QoS profile for the result service
736
+ cancel_service_qos_profile: The QoS profile for the cancel service
737
+ feedback_pub_qos_profile: The QoS profile for the feedback publisher
738
+ status_pub_qos_profile: The QoS profile for the status publisher
739
+ result_timeout: The timeout for waiting for a result
740
+
741
+ Returns:
742
+ The handle for the created action server
743
+
744
+ Raises:
745
+ ValueError: If the action server cannot be created
746
+ """
747
+ handle = self ._generate_handle ()
748
+ action_ros_type = import_message_from_str (action_type )
749
+ try :
750
+ action_server = ActionServer (
751
+ node = self .node ,
752
+ action_type = action_ros_type ,
753
+ action_name = action_name ,
754
+ execute_callback = execute_callback ,
755
+ callback_group = callback_group ,
756
+ goal_callback = goal_callback ,
757
+ handle_accepted_callback = handle_accepted_callback ,
758
+ cancel_callback = cancel_callback ,
759
+ goal_service_qos_profile = goal_service_qos_profile ,
760
+ result_service_qos_profile = result_service_qos_profile ,
761
+ cancel_service_qos_profile = cancel_service_qos_profile ,
762
+ feedback_pub_qos_profile = feedback_pub_qos_profile ,
763
+ status_pub_qos_profile = status_pub_qos_profile ,
764
+ result_timeout = result_timeout ,
765
+ )
766
+ self ._logger .info (f"Created action server: { action_name } " )
767
+ except TypeError as e :
768
+ import inspect
769
+
770
+ signature = inspect .signature (ActionServer .__init__ )
771
+ args = [
772
+ param .name
773
+ for param in signature .parameters .values ()
774
+ if param .name != "self"
775
+ ]
776
+
777
+ raise ValueError (
778
+ f"Failed to create action server: { str (e )} . Valid arguments are: { args } "
779
+ )
780
+ self ._action_servers [handle ] = action_server
781
+ return handle
782
+
675
783
def send_goal (
676
784
self ,
677
785
action_name : str ,
0 commit comments