iotagent-node-lib 4.3.0 → 4.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/config.js +2 -1
  2. package/doc/admin.md +16 -7
  3. package/doc/api.md +230 -45
  4. package/doc/requirements.txt +1 -1
  5. package/docker/Mosquitto/Dockerfile +1 -1
  6. package/lib/commonConfig.js +25 -1
  7. package/lib/errors.js +4 -1
  8. package/lib/services/devices/deviceRegistryMemory.js +1 -1
  9. package/lib/services/devices/deviceRegistryMongoDB.js +3 -10
  10. package/lib/services/devices/deviceService.js +5 -3
  11. package/lib/services/devices/devices-NGSI-LD.js +5 -5
  12. package/lib/services/devices/devices-NGSI-mixed.js +3 -3
  13. package/lib/services/devices/devices-NGSI-v2.js +5 -5
  14. package/lib/services/ngsi/entities-NGSI-LD.js +1 -1
  15. package/lib/services/ngsi/entities-NGSI-v2.js +324 -268
  16. package/lib/services/ngsi/ngsiService.js +2 -2
  17. package/lib/services/ngsi/subscription-NGSI-LD.js +2 -2
  18. package/lib/services/ngsi/subscription-NGSI-v2.js +2 -2
  19. package/lib/services/northBound/deviceProvisioningServer.js +5 -4
  20. package/lib/services/northBound/northboundServer.js +2 -2
  21. package/package.json +2 -2
  22. package/scripts/legacy_expression_tool/requirements.txt +1 -1
  23. package/test/functional/README.md +60 -37
  24. package/test/functional/testCases.js +2206 -722
  25. package/test/functional/testUtils.js +53 -20
  26. package/test/unit/examples/deviceProvisioningRequests/updateProvisionDeviceWithApikey.json +4 -0
  27. package/test/unit/ngsi-ld/provisioning/device-update-registration_test.js +5 -5
  28. package/test/unit/ngsiv2/plugins/multientity-plugin_test.js +34 -2
  29. package/test/unit/ngsiv2/provisioning/device-update-registration_test.js +6 -6
  30. package/test/unit/ngsiv2/provisioning/updateProvisionedDevices-test.js +35 -0
package/config.js CHANGED
@@ -76,7 +76,8 @@ var config = {
76
76
  subservice: '/gardens',
77
77
  providerUrl: 'http://192.168.56.1:4041',
78
78
  deviceRegistrationDuration: 'P1M',
79
- defaultType: 'Thing'
79
+ defaultType: 'Thing',
80
+ expressLimit: '1Mb'
80
81
  };
81
82
 
82
83
  module.exports = config;
package/doc/admin.md CHANGED
@@ -260,13 +260,13 @@ the `mongob` section (as described bellow). E.g.:
260
260
 
261
261
  It configures the MongoDB driver for those repositories with 'mongodb' type. If the `host` parameter is a list of
262
262
  comma-separated IPs, they will be considered to be part of a Replica Set. In that case, the optional property
263
- `replicaSet` should contain the Replica Set name. If the database requires authentication, username (`user`),
264
- password (`password`) and authSource (`authSource`) can be set. If the database requires TLS/SSL connection but any
265
- validation of the certificate chain is not mandatory, all you need is to set the ssl (`ssl`) option as `true` to connect
266
- the database. If you need to add more complex option(s) such as `retryWrites=true` or `w=majority` when connection
267
- database, extraArgs (`extraArgs`) can be used to perform it. For The MongoBD driver will retry the connection at startup
268
- time `retries` times, waiting `retryTime` seconds between attempts, if those attributes are present (default values are
269
- 5 and 5 respectively). E.g.:
263
+ `replicaSet` should contain the Replica Set name. If the database requires authentication, username (`user`), password
264
+ (`password`) and authSource (`authSource`) can be set. If the database requires TLS/SSL connection but any validation of
265
+ the certificate chain is not mandatory, all you need is to set the ssl (`ssl`) option as `true` to connect the database.
266
+ If you need to add more complex option(s) such as `retryWrites=true` or `w=majority` when connection database, extraArgs
267
+ (`extraArgs`) can be used to perform it. For The MongoBD driver will retry the connection at startup time `retries`
268
+ times, waiting `retryTime` seconds between attempts, if those attributes are present (default values are 5 and 5
269
+ respectively). E.g.:
270
270
 
271
271
  ```javascript
272
272
  {
@@ -421,6 +421,14 @@ characters (such as semi-colons) which are
421
421
  specification. When provisioning devices, it is necessary that the developer provides valid `objectId`-`name` mappings
422
422
  whenever relaxed mode is used, to prevent the consumption of forbidden characters.
423
423
 
424
+ #### `expressLimit`
425
+
426
+ IotAgents, as all Express applications that use the body-parser middleware, have a default limit to the request body
427
+ size that the application will handle. This default limit for ioiotagnets are 1Mb. So, if your IotAgent receives a
428
+ request with a body that exceeds this limit, the application will throw a “Error: Request entity too large”.
429
+
430
+ The 1Mb default can be changed setting the `expressLimit` configuration parameter (or equivalente `IOTA_EXPRESS_LIMIT` environment variable).
431
+
424
432
  ### Configuration using environment variables
425
433
 
426
434
  Some of the configuration parameters can be overriden with environment variables, to ease the use of those parameters
@@ -482,6 +490,7 @@ overrides.
482
490
  | IOTA_EXPLICIT_ATTRS | `explicitAttrs` |
483
491
  | IOTA_DEFAULT_ENTITY_NAME_CONJUNCTION | `defaultEntityNameConjunction` |
484
492
  | IOTA_RELAX_TEMPLATE_VALIDATION | `relaxTemplateValidation` |
493
+ | IOTA_EXPRESS_LIMIT | `expressLimit` |
485
494
 
486
495
  Note:
487
496
 
package/doc/api.md CHANGED
@@ -8,9 +8,10 @@
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
- - [Multientity support)](#multientity-support)
14
+ - [Multientity support](#multientity-support)
14
15
  - [Metadata support](#metadata-support)
15
16
  - [NGSI LD data and metadata considerations](#ngsi-ld-data-and-metadata-considerations)
16
17
  - [Advice on Attribute definitions](#advice-on-attribute-definitions)
@@ -31,6 +32,7 @@
31
32
  - [Measurement transformation order](#measurement-transformation-order)
32
33
  - [Multientity measurement transformation support (`object_id`)](#multientity-measurement-transformation-support-object_id)
33
34
  - [Timestamp Processing](#timestamp-processing)
35
+ - [Multimeasure support](#multimeasure-support)
34
36
  - [Overriding global Context Broker host](#overriding-global-context-broker-host)
35
37
  - [Multitenancy, FIWARE Service and FIWARE ServicePath](#multitenancy-fiware-service-and-fiware-servicepath)
36
38
  - [Secured access to the Context Broker](#secured-access-to-the-context-broker)
@@ -133,9 +135,9 @@ parameters. The specific parameters that can be configured for a given config gr
133
135
  ### Devices
134
136
 
135
137
  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 `apiKey` matching or
137
- `type` matching (in the case `apiKey` matching fails). For instance, let's consider a situation in which a config group
138
- has been provisioned with `type=X`/`apiKey=111` and no other config group has been provisioned.
138
+ are identified by a `device_id`, and they are associated to an existing config group based in `apikey` matching. For
139
+ instance, let's consider a situation in which a config group has been provisioned with `type=X`/`apikey=111` and no
140
+ other config group has been provisioned.
139
141
 
140
142
  The IoT Agents offer a provisioning API where devices can be preregistered, so all the information about service and
141
143
  subservice mapping, security information and attribute configuration can be specified in a per device way instead of
@@ -143,7 +145,7 @@ relaying on the config group configuration. The specific parameters that can be
143
145
  described in the [Device datamodel](#device-datamodel) section.
144
146
 
145
147
  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 `apiKey` and `type` - the
148
+ process is known as autoprovisioning. The IoT Agent will create an empty device with the group `apikey` and `type` - the
147
149
  associated document created in database doesn't include config group parameters (in particular, `timestamp`,
148
150
  `explicitAttrs`, `active` or `attributes`, `static` and `lazy` attributes and commands). The IoT Agent will also create
149
151
  the entity in the Context Broker if it does not exist yet.
@@ -152,6 +154,13 @@ This behavior allows that autoprovisioned parameters can freely established modi
152
154
  creation using the provisioning API. However, note that if a device (autoprovisioned or not) doesn't have these
153
155
  parameters defined at device level in database, the parameters are inherit from config group parameters.
154
156
 
157
+ ### Uniqueness of groups and devices
158
+
159
+ Group service uniqueness is defined by the combination of: service, subservice and apikey
160
+
161
+ Device uniqueness is defined by the combination of: service, subservice, device_id and apikey. Note that several devices
162
+ with the same device_id are allowed in the same service and subservice as long as their apikeys are different.
163
+
155
164
  ## Special measures and attributes names
156
165
 
157
166
  In case of arriving measures with name `id` or `type`, they are automatically transformed to `measure_id` and
@@ -278,32 +287,76 @@ e.g.:
278
287
 
279
288
  ```json
280
289
  {
281
- "entity_type": "Lamp",
282
- "resource": "/iot/d",
283
- "protocol": "PDI-IoTA-UltraLight",
284
- ..etc
285
- "commands": [
286
- {"name": "on","type": "command"},
287
- {"name": "off","type": "command"}
288
- ],
289
- "attributes": [
290
- {"object_id": "s", "name": "state", "type":"Text"},
291
- {"object_id": "l", "name": "luminosity", "type":"Integer",
292
- "metadata":{
293
- "unitCode":{"type": "Text", "value" :"CAL"}
294
- }
290
+ "entity_type": "Lamp",
291
+ "resource": "/iot/d",
292
+ "protocol": "PDI-IoTA-UltraLight",
293
+ "commands": [
294
+ { "name": "on", "type": "command" },
295
+ { "name": "off", "type": "command" }
296
+ ],
297
+ "attributes": [
298
+ { "object_id": "s", "name": "state", "type": "Text" },
299
+ {
300
+ "object_id": "l",
301
+ "name": "luminosity",
302
+ "type": "Integer",
303
+ "metadata": {
304
+ "unitCode": { "type": "Text", "value": "CAL" }
305
+ }
295
306
  }
296
- ],
297
- "static_attributes": [
298
- {"name": "category", "type":"Text", "value": ["actuator","sensor"]},
299
- {"name": "controlledProperty", "type": "Text", "value": ["light"],
300
- "metadata":{
301
- "includes":{"type": "Text", "value" :["state", "luminosity"]},
302
- "alias":{"type": "Text", "value" :"lamp"}
307
+ ],
308
+ "static_attributes": [
309
+ { "name": "category", "type": "Text", "value": ["actuator", "sensor"] },
310
+ {
311
+ "name": "controlledProperty",
312
+ "type": "Text",
313
+ "value": ["light"],
314
+ "metadata": {
315
+ "includes": { "type": "Text", "value": ["state", "luminosity"] },
316
+ "alias": { "type": "Text", "value": "lamp" }
303
317
  }
304
- },
305
- ]
306
- }
318
+ }
319
+ ]
320
+ }
321
+ ```
322
+
323
+ Metadata could also has `expression` like attributes in order to expand it:
324
+
325
+ e.g.:
326
+
327
+ ```json
328
+ {
329
+ "entity_type": "Lamp",
330
+ "resource": "/iot/d",
331
+ "protocol": "PDI-IoTA-UltraLight",
332
+ "commands": [
333
+ { "name": "on", "type": "command" },
334
+ { "name": "off", "type": "command" }
335
+ ],
336
+ "attributes": [
337
+ { "object_id": "s", "name": "state", "type": "Text" },
338
+ {
339
+ "object_id": "l",
340
+ "name": "luminosity",
341
+ "type": "Integer",
342
+ "metadata": {
343
+ "unitCode": { "type": "Text", "value": "CAL" }
344
+ }
345
+ }
346
+ ],
347
+ "static_attributes": [
348
+ { "name": "category", "type": "Text", "value": ["actuator", "sensor"] },
349
+ {
350
+ "name": "controlledProperty",
351
+ "type": "Text",
352
+ "value": ["light"],
353
+ "metadata": {
354
+ "includes": { "type": "Text", "value": ["state", "luminosity"], "expression": "level / 100" },
355
+ "alias": { "type": "Text", "value": "lamp" }
356
+ }
357
+ }
358
+ ]
359
+ }
307
360
  ```
308
361
 
309
362
  ### NGSI-LD data and metadata considerations
@@ -413,6 +466,8 @@ mappings of the provision. If `explicitAttrs` is provided both at device and con
413
466
  precedence. Additionally `explicitAttrs` can be used to define which measures (identified by their attribute names, not
414
467
  by their object_id) defined in JSON/JEXL array will be propagated to NGSI interface.
415
468
 
469
+ Note that when `explicitAttrs` is an array or a JEXL expression resulting in to Array, if this array is empty then `TimeInstant` is not propaged to CB.
470
+
416
471
  The different possibilities are summarized below:
417
472
 
418
473
  Case 1 (default):
@@ -517,8 +572,8 @@ expression. In all cases the following data is available to all expressions:
517
572
  - `subservice`: device subservice
518
573
  - `staticAttributes`: static attributes defined in the device or config group
519
574
 
520
- Additionally, for attribute expressions (`expression`, `entity_name`) and `entityNameExp` measures are avaiable in the
521
- **context** used to evaluate them.
575
+ Additionally, for attribute expressions (`expression`, `entity_name`), `entityNameExp` and metadata expressions
576
+ (`expression`) measures are available in the **context** used to evaluate them.
522
577
 
523
578
  ### Examples of JEXL expressions
524
579
 
@@ -964,32 +1019,162 @@ Will now generate the following NGSI v2 payload:
964
1019
 
965
1020
  ## Timestamp Processing
966
1021
 
967
- The IOTA processes the entity attributes looking for a `TimeInstant` attribute. If one is found, for NGSI v2, then it
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.
1022
+ Timestamp processing done by IOTA is as follows:
970
1023
 
971
- If a `TimeInstant` arrives as measure but not follows [ISO_8601](https://en.wikipedia.org/wiki/ISO_8601) then measure is
972
- refused.
1024
+ - An attribute `TimeInstant` is added to updated entities
1025
+ - In the case of NGSI-v2, a `TimeInstant` metadata is added in each updated attribute. With NGSI-LD, the Standard
1026
+ `observedAt` property-of-a-property is used instead.
973
1027
 
974
1028
  Depending on the `timestamp` configuration and if the measure contains a value named `TimeInstant` with a correct value,
975
1029
  the IoTA behaviour is described in the following table:
976
1030
 
977
- | `timestamp` value | measure contains `TimeInstant` | Behaviour |
978
- | ----------------- | ------------------------------ | ------------------------------------------------------ |
979
- | true | Yes | TimeInstant and metadata updated with measure value |
980
- | true | No | TimeInstant and metadata updated with server timestamp |
981
- | false | Yes | TimeInstant and metadata updated with measure value |
982
- | false | No | TimeInstant and metadata updated with server timestamp |
983
- | Not defined | Yes | TimeInstant and metadata updated with measure value |
984
- | Not defined | No | TimeInstant and metadata updated with server timestamp |
1031
+ | `timestamp` conf value | measure contains `TimeInstant` | Behaviour |
1032
+ | ---------------------- | ------------------------------ | ------------------------------------------------------ |
1033
+ | true | Yes | TimeInstant and metadata updated with measure value |
1034
+ | true | No | TimeInstant and metadata updated with server timestamp |
1035
+ | false | Yes | TimeInstant and metadata updated with measure value |
1036
+ | false | No | TimeInstant and metadata updated with server timestamp |
1037
+ | Not defined | Yes | TimeInstant and metadata updated with measure value |
1038
+ | Not defined | No | TimeInstant and metadata updated with server timestamp |
985
1039
 
986
- The `timestamp` value used is:
1040
+ The `timestamp` conf value used is:
987
1041
 
988
1042
  - The one defined at device level
989
1043
  - The one defined at group level (if not defined at device level)
990
1044
  - The one defined at [IoTA configuration level](admin.md#timestamp) / `IOTA_TIMESTAMP` env var (if not defined at
991
1045
  group level or device level)
992
1046
 
1047
+ Some additional considerations to take into account:
1048
+
1049
+ - If there is an attribute which maps a measure to `TimeInstant` attribute (after
1050
+ [expression evaluation](#expression-language-support) if any is defined), then that value will be used as
1051
+ `TimeInstant, overwriting the above rules specified in "Behaviour" column. Note that an expression in the could be
1052
+ used in that mapping.
1053
+ - If the resulting `TimeInstant` not follows [ISO_8601](https://en.wikipedia.org/wiki/ISO_8601) (either from a direct
1054
+ measure of after a mapping, as described in the previous bullet) then it is refused (so a failover to server
1055
+ timestamp will take place).
1056
+
1057
+ ## Multimeasure support
1058
+
1059
+ A device could receive several measures at the same time.
1060
+
1061
+ For example:
1062
+
1063
+ ```json
1064
+ [
1065
+ {
1066
+ "vol": 0
1067
+ },
1068
+ {
1069
+ "vol": 1
1070
+ },
1071
+ {
1072
+ "vol": 2
1073
+ }
1074
+ ]
1075
+ ```
1076
+
1077
+ In this case a batch update (`POST /v2/op/update`) to CB will be generated with the following NGSI v2 payload:
1078
+
1079
+ ```json
1080
+ {
1081
+ "actionType": "append",
1082
+ "entities": [
1083
+ {
1084
+ "id": "ws",
1085
+ "type": "WeatherStation",
1086
+ "vol": {
1087
+ "type": "Number",
1088
+ "value": 0
1089
+ }
1090
+ },
1091
+ {
1092
+ "id": "ws",
1093
+ "type": "WeatherStation",
1094
+ "vol": {
1095
+ "type": "Number",
1096
+ "value": 1
1097
+ }
1098
+ },
1099
+ {
1100
+ "id": "ws",
1101
+ "type": "WeatherStation",
1102
+ "vol": {
1103
+ "type": "Number",
1104
+ "value": 1
1105
+ }
1106
+ }
1107
+ ]
1108
+ }
1109
+ ```
1110
+
1111
+ Moreover if a multimeasure contains TimeInstant attribute, then CB update is sorted by attribute TimeInstant:
1112
+
1113
+ For example:
1114
+
1115
+ ```json
1116
+ [
1117
+ {
1118
+ "vol": 0,
1119
+ "TimeInstant": "2024-04-10T10:15:00Z"
1120
+ },
1121
+ {
1122
+ "vol": 1,
1123
+ "TimeInstant": "2024-04-10T10:10:00Z"
1124
+ },
1125
+ {
1126
+ "vol": 2,
1127
+ "TimeInstant": "2024-04-10T10:05:00Z"
1128
+ }
1129
+ ]
1130
+ ```
1131
+
1132
+ In this case a batch update (`POST /v2/op/update`) to CB will be generated with the following NGSI v2 payload:
1133
+
1134
+ ```json
1135
+ {
1136
+ "actionType": "append",
1137
+ "entities": [
1138
+ {
1139
+ "id": "ws",
1140
+ "type": "WeatherStation",
1141
+ "vol": {
1142
+ "type": "Number",
1143
+ "value": 2
1144
+ },
1145
+ "TimeInstant": {
1146
+ "type": "DateTime",
1147
+ "value": "2024-04-10T10:05:00Z"
1148
+ }
1149
+ },
1150
+ {
1151
+ "id": "ws",
1152
+ "type": "WeatherStation",
1153
+ "vol": {
1154
+ "type": "Number",
1155
+ "value": 1
1156
+ },
1157
+ "TimeInstant": {
1158
+ "type": "DateTime",
1159
+ "value": "2024-04-10T10:10:00Z"
1160
+ }
1161
+ },
1162
+ {
1163
+ "id": "ws",
1164
+ "type": "WeatherStation",
1165
+ "vol": {
1166
+ "type": "Number",
1167
+ "value": 0
1168
+ },
1169
+ "TimeInstant": {
1170
+ "type": "DateTime",
1171
+ "value": "2024-04-10T10:15:00Z"
1172
+ }
1173
+ }
1174
+ ]
1175
+ }
1176
+ ```
1177
+
993
1178
  ## Overriding global Context Broker host
994
1179
 
995
1180
  **cbHost**: Context Broker host URL. This option can be used to override the global CB configuration for specific types
@@ -1,4 +1,4 @@
1
1
  mkdocs==1.2.4
2
2
  Pygments==2.15.0
3
3
  Markdown==3.3.4
4
- jinja2==3.1.3
4
+ jinja2==3.1.4
@@ -1,4 +1,4 @@
1
- ARG IMAGE_TAG=12.4-slim
1
+ ARG IMAGE_TAG=12.5-slim
2
2
  FROM debian:${IMAGE_TAG}
3
3
 
4
4
  ARG CLEAN_DEV_TOOLS
@@ -156,7 +156,8 @@ function processEnvironmentVariables() {
156
156
  'IOTA_FALLBACK_TENANT',
157
157
  'IOTA_FALLBACK_PATH',
158
158
  'IOTA_LD_SUPPORT_NULL',
159
- 'IOTA_LD_SUPPORT_DATASET_ID'
159
+ 'IOTA_LD_SUPPORT_DATASET_ID',
160
+ 'IOTA_EXPRESS_LIMIT'
160
161
  ];
161
162
  const iotamVariables = [
162
163
  'IOTA_IOTAM_URL',
@@ -468,6 +469,11 @@ function processEnvironmentVariables() {
468
469
  ? config.defaultEntityNameConjunction
469
470
  : ':';
470
471
  }
472
+ if (process.env.IOTA_EXPRESS_LIMIT) {
473
+ config.expressLimit = process.env.IOTA_EXPRESS_LIMIT;
474
+ } else {
475
+ config.expressLimit = config.expressLimit ? config.expressLimit : '1mb';
476
+ }
471
477
  }
472
478
 
473
479
  function setConfig(newConfig) {
@@ -485,6 +491,23 @@ function getConfig() {
485
491
  return config;
486
492
  }
487
493
 
494
+ function getConfigForTypeInformation() {
495
+ // Just return relevant configuration flags
496
+ // avoid to include server, authentication, mongodb, orion and iotamanger info
497
+ let conf = {
498
+ timestamp: config.timestamp,
499
+ defaultResource: config.defaultResource,
500
+ explicitAttrs: config.explicitAttrs,
501
+ pollingExpiration: config.pollingExpiration,
502
+ pollingDaemonFrequency: config.pollingDaemonFrequency,
503
+ multiCore: config.multiCore,
504
+ relaxTemplateValidation: config.relaxTemplateValidation,
505
+ defaultEntityNameConjunction: config.defaultEntityNameConjunction,
506
+ defaultType: config.defaultType
507
+ };
508
+ return conf;
509
+ }
510
+
488
511
  function setRegistry(newRegistry) {
489
512
  registry = newRegistry;
490
513
  }
@@ -541,6 +564,7 @@ function getSecurityService() {
541
564
 
542
565
  exports.setConfig = setConfig;
543
566
  exports.getConfig = getConfig;
567
+ exports.getConfigForTypeInformation = getConfigForTypeInformation;
544
568
  exports.setRegistry = setRegistry;
545
569
  exports.getRegistry = getRegistry;
546
570
  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 pair (Service, DeviceId) was found:' + device.id + ' and ' + JSON.stringify(device);
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
  }
@@ -176,7 +176,7 @@ function getByName(name, service, subservice, callback) {
176
176
  getByNameAndType(name, null, service, subservice, callback);
177
177
  }
178
178
 
179
- function update(device, callback) {
179
+ function update(previousDevice, device, callback) {
180
180
  registeredDevices[device.service][device.id] = deepClone(device);
181
181
  callback(null, device);
182
182
  }
@@ -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
- // Try without apikey: apikey will be added to device later
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
  }
@@ -297,9 +290,9 @@ function getByName(name, service, servicepath, callback) {
297
290
  *
298
291
  * @param {Object} device Device object with the new values to write.
299
292
  */
300
- function update(device, callback) {
293
+ function update(previousDevice, device, callback) {
301
294
  logger.debug(context, 'Storing updated values for device [%s]:\n%s', device.id, JSON.stringify(device, null, 4));
302
- getDevice(device.id, device.apikey, device.service, device.subservice, function (error, data) {
295
+ getDevice(device.id, previousDevice.apikey, device.service, device.subservice, function (error, data) {
303
296
  if (error) {
304
297
  callback(error);
305
298
  } else {
@@ -444,8 +444,8 @@ function unregisterDevice(id, apikey, service, subservice, callback) {
444
444
  });
445
445
  }
446
446
 
447
- function updateRegisterDevice(deviceObj, entityInfoUpdated, callback) {
448
- deviceHandler.updateRegisterDevice(deviceObj, entityInfoUpdated, callback);
447
+ function updateRegisterDevice(deviceObj, previousDevice, entityInfoUpdated, callback) {
448
+ deviceHandler.updateRegisterDevice(deviceObj, previousDevice, entityInfoUpdated, callback);
449
449
  }
450
450
 
451
451
  /**
@@ -518,7 +518,8 @@ function getDevice(deviceId, apikey, service, subservice, callback) {
518
518
  * @param {String} device JSON object contain the device to update.
519
519
  */
520
520
  function updateDevice(device, callback) {
521
- config.getRegistry().update(device, callback);
521
+ logger.debug(context, 'updateDevice %j', device);
522
+ config.getRegistry().update(device, device, callback);
522
523
  }
523
524
 
524
525
  /**
@@ -615,6 +616,7 @@ function findOrCreate(deviceId, apikey, group, callback) {
615
616
  } else if (error.name === 'DEVICE_NOT_FOUND') {
616
617
  const newDevice = {
617
618
  id: deviceId,
619
+ apikey: apikey,
618
620
  service: group.service,
619
621
  subservice: group.subservice,
620
622
  type: group.type
@@ -182,7 +182,7 @@ function updateEntityNgsiLD(deviceData, updatedDevice, callback) {
182
182
  *
183
183
  * @param {Object} deviceObj Object with all the device information (mandatory).
184
184
  */
185
- function updateRegisterDeviceNgsiLD(deviceObj, entityInfoUpdated, callback) {
185
+ function updateRegisterDeviceNgsiLD(deviceObj, previousDevice, entityInfoUpdated, callback) {
186
186
  if (!deviceObj.id || !deviceObj.type) {
187
187
  callback(new errors.MissingAttributes('Id or device missing', deviceObj));
188
188
  return;
@@ -280,7 +280,7 @@ function updateRegisterDeviceNgsiLD(deviceObj, entityInfoUpdated, callback) {
280
280
  apply(
281
281
  config.getRegistry().get,
282
282
  deviceObj.id,
283
- deviceObj.apikey,
283
+ previousDevice.apikey,
284
284
  deviceObj.service,
285
285
  deviceObj.subservice
286
286
  ),
@@ -289,7 +289,7 @@ function updateRegisterDeviceNgsiLD(deviceObj, entityInfoUpdated, callback) {
289
289
  apply(combineWithNewDevice, deviceObj),
290
290
  apply(registrationUtils.sendRegistrations, false),
291
291
  apply(registrationUtils.processContextRegistration, deviceObj),
292
- config.getRegistry().update
292
+ apply(config.getRegistry().update, previousDevice)
293
293
  ],
294
294
  callback
295
295
  );
@@ -299,7 +299,7 @@ function updateRegisterDeviceNgsiLD(deviceObj, entityInfoUpdated, callback) {
299
299
  apply(
300
300
  config.getRegistry().get,
301
301
  deviceObj.id,
302
- deviceObj.apikey,
302
+ previousDevice.apikey,
303
303
  deviceObj.service,
304
304
  deviceObj.subservice
305
305
  ),
@@ -308,7 +308,7 @@ function updateRegisterDeviceNgsiLD(deviceObj, entityInfoUpdated, callback) {
308
308
  apply(combineWithNewDevice, deviceObj),
309
309
  apply(registrationUtils.sendRegistrations, false),
310
310
  apply(registrationUtils.processContextRegistration, deviceObj),
311
- config.getRegistry().update
311
+ apply(config.getRegistry().update, previousDevice)
312
312
  ],
313
313
  callback
314
314
  );
@@ -38,11 +38,11 @@ const deviceHandlerV2 = require('./devices-NGSI-v2');
38
38
  *
39
39
  * @param {Object} deviceObj Object with all the device information (mandatory).
40
40
  */
41
- function updateRegisterDeviceNgsiMixed(deviceObj, entityInfoUpdated, callback) {
41
+ function updateRegisterDeviceNgsiMixed(deviceObj, previousDevice, entityInfoUpdated, callback) {
42
42
  if (config.checkNgsiLD(deviceObj)) {
43
- deviceHandlerLD.updateRegisterDevice(deviceObj, entityInfoUpdated, callback);
43
+ deviceHandlerLD.updateRegisterDevice(deviceObj, previousDevice, entityInfoUpdated, callback);
44
44
  } else {
45
- deviceHandlerV2.updateRegisterDevice(deviceObj, entityInfoUpdated, callback);
45
+ deviceHandlerV2.updateRegisterDevice(deviceObj, previousDevice, entityInfoUpdated, callback);
46
46
  }
47
47
  }
48
48
 
@@ -247,7 +247,7 @@ function updateEntityNgsi2(deviceData, updatedDevice, callback) {
247
247
  *
248
248
  * @param {Object} deviceObj Object with all the device information (mandatory).
249
249
  */
250
- function updateRegisterDeviceNgsi2(deviceObj, entityInfoUpdated, callback) {
250
+ function updateRegisterDeviceNgsi2(deviceObj, previousDevice, entityInfoUpdated, callback) {
251
251
  if (!deviceObj.id || !deviceObj.type) {
252
252
  callback(new errors.MissingAttributes('Id or device missing', deviceObj));
253
253
  return;
@@ -352,7 +352,7 @@ function updateRegisterDeviceNgsi2(deviceObj, entityInfoUpdated, callback) {
352
352
  apply(
353
353
  config.getRegistry().get,
354
354
  deviceObj.id,
355
- deviceObj.apikey,
355
+ previousDevice.apikey, // it could be updated
356
356
  deviceObj.service,
357
357
  deviceObj.subservice
358
358
  ),
@@ -361,7 +361,7 @@ function updateRegisterDeviceNgsi2(deviceObj, entityInfoUpdated, callback) {
361
361
  apply(combineWithNewDevice, deviceObj),
362
362
  apply(registrationUtils.sendRegistrations, false),
363
363
  apply(registrationUtils.processContextRegistration, deviceObj),
364
- config.getRegistry().update
364
+ apply(config.getRegistry().update, previousDevice)
365
365
  ],
366
366
  callback
367
367
  );
@@ -371,7 +371,7 @@ function updateRegisterDeviceNgsi2(deviceObj, entityInfoUpdated, callback) {
371
371
  apply(
372
372
  config.getRegistry().get,
373
373
  deviceObj.id,
374
- deviceObj.apikey,
374
+ previousDevice.apikey,
375
375
  deviceObj.service,
376
376
  deviceObj.subservice
377
377
  ),
@@ -380,7 +380,7 @@ function updateRegisterDeviceNgsi2(deviceObj, entityInfoUpdated, callback) {
380
380
  apply(combineWithNewDevice, deviceObj),
381
381
  apply(registrationUtils.sendRegistrations, false),
382
382
  apply(registrationUtils.processContextRegistration, deviceObj),
383
- config.getRegistry().update
383
+ apply(config.getRegistry().update, previousDevice)
384
384
  ],
385
385
  callback
386
386
  );