Types and AIDParsing
This commit is contained in:
+70
-15
@@ -2,6 +2,8 @@
|
||||
import { readdirSync, readFileSync } from "fs";
|
||||
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> = [];
|
||||
@@ -57,11 +59,8 @@ export default class AASStore {
|
||||
|
||||
public findAASById(id: string): types.AssetAdministrationShell | null {
|
||||
for (const env of this.envs) {
|
||||
if (env.assetAdministrationShells === null) continue;
|
||||
|
||||
for (const aas of env.assetAdministrationShells) {
|
||||
if (aas.id === id) return aas;
|
||||
}
|
||||
const aas = AASStore.findAASById(env, id);
|
||||
if(aas) return aas;
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -69,22 +68,78 @@ export default class AASStore {
|
||||
|
||||
public findSMById(id: string): types.Submodel | null {
|
||||
for (const env of this.envs) {
|
||||
if (env.submodels === null) continue;
|
||||
|
||||
for (const sm of env.submodels) {
|
||||
if (sm.id === id) return sm;
|
||||
}
|
||||
const sm = AASStore.findSMById(env, id);
|
||||
if (sm) return sm;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public findElement(submodelId: string, elementIdShort: string): types.Class | null {
|
||||
const sm = this.findSMById(submodelId);
|
||||
if (!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;
|
||||
}
|
||||
|
||||
for (const element of sm.descend()) {
|
||||
if ((element as any).idShort === elementIdShort) return element;
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
@@ -49,4 +49,8 @@ export default class HTTPInterfaceServer extends AASInterfaceServer<Config> {
|
||||
protected findElementByRequest(request: Request) {
|
||||
|
||||
}
|
||||
|
||||
private createRoutes()/*: express.Router*/ {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
+12
-17
@@ -1,23 +1,18 @@
|
||||
import { readFileSync } from "fs";
|
||||
import * as aasCore from "@aas-core-works/aas-core3.0-typescript";
|
||||
import AASStore from "./aasStore";
|
||||
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 aasJSON = JSON.parse(aasFile);
|
||||
const store = AASStore.getInstance();
|
||||
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 submodels = env.value.submodels;
|
||||
const AIMCEndpointRef = (((aimc.submodelElements?.at(0) as SubmodelElementList)
|
||||
.value?.at(0) as SubmodelElementCollection)
|
||||
.value?.at(0) as ReferenceElement)
|
||||
.value as Reference;
|
||||
|
||||
const aid = submodels[0].submodelElements
|
||||
if (aid === null) throw new Error("Invalid AAS file");
|
||||
// console.log(store.resolveReference(AIMCEndpointRef));
|
||||
|
||||
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("---------------------------------------");
|
||||
}*/
|
||||
|
||||
@@ -9,7 +9,7 @@ class MultiMessageBroker {
|
||||
|
||||
private serverInterfaces: Array<typeof AASInterfaceServer> = [];
|
||||
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 connectorConfigs: Array<any> = [];
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export class AIMCMapper {
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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[];
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
Reference in New Issue
Block a user