iotagent-node-lib 4.5.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 (84) hide show
  1. package/README.md +67 -272
  2. package/doc/README.md +1 -1
  3. package/doc/admin.md +3 -15
  4. package/doc/api.md +469 -134
  5. package/doc/deprecated.md +4 -0
  6. package/doc/devel/architecture.md +5 -135
  7. package/doc/devel/development.md +224 -12
  8. package/doc/getting-started.md +114 -53
  9. package/doc/roadmap.md +5 -5
  10. package/docker/Mosquitto/Dockerfile +2 -2
  11. package/docker/Mosquitto/README.md +14 -11
  12. package/lib/constants.js +3 -0
  13. package/lib/fiware-iotagent-lib.js +12 -15
  14. package/lib/jexlTranformsMap.js +3 -1
  15. package/lib/model/dbConn.js +1 -4
  16. package/lib/services/common/alarmManagement.js +3 -0
  17. package/lib/services/groups/groupService.js +1 -1
  18. package/lib/services/ngsi/entities-NGSI-LD.js +320 -570
  19. package/lib/services/ngsi/entities-NGSI-v2.js +36 -1
  20. package/lib/services/ngsi/ngsiService.js +3 -1
  21. package/lib/services/northBound/deviceGroupAdministrationServer.js +42 -6
  22. package/lib/services/northBound/deviceProvisioningServer.js +0 -1
  23. package/lib/services/northBound/northboundServer.js +2 -0
  24. package/lib/services/stats/statsRegistry.js +128 -101
  25. package/lib/templates/createDevice.json +0 -24
  26. package/lib/templates/createDeviceLax.json +0 -23
  27. package/lib/templates/deviceGroup.json +1 -25
  28. package/lib/templates/updateDevice.json +0 -24
  29. package/lib/templates/updateDeviceLax.json +0 -23
  30. package/package.json +2 -2
  31. package/scripts/legacy_expression_tool/README.md +0 -1
  32. package/test/functional/README.md +22 -17
  33. package/test/functional/functional-tests-runner.js +9 -4
  34. package/test/functional/functional-tests.js +4 -4
  35. package/test/functional/testCases.js +245 -4
  36. package/test/unit/examples/deviceProvisioningRequests/provisionFullDevice.json +1 -13
  37. package/test/unit/examples/groupProvisioningRequests/multipleConfigGroupsCreation.json +44 -0
  38. package/test/unit/examples/groupProvisioningRequests/provisionDuplicateConfigGroup.json +35 -0
  39. package/test/unit/examples/groupProvisioningRequests/provisionFullConfigGroup.json +36 -0
  40. package/test/unit/examples/groupProvisioningRequests/provisionFullConfigGroupAlternate.json +36 -0
  41. package/test/unit/general/deviceService-test.js +102 -0
  42. package/test/unit/general/statistics-service_test.js +1 -74
  43. package/test/unit/mongodb/mongodb-configGroup-registry-test.js +452 -0
  44. package/test/unit/mongodb/mongodb-connectionoptions-test.js +2 -3
  45. package/test/unit/mongodb/mongodb-group-registry-test.js +34 -33
  46. package/test/unit/mongodb/mongodb-service-registry-test.js +477 -0
  47. package/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin1a.json +4 -4
  48. package/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin2.json +22 -22
  49. package/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin29.json +4 -4
  50. package/test/unit/ngsi-ld/examples/contextRequests/updateContextExpressionPlugin32.json +14 -15
  51. package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin1.json +23 -23
  52. package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin15.json +0 -5
  53. package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin4.json +11 -16
  54. package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin5.json +23 -28
  55. package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin6.json +8 -13
  56. package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin7.json +0 -5
  57. package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityPlugin8.json +24 -29
  58. package/test/unit/ngsi-ld/examples/contextRequests/updateContextMultientityTimestampPlugin2.json +12 -17
  59. package/test/unit/ngsi-ld/examples/contextRequests/updateContextStaticLinkedAttributes.json +12 -10
  60. package/test/unit/ngsi-ld/expressions/jexlBasedTransformations-test.js +0 -102
  61. package/test/unit/ngsi-ld/plugins/multientity-plugin_test.js +4 -5
  62. package/test/unit/ngsi-ld/provisioning/listProvisionedDevices-test.js +0 -4
  63. package/test/unit/ngsiv2/general/deviceService-test.js +94 -1
  64. package/test/unit/ngsiv2/general/iotam-autoregistration-test.js +195 -0
  65. package/test/unit/ngsiv2/provisioning/device-group-api-test.js +259 -0
  66. package/test/unit/ngsiv2/provisioning/device-provisioning-configGroup-api_test.js +1189 -0
  67. package/test/unit/ngsiv2/provisioning/listProvisionedDevices-test.js +0 -4
  68. package/test/unit/statsRegistry/openmetrics-test.js +167 -0
  69. package/lib/templates/queryContext.json +0 -25
  70. package/test/unit/examples/deviceProvisioningRequests/provisionBidirectionalDevice.json +0 -35
  71. package/test/unit/examples/deviceProvisioningRequests/provisionDeviceBidirectionalGroup.json +0 -17
  72. package/test/unit/examples/groupProvisioningRequests/bidirectionalGroup.json +0 -31
  73. package/test/unit/general/statistics-persistence_test.js +0 -121
  74. package/test/unit/ngsi-ld/examples/contextRequests/createBidirectionalDevice.json +0 -17
  75. package/test/unit/ngsi-ld/examples/contextRequests/updateContextProcessTimestamp.json +0 -12
  76. package/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotification.json +0 -13
  77. package/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotificationWithDatasetId.json +0 -21
  78. package/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalNotificationWithMetadata.json +0 -17
  79. package/test/unit/ngsi-ld/examples/subscriptionRequests/bidirectionalSubscriptionRequest.json +0 -23
  80. package/test/unit/ngsi-ld/plugins/timestamp-processing-plugin_test.js +0 -132
  81. package/test/unit/ngsiv2/examples/contextRequests/createBidirectionalDevice.json +0 -8
  82. package/test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalNotification.json +0 -13
  83. package/test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalNotificationWithMetadata.json +0 -19
  84. package/test/unit/ngsiv2/examples/subscriptionRequests/bidirectionalSubscriptionRequest.json +0 -24
package/doc/api.md CHANGED
@@ -10,7 +10,8 @@
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)
14
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)
@@ -26,12 +27,17 @@
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)
35
41
  - [Multimeasure support](#multimeasure-support)
36
42
  - [Overriding global Context Broker host](#overriding-global-context-broker-host)
37
43
  - [Multitenancy, FIWARE Service and FIWARE ServicePath](#multitenancy-fiware-service-and-fiware-servicepath)
@@ -44,10 +50,10 @@
44
50
  - [Config group API](#config-group-api)
45
51
  - [Config group datamodel](#config-group-datamodel)
46
52
  - [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)
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)
51
57
  - [Device API](#device-api)
52
58
  - [Device datamodel](#device-datamodel)
53
59
  - [Device operations](#device-operations)
@@ -64,6 +70,8 @@
64
70
  - [Retrieve log level `GET /admin/log`](#retrieve-log-level-get-adminlog)
65
71
  - [About operations](#about-operations)
66
72
  - [List IoTA Information `GET /iot/about`](#list-iota-information-get-iotabout)
73
+ - [Metrics](#metrics)
74
+ - [Retrieve metrics `GET /metrics`](#retrieve-metrics-get-metrics)
67
75
 
68
76
  <!-- /TOC -->
69
77
 
@@ -128,9 +136,9 @@ For every config group, the pair (resource, apikey) _must_ be unique (as it is u
128
136
  which device). Those operations of the API targeting specific resources will need the use of the `resource` and `apikey`
129
137
  parameters to select the appropriate instance.
130
138
 
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.
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.
134
142
 
135
143
  ### Devices
136
144
 
@@ -156,7 +164,7 @@ parameters defined at device level in database, the parameters are inherit from
156
164
 
157
165
  ### Uniqueness of groups and devices
158
166
 
159
- 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
160
168
 
161
169
  Device uniqueness is defined by the combination of: service, subservice, device_id and apikey. Note that several devices
162
170
  with the same device_id are allowed in the same service and subservice as long as their apikeys are different.
@@ -171,32 +179,47 @@ applies to autoprovisioned attributes and is also available at JEXL context with
171
179
 
172
180
  In case of provisioning attributes using `id` or `type` as names (please don't do that ;), they are ignored.
173
181
 
174
- ## Entity attributes
182
+ ## Device to NGSI Mapping
175
183
 
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:
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:
178
197
 
179
198
  - **`attributes`**: Are measures that are pushed from the device to the IoT agent. This measure changes will be sent
180
199
  to the Context Broker as updateContext requests over the device entity. NGSI queries to the context broker will be
181
200
  resolved in the Broker database. For each attribute, its `name` and `type` must be provided. Additional `metadata`
182
- is optional.
201
+ is optional. They are called internally as _active attributes_.
183
202
 
184
203
  - **`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.
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_.
190
210
 
191
211
  - **`static`**: It is static attributes that are persisted in the Context Broker. They are not updated by the device,
192
212
  but they can be modified by the user. They are useful to store information about the device that is not updated by
193
213
  the device itself. For instance, a `location` static attribute is can be used to store the location of a fixed
194
214
  device.
195
215
 
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.
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.
200
223
 
201
224
  All of them have the same syntax, a list of objects with the following attributes:
202
225
 
@@ -237,6 +260,17 @@ Note that, when information coming from devices, this means measures, are not de
237
260
  device, the IoT agent will store that information into the destination entity using the same attribute name than the
238
261
  measure name, unless `explicitAttrs` is defined. Measures `id` or `type` names are invalid, and will be ignored.
239
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
+
240
274
  ## Multientity support
241
275
 
242
276
  The IOTA is able to persists measures coming from a single device to more than one entity, declaring the target entities
@@ -320,45 +354,6 @@ e.g.:
320
354
  }
321
355
  ```
322
356
 
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
357
  ### NGSI-LD data and metadata considerations
363
358
 
364
359
  When provisioning devices for an NGSI-LD Context Broker, `type` values should typically correspond to one of the
@@ -466,7 +461,8 @@ mappings of the provision. If `explicitAttrs` is provided both at device and con
466
461
  precedence. Additionally `explicitAttrs` can be used to define which measures (identified by their attribute names, not
467
462
  by their object_id) defined in JSON/JEXL array will be propagated to NGSI interface.
468
463
 
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.
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.
470
466
 
471
467
  The different possibilities are summarized below:
472
468
 
@@ -556,6 +552,7 @@ really useful when you need to adapt measure (for example, to change the units,
556
552
  of expression in the IoT Agent are:
557
553
 
558
554
  - [Measurement transformation](#measurement-transformation).
555
+ - [Metadata](#expression-support-in-metadata)
559
556
  - Commands payload transformation (push and pull).
560
557
  - Auto provisioned devices entity name. It is configured at config Group level by setting the `entityNameExp`
561
558
  parameter. It defines an expression to generate the Entity Name for autoprovisioned devices.
@@ -568,12 +565,18 @@ expression. In all cases the following data is available to all expressions:
568
565
  - `id`: device ID
569
566
  - `entity_name`: NGSI entity Name (principal)
570
567
  - `type`: NGSI entity type (principal)
571
- - `service`: device service
572
- - `subservice`: device subservice
568
+ - `service`: device service (`Fiware-Service`)
569
+ - `subservice`: device subservice (`Fiware-ServicePath`)
573
570
  - `staticAttributes`: static attributes defined in the device or config group
574
571
 
575
572
  Additionally, for attribute expressions (`expression`, `entity_name`), `entityNameExp` and metadata expressions
576
- (`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)
577
580
 
578
581
  ### Examples of JEXL expressions
579
582
 
@@ -619,50 +622,52 @@ to incorporate new transformations from the IoT Agent configuration file in a fa
619
622
 
620
623
  Current common transformation set:
621
624
 
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))))` |
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> |
666
671
 
667
672
  You have available this [JEXL interactive playground][99] with all the transformations already loaded, in which you can
668
673
  test all the functions described above.
@@ -691,6 +696,50 @@ Another example using functions that return more than one value is the following
691
696
 
692
697
  For a location value `"40.4165, -3.70256"`, the result of the previous expression will be `-3.70256`.
693
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
+
694
743
  ## Measurement transformation
695
744
 
696
745
  The IoTAgent Library provides support for measurement transformation using a
@@ -1017,13 +1066,34 @@ Will now generate the following NGSI v2 payload:
1017
1066
  }
1018
1067
  ```
1019
1068
 
1020
- ## Timestamp Processing
1069
+ ## TimeInstant and Timestamp flag
1021
1070
 
1022
- Timestamp processing done by IOTA is as follows:
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:
1023
1073
 
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.
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
1077
+
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)
1027
1097
 
1028
1098
  Depending on the `timestamp` configuration and if the measure contains a value named `TimeInstant` with a correct value,
1029
1099
  the IoTA behaviour is described in the following table:
@@ -1037,13 +1107,6 @@ the IoTA behaviour is described in the following table:
1037
1107
  | Not defined | Yes | TimeInstant and metadata updated with measure value |
1038
1108
  | Not defined | No | TimeInstant and metadata updated with server timestamp |
1039
1109
 
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
1110
  Some additional considerations to take into account:
1048
1111
 
1049
1112
  - If there is an attribute which maps a measure to `TimeInstant` attribute (after
@@ -1053,6 +1116,18 @@ Some additional considerations to take into account:
1053
1116
  - If the resulting `TimeInstant` not follows [ISO_8601](https://en.wikipedia.org/wiki/ISO_8601) (either from a direct
1054
1117
  measure of after a mapping, as described in the previous bullet) then it is refused (so a failover to server
1055
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.
1056
1131
 
1057
1132
  ## Multimeasure support
1058
1133
 
@@ -1175,6 +1250,215 @@ In this case a batch update (`POST /v2/op/update`) to CB will be generated with
1175
1250
  }
1176
1251
  ```
1177
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.
1461
+
1178
1462
  ## Overriding global Context Broker host
1179
1463
 
1180
1464
  **cbHost**: Context Broker host URL. This option can be used to override the global CB configuration for specific types
@@ -1443,7 +1727,7 @@ Config group is represented by a JSON object with the following fields:
1443
1727
 
1444
1728
  The following actions are available under the config group endpoint:
1445
1729
 
1446
- #### Retrieve config groups `GET /iot/services`
1730
+ #### Retrieve config groups `GET /iot/groups`
1447
1731
 
1448
1732
  List all the config groups for the given `fiware-service` and `fiware-servicepath`. The config groups that match the
1449
1733
  `fiware-servicepath` are returned in any other case.
@@ -1467,14 +1751,14 @@ Successful operations return `Content-Type` header with `application/json` value
1467
1751
 
1468
1752
  _**Response payload**_
1469
1753
 
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.
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.
1472
1756
 
1473
1757
  Example:
1474
1758
 
1475
1759
  ```json
1476
1760
  {
1477
- "services": [
1761
+ "groups": [
1478
1762
  {
1479
1763
  "resource": "/deviceTest",
1480
1764
  "apikey": "801230BJKL23Y9090DSFL123HJK09H324HV8732",
@@ -1511,7 +1795,7 @@ Example:
1511
1795
  }
1512
1796
  ```
1513
1797
 
1514
- #### Create config group `POST /iot/services`
1798
+ #### Create config group `POST /iot/groups`
1515
1799
 
1516
1800
  Creates a set of config groups for the given service and service path. The service and subservice information will taken
1517
1801
  from the headers, overwritting any preexisting values.
@@ -1525,14 +1809,14 @@ _**Request headers**_
1525
1809
 
1526
1810
  _**Request payload**_
1527
1811
 
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.
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.
1530
1814
 
1531
1815
  Example:
1532
1816
 
1533
1817
  ```json
1534
1818
  {
1535
- "services": [
1819
+ "groups": [
1536
1820
  {
1537
1821
  "resource": "/deviceTest",
1538
1822
  "apikey": "801230BJKL23Y9090DSFL123HJK09H324HV8732",
@@ -1566,7 +1850,7 @@ _**Response headers**_
1566
1850
 
1567
1851
  Successful operations return `Content-Type` header with `application/json` value.
1568
1852
 
1569
- #### Modify config group `PUT /iot/services`
1853
+ #### Modify config group `PUT /iot/groups`
1570
1854
 
1571
1855
  Modifies the information of a config group, identified by the `resource` and `apikey` query parameters. Takes a service
1572
1856
  group body as the payload. The body does not have to be complete: for incomplete bodies, just the attributes included in
@@ -1588,8 +1872,8 @@ _**Request headers**_
1588
1872
 
1589
1873
  _**Request payload**_
1590
1874
 
1591
- A JSON object with the config group information to be modified. See the
1592
- [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.
1593
1877
 
1594
1878
  Example:
1595
1879
 
@@ -1606,7 +1890,7 @@ _**Response code**_
1606
1890
  - 400 MISSING_HEADERS if any of the mandatory headers is not present.
1607
1891
  - 500 SERVER ERROR if there was any error not contemplated above.:
1608
1892
 
1609
- #### Remove config group `DELETE /iot/services`
1893
+ #### Remove config group `DELETE /iot/groups`
1610
1894
 
1611
1895
  Removes a config group, identified by the `resource` and `apikey` query parameters.
1612
1896
 
@@ -2026,6 +2310,57 @@ Example:
2026
2310
  }
2027
2311
  ```
2028
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
+
2029
2364
  [1]:
2030
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
2031
2366
  [2]: