From f59f199207849966d3e9412d43dd668d6bf68908 Mon Sep 17 00:00:00 2001 From: sn0wcat Date: Thu, 20 May 2021 23:56:14 +0200 Subject: [PATCH] chore: release 3.12.0 (#133) * feat: assetinfo + customcode * chore: alpha release: 3.12.0-1 :tada: * docs: assetinfo and sdk function docs * chore: release 3.12.0 :tada: * chore: fixed test * feat: error handling for new messages --- CHANGELOG.md | 5 +- README.md | 32 +++++ package-lock.json | 204 ++++++++++++++++---------------- package.json | 18 +-- src/mindconnect-ops.ts | 63 +++++++++- src/mindconnect-schema.ts | 72 ++++++++++- src/mindconnect-utils.ts | 18 ++- src/mindconnect.html | 91 +++++++++++++- src/mindconnect.ts | 14 ++- test/mindconnect-schema.spec.ts | 36 ++++-- 10 files changed, 420 insertions(+), 133 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 706b0aa..e2527b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,12 @@ # Changelog -## 3.12.0 (Vienna Siberian Tigers) - June 2021 +## 3.12.0 (Vienna Siberian Tigers) - May 2021 ## New Features 3.12.0 +- Support for "bidirectional" communication with MindSphere [#129] +- new message type to read asset information [#129] +- new message type to execute a custom javascript script using MindSphere javascript/typescript SDK [#129] - Bumped all dependencies ## 3.11.0 (Vienna Jaguarundi) - March 2021 diff --git a/README.md b/README.md index d04b871..c8c7c8d 100644 --- a/README.md +++ b/README.md @@ -214,6 +214,38 @@ Please note: - You can pass either a javascript buffer or path to file in the dataLakeFile property for upload - The subTenantId can be optionally added to the messsage +#### Reading Asset Information + +You can read the data (e.g. static asset variables, or full asset information) from MindSphere using the following message. This can be used to implement a "digital shadow/digital twin" pattern, where the change in the MindSphere variables is reflected to the real world asset. See [bidirectional communication example flow](https://playground.mindconnect.rocks/#flow/9ff72be.3d502d8) on playground for a full example. + +```javascript +msg.payload = { + "assetId": "{assetId}", + "includeShared": false, + "propertyNames": [] +}; + +return msg; +``` + +You can reduce the number of items in payload by specifying list of properties to include in the message: e.g. +`propertyNames: ["variables"] or ["location"]` + +#### Executing custom functions using MindSphere javascript/typescript SDK + +The node can be used to execute a complex script which uses [MindSphere javascript/typescript SDK](https://opensource.mindsphere.io/docs/mindconnect-nodejs/sdk/index.html). The node will create an asyncronous function with one parameter (sdk) and the specified function body and execute it. You can only call the MindSphere APIs which allow agent authorization. + +```javascript +msg.payload = { + function: ` +const assetManagement = sdk.GetAssetManagementClient(); +const asset = await assetManagement.GetAsset('{assetId}'); +return asset; +`}; + +return msg; +``` + #### Error handling in the flows The node can be configured to retry all mindsphere operations (1-10 times, with delay of time \* 300ms before the next try) diff --git a/package-lock.json b/package-lock.json index af1819a..ac44b20 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@mindconnect/node-red-contrib-mindconnect", - "version": "3.12.0-0", + "version": "3.12.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -417,9 +417,9 @@ } }, "lodash": { - "version": "4.17.19", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", - "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, "minimist": { @@ -552,27 +552,27 @@ "dev": true }, "@mindconnect/mindconnect-nodejs": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/@mindconnect/mindconnect-nodejs/-/mindconnect-nodejs-3.12.0.tgz", - "integrity": "sha512-Tl2GOWZaHHNBEmaKKBvhKjvMNVfREw6Wg0il7XsDY589adN6q9bZqPmroHoLD+nPFd7iga2lWoHw7igvO5olTA==", + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/@mindconnect/mindconnect-nodejs/-/mindconnect-nodejs-3.13.0.tgz", + "integrity": "sha512-/ac+nrwtLJHKd/JbERKn6RdQTIBzE7rIcL8XLRmRHF6SyFPPZyrb4gDIVKpUTqQoHy8RFx5qad3Vr51OKNFndg==", "requires": { "ajv": "^6.12.6", "ajv-keywords": "^3.5.2", - "async-lock": "^1.2.8", - "chalk": "^4.1.0", + "async-lock": "^1.3.0", + "chalk": "^4.1.1", "commander": "^7.2.0", - "cross-fetch": "^3.1.2", + "cross-fetch": "^3.1.4", "csvtojson": "^2.0.10", "debug": "^4.3.1", "https-proxy-agent": "^5.0.0", "json-groupby": "^1.1.0", "jsonwebtoken": "^8.5.1", "lodash": "^4.17.21", - "mime-types": "^2.1.29", + "mime-types": "^2.1.30", "ora": "^5.4.0", "rsa-pem-to-jwk": "^1.1.3", "update-notifier": "^5.1.0", - "url-search-params-polyfill": "^8.1.0", + "url-search-params-polyfill": "^8.1.1", "uuid": "^8.3.2" }, "dependencies": { @@ -798,13 +798,13 @@ } }, "@node-red/editor-api": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@node-red/editor-api/-/editor-api-1.3.3.tgz", - "integrity": "sha512-PafnnsKUYDQy+bQfk7THSCTy0zncDMvbn4HXGkavqN4nEuvm0bdvA6ZhBNa5G/WaKPt0srptl2KKCsavd3O8jA==", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@node-red/editor-api/-/editor-api-1.3.5.tgz", + "integrity": "sha512-IJyfyVhEvkf2YNRPoR+1SPSi76evVV/boAKhOeVoIMBBeuYd1wM1ihMM3rnNWDYXjN/7CbTKz2DVmdMQ5aCIeQ==", "dev": true, "requires": { - "@node-red/editor-client": "1.3.3", - "@node-red/util": "1.3.3", + "@node-red/editor-client": "1.3.5", + "@node-red/util": "1.3.5", "bcrypt": "3.0.6", "bcryptjs": "2.4.3", "body-parser": "1.19.0", @@ -838,15 +838,15 @@ } }, "@node-red/editor-client": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@node-red/editor-client/-/editor-client-1.3.3.tgz", - "integrity": "sha512-08vlp1s5yrUSidgjUg6/3sQIZ/c7r08oUNYZaN9kEgKVhYBRizet0fHBev8OWEO7nrMJAs8nQmYQGTRg4P/2uA==", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@node-red/editor-client/-/editor-client-1.3.5.tgz", + "integrity": "sha512-B/JB7KuSeTHkiwzrQtCgmkUfHN5nP2htUoE4wkTt118IA/1xUWXEtl+WnT5aDLiFUyFI21q9Fl4BnDOa7JqLEw==", "dev": true }, "@node-red/nodes": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@node-red/nodes/-/nodes-1.3.3.tgz", - "integrity": "sha512-ztCxN5ZWlUf8YkMD9NZd+izrnFVkIgv1ayzZzwCx8gsqLyGIQkzESgfKnqqU4ID8ih/r5Xs2SNl+XP9+OJEO6w==", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@node-red/nodes/-/nodes-1.3.5.tgz", + "integrity": "sha512-kLZOn8YDiDemJ/ufx0eagpmPPbUzLifgvoF1fHq9Jzf+K/xWtzDLHqNyLM6VSzo6QlCAnS939dU9kDb5G3fs3Q==", "dev": true, "requires": { "ajv": "6.12.6", @@ -969,12 +969,12 @@ } }, "@node-red/registry": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@node-red/registry/-/registry-1.3.3.tgz", - "integrity": "sha512-JaHsvXxz9/DJ02NwlBLeUTYPGZEpRvOFR99D34YHF753bw8ocROnYAJemNTmNbua/jW4RRL0tCXuJlrOQZNSGA==", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@node-red/registry/-/registry-1.3.5.tgz", + "integrity": "sha512-wy74xMnD87v/oB5P/HnUiJ6kTgdcCTgIJadeRBh0DSwcMjG3MhSxe8QCk3U9BxLH1fxw0IwxWbNgGwzuM8EOdw==", "dev": true, "requires": { - "@node-red/util": "1.3.3", + "@node-red/util": "1.3.5", "semver": "6.3.0", "tar": "6.1.0", "uglify-js": "3.13.3" @@ -1055,13 +1055,13 @@ } }, "@node-red/runtime": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@node-red/runtime/-/runtime-1.3.3.tgz", - "integrity": "sha512-+tjva7c1lI+87ho1XTS5Lj0t+OapuA+/W3hRb1zXUTNfflUrkVcMMSujMw5v+V2Z5/cMNJBRgEYj38svj9pG3w==", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@node-red/runtime/-/runtime-1.3.5.tgz", + "integrity": "sha512-4Cz83wX9trvhhPxFkF5HfYzqYQEcVOYf4f1lSDHP2xYowE22+x4BTCs8gZA4hCI4kbon3eyaU5NrzlrKd+nZbQ==", "dev": true, "requires": { - "@node-red/registry": "1.3.3", - "@node-red/util": "1.3.3", + "@node-red/registry": "1.3.5", + "@node-red/util": "1.3.5", "async-mutex": "0.3.1", "clone": "2.1.2", "express": "4.17.1", @@ -1078,9 +1078,9 @@ } }, "@node-red/util": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@node-red/util/-/util-1.3.3.tgz", - "integrity": "sha512-xB76cff/HlXJcbZlOe1duqBiCoqJTHM4KKFElcSJGNVjQSeOhZgxMsuECFHCCCh8Y9TVkL96VFwWybq2fKonew==", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@node-red/util/-/util-1.3.5.tgz", + "integrity": "sha512-1TTvTwvGMSe9SQMEKN5zGdyGMv8Kth49eiUcdPnUWigtafk1BE9iP7gVSyLVYgijzCwh40GUkSYey0TUaSbRBw==", "dev": true, "requires": { "clone": "2.1.2", @@ -1172,9 +1172,9 @@ } }, "@types/chai": { - "version": "4.2.16", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.16.tgz", - "integrity": "sha512-vI5iOAsez9+roLS3M3+Xx7w+WRuDtSmF8bQkrbcIJ2sC1PcDgVoA0WGpa+bIrJ+y8zqY2oi//fUctkxtIcXJCw==", + "version": "4.2.18", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.18.tgz", + "integrity": "sha512-rS27+EkB/RE1Iz3u0XtVL5q36MGDWbgYe7zWiodyKNUnthxY0rukK5V36eiUCtCisB7NN8zKYH6DO2M37qxFEQ==", "dev": true }, "@types/color-name": { @@ -1190,9 +1190,9 @@ "dev": true }, "@types/node": { - "version": "14.14.41", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.41.tgz", - "integrity": "sha512-dueRKfaJL4RTtSa7bWeTK1M+VH+Gns73oCgzvYfHZywRCoPSd8EkXBL0mZ9unPTveBn+D9phZBaxuzpwjWkW0g==", + "version": "15.3.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-15.3.1.tgz", + "integrity": "sha512-weaeiP4UF4XgF++3rpQhpIJWsCTS4QJw5gvBhQu6cFIxTwyxWIe3xbnrY/o2lTCQ0lsdb8YIUDUvLR4Vuz5rbw==", "dev": true }, "@types/node-fetch": { @@ -1288,9 +1288,9 @@ } }, "ajv": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.1.0.tgz", - "integrity": "sha512-B/Sk2Ix7A36fs/ZkuGLIR86EdjbgR6fsAcbx9lOP/QBSXujDNbVmIS/U4Itz5k8fPFDeVZl/zQ/gJW4Jrq6XjQ==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.4.0.tgz", + "integrity": "sha512-7QD2l6+KBSLwf+7MuYocbWvRPdOu63/trReTLu2KFwkgctnub1auoF+Y1WYcm09CTM7quuscrzqmASaLHC/K4Q==", "requires": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -1306,9 +1306,9 @@ } }, "ajv-formats": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.0.2.tgz", - "integrity": "sha512-Brah4Uo5/U8v76c6euTwtjVFFaVishwnJrQBYpev1JRh4vjA1F4HY3UzQez41YUCszUCXKagG8v6eVRBHV1gkw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.0.tgz", + "integrity": "sha512-USH2jBb+C/hIpwD2iRjp0pe0k+MvzG0mlSn/FIdCgQhUb9ALPRjt2KIQdfZDS9r0ZIeUAg7gOu9KL0PFqGqr5Q==", "requires": { "ajv": "^8.0.0" } @@ -1652,9 +1652,9 @@ "dev": true }, "async-lock": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.2.8.tgz", - "integrity": "sha512-G+26B2jc0Gw0EG/WN2M6IczuGepBsfR1+DtqLnyFSH4p2C668qkOCtEkGNVEaaNAVlYwEMazy1+/jnLxltBkIQ==" + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.3.0.tgz", + "integrity": "sha512-8A7SkiisnEgME2zEedtDYPxUPzdv3x//E7n5IFktPAtMYSEAV7eNJF0rMwrVyUFj6d/8rgajLantbjcNRQYXIg==" }, "async-mutex": { "version": "0.3.1", @@ -2659,9 +2659,9 @@ } }, "concurrently": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-6.0.2.tgz", - "integrity": "sha512-u+1Q0dJG5BidgUTpz9CU16yoHTt/oApFDQ3mbvHwSDgMjU7aGqy0q8ZQyaZyaNxdwRKTD872Ux3Twc6//sWA+Q==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-6.1.0.tgz", + "integrity": "sha512-jy+xj49pvqeKjc2TAVXRIhrgPG51eBKDZti0kZ41kaWk9iLbyWBjH6KMFpW7peOLkEymD+ZM83Lx6UEy3N/M9g==", "dev": true, "requires": { "chalk": "^4.1.0", @@ -3033,9 +3033,9 @@ } }, "date-fns": { - "version": "2.21.1", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.21.1.tgz", - "integrity": "sha512-m1WR0xGiC6j6jNFAyW4Nvh4WxAi4JF4w9jRJwSI8nBmNcyZXPcP9VUQG+6gHQXAmqaGEKDKhOqAtENDC941UkA==", + "version": "2.21.3", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.21.3.tgz", + "integrity": "sha512-HeYdzCaFflc1i4tGbj7JKMjM4cKGYoyxwcIIkHzNgCkX8xXDNJDZXgDDVchIWpN4eQc3lH37WarduXFZJOtxfw==", "dev": true }, "debug": { @@ -4124,9 +4124,9 @@ "dev": true }, "follow-redirects": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.0.tgz", - "integrity": "sha512-0vRwd7RKQBTt+mgu87mtYeofLFZpTas2S9zY+jIeuLJMNvudIgF52nr19q40HOwH5RrhWIPuj9puybzSJiRrVg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.1.tgz", + "integrity": "sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==", "dev": true }, "fontkit": { @@ -4596,9 +4596,9 @@ "dev": true }, "handlebars": { - "version": "4.7.6", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.6.tgz", - "integrity": "sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==", + "version": "4.7.7", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", + "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", "dev": true, "requires": { "minimist": "^1.2.5", @@ -4608,12 +4608,6 @@ "wordwrap": "^1.0.0" }, "dependencies": { - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, "wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", @@ -4780,9 +4774,9 @@ } }, "hosted-git-info": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", - "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true }, "html-entities": { @@ -4911,9 +4905,9 @@ "dev": true }, "ignore-walk": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", - "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.4.tgz", + "integrity": "sha512-PY6Ii8o1jMRA1z4F2hRkH/xN59ox43DavKvD3oDpfurRlOJyAHpifIwpbdv1n4jt4ov0jSpw3kQ4GhJnpBL6WQ==", "dev": true, "optional": true, "requires": { @@ -6536,9 +6530,9 @@ } }, "lodash": { - "version": "4.17.19", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", - "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "lodash.assignin": { "version": "4.2.0", @@ -6684,9 +6678,9 @@ } }, "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -7085,9 +7079,9 @@ } }, "mocha": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.3.2.tgz", - "integrity": "sha512-UdmISwr/5w+uXLPKspgoV7/RXZwKRTiTjJ2/AC5ZiEztIoOYdfKb19+9jNmEInzx5pBsCyJQzarAxqIGBNYJhg==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.4.0.tgz", + "integrity": "sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ==", "dev": true, "requires": { "@ungap/promise-all-settled": "1.1.2", @@ -7361,9 +7355,9 @@ } }, "mqtt-packet": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/mqtt-packet/-/mqtt-packet-6.9.1.tgz", - "integrity": "sha512-0+u0ZoRj6H6AuzNY5d8qzXzyXmFI19gkdPRA14kGfKvbqYcpOL+HWUGHjtCxHqjm8CscwsH+dX0+Rxx4se5HSA==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/mqtt-packet/-/mqtt-packet-6.10.0.tgz", + "integrity": "sha512-ja8+mFKIHdB1Tpl6vac+sktqy3gA8t9Mduom1BA75cI+R9AHnZOiaBQwpGiWnaVJLDGRdNhQmFaAqd7tkKSMGA==", "dev": true, "requires": { "bl": "^4.0.2", @@ -7487,9 +7481,9 @@ "dev": true }, "neo-async": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", - "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, "next-tick": { @@ -7568,15 +7562,15 @@ } }, "node-red": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/node-red/-/node-red-1.3.3.tgz", - "integrity": "sha512-byKE+FxquGqaZ8HOCaZ6iz7CUwhJeBCfNqsuOCpUDxOa/Zqg2vh0OnZBv3W2DburZq/hEy2/LZxpXPg6iYSh4A==", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/node-red/-/node-red-1.3.5.tgz", + "integrity": "sha512-P/EmWwzuY/0mQFlrklSUrVANdv9xQsbrpKrpfA8tuhhxu1OEiSgzYdo87Zj13Hi38K4f1rNPQj4rw2vEBcYS/g==", "dev": true, "requires": { - "@node-red/editor-api": "1.3.3", - "@node-red/nodes": "1.3.3", - "@node-red/runtime": "1.3.3", - "@node-red/util": "1.3.3", + "@node-red/editor-api": "1.3.5", + "@node-red/nodes": "1.3.5", + "@node-red/runtime": "1.3.5", + "@node-red/util": "1.3.5", "basic-auth": "2.0.1", "bcrypt": "3.0.6", "bcryptjs": "2.4.3", @@ -9360,9 +9354,9 @@ }, "dependencies": { "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", "dev": true, "optional": true, "requires": { @@ -10880,9 +10874,9 @@ } }, "uglify-js": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.10.0.tgz", - "integrity": "sha512-Esj5HG5WAyrLIdYU74Z3JdG2PxdIusvj6IWHMtlyESxc7kcDz7zYlYjpnSokn1UbpV0d/QX9fan7gkCNd/9BQA==", + "version": "3.13.7", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.13.7.tgz", + "integrity": "sha512-1Psi2MmnZJbnEsgJJIlfnd7tFlJfitusmR7zDI8lXlFI0ACD4/Rm/xdrU8bh6zF0i74aiVoBtkRiFulkrmh3AA==", "dev": true, "optional": true }, @@ -11615,9 +11609,9 @@ } }, "y18n": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.5.tgz", - "integrity": "sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true } } diff --git a/package.json b/package.json index c1c28bb..5da8f83 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@mindconnect/node-red-contrib-mindconnect", - "version": "3.12.0-0", + "version": "3.12.0", "description": "node red mindconnect node using mindconnect-nodejs library.", "main": "index.js", "scripts": { @@ -33,17 +33,17 @@ "devDependencies": { "@compodoc/compodoc": "^1.1.11", "@types/ajv": "^1.0.0", - "@types/chai": "^4.2.16", + "@types/chai": "^4.2.18", "@types/mocha": "^8.2.2", - "@types/node": "^14.14.41", + "@types/node": "^15.3.1", "@types/node-fetch": "^2.5.10", "chai": "^4.3.4", - "concurrently": "^6.0.2", + "concurrently": "^6.1.0", "cross-env": "^7.0.3", "license-checker": "^25.0.1", - "mocha": "^8.3.2", + "mocha": "^8.4.0", "mocha-jenkins-reporter": "^0.4.5", - "node-red": "^1.3.3", + "node-red": "^1.3.5", "node-red-node-test-helper": "^0.2.7", "nodemon": "^2.0.7", "nyc": "^15.1.0", @@ -57,9 +57,9 @@ } }, "dependencies": { - "@mindconnect/mindconnect-nodejs": "^3.12.0", - "ajv": "^8.1.0", - "ajv-formats": "^2.0.2", + "@mindconnect/mindconnect-nodejs": "^3.13.0", + "ajv": "^8.4.0", + "ajv-formats": "^2.1.0", "debug": "^4.3.1", "node-fetch": "^2.6.1" } diff --git a/src/mindconnect-ops.ts b/src/mindconnect-ops.ts index 3b30fcf..256f7ef 100644 --- a/src/mindconnect-ops.ts +++ b/src/mindconnect-ops.ts @@ -1,7 +1,7 @@ // * MindConnect Operations - sending of data import { BaseEvent, MindConnectAgent } from "@mindconnect/mindconnect-nodejs"; -import { IDataLakeFileInfo, IFileInfo } from "./mindconnect-schema"; +import { IAssetInfo, IDataLakeFileInfo, IFileInfo } from "./mindconnect-schema"; import { handleError, IMindConnectNode, retryWithNodeLog } from "./mindconnect-utils"; export interface OperationParameters { @@ -182,3 +182,64 @@ export async function sendEvent({ msg, agent, timestamp, node, send }: Operation } return msg; } + +export async function getAssetInfo({ msg, agent, timestamp, node, send }: OperationParameters) { + try { + !node.supressverbosity && node.status({ fill: "grey", shape: "dot", text: `reading asset data` }); + + const assetInfo = msg.payload as IAssetInfo; + const assetManagement = agent.Sdk().GetAssetManagementClient(); + + const result = await retryWithNodeLog( + node.retry, + () => assetManagement.GetAsset(assetInfo.assetId, { includeShared: assetInfo.includeShared }), + "Get AssetInfo", + node + ); + node.log(`got asset info for asset with ${assetInfo.assetId} at ${timestamp}`); + !node.supressverbosity && + node.status({ + fill: "green", + shape: "dot", + text: `got asset info for asset with ${assetInfo.assetId} at ${timestamp}`, + }); + msg._mindsphereStatus = result ? "OK" : "Error"; + + msg.payload = result; + if (assetInfo.propertyNames.length > 0) { + const reducedResult = {}; + + assetInfo.propertyNames.forEach((element) => { + reducedResult[element] = result[element]; + }); + msg.payload = reducedResult; + } + node.send(msg); + } catch (error) { + console.log(error); + handleError(node, msg, error); + } + return msg; +} + +export async function applySdkOperation({ msg, agent, timestamp, node, send }: OperationParameters) { + try { + !node.supressverbosity && node.status({ fill: "grey", shape: "dot", text: `reading asset data` }); + + // stop typescript from manging the constructor of async functions. + const AsyncFunction = new Function(`return Object.getPrototypeOf(async function(){}).constructor`)(); + const fn = new AsyncFunction("sdk", msg.payload.function); + + const result = await retryWithNodeLog(node.retry, () => fn.apply({}, [agent.Sdk()]), "CallSdk", node); + node.log(`executed sdk operation at ${timestamp}`); + !node.supressverbosity && + node.status({ fill: "green", shape: "dot", text: `executed sdk operation at ${timestamp}` }); + msg._mindsphereStatus = result ? "OK" : "Error"; + msg.payload = result; + node.send(msg); + } catch (error) { + console.log(error); + handleError(node, msg, error); + } + return msg; +} diff --git a/src/mindconnect-schema.ts b/src/mindconnect-schema.ts index ac97ce8..d5959d0 100644 --- a/src/mindconnect-schema.ts +++ b/src/mindconnect-schema.ts @@ -31,6 +31,60 @@ export const actionSchema = { }, }; +export const sdkFunctionSchema = { + definitions: {}, + $schema: "http://json-schema.org/draft-07/schema#", + $id: "http://opensource.mindsphere.io/mindconnect/sdk", + type: "object", + title: "The Root Schema", + required: ["function"], + properties: { + function: { + $id: "#/properties/function", + type: "string", + title: "The operation Schema", + default: "", + examples: ["return agent.Sdk().GetAssetManagementClient().GetAsset('');"], + }, + }, +}; + +export const assetInfoSchema = { + definitions: {}, + $schema: "http://json-schema.org/draft-07/schema#", + $id: "http://opensource.mindsphere.io/mindconnect/assetInfoSchema", + type: "object", + title: "The Root Schema", + required: ["assetId", "includeShared", "propertyNames"], + properties: { + assetId: { + $id: "#/properties/assetId", + type: "string", + title: "The assetId Schema", + default: "", + examples: ["assetId"], + }, + includeShared: { + $id: "#/properties/includeShared", + type: "boolean", + title: "The assetId Schema", + default: false, + examples: [true], + }, + + propertyNames: { + $id: "#/properties/propertyNames", + type: "array", + items: { + type: "string", + }, + title: "The propertyNames Schema", + default: "", + examples: ["staticVariables"], + }, + }, +}; + export const eventSchema = { $id: "http://opensource.mindsphere.io/mindconnect/event", type: "object", @@ -275,8 +329,7 @@ export const remoteConfigurationSchema = { type: "array", title: "The Clientcredentialprofile Schema", items: { - $id: - "#/properties/agentconfig/properties/content/properties/clientCredentialProfile/items", + $id: "#/properties/agentconfig/properties/content/properties/clientCredentialProfile/items", type: "string", title: "The Items Schema", default: "", @@ -403,6 +456,11 @@ export function actionSchemaValidator(): ValidateFunction { return schemaValidator.compile(actionSchema); } +export function assetInfoValidator(): ValidateFunction { + return schemaValidator.compile(assetInfoSchema); +} + + export function remoteConfigurationValidator(): ValidateFunction { return schemaValidator.compile(remoteConfigurationSchema); } @@ -423,10 +481,20 @@ export function bulkUploadValidator(): ValidateFunction { return schemaValidator.compile(bulkUploadSchema); } +export function sdkFunctionValidator(): ValidateFunction { + return schemaValidator.compile(sdkFunctionSchema); +} + export function timeSeriesValidator(): ValidateFunction { return schemaValidator.compile(timeSeriesSchema); } +export interface IAssetInfo { + assetId: string; + includeShared: boolean; + propertyNames: Array; +} + export interface IDataLakeFileInfo { dataLakeFile: Buffer | string; dataLakeFilePath: string; diff --git a/src/mindconnect-utils.ts b/src/mindconnect-utils.ts index bf34b74..8919651 100644 --- a/src/mindconnect-utils.ts +++ b/src/mindconnect-utils.ts @@ -210,7 +210,9 @@ export const extractErrorString = ( tsValidator: { errors?: any[] }, rcValidator: { errors?: any[] }, actionValidator: { errors?: any[] }, - dataLakeValidator: { errors?: any[] } + dataLakeValidator: { errors?: any[] }, + assetInfoValidator: { errors?: any[] }, + sdkFunctionValidator: { errors?: any[] } ) => { const eventErrors = eventValidator.errors || []; const fileErrors = fileValidator.errors || []; @@ -219,10 +221,12 @@ export const extractErrorString = ( const rcValidatorErrors = rcValidator.errors || []; const actionErrors = actionValidator.errors || []; const dataLakeErrors = dataLakeValidator.errors || []; + const assetInfoErrors = assetInfoValidator.errors || []; + const sdkFunctionErrors = sdkFunctionValidator.errors || []; const result = { message: - "the payload was not recognized as an event, file or datapoints. See node help for proper msg.payload.formats (see msg._errorObject for all errors)", + "the payload was not recognized as an event, file, datapoints, assetinfo message or scripts. See node help for proper msg.payload.formats (see msg._errorObject for all errors)", actionErrors: [], eventErrors: [], fileErrors: [], @@ -230,6 +234,8 @@ export const extractErrorString = ( bulkErrors: [], timeSeriesErrors: [], remoteConfigurationErrors: [], + assetInfoErrors: [], + sdkFunctionErrors: [], }; actionErrors.forEach((element) => { @@ -259,6 +265,14 @@ export const extractErrorString = ( result.remoteConfigurationErrors.push(element.message); }); + assetInfoErrors.forEach((element) => { + result.assetInfoErrors.push(element.message); + }); + + sdkFunctionErrors.forEach((element) => { + result.sdkFunctionErrors.push(element.message); + }); + return result; }; diff --git a/src/mindconnect.html b/src/mindconnect.html index d38a1b4..3bc1745 100644 --- a/src/mindconnect.html +++ b/src/mindconnect.html @@ -6,7 +6,7 @@    MindConnect Node-RED Agent - v3.12.0 (alpha) + v3.12.0
@@ -111,7 +111,10 @@ Bulk TimeSeries Template Event Template File Template +
DataLake Template + AssetInfo Template + SdkFunction Template


@@ -341,6 +344,39 @@ If you just want to generate a signed upload URL without actually uploading the file you can pass
msg._ignorePayload=true;
to the node.

+

+ Asset Info:
+ You can read the data (e.g. static asset variables) from MindSphere using this node. + You can reduce the number of items in payload by specifying list of properties to include in the message: + e.g. propertyNames: ["variables"] or ["location"] +

+
+msg.payload = {
+  "assetId": "{assetId}",
+  "includeShared": false,
+  "propertyNames": []
+};
+
+return msg;
+        
+ +

+ SDK JavaScript Function:
+ The node can be used to execute a complex script which uses MindSphere javascript/typescript SDK + The node will create an asyncronous function with one parameter (sdk) and the specified function body + and execute it. You can only call the MindSphere APIs which allow agent authorization. +

+
+msg.payload = {
+    function: `
+const assetManagement = sdk.GetAssetManagementClient();
+const asset = await assetManagement.GetAsset('{assetId}');
+return asset;
+`};
+           
+return msg;
+        
+

Forcing Onboarding Actions:
You can force the onboarding or the retrival of the agent configuration by setting @@ -787,7 +823,52 @@ }; function copyLink(dataSourceConfig, option) { - if (option === "DATALAKE") { + + if (option === "SDKFUNCTION") { + + const sdkFunction = "" + + "// take a look at the flow examples at https://playground.mindconnect.rocks " + + "\n//" + + "\n// execute custom script using mindsphere SDK" + + "\n// the node will create an asyncronous function with one parameter (sdk)" + + "\n// and the function body specified below " + + "\nconst sdkFunction = \{" + + "\n function: `" + + "\nconst assetManagement = sdk.GetAssetManagementClient()" + + "\nconst asset = await assetManagement.GetAsset('')" + + "\nreturn asset;`" + + "\n\};" + + "\n\nmsg.payload = sdkFunction;" + + "\nreturn msg;"; + + const success = copyToClipboard(sdkFunction); + $("#mindconnect-infoDialog-output").text( + success ? `Copied SDK Function Template to Clipboard. (Paste this template to a function node)` : + "Couldn't copy the text to clipboard" + ); + + } else if (option === "ASSETINFO") { + + const assetInfoTemplate = "" + + "// take a look at the flow examples at https://playground.mindconnect.rocks " + + "\n//" + + "\n// Read the static asset information from MindSphere" + + "\n// You can reduce the number of items in payload by specifying list of properties" + + "\n// in property Names list e.g. [\"variables\"] or [\"location\"]" + + "\n// Set includeShared to true to include the assets shared with your agents tenant via cross-tenancy" + + "\n" + + "\nconst assetInfo = " + JSON.stringify({assetId: "", includeShared: false, propertyNames:[]}, null, 2) + ";" + + "\n\nmsg.payload = assetInfo;" + + "\nreturn msg;"; + + const success = copyToClipboard(assetInfoTemplate); + $("#mindconnect-infoDialog-output").text( + success ? `Copied Asset Info Template to Clipboard. (Paste this template to a function node)` : + "Couldn't copy the text to clipboard" + ); + + } + else if (option === "DATALAKE") { const dataLakeTemplate = "" + "// take a look at the flow examples at https://playground.mindconnect.rocks " + @@ -814,7 +895,7 @@ } else if (option === "BULK" || option === "TIMESERIES") { const values = []; - + const result = ""; dataSourceConfig.forEach((dataSource) => { dataSource.dataPoints.forEach((dataPoint) => { values.push({ @@ -823,6 +904,7 @@ value: `<${dataPoint.type}>`, }); }); + }); const bulk = [{ timestamp: new Date().toISOString(), values: values }]; const headertemplate = "// take a look at the flow examples at https://playground.mindconnect.rocks" + "\n//" + @@ -853,7 +935,6 @@ } TimeSeries Template Data to Clipboard. (Paste this template to a function node)` : "Couldn't copy the text to clipboard" ); - }); } else { const event = { sourceType: "Agent", @@ -925,6 +1006,8 @@ $("#mindconnect-infoDialog-copyEventLink").click(() => copyLink(result.configuration.dataSources, "EVENT")); $("#mindconnect-infoDialog-copyFileLink").click(() => copyLink(result.configuration.dataSources, "FILE")); $("#mindconnect-infoDialog-copyDataLakeFileLink").click(() => copyLink(result.configuration.dataSources, "DATALAKE")); + $("#mindconnect-infoDialog-copyAssetInfoLink").click(() => copyLink(result.configuration.dataSources, "ASSETINFO")); + $("#mindconnect-infoDialog-copySdkFunctionLink").click(() => copyLink(result.configuration.dataSources, "SDKFUNCTION")); result.configuration.dataSources.forEach((element) => { $("#mindconnect-infoDialog-infoList").append( diff --git a/src/mindconnect.ts b/src/mindconnect.ts index 1a5d71a..7b1ed1f 100644 --- a/src/mindconnect.ts +++ b/src/mindconnect.ts @@ -5,6 +5,8 @@ import * as fs from "fs"; import * as path from "path"; import { RegisterHttpHandlers } from "./http-handlers"; import { + applySdkOperation, + getAssetInfo, renewToken, sendBulkTimeSeriesData, sendEvent, @@ -14,12 +16,14 @@ import { } from "./mindconnect-ops"; import { actionSchemaValidator, + assetInfoValidator, bulkUploadValidator, dataLakeFileInfoValidator, eventSchemaValidator, fileInfoValidator, IConfigurationInfo, remoteConfigurationValidator, + sdkFunctionValidator, timeSeriesValidator, } from "./mindconnect-schema"; import { @@ -112,12 +116,14 @@ export = function (RED: any): void { throw new Error("you have to have a payload in your msg.payload to post the data!"); } + const assetValidator = assetInfoValidator(); const eventValidator = eventSchemaValidator(); const fileValidator = fileInfoValidator(); const bulkValidator = bulkUploadValidator(); const tsValidator = timeSeriesValidator(); const actionValidator = actionSchemaValidator(); const dataLakeValidator = dataLakeFileInfoValidator(); + const sdkValidator = sdkFunctionValidator(); if (msg._includeMindSphereToken) { msg.headers = { ...msg.headers, Authorization: `Bearer ${await agent.GetAgentToken()}` }; @@ -139,6 +145,10 @@ export = function (RED: any): void { } else if (msg.payload.action === "renew") { promises.push(queryablePromise(renewToken({ msg, agent, timestamp, node }))); } + } else if (assetValidator(msg.payload)) { + promises.push(queryablePromise(getAssetInfo({ msg, agent, timestamp, node }))); + } else if (sdkValidator(msg.payload)) { + promises.push(queryablePromise(applySdkOperation({ msg, agent, timestamp, node }))); } else if (eventValidator(msg.payload) || msg._customEvent === true) { promises.push(queryablePromise(sendEvent({ msg, agent, timestamp, node }))); } else if (fileValidator(msg.payload)) { @@ -157,7 +167,9 @@ export = function (RED: any): void { tsValidator, rcValidator, actionValidator, - dataLakeValidator + dataLakeValidator, + assetValidator, + sdkValidator ); promises.push(queryablePromise(handleInputError(msg, errorObject, timestamp))); diff --git a/test/mindconnect-schema.spec.ts b/test/mindconnect-schema.spec.ts index fc7b8b2..6a7d3f5 100644 --- a/test/mindconnect-schema.spec.ts +++ b/test/mindconnect-schema.spec.ts @@ -1,14 +1,13 @@ -import { IMindConnectConfiguration } from "@mindconnect/mindconnect-nodejs"; import * as chai from "chai"; -import * as fs from "fs"; import { describe, it } from "mocha"; -import * as path from "path"; import { actionSchemaValidator, + assetInfoValidator, bulkUploadValidator, eventSchemaValidator, fileInfoValidator, remoteConfigurationValidator, + sdkFunctionValidator, timeSeriesValidator, } from "../src/mindconnect-schema"; @@ -124,14 +123,20 @@ describe("Schema Validators", () => { buVal([{ xdataPointId: "123", qualityCode: "1", value: "33.5" }]).should.be.false; }); - it("should validate remoteConfiguration", async () => { + it("should validate remoteConfiguration @ci", async () => { const rcVal = remoteConfigurationValidator(); rcVal.should.exist; - let sharedSecretConfig: IMindConnectConfiguration = {} as IMindConnectConfiguration; - if (fs.existsSync(path.resolve("../agentconfig.json"))) { - sharedSecretConfig = JSON.parse(fs.readFileSync(path.resolve("../agentconfig.json")).toString()); - } + let sharedSecretConfig = { + content: { + baseUrl: "https://southgate.eu1.mindsphere.io", + iat: "xxx", + clientCredentialProfile: ["SHARED_SECRET"], + clientId: "xxx", + tenant: "xxx", + }, + expiration: "2021-05-02T17:45:03.000Z", + }; rcVal({}).should.be.false; rcVal([{}]).should.be.false; @@ -192,4 +197,19 @@ describe("Schema Validators", () => { actionVal({ action: "renew", timestamp: new Date().toISOString() }).should.be.true; actionVal({ action: "non-existing-action", timestamp: new Date().toISOString() }).should.be.false; }); + + it("should validate assetInfo @ci", async () => { + const assetInfo = assetInfoValidator(); + assetInfo({}).should.be.false; + assetInfo({ assetId: "" }).should.be.false; + assetInfo({ assetId: "123", includeShared: "xx", propertyNames: [] }).should.be.false; + assetInfo({ assetId: "123", includeShared: true, propertyNames: [] }).should.be.true; + }); + + it("should validate sdk call @ci", async () => { + const assetInfo = sdkFunctionValidator(); + assetInfo({}).should.be.false; + assetInfo({ assetId: "" }).should.be.false; + assetInfo({ function: "123" }).should.be.true; + }); });