44import skiros2_common .tools .logger as log
55import rclpy
66from rclpy import action , task
7+ from rclpy .time import Duration
78from actionlib_msgs .msg import GoalStatus
89import queue
910from threading import Lock
1011from typing import Optional
1112from action_msgs .msg import GoalStatus
12-
13- class AtomicVar :
14- def __init__ (self , initialValue : Optional [any ]= None , sharedLock : Optional [Lock ] = None ) -> None :
15- self .lock = sharedLock
16- self .var = initialValue
17- if self .lock is None :
18- self .lock = Lock ()
19-
20- def get_and_reset (self ):
21- with self .lock :
22- var = self .var
23- if var is not None :
24- self .var = None
25- return var
26-
27- def set (self , var ):
28- with self .lock :
29- self .var = var
13+ from skiros2_std_skills .utils import AtomicVar
3014
3115class PrimitiveActionClient (PrimitiveBase ):
3216 """
@@ -36,8 +20,17 @@ class PrimitiveActionClient(PrimitiveBase):
3620
3721 build_client_onstart defines if the client will be built every time the skill it is called or not
3822 (save up to 0.3 seconds). Default is True
23+
24+ feedback_timeout_sec defines a timeout in seconds for not reciving action
25+ feedback in a timely manner. Could be used to detect if the action server
26+ has gone down or experiences issues. Default is no timeout.
3927 """
4028 build_client_onstart = True
29+ feedback_timeout_sec = None # set to second if you want reliability
30+
31+ def __init__ (self ):
32+ super ().__init__ ()
33+ self .client = None
4134
4235 def onPreempt (self ):
4336 """
@@ -60,17 +53,26 @@ def onStart(self):
6053 self ._result_msg = AtomicVar ()
6154 self ._fail_status = AtomicVar ()
6255
63- if self .build_client_onstart :
56+ if self .build_client_onstart or self . client is None :
6457 self .client = self .buildClient ()
65-
66- if not self .client .wait_for_server (0.5 ):
67- return self .startError ("Action server {} is not available." .format (self .client .action_client .ns ), - 101 )
6858
59+ if self .client is None :
60+ return self .startError ("Action client returned by buildClient() is None." , - 105 )
61+
62+ if not self .client .wait_for_server (0.5 ):
63+ return self .startError ("Action server is not available: {}" .format (self .client ), - 101 )
64+
6965 self ._goal_handle = None
70- self ._goal_future = self .client .send_goal_async (self .buildGoal (), feedback_callback = self ._feedback_callback )
66+
67+ goal = self .buildGoal ()
68+ if goal is None :
69+ return self .startError ("Action goal is None" , - 106 )
70+
71+ self ._goal_future = self .client .send_goal_async (goal , feedback_callback = self ._feedback_callback )
7172 self ._goal_future .add_done_callback (self ._goal_request_callback )
7273
7374 self ._get_result_future = None
75+ self ._last_feedback_timestamp = self .node .get_clock ().now ()
7476
7577 return True
7678
@@ -82,12 +84,12 @@ def onStart(self):
8284 def execute (self ):
8385 res = self ._goal_msg .get_and_reset ()
8486 if res is not None :
85- if not res :
86- # Rejected
87+ if res == False :
8788 return self .fail ("Rejected." , - 102 )
8889
8990 fb = self ._feedback_msg .get_and_reset ()
9091 if fb is not None :
92+ self ._last_feedback_timestamp = self .node .get_clock ().now ()
9193 return self .onFeedback (fb )
9294
9395 status = self ._fail_status .get_and_reset ()
@@ -97,11 +99,16 @@ def execute(self):
9799 res = self ._result_msg .get_and_reset ()
98100 if res is not None :
99101 return self .onDone (res ) # TODO: Remove None
102+
103+ if self .feedback_timeout_sec is not None :
104+ time_since_start_sec = (self .node .get_clock ().now ()- self ._last_feedback_timestamp ).nanoseconds / 1.0e9 # type: Duration
105+ log .info ("Time since start %f, %d" % (time_since_start_sec , self .feedback_timeout_sec ))
106+ if time_since_start_sec > self .feedback_timeout_sec :
107+ self .client .destroy ()
108+ self .client = None
109+ return self .fail ("Action feedback did not arrive within timeout of %d sec" % self .feedback_timeout_sec , - 104 )
100110
101111 return State .Running
102-
103- def get_result_msg (self ):
104- return self .client .get_result ()
105112
106113 def _cancel_callback (self , future ):
107114 cancel_response = future .result ()
0 commit comments