diff --git a/lib/src/aasStore.ts b/lib/src/aasHelper.ts similarity index 50% rename from lib/src/aasStore.ts rename to lib/src/aasHelper.ts index c0e9c86..3fcd38b 100644 --- a/lib/src/aasStore.ts +++ b/lib/src/aasHelper.ts @@ -4,29 +4,9 @@ import { types, jsonization } from "@aas-core-works/aas-core3.0-typescript"; const GLOBALLY_IDENTIFIABLES = [types.KeyTypes.GlobalReference, types.KeyTypes.AssetAdministrationShell, types.KeyTypes.ConceptDescription, types.KeyTypes.Identifiable, types.KeyTypes.Submodel] -export default class AASStore { - private static instance: AASStore; - private envs: Array = []; +export default class AASHelper { - private constructor() { } - - public static getInstance(): AASStore { - if (!AASStore.instance) { - AASStore.instance = new AASStore(); - } - - return AASStore.instance; - } - - public addAAS(aas: types.Environment): void { - this.envs.push(aas); - } - - public getAll(): Array { - return this.envs; - } - - public addAASByPath(path: string): void { + public static readAASByPath(path: string): types.Environment { if (!path.endsWith(".json")) throw new Error("File must be a JSON file"); const file = readFileSync(path, { encoding: "utf-8" }); @@ -35,73 +15,26 @@ export default class AASStore { const aasJson = jsonization.environmentFromJsonable(aas); if (aasJson.error) throw aasJson.error; - this.addAAS(aasJson.mustValue()); + return aasJson.mustValue(); } - public addAllAASFromPath(path: string): void { - const errors: any[] = []; - - this.getAllAASFilePaths(path).forEach(file => { + public static readAllAASFromPath(path: string): types.Environment[] { + + return AASHelper.getAllAASFilePaths(path).map(file => { try { - this.addAASByPath(file); + return AASHelper.readAASByPath(file); } catch (e) { - errors.push(e); + console.log(`Error while reading AAS from ${file}`, e); + return null; } - }); - - throw errors; + }).filter(aas => aas !== null) as types.Environment[]; } - private getAllAASFilePaths(aasPath: string): string[] { + private static getAllAASFilePaths(aasPath: string): string[] { const files = readdirSync(aasPath); return files.filter(file => file.endsWith(".json")).map(file => `${aasPath}/${file}`); } - public findAASById(id: string): types.AssetAdministrationShell | null { - for (const env of this.envs) { - const aas = AASStore.findAASById(env, id); - if(aas) return aas; - } - - return null; - } - - public findSMById(id: string): types.Submodel | null { - for (const env of this.envs) { - const sm = AASStore.findSMById(env, id); - if (sm) return sm; - } - - return null; - } - - public findSMByIdShort(id: string): types.Submodel | null { - for (const env of this.envs) { - const sm = AASStore.findSMByIdShort(env, id); - if (sm) return sm; - } - - return null; - } - - public resolveReference(ref: types.Reference): types.Class | null { - for (const env of this.envs) { - const e = AASStore.resolveReference(env, ref); - if (e !== null) return e; - } - - return null; - } - - public findElement(checkFunction: (element: types.Class) => boolean): types.Class | null { - for (const env of this.envs) { - const e = AASStore.findElement(env, checkFunction) - if (e !== null) return e; - } - - return null; - } - public static findAASById(environment: types.Environment, id: string): types.AssetAdministrationShell | null { if (environment.assetAdministrationShells === null) return null; @@ -128,9 +61,9 @@ export default class AASStore { if (current === null && !GLOBALLY_IDENTIFIABLES.includes(key.type)) break; if (current === null) { - current = AASStore.findElement(env, element => (element as any).id === key.value) + current = AASHelper.findElement(env, element => (element as any).id === key.value) } else { - current = AASStore.findElement(current, element => (element as any).idShort === key.value) + current = AASHelper.findElement(current, element => (element as any).idShort === key.value) } } diff --git a/lib/src/example_modules/httpInterfaceServer.ts b/lib/src/example_modules/httpInterfaceServer.ts index 1c30712..a000092 100644 --- a/lib/src/example_modules/httpInterfaceServer.ts +++ b/lib/src/example_modules/httpInterfaceServer.ts @@ -3,10 +3,9 @@ import express from "express"; import { jsonization, types } from "@aas-core-works/aas-core3.0-typescript"; import AASInterfaceServer from "../server"; import type { OnRequestCallback, Request } from "types/requests"; -import AASStore from "../aasStore"; -type Config = { - bindName: string, +export type Config = { + bindAddress: string, bindPort: number, } @@ -30,8 +29,8 @@ export default class HTTPInterfaceServer extends AASInterfaceServer { } public run(): void { - this.server = this.app.listen(this.config.bindPort, this.config.bindName, () => { - console.log(`Listening on ${this.config.bindName}:${this.config.bindPort}`); + this.server = this.app.listen(this.config.bindPort, this.config.bindAddress, () => { + console.log(`Listening on ${this.config.bindAddress}:${this.config.bindPort}`); }); } diff --git a/lib/src/index.ts b/lib/src/index.ts index 75dd4bc..70d0196 100644 --- a/lib/src/index.ts +++ b/lib/src/index.ts @@ -1,18 +1,11 @@ -import AASStore from "./aasStore"; -import type { SubmodelElementCollection, SubmodelElementList, ReferenceElement, Reference } from "@aas-core-works/aas-core3.0-typescript/dist/types/types"; +import AASHelper from "./aasHelper"; +import MultiMessageBroker from "./multimessageBroker"; +import HTTPInterfaceServer, { Config } from "./example_modules/httpInterfaceServer"; -const store = AASStore.getInstance(); -store.addAASByPath("../AID-AIMC-Full-Example-20230911T1608.json"); +const aas = AASHelper.readAASByPath("../AID-AIMC-Full-Example-20230911T1608.json"); -const aid = store.findSMByIdShort("AssetInterfacesDescription"); -const aimc = store.findSMByIdShort("AssetInterfacesMappingConfiguration"); - -if (!aid || !aimc) throw new Error("Invalid AAS file"); - -const AIMCEndpointRef = (((aimc.submodelElements?.at(0) as SubmodelElementList) - .value?.at(0) as SubmodelElementCollection) - .value?.at(0) as ReferenceElement) - .value as Reference; - -// console.log(store.resolveReference(AIMCEndpointRef)); +const broker = new MultiMessageBroker(); +broker.registerAAS({ aas, serverInterfaces: { serverInterface: HTTPInterfaceServer, config: { bindPort: 3000, bindAddress: "0.0.0.0" } } }); +broker.prepare(); +broker.start(); diff --git a/lib/src/multimessageBroker.ts b/lib/src/multimessageBroker.ts index 7e9858f..89c464f 100644 --- a/lib/src/multimessageBroker.ts +++ b/lib/src/multimessageBroker.ts @@ -2,57 +2,70 @@ import { types } from "@aas-core-works/aas-core3.0-typescript"; import type AASInterfaceServer from "server"; import type { Request } from "types/requests"; import type InterfaceConnectionObject from "interfaceConnectionObject"; -import AASStore from "aasStore"; +import AASHelper from "./aasHelper"; +import type { AIMCMapper } from "./parser/AIMCMapper"; +import { AssetInterfaceDescription } from "types/aidConf"; +import AIDParser from "./parser/AIDParser"; -class MultiMessageBroker { - private store = AASStore.getInstance(); +type AASRegistration = { + aas: types.Environment, + serverInterfaces: ServerInterfaceEntry | ServerInterfaceEntry[], +} - private serverInterfaces: Array = []; - private serverInterfaceConfigs: Array = []; - private serverInstances: Array|null> = []; +type AASRegistrationPrepared = AASRegistration & { + serverInstaces?: AASInterfaceServer[]; + connectorInterfaces?: InterfaceConnectionObject[]; + mappingConfiguration?: AIMCMapper; +} - private connectorInterfaces: Array = []; - private connectorConfigs: Array = []; - private connectorInstances: Array|null> = []; +type ServerInterfaceEntry = { + serverInterface: typeof AASInterfaceServer, + config: ConfigInterface +} + +export default class MultiMessageBroker { + private prepared: boolean = false; + + private aasRegistrations: AASRegistrationPrepared[] = []; public constructor() {} - public registerServerInterface(serverInterface: typeof AASInterfaceServer, config: any): void { - this.serverInterfaces.push(serverInterface); - this.serverInterfaceConfigs.push(config); + public registerAAS(registration: AASRegistration): void { + this.aasRegistrations.push(registration as AASRegistrationPrepared); } - public registerConnectorInterface(connectorInterface: typeof InterfaceConnectionObject, config: any): void { - this.connectorInterfaces.push(connectorInterface); - this.connectorConfigs.push(config); + public prepare(): void { + for (const registration of this.aasRegistrations) { + + if (!Array.isArray(registration.serverInterfaces)) registration.serverInterfaces = [registration.serverInterfaces]; + + registration.serverInstaces = registration.serverInterfaces.map(serverInterface => { + try { + // @ts-ignore + const server = new serverInterface.serverInterface(serverInterface.config, registration.aas, this.onInterfaceRequest.bind(this)); + server.prepare(); + return server; + } catch (e) { + console.error("Error while creating server interface instance", e); + return null; + } + }).filter(i => i !== null); + + const interfaceDescription = AIDParser.parse(registration.aas); + + // TODO + // Create connectors and mapping configuration + } + + this.prepared = true; } - private createServerInstances(aas: types.Environment): void { - this.serverInstances = this.serverInterfaces.map((serverInterface, index) => { - try { - // @ts-ignore - return new serverInterface(this.serverInterfaceConfigs[index], aas, this.onInterfaceRequest); - } catch (e) { - console.error(`Error while creating server instance for ${serverInterface.name}: ${e}`); - return null; - } - }); - } + public start(): void { + if (!this.prepared) this.prepare(); - private createConnectorInstances(): void { - this.connectorInstances = this.connectorInterfaces.map((connectorInterface, index) => { - try { - // @ts-ignore - return new connectorInterface(this.connectorConfigs[index], this.onConnectorEvent); - } catch (e) { - console.error(`Error while creating connector instance for ${connectorInterface.name}: ${e}`); - return null; - } - }); - } - - private connectorConfigsFromAAS(aas: types.Environment): void { - // TODO + for (const registration of this.aasRegistrations) { + registration.serverInstaces?.forEach(server => server?.run()); + } } private onInterfaceRequest(request: Request) { @@ -60,8 +73,10 @@ class MultiMessageBroker { } private onConnectorEvent(response: any) { - this.serverInstances.forEach(server => { - if (server) server.notify(response); - }); + for (const aas of this.aasRegistrations) { + for (const server of aas.serverInstaces ?? []) { + server?.notify(response); + } + } } } \ No newline at end of file diff --git a/lib/src/parser/AIDParser.ts b/lib/src/parser/AIDParser.ts index 4570ce6..8bdf24f 100644 --- a/lib/src/parser/AIDParser.ts +++ b/lib/src/parser/AIDParser.ts @@ -1,12 +1,12 @@ 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 { AvailableEndpoint, endpointAvailable } from "types/common"; -import AASStore from "aasStore"; +import { AvailableEndpoint, endpointAvailable } from "../types/common"; +import AASHelper from "../aasHelper"; export default class AIDParser { public static parse(env: aasCore.types.Environment): AssetInterfaceDescription | null { - const sm = AASStore.findSMByIdShort(env, "AssetInterfaceDescription"); + const sm = AASHelper.findSMByIdShort(env, "AssetInterfaceDescription"); if (sm === null || sm.submodelElements === null) return null; const parsed = {} as AssetInterfaceDescription; @@ -28,11 +28,11 @@ export default class AIDParser { } private static parseAIDEntry(entry: aasCore.types.SubmodelElementCollection, endpointProtocol?: AvailableEndpoint): InterfaceDescription | null { - const title = AASStore.findElement(entry, element => (element as any).idShort.toLocaleLowerCase() === "title"); + const title = AASHelper.findElement(entry, element => (element as any).idShort.toLocaleLowerCase() === "title"); const titleString = title !== null && aasCore.types.isProperty(title) && title.value ? title.value : ""; - const ep = AASStore.findElement(entry, element => (element as any).idShort.toLocaleLowerCase() === "endpointmetadata"); - const im = AASStore.findElement(entry, element => (element as any).idShort.toLocaleLowerCase() === "interfacemetadata"); + const ep = AASHelper.findElement(entry, element => (element as any).idShort.toLocaleLowerCase() === "endpointmetadata"); + const im = AASHelper.findElement(entry, element => (element as any).idShort.toLocaleLowerCase() === "interfacemetadata"); if (ep === null || !aasCore.types.isSubmodelElementCollection(ep) || !ep.value || im === null || !aasCore.types.isSubmodelElementCollection(im) || !im.value) return null; const parsedEp = AIDParser.parseEndpointMetaData(ep, endpointProtocol); @@ -53,7 +53,7 @@ export default class AIDParser { const parsed = {} as EndpointMetadata; for (const key of ["base", "contentType"]) { - const prop = AASStore.findElement(endpoint, element => (element as any).idShort.toLocaleLowerCase() === key.toLocaleLowerCase()); + const prop = AASHelper.findElement(endpoint, element => (element as any).idShort.toLocaleLowerCase() === key.toLocaleLowerCase()); if (prop === null || !aasCore.types.isProperty(prop) || !prop.value) return null; // Mandatory else parsed[key] = prop.value; } @@ -73,7 +73,7 @@ export default class AIDParser { events: [] } as InterfaceMetadata; - const prop = AASStore.findElement(interfaceMeta, element => (element as any).idShort.toLocaleLowerCase() === "properties"); + const prop = AASHelper.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) { if (!aasCore.types.isSubmodelElementCollection(element)) continue; @@ -82,7 +82,7 @@ export default class AIDParser { parsed.properties.push(parsedProp); } - const actions = AASStore.findElement(interfaceMeta, element => (element as any).idShort.toLocaleLowerCase() === "actions"); + const actions = AASHelper.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) { if (!aasCore.types.isSubmodelElementCollection(element)) continue; @@ -106,7 +106,7 @@ export default class AIDParser { else parsed[key] = prop.value; } */ - const forms = AASStore.findElement(prop, element => (element as any).idShort.toLocaleLowerCase() === "forms"); + const forms = AASHelper.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) { if (!aasCore.types.isSubmodelElementCollection(element)) continue; @@ -127,7 +127,7 @@ export default class AIDParser { const parsed = {} as any; for (const key of ["href", "contentType"]) { - const prop = AASStore.findElement(form, element => (element as any).idShort.toLocaleLowerCase() === key.toLocaleLowerCase()); + const prop = AASHelper.findElement(form, element => (element as any).idShort.toLocaleLowerCase() === key.toLocaleLowerCase()); if (prop === null || !aasCore.types.isProperty(prop) || !prop.value) return null; // Mandatory else parsed[key] = prop.value; } diff --git a/lib/src/types/aimcConf.ts b/lib/src/types/aimcConf.ts index f081bff..d414e53 100644 --- a/lib/src/types/aimcConf.ts +++ b/lib/src/types/aimcConf.ts @@ -3,12 +3,12 @@ import { AvailableEndpoint } from "./common"; export type AssetInterfaceMappingConfiguration = Record; -type MappingConfEntry = { +export type MappingConfEntry = { EndpointMetaDataReference: types.ReferenceElement; MappingConfiguration: MappingConfiguration; } -type MappingConfiguration = { +export type MappingConfiguration = { InterfaceMetaDataReference: types.ReferenceElement; SourceSinkMappings: types.RelationshipElement[]; } \ No newline at end of file