iotagent-node-lib 4.2.0 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/doc/api.md +178 -69
  2. package/docker/Mosquitto/Dockerfile +1 -1
  3. package/lib/commonConfig.js +18 -0
  4. package/lib/errors.js +48 -20
  5. package/lib/services/commands/commandRegistryMemory.js +12 -2
  6. package/lib/services/commands/commandRegistryMongoDB.js +26 -15
  7. package/lib/services/devices/deviceRegistryMemory.js +1 -1
  8. package/lib/services/devices/deviceRegistryMongoDB.js +7 -12
  9. package/lib/services/devices/deviceService.js +4 -3
  10. package/lib/services/devices/devices-NGSI-LD.js +3 -3
  11. package/lib/services/devices/devices-NGSI-v2.js +10 -4
  12. package/lib/services/groups/groupRegistryMemory.js +1 -1
  13. package/lib/services/groups/groupRegistryMongoDB.js +1 -1
  14. package/lib/services/groups/groupService.js +9 -3
  15. package/lib/services/ngsi/entities-NGSI-LD.js +15 -7
  16. package/lib/services/ngsi/entities-NGSI-v2.js +86 -54
  17. package/lib/services/ngsi/ngsiService.js +3 -3
  18. package/lib/services/ngsi/subscription-NGSI-LD.js +2 -0
  19. package/lib/services/ngsi/subscription-NGSI-v2.js +2 -0
  20. package/lib/services/northBound/contextServer-NGSI-LD.js +50 -55
  21. package/lib/services/northBound/contextServer-NGSI-v2.js +1 -1
  22. package/lib/services/northBound/deviceProvisioningServer.js +100 -26
  23. package/lib/services/northBound/restUtils.js +1 -1
  24. package/package.json +2 -2
  25. package/scripts/legacy_expression_tool/requirements.txt +1 -1
  26. package/test/functional/testCases.js +797 -66
  27. package/test/functional/testUtils.js +3 -1
  28. package/test/unit/ngsi-ld/ngsiService/active-devices-test.js +0 -1
  29. package/test/unit/ngsiv2/ngsiService/active-devices-test.js +8 -5
  30. package/test/unit/ngsiv2/plugins/multientity-plugin_test.js +34 -2
  31. package/test/unit/ngsiv2/provisioning/removeProvisionedDevice-test.js +37 -2
@@ -39,7 +39,7 @@ function findCommand(service, subservice, deviceId, name, callback) {
39
39
  name
40
40
  };
41
41
 
42
- logger.debug(context, 'Looking for command [%s] for device [%s]', name, deviceId);
42
+ logger.debug(context, 'Looking for command [%s] for device [%s] with [%j]', name, deviceId, queryObj);
43
43
 
44
44
  const query = Command.model.findOne(queryObj);
45
45
 
@@ -53,8 +53,14 @@ function findCommand(service, subservice, deviceId, name, callback) {
53
53
  } else if (data) {
54
54
  callback(null, data);
55
55
  } else {
56
- logger.debug(context, 'Command for DeviceID [%j] with name [%j] not found', deviceId, name);
57
- callback(new errors.CommandNotFound(name));
56
+ logger.debug(
57
+ context,
58
+ 'Command for DeviceID [%j] with name [%j] not found with [%j]',
59
+ deviceId,
60
+ name,
61
+ queryObj
62
+ );
63
+ callback(new errors.CommandNotFound(name, queryObj));
58
64
  }
59
65
  });
60
66
  }
@@ -124,15 +130,15 @@ function listCommands(service, subservice, deviceId, callback) {
124
130
 
125
131
  const query = Command.model.find(condition).sort();
126
132
 
127
- async.series([query.exec.bind(query), Command.model.countDocuments.bind(Command.model, condition)], function (
128
- error,
129
- results
130
- ) {
131
- callback(error, {
132
- count: results[1],
133
- commands: results[0].map(toObjectFn)
134
- });
135
- });
133
+ async.series(
134
+ [query.exec.bind(query), Command.model.countDocuments.bind(Command.model, condition)],
135
+ function (error, results) {
136
+ callback(error, {
137
+ count: results[1],
138
+ commands: results[0].map(toObjectFn)
139
+ });
140
+ }
141
+ );
136
142
  }
137
143
 
138
144
  function remove(service, subservice, deviceId, name, callback) {
@@ -159,9 +165,14 @@ function remove(service, subservice, deviceId, name, callback) {
159
165
 
160
166
  callback(null, commandResult);
161
167
  } else {
162
- logger.debug(context, 'Command [%s] not found for removal.', name);
163
-
164
- callback(new errors.CommandNotFound(name));
168
+ const deviceInfo = {
169
+ service,
170
+ subservice,
171
+ deviceId,
172
+ name
173
+ };
174
+ logger.debug(context, 'Command [%s] not found for removal with %j', name, deviceInfo);
175
+ callback(new errors.CommandNotFound(name, deviceInfo));
165
176
  }
166
177
  });
167
178
  }
@@ -54,7 +54,7 @@ function storeDevice(newDevice, callback) {
54
54
  }
55
55
 
56
56
  if (registeredDevices[newDevice.service][newDevice.id]) {
57
- callback(new errors.DuplicateDeviceId(newDevice.id));
57
+ callback(new errors.DuplicateDeviceId(newDevice));
58
58
  } else {
59
59
  registeredDevices[newDevice.service][newDevice.id] = deepClone(newDevice);
60
60
  registeredDevices[newDevice.service][newDevice.id].creationDate = Date.now();
@@ -56,6 +56,7 @@ const attributeList = [
56
56
  'timestamp',
57
57
  'explicitAttrs',
58
58
  'ngsiVersion',
59
+ 'subscriptions',
59
60
  'payloadType'
60
61
  ];
61
62
 
@@ -101,7 +102,7 @@ function storeDevice(newDevice, callback) {
101
102
  if (error.code === 11000) {
102
103
  logger.debug(context, 'Tried to insert a device with duplicate ID in the database: %s', error);
103
104
 
104
- callback(new errors.DuplicateDeviceId(newDevice.id));
105
+ callback(new errors.DuplicateDeviceId(newDevice));
105
106
  } else {
106
107
  logger.debug(context, 'Error storing device information: %s', error);
107
108
 
@@ -205,7 +206,7 @@ function findOneInMongoDB(queryParams, id, callback) {
205
206
  } else {
206
207
  logger.debug(context, 'Device [%s] not found.', id);
207
208
 
208
- callback(new errors.DeviceNotFound(id));
209
+ callback(new errors.DeviceNotFound(id, queryParams));
209
210
  }
210
211
  });
211
212
  }
@@ -242,14 +243,7 @@ function getDeviceById(id, apikey, service, subservice, callback) {
242
243
  function getDevice(id, apikey, service, subservice, callback) {
243
244
  getDeviceById(id, apikey, service, subservice, function (error, data) {
244
245
  if (error) {
245
- // Try without apikey: apikey will be added to device later
246
- getDeviceById(id, null, service, subservice, function (error, data) {
247
- if (error) {
248
- callback(error);
249
- } else {
250
- callback(null, data);
251
- }
252
- });
246
+ callback(error);
253
247
  } else {
254
248
  callback(null, data);
255
249
  }
@@ -281,7 +275,7 @@ function getByNameAndType(name, type, service, servicepath, callback) {
281
275
  } else {
282
276
  logger.debug(context, 'Device [%s] not found.', name);
283
277
 
284
- callback(new errors.DeviceNotFound(name));
278
+ callback(new errors.DeviceNotFound(name, optionsQuery));
285
279
  }
286
280
  });
287
281
  }
@@ -318,6 +312,7 @@ function update(device, callback) {
318
312
  data.explicitAttrs = device.explicitAttrs;
319
313
  data.ngsiVersion = device.ngsiVersion;
320
314
  data.timestamp = device.timestamp;
315
+ data.subscriptions = device.subscriptions;
321
316
  data.payloadType = device.payloadType;
322
317
 
323
318
  /* eslint-disable-next-line new-cap */
@@ -371,7 +366,7 @@ function getDevicesByAttribute(name, value, service, subservice, callback) {
371
366
  } else {
372
367
  logger.debug(context, 'Device [%s] not found.', name);
373
368
 
374
- callback(new errors.DeviceNotFound(name));
369
+ callback(new errors.DeviceNotFound(name, filter));
375
370
  }
376
371
  });
377
372
  }
@@ -244,7 +244,7 @@ function registerDevice(deviceObj, callback) {
244
244
  /* eslint-disable-next-line no-unused-vars */
245
245
  function (error, device) {
246
246
  if (!error) {
247
- innerCb(new errors.DuplicateDeviceId(deviceObj.id));
247
+ innerCb(new errors.DuplicateDeviceId(deviceObj));
248
248
  } else {
249
249
  innerCb();
250
250
  }
@@ -615,6 +615,7 @@ function findOrCreate(deviceId, apikey, group, callback) {
615
615
  } else if (error.name === 'DEVICE_NOT_FOUND') {
616
616
  const newDevice = {
617
617
  id: deviceId,
618
+ apikey: apikey,
618
619
  service: group.service,
619
620
  subservice: group.subservice,
620
621
  type: group.type
@@ -646,7 +647,7 @@ function findOrCreate(deviceId, apikey, group, callback) {
646
647
  newDevice,
647
648
  group
648
649
  );
649
- callback(new errors.DeviceNotFound(deviceId));
650
+ callback(new errors.DeviceNotFound(deviceId, newDevice));
650
651
  }
651
652
  } else {
652
653
  callback(error);
@@ -671,7 +672,7 @@ function retrieveDevice(deviceId, apiKey, callback) {
671
672
  } else {
672
673
  logger.error(context, "Couldn't find device data for APIKey [%s] and DeviceId[%s]", deviceId, apiKey);
673
674
 
674
- callback(new errors.DeviceNotFound(deviceId));
675
+ callback(new errors.DeviceNotFound(deviceId, { apikey: apiKey }));
675
676
  }
676
677
  });
677
678
  } else {
@@ -93,7 +93,7 @@ function updateEntityHandlerNgsiLD(deviceData, updatedDevice, callback) {
93
93
  body
94
94
  );
95
95
 
96
- const errorObj = new errors.EntityGenericError(deviceData.id, deviceData.type, body);
96
+ const errorObj = new errors.EntityGenericError(deviceData.id, deviceData.type, deviceData, body);
97
97
 
98
98
  callback(errorObj);
99
99
  }
@@ -184,7 +184,7 @@ function updateEntityNgsiLD(deviceData, updatedDevice, callback) {
184
184
  */
185
185
  function updateRegisterDeviceNgsiLD(deviceObj, entityInfoUpdated, callback) {
186
186
  if (!deviceObj.id || !deviceObj.type) {
187
- callback(new errors.MissingAttributes('Id or device missing'));
187
+ callback(new errors.MissingAttributes('Id or device missing', deviceObj));
188
188
  return;
189
189
  }
190
190
 
@@ -216,7 +216,7 @@ function updateRegisterDeviceNgsiLD(deviceObj, entityInfoUpdated, callback) {
216
216
 
217
217
  callback(null, oldDevice);
218
218
  } else {
219
- callback(new errors.DeviceNotFound(newDevice.id));
219
+ callback(new errors.DeviceNotFound(newDevice.id, newDevice));
220
220
  }
221
221
  }
222
222
 
@@ -89,7 +89,13 @@ function updateEntityHandlerNgsi2(deviceData, updatedDevice, callback) {
89
89
  body
90
90
  );
91
91
 
92
- const errorObj = new errors.EntityGenericError(deviceData.id, deviceData.type, body, response.statusCode);
92
+ const errorObj = new errors.EntityGenericError(
93
+ deviceData.id,
94
+ deviceData.type,
95
+ deviceData,
96
+ body,
97
+ response.statusCode
98
+ );
93
99
 
94
100
  callback(errorObj);
95
101
  }
@@ -204,7 +210,7 @@ function updateEntityNgsi2(deviceData, updatedDevice, callback) {
204
210
  // Format any GeoJSON attrs properly
205
211
  options.json[att] = NGSIv2.formatGeoAttrs(options.json[att]);
206
212
  } catch (error) {
207
- return callback(new errors.BadGeocoordinates(JSON.stringify(options.json)));
213
+ return callback(new errors.BadGeocoordinates(JSON.stringify(options.json), deviceData));
208
214
  }
209
215
  }
210
216
 
@@ -243,7 +249,7 @@ function updateEntityNgsi2(deviceData, updatedDevice, callback) {
243
249
  */
244
250
  function updateRegisterDeviceNgsi2(deviceObj, entityInfoUpdated, callback) {
245
251
  if (!deviceObj.id || !deviceObj.type) {
246
- callback(new errors.MissingAttributes('Id or device missing'));
252
+ callback(new errors.MissingAttributes('Id or device missing', deviceObj));
247
253
  return;
248
254
  }
249
255
 
@@ -282,7 +288,7 @@ function updateRegisterDeviceNgsi2(deviceObj, entityInfoUpdated, callback) {
282
288
 
283
289
  callback(null, oldDevice);
284
290
  } else {
285
- callback(new errors.DeviceNotFound(newDevice.id));
291
+ callback(new errors.DeviceNotFound(newDevice.id, newDevice));
286
292
  }
287
293
  }
288
294
 
@@ -50,7 +50,7 @@ function exists(group) {
50
50
 
51
51
  function createGroup(group, callback) {
52
52
  if (exists(group)) {
53
- callback(new errors.DuplicateGroup(group.resource, group.apikey));
53
+ callback(new errors.DuplicateGroup(group));
54
54
  } else {
55
55
  const storeGroup = _.clone(group);
56
56
 
@@ -110,7 +110,7 @@ function createGroup(group, callback) {
110
110
  group.apikey
111
111
  );
112
112
 
113
- callback(new errors.DuplicateGroup(group.resource, group.apikey));
113
+ callback(new errors.DuplicateGroup(group));
114
114
  } else {
115
115
  logger.debug(context, 'Error storing device group information: %s', error);
116
116
 
@@ -48,7 +48,7 @@ function validateGroup(group, callback) {
48
48
  return function (error, foundGroup) {
49
49
  logger.debug(context, 'generateDuplicateHander error %j and foundGroup %j', error, foundGroup);
50
50
  if (!error || (foundGroup && foundGroup.count > 0)) {
51
- innerCb(new errors.DuplicateGroup(group.resource, group.apikey));
51
+ innerCb(new errors.DuplicateGroup(group));
52
52
  } else {
53
53
  innerCb();
54
54
  }
@@ -291,8 +291,14 @@ function getEffectiveApiKey(service, subservice, type, callback) {
291
291
  logger.debug(context, 'Using default API Key: %s', config.getConfig().defaultKey);
292
292
  callback(null, config.getConfig().defaultKey);
293
293
  } else {
294
- logger.error(context, 'Could not find any API Key information for device.');
295
- callback(new errors.GroupNotFound(service, subservice));
294
+ logger.error(
295
+ context,
296
+ 'Could not find any APIKey information for device in service %s subservice %s and type %s',
297
+ service,
298
+ subservice,
299
+ type
300
+ );
301
+ callback(new errors.GroupNotFound(service, subservice, type));
296
302
  }
297
303
  }
298
304
 
@@ -381,9 +381,9 @@ function generateNGSILDOperationHandler(operationName, entityName, typeInformati
381
381
  }
382
382
 
383
383
  if (errorField !== undefined) {
384
- callback(new errors.DeviceNotFound(entityName));
384
+ callback(new errors.DeviceNotFound(entityName, typeInformation));
385
385
  } else {
386
- callback(new errors.EntityGenericError(entityName, typeInformation.type, body));
386
+ callback(new errors.EntityGenericError(entityName, typeInformation.type, typeInformation, body));
387
387
  }
388
388
  } else {
389
389
  logger.debug(context, 'Unknown error executing ' + operationName + ' operation');
@@ -391,7 +391,15 @@ function generateNGSILDOperationHandler(operationName, entityName, typeInformati
391
391
  body = JSON.parse(body);
392
392
  }
393
393
 
394
- callback(new errors.EntityGenericError(entityName, typeInformation.type, body, response.statusCode));
394
+ callback(
395
+ new errors.EntityGenericError(
396
+ entityName,
397
+ typeInformation.type,
398
+ typeInformation,
399
+ body,
400
+ response.statusCode
401
+ )
402
+ );
395
403
  }
396
404
  };
397
405
  }
@@ -416,7 +424,7 @@ function sendQueryValueNgsiLD(entityName, attributes, typeInformation, token, ca
416
424
  options.method = 'GET';
417
425
 
418
426
  if (!typeInformation || !typeInformation.type) {
419
- callback(new errors.TypeNotFound(null, entityName));
427
+ callback(new errors.TypeNotFound(null, entityName, typeInformation));
420
428
  return;
421
429
  }
422
430
 
@@ -529,7 +537,7 @@ function sendUpdateValueNgsiLD(entityName, attributes, typeInformation, token, c
529
537
  }
530
538
 
531
539
  if (!typeInformation || !typeInformation.type) {
532
- callback(new errors.TypeNotFound(null, entityName));
540
+ callback(new errors.TypeNotFound(null, entityName, typeInformation));
533
541
  return;
534
542
  }
535
543
  const idTypeSSSList = pluginUtils.getIdTypeServSubServiceFromDevice(typeInformation);
@@ -1015,7 +1023,7 @@ function sendUpdateValueNgsiLD(entityName, attributes, typeInformation, token, c
1015
1023
  } else if (!utils.IsValidTimestampedNgsi2(payload[n])) {
1016
1024
  // legacy check needed?
1017
1025
  logger.error(context, 'Invalid timestamp:%s', JSON.stringify(payload[0]));
1018
- callback(new errors.BadTimestamp(payload, entityName));
1026
+ callback(new errors.BadTimestamp(payload, entityName, typeInformation));
1019
1027
  return;
1020
1028
  }
1021
1029
  }
@@ -1044,7 +1052,7 @@ function sendUpdateValueNgsiLD(entityName, attributes, typeInformation, token, c
1044
1052
  options.json = [formatAsNGSILD(options.json)];
1045
1053
  }
1046
1054
  } catch (error) {
1047
- return callback(new errors.BadGeocoordinates(JSON.stringify(payload)));
1055
+ return callback(new errors.BadGeocoordinates(JSON.stringify(payload), typeInformation));
1048
1056
  }
1049
1057
 
1050
1058
  if (typeInformation.active) {
@@ -31,7 +31,6 @@ const request = require('../../request-shim');
31
31
  const alarms = require('../common/alarmManagement');
32
32
  const errors = require('../../errors');
33
33
  const pluginUtils = require('../../plugins/pluginUtils');
34
- const config = require('../../commonConfig');
35
34
  const constants = require('../../constants');
36
35
  const jexlParser = require('../../plugins/jexlParser');
37
36
  const expressionPlugin = require('../../plugins/expressionPlugin');
@@ -173,9 +172,9 @@ function generateNGSI2OperationHandler(operationName, entityName, typeInformatio
173
172
  }
174
173
 
175
174
  if (errorField !== undefined) {
176
- callback(new errors.DeviceNotFound(entityName));
175
+ callback(new errors.DeviceNotFound(entityName, typeInformation));
177
176
  } else {
178
- callback(new errors.EntityGenericError(entityName, typeInformation.type, body));
177
+ callback(new errors.EntityGenericError(entityName, typeInformation.type, typeInformation, body));
179
178
  }
180
179
  } else {
181
180
  logger.debug(context, 'Unknown error executing ' + operationName + ' operation');
@@ -183,7 +182,15 @@ function generateNGSI2OperationHandler(operationName, entityName, typeInformatio
183
182
  body = JSON.parse(body);
184
183
  }
185
184
 
186
- callback(new errors.EntityGenericError(entityName, typeInformation.type, body, response.statusCode));
185
+ callback(
186
+ new errors.EntityGenericError(
187
+ entityName,
188
+ typeInformation.type,
189
+ typeInformation,
190
+ body,
191
+ response.statusCode
192
+ )
193
+ );
187
194
  }
188
195
  };
189
196
  }
@@ -225,7 +232,7 @@ function sendQueryValueNgsi2(entityName, attributes, typeInformation, token, cal
225
232
  options.method = 'GET';
226
233
 
227
234
  if (!typeInformation || !typeInformation.type) {
228
- callback(new errors.TypeNotFound(null, entityName));
235
+ callback(new errors.TypeNotFound(null, entityName, typeInformation));
229
236
  return;
230
237
  }
231
238
 
@@ -270,7 +277,6 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call
270
277
  let entities = {}; //{entityName:{entityType:[attrs]}} //SubGoal Populate entoties data striucture
271
278
  let jexlctxt = {}; //will store the whole context (not just for JEXL)
272
279
  let payload = {}; //will store the final payload
273
- let timestamp = { type: constants.TIMESTAMP_TYPE_NGSI2 }; //timestamp scafold-attr for insertions.
274
280
  let plainMeasures = null; //will contain measures POJO
275
281
  let idTypeSSSList = pluginUtils.getIdTypeServSubServiceFromDevice(typeInformation);
276
282
 
@@ -279,7 +285,7 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call
279
285
 
280
286
  //Check mandatory information: type
281
287
  if (!typeInformation || !typeInformation.type) {
282
- callback(new errors.TypeNotFound(null, entityName));
288
+ callback(new errors.TypeNotFound(null, entityName, typeInformation));
283
289
  return;
284
290
  }
285
291
  //Rename all measures with matches with id and type to measure_id and measure_type
@@ -301,42 +307,16 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call
301
307
  jexlctxt = reduceAttrToPlainObject(idTypeSSSList, jexlctxt);
302
308
 
303
309
  //Managing timestamp (mustInsertTimeInstant flag to decide if we should insert Timestamp later on)
304
- const mustInsertTimeInstant =
305
- typeInformation.timestamp !== undefined
306
- ? typeInformation.timestamp
307
- : config.getConfig().timestamp !== undefined
308
- ? config.getConfig().timestamp
309
- : false;
310
-
311
- if (mustInsertTimeInstant) {
312
- //remove TimeInstant from measures
313
- measures = measures.filter((item) => item.name !== constants.TIMESTAMP_ATTRIBUTE);
314
-
315
- if (plainMeasures[constants.TIMESTAMP_ATTRIBUTE]) {
316
- //if it comes from a measure
317
- if (moment(plainMeasures[constants.TIMESTAMP_ATTRIBUTE], moment.ISO_8601, true).isValid()) {
318
- timestamp.value = plainMeasures[constants.TIMESTAMP_ATTRIBUTE];
319
- } else {
320
- callback(new errors.BadTimestamp(plainMeasures[constants.TIMESTAMP_ATTRIBUTE], entityName));
321
- }
322
- } else if (!typeInformation.timezone) {
323
- timestamp.value = new Date().toISOString();
324
- jexlctxt[constants.TIMESTAMP_ATTRIBUTE] = timestamp.value;
325
- } else {
326
- timestamp.value = moment().tz(typeInformation.timezone).format('YYYY-MM-DD[T]HH:mm:ss.SSSZ');
327
- jexlctxt[constants.TIMESTAMP_ATTRIBUTE] = timestamp.value;
328
- }
329
- }
310
+ const mustInsertTimeInstant = typeInformation.timestamp !== undefined ? typeInformation.timestamp : false;
330
311
 
331
312
  logger.debug(
332
313
  context,
333
- 'sendUpdateValueNgsi2 called with: entityName=%s, measures=%j, typeInformation=%j, initial jexlContext=%j, timestamp=%j with value=%j',
314
+ 'sendUpdateValueNgsi2 called with: entityName=%s, measures=%j, typeInformation=%j, initial jexlContext=%j, timestamp=%j',
334
315
  entityName,
335
316
  plainMeasures,
336
317
  typeInformation,
337
318
  jexlctxt,
338
- mustInsertTimeInstant,
339
- timestamp.value
319
+ mustInsertTimeInstant
340
320
  );
341
321
 
342
322
  //Now we can calculate the EntityName of primary entity
@@ -471,14 +451,6 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call
471
451
  currentAttr.hitted = hitted;
472
452
  currentAttr.value = valueExpression;
473
453
 
474
- //add TimeInstant to attr metadata
475
- if (mustInsertTimeInstant) {
476
- if (!currentAttr.metadata) {
477
- currentAttr.metadata = {};
478
- }
479
- currentAttr.metadata[constants.TIMESTAMP_ATTRIBUTE] = timestamp;
480
- }
481
-
482
454
  //store de New Attributte in entity data structure
483
455
  if (hitted === true) {
484
456
  if (entities[attrEntityName] === undefined) {
@@ -493,6 +465,38 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call
493
465
 
494
466
  //RE-Populate de JEXLcontext (except for null or NaN we preffer undefined)
495
467
  jexlctxt[currentAttr.name] = valueExpression;
468
+
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
+ ) {
490
+ metaValueExpression = null;
491
+ }
492
+ } catch (e) {
493
+ metaValueExpression = null;
494
+ }
495
+ newAttrMeta['value'] = metaValueExpression;
496
+ currentAttr.metadata[metaKey] = newAttrMeta;
497
+ }
498
+ }
499
+ }
496
500
  }
497
501
 
498
502
  //now we can compute explicit (Bool or Array) with the complete JexlContext
@@ -519,16 +523,6 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call
519
523
 
520
524
  //more mesures may be added to the attribute list (unnhandled/left mesaures) l
521
525
  if (explicit === false && Object.keys(measures).length > 0) {
522
- //add Timestamp to measures if needed
523
- if (mustInsertTimeInstant) {
524
- for (let currentMeasure of measures) {
525
- if (!currentMeasure.metadata) {
526
- currentMeasure.metadata = {};
527
- }
528
- currentMeasure.metadata[constants.TIMESTAMP_ATTRIBUTE] = timestamp;
529
- }
530
- //If just measures in the principal entity we missed the Timestamp.
531
- }
532
526
  entities[entityName][typeInformation.type] = entities[entityName][typeInformation.type].concat(measures);
533
527
  }
534
528
 
@@ -539,11 +533,40 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call
539
533
  payload.actionType = 'append';
540
534
 
541
535
  payload.entities = [];
536
+ const currentIsoDate = new Date().toISOString();
537
+ const currentMoment = moment(currentIsoDate);
542
538
  for (let ename in entities) {
543
539
  for (let etype in entities[ename]) {
544
540
  let e = {};
545
541
  e.id = String(ename);
546
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
+ }
552
+
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;
557
+ }
558
+ } else {
559
+ if (!typeInformation.timezone) {
560
+ timestamp.value = currentIsoDate;
561
+ jexlctxt[constants.TIMESTAMP_ATTRIBUTE] = timestamp.value;
562
+ } 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;
567
+ }
568
+ }
569
+ }
547
570
  //extract attributes
548
571
  let isEmpty = true;
549
572
  for (let attr of entities[ename][etype]) {
@@ -560,6 +583,15 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call
560
583
  ))))
561
584
  ) {
562
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 = {};
591
+ }
592
+ attr.metadata[constants.TIMESTAMP_ATTRIBUTE] = timestamp;
593
+ }
594
+ }
563
595
  e[attr.name] = { type: attr.type, value: attr.value, metadata: attr.metadata };
564
596
  }
565
597
  }
@@ -144,9 +144,9 @@ function executeWithDeviceInformation(operationFunction) {
144
144
  // For preregistered devices, augment the existing deviceInformation with selected attributes.
145
145
  if (!callback) {
146
146
  callback = deviceInformation;
147
- typeInformation = deviceGroup || configDeviceInfo;
147
+ typeInformation = deviceGroup || { ...config.getConfigForTypeInformation(), ...configDeviceInfo };
148
148
  } else {
149
- typeInformation = deviceInformation;
149
+ typeInformation = { ...config.getConfigForTypeInformation(), ...deviceInformation };
150
150
  attributeList.forEach((key) => {
151
151
  typeInformation[key] =
152
152
  typeInformation[key] || (deviceGroup || {})[key] || (configDeviceInfo || {})[key];
@@ -236,7 +236,7 @@ function setCommandResult(
236
236
  if (commandInfo.length === 1) {
237
237
  exports.update(entityName, typeInformation.type, apikey, attributes, typeInformation, callback);
238
238
  } else {
239
- callback(new errors.CommandNotFound(commandName));
239
+ callback(new errors.CommandNotFound(commandName, typeInformation));
240
240
  }
241
241
  });
242
242
  }
@@ -62,6 +62,7 @@ function createSubscriptionHandlerNgsiLD(device, triggers, store, callback) {
62
62
  new errors.EntityGenericError(
63
63
  device.name,
64
64
  device.type,
65
+ device,
65
66
  {
66
67
  details: body
67
68
  },
@@ -183,6 +184,7 @@ function createUnsubscribeHandlerNgsiLD(device, id, callback) {
183
184
  new errors.EntityGenericError(
184
185
  device.name,
185
186
  device.type,
187
+ device,
186
188
  {
187
189
  details: body
188
190
  },
@@ -64,6 +64,7 @@ function createSubscriptionHandlerNgsi2(device, triggers, store, callback) {
64
64
  new errors.EntityGenericError(
65
65
  device.name,
66
66
  device.type,
67
+ device,
67
68
  {
68
69
  details: body
69
70
  },
@@ -183,6 +184,7 @@ function createUnsubscribeHandlerNgsi2(device, id, callback) {
183
184
  new errors.EntityGenericError(
184
185
  device.name,
185
186
  device.type,
187
+ device,
186
188
  {
187
189
  details: body
188
190
  },