iotagent-node-lib 4.9.0 → 4.11.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 (49) hide show
  1. package/.github/workflows/ci.yml +6 -8
  2. package/CHANGES_NEXT_RELEASE +0 -1
  3. package/Changelog +14 -0
  4. package/config.js +2 -1
  5. package/doc/admin.md +17 -20
  6. package/doc/api.md +87 -50
  7. package/doc/deprecated.md +18 -0
  8. package/doc/devel/northboundinteractions.md +213 -113
  9. package/lib/commonConfig.js +12 -1
  10. package/lib/jexlTranformsMap.js +15 -0
  11. package/lib/model/Device.js +3 -1
  12. package/lib/model/Group.js +2 -1
  13. package/lib/model/dbConn.js +57 -58
  14. package/lib/services/common/iotManagerService.js +2 -1
  15. package/lib/services/devices/deviceRegistryMongoDB.js +5 -1
  16. package/lib/services/devices/deviceService.js +17 -1
  17. package/lib/services/devices/devices-NGSI-LD.js +3 -2
  18. package/lib/services/devices/devices-NGSI-mixed.js +16 -0
  19. package/lib/services/devices/devices-NGSI-v2.js +122 -8
  20. package/lib/services/devices/registrationUtils.js +97 -30
  21. package/lib/services/groups/groupRegistryMongoDB.js +2 -1
  22. package/lib/services/ngsi/subscription-NGSI-LD.js +2 -2
  23. package/lib/services/ngsi/subscription-NGSI-mixed.js +3 -3
  24. package/lib/services/ngsi/subscription-NGSI-v2.js +20 -6
  25. package/lib/services/ngsi/subscriptionService.js +2 -2
  26. package/lib/services/northBound/contextServer-NGSI-v2.js +4 -2
  27. package/lib/services/northBound/deviceProvisioningServer.js +6 -3
  28. package/lib/templates/updateDevice.json +12 -0
  29. package/lib/templates/updateDeviceLax.json +4 -0
  30. package/package.json +2 -2
  31. package/test/unit/expressions/jexlExpression-test.js +30 -0
  32. package/test/unit/general/contextBrokerKeystoneSecurityAccess-test.js +2 -2
  33. package/test/unit/memoryRegistry/deviceRegistryMemory_test.js +1 -1
  34. package/test/unit/ngsi-ld/general/contextBrokerOAuthSecurityAccess-test.js +2 -2
  35. package/test/unit/ngsi-ld/general/https-support-test.js +1 -1
  36. package/test/unit/ngsi-ld/ngsiService/subscriptions-test.js +6 -6
  37. package/test/unit/ngsiv2/examples/contextAvailabilityRequests/subscribeIoTAgentCommands.json +24 -0
  38. package/test/unit/ngsiv2/examples/contextAvailabilityRequests/subscribeIoTAgentCommands2.json +24 -0
  39. package/test/unit/ngsiv2/examples/contextAvailabilityRequests/subscribeIoTAgentCommands3.json +24 -0
  40. package/test/unit/ngsiv2/examples/contextAvailabilityRequests/subscribeIoTAgentCommands4.json +24 -0
  41. package/test/unit/ngsiv2/examples/contextRequests/updateEntity.json +5 -0
  42. package/test/unit/ngsiv2/examples/contextRequests/updateEntity2.json +5 -0
  43. package/test/unit/ngsiv2/examples/contextRequests/updateEntity2b.json +7 -0
  44. package/test/unit/ngsiv2/examples/contextRequests/updateEntity3.json +5 -0
  45. package/test/unit/ngsiv2/examples/subscriptionRequests/simpleSubscriptionRequest.json +4 -2
  46. package/test/unit/ngsiv2/examples/subscriptionRequests/simpleSubscriptionRequest2.json +4 -2
  47. package/test/unit/ngsiv2/general/contextBrokerOAuthSecurityAccess-test.js +2 -2
  48. package/test/unit/ngsiv2/general/https-support-test.js +1 -1
  49. package/test/unit/ngsiv2/lazyAndCommands/command-test.js +245 -2
@@ -21,7 +21,7 @@
21
21
  * please contact with::daniel.moranjimenez@telefonica.com
22
22
  */
23
23
 
24
- /**
24
+ /*
25
25
  * This module sets up the connection with the mongodb through mongoose. This connection will be used
26
26
  * in mongoose schemas to persist objects.
27
27
  */
@@ -49,8 +49,7 @@ function loadModels() {
49
49
  *
50
50
  * @this Reference to the dbConn module itself.
51
51
  */
52
-
53
- function init(host, db, port, options, callback) {
52
+ function init(host, db, port, options, callback, fullUri = null) {
54
53
  let url;
55
54
  let retries = 0;
56
55
  let lastError;
@@ -60,21 +59,25 @@ function init(host, db, port, options, callback) {
60
59
  return `${item}:${port}`;
61
60
  }
62
61
 
63
- url = 'mongodb://';
62
+ if (fullUri) {
63
+ url = fullUri;
64
+ } else {
65
+ url = 'mongodb://';
64
66
 
65
- if (options.auth) {
66
- url += `${encodeURIComponent(options.auth.user)}:${encodeURIComponent(options.auth.password)}@`;
67
- }
67
+ if (options.auth) {
68
+ url += `${encodeURIComponent(options.auth.user)}:${encodeURIComponent(options.auth.password)}@`;
69
+ }
68
70
 
69
- const hosts = host.split(',').map(addPort).join(',');
70
- url += `${hosts}/${db}`;
71
+ const hosts = host.split(',').map(addPort).join(',');
72
+ url += `${hosts}/${db}`;
71
73
 
72
- if (options.extraArgs) {
73
- const query = new URLSearchParams(options.extraArgs).toString();
74
- if (query) {
75
- url += `?${query}`;
74
+ if (options.extraArgs) {
75
+ const query = new URLSearchParams(options.extraArgs).toString();
76
+ if (query) {
77
+ url += `?${query}`;
78
+ }
79
+ delete options.extraArgs;
76
80
  }
77
- delete options.extraArgs;
78
81
  }
79
82
 
80
83
  function connectionAttempt(callback) {
@@ -91,32 +94,17 @@ function init(host, db, port, options, callback) {
91
94
  lastError = error;
92
95
  alarms.raise(constants.MONGO_ALARM, error);
93
96
  });
94
- /* eslint-disable-next-line no-unused-vars */
95
- defaultDb.on('connecting', function (error) {
96
- logger.debug(context, 'Mongo Driver connecting');
97
- });
98
- defaultDb.on('connected', function () {
99
- logger.debug(context, 'Mongo Driver connected');
100
- });
101
- defaultDb.on('reconnected', function () {
102
- logger.debug(context, 'Mongo Driver reconnected');
103
- });
104
- defaultDb.on('disconnected', function () {
105
- logger.debug(context, 'Mongo Driver disconnected');
106
- });
107
- defaultDb.on('reconnectFailed', function () {
97
+ defaultDb.on('connecting', () => logger.debug(context, 'Mongo Driver connecting'));
98
+ defaultDb.on('connected', () => logger.debug(context, 'Mongo Driver connected'));
99
+ defaultDb.on('reconnected', () => logger.debug(context, 'Mongo Driver reconnected'));
100
+ defaultDb.on('disconnected', () => logger.debug(context, 'Mongo Driver disconnected'));
101
+ defaultDb.on('reconnectFailed', () => {
108
102
  logger.error(context, 'MONGODB-004: MongoDB connection was lost');
109
103
  process.exit(1);
110
104
  });
111
- defaultDb.on('disconnecting', function () {
112
- logger.debug(context, 'Mongo Driver disconnecting');
113
- });
114
- defaultDb.on('open', function () {
115
- logger.debug(context, 'Mongo Driver open');
116
- });
117
- defaultDb.on('close', function () {
118
- logger.debug(context, 'Mongo Driver close');
119
- });
105
+ defaultDb.on('disconnecting', () => logger.debug(context, 'Mongo Driver disconnecting'));
106
+ defaultDb.on('open', () => logger.debug(context, 'Mongo Driver open'));
107
+ defaultDb.on('close', () => logger.debug(context, 'Mongo Driver close'));
120
108
  callback();
121
109
  })
122
110
  .catch((err) => {
@@ -144,34 +132,45 @@ function init(host, db, port, options, callback) {
144
132
 
145
133
  function configureDb(callback) {
146
134
  const currentConfig = config.getConfig();
135
+
147
136
  if (currentConfig.deviceRegistry?.type === 'mongodb') {
148
- if (!currentConfig.mongodb?.host) {
149
- logger.fatal(context, 'MONGODB-003: No host found for MongoDB driver.');
150
- callback(new errors.BadConfiguration('No host found for MongoDB driver'));
151
- } else {
152
- const dbName = currentConfig.mongodb.db || DEFAULT_DB_NAME;
153
- const port = currentConfig.mongodb.port || 27017;
137
+ const mongoCfg = currentConfig.mongodb;
138
+
139
+ if (mongoCfg?.uri) {
154
140
  const options = {};
141
+ logger.info(context, `Using full MongoDB URI from configuration`);
142
+ init(null, null, null, options, callback, mongoCfg.uri);
143
+ return;
144
+ }
155
145
 
156
- if (currentConfig.mongodb.replicaSet) options.replicaSet = currentConfig.mongodb.replicaSet;
157
- if (currentConfig.mongodb.ssl) options.ssl = currentConfig.mongodb.ssl;
158
- if (currentConfig.mongodb.extraArgs) options.extraArgs = currentConfig.mongodb.extraArgs;
146
+ if (!mongoCfg?.host) {
147
+ logger.fatal(context, 'MONGODB-003: No host found for MongoDB driver.');
148
+ callback(new errors.BadConfiguration('No host found for MongoDB driver'));
149
+ return;
150
+ }
159
151
 
160
- if (currentConfig.mongodb.user && currentConfig.mongodb.password) {
161
- options.auth = {
162
- user: currentConfig.mongodb.user,
163
- password: currentConfig.mongodb.password
152
+ const dbName = mongoCfg.db || DEFAULT_DB_NAME;
153
+ const port = mongoCfg.port || 27017;
154
+ const options = {};
155
+
156
+ if (mongoCfg.replicaSet) options.replicaSet = mongoCfg.replicaSet;
157
+ if (mongoCfg.ssl) options.ssl = mongoCfg.ssl;
158
+ if (mongoCfg.extraArgs) options.extraArgs = mongoCfg.extraArgs;
159
+
160
+ if (mongoCfg.user && mongoCfg.password) {
161
+ options.auth = {
162
+ user: mongoCfg.user,
163
+ password: mongoCfg.password
164
+ };
165
+ if (mongoCfg.authSource) {
166
+ options.extraArgs = {
167
+ ...options.extraArgs,
168
+ authSource: mongoCfg.authSource
164
169
  };
165
- if (currentConfig.mongodb.authSource) {
166
- options.extraArgs = {
167
- ...options.extraArgs,
168
- authSource: currentConfig.mongodb.authSource
169
- };
170
- }
171
170
  }
172
-
173
- init(currentConfig.mongodb.host, dbName, port, options, callback);
174
171
  }
172
+
173
+ init(mongoCfg.host, dbName, port, options, callback);
175
174
  } else {
176
175
  callback();
177
176
  }
@@ -65,7 +65,8 @@ function register(callback) {
65
65
  endpoint: service.endpoint,
66
66
  transport: service.transport,
67
67
  useCBflowControl: service.useCBflowControl,
68
- storeLastMeasure: service.storeLastMeasure
68
+ storeLastMeasure: service.storeLastMeasure,
69
+ cmdMode: service.cmdMode
69
70
  };
70
71
  }
71
72
 
@@ -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 = 'normalized';
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);