Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Task/usage transport endpoint from group #656

Merged
merged 5 commits into from
Dec 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions CHANGES_NEXT_RELEASE
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@


- Add: check and usage endpoint and transport from Group level when commands
112 changes: 54 additions & 58 deletions docs/usermanual.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,60 +149,58 @@ then the NGSI v2 update uses `10`(number), `true` (boolean) and `78.8` (number)
(string) and "78.8" (string).

This functionality relies on string measures casting feature implemented in the iotagent library. This functionality
uses native JavaScript [`JSON.parse()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse)
function to cast data coming from measures (as text) to JSON native types. This functionality does not change the attribute type,
using the type specified in the config group or device provision, even if it is not consistent with the measures that are coming.
As an example, for a given measure:
uses native JavaScript
[`JSON.parse()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse) function
to cast data coming from measures (as text) to JSON native types. This functionality does not change the attribute type,
using the type specified in the config group or device provision, even if it is not consistent with the measures that
are coming. As an example, for a given measure:

```
a|1|b|1.01|c|true|d|null|e|[1,2,3]|f|['a','b','c']|g|{a:1,b:2,c:3}|h|I'm a string
a|1|b|1.01|c|true|d|null|e|[1,2,3]|f|['a','b','c']|g|{a:1,b:2,c:3}|h|I'm a string
```

The resulting entity would be something like:

```json
{
"id": "entityid:001",
"type": "entitytype",
"a": {
"type": "provisionedType",
"value": 1
},
"b": {
"type": "provisionedType",
"value": 1.01
},
"c": {
"type": "provisionedType",
"value": true
},
"d": {
"type": "provisionedType",
"value": null
},
"e": {
"type": "provisionedType",
"value": [1,2,3]
},
"f": {
"type": "provisionedType",
"value": ["a","b","c"]
},
"g": {
"type": "provisionedType",
"value": {"a":1,"b":2,"c":3}
},
"h": {
"type": "provisionedType",
"value": "I'm a string"
}
"id": "entityid:001",
"type": "entitytype",
"a": {
"type": "provisionedType",
"value": 1
},
"b": {
"type": "provisionedType",
"value": 1.01
},
"c": {
"type": "provisionedType",
"value": true
},
"d": {
"type": "provisionedType",
"value": null
},
"e": {
"type": "provisionedType",
"value": [1, 2, 3]
},
"f": {
"type": "provisionedType",
"value": ["a", "b", "c"]
},
"g": {
"type": "provisionedType",
"value": { "a": 1, "b": 2, "c": 3 }
},
"h": {
"type": "provisionedType",
"value": "I'm a string"
}
}
```

Note that `provisionedType` is the type included in the device provision or config group, and it is not changed.


Note that `provisionedType` is the type included in the device provision or config group, and it is not changed.

### Transport Protocol

Expand Down Expand Up @@ -247,8 +245,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 the one described in the UL Protocol description. The device
will reply with a 200OK response containing the result of the command in the UL2.0 result format. Example of the
Expand Down Expand Up @@ -315,19 +313,16 @@ by the protocol, in this case '/ul', just include apikey and deviceid (e.g: `/FF

> **Note** Measures and commands are sent over different MQTT topics:
>
> * _Measures_ are sent on the `/<protocol>/<api-key>/<device-id>/attrs` topic,
> * _Commands_ are sent on the `/<api-key>/<device-id>/cmd` topic,
> - _Measures_ are sent on the `/<protocol>/<api-key>/<device-id>/attrs` topic,
> - _Commands_ are sent on the `/<api-key>/<device-id>/cmd` topic,
>
> The reasoning behind this is that when sending measures northbound from device to IoT Agent,
> it is necessary to explicitly identify which IoT Agent is needed to parse the data. This
> is done by prefixing the relevant MQTT topic with a protocol, otherwise there is no way to
> define which agent is processing the measure. This mechanism allows smart systems to connect
> different devices to different IoT Agents according to need.
> The reasoning behind this is that when sending measures northbound from device to IoT Agent, it is necessary to
> explicitly identify which IoT Agent is needed to parse the data. This is done by prefixing the relevant MQTT topic
> with a protocol, otherwise there is no way to define which agent is processing the measure. This mechanism allows
> smart systems to connect different devices to different IoT Agents according to need.
>
> For southbound commands, this distinction is unnecessary since the correct IoT Agent has already
> registered itself for the command during the device provisioning step and the device will always
> receive commands in an appropriate format.

> For southbound commands, this distinction is unnecessary since the correct IoT Agent has already registered itself for
> the command during the device provisioning step and the device will always receive commands in an appropriate format.

This transport protocol binding is still under development.

Expand Down Expand Up @@ -375,7 +370,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.

The parameter will be given as follows:

Expand Down
41 changes: 33 additions & 8 deletions lib/bindings/HTTPBindings.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ const config = require('../configService');
let context = {
op: 'IOTAUL.HTTP.Binding'
};
const transport = 'HTTP';

/* eslint-disable-next-line no-unused-vars */
function handleError(error, req, res, next) {
Expand All @@ -67,7 +66,7 @@ function parseData(req, res, next) {
let data;
let error;
let payload;

context = fillService(context, { service: 'n/a', subservice: 'n/a' });
if (req.body) {
payload = req.body;
} else {
Expand Down Expand Up @@ -264,7 +263,7 @@ function handleIncomingMeasure(req, res, next) {
}
}

utils.retrieveDevice(req.deviceId, req.apiKey, transport, processDeviceMeasure);
utils.retrieveDevice(req.deviceId, req.apiKey, processDeviceMeasure);
}

/**
Expand All @@ -278,6 +277,7 @@ function handleIncomingMeasure(req, res, next) {
function generateCommandExecution(apiKey, device, attribute) {
const cmdName = attribute.name;
const cmdAttributes = attribute.value;
context = fillService(context, device);
const options = {
url: device.endpoint,
method: 'POST',
Expand Down Expand Up @@ -360,6 +360,7 @@ function generateCommandExecution(apiKey, device, attribute) {
* @param {String} attributes Command attributes (in NGSIv1 format).
*/
function commandHandler(device, attributes, callback) {
context = fillService(context, device);
utils.getEffectiveApiKey(device.service, device.subservice, device, function (error, apiKey) {
async.series(attributes.map(generateCommandExecution.bind(null, apiKey, device)), function (error) {
if (error) {
Expand Down Expand Up @@ -403,16 +404,18 @@ function addDefaultHeader(req, res, next) {
*
* @param {Object} device Device object containing all the information about the provisioned device.
*/
function setPollingAndDefaultTransport(device, callback) {
function setPollingAndDefaultTransport(device, group, callback) {
context = fillService(context, device);
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);
}
}

Expand All @@ -425,8 +428,19 @@ function setPollingAndDefaultTransport(device, callback) {
* @param {Object} device Device object containing all the information about the provisioned device.
*/
function deviceProvisioningHandler(device, callback) {
context = fillService(context, device);
config.getLogger().debug(context, 'httpbinding.deviceProvisioningHandler %j', device);
setPollingAndDefaultTransport(device, callback);
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);
});
}

/**
Expand All @@ -435,8 +449,19 @@ function deviceProvisioningHandler(device, callback) {
* @param {Object} device Device object containing all the information about the updated device.
*/
function deviceUpdatingHandler(device, callback) {
context = fillService(context, device);
config.getLogger().debug(context, 'httpbinding.deviceUpdatingHandler %j', device);
setPollingAndDefaultTransport(device, callback);
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);
});
}

function start(callback) {
Expand Down
2 changes: 1 addition & 1 deletion lib/bindings/MQTTBinding.js
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ function commandHandler(device, attributes, 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 = utils.createConfigurationNotification(results);
const options = {};
context = fillService(context, { service: 'n/a', subservice: 'n/a' });
Expand Down
30 changes: 19 additions & 11 deletions lib/commonBindings.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,21 @@ function guessType(attribute, device) {
return constants.DEFAULT_ATTRIBUTE_TYPE;
}

function sendConfigurationToDevice(device, apiKey, deviceId, results, callback) {
transportSelector.applyFunctionFromBinding(
[apiKey, deviceId, results],
'sendConfigurationToDevice',
device.transport || config.getConfig().defaultTransport,
callback
);
function sendConfigurationToDevice(device, apiKey, group, deviceId, results, callback) {
iotAgentLib.getConfigurationSilently(config.getConfig().iota.defaultResource || '', apiKey, function (
error,
foundGroup
) {
if (!error) {
group = foundGroup;
}
transportSelector.applyFunctionFromBinding(
[apiKey, group, deviceId, results],
'sendConfigurationToDevice',
device.transport || group.transport || config.getConfig().defaultTransport,
callback
);
});
}

/**
Expand Down Expand Up @@ -254,7 +262,7 @@ function singleMeasure(apiKey, attribute, device, messageStr, message) {
* @param {String} topic Topic of the form: '/<APIKey>/deviceId/attributes[/<attributeName>]'.
* @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;
}
Expand Down Expand Up @@ -321,7 +329,7 @@ function messageHandler(topic, message, protocol) {
}
}

utils.retrieveDevice(deviceId, apiKey, protocol, processDeviceMeasure);
utils.retrieveDevice(deviceId, apiKey, processDeviceMeasure);
}

/**
Expand All @@ -333,7 +341,7 @@ function messageHandler(topic, message, protocol) {
*/
function amqpMessageHandler(topic, message) {
regenerateTransid(topic);
messageHandler(topic, message, 'AMQP');
messageHandler(topic, message);
}

/**
Expand All @@ -346,7 +354,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;
Expand Down
Loading
Loading