Types and AIDParsing

This commit is contained in:
Daniel Kluge
2023-10-22 17:46:59 +02:00
parent f804729689
commit 5296f528c3
9 changed files with 348 additions and 33 deletions
+70 -15
View File
@@ -2,6 +2,8 @@
import { readdirSync, readFileSync } from "fs"; import { readdirSync, readFileSync } from "fs";
import { types, jsonization } from "@aas-core-works/aas-core3.0-typescript"; 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 { export default class AASStore {
private static instance: AASStore; private static instance: AASStore;
private envs: Array<types.Environment> = []; private envs: Array<types.Environment> = [];
@@ -57,11 +59,8 @@ export default class AASStore {
public findAASById(id: string): types.AssetAdministrationShell | null { public findAASById(id: string): types.AssetAdministrationShell | null {
for (const env of this.envs) { for (const env of this.envs) {
if (env.assetAdministrationShells === null) continue; const aas = AASStore.findAASById(env, id);
if(aas) return aas;
for (const aas of env.assetAdministrationShells) {
if (aas.id === id) return aas;
}
} }
return null; return null;
@@ -69,22 +68,78 @@ export default class AASStore {
public findSMById(id: string): types.Submodel | null { public findSMById(id: string): types.Submodel | null {
for (const env of this.envs) { for (const env of this.envs) {
if (env.submodels === null) continue; const sm = AASStore.findSMById(env, id);
if (sm) return sm;
for (const sm of env.submodels) {
if (sm.id === id) return sm;
}
} }
return null; return null;
} }
public findElement(submodelId: string, elementIdShort: string): types.Class | null { public findSMByIdShort(id: string): types.Submodel | null {
const sm = this.findSMById(submodelId); for (const env of this.envs) {
if (!sm) return null; const sm = AASStore.findSMByIdShort(env, id);
if (sm) return sm;
}
for (const element of sm.descend()) { return null;
if ((element as any).idShort === elementIdShort) return element; }
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;
return (environment.assetAdministrationShells.find(aas => aas.id === id)) ?? null;
}
public static findSMById(environment: types.Environment, id: string): types.Submodel | null {
if (environment.submodels === null) return null;
return (environment.submodels.find(sm => sm.id === id)) ?? null;
}
public static findSMByIdShort(environment: types.Environment, id: string): types.Submodel | null {
if (environment.submodels === null) return null;
return (environment.submodels.find(sm => sm.idShort === id)) ?? null;
}
public static resolveReference(env: types.Environment, ref: types.Reference): types.Class | null {
if (ref.type === types.ReferenceTypes.ExternalReference) return null; // Not implemented
let current: types.Class | null = null;
for (const key of ref.keys) {
if (current === null && !GLOBALLY_IDENTIFIABLES.includes(key.type)) break;
if (current === null) {
current = AASStore.findElement(env, element => (element as any).id === key.value)
} else {
current = AASStore.findElement(current, element => (element as any).idShort === key.value)
}
}
return current;
}
public static findElement(environment: types.Class, checkFunction: (element: types.Class) => boolean): types.Class | null {
for (const element of environment.descend()) {
if (checkFunction(element)) return element;
} }
return null; return null;
@@ -49,4 +49,8 @@ export default class HTTPInterfaceServer extends AASInterfaceServer<Config> {
protected findElementByRequest(request: Request) { protected findElementByRequest(request: Request) {
} }
private createRoutes()/*: express.Router*/ {
// TODO
}
} }
+12 -17
View File
@@ -1,23 +1,18 @@
import { readFileSync } from "fs"; import AASStore from "./aasStore";
import * as aasCore from "@aas-core-works/aas-core3.0-typescript"; import type { SubmodelElementCollection, SubmodelElementList, ReferenceElement, Reference } from "@aas-core-works/aas-core3.0-typescript/dist/types/types";
const aasFile = readFileSync("../AID-AIMC-Full-Example-20230911T1608.json", {encoding: "utf-8"}); const store = AASStore.getInstance();
const aasJSON = JSON.parse(aasFile); store.addAASByPath("../AID-AIMC-Full-Example-20230911T1608.json");
const env = aasCore.jsonization.environmentFromJsonable(aasJSON); const aid = store.findSMByIdShort("AssetInterfacesDescription");
const aimc = store.findSMByIdShort("AssetInterfacesMappingConfiguration");
if (env.error || !env.value || !env.value.assetAdministrationShells || !env.value.submodels) throw new Error("Invalid AAS file"); if (!aid || !aimc) throw new Error("Invalid AAS file");
const aas = env.value.assetAdministrationShells[0]; const AIMCEndpointRef = (((aimc.submodelElements?.at(0) as SubmodelElementList)
const submodels = env.value.submodels; .value?.at(0) as SubmodelElementCollection)
.value?.at(0) as ReferenceElement)
.value as Reference;
const aid = submodels[0].submodelElements // console.log(store.resolveReference(AIMCEndpointRef));
if (aid === null) throw new Error("Invalid AAS file");
console.log(((aid[0] as aasCore.types.SubmodelElementList).value as aasCore.types.SubmodelElementCollection[])[0])
/*
for (const something of aas.descend()) {
console.log(something);
console.log("---------------------------------------");
}*/
+1 -1
View File
@@ -9,7 +9,7 @@ class MultiMessageBroker {
private serverInterfaces: Array<typeof AASInterfaceServer> = []; private serverInterfaces: Array<typeof AASInterfaceServer> = [];
private serverInterfaceConfigs: Array<any> = []; private serverInterfaceConfigs: Array<any> = [];
private serverInstances: Array<AASInterfaceServer<typeof this.serverInterfaceConfigs[keyof typeof this.serverInterfaceConfigs]>|null> = []; private serverInstances: Array<AASInterfaceServer<typeof this.serverInterfaceConfigs[number]>|null> = [];
private connectorInterfaces: Array<typeof InterfaceConnectionObject> = []; private connectorInterfaces: Array<typeof InterfaceConnectionObject> = [];
private connectorConfigs: Array<any> = []; private connectorConfigs: Array<any> = [];
+150
View File
@@ -0,0 +1,150 @@
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";
export default class AIDParser {
public static parse(env: aasCore.types.Environment): AssetInterfaceDescription | null {
const sm = AASStore.findSMByIdShort(env, "AssetInterfaceDescription");
if (sm === null || sm.submodelElements === null) return null;
const parsed = {} as AssetInterfaceDescription;
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 AIDParser.parseAIDEntry(element, endpointProto);
}).filter(e => e !== null) as InterfaceDescription[];
});
return parsed;
}
private static parseAIDEntry(entry: aasCore.types.SubmodelElementCollection, endpointProtocol?: AvailableEndpoint): InterfaceDescription | null {
const title = AASStore.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");
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 parsedIm = AIDParser.parseInterfaceMetaData(im, endpointProtocol);
if (parsedEp === null || parsedIm === null) return null;
return {
title: titleString,
EndPointMetadata: parsedEp,
InterfaceMetadata: parsedIm
} as InterfaceDescription;
}
public static parseEndpointMetaData(endpoint: aasCore.types.SubmodelElementCollection, endpointProtocol?: AvailableEndpoint): EndpointMetadata | null {
if (!endpoint.value || endpoint.idShort?.toLocaleLowerCase() !== "endpointmetadata") return null;
const parsed = {} as EndpointMetadata;
for (const key of ["base", "contentType"]) {
const prop = AASStore.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;
}
// TODO
// Other values based on endpoint protocol
return parsed;
}
public static parseInterfaceMetaData(interfaceMeta: aasCore.types.SubmodelElementCollection, endpointProtocol?: AvailableEndpoint): InterfaceMetadata | null {
if (!interfaceMeta.value || interfaceMeta.idShort?.toLocaleLowerCase() !== "interfacemetadata") return null;
const parsed = {
properties: [],
actions: [],
events: []
} as InterfaceMetadata;
const prop = AASStore.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;
const parsedProp = AIDParser.parseInterfaceMetadataProperty(element, endpointProtocol);
if (parsedProp === null) continue;
parsed.properties.push(parsedProp);
}
const actions = AASStore.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;
const parsedAction = AIDParser.parseInterfaceMetadataProperty(element, endpointProtocol);
if (parsedAction === null) continue;
parsed.actions.push(parsedAction);
}
return parsed as InterfaceMetadata;
}
public static parseInterfaceMetadataProperty(prop: aasCore.types.SubmodelElementCollection, endpointProtocol?: AvailableEndpoint): InterfaceProperty | null {
if (!prop.value || prop.idShort?.toLocaleLowerCase() !== "properties") return null;
const parsed = {} as InterfaceProperty;
// This does not matter in any way for the interface
/* for (const key of ["title", "observeable", "type"]) {
const prop = AASStore.findElement(prop, element => (element as any).idShort.toLocaleLowerCase() === key.toLocaleLowerCase());
if (prop === null || !aasCore.types.isProperty(prop) || !prop.value) continue; // Not mandatory
else parsed[key] = prop.value;
} */
const forms = AASStore.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;
const parsedForm = AIDParser.parseInterfaceMetadataPropertyForm(element, endpointProtocol);
if (parsedForm === null) continue;
parsed.forms.push(parsedForm);
}
// TODO
// Other values based on endpoint protocol
return parsed;
}
private static parseInterfaceMetadataPropertyForm(form: aasCore.types.SubmodelElementCollection, endpointProtocol?: AvailableEndpoint): InterfacePropertyForm | null {
if (!form.value) return null;
const parsed = {} as any;
for (const key of ["href", "contentType"]) {
const prop = AASStore.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;
}
// TODO
// Other values based on endpoint protocol
return parsed as InterfacePropertyForm;
}
public static parseInterfaceMetadataAction(action: aasCore.types.SubmodelElementCollection, endpointProtocol?: AvailableEndpoint): InterfaceAction | null {
// TODO
return null;
}
public static parseInterfaceMetadataEvent(event: aasCore.types.SubmodelElementCollection, endpointProtocol?: AvailableEndpoint): InterfaceAction | null {
// TODO
return null;
}
}
+3
View File
@@ -0,0 +1,3 @@
export class AIMCMapper {
}
+86
View File
@@ -0,0 +1,86 @@
import { AvailableEndpoint } from "./common";
export type AssetInterfaceDescription = {
[key in AvailableEndpoint]: InterfaceDescription[];
};
export type InterfaceDescription = {
title: string;
//created?: Date;
//modified?: Date;
//support?: string;
//externalDescriptor: never; // We don't use these (atm)
EndPointMetadata: EndpointMetadata;
InterfaceMetadata: InterfaceMetadata;
}
export type EndpointMetadata = {
base: string; // This is the connection base string
contentType: string;
//securityDefinition?: any; // Will currently not be used
[key: string]: any; // This is for the additional metadata
}
export type InterfaceMetadata = {
properties: InterfaceProperty[];
actions: InterfaceAction[];
events: InterfaceEvent[];
}
export type InterfaceProperty = {
title?: string;
observable?: boolean;
forms: InterfacePropertyForm[];
type?: string;
enum?: string[];
const?: any;
default?: any;
//unit?: string;
minimum?: number; // TODO muss das gecapped werden?
maximum?: number;
minLength?: number;
maxLength?: number;
//items?: any[];
minItems?: number;
maxItems?: number;
}
export type InterfacePropertyForm = InterfacePropertyFormHTTP | InterfacePropertyFormMQTT | InterfacePropertyFormModbus;
type InterfacePropertyFormBase = {
href: string;
contentType: string;
}
type InterfacePropertyFormHTTP = InterfacePropertyFormBase & {
methodName: string;
headers?: Record<string, string>;
}
type InterfacePropertyFormModbus = InterfacePropertyFormBase & {
function: "readCoil" |
"readDeviceIdentification" |
"readDiscreteInput" |
"readHoldingRegisters" |
"readInputRegisters" |
"writeMultipleCoils" |
"writeMultipleHoldingRegisters" |
"writeSingleCoil" |
"writeSingleHoldingRegister";
entity?: "Coil" | "DiscreteInput" | "HoldingRegister" | "InputRegister";
zeroBasedAddressing?: boolean;
}
type InterfacePropertyFormMQTT = InterfacePropertyFormBase & {
retain?: boolean;
controlPacket: "publish" | "subscribe" | "unsubscribe";
qos?: 0 | 1 | 2;
}
export type InterfaceAction = {
// TODO
}
export type InterfaceEvent = {
// TODO
}
+14
View File
@@ -0,0 +1,14 @@
import type { types } from "@aas-core-works/aas-core3.0-typescript";
import { AvailableEndpoint } from "./common";
export type AssetInterfaceMappingConfiguration = Record<AvailableEndpoint, MappingConfEntry[]>;
type MappingConfEntry = {
EndpointMetaDataReference: types.ReferenceElement;
MappingConfiguration: MappingConfiguration;
}
type MappingConfiguration = {
InterfaceMetaDataReference: types.ReferenceElement;
SourceSinkMappings: types.RelationshipElement[];
}
+8
View File
@@ -0,0 +1,8 @@
export const availableEndpoints = ["HTTP", "MQTT", "Modbus"] as const;
export type AvailableEndpoint = typeof availableEndpoints[number];
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];
}