From da7669cfd5083abd069f523e80f5130ae3e27b1b Mon Sep 17 00:00:00 2001 From: Kunihiko Toumura Date: Thu, 31 Mar 2022 13:53:57 +0900 Subject: [PATCH] Add MQTT binding support, etc. --- lib/webofthings/index.js | 13 + .../td-json-schema-validation.json | 2327 +++++++++-------- lib/webofthings/wotutils.js | 12 +- package.json | 4 +- templates/webofthings/node.js.mustache | 187 +- templates/webofthings/package.json.mustache | 5 +- 6 files changed, 1426 insertions(+), 1122 deletions(-) diff --git a/lib/webofthings/index.js b/lib/webofthings/index.js index 66e79a2..2845637 100644 --- a/lib/webofthings/index.js +++ b/lib/webofthings/index.js @@ -37,6 +37,19 @@ module.exports = async function(data, options) { console.info(`Schema validation succeeded.`); } + // if there is no title but titles, put one of titles to title. + if (!td.title) { + if (td.titles) { + if (td.titles.en) { + td.title = td.titles.en + } else { + td.title = Object.values(td.titles)[0]; + } + } else { + td.title = "notitle"; + } + } + // if name is not specified, use td.title for module name. if (!data.name || data.name === '') { // filtering out special characters diff --git a/lib/webofthings/td-json-schema-validation.json b/lib/webofthings/td-json-schema-validation.json index 5751ca5..f02ddc0 100644 --- a/lib/webofthings/td-json-schema-validation.json +++ b/lib/webofthings/td-json-schema-validation.json @@ -1,1153 +1,1316 @@ { - "title": "WoT TD Schema - 16 October 2019", - "description": "JSON Schema for validating TD instances against the TD model. TD instances can be with or without terms that have default values", + "title": "Thing Description", + "version": "1.1-28-January-2022", + "description": "JSON Schema for validating TD instances against the TD information model. TD instances can be with or without terms that have default values", "$schema ": "http://json-schema.org/draft-07/schema#", + "$id":"https://raw.githubusercontent.com/w3c/wot-thing-description/main/validation/td-json-schema-validation.json", "definitions": { - "thing-context-w3c-uri": { - "type": "string", - "enum": [ - "https://www.w3.org/2019/wot/td/v1" - ] - }, - "thing-context": { - "oneOf": [{ - "type": "array", - "items": { - "anyOf": [{ - "$ref": "#/definitions/anyUri" - }, - { - "type": "object" - } - ] - }, - "contains": { - "$ref": "#/definitions/thing-context-w3c-uri" - } + "anyUri": { + "type": "string", + "format": "iri-reference" + }, + "description": { + "type": "string" + }, + "descriptions": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "title": { + "type": "string" + }, + "titles": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "security": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "scopes": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "subprotocol": { + "type": "string", + "examples": [ + "longpoll", + "websub", + "sse" + ] + }, + "thing-context-w3c-uri": { + "type": "string", + "enum": [ + "https://www.w3.org/2019/wot/td/v1", + "http://www.w3.org/ns/td", + "https://www.w3.org/2022/wot/td/v1.1" + ] + }, + "thing-context": { + "oneOf": [ + { + "type": "array", + "items": [ + { + "$ref": "#/definitions/thing-context-w3c-uri" + } + ], + "additionalItems": { + "anyOf": [ + { + "$ref": "#/definitions/anyUri" }, { - "$ref": "#/definitions/thing-context-w3c-uri" + "type": "object" + } + ] + } + }, + { + "$ref": "#/definitions/thing-context-w3c-uri" + } + ] + }, + "type_declaration": { + "oneOf": [ + { + "type": "string", + "not": { + "const": "tm:ThingModel" + } + }, + { + "type": "array", + "items": { + "type": "string", + "not": { + "const": "tm:ThingModel" + } + } + } + ] + }, + "dataSchema-type": { + "type": "string", + "enum": [ + "boolean", + "integer", + "number", + "string", + "object", + "array", + "null" + ] + }, + "dataSchema": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "title": { + "$ref": "#/definitions/title" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "titles": { + "$ref": "#/definitions/titles" + }, + "writeOnly": { + "type": "boolean" + }, + "readOnly": { + "type": "boolean" + }, + "oneOf": { + "type": "array", + "items": { + "$ref": "#/definitions/dataSchema" + } + }, + "unit": { + "type": "string" + }, + "enum": { + "type": "array", + "minItems": 1, + "uniqueItems": true + }, + "format": { + "type": "string" + }, + "const": {}, + "default": {}, + "contentEncoding": { + "type": "string" + }, + "contentMediaType": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/dataSchema-type" + }, + "items": { + "oneOf": [ + { + "$ref": "#/definitions/dataSchema" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/dataSchema" } + } ] - }, - "type_declaration": { - "oneOf": [{ - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } + }, + "maxItems": { + "type": "integer", + "minimum": 0 + }, + "minItems": { + "type": "integer", + "minimum": 0 + }, + "minimum": { + "type": "number" + }, + "maximum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + }, + "minLength": { + "type": "integer", + "minimum": 0 + }, + "maxLength": { + "type": "integer", + "minimum": 0 + }, + "multipleOf": { + "$ref": "#/definitions/multipleOfDefinition" + }, + "properties": { + "additionalProperties": { + "$ref": "#/definitions/dataSchema" + } + }, + "required": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "additionalResponsesDefinition": { + "type": "array", + "items": { + "type": "object", + "properties": { + "contentType": { + "type": "string" + }, + "schema": { + "type": "string" + }, + "success": { + "type": "boolean" + } + } + } + }, + "multipleOfDefinition": { + "type": [ + "integer", + "number" + ], + "exclusiveMinimum": 0 + }, + "expectedResponse": { + "type": "object", + "properties": { + "contentType": { + "type": "string" + } + } + }, + "form_element_base": { + "type": "object", + "properties": { + "op": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" } + } ] + }, + "href": { + "$ref": "#/definitions/anyUri" + }, + "contentType": { + "type": "string" + }, + "contentCoding": { + "type": "string" + }, + "subprotocol": { + "$ref": "#/definitions/subprotocol" + }, + "security": { + "$ref": "#/definitions/security" + }, + "scopes": { + "$ref": "#/definitions/scopes" + }, + "response": { + "$ref": "#/definitions/expectedResponse" + }, + "additionalResponses": { + "$ref": "#/definitions/additionalResponsesDefinition" + } }, - "property_element": { - "type": "object", - "properties": { - "@type": { - "$ref": "#/definitions/type_declaration" - }, - "description": { - "$ref": "#/definitions/description" - }, - "descriptions": { - "$ref": "#/definitions/descriptions" - }, - "title": { - "$ref": "#/definitions/title" - }, - "titles": { - "$ref": "#/definitions/titles" - }, - "uriVariables": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/dataSchema" - } - }, - "forms": { - "type": "array", - "minItems": 1, - "items": { - "$ref": "#/definitions/form_element_property" - } - }, - "observable": { - "type": "boolean" - }, - "writeOnly": { - "type": "boolean" - }, - "readOnly": { - "type": "boolean" - }, - "oneOf": { - "type": "array", - "items": { - "$ref": "#/definitions/dataSchema" - } - }, - "unit": { - "type": "string" - }, - "enum": { - "type": "array", - "minItems": 1, - "uniqueItems": true - }, - "format": { - "type": "string" - }, - "const": {}, - "type": { - "type": "string", - "enum": [ - "boolean", - "integer", - "number", - "string", - "object", - "array", - "null" - ] - }, + "required": [ + "href" + ], + "additionalProperties": true + }, + "form_element_property": { + "allOf":[{"$ref":"#/definitions/form_element_base"}], + "type": "object", + "properties": { + "op": { + "oneOf": [ + { + "type": "string", + "enum": [ + "readproperty", + "writeproperty", + "observeproperty", + "unobserveproperty" + ] + }, + { + "type": "array", "items": { - "oneOf": [{ - "$ref": "#/definitions/dataSchema" - }, - { - "type": "array", - "items": { - "$ref": "#/definitions/dataSchema" - } - } - ] - }, - "maxItems": { - "type": "integer", - "minimum": 0 - }, - "minItems": { - "type": "integer", - "minimum": 0 - }, - "minimum": { - "type": "number" - }, - "maximum": { - "type": "number" - }, - "properties": { - "additionalProperties": { - "$ref": "#/definitions/dataSchema" - } - }, - "required": { - "type": "array", - "items": { - "type": "string" - } + "type": "string", + "enum": [ + "readproperty", + "writeproperty", + "observeproperty", + "unobserveproperty" + ] } - }, - "required": [ - "forms" - ], - "additionalProperties": true + } + ] + } }, - "action_element": { - "type": "object", - "properties": { - "description": { - "type": "string" - }, - "descriptions": { - "$ref": "#/definitions/descriptions" - }, - "title": { - "$ref": "#/definitions/title" - }, - "titles": { - "$ref": "#/definitions/titles" - }, - "uriVariables": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/dataSchema" - } - }, - "@type": { - "$ref": "#/definitions/type_declaration" - }, - "forms": { - "type": "array", - "minItems": 1, - "items": { - "$ref": "#/definitions/form_element_action" - } - }, - "input": { - "$ref": "#/definitions/dataSchema" - }, - "output": { - "$ref": "#/definitions/dataSchema" - }, - "safe": { - "type": "boolean" - }, - "idempotent": { - "type": "boolean" + "additionalProperties": true + }, + "form_element_action": { + "allOf":[{"$ref":"#/definitions/form_element_base"}], + "type": "object", + "properties": { + "op": { + "oneOf": [ + { + "type": "string", + "enum": [ + "invokeaction", + "queryaction", + "cancelaction" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "invokeaction", + "queryaction", + "cancelaction" + ] } - }, - "required": [ - "forms" - ], - "additionalProperties": true + } + ] + } }, - "event_element": { - "type": "object", - "properties": { - "description": { - "type": "string" - }, - "descriptions": { - "$ref": "#/definitions/descriptions" - }, - "title": { - "$ref": "#/definitions/title" - }, - "titles": { - "$ref": "#/definitions/titles" - }, - "uriVariables": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/dataSchema" - } - }, - "@type": { - "$ref": "#/definitions/type_declaration" - }, - "forms": { - "type": "array", - "minItems": 1, - "items": { - "$ref": "#/definitions/form_element_event" - } - }, - "subscription": { - "$ref": "#/definitions/dataSchema" - }, - "data": { - "$ref": "#/definitions/dataSchema" - }, - "cancellation": { - "$ref": "#/definitions/dataSchema" - }, - "type": { - "not": {} - }, - "enum": { - "not": {} - }, - "const": { - "not": {} + "additionalProperties": true + }, + "form_element_event": { + "allOf":[{"$ref":"#/definitions/form_element_base"}], + "type": "object", + "properties": { + "op": { + "oneOf": [ + { + "type": "string", + "enum": [ + "subscribeevent", + "unsubscribeevent" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "subscribeevent", + "unsubscribeevent" + ] } - }, - "required": [ - "forms" - ], - "additionalProperties": true + } + ] + } }, - "form_element_property": { - "type": "object", - "properties": { - "href": { - "$ref": "#/definitions/anyUri" - }, - "op": { - "oneOf": [{ - "type": "string", - "enum": [ - "readproperty", - "writeproperty", - "observeproperty", - "unobserveproperty" - ] - }, - { - "type": "array", - "items": { - "type": "string", - "enum": [ - "readproperty", - "writeproperty", - "observeproperty", - "unobserveproperty" - ] - } - } - ] - }, - "contentType": { - "type": "string" - }, - "security": { - "type": "array", - "items": { - "type": "string" - } - }, - "scopes": { - "type": "array", - "items": { - "type": "string" - } - }, - "subProtocol": { - "type": "string", - "enum": [ - "longpoll", - "websub", - "sse" - ] - }, - "response": { - "type": "object", - "properties": { - "contentType": { - "type": "string" - } - } + "additionalProperties": true + }, + "form_element_root": { + "allOf":[{"$ref":"#/definitions/form_element_base"}], + "type": "object", + "properties": { + "op": { + "oneOf": [ + { + "type": "string", + "enum": [ + "readallproperties", + "writeallproperties", + "readmultipleproperties", + "writemultipleproperties", + "observeallproperties", + "unobserveallproperties", + "queryallactions", + "subscribeallevents", + "unsubscribeallevents" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "readallproperties", + "writeallproperties", + "readmultipleproperties", + "writemultipleproperties", + "observeallproperties", + "unobserveallproperties", + "queryallactions", + "subscribeallevents", + "unsubscribeallevents" + ] } - }, - "required": [ - "href" - ], - "additionalProperties": true + } + ] + } }, - "form_element_action": { + "additionalProperties": true + }, + "form" : { + "$comment":"This is NOT for validation purposes but for automatic generation of TS types. For more info, please see: https://github.com/w3c/wot-thing-description/pull/1319#issuecomment-994950057", + "oneOf": [ + { "$ref" : "#/definitions/form_element_property" }, + { "$ref" : "#/definitions/form_element_action" }, + { "$ref" : "#/definitions/form_element_event" }, + { "$ref" : "#/definitions/form_element_root" } + ] + }, + "property_element": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "title": { + "$ref": "#/definitions/title" + }, + "titles": { + "$ref": "#/definitions/titles" + }, + "forms": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/form_element_property" + } + }, + "uriVariables": { "type": "object", - "properties": { - "href": { - "$ref": "#/definitions/anyUri" - }, - "op": { - "oneOf": [{ - "type": "string", - "enum": [ - "invokeaction" - ] - }, - { - "type": "array", - "items": { - "type": "string", - "enum": [ - "invokeaction" - ] - } - } - ] - }, - "contentType": { - "type": "string" - }, - "security": { - "type": "array", - "items": { - "type": "string" - } - }, - "scopes": { - "type": "array", - "items": { - "type": "string" - } - }, - "subProtocol": { - "type": "string", - "enum": [ - "longpoll", - "websub", - "sse" - ] - }, - "response": { - "type": "object", - "properties": { - "contentType": { - "type": "string" - } - } + "additionalProperties": { + "$ref": "#/definitions/dataSchema" + } + }, + "observable": { + "type": "boolean" + }, + "writeOnly": { + "type": "boolean" + }, + "readOnly": { + "type": "boolean" + }, + "oneOf": { + "type": "array", + "items": { + "$ref": "#/definitions/dataSchema" + } + }, + "unit": { + "type": "string" + }, + "enum": { + "type": "array", + "minItems": 1, + "uniqueItems": true + }, + "format": { + "type": "string" + }, + "const": {}, + "default": {}, + "type": { + "$ref": "#/definitions/dataSchema-type" + }, + "items": { + "oneOf": [ + { + "$ref": "#/definitions/dataSchema" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/dataSchema" } - }, - "required": [ - "href" - ], - "additionalProperties": true + } + ] + }, + "maxItems": { + "type": "integer", + "minimum": 0 + }, + "minItems": { + "type": "integer", + "minimum": 0 + }, + "minimum": { + "type": "number" + }, + "maximum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + }, + "minLength": { + "type": "integer", + "minimum": 0 + }, + "maxLength": { + "type": "integer", + "minimum": 0 + }, + "multipleOf": { + "$ref": "#/definitions/multipleOfDefinition" + }, + "properties": { + "additionalProperties": { + "$ref": "#/definitions/dataSchema" + } + }, + "required": { + "type": "array", + "items": { + "type": "string" + } + } }, - "form_element_event": { + "required": [ + "forms" + ], + "additionalProperties": true + }, + "action_element": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "title": { + "$ref": "#/definitions/title" + }, + "titles": { + "$ref": "#/definitions/titles" + }, + "forms": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/form_element_action" + } + }, + "uriVariables": { "type": "object", - "properties": { - "href": { - "$ref": "#/definitions/anyUri" - }, - "op": { - "oneOf": [{ - "type": "string", - "enum": [ - "subscribeevent", - "unsubscribeevent" - ] - }, - { - "type": "array", - "items": { - "type": "string", - "enum": [ - "subscribeevent", - "unsubscribeevent" - ] - } - } - ] - }, - "contentType": { - "type": "string" - }, - "security": { - "type": "array", - "items": { - "type": "string" - } - }, - "scopes": { - "type": "array", - "items": { - "type": "string" - } - }, - "subProtocol": { - "type": "string", - "enum": [ - "longpoll", - "websub", - "sse" - ] - }, - "response": { - "type": "object", - "properties": { - "contentType": { - "type": "string" - } - } - } - }, - "required": [ - "href" - ], - "additionalProperties": true + "additionalProperties": { + "$ref": "#/definitions/dataSchema" + } + }, + "input": { + "$ref": "#/definitions/dataSchema" + }, + "output": { + "$ref": "#/definitions/dataSchema" + }, + "safe": { + "type": "boolean" + }, + "idempotent": { + "type": "boolean" + } }, - "form_element_root": { + "required": [ + "forms" + ], + "additionalProperties": true + }, + "event_element": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "title": { + "$ref": "#/definitions/title" + }, + "titles": { + "$ref": "#/definitions/titles" + }, + "forms": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/form_element_event" + } + }, + "uriVariables": { "type": "object", - "properties": { - "href": { - "$ref": "#/definitions/anyUri" - }, - "op": { - "oneOf": [{ - "type": "string", - "enum": [ - "readallproperties", - "writeallproperties", - "readmultipleproperties", - "writemultipleproperties" - ] - }, - { - "type": "array", - "items": { - "type": "string", - "enum": [ - "readallproperties", - "writeallproperties", - "readmultipleproperties", - "writemultipleproperties" - ] - } - } - ] - }, - "contentType": { - "type": "string" - }, - "security": { - "type": "array", - "items": { - "type": "string" - } - }, - "scopes": { - "type": "array", - "items": { - "type": "string" - } - }, - "subProtocol": { - "type": "string", - "enum": [ - "longpoll", - "websub", - "sse" - ] - }, - "response": { - "type": "object", - "properties": { - "contentType": { - "type": "string" - } - } - } - }, - "required": [ - "href" - ], - "additionalProperties": true + "additionalProperties": { + "$ref": "#/definitions/dataSchema" + } + }, + "subscription": { + "$ref": "#/definitions/dataSchema" + }, + "data": { + "$ref": "#/definitions/dataSchema" + }, + "dataResponse": { + "$ref": "#/definitions/dataSchema" + }, + "cancellation": { + "$ref": "#/definitions/dataSchema" + } }, - "description": { + "required": [ + "forms" + ], + "additionalProperties": true + }, + "base_link_element": { + "type": "object", + "properties": { + "href": { + "$ref": "#/definitions/anyUri" + }, + "type": { "type": "string" - }, - "title": { + }, + "rel": { "type": "string" + }, + "anchor": { + "$ref": "#/definitions/anyUri" + } }, - "descriptions": { - "type": "object" + "required": [ + "href" + ], + "additionalProperties": true + }, + "link_element": { + "allOf": [ + { + "$ref": "#/definitions/base_link_element" + }, + { + "not": { + "description": "A basic link element should not contain sizes", + "type": "object", + "properties": { + "sizes": {} + }, + "required": [ + "sizes" + ] + } + }, + { + "not": { + "description": "A basic link element should not contain icon or tm:extends", + "properties": { + "rel": { + "enum": [ + "icon", + "tm:extends" + ] + } + }, + "required": [ + "rel" + ] + } + } + ] + }, + "icon_link_element": { + "allOf": [ + { + "$ref": "#/definitions/base_link_element" + }, + { + "properties": { + "rel": { + "const": "icon" + }, + "sizes": { + "type": "string", + "pattern": "[0-9]*x[0-9]+" + } + }, + "required": [ + "rel" + ] + } + ] + }, + "noSecurityScheme": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "enum": [ + "nosec" + ] + } }, - "titles": { - "type": "object" + "required": [ + "scheme" + ] + }, + "autoSecurityScheme": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "enum": [ + "auto" + ] + } }, - "dataSchema": { + "required": [ + "scheme" + ] + }, + "comboSecurityScheme": { + "oneOf": [ + { "type": "object", "properties": { - "@type": { - "$ref": "#/definitions/type_declaration" - }, - "description": { - "$ref": "#/definitions/description" - }, - "title": { - "$ref": "#/definitions/title" - }, - "descriptions": { - "$ref": "#/definitions/descriptions" - }, - "titles": { - "$ref": "#/definitions/titles" - }, - "writeOnly": { - "type": "boolean" - }, - "readOnly": { - "type": "boolean" - }, - "oneOf": { - "type": "array", - "items": { - "$ref": "#/definitions/dataSchema" - } - }, - "unit": { - "type": "string" - }, - "enum": { - "type": "array", - "minItems": 1, - "uniqueItems": true - }, - "format": { - "type": "string" - }, - "const": {}, - "type": { - "type": "string", - "enum": [ - "boolean", - "integer", - "number", - "string", - "object", - "array", - "null" - ] - }, + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "enum": [ + "combo" + ] + }, + "oneOf": { + "type": "array", + "minItems": 2, "items": { - "oneOf": [{ - "$ref": "#/definitions/dataSchema" - }, - { - "type": "array", - "items": { - "$ref": "#/definitions/dataSchema" - } - } - ] - }, - "maxItems": { - "type": "integer", - "minimum": 0 - }, - "minItems": { - "type": "integer", - "minimum": 0 - }, - "minimum": { - "type": "number" - }, - "maximum": { - "type": "number" - }, - "properties": { - "additionalProperties": { - "$ref": "#/definitions/dataSchema" - } - }, - "required": { - "type": "array", - "items": { - "type": "string" - } + "type": "string" } - } - }, - "link_element": { + } + }, + "required": [ + "scheme", + "oneOf" + ] + }, + { "type": "object", "properties": { - "anchor": { - "$ref": "#/definitions/anyUri" - }, - "href": { - "$ref": "#/definitions/anyUri" - }, - "rel": { - "type": "string" - }, - "type": { - "type": "string" + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "enum": [ + "combo" + ] + }, + "allOf": { + "type": "array", + "minItems": 2, + "items": { + "type": "string" } + } }, "required": [ - "href" - ], - "additionalProperties": true - }, - "securityScheme": { - "oneOf": [{ - "type": "object", - "properties": { - "@type": { - "$ref": "#/definitions/type_declaration" - }, - "description": { - "$ref": "#/definitions/description" - }, - "descriptions": { - "$ref": "#/definitions/descriptions" - }, - "proxy": { - "$ref": "#/definitions/anyUri" - }, - "scheme": { - "type": "string", - "enum": [ - "nosec" - ] - } - }, - "required": [ - "scheme" - ] - }, - { - "type": "object", - "properties": { - "@type": { - "$ref": "#/definitions/type_declaration" - }, - "description": { - "$ref": "#/definitions/description" - }, - "descriptions": { - "$ref": "#/definitions/descriptions" - }, - "proxy": { - "$ref": "#/definitions/anyUri" - }, - "scheme": { - "type": "string", - "enum": [ - "basic" - ] - }, - "in": { - "type": "string", - "enum": [ - "header", - "query", - "body", - "cookie" - ] - }, - "name": { - "type": "string" - } - }, - "required": [ - "scheme" - ] - }, - { - "type": "object", - "properties": { - "@type": { - "$ref": "#/definitions/type_declaration" - }, - "description": { - "$ref": "#/definitions/description" - }, - "descriptions": { - "$ref": "#/definitions/descriptions" - }, - "proxy": { - "$ref": "#/definitions/anyUri" - }, - "scheme": { - "type": "string", - "enum": [ - "cert" - ] - }, - "identity": { - "type": "string" - } - }, - "required": [ - "scheme" - ] - }, - { - "type": "object", - "properties": { - "@type": { - "$ref": "#/definitions/type_declaration" - }, - "description": { - "$ref": "#/definitions/description" - }, - "descriptions": { - "$ref": "#/definitions/descriptions" - }, - "proxy": { - "$ref": "#/definitions/anyUri" - }, - "scheme": { - "type": "string", - "enum": [ - "digest" - ] - }, - "qop": { - "type": "string", - "enum": [ - "auth", - "auth-int" - ] - }, - "in": { - "type": "string", - "enum": [ - "header", - "query", - "body", - "cookie" - ] - }, - "name": { - "type": "string" - } - }, - "required": [ - "scheme" - ] - }, - { - "type": "object", - "properties": { - "@type": { - "$ref": "#/definitions/type_declaration" - }, - "description": { - "$ref": "#/definitions/description" - }, - "descriptions": { - "$ref": "#/definitions/descriptions" - }, - "proxy": { - "$ref": "#/definitions/anyUri" - }, - "scheme": { - "type": "string", - "enum": [ - "bearer" - ] - }, - "authorization": { - "$ref": "#/definitions/anyUri" - }, - "alg": { - "type": "string", - "enum": [ - "MD5", - "ES256", - "ES512-256" - ] - }, - "format": { - "type": "string", - "enum": [ - "jwt", - "jwe", - "jws" - ] - }, - "in": { - "type": "string", - "enum": [ - "header", - "query", - "body", - "cookie" - ] - }, - "name": { - "type": "string" - } - }, - "required": [ - "scheme" - ] - }, - { - "type": "object", - "properties": { - "@type": { - "$ref": "#/definitions/type_declaration" - }, - "description": { - "$ref": "#/definitions/description" - }, - "descriptions": { - "$ref": "#/definitions/descriptions" - }, - "proxy": { - "$ref": "#/definitions/anyUri" - }, - "scheme": { - "type": "string", - "enum": [ - "psk" - ] - }, - "identity": { - "type": "string" - } - }, - "required": [ - "scheme" - ] - }, - { - "type": "object", - "properties": { - "@type": { - "$ref": "#/definitions/type_declaration" - }, - "description": { - "$ref": "#/definitions/description" - }, - "descriptions": { - "$ref": "#/definitions/descriptions" - }, - "proxy": { - "$ref": "#/definitions/anyUri" - }, - "scheme": { - "type": "string", - "enum": [ - "public" - ] - }, - "identity": { - "type": "string" - } - }, - "required": [ - "scheme" - ] - }, - { - "type": "object", - "properties": { - "@type": { - "$ref": "#/definitions/type_declaration" - }, - "description": { - "$ref": "#/definitions/description" - }, - "descriptions": { - "$ref": "#/definitions/descriptions" - }, - "proxy": { - "$ref": "#/definitions/anyUri" - }, - "scheme": { - "type": "string", - "enum": [ - "oauth2" - ] - }, - "authorization": { - "$ref": "#/definitions/anyUri" - }, - "token": { - "$ref": "#/definitions/anyUri" - }, - "refresh": { - "$ref": "#/definitions/anyUri" - }, - "scopes": { - "type": "array", - "items": { - "type": "string" - } - }, - "flow": { - "type": "string", - "enum": [ - "implicit", - "password", - "client", - "code" - ] - } - }, - "required": [ - "scheme", - "flow" - ] - }, - { - "type": "object", - "properties": { - "@type": { - "$ref": "#/definitions/type_declaration" - }, - "description": { - "$ref": "#/definitions/description" - }, - "descriptions": { - "$ref": "#/definitions/descriptions" - }, - "proxy": { - "$ref": "#/definitions/anyUri" - }, - "scheme": { - "type": "string", - "enum": [ - "apikey" - ] - }, - "in": { - "type": "string", - "enum": [ - "header", - "query", - "body", - "cookie" - ] - }, - "name": { - "type": "string" - } - }, - "required": [ - "scheme" - ] - }, - { - "type": "object", - "properties": { - "@type": { - "$ref": "#/definitions/type_declaration" - }, - "description": { - "$ref": "#/definitions/description" - }, - "descriptions": { - "$ref": "#/definitions/descriptions" - }, - "proxy": { - "$ref": "#/definitions/anyUri" - }, - "scheme": { - "type": "string", - "enum": [ - "pop" - ] - }, - "authorization": { - "$ref": "#/definitions/anyUri" - }, - "format": { - "type": "string", - "enum": [ - "jwt", - "jwe", - "jws" - ] - }, - "alg": { - "type": "string", - "enum": [ - "MD5", - "ES256", - "ES512-256" - ] - }, - "in": { - "type": "string", - "enum": [ - "header", - "query", - "body", - "cookie" - ] - }, - "name": { - "type": "string" - } - }, - "required": [ - "scheme" - ] - } + "scheme", + "allOf" ] - }, - "anyUri": { + } + ] + }, + "basicSecurityScheme": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { "type": "string", - "format": "uri-reference" - } - }, - "type": "object", - "properties": { - "id": { + "enum": [ + "basic" + ] + }, + "in": { "type": "string", - "format": "uri" - }, - "title": { - "$ref": "#/definitions/title" - }, - "titles": { - "$ref": "#/definitions/titles" + "enum": [ + "header", + "query", + "body", + "cookie", + "auto" + ] + }, + "name": { + "type": "string" + } }, + "required": [ + "scheme" + ] + }, + "digestSecurityScheme": { + "type": "object", "properties": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/property_element" - } - }, - "actions": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/action_element" - } + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "enum": [ + "digest" + ] + }, + "qop": { + "type": "string", + "enum": [ + "auth", + "auth-int" + ] + }, + "in": { + "type": "string", + "enum": [ + "header", + "query", + "body", + "cookie", + "auto" + ] + }, + "name": { + "type": "string" + } }, - "events": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/event_element" - } + "required": [ + "scheme" + ] + }, + "apiKeySecurityScheme": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "enum": [ + "apikey" + ] + }, + "in": { + "type": "string", + "enum": [ + "header", + "query", + "body", + "cookie" + ] + }, + "name": { + "type": "string" + } }, - "description": { + "required": [ + "scheme" + ] + }, + "bearerSecurityScheme": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "enum": [ + "bearer" + ] + }, + "authorization": { + "$ref": "#/definitions/anyUri" + }, + "alg": { + "type": "string" + }, + "format": { + "type": "string" + }, + "in": { + "type": "string", + "enum": [ + "header", + "query", + "body", + "cookie", + "auto" + ] + }, + "name": { + "type": "string" + } }, - "descriptions": { + "required": [ + "scheme" + ] + }, + "pskSecurityScheme": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "enum": [ + "psk" + ] + }, + "identity": { + "type": "string" + } }, - "version": { - "type": "object", - "properties": { - "instance": { - "type": "string" + "required": [ + "scheme" + ] + }, + "oAuth2SecurityScheme": { + "type": "object", + "properties": { + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "proxy": { + "$ref": "#/definitions/anyUri" + }, + "scheme": { + "type": "string", + "enum": [ + "oauth2" + ] + }, + "authorization": { + "$ref": "#/definitions/anyUri" + }, + "token": { + "$ref": "#/definitions/anyUri" + }, + "refresh": { + "$ref": "#/definitions/anyUri" + }, + "scopes": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" } - }, - "required": [ - "instance" + }, + { + "type": "string" + } ] + }, + "flow": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string", + "enum": [ + "code", + "client", + "device" + ] + } + ] + } }, - "links": { - "type": "array", - "items": { - "$ref": "#/definitions/link_element" - } + "required": [ + "scheme" + ] + }, + "securityScheme": { + "oneOf": [ + { + "$ref": "#/definitions/noSecurityScheme" + }, + { + "$ref": "#/definitions/autoSecurityScheme" + }, + { + "$ref": "#/definitions/comboSecurityScheme" + }, + { + "$ref": "#/definitions/basicSecurityScheme" + }, + { + "$ref": "#/definitions/digestSecurityScheme" + }, + { + "$ref": "#/definitions/apiKeySecurityScheme" + }, + { + "$ref": "#/definitions/bearerSecurityScheme" + }, + { + "$ref": "#/definitions/pskSecurityScheme" + }, + { + "$ref": "#/definitions/oAuth2SecurityScheme" + } + ] + } + }, + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uri" + }, + "title": { + "$ref": "#/definitions/title" + }, + "titles": { + "$ref": "#/definitions/titles" + }, + "properties": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/property_element" + } + }, + "actions": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/action_element" + } + }, + "events": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/event_element" + } + }, + "description": { + "$ref": "#/definitions/description" + }, + "descriptions": { + "$ref": "#/definitions/descriptions" + }, + "version": { + "type": "object", + "properties": { + "instance": { + "type": "string" + } }, - "forms": { + "required": [ + "instance" + ] + }, + "links": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/definitions/link_element" + }, + { + "$ref": "#/definitions/icon_link_element" + } + ] + } + }, + "forms": { "type": "array", "minItems": 1, "items": { - "$ref": "#/definitions/form_element_root" + "$ref": "#/definitions/form_element_root" } - }, - "base": { + }, + "base": { + "$ref": "#/definitions/anyUri" + }, + "securityDefinitions": { + "type": "object", + "minProperties": 1, + "additionalProperties": { + "$ref": "#/definitions/securityScheme" + } + }, + "schemaDefinitions": { + "type": "object", + "minProperties": 1, + "additionalProperties": { + "$ref": "#/definitions/dataSchema" + } + }, + "support": { + "$ref": "#/definitions/anyUri" + }, + "created": { + "type": "string", + "format": "date-time" + }, + "modified": { + "type": "string", + "format": "date-time" + }, + "profile": { + "oneOf": [ + { "$ref": "#/definitions/anyUri" - }, - "securityDefinitions": { - "type": "object", - "minProperties": 1, - "additionalProperties": { - "$ref": "#/definitions/securityScheme" + }, + { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/anyUri" } - }, - "support": { - "$ref": "#/definitions/anyUri" - }, - "created": { + } + ] + }, + "security": { + "oneOf": [ + { "type": "string" - }, - "modified": { - "type": "string" - }, - "security": { + }, + { "type": "array", "minItems": 1, "items": { - "type": "string" + "type": "string" } - }, - "@type": { - "$ref": "#/definitions/type_declaration" - }, - "@context": { - "$ref": "#/definitions/thing-context" + } + ] + }, + "uriVariables": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/dataSchema" } + }, + "@type": { + "$ref": "#/definitions/type_declaration" + }, + "@context": { + "$ref": "#/definitions/thing-context" + } }, "required": [ - "title", - "security", - "securityDefinitions", - "@context" + "title", + "security", + "securityDefinitions", + "@context" ], "additionalProperties": true -} + } diff --git a/lib/webofthings/wotutils.js b/lib/webofthings/wotutils.js index 0e95a4c..d9b35b8 100644 --- a/lib/webofthings/wotutils.js +++ b/lib/webofthings/wotutils.js @@ -15,6 +15,8 @@ **/ const Ajv = require('ajv'); +const addFormats = require('ajv-formats'); +const addFormatsDraft2019 = require('ajv-formats-draft2019'); const url = require('url'); const fs = require('fs'); const path = require('path'); @@ -22,7 +24,9 @@ const path = require('path'); function validateTd(td) { const TDSchema = JSON.parse(fs.readFileSync(path.join(__dirname, './td-json-schema-validation.json'), 'utf-8')); - const ajv = new Ajv({allErrors: true}); + const ajv = new Ajv({allErrors: true, strict: false, strictSchema: false}); + addFormats(ajv); + addFormatsDraft2019(ajv); const valid = ajv.validate(TDSchema, td); return { result: valid, errorText: valid?"":ajv.errorsText()}; @@ -237,7 +241,7 @@ function filterFormTd(td) { let forms = td.properties[p].forms; if (forms) { forms = forms.filter((f) => (f.hasOwnProperty("href") && - (f.href.match(/^https?:/) || f.href.match(/^wss?:/) || f.href.match(/^coaps?:/)))); + (f.href.match(/^https?:/) || f.href.match(/^wss?:/) || f.href.match(/^coaps?:/) || f.href.match(/^mqtt:/)))); } td.properties[p].forms = forms; } @@ -245,7 +249,7 @@ function filterFormTd(td) { let forms = td.actions[a].forms; if (forms) { forms = forms.filter((f) => (f.hasOwnProperty("href") && - (f.href.match(/^https?:/) || f.href.match(/^wss?:/) || f.href.match(/^coaps?:/)))); + (f.href.match(/^https?:/) || f.href.match(/^wss?:/) || f.href.match(/^coaps?:/) || f.href.match(/^mqtt:/)))); } td.actions[a].forms = forms; } @@ -253,7 +257,7 @@ function filterFormTd(td) { let forms = td.events[e].forms; if (forms) { forms = forms.filter((f) => (f.hasOwnProperty("href") && - (f.href.match(/^https?:/) || f.href.match(/^wss?:/) || f.href.match(/^coaps?:/)))); + (f.href.match(/^https?:/) || f.href.match(/^wss?:/) || f.href.match(/^coaps?:/) || f.href.match(/^mqtt:/)))); } td.events[e].forms = forms; } diff --git a/package.json b/package.json index 2bf801e..18cdb67 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,9 @@ "web of things" ], "dependencies": { - "ajv": "6.12.6", + "ajv": "8.11.0", + "ajv-formats": "2.1.1", + "ajv-formats-draft2019": "1.6.1", "api-spec-converter": "2.12.0", "axios": "0.26.0", "colors": "1.4.0", diff --git a/templates/webofthings/node.js.mustache b/templates/webofthings/node.js.mustache index a388c71..66d8e78 100644 --- a/templates/webofthings/node.js.mustache +++ b/templates/webofthings/node.js.mustache @@ -7,6 +7,7 @@ module.exports = function (RED) { const urltemplate = require('url-template'); const Ajv = require('ajv').default; const nodecoap = require('coap'); + const mqtt = require('mqtt'); function extractTemplate(href, context={}) { return urltemplate.parse(href).expand(context); @@ -15,9 +16,9 @@ module.exports = function (RED) { function getResType(form) { if (form) { if (form.response && form.response.contentType) { - return form.response.contentType; + return form.response.contentType.split(';')[0]; } else if (form.contentType) { - return form.contentType; + return form.contentType.split(';')[0]; } } return "application/json"; @@ -25,7 +26,7 @@ module.exports = function (RED) { function isBinaryType(contentType) { let result; - switch (contentType) { + switch (contentType.split(';')[0]) { case "image/jpeg": case "application/octet-stream": result = true; @@ -37,6 +38,101 @@ module.exports = function (RED) { return result; } + function bindingMqtt(node, send, done, form, options={}) { // options.interaction, options.reqbody, options.msg + let jobqueue = []; // [{topic, op: "publish"/"subscribe"/"subscribeonce", msg, callback(recvmsg)}] + const mqttcli = options.mqttcli; + mqttcli.on('connect', () => { + node.trace('connected to broker'); + flushJobQueue(); + }); + function flushJobQueue() { + jobqueue.forEach((e) => { + processMqttOp(e); + }); + jobqueue = []; + } + function processMqttOp({ topic, op, msg, callback }) { + switch (op) { + case "publish": + mqttcli.publish(topic, msg); + break; + case "subscribe": + mqttcli.on('message', (t, m, _) => { + if (t !== topic) { + node.trace(`wrong topic: ${t}`); + } + callback(m); + }) + mqttcli.subscribe(topic); + break; + case "subscribeonce": + mqttcli.once('message', (t, m, _) => { + if (t !== topic) { + node.trace(`wrong topic: ${t}`); + } + mqttcli.unsubscribe(topic); + callback(m); + }) + mqttcli.subscribe(topic); + default: + // unknown op + break; + } + } + function mkmessage(form, origmessage) { + let retval; + if (getResType(form) == "application/json") { + try { + retval = {payload: JSON.parse(origmessage.toString())}; + } catch (_) { + retval = {payload: origmessage}; + } + } else { + retval = {payload: origmessage}; + } + return retval; + } + + const msg = options.msg || {}; + const topic = (new URL(extractTemplate(form.href, options.urivars))).pathname.slice(1); + node.trace(`interaction: ${options.interaction}`); + switch (options.interaction) { + case "property-read": + node.trace(`MQTT subscribe (property-read)`); + jobqueue.push({topic, op: "subscribeonce", callback: (m) => { + send(mkmessage(form, m)); + done(); + }}); + break; + case "property-write": + node.trace(`MQTT publish (property-write)`); + jobqueue.push({topic, op: "publish", msg: JSON.stringify(options.reqbody), callback: (_) => {done()}}); + break; + case "property-observe": + node.trace(`MQTT subscribe (property-observe)`); + jobqueue.push({topic, op: "subscribe", callback: (m) => { + node.send(mkmessage(form, m)); + }}); + break; + case "action": + node.trace(`MQTT publish (action)`); + jobqueue.push({topic, op: "publish", msg: JSON.stringify(options.reqbody), callback: (_) => {done()}}); + break; + case "event": + node.trace(`MQTT subscribe (event)`); + jobqueue.push({topic, op: "subscribe", callback: (m) => { + node.send(mkmessage(form, m)); + }}); + break; + default: + node.trace(`Unknown interaction`); + break; + } + if (mqttcli.connected) { + flushJobQueue(); + } + } + function coapMethodCodeToName(methodCode) { switch (methodCode) { case '1': @@ -451,6 +547,26 @@ module.exports = function (RED) { return auth; } + function setupMqttConnection(form, node) { + const u = new URL(form.href); + const mqtturi = `${u.protocol}//${u.host}`; + const cli = mqtt.connect(mqtturi); + cli.on('connect', ()=> { + node.status({fill:"green", shape:"dot", text:"connected"}); + }); + cli.on('reconnect', () => { + node.status({fill:"yellow", shape:"dot", text:"reconnecting..."}); + }); + cli.on('offline', () => { + node.status({fill:"red", shape:"ring", text:"disconnected"}); + }); + cli.on('error', () => { + node.status({fill:"red", shape:"dot", text:"error"}); + }); + return cli; + } + + function Node(config) { RED.nodes.createNode(this, config); const node = this; @@ -465,16 +581,20 @@ module.exports = function (RED) { const username = node.credentials.username; const password = node.credentials.password; const token = node.credentials.token; + let mqttcli = null; node.td = {{{tdstr}}}; const normTd = {{{normtd}}}; if (node.interactiontype === "property") { + const prop = normTd.properties[node.propname]; + const form = prop.forms[node.formindex];// formSelection("property-read", prop.forms); + const auth = makeauth(normTd, form, username, password, token); + if (form.href.match(/^mqtt:/)) { + mqttcli = setupMqttConnection(form, node); + } if (node.proptype === "read") { node.on("input", (msg, send, done) => { send = send || function() { node.send.apply(node,arguments) }; - const prop = normTd.properties[node.propname]; - const form = prop.forms[node.formindex];// formSelection("property-read", prop.forms); - const auth = makeauth(normTd, form, username, password, token); const urivars = prop.hasOwnProperty("uriVariables") ? msg.payload : {}; if (prop.uriVariables) { const ajv = new Ajv({allErrors: true, strict: false, validateFormats: false}); @@ -487,14 +607,13 @@ module.exports = function (RED) { bindingHttp(node, send, done, form, {interaction:"property-read", auth, msg, urivars, outschema: prop}); } else if (form.href.match(/^coaps?:/)) { bindingCoap(node, send, done, form, {interaction:"property-read", auth, msg, urivars, outschema: prop}); + } else if (form.href.match(/^mqtt:/)) { + bindingMqtt(node, send, done, form, {interaction:"property-read", auth, msg, urivars, outschema: prop, mqttcli}); } }); } else if (node.proptype === "write") { node.on("input", (msg, send, done) => { send = send || function() { node.send.apply(node,arguments) }; - const prop = normTd.properties[node.propname]; - const form = prop.forms[node.formindex];// formSelection("property-write", prop.forms); - const auth = makeauth(normTd, form, username, password, token); const ajv = new Ajv({allErrors: true, strict: false, validateFormats: false}); if (!ajv.validate(prop, msg.payload)) { node.warn(`input schema validation error: ${ajv.errorsText()}`, msg); @@ -504,34 +623,31 @@ module.exports = function (RED) { bindingHttp(node, send, done, form, {interaction: "property-write", auth, msg, reqbody: msg.payload}); } else if (form.href.match(/^coaps?:/)) { bindingCoap(node, send, done, form, {interaction: "property-write", auth, msg, reqbody: msg.payload}); + } else if (form.href.match(/^mqtt:/)) { + bindingMqtt(node, send, done, form, {interaction: "property-write", auth, msg, reqbody: msg.payload, mqttcli}); } }); } else if (node.proptype === "observe") { - const prop = normTd.properties[node.propname]; - const form = prop.forms[node.formindex];// formSelection("property-observe", prop.forms); - const auth = makeauth(normTd, form, username, password, token); - const urivars = prop.hasOwnProperty("uriVariables") ? msg.payload : {}; - if (prop.uriVariables) { - const ajv = new Ajv({allErrors: true, strict: false, validateFormats: false}); - const uvschema = { type: "object", properties: prop.uriVariables }; - if (!ajv.validate(uvschema, urivars)) { - node.warn(`input schema validation error: ${ajv.errorsText()}`, msg); - } - } + const urivars = {}; if (form.href.match(/^wss?:/)) { // websocket bindingWebSocket(node, form, {auth, urivars, outschema: prop}); } else if (form.href.match(/^coaps?:/)) { // CoAP observe bindingCoapObserve(node, form, {auth, urivars, outschema: prop}); - } else { // long polling + } else if (form.href.match(/^https?:/)) { // long polling bindingLongPoll(node, form, {auth, urivars, outschema: prop}); + } else if (form.href.match(/^mqtt:/)) { + bindingMqtt(node, undefined, undefined, form, {interaction: "property-observe", auth, urivars, outschema: prop, mqttcli}); } } } else if (node.interactiontype === "action") { + const act = normTd.actions[node.actname]; + const form = act.forms[node.formindex];// formSelection("action", act.forms); + const auth = makeauth(normTd, form, username, password, token); + if (form.href.match(/^mqtt:/)) { + mqttcli = setupMqttConnection(form, node); + } node.on("input", (msg, send, done) => { send = send || function() { node.send.apply(node,arguments) }; - const act = normTd.actions[node.actname]; - const form = act.forms[node.formindex];// formSelection("action", act.forms); - const auth = makeauth(normTd, form, username, password, token); const urivars = act.hasOwnProperty("uriVariables") ? msg.payload : {}; if (act.uriVariables) { const ajv = new Ajv({allErrors: true, strict: false, validateFormats: false}); @@ -550,28 +666,33 @@ module.exports = function (RED) { bindingHttp(node, send, done, form, {interaction: "action", auth, msg, urivars, reqbody:msg.payload}); } else if (form.href.match(/^coaps?/)) { bindingCoap(node, send, done, form, {interaction: "action", auth, msg, urivars, reqbody:msg.payload}); + } else if (form.href.match(/^mqtt:/)) { + bindingMqtt(node, send, done, form, {interaction: "action", auth, msg, urivars, reqbody:msg.payload, mqttcli}); } }); } else if (node.interactiontype === "event") { const ev = normTd.events[node.evname]; const form = ev.forms[node.formindex];// formSelection("event", ev.forms); const auth = makeauth(normTd, form, username, password, token); - const urivars = ev.hasOwnProperty("uriVariables") ? msg.payload : {}; - if (ev.uriVariables) { - const ajv = new Ajv({allErrors: true, strict: false, validateFormats: false}); - const uvschema = { type: "object", properties: ev.uriVariables }; - if (!ajv.validate(uvschema, urivars)) { - node.warn(`input schema validation error: ${ajv.errorsText()}`, msg); - } + if (form.href.match(/^mqtt:/)) { + mqttcli = setupMqttConnection(form, node); } + const urivars = {}; if (form.href.match(/^wss?:/)) { // websocket bindingWebSocket(node, form, {auth, urivars}); } else if (form.href.match(/^coaps?:/)) { bindingCoapObserve(node, form, {auth, urivars}); - } else { // long polling + } else if (form.href.match(/^https?:/)) { // long polling bindingLongPoll(node, form, {auth, urivars}); - } + } else if (form.href.match(/^mqtt:/)) { + bindingMqtt(node, undefined, undefined, form, {interaction: "event", auth, urivars, mqttcli }); + } } + node.on('close', () => { + if (mqttcli) { + mqttcli.end(true); + } + }); } RED.nodes.registerType("{{&nodeName}}", Node, { diff --git a/templates/webofthings/package.json.mustache b/templates/webofthings/package.json.mustache index 4b18e4b..3d21096 100644 --- a/templates/webofthings/package.json.mustache +++ b/templates/webofthings/package.json.mustache @@ -25,8 +25,9 @@ "request": "2.88.2", "ws": "8.5.0", "url-template": "2.0.8", - "ajv": "8.10.0", - "coap": "1.0.3" + "ajv": "8.11.0", + "coap": "1.0.3", + "mqtt": "4.3.7" }, "devDependencies": { "node-red": "2.2.2",