iotagent-node-lib 3.1.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 (48) hide show
  1. package/CHANGES_NEXT_RELEASE +0 -2
  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/lib/commonConfig.js +7 -10
  8. package/lib/fiware-iotagent-lib.js +0 -10
  9. package/lib/jexlTranformsMap.js +2 -1
  10. package/lib/plugins/bidirectionalData.js +8 -26
  11. package/lib/plugins/expressionPlugin.js +8 -40
  12. package/lib/plugins/jexlParser.js +28 -0
  13. package/lib/services/commands/commandService.js +1 -1
  14. package/lib/services/devices/deviceService.js +2 -1
  15. package/lib/services/ngsi/entities-NGSI-LD.js +13 -57
  16. package/lib/services/ngsi/entities-NGSI-v2.js +145 -108
  17. package/lib/services/northBound/deviceProvisioningServer.js +17 -14
  18. package/lib/templates/createDevice.json +5 -2
  19. package/lib/templates/createDeviceLax.json +7 -5
  20. package/lib/templates/updateDevice.json +5 -2
  21. package/lib/templates/updateDeviceLax.json +3 -5
  22. package/package.json +1 -1
  23. package/test/unit/examples/deviceProvisioningRequests/provisionBidirectionalDevice.json +5 -5
  24. package/test/unit/examples/deviceProvisioningRequests/provisionMinimumDevice4.json +1 -0
  25. package/test/unit/examples/groupProvisioningRequests/bidirectionalGroup.json +4 -4
  26. package/test/unit/ngsi-ld/expressions/jexlBasedTransformations-test.js +0 -2
  27. package/test/unit/ngsi-ld/plugins/bidirectional-plugin_test.js +8 -8
  28. package/test/unit/ngsi-ld/plugins/custom-plugin_test.js +152 -0
  29. package/test/unit/ngsi-ld/plugins/multientity-plugin_test.js +1 -1
  30. package/test/unit/ngsiv2/examples/contextRequests/createMinimumProvisionedDevice4.json +5 -1
  31. package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin31.json +0 -8
  32. package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin35.json +20 -0
  33. package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin40.json +42 -0
  34. package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin41.json +32 -0
  35. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin25.json +37 -0
  36. package/test/unit/ngsiv2/expressions/jexlBasedTransformations-test.js +228 -6
  37. package/test/unit/ngsiv2/lazyAndCommands/polling-commands-test.js +1 -2
  38. package/test/unit/ngsiv2/plugins/bidirectional-plugin_test.js +6 -6
  39. package/test/unit/ngsiv2/plugins/custom-plugin_test.js +151 -0
  40. package/test/unit/ngsiv2/plugins/multientity-plugin_test.js +85 -12
  41. package/test/unit/ngsiv2/provisioning/device-provisioning-api_test.js +11 -3
  42. package/doc/advanced-topics.md +0 -626
  43. package/doc/expressionLanguage.md +0 -762
  44. package/lib/plugins/expressionParser.js +0 -205
  45. package/test/unit/expressions/expression-test.js +0 -197
  46. package/test/unit/ngsi-ld/expressions/expressionBasedTransformations-test.js +0 -882
  47. package/test/unit/ngsiv2/expressions/expressionBasedTransformations-test.js +0 -951
  48. 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
@@ -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,15 @@ 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
+ logger.debug(context, 'sendUpdateValueNgsi2 currentEntity sorted %j ', currentEntity);
631
679
  let timestampValue = undefined;
632
680
  // Loop for each final attribute to apply alias, multientity and expressions
633
681
  for (const j in currentEntity) {
@@ -660,7 +708,7 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
660
708
  // invalid mapping
661
709
  logger.debug(
662
710
  context,
663
- 'sendUpdateValueNgsi2 \n invalid mapping for attr %j \n newAttr %j',
711
+ 'sendUpdateValueNgsi2 invalid mapping for attr=%j newAttr=%j',
664
712
  attr,
665
713
  newAttr
666
714
  );
@@ -668,12 +716,12 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
668
716
  attr = undefined; // stop processing attr
669
717
  newAttr = undefined;
670
718
  } else {
671
- ctxt[attr.name] = payload.entities[0][j]['value'];
719
+ ctxt[attr.name] = payload.entities[0][j].value;
672
720
  }
673
721
  }
674
722
  logger.debug(
675
723
  context,
676
- 'sendUpdateValueNgsi2 \n procesing j %j attr %j ctxt %j \n newAttr %j ',
724
+ 'sendUpdateValueNgsi2 procesing j=%j attr=%j ctxt=%j newAttr=%j ',
677
725
  j,
678
726
  attr,
679
727
  ctxt,
@@ -687,7 +735,7 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
687
735
  if (attr && attr.expression) {
688
736
  logger.debug(
689
737
  context,
690
- 'sendUpdateValueNgsi2 \n apply expression %j \n over ctxt %j \n and device %j',
738
+ 'sendUpdateValueNgsi2 apply expression=%j over ctxt=%j and device=%j',
691
739
  attr.expression,
692
740
  ctxt,
693
741
  typeInformation
@@ -696,58 +744,43 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
696
744
  try {
697
745
  if (expressionPlugin.contextAvailable(attr.expression, ctxt, typeInformation)) {
698
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
+ }
699
762
  } else {
700
- logger.warn(
763
+ logger.info(
701
764
  context,
702
- 'sendUpdateValueNgsi2 \n no context available for apply expression %j \n',
765
+ 'sendUpdateValueNgsi2 no context available for apply expression=%j',
703
766
  attr.expression
704
767
  );
705
768
  res = newAttr.value; // keep newAttr value
706
769
  }
707
770
  } 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
771
+ logger.error(context, 'sendUpdateValueNgsi2 apply expression exception=%j', e);
772
+ if (attr && attr.name) {
773
+ res = ctxt[attr.name];
713
774
  }
714
775
  }
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;
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;
749
783
  }
750
- logger.debug(context, 'sendUpdateValueNgsi2 \n apply expression result %j \n newAttr %j', res, newAttr);
751
784
  }
752
785
 
753
786
  // Apply Multientity: entity_type and entity_name in attributes are in typeInformation.active
@@ -759,21 +792,21 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
759
792
  if (expressionPlugin.contextAvailable(attr.entity_name, ctxt, typeInformation)) {
760
793
  newEntityName = expressionPlugin.applyExpression(attr.entity_name, ctxt, typeInformation);
761
794
  } else {
762
- logger.warn(
795
+ logger.info(
763
796
  context,
764
- 'sendUpdateValueNgsi2 \n MULTI no context available for apply expression %j \n',
797
+ 'sendUpdateValueNgsi2 MULTI no context available for apply expression=%j',
765
798
  attr.entity_name
766
799
  );
767
800
  newEntityName = attr.entity_name;
768
801
  }
769
802
  newEntityName = newEntityName ? newEntityName : attr.entity_name;
770
803
  } catch (e) {
771
- logger.error(context, 'sendUpdateValueNgsi2 \n MULTI apply expression exception %j \n', e);
804
+ logger.error(context, 'sendUpdateValueNgsi2 MULTI apply expression exception=%j', e);
772
805
  newEntityName = attr.entity_name;
773
806
  }
774
807
  logger.debug(
775
808
  context,
776
- 'sendUpdateValueNgsi2 \n MULTI apply expression %j \n result %j \n payload %j',
809
+ 'sendUpdateValueNgsi2 MULTI apply expression=%j result=%j payload=%j',
777
810
  attr.entity_name,
778
811
  newEntityName,
779
812
  payload
@@ -785,7 +818,7 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
785
818
  type: attr.entity_type ? attr.entity_type : payload.entities[0].type
786
819
  };
787
820
  // Check if there is already a newEntity created
788
- let alreadyEntity = payload.entities.find((entity) => {
821
+ const alreadyEntity = payload.entities.find((entity) => {
789
822
  return entity.id === newEntity.id && entity.type === newEntity.type;
790
823
  });
791
824
  if (alreadyEntity) {
@@ -802,7 +835,7 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
802
835
  : timestampValue !== undefined
803
836
  ) {
804
837
  newEntity = addTimestampNgsi2(newEntity, typeInformation.timezone, timestampValue);
805
- logger.debug(context, 'sendUpdateValueNgsi2 \n timestamped newEntity=%j', newEntity);
838
+ logger.debug(context, 'sendUpdateValueNgsi2 timestamped newEntity=%j', newEntity);
806
839
  }
807
840
  payload.entities.push(newEntity);
808
841
  }
@@ -810,7 +843,7 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
810
843
  if (attr.name !== j) {
811
844
  logger.debug(
812
845
  context,
813
- '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',
814
847
  j,
815
848
  attr,
816
849
  payload
@@ -828,7 +861,7 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
828
861
  }
829
862
  }
830
863
  if (newAttr && newAttr.type === constants.TIMESTAMP_TYPE_NGSI2 && newAttr.value) {
831
- let extendedTime = compressTimestampPlugin.fromBasicToExtended(newAttr.value);
864
+ const extendedTime = compressTimestampPlugin.fromBasicToExtended(newAttr.value);
832
865
  if (extendedTime) {
833
866
  // TBD: there is not flag about compressTimestamp in iotagent-node-lib,
834
867
  // but there is one in agents
@@ -840,7 +873,7 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
840
873
  timestampValue = newAttr.value;
841
874
  logger.debug(
842
875
  context,
843
- 'sendUpdateValueNgsi2 \n newAttr is TimeInstant and new payload=%j',
876
+ 'sendUpdateValueNgsi2 newAttr is TimeInstant and new payload=%j',
844
877
  payload
845
878
  );
846
879
  }
@@ -852,7 +885,7 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
852
885
  newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].type === constants.TIMESTAMP_TYPE_NGSI2 &&
853
886
  newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].value
854
887
  ) {
855
- let extendedTime = compressTimestampPlugin.fromBasicToExtended(
888
+ const extendedTime = compressTimestampPlugin.fromBasicToExtended(
856
889
  newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].value
857
890
  );
858
891
  if (extendedTime) {
@@ -865,7 +898,7 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
865
898
  // final attr loop
866
899
  logger.debug(
867
900
  context,
868
- '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',
869
902
  j,
870
903
  currentEntity,
871
904
  payload
@@ -909,10 +942,10 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
909
942
  }
910
943
  }
911
944
  }
912
- logger.debug(context, 'sendUpdateValueNgsi2 \n ending payload=%j', payload);
945
+ logger.debug(context, 'sendUpdateValueNgsi2 ending payload=%j', payload);
913
946
 
914
947
  for (let m = 0; m < payload.entities.length; m++) {
915
- for (var key in payload.entities[m]) {
948
+ for (const key in payload.entities[m]) {
916
949
  // purge object_id from payload
917
950
  if (payload.entities[m][key] && payload.entities[m][key].object_id) {
918
951
  delete payload.entities[m][key].object_id;
@@ -920,7 +953,7 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
920
953
  }
921
954
  payload.entities[m] = NGSIUtils.castJsonNativeAttributes(payload.entities[m]); // native types
922
955
  }
923
- 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);
924
957
 
925
958
  options.json = payload;
926
959
 
@@ -969,6 +1002,10 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
969
1002
  }
970
1003
 
971
1004
  exports.sendQueryValue = sendQueryValueNgsi2;
972
- 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
+ };
973
1010
  exports.addTimestamp = addTimestampNgsi2;
974
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",
@@ -38,7 +38,7 @@
38
38
  },
39
39
  "expressionLanguage": {
40
40
  "description": "Expression language used to apply expressions for this device",
41
- "type": "boolean"
41
+ "type": "string"
42
42
  },
43
43
  "explicitAttrs": {
44
44
  "description": "Flag about only provisioned attributes will be processed to Context Broker"
@@ -98,13 +98,15 @@
98
98
  },
99
99
  "expression": {
100
100
  "description": "Optional expression for measurement transformation",
101
- "type": "string",
102
- "pattern": "^([^<>;'=]+)+$"
101
+ "type": "string"
102
+ },
103
+ "skipValue": {
104
+ "description": "Attribute is skipped when result of apply expression is this value",
105
+ "type": "any"
103
106
  },
104
107
  "entity_name": {
105
108
  "description": "Optional entity name for multientity updates",
106
- "type": "string",
107
- "pattern": "^([^<>;'=\"]+)+$"
109
+ "type": "string"
108
110
  },
109
111
  "entity_type": {
110
112
  "description": "Optional entity type for multientity updatess",
@@ -88,10 +88,13 @@
88
88
  "description": "Optional expression for measurement transformation",
89
89
  "type": "string"
90
90
  },
91
+ "skipValue": {
92
+ "description": "Attribute is skipped when result of apply expression is this value",
93
+ "type": "any"
94
+ },
91
95
  "entity_name": {
92
96
  "description": "Optional entity name for multientity updates",
93
- "type": "string",
94
- "pattern": "^([^<>;'=\"]+)+$"
97
+ "type": "string"
95
98
  },
96
99
  "entity_type": {
97
100
  "description": "Optional entity type for multientity updatess",
@@ -79,13 +79,11 @@
79
79
  },
80
80
  "expression": {
81
81
  "description": "Optional expression for measurement transformation",
82
- "type": "string",
83
- "pattern": "^([^<>;'=]+)+$"
82
+ "type": "string"
84
83
  },
85
84
  "entity_name": {
86
85
  "description": "Optional entity name for multientity updates",
87
- "type": "string",
88
- "pattern": "^([^<>;'=\"]+)+$"
86
+ "type": "string"
89
87
  },
90
88
  "entity_type": {
91
89
  "description": "Optional entity type for multientity updatess",
@@ -158,4 +156,4 @@
158
156
  "type": "array"
159
157
  }
160
158
  }
161
- }
159
+ }