iotagent-node-lib 4.4.0 → 4.5.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.
@@ -260,7 +260,7 @@ function sendQueryValueNgsi2(entityName, attributes, typeInformation, token, cal
260
260
  * @param {Object} typeInformation Configuration information for the device.
261
261
  * @param {String} token User token to identify against the PEP Proxies (optional).
262
262
  */
263
- function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, callback) {
263
+ function sendUpdateValueNgsi2(entityName, originMeasures, originTypeInformation, token, callback) {
264
264
  //aux function used to builf JEXL context.
265
265
  //it returns a flat object from an Attr array
266
266
  function reduceAttrToPlainObject(attrs, initObj = {}) {
@@ -273,339 +273,354 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call
273
273
  return initObj;
274
274
  }
275
275
  }
276
-
277
- let entities = {}; //{entityName:{entityType:[attrs]}} //SubGoal Populate entoties data striucture
278
- let jexlctxt = {}; //will store the whole context (not just for JEXL)
279
- let payload = {}; //will store the final payload
280
- let plainMeasures = null; //will contain measures POJO
281
- let idTypeSSSList = pluginUtils.getIdTypeServSubServiceFromDevice(typeInformation);
282
-
283
276
  //Make a clone and overwrite
284
- typeInformation = JSON.parse(JSON.stringify(typeInformation));
277
+ let idTypeSSSList = pluginUtils.getIdTypeServSubServiceFromDevice(originTypeInformation);
285
278
 
286
279
  //Check mandatory information: type
287
- if (!typeInformation || !typeInformation.type) {
288
- callback(new errors.TypeNotFound(null, entityName, typeInformation));
280
+ if (!originTypeInformation || !originTypeInformation.type) {
281
+ callback(new errors.TypeNotFound(null, entityName, originTypeInformation));
289
282
  return;
290
283
  }
291
- //Rename all measures with matches with id and type to measure_id and measure_type
292
- for (let measure of measures) {
293
- if (measure.name === 'id' || measure.name === 'type') {
294
- measure.name = constants.MEASURE + measure.name;
295
- }
296
- }
297
284
 
298
- //Make a copy of measures in an plain object: plainMeasures
299
- plainMeasures = reduceAttrToPlainObject(measures);
300
-
301
- //Build the initital JEXL Context
302
- //All the measures (avoid references make another copy instead)
303
- jexlctxt = reduceAttrToPlainObject(measures);
304
- //All the static
305
- jexlctxt = reduceAttrToPlainObject(typeInformation.staticAttributes, jexlctxt);
306
- //id type Service and Subservice
307
- jexlctxt = reduceAttrToPlainObject(idTypeSSSList, jexlctxt);
285
+ let payload = {}; //will store the final payload
286
+ let entities = {};
287
+ payload.actionType = 'append';
288
+ payload.entities = [];
308
289
 
290
+ const currentIsoDate = new Date().toISOString();
291
+ const currentMoment = moment(currentIsoDate);
309
292
  //Managing timestamp (mustInsertTimeInstant flag to decide if we should insert Timestamp later on)
310
- const mustInsertTimeInstant = typeInformation.timestamp !== undefined ? typeInformation.timestamp : false;
311
-
312
- logger.debug(
313
- context,
314
- 'sendUpdateValueNgsi2 called with: entityName=%s, measures=%j, typeInformation=%j, initial jexlContext=%j, timestamp=%j',
315
- entityName,
316
- plainMeasures,
317
- typeInformation,
318
- jexlctxt,
319
- mustInsertTimeInstant
320
- );
293
+ const mustInsertTimeInstant =
294
+ originTypeInformation.timestamp !== undefined ? originTypeInformation.timestamp : false;
321
295
 
322
- //Now we can calculate the EntityName of primary entity
323
- let entityNameCalc = null;
324
- if (typeInformation.entityNameExp !== undefined && typeInformation.entityNameExp !== '') {
325
- try {
326
- logger.debug(context, 'sendUpdateValueNgsi2 entityNameExp %j', typeInformation.entityNameExp);
327
- entityNameCalc = expressionPlugin.applyExpression(typeInformation.entityNameExp, jexlctxt, typeInformation);
328
- } catch (e) {
329
- logger.debug(
330
- context,
331
- 'Error evaluating expression for entityName: %j with context: %j',
332
- typeInformation.entityNameExp,
333
- jexlctxt
334
- );
335
- }
296
+ // Check if measures is a single measure or a array of measures (a multimeasure)
297
+ if (originMeasures[0] && !originMeasures[0][0]) {
298
+ originMeasures = [originMeasures];
336
299
  }
300
+ for (let measures of originMeasures) {
301
+ entities = {}; //{entityName:{entityType:[attrs]}} //SubGoal Populate entities data structure
302
+ let jexlctxt = {}; //will store the whole context (not just for JEXL)
303
+
304
+ let plainMeasures = null; //will contain measures POJO
305
+ //Make a clone and overwrite
306
+ let typeInformation = JSON.parse(JSON.stringify(originTypeInformation));
307
+
308
+ //Rename all measures with matches with id and type to measure_id and measure_type
309
+ for (let measure of measures) {
310
+ if (measure.name === 'id' || measure.name === 'type') {
311
+ measure.name = constants.MEASURE + measure.name;
312
+ }
313
+ }
337
314
 
338
- entityName = entityNameCalc ? entityNameCalc : entityName;
339
- //enrich JEXL context
340
- jexlctxt['entity_name'] = entityName;
315
+ //Make a copy of measures in an plain object: plainMeasures
316
+ plainMeasures = reduceAttrToPlainObject(measures);
317
+ //Build the initital JEXL Context
318
+ //All the measures (avoid references make another copy instead)
319
+ jexlctxt = reduceAttrToPlainObject(measures);
320
+ //All the static
321
+ jexlctxt = reduceAttrToPlainObject(typeInformation.staticAttributes, jexlctxt);
322
+ //id type Service and Subservice
323
+ jexlctxt = reduceAttrToPlainObject(idTypeSSSList, jexlctxt);
341
324
 
342
- let preprocessedAttr = [];
343
- //Add Raw Static, Lazy, Command and Actives attr attributes
344
- if (typeInformation && typeInformation.staticAttributes) {
345
- preprocessedAttr = preprocessedAttr.concat(typeInformation.staticAttributes);
346
- }
347
- if (typeInformation && typeInformation.lazy) {
348
- preprocessedAttr = preprocessedAttr.concat(typeInformation.lazy);
349
- }
350
- if (typeInformation && typeInformation.active) {
351
- preprocessedAttr = preprocessedAttr.concat(typeInformation.active);
352
- }
325
+ logger.debug(
326
+ context,
327
+ 'sendUpdateValueNgsi2 loop with: entityName=%s, measures=%j, typeInformation=%j, initial jexlContext=%j, timestamp=%j',
328
+ entityName,
329
+ plainMeasures,
330
+ typeInformation,
331
+ jexlctxt,
332
+ mustInsertTimeInstant
333
+ );
353
334
 
354
- //Proccess every proto Attribute to populate entities data steuture
355
- entities[entityName] = {};
356
- entities[entityName][typeInformation.type] = [];
357
-
358
- for (let currentAttr of preprocessedAttr) {
359
- let hitted = false; //any measure, expressiom or value hit the attr (avoid propagate "silent attr" with null values )
360
- let attrEntityName = entityName;
361
- let attrEntityType = typeInformation.type;
362
- let valueExpression = null;
363
- //manage active attr without object__id (name by default)
364
- currentAttr.object_id = currentAttr.object_id ? currentAttr.object_id : currentAttr.name;
365
- //Enrich the attr (skip, hit, value, meta-timeInstant)
366
- currentAttr.skipValue = currentAttr.skipValue ? currentAttr.skipValue : null;
367
-
368
- //determine AttrEntityName for multientity
369
- if (
370
- currentAttr.entity_name !== null &&
371
- currentAttr.entity_name !== undefined &&
372
- currentAttr.entity_name !== '' &&
373
- typeof currentAttr.entity_name == 'string'
374
- ) {
335
+ //Now we can calculate the EntityName of primary entity
336
+ let entityNameCalc = null;
337
+ if (typeInformation.entityNameExp !== undefined && typeInformation.entityNameExp !== '') {
375
338
  try {
376
- logger.debug(
377
- context,
378
- 'Evaluating attribute: %j, for entity_name(exp):%j, with ctxt: %j',
379
- currentAttr.name,
380
- currentAttr.entity_name,
381
- jexlctxt
339
+ logger.debug(context, 'sendUpdateValueNgsi2 entityNameExp %j', typeInformation.entityNameExp);
340
+ entityNameCalc = expressionPlugin.applyExpression(
341
+ typeInformation.entityNameExp,
342
+ jexlctxt,
343
+ typeInformation
382
344
  );
383
- attrEntityName = jexlParser.applyExpression(currentAttr.entity_name, jexlctxt, typeInformation);
384
- if (!attrEntityName) {
385
- attrEntityName = currentAttr.entity_name;
386
- }
387
345
  } catch (e) {
388
346
  logger.debug(
389
347
  context,
390
- 'Exception evaluating entityNameExp:%j, with jexlctxt: %j',
391
- currentAttr.entity_name,
348
+ 'Error evaluating expression for entityName: %j with context: %j',
349
+ typeInformation.entityNameExp,
392
350
  jexlctxt
393
351
  );
394
- attrEntityName = currentAttr.entity_name;
395
352
  }
396
353
  }
397
354
 
398
- //determine AttrEntityType for multientity
399
- if (
400
- currentAttr.entity_type !== null &&
401
- currentAttr.entity_type !== undefined &&
402
- currentAttr.entity_type !== '' &&
403
- typeof currentAttr.entity_type === 'string'
404
- ) {
405
- attrEntityType = currentAttr.entity_type;
406
- }
355
+ entityName = entityNameCalc ? entityNameCalc : entityName;
356
+ //enrich JEXL context
357
+ jexlctxt['entity_name'] = entityName;
407
358
 
408
- //PRE POPULATE CONTEXT
409
- jexlctxt[currentAttr.name] = plainMeasures[currentAttr.object_id];
410
-
411
- //determine Value
412
- if (currentAttr.value !== undefined) {
413
- //static attributes already have a value
414
- hitted = true;
415
- valueExpression = currentAttr.value;
416
- } else if (plainMeasures[currentAttr.object_id] !== undefined) {
417
- //we have got a meaure for that Attr
418
- //actives ¿lazis?
419
- hitted = true;
420
- valueExpression = plainMeasures[currentAttr.object_id];
359
+ let preprocessedAttr = [];
360
+ //Add Raw Static, Lazy, Command and Actives attr attributes
361
+ if (typeInformation && typeInformation.staticAttributes) {
362
+ preprocessedAttr = preprocessedAttr.concat(typeInformation.staticAttributes);
421
363
  }
422
- //remove measures that has been shadowed by an alias (some may be left and managed later)
423
- //Maybe we must filter object_id if there is name == object_id
424
- measures = measures.filter((item) => item.name !== currentAttr.object_id && item.name !== currentAttr.name);
425
-
426
- if (
427
- currentAttr.expression !== undefined &&
428
- currentAttr.expression !== '' &&
429
- typeof currentAttr.expression == 'string'
430
- ) {
431
- try {
364
+ if (typeInformation && typeInformation.lazy) {
365
+ preprocessedAttr = preprocessedAttr.concat(typeInformation.lazy);
366
+ }
367
+ if (typeInformation && typeInformation.active) {
368
+ preprocessedAttr = preprocessedAttr.concat(typeInformation.active);
369
+ }
370
+
371
+ //Proccess every proto Attribute to populate entities data steuture
372
+ entities[entityName] = {};
373
+ entities[entityName][typeInformation.type] = [];
374
+
375
+ for (let currentAttr of preprocessedAttr) {
376
+ let hitted = false; //any measure, expressiom or value hit the attr (avoid propagate "silent attr" with null values )
377
+ let attrEntityName = entityName;
378
+ let attrEntityType = typeInformation.type;
379
+ let valueExpression = null;
380
+ //manage active attr without object__id (name by default)
381
+ currentAttr.object_id = currentAttr.object_id ? currentAttr.object_id : currentAttr.name;
382
+ //Enrich the attr (skip, hit, value, meta-timeInstant)
383
+ currentAttr.skipValue = currentAttr.skipValue ? currentAttr.skipValue : null;
384
+
385
+ //determine AttrEntityName for multientity
386
+ if (
387
+ currentAttr.entity_name !== null &&
388
+ currentAttr.entity_name !== undefined &&
389
+ currentAttr.entity_name !== '' &&
390
+ typeof currentAttr.entity_name == 'string'
391
+ ) {
392
+ try {
393
+ logger.debug(
394
+ context,
395
+ 'Evaluating attribute: %j, for entity_name(exp):%j, with ctxt: %j',
396
+ currentAttr.name,
397
+ currentAttr.entity_name,
398
+ jexlctxt
399
+ );
400
+ attrEntityName = jexlParser.applyExpression(currentAttr.entity_name, jexlctxt, typeInformation);
401
+ if (!attrEntityName) {
402
+ attrEntityName = currentAttr.entity_name;
403
+ }
404
+ } catch (e) {
405
+ logger.debug(
406
+ context,
407
+ 'Exception evaluating entityNameExp:%j, with jexlctxt: %j',
408
+ currentAttr.entity_name,
409
+ jexlctxt
410
+ );
411
+ attrEntityName = currentAttr.entity_name;
412
+ }
413
+ }
414
+
415
+ //determine AttrEntityType for multientity
416
+ if (
417
+ currentAttr.entity_type !== null &&
418
+ currentAttr.entity_type !== undefined &&
419
+ currentAttr.entity_type !== '' &&
420
+ typeof currentAttr.entity_type === 'string'
421
+ ) {
422
+ attrEntityType = currentAttr.entity_type;
423
+ }
424
+
425
+ //PRE POPULATE CONTEXT
426
+ jexlctxt[currentAttr.name] = plainMeasures[currentAttr.object_id];
427
+
428
+ //determine Value
429
+ if (currentAttr.value !== undefined) {
430
+ //static attributes already have a value
432
431
  hitted = true;
433
- valueExpression = jexlParser.applyExpression(currentAttr.expression, jexlctxt, typeInformation);
434
- //we fallback to null if anything unexpecte happend
435
- if (valueExpression === null || valueExpression === undefined || Number.isNaN(valueExpression)) {
432
+ valueExpression = currentAttr.value;
433
+ } else if (plainMeasures[currentAttr.object_id] !== undefined) {
434
+ //we have got a meaure for that Attr
435
+ //actives ¿lazis?
436
+ hitted = true;
437
+ valueExpression = plainMeasures[currentAttr.object_id];
438
+ }
439
+ //remove measures that has been shadowed by an alias (some may be left and managed later)
440
+ //Maybe we must filter object_id if there is name == object_id
441
+ measures = measures.filter((item) => item.name !== currentAttr.object_id && item.name !== currentAttr.name);
442
+
443
+ if (
444
+ currentAttr.expression !== undefined &&
445
+ currentAttr.expression !== '' &&
446
+ typeof currentAttr.expression == 'string'
447
+ ) {
448
+ try {
449
+ hitted = true;
450
+ valueExpression = jexlParser.applyExpression(currentAttr.expression, jexlctxt, typeInformation);
451
+ //we fallback to null if anything unexpecte happend
452
+ if (valueExpression === null || valueExpression === undefined || Number.isNaN(valueExpression)) {
453
+ valueExpression = null;
454
+ }
455
+ } catch (e) {
436
456
  valueExpression = null;
437
457
  }
438
- } catch (e) {
439
- valueExpression = null;
458
+ logger.debug(
459
+ context,
460
+ 'Evaluated attr: %j, with expression: %j, and ctxt: %j resulting: %j',
461
+ currentAttr.name,
462
+ currentAttr.expression,
463
+ jexlctxt,
464
+ valueExpression
465
+ );
440
466
  }
441
- logger.debug(
442
- context,
443
- 'Evaluated attr: %j, with expression: %j, and ctxt: %j resulting: %j',
444
- currentAttr.name,
445
- currentAttr.expression,
446
- jexlctxt,
447
- valueExpression
448
- );
449
- }
450
467
 
451
- currentAttr.hitted = hitted;
452
- currentAttr.value = valueExpression;
468
+ currentAttr.hitted = hitted;
469
+ currentAttr.value = valueExpression;
453
470
 
454
- //store de New Attributte in entity data structure
455
- if (hitted === true) {
456
- if (entities[attrEntityName] === undefined) {
457
- entities[attrEntityName] = {};
458
- }
459
- if (entities[attrEntityName][attrEntityType] === undefined) {
460
- entities[attrEntityName][attrEntityType] = [];
471
+ //store de New Attributte in entity data structure
472
+ if (hitted === true) {
473
+ if (entities[attrEntityName] === undefined) {
474
+ entities[attrEntityName] = {};
475
+ }
476
+ if (entities[attrEntityName][attrEntityType] === undefined) {
477
+ entities[attrEntityName][attrEntityType] = [];
478
+ }
479
+ //store de New Attributte
480
+ entities[attrEntityName][attrEntityType].push(currentAttr);
461
481
  }
462
- //store de New Attributte
463
- entities[attrEntityName][attrEntityType].push(currentAttr);
464
- }
465
482
 
466
- //RE-Populate de JEXLcontext (except for null or NaN we preffer undefined)
467
- jexlctxt[currentAttr.name] = valueExpression;
483
+ //RE-Populate de JEXLcontext (except for null or NaN we preffer undefined)
484
+ jexlctxt[currentAttr.name] = valueExpression;
468
485
 
469
- // Expand metadata value expression
470
- if (currentAttr.metadata) {
471
- for (var metaKey in currentAttr.metadata) {
472
- if (currentAttr.metadata[metaKey].expression && metaKey !== constants.TIMESTAMP_ATTRIBUTE) {
473
- let newAttrMeta = {};
474
- if (currentAttr.metadata[metaKey].type) {
475
- newAttrMeta['type'] = currentAttr.metadata[metaKey].type;
476
- }
477
- let metaValueExpression;
478
- try {
479
- metaValueExpression = jexlParser.applyExpression(
480
- currentAttr.metadata[metaKey].expression,
481
- jexlctxt,
482
- typeInformation
483
- );
484
- //we fallback to null if anything unexpecte happend
485
- if (
486
- metaValueExpression === null ||
487
- metaValueExpression === undefined ||
488
- Number.isNaN(metaValueExpression)
489
- ) {
486
+ // Expand metadata value expression
487
+ if (currentAttr.metadata) {
488
+ for (var metaKey in currentAttr.metadata) {
489
+ if (currentAttr.metadata[metaKey].expression && metaKey !== constants.TIMESTAMP_ATTRIBUTE) {
490
+ let newAttrMeta = {};
491
+ if (currentAttr.metadata[metaKey].type) {
492
+ newAttrMeta['type'] = currentAttr.metadata[metaKey].type;
493
+ }
494
+ let metaValueExpression;
495
+ try {
496
+ metaValueExpression = jexlParser.applyExpression(
497
+ currentAttr.metadata[metaKey].expression,
498
+ jexlctxt,
499
+ typeInformation
500
+ );
501
+ //we fallback to null if anything unexpecte happend
502
+ if (
503
+ metaValueExpression === null ||
504
+ metaValueExpression === undefined ||
505
+ Number.isNaN(metaValueExpression)
506
+ ) {
507
+ metaValueExpression = null;
508
+ }
509
+ } catch (e) {
490
510
  metaValueExpression = null;
491
511
  }
492
- } catch (e) {
493
- metaValueExpression = null;
512
+ newAttrMeta['value'] = metaValueExpression;
513
+ currentAttr.metadata[metaKey] = newAttrMeta;
494
514
  }
495
- newAttrMeta['value'] = metaValueExpression;
496
- currentAttr.metadata[metaKey] = newAttrMeta;
497
515
  }
498
516
  }
499
517
  }
500
- }
501
518
 
502
- //now we can compute explicit (Bool or Array) with the complete JexlContext
503
- let explicit = false;
504
- if (typeof typeInformation.explicitAttrs === 'string') {
505
- try {
506
- explicit = jexlParser.applyExpression(typeInformation.explicitAttrs, jexlctxt, typeInformation);
507
- if (explicit instanceof Array && mustInsertTimeInstant) {
508
- explicit.push(constants.TIMESTAMP_ATTRIBUTE);
519
+ //now we can compute explicit (Bool or Array) with the complete JexlContext
520
+ let explicit = false;
521
+ if (typeof typeInformation.explicitAttrs === 'string') {
522
+ try {
523
+ explicit = jexlParser.applyExpression(typeInformation.explicitAttrs, jexlctxt, typeInformation);
524
+ if (explicit instanceof Array && explicit.length > 0 && mustInsertTimeInstant) {
525
+ explicit.push(constants.TIMESTAMP_ATTRIBUTE);
526
+ }
527
+ logger.debug(
528
+ context,
529
+ 'Calculated explicitAttrs with expression: %j and ctxt: %j resulting: %j',
530
+ typeInformation.explicitAttrs,
531
+ jexlctxt,
532
+ explicit
533
+ );
534
+ } catch (e) {
535
+ // nothing to do: exception is already logged at info level
509
536
  }
510
- logger.debug(
511
- context,
512
- 'Calculated explicitAttrs with expression: %j and ctxt: %j resulting: %j',
513
- typeInformation.explicitAttrs,
514
- jexlctxt,
515
- explicit
516
- );
517
- } catch (e) {
518
- // nothing to do: exception is already logged at info level
537
+ } else if (typeof typeInformation.explicitAttrs == 'boolean') {
538
+ explicit = typeInformation.explicitAttrs;
519
539
  }
520
- } else if (typeof typeInformation.explicitAttrs == 'boolean') {
521
- explicit = typeInformation.explicitAttrs;
522
- }
523
-
524
- //more mesures may be added to the attribute list (unnhandled/left mesaures) l
525
- if (explicit === false && Object.keys(measures).length > 0) {
526
- entities[entityName][typeInformation.type] = entities[entityName][typeInformation.type].concat(measures);
527
- }
528
-
529
- //PRE-PROCESSING FINISHED
530
- //Explicit ATTRS and SKIPVALUES will be managed while we build NGSI payload
531
540
 
532
- //Get ready to build and send NGSI payload (entities-->payload)
533
- payload.actionType = 'append';
541
+ //more mesures may be added to the attribute list (unnhandled/left mesaures) l
542
+ if (explicit === false && Object.keys(measures).length > 0) {
543
+ entities[entityName][typeInformation.type] = entities[entityName][typeInformation.type].concat(measures);
544
+ }
534
545
 
535
- payload.entities = [];
536
- const currentIsoDate = new Date().toISOString();
537
- const currentMoment = moment(currentIsoDate);
538
- for (let ename in entities) {
539
- for (let etype in entities[ename]) {
540
- let e = {};
541
- e.id = String(ename);
542
- e.type = String(etype);
543
- let timestamp = { type: constants.TIMESTAMP_TYPE_NGSI2 }; //timestamp scafold-attr for insertions.
544
- let timestampAttrs = null;
545
- if (mustInsertTimeInstant) {
546
- // get timestamp for current entity
547
-
548
- timestampAttrs = entities[ename][etype].filter((item) => item.name === constants.TIMESTAMP_ATTRIBUTE);
549
- if (timestampAttrs && timestampAttrs.length > 0) {
550
- timestamp.value = timestampAttrs[0]['value'];
551
- }
546
+ //PRE-PROCESSING FINISHED
547
+ //Explicit ATTRS and SKIPVALUES will be managed while we build NGSI payload
548
+ //Get ready to build and send NGSI payload (entities-->payload)
549
+
550
+ for (let ename in entities) {
551
+ for (let etype in entities[ename]) {
552
+ let e = {};
553
+ e.id = String(ename);
554
+ e.type = String(etype);
555
+ let timestamp = { type: constants.TIMESTAMP_TYPE_NGSI2 }; //timestamp scafold-attr for insertions.
556
+ let timestampAttrs = null;
557
+ if (mustInsertTimeInstant) {
558
+ // get timestamp for current entity
552
559
 
553
- if (timestamp.value) {
554
- if (!moment(timestamp.value, moment.ISO_8601, true).isValid()) {
555
- callback(new errors.BadTimestamp(timestamp.value, entityName, typeInformation));
556
- return;
560
+ timestampAttrs = entities[ename][etype].filter(
561
+ (item) => item.name === constants.TIMESTAMP_ATTRIBUTE
562
+ );
563
+ if (timestampAttrs && timestampAttrs.length > 0) {
564
+ timestamp.value = timestampAttrs[0]['value'];
557
565
  }
558
- } else {
559
- if (!typeInformation.timezone) {
560
- timestamp.value = currentIsoDate;
561
- jexlctxt[constants.TIMESTAMP_ATTRIBUTE] = timestamp.value;
566
+
567
+ if (timestamp.value) {
568
+ if (!moment(timestamp.value, moment.ISO_8601, true).isValid()) {
569
+ callback(new errors.BadTimestamp(timestamp.value, entityName, typeInformation));
570
+ return;
571
+ }
562
572
  } else {
563
- timestamp.value = currentMoment
564
- .tz(typeInformation.timezone)
565
- .format('YYYY-MM-DD[T]HH:mm:ss.SSSZ');
566
- jexlctxt[constants.TIMESTAMP_ATTRIBUTE] = timestamp.value;
573
+ if (!typeInformation.timezone) {
574
+ timestamp.value = currentIsoDate;
575
+ jexlctxt[constants.TIMESTAMP_ATTRIBUTE] = timestamp.value;
576
+ } else {
577
+ timestamp.value = currentMoment
578
+ .tz(typeInformation.timezone)
579
+ .format('YYYY-MM-DD[T]HH:mm:ss.SSSZ');
580
+ jexlctxt[constants.TIMESTAMP_ATTRIBUTE] = timestamp.value;
581
+ }
567
582
  }
568
583
  }
569
- }
570
- //extract attributes
571
- let isEmpty = true;
572
- for (let attr of entities[ename][etype]) {
573
- if (
574
- attr.name !== 'id' &&
575
- attr.name !== 'type' &&
576
- (attr.value !== attr.skipValue || attr.skipValue === undefined) &&
577
- (attr.hitted || attr.hitted === undefined) && //undefined is for pure measures
578
- (typeof explicit === 'boolean' || //true and false already handled
579
- (explicit instanceof Array && //check the array version
580
- (explicit.includes(attr.name) ||
581
- explicit.some(
582
- (item) => attr.object_id !== undefined && item.object_id === attr.object_id
583
- ))))
584
- ) {
585
- isEmpty = false;
586
- if (mustInsertTimeInstant) {
587
- // Add TimeInstant to all attribute metadata of all entities
588
- if (attr.name !== constants.TIMESTAMP_ATTRIBUTE) {
589
- if (!attr.metadata) {
590
- attr.metadata = {};
584
+ //extract attributes
585
+ let isEmpty = true;
586
+ for (let attr of entities[ename][etype]) {
587
+ if (
588
+ attr.name !== 'id' &&
589
+ attr.name !== 'type' &&
590
+ (attr.value !== attr.skipValue || attr.skipValue === undefined) &&
591
+ (attr.hitted || attr.hitted === undefined) && //undefined is for pure measures
592
+ (typeof explicit === 'boolean' || //true and false already handled
593
+ (explicit instanceof Array && //check the array version
594
+ (explicit.includes(attr.name) ||
595
+ explicit.some(
596
+ (item) => attr.object_id !== undefined && item.object_id === attr.object_id
597
+ ))))
598
+ ) {
599
+ isEmpty = false;
600
+ if (mustInsertTimeInstant) {
601
+ // Add TimeInstant to all attribute metadata of all entities
602
+ if (attr.name !== constants.TIMESTAMP_ATTRIBUTE) {
603
+ if (!attr.metadata) {
604
+ attr.metadata = {};
605
+ }
606
+ attr.metadata[constants.TIMESTAMP_ATTRIBUTE] = timestamp;
591
607
  }
592
- attr.metadata[constants.TIMESTAMP_ATTRIBUTE] = timestamp;
593
608
  }
609
+ e[attr.name] = { type: attr.type, value: attr.value, metadata: attr.metadata };
594
610
  }
595
- e[attr.name] = { type: attr.type, value: attr.value, metadata: attr.metadata };
596
611
  }
597
- }
598
- if (!isEmpty) {
599
- if (mustInsertTimeInstant) {
600
- e[constants.TIMESTAMP_ATTRIBUTE] = timestamp;
612
+ if (!isEmpty) {
613
+ if (mustInsertTimeInstant) {
614
+ e[constants.TIMESTAMP_ATTRIBUTE] = timestamp;
615
+ }
616
+ payload.entities.push(e);
601
617
  }
602
- payload.entities.push(e);
603
618
  }
604
619
  }
605
- }
620
+ } // end for (let measures of originMeasures)
606
621
 
607
622
  let url = '/v2/op/update';
608
- let options = NGSIUtils.createRequestObject(url, typeInformation, token);
623
+ let options = NGSIUtils.createRequestObject(url, originTypeInformation, token);
609
624
  options.json = payload;
610
625
 
611
626
  // Prevent to update an entity with an empty payload: more than id and type
@@ -620,13 +635,17 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call
620
635
  // Note that the options object is prepared for the second case (multi entity), so we "patch" it
621
636
  // only in the first case
622
637
 
623
- //Multientity more than one name o more than one type at primary entity
624
- let multientity = Object.keys(entities).length > 1 || Object.keys(entities[entityName]).length > 1;
638
+ //Multi: multientity (more than one name o more than one type at primary entity)
639
+ // of multimeasure (originMeasures is an array of more than one element)
640
+ let multi =
641
+ Object.keys(entities).length > 1 ||
642
+ Object.keys(entities[entityName]).length > 1 ||
643
+ originMeasures.length > 1;
625
644
 
626
- if (!multientity) {
645
+ if (!multi) {
627
646
  // recreate options object to use single entity update
628
647
  url = '/v2/entities?options=upsert';
629
- options = NGSIUtils.createRequestObject(url, typeInformation, token);
648
+ options = NGSIUtils.createRequestObject(url, originTypeInformation, token);
630
649
  delete payload.actionType;
631
650
 
632
651
  let entityAttrs = payload.entities[0];
@@ -643,7 +662,20 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call
643
662
  transformedObject.type = entityAttrs.type;
644
663
  options.json = transformedObject;
645
664
  options.method = 'POST';
646
- } // else: keep current options object created for a batch update
665
+ } else if (payload.entities.every((entity) => 'TimeInstant' in entity)) {
666
+ // Try sort entities by TimeInstant
667
+ payload.entities.sort(
668
+ (a, b) => new Date(a.TimeInstant.value).getTime() - new Date(b.TimeInstant.value).getTime()
669
+ );
670
+ options.json = payload;
671
+ } else {
672
+ // keep current options object created for a batch update
673
+ logger.debug(
674
+ context,
675
+ "some entities lack the 'TimeInstant' key. Sorting is not feasible: %j ",
676
+ payload.entities
677
+ );
678
+ }
647
679
 
648
680
  //Send the NGSI request
649
681
  logger.debug(context, 'Updating device value in the Context Broker at: %j', options.url);
@@ -651,7 +683,7 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call
651
683
 
652
684
  request(
653
685
  options,
654
- generateNGSI2OperationHandler('update', entityName, typeInformation, token, options, callback)
686
+ generateNGSI2OperationHandler('update', entityName, originTypeInformation, token, options, callback)
655
687
  );
656
688
  } else {
657
689
  logger.debug(