From b94fc683ebbca670533d8e7a7621b851e0a0d092 Mon Sep 17 00:00:00 2001 From: Alvaro Vega Date: Wed, 13 Dec 2023 13:30:36 +0100 Subject: [PATCH 01/18] use transport and endpoint from group --- lib/bindings/AMQPBinding.js | 2 +- lib/bindings/HTTPBinding.js | 24 ++++++++++++------------ lib/bindings/MQTTBinding.js | 5 +++-- lib/commandHandler.js | 13 +++++++++++-- lib/commonBindings.js | 14 ++++++++++++-- lib/iotaUtils.js | 12 ++++++++---- 6 files changed, 47 insertions(+), 23 deletions(-) diff --git a/lib/bindings/AMQPBinding.js b/lib/bindings/AMQPBinding.js index 9c371ab5..b195d7df 100644 --- a/lib/bindings/AMQPBinding.js +++ b/lib/bindings/AMQPBinding.js @@ -44,7 +44,7 @@ let amqpChannel; * @param {Object} device Data object for the device receiving the command. * @param {String} serializedPayload String payload in JSON format for the command. */ -function executeCommand(apiKey, device, cmdName, serializedPayload, contentType, callback) { +function executeCommand(apiKey, group, device, cmdName, serializedPayload, contentType, callback) { config .getLogger() .debug( diff --git a/lib/bindings/HTTPBinding.js b/lib/bindings/HTTPBinding.js index ff698864..d4c316ab 100644 --- a/lib/bindings/HTTPBinding.js +++ b/lib/bindings/HTTPBinding.js @@ -202,9 +202,9 @@ function parseDataMultipleMeasure(req, res, next) { } } -function executeCommand(apiKey, device, cmdName, serializedPayload, contentType, callback) { +function executeCommand(apiKey, group, device, cmdName, serializedPayload, contentType, callback) { const options = { - url: device.endpoint, + url: device.endpoint || group.endpoint, method: 'POST', body: serializedPayload, headers: { @@ -213,8 +213,8 @@ function executeCommand(apiKey, device, cmdName, serializedPayload, contentType, 'content-type': contentType } }; - if (device.endpoint) { - // device.endpoint or another field like device.endpointExp ? + if (options.endpoint) { + // endpoint could be an expression const parser = iotAgentLib.dataPlugins.expressionTransformation; let attrList = iotAgentLib.dataPlugins.utils.getIdTypeServSubServiceFromDevice(device); attrList = device.staticAttributes ? attrList.concat(device.staticAttributes) : attrList.concat([]); @@ -223,11 +223,11 @@ function executeCommand(apiKey, device, cmdName, serializedPayload, contentType, // expression result will be the full command payload let endpointRes = null; try { - endpointRes = parser.applyExpression(device.endpoint, ctxt, device); + endpointRes = parser.applyExpression(options.endpoint, ctxt, device); } catch (e) { // no error should be reported } - options.url = endpointRes ? endpointRes : device.endpoint; + options.url = endpointRes ? endpointRes : options.endpoint; } if (config.getConfig().http.timeout) { options.timeout = config.getConfig().http.timeout; @@ -404,7 +404,7 @@ function handleIncomingMeasure(req, res, next) { } } - iotaUtils.retrieveDevice(req.deviceId, req.apiKey, transport, processDeviceMeasure); + iotaUtils.retrieveDevice(req.deviceId, req.apiKey, group.transport || transport, processDeviceMeasure); } function isCommand(req, res, next) { @@ -418,7 +418,7 @@ function isCommand(req, res, next) { next(); } -function sendConfigurationToDevice(apiKey, deviceId, results, callback) { +function sendConfigurationToDevice(apiKey, group, deviceId, results, callback) { function handleDeviceResponse(innerCallback) { return function (error, response, body) { if (error) { @@ -431,9 +431,9 @@ function sendConfigurationToDevice(apiKey, deviceId, results, callback) { }; } - function sendRequest(device, results, innerCallback) { + function sendRequest(device, group, results, innerCallback) { const resultRequest = { - url: device.endpoint + constants.HTTP_CONFIGURATION_PATH, + url: (device.endpoint || group.endpoint) + constants.HTTP_CONFIGURATION_PATH, method: 'POST', json: iotaUtils.createConfigurationNotification(results), headers: { @@ -445,13 +445,13 @@ function sendConfigurationToDevice(apiKey, deviceId, results, callback) { request(resultRequest, handleDeviceResponse(innerCallback)); } - iotaUtils.retrieveDevice(deviceId, apiKey, transport, function (error, device) { + iotaUtils.retrieveDevice(deviceId, apiKey, group.transport || transport, function (error, device) { if (error) { callback(error); } else if (!device.endpoint) { callback(new errors.EndpointNotFound(device.id)); } else { - sendRequest(device, results, callback); + sendRequest(device, group, results, callback); } }); } diff --git a/lib/bindings/MQTTBinding.js b/lib/bindings/MQTTBinding.js index b512d5bf..d0361ba6 100644 --- a/lib/bindings/MQTTBinding.js +++ b/lib/bindings/MQTTBinding.js @@ -165,7 +165,7 @@ function recreateSubscriptions(callback) { * @param {String} deviceId ID of the Device. * @param {Object} results Context Broker response. */ -function sendConfigurationToDevice(apiKey, deviceId, results, callback) { +function sendConfigurationToDevice(apiKey, group, deviceId, results, callback) { const configurations = iotaUtils.createConfigurationNotification(results); const options = {}; context = fillService(context, { service: 'n/a', subservice: 'n/a' }); @@ -369,10 +369,11 @@ function stop(callback) { * JSON payload (already containing the command information). * * @param {String} apiKey APIKey of the device that will be receiving the command. + * @param {Object} group Data object for the group receiving the command. * @param {Object} device Data object for the device receiving the command. * @param {String} serializedPayload String payload in JSON format for the command. */ -function executeCommand(apiKey, device, cmdName, serializedPayload, contentType, callback) { +function executeCommand(apiKey, group, device, cmdName, serializedPayload, contentType, callback) { const options = {}; // retrieve command mqtt options from device const commands = Object.assign({}, ...device.commands.map((c) => ({ [c.name]: c }))); diff --git a/lib/commandHandler.js b/lib/commandHandler.js index d9830eff..91310e73 100644 --- a/lib/commandHandler.js +++ b/lib/commandHandler.js @@ -75,6 +75,15 @@ function serializedPayloadCommand(payload, command) { function generateCommandExecution(apiKey, device, attribute) { let payload = {}; let command = device && device.commands.find((att) => att.name === attribute.name); + let group; + iotAgentLib.getConfigurationSilently(config.getConfig().iota.defaultResource || '', apiKey, function ( + error, + foundGroup + ) { + if (!error) { + group = foundGroup; + } + }); if (command && command.expression) { let parser = iotAgentLib.dataPlugins.expressionTransformation; // The context for the JEXL expression should be the ID, TYPE, S, SS @@ -111,9 +120,9 @@ function generateCommandExecution(apiKey, device, attribute) { ); const executions = transportSelector.createExecutionsForBinding( - [apiKey, device, attribute.name, serialized, contentType], + [apiKey, group, device, attribute.name, serialized, contentType], 'executeCommand', - device.transport || config.getConfig().defaultTransport + device.transport || group.transport || config.getConfig().defaultTransport ); return executions; diff --git a/lib/commonBindings.js b/lib/commonBindings.js index 3ba5afd0..9f07b63d 100644 --- a/lib/commonBindings.js +++ b/lib/commonBindings.js @@ -173,10 +173,20 @@ function extractAttributes(device, current, payloadType) { } function sendConfigurationToDevice(device, apiKey, deviceId, results, callback) { + let group; + iotAgentLib.getConfigurationSilently(config.getConfig().iota.defaultResource || '', apiKey, function ( + error, + foundGroup + ) { + if (!error) { + group = foundGroup; + } + }); + transportSelector.applyFunctionFromBinding( - [apiKey, deviceId, results], + [apiKey, group, deviceId, results], 'sendConfigurationToDevice', - device.transport || config.getConfig().defaultTransport, + device.transport || group.transport || config.getConfig().defaultTransport, callback ); } diff --git a/lib/iotaUtils.js b/lib/iotaUtils.js index ff1f1c0b..a2ef810d 100644 --- a/lib/iotaUtils.js +++ b/lib/iotaUtils.js @@ -171,10 +171,14 @@ function findOrCreate(deviceId, transport, apikey, group, callback) { ) { newDevice.protocol = config.getConfig().iota.iotManager.protocol; } - // Fix transport depending on binding - if (!newDevice.transport) { - newDevice.transport = transport; - } + // // Fix transport depending on binding and group + // if (!newDevice.transport) { + // if ('transport' in group && group.transport !== undefined) { + // newDevice.transport = group.transport; + // } else { + // newDevice.transport = transport; + // } + // } if ('ngsiVersion' in group && group.ngsiVersion !== undefined) { newDevice.ngsiVersion = group.ngsiVersion; } From 9f48709ab601033e513c1c207b9bfac42786aa5a Mon Sep 17 00:00:00 2001 From: Alvaro Vega Date: Wed, 13 Dec 2023 14:42:55 +0100 Subject: [PATCH 02/18] fix --- lib/bindings/HTTPBinding.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/bindings/HTTPBinding.js b/lib/bindings/HTTPBinding.js index d4c316ab..d27eb21c 100644 --- a/lib/bindings/HTTPBinding.js +++ b/lib/bindings/HTTPBinding.js @@ -404,7 +404,7 @@ function handleIncomingMeasure(req, res, next) { } } - iotaUtils.retrieveDevice(req.deviceId, req.apiKey, group.transport || transport, processDeviceMeasure); + iotaUtils.retrieveDevice(req.deviceId, req.apiKey, transport, processDeviceMeasure); } function isCommand(req, res, next) { From e91da52d8bcb0283aa28acbbfa758881c386a9ba Mon Sep 17 00:00:00 2001 From: Alvaro Vega Date: Wed, 13 Dec 2023 16:47:07 +0100 Subject: [PATCH 03/18] fix sendConfigurationToDevice --- lib/bindings/AMQPBinding.js | 2 +- lib/bindings/HTTPBinding.js | 1 + lib/commonBindings.js | 4 ++-- lib/iotaUtils.js | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/bindings/AMQPBinding.js b/lib/bindings/AMQPBinding.js index b195d7df..1d82c075 100644 --- a/lib/bindings/AMQPBinding.js +++ b/lib/bindings/AMQPBinding.js @@ -258,7 +258,7 @@ function deviceUpdatingHandler(device, callback) { * @param {String} deviceId ID of the Device. * @param {Object} results Context Broker response. */ -function sendConfigurationToDevice(apiKey, deviceId, results, callback) { +function sendConfigurationToDevice(apiKey, group, deviceId, results, callback) { callback(); } diff --git a/lib/bindings/HTTPBinding.js b/lib/bindings/HTTPBinding.js index d27eb21c..4acd9bdd 100644 --- a/lib/bindings/HTTPBinding.js +++ b/lib/bindings/HTTPBinding.js @@ -434,6 +434,7 @@ function sendConfigurationToDevice(apiKey, group, deviceId, results, callback) { function sendRequest(device, group, results, innerCallback) { const resultRequest = { url: (device.endpoint || group.endpoint) + constants.HTTP_CONFIGURATION_PATH, + method: 'POST', json: iotaUtils.createConfigurationNotification(results), headers: { diff --git a/lib/commonBindings.js b/lib/commonBindings.js index 9f07b63d..e489f49b 100644 --- a/lib/commonBindings.js +++ b/lib/commonBindings.js @@ -172,8 +172,8 @@ function extractAttributes(device, current, payloadType) { return values; } -function sendConfigurationToDevice(device, apiKey, deviceId, results, callback) { - let group; +function sendConfigurationToDevice(device, apiKey, group, deviceId, results, callback) { + //let group; iotAgentLib.getConfigurationSilently(config.getConfig().iota.defaultResource || '', apiKey, function ( error, foundGroup diff --git a/lib/iotaUtils.js b/lib/iotaUtils.js index a2ef810d..60a0c965 100644 --- a/lib/iotaUtils.js +++ b/lib/iotaUtils.js @@ -86,7 +86,7 @@ function manageConfiguration(apiKey, deviceId, device, objMessage, sendFunction, async.waterfall( [ apply(iotAgentLib.query, device.name, device.type, '', objMessage.fields, device), - apply(sendFunction, apiKey, deviceId) + apply(sendFunction, apiKey, {}, deviceId) ], handleSendConfigurationError ); From 8edac44f55ee8be1939add34acb2a2a1f2d5b585 Mon Sep 17 00:00:00 2001 From: Alvaro Vega Date: Wed, 13 Dec 2023 17:02:11 +0100 Subject: [PATCH 04/18] disable notify tests --- .../ngsiv2/HTTP_get-configuration_test.js | 44 +++++++-------- .../ngsiv2/MQTT_get-configuration_test.js | 54 +++++++++---------- 2 files changed, 49 insertions(+), 49 deletions(-) diff --git a/test/unit/ngsiv2/HTTP_get-configuration_test.js b/test/unit/ngsiv2/HTTP_get-configuration_test.js index 12128bb5..c9df0b4b 100644 --- a/test/unit/ngsiv2/HTTP_get-configuration_test.js +++ b/test/unit/ngsiv2/HTTP_get-configuration_test.js @@ -186,27 +186,27 @@ describe('HTTP: Get configuration from the devices', function () { done(); }); }); - it('should update the values in the MQTT topic when a notification is received', function (done) { - const optionsNotify = { - url: 'http://localhost:' + config.iota.server.port + '/notify', - method: 'POST', - json: utils.readExampleFile('./test/subscriptions/notification.json'), - headers: { - 'fiware-service': 'smartgondor', - 'fiware-servicepath': '/gardens' - } - }; - - request(configurationRequest, function (error, response, body) { - setTimeout(function () { - request(optionsNotify, function () { - setTimeout(function () { - mockedClientServer.done(); - done(); - }, 100); - }); - }, 100); - }); - }); + // it('should update the values in the MQTT topic when a notification is received', function (done) { + // const optionsNotify = { + // url: 'http://localhost:' + config.iota.server.port + '/notify', + // method: 'POST', + // json: utils.readExampleFile('./test/subscriptions/notification.json'), + // headers: { + // 'fiware-service': 'smartgondor', + // 'fiware-servicepath': '/gardens' + // } + // }; + + // request(configurationRequest, function (error, response, body) { + // setTimeout(function () { + // request(optionsNotify, function () { + // setTimeout(function () { + // mockedClientServer.done(); + // done(); + // }, 100); + // }); + // }, 100); + // }); + // }); }); }); diff --git a/test/unit/ngsiv2/MQTT_get-configuration_test.js b/test/unit/ngsiv2/MQTT_get-configuration_test.js index 3203aa35..111821f7 100644 --- a/test/unit/ngsiv2/MQTT_get-configuration_test.js +++ b/test/unit/ngsiv2/MQTT_get-configuration_test.js @@ -198,32 +198,32 @@ describe('MQTT: Get configuration from the devices', function () { }, 100); }); }); - it('should update the values in the MQTT topic when a notification is received', function (done) { - const optionsNotify = { - url: 'http://localhost:' + config.iota.server.port + '/notify', - method: 'POST', - json: utils.readExampleFile('./test/subscriptions/notification.json'), - headers: { - 'fiware-service': 'smartgondor', - 'fiware-servicepath': '/gardens' - } - }; - - mqttClient.on('message', function (topic, data) { - const result = JSON.parse(data); - configurationReceived = result.sleepTime === '200' && result.warningLevel === 'ERROR'; - }); - - mqttClient.publish('/1234/MQTT_2/configuration/commands', JSON.stringify(values), null, function (error) { - setTimeout(function () { - request(optionsNotify, function (error, response, body) { - setTimeout(function () { - configurationReceived.should.equal(true); - done(); - }, 100); - }); - }, 100); - }); - }); + // it('should update the values in the MQTT topic when a notification is received', function (done) { + // const optionsNotify = { + // url: 'http://localhost:' + config.iota.server.port + '/notify', + // method: 'POST', + // json: utils.readExampleFile('./test/subscriptions/notification.json'), + // headers: { + // 'fiware-service': 'smartgondor', + // 'fiware-servicepath': '/gardens' + // } + // }; + + // mqttClient.on('message', function (topic, data) { + // const result = JSON.parse(data); + // configurationReceived = result.sleepTime === '200' && result.warningLevel === 'ERROR'; + // }); + + // mqttClient.publish('/1234/MQTT_2/configuration/commands', JSON.stringify(values), null, function (error) { + // setTimeout(function () { + // request(optionsNotify, function (error, response, body) { + // setTimeout(function () { + // configurationReceived.should.equal(true); + // done(); + // }, 100); + // }); + // }, 100); + // }); + // }); }); }); From 679545ad4e408e8c345f437b950a8eb722f6a076 Mon Sep 17 00:00:00 2001 From: Alvaro Vega Date: Wed, 13 Dec 2023 17:23:15 +0100 Subject: [PATCH 05/18] fix subscriptions tests --- lib/bindings/HTTPBinding.js | 5 +- lib/iotagent-json.js | 13 ++++- .../ngsiv2/HTTP_get-configuration_test.js | 44 +++++++-------- .../ngsiv2/MQTT_get-configuration_test.js | 54 +++++++++---------- 4 files changed, 64 insertions(+), 52 deletions(-) diff --git a/lib/bindings/HTTPBinding.js b/lib/bindings/HTTPBinding.js index 4acd9bdd..0109a48d 100644 --- a/lib/bindings/HTTPBinding.js +++ b/lib/bindings/HTTPBinding.js @@ -446,7 +446,10 @@ function sendConfigurationToDevice(apiKey, group, deviceId, results, callback) { request(resultRequest, handleDeviceResponse(innerCallback)); } - iotaUtils.retrieveDevice(deviceId, apiKey, group.transport || transport, function (error, device) { + iotaUtils.retrieveDevice(deviceId, apiKey, group ? group.transport : undefined || transport, function ( + error, + device + ) { if (error) { callback(error); } else if (!device.endpoint) { diff --git a/lib/iotagent-json.js b/lib/iotagent-json.js index 430ef575..9aa771de 100644 --- a/lib/iotagent-json.js +++ b/lib/iotagent-json.js @@ -42,10 +42,19 @@ const config = require('./configService'); */ function configurationNotificationHandler(device, updates, callback) { function invokeConfiguration(apiKey, callback) { + let group; + iotAgentLib.getConfigurationSilently(config.getConfig().iota.defaultResource || '', apiKey, function ( + error, + foundGroup + ) { + if (!error) { + group = foundGroup; + } + }); transportSelector.applyFunctionFromBinding( - [apiKey, device.id, updates], + [apiKey, group, device.id, updates], 'sendConfigurationToDevice', - device.transport || config.getConfig().defaultTransport, + device.transport || group.transport || config.getConfig().defaultTransport, callback ); } diff --git a/test/unit/ngsiv2/HTTP_get-configuration_test.js b/test/unit/ngsiv2/HTTP_get-configuration_test.js index c9df0b4b..12128bb5 100644 --- a/test/unit/ngsiv2/HTTP_get-configuration_test.js +++ b/test/unit/ngsiv2/HTTP_get-configuration_test.js @@ -186,27 +186,27 @@ describe('HTTP: Get configuration from the devices', function () { done(); }); }); - // it('should update the values in the MQTT topic when a notification is received', function (done) { - // const optionsNotify = { - // url: 'http://localhost:' + config.iota.server.port + '/notify', - // method: 'POST', - // json: utils.readExampleFile('./test/subscriptions/notification.json'), - // headers: { - // 'fiware-service': 'smartgondor', - // 'fiware-servicepath': '/gardens' - // } - // }; - - // request(configurationRequest, function (error, response, body) { - // setTimeout(function () { - // request(optionsNotify, function () { - // setTimeout(function () { - // mockedClientServer.done(); - // done(); - // }, 100); - // }); - // }, 100); - // }); - // }); + it('should update the values in the MQTT topic when a notification is received', function (done) { + const optionsNotify = { + url: 'http://localhost:' + config.iota.server.port + '/notify', + method: 'POST', + json: utils.readExampleFile('./test/subscriptions/notification.json'), + headers: { + 'fiware-service': 'smartgondor', + 'fiware-servicepath': '/gardens' + } + }; + + request(configurationRequest, function (error, response, body) { + setTimeout(function () { + request(optionsNotify, function () { + setTimeout(function () { + mockedClientServer.done(); + done(); + }, 100); + }); + }, 100); + }); + }); }); }); diff --git a/test/unit/ngsiv2/MQTT_get-configuration_test.js b/test/unit/ngsiv2/MQTT_get-configuration_test.js index 111821f7..3203aa35 100644 --- a/test/unit/ngsiv2/MQTT_get-configuration_test.js +++ b/test/unit/ngsiv2/MQTT_get-configuration_test.js @@ -198,32 +198,32 @@ describe('MQTT: Get configuration from the devices', function () { }, 100); }); }); - // it('should update the values in the MQTT topic when a notification is received', function (done) { - // const optionsNotify = { - // url: 'http://localhost:' + config.iota.server.port + '/notify', - // method: 'POST', - // json: utils.readExampleFile('./test/subscriptions/notification.json'), - // headers: { - // 'fiware-service': 'smartgondor', - // 'fiware-servicepath': '/gardens' - // } - // }; - - // mqttClient.on('message', function (topic, data) { - // const result = JSON.parse(data); - // configurationReceived = result.sleepTime === '200' && result.warningLevel === 'ERROR'; - // }); - - // mqttClient.publish('/1234/MQTT_2/configuration/commands', JSON.stringify(values), null, function (error) { - // setTimeout(function () { - // request(optionsNotify, function (error, response, body) { - // setTimeout(function () { - // configurationReceived.should.equal(true); - // done(); - // }, 100); - // }); - // }, 100); - // }); - // }); + it('should update the values in the MQTT topic when a notification is received', function (done) { + const optionsNotify = { + url: 'http://localhost:' + config.iota.server.port + '/notify', + method: 'POST', + json: utils.readExampleFile('./test/subscriptions/notification.json'), + headers: { + 'fiware-service': 'smartgondor', + 'fiware-servicepath': '/gardens' + } + }; + + mqttClient.on('message', function (topic, data) { + const result = JSON.parse(data); + configurationReceived = result.sleepTime === '200' && result.warningLevel === 'ERROR'; + }); + + mqttClient.publish('/1234/MQTT_2/configuration/commands', JSON.stringify(values), null, function (error) { + setTimeout(function () { + request(optionsNotify, function (error, response, body) { + setTimeout(function () { + configurationReceived.should.equal(true); + done(); + }, 100); + }); + }, 100); + }); + }); }); }); From 0f52f1efb1a307f93a99c5922de041ed789675ca Mon Sep 17 00:00:00 2001 From: Alvaro Vega Date: Thu, 14 Dec 2023 12:20:21 +0100 Subject: [PATCH 06/18] fix how get group --- lib/bindings/HTTPBinding.js | 36 ++++++++++++++++++++++++++------- lib/commandHandler.js | 40 +++++++++++++++++++------------------ lib/commonBindings.js | 16 +++++++-------- 3 files changed, 57 insertions(+), 35 deletions(-) diff --git a/lib/bindings/HTTPBinding.js b/lib/bindings/HTTPBinding.js index 0109a48d..4a4f76ac 100644 --- a/lib/bindings/HTTPBinding.js +++ b/lib/bindings/HTTPBinding.js @@ -503,17 +503,19 @@ function handleError(error, req, res, next) { * Just fills in the transport protocol in case there is none and polling if endpoint. * * @param {Object} device Device object containing all the information about the device. + * @param {Object} group Group object containing all the information about the device. */ -function setPollingAndDefaultTransport(device, callback) { +function setPollingAndDefaultTransport(device, group, callback) { + config.getLogger().debug(context, 'httpbinding.setPollingAndDefaultTransport device %j group %j', device, group); if (!device.transport) { - device.transport = 'HTTP'; + device.transport = group && group.transport ? group.transport : 'HTTP'; } if (device.transport === 'HTTP') { if (device.endpoint) { device.polling = false; } else { - device.polling = true; + device.polling = !(group && group.endpoint); } } @@ -526,8 +528,18 @@ function setPollingAndDefaultTransport(device, callback) { * @param {Object} device Device object containing all the information about the provisioned device. */ function deviceProvisioningHandler(device, callback) { - config.getLogger().debug(context, 'httpbinding.deviceProvisioningHandler %j', device); - setPollingAndDefaultTransport(device, callback); + config.getLogger().debug(context, 'httpbinding.deviceProvisioningHandler device %j', device); + let group; + iotAgentLib.getConfigurationSilently(config.getConfig().iota.defaultResource || '', device.apikey, function ( + error, + foundGroup + ) { + if (!error) { + group = foundGroup; + } + config.getLogger().debug(context, 'httpbinding.deviceProvisioningHandler group %j', group); + setPollingAndDefaultTransport(device, group, callback); + }); } /** @@ -536,8 +548,18 @@ function deviceProvisioningHandler(device, callback) { * @param {Object} device Device object containing all the information about the updated device. */ function deviceUpdatingHandler(device, callback) { - config.getLogger().debug(context, 'httpbinding.deviceUpdatingHandler %j', device); - setPollingAndDefaultTransport(device, callback); + config.getLogger().debug(context, 'httpbinding.deviceUpdatingHandler device %j', device); + let group; + iotAgentLib.getConfigurationSilently(config.getConfig().iota.defaultResource || '', device.apikey, function ( + error, + foundGroup + ) { + if (!error) { + group = foundGroup; + } + config.getLogger().debug(context, 'httpbinding.deviceUpdatingHandler group %j', group); + setPollingAndDefaultTransport(device, group, callback); + }); } /** diff --git a/lib/commandHandler.js b/lib/commandHandler.js index 91310e73..932e3139 100644 --- a/lib/commandHandler.js +++ b/lib/commandHandler.js @@ -74,24 +74,16 @@ function serializedPayloadCommand(payload, command) { */ function generateCommandExecution(apiKey, device, attribute) { let payload = {}; - let command = device && device.commands.find((att) => att.name === attribute.name); - let group; - iotAgentLib.getConfigurationSilently(config.getConfig().iota.defaultResource || '', apiKey, function ( - error, - foundGroup - ) { - if (!error) { - group = foundGroup; - } - }); + const command = device && device.commands.find((att) => att.name === attribute.name); + if (command && command.expression) { - let parser = iotAgentLib.dataPlugins.expressionTransformation; + const parser = iotAgentLib.dataPlugins.expressionTransformation; // The context for the JEXL expression should be the ID, TYPE, S, SS let attrList = iotAgentLib.dataPlugins.utils.getIdTypeServSubServiceFromDevice(device); attrList = device.staticAttributes ? attrList.concat(device.staticAttributes).concat(attribute) : attrList.concat(attribute); - let ctxt = parser.extractContext(attrList, device); + const ctxt = parser.extractContext(attrList, device); // expression result will be the full command payload let payloadRes = null; try { @@ -118,13 +110,23 @@ function generateCommandExecution(apiKey, device, attribute) { apiKey, payload ); - - const executions = transportSelector.createExecutionsForBinding( - [apiKey, group, device, attribute.name, serialized, contentType], - 'executeCommand', - device.transport || group.transport || config.getConfig().defaultTransport - ); - + let group; + let executions; + iotAgentLib.getConfigurationSilently(config.getConfig().iota.defaultResource || '', apiKey, function ( + error, + foundGroup + ) { + if (!error) { + group = foundGroup; + } + executions = transportSelector.createExecutionsForBinding( + [apiKey, group, device, attribute.name, serialized, contentType], + 'executeCommand', + device.transport || + (group && group.transport ? group.transport : undefined) || + config.getConfig().defaultTransport + ); + }); return executions; } diff --git a/lib/commonBindings.js b/lib/commonBindings.js index e489f49b..8ae8ac18 100644 --- a/lib/commonBindings.js +++ b/lib/commonBindings.js @@ -139,7 +139,7 @@ function extractAttributes(device, current, payloadType) { } } // Add other stuff as metadata - for (let key in entity[k]) { + for (const key in entity[k]) { if (!['type', 'value', 'object'].includes(key.toLowerCase())) { if (!ent.metadata) { ent.metadata = {}; @@ -173,7 +173,6 @@ function extractAttributes(device, current, payloadType) { } function sendConfigurationToDevice(device, apiKey, group, deviceId, results, callback) { - //let group; iotAgentLib.getConfigurationSilently(config.getConfig().iota.defaultResource || '', apiKey, function ( error, foundGroup @@ -181,14 +180,13 @@ function sendConfigurationToDevice(device, apiKey, group, deviceId, results, cal if (!error) { group = foundGroup; } + transportSelector.applyFunctionFromBinding( + [apiKey, group, deviceId, results], + 'sendConfigurationToDevice', + device.transport || group.transport || config.getConfig().defaultTransport, + callback + ); }); - - transportSelector.applyFunctionFromBinding( - [apiKey, group, deviceId, results], - 'sendConfigurationToDevice', - device.transport || group.transport || config.getConfig().defaultTransport, - callback - ); } /** From cd7a28c8e71ba91580024adfe48a736b86816528 Mon Sep 17 00:00:00 2001 From: Alvaro Vega Date: Thu, 14 Dec 2023 13:03:37 +0100 Subject: [PATCH 07/18] fix checks --- lib/bindings/HTTPBinding.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/bindings/HTTPBinding.js b/lib/bindings/HTTPBinding.js index 4a4f76ac..1807ab54 100644 --- a/lib/bindings/HTTPBinding.js +++ b/lib/bindings/HTTPBinding.js @@ -204,7 +204,7 @@ function parseDataMultipleMeasure(req, res, next) { function executeCommand(apiKey, group, device, cmdName, serializedPayload, contentType, callback) { const options = { - url: device.endpoint || group.endpoint, + url: device.endpoint || (group && group.endpoint ? group.endpoint : undefined), method: 'POST', body: serializedPayload, headers: { @@ -433,7 +433,9 @@ function sendConfigurationToDevice(apiKey, group, deviceId, results, callback) { function sendRequest(device, group, results, innerCallback) { const resultRequest = { - url: (device.endpoint || group.endpoint) + constants.HTTP_CONFIGURATION_PATH, + url: + (device.endpoint || (group && group.endpoint ? group.endpoint : undefined)) + + constants.HTTP_CONFIGURATION_PATH, method: 'POST', json: iotaUtils.createConfigurationNotification(results), From f2aeac8a9aedde351ed2a611564f875a160d384f Mon Sep 17 00:00:00 2001 From: Alvaro Vega Date: Thu, 14 Dec 2023 15:59:58 +0100 Subject: [PATCH 08/18] fix get group --- lib/commandHandler.js | 44 ++++++++++++++++++++++--------------------- lib/iotagent-json.js | 15 +++++++++------ 2 files changed, 32 insertions(+), 27 deletions(-) diff --git a/lib/commandHandler.js b/lib/commandHandler.js index 932e3139..1f46b9fc 100644 --- a/lib/commandHandler.js +++ b/lib/commandHandler.js @@ -72,7 +72,7 @@ function serializedPayloadCommand(payload, command) { * @param {Object} attribute Attribute in NGSI format. * @return {Function} Command execution function ready to be called with async.series. */ -function generateCommandExecution(apiKey, device, attribute) { +function generateCommandExecution(apiKey, device, group, attribute) { let payload = {}; const command = device && device.commands.find((att) => att.name === attribute.name); @@ -110,23 +110,13 @@ function generateCommandExecution(apiKey, device, attribute) { apiKey, payload ); - let group; - let executions; - iotAgentLib.getConfigurationSilently(config.getConfig().iota.defaultResource || '', apiKey, function ( - error, - foundGroup - ) { - if (!error) { - group = foundGroup; - } - executions = transportSelector.createExecutionsForBinding( - [apiKey, group, device, attribute.name, serialized, contentType], - 'executeCommand', - device.transport || - (group && group.transport ? group.transport : undefined) || - config.getConfig().defaultTransport - ); - }); + const executions = transportSelector.createExecutionsForBinding( + [apiKey, group, device, attribute.name, serialized, contentType], + 'executeCommand', + device.transport || + (group && group.transport ? group.transport : undefined) || + config.getConfig().defaultTransport + ); return executions; } @@ -168,9 +158,21 @@ function commandHandler(id, type, service, subservice, attributes, callback) { if (error) { callback(error); } else { - async.series( - attributes.map(generateCommandExecution.bind(null, apiKey, device)).reduce(concat, []), - callback + let group = {}; + iotAgentLib.getConfigurationSilently( + config.getConfig().iota.defaultResource || '', + apiKey, + function (error, foundGroup) { + if (!error) { + group = foundGroup; + } + async.series( + attributes + .map(generateCommandExecution.bind(null, apiKey, device, group)) + .reduce(concat, []), + callback + ); + } ); } }); diff --git a/lib/iotagent-json.js b/lib/iotagent-json.js index 9aa771de..228b4dc2 100644 --- a/lib/iotagent-json.js +++ b/lib/iotagent-json.js @@ -50,13 +50,16 @@ function configurationNotificationHandler(device, updates, callback) { if (!error) { group = foundGroup; } + + transportSelector.applyFunctionFromBinding( + [apiKey, group, device.id, updates], + 'sendConfigurationToDevice', + device.transport || + (group && group.transport ? group.transport : undefined) || + config.getConfig().defaultTransport, + callback + ); }); - transportSelector.applyFunctionFromBinding( - [apiKey, group, device.id, updates], - 'sendConfigurationToDevice', - device.transport || group.transport || config.getConfig().defaultTransport, - callback - ); } async.waterfall( From b1447a4b5cb7c958e3f95ebacfdceb88fd62d313 Mon Sep 17 00:00:00 2001 From: Alvaro Vega Date: Fri, 15 Dec 2023 10:09:04 +0100 Subject: [PATCH 09/18] Update CHANGES_NEXT_RELEASE --- CHANGES_NEXT_RELEASE | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES_NEXT_RELEASE b/CHANGES_NEXT_RELEASE index 7de4cbcb..2535c551 100644 --- a/CHANGES_NEXT_RELEASE +++ b/CHANGES_NEXT_RELEASE @@ -1 +1,2 @@ - Fix: protect access to multimeasure array +- ADD: check and usage endpoint and transport from Group level when commands From ace76530f955c900bcac0d147b6b2e4f382426b7 Mon Sep 17 00:00:00 2001 From: Alvaro Vega Date: Fri, 15 Dec 2023 11:42:47 +0100 Subject: [PATCH 10/18] remove transport --- lib/bindings/HTTPBinding.js | 13 +++++-------- lib/commonBindings.js | 2 +- lib/iotaUtils.js | 14 +++----------- lib/iotagent-json.js | 2 +- 4 files changed, 10 insertions(+), 21 deletions(-) diff --git a/lib/bindings/HTTPBinding.js b/lib/bindings/HTTPBinding.js index 1807ab54..2a4412bd 100644 --- a/lib/bindings/HTTPBinding.js +++ b/lib/bindings/HTTPBinding.js @@ -404,7 +404,7 @@ function handleIncomingMeasure(req, res, next) { } } - iotaUtils.retrieveDevice(req.deviceId, req.apiKey, transport, processDeviceMeasure); + iotaUtils.retrieveDevice(req.deviceId, req.apiKey, processDeviceMeasure); } function isCommand(req, res, next) { @@ -448,10 +448,7 @@ function sendConfigurationToDevice(apiKey, group, deviceId, results, callback) { request(resultRequest, handleDeviceResponse(innerCallback)); } - iotaUtils.retrieveDevice(deviceId, apiKey, group ? group.transport : undefined || transport, function ( - error, - device - ) { + iotaUtils.retrieveDevice(deviceId, apiKey, function (error, device) { if (error) { callback(error); } else if (!device.endpoint) { @@ -470,7 +467,7 @@ function handleConfigurationRequest(req, res, next) { res.status(200).json({}); } } - iotaUtils.retrieveDevice(req.deviceId, req.apiKey, transport, function (error, device) { + iotaUtils.retrieveDevice(req.deviceId, req.apiKey, function (error, device) { if (error) { next(error); } else { @@ -531,7 +528,7 @@ function setPollingAndDefaultTransport(device, group, callback) { */ function deviceProvisioningHandler(device, callback) { config.getLogger().debug(context, 'httpbinding.deviceProvisioningHandler device %j', device); - let group; + let group = {}; iotAgentLib.getConfigurationSilently(config.getConfig().iota.defaultResource || '', device.apikey, function ( error, foundGroup @@ -551,7 +548,7 @@ function deviceProvisioningHandler(device, callback) { */ function deviceUpdatingHandler(device, callback) { config.getLogger().debug(context, 'httpbinding.deviceUpdatingHandler device %j', device); - let group; + let group = {}; iotAgentLib.getConfigurationSilently(config.getConfig().iota.defaultResource || '', device.apikey, function ( error, foundGroup diff --git a/lib/commonBindings.js b/lib/commonBindings.js index 8ae8ac18..b741245d 100644 --- a/lib/commonBindings.js +++ b/lib/commonBindings.js @@ -381,7 +381,7 @@ function messageHandler(topic, message, protocol) { } iotAgentLib.alarms.release(constants.MQTTB_ALARM); - iotaUtils.retrieveDevice(deviceId, apiKey, protocol, processDeviceMeasure); + iotaUtils.retrieveDevice(deviceId, apiKey, processDeviceMeasure); } /** diff --git a/lib/iotaUtils.js b/lib/iotaUtils.js index 60a0c965..1010b9e6 100644 --- a/lib/iotaUtils.js +++ b/lib/iotaUtils.js @@ -139,7 +139,7 @@ function createConfigurationNotification(results) { return configurations; } -function findOrCreate(deviceId, transport, apikey, group, callback) { +function findOrCreate(deviceId, apikey, group, callback) { iotAgentLib.getDeviceSilently(deviceId, apikey, group.service, group.subservice, function (error, device) { if (!error && device) { if ( @@ -171,14 +171,6 @@ function findOrCreate(deviceId, transport, apikey, group, callback) { ) { newDevice.protocol = config.getConfig().iota.iotManager.protocol; } - // // Fix transport depending on binding and group - // if (!newDevice.transport) { - // if ('transport' in group && group.transport !== undefined) { - // newDevice.transport = group.transport; - // } else { - // newDevice.transport = transport; - // } - // } if ('ngsiVersion' in group && group.ngsiVersion !== undefined) { newDevice.ngsiVersion = group.ngsiVersion; } @@ -221,7 +213,7 @@ function findOrCreate(deviceId, transport, apikey, group, callback) { * @param {String} deviceId Device ID of the device that wants to be retrieved or created. * @param {String} apiKey APIKey of the Device Group (or default APIKey). */ -function retrieveDevice(deviceId, apiKey, transport, callback) { +function retrieveDevice(deviceId, apiKey, callback) { if (apiKey === config.getConfig().defaultKey) { iotAgentLib.getDevicesByAttribute('id', deviceId, null, null, function (error, devices) { if (error) { @@ -245,7 +237,7 @@ function retrieveDevice(deviceId, apiKey, transport, callback) { async.waterfall( [ apply(iotAgentLib.getConfigurationSilently, config.getConfig().iota.defaultResource || '', apiKey), - apply(findOrCreate, deviceId, transport, apiKey), // group.apikey and apikey are the same + apply(findOrCreate, deviceId, apiKey), // group.apikey and apikey are the same apply( iotAgentLib.mergeDeviceWithConfiguration, ['lazy', 'active', 'staticAttributes', 'commands', 'subscriptions'], diff --git a/lib/iotagent-json.js b/lib/iotagent-json.js index 228b4dc2..3d66dcab 100644 --- a/lib/iotagent-json.js +++ b/lib/iotagent-json.js @@ -42,7 +42,7 @@ const config = require('./configService'); */ function configurationNotificationHandler(device, updates, callback) { function invokeConfiguration(apiKey, callback) { - let group; + let group = {}; iotAgentLib.getConfigurationSilently(config.getConfig().iota.defaultResource || '', apiKey, function ( error, foundGroup From f0917ab67fd6d2e718a96b544babbfa07a3a3fb6 Mon Sep 17 00:00:00 2001 From: Alvaro Vega Date: Fri, 15 Dec 2023 12:22:32 +0100 Subject: [PATCH 11/18] remove protocol from messageHandler --- lib/commonBindings.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/commonBindings.js b/lib/commonBindings.js index b741245d..685d2646 100644 --- a/lib/commonBindings.js +++ b/lib/commonBindings.js @@ -329,7 +329,7 @@ function multipleMeasures(apiKey, deviceId, device, messageObj) { * @param {String} topic Topic of the form: '//deviceId/attrs[/]'. * @param {Object} message message body (Object or Buffer, depending on the value). */ -function messageHandler(topic, message, protocol) { +function messageHandler(topic, message) { if (topic[0] !== '/') { topic = '/' + topic; } @@ -393,7 +393,7 @@ function messageHandler(topic, message, protocol) { */ function amqpMessageHandler(topic, message) { regenerateTransid(topic); - messageHandler(topic, message, 'AMQP'); + messageHandler(topic, message); } /** From 5ce02646f88c97e39afeb4f31d3e1ae4a74ac2e6 Mon Sep 17 00:00:00 2001 From: Alvaro Vega Date: Fri, 15 Dec 2023 12:41:28 +0100 Subject: [PATCH 12/18] fix message handler --- lib/commonBindings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/commonBindings.js b/lib/commonBindings.js index 685d2646..8de4334c 100644 --- a/lib/commonBindings.js +++ b/lib/commonBindings.js @@ -406,7 +406,7 @@ function amqpMessageHandler(topic, message) { function mqttMessageHandler(topic, message) { regenerateTransid(topic); config.getLogger().debug(context, 'message topic: %s', topic); - messageHandler(topic, message, 'MQTT'); + messageHandler(topic, message); } exports.amqpMessageHandler = amqpMessageHandler; From 495d41262d7b9870701ee9b730ab495e42c82bd3 Mon Sep 17 00:00:00 2001 From: Alvaro Vega Date: Fri, 15 Dec 2023 13:00:12 +0100 Subject: [PATCH 13/18] update doc --- docs/usermanual.md | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/docs/usermanual.md b/docs/usermanual.md index eab2777e..15920625 100644 --- a/docs/usermanual.md +++ b/docs/usermanual.md @@ -170,7 +170,8 @@ Examples of these `ngsiv2` payloads are the following ones: ... } ``` -``` + +```` Example of these `ngsild` payloads are the following ones: @@ -212,7 +213,7 @@ Example of these `ngsild` payloads are the following ones: }, ... ] -``` +```` (2) NGSI-LD single entity format: @@ -254,8 +255,16 @@ Example of these `ngsild` payloads are the following ones: Some additional considerations to take into account: - In the case of array of entities, they are handled as a multiple measure, i.e. each entity is a measure. -- The `type` of the attribute is the one used in the provision of the attribute, not the one in the measure. The exception is the autoprovisioned devices case, in which case the `type` of the attribute is taken from the measure (given the attribute lacks proviosioned type). In this latter case, if the attribute `type` is not included in the measure the [explicit type omission rules for Context Broker](https://github.com/telefonicaid/fiware-orion/blob/master/doc/manuals/orion-api.md#partial-representations) are also taken into account in this case. -- In the case of NGSI-LD, fields different from `type`, `value` or `object` (e.g. `observedAt` in the examples above) are include as NGSI-v2 metadata in the entity corresponding to the measure at Context Broker. Note IOTA doesn't provide the `type` for that metadata, so the Context Broker applies [a default type based in the metadata `value` JSON type](https://github.com/telefonicaid/fiware-orion/blob/master/doc/manuals/orion-api.md#partial-representations). +- The `type` of the attribute is the one used in the provision of the attribute, not the one in the measure. The + exception is the autoprovisioned devices case, in which case the `type` of the attribute is taken from the measure + (given the attribute lacks proviosioned type). In this latter case, if the attribute `type` is not included in the + measure the + [explicit type omission rules for Context Broker](https://github.com/telefonicaid/fiware-orion/blob/master/doc/manuals/orion-api.md#partial-representations) + are also taken into account in this case. +- In the case of NGSI-LD, fields different from `type`, `value` or `object` (e.g. `observedAt` in the examples above) + are include as NGSI-v2 metadata in the entity corresponding to the measure at Context Broker. Note IOTA doesn't + provide the `type` for that metadata, so the Context Broker applies + [a default type based in the metadata `value` JSON type](https://github.com/telefonicaid/fiware-orion/blob/master/doc/manuals/orion-api.md#partial-representations). ##### SOAP-XML Measure reporting @@ -985,9 +994,10 @@ simple restart should be enough). In order to distinguish which device uses which attribute, a new field, `transport`, will be added to the device provisioning. When a command or a notification arrives to the IoTAgent, this field is read to guess what plugin to -invoke in order to execute the requested task. If the field is not found, the value of the configuration parameter -`defaultTransport` will be used instead. In order to associate a module with a device, the value of the `transport` -attribute of the device provisioning must match the value of the `protocol` field of the binding. +invoke in order to execute the requested task. If the field is not found, the same field is search in configuration +group and then used, but if not the value of the configuration parameter `defaultTransport` will be used instead. In +order to associate a module with a device, the value of the `transport` attribute of the device provisioning must match +the value of the `protocol` field of the binding. ### API From f6407a6ad250545c32b50cdaeac0399c0cfedebb Mon Sep 17 00:00:00 2001 From: Alvaro Vega Date: Fri, 15 Dec 2023 13:02:56 +0100 Subject: [PATCH 14/18] update doc --- docs/usermanual.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/usermanual.md b/docs/usermanual.md index 15920625..082b0026 100644 --- a/docs/usermanual.md +++ b/docs/usermanual.md @@ -421,8 +421,8 @@ and [Practice: Scenario 3: commands - error](https://github.com/telefonicaid/iotagent-node-lib/blob/master/doc/northboundinteractions.md#scenario-3-commands-error). MQTT devices commands are always push. For HTTP Devices commands to be push they **must** be provisioned with the -`endpoint` attribute, that will contain the URL where the IoT Agent will send the received commands. Otherwise the -command will be poll. When using the HTTP transport, the command handling have two flavours: +`endpoint` attribute, from device or group device, that will contain the URL where the IoT Agent will send the received +commands. Otherwise the command will be poll. When using the HTTP transport, the command handling have two flavours: - **Push commands**: The request payload format will be a plain JSON, as described in the "Payload" section. The device will reply with a 200OK response containing the result of the command in the JSON result format. Example of @@ -596,7 +596,8 @@ commands and a topic to receive configuration information. This mechanism can be configuration flag, `configRetrieval`. In case of MQTT to retrieve configuration parameters from the Context Broker, it is required that the device should be -provisioned using "MQTT" as transport key. By default it will be considered "HTTP" as transport. +provisioned using "MQTT" as transport key, at device or group level. By default it will be considered "HTTP" as +transport if none transport is defined at device or group level. The parameter will be given as follows: From 9bce1c2034abf22bfec19733ade5be852d7f3475 Mon Sep 17 00:00:00 2001 From: Alvaro Vega Date: Tue, 19 Dec 2023 10:32:38 +0100 Subject: [PATCH 15/18] use findOrCreate from iota library --- lib/iotaUtils.js | 69 +----------------------------------------------- 1 file changed, 1 insertion(+), 68 deletions(-) diff --git a/lib/iotaUtils.js b/lib/iotaUtils.js index 1010b9e6..4b845736 100644 --- a/lib/iotaUtils.js +++ b/lib/iotaUtils.js @@ -139,73 +139,6 @@ function createConfigurationNotification(results) { return configurations; } -function findOrCreate(deviceId, apikey, group, callback) { - iotAgentLib.getDeviceSilently(deviceId, apikey, group.service, group.subservice, function (error, device) { - if (!error && device) { - if ( - (!('apikey' in device) || device.apikey === undefined) && - 'apikey' in group && - group.apikey !== undefined - ) { - config - .getLogger() - .info(context, 'Update provisioned device %j with measure/group apikey %j', device, group.apikey); - device.apikey = group.apikey; // group apikey is the same of current measure apikey - iotAgentLib.updateDevice(device, function (error) { - callback(error, device, group); - }); - } else { - callback(null, device, group); - } - } else if (error.name === 'DEVICE_NOT_FOUND') { - const newDevice = { - id: deviceId, - service: group.service, - subservice: group.subservice, - type: group.type - }; - if ( - config.getConfig().iota && - config.getConfig().iota.iotManager && - config.getConfig().iota.iotManager.protocol - ) { - newDevice.protocol = config.getConfig().iota.iotManager.protocol; - } - if ('ngsiVersion' in group && group.ngsiVersion !== undefined) { - newDevice.ngsiVersion = group.ngsiVersion; - } - if ( - (!('apikey' in newDevice) || newDevice.apikey === undefined) && - 'apikey' in group && - group.apikey !== undefined - ) { - newDevice.apikey = group.apikey; - } - // Check autoprovision flag in order to register or not device - if (group.autoprovision === undefined || group.autoprovision === true) { - config - .getLogger() - .debug(context, 'Registering autoprovision of Device %j for its conf %j', newDevice, group); - iotAgentLib.register(newDevice, function (error, device) { - callback(error, device, group); - }); - } else { - config - .getLogger() - .info( - context, - 'Device %j not provisioned due autoprovision is disabled by its conf %j', - newDevice, - group - ); - callback(new errors.DeviceNotFound(deviceId)); - } - } else { - callback(error); - } - }); -} - /** * Retrieve a device from the device repository based on the given APIKey and DeviceID, creating one if none is * found for the given data. @@ -237,7 +170,7 @@ function retrieveDevice(deviceId, apiKey, callback) { async.waterfall( [ apply(iotAgentLib.getConfigurationSilently, config.getConfig().iota.defaultResource || '', apiKey), - apply(findOrCreate, deviceId, apiKey), // group.apikey and apikey are the same + apply(iotAgentLib.findOrCreate, deviceId, apiKey), // group.apikey and apikey are the same apply( iotAgentLib.mergeDeviceWithConfiguration, ['lazy', 'active', 'staticAttributes', 'commands', 'subscriptions'], From f6a03bda0aa3dcee742d5779e0ce593cd3965508 Mon Sep 17 00:00:00 2001 From: Alvaro Vega Date: Tue, 19 Dec 2023 16:44:51 +0100 Subject: [PATCH 16/18] add test with group endpoint --- test/unit/ngsiv2/HTTP_commands_test.js | 163 ++++++++++++++++++ .../contextRequests/unprovisionedDevice3.json | 8 + .../contextRequests/updateCommand2.json | 15 ++ .../contextRequests/updateStatus10.json | 14 ++ .../ngsiv2/contextRequests/updateStatus9.json | 8 + 5 files changed, 208 insertions(+) create mode 100644 test/unit/ngsiv2/contextRequests/unprovisionedDevice3.json create mode 100644 test/unit/ngsiv2/contextRequests/updateCommand2.json create mode 100644 test/unit/ngsiv2/contextRequests/updateStatus10.json create mode 100644 test/unit/ngsiv2/contextRequests/updateStatus9.json diff --git a/test/unit/ngsiv2/HTTP_commands_test.js b/test/unit/ngsiv2/HTTP_commands_test.js index 6eca5f85..6df8b8b2 100644 --- a/test/unit/ngsiv2/HTTP_commands_test.js +++ b/test/unit/ngsiv2/HTTP_commands_test.js @@ -36,6 +36,35 @@ const request = utils.request; let mockedClientServer; let contextBrokerMock; +const groupCreation = { + url: 'http://localhost:' + config.iota.server.port + '/iot/services', + method: 'POST', + json: { + services: [ + { + resource: '/iot/json', + apikey: 'KL223HHV8732SFL1', + entity_type: 'TheLightType', + endpoint: 'http://localhost:9876/command', + transport: 'HTTP', + commands: [ + { + name: 'cmd1', + type: 'command' + } + ], + lazy: [], + attributes: [], + static_attributes: [] + } + ] + }, + headers: { + 'fiware-service': 'smartgondor', + 'fiware-servicepath': '/gardens' + } +}; + describe('HTTP: Commands', function () { beforeEach(function (done) { const provisionOptions = { @@ -225,3 +254,137 @@ describe('HTTP: Commands with expressions', function () { }); }); }); + +describe('HTTP: Commands from groups', function () { + beforeEach(function (done) { + config.logLevel = 'INFO'; + + nock.cleanAll(); + + contextBrokerMock = nock('http://192.168.1.1:1026') + .matchHeader('fiware-service', 'smartgondor') + .matchHeader('fiware-servicepath', '/gardens') + .post('/v2/registrations') + .reply(201, null, { Location: '/v2/registrations/6319a7f5254b05844116584d' }); + + iotagentMqtt.start(config, function () { + done(); + }); + }); + + afterEach(function (done) { + nock.cleanAll(); + async.series([iotAgentLib.clearAll, iotagentMqtt.stop], done); + }); + describe('When a POST measure arrives for an unprovisioned device in a command group', function () { + const optionsMeasure = { + url: 'http://localhost:' + config.http.port + '/iot/json', + method: 'POST', + json: { + h: '33' + }, + headers: { + 'fiware-service': 'smartgondor', + 'fiware-servicepath': '/gardens' + }, + qs: { + i: 'JSON_UNPROVISIONED', + k: 'KL223HHV8732SFL1' + } + }; + // This mock does not check the payload since the aim of the test is not to verify + // device provisioning functionality. Appropriate verification is done in tests under + // provisioning folder of iotagent-node-lib + beforeEach(function (done) { + //contextBrokerUnprovMock = nock('http://192.168.1.1:1026'); + + contextBrokerMock + .matchHeader('fiware-service', 'smartgondor') + .matchHeader('fiware-servicepath', '/gardens') + .post( + '/v2/entities?options=upsert', + utils.readExampleFile('./test/unit/ngsiv2/contextRequests/unprovisionedDevice3.json') + ) + .reply(204); + + request(groupCreation, function (error, response, body) { + done(); + }); + }); + + it('should send its value to the Context Broker', function (done) { + request(optionsMeasure, function (error, result, body) { + contextBrokerMock.done(); + done(); + }); + }); + + it('should not add a transport to the registered devices', function (done) { + const getDeviceOptions = { + url: 'http://localhost:' + config.iota.server.port + '/iot/devices', + method: 'GET', + headers: { + 'fiware-service': 'smartgondor', + 'fiware-servicepath': '/gardens' + }, + qs: { + i: 'JSON_UNPROVISIONED', + k: 'KL223HHV8732SFL1' + } + }; + request(optionsMeasure, function (error, result, body) { + request(getDeviceOptions, function (error, response, body) { + should.not.exist(error); + response.statusCode.should.equal(200); + should.not.exist(body.devices[0].transport); + done(); + }); + }); + }); + + describe('When a command arrive to the Agent for a device with the HTTP protocol', function () { + const commandOptions = { + url: 'http://localhost:' + config.iota.server.port + '/v2/op/update', + method: 'POST', + json: utils.readExampleFile('./test/unit/ngsiv2/contextRequests/updateCommand2.json'), + headers: { + 'fiware-service': 'smartgondor', + 'fiware-servicepath': '/gardens' + } + }; + beforeEach(function () { + contextBrokerMock + .matchHeader('fiware-service', 'smartgondor') + .matchHeader('fiware-servicepath', '/gardens') + .post( + '/v2/entities?options=upsert', + utils.readExampleFile('./test/unit/ngsiv2/contextRequests/updateStatus9.json') + ) + .reply(204); + contextBrokerMock + .matchHeader('fiware-service', 'smartgondor') + .matchHeader('fiware-servicepath', '/gardens') + .post( + '/v2/entities?options=upsert', + utils.readExampleFile('./test/unit/ngsiv2/contextRequests/updateStatus10.json') + ) + .reply(204); + mockedClientServer = nock('http://localhost:9876') + .post('/command', function (body) { + return body.cmd1 || body.cmd1.data || body.cmd1.data === 22; + }) + .reply(200, '{"cmd1":{"data":"22"}}'); + }); + it('should return a 204 OK without errors', function (done) { + request(optionsMeasure, function (error, result, body) { + request(commandOptions, function (error, response, body) { + should.not.exist(error); + response.statusCode.should.equal(204); + contextBrokerMock.done(); + done(); + }); + }); + }); + }); + }); +}); diff --git a/test/unit/ngsiv2/contextRequests/unprovisionedDevice3.json b/test/unit/ngsiv2/contextRequests/unprovisionedDevice3.json new file mode 100644 index 00000000..a5493ad1 --- /dev/null +++ b/test/unit/ngsiv2/contextRequests/unprovisionedDevice3.json @@ -0,0 +1,8 @@ +{ + "id": "TheLightType:JSON_UNPROVISIONED", + "type": "TheLightType", + "h":{ + "type": "string", + "value": "33" + } +} diff --git a/test/unit/ngsiv2/contextRequests/updateCommand2.json b/test/unit/ngsiv2/contextRequests/updateCommand2.json new file mode 100644 index 00000000..4e0192c4 --- /dev/null +++ b/test/unit/ngsiv2/contextRequests/updateCommand2.json @@ -0,0 +1,15 @@ +{ + "actionType": "update", + "entities": [ + { + "id": "TheLightType:JSON_UNPROVISIONED", + "type": "TheLightType", + "cmd1": { + "type": "command", + "value":{ + "data": "22" + } + } + } + ] +} diff --git a/test/unit/ngsiv2/contextRequests/updateStatus10.json b/test/unit/ngsiv2/contextRequests/updateStatus10.json new file mode 100644 index 00000000..55de81e1 --- /dev/null +++ b/test/unit/ngsiv2/contextRequests/updateStatus10.json @@ -0,0 +1,14 @@ +{ + "id":"TheLightType:JSON_UNPROVISIONED", + "type":"TheLightType", + "cmd1_status": { + "type": "commandStatus", + "value": "OK" + }, + "cmd1_info": { + "type": "commandResult", + "value": { + "data": "22" + } + } +} diff --git a/test/unit/ngsiv2/contextRequests/updateStatus9.json b/test/unit/ngsiv2/contextRequests/updateStatus9.json new file mode 100644 index 00000000..a146e003 --- /dev/null +++ b/test/unit/ngsiv2/contextRequests/updateStatus9.json @@ -0,0 +1,8 @@ +{ + "id":"TheLightType:JSON_UNPROVISIONED", + "type":"TheLightType", + "cmd1_status": { + "type": "commandStatus", + "value": "PENDING" + } +} From afacd9da36c721713ccfe90f852e5c73eae6ab57 Mon Sep 17 00:00:00 2001 From: Alvaro Vega Date: Wed, 20 Dec 2023 10:12:28 +0100 Subject: [PATCH 17/18] add test about MQTT and command groups --- test/unit/ngsiv2/HTTP_commands_test.js | 4 +- test/unit/ngsiv2/MQTT_commands_test.js | 193 +++++++++++++++++++++++++ 2 files changed, 195 insertions(+), 2 deletions(-) diff --git a/test/unit/ngsiv2/HTTP_commands_test.js b/test/unit/ngsiv2/HTTP_commands_test.js index 6df8b8b2..26041529 100644 --- a/test/unit/ngsiv2/HTTP_commands_test.js +++ b/test/unit/ngsiv2/HTTP_commands_test.js @@ -149,7 +149,7 @@ describe('HTTP: Commands', function () { done(); }); }); - it('should publish the command information in the MQTT topic', function (done) { + it('should publish the command information in the HTTP endpoint', function (done) { request(commandOptions, function (error, response, body) { setTimeout(function () { mockedClientServer.done(); @@ -244,7 +244,7 @@ describe('HTTP: Commands with expressions', function () { done(); }); }); - it('should publish the command information in the MQTT topic', function (done) { + it('should publish the command information in the HTTP endpoint', function (done) { request(commandOptions, function (error, response, body) { setTimeout(function () { mockedClientServer.done(); diff --git a/test/unit/ngsiv2/MQTT_commands_test.js b/test/unit/ngsiv2/MQTT_commands_test.js index f31f282a..fd9a3082 100644 --- a/test/unit/ngsiv2/MQTT_commands_test.js +++ b/test/unit/ngsiv2/MQTT_commands_test.js @@ -38,6 +38,34 @@ const request = utils.request; let contextBrokerMock; let mqttClient; +const groupCreation = { + url: 'http://localhost:' + config.iota.server.port + '/iot/services', + method: 'POST', + json: { + services: [ + { + resource: '/iot/json', + apikey: 'KL223HHV8732SFL1', + entity_type: 'TheLightType', + transport: 'MQTT', + commands: [ + { + name: 'cmd1', + type: 'command' + } + ], + lazy: [], + attributes: [], + static_attributes: [] + } + ] + }, + headers: { + 'fiware-service': 'smartgondor', + 'fiware-servicepath': '/gardens' + } +}; + describe('MQTT: Commands', function () { beforeEach(function (done) { const provisionOptions = { @@ -286,3 +314,168 @@ describe('MQTT: Commands', function () { }); }); }); + +describe('MQTT: Commands from groups', function () { + beforeEach(function (done) { + config.logLevel = 'INFO'; + + nock.cleanAll(); + + mqttClient = mqtt.connect('mqtt://' + config.mqtt.host, { + keepalive: 0, + connectTimeout: 60 * 60 * 1000 + }); + + mqttClient.subscribe('/KL223HHV8732SFL1/JSON_UNPROVISIONED/cmd', null); + + contextBrokerMock = nock('http://192.168.1.1:1026') + .matchHeader('fiware-service', 'smartgondor') + .matchHeader('fiware-servicepath', '/gardens') + .post('/v2/registrations') + .reply(201, null, { Location: '/v2/registrations/6319a7f5254b05844116584d' }); + + iotagentMqtt.start(config, function () { + done(); + }); + }); + + afterEach(function (done) { + nock.cleanAll(); + async.series([iotAgentLib.clearAll, iotagentMqtt.stop], done); + }); + describe('When a POST measure arrives for an unprovisioned device in a command group', function () { + const values = { + h: '33' + }; + + // provisioning folder of iotagent-node-lib + beforeEach(function (done) { + //contextBrokerUnprovMock = nock('http://192.168.1.1:1026'); + + contextBrokerMock + .matchHeader('fiware-service', 'smartgondor') + .matchHeader('fiware-servicepath', '/gardens') + .post( + '/v2/entities?options=upsert', + utils.readExampleFile('./test/unit/ngsiv2/contextRequests/unprovisionedDevice3.json') + ) + .reply(204); + + request(groupCreation, function (error, response, body) { + done(); + }); + }); + + it('should send its value to the Context Broker', function (done) { + mqttClient.publish('/KL223HHV8732SFL1/JSON_UNPROVISIONED/attrs', JSON.stringify(values), null, function ( + error + ) { + setTimeout(function () { + contextBrokerMock.done(); + done(); + }, 100); + }); + }); + + it('should not add a transport to the registered devices', function (done) { + const getDeviceOptions = { + url: 'http://localhost:' + config.iota.server.port + '/iot/devices', + method: 'GET', + headers: { + 'fiware-service': 'smartgondor', + 'fiware-servicepath': '/gardens' + }, + qs: { + i: 'JSON_UNPROVISIONED', + k: 'KL223HHV8732SFL1' + } + }; + mqttClient.publish('/KL223HHV8732SFL1/JSON_UNPROVISIONED/attrs', JSON.stringify(values), null, function ( + error + ) { + setTimeout(function () { + request(getDeviceOptions, function (error, response, body) { + should.not.exist(error); + response.statusCode.should.equal(200); + should.not.exist(body.devices[0].transport); + done(); + }); + }, 100); + }); + }); + + describe('When a command arrive to the Agent for a device with the MQTT protocol', function () { + const commandOptions = { + url: 'http://localhost:' + config.iota.server.port + '/v2/op/update', + method: 'POST', + json: utils.readExampleFile('./test/unit/ngsiv2/contextRequests/updateCommand2.json'), + headers: { + 'fiware-service': 'smartgondor', + 'fiware-servicepath': '/gardens' + } + }; + beforeEach(function () { + contextBrokerMock + .matchHeader('fiware-service', 'smartgondor') + .matchHeader('fiware-servicepath', '/gardens') + .post( + '/v2/entities?options=upsert', + utils.readExampleFile('./test/unit/ngsiv2/contextRequests/updateStatus9.json') + ) + .reply(204); + contextBrokerMock + .matchHeader('fiware-service', 'smartgondor') + .matchHeader('fiware-servicepath', '/gardens') + .post( + '/v2/entities?options=upsert', + utils.readExampleFile('./test/unit/ngsiv2/contextRequests/updateStatus10.json') + ) + .reply(204); + }); + it('should return a 204 OK without errors', function (done) { + mqttClient.publish( + '/KL223HHV8732SFL1/JSON_UNPROVISIONED/attrs', + JSON.stringify(values), + null, + function (error) { + setTimeout(function () { + request(commandOptions, function (error, response, body) { + should.not.exist(error); + response.statusCode.should.equal(204); + done(); + }); + }, 100); + } + ); + }); + it('should publish the command information in the MQTT topic', function (done) { + let payload; + mqttClient.on('message', function (topic, data) { + payload = data.toString(); + }); + mqttClient.publish( + '/KL223HHV8732SFL1/JSON_UNPROVISIONED/attrs', + JSON.stringify(values), + null, + function (error) { + setTimeout(function () { + request(commandOptions, function (error, response, body) { + mqttClient.publish( + '/KL223HHV8732SFL1/JSON_UNPROVISIONED/cmdexe', + '{ "cmd1": {"data":"22"}}', + null, + function (error) { + setTimeout(function () { + contextBrokerMock.done(); + done(); + }, 200); + } + ); + }); + }, 100); + } + ); + }); + }); + }); +}); From 108b0d518fcd5b48be785e185d09e2608060e229 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferm=C3=ADn=20Gal=C3=A1n=20M=C3=A1rquez?= Date: Wed, 20 Dec 2023 12:33:24 +0100 Subject: [PATCH 18/18] Update CHANGES_NEXT_RELEASE --- CHANGES_NEXT_RELEASE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES_NEXT_RELEASE b/CHANGES_NEXT_RELEASE index 2535c551..a6c778ef 100644 --- a/CHANGES_NEXT_RELEASE +++ b/CHANGES_NEXT_RELEASE @@ -1,2 +1,2 @@ - Fix: protect access to multimeasure array -- ADD: check and usage endpoint and transport from Group level when commands +- Add: check and usage endpoint and transport from Group level when commands