iotagent-node-lib 4.6.0 → 4.8.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 (70) hide show
  1. package/.github/workflows/ci.yml +2 -3
  2. package/CHANGES_NEXT_RELEASE +1 -0
  3. package/Changelog +20 -0
  4. package/config.js +3 -1
  5. package/doc/README.md +1 -0
  6. package/doc/admin.md +39 -5
  7. package/doc/api.md +74 -9
  8. package/doc/devel/northboundinteractions.md +122 -15
  9. package/doc/models/models.md +260 -0
  10. package/doc/requirements.txt +1 -1
  11. package/docker/Mosquitto/Dockerfile +1 -1
  12. package/lib/commonConfig.js +21 -2
  13. package/lib/fiware-iotagent-lib.js +15 -11
  14. package/lib/jexlTranformsMap.js +182 -35
  15. package/lib/model/Command.js +11 -2
  16. package/lib/model/Device.js +8 -3
  17. package/lib/model/Group.js +5 -3
  18. package/lib/model/dbConn.js +53 -112
  19. package/lib/services/commands/commandRegistryMongoDB.js +130 -76
  20. package/lib/services/commands/commandService.js +3 -3
  21. package/lib/services/common/iotManagerService.js +3 -1
  22. package/lib/services/devices/deviceRegistryMemory.js +36 -0
  23. package/lib/services/devices/deviceRegistryMongoDB.js +161 -88
  24. package/lib/services/devices/deviceService.js +44 -5
  25. package/lib/services/devices/devices-NGSI-v2.js +6 -1
  26. package/lib/services/groups/groupRegistryMongoDB.js +120 -83
  27. package/lib/services/ngsi/entities-NGSI-v2.js +14 -1
  28. package/lib/services/ngsi/ngsiService.js +32 -1
  29. package/lib/services/northBound/contextServer-NGSI-v2.js +12 -3
  30. package/lib/services/northBound/contextServer.js +2 -1
  31. package/lib/services/northBound/deviceProvisioningServer.js +12 -3
  32. package/lib/services/northBound/northboundServer.js +1 -0
  33. package/lib/templates/createDevice.json +4 -0
  34. package/lib/templates/updateDevice.json +16 -0
  35. package/lib/templates/updateDeviceLax.json +12 -0
  36. package/package.json +4 -4
  37. package/test/functional/config-test.js +3 -2
  38. package/test/functional/testUtils.js +15 -4
  39. package/test/unit/examples/groupProvisioningRequests/provisionFullGroup.json +1 -0
  40. package/test/unit/expressions/jexlExpression-test.js +165 -1
  41. package/test/unit/general/config-multi-core-test.js +1 -2
  42. package/test/unit/general/contextBrokerKeystoneSecurityAccess-test.js +5 -4
  43. package/test/unit/general/deviceService-test.js +6 -5
  44. package/test/unit/memoryRegistry/deviceRegistryMemory_test.js +6 -5
  45. package/test/unit/mongodb/mongodb-connectionoptions-test.js +7 -39
  46. package/test/unit/ngsi-ld/expressions/jexlBasedTransformations-test.js +1 -2
  47. package/test/unit/ngsi-ld/general/config-jsonld-contexts-test.js +1 -2
  48. package/test/unit/ngsi-mixed/provisioning/ngsi-versioning-test.js +8 -5
  49. package/test/unit/ngsiv2/expressions/jexlBasedTransformations-test.js +42 -41
  50. package/test/unit/ngsiv2/general/contextBrokerOAuthSecurityAccess-test.js +11 -10
  51. package/test/unit/ngsiv2/general/deviceService-test.js +6 -5
  52. package/test/unit/ngsiv2/general/https-support-test.js +1 -1
  53. package/test/unit/ngsiv2/lazyAndCommands/active-devices-attribute-update-test.js +4 -3
  54. package/test/unit/ngsiv2/lazyAndCommands/command-test.js +6 -5
  55. package/test/unit/ngsiv2/lazyAndCommands/lazy-devices-test.js +17 -16
  56. package/test/unit/ngsiv2/lazyAndCommands/polling-commands-test.js +10 -18
  57. package/test/unit/ngsiv2/ngsiService/active-devices-test.js +21 -20
  58. package/test/unit/ngsiv2/ngsiService/staticAttributes-test.js +8 -7
  59. package/test/unit/ngsiv2/plugins/alias-plugin_test.js +12 -11
  60. package/test/unit/ngsiv2/plugins/custom-plugin_test.js +3 -2
  61. package/test/unit/ngsiv2/plugins/multientity-plugin_test.js +28 -27
  62. package/test/unit/ngsiv2/provisioning/device-group-api-test.js +6 -4
  63. package/test/unit/ngsiv2/provisioning/device-provisioning-api_test.js +12 -11
  64. package/test/unit/ngsiv2/provisioning/device-provisioning-configGroup-api_test.js +11 -10
  65. package/test/unit/ngsiv2/provisioning/device-registration_test.js +5 -4
  66. package/test/unit/ngsiv2/provisioning/listProvisionedDevices-test.js +6 -5
  67. package/test/unit/ngsiv2/provisioning/provisionDeviceMultientity-test.js +1 -1
  68. package/test/unit/ngsiv2/provisioning/removeProvisionedDevice-test.js +5 -4
  69. package/test/unit/ngsiv2/provisioning/updateProvisionedDevices-test.js +8 -7
  70. package/test/unit/ngsiv2/ngsiService/subscriptions-test.js +0 -326
@@ -45,13 +45,12 @@ jobs:
45
45
  runs-on: ubuntu-latest
46
46
  services:
47
47
  mongodb:
48
- image: mongo:6.0
48
+ image: mongo:8.0
49
49
  ports:
50
50
  - 27017:27017
51
51
  strategy:
52
52
  matrix:
53
53
  node-version:
54
- - 14.x
55
54
  - 16.x
56
55
  - 18.x
57
56
  steps:
@@ -72,7 +71,7 @@ jobs:
72
71
  needs: unit-test
73
72
  services:
74
73
  mongodb:
75
- image: mongo:6.0
74
+ image: mongo:8.0
76
75
  ports:
77
76
  - 27017:27017
78
77
  steps:
@@ -0,0 +1 @@
1
+
package/Changelog ADDED
@@ -0,0 +1,20 @@
1
+ 4.8.0 (May 23rd, 2025)
2
+
3
+ - Add: notification-based commands (#1455)
4
+ - Add: allow define headers in device commands (iotagent-json#873)
5
+ - Add: index for Device model based on {service: 1, subservice: 1, id: 1, apikey: 1} (#1576)
6
+ - Fix: Duplicated Devices when burst measures to non provisioned Device (iotagent-json#865)
7
+ - Fix: modified JEXL transformations (toisostring, gettime, parseint, etc.) to return null instead of NaN when some unexpected situation occurs (#1701)
8
+
9
+ 4.7.0 (February 3rd, 2025)
10
+
11
+ - Add: store (and recover) previous jexlctxt and make available in current jexlctxt (#1690)
12
+ - Add: option to force to use CB flow control with new API field useCBflowControl at group and device device level (#1420)
13
+ - Add: useCBflowControl config setting (IOTA_CB_FLOW_CONTROL env var) to set CB flow control behaviour at instance level (#1420)
14
+ - Add: allow remove last measure in device
15
+ - Add: store last measure in device (by id, apikey, service and subservice) and new API field storeLastMeasure at group and device levels (#1669)
16
+ - Add: storeLastMeasure config setting (IOTA_STORE_LAST_MEASURE env var) to set default store last measure behaviour at instance level (#1669)
17
+ - Fix: set polling and transport for autoprovisioned devices
18
+ - Upgrade express dep from 4.19.2 to 4.21.2
19
+ - Upgrade mongodb devdep from 4.17.1 to 4.17.2
20
+ - Upgrade mongoose dep from 5.13.20 to 8.9.5 (solving vulnerabilies CVE-2024-53900 and CVE-2025-23061) (#1674)
package/config.js CHANGED
@@ -77,7 +77,9 @@ var config = {
77
77
  providerUrl: 'http://192.168.56.1:4041',
78
78
  deviceRegistrationDuration: 'P1M',
79
79
  defaultType: 'Thing',
80
- expressLimit: '1Mb'
80
+ expressLimit: '1Mb',
81
+ useCBflowControl: false,
82
+ storeLastMeasure: false
81
83
  };
82
84
 
83
85
  module.exports = config;
package/doc/README.md CHANGED
@@ -7,6 +7,7 @@
7
7
  - [Administration manual](admin.md)
8
8
  - [Deprecated features](deprecated.md)
9
9
  - [Roadmap](roadmap.md)
10
+ - [Data models](models/models.md)
10
11
 
11
12
  ## Development documentation
12
13
 
package/doc/admin.md CHANGED
@@ -125,9 +125,9 @@ allowing the computer to interpret the rest of the data with more clarity and de
125
125
  ```
126
126
 
127
127
  Under mixed mode, **NGSI v2** payloads are used for context broker communications by default, but this payload may also
128
- be switched to **NGSI LD** at group or device provisioning time using the `ngsiVersion` field in the
129
- provisioning API. The `ngsiVersion` field switch may be added at either group or device level, with the device level
130
- overriding the group setting.
128
+ be switched to **NGSI LD** at group or device provisioning time using the `ngsiVersion` field in the provisioning API.
129
+ The `ngsiVersion` field switch may be added at either group or device level, with the device level overriding the group
130
+ setting.
131
131
 
132
132
  #### `server`
133
133
 
@@ -306,7 +306,8 @@ added `agentPath`:
306
306
 
307
307
  #### `types`
308
308
 
309
- This parameter includes additional groups configuration as described into the [Config group API](api.md#config-group-api) section.
309
+ This parameter includes additional groups configuration as described into the
310
+ [Config group API](api.md#config-group-api) section.
310
311
 
311
312
  #### `service`
312
313
 
@@ -415,7 +416,38 @@ IotAgents, as all Express applications that use the body-parser middleware, have
415
416
  size that the application will handle. This default limit for ioiotagnets are 1Mb. So, if your IotAgent receives a
416
417
  request with a body that exceeds this limit, the application will throw a “Error: Request entity too large”.
417
418
 
418
- The 1Mb default can be changed setting the `expressLimit` configuration parameter (or equivalente `IOTA_EXPRESS_LIMIT` environment variable).
419
+ The 1Mb default can be changed setting the `expressLimit` configuration parameter (or equivalente `IOTA_EXPRESS_LIMIT`
420
+ environment variable).
421
+
422
+ #### `storeLastMeasure`
423
+
424
+ If this flag is activated, last measure arrived to Device IoTAgent without be processed will be stored in Device under
425
+ `lastMeasure` field (composed of sub-fields `timestamp` and `measure` for the measure itself, in multi-measure format).
426
+ This flag is overwritten by `storeLastMeasure` flag in group or device. This flag is disabled by default.
427
+
428
+ For example in a device document stored in MongoDB will be extended with a subdocument named lastMeasure like this:
429
+
430
+ ```json
431
+ {
432
+ "lastMeasure": {
433
+ "timestamp": "2025-01-09T10:35:33.079Z",
434
+ "measure": [
435
+ [
436
+ {
437
+ "name": "level",
438
+ "type": "Text",
439
+ "value": 33
440
+ }
441
+ ]
442
+ ]
443
+ }
444
+ }
445
+ ```
446
+
447
+ #### `useCBflowControl`
448
+
449
+ If this flag is activated, when iotAgent invokes Context Broker will use [flowControl option](https://github.com/telefonicaid/fiware-orion/blob/master/doc/manuals/admin/perf_tuning.md#updates-flow-control-mechanism). This flag is overwritten by
450
+ `useCBflowControl` flag in group or device. This flag is disabled by default.
419
451
 
420
452
  ### Configuration using environment variables
421
453
 
@@ -479,6 +511,8 @@ overrides.
479
511
  | IOTA_DEFAULT_ENTITY_NAME_CONJUNCTION | `defaultEntityNameConjunction` |
480
512
  | IOTA_RELAX_TEMPLATE_VALIDATION | `relaxTemplateValidation` |
481
513
  | IOTA_EXPRESS_LIMIT | `expressLimit` |
514
+ | IOTA_STORE_LAST_MEASURE | `storeLastMeasure` |
515
+ | IOTA_CB_FLOW_CONTROL | `useCBflowControl` |
482
516
 
483
517
  Note:
484
518
 
package/doc/api.md CHANGED
@@ -12,6 +12,7 @@
12
12
  - [Special measures and attributes names](#special-measures-and-attributes-names)
13
13
  - [Device to NGSI Mapping](#device-to-ngsi-mapping)
14
14
  - [Device autoprovision and entity creation](#device-autoprovision-and-entity-creation)
15
+ - [Entity Name expression support](#entity-name-expression-support)
15
16
  - [Multientity support](#multientity-support)
16
17
  - [Metadata support](#metadata-support)
17
18
  - [NGSI LD data and metadata considerations](#ngsi-ld-data-and-metadata-considerations)
@@ -164,7 +165,8 @@ parameters defined at device level in database, the parameters are inherit from
164
165
 
165
166
  ### Uniqueness of groups and devices
166
167
 
167
- Group uniqueness is defined by the combination of: service, subservice, resource and apikey
168
+ Group uniqueness is defined by the combination of: resource and apikey. This is so because given a measure (identified
169
+ by an apikey) there is no way to identify group related if apikey is not unique for all services and subservices.
168
170
 
169
171
  Device uniqueness is defined by the combination of: service, subservice, device_id and apikey. Note that several devices
170
172
  with the same device_id are allowed in the same service and subservice as long as their apikeys are different.
@@ -187,9 +189,9 @@ configured for a single device in the device provisioning, or it can be defined
187
189
 
188
190
  The entity type should be defined both in the group and in the device, but the entity name (entity ID) is not defined in
189
191
  the group. In that case, if there is no a existing device the same device ID, the entity name of the device generated
190
- will be a concatenation of the entity type and the device ID (I.E: `entityType:device_id`). It is possible to define the
191
- entity name as an expression, using the [Expression Language](#expression-language-support) through the `entityNameExp`
192
- attribute in the group.
192
+ will be a concatenation of the entity type and the device ID (I.E: `entityType:device_id`). If you need to generate the
193
+ entity name differently, it is possible to define an expression to generate it, using the parameter `entityNameExp` in
194
+ the group as described in the [Entity Name expression support](#entity-name-expression-support) section.
193
195
 
194
196
  It is also possible to configure how each of the measures obtained from the device is mapped to different attributes.
195
197
  The name and type of the attribute is configured by the user (globally for all the devices in the group or in a per
@@ -255,6 +257,9 @@ Additionally for commands (which are attributes of type `command`) the following
255
257
  particular IOTAs documentation for allowed values of this field in each case.
256
258
  - **contentType**: `content-type` header used when send command by HTTP transport (ignored in other kinds of
257
259
  transports)
260
+ - **headers**: extra customer headers used when send command by HTTP transport (ignored in other kinds of
261
+ transports)
262
+ Check full detail of these fields in [comand-transformations](https://github.com/telefonicaid/iotagent-json/blob/master/docs/usermanual.md#commands-transformations)
258
263
 
259
264
  Note that, when information coming from devices, this means measures, are not defined neither in the group, nor in the
260
265
  device, the IoT agent will store that information into the destination entity using the same attribute name than the
@@ -271,6 +276,59 @@ If for any reason you need the entity at CB before the first measure of the corr
271
276
  IOTAgent, you can create it in advance using the Context Broker
272
277
  [NGSI v2 API](https://github.com/telefonicaid/fiware-orion/blob/master/doc/manuals/orion-api.md).
273
278
 
279
+ ## Entity Name expression support
280
+
281
+ By default, the entity name used to persist the device measures in the Context Broker can be defined in the device
282
+ provisioning or, if the device is autoprovisioned, it is generated by the IoT Agent as a concatenation of the entity
283
+ type and the device ID. If you need to generate the entity name differently, it is possible to define an expression to
284
+ generate it, using the [Expression Language](#expression-language-support) through the `entityNameExp` field in the
285
+ group.
286
+
287
+ With this feature, the entity name can be generated dynamically based not only on the device ID and entity type, but
288
+ also on the measures reported by the device or any other context information. The `entityNameExp` field is only
289
+ available at the group level. **Important**: when using `entityNameExp`, the `entity_name` field in the device
290
+ provisioning is ignored. This means that the entity name used to store the device information in the Context Broker is
291
+ always generated by the `entityNameExp` expression. If you need to explicitly define the entity name for a particular
292
+ device, you can include a particular condition in the `entityNameExp` expression to handle that case (e.g.
293
+ `id == 'myDevice' ? 'myEntity' : entityType + ':' + id`).
294
+
295
+ The following example shows how to define an entity name expression:
296
+
297
+ ```json
298
+ {
299
+ "services": [
300
+ {
301
+ "resource": "/json",
302
+ "apikey": "801230BJKL23Y9090DSFL123HJK09H324HV8732",
303
+ "entity_type": "TemperatureSensor",
304
+ "entityNameExp": "id + '__' + sn",
305
+ "attributes": [
306
+ {
307
+ "object_id": "t",
308
+ "name": "temperature",
309
+ "type": "Number"
310
+ },
311
+ {
312
+ "object_id": "sn",
313
+ "name": "serialNumber",
314
+ "type": "Text"
315
+ }
316
+ ]
317
+ }
318
+ ]
319
+ }
320
+ ```
321
+
322
+ As defined above, the `entityNameExp` is `id + '__' + sn` and it will generate the entity name by concatenating the
323
+ device ID and the serial number reported by the device. For example, for a given measure with `id` equal to `dev123` and
324
+ `sn` equal to `ABCDEF`, the resulting entity name will be `dev123__ABCDEF`.
325
+
326
+ Note that, when using `entityNameExp`, the `entity_name` of the device provisioning is set to the result of the
327
+ expression the first time the device is created. If the expression is modified later, the `entity_name` of the device
328
+ provisioning will not be updated, but the value used to persist the device measures in the Context Broker will be the
329
+ result of the new expression. This can lead to a situation where the `entity_name` of the device provisioning and the
330
+ entity name used in the Context Broker are different.
331
+
274
332
  ## Multientity support
275
333
 
276
334
  The IOTA is able to persists measures coming from a single device to more than one entity, declaring the target entities
@@ -525,8 +583,8 @@ Case 5:
525
583
 
526
584
  depending on the JEXL expression evaluation:
527
585
 
528
- - If it evaluates to `true` every measure will be propagated to NGSI interface (as in case 1)
529
- - If it evaluates to `false` just measures defined in active, static (plus conditionally TimeInstant) will be
586
+ - If it evaluates to `false` every measure will be propagated to NGSI interface (as in case 1)
587
+ - If it evaluates to `true` just measures defined in active, static (plus conditionally TimeInstant) will be
530
588
  propagated to NGSI interface (as in case 2)
531
589
  - If it evaluates to an array just measures defined in the array (identified by their attribute names, not by their
532
590
  object_id) will be will be propagated to NGSI interface (as in case 3)
@@ -555,7 +613,8 @@ of expression in the IoT Agent are:
555
613
  - [Metadata](#expression-support-in-metadata)
556
614
  - Commands payload transformation (push and pull).
557
615
  - Auto provisioned devices entity name. It is configured at config Group level by setting the `entityNameExp`
558
- parameter. It defines an expression to generate the Entity Name for autoprovisioned devices.
616
+ parameter. It defines an expression to generate the Entity Name for autoprovisioned devices. More information in the
617
+ [Entity Name expression support](#entity-name-expression-support) section.
559
618
  - Dynamic `endpoint` definition. Configured at device level, it defines where the device listen for push http
560
619
  commands. It can be either a static value or an expression.
561
620
 
@@ -568,6 +627,7 @@ expression. In all cases the following data is available to all expressions:
568
627
  - `service`: device service (`Fiware-Service`)
569
628
  - `subservice`: device subservice (`Fiware-ServicePath`)
570
629
  - `staticAttributes`: static attributes defined in the device or config group
630
+ - `oldCtxt`: previous JEXL context (related to last processed measure)
571
631
 
572
632
  Additionally, for attribute expressions (`expression`, `entity_name`), `entityNameExp` and metadata expressions
573
633
  (`expression`) the following is available in the **context** used to evalute:
@@ -1715,13 +1775,15 @@ Config group is represented by a JSON object with the following fields:
1715
1775
  | `static_attributes` | ✓ | | | this attributes will be added to all the entities of this group 'as is', additional `metadata` is optional. |
1716
1776
  | `internal_attributes` | ✓ | | | optional section with free format, to allow specific IoT Agents to store information along with the devices in the Device Registry. |
1717
1777
  | `explicitAttrs` | ✓ | string or bool | ✓ | optional field to support selective ignore of measures so that IOTA doesn’t progress. See details in [specific section](#explicitly-defined-attributes-explicitattrs) |
1718
- | `entityNameExp` | ✓ | string | | optional field to allow use expressions to define entity name, instead default name, based on the device ID and the entity type (i.e. `<device_id>:<entity_type>`) |
1778
+ | `entityNameExp` | ✓ | string | | optional field to allow use expressions to define entity name, instead default name, based on the device ID and the entity type (i.e. `<device_id>:<entity_type>`). More information in [specific section](#entity-name-expression-entitynameexp) |
1719
1779
  | `ngsiVersion` | ✓ | string | | optional string value used in mixed mode to switch between **NGSI-v2** and **NGSI-LD** payloads. Possible values are: `v2` or `ld`. The default is `v2`. When not running in mixed mode, this field is ignored. |
1720
1780
  | `defaultEntityNameConjunction` | ✓ | string | | optional string value to set default conjunction string used to compose a default `entity_name` when is not provided at device provisioning time. |
1721
1781
  | `autoprovision` | ✓ | bool | ✓? | optional boolean: If `false`, autoprovisioned devices (i.e. devices that are not created with an explicit provision operation but when the first measure arrives) are not allowed in this group. Default (in the case of omitting the field) is `true`. |
1722
1782
  | `payloadType` | ✓ | string | | optional string value used to switch between **IoTAgent**, **NGSI-v2** and **NGSI-LD** measure payloads types. Possible values are: `iotagent`, `ngsiv2` or `ngsild`. The default is `iotagent`. |
1723
1783
  | `transport` | ✓ | `string` | | Transport protocol used by the group of devices to send updates, for the IoT Agents with multiple transport protocols. |
1724
1784
  | `endpoint` | ✓ | `string` | | Endpoint where the group of device is going to receive commands, if any. |
1785
+ | `storeLastMeasure` | ✓ | `boolean` | | Store in device last measure received. See more info [in this section](admin.md#storelastmeasure). False by default |
1786
+ | `useCBflowControl` | ✓ | `boolean` | | Use Context Broker flow control. See more info [in this section](admin.md#useCBflowControl). False by default |
1725
1787
 
1726
1788
  ### Config group operations
1727
1789
 
@@ -1942,6 +2004,9 @@ the API resource fields and the same fields in the database model.
1942
2004
  | `explicitAttrs` | ✓ | `boolean` | ✓ | Field to support selective ignore of measures so that IOTA doesn’t progress. See details in [specific section](#explicitly-defined-attributes-explicitattrs) |
1943
2005
  | `ngsiVersion` | ✓ | `string` | | string value used in mixed mode to switch between **NGSI-v2** and **NGSI-LD** payloads. The default is `v2`. When not running in mixed mode, this field is ignored. |
1944
2006
  | `payloadType` | ✓ | `string` | | optional string value used to switch between **IoTAgent**, **NGSI-v2** and **NGSI-LD** measure payloads types. Possible values are: `iotagent`, `ngsiv2` or `ngsild`. The default is `iotagent`. |
2007
+ | `storeLastMeasure` | ✓ | `boolean` | | Store in device last measure received. Useful just for debugging purpose. See more info [in this section](admin.md#storelastmeasure). False by default. |
2008
+ | `lastMeasure` | ✓ | `object` | | last measure stored on device when `storeLastMeasure` is enabled. See more info [in this section](admin.md#storelastmeasure). This field can be cleared using `{}` in a device update request. In that case, `lastMeasure` is removed from device (until a next measure is received and `lastMesuare` gets created again). |
2009
+ | `useCBflowControl` | ✓ | `boolean` | | Use Context Broker flow control. See more info [in this section](admin.md#useCBflowControl). False by default. |
1945
2010
 
1946
2011
  ### Device operations
1947
2012
 
@@ -2398,4 +2463,4 @@ updateEntityRequestsError 5
2398
2463
  [18]:
2399
2464
  https://czosel.github.io/jexl-playground/#/?context=%7B%0A%20%20%22value%22%20%3A%206%2C%0A%20%20%22ts%22%3A%201637245214901%2C%0A%20%22name%22%3A%20%22DevId629%22%2C%0A%20%22object%22%3A%7Bname%3A%20%22John%22%2C%20surname%3A%20%22Doe%22%7D%2C%0A%20%20%22array%22%3A%5B1%2C3%5D%0A%7D&input=name%7Csubstr(0%2Cname%7CindexOf(%22e%22)%2B1)&transforms=%7B%0A%20%20%20%20jsonparse%3A%20(str)%20%3D%3E%20JSON.parse(str)%2C%0A%20%20%20%20jsonstringify%3A%20(obj)%20%3D%3E%20JSON.stringify(obj)%2C%0A%20%20%20%20indexOf%3A%20(val%2C%20char)%20%3D%3E%20String(val).indexOf(char)%2C%0A%20%20%20%20length%3A%20(val)%20%3D%3E%20String(val).length%2C%0A%20%20%20%20trim%3A%20(val)%20%3D%3E%20String(val).trim()%2C%0A%20%20%20%20substr%3A%20(val%2C%20int1%2C%20int2)%20%3D%3E%20String(val).substr(int1%2C%20int2)%2C%0A%20%20%20%20addreduce%3A%20(arr)%20%3D%3E%20arr.reduce((i%2C%20v)%20%3D%3E%20i%20%2B%20v)%2C%0A%20%20%20%20lengtharray%3A%20(arr)%20%3D%3E%20arr.length%2C%0A%20%20%20%20typeof%3A%20(val)%20%3D%3E%20typeof%20val%2C%0A%20%20%20%20isarray%3A%20(arr)%20%3D%3E%20Array.isArray(arr)%2C%0A%20%20%20%20isnan%3A%20(val)%20%3D%3E%20isNaN(val)%2C%0A%20%20%20%20parseint%3A%20(val)%20%3D%3E%20parseInt(val)%2C%0A%20%20%20%20parsefloat%3A%20(val)%20%3D%3E%20parseFloat(val)%2C%0A%20%20%20%20toisodate%3A%20(val)%20%3D%3E%20new%20Date(val).toISOString()%2C%0A%20%20%20%20timeoffset%3A%20(isostr)%20%3D%3E%20new%20Date(isostr).getTimezoneOffset()%2C%0A%20%20%20%20tostring%3A%20(val)%20%3D%3E%20val.toString()%2C%0A%20%20%20%20urlencode%3A%20(val)%20%3D%3E%20encodeURI(val)%2C%0A%20%20%20%20urldecode%3A%20(val)%20%3D%3E%20decodeURI(val)%2C%0A%20%20%20%20replacestr%3A%20(str%2C%20from%2C%20to)%20%3D%3E%20str.replace(from%2C%20to)%2C%0A%20%20%20%20replaceregexp%3A%20(str%2C%20reg%2C%20to)%20%3D%3E%20str.replace(new%20RegExp(reg)%2C%20to)%2C%0A%20%20%20%20replaceallstr%3A%20(str%2C%20from%2C%20to)%20%3D%3E%20str.replaceAll(from%2C%20to)%2C%0A%20%20%20%20replaceallregexp%3A%20(str%2C%20reg%2C%20to)%20%3D%3E%20str.replaceAll(new%20RegExp(reg%2C%20'g')%2C%20to)%2C%0A%20%20%20%20split%3A%20(str%2C%20ch)%20%3D%3E%20str.split(ch)%2C%0A%20%20%20%20mapper%3A%20(val%2C%20values%2C%20choices)%20%3D%3E%20choices%5Bvalues.findIndex((target)%20%3D%3E%20target%20%3D%3D%3D%20val)%5D%2C%0A%20%20%20%20thmapper%3A%20(val%2C%20values%2C%20choices)%20%3D%3E%0A%20%20%20%20%20%20%20%20choices%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20values.reduce((acc%2C%20curr%2C%20i)%20%3D%3E%20(acc%20%3D%3D%3D%200%20%7C%7C%20acc%20%3F%20acc%20%3A%20val%20%3C%3D%20curr%20%3F%20(acc%20%3D%20i)%20%3A%20(acc%20%3D%20null))%2C%20null)%0A%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20bitwisemask%3A%20(i%2C%20mask%2C%20op%2C%20shf)%20%3D%3E%0A%20%20%20%20%20%20%20%20(op%20%3D%3D%3D%20'%26'%20%3F%20parseInt(i)%20%26%20mask%20%3A%20op%20%3D%3D%3D%20'%7C'%20%3F%20parseInt(i)%20%7C%20mask%20%3A%20op%20%3D%3D%3D%20'%5E'%20%3F%20parseInt(i)%20%5E%20mask%20%3A%20i)%20%3E%3E%0A%20%20%20%20%20%20%20%20shf%2C%0A%20%20%20%20slice%3A%20(arr%2C%20init%2C%20end)%20%3D%3E%20arr.slice(init%2C%20end)%0A%7D
2400
2465
  [99]:
2401
- https://czosel.github.io/jexl-playground/#/?context=%7B%0A%20%20%22text%22%20%3A%20%22%20%20foobar%7B%7D%20%20%22%0A%7D&input=text%20%7C%20replacestr(%22foo%22%2C%22FOO%22)%7Ctrim%7Curlencode&transforms=%7B%0A%20%20%20%20jsonparse%3A%20(str)%20%3D%3E%20JSON.parse(str)%2C%0A%20%20%20%20jsonstringify%3A%20(obj)%20%3D%3E%20JSON.stringify(obj)%2C%0A%20%20%20%20indexOf%3A%20(val%2C%20char)%20%3D%3E%20String(val).indexOf(char)%2C%0A%20%20%20%20length%3A%20(val)%20%3D%3E%20String(val).length%2C%0A%20%20%20%20trim%3A%20(val)%20%3D%3E%20String(val).trim()%2C%0A%20%20%20%20substr%3A%20(val%2C%20int1%2C%20int2)%20%3D%3E%20String(val).substr(int1%2C%20int2)%2C%0A%20%20%20%20addreduce%3A%20(arr)%20%3D%3E%20arr.reduce((i%2C%20v)%20%3D%3E%20i%20%2B%20v)%2C%0A%20%20%20%20lengtharray%3A%20(arr)%20%3D%3E%20arr.length%2C%0A%20%20%20%20typeof%3A%20(val)%20%3D%3E%20typeof%20val%2C%0A%20%20%20%20isarray%3A%20(arr)%20%3D%3E%20Array.isArray(arr)%2C%0A%20%20%20%20isnan%3A%20(val)%20%3D%3E%20isNaN(val)%2C%0A%20%20%20%20parseint%3A%20(val)%20%3D%3E%20parseInt(val)%2C%0A%20%20%20%20parsefloat%3A%20(val)%20%3D%3E%20parseFloat(val)%2C%0A%20%20%20%20toisodate%3A%20(val)%20%3D%3E%20new%20Date(val).toISOString()%2C%0A%20%20%20%20timeoffset%3A%20(isostr)%20%3D%3E%20new%20Date(isostr).getTimezoneOffset()%2C%0A%20%20%20%20tostring%3A%20(val)%20%3D%3E%20val.toString()%2C%0A%20%20%20%20urlencode%3A%20(val)%20%3D%3E%20encodeURI(val)%2C%0A%20%20%20%20urldecode%3A%20(val)%20%3D%3E%20decodeURI(val)%2C%0A%20%20%20%20replacestr%3A%20(str%2C%20from%2C%20to)%20%3D%3E%20str.replace(from%2C%20to)%2C%0A%20%20%20%20replaceregexp%3A%20(str%2C%20reg%2C%20to)%20%3D%3E%20str.replace(new%20RegExp(reg)%2C%20to)%2C%0A%20%20%20%20replaceallstr%3A%20(str%2C%20from%2C%20to)%20%3D%3E%20str.replaceAll(from%2C%20to)%2C%0A%20%20%20%20replaceallregexp%3A%20(str%2C%20reg%2C%20to)%20%3D%3E%20str.replaceAll(new%20RegExp(reg%2C%20'g')%2C%20to)%2C%0A%20%20%20%20split%3A%20(str%2C%20ch)%20%3D%3E%20str.split(ch)%2C%0A%20%20%20%20mapper%3A%20(val%2C%20values%2C%20choices)%20%3D%3E%20choices%5Bvalues.findIndex((target)%20%3D%3E%20target%20%3D%3D%3D%20val)%5D%2C%0A%20%20%20%20thmapper%3A%20(val%2C%20values%2C%20choices)%20%3D%3E%0A%20%20%20%20%20%20%20%20choices%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20values.reduce((acc%2C%20curr%2C%20i)%20%3D%3E%20(acc%20%3D%3D%3D%200%20%7C%7C%20acc%20%3F%20acc%20%3A%20val%20%3C%3D%20curr%20%3F%20(acc%20%3D%20i)%20%3A%20(acc%20%3D%20null))%2C%20null)%0A%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20bitwisemask%3A%20(i%2C%20mask%2C%20op%2C%20shf)%20%3D%3E%0A%20%20%20%20%20%20%20%20(op%20%3D%3D%3D%20'%26'%20%3F%20parseInt(i)%20%26%20mask%20%3A%20op%20%3D%3D%3D%20'%7C'%20%3F%20parseInt(i)%20%7C%20mask%20%3A%20op%20%3D%3D%3D%20'%5E'%20%3F%20parseInt(i)%20%5E%20mask%20%3A%20i)%20%3E%3E%0A%20%20%20%20%20%20%20%20shf%2C%0A%20%20%20%20slice%3A%20(arr%2C%20init%2C%20end)%20%3D%3E%20arr.slice(init%2C%20end)%0A%7D
2466
+ https://czosel.github.io/jexl-playground/#/?context=%7B%0A%20%20%22text%22%20%3A%20%22%20%20foobar%7B%7D%20%20%22%0A%7D&input=text%20%7C%20replacestr(%22foo%22%2C%22FOO%22)%7Ctrim%7Curlencode&transforms=%7B%0A%20%20%20%20jsonparse%3A%20(str)%20%3D%3E%20JSON.parse(str)%2C%0A%20%20%20%20jsonstringify%3A%20(obj)%20%3D%3E%20JSON.stringify(obj)%2C%0A%20%20%20%20indexOf%3A%20(val%2C%20char)%20%3D%3E%20String(val).indexOf(char)%2C%0A%20%20%20%20length%3A%20(val)%20%3D%3E%20String(val).length%2C%0A%20%20%20%20trim%3A%20(val)%20%3D%3E%20String(val).trim()%2C%0A%20%20%20%20substr%3A%20(val%2C%20int1%2C%20int2)%20%3D%3E%20String(val).substr(int1%2C%20int2)%2C%0A%20%20%20%20addreduce%3A%20(arr)%20%3D%3E%20arr.reduce((i%2C%20v)%20%3D%3E%20i%20%2B%20v)%2C%0A%20%20%20%20lengtharray%3A%20(arr)%20%3D%3E%20arr.length%2C%0A%20%20%20%20typeof%3A%20(val)%20%3D%3E%20typeof%20val%2C%0A%20%20%20%20isarray%3A%20(arr)%20%3D%3E%20Array.isArray(arr)%2C%0A%20%20%20%20isnan%3A%20(val)%20%3D%3E%20isNaN(val)%2C%0A%20%20%20%20parseint%3A%20(val)%20%3D%3E%20parseInt(val)%2C%0A%20%20%20%20parsefloat%3A%20(val)%20%3D%3E%20parseFloat(val)%2C%0A%20%20%20%20toisodate%3A%20(val)%20%3D%3E%20new%20Date(val).toISOString()%2C%0A%20%20%20%20timeoffset%3A%20(isostr)%20%3D%3E%20new%20Date(isostr).getTimezoneOffset()%2C%0A%20%20%20%20tostring%3A%20(val)%20%3D%3E%20val.toString()%2C%0A%20%20%20%20urlencode%3A%20(val)%20%3D%3E%20encodeURI(val)%2C%0A%20%20%20%20urldecode%3A%20(val)%20%3D%3E%20decodeURI(val)%2C%0A%20%20%20%20replacestr%3A%20(str%2C%20from%2C%20to)%20%3D%3E%20str.replace(from%2C%20to)%2C%0A%20%20%20%20replaceregexp%3A%20(str%2C%20reg%2C%20to)%20%3D%3E%20str.replace(new%20RegExp(reg)%2C%20to)%2C%0A%20%20%20%20replaceallstr%3A%20(str%2C%20from%2C%20to)%20%3D%3E%20str.replaceAll(from%2C%20to)%2C%0A%20%20%20%20replaceallregexp%3A%20(str%2C%20reg%2C%20to)%20%3D%3E%20str.replaceAll(new%20RegExp(reg%2C%20'g')%2C%20to)%2C%0A%20%20%20%20split%3A%20(str%2C%20ch)%20%3D%3E%20str.split(ch)%2C%0A%20%20%20%20joinarrtostr%3A%20(arr%2C%20ch)%20%3D%3E%20arr.join(ch)%2C%0A%20%20%20%20concatarr%3A%20(arr%2C%20arr2)%20%3D%3E%20arr.concat(arr2)%2C%0A%20%20%20%20mapper%3A%20(val%2C%20values%2C%20choices)%20%3D%3E%20choices%5Bvalues.findIndex((target)%20%3D%3E%20target%20%3D%3D%3D%20val)%5D%2C%0A%20%20%20%20thmapper%3A%20(val%2C%20values%2C%20choices)%20%3D%3E%0A%20%20%20%20%20%20%20%20choices%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20values.reduce((acc%2C%20curr%2C%20i)%20%3D%3E%20(acc%20%3D%3D%3D%200%20%7C%7C%20acc%20%3F%20acc%20%3A%20val%20%3C%3D%20curr%20%3F%20(acc%20%3D%20i)%20%3A%20(acc%20%3D%20null))%2C%20null)%0A%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20bitwisemask%3A%20(i%2C%20mask%2C%20op%2C%20shf)%20%3D%3E%0A%20%20%20%20%20%20%20%20(op%20%3D%3D%3D%20'%26'%20%3F%20parseInt(i)%20%26%20mask%20%3A%20op%20%3D%3D%3D%20'%7C'%20%3F%20parseInt(i)%20%7C%20mask%20%3A%20op%20%3D%3D%3D%20'%5E'%20%3F%20parseInt(i)%20%5E%20mask%20%3A%20i)%20%3E%3E%0A%20%20%20%20%20%20%20%20shf%2C%0A%20%20%20%20slice%3A%20(arr%2C%20init%2C%20end)%20%3D%3E%20arr.slice(init%2C%20end)%2C%0A%20%20%20%20addset%3A%20(arr%2C%20x)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20return%20Array.from(new%20Set(arr).add(x))%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20removeset%3A%20(arr%2C%20x)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20let%20s%20%3D%20new%20Set(arr)%3B%0A%20%20%20%20%20%20%20%20s.delete(x)%3B%0A%20%20%20%20%20%20%20%20return%20Array.from(s)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20touppercase%3A%20(val)%20%3D%3E%20String(val).toUpperCase()%2C%0A%20%20%20%20tolowercase%3A%20(val)%20%3D%3E%20String(val).toLowerCase()%2C%0A%20%20%20%20floor%3A%20(val)%20%3D%3E%20Math.floor(val)%2C%0A%20%20%20%20ceil%3A%20(val)%20%3D%3E%20Math.ceil(val)%2C%0A%20%20%20%20round%3A%20(val)%20%3D%3E%20Math.round(val)%2C%0A%20%20%20%20tofixed%3A%20(val%2C%20decimals)%20%3D%3E%20Number.parseFloat(val).toFixed(decimals)%2C%0A%20%20%20%20gettime%3A%20(d)%20%3D%3E%20new%20Date(d).getTime()%2C%0A%20%20%20%20toisostring%3A%20(d)%20%3D%3E%20new%20Date(d).toISOString()%2C%0A%20%20%20%20%2F%2F%20https%3A%2F%2Fdeveloper.mozilla.org%2Fes%2Fdocs%2FWeb%2FJavaScript%2FReference%2FGlobal_Objects%2FDate%2FtoLocaleString%0A%20%20%20%20localestring%3A%20(d%2C%20timezone%2C%20options)%20%3D%3E%20new%20Date(d).toLocaleString(timezone%2C%20options)%2C%0A%20%20%20%20now%3A%20()%20%3D%3E%20Date.now()%2C%0A%20%20%20%20hextostring%3A%20(val)%20%3D%3E%0A%20%20%20%20%20%20%20%20new%20TextDecoder().decode(new%20Uint8Array(val.match(%2F.%7B1%2C2%7D%2Fg).map((byte)%20%3D%3E%20parseInt(byte%2C%2016))))%2C%0A%20%20%20%20valuePicker%3A%20(val%2Cpick)%20%3D%3E%20Object.entries(val).filter((%5B%2C%20v%5D)%20%3D%3E%20v%20%3D%3D%3D%20pick).map((%5Bk%2C%5D)%20%3D%3E%20k)%2C%0A%20%20%20%20valuePickerMulti%3A%20(val%2Cpick)%20%3D%3E%20Object.entries(val).filter((%5B%2C%20v%5D)%20%3D%3E%20pick.includes(v)).map((%5Bk%2C%5D)%20%3D%3E%20k)%0A%7D
@@ -775,6 +775,8 @@ The registration of the commands is performed once in the lifetime of the Device
775
775
 
776
776
  #### Command Execution
777
777
 
778
+ ##### Based in update (classic way)
779
+
778
780
  Scenario 3 begins with the request for a command from the User to the Context Broker (P1):
779
781
 
780
782
  ```bash
@@ -825,21 +827,8 @@ Fiware-Correlator: 9cae9496-8ec7-11e6-80fc-fa163e734aab
825
827
  }
826
828
  ```
827
829
 
828
- The IoT Agent detects the selected attribute is a command, and replies to the Context Broker with the following payload
829
- (200 OK):
830
-
831
- ```json
832
- [
833
- {
834
- "type": "device",
835
- "id": "Dev0001",
836
- "switch": {
837
- "type": "command",
838
- "value": ""
839
- }
840
- }
841
- ]
842
- ```
830
+ The IoT Agent detects the selected attribute is a command, and replies to the Context Broker with a 204 OK (without
831
+ payload).
843
832
 
844
833
  This response just indicates that the IoT Agent has received the command successfully, and gives no information about
845
834
  the requested information or command execution.
@@ -862,6 +851,100 @@ The Context Broker, forwards the same response to the user, thus replying the or
862
851
  At this point, the command has been issued to the IoTAgent and the User doesn't still know what the result of its
863
852
  request will be.
864
853
 
854
+ ##### Based in notification (new way)
855
+
856
+ A new way to ContextBroker provides a command to a IoTAgent is through notifications. In this case CB notify the command
857
+ to the IotAgent with a request like the following:
858
+
859
+ ```bash
860
+ POST /notify HTTP/1.1
861
+ Host: <target-host>:<northbound_port>
862
+ fiware-service: workshop
863
+ Fiware-ServicePath: /iota2ngsi
864
+ Accept: application/json
865
+ Content-length: 290
866
+ Content-type: application/json; charset=utf-8
867
+ Fiware-Correlator: 9cae9496-8ec7-11e6-80fc-fa163e734aab
868
+
869
+ {
870
+ "subscriptionId": "60b0cedd497e8b681d40b58e",
871
+ "data": [{
872
+ "id": "123456abcdefg",
873
+ "type": "switchOnOffExecution",
874
+ "targetEntityId": {
875
+ "type": "Text",
876
+ "value": "Dev0001",
877
+ "metadata": {}
878
+ },
879
+ "targetEntityType": {
880
+ "type": "Text",
881
+ "value": "device",
882
+ "metadata": {}
883
+ },
884
+ "execTs": {
885
+ "type": "DateTime",
886
+ "value": "2020-05-27T00:00:00.000Z",
887
+ "metadata": {}
888
+ },
889
+ "cmd": {
890
+ "type": "Text",
891
+ "value": "switch",
892
+ "metadata": {}
893
+ },
894
+ "params": {
895
+ "type": "Text",
896
+ "value": "54, 12",
897
+ "metadata": {}
898
+ },
899
+ "status": {
900
+ "type": "Text",
901
+ "value": "FORWARDED",
902
+ "metadata": {}
903
+ },
904
+ "info": {
905
+ "type": "Text",
906
+ "value": null,
907
+ "metadata": {}
908
+ },
909
+ "onDelivered": {
910
+ "type": "Request",
911
+ "value": {
912
+ }
913
+ },
914
+ "onOk": {
915
+ "type": "Request",
916
+ "value": {
917
+ }
918
+ },
919
+ "onError": {
920
+ "type": "Request",
921
+ "value": {
922
+ }
923
+ },
924
+ "onInfo": {
925
+ "type": "Request",
926
+ "value": {
927
+ }
928
+ },
929
+ "cmdExecution": {
930
+ "type": "value",
931
+ "value": true,
932
+ "metadata": {}
933
+ },
934
+ "dateExpiration": {
935
+ "type": "DateTime",
936
+ "value": "2020-05-27T20:00:00.000Z",
937
+ "metadata": {}
938
+ }
939
+ }]
940
+ }
941
+ ```
942
+
943
+ In this case relevant fields are just `targetEntityId`, `targetEntityType`, `cmd` and `params`.
944
+
945
+ The IoT Agent detects the selected attribute is a command, and replies to the Context Broker with a 204 OK (without
946
+ payload).
947
+
865
948
  #### Result reporting
866
949
 
867
950
  Once the IoT Agent has executed the command or retrieved the information from the device, it reports the results to the
@@ -955,6 +1038,30 @@ The Context Broker replies with all the desired data, in R2 format (200 OK):
955
1038
  ]
956
1039
  ```
957
1040
 
1041
+ #### Differences regarding the new commands mode
1042
+
1043
+ A new commands flow has been defined (involving also modifications at ContextBroker). As part of that design, commands
1044
+ are not sent to IOTAs using NGSIv2 notifications, but the current implementation has some differences regarding the
1045
+ desired behaviour, which are described next:
1046
+
1047
+ - Fields others than `targetEntityId`, `targetEntityType`, `cmd` and `params` (i.e. `execTs`, `status`, `info`,
1048
+ `onDelivered`, `onOk`, `onError`, `onInfo`, `cmdExecution` and `dataExpiration`), are not actually used. By the
1049
+ moment they are stored in the commands model (commands collection) but nothing is done with them appart from
1050
+ storing.
1051
+ - The "Result reporting" should not be a "hardwired" behaviour updating the entity associated to the device, but using
1052
+ the corresponding `on*` attribute in the notificaiton (e.g. `onOk` in the case of success). That attribute would
1053
+ typically be a [HATEOAS](https://en.wikipedia.org/wiki/HATEOAS) object like this
1054
+ `"onOk": { "href": "/v2/entities/123456abcdefg/attrs/status?type=switchExecution", "method": "PUT" }`. Moreover, the
1055
+ entity to be updated in that HATEOAS would be the transient entity corresponding to command execuion, not the entity
1056
+ associated to the device.
1057
+
1058
+ ```
1059
+ PUT /v2/entities/123456abcdefg/attrs/status?type=switchExecution
1060
+ content-type: text/plain
1061
+
1062
+ OK
1063
+ ```
1064
+
958
1065
  ### Scenario 3: commands (error)
959
1066
 
960
1067
  In Scenario 3, errors can happen asynchronously, out of the main interactions. When the IoTAgent detects an error