iotagent-node-lib 2.20.0 → 2.21.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/.readthedocs.yml +3 -1
- package/README.md +2 -2
- package/doc/advanced-topics.md +6 -6
- package/doc/api.md +11 -2
- package/doc/expressionLanguage.md +3 -0
- package/doc/northboundinteractions.md +40 -33
- package/doc/requirements.txt +4 -0
- package/{docs → doc}/roadmap.md +21 -6
- package/doc/usermanual.md +2 -2
- package/docker/Mosquitto/Dockerfile +28 -12
- package/docker/Mosquitto/README.md +8 -7
- package/docker/Mosquitto/startMosquitto.sh +8 -0
- package/lib/fiware-iotagent-lib.js +1 -0
- package/lib/jexlTranformsMap.js +3 -1
- package/lib/plugins/expressionPlugin.js +55 -21
- package/lib/plugins/multiEntity.js +2 -21
- package/lib/services/devices/deviceRegistryMemory.js +13 -2
- package/lib/services/devices/deviceRegistryMongoDB.js +15 -7
- package/lib/services/devices/deviceService.js +26 -2
- package/lib/services/ngsi/entities-NGSI-LD.js +6 -4
- package/lib/services/ngsi/entities-NGSI-v2.js +8 -6
- package/lib/services/ngsi/ngsiService.js +2 -2
- package/lib/services/northBound/contextServer-NGSI-v2.js +32 -27
- package/lib/services/northBound/contextServerUtils.js +1 -1
- package/lib/services/northBound/deviceProvisioningServer.js +2 -0
- package/lib/templates/createDevice.json +12 -0
- package/lib/templates/updateDevice.json +12 -0
- package/package.json +5 -5
- package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin35.json +2 -0
- package/test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin36.json +1 -0
- package/test/unit/ngsiv2/expressions/jexlBasedTransformations-test.js +63 -2
package/.readthedocs.yml
CHANGED
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ platform (authentication and authorization of the channel) and provide other com
|
|
|
20
20
|
This project is part of [FIWARE](https://www.fiware.org/). For more information check the FIWARE Catalogue entry for the
|
|
21
21
|
[IoT Agents](https://github.com/Fiware/catalogue/tree/master/iot-agents).
|
|
22
22
|
|
|
23
|
-
| :books: [Documentation](https://iotagent-node-lib.rtfd.io) | :mortar_board: [Academy](https://fiware-academy.readthedocs.io/en/latest/iot-agents/idas) | :dart: [Roadmap](https://github.com/telefonicaid/iotagent-node-lib/blob/master/
|
|
23
|
+
| :books: [Documentation](https://iotagent-node-lib.rtfd.io) | :mortar_board: [Academy](https://fiware-academy.readthedocs.io/en/latest/iot-agents/idas) | :dart: [Roadmap](https://github.com/telefonicaid/iotagent-node-lib/blob/master/doc/roadmap.md) |
|
|
24
24
|
| ---------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
|
|
25
25
|
|
|
26
26
|
|
|
@@ -304,7 +304,7 @@ deleted upon startup.
|
|
|
304
304
|
|
|
305
305
|
The IoT Agent Node Library is licensed under [Affero General Public License (GPL) version 3](./LICENSE).
|
|
306
306
|
|
|
307
|
-
©
|
|
307
|
+
© 2022 Telefonica Investigación y Desarrollo, S.A.U
|
|
308
308
|
|
|
309
309
|
### Are there any legal issues with AGPL 3.0? Is it safe for me to use?
|
|
310
310
|
|
package/doc/advanced-topics.md
CHANGED
|
@@ -305,8 +305,8 @@ stored in the Context Broker by adding a new attribute to the entity with the sa
|
|
|
305
305
|
element. By adding the field `explicitAttrs` with `true` value to device or group provision, the IoTAgent rejects the
|
|
306
306
|
measure elements that are not defined in the mappings of device or group provision, persisting only the one defined in
|
|
307
307
|
the mappings of the provision. If `explicitAttrs` is provided both at device and group level, the device level takes
|
|
308
|
-
precedence. Additionally `explicitAttrs` can be used to define which meassures
|
|
309
|
-
propagated to NGSI interface.
|
|
308
|
+
precedence. Additionally `explicitAttrs` can be used to define which meassures (identified by their attribute names, not
|
|
309
|
+
by their object_id) defined in JSON/JEXL array will be propagated to NGSI interface.
|
|
310
310
|
|
|
311
311
|
The different possibilities are summarized below:
|
|
312
312
|
|
|
@@ -332,8 +332,8 @@ Case 3:
|
|
|
332
332
|
"explicitAttrs": "['attr1','atrr2']"
|
|
333
333
|
```
|
|
334
334
|
|
|
335
|
-
just measures defined in the array
|
|
336
|
-
`explicitAttrs` is not a JSON but a string that looks likes a JSON).
|
|
335
|
+
just measures defined in the array (identified by their attribute names, not by their object_id) will be will be
|
|
336
|
+
propagated to NGSI interface (note that in this case the value of `explicitAttrs` is not a JSON but a string that looks likes a JSON).
|
|
337
337
|
|
|
338
338
|
Case 4:
|
|
339
339
|
|
|
@@ -346,8 +346,8 @@ depending on the JEXL expression evaluation:
|
|
|
346
346
|
- If it evaluates to `true` every measure will be propagated to NGSI interface (as in case 1)
|
|
347
347
|
- If it evaluates to `false` just measures defined in active, static (plus conditionally TimeInstant) will be
|
|
348
348
|
propagated to NGSI interface (as in case 2)
|
|
349
|
-
- If it evaluates to an array just measures defined in the array
|
|
350
|
-
case 3)
|
|
349
|
+
- If it evaluates to an array just measures defined in the array (identified by their attribute names, not by their object_id)
|
|
350
|
+
will be will be propagated to NGSI interface (as in case 3)
|
|
351
351
|
|
|
352
352
|
### Configuring operation to persist the data in Context Broker (appendMode)
|
|
353
353
|
|
package/doc/api.md
CHANGED
|
@@ -247,7 +247,7 @@ the API resource fields and the same fields in the database model.
|
|
|
247
247
|
|
|
248
248
|
#### Attribute lists
|
|
249
249
|
|
|
250
|
-
In the device model there are three list of attributes that can be declared: attributes, lazy and commands. All of them
|
|
250
|
+
In the group/device model there are three list of attributes that can be declared: attributes, lazy and commands. All of them
|
|
251
251
|
have the same syntax, an object containing the following attributes:
|
|
252
252
|
|
|
253
253
|
- **object_id** (optional): name of the attribute as coming from the device.
|
|
@@ -255,7 +255,7 @@ have the same syntax, an object containing the following attributes:
|
|
|
255
255
|
- **type** (mandatory): name of the type of the attribute in the target entity.
|
|
256
256
|
- **metadata** (optional): additional static metadata for the attribute in the target entity. (e.g. `unitCode`)
|
|
257
257
|
|
|
258
|
-
Some transformation plugins also allow the use of the following optional
|
|
258
|
+
Some transformation plugins also allow the use of the following optional fields:
|
|
259
259
|
|
|
260
260
|
- **expression**: indicates that the value of the target attribute will not be the plain value or the measurement, but
|
|
261
261
|
an expression based on a combination of the reported values. See the
|
|
@@ -268,6 +268,15 @@ Some transformation plugins also allow the use of the following optional attribu
|
|
|
268
268
|
- **reverse**: add bidirectionality expressions to the attribute. See the **bidirectionality** transformation plugin
|
|
269
269
|
in the [Data Mapping Plugins section](advanced-topics.md#bidirectionality-plugin-bidirectional) for details.
|
|
270
270
|
|
|
271
|
+
Additionally for commands (which are attributes of type `command`) the following fields are optional:
|
|
272
|
+
|
|
273
|
+
- **expression** indicates that the value of the target command will not be the plain value or the command, but an
|
|
274
|
+
expression based on a combination of the returned values. See the
|
|
275
|
+
[Expression Language definition](expressionLanguage.md) for details
|
|
276
|
+
- **payloadType**: indicates how command payload will be transformed before be sent to device. Please have a look to particular
|
|
277
|
+
IOTAs documentation for allowed values of this field in each case.
|
|
278
|
+
- **contentType**: `content-type` header used when send command by HTTP transport (ignored in other kinds of transports)
|
|
279
|
+
|
|
271
280
|
See the transformation plugins Section for more details.
|
|
272
281
|
|
|
273
282
|
#### Advice on Attribute defintions
|
|
@@ -474,7 +474,10 @@ Current common transformation set:
|
|
|
474
474
|
| slice: (arr, init, end) | `arr.slice(init,end);` |
|
|
475
475
|
| addset: (arr, x) | <code>{ return Array.from((new Set(arr)).add(x)) }</code> |
|
|
476
476
|
| removeset: (arr, x) | <code>{ let s = new Set(arr); s.delete(x); return Array.from(s) }</code> |
|
|
477
|
+
| touppercase: (val) | `String(val).toUpperCase()` |
|
|
478
|
+
| tolowercase: (val) | `String(val).toLowerCase()` |
|
|
477
479
|
|
|
480
|
+
|
|
478
481
|
You have available this [JEXL interactive playground][99] with all the transformations already loaded, in which you can
|
|
479
482
|
test all the functions described above.
|
|
480
483
|
|
|
@@ -163,16 +163,15 @@ The equivalent **NGSI-LD** payload is associated to an update operation (PATCH `
|
|
|
163
163
|
|
|
164
164
|
```json
|
|
165
165
|
{
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
166
|
+
"temperature": {
|
|
167
|
+
"type": "Property",
|
|
168
|
+
"value": "23"
|
|
169
|
+
},
|
|
170
|
+
"pressure": {
|
|
171
|
+
"type": "Property",
|
|
172
|
+
"value": "720"
|
|
173
|
+
}
|
|
174
174
|
}
|
|
175
|
-
|
|
176
175
|
```
|
|
177
176
|
|
|
178
177
|
#### R1 - UpdateContext (response)
|
|
@@ -285,7 +284,8 @@ This is an **NGSI-LD** response to `/ngsi-ld/v1/entities/<entity>`
|
|
|
285
284
|
```
|
|
286
285
|
|
|
287
286
|
In this case, the response to the QueryContext is a list of responses, one for each requested entity, indicating whether
|
|
288
|
-
the information has been retrieved successfully (in the HTTP Status code) and the requested Context information in the
|
|
287
|
+
the information has been retrieved successfully (in the HTTP Status code) and the requested Context information in the
|
|
288
|
+
body of the response.
|
|
289
289
|
|
|
290
290
|
Application level errors can be specified for each entity in this payload.
|
|
291
291
|
|
|
@@ -305,7 +305,7 @@ Context Element, but with the request as a whole.
|
|
|
305
305
|
|
|
306
306
|
### Scenario 1: active attributes
|
|
307
307
|
|
|
308
|
-

|
|
309
309
|
|
|
310
310
|
In this scenario, the interaction is started by the device, that is going to actively send a piece of data to the
|
|
311
311
|
platform. When the IoTAgent receives the data, it sends it to the Context Broker through a P1 request. The Context
|
|
@@ -319,7 +319,7 @@ updating process, and can occur at any time (they are to completely different pr
|
|
|
319
319
|
|
|
320
320
|
### Scenario 2: lazy attributes
|
|
321
321
|
|
|
322
|
-

|
|
323
323
|
|
|
324
324
|
This scenario requires that the attributes that are going to be requested are marked as provided by the IoT Agent,
|
|
325
325
|
through a registration process (NGSIv9). Examples of this registration process will be provided in the practical section
|
|
@@ -345,7 +345,7 @@ queries (and thus P2 and R2 payloads).
|
|
|
345
345
|
|
|
346
346
|
### Scenario 3: commands
|
|
347
347
|
|
|
348
|
-

|
|
349
349
|
|
|
350
350
|
This scenario requires that the attributes that are going to be requested are marked as provided by the IoT Agent,
|
|
351
351
|
through a registration process (NGSIv9). Examples of this registration process will be provided in the practical section
|
|
@@ -359,15 +359,26 @@ use three kinds of attributes:
|
|
|
359
359
|
- An attribute will be used as the _input attribute_ (the attribute registered in the Context Provider). This input
|
|
360
360
|
attribute can be thought of as a command issued to the IoTAgent (from here the name of the scenario) whose value is
|
|
361
361
|
the set of arguments of the command. Only updateContext operations will be used to interact with this attributes.
|
|
362
|
+
Typically this attribute is of type `command`.
|
|
362
363
|
|
|
363
364
|
- Another attribute will be used as the _result attribute_. This attribute will be updated from the IoTAgent, and its
|
|
364
365
|
value stored in the Context Broker. This attribute will contain the result of the command (this result can be
|
|
365
366
|
information in case the command was a "information retrieval" command or the result of an action if it was an
|
|
366
|
-
"actuator command"). Typically, the name of this attribute will be the same of the
|
|
367
|
-
additional sufix (`_info`)
|
|
367
|
+
"actuator command"). Initially its value is empty. Typically, the name of this attribute will be the same of the
|
|
368
|
+
input attribute, with an additional sufix (`_info`) and the type is `commandResult`.
|
|
368
369
|
|
|
369
370
|
- Another attribute with the same characteristics as the later will be used to indicate whether the command has ended
|
|
370
|
-
successfully or whether an error has been reported.
|
|
371
|
+
successfully or whether an error has been reported. Typically, the name of this attribute will be the same of the
|
|
372
|
+
input attribute, with an additional sufix (`_status`) and the type is `commandStatus`. The possible values of this
|
|
373
|
+
attribute are: `ERROR`, `EXPIRED`, `PENDING`, `DELIVERED`, `UNKNOWN` with the following meanings:
|
|
374
|
+
- ERROR: There is a kind of error.
|
|
375
|
+
- EXPIRED: This meens that pull command has been expired without be delivered to device according with
|
|
376
|
+
`pollingExpiration` time defined by config.
|
|
377
|
+
- PENDING: In a PUSH command means that command has been sent to device but not device has still not respond. In a
|
|
378
|
+
PULL command means that command has been stored and device still has no ask for it.
|
|
379
|
+
- DELIVERED: The command has been delivered to phisical device.
|
|
380
|
+
- OK: The command has been delivered and device has respond.
|
|
381
|
+
- UNKNOWN: This is the initial value.
|
|
371
382
|
|
|
372
383
|
In this scenario, the interaction is also initiated by the User. The user starts the scenario by sending an update
|
|
373
384
|
request P1 to the Context Broker, to the input attribute (1). The Context Broker redirects this same payload to the
|
|
@@ -835,7 +846,6 @@ The IoT Agent detects the selected attribute is a command, and replies to the Co
|
|
|
835
846
|
```json
|
|
836
847
|
[
|
|
837
848
|
{
|
|
838
|
-
|
|
839
849
|
"type": "device",
|
|
840
850
|
"id": "Dev0001",
|
|
841
851
|
"switch": {
|
|
@@ -854,7 +864,6 @@ The Context Broker, forwards the same response to the user, thus replying the or
|
|
|
854
864
|
```json
|
|
855
865
|
[
|
|
856
866
|
{
|
|
857
|
-
|
|
858
867
|
"type": "device",
|
|
859
868
|
"id": "Dev0001",
|
|
860
869
|
"switch": {
|
|
@@ -882,11 +891,11 @@ curl -X POST -H "Content-Type: application/json" -H "Accept: application/json" -
|
|
|
882
891
|
"isPattern": "false",
|
|
883
892
|
"id": "Dev0001",
|
|
884
893
|
"switch_info": {
|
|
885
|
-
"type": "
|
|
894
|
+
"type": "commandResult",
|
|
886
895
|
"value": "Switched successfully!"
|
|
887
896
|
},
|
|
888
897
|
"switch_status": {
|
|
889
|
-
"type": "
|
|
898
|
+
"type": "commandStatus",
|
|
890
899
|
"value": "OK"
|
|
891
900
|
}
|
|
892
901
|
}
|
|
@@ -906,11 +915,11 @@ The Context Broker replies to the IoT Agent with a R1 payload (200 OK):
|
|
|
906
915
|
"type": "device",
|
|
907
916
|
"id": "Dev0001",
|
|
908
917
|
"switch_info": {
|
|
909
|
-
"type": "
|
|
918
|
+
"type": "commandResult",
|
|
910
919
|
"value": ""
|
|
911
920
|
},
|
|
912
|
-
"switch_status":
|
|
913
|
-
"type": "
|
|
921
|
+
"switch_status": {
|
|
922
|
+
"type": "commandStatus",
|
|
914
923
|
"value": ""
|
|
915
924
|
}
|
|
916
925
|
}
|
|
@@ -936,8 +945,8 @@ curl -X POST -H "Content-Type: application/json" -H "Accept: application/json" -
|
|
|
936
945
|
}
|
|
937
946
|
],
|
|
938
947
|
"attributes": [
|
|
939
|
-
|
|
940
|
-
|
|
948
|
+
"switch_info",
|
|
949
|
+
"switch_status"
|
|
941
950
|
]
|
|
942
951
|
}' "https://<platform-ip>:10027/v2/op/query"
|
|
943
952
|
```
|
|
@@ -947,20 +956,18 @@ The Context Broker replies with all the desired data, in R2 format (200 OK):
|
|
|
947
956
|
```json
|
|
948
957
|
[
|
|
949
958
|
{
|
|
950
|
-
|
|
951
959
|
"type": "device",
|
|
952
960
|
"id": "Dev0001",
|
|
953
961
|
"switch_info": {
|
|
954
|
-
"type": "
|
|
962
|
+
"type": "commandResult",
|
|
955
963
|
"value": "Switched successfully!"
|
|
956
964
|
},
|
|
957
965
|
"switch_status": {
|
|
958
|
-
"type": "
|
|
966
|
+
"type": "commandStatus",
|
|
959
967
|
"value": "OK"
|
|
960
968
|
}
|
|
961
969
|
}
|
|
962
970
|
]
|
|
963
|
-
|
|
964
971
|
```
|
|
965
972
|
|
|
966
973
|
### Scenario 3: commands (error)
|
|
@@ -978,11 +985,11 @@ curl -X POST -H "Content-Type: application/json" -H "Accept: application/json" -
|
|
|
978
985
|
"isPattern": "false",
|
|
979
986
|
"id": "Dev0001",
|
|
980
987
|
"switch_info":{
|
|
981
|
-
"type": "
|
|
988
|
+
"type": "commandResult",
|
|
982
989
|
"value": "The switch could not be switched due to the following error: switch blocked"
|
|
983
990
|
},
|
|
984
991
|
"switch_status":{
|
|
985
|
-
"type": "
|
|
992
|
+
"type": "commandStatus",
|
|
986
993
|
"value": "ERROR"
|
|
987
994
|
}
|
|
988
995
|
}
|
|
@@ -999,11 +1006,11 @@ In this case, the Context Broker reply with the following response (200 OK):
|
|
|
999
1006
|
"type": "device",
|
|
1000
1007
|
"id": "Dev0001",
|
|
1001
1008
|
"switch_info": {
|
|
1002
|
-
"type": "
|
|
1009
|
+
"type": "commandResult",
|
|
1003
1010
|
"value": ""
|
|
1004
1011
|
},
|
|
1005
1012
|
"switch_status": {
|
|
1006
|
-
"type": "
|
|
1013
|
+
"type": "commandStatus",
|
|
1007
1014
|
"value": ""
|
|
1008
1015
|
}
|
|
1009
1016
|
}
|
package/{docs → doc}/roadmap.md
RENAMED
|
@@ -13,7 +13,7 @@ only, and this section may be revised to provide newer information at any time.
|
|
|
13
13
|
|
|
14
14
|
Disclaimer:
|
|
15
15
|
|
|
16
|
-
- This section has been last updated in March
|
|
16
|
+
- This section has been last updated in March 2022. Please take into account its content could be obsolete.
|
|
17
17
|
- Note we develop this software in Agile way, so development plan is continuously under review. Thus, this roadmap has
|
|
18
18
|
to be understood as rough plan of features to be done along time which is fully valid only at the time of writing
|
|
19
19
|
it. This roadmap has not be understood as a commitment on features and/or dates.
|
|
@@ -25,19 +25,17 @@ Disclaimer:
|
|
|
25
25
|
The following list of features are planned to be addressed in the short term, and incorporated in a release of the
|
|
26
26
|
product:
|
|
27
27
|
|
|
28
|
-
- Selectively ignore measure in the southbound interface (community)
|
|
29
|
-
- JEXL support in expressions (community)
|
|
30
28
|
- cgroup literal in configuration groups management API (community)
|
|
31
29
|
- Metadata processing improvements
|
|
32
|
-
-
|
|
30
|
+
- Improve command functionalities (binary data + expression + mapping)
|
|
33
31
|
|
|
34
32
|
### Medium term
|
|
35
33
|
|
|
36
34
|
The following list of features are planned to be addressed in the medium term, typically within the subsequent
|
|
37
35
|
release(s) generated in the next 9 months after the next planned release:
|
|
38
36
|
|
|
39
|
-
-
|
|
40
|
-
-
|
|
37
|
+
- Accept JEXL Expressions for entity name in autoprovisioned devices (#1145)
|
|
38
|
+
- Refactor entities-NGSI-v2.js module (#1166)
|
|
41
39
|
|
|
42
40
|
### Long term
|
|
43
41
|
|
|
@@ -48,3 +46,20 @@ us if you wish to get involved in the implementation or influence the roadmap:
|
|
|
48
46
|
- Incremental introduccion of ECMAScript6 syntax (previous analysis of which sub-set of interesting aspect we want to
|
|
49
47
|
take)
|
|
50
48
|
- Use the lightweight ingestion mechanism for connection oriented updates implemented in Context Broker
|
|
49
|
+
- Add support to other transport protocols (BacNET, Modbus, etc)
|
|
50
|
+
|
|
51
|
+
### Features already completed
|
|
52
|
+
|
|
53
|
+
The following list contains all features that were in the roadmap and have already been implemented.
|
|
54
|
+
|
|
55
|
+
- Support for "delta" measures (i.e. "temperature _increased_ in 5 degress" instead of "temperature _is_ 25")
|
|
56
|
+
- Allow to handle binary messages ([iota-ul#530](https://github.com/telefonicaid/iotagent-ul/issues/530))
|
|
57
|
+
- Removal support for NGSIv1 (#966) ([2.18.0](https://github.com/telefonicaid/iotagent-node-lib/releases/tag/2.18.0))
|
|
58
|
+
- Selectively ignore measure in the southbound interface
|
|
59
|
+
([iotagent-json#416](https://github.com/telefonicaid/iotagent-json/issues/416),
|
|
60
|
+
[iotagent-ul#372](https://github.com/telefonicaid/iotagent-ul/issues/372))
|
|
61
|
+
([2.13.0](https://github.com/telefonicaid/iotagent-node-lib/releases/tag/2.13.0))
|
|
62
|
+
- JEXL support in expressions (#801, #687, #868)
|
|
63
|
+
([2.13.0](https://github.com/telefonicaid/iotagent-node-lib/releases/tag/2.13.0))
|
|
64
|
+
- Add MongoDB authentication support (#844)
|
|
65
|
+
([2.12.0](https://github.com/telefonicaid/iotagent-node-lib/releases/tag/2.12.0))
|
package/doc/usermanual.md
CHANGED
|
@@ -209,8 +209,8 @@ function setCommandResult(entityName, resource, apikey, commandName, commandResu
|
|
|
209
209
|
###### Description
|
|
210
210
|
|
|
211
211
|
Update the result of a command in the Context Broker. The result of the command has two components: the result of the
|
|
212
|
-
command itself will be represented with the suffix `
|
|
213
|
-
with the `
|
|
212
|
+
command itself will be represented with the suffix `_info` in the entity while the status is updated in the attribute
|
|
213
|
+
with the `_status` suffix.
|
|
214
214
|
|
|
215
215
|
###### Params
|
|
216
216
|
|
|
@@ -1,19 +1,35 @@
|
|
|
1
|
-
|
|
1
|
+
ARG IMAGE_TAG=11.2-slim
|
|
2
|
+
FROM debian:${IMAGE_TAG}
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
4
|
+
ARG CLEAN_DEV_TOOLS
|
|
5
|
+
ENV CLEAN_DEV_TOOLS ${CLEAN_DEV_TOOLS:-1}
|
|
5
6
|
|
|
6
7
|
ENV CONGIF_FROM_ENV true
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
9
|
+
COPY aclfile /root/
|
|
10
|
+
COPY startMosquitto.sh /bin
|
|
11
|
+
|
|
12
|
+
RUN \
|
|
13
|
+
# Install security updates
|
|
14
|
+
apt-get -y update && \
|
|
15
|
+
apt-get -y upgrade && \
|
|
16
|
+
# Install dependencies
|
|
17
|
+
apt-get -y install \
|
|
18
|
+
wget \
|
|
19
|
+
mosquitto && \
|
|
20
|
+
cp /etc/mosquitto/mosquitto.conf /etc/mosquitto/mosquitto.conf.orig && \
|
|
21
|
+
chmod 755 /bin/startMosquitto.sh && \
|
|
22
|
+
mkdir -p /var/log/mosquitto && \
|
|
23
|
+
chown mosquitto:mosquitto /var/log/mosquitto && \
|
|
24
|
+
mkdir -p /var/run/mosquitto/ && \
|
|
25
|
+
chown mosquitto:mosquitto /var/run/mosquitto && \
|
|
26
|
+
echo "INFO: Cleaning unused software..." && \
|
|
27
|
+
apt-get clean && \
|
|
28
|
+
apt-get -y autoremove --purge && \
|
|
29
|
+
if [ ${CLEAN_DEV_TOOLS} -eq 0 ] ; then exit 0 ; fi && \
|
|
30
|
+
# remove the same packages we installed at the beginning to build Orch
|
|
31
|
+
apt-get -y autoremove --purge \
|
|
32
|
+
wget
|
|
17
33
|
|
|
18
34
|
|
|
19
35
|
EXPOSE 1883
|
|
@@ -2,10 +2,11 @@ Thi directory containts the Dockerfile (and associated files) for a container of
|
|
|
2
2
|
This container is provide as a help for users to test with MQTT, but it is just an auxiliary material in this repository.
|
|
3
3
|
|
|
4
4
|
The following releases matches with eclipse-mosquitto version:
|
|
5
|
-
-
|
|
6
|
-
- 1.
|
|
7
|
-
- 1.
|
|
8
|
-
- 1.
|
|
9
|
-
- 1.
|
|
10
|
-
- 1.
|
|
11
|
-
- 1.
|
|
5
|
+
- 2.0.0 uses mosquitto-2.0.11 from Debian 11
|
|
6
|
+
- 1.6.0 uses mosquitto-1.6.10-1.el7.x86_64 (from Centos7)
|
|
7
|
+
- 1.5.0 uses mosquitto-1.6.10-1.el7.x86_64 (from Centos7)
|
|
8
|
+
- 1.4.0 uses mosquitto-1.6.10-1.el7.x86_64 (from Centos7)
|
|
9
|
+
- 1.3.0 uses mosquitto-1.6.8-1.el7.x86_64 (from Centos7)
|
|
10
|
+
- 1.2.0 uses mosquitto-1.6.7-1.el7.x86_64 (from Centos7)
|
|
11
|
+
- 1.1.0 uses mosquitto-1.5.8-1.el7.x86_64 (from Centos7)
|
|
12
|
+
- 1.0.0 uses mosquitto-1.4.8-1.el7.x86_64 (from Centos7)
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
|
|
3
|
+
echo "INFO: startMosquitto..."
|
|
4
|
+
|
|
3
5
|
if [ "${CONGIF_FROM_ENV}" = true ] ; then
|
|
4
6
|
cp /etc/mosquitto/mosquitto.conf.orig /etc/mosquitto/mosquitto.conf
|
|
7
|
+
sed -i 's/log_dest file \/var\/log\/mosquitto\/mosquitto.log/log_dest stderr/g' /etc/mosquitto/mosquitto.conf
|
|
5
8
|
echo "log_timestamp true" >> /etc/mosquitto/mosquitto.conf
|
|
6
9
|
echo "log_timestamp_format %Y-%m-%dT%H:%M:%S" >> /etc/mosquitto/mosquitto.conf
|
|
7
10
|
echo 'listener 9001' >> /etc/mosquitto/mosquitto.conf
|
|
@@ -18,4 +21,9 @@ if [ "${CONGIF_FROM_ENV}" = true ] ; then
|
|
|
18
21
|
fi
|
|
19
22
|
fi
|
|
20
23
|
|
|
24
|
+
echo "INFO: content /etc/mosquitto/mosquitto.conf: "
|
|
25
|
+
cat /etc/mosquitto/mosquitto.conf
|
|
26
|
+
|
|
27
|
+
echo "INFO: start: startMosquitto -c /etc/mosquitto/mosquitto.conf"
|
|
28
|
+
|
|
21
29
|
/usr/sbin/mosquitto -c /etc/mosquitto/mosquitto.conf
|
|
@@ -314,6 +314,7 @@ exports.listDevices = deviceService.listDevices;
|
|
|
314
314
|
exports.getDevice = deviceService.getDevice;
|
|
315
315
|
exports.getDeviceSilently = deviceService.getDeviceSilently;
|
|
316
316
|
exports.getDeviceByName = deviceService.getDeviceByName;
|
|
317
|
+
exports.getDeviceByNameAndType = deviceService.getDeviceByNameAndType;
|
|
317
318
|
exports.getDevicesByAttribute = deviceService.getDevicesByAttribute;
|
|
318
319
|
exports.mergeDeviceWithConfiguration = deviceService.mergeDeviceWithConfiguration;
|
|
319
320
|
exports.retrieveDevice = deviceService.retrieveDevice;
|
package/lib/jexlTranformsMap.js
CHANGED
|
@@ -30,6 +30,8 @@ const jexlParser = require('./jexlParser');
|
|
|
30
30
|
const config = require('../commonConfig');
|
|
31
31
|
/* eslint-disable no-unused-vars */
|
|
32
32
|
const logger = require('logops');
|
|
33
|
+
const errors = require('../errors');
|
|
34
|
+
const constants = require('../constants');
|
|
33
35
|
const context = {
|
|
34
36
|
op: 'IoTAgentNGSI.expressionPlugin'
|
|
35
37
|
};
|
|
@@ -42,6 +44,22 @@ function setJEXLTransforms(transformationMap) {
|
|
|
42
44
|
jexlParser.setTransforms(transformationMap);
|
|
43
45
|
}
|
|
44
46
|
|
|
47
|
+
function applyExpression(expression, context, typeInformation) {
|
|
48
|
+
let parser = legacyParser;
|
|
49
|
+
if (checkJexl(typeInformation)) {
|
|
50
|
+
parser = jexlParser;
|
|
51
|
+
}
|
|
52
|
+
return parser.applyExpression(expression, context, typeInformation);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function extractContext(attributeList, typeInformation) {
|
|
56
|
+
let parser = legacyParser;
|
|
57
|
+
if (checkJexl(typeInformation)) {
|
|
58
|
+
parser = jexlParser;
|
|
59
|
+
}
|
|
60
|
+
return parser.extractContext(attributeList);
|
|
61
|
+
}
|
|
62
|
+
|
|
45
63
|
function mergeAttributes(attrList1, attrList2) {
|
|
46
64
|
const finalCollection = _.clone(attrList1);
|
|
47
65
|
const additionalItems = [];
|
|
@@ -70,26 +88,26 @@ function mergeAttributes(attrList1, attrList2) {
|
|
|
70
88
|
return finalCollection.concat(additionalItems);
|
|
71
89
|
}
|
|
72
90
|
|
|
73
|
-
function
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
return true;
|
|
89
|
-
}
|
|
90
|
-
return false;
|
|
91
|
+
function checkJexl(typeInformation) {
|
|
92
|
+
if (
|
|
93
|
+
config.getConfig().defaultExpressionLanguage === 'jexl' &&
|
|
94
|
+
typeInformation.expressionLanguage &&
|
|
95
|
+
typeInformation.expressionLanguage !== 'legacy'
|
|
96
|
+
) {
|
|
97
|
+
return true;
|
|
98
|
+
} else if (config.getConfig().defaultExpressionLanguage === 'jexl' && !typeInformation.expressionLanguage) {
|
|
99
|
+
return true;
|
|
100
|
+
} else if (
|
|
101
|
+
config.getConfig().defaultExpressionLanguage === 'legacy' &&
|
|
102
|
+
typeInformation.expressionLanguage &&
|
|
103
|
+
typeInformation.expressionLanguage === 'jexl'
|
|
104
|
+
) {
|
|
105
|
+
return true;
|
|
91
106
|
}
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
92
109
|
|
|
110
|
+
function update(entity, typeInformation, callback) {
|
|
93
111
|
function processEntityUpdateNgsi2(attributes) {
|
|
94
112
|
let parser = legacyParser;
|
|
95
113
|
if (checkJexl(typeInformation)) {
|
|
@@ -114,15 +132,31 @@ function update(entity, typeInformation, callback) {
|
|
|
114
132
|
|
|
115
133
|
try {
|
|
116
134
|
logger.debug(context, 'expressionPlugin entity %j', entity);
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
135
|
+
const attsArray = utils.extractAttributesArrayFromNgsi2Entity(entity);
|
|
136
|
+
// Exclude processing all attr expressions when current attr is of type 'commandStatus' or 'commandResult'
|
|
137
|
+
const attsArrayFiltered = attsArray.filter((obj) => {
|
|
138
|
+
return ![constants.COMMAND_STATUS, constants.COMMAND_RESULT].includes(obj.type);
|
|
139
|
+
});
|
|
140
|
+
const attsArrayCmd = attsArray.filter((obj) => {
|
|
141
|
+
// just attr of type 'commandStatus' or 'commandResult'
|
|
142
|
+
return [constants.COMMAND_STATUS, constants.COMMAND_RESULT].includes(obj.type);
|
|
143
|
+
});
|
|
144
|
+
let attsArrayFinal = [];
|
|
145
|
+
if (attsArrayFiltered.length > 0) {
|
|
146
|
+
attsArrayFinal = processEntityUpdateNgsi2(attsArrayFiltered);
|
|
147
|
+
}
|
|
148
|
+
attsArrayFinal = attsArrayFinal.concat(attsArrayCmd);
|
|
149
|
+
entity = utils.createNgsi2Entity(entity.id, entity.type, attsArrayFinal, true);
|
|
120
150
|
|
|
121
151
|
callback(null, entity, typeInformation);
|
|
122
152
|
} catch (e) {
|
|
153
|
+
logger.error(context, 'expressionPlugin error %j procesing entity %j', e, entity);
|
|
123
154
|
callback(e);
|
|
124
155
|
}
|
|
125
156
|
}
|
|
126
157
|
|
|
127
158
|
exports.update = update;
|
|
128
159
|
exports.setJEXLTransforms = setJEXLTransforms;
|
|
160
|
+
exports.applyExpression = applyExpression;
|
|
161
|
+
exports.extractContext = extractContext;
|
|
162
|
+
exports.checkJexl = checkJexl;
|
|
@@ -30,7 +30,7 @@ const _ = require('underscore');
|
|
|
30
30
|
const constants = require('../constants');
|
|
31
31
|
const legacyParser = require('./expressionParser');
|
|
32
32
|
const jexlParser = require('./jexlParser');
|
|
33
|
-
const
|
|
33
|
+
const expressionPlugin = require('./expressionPlugin');
|
|
34
34
|
/* eslint-disable-next-line no-unused-vars */
|
|
35
35
|
const logger = require('logops');
|
|
36
36
|
/* eslint-disable-next-line no-unused-vars */
|
|
@@ -41,25 +41,6 @@ const utils = require('./pluginUtils');
|
|
|
41
41
|
/* eslint-disable-next-line no-unused-vars */
|
|
42
42
|
const aliasPlugin = require('./attributeAlias');
|
|
43
43
|
|
|
44
|
-
function checkJexl(typeInformation) {
|
|
45
|
-
if (
|
|
46
|
-
config.getConfig().defaultExpressionLanguage === 'jexl' &&
|
|
47
|
-
typeInformation.expressionLanguage &&
|
|
48
|
-
typeInformation.expressionLanguage !== 'legacy'
|
|
49
|
-
) {
|
|
50
|
-
return true;
|
|
51
|
-
} else if (config.getConfig().defaultExpressionLanguage === 'jexl' && !typeInformation.expressionLanguage) {
|
|
52
|
-
return true;
|
|
53
|
-
} else if (
|
|
54
|
-
config.getConfig().defaultExpressionLanguage === 'legacy' &&
|
|
55
|
-
typeInformation.expressionLanguage &&
|
|
56
|
-
typeInformation.expressionLanguage === 'jexl'
|
|
57
|
-
) {
|
|
58
|
-
return true;
|
|
59
|
-
}
|
|
60
|
-
return false;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
44
|
function hasEntityName(item) {
|
|
64
45
|
return item.entity_name;
|
|
65
46
|
}
|
|
@@ -226,7 +207,7 @@ function propagateTimestamp(entity, entities) {
|
|
|
226
207
|
|
|
227
208
|
function updateAttribute(entity, typeInformation, callback) {
|
|
228
209
|
let parser = legacyParser;
|
|
229
|
-
if (checkJexl(typeInformation)) {
|
|
210
|
+
if (expressionPlugin.checkJexl(typeInformation)) {
|
|
230
211
|
parser = jexlParser;
|
|
231
212
|
}
|
|
232
213
|
const attsArray = utils.extractAttributesArrayFromNgsi2Entity(entity);
|
|
@@ -149,13 +149,19 @@ function getDevice(id, service, subservice, callback) {
|
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
-
function
|
|
152
|
+
function getByNameAndType(name, type, service, subservice, callback) {
|
|
153
153
|
const devices = _.values(registeredDevices[service]);
|
|
154
154
|
let device;
|
|
155
155
|
|
|
156
156
|
for (let i = 0; i < devices.length; i++) {
|
|
157
157
|
if (devices[i].name === name) {
|
|
158
|
-
|
|
158
|
+
if (type) {
|
|
159
|
+
if (devices[i].type === type) {
|
|
160
|
+
device = devices[i];
|
|
161
|
+
}
|
|
162
|
+
} else {
|
|
163
|
+
device = devices[i];
|
|
164
|
+
}
|
|
159
165
|
}
|
|
160
166
|
}
|
|
161
167
|
|
|
@@ -166,6 +172,10 @@ function getByName(name, service, subservice, callback) {
|
|
|
166
172
|
}
|
|
167
173
|
}
|
|
168
174
|
|
|
175
|
+
function getByName(name, service, subservice, callback) {
|
|
176
|
+
getByNameAndType(name, null, service, subservice, callback);
|
|
177
|
+
}
|
|
178
|
+
|
|
169
179
|
function update(device, callback) {
|
|
170
180
|
registeredDevices[device.service][device.id] = deepClone(device);
|
|
171
181
|
callback(null, device);
|
|
@@ -208,4 +218,5 @@ exports.list = listDevices;
|
|
|
208
218
|
exports.get = getDevice;
|
|
209
219
|
exports.getSilently = getDevice;
|
|
210
220
|
exports.getByName = getByName;
|
|
221
|
+
exports.getByNameAndType = getByNameAndType;
|
|
211
222
|
exports.clear = clear;
|
|
@@ -243,15 +243,18 @@ function getDevice(id, service, subservice, callback) {
|
|
|
243
243
|
});
|
|
244
244
|
}
|
|
245
245
|
|
|
246
|
-
function
|
|
246
|
+
function getByNameAndType(name, type, service, servicepath, callback) {
|
|
247
247
|
context = fillService(context, { service, subservice: servicepath });
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
name,
|
|
252
|
-
service,
|
|
248
|
+
let optionsQuery = {
|
|
249
|
+
name: name,
|
|
250
|
+
service: service,
|
|
253
251
|
subservice: servicepath
|
|
254
|
-
}
|
|
252
|
+
};
|
|
253
|
+
if (type) {
|
|
254
|
+
optionsQuery.type = type;
|
|
255
|
+
}
|
|
256
|
+
logger.debug(context, 'Looking for device with [%j].', optionsQuery);
|
|
257
|
+
const query = Device.model.findOne(optionsQuery);
|
|
255
258
|
|
|
256
259
|
query.select({ __v: 0 });
|
|
257
260
|
|
|
@@ -270,6 +273,10 @@ function getByName(name, service, servicepath, callback) {
|
|
|
270
273
|
});
|
|
271
274
|
}
|
|
272
275
|
|
|
276
|
+
function getByName(name, service, servicepath, callback) {
|
|
277
|
+
getByNameAndType(name, null, service, servicepath, callback);
|
|
278
|
+
}
|
|
279
|
+
|
|
273
280
|
/**
|
|
274
281
|
* Updates the given device into the database.
|
|
275
282
|
* updated.
|
|
@@ -361,4 +368,5 @@ exports.list = alarmsInt(constants.MONGO_ALARM, listDevices);
|
|
|
361
368
|
exports.get = alarmsInt(constants.MONGO_ALARM, getDevice);
|
|
362
369
|
exports.getSilently = getDevice;
|
|
363
370
|
exports.getByName = alarmsInt(constants.MONGO_ALARM, getByName);
|
|
371
|
+
exports.getByNameAndType = alarmsInt(constants.MONGO_ALARM, getByNameAndType);
|
|
364
372
|
exports.clear = alarmsInt(constants.MONGO_ALARM, clear);
|
|
@@ -214,7 +214,13 @@ function findConfigurationGroup(deviceObj, callback) {
|
|
|
214
214
|
} else {
|
|
215
215
|
config
|
|
216
216
|
.getGroupRegistry()
|
|
217
|
-
.findTypeSilently(
|
|
217
|
+
.findTypeSilently(
|
|
218
|
+
deviceObj.service,
|
|
219
|
+
deviceObj.subservice,
|
|
220
|
+
deviceObj.type,
|
|
221
|
+
deviceObj.apikey,
|
|
222
|
+
handlerGroupFindByType
|
|
223
|
+
);
|
|
218
224
|
}
|
|
219
225
|
}
|
|
220
226
|
|
|
@@ -513,6 +519,18 @@ function getDeviceByName(deviceName, service, subservice, callback) {
|
|
|
513
519
|
config.getRegistry().getByName(deviceName, service, subservice, callback);
|
|
514
520
|
}
|
|
515
521
|
|
|
522
|
+
/**
|
|
523
|
+
* Retrieve a device from the registry based on its entity name and type
|
|
524
|
+
*
|
|
525
|
+
* @param {String} deviceName Name of the entity associated to a device.
|
|
526
|
+
* @param {String} deviceType Type of the entity associated to a device.
|
|
527
|
+
* @param {String} service Service the device belongs to.
|
|
528
|
+
* @param {String} subservice Division inside the service.
|
|
529
|
+
*/
|
|
530
|
+
function getDeviceByNameAndType(deviceName, deviceType, service, subservice, callback) {
|
|
531
|
+
config.getRegistry().getByNameAndType(deviceName, deviceType, service, subservice, callback);
|
|
532
|
+
}
|
|
533
|
+
|
|
516
534
|
/**
|
|
517
535
|
* Retrieve a device from the registry based on the value of a given attribute.
|
|
518
536
|
*
|
|
@@ -581,7 +599,12 @@ function findOrCreate(deviceId, group, callback) {
|
|
|
581
599
|
callback(error, device, group);
|
|
582
600
|
});
|
|
583
601
|
} else {
|
|
584
|
-
logger.info(
|
|
602
|
+
logger.info(
|
|
603
|
+
context,
|
|
604
|
+
'Device %j not provisioned due autoprovision is disabled by its conf %j',
|
|
605
|
+
newDevice,
|
|
606
|
+
group
|
|
607
|
+
);
|
|
585
608
|
callback(new errors.DeviceNotFound(deviceId));
|
|
586
609
|
}
|
|
587
610
|
} else {
|
|
@@ -632,6 +655,7 @@ exports.getDevice = intoTrans(context, checkRegistry)(getDevice);
|
|
|
632
655
|
exports.getDeviceSilently = intoTrans(context, checkRegistry)(getDeviceSilently);
|
|
633
656
|
exports.getDevicesByAttribute = intoTrans(context, checkRegistry)(getDevicesByAttribute);
|
|
634
657
|
exports.getDeviceByName = intoTrans(context, checkRegistry)(getDeviceByName);
|
|
658
|
+
exports.getDeviceByNameAndType = intoTrans(context, checkRegistry)(getDeviceByNameAndType);
|
|
635
659
|
exports.register = intoTrans(context, registerDevice);
|
|
636
660
|
exports.updateRegister = intoTrans(context, updateRegisterDevice);
|
|
637
661
|
exports.unregister = intoTrans(context, unregisterDevice);
|
|
@@ -410,8 +410,9 @@ function addLinkedEntities(typeInformation, json) {
|
|
|
410
410
|
* @param {Object} typeInformation Configuration information for the device.
|
|
411
411
|
*/
|
|
412
412
|
function removeHiddenAttrsFromMultiEntity(entities, typeInformation) {
|
|
413
|
-
|
|
413
|
+
let explicitAttrsList;
|
|
414
414
|
if (typeInformation.explicitAttrs) {
|
|
415
|
+
explicitAttrsList = [];
|
|
415
416
|
explicitAttrsList.push('type');
|
|
416
417
|
explicitAttrsList.push('id');
|
|
417
418
|
if (typeof typeInformation.explicitAttrs === 'boolean') {
|
|
@@ -437,7 +438,7 @@ function removeHiddenAttrsFromMultiEntity(entities, typeInformation) {
|
|
|
437
438
|
});
|
|
438
439
|
}
|
|
439
440
|
}
|
|
440
|
-
if (explicitAttrsList.length
|
|
441
|
+
if (explicitAttrsList && explicitAttrsList.length >= 0) {
|
|
441
442
|
entities.forEach((entity) => {
|
|
442
443
|
const hidden = _.difference(_.keys(entity), explicitAttrsList);
|
|
443
444
|
hidden.forEach((attr) => {
|
|
@@ -457,8 +458,9 @@ function removeHiddenAttrsFromMultiEntity(entities, typeInformation) {
|
|
|
457
458
|
function removeHiddenAttrs(result, typeInformation) {
|
|
458
459
|
delete result.id;
|
|
459
460
|
delete result.type;
|
|
460
|
-
|
|
461
|
+
let explicitAttrsList;
|
|
461
462
|
if (typeInformation.explicitAttrs && typeof typeInformation.explicitAttrs === 'boolean') {
|
|
463
|
+
explicitAttrsList = [];
|
|
462
464
|
if (typeInformation.timestamp) {
|
|
463
465
|
explicitAttrsList.push(constants.TIMESTAMP_ATTRIBUTE);
|
|
464
466
|
}
|
|
@@ -478,7 +480,7 @@ function removeHiddenAttrs(result, typeInformation) {
|
|
|
478
480
|
const res = jexlParser.parse(typeInformation.explicitAttrs, ctx);
|
|
479
481
|
explicitAttrsList = res;
|
|
480
482
|
}
|
|
481
|
-
if (explicitAttrsList.length
|
|
483
|
+
if (explicitAttrsList && explicitAttrsList.length >= 0) {
|
|
482
484
|
const hidden = _.difference(_.keys(result), explicitAttrsList);
|
|
483
485
|
hidden.forEach((attr) => {
|
|
484
486
|
delete result[attr];
|
|
@@ -321,8 +321,9 @@ function sendQueryValueNgsi2(entityName, attributes, typeInformation, token, cal
|
|
|
321
321
|
* @param {Object} typeInformation Configuration information for the device.
|
|
322
322
|
*/
|
|
323
323
|
function removeHiddenAttrsFromMultiEntity(entities, typeInformation) {
|
|
324
|
-
|
|
324
|
+
let explicitAttrsList;
|
|
325
325
|
if (typeInformation.explicitAttrs) {
|
|
326
|
+
explicitAttrsList = [];
|
|
326
327
|
explicitAttrsList.push('type');
|
|
327
328
|
explicitAttrsList.push('id');
|
|
328
329
|
if (typeof typeInformation.explicitAttrs === 'boolean') {
|
|
@@ -349,10 +350,10 @@ function removeHiddenAttrsFromMultiEntity(entities, typeInformation) {
|
|
|
349
350
|
});
|
|
350
351
|
}
|
|
351
352
|
}
|
|
352
|
-
if (explicitAttrsList.length
|
|
353
|
-
logger.debug(context, 'removeHiddenAttrsFromMultiEntity %s', explicitAttrsList);
|
|
353
|
+
if (explicitAttrsList && explicitAttrsList.length >= 0) {
|
|
354
354
|
entities.forEach((entity) => {
|
|
355
355
|
const hidden = _.difference(_.keys(entity), explicitAttrsList);
|
|
356
|
+
logger.debug(context, 'removeHiddenAttrsFromMultiEntity %s from entity %s', hidden, entity);
|
|
356
357
|
hidden.forEach((attr) => {
|
|
357
358
|
delete entity[attr];
|
|
358
359
|
});
|
|
@@ -369,8 +370,9 @@ function removeHiddenAttrsFromMultiEntity(entities, typeInformation) {
|
|
|
369
370
|
function removeHiddenAttrs(result, typeInformation) {
|
|
370
371
|
delete result.id;
|
|
371
372
|
delete result.type;
|
|
372
|
-
|
|
373
|
+
let explicitAttrsList;
|
|
373
374
|
if (typeInformation.explicitAttrs && typeof typeInformation.explicitAttrs === 'boolean') {
|
|
375
|
+
explicitAttrsList = [];
|
|
374
376
|
if (typeInformation.timestamp) {
|
|
375
377
|
explicitAttrsList.push(constants.TIMESTAMP_ATTRIBUTE);
|
|
376
378
|
}
|
|
@@ -390,9 +392,9 @@ function removeHiddenAttrs(result, typeInformation) {
|
|
|
390
392
|
const res = jexlParser.applyExpression(typeInformation.explicitAttrs, ctx, typeInformation);
|
|
391
393
|
explicitAttrsList = res;
|
|
392
394
|
}
|
|
393
|
-
if (explicitAttrsList.length
|
|
394
|
-
logger.debug(context, 'removeHiddenAttrs %s', explicitAttrsList);
|
|
395
|
+
if (explicitAttrsList && explicitAttrsList.length >= 0) {
|
|
395
396
|
const hidden = _.difference(_.keys(result), explicitAttrsList);
|
|
397
|
+
logger.debug(context, 'removeHiddenAttrs %s', hidden);
|
|
396
398
|
hidden.forEach((attr) => {
|
|
397
399
|
delete result[attr];
|
|
398
400
|
});
|
|
@@ -177,7 +177,7 @@ function executeWithDeviceInformation(operationFunction) {
|
|
|
177
177
|
|
|
178
178
|
/**
|
|
179
179
|
* Update the result of a command in the Context Broker. The result of the command has two components: the result
|
|
180
|
-
* of the command itself will be represented with the sufix '
|
|
180
|
+
* of the command itself will be represented with the sufix '_info' in the entity while the status is updated in the
|
|
181
181
|
* attribute with the '_status' sufix.
|
|
182
182
|
*
|
|
183
183
|
* @param {String} entityName Name of the entity holding the command.
|
|
@@ -234,7 +234,7 @@ function setCommandResult(
|
|
|
234
234
|
}
|
|
235
235
|
|
|
236
236
|
if (commandInfo.length === 1) {
|
|
237
|
-
exports.update(entityName,
|
|
237
|
+
exports.update(entityName, typeInformation.type, apikey, attributes, typeInformation, callback);
|
|
238
238
|
} else {
|
|
239
239
|
callback(new errors.CommandNotFound(commandName));
|
|
240
240
|
}
|
|
@@ -159,29 +159,32 @@ function generateUpdateActionsNgsi2(req, contextElement, callback) {
|
|
|
159
159
|
callback(null, updateActions);
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
deviceService.
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
deviceService.
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
162
|
+
deviceService.getDeviceByNameAndType(
|
|
163
|
+
entityId,
|
|
164
|
+
entityType,
|
|
165
|
+
req.headers['fiware-service'],
|
|
166
|
+
req.headers['fiware-servicepath'],
|
|
167
|
+
function (error, deviceObj) {
|
|
168
|
+
if (error) {
|
|
169
|
+
callback(error);
|
|
170
|
+
} else {
|
|
171
|
+
async.waterfall(
|
|
172
|
+
[
|
|
173
|
+
apply(deviceService.findConfigurationGroup, deviceObj),
|
|
174
|
+
apply(
|
|
175
|
+
deviceService.mergeDeviceWithConfiguration,
|
|
176
|
+
['lazy', 'internalAttributes', 'active', 'staticAttributes', 'commands', 'subscriptions'],
|
|
177
|
+
[null, null, [], [], [], [], []],
|
|
178
|
+
deviceObj
|
|
179
|
+
),
|
|
180
|
+
splitUpdates,
|
|
181
|
+
createActionsArray
|
|
182
|
+
],
|
|
183
|
+
callback
|
|
184
|
+
);
|
|
185
|
+
}
|
|
183
186
|
}
|
|
184
|
-
|
|
187
|
+
);
|
|
185
188
|
}
|
|
186
189
|
|
|
187
190
|
/** Express middleware to manage incoming update requests using NGSIv2.
|
|
@@ -238,8 +241,7 @@ function defaultQueryHandlerNgsi2(id, type, service, subservice, attributes, cal
|
|
|
238
241
|
type,
|
|
239
242
|
id
|
|
240
243
|
};
|
|
241
|
-
|
|
242
|
-
deviceService.getDeviceByName(id, service, subservice, function (error, ngsiDevice) {
|
|
244
|
+
deviceService.getDeviceByNameAndType(id, type, service, subservice, function (error, ngsiDevice) {
|
|
243
245
|
if (error) {
|
|
244
246
|
callback(error);
|
|
245
247
|
} else {
|
|
@@ -285,8 +287,9 @@ function handleNotificationNgsi2(req, res, next) {
|
|
|
285
287
|
}
|
|
286
288
|
}
|
|
287
289
|
}
|
|
288
|
-
deviceService.
|
|
290
|
+
deviceService.getDeviceByNameAndType(
|
|
289
291
|
dataElement.id,
|
|
292
|
+
dataElement.type,
|
|
290
293
|
req.headers['fiware-service'],
|
|
291
294
|
req.headers['fiware-servicepath'],
|
|
292
295
|
function (error, device) {
|
|
@@ -448,6 +451,7 @@ function handleQueryNgsi2(req, res, next) {
|
|
|
448
451
|
}
|
|
449
452
|
|
|
450
453
|
deviceService.findConfigurationGroup(device, function (error, group) {
|
|
454
|
+
logger.debug(context, 'finishQueryForDevice %j', group);
|
|
451
455
|
const executeCompleteAttributes = apply(completeAttributes, attributes, group);
|
|
452
456
|
const executeQueryHandler = apply(
|
|
453
457
|
actualHandler,
|
|
@@ -474,8 +478,9 @@ function handleQueryNgsi2(req, res, next) {
|
|
|
474
478
|
|
|
475
479
|
if (contextEntity.id) {
|
|
476
480
|
getFunction = apply(
|
|
477
|
-
deviceService.
|
|
481
|
+
deviceService.getDeviceByNameAndType,
|
|
478
482
|
contextEntity.id,
|
|
483
|
+
contextEntity.type,
|
|
479
484
|
req.headers['fiware-service'],
|
|
480
485
|
req.headers['fiware-servicepath']
|
|
481
486
|
);
|
|
@@ -504,7 +509,7 @@ function handleQueryNgsi2(req, res, next) {
|
|
|
504
509
|
} else {
|
|
505
510
|
deviceList = [innerDevice];
|
|
506
511
|
}
|
|
507
|
-
|
|
512
|
+
logger.debug(context, 'handleFindDevice from %j', deviceList);
|
|
508
513
|
async.map(
|
|
509
514
|
deviceList,
|
|
510
515
|
async.apply(finishQueryForDevice, attributes, contextEntity, actualHandler),
|
|
@@ -91,7 +91,7 @@ function executeUpdateSideEffects(device, id, type, service, subservice, attribu
|
|
|
91
91
|
];
|
|
92
92
|
|
|
93
93
|
sideEffects.push(
|
|
94
|
-
apply(ngsi.update, device.name, device.
|
|
94
|
+
apply(ngsi.update, device.name, device.type, device.apikey, newAttributes, device)
|
|
95
95
|
);
|
|
96
96
|
}
|
|
97
97
|
}
|
|
@@ -184,6 +184,8 @@ function attributeToProvisioningAPIFormat(attribute) {
|
|
|
184
184
|
entity_name: attribute.entity_name,
|
|
185
185
|
entity_type: attribute.entity_type,
|
|
186
186
|
mqtt: attribute.mqtt,
|
|
187
|
+
payloadType: attribute.payloadType,
|
|
188
|
+
contentType: attribute.contentType,
|
|
187
189
|
metadata: attribute.metadata
|
|
188
190
|
};
|
|
189
191
|
}
|
|
@@ -173,6 +173,18 @@
|
|
|
173
173
|
"type": "string",
|
|
174
174
|
"pattern": "^([^<>();'=\"]+)*$"
|
|
175
175
|
},
|
|
176
|
+
"expression": {
|
|
177
|
+
"description": "Optional expression for command transformation",
|
|
178
|
+
"type": "string"
|
|
179
|
+
},
|
|
180
|
+
"payloadType": {
|
|
181
|
+
"description": "Payload type",
|
|
182
|
+
"type": "string"
|
|
183
|
+
},
|
|
184
|
+
"contentType": {
|
|
185
|
+
"description": "Content type",
|
|
186
|
+
"type": "string"
|
|
187
|
+
},
|
|
176
188
|
"mqtt": {
|
|
177
189
|
"description": "Mqtt properties",
|
|
178
190
|
"type": "object",
|
|
@@ -158,6 +158,18 @@
|
|
|
158
158
|
"type": "string",
|
|
159
159
|
"pattern": "^([^<>();'=\"]+)*$"
|
|
160
160
|
},
|
|
161
|
+
"expression": {
|
|
162
|
+
"description": "Optional expression for command transformation",
|
|
163
|
+
"type": "string"
|
|
164
|
+
},
|
|
165
|
+
"payloadType": {
|
|
166
|
+
"description": "Payload type",
|
|
167
|
+
"type": "string"
|
|
168
|
+
},
|
|
169
|
+
"contentType": {
|
|
170
|
+
"description": "Content type",
|
|
171
|
+
"type": "string"
|
|
172
|
+
},
|
|
161
173
|
"mqtt": {
|
|
162
174
|
"description": "Mqtt properties",
|
|
163
175
|
"type": "object",
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "iotagent-node-lib",
|
|
3
3
|
"license": "AGPL-3.0-only",
|
|
4
4
|
"description": "IoT Agent library to interface with NGSI Context Broker",
|
|
5
|
-
"version": "2.
|
|
5
|
+
"version": "2.21.0",
|
|
6
6
|
"homepage": "https://github.com/telefonicaid/iotagent-node-lib",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"fiware",
|
|
@@ -42,17 +42,17 @@
|
|
|
42
42
|
"watch": "watch 'npm test && npm run lint' ./lib ./test"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"async": "2.6.
|
|
45
|
+
"async": "2.6.4",
|
|
46
46
|
"body-parser": "~1.19.0",
|
|
47
47
|
"express": "~4.16.4",
|
|
48
48
|
"got": "~11.8.2",
|
|
49
49
|
"jexl": "2.3.0",
|
|
50
50
|
"jison": "0.4.18",
|
|
51
51
|
"logops": "2.1.2",
|
|
52
|
-
"moment": "~2.
|
|
52
|
+
"moment": "~2.29.2",
|
|
53
53
|
"moment-timezone": "~0.5.25",
|
|
54
|
-
"mongodb": "3.6.
|
|
55
|
-
"mongoose": "5.7.
|
|
54
|
+
"mongodb": "3.6.12",
|
|
55
|
+
"mongoose": "5.7.14",
|
|
56
56
|
"query-string": "6.5.0",
|
|
57
57
|
"revalidator": "~0.3.1",
|
|
58
58
|
"underscore": "~1.12.1",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"myattr":{"type": "String","value": "location"}}
|
|
@@ -319,7 +319,7 @@ const iotAgentConfig = {
|
|
|
319
319
|
expression: "{coordinates: [lon,lat], type: 'Point'}"
|
|
320
320
|
}
|
|
321
321
|
],
|
|
322
|
-
explicitAttrs:
|
|
322
|
+
explicitAttrs: "[ 'myattr' ]"
|
|
323
323
|
},
|
|
324
324
|
GPS6: {
|
|
325
325
|
commands: [],
|
|
@@ -345,6 +345,30 @@ const iotAgentConfig = {
|
|
|
345
345
|
}
|
|
346
346
|
],
|
|
347
347
|
explicitAttrs: true
|
|
348
|
+
},
|
|
349
|
+
GPS7: {
|
|
350
|
+
commands: [],
|
|
351
|
+
type: 'GPS',
|
|
352
|
+
lazy: [],
|
|
353
|
+
static: [
|
|
354
|
+
{
|
|
355
|
+
name: 'color',
|
|
356
|
+
type: 'string',
|
|
357
|
+
value: 'blue'
|
|
358
|
+
}
|
|
359
|
+
],
|
|
360
|
+
active: [
|
|
361
|
+
{
|
|
362
|
+
name: 'price',
|
|
363
|
+
type: 'number'
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
name: 'location',
|
|
367
|
+
type: 'geo:json',
|
|
368
|
+
expression: "{coordinates: [lon,lat], type: 'Point'}"
|
|
369
|
+
}
|
|
370
|
+
],
|
|
371
|
+
explicitAttrs: '[ ]'
|
|
348
372
|
}
|
|
349
373
|
},
|
|
350
374
|
service: 'smartgondor',
|
|
@@ -1180,7 +1204,7 @@ describe('Java expression language (JEXL) based transformations plugin', functio
|
|
|
1180
1204
|
.patch(
|
|
1181
1205
|
'/v2/entities/gps1/attrs',
|
|
1182
1206
|
utils.readExampleFile(
|
|
1183
|
-
'./test/unit/ngsiv2/examples/contextRequests/
|
|
1207
|
+
'./test/unit/ngsiv2/examples/contextRequests/updateContextExpressionPlugin36.json'
|
|
1184
1208
|
)
|
|
1185
1209
|
)
|
|
1186
1210
|
.query({ type: 'GPS' })
|
|
@@ -1230,6 +1254,43 @@ describe('Java expression language (JEXL) based transformations plugin', functio
|
|
|
1230
1254
|
});
|
|
1231
1255
|
});
|
|
1232
1256
|
});
|
|
1257
|
+
|
|
1258
|
+
describe('When there is an extra TimeInstant sent by the device to be removed by jexl expression with context but with empty explicitAttrs', function () {
|
|
1259
|
+
// Case: Expression which results is sent as a new attribute
|
|
1260
|
+
const values = [
|
|
1261
|
+
{
|
|
1262
|
+
name: 'lat',
|
|
1263
|
+
type: 'Number',
|
|
1264
|
+
value: 52
|
|
1265
|
+
},
|
|
1266
|
+
{
|
|
1267
|
+
name: 'lon',
|
|
1268
|
+
type: 'Number',
|
|
1269
|
+
value: 13
|
|
1270
|
+
},
|
|
1271
|
+
{
|
|
1272
|
+
name: 'myattr',
|
|
1273
|
+
type: 'String',
|
|
1274
|
+
value: 'location'
|
|
1275
|
+
},
|
|
1276
|
+
{
|
|
1277
|
+
name: 'TimeInstant',
|
|
1278
|
+
type: 'DateTime',
|
|
1279
|
+
value: '2015-08-05T07:35:01.468+00:00'
|
|
1280
|
+
}
|
|
1281
|
+
];
|
|
1282
|
+
|
|
1283
|
+
beforeEach(function () {
|
|
1284
|
+
nock.cleanAll();
|
|
1285
|
+
});
|
|
1286
|
+
|
|
1287
|
+
it('should calculate them and remove non-explicitAttrs by jexl expression with context from the payload ', function (done) {
|
|
1288
|
+
iotAgentLib.update('gps1', 'GPS7', '', values, function (error) {
|
|
1289
|
+
should.not.exist(error);
|
|
1290
|
+
done();
|
|
1291
|
+
});
|
|
1292
|
+
});
|
|
1293
|
+
});
|
|
1233
1294
|
});
|
|
1234
1295
|
|
|
1235
1296
|
describe('Java expression language (JEXL) based transformations plugin - Timestamps', function () {
|