Skip to main content

c_its_parser/standards/
conversions.rs

1// Copyright (c) 2025 consider it GmbH
2
3//! Conversions between ETSI ASN.1 values and common (SI) units
4//!
5//! Take a look at the individual data types in [`crate::standards`] to discover available conversion methods and initialization functions.
6
7pub const MPS_TO_KMH_FACTOR: f32 = 3.6;
8
9#[cfg(feature = "_cdd_1_3_1_1")]
10use crate::standards::cdd_1_3_1_1;
11#[cfg(feature = "_cdd_2_2_1")]
12use crate::standards::cdd_2_2_1;
13#[cfg(feature = "cpm_1")]
14use crate::standards::cpm_1;
15#[cfg(feature = "_dsrc_2_2_1")]
16use crate::standards::dsrc_2_2_1;
17
18/// Create conversions for ETSI type `t` and some "unavailable" value
19#[cfg(any(feature = "_cdd_1_3_1_1", feature = "_cdd_2_2_1"))]
20macro_rules! latlon_to_deg {
21    ($t:ty, $unavailable:expr) => {
22        impl $t {
23            /// convert ETSI Latitude/ Longitude to degrees
24            #[must_use]
25            pub fn as_deg(&self) -> f64 {
26                f64::from(self.0) / 10_000_000.
27            }
28
29            /// convert ETSI Latitude/ Longitude to degrees or `None` if "unavailable"
30            #[must_use]
31            pub fn try_as_deg(&self) -> Option<f64> {
32                if self.is_unavailable() {
33                    None
34                } else {
35                    Some(self.as_deg())
36                }
37            }
38
39            /// convert ETSI Latitude/ Longitude to degrees
40            #[must_use]
41            pub fn from_deg(other: f64) -> Self {
42                Self((other * 10_000_000.) as i32)
43            }
44
45            /// create ETSI type with "unavailable" value
46            pub fn unavailable() -> Self {
47                Self($unavailable)
48            }
49
50            /// determines if the ETSI value is special "unavailable" value
51            pub fn is_unavailable(&self) -> bool {
52                self.0 == $unavailable
53            }
54        }
55    };
56}
57
58#[cfg(feature = "_cdd_1_3_1_1")]
59latlon_to_deg!(cdd_1_3_1_1::its_container::Longitude, 1_800_000_001);
60#[cfg(feature = "_cdd_1_3_1_1")]
61latlon_to_deg!(cdd_1_3_1_1::its_container::Latitude, 900_000_001);
62#[cfg(feature = "_cdd_2_2_1")]
63latlon_to_deg!(cdd_2_2_1::etsi_its_cdd::Longitude, 1_800_000_001);
64#[cfg(feature = "_cdd_2_2_1")]
65latlon_to_deg!(cdd_2_2_1::etsi_its_cdd::Latitude, 900_000_001);
66
67#[cfg(feature = "_cdd_1_3_1_1")]
68latlon_to_deg!(cdd_1_3_1_1::its_container::DeltaLongitude, 131_072);
69#[cfg(feature = "_cdd_1_3_1_1")]
70latlon_to_deg!(cdd_1_3_1_1::its_container::DeltaLatitude, 131_072);
71#[cfg(feature = "_cdd_2_2_1")]
72latlon_to_deg!(cdd_2_2_1::etsi_its_cdd::DeltaLongitude, 131_072);
73#[cfg(feature = "_cdd_2_2_1")]
74latlon_to_deg!(cdd_2_2_1::etsi_its_cdd::DeltaLatitude, 131_072);
75
76/// Create conversions for ETSI type `t` (which has underlying data type `tt`) with conversion factor `conv`
77#[cfg(feature = "cpm_1")]
78macro_rules! etsi_to_meters {
79    ($t:ty, $tt:ty, $conv:expr) => {
80        impl $t {
81            /// convert ETSI data to meters
82            #[must_use]
83            pub fn as_meters(&self) -> f32 {
84                self.0 as f32 / $conv
85            }
86
87            /// create ETSI data from meters
88            ///
89            /// # Errors
90            /// human-readable string when input value is out of bounds
91            pub fn from_meters(value: f32) -> Result<Self, alloc::string::String> {
92                use rasn::AsnType;
93
94                #[allow(clippy::cast_possible_truncation)]
95                let etsi_val = (value * $conv) as $tt;
96
97                if let Some(constraints) = Self::CONSTRAINTS.value() {
98                    if !constraints.constraint.in_bound(&etsi_val) {
99                        return Err(alloc::format!("Value out of bounds"));
100                    }
101                }
102
103                Ok(Self(etsi_val))
104            }
105        }
106
107        impl From<&$t> for f32 {
108            fn from(other: &$t) -> f32 {
109                other.as_meters()
110            }
111        }
112        impl From<$t> for f32 {
113            fn from(other: $t) -> f32 {
114                other.as_meters()
115            }
116        }
117
118        impl TryFrom<f32> for $t {
119            type Error = alloc::string::String;
120
121            fn try_from(value: f32) -> Result<Self, Self::Error> {
122                Self::from_meters(value)
123            }
124        }
125    };
126}
127
128/// Create conversions for ETSI type `t` (which has underlying data type `tt`) with conversion factor `conv` and some "unavailable" value
129#[cfg(any(
130    feature = "_cdd_1_3_1_1",
131    feature = "_cdd_2_2_1",
132    feature = "_dsrc_2_2_1",
133    feature = "cpm_1"
134))]
135macro_rules! etsi_to_meters_unavailable {
136    ($t:ty, $tt:ty, $conv:expr, $unavailable:expr) => {
137        impl $t {
138            /// convert ETSI data to meters
139            #[must_use]
140            pub fn as_meters(&self) -> f32 {
141                self.0 as f32 / $conv
142            }
143
144            /// convert ETSI data to meters or `None` if "unavailable"
145            #[must_use]
146            pub fn try_as_meters(&self) -> Option<f32> {
147                if self.is_unavailable() {
148                    None
149                } else {
150                    Some(self.as_meters())
151                }
152            }
153
154            /// create ETSI data from meters
155            ///
156            /// # Errors
157            /// human-readable string when input value is out of bounds
158            pub fn from_meters(value: f32) -> Result<Self, alloc::string::String> {
159                use rasn::AsnType;
160
161                #[allow(clippy::cast_possible_truncation)]
162                let etsi_val = (value * $conv) as $tt;
163
164                if let Some(constraints) = Self::CONSTRAINTS.value() {
165                    if !constraints.constraint.in_bound(&etsi_val) {
166                        return Err(alloc::format!("Value out of bounds"));
167                    }
168                }
169
170                // Not all "unavailable" values are positive, but always at the very edge of the allowed value range.
171                // So by checking for constraints first, we can use a strict equals condition.
172                if etsi_val == $unavailable {
173                    return Err(alloc::format!("Value out of bounds"));
174                }
175
176                Ok(Self(etsi_val))
177            }
178
179            /// create ETSI type with "unavailable" value
180            pub fn unavailable() -> Self {
181                Self($unavailable)
182            }
183
184            /// determines if the ETSI value is special "unavailable" value
185            pub fn is_unavailable(&self) -> bool {
186                self.0 == $unavailable
187            }
188        }
189
190        impl From<&$t> for f32 {
191            fn from(other: &$t) -> f32 {
192                other.as_meters()
193            }
194        }
195        impl From<$t> for f32 {
196            fn from(other: $t) -> f32 {
197                other.as_meters()
198            }
199        }
200
201        impl TryFrom<f32> for $t {
202            type Error = alloc::string::String;
203
204            fn try_from(value: f32) -> Result<Self, Self::Error> {
205                Self::from_meters(value)
206            }
207        }
208    };
209}
210
211#[cfg(feature = "_dsrc_2_2_1")]
212etsi_to_meters_unavailable!(dsrc_2_2_1::etsi_its_dsrc::OffsetB09, i16, 100., -256);
213#[cfg(feature = "_dsrc_2_2_1")]
214etsi_to_meters_unavailable!(dsrc_2_2_1::etsi_its_dsrc::OffsetB10, i16, 100., -512);
215#[cfg(feature = "_dsrc_2_2_1")]
216etsi_to_meters_unavailable!(dsrc_2_2_1::etsi_its_dsrc::OffsetB11, i16, 100., -1024);
217#[cfg(feature = "_dsrc_2_2_1")]
218etsi_to_meters_unavailable!(dsrc_2_2_1::etsi_its_dsrc::OffsetB12, i16, 100., -2048);
219#[cfg(feature = "_dsrc_2_2_1")]
220etsi_to_meters_unavailable!(dsrc_2_2_1::etsi_its_dsrc::OffsetB13, i16, 100., -4096);
221#[cfg(feature = "_dsrc_2_2_1")]
222etsi_to_meters_unavailable!(dsrc_2_2_1::etsi_its_dsrc::OffsetB14, i16, 100., -8192);
223#[cfg(feature = "_dsrc_2_2_1")]
224etsi_to_meters_unavailable!(dsrc_2_2_1::etsi_its_dsrc::OffsetB16, i16, 100., -32768);
225
226#[cfg(feature = "cpm_1")]
227etsi_to_meters!(cpm_1::cpm_pdu_descriptions::DistanceValue, i32, 100.);
228#[cfg(feature = "_cdd_2_2_1")]
229etsi_to_meters_unavailable!(cdd_2_2_1::etsi_its_cdd::ObjectDimensionValue, u16, 10., 256);
230#[cfg(feature = "cpm_1")]
231etsi_to_meters!(cpm_1::cpm_pdu_descriptions::ObjectDimensionValue, u16, 10.);
232#[cfg(feature = "cpm_1")]
233etsi_to_meters!(cpm_1::cpm_pdu_descriptions::Radius, u16, 10.);
234#[cfg(feature = "cpm_1")]
235etsi_to_meters!(cpm_1::cpm_pdu_descriptions::Range, u16, 10.);
236#[cfg(feature = "cpm_1")]
237etsi_to_meters!(cpm_1::cpm_pdu_descriptions::SemiRangeLength, u16, 10.);
238
239#[cfg(feature = "_cdd_1_3_1_1")]
240etsi_to_meters_unavailable!(cdd_1_3_1_1::its_container::VehicleWidth, u8, 10., 62); // Unit: 0,1 metre
241#[cfg(feature = "_cdd_2_2_1")]
242etsi_to_meters_unavailable!(cdd_2_2_1::etsi_its_cdd::VehicleWidth, u8, 10., 62); // Unit: 0,1 metre
243#[cfg(feature = "_cdd_1_3_1_1")]
244etsi_to_meters_unavailable!(
245    cdd_1_3_1_1::its_container::VehicleLengthValue,
246    u16,
247    10.,
248    1023
249); // Unit: 0,1 metre
250#[cfg(feature = "_cdd_2_2_1")]
251etsi_to_meters_unavailable!(cdd_2_2_1::etsi_its_cdd::VehicleLengthValue, u16, 10., 1023); // Unit: 0,1 metre
252
253#[cfg(feature = "_dsrc_2_2_1")]
254etsi_to_meters_unavailable!(dsrc_2_2_1::etsi_its_dsrc::VehicleHeight, u8, 20., 127); // Unit: 0,05 metre
255#[cfg(feature = "_cdd_2_2_1")]
256etsi_to_meters_unavailable!(cdd_2_2_1::etsi_its_cdd::VehicleHeight, u8, 20., 127); // Unit: 0,05 metre
257
258#[cfg(feature = "_cdd_1_3_1_1")]
259etsi_to_meters_unavailable!(cdd_1_3_1_1::its_container::SemiAxisLength, u16, 100., 4095); // Unit: 0,01 metre
260#[cfg(feature = "_cdd_2_2_1")]
261etsi_to_meters_unavailable!(cdd_2_2_1::etsi_its_cdd::SemiAxisLength, u16, 100., 4095); // Unit: 0,01 metre
262
263#[cfg(feature = "_cdd_1_3_1_1")]
264etsi_to_meters_unavailable!(cdd_1_3_1_1::its_container::AltitudeValue, i32, 100., 800001); // Unit: 0,01 metre
265#[cfg(feature = "_cdd_2_2_1")]
266etsi_to_meters_unavailable!(cdd_2_2_1::etsi_its_cdd::AltitudeValue, i32, 100., 800001); // Unit: 0,01 metre
267
268#[cfg(feature = "_cdd_1_3_1_1")]
269etsi_to_meters_unavailable!(cdd_1_3_1_1::its_container::DeltaAltitude, i16, 100., 12800); // Unit: 0,01 metre
270#[cfg(feature = "_cdd_2_2_1")]
271etsi_to_meters_unavailable!(cdd_2_2_1::etsi_its_cdd::DeltaAltitude, i16, 100., 12800); // Unit: 0,01 metre
272
273#[cfg(feature = "_cdd_1_3_1_1")]
274etsi_to_meters_unavailable!(cdd_1_3_1_1::its_container::HeightLonCarr, u8, 100., 100); // Unit: 0,01 metre
275#[cfg(feature = "_cdd_2_2_1")]
276etsi_to_meters_unavailable!(cdd_2_2_1::etsi_its_cdd::HeightLonCarr, u8, 100., 100); // Unit: 0,01 metre
277
278#[cfg(feature = "_cdd_1_3_1_1")]
279etsi_to_meters_unavailable!(cdd_1_3_1_1::its_container::PosLonCarr, u8, 100., 127); // Unit: 0,01 metre
280#[cfg(feature = "_cdd_2_2_1")]
281etsi_to_meters_unavailable!(cdd_2_2_1::etsi_its_cdd::PosLonCarr, u8, 100., 127); // Unit: 0,01 metre
282
283#[cfg(feature = "_cdd_1_3_1_1")]
284etsi_to_meters_unavailable!(cdd_1_3_1_1::its_container::PosFrontAx, u8, 100., 20); // Unit: 0,01 metre
285#[cfg(feature = "_cdd_2_2_1")]
286etsi_to_meters_unavailable!(cdd_2_2_1::etsi_its_cdd::PosFrontAx, u8, 100., 20); // Unit: 0,01 metre
287
288#[cfg(feature = "_cdd_1_3_1_1")]
289etsi_to_meters_unavailable!(cdd_1_3_1_1::its_container::PosPillar, u8, 100., 30); // Unit: 0,01 metre
290#[cfg(feature = "_cdd_2_2_1")]
291etsi_to_meters_unavailable!(cdd_2_2_1::etsi_its_cdd::PosPillar, u8, 100., 30); // Unit: 0,01 metre
292
293#[cfg(feature = "_cdd_1_3_1_1")]
294etsi_to_meters_unavailable!(cdd_1_3_1_1::its_container::WheelBaseVehicle, u8, 100., 127); // Unit: 0,01 metre
295#[cfg(feature = "_cdd_2_2_1")]
296etsi_to_meters_unavailable!(cdd_2_2_1::etsi_its_cdd::WheelBaseVehicle, u8, 100., 127); // Unit: 0,01 metre
297
298#[cfg(feature = "_cdd_1_3_1_1")]
299etsi_to_meters_unavailable!(cdd_1_3_1_1::its_container::TurningRadius, u8, 2.5, 255); // Unit: 0,4 metre
300#[cfg(feature = "_cdd_2_2_1")]
301etsi_to_meters_unavailable!(cdd_2_2_1::etsi_its_cdd::TurningRadius, u8, 2.5, 255); // Unit: 0,4 metre
302
303#[cfg(feature = "_cdd_2_2_1")] // CPM v2
304etsi_to_meters_unavailable!(
305    cdd_2_2_1::etsi_its_cdd::CoordinateConfidence,
306    u16,
307    100.,
308    4096
309); // Unit: 0,01 metre
310
311#[cfg(feature = "cpm_1")]
312etsi_to_meters!(
313    cpm_1::cpm_pdu_descriptions::LongitudinalLanePositionValue,
314    u16,
315    10.
316); // Unit: 0,1 metre
317#[cfg(feature = "_cdd_2_2_1")]
318etsi_to_meters_unavailable!(
319    cdd_2_2_1::etsi_its_cdd::LongitudinalLanePositionValue,
320    u16,
321    10.,
322    32767
323); // Unit: 0,1 metre
324
325#[cfg(feature = "cpm_1")]
326etsi_to_meters_unavailable!(
327    cpm_1::cpm_pdu_descriptions::LongitudinalLanePositionConfidence,
328    u8,
329    100.,
330    102
331); // Unit: 0.01 metre
332#[cfg(feature = "_cdd_2_2_1")]
333etsi_to_meters_unavailable!(
334    cdd_2_2_1::etsi_its_cdd::LongitudinalLanePositionConfidence,
335    u16,
336    10.,
337    1023
338); // Unit: 0,1 metre
339
340#[cfg(feature = "cpm_1")]
341etsi_to_meters_unavailable!(
342    cpm_1::cpm_pdu_descriptions::ObjectDimensionConfidence,
343    u8,
344    100.,
345    102
346); // Unit: 0.01 metre
347#[cfg(feature = "_cdd_2_2_1")]
348etsi_to_meters_unavailable!(
349    cdd_2_2_1::etsi_its_cdd::ObjectDimensionConfidence,
350    u8,
351    10.,
352    32
353); // Unit: 0,1 metre
354
355#[cfg(feature = "_cdd_2_2_1")] // DENM v2
356etsi_to_meters_unavailable!(cdd_2_2_1::etsi_its_cdd::Position1d, i16, 1., 8191); // Unit: 1 metre
357
358/// Create conversions for ETSI type `t` (which has underlying data type `tt`) with conversion factor `conv` and some "unavailable" value
359#[cfg(any(feature = "cpm_1", feature = "_cdd_1_3_1_1", feature = "_cdd_2_2_1"))]
360macro_rules! etsi_to_mps {
361    ($t:ty, $tt:ty, $conv:expr, $unavailable:expr) => {
362        impl $t {
363            /// convert ETSI speed to m/s
364            #[must_use]
365            pub fn as_mps(&self) -> f32 {
366                f32::from(self.0) / $conv
367            }
368
369            /// convert ETSI speed to m/s or `None` if "unavailable"
370            #[must_use]
371            pub fn try_as_mps(&self) -> Option<f32> {
372                if self.is_unavailable() {
373                    None
374                } else {
375                    Some(self.as_mps())
376                }
377            }
378
379            /// create ETSI speed from m/s
380            ///
381            /// # Errors
382            /// human-readable string when input value is out of bounds
383            pub fn from_mps(value: f32) -> Result<Self, alloc::string::String> {
384                use rasn::AsnType;
385
386                #[allow(clippy::cast_possible_truncation)]
387                let etsi_val = (value * $conv) as $tt;
388
389                if let Some(constraints) = Self::CONSTRAINTS.value() {
390                    if !constraints.constraint.in_bound(&etsi_val) {
391                        return Err(alloc::format!("Value out of bounds"));
392                    }
393                }
394
395                // Not all "unavailable" values are positive, but always at the very edge of the allowed value range.
396                // So by checking for constraints first, we can use a strict equals condition.
397                if etsi_val == $unavailable {
398                    return Err(alloc::format!("Value out of bounds"));
399                }
400
401                Ok(Self(etsi_val))
402            }
403
404            /// convert ETSI speed to km/h
405            #[must_use]
406            pub fn as_kmh(&self) -> f32 {
407                self.as_mps() * MPS_TO_KMH_FACTOR
408            }
409
410            /// convert ETSI speed to km/h or `None` if "unavailable"
411            #[must_use]
412            pub fn try_as_kmh(&self) -> Option<f32> {
413                if self.is_unavailable() {
414                    None
415                } else {
416                    Some(self.as_kmh())
417                }
418            }
419
420            /// create ETSI speed from km/h
421            ///
422            /// # Errors
423            /// human-readable string when input value is out of bounds
424            pub fn from_kmh(value: f32) -> Result<Self, alloc::string::String> {
425                Self::from_mps(value / MPS_TO_KMH_FACTOR)
426            }
427
428            /// create ETSI type with "unavailable" value
429            pub fn unavailable() -> Self {
430                Self($unavailable)
431            }
432
433            /// determines if the ETSI value is special "unavailable" value
434            pub fn is_unavailable(&self) -> bool {
435                self.0 == $unavailable
436            }
437        }
438
439        impl From<&$t> for f32 {
440            fn from(other: &$t) -> f32 {
441                other.as_mps()
442            }
443        }
444        impl From<$t> for f32 {
445            fn from(other: $t) -> f32 {
446                other.as_mps()
447            }
448        }
449
450        impl TryFrom<f32> for $t {
451            type Error = alloc::string::String;
452
453            fn try_from(value: f32) -> Result<Self, Self::Error> {
454                Self::from_mps(value)
455            }
456        }
457    };
458}
459
460#[cfg(feature = "cpm_1")]
461etsi_to_mps!(
462    cpm_1::cpm_pdu_descriptions::SpeedValueExtended,
463    i16,
464    100.,
465    16_383
466); // Unit: 0,01 m/s
467#[cfg(feature = "_cdd_1_3_1_1")]
468etsi_to_mps!(cdd_1_3_1_1::its_container::SpeedValue, u16, 100., 16_383); // Unit: 0,01 m/s
469#[cfg(feature = "_cdd_2_2_1")]
470etsi_to_mps!(cdd_2_2_1::etsi_its_cdd::SpeedValue, u16, 100., 16_383); // Unit: 0,01 m/s
471
472#[cfg(feature = "_cdd_2_2_1")]
473etsi_to_mps!(
474    cdd_2_2_1::etsi_its_cdd::VelocityComponentValue,
475    i16,
476    100.,
477    16_383
478); // Unit: 0,01 m/s
479
480#[cfg(feature = "_cdd_1_3_1_1")]
481etsi_to_mps!(cdd_1_3_1_1::its_container::SpeedConfidence, u8, 100., 127); // Unit: 0,01 m/s
482#[cfg(feature = "_cdd_2_2_1")]
483etsi_to_mps!(cdd_2_2_1::etsi_its_cdd::SpeedConfidence, u8, 100., 127); // Unit: 0,01 m/s
484
485#[cfg(feature = "_dsrc_2_2_1")]
486etsi_to_mps!(dsrc_2_2_1::etsi_its_dsrc::Velocity, u16, 50., 8191); // Unit: 0.02 m/s
487
488/// Create conversions for ETSI type `t` (which has underlying data type `tt`) with conversion factor `conv` and some "unavailable" value
489#[cfg(any(feature = "_cdd_1_3_1_1", feature = "_cdd_2_2_1"))]
490macro_rules! etsi_to_mpss {
491    ($t:ty, $tt:ty, $conv:expr, $unavailable:expr) => {
492        impl $t {
493            /// convert ETSI acceleration to m/s/s
494            #[must_use]
495            pub fn as_mpss(&self) -> f32 {
496                f32::from(self.0) / $conv
497            }
498
499            /// convert ETSI acceleration to m/s/s or `None` if "unavailable"
500            #[must_use]
501            pub fn try_as_mpss(&self) -> Option<f32> {
502                if self.is_unavailable() {
503                    None
504                } else {
505                    Some(self.as_mpss())
506                }
507            }
508
509            /// create ETSI acceleration from m/s/s
510            ///
511            /// # Errors
512            /// human-readable string when input value is out of bounds
513            pub fn from_mpss(value: f32) -> Result<Self, alloc::string::String> {
514                use rasn::AsnType;
515
516                #[allow(clippy::cast_possible_truncation)]
517                let etsi_val = (value * $conv) as $tt;
518
519                if let Some(constraints) = Self::CONSTRAINTS.value() {
520                    if !constraints.constraint.in_bound(&etsi_val) {
521                        return Err(alloc::format!("Value out of bounds"));
522                    }
523                }
524
525                // Not all "unavailable" values are positive, but always at the very edge of the allowed value range.
526                // So by checking for constraints first, we can use a strict equals condition.
527                if etsi_val == $unavailable {
528                    return Err(alloc::format!("Value out of bounds"));
529                }
530
531                Ok(Self(etsi_val))
532            }
533
534            /// create ETSI type with "unavailable" value
535            pub fn unavailable() -> Self {
536                Self($unavailable)
537            }
538
539            /// determines if the ETSI value is special "unavailable" value
540            pub fn is_unavailable(&self) -> bool {
541                self.0 == $unavailable
542            }
543        }
544
545        impl From<&$t> for f32 {
546            fn from(other: &$t) -> f32 {
547                other.as_mpss()
548            }
549        }
550        impl From<$t> for f32 {
551            fn from(other: $t) -> f32 {
552                other.as_mpss()
553            }
554        }
555
556        impl TryFrom<f32> for $t {
557            type Error = alloc::string::String;
558
559            fn try_from(value: f32) -> Result<Self, Self::Error> {
560                Self::from_mpss(value)
561            }
562        }
563    };
564}
565
566#[cfg(feature = "_cdd_1_3_1_1")]
567etsi_to_mpss!(
568    cdd_1_3_1_1::its_container::LongitudinalAccelerationValue,
569    i16,
570    10.,
571    161
572); // Unit: 0,1 m/s^2
573#[cfg(feature = "_cdd_2_2_1")]
574etsi_to_mpss!(
575    cdd_2_2_1::etsi_its_cdd::LongitudinalAccelerationValue,
576    i16,
577    10.,
578    161
579); // Unit: 0,1 m/s^2
580#[cfg(feature = "_cdd_1_3_1_1")]
581etsi_to_mpss!(
582    cdd_1_3_1_1::its_container::LateralAccelerationValue,
583    i16,
584    10.,
585    161
586); // Unit: 0,1 m/s^2
587#[cfg(feature = "_cdd_2_2_1")]
588etsi_to_mpss!(
589    cdd_2_2_1::etsi_its_cdd::LateralAccelerationValue,
590    i16,
591    10.,
592    161
593); // Unit: 0,1 m/s^2
594#[cfg(feature = "_cdd_2_2_1")]
595etsi_to_mpss!(cdd_2_2_1::etsi_its_cdd::AccelerationValue, i16, 10., 161); // Unit: 0,1 m/s^2
596
597#[cfg(feature = "_cdd_1_3_1_1")]
598etsi_to_mpss!(
599    cdd_1_3_1_1::its_container::VerticalAccelerationValue,
600    i16,
601    10.,
602    161
603); // Unit: 0,1 m/s^2
604#[cfg(feature = "_cdd_2_2_1")]
605etsi_to_mpss!(
606    cdd_2_2_1::etsi_its_cdd::VerticalAccelerationValue,
607    i16,
608    10.,
609    161
610); // Unit: 0,1 m/s^2
611
612#[cfg(feature = "_cdd_2_2_1")]
613etsi_to_mpss!(
614    cdd_2_2_1::etsi_its_cdd::AccelerationMagnitudeValue,
615    u8,
616    10.,
617    161
618); // Unit: 0,1 m/s^2
619
620#[cfg(feature = "_cdd_1_3_1_1")]
621etsi_to_mpss!(
622    cdd_1_3_1_1::its_container::AccelerationConfidence,
623    u8,
624    10.,
625    102
626); // Unit: 0,1 m/s^2
627#[cfg(feature = "_cdd_2_2_1")]
628etsi_to_mpss!(
629    cdd_2_2_1::etsi_its_cdd::AccelerationConfidence,
630    u8,
631    10.,
632    102
633); // Unit: 0,1 m/s^2
634
635/// Check for unavailable data of ETSI type `t` (which has underlying data type `tt`)
636#[cfg(any(feature = "_cdd_1_3_1_1", feature = "_cdd_2_2_1"))]
637macro_rules! etsi_raw_unavailable {
638    ($t:ty, $tt:ty, $unavailable:expr) => {
639        impl $t {
640            /// convert ETSI acceleration to m/s/s or `None` if "unavailable"
641            #[must_use]
642            pub fn try_as_raw(&self) -> Option<$tt> {
643                if self.is_unavailable() {
644                    None
645                } else {
646                    Some(self.0)
647                }
648            }
649
650            /// create ETSI acceleration from raw value
651            ///
652            /// # Errors
653            /// human-readable string when input value is out of bounds
654            pub fn from_raw(value: $tt) -> Result<Self, alloc::string::String> {
655                use rasn::AsnType;
656
657                if let Some(constraints) = Self::CONSTRAINTS.value() {
658                    if !constraints.constraint.in_bound(&value) {
659                        return Err(alloc::format!("Value out of bounds"));
660                    }
661                }
662
663                // Not all "unavailable" values are positive, but always at the very edge of the allowed value range.
664                // So by checking for constraints first, we can use a strict equals condition.
665                if value == $unavailable {
666                    return Err(alloc::format!("Value out of bounds"));
667                }
668
669                Ok(Self(value))
670            }
671
672            /// create ETSI type with "unavailable" value
673            pub fn unavailable() -> Self {
674                Self($unavailable)
675            }
676
677            /// determines if the ETSI value is special "unavailable" value
678            pub fn is_unavailable(&self) -> bool {
679                self.0 == $unavailable
680            }
681        }
682
683        impl From<&$t> for $tt {
684            fn from(other: &$t) -> $tt {
685                other.0
686            }
687        }
688        impl From<$t> for $tt {
689            fn from(other: $t) -> $tt {
690                other.0
691            }
692        }
693
694        impl TryFrom<$tt> for $t {
695            type Error = alloc::string::String;
696
697            fn try_from(value: $tt) -> Result<Self, Self::Error> {
698                Self::from_raw(value)
699            }
700        }
701    };
702}
703
704#[cfg(feature = "_cdd_1_3_1_1")]
705etsi_raw_unavailable!(cdd_1_3_1_1::its_container::CurvatureValue, i16, 1023);
706#[cfg(feature = "_cdd_2_2_1")]
707etsi_raw_unavailable!(cdd_2_2_1::etsi_its_cdd::CurvatureValue, i16, 1023);
708
709#[cfg(feature = "_cdd_2_2_1")]
710etsi_raw_unavailable!(cdd_2_2_1::etsi_its_cdd::ConfidenceLevel, u8, 101); // Unit: percent
711#[cfg(feature = "_cdd_2_2_1")]
712etsi_raw_unavailable!(cdd_2_2_1::etsi_its_cdd::CorrelationCellValue, i8, 101); // Unit: the value is scaled by 100
713#[cfg(feature = "_cdd_2_2_1")]
714etsi_raw_unavailable!(cdd_2_2_1::etsi_its_cdd::NumberOfOccupants, u8, 127); // Unit: 1 person
715
716#[cfg(feature = "_cdd_2_2_1")]
717etsi_raw_unavailable!(cdd_2_2_1::etsi_its_cdd::StabilityLossProbability, u8, 63); // Unit: 2 %
718#[cfg(feature = "_cdd_2_2_1")]
719etsi_raw_unavailable!(
720    cdd_2_2_1::etsi_its_cdd::TrajectoryInterceptionProbability,
721    u8,
722    63
723); // Unit: 2 %
724
725/// Create conversions for ETSI type `t` with conversion factor `conv` and some "unavailable" value
726#[cfg(any(
727    feature = "cpm_1",
728    feature = "_cdd_1_3_1_1",
729    feature = "_cdd_2_2_1",
730    feature = "_dsrc_2_2_1"
731))]
732macro_rules! angle_to_deg {
733    ($t:ty, $tt:ty, $conv:expr, $unavailable:expr) => {
734        impl $t {
735            /// convert ETSI WGS84AngleValue/ CartesianAngleValue to degrees
736            #[must_use]
737            pub fn as_deg(&self) -> f32 {
738                f32::from(self.0) / $conv
739            }
740
741            /// convert ETSI WGS84AngleValue/ CartesianAngleValue to degrees or `None` if "unavailable"
742            #[must_use]
743            pub fn try_as_deg(&self) -> Option<f32> {
744                if self.is_unavailable() {
745                    None
746                } else {
747                    Some(self.as_deg())
748                }
749            }
750
751            /// create ETSI WGS84AngleValue/ CartesianAngleValue from degrees
752            ///
753            /// # Errors
754            /// human-readable string when input value is out of bounds
755            pub fn from_deg(value: f32) -> Result<Self, alloc::string::String> {
756                use rasn::AsnType;
757
758                #[allow(clippy::cast_possible_truncation)]
759                let etsi_val = (value * $conv) as $tt;
760
761                if let Some(constraints) = Self::CONSTRAINTS.value() {
762                    if !constraints.constraint.in_bound(&etsi_val) {
763                        return Err(alloc::format!("Value out of bounds"));
764                    }
765                }
766
767                // Not all "unavailable" values are positive, but always at the very edge of the allowed value range.
768                // So by checking for constraints first, we can use a strict equals condition.
769                if etsi_val == $unavailable {
770                    return Err(alloc::format!("Value out of bounds"));
771                }
772
773                Ok(Self(etsi_val))
774            }
775
776            /// create ETSI type with "unavailable" value
777            pub fn unavailable() -> Self {
778                Self($unavailable)
779            }
780
781            /// determines if the ETSI value is special "unavailable" value
782            pub fn is_unavailable(&self) -> bool {
783                self.0 == $unavailable
784            }
785        }
786
787        impl From<&$t> for f32 {
788            fn from(other: &$t) -> f32 {
789                other.as_deg()
790            }
791        }
792        impl From<$t> for f32 {
793            fn from(other: $t) -> f32 {
794                other.as_deg()
795            }
796        }
797
798        impl TryFrom<f32> for $t {
799            type Error = alloc::string::String;
800
801            fn try_from(value: f32) -> Result<Self, Self::Error> {
802                Self::from_deg(value)
803            }
804        }
805    };
806}
807
808#[cfg(feature = "_cdd_2_2_1")]
809angle_to_deg!(cdd_2_2_1::etsi_its_cdd::CartesianAngleValue, u16, 10., 3601); // Unit: 0,1 degrees
810#[cfg(feature = "cpm_1")]
811angle_to_deg!(
812    cpm_1::cpm_pdu_descriptions::CartesianAngleValue,
813    u16,
814    10.,
815    3601
816); // Unit: 0,1 degrees
817#[cfg(feature = "cpm_1")]
818angle_to_deg!(cpm_1::cpm_pdu_descriptions::WGS84AngleValue, u16, 10., 3601); // Unit: 0,1 degrees
819#[cfg(feature = "_cdd_2_2_1")]
820angle_to_deg!(cdd_2_2_1::etsi_its_cdd::Wgs84AngleValue, u16, 10., 3601); // Unit: 0,1 degrees
821#[cfg(feature = "_dsrc_2_2_1")]
822angle_to_deg!(dsrc_2_2_1::etsi_its_dsrc::Angle, u16, 80., 28800); // Unit: 0.0125 degrees
823#[cfg(feature = "_cdd_2_2_1")]
824angle_to_deg!(cdd_2_2_1::etsi_its_cdd::HeadingValue, u16, 10., 3601); // Unit: 0,1 degree
825#[cfg(feature = "_cdd_1_3_1_1")]
826angle_to_deg!(cdd_1_3_1_1::its_container::HeadingValue, u16, 10., 3601); // Unit: 0,1 degree
827#[cfg(feature = "_cdd_2_2_1")]
828angle_to_deg!(cdd_2_2_1::etsi_its_cdd::HeadingConfidence, u8, 10., 127); // Unit: 0,1 degree
829#[cfg(feature = "_cdd_1_3_1_1")]
830angle_to_deg!(cdd_1_3_1_1::its_container::HeadingConfidence, u8, 10., 127); // Unit: 0,1 degree
831
832#[cfg(feature = "_cdd_2_2_1")]
833angle_to_deg!(cdd_2_2_1::etsi_its_cdd::AngleConfidence, u8, 10., 127); // Unit: 0,1 degrees
834#[cfg(feature = "cpm_1")]
835angle_to_deg!(cpm_1::cpm_pdu_descriptions::AngleConfidence, u8, 10., 127); // Unit: 0,1 degrees
836
837#[cfg(feature = "_cdd_2_2_1")]
838angle_to_deg!(
839    cdd_2_2_1::etsi_its_cdd::SteeringWheelAngleValue,
840    i16,
841    (1. / 1.5),
842    512
843); // Unit: 1,5 degree
844#[cfg(feature = "_cdd_1_3_1_1")]
845angle_to_deg!(
846    cdd_1_3_1_1::its_container::SteeringWheelAngleValue,
847    i16,
848    (1. / 1.5),
849    512
850); // Unit: 1,5 degree
851
852#[cfg(feature = "_cdd_2_2_1")]
853angle_to_deg!(
854    cdd_2_2_1::etsi_its_cdd::SteeringWheelAngleConfidence,
855    u8,
856    (1. / 1.5),
857    127
858); // Unit: 1,5 degree
859#[cfg(feature = "_cdd_1_3_1_1")]
860angle_to_deg!(
861    cdd_1_3_1_1::its_container::SteeringWheelAngleConfidence,
862    u8,
863    (1. / 1.5),
864    127
865); // Unit: 1,5 degree
866
867/// Create conversions for ETSI type `t` with conversion factor `conv` and some "unavailable" value
868#[cfg(any(feature = "_cdd_2_2_1", feature = "_cdd_1_3_1_1"))]
869macro_rules! angle_to_degrate {
870    ($t:ty, $conv:expr, $unavailable:expr) => {
871        impl $t {
872            /// convert ETSI YawRateValue to degrees per second
873            #[must_use]
874            pub fn as_deg_rate(&self) -> f32 {
875                f32::from(self.0) / $conv
876            }
877
878            /// convert ETSI YawRateValue to degrees per second or `None` if "unavailable"
879            #[must_use]
880            pub fn try_as_deg_rate(&self) -> Option<f32> {
881                if self.is_unavailable() {
882                    None
883                } else {
884                    Some(self.as_deg_rate())
885                }
886            }
887
888            /// create ETSI YawRateValue from degrees per second
889            ///
890            /// # Errors
891            /// human-readable string when input value is out of bounds
892            pub fn from_deg_rate(value: f32) -> Result<Self, alloc::string::String> {
893                use rasn::AsnType;
894
895                #[allow(clippy::cast_possible_truncation)]
896                let etsi_val = (value * $conv) as i16;
897
898                if let Some(constraints) = Self::CONSTRAINTS.value() {
899                    if !constraints.constraint.in_bound(&etsi_val) {
900                        return Err(alloc::format!("Value out of bounds"));
901                    }
902                }
903
904                // Not all "unavailable" values are positive, but always at the very edge of the allowed value range.
905                // So by checking for constraints first, we can use a strict equals condition.
906                if etsi_val == $unavailable {
907                    return Err(alloc::format!("Value out of bounds"));
908                }
909
910                Ok(Self(etsi_val))
911            }
912
913            /// create ETSI type with "unavailable" value
914            pub fn unavailable() -> Self {
915                Self($unavailable)
916            }
917
918            /// determines if the ETSI value is special "unavailable" value
919            pub fn is_unavailable(&self) -> bool {
920                self.0 == $unavailable
921            }
922        }
923
924        impl From<&$t> for f32 {
925            fn from(other: &$t) -> f32 {
926                other.as_deg_rate()
927            }
928        }
929        impl From<$t> for f32 {
930            fn from(other: $t) -> f32 {
931                other.as_deg_rate()
932            }
933        }
934
935        impl TryFrom<f32> for $t {
936            type Error = alloc::string::String;
937
938            fn try_from(value: f32) -> Result<Self, Self::Error> {
939                Self::from_deg_rate(value)
940            }
941        }
942    };
943}
944
945#[cfg(feature = "_cdd_2_2_1")]
946angle_to_degrate!(cdd_2_2_1::etsi_its_cdd::YawRateValue, 100., 32767); // Unit: 0,01 degree per second
947#[cfg(feature = "_cdd_1_3_1_1")]
948angle_to_degrate!(cdd_1_3_1_1::its_container::YawRateValue, 100., 32767); // Unit: 0,01 degree per second
949
950#[cfg(feature = "_cdd_2_2_1")]
951angle_to_degrate!(
952    cdd_2_2_1::etsi_its_cdd::CartesianAngularVelocityComponentValue,
953    1.,
954    256
955); // Unit: degree/s
956
957// DeltaTime: unit 10 seconds, clamping to -121 for <-20 minutes and +120 for >+20 minutes, -122 for unavailable
958#[cfg(feature = "_dsrc_2_2_1")]
959impl dsrc_2_2_1::etsi_its_dsrc::DeltaTime {
960    /// convert ETSI DeltaTime to seconds
961    #[must_use]
962    pub fn as_sec(&self) -> i16 {
963        i16::from(self.0) * 10
964    }
965
966    /// convert ETSI DeltaTime to seconds or `None` if "unavailable"
967    #[must_use]
968    pub fn try_as_sec(&self) -> Option<i16> {
969        if self.is_unavailable() {
970            None
971        } else {
972            Some(self.as_sec())
973        }
974    }
975
976    /// create ETSI DeltaTime from seconds, clamping at min. and max. bounds
977    #[must_use]
978    pub fn from_sec(value: i16) -> Self {
979        #[allow(clippy::cast_possible_truncation)]
980        let etsi_val = (value / 10) as i8;
981
982        Self(etsi_val.clamp(-121, 120))
983    }
984
985    /// create ETSI type with "unavailable" value
986    pub fn unavailable() -> Self {
987        Self(-122)
988    }
989
990    /// determines if the ETSI value is special "unavailable" value
991    pub fn is_unavailable(&self) -> bool {
992        self.0 == -122
993    }
994}
995
996#[cfg(feature = "_dsrc_2_2_1")]
997impl From<&dsrc_2_2_1::etsi_its_dsrc::DeltaTime> for i16 {
998    fn from(other: &dsrc_2_2_1::etsi_its_dsrc::DeltaTime) -> i16 {
999        other.as_sec()
1000    }
1001}
1002#[cfg(feature = "_dsrc_2_2_1")]
1003impl From<dsrc_2_2_1::etsi_its_dsrc::DeltaTime> for i16 {
1004    fn from(other: dsrc_2_2_1::etsi_its_dsrc::DeltaTime) -> i16 {
1005        other.as_sec()
1006    }
1007}
1008
1009// DSecond: unit milliseconds, 65535 for unavailable
1010#[cfg(feature = "_dsrc_2_2_1")]
1011impl dsrc_2_2_1::etsi_its_dsrc::DSecond {
1012    /// convert ETSI DeltaTime to milliseconds
1013    #[must_use]
1014    pub fn as_millis(&self) -> u16 {
1015        self.0
1016    }
1017
1018    /// convert ETSI DeltaTime to milliseconds or `None` if "unavailable"
1019    #[must_use]
1020    pub fn try_as_millis(&self) -> Option<u16> {
1021        if self.is_unavailable() {
1022            None
1023        } else {
1024            Some(self.as_millis())
1025        }
1026    }
1027
1028    /// create ETSI DSecond from milliseconds
1029    ///
1030    /// # Errors
1031    /// human-readable string when input value is out of bounds
1032    pub fn from_millis(value: u16) -> Result<Self, alloc::string::String> {
1033        // ASN.1 bounds are bigger than allowed values (0..59999 for normal values, 60000..60999 for leap seconds)
1034
1035        if value > 60999 {
1036            return Err(alloc::format!("Value out of bounds"));
1037        }
1038
1039        Ok(Self(value))
1040    }
1041
1042    /// create ETSI type with "unavailable" value
1043    pub fn unavailable() -> Self {
1044        Self(65535)
1045    }
1046
1047    /// determines if the ETSI value is special "unavailable" value
1048    pub fn is_unavailable(&self) -> bool {
1049        self.0 == 65535
1050    }
1051}
1052
1053#[cfg(feature = "_dsrc_2_2_1")]
1054impl From<&dsrc_2_2_1::etsi_its_dsrc::DSecond> for u16 {
1055    fn from(other: &dsrc_2_2_1::etsi_its_dsrc::DSecond) -> u16 {
1056        other.as_millis()
1057    }
1058}
1059#[cfg(feature = "_dsrc_2_2_1")]
1060impl From<dsrc_2_2_1::etsi_its_dsrc::DSecond> for u16 {
1061    fn from(other: dsrc_2_2_1::etsi_its_dsrc::DSecond) -> u16 {
1062        other.as_millis()
1063    }
1064}
1065
1066// TimeMark: unit 1/10 of a second, 36001 for unknown, 36000 for out-of-range
1067#[cfg(feature = "_dsrc_2_2_1")]
1068impl dsrc_2_2_1::etsi_its_dsrc::TimeMark {
1069    const CONV_FACTOR: u32 = 100;
1070    const UNKNOWN: u16 = 36001;
1071    const OUT_OF_RANGE: u16 = 36000;
1072
1073    /// convert ETSI TimeMark to milliseconds
1074    #[must_use]
1075    pub fn as_millis(&self) -> u32 {
1076        self.0 as u32 * Self::CONV_FACTOR
1077    }
1078
1079    /// convert ETSI TimeMark to milliseconds or `None` if "unknown"
1080    #[must_use]
1081    pub fn try_as_millis(&self) -> Option<u32> {
1082        if self.is_unknown() {
1083            None
1084        } else {
1085            Some(self.as_millis())
1086        }
1087    }
1088
1089    /// create ETSI TimeMark from milliseconds
1090    ///
1091    /// # Errors
1092    /// human-readable string when input value is out of bounds
1093    pub fn from_millis(value: u32) -> Result<Self, alloc::string::String> {
1094        #[allow(clippy::cast_possible_truncation)]
1095        let etsi_val = (value / Self::CONV_FACTOR) as u16;
1096
1097        // ASN.1 bounds are bigger than allowed values (0..35990 for normal values, 35991..35999 for leap seconds)
1098        if etsi_val > 35999 {
1099            return Err(alloc::format!("Value out of bounds"));
1100        }
1101
1102        Ok(Self(etsi_val))
1103    }
1104
1105    /// create ETSI type with "unknown" value
1106    pub fn unknown() -> Self {
1107        Self(Self::UNKNOWN)
1108    }
1109
1110    /// determines if the ETSI value is special "unknown" value
1111    pub fn is_unknown(&self) -> bool {
1112        self.0 == Self::UNKNOWN
1113    }
1114
1115    /// create ETSI type with "out-of-range" value
1116    pub fn out_of_range() -> Self {
1117        Self(Self::OUT_OF_RANGE)
1118    }
1119
1120    /// determines if the ETSI value is special "out-of-range" value
1121    pub fn is_out_of_range(&self) -> bool {
1122        self.0 == Self::OUT_OF_RANGE
1123    }
1124}
1125
1126#[cfg(feature = "_dsrc_2_2_1")]
1127impl From<&dsrc_2_2_1::etsi_its_dsrc::TimeMark> for u32 {
1128    fn from(other: &dsrc_2_2_1::etsi_its_dsrc::TimeMark) -> u32 {
1129        other.as_millis()
1130    }
1131}
1132#[cfg(feature = "_dsrc_2_2_1")]
1133impl From<dsrc_2_2_1::etsi_its_dsrc::TimeMark> for u32 {
1134    fn from(other: dsrc_2_2_1::etsi_its_dsrc::TimeMark) -> u32 {
1135        other.as_millis()
1136    }
1137}
1138
1139/// Create conversions for PathDeltaTime
1140///
1141/// Value range is 1..65535 (extensible), so u16 fits currently, but u32 should have enough space for extensions
1142#[cfg(any(feature = "_cdd_2_2_1", feature = "_cdd_1_3_1_1"))]
1143macro_rules! path_delta_time_to_millis {
1144    ($t:ty) => {
1145        impl $t {
1146            const ETSI_TO_MS_FACTOR: u32 = 10; // unit: 10 milliseconds
1147
1148            /// convert ETSI PathDeltaTime to milliseconds
1149            #[must_use]
1150            pub fn as_millis(&self) -> u32 {
1151                let etsi_val: i64 = (&self.0).try_into().unwrap_or_default();
1152
1153                #[allow(clippy::cast_possible_truncation)]
1154                let etsi_val = etsi_val as u32;
1155                etsi_val * Self::ETSI_TO_MS_FACTOR
1156            }
1157
1158            /// create ETSI PathDeltaTime from milliseconds
1159            ///
1160            /// # Errors
1161            /// human-readable string when input value is out of bounds
1162            pub fn from_millis(value: u32) -> Result<Self, alloc::string::String> {
1163                use rasn::AsnType;
1164
1165                #[allow(clippy::cast_possible_truncation)]
1166                let etsi_val = value / Self::ETSI_TO_MS_FACTOR;
1167
1168                if let Some(constraints) = Self::CONSTRAINTS.value() {
1169                    if !constraints.constraint.in_bound(&etsi_val) {
1170                        return Err(alloc::format!("Value out of bounds"));
1171                    }
1172                }
1173
1174                Ok(Self(etsi_val.into()))
1175            }
1176        }
1177    };
1178}
1179
1180#[cfg(feature = "_cdd_1_3_1_1")]
1181path_delta_time_to_millis!(cdd_1_3_1_1::its_container::PathDeltaTime);
1182#[cfg(feature = "_cdd_2_2_1")]
1183path_delta_time_to_millis!(cdd_2_2_1::etsi_its_cdd::PathDeltaTime);
1184
1185// MinuteOfTheYear: unit minute, 527040 for invalid
1186#[cfg(feature = "_dsrc_2_2_1")]
1187impl dsrc_2_2_1::etsi_its_dsrc::MinuteOfTheYear {
1188    /// create ETSI MinuteOfTheYear with "invalid" value
1189    pub fn invalid() -> Self {
1190        Self(527040)
1191    }
1192
1193    /// determines if the ETSI value is special "invalid" value
1194    pub fn is_invalid(&self) -> bool {
1195        self.0 == 527040
1196    }
1197}
1198
1199// MsgCount 0..127
1200#[cfg(feature = "_dsrc_2_2_1")]
1201impl crate::standards::dsrc_2_2_1::etsi_its_dsrc::MsgCount {
1202    pub fn increment(&self) -> Self {
1203        Self((self.0 + 1) % 128)
1204    }
1205}
1206#[cfg(feature = "_dsrc_2_2_1")]
1207impl From<u8> for dsrc_2_2_1::etsi_its_dsrc::MsgCount {
1208    fn from(value: u8) -> Self {
1209        Self(value % 128)
1210    }
1211}
1212
1213// RequestID 0..255
1214#[cfg(feature = "_dsrc_2_2_1")]
1215impl crate::standards::dsrc_2_2_1::etsi_its_dsrc::RequestID {
1216    pub fn increment(&self) -> Self {
1217        Self(self.0.wrapping_add(1))
1218    }
1219}
1220#[cfg(feature = "_dsrc_2_2_1")]
1221impl From<u8> for dsrc_2_2_1::etsi_its_dsrc::RequestID {
1222    // for convenience and interface unification only
1223    fn from(value: u8) -> Self {
1224        Self(value)
1225    }
1226}
1227
1228// convenience getters
1229
1230#[cfg(feature = "_dsrc_2_2_1")]
1231impl dsrc_2_2_1::etsi_its_dsrc::SpeedLimitList {
1232    /// Extracts a certain speed limit in m/s, if existing
1233    pub fn get_speed_limit_mps(
1234        &self,
1235        limit_type: dsrc_2_2_1::etsi_its_dsrc::SpeedLimitType,
1236    ) -> Option<f32> {
1237        self.0.iter().find_map(|item| {
1238            if item.r_type == limit_type {
1239                Some(item.speed.as_mps())
1240            } else {
1241                None
1242            }
1243        })
1244    }
1245}
1246
1247#[cfg(feature = "_cdd_2_2_1")]
1248impl cdd_2_2_1::etsi_its_cdd::CauseCodeChoice {
1249    /// Converts a Cause Code to an integer tuple of cause code and sub cause code ID
1250    pub fn to_u8_tuple(&self) -> (u8, u8) {
1251        match self {
1252            Self::reserved0(scc) => (0, scc.0),
1253            Self::trafficCondition1(scc) => (1, scc.0),
1254            Self::accident2(scc) => (2, scc.0),
1255            Self::roadworks3(scc) => (3, scc.0),
1256            Self::reserved4(scc) => (4, scc.0),
1257            Self::impassability5(scc) => (5, scc.0),
1258            Self::adverseWeatherCondition_Adhesion6(scc) => (6, scc.0),
1259            Self::aquaplaning7(scc) => (7, scc.0),
1260            Self::reserved8(scc) => (8, scc.0),
1261            Self::hazardousLocation_SurfaceCondition9(scc) => (9, scc.0),
1262            Self::hazardousLocation_ObstacleOnTheRoad10(scc) => (10, scc.0),
1263            Self::hazardousLocation_AnimalOnTheRoad11(scc) => (11, scc.0),
1264            Self::humanPresenceOnTheRoad12(scc) => (12, scc.0),
1265            Self::reserved13(scc) => (13, scc.0),
1266            Self::wrongWayDriving14(scc) => (14, scc.0),
1267            Self::rescueAndRecoveryWorkInProgress15(scc) => (15, scc.0),
1268            Self::reserved16(scc) => (16, scc.0),
1269            Self::adverseWeatherCondition_ExtremeWeatherCondition17(scc) => (17, scc.0),
1270            Self::adverseWeatherCondition_Visibility18(scc) => (18, scc.0),
1271            Self::adverseWeatherCondition_Precipitation19(scc) => (19, scc.0),
1272            Self::violence20(scc) => (20, scc.0),
1273            Self::reserved21(scc) => (21, scc.0),
1274            Self::reserved22(scc) => (22, scc.0),
1275            Self::reserved23(scc) => (23, scc.0),
1276            Self::reserved24(scc) => (24, scc.0),
1277            Self::reserved25(scc) => (25, scc.0),
1278            Self::slowVehicle26(scc) => (26, scc.0),
1279            Self::dangerousEndOfQueue27(scc) => (27, scc.0),
1280            Self::publicTransportVehicleApproaching28(scc) => (28, scc.0),
1281            Self::reserved29(scc) => (29, scc.0),
1282            Self::reserved30(scc) => (30, scc.0),
1283            Self::reserved31(scc) => (31, scc.0),
1284            Self::reserved32(scc) => (32, scc.0),
1285            Self::reserved33(scc) => (33, scc.0),
1286            Self::reserved34(scc) => (34, scc.0),
1287            Self::reserved35(scc) => (35, scc.0),
1288            Self::reserved36(scc) => (36, scc.0),
1289            Self::reserved37(scc) => (37, scc.0),
1290            Self::reserved38(scc) => (38, scc.0),
1291            Self::reserved39(scc) => (39, scc.0),
1292            Self::reserved40(scc) => (40, scc.0),
1293            Self::reserved41(scc) => (41, scc.0),
1294            Self::reserved42(scc) => (42, scc.0),
1295            Self::reserved43(scc) => (43, scc.0),
1296            Self::reserved44(scc) => (44, scc.0),
1297            Self::reserved45(scc) => (45, scc.0),
1298            Self::reserved46(scc) => (46, scc.0),
1299            Self::reserved47(scc) => (47, scc.0),
1300            Self::reserved48(scc) => (48, scc.0),
1301            Self::reserved49(scc) => (49, scc.0),
1302            Self::reserved50(scc) => (50, scc.0),
1303            Self::reserved51(scc) => (51, scc.0),
1304            Self::reserved52(scc) => (52, scc.0),
1305            Self::reserved53(scc) => (53, scc.0),
1306            Self::reserved54(scc) => (54, scc.0),
1307            Self::reserved55(scc) => (55, scc.0),
1308            Self::reserved56(scc) => (56, scc.0),
1309            Self::reserved57(scc) => (57, scc.0),
1310            Self::reserved58(scc) => (58, scc.0),
1311            Self::reserved59(scc) => (59, scc.0),
1312            Self::reserved60(scc) => (60, scc.0),
1313            Self::reserved61(scc) => (61, scc.0),
1314            Self::reserved62(scc) => (62, scc.0),
1315            Self::reserved63(scc) => (63, scc.0),
1316            Self::reserved64(scc) => (64, scc.0),
1317            Self::reserved65(scc) => (65, scc.0),
1318            Self::reserved66(scc) => (66, scc.0),
1319            Self::reserved67(scc) => (67, scc.0),
1320            Self::reserved68(scc) => (68, scc.0),
1321            Self::reserved69(scc) => (69, scc.0),
1322            Self::reserved70(scc) => (70, scc.0),
1323            Self::reserved71(scc) => (71, scc.0),
1324            Self::reserved72(scc) => (72, scc.0),
1325            Self::reserved73(scc) => (73, scc.0),
1326            Self::reserved74(scc) => (74, scc.0),
1327            Self::reserved75(scc) => (75, scc.0),
1328            Self::reserved76(scc) => (76, scc.0),
1329            Self::reserved77(scc) => (77, scc.0),
1330            Self::reserved78(scc) => (78, scc.0),
1331            Self::reserved79(scc) => (79, scc.0),
1332            Self::reserved80(scc) => (80, scc.0),
1333            Self::reserved81(scc) => (81, scc.0),
1334            Self::reserved82(scc) => (82, scc.0),
1335            Self::reserved83(scc) => (83, scc.0),
1336            Self::reserved84(scc) => (84, scc.0),
1337            Self::reserved85(scc) => (85, scc.0),
1338            Self::reserved86(scc) => (86, scc.0),
1339            Self::reserved87(scc) => (87, scc.0),
1340            Self::reserved88(scc) => (88, scc.0),
1341            Self::reserved89(scc) => (89, scc.0),
1342            Self::reserved90(scc) => (90, scc.0),
1343            Self::vehicleBreakdown91(scc) => (91, scc.0),
1344            Self::postCrash92(scc) => (92, scc.0),
1345            Self::humanProblem93(scc) => (93, scc.0),
1346            Self::stationaryVehicle94(scc) => (94, scc.0),
1347            Self::emergencyVehicleApproaching95(scc) => (95, scc.0),
1348            Self::hazardousLocation_DangerousCurve96(scc) => (96, scc.0),
1349            Self::collisionRisk97(scc) => (97, scc.0),
1350            Self::signalViolation98(scc) => (98, scc.0),
1351            Self::dangerousSituation99(scc) => (99, scc.0),
1352            Self::railwayLevelCrossing100(scc) => (100, scc.0),
1353            Self::reserved101(scc) => (101, scc.0),
1354            Self::reserved102(scc) => (102, scc.0),
1355            Self::reserved103(scc) => (103, scc.0),
1356            Self::reserved104(scc) => (104, scc.0),
1357            Self::reserved105(scc) => (105, scc.0),
1358            Self::reserved106(scc) => (106, scc.0),
1359            Self::reserved107(scc) => (107, scc.0),
1360            Self::reserved108(scc) => (108, scc.0),
1361            Self::reserved109(scc) => (109, scc.0),
1362            Self::reserved110(scc) => (110, scc.0),
1363            Self::reserved111(scc) => (111, scc.0),
1364            Self::reserved112(scc) => (112, scc.0),
1365            Self::reserved113(scc) => (113, scc.0),
1366            Self::reserved114(scc) => (114, scc.0),
1367            Self::reserved115(scc) => (115, scc.0),
1368            Self::reserved116(scc) => (116, scc.0),
1369            Self::reserved117(scc) => (117, scc.0),
1370            Self::reserved118(scc) => (118, scc.0),
1371            Self::reserved119(scc) => (119, scc.0),
1372            Self::reserved120(scc) => (120, scc.0),
1373            Self::reserved121(scc) => (121, scc.0),
1374            Self::reserved122(scc) => (122, scc.0),
1375            Self::reserved123(scc) => (123, scc.0),
1376            Self::reserved124(scc) => (124, scc.0),
1377            Self::reserved125(scc) => (125, scc.0),
1378            Self::reserved126(scc) => (126, scc.0),
1379            Self::reserved127(scc) => (127, scc.0),
1380            Self::reserved128(scc) => (128, scc.0),
1381        }
1382    }
1383}