diff --git a/autosar-data-abstraction/src/arpackage.rs b/autosar-data-abstraction/src/arpackage.rs index 0d3b17a..0675a91 100644 --- a/autosar-data-abstraction/src/arpackage.rs +++ b/autosar-data-abstraction/src/arpackage.rs @@ -63,7 +63,7 @@ mod test { let result = ArPackage::get_or_create(&model, "/pkg1"); assert!(result.is_ok()); let package = result.unwrap(); - assert_eq!(package.name(), "pkg1"); + assert_eq!(package.name().unwrap(), "pkg1"); // get the existing package let result = ArPackage::get_or_create(&model, "/pkg1"); assert!(result.is_ok()); @@ -71,7 +71,7 @@ mod test { let result = ArPackage::get_or_create(&model, "/level1/level2/level3"); assert!(result.is_ok()); let package = result.unwrap(); - assert_eq!(package.name(), "level3"); + assert_eq!(package.name().unwrap(), "level3"); // can't create a package due to an element name conflict let pkg = ArPackage::get_or_create(&model, "/test").unwrap(); diff --git a/autosar-data-abstraction/src/can.rs b/autosar-data-abstraction/src/can.rs deleted file mode 100644 index eabaa56..0000000 --- a/autosar-data-abstraction/src/can.rs +++ /dev/null @@ -1,568 +0,0 @@ -use crate::{abstraction_element, AbstractionElement, ArPackage, AutosarAbstractionError, EcuInstance}; -use autosar_data::{ - AutosarDataError, AutosarModel, CharacterData, Element, ElementName, ElementsIterator, WeakElement, -}; - -/// Settings for a CAN cluster -#[derive(Debug, Clone, PartialEq)] -pub struct CanClusterSettings { - pub baudrate: u32, - pub can_fd_baudrate: Option, - pub can_xl_baudrate: Option, -} - -/// A `CanCluster` contains all configuration items associated with a CAN network. -/// The cluster connects multiple ECUs. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct CanCluster(Element); -abstraction_element!(CanCluster, CanCluster); - -impl CanCluster { - // create a new CanCluster - for internal use. User code should call System::create_can_cluster - pub(crate) fn new( - cluster_name: &str, - package: &ArPackage, - settings: &CanClusterSettings, - ) -> Result { - let elem_pkg_elements = package.element().get_or_create_sub_element(ElementName::Elements)?; - let elem_cluster = elem_pkg_elements.create_named_sub_element(ElementName::CanCluster, cluster_name)?; - if let Ok(cluster_content) = elem_cluster - .create_sub_element(ElementName::CanClusterVariants) - .and_then(|ccv| ccv.create_sub_element(ElementName::CanClusterConditional)) - { - let _ = cluster_content - .create_sub_element(ElementName::ProtocolName) - .and_then(|pn| pn.set_character_data(autosar_data::CharacterData::String("CAN".to_string()))); - - let _ = cluster_content.create_sub_element(ElementName::PhysicalChannels); - } - - let can_cluster = CanCluster(elem_cluster); - can_cluster.update_settings(settings); - - Ok(can_cluster) - } - - /// Update the settings of this `CanCluster` with new values for the baudrates - /// - /// The baudrates for CanFD and CanXL are optional. - /// If they are set to None in the settings, then corresponding elements in the model will be removed. - /// - /// # Example - /// - /// ``` - /// # use autosar_data::*; - /// # use autosar_data_abstraction::*; - /// # let model = AutosarModel::new(); - /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); - /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); - /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); - /// let cluster = system.create_can_cluster("Cluster", &package, &CanClusterSettings::default()).unwrap(); - /// let mut settings = cluster.get_settings(); - /// settings.can_fd_baudrate = Some(2000000); - /// cluster.update_settings(&settings); - /// ``` - pub fn update_settings(&self, settings: &CanClusterSettings) { - if let Ok(cluster_content) = self - .0 - .get_or_create_sub_element(ElementName::CanClusterVariants) - .and_then(|ccv| ccv.get_or_create_sub_element(ElementName::CanClusterConditional)) - { - let _ = cluster_content - .create_sub_element(ElementName::Baudrate) - .and_then(|br| br.set_character_data(CharacterData::String(settings.baudrate.to_string()))); - if let Some(can_fd_baudrate) = settings.can_fd_baudrate { - let _ = cluster_content - .create_sub_element(ElementName::CanFdBaudrate) - .and_then(|cfbr| cfbr.set_character_data(CharacterData::String(can_fd_baudrate.to_string()))); - } else if let Some(cfbr) = cluster_content.get_sub_element(ElementName::CanFdBaudrate) { - let _ = cluster_content.remove_sub_element(cfbr); - } - if let Some(can_xl_baudrate) = settings.can_xl_baudrate { - cluster_content - .create_sub_element(ElementName::CanXlBaudrate) - .and_then(|cxbr| cxbr.set_character_data(CharacterData::String(can_xl_baudrate.to_string()))) - .unwrap(); - } else if let Some(cxbr) = cluster_content.get_sub_element(ElementName::CanXlBaudrate) { - let _ = cluster_content.remove_sub_element(cxbr); - } - } - } - - /// get the setings of this `CanCluster` - /// - /// # Example - /// - /// ``` - /// # use autosar_data::*; - /// # use autosar_data_abstraction::*; - /// # let model = AutosarModel::new(); - /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); - /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); - /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); - /// let settings = CanClusterSettings {baudrate: 500000, can_fd_baudrate: None, can_xl_baudrate: None}; - /// let cluster = system.create_can_cluster("Cluster", &package, &settings).unwrap(); - /// let settings2 = cluster.get_settings(); - /// assert_eq!(settings, settings2); - /// ``` - #[must_use] - pub fn get_settings(&self) -> CanClusterSettings { - let mut settings = CanClusterSettings { - baudrate: 0, - can_fd_baudrate: None, - can_xl_baudrate: None, - }; - if let Some(cluster_content) = self - .0 - .get_sub_element(ElementName::CanClusterVariants) - .and_then(|ccv| ccv.get_sub_element(ElementName::CanClusterConditional)) - { - if let Some(baudrate) = cluster_content - .get_sub_element(ElementName::Baudrate) - .and_then(|br| br.character_data()) - .and_then(|cdata| cdata.decode_integer()) - { - settings.baudrate = baudrate; - } - - settings.can_fd_baudrate = cluster_content - .get_sub_element(ElementName::CanFdBaudrate) - .and_then(|br| br.character_data()) - .and_then(|cdata| cdata.decode_integer()); - - settings.can_xl_baudrate = cluster_content - .get_sub_element(ElementName::CanXlBaudrate) - .and_then(|br| br.character_data()) - .and_then(|cdata| cdata.decode_integer()); - } - settings - } - - /// Create a new physical channel for the cluster - /// - /// A can cluster must contain exactly one physical channel; trying to add a second one triggers an error. - /// - /// # Example - /// - /// ``` - /// # use autosar_data::*; - /// # use autosar_data_abstraction::*; - /// # let model = AutosarModel::new(); - /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); - /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); - /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); - /// let cluster = system.create_can_cluster("Cluster", &package, &CanClusterSettings::default()).unwrap(); - /// let channel = cluster.create_physical_channel("Channel").unwrap(); - /// ``` - /// - /// # Errors - /// - /// - [`AutosarAbstractionError::ItemAlreadyExists`] There is already a physical channel in this CAN cluster - /// - [`AutosarAbstractionError::ModelError`] An error occurred in the Autosar model while trying to create the ECU-INSTANCE - pub fn create_physical_channel(&self, channel_name: &str) -> Result { - let phys_channels = self - .0 - .get_or_create_sub_element(ElementName::CanClusterVariants)? - .get_or_create_sub_element(ElementName::CanClusterConditional)? - .get_or_create_sub_element(ElementName::PhysicalChannels)?; - - if phys_channels.sub_elements().count() != 0 { - return Err(AutosarAbstractionError::ItemAlreadyExists); - } - - let channel = phys_channels.create_named_sub_element(ElementName::CanPhysicalChannel, channel_name)?; - - Ok(CanPhysicalChannel(channel)) - } - - /// return the CanPhysicalChannel of the Cluster, if it has been created - /// - /// # Example - /// - /// ``` - /// # use autosar_data::*; - /// # use autosar_data_abstraction::*; - /// # let model = AutosarModel::new(); - /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); - /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); - /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); - /// # let cluster = system.create_can_cluster("Cluster", &package, &CanClusterSettings::default()).unwrap(); - /// # let can_channel = cluster.create_physical_channel("Channel").unwrap(); - /// let channel = cluster.physical_channel().unwrap(); - /// # assert_eq!(channel, can_channel); - /// ```` - #[must_use] - pub fn physical_channel(&self) -> Option { - let channel = self - .0 - .get_sub_element(ElementName::CanClusterVariants)? - .get_sub_element(ElementName::CanClusterConditional)? - .get_sub_element(ElementName::PhysicalChannels)? - .get_sub_element(ElementName::CanPhysicalChannel)?; - Some(CanPhysicalChannel(channel)) - } -} - -//################################################################## - -/// The `CanPhysicalChannel contains all of the communication on a CAN network -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct CanPhysicalChannel(Element); -abstraction_element!(CanPhysicalChannel, CanPhysicalChannel); - -impl CanPhysicalChannel { - /// get the cluster containing this physical channel - /// - /// # Example - /// - /// ``` - /// # use autosar_data::*; - /// # use autosar_data_abstraction::*; - /// # let model = AutosarModel::new(); - /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); - /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); - /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); - /// # let cluster = system.create_can_cluster("Cluster", &package, &CanClusterSettings::default()).unwrap(); - /// let channel = cluster.create_physical_channel("Channel").unwrap(); - /// let cluster_2 = channel.cluster().unwrap(); - /// assert_eq!(cluster, cluster_2); - /// ``` - pub fn cluster(&self) -> Result { - let cluster_elem = self.0.named_parent()?.unwrap(); - CanCluster::try_from(cluster_elem) - } -} - -//################################################################## - -/// An `EcuInstance` needs a `CanCommunicationController` in order to connect to a CAN cluster. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct CanCommunicationController(Element); -abstraction_element!(CanCommunicationController, CanCommunicationController); - -impl CanCommunicationController { - // create a new CanCommunicationController - called by EcuInstance::create_can_communication_controller - pub(crate) fn new(name: &str, ecu: &EcuInstance) -> Result { - let commcontrollers = ecu.element().get_or_create_sub_element(ElementName::CommControllers)?; - let ctrl = commcontrollers.create_named_sub_element(ElementName::CanCommunicationController, name)?; - let _canccc = ctrl - .create_sub_element(ElementName::CanCommunicationControllerVariants)? - .create_sub_element(ElementName::CanCommunicationControllerConditional)?; - - Ok(Self(ctrl)) - } - - /// return an iterator over the [`CanPhysicalChannel`]s connected to this controller - /// - /// # Example - /// - /// ``` - /// # use autosar_data::*; - /// # use autosar_data_abstraction::*; - /// # let model = AutosarModel::new(); - /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); - /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); - /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); - /// # let ecu_instance = system.create_ecu_instance("ecu_name", &package).unwrap(); - /// let can_controller = ecu_instance.create_can_communication_controller("CanCtrl").unwrap(); - /// # let cluster = system.create_can_cluster("Cluster", &package, &CanClusterSettings::default()).unwrap(); - /// # let physical_channel = cluster.create_physical_channel("Channel").unwrap(); - /// can_controller.connect_physical_channel("connection", &physical_channel).unwrap(); - /// for channel in can_controller.connected_channels() { - /// // ... - /// } - /// ``` - /// - /// # Errors - /// - /// - [`AutosarAbstractionError::ModelError`] An error occurred in the Autosar model while trying to create the ECU-INSTANCE - #[must_use] - pub fn connected_channels(&self) -> CanCtrlChannelsIterator { - if let Ok(ecu) = self.ecu_instance().map(|ecuinstance| ecuinstance.element().clone()) { - CanCtrlChannelsIterator::new(self, &ecu) - } else { - CanCtrlChannelsIterator { - connector_iter: None, - comm_controller: self.0.clone(), - model: Err(AutosarDataError::ElementNotFound), - } - } - } - - /// get the [`EcuInstance`] that contains this `CanCommunicationController` - /// - /// # Example - /// - /// ``` - /// # use autosar_data::*; - /// # use autosar_data_abstraction::*; - /// # let model = AutosarModel::new(); - /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); - /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); - /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); - /// # let ecu_instance = system.create_ecu_instance("ecu_name", &package).unwrap(); - /// let can_controller = ecu_instance.create_can_communication_controller("CanCtrl").unwrap(); - /// assert_eq!(ecu_instance, can_controller.ecu_instance().unwrap()); - /// ``` - /// - /// # Errors - /// - /// - [`AutosarAbstractionError::ModelError`] An error occurred in the Autosar model while trying to create the ECU-INSTANCE - pub fn ecu_instance(&self) -> Result { - // unwrapping is safe here - self.0.named_parent() cannot return Ok(None). - // the CanCommunicationController is always a child of an EcuInstance, - // or else it is deleted and named_parent() return Err(...), which is handled by the ? - let ecu: Element = self.0.named_parent()?.unwrap(); - EcuInstance::try_from(ecu) - } - - /// Connect this [`CanCommunicationController`] inside an [`EcuInstance`] to a [`CanPhysicalChannel`] in the [`crate::System`] - /// - /// Creates a CanCommunicationConnector in the [`EcuInstance`] that contains this [`CanCommunicationController`]. - /// - /// This function establishes the relationships: - /// - [`CanPhysicalChannel`] -> CanCommunicationConnector - /// - CanCommunicationConnector -> [`CanCommunicationController`] - /// - /// # Example - /// - /// ``` - /// # use autosar_data::*; - /// # use autosar_data_abstraction::*; - /// # let model = AutosarModel::new(); - /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); - /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); - /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); - /// # let ecu_instance = system.create_ecu_instance("ecu_name", &package).unwrap(); - /// let can_controller = ecu_instance.create_can_communication_controller("CanCtrl").unwrap(); - /// # let cluster = system.create_can_cluster("Cluster", &package, &CanClusterSettings::default()).unwrap(); - /// # let physical_channel = cluster.create_physical_channel("Channel").unwrap(); - /// can_controller.connect_physical_channel("connection", &physical_channel).unwrap(); - /// ``` - /// - /// # Errors - /// - /// - [`AutosarAbstractionError::ModelError`] An error occurred in the Autosar model while trying to create the ECU-INSTANCE - pub fn connect_physical_channel( - &self, - connection_name: &str, - can_channel: &CanPhysicalChannel, - ) -> Result<(), AutosarAbstractionError> { - let ecu = self.0.named_parent()?.unwrap(); - // check that there is no existing connector for this CanCommunicationController - if let Some(connectors) = ecu.get_sub_element(ElementName::Connectors) { - for connector in connectors.sub_elements() { - // Does the existing connector reference this CanCommunicationController? - // A CanCommunicationController can only connect to a single CAN cluster, so a second - // connector cannot be created. - if let Some(ccref) = connector.get_sub_element(ElementName::CommControllerRef) { - if let Ok(commcontroller_of_connector) = ccref.get_reference_target() { - if commcontroller_of_connector == self.0 { - return Err(AutosarAbstractionError::ItemAlreadyExists); - } - } - } - } - } - // create a new connector - let connectors = ecu.get_or_create_sub_element(ElementName::Connectors)?; - let connector = connectors.create_named_sub_element(ElementName::CanCommunicationConnector, connection_name)?; - connector - .create_sub_element(ElementName::CommControllerRef) - .and_then(|refelem| refelem.set_reference_target(&self.0))?; - - let channel_connctor_refs = can_channel - .element() - .get_or_create_sub_element(ElementName::CommConnectors)?; - channel_connctor_refs - .create_sub_element(ElementName::CommunicationConnectorRefConditional) - .and_then(|ccrc| ccrc.create_sub_element(ElementName::CommunicationConnectorRef)) - .and_then(|ccr| ccr.set_reference_target(&connector))?; - - Ok(()) - } -} - -//################################################################## - -#[doc(hidden)] -pub struct CanCtrlChannelsIterator { - connector_iter: Option, - comm_controller: Element, - model: Result, -} - -impl CanCtrlChannelsIterator { - fn new(controller: &CanCommunicationController, ecu: &Element) -> Self { - let iter = ecu.get_sub_element(ElementName::Connectors).map(|c| c.sub_elements()); - let comm_controller = controller.element().clone(); - let model = comm_controller.model(); - Self { - connector_iter: iter, - comm_controller, - model, - } - } -} - -impl Iterator for CanCtrlChannelsIterator { - type Item = CanPhysicalChannel; - - fn next(&mut self) -> Option { - let model = self.model.as_ref().ok()?; - let connector_iter = self.connector_iter.as_mut()?; - for connector in connector_iter.by_ref() { - if connector.element_name() == ElementName::CanCommunicationConnector { - if let Some(commcontroller_of_connector) = connector - .get_sub_element(ElementName::CommControllerRef) - .and_then(|ccr| ccr.get_reference_target().ok()) - { - if commcontroller_of_connector == self.comm_controller { - for ref_origin in model - .get_references_to(&connector.path().ok()?) - .iter() - .filter_map(WeakElement::upgrade) - .filter_map(|elem| elem.named_parent().ok().flatten()) - { - // This assumes that each connector will only ever be referenced by at most one - // PhysicalChannel, which is true for well-formed files. - if ref_origin.element_name() == ElementName::CanPhysicalChannel { - return Some(CanPhysicalChannel(ref_origin)); - } - } - } - } - } - } - None - } -} - -//################################################################## - -/// settings for a CAN cluster -impl CanClusterSettings { - /// create a new `CanClusterSettings` object - #[must_use] - pub fn new() -> Self { - Self { - baudrate: 500_000, - can_fd_baudrate: None, - can_xl_baudrate: None, - } - } -} - -impl Default for CanClusterSettings { - fn default() -> Self { - Self::new() - } -} - -//################################################################## - -#[cfg(test)] -mod test { - use crate::{System, SystemCategory}; - - use super::*; - use autosar_data::AutosarVersion; - - #[test] - fn cluster() { - let model = AutosarModel::new(); - model.create_file("filename", AutosarVersion::Autosar_00051).unwrap(); - let pkg = ArPackage::get_or_create(&model, "/test").unwrap(); - let system = System::new("System", &pkg, SystemCategory::SystemDescription).unwrap(); - - let pkg2 = ArPackage::get_or_create(&model, "/can").unwrap(); - // create the CAN cluster CanCluster - let settings = CanClusterSettings::default(); - let result = system.create_can_cluster("CanCluster", &pkg2, &settings); - assert!(result.is_ok()); - let cluster = result.unwrap(); - // creating the same cluster again is not possible - let settings = CanClusterSettings::default(); - let result = system.create_can_cluster("CanCluster", &pkg2, &settings); - assert!(result.is_err()); - - // settings for CanFd - let mut settings = cluster.get_settings(); - assert!(settings.can_fd_baudrate.is_none()); - settings.can_fd_baudrate = Some(2_000_000); - cluster.update_settings(&settings); - let mut settings = cluster.get_settings(); - assert!(settings.can_fd_baudrate.is_some()); - // add setings for CanXL, remove CanFd - settings.can_fd_baudrate = None; - settings.can_xl_baudrate = Some(10_000_000); - cluster.update_settings(&settings); - let mut settings = cluster.get_settings(); - assert!(settings.can_fd_baudrate.is_none()); - assert!(settings.can_xl_baudrate.is_some()); - // remove CanXl settings - settings.can_xl_baudrate = None; - cluster.update_settings(&settings); - let settings = cluster.get_settings(); - assert!(settings.can_fd_baudrate.is_none()); - assert!(settings.can_xl_baudrate.is_none()); - - // create a channel - let result = cluster.create_physical_channel("Channel1"); - assert!(result.is_ok()); - // can't create a second channel - let result = cluster.create_physical_channel("Channel2"); - assert!(result.is_err()); - - let pc = cluster.physical_channel(); - assert!(pc.is_some()); - } - - #[test] - fn channel() { - let model = AutosarModel::new(); - model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); - let pkg = ArPackage::get_or_create(&model, "/test").unwrap(); - let system = System::new("System", &pkg, SystemCategory::SystemDescription).unwrap(); - let settings = CanClusterSettings::default(); - let cluster = system.create_can_cluster("CanCluster", &pkg, &settings).unwrap(); - - let channel = cluster.create_physical_channel("channel_name").unwrap(); - let c2 = channel.cluster().unwrap(); - assert_eq!(cluster, c2); - } - - #[test] - fn controller() { - let model = AutosarModel::new(); - model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); - let pkg = ArPackage::get_or_create(&model, "/test").unwrap(); - let system = System::new("System", &pkg, SystemCategory::SystemDescription).unwrap(); - let ecu = system.create_ecu_instance("ECU", &pkg).unwrap(); - - // create a controller - let result = ecu.create_can_communication_controller("Controller"); - let controller = result.unwrap(); - - // create some physical channels - let settings = CanClusterSettings::default(); - let cluster = system.create_can_cluster("CanCluster", &pkg, &settings).unwrap(); - let channel1 = cluster.create_physical_channel("C1").unwrap(); - - // connect the controller to channel1 - let result = controller.connect_physical_channel("connection_name1", &channel1); - assert!(result.is_ok()); - // can't connect to the same channel again - let result = controller.connect_physical_channel("connection_name2", &channel1); - assert!(result.is_err()); - - let count = controller.connected_channels().count(); - assert_eq!(count, 1); - - // remove the controller and try to list its connected channels again - let ctrl_parent = controller.0.parent().unwrap().unwrap(); - ctrl_parent.remove_sub_element(controller.0.clone()).unwrap(); - let count = controller.connected_channels().count(); - assert_eq!(count, 0); - } -} diff --git a/autosar-data-abstraction/src/communication/cluster/can.rs b/autosar-data-abstraction/src/communication/cluster/can.rs new file mode 100644 index 0000000..ea1008e --- /dev/null +++ b/autosar-data-abstraction/src/communication/cluster/can.rs @@ -0,0 +1,295 @@ +use crate::communication::{AbstractCluster, CanPhysicalChannel}; +use crate::{abstraction_element, AbstractionElement, ArPackage, AutosarAbstractionError}; +use autosar_data::{Element, ElementName}; + +/// A `CanCluster` contains all configuration items associated with a CAN network. +/// The cluster connects multiple ECUs. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct CanCluster(Element); +abstraction_element!(CanCluster, CanCluster); + +impl CanCluster { + // create a new CanCluster - for internal use. User code should call System::create_can_cluster + pub(crate) fn new( + cluster_name: &str, + package: &ArPackage, + settings: &CanClusterSettings, + ) -> Result { + let elem_pkg_elements = package.element().get_or_create_sub_element(ElementName::Elements)?; + let elem_cluster = elem_pkg_elements.create_named_sub_element(ElementName::CanCluster, cluster_name)?; + if let Ok(cluster_content) = elem_cluster + .create_sub_element(ElementName::CanClusterVariants) + .and_then(|ccv| ccv.create_sub_element(ElementName::CanClusterConditional)) + { + let _ = cluster_content + .create_sub_element(ElementName::ProtocolName) + .and_then(|pn| pn.set_character_data("CAN")); + + let _ = cluster_content.create_sub_element(ElementName::PhysicalChannels); + } + + let can_cluster = CanCluster(elem_cluster); + can_cluster.update_settings(settings); + + Ok(can_cluster) + } + + /// Update the settings of this `CanCluster` with new values for the baudrates + /// + /// The baudrates for CanFD and CanXL are optional. + /// If they are set to None in the settings, then corresponding elements in the model will be removed. + /// + /// # Example + /// + /// ``` + /// # use autosar_data::*; + /// # use autosar_data_abstraction::*; + /// # use autosar_data_abstraction::communication::*; + /// # let model = AutosarModel::new(); + /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); + /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); + /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); + /// let cluster = system.create_can_cluster("Cluster", &package, &CanClusterSettings::default()).unwrap(); + /// let mut settings = cluster.get_settings(); + /// settings.can_fd_baudrate = Some(2000000); + /// cluster.update_settings(&settings); + /// ``` + pub fn update_settings(&self, settings: &CanClusterSettings) { + if let Ok(cluster_content) = self + .0 + .get_or_create_sub_element(ElementName::CanClusterVariants) + .and_then(|ccv| ccv.get_or_create_sub_element(ElementName::CanClusterConditional)) + { + let _ = cluster_content + .create_sub_element(ElementName::Baudrate) + .and_then(|br| br.set_character_data(settings.baudrate.to_string())); + if let Some(can_fd_baudrate) = settings.can_fd_baudrate { + let _ = cluster_content + .create_sub_element(ElementName::CanFdBaudrate) + .and_then(|cfbr| cfbr.set_character_data(can_fd_baudrate.to_string())); + } else if let Some(cfbr) = cluster_content.get_sub_element(ElementName::CanFdBaudrate) { + let _ = cluster_content.remove_sub_element(cfbr); + } + if let Some(can_xl_baudrate) = settings.can_xl_baudrate { + cluster_content + .create_sub_element(ElementName::CanXlBaudrate) + .and_then(|cxbr| cxbr.set_character_data(can_xl_baudrate.to_string())) + .unwrap(); + } else if let Some(cxbr) = cluster_content.get_sub_element(ElementName::CanXlBaudrate) { + let _ = cluster_content.remove_sub_element(cxbr); + } + } + } + + /// get the setings of this `CanCluster` + /// + /// # Example + /// + /// ``` + /// # use autosar_data::*; + /// # use autosar_data_abstraction::*; + /// # use autosar_data_abstraction::communication::*; + /// # let model = AutosarModel::new(); + /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); + /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); + /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); + /// let settings = CanClusterSettings {baudrate: 500000, can_fd_baudrate: None, can_xl_baudrate: None}; + /// let cluster = system.create_can_cluster("Cluster", &package, &settings).unwrap(); + /// let settings2 = cluster.get_settings(); + /// assert_eq!(settings, settings2); + /// ``` + #[must_use] + pub fn get_settings(&self) -> CanClusterSettings { + let mut settings = CanClusterSettings { + baudrate: 0, + can_fd_baudrate: None, + can_xl_baudrate: None, + }; + if let Some(cluster_content) = self + .0 + .get_sub_element(ElementName::CanClusterVariants) + .and_then(|ccv| ccv.get_sub_element(ElementName::CanClusterConditional)) + { + if let Some(baudrate) = cluster_content + .get_sub_element(ElementName::Baudrate) + .and_then(|br| br.character_data()) + .and_then(|cdata| cdata.decode_integer()) + { + settings.baudrate = baudrate; + } + + settings.can_fd_baudrate = cluster_content + .get_sub_element(ElementName::CanFdBaudrate) + .and_then(|br| br.character_data()) + .and_then(|cdata| cdata.decode_integer()); + + settings.can_xl_baudrate = cluster_content + .get_sub_element(ElementName::CanXlBaudrate) + .and_then(|br| br.character_data()) + .and_then(|cdata| cdata.decode_integer()); + } + settings + } + + /// Create a new physical channel for the cluster + /// + /// A can cluster must contain exactly one physical channel; trying to add a second one triggers an error. + /// + /// # Example + /// + /// ``` + /// # use autosar_data::*; + /// # use autosar_data_abstraction::*; + /// # use autosar_data_abstraction::communication::*; + /// # let model = AutosarModel::new(); + /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); + /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); + /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); + /// let cluster = system.create_can_cluster("Cluster", &package, &CanClusterSettings::default()).unwrap(); + /// let channel = cluster.create_physical_channel("Channel").unwrap(); + /// ``` + /// + /// # Errors + /// + /// - [`AutosarAbstractionError::ItemAlreadyExists`] There is already a physical channel in this CAN cluster + /// - [`AutosarAbstractionError::ModelError`] An error occurred in the Autosar model while trying to create the ECU-INSTANCE + pub fn create_physical_channel(&self, channel_name: &str) -> Result { + let phys_channels = self + .0 + .get_or_create_sub_element(ElementName::CanClusterVariants)? + .get_or_create_sub_element(ElementName::CanClusterConditional)? + .get_or_create_sub_element(ElementName::PhysicalChannels)?; + + if phys_channels.sub_elements().count() != 0 { + return Err(AutosarAbstractionError::ItemAlreadyExists); + } + + let channel = phys_channels.create_named_sub_element(ElementName::CanPhysicalChannel, channel_name)?; + + CanPhysicalChannel::try_from(channel) + } + + /// return the CanPhysicalChannel of the Cluster, if it has been created + /// + /// # Example + /// + /// ``` + /// # use autosar_data::*; + /// # use autosar_data_abstraction::*; + /// # use autosar_data_abstraction::communication::*; + /// # let model = AutosarModel::new(); + /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); + /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); + /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); + /// # let cluster = system.create_can_cluster("Cluster", &package, &CanClusterSettings::default()).unwrap(); + /// # let can_channel = cluster.create_physical_channel("Channel").unwrap(); + /// let channel = cluster.physical_channel().unwrap(); + /// # assert_eq!(channel, can_channel); + /// ```` + #[must_use] + pub fn physical_channel(&self) -> Option { + let channel = self + .0 + .get_sub_element(ElementName::CanClusterVariants)? + .get_sub_element(ElementName::CanClusterConditional)? + .get_sub_element(ElementName::PhysicalChannels)? + .get_sub_element(ElementName::CanPhysicalChannel)?; + CanPhysicalChannel::try_from(channel).ok() + } +} + +impl AbstractCluster for CanCluster {} + +//################################################################## + +/// Settings for a CAN cluster +#[derive(Debug, Clone, PartialEq)] +pub struct CanClusterSettings { + pub baudrate: u32, + pub can_fd_baudrate: Option, + pub can_xl_baudrate: Option, +} + +/// settings for a CAN cluster +impl CanClusterSettings { + /// create a new `CanClusterSettings` object + #[must_use] + pub fn new() -> Self { + Self { + baudrate: 500_000, + can_fd_baudrate: None, + can_xl_baudrate: None, + } + } +} + +impl Default for CanClusterSettings { + fn default() -> Self { + Self::new() + } +} + +//################################################################## + +#[cfg(test)] +mod test { + use crate::{ + communication::{AbstractCluster, CanClusterSettings}, + ArPackage, System, SystemCategory, + }; + use autosar_data::{AutosarModel, AutosarVersion}; + + #[test] + fn cluster() { + let model = AutosarModel::new(); + model.create_file("filename", AutosarVersion::Autosar_00051).unwrap(); + let pkg = ArPackage::get_or_create(&model, "/test").unwrap(); + let system = System::new("System", &pkg, SystemCategory::SystemDescription).unwrap(); + + let pkg2 = ArPackage::get_or_create(&model, "/can").unwrap(); + // create the CAN cluster CanCluster + let settings = CanClusterSettings::default(); + let result = system.create_can_cluster("CanCluster", &pkg2, &settings); + assert!(result.is_ok()); + let cluster = result.unwrap(); + // creating the same cluster again is not possible + let settings = CanClusterSettings::default(); + let result = system.create_can_cluster("CanCluster", &pkg2, &settings); + assert!(result.is_err()); + + // system link + let linked_system = cluster.system().unwrap(); + assert_eq!(linked_system, system); + + // settings for CanFd + let mut settings = cluster.get_settings(); + assert!(settings.can_fd_baudrate.is_none()); + settings.can_fd_baudrate = Some(2_000_000); + cluster.update_settings(&settings); + let mut settings = cluster.get_settings(); + assert!(settings.can_fd_baudrate.is_some()); + // add setings for CanXL, remove CanFd + settings.can_fd_baudrate = None; + settings.can_xl_baudrate = Some(10_000_000); + cluster.update_settings(&settings); + let mut settings = cluster.get_settings(); + assert!(settings.can_fd_baudrate.is_none()); + assert!(settings.can_xl_baudrate.is_some()); + // remove CanXl settings + settings.can_xl_baudrate = None; + cluster.update_settings(&settings); + let settings = cluster.get_settings(); + assert!(settings.can_fd_baudrate.is_none()); + assert!(settings.can_xl_baudrate.is_none()); + + // create a channel + let result = cluster.create_physical_channel("Channel1"); + assert!(result.is_ok()); + // can't create a second channel + let result = cluster.create_physical_channel("Channel2"); + assert!(result.is_err()); + + let pc = cluster.physical_channel(); + assert!(pc.is_some()); + } +} diff --git a/autosar-data-abstraction/src/communication/cluster/ethernet.rs b/autosar-data-abstraction/src/communication/cluster/ethernet.rs new file mode 100644 index 0000000..c5b669e --- /dev/null +++ b/autosar-data-abstraction/src/communication/cluster/ethernet.rs @@ -0,0 +1,203 @@ +use crate::communication::{AbstractCluster, EthernetPhysicalChannel, EthernetVlanInfo}; +use crate::{abstraction_element, basic_element_iterator, AbstractionElement, ArPackage, AutosarAbstractionError}; +use autosar_data::{Element, ElementName}; + +/// An `EthernetCluster` contains all configuration items associated with an ethernet network. +/// The cluster connects multiple ECUs. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct EthernetCluster(Element); +abstraction_element!(EthernetCluster, EthernetCluster); + +impl EthernetCluster { + // create a new EthernetCluster - for internal use. User code should call System::create_ethernet_cluster + pub(crate) fn new(cluster_name: &str, package: &ArPackage) -> Result { + let elem_pkg_elements = package.element().get_or_create_sub_element(ElementName::Elements)?; + let elem_cluster = elem_pkg_elements.create_named_sub_element(ElementName::EthernetCluster, cluster_name)?; + if let Ok(cluster_content) = elem_cluster + .create_sub_element(ElementName::EthernetClusterVariants) + .and_then(|ecv| ecv.create_sub_element(ElementName::EthernetClusterConditional)) + { + let _ = cluster_content.create_sub_element(ElementName::PhysicalChannels); + } + + Ok(EthernetCluster(elem_cluster)) + } + + /// Create a new physical channel for the cluster + /// + /// The supplied VLAN info must be unique - there cannot be two VLANs with the same vlan identifier. + /// One channel may be created without VLAN information; it carries untagged traffic. + /// + /// # Example + /// + /// ``` + /// # use autosar_data::*; + /// # use autosar_data_abstraction::*; + /// # use autosar_data_abstraction::communication::*; + /// # let model = AutosarModel::new(); + /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); + /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); + /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); + /// let cluster = system.create_ethernet_cluster("Cluster", &package).unwrap(); + /// let vlan_info = EthernetVlanInfo { + /// vlan_name: "VLAN_1".to_string(), + /// vlan_id: 1, + /// }; + /// let channel = cluster.create_physical_channel("Channel", Some(vlan_info)).unwrap(); + /// ``` + /// + /// # Errors + /// + /// - [`AutosarAbstractionError::ItemAlreadyExists`] There is already a physical channel for this VLAN + /// - [`AutosarAbstractionError::ModelError`] An error occurred in the Autosar model while trying to create the ECU-INSTANCE + pub fn create_physical_channel( + &self, + channel_name: &str, + vlan_info: Option, + ) -> Result { + let phys_channels = self + .0 + .get_or_create_sub_element(ElementName::EthernetClusterVariants)? + .get_or_create_sub_element(ElementName::EthernetClusterConditional)? + .get_or_create_sub_element(ElementName::PhysicalChannels)?; + + // make sure there is no other channel with the same VLAN info + // If vlan_info is None, then there must be no existing channel without VLAN info + for existing_channel in phys_channels.sub_elements() { + let existing_vlan_info = EthernetPhysicalChannel::try_from(existing_channel) + .ok() + .and_then(|channel| channel.get_vlan_info()); + if let (Some(v1), Some(v2)) = (&vlan_info, &existing_vlan_info) { + if v1.vlan_id == v2.vlan_id { + // the vlan identifier of an existing channel matches the new vlan identifier + return Err(AutosarAbstractionError::ItemAlreadyExists); + } + } else if existing_vlan_info.is_none() && vlan_info.is_none() { + // the new channel is for untagged traffic (no VLAN), but there is already a channel for untagged traffic + return Err(AutosarAbstractionError::ItemAlreadyExists); + } + } + + let channel = phys_channels.create_named_sub_element(ElementName::EthernetPhysicalChannel, channel_name)?; + // set the vlan info + if let Some(vlan_info) = vlan_info { + let _ = channel + .create_named_sub_element(ElementName::Vlan, &vlan_info.vlan_name) + .and_then(|vlan| vlan.create_sub_element(ElementName::VlanIdentifier)) + .and_then(|vlan_id| vlan_id.set_character_data(vlan_info.vlan_id.to_string())); + } + // always set CATEGORY = WIRED, since this is the common case + let _ = channel + .create_sub_element(ElementName::Category) + .and_then(|cat| cat.set_character_data("WIRED")); + + EthernetPhysicalChannel::try_from(channel) + } + + /// returns an iterator over all [`EthernetPhysicalChannel`]s in the cluster + /// + /// # Example + /// + /// ``` + /// # use autosar_data::*; + /// # use autosar_data_abstraction::*; + /// # let model = AutosarModel::new(); + /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); + /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); + /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); + /// # let cluster = system.create_ethernet_cluster("Cluster", &package).unwrap(); + /// cluster.create_physical_channel("Channel", None).unwrap(); + /// for channel in cluster.physical_channels() { + /// // ... + /// } + /// ``` + #[must_use] + pub fn physical_channels(&self) -> EthernetClusterChannelsIterator { + EthernetClusterChannelsIterator::new( + self.element() + .get_sub_element(ElementName::EthernetClusterVariants) + .and_then(|ecv| ecv.get_sub_element(ElementName::EthernetClusterConditional)) + .and_then(|ecc| ecc.get_sub_element(ElementName::PhysicalChannels)), + ) + } +} + +impl AbstractCluster for EthernetCluster {} + +//################################################################## + +basic_element_iterator!(EthernetClusterChannelsIterator, EthernetPhysicalChannel); + +//################################################################## + +#[cfg(test)] +mod test { + use crate::{ + communication::{AbstractCluster, EthernetVlanInfo}, + ArPackage, System, SystemCategory, + }; + use autosar_data::{AutosarModel, AutosarVersion}; + + #[test] + fn cluster() { + let model = AutosarModel::new(); + model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); + let pkg = ArPackage::get_or_create(&model, "/test").unwrap(); + let system = System::new("System", &pkg, SystemCategory::SystemDescription).unwrap(); + + let pkg2 = ArPackage::get_or_create(&model, "/ethernet").unwrap(); + // create the ethernet cluster EthCluster + let result = system.create_ethernet_cluster("EthCluster", &pkg2); + assert!(result.is_ok()); + let cluster = result.unwrap(); + // creating the same cluster again is not possible + let result = system.create_ethernet_cluster("EthCluster", &pkg2); + assert!(result.is_err()); + + // system link + let linked_system = cluster.system().unwrap(); + assert_eq!(linked_system, system); + + // create an untagged channel + let result = cluster.create_physical_channel("Channel1", None); + assert!(result.is_ok()); + // can't create a second untagged channel + let result = cluster.create_physical_channel("Channel2", None); + assert!(result.is_err()); + + // create a channel for VLAN 1 + let vlan_info = EthernetVlanInfo { + vlan_name: "VLAN_1".to_string(), + vlan_id: 1, + }; + let result = cluster.create_physical_channel("Channel3", Some(vlan_info)); + assert!(result.is_ok()); + + // can't create a second channel called Channel3 + let vlan_info = EthernetVlanInfo { + vlan_name: "VLAN_2".to_string(), + vlan_id: 2, + }; + let result = cluster.create_physical_channel("Channel3", Some(vlan_info)); + assert!(result.is_err()); + + // create a channel for VLAN 2 + let vlan_info = EthernetVlanInfo { + vlan_name: "VLAN_2".to_string(), + vlan_id: 2, + }; + let result = cluster.create_physical_channel("Channel4", Some(vlan_info)); + assert!(result.is_ok()); + + // can't create a second channel for VLAN 2 + let vlan_info = EthernetVlanInfo { + vlan_name: "VLAN_2".to_string(), + vlan_id: 2, + }; + let result = cluster.create_physical_channel("Channel5", Some(vlan_info)); + assert!(result.is_err()); + + let count = cluster.physical_channels().count(); + assert_eq!(count, 3); + } +} diff --git a/autosar-data-abstraction/src/flexray.rs b/autosar-data-abstraction/src/communication/cluster/flexray.rs similarity index 70% rename from autosar-data-abstraction/src/flexray.rs rename to autosar-data-abstraction/src/communication/cluster/flexray.rs index af3265a..02cf332 100644 --- a/autosar-data-abstraction/src/flexray.rs +++ b/autosar-data-abstraction/src/communication/cluster/flexray.rs @@ -1,113 +1,9 @@ -use crate::{abstraction_element, AbstractionElement, ArPackage, AutosarAbstractionError, EcuInstance}; -use autosar_data::{ - AutosarDataError, AutosarModel, CharacterData, Element, ElementName, ElementsIterator, EnumItem, WeakElement, +use crate::{ + abstraction_element, + communication::{AbstractCluster, FlexrayChannelName, FlexrayPhysicalChannel}, + AbstractionElement, ArPackage, AutosarAbstractionError, }; - -/// Configuration settings of the Flexray cluster. -/// -/// Refer to `AUTOSAR_TPS_SystemTemplate.pdf`, chapter 3.3.4 and Flexay 2.1 standard, appendix A + B -/// for the meanings and ranges of these parameters. -#[derive(Debug, Clone, PartialEq)] -pub struct FlexrayClusterSettings { - /// baud rate of the Flexray cluster: one of 10000000 (10Mbit/s), 5000000 (5Mbit/s), 2500000 (2.5Mbit/s) - pub baudrate: u32, - /// gdActionPointOffset: Number of macroticks the action point is offset from the - /// beginning of a static slot or symbol window: in the range 1 - 63 MT - pub action_point_offset: u8, - /// gdBit: Nominal bit time; inverse of the baudrate: 0.1µs, 0.2µs, or 0.4µs - pub bit: f64, - /// gdCASRxLowMax: Upper limit of the CAS acceptance window. Range 67 - 99 gdBit - pub cas_rx_low_max: u8, - /// gColdStartAttempts: Maximum number of times a node in the cluster is permitted to attempt to start the cluster. - /// Range: 2 - 31 - pub cold_start_attempts: u8, - /// gdCycle: Length of the cycle in seconds. Range: 10µs - 16000µs; typically 0.005s - pub cycle: f64, - /// cCycleCountMax. Must be 63 to conform with Flexray 2.1 - pub cycle_count_max: u8, - /// Indicates whether network idle time (NIT) error status should be detected - pub detect_nit_error: bool, - /// gdDynamicSlotIdlePhase: Duration of the idle phase within a dynamic slot. Range 0 - 2 - pub dynamic_slot_idle_phase: u8, - /// Duration for which the bitstrobing is paused after transmission - pub ignore_after_tx: u16, - /// gListenNoise: Upper limit for the startup listen timeout and wakeup listen timeout in the presence of noise. - /// Range 2 - 16 - pub listen_noise: u8, - /// gMacroPerCycle: Number of macroticks in a communication cycle. Range: 10 - 16000 MT - pub macro_per_cycle: u16, - /// macrotick_duration \[s\] = cycle \[s\] / macro_per_cycle - pub macrotick_duration: f64, - /// gMaxWithoutClockCorrectionFatal: Threshold used for testing the vClockCorrectionFailed counter. - /// Range: - 15 even/odd cycle pairs - pub max_without_clock_correction_fatal: u8, - /// gMaxWithoutClockCorrectionPassive: Threshold used for testing the vClockCorrectionFailed counter. - /// Range: 1 - 15 even/odd cycle pairs - pub max_without_clock_correction_passive: u8, - /// gdMinislotActionPointOffset: Number of macroticks the minislot action point is offset from the beginning of a minislot. Range: 1 - 31 MT - pub minislot_action_point_offset: u8, - /// gdMinislot: Duration of a minislot. Range: 2 - 63 MT - pub minislot_duration: u8, - /// gdNIT: Duration of the Network Idle Time. 2 - 805 MT - pub network_idle_time: u16, - /// gNetworkManagementVectorLength: Length of the Network Management vector in a cluster. Range: 0 - 12 bytes - pub network_management_vector_length: u8, - /// gNumberOfMinislots: Number of minislots in the dynamic segment. Range: 0 - 7986 - pub number_of_minislots: u16, - /// gNumberOfStaticSlots: Number of static slots in the static segment. Range: 2 - 1023 - pub number_of_static_slots: u16, - /// gOffsetCorrectionStart: Start of the offset correction phase within the NIT, as MT from the start of the cycle. Range: 9 - 15999 MT - pub offset_correction_start: u16, - /// gPayloadLengthStatic: Payload length of a static frame, in two byte words. Range: 0 - 127 words - pub payload_length_static: u16, - /// Additional timespan in macroticks which takes jitter into account - pub safety_margin: u16, - /// gdSampleClockPeriod: Sample clock period. One of [1.25e-8 s, 2.5e-8 s, 5e-8 s] - pub sample_clock_period: Option, - /// gdStaticSlot: Duration of a static slot. Range: 4 - 661 MT - pub static_slot_duration: u16, - /// gdSymbolWindow: Duration of the symbol window. Range: 0 - 142 MT - pub symbol_window: u8, - // according to the Flexray spec: "gdActionPointOffset parameter also specifies the action point in the symbol window" - // so this parameter is useless? - pub symbol_window_action_point_offset: Option, - /// gSyncNodeMax: Maximum number of nodes that may send frames with the sync frame indicator bit set to one. Range: 2 - 15 - pub sync_frame_id_count_max: u8, - /// The duration of timer t_TrcvStdbyDelay in seconds. - pub transceiver_standby_delay: Option, - /// gdTSSTransmitter: Number of bits in the Transmission Start Sequence. Range: 3 - 15 gdBit - pub transmission_start_sequence_duration: u8, - /// gdWakeupSymbolRxIdle: Number of bits used by the node to test the duration - /// of the 'idle' portion of a received wakeup symbol. Range: 14 - 59 gdBit - pub wakeup_rx_idle: u16, - /// gdWakeupSymbolRxLow: Number of bits used by the node to test the LOW - /// portion of a received wakeup symbol. Range 11 - 59 gdBit - pub wakeup_rx_low: u8, - /// gdWakeupSymbolRxWindow: The size of the window used to detect wakeups. Range: 76 - 301 gdBit - pub wakeup_rx_window: u16, - /// gdWakeupSymbolTxLow: Number of bits used by the node to transmit the LOW part of a wakeup symbol. Range: 15 - 60 gdBit - pub wakeup_tx_active: u16, - /// gdWakeupSymbolTxIdle: Number of bits used by the node to transmit the 'idle' part of a wakeup symbol. Range: 45 - 180 gdBit - pub wakeup_tx_idle: u16, -} - -/// Information about the flexray physical channels present inside a cluster -#[derive(Debug, Clone, PartialEq)] -pub struct FlexrayPhysicalChannelsInfo { - /// channel A - it contains FlexrayChannelName::A - pub channel_a: Option, - /// channel B - it contains FlexrayChannelName::B - pub channel_b: Option, -} - -/// A flexray cluster may contain the channels A and/or B. -/// -/// This enum is an abstraction over the \ element. -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum FlexrayChannelName { - A, - B, -} +use autosar_data::{CharacterData, Element, ElementName, EnumItem}; /// A `FlexrayCluster` contains all configuration items associated with a Flexray network. /// The cluster connects multiple ECUs. @@ -130,10 +26,10 @@ impl FlexrayCluster { { cluster_content .create_sub_element(ElementName::ProtocolName) - .and_then(|pn| pn.set_character_data(CharacterData::String("FlexRay".to_string())))?; + .and_then(|pn| pn.set_character_data("FlexRay"))?; cluster_content .create_sub_element(ElementName::ProtocolVersion) - .and_then(|pv| pv.set_character_data(CharacterData::String("2.1".to_string())))?; + .and_then(|pv| pv.set_character_data("2.1"))?; cluster_content.create_sub_element(ElementName::PhysicalChannels)?; } @@ -159,6 +55,7 @@ impl FlexrayCluster { /// ``` /// # use autosar_data::*; /// # use autosar_data_abstraction::*; + /// # use autosar_data_abstraction::communication::*; /// # let model = AutosarModel::new(); /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); @@ -176,163 +73,125 @@ impl FlexrayCluster { { let _ = cluster_content .get_or_create_sub_element(ElementName::Baudrate) - .and_then(|br| br.set_character_data(CharacterData::String(settings.baudrate.to_string()))); + .and_then(|br| br.set_character_data(settings.baudrate.to_string())); let _ = cluster_content .get_or_create_sub_element(ElementName::ActionPointOffset) - .and_then(|apo| { - apo.set_character_data(CharacterData::String(settings.action_point_offset.to_string())) - }); + .and_then(|apo| apo.set_character_data(settings.action_point_offset.to_string())); let _ = cluster_content .get_or_create_sub_element(ElementName::Bit) - .and_then(|bit| bit.set_character_data(CharacterData::Double(settings.bit))); + .and_then(|bit| bit.set_character_data(settings.bit)); let _ = cluster_content .get_or_create_sub_element(ElementName::CasRxLowMax) - .and_then(|crlm| crlm.set_character_data(CharacterData::String(settings.cas_rx_low_max.to_string()))); + .and_then(|crlm| crlm.set_character_data(settings.cas_rx_low_max.to_string())); let _ = cluster_content .get_or_create_sub_element(ElementName::ColdStartAttempts) - .and_then(|csa| { - csa.set_character_data(CharacterData::String(settings.cold_start_attempts.to_string())) - }); + .and_then(|csa| csa.set_character_data(settings.cold_start_attempts.to_string())); let _ = cluster_content .get_or_create_sub_element(ElementName::Cycle) - .and_then(|apo| apo.set_character_data(CharacterData::Double(settings.cycle))); + .and_then(|apo| apo.set_character_data(settings.cycle)); let _ = cluster_content .get_or_create_sub_element(ElementName::CycleCountMax) - .and_then(|ccm| ccm.set_character_data(CharacterData::String(settings.cycle_count_max.to_string()))); + .and_then(|ccm| ccm.set_character_data(settings.cycle_count_max.to_string())); let _ = cluster_content .get_or_create_sub_element(ElementName::DetectNitError) - .and_then(|dne| dne.set_character_data(CharacterData::String(settings.detect_nit_error.to_string()))); + .and_then(|dne| dne.set_character_data(settings.detect_nit_error.to_string())); let _ = cluster_content .get_or_create_sub_element(ElementName::DynamicSlotIdlePhase) - .and_then(|dsip| { - dsip.set_character_data(CharacterData::String(settings.dynamic_slot_idle_phase.to_string())) - }); + .and_then(|dsip| dsip.set_character_data(settings.dynamic_slot_idle_phase.to_string())); let _ = cluster_content .get_or_create_sub_element(ElementName::IgnoreAfterTx) - .and_then(|iat| iat.set_character_data(CharacterData::String(settings.ignore_after_tx.to_string()))); + .and_then(|iat| iat.set_character_data(settings.ignore_after_tx.to_string())); let _ = cluster_content .get_or_create_sub_element(ElementName::ListenNoise) - .and_then(|ln| ln.set_character_data(CharacterData::String(settings.listen_noise.to_string()))); + .and_then(|ln| ln.set_character_data(settings.listen_noise.to_string())); let _ = cluster_content .get_or_create_sub_element(ElementName::MacroPerCycle) - .and_then(|mpc| mpc.set_character_data(CharacterData::String(settings.macro_per_cycle.to_string()))); + .and_then(|mpc| mpc.set_character_data(settings.macro_per_cycle.to_string())); let _ = cluster_content .get_or_create_sub_element(ElementName::MacrotickDuration) - .and_then(|mpc| mpc.set_character_data(CharacterData::Double(settings.macrotick_duration))); + .and_then(|mpc| mpc.set_character_data(settings.macrotick_duration)); let _ = cluster_content .get_or_create_sub_element(ElementName::MaxWithoutClockCorrectionFatal) - .and_then(|mwccf| { - mwccf.set_character_data(CharacterData::String( - settings.max_without_clock_correction_fatal.to_string(), - )) - }); + .and_then(|mwccf| mwccf.set_character_data(settings.max_without_clock_correction_fatal.to_string())); let _ = cluster_content .get_or_create_sub_element(ElementName::MaxWithoutClockCorrectionPassive) - .and_then(|mwccp| { - mwccp.set_character_data(CharacterData::String( - settings.max_without_clock_correction_passive.to_string(), - )) - }); + .and_then(|mwccp| mwccp.set_character_data(settings.max_without_clock_correction_passive.to_string())); let _ = cluster_content .get_or_create_sub_element(ElementName::MinislotActionPointOffset) - .and_then(|mapo| { - mapo.set_character_data(CharacterData::String(settings.minislot_action_point_offset.to_string())) - }); + .and_then(|mapo| mapo.set_character_data(settings.minislot_action_point_offset.to_string())); let _ = cluster_content .get_or_create_sub_element(ElementName::MinislotDuration) - .and_then(|md| md.set_character_data(CharacterData::String(settings.minislot_duration.to_string()))); + .and_then(|md| md.set_character_data(settings.minislot_duration.to_string())); let _ = cluster_content .get_or_create_sub_element(ElementName::NetworkIdleTime) - .and_then(|nit| nit.set_character_data(CharacterData::String(settings.network_idle_time.to_string()))); + .and_then(|nit| nit.set_character_data(settings.network_idle_time.to_string())); let _ = cluster_content .get_or_create_sub_element(ElementName::NetworkManagementVectorLength) - .and_then(|nmvl| { - nmvl.set_character_data(CharacterData::String( - settings.network_management_vector_length.to_string(), - )) - }); + .and_then(|nmvl| nmvl.set_character_data(settings.network_management_vector_length.to_string())); let _ = cluster_content .get_or_create_sub_element(ElementName::NumberOfMinislots) - .and_then(|nom| { - nom.set_character_data(CharacterData::String(settings.number_of_minislots.to_string())) - }); + .and_then(|nom| nom.set_character_data(settings.number_of_minislots.to_string())); let _ = cluster_content .get_or_create_sub_element(ElementName::NumberOfStaticSlots) - .and_then(|noss| { - noss.set_character_data(CharacterData::String(settings.number_of_static_slots.to_string())) - }); + .and_then(|noss| noss.set_character_data(settings.number_of_static_slots.to_string())); let _ = cluster_content .get_or_create_sub_element(ElementName::OffsetCorrectionStart) - .and_then(|ocs| { - ocs.set_character_data(CharacterData::String(settings.offset_correction_start.to_string())) - }); + .and_then(|ocs| ocs.set_character_data(settings.offset_correction_start.to_string())); let _ = cluster_content .get_or_create_sub_element(ElementName::PayloadLengthStatic) - .and_then(|pls| { - pls.set_character_data(CharacterData::String(settings.payload_length_static.to_string())) - }); + .and_then(|pls| pls.set_character_data(settings.payload_length_static.to_string())); let _ = cluster_content .get_or_create_sub_element(ElementName::SafetyMargin) - .and_then(|sm| sm.set_character_data(CharacterData::String(settings.safety_margin.to_string()))); + .and_then(|sm| sm.set_character_data(settings.safety_margin.to_string())); if let Some(sample_clock_period) = settings.sample_clock_period { let _ = cluster_content .get_or_create_sub_element(ElementName::SampleClockPeriod) - .and_then(|scp| scp.set_character_data(CharacterData::Double(sample_clock_period))); + .and_then(|scp| scp.set_character_data(sample_clock_period)); } else if let Some(scp) = cluster_content.get_sub_element(ElementName::SampleClockPeriod) { let _ = cluster_content.remove_sub_element(scp); } let _ = cluster_content .get_or_create_sub_element(ElementName::StaticSlotDuration) - .and_then(|ssd| { - ssd.set_character_data(CharacterData::String(settings.static_slot_duration.to_string())) - }); + .and_then(|ssd| ssd.set_character_data(settings.static_slot_duration.to_string())); let _ = cluster_content .get_or_create_sub_element(ElementName::SymbolWindow) - .and_then(|sw| sw.set_character_data(CharacterData::String(settings.symbol_window.to_string()))); + .and_then(|sw| sw.set_character_data(settings.symbol_window.to_string())); if let Some(symbol_window_action_point_offset) = settings.symbol_window_action_point_offset { let _ = cluster_content .get_or_create_sub_element(ElementName::SymbolWindowActionPointOffset) - .and_then(|swapo| { - swapo.set_character_data(CharacterData::String(symbol_window_action_point_offset.to_string())) - }); + .and_then(|swapo| swapo.set_character_data(symbol_window_action_point_offset.to_string())); } else if let Some(swapo) = cluster_content.get_sub_element(ElementName::SymbolWindowActionPointOffset) { let _ = cluster_content.remove_sub_element(swapo); } let _ = cluster_content .get_or_create_sub_element(ElementName::SyncFrameIdCountMax) - .and_then(|sficm| { - sficm.set_character_data(CharacterData::String(settings.sync_frame_id_count_max.to_string())) - }); + .and_then(|sficm| sficm.set_character_data(settings.sync_frame_id_count_max.to_string())); if let Some(transceiver_standby_delay) = settings.transceiver_standby_delay { let _ = cluster_content .get_or_create_sub_element(ElementName::TranceiverStandbyDelay) - .and_then(|tsd| tsd.set_character_data(CharacterData::Double(transceiver_standby_delay))); + .and_then(|tsd| tsd.set_character_data(transceiver_standby_delay)); } else if let Some(tsd) = cluster_content.get_sub_element(ElementName::TranceiverStandbyDelay) { let _ = cluster_content.remove_sub_element(tsd); } let _ = cluster_content .get_or_create_sub_element(ElementName::TransmissionStartSequenceDuration) - .and_then(|tssd| { - tssd.set_character_data(CharacterData::String( - settings.transmission_start_sequence_duration.to_string(), - )) - }); + .and_then(|tssd| tssd.set_character_data(settings.transmission_start_sequence_duration.to_string())); let _ = cluster_content .get_or_create_sub_element(ElementName::WakeupRxIdle) - .and_then(|wri| wri.set_character_data(CharacterData::String(settings.wakeup_rx_idle.to_string()))); + .and_then(|wri| wri.set_character_data(settings.wakeup_rx_idle.to_string())); let _ = cluster_content .get_or_create_sub_element(ElementName::WakeupRxLow) - .and_then(|wrl| wrl.set_character_data(CharacterData::String(settings.wakeup_rx_low.to_string()))); + .and_then(|wrl| wrl.set_character_data(settings.wakeup_rx_low.to_string())); let _ = cluster_content .get_or_create_sub_element(ElementName::WakeupRxWindow) - .and_then(|wrw| wrw.set_character_data(CharacterData::String(settings.wakeup_rx_window.to_string()))); + .and_then(|wrw| wrw.set_character_data(settings.wakeup_rx_window.to_string())); let _ = cluster_content .get_or_create_sub_element(ElementName::WakeupTxActive) - .and_then(|wta| wta.set_character_data(CharacterData::String(settings.wakeup_tx_active.to_string()))); + .and_then(|wta| wta.set_character_data(settings.wakeup_tx_active.to_string())); let _ = cluster_content .get_or_create_sub_element(ElementName::WakeupTxIdle) - .and_then(|wti| wti.set_character_data(CharacterData::String(settings.wakeup_tx_idle.to_string()))); + .and_then(|wti| wti.set_character_data(settings.wakeup_tx_idle.to_string())); } } @@ -343,6 +202,7 @@ impl FlexrayCluster { /// ``` /// # use autosar_data::*; /// # use autosar_data_abstraction::*; + /// # use autosar_data_abstraction::communication::*; /// # let model = AutosarModel::new(); /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); @@ -661,6 +521,7 @@ impl FlexrayCluster { /// ``` /// # use autosar_data::*; /// # use autosar_data_abstraction::*; + /// # use autosar_data_abstraction::communication::*; /// # let model = AutosarModel::new(); /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); @@ -686,7 +547,10 @@ impl FlexrayCluster { .get_or_create_sub_element(ElementName::PhysicalChannels)?; for existing_channel in phys_channels.sub_elements() { - if let Some(existing_channel_name) = FlexrayPhysicalChannel(existing_channel).channel_name() { + if let Some(existing_channel_name) = FlexrayPhysicalChannel::try_from(existing_channel) + .ok() + .and_then(|channel| channel.channel_name()) + { if existing_channel_name == channel_name { return Err(AutosarAbstractionError::ItemAlreadyExists); } @@ -704,7 +568,7 @@ impl FlexrayCluster { .create_sub_element(ElementName::ChannelName) .and_then(|cn| cn.set_character_data(CharacterData::Enum(cn_item))); - Ok(FlexrayPhysicalChannel(channel)) + FlexrayPhysicalChannel::try_from(channel) } /// get the physical channels of this cluster @@ -714,6 +578,7 @@ impl FlexrayCluster { /// ``` /// # use autosar_data::*; /// # use autosar_data_abstraction::*; + /// # use autosar_data_abstraction::communication::*; /// # let model = AutosarModel::new(); /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); @@ -747,255 +612,109 @@ impl FlexrayCluster { } } -//################################################################## - -/// the `FlexrayPhysicalChannel` represents either channel A or B of Flexray cluster -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct FlexrayPhysicalChannel(Element); -abstraction_element!(FlexrayPhysicalChannel, FlexrayPhysicalChannel); - -impl FlexrayPhysicalChannel { - /// get the channel name of a `FlexrayPhysicalChannel` - #[must_use] - pub fn channel_name(&self) -> Option { - let cn = self - .0 - .get_sub_element(ElementName::ChannelName)? - .character_data()? - .enum_value()?; - match cn { - EnumItem::ChannelA => Some(FlexrayChannelName::A), - EnumItem::ChannelB => Some(FlexrayChannelName::B), - _ => None, - } - } - - /// get the cluster containing this physical channel - /// - /// # Example - /// - /// ``` - /// # use autosar_data::*; - /// # use autosar_data_abstraction::*; - /// # let model = AutosarModel::new(); - /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); - /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); - /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); - /// # let cluster = system.create_can_cluster("Cluster", &package, &CanClusterSettings::default()).unwrap(); - /// let channel = cluster.create_physical_channel("Channel").unwrap(); - /// let cluster_2 = channel.cluster().unwrap(); - /// assert_eq!(cluster, cluster_2); - /// ``` - /// - /// # Errors - /// - /// - [`AutosarAbstractionError::ModelError`] An error occurred in the Autosar model - pub fn cluster(&self) -> Result { - let cluster_elem = self.0.named_parent()?.unwrap(); - FlexrayCluster::try_from(cluster_elem) - } -} +impl AbstractCluster for FlexrayCluster {} //################################################################## -/// An `EcuInstance` needs a `FlexrayCommunicationController` in order to connect to a Flexray cluster. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct FlexrayCommunicationController(Element); -abstraction_element!(FlexrayCommunicationController, FlexrayCommunicationController); - -impl FlexrayCommunicationController { - // create a new FlexrayCommunicationController - called by EcuInstance::create_flexray_communication_controller - pub(crate) fn new(name: &str, ecu: &EcuInstance) -> Result { - let commcontrollers = ecu.element().get_or_create_sub_element(ElementName::CommControllers)?; - let ctrl = commcontrollers.create_named_sub_element(ElementName::FlexrayCommunicationController, name)?; - let _flxccc = ctrl - .create_sub_element(ElementName::FlexrayCommunicationControllerVariants)? - .create_sub_element(ElementName::FlexrayCommunicationControllerConditional)?; - - Ok(Self(ctrl)) - } - - /// return an iterator over the [`FlexrayPhysicalChannel`]s connected to this controller - /// - /// # Example - /// - /// ``` - /// # use autosar_data::*; - /// # use autosar_data_abstraction::*; - /// # let model = AutosarModel::new(); - /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); - /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); - /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); - /// # let ecu_instance = system.create_ecu_instance("ecu_name", &package).unwrap(); - /// let flexray_controller = ecu_instance.create_flexray_communication_controller("FRCtrl").unwrap(); - /// # let cluster = system.create_flexray_cluster("Cluster", &package, &FlexrayClusterSettings::default()).unwrap(); - /// # let physical_channel = cluster.create_physical_channel("Channel", FlexrayChannelName::A).unwrap(); - /// flexray_controller.connect_physical_channel("connection", &physical_channel).unwrap(); - /// for channel in flexray_controller.connected_channels() { - /// // ... - /// } - /// ``` - /// - /// # Errors - /// - /// - [`AutosarAbstractionError::ModelError`] An error occurred in the Autosar model - #[must_use] - pub fn connected_channels(&self) -> FlexrayCtrlChannelsIterator { - if let Ok(ecu) = self.ecu_instance().map(|ecuinstance| ecuinstance.element().clone()) { - FlexrayCtrlChannelsIterator::new(self, &ecu) - } else { - FlexrayCtrlChannelsIterator { - connector_iter: None, - comm_controller: self.0.clone(), - model: Err(AutosarDataError::ElementNotFound), - } - } - } - - /// get the `EcuInstance` that contains this `FlexrayCommunicationController` - /// - /// # Example - /// - /// ``` - /// # use autosar_data::*; - /// # use autosar_data_abstraction::*; - /// # let model = AutosarModel::new(); - /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); - /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); - /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); - /// # let ecu_instance = system.create_ecu_instance("ecu_name", &package).unwrap(); - /// let flexray_controller = ecu_instance.create_flexray_communication_controller("FRCtrl").unwrap(); - /// assert_eq!(ecu_instance, flexray_controller.ecu_instance().unwrap()); - /// ``` - /// - /// # Errors - /// - /// - [`AutosarAbstractionError::ModelError`] An error occurred in the Autosar model while trying to create the ECU-INSTANCE - pub fn ecu_instance(&self) -> Result { - // unwrapping is safe here - self.0.named_parent() cannot return Ok(None). - // the FlexrayCommunicationController is always a child of an EcuInstance, - // or else it is deleted and named_parent() return Err(...), which is handled by the ? - let ecu: Element = self.0.named_parent()?.unwrap(); - EcuInstance::try_from(ecu) - } - - /// Connect this [`FlexrayCommunicationController`] inside an [`EcuInstance`] to a [`FlexrayPhysicalChannel`] in the [`crate::System`] - /// - /// Creates a FlexrayCommunicationConnector in the [`EcuInstance`] that contains this [`FlexrayCommunicationController`]. - /// - /// This function establishes the relationships: - /// - [`FlexrayPhysicalChannel`] -> FlexrayCommunicationConnector - /// - FlexrayCommunicationConnector -> [`FlexrayCommunicationController`] - /// - /// # Example - /// - /// ``` - /// # use autosar_data::*; - /// # use autosar_data_abstraction::*; - /// # let model = AutosarModel::new(); - /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); - /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); - /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); - /// # let ecu_instance = system.create_ecu_instance("ecu_name", &package).unwrap(); - /// let flexray_controller = ecu_instance.create_flexray_communication_controller("FlxCtrl").unwrap(); - /// # let cluster = system.create_flexray_cluster("Cluster", &package, &FlexrayClusterSettings::default()).unwrap(); - /// # let physical_channel = cluster.create_physical_channel("Channel", FlexrayChannelName::A).unwrap(); - /// flexray_controller.connect_physical_channel("connection", &physical_channel).unwrap(); - /// ``` - /// - /// # Errors - /// - /// - [`AutosarAbstractionError::ModelError`] An error occurred in the Autosar model while trying to create the ECU-INSTANCE - pub fn connect_physical_channel( - &self, - connection_name: &str, - flx_channel: &FlexrayPhysicalChannel, - ) -> Result<(), AutosarAbstractionError> { - let ecu = self.0.named_parent()?.unwrap(); - - for existing_channel in self.connected_channels() { - if existing_channel == *flx_channel { - return Err(AutosarAbstractionError::ItemAlreadyExists); - } - } - - // create a new connector - let connectors = ecu.get_or_create_sub_element(ElementName::Connectors)?; - let connector = - connectors.create_named_sub_element(ElementName::FlexrayCommunicationConnector, connection_name)?; - connector - .create_sub_element(ElementName::CommControllerRef) - .and_then(|refelem| refelem.set_reference_target(&self.0))?; - - let channel_connctor_refs = flx_channel - .element() - .get_or_create_sub_element(ElementName::CommConnectors)?; - channel_connctor_refs - .create_sub_element(ElementName::CommunicationConnectorRefConditional) - .and_then(|ccrc| ccrc.create_sub_element(ElementName::CommunicationConnectorRef)) - .and_then(|ccr| ccr.set_reference_target(&connector))?; - - Ok(()) - } +/// Information about the flexray physical channels present inside a cluster +#[derive(Debug, Clone, PartialEq)] +pub struct FlexrayPhysicalChannelsInfo { + /// channel A - it contains FlexrayChannelName::A + pub channel_a: Option, + /// channel B - it contains FlexrayChannelName::B + pub channel_b: Option, } //################################################################## -#[doc(hidden)] -pub struct FlexrayCtrlChannelsIterator { - connector_iter: Option, - comm_controller: Element, - model: Result, -} - -impl FlexrayCtrlChannelsIterator { - fn new(controller: &FlexrayCommunicationController, ecu: &Element) -> Self { - let iter = ecu.get_sub_element(ElementName::Connectors).map(|c| c.sub_elements()); - let comm_controller = controller.element().clone(); - let model = comm_controller.model(); - Self { - connector_iter: iter, - comm_controller, - model, - } - } -} - -impl Iterator for FlexrayCtrlChannelsIterator { - type Item = FlexrayPhysicalChannel; - - fn next(&mut self) -> Option { - let model = self.model.as_ref().ok()?; - let connector_iter = self.connector_iter.as_mut()?; - for connector in connector_iter.by_ref() { - if connector.element_name() == ElementName::FlexrayCommunicationConnector { - if let Some(commcontroller_of_connector) = connector - .get_sub_element(ElementName::CommControllerRef) - .and_then(|ccr| ccr.get_reference_target().ok()) - { - if commcontroller_of_connector == self.comm_controller { - for ref_origin in model - .get_references_to(&connector.path().ok()?) - .iter() - .filter_map(WeakElement::upgrade) - .filter_map(|elem| elem.named_parent().ok().flatten()) - { - // This assumes that each connector will only ever be referenced by at most one - // PhysicalChannel, which is true for well-formed files. - if ref_origin.element_name() == ElementName::FlexrayPhysicalChannel { - return Some(FlexrayPhysicalChannel(ref_origin)); - } - } - } - } - } - } - None - } +/// Configuration settings of the Flexray cluster. +/// +/// Refer to `AUTOSAR_TPS_SystemTemplate.pdf`, chapter 3.3.4 and Flexray 2.1a / 3.0 standard, appendix A + B +/// for the meanings and ranges of these parameters. +#[derive(Debug, Clone, PartialEq)] +pub struct FlexrayClusterSettings { + /// baud rate of the Flexray cluster: one of 10000000 (10Mbit/s), 5000000 (5Mbit/s), 2500000 (2.5Mbit/s) + pub baudrate: u32, + /// gdActionPointOffset: Number of macroticks the action point is offset from the + /// beginning of a static slot or symbol window: in the range 1 - 63 MT + pub action_point_offset: u8, + /// gdBit: Nominal bit time; inverse of the baudrate: 0.1µs, 0.2µs, or 0.4µs + pub bit: f64, + /// gdCASRxLowMax: Upper limit of the CAS acceptance window. Range 67 - 99 gdBit + pub cas_rx_low_max: u8, + /// gColdStartAttempts: Maximum number of times a node in the cluster is permitted to attempt to start the cluster. + /// Range: 2 - 31 + pub cold_start_attempts: u8, + /// gdCycle: Length of the cycle in seconds. Range: 10µs - 16000µs; typically 0.005s + pub cycle: f64, + /// cCycleCountMax. Must be 63 to conform with Flexray 2.1 + pub cycle_count_max: u8, + /// Indicates whether network idle time (NIT) error status should be detected + pub detect_nit_error: bool, + /// gdDynamicSlotIdlePhase: Duration of the idle phase within a dynamic slot. Range 0 - 2 + pub dynamic_slot_idle_phase: u8, + /// Duration for which the bitstrobing is paused after transmission + pub ignore_after_tx: u16, + /// gListenNoise: Upper limit for the startup listen timeout and wakeup listen timeout in the presence of noise. + /// Range 2 - 16 + pub listen_noise: u8, + /// gMacroPerCycle: Number of macroticks in a communication cycle. Range: 10 - 16000 MT + pub macro_per_cycle: u16, + /// macrotick_duration \[s\] = cycle \[s\] / macro_per_cycle + pub macrotick_duration: f64, + /// gMaxWithoutClockCorrectionFatal: Threshold used for testing the vClockCorrectionFailed counter. + /// Range: - 15 even/odd cycle pairs + pub max_without_clock_correction_fatal: u8, + /// gMaxWithoutClockCorrectionPassive: Threshold used for testing the vClockCorrectionFailed counter. + /// Range: 1 - 15 even/odd cycle pairs + pub max_without_clock_correction_passive: u8, + /// gdMinislotActionPointOffset: Number of macroticks the minislot action point is offset from the beginning of a minislot. Range: 1 - 31 MT + pub minislot_action_point_offset: u8, + /// gdMinislot: Duration of a minislot. Range: 2 - 63 MT + pub minislot_duration: u8, + /// gdNIT: Duration of the Network Idle Time. 2 - 805 MT + pub network_idle_time: u16, + /// gNetworkManagementVectorLength: Length of the Network Management vector in a cluster. Range: 0 - 12 bytes + pub network_management_vector_length: u8, + /// gNumberOfMinislots: Number of minislots in the dynamic segment. Range: 0 - 7986 + pub number_of_minislots: u16, + /// gNumberOfStaticSlots: Number of static slots in the static segment. Range: 2 - 1023 + pub number_of_static_slots: u16, + /// gOffsetCorrectionStart: Start of the offset correction phase within the NIT, as MT from the start of the cycle. Range: 9 - 15999 MT + pub offset_correction_start: u16, + /// gPayloadLengthStatic: Payload length of a static frame, in two byte words. Range: 0 - 127 words + pub payload_length_static: u16, + /// Additional timespan in macroticks which takes jitter into account + pub safety_margin: u16, + /// gdSampleClockPeriod: Sample clock period. One of [1.25e-8 s, 2.5e-8 s, 5e-8 s] + pub sample_clock_period: Option, + /// gdStaticSlot: Duration of a static slot. Range: 4 - 661 MT + pub static_slot_duration: u16, + /// gdSymbolWindow: Duration of the symbol window. Range: 0 - 142 MT + pub symbol_window: u8, + // according to the Flexray spec: "gdActionPointOffset parameter also specifies the action point in the symbol window" + // so this parameter is useless? + pub symbol_window_action_point_offset: Option, + /// gSyncNodeMax: Maximum number of nodes that may send frames with the sync frame indicator bit set to one. Range: 2 - 15 + pub sync_frame_id_count_max: u8, + /// The duration of timer t_TrcvStdbyDelay in seconds. + pub transceiver_standby_delay: Option, + /// gdTSSTransmitter: Number of bits in the Transmission Start Sequence. Range: 3 - 15 gdBit + pub transmission_start_sequence_duration: u8, + /// gdWakeupSymbolRxIdle: Number of bits used by the node to test the duration + /// of the 'idle' portion of a received wakeup symbol. Range: 14 - 59 gdBit + pub wakeup_rx_idle: u16, + /// gdWakeupSymbolRxLow: Number of bits used by the node to test the LOW + /// portion of a received wakeup symbol. Range 11 - 59 gdBit + pub wakeup_rx_low: u8, + /// gdWakeupSymbolRxWindow: The size of the window used to detect wakeups. Range: 76 - 301 gdBit + pub wakeup_rx_window: u16, + /// gdWakeupSymbolTxLow: Number of bits used by the node to transmit the LOW part of a wakeup symbol. Range: 15 - 60 gdBit + pub wakeup_tx_active: u16, + /// gdWakeupSymbolTxIdle: Number of bits used by the node to transmit the 'idle' part of a wakeup symbol. Range: 45 - 180 gdBit + pub wakeup_tx_idle: u16, } -//################################################################## - impl FlexrayClusterSettings { /// Create a new `FlexrayClusterSettings` object with default values #[must_use] @@ -1048,7 +767,7 @@ impl FlexrayClusterSettings { /// # Example /// /// ``` - /// # use autosar_data_abstraction::*; + /// # use autosar_data_abstraction::communication::*; /// let mut settings = FlexrayClusterSettings::default(); /// settings.macro_per_cycle = 1111; /// assert!(!settings.verify()) @@ -1234,10 +953,11 @@ impl Default for FlexrayClusterSettings { #[cfg(test)] mod test { - use crate::{System, SystemCategory}; - - use super::*; - use autosar_data::AutosarVersion; + use crate::{ + communication::{AbstractCluster, FlexrayChannelName, FlexrayClusterSettings}, + ArPackage, System, SystemCategory, + }; + use autosar_data::{AutosarModel, AutosarVersion}; #[test] fn cluster() { @@ -1257,6 +977,10 @@ mod test { let result = system.create_flexray_cluster("FlxCluster", &pkg2, &settings); assert!(result.is_err()); + // system link + let linked_system = cluster.system().unwrap(); + assert_eq!(linked_system, system); + // create channel A let result = cluster.create_physical_channel("Channel1", FlexrayChannelName::A); assert!(result.is_ok()); @@ -1272,65 +996,6 @@ mod test { assert!(pc.channel_b.is_some()); } - #[test] - fn channel() { - let model = AutosarModel::new(); - model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); - let pkg = ArPackage::get_or_create(&model, "/test").unwrap(); - let system = System::new("System", &pkg, SystemCategory::SystemDescription).unwrap(); - let settings = FlexrayClusterSettings::default(); - let cluster = system.create_flexray_cluster("FlxCluster", &pkg, &settings).unwrap(); - - let channel = cluster - .create_physical_channel("channel_name", FlexrayChannelName::A) - .unwrap(); - let c2 = channel.cluster().unwrap(); - assert_eq!(cluster, c2); - - // damage the channel info by removing the channel name - let elem_channelname = channel.0.get_sub_element(ElementName::ChannelName).unwrap(); - elem_channelname.remove_character_data().unwrap(); - assert!(channel.channel_name().is_none()); - - // now there is no longer a channel A - let channel2 = cluster.create_physical_channel("channel_name2", FlexrayChannelName::A); - assert!(channel2.is_ok()) - } - - #[test] - fn controller() { - let model = AutosarModel::new(); - model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); - let pkg = ArPackage::get_or_create(&model, "/test").unwrap(); - let system = System::new("System", &pkg, SystemCategory::SystemDescription).unwrap(); - let ecu = system.create_ecu_instance("ECU", &pkg).unwrap(); - - // create a controller - let result = ecu.create_flexray_communication_controller("Controller"); - let controller = result.unwrap(); - - // create some physical channels - let settings = FlexrayClusterSettings::default(); - let cluster = system.create_flexray_cluster("FlxCluster", &pkg, &settings).unwrap(); - let channel1 = cluster.create_physical_channel("C1", FlexrayChannelName::A).unwrap(); - - // connect the controller to channel1 - let result = controller.connect_physical_channel("connection_name1", &channel1); - assert!(result.is_ok()); - // can't connect to the same channel again - let result = controller.connect_physical_channel("connection_name2", &channel1); - assert!(result.is_err()); - - let count = controller.connected_channels().count(); - assert_eq!(count, 1); - - // remove the controller and try to list its connected channels again - let ctrl_parent = controller.0.parent().unwrap().unwrap(); - ctrl_parent.remove_sub_element(controller.0.clone()).unwrap(); - let count = controller.connected_channels().count(); - assert_eq!(count, 0); - } - #[test] fn flexray_settings() { let model = AutosarModel::new(); diff --git a/autosar-data-abstraction/src/communication/cluster/mod.rs b/autosar-data-abstraction/src/communication/cluster/mod.rs new file mode 100644 index 0000000..d365856 --- /dev/null +++ b/autosar-data-abstraction/src/communication/cluster/mod.rs @@ -0,0 +1,78 @@ +use crate::{AbstractionElement, System}; + +mod can; +mod ethernet; +mod flexray; + +use autosar_data::ElementName; +pub use can::*; +pub use ethernet::*; +pub use flexray::*; + +//################################################################## + +pub trait AbstractCluster: AbstractionElement { + fn system(&self) -> Option { + if let Ok(model) = self.element().model() { + let path = self.element().path().ok()?; + let refs = model.get_references_to(&path); + + for system in refs + .iter() + .filter_map(|weak| weak.upgrade()) + .filter(|elem| elem.element_name() == ElementName::FibexElementRef) + .filter_map(|elem| elem.named_parent().ok().flatten()) + .filter_map(|parent| System::try_from(parent).ok()) + { + return Some(system); + } + } + None + } +} + +//################################################################## + +/// A [`Cluster`] is returned by [`System::clusters`]. +/// It can contain any supported communication cluster. +#[derive(Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub enum Cluster { + /// The Cluster is a [`CanCluster`] + Can(CanCluster), + /// The Cluster is an [`EthernetCluster`] + Ethernet(EthernetCluster), + /// The Cluster is a [`FlexrayCluster`] + FlexRay(FlexrayCluster), + // missing: Lin, TTCAN, J1939, CDD (aka user defined) +} + +impl AbstractionElement for Cluster { + fn element(&self) -> &autosar_data::Element { + match self { + Cluster::Can(cancluster) => cancluster.element(), + Cluster::Ethernet(ethcluster) => ethcluster.element(), + Cluster::FlexRay(flxcluster) => flxcluster.element(), + } + } +} + +impl AbstractCluster for Cluster {} + +impl From for Cluster { + fn from(value: CanCluster) -> Self { + Cluster::Can(value) + } +} + +impl From for Cluster { + fn from(value: EthernetCluster) -> Self { + Cluster::Ethernet(value) + } +} + +impl From for Cluster { + fn from(value: FlexrayCluster) -> Self { + Cluster::FlexRay(value) + } +} diff --git a/autosar-data-abstraction/src/communication/controller/can.rs b/autosar-data-abstraction/src/communication/controller/can.rs new file mode 100644 index 0000000..0d5b843 --- /dev/null +++ b/autosar-data-abstraction/src/communication/controller/can.rs @@ -0,0 +1,252 @@ +use crate::{ + abstraction_element, communication::CanPhysicalChannel, AbstractionElement, AutosarAbstractionError, EcuInstance, +}; +use autosar_data::{AutosarDataError, AutosarModel, Element, ElementName, ElementsIterator, WeakElement}; + +/// An `EcuInstance` needs a `CanCommunicationController` in order to connect to a CAN cluster. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct CanCommunicationController(Element); +abstraction_element!(CanCommunicationController, CanCommunicationController); + +impl CanCommunicationController { + // create a new CanCommunicationController - called by EcuInstance::create_can_communication_controller + pub(crate) fn new(name: &str, ecu: &EcuInstance) -> Result { + let commcontrollers = ecu.element().get_or_create_sub_element(ElementName::CommControllers)?; + let ctrl = commcontrollers.create_named_sub_element(ElementName::CanCommunicationController, name)?; + let _canccc = ctrl + .create_sub_element(ElementName::CanCommunicationControllerVariants)? + .create_sub_element(ElementName::CanCommunicationControllerConditional)?; + + Ok(Self(ctrl)) + } + + /// return an iterator over the [`CanPhysicalChannel`]s connected to this controller + /// + /// # Example + /// + /// ``` + /// # use autosar_data::*; + /// # use autosar_data_abstraction::*; + /// # use autosar_data_abstraction::communication::*; + /// # let model = AutosarModel::new(); + /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); + /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); + /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); + /// # let ecu_instance = system.create_ecu_instance("ecu_name", &package).unwrap(); + /// let can_controller = ecu_instance.create_can_communication_controller("CanCtrl").unwrap(); + /// # let cluster = system.create_can_cluster("Cluster", &package, &CanClusterSettings::default()).unwrap(); + /// # let physical_channel = cluster.create_physical_channel("Channel").unwrap(); + /// can_controller.connect_physical_channel("connection", &physical_channel).unwrap(); + /// for channel in can_controller.connected_channels() { + /// // ... + /// } + /// ``` + /// + /// # Errors + /// + /// - [`AutosarAbstractionError::ModelError`] An error occurred in the Autosar model while trying to create the ECU-INSTANCE + #[must_use] + pub fn connected_channels(&self) -> CanCtrlChannelsIterator { + if let Ok(ecu) = self.ecu_instance().map(|ecuinstance| ecuinstance.element().clone()) { + CanCtrlChannelsIterator::new(self, &ecu) + } else { + CanCtrlChannelsIterator { + connector_iter: None, + comm_controller: self.0.clone(), + model: Err(AutosarDataError::ElementNotFound), + } + } + } + + /// get the [`EcuInstance`] that contains this `CanCommunicationController` + /// + /// # Example + /// + /// ``` + /// # use autosar_data::*; + /// # use autosar_data_abstraction::*; + /// # let model = AutosarModel::new(); + /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); + /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); + /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); + /// # let ecu_instance = system.create_ecu_instance("ecu_name", &package).unwrap(); + /// let can_controller = ecu_instance.create_can_communication_controller("CanCtrl").unwrap(); + /// assert_eq!(ecu_instance, can_controller.ecu_instance().unwrap()); + /// ``` + /// + /// # Errors + /// + /// - [`AutosarAbstractionError::ModelError`] An error occurred in the Autosar model while trying to create the ECU-INSTANCE + pub fn ecu_instance(&self) -> Result { + // unwrapping is safe here - self.0.named_parent() cannot return Ok(None). + // the CanCommunicationController is always a child of an EcuInstance, + // or else it is deleted and named_parent() return Err(...), which is handled by the ? + let ecu: Element = self.0.named_parent()?.unwrap(); + EcuInstance::try_from(ecu) + } + + /// Connect this [`CanCommunicationController`] inside an [`EcuInstance`] to a [`CanPhysicalChannel`] in the [`crate::System`] + /// + /// Creates a CanCommunicationConnector in the [`EcuInstance`] that contains this [`CanCommunicationController`]. + /// + /// This function establishes the relationships: + /// - [`CanPhysicalChannel`] -> CanCommunicationConnector + /// - CanCommunicationConnector -> [`CanCommunicationController`] + /// + /// # Example + /// + /// ``` + /// # use autosar_data::*; + /// # use autosar_data_abstraction::*; + /// # use autosar_data_abstraction::communication::*; + /// # let model = AutosarModel::new(); + /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); + /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); + /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); + /// # let ecu_instance = system.create_ecu_instance("ecu_name", &package).unwrap(); + /// let can_controller = ecu_instance.create_can_communication_controller("CanCtrl").unwrap(); + /// # let cluster = system.create_can_cluster("Cluster", &package, &CanClusterSettings::default()).unwrap(); + /// # let physical_channel = cluster.create_physical_channel("Channel").unwrap(); + /// can_controller.connect_physical_channel("connection", &physical_channel).unwrap(); + /// ``` + /// + /// # Errors + /// + /// - [`AutosarAbstractionError::ModelError`] An error occurred in the Autosar model while trying to create the ECU-INSTANCE + pub fn connect_physical_channel( + &self, + connection_name: &str, + can_channel: &CanPhysicalChannel, + ) -> Result<(), AutosarAbstractionError> { + let ecu = self.0.named_parent()?.unwrap(); + // check that there is no existing connector for this CanCommunicationController + if let Some(connectors) = ecu.get_sub_element(ElementName::Connectors) { + for connector in connectors.sub_elements() { + // Does the existing connector reference this CanCommunicationController? + // A CanCommunicationController can only connect to a single CAN cluster, so a second + // connector cannot be created. + if let Some(ccref) = connector.get_sub_element(ElementName::CommControllerRef) { + if let Ok(commcontroller_of_connector) = ccref.get_reference_target() { + if commcontroller_of_connector == self.0 { + return Err(AutosarAbstractionError::ItemAlreadyExists); + } + } + } + } + } + // create a new connector + let connectors = ecu.get_or_create_sub_element(ElementName::Connectors)?; + let connector = connectors.create_named_sub_element(ElementName::CanCommunicationConnector, connection_name)?; + connector + .create_sub_element(ElementName::CommControllerRef) + .and_then(|refelem| refelem.set_reference_target(&self.0))?; + + let channel_connctor_refs = can_channel + .element() + .get_or_create_sub_element(ElementName::CommConnectors)?; + channel_connctor_refs + .create_sub_element(ElementName::CommunicationConnectorRefConditional) + .and_then(|ccrc| ccrc.create_sub_element(ElementName::CommunicationConnectorRef)) + .and_then(|ccr| ccr.set_reference_target(&connector))?; + + Ok(()) + } +} + +//################################################################## + +#[doc(hidden)] +pub struct CanCtrlChannelsIterator { + connector_iter: Option, + comm_controller: Element, + model: Result, +} + +impl CanCtrlChannelsIterator { + fn new(controller: &CanCommunicationController, ecu: &Element) -> Self { + let iter = ecu.get_sub_element(ElementName::Connectors).map(|c| c.sub_elements()); + let comm_controller = controller.element().clone(); + let model = comm_controller.model(); + Self { + connector_iter: iter, + comm_controller, + model, + } + } +} + +impl Iterator for CanCtrlChannelsIterator { + type Item = CanPhysicalChannel; + + fn next(&mut self) -> Option { + let model = self.model.as_ref().ok()?; + let connector_iter = self.connector_iter.as_mut()?; + for connector in connector_iter.by_ref() { + if connector.element_name() == ElementName::CanCommunicationConnector { + if let Some(commcontroller_of_connector) = connector + .get_sub_element(ElementName::CommControllerRef) + .and_then(|ccr| ccr.get_reference_target().ok()) + { + if commcontroller_of_connector == self.comm_controller { + for ref_origin in model + .get_references_to(&connector.path().ok()?) + .iter() + .filter_map(WeakElement::upgrade) + .filter_map(|elem| elem.named_parent().ok().flatten()) + { + // This assumes that each connector will only ever be referenced by at most one + // PhysicalChannel, which is true for well-formed files. + if ref_origin.element_name() == ElementName::CanPhysicalChannel { + return CanPhysicalChannel::try_from(ref_origin).ok(); + } + } + } + } + } + } + None + } +} + +//################################################################## + +#[cfg(test)] +mod test { + use super::*; + use crate::{communication::CanClusterSettings, ArPackage, System, SystemCategory}; + use autosar_data::AutosarVersion; + + #[test] + fn controller() { + let model = AutosarModel::new(); + model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); + let pkg = ArPackage::get_or_create(&model, "/test").unwrap(); + let system = System::new("System", &pkg, SystemCategory::SystemDescription).unwrap(); + let ecu = system.create_ecu_instance("ECU", &pkg).unwrap(); + + // create a controller + let result = ecu.create_can_communication_controller("Controller"); + let controller = result.unwrap(); + + // create some physical channels + let settings = CanClusterSettings::default(); + let cluster = system.create_can_cluster("CanCluster", &pkg, &settings).unwrap(); + let channel1 = cluster.create_physical_channel("C1").unwrap(); + + // connect the controller to channel1 + let result = controller.connect_physical_channel("connection_name1", &channel1); + assert!(result.is_ok()); + // can't connect to the same channel again + let result = controller.connect_physical_channel("connection_name2", &channel1); + assert!(result.is_err()); + + let count = controller.connected_channels().count(); + assert_eq!(count, 1); + + // remove the controller and try to list its connected channels again + let ctrl_parent = controller.0.parent().unwrap().unwrap(); + ctrl_parent.remove_sub_element(controller.0.clone()).unwrap(); + let count = controller.connected_channels().count(); + assert_eq!(count, 0); + } +} diff --git a/autosar-data-abstraction/src/communication/controller/ethernet.rs b/autosar-data-abstraction/src/communication/controller/ethernet.rs new file mode 100644 index 0000000..3f61415 --- /dev/null +++ b/autosar-data-abstraction/src/communication/controller/ethernet.rs @@ -0,0 +1,316 @@ +use crate::communication::{EthernetPhysicalChannel, EthernetVlanInfo}; +use crate::{abstraction_element, AbstractionElement, AutosarAbstractionError, EcuInstance}; +use autosar_data::{ + AutosarDataError, AutosarModel, CharacterData, Element, ElementName, ElementsIterator, WeakElement, +}; + +/// An `EcuInstance` needs an `EthernetCommunicationController` in order to connect to an ethernet cluster. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct EthernetCommunicationController(Element); +abstraction_element!(EthernetCommunicationController, EthernetCommunicationController); + +impl EthernetCommunicationController { + // create an EthernetCommunicationController + pub(crate) fn new( + name: &str, + ecu: &EcuInstance, + mac_address: Option, + ) -> Result { + let commcontrollers = ecu.element().get_or_create_sub_element(ElementName::CommControllers)?; + let ctrl = commcontrollers.create_named_sub_element(ElementName::EthernetCommunicationController, name)?; + let ethccc = ctrl + .create_sub_element(ElementName::EthernetCommunicationControllerVariants)? + .create_sub_element(ElementName::EthernetCommunicationControllerConditional)?; + if let Some(mac_address) = mac_address { + // creating the mac address element fails if the supplied string has an invalid format + let result = ethccc + .create_sub_element(ElementName::MacUnicastAddress) + .and_then(|mua| mua.set_character_data(mac_address)); + if let Err(mac_address_error) = result { + let _ = commcontrollers.remove_sub_element(ctrl); + return Err(mac_address_error.into()); + } + } + let coupling_port_name = format!("{name}_CouplingPort"); + let _ = ethccc + .create_sub_element(ElementName::CouplingPorts) + .and_then(|cps| cps.create_named_sub_element(ElementName::CouplingPort, &coupling_port_name)); + + Ok(Self(ctrl)) + } + + /// return an iterator over the [`EthernetPhysicalChannel`]s connected to this controller + /// + /// # Example + /// + /// ``` + /// # use autosar_data::*; + /// # use autosar_data_abstraction::*; + /// # let model = AutosarModel::new(); + /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); + /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); + /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); + /// # let ecu_instance = system.create_ecu_instance("ecu_name", &package).unwrap(); + /// let ethernet_controller = ecu_instance.create_ethernet_communication_controller("EthCtrl", None).unwrap(); + /// # let cluster = system.create_ethernet_cluster("Cluster", &package).unwrap(); + /// # let physical_channel = cluster.create_physical_channel("Channel", None).unwrap(); + /// ethernet_controller.connect_physical_channel("connection", &physical_channel).unwrap(); + /// for channel in ethernet_controller.connected_channels() { + /// // ... + /// } + /// ``` + /// + /// # Errors + /// + /// - [`AutosarAbstractionError::ModelError`] An error occurred in the Autosar model while trying to create the ECU-INSTANCE + #[must_use] + pub fn connected_channels(&self) -> EthernetCtrlChannelsIterator { + if let Ok(ecu) = self.ecu_instance().map(|ecuinstance| ecuinstance.element().clone()) { + EthernetCtrlChannelsIterator::new(self, &ecu) + } else { + EthernetCtrlChannelsIterator { + connector_iter: None, + comm_controller: self.0.clone(), + model: Err(AutosarDataError::ElementNotFound), + } + } + } + + /// get the `EcuInstance` that contains this `EthernetCommunicationController` + /// + /// # Example + /// + /// ``` + /// # use autosar_data::*; + /// # use autosar_data_abstraction::*; + /// # let model = AutosarModel::new(); + /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); + /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); + /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); + /// # let ecu_instance = system.create_ecu_instance("ecu_name", &package).unwrap(); + /// let ethernet_controller = ecu_instance.create_ethernet_communication_controller("EthCtrl", None).unwrap(); + /// assert_eq!(ecu_instance, ethernet_controller.ecu_instance().unwrap()); + /// ``` + /// + /// # Errors + /// + /// - [`AutosarAbstractionError::ModelError`] An error occurred in the Autosar model while trying to create the ECU-INSTANCE + pub fn ecu_instance(&self) -> Result { + // unwrapping is safe here - self.0.named_parent() cannot return Ok(None). + // the EthernetCommunicationController is always a child of an EcuInstance, + // or else it is deleted and named_parent() return Err(...), which is handled by the ? + let ecu: Element = self.0.named_parent()?.unwrap(); + EcuInstance::try_from(ecu) + } + + /// Connect this [`EthernetCommunicationController`] inside an [`EcuInstance`] to an [`EthernetPhysicalChannel`] in the [`crate::System`] + /// + /// Creates an EthernetCommunicationConnector in the [`EcuInstance`] that contains this [`EthernetCommunicationController`]. + /// + /// This function establishes the relationships: + /// - [`EthernetPhysicalChannel`] -> EthernetCommunicationConnector + /// - EthernetCommunicationConnector -> [`EthernetCommunicationController`] + /// + /// # Example + /// + /// ``` + /// # use autosar_data::*; + /// # use autosar_data_abstraction::*; + /// # let model = AutosarModel::new(); + /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); + /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); + /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); + /// # let ecu_instance = system.create_ecu_instance("ecu_name", &package).unwrap(); + /// let ethernet_controller = ecu_instance.create_ethernet_communication_controller("EthCtrl", None).unwrap(); + /// # let cluster = system.create_ethernet_cluster("Cluster", &package).unwrap(); + /// # let physical_channel = cluster.create_physical_channel("Channel", None).unwrap(); + /// ethernet_controller.connect_physical_channel("connection", &physical_channel).unwrap(); + /// ``` + /// + /// # Errors + /// + /// - [`AutosarAbstractionError::ModelError`] An error occurred in the Autosar model while trying to create the ECU-INSTANCE + pub fn connect_physical_channel( + &self, + connection_name: &str, + eth_channel: &EthernetPhysicalChannel, + ) -> Result<(), AutosarAbstractionError> { + let ecu: Element = self.0.named_parent()?.unwrap(); + let cluster_of_channel = eth_channel.cluster()?; + + // There can be multiple connectors referring to a single EthernetCommunicationController, + // but all of these connectors must refer to different PhysicalChannels + // (= VLANs) of the same EthernetCluster. + for phys_channel in self.connected_channels() { + if phys_channel == *eth_channel { + return Err(AutosarAbstractionError::ItemAlreadyExists); + } + + if phys_channel.cluster()? != cluster_of_channel { + return Err(AutosarAbstractionError::InvalidParameter( + "The EthernetCommunicationController may only refer to different channels within the same cluster" + .to_string(), + )); + } + } + + // create a new connector + let connectors = ecu.get_or_create_sub_element(ElementName::Connectors)?; + let connector = + connectors.create_named_sub_element(ElementName::EthernetCommunicationConnector, connection_name)?; + connector + .create_sub_element(ElementName::CommControllerRef) + .and_then(|refelem| refelem.set_reference_target(&self.0))?; + + // if the ethernet physical channel has a category (WIRED / WIRELESS / CANXL) then + // set the category of the connector to the same value + if let Some(category) = eth_channel + .element() + .get_sub_element(ElementName::Category) + .and_then(|cat| cat.character_data()) + .and_then(|cdata| cdata.string_value()) + { + let _ = connector + .create_sub_element(ElementName::Category) + .and_then(|cat| cat.set_character_data(category)); + } + + // create a communication connector ref in the ethernet channel that refers to this connector + let channel_connctor_refs = eth_channel + .element() + .get_or_create_sub_element(ElementName::CommConnectors)?; + channel_connctor_refs + .create_sub_element(ElementName::CommunicationConnectorRefConditional) + .and_then(|ccrc| ccrc.create_sub_element(ElementName::CommunicationConnectorRef)) + .and_then(|ccr| ccr.set_reference_target(&connector))?; + + // if the PhysicalChannel has VLAN info AND if there is a coupling port in this CommunicationController + // then the coupling port should link to the PhysicalChannel / VLAN + if let Some(EthernetVlanInfo { .. }) = eth_channel.get_vlan_info() { + if let Some(coupling_port) = self + .0 + .get_sub_element(ElementName::EthernetCommunicationControllerVariants) + .and_then(|eccv| eccv.get_sub_element(ElementName::EthernetCommunicationControllerConditional)) + .and_then(|eccc| eccc.get_sub_element(ElementName::CouplingPorts)) + .and_then(|cps| cps.get_sub_element(ElementName::CouplingPort)) + { + coupling_port + .get_or_create_sub_element(ElementName::VlanMemberships) + .and_then(|vms| vms.create_sub_element(ElementName::VlanMembership)) + .and_then(|vm| vm.create_sub_element(ElementName::VlanRef)) + .and_then(|vr| vr.set_reference_target(ð_channel.element()))?; + } + } + + Ok(()) + } +} + +//################################################################## + +#[doc(hidden)] +pub struct EthernetCtrlChannelsIterator { + connector_iter: Option, + comm_controller: Element, + model: Result, +} + +impl EthernetCtrlChannelsIterator { + fn new(controller: &EthernetCommunicationController, ecu: &Element) -> Self { + let iter = ecu.get_sub_element(ElementName::Connectors).map(|c| c.sub_elements()); + let comm_controller = controller.element().clone(); + let model = comm_controller.model(); + Self { + connector_iter: iter, + comm_controller, + model, + } + } +} + +impl Iterator for EthernetCtrlChannelsIterator { + type Item = EthernetPhysicalChannel; + + fn next(&mut self) -> Option { + let model = self.model.as_ref().ok()?; + let connector_iter = self.connector_iter.as_mut()?; + for connector in connector_iter.by_ref() { + if connector.element_name() == ElementName::EthernetCommunicationConnector { + if let Some(commcontroller_of_connector) = connector + .get_sub_element(ElementName::CommControllerRef) + .and_then(|ccr| ccr.get_reference_target().ok()) + { + if commcontroller_of_connector == self.comm_controller { + for ref_origin in model + .get_references_to(&connector.path().ok()?) + .iter() + .filter_map(WeakElement::upgrade) + .filter_map(|elem| elem.named_parent().ok().flatten()) + { + // This assumes that each connector will only ever be referenced by at most one + // PhysicalChannel, which is true for well-formed files. + if ref_origin.element_name() == ElementName::EthernetPhysicalChannel { + return EthernetPhysicalChannel::try_from(ref_origin).ok(); + } + } + } + } + } + } + None + } +} + +//################################################################## + +#[cfg(test)] +mod test { + use super::*; + use crate::{communication::EthernetVlanInfo, ArPackage, System, SystemCategory}; + use autosar_data::{AutosarModel, AutosarVersion}; + + #[test] + fn controller() { + let model = AutosarModel::new(); + model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); + let pkg = ArPackage::get_or_create(&model, "/test").unwrap(); + let system = System::new("System", &pkg, SystemCategory::SystemDescription).unwrap(); + let ecu = system.create_ecu_instance("ECU", &pkg).unwrap(); + + // can't create a controller with an invalid MAC address + let result = ecu.create_ethernet_communication_controller("Controller", Some("abcdef".to_string())); + assert!(result.is_err()); + + // create a controller + let result = ecu.create_ethernet_communication_controller("Controller", Some("01:02:03:04:05:06".to_string())); + let controller = result.unwrap(); + + // create some physical channels + let cluster = system.create_ethernet_cluster("EthCluster", &pkg).unwrap(); + let channel1 = cluster.create_physical_channel("C1", None).unwrap(); + let vlan_info = EthernetVlanInfo { + vlan_name: "VLAN_1".to_string(), + vlan_id: 1, + }; + let channel2 = cluster.create_physical_channel("C2", Some(vlan_info)).unwrap(); + + // connect the controller to channel1 + let result = controller.connect_physical_channel("connection_name1", &channel1); + assert!(result.is_ok()); + // can't connect to the same channel again + let result = controller.connect_physical_channel("connection_name2", &channel1); + assert!(result.is_err()); + // connect the controller to channel2 + let result = controller.connect_physical_channel("connection_name2", &channel2); + assert!(result.is_ok()); + + let count = controller.connected_channels().count(); + assert_eq!(count, 2); + + // remove the controller and try to list its connected channels again + let ctrl_parent = controller.element().parent().unwrap().unwrap(); + ctrl_parent.remove_sub_element(controller.element().clone()).unwrap(); + let count = controller.connected_channels().count(); + assert_eq!(count, 0); + } +} diff --git a/autosar-data-abstraction/src/communication/controller/flexray.rs b/autosar-data-abstraction/src/communication/controller/flexray.rs new file mode 100644 index 0000000..c86a0b7 --- /dev/null +++ b/autosar-data-abstraction/src/communication/controller/flexray.rs @@ -0,0 +1,251 @@ +use crate::{ + abstraction_element, communication::FlexrayPhysicalChannel, AbstractionElement, AutosarAbstractionError, + EcuInstance, +}; +use autosar_data::{AutosarDataError, AutosarModel, Element, ElementName, ElementsIterator, WeakElement}; + +/// An `EcuInstance` needs a `FlexrayCommunicationController` in order to connect to a Flexray cluster. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct FlexrayCommunicationController(Element); +abstraction_element!(FlexrayCommunicationController, FlexrayCommunicationController); + +impl FlexrayCommunicationController { + // create a new FlexrayCommunicationController - called by EcuInstance::create_flexray_communication_controller + pub(crate) fn new(name: &str, ecu: &EcuInstance) -> Result { + let commcontrollers = ecu.element().get_or_create_sub_element(ElementName::CommControllers)?; + let ctrl = commcontrollers.create_named_sub_element(ElementName::FlexrayCommunicationController, name)?; + let _flxccc = ctrl + .create_sub_element(ElementName::FlexrayCommunicationControllerVariants)? + .create_sub_element(ElementName::FlexrayCommunicationControllerConditional)?; + + Ok(Self(ctrl)) + } + + /// return an iterator over the [`FlexrayPhysicalChannel`]s connected to this controller + /// + /// # Example + /// + /// ``` + /// # use autosar_data::*; + /// # use autosar_data_abstraction::*; + /// # use autosar_data_abstraction::communication::*; + /// # let model = AutosarModel::new(); + /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); + /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); + /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); + /// # let ecu_instance = system.create_ecu_instance("ecu_name", &package).unwrap(); + /// let flexray_controller = ecu_instance.create_flexray_communication_controller("FRCtrl").unwrap(); + /// # let cluster = system.create_flexray_cluster("Cluster", &package, &FlexrayClusterSettings::default()).unwrap(); + /// # let physical_channel = cluster.create_physical_channel("Channel", FlexrayChannelName::A).unwrap(); + /// flexray_controller.connect_physical_channel("connection", &physical_channel).unwrap(); + /// for channel in flexray_controller.connected_channels() { + /// // ... + /// } + /// ``` + /// + /// # Errors + /// + /// - [`AutosarAbstractionError::ModelError`] An error occurred in the Autosar model + #[must_use] + pub fn connected_channels(&self) -> FlexrayCtrlChannelsIterator { + if let Ok(ecu) = self.ecu_instance().map(|ecuinstance| ecuinstance.element().clone()) { + FlexrayCtrlChannelsIterator::new(self, &ecu) + } else { + FlexrayCtrlChannelsIterator { + connector_iter: None, + comm_controller: self.0.clone(), + model: Err(AutosarDataError::ElementNotFound), + } + } + } + + /// get the `EcuInstance` that contains this `FlexrayCommunicationController` + /// + /// # Example + /// + /// ``` + /// # use autosar_data::*; + /// # use autosar_data_abstraction::*; + /// # use autosar_data_abstraction::communication::*; + /// # let model = AutosarModel::new(); + /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); + /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); + /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); + /// # let ecu_instance = system.create_ecu_instance("ecu_name", &package).unwrap(); + /// let flexray_controller = ecu_instance.create_flexray_communication_controller("FRCtrl").unwrap(); + /// assert_eq!(ecu_instance, flexray_controller.ecu_instance().unwrap()); + /// ``` + /// + /// # Errors + /// + /// - [`AutosarAbstractionError::ModelError`] An error occurred in the Autosar model while trying to create the ECU-INSTANCE + pub fn ecu_instance(&self) -> Result { + // unwrapping is safe here - self.0.named_parent() cannot return Ok(None). + // the FlexrayCommunicationController is always a child of an EcuInstance, + // or else it is deleted and named_parent() return Err(...), which is handled by the ? + let ecu: Element = self.0.named_parent()?.unwrap(); + EcuInstance::try_from(ecu) + } + + /// Connect this [`FlexrayCommunicationController`] inside an [`EcuInstance`] to a [`FlexrayPhysicalChannel`] in the [`crate::System`] + /// + /// Creates a FlexrayCommunicationConnector in the [`EcuInstance`] that contains this [`FlexrayCommunicationController`]. + /// + /// This function establishes the relationships: + /// - [`FlexrayPhysicalChannel`] -> FlexrayCommunicationConnector + /// - FlexrayCommunicationConnector -> [`FlexrayCommunicationController`] + /// + /// # Example + /// + /// ``` + /// # use autosar_data::*; + /// # use autosar_data_abstraction::*; + /// # use autosar_data_abstraction::communication::*; + /// # let model = AutosarModel::new(); + /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); + /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); + /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); + /// # let ecu_instance = system.create_ecu_instance("ecu_name", &package).unwrap(); + /// let flexray_controller = ecu_instance.create_flexray_communication_controller("FlxCtrl").unwrap(); + /// # let cluster = system.create_flexray_cluster("Cluster", &package, &FlexrayClusterSettings::default()).unwrap(); + /// # let physical_channel = cluster.create_physical_channel("Channel", FlexrayChannelName::A).unwrap(); + /// flexray_controller.connect_physical_channel("connection", &physical_channel).unwrap(); + /// ``` + /// + /// # Errors + /// + /// - [`AutosarAbstractionError::ModelError`] An error occurred in the Autosar model while trying to create the ECU-INSTANCE + pub fn connect_physical_channel( + &self, + connection_name: &str, + flx_channel: &FlexrayPhysicalChannel, + ) -> Result<(), AutosarAbstractionError> { + let ecu = self.0.named_parent()?.unwrap(); + + for existing_channel in self.connected_channels() { + if existing_channel == *flx_channel { + return Err(AutosarAbstractionError::ItemAlreadyExists); + } + } + + // create a new connector + let connectors = ecu.get_or_create_sub_element(ElementName::Connectors)?; + let connector = + connectors.create_named_sub_element(ElementName::FlexrayCommunicationConnector, connection_name)?; + connector + .create_sub_element(ElementName::CommControllerRef) + .and_then(|refelem| refelem.set_reference_target(&self.0))?; + + let channel_connctor_refs = flx_channel + .element() + .get_or_create_sub_element(ElementName::CommConnectors)?; + channel_connctor_refs + .create_sub_element(ElementName::CommunicationConnectorRefConditional) + .and_then(|ccrc| ccrc.create_sub_element(ElementName::CommunicationConnectorRef)) + .and_then(|ccr| ccr.set_reference_target(&connector))?; + + Ok(()) + } +} + +//################################################################## + +#[doc(hidden)] +pub struct FlexrayCtrlChannelsIterator { + connector_iter: Option, + comm_controller: Element, + model: Result, +} + +impl FlexrayCtrlChannelsIterator { + fn new(controller: &FlexrayCommunicationController, ecu: &Element) -> Self { + let iter = ecu.get_sub_element(ElementName::Connectors).map(|c| c.sub_elements()); + let comm_controller = controller.element().clone(); + let model = comm_controller.model(); + Self { + connector_iter: iter, + comm_controller, + model, + } + } +} + +impl Iterator for FlexrayCtrlChannelsIterator { + type Item = FlexrayPhysicalChannel; + + fn next(&mut self) -> Option { + let model = self.model.as_ref().ok()?; + let connector_iter = self.connector_iter.as_mut()?; + for connector in connector_iter.by_ref() { + if connector.element_name() == ElementName::FlexrayCommunicationConnector { + if let Some(commcontroller_of_connector) = connector + .get_sub_element(ElementName::CommControllerRef) + .and_then(|ccr| ccr.get_reference_target().ok()) + { + if commcontroller_of_connector == self.comm_controller { + for ref_origin in model + .get_references_to(&connector.path().ok()?) + .iter() + .filter_map(WeakElement::upgrade) + .filter_map(|elem| elem.named_parent().ok().flatten()) + { + // This assumes that each connector will only ever be referenced by at most one + // PhysicalChannel, which is true for well-formed files. + if ref_origin.element_name() == ElementName::FlexrayPhysicalChannel { + return FlexrayPhysicalChannel::try_from(ref_origin).ok(); + } + } + } + } + } + } + None + } +} + +//################################################################## + +#[cfg(test)] +mod test { + use crate::{ + communication::{FlexrayChannelName, FlexrayClusterSettings}, + ArPackage, System, SystemCategory, + }; + + use super::*; + use autosar_data::AutosarVersion; + + #[test] + fn controller() { + let model = AutosarModel::new(); + model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); + let pkg = ArPackage::get_or_create(&model, "/test").unwrap(); + let system = System::new("System", &pkg, SystemCategory::SystemDescription).unwrap(); + let ecu = system.create_ecu_instance("ECU", &pkg).unwrap(); + + // create a controller + let result = ecu.create_flexray_communication_controller("Controller"); + let controller = result.unwrap(); + + // create some physical channels + let settings = FlexrayClusterSettings::default(); + let cluster = system.create_flexray_cluster("FlxCluster", &pkg, &settings).unwrap(); + let channel1 = cluster.create_physical_channel("C1", FlexrayChannelName::A).unwrap(); + + // connect the controller to channel1 + let result = controller.connect_physical_channel("connection_name1", &channel1); + assert!(result.is_ok()); + // can't connect to the same channel again + let result = controller.connect_physical_channel("connection_name2", &channel1); + assert!(result.is_err()); + + let count = controller.connected_channels().count(); + assert_eq!(count, 1); + + // remove the controller and try to list its connected channels again + let ctrl_parent = controller.0.parent().unwrap().unwrap(); + ctrl_parent.remove_sub_element(controller.0.clone()).unwrap(); + let count = controller.connected_channels().count(); + assert_eq!(count, 0); + } +} diff --git a/autosar-data-abstraction/src/communication/controller/mod.rs b/autosar-data-abstraction/src/communication/controller/mod.rs new file mode 100644 index 0000000..d76211f --- /dev/null +++ b/autosar-data-abstraction/src/communication/controller/mod.rs @@ -0,0 +1,48 @@ +use crate::AbstractionElement; + +mod can; +mod ethernet; +mod flexray; + +pub use can::*; +pub use ethernet::*; +pub use flexray::*; + +//################################################################## + +/// wraps all different kinds of communication controller +#[derive(Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub enum CommunicationController { + Can(CanCommunicationController), + Ethernet(EthernetCommunicationController), + Flexray(FlexrayCommunicationController), +} + +impl AbstractionElement for CommunicationController { + fn element(&self) -> &autosar_data::Element { + match self { + CommunicationController::Can(ccc) => ccc.element(), + CommunicationController::Ethernet(ecc) => ecc.element(), + CommunicationController::Flexray(fcc) => fcc.element(), + } + } +} + +impl From for CommunicationController { + fn from(value: CanCommunicationController) -> Self { + CommunicationController::Can(value) + } +} + +impl From for CommunicationController { + fn from(value: EthernetCommunicationController) -> Self { + CommunicationController::Ethernet(value) + } +} + +impl From for CommunicationController { + fn from(value: FlexrayCommunicationController) -> Self { + CommunicationController::Flexray(value) + } +} diff --git a/autosar-data-abstraction/src/communication/frame/can.rs b/autosar-data-abstraction/src/communication/frame/can.rs new file mode 100644 index 0000000..1fea27f --- /dev/null +++ b/autosar-data-abstraction/src/communication/frame/can.rs @@ -0,0 +1,211 @@ +use crate::communication::{CanPhysicalChannel, Frame, Pdu}; +use crate::{abstraction_element, make_unique_name, AbstractionElement, ArPackage, AutosarAbstractionError, ByteOrder}; +use autosar_data::{Element, ElementName, EnumItem}; + +/// +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct CanFrame(Element); +abstraction_element!(CanFrame, CanFrame); + +impl CanFrame { + pub(crate) fn new(name: &str, byte_length: u64, package: &ArPackage) -> Result { + let pkg_elements = package.element().get_or_create_sub_element(ElementName::Elements)?; + let can_frame = pkg_elements.create_named_sub_element(ElementName::CanFrame, name)?; + + can_frame + .create_sub_element(ElementName::FrameLength)? + .set_character_data(byte_length.to_string())?; + + Ok(Self(can_frame)) + } + + pub fn map_pdu( + &self, + pdu: &Pdu, + start_position: u32, + byte_order: ByteOrder, + update_bit: Option, + ) -> Result<(), AutosarAbstractionError> { + Frame::from(self.clone()).map_pdu(pdu, start_position, byte_order, update_bit) + } +} + +impl From for Frame { + fn from(frame: CanFrame) -> Self { + Frame::Can(frame) + } +} + +//################################################################## + +/// The frame triggering connects a frame to a physical channel +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct CanFrameTriggering(Element); +abstraction_element!(CanFrameTriggering, CanFrameTriggering); + +impl CanFrameTriggering { + pub(crate) fn new( + channel: &CanPhysicalChannel, + frame: &CanFrame, + identifier: u32, + addressing_mode: CanAddressingMode, + frame_type: CanFrameType, + ) -> Result { + let model = channel.element().model()?; + let base_path = channel.element().path()?; + let frame_name = frame + .name() + .ok_or(AutosarAbstractionError::InvalidParameter("invalid frame".to_string()))?; + let ft_name = format!("FT_{frame_name}"); + let ft_name = make_unique_name(&model, base_path, ft_name); + + let frame_triggerings = channel + .element() + .get_or_create_sub_element(ElementName::FrameTriggerings)?; + let can_triggering = frame_triggerings.create_named_sub_element(ElementName::CanFrameTriggering, &ft_name)?; + + can_triggering + .create_sub_element(ElementName::FrameRef)? + .set_reference_target(frame.element())?; + + let ft = Self(can_triggering); + ft.set_addressing_mode(addressing_mode)?; + ft.set_frame_type(frame_type)?; + if let Err(error) = ft.set_identifier(identifier) { + let _ = frame_triggerings.remove_sub_element(ft.0); + return Err(error); + } + + Ok(ft) + } + + /// set the can id associated with this frame + pub fn set_identifier(&self, identifier: u32) -> Result<(), AutosarAbstractionError> { + let amode = self.get_addressing_mode().unwrap_or(CanAddressingMode::Standard); + if amode == CanAddressingMode::Standard && identifier > 0x7ff { + return Err(AutosarAbstractionError::InvalidParameter(format!( + "CAN-ID {identifier} is outside the 11-bit range allowed by standard addressing" + ))); + } else if identifier > 0x1fff_ffff { + return Err(AutosarAbstractionError::InvalidParameter(format!( + "CAN-ID {identifier} is outside the 29-bit range allowed by extended addressing" + ))); + } + self.element() + .get_or_create_sub_element(ElementName::Identifier)? + .set_character_data(identifier.to_string())?; + + Ok(()) + } + + pub fn get_identifier(&self) -> Option { + self.element() + .get_sub_element(ElementName::Identifier)? + .character_data()? + .decode_integer() + } + + pub fn set_addressing_mode(&self, addressing_mode: CanAddressingMode) -> Result<(), AutosarAbstractionError> { + self.element() + .get_or_create_sub_element(ElementName::CanAddressingMode)? + .set_character_data::(addressing_mode.into())?; + + Ok(()) + } + + pub fn get_addressing_mode(&self) -> Option { + self.element() + .get_sub_element(ElementName::CanAddressingMode)? + .character_data()? + .enum_value()? + .try_into() + .ok() + } + + pub fn set_frame_type(&self, frame_type: CanFrameType) -> Result<(), AutosarAbstractionError> { + self.element() + .get_or_create_sub_element(ElementName::CanFrameRxBehavior)? + .set_character_data::(frame_type.into())?; + self.element() + .get_or_create_sub_element(ElementName::CanFrameTxBehavior)? + .set_character_data::(frame_type.into())?; + + Ok(()) + } + + pub fn get_frame_type(&self) -> Option { + self.element() + .get_sub_element(ElementName::CanFrameTxBehavior)? + .character_data()? + .enum_value()? + .try_into() + .ok() + } +} + +//################################################################## + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CanAddressingMode { + Standard, + Extended, +} + +impl TryFrom for CanAddressingMode { + type Error = AutosarAbstractionError; + + fn try_from(value: EnumItem) -> Result { + match value { + EnumItem::Standard => Ok(CanAddressingMode::Standard), + EnumItem::Extended => Ok(CanAddressingMode::Extended), + _ => Err(AutosarAbstractionError::ValueConversionError { + value: value.to_string(), + dest: "CanAddressingMode".to_string(), + }), + } + } +} + +impl From for EnumItem { + fn from(value: CanAddressingMode) -> Self { + match value { + CanAddressingMode::Standard => EnumItem::Standard, + CanAddressingMode::Extended => EnumItem::Extended, + } + } +} + +//################################################################## + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CanFrameType { + Can20, + CanFd, + Any, +} + +impl TryFrom for CanFrameType { + type Error = AutosarAbstractionError; + + fn try_from(value: EnumItem) -> Result { + match value { + EnumItem::Can20 => Ok(CanFrameType::Can20), + EnumItem::CanFd => Ok(CanFrameType::CanFd), + EnumItem::Any => Ok(CanFrameType::Any), + _ => Err(AutosarAbstractionError::ValueConversionError { + value: value.to_string(), + dest: "CanFrameType".to_string(), + }), + } + } +} + +impl From for EnumItem { + fn from(value: CanFrameType) -> Self { + match value { + CanFrameType::Can20 => EnumItem::Can20, + CanFrameType::CanFd => EnumItem::CanFd, + CanFrameType::Any => EnumItem::Any, + } + } +} diff --git a/autosar-data-abstraction/src/communication/frame/flexray.rs b/autosar-data-abstraction/src/communication/frame/flexray.rs new file mode 100644 index 0000000..cdae041 --- /dev/null +++ b/autosar-data-abstraction/src/communication/frame/flexray.rs @@ -0,0 +1,246 @@ +use crate::communication::{FlexrayPhysicalChannel, Frame, Pdu}; +use crate::{abstraction_element, make_unique_name, AbstractionElement, ArPackage, AutosarAbstractionError, ByteOrder}; +use autosar_data::{Element, ElementName, EnumItem}; + +/// a Flexray frame +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct FlexrayFrame(Element); +abstraction_element!(FlexrayFrame, FlexrayFrame); + +impl FlexrayFrame { + pub(crate) fn new(name: &str, byte_length: u64, package: &ArPackage) -> Result { + let pkg_elements = package.element().get_or_create_sub_element(ElementName::Elements)?; + let fr_frame = pkg_elements.create_named_sub_element(ElementName::FlexrayFrame, name)?; + + fr_frame + .create_sub_element(ElementName::FrameLength)? + .set_character_data(byte_length.to_string())?; + + Ok(Self(fr_frame)) + } + + pub fn map_pdu( + &self, + pdu: &Pdu, + start_position: u32, + byte_order: ByteOrder, + update_bit: Option, + ) -> Result<(), AutosarAbstractionError> { + Frame::from(self.clone()).map_pdu(pdu, start_position, byte_order, update_bit) + } +} + +impl From for Frame { + fn from(frame: FlexrayFrame) -> Self { + Frame::Flexray(frame) + } +} + +//################################################################## + +/// The frame triggering connects a frame to a physical channel +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct FlexrayFrameTriggering(Element); +abstraction_element!(FlexrayFrameTriggering, FlexrayFrameTriggering); + +impl FlexrayFrameTriggering { + pub(crate) fn new( + channel: &FlexrayPhysicalChannel, + frame: &FlexrayFrame, + slot_id: u16, + timing: &FlexrayCommunicationCycle, + ) -> Result { + let model = channel.element().model()?; + let base_path = channel.element().path()?; + let frame_name = frame + .name() + .ok_or(AutosarAbstractionError::InvalidParameter("invalid frame".to_string()))?; + let ft_name = format!("FT_{frame_name}"); + let ft_name = make_unique_name(&model, base_path, ft_name); + + let frame_triggerings = channel + .element() + .get_or_create_sub_element(ElementName::FrameTriggerings)?; + let fr_triggering = + frame_triggerings.create_named_sub_element(ElementName::FlexrayFrameTriggering, &ft_name)?; + + fr_triggering + .create_sub_element(ElementName::FrameRef)? + .set_reference_target(frame.element())?; + + let ft = Self(fr_triggering); + ft.set_slot(slot_id)?; + ft.set_timing(timing)?; + + Ok(ft) + } + + /// set the slot id for the flexray frame triggering + pub fn set_slot(&self, slot_id: u16) -> Result<(), AutosarAbstractionError> { + self.element() + .get_or_create_sub_element(ElementName::AbsolutelyScheduledTimings)? + .get_or_create_sub_element(ElementName::FlexrayAbsolutelyScheduledTiming)? + .get_or_create_sub_element(ElementName::SlotId)? + .set_character_data(slot_id.to_string())?; + Ok(()) + } + + /// get the slot id of the flexray frame triggering + /// + /// In a well-formed file this always returns Some(value); None should only be seen if the file is incomplete. + pub fn get_slot(&self) -> Option { + self.element() + .get_sub_element(ElementName::AbsolutelyScheduledTimings)? + .get_sub_element(ElementName::FlexrayAbsolutelyScheduledTiming)? + .get_sub_element(ElementName::SlotId)? + .character_data()? + .decode_integer() + } + + /// set the timing of the flexray frame + pub fn set_timing(&self, timing: &FlexrayCommunicationCycle) -> Result<(), AutosarAbstractionError> { + let timings_elem = self + .element() + .get_or_create_sub_element(ElementName::AbsolutelyScheduledTimings)? + .get_or_create_sub_element(ElementName::FlexrayAbsolutelyScheduledTiming)? + .get_or_create_sub_element(ElementName::CommunicationCycle)?; + match timing { + FlexrayCommunicationCycle::Counter { cycle_counter } => { + if let Some(repetition) = timings_elem.get_sub_element(ElementName::CycleRepetition) { + let _ = timings_elem.remove_sub_element(repetition); + } + timings_elem + .get_or_create_sub_element(ElementName::CycleCounter)? + .get_or_create_sub_element(ElementName::CycleCounter)? + .set_character_data(cycle_counter.to_string())?; + } + FlexrayCommunicationCycle::Repetition { + base_cycle, + cycle_repetition, + } => { + if let Some(counter) = timings_elem.get_sub_element(ElementName::CycleCounter) { + let _ = timings_elem.remove_sub_element(counter); + } + let repetition = timings_elem.get_or_create_sub_element(ElementName::CycleRepetition)?; + repetition + .get_or_create_sub_element(ElementName::BaseCycle)? + .set_character_data(base_cycle.to_string())?; + repetition + .get_or_create_sub_element(ElementName::CycleRepetition)? + .set_character_data::((*cycle_repetition).into())?; + } + } + Ok(()) + } + + /// get the timing of the flexray frame + /// + /// In a well-formed file this should always return Some(...) + pub fn get_timing(&self) -> Option { + let timings = self + .element() + .get_sub_element(ElementName::AbsolutelyScheduledTimings)? + .get_sub_element(ElementName::FlexrayAbsolutelyScheduledTiming)? + .get_sub_element(ElementName::CommunicationCycle)?; + + if let Some(counter_based) = timings.get_sub_element(ElementName::CycleCounter) { + let cycle_counter = counter_based + .get_sub_element(ElementName::CycleCounter)? + .character_data()? + .decode_integer()?; + Some(FlexrayCommunicationCycle::Counter { cycle_counter }) + } else if let Some(repetition) = timings.get_sub_element(ElementName::CycleRepetition) { + let base_cycle = repetition + .get_sub_element(ElementName::BaseCycle)? + .character_data()? + .decode_integer()?; + let cycle_repetition = repetition + .get_sub_element(ElementName::CycleRepetition)? + .character_data()? + .enum_value()? + .try_into() + .ok()?; + + Some(FlexrayCommunicationCycle::Repetition { + base_cycle, + cycle_repetition, + }) + } else { + None + } + } +} + +//################################################################## + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum FlexrayCommunicationCycle { + Counter { + cycle_counter: u8, + }, + Repetition { + base_cycle: u8, + cycle_repetition: CycleRepetition, + }, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CycleRepetition { + C1, + C2, + C4, + C5, + C8, + C10, + C16, + C20, + C32, + C40, + C50, + C64, +} + +impl TryFrom for CycleRepetition { + type Error = AutosarAbstractionError; + + fn try_from(value: EnumItem) -> Result { + match value { + EnumItem::CycleRepetition1 => Ok(Self::C1), + EnumItem::CycleRepetition2 => Ok(Self::C2), + EnumItem::CycleRepetition4 => Ok(Self::C4), + EnumItem::CycleRepetition5 => Ok(Self::C5), + EnumItem::CycleRepetition8 => Ok(Self::C8), + EnumItem::CycleRepetition10 => Ok(Self::C10), + EnumItem::CycleRepetition16 => Ok(Self::C16), + EnumItem::CycleRepetition20 => Ok(Self::C20), + EnumItem::CycleRepetition32 => Ok(Self::C32), + EnumItem::CycleRepetition40 => Ok(Self::C40), + EnumItem::CycleRepetition50 => Ok(Self::C50), + EnumItem::CycleRepetition64 => Ok(Self::C64), + + _ => Err(AutosarAbstractionError::ValueConversionError { + value: value.to_string(), + dest: "CycleRepetitionType".to_string(), + }), + } + } +} + +impl From for EnumItem { + fn from(value: CycleRepetition) -> Self { + match value { + CycleRepetition::C1 => EnumItem::CycleRepetition1, + CycleRepetition::C2 => EnumItem::CycleRepetition2, + CycleRepetition::C4 => EnumItem::CycleRepetition4, + CycleRepetition::C5 => EnumItem::CycleRepetition5, + CycleRepetition::C8 => EnumItem::CycleRepetition8, + CycleRepetition::C10 => EnumItem::CycleRepetition10, + CycleRepetition::C16 => EnumItem::CycleRepetition16, + CycleRepetition::C20 => EnumItem::CycleRepetition20, + CycleRepetition::C32 => EnumItem::CycleRepetition32, + CycleRepetition::C40 => EnumItem::CycleRepetition40, + CycleRepetition::C50 => EnumItem::CycleRepetition50, + CycleRepetition::C64 => EnumItem::CycleRepetition64, + } + } +} diff --git a/autosar-data-abstraction/src/communication/frame/mod.rs b/autosar-data-abstraction/src/communication/frame/mod.rs new file mode 100644 index 0000000..5a276e4 --- /dev/null +++ b/autosar-data-abstraction/src/communication/frame/mod.rs @@ -0,0 +1,33 @@ +use crate::{AbstractionElement, AutosarAbstractionError, ByteOrder}; +use crate::communication::Pdu; + +mod can; +mod flexray; +// ethernet does not use frames. PDUs are transmitted over SomeIp or static SocketConnections + +pub use can::*; +pub use flexray::*; + +//################################################################## + +#[derive(Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub enum Frame { + Can(CanFrame), + Flexray(FlexrayFrame), +} + +impl AbstractionElement for Frame { + fn element(&self) -> &autosar_data::Element { + match self { + Self::Can(cf) => cf.element(), + Self::Flexray(ff) => ff.element(), + } + } +} + +impl Frame { + pub fn map_pdu(&self, pdu: &Pdu, start_position: u32, byte_order: ByteOrder, update_bit: Option) -> Result<(), AutosarAbstractionError> { + todo!() + } +} diff --git a/autosar-data-abstraction/src/communication/mod.rs b/autosar-data-abstraction/src/communication/mod.rs new file mode 100644 index 0000000..22ffde5 --- /dev/null +++ b/autosar-data-abstraction/src/communication/mod.rs @@ -0,0 +1,17 @@ +mod cluster; +mod controller; +mod frame; +mod networkendpoint; +mod pdu; +mod physical_channel; +mod signal; +mod socketaddress; + +pub use cluster::*; +pub use controller::*; +pub use frame::*; +pub use networkendpoint::*; +pub use pdu::*; +pub use physical_channel::*; +pub use signal::*; +pub use socketaddress::*; diff --git a/autosar-data-abstraction/src/communication/networkendpoint.rs b/autosar-data-abstraction/src/communication/networkendpoint.rs new file mode 100644 index 0000000..51bc7d9 --- /dev/null +++ b/autosar-data-abstraction/src/communication/networkendpoint.rs @@ -0,0 +1,280 @@ +use crate::communication::EthernetPhysicalChannel; +use crate::{abstraction_element, basic_element_iterator, AbstractionElement, AutosarAbstractionError}; +use autosar_data::{CharacterData, Element, ElementName, EnumItem}; + +/// A network endpoint contains address information for a connection +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct NetworkEndpoint(Element); +abstraction_element!(NetworkEndpoint, NetworkEndpoint); + +impl NetworkEndpoint { + pub(crate) fn new( + name: &str, + channel: &EthernetPhysicalChannel, + address: NetworkEndpointAddress, + ) -> Result { + let el_network_endpoint = channel + .element() + .get_or_create_sub_element(ElementName::NetworkEndpoints)? + .create_named_sub_element(ElementName::NetworkEndpoint, name)?; + + let network_endpoint = Self(el_network_endpoint); + let result = network_endpoint.add_network_endpoint_address(address); + if let Err(error) = result { + let _ = channel.element().remove_sub_element(network_endpoint.0); + return Err(error); + } + + Ok(network_endpoint) + } + + /// add a network endpoint address to this `NetworkEndpoint` + /// + /// A `NetworkEndpoint` may have multiple sets of address information. The following restrictions apply: + /// + /// - all addresses must have the same type, i.e. all IPv4 or all IPv6 + /// - only one of them may be a `Fixed` address, all others must be dynamic (DHCP, automatic link local, etc.) + pub fn add_network_endpoint_address(&self, address: NetworkEndpointAddress) -> Result<(), AutosarAbstractionError> { + let mut fixedcount = 0; + if matches!(address, NetworkEndpointAddress::IPv4 { address_source, .. } if address_source == Some(IPv4AddressSource::Fixed)) + || matches!(address, NetworkEndpointAddress::IPv6 { address_source, .. } if address_source == Some(IPv6AddressSource::Fixed)) + { + fixedcount = 1; + } + for existing_address in self.addresses() { + if std::mem::discriminant(&existing_address) != std::mem::discriminant(&address) { + return Err(AutosarAbstractionError::InvalidParameter( + "you cannot mix IPv4 and IPv6 inside one NetworkEndpoint".to_string(), + )); + } + if matches!(existing_address, NetworkEndpointAddress::IPv4 { address_source, .. } if address_source == Some(IPv4AddressSource::Fixed)) + || matches!(existing_address, NetworkEndpointAddress::IPv6 { address_source, .. } if address_source == Some(IPv6AddressSource::Fixed)) + { + fixedcount += 1; + } + } + if fixedcount > 1 { + return Err(AutosarAbstractionError::InvalidParameter( + "Only one NetworkEndpointAddress can be a fixed address".to_string(), + )); + } + + let addresses = self + .0 + .get_or_create_sub_element(ElementName::NetworkEndpointAddresses)?; + match address { + NetworkEndpointAddress::IPv4 { + address, + address_source, + default_gateway, + network_mask, + } => { + let cfg = addresses.create_sub_element(ElementName::Ipv4Configuration)?; + if let Some(addr) = address { + cfg.create_sub_element(ElementName::Ipv4Address)? + .set_character_data(addr)?; + } + if let Some(addr_src) = address_source { + cfg.create_sub_element(ElementName::Ipv4AddressSource)? + .set_character_data(addr_src)?; + } + if let Some(defgw) = default_gateway { + cfg.create_sub_element(ElementName::DefaultGateway)? + .set_character_data(defgw)?; + } + if let Some(netmask) = network_mask { + cfg.create_sub_element(ElementName::NetworkMask)? + .set_character_data(netmask)?; + } + } + NetworkEndpointAddress::IPv6 { + address, + address_source, + default_router, + } => { + let cfg = addresses.create_sub_element(ElementName::Ipv6Configuration)?; + if let Some(addr) = address { + cfg.create_sub_element(ElementName::Ipv6Address)? + .set_character_data(addr)?; + } + if let Some(addr_src) = address_source { + cfg.create_sub_element(ElementName::Ipv6AddressSource)? + .set_character_data(addr_src.to_cdata())?; + } + if let Some(dr) = default_router { + cfg.create_sub_element(ElementName::DefaultRouter)? + .set_character_data(dr)?; + } + } + } + Ok(()) + } + + /// iterator over all addresses in the NetworkEndpoint + pub fn addresses(&self) -> NetworkEndpointAddressIterator { + NetworkEndpointAddressIterator::new(self.element().get_sub_element(ElementName::NetworkEndpointAddresses)) + } +} + +//################################################################## + +/// address information for a network endpoint +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum NetworkEndpointAddress { + /// IPv4 addressing information + IPv4 { + /// IPv4 address in the form "a.b.c.d". This is used if the address source is FIXED + address: Option, + /// defines how the address is obtained + address_source: Option, + /// ip address of the default gateway + default_gateway: Option, + /// Network mask in the form "a.b.c.d" + network_mask: Option, + }, + /// IPv6 addressing information + IPv6 { + /// IPv6 address, without abbreviation + address: Option, + /// defines how the address is obtained + address_source: Option, + /// IP address of the default router + default_router: Option, + }, +} + +impl TryFrom for NetworkEndpointAddress { + type Error = AutosarAbstractionError; + + fn try_from(element: Element) -> Result { + match element.element_name() { + ElementName::Ipv4Configuration => { + let address = element + .get_sub_element(ElementName::Ipv4Address) + .and_then(|i4a| i4a.character_data()) + .and_then(|cdata| cdata.string_value()); + let address_source = element + .get_sub_element(ElementName::Ipv4AddressSource) + .and_then(|i4as| i4as.character_data()) + .and_then(IPv4AddressSource::from_cdata); + let default_gateway = element + .get_sub_element(ElementName::DefaultGateway) + .and_then(|dg| dg.character_data()) + .and_then(|cdata| cdata.string_value()); + let network_mask = element + .get_sub_element(ElementName::NetworkMask) + .and_then(|nm| nm.character_data()) + .and_then(|cdata| cdata.string_value()); + + Ok(NetworkEndpointAddress::IPv4 { + address, + address_source, + default_gateway, + network_mask, + }) + } + ElementName::Ipv6Configuration => { + let address = element + .get_sub_element(ElementName::Ipv6Address) + .and_then(|i6a| i6a.character_data()) + .and_then(|cdata| cdata.string_value()); + let address_source = element + .get_sub_element(ElementName::Ipv6AddressSource) + .and_then(|i6as| i6as.character_data()) + .and_then(IPv6AddressSource::from_cdata); + let default_router = element + .get_sub_element(ElementName::DefaultRouter) + .and_then(|dr| dr.character_data()) + .and_then(|cdata| cdata.string_value()); + + Ok(NetworkEndpointAddress::IPv6 { + address, + address_source, + default_router, + }) + } + _ => Err(AutosarAbstractionError::ConversionError { + element, + dest: "NetwworkEndpointAddress".to_string(), + }), + } + } +} + +/// `IPv4AddressSource` defines how the address of an IPv4 `NetworkEndpoint` is obtained +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum IPv4AddressSource { + /// use AutoIp (aka APIPA) to assign a link-local address + AutoIp, + /// use AutoIp with DoIp settings to assign a link-local address + AutoIpDoIp, + /// dynamic assignment using DHCP + DHCPv4, + /// static IP address configuration - the address must be specified in NetworkEndpointAddress + Fixed, +} + +impl IPv4AddressSource { + fn from_cdata(cdata: CharacterData) -> Option { + match cdata { + CharacterData::Enum(EnumItem::AutoIp) => Some(Self::AutoIp), + CharacterData::Enum(EnumItem::AutoIpDoip) => Some(Self::AutoIpDoIp), + CharacterData::Enum(EnumItem::Dhcpv4) => Some(Self::DHCPv4), + CharacterData::Enum(EnumItem::Fixed) => Some(Self::Fixed), + _ => None, + } + } +} + +impl From for CharacterData { + fn from(value: IPv4AddressSource) -> Self { + match value { + IPv4AddressSource::AutoIp => CharacterData::Enum(EnumItem::AutoIp), + IPv4AddressSource::AutoIpDoIp => CharacterData::Enum(EnumItem::AutoIpDoip), + IPv4AddressSource::DHCPv4 => CharacterData::Enum(EnumItem::Dhcpv4), + IPv4AddressSource::Fixed => CharacterData::Enum(EnumItem::Fixed), + } + } +} + +/// `IPv6AddressSource` defines how the address of an IPv6 `NetworkEndpoint` is obtained +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum IPv6AddressSource { + /// dynamic assignment using DHCP + DHCPv6, + /// static IP address configuration - the address must be specified in NetworkEndpointAddress + Fixed, + /// automatic link local address assignment + LinkLocal, + /// automatic link local address assignment using doip parameters + LinkLocalDoIp, + /// IPv6 stateless autoconfiguration + RouterAdvertisement, +} + +impl IPv6AddressSource { + fn from_cdata(cdata: CharacterData) -> Option { + match cdata { + CharacterData::Enum(EnumItem::Dhcpv6) => Some(Self::DHCPv6), + CharacterData::Enum(EnumItem::Fixed) => Some(Self::Fixed), + CharacterData::Enum(EnumItem::LinkLocal) => Some(Self::LinkLocal), + CharacterData::Enum(EnumItem::LinkLocalDoip) => Some(Self::LinkLocalDoIp), + CharacterData::Enum(EnumItem::RouterAdvertisement) => Some(Self::RouterAdvertisement), + _ => None, + } + } + + fn to_cdata(self) -> CharacterData { + match self { + Self::DHCPv6 => CharacterData::Enum(EnumItem::Dhcpv6), + Self::Fixed => CharacterData::Enum(EnumItem::Fixed), + Self::LinkLocal => CharacterData::Enum(EnumItem::LinkLocal), + Self::LinkLocalDoIp => CharacterData::Enum(EnumItem::LinkLocalDoip), + Self::RouterAdvertisement => CharacterData::Enum(EnumItem::RouterAdvertisement), + } + } +} + +//################################################################## + +basic_element_iterator!(NetworkEndpointAddressIterator, NetworkEndpointAddress); diff --git a/autosar-data-abstraction/src/communication/pdu/mod.rs b/autosar-data-abstraction/src/communication/pdu/mod.rs new file mode 100644 index 0000000..6d948dd --- /dev/null +++ b/autosar-data-abstraction/src/communication/pdu/mod.rs @@ -0,0 +1,254 @@ +use crate::{abstraction_element, make_unique_name, AbstractionElement, ArPackage, AutosarAbstractionError}; +use autosar_data::{Element, ElementName, EnumItem}; + +use super::PhysicalChannel; + +//################################################################## + +/// Represents the IPdus handled by Com +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct ISignalIPdu(Element); +abstraction_element!(ISignalIPdu, ISignalIPdu); + +impl AbstractPdu for ISignalIPdu {} + +impl ISignalIPdu { + pub(crate) fn new(name: &str, package: &ArPackage) -> Result { + let pkg_elements = package.element().get_or_create_sub_element(ElementName::Elements)?; + let elem_pdu = pkg_elements.create_named_sub_element(ElementName::ISignalIPdu, name)?; + + Ok(Self(elem_pdu)) + } +} + +//################################################################## + +/// Network Management Pdu +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct NmPdu(Element); +abstraction_element!(NmPdu, NmPdu); + +impl AbstractPdu for NmPdu {} + +impl NmPdu { + pub(crate) fn new(name: &str, package: &ArPackage) -> Result { + let pkg_elements = package.element().get_or_create_sub_element(ElementName::Elements)?; + let elem_pdu = pkg_elements.create_named_sub_element(ElementName::NmPdu, name)?; + + Ok(Self(elem_pdu)) + } +} + +//################################################################## + +/// This is a Pdu of the transport layer. The main purpose of the TP layer is to segment and reassemble IPdus. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct NPdu(Element); +abstraction_element!(NPdu, NPdu); + +impl AbstractPdu for NPdu {} + +impl NPdu { + pub(crate) fn new(name: &str, package: &ArPackage) -> Result { + let pkg_elements = package.element().get_or_create_sub_element(ElementName::Elements)?; + let elem_pdu = pkg_elements.create_named_sub_element(ElementName::NPdu, name)?; + + Ok(Self(elem_pdu)) + } +} + +//################################################################## + +/// Represents the IPdus handled by Dcm +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct DcmIPdu(Element); +abstraction_element!(DcmIPdu, DcmIPdu); + +impl AbstractPdu for DcmIPdu {} + +impl DcmIPdu { + pub(crate) fn new(name: &str, package: &ArPackage) -> Result { + let pkg_elements = package.element().get_or_create_sub_element(ElementName::Elements)?; + let elem_pdu = pkg_elements.create_named_sub_element(ElementName::DcmIPdu, name)?; + + Ok(Self(elem_pdu)) + } +} + +//################################################################## + +/// This element is used for AUTOSAR Pdus without additional attributes that are routed by a bus interface +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct GeneralPurposePdu(Element); +abstraction_element!(GeneralPurposePdu, GeneralPurposePdu); + +impl AbstractPdu for GeneralPurposePdu {} + +impl GeneralPurposePdu { + pub(crate) fn new(name: &str, package: &ArPackage) -> Result { + let pkg_elements = package.element().get_or_create_sub_element(ElementName::Elements)?; + let elem_pdu = pkg_elements.create_named_sub_element(ElementName::GeneralPurposePdu, name)?; + + Ok(Self(elem_pdu)) + } +} + +//################################################################## + +/// This element is used for AUTOSAR Pdus without attributes that are routed by the PduR +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct GeneralPurposeIPdu(Element); +abstraction_element!(GeneralPurposeIPdu, GeneralPurposeIPdu); + +impl AbstractPdu for GeneralPurposeIPdu {} + +impl GeneralPurposeIPdu { + pub(crate) fn new(name: &str, package: &ArPackage) -> Result { + let pkg_elements = package.element().get_or_create_sub_element(ElementName::Elements)?; + let elem_pdu = pkg_elements.create_named_sub_element(ElementName::GeneralPurposeIPdu, name)?; + + Ok(Self(elem_pdu)) + } +} + +//################################################################## + +/// Several IPdus can be collected in one ContainerIPdu based on the headerType +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct ContainerIPdu(Element); +abstraction_element!(ContainerIPdu, ContainerIPdu); + +impl AbstractPdu for ContainerIPdu {} + +impl ContainerIPdu { + pub(crate) fn new(name: &str, package: &ArPackage) -> Result { + let pkg_elements = package.element().get_or_create_sub_element(ElementName::Elements)?; + let elem_pdu = pkg_elements.create_named_sub_element(ElementName::ContainerIPdu, name)?; + + Ok(Self(elem_pdu)) + } +} + +//################################################################## + +/// Wraps an IPdu to protect it from unauthorized manipulation +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct SecuredIPdu(Element); +abstraction_element!(SecuredIPdu, SecuredIPdu); + +impl AbstractPdu for SecuredIPdu {} + +impl SecuredIPdu { + pub(crate) fn new(name: &str, package: &ArPackage) -> Result { + let pkg_elements = package.element().get_or_create_sub_element(ElementName::Elements)?; + let elem_pdu = pkg_elements.create_named_sub_element(ElementName::SecuredIPdu, name)?; + + Ok(Self(elem_pdu)) + } +} + +//################################################################## + +/// The multiplexed pdu contains one of serveral signal pdus +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct MultiplexedIPdu(Element); +abstraction_element!(MultiplexedIPdu, MultiplexedIPdu); + +impl AbstractPdu for MultiplexedIPdu {} + +impl MultiplexedIPdu { + pub(crate) fn new(name: &str, package: &ArPackage) -> Result { + let pkg_elements = package.element().get_or_create_sub_element(ElementName::Elements)?; + let elem_pdu = pkg_elements.create_named_sub_element(ElementName::MultiplexedIPdu, name)?; + + Ok(Self(elem_pdu)) + } +} + +//################################################################## + +pub trait AbstractPdu: AbstractionElement {} + +//################################################################## + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum Pdu { + ISignalIPdu(ISignalIPdu), + NmPdu(NmPdu), + NPdu(NPdu), + DcmIPdu(DcmIPdu), + GeneralPurposePdu(GeneralPurposePdu), + GeneralPurposeIPdu(GeneralPurposeIPdu), + ContainerIPdu(ContainerIPdu), + SecuredIPdu(SecuredIPdu), + MultiplexedIPdu(MultiplexedIPdu), +} + +impl AbstractionElement for Pdu { + fn element(&self) -> &Element { + match self { + Pdu::ISignalIPdu(pdu) => pdu.element(), + Pdu::NmPdu(pdu) => pdu.element(), + Pdu::NPdu(pdu) => pdu.element(), + Pdu::DcmIPdu(pdu) => pdu.element(), + Pdu::GeneralPurposePdu(pdu) => pdu.element(), + Pdu::GeneralPurposeIPdu(pdu) => pdu.element(), + Pdu::ContainerIPdu(pdu) => pdu.element(), + Pdu::SecuredIPdu(pdu) => pdu.element(), + Pdu::MultiplexedIPdu(pdu) => pdu.element(), + } + } +} + +impl AbstractPdu for Pdu {} + +//################################################################## + +/// +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct PduTriggering(Element); +abstraction_element!(PduTriggering, PduTriggering); + +impl PduTriggering { + pub(crate) fn new(pdu: &Pdu, channel: &PhysicalChannel) -> Result { + let model = channel.element().model()?; + let base_path = channel.element().path()?; + let pdu_name = pdu + .name() + .ok_or(AutosarAbstractionError::InvalidParameter("invalid pdu".to_string()))?; + let pt_name = format!("PT_{pdu_name}"); + let pt_name = make_unique_name(&model, base_path, pt_name); + + let triggerings = channel + .element() + .get_or_create_sub_element(ElementName::PduTriggerings)?; + let pt_elem = triggerings.create_named_sub_element(ElementName::PduTriggering, &pt_name)?; + + let pt = Self(pt_elem); + + Ok(pt) + } +} + +//################################################################## + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PduCollectionTrigger { + Always, + Never, +} + +impl PduCollectionTrigger { + pub fn to_enumitem(&self) -> EnumItem { + match self { + Self::Always => EnumItem::Always, + Self::Never => EnumItem::Never, + } + } +} + +impl ToString for PduCollectionTrigger { + fn to_string(&self) -> String { + self.to_enumitem().to_string() + } +} diff --git a/autosar-data-abstraction/src/communication/physical_channel/can.rs b/autosar-data-abstraction/src/communication/physical_channel/can.rs new file mode 100644 index 0000000..1c7fbcc --- /dev/null +++ b/autosar-data-abstraction/src/communication/physical_channel/can.rs @@ -0,0 +1,85 @@ +use crate::{ + abstraction_element, + communication::{CanAddressingMode, CanCluster, CanFrame, CanFrameTriggering, CanFrameType}, + AbstractionElement, AutosarAbstractionError, +}; +use autosar_data::Element; + +/// The `CanPhysicalChannel contains all of the communication on a CAN network +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct CanPhysicalChannel(Element); +abstraction_element!(CanPhysicalChannel, CanPhysicalChannel); + +impl CanPhysicalChannel { + /// get the cluster containing this physical channel + /// + /// # Example + /// + /// ``` + /// # use autosar_data::*; + /// # use autosar_data_abstraction::*; + /// # use autosar_data_abstraction::communication::*; + /// # let model = AutosarModel::new(); + /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); + /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); + /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); + /// # let cluster = system.create_can_cluster("Cluster", &package, &CanClusterSettings::default()).unwrap(); + /// let channel = cluster.create_physical_channel("Channel").unwrap(); + /// let cluster_2 = channel.cluster().unwrap(); + /// assert_eq!(cluster, cluster_2); + /// ``` + pub fn cluster(&self) -> Result { + let cluster_elem = self.0.named_parent()?.unwrap(); + CanCluster::try_from(cluster_elem) + } + + /// add a trigger for a CAN frame in this physical channel + /// + /// # Example + /// + /// ``` + /// # use autosar_data::*; + /// # use autosar_data_abstraction::*; + /// # use autosar_data_abstraction::communication::*; + /// # let model = AutosarModel::new(); + /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); + /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); + /// # let frame_package = ArPackage::get_or_create(&model, "/Frames").unwrap(); + /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); + /// # let cluster = system.create_can_cluster("Cluster", &package, &CanClusterSettings::default()).unwrap(); + /// let channel = cluster.create_physical_channel("Channel").unwrap(); + /// let frame = system.create_can_frame("Frame", 8, &frame_package).unwrap(); + /// channel.trigger_frame(&frame, 0x100, CanAddressingMode::Standard, CanFrameType::Can20).unwrap(); + /// ``` + pub fn trigger_frame( + &self, + frame: &CanFrame, + identifier: u32, + addressing_mode: CanAddressingMode, + frame_type: CanFrameType, + ) -> Result { + CanFrameTriggering::new(self, frame, identifier, addressing_mode, frame_type) + } +} + +//################################################################## + +#[cfg(test)] +mod test { + use crate::{communication::CanClusterSettings, ArPackage, System, SystemCategory}; + use autosar_data::{AutosarModel, AutosarVersion}; + + #[test] + fn channel() { + let model = AutosarModel::new(); + model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); + let pkg = ArPackage::get_or_create(&model, "/test").unwrap(); + let system = System::new("System", &pkg, SystemCategory::SystemDescription).unwrap(); + let settings = CanClusterSettings::default(); + let cluster = system.create_can_cluster("CanCluster", &pkg, &settings).unwrap(); + + let channel = cluster.create_physical_channel("channel_name").unwrap(); + let c2 = channel.cluster().unwrap(); + assert_eq!(cluster, c2); + } +} diff --git a/autosar-data-abstraction/src/communication/physical_channel/ethernet.rs b/autosar-data-abstraction/src/communication/physical_channel/ethernet.rs new file mode 100644 index 0000000..869570f --- /dev/null +++ b/autosar-data-abstraction/src/communication/physical_channel/ethernet.rs @@ -0,0 +1,411 @@ +use crate::communication::{ + EthernetCluster, NetworkEndpoint, NetworkEndpointAddress, Pdu, PduCollectionTrigger, SocketAddress, + SocketAddressType, TpConfig, +}; +use crate::{abstraction_element, basic_element_iterator, AbstractionElement, AutosarAbstractionError, EcuInstance}; +use autosar_data::{AutosarVersion, Element, ElementName}; + +/// Provides information about the VLAN of an [`EthernetPhysicalChannel`] +#[derive(Debug, Clone, PartialEq)] +pub struct EthernetVlanInfo { + pub vlan_name: String, + pub vlan_id: u16, +} + +//################################################################## + +/// The `EthernetPhysicalChannel` represents a VLAN or untagged traffic +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct EthernetPhysicalChannel(Element); +abstraction_element!(EthernetPhysicalChannel, EthernetPhysicalChannel); + +impl EthernetPhysicalChannel { + /// Retrieves the VLAN information from a channel + /// + /// An ethernet physical channel that represents untagged traffic has no VLAN information and returns None. + /// + /// # Example + /// + /// ``` + /// # use autosar_data::*; + /// # use autosar_data_abstraction::*; + /// # use autosar_data_abstraction::communication::*; + /// # let model = AutosarModel::new(); + /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); + /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); + /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); + /// # let cluster = system.create_ethernet_cluster("Cluster", &package).unwrap(); + /// let vlan_info = EthernetVlanInfo { + /// vlan_name: "VLAN_1".to_string(), + /// vlan_id: 1, + /// }; + /// let channel = cluster.create_physical_channel("Channel", Some(vlan_info)).unwrap(); + /// let info = channel.get_vlan_info().unwrap(); + /// assert_eq!(info.vlan_id, 1); + /// ``` + #[must_use] + pub fn get_vlan_info(&self) -> Option { + let vlan = self.0.get_sub_element(ElementName::Vlan)?; + let vlan_id = vlan + .get_sub_element(ElementName::VlanIdentifier)? + .character_data()? + .decode_integer::()?; + Some(EthernetVlanInfo { + vlan_name: vlan.item_name()?, + vlan_id, + }) + } + + /// get the cluster containing this physical channel + /// + /// # Example + /// + /// ``` + /// # use autosar_data::*; + /// # use autosar_data_abstraction::*; + /// # let model = AutosarModel::new(); + /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); + /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); + /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); + /// # let cluster = system.create_ethernet_cluster("Cluster", &package).unwrap(); + /// let channel = cluster.create_physical_channel("Channel", None).unwrap(); + /// let cluster_2 = channel.cluster().unwrap(); + /// assert_eq!(cluster, cluster_2); + /// ``` + /// + /// # Errors + /// + /// - [`AutosarAbstractionError::ModelError`] An error occurred in the Autosar model + pub fn cluster(&self) -> Result { + let cluster_elem = self.0.named_parent()?.unwrap(); + EthernetCluster::try_from(cluster_elem) + } + + /// create a network endpoint - IPv4 or IPv6 address - for this channel + /// + /// In older versions of the Autosar standard, up to version 4.4.0, the `NetworkEndpoint` could be linked to an Ecu. + /// The parameter `ecu` specifies the target. + /// The link is obsoleted in newer versions, and will only be created if the file version allows it. + /// + /// # Example + /// + /// ``` + /// # use autosar_data::*; + /// # use autosar_data_abstraction::*; + /// # use autosar_data_abstraction::communication::*; + /// # let model = AutosarModel::new(); + /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); + /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); + /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); + /// # let cluster = system.create_ethernet_cluster("Cluster", &package).unwrap(); + /// # let channel = cluster.create_physical_channel("Channel", None).unwrap(); + /// let endpoint_address = NetworkEndpointAddress::IPv4 { + /// address: Some("192.168.0.1".to_string()), + /// address_source: Some(IPv4AddressSource::Fixed), + /// default_gateway: Some("192.168.0.2".to_string()), + /// network_mask: Some("255.255.255.0".to_string()), + /// }; + /// let network_endpoint = channel.create_network_endpoint("Address1", endpoint_address, None).unwrap(); + /// ``` + /// + /// # Errors + /// + /// - [`AutosarAbstractionError::ModelError`] An error occurred in the Autosar model + pub fn create_network_endpoint( + &self, + name: &str, + address: NetworkEndpointAddress, + ecu: Option<&EcuInstance>, + ) -> Result { + let network_endpoint = NetworkEndpoint::new(name, self, address)?; + + if let Some(ecu_instance) = ecu { + let version = self.0.min_version()?; + if version <= AutosarVersion::Autosar_00046 { + let ne_element = network_endpoint.element().clone(); + + // get a connector referenced by this physical channel which is contained in the ecu_instance + if let Some(connector) = self.get_ecu_connector(ecu_instance) { + let _ = connector + .get_or_create_sub_element(ElementName::NetworkEndpointRefs) + .and_then(|ner| ner.create_sub_element(ElementName::NetworkEndpointRef)) + .and_then(|ner| ner.set_reference_target(&ne_element)); + } else { + // no connector between the ECU and this channel -> abort + self.0 + .get_sub_element(ElementName::NetworkEndpoints) + .and_then(|endpoints| endpoints.remove_sub_element(ne_element).ok()); + } + } + } + + Ok(network_endpoint) + } + + /// create an iterator over all [`NetworkEndpoint`]s in this channel + /// + /// # Example + /// + /// ``` + /// # use autosar_data::*; + /// # use autosar_data_abstraction::*; + /// # use autosar_data_abstraction::communication::*; + /// # let model = AutosarModel::new(); + /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); + /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); + /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); + /// # let cluster = system.create_ethernet_cluster("Cluster", &package).unwrap(); + /// # let channel = cluster.create_physical_channel("Channel", None).unwrap(); + /// # let endpoint_address = NetworkEndpointAddress::IPv4 { + /// # address: Some("192.168.0.1".to_string()), + /// # address_source: Some(IPv4AddressSource::Fixed), + /// # default_gateway: Some("192.168.0.2".to_string()), + /// # network_mask: Some("255.255.255.0".to_string()), + /// # }; + /// # channel.create_network_endpoint("Address1", endpoint_address, None).unwrap(); + /// for network_endpoint in channel.network_endpoints() { + /// // ... + /// } + /// # assert_eq!(channel.network_endpoints().count(), 1) + /// ``` + #[must_use] + pub fn network_endpoints(&self) -> NetworkEndpointIterator { + NetworkEndpointIterator::new(self.element().get_sub_element(ElementName::NetworkEndpoints)) + } + + /// create a socket address in the ethernet channel + /// + /// It contains the settings of the TCP/UDP port and links to a [`NetworkEndpoint`] which contains the IP address. + /// The socket address can either be a unicast adress which is associated with a single ECU, or a multicast address + /// + /// # Example + /// + /// ``` + /// # use autosar_data::*; + /// # use autosar_data_abstraction::*; + /// # use autosar_data_abstraction::communication::*; + /// # let model = AutosarModel::new(); + /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); + /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); + /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); + /// # let ecu_instance = system.create_ecu_instance("Ecu", &package).unwrap(); + /// # let controller = ecu_instance.create_ethernet_communication_controller("EthCtrl", None).unwrap(); + /// # let cluster = system.create_ethernet_cluster("Cluster", &package).unwrap(); + /// # let channel = cluster.create_physical_channel("Channel", None).unwrap(); + /// # controller.connect_physical_channel("connection", &channel).unwrap(); + /// # let endpoint_address = NetworkEndpointAddress::IPv4 { + /// # address: Some("192.168.0.1".to_string()), + /// # address_source: Some(IPv4AddressSource::Fixed), + /// # default_gateway: Some("192.168.0.2".to_string()), + /// # network_mask: Some("255.255.255.0".to_string()), + /// # }; + /// # let network_endpoint = channel.create_network_endpoint("Address", endpoint_address, None).unwrap(); + /// let tcp_port = TpConfig::TcpTp { + /// port_number: Some(1234), + /// port_dynamically_assigned: None, + /// }; + /// let socket_type = SocketAddressType::Unicast(Some(ecu_instance)); + /// channel.create_socket_address("SocketName", &network_endpoint, &tcp_port, socket_type).unwrap(); + /// ``` + /// + /// # Errors + /// + /// - [`AutosarAbstractionError::InvalidParameter`] `sa_type` contains a reference to an EcuInstance which is not connected to this channel + /// - [`AutosarAbstractionError::ModelError`] An error occurred in the Autosar model + pub fn create_socket_address( + &self, + name: &str, + network_endpoint: &NetworkEndpoint, + tp_config: &TpConfig, + sa_type: SocketAddressType, + ) -> Result { + SocketAddress::new(name, self, network_endpoint, tp_config, sa_type) + } + + /// create an iterator over all [`SocketAddress`]es in this channel + /// + /// # Example + /// + /// ``` + /// # use autosar_data::*; + /// # use autosar_data_abstraction::*; + /// # use autosar_data_abstraction::communication::*; + /// # let model = AutosarModel::new(); + /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); + /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); + /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); + /// # let ecu_instance = system.create_ecu_instance("Ecu", &package).unwrap(); + /// # let controller = ecu_instance.create_ethernet_communication_controller("EthCtrl", None).unwrap(); + /// # let cluster = system.create_ethernet_cluster("Cluster", &package).unwrap(); + /// # let channel = cluster.create_physical_channel("Channel", None).unwrap(); + /// # controller.connect_physical_channel("connection", &channel).unwrap(); + /// # let endpoint_address = NetworkEndpointAddress::IPv4 { + /// # address: Some("192.168.0.1".to_string()), + /// # address_source: Some(IPv4AddressSource::Fixed), + /// # default_gateway: Some("192.168.0.2".to_string()), + /// # network_mask: Some("255.255.255.0".to_string()), + /// # }; + /// # let network_endpoint = channel.create_network_endpoint("Address", endpoint_address, None).unwrap(); + /// let tcp_port = TpConfig::TcpTp { + /// port_number: Some(1234), + /// port_dynamically_assigned: None, + /// }; + /// let socket_type = SocketAddressType::Unicast(Some(ecu_instance)); + /// channel.create_socket_address("SocketName", &network_endpoint, &tcp_port, socket_type).unwrap(); + /// assert_eq!(channel.socket_addresses().count(), 1); + /// ``` + pub fn socket_addresses(&self) -> SocketAddressIterator { + SocketAddressIterator::new( + self.element() + .get_sub_element(ElementName::SoAdConfig) + .and_then(|sc| sc.get_sub_element(ElementName::SocketAddresss)), + ) + } + + // get the connector element between this channel and an ecu + pub(crate) fn get_ecu_connector(&self, ecu_instance: &EcuInstance) -> Option { + // get a connector referenced by this physical channel which is contained in the ecu_instance + // iterate over the CommunicationConnectorRefConditionals + for ccrc in self.0.get_sub_element(ElementName::CommConnectors)?.sub_elements() { + // check the ecu instance of each referenced communication connector + if let Some(comm_connector) = ccrc + .get_sub_element(ElementName::CommunicationConnectorRef) + .and_then(|ccr| ccr.get_reference_target().ok()) + { + if let Some(ecu_of_connector) = comm_connector.named_parent().ok().flatten() { + if &ecu_of_connector == ecu_instance.element() { + return Some(comm_connector); + } + } + } + } + None + } + + /// create a static connection between two sockets (v1) + /// + /// The static connection transports PDUs between two sockets. + /// + /// The Autosar standard specified two different ways to establish a connection. + /// This function creates a connection using the first method, using SOCKET-CONNECTION-BUNDLEs. + /// + /// Using SOCKET-CONNECTION-BUNDLEs was deprecated after Autosar 4.4.0, but remains available for compatibility. + /// If the file version is <= AUTOSAR_00046 then this is the only available method. + /// In newer files it is important to never mix the two methods, since this is explicitly forbidden by the standard. + pub fn create_static_connection_v1( + &self, + name: &str, + server: &SocketAddress, + client: &SocketAddress, + pdu_config: &Vec, + ) -> Result<(), AutosarAbstractionError> { + let soadcfg = self.0.get_or_create_sub_element(ElementName::SoAdConfig)?; + let connections = soadcfg.get_or_create_sub_element(ElementName::ConnectionBundles)?; + let scb = connections.create_named_sub_element(ElementName::SocketConnectionBundle, name)?; + + scb.create_sub_element(ElementName::ServerPortRef)? + .set_reference_target(server.element())?; + let conn = scb + .create_sub_element(ElementName::BundledConnections)? + .create_sub_element(ElementName::SocketConnection)?; + conn.create_sub_element(ElementName::ClientPortRef)? + .set_reference_target(client.element())?; + + let pdus = conn.create_sub_element(ElementName::Pdus)?; + for pdu_cfg in pdu_config { + let scii = pdus.create_sub_element(ElementName::SocketConnectionIpduIdentifier)?; + scii.create_sub_element(ElementName::HeaderId)? + .set_character_data(pdu_cfg.header_id.to_string())?; + if let Some(timeout) = &pdu_cfg.timeout { + scii.create_sub_element(ElementName::PduCollectionPduTimeout)? + .set_character_data(*timeout)?; + } + if let Some(collection_trigger) = &pdu_cfg.collection_trigger { + scii.create_sub_element(ElementName::PduCollectionTrigger)? + .set_character_data(collection_trigger.to_enumitem())?; + } + + todo!("pdu triggering") + } + + todo!() + } + + /// create a static connection between two sockets (v2) + /// + /// The static connection transports PDUs between two sockets. + /// + /// The Autosar standard specified two different ways to establish a connection. + /// This function creates a connection using the second method, using STATIC-SOCKET-CONNECTIONs. + /// + /// STATIC-SOCKET-CONNECTIONs were introduced in Autosar 4.5.0 (AUTOSAR_00048). + /// If the file version is <= AUTOSAR_00048 then this method always fails. + pub fn create_static_connection_v2( + &self, + name: &str, + server: &SocketAddress, + client: &SocketAddress, + pdu_config: &Vec, + ) -> Result<(), AutosarAbstractionError> { + // + todo!() + } +} + +//################################################################## + +#[derive(Debug, Clone, PartialEq)] +pub struct SoConnIPdu { + pub pdu: Pdu, + pub header_id: u32, + pub timeout: Option, + pub collection_trigger: Option, +} + +//################################################################## + +basic_element_iterator!(NetworkEndpointIterator, NetworkEndpoint); + +//################################################################## + +basic_element_iterator!(SocketAddressIterator, SocketAddress); + +//################################################################## + +#[cfg(test)] +mod test { + use super::*; + use crate::{ArPackage, System, SystemCategory}; + use autosar_data::{AutosarModel, AutosarVersion}; + + #[test] + fn channel() { + let model = AutosarModel::new(); + model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); + let pkg = ArPackage::get_or_create(&model, "/test").unwrap(); + let system = System::new("System", &pkg, SystemCategory::SystemDescription).unwrap(); + let cluster = system.create_ethernet_cluster("EthCluster", &pkg).unwrap(); + + let channel = cluster.create_physical_channel("channel_name", None).unwrap(); + let c2 = channel.cluster().unwrap(); + assert_eq!(cluster, c2); + + let vi = channel.get_vlan_info(); + assert!(vi.is_none()); + + let elem_vlan = channel + .element() + .create_named_sub_element(ElementName::Vlan, "VLAN_1") + .unwrap(); + let vi = channel.get_vlan_info(); + assert!(vi.is_none()); + + let elem_vlanid = elem_vlan.create_sub_element(ElementName::VlanIdentifier).unwrap(); + let vi = channel.get_vlan_info(); + assert!(vi.is_none()); + + elem_vlanid.set_character_data(1).unwrap(); + let vi = channel.get_vlan_info().unwrap(); + assert_eq!(vi.vlan_id, 1); + } +} diff --git a/autosar-data-abstraction/src/communication/physical_channel/flexray.rs b/autosar-data-abstraction/src/communication/physical_channel/flexray.rs new file mode 100644 index 0000000..9128dbd --- /dev/null +++ b/autosar-data-abstraction/src/communication/physical_channel/flexray.rs @@ -0,0 +1,129 @@ +use crate::{ + abstraction_element, + communication::{FlexrayCluster, FlexrayCommunicationCycle, FlexrayFrame, FlexrayFrameTriggering}, + AbstractionElement, AutosarAbstractionError, +}; +use autosar_data::{Element, ElementName, EnumItem}; + +/// the `FlexrayPhysicalChannel` represents either channel A or B of Flexray cluster +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct FlexrayPhysicalChannel(Element); +abstraction_element!(FlexrayPhysicalChannel, FlexrayPhysicalChannel); + +impl FlexrayPhysicalChannel { + /// get the channel name of a `FlexrayPhysicalChannel` + #[must_use] + pub fn channel_name(&self) -> Option { + let cn = self + .0 + .get_sub_element(ElementName::ChannelName)? + .character_data()? + .enum_value()?; + match cn { + EnumItem::ChannelA => Some(FlexrayChannelName::A), + EnumItem::ChannelB => Some(FlexrayChannelName::B), + _ => None, + } + } + + /// get the cluster containing this physical channel + /// + /// # Example + /// + /// ``` + /// # use autosar_data::*; + /// # use autosar_data_abstraction::*; + /// # use autosar_data_abstraction::communication::*; + /// # let model = AutosarModel::new(); + /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); + /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); + /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); + /// # let cluster = system.create_flexray_cluster("Cluster", &package, &FlexrayClusterSettings::default()).unwrap(); + /// let channel = cluster.create_physical_channel("Channel", FlexrayChannelName::A).unwrap(); + /// let cluster_2 = channel.cluster().unwrap(); + /// assert_eq!(cluster, cluster_2); + /// ``` + /// + /// # Errors + /// + /// - [`AutosarAbstractionError::ModelError`] An error occurred in the Autosar model + pub fn cluster(&self) -> Result { + let cluster_elem = self.0.named_parent()?.unwrap(); + FlexrayCluster::try_from(cluster_elem) + } + + /// add a trigger for a flexray frame in this physical channel + /// + /// # Example + /// + /// ``` + /// # use autosar_data::*; + /// # use autosar_data_abstraction::*; + /// # use autosar_data_abstraction::communication::*; + /// # let model = AutosarModel::new(); + /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); + /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); + /// # let frame_package = ArPackage::get_or_create(&model, "/Frames").unwrap(); + /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); + /// # let cluster = system.create_flexray_cluster("Cluster", &package, &FlexrayClusterSettings::default()).unwrap(); + /// let channel = cluster.create_physical_channel("Channel", FlexrayChannelName::A).unwrap(); + /// let frame = system.create_flexray_frame("Frame", 64, &frame_package).unwrap(); + /// let timing = FlexrayCommunicationCycle::Repetition {base_cycle: 1, cycle_repetition: CycleRepetition::C1}; + /// channel.trigger_frame(&frame, 1, &timing).unwrap(); + /// ``` + pub fn trigger_frame( + &self, + frame: &FlexrayFrame, + slot_id: u16, + timing: &FlexrayCommunicationCycle, + ) -> Result { + FlexrayFrameTriggering::new(self, frame, slot_id, timing) + } +} + +//################################################################## + +/// A flexray cluster may contain the channels A and/or B. +/// +/// This enum is an abstraction over the \ element. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum FlexrayChannelName { + A, + B, +} + +//################################################################## + +#[cfg(test)] +mod test { + use crate::{ + communication::{FlexrayChannelName, FlexrayClusterSettings}, + AbstractionElement, ArPackage, System, SystemCategory, + }; + use autosar_data::{AutosarModel, AutosarVersion, ElementName}; + + #[test] + fn channel() { + let model = AutosarModel::new(); + model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); + let pkg = ArPackage::get_or_create(&model, "/test").unwrap(); + let system = System::new("System", &pkg, SystemCategory::SystemDescription).unwrap(); + let settings = FlexrayClusterSettings::default(); + let cluster = system.create_flexray_cluster("FlxCluster", &pkg, &settings).unwrap(); + + let channel = cluster + .create_physical_channel("channel_name", FlexrayChannelName::A) + .unwrap(); + let c2 = channel.cluster().unwrap(); + assert_eq!(cluster, c2); + + // damage the channel info by removing the channel name + let elem_channelname = channel.element().get_sub_element(ElementName::ChannelName).unwrap(); + elem_channelname.remove_character_data().unwrap(); + assert!(channel.channel_name().is_none()); + + // now there is no longer a channel A + let channel2 = cluster.create_physical_channel("channel_name2", FlexrayChannelName::A); + assert!(channel2.is_ok()) + } +} diff --git a/autosar-data-abstraction/src/communication/physical_channel/mod.rs b/autosar-data-abstraction/src/communication/physical_channel/mod.rs new file mode 100644 index 0000000..f6bb8c6 --- /dev/null +++ b/autosar-data-abstraction/src/communication/physical_channel/mod.rs @@ -0,0 +1,46 @@ +mod can; +mod ethernet; +mod flexray; + +pub use can::*; +pub use ethernet::*; +pub use flexray::*; + +use crate::AbstractionElement; + +//################################################################## + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum PhysicalChannel { + Can(CanPhysicalChannel), + Ethernet(EthernetPhysicalChannel), + FlexRay(FlexrayPhysicalChannel), +} + +impl AbstractionElement for PhysicalChannel { + fn element(&self) -> &autosar_data::Element { + match self { + PhysicalChannel::Can(cpc) => cpc.element(), + PhysicalChannel::Ethernet(epc) => epc.element(), + PhysicalChannel::FlexRay(fpc) => fpc.element(), + } + } +} + +impl From for PhysicalChannel { + fn from(value: CanPhysicalChannel) -> Self { + PhysicalChannel::Can(value) + } +} + +impl From for PhysicalChannel { + fn from(value: EthernetPhysicalChannel) -> Self { + PhysicalChannel::Ethernet(value) + } +} + +impl From for PhysicalChannel { + fn from(value: FlexrayPhysicalChannel) -> Self { + PhysicalChannel::FlexRay(value) + } +} diff --git a/autosar-data-abstraction/src/communication/signal/mod.rs b/autosar-data-abstraction/src/communication/signal/mod.rs new file mode 100644 index 0000000..947a772 --- /dev/null +++ b/autosar-data-abstraction/src/communication/signal/mod.rs @@ -0,0 +1,103 @@ +use crate::{abstraction_element, referenced_element_iterator, AbstractionElement, ArPackage, AutosarAbstractionError}; +use autosar_data::{Element, ElementName, EnumItem}; + +/// The [`Signal`] represents the combination of an `I-SIGNAL` and its paired `SYSTEM-SIGNAL` +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Signal(Element); +abstraction_element!(Signal, ISignal); + +impl Signal { + pub(crate) fn new( + name: &str, + bit_length: u64, + sig_package: &ArPackage, + sys_package: &ArPackage, + ) -> Result { + if sig_package == sys_package { + return Err(AutosarAbstractionError::InvalidParameter( + "you must use different packages for the ISIgnal and the SystemSignal".to_string(), + )); + } + let sig_pkg_elements = sig_package.element().get_or_create_sub_element(ElementName::Elements)?; + let elem_isignal = sig_pkg_elements.create_named_sub_element(ElementName::ISignal, name)?; + + let sys_pkg_elements = sys_package.element().get_or_create_sub_element(ElementName::Elements)?; + let elem_syssignal = sys_pkg_elements.create_named_sub_element(ElementName::SystemSignal, name)?; + + elem_isignal + .create_sub_element(ElementName::Length)? + .set_character_data(bit_length.to_string())?; + elem_isignal + .create_sub_element(ElementName::SystemSignalRef)? + .set_reference_target(&elem_syssignal)?; + elem_isignal + .create_sub_element(ElementName::DataTypePolicy)? + .set_character_data(EnumItem::Override)?; + + Ok(Self(elem_isignal)) + } + + pub fn set_datatype(&self, _datatype: ()) -> Result<(), AutosarAbstractionError> { + todo!() + } + + pub fn set_transformation(&self) -> Result<(), AutosarAbstractionError> { + todo!() + } +} + +//################################################################## + +/// The [`SignalGroup`] represents the combination of an `I-SIGNAL-GROUP` and its paired `SYSTEM-SIGNAL-GROUP` +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct SignalGroup(Element); +abstraction_element!(SignalGroup, ISignalGroup); + +impl SignalGroup { + pub(crate) fn new( + name: &str, + sig_package: &ArPackage, + sys_package: &ArPackage, + ) -> Result { + if sig_package == sys_package { + return Err(AutosarAbstractionError::InvalidParameter( + "you must use different packages for the ISIgnal and the SystemSignal".to_string(), + )); + } + + let sig_pkg_elements = sig_package.element().get_or_create_sub_element(ElementName::Elements)?; + let elem_isiggrp = sig_pkg_elements.create_named_sub_element(ElementName::ISignalGroup, name)?; + let sys_pkg_elements = sys_package.element().get_or_create_sub_element(ElementName::Elements)?; + let elem_syssiggrp = sys_pkg_elements.create_named_sub_element(ElementName::SystemSignalGroup, name)?; + + elem_isiggrp + .create_sub_element(ElementName::SystemSignalGroupRef)? + .set_reference_target(&elem_syssiggrp)?; + + Ok(Self(elem_isiggrp)) + } + + /// Add a signal to the signal group + pub fn add_signal(&self, _signal: &Signal) -> Result<(), AutosarAbstractionError> { + todo!() + } + + /// set the signals of the signal group to the given list + pub fn set_signals(&self, signals: &mut impl Iterator) { + for _sig in signals { + + } + todo!() + } + + /// Iterator over all [`Signal`]s in this group + /// + /// # Example + pub fn signals(&self) -> SignalsIterator { + SignalsIterator::new(self.element().get_sub_element(ElementName::Signals)) + } +} + +//################################################################## + +referenced_element_iterator!(SignalsIterator, Signal); diff --git a/autosar-data-abstraction/src/communication/socketaddress.rs b/autosar-data-abstraction/src/communication/socketaddress.rs new file mode 100644 index 0000000..f76c651 --- /dev/null +++ b/autosar-data-abstraction/src/communication/socketaddress.rs @@ -0,0 +1,236 @@ +use crate::communication::{EthernetPhysicalChannel, NetworkEndpoint}; +use crate::{abstraction_element, AbstractionElement, AutosarAbstractionError, EcuInstance}; +use autosar_data::{Element, ElementName}; + +//################################################################## + +/// A socket address estapblishes the link between one or more ECUs and a NetworkEndpoint. +/// It contains all settings that are relevant for this combination. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct SocketAddress(Element); +abstraction_element!(SocketAddress, SocketAddress); + +impl SocketAddress { + pub(crate) fn new( + name: &str, + channel: &EthernetPhysicalChannel, + network_endpoint: &NetworkEndpoint, + tp_config: &TpConfig, + sa_type: SocketAddressType, + ) -> Result { + let channel_elem = channel.element(); + let (unicast, ecu_instances) = match sa_type { + SocketAddressType::Unicast(Some(ecu_instance)) => (true, vec![ecu_instance]), + SocketAddressType::Unicast(None) => (true, vec![]), + SocketAddressType::Multicast(ecu_instances) => (false, ecu_instances), + }; + + // get the connector for each ECU in advance, so that nothing needs to be cleaned up if there is a problem here + let connectors = ecu_instances + .iter() + .filter_map(|ecu_instance| channel.get_ecu_connector(ecu_instance)) + .collect::>(); + if connectors.len() != ecu_instances.len() { + return Err(AutosarAbstractionError::InvalidParameter( + "All EcuInstances must be connected to the EthernetPhysicalChannel".to_string(), + )); + } + + let elem = channel_elem + .get_or_create_sub_element(ElementName::SoAdConfig)? + .get_or_create_sub_element(ElementName::SocketAddresss)? + .create_named_sub_element(ElementName::SocketAddress, name)?; + + if unicast { + elem.create_sub_element(ElementName::ConnectorRef) + .unwrap() + .set_reference_target(&connectors[0]) + .unwrap(); + } else { + let mc_connectors = elem.create_sub_element(ElementName::MulticastConnectorRefs)?; + for conn in &connectors { + mc_connectors + .create_sub_element(ElementName::MulticastConnectorRef)? + .set_reference_target(conn)?; + } + } + + let ae_name = format!("{name}_AE"); + let ae = elem.create_named_sub_element(ElementName::ApplicationEndpoint, &ae_name)?; + ae.create_sub_element(ElementName::NetworkEndpointRef)? + .set_reference_target(&network_endpoint.element())?; + let tp_configuration = ae.create_sub_element(ElementName::TpConfiguration)?; + match tp_config { + TpConfig::TcpTp { + port_number, + port_dynamically_assigned, + } => { + let tcptp = tp_configuration.create_sub_element(ElementName::TcpTp)?; + let tcptp_port = tcptp.create_sub_element(ElementName::TcpTpPort)?; + if let Some(portnum) = port_number { + tcptp_port + .create_sub_element(ElementName::PortNumber)? + .set_character_data(portnum.to_string())?; + } + if let Some(dyn_assign) = port_dynamically_assigned { + let boolstr = if *dyn_assign { "true" } else { "false" }; + tcptp_port + .create_sub_element(ElementName::DynamicallyAssigned)? + .set_character_data(boolstr)?; + } + } + TpConfig::UdpTp { + port_number, + port_dynamically_assigned, + } => { + let udptp_port = tp_configuration + .create_sub_element(ElementName::UdpTp)? + .create_sub_element(ElementName::UdpTpPort)?; + if let Some(portnum) = port_number { + udptp_port + .create_sub_element(ElementName::PortNumber)? + .set_character_data(portnum.to_string())?; + } + if let Some(dyn_assign) = port_dynamically_assigned { + let boolstr = if *dyn_assign { "true" } else { "false" }; + udptp_port + .create_sub_element(ElementName::DynamicallyAssigned)? + .set_character_data(boolstr)?; + } + } + } + + Ok(Self(elem)) + } + + /// get the socket address type: unicast / multicast, as well as the connected ecus + pub fn get_type(&self) -> Option { + if let Some(connector_ref) = self.0.get_sub_element(ElementName::ConnectorRef) { + let ecu = EcuInstance::try_from(connector_ref.get_reference_target().ok()?.named_parent().ok()??).ok()?; + Some(SocketAddressType::Unicast(Some(ecu))) + } else if let Some(mcr) = self.0.get_sub_element(ElementName::MulticastConnectorRefs) { + let ecus = mcr + .sub_elements() + .filter_map(|cr| { + cr.get_reference_target() + .ok() + .and_then(|conn| conn.named_parent().ok().flatten()) + }) + .filter_map(|ecu_elem| EcuInstance::try_from(ecu_elem).ok()) + .collect::>(); + Some(SocketAddressType::Multicast(ecus)) + } else { + None + } + } + + /// get the transport protocol settings for this `SocketAddress` + pub fn get_tp_config(&self) -> Option { + let tp = self + .0 + .get_sub_element(ElementName::ApplicationEndpoint)? + .get_sub_element(ElementName::TpConfiguration)?; + + if let Some(tcp_tp) = tp.get_sub_element(ElementName::TcpTp) { + let port = tcp_tp.get_sub_element(ElementName::TcpTpPort)?; + let (port_number, port_dynamically_assigned) = Self::get_port_config(&port); + Some(TpConfig::TcpTp { + port_number, + port_dynamically_assigned, + }) + } else if let Some(udp_tp) = tp.get_sub_element(ElementName::UdpTp) { + let port = udp_tp.get_sub_element(ElementName::UdpTpPort)?; + let (port_number, port_dynamically_assigned) = Self::get_port_config(&port); + Some(TpConfig::UdpTp { + port_number, + port_dynamically_assigned, + }) + } else { + None + } + } + + fn get_port_config(port_element: &Element) -> (Option, Option) { + let port_number = port_element + .get_sub_element(ElementName::PortNumber) + .and_then(|pn| pn.character_data()) + .and_then(|cdata| cdata.decode_integer()); + let port_dynamically_assigned = port_element + .get_sub_element(ElementName::DynamicallyAssigned) + .and_then(|da| da.character_data()) + .and_then(|cdata| cdata.string_value()) + .map(|val| val == "true" || val == "1"); + (port_number, port_dynamically_assigned) + } +} + +//################################################################## + +/// transport protocol settings of a [`SocketAddress`] +#[derive(Debug, Clone, PartialEq)] +pub enum TpConfig { + /// The socket uses TCP + TcpTp { + port_number: Option, + port_dynamically_assigned: Option, + // additional TCP options: currently not supported + }, + // The socket uses UDP + UdpTp { + port_number: Option, + port_dynamically_assigned: Option, + }, + // RtpTp, Ieee1722Tp, HttpTp: currently not supported +} + +//################################################################## + +/// Describes if a [`SocketAddress`] is used for unicast or multicast +#[derive(Debug, Clone, PartialEq)] +pub enum SocketAddressType { + Unicast(Option), + Multicast(Vec), +} + +//################################################################## + +#[cfg(test)] +mod test { + use super::*; + use crate::communication::{IPv4AddressSource, NetworkEndpointAddress}; + use crate::{ArPackage, System, SystemCategory}; + use autosar_data::{AutosarModel, AutosarVersion}; + + #[test] + fn socket_address() { + let model = AutosarModel::new(); + model.create_file("filename", AutosarVersion::Autosar_4_3_0).unwrap(); + let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); + let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); + let ecu_instance = system.create_ecu_instance("Ecu", &package).unwrap(); + let controller = ecu_instance + .create_ethernet_communication_controller("EthCtrl", None) + .unwrap(); + let cluster = system.create_ethernet_cluster("Cluster", &package).unwrap(); + let channel = cluster.create_physical_channel("Channel", None).unwrap(); + controller.connect_physical_channel("connection", &channel).unwrap(); + let endpoint_address = NetworkEndpointAddress::IPv4 { + address: Some("192.168.0.1".to_string()), + address_source: Some(IPv4AddressSource::Fixed), + default_gateway: Some("192.168.0.2".to_string()), + network_mask: Some("255.255.255.0".to_string()), + }; + let network_endpoint = channel + .create_network_endpoint("Address", endpoint_address, Some(&ecu_instance)) + .unwrap(); + let tcp_port = TpConfig::TcpTp { + port_number: Some(1234), + port_dynamically_assigned: None, + }; + let socket_type = SocketAddressType::Unicast(Some(ecu_instance)); + channel + .create_socket_address("SocketName", &network_endpoint, &tcp_port, socket_type) + .unwrap(); + assert_eq!(channel.socket_addresses().count(), 1); + } +} diff --git a/autosar-data-abstraction/src/ecuinstance.rs b/autosar-data-abstraction/src/ecuinstance.rs index b0c90b3..be82cd4 100644 --- a/autosar-data-abstraction/src/ecuinstance.rs +++ b/autosar-data-abstraction/src/ecuinstance.rs @@ -1,7 +1,8 @@ -use crate::{ - abstraction_element, can::CanCommunicationController, ethernet::EthernetCommunicationController, - flexray::FlexrayCommunicationController, AbstractionElement, ArPackage, AutosarAbstractionError, +use crate::communication::{ + CanCommunicationController, CommunicationController, EthernetCommunicationController, + FlexrayCommunicationController, }; +use crate::{abstraction_element, AbstractionElement, ArPackage, AutosarAbstractionError}; use autosar_data::{Element, ElementName, ElementsIterator}; /// The `EcuInstance` represents one ECU in a `System` @@ -185,15 +186,6 @@ impl Iterator for EcuInstanceControllersIterator { //################################################################## -/// wraps all different kinds of communication controller -pub enum CommunicationController { - Can(CanCommunicationController), - Ethernet(EthernetCommunicationController), - Flexray(FlexrayCommunicationController), -} - -//################################################################## - #[cfg(test)] mod test { use crate::*; diff --git a/autosar-data-abstraction/src/ethernet.rs b/autosar-data-abstraction/src/ethernet.rs deleted file mode 100644 index 47f39b1..0000000 --- a/autosar-data-abstraction/src/ethernet.rs +++ /dev/null @@ -1,1426 +0,0 @@ -use crate::{abstraction_element, AbstractionElement, ArPackage, AutosarAbstractionError, EcuInstance}; -use autosar_data::{ - AutosarDataError, AutosarModel, AutosarVersion, CharacterData, Element, ElementName, ElementsIterator, EnumItem, - WeakElement, -}; - -/// Provides information about the VLAN of an [`EthernetPhysicalChannel`] -#[derive(Debug, Clone, PartialEq)] -pub struct EthernetVlanInfo { - pub vlan_name: String, - pub vlan_id: u16, -} - -/// An `EthernetCluster` contains all configuration items associated with an ethernet network. -/// The cluster connects multiple ECUs. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct EthernetCluster(Element); -abstraction_element!(EthernetCluster, EthernetCluster); - -impl EthernetCluster { - // create a new EthernetCluster - for internal use. User code should call System::create_ethernet_cluster - pub(crate) fn new(cluster_name: &str, package: &ArPackage) -> Result { - let elem_pkg_elements = package.element().get_or_create_sub_element(ElementName::Elements)?; - let elem_cluster = elem_pkg_elements.create_named_sub_element(ElementName::EthernetCluster, cluster_name)?; - if let Ok(cluster_content) = elem_cluster - .create_sub_element(ElementName::EthernetClusterVariants) - .and_then(|ecv| ecv.create_sub_element(ElementName::EthernetClusterConditional)) - { - let _ = cluster_content.create_sub_element(ElementName::PhysicalChannels); - } - - Ok(EthernetCluster(elem_cluster)) - } - - /// Create a new physical channel for the cluster - /// - /// The supplied VLAN info must be unique - there cannot be two VLANs with the same vlan identifier. - /// One channel may be created without VLAN information; it carries untagged traffic. - /// - /// # Example - /// - /// ``` - /// # use autosar_data::*; - /// # use autosar_data_abstraction::*; - /// # let model = AutosarModel::new(); - /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); - /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); - /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); - /// let cluster = system.create_ethernet_cluster("Cluster", &package).unwrap(); - /// let vlan_info = EthernetVlanInfo { - /// vlan_name: "VLAN_1".to_string(), - /// vlan_id: 1, - /// }; - /// let channel = cluster.create_physical_channel("Channel", Some(vlan_info)).unwrap(); - /// ``` - /// - /// # Errors - /// - /// - [`AutosarAbstractionError::ItemAlreadyExists`] There is already a physical channel for this VLAN - /// - [`AutosarAbstractionError::ModelError`] An error occurred in the Autosar model while trying to create the ECU-INSTANCE - pub fn create_physical_channel( - &self, - channel_name: &str, - vlan_info: Option, - ) -> Result { - let phys_channels = self - .0 - .get_or_create_sub_element(ElementName::EthernetClusterVariants)? - .get_or_create_sub_element(ElementName::EthernetClusterConditional)? - .get_or_create_sub_element(ElementName::PhysicalChannels)?; - - // make sure there is no other channel with the same VLAN info - // If vlan_info is None, then there must be no existing channel without VLAN info - for existing_channel in phys_channels.sub_elements() { - let existing_vlan_info = EthernetPhysicalChannel(existing_channel).get_vlan_info(); - if let (Some(v1), Some(v2)) = (&vlan_info, &existing_vlan_info) { - if v1.vlan_id == v2.vlan_id { - // the vlan identifier of an existing channel matches the new vlan identifier - return Err(AutosarAbstractionError::ItemAlreadyExists); - } - } else if existing_vlan_info.is_none() && vlan_info.is_none() { - // the new channel is for untagged traffic (no VLAN), but there is already a channel for untagged traffic - return Err(AutosarAbstractionError::ItemAlreadyExists); - } - } - - let channel = phys_channels.create_named_sub_element(ElementName::EthernetPhysicalChannel, channel_name)?; - // set the vlan info - if let Some(vlan_info) = vlan_info { - let _ = channel - .create_named_sub_element(ElementName::Vlan, &vlan_info.vlan_name) - .and_then(|vlan| vlan.create_sub_element(ElementName::VlanIdentifier)) - .and_then(|vlan_id| vlan_id.set_character_data(CharacterData::String(vlan_info.vlan_id.to_string()))); - } - // always set CATEGORY = WIRED, since this is the common case - let _ = channel - .create_sub_element(ElementName::Category) - .and_then(|cat| cat.set_character_data(CharacterData::String("WIRED".to_string()))); - - Ok(EthernetPhysicalChannel(channel)) - } - - /// returns an iterator over all [`EthernetPhysicalChannel`]s in the cluster - /// - /// # Example - /// - /// ``` - /// # use autosar_data::*; - /// # use autosar_data_abstraction::*; - /// # let model = AutosarModel::new(); - /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); - /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); - /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); - /// # let cluster = system.create_ethernet_cluster("Cluster", &package).unwrap(); - /// cluster.create_physical_channel("Channel", None).unwrap(); - /// for channel in cluster.physical_channels() { - /// // ... - /// } - /// ``` - #[must_use] - pub fn physical_channels(&self) -> EthernetClusterChannelsIterator { - EthernetClusterChannelsIterator::new(self) - } -} - -//################################################################## - -/// The `EthernetPhysicalChannel` represents a VLAN or untagged traffic -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct EthernetPhysicalChannel(Element); -abstraction_element!(EthernetPhysicalChannel, EthernetPhysicalChannel); - -impl EthernetPhysicalChannel { - /// Retrieves the VLAN information from a channel - /// - /// An ethernet physical channel that represents untagged traffic has no VLAN information and returns None. - /// - /// # Example - /// - /// ``` - /// # use autosar_data::*; - /// # use autosar_data_abstraction::*; - /// # let model = AutosarModel::new(); - /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); - /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); - /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); - /// # let cluster = system.create_ethernet_cluster("Cluster", &package).unwrap(); - /// let vlan_info = EthernetVlanInfo { - /// vlan_name: "VLAN_1".to_string(), - /// vlan_id: 1, - /// }; - /// let channel = cluster.create_physical_channel("Channel", Some(vlan_info)).unwrap(); - /// let info = channel.get_vlan_info().unwrap(); - /// assert_eq!(info.vlan_id, 1); - /// ``` - #[must_use] - pub fn get_vlan_info(&self) -> Option { - let vlan = self.0.get_sub_element(ElementName::Vlan)?; - let vlan_id = vlan - .get_sub_element(ElementName::VlanIdentifier)? - .character_data()? - .decode_integer::()?; - Some(EthernetVlanInfo { - vlan_name: vlan.item_name()?, - vlan_id, - }) - } - - /// get the cluster containing this physical channel - /// - /// # Example - /// - /// ``` - /// # use autosar_data::*; - /// # use autosar_data_abstraction::*; - /// # let model = AutosarModel::new(); - /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); - /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); - /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); - /// # let cluster = system.create_ethernet_cluster("Cluster", &package).unwrap(); - /// let channel = cluster.create_physical_channel("Channel", None).unwrap(); - /// let cluster_2 = channel.cluster().unwrap(); - /// assert_eq!(cluster, cluster_2); - /// ``` - /// - /// # Errors - /// - /// - [`AutosarAbstractionError::ModelError`] An error occurred in the Autosar model - pub fn cluster(&self) -> Result { - let cluster_elem = self.0.named_parent()?.unwrap(); - EthernetCluster::try_from(cluster_elem) - } - - /// create a network endpoint - IPv4 or IPv6 address - for this channel - /// - /// In older versions of the Autosar standard, up to version 4.4.0, the `NetworkEndpoint` could be linked to an Ecu. - /// The parameter `ecu` specifies the target. - /// The link is obsoleted in newer versions, and will only be created if the file version allows it. - /// - /// # Example - /// - /// ``` - /// # use autosar_data::*; - /// # use autosar_data_abstraction::*; - /// # let model = AutosarModel::new(); - /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); - /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); - /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); - /// # let cluster = system.create_ethernet_cluster("Cluster", &package).unwrap(); - /// # let channel = cluster.create_physical_channel("Channel", None).unwrap(); - /// let endpoint_address = NetworkEndpointAddress::IPv4 { - /// address: Some("192.168.0.1".to_string()), - /// address_source: Some(IPv4AddressSource::Fixed), - /// default_gateway: Some("192.168.0.2".to_string()), - /// network_mask: Some("255.255.255.0".to_string()), - /// }; - /// let network_endpoint = channel.create_network_endpoint("Address1", endpoint_address, None).unwrap(); - /// ``` - /// - /// # Errors - /// - /// - [`AutosarAbstractionError::ModelError`] An error occurred in the Autosar model - pub fn create_network_endpoint( - &self, - name: &str, - address: NetworkEndpointAddress, - ecu: Option<&EcuInstance>, - ) -> Result { - let network_endpoint = NetworkEndpoint::new(name, self, address)?; - - if let Some(ecu_instance) = ecu { - let version = self.0.min_version()?; - if version <= AutosarVersion::Autosar_00046 { - let ne_element = network_endpoint.element().clone(); - - // get a connector referenced by this physical channel which is contained in the ecu_instance - if let Some(connector) = self.get_ecu_connector(ecu_instance) { - let _ = connector - .get_or_create_sub_element(ElementName::NetworkEndpointRefs) - .and_then(|ner| ner.create_sub_element(ElementName::NetworkEndpointRef)) - .and_then(|ner| ner.set_reference_target(&ne_element)); - } else { - // no connector between the ECU and this channel -> abort - self.0 - .get_sub_element(ElementName::NetworkEndpoints) - .and_then(|endpoints| endpoints.remove_sub_element(ne_element).ok()); - } - } - } - - Ok(network_endpoint) - } - - /// create an iterator over all [`NetworkEndpoint`]s in this channel - /// - /// # Example - /// - /// ``` - /// # use autosar_data::*; - /// # use autosar_data_abstraction::*; - /// # let model = AutosarModel::new(); - /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); - /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); - /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); - /// # let cluster = system.create_ethernet_cluster("Cluster", &package).unwrap(); - /// # let channel = cluster.create_physical_channel("Channel", None).unwrap(); - /// # let endpoint_address = NetworkEndpointAddress::IPv4 { - /// # address: Some("192.168.0.1".to_string()), - /// # address_source: Some(IPv4AddressSource::Fixed), - /// # default_gateway: Some("192.168.0.2".to_string()), - /// # network_mask: Some("255.255.255.0".to_string()), - /// # }; - /// # channel.create_network_endpoint("Address1", endpoint_address, None).unwrap(); - /// for network_endpoint in channel.network_endpoints() { - /// // ... - /// } - /// # assert_eq!(channel.network_endpoints().count(), 1) - /// ``` - #[must_use] - pub fn network_endpoints(&self) -> NetworkEndpointIterator { - NetworkEndpointIterator::new(self) - } - - /// create a socket address in the ethernet channel - /// - /// It contains the settings of the TCP/UDP port and links to a [`NetworkEndpoint`] which contains the IP address. - /// The socket address can either be a unicast adress which is associated with a single ECU, or a multicast address - /// - /// # Example - /// - /// ``` - /// # use autosar_data::*; - /// # use autosar_data_abstraction::*; - /// # let model = AutosarModel::new(); - /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); - /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); - /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); - /// # let ecu_instance = system.create_ecu_instance("Ecu", &package).unwrap(); - /// # let controller = ecu_instance.create_ethernet_communication_controller("EthCtrl", None).unwrap(); - /// # let cluster = system.create_ethernet_cluster("Cluster", &package).unwrap(); - /// # let channel = cluster.create_physical_channel("Channel", None).unwrap(); - /// # controller.connect_physical_channel("connection", &channel).unwrap(); - /// # let endpoint_address = NetworkEndpointAddress::IPv4 { - /// # address: Some("192.168.0.1".to_string()), - /// # address_source: Some(IPv4AddressSource::Fixed), - /// # default_gateway: Some("192.168.0.2".to_string()), - /// # network_mask: Some("255.255.255.0".to_string()), - /// # }; - /// # let network_endpoint = channel.create_network_endpoint("Address", endpoint_address, None).unwrap(); - /// let tcp_port = TpConfig::TcpTp { - /// port_number: Some(1234), - /// port_dynamically_assigned: None, - /// }; - /// let socket_type = SocketAddressType::Unicast(Some(ecu_instance)); - /// channel.create_socket_address("SocketName", &network_endpoint, &tcp_port, socket_type).unwrap(); - /// ``` - /// - /// # Errors - /// - /// - [`AutosarAbstractionError::InvalidParameter`] `sa_type` contains a reference to an EcuInstance which is not connected to this channel - /// - [`AutosarAbstractionError::ModelError`] An error occurred in the Autosar model - pub fn create_socket_address( - &self, - name: &str, - network_endpoint: &NetworkEndpoint, - tp_config: &TpConfig, - sa_type: SocketAddressType, - ) -> Result { - SocketAddress::new(name, self, network_endpoint, tp_config, sa_type) - } - - /// create an iterator over all [`SocketAddress`]es in this channel - /// - /// # Example - /// - /// ``` - /// # use autosar_data::*; - /// # use autosar_data_abstraction::*; - /// # let model = AutosarModel::new(); - /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); - /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); - /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); - /// # let ecu_instance = system.create_ecu_instance("Ecu", &package).unwrap(); - /// # let controller = ecu_instance.create_ethernet_communication_controller("EthCtrl", None).unwrap(); - /// # let cluster = system.create_ethernet_cluster("Cluster", &package).unwrap(); - /// # let channel = cluster.create_physical_channel("Channel", None).unwrap(); - /// # controller.connect_physical_channel("connection", &channel).unwrap(); - /// # let endpoint_address = NetworkEndpointAddress::IPv4 { - /// # address: Some("192.168.0.1".to_string()), - /// # address_source: Some(IPv4AddressSource::Fixed), - /// # default_gateway: Some("192.168.0.2".to_string()), - /// # network_mask: Some("255.255.255.0".to_string()), - /// # }; - /// # let network_endpoint = channel.create_network_endpoint("Address", endpoint_address, None).unwrap(); - /// let tcp_port = TpConfig::TcpTp { - /// port_number: Some(1234), - /// port_dynamically_assigned: None, - /// }; - /// let socket_type = SocketAddressType::Unicast(Some(&ecu_instance)); - /// channel.create_socket_address("SocketName", &network_endpoint, &tcp_port, socket_type).unwrap(); - /// ``` - pub fn socket_addresses(&self) -> SocketAddressIterator { - SocketAddressIterator::new(self) - } - - // get the connector element between this channel and an ecu - pub(crate) fn get_ecu_connector(&self, ecu_instance: &EcuInstance) -> Option { - // get a connector referenced by this physical channel which is contained in the ecu_instance - self.0 - .get_sub_element(ElementName::CommConnectors) - .and_then(|cc| { - cc.sub_elements().find(|ccrc| { - ccrc.get_sub_element(ElementName::CommunicationConnectorRef) - .and_then(|ccr| { - ccr.get_reference_target() - .and_then(|r| r.named_parent()) - .ok() - .flatten() - .map(|p| &p == ecu_instance.element()) - }) - .unwrap_or(false) - }) - }) - .and_then(|ccrc| ccrc.get_sub_element(ElementName::CommunicationConnectorRef)) - .and_then(|ccr| ccr.get_reference_target().ok()) - } -} - -//################################################################## - -/// An `EcuInstance` needs an `EthernetCommunicationController` in order to connect to an ethernet cluster. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct EthernetCommunicationController(Element); -abstraction_element!(EthernetCommunicationController, EthernetCommunicationController); - -impl EthernetCommunicationController { - // create an EthernetCommunicationController - pub(crate) fn new( - name: &str, - ecu: &EcuInstance, - mac_address: Option, - ) -> Result { - let commcontrollers = ecu.element().get_or_create_sub_element(ElementName::CommControllers)?; - let ctrl = commcontrollers.create_named_sub_element(ElementName::EthernetCommunicationController, name)?; - let ethccc = ctrl - .create_sub_element(ElementName::EthernetCommunicationControllerVariants)? - .create_sub_element(ElementName::EthernetCommunicationControllerConditional)?; - if let Some(mac_address) = mac_address { - // creating the mac address element fails if the supplied string has an invalid format - let result = ethccc - .create_sub_element(ElementName::MacUnicastAddress) - .and_then(|mua| mua.set_character_data(CharacterData::String(mac_address))); - if let Err(mac_address_error) = result { - let _ = commcontrollers.remove_sub_element(ctrl); - return Err(mac_address_error.into()); - } - } - let coupling_port_name = format!("{name}_CouplingPort"); - let _ = ethccc - .create_sub_element(ElementName::CouplingPorts) - .and_then(|cps| cps.create_named_sub_element(ElementName::CouplingPort, &coupling_port_name)); - - Ok(Self(ctrl)) - } - - /// return an iterator over the [`EthernetPhysicalChannel`]s connected to this controller - /// - /// # Example - /// - /// ``` - /// # use autosar_data::*; - /// # use autosar_data_abstraction::*; - /// # let model = AutosarModel::new(); - /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); - /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); - /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); - /// # let ecu_instance = system.create_ecu_instance("ecu_name", &package).unwrap(); - /// let ethernet_controller = ecu_instance.create_ethernet_communication_controller("EthCtrl", None).unwrap(); - /// # let cluster = system.create_ethernet_cluster("Cluster", &package).unwrap(); - /// # let physical_channel = cluster.create_physical_channel("Channel", None).unwrap(); - /// ethernet_controller.connect_physical_channel("connection", &physical_channel).unwrap(); - /// for channel in ethernet_controller.connected_channels() { - /// // ... - /// } - /// ``` - /// - /// # Errors - /// - /// - [`AutosarAbstractionError::ModelError`] An error occurred in the Autosar model while trying to create the ECU-INSTANCE - #[must_use] - pub fn connected_channels(&self) -> EthernetCtrlChannelsIterator { - if let Ok(ecu) = self.ecu_instance().map(|ecuinstance| ecuinstance.element().clone()) { - EthernetCtrlChannelsIterator::new(self, &ecu) - } else { - EthernetCtrlChannelsIterator { - connector_iter: None, - comm_controller: self.0.clone(), - model: Err(AutosarDataError::ElementNotFound), - } - } - } - - /// get the `EcuInstance` that contains this `EthernetCommunicationController` - /// - /// # Example - /// - /// ``` - /// # use autosar_data::*; - /// # use autosar_data_abstraction::*; - /// # let model = AutosarModel::new(); - /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); - /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); - /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); - /// # let ecu_instance = system.create_ecu_instance("ecu_name", &package).unwrap(); - /// let ethernet_controller = ecu_instance.create_ethernet_communication_controller("EthCtrl", None).unwrap(); - /// assert_eq!(ecu_instance, ethernet_controller.ecu_instance().unwrap()); - /// ``` - /// - /// # Errors - /// - /// - [`AutosarAbstractionError::ModelError`] An error occurred in the Autosar model while trying to create the ECU-INSTANCE - pub fn ecu_instance(&self) -> Result { - // unwrapping is safe here - self.0.named_parent() cannot return Ok(None). - // the EthernetCommunicationController is always a child of an EcuInstance, - // or else it is deleted and named_parent() return Err(...), which is handled by the ? - let ecu: Element = self.0.named_parent()?.unwrap(); - EcuInstance::try_from(ecu) - } - - /// Connect this [`EthernetCommunicationController`] inside an [`EcuInstance`] to an [`EthernetPhysicalChannel`] in the [`crate::System`] - /// - /// Creates an EthernetCommunicationConnector in the [`EcuInstance`] that contains this [`EthernetCommunicationController`]. - /// - /// This function establishes the relationships: - /// - [`EthernetPhysicalChannel`] -> EthernetCommunicationConnector - /// - EthernetCommunicationConnector -> [`EthernetCommunicationController`] - /// - /// # Example - /// - /// ``` - /// # use autosar_data::*; - /// # use autosar_data_abstraction::*; - /// # let model = AutosarModel::new(); - /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); - /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); - /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); - /// # let ecu_instance = system.create_ecu_instance("ecu_name", &package).unwrap(); - /// let ethernet_controller = ecu_instance.create_ethernet_communication_controller("EthCtrl", None).unwrap(); - /// # let cluster = system.create_ethernet_cluster("Cluster", &package).unwrap(); - /// # let physical_channel = cluster.create_physical_channel("Channel", None).unwrap(); - /// ethernet_controller.connect_physical_channel("connection", &physical_channel).unwrap(); - /// ``` - /// - /// # Errors - /// - /// - [`AutosarAbstractionError::ModelError`] An error occurred in the Autosar model while trying to create the ECU-INSTANCE - pub fn connect_physical_channel( - &self, - connection_name: &str, - eth_channel: &EthernetPhysicalChannel, - ) -> Result<(), AutosarAbstractionError> { - let ecu: Element = self.0.named_parent()?.unwrap(); - let cluster_of_channel = eth_channel.cluster()?; - - // There can be multiple connectors referring to a single EthernetCommunicationController, - // but all of these connectors must refer to different PhysicalChannels - // (= VLANs) of the same EthernetCluster. - for phys_channel in self.connected_channels() { - if phys_channel == *eth_channel { - return Err(AutosarAbstractionError::ItemAlreadyExists); - } - - if phys_channel.cluster()? != cluster_of_channel { - return Err(AutosarAbstractionError::InvalidParameter( - "The EthernetCommunicationController may only refer to different channels within the same cluster" - .to_string(), - )); - } - } - - // create a new connector - let connectors = ecu.get_or_create_sub_element(ElementName::Connectors)?; - let connector = - connectors.create_named_sub_element(ElementName::EthernetCommunicationConnector, connection_name)?; - connector - .create_sub_element(ElementName::CommControllerRef) - .and_then(|refelem| refelem.set_reference_target(&self.0))?; - - // if the ethernet physical channel has a category (WIRED / WIRELESS / CANXL) then - // set the category of the connector to the same value - if let Some(category) = eth_channel - .element() - .get_sub_element(ElementName::Category) - .and_then(|cat| cat.character_data()) - .and_then(|cdata| cdata.string_value()) - { - let _ = connector - .create_sub_element(ElementName::Category) - .and_then(|cat| cat.set_character_data(CharacterData::String(category))); - } - - // create a communication connector ref in the ethernet channel that refers to this connector - let channel_connctor_refs = eth_channel - .element() - .get_or_create_sub_element(ElementName::CommConnectors)?; - channel_connctor_refs - .create_sub_element(ElementName::CommunicationConnectorRefConditional) - .and_then(|ccrc| ccrc.create_sub_element(ElementName::CommunicationConnectorRef)) - .and_then(|ccr| ccr.set_reference_target(&connector))?; - - // if the PhysicalChannel has VLAN info AND if there is a coupling port in this CommunicationController - // then the coupling port should link to the PhysicalChannel / VLAN - if let Some(EthernetVlanInfo { .. }) = eth_channel.get_vlan_info() { - if let Some(coupling_port) = self - .0 - .get_sub_element(ElementName::EthernetCommunicationControllerVariants) - .and_then(|eccv| eccv.get_sub_element(ElementName::EthernetCommunicationControllerConditional)) - .and_then(|eccc| eccc.get_sub_element(ElementName::CouplingPorts)) - .and_then(|cps| cps.get_sub_element(ElementName::CouplingPort)) - { - coupling_port - .get_or_create_sub_element(ElementName::VlanMemberships) - .and_then(|vms| vms.create_sub_element(ElementName::VlanMembership)) - .and_then(|vm| vm.create_sub_element(ElementName::VlanRef)) - .and_then(|vr| vr.set_reference_target(ð_channel.element()))?; - } - } - - Ok(()) - } -} - -//################################################################## - -/// A network endpoint contains address information for a connection -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct NetworkEndpoint(Element); -abstraction_element!(NetworkEndpoint, NetworkEndpoint); - -impl NetworkEndpoint { - fn new( - name: &str, - channel: &EthernetPhysicalChannel, - address: NetworkEndpointAddress, - ) -> Result { - let el_network_endpoint = channel - .0 - .get_or_create_sub_element(ElementName::NetworkEndpoints)? - .create_named_sub_element(ElementName::NetworkEndpoint, name)?; - - let network_endpoint = Self(el_network_endpoint); - let result = network_endpoint.add_network_endpoint_address(address); - if let Err(error) = result { - let _ = channel.0.remove_sub_element(network_endpoint.0); - return Err(error); - } - - Ok(network_endpoint) - } - - /// add a network endpoint address to this `NetworkEndpoint` - /// - /// A `NetworkEndpoint` may have multiple sets of address information. The following restrictions apply: - /// - /// - all addresses must have the same type, i.e. all IPv4 or all IPv6 - /// - only one of them may be a `Fixed` address, all others must be dynamic (DHCP, automatic link local, etc.) - pub fn add_network_endpoint_address(&self, address: NetworkEndpointAddress) -> Result<(), AutosarAbstractionError> { - let mut fixedcount = 0; - if matches!(address, NetworkEndpointAddress::IPv4 { address_source, .. } if address_source == Some(IPv4AddressSource::Fixed)) - || matches!(address, NetworkEndpointAddress::IPv6 { address_source, .. } if address_source == Some(IPv6AddressSource::Fixed)) - { - fixedcount = 1; - } - for existing_address in self.addresses() { - if std::mem::discriminant(&existing_address) != std::mem::discriminant(&address) { - return Err(AutosarAbstractionError::InvalidParameter( - "you cannot mix IPv4 and IPv6 inside one NetworkEndpoint".to_string(), - )); - } - if matches!(existing_address, NetworkEndpointAddress::IPv4 { address_source, .. } if address_source == Some(IPv4AddressSource::Fixed)) - || matches!(existing_address, NetworkEndpointAddress::IPv6 { address_source, .. } if address_source == Some(IPv6AddressSource::Fixed)) - { - fixedcount += 1; - } - } - if fixedcount > 1 { - return Err(AutosarAbstractionError::InvalidParameter( - "Only one NetworkEndpointAddress can be a fixed address".to_string(), - )); - } - - let addresses = self - .0 - .get_or_create_sub_element(ElementName::NetworkEndpointAddresses)?; - match address { - NetworkEndpointAddress::IPv4 { - address, - address_source, - default_gateway, - network_mask, - } => { - let cfg = addresses.create_sub_element(ElementName::Ipv4Configuration)?; - if let Some(addr) = address { - cfg.create_sub_element(ElementName::Ipv4Address)? - .set_character_data(CharacterData::String(addr))?; - } - if let Some(addr_src) = address_source { - cfg.create_sub_element(ElementName::Ipv4AddressSource)? - .set_character_data(addr_src.to_cdata())?; - } - if let Some(defgw) = default_gateway { - cfg.create_sub_element(ElementName::DefaultGateway)? - .set_character_data(CharacterData::String(defgw))?; - } - if let Some(netmask) = network_mask { - cfg.create_sub_element(ElementName::NetworkMask)? - .set_character_data(CharacterData::String(netmask))?; - } - } - NetworkEndpointAddress::IPv6 { - address, - address_source, - default_router, - } => { - let cfg = addresses.create_sub_element(ElementName::Ipv6Configuration)?; - if let Some(addr) = address { - cfg.create_sub_element(ElementName::Ipv6Address)? - .set_character_data(CharacterData::String(addr))?; - } - if let Some(addr_src) = address_source { - cfg.create_sub_element(ElementName::Ipv6AddressSource)? - .set_character_data(addr_src.to_cdata())?; - } - if let Some(dr) = default_router { - cfg.create_sub_element(ElementName::DefaultRouter)? - .set_character_data(CharacterData::String(dr))?; - } - } - } - Ok(()) - } - - pub fn addresses(&self) -> NetworkEndpointAddressIterator { - NetworkEndpointAddressIterator::new(self) - } -} - -//################################################################## - -/// address information for a network endpoint -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum NetworkEndpointAddress { - /// IPv4 addressing information - IPv4 { - /// IPv4 address in the form "a.b.c.d". This is used if the address source is FIXED - address: Option, - /// defines how the address is obtained - address_source: Option, - /// ip address of the default gateway - default_gateway: Option, - /// Network mask in the form "a.b.c.d" - network_mask: Option, - }, - /// IPv6 addressing information - IPv6 { - /// IPv6 address, without abbreviation - address: Option, - /// defines how the address is obtained - address_source: Option, - /// IP address of the default router - default_router: Option, - }, -} - -impl TryFrom for NetworkEndpointAddress { - type Error = AutosarAbstractionError; - - fn try_from(element: Element) -> Result { - match element.element_name() { - ElementName::Ipv4Configuration => { - let address = element - .get_sub_element(ElementName::Ipv4Address) - .and_then(|i4a| i4a.character_data()) - .and_then(|cdata| cdata.string_value()); - let address_source = element - .get_sub_element(ElementName::Ipv4AddressSource) - .and_then(|i4as| i4as.character_data()) - .and_then(IPv4AddressSource::from_cdata); - let default_gateway = element - .get_sub_element(ElementName::DefaultGateway) - .and_then(|dg| dg.character_data()) - .and_then(|cdata| cdata.string_value()); - let network_mask = element - .get_sub_element(ElementName::NetworkMask) - .and_then(|nm| nm.character_data()) - .and_then(|cdata| cdata.string_value()); - - Ok(NetworkEndpointAddress::IPv4 { - address, - address_source, - default_gateway, - network_mask, - }) - } - ElementName::Ipv6Configuration => { - let address = element - .get_sub_element(ElementName::Ipv6Address) - .and_then(|i6a| i6a.character_data()) - .and_then(|cdata| cdata.string_value()); - let address_source = element - .get_sub_element(ElementName::Ipv6AddressSource) - .and_then(|i6as| i6as.character_data()) - .and_then(IPv6AddressSource::from_cdata); - let default_router = element - .get_sub_element(ElementName::DefaultRouter) - .and_then(|dr| dr.character_data()) - .and_then(|cdata| cdata.string_value()); - - Ok(NetworkEndpointAddress::IPv6 { - address, - address_source, - default_router, - }) - } - _ => Err(AutosarAbstractionError::ConversionError { - element, - dest: "NetwworkEndpointAddress".to_string(), - }), - } - } -} - -/// `IPv4AddressSource` defines how the address of an IPv4 `NetworkEndpoint` is obtained -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum IPv4AddressSource { - /// use AutoIp (aka APIPA) to assign a link-local address - AutoIp, - /// use AutoIp with DoIp settings to assign a link-local address - AutoIpDoIp, - /// dynamic assignment using DHCP - DHCPv4, - /// static IP address configuration - the address must be specified in NetworkEndpointAddress - Fixed, -} - -impl IPv4AddressSource { - fn from_cdata(cdata: CharacterData) -> Option { - match cdata { - CharacterData::Enum(EnumItem::AutoIp) => Some(Self::AutoIp), - CharacterData::Enum(EnumItem::AutoIpDoip) => Some(Self::AutoIpDoIp), - CharacterData::Enum(EnumItem::Dhcpv4) => Some(Self::DHCPv4), - CharacterData::Enum(EnumItem::Fixed) => Some(Self::Fixed), - _ => None, - } - } - - fn to_cdata(self) -> CharacterData { - match self { - Self::AutoIp => CharacterData::Enum(EnumItem::AutoIp), - Self::AutoIpDoIp => CharacterData::Enum(EnumItem::AutoIpDoip), - Self::DHCPv4 => CharacterData::Enum(EnumItem::Dhcpv4), - Self::Fixed => CharacterData::Enum(EnumItem::Fixed), - } - } -} - -/// `IPv6AddressSource` defines how the address of an IPv6 `NetworkEndpoint` is obtained -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum IPv6AddressSource { - /// dynamic assignment using DHCP - DHCPv6, - /// static IP address configuration - the address must be specified in NetworkEndpointAddress - Fixed, - /// automatic link local address assignment - LinkLocal, - /// automatic link local address assignment using doip parameters - LinkLocalDoIp, - /// IPv6 stateless autoconfiguration - RouterAdvertisement, -} - -impl IPv6AddressSource { - fn from_cdata(cdata: CharacterData) -> Option { - match cdata { - CharacterData::Enum(EnumItem::Dhcpv6) => Some(Self::DHCPv6), - CharacterData::Enum(EnumItem::Fixed) => Some(Self::Fixed), - CharacterData::Enum(EnumItem::LinkLocal) => Some(Self::LinkLocal), - CharacterData::Enum(EnumItem::LinkLocalDoip) => Some(Self::LinkLocalDoIp), - CharacterData::Enum(EnumItem::RouterAdvertisement) => Some(Self::RouterAdvertisement), - _ => None, - } - } - - fn to_cdata(self) -> CharacterData { - match self { - Self::DHCPv6 => CharacterData::Enum(EnumItem::Dhcpv6), - Self::Fixed => CharacterData::Enum(EnumItem::Fixed), - Self::LinkLocal => CharacterData::Enum(EnumItem::LinkLocal), - Self::LinkLocalDoIp => CharacterData::Enum(EnumItem::LinkLocalDoip), - Self::RouterAdvertisement => CharacterData::Enum(EnumItem::RouterAdvertisement), - } - } -} - -//################################################################## - -/// A socket address estapblishes the link between one or more ECUs and a NetworkEndpoint. -/// It contains all settings that are relevant for this combination. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct SocketAddress(Element); -abstraction_element!(SocketAddress, SocketAddress); - -impl SocketAddress { - fn new( - name: &str, - channel: &EthernetPhysicalChannel, - network_endpoint: &NetworkEndpoint, - tp_config: &TpConfig, - sa_type: SocketAddressType, - ) -> Result { - let channel_elem = channel.element(); - let (unicast, ecu_instances) = match sa_type { - SocketAddressType::Unicast(Some(ecu_instance)) => (true, vec![ecu_instance]), - SocketAddressType::Unicast(None) => (true, vec![]), - SocketAddressType::Multicast(ecu_instances) => (false, ecu_instances), - }; - - // get the connector for each ECU in advance, so that nothing needs to be cleaned up if there is a problem here - let connectors = ecu_instances - .iter() - .filter_map(|ecu_instance| channel.get_ecu_connector(ecu_instance)) - .collect::>(); - if connectors.len() != ecu_instances.len() { - return Err(AutosarAbstractionError::InvalidParameter( - "All EcuInstances must be connected to the EthernetPhysicalChannel".to_string(), - )); - } - - let elem = channel_elem - .get_or_create_sub_element(ElementName::SoAdConfig)? - .get_or_create_sub_element(ElementName::SocketAddresss)? - .create_named_sub_element(ElementName::SocketAddress, name)?; - - if unicast { - elem.create_sub_element(ElementName::ConnectorRef) - .unwrap() - .set_reference_target(&connectors[0]) - .unwrap(); - } else { - let mc_connectors = elem.create_sub_element(ElementName::MulticastConnectorRefs)?; - for conn in &connectors { - mc_connectors - .create_sub_element(ElementName::MulticastConnectorRef)? - .set_reference_target(conn)?; - } - } - - let ae_name = format!("{name}_AE"); - let ae = elem.create_named_sub_element(ElementName::ApplicationEndpoint, &ae_name)?; - ae.create_sub_element(ElementName::NetworkEndpointRef)? - .set_reference_target(&network_endpoint.element())?; - let tp_configuration = ae.create_sub_element(ElementName::TpConfiguration)?; - match tp_config { - TpConfig::TcpTp { - port_number, - port_dynamically_assigned, - } => { - let tcptp = tp_configuration.create_sub_element(ElementName::TcpTp)?; - let tcptp_port = tcptp.create_sub_element(ElementName::TcpTpPort)?; - if let Some(portnum) = port_number { - tcptp_port - .create_sub_element(ElementName::PortNumber)? - .set_character_data(CharacterData::String(portnum.to_string()))?; - } - if let Some(dyn_assign) = port_dynamically_assigned { - let boolstr = if *dyn_assign { - "true".to_string() - } else { - "false".to_string() - }; - tcptp_port - .create_sub_element(ElementName::DynamicallyAssigned)? - .set_character_data(CharacterData::String(boolstr))?; - } - } - TpConfig::UdpTp { - port_number, - port_dynamically_assigned, - } => { - let udptp_port = tp_configuration - .create_sub_element(ElementName::UdpTp)? - .create_sub_element(ElementName::UdpTpPort)?; - if let Some(portnum) = port_number { - udptp_port - .create_sub_element(ElementName::PortNumber)? - .set_character_data(CharacterData::String(portnum.to_string()))?; - } - if let Some(dyn_assign) = port_dynamically_assigned { - let boolstr = if *dyn_assign { - "true".to_string() - } else { - "false".to_string() - }; - udptp_port - .create_sub_element(ElementName::DynamicallyAssigned)? - .set_character_data(CharacterData::String(boolstr))?; - } - } - } - - Ok(Self(elem)) - } - - /// get the socket address type: unicast / multicast, as well as the connected ecus - pub fn get_type(&self) -> Option { - if let Some(connector_ref) = self.0.get_sub_element(ElementName::ConnectorRef) { - let ecu = EcuInstance::try_from(connector_ref.get_reference_target().ok()?.named_parent().ok()??).ok()?; - Some(SocketAddressType::Unicast(Some(ecu))) - } else if let Some(mcr) = self.0.get_sub_element(ElementName::MulticastConnectorRefs) { - let ecus = mcr - .sub_elements() - .filter_map(|cr| { - cr.get_reference_target() - .ok() - .and_then(|conn| conn.named_parent().ok().flatten()) - }) - .filter_map(|ecu_elem| EcuInstance::try_from(ecu_elem).ok()) - .collect::>(); - Some(SocketAddressType::Multicast(ecus)) - } else { - None - } - } - - /// get the transport protocol settings for this `SocketAddress` - pub fn get_tp_config(&self) -> Option { - let tp = self - .0 - .get_sub_element(ElementName::ApplicationEndpoint)? - .get_sub_element(ElementName::TpConfiguration)?; - - if let Some(tcp_tp) = tp.get_sub_element(ElementName::TcpTp) { - let port = tcp_tp.get_sub_element(ElementName::TcpTpPort)?; - let (port_number, port_dynamically_assigned) = Self::get_port_config(&port); - Some(TpConfig::TcpTp { - port_number, - port_dynamically_assigned, - }) - } else if let Some(udp_tp) = tp.get_sub_element(ElementName::UdpTp) { - let port = udp_tp.get_sub_element(ElementName::UdpTpPort)?; - let (port_number, port_dynamically_assigned) = Self::get_port_config(&port); - Some(TpConfig::UdpTp { - port_number, - port_dynamically_assigned, - }) - } else { - None - } - } - - fn get_port_config(port_element: &Element) -> (Option, Option) { - let port_number = port_element - .get_sub_element(ElementName::PortNumber) - .and_then(|pn| pn.character_data()) - .and_then(|cdata| cdata.decode_integer()); - let port_dynamically_assigned = port_element - .get_sub_element(ElementName::DynamicallyAssigned) - .and_then(|da| da.character_data()) - .and_then(|cdata| cdata.string_value()) - .map(|val| val == "true" || val == "1"); - (port_number, port_dynamically_assigned) - } -} - -//################################################################## - -/// transport protocol settings of a [`SocketAddress`] -#[derive(Debug, Clone, PartialEq)] -pub enum TpConfig { - /// The socket uses TCP - TcpTp { - port_number: Option, - port_dynamically_assigned: Option, - // additional TCP options: currently not supported - }, - // The socket uses UDP - UdpTp { - port_number: Option, - port_dynamically_assigned: Option, - }, - // RtpTp, Ieee1722Tp, HttpTp: currently not supported -} - -//################################################################## - -/// Describes if a [`SocketAddress`] is used for unicast or multicast -#[derive(Debug, Clone, PartialEq)] -pub enum SocketAddressType { - Unicast(Option), - Multicast(Vec), -} - -//################################################################## - -#[doc(hidden)] -pub struct EthernetCtrlChannelsIterator { - connector_iter: Option, - comm_controller: Element, - model: Result, -} - -impl EthernetCtrlChannelsIterator { - fn new(controller: &EthernetCommunicationController, ecu: &Element) -> Self { - let iter = ecu.get_sub_element(ElementName::Connectors).map(|c| c.sub_elements()); - let comm_controller = controller.element().clone(); - let model = comm_controller.model(); - Self { - connector_iter: iter, - comm_controller, - model, - } - } -} - -impl Iterator for EthernetCtrlChannelsIterator { - type Item = EthernetPhysicalChannel; - - fn next(&mut self) -> Option { - let model = self.model.as_ref().ok()?; - let connector_iter = self.connector_iter.as_mut()?; - for connector in connector_iter.by_ref() { - if connector.element_name() == ElementName::EthernetCommunicationConnector { - if let Some(commcontroller_of_connector) = connector - .get_sub_element(ElementName::CommControllerRef) - .and_then(|ccr| ccr.get_reference_target().ok()) - { - if commcontroller_of_connector == self.comm_controller { - for ref_origin in model - .get_references_to(&connector.path().ok()?) - .iter() - .filter_map(WeakElement::upgrade) - .filter_map(|elem| elem.named_parent().ok().flatten()) - { - // This assumes that each connector will only ever be referenced by at most one - // PhysicalChannel, which is true for well-formed files. - if ref_origin.element_name() == ElementName::EthernetPhysicalChannel { - return Some(EthernetPhysicalChannel(ref_origin)); - } - } - } - } - } - } - None - } -} - -//################################################################## - -#[doc(hidden)] -pub struct EthernetClusterChannelsIterator { - elements_iter: Option, -} - -impl EthernetClusterChannelsIterator { - fn new(cluster: &EthernetCluster) -> Self { - let elements_iter = cluster - .0 - .get_sub_element(ElementName::EthernetClusterVariants) - .and_then(|ecv| ecv.get_sub_element(ElementName::EthernetClusterConditional)) - .and_then(|ecc| ecc.get_sub_element(ElementName::PhysicalChannels)) - .map(|pc| pc.sub_elements()); - - Self { elements_iter } - } -} - -impl Iterator for EthernetClusterChannelsIterator { - type Item = EthernetPhysicalChannel; - - fn next(&mut self) -> Option { - let elements_iter = self.elements_iter.as_mut()?; - for channel in elements_iter.by_ref() { - if let Ok(ethernet_physical_channel) = EthernetPhysicalChannel::try_from(channel) { - return Some(ethernet_physical_channel); - } - } - None - } -} - -//################################################################## - -#[doc(hidden)] -pub struct NetworkEndpointIterator { - elements_iter: Option, -} - -impl NetworkEndpointIterator { - fn new(channel: &EthernetPhysicalChannel) -> Self { - let elements_iter = channel - .0 - .get_sub_element(ElementName::NetworkEndpoints) - .map(|ne| ne.sub_elements()); - - Self { elements_iter } - } -} - -impl Iterator for NetworkEndpointIterator { - type Item = NetworkEndpoint; - - fn next(&mut self) -> Option { - let elements_iter = self.elements_iter.as_mut()?; - for endpoint in elements_iter.by_ref() { - if let Ok(network_endpoint) = NetworkEndpoint::try_from(endpoint) { - return Some(network_endpoint); - } - } - None - } -} - -//################################################################## - -#[doc(hidden)] -pub struct NetworkEndpointAddressIterator { - elements_iter: Option, -} - -impl NetworkEndpointAddressIterator { - fn new(network_endpoint: &NetworkEndpoint) -> Self { - let elements_iter = network_endpoint - .0 - .get_sub_element(ElementName::NetworkEndpointAddresses) - .map(|nea| nea.sub_elements()); - - Self { elements_iter } - } -} - -impl Iterator for NetworkEndpointAddressIterator { - type Item = NetworkEndpointAddress; - - fn next(&mut self) -> Option { - let elements_iter = self.elements_iter.as_mut()?; - for endpoint_address in elements_iter.by_ref() { - if let Ok(network_endpoint_address) = NetworkEndpointAddress::try_from(endpoint_address) { - return Some(network_endpoint_address); - } - } - None - } -} -//################################################################## - -#[doc(hidden)] -pub struct SocketAddressIterator { - elements_iter: Option, -} - -impl SocketAddressIterator { - fn new(channel: &EthernetPhysicalChannel) -> Self { - let elements_iter = channel - .0 - .get_sub_element(ElementName::SoAdConfig) - .and_then(|sc| sc.get_sub_element(ElementName::SocketAddresss)) - .map(|nea| nea.sub_elements()); - - Self { elements_iter } - } -} - -impl Iterator for SocketAddressIterator { - type Item = SocketAddress; - - fn next(&mut self) -> Option { - let elements_iter = self.elements_iter.as_mut()?; - for raw_socket_address in elements_iter.by_ref() { - if let Ok(socket_address) = SocketAddress::try_from(raw_socket_address) { - return Some(socket_address); - } - } - None - } -} - -//################################################################## - -#[cfg(test)] -mod test { - use crate::{System, SystemCategory}; - - use super::*; - use autosar_data::AutosarVersion; - - #[test] - fn cluster() { - let model = AutosarModel::new(); - model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); - let pkg = ArPackage::get_or_create(&model, "/test").unwrap(); - let system = System::new("System", &pkg, SystemCategory::SystemDescription).unwrap(); - - let pkg2 = ArPackage::get_or_create(&model, "/ethernet").unwrap(); - // create the ethernet cluster EthCluster - let result = system.create_ethernet_cluster("EthCluster", &pkg2); - assert!(result.is_ok()); - let cluster = result.unwrap(); - // creating the same cluster again is not possible - let result = system.create_ethernet_cluster("EthCluster", &pkg2); - assert!(result.is_err()); - - // create an untagged channel - let result = cluster.create_physical_channel("Channel1", None); - assert!(result.is_ok()); - // can't create a second untagged channel - let result = cluster.create_physical_channel("Channel2", None); - assert!(result.is_err()); - - // create a channel for VLAN 1 - let vlan_info = EthernetVlanInfo { - vlan_name: "VLAN_1".to_string(), - vlan_id: 1, - }; - let result = cluster.create_physical_channel("Channel3", Some(vlan_info)); - assert!(result.is_ok()); - - // can't create a second channel called Channel3 - let vlan_info = EthernetVlanInfo { - vlan_name: "VLAN_2".to_string(), - vlan_id: 2, - }; - let result = cluster.create_physical_channel("Channel3", Some(vlan_info)); - assert!(result.is_err()); - - // create a channel for VLAN 2 - let vlan_info = EthernetVlanInfo { - vlan_name: "VLAN_2".to_string(), - vlan_id: 2, - }; - let result = cluster.create_physical_channel("Channel4", Some(vlan_info)); - assert!(result.is_ok()); - - // can't create a second channel for VLAN 2 - let vlan_info = EthernetVlanInfo { - vlan_name: "VLAN_2".to_string(), - vlan_id: 2, - }; - let result = cluster.create_physical_channel("Channel5", Some(vlan_info)); - assert!(result.is_err()); - - let count = cluster.physical_channels().count(); - assert_eq!(count, 3); - } - - #[test] - fn channel() { - let model = AutosarModel::new(); - model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); - let pkg = ArPackage::get_or_create(&model, "/test").unwrap(); - let system = System::new("System", &pkg, SystemCategory::SystemDescription).unwrap(); - let cluster = system.create_ethernet_cluster("EthCluster", &pkg).unwrap(); - - let channel = cluster.create_physical_channel("channel_name", None).unwrap(); - let c2 = channel.cluster().unwrap(); - assert_eq!(cluster, c2); - - let vi = channel.get_vlan_info(); - assert!(vi.is_none()); - - let elem_vlan = channel - .element() - .create_named_sub_element(ElementName::Vlan, "VLAN_1") - .unwrap(); - let vi = channel.get_vlan_info(); - assert!(vi.is_none()); - - let elem_vlanid = elem_vlan.create_sub_element(ElementName::VlanIdentifier).unwrap(); - let vi = channel.get_vlan_info(); - assert!(vi.is_none()); - - elem_vlanid - .set_character_data(CharacterData::String(1.to_string())) - .unwrap(); - let vi = channel.get_vlan_info().unwrap(); - assert_eq!(vi.vlan_id, 1); - } - - #[test] - fn controller() { - let model = AutosarModel::new(); - model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); - let pkg = ArPackage::get_or_create(&model, "/test").unwrap(); - let system = System::new("System", &pkg, SystemCategory::SystemDescription).unwrap(); - let ecu = system.create_ecu_instance("ECU", &pkg).unwrap(); - - // can't create a controller with an invalid MAC address - let result = ecu.create_ethernet_communication_controller("Controller", Some("abcdef".to_string())); - assert!(result.is_err()); - - // create a controller - let result = ecu.create_ethernet_communication_controller("Controller", Some("01:02:03:04:05:06".to_string())); - let controller = result.unwrap(); - - // create some physical channels - let cluster = system.create_ethernet_cluster("EthCluster", &pkg).unwrap(); - let channel1 = cluster.create_physical_channel("C1", None).unwrap(); - let vlan_info = EthernetVlanInfo { - vlan_name: "VLAN_1".to_string(), - vlan_id: 1, - }; - let channel2 = cluster.create_physical_channel("C2", Some(vlan_info)).unwrap(); - - // connect the controller to channel1 - let result = controller.connect_physical_channel("connection_name1", &channel1); - assert!(result.is_ok()); - // can't connect to the same channel again - let result = controller.connect_physical_channel("connection_name2", &channel1); - assert!(result.is_err()); - // connect the controller to channel2 - let result = controller.connect_physical_channel("connection_name2", &channel2); - assert!(result.is_ok()); - - let count = controller.connected_channels().count(); - assert_eq!(count, 2); - - // remove the controller and try to list its connected channels again - let ctrl_parent = controller.0.parent().unwrap().unwrap(); - ctrl_parent.remove_sub_element(controller.0.clone()).unwrap(); - let count = controller.connected_channels().count(); - assert_eq!(count, 0); - } - - #[test] - fn socket_address() { - let model = AutosarModel::new(); - model.create_file("filename", AutosarVersion::Autosar_4_3_0).unwrap(); - let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); - let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); - let ecu_instance = system.create_ecu_instance("Ecu", &package).unwrap(); - let controller = ecu_instance - .create_ethernet_communication_controller("EthCtrl", None) - .unwrap(); - let cluster = system.create_ethernet_cluster("Cluster", &package).unwrap(); - let channel = cluster.create_physical_channel("Channel", None).unwrap(); - controller.connect_physical_channel("connection", &channel).unwrap(); - let endpoint_address = NetworkEndpointAddress::IPv4 { - address: Some("192.168.0.1".to_string()), - address_source: Some(IPv4AddressSource::Fixed), - default_gateway: Some("192.168.0.2".to_string()), - network_mask: Some("255.255.255.0".to_string()), - }; - let network_endpoint = channel - .create_network_endpoint("Address", endpoint_address, Some(&ecu_instance)) - .unwrap(); - let tcp_port = TpConfig::TcpTp { - port_number: Some(1234), - port_dynamically_assigned: None, - }; - let socket_type = SocketAddressType::Unicast(Some(ecu_instance)); - channel - .create_socket_address("SocketName", &network_endpoint, &tcp_port, socket_type) - .unwrap(); - assert_eq!(channel.socket_addresses().count(), 1); - } -} diff --git a/autosar-data-abstraction/src/lib.rs b/autosar-data-abstraction/src/lib.rs index 4d634c5..33555ab 100644 --- a/autosar-data-abstraction/src/lib.rs +++ b/autosar-data-abstraction/src/lib.rs @@ -1,18 +1,14 @@ -use autosar_data::{AutosarDataError, Element}; +use autosar_data::{AutosarDataError, AutosarModel, Element, EnumItem}; use thiserror::Error; +pub mod communication; + mod arpackage; -mod can; mod ecuinstance; -mod ethernet; -mod flexray; mod system; pub use arpackage::ArPackage; -pub use can::*; pub use ecuinstance::*; -pub use ethernet::*; -pub use flexray::*; pub use system::*; /// The error type `AutosarAbstractionError` wraps all errors from the crate @@ -23,6 +19,10 @@ pub enum AutosarAbstractionError { #[error("conversion error: could not convert {} to {}", .element.element_name(), dest)] ConversionError { element: Element, dest: String }, + /// converting an autosar-data element to a class in the abstract model failed + #[error("value conversion error: could not convert {} to {}", .value, .dest)] + ValueConversionError { value: String, dest: String }, + /// `ModelError` wraps [`AutosarDataError`] errors from autosar-data operations, e.g. /// [`AutosarDataError::ItemDeleted`], [`AutosarDataError::IncorrectContentType`], ... #[error("model error: {}", .0)] @@ -47,23 +47,29 @@ impl From for AutosarAbstractionError { } } -pub trait AbstractionElement { +//######################################################### + +pub trait AbstractionElement: Clone + PartialEq { #[must_use] fn element(&self) -> ∈ #[must_use] - fn name(&self) -> String { - self.element().item_name().unwrap() + fn name(&self) -> Option { + self.element().item_name() + } + + fn set_timestamp(&self) { + todo!() } } macro_rules! abstraction_element { ($name: ident, $base_elem: ident) => { - impl TryFrom for $name { + impl TryFrom for $name { type Error = AutosarAbstractionError; - fn try_from(element: Element) -> Result { - if element.element_name() == ElementName::$base_elem { + fn try_from(element: autosar_data::Element) -> Result { + if element.element_name() == autosar_data::ElementName::$base_elem { Ok($name(element)) } else { Err(AutosarAbstractionError::ConversionError { @@ -75,12 +81,12 @@ macro_rules! abstraction_element { } impl AbstractionElement for $name { - fn element(&self) -> &Element { + fn element(&self) -> &autosar_data::Element { &self.0 } } - impl From<$name> for Element { + impl From<$name> for autosar_data::Element { fn from(val: $name) -> Self { val.0 } @@ -90,6 +96,132 @@ macro_rules! abstraction_element { pub(crate) use abstraction_element; +//######################################################### + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ByteOrder { + MostSignificantByteFirst, + MostSignificantByteLast, + Opaque, +} + +impl TryFrom for ByteOrder { + type Error = AutosarAbstractionError; + + fn try_from(value: EnumItem) -> Result { + match value { + EnumItem::MostSignificantByteFirst => Ok(ByteOrder::MostSignificantByteFirst), + EnumItem::MostSignificantByteLast => Ok(ByteOrder::MostSignificantByteLast), + EnumItem::Opaque => Ok(ByteOrder::Opaque), + _ => Err(AutosarAbstractionError::ValueConversionError { + value: value.to_string(), + dest: "ByteOrder".to_string(), + }), + } + } +} + +impl From for EnumItem { + fn from(value: ByteOrder) -> Self { + match value { + ByteOrder::MostSignificantByteFirst => EnumItem::MostSignificantByteFirst, + ByteOrder::MostSignificantByteLast => EnumItem::MostSignificantByteLast, + ByteOrder::Opaque => EnumItem::Opaque, + } + } +} + +//######################################################### + +macro_rules! basic_element_iterator { + ($name: ident, $output: ident) => { + #[doc(hidden)] + pub struct $name { + iter: Option, + } + + impl $name { + pub(crate) fn new(elem_container: Option) -> Self { + let iter = elem_container.map(|se| se.sub_elements()); + Self { iter } + } + } + + impl Iterator for $name { + type Item = $output; + + fn next(&mut self) -> Option { + let iter = self.iter.as_mut()?; + for element in iter.by_ref() { + if let Ok(output_item) = $output::try_from(element) { + return Some(output_item); + } + } + self.iter = None; + None + } + } + + impl std::iter::FusedIterator for $name {} + }; +} + +pub(crate) use basic_element_iterator; + +//######################################################### + +macro_rules! referenced_element_iterator { + ($name: ident, $output: ident) => { + #[doc(hidden)] + pub struct $name { + iter: Option, + } + + impl $name { + pub(crate) fn new(elem_container: Option) -> Self { + let iter = elem_container.map(|se| se.sub_elements()); + Self { iter } + } + } + + impl Iterator for $name { + type Item = $output; + + fn next(&mut self) -> Option { + let iter = self.iter.as_mut()?; + for element in iter.by_ref() { + if let Ok(Ok(output_item)) = element.get_reference_target().map($output::try_from) { + return Some(output_item); + } + } + self.iter = None; + None + } + } + + impl std::iter::FusedIterator for $name {} + }; +} + +pub(crate) use referenced_element_iterator; + +//################################################################## + +pub(crate) fn make_unique_name(model: &AutosarModel, base_path: String, initial_name: String) -> String { + let mut full_path = format!("{base_path}/{initial_name}"); + let mut name = initial_name.clone(); + let mut counter = 0; + while model.get_element_by_path(&full_path).is_some() { + counter += 1; + name = format!("{initial_name}_{counter}"); + full_path = format!("{base_path}/{name}"); + } + + name +} + +//######################################################### + #[cfg(test)] mod test { use autosar_data::AutosarModel; diff --git a/autosar-data-abstraction/src/system.rs b/autosar-data-abstraction/src/system.rs index 99a09bd..f1a4f9b 100644 --- a/autosar-data-abstraction/src/system.rs +++ b/autosar-data-abstraction/src/system.rs @@ -1,9 +1,9 @@ use std::iter::FusedIterator; -use crate::{ - abstraction_element, can::CanClusterSettings, flexray::FlexrayClusterSettings, AbstractionElement, ArPackage, - AutosarAbstractionError, CanCluster, EcuInstance, EthernetCluster, FlexrayCluster, +use crate::communication::{ + CanCluster, CanClusterSettings, CanFrame, Cluster, ContainerIPdu, DcmIPdu, EthernetCluster, FlexrayCluster, FlexrayClusterSettings, FlexrayFrame, GeneralPurposeIPdu, GeneralPurposePdu, ISignalIPdu, MultiplexedIPdu, NPdu, NmPdu, SecuredIPdu, Signal, SignalGroup }; +use crate::{abstraction_element, AbstractionElement, ArPackage, AutosarAbstractionError, EcuInstance}; use autosar_data::{AutosarDataError, AutosarModel, Element, ElementName, WeakElement}; /// The System is the top level of a system template @@ -63,7 +63,7 @@ impl System { let system_elem = pkg_elem_elements.create_named_sub_element(ElementName::System, name)?; system_elem .create_sub_element(ElementName::Category) - .and_then(|cat| cat.set_character_data(autosar_data::CharacterData::String(category.to_string())))?; + .and_then(|cat| cat.set_character_data(category.to_string()))?; Ok(System(system_elem)) } @@ -128,6 +128,7 @@ impl System { /// ``` /// # use autosar_data::*; /// # use autosar_data_abstraction::*; + /// # use autosar_data_abstraction::communication::*; /// # let model = AutosarModel::new(); /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); @@ -161,6 +162,7 @@ impl System { /// ``` /// # use autosar_data::*; /// # use autosar_data_abstraction::*; + /// # use autosar_data_abstraction::communication::*; /// # let model = AutosarModel::new(); /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); @@ -196,6 +198,7 @@ impl System { /// ``` /// # use autosar_data::*; /// # use autosar_data_abstraction::*; + /// # use autosar_data_abstraction::communication::*; /// # let model = AutosarModel::new(); /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); @@ -219,6 +222,345 @@ impl System { Ok(cluster) } + /// create a new [`CanFrame`] + /// + /// This new frame needs to be linked to a `CanPhysicalChannel` + pub fn create_can_frame( + &self, + name: &str, + byte_length: u64, + package: &ArPackage, + ) -> Result { + let can_frame = CanFrame::new(name, byte_length, package)?; + self.create_fibex_element_ref_unchecked(can_frame.element())?; + + Ok(can_frame) + } + + /// create a new [`FlexrayFrame`] + /// + /// This new frame needs to be linked to a `FlexrayPhysicalChannel` + pub fn create_flexray_frame( + &self, + name: &str, + byte_length: u64, + package: &ArPackage, + ) -> Result { + let flexray_frame = FlexrayFrame::new(name, byte_length, package)?; + self.create_fibex_element_ref_unchecked(flexray_frame.element())?; + + Ok(flexray_frame) + } + + /// create a new signal in the [`System`] + /// + /// `I-SIGNAL` and `SYSTEM-SIGNAL` are created using the same name; therefore they must be placed in + /// different packages: sig_package and sys_package may not be identical. + /// + /// # Example + /// + /// ``` + /// # use autosar_data::*; + /// # use autosar_data_abstraction::*; + /// # use autosar_data_abstraction::communication::*; + /// # let model = AutosarModel::new(); + /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); + /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); + /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); + /// let sig_package = ArPackage::get_or_create(&model, "/ISignals").unwrap(); + /// let sys_package = ArPackage::get_or_create(&model, "/SystemSignals").unwrap(); + /// system.create_signal("signal1", 32, &sig_package, &sys_package).unwrap(); + /// ``` + /// + /// # Errors + /// + /// - [`AutosarAbstractionError::InvalidParameter`] sig_package and sys_package may not be identical + /// - [`AutosarAbstractionError::ModelError`] An error occurred in the Autosar model while trying to create elements + pub fn create_signal( + &self, + name: &str, + bit_length: u64, + sig_package: &ArPackage, + sys_package: &ArPackage, + ) -> Result { + let signal = Signal::new(name, bit_length, sig_package, sys_package)?; + + self.create_fibex_element_ref_unchecked(signal.element())?; + + Ok(signal) + } + + /// create a new signal group in the [`System`] + /// + /// `I-SIGNAL-GROUP` and `SYSTEM-SIGNAL-GROUP` are created using the same name; therefore they must be placed in + /// different packages: sig_package and sys_package may not be identical. + /// + /// # Example + /// + /// ``` + /// # use autosar_data::*; + /// # use autosar_data_abstraction::*; + /// # use autosar_data_abstraction::communication::*; + /// # let model = AutosarModel::new(); + /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); + /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); + /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); + /// let sig_package = ArPackage::get_or_create(&model, "/ISignals").unwrap(); + /// let sys_package = ArPackage::get_or_create(&model, "/SystemSignals").unwrap(); + /// system.create_signal_group("signal_group", &sig_package, &sys_package).unwrap(); + /// ``` + /// + /// # Errors + /// + /// - [`AutosarAbstractionError::InvalidParameter`] sig_package and sys_package may not be identical + /// - [`AutosarAbstractionError::ModelError`] An error occurred in the Autosar model while trying to create elements + pub fn create_signal_group( + &self, + name: &str, + sig_package: &ArPackage, + sys_package: &ArPackage, + ) -> Result { + let signal_group = SignalGroup::new(name, sig_package, sys_package)?; + + self.create_fibex_element_ref_unchecked(signal_group.element())?; + + Ok(signal_group) + } + + /// create an [`ISignalIPdu`] in the [`System`] + /// + /// # Example + /// + /// ``` + /// # use autosar_data::*; + /// # use autosar_data_abstraction::*; + /// # use autosar_data_abstraction::communication::*; + /// # let model = AutosarModel::new(); + /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); + /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); + /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); + /// let package = ArPackage::get_or_create(&model, "/Pdus").unwrap(); + /// system.create_isignal_ipdu("pdu", &package).unwrap(); + /// ``` + /// + /// # Errors + /// + /// - [`AutosarAbstractionError::ModelError`] An error occurred in the Autosar model while trying to create elements + pub fn create_isignal_ipdu(&self, name: &str, package: &ArPackage) -> Result { + let pdu = ISignalIPdu::new(name, package)?; + self.create_fibex_element_ref_unchecked(pdu.element())?; + + Ok(pdu) + } + + /// create an [`NmPdu`] in the [`System`] + /// + /// # Example + /// + /// ``` + /// # use autosar_data::*; + /// # use autosar_data_abstraction::*; + /// # use autosar_data_abstraction::communication::*; + /// # let model = AutosarModel::new(); + /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); + /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); + /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); + /// let package = ArPackage::get_or_create(&model, "/Pdus").unwrap(); + /// system.create_nm_pdu("pdu", &package).unwrap(); + /// ``` + /// + /// # Errors + /// + /// - [`AutosarAbstractionError::ModelError`] An error occurred in the Autosar model while trying to create elements + pub fn create_nm_pdu(&self, name: &str, package: &ArPackage) -> Result { + let pdu = NmPdu::new(name, package)?; + self.create_fibex_element_ref_unchecked(pdu.element())?; + + Ok(pdu) + } + + /// create an [`NPdu`] in the [`System`] + /// + /// # Example + /// + /// ``` + /// # use autosar_data::*; + /// # use autosar_data_abstraction::*; + /// # use autosar_data_abstraction::communication::*; + /// # let model = AutosarModel::new(); + /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); + /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); + /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); + /// let package = ArPackage::get_or_create(&model, "/Pdus").unwrap(); + /// system.create_n_pdu("pdu", &package).unwrap(); + /// ``` + /// + /// # Errors + /// + /// - [`AutosarAbstractionError::ModelError`] An error occurred in the Autosar model while trying to create elements + pub fn create_n_pdu(&self, name: &str, package: &ArPackage) -> Result { + let pdu = NPdu::new(name, package)?; + self.create_fibex_element_ref_unchecked(pdu.element())?; + + Ok(pdu) + } + + /// create a [`DcmIPdu`] in the [`System`] + /// + /// # Example + /// + /// ``` + /// # use autosar_data::*; + /// # use autosar_data_abstraction::*; + /// # use autosar_data_abstraction::communication::*; + /// # let model = AutosarModel::new(); + /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); + /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); + /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); + /// let package = ArPackage::get_or_create(&model, "/Pdus").unwrap(); + /// system.create_dcm_ipdu("pdu", &package).unwrap(); + /// ``` + /// + /// # Errors + /// + /// - [`AutosarAbstractionError::ModelError`] An error occurred in the Autosar model while trying to create elements + pub fn create_dcm_ipdu(&self, name: &str, package: &ArPackage) -> Result { + let pdu = DcmIPdu::new(name, package)?; + self.create_fibex_element_ref_unchecked(pdu.element())?; + + Ok(pdu) + } + + /// create a [`GeneralPurposePdu`] in the [`System`] + /// + /// # Example + /// + /// ``` + /// # use autosar_data::*; + /// # use autosar_data_abstraction::*; + /// # use autosar_data_abstraction::communication::*; + /// # let model = AutosarModel::new(); + /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); + /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); + /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); + /// let package = ArPackage::get_or_create(&model, "/Pdus").unwrap(); + /// system.create_general_purpose_pdu("pdu", &package).unwrap(); + /// ``` + /// + /// # Errors + /// + /// - [`AutosarAbstractionError::ModelError`] An error occurred in the Autosar model while trying to create elements + pub fn create_general_purpose_pdu(&self, name: &str, package: &ArPackage) -> Result { + let pdu = GeneralPurposePdu::new(name, package)?; + self.create_fibex_element_ref_unchecked(pdu.element())?; + + Ok(pdu) + } + + /// create a [`GeneralPurposeIPdu`] in the [`System`] + /// + /// # Example + /// + /// ``` + /// # use autosar_data::*; + /// # use autosar_data_abstraction::*; + /// # use autosar_data_abstraction::communication::*; + /// # let model = AutosarModel::new(); + /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); + /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); + /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); + /// let package = ArPackage::get_or_create(&model, "/Pdus").unwrap(); + /// system.create_general_purpose_ipdu("pdu", &package).unwrap(); + /// ``` + /// + /// # Errors + /// + /// - [`AutosarAbstractionError::ModelError`] An error occurred in the Autosar model while trying to create elements + pub fn create_general_purpose_ipdu(&self, name: &str, package: &ArPackage) -> Result { + let pdu = GeneralPurposeIPdu::new(name, package)?; + self.create_fibex_element_ref_unchecked(pdu.element())?; + + Ok(pdu) + } + + /// create a [`ContainerIPdu`] in the [`System`] + /// + /// # Example + /// + /// ``` + /// # use autosar_data::*; + /// # use autosar_data_abstraction::*; + /// # use autosar_data_abstraction::communication::*; + /// # let model = AutosarModel::new(); + /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); + /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); + /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); + /// let package = ArPackage::get_or_create(&model, "/Pdus").unwrap(); + /// system.create_container_ipdu("pdu", &package).unwrap(); + /// ``` + /// + /// # Errors + /// + /// - [`AutosarAbstractionError::ModelError`] An error occurred in the Autosar model while trying to create elements + pub fn create_container_ipdu(&self, name: &str, package: &ArPackage) -> Result { + let pdu = ContainerIPdu::new(name, package)?; + self.create_fibex_element_ref_unchecked(pdu.element())?; + + Ok(pdu) + } + + /// create a [`SecuredIPdu`] in the [`System`] + /// + /// # Example + /// + /// ``` + /// # use autosar_data::*; + /// # use autosar_data_abstraction::*; + /// # use autosar_data_abstraction::communication::*; + /// # let model = AutosarModel::new(); + /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); + /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); + /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); + /// let package = ArPackage::get_or_create(&model, "/Pdus").unwrap(); + /// system.create_secured_ipdu("pdu", &package).unwrap(); + /// ``` + /// + /// # Errors + /// + /// - [`AutosarAbstractionError::ModelError`] An error occurred in the Autosar model while trying to create elements + pub fn create_secured_ipdu(&self, name: &str, package: &ArPackage) -> Result { + let pdu = SecuredIPdu::new(name, package)?; + self.create_fibex_element_ref_unchecked(pdu.element())?; + + Ok(pdu) + } + + /// create a [`MultiplexedIPdu`] in the [`System`] + /// + /// # Example + /// + /// ``` + /// # use autosar_data::*; + /// # use autosar_data_abstraction::*; + /// # use autosar_data_abstraction::communication::*; + /// # let model = AutosarModel::new(); + /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); + /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); + /// # let system = System::new("System", &package, SystemCategory::SystemExtract).unwrap(); + /// let package = ArPackage::get_or_create(&model, "/Pdus").unwrap(); + /// system.create_multiplexed_ipdu("pdu", &package).unwrap(); + /// ``` + /// + /// # Errors + /// + /// - [`AutosarAbstractionError::ModelError`] An error occurred in the Autosar model while trying to create elements + pub fn create_multiplexed_ipdu(&self, name: &str, package: &ArPackage) -> Result { + let pdu = MultiplexedIPdu::new(name, package)?; + self.create_fibex_element_ref_unchecked(pdu.element())?; + + Ok(pdu) + } + /// Create an iterator over all clusters connected to the SYSTEM /// /// # Example @@ -226,6 +568,7 @@ impl System { /// ``` /// # use autosar_data::*; /// # use autosar_data_abstraction::*; + /// # use autosar_data_abstraction::communication::*; /// # let model = AutosarModel::new(); /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); @@ -251,6 +594,7 @@ impl System { /// ``` /// # use autosar_data::*; /// # use autosar_data_abstraction::*; + /// # use autosar_data_abstraction::communication::*; /// # let model = AutosarModel::new(); /// # model.create_file("filename", AutosarVersion::Autosar_00048).unwrap(); /// # let package = ArPackage::get_or_create(&model, "/pkg1").unwrap(); @@ -371,16 +715,6 @@ pub struct ClusterIterator { position: usize, } -/// An `AbstractCluster` is returned by [`System::clusters`]. -/// It can contain any supported communication cluster. -#[non_exhaustive] -pub enum AbstractCluster { - Can(CanCluster), - Ethernet(EthernetCluster), - FlexRay(FlexrayCluster), - // missing: Lin, TTCAN, J1939, CDD (aka user defined) -} - impl ClusterIterator { pub(crate) fn new(system: &System) -> Self { let fibex_elements = system.0.get_sub_element(ElementName::FibexElements); @@ -393,7 +727,7 @@ impl ClusterIterator { } impl Iterator for ClusterIterator { - type Item = AbstractCluster; + type Item = Cluster; fn next(&mut self) -> Option { let fibelem = self.fibex_elements.as_ref()?; @@ -407,17 +741,17 @@ impl Iterator for ClusterIterator { match ref_element.element_name() { ElementName::CanCluster => { if let Ok(can_cluster) = CanCluster::try_from(ref_element) { - return Some(AbstractCluster::Can(can_cluster)); + return Some(Cluster::Can(can_cluster)); } } ElementName::EthernetCluster => { if let Ok(can_cluster) = EthernetCluster::try_from(ref_element) { - return Some(AbstractCluster::Ethernet(can_cluster)); + return Some(Cluster::Ethernet(can_cluster)); } } ElementName::FlexrayCluster => { if let Ok(can_cluster) = FlexrayCluster::try_from(ref_element) { - return Some(AbstractCluster::FlexRay(can_cluster)); + return Some(Cluster::FlexRay(can_cluster)); } } // missing: LIN, TTCAN, J1939 @@ -437,8 +771,8 @@ impl FusedIterator for ClusterIterator {} #[cfg(test)] mod test { use crate::{ - can::CanClusterSettings, flexray::FlexrayClusterSettings, system::SystemCategory, AbstractionElement, ArPackage, - System, + communication::CanClusterSettings, communication::FlexrayClusterSettings, system::SystemCategory, + AbstractionElement, ArPackage, System, }; use autosar_data::{AutosarModel, AutosarVersion, ElementName}; @@ -545,13 +879,13 @@ mod test { let mut iter = system.ecu_instances(); let item = iter.next().unwrap(); - assert_eq!(item.name(), "Ecu_1"); + assert_eq!(item.name().unwrap(), "Ecu_1"); assert_eq!(model.get_element_by_path("/ECU/Ecu_1").unwrap(), *item.element()); let item = iter.next().unwrap(); - assert_eq!(item.name(), "Ecu_2"); + assert_eq!(item.name().unwrap(), "Ecu_2"); assert_eq!(model.get_element_by_path("/ECU/Ecu_2").unwrap(), *item.element()); let item = iter.next().unwrap(); - assert_eq!(item.name(), "Ecu_3"); + assert_eq!(item.name().unwrap(), "Ecu_3"); assert_eq!(model.get_element_by_path("/ECU/Ecu_3").unwrap(), *item.element()); assert!(iter.next().is_none()); diff --git a/autosar-data-abstraction/tests/test.rs b/autosar-data-abstraction/tests/test.rs index 331bcd3..7787660 100644 --- a/autosar-data-abstraction/tests/test.rs +++ b/autosar-data-abstraction/tests/test.rs @@ -2,7 +2,11 @@ mod test { use autosar_data::{AutosarModel, AutosarVersion, ElementName}; use autosar_data_abstraction::{ - AbstractionElement, ArPackage, CanClusterSettings, EthernetVlanInfo, FlexrayChannelName, FlexrayClusterSettings, IPv4AddressSource, NetworkEndpointAddress, SocketAddressType, System, SystemCategory, TpConfig + communication::{ + CanClusterSettings, EthernetVlanInfo, FlexrayChannelName, FlexrayClusterSettings, IPv4AddressSource, + NetworkEndpointAddress, SocketAddressType, TpConfig, + }, + AbstractionElement, ArPackage, System, SystemCategory, }; #[test] @@ -81,14 +85,17 @@ mod test { port_number: Some(1234), port_dynamically_assigned: None, }; - let socket_type = SocketAddressType::Unicast(Some(ecu_instance)); + let socket_type = SocketAddressType::Unicast(Some(ecu_instance.clone())); let socket_address = eth_channel .create_socket_address("SocketName", &network_endpoint, &tcp_port, socket_type) .unwrap(); let tcp_port_2 = socket_address.get_tp_config().unwrap(); assert_eq!(tcp_port, tcp_port_2); let socket_type = socket_address.get_type().unwrap(); - assert!(matches!(socket_type, SocketAddressType::Unicast(_))); + assert!(matches!(socket_type, SocketAddressType::Unicast(Some(_)))); + if let SocketAddressType::Unicast(Some(ecu)) = socket_type { + assert_eq!(ecu, ecu_instance); + } println!("{}", model.files().next().unwrap().serialize().unwrap()); }