seersdk_rs/api/
response.rs

1use crate::{PointId, TaskId};
2
3#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
4pub struct StatusMessage {
5    #[serde(rename = "ret_code")]
6    pub code: StatusCode,
7    #[serde(rename = "err_msg", default)]
8    pub message: String,
9    #[serde(rename = "create_on", default)]
10    pub timestamp: Option<String>,
11}
12
13impl StatusMessage {
14    //fixme: to weird impl
15    pub fn into_result(self) -> Result<(), StatusMessage> {
16        if self.code == StatusCode::Success {
17            Ok(())
18        } else {
19            Err(self)
20        }
21    }
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, num_enum::FromPrimitive)]
25#[repr(u32)]
26pub enum StatusCode {
27    /// Success
28    Success = 0,
29    Unavailable = 40000,
30    /// The request parameter is missing
31    ParamMissing = 40001,
32    /// The request parameter type is incorrect
33    ParamTypeError = 40002,
34    /// The request parameter is not legal
35    ParamIllegal = 40003,
36    /// Operating mode error
37    ModeError = 40004,
38    /// Illegal map name
39    IllegalMapName = 40005,
40    /// Programming firmware
41    ProgrammingDsp = 40006,
42    /// Programming firmware error
43    ProgramDspError = 40007,
44    /// An error occurred in the shutdown command
45    ShutdownError = 40010,
46    /// An error occurred in the restart command
47    RebootError = 40011,
48    /// Map analysis error
49    MapParseError = 40050,
50    /// The map does not exist
51    MapNotExists = 40051,
52    /// Loading map error
53    LoadMapError = 40052,
54    /// Overload map error
55    LoadMapobjError = 40053,
56    /// Open map
57    EmptyMap = 40054,
58    /// Request execution timeout
59    ReqTimeout = 40100,
60    /// Request is prohibited
61    ReqForbidden = 40101,
62    /// The robot is busy
63    RobotBusy = 40102,
64    /// Internal error
65    RobotInternalError = 40199,
66    /// Initialization status error
67    InitStatusError = 41000,
68    /// Map loading status error
69    LoadmapStatusError = 41001,
70    /// Relocation status error
71    RelocStatusError = 41002,
72
73    /// Unknown error code
74    #[num_enum(default)]
75    Custom,
76}
77
78/// Assumed that the enum is represented as u32
79macro_rules! impl_serde_for_num_enum {
80    ($enum_type:ty) => {
81        impl<'de> serde::Deserialize<'de> for $enum_type {
82            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
83            where
84                D: serde::Deserializer<'de>,
85            {
86                let code = u32::deserialize(deserializer)?;
87                Ok(<$enum_type>::from(code))
88            }
89        }
90
91        impl serde::Serialize for $enum_type {
92            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
93            where
94                S: serde::Serializer,
95            {
96                serializer.serialize_u32(*self as u32)
97            }
98        }
99    };
100}
101
102impl_serde_for_num_enum!(StatusCode);
103impl_serde_for_num_enum!(JackOperationStatus);
104
105pub trait FromResponseBody: Sized {
106    type Response: serde::de::DeserializeOwned;
107}
108
109#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
110pub struct CommonInfo {
111    pub id: String,
112    pub version: String,
113    pub model: String,
114    #[serde(rename = "ret_code", default)]
115    pub code: Option<StatusCode>,
116    #[serde(rename = "err_msg", default)]
117    pub message: String,
118}
119
120#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
121pub struct OperationInfo {
122    #[serde(rename = "odo")]
123    pub mileage: f64,
124    #[serde(rename = "total")]
125    pub session_time_ms: f64,
126    #[serde(rename = "total_time")]
127    pub total_time_ms: f64,
128    /// Controller temperature in Celsius
129    pub controller_temp: f64,
130    /// Controller humidity in percentage
131    #[serde(default)]
132    pub controller_humi: f64,
133    /// Controller voltage in Volts
134    #[serde(default)]
135    pub controller_voltage: f64,
136
137    #[serde(rename = "ret_code", default)]
138    pub code: Option<StatusCode>,
139    #[serde(rename = "err_msg", default)]
140    pub message: String,
141}
142
143#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
144pub struct RobotPose {
145    /// X coordinate in meters
146    pub x: f64,
147    /// Y coordinate in meters
148    pub y: f64,
149    /// Angle in radians
150    pub angle: f64,
151
152    /// Confidence level (0.0 to 1.0)
153    pub confidence: f64,
154
155    #[serde(rename = "ret_code", default)]
156    pub code: Option<StatusCode>,
157    #[serde(rename = "err_msg", default)]
158    pub message: String,
159}
160
161#[derive(Debug, Clone, Copy, PartialEq, Eq, num_enum::FromPrimitive)]
162#[repr(u8)]
163pub enum BlockReason {
164    Laser = 1,
165    Fallingdown = 2,
166    Collision = 3,
167    Infrared = 4,
168
169    #[num_enum(default)]
170    Custom,
171}
172
173impl std::fmt::Display for BlockReason {
174    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
175        let description = match self {
176            BlockReason::Laser => "Laser Obstacle",
177            BlockReason::Fallingdown => "Falling Down",
178            BlockReason::Collision => "Collision Detected",
179            BlockReason::Infrared => "Infrared Obstacle",
180            BlockReason::Custom => "Custom Reason",
181        };
182
183        write!(f, "{}", description)
184    }
185}
186
187impl<'de> serde::Deserialize<'de> for BlockReason {
188    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
189    where
190        D: serde::Deserializer<'de>,
191    {
192        let code = u8::deserialize(deserializer)?;
193        Ok(BlockReason::from(code))
194    }
195}
196
197impl serde::Serialize for BlockReason {
198    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
199    where
200        S: serde::Serializer,
201    {
202        serializer.serialize_u8(*self as u8)
203    }
204}
205
206#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
207pub struct BlockStatus {
208    #[serde(rename = "blocked")]
209    pub is_blocked: bool,
210    #[serde(rename = "block_reason", default)]
211    pub reason: Option<BlockReason>,
212    #[serde(rename = "block_x", default)]
213    pub x: Option<f64>,
214    #[serde(rename = "block_y", default)]
215    pub y: Option<f64>,
216
217    #[serde(rename = "ret_code")]
218    pub code: StatusCode,
219    #[serde(rename = "err_msg", default)]
220    pub message: String,
221}
222
223#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
224pub struct BatteryStatus {
225    /// Level in range 0.0 to 1.0
226    pub battery_level: f64,
227    /// Temperature in Celsius
228    pub battery_temp: f64,
229    /// Is the robot currently charging
230    pub charging: bool,
231    /// Voltage in Volts
232    pub voltage: f64,
233    /// Current in Amperes
234    pub current: f64,
235
236    #[serde(rename = "ret_code", default)]
237    pub code: Option<StatusCode>,
238    #[serde(rename = "err_msg", default)]
239    pub message: String,
240}
241
242#[derive(Debug, Clone, Copy, PartialEq, Eq, num_enum::FromPrimitive)]
243#[repr(u32)]
244pub enum JackOperationStatus {
245    Rising = 0x0,
246    RisingInPlace = 0x1,
247    Lowering = 0x2,
248    LoweringInPlace = 0x3,
249    Stop = 0x4,
250    #[num_enum(default)]
251    Failed = 0xFF,
252}
253
254/// Status of the robot's jack
255/// ### Example
256/// ```
257/// use seersdk_rs::{JackStatus, JackOperation};
258/// let raw_json = r#"
259///  {
260///   "jack_emc": false,
261///   "jack_enable": false,
262///   "jack_error_code": 0,
263///   "jack_height": 0,
264///   "jack_isFull": false,
265///   "jack_mode": false,
266///   "jack_speed": 0,
267///   "jack_state": 0,
268///   "peripheral_data": [],
269///   "ret_code": 0
270/// }"#;
271///
272#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
273pub struct JackStatus {
274    /// Current mode is automatic or manual
275    #[serde(rename = "jack_mode")]
276    pub automatic_mode: bool,
277
278    #[serde(rename = "jack_enable")]
279    pub enabled: bool,
280
281    #[serde(rename = "jack_error_code")]
282    pub error_code: u32,
283
284    /// Current jack operation
285    #[serde(rename = "jack_state")]
286    pub operation: JackOperationStatus,
287
288    #[serde(rename = "jack_isFull")]
289    pub has_payload: bool,
290    /// Jacking speed in mm/s
291    #[serde(rename = "jack_speed")]
292    pub speed: u32,
293    /// Is emergency stop activated
294    #[serde(rename = "jack_emc")]
295    pub emergency_stop: bool,
296    /// Current height in meters
297    #[serde(rename = "jack_height")]
298    pub height: f64,
299    /// User defined peripheral data
300    #[serde(rename = "peripheral_data")]
301    pub peripheral_data: Vec<u8>,
302
303    #[serde(rename = "ret_code", default)]
304    pub code: Option<StatusCode>,
305    #[serde(rename = "err_msg", default)]
306    pub message: String,
307
308    /// API Upload timestamp
309    #[serde(rename = "create_on", default)]
310    pub timestamp: Option<String>,
311}
312
313#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
314pub struct NavStatus {
315    #[serde(rename = "task_status")]
316    pub status: TaskStatus,
317    #[serde(rename = "task_type")]
318    pub ty: TaskType,
319    pub target_id: PointId,
320    /// Target point coordinates (x, y, angle)
321    pub target_point: [f64; 3],
322
323    /// Stations already passed on the current navigation path,
324    /// an array of stations, this field is only valid when task_type is 3.
325    /// All intermediate points already passed will be listed here
326    pub finished_path: Vec<PointId>,
327
328    /// Stations on the current navigation path that have not yet been passed,
329    /// represented as an array of stations, are only valid when task_type is 3.
330    /// All intermediate points that have not yet been passed will be listed here.
331    pub unfinished_path: Vec<PointId>,
332
333    /// Navigation Task Additional Information
334    pub move_status_info: String,
335
336    /// API Error Code
337    #[serde(rename = "ret_code", default)]
338    pub code: Option<StatusCode>,
339    /// API Upload Timestamp
340    pub create_on: Option<String>,
341    /// Error Message
342    #[serde(rename = "err_msg", default)]
343    pub message: String,
344}
345
346#[derive(Debug, Clone, Copy, PartialEq, Eq, num_enum::FromPrimitive)]
347#[repr(u32)]
348pub enum TaskType {
349    NoNav = 0,
350    FreeNavToPoint = 1,
351    FreeNavToSite = 2,
352    PathNavToSite = 3,
353    Manual = 7,
354    #[num_enum(default)]
355    Other = 100,
356}
357
358impl_serde_for_num_enum!(TaskType);
359
360#[derive(Debug, Clone, Copy, PartialEq, Eq, num_enum::FromPrimitive)]
361#[repr(u32)]
362pub enum TaskStatus {
363    #[num_enum(default)]
364    None = 0,
365    Waiting = 1,
366    Running = 2,
367    Suspended = 3,
368    Completed = 4,
369    Failed = 5,
370    Canceled = 6,
371    OverTime = 7,
372    NotFound = 404,
373}
374
375impl_serde_for_num_enum!(TaskStatus);
376
377#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
378pub struct TaskStatusItem {
379    pub task_id: TaskId,
380    pub status: TaskStatus,
381}
382
383#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
384pub struct TaskPackage {
385    /// The station closest to the robot within a certain linear distance (this distance is a
386    pub closest_target: PointId,
387    /// The "source_id" in the navigation task currently being executed by the robot
388    pub source_name: TaskId,
389    /// The "id" of the navigation task currently being executed by the robot
390    pub target_name: TaskId,
391    /// In the navigation task currently being executed by the robot, for the corresponding path,
392    /// the proportion of the part that the robot has completed to the entire path
393    pub percentage: f64,
394    /// Projection distance of the robot to the "path corresponding to the currently executing
395    /// navigation task
396    pub distance: Option<f64>,
397
398    #[serde(rename = "task_status_list")]
399    pub tasks: Vec<TaskStatusItem>,
400    /// During the navigation process, some prompts from the robot to the user can be output to the
401    /// front end. This field does not participate in actual logical judgment
402    pub info: String,
403
404    #[serde(rename = "ret_code", default)]
405    pub code: Option<StatusCode>,
406    #[serde(rename = "err_msg", default)]
407    pub message: String,
408    pub create_on: Option<String>,
409}
410
411#[cfg(test)]
412mod tests {
413    use crate::StatusCode;
414
415    #[test]
416    fn test_error_code_serialization() {
417        use serde::{Deserialize, Serialize};
418
419        #[derive(Serialize, Deserialize)]
420        struct TestStruct {
421            code: StatusCode,
422        }
423
424        let test_instance = TestStruct {
425            code: StatusCode::ParamMissing,
426        };
427
428        let serialized = serde_json::to_string(&test_instance).unwrap();
429        assert_eq!(serialized, r#"{"code":40001}"#);
430
431        let deserialized: TestStruct =
432            serde_json::from_str(&serialized).unwrap();
433        assert_eq!(deserialized.code, StatusCode::ParamMissing);
434
435        let custom_code = r#"{"code":99999}"#;
436        let deserialized_custom: TestStruct =
437            serde_json::from_str(custom_code).unwrap();
438        assert_eq!(deserialized_custom.code, StatusCode::Custom);
439    }
440
441    #[test]
442    #[allow(clippy::approx_constant)]
443    fn test_robot_pose_serialization_deserializatio() {
444        let with_error_code = r#"
445        {
446            "x": 1.0,
447            "y": 2.0,
448            "angle": 0.7854,
449            "confidence": 0.95,
450            "ret_code": 40000,
451            "err_msg": "msg"
452        }"#;
453
454        let pose: super::RobotPose =
455            serde_json::from_str(with_error_code).unwrap();
456        assert_eq!(pose.x, 1.0);
457        assert_eq!(pose.y, 2.0);
458        assert!((pose.angle - 0.7854).abs() < 0.0001);
459        assert_eq!(pose.confidence, 0.95);
460        assert_eq!(pose.code, Some(StatusCode::Unavailable));
461        assert_eq!(pose.message, "msg");
462
463        let without_error_code = r#"
464        {
465            "x": 3.0,
466            "y": 4.0,
467            "angle": 1.5708,
468            "confidence": 0.9
469        }"#;
470        let pose_no_code: super::RobotPose =
471            serde_json::from_str(without_error_code).unwrap();
472        assert_eq!(pose_no_code.x, 3.0);
473        assert_eq!(pose_no_code.y, 4.0);
474        assert!((pose_no_code.angle - 1.5708).abs() < 0.0001);
475        assert_eq!(pose_no_code.confidence, 0.9);
476        assert_eq!(pose_no_code.code, None);
477        assert_eq!(pose_no_code.message, "");
478    }
479
480    #[test]
481    fn test_block_status_serialization_deserialization() {
482        let with_error_code = r#"
483        {
484            "blocked": true,
485            "block_reason": 2,
486            "block_x": 1.5,
487            "block_y": 2.5,
488            "ret_code": 40002,
489            "err_msg": "Parameter type error"
490        }"#;
491
492        let status: super::BlockStatus =
493            serde_json::from_str(with_error_code).unwrap();
494        assert!(status.is_blocked);
495        assert_eq!(status.reason, Some(super::BlockReason::Fallingdown));
496        assert_eq!(status.x, Some(1.5));
497        assert_eq!(status.y, Some(2.5));
498        assert_eq!(status.code, StatusCode::ParamTypeError);
499        assert_eq!(status.message, "Parameter type error");
500
501        let without_error_code = r#"
502        {
503            "ret_code": 0,
504            "blocked": false
505        }"#;
506        let status_no_code: super::BlockStatus =
507            serde_json::from_str(without_error_code).unwrap();
508        assert!(!status_no_code.is_blocked);
509        assert_eq!(status_no_code.reason, None);
510        assert_eq!(status_no_code.x, None);
511        assert_eq!(status_no_code.y, None);
512        assert_eq!(status_no_code.code, StatusCode::Success);
513        assert_eq!(status_no_code.message, "");
514    }
515}