iotagent-node-lib 2.23.0 → 2.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +6 -7
- package/.nyc_output/33364de2-1199-4ec2-b33c-cae063ef8cc4.json +1 -0
- package/.nyc_output/processinfo/33364de2-1199-4ec2-b33c-cae063ef8cc4.json +1 -0
- package/.nyc_output/processinfo/index.json +1 -1
- package/CHANGES_NEXT_RELEASE +1 -2
- package/doc/advanced-topics.md +78 -3
- package/doc/apiary/iotagent.apib +5 -5
- package/doc/architecture.md +52 -5
- package/doc/deprecated.md +10 -7
- package/doc/expressionLanguage.md +46 -35
- package/doc/getting-started.md +1 -1
- package/doc/usermanual.md +18 -16
- package/examples/TTOpen-service.json +1 -1
- package/lib/errors.js +9 -1
- package/lib/fiware-iotagent-lib.js +2 -1
- package/lib/jexlTranformsMap.js +12 -1
- package/lib/plugins/bidirectionalData.js +104 -6
- package/lib/plugins/expressionPlugin.js +10 -0
- package/lib/plugins/jexlParser.js +2 -2
- package/lib/plugins/pluginUtils.js +2 -1
- package/lib/request-shim.js +2 -1
- package/lib/services/devices/deviceService.js +53 -21
- package/lib/services/devices/devices-NGSI-v2.js +3 -1
- package/lib/services/ngsi/entities-NGSI-v2.js +14 -4
- package/lib/services/northBound/contextServer-NGSI-LD.js +96 -20
- package/lib/services/northBound/contextServer-NGSI-v2.js +1 -0
- package/lib/services/northBound/restUtils.js +3 -5
- package/lib/templates/deviceGroup.json +1 -1
- package/package.json +2 -2
- package/test/unit/examples/deviceProvisioningRequests/provisionNewDeviceEmpty.json +43 -0
- package/test/unit/examples/mongoCollections/configurations.json +3 -3
- package/test/unit/expressions/jexlExpression-test.js +29 -8
- package/test/unit/general/deviceService-test.js +31 -29
- package/test/unit/memoryRegistry/deviceRegistryMemory_test.js +62 -10
- package/test/unit/mongodb/mongodb-group-registry-test.js +24 -0
- package/test/unit/mongodb/mongodb-registry-test.js +68 -10
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin30.json +11 -0
- package/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotificationWithDatasetId.json +21 -0
- package/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotificationWithMetadata.json +17 -0
- package/test/unit/ngsi-ld/expressions/jexlBasedTransformations-test.js +18 -4
- package/test/unit/ngsi-ld/general/deviceService-test.js +31 -29
- package/test/unit/ngsi-ld/lazyAndCommands/command-test.js +656 -2
- package/test/unit/ngsi-ld/ngsiService/unsupported-endpoints-test.js +111 -0
- package/test/unit/ngsi-ld/plugins/bidirectional-plugin_test.js +221 -0
- package/test/unit/ngsi-mixed/provisioning/ngsi-versioning-test.js +0 -3
- package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityJexlExpressionPlugin1.json +22 -0
- package/test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalNotificationWithMetadata.json +19 -0
- package/test/unit/ngsiv2/expressions/jexlBasedTransformations-test.js +21 -6
- package/test/unit/ngsiv2/general/deviceService-test.js +25 -23
- package/test/unit/ngsiv2/lazyAndCommands/command-test.js +106 -0
- package/test/unit/ngsiv2/plugins/bidirectional-plugin_test.js +115 -0
- package/test/unit/ngsiv2/plugins/multientity-plugin_test.js +58 -0
- package/test/unit/ngsiv2/provisioning/device-provisioning-api_test.js +25 -6
- package/.nyc_output/6e3d7795-bf8c-4a50-bd2f-f8221f27aeae.json +0 -1
- package/.nyc_output/processinfo/6e3d7795-bf8c-4a50-bd2f-f8221f27aeae.json +0 -1
- package/config +0 -0
- package/docker/Mosquitto/Dockerfile.debian +0 -38
- package/docker/Mosquitto/Dockerfile.debian~ +0 -23
- package/lib/plugins/multiEntity.js_avg2 +0 -343
- package/test/unit/ngsiv2/plugins/multientity-plugin_test.js_avg2 +0 -1224
package/lib/jexlTranformsMap.js
CHANGED
|
@@ -49,6 +49,8 @@ const map = {
|
|
|
49
49
|
replaceallstr: (str, from, to) => str.replaceAll(from, to),
|
|
50
50
|
replaceallregexp: (str, reg, to) => str.replaceAll(new RegExp(reg, 'g'), to),
|
|
51
51
|
split: (str, ch) => str.split(ch),
|
|
52
|
+
joinarrtostr: (arr, ch) => arr.join(ch),
|
|
53
|
+
concatarr: (arr, arr2) => arr.concat(arr2),
|
|
52
54
|
mapper: (val, values, choices) => choices[values.findIndex((target) => target === val)],
|
|
53
55
|
thmapper: (val, values, choices) =>
|
|
54
56
|
choices[
|
|
@@ -67,7 +69,16 @@ const map = {
|
|
|
67
69
|
return Array.from(s);
|
|
68
70
|
},
|
|
69
71
|
touppercase: (val) => String(val).toUpperCase(),
|
|
70
|
-
tolowercase: (val) => String(val).toLowerCase()
|
|
72
|
+
tolowercase: (val) => String(val).toLowerCase(),
|
|
73
|
+
floor: (val) => Math.floor(val),
|
|
74
|
+
ceil: (val) => Math.ceil(val),
|
|
75
|
+
round: (val) => Math.round(val),
|
|
76
|
+
tofixed: (val, decimals) => Number.parseFloat(val).toFixed(decimals),
|
|
77
|
+
gettime: (d) => new Date(d).getTime(),
|
|
78
|
+
toisostring: (d) => new Date(d).toISOString(),
|
|
79
|
+
// https://developer.mozilla.org/es/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleString
|
|
80
|
+
localestring: (d, timezone, options) => new Date(d).toLocaleString(timezone, options),
|
|
81
|
+
now: () => Date.now()
|
|
71
82
|
};
|
|
72
83
|
|
|
73
84
|
exports.map = map;
|
|
@@ -26,8 +26,9 @@
|
|
|
26
26
|
const async = require('async');
|
|
27
27
|
const apply = async.apply;
|
|
28
28
|
const _ = require('underscore');
|
|
29
|
-
const parser = require('./
|
|
29
|
+
const parser = require('./expressionPlugin');
|
|
30
30
|
const logger = require('logops');
|
|
31
|
+
const config = require('../commonConfig');
|
|
31
32
|
const subscriptions = require('../services/ngsi/subscriptionService');
|
|
32
33
|
const deviceService = require('../services/devices/deviceService');
|
|
33
34
|
const context = {
|
|
@@ -180,14 +181,17 @@ function getReverseTransformations(list, values, callback) {
|
|
|
180
181
|
}
|
|
181
182
|
|
|
182
183
|
/**
|
|
183
|
-
* Apply a list of transformations to the reported values,
|
|
184
|
+
* Apply a list of transformations to the reported values,
|
|
185
|
+
* generating a new array of values with the additional data.
|
|
186
|
+
* For NGSI-v2 this consists of value transformations only
|
|
184
187
|
*
|
|
185
188
|
* @param {Array} values List of reported attributes (with their name, type and value).
|
|
189
|
+
* @param {Object} typeInformation Provisioning information about the device represented by the entity.
|
|
186
190
|
* @param {Array} transformations List of transformations to apply (with their name, type and expression).
|
|
187
191
|
*/
|
|
188
|
-
function
|
|
192
|
+
function processTransformationsNGSIv2(values, typeInformation, transformations, callback) {
|
|
189
193
|
let cleanedExpression;
|
|
190
|
-
const ctx = parser.extractContext(values);
|
|
194
|
+
const ctx = parser.extractContext(values, typeInformation);
|
|
191
195
|
let resultTransformations = [];
|
|
192
196
|
|
|
193
197
|
for (let j = 0; j < 2; j++) {
|
|
@@ -205,13 +209,107 @@ function processTransformations(values, transformations, callback) {
|
|
|
205
209
|
values.push({
|
|
206
210
|
name: resultTransformations[i].object_id,
|
|
207
211
|
type: resultTransformations[i].type,
|
|
208
|
-
value: parser.parse(cleanedExpression, ctx, 'String')
|
|
212
|
+
value: parser.parse(cleanedExpression, ctx, 'String', typeInformation)
|
|
209
213
|
});
|
|
210
214
|
}
|
|
211
215
|
|
|
212
216
|
callback(null, values);
|
|
213
217
|
}
|
|
214
218
|
|
|
219
|
+
/**
|
|
220
|
+
* Apply a list of transformations to the reported values,
|
|
221
|
+
* generating a new array of values with the additional data.
|
|
222
|
+
* For NGSI-LD the output includes value, metadata and datasetId
|
|
223
|
+
*
|
|
224
|
+
* @param {Array} values List of reported attributes (with their name, type and value).
|
|
225
|
+
* @param {Object} typeInformation Provisioning information about the device represented by the entity.
|
|
226
|
+
* @param {Array} transformations List of transformations to apply (with their name, type and expression).
|
|
227
|
+
*/
|
|
228
|
+
function processTransformationsNGSILD(values, typeInformation, transformations, callback) {
|
|
229
|
+
let cleanedExpression;
|
|
230
|
+
const defaultValues = [];
|
|
231
|
+
const datasetIds = {};
|
|
232
|
+
|
|
233
|
+
// Split incoming values into those with and without datasetId
|
|
234
|
+
values.forEach((value) => {
|
|
235
|
+
if (value.datasetId) {
|
|
236
|
+
datasetIds[value.datasetId] = datasetIds[value.datasetId] || [];
|
|
237
|
+
datasetIds[value.datasetId].push(value);
|
|
238
|
+
} else {
|
|
239
|
+
defaultValues.push(value);
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
let resultTransformations = [];
|
|
244
|
+
|
|
245
|
+
for (let j = 0; j < 2; j++) {
|
|
246
|
+
if (transformations[j]) {
|
|
247
|
+
resultTransformations = resultTransformations.concat(transformations[j]);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Process Transformations using the default datasetId
|
|
252
|
+
// This is the direct equivalent of the NGSI-v2 function
|
|
253
|
+
if (!_.isEmpty(defaultValues)) {
|
|
254
|
+
for (let i = 0; i < resultTransformations.length; i++) {
|
|
255
|
+
cleanedExpression = resultTransformations[i].expression.substr(
|
|
256
|
+
2,
|
|
257
|
+
resultTransformations[i].expression.length - 3
|
|
258
|
+
);
|
|
259
|
+
values.push({
|
|
260
|
+
name: resultTransformations[i].object_id,
|
|
261
|
+
type: resultTransformations[i].type,
|
|
262
|
+
metadata: {},
|
|
263
|
+
value: parser.parse(
|
|
264
|
+
cleanedExpression,
|
|
265
|
+
parser.extractContext(defaultValues, typeInformation),
|
|
266
|
+
'String',
|
|
267
|
+
typeInformation
|
|
268
|
+
)
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Process Transformations using an explicit datasetId
|
|
274
|
+
// There is no equivalent with NGSI-v2
|
|
275
|
+
_.keys(datasetIds).forEach((datasetId) => {
|
|
276
|
+
for (let i = 0; i < resultTransformations.length; i++) {
|
|
277
|
+
cleanedExpression = resultTransformations[i].expression.substr(
|
|
278
|
+
2,
|
|
279
|
+
resultTransformations[i].expression.length - 3
|
|
280
|
+
);
|
|
281
|
+
values.push({
|
|
282
|
+
name: resultTransformations[i].object_id,
|
|
283
|
+
type: resultTransformations[i].type,
|
|
284
|
+
datasetId,
|
|
285
|
+
metadata: {},
|
|
286
|
+
value: parser.parse(
|
|
287
|
+
cleanedExpression,
|
|
288
|
+
parser.extractContext(datasetIds[datasetId], typeInformation),
|
|
289
|
+
'String',
|
|
290
|
+
typeInformation
|
|
291
|
+
)
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
callback(null, values);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Apply a list of transformations to the reported values, generating a new array of values with the additional data.
|
|
300
|
+
*
|
|
301
|
+
* @param {Array} values List of reported attributes (with their name, type and value).
|
|
302
|
+
* @param {Object} typeInformation Provisioning information about the device represented by the entity.
|
|
303
|
+
* @param {Array} transformations List of transformations to apply (with their name, type and expression).
|
|
304
|
+
*/
|
|
305
|
+
function processTransformations(values, typeInformation, transformations, callback) {
|
|
306
|
+
if (config.checkNgsiLD({})) {
|
|
307
|
+
processTransformationsNGSILD(values, typeInformation, transformations, callback);
|
|
308
|
+
} else {
|
|
309
|
+
processTransformationsNGSIv2(values, typeInformation, transformations, callback);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
215
313
|
/**
|
|
216
314
|
* Middleware to handle incoming Configuration group provisions. Should check for the existence of reverse active
|
|
217
315
|
* attributes and create subscriptions for the modifciation of those values.
|
|
@@ -259,7 +357,7 @@ function handleNotification(device, values, callback) {
|
|
|
259
357
|
apply(getReverseTransformations, deviceAttributes, values),
|
|
260
358
|
apply(getReverseTransformations, groupAttributes, values)
|
|
261
359
|
]),
|
|
262
|
-
apply(processTransformations, values)
|
|
360
|
+
apply(processTransformations, values, device)
|
|
263
361
|
],
|
|
264
362
|
function (error, results) {
|
|
265
363
|
callback(error, device, results);
|
|
@@ -60,6 +60,13 @@ function extractContext(attributeList, typeInformation) {
|
|
|
60
60
|
return parser.extractContext(attributeList);
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
function parse(expression, context, type, typeInformation) {
|
|
64
|
+
if (!checkJexl(typeInformation)) {
|
|
65
|
+
return legacyParser.parse(expression, context, type);
|
|
66
|
+
}
|
|
67
|
+
return jexlParser.parse(expression, context);
|
|
68
|
+
}
|
|
69
|
+
|
|
63
70
|
function mergeAttributes(attrList1, attrList2) {
|
|
64
71
|
const finalCollection = _.clone(attrList1);
|
|
65
72
|
const additionalItems = [];
|
|
@@ -120,6 +127,8 @@ function update(entity, typeInformation, callback) {
|
|
|
120
127
|
attributesCtxt.push(att);
|
|
121
128
|
});
|
|
122
129
|
}
|
|
130
|
+
let idTypeSSSList = utils.getIdTypeServSubServiceFromDevice(typeInformation);
|
|
131
|
+
attributesCtxt = attributesCtxt.concat(idTypeSSSList);
|
|
123
132
|
const ctx = parser.extractContext(attributesCtxt);
|
|
124
133
|
|
|
125
134
|
if (typeInformation.active) {
|
|
@@ -156,6 +165,7 @@ function update(entity, typeInformation, callback) {
|
|
|
156
165
|
}
|
|
157
166
|
}
|
|
158
167
|
|
|
168
|
+
exports.parse = parse;
|
|
159
169
|
exports.update = update;
|
|
160
170
|
exports.setJEXLTransforms = setJEXLTransforms;
|
|
161
171
|
exports.applyExpression = applyExpression;
|
|
@@ -141,8 +141,8 @@ function contextAvailable(expression, context) {
|
|
|
141
141
|
jexl.evalSync(expression, context);
|
|
142
142
|
return true;
|
|
143
143
|
} catch (e) {
|
|
144
|
-
|
|
145
|
-
|
|
144
|
+
logger.warn(logContext, 'Wrong expression found "[%j]" over "[%j]", it will be ignored', expression, context);
|
|
145
|
+
return false;
|
|
146
146
|
}
|
|
147
147
|
}
|
|
148
148
|
|
|
@@ -177,7 +177,8 @@ function getIdTypeServSubServiceFromDevice(typeInformation) {
|
|
|
177
177
|
{ name: 'id', type: 'String', value: typeInformation.id },
|
|
178
178
|
{ name: 'type', type: 'String', value: typeInformation.type },
|
|
179
179
|
{ name: 'service', type: 'String', value: typeInformation.service },
|
|
180
|
-
{ name: 'subservice', type: 'String', value: typeInformation.subservice }
|
|
180
|
+
{ name: 'subservice', type: 'String', value: typeInformation.subservice },
|
|
181
|
+
{ name: 'entity_name', type: 'String', value: typeInformation.name }
|
|
181
182
|
];
|
|
182
183
|
return attrList;
|
|
183
184
|
}
|
package/lib/request-shim.js
CHANGED
|
@@ -26,6 +26,7 @@ const logger = require('logops');
|
|
|
26
26
|
const context = {
|
|
27
27
|
op: 'IoTAgentNGSI.Request'
|
|
28
28
|
};
|
|
29
|
+
const util = require('util');
|
|
29
30
|
|
|
30
31
|
/**
|
|
31
32
|
* Transform the "request" options into "got" options and add additional "got" defaults
|
|
@@ -103,7 +104,7 @@ function request(options, callback) {
|
|
|
103
104
|
return callback(null, response, response.body);
|
|
104
105
|
})
|
|
105
106
|
.catch((error) => {
|
|
106
|
-
logger.debug(context, 'Error: %s', JSON.stringify(error, null, 4));
|
|
107
|
+
logger.debug(context, 'Error: %s', JSON.stringify(util.inspect(error), null, 4));
|
|
107
108
|
return callback(error);
|
|
108
109
|
});
|
|
109
110
|
}
|
|
@@ -132,7 +132,14 @@ function mergeArrays(original, newArray) {
|
|
|
132
132
|
* @param {Object} configuration Configuration data.
|
|
133
133
|
*/
|
|
134
134
|
function mergeDeviceWithConfiguration(fields, defaults, deviceData, configuration, callback) {
|
|
135
|
-
logger.debug(
|
|
135
|
+
logger.debug(
|
|
136
|
+
context,
|
|
137
|
+
'deviceData before merge with conf: %j defaults: %j fields: %j configuration %j',
|
|
138
|
+
deviceData,
|
|
139
|
+
defaults,
|
|
140
|
+
fields,
|
|
141
|
+
configuration
|
|
142
|
+
);
|
|
136
143
|
for (let i = 0; i < fields.length; i++) {
|
|
137
144
|
const confField = fields[i] === 'active' ? 'attributes' : fields[i];
|
|
138
145
|
if (deviceData && deviceData[fields[i]] && ['active', 'lazy', 'commands'].indexOf(fields[i]) >= 0) {
|
|
@@ -282,32 +289,24 @@ function registerDevice(deviceObj, callback) {
|
|
|
282
289
|
}
|
|
283
290
|
|
|
284
291
|
if (!deviceData.name) {
|
|
285
|
-
|
|
292
|
+
let entityName = null;
|
|
293
|
+
if (configuration && configuration.entityNameExp !== undefined && configuration.entityNameExp !== '') {
|
|
286
294
|
// Add device ID, TYPE, S, SS to the context for the JEXL expression
|
|
287
295
|
let attrList = pluginUtils.getIdTypeServSubServiceFromDevice(deviceData);
|
|
288
296
|
attrList = deviceData.staticAttributes ? attrList.concat(deviceData.staticAttributes) : attrList;
|
|
289
297
|
let ctxt = expressionPlugin.extractContext(attrList, deviceData);
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
deviceData.name = deviceData.type + conjunction + deviceData.id;
|
|
300
|
-
|
|
301
|
-
if (config.checkNgsiLD(configuration)) {
|
|
302
|
-
deviceData.name = 'urn:ngsi-ld:' + deviceData.type + conjunction + deviceData.id;
|
|
298
|
+
try {
|
|
299
|
+
entityName = expressionPlugin.applyExpression(configuration.entityNameExp, ctxt, deviceData);
|
|
300
|
+
} catch (e) {
|
|
301
|
+
logger.debug(
|
|
302
|
+
context,
|
|
303
|
+
'Error evaluating expression for entityName: %s with context: %s',
|
|
304
|
+
configuration.entityNameExp,
|
|
305
|
+
ctxt
|
|
306
|
+
);
|
|
303
307
|
}
|
|
304
|
-
logger.debug(
|
|
305
|
-
context,
|
|
306
|
-
'Device name not found, falling back to deviceType%sdeviceId [%s]',
|
|
307
|
-
conjunction,
|
|
308
|
-
deviceData.name
|
|
309
|
-
);
|
|
310
308
|
}
|
|
309
|
+
deviceData.name = entityName ? entityName : defaultName();
|
|
311
310
|
}
|
|
312
311
|
|
|
313
312
|
if (!configuration && config.getConfig().types[deviceData.type]) {
|
|
@@ -316,6 +315,28 @@ function registerDevice(deviceObj, callback) {
|
|
|
316
315
|
selectedConfiguration = configuration;
|
|
317
316
|
}
|
|
318
317
|
callback(null, deviceData, selectedConfiguration);
|
|
318
|
+
|
|
319
|
+
function defaultName() {
|
|
320
|
+
let conjunction;
|
|
321
|
+
let name;
|
|
322
|
+
if (configuration && configuration.defaultEntityNameConjunction !== undefined) {
|
|
323
|
+
conjunction = configuration.defaultEntityNameConjunction;
|
|
324
|
+
} else {
|
|
325
|
+
conjunction = config.getConfig().defaultEntityNameConjunction;
|
|
326
|
+
}
|
|
327
|
+
name = deviceData.type + conjunction + deviceData.id;
|
|
328
|
+
|
|
329
|
+
if (config.checkNgsiLD(configuration)) {
|
|
330
|
+
name = 'urn:ngsi-ld:' + deviceData.type + conjunction + deviceData.id;
|
|
331
|
+
}
|
|
332
|
+
logger.debug(
|
|
333
|
+
context,
|
|
334
|
+
'Device name not found, falling back to deviceType%sdeviceId [%s]',
|
|
335
|
+
conjunction,
|
|
336
|
+
deviceData.name
|
|
337
|
+
);
|
|
338
|
+
return name;
|
|
339
|
+
}
|
|
319
340
|
}
|
|
320
341
|
|
|
321
342
|
function completeRegistrations(error, deviceData) {
|
|
@@ -348,6 +369,9 @@ function registerDevice(deviceObj, callback) {
|
|
|
348
369
|
if ('explicitAttrs' in deviceData && deviceData.explicitAttrs !== undefined) {
|
|
349
370
|
deviceObj.explicitAttrs = deviceData.explicitAttrs;
|
|
350
371
|
}
|
|
372
|
+
if ('apikey' in deviceData && deviceData.apikey !== undefined) {
|
|
373
|
+
deviceObj.apikey = deviceData.apikey;
|
|
374
|
+
}
|
|
351
375
|
config.getRegistry().store(deviceObj, callback);
|
|
352
376
|
}
|
|
353
377
|
}
|
|
@@ -605,8 +629,16 @@ function findOrCreate(deviceId, group, callback) {
|
|
|
605
629
|
if ('expressionLanguage' in group && group.expressionLanguage !== undefined) {
|
|
606
630
|
newDevice.expressionLanguage = group.expressionLanguage;
|
|
607
631
|
}
|
|
632
|
+
if (
|
|
633
|
+
(!('apikey' in newDevice) || newDevice.apikey === undefined) &&
|
|
634
|
+
'apikey' in group &&
|
|
635
|
+
group.apikey !== undefined
|
|
636
|
+
) {
|
|
637
|
+
newDevice.apikey = group.apikey;
|
|
638
|
+
}
|
|
608
639
|
// Check autoprovision flag in order to register or not device
|
|
609
640
|
if (group.autoprovision === undefined || group.autoprovision === true) {
|
|
641
|
+
logger.debug(context, 'Registering autoprovision of Device %j for its conf %j', newDevice, group);
|
|
610
642
|
registerDevice(newDevice, function (error, device) {
|
|
611
643
|
callback(error, device, group);
|
|
612
644
|
});
|
|
@@ -359,7 +359,9 @@ function updateRegisterDeviceNgsi2(deviceObj, entityInfoUpdated, callback) {
|
|
|
359
359
|
if ('expressionLanguage' in newDevice && newDevice.expressionLanguage !== undefined) {
|
|
360
360
|
oldDevice.expressionLanguage = newDevice.expressionLanguage;
|
|
361
361
|
}
|
|
362
|
-
|
|
362
|
+
if ('apikey' in newDevice && newDevice.apikey !== undefined) {
|
|
363
|
+
oldDevice.explicitAttrs = newDevice.apikey;
|
|
364
|
+
}
|
|
363
365
|
oldDevice.endpoint = newDevice.endpoint || oldDevice.endpoint;
|
|
364
366
|
|
|
365
367
|
callback(null, oldDevice);
|
|
@@ -387,8 +387,13 @@ function removeHiddenAttrsFromMultiEntity(entities, typeInformation, payload) {
|
|
|
387
387
|
...jexlParser.extractContext(attsArray),
|
|
388
388
|
...extractContextFromPayload(payload)
|
|
389
389
|
}; //maybe overlapping between this two objects.
|
|
390
|
-
|
|
391
|
-
|
|
390
|
+
let res = null;
|
|
391
|
+
try {
|
|
392
|
+
res = jexlParser.applyExpression(typeInformation.explicitAttrs, ctx, typeInformation);
|
|
393
|
+
} catch (e) {
|
|
394
|
+
// nothing to do: exception is already logged at info level
|
|
395
|
+
}
|
|
396
|
+
explicitAttrsList = res ? explicitAttrsList.concat(res) : explicitAttrsList;
|
|
392
397
|
});
|
|
393
398
|
}
|
|
394
399
|
}
|
|
@@ -438,8 +443,13 @@ function removeHiddenAttrs(result, typeInformation, payload) {
|
|
|
438
443
|
...jexlParser.extractContext(attsArray),
|
|
439
444
|
...extractContextFromPayload(payload)
|
|
440
445
|
}; //maybe overlapping between this two objects. Measures not in active attrs.
|
|
441
|
-
|
|
442
|
-
|
|
446
|
+
let res = null;
|
|
447
|
+
try {
|
|
448
|
+
res = jexlParser.applyExpression(typeInformation.explicitAttrs, ctx, typeInformation);
|
|
449
|
+
} catch (e) {
|
|
450
|
+
// nothing to do: exception is already logged at info level
|
|
451
|
+
}
|
|
452
|
+
explicitAttrsList = res ? res : [];
|
|
443
453
|
}
|
|
444
454
|
if (explicitAttrsList && explicitAttrsList.length >= 0) {
|
|
445
455
|
const hidden = _.difference(_.keys(result), explicitAttrsList);
|
|
@@ -46,6 +46,21 @@ const overwritePaths = ['/ngsi-ld/v1/entities/:entity/attrs', '/ngsi-ld/v1/entit
|
|
|
46
46
|
const updatePaths = ['/ngsi-ld/v1/entities/:entity/attrs', '/ngsi-ld/v1/entities/:entity/attrs/:attr'];
|
|
47
47
|
const queryPaths = ['/ngsi-ld/v1/entities/:entity'];
|
|
48
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Extract metadata attributes from input.
|
|
51
|
+
*
|
|
52
|
+
* @param {Object} obj input object
|
|
53
|
+
*/
|
|
54
|
+
function getMetaData(obj) {
|
|
55
|
+
const excludedKeys = ['datasetId', 'value', 'type'];
|
|
56
|
+
const metaData = {};
|
|
57
|
+
_.keys(obj).forEach((key) => {
|
|
58
|
+
if (!excludedKeys.includes(key)) {
|
|
59
|
+
metaData[key] = obj[key];
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
return !_.isEmpty(metaData) ? metaData : {};
|
|
63
|
+
}
|
|
49
64
|
/**
|
|
50
65
|
* Generate all the update actions corresponding to a update context request using Ngsi2.
|
|
51
66
|
* Update actions include updates in attributes and execution of commands.
|
|
@@ -59,6 +74,7 @@ function generateUpdateActionsNgsiLD(req, contextElement, callback) {
|
|
|
59
74
|
|
|
60
75
|
const attribute = req.params.attr;
|
|
61
76
|
const value = req.body.value;
|
|
77
|
+
const datasetId = req.body.datasetId;
|
|
62
78
|
const incomingAttrs = !req.params.attr ? _.keys(req.body) : [];
|
|
63
79
|
|
|
64
80
|
if (contextElement.id && contextElement.type) {
|
|
@@ -75,19 +91,49 @@ function generateUpdateActionsNgsiLD(req, contextElement, callback) {
|
|
|
75
91
|
|
|
76
92
|
if (device.commands) {
|
|
77
93
|
for (const j in device.commands) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
94
|
+
const name = device.commands[j].name;
|
|
95
|
+
if (attribute === name) {
|
|
96
|
+
if (_.isArray(req.body)) {
|
|
97
|
+
req.body[name].forEach((obj) => {
|
|
98
|
+
commands.push({
|
|
99
|
+
type: device.commands[j].type,
|
|
100
|
+
value: obj.value,
|
|
101
|
+
name: name,
|
|
102
|
+
datasetId: obj.datasetId,
|
|
103
|
+
metadata: getMetaData(obj)
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
} else {
|
|
107
|
+
commands.push({
|
|
108
|
+
type: device.commands[j].type,
|
|
109
|
+
value,
|
|
110
|
+
name,
|
|
111
|
+
datasetId,
|
|
112
|
+
metadata: getMetaData(req.body)
|
|
113
|
+
});
|
|
114
|
+
}
|
|
84
115
|
found = true;
|
|
85
|
-
} else if (incomingAttrs.includes(
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
116
|
+
} else if (incomingAttrs.includes(name)) {
|
|
117
|
+
if (_.isArray(req.body[name])) {
|
|
118
|
+
req.body[name].forEach((obj) => {
|
|
119
|
+
commands.push({
|
|
120
|
+
type: device.commands[j].type,
|
|
121
|
+
value: obj.value,
|
|
122
|
+
name: name,
|
|
123
|
+
datasetId: obj.datasetId,
|
|
124
|
+
metadata: getMetaData(obj)
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
} else {
|
|
128
|
+
const obj = req.body[name];
|
|
129
|
+
commands.push({
|
|
130
|
+
type: device.commands[j].type,
|
|
131
|
+
value: obj.value,
|
|
132
|
+
name: name,
|
|
133
|
+
datasetId: obj.datasetId,
|
|
134
|
+
metadata: getMetaData(obj)
|
|
135
|
+
});
|
|
136
|
+
}
|
|
91
137
|
found = true;
|
|
92
138
|
}
|
|
93
139
|
}
|
|
@@ -290,7 +336,7 @@ function handleQueryNgsiLD(req, res, next) {
|
|
|
290
336
|
|
|
291
337
|
if (device.staticAttributes) {
|
|
292
338
|
let selectedAttributes = [];
|
|
293
|
-
if (attributes === undefined || attributes.length === 0) {
|
|
339
|
+
if (attributes === null || attributes === undefined || attributes.length === 0) {
|
|
294
340
|
selectedAttributes = device.staticAttributes;
|
|
295
341
|
} else {
|
|
296
342
|
selectedAttributes = device.staticAttributes.filter(inAttributes);
|
|
@@ -316,8 +362,8 @@ function handleQueryNgsiLD(req, res, next) {
|
|
|
316
362
|
const results = device.lazy.map(getName);
|
|
317
363
|
callback(null, results);
|
|
318
364
|
} else {
|
|
319
|
-
logger.debug(context, "Couldn't find any attributes. Handling with
|
|
320
|
-
callback(null,
|
|
365
|
+
logger.debug(context, "Couldn't find any attributes. Handling with empty reference");
|
|
366
|
+
callback(null, []);
|
|
321
367
|
}
|
|
322
368
|
}
|
|
323
369
|
|
|
@@ -472,11 +518,25 @@ function handleNotificationNgsiLD(req, res, next) {
|
|
|
472
518
|
/* eslint-disable-next-line no-prototype-builtins */
|
|
473
519
|
if (dataElement.hasOwnProperty(key)) {
|
|
474
520
|
if (key !== 'id' && key !== 'type') {
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
521
|
+
if (_.isArray(dataElement[key])) {
|
|
522
|
+
dataElement[key].forEach((obj) => {
|
|
523
|
+
atts.push({
|
|
524
|
+
type: obj.type,
|
|
525
|
+
value: obj.value,
|
|
526
|
+
name: key,
|
|
527
|
+
datasetId: obj.datasetId,
|
|
528
|
+
metadata: getMetaData(obj)
|
|
529
|
+
});
|
|
530
|
+
});
|
|
531
|
+
} else {
|
|
532
|
+
atts.push({
|
|
533
|
+
type: dataElement[key].type,
|
|
534
|
+
value: dataElement[key].value,
|
|
535
|
+
name: key,
|
|
536
|
+
datasetId: dataElement[key].datasetId,
|
|
537
|
+
metadata: getMetaData(dataElement[key])
|
|
538
|
+
});
|
|
539
|
+
}
|
|
480
540
|
}
|
|
481
541
|
}
|
|
482
542
|
}
|
|
@@ -563,6 +623,21 @@ function updateErrorHandlingNgsiLD(error, req, res, next) {
|
|
|
563
623
|
});
|
|
564
624
|
}
|
|
565
625
|
|
|
626
|
+
/**
|
|
627
|
+
* Load unsupported NGSI-LD entity routes and return proper NGSI-LD not supported responses
|
|
628
|
+
*
|
|
629
|
+
* @param {Object} router Express request router object.
|
|
630
|
+
*/
|
|
631
|
+
function loadUnsupportedEndpointsNGSILD(router) {
|
|
632
|
+
const unsupportedEndpoint = function (req, res) {
|
|
633
|
+
return res.status(501).send(new errors.MethodNotSupported(req.method, req.path));
|
|
634
|
+
};
|
|
635
|
+
router.get('/ngsi-ld/v1/entities', unsupportedEndpoint);
|
|
636
|
+
router.post('/ngsi-ld/v1/entities', unsupportedEndpoint);
|
|
637
|
+
router.delete('/ngsi-ld/v1/entities/:entity', unsupportedEndpoint);
|
|
638
|
+
router.delete('/ngsi-ld/v1/entities/:entity/attrs/:attr', unsupportedEndpoint);
|
|
639
|
+
}
|
|
640
|
+
|
|
566
641
|
/**
|
|
567
642
|
* Load the routes related to context dispatching (NGSI10 calls).
|
|
568
643
|
*
|
|
@@ -601,6 +676,7 @@ function loadContextRoutesNGSILD(router) {
|
|
|
601
676
|
handleNotificationNgsiLD,
|
|
602
677
|
queryErrorHandlingNgsiLD
|
|
603
678
|
]);
|
|
679
|
+
loadUnsupportedEndpointsNGSILD(router);
|
|
604
680
|
}
|
|
605
681
|
|
|
606
682
|
exports.loadContextRoutes = loadContextRoutesNGSILD;
|
|
@@ -53,11 +53,9 @@ function checkMandatoryQueryParams(mandatoryAttributes, body, callback) {
|
|
|
53
53
|
for (const p in mandatoryAttributes) {
|
|
54
54
|
let found = false;
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
if (body
|
|
58
|
-
|
|
59
|
-
found = true;
|
|
60
|
-
}
|
|
56
|
+
if (body.hasOwnProperty(mandatoryAttributes[p])) {
|
|
57
|
+
if (body[mandatoryAttributes[p]] !== '' && body[mandatoryAttributes[p]] !== null) {
|
|
58
|
+
found = true;
|
|
61
59
|
}
|
|
62
60
|
}
|
|
63
61
|
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "iotagent-node-lib",
|
|
3
3
|
"license": "AGPL-3.0-only",
|
|
4
4
|
"description": "IoT Agent library to interface with NGSI Context Broker",
|
|
5
|
-
"version": "2.
|
|
5
|
+
"version": "2.25.0",
|
|
6
6
|
"homepage": "https://github.com/telefonicaid/iotagent-node-lib",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"fiware",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
},
|
|
24
24
|
"main": "lib/fiware-iotagent-lib",
|
|
25
25
|
"engines": {
|
|
26
|
-
"node": ">=
|
|
26
|
+
"node": ">=14"
|
|
27
27
|
},
|
|
28
28
|
"scripts": {
|
|
29
29
|
"clean": "rm -rf package-lock.json && rm -rf node_modules && rm -rf coverage",
|