use super::type_mapper::{map_xsd_to_aas_data_type_def_xsd, DataTypeDefXsd};
use crate::error::SammError;
use crate::metamodel::{
Aspect, ModelElement, Operation as SammOperation, Property as SammProperty,
};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Environment {
#[serde(skip_serializing_if = "Option::is_none")]
pub asset_administration_shells: Option<Vec<AssetAdministrationShell>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub submodels: Option<Vec<Submodel>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub concept_descriptions: Option<Vec<ConceptDescription>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AssetAdministrationShell {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub id_short: Option<String>,
pub model_type: String,
pub asset_information: AssetInformation,
#[serde(skip_serializing_if = "Option::is_none")]
pub submodels: Option<Vec<Reference>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AssetInformation {
pub asset_kind: AssetKind,
#[serde(skip_serializing_if = "Option::is_none")]
pub global_asset_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AssetKind {
Instance,
NotApplicable,
Role,
Type,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Submodel {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub id_short: Option<String>,
pub model_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub kind: Option<ModellingKind>,
#[serde(skip_serializing_if = "Option::is_none")]
pub semantic_id: Option<Reference>,
#[serde(skip_serializing_if = "Option::is_none")]
pub submodel_elements: Option<Vec<SubmodelElement>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ModellingKind {
Instance,
Template,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum SubmodelElement {
Property(Property),
Operation(Operation),
Entity(Entity),
SubmodelElementCollection(SubmodelElementCollection),
SubmodelElementList(SubmodelElementList),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Property {
#[serde(skip_serializing_if = "Option::is_none")]
pub id_short: Option<String>,
pub model_type: String,
pub value_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub value: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub semantic_id: Option<Reference>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Operation {
#[serde(skip_serializing_if = "Option::is_none")]
pub id_short: Option<String>,
pub model_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub input_variables: Option<Vec<OperationVariable>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub output_variables: Option<Vec<OperationVariable>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OperationVariable {
pub value: Box<SubmodelElement>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Entity {
#[serde(skip_serializing_if = "Option::is_none")]
pub id_short: Option<String>,
pub model_type: String,
pub entity_type: EntityType,
#[serde(skip_serializing_if = "Option::is_none")]
pub semantic_id: Option<Reference>,
#[serde(skip_serializing_if = "Option::is_none")]
pub statements: Option<Vec<SubmodelElement>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum EntityType {
CoManagedEntity,
SelfManagedEntity,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SubmodelElementCollection {
#[serde(skip_serializing_if = "Option::is_none")]
pub id_short: Option<String>,
pub model_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub semantic_id: Option<Reference>,
#[serde(skip_serializing_if = "Option::is_none")]
pub value: Option<Vec<SubmodelElement>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SubmodelElementList {
#[serde(skip_serializing_if = "Option::is_none")]
pub id_short: Option<String>,
pub model_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub semantic_id: Option<Reference>,
pub type_value_list_element: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub value: Option<Vec<SubmodelElement>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Reference {
#[serde(rename = "type")]
pub ref_type: ReferenceType,
pub keys: Vec<Key>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ReferenceType {
ExternalReference,
ModelReference,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Key {
#[serde(rename = "type")]
pub key_type: KeyType,
pub value: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum KeyType {
AssetAdministrationShell,
ConceptDescription,
Submodel,
SubmodelElement,
GlobalReference,
Property,
Operation,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ConceptDescription {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub id_short: Option<String>,
pub model_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub embedded_data_specifications: Option<Vec<EmbeddedDataSpecification>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EmbeddedDataSpecification {
pub data_specification: Reference,
pub data_specification_content: DataSpecificationIec61360,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DataSpecificationIec61360 {
pub model_type: String,
pub preferred_name: Vec<LangString>,
#[serde(skip_serializing_if = "Option::is_none")]
pub short_name: Option<Vec<LangString>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub definition: Option<Vec<LangString>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub data_type: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LangString {
pub language: String,
pub text: String,
}
pub fn build_aas_environment(aspect: &Aspect) -> Result<Environment, SammError> {
let aspect_urn = aspect.metadata.urn.clone();
let aspect_name = aspect.name();
let submodel_id = format!("{}/submodel", aspect_urn);
let mut submodel_elements = Vec::new();
for prop in aspect.properties() {
let property_element = build_property(prop)?;
submodel_elements.push(SubmodelElement::Property(property_element));
}
for op in aspect.operations() {
let operation_element = build_operation(op)?;
submodel_elements.push(SubmodelElement::Operation(operation_element));
}
let submodel = Submodel {
id: submodel_id.clone(),
id_short: Some(aspect_name.clone()),
model_type: "Submodel".to_string(),
kind: Some(ModellingKind::Template),
semantic_id: Some(Reference {
ref_type: ReferenceType::ExternalReference,
keys: vec![Key {
key_type: KeyType::GlobalReference,
value: aspect_urn.clone(),
}],
}),
submodel_elements: Some(submodel_elements),
};
let aas = AssetAdministrationShell {
id: aspect_urn.clone(),
id_short: Some("defaultAdminShell".to_string()),
model_type: "AssetAdministrationShell".to_string(),
asset_information: AssetInformation {
asset_kind: AssetKind::Type,
global_asset_id: Some(aspect_urn.clone()),
},
submodels: Some(vec![Reference {
ref_type: ReferenceType::ModelReference,
keys: vec![Key {
key_type: KeyType::Submodel,
value: submodel_id.clone(),
}],
}]),
};
let mut concept_descriptions = Vec::new();
concept_descriptions.push(build_concept_description(&aspect.metadata));
for prop in aspect.properties() {
concept_descriptions.push(build_concept_description(&prop.metadata));
if let Some(ref characteristic) = prop.characteristic {
concept_descriptions.push(build_concept_description(&characteristic.metadata));
}
}
for op in aspect.operations() {
concept_descriptions.push(build_concept_description(&op.metadata));
}
Ok(Environment {
asset_administration_shells: Some(vec![aas]),
submodels: Some(vec![submodel]),
concept_descriptions: Some(concept_descriptions),
})
}
fn build_property(prop: &SammProperty) -> Result<Property, SammError> {
let data_type = if let Some(characteristic) = &prop.characteristic {
if let Some(dt) = &characteristic.data_type {
let aas_type = map_xsd_to_aas_data_type_def_xsd(dt);
aas_type.to_xsd_string().to_string()
} else {
"xs:string".to_string()
}
} else {
"xs:string".to_string()
};
Ok(Property {
id_short: Some(prop.name().clone()),
model_type: "Property".to_string(),
value_type: data_type,
value: None,
semantic_id: Some(Reference {
ref_type: ReferenceType::ExternalReference,
keys: vec![Key {
key_type: KeyType::GlobalReference,
value: prop.metadata.urn.clone(),
}],
}),
})
}
fn build_operation(op: &SammOperation) -> Result<Operation, SammError> {
let input_variables = if !op.input.is_empty() {
let inputs: Result<Vec<OperationVariable>, SammError> = op
.input
.iter()
.map(|prop| {
let property = build_property(prop)?;
Ok(OperationVariable {
value: Box::new(SubmodelElement::Property(property)),
})
})
.collect();
Some(inputs?)
} else {
None
};
let output_variables = if let Some(ref out_prop) = op.output {
let property = build_property(out_prop)?;
Some(vec![OperationVariable {
value: Box::new(SubmodelElement::Property(property)),
}])
} else {
None
};
Ok(Operation {
id_short: Some(op.name().clone()),
model_type: "Operation".to_string(),
input_variables,
output_variables,
})
}
fn build_concept_description(metadata: &crate::metamodel::ElementMetadata) -> ConceptDescription {
let preferred_name: Vec<LangString> = metadata
.preferred_names
.iter()
.map(|(lang, text)| LangString {
language: lang.clone(),
text: text.clone(),
})
.collect();
let definition: Option<Vec<LangString>> = if metadata.descriptions.is_empty() {
None
} else {
Some(
metadata
.descriptions
.iter()
.map(|(lang, text)| LangString {
language: lang.clone(),
text: text.clone(),
})
.collect(),
)
};
let short_name = metadata.urn.split('#').next_back().unwrap_or("element");
let data_spec_content = DataSpecificationIec61360 {
model_type: "DataSpecificationIec61360".to_string(),
preferred_name,
short_name: Some(vec![LangString {
language: "en".to_string(),
text: short_name.to_string(),
}]),
definition,
data_type: None, };
let embedded_data_spec = EmbeddedDataSpecification {
data_specification: Reference {
ref_type: ReferenceType::ExternalReference,
keys: vec![Key {
key_type: KeyType::GlobalReference,
value:
"http://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/0"
.to_string(),
}],
},
data_specification_content: data_spec_content,
};
ConceptDescription {
id: metadata.urn.clone(),
id_short: Some(short_name.to_string()),
model_type: "ConceptDescription".to_string(),
embedded_data_specifications: Some(vec![embedded_data_spec]),
}
}