1use std::collections::HashMap;
2
3use crate::types::{BatteryInfo, CommandResult, CommandState, KachakaError, Pose};
4use crate::KachakaApiError;
5use crate::{kachaka_api, StartCommandOptions};
6
7use futures::stream::Stream;
8use image::DynamicImage;
9use kachaka_api::kachaka_api_client::KachakaApiClient as TonicKachakaApiClient;
10use tokio::sync::mpsc;
11use tokio_stream::wrappers::UnboundedReceiverStream;
12use tonic::transport::Channel;
13
14fn parse_rpc_response_with_result<T>(
15 response_result: std::result::Result<tonic::Response<T>, tonic::Status>,
16 get_result: impl Fn(&T) -> Option<kachaka_api::Result>,
17) -> Result<T, KachakaApiError> {
18 match response_result {
19 Ok(response) => {
20 if let Some(result) = get_result(response.get_ref()) {
21 if result.success {
22 Ok(response.into_inner())
23 } else {
24 Err(KachakaApiError::ApiError(KachakaError {
25 error_code: result.error_code,
26 }))
27 }
28 } else {
29 Err(KachakaApiError::NullResult)
30 }
31 }
32 Err(e) => Err(KachakaApiError::CommunicationError(e)),
33 }
34}
35
36fn parse_getter_response<T>(
37 maybe_response: std::result::Result<tonic::Response<T>, tonic::Status>,
38) -> Result<T, KachakaApiError> {
39 match maybe_response {
40 Ok(response) => Ok(response.into_inner()),
41 Err(e) => Err(KachakaApiError::CommunicationError(e)),
42 }
43}
44
45async fn get_robot_serial_number_with_cursor(
49 client: &mut TonicKachakaApiClient<Channel>,
50 cursor: i64,
51) -> Result<(i64, String), KachakaApiError> {
52 let request = tonic::Request::new(kachaka_api::GetRequest {
53 metadata: Some(kachaka_api::Metadata { cursor }),
54 });
55 let response = client.get_robot_serial_number(request).await;
56 parse_getter_response(response)
57 .map(|response| (response.metadata.unwrap().cursor, response.serial_number))
58}
59
60pub async fn get_robot_serial_number(
61 client: &mut TonicKachakaApiClient<Channel>,
62 cursor: i64,
63) -> Result<String, KachakaApiError> {
64 get_robot_serial_number_with_cursor(client, cursor)
65 .await
66 .map(|(_, serial_number)| serial_number)
67}
68
69pub async fn get_latest_robot_serial_number(
70 client: &mut TonicKachakaApiClient<Channel>,
71) -> Result<String, KachakaApiError> {
72 get_robot_serial_number(client, 0).await
73}
74
75pub async fn watch_robot_serial_number(
76 client: &mut TonicKachakaApiClient<Channel>,
77) -> impl Stream<Item = Result<String, KachakaApiError>> {
78 let (tx, rx) = mpsc::unbounded_channel::<Result<String, KachakaApiError>>();
79 let mut cursor = 0;
80 let mut client_clone = client.clone();
81 tokio::spawn(async move {
82 loop {
83 match get_robot_serial_number_with_cursor(&mut client_clone, cursor).await {
84 Ok((new_cursor, serial_number)) => {
85 cursor = new_cursor;
86 tx.send(Ok(serial_number)).unwrap();
87 }
88 Err(e) => {
89 tx.send(Err(e)).unwrap();
90 }
91 }
92 }
93 });
94 UnboundedReceiverStream::new(rx)
95}
96
97async fn get_robot_version_with_cursor(
99 client: &mut TonicKachakaApiClient<Channel>,
100 cursor: i64,
101) -> Result<(i64, String), KachakaApiError> {
102 let request = tonic::Request::new(kachaka_api::GetRequest {
103 metadata: Some(kachaka_api::Metadata { cursor }),
104 });
105 let response = client.get_robot_version(request).await;
106 parse_getter_response(response)
107 .map(|response| (response.metadata.unwrap().cursor, response.version))
108}
109
110pub async fn get_robot_version(
111 client: &mut TonicKachakaApiClient<Channel>,
112 cursor: i64,
113) -> Result<String, KachakaApiError> {
114 get_robot_version_with_cursor(client, cursor)
115 .await
116 .map(|(_, version)| version)
117}
118
119pub async fn get_latest_robot_version(
120 client: &mut TonicKachakaApiClient<Channel>,
121) -> Result<String, KachakaApiError> {
122 get_robot_version(client, 0).await
123}
124
125pub async fn watch_robot_version(
126 client: &mut TonicKachakaApiClient<Channel>,
127) -> impl Stream<Item = Result<String, KachakaApiError>> {
128 let (tx, rx) = mpsc::unbounded_channel::<Result<String, KachakaApiError>>();
129 let mut cursor = 0;
130 let mut client_clone = client.clone();
131 tokio::spawn(async move {
132 loop {
133 match get_robot_version_with_cursor(&mut client_clone, cursor).await {
134 Ok((new_cursor, version)) => {
135 cursor = new_cursor;
136 tx.send(Ok(version)).unwrap();
137 }
138 Err(e) => {
139 tx.send(Err(e)).unwrap();
140 }
141 }
142 }
143 });
144 UnboundedReceiverStream::new(rx)
145}
146
147async fn get_robot_pose_with_cursor(
149 client: &mut TonicKachakaApiClient<Channel>,
150 cursor: i64,
151) -> Result<(i64, Pose), KachakaApiError> {
152 let request = tonic::Request::new(kachaka_api::GetRequest {
153 metadata: Some(kachaka_api::Metadata { cursor }),
154 });
155 let response = client.get_robot_pose(request).await;
156 let pose_result = parse_getter_response(response)?;
157 if let Some(pose) = pose_result.pose {
158 Ok((pose_result.metadata.unwrap().cursor, pose.into()))
159 } else {
160 Err(KachakaApiError::NullResult)
161 }
162}
163
164pub async fn get_robot_pose(
165 client: &mut TonicKachakaApiClient<Channel>,
166 cursor: i64,
167) -> Result<Pose, KachakaApiError> {
168 get_robot_pose_with_cursor(client, cursor)
169 .await
170 .map(|(_, pose)| pose)
171}
172
173pub async fn get_latest_robot_pose(
174 client: &mut TonicKachakaApiClient<Channel>,
175) -> Result<Pose, KachakaApiError> {
176 get_robot_pose(client, 0).await
177}
178
179pub async fn watch_robot_pose(
180 client: &mut TonicKachakaApiClient<Channel>,
181) -> impl Stream<Item = Result<Pose, KachakaApiError>> {
182 let (tx, rx) = mpsc::unbounded_channel::<Result<Pose, KachakaApiError>>();
183 let mut cursor = 0;
184 let mut client_clone = client.clone();
185 tokio::spawn(async move {
186 loop {
187 match get_robot_pose_with_cursor(&mut client_clone, cursor).await {
188 Ok((new_cursor, pose)) => {
189 cursor = new_cursor;
190 tx.send(Ok(pose)).unwrap();
191 }
192 Err(e) => {
193 tx.send(Err(e)).unwrap();
194 }
195 }
196 }
197 });
198 UnboundedReceiverStream::new(rx)
199}
200
201async fn get_battery_info_with_cursor(
203 client: &mut TonicKachakaApiClient<Channel>,
204 cursor: i64,
205) -> Result<(i64, BatteryInfo), KachakaApiError> {
206 let request = tonic::Request::new(kachaka_api::GetRequest {
207 metadata: Some(kachaka_api::Metadata { cursor }),
208 });
209 let response = client.get_battery_info(request).await;
210 parse_getter_response(response).map(|response| {
211 (
212 response.metadata.unwrap().cursor,
213 BatteryInfo {
214 power_supply_status: response.power_supply_status.into(),
215 remaining_percentage: response.remaining_percentage,
216 },
217 )
218 })
219}
220
221pub async fn get_battery_info(
222 client: &mut TonicKachakaApiClient<Channel>,
223 cursor: i64,
224) -> Result<BatteryInfo, KachakaApiError> {
225 get_battery_info_with_cursor(client, cursor)
226 .await
227 .map(|(_, battery_info)| battery_info)
228}
229
230pub async fn get_latest_battery_info(
231 client: &mut TonicKachakaApiClient<Channel>,
232) -> Result<BatteryInfo, KachakaApiError> {
233 get_battery_info(client, 0).await
234}
235
236pub async fn watch_battery_info(
237 client: &mut TonicKachakaApiClient<Channel>,
238) -> impl Stream<Item = Result<BatteryInfo, KachakaApiError>> {
239 let (tx, rx) = mpsc::unbounded_channel::<Result<BatteryInfo, KachakaApiError>>();
240 let mut cursor = 0;
241 let mut client_clone = client.clone();
242 tokio::spawn(async move {
243 loop {
244 match get_battery_info_with_cursor(&mut client_clone, cursor).await {
245 Ok((new_cursor, battery_info)) => {
246 cursor = new_cursor;
247 tx.send(Ok(battery_info)).unwrap();
248 }
249 Err(e) => {
250 tx.send(Err(e)).unwrap();
251 }
252 }
253 }
254 });
255 UnboundedReceiverStream::new(rx)
256}
257
258async fn get_front_camera_ros_image_with_cursor(
260 client: &mut TonicKachakaApiClient<Channel>,
261 cursor: i64,
262) -> Result<(i64, DynamicImage), KachakaApiError> {
263 let request = tonic::Request::new(kachaka_api::GetRequest {
264 metadata: Some(kachaka_api::Metadata { cursor }),
265 });
266 let response = client.get_front_camera_ros_image(request).await;
267 parse_getter_response(response).map(|response| {
268 (
269 response.metadata.unwrap().cursor,
270 DynamicImage::from(response.image.unwrap()),
271 )
272 })
273}
274
275pub async fn get_front_camera_ros_image(
276 client: &mut TonicKachakaApiClient<Channel>,
277 cursor: i64,
278) -> Result<DynamicImage, KachakaApiError> {
279 get_front_camera_ros_image_with_cursor(client, cursor)
280 .await
281 .map(|(_, image)| image)
282}
283
284pub async fn get_latest_front_camera_ros_image(
285 client: &mut TonicKachakaApiClient<Channel>,
286) -> Result<DynamicImage, KachakaApiError> {
287 get_front_camera_ros_image(client, 0).await
288}
289
290pub async fn watch_front_camera_ros_image(
291 client: &mut TonicKachakaApiClient<Channel>,
292) -> impl Stream<Item = Result<DynamicImage, KachakaApiError>> {
293 let (tx, rx) = mpsc::unbounded_channel::<Result<DynamicImage, KachakaApiError>>();
294 let mut cursor = 0;
295 let mut client_clone = client.clone();
296 tokio::spawn(async move {
297 loop {
298 match get_front_camera_ros_image_with_cursor(&mut client_clone, cursor).await {
299 Ok((new_cursor, image)) => {
300 cursor = new_cursor;
301 tx.send(Ok(image)).unwrap();
302 }
303 Err(e) => {
304 tx.send(Err(e)).unwrap();
305 }
306 }
307 }
308 });
309 UnboundedReceiverStream::new(rx)
310}
311
312async fn get_front_camera_ros_compressed_image_with_cursor(
314 client: &mut TonicKachakaApiClient<Channel>,
315 cursor: i64,
316) -> Result<(i64, DynamicImage), KachakaApiError> {
317 let request = tonic::Request::new(kachaka_api::GetRequest {
318 metadata: Some(kachaka_api::Metadata { cursor }),
319 });
320 let response = client.get_front_camera_ros_compressed_image(request).await;
321 parse_getter_response(response).map(|response| {
322 (
323 response.metadata.unwrap().cursor,
324 DynamicImage::from(response.image.unwrap()),
325 )
326 })
327}
328
329pub async fn get_front_camera_ros_compressed_image(
330 client: &mut TonicKachakaApiClient<Channel>,
331 cursor: i64,
332) -> Result<DynamicImage, KachakaApiError> {
333 get_front_camera_ros_compressed_image_with_cursor(client, cursor)
334 .await
335 .map(|(_, image)| image)
336}
337
338pub async fn get_latest_front_camera_ros_compressed_image(
339 client: &mut TonicKachakaApiClient<Channel>,
340) -> Result<DynamicImage, KachakaApiError> {
341 get_front_camera_ros_compressed_image(client, 0).await
342}
343
344pub async fn watch_front_camera_ros_compressed_image(
345 client: &mut TonicKachakaApiClient<Channel>,
346) -> impl Stream<Item = Result<DynamicImage, KachakaApiError>> {
347 let (tx, rx) = mpsc::unbounded_channel::<Result<DynamicImage, KachakaApiError>>();
348 let mut cursor = 0;
349 let mut client_clone = client.clone();
350 tokio::spawn(async move {
351 loop {
352 match get_front_camera_ros_compressed_image_with_cursor(&mut client_clone, cursor).await
353 {
354 Ok((new_cursor, image)) => {
355 cursor = new_cursor;
356 tx.send(Ok(image)).unwrap();
357 }
358 Err(e) => {
359 tx.send(Err(e)).unwrap();
360 }
361 }
362 }
363 });
364 UnboundedReceiverStream::new(rx)
365}
366
367async fn get_back_camera_ros_image_with_cursor(
369 client: &mut TonicKachakaApiClient<Channel>,
370 cursor: i64,
371) -> Result<(i64, DynamicImage), KachakaApiError> {
372 let request = tonic::Request::new(kachaka_api::GetRequest {
373 metadata: Some(kachaka_api::Metadata { cursor }),
374 });
375 let response = client.get_back_camera_ros_image(request).await;
376 parse_getter_response(response).map(|response| {
377 (
378 response.metadata.unwrap().cursor,
379 DynamicImage::from(response.image.unwrap()),
380 )
381 })
382}
383
384pub async fn get_back_camera_ros_image(
385 client: &mut TonicKachakaApiClient<Channel>,
386 cursor: i64,
387) -> Result<DynamicImage, KachakaApiError> {
388 get_back_camera_ros_image_with_cursor(client, cursor)
389 .await
390 .map(|(_, image)| image)
391}
392
393pub async fn get_latest_back_camera_ros_image(
394 client: &mut TonicKachakaApiClient<Channel>,
395) -> Result<DynamicImage, KachakaApiError> {
396 get_back_camera_ros_image(client, 0).await
397}
398
399pub async fn watch_back_camera_ros_image(
400 client: &mut TonicKachakaApiClient<Channel>,
401) -> impl Stream<Item = Result<DynamicImage, KachakaApiError>> {
402 let (tx, rx) = mpsc::unbounded_channel::<Result<DynamicImage, KachakaApiError>>();
403 let mut cursor = 0;
404 let mut client_clone = client.clone();
405 tokio::spawn(async move {
406 loop {
407 match get_back_camera_ros_image_with_cursor(&mut client_clone, cursor).await {
408 Ok((new_cursor, image)) => {
409 cursor = new_cursor;
410 tx.send(Ok(image)).unwrap();
411 }
412 Err(e) => {
413 tx.send(Err(e)).unwrap();
414 }
415 }
416 }
417 });
418 UnboundedReceiverStream::new(rx)
419}
420
421async fn get_back_camera_ros_compressed_image_with_cursor(
423 client: &mut TonicKachakaApiClient<Channel>,
424 cursor: i64,
425) -> Result<(i64, DynamicImage), KachakaApiError> {
426 let request = tonic::Request::new(kachaka_api::GetRequest {
427 metadata: Some(kachaka_api::Metadata { cursor }),
428 });
429 let response = client.get_back_camera_ros_compressed_image(request).await;
430 parse_getter_response(response).map(|response| {
431 (
432 response.metadata.unwrap().cursor,
433 DynamicImage::from(response.image.unwrap()),
434 )
435 })
436}
437
438pub async fn get_back_camera_ros_compressed_image(
439 client: &mut TonicKachakaApiClient<Channel>,
440 cursor: i64,
441) -> Result<DynamicImage, KachakaApiError> {
442 get_back_camera_ros_compressed_image_with_cursor(client, cursor)
443 .await
444 .map(|(_, image)| image)
445}
446
447pub async fn get_latest_back_camera_ros_compressed_image(
448 client: &mut TonicKachakaApiClient<Channel>,
449) -> Result<DynamicImage, KachakaApiError> {
450 get_back_camera_ros_compressed_image(client, 0).await
451}
452
453pub async fn watch_back_camera_ros_compressed_image(
454 client: &mut TonicKachakaApiClient<Channel>,
455) -> impl Stream<Item = Result<DynamicImage, KachakaApiError>> {
456 let (tx, rx) = mpsc::unbounded_channel::<Result<DynamicImage, KachakaApiError>>();
457 let mut cursor = 0;
458 let mut client_clone = client.clone();
459 tokio::spawn(async move {
460 loop {
461 match get_back_camera_ros_compressed_image_with_cursor(&mut client_clone, cursor).await
462 {
463 Ok((new_cursor, image)) => {
464 cursor = new_cursor;
465 tx.send(Ok(image)).unwrap();
466 }
467 Err(e) => {
468 tx.send(Err(e)).unwrap();
469 }
470 }
471 }
472 });
473 UnboundedReceiverStream::new(rx)
474}
475
476fn parse_robot_error_code_json(
478 response: kachaka_api::GetRobotErrorCodeJsonResponse,
479) -> Result<HashMap<i32, HashMap<String, String>>, KachakaApiError> {
480 let items: Vec<HashMap<String, serde_json::Value>> =
481 serde_json::from_str(&response.json).map_err(KachakaApiError::JsonParseError)?;
482 let mut result = HashMap::new();
483 for item in items {
484 let key = item.get("code").unwrap().as_i64().unwrap() as i32;
485 let mut map = HashMap::new();
486 for (k, v) in item {
487 if k == "code" {
488 continue;
489 }
490 map.insert(k, v.as_str().unwrap().to_string());
491 }
492 result.insert(key, map);
493 }
494 Ok(result)
495}
496
497pub async fn get_robot_error_code_json(
498 client: &mut TonicKachakaApiClient<Channel>,
499) -> Result<HashMap<i32, HashMap<String, String>>, KachakaApiError> {
500 let request = tonic::Request::new(kachaka_api::EmptyRequest {});
501 let response = client.get_robot_error_code_json(request).await;
502 match response {
503 Ok(response) => {
504 if let Some(result) = response.get_ref().result {
505 if result.success {
506 Ok(parse_robot_error_code_json(response.into_inner())?)
507 } else {
508 Err(KachakaApiError::ApiError(KachakaError {
509 error_code: result.error_code,
510 }))
511 }
512 } else {
513 Err(KachakaApiError::NullResult)
514 }
515 }
516 Err(e) => Err(KachakaApiError::CommunicationError(e)),
517 }
518}
519
520async fn get_error_with_cursor(
522 client: &mut TonicKachakaApiClient<Channel>,
523 cursor: i64,
524) -> Result<(i64, Vec<KachakaError>), KachakaApiError> {
525 let request = tonic::Request::new(kachaka_api::GetRequest {
526 metadata: Some(kachaka_api::Metadata { cursor }),
527 });
528 let response = client.get_error(request).await;
529 parse_getter_response(response).map(|response| {
530 (
531 response.metadata.unwrap().cursor,
532 response
533 .error_codes
534 .into_iter()
535 .map(|e| KachakaError { error_code: e })
536 .collect(),
537 )
538 })
539}
540
541pub async fn get_error(
542 client: &mut TonicKachakaApiClient<Channel>,
543 cursor: i64,
544) -> Result<Vec<KachakaError>, KachakaApiError> {
545 get_error_with_cursor(client, cursor)
546 .await
547 .map(|(_, errors)| errors)
548}
549
550pub async fn get_latest_error(
551 client: &mut TonicKachakaApiClient<Channel>,
552) -> Result<Vec<KachakaError>, KachakaApiError> {
553 get_error_with_cursor(client, 0)
554 .await
555 .map(|(_, errors)| errors)
556}
557
558pub async fn watch_error(
559 client: &mut TonicKachakaApiClient<Channel>,
560) -> impl Stream<Item = Result<Vec<KachakaError>, KachakaApiError>> {
561 let (tx, rx) = mpsc::unbounded_channel::<Result<Vec<KachakaError>, KachakaApiError>>();
562 let mut cursor = 0;
563 let mut client_clone = client.clone();
564 tokio::spawn(async move {
565 loop {
566 match get_error_with_cursor(&mut client_clone, cursor).await {
567 Ok((new_cursor, errors)) => {
568 cursor = new_cursor;
569 tx.send(Ok(errors)).unwrap();
570 }
571 Err(e) => {
572 tx.send(Err(e)).unwrap();
573 }
574 }
575 }
576 });
577 UnboundedReceiverStream::new(rx)
578}
579
580async fn get_command_state_with_cursor(
582 client: &mut TonicKachakaApiClient<Channel>,
583 cursor: i64,
584) -> Result<(i64, CommandState), KachakaApiError> {
585 let request = tonic::Request::new(kachaka_api::GetRequest {
586 metadata: Some(kachaka_api::Metadata { cursor }),
587 });
588 let response = client.get_command_state(request).await;
589 parse_getter_response(response)
590 .map(|response| (response.metadata.unwrap().cursor, response.into()))
591}
592
593pub async fn get_command_state(
594 client: &mut TonicKachakaApiClient<Channel>,
595 cursor: i64,
596) -> Result<CommandState, KachakaApiError> {
597 get_command_state_with_cursor(client, cursor)
598 .await
599 .map(|(_, command_state)| command_state)
600}
601
602pub async fn get_latest_command_state(
603 client: &mut TonicKachakaApiClient<Channel>,
604) -> Result<CommandState, KachakaApiError> {
605 get_command_state(client, 0).await
606}
607
608pub async fn watch_command_state(
609 client: &mut TonicKachakaApiClient<Channel>,
610) -> impl Stream<Item = Result<CommandState, KachakaApiError>> {
611 let (tx, rx) = mpsc::unbounded_channel::<Result<CommandState, KachakaApiError>>();
612 let mut cursor = 0;
613 let mut client_clone = client.clone();
614 tokio::spawn(async move {
615 loop {
616 match get_command_state_with_cursor(&mut client_clone, cursor).await {
617 Ok((new_cursor, command_state)) => {
618 cursor = new_cursor;
619 tx.send(Ok(command_state)).unwrap();
620 }
621 Err(e) => {
622 tx.send(Err(e)).unwrap();
623 }
624 }
625 }
626 });
627 UnboundedReceiverStream::new(rx)
628}
629
630async fn get_last_command_result_with_cursor(
632 client: &mut TonicKachakaApiClient<Channel>,
633 cursor: i64,
634) -> Result<(i64, Option<CommandResult>), KachakaApiError> {
635 let request = tonic::Request::new(kachaka_api::GetRequest {
636 metadata: Some(kachaka_api::Metadata { cursor }),
637 });
638 let response = client.get_last_command_result(request).await;
639 parse_getter_response(response)
640 .map(|response| (response.metadata.unwrap().cursor, response.into()))
641}
642
643pub async fn get_last_command_result(
644 client: &mut TonicKachakaApiClient<Channel>,
645 cursor: i64,
646) -> Result<Option<CommandResult>, KachakaApiError> {
647 get_last_command_result_with_cursor(client, cursor)
648 .await
649 .map(|(_, result)| result)
650}
651
652pub async fn get_latest_last_command_result(
653 client: &mut TonicKachakaApiClient<Channel>,
654) -> Result<Option<CommandResult>, KachakaApiError> {
655 get_last_command_result(client, 0).await
656}
657
658pub async fn watch_last_command_result(
659 client: &mut TonicKachakaApiClient<Channel>,
660) -> impl Stream<Item = Result<Option<CommandResult>, KachakaApiError>> {
661 let (tx, rx) = mpsc::unbounded_channel::<Result<Option<CommandResult>, KachakaApiError>>();
662
663 let mut cursor = 0;
664 let mut client_clone = client.clone();
665 tokio::spawn(async move {
666 loop {
667 match get_last_command_result_with_cursor(&mut client_clone, cursor).await {
668 Ok((new_cursor, result)) => {
669 cursor = new_cursor;
670 tx.send(Ok(result)).unwrap();
671 }
672 Err(e) => {
673 tx.send(Err(e)).unwrap();
674 }
675 }
676 }
677 });
678
679 UnboundedReceiverStream::new(rx)
680}
681
682async fn start_command(
685 client: &mut TonicKachakaApiClient<Channel>,
686 command: kachaka_api::command::Command,
687 options: StartCommandOptions,
688) -> Result<String, KachakaApiError> {
689 let request = tonic::Request::new(kachaka_api::StartCommandRequest {
690 command: Some(kachaka_api::Command {
691 command: Some(command),
692 }),
693 cancel_all: options.cancel_all,
694 deferrable: options.deferrable,
695 lock_on_end: options.lock_on_end,
696 title: options.title,
697 tts_on_success: options.tts_on_success,
698 });
699 let response = client.start_command(request).await;
700 parse_rpc_response_with_result(
701 response,
702 |rpc_response: &kachaka_api::StartCommandResponse| rpc_response.result,
703 )
704 .map(|response| response.command_id)
705}
706
707pub async fn move_shelf(
708 client: &mut TonicKachakaApiClient<Channel>,
709 shelf_id: &str,
710 location_id: &str,
711 options: StartCommandOptions,
712) -> Result<String, KachakaApiError> {
713 start_command(
714 client,
715 kachaka_api::command::Command::MoveShelfCommand(kachaka_api::MoveShelfCommand {
716 target_shelf_id: shelf_id.to_string(),
717 destination_location_id: location_id.to_string(),
718 }),
719 options,
720 )
721 .await
722}
723
724pub async fn return_shelf(
725 client: &mut TonicKachakaApiClient<Channel>,
726 shelf_id: &str,
727 options: StartCommandOptions,
728) -> Result<String, KachakaApiError> {
729 start_command(
730 client,
731 kachaka_api::command::Command::ReturnShelfCommand(kachaka_api::ReturnShelfCommand {
732 target_shelf_id: shelf_id.to_string(),
733 }),
734 options,
735 )
736 .await
737}
738
739pub async fn undock_shelf(
740 client: &mut TonicKachakaApiClient<Channel>,
741 options: StartCommandOptions,
742) -> Result<String, KachakaApiError> {
743 start_command(
744 client,
745 kachaka_api::command::Command::UndockShelfCommand(kachaka_api::UndockShelfCommand {
746 target_shelf_id: "".to_string(),
747 }),
748 options,
749 )
750 .await
751}
752
753pub async fn move_to_location(
754 client: &mut TonicKachakaApiClient<Channel>,
755 location_id: &str,
756 options: StartCommandOptions,
757) -> Result<String, KachakaApiError> {
758 start_command(
759 client,
760 kachaka_api::command::Command::MoveToLocationCommand(kachaka_api::MoveToLocationCommand {
761 target_location_id: location_id.to_string(),
762 }),
763 options,
764 )
765 .await
766}
767
768pub async fn return_home(
769 client: &mut TonicKachakaApiClient<Channel>,
770 options: StartCommandOptions,
771) -> Result<String, KachakaApiError> {
772 start_command(
773 client,
774 kachaka_api::command::Command::ReturnHomeCommand(kachaka_api::ReturnHomeCommand {}),
775 options,
776 )
777 .await
778}
779
780pub async fn dock_shelf(
781 client: &mut TonicKachakaApiClient<Channel>,
782 options: StartCommandOptions,
783) -> Result<String, KachakaApiError> {
784 start_command(
785 client,
786 kachaka_api::command::Command::DockShelfCommand(kachaka_api::DockShelfCommand {}),
787 options,
788 )
789 .await
790}
791
792pub async fn speak(
793 client: &mut TonicKachakaApiClient<Channel>,
794 text: &str,
795 options: StartCommandOptions,
796) -> Result<String, KachakaApiError> {
797 start_command(
798 client,
799 kachaka_api::command::Command::SpeakCommand(kachaka_api::SpeakCommand {
800 text: text.to_string(),
801 }),
802 options,
803 )
804 .await
805}
806
807pub async fn move_to_pose(
808 client: &mut TonicKachakaApiClient<Channel>,
809 x: f64,
810 y: f64,
811 yaw: f64,
812 options: StartCommandOptions,
813) -> Result<String, KachakaApiError> {
814 start_command(
815 client,
816 kachaka_api::command::Command::MoveToPoseCommand(kachaka_api::MoveToPoseCommand {
817 x,
818 y,
819 yaw,
820 }),
821 options,
822 )
823 .await
824}
825
826pub async fn lock(
827 client: &mut TonicKachakaApiClient<Channel>,
828 duration_sec: f64,
829 options: StartCommandOptions,
830) -> Result<String, KachakaApiError> {
831 start_command(
832 client,
833 kachaka_api::command::Command::LockCommand(kachaka_api::LockCommand { duration_sec }),
834 options,
835 )
836 .await
837}
838
839pub async fn move_forward(
840 client: &mut TonicKachakaApiClient<Channel>,
841 distance_meter: f64,
842 speed: f64,
843 options: StartCommandOptions,
844) -> Result<String, KachakaApiError> {
845 start_command(
846 client,
847 kachaka_api::command::Command::MoveForwardCommand(kachaka_api::MoveForwardCommand {
848 distance_meter,
849 speed,
850 }),
851 options,
852 )
853 .await
854}
855
856pub async fn rotate_in_place(
857 client: &mut TonicKachakaApiClient<Channel>,
858 angle_radian: f64,
859 options: StartCommandOptions,
860) -> Result<String, KachakaApiError> {
861 start_command(
862 client,
863 kachaka_api::command::Command::RotateInPlaceCommand(kachaka_api::RotateInPlaceCommand {
864 angle_radian,
865 }),
866 options,
867 )
868 .await
869}
870
871pub async fn dock_any_shelf_with_registration(
872 client: &mut TonicKachakaApiClient<Channel>,
873 location_id: &str,
874 options: StartCommandOptions,
875) -> Result<String, KachakaApiError> {
876 start_command(
877 client,
878 kachaka_api::command::Command::DockAnyShelfWithRegistrationCommand(
879 kachaka_api::DockAnyShelfWithRegistrationCommand {
880 dock_forward: true,
881 target_location_id: location_id.to_string(),
882 },
883 ),
884 options,
885 )
886 .await
887}
888
889pub async fn cancel_command(
891 client: &mut TonicKachakaApiClient<Channel>,
892) -> Result<(), KachakaApiError> {
893 let request = tonic::Request::new(kachaka_api::EmptyRequest {});
894 let response = client.cancel_command(request).await;
895 parse_rpc_response_with_result(
896 response,
897 |rpc_response: &kachaka_api::CancelCommandResponse| rpc_response.result,
898 )
899 .map(|_response| ())
900}
901
902pub async fn proceed(client: &mut TonicKachakaApiClient<Channel>) -> Result<(), KachakaApiError> {
904 let request = tonic::Request::new(kachaka_api::EmptyRequest {});
905 let response = client.proceed(request).await;
906 parse_rpc_response_with_result(response, |rpc_response: &kachaka_api::ProceedResponse| {
907 rpc_response.result
908 })
909 .map(|_response| ())
910}
911
912async fn get_locations_with_cursor(
914 client: &mut TonicKachakaApiClient<Channel>,
915 cursor: i64,
916) -> Result<(i64, Vec<kachaka_api::Location>), KachakaApiError> {
917 let request = tonic::Request::new(kachaka_api::GetRequest {
918 metadata: Some(kachaka_api::Metadata { cursor }),
919 });
920 let response = client.get_locations(request).await;
921 parse_getter_response(response)
922 .map(|response| (response.metadata.unwrap().cursor, response.locations))
923}
924
925pub async fn get_locations(
926 client: &mut TonicKachakaApiClient<Channel>,
927 cursor: i64,
928) -> Result<Vec<kachaka_api::Location>, KachakaApiError> {
929 get_locations_with_cursor(client, cursor)
930 .await
931 .map(|(_, locations)| locations)
932}
933
934pub async fn get_latest_locations(
935 client: &mut TonicKachakaApiClient<Channel>,
936) -> Result<Vec<kachaka_api::Location>, KachakaApiError> {
937 get_locations(client, 0).await
938}
939
940pub async fn watch_locations(
941 client: &mut TonicKachakaApiClient<Channel>,
942) -> impl Stream<Item = Result<Vec<kachaka_api::Location>, KachakaApiError>> {
943 let (tx, rx) = mpsc::unbounded_channel::<Result<Vec<kachaka_api::Location>, KachakaApiError>>();
944 let mut cursor = 0;
945 let mut client_clone = client.clone();
946 tokio::spawn(async move {
947 loop {
948 match get_locations_with_cursor(&mut client_clone, cursor).await {
949 Ok((new_cursor, locations)) => {
950 cursor = new_cursor;
951 tx.send(Ok(locations)).unwrap();
952 }
953 Err(e) => {
954 tx.send(Err(e)).unwrap();
955 }
956 }
957 }
958 });
959
960 UnboundedReceiverStream::new(rx)
961}
962
963async fn get_shelves_with_cursor(
965 client: &mut TonicKachakaApiClient<Channel>,
966 cursor: i64,
967) -> Result<(i64, Vec<kachaka_api::Shelf>), KachakaApiError> {
968 let request = tonic::Request::new(kachaka_api::GetRequest {
969 metadata: Some(kachaka_api::Metadata { cursor }),
970 });
971 let response = client.get_shelves(request).await;
972 parse_getter_response(response)
973 .map(|response| (response.metadata.unwrap().cursor, response.shelves))
974}
975
976pub async fn get_shelves(
977 client: &mut TonicKachakaApiClient<Channel>,
978 cursor: i64,
979) -> Result<Vec<kachaka_api::Shelf>, KachakaApiError> {
980 get_shelves_with_cursor(client, cursor)
981 .await
982 .map(|(_, shelves)| shelves)
983}
984
985pub async fn get_latest_shelves(
986 client: &mut TonicKachakaApiClient<Channel>,
987) -> Result<Vec<kachaka_api::Shelf>, KachakaApiError> {
988 get_shelves(client, 0).await
989}
990
991pub async fn watch_shelves(
992 client: &mut TonicKachakaApiClient<Channel>,
993) -> impl Stream<Item = Result<Vec<kachaka_api::Shelf>, KachakaApiError>> {
994 let (tx, rx) = mpsc::unbounded_channel::<Result<Vec<kachaka_api::Shelf>, KachakaApiError>>();
995 let mut cursor = 0;
996 let mut client_clone = client.clone();
997
998 tokio::spawn(async move {
999 loop {
1000 match get_shelves_with_cursor(&mut client_clone, cursor).await {
1001 Ok((new_cursor, shelves)) => {
1002 cursor = new_cursor;
1003 tx.send(Ok(shelves)).unwrap();
1004 }
1005 Err(e) => {
1006 tx.send(Err(e)).unwrap();
1007 }
1008 }
1009 }
1010 });
1011
1012 UnboundedReceiverStream::new(rx)
1013}
1014
1015async fn get_moving_shelf_id_with_cursor(
1017 client: &mut TonicKachakaApiClient<Channel>,
1018 cursor: i64,
1019) -> Result<(i64, String), KachakaApiError> {
1020 let request = tonic::Request::new(kachaka_api::GetRequest {
1021 metadata: Some(kachaka_api::Metadata { cursor }),
1022 });
1023 let response = client.get_moving_shelf_id(request).await;
1024 parse_getter_response(response)
1025 .map(|response| (response.metadata.unwrap().cursor, response.shelf_id))
1026}
1027
1028pub async fn get_moving_shelf_id(
1029 client: &mut TonicKachakaApiClient<Channel>,
1030 cursor: i64,
1031) -> Result<String, KachakaApiError> {
1032 get_moving_shelf_id_with_cursor(client, cursor)
1033 .await
1034 .map(|(_, shelf_id)| shelf_id)
1035}
1036
1037pub async fn get_latest_moving_shelf_id(
1038 client: &mut TonicKachakaApiClient<Channel>,
1039) -> Result<String, KachakaApiError> {
1040 get_moving_shelf_id(client, 0).await
1041}
1042
1043pub async fn watch_moving_shelf_id(
1044 client: &mut TonicKachakaApiClient<Channel>,
1045) -> impl Stream<Item = Result<String, KachakaApiError>> {
1046 let (tx, rx) = mpsc::unbounded_channel::<Result<String, KachakaApiError>>();
1047 let mut cursor = 0;
1048 let mut client_clone = client.clone();
1049
1050 tokio::spawn(async move {
1051 loop {
1052 match get_moving_shelf_id_with_cursor(&mut client_clone, cursor).await {
1053 Ok((new_cursor, shelf_id)) => {
1054 cursor = new_cursor;
1055 tx.send(Ok(shelf_id)).unwrap();
1056 }
1057 Err(e) => {
1058 tx.send(Err(e)).unwrap();
1059 }
1060 }
1061 }
1062 });
1063
1064 UnboundedReceiverStream::new(rx)
1065}
1066
1067pub async fn reset_shelf_pose(
1069 client: &mut TonicKachakaApiClient<Channel>,
1070 shelf_id: &str,
1071) -> Result<(), KachakaApiError> {
1072 let request = tonic::Request::new(kachaka_api::ResetShelfPoseRequest {
1073 shelf_id: shelf_id.to_string(),
1074 });
1075 let response = client.reset_shelf_pose(request).await;
1076 parse_rpc_response_with_result(
1077 response,
1078 |rpc_response: &kachaka_api::ResetShelfPoseResponse| rpc_response.result,
1079 )
1080 .map(|_response| ())
1081}