1use crate::*;
19#[cfg(feature = "bevy")]
20use {
21 bevy::{
22 ecs::component::Mutable,
23 prelude::{Component, *},
24 },
25 sdformat::{ElementData, ElementMap},
26 serde::de::DeserializeOwned,
27};
28
29use serde::{Deserialize, Serialize};
30use serde_json::{Map, Value};
31use thiserror::Error;
32
33#[cfg(feature = "bevy")]
34pub trait RobotProperty:
35 'static
36 + Send
37 + Sync
38 + Default
39 + Clone
40 + Component<Mutability = Mutable>
41 + PartialEq
42 + Serialize
43 + DeserializeOwned
44{
45 fn new(kind: String, config: Value) -> Self;
46
47 fn is_default(&self) -> bool;
48
49 fn kind(&self) -> Option<String>;
50
51 fn label() -> String;
52}
53
54#[cfg(feature = "bevy")]
55pub trait RobotPropertyKind:
56 'static + Send + Sync + Default + Clone + Component + PartialEq + Serialize + DeserializeOwned
57{
58 fn label() -> String;
59}
60
61#[cfg(feature = "bevy")]
62pub trait RecallPropertyKind: Recall + Default + Component<Mutability = Mutable> {
63 type Kind: RobotPropertyKind;
64 fn assume(&self) -> Self::Kind;
65}
66
67#[derive(Debug, Error)]
68pub enum RobotPropertyError {
69 #[error("Robot property [{0}] is not found on this robot")]
70 PropertyNotFound(String),
71 #[error("Robot property kind [{0}] is not found in this robot")]
72 PropertyKindNotFound(String),
73 #[error("Failed serializing robot property kind: {0}")]
74 SerializePropertyError(String),
75 #[error("Failed serializing robot property: {0}")]
76 SerializePropertyKindError(String),
77 #[error("Failed deserializing robot property: {0}")]
78 DeserializePropertyError(String),
79 #[error("Failed deserializing robot property kind: {0}")]
80 DeserializePropertyKindError(String),
81}
82
83#[cfg(feature = "bevy")]
84pub fn serialize_robot_property<Property: RobotProperty>(
85 property: Property,
86) -> Result<Value, RobotPropertyError> {
87 serde_json::to_value(property)
88 .map_err(|_| RobotPropertyError::SerializePropertyError(Property::label()))
89}
90
91#[cfg(feature = "bevy")]
92pub fn serialize_robot_property_from_kind<Property: RobotProperty, Kind: RobotPropertyKind>(
93 property_kind: Kind,
94) -> Result<Value, RobotPropertyError> {
95 let label = Kind::label();
96 serde_json::to_value(property_kind)
97 .map_err(|_| RobotPropertyError::SerializePropertyKindError(label.clone()))
98 .map(|val| Property::new(label, val))
99 .and_then(|new_property| serialize_robot_property::<Property>(new_property))
100}
101
102#[cfg(feature = "bevy")]
103pub fn serialize_robot_property_kind<Kind: RobotPropertyKind>(
104 property_kind: Kind,
105) -> Result<Value, RobotPropertyError> {
106 let label = Kind::label();
107 serde_json::to_value(property_kind)
108 .map_err(|_| RobotPropertyError::SerializePropertyKindError(label))
109}
110
111#[cfg(feature = "bevy")]
112pub fn deserialize_robot_property<Property: RobotProperty>(
113 value: Value,
114) -> Result<Property, RobotPropertyError> {
115 serde_json::from_value::<Property>(value)
116 .map_err(|_| RobotPropertyError::DeserializePropertyError(Property::label()))
117}
118
119#[cfg(feature = "bevy")]
120pub fn deserialize_robot_property_kind<Kind: RobotPropertyKind>(
121 value: Value,
122) -> Result<Kind, RobotPropertyError> {
123 serde_json::from_value::<Kind>(value)
124 .map_err(|_| RobotPropertyError::DeserializePropertyKindError(Kind::label()))
125}
126
127#[cfg(feature = "bevy")]
128pub fn retrieve_robot_property<Property: RobotProperty>(
130 robot: Robot,
131) -> Result<(Property, Value), RobotPropertyError> {
132 let property_label = Property::label();
133
134 match robot.properties.get(&property_label) {
135 Some(property_value) => {
136 if property_value.as_object().is_none_or(|m| m.is_empty()) {
137 Ok((Property::default(), Value::Object(Map::new())))
140 } else {
141 deserialize_robot_property::<Property>(property_value.clone())
142 .map(|property| (property.clone(), property_value.clone()))
143 }
144 }
145 None => Err(RobotPropertyError::PropertyNotFound(property_label)),
146 }
147}
148
149#[cfg(feature = "bevy")]
151pub fn retrieve_robot_property_kind<
152 Kind: RobotPropertyKind,
153 Property: RobotProperty,
154 RecallKind: RecallPropertyKind<Kind = Kind>,
155>(
156 value: Value,
157 recall_kind: Option<&RecallKind>,
158) -> Result<Kind, RobotPropertyError> {
159 let kind_label = Kind::label();
160 if let Some((property, config)) = value
161 .as_object()
162 .and_then(|val_obj| Some(val_obj).zip(val_obj.get("config")))
163 {
164 let opt_kind_config = if property
165 .get("kind")
166 .and_then(|kind| kind.as_str())
167 .is_some_and(|label| label == &kind_label)
168 {
169 Some(config.clone())
171 } else if let Some(kind_config) = config.as_object().and_then(|m| m.get(&kind_label)) {
172 Some(kind_config.clone())
175 } else {
176 None
178 };
179
180 if let Some(kind_config) = opt_kind_config {
181 let kind = match deserialize_robot_property_kind::<Kind>(kind_config) {
182 Ok(property_kind) => {
183 if let Some(recall) = recall_kind.filter(|_| property_kind == Kind::default()) {
184 recall.assume()
185 } else {
186 property_kind
187 }
188 }
189 Err(_) => Kind::default(),
190 };
191 return Ok(kind);
192 }
193 }
194 Err(RobotPropertyError::PropertyKindNotFound(Kind::label()))
195}
196
197#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
201#[cfg(feature = "bevy")]
202#[cfg_attr(feature = "bevy", derive(Component, Reflect))]
203#[cfg_attr(feature = "bevy", reflect(Component))]
204pub struct EmptyRobotProperty<T: RobotProperty> {
205 _ignore: std::marker::PhantomData<T>,
206}
207
208#[cfg(feature = "bevy")]
209impl<T: RobotProperty> RobotPropertyKind for EmptyRobotProperty<T> {
210 fn label() -> String {
211 format!("Empty {}", T::label())
212 }
213}
214
215#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
216#[cfg_attr(feature = "bevy", derive(Component))]
217pub struct Mobility {
218 pub kind: String,
219 pub config: Value,
220}
221
222impl Default for Mobility {
223 fn default() -> Self {
224 Self {
225 kind: String::new(),
226 config: Value::Object(Map::new()),
227 }
228 }
229}
230
231#[cfg(feature = "bevy")]
232impl RobotProperty for Mobility {
233 fn new(kind: String, config: Value) -> Self {
234 Self { kind, config }
235 }
236
237 fn is_default(&self) -> bool {
238 if *self == Self::default() {
239 return true;
240 }
241 false
242 }
243
244 fn kind(&self) -> Option<String> {
245 Some(self.kind.clone())
246 }
247
248 fn label() -> String {
249 "Mobility".to_string()
250 }
251}
252
253#[derive(Clone, Debug, Default, PartialEq)]
254#[cfg_attr(feature = "bevy", derive(Component))]
255pub struct RecallMobility {
256 pub kind: Option<String>,
257 pub config: Option<Value>,
258}
259
260impl Recall for RecallMobility {
261 type Source = Mobility;
262
263 fn remember(&mut self, source: &Mobility) {
264 self.kind = Some(source.kind.clone());
265 self.config = Some(source.config.clone());
266 }
267}
268
269#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
271#[cfg_attr(feature = "bevy", derive(Component, Reflect))]
272#[cfg_attr(feature = "bevy", reflect(Component))]
273pub struct DifferentialDrive {
274 pub bidirectional: bool,
275 pub rotation_center_offset: [f32; 2],
276 pub translational_speed: f32,
277 pub rotational_speed: f32,
278}
279
280impl Default for DifferentialDrive {
281 fn default() -> Self {
282 Self {
283 bidirectional: false,
284 rotation_center_offset: [0.0, 0.0],
285 translational_speed: 0.5,
286 rotational_speed: 1.0,
287 }
288 }
289}
290
291#[cfg(feature = "bevy")]
292impl RobotPropertyKind for DifferentialDrive {
293 fn label() -> String {
294 "Differential Drive".to_string()
295 }
296}
297
298#[cfg(feature = "bevy")]
299impl From<&ElementMap> for DifferentialDrive {
300 fn from(elements: &ElementMap) -> Self {
301 let mut diff_drive = DifferentialDrive::default();
302 if let Some(reversible) = elements.get("reversible") {
303 if let ElementData::String(reversible_str) = &reversible.data {
304 diff_drive.bidirectional = if reversible_str == "true" {
305 true
306 } else if reversible_str == "false" {
307 false
308 } else {
309 warn!(
310 "Found invalid slotcar reversibility data {:?},
311 setting DifferentialDrive reversibility to false.",
312 reversible_str
313 );
314 false
315 };
316 }
317 }
318 if let Some(translational_speed) = elements
319 .get("nominal_drive_speed")
320 .and_then(|speed| f64::try_from(speed.data.clone()).ok())
321 {
322 diff_drive.translational_speed = translational_speed as f32;
323 }
324 if let Some(rotational_speed) = elements
325 .get("nominal_turn_speed")
326 .and_then(|speed| f64::try_from(speed.data.clone()).ok())
327 {
328 diff_drive.rotational_speed = rotational_speed as f32;
329 }
330
331 diff_drive
332 }
333}
334
335#[derive(Clone, Debug, Default, PartialEq)]
336#[cfg_attr(feature = "bevy", derive(Component))]
337pub struct RecallDifferentialDrive {
338 pub bidirectional: Option<bool>,
339 pub rotation_center_offset: Option<[f32; 2]>,
340 pub translational_speed: Option<f32>,
341 pub rotational_speed: Option<f32>,
342}
343
344#[cfg(feature = "bevy")]
345impl RecallPropertyKind for RecallDifferentialDrive {
346 type Kind = DifferentialDrive;
347
348 fn assume(&self) -> DifferentialDrive {
349 DifferentialDrive {
350 bidirectional: self.bidirectional.clone().unwrap_or(false),
351 rotation_center_offset: self.rotation_center_offset.clone().unwrap_or([0.0, 0.0]),
352 translational_speed: self.translational_speed.clone().unwrap_or(0.5),
353 rotational_speed: self.rotational_speed.clone().unwrap_or(1.0),
354 }
355 }
356}
357
358impl Recall for RecallDifferentialDrive {
359 type Source = DifferentialDrive;
360
361 fn remember(&mut self, source: &DifferentialDrive) {
362 self.bidirectional = Some(source.bidirectional);
363 self.rotation_center_offset = Some(source.rotation_center_offset);
364 self.translational_speed = Some(source.translational_speed);
365 self.rotational_speed = Some(source.rotational_speed);
366 }
367}
368
369#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
370#[cfg_attr(feature = "bevy", derive(Component))]
371pub struct Collision {
372 pub kind: String,
373 pub config: Value,
374}
375
376impl Default for Collision {
377 fn default() -> Self {
378 Self {
379 kind: String::new(),
380 config: Value::Object(Map::new()),
381 }
382 }
383}
384
385#[cfg(feature = "bevy")]
386impl RobotProperty for Collision {
387 fn new(kind: String, config: Value) -> Self {
388 Self { kind, config }
389 }
390
391 fn is_default(&self) -> bool {
392 if *self == Self::default() {
393 return true;
394 }
395 false
396 }
397
398 fn kind(&self) -> Option<String> {
399 Some(self.kind.clone())
400 }
401
402 fn label() -> String {
403 "Collision".to_string()
404 }
405}
406
407#[derive(Clone, Debug, Default, PartialEq)]
408#[cfg_attr(feature = "bevy", derive(Component))]
409pub struct RecallCollision {
410 pub kind: Option<String>,
411 pub config: Option<Value>,
412}
413
414impl Recall for RecallCollision {
415 type Source = Collision;
416
417 fn remember(&mut self, source: &Collision) {
418 self.kind = Some(source.kind.clone());
419 self.config = Some(source.config.clone());
420 }
421}
422
423#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
425#[cfg_attr(feature = "bevy", derive(Component, Reflect))]
426#[cfg_attr(feature = "bevy", reflect(Component))]
427pub struct CircleCollision {
428 pub radius: f32,
429 pub offset: [f32; 2],
430}
431
432impl Default for CircleCollision {
433 fn default() -> Self {
434 Self {
435 radius: 0.0,
436 offset: [0.0, 0.0],
437 }
438 }
439}
440
441#[cfg(feature = "bevy")]
442impl RobotPropertyKind for CircleCollision {
443 fn label() -> String {
444 "Circle Collision".to_string()
445 }
446}
447
448#[derive(Clone, Debug, Default, PartialEq)]
449#[cfg_attr(feature = "bevy", derive(Component))]
450pub struct RecallCircleCollision {
451 pub radius: Option<f32>,
452 pub offset: Option<[f32; 2]>,
453}
454
455#[cfg(feature = "bevy")]
456impl RecallPropertyKind for RecallCircleCollision {
457 type Kind = CircleCollision;
458
459 fn assume(&self) -> CircleCollision {
460 CircleCollision {
461 radius: self.radius.clone().unwrap_or_default(),
462 offset: self.offset.clone().unwrap_or_default(),
463 }
464 }
465}
466
467impl Recall for RecallCircleCollision {
468 type Source = CircleCollision;
469
470 fn remember(&mut self, source: &CircleCollision) {
471 self.radius = Some(source.radius);
472 self.offset = Some(source.offset);
473 }
474}
475
476#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
477#[cfg_attr(feature = "bevy", derive(Component))]
478pub struct PowerSource {
479 pub kind: String,
480 pub config: Value,
481}
482
483impl Default for PowerSource {
484 fn default() -> Self {
485 Self {
486 kind: String::new(),
487 config: Value::Object(Map::new()),
488 }
489 }
490}
491
492#[cfg(feature = "bevy")]
493impl RobotProperty for PowerSource {
494 fn new(kind: String, config: Value) -> Self {
495 Self { kind, config }
496 }
497
498 fn is_default(&self) -> bool {
499 if *self == Self::default() {
500 return true;
501 }
502 false
503 }
504
505 fn kind(&self) -> Option<String> {
506 Some(self.kind.clone())
507 }
508
509 fn label() -> String {
510 "Power Source".to_string()
511 }
512}
513
514#[derive(Clone, Debug, Default, PartialEq)]
515#[cfg_attr(feature = "bevy", derive(Component))]
516pub struct RecallPowerSource {
517 pub kind: Option<String>,
518 pub config: Option<Value>,
519}
520
521impl Recall for RecallPowerSource {
522 type Source = PowerSource;
523
524 fn remember(&mut self, source: &PowerSource) {
525 self.kind = Some(source.kind.clone());
526 self.config = Some(source.config.clone());
527 }
528}
529
530#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
532#[cfg_attr(feature = "bevy", derive(Component, Reflect))]
533#[cfg_attr(feature = "bevy", reflect(Component))]
534pub struct Battery {
535 pub voltage: f32,
536 pub capacity: f32,
537 pub charging_current: f32,
538}
539
540impl Default for Battery {
541 fn default() -> Self {
542 Self {
543 voltage: 12.0,
544 capacity: 24.0,
545 charging_current: 5.0,
546 }
547 }
548}
549
550#[cfg(feature = "bevy")]
551impl RobotPropertyKind for Battery {
552 fn label() -> String {
553 "Battery".to_string()
554 }
555}
556
557#[cfg(feature = "bevy")]
558impl From<&ElementMap> for Battery {
559 fn from(elements: &ElementMap) -> Self {
560 let mut battery = Battery::default();
561 if let Some(voltage) = elements
562 .get("nominal_voltage")
563 .and_then(|voltage| f64::try_from(voltage.data.clone()).ok())
564 {
565 battery.voltage = voltage as f32;
566 }
567 if let Some(capacity) = elements
568 .get("nominal_capacity")
569 .and_then(|capacity| f64::try_from(capacity.data.clone()).ok())
570 {
571 battery.capacity = capacity as f32;
572 }
573 if let Some(current) = elements
574 .get("charging_current")
575 .and_then(|current| f64::try_from(current.data.clone()).ok())
576 {
577 battery.charging_current = current as f32;
578 }
579
580 battery
581 }
582}
583
584#[derive(Clone, Debug, Default, PartialEq)]
585#[cfg_attr(feature = "bevy", derive(Component))]
586pub struct RecallBattery {
587 pub voltage: Option<f32>,
588 pub capacity: Option<f32>,
589 pub charging_current: Option<f32>,
590}
591
592#[cfg(feature = "bevy")]
593impl RecallPropertyKind for RecallBattery {
594 type Kind = Battery;
595
596 fn assume(&self) -> Battery {
597 Battery {
598 voltage: self.voltage.clone().unwrap_or(12.0),
599 capacity: self.capacity.clone().unwrap_or(24.0),
600 charging_current: self.charging_current.clone().unwrap_or(5.0),
601 }
602 }
603}
604
605impl Recall for RecallBattery {
606 type Source = Battery;
607
608 fn remember(&mut self, source: &Battery) {
609 self.voltage = Some(source.voltage);
610 self.capacity = Some(source.capacity);
611 self.charging_current = Some(source.charging_current);
612 }
613}
614
615#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
617#[cfg_attr(feature = "bevy", derive(Component))]
618pub struct PowerDissipation {
619 pub config: Value,
620}
621
622impl Default for PowerDissipation {
623 fn default() -> Self {
624 Self {
625 config: Value::Object(Map::new()),
626 }
627 }
628}
629
630#[cfg(feature = "bevy")]
631impl RobotProperty for PowerDissipation {
632 fn new(kind: String, config: Value) -> Self {
633 let mut property_map = Map::new();
634 property_map.insert(kind.clone(), config);
635
636 Self {
637 config: Value::Object(property_map),
638 }
639 }
640
641 fn is_default(&self) -> bool {
642 if *self == Self::default() {
643 return true;
644 }
645 false
646 }
647
648 fn kind(&self) -> Option<String> {
649 None
651 }
652
653 fn label() -> String {
654 "Power Dissipation".to_string()
655 }
656}
657
658#[derive(Clone, Debug, Default, PartialEq)]
659#[cfg_attr(feature = "bevy", derive(Component))]
660pub struct RecallPowerDissipation {
661 pub config: Option<Value>,
662}
663
664impl Recall for RecallPowerDissipation {
665 type Source = PowerDissipation;
666
667 fn remember(&mut self, source: &PowerDissipation) {
668 self.config = Some(source.config.clone());
669 }
670}
671
672#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
674#[cfg_attr(feature = "bevy", derive(Component, Reflect))]
675#[cfg_attr(feature = "bevy", reflect(Component))]
676pub struct MechanicalSystem {
677 pub mass: f32,
678 pub moment_of_inertia: f32,
679 pub friction_coefficient: f32,
680}
681
682impl Default for MechanicalSystem {
683 fn default() -> Self {
684 Self {
685 mass: 20.0,
686 moment_of_inertia: 10.0,
687 friction_coefficient: 0.22,
688 }
689 }
690}
691
692#[cfg(feature = "bevy")]
693impl RobotPropertyKind for MechanicalSystem {
694 fn label() -> String {
695 "Mechanical System".to_string()
696 }
697}
698
699#[cfg(feature = "bevy")]
700impl From<&ElementMap> for MechanicalSystem {
701 fn from(elements: &ElementMap) -> Self {
702 let mut mechanical_system = MechanicalSystem::default();
703 if let Some(mass) = elements
704 .get("mass")
705 .and_then(|mass| f64::try_from(mass.data.clone()).ok())
706 {
707 mechanical_system.mass = mass as f32;
708 }
709 if let Some(moment_of_inertia) = elements
710 .get("inertia")
711 .and_then(|moment_of_inertia| f64::try_from(moment_of_inertia.data.clone()).ok())
712 {
713 mechanical_system.moment_of_inertia = moment_of_inertia as f32;
714 }
715 if let Some(friction_coefficient) = elements
716 .get("friction_coefficient")
717 .and_then(|friction_coefficient| f64::try_from(friction_coefficient.data.clone()).ok())
718 {
719 mechanical_system.friction_coefficient = friction_coefficient as f32;
720 }
721
722 mechanical_system
723 }
724}
725
726#[derive(Clone, Debug, Default, PartialEq)]
727#[cfg_attr(feature = "bevy", derive(Component))]
728pub struct RecallMechanicalSystem {
729 pub mass: Option<f32>,
730 pub moment_of_inertia: Option<f32>,
731 pub friction_coefficient: Option<f32>,
732}
733
734#[cfg(feature = "bevy")]
735impl RecallPropertyKind for RecallMechanicalSystem {
736 type Kind = MechanicalSystem;
737
738 fn assume(&self) -> MechanicalSystem {
739 MechanicalSystem {
740 mass: self.mass.clone().unwrap_or(20.0),
741 moment_of_inertia: self.moment_of_inertia.clone().unwrap_or(10.0),
742 friction_coefficient: self.friction_coefficient.clone().unwrap_or(0.22),
743 }
744 }
745}
746
747impl Recall for RecallMechanicalSystem {
748 type Source = MechanicalSystem;
749
750 fn remember(&mut self, source: &MechanicalSystem) {
751 self.mass = Some(source.mass);
752 self.moment_of_inertia = Some(source.moment_of_inertia);
753 self.friction_coefficient = Some(source.friction_coefficient);
754 }
755}
756
757#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
758#[cfg_attr(feature = "bevy", derive(Component, Reflect))]
759#[cfg_attr(feature = "bevy", reflect(Component))]
760pub struct AmbientSystem {
761 pub idle_power: f32,
762}
763
764impl Default for AmbientSystem {
765 fn default() -> Self {
766 Self { idle_power: 20.0 }
767 }
768}
769
770#[cfg(feature = "bevy")]
771impl RobotPropertyKind for AmbientSystem {
772 fn label() -> String {
773 "Ambient System".to_string()
774 }
775}
776
777#[cfg(feature = "bevy")]
778impl From<&ElementMap> for AmbientSystem {
779 fn from(elements: &ElementMap) -> Self {
780 let mut ambient_system = AmbientSystem::default();
781 if let Some(power) = elements
782 .get("nominal_power")
783 .and_then(|power| f64::try_from(power.data.clone()).ok())
784 {
785 ambient_system.idle_power = power as f32;
786 }
787
788 ambient_system
789 }
790}
791
792#[derive(Clone, Debug, Default, PartialEq)]
793#[cfg_attr(feature = "bevy", derive(Component))]
794pub struct RecallAmbientSystem {
795 pub idle_power: Option<f32>,
796}
797
798#[cfg(feature = "bevy")]
799impl RecallPropertyKind for RecallAmbientSystem {
800 type Kind = AmbientSystem;
801
802 fn assume(&self) -> AmbientSystem {
803 AmbientSystem {
804 idle_power: self.idle_power.clone().unwrap_or(20.0),
805 }
806 }
807}
808
809impl Recall for RecallAmbientSystem {
810 type Source = AmbientSystem;
811
812 fn remember(&mut self, source: &AmbientSystem) {
813 self.idle_power = Some(source.idle_power);
814 }
815}