Skip to main content

phoxal_model/component/
capability.rs

1//! Canonical component capabilities normalized from versioned source documents.
2
3#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
4#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
5pub enum Capability {
6    Motor(Motor),
7    Encoder(Encoder),
8    Accelerometer(Accelerometer),
9    Gyroscope(Gyroscope),
10    Magnetometer(Magnetometer),
11    Imu(Imu),
12    Gnss(Gnss),
13    Camera(Camera),
14    Depth(Depth),
15    EmergencyStop(EmergencyStop),
16    Range(Range),
17    Lidar(Lidar),
18    Mmwave(Mmwave),
19    Microphone(Microphone),
20    Speaker(Speaker),
21    Battery(Battery),
22    Led(Led),
23}
24
25#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
26#[serde(rename_all = "snake_case")]
27pub enum EncoderType {
28    Incremental,
29    Absolute,
30}
31
32#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
33#[serde(rename_all = "snake_case")]
34pub enum MotorCommand {
35    Position,
36    Velocity,
37    Torque,
38}
39
40#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
41#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
42pub enum StructuralTarget {
43    Joint { id: String },
44    Link { id: String },
45}
46
47pub const MODULE_INSTANCE_SEPARATOR: &str = "__";
48
49/// Build the canonical runtime identity for one component-local structural item.
50#[must_use]
51pub fn namespaced_structure_id(component_id: &str, local_id: &str) -> String {
52    format!("{component_id}{MODULE_INSTANCE_SEPARATOR}{local_id}")
53}
54
55impl StructuralTarget {
56    #[must_use]
57    pub fn namespaced(&self, component_id: &str) -> Self {
58        match self {
59            Self::Joint { id } => Self::Joint {
60                id: namespaced_structure_id(component_id, id),
61            },
62            Self::Link { id } => Self::Link {
63                id: namespaced_structure_id(component_id, id),
64            },
65        }
66    }
67}
68
69#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
70#[serde(rename_all = "snake_case")]
71pub enum LidarOutput {
72    Ranges,
73    Points,
74}
75
76#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
77#[serde(rename_all = "snake_case")]
78pub enum CameraMode {
79    Mono,
80    Rgb,
81}
82
83#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, Default, PartialEq, Eq)]
84#[serde(rename_all = "snake_case")]
85pub enum GnssCoordinateSystem {
86    #[default]
87    Local,
88    Wgs84,
89}
90
91impl Capability {
92    #[must_use]
93    pub const fn kind_name(&self) -> &'static str {
94        match self {
95            Self::Motor(_) => "motor",
96            Self::Encoder(_) => "encoder",
97            Self::Accelerometer(_) => "accelerometer",
98            Self::Gyroscope(_) => "gyroscope",
99            Self::Magnetometer(_) => "magnetometer",
100            Self::Imu(_) => "imu",
101            Self::Gnss(_) => "gnss",
102            Self::Camera(_) => "camera",
103            Self::Depth(_) => "depth",
104            Self::EmergencyStop(_) => "emergency_stop",
105            Self::Range(_) => "range",
106            Self::Lidar(_) => "lidar",
107            Self::Mmwave(_) => "mmwave",
108            Self::Microphone(_) => "microphone",
109            Self::Speaker(_) => "speaker",
110            Self::Battery(_) => "battery",
111            Self::Led(_) => "led",
112        }
113    }
114
115    #[must_use]
116    pub const fn target(&self) -> &StructuralTarget {
117        match self {
118            Self::Motor(value) => &value.target,
119            Self::Encoder(value) => &value.target,
120            Self::Accelerometer(value) => &value.target,
121            Self::Gyroscope(value) => &value.target,
122            Self::Magnetometer(value) => &value.target,
123            Self::Imu(value) => &value.target,
124            Self::Gnss(value) => &value.target,
125            Self::Camera(value) => &value.target,
126            Self::Depth(value) => &value.target,
127            Self::EmergencyStop(value) => &value.target,
128            Self::Range(value) => &value.target,
129            Self::Lidar(value) => &value.target,
130            Self::Mmwave(value) => &value.target,
131            Self::Microphone(value) => &value.target,
132            Self::Speaker(value) => &value.target,
133            Self::Battery(value) => &value.target,
134            Self::Led(value) => &value.target,
135        }
136    }
137}
138
139#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
140#[serde(deny_unknown_fields)]
141pub struct Motor {
142    pub target: StructuralTarget,
143    pub command: MotorCommand,
144    pub gear_ratio: f64,
145    pub max_torque_nm: Option<f64>,
146    pub max_velocity_radps: Option<f64>,
147}
148
149#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
150#[serde(deny_unknown_fields)]
151pub struct Encoder {
152    pub target: StructuralTarget,
153    pub publish_rate_hz: f64,
154    pub gear_ratio: f64,
155    pub encoder_type: EncoderType,
156    pub counts_per_revolution: u32,
157}
158
159#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
160#[serde(deny_unknown_fields)]
161pub struct Accelerometer {
162    pub target: StructuralTarget,
163    pub publish_rate_hz: f64,
164    pub axes: Option<[bool; 3]>,
165}
166
167#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
168#[serde(deny_unknown_fields)]
169pub struct Gyroscope {
170    pub target: StructuralTarget,
171    pub publish_rate_hz: f64,
172    pub axes: Option<[bool; 3]>,
173}
174
175#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
176#[serde(deny_unknown_fields)]
177pub struct Magnetometer {
178    pub target: StructuralTarget,
179    pub publish_rate_hz: f64,
180    pub axes: Option<[bool; 3]>,
181}
182
183#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
184#[serde(deny_unknown_fields)]
185pub struct Imu {
186    pub target: StructuralTarget,
187    pub publish_rate_hz: f64,
188    pub axes: Option<[bool; 3]>,
189}
190
191#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
192#[serde(deny_unknown_fields)]
193pub struct Gnss {
194    pub target: StructuralTarget,
195    pub publish_rate_hz: f64,
196    pub coordinate_system: GnssCoordinateSystem,
197}
198
199#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
200#[serde(deny_unknown_fields)]
201pub struct Camera {
202    pub target: StructuralTarget,
203    pub mode: CameraMode,
204    pub publish_rate_hz: f64,
205    pub width_px: u32,
206    pub height_px: u32,
207    pub field_of_view_rad: Option<f64>,
208}
209
210#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
211#[serde(deny_unknown_fields)]
212pub struct Depth {
213    pub target: StructuralTarget,
214    pub publish_rate_hz: f64,
215    pub width_px: u32,
216    pub height_px: u32,
217    pub field_of_view_rad: Option<f64>,
218    pub min_range_m: Option<f64>,
219    pub max_range_m: Option<f64>,
220}
221
222#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
223#[serde(deny_unknown_fields)]
224pub struct Range {
225    pub target: StructuralTarget,
226    pub publish_rate_hz: f64,
227    pub min_range_m: f64,
228    pub max_range_m: f64,
229    pub field_of_view_rad: f64,
230}
231
232#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
233#[serde(deny_unknown_fields)]
234pub struct EmergencyStop {
235    pub target: StructuralTarget,
236}
237
238#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
239#[serde(deny_unknown_fields)]
240pub struct Lidar {
241    pub target: StructuralTarget,
242    pub publish_rate_hz: f64,
243    pub output: LidarOutput,
244    pub min_range_m: Option<f64>,
245    pub max_range_m: Option<f64>,
246    pub horizontal_fov_rad: Option<f64>,
247    pub horizontal_resolution_rad: Option<f64>,
248    pub vertical_fov_rad: Option<f64>,
249    pub vertical_resolution_rad: Option<f64>,
250}
251
252#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
253#[serde(deny_unknown_fields)]
254pub struct Mmwave {
255    pub target: StructuralTarget,
256    pub publish_rate_hz: f64,
257}
258
259#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
260#[serde(deny_unknown_fields)]
261pub struct Microphone {
262    pub target: StructuralTarget,
263    pub publish_rate_hz: f64,
264}
265
266#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
267#[serde(deny_unknown_fields)]
268pub struct Speaker {
269    pub target: StructuralTarget,
270}
271
272#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
273#[serde(deny_unknown_fields)]
274pub struct Battery {
275    pub target: StructuralTarget,
276    pub publish_rate_hz: f64,
277    pub voltage_v: f64,
278    pub capacity_ah: f64,
279}
280
281#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
282#[serde(deny_unknown_fields)]
283pub struct Led {
284    pub target: StructuralTarget,
285}