1#[allow(unused_imports)]
2use log::{debug, error, info, warn};
3use rustdds::dds::{ReadResult, WriteResult};
4pub use action_msgs::{CancelGoalRequest, CancelGoalResponse, GoalId, GoalInfo, GoalStatusEnum};
5use builtin_interfaces::Time;
6use futures::{
7 stream::{FusedStream, StreamExt},
9 Future,
10};
11
12use crate::{
13 action_msgs, builtin_interfaces,
14 message::Message,
15 names::Name,
16 service::{request_id::RmwRequestId, AService, CallServiceError, Client},
17 unique_identifier_msgs, Subscription,
18};
19use super::{
20 ActionTypes, FeedbackMessage, GetResultRequest, GetResultResponse, SendGoalRequest,
21 SendGoalResponse,
22};
23
24pub struct ActionClient<A>
26where
27 A: ActionTypes,
28 A::GoalType: Message + Clone,
29 A::ResultType: Message + Clone,
30 A::FeedbackType: Message,
31{
32 pub(crate) my_goal_client: Client<AService<SendGoalRequest<A::GoalType>, SendGoalResponse>>,
33
34 pub(crate) my_cancel_client:
35 Client<AService<action_msgs::CancelGoalRequest, action_msgs::CancelGoalResponse>>,
36
37 pub(crate) my_result_client: Client<AService<GetResultRequest, GetResultResponse<A::ResultType>>>,
38
39 pub(crate) my_feedback_subscription: Subscription<FeedbackMessage<A::FeedbackType>>,
40
41 pub(crate) my_status_subscription: Subscription<action_msgs::GoalStatusArray>,
42
43 pub(crate) my_action_name: Name,
44}
45
46impl<A> ActionClient<A>
47where
48 A: ActionTypes,
49 A::GoalType: Message + Clone,
50 A::ResultType: Message + Clone,
51 A::FeedbackType: Message,
52{
53 pub fn name(&self) -> &Name {
54 &self.my_action_name
55 }
56
57 pub fn goal_client(&self) -> &Client<AService<SendGoalRequest<A::GoalType>, SendGoalResponse>> {
58 &self.my_goal_client
59 }
60 pub fn cancel_client(
61 &self,
62 ) -> &Client<AService<action_msgs::CancelGoalRequest, action_msgs::CancelGoalResponse>> {
63 &self.my_cancel_client
64 }
65 pub fn result_client(
66 &self,
67 ) -> &Client<AService<GetResultRequest, GetResultResponse<A::ResultType>>> {
68 &self.my_result_client
69 }
70 pub fn feedback_subscription(&self) -> &Subscription<FeedbackMessage<A::FeedbackType>> {
71 &self.my_feedback_subscription
72 }
73 pub fn status_subscription(&self) -> &Subscription<action_msgs::GoalStatusArray> {
74 &self.my_status_subscription
75 }
76
77 pub fn send_goal(&self, goal: A::GoalType) -> WriteResult<(RmwRequestId, GoalId), ()>
81 where
82 <A as ActionTypes>::GoalType: 'static,
83 {
84 let goal_id = unique_identifier_msgs::UUID::new_random();
85 self
86 .my_goal_client
87 .send_request(SendGoalRequest { goal_id, goal })
88 .map(|req_id| (req_id, goal_id))
89 }
90
91 pub fn receive_goal_response(&self, req_id: RmwRequestId) -> ReadResult<Option<SendGoalResponse>>
94 where
95 <A as ActionTypes>::GoalType: 'static,
96 {
97 loop {
98 match self.my_goal_client.receive_response() {
99 Err(e) => break Err(e),
100 Ok(None) => break Ok(None), Ok(Some((incoming_req_id, resp))) if incoming_req_id == req_id =>
102 {
104 break Ok(Some(resp))
105 }
106 Ok(Some((incoming_req_id, _resp))) => {
107 info!("Goal Response not for us: {incoming_req_id:?} != {req_id:?}");
109 continue;
110 }
111 }
112 }
113 }
117
118 pub async fn async_send_goal(
119 &self,
120 goal: A::GoalType,
121 ) -> Result<(GoalId, SendGoalResponse), CallServiceError<()>>
122 where
123 <A as ActionTypes>::GoalType: 'static,
124 {
125 let goal_id = unique_identifier_msgs::UUID::new_random();
126 let send_goal_response = self
127 .my_goal_client
128 .async_call_service(SendGoalRequest { goal_id, goal })
129 .await?;
130 Ok((goal_id, send_goal_response))
131 }
132
133 fn cancel_goal_raw(&self, goal_id: GoalId, timestamp: Time) -> WriteResult<RmwRequestId, ()> {
146 let goal_info = GoalInfo {
147 goal_id,
148 stamp: timestamp,
149 };
150 self
151 .my_cancel_client
152 .send_request(CancelGoalRequest { goal_info })
153 }
154
155 pub fn cancel_goal(&self, goal_id: GoalId) -> WriteResult<RmwRequestId, ()> {
156 self.cancel_goal_raw(goal_id, Time::ZERO)
157 }
158
159 pub fn cancel_all_goals_before(&self, timestamp: Time) -> WriteResult<RmwRequestId, ()> {
160 self.cancel_goal_raw(GoalId::ZERO, timestamp)
161 }
162
163 pub fn cancel_all_goals(&self) -> WriteResult<RmwRequestId, ()> {
164 self.cancel_goal_raw(GoalId::ZERO, Time::ZERO)
165 }
166
167 pub fn receive_cancel_response(
168 &self,
169 cancel_request_id: RmwRequestId,
170 ) -> ReadResult<Option<CancelGoalResponse>> {
171 loop {
172 match self.my_cancel_client.receive_response() {
173 Err(e) => break Err(e),
174 Ok(None) => break Ok(None), Ok(Some((incoming_req_id, resp))) if incoming_req_id == cancel_request_id => {
176 break Ok(Some(resp))
177 } Ok(Some(_)) => continue, }
180 }
181 }
182
183 pub fn async_cancel_goal(
184 &self,
185 goal_id: GoalId,
186 timestamp: Time,
187 ) -> impl Future<Output = Result<CancelGoalResponse, CallServiceError<()>>> + '_ {
188 let goal_info = GoalInfo {
189 goal_id,
190 stamp: timestamp,
191 };
192 self
193 .my_cancel_client
194 .async_call_service(CancelGoalRequest { goal_info })
195 }
196
197 pub fn request_result(&self, goal_id: GoalId) -> WriteResult<RmwRequestId, ()>
198 where
199 <A as ActionTypes>::ResultType: 'static,
200 {
201 self
202 .my_result_client
203 .send_request(GetResultRequest { goal_id })
204 }
205
206 pub fn receive_result(
207 &self,
208 result_request_id: RmwRequestId,
209 ) -> ReadResult<Option<(GoalStatusEnum, A::ResultType)>>
210 where
211 <A as ActionTypes>::ResultType: 'static,
212 {
213 loop {
214 match self.my_result_client.receive_response() {
215 Err(e) => break Err(e),
216 Ok(None) => break Ok(None), Ok(Some((incoming_req_id, GetResultResponse { status, result })))
218 if incoming_req_id == result_request_id =>
219 {
220 break Ok(Some((status, result)))
221 } Ok(Some(_)) => continue, }
224 }
225 }
226
227 pub async fn async_request_result(
232 &self,
233 goal_id: GoalId,
234 ) -> Result<(GoalStatusEnum, A::ResultType), CallServiceError<()>>
235 where
236 <A as ActionTypes>::ResultType: 'static,
237 {
238 let GetResultResponse { status, result } = self
239 .my_result_client
240 .async_call_service(GetResultRequest { goal_id })
241 .await?;
242 Ok((status, result))
243 }
244
245 pub fn receive_feedback(&self, goal_id: GoalId) -> ReadResult<Option<A::FeedbackType>>
246 where
247 <A as ActionTypes>::FeedbackType: 'static,
248 {
249 loop {
250 match self.my_feedback_subscription.take() {
251 Err(e) => break Err(e),
252 Ok(None) => break Ok(None),
253 Ok(Some((fb_msg, _msg_info))) if fb_msg.goal_id == goal_id => {
254 break Ok(Some(fb_msg.feedback))
255 }
256 Ok(Some((fb_msg, _msg_info))) => {
257 debug!(
259 "Feedback on another goal {:?} != {:?}",
260 fb_msg.goal_id, goal_id
261 )
262 }
263 }
264 }
265 }
266
267 pub fn feedback_stream(
269 &self,
270 goal_id: GoalId,
271 ) -> impl FusedStream<Item = ReadResult<A::FeedbackType>> + '_
272 where
273 <A as ActionTypes>::FeedbackType: 'static,
274 {
275 let expected_goal_id = goal_id; self
277 .my_feedback_subscription
278 .async_stream()
279 .filter_map(move |result| async move {
280 match result {
281 Err(e) => Some(Err(e)),
282 Ok((FeedbackMessage { goal_id, feedback }, _msg_info)) => {
283 if goal_id == expected_goal_id {
284 Some(Ok(feedback))
285 } else {
286 debug!("Feedback for some other {goal_id:?}.");
287 None
288 }
289 }
290 }
291 })
292 }
293
294 pub fn receive_status(&self) -> ReadResult<Option<action_msgs::GoalStatusArray>> {
297 self
298 .my_status_subscription
299 .take()
300 .map(|r| r.map(|(gsa, _msg_info)| gsa))
301 }
302
303 pub async fn async_receive_status(&self) -> ReadResult<action_msgs::GoalStatusArray> {
304 let (m, _msg_info) = self.my_status_subscription.async_take().await?;
305 Ok(m)
306 }
307
308 pub fn all_statuses_stream(
311 &self,
312 ) -> impl FusedStream<Item = ReadResult<action_msgs::GoalStatusArray>> + '_ {
313 self
314 .my_status_subscription
315 .async_stream()
316 .map(|result| result.map(|(gsa, _mi)| gsa))
317 }
318
319 pub fn status_stream(
320 &self,
321 goal_id: GoalId,
322 ) -> impl FusedStream<Item = ReadResult<action_msgs::GoalStatus>> + '_ {
323 self
324 .all_statuses_stream()
325 .filter_map(move |result| async move {
326 match result {
327 Err(e) => Some(Err(e)),
328 Ok(gsa) => gsa
329 .status_list
330 .into_iter()
331 .find(|gs| gs.goal_info.goal_id == goal_id)
332 .map(Ok),
333 }
334 })
335 }
336}