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.
- package/config.js +2 -1
- package/doc/admin.md +16 -7
- package/doc/api.md +183 -52
- package/doc/requirements.txt +1 -1
- package/lib/commonConfig.js +7 -1
- package/lib/services/devices/deviceRegistryMemory.js +1 -1
- package/lib/services/devices/deviceRegistryMongoDB.js +2 -2
- package/lib/services/devices/deviceService.js +4 -3
- package/lib/services/devices/devices-NGSI-LD.js +5 -5
- package/lib/services/devices/devices-NGSI-mixed.js +3 -3
- package/lib/services/devices/devices-NGSI-v2.js +5 -5
- package/lib/services/ngsi/entities-NGSI-LD.js +1 -1
- package/lib/services/ngsi/entities-NGSI-v2.js +313 -281
- package/lib/services/ngsi/subscription-NGSI-LD.js +2 -2
- package/lib/services/ngsi/subscription-NGSI-v2.js +2 -2
- package/lib/services/northBound/deviceProvisioningServer.js +5 -4
- package/lib/services/northBound/northboundServer.js +2 -2
- package/package.json +1 -1
- package/test/functional/README.md +60 -37
- package/test/functional/testCases.js +1017 -264
- package/test/functional/testUtils.js +53 -20
- package/test/unit/examples/deviceProvisioningRequests/updateProvisionDeviceWithApikey.json +4 -0
- package/test/unit/ngsi-ld/provisioning/device-update-registration_test.js +5 -5
- package/test/unit/ngsiv2/provisioning/device-update-registration_test.js +6 -6
- package/test/unit/ngsiv2/provisioning/updateProvisionedDevices-test.js +35 -0
|
@@ -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,
|
|
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
|
-
|
|
277
|
+
let idTypeSSSList = pluginUtils.getIdTypeServSubServiceFromDevice(originTypeInformation);
|
|
285
278
|
|
|
286
279
|
//Check mandatory information: type
|
|
287
|
-
if (!
|
|
288
|
-
callback(new errors.TypeNotFound(null, entityName,
|
|
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
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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 =
|
|
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
|
-
//
|
|
323
|
-
|
|
324
|
-
|
|
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
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
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
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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
|
-
'
|
|
391
|
-
|
|
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
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
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
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
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 =
|
|
434
|
-
|
|
435
|
-
|
|
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
|
-
|
|
439
|
-
|
|
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
|
-
|
|
452
|
-
|
|
468
|
+
currentAttr.hitted = hitted;
|
|
469
|
+
currentAttr.value = valueExpression;
|
|
453
470
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
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
|
-
|
|
467
|
-
|
|
483
|
+
//RE-Populate de JEXLcontext (except for null or NaN we preffer undefined)
|
|
484
|
+
jexlctxt[currentAttr.name] = valueExpression;
|
|
468
485
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
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
|
-
|
|
493
|
-
|
|
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
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
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
|
-
|
|
511
|
-
|
|
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
|
-
|
|
533
|
-
|
|
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
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
for (let
|
|
540
|
-
let
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
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
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
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
|
-
|
|
559
|
-
if (
|
|
560
|
-
timestamp.value
|
|
561
|
-
|
|
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
|
-
|
|
564
|
-
.
|
|
565
|
-
.
|
|
566
|
-
|
|
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
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
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
|
-
|
|
599
|
-
|
|
600
|
-
|
|
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,
|
|
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
|
-
//
|
|
624
|
-
|
|
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 (!
|
|
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,
|
|
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
|
-
}
|
|
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,
|
|
686
|
+
generateNGSI2OperationHandler('update', entityName, originTypeInformation, token, options, callback)
|
|
655
687
|
);
|
|
656
688
|
} else {
|
|
657
689
|
logger.debug(
|