iotagent-node-lib 4.3.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 +74 -20
- package/docker/Mosquitto/Dockerfile +1 -1
- package/lib/commonConfig.js +18 -0
- package/lib/errors.js +4 -1
- package/lib/services/devices/deviceRegistryMongoDB.js +1 -8
- package/lib/services/devices/deviceService.js +1 -0
- package/lib/services/ngsi/entities-NGSI-v2.js +73 -49
- package/lib/services/ngsi/ngsiService.js +2 -2
- package/package.json +2 -2
- package/scripts/legacy_expression_tool/requirements.txt +1 -1
- package/test/functional/testCases.js +732 -1
- package/test/unit/ngsiv2/plugins/multientity-plugin_test.js +34 -2
package/doc/api.md
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
- [IoT Agent information model](#iot-agent-information-model)
|
|
9
9
|
- [Config groups](#config-groups)
|
|
10
10
|
- [Devices](#devices)
|
|
11
|
+
- [Uniqueness of groups and devices](#uniqueness-of-groups-and-devices)
|
|
11
12
|
- [Special measures and attributes names](#special-measures-and-attributes-names)
|
|
12
13
|
- [Entity attributes](#entity-attributes)
|
|
13
14
|
- [Multientity support)](#multientity-support)
|
|
@@ -133,9 +134,9 @@ parameters. The specific parameters that can be configured for a given config gr
|
|
|
133
134
|
### Devices
|
|
134
135
|
|
|
135
136
|
A device contains the information that connects a physical device to a particular entity in the Context Broker. Devices
|
|
136
|
-
are identified by a `device_id`, and they are associated to an existing config group based in `
|
|
137
|
-
|
|
138
|
-
|
|
137
|
+
are identified by a `device_id`, and they are associated to an existing config group based in `apikey` matching. For
|
|
138
|
+
instance, let's consider a situation in which a config group has been provisioned with `type=X`/`apikey=111` and no
|
|
139
|
+
other config group has been provisioned.
|
|
139
140
|
|
|
140
141
|
The IoT Agents offer a provisioning API where devices can be preregistered, so all the information about service and
|
|
141
142
|
subservice mapping, security information and attribute configuration can be specified in a per device way instead of
|
|
@@ -143,7 +144,7 @@ relaying on the config group configuration. The specific parameters that can be
|
|
|
143
144
|
described in the [Device datamodel](#device-datamodel) section.
|
|
144
145
|
|
|
145
146
|
If devices are not pre-registered, they will be automatically created when a measure arrives to the IoT Agent - this
|
|
146
|
-
process is known as autoprovisioning. The IoT Agent will create an empty device with the group `
|
|
147
|
+
process is known as autoprovisioning. The IoT Agent will create an empty device with the group `apikey` and `type` - the
|
|
147
148
|
associated document created in database doesn't include config group parameters (in particular, `timestamp`,
|
|
148
149
|
`explicitAttrs`, `active` or `attributes`, `static` and `lazy` attributes and commands). The IoT Agent will also create
|
|
149
150
|
the entity in the Context Broker if it does not exist yet.
|
|
@@ -152,6 +153,13 @@ This behavior allows that autoprovisioned parameters can freely established modi
|
|
|
152
153
|
creation using the provisioning API. However, note that if a device (autoprovisioned or not) doesn't have these
|
|
153
154
|
parameters defined at device level in database, the parameters are inherit from config group parameters.
|
|
154
155
|
|
|
156
|
+
### Uniqueness of groups and devices
|
|
157
|
+
|
|
158
|
+
Group service uniqueness is defined by the combination of: service, subservice and apikey
|
|
159
|
+
|
|
160
|
+
Device uniqueness is defined by the combination of: service, subservice, device_id and apikey. Note that several devices
|
|
161
|
+
with the same device_id are allowed in the same service and subservice as long as their apikeys are different.
|
|
162
|
+
|
|
155
163
|
## Special measures and attributes names
|
|
156
164
|
|
|
157
165
|
In case of arriving measures with name `id` or `type`, they are automatically transformed to `measure_id` and
|
|
@@ -306,6 +314,43 @@ e.g.:
|
|
|
306
314
|
}
|
|
307
315
|
```
|
|
308
316
|
|
|
317
|
+
Metadata could also has `expression` like attributes in order to expand it:
|
|
318
|
+
|
|
319
|
+
e.g.:
|
|
320
|
+
|
|
321
|
+
```json
|
|
322
|
+
{
|
|
323
|
+
"entity_type": "Lamp",
|
|
324
|
+
"resource": "/iot/d",
|
|
325
|
+
"protocol": "PDI-IoTA-UltraLight",
|
|
326
|
+
..etc
|
|
327
|
+
"commands": [
|
|
328
|
+
{"name": "on","type": "command"},
|
|
329
|
+
{"name": "off","type": "command"}
|
|
330
|
+
],
|
|
331
|
+
"attributes": [
|
|
332
|
+
{"object_id": "s", "name": "state", "type":"Text"},
|
|
333
|
+
{"object_id": "l", "name": "luminosity", "type":"Integer",
|
|
334
|
+
"metadata":{
|
|
335
|
+
"unitCode":{"type": "Text", "value" :"CAL"}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
],
|
|
339
|
+
"static_attributes": [
|
|
340
|
+
{"name": "category", "type":"Text", "value": ["actuator","sensor"]},
|
|
341
|
+
{"name": "controlledProperty", "type": "Text", "value": ["light"],
|
|
342
|
+
"metadata":{
|
|
343
|
+
"includes":{"type": "Text",
|
|
344
|
+
"value" :["state", "luminosity"],
|
|
345
|
+
"expression": "level / 100"
|
|
346
|
+
},
|
|
347
|
+
"alias":{"type": "Text", "value" :"lamp"}
|
|
348
|
+
}
|
|
349
|
+
},
|
|
350
|
+
]
|
|
351
|
+
}
|
|
352
|
+
```
|
|
353
|
+
|
|
309
354
|
### NGSI-LD data and metadata considerations
|
|
310
355
|
|
|
311
356
|
When provisioning devices for an NGSI-LD Context Broker, `type` values should typically correspond to one of the
|
|
@@ -517,8 +562,8 @@ expression. In all cases the following data is available to all expressions:
|
|
|
517
562
|
- `subservice`: device subservice
|
|
518
563
|
- `staticAttributes`: static attributes defined in the device or config group
|
|
519
564
|
|
|
520
|
-
Additionally, for attribute expressions (`expression`, `entity_name`)
|
|
521
|
-
**context** used to evaluate them.
|
|
565
|
+
Additionally, for attribute expressions (`expression`, `entity_name`), `entityNameExp` and metadata expressions
|
|
566
|
+
(`expression`) measures are available in the **context** used to evaluate them.
|
|
522
567
|
|
|
523
568
|
### Examples of JEXL expressions
|
|
524
569
|
|
|
@@ -964,32 +1009,41 @@ Will now generate the following NGSI v2 payload:
|
|
|
964
1009
|
|
|
965
1010
|
## Timestamp Processing
|
|
966
1011
|
|
|
967
|
-
|
|
968
|
-
adds a `TimeInstant` attribute as metadata for every other attribute in the same request. With NGSI-LD, the Standard
|
|
969
|
-
`observedAt` property-of-a-property is used instead.
|
|
1012
|
+
Timestamp processing done by IOTA is as follows:
|
|
970
1013
|
|
|
971
|
-
|
|
972
|
-
|
|
1014
|
+
- An attribute `TimeInstant` is added to updated entities
|
|
1015
|
+
- In the case of NGSI-v2, a `TimeInstant` metadata is added in each updated attribute. With NGSI-LD, the Standard
|
|
1016
|
+
`observedAt` property-of-a-property is used instead.
|
|
973
1017
|
|
|
974
1018
|
Depending on the `timestamp` configuration and if the measure contains a value named `TimeInstant` with a correct value,
|
|
975
1019
|
the IoTA behaviour is described in the following table:
|
|
976
1020
|
|
|
977
|
-
| `timestamp` value | measure contains `TimeInstant` | Behaviour |
|
|
978
|
-
|
|
|
979
|
-
| true
|
|
980
|
-
| true
|
|
981
|
-
| false
|
|
982
|
-
| false
|
|
983
|
-
| Not defined
|
|
984
|
-
| Not defined
|
|
1021
|
+
| `timestamp` conf value | measure contains `TimeInstant` | Behaviour |
|
|
1022
|
+
| ---------------------- | ------------------------------ | ------------------------------------------------------ |
|
|
1023
|
+
| true | Yes | TimeInstant and metadata updated with measure value |
|
|
1024
|
+
| true | No | TimeInstant and metadata updated with server timestamp |
|
|
1025
|
+
| false | Yes | TimeInstant and metadata updated with measure value |
|
|
1026
|
+
| false | No | TimeInstant and metadata updated with server timestamp |
|
|
1027
|
+
| Not defined | Yes | TimeInstant and metadata updated with measure value |
|
|
1028
|
+
| Not defined | No | TimeInstant and metadata updated with server timestamp |
|
|
985
1029
|
|
|
986
|
-
The `timestamp` value used is:
|
|
1030
|
+
The `timestamp` conf value used is:
|
|
987
1031
|
|
|
988
1032
|
- The one defined at device level
|
|
989
1033
|
- The one defined at group level (if not defined at device level)
|
|
990
1034
|
- The one defined at [IoTA configuration level](admin.md#timestamp) / `IOTA_TIMESTAMP` env var (if not defined at
|
|
991
1035
|
group level or device level)
|
|
992
1036
|
|
|
1037
|
+
Some additional considerations to take into account:
|
|
1038
|
+
|
|
1039
|
+
- If there is an attribute which maps a measure to `TimeInstant` attribute (after
|
|
1040
|
+
[expression evaluation](#expression-language-support) if any is defined), then that value will be used as
|
|
1041
|
+
`TimeInstant, overwriting the above rules specified in "Behaviour" column. Note that an expression in the could be
|
|
1042
|
+
used in that mapping.
|
|
1043
|
+
- If the resulting `TimeInstant` not follows [ISO_8601](https://en.wikipedia.org/wiki/ISO_8601) (either from a direct
|
|
1044
|
+
measure of after a mapping, as described in the previous bullet) then it is refused (so a failover to server
|
|
1045
|
+
timestamp will take place).
|
|
1046
|
+
|
|
993
1047
|
## Overriding global Context Broker host
|
|
994
1048
|
|
|
995
1049
|
**cbHost**: Context Broker host URL. This option can be used to override the global CB configuration for specific types
|
package/lib/commonConfig.js
CHANGED
|
@@ -485,6 +485,23 @@ function getConfig() {
|
|
|
485
485
|
return config;
|
|
486
486
|
}
|
|
487
487
|
|
|
488
|
+
function getConfigForTypeInformation() {
|
|
489
|
+
// Just return relevant configuration flags
|
|
490
|
+
// avoid to include server, authentication, mongodb, orion and iotamanger info
|
|
491
|
+
let conf = {
|
|
492
|
+
timestamp: config.timestamp,
|
|
493
|
+
defaultResource: config.defaultResource,
|
|
494
|
+
explicitAttrs: config.explicitAttrs,
|
|
495
|
+
pollingExpiration: config.pollingExpiration,
|
|
496
|
+
pollingDaemonFrequency: config.pollingDaemonFrequency,
|
|
497
|
+
multiCore: config.multiCore,
|
|
498
|
+
relaxTemplateValidation: config.relaxTemplateValidation,
|
|
499
|
+
defaultEntityNameConjunction: config.defaultEntityNameConjunction,
|
|
500
|
+
defaultType: config.defaultType
|
|
501
|
+
};
|
|
502
|
+
return conf;
|
|
503
|
+
}
|
|
504
|
+
|
|
488
505
|
function setRegistry(newRegistry) {
|
|
489
506
|
registry = newRegistry;
|
|
490
507
|
}
|
|
@@ -541,6 +558,7 @@ function getSecurityService() {
|
|
|
541
558
|
|
|
542
559
|
exports.setConfig = setConfig;
|
|
543
560
|
exports.getConfig = getConfig;
|
|
561
|
+
exports.getConfigForTypeInformation = getConfigForTypeInformation;
|
|
544
562
|
exports.setRegistry = setRegistry;
|
|
545
563
|
exports.getRegistry = getRegistry;
|
|
546
564
|
exports.setGroupRegistry = setGroupRegistry;
|
package/lib/errors.js
CHANGED
|
@@ -107,7 +107,10 @@ class DuplicateDeviceId {
|
|
|
107
107
|
constructor(device) {
|
|
108
108
|
this.name = 'DUPLICATE_DEVICE_ID';
|
|
109
109
|
this.message =
|
|
110
|
-
'A device with the same
|
|
110
|
+
'A device with the same info (DeviceId, ApiKey, Service, Subservice) was found:' +
|
|
111
|
+
device.id +
|
|
112
|
+
' and ' +
|
|
113
|
+
JSON.stringify(device);
|
|
111
114
|
this.code = 409;
|
|
112
115
|
}
|
|
113
116
|
}
|
|
@@ -243,14 +243,7 @@ function getDeviceById(id, apikey, service, subservice, callback) {
|
|
|
243
243
|
function getDevice(id, apikey, service, subservice, callback) {
|
|
244
244
|
getDeviceById(id, apikey, service, subservice, function (error, data) {
|
|
245
245
|
if (error) {
|
|
246
|
-
|
|
247
|
-
getDeviceById(id, null, service, subservice, function (error, data) {
|
|
248
|
-
if (error) {
|
|
249
|
-
callback(error);
|
|
250
|
-
} else {
|
|
251
|
-
callback(null, data);
|
|
252
|
-
}
|
|
253
|
-
});
|
|
246
|
+
callback(error);
|
|
254
247
|
} else {
|
|
255
248
|
callback(null, data);
|
|
256
249
|
}
|
|
@@ -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
|
|
@@ -277,7 +277,6 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call
|
|
|
277
277
|
let entities = {}; //{entityName:{entityType:[attrs]}} //SubGoal Populate entoties data striucture
|
|
278
278
|
let jexlctxt = {}; //will store the whole context (not just for JEXL)
|
|
279
279
|
let payload = {}; //will store the final payload
|
|
280
|
-
let timestamp = { type: constants.TIMESTAMP_TYPE_NGSI2 }; //timestamp scafold-attr for insertions.
|
|
281
280
|
let plainMeasures = null; //will contain measures POJO
|
|
282
281
|
let idTypeSSSList = pluginUtils.getIdTypeServSubServiceFromDevice(typeInformation);
|
|
283
282
|
|
|
@@ -308,43 +307,16 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call
|
|
|
308
307
|
jexlctxt = reduceAttrToPlainObject(idTypeSSSList, jexlctxt);
|
|
309
308
|
|
|
310
309
|
//Managing timestamp (mustInsertTimeInstant flag to decide if we should insert Timestamp later on)
|
|
311
|
-
const mustInsertTimeInstant =
|
|
312
|
-
typeInformation.timestamp !== undefined
|
|
313
|
-
? typeInformation.timestamp
|
|
314
|
-
: false;
|
|
315
|
-
|
|
316
|
-
if (mustInsertTimeInstant) {
|
|
317
|
-
//remove TimeInstant from measures
|
|
318
|
-
measures = measures.filter((item) => item.name !== constants.TIMESTAMP_ATTRIBUTE);
|
|
319
|
-
|
|
320
|
-
if (plainMeasures[constants.TIMESTAMP_ATTRIBUTE]) {
|
|
321
|
-
//if it comes from a measure
|
|
322
|
-
if (moment(plainMeasures[constants.TIMESTAMP_ATTRIBUTE], moment.ISO_8601, true).isValid()) {
|
|
323
|
-
timestamp.value = plainMeasures[constants.TIMESTAMP_ATTRIBUTE];
|
|
324
|
-
} else {
|
|
325
|
-
callback(
|
|
326
|
-
new errors.BadTimestamp(plainMeasures[constants.TIMESTAMP_ATTRIBUTE], entityName, typeInformation)
|
|
327
|
-
);
|
|
328
|
-
return;
|
|
329
|
-
}
|
|
330
|
-
} else if (!typeInformation.timezone) {
|
|
331
|
-
timestamp.value = new Date().toISOString();
|
|
332
|
-
jexlctxt[constants.TIMESTAMP_ATTRIBUTE] = timestamp.value;
|
|
333
|
-
} else {
|
|
334
|
-
timestamp.value = moment().tz(typeInformation.timezone).format('YYYY-MM-DD[T]HH:mm:ss.SSSZ');
|
|
335
|
-
jexlctxt[constants.TIMESTAMP_ATTRIBUTE] = timestamp.value;
|
|
336
|
-
}
|
|
337
|
-
}
|
|
310
|
+
const mustInsertTimeInstant = typeInformation.timestamp !== undefined ? typeInformation.timestamp : false;
|
|
338
311
|
|
|
339
312
|
logger.debug(
|
|
340
313
|
context,
|
|
341
|
-
'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',
|
|
342
315
|
entityName,
|
|
343
316
|
plainMeasures,
|
|
344
317
|
typeInformation,
|
|
345
318
|
jexlctxt,
|
|
346
|
-
mustInsertTimeInstant
|
|
347
|
-
timestamp.value
|
|
319
|
+
mustInsertTimeInstant
|
|
348
320
|
);
|
|
349
321
|
|
|
350
322
|
//Now we can calculate the EntityName of primary entity
|
|
@@ -479,14 +451,6 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call
|
|
|
479
451
|
currentAttr.hitted = hitted;
|
|
480
452
|
currentAttr.value = valueExpression;
|
|
481
453
|
|
|
482
|
-
//add TimeInstant to attr metadata
|
|
483
|
-
if (mustInsertTimeInstant) {
|
|
484
|
-
if (!currentAttr.metadata) {
|
|
485
|
-
currentAttr.metadata = {};
|
|
486
|
-
}
|
|
487
|
-
currentAttr.metadata[constants.TIMESTAMP_ATTRIBUTE] = timestamp;
|
|
488
|
-
}
|
|
489
|
-
|
|
490
454
|
//store de New Attributte in entity data structure
|
|
491
455
|
if (hitted === true) {
|
|
492
456
|
if (entities[attrEntityName] === undefined) {
|
|
@@ -501,6 +465,38 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call
|
|
|
501
465
|
|
|
502
466
|
//RE-Populate de JEXLcontext (except for null or NaN we preffer undefined)
|
|
503
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
|
+
}
|
|
504
500
|
}
|
|
505
501
|
|
|
506
502
|
//now we can compute explicit (Bool or Array) with the complete JexlContext
|
|
@@ -527,16 +523,6 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call
|
|
|
527
523
|
|
|
528
524
|
//more mesures may be added to the attribute list (unnhandled/left mesaures) l
|
|
529
525
|
if (explicit === false && Object.keys(measures).length > 0) {
|
|
530
|
-
//add Timestamp to measures if needed
|
|
531
|
-
if (mustInsertTimeInstant) {
|
|
532
|
-
for (let currentMeasure of measures) {
|
|
533
|
-
if (!currentMeasure.metadata) {
|
|
534
|
-
currentMeasure.metadata = {};
|
|
535
|
-
}
|
|
536
|
-
currentMeasure.metadata[constants.TIMESTAMP_ATTRIBUTE] = timestamp;
|
|
537
|
-
}
|
|
538
|
-
//If just measures in the principal entity we missed the Timestamp.
|
|
539
|
-
}
|
|
540
526
|
entities[entityName][typeInformation.type] = entities[entityName][typeInformation.type].concat(measures);
|
|
541
527
|
}
|
|
542
528
|
|
|
@@ -547,11 +533,40 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call
|
|
|
547
533
|
payload.actionType = 'append';
|
|
548
534
|
|
|
549
535
|
payload.entities = [];
|
|
536
|
+
const currentIsoDate = new Date().toISOString();
|
|
537
|
+
const currentMoment = moment(currentIsoDate);
|
|
550
538
|
for (let ename in entities) {
|
|
551
539
|
for (let etype in entities[ename]) {
|
|
552
540
|
let e = {};
|
|
553
541
|
e.id = String(ename);
|
|
554
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
|
+
}
|
|
555
570
|
//extract attributes
|
|
556
571
|
let isEmpty = true;
|
|
557
572
|
for (let attr of entities[ename][etype]) {
|
|
@@ -568,6 +583,15 @@ function sendUpdateValueNgsi2(entityName, measures, typeInformation, token, call
|
|
|
568
583
|
))))
|
|
569
584
|
) {
|
|
570
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
|
+
}
|
|
571
595
|
e[attr.name] = { type: attr.type, value: attr.value, metadata: attr.metadata };
|
|
572
596
|
}
|
|
573
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 || { ...config.
|
|
147
|
+
typeInformation = deviceGroup || { ...config.getConfigForTypeInformation(), ...configDeviceInfo };
|
|
148
148
|
} else {
|
|
149
|
-
typeInformation = { ...config.
|
|
149
|
+
typeInformation = { ...config.getConfigForTypeInformation(), ...deviceInformation };
|
|
150
150
|
attributeList.forEach((key) => {
|
|
151
151
|
typeInformation[key] =
|
|
152
152
|
typeInformation[key] || (deviceGroup || {})[key] || (configDeviceInfo || {})[key];
|
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": "4.
|
|
5
|
+
"version": "4.4.0",
|
|
6
6
|
"homepage": "https://github.com/telefonicaid/iotagent-node-lib",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"fiware",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"async": "2.6.4",
|
|
47
47
|
"body-parser": "~1.20.0",
|
|
48
|
-
"express": "~4.
|
|
48
|
+
"express": "~4.19.2",
|
|
49
49
|
"got": "~11.8.5",
|
|
50
50
|
"jexl": "2.3.0",
|
|
51
51
|
"jison": "0.4.18",
|
|
@@ -445,6 +445,74 @@ const testCases = [
|
|
|
445
445
|
}
|
|
446
446
|
]
|
|
447
447
|
},
|
|
448
|
+
{
|
|
449
|
+
describeName: '0021 Simple group with active attributes with metadata',
|
|
450
|
+
provision: {
|
|
451
|
+
url: 'http://localhost:' + config.iota.server.port + '/iot/services',
|
|
452
|
+
method: 'POST',
|
|
453
|
+
json: {
|
|
454
|
+
services: [
|
|
455
|
+
{
|
|
456
|
+
resource: '/iot/json',
|
|
457
|
+
apikey: globalEnv.apikey,
|
|
458
|
+
entity_type: globalEnv.entity_type,
|
|
459
|
+
commands: [],
|
|
460
|
+
lazy: [],
|
|
461
|
+
attributes: [
|
|
462
|
+
{
|
|
463
|
+
object_id: 'a',
|
|
464
|
+
name: 'attr_a',
|
|
465
|
+
type: 'Boolean',
|
|
466
|
+
metadata: {
|
|
467
|
+
accuracy: {
|
|
468
|
+
value: 0.8,
|
|
469
|
+
type: 'Float'
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
],
|
|
474
|
+
static_attributes: []
|
|
475
|
+
}
|
|
476
|
+
]
|
|
477
|
+
},
|
|
478
|
+
headers: {
|
|
479
|
+
'fiware-service': globalEnv.service,
|
|
480
|
+
'fiware-servicepath': globalEnv.servicePath
|
|
481
|
+
}
|
|
482
|
+
},
|
|
483
|
+
should: [
|
|
484
|
+
{
|
|
485
|
+
shouldName:
|
|
486
|
+
'A - WHEN sending defined object_ids (measures) through http IT should send measures to Context Broker preserving value types, name mappings and metadatas',
|
|
487
|
+
type: 'single',
|
|
488
|
+
measure: {
|
|
489
|
+
url: 'http://localhost:' + config.http.port + '/iot/json',
|
|
490
|
+
method: 'POST',
|
|
491
|
+
qs: {
|
|
492
|
+
i: globalEnv.deviceId,
|
|
493
|
+
k: globalEnv.apikey
|
|
494
|
+
},
|
|
495
|
+
json: {
|
|
496
|
+
a: false
|
|
497
|
+
}
|
|
498
|
+
},
|
|
499
|
+
expectation: {
|
|
500
|
+
id: globalEnv.entity_name,
|
|
501
|
+
type: globalEnv.entity_type,
|
|
502
|
+
attr_a: {
|
|
503
|
+
value: false,
|
|
504
|
+
type: 'Boolean',
|
|
505
|
+
metadata: {
|
|
506
|
+
accuracy: {
|
|
507
|
+
value: 0.8,
|
|
508
|
+
type: 'Float'
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
]
|
|
515
|
+
},
|
|
448
516
|
// 0100 - JEXL TESTS
|
|
449
517
|
{
|
|
450
518
|
describeName: '0100 - Simple group with active attribute + JEXL expression boolean (!)',
|
|
@@ -1036,7 +1104,8 @@ const testCases = [
|
|
|
1036
1104
|
]
|
|
1037
1105
|
},
|
|
1038
1106
|
{
|
|
1039
|
-
describeName:
|
|
1107
|
+
describeName:
|
|
1108
|
+
'0140 - Simple group with active attribute + chained JEXL expression text (a | trim | replacestr("hello","hi"))',
|
|
1040
1109
|
provision: {
|
|
1041
1110
|
url: 'http://localhost:' + config.iota.server.port + '/iot/services',
|
|
1042
1111
|
method: 'POST',
|
|
@@ -1476,6 +1545,75 @@ const testCases = [
|
|
|
1476
1545
|
}
|
|
1477
1546
|
]
|
|
1478
1547
|
},
|
|
1548
|
+
{
|
|
1549
|
+
describeName: '0191 - Simple group with JEXL expression in metadata',
|
|
1550
|
+
provision: {
|
|
1551
|
+
url: 'http://localhost:' + config.iota.server.port + '/iot/services',
|
|
1552
|
+
method: 'POST',
|
|
1553
|
+
json: {
|
|
1554
|
+
services: [
|
|
1555
|
+
{
|
|
1556
|
+
resource: '/iot/json',
|
|
1557
|
+
apikey: globalEnv.apikey,
|
|
1558
|
+
entity_type: globalEnv.entity_type,
|
|
1559
|
+
commands: [],
|
|
1560
|
+
lazy: [],
|
|
1561
|
+
attributes: [
|
|
1562
|
+
{
|
|
1563
|
+
object_id: 'a',
|
|
1564
|
+
name: 'attr_a',
|
|
1565
|
+
type: 'Boolean',
|
|
1566
|
+
expression: 'a?threshold[90|tostring].max:true',
|
|
1567
|
+
metadata: {
|
|
1568
|
+
unit: {
|
|
1569
|
+
type: 'Text',
|
|
1570
|
+
expression: '"hola" + "adios"'
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
]
|
|
1575
|
+
}
|
|
1576
|
+
]
|
|
1577
|
+
},
|
|
1578
|
+
headers: {
|
|
1579
|
+
'fiware-service': globalEnv.service,
|
|
1580
|
+
'fiware-servicepath': globalEnv.servicePath
|
|
1581
|
+
}
|
|
1582
|
+
},
|
|
1583
|
+
should: [
|
|
1584
|
+
{
|
|
1585
|
+
shouldName:
|
|
1586
|
+
'A - WHEN sending a measure through http IT should send to Context Broker expanded metadata value ',
|
|
1587
|
+
type: 'single',
|
|
1588
|
+
isRegex: true,
|
|
1589
|
+
measure: {
|
|
1590
|
+
url: 'http://localhost:' + config.http.port + '/iot/json',
|
|
1591
|
+
method: 'POST',
|
|
1592
|
+
qs: {
|
|
1593
|
+
i: globalEnv.deviceId,
|
|
1594
|
+
k: globalEnv.apikey
|
|
1595
|
+
},
|
|
1596
|
+
json: {
|
|
1597
|
+
a: false
|
|
1598
|
+
}
|
|
1599
|
+
},
|
|
1600
|
+
expectation: {
|
|
1601
|
+
id: globalEnv.entity_name,
|
|
1602
|
+
type: globalEnv.entity_type,
|
|
1603
|
+
attr_a: {
|
|
1604
|
+
value: true,
|
|
1605
|
+
type: 'Boolean',
|
|
1606
|
+
metadata: {
|
|
1607
|
+
unit: {
|
|
1608
|
+
type: 'Text',
|
|
1609
|
+
value: 'holaadios'
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
]
|
|
1616
|
+
},
|
|
1479
1617
|
// 0200 - COMMANDS TESTS
|
|
1480
1618
|
{
|
|
1481
1619
|
describeName: '0200 - Simple group with commands',
|
|
@@ -1704,6 +1842,78 @@ const testCases = [
|
|
|
1704
1842
|
}
|
|
1705
1843
|
]
|
|
1706
1844
|
},
|
|
1845
|
+
{
|
|
1846
|
+
describeName: '0320 - Simple group with active attributes + metadata in Static attributes ',
|
|
1847
|
+
provision: {
|
|
1848
|
+
url: 'http://localhost:' + config.iota.server.port + '/iot/services',
|
|
1849
|
+
method: 'POST',
|
|
1850
|
+
json: {
|
|
1851
|
+
services: [
|
|
1852
|
+
{
|
|
1853
|
+
resource: '/iot/json',
|
|
1854
|
+
apikey: globalEnv.apikey,
|
|
1855
|
+
entity_type: globalEnv.entity_type,
|
|
1856
|
+
commands: [],
|
|
1857
|
+
lazy: [],
|
|
1858
|
+
attributes: [],
|
|
1859
|
+
static_attributes: [
|
|
1860
|
+
{
|
|
1861
|
+
name: 'static_a',
|
|
1862
|
+
type: 'Number',
|
|
1863
|
+
value: 4,
|
|
1864
|
+
metadata: {
|
|
1865
|
+
accuracy: {
|
|
1866
|
+
value: 0.8,
|
|
1867
|
+
type: 'Float'
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
]
|
|
1872
|
+
}
|
|
1873
|
+
]
|
|
1874
|
+
},
|
|
1875
|
+
headers: {
|
|
1876
|
+
'fiware-service': globalEnv.service,
|
|
1877
|
+
'fiware-servicepath': globalEnv.servicePath
|
|
1878
|
+
}
|
|
1879
|
+
},
|
|
1880
|
+
should: [
|
|
1881
|
+
{
|
|
1882
|
+
shouldName:
|
|
1883
|
+
'A - WHEN sending measures through http IT should store metatada static attributes into Context Broker',
|
|
1884
|
+
type: 'single',
|
|
1885
|
+
measure: {
|
|
1886
|
+
url: 'http://localhost:' + config.http.port + '/iot/json',
|
|
1887
|
+
method: 'POST',
|
|
1888
|
+
qs: {
|
|
1889
|
+
i: globalEnv.deviceId,
|
|
1890
|
+
k: globalEnv.apikey
|
|
1891
|
+
},
|
|
1892
|
+
json: {
|
|
1893
|
+
a: 10
|
|
1894
|
+
}
|
|
1895
|
+
},
|
|
1896
|
+
expectation: {
|
|
1897
|
+
id: globalEnv.entity_name,
|
|
1898
|
+
type: globalEnv.entity_type,
|
|
1899
|
+
a: {
|
|
1900
|
+
value: 10,
|
|
1901
|
+
type: 'Text'
|
|
1902
|
+
},
|
|
1903
|
+
static_a: {
|
|
1904
|
+
value: 4,
|
|
1905
|
+
type: 'Number',
|
|
1906
|
+
metadata: {
|
|
1907
|
+
accuracy: {
|
|
1908
|
+
value: 0.8,
|
|
1909
|
+
type: 'Float'
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
]
|
|
1916
|
+
},
|
|
1707
1917
|
// 0400 - TIMESTAMP TESTS
|
|
1708
1918
|
{
|
|
1709
1919
|
describeName: '0400 - Simple group with active attribute + timestamp:false',
|
|
@@ -1976,6 +2186,527 @@ const testCases = [
|
|
|
1976
2186
|
}
|
|
1977
2187
|
]
|
|
1978
2188
|
},
|
|
2189
|
+
{
|
|
2190
|
+
describeName: '0430 - Simple group with active attribute + timestamp mapping defined',
|
|
2191
|
+
provision: {
|
|
2192
|
+
url: 'http://localhost:' + config.iota.server.port + '/iot/services',
|
|
2193
|
+
method: 'POST',
|
|
2194
|
+
json: {
|
|
2195
|
+
services: [
|
|
2196
|
+
{
|
|
2197
|
+
resource: '/iot/json',
|
|
2198
|
+
apikey: globalEnv.apikey,
|
|
2199
|
+
timestamp: true,
|
|
2200
|
+
entity_type: globalEnv.entity_type,
|
|
2201
|
+
commands: [],
|
|
2202
|
+
lazy: [],
|
|
2203
|
+
attributes: [
|
|
2204
|
+
{
|
|
2205
|
+
object_id: 'mydatetime',
|
|
2206
|
+
name: 'TimeInstant',
|
|
2207
|
+
type: 'DateTime'
|
|
2208
|
+
}
|
|
2209
|
+
]
|
|
2210
|
+
}
|
|
2211
|
+
]
|
|
2212
|
+
},
|
|
2213
|
+
headers: {
|
|
2214
|
+
'fiware-service': globalEnv.service,
|
|
2215
|
+
'fiware-servicepath': globalEnv.servicePath
|
|
2216
|
+
}
|
|
2217
|
+
},
|
|
2218
|
+
should: [
|
|
2219
|
+
{
|
|
2220
|
+
shouldName:
|
|
2221
|
+
'A - WHEN sending a measure through http IT should map the measure to timestamp attribute and use it for timestmap and other metadata attributes sent to Context Broker',
|
|
2222
|
+
type: 'single',
|
|
2223
|
+
measure: {
|
|
2224
|
+
url: 'http://localhost:' + config.http.port + '/iot/json',
|
|
2225
|
+
method: 'POST',
|
|
2226
|
+
qs: {
|
|
2227
|
+
i: globalEnv.deviceId,
|
|
2228
|
+
k: globalEnv.apikey
|
|
2229
|
+
},
|
|
2230
|
+
json: {
|
|
2231
|
+
mydatetime: '2022-02-02T02:22:22.222Z',
|
|
2232
|
+
a: 23
|
|
2233
|
+
}
|
|
2234
|
+
},
|
|
2235
|
+
expectation: {
|
|
2236
|
+
id: globalEnv.entity_name,
|
|
2237
|
+
type: globalEnv.entity_type,
|
|
2238
|
+
a: {
|
|
2239
|
+
value: 23,
|
|
2240
|
+
type: 'Text',
|
|
2241
|
+
metadata: {
|
|
2242
|
+
TimeInstant: {
|
|
2243
|
+
value: '2022-02-02T02:22:22.222Z',
|
|
2244
|
+
type: 'DateTime'
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
},
|
|
2248
|
+
TimeInstant: {
|
|
2249
|
+
value: '2022-02-02T02:22:22.222Z',
|
|
2250
|
+
type: 'DateTime'
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
},
|
|
2254
|
+
{
|
|
2255
|
+
shouldName:
|
|
2256
|
+
'A - WHEN sending a measure without timestamp through http IT should use system timestamp for mapped attribute and use it for timestmap and other metadata attributes sent to Context Broker',
|
|
2257
|
+
type: 'single',
|
|
2258
|
+
isRegex: true,
|
|
2259
|
+
measure: {
|
|
2260
|
+
url: 'http://localhost:' + config.http.port + '/iot/json',
|
|
2261
|
+
method: 'POST',
|
|
2262
|
+
qs: {
|
|
2263
|
+
i: globalEnv.deviceId,
|
|
2264
|
+
k: globalEnv.apikey
|
|
2265
|
+
},
|
|
2266
|
+
json: {
|
|
2267
|
+
a: 23
|
|
2268
|
+
}
|
|
2269
|
+
},
|
|
2270
|
+
expectation: {
|
|
2271
|
+
id: globalEnv.entity_name,
|
|
2272
|
+
type: globalEnv.entity_type,
|
|
2273
|
+
a: {
|
|
2274
|
+
value: 23,
|
|
2275
|
+
type: 'Text',
|
|
2276
|
+
metadata: {
|
|
2277
|
+
TimeInstant: {
|
|
2278
|
+
value: _.isDateString,
|
|
2279
|
+
type: 'DateTime'
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
},
|
|
2283
|
+
TimeInstant: {
|
|
2284
|
+
value: _.isDateString,
|
|
2285
|
+
type: 'DateTime'
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
},
|
|
2289
|
+
{
|
|
2290
|
+
shouldName:
|
|
2291
|
+
'A - WHEN sending a measure with timestamp through http IT should map the measure to timestamp attribute and use it for timestmap and other metadata attributes sent to Context Broker',
|
|
2292
|
+
type: 'single',
|
|
2293
|
+
measure: {
|
|
2294
|
+
url: 'http://localhost:' + config.http.port + '/iot/json',
|
|
2295
|
+
method: 'POST',
|
|
2296
|
+
qs: {
|
|
2297
|
+
i: globalEnv.deviceId,
|
|
2298
|
+
k: globalEnv.apikey
|
|
2299
|
+
},
|
|
2300
|
+
json: {
|
|
2301
|
+
mydatetime: '2022-02-02T02:22:22.222Z',
|
|
2302
|
+
TimeInstant: '2033-03-03T03:33:33.333Z',
|
|
2303
|
+
a: 23
|
|
2304
|
+
}
|
|
2305
|
+
},
|
|
2306
|
+
expectation: {
|
|
2307
|
+
id: globalEnv.entity_name,
|
|
2308
|
+
type: globalEnv.entity_type,
|
|
2309
|
+
a: {
|
|
2310
|
+
value: 23,
|
|
2311
|
+
type: 'Text',
|
|
2312
|
+
metadata: {
|
|
2313
|
+
TimeInstant: {
|
|
2314
|
+
value: '2022-02-02T02:22:22.222Z',
|
|
2315
|
+
type: 'DateTime'
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2318
|
+
},
|
|
2319
|
+
TimeInstant: {
|
|
2320
|
+
value: '2022-02-02T02:22:22.222Z',
|
|
2321
|
+
type: 'DateTime'
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
]
|
|
2326
|
+
},
|
|
2327
|
+
{
|
|
2328
|
+
describeName: '0431 - Simple group with active attribute + timestamp mapping defined + explicitAttrs',
|
|
2329
|
+
provision: {
|
|
2330
|
+
url: 'http://localhost:' + config.iota.server.port + '/iot/services',
|
|
2331
|
+
method: 'POST',
|
|
2332
|
+
json: {
|
|
2333
|
+
services: [
|
|
2334
|
+
{
|
|
2335
|
+
resource: '/iot/json',
|
|
2336
|
+
apikey: globalEnv.apikey,
|
|
2337
|
+
timestamp: true,
|
|
2338
|
+
explicitAttrs: true,
|
|
2339
|
+
entity_type: globalEnv.entity_type,
|
|
2340
|
+
commands: [],
|
|
2341
|
+
lazy: [],
|
|
2342
|
+
attributes: [
|
|
2343
|
+
{
|
|
2344
|
+
object_id: 'mydatetime',
|
|
2345
|
+
name: 'TimeInstant',
|
|
2346
|
+
type: 'DateTime'
|
|
2347
|
+
},
|
|
2348
|
+
{
|
|
2349
|
+
object_id: 'a',
|
|
2350
|
+
name: 'a',
|
|
2351
|
+
type: 'Text'
|
|
2352
|
+
}
|
|
2353
|
+
]
|
|
2354
|
+
}
|
|
2355
|
+
]
|
|
2356
|
+
},
|
|
2357
|
+
headers: {
|
|
2358
|
+
'fiware-service': globalEnv.service,
|
|
2359
|
+
'fiware-servicepath': globalEnv.servicePath
|
|
2360
|
+
}
|
|
2361
|
+
},
|
|
2362
|
+
should: [
|
|
2363
|
+
{
|
|
2364
|
+
shouldName:
|
|
2365
|
+
'A - WHEN sending a measure through http IT should map the measure to timestamp attribute and use it for timestmap and other metadata attributes sent to Context Broker',
|
|
2366
|
+
type: 'single',
|
|
2367
|
+
measure: {
|
|
2368
|
+
url: 'http://localhost:' + config.http.port + '/iot/json',
|
|
2369
|
+
method: 'POST',
|
|
2370
|
+
qs: {
|
|
2371
|
+
i: globalEnv.deviceId,
|
|
2372
|
+
k: globalEnv.apikey
|
|
2373
|
+
},
|
|
2374
|
+
json: {
|
|
2375
|
+
mydatetime: '2022-02-02T02:22:22.222Z',
|
|
2376
|
+
a: 23
|
|
2377
|
+
}
|
|
2378
|
+
},
|
|
2379
|
+
expectation: {
|
|
2380
|
+
id: globalEnv.entity_name,
|
|
2381
|
+
type: globalEnv.entity_type,
|
|
2382
|
+
a: {
|
|
2383
|
+
value: 23,
|
|
2384
|
+
type: 'Text',
|
|
2385
|
+
metadata: {
|
|
2386
|
+
TimeInstant: {
|
|
2387
|
+
value: '2022-02-02T02:22:22.222Z',
|
|
2388
|
+
type: 'DateTime'
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
},
|
|
2392
|
+
TimeInstant: {
|
|
2393
|
+
value: '2022-02-02T02:22:22.222Z',
|
|
2394
|
+
type: 'DateTime'
|
|
2395
|
+
}
|
|
2396
|
+
}
|
|
2397
|
+
},
|
|
2398
|
+
{
|
|
2399
|
+
shouldName:
|
|
2400
|
+
'A - WHEN sending a measure without timestamp through http IT should use system timestamp for mapped attribute and use it for timestmap and other metadata attributes sent to Context Broker',
|
|
2401
|
+
type: 'single',
|
|
2402
|
+
isRegex: true,
|
|
2403
|
+
measure: {
|
|
2404
|
+
url: 'http://localhost:' + config.http.port + '/iot/json',
|
|
2405
|
+
method: 'POST',
|
|
2406
|
+
qs: {
|
|
2407
|
+
i: globalEnv.deviceId,
|
|
2408
|
+
k: globalEnv.apikey
|
|
2409
|
+
},
|
|
2410
|
+
json: {
|
|
2411
|
+
a: 23
|
|
2412
|
+
}
|
|
2413
|
+
},
|
|
2414
|
+
expectation: {
|
|
2415
|
+
id: globalEnv.entity_name,
|
|
2416
|
+
type: globalEnv.entity_type,
|
|
2417
|
+
a: {
|
|
2418
|
+
value: 23,
|
|
2419
|
+
type: 'Text',
|
|
2420
|
+
metadata: {
|
|
2421
|
+
TimeInstant: {
|
|
2422
|
+
value: _.isDateString,
|
|
2423
|
+
type: 'DateTime'
|
|
2424
|
+
}
|
|
2425
|
+
}
|
|
2426
|
+
},
|
|
2427
|
+
TimeInstant: {
|
|
2428
|
+
value: _.isDateString,
|
|
2429
|
+
type: 'DateTime'
|
|
2430
|
+
}
|
|
2431
|
+
}
|
|
2432
|
+
},
|
|
2433
|
+
{
|
|
2434
|
+
shouldName:
|
|
2435
|
+
'A - WHEN sending a measure with timestamp through http IT should map the measure to timestamp attribute and use it for timestmap and other metadata attributes sent to Context Broker',
|
|
2436
|
+
type: 'single',
|
|
2437
|
+
measure: {
|
|
2438
|
+
url: 'http://localhost:' + config.http.port + '/iot/json',
|
|
2439
|
+
method: 'POST',
|
|
2440
|
+
qs: {
|
|
2441
|
+
i: globalEnv.deviceId,
|
|
2442
|
+
k: globalEnv.apikey
|
|
2443
|
+
},
|
|
2444
|
+
json: {
|
|
2445
|
+
mydatetime: '2022-02-02T02:22:22.222Z',
|
|
2446
|
+
TimeInstant: '2033-03-03T03:33:33.333Z',
|
|
2447
|
+
a: 23
|
|
2448
|
+
}
|
|
2449
|
+
},
|
|
2450
|
+
expectation: {
|
|
2451
|
+
id: globalEnv.entity_name,
|
|
2452
|
+
type: globalEnv.entity_type,
|
|
2453
|
+
a: {
|
|
2454
|
+
value: 23,
|
|
2455
|
+
type: 'Text',
|
|
2456
|
+
metadata: {
|
|
2457
|
+
TimeInstant: {
|
|
2458
|
+
value: '2022-02-02T02:22:22.222Z',
|
|
2459
|
+
type: 'DateTime'
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2462
|
+
},
|
|
2463
|
+
TimeInstant: {
|
|
2464
|
+
value: '2022-02-02T02:22:22.222Z',
|
|
2465
|
+
type: 'DateTime'
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
]
|
|
2470
|
+
},
|
|
2471
|
+
{
|
|
2472
|
+
describeName: '0432 - Simple group with active attribute + bad timestamp mapping defined',
|
|
2473
|
+
provision: {
|
|
2474
|
+
url: 'http://localhost:' + config.iota.server.port + '/iot/services',
|
|
2475
|
+
method: 'POST',
|
|
2476
|
+
json: {
|
|
2477
|
+
services: [
|
|
2478
|
+
{
|
|
2479
|
+
resource: '/iot/json',
|
|
2480
|
+
apikey: globalEnv.apikey,
|
|
2481
|
+
timestamp: true,
|
|
2482
|
+
entity_type: globalEnv.entity_type,
|
|
2483
|
+
commands: [],
|
|
2484
|
+
lazy: [],
|
|
2485
|
+
attributes: [
|
|
2486
|
+
{
|
|
2487
|
+
object_id: 'mydatetime',
|
|
2488
|
+
name: 'TimeInstant',
|
|
2489
|
+
type: 'DateTime',
|
|
2490
|
+
expression: '"2033-03-03T" + "03:33:33.333Z"'
|
|
2491
|
+
}
|
|
2492
|
+
]
|
|
2493
|
+
}
|
|
2494
|
+
]
|
|
2495
|
+
},
|
|
2496
|
+
headers: {
|
|
2497
|
+
'fiware-service': globalEnv.service,
|
|
2498
|
+
'fiware-servicepath': globalEnv.servicePath
|
|
2499
|
+
}
|
|
2500
|
+
},
|
|
2501
|
+
should: [
|
|
2502
|
+
{
|
|
2503
|
+
shouldName:
|
|
2504
|
+
'A - WHEN sending a measure through http IT should map the measure to fixed timestamp attribute and use it for timestmap sent to Context Broker',
|
|
2505
|
+
type: 'single',
|
|
2506
|
+
measure: {
|
|
2507
|
+
url: 'http://localhost:' + config.http.port + '/iot/json',
|
|
2508
|
+
method: 'POST',
|
|
2509
|
+
qs: {
|
|
2510
|
+
i: globalEnv.deviceId,
|
|
2511
|
+
k: globalEnv.apikey
|
|
2512
|
+
},
|
|
2513
|
+
json: {
|
|
2514
|
+
mydatetime: '2022-02-02T02:22:22.222Z',
|
|
2515
|
+
a: 23
|
|
2516
|
+
}
|
|
2517
|
+
},
|
|
2518
|
+
expectation: {
|
|
2519
|
+
id: globalEnv.entity_name,
|
|
2520
|
+
type: globalEnv.entity_type,
|
|
2521
|
+
a: {
|
|
2522
|
+
value: 23,
|
|
2523
|
+
type: 'Text',
|
|
2524
|
+
metadata: {
|
|
2525
|
+
TimeInstant: {
|
|
2526
|
+
value: '2033-03-03T03:33:33.333Z',
|
|
2527
|
+
type: 'DateTime'
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
},
|
|
2531
|
+
TimeInstant: {
|
|
2532
|
+
value: '2033-03-03T03:33:33.333Z',
|
|
2533
|
+
type: 'DateTime'
|
|
2534
|
+
}
|
|
2535
|
+
}
|
|
2536
|
+
}
|
|
2537
|
+
]
|
|
2538
|
+
},
|
|
2539
|
+
{
|
|
2540
|
+
describeName: '0433 - Simple group with active attribute + several timestamp mappings defined in multientity',
|
|
2541
|
+
provision: {
|
|
2542
|
+
url: 'http://localhost:' + config.iota.server.port + '/iot/services',
|
|
2543
|
+
method: 'POST',
|
|
2544
|
+
json: {
|
|
2545
|
+
services: [
|
|
2546
|
+
{
|
|
2547
|
+
resource: '/iot/json',
|
|
2548
|
+
apikey: globalEnv.apikey,
|
|
2549
|
+
timestamp: true,
|
|
2550
|
+
entity_type: globalEnv.entity_type,
|
|
2551
|
+
commands: [],
|
|
2552
|
+
lazy: [],
|
|
2553
|
+
attributes: [
|
|
2554
|
+
{
|
|
2555
|
+
object_id: 'a',
|
|
2556
|
+
name: 'a',
|
|
2557
|
+
type: 'Text'
|
|
2558
|
+
},
|
|
2559
|
+
{
|
|
2560
|
+
object_id: 'mydatetime1',
|
|
2561
|
+
name: 'TimeInstant',
|
|
2562
|
+
type: 'DateTime',
|
|
2563
|
+
entity_name: 'TestType:TestDevice1',
|
|
2564
|
+
entity_type: 'TestType'
|
|
2565
|
+
},
|
|
2566
|
+
{
|
|
2567
|
+
object_id: 'mydatetime2',
|
|
2568
|
+
name: 'TimeInstant',
|
|
2569
|
+
type: 'DateTime',
|
|
2570
|
+
entity_name: 'TestType:TestDevice2',
|
|
2571
|
+
entity_type: 'TestType'
|
|
2572
|
+
},
|
|
2573
|
+
{
|
|
2574
|
+
object_id: 'a1',
|
|
2575
|
+
name: 'a1',
|
|
2576
|
+
type: 'Text',
|
|
2577
|
+
expression: 'a',
|
|
2578
|
+
entity_name: 'TestType:TestDevice1',
|
|
2579
|
+
entity_type: 'TestType'
|
|
2580
|
+
},
|
|
2581
|
+
{
|
|
2582
|
+
object_id: 'a2',
|
|
2583
|
+
name: 'a2',
|
|
2584
|
+
type: 'Text',
|
|
2585
|
+
expression: 'a',
|
|
2586
|
+
entity_name: 'TestType:TestDevice2',
|
|
2587
|
+
entity_type: 'TestType'
|
|
2588
|
+
}
|
|
2589
|
+
]
|
|
2590
|
+
}
|
|
2591
|
+
]
|
|
2592
|
+
},
|
|
2593
|
+
headers: {
|
|
2594
|
+
'fiware-service': globalEnv.service,
|
|
2595
|
+
'fiware-servicepath': globalEnv.servicePath
|
|
2596
|
+
}
|
|
2597
|
+
},
|
|
2598
|
+
should: [
|
|
2599
|
+
{
|
|
2600
|
+
shouldName:
|
|
2601
|
+
'A - WHEN sending a measure through http IT should map the measure to timestamp attributes and use it for timestmap and other metadata attributes sent to Context Broker',
|
|
2602
|
+
type: 'multientity',
|
|
2603
|
+
isRegex: true,
|
|
2604
|
+
measure: {
|
|
2605
|
+
url: 'http://localhost:' + config.http.port + '/iot/json',
|
|
2606
|
+
method: 'POST',
|
|
2607
|
+
qs: {
|
|
2608
|
+
i: globalEnv.deviceId,
|
|
2609
|
+
k: globalEnv.apikey
|
|
2610
|
+
},
|
|
2611
|
+
json: {
|
|
2612
|
+
a: 23,
|
|
2613
|
+
mydatetime1: '2011-01-01T01:11:11.111Z',
|
|
2614
|
+
mydatetime2: '2022-02-02T02:22:22.222Z'
|
|
2615
|
+
}
|
|
2616
|
+
},
|
|
2617
|
+
expectation: {
|
|
2618
|
+
actionType: 'append',
|
|
2619
|
+
entities: [
|
|
2620
|
+
{
|
|
2621
|
+
id: globalEnv.entity_name,
|
|
2622
|
+
type: globalEnv.entity_type,
|
|
2623
|
+
a: {
|
|
2624
|
+
value: 23,
|
|
2625
|
+
type: 'Text',
|
|
2626
|
+
metadata: {
|
|
2627
|
+
TimeInstant: {
|
|
2628
|
+
value: _.isDateString,
|
|
2629
|
+
type: 'DateTime'
|
|
2630
|
+
}
|
|
2631
|
+
}
|
|
2632
|
+
},
|
|
2633
|
+
TimeInstant: {
|
|
2634
|
+
value: _.isDateString,
|
|
2635
|
+
type: 'DateTime'
|
|
2636
|
+
}
|
|
2637
|
+
},
|
|
2638
|
+
{
|
|
2639
|
+
id: 'TestType:TestDevice1',
|
|
2640
|
+
type: globalEnv.entity_type,
|
|
2641
|
+
a1: {
|
|
2642
|
+
value: 23,
|
|
2643
|
+
type: 'Text',
|
|
2644
|
+
metadata: {
|
|
2645
|
+
TimeInstant: {
|
|
2646
|
+
value: '2011-01-01T01:11:11.111Z',
|
|
2647
|
+
type: 'DateTime'
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
},
|
|
2651
|
+
TimeInstant: {
|
|
2652
|
+
value: '2011-01-01T01:11:11.111Z',
|
|
2653
|
+
type: 'DateTime'
|
|
2654
|
+
}
|
|
2655
|
+
},
|
|
2656
|
+
{
|
|
2657
|
+
id: 'TestType:TestDevice2',
|
|
2658
|
+
type: globalEnv.entity_type,
|
|
2659
|
+
a2: {
|
|
2660
|
+
value: 23,
|
|
2661
|
+
type: 'Text',
|
|
2662
|
+
metadata: {
|
|
2663
|
+
TimeInstant: {
|
|
2664
|
+
value: '2022-02-02T02:22:22.222Z',
|
|
2665
|
+
type: 'DateTime'
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
},
|
|
2669
|
+
TimeInstant: {
|
|
2670
|
+
value: '2022-02-02T02:22:22.222Z',
|
|
2671
|
+
type: 'DateTime'
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2674
|
+
]
|
|
2675
|
+
}
|
|
2676
|
+
},
|
|
2677
|
+
{
|
|
2678
|
+
shouldName:
|
|
2679
|
+
'A - WHEN sending a measure through http IT should map the measure to timestamp attributes for just mapped entity and sent to Context Broker',
|
|
2680
|
+
type: 'multientity',
|
|
2681
|
+
isRegex: true,
|
|
2682
|
+
measure: {
|
|
2683
|
+
url: 'http://localhost:' + config.http.port + '/iot/json',
|
|
2684
|
+
method: 'POST',
|
|
2685
|
+
qs: {
|
|
2686
|
+
i: globalEnv.deviceId,
|
|
2687
|
+
k: globalEnv.apikey
|
|
2688
|
+
},
|
|
2689
|
+
json: {
|
|
2690
|
+
mydatetime1: '2011-01-01T01:11:11.111Z'
|
|
2691
|
+
}
|
|
2692
|
+
},
|
|
2693
|
+
expectation: {
|
|
2694
|
+
actionType: 'append',
|
|
2695
|
+
entities: [
|
|
2696
|
+
{
|
|
2697
|
+
id: 'TestType:TestDevice1',
|
|
2698
|
+
type: globalEnv.entity_type,
|
|
2699
|
+
TimeInstant: {
|
|
2700
|
+
value: '2011-01-01T01:11:11.111Z',
|
|
2701
|
+
type: 'DateTime'
|
|
2702
|
+
}
|
|
2703
|
+
}
|
|
2704
|
+
]
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
]
|
|
2708
|
+
},
|
|
2709
|
+
|
|
1979
2710
|
// 0500 - EXPLICIT ATTRIBUTES TESTS
|
|
1980
2711
|
{
|
|
1981
2712
|
describeName: '0500 - Group with explicit attrs:false (boolean) + active atributes',
|
|
@@ -307,6 +307,38 @@ const iotAgentConfig = {
|
|
|
307
307
|
}
|
|
308
308
|
]
|
|
309
309
|
},
|
|
310
|
+
WeatherStation10: {
|
|
311
|
+
commands: [],
|
|
312
|
+
type: 'WeatherStation',
|
|
313
|
+
lazy: [],
|
|
314
|
+
active: [
|
|
315
|
+
{
|
|
316
|
+
object_id: 'p',
|
|
317
|
+
name: 'pressure',
|
|
318
|
+
type: 'Hgmm'
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
object_id: 'h',
|
|
322
|
+
name: 'humidity',
|
|
323
|
+
type: 'Percentage',
|
|
324
|
+
entity_name: 'Higro2000',
|
|
325
|
+
entity_type: 'Higrometer',
|
|
326
|
+
metadata: {
|
|
327
|
+
unitCode: {
|
|
328
|
+
type: 'Text',
|
|
329
|
+
value: 'Hgmm'
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
object_id: 'TimeInstant',
|
|
335
|
+
name: 'TimeInstant',
|
|
336
|
+
type: 'DateTime',
|
|
337
|
+
entity_name: 'Higro2000',
|
|
338
|
+
entity_type: 'Higrometer'
|
|
339
|
+
}
|
|
340
|
+
]
|
|
341
|
+
},
|
|
310
342
|
Sensor001: {
|
|
311
343
|
commands: [],
|
|
312
344
|
type: 'Sensor',
|
|
@@ -1488,7 +1520,7 @@ describe('NGSI-v2 - Multi-entity plugin', function () {
|
|
|
1488
1520
|
|
|
1489
1521
|
describe('NGSI-v2 - Multi-entity plugin is executed before timestamp process plugin', function () {
|
|
1490
1522
|
beforeEach(function (done) {
|
|
1491
|
-
logger.setLevel('
|
|
1523
|
+
logger.setLevel('DEBUG');
|
|
1492
1524
|
iotAgentConfig.timestamp = true;
|
|
1493
1525
|
iotAgentLib.activate(iotAgentConfig, function () {
|
|
1494
1526
|
iotAgentLib.clearAll(function () {
|
|
@@ -1635,7 +1667,7 @@ describe('NGSI-v2 - Multi-entity plugin is executed before timestamp process plu
|
|
|
1635
1667
|
value: '2018-06-13T13:28:34.611Z'
|
|
1636
1668
|
}
|
|
1637
1669
|
];
|
|
1638
|
-
iotAgentLib.update('ws5', '
|
|
1670
|
+
iotAgentLib.update('ws5', 'WeatherStation10', '', tsValue, function (error) {
|
|
1639
1671
|
should.not.exist(error);
|
|
1640
1672
|
contextBrokerMock.done();
|
|
1641
1673
|
done();
|