arci_ros/
ros_nav_client.rs1use std::{sync::Arc, time};
2
3use arci::*;
4use nalgebra as na;
5use schemars::JsonSchema;
6use serde::{Deserialize, Serialize};
7
8use crate::{define_action_client, msg, ActionResultWait};
9define_action_client!(MoveBaseActionClient, msg::move_base_msgs, MoveBase);
10
11impl From<na::Isometry2<f64>> for msg::geometry_msgs::Pose {
12 fn from(goal: na::Isometry2<f64>) -> Self {
13 let iso_pose = na::Isometry3::from_parts(
14 na::Translation3::new(goal.translation.x, goal.translation.y, 0.0),
15 na::UnitQuaternion::from_euler_angles(0.0, 0.0, goal.rotation.angle()),
16 );
17 iso_pose.into()
18 }
19}
20
21const NO_MOTION_UPDATE_SERVICE: &str = "request_nomotion_update";
22const MOVE_BASE_ACTION: &str = "/move_base";
23const CLEAR_COSTMAP_SERVICE: &str = "/move_base/clear_costmaps";
24
25#[derive(Clone, Debug)]
33pub struct RosNavClientBuilder {
34 move_base_action_base_name: String,
35 clear_costmap_service_name: String,
36 nomotion_update_service_name: String,
37 request_final_nomotion_update_hack: bool,
38 clear_costmap_before_start: bool,
39}
40
41impl RosNavClientBuilder {
42 pub fn new() -> Self {
50 Self {
51 move_base_action_base_name: MOVE_BASE_ACTION.to_string(),
52 clear_costmap_service_name: CLEAR_COSTMAP_SERVICE.to_string(),
53 nomotion_update_service_name: NO_MOTION_UPDATE_SERVICE.to_string(),
54 request_final_nomotion_update_hack: false,
55 clear_costmap_before_start: false,
56 }
57 }
58
59 pub fn request_final_nomotion_update_hack(mut self, val: bool) -> Self {
68 self.request_final_nomotion_update_hack = val;
69 self
70 }
71
72 pub fn clear_costmap_before_start(mut self, val: bool) -> Self {
81 self.clear_costmap_before_start = val;
82 self
83 }
84
85 pub fn finalize(self) -> RosNavClient {
93 let mut c = RosNavClient::new(
94 self.request_final_nomotion_update_hack,
95 self.move_base_action_base_name,
96 self.nomotion_update_service_name,
97 self.clear_costmap_service_name,
98 );
99 c.clear_costmap_before_start = self.clear_costmap_before_start;
100 c
101 }
102}
103
104impl Default for RosNavClientBuilder {
105 fn default() -> Self {
106 Self::new()
107 }
108}
109
110#[derive(Clone)]
111pub struct RosNavClient {
112 pub clear_costmap_before_start: bool,
113 action_client: Arc<MoveBaseActionClient>,
114 nomotion_update_client: Option<rosrust::Client<msg::std_srvs::Empty>>,
115 nomotion_update_service_name: String,
116 clear_costmap_service_name: String,
117}
118
119impl RosNavClient {
120 pub fn new(
121 request_final_nomotion_update_hack: bool,
122 move_base_action_base_name: String,
123 nomotion_update_service_name: String,
124 clear_costmap_service_name: String,
125 ) -> Self {
126 let action_client = MoveBaseActionClient::new(&move_base_action_base_name, 1);
127
128 let nomotion_update_client = if request_final_nomotion_update_hack {
129 rosrust::wait_for_service(
130 &nomotion_update_service_name,
131 Some(std::time::Duration::from_secs(10)),
132 )
133 .unwrap();
134 Some(rosrust::client::<msg::std_srvs::Empty>(&nomotion_update_service_name).unwrap())
135 } else {
136 None
137 };
138 Self {
139 clear_costmap_before_start: false,
140 action_client: Arc::new(action_client),
141 nomotion_update_client,
142 nomotion_update_service_name,
143 clear_costmap_service_name,
144 }
145 }
146
147 pub fn new_from_config(config: RosNavClientConfig) -> Self {
148 let mut s = Self::new(
149 config.request_final_nomotion_update_hack,
150 config.move_base_action_base_name,
151 config.nomotion_update_service_name,
152 config.clear_costmap_service_name,
153 );
154 s.clear_costmap_before_start = config.clear_costmap_before_start;
155 s
156 }
157
158 pub fn clear_costmap(&self) -> Result<(), Error> {
159 rosrust::wait_for_service(
161 &self.clear_costmap_service_name,
162 Some(time::Duration::from_secs(10)),
163 )
164 .unwrap();
165 let client =
167 rosrust::client::<msg::std_srvs::Empty>(&self.clear_costmap_service_name).unwrap();
168 client.req(&msg::std_srvs::EmptyReq {}).unwrap().unwrap();
169 rosrust::ros_info!("requested to clear costmaps");
170 Ok(())
171 }
172
173 fn wait_until_reach(
174 &self,
175 mut action_result_wait: ActionResultWait,
176 timeout: std::time::Duration,
177 ) -> Result<(), crate::Error> {
178 match action_result_wait.wait(timeout) {
179 Ok(_) => {
180 rosrust::ros_info!("Action succeeds");
181 if let Some(client) = self.nomotion_update_client.as_ref() {
182 client.req(&msg::std_srvs::EmptyReq {}).unwrap().unwrap();
183 rosrust::ros_info!("Called {}", self.nomotion_update_service_name);
184 }
185 Ok(())
186 }
187 Err(e) => match e {
188 crate::Error::ActionResultPreempted(_) => {
189 rosrust::ros_warn!("Action is cancelled");
190 Err(e)
191 }
192 _ => {
193 rosrust::ros_err!("Action does not succeed {e:?}");
194 Err(e)
195 }
196 },
197 }
198 }
199}
200
201impl Navigation for RosNavClient {
202 fn send_goal_pose(
203 &self,
204 goal: na::Isometry2<f64>,
205 frame_id: &str,
206 timeout: std::time::Duration,
207 ) -> Result<WaitFuture, Error> {
208 if self.clear_costmap_before_start {
209 self.clear_costmap()?;
210 }
211 let mut target_pose = msg::geometry_msgs::PoseStamped {
212 pose: goal.into(),
213 ..Default::default()
214 };
215 frame_id.clone_into(&mut target_pose.header.frame_id);
216 target_pose.header.stamp = rosrust::now();
217 let action_result_wait = self
218 .action_client
219 .send_goal(msg::move_base_msgs::MoveBaseGoal { target_pose })
220 .map_err(|e| anyhow::anyhow!("Failed to send_goal_and_wait : {e}"))?;
221
222 let self_clone = self.clone();
223 let wait = WaitFuture::new(async move {
228 tokio::task::spawn_blocking(move || {
229 self_clone.wait_until_reach(action_result_wait, timeout)
230 })
231 .await
232 .map_err(|e| arci::Error::Other(e.into()))??;
233 Ok(())
234 });
235
236 Ok(wait)
237 }
238
239 fn cancel(&self) -> Result<(), Error> {
240 self.action_client
241 .cancel_all_goal()
242 .map_err(|e| anyhow::anyhow!("Failed to cancel_all_goal : {e}"))?;
243 Ok(())
244 }
245}
246
247#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema)]
248#[serde(deny_unknown_fields)]
249pub struct RosNavClientConfig {
250 pub request_final_nomotion_update_hack: bool,
251 pub clear_costmap_before_start: bool,
252 #[serde(default = "default_move_base_action_base_name")]
253 pub move_base_action_base_name: String,
254 #[serde(default = "default_nomotion_update_service_name")]
255 pub nomotion_update_service_name: String,
256 #[serde(default = "default_clear_costmap_service_name")]
257 pub clear_costmap_service_name: String,
258}
259
260fn default_move_base_action_base_name() -> String {
261 MOVE_BASE_ACTION.to_string()
262}
263
264fn default_nomotion_update_service_name() -> String {
265 NO_MOTION_UPDATE_SERVICE.to_string()
266}
267
268fn default_clear_costmap_service_name() -> String {
269 CLEAR_COSTMAP_SERVICE.to_string()
270}