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]
export default class AASStore {
private static instance: AASStore;
private envs: Array<types.Environment> = [];
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<types.Environment> {
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[] = [];
public static readAllAASFromPath(path: string): types.Environment[] {
this.getAllAASFilePaths(path).forEach(file => {
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)
}
}
@@ -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<Config> {
}
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}`);
});
}
+8 -15
View File
@@ -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();
+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 { 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<any> | ServerInterfaceEntry<any>[],
}
private serverInterfaces: Array<typeof AASInterfaceServer> = [];
private serverInterfaceConfigs: Array<any> = [];
private serverInstances: Array<AASInterfaceServer<typeof this.serverInterfaceConfigs[number]>|null> = [];
type AASRegistrationPrepared = AASRegistration & {
serverInstaces?: AASInterfaceServer<any>[];
connectorInterfaces?: InterfaceConnectionObject<any>[];
mappingConfiguration?: AIMCMapper;
}
private connectorInterfaces: Array<typeof InterfaceConnectionObject> = [];
private connectorConfigs: Array<any> = [];
private connectorInstances: Array<InterfaceConnectionObject<typeof this.connectorConfigs[keyof typeof this.connectorConfigs]>|null> = [];
type ServerInterfaceEntry<ConfigInterface> = {
serverInterface: typeof AASInterfaceServer<ConfigInterface>,
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) {
private createServerInstances(aas: types.Environment): void {
this.serverInstances = this.serverInterfaces.map((serverInterface, index) => {
if (!Array.isArray(registration.serverInterfaces)) registration.serverInterfaces = [registration.serverInterfaces];
registration.serverInstaces = registration.serverInterfaces.map(serverInterface => {
try {
// @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) {
console.error(`Error while creating server instance for ${serverInterface.name}: ${e}`);
console.error("Error while creating server interface instance", e);
return null;
}
});
}
}).filter(i => i !== null);
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;
}
});
}
const interfaceDescription = AIDParser.parse(registration.aas);
private connectorConfigsFromAAS(aas: types.Environment): void {
// 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) {
@@ -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);
}
}
}
}
+11 -11
View File
@@ -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;
}
+2 -2
View File
@@ -3,12 +3,12 @@ import { AvailableEndpoint } from "./common";
export type AssetInterfaceMappingConfiguration = Record<AvailableEndpoint, MappingConfEntry[]>;
type MappingConfEntry = {
export type MappingConfEntry = {
EndpointMetaDataReference: types.ReferenceElement;
MappingConfiguration: MappingConfiguration;
}
type MappingConfiguration = {
export type MappingConfiguration = {
InterfaceMetaDataReference: types.ReferenceElement;
SourceSinkMappings: types.RelationshipElement[];
}