kachaka_api/
api_impl.rs

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
45// getter api
46
47// GetRobotSerialNumber
48async 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
97// GetRobotVersion
98async 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
147// GetRobotPose
148async 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
201// GetBatteryInfo
202async 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
258// GetFrontCameraRosImage
259async 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
312// GetFrontCameraRosCompressedImage
313async 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
367// GetBackCameraRosImage
368async 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
421// GetBackCameraRosCompressedImage
422async 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
476// GetRobotErrorCodeJson
477fn 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
520// GetError
521async 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
580// GetCommandState
581async 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
630// GetLastCommandResult
631async 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
682// command api
683// StartCommand
684async 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
889// CancelCommand
890pub 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
902// Proceed
903pub 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
912// GetLocations
913async 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
963// GetShelves
964async 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
1015// GetMovingShelfId
1016async 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
1067// ResetShelfPose
1068pub 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}