iotagent-node-lib 2.26.0 → 3.0.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/lib/fiware-iotagent-lib.js +0 -5
- package/lib/plugins/compressTimestamp.js +2 -7
- package/lib/plugins/expressionParser.js +0 -47
- package/lib/plugins/expressionPlugin.js +6 -78
- package/lib/plugins/jexlParser.js +0 -36
- package/lib/plugins/pluginUtils.js +0 -119
- package/lib/services/ngsi/entities-NGSI-LD.js +600 -199
- package/lib/services/ngsi/entities-NGSI-v2.js +620 -298
- package/lib/services/ngsi/ngsiUtils.js +15 -22
- package/package.json +1 -1
- package/test/unit/general/loglevel-api_test.js +0 -2
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextAliasPlugin1.json +0 -5
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast10.json +1 -1
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast8.json +1 -1
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextAutocast9.json +1 -1
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin32.json +16 -15
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin4.json +8 -8
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin4a.json +34 -34
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin5.json +8 -8
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin6.json +0 -20
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin7.json +0 -5
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin8.json +10 -10
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityTimestampPlugin1.json +8 -7
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityTimestampPlugin2.json +5 -4
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityTimestampPlugin3.json +1 -1
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextProcessTimestamp.json +4 -4
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextTimestampOverride.json +4 -3
- package/test/unit/ngsi-ld/examples/contextRequests/updateContextTimestampOverrideWithoutMilis.json +4 -3
- package/test/unit/ngsi-ld/expressions/expressionBasedTransformations-test.js +2 -5
- package/test/unit/ngsi-ld/expressions/jexlBasedTransformations-test.js +1 -7
- package/test/unit/ngsi-ld/ngsiService/autocast-test.js +15 -3
- package/test/unit/ngsi-ld/plugins/alias-plugin_test.js +0 -2
- package/test/unit/ngsi-ld/plugins/compress-timestamp-plugin_test.js +0 -31
- package/test/unit/ngsi-ld/plugins/multientity-plugin_test.js +18 -18
- package/test/unit/ngsi-ld/plugins/timestamp-processing-plugin_test.js +19 -6
- package/test/unit/ngsiv2/examples/contextRequests/updateContextAliasPlugin1.json +2 -2
- package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin11.json +5 -1
- package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin5.json +4 -4
- package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin6.json +0 -16
- package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin7.json +0 -4
- package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin8.json +8 -8
- package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin1.json +42 -42
- package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin2.json +7 -7
- package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityTimestampPlugin4.json +0 -5
- package/test/unit/ngsiv2/examples/contextRequests/updateContextProcessTimestamp.json +1 -7
- package/test/unit/ngsiv2/expressions/expressionBasedTransformations-test.js +0 -3
- package/test/unit/ngsiv2/expressions/expressionCombinedTransformations-test.js +0 -6
- package/test/unit/ngsiv2/expressions/jexlBasedTransformations-test.js +1 -7
- package/test/unit/ngsiv2/plugins/alias-plugin_test.js +0 -2
- package/test/unit/ngsiv2/plugins/bidirectional-plugin_test.js +0 -4
- package/test/unit/ngsiv2/plugins/compress-timestamp-plugin_test.js +0 -32
- package/test/unit/ngsiv2/plugins/multientity-plugin_test.js +5 -15
- package/test/unit/ngsiv2/plugins/timestamp-processing-plugin_test.js +1 -1
- package/lib/plugins/addEvent.js +0 -32
- package/lib/plugins/attributeAlias.js +0 -107
- package/lib/plugins/multiEntity.js +0 -255
- package/lib/plugins/timestampProcessPlugin.js +0 -95
- package/test/unit/ngsi-ld/plugins/event-plugin_test.js +0 -116
- package/test/unit/ngsiv2/plugins/event-plugin_test.js +0 -118
- package/test/unit/ngsiv2/plugins/translation-inPlugins_test.js +0 -262
|
@@ -28,9 +28,6 @@
|
|
|
28
28
|
/* eslint-disable consistent-return */
|
|
29
29
|
|
|
30
30
|
const request = require('../../request-shim');
|
|
31
|
-
const statsService = require('./../stats/statsRegistry');
|
|
32
|
-
const async = require('async');
|
|
33
|
-
const apply = async.apply;
|
|
34
31
|
const alarms = require('../common/alarmManagement');
|
|
35
32
|
const errors = require('../../errors');
|
|
36
33
|
const utils = require('../northBound/restUtils');
|
|
@@ -38,13 +35,14 @@ const pluginUtils = require('../../plugins/pluginUtils');
|
|
|
38
35
|
const config = require('../../commonConfig');
|
|
39
36
|
const constants = require('../../constants');
|
|
40
37
|
const jexlParser = require('../../plugins/jexlParser');
|
|
38
|
+
const expressionPlugin = require('../../plugins/expressionPlugin');
|
|
39
|
+
const compressTimestampPlugin = require('../../plugins/compressTimestamp');
|
|
41
40
|
const moment = require('moment-timezone');
|
|
42
41
|
const NGSIUtils = require('./ngsiUtils');
|
|
43
42
|
const logger = require('logops');
|
|
44
43
|
const context = {
|
|
45
44
|
op: 'IoTAgentNGSI.Entities-v2'
|
|
46
45
|
};
|
|
47
|
-
const _ = require('underscore');
|
|
48
46
|
|
|
49
47
|
/**
|
|
50
48
|
* Amends an NGSIv2 Geoattribute from String to GeoJSON format
|
|
@@ -96,16 +94,29 @@ function formatGeoAttrs(attr) {
|
|
|
96
94
|
return obj;
|
|
97
95
|
}
|
|
98
96
|
|
|
99
|
-
|
|
100
|
-
|
|
97
|
+
/**
|
|
98
|
+
* Adds timestamp to ngsiv2 payload entities accoding to timezone, and an optional timestampvalue.
|
|
99
|
+
*
|
|
100
|
+
* @param {Object} payload NGSIv2 payload with one or more entities
|
|
101
|
+
* @param String timezone TimeZone value (optional)
|
|
102
|
+
* @param String timestampValue Timestamp value (optional). If not provided current timestamp is used
|
|
103
|
+
* @param Boolean skipMetadataAtt An optional flag to indicate if timestamp should be added to each metadata attribute. Default is false
|
|
104
|
+
* @return {Object} NGSIv2 payload entities with timestamp
|
|
105
|
+
*/
|
|
106
|
+
function addTimestampNgsi2(payload, timezone, timestampValue, skipMetadataAtt) {
|
|
107
|
+
function addTimestampEntity(entity, timezone, timestampValue) {
|
|
101
108
|
const timestamp = {
|
|
102
109
|
type: constants.TIMESTAMP_TYPE_NGSI2
|
|
103
110
|
};
|
|
104
111
|
|
|
105
|
-
if (
|
|
106
|
-
timestamp.value =
|
|
112
|
+
if (timestampValue) {
|
|
113
|
+
timestamp.value = timestampValue;
|
|
107
114
|
} else {
|
|
108
|
-
|
|
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
|
+
}
|
|
109
120
|
}
|
|
110
121
|
|
|
111
122
|
function addMetadata(attribute) {
|
|
@@ -137,7 +148,9 @@ function addTimestampNgsi2(payload, timezone) {
|
|
|
137
148
|
for (const key in entity) {
|
|
138
149
|
/* eslint-disable-next-line no-prototype-builtins */
|
|
139
150
|
if (entity.hasOwnProperty(key) && key !== 'id' && key !== 'type') {
|
|
140
|
-
|
|
151
|
+
if (!skipMetadataAtt) {
|
|
152
|
+
addMetadata(entity[key]);
|
|
153
|
+
}
|
|
141
154
|
keyCount += 1;
|
|
142
155
|
}
|
|
143
156
|
}
|
|
@@ -153,13 +166,13 @@ function addTimestampNgsi2(payload, timezone) {
|
|
|
153
166
|
if (payload instanceof Array) {
|
|
154
167
|
for (let i = 0; i < payload.length; i++) {
|
|
155
168
|
if (!utils.isTimestampedNgsi2(payload[i])) {
|
|
156
|
-
payload[i] = addTimestampEntity(payload[i], timezone);
|
|
169
|
+
payload[i] = addTimestampEntity(payload[i], timezone, timestampValue);
|
|
157
170
|
}
|
|
158
171
|
}
|
|
159
172
|
|
|
160
173
|
return payload;
|
|
161
174
|
}
|
|
162
|
-
return addTimestampEntity(payload, timezone);
|
|
175
|
+
return addTimestampEntity(payload, timezone, timestampValue);
|
|
163
176
|
}
|
|
164
177
|
|
|
165
178
|
/**
|
|
@@ -314,151 +327,14 @@ function sendQueryValueNgsi2(entityName, attributes, typeInformation, token, cal
|
|
|
314
327
|
);
|
|
315
328
|
}
|
|
316
329
|
|
|
317
|
-
function extractContextFromPayload(payload) {
|
|
318
|
-
//{b: {value: 3,type: "string"},c: {value: false,type: "string"},d:{value: {g: 45},type: "string"},id: "tamcaysanto",type: "WaterTankMulti"}
|
|
319
|
-
//to
|
|
320
|
-
//{b:3,c:false,d:{g:45}}
|
|
321
|
-
|
|
322
|
-
let ctx = {};
|
|
323
|
-
for (const key in payload) {
|
|
324
|
-
if (key !== 'type' && key !== 'id') {
|
|
325
|
-
ctx[key] = payload[key].value;
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
return ctx;
|
|
329
|
-
}
|
|
330
330
|
/**
|
|
331
|
-
*
|
|
331
|
+
* Determines if a value is of type float
|
|
332
332
|
*
|
|
333
|
-
* @param
|
|
334
|
-
* @
|
|
333
|
+
* @param {String} value Value to be analyzed
|
|
334
|
+
* @return {boolean} True if float, False otherwise.
|
|
335
335
|
*/
|
|
336
|
-
function
|
|
337
|
-
|
|
338
|
-
let effectiveList = [];
|
|
339
|
-
for (const attr of explicitAttrsList) {
|
|
340
|
-
if (typeof attr === 'string') {
|
|
341
|
-
effectiveList.push(attr);
|
|
342
|
-
} else if (typeof attr === 'object' && attr.object_id) {
|
|
343
|
-
//find objectId in active attributes
|
|
344
|
-
for (const active of typeInformation.active) {
|
|
345
|
-
if (active.object_id === attr.object_id) {
|
|
346
|
-
if (
|
|
347
|
-
active &&
|
|
348
|
-
active.name &&
|
|
349
|
-
active.entity_name &&
|
|
350
|
-
active.entity_name === entity.id
|
|
351
|
-
//if name is an expression it could fail... BUT IT WORKS!
|
|
352
|
-
) {
|
|
353
|
-
effectiveList.push(active.name);
|
|
354
|
-
}
|
|
355
|
-
continue; //object_id must be unique
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
return effectiveList;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
let explicitAttrsList;
|
|
364
|
-
if (typeInformation.explicitAttrs) {
|
|
365
|
-
explicitAttrsList = [];
|
|
366
|
-
explicitAttrsList.push('type');
|
|
367
|
-
explicitAttrsList.push('id');
|
|
368
|
-
if (typeof typeInformation.explicitAttrs === 'boolean') {
|
|
369
|
-
if (typeInformation.timestamp) {
|
|
370
|
-
explicitAttrsList.push(constants.TIMESTAMP_ATTRIBUTE);
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
if (typeInformation.active) {
|
|
374
|
-
typeInformation.active.forEach((attr) => {
|
|
375
|
-
explicitAttrsList.push(attr.name);
|
|
376
|
-
});
|
|
377
|
-
}
|
|
378
|
-
if (typeInformation.staticAttributes) {
|
|
379
|
-
typeInformation.staticAttributes.forEach((attr) => {
|
|
380
|
-
explicitAttrsList.push(attr.name);
|
|
381
|
-
});
|
|
382
|
-
}
|
|
383
|
-
} else if (typeof typeInformation.explicitAttrs === 'string') {
|
|
384
|
-
entities.forEach((entity) => {
|
|
385
|
-
const attsArray = pluginUtils.extractAttributesArrayFromNgsi2Entity(entity);
|
|
386
|
-
const ctx = {
|
|
387
|
-
...jexlParser.extractContext(attsArray),
|
|
388
|
-
...extractContextFromPayload(payload)
|
|
389
|
-
}; //maybe overlapping between this two objects.
|
|
390
|
-
let res = null;
|
|
391
|
-
try {
|
|
392
|
-
res = jexlParser.applyExpression(typeInformation.explicitAttrs, ctx, typeInformation);
|
|
393
|
-
} catch (e) {
|
|
394
|
-
// nothing to do: exception is already logged at info level
|
|
395
|
-
}
|
|
396
|
-
explicitAttrsList = res ? explicitAttrsList.concat(res) : explicitAttrsList;
|
|
397
|
-
});
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
if (explicitAttrsList && explicitAttrsList.length >= 0) {
|
|
401
|
-
entities.forEach((entity) => {
|
|
402
|
-
//some attrs are object_ids {object_id:oid} and need to be resolved for this entity
|
|
403
|
-
//"explicitAttrs" : "attrA?['attrA',{object_id:'foo'}]:['attrB',{object_id:'bar'}]",
|
|
404
|
-
let efectiveAttrsList = filterForEntity(entity, explicitAttrsList, typeInformation);
|
|
405
|
-
const hidden = _.difference(_.keys(entity), efectiveAttrsList);
|
|
406
|
-
logger.debug(context, 'removeHiddenAttrsFromMultiEntity %s from entity %s', hidden, entity);
|
|
407
|
-
hidden.forEach((attr) => {
|
|
408
|
-
delete entity[attr];
|
|
409
|
-
});
|
|
410
|
-
});
|
|
411
|
-
}
|
|
412
|
-
return entities;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
/**
|
|
416
|
-
* Remove id, type and any hidden attrs after processing
|
|
417
|
-
*
|
|
418
|
-
* @param {Object} result An Unprocessed entity
|
|
419
|
-
* @param {Object} typeInformation Configuration information for the device.
|
|
420
|
-
*/
|
|
421
|
-
function removeHiddenAttrs(result, typeInformation, payload) {
|
|
422
|
-
delete result.id;
|
|
423
|
-
delete result.type;
|
|
424
|
-
let explicitAttrsList;
|
|
425
|
-
if (typeInformation.explicitAttrs && typeof typeInformation.explicitAttrs === 'boolean') {
|
|
426
|
-
explicitAttrsList = [];
|
|
427
|
-
if (typeInformation.timestamp) {
|
|
428
|
-
explicitAttrsList.push(constants.TIMESTAMP_ATTRIBUTE);
|
|
429
|
-
}
|
|
430
|
-
if (typeInformation.active) {
|
|
431
|
-
typeInformation.active.forEach((attr) => {
|
|
432
|
-
explicitAttrsList.push(attr.name);
|
|
433
|
-
});
|
|
434
|
-
}
|
|
435
|
-
if (typeInformation.staticAttributes) {
|
|
436
|
-
typeInformation.staticAttributes.forEach((attr) => {
|
|
437
|
-
explicitAttrsList.push(attr.name);
|
|
438
|
-
});
|
|
439
|
-
}
|
|
440
|
-
} else if (typeInformation.explicitAttrs && typeof typeInformation.explicitAttrs === 'string') {
|
|
441
|
-
const attsArray = pluginUtils.extractAttributesArrayFromNgsi2Entity(result);
|
|
442
|
-
const ctx = {
|
|
443
|
-
...jexlParser.extractContext(attsArray),
|
|
444
|
-
...extractContextFromPayload(payload)
|
|
445
|
-
}; //maybe overlapping between this two objects. Measures not in active attrs.
|
|
446
|
-
let res = null;
|
|
447
|
-
try {
|
|
448
|
-
res = jexlParser.applyExpression(typeInformation.explicitAttrs, ctx, typeInformation);
|
|
449
|
-
} catch (e) {
|
|
450
|
-
// nothing to do: exception is already logged at info level
|
|
451
|
-
}
|
|
452
|
-
explicitAttrsList = res ? res : [];
|
|
453
|
-
}
|
|
454
|
-
if (explicitAttrsList && explicitAttrsList.length >= 0) {
|
|
455
|
-
const hidden = _.difference(_.keys(result), explicitAttrsList);
|
|
456
|
-
logger.debug(context, 'removeHiddenAttrs %s', hidden);
|
|
457
|
-
hidden.forEach((attr) => {
|
|
458
|
-
delete result[attr];
|
|
459
|
-
});
|
|
460
|
-
}
|
|
461
|
-
return result;
|
|
336
|
+
function isFloat(value) {
|
|
337
|
+
return !isNaN(value) && value.toString().indexOf('.') !== -1;
|
|
462
338
|
}
|
|
463
339
|
|
|
464
340
|
/**
|
|
@@ -471,12 +347,31 @@ function removeHiddenAttrs(result, typeInformation, payload) {
|
|
|
471
347
|
* @param {String} token User token to identify against the PEP Proxies (optional).
|
|
472
348
|
*/
|
|
473
349
|
function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, callback) {
|
|
474
|
-
|
|
350
|
+
logger.debug(
|
|
351
|
+
context,
|
|
352
|
+
'sendUpdateValueNgsi2 called with: \n entityName=%s \n attributes=%j \n typeInformation=%j',
|
|
353
|
+
entityName,
|
|
354
|
+
attributes,
|
|
355
|
+
typeInformation
|
|
356
|
+
);
|
|
357
|
+
let payload = {
|
|
358
|
+
entities: [
|
|
359
|
+
{
|
|
360
|
+
id: entityName
|
|
361
|
+
}
|
|
362
|
+
]
|
|
363
|
+
};
|
|
475
364
|
|
|
476
|
-
let url = '/v2/
|
|
365
|
+
let url = '/v2/op/update';
|
|
477
366
|
|
|
478
367
|
if (typeInformation && typeInformation.type) {
|
|
479
|
-
|
|
368
|
+
payload.entities[0].type = typeInformation.type;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (config.getConfig().appendMode === true) {
|
|
372
|
+
payload.actionType = 'append';
|
|
373
|
+
} else {
|
|
374
|
+
payload.actionType = 'update';
|
|
480
375
|
}
|
|
481
376
|
|
|
482
377
|
let options = NGSIUtils.createRequestObject(url, typeInformation, token);
|
|
@@ -489,173 +384,600 @@ function sendUpdateValueNgsi2(entityName, attributes, typeInformation, token, ca
|
|
|
489
384
|
callback(new errors.TypeNotFound(null, entityName));
|
|
490
385
|
return;
|
|
491
386
|
}
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
387
|
+
let idTypeSSSList = pluginUtils.getIdTypeServSubServiceFromDevice(typeInformation);
|
|
388
|
+
logger.debug(context, 'sendUpdateValueNgsi2 \n idTypeSSS are %j ', idTypeSSSList);
|
|
389
|
+
let measureAttrsForCtxt = [];
|
|
390
|
+
|
|
391
|
+
// Check explicitAttrs: adds all final needed attributes to payload
|
|
392
|
+
if (
|
|
393
|
+
typeInformation.explicitAttrs === undefined ||
|
|
394
|
+
(typeof typeInformation.explicitAttrs === 'boolean' && !typeInformation.explicitAttrs)
|
|
395
|
+
// explicitAttrs is not defined => default case: all attrs should be included
|
|
396
|
+
) {
|
|
397
|
+
// This loop adds all measure values (attributes) into payload entities (entity[0])
|
|
398
|
+
for (let i = 0; i < attributes.length; i++) {
|
|
399
|
+
if (attributes[i].name && attributes[i].type) {
|
|
400
|
+
payload.entities[0][attributes[i].name] = {
|
|
401
|
+
value: attributes[i].value,
|
|
402
|
+
type: attributes[i].type
|
|
403
|
+
};
|
|
404
|
+
const metadata = NGSIUtils.getMetaData(typeInformation, attributes[i].name, attributes[i].metadata);
|
|
405
|
+
if (metadata) {
|
|
406
|
+
payload.entities[0][attributes[i].name].metadata = metadata;
|
|
407
|
+
}
|
|
408
|
+
} else {
|
|
409
|
+
callback(new errors.BadRequest(null, entityName));
|
|
410
|
+
return;
|
|
502
411
|
}
|
|
503
|
-
} else {
|
|
504
|
-
callback(new errors.BadRequest(null, entityName));
|
|
505
|
-
return;
|
|
506
412
|
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
callback(error);
|
|
520
|
-
} else {
|
|
521
|
-
if (result) {
|
|
522
|
-
// The payload has been transformed by multientity plugin. It is not a JSON object but an Array.
|
|
523
|
-
if (result instanceof Array) {
|
|
524
|
-
options = NGSIUtils.createRequestObject('/v2/op/update', typeInformation, token);
|
|
525
|
-
|
|
526
|
-
if (
|
|
527
|
-
'timestamp' in typeInformation && typeInformation.timestamp !== undefined
|
|
528
|
-
? typeInformation.timestamp
|
|
529
|
-
: config.getConfig().timestamp
|
|
530
|
-
) {
|
|
531
|
-
// jshint maxdepth:5
|
|
532
|
-
if (!utils.isTimestampedNgsi2(result)) {
|
|
533
|
-
result = addTimestampNgsi2(result, typeInformation.timezone);
|
|
534
|
-
// jshint maxdepth:5
|
|
535
|
-
} else if (!utils.IsValidTimestampedNgsi2(result)) {
|
|
536
|
-
logger.error(context, 'Invalid timestamp:%s', JSON.stringify(result));
|
|
537
|
-
callback(new errors.BadTimestamp(result));
|
|
538
|
-
return;
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
options.json = {
|
|
542
|
-
actionType: 'append',
|
|
543
|
-
entities: removeHiddenAttrsFromMultiEntity(result, typeInformation, payload)
|
|
413
|
+
logger.debug(context, 'sendUpdateValueNgsi2 \n pre-initial non-explicitAttrs payload=%j', payload);
|
|
414
|
+
// Loop for add attrs from type.information.active (and lazys?) into payload entities (entity[0])
|
|
415
|
+
if (typeInformation.active) {
|
|
416
|
+
typeInformation.active.forEach((attr) => {
|
|
417
|
+
if (attr.expression) {
|
|
418
|
+
if (attr.object_id) {
|
|
419
|
+
payload.entities[0][attr.object_id] = {
|
|
420
|
+
value: payload.entities[0][attr.object_id]
|
|
421
|
+
? payload.entities[0][attr.object_id].value
|
|
422
|
+
: undefined,
|
|
423
|
+
type: attr.type,
|
|
424
|
+
object_id: attr.object_id
|
|
544
425
|
};
|
|
545
|
-
if (config.getConfig().appendMode === true) {
|
|
546
|
-
options.json.actionType = 'append';
|
|
547
|
-
} else {
|
|
548
|
-
options.json.actionType = 'update';
|
|
549
|
-
}
|
|
550
426
|
} else {
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
427
|
+
payload.entities[0][attr.name] = {
|
|
428
|
+
value: payload.entities[0][attr.name] ? payload.entities[0][attr.name].value : undefined,
|
|
429
|
+
type: attr.type
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
} else {
|
|
436
|
+
let selectedAttrs = [];
|
|
437
|
+
if (typeof typeInformation.explicitAttrs === 'string') {
|
|
438
|
+
// explicitAttrs is a jexlExpression
|
|
439
|
+
// This ctxt should include all possible attrs
|
|
440
|
+
let attributesCtxt = [];
|
|
441
|
+
if (typeInformation.static) {
|
|
442
|
+
typeInformation.static.forEach(function (att) {
|
|
443
|
+
attributesCtxt.push(att);
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
// Measures
|
|
447
|
+
for (let i = 0; i < attributes.length; i++) {
|
|
448
|
+
if (attributes[i].name && attributes[i].type) {
|
|
449
|
+
let measureAttr = {
|
|
450
|
+
name: attributes[i].name,
|
|
451
|
+
value: attributes[i].value,
|
|
452
|
+
type: attributes[i].type
|
|
453
|
+
};
|
|
454
|
+
attributesCtxt.push(measureAttr);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
// 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
|
+
if (typeInformation.active) {
|
|
461
|
+
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
|
|
467
|
+
type: att.type
|
|
468
|
+
};
|
|
469
|
+
attributesCtxt.push(expandedAttr);
|
|
470
|
+
ctxt = expressionPlugin.extractContext(
|
|
471
|
+
attributesCtxt.concat(idTypeSSSList),
|
|
472
|
+
typeInformation
|
|
473
|
+
);
|
|
570
474
|
}
|
|
571
475
|
}
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
// calculate expression for explicitAttrs
|
|
479
|
+
try {
|
|
480
|
+
let res = jexlParser.applyExpression(typeInformation.explicitAttrs, ctxt, typeInformation);
|
|
481
|
+
if (res === true) {
|
|
482
|
+
// like explicitAttrs == true
|
|
483
|
+
// selectAttrs should be measures which are defined attributes
|
|
484
|
+
typeInformation.active.forEach((attr) => {
|
|
485
|
+
selectedAttrs.push(attr.name);
|
|
486
|
+
selectedAttrs.push(attr.object_id);
|
|
487
|
+
});
|
|
488
|
+
} else if (res === false) {
|
|
489
|
+
// like explicitAttrs == false
|
|
490
|
+
// selectAttrs should be measures and defined attributes
|
|
491
|
+
typeInformation.active.forEach((attr) => {
|
|
492
|
+
selectedAttrs.push(attr.name);
|
|
493
|
+
selectedAttrs.push(attr.object_id);
|
|
494
|
+
});
|
|
495
|
+
for (let i = 0; i < attributes.length; i++) {
|
|
496
|
+
selectedAttrs.push(attributes[i].name);
|
|
497
|
+
}
|
|
572
498
|
} else {
|
|
573
|
-
|
|
574
|
-
delete payload.type;
|
|
575
|
-
options.json = payload;
|
|
499
|
+
selectedAttrs = res; // TBD: Check ensure is an array of strings
|
|
576
500
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
501
|
+
if (selectedAttrs.length === 0) {
|
|
502
|
+
// implies do nothing
|
|
503
|
+
logger.info(
|
|
504
|
+
context,
|
|
505
|
+
'sendUpdateValueNgsi2 \n none selectedAttrs with %j and ctxt %j',
|
|
506
|
+
typeInformation.explicitAttrs,
|
|
507
|
+
ctxt
|
|
508
|
+
);
|
|
509
|
+
return callback(null);
|
|
585
510
|
}
|
|
511
|
+
} catch (e) {
|
|
512
|
+
// nothing to do: exception is already logged at info level
|
|
513
|
+
}
|
|
586
514
|
|
|
587
|
-
|
|
588
|
-
if (
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
515
|
+
typeInformation.active.forEach((attr) => {
|
|
516
|
+
if (selectedAttrs.includes(attr.name)) {
|
|
517
|
+
selectedAttrs.push(attr.object_id);
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
} 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
|
|
523
|
+
typeInformation.active.forEach((attr) => {
|
|
524
|
+
selectedAttrs.push(attr.name);
|
|
525
|
+
selectedAttrs.push(attr.object_id);
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
// This loop adds selected measured values (attributes) into payload entities (entity[0])
|
|
529
|
+
for (let i = 0; i < attributes.length; i++) {
|
|
530
|
+
if (attributes[i].name && selectedAttrs.includes(attributes[i].name) && attributes[i].type) {
|
|
531
|
+
let attr = typeInformation.active.find((obj) => {
|
|
532
|
+
return obj.name === attributes[i].name;
|
|
533
|
+
});
|
|
534
|
+
payload.entities[0][attributes[i].name] = {
|
|
535
|
+
value: attributes[i].value,
|
|
536
|
+
type: attributes[i].type
|
|
537
|
+
};
|
|
538
|
+
// ensure payload has attr with proper object_id
|
|
539
|
+
if (attr && attr.object_id) {
|
|
540
|
+
payload.entities[0][attributes[i].name].object_id = attr.object_id;
|
|
541
|
+
}
|
|
542
|
+
const metadata = NGSIUtils.getMetaData(typeInformation, attributes[i].name, attributes[i].metadata);
|
|
543
|
+
if (metadata) {
|
|
544
|
+
payload.entities[0][attributes[i].name].metadata = metadata;
|
|
545
|
+
}
|
|
546
|
+
} else if (attributes[i].name && !selectedAttrs.includes(attributes[i].name) && attributes[i].type) {
|
|
547
|
+
let att = {
|
|
548
|
+
name: attributes[i].name,
|
|
549
|
+
type: attributes[i].type,
|
|
550
|
+
value: attributes[i].value
|
|
551
|
+
};
|
|
552
|
+
measureAttrsForCtxt.push(att);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
logger.debug(
|
|
556
|
+
context,
|
|
557
|
+
'sendUpdateValueNgsi2 \n pre-initial explicitAttrs payload=%j \n selectedAttrs',
|
|
558
|
+
payload,
|
|
559
|
+
selectedAttrs
|
|
560
|
+
);
|
|
561
|
+
|
|
562
|
+
// Loop for add seleted attrs from type.information.active into pyaload entities (entity[0])
|
|
563
|
+
if (typeInformation.active) {
|
|
564
|
+
typeInformation.active.forEach((attr) => {
|
|
565
|
+
if (selectedAttrs.includes(attr.name)) {
|
|
566
|
+
if (attr.object_id) {
|
|
567
|
+
payload.entities[0][attr.object_id] = {
|
|
568
|
+
value: payload.entities[0][attr.object_id]
|
|
569
|
+
? payload.entities[0][attr.object_id].value
|
|
570
|
+
: payload.entities[0][attr.name]
|
|
571
|
+
? payload.entities[0][attr.name].value
|
|
572
|
+
: undefined,
|
|
573
|
+
type: attr.type,
|
|
574
|
+
object_id: attr.object_id
|
|
575
|
+
};
|
|
576
|
+
} else {
|
|
577
|
+
payload.entities[0][attr.name] = {
|
|
578
|
+
value: payload.entities[0][attr.name] ? payload.entities[0][attr.name].value : undefined,
|
|
579
|
+
type: attr.type
|
|
580
|
+
};
|
|
610
581
|
}
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
} // END check explicitAttrs
|
|
586
|
+
logger.debug(context, 'sendUpdateValueNgsi2 \n initial payload=%j', payload);
|
|
587
|
+
|
|
588
|
+
let currentEntity = payload.entities[0];
|
|
589
|
+
|
|
590
|
+
// Prepare attributes for expresionPlugin
|
|
591
|
+
const attsArray = pluginUtils.extractAttributesArrayFromNgsi2Entity(currentEntity);
|
|
592
|
+
|
|
593
|
+
// Exclude processing all attr expressions when current attr is of type 'commandStatus' or 'commandResult'
|
|
594
|
+
let attsArrayFiltered = [];
|
|
595
|
+
if (attsArray) {
|
|
596
|
+
attsArrayFiltered = attsArray.filter((obj) => {
|
|
597
|
+
return ![constants.COMMAND_STATUS, constants.COMMAND_RESULT].includes(obj.type);
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
let attributesCtxt = [...attsArrayFiltered]; // just copy
|
|
601
|
+
if (typeInformation.static) {
|
|
602
|
+
typeInformation.static.forEach(function (att) {
|
|
603
|
+
attributesCtxt.push(att);
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
if (measureAttrsForCtxt) {
|
|
607
|
+
measureAttrsForCtxt.forEach(function (att) {
|
|
608
|
+
attributesCtxt.push(att);
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
attributesCtxt = attributesCtxt.concat(idTypeSSSList);
|
|
612
|
+
let ctxt = expressionPlugin.extractContext(attributesCtxt, typeInformation);
|
|
613
|
+
logger.debug(context, 'sendUpdateValueNgsi2 \n initial ctxt %j ', ctxt);
|
|
614
|
+
|
|
615
|
+
// Sort currentEntity to get first attrs without expressions (checking attrs in typeInformation.active)
|
|
616
|
+
// attributes without expressions should be processed before
|
|
617
|
+
logger.debug(context, 'sendUpdateValueNgsi2 \n currentEntity %j ', currentEntity);
|
|
618
|
+
if (typeInformation.active && typeInformation.active.length > 0) {
|
|
619
|
+
for (const k in currentEntity) {
|
|
620
|
+
typeInformation.active.forEach(function (att) {
|
|
621
|
+
if (
|
|
622
|
+
(att.object_id && att.object_id === k && att.expression) ||
|
|
623
|
+
(att.name && att.name === k && att.expression)
|
|
624
|
+
) {
|
|
625
|
+
let m = currentEntity[k];
|
|
626
|
+
delete currentEntity[k];
|
|
627
|
+
currentEntity[k] = m; // put into the end of currentEntity
|
|
628
|
+
}
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
logger.debug(context, 'sendUpdateValueNgsi2 \n currentEntity sorted %j ', currentEntity);
|
|
633
|
+
let timestampValue = undefined;
|
|
634
|
+
// Loop for each final attribute to apply alias, multientity and expressions
|
|
635
|
+
for (const j in currentEntity) {
|
|
636
|
+
// discard id and type
|
|
637
|
+
if (j !== 'id' || j !== 'type') {
|
|
638
|
+
// Apply Mapping Alias: object_id in attributes are in typeInformation.active
|
|
639
|
+
let attr;
|
|
640
|
+
let newAttr = payload.entities[0][j];
|
|
641
|
+
if (typeInformation.active) {
|
|
642
|
+
attr = typeInformation.active.find((obj) => {
|
|
643
|
+
return obj.object_id === j;
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
if (!attr) {
|
|
647
|
+
if (typeInformation.lazy) {
|
|
648
|
+
attr = typeInformation.lazy.find((obj) => {
|
|
649
|
+
return obj.object_id === j;
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
if (!attr) {
|
|
654
|
+
if (typeInformation.active) {
|
|
655
|
+
attr = typeInformation.active.find((obj) => {
|
|
656
|
+
return obj.name === j;
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
if (attr && attr.name) {
|
|
661
|
+
if (['id', 'type'].includes(attr.name)) {
|
|
662
|
+
// invalid mapping
|
|
663
|
+
logger.debug(
|
|
664
|
+
context,
|
|
665
|
+
'sendUpdateValueNgsi2 \n invalid mapping for attr %j \n newAttr %j',
|
|
666
|
+
attr,
|
|
667
|
+
newAttr
|
|
668
|
+
);
|
|
669
|
+
delete payload.entities[0][attr.object_id];
|
|
670
|
+
attr = undefined; // stop processing attr
|
|
671
|
+
newAttr = undefined;
|
|
611
672
|
} else {
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
673
|
+
ctxt[attr.name] = payload.entities[0][j]['value'];
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
logger.debug(
|
|
677
|
+
context,
|
|
678
|
+
'sendUpdateValueNgsi2 \n procesing j %j attr %j ctxt %j \n newAttr %j ',
|
|
679
|
+
j,
|
|
680
|
+
attr,
|
|
681
|
+
ctxt,
|
|
682
|
+
newAttr
|
|
683
|
+
);
|
|
684
|
+
if (attr && attr.type) {
|
|
685
|
+
newAttr.type = attr.type;
|
|
686
|
+
}
|
|
624
687
|
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
688
|
+
// Apply expression
|
|
689
|
+
if (attr && attr.expression) {
|
|
690
|
+
logger.debug(
|
|
691
|
+
context,
|
|
692
|
+
'sendUpdateValueNgsi2 \n apply expression %j \n over ctxt %j \n and device %j',
|
|
693
|
+
attr.expression,
|
|
694
|
+
ctxt,
|
|
695
|
+
typeInformation
|
|
696
|
+
);
|
|
697
|
+
let res = null;
|
|
698
|
+
try {
|
|
699
|
+
if (expressionPlugin.contextAvailable(attr.expression, ctxt, typeInformation)) {
|
|
700
|
+
res = expressionPlugin.applyExpression(attr.expression, ctxt, typeInformation);
|
|
701
|
+
} else {
|
|
702
|
+
logger.warn(
|
|
703
|
+
context,
|
|
704
|
+
'sendUpdateValueNgsi2 \n no context available for apply expression %j \n',
|
|
705
|
+
attr.expression
|
|
706
|
+
);
|
|
707
|
+
res = newAttr.value; // keep newAttr value
|
|
708
|
+
}
|
|
709
|
+
} 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
|
|
715
|
+
}
|
|
716
|
+
}
|
|
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
|
|
630
746
|
}
|
|
631
747
|
}
|
|
748
|
+
} else {
|
|
749
|
+
// jexl expression plugin
|
|
750
|
+
newAttr.value = res;
|
|
632
751
|
}
|
|
752
|
+
logger.debug(context, 'sendUpdateValueNgsi2 \n apply expression result %j \n newAttr %j', res, newAttr);
|
|
753
|
+
}
|
|
633
754
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
755
|
+
// Apply Multientity: entity_type and entity_name in attributes are in typeInformation.active
|
|
756
|
+
if (attr && (attr.entity_type || attr.entity_name)) {
|
|
757
|
+
// Create a newEntity for this attribute
|
|
758
|
+
let newEntityName = null;
|
|
759
|
+
if (attr.entity_name) {
|
|
760
|
+
try {
|
|
761
|
+
if (expressionPlugin.contextAvailable(attr.entity_name, ctxt, typeInformation)) {
|
|
762
|
+
newEntityName = expressionPlugin.applyExpression(attr.entity_name, ctxt, typeInformation);
|
|
763
|
+
} else {
|
|
764
|
+
logger.warn(
|
|
765
|
+
context,
|
|
766
|
+
'sendUpdateValueNgsi2 \n MULTI no context available for apply expression %j \n',
|
|
767
|
+
attr.entity_name
|
|
768
|
+
);
|
|
769
|
+
newEntityName = attr.entity_name;
|
|
770
|
+
}
|
|
771
|
+
newEntityName = newEntityName ? newEntityName : attr.entity_name;
|
|
772
|
+
} catch (e) {
|
|
773
|
+
logger.error(context, 'sendUpdateValueNgsi2 \n MULTI apply expression exception %j \n', e);
|
|
774
|
+
newEntityName = attr.entity_name;
|
|
775
|
+
}
|
|
637
776
|
logger.debug(
|
|
638
777
|
context,
|
|
639
|
-
'
|
|
640
|
-
|
|
778
|
+
'sendUpdateValueNgsi2 \n MULTI apply expression %j \n result %j \n payload %j',
|
|
779
|
+
attr.entity_name,
|
|
780
|
+
newEntityName,
|
|
781
|
+
payload
|
|
641
782
|
);
|
|
783
|
+
}
|
|
642
784
|
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
785
|
+
let newEntity = {
|
|
786
|
+
id: newEntityName ? newEntityName : payload.entities[0].id,
|
|
787
|
+
type: attr.entity_type ? attr.entity_type : payload.entities[0].type
|
|
788
|
+
};
|
|
789
|
+
// Check if there is already a newEntity created
|
|
790
|
+
let alreadyEntity = payload.entities.find((entity) => {
|
|
791
|
+
return entity.id === newEntity.id && entity.type === newEntity.type;
|
|
792
|
+
});
|
|
793
|
+
if (alreadyEntity) {
|
|
794
|
+
// Use alreadyEntity
|
|
795
|
+
alreadyEntity[attr.name] = newAttr;
|
|
647
796
|
} else {
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
797
|
+
// Add newEntity to payload.entities
|
|
798
|
+
newEntity[attr.name] = newAttr;
|
|
799
|
+
if (
|
|
800
|
+
'timestamp' in typeInformation && typeInformation.timestamp !== undefined
|
|
801
|
+
? typeInformation.timestamp
|
|
802
|
+
: config.getConfig().timestamp !== undefined
|
|
803
|
+
? config.getConfig().timestamp
|
|
804
|
+
: timestampValue !== undefined
|
|
805
|
+
) {
|
|
806
|
+
newEntity = addTimestampNgsi2(newEntity, typeInformation.timezone, timestampValue);
|
|
807
|
+
logger.debug(context, 'sendUpdateValueNgsi2 \n timestamped newEntity=%j', newEntity);
|
|
808
|
+
}
|
|
809
|
+
payload.entities.push(newEntity);
|
|
810
|
+
}
|
|
811
|
+
if (attr && attr.name) {
|
|
812
|
+
if (attr.name !== j) {
|
|
813
|
+
logger.debug(
|
|
814
|
+
context,
|
|
815
|
+
'sendUpdateValueNgsi2 \n MULTI remove measure attr %j keep alias j %j from %j \n',
|
|
816
|
+
j,
|
|
817
|
+
attr,
|
|
818
|
+
payload
|
|
819
|
+
);
|
|
820
|
+
delete payload.entities[0][j];
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
// if (attr && (attr.entity_type || attr.entity_name))
|
|
824
|
+
} else {
|
|
825
|
+
// Not a multientity attr
|
|
826
|
+
if (attr && attr.name) {
|
|
827
|
+
payload.entities[0][attr.name] = newAttr;
|
|
828
|
+
if (attr.name !== j) {
|
|
829
|
+
delete payload.entities[0][j]; // keep alias name, remove measure name
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
if (newAttr && newAttr.type === constants.TIMESTAMP_TYPE_NGSI2 && newAttr.value) {
|
|
833
|
+
let extendedTime = compressTimestampPlugin.fromBasicToExtended(newAttr.value);
|
|
834
|
+
if (extendedTime) {
|
|
835
|
+
// TBD: there is not flag about compressTimestamp in iotagent-node-lib,
|
|
836
|
+
// but there is one in agents
|
|
837
|
+
newAttr.value = extendedTime;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
if (j === constants.TIMESTAMP_ATTRIBUTE) {
|
|
841
|
+
if (newAttr && newAttr.type === constants.TIMESTAMP_TYPE_NGSI2 && newAttr.value) {
|
|
842
|
+
timestampValue = newAttr.value;
|
|
843
|
+
logger.debug(
|
|
844
|
+
context,
|
|
845
|
+
'sendUpdateValueNgsi2 \n newAttr is TimeInstant and new payload=%j',
|
|
846
|
+
payload
|
|
847
|
+
);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
if (
|
|
851
|
+
newAttr &&
|
|
852
|
+
newAttr.metadata &&
|
|
853
|
+
newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE] &&
|
|
854
|
+
newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].type === constants.TIMESTAMP_TYPE_NGSI2 &&
|
|
855
|
+
newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].value
|
|
856
|
+
) {
|
|
857
|
+
let extendedTime = compressTimestampPlugin.fromBasicToExtended(
|
|
858
|
+
newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].value
|
|
859
|
+
);
|
|
860
|
+
if (extendedTime) {
|
|
861
|
+
newAttr.metadata[constants.TIMESTAMP_ATTRIBUTE].value = extendedTime;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
} // if (j !== 'id' || j !== 'type')
|
|
866
|
+
|
|
867
|
+
// final attr loop
|
|
868
|
+
logger.debug(
|
|
869
|
+
context,
|
|
870
|
+
'sendUpdateValueNgsi2 \n after procesing attr %j \n current entity %j \n current payload=%j',
|
|
871
|
+
j,
|
|
872
|
+
currentEntity,
|
|
873
|
+
payload
|
|
874
|
+
);
|
|
875
|
+
}
|
|
876
|
+
// for attr loop
|
|
877
|
+
|
|
878
|
+
// Add timestamp to paylaod
|
|
879
|
+
if (
|
|
880
|
+
'timestamp' in typeInformation && typeInformation.timestamp !== undefined
|
|
881
|
+
? typeInformation.timestamp
|
|
882
|
+
: config.getConfig().timestamp !== undefined
|
|
883
|
+
? config.getConfig().timestamp
|
|
884
|
+
: timestampValue !== undefined
|
|
885
|
+
) {
|
|
886
|
+
if (timestampValue) {
|
|
887
|
+
// timeInstant is provided as measure
|
|
888
|
+
if (payload.entities.length > 1) {
|
|
889
|
+
for (let n = 0; n < payload.entities.length; n++) {
|
|
890
|
+
// include metadata with TimeInstant in attrs when TimeInstant is provided as measure in all entities
|
|
891
|
+
payload.entities[n] = addTimestampNgsi2(
|
|
892
|
+
payload.entities[n],
|
|
893
|
+
typeInformation.timezone,
|
|
894
|
+
timestampValue,
|
|
895
|
+
false // skipMetadataAtt
|
|
653
896
|
);
|
|
654
|
-
|
|
897
|
+
}
|
|
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
|
+
}
|
|
908
|
+
} else {
|
|
909
|
+
// jshint maxdepth:5
|
|
910
|
+
for (let n = 0; n < payload.entities.length; n++) {
|
|
911
|
+
if (!utils.isTimestampedNgsi2(payload.entities[n])) {
|
|
912
|
+
// legacy check needed?
|
|
913
|
+
payload.entities[n] = addTimestampNgsi2(payload.entities[n], typeInformation.timezone);
|
|
914
|
+
// jshint maxdepth:5
|
|
915
|
+
} else if (!utils.IsValidTimestampedNgsi2(payload.entities[n])) {
|
|
916
|
+
// legacy check needed?
|
|
917
|
+
logger.error(context, 'Invalid timestamp:%s', JSON.stringify(payload.entities[0]));
|
|
918
|
+
callback(new errors.BadTimestamp(payload.entities));
|
|
919
|
+
return;
|
|
655
920
|
}
|
|
656
921
|
}
|
|
657
922
|
}
|
|
658
|
-
|
|
923
|
+
}
|
|
924
|
+
logger.debug(context, 'sendUpdateValueNgsi2 \n ending payload=%j', payload);
|
|
925
|
+
|
|
926
|
+
for (let m = 0; m < payload.entities.length; m++) {
|
|
927
|
+
for (var key in payload.entities[m]) {
|
|
928
|
+
// purge object_id from payload
|
|
929
|
+
if (payload.entities[m][key] && payload.entities[m][key].object_id) {
|
|
930
|
+
delete payload.entities[m][key].object_id;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
payload.entities[m] = NGSIUtils.castJsonNativeAttributes(payload.entities[m]); // native types
|
|
934
|
+
}
|
|
935
|
+
logger.debug(context, 'sendUpdateValueNgsi2 \n payload with native types and without object_id %j', payload);
|
|
936
|
+
|
|
937
|
+
options.json = payload;
|
|
938
|
+
|
|
939
|
+
// Prevent to update an entity with an empty payload
|
|
940
|
+
if (
|
|
941
|
+
Object.keys(options.json).length > 0 &&
|
|
942
|
+
(options.json.entities.length > 1 ||
|
|
943
|
+
(options.json.entities.length === 1 && Object.keys(options.json.entities[0]).length > 2)) // more than id and type
|
|
944
|
+
) {
|
|
945
|
+
// Final check: (to keep tests unchanged) before do CB requests
|
|
946
|
+
// one entity -> request /v2/entities/ + entityName + /atts ?type=typeInformation.type
|
|
947
|
+
// multi entities -> request /v2/op/update
|
|
948
|
+
// Note that the options object is prepared for the second case (multi entity), so we "patch" it
|
|
949
|
+
// only in the first case
|
|
950
|
+
if (options.json.entities.length === 1) {
|
|
951
|
+
// recreate options object to use single entity update
|
|
952
|
+
url = '/v2/entities/' + entityName + '/attrs';
|
|
953
|
+
if (typeInformation && typeInformation.type) {
|
|
954
|
+
url += '?type=' + typeInformation.type;
|
|
955
|
+
}
|
|
956
|
+
options = NGSIUtils.createRequestObject(url, typeInformation, token);
|
|
957
|
+
options.json = payload.entities[0];
|
|
958
|
+
delete options.json.id;
|
|
959
|
+
delete options.json.type;
|
|
960
|
+
if (config.getConfig().appendMode === true) {
|
|
961
|
+
options.method = 'POST';
|
|
962
|
+
} else {
|
|
963
|
+
options.method = 'PATCH';
|
|
964
|
+
}
|
|
965
|
+
} // else: keep current options object created for a batch update
|
|
966
|
+
logger.debug(context, 'Updating device value in the Context Broker at [%s]', options.url);
|
|
967
|
+
logger.debug(context, 'Using the following NGSI v2 request:\n\n%s\n\n', JSON.stringify(options, null, 4));
|
|
968
|
+
request(
|
|
969
|
+
options,
|
|
970
|
+
generateNGSI2OperationHandler('update', entityName, typeInformation, token, options, callback)
|
|
971
|
+
);
|
|
972
|
+
} else {
|
|
973
|
+
logger.debug(
|
|
974
|
+
context,
|
|
975
|
+
'Not updating device value in the Context Broker at [%s] due to empty payload \n\n[%s]\n\n',
|
|
976
|
+
options.url,
|
|
977
|
+
JSON.stringify(options, null, 4)
|
|
978
|
+
);
|
|
979
|
+
callback(null);
|
|
980
|
+
}
|
|
659
981
|
}
|
|
660
982
|
|
|
661
983
|
exports.sendQueryValue = sendQueryValueNgsi2;
|