Skip to main content

sim_lib_auto_core/
manifest.rs

1//! Automotive citizens used by core manifests and transport descriptors.
2
3use sim_citizen_derive::Citizen;
4use sim_kernel::CapabilityName;
5
6use crate::{
7    AUTO_CONTROL_EXEC, AUTO_DIAGNOSTICS_READ, AUTO_MANIFEST_READ, AUTO_ORDER, AUTO_SERVICE_WRITE,
8    AUTO_TELEMETRY_READ, AUTO_TRANSPORT_CONNECT,
9};
10
11mod fields;
12
13/// A modeled vehicle identity safe for committed fixtures and manifests.
14#[derive(Clone, Debug, PartialEq, Eq, Citizen)]
15#[citizen(symbol = "auto/VehicleId", version = 0)]
16pub struct VehicleId {
17    /// Namespace that owns the modeled key, such as a shop or fixture set.
18    pub namespace: String,
19    /// Synthetic key for the vehicle inside the namespace.
20    pub key: String,
21}
22
23impl Default for VehicleId {
24    fn default() -> Self {
25        vehicle_id_example()
26    }
27}
28
29impl VehicleId {
30    /// Builds a modeled vehicle identity.
31    pub fn new(namespace: impl Into<String>, key: impl Into<String>) -> Self {
32        Self {
33            namespace: namespace.into(),
34            key: key.into(),
35        }
36    }
37}
38
39/// A decoded diagnostic trouble code with a modeled description.
40#[derive(Clone, Debug, PartialEq, Eq, Citizen)]
41#[citizen(symbol = "auto/Dtc", version = 0)]
42pub struct Dtc {
43    /// Diagnostic family or subsystem.
44    pub system: String,
45    /// Diagnostic code text.
46    pub code: String,
47    /// Human-facing description for the modeled code.
48    pub description: String,
49    /// Standardized diagnostic status bits supplied by the transport.
50    #[citizen(with = "fields::dtc_status_field")]
51    pub status: DtcStatus,
52}
53
54impl Default for Dtc {
55    fn default() -> Self {
56        dtc_example()
57    }
58}
59
60impl Dtc {
61    /// Builds a diagnostic trouble code descriptor.
62    pub fn new(
63        system: impl Into<String>,
64        code: impl Into<String>,
65        description: impl Into<String>,
66    ) -> Self {
67        Self::with_status(system, code, description, DtcStatus::default())
68    }
69
70    /// Builds a diagnostic trouble code descriptor with explicit status bits.
71    pub fn with_status(
72        system: impl Into<String>,
73        code: impl Into<String>,
74        description: impl Into<String>,
75        status: DtcStatus,
76    ) -> Self {
77        Self {
78            system: system.into(),
79            code: code.into(),
80            description: description.into(),
81            status,
82        }
83    }
84}
85
86/// Standard UDS diagnostic status bits for a trouble code.
87#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Citizen)]
88#[citizen(symbol = "auto/DtcStatus", version = 0)]
89pub struct DtcStatus {
90    /// The DTC test is failed now.
91    pub test_failed: bool,
92    /// The DTC test failed during the current operation cycle.
93    pub test_failed_this_operation_cycle: bool,
94    /// The DTC is pending confirmation.
95    pub pending: bool,
96    /// The DTC is confirmed.
97    pub confirmed: bool,
98    /// The DTC has not completed since the last clear operation.
99    pub test_not_completed_since_clear: bool,
100    /// The DTC failed at least once since the last clear operation.
101    pub test_failed_since_clear: bool,
102    /// The DTC test has not completed in the current operation cycle.
103    pub test_not_completed_this_operation_cycle: bool,
104    /// The warning indicator is requested.
105    pub warning_indicator: bool,
106}
107
108impl DtcStatus {
109    /// Decodes a UDS DTC status byte.
110    pub fn from_byte(byte: u8) -> Self {
111        Self {
112            test_failed: byte & 0x01 != 0,
113            test_failed_this_operation_cycle: byte & 0x02 != 0,
114            pending: byte & 0x04 != 0,
115            confirmed: byte & 0x08 != 0,
116            test_not_completed_since_clear: byte & 0x10 != 0,
117            test_failed_since_clear: byte & 0x20 != 0,
118            test_not_completed_this_operation_cycle: byte & 0x40 != 0,
119            warning_indicator: byte & 0x80 != 0,
120        }
121    }
122
123    /// Encodes the status bits back to a UDS status byte.
124    pub fn to_byte(self) -> u8 {
125        u8::from(self.test_failed)
126            | (u8::from(self.test_failed_this_operation_cycle) << 1)
127            | (u8::from(self.pending) << 2)
128            | (u8::from(self.confirmed) << 3)
129            | (u8::from(self.test_not_completed_since_clear) << 4)
130            | (u8::from(self.test_failed_since_clear) << 5)
131            | (u8::from(self.test_not_completed_this_operation_cycle) << 6)
132            | (u8::from(self.warning_indicator) << 7)
133    }
134}
135
136/// Brand or workshop capability set.
137#[derive(Clone, Debug, PartialEq, Eq, Citizen)]
138#[citizen(symbol = "auto/BrandCaps", version = 0)]
139pub struct BrandCaps {
140    /// Brand, workshop, or fleet label.
141    pub brand: String,
142    /// Capabilities granted by this brand profile.
143    pub capabilities: Vec<CapabilityName>,
144}
145
146impl Default for BrandCaps {
147    fn default() -> Self {
148        brand_caps_example()
149    }
150}
151
152impl BrandCaps {
153    /// Builds a brand capability set.
154    pub fn new(brand: impl Into<String>, capabilities: Vec<CapabilityName>) -> Self {
155        Self {
156            brand: brand.into(),
157            capabilities,
158        }
159    }
160}
161
162/// An open automotive lane name, such as diagnostics or telemetry.
163#[derive(Clone, Debug, PartialEq, Eq, Citizen)]
164#[citizen(symbol = "auto/AutoLane", version = 0)]
165pub struct AutoLane {
166    /// Lane name.
167    pub name: String,
168}
169
170impl Default for AutoLane {
171    fn default() -> Self {
172        auto_lane_example()
173    }
174}
175
176impl AutoLane {
177    /// Builds an automotive lane descriptor.
178    pub fn new(name: impl Into<String>) -> Self {
179        Self { name: name.into() }
180    }
181}
182
183/// An open effect classification used by operation capabilities.
184#[derive(Clone, Debug, PartialEq, Eq, Citizen)]
185#[citizen(symbol = "auto/EffectClass", version = 0)]
186pub struct EffectClass {
187    /// Effect class name.
188    pub name: String,
189}
190
191impl Default for EffectClass {
192    fn default() -> Self {
193        effect_class_example()
194    }
195}
196
197impl EffectClass {
198    /// Builds an automotive effect class.
199    pub fn new(name: impl Into<String>) -> Self {
200        Self { name: name.into() }
201    }
202}
203
204/// Capability required to run one automotive operation.
205#[derive(Clone, Debug, PartialEq, Eq, Citizen)]
206#[citizen(symbol = "auto/OpCap", version = 0)]
207pub struct OpCap {
208    /// Operation symbol text.
209    pub operation: String,
210    /// Capability required by the operation.
211    pub capability: CapabilityName,
212    /// Effect class applied by the operation.
213    pub effect_class: String,
214}
215
216impl Default for OpCap {
217    fn default() -> Self {
218        op_cap_example()
219    }
220}
221
222impl OpCap {
223    /// Builds an operation capability descriptor.
224    pub fn new(
225        operation: impl Into<String>,
226        capability: CapabilityName,
227        effect_class: impl Into<String>,
228    ) -> Self {
229        Self {
230            operation: operation.into(),
231            capability,
232            effect_class: effect_class.into(),
233        }
234    }
235}
236
237/// Transport endpoint descriptor for an automotive site.
238#[derive(Clone, Debug, PartialEq, Eq, Citizen)]
239#[citizen(symbol = "auto/TransportSpec", version = 0)]
240pub struct TransportSpec {
241    /// Transport name.
242    pub name: String,
243    /// Protocol or codec family.
244    pub protocol: String,
245    /// Lane this transport serves.
246    pub lane: String,
247    /// Capability required to read through the transport.
248    pub read_capability: CapabilityName,
249    /// Capability required to write through the transport.
250    pub write_capability: CapabilityName,
251}
252
253impl Default for TransportSpec {
254    fn default() -> Self {
255        transport_spec_example()
256    }
257}
258
259impl TransportSpec {
260    /// Builds an automotive transport descriptor.
261    pub fn new(
262        name: impl Into<String>,
263        protocol: impl Into<String>,
264        lane: impl Into<String>,
265        read_capability: CapabilityName,
266        write_capability: CapabilityName,
267    ) -> Self {
268        Self {
269            name: name.into(),
270            protocol: protocol.into(),
271            lane: lane.into(),
272            read_capability,
273            write_capability,
274        }
275    }
276}
277
278/// Site-level automotive manifest.
279#[derive(Clone, Debug, PartialEq, Eq, Citizen)]
280#[citizen(symbol = "auto/SiteManifest", version = 0)]
281pub struct SiteManifest {
282    /// Site label.
283    pub site: String,
284    /// Modeled vehicle key the site describes.
285    pub vehicle: String,
286    /// Brand or workshop label.
287    pub brand: String,
288    /// Vehicle makes this site covers, or `*` for a multi-brand fallback.
289    pub makes: Vec<String>,
290    /// Lane names exposed by the site.
291    pub lanes: Vec<String>,
292    /// Transport names exposed by the site.
293    pub transports: Vec<String>,
294    /// Operation names exposed by the site.
295    pub operations: Vec<String>,
296    /// Explicit per-operation capability and effect policy.
297    #[citizen(with = "fields::op_caps_field")]
298    pub op_caps: Vec<OpCap>,
299    /// Capability ceiling this site may ever hold.
300    pub ceiling: Vec<CapabilityName>,
301}
302
303impl Default for SiteManifest {
304    fn default() -> Self {
305        site_manifest_example()
306    }
307}
308
309impl SiteManifest {
310    /// Builds an automotive site manifest.
311    pub fn new(
312        site: impl Into<String>,
313        vehicle: impl Into<String>,
314        brand: impl Into<String>,
315        lanes: Vec<String>,
316        transports: Vec<String>,
317        operations: Vec<String>,
318    ) -> Self {
319        let brand = brand.into();
320        Self {
321            site: site.into(),
322            vehicle: vehicle.into(),
323            makes: vec![brand.clone()],
324            brand,
325            lanes,
326            transports,
327            operations,
328            op_caps: Vec::new(),
329            ceiling: Vec::new(),
330        }
331    }
332
333    /// Replaces the make coverage set.
334    pub fn with_makes(mut self, makes: Vec<String>) -> Self {
335        self.makes = makes;
336        self
337    }
338
339    /// Replaces the explicit operation capability policy.
340    pub fn with_op_caps(mut self, op_caps: Vec<OpCap>) -> Self {
341        self.op_caps = op_caps;
342        self
343    }
344
345    /// Replaces the site capability ceiling.
346    pub fn with_ceiling(mut self, ceiling: Vec<CapabilityName>) -> Self {
347        self.ceiling = ceiling;
348        self
349    }
350}
351
352/// Standard diagnostics lane.
353pub fn diagnostic_lane() -> AutoLane {
354    auto_lane("diagnostics")
355}
356
357/// Standard telemetry lane.
358pub fn telemetry_lane() -> AutoLane {
359    auto_lane("telemetry")
360}
361
362/// Standard manifest lane.
363pub fn manifest_lane() -> AutoLane {
364    auto_lane("manifest")
365}
366
367/// Builds an open automotive lane.
368pub fn auto_lane(name: impl Into<String>) -> AutoLane {
369    AutoLane::new(name)
370}
371
372/// Standard diagnostic effect class.
373pub fn diagnostic_effect() -> EffectClass {
374    EffectClass::new("diagnostic-read")
375}
376
377/// Standard control effect class.
378pub fn control_effect() -> EffectClass {
379    EffectClass::new("control-write")
380}
381
382fn vehicle_id_example() -> VehicleId {
383    VehicleId::new("fixture", "vehicle-alpha")
384}
385
386fn dtc_example() -> Dtc {
387    Dtc::with_status(
388        "body",
389        "B0000",
390        "modeled diagnostic",
391        DtcStatus::from_byte(0x08),
392    )
393}
394
395fn brand_caps_example() -> BrandCaps {
396    BrandCaps::new(
397        "fixture-brand",
398        vec![
399            CapabilityName::new(AUTO_DIAGNOSTICS_READ),
400            CapabilityName::new(AUTO_TELEMETRY_READ),
401        ],
402    )
403}
404
405fn auto_lane_example() -> AutoLane {
406    diagnostic_lane()
407}
408
409fn effect_class_example() -> EffectClass {
410    diagnostic_effect()
411}
412
413fn op_cap_example() -> OpCap {
414    OpCap::new(
415        "diagnostics/read-dtc",
416        CapabilityName::new(AUTO_DIAGNOSTICS_READ),
417        "diagnostic-read",
418    )
419}
420
421fn transport_spec_example() -> TransportSpec {
422    TransportSpec::new(
423        "fixture-transport",
424        "modeled-bus",
425        "diagnostics",
426        CapabilityName::new(AUTO_TRANSPORT_CONNECT),
427        CapabilityName::new(AUTO_SERVICE_WRITE),
428    )
429}
430
431fn site_manifest_example() -> SiteManifest {
432    SiteManifest::new(
433        "fixture-site",
434        "vehicle-alpha",
435        "fixture-brand",
436        vec!["diagnostics".to_owned(), "telemetry".to_owned()],
437        vec!["fixture-transport".to_owned()],
438        vec!["diagnostics/read-dtc".to_owned()],
439    )
440}
441
442#[allow(dead_code)]
443fn capability_examples() -> [CapabilityName; 5] {
444    [
445        CapabilityName::new(AUTO_CONTROL_EXEC),
446        CapabilityName::new(AUTO_MANIFEST_READ),
447        CapabilityName::new(AUTO_SERVICE_WRITE),
448        CapabilityName::new(AUTO_ORDER),
449        CapabilityName::new(AUTO_TELEMETRY_READ),
450    ]
451}