diff --git a/node-red-node-wot/src/locales/en-US/wot-thing-config.html b/node-red-node-wot/src/locales/en-US/wot-thing-config.html index 3f15dc0..93aaaf7 100644 --- a/node-red-node-wot/src/locales/en-US/wot-thing-config.html +++ b/node-red-node-wot/src/locales/en-US/wot-thing-config.html @@ -10,6 +10,15 @@

Details

to this setting are published with the Thing title specified here.
  • Description: Specify the description of the Thing.
  • +
  • + Thing ID: Specify the Thing ID. The specified Thing ID is used as the ID for Thing Description. + If omitted, the ID for Thing Description is automatically generated.
    + Example of Thing ID: "urn:dev:ops:32473-WoTLamp-1234" +
  • +
  • + Use basic authentication: Specifies whether Basic authentication is applied to the Thing.
    + If you use Basic Authentication, please set your user name and password. +
  • The Thing Description is stored in the thingDescriptions variable of the global context with the key of diff --git a/node-red-node-wot/src/locales/en-US/wot-thing-config.json b/node-red-node-wot/src/locales/en-US/wot-thing-config.json index 20e987e..fa4dccd 100644 --- a/node-red-node-wot/src/locales/en-US/wot-thing-config.json +++ b/node-red-node-wot/src/locales/en-US/wot-thing-config.json @@ -1,6 +1,11 @@ { "editor": { "nameLabel": "Thing title", - "descriptionLabel": "Description" + "descriptionLabel": "Description", + "thingIdLabel": "Thing ID", + "securityLabel": "Security:", + "basicAuthLabel": "Use basic authentication", + "usernameLabel": "Username", + "passwordLabel": "Password" } } diff --git a/node-red-node-wot/src/locales/ja/wot-thing-config.html b/node-red-node-wot/src/locales/ja/wot-thing-config.html index d2b3d4a..04afa04 100644 --- a/node-red-node-wot/src/locales/ja/wot-thing-config.html +++ b/node-red-node-wot/src/locales/ja/wot-thing-config.html @@ -10,6 +10,15 @@

    Details

    Descriptionでは、ここで指定したThing名で、本設定を参照するプロパティ、アクション、イベントを公開します。
  • 説明: Thingの説明を指定します。
  • +
  • + Thing ID: Thing IDを指定します。指定したThing IDは、Thing + DescriptionのIDとして利用します。省略した場合は、Thing DescriptionのIDは自動的に生成されます。
    + Thing IDの例: "urn:dev:ops:32473-WoTLamp-1234" +
  • +
  • + Basic認証利用: ThingにBasic認証をかけるかどうかを指定します。
    + Basic認証を利用する場合は、ユーザー名とパスワードを設定してください。 +
  • Thing diff --git a/node-red-node-wot/src/locales/ja/wot-thing-config.json b/node-red-node-wot/src/locales/ja/wot-thing-config.json index 044e695..128d9b0 100644 --- a/node-red-node-wot/src/locales/ja/wot-thing-config.json +++ b/node-red-node-wot/src/locales/ja/wot-thing-config.json @@ -1,6 +1,11 @@ { "editor": { "nameLabel": "Thing名", - "descriptionLabel": "説明" + "descriptionLabel": "説明", + "thingIdLabel": "Thing ID", + "securityLabel": "セキュリティ:", + "basicAuthLabel": "Basic認証利用", + "usernameLabel": "ユーザー名", + "passwordLabel": "パスワード" } } diff --git a/node-red-node-wot/src/servients/servient-wrapper.ts b/node-red-node-wot/src/servients/servient-wrapper.ts index c15efc1..00563d1 100644 --- a/node-red-node-wot/src/servients/servient-wrapper.ts +++ b/node-red-node-wot/src/servients/servient-wrapper.ts @@ -51,6 +51,12 @@ export default class ServientWrapper { return this.things[thingName] } + public addCredentials(title, credentials) { + const thing = this.things[title] + const td = thing.getThingDescription() + this.servient.addCredentials({ [td.id]: credentials }) + } + public async endServient() { if (this.server) { console.debug("[debug] endServient called.") diff --git a/node-red-node-wot/src/wot-event.js b/node-red-node-wot/src/wot-event.js index 2900eca..0afebc4 100644 --- a/node-red-node-wot/src/wot-event.js +++ b/node-red-node-wot/src/wot-event.js @@ -6,6 +6,7 @@ module.exports = function (RED) { let node = this let consumedThing let subscription + let repeatId this.status({}) @@ -19,15 +20,20 @@ module.exports = function (RED) { const thingNode = RED.nodes.getNode(config.thing) thingNode.addUpdateTDListener(async (_consumedThing) => { + if (repeatId) { + clearInterval(repeatId) + repeatId = undefined + } if (subscription) { // Stop if already subscribed await subscription.stop() } + subscription = undefined consumedThing = _consumedThing // Repeat until event subscription succeeds. - try { - while (true) { - subscription = await consumedThing + await new Promise((resolve, reject) => { + repeatId = setInterval(() => { + consumedThing .subscribeEvent( config.event, async (resp) => { @@ -63,24 +69,17 @@ module.exports = function (RED) { subscription = undefined } ) + .then((sub) => { + subscription = sub + clearInterval(repeatId) + repeatId = undefined + resolve() + }) .catch((err) => { console.warn("[warn] event subscribe error. try again. error: " + err) }) - if (subscription) { - break - } - await new Promise((resolve) => { - setTimeout(resolve, 500) - }) - } - } catch (err) { - node.status({ - fill: "red", - shape: "ring", - text: "Subscription error", - }) - node.error(`[error] failed to subscribe events. error: ${err.toString()}`) - } + }, 1000) + }) if (subscription) { node.status({ @@ -92,6 +91,10 @@ module.exports = function (RED) { }) this.on("close", async function (removed, done) { + if (repeatId) { + clearInterval(repeatId) + repeatId = undefined + } if (subscription) { // Stop if already subscribed await subscription.stop() diff --git a/node-red-node-wot/src/wot-property.js b/node-red-node-wot/src/wot-property.js index 7acda80..d9f5ee6 100644 --- a/node-red-node-wot/src/wot-property.js +++ b/node-red-node-wot/src/wot-property.js @@ -6,6 +6,7 @@ module.exports = function (RED) { let node = this let consumedThing let subscription + let repeatId this.status({}) @@ -23,62 +24,67 @@ module.exports = function (RED) { const thingNode = RED.nodes.getNode(config.thing) thingNode.addUpdateTDListener(async (_consumedThing) => { + if (repeatId) { + clearInterval(repeatId) + repeatId = undefined + } if (subscription) { // Stop if already subscribed await subscription.stop() } + subscription = undefined consumedThing = _consumedThing if (config.observe === false) { return } // Repeat until observeProperty succeeds. - while (true) { - try { - subscription = await consumedThing.observeProperty( - config.property, - async (resp) => { - let payload - try { - payload = await resp.value() - } catch (err) { - node.error(`[error] failed to get property change. err: ${err.toString()}`) - console.error(`[error] failed to get property change. err:`, err) + await new Promise((resolve, reject) => { + repeatId = setInterval(() => { + consumedThing + .observeProperty( + config.property, + async (resp) => { + let payload + try { + payload = await resp.value() + } catch (err) { + node.error(`[error] failed to get property change. err: ${err.toString()}`) + console.error(`[error] failed to get property change. err:`, err) + } + node.send({ payload, topic: config.topic }) + }, + (err) => { + node.error(`[error] property observe error. error: ${err.toString()}`) + console.error(`[error] property observe error. error: `, err) + node.status({ + fill: "red", + shape: "ring", + text: "Observe error", + }) } - node.send({ payload, topic: config.topic }) - }, - (err) => { - node.error(`[error] property observe error. error: ${err.toString()}`) - console.error(`[error] property observe error. error: `, err) + ) + .then((sub) => { + subscription = sub + clearInterval(repeatId) + repeatId = undefined + resolve() + }) + .catch((err) => { + console.warn("[warn] property observe error. try again. error: " + err) node.status({ fill: "red", shape: "ring", text: "Observe error", }) - } - ) - } catch (err) { - console.warn("[warn] property observe error. try again. error: " + err) - node.status({ - fill: "red", - shape: "ring", - text: "Observe error", - }) - } - if (subscription) { - node.status({ - fill: "green", - shape: "dot", - text: "connected", - }) - break - } - await (() => { - return new Promise((resolve) => { - setTimeout(() => { - resolve() - }, 500) - }) - })() + }) + }, 1000) + }) + if (subscription) { + node.status({ + fill: "green", + shape: "dot", + text: "connected", + }) } }) @@ -119,6 +125,10 @@ module.exports = function (RED) { }) node.on("close", async function (removed, done) { + if (repeatId) { + clearInterval(repeatId) + repeatId = undefined + } if (subscription) { // Stop if already subscribed await subscription.stop() diff --git a/node-red-node-wot/src/wot-server-action.ts b/node-red-node-wot/src/wot-server-action.ts index 07e5dc1..2659c5a 100644 --- a/node-red-node-wot/src/wot-server-action.ts +++ b/node-red-node-wot/src/wot-server-action.ts @@ -31,9 +31,8 @@ module.exports = function (RED) { } // for wot-server-config - node.getThingProps = () => { - const woTThingConfig = RED.nodes.getNode(config.woTThingConfig) - return woTThingConfig.getProps() + node.getThingNode = () => { + return RED.nodes.getNode(config.woTThingConfig) } node.on("close", function (removed, done) { diff --git a/node-red-node-wot/src/wot-server-config.ts b/node-red-node-wot/src/wot-server-config.ts index d7fe917..aa069f5 100644 --- a/node-red-node-wot/src/wot-server-config.ts +++ b/node-red-node-wot/src/wot-server-config.ts @@ -21,6 +21,16 @@ module.exports = function (RED) { } } + function getSecurityDefinition(scheme) { + let params + if (scheme === "basic") { + params = { scheme, in: "header" } + } else { + params = { scheme } + } + return params + } + async function waitForFinishPrepareRelatedNodes(userNodes: any[], userNodeIds: string[]) { const MAX_CHECK_COUNT = 50 const WAIT_MILLI_SEC = 100 //ms @@ -118,7 +128,13 @@ module.exports = function (RED) { } async function createWoTScriptAndExpose( - thingProps: { title: string; description: string }, + thingProps: { + title: string + description: string + id?: string + securityDefinitions?: any + security?: string[] + }, servientWrapper: ServientWrapper, userNodes: any[] ) { @@ -156,7 +172,6 @@ module.exports = function (RED) { } async function launchServient() { - node.bindingType = node.credentials.bindingType if (config.bindingConfigConstValue && config.bindingConfigType) { node.bindingConfig = RED.util.evaluateNodeProperty( config.bindingConfigConstValue, @@ -168,27 +183,53 @@ module.exports = function (RED) { // create thing const bindingType = config.bindingType const bindingConfig = node.bindingConfig - console.debug("[debug] createServient ", node.id, bindingType, bindingConfig) - const servientWrapper = servientManager.createServientWrapper(node.id, bindingType, bindingConfig) try { await waitForFinishPrepareRelatedNodes(userNodes, config._users) - await servientWrapper.startServient() - // make thing title list - const thingNamesObj = {} + // make thing title list and security definitions + const securityDefinitions = [] + const thingTitles = [] for (const userNode of userNodes) { if (userNode.type === "wot-server-td") { continue } - thingNamesObj[userNode.getThingProps().title] = true + let thingNode = userNode.getThingNode() + if (!thingNode) { + continue + } + let title = thingNode.getProps()?.title + if (title && !thingTitles.includes(title)) { + thingTitles.push(title) + // make security definitions for server + let secDef = getSecurityDefinition(thingNode.getSecurityScheme()) + if (secDef.scheme !== "nosec") { + securityDefinitions.push(secDef) + } + } } - const thingNames = Object.keys(thingNamesObj) + // merge security params to bindingConfig + bindingConfig["security"] = securityDefinitions + console.debug("[debug] createServient ", node.id, bindingType, bindingConfig) + const servientWrapper = servientManager.createServientWrapper(node.id, bindingType, bindingConfig) + await servientWrapper.startServient() // Generate and Expose a Thing for each Thing title - for (const thingName of thingNames) { + for (const thingTitle of thingTitles) { const targetNodes = userNodes.filter( - (n) => n.type !== "wot-server-td" && n.getThingProps().title === thingName + (n) => n.type !== "wot-server-td" && n.getThingNode().getProps().title === thingTitle ) - const thingProps = targetNodes[0]?.getThingProps() || {} - await createWoTScriptAndExpose(thingProps, servientWrapper, targetNodes) + if (targetNodes.length > 0) { + const thingNode = targetNodes[0].getThingNode() + const thingProps = thingNode.getProps() || {} + // add security definition to thingProps + const secScheme = thingNode.getSecurityScheme() + if (secScheme !== "nosec") { + thingProps["securityDefinitions"] = { + sc: getSecurityDefinition(secScheme), + } + thingProps["security"] = ["sc"] + } + await createWoTScriptAndExpose(thingProps, servientWrapper, targetNodes) + servientWrapper.addCredentials(thingProps.title, thingNode.getCredentials()) + } } node.running = true userNodes.forEach((n) => { diff --git a/node-red-node-wot/src/wot-server-event.ts b/node-red-node-wot/src/wot-server-event.ts index cc4497b..1e3f82b 100644 --- a/node-red-node-wot/src/wot-server-event.ts +++ b/node-red-node-wot/src/wot-server-event.ts @@ -28,9 +28,8 @@ module.exports = function (RED) { } // for wot-server-config - node.getThingProps = () => { - const woTThingConfig = RED.nodes.getNode(config.woTThingConfig) - return woTThingConfig.getProps() + node.getThingNode = () => { + return RED.nodes.getNode(config.woTThingConfig) } node.on("input", async (msg, send, done) => { @@ -47,7 +46,7 @@ module.exports = function (RED) { ) } await ServientManager.getInstance() - .getThing(woTServerConfig.id, node.getThingProps().title) + .getThing(woTServerConfig.id, node.getThingNode().getProps().title) .emitEvent(config.eventName, node.inParams_eventValue) console.debug("[debug] emitEvent finished. eventName: ", config.eventName) diff --git a/node-red-node-wot/src/wot-server-property.ts b/node-red-node-wot/src/wot-server-property.ts index 5114bde..76ab7af 100644 --- a/node-red-node-wot/src/wot-server-property.ts +++ b/node-red-node-wot/src/wot-server-property.ts @@ -30,9 +30,8 @@ module.exports = function (RED) { } // for wot-server-config - node.getThingProps = () => { - const woTThingConfig = RED.nodes.getNode(config.woTThingConfig) - return woTThingConfig.getProps() + node.getThingNode = () => { + return RED.nodes.getNode(config.woTThingConfig) } node.on("input", async (msg, send, done) => { @@ -40,7 +39,7 @@ module.exports = function (RED) { const woTServerConfig = RED.nodes.getNode(config.woTServerConfig) await ServientManager.getInstance() - .getThing(woTServerConfig.id, node.getThingProps().title) + .getThing(woTServerConfig.id, node.getThingNode().getProps().title) .emitPropertyChange(config.propertyName) console.debug("[debug] emitPropertyChange finished. propertyName: ", config.propertyName) diff --git a/node-red-node-wot/src/wot-thing-config.html b/node-red-node-wot/src/wot-thing-config.html index bab0083..ff8a745 100644 --- a/node-red-node-wot/src/wot-thing-config.html +++ b/node-red-node-wot/src/wot-thing-config.html @@ -4,7 +4,11 @@ category: "config", defaults: { name: { value: "", required: true }, - description: { value: "" }, + description: { value: "", required: false }, + thingId: { value: "", required: false }, + basicAuth: { value:false }, + basicAuthUsername: { value:"", required:false }, + basicAuthPassword: { value:"", required:false }, }, credentials: {}, label: function () { @@ -13,7 +17,19 @@ labelStyle: function () { return this.name ? "node_label_italic" : "" }, - oneditprepare: function () {}, + oneditprepare: function () { + const $basicAuth = $("#node-config-input-basicAuth") + function toggleBasicAuthContainer() { + // Check if event is triggered by user + if ($basicAuth.prop("checked") === true) { + $(".basicAuthContainer").show() + } else { + $(".basicAuthContainer").hide() + } + } + // Toggle username and password when basic auth is enabled/disabled + $basicAuth.change(toggleBasicAuthContainer) + }, oneditsave: function () {}, }) @@ -62,4 +78,21 @@ +

    + + +
    +

    +
    + + +
    +
    + + +
    +
    + + +
    diff --git a/node-red-node-wot/src/wot-thing-config.ts b/node-red-node-wot/src/wot-thing-config.ts index f94a699..a780c24 100644 --- a/node-red-node-wot/src/wot-thing-config.ts +++ b/node-red-node-wot/src/wot-thing-config.ts @@ -6,6 +6,20 @@ module.exports = function (RED) { return { title: config.name, description: config.description, + id: config.thingId, + } + } + node.getCredentials = () => { + return { + username: config.basicAuthUsername, + password: config.basicAuthPassword, + } + } + node.getSecurityScheme = () => { + if (config.basicAuth) { + return "basic" + } else { + return "nosec" } } } diff --git a/node-red-node-wot/src/wot-thing.js b/node-red-node-wot/src/wot-thing.js index fe653ab..7124a4e 100644 --- a/node-red-node-wot/src/wot-thing.js +++ b/node-red-node-wot/src/wot-thing.js @@ -15,6 +15,7 @@ module.exports = function (RED) { const node = this let consumedThing let tdListeners = [] + let servient this.addUpdateTDListener = (listener) => { tdListeners.push(listener) @@ -24,7 +25,11 @@ module.exports = function (RED) { } this.createConsumedThing = async (td) => { - let servient = new Servient() + node.td = td //for debug + if (servient) { + servient.shutdown() + } + servient = new Servient() if (config.basicAuth) { servient.addCredentials({ diff --git a/node-red-node-wot/test/update-td-test.ts b/node-red-node-wot/test/update-td-test.ts index 001d998..210eebf 100644 --- a/node-red-node-wot/test/update-td-test.ts +++ b/node-red-node-wot/test/update-td-test.ts @@ -216,7 +216,7 @@ describe("Tests for Update TD", function () { description: "", }, }) - new Promise((resolve) => setTimeout(resolve, 500)).then(() => { + new Promise((resolve) => setTimeout(resolve, 1500)).then(() => { expectedEvent = "event from server02" serverEventNode01.receive({ payload: "event from server01" }) serverEventNode02.receive({ payload: "event from server02" })