iotagent-node-lib 3.0.0 → 3.2.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 (122) hide show
  1. package/.github/workflows/ci.yml +1 -0
  2. package/config.js +5 -5
  3. package/doc/api.md +1540 -298
  4. package/doc/deprecated.md +3 -1
  5. package/doc/development.md +120 -0
  6. package/doc/installationguide.md +3 -6
  7. package/docker-compose-dev.yml +1 -1
  8. package/lib/commonConfig.js +7 -10
  9. package/lib/fiware-iotagent-lib.js +0 -10
  10. package/lib/jexlTranformsMap.js +2 -1
  11. package/lib/plugins/bidirectionalData.js +8 -26
  12. package/lib/plugins/expressionPlugin.js +8 -40
  13. package/lib/plugins/jexlParser.js +28 -0
  14. package/lib/services/commands/commandService.js +1 -1
  15. package/lib/services/devices/deviceService.js +2 -1
  16. package/lib/services/ngsi/entities-NGSI-LD.js +15 -73
  17. package/lib/services/ngsi/entities-NGSI-v2.js +149 -124
  18. package/lib/services/northBound/deviceProvisioningServer.js +17 -14
  19. package/lib/templates/createDevice.json +5 -2
  20. package/lib/templates/createDeviceLax.json +7 -5
  21. package/lib/templates/updateDevice.json +5 -2
  22. package/lib/templates/updateDeviceLax.json +3 -5
  23. package/package.json +2 -2
  24. package/test/unit/examples/deviceProvisioningRequests/provisionBidirectionalDevice.json +5 -5
  25. package/test/unit/examples/deviceProvisioningRequests/provisionMinimumDevice4.json +1 -0
  26. package/test/unit/examples/groupProvisioningRequests/bidirectionalGroup.json +4 -4
  27. package/test/unit/general/config-multi-core-test.js +2 -1
  28. package/test/unit/general/contextBrokerKeystoneSecurityAccess-test.js +2 -1
  29. package/test/unit/general/deviceService-test.js +2 -1
  30. package/test/unit/general/loglevel-api_test.js +2 -1
  31. package/test/unit/general/startup-test.js +2 -1
  32. package/test/unit/general/statistics-persistence_test.js +1 -0
  33. package/test/unit/general/statistics-service_test.js +1 -0
  34. package/test/unit/lazyAndCommands/commandRegistry_test.js +1 -0
  35. package/test/unit/memoryRegistry/deviceRegistryMemory_test.js +1 -0
  36. package/test/unit/mongodb/mongodb-connectionoptions-test.js +1 -0
  37. package/test/unit/mongodb/mongodb-group-registry-test.js +1 -0
  38. package/test/unit/mongodb/mongodb-registry-test.js +2 -1
  39. package/test/unit/ngsi-ld/expressions/jexlBasedTransformations-test.js +0 -2
  40. package/test/unit/ngsi-ld/general/config-jsonld-contexts-test.js +2 -1
  41. package/test/unit/ngsi-ld/general/contextBrokerOAuthSecurityAccess-test.js +2 -1
  42. package/test/unit/ngsi-ld/general/deviceService-test.js +2 -1
  43. package/test/unit/ngsi-ld/general/https-support-test.js +2 -1
  44. package/test/unit/ngsi-ld/general/iotam-autoregistration-test.js +2 -1
  45. package/test/unit/ngsi-ld/general/startup-test.js +3 -2
  46. package/test/unit/ngsi-ld/lazyAndCommands/active-devices-attribute-update-test.js +2 -1
  47. package/test/unit/ngsi-ld/lazyAndCommands/command-test.js +5 -7
  48. package/test/unit/ngsi-ld/lazyAndCommands/lazy-devices-test.js +8 -6
  49. package/test/unit/ngsi-ld/lazyAndCommands/merge-patch-test.js +18 -22
  50. package/test/unit/ngsi-ld/lazyAndCommands/polling-commands-test.js +2 -1
  51. package/test/unit/ngsi-ld/ngsiService/active-devices-test.js +2 -1
  52. package/test/unit/ngsi-ld/ngsiService/autocast-test.js +2 -1
  53. package/test/unit/ngsi-ld/ngsiService/geoproperties-test.js +2 -1
  54. package/test/unit/ngsi-ld/ngsiService/languageProperties-test.js +32 -34
  55. package/test/unit/ngsi-ld/ngsiService/staticAttributes-test.js +2 -1
  56. package/test/unit/ngsi-ld/ngsiService/subscriptions-test.js +2 -1
  57. package/test/unit/ngsi-ld/ngsiService/unsupported-endpoints-test.js +11 -15
  58. package/test/unit/ngsi-ld/plugins/alias-plugin_test.js +2 -1
  59. package/test/unit/ngsi-ld/plugins/bidirectional-plugin_test.js +10 -9
  60. package/test/unit/ngsi-ld/plugins/compress-timestamp-plugin_test.js +2 -1
  61. package/test/unit/ngsi-ld/plugins/custom-plugin_test.js +152 -0
  62. package/test/unit/ngsi-ld/plugins/multientity-plugin_test.js +3 -2
  63. package/test/unit/ngsi-ld/plugins/timestamp-processing-plugin_test.js +2 -1
  64. package/test/unit/ngsi-ld/provisioning/device-provisioning-api_test.js +1 -0
  65. package/test/unit/ngsi-ld/provisioning/device-registration_test.js +2 -1
  66. package/test/unit/ngsi-ld/provisioning/device-update-registration_test.js +15 -12
  67. package/test/unit/ngsi-ld/provisioning/listProvisionedDevices-test.js +1 -0
  68. package/test/unit/ngsi-ld/provisioning/provisionDeviceMultientity-test.js +1 -0
  69. package/test/unit/ngsi-ld/provisioning/removeProvisionedDevice-test.js +1 -0
  70. package/test/unit/ngsi-ld/provisioning/singleConfigurationMode-test.js +1 -0
  71. package/test/unit/ngsi-ld/provisioning/updateProvisionedDevices-test.js +1 -0
  72. package/test/unit/ngsi-mixed/provisioning/ngsi-versioning-test.js +13 -7
  73. package/test/unit/ngsiv2/examples/contextRequests/createMinimumProvisionedDevice4.json +5 -1
  74. package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin31.json +0 -8
  75. package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin32.json +6 -0
  76. package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin35.json +20 -0
  77. package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin40.json +42 -0
  78. package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin41.json +32 -0
  79. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin25.json +37 -0
  80. package/test/unit/ngsiv2/examples/contextRequests/updateContextProcessTimestamp.json +7 -1
  81. package/test/unit/ngsiv2/examples/contextRequests/updateContextTimestampOverride.json +7 -1
  82. package/test/unit/ngsiv2/examples/contextRequests/updateContextTimestampOverrideWithoutMilis.json +7 -1
  83. package/test/unit/ngsiv2/expressions/jexlBasedTransformations-test.js +232 -8
  84. package/test/unit/ngsiv2/general/contextBrokerOAuthSecurityAccess-test.js +2 -1
  85. package/test/unit/ngsiv2/general/deviceService-test.js +2 -1
  86. package/test/unit/ngsiv2/general/https-support-test.js +2 -1
  87. package/test/unit/ngsiv2/general/iotam-autoregistration-test.js +2 -1
  88. package/test/unit/ngsiv2/general/startup-test.js +2 -1
  89. package/test/unit/ngsiv2/lazyAndCommands/active-devices-attribute-update-test.js +2 -1
  90. package/test/unit/ngsiv2/lazyAndCommands/command-test.js +2 -1
  91. package/test/unit/ngsiv2/lazyAndCommands/lazy-devices-test.js +8 -6
  92. package/test/unit/ngsiv2/lazyAndCommands/polling-commands-test.js +3 -3
  93. package/test/unit/ngsiv2/ngsiService/active-devices-test.js +3 -2
  94. package/test/unit/ngsiv2/ngsiService/autocast-test.js +2 -1
  95. package/test/unit/ngsiv2/ngsiService/queryDeviceInformationInCb-test.js +2 -1
  96. package/test/unit/ngsiv2/ngsiService/staticAttributes-test.js +2 -1
  97. package/test/unit/ngsiv2/ngsiService/subscriptions-test.js +2 -1
  98. package/test/unit/ngsiv2/plugins/alias-plugin_test.js +2 -1
  99. package/test/unit/ngsiv2/plugins/bidirectional-plugin_test.js +8 -7
  100. package/test/unit/ngsiv2/plugins/compress-timestamp-plugin_test.js +2 -1
  101. package/test/unit/ngsiv2/plugins/custom-plugin_test.js +151 -0
  102. package/test/unit/ngsiv2/plugins/multientity-plugin_test.js +87 -13
  103. package/test/unit/ngsiv2/plugins/timestamp-processing-plugin_test.js +2 -1
  104. package/test/unit/ngsiv2/provisioning/device-group-api-test.js +1 -0
  105. package/test/unit/ngsiv2/provisioning/device-group-utils-test.js +1 -0
  106. package/test/unit/ngsiv2/provisioning/device-provisioning-api_test.js +12 -3
  107. package/test/unit/ngsiv2/provisioning/device-registration_test.js +2 -1
  108. package/test/unit/ngsiv2/provisioning/device-update-registration_test.js +15 -12
  109. package/test/unit/ngsiv2/provisioning/listProvisionedDevices-test.js +1 -0
  110. package/test/unit/ngsiv2/provisioning/provisionDeviceMultientity-test.js +1 -0
  111. package/test/unit/ngsiv2/provisioning/removeProvisionedDevice-test.js +1 -0
  112. package/test/unit/ngsiv2/provisioning/singleConfigurationMode-test.js +1 -0
  113. package/test/unit/ngsiv2/provisioning/updateProvisionedDevices-test.js +1 -0
  114. package/test/unit/plugins/capture-configuration-inPlugins_test.js +2 -1
  115. package/test/unit/plugins/capture-provision-inPlugins_test.js +2 -1
  116. package/doc/advanced-topics.md +0 -626
  117. package/doc/expressionLanguage.md +0 -762
  118. package/lib/plugins/expressionParser.js +0 -205
  119. package/test/unit/expressions/expression-test.js +0 -197
  120. package/test/unit/ngsi-ld/expressions/expressionBasedTransformations-test.js +0 -881
  121. package/test/unit/ngsiv2/expressions/expressionBasedTransformations-test.js +0 -950
  122. package/test/unit/ngsiv2/expressions/expressionCombinedTransformations-test.js +0 -294
@@ -103,7 +103,7 @@ function formatGeoAttrs(attr) {
103
103
  * @param Boolean skipMetadataAtt An optional flag to indicate if timestamp should be added to each metadata attribute. Default is false
104
104
  * @return {Object} NGSIv2 payload entities with timestamp
105
105
  */
106
- function addTimestampNgsi2(payload, timezone, timestampValue, skipMetadataAtt) {
106
+ function addTimestampNgsi2(payload, timezone, timestampValue) {
107
107
  function addTimestampEntity(entity, timezone, timestampValue) {
108
108
  const timestamp = {
109
109
  type: constants.TIMESTAMP_TYPE_NGSI2
@@ -111,12 +111,10 @@ function addTimestampNgsi2(payload, timezone, timestampValue, skipMetadataAtt) {
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) {
@@ -148,9 +146,7 @@ function addTimestampNgsi2(payload, timezone, timestampValue, skipMetadataAtt) {
148
146
  for (const key in entity) {
149
147
  /* eslint-disable-next-line no-prototype-builtins */
150
148
  if (entity.hasOwnProperty(key) && key !== 'id' && key !== 'type') {
151
- if (!skipMetadataAtt) {
152
- addMetadata(entity[key]);
153
- }
149
+ addMetadata(entity[key]);
154
150
  keyCount += 1;
155
151
  }
156
152
  }
@@ -327,16 +323,6 @@ function sendQueryValueNgsi2(entityName, attributes, typeInformation, token, cal
327
323
  );
328
324
  }
329
325
 
330
- /**
331
- * Determines if a value is of type float
332
- *
333
- * @param {String} value Value to be analyzed
334
- * @return {boolean} True if float, False otherwise.
335
- */
336
- function isFloat(value) {
337
- return !isNaN(value) && value.toString().indexOf('.') !== -1;
338
- }
339
-
340
326
  /**
341
327
  * Makes an update in the Device's entity in the context broker, with the values given in the 'attributes' array. This
342
328
  * array should comply to the NGSIv2's attribute format.
@@ -349,12 +335,12 @@ function isFloat(value) {
349
335
  function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, callback) {
350
336
  logger.debug(
351
337
  context,
352
- 'sendUpdateValueNgsi2 called with: \n entityName=%s \n attributes=%j \n typeInformation=%j',
338
+ 'sendUpdateValueNgsi2 called with: entityName=%s attributes=%j typeInformation=%j',
353
339
  entityName,
354
340
  attributes,
355
341
  typeInformation
356
342
  );
357
- let payload = {
343
+ const payload = {
358
344
  entities: [
359
345
  {
360
346
  id: entityName
@@ -384,8 +370,9 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
384
370
  callback(new errors.TypeNotFound(null, entityName));
385
371
  return;
386
372
  }
373
+
387
374
  let idTypeSSSList = pluginUtils.getIdTypeServSubServiceFromDevice(typeInformation);
388
- logger.debug(context, 'sendUpdateValueNgsi2 \n idTypeSSS are %j ', idTypeSSSList);
375
+ logger.debug(context, 'sendUpdateValueNgsi2 idTypeSSS are %j ', idTypeSSSList);
389
376
  let measureAttrsForCtxt = [];
390
377
 
391
378
  // Check explicitAttrs: adds all final needed attributes to payload
@@ -410,7 +397,7 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
410
397
  return;
411
398
  }
412
399
  }
413
- logger.debug(context, 'sendUpdateValueNgsi2 \n pre-initial non-explicitAttrs payload=%j', payload);
400
+ logger.debug(context, 'sendUpdateValueNgsi2 pre-initial non-explicitAttrs payload=%j', payload);
414
401
  // Loop for add attrs from type.information.active (and lazys?) into payload entities (entity[0])
415
402
  if (typeInformation.active) {
416
403
  typeInformation.active.forEach((attr) => {
@@ -437,7 +424,7 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
437
424
  if (typeof typeInformation.explicitAttrs === 'string') {
438
425
  // explicitAttrs is a jexlExpression
439
426
  // This ctxt should include all possible attrs
440
- let attributesCtxt = [];
427
+ const attributesCtxt = [];
441
428
  if (typeInformation.static) {
442
429
  typeInformation.static.forEach(function (att) {
443
430
  attributesCtxt.push(att);
@@ -446,37 +433,57 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
446
433
  // Measures
447
434
  for (let i = 0; i < attributes.length; i++) {
448
435
  if (attributes[i].name && attributes[i].type) {
449
- let measureAttr = {
436
+ const measureAttr = {
450
437
  name: attributes[i].name,
451
438
  value: attributes[i].value,
452
439
  type: attributes[i].type
453
440
  };
454
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
+ }
455
457
  }
456
458
  }
457
459
  // This context is just to calculate explicitAttrs when is an expression
458
- let ctxt = expressionPlugin.extractContext(attributesCtxt.concat(idTypeSSSList), typeInformation);
459
- // 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
460
463
  if (typeInformation.active) {
461
464
  typeInformation.active.forEach(function (att) {
462
- if (att.expression) {
463
- if (expressionPlugin.contextAvailable(att.expression, ctxt, typeInformation)) {
464
- let expandedAttr = {
465
- name: att.name,
466
- 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,
467
476
  type: att.type
468
477
  };
469
- attributesCtxt.push(expandedAttr);
470
- ctxt = expressionPlugin.extractContext(
471
- attributesCtxt.concat(idTypeSSSList),
472
- typeInformation
473
- );
478
+ attributesCtxt.push(expandedAttrByObjectId);
474
479
  }
480
+ ctxt = expressionPlugin.extractContext(attributesCtxt.concat(idTypeSSSList));
475
481
  }
476
482
  });
477
483
  }
478
484
  // calculate expression for explicitAttrs
479
485
  try {
486
+ logger.debug(context, 'sendUpdateValueNgsi2 selectedAttrs ctxt %j', ctxt);
480
487
  let res = jexlParser.applyExpression(typeInformation.explicitAttrs, ctxt, typeInformation);
481
488
  if (res === true) {
482
489
  // like explicitAttrs == true
@@ -502,7 +509,7 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
502
509
  // implies do nothing
503
510
  logger.info(
504
511
  context,
505
- 'sendUpdateValueNgsi2 \n none selectedAttrs with %j and ctxt %j',
512
+ 'sendUpdateValueNgsi2 none selectedAttrs with %j and ctxt %j',
506
513
  typeInformation.explicitAttrs,
507
514
  ctxt
508
515
  );
@@ -516,19 +523,42 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
516
523
  if (selectedAttrs.includes(attr.name)) {
517
524
  selectedAttrs.push(attr.object_id);
518
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
+ }
519
530
  });
520
531
  } else if (typeInformation.explicitAttrs && typeof typeInformation.explicitAttrs === 'boolean') {
521
- // TBD: selectedAttrs could be a boolean as a result of applyExpression
522
- // 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'];
523
536
  typeInformation.active.forEach((attr) => {
524
- selectedAttrs.push(attr.name);
525
- 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
+ }
526
556
  });
527
557
  }
528
558
  // This loop adds selected measured values (attributes) into payload entities (entity[0])
529
559
  for (let i = 0; i < attributes.length; i++) {
530
560
  if (attributes[i].name && selectedAttrs.includes(attributes[i].name) && attributes[i].type) {
531
- let attr = typeInformation.active.find((obj) => {
561
+ const attr = typeInformation.active.find((obj) => {
532
562
  return obj.name === attributes[i].name;
533
563
  });
534
564
  payload.entities[0][attributes[i].name] = {
@@ -544,7 +574,7 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
544
574
  payload.entities[0][attributes[i].name].metadata = metadata;
545
575
  }
546
576
  } else if (attributes[i].name && !selectedAttrs.includes(attributes[i].name) && attributes[i].type) {
547
- let att = {
577
+ const att = {
548
578
  name: attributes[i].name,
549
579
  type: attributes[i].type,
550
580
  value: attributes[i].value
@@ -554,10 +584,15 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
554
584
  }
555
585
  logger.debug(
556
586
  context,
557
- 'sendUpdateValueNgsi2 \n pre-initial explicitAttrs payload=%j \n selectedAttrs',
587
+ 'sendUpdateValueNgsi2 pre-initial explicitAttrs payload=%j selectedAttrs=%j',
558
588
  payload,
559
589
  selectedAttrs
560
590
  );
591
+ let selectedAttrsByObjectId = selectedAttrs
592
+ .filter((o) => o !== undefined && o.object_id)
593
+ .map(function (el) {
594
+ return el.object_id;
595
+ });
561
596
 
562
597
  // Loop for add seleted attrs from type.information.active into pyaload entities (entity[0])
563
598
  if (typeInformation.active) {
@@ -579,13 +614,23 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
579
614
  type: attr.type
580
615
  };
581
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
+ };
582
627
  }
583
628
  });
584
629
  }
585
630
  } // END check explicitAttrs
586
- logger.debug(context, 'sendUpdateValueNgsi2 \n initial payload=%j', payload);
631
+ logger.debug(context, 'sendUpdateValueNgsi2 initial payload=%j', payload);
587
632
 
588
- let currentEntity = payload.entities[0];
633
+ const currentEntity = payload.entities[0];
589
634
 
590
635
  // Prepare attributes for expresionPlugin
591
636
  const attsArray = pluginUtils.extractAttributesArrayFromNgsi2Entity(currentEntity);
@@ -610,11 +655,11 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
610
655
  }
611
656
  attributesCtxt = attributesCtxt.concat(idTypeSSSList);
612
657
  let ctxt = expressionPlugin.extractContext(attributesCtxt, typeInformation);
613
- logger.debug(context, 'sendUpdateValueNgsi2 \n initial ctxt %j ', ctxt);
658
+ logger.debug(context, 'sendUpdateValueNgsi2 initial ctxt %j ', ctxt);
614
659
 
615
660
  // Sort currentEntity to get first attrs without expressions (checking attrs in typeInformation.active)
616
661
  // attributes without expressions should be processed before
617
- logger.debug(context, 'sendUpdateValueNgsi2 \n currentEntity %j ', currentEntity);
662
+ logger.debug(context, 'sendUpdateValueNgsi2 currentEntity %j ', currentEntity);
618
663
  if (typeInformation.active && typeInformation.active.length > 0) {
619
664
  for (const k in currentEntity) {
620
665
  typeInformation.active.forEach(function (att) {
@@ -622,14 +667,15 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
622
667
  (att.object_id && att.object_id === k && att.expression) ||
623
668
  (att.name && att.name === k && att.expression)
624
669
  ) {
625
- let m = currentEntity[k];
670
+ const m = currentEntity[k];
626
671
  delete currentEntity[k];
627
672
  currentEntity[k] = m; // put into the end of currentEntity
628
673
  }
629
674
  });
630
675
  }
631
676
  }
632
- logger.debug(context, 'sendUpdateValueNgsi2 \n currentEntity sorted %j ', currentEntity);
677
+
678
+ logger.debug(context, 'sendUpdateValueNgsi2 currentEntity sorted %j ', currentEntity);
633
679
  let timestampValue = undefined;
634
680
  // Loop for each final attribute to apply alias, multientity and expressions
635
681
  for (const j in currentEntity) {
@@ -662,7 +708,7 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
662
708
  // invalid mapping
663
709
  logger.debug(
664
710
  context,
665
- 'sendUpdateValueNgsi2 \n invalid mapping for attr %j \n newAttr %j',
711
+ 'sendUpdateValueNgsi2 invalid mapping for attr=%j newAttr=%j',
666
712
  attr,
667
713
  newAttr
668
714
  );
@@ -670,12 +716,12 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
670
716
  attr = undefined; // stop processing attr
671
717
  newAttr = undefined;
672
718
  } else {
673
- ctxt[attr.name] = payload.entities[0][j]['value'];
719
+ ctxt[attr.name] = payload.entities[0][j].value;
674
720
  }
675
721
  }
676
722
  logger.debug(
677
723
  context,
678
- 'sendUpdateValueNgsi2 \n procesing j %j attr %j ctxt %j \n newAttr %j ',
724
+ 'sendUpdateValueNgsi2 procesing j=%j attr=%j ctxt=%j newAttr=%j ',
679
725
  j,
680
726
  attr,
681
727
  ctxt,
@@ -689,7 +735,7 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
689
735
  if (attr && attr.expression) {
690
736
  logger.debug(
691
737
  context,
692
- 'sendUpdateValueNgsi2 \n apply expression %j \n over ctxt %j \n and device %j',
738
+ 'sendUpdateValueNgsi2 apply expression=%j over ctxt=%j and device=%j',
693
739
  attr.expression,
694
740
  ctxt,
695
741
  typeInformation
@@ -698,58 +744,43 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
698
744
  try {
699
745
  if (expressionPlugin.contextAvailable(attr.expression, ctxt, typeInformation)) {
700
746
  res = expressionPlugin.applyExpression(attr.expression, ctxt, typeInformation);
747
+ if (
748
+ // By default undefined is equivalent to null: should not progress
749
+ (attr.skipValue === undefined && res === null) ||
750
+ (attr.skipValue !== undefined && res === attr.skipValue)
751
+ ) {
752
+ logger.debug(
753
+ context,
754
+ 'sendUpdateValueNgsi2 skip value=%j for res=%j with expression=%j',
755
+ attr.skipValue,
756
+ res,
757
+ attr.expression
758
+ );
759
+ delete payload.entities[0][j]; // remove measure attr
760
+ attr = undefined; // stop process attr
761
+ }
701
762
  } else {
702
- logger.warn(
763
+ logger.info(
703
764
  context,
704
- 'sendUpdateValueNgsi2 \n no context available for apply expression %j \n',
765
+ 'sendUpdateValueNgsi2 no context available for apply expression=%j',
705
766
  attr.expression
706
767
  );
707
768
  res = newAttr.value; // keep newAttr value
708
769
  }
709
770
  } catch (e) {
710
- logger.error(context, 'sendUpdateValueNgsi2 \n apply expression exception %j \n', e);
711
- if (!expressionPlugin.checkJexl(typeInformation)) {
712
- return callback(e); // just throw error with legacy parser for backward compatiblity
713
- } else {
714
- res = ctxt[attr.name]; // TBD: add reference to test
771
+ logger.error(context, 'sendUpdateValueNgsi2 apply expression exception=%j', e);
772
+ if (attr && attr.name) {
773
+ res = ctxt[attr.name];
715
774
  }
716
775
  }
717
- if (!expressionPlugin.checkJexl(typeInformation)) {
718
- // legacy expression plugin: Involves some legacy checks performed by applierExpression
719
- if (
720
- res &&
721
- res !== 'undefined' &&
722
- res !== 'null' &&
723
- (typeof res === 'string' || res instanceof String) &&
724
- !res.includes('NaN')
725
- ) {
726
- newAttr.value = res;
727
- if (newAttr.type === 'Number' && isFloat(newAttr.value)) {
728
- newAttr.value = Number.parseFloat(newAttr.value);
729
- } else if (newAttr.type === 'Number' && !Number.isNaN(Number.parseInt(newAttr.value))) {
730
- newAttr.value = Number.parseInt(newAttr.value);
731
- } else if (newAttr.type === 'Boolean') {
732
- newAttr.value = newAttr.value === 'true' || newAttr.value === '1';
733
- } else if (newAttr.type === 'None') {
734
- newAttr.value = null;
735
- }
736
- } else if (isNaN(res) || res === 'undefined' || res === 'null') {
737
- logger.debug(
738
- context,
739
- 'sendUpdateValueNgsi2 \n apply expression result: isNaN || undefined || null'
740
- );
741
- if (res === 'null' && newAttr.type === 'None') {
742
- newAttr.value = null;
743
- } else {
744
- delete payload.entities[0][j]; // remove measure attr
745
- attr = undefined; // stop process attr
746
- }
747
- }
748
- } else {
749
- // jexl expression plugin
750
- newAttr.value = res;
776
+ // jexl expression plugin
777
+ newAttr.value = res;
778
+
779
+ logger.debug(context, 'sendUpdateValueNgsi2 apply expression result=%j newAttr=%j', res, newAttr);
780
+ // update current context with value
781
+ if (attr && attr.name && ctxt[attr.name] !== undefined) {
782
+ ctxt[attr.name] = newAttr.value;
751
783
  }
752
- logger.debug(context, 'sendUpdateValueNgsi2 \n apply expression result %j \n newAttr %j', res, newAttr);
753
784
  }
754
785
 
755
786
  // Apply Multientity: entity_type and entity_name in attributes are in typeInformation.active
@@ -761,21 +792,21 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
761
792
  if (expressionPlugin.contextAvailable(attr.entity_name, ctxt, typeInformation)) {
762
793
  newEntityName = expressionPlugin.applyExpression(attr.entity_name, ctxt, typeInformation);
763
794
  } else {
764
- logger.warn(
795
+ logger.info(
765
796
  context,
766
- 'sendUpdateValueNgsi2 \n MULTI no context available for apply expression %j \n',
797
+ 'sendUpdateValueNgsi2 MULTI no context available for apply expression=%j',
767
798
  attr.entity_name
768
799
  );
769
800
  newEntityName = attr.entity_name;
770
801
  }
771
802
  newEntityName = newEntityName ? newEntityName : attr.entity_name;
772
803
  } catch (e) {
773
- logger.error(context, 'sendUpdateValueNgsi2 \n MULTI apply expression exception %j \n', e);
804
+ logger.error(context, 'sendUpdateValueNgsi2 MULTI apply expression exception=%j', e);
774
805
  newEntityName = attr.entity_name;
775
806
  }
776
807
  logger.debug(
777
808
  context,
778
- 'sendUpdateValueNgsi2 \n MULTI apply expression %j \n result %j \n payload %j',
809
+ 'sendUpdateValueNgsi2 MULTI apply expression=%j result=%j payload=%j',
779
810
  attr.entity_name,
780
811
  newEntityName,
781
812
  payload
@@ -787,7 +818,7 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
787
818
  type: attr.entity_type ? attr.entity_type : payload.entities[0].type
788
819
  };
789
820
  // Check if there is already a newEntity created
790
- let alreadyEntity = payload.entities.find((entity) => {
821
+ const alreadyEntity = payload.entities.find((entity) => {
791
822
  return entity.id === newEntity.id && entity.type === newEntity.type;
792
823
  });
793
824
  if (alreadyEntity) {
@@ -804,7 +835,7 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
804
835
  : timestampValue !== undefined
805
836
  ) {
806
837
  newEntity = addTimestampNgsi2(newEntity, typeInformation.timezone, timestampValue);
807
- logger.debug(context, 'sendUpdateValueNgsi2 \n timestamped newEntity=%j', newEntity);
838
+ logger.debug(context, 'sendUpdateValueNgsi2 timestamped newEntity=%j', newEntity);
808
839
  }
809
840
  payload.entities.push(newEntity);
810
841
  }
@@ -812,7 +843,7 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
812
843
  if (attr.name !== j) {
813
844
  logger.debug(
814
845
  context,
815
- 'sendUpdateValueNgsi2 \n MULTI remove measure attr %j keep alias j %j from %j \n',
846
+ 'sendUpdateValueNgsi2 MULTI remove measure attr=%j keep alias j=%j from %j',
816
847
  j,
817
848
  attr,
818
849
  payload
@@ -830,7 +861,7 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
830
861
  }
831
862
  }
832
863
  if (newAttr && newAttr.type === constants.TIMESTAMP_TYPE_NGSI2 && newAttr.value) {
833
- let extendedTime = compressTimestampPlugin.fromBasicToExtended(newAttr.value);
864
+ const extendedTime = compressTimestampPlugin.fromBasicToExtended(newAttr.value);
834
865
  if (extendedTime) {
835
866
  // TBD: there is not flag about compressTimestamp in iotagent-node-lib,
836
867
  // but there is one in agents
@@ -842,7 +873,7 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
842
873
  timestampValue = newAttr.value;
843
874
  logger.debug(
844
875
  context,
845
- 'sendUpdateValueNgsi2 \n newAttr is TimeInstant and new payload=%j',
876
+ 'sendUpdateValueNgsi2 newAttr is TimeInstant and new payload=%j',
846
877
  payload
847
878
  );
848
879
  }
@@ -854,7 +885,7 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
854
885
  newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].type === constants.TIMESTAMP_TYPE_NGSI2 &&
855
886
  newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].value
856
887
  ) {
857
- let extendedTime = compressTimestampPlugin.fromBasicToExtended(
888
+ const extendedTime = compressTimestampPlugin.fromBasicToExtended(
858
889
  newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].value
859
890
  );
860
891
  if (extendedTime) {
@@ -867,7 +898,7 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
867
898
  // final attr loop
868
899
  logger.debug(
869
900
  context,
870
- 'sendUpdateValueNgsi2 \n after procesing attr %j \n current entity %j \n current payload=%j',
901
+ 'sendUpdateValueNgsi2 after procesing attr=%j current entity=%j current payload=%j',
871
902
  j,
872
903
  currentEntity,
873
904
  payload
@@ -885,25 +916,15 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
885
916
  ) {
886
917
  if (timestampValue) {
887
918
  // timeInstant is provided as measure
888
- if (payload.entities.length > 1) {
919
+ if (payload.entities.length > 0) {
889
920
  for (let n = 0; n < payload.entities.length; n++) {
890
921
  // include metadata with TimeInstant in attrs when TimeInstant is provided as measure in all entities
891
922
  payload.entities[n] = addTimestampNgsi2(
892
923
  payload.entities[n],
893
924
  typeInformation.timezone,
894
- timestampValue,
895
- false // skipMetadataAtt
925
+ timestampValue
896
926
  );
897
927
  }
898
- } else {
899
- // Do not include metadata with TimeInstant in attrs when TimeInstant is provided as measure
900
- // and no more entities
901
- payload.entities[0] = addTimestampNgsi2(
902
- payload.entities[0],
903
- typeInformation.timezone,
904
- timestampValue,
905
- true // skipMetadataAtt
906
- );
907
928
  }
908
929
  } else {
909
930
  // jshint maxdepth:5
@@ -921,10 +942,10 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
921
942
  }
922
943
  }
923
944
  }
924
- logger.debug(context, 'sendUpdateValueNgsi2 \n ending payload=%j', payload);
945
+ logger.debug(context, 'sendUpdateValueNgsi2 ending payload=%j', payload);
925
946
 
926
947
  for (let m = 0; m < payload.entities.length; m++) {
927
- for (var key in payload.entities[m]) {
948
+ for (const key in payload.entities[m]) {
928
949
  // purge object_id from payload
929
950
  if (payload.entities[m][key] && payload.entities[m][key].object_id) {
930
951
  delete payload.entities[m][key].object_id;
@@ -932,7 +953,7 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
932
953
  }
933
954
  payload.entities[m] = NGSIUtils.castJsonNativeAttributes(payload.entities[m]); // native types
934
955
  }
935
- logger.debug(context, 'sendUpdateValueNgsi2 \n payload with native types and without object_id %j', payload);
956
+ logger.debug(context, 'sendUpdateValueNgsi2 payload with native types and without object_id=%j', payload);
936
957
 
937
958
  options.json = payload;
938
959
 
@@ -981,6 +1002,10 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
981
1002
  }
982
1003
 
983
1004
  exports.sendQueryValue = sendQueryValueNgsi2;
984
- exports.sendUpdateValue = sendUpdateValueNgsi2;
1005
+ exports.sendUpdateValue = function (entityName, attributes, typeInformation, token, callback) {
1006
+ NGSIUtils.applyMiddlewares(NGSIUtils.updateMiddleware, attributes, typeInformation, () => {
1007
+ return sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, callback);
1008
+ });
1009
+ };
985
1010
  exports.addTimestamp = addTimestampNgsi2;
986
1011
  exports.formatGeoAttrs = formatGeoAttrs;
@@ -181,6 +181,7 @@ function attributeToProvisioningAPIFormat(attribute) {
181
181
  name: attribute.name,
182
182
  type: attribute.type,
183
183
  expression: attribute.expression,
184
+ skipValue: attribute.skipValue,
184
185
  reverse: attribute.reverse,
185
186
  entity_name: attribute.entity_name,
186
187
  entity_type: attribute.entity_type,
@@ -352,20 +353,22 @@ function handleUpdateDevice(req, res, next) {
352
353
  if (req.body.entity_name || req.body.entity_type) {
353
354
  isTypeOrNameUpdated = true;
354
355
  }
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
- });
356
+ async.waterfall(
357
+ [apply(applyUpdatingHandler, newDevice)],
358
+ function handleUpdating(error, newDeviceUpdated) {
359
+ deviceService.updateRegister(
360
+ newDeviceUpdated,
361
+ isTypeOrNameUpdated,
362
+ function handleDeviceUpdate(error) {
363
+ if (error) {
364
+ next(error);
365
+ } else {
366
+ res.status(204).json({});
367
+ }
368
+ }
369
+ );
370
+ }
371
+ );
369
372
  } else {
370
373
  next(new errors.DeviceNotFound(req.params.deviceId));
371
374
  }
@@ -102,10 +102,13 @@
102
102
  "description": "Optional expression for measurement transformation",
103
103
  "type": "string"
104
104
  },
105
+ "skipValue": {
106
+ "description": "Attribute is skipped when result of apply expression is this value",
107
+ "type": "any"
108
+ },
105
109
  "entity_name": {
106
110
  "description": "Optional entity name for multientity updates",
107
- "type": "string",
108
- "pattern": "^([^<>;'=\"]+)+$"
111
+ "type": "string"
109
112
  },
110
113
  "entity_type": {
111
114
  "description": "Optional entity type for multientity updatess",