iotagent-node-lib 3.1.0 → 3.3.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 (163) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.yml +134 -0
  2. package/.github/ISSUE_TEMPLATE/config.yml +16 -0
  3. package/.github/ISSUE_TEMPLATE/feature_request.yml +55 -0
  4. package/.github/advanced-issue-labeler.yml +30 -0
  5. package/.github/workflows/issue-labeler.yml +43 -0
  6. package/CHANGES_NEXT_RELEASE +0 -2
  7. package/config.js +5 -5
  8. package/doc/api.md +1483 -298
  9. package/doc/deprecated.md +7 -1
  10. package/doc/development.md +120 -0
  11. package/doc/howto.md +58 -62
  12. package/doc/installationguide.md +3 -11
  13. package/doc/requirements.txt +1 -1
  14. package/docker/Mosquitto/Dockerfile +1 -1
  15. package/lib/commonConfig.js +7 -10
  16. package/lib/fiware-iotagent-lib.js +0 -10
  17. package/lib/jexlTranformsMap.js +2 -1
  18. package/lib/model/Device.js +0 -1
  19. package/lib/model/Group.js +0 -1
  20. package/lib/model/dbConn.js +1 -7
  21. package/lib/plugins/bidirectionalData.js +8 -26
  22. package/lib/plugins/expressionPlugin.js +8 -40
  23. package/lib/plugins/jexlParser.js +28 -0
  24. package/lib/services/commands/commandService.js +1 -1
  25. package/lib/services/common/iotManagerService.js +0 -1
  26. package/lib/services/devices/deviceRegistryMongoDB.js +10 -10
  27. package/lib/services/devices/deviceService.js +16 -20
  28. package/lib/services/devices/devices-NGSI-v2.js +2 -5
  29. package/lib/services/ngsi/entities-NGSI-LD.js +16 -60
  30. package/lib/services/ngsi/entities-NGSI-v2.js +179 -119
  31. package/lib/services/northBound/deviceProvisioningServer.js +17 -17
  32. package/lib/templates/createDevice.json +5 -6
  33. package/lib/templates/createDeviceLax.json +6 -8
  34. package/lib/templates/deviceGroup.json +1 -5
  35. package/lib/templates/updateDevice.json +9 -2
  36. package/lib/templates/updateDeviceLax.json +14 -5
  37. package/package.json +1 -1
  38. package/scripts/legacy_expression_tool/README.md +262 -0
  39. package/scripts/legacy_expression_tool/legacy_expression_tool.py +423 -0
  40. package/scripts/legacy_expression_tool/requirements.txt +3 -0
  41. package/test/unit/examples/deviceProvisioningRequests/provisionBidirectionalDevice.json +5 -5
  42. package/test/unit/examples/groupProvisioningRequests/bidirectionalGroup.json +4 -4
  43. package/test/unit/general/contextBrokerKeystoneSecurityAccess-test.js +3 -13
  44. package/test/unit/ngsi-ld/expressions/jexlBasedTransformations-test.js +0 -2
  45. package/test/unit/ngsi-ld/lazyAndCommands/merge-patch-test.js +31 -30
  46. package/test/unit/ngsi-ld/plugins/bidirectional-plugin_test.js +8 -8
  47. package/test/unit/ngsi-ld/plugins/custom-plugin_test.js +152 -0
  48. package/test/unit/ngsi-ld/plugins/multientity-plugin_test.js +1 -1
  49. package/test/unit/ngsi-mixed/provisioning/ngsi-versioning-test.js +33 -37
  50. package/test/unit/ngsiv2/examples/contextRequests/createMinimumProvisionedDevice4.json +5 -1
  51. package/test/unit/ngsiv2/examples/contextRequests/updateContext.json +2 -0
  52. package/test/unit/ngsiv2/examples/contextRequests/updateContext1.json +3 -1
  53. package/test/unit/ngsiv2/examples/contextRequests/updateContext3WithStatic.json +2 -0
  54. package/test/unit/ngsiv2/examples/contextRequests/updateContext4.json +4 -1
  55. package/test/unit/ngsiv2/examples/contextRequests/updateContext5.json +12 -0
  56. package/test/unit/ngsiv2/examples/contextRequests/updateContext6.json +10 -0
  57. package/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin1.json +2 -0
  58. package/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin2.json +3 -1
  59. package/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin3.json +3 -1
  60. package/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin4.json +3 -1
  61. package/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin5.json +3 -1
  62. package/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin6.json +3 -1
  63. package/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin7.json +3 -1
  64. package/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin8.json +3 -1
  65. package/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin9.json +3 -1
  66. package/test/unit/ngsiv2/examples/contextRequests/updateContextAutocast1.json +2 -0
  67. package/test/unit/ngsiv2/examples/contextRequests/updateContextAutocast2.json +2 -0
  68. package/test/unit/ngsiv2/examples/contextRequests/updateContextAutocast3.json +3 -1
  69. package/test/unit/ngsiv2/examples/contextRequests/updateContextAutocast4.json +3 -1
  70. package/test/unit/ngsiv2/examples/contextRequests/updateContextAutocast5.json +3 -1
  71. package/test/unit/ngsiv2/examples/contextRequests/updateContextAutocast6.json +3 -1
  72. package/test/unit/ngsiv2/examples/contextRequests/updateContextAutocast7.json +3 -1
  73. package/test/unit/ngsiv2/examples/contextRequests/updateContextCommandError.json +3 -1
  74. package/test/unit/ngsiv2/examples/contextRequests/updateContextCommandExpired.json +3 -1
  75. package/test/unit/ngsiv2/examples/contextRequests/updateContextCommandFinish.json +3 -1
  76. package/test/unit/ngsiv2/examples/contextRequests/updateContextCommandStatus.json +2 -0
  77. package/test/unit/ngsiv2/examples/contextRequests/updateContextCommandStatus2.json +2 -0
  78. package/test/unit/ngsiv2/examples/contextRequests/updateContextCompressTimestamp1.json +3 -1
  79. package/test/unit/ngsiv2/examples/contextRequests/updateContextCompressTimestamp2.json +3 -1
  80. package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin1.json +2 -0
  81. package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin11.json +2 -0
  82. package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin12.json +2 -0
  83. package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin13.json +3 -1
  84. package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin2.json +2 -0
  85. package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin29.json +2 -0
  86. package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin3.json +2 -0
  87. package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin30.json +2 -0
  88. package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin31.json +2 -8
  89. package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin32.json +2 -0
  90. package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin33.json +2 -0
  91. package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin34.json +2 -0
  92. package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin35.json +22 -0
  93. package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin36.json +1 -0
  94. package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin4.json +2 -0
  95. package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin40.json +42 -0
  96. package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin41.json +33 -0
  97. package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin5.json +2 -0
  98. package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin6.json +2 -0
  99. package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin7.json +2 -0
  100. package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin8.json +2 -0
  101. package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin9.json +2 -0
  102. package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionSkip.json +12 -0
  103. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityJexlExpressionPlugin1.json +1 -1
  104. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin1.json +1 -1
  105. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin10.json +1 -1
  106. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin11.json +1 -1
  107. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin12.json +1 -1
  108. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin13.json +1 -1
  109. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin14.json +1 -1
  110. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin15.json +1 -1
  111. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin16.json +1 -1
  112. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin17.json +1 -1
  113. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin2.json +1 -1
  114. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin25.json +37 -0
  115. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin3.json +1 -1
  116. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin4.json +1 -1
  117. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin5.json +1 -1
  118. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin6.json +1 -1
  119. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin7.json +1 -1
  120. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin8.json +1 -1
  121. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin9.json +1 -1
  122. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin1.json +1 -1
  123. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin2.json +1 -1
  124. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin3.json +1 -1
  125. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin4.json +2 -0
  126. package/test/unit/ngsiv2/examples/contextRequests/updateContextProcessTimestamp.json +2 -0
  127. package/test/unit/ngsiv2/examples/contextRequests/updateContextStaticAttributes.json +2 -0
  128. package/test/unit/ngsiv2/examples/contextRequests/updateContextStaticAttributesMetadata.json +3 -1
  129. package/test/unit/ngsiv2/examples/contextRequests/updateContextTimestamp.json +3 -1
  130. package/test/unit/ngsiv2/examples/contextRequests/updateContextTimestampFalse.json +12 -0
  131. package/test/unit/ngsiv2/examples/contextRequests/updateContextTimestampFalseTimeInstant.json +12 -0
  132. package/test/unit/ngsiv2/examples/contextRequests/updateContextTimestampOverride.json +2 -0
  133. package/test/unit/ngsiv2/examples/contextRequests/updateContextTimestampOverrideWithoutMilis.json +2 -0
  134. package/test/unit/ngsiv2/examples/contextRequests/updateContextTimestampTimezone.json +3 -1
  135. package/test/unit/ngsiv2/expressions/jexlBasedTransformations-test.js +355 -75
  136. package/test/unit/ngsiv2/general/contextBrokerOAuthSecurityAccess-test.js +18 -51
  137. package/test/unit/ngsiv2/general/https-support-test.js +1 -5
  138. package/test/unit/ngsiv2/lazyAndCommands/command-test.js +4 -10
  139. package/test/unit/ngsiv2/lazyAndCommands/polling-commands-test.js +9 -26
  140. package/test/unit/ngsiv2/ngsiService/active-devices-test.js +143 -57
  141. package/test/unit/ngsiv2/ngsiService/autocast-test.js +14 -21
  142. package/test/unit/ngsiv2/ngsiService/staticAttributes-test.js +3 -5
  143. package/test/unit/ngsiv2/ngsiService/subscriptions-test.js +1 -10
  144. package/test/unit/ngsiv2/plugins/alias-plugin_test.js +20 -30
  145. package/test/unit/ngsiv2/plugins/bidirectional-plugin_test.js +6 -69
  146. package/test/unit/ngsiv2/plugins/compress-timestamp-plugin_test.js +4 -6
  147. package/test/unit/ngsiv2/plugins/custom-plugin_test.js +150 -0
  148. package/test/unit/ngsiv2/plugins/multientity-plugin_test.js +87 -16
  149. package/test/unit/ngsiv2/plugins/timestamp-processing-plugin_test.js +2 -3
  150. package/test/unit/ngsiv2/provisioning/device-group-api-test.js +2 -3
  151. package/test/unit/ngsiv2/provisioning/device-provisioning-api_test.js +15 -55
  152. package/test/unit/ngsiv2/provisioning/device-registration_test.js +1 -7
  153. package/test/unit/ngsiv2/provisioning/device-update-registration_test.js +2 -9
  154. package/test/unit/ngsiv2/provisioning/singleConfigurationMode-test.js +0 -11
  155. package/test/unit/ngsiv2/provisioning/updateProvisionedDevices-test.js +0 -7
  156. package/test/unit/plugins/capture-provision-inPlugins_test.js +0 -6
  157. package/doc/advanced-topics.md +0 -626
  158. package/doc/expressionLanguage.md +0 -762
  159. package/lib/plugins/expressionParser.js +0 -205
  160. package/test/unit/expressions/expression-test.js +0 -197
  161. package/test/unit/ngsi-ld/expressions/expressionBasedTransformations-test.js +0 -882
  162. package/test/unit/ngsiv2/expressions/expressionBasedTransformations-test.js +0 -951
  163. package/test/unit/ngsiv2/expressions/expressionCombinedTransformations-test.js +0 -296
@@ -111,12 +111,10 @@ function addTimestampNgsi2(payload, timezone, timestampValue) {
111
111
 
112
112
  if (timestampValue) {
113
113
  timestamp.value = timestampValue;
114
+ } else if (!timezone) {
115
+ timestamp.value = new Date().toISOString();
114
116
  } else {
115
- if (!timezone) {
116
- timestamp.value = new Date().toISOString();
117
- } else {
118
- timestamp.value = moment().tz(timezone).format('YYYY-MM-DD[T]HH:mm:ss.SSSZ');
119
- }
117
+ timestamp.value = moment().tz(timezone).format('YYYY-MM-DD[T]HH:mm:ss.SSSZ');
120
118
  }
121
119
 
122
120
  function addMetadata(attribute) {
@@ -325,16 +323,6 @@ function sendQueryValueNgsi2(entityName, attributes, typeInformation, token, cal
325
323
  );
326
324
  }
327
325
 
328
- /**
329
- * Determines if a value is of type float
330
- *
331
- * @param {String} value Value to be analyzed
332
- * @return {boolean} True if float, False otherwise.
333
- */
334
- function isFloat(value) {
335
- return !isNaN(value) && value.toString().indexOf('.') !== -1;
336
- }
337
-
338
326
  /**
339
327
  * Makes an update in the Device's entity in the context broker, with the values given in the 'attributes' array. This
340
328
  * array should comply to the NGSIv2's attribute format.
@@ -347,12 +335,12 @@ function isFloat(value) {
347
335
  function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, callback) {
348
336
  logger.debug(
349
337
  context,
350
- 'sendUpdateValueNgsi2 called with: \n entityName=%s \n attributes=%j \n typeInformation=%j',
338
+ 'sendUpdateValueNgsi2 called with: entityName=%s attributes=%j typeInformation=%j',
351
339
  entityName,
352
340
  attributes,
353
341
  typeInformation
354
342
  );
355
- let payload = {
343
+ const payload = {
356
344
  entities: [
357
345
  {
358
346
  id: entityName
@@ -366,10 +354,10 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
366
354
  payload.entities[0].type = typeInformation.type;
367
355
  }
368
356
 
369
- if (config.getConfig().appendMode === true) {
370
- payload.actionType = 'append';
371
- } else {
357
+ if (config.getConfig().appendMode === false) {
372
358
  payload.actionType = 'update';
359
+ } else {
360
+ payload.actionType = 'append';
373
361
  }
374
362
 
375
363
  let options = NGSIUtils.createRequestObject(url, typeInformation, token);
@@ -382,8 +370,9 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
382
370
  callback(new errors.TypeNotFound(null, entityName));
383
371
  return;
384
372
  }
373
+
385
374
  let idTypeSSSList = pluginUtils.getIdTypeServSubServiceFromDevice(typeInformation);
386
- logger.debug(context, 'sendUpdateValueNgsi2 \n idTypeSSS are %j ', idTypeSSSList);
375
+ logger.debug(context, 'sendUpdateValueNgsi2 idTypeSSS are %j ', idTypeSSSList);
387
376
  let measureAttrsForCtxt = [];
388
377
 
389
378
  // Check explicitAttrs: adds all final needed attributes to payload
@@ -408,7 +397,7 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
408
397
  return;
409
398
  }
410
399
  }
411
- logger.debug(context, 'sendUpdateValueNgsi2 \n pre-initial non-explicitAttrs payload=%j', payload);
400
+ logger.debug(context, 'sendUpdateValueNgsi2 pre-initial non-explicitAttrs payload=%j', payload);
412
401
  // Loop for add attrs from type.information.active (and lazys?) into payload entities (entity[0])
413
402
  if (typeInformation.active) {
414
403
  typeInformation.active.forEach((attr) => {
@@ -435,7 +424,7 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
435
424
  if (typeof typeInformation.explicitAttrs === 'string') {
436
425
  // explicitAttrs is a jexlExpression
437
426
  // This ctxt should include all possible attrs
438
- let attributesCtxt = [];
427
+ const attributesCtxt = [];
439
428
  if (typeInformation.static) {
440
429
  typeInformation.static.forEach(function (att) {
441
430
  attributesCtxt.push(att);
@@ -444,37 +433,57 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
444
433
  // Measures
445
434
  for (let i = 0; i < attributes.length; i++) {
446
435
  if (attributes[i].name && attributes[i].type) {
447
- let measureAttr = {
436
+ const measureAttr = {
448
437
  name: attributes[i].name,
449
438
  value: attributes[i].value,
450
439
  type: attributes[i].type
451
440
  };
452
441
  attributesCtxt.push(measureAttr);
442
+ // check measureAttr by object_id -> if in active
443
+ let j = 0;
444
+ let found = false;
445
+ while (j < typeInformation.active.length && !found) {
446
+ if (attributes[i].name === typeInformation.active[j].object_id) {
447
+ let measureAttrByObjectId = {
448
+ name: typeInformation.active[j].name,
449
+ value: attributes[i].value,
450
+ type: attributes[i].type
451
+ };
452
+ attributesCtxt.push(measureAttrByObjectId);
453
+ found = true;
454
+ }
455
+ j++;
456
+ }
453
457
  }
454
458
  }
455
459
  // This context is just to calculate explicitAttrs when is an expression
456
- let ctxt = expressionPlugin.extractContext(attributesCtxt.concat(idTypeSSSList), typeInformation);
457
- // typeInformation.active attrs with expressions expanded by current ctxt
460
+
461
+ let ctxt = expressionPlugin.extractContext(attributesCtxt.concat(idTypeSSSList));
462
+ // typeInformation.active all attrs with expressions
458
463
  if (typeInformation.active) {
459
464
  typeInformation.active.forEach(function (att) {
460
- if (att.expression) {
461
- if (expressionPlugin.contextAvailable(att.expression, ctxt, typeInformation)) {
462
- let expandedAttr = {
463
- name: att.name,
464
- value: att.expression, // it doesn't matter final value here
465
+ if (att.expression !== undefined) {
466
+ let expandedAttr = {
467
+ name: att.name,
468
+ value: att.expression,
469
+ type: att.type
470
+ };
471
+ attributesCtxt.push(expandedAttr);
472
+ if (att.object_id !== undefined) {
473
+ let expandedAttrByObjectId = {
474
+ name: att.object_id,
475
+ value: att.expression,
465
476
  type: att.type
466
477
  };
467
- attributesCtxt.push(expandedAttr);
468
- ctxt = expressionPlugin.extractContext(
469
- attributesCtxt.concat(idTypeSSSList),
470
- typeInformation
471
- );
478
+ attributesCtxt.push(expandedAttrByObjectId);
472
479
  }
480
+ ctxt = expressionPlugin.extractContext(attributesCtxt.concat(idTypeSSSList));
473
481
  }
474
482
  });
475
483
  }
476
484
  // calculate expression for explicitAttrs
477
485
  try {
486
+ logger.debug(context, 'sendUpdateValueNgsi2 selectedAttrs ctxt %j', ctxt);
478
487
  let res = jexlParser.applyExpression(typeInformation.explicitAttrs, ctxt, typeInformation);
479
488
  if (res === true) {
480
489
  // like explicitAttrs == true
@@ -500,7 +509,7 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
500
509
  // implies do nothing
501
510
  logger.info(
502
511
  context,
503
- 'sendUpdateValueNgsi2 \n none selectedAttrs with %j and ctxt %j',
512
+ 'sendUpdateValueNgsi2 none selectedAttrs with %j and ctxt %j',
504
513
  typeInformation.explicitAttrs,
505
514
  ctxt
506
515
  );
@@ -514,19 +523,42 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
514
523
  if (selectedAttrs.includes(attr.name)) {
515
524
  selectedAttrs.push(attr.object_id);
516
525
  }
526
+ // Check if selectedAttrs includes an attribute with format {object_id: xxxx}
527
+ if (selectedAttrs.includes({ object_id: attr.object_id })) {
528
+ selectedAttrs.push(attr.object_id);
529
+ }
517
530
  });
518
531
  } else if (typeInformation.explicitAttrs && typeof typeInformation.explicitAttrs === 'boolean') {
519
- // TBD: selectedAttrs could be a boolean as a result of applyExpression
520
- // explicitAtts is true => Add measures which are defined attributes
532
+ // explicitAtts is true => Add just measures which are defined in active attributes
533
+ // and active attributes with expressions
534
+ // and TimeInstant
535
+ selectedAttrs = ['TimeInstant'];
521
536
  typeInformation.active.forEach((attr) => {
522
- selectedAttrs.push(attr.name);
523
- selectedAttrs.push(attr.object_id);
537
+ // Measures
538
+ if (attr.expression !== undefined) {
539
+ selectedAttrs.push(attr.name);
540
+ selectedAttrs.push(attr.object_id);
541
+ } else {
542
+ // check if active attr is receiving a measure
543
+ let i = 0;
544
+ let found = false;
545
+ while (i < attributes.length && !found) {
546
+ if (attributes[i].name && attributes[i].type) {
547
+ if (attributes[i].name === attr.object_id || attributes[i].name === attr.name) {
548
+ selectedAttrs.push(attr.name);
549
+ selectedAttrs.push(attr.object_id);
550
+ found = true;
551
+ }
552
+ }
553
+ i++;
554
+ }
555
+ }
524
556
  });
525
557
  }
526
558
  // This loop adds selected measured values (attributes) into payload entities (entity[0])
527
559
  for (let i = 0; i < attributes.length; i++) {
528
560
  if (attributes[i].name && selectedAttrs.includes(attributes[i].name) && attributes[i].type) {
529
- let attr = typeInformation.active.find((obj) => {
561
+ const attr = typeInformation.active.find((obj) => {
530
562
  return obj.name === attributes[i].name;
531
563
  });
532
564
  payload.entities[0][attributes[i].name] = {
@@ -542,7 +574,7 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
542
574
  payload.entities[0][attributes[i].name].metadata = metadata;
543
575
  }
544
576
  } else if (attributes[i].name && !selectedAttrs.includes(attributes[i].name) && attributes[i].type) {
545
- let att = {
577
+ const att = {
546
578
  name: attributes[i].name,
547
579
  type: attributes[i].type,
548
580
  value: attributes[i].value
@@ -552,10 +584,15 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
552
584
  }
553
585
  logger.debug(
554
586
  context,
555
- 'sendUpdateValueNgsi2 \n pre-initial explicitAttrs payload=%j \n selectedAttrs',
587
+ 'sendUpdateValueNgsi2 pre-initial explicitAttrs payload=%j selectedAttrs=%j',
556
588
  payload,
557
589
  selectedAttrs
558
590
  );
591
+ let selectedAttrsByObjectId = selectedAttrs
592
+ .filter((o) => o !== undefined && o.object_id)
593
+ .map(function (el) {
594
+ return el.object_id;
595
+ });
559
596
 
560
597
  // Loop for add seleted attrs from type.information.active into pyaload entities (entity[0])
561
598
  if (typeInformation.active) {
@@ -577,13 +614,23 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
577
614
  type: attr.type
578
615
  };
579
616
  }
617
+ } else if (attr.object_id !== undefined && selectedAttrsByObjectId.includes(attr.object_id)) {
618
+ payload.entities[0][attr.object_id] = {
619
+ value: payload.entities[0][attr.object_id]
620
+ ? payload.entities[0][attr.object_id].value
621
+ : payload.entities[0][attr.name]
622
+ ? payload.entities[0][attr.name].value
623
+ : undefined,
624
+ type: attr.type,
625
+ object_id: attr.object_id
626
+ };
580
627
  }
581
628
  });
582
629
  }
583
630
  } // END check explicitAttrs
584
- logger.debug(context, 'sendUpdateValueNgsi2 \n initial payload=%j', payload);
631
+ logger.debug(context, 'sendUpdateValueNgsi2 initial payload=%j', payload);
585
632
 
586
- let currentEntity = payload.entities[0];
633
+ const currentEntity = payload.entities[0];
587
634
 
588
635
  // Prepare attributes for expresionPlugin
589
636
  const attsArray = pluginUtils.extractAttributesArrayFromNgsi2Entity(currentEntity);
@@ -608,11 +655,11 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
608
655
  }
609
656
  attributesCtxt = attributesCtxt.concat(idTypeSSSList);
610
657
  let ctxt = expressionPlugin.extractContext(attributesCtxt, typeInformation);
611
- logger.debug(context, 'sendUpdateValueNgsi2 \n initial ctxt %j ', ctxt);
658
+ logger.debug(context, 'sendUpdateValueNgsi2 initial ctxt %j ', ctxt);
612
659
 
613
660
  // Sort currentEntity to get first attrs without expressions (checking attrs in typeInformation.active)
614
661
  // attributes without expressions should be processed before
615
- logger.debug(context, 'sendUpdateValueNgsi2 \n currentEntity %j ', currentEntity);
662
+ logger.debug(context, 'sendUpdateValueNgsi2 currentEntity %j ', currentEntity);
616
663
  if (typeInformation.active && typeInformation.active.length > 0) {
617
664
  for (const k in currentEntity) {
618
665
  typeInformation.active.forEach(function (att) {
@@ -620,14 +667,32 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
620
667
  (att.object_id && att.object_id === k && att.expression) ||
621
668
  (att.name && att.name === k && att.expression)
622
669
  ) {
623
- let m = currentEntity[k];
670
+ const m = currentEntity[k];
624
671
  delete currentEntity[k];
625
672
  currentEntity[k] = m; // put into the end of currentEntity
626
673
  }
627
674
  });
628
675
  }
629
676
  }
630
- logger.debug(context, 'sendUpdateValueNgsi2 \n currentEntity sorted %j ', currentEntity);
677
+
678
+ // Evaluate entityNameExp with a context including measures
679
+ if (typeInformation.entityNameExp !== undefined && typeInformation.entityNameExp !== '') {
680
+ try {
681
+ logger.debug(context, 'sendUpdateValueNgsi2 entityNameExp %j ', typeInformation.entityNameExp);
682
+ entityName = expressionPlugin.applyExpression(typeInformation.entityNameExp, ctxt, typeInformation);
683
+ payload.entities[0].id = entityName;
684
+ ctxt['entity_name'] = entityName;
685
+ } catch (e) {
686
+ logger.debug(
687
+ context,
688
+ 'Error evaluating expression for entityName: %s with context: %s',
689
+ typeInformation.entityNameExp,
690
+ ctxt
691
+ );
692
+ }
693
+ }
694
+
695
+ logger.debug(context, 'sendUpdateValueNgsi2 currentEntity sorted %j ', currentEntity);
631
696
  let timestampValue = undefined;
632
697
  // Loop for each final attribute to apply alias, multientity and expressions
633
698
  for (const j in currentEntity) {
@@ -660,7 +725,7 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
660
725
  // invalid mapping
661
726
  logger.debug(
662
727
  context,
663
- 'sendUpdateValueNgsi2 \n invalid mapping for attr %j \n newAttr %j',
728
+ 'sendUpdateValueNgsi2 invalid mapping for attr=%j newAttr=%j',
664
729
  attr,
665
730
  newAttr
666
731
  );
@@ -668,12 +733,12 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
668
733
  attr = undefined; // stop processing attr
669
734
  newAttr = undefined;
670
735
  } else {
671
- ctxt[attr.name] = payload.entities[0][j]['value'];
736
+ ctxt[attr.name] = payload.entities[0][j].value;
672
737
  }
673
738
  }
674
739
  logger.debug(
675
740
  context,
676
- 'sendUpdateValueNgsi2 \n procesing j %j attr %j ctxt %j \n newAttr %j ',
741
+ 'sendUpdateValueNgsi2 procesing j=%j attr=%j ctxt=%j newAttr=%j ',
677
742
  j,
678
743
  attr,
679
744
  ctxt,
@@ -687,7 +752,7 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
687
752
  if (attr && attr.expression) {
688
753
  logger.debug(
689
754
  context,
690
- 'sendUpdateValueNgsi2 \n apply expression %j \n over ctxt %j \n and device %j',
755
+ 'sendUpdateValueNgsi2 apply expression=%j over ctxt=%j and device=%j',
691
756
  attr.expression,
692
757
  ctxt,
693
758
  typeInformation
@@ -696,58 +761,43 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
696
761
  try {
697
762
  if (expressionPlugin.contextAvailable(attr.expression, ctxt, typeInformation)) {
698
763
  res = expressionPlugin.applyExpression(attr.expression, ctxt, typeInformation);
764
+ if (
765
+ // By default undefined is equivalent to null: should not progress
766
+ (attr.skipValue === undefined && res === null) ||
767
+ (attr.skipValue !== undefined && res === attr.skipValue)
768
+ ) {
769
+ logger.debug(
770
+ context,
771
+ 'sendUpdateValueNgsi2 skip value=%j for res=%j with expression=%j',
772
+ attr.skipValue,
773
+ res,
774
+ attr.expression
775
+ );
776
+ delete payload.entities[0][j]; // remove measure attr
777
+ attr = undefined; // stop process attr
778
+ }
699
779
  } else {
700
- logger.warn(
780
+ logger.info(
701
781
  context,
702
- 'sendUpdateValueNgsi2 \n no context available for apply expression %j \n',
782
+ 'sendUpdateValueNgsi2 no context available for apply expression=%j',
703
783
  attr.expression
704
784
  );
705
785
  res = newAttr.value; // keep newAttr value
706
786
  }
707
787
  } catch (e) {
708
- logger.error(context, 'sendUpdateValueNgsi2 \n apply expression exception %j \n', e);
709
- if (!expressionPlugin.checkJexl(typeInformation)) {
710
- return callback(e); // just throw error with legacy parser for backward compatiblity
711
- } else {
712
- res = ctxt[attr.name]; // TBD: add reference to test
788
+ logger.error(context, 'sendUpdateValueNgsi2 apply expression exception=%j', e);
789
+ if (attr && attr.name) {
790
+ res = ctxt[attr.name];
713
791
  }
714
792
  }
715
- if (!expressionPlugin.checkJexl(typeInformation)) {
716
- // legacy expression plugin: Involves some legacy checks performed by applierExpression
717
- if (
718
- res &&
719
- res !== 'undefined' &&
720
- res !== 'null' &&
721
- (typeof res === 'string' || res instanceof String) &&
722
- !res.includes('NaN')
723
- ) {
724
- newAttr.value = res;
725
- if (newAttr.type === 'Number' && isFloat(newAttr.value)) {
726
- newAttr.value = Number.parseFloat(newAttr.value);
727
- } else if (newAttr.type === 'Number' && !Number.isNaN(Number.parseInt(newAttr.value))) {
728
- newAttr.value = Number.parseInt(newAttr.value);
729
- } else if (newAttr.type === 'Boolean') {
730
- newAttr.value = newAttr.value === 'true' || newAttr.value === '1';
731
- } else if (newAttr.type === 'None') {
732
- newAttr.value = null;
733
- }
734
- } else if (isNaN(res) || res === 'undefined' || res === 'null') {
735
- logger.debug(
736
- context,
737
- 'sendUpdateValueNgsi2 \n apply expression result: isNaN || undefined || null'
738
- );
739
- if (res === 'null' && newAttr.type === 'None') {
740
- newAttr.value = null;
741
- } else {
742
- delete payload.entities[0][j]; // remove measure attr
743
- attr = undefined; // stop process attr
744
- }
745
- }
746
- } else {
747
- // jexl expression plugin
748
- newAttr.value = res;
793
+ // jexl expression plugin
794
+ newAttr.value = res;
795
+
796
+ logger.debug(context, 'sendUpdateValueNgsi2 apply expression result=%j newAttr=%j', res, newAttr);
797
+ // update current context with value
798
+ if (attr && attr.name && ctxt[attr.name] !== undefined) {
799
+ ctxt[attr.name] = newAttr.value;
749
800
  }
750
- logger.debug(context, 'sendUpdateValueNgsi2 \n apply expression result %j \n newAttr %j', res, newAttr);
751
801
  }
752
802
 
753
803
  // Apply Multientity: entity_type and entity_name in attributes are in typeInformation.active
@@ -759,21 +809,21 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
759
809
  if (expressionPlugin.contextAvailable(attr.entity_name, ctxt, typeInformation)) {
760
810
  newEntityName = expressionPlugin.applyExpression(attr.entity_name, ctxt, typeInformation);
761
811
  } else {
762
- logger.warn(
812
+ logger.info(
763
813
  context,
764
- 'sendUpdateValueNgsi2 \n MULTI no context available for apply expression %j \n',
814
+ 'sendUpdateValueNgsi2 MULTI no context available for apply expression=%j',
765
815
  attr.entity_name
766
816
  );
767
817
  newEntityName = attr.entity_name;
768
818
  }
769
819
  newEntityName = newEntityName ? newEntityName : attr.entity_name;
770
820
  } catch (e) {
771
- logger.error(context, 'sendUpdateValueNgsi2 \n MULTI apply expression exception %j \n', e);
821
+ logger.error(context, 'sendUpdateValueNgsi2 MULTI apply expression exception=%j', e);
772
822
  newEntityName = attr.entity_name;
773
823
  }
774
824
  logger.debug(
775
825
  context,
776
- 'sendUpdateValueNgsi2 \n MULTI apply expression %j \n result %j \n payload %j',
826
+ 'sendUpdateValueNgsi2 MULTI apply expression=%j result=%j payload=%j',
777
827
  attr.entity_name,
778
828
  newEntityName,
779
829
  payload
@@ -785,7 +835,7 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
785
835
  type: attr.entity_type ? attr.entity_type : payload.entities[0].type
786
836
  };
787
837
  // Check if there is already a newEntity created
788
- let alreadyEntity = payload.entities.find((entity) => {
838
+ const alreadyEntity = payload.entities.find((entity) => {
789
839
  return entity.id === newEntity.id && entity.type === newEntity.type;
790
840
  });
791
841
  if (alreadyEntity) {
@@ -802,7 +852,7 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
802
852
  : timestampValue !== undefined
803
853
  ) {
804
854
  newEntity = addTimestampNgsi2(newEntity, typeInformation.timezone, timestampValue);
805
- logger.debug(context, 'sendUpdateValueNgsi2 \n timestamped newEntity=%j', newEntity);
855
+ logger.debug(context, 'sendUpdateValueNgsi2 timestamped newEntity=%j', newEntity);
806
856
  }
807
857
  payload.entities.push(newEntity);
808
858
  }
@@ -810,7 +860,7 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
810
860
  if (attr.name !== j) {
811
861
  logger.debug(
812
862
  context,
813
- 'sendUpdateValueNgsi2 \n MULTI remove measure attr %j keep alias j %j from %j \n',
863
+ 'sendUpdateValueNgsi2 MULTI remove measure attr=%j keep alias j=%j from %j',
814
864
  j,
815
865
  attr,
816
866
  payload
@@ -828,7 +878,7 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
828
878
  }
829
879
  }
830
880
  if (newAttr && newAttr.type === constants.TIMESTAMP_TYPE_NGSI2 && newAttr.value) {
831
- let extendedTime = compressTimestampPlugin.fromBasicToExtended(newAttr.value);
881
+ const extendedTime = compressTimestampPlugin.fromBasicToExtended(newAttr.value);
832
882
  if (extendedTime) {
833
883
  // TBD: there is not flag about compressTimestamp in iotagent-node-lib,
834
884
  // but there is one in agents
@@ -840,7 +890,7 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
840
890
  timestampValue = newAttr.value;
841
891
  logger.debug(
842
892
  context,
843
- 'sendUpdateValueNgsi2 \n newAttr is TimeInstant and new payload=%j',
893
+ 'sendUpdateValueNgsi2 newAttr is TimeInstant and new payload=%j',
844
894
  payload
845
895
  );
846
896
  }
@@ -852,7 +902,7 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
852
902
  newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].type === constants.TIMESTAMP_TYPE_NGSI2 &&
853
903
  newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].value
854
904
  ) {
855
- let extendedTime = compressTimestampPlugin.fromBasicToExtended(
905
+ const extendedTime = compressTimestampPlugin.fromBasicToExtended(
856
906
  newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].value
857
907
  );
858
908
  if (extendedTime) {
@@ -865,7 +915,7 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
865
915
  // final attr loop
866
916
  logger.debug(
867
917
  context,
868
- 'sendUpdateValueNgsi2 \n after procesing attr %j \n current entity %j \n current payload=%j',
918
+ 'sendUpdateValueNgsi2 after procesing attr=%j current entity=%j current payload=%j',
869
919
  j,
870
920
  currentEntity,
871
921
  payload
@@ -909,10 +959,10 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
909
959
  }
910
960
  }
911
961
  }
912
- logger.debug(context, 'sendUpdateValueNgsi2 \n ending payload=%j', payload);
962
+ logger.debug(context, 'sendUpdateValueNgsi2 ending payload=%j', payload);
913
963
 
914
964
  for (let m = 0; m < payload.entities.length; m++) {
915
- for (var key in payload.entities[m]) {
965
+ for (const key in payload.entities[m]) {
916
966
  // purge object_id from payload
917
967
  if (payload.entities[m][key] && payload.entities[m][key].object_id) {
918
968
  delete payload.entities[m][key].object_id;
@@ -920,7 +970,7 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
920
970
  }
921
971
  payload.entities[m] = NGSIUtils.castJsonNativeAttributes(payload.entities[m]); // native types
922
972
  }
923
- logger.debug(context, 'sendUpdateValueNgsi2 \n payload with native types and without object_id %j', payload);
973
+ logger.debug(context, 'sendUpdateValueNgsi2 payload with native types and without object_id=%j', payload);
924
974
 
925
975
  options.json = payload;
926
976
 
@@ -937,18 +987,24 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
937
987
  // only in the first case
938
988
  if (options.json.entities.length === 1) {
939
989
  // recreate options object to use single entity update
940
- url = '/v2/entities/' + entityName + '/attrs';
941
- if (typeInformation && typeInformation.type) {
942
- url += '?type=' + typeInformation.type;
990
+ url = '/v2/entities';
991
+ if (config.getConfig().appendMode === false) {
992
+ url += '/' + entityName + '/attrs';
993
+ if (typeInformation && typeInformation.type) {
994
+ url += '?type=' + typeInformation.type;
995
+ }
996
+ } else {
997
+ // appendMode === true
998
+ url += '?options=upsert';
943
999
  }
944
1000
  options = NGSIUtils.createRequestObject(url, typeInformation, token);
945
1001
  options.json = payload.entities[0];
946
- delete options.json.id;
947
- delete options.json.type;
948
- if (config.getConfig().appendMode === true) {
949
- options.method = 'POST';
950
- } else {
1002
+ if (config.getConfig().appendMode === false) {
951
1003
  options.method = 'PATCH';
1004
+ delete options.json.id;
1005
+ delete options.json.type;
1006
+ } else {
1007
+ options.method = 'POST';
952
1008
  }
953
1009
  } // else: keep current options object created for a batch update
954
1010
  logger.debug(context, 'Updating device value in the Context Broker at [%s]', options.url);
@@ -969,6 +1025,10 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
969
1025
  }
970
1026
 
971
1027
  exports.sendQueryValue = sendQueryValueNgsi2;
972
- exports.sendUpdateValue = sendUpdateValueNgsi2;
1028
+ exports.sendUpdateValue = function (entityName, attributes, typeInformation, token, callback) {
1029
+ NGSIUtils.applyMiddlewares(NGSIUtils.updateMiddleware, attributes, typeInformation, () => {
1030
+ return sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, callback);
1031
+ });
1032
+ };
973
1033
  exports.addTimestamp = addTimestampNgsi2;
974
1034
  exports.formatGeoAttrs = formatGeoAttrs;
@@ -62,7 +62,6 @@ const provisioningAPITranslation = {
62
62
  static_attributes: 'staticAttributes',
63
63
  autoprovision: 'autoprovision',
64
64
  explicitAttrs: 'explicitAttrs',
65
- expressionLanguage: 'expressionLanguage',
66
65
  ngsiVersion: 'ngsiVersion',
67
66
  entityNameExp: 'entityNameExp'
68
67
  };
@@ -142,7 +141,6 @@ function handleProvision(req, res, next) {
142
141
  internalId: null,
143
142
  autoprovision: body.autoprovision,
144
143
  explicitAttrs: body.explicitAttrs,
145
- expressionLanguage: body.expressionLanguage,
146
144
  ngsiVersion: body.ngsiVersion
147
145
  });
148
146
  }
@@ -181,6 +179,7 @@ function attributeToProvisioningAPIFormat(attribute) {
181
179
  name: attribute.name,
182
180
  type: attribute.type,
183
181
  expression: attribute.expression,
182
+ skipValue: attribute.skipValue,
184
183
  reverse: attribute.reverse,
185
184
  entity_name: attribute.entity_name,
186
185
  entity_type: attribute.entity_type,
@@ -218,7 +217,6 @@ function toProvisioningAPIFormat(device) {
218
217
  internal_attributes: device.internalAttributes,
219
218
  protocol: device.protocol,
220
219
  autoprovision: device.autoprovision,
221
- expressionLanguage: device.expressionLanguage,
222
220
  explicitAttrs: device.explicitAttrs,
223
221
  ngsiVersion: device.ngsiVersion
224
222
  };
@@ -352,20 +350,22 @@ function handleUpdateDevice(req, res, next) {
352
350
  if (req.body.entity_name || req.body.entity_type) {
353
351
  isTypeOrNameUpdated = true;
354
352
  }
355
- async.waterfall([apply(applyUpdatingHandler, newDevice)], function handleUpdating(
356
- error,
357
- newDeviceUpdated
358
- ) {
359
- deviceService.updateRegister(newDeviceUpdated, isTypeOrNameUpdated, function handleDeviceUpdate(
360
- error
361
- ) {
362
- if (error) {
363
- next(error);
364
- } else {
365
- res.status(204).json({});
366
- }
367
- });
368
- });
353
+ async.waterfall(
354
+ [apply(applyUpdatingHandler, newDevice)],
355
+ function handleUpdating(error, newDeviceUpdated) {
356
+ deviceService.updateRegister(
357
+ newDeviceUpdated,
358
+ isTypeOrNameUpdated,
359
+ function handleDeviceUpdate(error) {
360
+ if (error) {
361
+ next(error);
362
+ } else {
363
+ res.status(204).json({});
364
+ }
365
+ }
366
+ );
367
+ }
368
+ );
369
369
  } else {
370
370
  next(new errors.DeviceNotFound(req.params.deviceId));
371
371
  }