iotagent-node-lib 4.9.0 → 4.10.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 (43) hide show
  1. package/.github/workflows/ci.yml +6 -8
  2. package/Changelog +6 -0
  3. package/config.js +2 -1
  4. package/doc/admin.md +12 -0
  5. package/doc/api.md +33 -3
  6. package/doc/devel/northboundinteractions.md +200 -112
  7. package/lib/commonConfig.js +5 -1
  8. package/lib/model/Device.js +3 -1
  9. package/lib/model/Group.js +2 -1
  10. package/lib/services/common/iotManagerService.js +2 -1
  11. package/lib/services/devices/deviceRegistryMongoDB.js +5 -1
  12. package/lib/services/devices/deviceService.js +17 -1
  13. package/lib/services/devices/devices-NGSI-LD.js +3 -2
  14. package/lib/services/devices/devices-NGSI-mixed.js +16 -0
  15. package/lib/services/devices/devices-NGSI-v2.js +122 -8
  16. package/lib/services/devices/registrationUtils.js +97 -30
  17. package/lib/services/groups/groupRegistryMongoDB.js +2 -1
  18. package/lib/services/ngsi/subscription-NGSI-LD.js +2 -2
  19. package/lib/services/ngsi/subscription-NGSI-mixed.js +3 -3
  20. package/lib/services/ngsi/subscription-NGSI-v2.js +20 -6
  21. package/lib/services/ngsi/subscriptionService.js +2 -2
  22. package/lib/services/northBound/deviceProvisioningServer.js +6 -3
  23. package/lib/templates/updateDevice.json +12 -0
  24. package/lib/templates/updateDeviceLax.json +4 -0
  25. package/package.json +2 -2
  26. package/test/unit/general/contextBrokerKeystoneSecurityAccess-test.js +2 -2
  27. package/test/unit/memoryRegistry/deviceRegistryMemory_test.js +1 -1
  28. package/test/unit/ngsi-ld/general/contextBrokerOAuthSecurityAccess-test.js +2 -2
  29. package/test/unit/ngsi-ld/general/https-support-test.js +1 -1
  30. package/test/unit/ngsi-ld/ngsiService/subscriptions-test.js +6 -6
  31. package/test/unit/ngsiv2/examples/contextAvailabilityRequests/subscribeIoTAgentCommands.json +24 -0
  32. package/test/unit/ngsiv2/examples/contextAvailabilityRequests/subscribeIoTAgentCommands2.json +24 -0
  33. package/test/unit/ngsiv2/examples/contextAvailabilityRequests/subscribeIoTAgentCommands3.json +24 -0
  34. package/test/unit/ngsiv2/examples/contextAvailabilityRequests/subscribeIoTAgentCommands4.json +24 -0
  35. package/test/unit/ngsiv2/examples/contextRequests/updateEntity.json +5 -0
  36. package/test/unit/ngsiv2/examples/contextRequests/updateEntity2.json +5 -0
  37. package/test/unit/ngsiv2/examples/contextRequests/updateEntity2b.json +7 -0
  38. package/test/unit/ngsiv2/examples/contextRequests/updateEntity3.json +5 -0
  39. package/test/unit/ngsiv2/examples/subscriptionRequests/simpleSubscriptionRequest.json +4 -2
  40. package/test/unit/ngsiv2/examples/subscriptionRequests/simpleSubscriptionRequest2.json +4 -2
  41. package/test/unit/ngsiv2/general/contextBrokerOAuthSecurityAccess-test.js +2 -2
  42. package/test/unit/ngsiv2/general/https-support-test.js +1 -1
  43. package/test/unit/ngsiv2/lazyAndCommands/command-test.js +245 -2
@@ -59,7 +59,9 @@ const attributeList = [
59
59
  'subscriptions',
60
60
  'payloadType',
61
61
  'useCBflowControl',
62
- 'storeLastMeasure'
62
+ 'storeLastMeasure',
63
+ 'cmdMode',
64
+ 'subscriptionId'
63
65
  ];
64
66
 
65
67
  /**
@@ -324,6 +326,8 @@ function update(previousDevice, device, callback) {
324
326
  data.useCBflowControl = device.useCBflowControl;
325
327
  data.storeLastMeasure = device.storeLastMeasure;
326
328
  data.lastMeasure = device.lastMeasure;
329
+ data.cmdMode = device.cmdMode;
330
+ data.subscriptionId = device.subscriptionId;
327
331
 
328
332
  /* eslint-disable-next-line new-cap */
329
333
  const deviceObj = new Device.model(data);
@@ -66,6 +66,17 @@ function init() {
66
66
  }
67
67
  }
68
68
 
69
+ /**
70
+ * Creates the initial entity representing the device in the Context Broker. This is important mainly to allow the
71
+ * rest of the updateContext operations to be performed using an UPDATE action instead of an APPEND one.
72
+ *
73
+ * @param {Object} deviceData Object containing all the deviceData needed to send the registration.
74
+ * @param {Object} newDevice Device object that will be stored in the database.
75
+ */
76
+ function createInitialEntity(deviceData, newDevice, callback) {
77
+ deviceHandler.createInitialEntity(deviceData, newDevice, callback);
78
+ }
79
+
69
80
  /**
70
81
  * If the object_id or the name of the attribute is missing, complete it with the other piece of data.
71
82
  *
@@ -187,6 +198,9 @@ function mergeDeviceWithConfiguration(fields, defaults, deviceData, configuratio
187
198
  if (configuration && configuration.storeLastMeasure !== undefined && deviceData.storeLastMeasure === undefined) {
188
199
  deviceData.storeLastMeasure = configuration.storeLastMeasure;
189
200
  }
201
+ if (configuration && configuration.cmdMode !== undefined && deviceData.cmdMode === undefined) {
202
+ deviceData.cmdMode = configuration.cmdMode;
203
+ }
190
204
  logger.debug(context, 'deviceData after merge with conf: %j', deviceData);
191
205
  callback(null, deviceData);
192
206
  }
@@ -361,13 +375,15 @@ function registerDevice(deviceObj, callback) {
361
375
  async.waterfall(
362
376
  [
363
377
  apply(registrationUtils.sendRegistrations, false, deviceData),
364
- apply(registrationUtils.processContextRegistration, deviceData)
378
+ apply(registrationUtils.processContextRegistration, deviceData),
379
+ apply(createInitialEntity, deviceData)
365
380
  ],
366
381
  function (error, results) {
367
382
  if (error) {
368
383
  callback(error);
369
384
  } else {
370
385
  deviceObj.registrationId = results.registrationId;
386
+ deviceObj.subscriptionId = results.subscriptionId;
371
387
  deviceObj.name = deviceData.name;
372
388
  deviceObj.service = deviceData.service;
373
389
  deviceObj.subservice = deviceData.subservice;
@@ -106,7 +106,7 @@ function updateEntityHandlerNgsiLD(deviceData, updatedDevice, callback) {
106
106
  * @param {Object} deviceData Object containing all the deviceData needed to send the registration.
107
107
  * @param {Object} newDevice Device object that will be stored in the database.
108
108
  */
109
- function createInitialEntityNgsiLDFake(deviceData, newDevice, callback) {
109
+ function createInitialEntityNgsiLD(deviceData, newDevice, callback) {
110
110
  callback(null, newDevice);
111
111
  }
112
112
 
@@ -284,7 +284,7 @@ function updateRegisterDeviceNgsiLD(deviceObj, previousDevice, entityInfoUpdated
284
284
  deviceObj.subservice
285
285
  ),
286
286
  apply(extractDeviceDifference, deviceObj),
287
- createInitialEntityNgsiLDFake,
287
+ createInitialEntityNgsiLD,
288
288
  apply(combineWithNewDevice, deviceObj),
289
289
  apply(registrationUtils.sendRegistrations, false),
290
290
  apply(registrationUtils.processContextRegistration, deviceObj),
@@ -314,4 +314,5 @@ function updateRegisterDeviceNgsiLD(deviceObj, previousDevice, entityInfoUpdated
314
314
  }
315
315
  }
316
316
 
317
+ exports.createInitialEntity = createInitialEntityNgsiLD;
317
318
  exports.updateRegisterDevice = updateRegisterDeviceNgsiLD;
@@ -27,6 +27,21 @@ const config = require('../../commonConfig');
27
27
  const deviceHandlerLD = require('./devices-NGSI-LD');
28
28
  const deviceHandlerV2 = require('./devices-NGSI-v2');
29
29
 
30
+ /**
31
+ * Creates the initial entity representing the device in the Context Broker using both NGSI-LD and NGSI-v2
32
+ * This is important mainly to allow the rest of the updateContext operations to be performed.
33
+ *
34
+ * @param {Object} deviceData Object containing all the deviceData needed to send the registration.
35
+ * @param {Object} newDevice Device object that will be stored in the database.
36
+ */
37
+ function createInitialEntityNgsiMixed(deviceData, newDevice, callback) {
38
+ if (config.checkNgsiLD(deviceData)) {
39
+ deviceHandlerLD.createInitialEntity(deviceData, newDevice, callback);
40
+ } else {
41
+ deviceHandlerV2.createInitialEntity(deviceData, newDevice, callback);
42
+ }
43
+ }
44
+
30
45
  /**
31
46
  * Updates the register of an existing device identified by the Id and Type in the Context Broker, and the internal
32
47
  * registry. It uses both NGSI-LD and NGSI-v2
@@ -46,4 +61,5 @@ function updateRegisterDeviceNgsiMixed(deviceObj, previousDevice, entityInfoUpda
46
61
  }
47
62
  }
48
63
 
64
+ exports.createInitialEntity = createInitialEntityNgsiMixed;
49
65
  exports.updateRegisterDevice = updateRegisterDeviceNgsiMixed;
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright 2020 Telefonica Investigación y Desarrollo, S.A.U
2
+ * Copyright 2020 Telefonica Investigación y Desarrollo, S.A.U
3
3
  *
4
4
  * This file is part of fiware-iotagent-lib
5
5
  *
@@ -164,12 +164,102 @@ function formatCommandsNgsi2(originalVector) {
164
164
  return attributeList;
165
165
  }
166
166
 
167
+ function formatCommandsBySubsNgsi2(originalVector) {
168
+ const attributeList = {};
169
+
170
+ if (originalVector && originalVector.length) {
171
+ for (let i = 0; i < originalVector.length; i++) {
172
+ attributeList[originalVector[i].name] = {
173
+ type: 'command',
174
+ value: null
175
+ };
176
+ }
177
+ }
178
+
179
+ return attributeList;
180
+ }
181
+
182
+ function createInitialEntityHandlerNgsi2(deviceData, newDevice, callback) {
183
+ return function handleInitialEntityResponse(error, response, body) {
184
+ if (error) {
185
+ logger.error(
186
+ context,
187
+ 'ORION-001: Connection error creating inital entity in the Context Broker: %s',
188
+ error
189
+ );
190
+
191
+ alarms.raise(constants.ORION_ALARM, error);
192
+
193
+ callback(error);
194
+ } else if (response && response.statusCode === 204) {
195
+ alarms.release(constants.ORION_ALARM);
196
+ logger.debug(context, 'Initial entity created successfully.');
197
+ callback(null, newDevice);
198
+ } else {
199
+ logger.error(
200
+ context,
201
+ 'Protocol error connecting to the Context Broker [%d]: %s',
202
+ response.statusCode,
203
+ body
204
+ );
205
+
206
+ const errorObj = new errors.EntityGenericError(newDevice.id, newDevice.type, body, response.statusCode);
207
+
208
+ callback(errorObj);
209
+ }
210
+ };
211
+ }
212
+
167
213
  /*
168
214
  * This methods makes a bypass in updateRegisterDeviceNgsi2 to allow not change
169
215
  * extractDeviceDifference and combineWithNewDevice methods
170
216
  */
171
- function createInitialEntityNgsi2Fake(deviceData, newDevice, callback) {
172
- callback(null, newDevice);
217
+ function createInitialEntityNgsi2(newDevice, deviceObj, callback) {
218
+ logger.debug(context, 'createInitialEntityNgsiv2 called with newDevice: %j', newDevice);
219
+
220
+ const cmdModeConfig = config.getConfig().cmdMode;
221
+
222
+ const isLegacy =
223
+ (!newDevice.cmdMode && (!cmdModeConfig || cmdModeConfig === 'legacy')) || newDevice.cmdMode === 'legacy';
224
+ if (isLegacy) {
225
+ logger.debug(context, 'Old cmdMode with newDevice: %j', deviceObj);
226
+ return callback(null, deviceObj);
227
+ }
228
+
229
+ if (!Array.isArray(newDevice.commands) || newDevice.commands.length === 0) {
230
+ logger.debug(context, 'No initial entity due to no commands by subs in newDevice: %j', newDevice);
231
+ return callback(null, deviceObj);
232
+ }
233
+
234
+ const entityPayload = {
235
+ id: String(newDevice.name),
236
+ type: newDevice.type
237
+ };
238
+ jsonConcat(entityPayload, formatCommandsBySubsNgsi2(newDevice.commands));
239
+
240
+ let url = config.getConfig().contextBroker.url + '/v2/entities?options=upsert';
241
+ if (newDevice.cbHost) {
242
+ url = newDevice.cbHost.includes('://')
243
+ ? newDevice.cbHost + '/v2/entities?options=upsert'
244
+ : 'http://' + newDevice.cbHost + '/v2/entities?options=upsert';
245
+ }
246
+
247
+ const headers = {
248
+ 'fiware-correlator': (domain.active && domain.active.corr) || uuid.v4()
249
+ };
250
+ if (newDevice.service) headers['fiware-service'] = newDevice.service;
251
+ if (newDevice.subservice) headers['fiware-servicepath'] = newDevice.subservice;
252
+
253
+ const options = { url, method: 'POST', json: entityPayload, headers };
254
+
255
+ logger.debug(context, 'Creating initial entity due to commands by subs with newDevice: %j', newDevice);
256
+ logger.debug(context, 'Creating initial entity in the Context Broker:\n %s', JSON.stringify(options, null, 4));
257
+
258
+ return utils.executeWithSecurity(
259
+ options,
260
+ newDevice,
261
+ createInitialEntityHandlerNgsi2(newDevice, deviceObj, callback)
262
+ );
173
263
  }
174
264
 
175
265
  /**
@@ -179,6 +269,7 @@ function createInitialEntityNgsi2Fake(deviceData, newDevice, callback) {
179
269
  * @param {Object} updatedDevice Device object that will be stored in the database.
180
270
  */
181
271
  function updateEntityNgsi2(deviceData, updatedDevice, callback) {
272
+ logger.debug(context, 'updateEntityNgsi2 called with deviceData: %j updatedDevice: %j', deviceData, updatedDevice);
182
273
  const options = {
183
274
  url: config.getConfig().contextBroker.url + '/v2/entities/' + String(deviceData.name) + '/attrs',
184
275
  method: 'POST',
@@ -202,7 +293,14 @@ function updateEntityNgsi2(deviceData, updatedDevice, callback) {
202
293
 
203
294
  jsonConcat(options.json, formatAttributesNgsi2(deviceData.active, false));
204
295
  jsonConcat(options.json, formatAttributesNgsi2(deviceData.staticAttributes, true));
205
- jsonConcat(options.json, formatCommandsNgsi2(deviceData.commands));
296
+ if (
297
+ (!updatedDevice.cmdMode && (!config.getConfig().cmdMode || config.getConfig().cmdMode === 'legacy')) ||
298
+ (updatedDevice && updatedDevice.cmdMode === 'legacy')
299
+ ) {
300
+ jsonConcat(options.json, formatCommandsNgsi2(deviceData.commands));
301
+ } else {
302
+ jsonConcat(options.json, formatCommandsBySubsNgsi2(deviceData.commands));
303
+ }
206
304
 
207
305
  for (const att in options.json) {
208
306
  try {
@@ -252,7 +350,13 @@ function updateRegisterDeviceNgsi2(deviceObj, previousDevice, entityInfoUpdated,
252
350
  return;
253
351
  }
254
352
 
255
- logger.debug(context, 'Update provisioned v2 device in Device Service %j %j', deviceObj, entityInfoUpdated);
353
+ logger.debug(
354
+ context,
355
+ 'Update provisioned v2 device %j with Device %j entityInfoUpdated %j',
356
+ previousDevice,
357
+ deviceObj,
358
+ entityInfoUpdated
359
+ );
256
360
 
257
361
  function combineWithNewDevice(newDevice, oldDevice, callback) {
258
362
  logger.debug(context, 'combineWithNewDevice %j %j', newDevice, oldDevice);
@@ -290,6 +394,9 @@ function updateRegisterDeviceNgsi2(deviceObj, previousDevice, entityInfoUpdated,
290
394
  if ('storeLastMeasure' in newDevice && newDevice.storeLastMeasure !== undefined) {
291
395
  oldDevice.storeLastMeasure = newDevice.storeLastMeasure;
292
396
  }
397
+ if ('cmdMode' in newDevice && newDevice.cmdMode !== undefined) {
398
+ oldDevice.cmdMode = newDevice.cmdMode;
399
+ }
293
400
  callback(null, oldDevice);
294
401
  } else {
295
402
  callback(new errors.DeviceNotFound(newDevice.id, newDevice));
@@ -337,7 +444,7 @@ function updateRegisterDeviceNgsi2(deviceObj, previousDevice, entityInfoUpdated,
337
444
  if (entityInfoUpdated) {
338
445
  jsonConcat(deviceData.active, oldDevice.active);
339
446
  jsonConcat(deviceData.lazy, oldDevice.lazy);
340
- jsonConcat(deviceData.commands, oldDevice.commands);
447
+ deviceData.commands = deviceData.commands.concat(oldDevice.commands);
341
448
  jsonConcat(deviceData.staticAttributes, oldDevice.staticAttributes);
342
449
  if (oldDevice.name !== newDevice.name) {
343
450
  deviceData.name = newDevice.name;
@@ -346,7 +453,13 @@ function updateRegisterDeviceNgsi2(deviceObj, previousDevice, entityInfoUpdated,
346
453
  deviceData.type = newDevice.type;
347
454
  }
348
455
  }
349
-
456
+ logger.debug(
457
+ context,
458
+ 'extractDeviceDifference newDevice %j oldDevice %j difference %j',
459
+ newDevice,
460
+ oldDevice,
461
+ deviceData
462
+ );
350
463
  callback(null, deviceData, oldDevice);
351
464
  }
352
465
 
@@ -361,7 +474,7 @@ function updateRegisterDeviceNgsi2(deviceObj, previousDevice, entityInfoUpdated,
361
474
  deviceObj.subservice
362
475
  ),
363
476
  apply(extractDeviceDifference, deviceObj),
364
- createInitialEntityNgsi2Fake,
477
+ createInitialEntityNgsi2,
365
478
  apply(combineWithNewDevice, deviceObj),
366
479
  apply(registrationUtils.sendRegistrations, false),
367
480
  apply(registrationUtils.processContextRegistration, deviceObj),
@@ -391,6 +504,7 @@ function updateRegisterDeviceNgsi2(deviceObj, previousDevice, entityInfoUpdated,
391
504
  }
392
505
  }
393
506
 
507
+ exports.createInitialEntity = createInitialEntityNgsi2;
394
508
  exports.updateRegisterDevice = updateRegisterDeviceNgsi2;
395
509
  exports.formatCommands = formatCommandsNgsi2;
396
510
  exports.formatAttributes = formatAttributesNgsi2;
@@ -36,6 +36,7 @@ const context = {
36
36
  };
37
37
  const async = require('async');
38
38
  const utils = require('../northBound/restUtils');
39
+ const subscriptionService = require('../ngsi/subscriptionService');
39
40
 
40
41
  const NGSI_LD_URN = 'urn:ngsi-ld:';
41
42
 
@@ -203,6 +204,23 @@ function sendUnregistrationsNgsiLD(deviceData, callback) {
203
204
  return callback(null, deviceData);
204
205
  }
205
206
 
207
+ function formatAttributes(originalVector) {
208
+ const attributeList = [];
209
+ if (originalVector && originalVector.length) {
210
+ for (let i = 0; i < originalVector.length; i++) {
211
+ attributeList.push(originalVector[i].name);
212
+ }
213
+ }
214
+ return attributeList;
215
+ }
216
+
217
+ function mergeWithSameName(old, current) {
218
+ if (old.indexOf(current) < 0) {
219
+ old.push(current);
220
+ }
221
+ return old;
222
+ }
223
+
206
224
  /**
207
225
  * Sends a Context Provider registration or unregistration request to the Context Broker using NGSIv2.
208
226
  *
@@ -210,26 +228,6 @@ function sendUnregistrationsNgsiLD(deviceData, callback) {
210
228
  * @param {Object} deviceData Object containing all the deviceData needed to send the registration.
211
229
  */
212
230
  function sendRegistrationsNgsi2(unregister, deviceData, callback) {
213
- function formatAttributes(originalVector) {
214
- const attributeList = [];
215
-
216
- if (originalVector && originalVector.length) {
217
- for (let i = 0; i < originalVector.length; i++) {
218
- attributeList.push(originalVector[i].name);
219
- }
220
- }
221
-
222
- return attributeList;
223
- }
224
-
225
- function mergeWithSameName(old, current) {
226
- if (old.indexOf(current) < 0) {
227
- old.push(current);
228
- }
229
-
230
- return old;
231
- }
232
-
233
231
  // FIXME: When https://github.com/telefonicaid/fiware-orion/issues/3007 is merged into master branch,
234
232
  // this function should use the new API. This is just a temporary solution which implies deleting the
235
233
  // registration and creating a new one.
@@ -299,6 +297,61 @@ function sendRegistrationsNgsi2(unregister, deviceData, callback) {
299
297
  utils.executeWithSecurity(options, deviceData, createRegistrationHandlerNgsi2(unregister, deviceData, callback));
300
298
  }
301
299
 
300
+ function sendUnsubscriptionsNgsi2(deviceData, callback) {
301
+ if (deviceData.subscriptionId) {
302
+ logger.debug(
303
+ context,
304
+ 'Sending v2 device unsubscriptions to Context Broker at [%s]',
305
+ config.getConfig().contextBroker.url
306
+ );
307
+ logger.debug(context, 'Using the following subscriptionId %j', deviceData.subscriptionId);
308
+ subscriptionService.unsubscribe(deviceData, deviceData.subscriptionId, callback);
309
+ } else {
310
+ logger.debug(context, 'No subscription found for unregister');
311
+ return callback(null, deviceData);
312
+ }
313
+ }
314
+
315
+ function sendSubscriptionsNgsi2(unregister, deviceData, callback) {
316
+ function updateSubscriptionNgsi2(deviceData, callback) {
317
+ const functions = [];
318
+
319
+ function removeSubscriptionId(deviceData, unregistrationResult, callback) {
320
+ delete deviceData.subscriptionId;
321
+ return callback(null, deviceData);
322
+ }
323
+
324
+ functions.push(async.apply(sendSubscriptionsNgsi2, true, deviceData));
325
+ functions.push(async.apply(removeSubscriptionId, deviceData));
326
+ functions.push(async.apply(sendSubscriptionsNgsi2, false));
327
+ async.waterfall(functions, callback);
328
+ }
329
+
330
+ if (unregister) {
331
+ return sendUnsubscriptionsNgsi2(deviceData, callback);
332
+ }
333
+ if (deviceData.subscriptionId) {
334
+ return updateSubscriptionNgsi2(deviceData, callback);
335
+ }
336
+ const attrs = [].concat(formatAttributes(deviceData.commands)).reduce(mergeWithSameName, []);
337
+
338
+ if (attrs.length === 0) {
339
+ logger.debug(context, 'Subscription is not needed. Device without commands');
340
+ return callback(null, deviceData);
341
+ }
342
+ const trigger = attrs; // one subscription for all commands
343
+ const content = attrs;
344
+ const attrsFormat = 'simplifiedNormalized';
345
+
346
+ logger.debug(
347
+ context,
348
+ 'Sending v2 device subscriptions to Context Broker at [%s]',
349
+ config.getConfig().contextBroker.url
350
+ );
351
+ logger.debug(context, 'Using the following trigger %j and content %j', trigger, content);
352
+ subscriptionService.subscribe(deviceData, trigger, content, attrsFormat, callback);
353
+ }
354
+
302
355
  /**
303
356
  * Sends a Context Provider registration or unregistration request to the Context Broker using NGSI-LD.
304
357
  *
@@ -323,14 +376,14 @@ function sendRegistrationsNgsiLD(unregister, deviceData, callback) {
323
376
  properties.push(element.name);
324
377
  });
325
378
 
326
- if (lazy.length > 0){
327
- operations.push('retrieveOps');
379
+ if (lazy.length > 0) {
380
+ operations.push('retrieveOps');
328
381
  }
329
- if (commands.length > 0){
330
- operations.push('updateOps');
382
+ if (commands.length > 0) {
383
+ operations.push('updateOps');
331
384
  }
332
- if (supportMerge){
333
- operations.push('mergeEntity');
385
+ if (supportMerge) {
386
+ operations.push('mergeEntity');
334
387
  }
335
388
 
336
389
  if (properties.length === 0) {
@@ -368,8 +421,8 @@ function sendRegistrationsNgsiLD(unregister, deviceData, callback) {
368
421
  endpoint: config.getConfig().providerUrl,
369
422
  contextSourceInfo: [
370
423
  {
371
- 'key': 'jsonldContext',
372
- 'value': config.getConfig().contextBroker.jsonLdContext
424
+ key: 'jsonldContext',
425
+ value: config.getConfig().contextBroker.jsonLdContext
373
426
  }
374
427
  ],
375
428
  '@context': config.getConfig().contextBroker.jsonLdContext
@@ -405,7 +458,14 @@ function sendRegistrations(unregister, deviceData, callback) {
405
458
  sendRegistrationsNgsiLD(unregister, deviceData, callback);
406
459
  break;
407
460
  default:
408
- sendRegistrationsNgsi2(unregister, deviceData, callback);
461
+ if (
462
+ (!deviceData.cmdMode && (!config.getConfig().cmdMode || config.getConfig().cmdMode === 'legacy')) ||
463
+ (deviceData && deviceData.cmdMode === 'legacy')
464
+ ) {
465
+ sendRegistrationsNgsi2(unregister, deviceData, callback);
466
+ } else {
467
+ sendSubscriptionsNgsi2(unregister, deviceData, callback);
468
+ }
409
469
  break;
410
470
  }
411
471
  }
@@ -421,7 +481,14 @@ function processContextRegistration(deviceData, body, callback) {
421
481
  const newDevice = _.clone(deviceData);
422
482
 
423
483
  if (body) {
424
- newDevice.registrationId = body.registrationId;
484
+ if (
485
+ (!deviceData.cmdMode && (!config.getConfig().cmdMode || config.getConfig().cmdMode === 'legacy')) ||
486
+ (deviceData && deviceData.cmdMode === 'legacy')
487
+ ) {
488
+ newDevice.registrationId = body.registrationId;
489
+ } else {
490
+ newDevice.subscriptionId = body.subscriptionId;
491
+ }
425
492
  }
426
493
 
427
494
  callback(null, newDevice);
@@ -63,7 +63,8 @@ const attributeList = [
63
63
  'entityNameExp',
64
64
  'payloadType',
65
65
  'useCBflowControl',
66
- 'storeLastMeasure'
66
+ 'storeLastMeasure',
67
+ 'cmdMode'
67
68
  ];
68
69
 
69
70
  function createGroup(group, callback) {
@@ -105,7 +105,7 @@ function createSubscriptionHandlerNgsiLD(device, triggers, store, callback) {
105
105
  * @param {Object} triggers Array with the names of the attributes that would trigger the subscription
106
106
  * @param {Object} content Array with the names of the attributes to retrieve in the notification.
107
107
  */
108
- function subscribeNgsiLD(device, triggers, content, callback) {
108
+ function subscribeNgsiLD(device, triggers, content, attrsFormat, callback) {
109
109
  const options = {
110
110
  method: 'POST',
111
111
  headers: {
@@ -132,7 +132,7 @@ function subscribeNgsiLD(device, triggers, content, callback) {
132
132
  accept: 'application/json'
133
133
  },
134
134
  attributes: content || [],
135
- format: 'normalized'
135
+ format: attrsFormat
136
136
  }
137
137
  }
138
138
  };
@@ -36,11 +36,11 @@ const subscriptionHandlerV2 = require('./subscription-NGSI-v2');
36
36
  * @param {Object} triggers Array with the names of the attributes that would trigger the subscription
37
37
  * @param {Object} content Array with the names of the attributes to retrieve in the notification.
38
38
  */
39
- function subscribeNgsiMixed(device, triggers, content, callback) {
39
+ function subscribeNgsiMixed(device, triggers, content, attrsFormat, callback) {
40
40
  if (config.checkNgsiLD(device)) {
41
- subscriptionHandlerLD.subscribe(device, triggers, content, callback);
41
+ subscriptionHandlerLD.subscribe(device, triggers, content, attrsFormat, callback);
42
42
  } else {
43
- subscriptionHandlerV2.subscribe(device, triggers, content, callback);
43
+ subscriptionHandlerV2.subscribe(device, triggers, content, attrsFormat, callback);
44
44
  }
45
45
  }
46
46
 
@@ -93,7 +93,9 @@ function createSubscriptionHandlerNgsi2(device, triggers, store, callback) {
93
93
 
94
94
  config.getRegistry().update(device, device, callback);
95
95
  } else {
96
- callback(null, response.headers.location);
96
+ callback(null, {
97
+ subscriptionId: response.headers.location.substr(response.headers.location.lastIndexOf('/') + 1)
98
+ });
97
99
  }
98
100
  };
99
101
  }
@@ -107,7 +109,7 @@ function createSubscriptionHandlerNgsi2(device, triggers, store, callback) {
107
109
  * @param {Object} triggers Array with the names of the attributes that would trigger the subscription
108
110
  * @param {Object} content Array with the names of the attributes to retrieve in the notification.
109
111
  */
110
- function subscribeNgsi2(device, triggers, content, callback) {
112
+ function subscribeNgsi2(device, triggers, content, attrsFormat, callback) {
111
113
  const options = {
112
114
  method: 'POST',
113
115
  headers: {
@@ -115,6 +117,7 @@ function subscribeNgsi2(device, triggers, content, callback) {
115
117
  'fiware-servicepath': device.subservice
116
118
  },
117
119
  json: {
120
+ description: 'Managed by IOTA: ' + device.name + ' ' + device.type + ' ' + triggers.join(','),
118
121
  subject: {
119
122
  entities: [
120
123
  {
@@ -132,7 +135,8 @@ function subscribeNgsi2(device, triggers, content, callback) {
132
135
  url: config.getConfig().providerUrl + '/notify'
133
136
  },
134
137
  attrs: content || [],
135
- attrsFormat: 'normalized'
138
+ attrsFormat: attrsFormat,
139
+ onlyChangedAttrs: true
136
140
  }
137
141
  }
138
142
  };
@@ -167,7 +171,7 @@ function createUnsubscribeHandlerNgsi2(device, id, callback) {
167
171
  if (error) {
168
172
  logger.debug(
169
173
  context,
170
- 'Transport error found subscribing device with id [%s] to entity [%s]',
174
+ 'Transport error found unsubscribing device with id [%s] to entity [%s]',
171
175
  device.id,
172
176
  device.name
173
177
  );
@@ -176,7 +180,9 @@ function createUnsubscribeHandlerNgsi2(device, id, callback) {
176
180
  } else if (response.statusCode !== 204) {
177
181
  logger.debug(
178
182
  context,
179
- 'Unknown error subscribing device with id [%s] to entity [%s]: $s',
183
+ 'Unknown error unsubscribing device with id [%s] to entity [%s]: %s',
184
+ device.id,
185
+ device.name,
180
186
  response.statusCode
181
187
  );
182
188
 
@@ -202,7 +208,15 @@ function createUnsubscribeHandlerNgsi2(device, id, callback) {
202
208
 
203
209
  callback(new errors.BadRequest(body.orionError.details));
204
210
  } else {
205
- device.subscriptions.splice(device.subscriptions.indexOf(id), 1);
211
+ logger.debug(context, 'removing subscription %s from device %j', id, device);
212
+ if (device.subscriptions) {
213
+ // check before try to eliminates
214
+ const index = device.subscriptions.indexOf(id);
215
+ if (index !== -1) {
216
+ // only eliminates if exits
217
+ device.subscriptions.splice(index, 1);
218
+ }
219
+ }
206
220
  config.getRegistry().update(device, device, callback);
207
221
  }
208
222
  };
@@ -57,8 +57,8 @@ function init() {
57
57
  * @param {Object} triggers Array with the names of the attributes that would trigger the subscription
58
58
  * @param {Object} content Array with the names of the attributes to retrieve in the notification.
59
59
  */
60
- function subscribe(device, triggers, content, callback) {
61
- subscriptionHandler.subscribe(device, triggers, content, callback);
60
+ function subscribe(device, triggers, content, attrsFormat, callback) {
61
+ subscriptionHandler.subscribe(device, triggers, content, attrsFormat, callback);
62
62
  }
63
63
 
64
64
  /**
@@ -67,7 +67,8 @@ const provisioningAPITranslation = {
67
67
  payloadType: 'payloadType',
68
68
  useCBflowControl: 'useCBflowControl',
69
69
  storeLastMeasure: 'storeLastMeasure',
70
- lastMeasure: 'lastMeasure'
70
+ lastMeasure: 'lastMeasure',
71
+ cmdMode: 'cmdMode'
71
72
  };
72
73
 
73
74
  /**
@@ -149,7 +150,8 @@ function handleProvision(req, res, next) {
149
150
  payloadType: body.payloadType,
150
151
  useCBflowControl: body.useCBflowControl,
151
152
  storeLastMeasure: body.storeLastMeasure,
152
- lastMeasure: body.lastMeasure
153
+ lastMeasure: body.lastMeasure,
154
+ cmdMode: body.cmdMode
153
155
  });
154
156
  }
155
157
 
@@ -229,7 +231,8 @@ function toProvisioningAPIFormat(device) {
229
231
  payloadType: device.payloadType,
230
232
  useCBflowControl: device.useCBflowControl,
231
233
  storeLastMeasure: device.storeLastMeasure,
232
- lastMeasure: device.lastMeasure
234
+ lastMeasure: device.lastMeasure,
235
+ cmdMode: device.cmdMode
233
236
  };
234
237
  }
235
238