diff --git a/lib/package-lock.json b/lib/package-lock.json index ff0ad06..783b47f 100644 --- a/lib/package-lock.json +++ b/lib/package-lock.json @@ -11,11 +11,13 @@ "dependencies": { "@aas-core-works/aas-core3.0-typescript": "^1.0.0-rc.3", "express": "^4.18.2", - "mqtt": "^5.1.2" + "mqtt": "^5.1.2", + "uuid": "^9.0.1" }, "devDependencies": { "@types/express": "^4.17.19", "@types/node": "^20.8.2", + "@types/uuid": "^9.0.6", "ts-node": "^10.9.1", "typescript": "^5.2.2" } @@ -193,6 +195,12 @@ "@types/node": "*" } }, + "node_modules/@types/uuid": { + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.6.tgz", + "integrity": "sha512-BT2Krtx4xaO6iwzwMFUYvWBWkV2pr37zD68Vmp1CDV196MzczBRxuEpD6Pr395HAgebC/co7hOphs53r8V7jew==", + "dev": true + }, "node_modules/@types/ws": { "version": "8.5.7", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.7.tgz", @@ -1358,6 +1366,18 @@ "node": ">= 0.4.0" } }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", @@ -1577,6 +1597,12 @@ "@types/node": "*" } }, + "@types/uuid": { + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.6.tgz", + "integrity": "sha512-BT2Krtx4xaO6iwzwMFUYvWBWkV2pr37zD68Vmp1CDV196MzczBRxuEpD6Pr395HAgebC/co7hOphs53r8V7jew==", + "dev": true + }, "@types/ws": { "version": "8.5.7", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.7.tgz", @@ -2431,6 +2457,11 @@ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" }, + "uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==" + }, "v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", diff --git a/lib/package.json b/lib/package.json index 5cdda9d..0868484 100644 --- a/lib/package.json +++ b/lib/package.json @@ -12,12 +12,14 @@ "devDependencies": { "@types/express": "^4.17.19", "@types/node": "^20.8.2", + "@types/uuid": "^9.0.6", "ts-node": "^10.9.1", "typescript": "^5.2.2" }, "dependencies": { "@aas-core-works/aas-core3.0-typescript": "^1.0.0-rc.3", "express": "^4.18.2", - "mqtt": "^5.1.2" + "mqtt": "^5.1.2", + "uuid": "^9.0.1" } } diff --git a/lib/src/example_modules/mqttConnector.ts b/lib/src/example_modules/mqttConnector.ts index ec0c28c..c2e1ed9 100644 --- a/lib/src/example_modules/mqttConnector.ts +++ b/lib/src/example_modules/mqttConnector.ts @@ -1,6 +1,7 @@ import * as mqtt from "mqtt"; import type { Property, Operation, OperationVariable, BasicEventElement } from "@aas-core-works/aas-core3.0-typescript/dist/types/types"; import InterfaceConnectionObject from "interfaceConnectionObject"; +import type { types } from "@aas-core-works/aas-core3.0-typescript"; class MQTTConnector extends InterfaceConnectionObject { protected name: string = "MQTT Connector"; @@ -10,13 +11,17 @@ class MQTTConnector extends InterfaceConnectionObject { private client: mqtt.MqttClient|null = null; - private store: Record = {}; + private messageStore: Record = {}; public connect(): boolean { - if (!this.client) this.client = mqtt.connect(this.connectionParameter); + if (!this.client) this.client = mqtt.connect(this.endpointMetadata.base, this.connectionParameter); this.client.on("message", (topic, message) => { // If json is expected you could parse it here - this.store[topic] = message.toString("utf-8"); + this.messageStore[topic] = message.toString("utf-8"); + // Notify observers + if (this.observerStore[topic] !== undefined) { + this.observerStore[topic].forEach(cb => cb(message.toString("utf-8"))); + } }); this.client.subscribe("#"); return this.client.connected; @@ -27,35 +32,85 @@ class MQTTConnector extends InterfaceConnectionObject { this.client = null; } - public readProperty(prop: Property): void { + public readProperty(prop: types.Property): any { + const cc = this.mapper.get(prop); + if (cc === undefined) return null; + + const errorReturn = cc.default ?? null; + if (!this.client || !this.client.connected) return errorReturn; + + const value = cc.forms.map(f => this.messageStore[f.href.substring(1)]).filter(v => v !== undefined)[0]; + if (value === undefined) return errorReturn; + switch (cc.type) { + case "integer": + return Number.parseInt(value); + case "float": + return Number.parseFloat(value); + case "boolean": + return !!value; + case "string": + default: + return value; + } + + } + + public writeProperty(prop: types.Property, value: any): boolean { + const cc = this.mapper.get(prop); + if (cc === undefined || !this.client || !this.client.connected) return false; + + const topic = cc.forms.map(f => f.href.substring(1))[0]; + if (topic === undefined) return false; + + this.client.publish(topic, value.toString()); + + return true; + } + + public observeProperty(prop: types.Property, callback: (value: any) => void): boolean { + const cc = this.mapper.get(prop); + if (cc === undefined || !cc.observable || !this.client || !this.client.connected) return false; + + cc.forms.map(f => f.href.substring(1)).forEach(topic => { + if (this.observerStore[topic] === undefined) this.observerStore[topic] = [callback]; + else this.observerStore[topic].push(callback); + }); + + return true; + } + + public callActionSync(action: types.Operation, args: OperationVariable[]): boolean { + // TODO + // Vorher mal ne ordentliche Mapping-Definition + throw new Error("Method not implemented."); } - public writeProperty(prop: Property, value: any): void { + public callActionAsync(action: types.Operation, args: OperationVariable[]): string | null { + // TODO + // Vorher mal ne ordentliche Mapping-Definition + throw new Error("Method not implemented."); } - public observeProperty(prop: Property, callback: (value: any) => void): boolean { + public readAsyncActionResponse(action: types.Operation): any { + // TODO + // Vorher mal ne ordentliche Mapping-Definition + throw new Error("Method not implemented."); } - public callActionSync(action: Operation, args: OperationVariable[]) { + public subscribeEvent(event: types.BasicEventElement, callback: (event: BasicEventElement) => void): boolean { + // TODO + // Vorher mal ne ordentliche Mapping-Definition + throw new Error("Method not implemented."); } - public callActionAsync(action: Operation, args: OperationVariable[]): Promise { - throw new Error("Method not implemented."); - } + public unsubscribeEvent(event: types.BasicEventElement): void { + // TODO + // Vorher mal ne ordentliche Mapping-Definition - public readAsyncActionResponse(action: Operation): Promise { - throw new Error("Method not implemented."); - } - - public subscribeEvent(callback: (event: BasicEventElement) => void): boolean { - throw new Error("Method not implemented."); - } - - public unsubscribeEvent(): void { throw new Error("Method not implemented."); } diff --git a/lib/src/index.ts b/lib/src/index.ts index 28c3303..0a2343e 100644 --- a/lib/src/index.ts +++ b/lib/src/index.ts @@ -1,6 +1,6 @@ import MultiMessageBroker from "./multimessageBroker"; import HTTPInterfaceServer from "./example_modules/httpInterfaceServer"; -import FileImporter from "helper/fileImporter"; +import FileImporter from "./helper/fileImporter"; const aas = FileImporter.readAASByPath("../AID-AIMC-Full-Example-20230911T1608.json"); @@ -8,4 +8,4 @@ const broker = new MultiMessageBroker(); broker.registerAAS({ aas, serverInterfaces: { serverInterface: HTTPInterfaceServer, config: { bindPort: 3000, bindAddress: "0.0.0.0" } } }); broker.prepare(); -broker.start(); +broker.start(); \ No newline at end of file diff --git a/lib/src/interfaceConnectionObject.ts b/lib/src/interfaceConnectionObject.ts index dfb85fa..cc6be36 100644 --- a/lib/src/interfaceConnectionObject.ts +++ b/lib/src/interfaceConnectionObject.ts @@ -1,4 +1,6 @@ import type { types } from "@aas-core-works/aas-core3.0-typescript"; +import { AIMCMapper } from "parser/AIMCMapper"; +import { v4 } from "uuid"; import type { EndpointMetadata } from "types/aidConf"; type ConnectionType = "ON_DEMAND" | "PERMANENT"; @@ -11,9 +13,14 @@ export default abstract class InterfaceConnectionObject { protected abstract readonly connectionType: ConnectionType; protected abstract readonly supportsSubscriptions: boolean; + protected observerStore: Record void)[]> = {}; + protected eventSubStore: Record void)[]> = {}; + protected asyncActionStateStore: Record = {}; + public constructor( protected readonly connectionParameter: ConfigInterface, - protected readonly endpointMetadata: EndpointMetadata, + protected readonly endpointMetadata: EndpointMetadata, + protected readonly mapper: AIMCMapper, private readonly onResponseCallback: OnResponseCallback) {} public abstract connect(): boolean; @@ -26,13 +33,19 @@ export default abstract class InterfaceConnectionObject { public abstract observeProperty(prop: types.Property, callback: (value: any) => void): boolean; - public abstract callActionSync(action: types.Operation, args: types.OperationVariable[]): any; + public abstract callActionSync(action: types.Operation, args: types.OperationVariable[]): boolean; - public abstract callActionAsync(action: types.Operation, args: types.OperationVariable[]): Promise; + public abstract callActionAsync(action: types.Operation, args: types.OperationVariable[]): string | null; - public abstract readAsyncActionResponse(action: types.Operation): Promise; + public abstract readAsyncActionResponse(action: types.Operation): any; - public abstract subscribeEvent(callback: (event: types.BasicEventElement) => void): boolean; + public abstract subscribeEvent(event: types.BasicEventElement, callback: (event: types.BasicEventElement) => void): boolean; - public abstract unsubscribeEvent(): void; + public abstract unsubscribeEvent(event: types.BasicEventElement): void; + + protected generateAsyncHandle() { + const handle = v4(); + this.asyncActionStateStore[handle] = {finished: false, result: null}; + return handle; + } } \ No newline at end of file diff --git a/lib/src/types/aimcConf.ts b/lib/src/types/aimcConf.ts index 659beef..f073aee 100644 --- a/lib/src/types/aimcConf.ts +++ b/lib/src/types/aimcConf.ts @@ -14,6 +14,6 @@ export type MappingConfiguration = { SourceSinkMappings: types.RelationshipElement[]; } -export type ElementMap = Record; +export type AIMCMap = Map; export type ConnectionConfiguration = EndpointMetadata & InterfaceProperty; // Wenn Transformationen implementiert werden, dann hier anpassen \ No newline at end of file