Skip to main content

fmi_schema/fmi3/variable/
mod.rs

1use super::Annotations;
2
3use crate::utils::AttrList;
4
5use std::{
6    fmt::{Debug, Display},
7    str::FromStr,
8};
9
10mod dimension;
11mod model_variables;
12#[cfg(test)]
13mod tests;
14
15pub use dimension::Dimension;
16pub use model_variables::{AppendToModelVariables, ModelVariables, Variable};
17
18/// Base type for variable aliases
19#[derive(PartialEq, Debug, Default, hard_xml::XmlRead, hard_xml::XmlWrite)]
20#[xml(tag = "Alias")]
21pub struct VariableAlias {
22    #[xml(attr = "name")]
23    pub name: String,
24    #[xml(attr = "description")]
25    pub description: Option<String>,
26}
27
28/// Alias for float variables (Float32 and Float64) with additional displayUnit attribute
29#[derive(PartialEq, Debug, Default, hard_xml::XmlRead, hard_xml::XmlWrite)]
30#[xml(tag = "Alias")]
31pub struct FloatVariableAlias {
32    #[xml(attr = "name")]
33    pub name: String,
34    #[xml(attr = "description")]
35    pub description: Option<String>,
36    #[xml(attr = "displayUnit")]
37    pub display_unit: Option<String>,
38}
39
40/// IntervalVariability declares the Clock type
41///
42/// See <https://fmi-standard.org/docs/3.0.1/#table-overview-clocks>
43#[derive(Clone, Copy, Default, PartialEq, Debug)]
44pub enum IntervalVariability {
45    Constant,
46    Fixed,
47    Tunable,
48    Changing,
49    Countdown,
50    /// IntervalVariability *must* be set to Triggered for Clocks with causality = Output
51    #[default]
52    Triggered,
53}
54
55impl FromStr for IntervalVariability {
56    type Err = String;
57
58    fn from_str(s: &str) -> Result<Self, Self::Err> {
59        match s {
60            "constant" => Ok(Self::Constant),
61            "fixed" => Ok(Self::Fixed),
62            "tunable" => Ok(Self::Tunable),
63            "changing" => Ok(Self::Changing),
64            "countdown" => Ok(Self::Countdown),
65            "triggered" => Ok(Self::Triggered),
66            _ => Err(format!("Invalid IntervalVariability: {}", s)),
67        }
68    }
69}
70
71impl Display for IntervalVariability {
72    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73        let s = match self {
74            Self::Constant => "constant",
75            Self::Fixed => "fixed",
76            Self::Tunable => "tunable",
77            Self::Changing => "changing",
78            Self::Countdown => "countdown",
79            Self::Triggered => "triggered",
80        };
81        write!(f, "{}", s)
82    }
83}
84
85/// An enumeration that defines the type of a variable.
86#[derive(Debug, PartialEq, Clone, Copy)]
87pub enum VariableType {
88    FmiFloat32,
89    FmiFloat64,
90    FmiInt8,
91    FmiUInt8,
92    FmiInt16,
93    FmiUInt16,
94    FmiInt32,
95    FmiUInt32,
96    FmiInt64,
97    FmiUInt64,
98    FmiBoolean,
99    FmiString,
100    FmiBinary,
101    FmiClock,
102}
103
104#[cfg(feature = "arrow")]
105impl From<VariableType> for arrow::datatypes::DataType {
106    fn from(v: VariableType) -> Self {
107        match v {
108            VariableType::FmiFloat32 => arrow::datatypes::DataType::Float32,
109            VariableType::FmiFloat64 => arrow::datatypes::DataType::Float64,
110            VariableType::FmiInt8 => arrow::datatypes::DataType::Int8,
111            VariableType::FmiUInt8 => arrow::datatypes::DataType::UInt8,
112            VariableType::FmiInt16 => arrow::datatypes::DataType::Int16,
113            VariableType::FmiUInt16 => arrow::datatypes::DataType::UInt16,
114            VariableType::FmiInt32 => arrow::datatypes::DataType::Int32,
115            VariableType::FmiUInt32 => arrow::datatypes::DataType::UInt32,
116            VariableType::FmiInt64 => arrow::datatypes::DataType::Int64,
117            VariableType::FmiUInt64 => arrow::datatypes::DataType::UInt64,
118            VariableType::FmiBoolean => arrow::datatypes::DataType::Boolean,
119            VariableType::FmiString => arrow::datatypes::DataType::Utf8,
120            VariableType::FmiBinary => arrow::datatypes::DataType::Binary,
121            VariableType::FmiClock => arrow::datatypes::DataType::Boolean,
122        }
123    }
124}
125
126pub trait AbstractVariableTrait {
127    /// The full, unique name of the variable.
128    fn name(&self) -> &str;
129    /// A handle of the variable to efficiently identify the variable value in the model interface
130    /// and for references within the modelDescription.xml
131    fn value_reference(&self) -> u32;
132    /// An optional description string describing the meaning of the variable.
133    fn description(&self) -> Option<&str>;
134    /// Enumeration that defines the causality of the variable.
135    fn causality(&self) -> Causality;
136    fn variability(&self) -> Variability;
137    fn can_handle_multiple_set_per_time_instant(&self) -> Option<bool>;
138    fn clocks(&self) -> Option<&[u32]>;
139    fn data_type(&self) -> VariableType;
140    fn annotations(&self) -> Option<&Annotations>;
141}
142
143pub trait ArrayableVariableTrait: AbstractVariableTrait {
144    /// Each `Dimension` element specifies the size of one dimension of the array
145    fn dimensions(&self) -> &[Dimension];
146    /// Extend the dimensions of the variable
147    fn add_dimensions(&mut self, dims: &[Dimension]);
148    /// If `true`, the variable can be updated during intermediate update mode.
149    fn intermediate_update(&self) -> Option<bool>;
150    /// The value reference of the variable that provides the previous value of this variable.
151    fn previous(&self) -> Option<u32>;
152}
153
154pub trait TypedVariableTrait {
155    fn declared_type(&self) -> Option<&str>;
156}
157
158pub trait TypedArrayableVariableTrait: ArrayableVariableTrait + TypedVariableTrait {}
159impl<T> TypedArrayableVariableTrait for T where T: ArrayableVariableTrait + TypedVariableTrait {}
160
161pub trait InitializableVariableTrait {
162    type StartType;
163    fn initial(&self) -> Option<Initial>;
164
165    fn start(&self) -> Option<&[Self::StartType]>;
166}
167
168macro_rules! impl_abstract_variable {
169    ($name: ident, $default_variability: expr, $variable_type: expr) => {
170        impl AbstractVariableTrait for $name {
171            fn name(&self) -> &str {
172                &self.name
173            }
174            fn value_reference(&self) -> u32 {
175                self.value_reference
176            }
177            fn description(&self) -> Option<&str> {
178                self.description.as_deref()
179            }
180            fn causality(&self) -> Causality {
181                self.causality.unwrap_or_default()
182            }
183            fn variability(&self) -> Variability {
184                self.variability.unwrap_or($default_variability)
185            }
186            fn can_handle_multiple_set_per_time_instant(&self) -> Option<bool> {
187                self.can_handle_multiple_set_per_time_instant
188            }
189            fn clocks(&self) -> Option<&[u32]> {
190                self.clocks.as_ref().map(|c| c.0.as_slice())
191            }
192            fn data_type(&self) -> VariableType {
193                $variable_type
194            }
195            fn annotations(&self) -> Option<&Annotations> {
196                self.annotations.as_ref()
197            }
198        }
199    };
200}
201
202macro_rules! impl_arrayable_variable {
203    ($name:ident) => {
204        impl ArrayableVariableTrait for $name {
205            fn dimensions(&self) -> &[Dimension] {
206                &self.dimensions
207            }
208            fn add_dimensions(&mut self, dims: &[Dimension]) {
209                self.dimensions.extend_from_slice(dims);
210            }
211            fn intermediate_update(&self) -> Option<bool> {
212                self.intermediate_update
213            }
214            fn previous(&self) -> Option<u32> {
215                self.previous
216            }
217        }
218    };
219}
220
221macro_rules! impl_typed_variable {
222    ($name:ident) => {
223        impl TypedVariableTrait for $name {
224            fn declared_type(&self) -> Option<&str> {
225                self.declared_type.as_deref()
226            }
227        }
228    };
229}
230
231macro_rules! impl_initializable_variable {
232    ($name: ident, $type: ty) => {
233        impl InitializableVariableTrait for $name {
234            type StartType = $type;
235
236            fn initial(&self) -> Option<Initial> {
237                self.initial
238            }
239
240            fn start(&self) -> Option<&[Self::StartType]> {
241                self.start.as_ref().map(|s| s.0.as_slice())
242            }
243        }
244    };
245}
246
247macro_rules! impl_float_type {
248    // Implementation for float types using hard_xml derives
249    ($name:ident, $tag:expr, $type:ty, $variable_type:expr) => {
250        #[derive(PartialEq, Debug, Default, hard_xml::XmlRead, hard_xml::XmlWrite)]
251        #[xml(tag = $tag, strict(unknown_attribute, unknown_element))]
252        pub struct $name {
253            #[xml(attr = "name")]
254            pub name: String,
255            #[xml(attr = "valueReference")]
256            pub value_reference: u32,
257            #[xml(attr = "description")]
258            pub description: Option<String>,
259            #[xml(attr = "causality")]
260            pub causality: Option<Causality>,
261            #[xml(attr = "variability")]
262            pub variability: Option<Variability>,
263            #[xml(attr = "canHandleMultipleSetPerTimeInstant")]
264            pub can_handle_multiple_set_per_time_instant: Option<bool>,
265            #[xml(attr = "clocks")]
266            pub clocks: Option<AttrList<u32>>,
267            #[xml(attr = "declaredType")]
268            pub declared_type: Option<String>,
269            #[xml(child = "Dimension")]
270            pub dimensions: Vec<Dimension>,
271            #[xml(attr = "intermediateUpdate")]
272            pub intermediate_update: Option<bool>,
273            #[xml(attr = "previous")]
274            pub previous: Option<u32>,
275            /// Initial or guess value of the variable. During instantiation, the FMU initializes its variables with their start values.
276            #[xml(attr = "start")]
277            pub start: Option<AttrList<$type>>,
278            #[xml(attr = "initial")]
279            pub initial: Option<Initial>,
280            #[xml(attr = "min")]
281            pub min: Option<$type>,
282            #[xml(attr = "max")]
283            pub max: Option<$type>,
284            #[xml(attr = "derivative")]
285            pub derivative: Option<u32>,
286            #[xml(attr = "reinit")]
287            pub reinit: Option<bool>,
288            #[xml(child = "Annotations")]
289            pub annotations: Option<Annotations>,
290            #[xml(child = "Alias")]
291            pub aliases: Vec<FloatVariableAlias>,
292        }
293
294        impl_abstract_variable!($name, Variability::Continuous, $variable_type);
295        impl_arrayable_variable!($name);
296        impl_typed_variable!($name);
297        impl_initializable_variable!($name, $type);
298
299        impl $name {
300            pub fn derivative(&self) -> Option<u32> {
301                self.derivative
302            }
303
304            pub fn reinit(&self) -> Option<bool> {
305                self.reinit
306            }
307
308            pub fn new(
309                name: String,
310                value_reference: u32,
311                description: Option<String>,
312                causality: Causality,
313                variability: Variability,
314                start: Option<Vec<$type>>,
315                initial: Option<Initial>,
316            ) -> Self {
317                Self {
318                    name,
319                    value_reference,
320                    description,
321                    causality: Some(causality),
322                    variability: Some(variability),
323                    start: start.map(AttrList),
324                    initial,
325                    ..Default::default()
326                }
327            }
328        }
329    };
330}
331
332macro_rules! impl_integer_type {
333    // Implementation for integer types using hard_xml derives
334    ($name:ident, $tag:expr, $type:ty, $variable_type:expr) => {
335        #[derive(PartialEq, Debug, Default, hard_xml::XmlRead, hard_xml::XmlWrite)]
336        #[xml(tag = $tag, strict(unknown_attribute, unknown_element))]
337        pub struct $name {
338            #[xml(attr = "name")]
339            pub name: String,
340            #[xml(attr = "valueReference")]
341            pub value_reference: u32,
342            #[xml(attr = "description")]
343            pub description: Option<String>,
344            #[xml(attr = "causality")]
345            pub causality: Option<Causality>,
346            #[xml(attr = "variability")]
347            pub variability: Option<Variability>,
348            #[xml(attr = "canHandleMultipleSetPerTimeInstant")]
349            pub can_handle_multiple_set_per_time_instant: Option<bool>,
350            #[xml(attr = "clocks")]
351            pub clocks: Option<AttrList<u32>>,
352            #[xml(attr = "declaredType")]
353            pub declared_type: Option<String>,
354            #[xml(child = "Dimension")]
355            pub dimensions: Vec<Dimension>,
356            #[xml(attr = "intermediateUpdate")]
357            pub intermediate_update: Option<bool>,
358            #[xml(attr = "previous")]
359            pub previous: Option<u32>,
360            /// Initial or guess value of the variable. During instantiation, the FMU initializes its variables with their start values.
361            #[xml(attr = "start")]
362            pub start: Option<AttrList<$type>>,
363            #[xml(attr = "initial")]
364            pub initial: Option<Initial>,
365            #[xml(attr = "min")]
366            pub min: Option<$type>,
367            #[xml(attr = "max")]
368            pub max: Option<$type>,
369            #[xml(attr = "quantity")]
370            pub quantity: Option<String>,
371            #[xml(child = "Annotations")]
372            pub annotations: Option<Annotations>,
373            #[xml(child = "Alias")]
374            pub aliases: Vec<VariableAlias>,
375        }
376
377        impl_abstract_variable!($name, Variability::Discrete, $variable_type);
378        impl_arrayable_variable!($name);
379        impl_typed_variable!($name);
380        impl_initializable_variable!($name, $type);
381
382        impl $name {
383            pub fn new(
384                name: String,
385                value_reference: u32,
386                description: Option<String>,
387                causality: Causality,
388                variability: Variability,
389                start: Option<Vec<$type>>,
390                initial: Option<Initial>,
391            ) -> Self {
392                Self {
393                    name,
394                    value_reference,
395                    description,
396                    causality: Some(causality),
397                    variability: Some(variability),
398                    start: start.map(AttrList),
399                    initial,
400                    ..Default::default()
401                }
402            }
403        }
404    };
405}
406
407/// Enumeration that defines the causality of the variable.
408///
409/// See <https://fmi-standard.org/docs/3.0.1/#causality>
410#[derive(Clone, Copy, Default, PartialEq, Debug)]
411pub enum Causality {
412    /// A data value that is constant during the simulation
413    Parameter,
414    /// A data value that is constant during the simulation and is computed during initialization
415    /// or when tunable parameters change.
416    CalculatedParameter,
417    /// The variable value can be provided by the importer.
418    Input,
419    /// The values of these variables are computed in the FMU and they are designed to be used outside the FMU.
420    Output,
421    /// Local variables of the FMU that must not be used for FMU connections
422    #[default]
423    Local,
424    /// The independent variable (usually time [but could also be, for example, angle]).
425    Independent,
426    Dependent,
427    /// The variable value can only be changed in Configuration Mode or Reconfiguration Mode.
428    StructuralParameter,
429}
430
431impl FromStr for Causality {
432    type Err = String;
433
434    fn from_str(s: &str) -> Result<Self, Self::Err> {
435        match s {
436            "parameter" => Ok(Causality::Parameter),
437            "calculatedParameter" => Ok(Causality::CalculatedParameter),
438            "input" => Ok(Causality::Input),
439            "output" => Ok(Causality::Output),
440            "local" => Ok(Causality::Local),
441            "independent" => Ok(Causality::Independent),
442            "dependent" => Ok(Causality::Dependent),
443            "structuralParameter" => Ok(Causality::StructuralParameter),
444            _ => Err(format!("Invalid Causality: {}", s)),
445        }
446    }
447}
448
449impl Display for Causality {
450    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
451        match self {
452            Causality::Parameter => write!(f, "parameter"),
453            Causality::CalculatedParameter => write!(f, "calculatedParameter"),
454            Causality::Input => write!(f, "input"),
455            Causality::Output => write!(f, "output"),
456            Causality::Local => write!(f, "local"),
457            Causality::Independent => write!(f, "independent"),
458            Causality::Dependent => write!(f, "dependent"),
459            Causality::StructuralParameter => write!(f, "structuralParameter"),
460        }
461    }
462}
463
464/// Enumeration that defines the time dependency of the variable, in other words, it defines the
465/// time instants when a variable may be changed by the importer or may change its value due to FMU
466/// internal computations, depending on their causality.
467///
468/// See <https://fmi-standard.org/docs/3.0.1/#variability>
469#[derive(Clone, Copy, Default, PartialEq, Debug)]
470pub enum Variability {
471    /// The value of the variable never changes.
472    Constant,
473    /// The value of the variable is fixed in super state Initialized, in other words, after
474    /// `exit_initialization_mode()` was called the variable value does not change anymore. The
475    /// default for variables of causality [`Causality::Parameter`],
476    /// [`Causality::StructuralParameter`] or [`Causality::CalculatedParameter`] is `Fixed`.
477    Fixed,
478    /// The value of the variable is constant between events (ME and CS if Event Mode is supported)
479    /// and between communication points (CS and SE). A parameter with variability = tunable
480    /// may be changed only in Event Mode or, if Event Mode is not supported, at communication
481    /// points (CS and SE).
482    Tunable,
483    /// * Model Exchange: The value of the variable may change only in Event Mode.
484    /// * Co-Simulation: If Event Mode is used (see `event_mode_used`), the value of the variable
485    ///   may only change in Event Mode. If Event Mode is not used, the value may change at
486    ///   communication points and the FMU must detect and handle such events internally. During
487    ///   Intermediate Update Mode, discrete variables are not allowed to change.
488    /// * Scheduled Execution: The value may change only at communication points.
489    #[default]
490    Discrete,
491    /// Only variables of type [`FmiFloat32`]or [`FmiFloat64`] may be continuous. The default for
492    /// variables of type `FmiFloat32` and `FmiFloat64` and causality other than
493    /// [`Causality::Parameter`], [`Causality::StructuralParameter`] or
494    /// [`Causality::CalculatedParameter`] is continuous. Variables with variability continuous
495    /// may change in Initialization Mode and in super state Initialized.
496    Continuous,
497}
498
499impl FromStr for Variability {
500    type Err = String;
501
502    fn from_str(s: &str) -> Result<Self, Self::Err> {
503        match s {
504            "constant" => Ok(Variability::Constant),
505            "fixed" => Ok(Variability::Fixed),
506            "tunable" => Ok(Variability::Tunable),
507            "discrete" => Ok(Variability::Discrete),
508            "continuous" => Ok(Variability::Continuous),
509            _ => Err(format!("Invalid Variability: {}", s)),
510        }
511    }
512}
513
514impl Display for Variability {
515    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
516        match self {
517            Variability::Constant => write!(f, "constant"),
518            Variability::Fixed => write!(f, "fixed"),
519            Variability::Tunable => write!(f, "tunable"),
520            Variability::Discrete => write!(f, "discrete"),
521            Variability::Continuous => write!(f, "continuous"),
522        }
523    }
524}
525
526#[derive(Clone, Copy, Default, PartialEq, Debug)]
527pub enum Initial {
528    #[default]
529    Exact,
530    Approx,
531    Calculated,
532}
533
534impl FromStr for Initial {
535    type Err = String;
536
537    fn from_str(s: &str) -> Result<Self, Self::Err> {
538        match s {
539            "exact" => Ok(Initial::Exact),
540            "approx" => Ok(Initial::Approx),
541            "calculated" => Ok(Initial::Calculated),
542            _ => Err(format!("Invalid Initial: {}", s)),
543        }
544    }
545}
546
547impl Display for Initial {
548    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
549        match self {
550            Initial::Exact => write!(f, "exact"),
551            Initial::Approx => write!(f, "approx"),
552            Initial::Calculated => write!(f, "calculated"),
553        }
554    }
555}
556
557impl_float_type!(FmiFloat32, "Float32", f32, VariableType::FmiFloat32);
558impl_float_type!(FmiFloat64, "Float64", f64, VariableType::FmiFloat64);
559impl_integer_type!(FmiInt8, "Int8", i8, VariableType::FmiInt8);
560impl_integer_type!(FmiUInt8, "UInt8", u8, VariableType::FmiUInt8);
561impl_integer_type!(FmiInt16, "Int16", i16, VariableType::FmiInt16);
562impl_integer_type!(FmiUInt16, "UInt16", u16, VariableType::FmiUInt16);
563impl_integer_type!(FmiInt32, "Int32", i32, VariableType::FmiInt32);
564impl_integer_type!(FmiUInt32, "UInt32", u32, VariableType::FmiUInt32);
565impl_integer_type!(FmiInt64, "Int64", i64, VariableType::FmiInt64);
566impl_integer_type!(FmiUInt64, "UInt64", u64, VariableType::FmiUInt64);
567
568#[derive(Default, PartialEq, Debug, hard_xml::XmlRead, hard_xml::XmlWrite)]
569#[xml(tag = "Boolean", strict(unknown_attribute, unknown_element))]
570pub struct FmiBoolean {
571    #[xml(attr = "name")]
572    pub name: String,
573    #[xml(attr = "valueReference")]
574    pub value_reference: u32,
575    #[xml(attr = "description")]
576    pub description: Option<String>,
577    #[xml(attr = "causality")]
578    pub causality: Option<Causality>,
579    #[xml(attr = "variability")]
580    pub variability: Option<Variability>,
581    #[xml(attr = "canHandleMultipleSetPerTimeInstant")]
582    pub can_handle_multiple_set_per_time_instant: Option<bool>,
583    #[xml(attr = "clocks")]
584    pub clocks: Option<AttrList<u32>>,
585    #[xml(attr = "declaredType")]
586    pub declared_type: Option<String>,
587    #[xml(child = "Dimension")]
588    pub dimensions: Vec<Dimension>,
589    #[xml(attr = "intermediateUpdate")]
590    pub intermediate_update: Option<bool>,
591    #[xml(attr = "previous")]
592    pub previous: Option<u32>,
593    #[xml(attr = "initial")]
594    pub initial: Option<Initial>,
595    #[xml(attr = "start")]
596    pub start: Option<AttrList<bool>>,
597    #[xml(child = "Annotations")]
598    pub annotations: Option<Annotations>,
599    #[xml(child = "Alias")]
600    pub aliases: Vec<VariableAlias>,
601}
602
603impl_abstract_variable!(FmiBoolean, Variability::Discrete, VariableType::FmiBoolean);
604impl_arrayable_variable!(FmiBoolean);
605impl_typed_variable!(FmiBoolean);
606impl_initializable_variable!(FmiBoolean, bool);
607
608impl FmiBoolean {
609    /// Create a new FMI boolean variable with the given parameters
610    pub fn new(
611        name: String,
612        value_reference: u32,
613        description: Option<String>,
614        causality: Causality,
615        variability: Variability,
616        start: Option<Vec<bool>>,
617        initial: Option<Initial>,
618    ) -> Self {
619        Self {
620            start: start.map(AttrList),
621            name,
622            value_reference,
623            description,
624            causality: Some(causality),
625            variability: Some(variability),
626            can_handle_multiple_set_per_time_instant: None,
627            clocks: None,
628            annotations: None,
629            dimensions: vec![],
630            intermediate_update: None,
631            previous: None,
632            declared_type: None,
633            initial,
634            aliases: vec![],
635        }
636    }
637}
638
639/// A String start value element
640#[derive(PartialEq, Debug, hard_xml::XmlRead, hard_xml::XmlWrite)]
641#[xml(tag = "Start")]
642pub struct StringStart {
643    #[xml(attr = "value")]
644    pub value: String,
645}
646
647#[derive(Default, PartialEq, Debug, hard_xml::XmlRead, hard_xml::XmlWrite)]
648#[xml(tag = "String", strict(unknown_attribute, unknown_element))]
649pub struct FmiString {
650    #[xml(attr = "name")]
651    pub name: String,
652    #[xml(attr = "valueReference")]
653    pub value_reference: u32,
654    #[xml(attr = "description")]
655    pub description: Option<String>,
656    #[xml(attr = "causality")]
657    pub causality: Option<Causality>,
658    #[xml(attr = "variability")]
659    pub variability: Option<Variability>,
660    #[xml(attr = "canHandleMultipleSetPerTimeInstant")]
661    pub can_handle_multiple_set_per_time_instant: Option<bool>,
662    #[xml(attr = "clocks")]
663    pub clocks: Option<AttrList<u32>>,
664    #[xml(attr = "declaredType")]
665    pub declared_type: Option<String>,
666    #[xml(child = "Dimension")]
667    pub dimensions: Vec<Dimension>,
668    #[xml(attr = "intermediateUpdate")]
669    pub intermediate_update: Option<bool>,
670    #[xml(attr = "previous")]
671    pub previous: Option<u32>,
672    /// Initial or guess value of the variable. During instantiation, the FMU initializes its variables with their start values.
673    #[xml(child = "Start")]
674    pub start: Vec<StringStart>,
675    #[xml(attr = "initial")]
676    pub initial: Option<Initial>,
677    #[xml(child = "Annotations")]
678    pub annotations: Option<Annotations>,
679    #[xml(child = "Alias")]
680    pub aliases: Vec<VariableAlias>,
681}
682
683impl FmiString {
684    /// Create a new FMI string variable with the given parameters
685    pub fn new(
686        name: String,
687        value_reference: u32,
688        description: Option<String>,
689        causality: Causality,
690        variability: Variability,
691        start: Option<Vec<String>>,
692        initial: Option<Initial>,
693    ) -> Self {
694        Self {
695            start: start
696                .unwrap_or_default()
697                .into_iter()
698                .map(|value| StringStart { value })
699                .collect(),
700            name,
701            value_reference,
702            description,
703            causality: Some(causality),
704            variability: Some(variability),
705            can_handle_multiple_set_per_time_instant: None,
706            clocks: None,
707            annotations: None,
708            dimensions: vec![],
709            intermediate_update: None,
710            previous: None,
711            declared_type: None,
712            initial,
713            aliases: vec![],
714        }
715    }
716}
717
718impl_abstract_variable!(FmiString, Variability::Discrete, VariableType::FmiString);
719impl_arrayable_variable!(FmiString);
720impl_typed_variable!(FmiString);
721impl InitializableVariableTrait for FmiString {
722    type StartType = StringStart;
723
724    fn initial(&self) -> Option<Initial> {
725        self.initial
726    }
727
728    fn start(&self) -> Option<&[Self::StartType]> {
729        if self.start.is_empty() {
730            None
731        } else {
732            Some(&self.start)
733        }
734    }
735}
736
737#[derive(PartialEq, Debug, hard_xml::XmlRead, hard_xml::XmlWrite)]
738#[xml(tag = "Start")]
739pub struct BinaryStart {
740    #[xml(attr = "value")]
741    pub value: String,
742}
743
744#[derive(Default, PartialEq, Debug, hard_xml::XmlRead, hard_xml::XmlWrite)]
745#[xml(tag = "Binary")]
746pub struct FmiBinary {
747    #[xml(attr = "name")]
748    pub name: String,
749    #[xml(attr = "valueReference")]
750    pub value_reference: u32,
751    #[xml(attr = "description")]
752    pub description: Option<String>,
753    #[xml(attr = "causality")]
754    pub causality: Option<Causality>,
755    #[xml(attr = "variability")]
756    pub variability: Option<Variability>,
757    #[xml(attr = "canHandleMultipleSetPerTimeInstant")]
758    pub can_handle_multiple_set_per_time_instant: Option<bool>,
759    #[xml(attr = "clocks")]
760    pub clocks: Option<AttrList<u32>>,
761    #[xml(attr = "declaredType")]
762    pub declared_type: Option<String>,
763    #[xml(child = "Dimension")]
764    pub dimensions: Vec<Dimension>,
765    #[xml(attr = "intermediateUpdate")]
766    pub intermediate_update: Option<bool>,
767    #[xml(attr = "previous")]
768    pub previous: Option<u32>,
769    #[xml(child = "Start")]
770    pub start: Vec<BinaryStart>,
771    #[xml(attr = "mimeType")]
772    pub mime_type: Option<String>,
773    #[xml(attr = "maxSize")]
774    pub max_size: Option<u32>,
775    #[xml(attr = "initial")]
776    pub initial: Option<Initial>,
777    #[xml(child = "Annotations")]
778    pub annotations: Option<Annotations>,
779    #[xml(child = "Alias")]
780    pub aliases: Vec<VariableAlias>,
781}
782
783fn default_mime_type() -> String {
784    "application/octet-stream".into()
785}
786
787impl FmiBinary {
788    /// Create a new FMI binary variable with the given parameters
789    pub fn new(
790        name: String,
791        value_reference: u32,
792        description: Option<String>,
793        causality: Causality,
794        variability: Variability,
795        start: Option<Vec<String>>,
796        initial: Option<Initial>,
797    ) -> Self {
798        Self {
799            start: start
800                .unwrap_or_default()
801                .into_iter()
802                .map(|value| BinaryStart { value })
803                .collect(),
804            mime_type: Some(default_mime_type()),
805            max_size: None,
806            name,
807            value_reference,
808            description,
809            causality: Some(causality),
810            variability: Some(variability),
811            can_handle_multiple_set_per_time_instant: None,
812            clocks: None,
813            annotations: None,
814            dimensions: vec![],
815            intermediate_update: None,
816            previous: None,
817            declared_type: None,
818            initial,
819            aliases: vec![],
820        }
821    }
822
823    /// Decode a single hex-encoded start value string into bytes.
824    /// Handles optional "0x" prefix and whitespace in the hex string.
825    pub fn decode_start_value(hex_string: &str) -> Result<Vec<u8>, std::num::ParseIntError> {
826        // Remove any whitespace and 0x prefix
827        let cleaned = hex_string.replace(|c: char| c.is_whitespace(), "");
828        let hex_str = cleaned.strip_prefix("0x").unwrap_or(&cleaned);
829
830        // Parse pairs of hex digits
831        (0..hex_str.len())
832            .step_by(2)
833            .map(|i| {
834                let byte_str = &hex_str[i..std::cmp::min(i + 2, hex_str.len())];
835                u8::from_str_radix(byte_str, 16)
836            })
837            .collect()
838    }
839}
840
841impl_abstract_variable!(FmiBinary, Variability::Discrete, VariableType::FmiBinary);
842impl_arrayable_variable!(FmiBinary);
843impl_typed_variable!(FmiBinary);
844
845impl InitializableVariableTrait for FmiBinary {
846    type StartType = BinaryStart;
847
848    fn initial(&self) -> Option<Initial> {
849        self.initial
850    }
851
852    fn start(&self) -> Option<&[Self::StartType]> {
853        if self.start.is_empty() {
854            None
855        } else {
856            Some(&self.start)
857        }
858    }
859}
860
861/// Clock variable type
862#[derive(Default, PartialEq, Debug, hard_xml::XmlRead, hard_xml::XmlWrite)]
863#[xml(tag = "Clock", strict(unknown_attribute, unknown_element))]
864pub struct FmiClock {
865    #[xml(attr = "name")]
866    pub name: String,
867    #[xml(attr = "valueReference")]
868    pub value_reference: u32,
869    #[xml(attr = "description")]
870    pub description: Option<String>,
871    #[xml(attr = "causality")]
872    pub causality: Option<Causality>,
873    #[xml(attr = "variability")]
874    pub variability: Option<Variability>,
875    #[xml(attr = "canHandleMultipleSetPerTimeInstant")]
876    pub can_handle_multiple_set_per_time_instant: Option<bool>,
877    #[xml(attr = "clocks")]
878    pub clocks: Option<AttrList<u32>>,
879    #[xml(attr = "declaredType")]
880    pub declared_type: Option<String>,
881    #[xml(attr = "canBeDeactivated")]
882    pub can_be_deactivated: Option<bool>,
883    #[xml(attr = "priority")]
884    pub priority: Option<i32>,
885    #[xml(attr = "intervalVariability")]
886    pub interval_variability: Option<IntervalVariability>,
887    #[xml(attr = "intervalDecimal")]
888    pub interval_decimal: Option<f64>,
889    #[xml(attr = "shiftDecimal")]
890    pub shift_decimal: Option<f64>,
891    #[xml(attr = "supportsFraction")]
892    pub supports_fraction: Option<bool>,
893    #[xml(attr = "resolution")]
894    pub resolution: Option<u64>,
895    #[xml(attr = "intervalCounter")]
896    pub interval_counter: Option<u64>,
897    #[xml(attr = "shiftCounter")]
898    pub shift_counter: Option<u64>,
899    #[xml(child = "Annotations")]
900    pub annotations: Option<Annotations>,
901    #[xml(child = "Alias")]
902    pub aliases: Vec<VariableAlias>,
903}
904
905impl_abstract_variable!(FmiClock, Variability::Discrete, VariableType::FmiClock);
906impl_typed_variable!(FmiClock);
907
908impl FmiClock {
909    pub fn new(
910        name: String,
911        value_reference: u32,
912        description: Option<String>,
913        causality: Causality,
914        variability: Variability,
915    ) -> Self {
916        Self {
917            name,
918            value_reference,
919            description,
920            causality: Some(causality),
921            variability: Some(variability),
922            can_handle_multiple_set_per_time_instant: None,
923            clocks: None,
924            declared_type: None,
925            can_be_deactivated: None,
926            priority: None,
927            interval_variability: None,
928            interval_decimal: None,
929            shift_decimal: None,
930            supports_fraction: None,
931            resolution: None,
932            interval_counter: None,
933            shift_counter: None,
934            annotations: None,
935            aliases: vec![],
936        }
937    }
938
939    pub fn interval_variability(&self) -> IntervalVariability {
940        self.interval_variability.unwrap_or_default()
941    }
942}