From 64279a6efaec16365421d06dd052af1c0200793d Mon Sep 17 00:00:00 2001 From: Daniel Kluge Date: Mon, 4 Dec 2023 17:10:03 +0100 Subject: [PATCH] More work on the (better) AID --- lib/src/AIMCMapper.ts | 42 +++++++++++++++++-- lib/src/abstractConnectionObject.ts | 43 ++++++++++++------- lib/src/multimessageBroker.ts | 64 +++++++++++------------------ lib/src/types/aidConf.ts | 2 +- lib/src/types/aimcConf.ts | 5 ++- lib/src/types/requests.ts | 58 ++++++++++++-------------- 6 files changed, 120 insertions(+), 94 deletions(-) diff --git a/lib/src/AIMCMapper.ts b/lib/src/AIMCMapper.ts index 3c9430d..89e70c0 100644 --- a/lib/src/AIMCMapper.ts +++ b/lib/src/AIMCMapper.ts @@ -3,6 +3,7 @@ import type { ConnectionConfiguration, AIMCMap } from "./types/aimcConf"; import AIMCParser from "./parser/AIMCParser"; import Traverser from "./helper/traverser"; import AIDParser from "./parser/AIDParser"; +import type { InterfaceAction, InterfaceActionForm, InterfaceEvent, InterfaceEventForm, InterfaceProperty, InterfacePropertyForm } from "./types/aidConf"; /** * The AIMC Mapper. @@ -49,14 +50,47 @@ export default class AIMCMapper { const resolved = Traverser.resolveRelationship(this.env, ssm); if (resolved === null) continue; + // To know if we currently view a property, action or event, they reference keys can help us. + // We know the third to last must be the "InterfaceMetadata" and the second to last must be the "Properties", "Actions" or "Events". + // As we _maybe_ don't know if the first and second reference are switched we can check for the third to last. + const elemType = (ssm as types.RelationshipElement).first.keys.at(-3)?.value.toLocaleLowerCase() === "interfacemetadata" ? (ssm as types.RelationshipElement).first.keys.at(-2)?.value : (ssm as types.RelationshipElement).second.keys.at(-2)?.value; + let {first, second} = resolved; - let parsedPropConf = AIDParser.parseInterfaceMetadataProperty(first as types.SubmodelElementCollection); + let parsedPropConf: InterfaceProperty | InterfaceAction | InterfaceEvent | null = null; + + switch (elemType?.toLocaleLowerCase()) { + case "properties": + parsedPropConf = AIDParser.parseInterfaceMetadataProperty(first as types.SubmodelElementCollection); + break; + case "actions": + parsedPropConf = AIDParser.parseInterfaceMetadataAction(first as types.SubmodelElementCollection); + break; + case "events": + parsedPropConf = AIDParser.parseInterfaceMetadataEvent(first as types.SubmodelElementCollection); + break; + default: + // Error. We have not found a valid type of element to parse. + continue; + } + if (parsedPropConf === null) { // maybe the property is in the first element [first, second] = [second, first]; - parsedPropConf = AIDParser.parseInterfaceMetadataProperty(first as types.SubmodelElementCollection); + + switch (elemType?.toLocaleLowerCase()) { + case "properties": + parsedPropConf = AIDParser.parseInterfaceMetadataProperty(first as types.SubmodelElementCollection); + break; + case "actions": + parsedPropConf = AIDParser.parseInterfaceMetadataAction(first as types.SubmodelElementCollection); + break; + case "events": + parsedPropConf = AIDParser.parseInterfaceMetadataEvent(first as types.SubmodelElementCollection); + break; + } } + if (parsedPropConf === null) continue; this.map.set(second, { @@ -113,8 +147,8 @@ export default class AIMCMapper { const result = []; for (const [e, cc] of this.map.entries()) { - const formsPath = cc.base + cc.forms.href; - if (formsPath === path) result.push(e); + const formsPaths = cc.forms.map((f: InterfaceActionForm | InterfacePropertyForm | InterfaceEventForm) => cc.base + f.href); + if (formsPaths.includes(path)) result.push(e); } return result; diff --git a/lib/src/abstractConnectionObject.ts b/lib/src/abstractConnectionObject.ts index 3763a5f..70243ff 100644 --- a/lib/src/abstractConnectionObject.ts +++ b/lib/src/abstractConnectionObject.ts @@ -1,7 +1,8 @@ import type { types } from "@aas-core-works/aas-core3.0-typescript"; import AIMCMapper from "./AIMCMapper"; import { v4 } from "uuid"; -import type { EndpointMetadata } from "./types/aidConf"; +import type { EndpointMetadata, FormOp, InterfaceActionForm, InterfaceEventForm, InterfacePropertyForm } from "./types/aidConf"; +import type { ConnectionConfiguration } from "./types/aimcConf"; type ConnectionType = "ON_DEMAND" | "PERMANENT"; @@ -94,51 +95,59 @@ export default abstract class AbstractConnectionObject { /** * Read a property value from the asset. - * @param prop Property + * @param mapping ConnectionConfiguration for the property * @returns Value of property casted to the type it says it should be. * @public * @virtual */ - public abstract readProperty(prop: types.Property): void; + public abstract readProperty(mapping: ConnectionConfiguration): void; /** * Write a property value to the asset. - * @param prop Property + * @param mapping ConnectionConfiguration for the property * @param value Value to write. * @public * @virtual */ - public abstract writeProperty(prop: types.Property, value: any): void; + public abstract writeProperty(mapping: ConnectionConfiguration, value: any): void; /** * Observe a property value from the asset. - * @param prop Property + * @param mapping ConnectionConfiguration for the property * @param callback Callback to call when the property changes. * @returns Whether the creation of an observer was successful. * @public * @virtual */ - public abstract observeProperty(prop: types.Property, callback: (value: any) => void): boolean; + public abstract observeProperty(mapping: ConnectionConfiguration, callback: (value: any) => void): boolean; + + /** + * Unobserve a property value from the asset. + * @param mapping ConnectionConfiguration for the property + * @public + * @virtual + */ + public abstract unobserveProperty(mapping: ConnectionConfiguration): void; /** * Call an action on the asset synchronously. - * @param action Action + * @param mapping ConnectionConfiguration for the action * @param args Arguments * @returns Return value of the action. * @public * @virtual */ - public abstract callActionSync(action: types.Operation, args: Record): any; + public abstract callActionSync(mapping: ConnectionConfiguration, args: Record): any; /** * Call an action on the asset asynchronously. - * @param action Action + * @param mapping ConnectionConfiguration for the action * @param args Arguments * @returns Handle for the async action. * @public * @virtual */ - public abstract callActionAsync(action: types.Operation, args: Record): string | null; + public abstract callActionAsync(mapping: ConnectionConfiguration, args: Record): string | null; /** * Read the state of an async action. @@ -164,21 +173,21 @@ export default abstract class AbstractConnectionObject { /** * Subscribe to an event. - * @param event Event + * @param mapping ConnectionConfiguration for the event * @param callback Callback to call when the event occurs. * @returns Whether the subscription was successful. * @public * @virtual */ - public abstract subscribeEvent(event: types.BasicEventElement, callback: (event: types.BasicEventElement) => void): boolean; + public abstract subscribeEvent(mapping: ConnectionConfiguration, callback: (event: types.BasicEventElement) => void): boolean; /** * Unsubscribe from an event. - * @param event Event + * @param mapping ConnectionConfiguration for the event * @public * @virtual */ - public abstract unsubscribeEvent(event: types.BasicEventElement): void; + public abstract unsubscribeEvent(mapping: ConnectionConfiguration): void; /** * Generate a handle for an async action. @@ -190,4 +199,8 @@ export default abstract class AbstractConnectionObject { this.asyncActionStateStore[handle] = {finished: false, result: null}; return handle; } + + protected static findValidForms(mapping: ConnectionConfiguration, op: FormOp): (InterfacePropertyForm | InterfaceActionForm | InterfaceEventForm)[] { + return mapping.forms.filter((form: InterfacePropertyForm | InterfaceActionForm | InterfaceEventForm) => form.op === op); + } } \ No newline at end of file diff --git a/lib/src/multimessageBroker.ts b/lib/src/multimessageBroker.ts index e1aff27..53726fa 100644 --- a/lib/src/multimessageBroker.ts +++ b/lib/src/multimessageBroker.ts @@ -4,7 +4,8 @@ import type { Request } from "./types/requests"; import type AbstractConnectionObject from "./abstractConnectionObject"; import AIMCMapper from "./AIMCMapper"; import AIDParser from "./parser/AIDParser"; -import { EndpointMetadata } from "types/aidConf"; +import type { EndpointMetadata, InterfaceActionForm, InterfaceEventForm, InterfacePropertyForm } from "./types/aidConf"; +import type { ConnectionConfiguration } from "./types/aimcConf"; type AASRegistration = { aas: types.Environment, @@ -165,63 +166,44 @@ export default class MultiMessageBroker { */ private onInterfaceRequest(request: Request, registration: AASRegistrationPrepared): any { - const getConnector = (target: types.Class) => { + const getMapping = (target: types.Class) => { const mapping = registration.mappingConfiguration?.get(target); if (mapping === undefined) throw new ReferenceError(`No mapping found for ${(request.target as any).idShort}`); + return mapping; + } + const getConnector = (mapping: ConnectionConfiguration) => { const connector = registration.connectorInterfaces?.find(connector => connector.endpointMetadata.base === mapping.base); if (connector === undefined) throw new ReferenceError(`No connector found for ${(request.target as any).idShort}`); return connector; } - switch (request.type) { - case "READ": { - const connector = getConnector(request.target); - - return connector.readProperty(request.target); - } - case "WRITE": { - const connector = getConnector(request.target); - - return connector.writeProperty(request.target, request.extraData.value); - } - case "OBSERVE": { - const connector = getConnector(request.target); - - return connector.observeProperty(request.target, request.extraData.callback); - } - case "SUBSCRIBE": { - const connector = getConnector(request.target); - - return connector.subscribeEvent(request.target, request.extraData.callback); - } - case "UNSUBSCRIBE": { - const connector = getConnector(request.target); - - return connector.unsubscribeEvent(request.target); - } - case "CALL": { - const connector = getConnector(request.target); - - return connector.callActionSync(request.target, request.extraData.args); - } - case "CALL-ASYNC": { - const connector = getConnector(request.target); - - return connector.callActionAsync(request.target, request.extraData.args); - } - case "GET-OP-STATE": { + if (request.type === "xAsyncActionState" || request.type === "xAsyncActionResult") { + if (request.type === "xAsyncActionState") { const state = registration.connectorInterfaces.map(connector => connector.readAsyncActionState(request.target)).filter(state => state !== undefined)[0]; if (state === undefined) throw new ReferenceError(`No connector found for operation handle ${request.target}`); return state; - } - case "GET-OP-RESULT": { + } else { const result = registration.connectorInterfaces.map(connector => connector.readAsyncActionResponse(request.target)).filter(result => result !== undefined)[0]; if (result === undefined) throw new ReferenceError(`No connector found for operation handle ${request.target}`); return result; } } + else { + const mapping = getMapping(request.target); + const connector = getConnector(mapping); + + switch (request.type) { + case "readProperty": return connector.readProperty(mapping); + case "writeProperty": return connector.writeProperty(mapping, request.extraData.value); + case "observeProperty": return connector.observeProperty(mapping, request.extraData.callback); + case "unobserveProperty": return connector.unobserveProperty(mapping); + case "subscribeEvent": return connector.subscribeEvent(mapping, request.extraData.callback); + case "unsubscribeEvent": return connector.unsubscribeEvent(mapping); + case "invokeAction": return request.extraData.async ? connector.callActionAsync(mapping, request.extraData.args) : connector.callActionSync(mapping, request.extraData.args); + } + } } /** diff --git a/lib/src/types/aidConf.ts b/lib/src/types/aidConf.ts index 42178b1..b8934b3 100644 --- a/lib/src/types/aidConf.ts +++ b/lib/src/types/aidConf.ts @@ -48,7 +48,7 @@ It should be pretty easy to extend this. So let's derive some of this from the WoT and see what we can achieve. */ -type FormOp = "readproperty" | +export type FormOp = "readproperty" | "writeproperty" | "observeproperty" | "unobserveproperty" | diff --git a/lib/src/types/aimcConf.ts b/lib/src/types/aimcConf.ts index f073aee..daab155 100644 --- a/lib/src/types/aimcConf.ts +++ b/lib/src/types/aimcConf.ts @@ -1,6 +1,6 @@ import type { types } from "@aas-core-works/aas-core3.0-typescript"; import type { AvailableEndpoint } from "./common"; -import type { EndpointMetadata, InterfaceProperty } from "./aidConf"; +import type { EndpointMetadata, InterfaceAction, InterfaceEvent, InterfaceProperty } from "./aidConf"; export type AssetInterfacesMappingConfiguration = Record; @@ -16,4 +16,5 @@ export type MappingConfiguration = { export type AIMCMap = Map; -export type ConnectionConfiguration = EndpointMetadata & InterfaceProperty; // Wenn Transformationen implementiert werden, dann hier anpassen \ No newline at end of file +type InterfaceElement = InterfaceProperty | InterfaceAction | InterfaceEvent; +export type ConnectionConfiguration = EndpointMetadata & InterfaceElement; // Wenn Transformationen implementiert werden, dann hier anpassen \ No newline at end of file diff --git a/lib/src/types/requests.ts b/lib/src/types/requests.ts index e32137a..50f19eb 100644 --- a/lib/src/types/requests.ts +++ b/lib/src/types/requests.ts @@ -4,7 +4,7 @@ import type { types } from "@aas-core-works/aas-core3.0-typescript"; * Read Property Request */ type GetRequest = { - type: "READ"; + type: "readProperty"; target: types.Property; } @@ -17,7 +17,7 @@ type GetRequestCallback = (request: GetRequest) => any; * Write Property Request */ type WriteRequest = { - type: "WRITE"; + type: "writeProperty"; target: types.Property; extraData: { value: any; @@ -33,7 +33,7 @@ type WriteRequestCallback = (request: WriteRequest) => void; * Observe Property Request */ type ObserveRequest = { - type: "OBSERVE"; + type: "observeProperty"; target: types.Property; extraData: { callback: (value: any) => void; @@ -45,11 +45,27 @@ type ObserveRequest = { */ type ObserveRequestCallback = (request: ObserveRequest) => boolean; +/** + * Unobserve Property Request + */ +type UnobserveRequest = { + type: "unobserveProperty"; + target: types.Property; + extraData: { + callback: (value: any) => void; + } +} + +/** + * Unobserve Property Request Callback + */ +type UnobserveRequestCallback = (request: ObserveRequest) => boolean; + /** * Subscribe Event Request */ type SubscribeRequest = { - type: "SUBSCRIBE"; + type: "subscribeEvent"; target: types.BasicEventElement; extraData: { callback: (value: any) => void; @@ -65,7 +81,7 @@ type SubscribeRequestCallback = (request: SubscribeRequest) => boolean; * Unsubscribe Event Request */ type UnsubscribeRequest = { - type: "UNSUBSCRIBE"; + type: "unsubscribeEvent"; target: types.BasicEventElement; } @@ -78,9 +94,10 @@ type UnsubscribeRequestCallback = (request: UnsubscribeRequest) => void; * Call Operation Request */ type CallRequest = { - type: "CALL"; + type: "invokeAction"; target: types.Operation; extraData: { + async: boolean; args: Record; } } @@ -90,27 +107,11 @@ type CallRequest = { */ type CallRequestCallback = (request: CallRequest) => any; -/** - * Call Async Operation Request - */ -type CallAsyncRequest = { - type: "CALL-ASYNC"; - target: types.Operation; - extraData: { - args: Record; - } -} - -/** - * Call Async Operation Request Callback - */ -type CallAsyncRequestCallback = (request: CallAsyncRequest) => string | null; - /** * Get Async Operation State Request */ type AsyncStateRequest = { - type: "GET-OP-STATE"; + type: "xAsyncActionState"; target: string; } @@ -123,7 +124,7 @@ type AsyncStateRequestCallback = (request: AsyncStateRequest) => boolean; * Get Async Operation Result Request */ type AsyncResultRequest = { - type: "GET-OP-RESULT"; + type: "xAsyncActionResult"; target: string; } @@ -132,17 +133,12 @@ type AsyncResultRequest = { */ type AsyncResultRequestCallback = (request: AsyncResultRequest) => any; -/** - * Possible Request Types - */ -type RequestType = "READ" | "WRITE" | "OBSERVE" | "CALL" | "CALL-ASYNC" | "GET-OP-STATE" | "GET-OP-RESULT" | "SUBSCRIBE" | "UNSUBSCRIBE" - /** * Combined Request Type */ -export type Request = GetRequest | WriteRequest | ObserveRequest | CallRequest | CallAsyncRequest | AsyncStateRequest | AsyncResultRequest | SubscribeRequest | UnsubscribeRequest; +export type Request = GetRequest | WriteRequest | ObserveRequest | UnobserveRequest | CallRequest | AsyncStateRequest | AsyncResultRequest | SubscribeRequest | UnsubscribeRequest; /** * Combined Request Callback Type */ -export type OnRequestCallback = (request: Request) => any & GetRequestCallback & WriteRequestCallback & ObserveRequestCallback & CallRequestCallback & CallAsyncRequestCallback & AsyncStateRequestCallback & AsyncResultRequestCallback & SubscribeRequestCallback & UnsubscribeRequestCallback; +export type OnRequestCallback = (request: Request) => any & GetRequestCallback & WriteRequestCallback & ObserveRequestCallback & UnobserveRequestCallback & CallRequestCallback & AsyncStateRequestCallback & AsyncResultRequestCallback & SubscribeRequestCallback & UnsubscribeRequestCallback;