iotagent-node-lib 4.2.0 → 4.4.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/doc/api.md +178 -69
- package/docker/Mosquitto/Dockerfile +1 -1
- package/lib/commonConfig.js +18 -0
- package/lib/errors.js +48 -20
- package/lib/services/commands/commandRegistryMemory.js +12 -2
- package/lib/services/commands/commandRegistryMongoDB.js +26 -15
- package/lib/services/devices/deviceRegistryMemory.js +1 -1
- package/lib/services/devices/deviceRegistryMongoDB.js +7 -12
- package/lib/services/devices/deviceService.js +4 -3
- package/lib/services/devices/devices-NGSI-LD.js +3 -3
- package/lib/services/devices/devices-NGSI-v2.js +10 -4
- package/lib/services/groups/groupRegistryMemory.js +1 -1
- package/lib/services/groups/groupRegistryMongoDB.js +1 -1
- package/lib/services/groups/groupService.js +9 -3
- package/lib/services/ngsi/entities-NGSI-LD.js +15 -7
- package/lib/services/ngsi/entities-NGSI-v2.js +86 -54
- package/lib/services/ngsi/ngsiService.js +3 -3
- package/lib/services/ngsi/subscription-NGSI-LD.js +2 -0
- package/lib/services/ngsi/subscription-NGSI-v2.js +2 -0
- package/lib/services/northBound/contextServer-NGSI-LD.js +50 -55
- package/lib/services/northBound/contextServer-NGSI-v2.js +1 -1
- package/lib/services/northBound/deviceProvisioningServer.js +100 -26
- package/lib/services/northBound/restUtils.js +1 -1
- package/package.json +2 -2
- package/scripts/legacy_expression_tool/requirements.txt +1 -1
- package/test/functional/testCases.js +797 -66
- package/test/functional/testUtils.js +3 -1
- package/test/unit/ngsi-ld/ngsiService/active-devices-test.js +0 -1
- package/test/unit/ngsiv2/ngsiService/active-devices-test.js +8 -5
- package/test/unit/ngsiv2/plugins/multientity-plugin_test.js +34 -2
- package/test/unit/ngsiv2/provisioning/removeProvisionedDevice-test.js +37 -2
|
@@ -39,7 +39,7 @@ function findCommand(service, subservice, deviceId, name, callback) {
|
|
|
39
39
|
name
|
|
40
40
|
};
|
|
41
41
|
|
|
42
|
-
logger.debug(context, 'Looking for command [%s] for device [%s]', name, deviceId);
|
|
42
|
+
logger.debug(context, 'Looking for command [%s] for device [%s] with [%j]', name, deviceId, queryObj);
|
|
43
43
|
|
|
44
44
|
const query = Command.model.findOne(queryObj);
|
|
45
45
|
|
|
@@ -53,8 +53,14 @@ function findCommand(service, subservice, deviceId, name, callback) {
|
|
|
53
53
|
} else if (data) {
|
|
54
54
|
callback(null, data);
|
|
55
55
|
} else {
|
|
56
|
-
logger.debug(
|
|
57
|
-
|
|
56
|
+
logger.debug(
|
|
57
|
+
context,
|
|
58
|
+
'Command for DeviceID [%j] with name [%j] not found with [%j]',
|
|
59
|
+
deviceId,
|
|
60
|
+
name,
|
|
61
|
+
queryObj
|
|
62
|
+
);
|
|
63
|
+
callback(new errors.CommandNotFound(name, queryObj));
|
|
58
64
|
}
|
|
59
65
|
});
|
|
60
66
|
}
|
|
@@ -124,15 +130,15 @@ function listCommands(service, subservice, deviceId, callback) {
|
|
|
124
130
|
|
|
125
131
|
const query = Command.model.find(condition).sort();
|
|
126
132
|
|
|
127
|
-
async.series(
|
|
128
|
-
|
|
129
|
-
results
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
|
|
133
|
+
async.series(
|
|
134
|
+
[query.exec.bind(query), Command.model.countDocuments.bind(Command.model, condition)],
|
|
135
|
+
function (error, results) {
|
|
136
|
+
callback(error, {
|
|
137
|
+
count: results[1],
|
|
138
|
+
commands: results[0].map(toObjectFn)
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
);
|
|
136
142
|
}
|
|
137
143
|
|
|
138
144
|
function remove(service, subservice, deviceId, name, callback) {
|
|
@@ -159,9 +165,14 @@ function remove(service, subservice, deviceId, name, callback) {
|
|
|
159
165
|
|
|
160
166
|
callback(null, commandResult);
|
|
161
167
|
} else {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
168
|
+
const deviceInfo = {
|
|
169
|
+
service,
|
|
170
|
+
subservice,
|
|
171
|
+
deviceId,
|
|
172
|
+
name
|
|
173
|
+
};
|
|
174
|
+
logger.debug(context, 'Command [%s] not found for removal with %j', name, deviceInfo);
|
|
175
|
+
callback(new errors.CommandNotFound(name, deviceInfo));
|
|
165
176
|
}
|
|
166
177
|
});
|
|
167
178
|
}
|
|
@@ -54,7 +54,7 @@ function storeDevice(newDevice, callback) {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
if (registeredDevices[newDevice.service][newDevice.id]) {
|
|
57
|
-
callback(new errors.DuplicateDeviceId(newDevice
|
|
57
|
+
callback(new errors.DuplicateDeviceId(newDevice));
|
|
58
58
|
} else {
|
|
59
59
|
registeredDevices[newDevice.service][newDevice.id] = deepClone(newDevice);
|
|
60
60
|
registeredDevices[newDevice.service][newDevice.id].creationDate = Date.now();
|
|
@@ -56,6 +56,7 @@ const attributeList = [
|
|
|
56
56
|
'timestamp',
|
|
57
57
|
'explicitAttrs',
|
|
58
58
|
'ngsiVersion',
|
|
59
|
+
'subscriptions',
|
|
59
60
|
'payloadType'
|
|
60
61
|
];
|
|
61
62
|
|
|
@@ -101,7 +102,7 @@ function storeDevice(newDevice, callback) {
|
|
|
101
102
|
if (error.code === 11000) {
|
|
102
103
|
logger.debug(context, 'Tried to insert a device with duplicate ID in the database: %s', error);
|
|
103
104
|
|
|
104
|
-
callback(new errors.DuplicateDeviceId(newDevice
|
|
105
|
+
callback(new errors.DuplicateDeviceId(newDevice));
|
|
105
106
|
} else {
|
|
106
107
|
logger.debug(context, 'Error storing device information: %s', error);
|
|
107
108
|
|
|
@@ -205,7 +206,7 @@ function findOneInMongoDB(queryParams, id, callback) {
|
|
|
205
206
|
} else {
|
|
206
207
|
logger.debug(context, 'Device [%s] not found.', id);
|
|
207
208
|
|
|
208
|
-
callback(new errors.DeviceNotFound(id));
|
|
209
|
+
callback(new errors.DeviceNotFound(id, queryParams));
|
|
209
210
|
}
|
|
210
211
|
});
|
|
211
212
|
}
|
|
@@ -242,14 +243,7 @@ function getDeviceById(id, apikey, service, subservice, callback) {
|
|
|
242
243
|
function getDevice(id, apikey, service, subservice, callback) {
|
|
243
244
|
getDeviceById(id, apikey, service, subservice, function (error, data) {
|
|
244
245
|
if (error) {
|
|
245
|
-
|
|
246
|
-
getDeviceById(id, null, service, subservice, function (error, data) {
|
|
247
|
-
if (error) {
|
|
248
|
-
callback(error);
|
|
249
|
-
} else {
|
|
250
|
-
callback(null, data);
|
|
251
|
-
}
|
|
252
|
-
});
|
|
246
|
+
callback(error);
|
|
253
247
|
} else {
|
|
254
248
|
callback(null, data);
|
|
255
249
|
}
|
|
@@ -281,7 +275,7 @@ function getByNameAndType(name, type, service, servicepath, callback) {
|
|
|
281
275
|
} else {
|
|
282
276
|
logger.debug(context, 'Device [%s] not found.', name);
|
|
283
277
|
|
|
284
|
-
callback(new errors.DeviceNotFound(name));
|
|
278
|
+
callback(new errors.DeviceNotFound(name, optionsQuery));
|
|
285
279
|
}
|
|
286
280
|
});
|
|
287
281
|
}
|
|
@@ -318,6 +312,7 @@ function update(device, callback) {
|
|
|
318
312
|
data.explicitAttrs = device.explicitAttrs;
|
|
319
313
|
data.ngsiVersion = device.ngsiVersion;
|
|
320
314
|
data.timestamp = device.timestamp;
|
|
315
|
+
data.subscriptions = device.subscriptions;
|
|
321
316
|
data.payloadType = device.payloadType;
|
|
322
317
|
|
|
323
318
|
/* eslint-disable-next-line new-cap */
|
|
@@ -371,7 +366,7 @@ function getDevicesByAttribute(name, value, service, subservice, callback) {
|
|
|
371
366
|
} else {
|
|
372
367
|
logger.debug(context, 'Device [%s] not found.', name);
|
|
373
368
|
|
|
374
|
-
callback(new errors.DeviceNotFound(name));
|
|
369
|
+
callback(new errors.DeviceNotFound(name, filter));
|
|
375
370
|
}
|
|
376
371
|
});
|
|
377
372
|
}
|
|
@@ -244,7 +244,7 @@ function registerDevice(deviceObj, callback) {
|
|
|
244
244
|
/* eslint-disable-next-line no-unused-vars */
|
|
245
245
|
function (error, device) {
|
|
246
246
|
if (!error) {
|
|
247
|
-
innerCb(new errors.DuplicateDeviceId(deviceObj
|
|
247
|
+
innerCb(new errors.DuplicateDeviceId(deviceObj));
|
|
248
248
|
} else {
|
|
249
249
|
innerCb();
|
|
250
250
|
}
|
|
@@ -615,6 +615,7 @@ function findOrCreate(deviceId, apikey, group, callback) {
|
|
|
615
615
|
} else if (error.name === 'DEVICE_NOT_FOUND') {
|
|
616
616
|
const newDevice = {
|
|
617
617
|
id: deviceId,
|
|
618
|
+
apikey: apikey,
|
|
618
619
|
service: group.service,
|
|
619
620
|
subservice: group.subservice,
|
|
620
621
|
type: group.type
|
|
@@ -646,7 +647,7 @@ function findOrCreate(deviceId, apikey, group, callback) {
|
|
|
646
647
|
newDevice,
|
|
647
648
|
group
|
|
648
649
|
);
|
|
649
|
-
callback(new errors.DeviceNotFound(deviceId));
|
|
650
|
+
callback(new errors.DeviceNotFound(deviceId, newDevice));
|
|
650
651
|
}
|
|
651
652
|
} else {
|
|
652
653
|
callback(error);
|
|
@@ -671,7 +672,7 @@ function retrieveDevice(deviceId, apiKey, callback) {
|
|
|
671
672
|
} else {
|
|
672
673
|
logger.error(context, "Couldn't find device data for APIKey [%s] and DeviceId[%s]", deviceId, apiKey);
|
|
673
674
|
|
|
674
|
-
callback(new errors.DeviceNotFound(deviceId));
|
|
675
|
+
callback(new errors.DeviceNotFound(deviceId, { apikey: apiKey }));
|
|
675
676
|
}
|
|
676
677
|
});
|
|
677
678
|
} else {
|
|
@@ -93,7 +93,7 @@ function updateEntityHandlerNgsiLD(deviceData, updatedDevice, callback) {
|
|
|
93
93
|
body
|
|
94
94
|
);
|
|
95
95
|
|
|
96
|
-
const errorObj = new errors.EntityGenericError(deviceData.id, deviceData.type, body);
|
|
96
|
+
const errorObj = new errors.EntityGenericError(deviceData.id, deviceData.type, deviceData, body);
|
|
97
97
|
|
|
98
98
|
callback(errorObj);
|
|
99
99
|
}
|
|
@@ -184,7 +184,7 @@ function updateEntityNgsiLD(deviceData, updatedDevice, callback) {
|
|
|
184
184
|
*/
|
|
185
185
|
function updateRegisterDeviceNgsiLD(deviceObj, entityInfoUpdated, callback) {
|
|
186
186
|
if (!deviceObj.id || !deviceObj.type) {
|
|
187
|
-
callback(new errors.MissingAttributes('Id or device missing'));
|
|
187
|
+
callback(new errors.MissingAttributes('Id or device missing', deviceObj));
|
|
188
188
|
return;
|
|
189
189
|
}
|
|
190
190
|
|
|
@@ -216,7 +216,7 @@ function updateRegisterDeviceNgsiLD(deviceObj, entityInfoUpdated, callback) {
|
|
|
216
216
|
|
|
217
217
|
callback(null, oldDevice);
|
|
218
218
|
} else {
|
|
219
|
-
callback(new errors.DeviceNotFound(newDevice.id));
|
|
219
|
+
callback(new errors.DeviceNotFound(newDevice.id, newDevice));
|
|
220
220
|
}
|
|
221
221
|
}
|
|
222
222
|
|
|
@@ -89,7 +89,13 @@ function updateEntityHandlerNgsi2(deviceData, updatedDevice, callback) {
|
|
|
89
89
|
body
|
|
90
90
|
);
|
|
91
91
|
|
|
92
|
-
const errorObj = new errors.EntityGenericError(
|
|
92
|
+
const errorObj = new errors.EntityGenericError(
|
|
93
|
+
deviceData.id,
|
|
94
|
+
deviceData.type,
|
|
95
|
+
deviceData,
|
|
96
|
+
body,
|
|
97
|
+
response.statusCode
|
|
98
|
+
);
|
|
93
99
|
|
|
94
100
|
callback(errorObj);
|
|
95
101
|
}
|
|
@@ -204,7 +210,7 @@ function updateEntityNgsi2(deviceData, updatedDevice, callback) {
|
|
|
204
210
|
// Format any GeoJSON attrs properly
|
|
205
211
|
options.json[att] = NGSIv2.formatGeoAttrs(options.json[att]);
|
|
206
212
|
} catch (error) {
|
|
207
|
-
return callback(new errors.BadGeocoordinates(JSON.stringify(options.json)));
|
|
213
|
+
return callback(new errors.BadGeocoordinates(JSON.stringify(options.json), deviceData));
|
|
208
214
|
}
|
|
209
215
|
}
|
|
210
216
|
|
|
@@ -243,7 +249,7 @@ function updateEntityNgsi2(deviceData, updatedDevice, callback) {
|
|
|
243
249
|
*/
|
|
244
250
|
function updateRegisterDeviceNgsi2(deviceObj, entityInfoUpdated, callback) {
|
|
245
251
|
if (!deviceObj.id || !deviceObj.type) {
|
|
246
|
-
callback(new errors.MissingAttributes('Id or device missing'));
|
|
252
|
+
callback(new errors.MissingAttributes('Id or device missing', deviceObj));
|
|
247
253
|
return;
|
|
248
254
|
}
|
|
249
255
|
|
|
@@ -282,7 +288,7 @@ function updateRegisterDeviceNgsi2(deviceObj, entityInfoUpdated, callback) {
|
|
|
282
288
|
|
|
283
289
|
callback(null, oldDevice);
|
|
284
290
|
} else {
|
|
285
|
-
callback(new errors.DeviceNotFound(newDevice.id));
|
|
291
|
+
callback(new errors.DeviceNotFound(newDevice.id, newDevice));
|
|
286
292
|
}
|
|
287
293
|
}
|
|
288
294
|
|
|
@@ -50,7 +50,7 @@ function exists(group) {
|
|
|
50
50
|
|
|
51
51
|
function createGroup(group, callback) {
|
|
52
52
|
if (exists(group)) {
|
|
53
|
-
callback(new errors.DuplicateGroup(group
|
|
53
|
+
callback(new errors.DuplicateGroup(group));
|
|
54
54
|
} else {
|
|
55
55
|
const storeGroup = _.clone(group);
|
|
56
56
|
|
|
@@ -110,7 +110,7 @@ function createGroup(group, callback) {
|
|
|
110
110
|
group.apikey
|
|
111
111
|
);
|
|
112
112
|
|
|
113
|
-
callback(new errors.DuplicateGroup(group
|
|
113
|
+
callback(new errors.DuplicateGroup(group));
|
|
114
114
|
} else {
|
|
115
115
|
logger.debug(context, 'Error storing device group information: %s', error);
|
|
116
116
|
|
|
@@ -48,7 +48,7 @@ function validateGroup(group, callback) {
|
|
|
48
48
|
return function (error, foundGroup) {
|
|
49
49
|
logger.debug(context, 'generateDuplicateHander error %j and foundGroup %j', error, foundGroup);
|
|
50
50
|
if (!error || (foundGroup && foundGroup.count > 0)) {
|
|
51
|
-
innerCb(new errors.DuplicateGroup(group
|
|
51
|
+
innerCb(new errors.DuplicateGroup(group));
|
|
52
52
|
} else {
|
|
53
53
|
innerCb();
|
|
54
54
|
}
|
|
@@ -291,8 +291,14 @@ function getEffectiveApiKey(service, subservice, type, callback) {
|
|
|
291
291
|
logger.debug(context, 'Using default API Key: %s', config.getConfig().defaultKey);
|
|
292
292
|
callback(null, config.getConfig().defaultKey);
|
|
293
293
|
} else {
|
|
294
|
-
logger.error(
|
|
295
|
-
|
|
294
|
+
logger.error(
|
|
295
|
+
context,
|
|
296
|
+
'Could not find any APIKey information for device in service %s subservice %s and type %s',
|
|
297
|
+
service,
|
|
298
|
+
subservice,
|
|
299
|
+
type
|
|
300
|
+
);
|
|
301
|
+
callback(new errors.GroupNotFound(service, subservice, type));
|
|
296
302
|
}
|
|
297
303
|
}
|
|
298
304
|
|
|
@@ -381,9 +381,9 @@ function generateNGSILDOperationHandler(operationName, entityName, typeInformati
|
|
|
381
381
|
}
|
|
382
382
|
|
|
383
383
|
if (errorField !== undefined) {
|
|
384
|
-
callback(new errors.DeviceNotFound(entityName));
|
|
384
|
+
callback(new errors.DeviceNotFound(entityName, typeInformation));
|
|
385
385
|
} else {
|
|
386
|
-
callback(new errors.EntityGenericError(entityName, typeInformation.type, body));
|
|
386
|
+
callback(new errors.EntityGenericError(entityName, typeInformation.type, typeInformation, body));
|
|
387
387
|
}
|
|
388
388
|
} else {
|
|
389
389
|
logger.debug(context, 'Unknown error executing ' + operationName + ' operation');
|
|
@@ -391,7 +391,15 @@ function generateNGSILDOperationHandler(operationName, entityName, typeInformati
|
|
|
391
391
|
body = JSON.parse(body);
|
|
392
392
|
}
|
|
393
393
|
|
|
394
|
-
callback(
|
|
394
|
+
callback(
|
|
395
|
+
new errors.EntityGenericError(
|
|
396
|
+
entityName,
|
|
397
|
+
typeInformation.type,
|
|
398
|
+
typeInformation,
|
|
399
|
+
body,
|
|
400
|
+
response.statusCode
|
|
401
|
+
)
|
|
402
|
+
);
|
|
395
403
|
}
|
|
396
404
|
};
|
|
397
405
|
}
|
|
@@ -416,7 +424,7 @@ function sendQueryValueNgsiLD(entityName, attributes, typeInformation, token, ca
|
|
|
416
424
|
options.method = 'GET';
|
|
417
425
|
|
|
418
426
|
if (!typeInformation || !typeInformation.type) {
|
|
419
|
-
callback(new errors.TypeNotFound(null, entityName));
|
|
427
|
+
callback(new errors.TypeNotFound(null, entityName, typeInformation));
|
|
420
428
|
return;
|
|
421
429
|
}
|
|
422
430
|
|
|
@@ -529,7 +537,7 @@ function sendUpdateValueNgsiLD(entityName, attributes, typeInformation, token, c
|
|
|
529
537
|
}
|
|
530
538
|
|
|
531
539
|
if (!typeInformation || !typeInformation.type) {
|
|
532
|
-
callback(new errors.TypeNotFound(null, entityName));
|
|
540
|
+
callback(new errors.TypeNotFound(null, entityName, typeInformation));
|
|
533
541
|
return;
|
|
534
542
|
}
|
|
535
543
|
const idTypeSSSList = pluginUtils.getIdTypeServSubServiceFromDevice(typeInformation);
|
|
@@ -1015,7 +1023,7 @@ function sendUpdateValueNgsiLD(entityName, attributes, typeInformation, token, c
|
|
|
1015
1023
|
} else if (!utils.IsValidTimestampedNgsi2(payload[n])) {
|
|
1016
1024
|
// legacy check needed?
|
|
1017
1025
|
logger.error(context, 'Invalid timestamp:%s', JSON.stringify(payload[0]));
|
|
1018
|
-
callback(new errors.BadTimestamp(payload, entityName));
|
|
1026
|
+
callback(new errors.BadTimestamp(payload, entityName, typeInformation));
|
|
1019
1027
|
return;
|
|
1020
1028
|
}
|
|
1021
1029
|
}
|
|
@@ -1044,7 +1052,7 @@ function sendUpdateValueNgsiLD(entityName, attributes, typeInformation, token, c
|
|
|
1044
1052
|
options.json = [formatAsNGSILD(options.json)];
|
|
1045
1053
|
}
|
|
1046
1054
|
} catch (error) {
|
|
1047
|
-
return callback(new errors.BadGeocoordinates(JSON.stringify(payload)));
|
|
1055
|
+
return callback(new errors.BadGeocoordinates(JSON.stringify(payload), typeInformation));
|
|
1048
1056
|
}
|
|
1049
1057
|
|
|
1050
1058
|
if (typeInformation.active) {
|
|
@@ -31,7 +31,6 @@ const request = require('../../request-shim');
|
|
|
31
31
|
const alarms = require('../common/alarmManagement');
|
|
32
32
|
const errors = require('../../errors');
|
|
33
33
|
const pluginUtils = require('../../plugins/pluginUtils');
|
|
34
|
-
const config = require('../../commonConfig');
|
|
35
34
|
const constants = require('../../constants');
|
|
36
35
|
const jexlParser = require('../../plugins/jexlParser');
|
|
37
36
|
const expressionPlugin = require('../../plugins/expressionPlugin');
|
|
@@ -173,9 +172,9 @@ function generateNGSI2OperationHandler(operationName, entityName, typeInformatio
|
|
|
173
172
|
}
|
|
174
173
|
|
|
175
174
|
if (errorField !== undefined) {
|
|
176
|
-
callback(new errors.DeviceNotFound(entityName));
|
|
175
|
+
callback(new errors.DeviceNotFound(entityName, typeInformation));
|
|
177
176
|
} else {
|
|
178
|
-
callback(new errors.EntityGenericError(entityName, typeInformation.type, body));
|
|
177
|
+
callback(new errors.EntityGenericError(entityName, typeInformation.type, typeInformation, body));
|
|
179
178
|
}
|
|
180
179
|
} else {
|
|
181
180
|
logger.debug(context, 'Unknown error executing ' + operationName + ' operation');
|
|
@@ -183,7 +182,15 @@ function generateNGSI2OperationHandler(operationName, entityName, typeInformatio
|
|
|
183
182
|
body = JSON.parse(body);
|
|
184
183
|
}
|
|
185
184
|
|
|
186
|
-
callback(
|
|
185
|
+
callback(
|
|
186
|
+
new errors.EntityGenericError(
|
|
187
|
+
entityName,
|
|
188
|
+
typeInformation.type,
|
|
189
|
+
typeInformation,
|
|
190
|
+
body,
|
|
191
|
+
response.statusCode
|
|
192
|
+
)
|
|
193
|
+
);
|
|
187
194
|
}
|
|
188
195
|
};
|
|
189
196
|
}
|
|
@@ -225,7 +232,7 @@ function sendQueryValueNgsi2(entityName, attributes, typeInformation, token, cal
|
|
|
225
232
|
options.method = 'GET';
|
|
226
233
|
|
|
227
234
|
if (!typeInformation || !typeInformation.type) {
|
|
228
|
-
callback(new errors.TypeNotFound(null, entityName));
|
|
235
|
+
callback(new errors.TypeNotFound(null, entityName, typeInformation));
|
|
229
236
|
return;
|
|
230
237
|
}
|
|
231
238
|
|
|
@@ -270,7 +277,6 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call
|
|
|
270
277
|
let entities = {}; //{entityName:{entityType:[attrs]}} //SubGoal Populate entoties data striucture
|
|
271
278
|
let jexlctxt = {}; //will store the whole context (not just for JEXL)
|
|
272
279
|
let payload = {}; //will store the final payload
|
|
273
|
-
let timestamp = { type: constants.TIMESTAMP_TYPE_NGSI2 }; //timestamp scafold-attr for insertions.
|
|
274
280
|
let plainMeasures = null; //will contain measures POJO
|
|
275
281
|
let idTypeSSSList = pluginUtils.getIdTypeServSubServiceFromDevice(typeInformation);
|
|
276
282
|
|
|
@@ -279,7 +285,7 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call
|
|
|
279
285
|
|
|
280
286
|
//Check mandatory information: type
|
|
281
287
|
if (!typeInformation || !typeInformation.type) {
|
|
282
|
-
callback(new errors.TypeNotFound(null, entityName));
|
|
288
|
+
callback(new errors.TypeNotFound(null, entityName, typeInformation));
|
|
283
289
|
return;
|
|
284
290
|
}
|
|
285
291
|
//Rename all measures with matches with id and type to measure_id and measure_type
|
|
@@ -301,42 +307,16 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call
|
|
|
301
307
|
jexlctxt = reduceAttrToPlainObject(idTypeSSSList, jexlctxt);
|
|
302
308
|
|
|
303
309
|
//Managing timestamp (mustInsertTimeInstant flag to decide if we should insert Timestamp later on)
|
|
304
|
-
const mustInsertTimeInstant =
|
|
305
|
-
typeInformation.timestamp !== undefined
|
|
306
|
-
? typeInformation.timestamp
|
|
307
|
-
: config.getConfig().timestamp !== undefined
|
|
308
|
-
? config.getConfig().timestamp
|
|
309
|
-
: false;
|
|
310
|
-
|
|
311
|
-
if (mustInsertTimeInstant) {
|
|
312
|
-
//remove TimeInstant from measures
|
|
313
|
-
measures = measures.filter((item) => item.name !== constants.TIMESTAMP_ATTRIBUTE);
|
|
314
|
-
|
|
315
|
-
if (plainMeasures[constants.TIMESTAMP_ATTRIBUTE]) {
|
|
316
|
-
//if it comes from a measure
|
|
317
|
-
if (moment(plainMeasures[constants.TIMESTAMP_ATTRIBUTE], moment.ISO_8601, true).isValid()) {
|
|
318
|
-
timestamp.value = plainMeasures[constants.TIMESTAMP_ATTRIBUTE];
|
|
319
|
-
} else {
|
|
320
|
-
callback(new errors.BadTimestamp(plainMeasures[constants.TIMESTAMP_ATTRIBUTE], entityName));
|
|
321
|
-
}
|
|
322
|
-
} else if (!typeInformation.timezone) {
|
|
323
|
-
timestamp.value = new Date().toISOString();
|
|
324
|
-
jexlctxt[constants.TIMESTAMP_ATTRIBUTE] = timestamp.value;
|
|
325
|
-
} else {
|
|
326
|
-
timestamp.value = moment().tz(typeInformation.timezone).format('YYYY-MM-DD[T]HH:mm:ss.SSSZ');
|
|
327
|
-
jexlctxt[constants.TIMESTAMP_ATTRIBUTE] = timestamp.value;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
310
|
+
const mustInsertTimeInstant = typeInformation.timestamp !== undefined ? typeInformation.timestamp : false;
|
|
330
311
|
|
|
331
312
|
logger.debug(
|
|
332
313
|
context,
|
|
333
|
-
'sendUpdateValueNgsi2 called with: entityName=%s, measures=%j, typeInformation=%j, initial jexlContext=%j, timestamp=%j
|
|
314
|
+
'sendUpdateValueNgsi2 called with: entityName=%s, measures=%j, typeInformation=%j, initial jexlContext=%j, timestamp=%j',
|
|
334
315
|
entityName,
|
|
335
316
|
plainMeasures,
|
|
336
317
|
typeInformation,
|
|
337
318
|
jexlctxt,
|
|
338
|
-
mustInsertTimeInstant
|
|
339
|
-
timestamp.value
|
|
319
|
+
mustInsertTimeInstant
|
|
340
320
|
);
|
|
341
321
|
|
|
342
322
|
//Now we can calculate the EntityName of primary entity
|
|
@@ -471,14 +451,6 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call
|
|
|
471
451
|
currentAttr.hitted = hitted;
|
|
472
452
|
currentAttr.value = valueExpression;
|
|
473
453
|
|
|
474
|
-
//add TimeInstant to attr metadata
|
|
475
|
-
if (mustInsertTimeInstant) {
|
|
476
|
-
if (!currentAttr.metadata) {
|
|
477
|
-
currentAttr.metadata = {};
|
|
478
|
-
}
|
|
479
|
-
currentAttr.metadata[constants.TIMESTAMP_ATTRIBUTE] = timestamp;
|
|
480
|
-
}
|
|
481
|
-
|
|
482
454
|
//store de New Attributte in entity data structure
|
|
483
455
|
if (hitted === true) {
|
|
484
456
|
if (entities[attrEntityName] === undefined) {
|
|
@@ -493,6 +465,38 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call
|
|
|
493
465
|
|
|
494
466
|
//RE-Populate de JEXLcontext (except for null or NaN we preffer undefined)
|
|
495
467
|
jexlctxt[currentAttr.name] = valueExpression;
|
|
468
|
+
|
|
469
|
+
// Expand metadata value expression
|
|
470
|
+
if (currentAttr.metadata) {
|
|
471
|
+
for (var metaKey in currentAttr.metadata) {
|
|
472
|
+
if (currentAttr.metadata[metaKey].expression && metaKey !== constants.TIMESTAMP_ATTRIBUTE) {
|
|
473
|
+
let newAttrMeta = {};
|
|
474
|
+
if (currentAttr.metadata[metaKey].type) {
|
|
475
|
+
newAttrMeta['type'] = currentAttr.metadata[metaKey].type;
|
|
476
|
+
}
|
|
477
|
+
let metaValueExpression;
|
|
478
|
+
try {
|
|
479
|
+
metaValueExpression = jexlParser.applyExpression(
|
|
480
|
+
currentAttr.metadata[metaKey].expression,
|
|
481
|
+
jexlctxt,
|
|
482
|
+
typeInformation
|
|
483
|
+
);
|
|
484
|
+
//we fallback to null if anything unexpecte happend
|
|
485
|
+
if (
|
|
486
|
+
metaValueExpression === null ||
|
|
487
|
+
metaValueExpression === undefined ||
|
|
488
|
+
Number.isNaN(metaValueExpression)
|
|
489
|
+
) {
|
|
490
|
+
metaValueExpression = null;
|
|
491
|
+
}
|
|
492
|
+
} catch (e) {
|
|
493
|
+
metaValueExpression = null;
|
|
494
|
+
}
|
|
495
|
+
newAttrMeta['value'] = metaValueExpression;
|
|
496
|
+
currentAttr.metadata[metaKey] = newAttrMeta;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
496
500
|
}
|
|
497
501
|
|
|
498
502
|
//now we can compute explicit (Bool or Array) with the complete JexlContext
|
|
@@ -519,16 +523,6 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call
|
|
|
519
523
|
|
|
520
524
|
//more mesures may be added to the attribute list (unnhandled/left mesaures) l
|
|
521
525
|
if (explicit === false && Object.keys(measures).length > 0) {
|
|
522
|
-
//add Timestamp to measures if needed
|
|
523
|
-
if (mustInsertTimeInstant) {
|
|
524
|
-
for (let currentMeasure of measures) {
|
|
525
|
-
if (!currentMeasure.metadata) {
|
|
526
|
-
currentMeasure.metadata = {};
|
|
527
|
-
}
|
|
528
|
-
currentMeasure.metadata[constants.TIMESTAMP_ATTRIBUTE] = timestamp;
|
|
529
|
-
}
|
|
530
|
-
//If just measures in the principal entity we missed the Timestamp.
|
|
531
|
-
}
|
|
532
526
|
entities[entityName][typeInformation.type] = entities[entityName][typeInformation.type].concat(measures);
|
|
533
527
|
}
|
|
534
528
|
|
|
@@ -539,11 +533,40 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call
|
|
|
539
533
|
payload.actionType = 'append';
|
|
540
534
|
|
|
541
535
|
payload.entities = [];
|
|
536
|
+
const currentIsoDate = new Date().toISOString();
|
|
537
|
+
const currentMoment = moment(currentIsoDate);
|
|
542
538
|
for (let ename in entities) {
|
|
543
539
|
for (let etype in entities[ename]) {
|
|
544
540
|
let e = {};
|
|
545
541
|
e.id = String(ename);
|
|
546
542
|
e.type = String(etype);
|
|
543
|
+
let timestamp = { type: constants.TIMESTAMP_TYPE_NGSI2 }; //timestamp scafold-attr for insertions.
|
|
544
|
+
let timestampAttrs = null;
|
|
545
|
+
if (mustInsertTimeInstant) {
|
|
546
|
+
// get timestamp for current entity
|
|
547
|
+
|
|
548
|
+
timestampAttrs = entities[ename][etype].filter((item) => item.name === constants.TIMESTAMP_ATTRIBUTE);
|
|
549
|
+
if (timestampAttrs && timestampAttrs.length > 0) {
|
|
550
|
+
timestamp.value = timestampAttrs[0]['value'];
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
if (timestamp.value) {
|
|
554
|
+
if (!moment(timestamp.value, moment.ISO_8601, true).isValid()) {
|
|
555
|
+
callback(new errors.BadTimestamp(timestamp.value, entityName, typeInformation));
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
} else {
|
|
559
|
+
if (!typeInformation.timezone) {
|
|
560
|
+
timestamp.value = currentIsoDate;
|
|
561
|
+
jexlctxt[constants.TIMESTAMP_ATTRIBUTE] = timestamp.value;
|
|
562
|
+
} else {
|
|
563
|
+
timestamp.value = currentMoment
|
|
564
|
+
.tz(typeInformation.timezone)
|
|
565
|
+
.format('YYYY-MM-DD[T]HH:mm:ss.SSSZ');
|
|
566
|
+
jexlctxt[constants.TIMESTAMP_ATTRIBUTE] = timestamp.value;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
547
570
|
//extract attributes
|
|
548
571
|
let isEmpty = true;
|
|
549
572
|
for (let attr of entities[ename][etype]) {
|
|
@@ -560,6 +583,15 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call
|
|
|
560
583
|
))))
|
|
561
584
|
) {
|
|
562
585
|
isEmpty = false;
|
|
586
|
+
if (mustInsertTimeInstant) {
|
|
587
|
+
// Add TimeInstant to all attribute metadata of all entities
|
|
588
|
+
if (attr.name !== constants.TIMESTAMP_ATTRIBUTE) {
|
|
589
|
+
if (!attr.metadata) {
|
|
590
|
+
attr.metadata = {};
|
|
591
|
+
}
|
|
592
|
+
attr.metadata[constants.TIMESTAMP_ATTRIBUTE] = timestamp;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
563
595
|
e[attr.name] = { type: attr.type, value: attr.value, metadata: attr.metadata };
|
|
564
596
|
}
|
|
565
597
|
}
|
|
@@ -144,9 +144,9 @@ function executeWithDeviceInformation(operationFunction) {
|
|
|
144
144
|
// For preregistered devices, augment the existing deviceInformation with selected attributes.
|
|
145
145
|
if (!callback) {
|
|
146
146
|
callback = deviceInformation;
|
|
147
|
-
typeInformation = deviceGroup || configDeviceInfo;
|
|
147
|
+
typeInformation = deviceGroup || { ...config.getConfigForTypeInformation(), ...configDeviceInfo };
|
|
148
148
|
} else {
|
|
149
|
-
typeInformation = deviceInformation;
|
|
149
|
+
typeInformation = { ...config.getConfigForTypeInformation(), ...deviceInformation };
|
|
150
150
|
attributeList.forEach((key) => {
|
|
151
151
|
typeInformation[key] =
|
|
152
152
|
typeInformation[key] || (deviceGroup || {})[key] || (configDeviceInfo || {})[key];
|
|
@@ -236,7 +236,7 @@ function setCommandResult(
|
|
|
236
236
|
if (commandInfo.length === 1) {
|
|
237
237
|
exports.update(entityName, typeInformation.type, apikey, attributes, typeInformation, callback);
|
|
238
238
|
} else {
|
|
239
|
-
callback(new errors.CommandNotFound(commandName));
|
|
239
|
+
callback(new errors.CommandNotFound(commandName, typeInformation));
|
|
240
240
|
}
|
|
241
241
|
});
|
|
242
242
|
}
|
|
@@ -62,6 +62,7 @@ function createSubscriptionHandlerNgsiLD(device, triggers, store, callback) {
|
|
|
62
62
|
new errors.EntityGenericError(
|
|
63
63
|
device.name,
|
|
64
64
|
device.type,
|
|
65
|
+
device,
|
|
65
66
|
{
|
|
66
67
|
details: body
|
|
67
68
|
},
|
|
@@ -183,6 +184,7 @@ function createUnsubscribeHandlerNgsiLD(device, id, callback) {
|
|
|
183
184
|
new errors.EntityGenericError(
|
|
184
185
|
device.name,
|
|
185
186
|
device.type,
|
|
187
|
+
device,
|
|
186
188
|
{
|
|
187
189
|
details: body
|
|
188
190
|
},
|
|
@@ -64,6 +64,7 @@ function createSubscriptionHandlerNgsi2(device, triggers, store, callback) {
|
|
|
64
64
|
new errors.EntityGenericError(
|
|
65
65
|
device.name,
|
|
66
66
|
device.type,
|
|
67
|
+
device,
|
|
67
68
|
{
|
|
68
69
|
details: body
|
|
69
70
|
},
|
|
@@ -183,6 +184,7 @@ function createUnsubscribeHandlerNgsi2(device, id, callback) {
|
|
|
183
184
|
new errors.EntityGenericError(
|
|
184
185
|
device.name,
|
|
185
186
|
device.type,
|
|
187
|
+
device,
|
|
186
188
|
{
|
|
187
189
|
details: body
|
|
188
190
|
},
|