iotagent-node-lib 2.21.0 → 2.24.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 (62) hide show
  1. package/.nyc_output/33364de2-1199-4ec2-b33c-cae063ef8cc4.json +1 -0
  2. package/.nyc_output/processinfo/{76bc24ff-5fac-4b5a-997d-de2799342eb0.json → 33364de2-1199-4ec2-b33c-cae063ef8cc4.json} +1 -1
  3. package/.nyc_output/processinfo/index.json +1 -1
  4. package/CHANGES_NEXT_RELEASE +0 -1
  5. package/doc/Contribution.md +3 -3
  6. package/doc/advanced-topics.md +73 -9
  7. package/doc/api.md +7 -5
  8. package/doc/architecture.md +52 -5
  9. package/doc/expressionLanguage.md +18 -3
  10. package/doc/operations.md +8 -5
  11. package/doc/usermanual.md +18 -16
  12. package/docker/Mosquitto/Dockerfile +1 -1
  13. package/lib/errors.js +9 -1
  14. package/lib/model/Group.js +2 -1
  15. package/lib/model/dbConn.js +4 -0
  16. package/lib/plugins/bidirectionalData.js +104 -6
  17. package/lib/plugins/expressionPlugin.js +18 -7
  18. package/lib/plugins/multiEntity.js +42 -29
  19. package/lib/plugins/pluginUtils.js +17 -0
  20. package/lib/request-shim.js +2 -1
  21. package/lib/services/commands/commandService.js +29 -2
  22. package/lib/services/common/iotManagerService.js +2 -1
  23. package/lib/services/devices/deviceService.js +35 -9
  24. package/lib/services/groups/groupRegistryMongoDB.js +13 -12
  25. package/lib/services/ngsi/entities-NGSI-LD.js +7 -0
  26. package/lib/services/ngsi/entities-NGSI-v2.js +70 -11
  27. package/lib/services/ngsi/ngsiService.js +1 -1
  28. package/lib/services/northBound/contextServer-NGSI-LD.js +110 -15
  29. package/lib/services/northBound/contextServer-NGSI-v2.js +8 -3
  30. package/lib/services/northBound/contextServerUtils.js +9 -9
  31. package/lib/services/northBound/deviceProvisioningServer.js +2 -1
  32. package/lib/templates/createDevice.json +1 -2
  33. package/lib/templates/createDeviceLax.json +2 -3
  34. package/lib/templates/updateDevice.json +1 -2
  35. package/package.json +24 -24
  36. package/test/unit/examples/deviceProvisioningRequests/provisionMinimumDevice4.json +14 -0
  37. package/test/unit/memoryRegistry/deviceRegistryMemory_test.js +51 -0
  38. package/test/unit/mongodb/mongoDBUtils.js +2 -2
  39. package/test/unit/mongodb/mongodb-group-registry-test.js +25 -1
  40. package/test/unit/mongodb/mongodb-registry-test.js +51 -3
  41. package/test/unit/ngsi-ld/examples/contextAvailabilityRequests/registerIoTAgentCommands.json +2 -1
  42. package/test/unit/ngsi-ld/examples/contextRequests/updateContextLanguageProperties1.json +15 -0
  43. package/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotificationWithDatasetId.json +21 -0
  44. package/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotificationWithMetadata.json +17 -0
  45. package/test/unit/ngsi-ld/lazyAndCommands/command-test.js +995 -27
  46. package/test/unit/ngsi-ld/ngsiService/languageProperties-test.js +112 -0
  47. package/test/unit/ngsi-ld/ngsiService/unsupported-endpoints-test.js +111 -0
  48. package/test/unit/ngsi-ld/plugins/bidirectional-plugin_test.js +221 -0
  49. package/test/unit/ngsi-mixed/provisioning/ngsi-versioning-test.js +1 -1
  50. package/test/unit/ngsiv2/examples/contextRequests/createMinimumProvisionedDevice4.json +8 -0
  51. package/test/unit/ngsiv2/examples/contextRequests/updateContextCommandStatus2.json +6 -0
  52. package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin36.json +12 -1
  53. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin17.json +27 -0
  54. package/test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalNotificationWithMetadata.json +19 -0
  55. package/test/unit/ngsiv2/expressions/jexlBasedTransformations-test.js +10 -8
  56. package/test/unit/ngsiv2/lazyAndCommands/command-test.js +106 -0
  57. package/test/unit/ngsiv2/lazyAndCommands/polling-commands-test.js +151 -0
  58. package/test/unit/ngsiv2/plugins/bidirectional-plugin_test.js +115 -0
  59. package/test/unit/ngsiv2/plugins/multientity-plugin_test.js +63 -0
  60. package/test/unit/ngsiv2/provisioning/device-provisioning-api_test.js +60 -0
  61. package/test/unit/ngsiv2/provisioning/updateProvisionedDevices-test.js +2 -1
  62. package/.nyc_output/76bc24ff-5fac-4b5a-997d-de2799342eb0.json +0 -1
@@ -311,4 +311,110 @@ describe('NGSI-v2 - Command functionalities', function () {
311
311
  });
312
312
  });
313
313
  });
314
+
315
+ describe('When a command update with metadata arrives to the IoT Agent as Context Provider', function () {
316
+ const options = {
317
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/v2/op/update',
318
+ method: 'POST',
319
+ json: {
320
+ actionType: 'update',
321
+ entities: [
322
+ {
323
+ id: 'Robot:r2d2',
324
+ type: 'Robot',
325
+ position: {
326
+ type: 'Array',
327
+ value: '[28, -104, 23]',
328
+ metadata: {
329
+ qos: 1
330
+ }
331
+ }
332
+ }
333
+ ]
334
+ },
335
+ headers: {
336
+ 'fiware-service': 'smartgondor',
337
+ 'fiware-servicepath': 'gardens'
338
+ }
339
+ };
340
+
341
+ beforeEach(function (done) {
342
+ iotAgentLib.register(device3, function (error) {
343
+ done();
344
+ });
345
+ });
346
+
347
+ it('should call the client handler once including metadata', function (done) {
348
+ let handlerCalled = 0;
349
+
350
+ iotAgentLib.setCommandHandler(function (id, type, service, subservice, attributes, callback) {
351
+ id.should.equal(device3.type + ':' + device3.id);
352
+ type.should.equal(device3.type);
353
+ attributes[0].name.should.equal('position');
354
+ attributes[0].value.should.equal('[28, -104, 23]');
355
+ attributes[0].metadata.qos.should.equal(1);
356
+ handlerCalled++;
357
+ callback(null, {
358
+ id,
359
+ type,
360
+ attributes: [
361
+ {
362
+ name: 'position',
363
+ type: 'Array',
364
+ value: '[28, -104, 23]'
365
+ }
366
+ ]
367
+ });
368
+ });
369
+
370
+ request(options, function (error, response, body) {
371
+ should.not.exist(error);
372
+ handlerCalled.should.equal(1);
373
+ done();
374
+ });
375
+ });
376
+ it('should create the attribute with the "_status" prefix in the Context Broker', function (done) {
377
+ iotAgentLib.setCommandHandler(function (id, type, service, subservice, attributes, callback) {
378
+ callback(null, {
379
+ id,
380
+ type,
381
+ attributes: [
382
+ {
383
+ name: 'position',
384
+ type: 'Array',
385
+ value: '[28, -104, 23]'
386
+ }
387
+ ]
388
+ });
389
+ });
390
+
391
+ request(options, function (error, response, body) {
392
+ should.not.exist(error);
393
+ done();
394
+ });
395
+ });
396
+ it('should create the attribute with the "_status" prefix in the Context Broker', function (done) {
397
+ let serviceAndSubservice = false;
398
+
399
+ iotAgentLib.setCommandHandler(function (id, type, service, subservice, attributes, callback) {
400
+ serviceAndSubservice = service === 'smartgondor' && subservice === 'gardens';
401
+ callback(null, {
402
+ id,
403
+ type,
404
+ attributes: [
405
+ {
406
+ name: 'position',
407
+ type: 'Array',
408
+ value: '[28, -104, 23]'
409
+ }
410
+ ]
411
+ });
412
+ });
413
+
414
+ request(options, function (error, response, body) {
415
+ serviceAndSubservice.should.equal(true);
416
+ done();
417
+ });
418
+ });
419
+ });
314
420
  });
@@ -98,6 +98,18 @@ const iotAgentConfig = {
98
98
  lazy: [],
99
99
  staticAttributes: [],
100
100
  active: []
101
+ },
102
+ RobotExp: {
103
+ commands: [
104
+ {
105
+ name: 'positionExp',
106
+ type: 'Array',
107
+ expression: '[22]'
108
+ }
109
+ ],
110
+ lazy: [],
111
+ staticAttributes: [],
112
+ active: []
101
113
  }
102
114
  },
103
115
  deviceRegistry: {
@@ -122,6 +134,14 @@ const device3 = {
122
134
  subservice: 'gardens',
123
135
  polling: true
124
136
  };
137
+ const device4 = {
138
+ id: 'r2d4',
139
+ type: 'RobotExp',
140
+ service: 'smartgondor',
141
+ subservice: 'gardens',
142
+ polling: true,
143
+ expressionLanguage: 'jexl'
144
+ };
125
145
 
126
146
  describe('NGSI-v2 - Polling commands', function () {
127
147
  beforeEach(function (done) {
@@ -386,3 +406,134 @@ describe('NGSI-v2 - Polling commands', function () {
386
406
  });
387
407
  });
388
408
  });
409
+
410
+ describe('NGSI-v2 - Polling commands expressions', function () {
411
+ beforeEach(function (done) {
412
+ logger.setLevel('FATAL');
413
+
414
+ nock.cleanAll();
415
+
416
+ contextBrokerMock = nock('http://192.168.1.1:1026')
417
+ .matchHeader('fiware-service', 'smartgondor')
418
+ .matchHeader('fiware-servicepath', 'gardens')
419
+ .post('/v2/registrations')
420
+ .reply(201, null, { Location: '/v2/registrations/6319a7f5254b05844116584m' });
421
+
422
+ contextBrokerMock
423
+ .matchHeader('fiware-service', 'smartgondor')
424
+ .matchHeader('fiware-servicepath', 'gardens')
425
+ .post('/v2/entities?options=upsert')
426
+ .reply(204);
427
+
428
+ iotAgentConfig.pollingExpiration = 0;
429
+ iotAgentConfig.pollingDaemonFrequency = 0;
430
+ iotAgentLib.activate(iotAgentConfig, done);
431
+ });
432
+
433
+ afterEach(function (done) {
434
+ delete device4.registrationId;
435
+ iotAgentLib.clearAll(function () {
436
+ iotAgentLib.deactivate(function () {
437
+ mongoUtils.cleanDbs(function () {
438
+ nock.cleanAll();
439
+ iotAgentLib.setDataUpdateHandler();
440
+ iotAgentLib.setCommandHandler();
441
+ done();
442
+ });
443
+ });
444
+ });
445
+ });
446
+
447
+ describe('When a command update arrives to the IoT Agent for a device with polling', function () {
448
+ const options = {
449
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/v2/op/update',
450
+ method: 'POST',
451
+ json: {
452
+ actionType: 'update',
453
+ entities: [
454
+ {
455
+ id: 'RobotExp:r2d4',
456
+ type: 'RobotExp',
457
+ positionExp: {
458
+ type: 'Array',
459
+ value: '[28, -104, 23]'
460
+ }
461
+ }
462
+ ]
463
+ },
464
+ headers: {
465
+ 'fiware-service': 'smartgondor',
466
+ 'fiware-servicepath': 'gardens'
467
+ }
468
+ };
469
+
470
+ beforeEach(function (done) {
471
+ statusAttributeMock = nock('http://192.168.1.1:1026')
472
+ .matchHeader('fiware-service', 'smartgondor')
473
+ .matchHeader('fiware-servicepath', 'gardens')
474
+ .patch(
475
+ '/v2/entities/RobotExp:r2d4/attrs?type=RobotExp',
476
+ utils.readExampleFile(
477
+ './test/unit/ngsiv2/examples/contextRequests/updateContextCommandStatus2.json'
478
+ )
479
+ )
480
+ .reply(204);
481
+
482
+ iotAgentLib.register(device4, function (error) {
483
+ done();
484
+ });
485
+ });
486
+
487
+ it('should not call the client handler', function (done) {
488
+ let handlerCalled = false;
489
+
490
+ iotAgentLib.setCommandHandler(function (id, type, service, subservice, attributes, callback) {
491
+ handlerCalled = true;
492
+ callback(null, {
493
+ id,
494
+ type,
495
+ attributes: [
496
+ {
497
+ name: 'positionExp',
498
+ type: 'Array',
499
+ value: '[28, -104, 23]'
500
+ }
501
+ ]
502
+ });
503
+ });
504
+
505
+ request(options, function (error, response, body) {
506
+ should.not.exist(error);
507
+ handlerCalled.should.equal(false);
508
+ done();
509
+ });
510
+ });
511
+ it('should create the attribute with the "_status" prefix in the Context Broker', function (done) {
512
+ iotAgentLib.setCommandHandler(function (id, type, service, subservice, attributes, callback) {
513
+ callback(null);
514
+ });
515
+
516
+ request(options, function (error, response, body) {
517
+ should.not.exist(error);
518
+ statusAttributeMock.done();
519
+ done();
520
+ });
521
+ });
522
+ it('should store the commands in the queue', function (done) {
523
+ iotAgentLib.setCommandHandler(function (id, type, service, subservice, attributes, callback) {
524
+ callback(null);
525
+ });
526
+
527
+ request(options, function (error, response, body) {
528
+ iotAgentLib.commandQueue('smartgondor', 'gardens', 'r2d4', function (error, listCommands) {
529
+ should.not.exist(error);
530
+ listCommands.count.should.equal(1);
531
+ listCommands.commands[0].name.should.equal('positionExp');
532
+ listCommands.commands[0].type.should.equal('Array');
533
+ listCommands.commands[0].value[0].should.equal(22);
534
+ done();
535
+ });
536
+ });
537
+ });
538
+ });
539
+ });
@@ -237,6 +237,120 @@ describe('NGSI-v2 - Bidirectional data plugin', function () {
237
237
  });
238
238
  });
239
239
 
240
+ it('should return the transformed values', function (done) {
241
+ let transformedHandler = false;
242
+
243
+ function mockedHandler(device, values, callback) {
244
+
245
+ let latitudeFound = false;
246
+ let longitudeFound = false;
247
+
248
+ for (let i = 0; i < values.length; i++) {
249
+ if (values[i].name === 'latitude' && values[i].type === 'string' && values[i].value === '-9.6') {
250
+ latitudeFound = true;
251
+ }
252
+
253
+ if (values[i].name === 'longitude' && values[i].type === 'string' && values[i].value === '12.4') {
254
+ longitudeFound = true;
255
+ }
256
+ }
257
+
258
+ transformedHandler = values.length >= 2 && longitudeFound && latitudeFound;
259
+ callback();
260
+ }
261
+
262
+ iotAgentLib.setNotificationHandler(mockedHandler);
263
+
264
+ request(options, function (error, response, body) {
265
+ request(notificationOptions, function (error, response, body) {
266
+ contextBrokerMock.done();
267
+ transformedHandler.should.equal(true);
268
+ done();
269
+ });
270
+ });
271
+ });
272
+ });
273
+
274
+
275
+ describe('When a notification with metadata arrives for a bidirectional attribute', function () {
276
+ const notificationOptions = {
277
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/notify',
278
+ method: 'POST',
279
+ json: utils.readExampleFile(
280
+ './test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalNotificationWithMetadata.json'
281
+ ),
282
+ headers: {
283
+ 'fiware-service': 'smartgondor',
284
+ 'fiware-servicepath': '/gardens'
285
+ }
286
+ };
287
+ let executedHandler = false;
288
+
289
+ beforeEach(function () {
290
+ contextBrokerMock
291
+ .matchHeader('fiware-service', 'smartgondor')
292
+ .matchHeader('fiware-servicepath', '/gardens')
293
+ .post(
294
+ '/v2/subscriptions',
295
+ utils.readExampleFile(
296
+ './test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalSubscriptionRequest.json'
297
+ )
298
+ )
299
+ .reply(201, null, { Location: '/v2/subscriptions/51c0ac9ed714fb3b37d7d5a8' });
300
+
301
+ contextBrokerMock
302
+ .matchHeader('fiware-service', 'smartgondor')
303
+ .matchHeader('fiware-servicepath', '/gardens')
304
+ .post(
305
+ '/v2/entities?options=upsert',
306
+ utils.readExampleFile('./test/unit/ngsiv2/examples/contextRequests/createBidirectionalDevice.json')
307
+ )
308
+ .reply(204);
309
+ });
310
+
311
+ afterEach(function () {
312
+ iotAgentLib.setNotificationHandler();
313
+ });
314
+
315
+ it('should execute the original handler', function (done) {
316
+ function mockedHandler(device, notification, callback) {
317
+
318
+ notification[0].name.should.equal('location');
319
+ notification[0].value.should.equal('12.4, -9.6');
320
+ notification[0].metadata.qos.value.should.equal(1);
321
+
322
+ executedHandler = true;
323
+ callback();
324
+ }
325
+
326
+ iotAgentLib.setNotificationHandler(mockedHandler);
327
+
328
+ request(options, function (error, response, body) {
329
+ request(notificationOptions, function (error, response, body) {
330
+ executedHandler.should.equal(true);
331
+ contextBrokerMock.done();
332
+ done();
333
+ });
334
+ });
335
+ });
336
+
337
+ it('should return a 200 OK', function (done) {
338
+ function mockedHandler(device, notification, callback) {
339
+ executedHandler = true;
340
+ callback();
341
+ }
342
+
343
+ iotAgentLib.setNotificationHandler(mockedHandler);
344
+
345
+ request(options, function (error, response, body) {
346
+ request(notificationOptions, function (error, response, body) {
347
+ response.statusCode.should.equal(200);
348
+ contextBrokerMock.done();
349
+ done();
350
+ });
351
+ });
352
+ });
353
+
240
354
  it('should return the transformed values', function (done) {
241
355
  let transformedHandler = false;
242
356
 
@@ -270,6 +384,7 @@ describe('NGSI-v2 - Bidirectional data plugin', function () {
270
384
  });
271
385
  });
272
386
 
387
+
273
388
  describe('When a new Group provisioning request arrives with bidirectional attributes', function () {
274
389
  const provisionGroup = {
275
390
  url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/services',
@@ -215,6 +215,31 @@ const iotAgentConfig = {
215
215
  }
216
216
  ]
217
217
  },
218
+ WeatherStation9: {
219
+ commands: [],
220
+ type: 'WeatherStation',
221
+ name: 'ws9b',
222
+ lazy: [],
223
+ active: [
224
+ {
225
+ object_id: 'p',
226
+ name: 'pressure',
227
+ type: 'Hgmm'
228
+ },
229
+ {
230
+ object_id: 'h',
231
+ name: 'humidity',
232
+ type: 'Percentage',
233
+ entity_type: 'Higrometer',
234
+ metadata: {
235
+ unitCode: {
236
+ type: 'Text',
237
+ value: 'Hgmm'
238
+ }
239
+ }
240
+ }
241
+ ]
242
+ },
218
243
  WeatherStation8Jexl: {
219
244
  commands: [],
220
245
  type: 'WeatherStation',
@@ -581,6 +606,44 @@ describe('NGSI-v2 - Multi-entity plugin', function () {
581
606
  });
582
607
  });
583
608
 
609
+ describe('When an update comes for a multientity measurement based on entity_type', function () {
610
+ const values = [
611
+ {
612
+ name: 'p',
613
+ type: 'centigrades',
614
+ value: '52'
615
+ },
616
+ {
617
+ name: 'h',
618
+ type: 'Percentage',
619
+ value: '12'
620
+ }
621
+ ];
622
+
623
+ beforeEach(function () {
624
+ nock.cleanAll();
625
+
626
+ contextBrokerMock = nock('http://192.168.1.1:1026')
627
+ .matchHeader('fiware-service', 'smartgondor')
628
+ .matchHeader('fiware-servicepath', 'gardens')
629
+ .post(
630
+ '/v2/op/update',
631
+ utils.readExampleFile(
632
+ './test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin17.json'
633
+ )
634
+ )
635
+ .reply(204);
636
+ });
637
+
638
+ it('should send two context elements, one for each entity', function (done) {
639
+ iotAgentLib.update('ws9b', 'WeatherStation9', '', values, function (error) {
640
+ should.not.exist(error);
641
+ contextBrokerMock.done();
642
+ done();
643
+ });
644
+ });
645
+ });
646
+
584
647
  describe('When an update comes for a multientity measurement with same attribute name', function () {
585
648
  const values = [
586
649
  {
@@ -705,6 +705,66 @@ describe('NGSI-v2 - Device provisioning API: Provision devices', function () {
705
705
  }
706
706
  );
707
707
 
708
+ describe('When a device provisioning request arrives to the IoTA and entityNameExp was configured at group level', function () {
709
+ const options = {
710
+ url: 'http://localhost:' + iotAgentConfig.server.port + '/iot/devices',
711
+ method: 'POST',
712
+ json: utils.readExampleFile('./test/unit/examples/deviceProvisioningRequests/provisionMinimumDevice4.json'),
713
+ headers: {
714
+ 'fiware-service': 'smartgondor',
715
+ 'fiware-servicepath': '/gardens'
716
+ }
717
+ };
718
+ const groupCreation = {
719
+ url: 'http://localhost:4041/iot/services',
720
+ method: 'POST',
721
+ json: {
722
+ services: [
723
+ {
724
+ resource: '/Thing',
725
+ apikey: '801230BJKL23Y9090DSFL123HJK09H324HV8732',
726
+ /*jshint camelcase: false */
727
+ entity_type: 'MicroLights',
728
+ entityNameExp: 'EntityNameByExp',
729
+ cbroker: 'http://192.168.1.1:1026'
730
+ }
731
+ ]
732
+ },
733
+ headers: {
734
+ 'fiware-service': 'smartgondor',
735
+ 'fiware-servicepath': '/gardens'
736
+ }
737
+ };
738
+
739
+ beforeEach(function (done) {
740
+ nock.cleanAll();
741
+ contextBrokerMock = nock('http://192.168.1.1:1026')
742
+ .matchHeader('fiware-service', 'smartgondor')
743
+ .matchHeader('fiware-servicepath', '/gardens')
744
+ .post(
745
+ '/v2/entities?options=upsert',
746
+ utils.readExampleFile(
747
+ './test/unit/ngsiv2/examples/contextRequests/createMinimumProvisionedDevice4.json'
748
+ )
749
+ )
750
+ .reply(204);
751
+
752
+ done();
753
+ });
754
+
755
+ it('should store the entity name defined by expression', function (done) {
756
+ request(groupCreation, function (error, response, body) {
757
+ request(options, function (error, response, body) {
758
+ iotAgentLib.listDevices('smartgondor', '/gardens', function (error, results) {
759
+ should.exist(results.devices[0].name);
760
+ results.devices[0].name.should.equal('EntityNameByExp');
761
+ done();
762
+ });
763
+ });
764
+ });
765
+ });
766
+ });
767
+
708
768
  describe(
709
769
  'When a device provisioning request with static attributes arrives to the IoTA' +
710
770
  ' and same static attribute is also configured at group level',
@@ -484,10 +484,11 @@ describe('NGSI-v2 - Device provisioning API: Update provisioned devices', functi
484
484
 
485
485
  it('should provision the explicitAttrs attribute appropriately', function (done) {
486
486
  request(optionsUpdate, function (error, response, body) {
487
+ should.not.exist(error);
488
+ response.statusCode.should.equal(204);
487
489
  request(optionsGetDevice, function (error, response, body) {
488
490
  should.not.exist(error);
489
491
  response.statusCode.should.equal(200);
490
-
491
492
  body.explicitAttrs.should.equal(false);
492
493
  done();
493
494
  });