1use serde::{Deserialize, Serialize};
6use validator::Validate;
7
8use crate::models::enums::{GripperState, LiftState, MotorStatus};
9
10#[derive(Debug, Clone, Default, Serialize, Deserialize, Validate)]
12pub struct ActuatorsDomain {
13 #[serde(skip_serializing_if = "Option::is_none")]
16 pub drive_motors: Option<Vec<DriveMotor>>,
17
18 #[serde(skip_serializing_if = "Option::is_none")]
21 pub joints: Option<Vec<Joint>>,
22
23 #[serde(skip_serializing_if = "Option::is_none")]
26 pub grippers: Option<Vec<Gripper>>,
27
28 #[serde(skip_serializing_if = "Option::is_none")]
31 pub lifts: Option<Vec<Lift>>,
32
33 #[serde(skip_serializing_if = "Option::is_none")]
36 pub steering: Option<Vec<SteeringActuator>>,
37
38 #[serde(skip_serializing_if = "Option::is_none")]
41 pub hydraulics: Option<HydraulicSystem>,
42
43 #[serde(skip_serializing_if = "Option::is_none")]
46 pub pneumatics: Option<PneumaticSystem>,
47}
48
49#[derive(Debug, Clone, Default, Serialize, Deserialize, Validate)]
51pub struct DriveMotor {
52 #[serde(skip_serializing_if = "Option::is_none")]
54 pub motor_id: Option<String>,
55
56 #[serde(skip_serializing_if = "Option::is_none")]
58 pub name: Option<String>,
59
60 #[serde(skip_serializing_if = "Option::is_none")]
62 pub status: Option<MotorStatus>,
63
64 #[serde(skip_serializing_if = "Option::is_none")]
66 pub velocity_rpm: Option<f64>,
67
68 #[serde(skip_serializing_if = "Option::is_none")]
70 pub commanded_rpm: Option<f64>,
71
72 #[serde(skip_serializing_if = "Option::is_none")]
74 pub current_a: Option<f64>,
75
76 #[serde(skip_serializing_if = "Option::is_none")]
78 pub voltage_v: Option<f64>,
79
80 #[serde(skip_serializing_if = "Option::is_none")]
82 pub power_w: Option<f64>,
83
84 #[serde(skip_serializing_if = "Option::is_none")]
86 pub temperature_c: Option<f64>,
87
88 #[serde(skip_serializing_if = "Option::is_none")]
90 pub torque_nm: Option<f64>,
91
92 #[serde(skip_serializing_if = "Option::is_none")]
94 pub encoder_position: Option<f64>,
95
96 #[serde(skip_serializing_if = "Option::is_none")]
98 pub is_enabled: Option<bool>,
99
100 #[serde(skip_serializing_if = "Option::is_none")]
102 pub error_code: Option<String>,
103}
104
105#[derive(Debug, Clone, Default, Serialize, Deserialize, Validate)]
107pub struct Joint {
108 #[serde(skip_serializing_if = "Option::is_none")]
110 pub joint_id: Option<String>,
111
112 #[serde(skip_serializing_if = "Option::is_none")]
114 pub name: Option<String>,
115
116 #[serde(skip_serializing_if = "Option::is_none")]
118 pub joint_type: Option<JointType>,
119
120 #[serde(skip_serializing_if = "Option::is_none")]
122 pub position: Option<f64>,
123
124 #[serde(skip_serializing_if = "Option::is_none")]
126 pub commanded_position: Option<f64>,
127
128 #[serde(skip_serializing_if = "Option::is_none")]
130 pub velocity: Option<f64>,
131
132 #[serde(skip_serializing_if = "Option::is_none")]
134 pub effort: Option<f64>,
135
136 #[serde(skip_serializing_if = "Option::is_none")]
138 pub temperature_c: Option<f64>,
139
140 #[serde(skip_serializing_if = "Option::is_none")]
142 pub min_position: Option<f64>,
143
144 #[serde(skip_serializing_if = "Option::is_none")]
146 pub max_position: Option<f64>,
147
148 #[serde(skip_serializing_if = "Option::is_none")]
150 pub status: Option<MotorStatus>,
151
152 #[serde(skip_serializing_if = "Option::is_none")]
154 pub at_limit: Option<bool>,
155}
156
157#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
159#[serde(rename_all = "snake_case")]
160pub enum JointType {
161 #[default]
163 Revolute,
164 Prismatic,
166 Continuous,
168 Fixed,
170}
171
172#[derive(Debug, Clone, Default, Serialize, Deserialize, Validate)]
174pub struct Gripper {
175 #[serde(skip_serializing_if = "Option::is_none")]
177 pub gripper_id: Option<String>,
178
179 #[serde(skip_serializing_if = "Option::is_none")]
181 pub name: Option<String>,
182
183 #[serde(skip_serializing_if = "Option::is_none")]
185 pub gripper_type: Option<String>,
186
187 #[serde(skip_serializing_if = "Option::is_none")]
189 pub state: Option<GripperState>,
190
191 #[serde(skip_serializing_if = "Option::is_none")]
193 #[validate(range(min = 0.0, max = 100.0))]
194 pub position_pct: Option<f64>,
195
196 #[serde(skip_serializing_if = "Option::is_none")]
198 #[validate(range(min = 0.0))]
199 pub force_n: Option<f64>,
200
201 #[serde(skip_serializing_if = "Option::is_none")]
203 pub object_detected: Option<bool>,
204
205 #[serde(skip_serializing_if = "Option::is_none")]
207 pub vacuum_kpa: Option<f64>,
208
209 #[serde(skip_serializing_if = "Option::is_none")]
211 pub grip_secure: Option<bool>,
212
213 #[serde(skip_serializing_if = "Option::is_none")]
215 pub status: Option<MotorStatus>,
216}
217
218#[derive(Debug, Clone, Default, Serialize, Deserialize, Validate)]
220pub struct Lift {
221 #[serde(skip_serializing_if = "Option::is_none")]
223 pub lift_id: Option<String>,
224
225 #[serde(skip_serializing_if = "Option::is_none")]
227 pub name: Option<String>,
228
229 #[serde(skip_serializing_if = "Option::is_none")]
231 pub state: Option<LiftState>,
232
233 #[serde(skip_serializing_if = "Option::is_none")]
235 #[validate(range(min = 0.0))]
236 pub height_m: Option<f64>,
237
238 #[serde(skip_serializing_if = "Option::is_none")]
240 #[validate(range(min = 0.0))]
241 pub target_height_m: Option<f64>,
242
243 #[serde(skip_serializing_if = "Option::is_none")]
245 pub min_height_m: Option<f64>,
246
247 #[serde(skip_serializing_if = "Option::is_none")]
249 pub max_height_m: Option<f64>,
250
251 #[serde(skip_serializing_if = "Option::is_none")]
253 #[validate(range(min = 0.0))]
254 pub load_kg: Option<f64>,
255
256 #[serde(skip_serializing_if = "Option::is_none")]
258 pub max_load_kg: Option<f64>,
259
260 #[serde(skip_serializing_if = "Option::is_none")]
262 pub velocity_mps: Option<f64>,
263
264 #[serde(skip_serializing_if = "Option::is_none")]
266 pub status: Option<MotorStatus>,
267
268 #[serde(skip_serializing_if = "Option::is_none")]
270 pub at_top: Option<bool>,
271
272 #[serde(skip_serializing_if = "Option::is_none")]
274 pub at_bottom: Option<bool>,
275}
276
277#[derive(Debug, Clone, Default, Serialize, Deserialize, Validate)]
279pub struct SteeringActuator {
280 #[serde(skip_serializing_if = "Option::is_none")]
282 pub actuator_id: Option<String>,
283
284 #[serde(skip_serializing_if = "Option::is_none")]
286 pub name: Option<String>,
287
288 #[serde(skip_serializing_if = "Option::is_none")]
290 pub angle_deg: Option<f64>,
291
292 #[serde(skip_serializing_if = "Option::is_none")]
294 pub commanded_deg: Option<f64>,
295
296 #[serde(skip_serializing_if = "Option::is_none")]
298 pub rate_dps: Option<f64>,
299
300 #[serde(skip_serializing_if = "Option::is_none")]
302 pub min_angle_deg: Option<f64>,
303
304 #[serde(skip_serializing_if = "Option::is_none")]
306 pub max_angle_deg: Option<f64>,
307
308 #[serde(skip_serializing_if = "Option::is_none")]
310 pub status: Option<MotorStatus>,
311}
312
313#[derive(Debug, Clone, Default, Serialize, Deserialize, Validate)]
315pub struct HydraulicSystem {
316 #[serde(skip_serializing_if = "Option::is_none")]
318 #[validate(range(min = 0.0))]
319 pub pressure_bar: Option<f64>,
320
321 #[serde(skip_serializing_if = "Option::is_none")]
323 pub target_pressure_bar: Option<f64>,
324
325 #[serde(skip_serializing_if = "Option::is_none")]
327 pub temperature_c: Option<f64>,
328
329 #[serde(skip_serializing_if = "Option::is_none")]
331 #[validate(range(min = 0.0, max = 100.0))]
332 pub fluid_level_pct: Option<f64>,
333
334 #[serde(skip_serializing_if = "Option::is_none")]
336 pub pump_status: Option<MotorStatus>,
337
338 #[serde(skip_serializing_if = "Option::is_none")]
340 pub is_pressurized: Option<bool>,
341}
342
343#[derive(Debug, Clone, Default, Serialize, Deserialize, Validate)]
345pub struct PneumaticSystem {
346 #[serde(skip_serializing_if = "Option::is_none")]
348 #[validate(range(min = 0.0))]
349 pub pressure_bar: Option<f64>,
350
351 #[serde(skip_serializing_if = "Option::is_none")]
353 pub tank_pressure_bar: Option<f64>,
354
355 #[serde(skip_serializing_if = "Option::is_none")]
357 pub compressor_status: Option<MotorStatus>,
358
359 #[serde(skip_serializing_if = "Option::is_none")]
361 pub compressor_running: Option<bool>,
362
363 #[serde(skip_serializing_if = "Option::is_none")]
365 pub flow_rate_lpm: Option<f64>,
366}
367
368impl ActuatorsDomain {
369 pub fn with_drive_motors(motors: Vec<DriveMotor>) -> Self {
371 Self {
372 drive_motors: Some(motors),
373 ..Default::default()
374 }
375 }
376
377 pub fn with_joint(mut self, joint: Joint) -> Self {
379 let joints = self.joints.get_or_insert_with(Vec::new);
380 joints.push(joint);
381 self
382 }
383
384 pub fn with_gripper(mut self, gripper: Gripper) -> Self {
386 let grippers = self.grippers.get_or_insert_with(Vec::new);
387 grippers.push(gripper);
388 self
389 }
390
391 pub fn with_lift(mut self, lift: Lift) -> Self {
393 let lifts = self.lifts.get_or_insert_with(Vec::new);
394 lifts.push(lift);
395 self
396 }
397}
398
399impl DriveMotor {
400 pub fn new(name: impl Into<String>) -> Self {
402 Self {
403 name: Some(name.into()),
404 status: Some(MotorStatus::Ok),
405 is_enabled: Some(true),
406 ..Default::default()
407 }
408 }
409
410 pub fn with_velocity(mut self, rpm: f64) -> Self {
412 self.velocity_rpm = Some(rpm);
413 self
414 }
415
416 pub fn with_current(mut self, amps: f64) -> Self {
418 self.current_a = Some(amps);
419 self
420 }
421
422 pub fn with_temperature(mut self, temp_c: f64) -> Self {
424 self.temperature_c = Some(temp_c);
425 self
426 }
427}
428
429impl Gripper {
430 pub fn open(name: impl Into<String>) -> Self {
432 Self {
433 name: Some(name.into()),
434 state: Some(GripperState::Open),
435 position_pct: Some(100.0),
436 object_detected: Some(false),
437 status: Some(MotorStatus::Ok),
438 ..Default::default()
439 }
440 }
441
442 pub fn holding(name: impl Into<String>, force_n: f64) -> Self {
444 Self {
445 name: Some(name.into()),
446 state: Some(GripperState::Holding),
447 object_detected: Some(true),
448 grip_secure: Some(true),
449 force_n: Some(force_n),
450 status: Some(MotorStatus::Ok),
451 ..Default::default()
452 }
453 }
454}
455
456impl Lift {
457 pub fn at_height(name: impl Into<String>, height_m: f64) -> Self {
459 Self {
460 name: Some(name.into()),
461 height_m: Some(height_m),
462 state: Some(if height_m < 0.1 {
463 LiftState::Lowered
464 } else {
465 LiftState::Intermediate
466 }),
467 status: Some(MotorStatus::Ok),
468 ..Default::default()
469 }
470 }
471}
472
473#[cfg(test)]
474mod tests {
475 use super::*;
476
477 #[test]
478 fn test_drive_motor() {
479 let motor = DriveMotor::new("left_wheel")
480 .with_velocity(100.0)
481 .with_current(2.5);
482
483 assert_eq!(motor.name, Some("left_wheel".to_string()));
484 assert_eq!(motor.velocity_rpm, Some(100.0));
485 }
486
487 #[test]
488 fn test_gripper_holding() {
489 let gripper = Gripper::holding("main_gripper", 15.0);
490 assert_eq!(gripper.state, Some(GripperState::Holding));
491 assert_eq!(gripper.grip_secure, Some(true));
492 }
493
494 #[test]
495 fn test_actuators_domain() {
496 let actuators = ActuatorsDomain::with_drive_motors(vec![
497 DriveMotor::new("left"),
498 DriveMotor::new("right"),
499 ])
500 .with_gripper(Gripper::open("gripper"));
501
502 assert_eq!(actuators.drive_motors.as_ref().unwrap().len(), 2);
503 assert_eq!(actuators.grippers.as_ref().unwrap().len(), 1);
504 }
505}