It lives!

This commit is contained in:
Daniel Kluge
2023-10-22 18:44:32 +02:00
parent 5296f528c3
commit 0fd7b81526
6 changed files with 96 additions and 156 deletions
+12 -79
View File
@@ -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] const GLOBALLY_IDENTIFIABLES = [types.KeyTypes.GlobalReference, types.KeyTypes.AssetAdministrationShell, types.KeyTypes.ConceptDescription, types.KeyTypes.Identifiable, types.KeyTypes.Submodel]
export default class AASStore { export default class AASHelper {
private static instance: AASStore;
private envs: Array<types.Environment> = [];
private constructor() { } public static readAASByPath(path: string): types.Environment {
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<types.Environment> {
return this.envs;
}
public addAASByPath(path: string): void {
if (!path.endsWith(".json")) throw new Error("File must be a JSON file"); if (!path.endsWith(".json")) throw new Error("File must be a JSON file");
const file = readFileSync(path, { encoding: "utf-8" }); const file = readFileSync(path, { encoding: "utf-8" });
@@ -35,73 +15,26 @@ export default class AASStore {
const aasJson = jsonization.environmentFromJsonable(aas); const aasJson = jsonization.environmentFromJsonable(aas);
if (aasJson.error) throw aasJson.error; if (aasJson.error) throw aasJson.error;
this.addAAS(aasJson.mustValue()); return aasJson.mustValue();
} }
public addAllAASFromPath(path: string): void { public static readAllAASFromPath(path: string): types.Environment[] {
const errors: any[] = [];
this.getAllAASFilePaths(path).forEach(file => { return AASHelper.getAllAASFilePaths(path).map(file => {
try { try {
this.addAASByPath(file); return AASHelper.readAASByPath(file);
} catch (e) { } catch (e) {
errors.push(e); console.log(`Error while reading AAS from ${file}`, e);
return null;
} }
}); }).filter(aas => aas !== null) as types.Environment[];
throw errors;
} }
private getAllAASFilePaths(aasPath: string): string[] { private static getAllAASFilePaths(aasPath: string): string[] {
const files = readdirSync(aasPath); const files = readdirSync(aasPath);
return files.filter(file => file.endsWith(".json")).map(file => `${aasPath}/${file}`); 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 { public static findAASById(environment: types.Environment, id: string): types.AssetAdministrationShell | null {
if (environment.assetAdministrationShells === null) return 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 && !GLOBALLY_IDENTIFIABLES.includes(key.type)) break;
if (current === null) { 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 { } else {
current = AASStore.findElement(current, element => (element as any).idShort === key.value) current = AASHelper.findElement(current, element => (element as any).idShort === key.value)
} }
} }
@@ -3,10 +3,9 @@ import express from "express";
import { jsonization, types } from "@aas-core-works/aas-core3.0-typescript"; import { jsonization, types } from "@aas-core-works/aas-core3.0-typescript";
import AASInterfaceServer from "../server"; import AASInterfaceServer from "../server";
import type { OnRequestCallback, Request } from "types/requests"; import type { OnRequestCallback, Request } from "types/requests";
import AASStore from "../aasStore";
type Config = { export type Config = {
bindName: string, bindAddress: string,
bindPort: number, bindPort: number,
} }
@@ -30,8 +29,8 @@ export default class HTTPInterfaceServer extends AASInterfaceServer<Config> {
} }
public run(): void { public run(): void {
this.server = this.app.listen(this.config.bindPort, this.config.bindName, () => { this.server = this.app.listen(this.config.bindPort, this.config.bindAddress, () => {
console.log(`Listening on ${this.config.bindName}:${this.config.bindPort}`); console.log(`Listening on ${this.config.bindAddress}:${this.config.bindPort}`);
}); });
} }
+8 -15
View File
@@ -1,18 +1,11 @@
import AASStore from "./aasStore"; import AASHelper from "./aasHelper";
import type { SubmodelElementCollection, SubmodelElementList, ReferenceElement, Reference } from "@aas-core-works/aas-core3.0-typescript/dist/types/types"; import MultiMessageBroker from "./multimessageBroker";
import HTTPInterfaceServer, { Config } from "./example_modules/httpInterfaceServer";
const store = AASStore.getInstance(); const aas = AASHelper.readAASByPath("../AID-AIMC-Full-Example-20230911T1608.json");
store.addAASByPath("../AID-AIMC-Full-Example-20230911T1608.json");
const aid = store.findSMByIdShort("AssetInterfacesDescription"); const broker = new MultiMessageBroker();
const aimc = store.findSMByIdShort("AssetInterfacesMappingConfiguration"); broker.registerAAS({ aas, serverInterfaces: { serverInterface: HTTPInterfaceServer, config: { bindPort: 3000, bindAddress: "0.0.0.0" } } });
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));
broker.prepare();
broker.start();
+52 -37
View File
@@ -2,57 +2,70 @@ import { types } from "@aas-core-works/aas-core3.0-typescript";
import type AASInterfaceServer from "server"; import type AASInterfaceServer from "server";
import type { Request } from "types/requests"; import type { Request } from "types/requests";
import type InterfaceConnectionObject from "interfaceConnectionObject"; 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 { type AASRegistration = {
private store = AASStore.getInstance(); aas: types.Environment,
serverInterfaces: ServerInterfaceEntry<any> | ServerInterfaceEntry<any>[],
}
private serverInterfaces: Array<typeof AASInterfaceServer> = []; type AASRegistrationPrepared = AASRegistration & {
private serverInterfaceConfigs: Array<any> = []; serverInstaces?: AASInterfaceServer<any>[];
private serverInstances: Array<AASInterfaceServer<typeof this.serverInterfaceConfigs[number]>|null> = []; connectorInterfaces?: InterfaceConnectionObject<any>[];
mappingConfiguration?: AIMCMapper;
}
private connectorInterfaces: Array<typeof InterfaceConnectionObject> = []; type ServerInterfaceEntry<ConfigInterface> = {
private connectorConfigs: Array<any> = []; serverInterface: typeof AASInterfaceServer<ConfigInterface>,
private connectorInstances: Array<InterfaceConnectionObject<typeof this.connectorConfigs[keyof typeof this.connectorConfigs]>|null> = []; config: ConfigInterface
}
export default class MultiMessageBroker {
private prepared: boolean = false;
private aasRegistrations: AASRegistrationPrepared[] = [];
public constructor() {} public constructor() {}
public registerServerInterface(serverInterface: typeof AASInterfaceServer, config: any): void { public registerAAS(registration: AASRegistration): void {
this.serverInterfaces.push(serverInterface); this.aasRegistrations.push(registration as AASRegistrationPrepared);
this.serverInterfaceConfigs.push(config);
} }
public registerConnectorInterface(connectorInterface: typeof InterfaceConnectionObject, config: any): void { public prepare(): void {
this.connectorInterfaces.push(connectorInterface); for (const registration of this.aasRegistrations) {
this.connectorConfigs.push(config);
}
private createServerInstances(aas: types.Environment): void { if (!Array.isArray(registration.serverInterfaces)) registration.serverInterfaces = [registration.serverInterfaces];
this.serverInstances = this.serverInterfaces.map((serverInterface, index) => {
registration.serverInstaces = registration.serverInterfaces.map(serverInterface => {
try { try {
// @ts-ignore // @ts-ignore
return new serverInterface(this.serverInterfaceConfigs[index], aas, this.onInterfaceRequest); const server = new serverInterface.serverInterface<typeof serverInterface.config>(serverInterface.config, registration.aas, this.onInterfaceRequest.bind(this));
server.prepare();
return server;
} catch (e) { } catch (e) {
console.error(`Error while creating server instance for ${serverInterface.name}: ${e}`); console.error("Error while creating server interface instance", e);
return null; return null;
} }
}); }).filter(i => i !== null);
}
private createConnectorInstances(): void { const interfaceDescription = AIDParser.parse(registration.aas);
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 // TODO
// Create connectors and mapping configuration
}
this.prepared = true;
}
public start(): void {
if (!this.prepared) this.prepare();
for (const registration of this.aasRegistrations) {
registration.serverInstaces?.forEach(server => server?.run());
}
} }
private onInterfaceRequest(request: Request) { private onInterfaceRequest(request: Request) {
@@ -60,8 +73,10 @@ class MultiMessageBroker {
} }
private onConnectorEvent(response: any) { private onConnectorEvent(response: any) {
this.serverInstances.forEach(server => { for (const aas of this.aasRegistrations) {
if (server) server.notify(response); for (const server of aas.serverInstaces ?? []) {
}); server?.notify(response);
}
}
} }
} }
+11 -11
View File
@@ -1,12 +1,12 @@
import * as aasCore from "@aas-core-works/aas-core3.0-typescript"; 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 { AssetInterfaceDescription, EndpointMetadata, InterfaceDescription, InterfaceMetadata, InterfaceAction, InterfaceEvent, InterfaceProperty, InterfacePropertyForm } from "types/aidConf";
import { AvailableEndpoint, endpointAvailable } from "types/common"; import { AvailableEndpoint, endpointAvailable } from "../types/common";
import AASStore from "aasStore"; import AASHelper from "../aasHelper";
export default class AIDParser { export default class AIDParser {
public static parse(env: aasCore.types.Environment): AssetInterfaceDescription | null { 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; if (sm === null || sm.submodelElements === null) return null;
const parsed = {} as AssetInterfaceDescription; const parsed = {} as AssetInterfaceDescription;
@@ -28,11 +28,11 @@ export default class AIDParser {
} }
private static parseAIDEntry(entry: aasCore.types.SubmodelElementCollection, endpointProtocol?: AvailableEndpoint): InterfaceDescription | null { 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 titleString = title !== null && aasCore.types.isProperty(title) && title.value ? title.value : "";
const ep = AASStore.findElement(entry, element => (element as any).idShort.toLocaleLowerCase() === "endpointmetadata"); const ep = AASHelper.findElement(entry, element => (element as any).idShort.toLocaleLowerCase() === "endpointmetadata");
const im = AASStore.findElement(entry, element => (element as any).idShort.toLocaleLowerCase() === "interfacemetadata"); 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; 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); const parsedEp = AIDParser.parseEndpointMetaData(ep, endpointProtocol);
@@ -53,7 +53,7 @@ export default class AIDParser {
const parsed = {} as EndpointMetadata; const parsed = {} as EndpointMetadata;
for (const key of ["base", "contentType"]) { 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 if (prop === null || !aasCore.types.isProperty(prop) || !prop.value) return null; // Mandatory
else parsed[key] = prop.value; else parsed[key] = prop.value;
} }
@@ -73,7 +73,7 @@ export default class AIDParser {
events: [] events: []
} as InterfaceMetadata; } 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 if (prop === null || !aasCore.types.isSubmodelElementList(prop) || !prop.value) return null; // Mandatory
for (const element of prop.value) { for (const element of prop.value) {
if (!aasCore.types.isSubmodelElementCollection(element)) continue; if (!aasCore.types.isSubmodelElementCollection(element)) continue;
@@ -82,7 +82,7 @@ export default class AIDParser {
parsed.properties.push(parsedProp); 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 if (actions === null || !aasCore.types.isSubmodelElementList(actions) || !actions.value) return null; // Mandatory
for (const element of actions.value) { for (const element of actions.value) {
if (!aasCore.types.isSubmodelElementCollection(element)) continue; if (!aasCore.types.isSubmodelElementCollection(element)) continue;
@@ -106,7 +106,7 @@ export default class AIDParser {
else parsed[key] = prop.value; 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 if (forms === null || !aasCore.types.isSubmodelElementList(forms) || !forms.value) return null; // Mandatory
for (const element of forms.value) { for (const element of forms.value) {
if (!aasCore.types.isSubmodelElementCollection(element)) continue; if (!aasCore.types.isSubmodelElementCollection(element)) continue;
@@ -127,7 +127,7 @@ export default class AIDParser {
const parsed = {} as any; const parsed = {} as any;
for (const key of ["href", "contentType"]) { 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 if (prop === null || !aasCore.types.isProperty(prop) || !prop.value) return null; // Mandatory
else parsed[key] = prop.value; else parsed[key] = prop.value;
} }
+2 -2
View File
@@ -3,12 +3,12 @@ import { AvailableEndpoint } from "./common";
export type AssetInterfaceMappingConfiguration = Record<AvailableEndpoint, MappingConfEntry[]>; export type AssetInterfaceMappingConfiguration = Record<AvailableEndpoint, MappingConfEntry[]>;
type MappingConfEntry = { export type MappingConfEntry = {
EndpointMetaDataReference: types.ReferenceElement; EndpointMetaDataReference: types.ReferenceElement;
MappingConfiguration: MappingConfiguration; MappingConfiguration: MappingConfiguration;
} }
type MappingConfiguration = { export type MappingConfiguration = {
InterfaceMetaDataReference: types.ReferenceElement; InterfaceMetaDataReference: types.ReferenceElement;
SourceSinkMappings: types.RelationshipElement[]; SourceSinkMappings: types.RelationshipElement[];
} }