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.
Files changed (60) hide show
  1. package/.github/workflows/ci.yml +6 -7
  2. package/.nyc_output/33364de2-1199-4ec2-b33c-cae063ef8cc4.json +1 -0
  3. package/.nyc_output/processinfo/33364de2-1199-4ec2-b33c-cae063ef8cc4.json +1 -0
  4. package/.nyc_output/processinfo/index.json +1 -1
  5. package/CHANGES_NEXT_RELEASE +1 -2
  6. package/doc/advanced-topics.md +78 -3
  7. package/doc/apiary/iotagent.apib +5 -5
  8. package/doc/architecture.md +52 -5
  9. package/doc/deprecated.md +10 -7
  10. package/doc/expressionLanguage.md +46 -35
  11. package/doc/getting-started.md +1 -1
  12. package/doc/usermanual.md +18 -16
  13. package/examples/TTOpen-service.json +1 -1
  14. package/lib/errors.js +9 -1
  15. package/lib/fiware-iotagent-lib.js +2 -1
  16. package/lib/jexlTranformsMap.js +12 -1
  17. package/lib/plugins/bidirectionalData.js +104 -6
  18. package/lib/plugins/expressionPlugin.js +10 -0
  19. package/lib/plugins/jexlParser.js +2 -2
  20. package/lib/plugins/pluginUtils.js +2 -1
  21. package/lib/request-shim.js +2 -1
  22. package/lib/services/devices/deviceService.js +53 -21
  23. package/lib/services/devices/devices-NGSI-v2.js +3 -1
  24. package/lib/services/ngsi/entities-NGSI-v2.js +14 -4
  25. package/lib/services/northBound/contextServer-NGSI-LD.js +96 -20
  26. package/lib/services/northBound/contextServer-NGSI-v2.js +1 -0
  27. package/lib/services/northBound/restUtils.js +3 -5
  28. package/lib/templates/deviceGroup.json +1 -1
  29. package/package.json +2 -2
  30. package/test/unit/examples/deviceProvisioningRequests/provisionNewDeviceEmpty.json +43 -0
  31. package/test/unit/examples/mongoCollections/configurations.json +3 -3
  32. package/test/unit/expressions/jexlExpression-test.js +29 -8
  33. package/test/unit/general/deviceService-test.js +31 -29
  34. package/test/unit/memoryRegistry/deviceRegistryMemory_test.js +62 -10
  35. package/test/unit/mongodb/mongodb-group-registry-test.js +24 -0
  36. package/test/unit/mongodb/mongodb-registry-test.js +68 -10
  37. package/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin30.json +11 -0
  38. package/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotificationWithDatasetId.json +21 -0
  39. package/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotificationWithMetadata.json +17 -0
  40. package/test/unit/ngsi-ld/expressions/jexlBasedTransformations-test.js +18 -4
  41. package/test/unit/ngsi-ld/general/deviceService-test.js +31 -29
  42. package/test/unit/ngsi-ld/lazyAndCommands/command-test.js +656 -2
  43. package/test/unit/ngsi-ld/ngsiService/unsupported-endpoints-test.js +111 -0
  44. package/test/unit/ngsi-ld/plugins/bidirectional-plugin_test.js +221 -0
  45. package/test/unit/ngsi-mixed/provisioning/ngsi-versioning-test.js +0 -3
  46. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityJexlExpressionPlugin1.json +22 -0
  47. package/test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalNotificationWithMetadata.json +19 -0
  48. package/test/unit/ngsiv2/expressions/jexlBasedTransformations-test.js +21 -6
  49. package/test/unit/ngsiv2/general/deviceService-test.js +25 -23
  50. package/test/unit/ngsiv2/lazyAndCommands/command-test.js +106 -0
  51. package/test/unit/ngsiv2/plugins/bidirectional-plugin_test.js +115 -0
  52. package/test/unit/ngsiv2/plugins/multientity-plugin_test.js +58 -0
  53. package/test/unit/ngsiv2/provisioning/device-provisioning-api_test.js +25 -6
  54. package/.nyc_output/6e3d7795-bf8c-4a50-bd2f-f8221f27aeae.json +0 -1
  55. package/.nyc_output/processinfo/6e3d7795-bf8c-4a50-bd2f-f8221f27aeae.json +0 -1
  56. package/config +0 -0
  57. package/docker/Mosquitto/Dockerfile.debian +0 -38
  58. package/docker/Mosquitto/Dockerfile.debian~ +0 -23
  59. package/lib/plugins/multiEntity.js_avg2 +0 -343
  60. package/test/unit/ngsiv2/plugins/multientity-plugin_test.js_avg2 +0 -1224
@@ -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('./expressionParser');
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, generating a new array of values with the additional data.
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 processTransformations(values, transformations, callback) {
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
- error = new errors.InvalidExpression(expression);
145
- throw error;
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
  }
@@ -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(context, 'deviceData before merge with conf: %j', deviceData);
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
- if (configuration && configuration.entityNameExp !== undefined) {
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
- let entityName = expressionPlugin.applyExpression(configuration.entityNameExp, ctxt, deviceData);
291
- deviceData.name = entityName ? entityName : configuration.entityNameExp;
292
- } else {
293
- let conjunction;
294
- if (configuration && configuration.defaultEntityNameConjunction !== undefined) {
295
- conjunction = configuration.defaultEntityNameConjunction;
296
- } else {
297
- conjunction = config.getConfig().defaultEntityNameConjunction;
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
- const res = jexlParser.applyExpression(typeInformation.explicitAttrs, ctx, typeInformation);
391
- explicitAttrsList = explicitAttrsList.concat(res);
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
- const res = jexlParser.applyExpression(typeInformation.explicitAttrs, ctx, typeInformation);
442
- explicitAttrsList = res;
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
- if (attribute === device.commands[j].name) {
79
- commands.push({
80
- type: device.commands[j].type,
81
- value,
82
- name: attribute
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(device.commands[j].name)) {
86
- commands.push({
87
- type: device.commands[j].type,
88
- value: req.body[device.commands[j].name].value,
89
- name: device.commands[j].name
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 null reference");
320
- callback(null, 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
- const att = {};
476
- att.type = dataElement[key].type;
477
- att.value = dataElement[key].value;
478
- att.name = key;
479
- atts.push(att);
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;
@@ -286,6 +286,7 @@ function handleNotificationNgsi2(req, res, next) {
286
286
  const att = {};
287
287
  att.type = dataElement[key].type;
288
288
  att.value = dataElement[key].value;
289
+ att.metadata = dataElement[key].metadata || {};
289
290
  att.name = key;
290
291
  atts.push(att);
291
292
  }
@@ -53,11 +53,9 @@ function checkMandatoryQueryParams(mandatoryAttributes, body, callback) {
53
53
  for (const p in mandatoryAttributes) {
54
54
  let found = false;
55
55
 
56
- for (const i in body) {
57
- if (body.hasOwnProperty(i)) {
58
- if (i === mandatoryAttributes[p]) {
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
 
@@ -24,7 +24,7 @@
24
24
  "description": "token",
25
25
  "type": "string"
26
26
  },
27
- "cbroker": {
27
+ "cbHost": {
28
28
  "description": "uri for the context broker",
29
29
  "type": "string"
30
30
  },
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.23.0",
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": ">=12"
26
+ "node": ">=14"
27
27
  },
28
28
  "scripts": {
29
29
  "clean": "rm -rf package-lock.json && rm -rf node_modules && rm -rf coverage",