iotagent-node-lib 3.0.0 → 3.2.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.
- package/.github/workflows/ci.yml +1 -0
- package/config.js +5 -5
- package/doc/api.md +1540 -298
- package/doc/deprecated.md +3 -1
- package/doc/development.md +120 -0
- package/doc/installationguide.md +3 -6
- package/docker-compose-dev.yml +1 -1
- package/lib/commonConfig.js +7 -10
- package/lib/fiware-iotagent-lib.js +0 -10
- package/lib/jexlTranformsMap.js +2 -1
- package/lib/plugins/bidirectionalData.js +8 -26
- package/lib/plugins/expressionPlugin.js +8 -40
- package/lib/plugins/jexlParser.js +28 -0
- package/lib/services/commands/commandService.js +1 -1
- package/lib/services/devices/deviceService.js +2 -1
- package/lib/services/ngsi/entities-NGSI-LD.js +15 -73
- package/lib/services/ngsi/entities-NGSI-v2.js +149 -124
- package/lib/services/northBound/deviceProvisioningServer.js +17 -14
- package/lib/templates/createDevice.json +5 -2
- package/lib/templates/createDeviceLax.json +7 -5
- package/lib/templates/updateDevice.json +5 -2
- package/lib/templates/updateDeviceLax.json +3 -5
- package/package.json +2 -2
- package/test/unit/examples/deviceProvisioningRequests/provisionBidirectionalDevice.json +5 -5
- package/test/unit/examples/deviceProvisioningRequests/provisionMinimumDevice4.json +1 -0
- package/test/unit/examples/groupProvisioningRequests/bidirectionalGroup.json +4 -4
- package/test/unit/general/config-multi-core-test.js +2 -1
- package/test/unit/general/contextBrokerKeystoneSecurityAccess-test.js +2 -1
- package/test/unit/general/deviceService-test.js +2 -1
- package/test/unit/general/loglevel-api_test.js +2 -1
- package/test/unit/general/startup-test.js +2 -1
- package/test/unit/general/statistics-persistence_test.js +1 -0
- package/test/unit/general/statistics-service_test.js +1 -0
- package/test/unit/lazyAndCommands/commandRegistry_test.js +1 -0
- package/test/unit/memoryRegistry/deviceRegistryMemory_test.js +1 -0
- package/test/unit/mongodb/mongodb-connectionoptions-test.js +1 -0
- package/test/unit/mongodb/mongodb-group-registry-test.js +1 -0
- package/test/unit/mongodb/mongodb-registry-test.js +2 -1
- package/test/unit/ngsi-ld/expressions/jexlBasedTransformations-test.js +0 -2
- package/test/unit/ngsi-ld/general/config-jsonld-contexts-test.js +2 -1
- package/test/unit/ngsi-ld/general/contextBrokerOAuthSecurityAccess-test.js +2 -1
- package/test/unit/ngsi-ld/general/deviceService-test.js +2 -1
- package/test/unit/ngsi-ld/general/https-support-test.js +2 -1
- package/test/unit/ngsi-ld/general/iotam-autoregistration-test.js +2 -1
- package/test/unit/ngsi-ld/general/startup-test.js +3 -2
- package/test/unit/ngsi-ld/lazyAndCommands/active-devices-attribute-update-test.js +2 -1
- package/test/unit/ngsi-ld/lazyAndCommands/command-test.js +5 -7
- package/test/unit/ngsi-ld/lazyAndCommands/lazy-devices-test.js +8 -6
- package/test/unit/ngsi-ld/lazyAndCommands/merge-patch-test.js +18 -22
- package/test/unit/ngsi-ld/lazyAndCommands/polling-commands-test.js +2 -1
- package/test/unit/ngsi-ld/ngsiService/active-devices-test.js +2 -1
- package/test/unit/ngsi-ld/ngsiService/autocast-test.js +2 -1
- package/test/unit/ngsi-ld/ngsiService/geoproperties-test.js +2 -1
- package/test/unit/ngsi-ld/ngsiService/languageProperties-test.js +32 -34
- package/test/unit/ngsi-ld/ngsiService/staticAttributes-test.js +2 -1
- package/test/unit/ngsi-ld/ngsiService/subscriptions-test.js +2 -1
- package/test/unit/ngsi-ld/ngsiService/unsupported-endpoints-test.js +11 -15
- package/test/unit/ngsi-ld/plugins/alias-plugin_test.js +2 -1
- package/test/unit/ngsi-ld/plugins/bidirectional-plugin_test.js +10 -9
- package/test/unit/ngsi-ld/plugins/compress-timestamp-plugin_test.js +2 -1
- package/test/unit/ngsi-ld/plugins/custom-plugin_test.js +152 -0
- package/test/unit/ngsi-ld/plugins/multientity-plugin_test.js +3 -2
- package/test/unit/ngsi-ld/plugins/timestamp-processing-plugin_test.js +2 -1
- package/test/unit/ngsi-ld/provisioning/device-provisioning-api_test.js +1 -0
- package/test/unit/ngsi-ld/provisioning/device-registration_test.js +2 -1
- package/test/unit/ngsi-ld/provisioning/device-update-registration_test.js +15 -12
- package/test/unit/ngsi-ld/provisioning/listProvisionedDevices-test.js +1 -0
- package/test/unit/ngsi-ld/provisioning/provisionDeviceMultientity-test.js +1 -0
- package/test/unit/ngsi-ld/provisioning/removeProvisionedDevice-test.js +1 -0
- package/test/unit/ngsi-ld/provisioning/singleConfigurationMode-test.js +1 -0
- package/test/unit/ngsi-ld/provisioning/updateProvisionedDevices-test.js +1 -0
- package/test/unit/ngsi-mixed/provisioning/ngsi-versioning-test.js +13 -7
- package/test/unit/ngsiv2/examples/contextRequests/createMinimumProvisionedDevice4.json +5 -1
- package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin31.json +0 -8
- package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin32.json +6 -0
- package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin35.json +20 -0
- package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin40.json +42 -0
- package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin41.json +32 -0
- package/test/unit/ngsiv2/examples/contextRequests/updateContextMultientityPlugin25.json +37 -0
- package/test/unit/ngsiv2/examples/contextRequests/updateContextProcessTimestamp.json +7 -1
- package/test/unit/ngsiv2/examples/contextRequests/updateContextTimestampOverride.json +7 -1
- package/test/unit/ngsiv2/examples/contextRequests/updateContextTimestampOverrideWithoutMilis.json +7 -1
- package/test/unit/ngsiv2/expressions/jexlBasedTransformations-test.js +232 -8
- package/test/unit/ngsiv2/general/contextBrokerOAuthSecurityAccess-test.js +2 -1
- package/test/unit/ngsiv2/general/deviceService-test.js +2 -1
- package/test/unit/ngsiv2/general/https-support-test.js +2 -1
- package/test/unit/ngsiv2/general/iotam-autoregistration-test.js +2 -1
- package/test/unit/ngsiv2/general/startup-test.js +2 -1
- package/test/unit/ngsiv2/lazyAndCommands/active-devices-attribute-update-test.js +2 -1
- package/test/unit/ngsiv2/lazyAndCommands/command-test.js +2 -1
- package/test/unit/ngsiv2/lazyAndCommands/lazy-devices-test.js +8 -6
- package/test/unit/ngsiv2/lazyAndCommands/polling-commands-test.js +3 -3
- package/test/unit/ngsiv2/ngsiService/active-devices-test.js +3 -2
- package/test/unit/ngsiv2/ngsiService/autocast-test.js +2 -1
- package/test/unit/ngsiv2/ngsiService/queryDeviceInformationInCb-test.js +2 -1
- package/test/unit/ngsiv2/ngsiService/staticAttributes-test.js +2 -1
- package/test/unit/ngsiv2/ngsiService/subscriptions-test.js +2 -1
- package/test/unit/ngsiv2/plugins/alias-plugin_test.js +2 -1
- package/test/unit/ngsiv2/plugins/bidirectional-plugin_test.js +8 -7
- package/test/unit/ngsiv2/plugins/compress-timestamp-plugin_test.js +2 -1
- package/test/unit/ngsiv2/plugins/custom-plugin_test.js +151 -0
- package/test/unit/ngsiv2/plugins/multientity-plugin_test.js +87 -13
- package/test/unit/ngsiv2/plugins/timestamp-processing-plugin_test.js +2 -1
- package/test/unit/ngsiv2/provisioning/device-group-api-test.js +1 -0
- package/test/unit/ngsiv2/provisioning/device-group-utils-test.js +1 -0
- package/test/unit/ngsiv2/provisioning/device-provisioning-api_test.js +12 -3
- package/test/unit/ngsiv2/provisioning/device-registration_test.js +2 -1
- package/test/unit/ngsiv2/provisioning/device-update-registration_test.js +15 -12
- package/test/unit/ngsiv2/provisioning/listProvisionedDevices-test.js +1 -0
- package/test/unit/ngsiv2/provisioning/provisionDeviceMultientity-test.js +1 -0
- package/test/unit/ngsiv2/provisioning/removeProvisionedDevice-test.js +1 -0
- package/test/unit/ngsiv2/provisioning/singleConfigurationMode-test.js +1 -0
- package/test/unit/ngsiv2/provisioning/updateProvisionedDevices-test.js +1 -0
- package/test/unit/plugins/capture-configuration-inPlugins_test.js +2 -1
- package/test/unit/plugins/capture-provision-inPlugins_test.js +2 -1
- package/doc/advanced-topics.md +0 -626
- package/doc/expressionLanguage.md +0 -762
- package/lib/plugins/expressionParser.js +0 -205
- package/test/unit/expressions/expression-test.js +0 -197
- package/test/unit/ngsi-ld/expressions/expressionBasedTransformations-test.js +0 -881
- package/test/unit/ngsiv2/expressions/expressionBasedTransformations-test.js +0 -950
- package/test/unit/ngsiv2/expressions/expressionCombinedTransformations-test.js +0 -294
package/doc/deprecated.md
CHANGED
|
@@ -21,6 +21,7 @@ A list of deprecated features and the version in which they were deprecated foll
|
|
|
21
21
|
- Support to NGSI-LD v1.3 in iotagent-node-lib 2.25.0 (finally removed in 2.26.0)
|
|
22
22
|
- Support groups (provision) statically defined by configuration
|
|
23
23
|
- Support to in-memory registry (i.e.`deviceRegistry.type=memory`)
|
|
24
|
+
- Support to legacy expressions (finally removed in 3.2.0)
|
|
24
25
|
|
|
25
26
|
The use of Node.js v14 is highly recommended.
|
|
26
27
|
|
|
@@ -48,4 +49,5 @@ The following table provides information about the last iotagent-node-lib versio
|
|
|
48
49
|
| Support to Node.js v8 | 2.12.0 | April 7th, 2020 |
|
|
49
50
|
| Support to Node.js v10 | 2.15.0 | February 18th, 2021 |
|
|
50
51
|
| Support to Node.js v12 | 2.24.0 | September 2nd, 2022 |
|
|
51
|
-
| Support to NGSI-LD 1.3 | 2.25.0 | January 24th, 2023
|
|
52
|
+
| Support to NGSI-LD 1.3 | 2.25.0 | January 24th, 2023 |
|
|
53
|
+
| Support to Legacy Expressions | 3.1.0 | April 25th, 2023 |
|
package/doc/development.md
CHANGED
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
- [Continuous testing](#continuous-testing)
|
|
8
8
|
- [Code Coverage](#code-coverage)
|
|
9
9
|
- [Clean](#clean)
|
|
10
|
+
- [Data mapping plugins](#data-mapping-plugins)
|
|
11
|
+
- [Development](#development)
|
|
12
|
+
- [Provided plugins](#provided-plugins)
|
|
10
13
|
|
|
11
14
|
### Contributions
|
|
12
15
|
|
|
@@ -163,3 +166,120 @@ To ensure consistent Markdown formatting run the following:
|
|
|
163
166
|
# Use git-bash on Windows
|
|
164
167
|
npm run prettier:text
|
|
165
168
|
```
|
|
169
|
+
|
|
170
|
+
# DB Models (from API document)
|
|
171
|
+
|
|
172
|
+
## Service group model
|
|
173
|
+
|
|
174
|
+
The table below shows the information held in the service group provisioning resource. The table also contains the
|
|
175
|
+
correspondence between the API resource fields and the same fields in the database model.
|
|
176
|
+
|
|
177
|
+
| Payload Field | DB Field | Definition |
|
|
178
|
+
| ------------------------------ | ------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---- |
|
|
179
|
+
| `service` | `service` | Service of the devices of this type |
|
|
180
|
+
| `subservice` | `subservice` | Subservice of the devices of this type. |
|
|
181
|
+
| `resource` | `resource` | string representing the Southbound resource that will be used to assign a type to a device (e.g.: pathname in the southbound port). |
|
|
182
|
+
| `apikey` | `apikey` | API Key string. |
|
|
183
|
+
| `timestamp` | `timestamp` | Optional flag about whether or not to add the `TimeInstant` attribute to the device entity created, as well as a `TimeInstant` metadata to each attribute, with the current timestamp. With NGSI-LD, the Standard `observedAt` property-of-a-property is created instead. | true |
|
|
184
|
+
| `entity_type` | `entity_type` | name of the Entity `type` to assign to the group. |
|
|
185
|
+
| `trust` | `trust` | trust token to use for secured access to the Context Broker for this type of devices (optional; only needed for secured scenarios). |
|
|
186
|
+
| `cbHost` | `cbHost` | Context Broker connection information. This options can be used to override the global ones for specific types of devices. |
|
|
187
|
+
| `lazy` | `lazy` | list of common lazy attributes of the device. For each attribute, its `name` and `type` must be provided. |
|
|
188
|
+
| `commands` | `commands` | list of common commands attributes of the device. For each attribute, its `name` and `type` must be provided, additional `metadata` is optional. |
|
|
189
|
+
| `attributes` | `attributes` | list of common active attributes of the device. For each attribute, its `name` and `type` must be provided, additional `metadata` is optional. |
|
|
190
|
+
| `static_attributes` | `staticAttributes` | this attributes will be added to all the entities of this group 'as is', additional `metadata` is optional. |
|
|
191
|
+
| `internal_attributes` | `internalAttributes` | optional section with free format, to allow specific IoT Agents to store information along with the devices in the Device Registry. |
|
|
192
|
+
| `expressionLanguage` | `expresionLanguage` | optional boolean value, to set expression language used to compute expressions, possible values are: legacy or jexl. When not set or wrongly set, `legacy` is used as default value. |
|
|
193
|
+
| `explicitAttrs` | `explicitAttrs` | optional field to support selective ignore of measures so that IOTA doesn’t progress. See details in [specific section](advanced-topics.md#explicitly-defined-attributes-explicitattrs) |
|
|
194
|
+
| `entityNameExp` | `entityNameExp` | optional field to allow use expressions to define entity name, instead default `id` and `type` |
|
|
195
|
+
| `ngsiVersion` | `ngsiVersion` | 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. |
|
|
196
|
+
| `defaultEntityNameConjunction` | `defaultEntityNameConjunction` | optional string value to set default conjunction string used to compose a default `entity_name` when is not provided at device provisioning time. |
|
|
197
|
+
| `autoprovision` | `autoprovision` | 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`. |
|
|
198
|
+
|
|
199
|
+
## Device model
|
|
200
|
+
|
|
201
|
+
The table below shows the information held in the Device resource. The table also contains the correspondence between
|
|
202
|
+
the API resource fields and the same fields in the database model.
|
|
203
|
+
|
|
204
|
+
| Payload Field | DB Field | Definition | Example of value |
|
|
205
|
+
| --------------------- | -------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :-------------------------------------------- |
|
|
206
|
+
| `device_id` | `id` | Device ID that will be used to identify the device. | UO834IO |
|
|
207
|
+
| `service` | `service` | Name of the service the device belongs to (will be used in the fiware-service header). | smartGondor |
|
|
208
|
+
| `service_path` | `subservice` | Name of the subservice the device belongs to (used in the fiware-servicepath header). | /gardens |
|
|
209
|
+
| `entity_name` | `name` | Name of the entity representing the device in the Context Broker | ParkLamplight12 |
|
|
210
|
+
| `entity_type` | `type` | Type of the entity in the Context Broker | Lamplights |
|
|
211
|
+
| `timezone` | `timezone` | Time zone of the sensor if it has any | America/Santiago |
|
|
212
|
+
| `timestamp` | `timestamp` | Optional flag about whether or not to add the `TimeInstant` attribute to the device entity created, as well as a `TimeInstant` metadata to each attribute, with the current timestamp. With NGSI-LD, the Standard `observedAt` property-of-a-property is created instead. | true |
|
|
213
|
+
| `apikey` | `apikey` | Optional Apikey key string to use instead of group apikey | 9n4hb1vpwbjozzmw9f0flf9c2 |
|
|
214
|
+
| `endpoint` | `endpoint` | Endpoint where the device is going to receive commands, if any. | http://theDeviceUrl:1234/commands |
|
|
215
|
+
| `protocol` | `protocol` | Name of the device protocol, for its use with an IoT Manager. | IoTA-UL |
|
|
216
|
+
| `transport` | `transport` | Name of the device transport protocol, for the IoT Agents with multiple transport protocols. | MQTT |
|
|
217
|
+
| `attributes` | `active` | List of active attributes of the device | `[ { "name": "attr_name", "type": "Text" } ]` |
|
|
218
|
+
| `lazy` | `lazy` | List of lazy attributes of the device | `[ { "name": "attr_name", "type": "Text" } ]` |
|
|
219
|
+
| `commands` | `commands` | List of commands of the device | `[ { "name": "attr_name", "type": "Text" } ]` |
|
|
220
|
+
| `internal_attributes` | `internalAttributes` | List of internal attributes with free format for specific IoT Agent configuration | LWM2M mappings from object URIs to attributes |
|
|
221
|
+
| `static_attributes` | `staticAttributes` | List of static attributes to append to the entity. All the updateContext requests to the CB will have this set of attributes appended. | `[ { "name": "attr_name", "type": "Text" } ]` |
|
|
222
|
+
| `expressionLanguage` | `expresionLanguage` | optional boolean value, to set expression language used to compute expressions, possible values are: legacy or jexl. When not set or wrongly set, legacy is used as default value. |
|
|
223
|
+
| `explicitAttrs` | `explicitAttrs` | optional field to support selective ignore of measures so that IOTA doesn’t progress. See details in [specific section](advanced-topics.md#explicitly-defined-attributes-explicitattrs) | (see details in specific section) |
|
|
224
|
+
| `ngsiVersion` | `ngsiVersion` | optional 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. | `v2/ld` |
|
|
225
|
+
|
|
226
|
+
## Data mapping plugins
|
|
227
|
+
|
|
228
|
+
The IoT Agent Library provides a plugin mechanism in order to facilitate reusing code that makes small transformations
|
|
229
|
+
on incoming data (both from the device and from the context consumers). This mechanism is based in the use of
|
|
230
|
+
middlewares, i.e.: small pieces of code that receive and return an `entity`, making as many changes as they need, but
|
|
231
|
+
taking care of returning a valid entity, that can be used as the input for other middlewares; this way, all those pieces
|
|
232
|
+
of code can be chained together in order to make all the needed transformations in the target entity.
|
|
233
|
+
|
|
234
|
+
There are two kinds of middlewares: updateContext middlewares and queryContext middlewares. The updateContext
|
|
235
|
+
middlewares are applied before the information is sent to the Context Broker, modifiying the entity before it is sent to
|
|
236
|
+
Orion. The queryContext middlewares are applied on the received data, whenever the IoT Agent queries the Context Broker
|
|
237
|
+
for information. I.e.: both middlewares will be automatically applied whenever the `update()` or `query()` functions are
|
|
238
|
+
called in the library.
|
|
239
|
+
|
|
240
|
+
All the middlewares have the opportunity to break the chain of middleware applications by calling the `callback()` with
|
|
241
|
+
an error object (the usual convention). If any of the updateContext middlewares raise an error, no request will be sent
|
|
242
|
+
to the Context Broker. On the other hand, the queryContext request is always performed, but the call to the `query()`
|
|
243
|
+
function will end up in an error if any of the queryContext middlewares report an error.
|
|
244
|
+
|
|
245
|
+
### Development
|
|
246
|
+
|
|
247
|
+
All the middlewares have the same signature:
|
|
248
|
+
|
|
249
|
+
```javascript
|
|
250
|
+
function middlewareName(entity, typeInformation, callback) {}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
The arguments for any middleware are the NGSI data over which it can operate:
|
|
254
|
+
|
|
255
|
+
- An updateContext payload in the case of an updateContext middleware and a queryContext payload otherwise;
|
|
256
|
+
- a typeInformation object containing all the information about the device stored during registration.
|
|
257
|
+
- and the customary `callback` parameter, with the usual meaning. It's really important for the library user to call
|
|
258
|
+
this callback, as failing to do so may hang the IoT Agent completely. The callback must be called with the an
|
|
259
|
+
optional error in the first argument and the same arguments received (potentially modified) as the following.
|
|
260
|
+
|
|
261
|
+
In order to manage the middlewares to the system, the following functions can be used:
|
|
262
|
+
|
|
263
|
+
- `addUpdateMiddleware`: adds an updateContext middleware to the stack of middlewares. All the middlewares will be
|
|
264
|
+
applied to every call to the `update()` function. The final payload of the updateContext request will be the result
|
|
265
|
+
of applying all this middlewares in the order they have been defined.
|
|
266
|
+
|
|
267
|
+
- `addQueryMiddleware`: adds a queryContext middleware to the stack of middlewares. All the middlewares will be
|
|
268
|
+
applied to every call to the `query()` function.
|
|
269
|
+
|
|
270
|
+
- `resetMiddlewares`: remove all the middlewares from the system.
|
|
271
|
+
|
|
272
|
+
Usually, the full list of middlewares an IoT Agent will use would be added in the IoTAgent start sequence, so they
|
|
273
|
+
should not change a lot during the IoT lifetime.
|
|
274
|
+
|
|
275
|
+
### Provided plugins
|
|
276
|
+
|
|
277
|
+
The library provides some plugins out of the box, in the `dataPlugins` collection. In order to load any of them, just
|
|
278
|
+
use the `addQueryMiddleware` and `addUpdateMiddleware` functions with the selected plugin, as in the example:
|
|
279
|
+
|
|
280
|
+
```javascript
|
|
281
|
+
var iotaLib = require('iotagent-node-lib');
|
|
282
|
+
|
|
283
|
+
iotaLib.addUpdateMiddleware(iotaLib.dataPlugins.compressTimestamp.update);
|
|
284
|
+
iotaLib.addQueryMiddleware(iotaLib.dataPlugins.compressTimestamp.query);
|
|
285
|
+
```
|
package/doc/installationguide.md
CHANGED
|
@@ -73,9 +73,9 @@ overriding the group setting.
|
|
|
73
73
|
```
|
|
74
74
|
|
|
75
75
|
When connected to an **NGSI-LD** context broker, an IoT Agent is able to indicate whether it is willing to accept `null`
|
|
76
|
-
values and also whether it is able to process the **NGSI-LD** `datasetId` metadata element. Setting these
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
values and also whether it is able to process the **NGSI-LD** `datasetId` metadata element. Setting these values to
|
|
77
|
+
`false` will cause the IoT Agent to return a 400 **Bad Request** HTTP status code explaining that the IoT Agent does not
|
|
78
|
+
support nulls or multi-attribute requests if they are encountered.
|
|
79
79
|
|
|
80
80
|
```javascript
|
|
81
81
|
{
|
|
@@ -277,8 +277,6 @@ used for the same purpose. For instance:
|
|
|
277
277
|
the IoTAgent runs in a single thread. For more details about multi-core functionality, please refer to the
|
|
278
278
|
[Cluster](https://nodejs.org/api/cluster.html) module in Node.js and
|
|
279
279
|
[this section](howto.md#iot-agent-in-multi-thread-mode) of the library documentation.
|
|
280
|
-
- **defaultExpressionLanguage**: the default expression language used to compute expressions, possible values are:
|
|
281
|
-
`legacy` or `jexl`. When not set or wrongly set, `legacy` is used as default value.
|
|
282
280
|
- **fallbackTenant** - For Linked Data Context Brokers which do not support multi-tenancy, this provides an
|
|
283
281
|
alternative mechanism for supplying the `NGSILD-Tenant` header. Note that NGSILD-Tenant has not yet been included in
|
|
284
282
|
the NGSI-LD standard (it has been proposed for the next update of the standard, but the final decision has yet been
|
|
@@ -362,7 +360,6 @@ overrides.
|
|
|
362
360
|
| IOTA_JSON_LD_CONTEXT | `jsonLdContext` |
|
|
363
361
|
| IOTA_FALLBACK_TENANT | `fallbackTenant` |
|
|
364
362
|
| IOTA_FALLBACK_PATH | `fallbackPath` |
|
|
365
|
-
| IOTA_DEFAULT_EXPRESSION_LANGUAGE | `defaultExpressionLanguage` |
|
|
366
363
|
| IOTA_EXPLICIT_ATTRS | `explicitAttrs` |
|
|
367
364
|
| IOTA_DEFAULT_ENTITY_NAME_CONJUNCTION | `defaultEntityNameConjunction` |
|
|
368
365
|
| IOTA_RELAX_TEMPLATE_VALIDATION | `relaxTemplateValidation` |
|
package/docker-compose-dev.yml
CHANGED
package/lib/commonConfig.js
CHANGED
|
@@ -153,7 +153,6 @@ function processEnvironmentVariables() {
|
|
|
153
153
|
'IOTA_POLLING_EXPIRATION',
|
|
154
154
|
'IOTA_POLLING_DAEMON_FREQ',
|
|
155
155
|
'IOTA_MULTI_CORE',
|
|
156
|
-
'IOTA_DEFAULT_EXPRESSION_LANGUAGE',
|
|
157
156
|
'IOTA_RELAX_TEMPLATE_VALIDATION',
|
|
158
157
|
'IOTA_DEFAULT_ENTITY_NAME_CONJUNCTION',
|
|
159
158
|
'IOTA_JSON_LD_CONTEXT',
|
|
@@ -245,9 +244,9 @@ function processEnvironmentVariables() {
|
|
|
245
244
|
config.contextBroker.jsonLdContext = process.env.IOTA_JSON_LD_CONTEXT.split(',').map((ctx) => ctx.trim());
|
|
246
245
|
}
|
|
247
246
|
|
|
248
|
-
|
|
247
|
+
if (Array.isArray(config.contextBroker.jsonLdContext) && config.contextBroker.jsonLdContext.length === 1) {
|
|
249
248
|
config.contextBroker.jsonLdContext = config.contextBroker.jsonLdContext[0];
|
|
250
|
-
|
|
249
|
+
}
|
|
251
250
|
|
|
252
251
|
config.contextBroker.fallbackTenant =
|
|
253
252
|
process.env.IOTA_FALLBACK_TENANT || config.contextBroker.service || 'iotagent';
|
|
@@ -265,7 +264,7 @@ function processEnvironmentVariables() {
|
|
|
265
264
|
config.server.port = process.env.IOTA_NORTH_PORT;
|
|
266
265
|
}
|
|
267
266
|
|
|
268
|
-
config.server.ldSupport = config.server.ldSupport || {null: true, datasetId: true, merge: false};
|
|
267
|
+
config.server.ldSupport = config.server.ldSupport || { null: true, datasetId: true, merge: false };
|
|
269
268
|
|
|
270
269
|
if (process.env.IOTA_LD_SUPPORT_NULL) {
|
|
271
270
|
config.server.ldSupport.null = process.env.IOTA_LD_SUPPORT_NULL === 'true';
|
|
@@ -273,7 +272,7 @@ function processEnvironmentVariables() {
|
|
|
273
272
|
if (process.env.IOTA_LD_SUPPORT_DATASET_ID) {
|
|
274
273
|
config.server.ldSupport.datasetId = process.env.IOTA_LD_SUPPORT_DATASET_ID === 'true';
|
|
275
274
|
}
|
|
276
|
-
|
|
275
|
+
if (process.env.IOTA_LD_SUPPORT_MERGE) {
|
|
277
276
|
config.server.ldSupport.datasetId = process.env.IOTA_LD_SUPPORT_MERGE === 'true';
|
|
278
277
|
}
|
|
279
278
|
|
|
@@ -473,9 +472,6 @@ function processEnvironmentVariables() {
|
|
|
473
472
|
config.multiCore = config.multiCore === true;
|
|
474
473
|
}
|
|
475
474
|
|
|
476
|
-
if (process.env.IOTA_DEFAULT_EXPRESSION_LANGUAGE) {
|
|
477
|
-
config.defaultExpressionLanguage = process.env.IOTA_DEFAULT_EXPRESSION_LANGUAGE;
|
|
478
|
-
}
|
|
479
475
|
if (process.env.IOTA_RELAX_TEMPLATE_VALIDATION) {
|
|
480
476
|
config.relaxTemplateValidation = process.env.IOTA_RELAX_TEMPLATE_VALIDATION === 'true';
|
|
481
477
|
} else {
|
|
@@ -484,7 +480,9 @@ function processEnvironmentVariables() {
|
|
|
484
480
|
if (process.env.IOTA_DEFAULT_ENTITY_NAME_CONJUNCTION) {
|
|
485
481
|
config.defaultEntityNameConjunction = process.env.IOTA_DEFAULT_ENTITY_NAME_CONJUNCTION;
|
|
486
482
|
} else {
|
|
487
|
-
config.defaultEntityNameConjunction = config.defaultEntityNameConjunction
|
|
483
|
+
config.defaultEntityNameConjunction = config.defaultEntityNameConjunction
|
|
484
|
+
? config.defaultEntityNameConjunction
|
|
485
|
+
: ':';
|
|
488
486
|
}
|
|
489
487
|
}
|
|
490
488
|
|
|
@@ -539,7 +537,6 @@ function ngsiVersion() {
|
|
|
539
537
|
return 'unknown';
|
|
540
538
|
}
|
|
541
539
|
|
|
542
|
-
|
|
543
540
|
/**
|
|
544
541
|
* It checks if a combination of typeInformation or common Config is LD
|
|
545
542
|
*
|
|
@@ -125,16 +125,6 @@ function doActivate(newConfig, callback) {
|
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
if (
|
|
129
|
-
newConfig.defaultExpressionLanguage &&
|
|
130
|
-
(newConfig.defaultExpressionLanguage === 'legacy' || newConfig.defaultExpressionLanguage === 'jexl')
|
|
131
|
-
) {
|
|
132
|
-
logger.info(context, 'Using ' + newConfig.defaultExpressionLanguage + ' as default expression language');
|
|
133
|
-
} else {
|
|
134
|
-
logger.info(context, 'Default expression language not set, or invalid, using legacy configuration');
|
|
135
|
-
newConfig.defaultExpressionLanguage = 'legacy';
|
|
136
|
-
}
|
|
137
|
-
|
|
138
128
|
config.setConfig(newConfig); //after chaging some configuration, we re apply the configuration
|
|
139
129
|
|
|
140
130
|
logger.info(context, 'Activating IOT Agent NGSI Library.');
|
package/lib/jexlTranformsMap.js
CHANGED
|
@@ -78,7 +78,8 @@ const map = {
|
|
|
78
78
|
toisostring: (d) => new Date(d).toISOString(),
|
|
79
79
|
// https://developer.mozilla.org/es/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleString
|
|
80
80
|
localestring: (d, timezone, options) => new Date(d).toLocaleString(timezone, options),
|
|
81
|
-
now: () => Date.now()
|
|
81
|
+
now: () => Date.now(),
|
|
82
|
+
hextostring: (val) => new TextDecoder().decode(new Uint8Array(val.match(/.{1,2}/g).map(byte => parseInt(byte, 16))))
|
|
82
83
|
};
|
|
83
84
|
|
|
84
85
|
exports.map = map;
|
|
@@ -75,9 +75,7 @@ function extractVariables(item) {
|
|
|
75
75
|
|
|
76
76
|
function extractFromExpression(value) {
|
|
77
77
|
if (value.expression) {
|
|
78
|
-
return value.expression
|
|
79
|
-
return item.substr(1);
|
|
80
|
-
});
|
|
78
|
+
return parser.extractVariables(value.expression);
|
|
81
79
|
}
|
|
82
80
|
return [];
|
|
83
81
|
}
|
|
@@ -157,9 +155,7 @@ function getReverseTransformations(list, values, callback) {
|
|
|
157
155
|
let transformations = [];
|
|
158
156
|
|
|
159
157
|
function getVariable(expression) {
|
|
160
|
-
return
|
|
161
|
-
return item.substr(1);
|
|
162
|
-
});
|
|
158
|
+
return parser.extractVariables(expression);
|
|
163
159
|
}
|
|
164
160
|
|
|
165
161
|
if (list && list.length > 0) {
|
|
@@ -191,7 +187,7 @@ function getReverseTransformations(list, values, callback) {
|
|
|
191
187
|
*/
|
|
192
188
|
function processTransformationsNGSIv2(values, typeInformation, transformations, callback) {
|
|
193
189
|
let cleanedExpression;
|
|
194
|
-
const ctx = parser.extractContext(values
|
|
190
|
+
const ctx = parser.extractContext(values);
|
|
195
191
|
let resultTransformations = [];
|
|
196
192
|
|
|
197
193
|
for (let j = 0; j < 2; j++) {
|
|
@@ -201,10 +197,7 @@ function processTransformationsNGSIv2(values, typeInformation, transformations,
|
|
|
201
197
|
}
|
|
202
198
|
|
|
203
199
|
for (let i = 0; i < resultTransformations.length; i++) {
|
|
204
|
-
cleanedExpression = resultTransformations[i].expression
|
|
205
|
-
2,
|
|
206
|
-
resultTransformations[i].expression.length - 3
|
|
207
|
-
);
|
|
200
|
+
cleanedExpression = resultTransformations[i].expression;
|
|
208
201
|
|
|
209
202
|
values.push({
|
|
210
203
|
name: resultTransformations[i].object_id,
|
|
@@ -252,20 +245,12 @@ function processTransformationsNGSILD(values, typeInformation, transformations,
|
|
|
252
245
|
// This is the direct equivalent of the NGSI-v2 function
|
|
253
246
|
if (!_.isEmpty(defaultValues)) {
|
|
254
247
|
for (let i = 0; i < resultTransformations.length; i++) {
|
|
255
|
-
cleanedExpression = resultTransformations[i].expression
|
|
256
|
-
2,
|
|
257
|
-
resultTransformations[i].expression.length - 3
|
|
258
|
-
);
|
|
248
|
+
cleanedExpression = resultTransformations[i].expression;
|
|
259
249
|
values.push({
|
|
260
250
|
name: resultTransformations[i].object_id,
|
|
261
251
|
type: resultTransformations[i].type,
|
|
262
252
|
metadata: {},
|
|
263
|
-
value: parser.parse(
|
|
264
|
-
cleanedExpression,
|
|
265
|
-
parser.extractContext(defaultValues, typeInformation),
|
|
266
|
-
'String',
|
|
267
|
-
typeInformation
|
|
268
|
-
)
|
|
253
|
+
value: parser.parse(cleanedExpression, parser.extractContext(defaultValues), 'String', typeInformation)
|
|
269
254
|
});
|
|
270
255
|
}
|
|
271
256
|
}
|
|
@@ -274,10 +259,7 @@ function processTransformationsNGSILD(values, typeInformation, transformations,
|
|
|
274
259
|
// There is no equivalent with NGSI-v2
|
|
275
260
|
_.keys(datasetIds).forEach((datasetId) => {
|
|
276
261
|
for (let i = 0; i < resultTransformations.length; i++) {
|
|
277
|
-
cleanedExpression = resultTransformations[i].expression
|
|
278
|
-
2,
|
|
279
|
-
resultTransformations[i].expression.length - 3
|
|
280
|
-
);
|
|
262
|
+
cleanedExpression = resultTransformations[i].expression;
|
|
281
263
|
values.push({
|
|
282
264
|
name: resultTransformations[i].object_id,
|
|
283
265
|
type: resultTransformations[i].type,
|
|
@@ -285,7 +267,7 @@ function processTransformationsNGSILD(values, typeInformation, transformations,
|
|
|
285
267
|
metadata: {},
|
|
286
268
|
value: parser.parse(
|
|
287
269
|
cleanedExpression,
|
|
288
|
-
parser.extractContext(datasetIds[datasetId]
|
|
270
|
+
parser.extractContext(datasetIds[datasetId]),
|
|
289
271
|
'String',
|
|
290
272
|
typeInformation
|
|
291
273
|
)
|
|
@@ -24,9 +24,7 @@
|
|
|
24
24
|
* Modified by: Federico M. Facca - Martel Innovate
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
|
-
const legacyParser = require('./expressionParser');
|
|
28
27
|
const jexlParser = require('./jexlParser');
|
|
29
|
-
const config = require('../commonConfig');
|
|
30
28
|
/* eslint-disable no-unused-vars */
|
|
31
29
|
const logger = require('logops');
|
|
32
30
|
const errors = require('../errors');
|
|
@@ -44,53 +42,23 @@ function setJEXLTransforms(transformationMap) {
|
|
|
44
42
|
}
|
|
45
43
|
|
|
46
44
|
function applyExpression(expression, context, typeInformation) {
|
|
47
|
-
|
|
48
|
-
if (!checkJexl(typeInformation)) {
|
|
49
|
-
parser = legacyParser;
|
|
50
|
-
}
|
|
51
|
-
return parser.applyExpression(expression, context, typeInformation);
|
|
45
|
+
return jexlParser.applyExpression(expression, context, typeInformation);
|
|
52
46
|
}
|
|
53
47
|
|
|
54
|
-
function extractContext(attributeList
|
|
55
|
-
|
|
56
|
-
if (!checkJexl(typeInformation)) {
|
|
57
|
-
parser = legacyParser;
|
|
58
|
-
}
|
|
59
|
-
return parser.extractContext(attributeList);
|
|
48
|
+
function extractContext(attributeList) {
|
|
49
|
+
return jexlParser.extractContext(attributeList);
|
|
60
50
|
}
|
|
61
51
|
|
|
62
|
-
function parse(expression, context
|
|
63
|
-
if (!checkJexl(typeInformation)) {
|
|
64
|
-
return legacyParser.parse(expression, context, type);
|
|
65
|
-
}
|
|
52
|
+
function parse(expression, context) {
|
|
66
53
|
return jexlParser.parse(expression, context);
|
|
67
54
|
}
|
|
68
55
|
|
|
69
56
|
function contextAvailable(expression, context, typeInformation) {
|
|
70
|
-
|
|
71
|
-
if (!checkJexl(typeInformation)) {
|
|
72
|
-
parser = legacyParser;
|
|
73
|
-
}
|
|
74
|
-
return parser.contextAvailable(expression, context);
|
|
57
|
+
return jexlParser.contextAvailable(expression, context);
|
|
75
58
|
}
|
|
76
59
|
|
|
77
|
-
function
|
|
78
|
-
|
|
79
|
-
config.getConfig().defaultExpressionLanguage === 'jexl' &&
|
|
80
|
-
typeInformation.expressionLanguage &&
|
|
81
|
-
typeInformation.expressionLanguage !== 'legacy'
|
|
82
|
-
) {
|
|
83
|
-
return true;
|
|
84
|
-
} else if (config.getConfig().defaultExpressionLanguage === 'jexl' && !typeInformation.expressionLanguage) {
|
|
85
|
-
return true;
|
|
86
|
-
} else if (
|
|
87
|
-
config.getConfig().defaultExpressionLanguage === 'legacy' &&
|
|
88
|
-
typeInformation.expressionLanguage &&
|
|
89
|
-
typeInformation.expressionLanguage === 'jexl'
|
|
90
|
-
) {
|
|
91
|
-
return true;
|
|
92
|
-
}
|
|
93
|
-
return false;
|
|
60
|
+
function extractVariables(expression) {
|
|
61
|
+
return jexlParser.extractVariables(expression);
|
|
94
62
|
}
|
|
95
63
|
|
|
96
64
|
exports.parse = parse;
|
|
@@ -98,4 +66,4 @@ exports.setJEXLTransforms = setJEXLTransforms;
|
|
|
98
66
|
exports.applyExpression = applyExpression;
|
|
99
67
|
exports.extractContext = extractContext;
|
|
100
68
|
exports.contextAvailable = contextAvailable;
|
|
101
|
-
exports.
|
|
69
|
+
exports.extractVariables = extractVariables;
|
|
@@ -28,6 +28,8 @@
|
|
|
28
28
|
/* eslint-disable no-unused-vars */
|
|
29
29
|
|
|
30
30
|
const jexl = require('jexl');
|
|
31
|
+
const grammar = require('jexl/dist/grammar').getGrammar();
|
|
32
|
+
const Lexer = require('jexl/dist/Lexer');
|
|
31
33
|
const errors = require('../errors');
|
|
32
34
|
const logger = require('logops');
|
|
33
35
|
const fillService = require('../services/common/domain').fillService;
|
|
@@ -62,6 +64,31 @@ function parse(expression, context, callback) {
|
|
|
62
64
|
}
|
|
63
65
|
}
|
|
64
66
|
|
|
67
|
+
function extractVariables(expression) {
|
|
68
|
+
const inst = new Lexer(grammar);
|
|
69
|
+
let variables = [];
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const tokens = inst.tokenize(expression);
|
|
73
|
+
|
|
74
|
+
// Keep only root attributes, removing the dot and sub-attributes. For example, if we have
|
|
75
|
+
// a.0.b, a.1.b and a.2.b, we will only keep a
|
|
76
|
+
// Additionaly, it will remove the function calls, since they are also detected as identifiers
|
|
77
|
+
variables = tokens.filter(function (token, index, array) {
|
|
78
|
+
return (
|
|
79
|
+
(token.type === ' ' && array[index - 1].type !== 'dot') ||
|
|
80
|
+
(token.type === 'identifier' && array[index + 1].type !== 'openParen')
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Return only array of values
|
|
85
|
+
return variables.map((a) => a.value);
|
|
86
|
+
} catch (e) {
|
|
87
|
+
logger.warn(logContext, 'Wrong expression found "[%j]" error: "[%s]", it will be ignored', expression, e);
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
65
92
|
function extractContext(attributeList) {
|
|
66
93
|
const context = {};
|
|
67
94
|
let value;
|
|
@@ -162,6 +189,7 @@ function setTransforms(configMap) {
|
|
|
162
189
|
logger.info(logContext, message);
|
|
163
190
|
}
|
|
164
191
|
|
|
192
|
+
exports.extractVariables = extractVariables;
|
|
165
193
|
exports.extractContext = extractContext;
|
|
166
194
|
exports.contextAvailable = contextAvailable;
|
|
167
195
|
exports.applyExpression = applyExpression;
|
|
@@ -58,7 +58,7 @@ function addCommandDevice(service, subservice, device, command, callback) {
|
|
|
58
58
|
// The context for the JEXL expression should be the ID, TYPE, S, SS
|
|
59
59
|
let attrList = pluginUtils.getIdTypeServSubServiceFromDevice(device);
|
|
60
60
|
attrList = device.staticAttributes ? attrList.concat(device.staticAttributes) : attrList.concat([]);
|
|
61
|
-
let ctxt = parser.extractContext(attrList
|
|
61
|
+
let ctxt = parser.extractContext(attrList);
|
|
62
62
|
logger.debug(context, 'attrList [%j] for device %j', attrList, device);
|
|
63
63
|
// expression result will be the full command payload
|
|
64
64
|
let cmdValueRes = null;
|
|
@@ -294,7 +294,8 @@ function registerDevice(deviceObj, callback) {
|
|
|
294
294
|
// Add device ID, TYPE, S, SS to the context for the JEXL expression
|
|
295
295
|
let attrList = pluginUtils.getIdTypeServSubServiceFromDevice(deviceData);
|
|
296
296
|
attrList = deviceData.staticAttributes ? attrList.concat(deviceData.staticAttributes) : attrList;
|
|
297
|
-
|
|
297
|
+
attrList = configuration.staticAttributes ? attrList.concat(configuration.staticAttributes) : attrList;
|
|
298
|
+
let ctxt = expressionPlugin.extractContext(attrList);
|
|
298
299
|
try {
|
|
299
300
|
entityName = expressionPlugin.applyExpression(configuration.entityNameExp, ctxt, deviceData);
|
|
300
301
|
} catch (e) {
|