iotagent-node-lib 3.1.0 → 3.2.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 (48) hide show
  1. package/CHANGES_NEXT_RELEASE +0 -2
  2. package/config.js +5 -5
  3. package/doc/api.md +1540 -298
  4. package/doc/deprecated.md +3 -1
  5. package/doc/development.md +120 -0
  6. package/doc/installationguide.md +3 -6
  7. package/lib/commonConfig.js +7 -10
  8. package/lib/fiware-iotagent-lib.js +0 -10
  9. package/lib/jexlTranformsMap.js +2 -1
  10. package/lib/plugins/bidirectionalData.js +8 -26
  11. package/lib/plugins/expressionPlugin.js +8 -40
  12. package/lib/plugins/jexlParser.js +28 -0
  13. package/lib/services/commands/commandService.js +1 -1
  14. package/lib/services/devices/deviceService.js +2 -1
  15. package/lib/services/ngsi/entities-NGSI-LD.js +13 -57
  16. package/lib/services/ngsi/entities-NGSI-v2.js +145 -108
  17. package/lib/services/northBound/deviceProvisioningServer.js +17 -14
  18. package/lib/templates/createDevice.json +5 -2
  19. package/lib/templates/createDeviceLax.json +7 -5
  20. package/lib/templates/updateDevice.json +5 -2
  21. package/lib/templates/updateDeviceLax.json +3 -5
  22. package/package.json +1 -1
  23. package/test/unit/examples/deviceProvisioningRequests/provisionBidirectionalDevice.json +5 -5
  24. package/test/unit/examples/deviceProvisioningRequests/provisionMinimumDevice4.json +1 -0
  25. package/test/unit/examples/groupProvisioningRequests/bidirectionalGroup.json +4 -4
  26. package/test/unit/ngsi-ld/expressions/jexlBasedTransformations-test.js +0 -2
  27. package/test/unit/ngsi-ld/plugins/bidirectional-plugin_test.js +8 -8
  28. package/test/unit/ngsi-ld/plugins/custom-plugin_test.js +152 -0
  29. package/test/unit/ngsi-ld/plugins/multientity-plugin_test.js +1 -1
  30. package/test/unit/ngsiv2/examples/contextRequests/createMinimumProvisionedDevice4.json +5 -1
  31. package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin31.json +0 -8
  32. package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin35.json +20 -0
  33. package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin40.json +42 -0
  34. package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin41.json +32 -0
  35. package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin25.json +37 -0
  36. package/test/unit/ngsiv2/expressions/jexlBasedTransformations-test.js +228 -6
  37. package/test/unit/ngsiv2/lazyAndCommands/polling-commands-test.js +1 -2
  38. package/test/unit/ngsiv2/plugins/bidirectional-plugin_test.js +6 -6
  39. package/test/unit/ngsiv2/plugins/custom-plugin_test.js +151 -0
  40. package/test/unit/ngsiv2/plugins/multientity-plugin_test.js +85 -12
  41. package/test/unit/ngsiv2/provisioning/device-provisioning-api_test.js +11 -3
  42. package/doc/advanced-topics.md +0 -626
  43. package/doc/expressionLanguage.md +0 -762
  44. package/lib/plugins/expressionParser.js +0 -205
  45. package/test/unit/expressions/expression-test.js +0 -197
  46. package/test/unit/ngsi-ld/expressions/expressionBasedTransformations-test.js +0 -882
  47. package/test/unit/ngsiv2/expressions/expressionBasedTransformations-test.js +0 -951
  48. package/test/unit/ngsiv2/expressions/expressionCombinedTransformations-test.js +0 -296
@@ -39,7 +39,6 @@ const iotAgentConfig = {
39
39
  port: '1026',
40
40
  ngsiVersion: 'v2'
41
41
  },
42
- defaultExpressionLanguage: 'jexl',
43
42
  server: {
44
43
  port: 4041,
45
44
  host: 'localhost'
@@ -126,7 +125,7 @@ const iotAgentConfig = {
126
125
  {
127
126
  name: 'weather',
128
127
  type: 'Summary',
129
- expression: '"Humidity " + (humidity / 2) + " and pressure " + (pressure * 20)'
128
+ expression: '"Humidity " + (humidity / 2) + " and pressure " + (p * 20)'
130
129
  },
131
130
  {
132
131
  object_id: 'a',
@@ -250,6 +249,24 @@ const iotAgentConfig = {
250
249
  ],
251
250
  explicitAttrs: true
252
251
  },
252
+ GPS2b: {
253
+ commands: [],
254
+ type: 'GPS',
255
+ lazy: [],
256
+ active: [
257
+ {
258
+ name: 'location',
259
+ type: 'geo:json',
260
+ expression: "{coordinates: [lon,lat], type: 'Point'}"
261
+ },
262
+ {
263
+ name: 'temperature',
264
+ type: 'Number',
265
+ expression: 't * 10'
266
+ }
267
+ ],
268
+ explicitAttrs: true
269
+ },
253
270
  GPS3: {
254
271
  commands: [],
255
272
  type: 'GPS',
@@ -321,8 +338,31 @@ const iotAgentConfig = {
321
338
  }
322
339
  ],
323
340
  explicitAttrs: "theLocation ? ['mylocation'] : []"
324
- // #1267 this is not working:
325
- //explicitAttrs: "theLocation ? [{object_id: 'theLocation'}] : []"
341
+ },
342
+ GPS5b: {
343
+ commands: [],
344
+ type: 'GPS',
345
+ lazy: [],
346
+ static: [
347
+ {
348
+ name: 'lat',
349
+ type: 'string',
350
+ value: '52'
351
+ }
352
+ ],
353
+ active: [
354
+ {
355
+ name: 'price',
356
+ type: 'number'
357
+ },
358
+ {
359
+ object_id: 'theLocation',
360
+ name: 'mylocation',
361
+ type: 'geo:json',
362
+ expression: "{coordinates: [lon,lat], type: 'Point'}"
363
+ }
364
+ ],
365
+ explicitAttrs: "theLocation ? [{object_id: 'theLocation'}] : []"
326
366
  },
327
367
  GPS6: {
328
368
  commands: [],
@@ -388,7 +428,6 @@ const iotAgentConfigTS = {
388
428
  port: '1026',
389
429
  ngsiVersion: 'v2'
390
430
  },
391
- defaultExpressionLanguage: 'jexl',
392
431
  server: {
393
432
  port: 4041,
394
433
  host: 'localhost'
@@ -406,6 +445,57 @@ const iotAgentConfigTS = {
406
445
  }
407
446
  ],
408
447
  explicitAttrs: true
448
+ },
449
+ WaterTank: {
450
+ commands: [],
451
+ type: 'WaterTank',
452
+ lazy: [],
453
+ active: [
454
+ {
455
+ object_id: 'cnt2',
456
+ name: 'contA',
457
+ type: 'Number'
458
+ },
459
+ {
460
+ object_id: 'cnt3',
461
+ name: 'contB',
462
+ type: 'Number'
463
+ },
464
+ {
465
+ object_id: 'false2',
466
+ name: 'waterLeavingTanks',
467
+ type: 'Number',
468
+ expression: 'cnt2*0.1',
469
+ entity_name: 'PA_A_0001',
470
+ entity_type: 'WaterTank'
471
+ },
472
+ {
473
+ object_id: 'false3',
474
+ name: 'waterLeavingTanks',
475
+ type: 'Number',
476
+ expression: 'cnt3*0.1',
477
+ entity_name: 'PA_B_0001',
478
+ entity_type: 'WaterTank'
479
+ },
480
+ {
481
+ object_id: 'foostatus2',
482
+ name: 'status',
483
+ type: 'Text',
484
+ expression: 'status',
485
+ entity_name: 'PA_A_0001',
486
+ entity_type: 'WaterTank'
487
+ },
488
+ {
489
+ object_id: 'foostatus3',
490
+ name: 'status',
491
+ type: 'Text',
492
+ expression: 'status',
493
+ entity_name: 'PA_B_0001',
494
+ entity_type: 'WaterTank'
495
+ }
496
+ ],
497
+ explicitAttrs:
498
+ "contA&&contB?['TimeInstant','contA',{object_id:'false2'},'contB',{object_id:'false3'},'status']:contA?['TimeInstant','contA',{object_id:'false2'},{object_id:'foostatus2'}]:contB?['TimeInstant','contB',{object_id:'false3'},{object_id:'foostatus3'}]:[]"
409
499
  }
410
500
  },
411
501
  timestamp: true,
@@ -1080,7 +1170,7 @@ describe('Java expression language (JEXL) based transformations plugin', functio
1080
1170
  .patch(
1081
1171
  '/v2/entities/gps1/attrs',
1082
1172
  utils.readExampleFile(
1083
- './test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin34.json'
1173
+ './test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin35.json'
1084
1174
  )
1085
1175
  )
1086
1176
  .query({ type: 'GPS' })
@@ -1096,6 +1186,56 @@ describe('Java expression language (JEXL) based transformations plugin', functio
1096
1186
  });
1097
1187
  });
1098
1188
 
1189
+ describe('When there is an extra measure sent by the device to be removed', function () {
1190
+ // Case: Expression which results is sent as a new attribute
1191
+ const values = [
1192
+ {
1193
+ name: 'lat',
1194
+ type: 'Number',
1195
+ value: 52
1196
+ },
1197
+ {
1198
+ name: 'lon',
1199
+ type: 'Number',
1200
+ value: 13
1201
+ },
1202
+ {
1203
+ name: 'another',
1204
+ type: 'Number',
1205
+ value: 99
1206
+ },
1207
+ {
1208
+ name: 'TimeInstant',
1209
+ type: 'DateTime',
1210
+ value: '2015-08-05T07:35:01.468+00:00'
1211
+ }
1212
+ ];
1213
+
1214
+ beforeEach(function () {
1215
+ nock.cleanAll();
1216
+
1217
+ contextBrokerMock = nock('http://192.168.1.1:1026')
1218
+ .matchHeader('fiware-service', 'smartgondor')
1219
+ .matchHeader('fiware-servicepath', 'gardens')
1220
+ .patch(
1221
+ '/v2/entities/gps1/attrs',
1222
+ utils.readExampleFile(
1223
+ './test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin41.json'
1224
+ )
1225
+ )
1226
+ .query({ type: 'GPS' })
1227
+ .reply(204);
1228
+ });
1229
+
1230
+ it('should calculate them and remove non-explicitAttrs from the payload', function (done) {
1231
+ iotAgentLib.update('gps1', 'GPS2b', '', values, function (error) {
1232
+ should.not.exist(error);
1233
+ contextBrokerMock.done();
1234
+ done();
1235
+ });
1236
+ });
1237
+ });
1238
+
1099
1239
  describe('When there is an extra TimeInstant sent by the device to be removed by string', function () {
1100
1240
  // Case: Expression which results is sent as a new attribute
1101
1241
  const values = [
@@ -1236,6 +1376,46 @@ describe('Java expression language (JEXL) based transformations plugin', functio
1236
1376
  });
1237
1377
  });
1238
1378
 
1379
+ describe('When there is an extra TimeInstant sent by the device to be removed by jexl expression with context defined with object_id', function () {
1380
+ // Case: Expression which results is sent as a new attribute
1381
+ const values = [
1382
+ {
1383
+ name: 'lon',
1384
+ type: 'Number',
1385
+ value: 13
1386
+ },
1387
+ {
1388
+ name: 'TimeInstant',
1389
+ type: 'DateTime',
1390
+ value: '2015-08-05T07:35:01.468+00:00'
1391
+ }
1392
+ ];
1393
+
1394
+ beforeEach(function () {
1395
+ nock.cleanAll();
1396
+
1397
+ contextBrokerMock = nock('http://192.168.1.1:1026')
1398
+ .matchHeader('fiware-service', 'smartgondor')
1399
+ .matchHeader('fiware-servicepath', 'gardens')
1400
+ .patch(
1401
+ '/v2/entities/gps1/attrs',
1402
+ utils.readExampleFile(
1403
+ './test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin36.json'
1404
+ )
1405
+ )
1406
+ .query({ type: 'GPS' })
1407
+ .reply(204);
1408
+ });
1409
+
1410
+ it('should calculate them and remove non-explicitAttrs by jexl expression with context from the payload ', function (done) {
1411
+ iotAgentLib.update('gps1', 'GPS5b', '', values, function (error) {
1412
+ should.not.exist(error);
1413
+ contextBrokerMock.done();
1414
+ done();
1415
+ });
1416
+ });
1417
+ });
1418
+
1239
1419
  describe('When there is an extra TimeInstant sent by the device to be removedb jexl expression using static attrs', function () {
1240
1420
  // Case: Expression which results is sent as a new attribute
1241
1421
  const values = [
@@ -1378,4 +1558,46 @@ describe('Java expression language (JEXL) based transformations plugin - Timesta
1378
1558
  });
1379
1559
  });
1380
1560
  });
1561
+
1562
+ describe('When explicitAttrs is a jexl expression in a multientity case', function () {
1563
+ // Case: Expression which results is sent as a new attribute
1564
+ const values = [
1565
+ {
1566
+ name: 'cnt3',
1567
+ type: 'Number',
1568
+ value: '31450.000'
1569
+ }
1570
+ ];
1571
+
1572
+ beforeEach(function () {
1573
+ const time = new Date(1438760101468); // 2015-08-05T07:35:01.468+00:00
1574
+
1575
+ timekeeper.freeze(time);
1576
+ nock.cleanAll();
1577
+
1578
+ contextBrokerMock = nock('http://192.168.1.1:1026')
1579
+ .matchHeader('fiware-service', 'smartgondor')
1580
+ .matchHeader('fiware-servicepath', 'gardens')
1581
+ .post(
1582
+ '/v2/op/update',
1583
+ utils.readExampleFile(
1584
+ './test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin40.json'
1585
+ )
1586
+ )
1587
+ .reply(204);
1588
+ });
1589
+
1590
+ afterEach(function (done) {
1591
+ timekeeper.reset();
1592
+ done();
1593
+ });
1594
+
1595
+ it('should calculate them and not remove the timestamp from the payload', function (done) {
1596
+ iotAgentLib.update('water1', 'WaterTank', '', values, function (error) {
1597
+ should.not.exist(error);
1598
+ contextBrokerMock.done();
1599
+ done();
1600
+ });
1601
+ });
1602
+ });
1381
1603
  });
@@ -140,8 +140,7 @@ const device4 = {
140
140
  type: 'RobotExp',
141
141
  service: 'smartgondor',
142
142
  subservice: 'gardens',
143
- polling: true,
144
- expressionLanguage: 'jexl'
143
+ polling: true
145
144
  };
146
145
 
147
146
  describe('NGSI-v2 - Polling commands', function () {
@@ -246,11 +246,11 @@ describe('NGSI-v2 - Bidirectional data plugin', function () {
246
246
  let longitudeFound = false;
247
247
 
248
248
  for (let i = 0; i < values.length; i++) {
249
- if (values[i].name === 'latitude' && values[i].type === 'string' && values[i].value === '-9.6') {
249
+ if (values[i].name === 'latitude' && values[i].type === 'Number' && values[i].value === -9.6) {
250
250
  latitudeFound = true;
251
251
  }
252
252
 
253
- if (values[i].name === 'longitude' && values[i].type === 'string' && values[i].value === '12.4') {
253
+ if (values[i].name === 'longitude' && values[i].type === 'Number' && values[i].value === 12.4) {
254
254
  longitudeFound = true;
255
255
  }
256
256
  }
@@ -357,11 +357,11 @@ describe('NGSI-v2 - Bidirectional data plugin', function () {
357
357
  let longitudeFound = false;
358
358
 
359
359
  for (let i = 0; i < values.length; i++) {
360
- if (values[i].name === 'latitude' && values[i].type === 'string' && values[i].value === '-9.6') {
360
+ if (values[i].name === 'latitude' && values[i].type === 'Number' && values[i].value === -9.6) {
361
361
  latitudeFound = true;
362
362
  }
363
363
 
364
- if (values[i].name === 'longitude' && values[i].type === 'string' && values[i].value === '12.4') {
364
+ if (values[i].name === 'longitude' && values[i].type === 'Number' && values[i].value === 12.4) {
365
365
  longitudeFound = true;
366
366
  }
367
367
  }
@@ -503,11 +503,11 @@ describe('NGSI-v2 - Bidirectional data plugin', function () {
503
503
  let longitudeFound = false;
504
504
 
505
505
  for (let i = 0; i < values.length; i++) {
506
- if (values[i].name === 'latitude' && values[i].type === 'string' && values[i].value === '-9.6') {
506
+ if (values[i].name === 'latitude' && values[i].type === 'Number' && values[i].value === -9.6) {
507
507
  latitudeFound = true;
508
508
  }
509
509
 
510
- if (values[i].name === 'longitude' && values[i].type === 'string' && values[i].value === '12.4') {
510
+ if (values[i].name === 'longitude' && values[i].type === 'Number' && values[i].value === 12.4) {
511
511
  longitudeFound = true;
512
512
  }
513
513
  }
@@ -0,0 +1,151 @@
1
+ /*
2
+ * Copyright 2015 Telefonica Investigación y Desarrollo, S.A.U
3
+ *
4
+ * This file is part of fiware-iotagent-lib
5
+ *
6
+ * fiware-iotagent-lib is free software: you can redistribute it and/or
7
+ * modify it under the terms of the GNU Affero General Public License as
8
+ * published by the Free Software Foundation, either version 3 of the License,
9
+ * or (at your option) any later version.
10
+ *
11
+ * fiware-iotagent-lib is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14
+ * See the GNU Affero General Public License for more details.
15
+ *
16
+ * You should have received a copy of the GNU Affero General Public
17
+ * License along with fiware-iotagent-lib.
18
+ * If not, see http://www.gnu.org/licenses/.
19
+ *
20
+ * For those usages not covered by the GNU Affero General Public License
21
+ * please contact with::daniel.moranjimenez@telefonica.com
22
+ *
23
+ * Modified by: Daniel Calvo - ATOS Research & Innovation
24
+ */
25
+
26
+ const iotAgentLib = require('../../../../lib/fiware-iotagent-lib');
27
+ const should = require('should');
28
+ const logger = require('logops');
29
+ const nock = require('nock');
30
+ let contextBrokerMock;
31
+ const iotAgentConfig = {
32
+ contextBroker: {
33
+ host: '192.168.1.1',
34
+ port: '1026',
35
+ ngsiVersion: 'v2'
36
+ },
37
+ server: {
38
+ port: 4041
39
+ },
40
+ types: {
41
+ Light: {
42
+ commands: [],
43
+ type: 'Light',
44
+ lazy: [
45
+ {
46
+ name: 'temperature',
47
+ type: 'centigrades'
48
+ }
49
+ ],
50
+ active: [
51
+ {
52
+ name: 'pressure',
53
+ type: 'Hgmm'
54
+ }
55
+ ]
56
+ }
57
+ },
58
+ service: 'smartgondor',
59
+ subservice: 'gardens',
60
+ providerUrl: 'http://smartgondor.com'
61
+ };
62
+
63
+ describe('NGSI-v2 - Custom plugin', function () {
64
+ let updateInvoked = false;
65
+ let queryInvoked = false;
66
+
67
+ function updatePlugin(entity, typeInformation, callback) {
68
+ updateInvoked = true;
69
+ return callback(null, entity, typeInformation);
70
+ }
71
+ function queryPlugin(entity, typeInformation, callback) {
72
+ queryInvoked = true;
73
+ return callback(null, entity, typeInformation);
74
+ }
75
+ beforeEach(function (done) {
76
+ logger.setLevel('FATAL');
77
+
78
+ iotAgentLib.activate(iotAgentConfig, function () {
79
+ iotAgentLib.clearAll(function () {
80
+ iotAgentLib.addUpdateMiddleware(updatePlugin);
81
+ iotAgentLib.addQueryMiddleware(queryPlugin);
82
+ done();
83
+ });
84
+ });
85
+ });
86
+
87
+ afterEach(function (done) {
88
+ iotAgentLib.clearAll(function () {
89
+ iotAgentLib.deactivate(done);
90
+ updateInvoked = false;
91
+ queryInvoked = false;
92
+ });
93
+ });
94
+ describe('When an update occurs', function () {
95
+ const values = [
96
+ {
97
+ name: 'state',
98
+ type: 'Boolean',
99
+ value: 'true'
100
+ },
101
+ {
102
+ name: 'dimming',
103
+ type: 'Number',
104
+ value: 23
105
+ }
106
+ ];
107
+
108
+ beforeEach(function () {
109
+ nock.cleanAll();
110
+
111
+ contextBrokerMock = nock('http://192.168.1.1:1026')
112
+ .matchHeader('fiware-service', 'smartgondor')
113
+ .matchHeader('fiware-servicepath', 'gardens')
114
+ .patch('/v2/entities/light1/attrs')
115
+ .query({ type: 'Light' })
116
+ .reply(204);
117
+ });
118
+
119
+ it('should invoke the plugin', function (done) {
120
+ iotAgentLib.update('light1', 'Light', '', values, function (error) {
121
+ should.not.exist(error);
122
+ contextBrokerMock.done();
123
+ updateInvoked.should.equal(true);
124
+ done();
125
+ });
126
+ });
127
+ });
128
+ describe('When an query occurs', function () {
129
+ beforeEach(function () {
130
+ nock.cleanAll();
131
+
132
+ contextBrokerMock = nock('http://192.168.1.1:1026')
133
+ .matchHeader('fiware-service', 'smartgondor')
134
+ .matchHeader('fiware-servicepath', 'gardens')
135
+ .get('/v2/entities/light1/attrs')
136
+ .query({ type: 'Light', attrs: 'state,dimming' })
137
+ .reply(200, { state: 'good', dimming: '23' });
138
+ });
139
+
140
+ it('should invoke the plugin', function (done) {
141
+ const attributes = ['state', 'dimming'];
142
+ iotAgentLib.query('light1', 'Light', '', attributes, function (error) {
143
+ should.not.exist(error);
144
+ contextBrokerMock.done();
145
+ should.not.exist(error);
146
+ queryInvoked.should.equal(true);
147
+ done();
148
+ });
149
+ });
150
+ });
151
+ });
@@ -102,7 +102,7 @@ const iotAgentConfig = {
102
102
  object_id: 'h',
103
103
  name: 'humidity',
104
104
  type: 'Percentage',
105
- entity_name: 'Station Number ${@sn * 10}'
105
+ entity_name: '"Station Number "+sn*10'
106
106
  }
107
107
  ]
108
108
  },
@@ -115,13 +115,13 @@ const iotAgentConfig = {
115
115
  object_id: 'p',
116
116
  name: 'pressure',
117
117
  type: 'Hgmm',
118
- entity_name: 'Station Number ${@sn * 10}'
118
+ entity_name: '"Station Number "+sn*10'
119
119
  },
120
120
  {
121
121
  object_id: 'h',
122
122
  name: 'humidity',
123
123
  type: 'Percentage',
124
- entity_name: 'Station Number ${@sn * 10}'
124
+ entity_name: '"Station Number "+sn*10'
125
125
  }
126
126
  ]
127
127
  },
@@ -197,21 +197,21 @@ const iotAgentConfig = {
197
197
  {
198
198
  object_id: 'v1',
199
199
  name: 'vol',
200
- expression: '${@v1*100}',
200
+ expression: 'v1*100',
201
201
  type: 'Number',
202
202
  entity_name: 'WeatherStation1'
203
203
  },
204
204
  {
205
205
  object_id: 'v2',
206
206
  name: 'vol',
207
- expression: '${@v2*100}',
207
+ expression: 'v2*100',
208
208
  type: 'Number',
209
209
  entity_name: 'WeatherStation2'
210
210
  },
211
211
  {
212
212
  object_id: 'v',
213
213
  name: 'vol',
214
- expression: '${@v*100}',
214
+ expression: 'v*100',
215
215
  type: 'Number'
216
216
  }
217
217
  ]
@@ -244,7 +244,6 @@ const iotAgentConfig = {
244
244
  WeatherStation8Jexl: {
245
245
  commands: [],
246
246
  type: 'WeatherStation',
247
- expressionLanguage: 'jexl',
248
247
  lazy: [],
249
248
  active: [
250
249
  {
@@ -272,7 +271,6 @@ const iotAgentConfig = {
272
271
  WeatherStation9Jexl: {
273
272
  commands: [],
274
273
  type: 'WeatherStation',
275
- expressionLanguage: 'jexl',
276
274
  lazy: [],
277
275
  static: [
278
276
  {
@@ -364,7 +362,6 @@ const iotAgentConfig = {
364
362
  WrongStation: {
365
363
  commands: [],
366
364
  type: 'WrongStation',
367
- expressionLanguage: 'jexl',
368
365
  lazy: [],
369
366
  active: [
370
367
  {
@@ -456,7 +453,6 @@ const iotAgentConfig = {
456
453
  SharedIds3: {
457
454
  commands: [],
458
455
  type: 'ShareStation',
459
- expressionLanguage: 'jexl',
460
456
  lazy: [],
461
457
  active: [
462
458
  {
@@ -510,6 +506,42 @@ const iotAgentConfig = {
510
506
  ],
511
507
  explicitAttrs: true
512
508
  },
509
+ GPS1: {
510
+ commands: [],
511
+ expressionLanguage: 'jexl',
512
+ type: 'GPS',
513
+ lazy: [],
514
+ active: [
515
+ {
516
+ name: 'explicit',
517
+ type: 'number',
518
+ object_id: 'z'
519
+ },
520
+ {
521
+ name: 'expectedAtt',
522
+ type: 'number',
523
+ expression: 'z+1'
524
+ },
525
+ {
526
+ name: 'nonexpectedAtt',
527
+ type: 'number',
528
+ expression: 'w+1'
529
+ },
530
+ {
531
+ name: 'explicit',
532
+ type: 'number',
533
+ entity_name: 'SO5',
534
+ object_id: 'x'
535
+ },
536
+ {
537
+ name: 'explicit',
538
+ type: 'number',
539
+ entity_name: 'SO6',
540
+ object_id: 'y'
541
+ }
542
+ ],
543
+ explicitAttrs: true
544
+ },
513
545
  GPS2: {
514
546
  commands: [],
515
547
  type: 'GPS',
@@ -559,8 +591,7 @@ const iotAgentConfig = {
559
591
  name: 'pressure',
560
592
  type: 'Number'
561
593
  }
562
- ],
563
- expressionLanguage: 'jexl'
594
+ ]
564
595
  }
565
596
  },
566
597
  service: 'smartgondor',
@@ -1244,6 +1275,48 @@ describe('NGSI-v2 - Multi-entity plugin', function () {
1244
1275
  });
1245
1276
  });
1246
1277
 
1278
+ describe('When an update comes for a multientity measurement explicitAttrs for several entities', function () {
1279
+ const values = [
1280
+ {
1281
+ name: 'x',
1282
+ type: 'Number',
1283
+ value: 52
1284
+ },
1285
+ {
1286
+ name: 'y',
1287
+ type: 'Number',
1288
+ value: 13
1289
+ },
1290
+ {
1291
+ name: 'z',
1292
+ type: 'Number',
1293
+ value: 12
1294
+ }
1295
+ ];
1296
+
1297
+ beforeEach(function () {
1298
+ nock.cleanAll();
1299
+ contextBrokerMock = nock('http://192.168.1.1:1026')
1300
+ .matchHeader('fiware-service', 'smartgondor')
1301
+ .matchHeader('fiware-servicepath', 'gardens')
1302
+ .post(
1303
+ '/v2/op/update',
1304
+ utils.readExampleFile(
1305
+ './test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin25.json'
1306
+ )
1307
+ )
1308
+ .reply(204);
1309
+ });
1310
+
1311
+ it('should remove hidden attrs from the value', function (done) {
1312
+ iotAgentLib.update('gps1', 'GPS1', '', values, function (error) {
1313
+ should.not.exist(error);
1314
+ contextBrokerMock.done();
1315
+ done();
1316
+ });
1317
+ });
1318
+ });
1319
+
1247
1320
  describe('When an update comes for a multientity measurement explicitAttrs as jexl for one entity', function () {
1248
1321
  const values = [
1249
1322
  {