iotagent-node-lib 4.5.0 → 4.7.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 (124) hide show
  1. package/.github/workflows/ci.yml +0 -1
  2. package/Changelog +12 -0
  3. package/README.md +67 -272
  4. package/config.js +3 -1
  5. package/doc/README.md +1 -1
  6. package/doc/admin.md +40 -18
  7. package/doc/api.md +532 -136
  8. package/doc/deprecated.md +4 -0
  9. package/doc/devel/architecture.md +5 -135
  10. package/doc/devel/development.md +224 -12
  11. package/doc/getting-started.md +114 -53
  12. package/doc/requirements.txt +1 -1
  13. package/doc/roadmap.md +5 -5
  14. package/docker/Mosquitto/Dockerfile +2 -2
  15. package/docker/Mosquitto/README.md +14 -11
  16. package/lib/commonConfig.js +21 -2
  17. package/lib/constants.js +3 -0
  18. package/lib/fiware-iotagent-lib.js +12 -15
  19. package/lib/jexlTranformsMap.js +3 -1
  20. package/lib/model/Command.js +2 -2
  21. package/lib/model/Device.js +7 -3
  22. package/lib/model/Group.js +5 -3
  23. package/lib/model/dbConn.js +53 -115
  24. package/lib/services/commands/commandRegistryMongoDB.js +115 -75
  25. package/lib/services/common/alarmManagement.js +3 -0
  26. package/lib/services/common/iotManagerService.js +3 -1
  27. package/lib/services/devices/deviceRegistryMemory.js +36 -0
  28. package/lib/services/devices/deviceRegistryMongoDB.js +160 -87
  29. package/lib/services/devices/deviceService.js +33 -3
  30. package/lib/services/devices/devices-NGSI-v2.js +6 -1
  31. package/lib/services/groups/groupRegistryMongoDB.js +120 -83
  32. package/lib/services/groups/groupService.js +1 -1
  33. package/lib/services/ngsi/entities-NGSI-LD.js +320 -570
  34. package/lib/services/ngsi/entities-NGSI-v2.js +51 -3
  35. package/lib/services/ngsi/ngsiService.js +34 -1
  36. package/lib/services/northBound/deviceGroupAdministrationServer.js +42 -6
  37. package/lib/services/northBound/deviceProvisioningServer.js +12 -4
  38. package/lib/services/northBound/northboundServer.js +2 -0
  39. package/lib/services/stats/statsRegistry.js +128 -101
  40. package/lib/templates/createDevice.json +0 -24
  41. package/lib/templates/createDeviceLax.json +0 -23
  42. package/lib/templates/deviceGroup.json +1 -25
  43. package/lib/templates/updateDevice.json +12 -24
  44. package/lib/templates/updateDeviceLax.json +12 -23
  45. package/package.json +5 -5
  46. package/scripts/legacy_expression_tool/README.md +0 -1
  47. package/test/functional/README.md +22 -17
  48. package/test/functional/config-test.js +3 -2
  49. package/test/functional/functional-tests-runner.js +9 -4
  50. package/test/functional/functional-tests.js +4 -4
  51. package/test/functional/testCases.js +245 -4
  52. package/test/functional/testUtils.js +2 -2
  53. package/test/unit/examples/deviceProvisioningRequests/provisionFullDevice.json +1 -13
  54. package/test/unit/examples/groupProvisioningRequests/multipleConfigGroupsCreation.json +44 -0
  55. package/test/unit/examples/groupProvisioningRequests/provisionDuplicateConfigGroup.json +35 -0
  56. package/test/unit/examples/groupProvisioningRequests/provisionFullConfigGroup.json +36 -0
  57. package/test/unit/examples/groupProvisioningRequests/provisionFullConfigGroupAlternate.json +36 -0
  58. package/test/unit/examples/groupProvisioningRequests/provisionFullGroup.json +1 -0
  59. package/test/unit/general/config-multi-core-test.js +1 -2
  60. package/test/unit/general/contextBrokerKeystoneSecurityAccess-test.js +5 -4
  61. package/test/unit/general/deviceService-test.js +106 -3
  62. package/test/unit/general/statistics-service_test.js +1 -74
  63. package/test/unit/memoryRegistry/deviceRegistryMemory_test.js +6 -5
  64. package/test/unit/mongodb/mongodb-configGroup-registry-test.js +452 -0
  65. package/test/unit/mongodb/mongodb-connectionoptions-test.js +9 -42
  66. package/test/unit/mongodb/mongodb-group-registry-test.js +34 -33
  67. package/test/unit/mongodb/mongodb-service-registry-test.js +477 -0
  68. package/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin1a.json +4 -4
  69. package/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin2.json +22 -22
  70. package/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin29.json +4 -4
  71. package/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin32.json +14 -15
  72. package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin1.json +23 -23
  73. package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin15.json +0 -5
  74. package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin4.json +11 -16
  75. package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin5.json +23 -28
  76. package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin6.json +8 -13
  77. package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin7.json +0 -5
  78. package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin8.json +24 -29
  79. package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityTimestampPlugin2.json +12 -17
  80. package/test/unit/ngsi-ld/examples/contextRequests/updateContextStaticLinkedAttributes.json +12 -10
  81. package/test/unit/ngsi-ld/expressions/jexlBasedTransformations-test.js +1 -104
  82. package/test/unit/ngsi-ld/general/config-jsonld-contexts-test.js +1 -2
  83. package/test/unit/ngsi-ld/plugins/multientity-plugin_test.js +4 -5
  84. package/test/unit/ngsi-ld/provisioning/listProvisionedDevices-test.js +0 -4
  85. package/test/unit/ngsi-mixed/provisioning/ngsi-versioning-test.js +8 -5
  86. package/test/unit/ngsiv2/expressions/jexlBasedTransformations-test.js +42 -41
  87. package/test/unit/ngsiv2/general/contextBrokerOAuthSecurityAccess-test.js +11 -10
  88. package/test/unit/ngsiv2/general/deviceService-test.js +98 -4
  89. package/test/unit/ngsiv2/general/https-support-test.js +1 -1
  90. package/test/unit/ngsiv2/general/iotam-autoregistration-test.js +195 -0
  91. package/test/unit/ngsiv2/lazyAndCommands/active-devices-attribute-update-test.js +4 -3
  92. package/test/unit/ngsiv2/lazyAndCommands/command-test.js +6 -5
  93. package/test/unit/ngsiv2/lazyAndCommands/lazy-devices-test.js +17 -16
  94. package/test/unit/ngsiv2/lazyAndCommands/polling-commands-test.js +10 -18
  95. package/test/unit/ngsiv2/ngsiService/active-devices-test.js +21 -20
  96. package/test/unit/ngsiv2/ngsiService/staticAttributes-test.js +8 -7
  97. package/test/unit/ngsiv2/plugins/alias-plugin_test.js +12 -11
  98. package/test/unit/ngsiv2/plugins/custom-plugin_test.js +3 -2
  99. package/test/unit/ngsiv2/plugins/multientity-plugin_test.js +28 -27
  100. package/test/unit/ngsiv2/provisioning/device-group-api-test.js +265 -4
  101. package/test/unit/ngsiv2/provisioning/device-provisioning-api_test.js +12 -11
  102. package/test/unit/ngsiv2/provisioning/device-provisioning-configGroup-api_test.js +1190 -0
  103. package/test/unit/ngsiv2/provisioning/device-registration_test.js +5 -4
  104. package/test/unit/ngsiv2/provisioning/listProvisionedDevices-test.js +6 -9
  105. package/test/unit/ngsiv2/provisioning/provisionDeviceMultientity-test.js +1 -1
  106. package/test/unit/ngsiv2/provisioning/removeProvisionedDevice-test.js +5 -4
  107. package/test/unit/ngsiv2/provisioning/updateProvisionedDevices-test.js +8 -7
  108. package/test/unit/statsRegistry/openmetrics-test.js +167 -0
  109. package/lib/templates/queryContext.json +0 -25
  110. package/test/unit/examples/deviceProvisioningRequests/provisionBidirectionalDevice.json +0 -35
  111. package/test/unit/examples/deviceProvisioningRequests/provisionDeviceBidirectionalGroup.json +0 -17
  112. package/test/unit/examples/groupProvisioningRequests/bidirectionalGroup.json +0 -31
  113. package/test/unit/general/statistics-persistence_test.js +0 -121
  114. package/test/unit/ngsi-ld/examples/contextRequests/createBidirectionalDevice.json +0 -17
  115. package/test/unit/ngsi-ld/examples/contextRequests/updateContextProcessTimestamp.json +0 -12
  116. package/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotification.json +0 -13
  117. package/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotificationWithDatasetId.json +0 -21
  118. package/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotificationWithMetadata.json +0 -17
  119. package/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalSubscriptionRequest.json +0 -23
  120. package/test/unit/ngsi-ld/plugins/timestamp-processing-plugin_test.js +0 -132
  121. package/test/unit/ngsiv2/examples/contextRequests/createBidirectionalDevice.json +0 -8
  122. package/test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalNotification.json +0 -13
  123. package/test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalNotificationWithMetadata.json +0 -19
  124. package/test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalSubscriptionRequest.json +0 -24
package/doc/api.md CHANGED
@@ -10,7 +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)
13
+ - [Device to NGSI Mapping](#device-to-ngsi-mapping)
14
+ - [Device autoprovision and entity creation](#device-autoprovision-and-entity-creation)
15
+ - [Entity Name expression support](#entity-name-expression-support)
14
16
  - [Multientity support](#multientity-support)
15
17
  - [Metadata support](#metadata-support)
16
18
  - [NGSI LD data and metadata considerations](#ngsi-ld-data-and-metadata-considerations)
@@ -26,12 +28,17 @@
26
28
  - [Examples of JEXL expressions](#examples-of-jexl-expressions)
27
29
  - [Available functions](#available-functions)
28
30
  - [Expressions with multiple transformations](#expressions-with-multiple-transformations)
31
+ - [Expression support in metadata](#expression-support-in-metadata)
29
32
  - [Measurement transformation](#measurement-transformation)
30
33
  - [Measurement transformation definition](#measurement-transformation-definition)
31
34
  - [Measurement transformation execution](#measurement-transformation-execution)
32
35
  - [Measurement transformation order](#measurement-transformation-order)
33
36
  - [Multientity measurement transformation support (`object_id`)](#multientity-measurement-transformation-support-object_id)
34
- - [Timestamp Processing](#timestamp-processing)
37
+ - [Command execution](#command-execution)
38
+ - [Triggering commands](#triggering-commands)
39
+ - [Command reception](#command-reception)
40
+ - [Command confirmation](#command-confirmation)
41
+ - [TimeInstant and Timestamp flag](#timeinstant-and-timestamp-flag)
35
42
  - [Multimeasure support](#multimeasure-support)
36
43
  - [Overriding global Context Broker host](#overriding-global-context-broker-host)
37
44
  - [Multitenancy, FIWARE Service and FIWARE ServicePath](#multitenancy-fiware-service-and-fiware-servicepath)
@@ -44,10 +51,10 @@
44
51
  - [Config group API](#config-group-api)
45
52
  - [Config group datamodel](#config-group-datamodel)
46
53
  - [Config group operations](#config-group-operations)
47
- - [Retrieve config groups `GET /iot/services`](#retrieve-config-groups-get-iotservices)
48
- - [Create config group `POST /iot/services`](#create-config-group-post-iotservices)
49
- - [Modify config group `PUT /iot/services`](#modify-config-group-put-iotservices)
50
- - [Remove config group `DELETE /iot/services`](#remove-config-group-delete-iotservices)
54
+ - [Retrieve config groups `GET /iot/groups`](#retrieve-config-groups-get-iotgroups)
55
+ - [Create config group `POST /iot/groups`](#create-config-group-post-iotgroups)
56
+ - [Modify config group `PUT /iot/groups`](#modify-config-group-put-iotgroups)
57
+ - [Remove config group `DELETE /iot/groups`](#remove-config-group-delete-iotgroups)
51
58
  - [Device API](#device-api)
52
59
  - [Device datamodel](#device-datamodel)
53
60
  - [Device operations](#device-operations)
@@ -64,6 +71,8 @@
64
71
  - [Retrieve log level `GET /admin/log`](#retrieve-log-level-get-adminlog)
65
72
  - [About operations](#about-operations)
66
73
  - [List IoTA Information `GET /iot/about`](#list-iota-information-get-iotabout)
74
+ - [Metrics](#metrics)
75
+ - [Retrieve metrics `GET /metrics`](#retrieve-metrics-get-metrics)
67
76
 
68
77
  <!-- /TOC -->
69
78
 
@@ -128,9 +137,9 @@ For every config group, the pair (resource, apikey) _must_ be unique (as it is u
128
137
  which device). Those operations of the API targeting specific resources will need the use of the `resource` and `apikey`
129
138
  parameters to select the appropriate instance.
130
139
 
131
- Config groups can be created with preconfigured sets of attributes, service information, security information and other
132
- parameters. The specific parameters that can be configured for a given config group are described in the
133
- [Config group datamodel](#config-group-datamodel) section.
140
+ Config groups can be created with preconfigured sets of attributes, service and subservice information, security
141
+ information and other parameters. The specific parameters that can be configured for a given config group are described
142
+ in the [Config group datamodel](#config-group-datamodel) section.
134
143
 
135
144
  ### Devices
136
145
 
@@ -156,7 +165,7 @@ parameters defined at device level in database, the parameters are inherit from
156
165
 
157
166
  ### Uniqueness of groups and devices
158
167
 
159
- Group service uniqueness is defined by the combination of: service, subservice and apikey
168
+ Group uniqueness is defined by the combination of: resource and apikey. This is so because given a measure (identified by an apikey) there is no way to identify group related if apikey is not unique for all services and subservices.
160
169
 
161
170
  Device uniqueness is defined by the combination of: service, subservice, device_id and apikey. Note that several devices
162
171
  with the same device_id are allowed in the same service and subservice as long as their apikeys are different.
@@ -171,32 +180,47 @@ applies to autoprovisioned attributes and is also available at JEXL context with
171
180
 
172
181
  In case of provisioning attributes using `id` or `type` as names (please don't do that ;), they are ignored.
173
182
 
174
- ## Entity attributes
183
+ ## Device to NGSI Mapping
175
184
 
176
- In the config group/device model there are four list of attributes with different purpose to configure how the
177
- information coming from the device (measures) is mapped to the Context Broker attributes:
185
+ The way to map the information coming or going to the device to the NGSI attributes is defined in the group or device.
186
+ It is possible to define the entity type and the entity ID that a device will use in the Context Broker. It can be
187
+ configured for a single device in the device provisioning, or it can be defined for all the devices in a group.
188
+
189
+ The entity type should be defined both in the group and in the device, but the entity name (entity ID) is not defined in
190
+ the group. In that case, if there is no a existing device the same device ID, the entity name of the device generated
191
+ will be a concatenation of the entity type and the device ID (I.E: `entityType:device_id`). If you need to generate the
192
+ entity name differently, it is possible to define an expression to generate it, using the parameter `entityNameExp` in
193
+ the group as described in the [Entity Name expression support](#entity-name-expression-support) section.
194
+
195
+ It is also possible to configure how each of the measures obtained from the device is mapped to different attributes.
196
+ The name and type of the attribute is configured by the user (globally for all the devices in the group or in a per
197
+ device preprovisioning). Device measures can have four different behaviors:
178
198
 
179
199
  - **`attributes`**: Are measures that are pushed from the device to the IoT agent. This measure changes will be sent
180
200
  to the Context Broker as updateContext requests over the device entity. NGSI queries to the context broker will be
181
201
  resolved in the Broker database. For each attribute, its `name` and `type` must be provided. Additional `metadata`
182
- is optional.
202
+ is optional. They are called internally as _active attributes_.
183
203
 
184
204
  - **`lazy`**: Passive measures that are pulled from the device to the IoT agent. When a request for data from a lazy
185
- attribute arrives to the Context Broker, it forwards the request to the Context Provider of that entity, in this
186
- case the IoT Agent. The IoT Agent will then ask the device for the information needed, transform that information to
187
- a NGSI format and return it to the Context Broker. This operation will be synchronous from the customer perspective:
188
- the Context Broker won't return a response until the device has returned its response to the IoT Agent. For each
189
- attribute, its `name` and `type` must be provided.
205
+ attribute arrives to the Context Broker, it forwards the request to the IoT Agent (that behaves as NGSI Context
206
+ Provider for all the lazy attributes or commands). The IoT Agent will then ask the device for the information
207
+ needed, transform that information to a NGSI format and return it to the Context Broker. This operation will be
208
+ synchronous from the customer perspective: the Context Broker won't return a response until the device has returned
209
+ its response to the IoT Agent. For each attribute, its `name` and `type` must be provided. They are called
210
+ internally as _lazy attributes_.
190
211
 
191
212
  - **`static`**: It is static attributes that are persisted in the Context Broker. They are not updated by the device,
192
213
  but they can be modified by the user. They are useful to store information about the device that is not updated by
193
214
  the device itself. For instance, a `location` static attribute is can be used to store the location of a fixed
194
215
  device.
195
216
 
196
- - **`commands`**: Commands are actions that can be invoked in the device. They are similar to attributes, but they are
197
- not updated by the device. They are updated by the Context Broker, and the IoT Agent will be in charge of
198
- translating the updateContext request to the proper action in the device. Two additional attributes are created for
199
- each command: `status` and `info`. For each command, its `name` and `type` must be provided.
217
+ - **`commands`**: Commands are actions that can be invoked in the device. They are entity attributes, but they are not
218
+ updated by the device, they are updated by the Context Broker. In this case, the interaction will begin by setting
219
+ an attribute in the device's entity, for which the IoT Agent will be regitered as Context Provider. The IoT Agent
220
+ will return an immediate response to the Context Broker, and will be held responsible of contacting the device to
221
+ perform the command itself using the device specific protocol. Special `status` and `info` attributes should be
222
+ update. For each command, its `name` and `type` must be provided. For further information, please refer to
223
+ [Command execution](#command-execution) section.
200
224
 
201
225
  All of them have the same syntax, a list of objects with the following attributes:
202
226
 
@@ -237,6 +261,70 @@ Note that, when information coming from devices, this means measures, are not de
237
261
  device, the IoT agent will store that information into the destination entity using the same attribute name than the
238
262
  measure name, unless `explicitAttrs` is defined. Measures `id` or `type` names are invalid, and will be ignored.
239
263
 
264
+ ## Device autoprovision and entity creation
265
+
266
+ For those agents that uses IoTA Node LIB version 3.4.0 or higher, you should consider that the entity is not created
267
+ automatically when a device is created. This means that all entities into the Context Broker are created when data
268
+ arrives from a device, no matter if the device is explicitly provisioned (via
269
+ [device provisioning API](#create-device-post-iotdevices)) or autoprovisioned.
270
+
271
+ If for any reason you need the entity at CB before the first measure of the corresponding device arrives to the
272
+ IOTAgent, you can create it in advance using the Context Broker
273
+ [NGSI v2 API](https://github.com/telefonicaid/fiware-orion/blob/master/doc/manuals/orion-api.md).
274
+
275
+ ## Entity Name expression support
276
+
277
+ By default, the entity name used to persist the device measures in the Context Broker can be defined in the device
278
+ provisioning or, if the device is autoprovisioned, it is generated by the IoT Agent as a concatenation of the entity
279
+ type and the device ID. If you need to generate the entity name differently, it is possible to define an expression to
280
+ generate it, using the [Expression Language](#expression-language-support) through the `entityNameExp` field in the
281
+ group.
282
+
283
+ With this feature, the entity name can be generated dynamically based not only on the device ID and entity type, but
284
+ also on the measures reported by the device or any other context information. The `entityNameExp` field is only
285
+ available at the group level. **Important**: when using `entityNameExp`, the `entity_name` field in the device
286
+ provisioning is ignored. This means that the entity name used to store the device information in the Context Broker is
287
+ always generated by the `entityNameExp` expression. If you need to explicitly define the entity name for a particular
288
+ device, you can include a particular condition in the `entityNameExp` expression to handle that case (e.g.
289
+ `id == 'myDevice' ? 'myEntity' : entityType + ':' + id`).
290
+
291
+ The following example shows how to define an entity name expression:
292
+
293
+ ```json
294
+ {
295
+ "services": [
296
+ {
297
+ "resource": "/json",
298
+ "apikey": "801230BJKL23Y9090DSFL123HJK09H324HV8732",
299
+ "entity_type": "TemperatureSensor",
300
+ "entityNameExp": "id + '__' + sn",
301
+ "attributes": [
302
+ {
303
+ "object_id": "t",
304
+ "name": "temperature",
305
+ "type": "Number"
306
+ },
307
+ {
308
+ "object_id": "sn",
309
+ "name": "serialNumber",
310
+ "type": "Text"
311
+ }
312
+ ]
313
+ }
314
+ ]
315
+ }
316
+ ```
317
+
318
+ As defined above, the `entityNameExp` is `id + '__' + sn` and it will generate the entity name by concatenating the
319
+ device ID and the serial number reported by the device. For example, for a given measure with `id` equal to `dev123` and
320
+ `sn` equal to `ABCDEF`, the resulting entity name will be `dev123__ABCDEF`.
321
+
322
+ Note that, when using `entityNameExp`, the `entity_name` of the device provisioning is set to the result of the
323
+ expression the first time the device is created. If the expression is modified later, the `entity_name` of the device
324
+ provisioning will not be updated, but the value used to persist the device measures in the Context Broker will be the
325
+ result of the new expression. This can lead to a situation where the `entity_name` of the device provisioning and the
326
+ entity name used in the Context Broker are different.
327
+
240
328
  ## Multientity support
241
329
 
242
330
  The IOTA is able to persists measures coming from a single device to more than one entity, declaring the target entities
@@ -320,45 +408,6 @@ e.g.:
320
408
  }
321
409
  ```
322
410
 
323
- Metadata could also has `expression` like attributes in order to expand it:
324
-
325
- e.g.:
326
-
327
- ```json
328
- {
329
- "entity_type": "Lamp",
330
- "resource": "/iot/d",
331
- "protocol": "PDI-IoTA-UltraLight",
332
- "commands": [
333
- { "name": "on", "type": "command" },
334
- { "name": "off", "type": "command" }
335
- ],
336
- "attributes": [
337
- { "object_id": "s", "name": "state", "type": "Text" },
338
- {
339
- "object_id": "l",
340
- "name": "luminosity",
341
- "type": "Integer",
342
- "metadata": {
343
- "unitCode": { "type": "Text", "value": "CAL" }
344
- }
345
- }
346
- ],
347
- "static_attributes": [
348
- { "name": "category", "type": "Text", "value": ["actuator", "sensor"] },
349
- {
350
- "name": "controlledProperty",
351
- "type": "Text",
352
- "value": ["light"],
353
- "metadata": {
354
- "includes": { "type": "Text", "value": ["state", "luminosity"], "expression": "level / 100" },
355
- "alias": { "type": "Text", "value": "lamp" }
356
- }
357
- }
358
- ]
359
- }
360
- ```
361
-
362
411
  ### NGSI-LD data and metadata considerations
363
412
 
364
413
  When provisioning devices for an NGSI-LD Context Broker, `type` values should typically correspond to one of the
@@ -466,7 +515,8 @@ mappings of the provision. If `explicitAttrs` is provided both at device and con
466
515
  precedence. Additionally `explicitAttrs` can be used to define which measures (identified by their attribute names, not
467
516
  by their object_id) defined in JSON/JEXL array will be propagated to NGSI interface.
468
517
 
469
- Note that when `explicitAttrs` is an array or a JEXL expression resulting in to Array, if this array is empty then `TimeInstant` is not propaged to CB.
518
+ Note that when `explicitAttrs` is an array or a JEXL expression resulting in to Array, if this array is empty then
519
+ `TimeInstant` is not propaged to CB.
470
520
 
471
521
  The different possibilities are summarized below:
472
522
 
@@ -556,9 +606,11 @@ really useful when you need to adapt measure (for example, to change the units,
556
606
  of expression in the IoT Agent are:
557
607
 
558
608
  - [Measurement transformation](#measurement-transformation).
609
+ - [Metadata](#expression-support-in-metadata)
559
610
  - Commands payload transformation (push and pull).
560
611
  - Auto provisioned devices entity name. It is configured at config Group level by setting the `entityNameExp`
561
- parameter. It defines an expression to generate the Entity Name for autoprovisioned devices.
612
+ parameter. It defines an expression to generate the Entity Name for autoprovisioned devices. More information in the
613
+ [Entity Name expression support](#entity-name-expression-support) section.
562
614
  - Dynamic `endpoint` definition. Configured at device level, it defines where the device listen for push http
563
615
  commands. It can be either a static value or an expression.
564
616
 
@@ -568,12 +620,19 @@ expression. In all cases the following data is available to all expressions:
568
620
  - `id`: device ID
569
621
  - `entity_name`: NGSI entity Name (principal)
570
622
  - `type`: NGSI entity type (principal)
571
- - `service`: device service
572
- - `subservice`: device subservice
623
+ - `service`: device service (`Fiware-Service`)
624
+ - `subservice`: device subservice (`Fiware-ServicePath`)
573
625
  - `staticAttributes`: static attributes defined in the device or config group
626
+ - `oldCtxt`: previous JEXL context (related to last processed measure)
574
627
 
575
628
  Additionally, for attribute expressions (`expression`, `entity_name`), `entityNameExp` and metadata expressions
576
- (`expression`) measures are available in the **context** used to evaluate them.
629
+ (`expression`) the following is available in the **context** used to evalute:
630
+
631
+ - measures, as `<AttributeName>`
632
+ - metadata (both for attribute measurement in the case of NGSI-v2 measurements and static attribute) are available in
633
+ the **context** under the following convention: `metadata.<AttributeName>.<MetadataName>` or
634
+ `metadata.<StaticAttributeName>.<MetadataName>` in a similar way of defined for
635
+ [Context Broker](https://github.com/telefonicaid/fiware-orion/blob/master/doc/manuals/orion-api.md#metadata-support)
577
636
 
578
637
  ### Examples of JEXL expressions
579
638
 
@@ -619,50 +678,52 @@ to incorporate new transformations from the IoT Agent configuration file in a fa
619
678
 
620
679
  Current common transformation set:
621
680
 
622
- | JEXL Transformation | Equivalent JavaScript Function |
623
- | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- |
624
- | jsonparse: (str) | `JSON.parse(str);` |
625
- | jsonstringify: (obj) | `JSON.stringify(obj);` |
626
- | indexOf: (val, char) | `String(val).indexOf(char);` |
627
- | length: (val) | `String(val).length;` |
628
- | trim: (val) | `String(val).trim();` |
629
- | substr: (val, int1, int2) | `String(val).substr(int1, int2);` |
630
- | addreduce: (arr) | <code>arr.reduce((i, v) &vert; i + v));</code> |
631
- | lengtharray: (arr) | `arr.length;` |
632
- | typeof: (val) | `typeof val;` |
633
- | isarray: (arr) | `Array.isArray(arr);` |
634
- | isnan: (val) | `isNaN(val);` |
635
- | parseint: (val) | `parseInt(val);` |
636
- | parsefloat: (val) | `parseFloat(val);` |
637
- | toisodate: (val) | `new Date(val).toISOString();` |
638
- | timeoffset:(isostr) | `new Date(isostr).getTimezoneOffset();` |
639
- | tostring: (val) | `val.toString();` |
640
- | urlencode: (val) | `encodeURI(val);` |
641
- | urldecode: (val) | `decodeURI(val);` |
642
- | replacestr: (str, from, to) | `str.replace(from, to);` |
643
- | replaceregexp: (str, reg, to) | `str.replace(new RegExp(reg), to);` |
644
- | replaceallstr: (str, from, to) | `str.replaceAll(from, to);` |
645
- | replaceallregexp: (str, reg, to) | `str.replaceAll(new RegExp(reg,"g"), to);` |
646
- | split: (str, ch) | `str.split(ch);` |
647
- | joinarrtostr: (arr, ch) | `arr.join(ch);` |
648
- | concatarr: (arr, arr2) | `arr.concat(arr2);` |
649
- | mapper: (val, values, choices) | <code>choices[values.findIndex((target) &vert; target == val)]);</code> |
650
- | 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> |
651
- | bitwisemask: (i,mask,op,shf) | <code>(op==="&"?parseInt(i)&mask: op==="&vert;"?parseInt(i)&vert;mask: op==="^"?parseInt(i)^mask:i)>>shf;</code> |
652
- | slice: (arr, init, end) | `arr.slice(init,end);` |
653
- | addset: (arr, x) | <code>{ return Array.from((new Set(arr)).add(x)) }</code> |
654
- | removeset: (arr, x) | <code>{ let s = new Set(arr); s.delete(x); return Array.from(s) }</code> |
655
- | touppercase: (val) | `String(val).toUpperCase()` |
656
- | tolowercase: (val) | `String(val).toLowerCase()` |
657
- | round: (val) | `Math.round(val)` |
658
- | floor: (val) | `Math.floor(val)` |
659
- | ceil: (val) | `Math.ceil(val)` |
660
- | tofixed: (val, decimals) | `Number.parseFloat(val).toFixed(decimals)` |
661
- | gettime: (d) | `new Date(d).getTime()` |
662
- | toisostring: (d) | `new Date(d).toISOString()` |
663
- | localestring: (d, timezone, options) | `new Date(d).toLocaleString(timezone, options)` |
664
- | now: () | `Date.now()` |
665
- | hextostring: (val) | `new TextDecoder().decode(new Uint8Array(val.match(/.{1,2}/g).map(byte => parseInt(byte, 16))))` |
681
+ | JEXL Transformation | Equivalent JavaScript Function |
682
+ | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------ |
683
+ | jsonparse: (str) | `JSON.parse(str);` |
684
+ | jsonstringify: (obj) | `JSON.stringify(obj);` |
685
+ | indexOf: (val, char) | `String(val).indexOf(char);` |
686
+ | length: (val) | `String(val).length;` |
687
+ | trim: (val) | `String(val).trim();` |
688
+ | substr: (val, int1, int2) | `String(val).substr(int1, int2);` |
689
+ | addreduce: (arr) | <code>arr.reduce((i, v) &vert; i + v));</code> |
690
+ | lengtharray: (arr) | `arr.length;` |
691
+ | typeof: (val) | `typeof val;` |
692
+ | isarray: (arr) | `Array.isArray(arr);` |
693
+ | isnan: (val) | `isNaN(val);` |
694
+ | parseint: (val) | `parseInt(val);` |
695
+ | parsefloat: (val) | `parseFloat(val);` |
696
+ | toisodate: (val) | `new Date(val).toISOString();` |
697
+ | timeoffset:(isostr) | `new Date(isostr).getTimezoneOffset();` |
698
+ | tostring: (val) | `val.toString();` |
699
+ | urlencode: (val) | `encodeURI(val);` |
700
+ | urldecode: (val) | `decodeURI(val);` |
701
+ | replacestr: (str, from, to) | `str.replace(from, to);` |
702
+ | replaceregexp: (str, reg, to) | `str.replace(new RegExp(reg), to);` |
703
+ | replaceallstr: (str, from, to) | `str.replaceAll(from, to);` |
704
+ | replaceallregexp: (str, reg, to) | `str.replaceAll(new RegExp(reg,"g"), to);` |
705
+ | split: (str, ch) | `str.split(ch);` |
706
+ | joinarrtostr: (arr, ch) | `arr.join(ch);` |
707
+ | concatarr: (arr, arr2) | `arr.concat(arr2);` |
708
+ | mapper: (val, values, choices) | <code>choices[values.findIndex((target) &vert; target == val)]);</code> |
709
+ | 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> |
710
+ | bitwisemask: (i,mask,op,shf) | <code>(op==="&"?parseInt(i)&mask: op==="&vert;"?parseInt(i)&vert;mask: op==="^"?parseInt(i)^mask:i)>>shf;</code> |
711
+ | slice: (arr, init, end) | `arr.slice(init,end);` |
712
+ | addset: (arr, x) | <code>{ return Array.from((new Set(arr)).add(x)) }</code> |
713
+ | removeset: (arr, x) | <code>{ let s = new Set(arr); s.delete(x); return Array.from(s) }</code> |
714
+ | touppercase: (val) | `String(val).toUpperCase()` |
715
+ | tolowercase: (val) | `String(val).toLowerCase()` |
716
+ | round: (val) | `Math.round(val)` |
717
+ | floor: (val) | `Math.floor(val)` |
718
+ | ceil: (val) | `Math.ceil(val)` |
719
+ | tofixed: (val, decimals) | `Number.parseFloat(val).toFixed(decimals)` |
720
+ | gettime: (d) | `new Date(d).getTime()` |
721
+ | toisostring: (d) | `new Date(d).toISOString()` |
722
+ | localestring: (d, timezone, options) | `new Date(d).toLocaleString(timezone, options)` |
723
+ | now: () | `Date.now()` |
724
+ | hextostring: (val) | `new TextDecoder().decode(new Uint8Array(val.match(/.{1,2}/g).map(byte => parseInt(byte, 16))))` |
725
+ | valuePicker: (val,pick) | <code>valuePicker: (val,pick) => Object.entries(val).filter(([_, v]) => v === pick).map(([k, _]) => k)</code> |
726
+ | valuePickerMulti: (val,pick) | <code>valuePickerMulti: (val,pick) => Object.entries(val).filter(([_, v]) => pick.includes(v)).map(([k, _]) => k)</code> |
666
727
 
667
728
  You have available this [JEXL interactive playground][99] with all the transformations already loaded, in which you can
668
729
  test all the functions described above.
@@ -691,6 +752,50 @@ Another example using functions that return more than one value is the following
691
752
 
692
753
  For a location value `"40.4165, -3.70256"`, the result of the previous expression will be `-3.70256`.
693
754
 
755
+ ### Expression support in metadata
756
+
757
+ Metadata could also has `expression` like attributes in order to expand it:
758
+
759
+ e.g.:
760
+
761
+ ```json
762
+ {
763
+ "entity_type": "Lamp",
764
+ "resource": "/iot/d",
765
+ "protocol": "PDI-IoTA-UltraLight",
766
+ "commands": [
767
+ { "name": "on", "type": "command" },
768
+ { "name": "off", "type": "command" }
769
+ ],
770
+ "attributes": [
771
+ { "object_id": "s", "name": "state", "type": "Text" },
772
+ {
773
+ "object_id": "l",
774
+ "name": "luminosity",
775
+ "type": "Integer",
776
+ "metadata": {
777
+ "unitCode": { "type": "Text", "value": "CAL" }
778
+ }
779
+ }
780
+ ],
781
+ "static_attributes": [
782
+ { "name": "category", "type": "Text", "value": ["actuator", "sensor"] },
783
+ {
784
+ "name": "controlledProperty",
785
+ "type": "Text",
786
+ "value": ["light"],
787
+ "metadata": {
788
+ "includes": { "type": "Text", "value": ["state", "luminosity"], "expression": "level / 100" },
789
+ "alias": { "type": "Text", "value": "lamp" }
790
+ }
791
+ }
792
+ ]
793
+ }
794
+ ```
795
+
796
+ Note that there is no order into metadata structure and there is no warranty about which metadata attribute expression
797
+ will be evaluated first.
798
+
694
799
  ## Measurement transformation
695
800
 
696
801
  The IoTAgent Library provides support for measurement transformation using a
@@ -1017,13 +1122,34 @@ Will now generate the following NGSI v2 payload:
1017
1122
  }
1018
1123
  ```
1019
1124
 
1020
- ## Timestamp Processing
1125
+ ## TimeInstant and Timestamp flag
1126
+
1127
+ As part of the device to entity mapping process, the IoT Agent creates and updates automatically a special timestamp
1128
+ attribute called `TimeInstant`. This timestamp is represented as two different properties of the mapped entity:
1129
+
1130
+ - An attribute `TimeInstant` is added to updated entities in the case of NGSI-v2, which captures as an ISO8601
1131
+ timestamp when the associated measurement was observed. With NGSI-LD, the Standard `observedAt` property is used
1132
+ instead
1021
1133
 
1022
- Timestamp processing done by IOTA is as follows:
1134
+ - With NGSI-v2, an attribute metadata named `TimeInstant` per active or lazy attribute mapped, which captures as an
1135
+ ISO8601 timestamp when the associated measurement (represented as attribute value) was observed. With NGSI-LD, the
1136
+ Standard `observedAt` property-of-a-property is used instead.
1023
1137
 
1024
- - An attribute `TimeInstant` is added to updated entities
1025
- - In the case of NGSI-v2, a `TimeInstant` metadata is added in each updated attribute. With NGSI-LD, the Standard
1026
- `observedAt` property-of-a-property is used instead.
1138
+ If timestamp is not explicily defined when sending the measures through the IoT Agent (for further details on how to
1139
+ attach timestamp information to the measures, please refer to the particular IoT Agent implementation documentation),
1140
+ the arrival time on the server when receiving the measurement will be used to generate a `TimeInstant` for both the
1141
+ entity attribute and the attribute metadata.
1142
+
1143
+ This functionality can be turned on and off globaly through the use of the `timestamp` configuration flag or
1144
+ `IOTA_TIMESTAMP` variable as well as `timestamp` flag in device or group provision (in this case, the device or group
1145
+ level flag takes precedence over the global one). The default value is `true`.
1146
+
1147
+ The `timestamp` configuration value used, according to the priority:
1148
+
1149
+ 1. The one defined at device level
1150
+ 2. The one defined at group level (if not defined at device level)
1151
+ 3. The one defined at [IoTA configuration level](admin.md#timestamp) / `IOTA_TIMESTAMP` env var (if not defined at
1152
+ group level or device level)
1027
1153
 
1028
1154
  Depending on the `timestamp` configuration and if the measure contains a value named `TimeInstant` with a correct value,
1029
1155
  the IoTA behaviour is described in the following table:
@@ -1037,13 +1163,6 @@ the IoTA behaviour is described in the following table:
1037
1163
  | Not defined | Yes | TimeInstant and metadata updated with measure value |
1038
1164
  | Not defined | No | TimeInstant and metadata updated with server timestamp |
1039
1165
 
1040
- The `timestamp` conf value used is:
1041
-
1042
- - The one defined at device level
1043
- - The one defined at group level (if not defined at device level)
1044
- - The one defined at [IoTA configuration level](admin.md#timestamp) / `IOTA_TIMESTAMP` env var (if not defined at
1045
- group level or device level)
1046
-
1047
1166
  Some additional considerations to take into account:
1048
1167
 
1049
1168
  - If there is an attribute which maps a measure to `TimeInstant` attribute (after
@@ -1053,6 +1172,18 @@ Some additional considerations to take into account:
1053
1172
  - If the resulting `TimeInstant` not follows [ISO_8601](https://en.wikipedia.org/wiki/ISO_8601) (either from a direct
1054
1173
  measure of after a mapping, as described in the previous bullet) then it is refused (so a failover to server
1055
1174
  timestamp will take place).
1175
+ - the timestamp of different attributes belonging to the same measurement record may not be equal.
1176
+ - the arrival time and the measurement timestamp will not be the same in the general case (when explicitly defining
1177
+ the timestamp in the measurement)
1178
+ - if `timezone` field is defined as part of the provisioning of the device or group, timestamp fields will be
1179
+ generated using it. For instance, if `timezone` is set to `America/Los_Angeles`, a possible timestamp could be
1180
+ `2025-08-05T00:35:01.468-07:00`. If `timezone` field is not defined, by default Zulu Time Zone (UTC +0) will be
1181
+ used. Following the previous example, timestamp could be `2015-08-05T07:35:01.468Z`.
1182
+
1183
+ E.g.: in the case of a device that can take measurements every hour of both temperature and humidity and sends the data
1184
+ once every day, at midnight, the `TimeInstant` reported for each measurement will be the hour when that measurement was
1185
+ observed (e.g. 4:00 PM), while all the measurements will have an arrival time around midnight. If no timestamps were
1186
+ reported with such measurements, the `TimeInstant` attribute would take those values around midnight.
1056
1187
 
1057
1188
  ## Multimeasure support
1058
1189
 
@@ -1175,6 +1306,215 @@ In this case a batch update (`POST /v2/op/update`) to CB will be generated with
1175
1306
  }
1176
1307
  ```
1177
1308
 
1309
+ ## Command execution
1310
+
1311
+ This section reviews the end-to-end process to trigger and receive commands into devices. The URL paths and messages
1312
+ format is based on the [IoT Agent JSON](https://github.com/telefonicaid/iotagent-json). It may differ in the case of
1313
+ using any other IoT Agent. In that case, please refer to the specific IoTA documentation.
1314
+
1315
+ ### Triggering commands
1316
+
1317
+ This starts the process of sending data to devices. It starts by updating an attribute into the Context Broker defined
1318
+ as `command` in the [config group](#config-group-datamodel) or in the [device provision](#device-datamodel). Commands
1319
+ attributes are created using `command` as attribute type. Also, you can define the protocol you want the commands to be
1320
+ sent (HTTP/MQTT) with the `transport` parameter at the provisioning process.
1321
+
1322
+ For a given device provisioned with a `ping` command defined, any update on this attribute "ping" at the NGSI entity in
1323
+ the Context Broker will send a command to your device. For instance, to send the `ping` command with value
1324
+ `Ping request` you could use the following operation in the Context Broker API:
1325
+
1326
+ ```
1327
+ PUT /v2/entities/<entity_id>/attrs/ping?type=<entity_type>
1328
+
1329
+ {
1330
+ "value": "Ping request",
1331
+ "type": "command"
1332
+ }
1333
+
1334
+ ```
1335
+
1336
+ It is important to note that parameter `type`, with the entity type must be included.
1337
+
1338
+ Context Broker API is quite flexible and allows to update an attribute in several ways. Please have a look to the
1339
+ [Orion API](<[http://telefonicaid.github.io/fiware-orion/api/v2/stable](https://github.com/telefonicaid/fiware-orion/blob/master/doc/manuals/orion-api.md)>)
1340
+ for details.
1341
+
1342
+ **Important note**: don't use operations in the NGSI API with creation semantics. Otherwise, the entity/attribute will
1343
+ be created locally to Context Broker and the command will not progress to the device (and you will need to delete the
1344
+ created entity/attribute if you want to make it to work again). Thus, the following operations _must not_ be used:
1345
+
1346
+ - `POST /v2/entities`
1347
+ - `POST /v2/entities/<id>/attrs`
1348
+ - `PUT /v2/entities/<id>/attrs`
1349
+ - `POST /v2/op/entites` with `actionType` `append`, `appendStrict` or `replace`
1350
+
1351
+ ### Command reception
1352
+
1353
+ Once the command is triggered, it is send to the device. Depending on transport protocol, it is going to be sent to the
1354
+ device in a different way. After sending the command, the IoT Agent will have updated the value of `ping_status` to
1355
+ `PENDING` for entity into the Context Broker. Neither `ping_info` nor `ping` will be updated.
1356
+
1357
+ #### HTTP devices
1358
+
1359
+ **Push commands**
1360
+
1361
+ Push commands are those that are sent to the device once the IoT Agent receives the request from the Context Broker. In
1362
+ order to send push commands it is needed to set the `"endpoint": "http://[DEVICE_IP]:[PORT]/"` in the device or group
1363
+ provision. The device is supposed to be listening for commands at that URL in a synchronous way. Make sure the device
1364
+ endpoint is reachable by the IoT Agent. Push commands are only valid for HTTP devices. For MQTT devices it is not needed
1365
+ to set the `endpoint` parameter.
1366
+
1367
+ Considering using the IoTA-JSON Agent, and given the previous example, the device should receive a POST request to
1368
+ `http://[DEVICE_IP]:[PORT]` with the following payload:
1369
+
1370
+ ```
1371
+ POST /
1372
+ Content-Type: application/json
1373
+
1374
+ {"ping":"Ping request"}
1375
+ ```
1376
+
1377
+ **Poll commands**
1378
+
1379
+ Poll commands are those that are stored in the IoT Agent waiting to be retrieved by the devices. This kind of commands
1380
+ are typically used for devices that doesn't have a public IP or the IP cannot be reached because of power or netkork
1381
+ constrictions. The device connects to the IoT Agent periodically to retrieve commands. In order to configure the device
1382
+ as poll commands you just need to avoid the usage of `endpoint` parameter in the device provision.
1383
+
1384
+ Once the command request is issued to the IoT agent, the command is stored waiting to be retrieved by the device. In
1385
+ that moment, the status of the command is `"<command>_status": "PENDING"`.
1386
+
1387
+ 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
1388
+ as `resource` in the provisioned group (`/iot/json` by default in IoTA-JSON if no `resource` is used) with the following
1389
+ parameters:
1390
+
1391
+ **FIXME [#1524](https://github.com/telefonicaid/iotagent-node-lib/issues/1524)**: `resource` different to the default
1392
+ one (`/iot/json` in the case of the [IoTA-JSON](https://github.com/telefonicaid/iotagent-json)) is not working at the
1393
+ present moment, but it will when this issue gets solved.
1394
+
1395
+ - `k`: API key of the device.
1396
+ - `i`: Device ID.
1397
+ - `getCmd`: This parameter is used to indicate the IoT Agent that the device is requesting a command. It is needed to
1398
+ set it to `1`
1399
+
1400
+ Taking the previous example, and considering the usage of the IoTA-JSON Agent, the device should make the following
1401
+ request, being the response to this request a JSON object with the command name as key and the command value as value:
1402
+
1403
+ **Request:**
1404
+
1405
+ ```
1406
+ GET /iot/json?k=<apikey>&i=<deviceId>&getCmd=1
1407
+ Accept: application/json
1408
+
1409
+ ```
1410
+
1411
+ **Response:**:
1412
+
1413
+ ```
1414
+ 200 OK
1415
+ Content-type: application/json
1416
+
1417
+ {"ping":"Ping request"}
1418
+ ```
1419
+
1420
+ For IoT Agents different from IoTA-JSON it is exactly the same just changing in the request the resource by the
1421
+ corresponding resource employed by the agent (i.e., IoTA-UL uses `/iot/d` as default resource instead of `/iot/json`)
1422
+ and setting the correct `<apikey>` and `<deviceId>`. The response will be also different depending on the IoT Agent
1423
+ employed.
1424
+
1425
+ **FIXME [#1524](https://github.com/telefonicaid/iotagent-node-lib/issues/1524)**: `resource` different to the default
1426
+ one (`/iot/json` in the case of the [IoTA-JSON](https://github.com/telefonicaid/iotagent-json)) is not working at the
1427
+ present moment, but it will when this issue gets solved.
1428
+
1429
+ **Request**
1430
+
1431
+ ```
1432
+ POST /iot/json?k=<apikey>&i=<deviceId>&getCmd=1
1433
+ Content-Type: application/json
1434
+
1435
+ {"t":25,"h":42,"l":"1299"}
1436
+ ```
1437
+
1438
+ **Response**
1439
+
1440
+ ```
1441
+ 200 OK
1442
+ Content-type: application/json
1443
+
1444
+ {"ping":"Ping request"}
1445
+ ```
1446
+
1447
+ This is also possible for IoTA-UL Agent changing in the request the resource, setting the correct `<apikey>`,
1448
+ `<deviceId>`, payload and headers.
1449
+
1450
+ Once the command is retrieved by the device the status is updated to `"<command>_status": "DELIVERED"`. Note that status
1451
+ `DELIVERED` only make sense in the case of poll commands. In the case of push command it cannot happen.
1452
+
1453
+ #### MQTT devices
1454
+
1455
+ For MQTT devices, it is not needed to declare an endpoint (i.e. if included in the provisioning request, it is not
1456
+ used). The device is supposed to be subscribed to the following MQTT topic where the IoT Agent will publish the command:
1457
+
1458
+ ```
1459
+ /<apiKey>/<deviceId>/cmd
1460
+ ```
1461
+
1462
+ In the case of using the IoTA-JSON Agent, the device should subscribe to the previous topic where it is going to receive
1463
+ a message like the following one when a command is triggered in the Context Broker like the previous step:
1464
+
1465
+ ```json
1466
+ { "ping": "Ping request" }
1467
+ ```
1468
+
1469
+ Please note that the device should subscribe to the broker using the disabled clean session mode (enabled using
1470
+ `--disable-clean-session` option CLI parameter in `mosquitto_sub`). This option means that all of the subscriptions for
1471
+ the device will be maintained after it disconnects, along with subsequent QoS 1 and QoS 2 commands that arrive. When the
1472
+ device reconnects, it will receive all of the queued commands.
1473
+
1474
+ ### Command confirmation
1475
+
1476
+ Once the command is completely processed by the device, it should return the result of the command to the IoT Agent.
1477
+ This result will be progressed to the Context Broker where it will be stored in the `<command>_info` attribute. The
1478
+ status of the command will be stored in the `<command>_status` attribute (`OK` if everything goes right).
1479
+
1480
+ For the IoTA-JSON, the payload of the confirmation message must be a JSON object with name of the command as key and the
1481
+ result of the command as value. For other IoT Agents, the payload must follow the corresponding protocol. For a given
1482
+ `ping` command, with a command result `status_ok`, the response payload should be:
1483
+
1484
+ ```JSON
1485
+ {"ping":"status_ok"}
1486
+ ```
1487
+
1488
+ Eventually, once the device makes the response request the IoTA would update the attributes `ping_status` to `OK` and
1489
+ `ping_info` to `status_ok` for the previous example.
1490
+
1491
+ #### HTTP
1492
+
1493
+ In order confirm the command execution, the device must make a POST request to the IoT Agent with the result of the
1494
+ command as payload, no matter if it is a push or a poll command. Following with the IoTAgent JSON case, the request must
1495
+ be made to the `/iot/json/commands`, this way:
1496
+
1497
+ ```
1498
+ POST /iot/json/commands?k=<apikey>&i=<deviceId>
1499
+ Content-Type: application/json
1500
+ Accept: application/json
1501
+
1502
+ {"ping":"status_ok"}
1503
+ ```
1504
+
1505
+ #### MQTT
1506
+
1507
+ The device should publish the result of the command (`{"ping":"status_ok"}` in the previous example) to a topic
1508
+ following the next pattern:
1509
+
1510
+ ```
1511
+ /<iotagent-protocol>/<apiKey>/<deviceId>/cmdexe
1512
+ ```
1513
+
1514
+ The IoTA is subscribed to that topic, so it gets the result of the command. When this happens, the status is updated
1515
+ to`"<command>_status": "OK"`. Also the result of the command delivered by the device is stored in the `<command>_info`
1516
+ attribute.
1517
+
1178
1518
  ## Overriding global Context Broker host
1179
1519
 
1180
1520
  **cbHost**: Context Broker host URL. This option can be used to override the global CB configuration for specific types
@@ -1431,19 +1771,21 @@ Config group is represented by a JSON object with the following fields:
1431
1771
  | `static_attributes` | ✓ | | | this attributes will be added to all the entities of this group 'as is', additional `metadata` is optional. |
1432
1772
  | `internal_attributes` | ✓ | | | optional section with free format, to allow specific IoT Agents to store information along with the devices in the Device Registry. |
1433
1773
  | `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) |
1434
- | `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>`) |
1774
+ | `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) |
1435
1775
  | `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. |
1436
1776
  | `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. |
1437
1777
  | `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`. |
1438
1778
  | `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`. |
1439
1779
  | `transport` | ✓ | `string` | | Transport protocol used by the group of devices to send updates, for the IoT Agents with multiple transport protocols. |
1440
1780
  | `endpoint` | ✓ | `string` | | Endpoint where the group of device is going to receive commands, if any. |
1781
+ | `storeLastMeasure` | ✓ | `boolean` | | Store in device last measure received. See more info [in this section](admin.md#storelastmeasure). False by default |
1782
+ | `useCBflowControl` | ✓ | `boolean` | | Use Context Broker flow control. See more info [in this section](admin.md#useCBflowControl). False by default |
1441
1783
 
1442
1784
  ### Config group operations
1443
1785
 
1444
1786
  The following actions are available under the config group endpoint:
1445
1787
 
1446
- #### Retrieve config groups `GET /iot/services`
1788
+ #### Retrieve config groups `GET /iot/groups`
1447
1789
 
1448
1790
  List all the config groups for the given `fiware-service` and `fiware-servicepath`. The config groups that match the
1449
1791
  `fiware-servicepath` are returned in any other case.
@@ -1467,14 +1809,14 @@ Successful operations return `Content-Type` header with `application/json` value
1467
1809
 
1468
1810
  _**Response payload**_
1469
1811
 
1470
- A JSON object with a services field that contains an array of services that match the request. See the
1471
- [config group datamodel](#service-group-datamodel) for more information.
1812
+ A JSON object with a `groups` field that contains an array of groups that match the request. See the
1813
+ [config group datamodel](#config-group-datamodel) for more information.
1472
1814
 
1473
1815
  Example:
1474
1816
 
1475
1817
  ```json
1476
1818
  {
1477
- "services": [
1819
+ "groups": [
1478
1820
  {
1479
1821
  "resource": "/deviceTest",
1480
1822
  "apikey": "801230BJKL23Y9090DSFL123HJK09H324HV8732",
@@ -1511,7 +1853,7 @@ Example:
1511
1853
  }
1512
1854
  ```
1513
1855
 
1514
- #### Create config group `POST /iot/services`
1856
+ #### Create config group `POST /iot/groups`
1515
1857
 
1516
1858
  Creates a set of config groups for the given service and service path. The service and subservice information will taken
1517
1859
  from the headers, overwritting any preexisting values.
@@ -1525,14 +1867,14 @@ _**Request headers**_
1525
1867
 
1526
1868
  _**Request payload**_
1527
1869
 
1528
- A JSON object with a `services` field. The value is an array of config groups objects to create. See the
1529
- [config group datamodel](#service-group-datamodel) for more information.
1870
+ A JSON object with a `groups` field. The value is an array of config groups objects to create. See the
1871
+ [config group datamodel](#config-group-datamodel) for more information.
1530
1872
 
1531
1873
  Example:
1532
1874
 
1533
1875
  ```json
1534
1876
  {
1535
- "services": [
1877
+ "groups": [
1536
1878
  {
1537
1879
  "resource": "/deviceTest",
1538
1880
  "apikey": "801230BJKL23Y9090DSFL123HJK09H324HV8732",
@@ -1566,7 +1908,7 @@ _**Response headers**_
1566
1908
 
1567
1909
  Successful operations return `Content-Type` header with `application/json` value.
1568
1910
 
1569
- #### Modify config group `PUT /iot/services`
1911
+ #### Modify config group `PUT /iot/groups`
1570
1912
 
1571
1913
  Modifies the information of a config group, identified by the `resource` and `apikey` query parameters. Takes a service
1572
1914
  group body as the payload. The body does not have to be complete: for incomplete bodies, just the attributes included in
@@ -1588,8 +1930,8 @@ _**Request headers**_
1588
1930
 
1589
1931
  _**Request payload**_
1590
1932
 
1591
- A JSON object with the config group information to be modified. See the
1592
- [config group datamodel](#service-group-datamodel) for more information.
1933
+ A JSON object with the config group information to be modified. See the [config group datamodel](config-group-datamodel)
1934
+ for more information.
1593
1935
 
1594
1936
  Example:
1595
1937
 
@@ -1606,7 +1948,7 @@ _**Response code**_
1606
1948
  - 400 MISSING_HEADERS if any of the mandatory headers is not present.
1607
1949
  - 500 SERVER ERROR if there was any error not contemplated above.:
1608
1950
 
1609
- #### Remove config group `DELETE /iot/services`
1951
+ #### Remove config group `DELETE /iot/groups`
1610
1952
 
1611
1953
  Removes a config group, identified by the `resource` and `apikey` query parameters.
1612
1954
 
@@ -1658,6 +2000,9 @@ the API resource fields and the same fields in the database model.
1658
2000
  | `explicitAttrs` | ✓ | `boolean` | ✓ | Field to support selective ignore of measures so that IOTA doesn’t progress. See details in [specific section](#explicitly-defined-attributes-explicitattrs) |
1659
2001
  | `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. |
1660
2002
  | `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`. |
2003
+ | `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. |
2004
+ | `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). |
2005
+ | `useCBflowControl` | ✓ | `boolean` | | Use Context Broker flow control. See more info [in this section](admin.md#useCBflowControl). False by default. |
1661
2006
 
1662
2007
  ### Device operations
1663
2008
 
@@ -2026,6 +2371,57 @@ Example:
2026
2371
  }
2027
2372
  ```
2028
2373
 
2374
+ ### Metrics
2375
+
2376
+ The IoT Agent Library exposes a [openmetrics-compatible](https://github.com/OpenObservability/OpenMetrics) endpoint for
2377
+ telemetry collectors to gather application statistics.
2378
+
2379
+ #### Retrieve metrics `GET /metrics`
2380
+
2381
+ _**Response code**_
2382
+
2383
+ - `200` `OK` if successful.
2384
+ - `406` `Wrong Accept Header` If accept format is not supported.
2385
+ - `500` `SERVER ERROR` if there was any error not contemplated above.
2386
+
2387
+ _**Response body**_
2388
+
2389
+ Returns the current value of the server stats,
2390
+
2391
+ - If `Accept` header contains `application/openmetrics-text; version=(1.0.0|0.0.1)`, the response has content-type
2392
+ `application/openmetrics-text; version=<the requested version>; charset=utf-8`
2393
+ - Else, If `Accept` header is missing or supports `text/plain` (explicitly or by `*/*`) , the response has
2394
+ content-type `text/plain; version=0.0.4; charset=utf-8` (legacy format for [prometheus](https://prometheus.io))
2395
+ - In any other case, returns an error message with `406` status.
2396
+
2397
+ For the kind of metrics exposed by the application, the actual payload itself is completely the same for both
2398
+ content-types, and follows the openmetrics specification, e.g:
2399
+
2400
+ ```
2401
+ # HELP deviceCreationRequests global metric for deviceCreationRequests
2402
+ # TYPE deviceCreationRequests counter
2403
+ deviceCreationRequests 0
2404
+ # HELP deviceRemovalRequests global metric for deviceRemovalRequests
2405
+ # TYPE deviceRemovalRequests counter
2406
+ deviceRemovalRequests 0
2407
+ # HELP measureRequests global metric for measureRequests
2408
+ # TYPE measureRequests counter
2409
+ measureRequests 0
2410
+ # HELP raiseAlarm global metric for raiseAlarm
2411
+ # TYPE raiseAlarm counter
2412
+ raiseAlarm 0
2413
+ # HELP releaseAlarm global metric for releaseAlarm
2414
+ # TYPE releaseAlarm counter
2415
+ releaseAlarm 0
2416
+ # HELP updateEntityRequestsOk global metric for updateEntityRequestsOk
2417
+ # TYPE updateEntityRequestsOk counter
2418
+ updateEntityRequestsOk 2
2419
+ # HELP updateEntityRequestsError global metric for updateEntityRequestsError
2420
+ # TYPE updateEntityRequestsError counter
2421
+ updateEntityRequestsError 5
2422
+ # EOF
2423
+ ```
2424
+
2029
2425
  [1]:
2030
2426
  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
2031
2427
  [2]: