iotagent-node-lib 2.23.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.
- package/.nyc_output/33364de2-1199-4ec2-b33c-cae063ef8cc4.json +1 -0
- package/.nyc_output/processinfo/33364de2-1199-4ec2-b33c-cae063ef8cc4.json +1 -0
- package/.nyc_output/processinfo/index.json +1 -1
- package/CHANGES_NEXT_RELEASE +0 -2
- package/doc/advanced-topics.md +55 -2
- package/doc/architecture.md +52 -5
- package/doc/expressionLanguage.md +2 -1
- package/doc/usermanual.md +18 -16
- package/lib/errors.js +9 -1
- package/lib/plugins/bidirectionalData.js +104 -6
- package/lib/plugins/expressionPlugin.js +10 -0
- package/lib/plugins/pluginUtils.js +2 -1
- package/lib/request-shim.js +2 -1
- package/lib/services/devices/deviceService.js +34 -20
- package/lib/services/ngsi/entities-NGSI-v2.js +14 -4
- package/lib/services/northBound/contextServer-NGSI-LD.js +96 -20
- package/lib/services/northBound/contextServer-NGSI-v2.js +1 -0
- package/package.json +1 -1
- package/test/unit/memoryRegistry/deviceRegistryMemory_test.js +51 -0
- package/test/unit/mongodb/mongodb-group-registry-test.js +24 -0
- package/test/unit/mongodb/mongodb-registry-test.js +49 -0
- package/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotificationWithDatasetId.json +21 -0
- package/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotificationWithMetadata.json +17 -0
- package/test/unit/ngsi-ld/lazyAndCommands/command-test.js +656 -2
- package/test/unit/ngsi-ld/ngsiService/unsupported-endpoints-test.js +111 -0
- package/test/unit/ngsi-ld/plugins/bidirectional-plugin_test.js +221 -0
- package/test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalNotificationWithMetadata.json +19 -0
- package/test/unit/ngsiv2/lazyAndCommands/command-test.js +106 -0
- package/test/unit/ngsiv2/plugins/bidirectional-plugin_test.js +115 -0
- package/.nyc_output/6e3d7795-bf8c-4a50-bd2f-f8221f27aeae.json +0 -1
- package/.nyc_output/processinfo/6e3d7795-bf8c-4a50-bd2f-f8221f27aeae.json +0 -1
- package/config +0 -0
- package/docker/Mosquitto/Dockerfile.debian +0 -38
- package/docker/Mosquitto/Dockerfile.debian~ +0 -23
- package/lib/plugins/multiEntity.js_avg2 +0 -343
- package/test/unit/ngsiv2/plugins/multientity-plugin_test.js_avg2 +0 -1224
|
@@ -387,8 +387,13 @@ function removeHiddenAttrsFromMultiEntity(entities, typeInformation, payload) {
|
|
|
387
387
|
...jexlParser.extractContext(attsArray),
|
|
388
388
|
...extractContextFromPayload(payload)
|
|
389
389
|
}; //maybe overlapping between this two objects.
|
|
390
|
-
|
|
391
|
-
|
|
390
|
+
let res = null;
|
|
391
|
+
try {
|
|
392
|
+
res = jexlParser.applyExpression(typeInformation.explicitAttrs, ctx, typeInformation);
|
|
393
|
+
} catch (e) {
|
|
394
|
+
// nothing to do: exception is already logged at info level
|
|
395
|
+
}
|
|
396
|
+
explicitAttrsList = res ? explicitAttrsList.concat(res) : explicitAttrsList;
|
|
392
397
|
});
|
|
393
398
|
}
|
|
394
399
|
}
|
|
@@ -438,8 +443,13 @@ function removeHiddenAttrs(result, typeInformation, payload) {
|
|
|
438
443
|
...jexlParser.extractContext(attsArray),
|
|
439
444
|
...extractContextFromPayload(payload)
|
|
440
445
|
}; //maybe overlapping between this two objects. Measures not in active attrs.
|
|
441
|
-
|
|
442
|
-
|
|
446
|
+
let res = null;
|
|
447
|
+
try {
|
|
448
|
+
res = jexlParser.applyExpression(typeInformation.explicitAttrs, ctx, typeInformation);
|
|
449
|
+
} catch (e) {
|
|
450
|
+
// nothing to do: exception is already logged at info level
|
|
451
|
+
}
|
|
452
|
+
explicitAttrsList = res ? res : [];
|
|
443
453
|
}
|
|
444
454
|
if (explicitAttrsList && explicitAttrsList.length >= 0) {
|
|
445
455
|
const hidden = _.difference(_.keys(result), explicitAttrsList);
|
|
@@ -46,6 +46,21 @@ const overwritePaths = ['/ngsi-ld/v1/entities/:entity/attrs', '/ngsi-ld/v1/entit
|
|
|
46
46
|
const updatePaths = ['/ngsi-ld/v1/entities/:entity/attrs', '/ngsi-ld/v1/entities/:entity/attrs/:attr'];
|
|
47
47
|
const queryPaths = ['/ngsi-ld/v1/entities/:entity'];
|
|
48
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Extract metadata attributes from input.
|
|
51
|
+
*
|
|
52
|
+
* @param {Object} obj input object
|
|
53
|
+
*/
|
|
54
|
+
function getMetaData(obj) {
|
|
55
|
+
const excludedKeys = ['datasetId', 'value', 'type'];
|
|
56
|
+
const metaData = {};
|
|
57
|
+
_.keys(obj).forEach((key) => {
|
|
58
|
+
if (!excludedKeys.includes(key)) {
|
|
59
|
+
metaData[key] = obj[key];
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
return !_.isEmpty(metaData) ? metaData : {};
|
|
63
|
+
}
|
|
49
64
|
/**
|
|
50
65
|
* Generate all the update actions corresponding to a update context request using Ngsi2.
|
|
51
66
|
* Update actions include updates in attributes and execution of commands.
|
|
@@ -59,6 +74,7 @@ function generateUpdateActionsNgsiLD(req, contextElement, callback) {
|
|
|
59
74
|
|
|
60
75
|
const attribute = req.params.attr;
|
|
61
76
|
const value = req.body.value;
|
|
77
|
+
const datasetId = req.body.datasetId;
|
|
62
78
|
const incomingAttrs = !req.params.attr ? _.keys(req.body) : [];
|
|
63
79
|
|
|
64
80
|
if (contextElement.id && contextElement.type) {
|
|
@@ -75,19 +91,49 @@ function generateUpdateActionsNgsiLD(req, contextElement, callback) {
|
|
|
75
91
|
|
|
76
92
|
if (device.commands) {
|
|
77
93
|
for (const j in device.commands) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
94
|
+
const name = device.commands[j].name;
|
|
95
|
+
if (attribute === name) {
|
|
96
|
+
if (_.isArray(req.body)) {
|
|
97
|
+
req.body[name].forEach((obj) => {
|
|
98
|
+
commands.push({
|
|
99
|
+
type: device.commands[j].type,
|
|
100
|
+
value: obj.value,
|
|
101
|
+
name: name,
|
|
102
|
+
datasetId: obj.datasetId,
|
|
103
|
+
metadata: getMetaData(obj)
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
} else {
|
|
107
|
+
commands.push({
|
|
108
|
+
type: device.commands[j].type,
|
|
109
|
+
value,
|
|
110
|
+
name,
|
|
111
|
+
datasetId,
|
|
112
|
+
metadata: getMetaData(req.body)
|
|
113
|
+
});
|
|
114
|
+
}
|
|
84
115
|
found = true;
|
|
85
|
-
} else if (incomingAttrs.includes(
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
116
|
+
} else if (incomingAttrs.includes(name)) {
|
|
117
|
+
if (_.isArray(req.body[name])) {
|
|
118
|
+
req.body[name].forEach((obj) => {
|
|
119
|
+
commands.push({
|
|
120
|
+
type: device.commands[j].type,
|
|
121
|
+
value: obj.value,
|
|
122
|
+
name: name,
|
|
123
|
+
datasetId: obj.datasetId,
|
|
124
|
+
metadata: getMetaData(obj)
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
} else {
|
|
128
|
+
const obj = req.body[name];
|
|
129
|
+
commands.push({
|
|
130
|
+
type: device.commands[j].type,
|
|
131
|
+
value: obj.value,
|
|
132
|
+
name: name,
|
|
133
|
+
datasetId: obj.datasetId,
|
|
134
|
+
metadata: getMetaData(obj)
|
|
135
|
+
});
|
|
136
|
+
}
|
|
91
137
|
found = true;
|
|
92
138
|
}
|
|
93
139
|
}
|
|
@@ -290,7 +336,7 @@ function handleQueryNgsiLD(req, res, next) {
|
|
|
290
336
|
|
|
291
337
|
if (device.staticAttributes) {
|
|
292
338
|
let selectedAttributes = [];
|
|
293
|
-
if (attributes === undefined || attributes.length === 0) {
|
|
339
|
+
if (attributes === null || attributes === undefined || attributes.length === 0) {
|
|
294
340
|
selectedAttributes = device.staticAttributes;
|
|
295
341
|
} else {
|
|
296
342
|
selectedAttributes = device.staticAttributes.filter(inAttributes);
|
|
@@ -316,8 +362,8 @@ function handleQueryNgsiLD(req, res, next) {
|
|
|
316
362
|
const results = device.lazy.map(getName);
|
|
317
363
|
callback(null, results);
|
|
318
364
|
} else {
|
|
319
|
-
logger.debug(context, "Couldn't find any attributes. Handling with
|
|
320
|
-
callback(null,
|
|
365
|
+
logger.debug(context, "Couldn't find any attributes. Handling with empty reference");
|
|
366
|
+
callback(null, []);
|
|
321
367
|
}
|
|
322
368
|
}
|
|
323
369
|
|
|
@@ -472,11 +518,25 @@ function handleNotificationNgsiLD(req, res, next) {
|
|
|
472
518
|
/* eslint-disable-next-line no-prototype-builtins */
|
|
473
519
|
if (dataElement.hasOwnProperty(key)) {
|
|
474
520
|
if (key !== 'id' && key !== 'type') {
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
521
|
+
if (_.isArray(dataElement[key])) {
|
|
522
|
+
dataElement[key].forEach((obj) => {
|
|
523
|
+
atts.push({
|
|
524
|
+
type: obj.type,
|
|
525
|
+
value: obj.value,
|
|
526
|
+
name: key,
|
|
527
|
+
datasetId: obj.datasetId,
|
|
528
|
+
metadata: getMetaData(obj)
|
|
529
|
+
});
|
|
530
|
+
});
|
|
531
|
+
} else {
|
|
532
|
+
atts.push({
|
|
533
|
+
type: dataElement[key].type,
|
|
534
|
+
value: dataElement[key].value,
|
|
535
|
+
name: key,
|
|
536
|
+
datasetId: dataElement[key].datasetId,
|
|
537
|
+
metadata: getMetaData(dataElement[key])
|
|
538
|
+
});
|
|
539
|
+
}
|
|
480
540
|
}
|
|
481
541
|
}
|
|
482
542
|
}
|
|
@@ -563,6 +623,21 @@ function updateErrorHandlingNgsiLD(error, req, res, next) {
|
|
|
563
623
|
});
|
|
564
624
|
}
|
|
565
625
|
|
|
626
|
+
/**
|
|
627
|
+
* Load unsupported NGSI-LD entity routes and return proper NGSI-LD not supported responses
|
|
628
|
+
*
|
|
629
|
+
* @param {Object} router Express request router object.
|
|
630
|
+
*/
|
|
631
|
+
function loadUnsupportedEndpointsNGSILD(router) {
|
|
632
|
+
const unsupportedEndpoint = function (req, res) {
|
|
633
|
+
return res.status(501).send(new errors.MethodNotSupported(req.method, req.path));
|
|
634
|
+
};
|
|
635
|
+
router.get('/ngsi-ld/v1/entities', unsupportedEndpoint);
|
|
636
|
+
router.post('/ngsi-ld/v1/entities', unsupportedEndpoint);
|
|
637
|
+
router.delete('/ngsi-ld/v1/entities/:entity', unsupportedEndpoint);
|
|
638
|
+
router.delete('/ngsi-ld/v1/entities/:entity/attrs/:attr', unsupportedEndpoint);
|
|
639
|
+
}
|
|
640
|
+
|
|
566
641
|
/**
|
|
567
642
|
* Load the routes related to context dispatching (NGSI10 calls).
|
|
568
643
|
*
|
|
@@ -601,6 +676,7 @@ function loadContextRoutesNGSILD(router) {
|
|
|
601
676
|
handleNotificationNgsiLD,
|
|
602
677
|
queryErrorHandlingNgsiLD
|
|
603
678
|
]);
|
|
679
|
+
loadUnsupportedEndpointsNGSILD(router);
|
|
604
680
|
}
|
|
605
681
|
|
|
606
682
|
exports.loadContextRoutes = loadContextRoutesNGSILD;
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "iotagent-node-lib",
|
|
3
3
|
"license": "AGPL-3.0-only",
|
|
4
4
|
"description": "IoT Agent library to interface with NGSI Context Broker",
|
|
5
|
-
"version": "2.
|
|
5
|
+
"version": "2.24.0",
|
|
6
6
|
"homepage": "https://github.com/telefonicaid/iotagent-node-lib",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"fiware",
|
|
@@ -194,4 +194,55 @@ describe('NGSI-v2 - In memory device registry', function () {
|
|
|
194
194
|
});
|
|
195
195
|
});
|
|
196
196
|
});
|
|
197
|
+
|
|
198
|
+
describe('When a the registry is queried for device in a particular name and type', function (){
|
|
199
|
+
beforeEach(function (done) {
|
|
200
|
+
contextBrokerMock = nock('http://192.168.1.1:1026')
|
|
201
|
+
.post('/v2/entities?options=upsert')
|
|
202
|
+
.times(10)
|
|
203
|
+
.reply(204);
|
|
204
|
+
|
|
205
|
+
const devices = [];
|
|
206
|
+
|
|
207
|
+
for (let i = 0; i < 10; i++)
|
|
208
|
+
{
|
|
209
|
+
devices.push(
|
|
210
|
+
{
|
|
211
|
+
id: 'id' + i,
|
|
212
|
+
name : 'name' + i,
|
|
213
|
+
type: 'Light' + i,
|
|
214
|
+
internalId: 'internal' + i,
|
|
215
|
+
service: 'smartgondor',
|
|
216
|
+
subservice: 'gardens',
|
|
217
|
+
active: [ {
|
|
218
|
+
id: 'attrId',
|
|
219
|
+
type: 'attrType' + i,
|
|
220
|
+
value: i
|
|
221
|
+
}]
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async.map(devices, iotAgentLib.register, function (error, results) {
|
|
226
|
+
done();
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
afterEach(function (done) {
|
|
231
|
+
iotAgentLib.clearRegistry(done);
|
|
232
|
+
});
|
|
233
|
+
it('should return the name and type of device', function (done)
|
|
234
|
+
{
|
|
235
|
+
iotAgentLib.getDeviceByNameAndType('name5', 'Light5','smartgondor', 'gardens' ,function(error, device)
|
|
236
|
+
{
|
|
237
|
+
should.not.exist(error);
|
|
238
|
+
should.exist(device);
|
|
239
|
+
should.exist(device.name);
|
|
240
|
+
should.exist(device.type);
|
|
241
|
+
device.name.should.equal('name5');
|
|
242
|
+
device.type.should.equal('Light5');
|
|
243
|
+
Object.keys(device).length.should.equal(11);
|
|
244
|
+
done();
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
});
|
|
197
248
|
});
|
|
@@ -448,4 +448,28 @@ describe('MongoDB Group Registry test', function () {
|
|
|
448
448
|
});
|
|
449
449
|
});
|
|
450
450
|
});
|
|
451
|
+
|
|
452
|
+
describe('When the device info request with name and type', function () {
|
|
453
|
+
beforeEach(function (done) {
|
|
454
|
+
async.series([async.apply(request, optionsCreation)], done);
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
afterEach(function (done) {
|
|
458
|
+
iotAgentLib.clearRegistry(done);
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
it('should return the name and type of device', function (done) {
|
|
462
|
+
request(optionsGet, function (error, response, body) {
|
|
463
|
+
should.exist(body);
|
|
464
|
+
should.exist(body.count);
|
|
465
|
+
body.count.should.equal(1);
|
|
466
|
+
should.exist(body.services);
|
|
467
|
+
should.exist(body.services.length);
|
|
468
|
+
body.services.length.should.equal(1);
|
|
469
|
+
should.exist(body.services[0].entity_type);
|
|
470
|
+
body.services[0].entity_type.should.equal('Light');
|
|
471
|
+
done();
|
|
472
|
+
});
|
|
473
|
+
});
|
|
474
|
+
});
|
|
451
475
|
});
|
|
@@ -507,4 +507,53 @@ describe('NGSI-v2 - MongoDB Device Registry', function () {
|
|
|
507
507
|
});
|
|
508
508
|
});
|
|
509
509
|
});
|
|
510
|
+
|
|
511
|
+
describe('When the device is queried with the name and type', function () {
|
|
512
|
+
beforeEach(function (done) {
|
|
513
|
+
contextBrokerMock = nock('http://192.168.1.1:1026')
|
|
514
|
+
.post('/v2/entities?options=upsert')
|
|
515
|
+
.times(10)
|
|
516
|
+
.matchHeader('fiware-service', 'smartgondor')
|
|
517
|
+
.matchHeader('fiware-servicepath', 'gardens')
|
|
518
|
+
.reply(204);
|
|
519
|
+
|
|
520
|
+
const devices = [];
|
|
521
|
+
|
|
522
|
+
for (let i = 0; i < 10; i++) {
|
|
523
|
+
devices.push({
|
|
524
|
+
id: 'id' + i,
|
|
525
|
+
type: 'Light' + i,
|
|
526
|
+
internalId: 'internal' + i,
|
|
527
|
+
service: 'smartgondor',
|
|
528
|
+
subservice: 'gardens',
|
|
529
|
+
active: [
|
|
530
|
+
{
|
|
531
|
+
id: 'attrId',
|
|
532
|
+
type: 'attrType' + i,
|
|
533
|
+
value: i
|
|
534
|
+
}
|
|
535
|
+
]
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
iotAgentLib.activate(iotAgentConfig, function (error) {
|
|
539
|
+
async.map(devices, iotAgentLib.register, function (error, results) {
|
|
540
|
+
done();
|
|
541
|
+
});
|
|
542
|
+
});
|
|
543
|
+
});
|
|
544
|
+
afterEach(function (done) {
|
|
545
|
+
iotAgentLib.clearRegistry(done);
|
|
546
|
+
});
|
|
547
|
+
it('should return the device with name and type', function (done) {
|
|
548
|
+
iotAgentLib.getDeviceByNameAndType('Light4:id4', 'Light4', 'smartgondor', 'gardens', function ( error, device ) {
|
|
549
|
+
should.not.exist(error);
|
|
550
|
+
should.exist(device);
|
|
551
|
+
should.exist(device.name);
|
|
552
|
+
should.exist(device.type);
|
|
553
|
+
device.name.should.equal('Light4:id4');
|
|
554
|
+
device.type.should.equal('Light4');
|
|
555
|
+
done();
|
|
556
|
+
});
|
|
557
|
+
});
|
|
558
|
+
});
|
|
510
559
|
});
|
package/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotificationWithDatasetId.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"data": [
|
|
3
|
+
{
|
|
4
|
+
"id": "TheFirstLight",
|
|
5
|
+
"location": [
|
|
6
|
+
{
|
|
7
|
+
"type": "GeoProperty",
|
|
8
|
+
"value": "12.4, -9.6",
|
|
9
|
+
"datasetId": "urn:ngsi-ld:Property:do-this"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"type": "GeoProperty",
|
|
13
|
+
"value": "6, 10",
|
|
14
|
+
"datasetId": "urn:ngsi-ld:Property:then-do-this"
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"type": "TheLightType"
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"subscriptionId": "51c0ac9ed714fb3b37d7d5a8"
|
|
21
|
+
}
|
package/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotificationWithMetadata.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"data": [
|
|
3
|
+
{
|
|
4
|
+
"id": "TheFirstLight",
|
|
5
|
+
"location": {
|
|
6
|
+
"type": "GeoProperty",
|
|
7
|
+
"value": "12.4, -9.6",
|
|
8
|
+
"qos": {
|
|
9
|
+
"type": "Property",
|
|
10
|
+
"value": 1
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"type": "TheLightType"
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"subscriptionId": "51c0ac9ed714fb3b37d7d5a8"
|
|
17
|
+
}
|