iotagent-node-lib 4.4.0 → 4.6.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 (100) hide show
  1. package/README.md +67 -272
  2. package/config.js +2 -1
  3. package/doc/README.md +1 -1
  4. package/doc/admin.md +19 -22
  5. package/doc/api.md +621 -155
  6. package/doc/deprecated.md +4 -0
  7. package/doc/devel/architecture.md +5 -135
  8. package/doc/devel/development.md +224 -12
  9. package/doc/getting-started.md +114 -53
  10. package/doc/requirements.txt +1 -1
  11. package/doc/roadmap.md +5 -5
  12. package/docker/Mosquitto/Dockerfile +2 -2
  13. package/docker/Mosquitto/README.md +14 -11
  14. package/lib/commonConfig.js +7 -1
  15. package/lib/constants.js +3 -0
  16. package/lib/fiware-iotagent-lib.js +12 -15
  17. package/lib/jexlTranformsMap.js +3 -1
  18. package/lib/model/dbConn.js +1 -4
  19. package/lib/services/common/alarmManagement.js +3 -0
  20. package/lib/services/devices/deviceRegistryMemory.js +1 -1
  21. package/lib/services/devices/deviceRegistryMongoDB.js +2 -2
  22. package/lib/services/devices/deviceService.js +4 -3
  23. package/lib/services/devices/devices-NGSI-LD.js +5 -5
  24. package/lib/services/devices/devices-NGSI-mixed.js +3 -3
  25. package/lib/services/devices/devices-NGSI-v2.js +5 -5
  26. package/lib/services/groups/groupService.js +1 -1
  27. package/lib/services/ngsi/entities-NGSI-LD.js +321 -571
  28. package/lib/services/ngsi/entities-NGSI-v2.js +348 -281
  29. package/lib/services/ngsi/ngsiService.js +3 -1
  30. package/lib/services/ngsi/subscription-NGSI-LD.js +2 -2
  31. package/lib/services/ngsi/subscription-NGSI-v2.js +2 -2
  32. package/lib/services/northBound/deviceGroupAdministrationServer.js +42 -6
  33. package/lib/services/northBound/deviceProvisioningServer.js +5 -5
  34. package/lib/services/northBound/northboundServer.js +4 -2
  35. package/lib/services/stats/statsRegistry.js +128 -101
  36. package/lib/templates/createDevice.json +0 -24
  37. package/lib/templates/createDeviceLax.json +0 -23
  38. package/lib/templates/deviceGroup.json +1 -25
  39. package/lib/templates/updateDevice.json +0 -24
  40. package/lib/templates/updateDeviceLax.json +0 -23
  41. package/package.json +2 -2
  42. package/scripts/legacy_expression_tool/README.md +0 -1
  43. package/test/functional/README.md +75 -47
  44. package/test/functional/functional-tests-runner.js +9 -4
  45. package/test/functional/functional-tests.js +4 -4
  46. package/test/functional/testCases.js +1251 -257
  47. package/test/functional/testUtils.js +53 -20
  48. package/test/unit/examples/deviceProvisioningRequests/provisionFullDevice.json +1 -13
  49. package/test/unit/examples/deviceProvisioningRequests/updateProvisionDeviceWithApikey.json +4 -0
  50. package/test/unit/examples/groupProvisioningRequests/multipleConfigGroupsCreation.json +44 -0
  51. package/test/unit/examples/groupProvisioningRequests/provisionDuplicateConfigGroup.json +35 -0
  52. package/test/unit/examples/groupProvisioningRequests/provisionFullConfigGroup.json +36 -0
  53. package/test/unit/examples/groupProvisioningRequests/provisionFullConfigGroupAlternate.json +36 -0
  54. package/test/unit/general/deviceService-test.js +102 -0
  55. package/test/unit/general/statistics-service_test.js +1 -74
  56. package/test/unit/mongodb/mongodb-configGroup-registry-test.js +452 -0
  57. package/test/unit/mongodb/mongodb-connectionoptions-test.js +2 -3
  58. package/test/unit/mongodb/mongodb-group-registry-test.js +34 -33
  59. package/test/unit/mongodb/mongodb-service-registry-test.js +477 -0
  60. package/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin1a.json +4 -4
  61. package/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin2.json +22 -22
  62. package/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin29.json +4 -4
  63. package/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin32.json +14 -15
  64. package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin1.json +23 -23
  65. package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin15.json +0 -5
  66. package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin4.json +11 -16
  67. package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin5.json +23 -28
  68. package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin6.json +8 -13
  69. package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin7.json +0 -5
  70. package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin8.json +24 -29
  71. package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityTimestampPlugin2.json +12 -17
  72. package/test/unit/ngsi-ld/examples/contextRequests/updateContextStaticLinkedAttributes.json +12 -10
  73. package/test/unit/ngsi-ld/expressions/jexlBasedTransformations-test.js +0 -102
  74. package/test/unit/ngsi-ld/plugins/multientity-plugin_test.js +4 -5
  75. package/test/unit/ngsi-ld/provisioning/device-update-registration_test.js +5 -5
  76. package/test/unit/ngsi-ld/provisioning/listProvisionedDevices-test.js +0 -4
  77. package/test/unit/ngsiv2/general/deviceService-test.js +94 -1
  78. package/test/unit/ngsiv2/general/iotam-autoregistration-test.js +195 -0
  79. package/test/unit/ngsiv2/provisioning/device-group-api-test.js +259 -0
  80. package/test/unit/ngsiv2/provisioning/device-provisioning-configGroup-api_test.js +1189 -0
  81. package/test/unit/ngsiv2/provisioning/device-update-registration_test.js +6 -6
  82. package/test/unit/ngsiv2/provisioning/listProvisionedDevices-test.js +0 -4
  83. package/test/unit/ngsiv2/provisioning/updateProvisionedDevices-test.js +35 -0
  84. package/test/unit/statsRegistry/openmetrics-test.js +167 -0
  85. package/lib/templates/queryContext.json +0 -25
  86. package/test/unit/examples/deviceProvisioningRequests/provisionBidirectionalDevice.json +0 -35
  87. package/test/unit/examples/deviceProvisioningRequests/provisionDeviceBidirectionalGroup.json +0 -17
  88. package/test/unit/examples/groupProvisioningRequests/bidirectionalGroup.json +0 -31
  89. package/test/unit/general/statistics-persistence_test.js +0 -121
  90. package/test/unit/ngsi-ld/examples/contextRequests/createBidirectionalDevice.json +0 -17
  91. package/test/unit/ngsi-ld/examples/contextRequests/updateContextProcessTimestamp.json +0 -12
  92. package/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotification.json +0 -13
  93. package/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotificationWithDatasetId.json +0 -21
  94. package/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotificationWithMetadata.json +0 -17
  95. package/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalSubscriptionRequest.json +0 -23
  96. package/test/unit/ngsi-ld/plugins/timestamp-processing-plugin_test.js +0 -132
  97. package/test/unit/ngsiv2/examples/contextRequests/createBidirectionalDevice.json +0 -8
  98. package/test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalNotification.json +0 -13
  99. package/test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalNotificationWithMetadata.json +0 -19
  100. package/test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalSubscriptionRequest.json +0 -24
package/doc/api.md CHANGED
@@ -10,8 +10,9 @@
10
10
  - [Devices](#devices)
11
11
  - [Uniqueness of groups and devices](#uniqueness-of-groups-and-devices)
12
12
  - [Special measures and attributes names](#special-measures-and-attributes-names)
13
- - [Entity attributes](#entity-attributes)
14
- - [Multientity support)](#multientity-support)
13
+ - [Device to NGSI Mapping](#device-to-ngsi-mapping)
14
+ - [Device autoprovision and entity creation](#device-autoprovision-and-entity-creation)
15
+ - [Multientity support](#multientity-support)
15
16
  - [Metadata support](#metadata-support)
16
17
  - [NGSI LD data and metadata considerations](#ngsi-ld-data-and-metadata-considerations)
17
18
  - [Advice on Attribute definitions](#advice-on-attribute-definitions)
@@ -26,12 +27,18 @@
26
27
  - [Examples of JEXL expressions](#examples-of-jexl-expressions)
27
28
  - [Available functions](#available-functions)
28
29
  - [Expressions with multiple transformations](#expressions-with-multiple-transformations)
30
+ - [Expression support in metadata](#expression-support-in-metadata)
29
31
  - [Measurement transformation](#measurement-transformation)
30
32
  - [Measurement transformation definition](#measurement-transformation-definition)
31
33
  - [Measurement transformation execution](#measurement-transformation-execution)
32
34
  - [Measurement transformation order](#measurement-transformation-order)
33
35
  - [Multientity measurement transformation support (`object_id`)](#multientity-measurement-transformation-support-object_id)
34
- - [Timestamp Processing](#timestamp-processing)
36
+ - [Command execution](#command-execution)
37
+ - [Triggering commands](#triggering-commands)
38
+ - [Command reception](#command-reception)
39
+ - [Command confirmation](#command-confirmation)
40
+ - [TimeInstant and Timestamp flag](#timeinstant-and-timestamp-flag)
41
+ - [Multimeasure support](#multimeasure-support)
35
42
  - [Overriding global Context Broker host](#overriding-global-context-broker-host)
36
43
  - [Multitenancy, FIWARE Service and FIWARE ServicePath](#multitenancy-fiware-service-and-fiware-servicepath)
37
44
  - [Secured access to the Context Broker](#secured-access-to-the-context-broker)
@@ -43,10 +50,10 @@
43
50
  - [Config group API](#config-group-api)
44
51
  - [Config group datamodel](#config-group-datamodel)
45
52
  - [Config group operations](#config-group-operations)
46
- - [Retrieve config groups `GET /iot/services`](#retrieve-config-groups-get-iotservices)
47
- - [Create config group `POST /iot/services`](#create-config-group-post-iotservices)
48
- - [Modify config group `PUT /iot/services`](#modify-config-group-put-iotservices)
49
- - [Remove config group `DELETE /iot/services`](#remove-config-group-delete-iotservices)
53
+ - [Retrieve config groups `GET /iot/groups`](#retrieve-config-groups-get-iotgroups)
54
+ - [Create config group `POST /iot/groups`](#create-config-group-post-iotgroups)
55
+ - [Modify config group `PUT /iot/groups`](#modify-config-group-put-iotgroups)
56
+ - [Remove config group `DELETE /iot/groups`](#remove-config-group-delete-iotgroups)
50
57
  - [Device API](#device-api)
51
58
  - [Device datamodel](#device-datamodel)
52
59
  - [Device operations](#device-operations)
@@ -63,6 +70,8 @@
63
70
  - [Retrieve log level `GET /admin/log`](#retrieve-log-level-get-adminlog)
64
71
  - [About operations](#about-operations)
65
72
  - [List IoTA Information `GET /iot/about`](#list-iota-information-get-iotabout)
73
+ - [Metrics](#metrics)
74
+ - [Retrieve metrics `GET /metrics`](#retrieve-metrics-get-metrics)
66
75
 
67
76
  <!-- /TOC -->
68
77
 
@@ -127,9 +136,9 @@ For every config group, the pair (resource, apikey) _must_ be unique (as it is u
127
136
  which device). Those operations of the API targeting specific resources will need the use of the `resource` and `apikey`
128
137
  parameters to select the appropriate instance.
129
138
 
130
- Config groups can be created with preconfigured sets of attributes, service information, security information and other
131
- parameters. The specific parameters that can be configured for a given config group are described in the
132
- [Config group datamodel](#config-group-datamodel) section.
139
+ Config groups can be created with preconfigured sets of attributes, service and subservice information, security
140
+ information and other parameters. The specific parameters that can be configured for a given config group are described
141
+ in the [Config group datamodel](#config-group-datamodel) section.
133
142
 
134
143
  ### Devices
135
144
 
@@ -155,7 +164,7 @@ parameters defined at device level in database, the parameters are inherit from
155
164
 
156
165
  ### Uniqueness of groups and devices
157
166
 
158
- Group service uniqueness is defined by the combination of: service, subservice and apikey
167
+ Group uniqueness is defined by the combination of: service, subservice, resource and apikey
159
168
 
160
169
  Device uniqueness is defined by the combination of: service, subservice, device_id and apikey. Note that several devices
161
170
  with the same device_id are allowed in the same service and subservice as long as their apikeys are different.
@@ -170,32 +179,47 @@ applies to autoprovisioned attributes and is also available at JEXL context with
170
179
 
171
180
  In case of provisioning attributes using `id` or `type` as names (please don't do that ;), they are ignored.
172
181
 
173
- ## Entity attributes
182
+ ## Device to NGSI Mapping
174
183
 
175
- In the config group/device model there are four list of attributes with different purpose to configure how the
176
- information coming from the device (measures) is mapped to the Context Broker attributes:
184
+ The way to map the information coming or going to the device to the NGSI attributes is defined in the group or device.
185
+ It is possible to define the entity type and the entity ID that a device will use in the Context Broker. It can be
186
+ configured for a single device in the device provisioning, or it can be defined for all the devices in a group.
187
+
188
+ 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
+ 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.
193
+
194
+ It is also possible to configure how each of the measures obtained from the device is mapped to different attributes.
195
+ The name and type of the attribute is configured by the user (globally for all the devices in the group or in a per
196
+ device preprovisioning). Device measures can have four different behaviors:
177
197
 
178
198
  - **`attributes`**: Are measures that are pushed from the device to the IoT agent. This measure changes will be sent
179
199
  to the Context Broker as updateContext requests over the device entity. NGSI queries to the context broker will be
180
200
  resolved in the Broker database. For each attribute, its `name` and `type` must be provided. Additional `metadata`
181
- is optional.
201
+ is optional. They are called internally as _active attributes_.
182
202
 
183
203
  - **`lazy`**: Passive measures that are pulled from the device to the IoT agent. When a request for data from a lazy
184
- attribute arrives to the Context Broker, it forwards the request to the Context Provider of that entity, in this
185
- case the IoT Agent. The IoT Agent will then ask the device for the information needed, transform that information to
186
- a NGSI format and return it to the Context Broker. This operation will be synchronous from the customer perspective:
187
- the Context Broker won't return a response until the device has returned its response to the IoT Agent. For each
188
- attribute, its `name` and `type` must be provided.
204
+ attribute arrives to the Context Broker, it forwards the request to the IoT Agent (that behaves as NGSI Context
205
+ Provider for all the lazy attributes or commands). The IoT Agent will then ask the device for the information
206
+ needed, transform that information to a NGSI format and return it to the Context Broker. This operation will be
207
+ synchronous from the customer perspective: the Context Broker won't return a response until the device has returned
208
+ its response to the IoT Agent. For each attribute, its `name` and `type` must be provided. They are called
209
+ internally as _lazy attributes_.
189
210
 
190
211
  - **`static`**: It is static attributes that are persisted in the Context Broker. They are not updated by the device,
191
212
  but they can be modified by the user. They are useful to store information about the device that is not updated by
192
213
  the device itself. For instance, a `location` static attribute is can be used to store the location of a fixed
193
214
  device.
194
215
 
195
- - **`commands`**: Commands are actions that can be invoked in the device. They are similar to attributes, but they are
196
- not updated by the device. They are updated by the Context Broker, and the IoT Agent will be in charge of
197
- translating the updateContext request to the proper action in the device. Two additional attributes are created for
198
- each command: `status` and `info`. For each command, its `name` and `type` must be provided.
216
+ - **`commands`**: Commands are actions that can be invoked in the device. They are entity attributes, but they are not
217
+ updated by the device, they are updated by the Context Broker. In this case, the interaction will begin by setting
218
+ an attribute in the device's entity, for which the IoT Agent will be regitered as Context Provider. The IoT Agent
219
+ will return an immediate response to the Context Broker, and will be held responsible of contacting the device to
220
+ perform the command itself using the device specific protocol. Special `status` and `info` attributes should be
221
+ update. For each command, its `name` and `type` must be provided. For further information, please refer to
222
+ [Command execution](#command-execution) section.
199
223
 
200
224
  All of them have the same syntax, a list of objects with the following attributes:
201
225
 
@@ -236,6 +260,17 @@ Note that, when information coming from devices, this means measures, are not de
236
260
  device, the IoT agent will store that information into the destination entity using the same attribute name than the
237
261
  measure name, unless `explicitAttrs` is defined. Measures `id` or `type` names are invalid, and will be ignored.
238
262
 
263
+ ## Device autoprovision and entity creation
264
+
265
+ For those agents that uses IoTA Node LIB version 3.4.0 or higher, you should consider that the entity is not created
266
+ automatically when a device is created. This means that all entities into the Context Broker are created when data
267
+ arrives from a device, no matter if the device is explicitly provisioned (via
268
+ [device provisioning API](#create-device-post-iotdevices)) or autoprovisioned.
269
+
270
+ If for any reason you need the entity at CB before the first measure of the corresponding device arrives to the
271
+ IOTAgent, you can create it in advance using the Context Broker
272
+ [NGSI v2 API](https://github.com/telefonicaid/fiware-orion/blob/master/doc/manuals/orion-api.md).
273
+
239
274
  ## Multientity support
240
275
 
241
276
  The IOTA is able to persists measures coming from a single device to more than one entity, declaring the target entities
@@ -286,69 +321,37 @@ e.g.:
286
321
 
287
322
  ```json
288
323
  {
289
- "entity_type": "Lamp",
290
- "resource": "/iot/d",
291
- "protocol": "PDI-IoTA-UltraLight",
292
- ..etc
293
- "commands": [
294
- {"name": "on","type": "command"},
295
- {"name": "off","type": "command"}
296
- ],
297
- "attributes": [
298
- {"object_id": "s", "name": "state", "type":"Text"},
299
- {"object_id": "l", "name": "luminosity", "type":"Integer",
300
- "metadata":{
301
- "unitCode":{"type": "Text", "value" :"CAL"}
302
- }
303
- }
304
- ],
305
- "static_attributes": [
306
- {"name": "category", "type":"Text", "value": ["actuator","sensor"]},
307
- {"name": "controlledProperty", "type": "Text", "value": ["light"],
308
- "metadata":{
309
- "includes":{"type": "Text", "value" :["state", "luminosity"]},
310
- "alias":{"type": "Text", "value" :"lamp"}
324
+ "entity_type": "Lamp",
325
+ "resource": "/iot/d",
326
+ "protocol": "PDI-IoTA-UltraLight",
327
+ "commands": [
328
+ { "name": "on", "type": "command" },
329
+ { "name": "off", "type": "command" }
330
+ ],
331
+ "attributes": [
332
+ { "object_id": "s", "name": "state", "type": "Text" },
333
+ {
334
+ "object_id": "l",
335
+ "name": "luminosity",
336
+ "type": "Integer",
337
+ "metadata": {
338
+ "unitCode": { "type": "Text", "value": "CAL" }
311
339
  }
312
- },
313
- ]
314
- }
315
- ```
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
340
  }
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"}
341
+ ],
342
+ "static_attributes": [
343
+ { "name": "category", "type": "Text", "value": ["actuator", "sensor"] },
344
+ {
345
+ "name": "controlledProperty",
346
+ "type": "Text",
347
+ "value": ["light"],
348
+ "metadata": {
349
+ "includes": { "type": "Text", "value": ["state", "luminosity"] },
350
+ "alias": { "type": "Text", "value": "lamp" }
348
351
  }
349
- },
350
- ]
351
- }
352
+ }
353
+ ]
354
+ }
352
355
  ```
353
356
 
354
357
  ### NGSI-LD data and metadata considerations
@@ -458,6 +461,9 @@ mappings of the provision. If `explicitAttrs` is provided both at device and con
458
461
  precedence. Additionally `explicitAttrs` can be used to define which measures (identified by their attribute names, not
459
462
  by their object_id) defined in JSON/JEXL array will be propagated to NGSI interface.
460
463
 
464
+ Note that when `explicitAttrs` is an array or a JEXL expression resulting in to Array, if this array is empty then
465
+ `TimeInstant` is not propaged to CB.
466
+
461
467
  The different possibilities are summarized below:
462
468
 
463
469
  Case 1 (default):
@@ -546,6 +552,7 @@ really useful when you need to adapt measure (for example, to change the units,
546
552
  of expression in the IoT Agent are:
547
553
 
548
554
  - [Measurement transformation](#measurement-transformation).
555
+ - [Metadata](#expression-support-in-metadata)
549
556
  - Commands payload transformation (push and pull).
550
557
  - Auto provisioned devices entity name. It is configured at config Group level by setting the `entityNameExp`
551
558
  parameter. It defines an expression to generate the Entity Name for autoprovisioned devices.
@@ -558,12 +565,18 @@ expression. In all cases the following data is available to all expressions:
558
565
  - `id`: device ID
559
566
  - `entity_name`: NGSI entity Name (principal)
560
567
  - `type`: NGSI entity type (principal)
561
- - `service`: device service
562
- - `subservice`: device subservice
568
+ - `service`: device service (`Fiware-Service`)
569
+ - `subservice`: device subservice (`Fiware-ServicePath`)
563
570
  - `staticAttributes`: static attributes defined in the device or config group
564
571
 
565
572
  Additionally, for attribute expressions (`expression`, `entity_name`), `entityNameExp` and metadata expressions
566
- (`expression`) measures are available in the **context** used to evaluate them.
573
+ (`expression`) the following is available in the **context** used to evalute:
574
+
575
+ - measures, as `<AttributeName>`
576
+ - metadata (both for attribute measurement in the case of NGSI-v2 measurements and static attribute) are available in
577
+ the **context** under the following convention: `metadata.<AttributeName>.<MetadataName>` or
578
+ `metadata.<StaticAttributeName>.<MetadataName>` in a similar way of defined for
579
+ [Context Broker](https://github.com/telefonicaid/fiware-orion/blob/master/doc/manuals/orion-api.md#metadata-support)
567
580
 
568
581
  ### Examples of JEXL expressions
569
582
 
@@ -609,50 +622,52 @@ to incorporate new transformations from the IoT Agent configuration file in a fa
609
622
 
610
623
  Current common transformation set:
611
624
 
612
- | JEXL Transformation | Equivalent JavaScript Function |
613
- | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- |
614
- | jsonparse: (str) | `JSON.parse(str);` |
615
- | jsonstringify: (obj) | `JSON.stringify(obj);` |
616
- | indexOf: (val, char) | `String(val).indexOf(char);` |
617
- | length: (val) | `String(val).length;` |
618
- | trim: (val) | `String(val).trim();` |
619
- | substr: (val, int1, int2) | `String(val).substr(int1, int2);` |
620
- | addreduce: (arr) | <code>arr.reduce((i, v) &vert; i + v));</code> |
621
- | lengtharray: (arr) | `arr.length;` |
622
- | typeof: (val) | `typeof val;` |
623
- | isarray: (arr) | `Array.isArray(arr);` |
624
- | isnan: (val) | `isNaN(val);` |
625
- | parseint: (val) | `parseInt(val);` |
626
- | parsefloat: (val) | `parseFloat(val);` |
627
- | toisodate: (val) | `new Date(val).toISOString();` |
628
- | timeoffset:(isostr) | `new Date(isostr).getTimezoneOffset();` |
629
- | tostring: (val) | `val.toString();` |
630
- | urlencode: (val) | `encodeURI(val);` |
631
- | urldecode: (val) | `decodeURI(val);` |
632
- | replacestr: (str, from, to) | `str.replace(from, to);` |
633
- | replaceregexp: (str, reg, to) | `str.replace(new RegExp(reg), to);` |
634
- | replaceallstr: (str, from, to) | `str.replaceAll(from, to);` |
635
- | replaceallregexp: (str, reg, to) | `str.replaceAll(new RegExp(reg,"g"), to);` |
636
- | split: (str, ch) | `str.split(ch);` |
637
- | joinarrtostr: (arr, ch) | `arr.join(ch);` |
638
- | concatarr: (arr, arr2) | `arr.concat(arr2);` |
639
- | mapper: (val, values, choices) | <code>choices[values.findIndex((target) &vert; target == val)]);</code> |
640
- | thmapper: (val, values, choices) | <code>choices[values.reduce((acc,curr,i,arr) &vert; (acc==0)&vert;&vert;acc?acc:val<=curr?acc=i:acc=null,null)];</code> |
641
- | bitwisemask: (i,mask,op,shf) | <code>(op==="&"?parseInt(i)&mask: op==="&vert;"?parseInt(i)&vert;mask: op==="^"?parseInt(i)^mask:i)>>shf;</code> |
642
- | slice: (arr, init, end) | `arr.slice(init,end);` |
643
- | addset: (arr, x) | <code>{ return Array.from((new Set(arr)).add(x)) }</code> |
644
- | removeset: (arr, x) | <code>{ let s = new Set(arr); s.delete(x); return Array.from(s) }</code> |
645
- | touppercase: (val) | `String(val).toUpperCase()` |
646
- | tolowercase: (val) | `String(val).toLowerCase()` |
647
- | round: (val) | `Math.round(val)` |
648
- | floor: (val) | `Math.floor(val)` |
649
- | ceil: (val) | `Math.ceil(val)` |
650
- | tofixed: (val, decimals) | `Number.parseFloat(val).toFixed(decimals)` |
651
- | gettime: (d) | `new Date(d).getTime()` |
652
- | toisostring: (d) | `new Date(d).toISOString()` |
653
- | localestring: (d, timezone, options) | `new Date(d).toLocaleString(timezone, options)` |
654
- | now: () | `Date.now()` |
655
- | hextostring: (val) | `new TextDecoder().decode(new Uint8Array(val.match(/.{1,2}/g).map(byte => parseInt(byte, 16))))` |
625
+ | JEXL Transformation | Equivalent JavaScript Function |
626
+ | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------ |
627
+ | jsonparse: (str) | `JSON.parse(str);` |
628
+ | jsonstringify: (obj) | `JSON.stringify(obj);` |
629
+ | indexOf: (val, char) | `String(val).indexOf(char);` |
630
+ | length: (val) | `String(val).length;` |
631
+ | trim: (val) | `String(val).trim();` |
632
+ | substr: (val, int1, int2) | `String(val).substr(int1, int2);` |
633
+ | addreduce: (arr) | <code>arr.reduce((i, v) &vert; i + v));</code> |
634
+ | lengtharray: (arr) | `arr.length;` |
635
+ | typeof: (val) | `typeof val;` |
636
+ | isarray: (arr) | `Array.isArray(arr);` |
637
+ | isnan: (val) | `isNaN(val);` |
638
+ | parseint: (val) | `parseInt(val);` |
639
+ | parsefloat: (val) | `parseFloat(val);` |
640
+ | toisodate: (val) | `new Date(val).toISOString();` |
641
+ | timeoffset:(isostr) | `new Date(isostr).getTimezoneOffset();` |
642
+ | tostring: (val) | `val.toString();` |
643
+ | urlencode: (val) | `encodeURI(val);` |
644
+ | urldecode: (val) | `decodeURI(val);` |
645
+ | replacestr: (str, from, to) | `str.replace(from, to);` |
646
+ | replaceregexp: (str, reg, to) | `str.replace(new RegExp(reg), to);` |
647
+ | replaceallstr: (str, from, to) | `str.replaceAll(from, to);` |
648
+ | replaceallregexp: (str, reg, to) | `str.replaceAll(new RegExp(reg,"g"), to);` |
649
+ | split: (str, ch) | `str.split(ch);` |
650
+ | joinarrtostr: (arr, ch) | `arr.join(ch);` |
651
+ | concatarr: (arr, arr2) | `arr.concat(arr2);` |
652
+ | mapper: (val, values, choices) | <code>choices[values.findIndex((target) &vert; target == val)]);</code> |
653
+ | thmapper: (val, values, choices) | <code>choices[values.reduce((acc,curr,i,arr) &vert; (acc==0)&vert;&vert;acc?acc:val<=curr?acc=i:acc=null,null)];</code> |
654
+ | bitwisemask: (i,mask,op,shf) | <code>(op==="&"?parseInt(i)&mask: op==="&vert;"?parseInt(i)&vert;mask: op==="^"?parseInt(i)^mask:i)>>shf;</code> |
655
+ | slice: (arr, init, end) | `arr.slice(init,end);` |
656
+ | addset: (arr, x) | <code>{ return Array.from((new Set(arr)).add(x)) }</code> |
657
+ | removeset: (arr, x) | <code>{ let s = new Set(arr); s.delete(x); return Array.from(s) }</code> |
658
+ | touppercase: (val) | `String(val).toUpperCase()` |
659
+ | tolowercase: (val) | `String(val).toLowerCase()` |
660
+ | round: (val) | `Math.round(val)` |
661
+ | floor: (val) | `Math.floor(val)` |
662
+ | ceil: (val) | `Math.ceil(val)` |
663
+ | tofixed: (val, decimals) | `Number.parseFloat(val).toFixed(decimals)` |
664
+ | gettime: (d) | `new Date(d).getTime()` |
665
+ | toisostring: (d) | `new Date(d).toISOString()` |
666
+ | localestring: (d, timezone, options) | `new Date(d).toLocaleString(timezone, options)` |
667
+ | now: () | `Date.now()` |
668
+ | hextostring: (val) | `new TextDecoder().decode(new Uint8Array(val.match(/.{1,2}/g).map(byte => parseInt(byte, 16))))` |
669
+ | valuePicker: (val,pick) | <code>valuePicker: (val,pick) => Object.entries(val).filter(([_, v]) => v === pick).map(([k, _]) => k)</code> |
670
+ | valuePickerMulti: (val,pick) | <code>valuePickerMulti: (val,pick) => Object.entries(val).filter(([_, v]) => pick.includes(v)).map(([k, _]) => k)</code> |
656
671
 
657
672
  You have available this [JEXL interactive playground][99] with all the transformations already loaded, in which you can
658
673
  test all the functions described above.
@@ -681,6 +696,50 @@ Another example using functions that return more than one value is the following
681
696
 
682
697
  For a location value `"40.4165, -3.70256"`, the result of the previous expression will be `-3.70256`.
683
698
 
699
+ ### Expression support in metadata
700
+
701
+ Metadata could also has `expression` like attributes in order to expand it:
702
+
703
+ e.g.:
704
+
705
+ ```json
706
+ {
707
+ "entity_type": "Lamp",
708
+ "resource": "/iot/d",
709
+ "protocol": "PDI-IoTA-UltraLight",
710
+ "commands": [
711
+ { "name": "on", "type": "command" },
712
+ { "name": "off", "type": "command" }
713
+ ],
714
+ "attributes": [
715
+ { "object_id": "s", "name": "state", "type": "Text" },
716
+ {
717
+ "object_id": "l",
718
+ "name": "luminosity",
719
+ "type": "Integer",
720
+ "metadata": {
721
+ "unitCode": { "type": "Text", "value": "CAL" }
722
+ }
723
+ }
724
+ ],
725
+ "static_attributes": [
726
+ { "name": "category", "type": "Text", "value": ["actuator", "sensor"] },
727
+ {
728
+ "name": "controlledProperty",
729
+ "type": "Text",
730
+ "value": ["light"],
731
+ "metadata": {
732
+ "includes": { "type": "Text", "value": ["state", "luminosity"], "expression": "level / 100" },
733
+ "alias": { "type": "Text", "value": "lamp" }
734
+ }
735
+ }
736
+ ]
737
+ }
738
+ ```
739
+
740
+ Note that there is no order into metadata structure and there is no warranty about which metadata attribute expression
741
+ will be evaluated first.
742
+
684
743
  ## Measurement transformation
685
744
 
686
745
  The IoTAgent Library provides support for measurement transformation using a
@@ -1007,13 +1066,34 @@ Will now generate the following NGSI v2 payload:
1007
1066
  }
1008
1067
  ```
1009
1068
 
1010
- ## Timestamp Processing
1069
+ ## TimeInstant and Timestamp flag
1070
+
1071
+ As part of the device to entity mapping process, the IoT Agent creates and updates automatically a special timestamp
1072
+ attribute called `TimeInstant`. This timestamp is represented as two different properties of the mapped entity:
1011
1073
 
1012
- Timestamp processing done by IOTA is as follows:
1074
+ - An attribute `TimeInstant` is added to updated entities in the case of NGSI-v2, which captures as an ISO8601
1075
+ timestamp when the associated measurement was observed. With NGSI-LD, the Standard `observedAt` property is used
1076
+ instead
1013
1077
 
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.
1078
+ - With NGSI-v2, an attribute metadata named `TimeInstant` per active or lazy attribute mapped, which captures as an
1079
+ ISO8601 timestamp when the associated measurement (represented as attribute value) was observed. With NGSI-LD, the
1080
+ Standard `observedAt` property-of-a-property is used instead.
1081
+
1082
+ If timestamp is not explicily defined when sending the measures through the IoT Agent (for further details on how to
1083
+ attach timestamp information to the measures, please refer to the particular IoT Agent implementation documentation),
1084
+ the arrival time on the server when receiving the measurement will be used to generate a `TimeInstant` for both the
1085
+ entity attribute and the attribute metadata.
1086
+
1087
+ This functionality can be turned on and off globaly through the use of the `timestamp` configuration flag or
1088
+ `IOTA_TIMESTAMP` variable as well as `timestamp` flag in device or group provision (in this case, the device or group
1089
+ level flag takes precedence over the global one). The default value is `true`.
1090
+
1091
+ The `timestamp` configuration value used, according to the priority:
1092
+
1093
+ 1. The one defined at device level
1094
+ 2. The one defined at group level (if not defined at device level)
1095
+ 3. The one defined at [IoTA configuration level](admin.md#timestamp) / `IOTA_TIMESTAMP` env var (if not defined at
1096
+ group level or device level)
1017
1097
 
1018
1098
  Depending on the `timestamp` configuration and if the measure contains a value named `TimeInstant` with a correct value,
1019
1099
  the IoTA behaviour is described in the following table:
@@ -1027,13 +1107,6 @@ the IoTA behaviour is described in the following table:
1027
1107
  | Not defined | Yes | TimeInstant and metadata updated with measure value |
1028
1108
  | Not defined | No | TimeInstant and metadata updated with server timestamp |
1029
1109
 
1030
- The `timestamp` conf value used is:
1031
-
1032
- - The one defined at device level
1033
- - The one defined at group level (if not defined at device level)
1034
- - The one defined at [IoTA configuration level](admin.md#timestamp) / `IOTA_TIMESTAMP` env var (if not defined at
1035
- group level or device level)
1036
-
1037
1110
  Some additional considerations to take into account:
1038
1111
 
1039
1112
  - If there is an attribute which maps a measure to `TimeInstant` attribute (after
@@ -1043,6 +1116,348 @@ Some additional considerations to take into account:
1043
1116
  - If the resulting `TimeInstant` not follows [ISO_8601](https://en.wikipedia.org/wiki/ISO_8601) (either from a direct
1044
1117
  measure of after a mapping, as described in the previous bullet) then it is refused (so a failover to server
1045
1118
  timestamp will take place).
1119
+ - the timestamp of different attributes belonging to the same measurement record may not be equal.
1120
+ - the arrival time and the measurement timestamp will not be the same in the general case (when explicitly defining
1121
+ the timestamp in the measurement)
1122
+ - if `timezone` field is defined as part of the provisioning of the device or group, timestamp fields will be
1123
+ generated using it. For instance, if `timezone` is set to `America/Los_Angeles`, a possible timestamp could be
1124
+ `2025-08-05T00:35:01.468-07:00`. If `timezone` field is not defined, by default Zulu Time Zone (UTC +0) will be
1125
+ used. Following the previous example, timestamp could be `2015-08-05T07:35:01.468Z`.
1126
+
1127
+ E.g.: in the case of a device that can take measurements every hour of both temperature and humidity and sends the data
1128
+ once every day, at midnight, the `TimeInstant` reported for each measurement will be the hour when that measurement was
1129
+ observed (e.g. 4:00 PM), while all the measurements will have an arrival time around midnight. If no timestamps were
1130
+ reported with such measurements, the `TimeInstant` attribute would take those values around midnight.
1131
+
1132
+ ## Multimeasure support
1133
+
1134
+ A device could receive several measures at the same time.
1135
+
1136
+ For example:
1137
+
1138
+ ```json
1139
+ [
1140
+ {
1141
+ "vol": 0
1142
+ },
1143
+ {
1144
+ "vol": 1
1145
+ },
1146
+ {
1147
+ "vol": 2
1148
+ }
1149
+ ]
1150
+ ```
1151
+
1152
+ In this case a batch update (`POST /v2/op/update`) to CB will be generated with the following NGSI v2 payload:
1153
+
1154
+ ```json
1155
+ {
1156
+ "actionType": "append",
1157
+ "entities": [
1158
+ {
1159
+ "id": "ws",
1160
+ "type": "WeatherStation",
1161
+ "vol": {
1162
+ "type": "Number",
1163
+ "value": 0
1164
+ }
1165
+ },
1166
+ {
1167
+ "id": "ws",
1168
+ "type": "WeatherStation",
1169
+ "vol": {
1170
+ "type": "Number",
1171
+ "value": 1
1172
+ }
1173
+ },
1174
+ {
1175
+ "id": "ws",
1176
+ "type": "WeatherStation",
1177
+ "vol": {
1178
+ "type": "Number",
1179
+ "value": 1
1180
+ }
1181
+ }
1182
+ ]
1183
+ }
1184
+ ```
1185
+
1186
+ Moreover if a multimeasure contains TimeInstant attribute, then CB update is sorted by attribute TimeInstant:
1187
+
1188
+ For example:
1189
+
1190
+ ```json
1191
+ [
1192
+ {
1193
+ "vol": 0,
1194
+ "TimeInstant": "2024-04-10T10:15:00Z"
1195
+ },
1196
+ {
1197
+ "vol": 1,
1198
+ "TimeInstant": "2024-04-10T10:10:00Z"
1199
+ },
1200
+ {
1201
+ "vol": 2,
1202
+ "TimeInstant": "2024-04-10T10:05:00Z"
1203
+ }
1204
+ ]
1205
+ ```
1206
+
1207
+ In this case a batch update (`POST /v2/op/update`) to CB will be generated with the following NGSI v2 payload:
1208
+
1209
+ ```json
1210
+ {
1211
+ "actionType": "append",
1212
+ "entities": [
1213
+ {
1214
+ "id": "ws",
1215
+ "type": "WeatherStation",
1216
+ "vol": {
1217
+ "type": "Number",
1218
+ "value": 2
1219
+ },
1220
+ "TimeInstant": {
1221
+ "type": "DateTime",
1222
+ "value": "2024-04-10T10:05:00Z"
1223
+ }
1224
+ },
1225
+ {
1226
+ "id": "ws",
1227
+ "type": "WeatherStation",
1228
+ "vol": {
1229
+ "type": "Number",
1230
+ "value": 1
1231
+ },
1232
+ "TimeInstant": {
1233
+ "type": "DateTime",
1234
+ "value": "2024-04-10T10:10:00Z"
1235
+ }
1236
+ },
1237
+ {
1238
+ "id": "ws",
1239
+ "type": "WeatherStation",
1240
+ "vol": {
1241
+ "type": "Number",
1242
+ "value": 0
1243
+ },
1244
+ "TimeInstant": {
1245
+ "type": "DateTime",
1246
+ "value": "2024-04-10T10:15:00Z"
1247
+ }
1248
+ }
1249
+ ]
1250
+ }
1251
+ ```
1252
+
1253
+ ## Command execution
1254
+
1255
+ This section reviews the end-to-end process to trigger and receive commands into devices. The URL paths and messages
1256
+ format is based on the [IoT Agent JSON](https://github.com/telefonicaid/iotagent-json). It may differ in the case of
1257
+ using any other IoT Agent. In that case, please refer to the specific IoTA documentation.
1258
+
1259
+ ### Triggering commands
1260
+
1261
+ This starts the process of sending data to devices. It starts by updating an attribute into the Context Broker defined
1262
+ as `command` in the [config group](#config-group-datamodel) or in the [device provision](#device-datamodel). Commands
1263
+ attributes are created using `command` as attribute type. Also, you can define the protocol you want the commands to be
1264
+ sent (HTTP/MQTT) with the `transport` parameter at the provisioning process.
1265
+
1266
+ For a given device provisioned with a `ping` command defined, any update on this attribute "ping" at the NGSI entity in
1267
+ the Context Broker will send a command to your device. For instance, to send the `ping` command with value
1268
+ `Ping request` you could use the following operation in the Context Broker API:
1269
+
1270
+ ```
1271
+ PUT /v2/entities/<entity_id>/attrs/ping?type=<entity_type>
1272
+
1273
+ {
1274
+ "value": "Ping request",
1275
+ "type": "command"
1276
+ }
1277
+
1278
+ ```
1279
+
1280
+ It is important to note that parameter `type`, with the entity type must be included.
1281
+
1282
+ Context Broker API is quite flexible and allows to update an attribute in several ways. Please have a look to the
1283
+ [Orion API](<[http://telefonicaid.github.io/fiware-orion/api/v2/stable](https://github.com/telefonicaid/fiware-orion/blob/master/doc/manuals/orion-api.md)>)
1284
+ for details.
1285
+
1286
+ **Important note**: don't use operations in the NGSI API with creation semantics. Otherwise, the entity/attribute will
1287
+ be created locally to Context Broker and the command will not progress to the device (and you will need to delete the
1288
+ created entity/attribute if you want to make it to work again). Thus, the following operations _must not_ be used:
1289
+
1290
+ - `POST /v2/entities`
1291
+ - `POST /v2/entities/<id>/attrs`
1292
+ - `PUT /v2/entities/<id>/attrs`
1293
+ - `POST /v2/op/entites` with `actionType` `append`, `appendStrict` or `replace`
1294
+
1295
+ ### Command reception
1296
+
1297
+ Once the command is triggered, it is send to the device. Depending on transport protocol, it is going to be sent to the
1298
+ device in a different way. After sending the command, the IoT Agent will have updated the value of `ping_status` to
1299
+ `PENDING` for entity into the Context Broker. Neither `ping_info` nor `ping` will be updated.
1300
+
1301
+ #### HTTP devices
1302
+
1303
+ **Push commands**
1304
+
1305
+ Push commands are those that are sent to the device once the IoT Agent receives the request from the Context Broker. In
1306
+ order to send push commands it is needed to set the `"endpoint": "http://[DEVICE_IP]:[PORT]/"` in the device or group
1307
+ provision. The device is supposed to be listening for commands at that URL in a synchronous way. Make sure the device
1308
+ endpoint is reachable by the IoT Agent. Push commands are only valid for HTTP devices. For MQTT devices it is not needed
1309
+ to set the `endpoint` parameter.
1310
+
1311
+ Considering using the IoTA-JSON Agent, and given the previous example, the device should receive a POST request to
1312
+ `http://[DEVICE_IP]:[PORT]` with the following payload:
1313
+
1314
+ ```
1315
+ POST /
1316
+ Content-Type: application/json
1317
+
1318
+ {"ping":"Ping request"}
1319
+ ```
1320
+
1321
+ **Poll commands**
1322
+
1323
+ Poll commands are those that are stored in the IoT Agent waiting to be retrieved by the devices. This kind of commands
1324
+ are typically used for devices that doesn't have a public IP or the IP cannot be reached because of power or netkork
1325
+ constrictions. The device connects to the IoT Agent periodically to retrieve commands. In order to configure the device
1326
+ as poll commands you just need to avoid the usage of `endpoint` parameter in the device provision.
1327
+
1328
+ Once the command request is issued to the IoT agent, the command is stored waiting to be retrieved by the device. In
1329
+ that moment, the status of the command is `"<command>_status": "PENDING"`.
1330
+
1331
+ For HTTP devices, in order to retrieve a poll command, the need to make a GET request to the IoT Agent to the path used
1332
+ as `resource` in the provisioned group (`/iot/json` by default in IoTA-JSON if no `resource` is used) with the following
1333
+ parameters:
1334
+
1335
+ **FIXME [#1524](https://github.com/telefonicaid/iotagent-node-lib/issues/1524)**: `resource` different to the default
1336
+ one (`/iot/json` in the case of the [IoTA-JSON](https://github.com/telefonicaid/iotagent-json)) is not working at the
1337
+ present moment, but it will when this issue gets solved.
1338
+
1339
+ - `k`: API key of the device.
1340
+ - `i`: Device ID.
1341
+ - `getCmd`: This parameter is used to indicate the IoT Agent that the device is requesting a command. It is needed to
1342
+ set it to `1`
1343
+
1344
+ Taking the previous example, and considering the usage of the IoTA-JSON Agent, the device should make the following
1345
+ request, being the response to this request a JSON object with the command name as key and the command value as value:
1346
+
1347
+ **Request:**
1348
+
1349
+ ```
1350
+ GET /iot/json?k=<apikey>&i=<deviceId>&getCmd=1
1351
+ Accept: application/json
1352
+
1353
+ ```
1354
+
1355
+ **Response:**:
1356
+
1357
+ ```
1358
+ 200 OK
1359
+ Content-type: application/json
1360
+
1361
+ {"ping":"Ping request"}
1362
+ ```
1363
+
1364
+ For IoT Agents different from IoTA-JSON it is exactly the same just changing in the request the resource by the
1365
+ corresponding resource employed by the agent (i.e., IoTA-UL uses `/iot/d` as default resource instead of `/iot/json`)
1366
+ and setting the correct `<apikey>` and `<deviceId>`. The response will be also different depending on the IoT Agent
1367
+ employed.
1368
+
1369
+ **FIXME [#1524](https://github.com/telefonicaid/iotagent-node-lib/issues/1524)**: `resource` different to the default
1370
+ one (`/iot/json` in the case of the [IoTA-JSON](https://github.com/telefonicaid/iotagent-json)) is not working at the
1371
+ present moment, but it will when this issue gets solved.
1372
+
1373
+ **Request**
1374
+
1375
+ ```
1376
+ POST /iot/json?k=<apikey>&i=<deviceId>&getCmd=1
1377
+ Content-Type: application/json
1378
+
1379
+ {"t":25,"h":42,"l":"1299"}
1380
+ ```
1381
+
1382
+ **Response**
1383
+
1384
+ ```
1385
+ 200 OK
1386
+ Content-type: application/json
1387
+
1388
+ {"ping":"Ping request"}
1389
+ ```
1390
+
1391
+ This is also possible for IoTA-UL Agent changing in the request the resource, setting the correct `<apikey>`,
1392
+ `<deviceId>`, payload and headers.
1393
+
1394
+ Once the command is retrieved by the device the status is updated to `"<command>_status": "DELIVERED"`. Note that status
1395
+ `DELIVERED` only make sense in the case of poll commands. In the case of push command it cannot happen.
1396
+
1397
+ #### MQTT devices
1398
+
1399
+ For MQTT devices, it is not needed to declare an endpoint (i.e. if included in the provisioning request, it is not
1400
+ used). The device is supposed to be subscribed to the following MQTT topic where the IoT Agent will publish the command:
1401
+
1402
+ ```
1403
+ /<apiKey>/<deviceId>/cmd
1404
+ ```
1405
+
1406
+ In the case of using the IoTA-JSON Agent, the device should subscribe to the previous topic where it is going to receive
1407
+ a message like the following one when a command is triggered in the Context Broker like the previous step:
1408
+
1409
+ ```json
1410
+ { "ping": "Ping request" }
1411
+ ```
1412
+
1413
+ Please note that the device should subscribe to the broker using the disabled clean session mode (enabled using
1414
+ `--disable-clean-session` option CLI parameter in `mosquitto_sub`). This option means that all of the subscriptions for
1415
+ the device will be maintained after it disconnects, along with subsequent QoS 1 and QoS 2 commands that arrive. When the
1416
+ device reconnects, it will receive all of the queued commands.
1417
+
1418
+ ### Command confirmation
1419
+
1420
+ Once the command is completely processed by the device, it should return the result of the command to the IoT Agent.
1421
+ This result will be progressed to the Context Broker where it will be stored in the `<command>_info` attribute. The
1422
+ status of the command will be stored in the `<command>_status` attribute (`OK` if everything goes right).
1423
+
1424
+ For the IoTA-JSON, the payload of the confirmation message must be a JSON object with name of the command as key and the
1425
+ result of the command as value. For other IoT Agents, the payload must follow the corresponding protocol. For a given
1426
+ `ping` command, with a command result `status_ok`, the response payload should be:
1427
+
1428
+ ```JSON
1429
+ {"ping":"status_ok"}
1430
+ ```
1431
+
1432
+ Eventually, once the device makes the response request the IoTA would update the attributes `ping_status` to `OK` and
1433
+ `ping_info` to `status_ok` for the previous example.
1434
+
1435
+ #### HTTP
1436
+
1437
+ In order confirm the command execution, the device must make a POST request to the IoT Agent with the result of the
1438
+ command as payload, no matter if it is a push or a poll command. Following with the IoTAgent JSON case, the request must
1439
+ be made to the `/iot/json/commands`, this way:
1440
+
1441
+ ```
1442
+ POST /iot/json/commands?k=<apikey>&i=<deviceId>
1443
+ Content-Type: application/json
1444
+ Accept: application/json
1445
+
1446
+ {"ping":"status_ok"}
1447
+ ```
1448
+
1449
+ #### MQTT
1450
+
1451
+ The device should publish the result of the command (`{"ping":"status_ok"}` in the previous example) to a topic
1452
+ following the next pattern:
1453
+
1454
+ ```
1455
+ /<iotagent-protocol>/<apiKey>/<deviceId>/cmdexe
1456
+ ```
1457
+
1458
+ The IoTA is subscribed to that topic, so it gets the result of the command. When this happens, the status is updated
1459
+ to`"<command>_status": "OK"`. Also the result of the command delivered by the device is stored in the `<command>_info`
1460
+ attribute.
1046
1461
 
1047
1462
  ## Overriding global Context Broker host
1048
1463
 
@@ -1312,7 +1727,7 @@ Config group is represented by a JSON object with the following fields:
1312
1727
 
1313
1728
  The following actions are available under the config group endpoint:
1314
1729
 
1315
- #### Retrieve config groups `GET /iot/services`
1730
+ #### Retrieve config groups `GET /iot/groups`
1316
1731
 
1317
1732
  List all the config groups for the given `fiware-service` and `fiware-servicepath`. The config groups that match the
1318
1733
  `fiware-servicepath` are returned in any other case.
@@ -1336,14 +1751,14 @@ Successful operations return `Content-Type` header with `application/json` value
1336
1751
 
1337
1752
  _**Response payload**_
1338
1753
 
1339
- A JSON object with a services field that contains an array of services that match the request. See the
1340
- [config group datamodel](#service-group-datamodel) for more information.
1754
+ A JSON object with a `groups` field that contains an array of groups that match the request. See the
1755
+ [config group datamodel](#config-group-datamodel) for more information.
1341
1756
 
1342
1757
  Example:
1343
1758
 
1344
1759
  ```json
1345
1760
  {
1346
- "services": [
1761
+ "groups": [
1347
1762
  {
1348
1763
  "resource": "/deviceTest",
1349
1764
  "apikey": "801230BJKL23Y9090DSFL123HJK09H324HV8732",
@@ -1380,7 +1795,7 @@ Example:
1380
1795
  }
1381
1796
  ```
1382
1797
 
1383
- #### Create config group `POST /iot/services`
1798
+ #### Create config group `POST /iot/groups`
1384
1799
 
1385
1800
  Creates a set of config groups for the given service and service path. The service and subservice information will taken
1386
1801
  from the headers, overwritting any preexisting values.
@@ -1394,14 +1809,14 @@ _**Request headers**_
1394
1809
 
1395
1810
  _**Request payload**_
1396
1811
 
1397
- A JSON object with a `services` field. The value is an array of config groups objects to create. See the
1398
- [config group datamodel](#service-group-datamodel) for more information.
1812
+ A JSON object with a `groups` field. The value is an array of config groups objects to create. See the
1813
+ [config group datamodel](#config-group-datamodel) for more information.
1399
1814
 
1400
1815
  Example:
1401
1816
 
1402
1817
  ```json
1403
1818
  {
1404
- "services": [
1819
+ "groups": [
1405
1820
  {
1406
1821
  "resource": "/deviceTest",
1407
1822
  "apikey": "801230BJKL23Y9090DSFL123HJK09H324HV8732",
@@ -1435,7 +1850,7 @@ _**Response headers**_
1435
1850
 
1436
1851
  Successful operations return `Content-Type` header with `application/json` value.
1437
1852
 
1438
- #### Modify config group `PUT /iot/services`
1853
+ #### Modify config group `PUT /iot/groups`
1439
1854
 
1440
1855
  Modifies the information of a config group, identified by the `resource` and `apikey` query parameters. Takes a service
1441
1856
  group body as the payload. The body does not have to be complete: for incomplete bodies, just the attributes included in
@@ -1457,8 +1872,8 @@ _**Request headers**_
1457
1872
 
1458
1873
  _**Request payload**_
1459
1874
 
1460
- A JSON object with the config group information to be modified. See the
1461
- [config group datamodel](#service-group-datamodel) for more information.
1875
+ A JSON object with the config group information to be modified. See the [config group datamodel](config-group-datamodel)
1876
+ for more information.
1462
1877
 
1463
1878
  Example:
1464
1879
 
@@ -1475,7 +1890,7 @@ _**Response code**_
1475
1890
  - 400 MISSING_HEADERS if any of the mandatory headers is not present.
1476
1891
  - 500 SERVER ERROR if there was any error not contemplated above.:
1477
1892
 
1478
- #### Remove config group `DELETE /iot/services`
1893
+ #### Remove config group `DELETE /iot/groups`
1479
1894
 
1480
1895
  Removes a config group, identified by the `resource` and `apikey` query parameters.
1481
1896
 
@@ -1895,6 +2310,57 @@ Example:
1895
2310
  }
1896
2311
  ```
1897
2312
 
2313
+ ### Metrics
2314
+
2315
+ The IoT Agent Library exposes a [openmetrics-compatible](https://github.com/OpenObservability/OpenMetrics) endpoint for
2316
+ telemetry collectors to gather application statistics.
2317
+
2318
+ #### Retrieve metrics `GET /metrics`
2319
+
2320
+ _**Response code**_
2321
+
2322
+ - `200` `OK` if successful.
2323
+ - `406` `Wrong Accept Header` If accept format is not supported.
2324
+ - `500` `SERVER ERROR` if there was any error not contemplated above.
2325
+
2326
+ _**Response body**_
2327
+
2328
+ Returns the current value of the server stats,
2329
+
2330
+ - If `Accept` header contains `application/openmetrics-text; version=(1.0.0|0.0.1)`, the response has content-type
2331
+ `application/openmetrics-text; version=<the requested version>; charset=utf-8`
2332
+ - Else, If `Accept` header is missing or supports `text/plain` (explicitly or by `*/*`) , the response has
2333
+ content-type `text/plain; version=0.0.4; charset=utf-8` (legacy format for [prometheus](https://prometheus.io))
2334
+ - In any other case, returns an error message with `406` status.
2335
+
2336
+ For the kind of metrics exposed by the application, the actual payload itself is completely the same for both
2337
+ content-types, and follows the openmetrics specification, e.g:
2338
+
2339
+ ```
2340
+ # HELP deviceCreationRequests global metric for deviceCreationRequests
2341
+ # TYPE deviceCreationRequests counter
2342
+ deviceCreationRequests 0
2343
+ # HELP deviceRemovalRequests global metric for deviceRemovalRequests
2344
+ # TYPE deviceRemovalRequests counter
2345
+ deviceRemovalRequests 0
2346
+ # HELP measureRequests global metric for measureRequests
2347
+ # TYPE measureRequests counter
2348
+ measureRequests 0
2349
+ # HELP raiseAlarm global metric for raiseAlarm
2350
+ # TYPE raiseAlarm counter
2351
+ raiseAlarm 0
2352
+ # HELP releaseAlarm global metric for releaseAlarm
2353
+ # TYPE releaseAlarm counter
2354
+ releaseAlarm 0
2355
+ # HELP updateEntityRequestsOk global metric for updateEntityRequestsOk
2356
+ # TYPE updateEntityRequestsOk counter
2357
+ updateEntityRequestsOk 2
2358
+ # HELP updateEntityRequestsError global metric for updateEntityRequestsError
2359
+ # TYPE updateEntityRequestsError counter
2360
+ updateEntityRequestsError 5
2361
+ # EOF
2362
+ ```
2363
+
1898
2364
  [1]:
1899
2365
  https://czosel.github.io/jexl-playground/#/?context=%7B%0A%20%20%22longitude%22%3A%205%2C%0A%20%20%22latitude%22%3A%2037%2C%0A%20%20%22level%22%3A223%0A%7D&input=%7Bcoordinates%3A%20%5Blongitude%2Clatitude%5D%2C%20type%3A%20'Point'%7D&transforms=%7B%0A%7D
1900
2366
  [2]: