Draw the rest of the f-ing owl 🦉

This commit is contained in:
Daniel Kluge
2023-12-06 16:15:54 +01:00
parent 64279a6efa
commit 76ec6e4c9d
13 changed files with 1981 additions and 132 deletions
+428
View File
@@ -0,0 +1,428 @@
{
"idShort": "AssetInterfacesDescription",
"id": "www.example.com/ids/sm/7142_2140_7032_7989",
"kind": "Instance",
"submodelElements": [
{
"idShort": "MQTT",
"typeValueListElement": "SubmodelElement",
"value": [
{
"idShort": "DEL-00",
"semanticId": {
"type": "ExternalReference",
"keys": []
},
"embeddedDataSpecifications": [],
"value": [
{
"idShort": "EndpointMetadata",
"semanticId": {
"type": "ExternalReference",
"keys": []
},
"embeddedDataSpecifications": [],
"value": [
{
"idShort": "titles",
"value": [
{
"language": "en",
"text": "AID and AIMC Test"
}
],
"modelType": "MultiLanguageProperty"
},
{
"idShort": "base",
"semanticId": {
"type": "ExternalReference",
"keys": []
},
"embeddedDataSpecifications": [],
"valueType": "xs:string",
"value": "mqtt://127.0.0.1:1883",
"modelType": "Property"
},
{
"idShort": "mqv:version",
"semanticId": {
"type": "ExternalReference",
"keys": []
},
"embeddedDataSpecifications": [],
"valueType": "xs:string",
"value": "5.0",
"modelType": "Property"
},
{
"idShort": "contentType",
"semanticId": {
"type": "ExternalReference",
"keys": []
},
"embeddedDataSpecifications": [],
"valueType": "xs:string",
"value": "application/json",
"modelType": "Property"
},
{
"idShort": "securityDefinitions",
"semanticId": {
"type": "ExternalReference",
"keys": []
},
"embeddedDataSpecifications": [],
"value": [
{
"idShort": "basic",
"semanticId": {
"type": "ExternalReference",
"keys": []
},
"embeddedDataSpecifications": [],
"value": [
{
"idShort": "in",
"semanticId": {
"type": "ExternalReference",
"keys": []
},
"embeddedDataSpecifications": [],
"valueType": "xs:string",
"value": "header",
"modelType": "Property"
}
],
"modelType": "SubmodelElementCollection"
}
],
"modelType": "SubmodelElementCollection"
},
{
"idShort": "AssetsReservedClientName",
"valueType": "xs:string",
"value": "thisandthat",
"modelType": "Property"
}
],
"modelType": "SubmodelElementCollection"
},
{
"idShort": "InterfaceMetadata",
"semanticId": {
"type": "ExternalReference",
"keys": []
},
"embeddedDataSpecifications": [],
"value": [
{
"idShort": "Properties",
"semanticId": {
"type": "ExternalReference",
"keys": []
},
"embeddedDataSpecifications": [],
"typeValueListElement": "SubmodelElementCollection",
"value": [
{
"idShort": "DEL-00",
"value": [
{
"idShort": "title",
"valueType": "xs:string",
"value": "TestingParameter01",
"modelType": "Property"
},
{
"idShort": "observable",
"valueType": "xs:boolean",
"value": "true",
"modelType": "Property"
},
{
"idShort": "type",
"valueType": "xs:string",
"value": "integer",
"modelType": "Property"
},
{
"idShort": "forms",
"typeValueListElement": "SubmodelElementCollection",
"value": [
{
"idShort": "",
"value": [
{
"idShort": "href",
"valueType": "xs:string",
"value": "/testtopic/testparam1",
"modelType": "Property"
},
{
"idShort": "contentType",
"valueType": "xs:string",
"value": "application/json",
"modelType": "Property"
},
{
"idShort": "op",
"valueType": "xs:string",
"value": "readProperty",
"modelType": "Property"
}
],
"modelType": "SubmodelElementCollection"
}
],
"modelType": "SubmodelElementList"
}
],
"modelType": "SubmodelElementCollection"
},
{
"idShort": "DEL-01",
"value": [
{
"idShort": "title",
"valueType": "xs:string",
"value": "TestingParameter02",
"modelType": "Property"
},
{
"idShort": "observable",
"valueType": "xs:boolean",
"value": "false",
"modelType": "Property"
},
{
"idShort": "type",
"valueType": "xs:string",
"value": "boolean",
"modelType": "Property"
},
{
"idShort": "forms",
"typeValueListElement": "SubmodelElementCollection",
"value": [
{
"idShort": "",
"value": [
{
"idShort": "href",
"valueType": "xs:string",
"value": "mqtt://127.0.0.1:1883/testtopic/testparam2",
"modelType": "Property"
},
{
"idShort": "contentType",
"valueType": "xs:string",
"value": "application/json",
"modelType": "Property"
},
{
"idShort": "op",
"valueType": "xs:string",
"value": "readProperty",
"modelType": "Property"
}
],
"modelType": "SubmodelElementCollection"
},
{
"idShort": "",
"value": [
{
"idShort": "href",
"valueType": "xs:string",
"value": "mqtt://127.0.0.1:1883/testtopic/testparam2/write",
"modelType": "Property"
},
{
"idShort": "contentType",
"valueType": "xs:string",
"value": "application/json",
"modelType": "Property"
},
{
"idShort": "op",
"valueType": "xs:string",
"value": "writeProperty",
"modelType": "Property"
}
],
"modelType": "SubmodelElementCollection"
}
],
"modelType": "SubmodelElementList"
}
],
"modelType": "SubmodelElementCollection"
}
],
"modelType": "SubmodelElementList"
},
{
"idShort": "Actions",
"semanticId": {
"type": "ExternalReference",
"keys": []
},
"embeddedDataSpecifications": [],
"typeValueListElement": "SubmodelElementCollection",
"value": [
{
"idShort": "DEL-00",
"value": [
{
"idShort": "async",
"valueType": "xs:boolean",
"value": "false",
"modelType": "Property"
},
{
"idShort": "forms",
"typeValueListElement": "SubmodelElementCollection",
"value": [
{
"idShort": "",
"value": [
{
"idShort": "href",
"valueType": "xs:string",
"value": "/testtopic/testoperation1",
"modelType": "Property"
},
{
"idShort": "contentType",
"valueType": "xs:string",
"value": "application/json",
"modelType": "Property"
},
{
"idShort": "op",
"valueType": "xs:string",
"value": "invokeAction",
"modelType": "Property"
}
],
"modelType": "SubmodelElementCollection"
}
],
"modelType": "SubmodelElementList"
}
],
"modelType": "SubmodelElementCollection"
},
{
"idShort": "DEL-01",
"value": [
{
"idShort": "async",
"valueType": "xs:boolean",
"value": "false",
"modelType": "Property"
},
{
"idShort": "forms",
"typeValueListElement": "SubmodelElementCollection",
"value": [
{
"idShort": "",
"value": [
{
"idShort": "href",
"valueType": "xs:string",
"value": "/testtopic/testoperation2",
"modelType": "Property"
},
{
"idShort": "contentType",
"valueType": "xs:string",
"value": "application/json",
"modelType": "Property"
},
{
"idShort": "op",
"valueType": "xs:string",
"value": "invokeAction",
"modelType": "Property"
}
],
"modelType": "SubmodelElementCollection"
}
],
"modelType": "SubmodelElementList"
}
],
"modelType": "SubmodelElementCollection"
},
{
"idShort": "DEL-02",
"value": [
{
"idShort": "async",
"valueType": "xs:boolean",
"value": "false",
"modelType": "Property"
},
{
"idShort": "forms",
"typeValueListElement": "SubmodelElementCollection",
"value": [
{
"idShort": "",
"value": [
{
"idShort": "href",
"valueType": "xs:string",
"value": "/testtopic/testoperation3",
"modelType": "Property"
},
{
"idShort": "contentType",
"valueType": "xs:string",
"value": "application/json",
"modelType": "Property"
},
{
"idShort": "op",
"valueType": "xs:string",
"value": "invokeAction",
"modelType": "Property"
}
],
"modelType": "SubmodelElementCollection"
}
],
"modelType": "SubmodelElementList"
}
],
"modelType": "SubmodelElementCollection"
}
],
"modelType": "SubmodelElementList"
},
{
"idShort": "Events",
"semanticId": {
"type": "ExternalReference",
"keys": []
},
"embeddedDataSpecifications": [],
"typeValueListElement": "SubmodelElementCollection",
"value": [],
"modelType": "SubmodelElementList"
}
],
"modelType": "SubmodelElementCollection"
}
],
"modelType": "SubmodelElementCollection"
}
],
"modelType": "SubmodelElementList"
}
],
"modelType": "Submodel"
}
@@ -0,0 +1,341 @@
{
"idShort": "AssetInterfacesMappingConfiguration",
"id": "https://example.com/ids/sm/4342_8060_7032_0872",
"kind": "Instance",
"submodelElements": [
{
"idShort": "MQTT",
"typeValueListElement": "SubmodelElementCollection",
"value": [
{
"idShort": "DEL-00",
"value": [
{
"idShort": "EndpointMetaDataReference",
"value": {
"type": "ModelReference",
"keys": [
{
"type": "Submodel",
"value": "www.example.com/ids/sm/7142_2140_7032_7989"
},
{
"type": "SubmodelElementList",
"value": "MQTT"
},
{
"type": "SubmodelElementCollection",
"value": "DEL-00"
},
{
"type": "SubmodelElementCollection",
"value": "EndpointMetadata"
}
]
},
"modelType": "ReferenceElement"
},
{
"idShort": "MappingConfiguration",
"typeValueListElement": "SubmodelElement",
"value": [
{
"idShort": "DEL-00",
"value": [
{
"idShort": "InterfaceMetaDataReference",
"value": {
"type": "ModelReference",
"keys": [
{
"type": "Submodel",
"value": "www.example.com/ids/sm/7142_2140_7032_7989"
},
{
"type": "SubmodelElementList",
"value": "MQTT"
},
{
"type": "SubmodelElementCollection",
"value": "DEL-00"
},
{
"type": "SubmodelElementCollection",
"value": "InterfaceMetadata"
}
]
},
"modelType": "ReferenceElement"
},
{
"idShort": "SourceSinkMappings",
"typeValueListElement": "SubmodelElement",
"value": [
{
"idShort": "DEL-00",
"first": {
"type": "ModelReference",
"keys": [
{
"type": "Submodel",
"value": "www.example.com/ids/sm/7142_2140_7032_7989"
},
{
"type": "SubmodelElementList",
"value": "MQTT"
},
{
"type": "SubmodelElementCollection",
"value": "DEL-00"
},
{
"type": "SubmodelElementCollection",
"value": "InterfaceMetadata"
},
{
"type": "SubmodelElementList",
"value": "Properties"
},
{
"type": "SubmodelElementCollection",
"value": "DEL-00"
}
]
},
"second": {
"type": "ModelReference",
"keys": [
{
"type": "Submodel",
"value": "https://example.com/ids/sm/8055_9060_7032_0793"
},
{
"type": "SubmodelElementCollection",
"value": "Params"
},
{
"type": "Property",
"value": "TestParam1"
}
]
},
"modelType": "RelationshipElement"
},
{
"idShort": "DEL-01",
"first": {
"type": "ModelReference",
"keys": [
{
"type": "Submodel",
"value": "www.example.com/ids/sm/7142_2140_7032_7989"
},
{
"type": "SubmodelElementList",
"value": "MQTT"
},
{
"type": "SubmodelElementCollection",
"value": "DEL-00"
},
{
"type": "SubmodelElementCollection",
"value": "InterfaceMetadata"
},
{
"type": "SubmodelElementList",
"value": "Properties"
},
{
"type": "SubmodelElementCollection",
"value": "DEL-01"
}
]
},
"second": {
"type": "ModelReference",
"keys": [
{
"type": "Submodel",
"value": "https://example.com/ids/sm/8055_9060_7032_0793"
},
{
"type": "SubmodelElementCollection",
"value": "Params"
},
{
"type": "Property",
"value": "TestParam2"
}
]
},
"modelType": "RelationshipElement"
},
{
"idShort": "DEL-02",
"first": {
"type": "ModelReference",
"keys": [
{
"type": "Submodel",
"value": "www.example.com/ids/sm/7142_2140_7032_7989"
},
{
"type": "SubmodelElementList",
"value": "MQTT"
},
{
"type": "SubmodelElementCollection",
"value": "DEL-00"
},
{
"type": "SubmodelElementCollection",
"value": "InterfaceMetadata"
},
{
"type": "SubmodelElementList",
"value": "Actions"
},
{
"type": "SubmodelElementCollection",
"value": "DEL-00"
}
]
},
"second": {
"type": "ModelReference",
"keys": [
{
"type": "Submodel",
"value": "https://example.com/ids/sm/8055_9060_7032_0793"
},
{
"type": "SubmodelElementCollection",
"value": "Operations"
},
{
"type": "Operation",
"value": "TestOperation1"
}
]
},
"modelType": "RelationshipElement"
},
{
"idShort": "DEL-03",
"first": {
"type": "ModelReference",
"keys": [
{
"type": "Submodel",
"value": "www.example.com/ids/sm/7142_2140_7032_7989"
},
{
"type": "SubmodelElementList",
"value": "MQTT"
},
{
"type": "SubmodelElementCollection",
"value": "DEL-00"
},
{
"type": "SubmodelElementCollection",
"value": "InterfaceMetadata"
},
{
"type": "SubmodelElementList",
"value": "Actions"
},
{
"type": "SubmodelElementCollection",
"value": "DEL-01"
}
]
},
"second": {
"type": "ModelReference",
"keys": [
{
"type": "Submodel",
"value": "https://example.com/ids/sm/8055_9060_7032_0793"
},
{
"type": "SubmodelElementCollection",
"value": "Operations"
},
{
"type": "Operation",
"value": "TestOperation2"
}
]
},
"modelType": "RelationshipElement"
},
{
"idShort": "DEL-04",
"first": {
"type": "ModelReference",
"keys": [
{
"type": "Submodel",
"value": "www.example.com/ids/sm/7142_2140_7032_7989"
},
{
"type": "SubmodelElementList",
"value": "MQTT"
},
{
"type": "SubmodelElementCollection",
"value": "DEL-00"
},
{
"type": "SubmodelElementCollection",
"value": "InterfaceMetadata"
},
{
"type": "SubmodelElementList",
"value": "Actions"
},
{
"type": "SubmodelElementCollection",
"value": "DEL-02"
}
]
},
"second": {
"type": "ModelReference",
"keys": [
{
"type": "Submodel",
"value": "https://example.com/ids/sm/8055_9060_7032_0793"
},
{
"type": "SubmodelElementCollection",
"value": "Operations"
},
{
"type": "Operation",
"value": "TestOperation3"
}
]
},
"modelType": "RelationshipElement"
}
],
"modelType": "SubmodelElementList"
}
],
"modelType": "SubmodelElementCollection"
}
],
"modelType": "SubmodelElementList"
}
],
"modelType": "SubmodelElementCollection"
}
],
"modelType": "SubmodelElementList"
}
],
"modelType": "Submodel"
}
+86
View File
@@ -0,0 +1,86 @@
{
"idShort": "OperationalData",
"id": "https://example.com/ids/sm/8055_9060_7032_0793",
"submodelElements": [
{
"idShort": "Params",
"value": [
{
"idShort": "TestParam1",
"valueType": "xs:string",
"modelType": "Property"
},
{
"idShort": "TestParam2",
"valueType": "xs:string",
"modelType": "Property"
}
],
"modelType": "SubmodelElementCollection"
},
{
"idShort": "Operations",
"value": [
{
"idShort": "TestOperation1",
"description": [
{
"language": "de",
"text": "Operation, sync mit keinem In/Out"
}
],
"modelType": "Operation"
},
{
"idShort": "TestOperation2",
"description": [
{
"language": "de",
"text": "Operation, sync mit einem Input"
}
],
"inputVariables": [
{
"value": {
"idShort": "TestInput",
"valueType": "xs:string",
"modelType": "Property"
}
}
],
"modelType": "Operation"
},
{
"idShort": "TestOperation3",
"description": [
{
"language": "de",
"text": "Operation, sync mit einem Input und einem Output"
}
],
"inputVariables": [
{
"value": {
"idShort": "TestInput",
"valueType": "xs:int",
"modelType": "Property"
}
}
],
"outputVariables": [
{
"value": {
"idShort": "TestOutput",
"valueType": "xs:float",
"modelType": "Property"
}
}
],
"modelType": "Operation"
}
],
"modelType": "SubmodelElementCollection"
}
],
"modelType": "Submodel"
}
+5 -5
View File
@@ -198,7 +198,7 @@ export default class HTTPInterfaceServer extends AbstractInterfaceServer<Config>
const prop = Traverser.getElementByIdPath(this.aas, sm, idShortPath);
if (prop === null || !AASCoreTypes.isProperty(prop)) return res.status(404).end();
const request: Types.RequestTypes.Request = req.method === "GET" ? { type: "READ", target: prop } : { type: "WRITE", target: prop, extraData: { value: req.body } };
const request: Types.RequestTypes.Request = req.method === "GET" ? { type: "readProperty", target: prop } : { type: "writeProperty", target: prop, extraData: { value: req.body } };
const value = this.onRequestCallback(request);
@@ -215,7 +215,7 @@ export default class HTTPInterfaceServer extends AbstractInterfaceServer<Config>
// TODO
// Parameter?
const success = this.onRequestCallback({ type: "CALL", target: op, extraData: { args: req.body } });
const success = this.onRequestCallback({ type: "invokeAction", target: op, extraData: { args: req.body, async: false } });
return res.send(success ? 204 : 500).end();
}
@@ -226,7 +226,7 @@ export default class HTTPInterfaceServer extends AbstractInterfaceServer<Config>
// TODO
// Invoke operation and get handle
const request: Types.RequestTypes.Request = { type: "CALL-ASYNC", target: op, extraData: { args: req.body } };
const request: Types.RequestTypes.Request = { type: "invokeAction", target: op, extraData: { args: req.body, async: true } };
const handle = this.onRequestCallback(request);
@@ -241,7 +241,7 @@ export default class HTTPInterfaceServer extends AbstractInterfaceServer<Config>
case "operation-status": {
if (req.method !== "GET") return res.status(405).end();
const state = this.onRequestCallback({ type: "GET-OP-STATE", target: handle });
const state = this.onRequestCallback({ type: "xAsyncActionState", target: handle });
if (state === undefined) return res.status(404).end();
@@ -253,7 +253,7 @@ export default class HTTPInterfaceServer extends AbstractInterfaceServer<Config>
case "operation-result": {
if (req.method !== "GET") return res.status(405).end();
const result = this.onRequestCallback({ type: "GET-OP-RESULT", target: handle });
const result = this.onRequestCallback({ type: "xAsyncActionResult", target: handle });
if (result === null) return res.status(404).end();
return res.json({
+71 -35
View File
@@ -1,6 +1,7 @@
import * as mqtt from "mqtt";
import { AbstractConnectionObject } from "aas-multimessagebroker";
import type { AASCoreTypes } from "aas-multimessagebroker";
import { AASCoreTypes, Types } from "aas-multimessagebroker";
import { MMBEvent } from "aas-multimessagebroker/dist/types/requests";
/**
* This is an example module for the asset connection object.
@@ -70,7 +71,10 @@ export default class MQTTConnector extends AbstractConnectionObject<mqtt.IClient
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.observerStore[topic].forEach(sub => sub.cb({ type: "event", value: message.toString("utf-8"), target: sub.target}));
}
if (this.eventSubStore[topic] !== undefined) {
this.eventSubStore[topic].forEach(sub => sub.cb({ type: "event", value: message.toString("utf-8"), target: sub.target}));
}
});
this.client.subscribe("#");
@@ -87,6 +91,10 @@ export default class MQTTConnector extends AbstractConnectionObject<mqtt.IClient
this.client = null;
}
private assertConnected(): void {
if (!this.client || !this.client.connected) throw new Error("Not connected");
}
/**
* Read a property value from the asset.
*
@@ -98,16 +106,13 @@ export default class MQTTConnector extends AbstractConnectionObject<mqtt.IClient
* @override
* @public
*/
public readProperty(prop: AASCoreTypes.Property): any {
const cc = this.mapper.get(prop);
if (cc === undefined) return null;
const errorReturn = cc.default ?? null;
public readProperty(_target: AASCoreTypes.Property, mapping: Types.RequestTypes.ConnectionConfiguration): any {
const errorReturn = mapping.default ?? null;
if (!this.client || !this.client.connected) return errorReturn;
const value = this.messageStore[cc.forms.href.substring(1)];
const value = this.messageStore[mapping.forms[0].href.substring(1)];
if (value === undefined) return errorReturn;
switch (cc.type) {
switch (mapping.type) {
case "integer":
return Number.parseInt(value);
case "float":
@@ -129,11 +134,10 @@ export default class MQTTConnector extends AbstractConnectionObject<mqtt.IClient
* @override
* @public
*/
public writeProperty(prop: AASCoreTypes.Property, value: any): boolean {
const cc = this.mapper.get(prop);
if (cc === undefined || !this.client || !this.client.connected) return false;
public writeProperty(_target: AASCoreTypes.Property, mapping: Types.RequestTypes.ConnectionConfiguration, value: any): boolean {
if (!this.client || !this.client.connected) return false;
const topic = cc.forms.href.substring(1);
const topic = mapping.forms[0].href.substring(1);
if (topic === undefined) return false;
this.client.publish(topic, value.toString());
@@ -149,17 +153,22 @@ export default class MQTTConnector extends AbstractConnectionObject<mqtt.IClient
* @override
* @public
*/
public observeProperty(prop: AASCoreTypes.Property, callback: (value: any) => void): boolean {
const cc = this.mapper.get(prop);
if (cc === undefined || !cc.observable || !this.client || !this.client.connected) return false;
public observeProperty(target: AASCoreTypes.Property, mapping: Types.RequestTypes.ConnectionConfiguration, cb: (value: Types.RequestTypes.MMBEvent) => void): boolean {
if (!mapping.observable || !this.client || !this.client.connected) return false;
const topic = cc.forms.href.substring(1);
if (this.observerStore[topic] === undefined) this.observerStore[topic] = [callback];
else this.observerStore[topic].push(callback);
const topic = mapping.forms[0].href.substring(1);
if (this.observerStore[topic] === undefined) this.observerStore[topic] = [{ target, cb }];
else this.observerStore[topic].push({ target, cb });
return true;
}
public unobserveProperty(target: AASCoreTypes.Property, mapping: Types.RequestTypes.ConnectionConfiguration): void {
const topic = mapping.forms[0].href.substring(1);
this.observerStore[topic];
//! TODO
}
/**
* Calls an action synchronously.
* @param action Action
@@ -168,13 +177,22 @@ export default class MQTTConnector extends AbstractConnectionObject<mqtt.IClient
* @returns Return value of action.
* @override
* @public
* @alpha
* @beta
*/
public callActionSync(action: AASCoreTypes.Operation, args: Record<string, any>): any {
// TODO
// Vorher mal ne ordentliche Mapping-Definition
public callActionSync(_target: AASCoreTypes.Operation, mapping: Types.RequestTypes.ConnectionConfiguration, args: Record<string, any>): any {
this.assertConnected();
throw new Error("Method not implemented.");
const form: Types.AIDTypes.InterfaceFormMQTTAction = mapping.forms[0];
const topic = form.href.substring(1);
if (topic === undefined || !mapping.forms) throw new Error("Mapping invalid.");
const message = JSON.stringify(args);
this.execute(form.mqv_controlPacketValue ?? "PUBLISH", topic, message);
// There is currently no way to get the result of an action
// Not because we cannot wait but because we don't know where the result is sent!
return;
}
/**
@@ -187,11 +205,17 @@ export default class MQTTConnector extends AbstractConnectionObject<mqtt.IClient
* @public
* @alpha
*/
public callActionAsync(action: AASCoreTypes.Operation, args: Record<string, any>): string | null {
// TODO
// Vorher mal ne ordentliche Mapping-Definition
public callActionAsync(_target: AASCoreTypes.Operation, mapping: Types.RequestTypes.ConnectionConfiguration, args: Record<string, any>): string {
this.assertConnected();
throw new Error("Method not implemented.");
const form: Types.AIDTypes.InterfaceFormMQTTAction = mapping.forms[0];
const topic = form.href.substring(1);
if (topic === undefined || !mapping.forms) throw new Error("Mapping invalid.");
const message = JSON.stringify(args);
this.execute(form.mqv_controlPacketValue ?? "PUBLISH", topic, message);
return this.generateAsyncHandle();
}
/**
@@ -203,11 +227,14 @@ export default class MQTTConnector extends AbstractConnectionObject<mqtt.IClient
* @public
* @alpha
*/
public subscribeEvent(event: AASCoreTypes.BasicEventElement, callback: (event: AASCoreTypes.BasicEventElement) => void): boolean {
// TODO
// Vorher mal ne ordentliche Mapping-Definition
public subscribeEvent(target: AASCoreTypes.Class, mapping: Types.RequestTypes.ConnectionConfiguration, cb: (event: Types.RequestTypes.MMBEvent) => void): boolean {
if (!mapping.observable || !this.client || !this.client.connected) return false;
throw new Error("Method not implemented.");
const topic = mapping.forms[0].href.substring(1);
if (this.eventSubStore[topic] === undefined) this.eventSubStore[topic] = [{ target, cb }];
else this.eventSubStore[topic].push({ target, cb });
return true;
}
/**
@@ -217,11 +244,20 @@ export default class MQTTConnector extends AbstractConnectionObject<mqtt.IClient
* @public
* @alpha
*/
public unsubscribeEvent(event: AASCoreTypes.BasicEventElement): void {
public unsubscribeEvent(event: AASCoreTypes.Class, mapping: Types.RequestTypes.ConnectionConfiguration): void {
// TODO
// Vorher mal ne ordentliche Mapping-Definition
// How do we know which event to unsubscribe from?
}
throw new Error("Method not implemented.");
private execute(packetType: "PUBLISH" | "SUBSCRIBE" | "UNSUBSCRIBE", topic: string, message?: string) {
this.assertConnected();
if (this.client){
switch (packetType) {
case "PUBLISH": return this.client.publish(topic, message ?? "1");
case "SUBSCRIBE": return this.client.subscribe(topic);
case "UNSUBSCRIBE": return this.client.unsubscribe(topic);
}
}
}
}
+5 -4
View File
@@ -1,9 +1,10 @@
import type { types } from "@aas-core-works/aas-core3.0-typescript";
import type { ConnectionConfiguration, AIMCMap } from "./types/aimcConf";
import type { AIMCMap } from "./types/aimcConf";
import type { ConnectionConfiguration } from "./types/requests";
import type { InterfaceAction, InterfaceActionForm, InterfaceEvent, InterfaceEventForm, InterfaceProperty, InterfacePropertyForm } from "./types/aidConf";
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.
@@ -93,10 +94,10 @@ export default class AIMCMapper {
if (parsedPropConf === null) continue;
this.map.set(second, {
this.map.set(second, ({
...ep,
...parsedPropConf
});
} as any as ConnectionConfiguration));
}
}
}
+12 -16
View File
@@ -1,8 +1,8 @@
import type { types } from "@aas-core-works/aas-core3.0-typescript";
import AIMCMapper from "./AIMCMapper";
import { v4 } from "uuid";
import type { EndpointMetadata, FormOp, InterfaceActionForm, InterfaceEventForm, InterfacePropertyForm } from "./types/aidConf";
import type { ConnectionConfiguration } from "./types/aimcConf";
import type { EndpointMetadata } from "./types/aidConf";
import type { ConnectionConfiguration, MMBEvent } from "./types/requests";
type ConnectionType = "ON_DEMAND" | "PERMANENT";
@@ -51,12 +51,12 @@ export default abstract class AbstractConnectionObject<ConfigInterface> {
* A store for all observers and their callbacks.
* @sealed
*/
protected readonly observerStore: Record<any, ((value: any) => void)[]> = {};
protected readonly observerStore: Record<any, { target: types.Property, cb:((event: MMBEvent) => void) }[]> = {};
/**
* A store for all event subscriptions and their callbacks.
* @sealed
*/
protected readonly eventSubStore: Record<any, ((value: any) => void)[]> = {};
protected readonly eventSubStore: Record<any, { target: types.Class, cb:((event: MMBEvent) => void) }[]> = {};
/**
* A store for all async action handles and their results.
* @sealed
@@ -100,7 +100,7 @@ export default abstract class AbstractConnectionObject<ConfigInterface> {
* @public
* @virtual
*/
public abstract readProperty(mapping: ConnectionConfiguration): void;
public abstract readProperty(target: types.Property, mapping: ConnectionConfiguration): void;
/**
* Write a property value to the asset.
@@ -109,7 +109,7 @@ export default abstract class AbstractConnectionObject<ConfigInterface> {
* @public
* @virtual
*/
public abstract writeProperty(mapping: ConnectionConfiguration, value: any): void;
public abstract writeProperty(target: types.Property, mapping: ConnectionConfiguration, value: any): void;
/**
* Observe a property value from the asset.
@@ -119,7 +119,7 @@ export default abstract class AbstractConnectionObject<ConfigInterface> {
* @public
* @virtual
*/
public abstract observeProperty(mapping: ConnectionConfiguration, callback: (value: any) => void): boolean;
public abstract observeProperty(target: types.Property, mapping: ConnectionConfiguration, callback: (value: any) => void): boolean;
/**
* Unobserve a property value from the asset.
@@ -127,7 +127,7 @@ export default abstract class AbstractConnectionObject<ConfigInterface> {
* @public
* @virtual
*/
public abstract unobserveProperty(mapping: ConnectionConfiguration): void;
public abstract unobserveProperty(target: types.Property, mapping: ConnectionConfiguration): void;
/**
* Call an action on the asset synchronously.
@@ -137,7 +137,7 @@ export default abstract class AbstractConnectionObject<ConfigInterface> {
* @public
* @virtual
*/
public abstract callActionSync(mapping: ConnectionConfiguration, args: Record<string, any>): any;
public abstract callActionSync(target: types.Operation, mapping: ConnectionConfiguration, args: Record<string, any>): any;
/**
* Call an action on the asset asynchronously.
@@ -147,7 +147,7 @@ export default abstract class AbstractConnectionObject<ConfigInterface> {
* @public
* @virtual
*/
public abstract callActionAsync(mapping: ConnectionConfiguration, args: Record<string, any>): string | null;
public abstract callActionAsync(target: types.Operation, mapping: ConnectionConfiguration, args: Record<string, any>): string | null;
/**
* Read the state of an async action.
@@ -179,7 +179,7 @@ export default abstract class AbstractConnectionObject<ConfigInterface> {
* @public
* @virtual
*/
public abstract subscribeEvent(mapping: ConnectionConfiguration, callback: (event: types.BasicEventElement) => void): boolean;
public abstract subscribeEvent(target: types.Class, mapping: ConnectionConfiguration, callback: (event: MMBEvent) => void): boolean;
/**
* Unsubscribe from an event.
@@ -187,7 +187,7 @@ export default abstract class AbstractConnectionObject<ConfigInterface> {
* @public
* @virtual
*/
public abstract unsubscribeEvent(mapping: ConnectionConfiguration): void;
public abstract unsubscribeEvent(target: types.Class, mapping: ConnectionConfiguration): void;
/**
* Generate a handle for an async action.
@@ -199,8 +199,4 @@ export default abstract class AbstractConnectionObject<ConfigInterface> {
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);
}
}
+17 -9
View File
@@ -4,8 +4,8 @@ import type { Request } from "./types/requests";
import type AbstractConnectionObject from "./abstractConnectionObject";
import AIMCMapper from "./AIMCMapper";
import AIDParser from "./parser/AIDParser";
import type { EndpointMetadata, InterfaceActionForm, InterfaceEventForm, InterfacePropertyForm } from "./types/aidConf";
import type { ConnectionConfiguration } from "./types/aimcConf";
import type { EndpointMetadata, InterfaceActionForm, InterfaceEventForm, InterfaceForm, InterfacePropertyForm } from "./types/aidConf";
import type { ConnectionConfiguration } from "./types/requests";
type AASRegistration = {
aas: types.Environment,
@@ -192,16 +192,24 @@ export default class MultiMessageBroker {
}
else {
const mapping = getMapping(request.target);
mapping.forms = mapping.forms.filter((form: InterfaceForm) => form.op.toLocaleLowerCase() === request.type.toLocaleLowerCase());
if (mapping.forms.length === 0) throw new ReferenceError(`No form found for ${(request.target as any).idShort} with operation ${request.type}`);
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);
case "readProperty": return connector.readProperty(request.target, mapping);
case "writeProperty": return connector.writeProperty(request.target, mapping, request.extraData.value);
case "observeProperty": return connector.observeProperty(request.target, mapping, request.extraData.callback);
case "unobserveProperty": return connector.unobserveProperty(request.target, mapping);
case "subscribeEvent": {
return connector.subscribeEvent(request.target, mapping, request.extraData.callback);
}
case "unsubscribeEvent": {
return connector.unsubscribeEvent(request.target, mapping);
}
case "invokeAction": return request.extraData.async ? connector.callActionAsync(request.target, mapping, request.extraData.args) : connector.callActionSync(request.target, mapping, request.extraData.args);
}
}
}
+9 -10
View File
@@ -2,7 +2,6 @@ import * as aasCore from "@aas-core-works/aas-core3.0-typescript";
import type { AssetInterfacesDescription, EndpointMetadata, InterfaceDescription, InterfaceMetadata, InterfaceAction, InterfaceEvent, InterfaceProperty, InterfacePropertyForm, InterfaceActionForm, InterfaceFormBase, InterfaceFormBaseHTTP, InterfaceEventForm } from "../types/aidConf";
import { AvailableEndpoint, endpointAvailable } from "../types/common";
import Traverser from "../helper/traverser";
import { Property } from "@aas-core-works/aas-core3.0-typescript/dist/types/types";
/**
* Class for static methods to parse the AssetInterfacesDescription Submodel
@@ -285,7 +284,7 @@ export default class AIDParser {
if (!endpoint) return true;
return validOperations[endpoint][element].includes(operation);
return validOperations[endpoint][element].includes(operation.toLocaleLowerCase());
}
private static parseAdditionalFormProperties(form: aasCore.types.SubmodelElementCollection, currentlyParsed: any, endpointProtocol?: AvailableEndpoint): InterfacePropertyForm | InterfaceActionForm | InterfaceEventForm {
@@ -297,21 +296,21 @@ export default class AIDParser {
// If method is set and it's a valid HTTP method we take it, else we use the default
// setting for read/write
const methodName = ["GET", "PUT", "POST", "DELETE", "PATCH"].includes(method.value?.toLocaleUpperCase() ?? "") ? method.value?.toLocaleUpperCase() : AIDParser.getDefaultMethod(currentlyParsed.op) ?? "GET";
currentlyParsed["htv:methodName"] = methodName;
currentlyParsed.htv_methodName = methodName;
}
const headers = Traverser.findElement(form, element => (element as any).idShort?.toLocaleLowerCase() === "htv:headers");
if (headers !== null && aasCore.types.isSubmodelElementList(headers)) {
const parsedHeaders: { "htv:fieldName": string, "htv:fieldValue": string }[] = [];
const parsedHeaders: { htv_fieldName: string, htv_fieldValue: string }[] = [];
for (const header of headers.overValueOrEmpty()) {
if (!aasCore.types.isSubmodelElementCollection(header) || !header.value) continue;
const fieldName = Traverser.findElement(header, element => (element as any).idShort?.toLocaleLowerCase() === "htv:fieldname");
const fieldValue = Traverser.findElement(header, element => (element as any).idShort?.toLocaleLowerCase() === "htv:fieldvalue");
if (fieldName === null || !aasCore.types.isProperty(fieldName) || !fieldName.value || fieldValue === null || !aasCore.types.isProperty(fieldValue) || !fieldValue.value) continue;
parsedHeaders.push({ "htv:fieldName": fieldName.value, "htv:fieldValue": fieldValue.value });
parsedHeaders.push({ htv_fieldName: fieldName.value, htv_fieldValue: fieldValue.value });
}
currentlyParsed["htv:headers"] = parsedHeaders;
currentlyParsed.htv_headers = parsedHeaders;
}
break;
}
@@ -322,12 +321,12 @@ export default class AIDParser {
// If controlPacket is set and it's a valid MQTT control packet we take it, else we use the default
// setting for read/write
const controlPacketValue = ["PUBLISH", "SUBSCRIBE", "UNSUBSCRIBE"].includes(controlPacket.value?.toLocaleUpperCase() ?? "") ? controlPacket.value?.toLocaleUpperCase() : AIDParser.getDefaultMethod(currentlyParsed.op);
currentlyParsed["mqv:controlPacketValue"] = controlPacketValue;
currentlyParsed.mqv_controlPacketValue = controlPacketValue;
}
const options = Traverser.findElement(form, element => (element as any).idShort?.toLocaleLowerCase() === "mqv:options");
if (options !== null && aasCore.types.isSubmodelElementList(options)) {
const parsedOptions: ({ "mqv:optionName": string, "mqv:optionValue": number | boolean })[] = [];
const parsedOptions: ({ mqv_optionName: string, mqv_optionValue: number | boolean })[] = [];
for (const option of options.overValueOrEmpty()) {
if (!aasCore.types.isSubmodelElementCollection(option) || !option.value) continue;
@@ -335,9 +334,9 @@ export default class AIDParser {
const optionValue = Traverser.findElement(option, element => (element as any).idShort?.toLocaleLowerCase() === "mqv:optionvalue");
if (optionName === null || !aasCore.types.isProperty(optionName) || !optionName.value || optionValue === null || !aasCore.types.isProperty(optionValue) || !optionValue.value) continue;
const value = Number.isNaN(Number.parseInt(optionValue.value)) ? optionValue.value === "true" : Number.parseInt(optionValue.value);
parsedOptions.push({ "mqv:optionName": optionName.value, "mqv:optionValue": value });
parsedOptions.push({ mqv_optionName: optionName.value, mqv_optionValue: value });
}
currentlyParsed["mqv:options"] = parsedOptions;
currentlyParsed.mqv_options = parsedOptions;
}
break;
+34 -42
View File
@@ -48,25 +48,25 @@ It should be pretty easy to extend this.
So let's derive some of this from the WoT and see what we can achieve.
*/
export type FormOp = "readproperty" |
"writeproperty" |
"observeproperty" |
"unobserveproperty" |
"invokeaction" |
"subscribeevent" |
"unsubscribeevent";
export type FormOp = "readProperty" |
"writeProperty" |
"observeProperty" |
"unobserveProperty" |
"invokeAction" |
"subscribeEvent" |
"unsubscribeEvent";
// The following are defined in WoT but not useful in our case
//"readallproperties" |
//"writeallproperties" |
//"readmultipleproperties" |
//"writemultipleproperties";
type FormOpHTTPProp = "readproperty" | "writeproperty";
type FormOpHTTPAction = "invokeaction";
type FormOpHTTPProp = "readProperty" | "writeProperty";
type FormOpHTTPAction = "invokeAction";
type FormOpMQTTProp = "readproperty" | "writeproperty" | "observeproperty" | "unobserveproperty";
type FormOpMQTTAction = "invokeaction";
type FormOpMQTTEvent = "subscribeevent" | "unsubscribeevent";
type FormOpMQTTProp = "readProperty" | "writeProperty" | "observeProperty" | "unobserveProperty";
type FormOpMQTTAction = "invokeAction";
type FormOpMQTTEvent = "subscribeEvent" | "unsubscribeEvent";
export type InterfaceFormBase = {
href: string;
@@ -75,50 +75,51 @@ export type InterfaceFormBase = {
}
export type InterfaceFormBaseHTTP = InterfaceFormBase & {
"htv:methodName": "GET" | "PUT" | "POST" | "DELETE" | "PATCH";
"htv:headers": {
"htv:fieldName": string;
"htv:fieldValue": string;
htv_methodName?: "GET" | "PUT" | "POST" | "DELETE" | "PATCH";
htv_headers?: {
htv_fieldName: string;
htv_fieldValue: string;
}[];
op: FormOpHTTPProp | FormOpHTTPAction;
}
type IFormHTTPProp = InterfaceFormBaseHTTP & {
export type InterfaceFormHTTPProp = InterfaceFormBaseHTTP & {
op: FormOpHTTPProp;
}
type IFormHTTPAction = InterfaceFormBaseHTTP & {
export type InterfaceFormHTTPAction = InterfaceFormBaseHTTP & {
op: FormOpHTTPAction;
}
export type InterfaceFormBaseMQTT = InterfaceFormBase & {
"mqv:controlPacketValue": "PUBLISH" | "SUBSCRIBE" | "UNSUBSCRIBE";
"mqv:options": ({
"mqv:optionName": "qos";
"mqv:optionValue": 0 | 1 | 2;
mqv_controlPacketValue?: "PUBLISH" | "SUBSCRIBE" | "UNSUBSCRIBE";
mqv_options?: ({
mqv_optionName: "qos";
mqv_optionValue: 0 | 1 | 2;
} | {
"mqv:optionName": "retain" | "dup";
"mqv:optionValue": boolean;
mqv_optionName: "retain" | "dup";
mqv_optionValue: boolean;
})[];
op: FormOpMQTTProp | FormOpMQTTAction | FormOpMQTTEvent;
}
type IFormMQTTProp = InterfaceFormBaseMQTT & {
export type InterfaceFormMQTTProp = InterfaceFormBaseMQTT & {
op: FormOpMQTTProp;
}
type IFormMQTTAction = InterfaceFormBaseMQTT & {
export type InterfaceFormMQTTAction = InterfaceFormBaseMQTT & {
op: FormOpMQTTAction;
}
type IFormMQTTEvent = InterfaceFormBaseMQTT & {
export type InterfaceFormMQTTEvent = InterfaceFormBaseMQTT & {
op: FormOpMQTTEvent;
}
export type InterfacePropertyForm = IFormHTTPProp | IFormMQTTProp;
export type InterfaceActionForm = IFormHTTPAction | IFormMQTTAction;
export type InterfaceEventForm = IFormMQTTEvent;
export type InterfacePropertyForm = InterfaceFormHTTPProp | InterfaceFormMQTTProp;
export type InterfaceActionForm = InterfaceFormHTTPAction | InterfaceFormMQTTAction;
export type InterfaceEventForm = InterfaceFormMQTTEvent;
export type InterfaceForm = InterfacePropertyForm | InterfaceActionForm | InterfaceEventForm;
export type InterfaceProperty = {
title?: string;
@@ -139,20 +140,11 @@ export type InterfaceProperty = {
}
/** @beta */
export type InterfaceAction = InterfaceActionSync | InterfaceActionAsync;
type InterfaceActionBase = {
export type InterfaceAction = {
title?: string;
type: string;
type?: string;
forms: InterfaceActionForm[];
}
type InterfaceActionSync = InterfaceActionBase & {
async: false;
}
type InterfaceActionAsync = {
async: true;
async: boolean;
}
/** @beta */
+1 -4
View File
@@ -1,6 +1,6 @@
import type { types } from "@aas-core-works/aas-core3.0-typescript";
import type { AvailableEndpoint } from "./common";
import type { EndpointMetadata, InterfaceAction, InterfaceEvent, InterfaceProperty } from "./aidConf";
import type { ConnectionConfiguration } from "./requests";
export type AssetInterfacesMappingConfiguration = Record<AvailableEndpoint, MappingConfEntry[]>;
@@ -15,6 +15,3 @@ export type MappingConfiguration = {
}
export type AIMCMap = Map<types.Class, ConnectionConfiguration>;
type InterfaceElement = InterfaceProperty | InterfaceAction | InterfaceEvent;
export type ConnectionConfiguration = EndpointMetadata & InterfaceElement; // Wenn Transformationen implementiert werden, dann hier anpassen
+14 -5
View File
@@ -1,4 +1,5 @@
import type { types } from "@aas-core-works/aas-core3.0-typescript";
import type { EndpointMetadata, InterfaceForm } from "./aidConf";
/**
* Read Property Request
@@ -36,7 +37,7 @@ type ObserveRequest = {
type: "observeProperty";
target: types.Property;
extraData: {
callback: (value: any) => void;
callback: (event: MMBEvent) => void;
}
}
@@ -52,7 +53,7 @@ type UnobserveRequest = {
type: "unobserveProperty";
target: types.Property;
extraData: {
callback: (value: any) => void;
callback: (event: MMBEvent) => void;
}
}
@@ -66,9 +67,9 @@ type UnobserveRequestCallback = (request: ObserveRequest) => boolean;
*/
type SubscribeRequest = {
type: "subscribeEvent";
target: types.BasicEventElement;
target: types.Class;
extraData: {
callback: (value: any) => void;
callback: (event: MMBEvent) => void;
}
}
@@ -82,7 +83,7 @@ type SubscribeRequestCallback = (request: SubscribeRequest) => boolean;
*/
type UnsubscribeRequest = {
type: "unsubscribeEvent";
target: types.BasicEventElement;
target: types.Class;
}
/**
@@ -142,3 +143,11 @@ export type Request = GetRequest | WriteRequest | ObserveRequest | UnobserveRequ
* Combined Request Callback Type
*/
export type OnRequestCallback = (request: Request) => any & GetRequestCallback & WriteRequestCallback & ObserveRequestCallback & UnobserveRequestCallback & CallRequestCallback & AsyncStateRequestCallback & AsyncResultRequestCallback & SubscribeRequestCallback & UnsubscribeRequestCallback;
export type ConnectionConfiguration = EndpointMetadata & InterfaceForm; // Wenn Transformationen implementiert werden, dann hier anpassen
export type MMBEvent = {
type: "changed" | "event";
target: types.Class;
value: any;
}
+957 -1
View File
File diff suppressed because one or more lines are too long