diff --git a/lib/src/helper/traverser.ts b/lib/src/helper/traverser.ts index 717f45a..86c6b7f 100644 --- a/lib/src/helper/traverser.ts +++ b/lib/src/helper/traverser.ts @@ -1,5 +1,7 @@ import { types } from "@aas-core-works/aas-core3.0-typescript"; +import { RelationshipElement } from "@aas-core-works/aas-core3.0-typescript/dist/types/types"; +import { ResolvedRelationshipElement } from "types/common"; const GLOBALLY_IDENTIFIABLES = [types.KeyTypes.GlobalReference, types.KeyTypes.AssetAdministrationShell, types.KeyTypes.ConceptDescription, types.KeyTypes.Identifiable, types.KeyTypes.Submodel] @@ -40,6 +42,14 @@ export default class Traverser { return current; } + public static resolveRelationship(env: types.Environment, relationship: RelationshipElement): ResolvedRelationshipElement | null { + const first = Traverser.resolveReference(env, relationship.first); + const second = Traverser.resolveReference(env, relationship.second); + + if (first === null || second === null) return null; + return { first, second }; + } + public static traverseByShortIds(start: types.Class, shortIds: string[]): types.Class | null { let current: types.Class | null = start; for (const shortId of shortIds) { diff --git a/lib/src/multimessageBroker.ts b/lib/src/multimessageBroker.ts index 0b2a37a..5fb9f54 100644 --- a/lib/src/multimessageBroker.ts +++ b/lib/src/multimessageBroker.ts @@ -4,7 +4,7 @@ import type { Request } from "types/requests"; import type InterfaceConnectionObject from "interfaceConnectionObject"; import Traverser from "./helper/traverser"; import type { AIMCMapper } from "./parser/AIMCMapper"; -import { AssetInterfaceDescription } from "types/aidConf"; +import { AssetInterfacesDescription } from "types/aidConf"; import AIDParser from "./parser/AIDParser"; type AASRegistration = { diff --git a/lib/src/parser/AIDParser.ts b/lib/src/parser/AIDParser.ts index 3f6530f..dc8924d 100644 --- a/lib/src/parser/AIDParser.ts +++ b/lib/src/parser/AIDParser.ts @@ -1,15 +1,15 @@ import * as aasCore from "@aas-core-works/aas-core3.0-typescript"; -import type { AssetInterfaceDescription, EndpointMetadata, InterfaceDescription, InterfaceMetadata, InterfaceAction, InterfaceEvent, InterfaceProperty, InterfacePropertyForm } from "types/aidConf"; +import type { AssetInterfacesDescription, EndpointMetadata, InterfaceDescription, InterfaceMetadata, InterfaceAction, InterfaceEvent, InterfaceProperty, InterfacePropertyForm } from "types/aidConf"; import { AvailableEndpoint, endpointAvailable } from "../types/common"; import Traverser from "../helper/traverser"; export default class AIDParser { - public static parse(env: aasCore.types.Environment): AssetInterfaceDescription | null { + public static parse(env: aasCore.types.Environment): AssetInterfacesDescription | null { const sm = Traverser.findSMByIdShort(env, "AssetInterfaceDescription"); if (sm === null || sm.submodelElements === null) return null; - const parsed = {} as AssetInterfaceDescription; + const parsed = {} as AssetInterfacesDescription; sm.submodelElements.forEach(e => { if (!aasCore.types.isSubmodelElementList(e) || !e.idShort || !e.value) return; @@ -75,7 +75,7 @@ export default class AIDParser { const prop = Traverser.findElement(interfaceMeta, element => (element as any).idShort.toLocaleLowerCase() === "properties"); if (prop === null || !aasCore.types.isSubmodelElementList(prop) || !prop.value) return null; // Mandatory - for (const element of prop.value) { + for (const element of prop.overValueOrEmpty()) { if (!aasCore.types.isSubmodelElementCollection(element)) continue; const parsedProp = AIDParser.parseInterfaceMetadataProperty(element, endpointProtocol); if (parsedProp === null) continue; @@ -84,7 +84,7 @@ export default class AIDParser { const actions = Traverser.findElement(interfaceMeta, element => (element as any).idShort.toLocaleLowerCase() === "actions"); if (actions === null || !aasCore.types.isSubmodelElementList(actions) || !actions.value) return null; // Mandatory - for (const element of actions.value) { + for (const element of actions.overValueOrEmpty()) { if (!aasCore.types.isSubmodelElementCollection(element)) continue; const parsedAction = AIDParser.parseInterfaceMetadataProperty(element, endpointProtocol); if (parsedAction === null) continue; @@ -95,7 +95,7 @@ export default class AIDParser { } public static parseInterfaceMetadataProperty(prop: aasCore.types.SubmodelElementCollection, endpointProtocol?: AvailableEndpoint): InterfaceProperty | null { - if (!prop.value || prop.idShort?.toLocaleLowerCase() !== "properties") return null; + if (!aasCore.types.isSubmodelElementCollection(prop) || !prop.value || prop.idShort?.toLocaleLowerCase() !== "properties") return null; const parsed = {} as InterfaceProperty; @@ -106,19 +106,21 @@ export default class AIDParser { else parsed[key] = prop.value; } */ + const parsedForms = [] as InterfacePropertyForm[]; + const forms = Traverser.findElement(prop, element => (element as any).idShort.toLocaleLowerCase() === "forms"); if (forms === null || !aasCore.types.isSubmodelElementList(forms) || !forms.value) return null; // Mandatory - for (const element of forms.value) { + for (const element of forms.overValueOrEmpty()) { if (!aasCore.types.isSubmodelElementCollection(element)) continue; const parsedForm = AIDParser.parseInterfaceMetadataPropertyForm(element, endpointProtocol); if (parsedForm === null) continue; - parsed.forms.push(parsedForm); + parsedForms.push(parsedForm); } // TODO // Other values based on endpoint protocol - return parsed; + return { ...parsed, forms: parsedForms } as InterfaceProperty; } private static parseInterfaceMetadataPropertyForm(form: aasCore.types.SubmodelElementCollection, endpointProtocol?: AvailableEndpoint): InterfacePropertyForm | null { @@ -143,7 +145,7 @@ export default class AIDParser { return null; } - public static parseInterfaceMetadataEvent(event: aasCore.types.SubmodelElementCollection, endpointProtocol?: AvailableEndpoint): InterfaceAction | null { + public static parseInterfaceMetadataEvent(event: aasCore.types.SubmodelElementCollection, endpointProtocol?: AvailableEndpoint): InterfaceEvent | null { // TODO return null; } diff --git a/lib/src/parser/AIMCMapper.ts b/lib/src/parser/AIMCMapper.ts index 2f3b4b9..2d2012d 100644 --- a/lib/src/parser/AIMCMapper.ts +++ b/lib/src/parser/AIMCMapper.ts @@ -1,3 +1,60 @@ +import { types } from "@aas-core-works/aas-core3.0-typescript"; +import { ConnectionConfiguration, ElementMap } from "types/aimcConf"; +import AIMCParser from "./AIMCParser"; +import Traverser from "helper/traverser"; +import AIDParser from "./AIDParser"; +import { SubmodelElementCollection } from "@aas-core-works/aas-core3.0-typescript/dist/types/types"; + export class AIMCMapper { + + private map: ElementMap = {}; + public constructor(private readonly env: types.Environment) { + this.generate(); + } + + private generate(): void { + const aimc = AIMCParser.parse(this.env); + if (aimc === null) return; + + for (const endpointConf of Object.values(aimc)) { + for (const entry of endpointConf) { + const ep = AIDParser.parseEndpointMetaData(entry.EndpointMetaDataReference); + if (ep === null) continue; + + for (const mapping of entry.MappingConfiguration) { + //const ifm = AIDParser.parseInterfaceMetaData(mapping.InterfaceMetaDataReference); + //if (ifm === null) continue; + // Why do we need this? We already have the interface metadata reference in every ssm + + for (const ssm of mapping.SourceSinkMappings) { + const resolved = Traverser.resolveRelationship(this.env, ssm); + if (resolved === null) continue; + + const path = ssm.first.keys.join("/"); + const parsedPropConf = AIDParser.parseInterfaceMetadataProperty(resolved.second as SubmodelElementCollection); + if (parsedPropConf === null) continue; + + this.map[path] = { + ...ep, + ...parsedPropConf + } + } + + } + } + } + } + + public getMap(): ElementMap { + return this.map; + } + + public get(path: string[]): ConnectionConfiguration | undefined { + return this.map[path.join("/")]; + } + + public getByIdShort(idShort: string): ConnectionConfiguration[] { + return Object.keys(this.map).filter(e => e.endsWith(idShort)).map(e => this.map[e]); + } } \ No newline at end of file diff --git a/lib/src/parser/AIMCParser.ts b/lib/src/parser/AIMCParser.ts new file mode 100644 index 0000000..4c55335 --- /dev/null +++ b/lib/src/parser/AIMCParser.ts @@ -0,0 +1,80 @@ +import * as aasCore from "@aas-core-works/aas-core3.0-typescript"; +import Traverser from "helper/traverser"; +import { AssetInterfacesMappingConfiguration, MappingConfEntry, MappingConfiguration } from "types/aimcConf"; +import { endpointAvailable, ResolvedRelationshipElement } from "types/common"; + +export default class AIMCParser { + + public static parse(env: aasCore.types.Environment): AssetInterfacesMappingConfiguration | null { + const sm = Traverser.findSMByIdShort(env, "AssetInterfaceMappingConfiguration"); + if (sm === null || sm.submodelElements === null) return null; + + const parsed = {} as AssetInterfacesMappingConfiguration; + + sm.submodelElements.forEach(e => { + if (!aasCore.types.isSubmodelElementList(e) || !e.idShort || !e.value) return; + + const endpointProto = endpointAvailable(e.idShort); + if (!endpointProto) return; + + parsed[endpointProto] = e.value.map(element => { + if (!aasCore.types.isSubmodelElementCollection(element)) return null; + + return AIMCParser.parseAIMCEntry(element, env); + }).filter(e => e !== null) as MappingConfEntry[]; + }); + + return parsed; + } + + public static parseAIMCEntry(entry: aasCore.types.SubmodelElementCollection, environment: aasCore.types.Environment): MappingConfEntry | null { + const ep = Traverser.findElement(entry, element => (element as any).idShort.toLocaleLowerCase() === "endpointmetadatareference"); + const mc = Traverser.findElement(entry, element => (element as any).idShort.toLocaleLowerCase() === "mappingconfiguration"); + if (ep === null || !aasCore.types.isReference(ep) || !ep.keys || !ep.keys[0] || !mc || !aasCore.types.isSubmodelElementList(mc) || !mc.value) return null; + + const resolvedEp = Traverser.resolveReference(environment, ep); + if (resolvedEp === null || !aasCore.types.isSubmodelElementCollection(resolvedEp) || resolvedEp.idShort?.toLocaleLowerCase() !== "endpointmetadatareference") return null; + + const parsedMc: (MappingConfiguration|null)[] = [] + for (const conf of mc.overValueOrEmpty()) { + if (!aasCore.types.isSubmodelElementCollection(conf)) continue; + const parsed = AIMCParser.parseMappingConfiguration(conf, environment); + if (parsed === null) continue; + parsedMc.push(parsed); + } + + return { + EndpointMetaDataReference: resolvedEp, + MappingConfiguration: parsedMc.filter(e => e !== null) as MappingConfiguration[] + }; + } + + public static parseMappingConfiguration(conf: aasCore.types.SubmodelElementCollection, env: aasCore.types.Environment): MappingConfiguration | null { + if (!conf.value) return null; + + const parsed = { + InterfaceMetaDataReference: null, + SourceSinkMappings: [] + } as any; + + const ifMetaRef = Traverser.findElement(conf, element => (element as any).idShort.toLocaleLowerCase() === "interfacemetadatareference"); + if (!ifMetaRef || !aasCore.types.isReference(ifMetaRef) || !ifMetaRef.keys) return null; + const ifMeta = Traverser.resolveReference(env, ifMetaRef); + if (!ifMeta || !aasCore.types.isSubmodelElementCollection(ifMeta) || ifMeta.idShort?.toLocaleLowerCase() !== "interfacemetadata") return null; + parsed.InterfaceMetaDataReference = ifMeta; + + const sourceSinkMappings = Traverser.findElement(conf, element => (element as any).idShort.toLocaleLowerCase() === "sourcesinkmappings"); + if (!sourceSinkMappings || !aasCore.types.isSubmodelElementList(sourceSinkMappings) || !sourceSinkMappings.value) return null; + + for (const ssm of sourceSinkMappings.overValueOrEmpty()) { + if (!aasCore.types.isRelationshipElement(ssm)) continue; + // No resolve, to allow for mapping + //const resolved = Traverser.resolveRelationship(env, ssm); + //if (resolved === null) continue; + + parsed.SourceSinkMappings.push(ssm as ResolvedRelationshipElement); + } + + return parsed as MappingConfiguration; + } +} \ No newline at end of file diff --git a/lib/src/types/aidConf.ts b/lib/src/types/aidConf.ts index a7a61ea..0fd046b 100644 --- a/lib/src/types/aidConf.ts +++ b/lib/src/types/aidConf.ts @@ -1,6 +1,6 @@ import { AvailableEndpoint } from "./common"; -export type AssetInterfaceDescription = { +export type AssetInterfacesDescription = { [key in AvailableEndpoint]: InterfaceDescription[]; }; diff --git a/lib/src/types/aimcConf.ts b/lib/src/types/aimcConf.ts index d414e53..659beef 100644 --- a/lib/src/types/aimcConf.ts +++ b/lib/src/types/aimcConf.ts @@ -1,14 +1,19 @@ import type { types } from "@aas-core-works/aas-core3.0-typescript"; -import { AvailableEndpoint } from "./common"; +import type { AvailableEndpoint } from "./common"; +import type { EndpointMetadata, InterfaceProperty } from "./aidConf"; -export type AssetInterfaceMappingConfiguration = Record; +export type AssetInterfacesMappingConfiguration = Record; export type MappingConfEntry = { - EndpointMetaDataReference: types.ReferenceElement; - MappingConfiguration: MappingConfiguration; + EndpointMetaDataReference:types.SubmodelElementCollection; + MappingConfiguration: MappingConfiguration[]; } export type MappingConfiguration = { - InterfaceMetaDataReference: types.ReferenceElement; + InterfaceMetaDataReference: types.SubmodelElementCollection; SourceSinkMappings: types.RelationshipElement[]; -} \ No newline at end of file +} + +export type ElementMap = Record; + +export type ConnectionConfiguration = EndpointMetadata & InterfaceProperty; // Wenn Transformationen implementiert werden, dann hier anpassen \ No newline at end of file diff --git a/lib/src/types/common.ts b/lib/src/types/common.ts index 1494d78..5b95e57 100644 --- a/lib/src/types/common.ts +++ b/lib/src/types/common.ts @@ -1,3 +1,5 @@ +import type { types } from "@aas-core-works/aas-core3.0-typescript"; + export const availableEndpoints = ["HTTP", "MQTT", "Modbus"] as const; export type AvailableEndpoint = typeof availableEndpoints[number]; @@ -5,4 +7,9 @@ export function endpointAvailable(endpoint: string): AvailableEndpoint | null { const idx = availableEndpoints.map(e => e.toLocaleLowerCase()).findIndex(e => e === endpoint.toLocaleLowerCase()); if (idx === -1) return null; return availableEndpoints[idx]; +} + +export type ResolvedRelationshipElement = { + first: types.Class; + second: types.Class; } \ No newline at end of file