iotagent-node-lib 4.7.0 → 4.8.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 +2 -2
- package/CHANGES_NEXT_RELEASE +1 -0
- package/Changelog +8 -0
- package/doc/README.md +1 -0
- package/doc/api.md +8 -4
- package/doc/devel/northboundinteractions.md +122 -15
- package/doc/models/models.md +260 -0
- package/doc/requirements.txt +1 -1
- package/docker/Mosquitto/Dockerfile +1 -1
- package/lib/fiware-iotagent-lib.js +15 -11
- package/lib/jexlTranformsMap.js +182 -35
- package/lib/model/Command.js +9 -0
- package/lib/model/Device.js +1 -0
- package/lib/services/commands/commandRegistryMongoDB.js +15 -1
- package/lib/services/commands/commandService.js +3 -3
- package/lib/services/devices/deviceRegistryMongoDB.js +1 -1
- package/lib/services/devices/deviceService.js +11 -2
- package/lib/services/northBound/contextServer-NGSI-v2.js +12 -3
- package/lib/services/northBound/contextServer.js +2 -1
- package/lib/services/northBound/northboundServer.js +1 -0
- package/lib/templates/createDevice.json +4 -0
- package/lib/templates/updateDevice.json +4 -0
- package/package.json +1 -1
- package/test/functional/testUtils.js +13 -2
- package/test/unit/expressions/jexlExpression-test.js +165 -1
- package/test/unit/ngsiv2/ngsiService/subscriptions-test.js +0 -326
package/.github/workflows/ci.yml
CHANGED
|
@@ -45,7 +45,7 @@ jobs:
|
|
|
45
45
|
runs-on: ubuntu-latest
|
|
46
46
|
services:
|
|
47
47
|
mongodb:
|
|
48
|
-
image: mongo:
|
|
48
|
+
image: mongo:8.0
|
|
49
49
|
ports:
|
|
50
50
|
- 27017:27017
|
|
51
51
|
strategy:
|
|
@@ -71,7 +71,7 @@ jobs:
|
|
|
71
71
|
needs: unit-test
|
|
72
72
|
services:
|
|
73
73
|
mongodb:
|
|
74
|
-
image: mongo:
|
|
74
|
+
image: mongo:8.0
|
|
75
75
|
ports:
|
|
76
76
|
- 27017:27017
|
|
77
77
|
steps:
|
package/CHANGES_NEXT_RELEASE
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
package/Changelog
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
4.8.0 (May 23rd, 2025)
|
|
2
|
+
|
|
3
|
+
- Add: notification-based commands (#1455)
|
|
4
|
+
- Add: allow define headers in device commands (iotagent-json#873)
|
|
5
|
+
- Add: index for Device model based on {service: 1, subservice: 1, id: 1, apikey: 1} (#1576)
|
|
6
|
+
- Fix: Duplicated Devices when burst measures to non provisioned Device (iotagent-json#865)
|
|
7
|
+
- Fix: modified JEXL transformations (toisostring, gettime, parseint, etc.) to return null instead of NaN when some unexpected situation occurs (#1701)
|
|
8
|
+
|
|
1
9
|
4.7.0 (February 3rd, 2025)
|
|
2
10
|
|
|
3
11
|
- Add: store (and recover) previous jexlctxt and make available in current jexlctxt (#1690)
|
package/doc/README.md
CHANGED
package/doc/api.md
CHANGED
|
@@ -165,7 +165,8 @@ parameters defined at device level in database, the parameters are inherit from
|
|
|
165
165
|
|
|
166
166
|
### Uniqueness of groups and devices
|
|
167
167
|
|
|
168
|
-
Group uniqueness is defined by the combination of: resource and apikey. This is so because given a measure (identified
|
|
168
|
+
Group uniqueness is defined by the combination of: resource and apikey. This is so because given a measure (identified
|
|
169
|
+
by an apikey) there is no way to identify group related if apikey is not unique for all services and subservices.
|
|
169
170
|
|
|
170
171
|
Device uniqueness is defined by the combination of: service, subservice, device_id and apikey. Note that several devices
|
|
171
172
|
with the same device_id are allowed in the same service and subservice as long as their apikeys are different.
|
|
@@ -256,6 +257,9 @@ Additionally for commands (which are attributes of type `command`) the following
|
|
|
256
257
|
particular IOTAs documentation for allowed values of this field in each case.
|
|
257
258
|
- **contentType**: `content-type` header used when send command by HTTP transport (ignored in other kinds of
|
|
258
259
|
transports)
|
|
260
|
+
- **headers**: extra customer headers used when send command by HTTP transport (ignored in other kinds of
|
|
261
|
+
transports)
|
|
262
|
+
Check full detail of these fields in [comand-transformations](https://github.com/telefonicaid/iotagent-json/blob/master/docs/usermanual.md#commands-transformations)
|
|
259
263
|
|
|
260
264
|
Note that, when information coming from devices, this means measures, are not defined neither in the group, nor in the
|
|
261
265
|
device, the IoT agent will store that information into the destination entity using the same attribute name than the
|
|
@@ -579,8 +583,8 @@ Case 5:
|
|
|
579
583
|
|
|
580
584
|
depending on the JEXL expression evaluation:
|
|
581
585
|
|
|
582
|
-
- If it evaluates to `
|
|
583
|
-
- If it evaluates to `
|
|
586
|
+
- If it evaluates to `false` every measure will be propagated to NGSI interface (as in case 1)
|
|
587
|
+
- If it evaluates to `true` just measures defined in active, static (plus conditionally TimeInstant) will be
|
|
584
588
|
propagated to NGSI interface (as in case 2)
|
|
585
589
|
- If it evaluates to an array just measures defined in the array (identified by their attribute names, not by their
|
|
586
590
|
object_id) will be will be propagated to NGSI interface (as in case 3)
|
|
@@ -2459,4 +2463,4 @@ updateEntityRequestsError 5
|
|
|
2459
2463
|
[18]:
|
|
2460
2464
|
https://czosel.github.io/jexl-playground/#/?context=%7B%0A%20%20%22value%22%20%3A%206%2C%0A%20%20%22ts%22%3A%201637245214901%2C%0A%20%22name%22%3A%20%22DevId629%22%2C%0A%20%22object%22%3A%7Bname%3A%20%22John%22%2C%20surname%3A%20%22Doe%22%7D%2C%0A%20%20%22array%22%3A%5B1%2C3%5D%0A%7D&input=name%7Csubstr(0%2Cname%7CindexOf(%22e%22)%2B1)&transforms=%7B%0A%20%20%20%20jsonparse%3A%20(str)%20%3D%3E%20JSON.parse(str)%2C%0A%20%20%20%20jsonstringify%3A%20(obj)%20%3D%3E%20JSON.stringify(obj)%2C%0A%20%20%20%20indexOf%3A%20(val%2C%20char)%20%3D%3E%20String(val).indexOf(char)%2C%0A%20%20%20%20length%3A%20(val)%20%3D%3E%20String(val).length%2C%0A%20%20%20%20trim%3A%20(val)%20%3D%3E%20String(val).trim()%2C%0A%20%20%20%20substr%3A%20(val%2C%20int1%2C%20int2)%20%3D%3E%20String(val).substr(int1%2C%20int2)%2C%0A%20%20%20%20addreduce%3A%20(arr)%20%3D%3E%20arr.reduce((i%2C%20v)%20%3D%3E%20i%20%2B%20v)%2C%0A%20%20%20%20lengtharray%3A%20(arr)%20%3D%3E%20arr.length%2C%0A%20%20%20%20typeof%3A%20(val)%20%3D%3E%20typeof%20val%2C%0A%20%20%20%20isarray%3A%20(arr)%20%3D%3E%20Array.isArray(arr)%2C%0A%20%20%20%20isnan%3A%20(val)%20%3D%3E%20isNaN(val)%2C%0A%20%20%20%20parseint%3A%20(val)%20%3D%3E%20parseInt(val)%2C%0A%20%20%20%20parsefloat%3A%20(val)%20%3D%3E%20parseFloat(val)%2C%0A%20%20%20%20toisodate%3A%20(val)%20%3D%3E%20new%20Date(val).toISOString()%2C%0A%20%20%20%20timeoffset%3A%20(isostr)%20%3D%3E%20new%20Date(isostr).getTimezoneOffset()%2C%0A%20%20%20%20tostring%3A%20(val)%20%3D%3E%20val.toString()%2C%0A%20%20%20%20urlencode%3A%20(val)%20%3D%3E%20encodeURI(val)%2C%0A%20%20%20%20urldecode%3A%20(val)%20%3D%3E%20decodeURI(val)%2C%0A%20%20%20%20replacestr%3A%20(str%2C%20from%2C%20to)%20%3D%3E%20str.replace(from%2C%20to)%2C%0A%20%20%20%20replaceregexp%3A%20(str%2C%20reg%2C%20to)%20%3D%3E%20str.replace(new%20RegExp(reg)%2C%20to)%2C%0A%20%20%20%20replaceallstr%3A%20(str%2C%20from%2C%20to)%20%3D%3E%20str.replaceAll(from%2C%20to)%2C%0A%20%20%20%20replaceallregexp%3A%20(str%2C%20reg%2C%20to)%20%3D%3E%20str.replaceAll(new%20RegExp(reg%2C%20'g')%2C%20to)%2C%0A%20%20%20%20split%3A%20(str%2C%20ch)%20%3D%3E%20str.split(ch)%2C%0A%20%20%20%20mapper%3A%20(val%2C%20values%2C%20choices)%20%3D%3E%20choices%5Bvalues.findIndex((target)%20%3D%3E%20target%20%3D%3D%3D%20val)%5D%2C%0A%20%20%20%20thmapper%3A%20(val%2C%20values%2C%20choices)%20%3D%3E%0A%20%20%20%20%20%20%20%20choices%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20values.reduce((acc%2C%20curr%2C%20i)%20%3D%3E%20(acc%20%3D%3D%3D%200%20%7C%7C%20acc%20%3F%20acc%20%3A%20val%20%3C%3D%20curr%20%3F%20(acc%20%3D%20i)%20%3A%20(acc%20%3D%20null))%2C%20null)%0A%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20bitwisemask%3A%20(i%2C%20mask%2C%20op%2C%20shf)%20%3D%3E%0A%20%20%20%20%20%20%20%20(op%20%3D%3D%3D%20'%26'%20%3F%20parseInt(i)%20%26%20mask%20%3A%20op%20%3D%3D%3D%20'%7C'%20%3F%20parseInt(i)%20%7C%20mask%20%3A%20op%20%3D%3D%3D%20'%5E'%20%3F%20parseInt(i)%20%5E%20mask%20%3A%20i)%20%3E%3E%0A%20%20%20%20%20%20%20%20shf%2C%0A%20%20%20%20slice%3A%20(arr%2C%20init%2C%20end)%20%3D%3E%20arr.slice(init%2C%20end)%0A%7D
|
|
2461
2465
|
[99]:
|
|
2462
|
-
https://czosel.github.io/jexl-playground/#/?context=%7B%0A%20%20%22text%22%20%3A%20%22%20%20foobar%7B%7D%20%20%22%0A%7D&input=text%20%7C%20replacestr(%22foo%22%2C%22FOO%22)%7Ctrim%7Curlencode&transforms=%7B%0A%20%20%20%20jsonparse%3A%20(str)%20%3D%3E%20JSON.parse(str)%2C%0A%20%20%20%20jsonstringify%3A%20(obj)%20%3D%3E%20JSON.stringify(obj)%2C%0A%20%20%20%20indexOf%3A%20(val%2C%20char)%20%3D%3E%20String(val).indexOf(char)%2C%0A%20%20%20%20length%3A%20(val)%20%3D%3E%20String(val).length%2C%0A%20%20%20%20trim%3A%20(val)%20%3D%3E%20String(val).trim()%2C%0A%20%20%20%20substr%3A%20(val%2C%20int1%2C%20int2)%20%3D%3E%20String(val).substr(int1%2C%20int2)%2C%0A%20%20%20%20addreduce%3A%20(arr)%20%3D%3E%20arr.reduce((i%2C%20v)%20%3D%3E%20i%20%2B%20v)%2C%0A%20%20%20%20lengtharray%3A%20(arr)%20%3D%3E%20arr.length%2C%0A%20%20%20%20typeof%3A%20(val)%20%3D%3E%20typeof%20val%2C%0A%20%20%20%20isarray%3A%20(arr)%20%3D%3E%20Array.isArray(arr)%2C%0A%20%20%20%20isnan%3A%20(val)%20%3D%3E%20isNaN(val)%2C%0A%20%20%20%20parseint%3A%20(val)%20%3D%3E%20parseInt(val)%2C%0A%20%20%20%20parsefloat%3A%20(val)%20%3D%3E%20parseFloat(val)%2C%0A%20%20%20%20toisodate%3A%20(val)%20%3D%3E%20new%20Date(val).toISOString()%2C%0A%20%20%20%20timeoffset%3A%20(isostr)%20%3D%3E%20new%20Date(isostr).getTimezoneOffset()%2C%0A%20%20%20%20tostring%3A%20(val)%20%3D%3E%20val.toString()%2C%0A%20%20%20%20urlencode%3A%20(val)%20%3D%3E%20encodeURI(val)%2C%0A%20%20%20%20urldecode%3A%20(val)%20%3D%3E%20decodeURI(val)%2C%0A%20%20%20%20replacestr%3A%20(str%2C%20from%2C%20to)%20%3D%3E%20str.replace(from%2C%20to)%2C%0A%20%20%20%20replaceregexp%3A%20(str%2C%20reg%2C%20to)%20%3D%3E%20str.replace(new%20RegExp(reg)%2C%20to)%2C%0A%20%20%20%20replaceallstr%3A%20(str%2C%20from%2C%20to)%20%3D%3E%20str.replaceAll(from%2C%20to)%2C%0A%20%20%20%20replaceallregexp%3A%20(str%2C%20reg%2C%20to)%20%3D%3E%20str.replaceAll(new%20RegExp(reg%2C%20'g')%2C%20to)%2C%0A%20%20%20%20split%3A%20(str%2C%20ch)%20%3D%3E%20str.split(ch)%2C%0A%20%20%20%20mapper%3A%20(val%2C%20values%2C%20choices)%20%3D%3E%20choices%5Bvalues.findIndex((target)%20%3D%3E%20target%20%3D%3D%3D%20val)%5D%2C%0A%20%20%20%20thmapper%3A%20(val%2C%20values%2C%20choices)%20%3D%3E%0A%20%20%20%20%20%20%20%20choices%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20values.reduce((acc%2C%20curr%2C%20i)%20%3D%3E%20(acc%20%3D%3D%3D%200%20%7C%7C%20acc%20%3F%20acc%20%3A%20val%20%3C%3D%20curr%20%3F%20(acc%20%3D%20i)%20%3A%20(acc%20%3D%20null))%2C%20null)%0A%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20bitwisemask%3A%20(i%2C%20mask%2C%20op%2C%20shf)%20%3D%3E%0A%20%20%20%20%20%20%20%20(op%20%3D%3D%3D%20'%26'%20%3F%20parseInt(i)%20%26%20mask%20%3A%20op%20%3D%3D%3D%20'%7C'%20%3F%20parseInt(i)%20%7C%20mask%20%3A%20op%20%3D%3D%3D%20'%5E'%20%3F%20parseInt(i)%20%5E%20mask%20%3A%20i)%20%3E%3E%0A%20%20%20%20%20%20%20%20shf%2C%0A%20%20%20%20slice%3A%20(arr%2C%20init%2C%20end)%20%3D%3E%20arr.slice(init%2C%20end)%0A%7D
|
|
2466
|
+
https://czosel.github.io/jexl-playground/#/?context=%7B%0A%20%20%22text%22%20%3A%20%22%20%20foobar%7B%7D%20%20%22%0A%7D&input=text%20%7C%20replacestr(%22foo%22%2C%22FOO%22)%7Ctrim%7Curlencode&transforms=%7B%0A%20%20%20%20jsonparse%3A%20(str)%20%3D%3E%20JSON.parse(str)%2C%0A%20%20%20%20jsonstringify%3A%20(obj)%20%3D%3E%20JSON.stringify(obj)%2C%0A%20%20%20%20indexOf%3A%20(val%2C%20char)%20%3D%3E%20String(val).indexOf(char)%2C%0A%20%20%20%20length%3A%20(val)%20%3D%3E%20String(val).length%2C%0A%20%20%20%20trim%3A%20(val)%20%3D%3E%20String(val).trim()%2C%0A%20%20%20%20substr%3A%20(val%2C%20int1%2C%20int2)%20%3D%3E%20String(val).substr(int1%2C%20int2)%2C%0A%20%20%20%20addreduce%3A%20(arr)%20%3D%3E%20arr.reduce((i%2C%20v)%20%3D%3E%20i%20%2B%20v)%2C%0A%20%20%20%20lengtharray%3A%20(arr)%20%3D%3E%20arr.length%2C%0A%20%20%20%20typeof%3A%20(val)%20%3D%3E%20typeof%20val%2C%0A%20%20%20%20isarray%3A%20(arr)%20%3D%3E%20Array.isArray(arr)%2C%0A%20%20%20%20isnan%3A%20(val)%20%3D%3E%20isNaN(val)%2C%0A%20%20%20%20parseint%3A%20(val)%20%3D%3E%20parseInt(val)%2C%0A%20%20%20%20parsefloat%3A%20(val)%20%3D%3E%20parseFloat(val)%2C%0A%20%20%20%20toisodate%3A%20(val)%20%3D%3E%20new%20Date(val).toISOString()%2C%0A%20%20%20%20timeoffset%3A%20(isostr)%20%3D%3E%20new%20Date(isostr).getTimezoneOffset()%2C%0A%20%20%20%20tostring%3A%20(val)%20%3D%3E%20val.toString()%2C%0A%20%20%20%20urlencode%3A%20(val)%20%3D%3E%20encodeURI(val)%2C%0A%20%20%20%20urldecode%3A%20(val)%20%3D%3E%20decodeURI(val)%2C%0A%20%20%20%20replacestr%3A%20(str%2C%20from%2C%20to)%20%3D%3E%20str.replace(from%2C%20to)%2C%0A%20%20%20%20replaceregexp%3A%20(str%2C%20reg%2C%20to)%20%3D%3E%20str.replace(new%20RegExp(reg)%2C%20to)%2C%0A%20%20%20%20replaceallstr%3A%20(str%2C%20from%2C%20to)%20%3D%3E%20str.replaceAll(from%2C%20to)%2C%0A%20%20%20%20replaceallregexp%3A%20(str%2C%20reg%2C%20to)%20%3D%3E%20str.replaceAll(new%20RegExp(reg%2C%20'g')%2C%20to)%2C%0A%20%20%20%20split%3A%20(str%2C%20ch)%20%3D%3E%20str.split(ch)%2C%0A%20%20%20%20joinarrtostr%3A%20(arr%2C%20ch)%20%3D%3E%20arr.join(ch)%2C%0A%20%20%20%20concatarr%3A%20(arr%2C%20arr2)%20%3D%3E%20arr.concat(arr2)%2C%0A%20%20%20%20mapper%3A%20(val%2C%20values%2C%20choices)%20%3D%3E%20choices%5Bvalues.findIndex((target)%20%3D%3E%20target%20%3D%3D%3D%20val)%5D%2C%0A%20%20%20%20thmapper%3A%20(val%2C%20values%2C%20choices)%20%3D%3E%0A%20%20%20%20%20%20%20%20choices%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20values.reduce((acc%2C%20curr%2C%20i)%20%3D%3E%20(acc%20%3D%3D%3D%200%20%7C%7C%20acc%20%3F%20acc%20%3A%20val%20%3C%3D%20curr%20%3F%20(acc%20%3D%20i)%20%3A%20(acc%20%3D%20null))%2C%20null)%0A%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20bitwisemask%3A%20(i%2C%20mask%2C%20op%2C%20shf)%20%3D%3E%0A%20%20%20%20%20%20%20%20(op%20%3D%3D%3D%20'%26'%20%3F%20parseInt(i)%20%26%20mask%20%3A%20op%20%3D%3D%3D%20'%7C'%20%3F%20parseInt(i)%20%7C%20mask%20%3A%20op%20%3D%3D%3D%20'%5E'%20%3F%20parseInt(i)%20%5E%20mask%20%3A%20i)%20%3E%3E%0A%20%20%20%20%20%20%20%20shf%2C%0A%20%20%20%20slice%3A%20(arr%2C%20init%2C%20end)%20%3D%3E%20arr.slice(init%2C%20end)%2C%0A%20%20%20%20addset%3A%20(arr%2C%20x)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20return%20Array.from(new%20Set(arr).add(x))%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20removeset%3A%20(arr%2C%20x)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20let%20s%20%3D%20new%20Set(arr)%3B%0A%20%20%20%20%20%20%20%20s.delete(x)%3B%0A%20%20%20%20%20%20%20%20return%20Array.from(s)%3B%0A%20%20%20%20%7D%2C%0A%20%20%20%20touppercase%3A%20(val)%20%3D%3E%20String(val).toUpperCase()%2C%0A%20%20%20%20tolowercase%3A%20(val)%20%3D%3E%20String(val).toLowerCase()%2C%0A%20%20%20%20floor%3A%20(val)%20%3D%3E%20Math.floor(val)%2C%0A%20%20%20%20ceil%3A%20(val)%20%3D%3E%20Math.ceil(val)%2C%0A%20%20%20%20round%3A%20(val)%20%3D%3E%20Math.round(val)%2C%0A%20%20%20%20tofixed%3A%20(val%2C%20decimals)%20%3D%3E%20Number.parseFloat(val).toFixed(decimals)%2C%0A%20%20%20%20gettime%3A%20(d)%20%3D%3E%20new%20Date(d).getTime()%2C%0A%20%20%20%20toisostring%3A%20(d)%20%3D%3E%20new%20Date(d).toISOString()%2C%0A%20%20%20%20%2F%2F%20https%3A%2F%2Fdeveloper.mozilla.org%2Fes%2Fdocs%2FWeb%2FJavaScript%2FReference%2FGlobal_Objects%2FDate%2FtoLocaleString%0A%20%20%20%20localestring%3A%20(d%2C%20timezone%2C%20options)%20%3D%3E%20new%20Date(d).toLocaleString(timezone%2C%20options)%2C%0A%20%20%20%20now%3A%20()%20%3D%3E%20Date.now()%2C%0A%20%20%20%20hextostring%3A%20(val)%20%3D%3E%0A%20%20%20%20%20%20%20%20new%20TextDecoder().decode(new%20Uint8Array(val.match(%2F.%7B1%2C2%7D%2Fg).map((byte)%20%3D%3E%20parseInt(byte%2C%2016))))%2C%0A%20%20%20%20valuePicker%3A%20(val%2Cpick)%20%3D%3E%20Object.entries(val).filter((%5B%2C%20v%5D)%20%3D%3E%20v%20%3D%3D%3D%20pick).map((%5Bk%2C%5D)%20%3D%3E%20k)%2C%0A%20%20%20%20valuePickerMulti%3A%20(val%2Cpick)%20%3D%3E%20Object.entries(val).filter((%5B%2C%20v%5D)%20%3D%3E%20pick.includes(v)).map((%5Bk%2C%5D)%20%3D%3E%20k)%0A%7D
|
|
@@ -775,6 +775,8 @@ The registration of the commands is performed once in the lifetime of the Device
|
|
|
775
775
|
|
|
776
776
|
#### Command Execution
|
|
777
777
|
|
|
778
|
+
##### Based in update (classic way)
|
|
779
|
+
|
|
778
780
|
Scenario 3 begins with the request for a command from the User to the Context Broker (P1):
|
|
779
781
|
|
|
780
782
|
```bash
|
|
@@ -825,21 +827,8 @@ Fiware-Correlator: 9cae9496-8ec7-11e6-80fc-fa163e734aab
|
|
|
825
827
|
}
|
|
826
828
|
```
|
|
827
829
|
|
|
828
|
-
The IoT Agent detects the selected attribute is a command, and replies to the Context Broker with
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
```json
|
|
832
|
-
[
|
|
833
|
-
{
|
|
834
|
-
"type": "device",
|
|
835
|
-
"id": "Dev0001",
|
|
836
|
-
"switch": {
|
|
837
|
-
"type": "command",
|
|
838
|
-
"value": ""
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
]
|
|
842
|
-
```
|
|
830
|
+
The IoT Agent detects the selected attribute is a command, and replies to the Context Broker with a 204 OK (without
|
|
831
|
+
payload).
|
|
843
832
|
|
|
844
833
|
This response just indicates that the IoT Agent has received the command successfully, and gives no information about
|
|
845
834
|
the requested information or command execution.
|
|
@@ -862,6 +851,100 @@ The Context Broker, forwards the same response to the user, thus replying the or
|
|
|
862
851
|
At this point, the command has been issued to the IoTAgent and the User doesn't still know what the result of its
|
|
863
852
|
request will be.
|
|
864
853
|
|
|
854
|
+
##### Based in notification (new way)
|
|
855
|
+
|
|
856
|
+
A new way to ContextBroker provides a command to a IoTAgent is through notifications. In this case CB notify the command
|
|
857
|
+
to the IotAgent with a request like the following:
|
|
858
|
+
|
|
859
|
+
```bash
|
|
860
|
+
POST /notify HTTP/1.1
|
|
861
|
+
Host: <target-host>:<northbound_port>
|
|
862
|
+
fiware-service: workshop
|
|
863
|
+
Fiware-ServicePath: /iota2ngsi
|
|
864
|
+
Accept: application/json
|
|
865
|
+
Content-length: 290
|
|
866
|
+
Content-type: application/json; charset=utf-8
|
|
867
|
+
Fiware-Correlator: 9cae9496-8ec7-11e6-80fc-fa163e734aab
|
|
868
|
+
|
|
869
|
+
{
|
|
870
|
+
"subscriptionId": "60b0cedd497e8b681d40b58e",
|
|
871
|
+
"data": [{
|
|
872
|
+
"id": "123456abcdefg",
|
|
873
|
+
"type": "switchOnOffExecution",
|
|
874
|
+
"targetEntityId": {
|
|
875
|
+
"type": "Text",
|
|
876
|
+
"value": "Dev0001",
|
|
877
|
+
"metadata": {}
|
|
878
|
+
},
|
|
879
|
+
"targetEntityType": {
|
|
880
|
+
"type": "Text",
|
|
881
|
+
"value": "device",
|
|
882
|
+
"metadata": {}
|
|
883
|
+
},
|
|
884
|
+
"execTs": {
|
|
885
|
+
"type": "DateTime",
|
|
886
|
+
"value": "2020-05-27T00:00:00.000Z",
|
|
887
|
+
"metadata": {}
|
|
888
|
+
},
|
|
889
|
+
"cmd": {
|
|
890
|
+
"type": "Text",
|
|
891
|
+
"value": "switch",
|
|
892
|
+
"metadata": {}
|
|
893
|
+
},
|
|
894
|
+
"params": {
|
|
895
|
+
"type": "Text",
|
|
896
|
+
"value": "54, 12",
|
|
897
|
+
"metadata": {}
|
|
898
|
+
},
|
|
899
|
+
"status": {
|
|
900
|
+
"type": "Text",
|
|
901
|
+
"value": "FORWARDED",
|
|
902
|
+
"metadata": {}
|
|
903
|
+
},
|
|
904
|
+
"info": {
|
|
905
|
+
"type": "Text",
|
|
906
|
+
"value": null,
|
|
907
|
+
"metadata": {}
|
|
908
|
+
},
|
|
909
|
+
"onDelivered": {
|
|
910
|
+
"type": "Request",
|
|
911
|
+
"value": {
|
|
912
|
+
}
|
|
913
|
+
},
|
|
914
|
+
"onOk": {
|
|
915
|
+
"type": "Request",
|
|
916
|
+
"value": {
|
|
917
|
+
}
|
|
918
|
+
},
|
|
919
|
+
"onError": {
|
|
920
|
+
"type": "Request",
|
|
921
|
+
"value": {
|
|
922
|
+
}
|
|
923
|
+
},
|
|
924
|
+
"onInfo": {
|
|
925
|
+
"type": "Request",
|
|
926
|
+
"value": {
|
|
927
|
+
}
|
|
928
|
+
},
|
|
929
|
+
"cmdExecution": {
|
|
930
|
+
"type": "value",
|
|
931
|
+
"value": true,
|
|
932
|
+
"metadata": {}
|
|
933
|
+
},
|
|
934
|
+
"dateExpiration": {
|
|
935
|
+
"type": "DateTime",
|
|
936
|
+
"value": "2020-05-27T20:00:00.000Z",
|
|
937
|
+
"metadata": {}
|
|
938
|
+
}
|
|
939
|
+
}]
|
|
940
|
+
}
|
|
941
|
+
```
|
|
942
|
+
|
|
943
|
+
In this case relevant fields are just `targetEntityId`, `targetEntityType`, `cmd` and `params`.
|
|
944
|
+
|
|
945
|
+
The IoT Agent detects the selected attribute is a command, and replies to the Context Broker with a 204 OK (without
|
|
946
|
+
payload).
|
|
947
|
+
|
|
865
948
|
#### Result reporting
|
|
866
949
|
|
|
867
950
|
Once the IoT Agent has executed the command or retrieved the information from the device, it reports the results to the
|
|
@@ -955,6 +1038,30 @@ The Context Broker replies with all the desired data, in R2 format (200 OK):
|
|
|
955
1038
|
]
|
|
956
1039
|
```
|
|
957
1040
|
|
|
1041
|
+
#### Differences regarding the new commands mode
|
|
1042
|
+
|
|
1043
|
+
A new commands flow has been defined (involving also modifications at ContextBroker). As part of that design, commands
|
|
1044
|
+
are not sent to IOTAs using NGSIv2 notifications, but the current implementation has some differences regarding the
|
|
1045
|
+
desired behaviour, which are described next:
|
|
1046
|
+
|
|
1047
|
+
- Fields others than `targetEntityId`, `targetEntityType`, `cmd` and `params` (i.e. `execTs`, `status`, `info`,
|
|
1048
|
+
`onDelivered`, `onOk`, `onError`, `onInfo`, `cmdExecution` and `dataExpiration`), are not actually used. By the
|
|
1049
|
+
moment they are stored in the commands model (commands collection) but nothing is done with them appart from
|
|
1050
|
+
storing.
|
|
1051
|
+
- The "Result reporting" should not be a "hardwired" behaviour updating the entity associated to the device, but using
|
|
1052
|
+
the corresponding `on*` attribute in the notificaiton (e.g. `onOk` in the case of success). That attribute would
|
|
1053
|
+
typically be a [HATEOAS](https://en.wikipedia.org/wiki/HATEOAS) object like this
|
|
1054
|
+
`"onOk": { "href": "/v2/entities/123456abcdefg/attrs/status?type=switchExecution", "method": "PUT" }`. Moreover, the
|
|
1055
|
+
entity to be updated in that HATEOAS would be the transient entity corresponding to command execuion, not the entity
|
|
1056
|
+
associated to the device.
|
|
1057
|
+
|
|
1058
|
+
```
|
|
1059
|
+
PUT /v2/entities/123456abcdefg/attrs/status?type=switchExecution
|
|
1060
|
+
content-type: text/plain
|
|
1061
|
+
|
|
1062
|
+
OK
|
|
1063
|
+
```
|
|
1064
|
+
|
|
958
1065
|
### Scenario 3: commands (error)
|
|
959
1066
|
|
|
960
1067
|
In Scenario 3, errors can happen asynchronously, out of the main interactions. When the IoTAgent detects an error
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# MongoDB Datamodel
|
|
2
|
+
|
|
3
|
+
This file displays IoT Agent datamodel stored in the MongoDB database.
|
|
4
|
+
|
|
5
|
+
## Collections
|
|
6
|
+
|
|
7
|
+
### Devices
|
|
8
|
+
|
|
9
|
+
The collection 'devices' sotres information about the iotagent devices
|
|
10
|
+
|
|
11
|
+
Fields:
|
|
12
|
+
|
|
13
|
+
- **\_ID** _ObjectId_: unique object ID used by mongoDB
|
|
14
|
+
- **id** _string_: id of device
|
|
15
|
+
- **type** _string_: entity type used
|
|
16
|
+
- **name** _string_: entity name used
|
|
17
|
+
- **lazy** _array_: array of lazy attributes of device
|
|
18
|
+
- **active** _array_: array of active attributes of device
|
|
19
|
+
- **commands** _array_: array of commands of device
|
|
20
|
+
- **apikey** _string_: apikey of device
|
|
21
|
+
- **endpoint** _string_: endpoint used by push commands when http
|
|
22
|
+
- **resource** _string_: iotagent resource
|
|
23
|
+
- **protocol** _string_: device protocol (JSON)
|
|
24
|
+
- **transport** _string_: device transport (http, mqtt, amqp)
|
|
25
|
+
- **staticAttributes** _array_: array of static attributes of device
|
|
26
|
+
- **subscriptions** _array_: subscriptions of device
|
|
27
|
+
- **service** _string_: service which the device belongs to
|
|
28
|
+
- **subservice** _string_: subservice which the rule belongs to.
|
|
29
|
+
- **polling** _boolean_: if device uses polling for commands
|
|
30
|
+
- **timezone** _string_: timezone of device
|
|
31
|
+
- **timestamp** _boolean_ timestamp of device
|
|
32
|
+
- **registrationId** _string_: registrationId of device
|
|
33
|
+
- **internalId** _string_: internalId of device
|
|
34
|
+
- **creationDate** _date_: creationDate of device
|
|
35
|
+
- **internalAttributes** _object_: internalAttributes of device
|
|
36
|
+
- **autoprovision** _boolean_: if device support autoprovision
|
|
37
|
+
- **explicitAttrs** _enum_:
|
|
38
|
+
- **ngsiVersion** _string_: ngsi version used by device
|
|
39
|
+
- **payloadType** _string_: payloadType used by device
|
|
40
|
+
- **useCBflowControl** _boolean_: if CBFlow will be used by device when updates in CB
|
|
41
|
+
- **storeLastMeasure** _boolean_: if device store last measure received
|
|
42
|
+
- **lastMeasure** _object_: last measure received by device
|
|
43
|
+
- **oldCtxt** _object_: jexl Ctxt used at last measure
|
|
44
|
+
|
|
45
|
+
Example:
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"_id": {
|
|
50
|
+
"$oid": "680b4b338d0e60f98718a8b2"
|
|
51
|
+
},
|
|
52
|
+
"lazy": [],
|
|
53
|
+
"commands": [
|
|
54
|
+
{
|
|
55
|
+
"name": "reset",
|
|
56
|
+
"type": "command",
|
|
57
|
+
"value": "",
|
|
58
|
+
"expression": "{ set: brand + '_' + reset }",
|
|
59
|
+
"object_id": "reset"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"name": "cmd1",
|
|
63
|
+
"type": "command",
|
|
64
|
+
"value": "",
|
|
65
|
+
"expression": "brand",
|
|
66
|
+
"object_id": "cmd1"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"name": "cmd2",
|
|
70
|
+
"type": "command",
|
|
71
|
+
"value": "",
|
|
72
|
+
"expression": "reset",
|
|
73
|
+
"object_id": "cmd2"
|
|
74
|
+
}
|
|
75
|
+
],
|
|
76
|
+
"staticAttributes": [],
|
|
77
|
+
"creationDate": {
|
|
78
|
+
"$date": {
|
|
79
|
+
"$numberLong": "1745570611491"
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"id": "disp",
|
|
83
|
+
"type": "thing",
|
|
84
|
+
"name": "thing:disp",
|
|
85
|
+
"service": "smartcity",
|
|
86
|
+
"subservice": "/",
|
|
87
|
+
"registrationId": "680b4b33956cc1ed0205840a",
|
|
88
|
+
"apikey": "APIKEY",
|
|
89
|
+
"protocol": "IoTA-JSON",
|
|
90
|
+
"transport": "HTTP",
|
|
91
|
+
"polling": false,
|
|
92
|
+
"active": [],
|
|
93
|
+
"oldCtxt": {
|
|
94
|
+
"level": "33",
|
|
95
|
+
"brand": "o1",
|
|
96
|
+
"id": "disp",
|
|
97
|
+
"type": "thing",
|
|
98
|
+
"service": "smartcity",
|
|
99
|
+
"subservice": "/",
|
|
100
|
+
"entity_name": "thing:disp",
|
|
101
|
+
"TimeInstant": "2025-04-25T08:43:31.496Z"
|
|
102
|
+
},
|
|
103
|
+
"subscriptions": []
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Groups
|
|
108
|
+
|
|
109
|
+
The collection groups' stores information about the iotagent groups of devices
|
|
110
|
+
|
|
111
|
+
Fields:
|
|
112
|
+
|
|
113
|
+
- **\_ID** _ObjectId_: unique object ID used by mongoDB
|
|
114
|
+
- **url** _string_: url used by group of devices
|
|
115
|
+
- **resource** _string_: iotagent resource
|
|
116
|
+
- **apikey** _string_: apikey used by group of devices
|
|
117
|
+
- **endpoint** _string_: endpoint used by push commands when http
|
|
118
|
+
- **transport** _string_: group transport (http, mqtt, amqp)
|
|
119
|
+
- **type** _string_: entity type used
|
|
120
|
+
- **service** _string_: service which the group of device belongs to
|
|
121
|
+
- **subservice** _string_: subservice which the group of devices belongs to
|
|
122
|
+
- **description** _string_: description of group of devices
|
|
123
|
+
- **trust** _string_: keystone trust id used when devices of this group request to CB
|
|
124
|
+
- **cbHost** _string_: CB endpoint used by devices of this group
|
|
125
|
+
- **timezone** _string_: timezone used by group of devices
|
|
126
|
+
- **timestamp** _boolean_: timestamp of group
|
|
127
|
+
- **commands** _array_: array of commands of device group
|
|
128
|
+
- **staticAttributes** _array_: array of static attributes of device group
|
|
129
|
+
- **lazy** _array_: array of lazy attributes of device group
|
|
130
|
+
- **attributes** _array_: array of active attributes of device group
|
|
131
|
+
- **internalAttributes** _array_: array of internal attributes used by devices of group
|
|
132
|
+
- **autoprovision** _boolean_: if devices of group supports autoprovision
|
|
133
|
+
- **explicitAttrs** _enum_: explicit attributes configuration used by devices of group
|
|
134
|
+
- **defaultEntityNameConjunction** _string_:
|
|
135
|
+
- **ngsiVersion** _string_: ngsi version used by devices of group
|
|
136
|
+
- **entityNameExp** _string_: entity name expression used by devics of group
|
|
137
|
+
- **payloadType** _string_: payloadType used by devices of group
|
|
138
|
+
- **useCBflowControl** _boolean_: payloadType used by device group
|
|
139
|
+
- **storeLastMeasure** _boolean_: if devices of group store last measure received
|
|
140
|
+
|
|
141
|
+
Example:
|
|
142
|
+
|
|
143
|
+
```json
|
|
144
|
+
{
|
|
145
|
+
"_id": {
|
|
146
|
+
"$oid": "67a1e6447ae8b4ba4478f019"
|
|
147
|
+
},
|
|
148
|
+
"commands": [
|
|
149
|
+
{
|
|
150
|
+
"name": "reset",
|
|
151
|
+
"type": "command",
|
|
152
|
+
"value": "",
|
|
153
|
+
"expression": "{ set: brand + '_' + reset }"
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
"name": "cmd1",
|
|
157
|
+
"type": "command",
|
|
158
|
+
"value": "",
|
|
159
|
+
"expression": "brand"
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
"name": "cmd2",
|
|
163
|
+
"type": "command",
|
|
164
|
+
"value": "",
|
|
165
|
+
"expression": "reset"
|
|
166
|
+
}
|
|
167
|
+
],
|
|
168
|
+
"staticAttributes": [
|
|
169
|
+
{
|
|
170
|
+
"name": "brand",
|
|
171
|
+
"type": "Text",
|
|
172
|
+
"value": "o1",
|
|
173
|
+
"metadata": {}
|
|
174
|
+
}
|
|
175
|
+
],
|
|
176
|
+
"attributes": [],
|
|
177
|
+
"resource": "/iot/json",
|
|
178
|
+
"apikey": "APIKEY",
|
|
179
|
+
"type": "thing",
|
|
180
|
+
"service": "smartcity",
|
|
181
|
+
"subservice": "/",
|
|
182
|
+
"description": "miJSON",
|
|
183
|
+
"timestamp": true,
|
|
184
|
+
"internalAttributes": [],
|
|
185
|
+
"lazy": [],
|
|
186
|
+
"transport": "HTTP",
|
|
187
|
+
"endpoint": "'https://eoykcmmm.m.pipedream.net' + '/' + service + '/' + subservice + '/' + id + '/' + type"
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Commands
|
|
192
|
+
|
|
193
|
+
The collection 'commands' stores information about the commands
|
|
194
|
+
|
|
195
|
+
Fields:
|
|
196
|
+
|
|
197
|
+
- **\_ID** _ObjectId_: unique object ID used by mongoDB
|
|
198
|
+
- **deviceId** _string_: device ID of the device
|
|
199
|
+
- **type** _string_: type of the command
|
|
200
|
+
- **name** _string_: name of the command
|
|
201
|
+
- **value** _object_: value of the command
|
|
202
|
+
- **service** _string_: service which the device command belongs to
|
|
203
|
+
- **subservice** _string_: subservice which the device command belongs to
|
|
204
|
+
- **execTs** _date_: related with new commands functionality (stored but not yet in use)
|
|
205
|
+
- **status** _string_: related with new commands functionality (stored but not yet in use)
|
|
206
|
+
- **info** _string_: related with new commands functionality (stored but not yet in use)
|
|
207
|
+
- **onDelivered** _Object_: related with new commands functionality (stored but not yet in use)
|
|
208
|
+
- **onOk**: _Object_: related with new commands functionality (stored but not yet in use)
|
|
209
|
+
- **onError** _Object_: related with new commands functionality (stored but not yet in use)
|
|
210
|
+
- **onInfo** _Object_: related with new commands functionality (stored but not yet in use)
|
|
211
|
+
- **cmdExecution** \_Boolean\_\_: related with new commands functionality (stored but not yet in use)
|
|
212
|
+
- **dateExpiration**: { type: Date }: related with new commands functionality (stored but not yet in use)
|
|
213
|
+
- **creationDate** _date_: creation date of command
|
|
214
|
+
|
|
215
|
+
Example:
|
|
216
|
+
|
|
217
|
+
```json
|
|
218
|
+
{
|
|
219
|
+
"_id": {
|
|
220
|
+
"$oid": "680b4b538d0e60f98718a8eb"
|
|
221
|
+
},
|
|
222
|
+
"creationDate": {
|
|
223
|
+
"$date": {
|
|
224
|
+
"$numberLong": "1745570643252"
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
"name": "cmd1",
|
|
228
|
+
"type": "command",
|
|
229
|
+
"value": "on",
|
|
230
|
+
"deviceId": "disp3",
|
|
231
|
+
"service": "smartcity",
|
|
232
|
+
"subservice": "/"
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Indexes
|
|
237
|
+
|
|
238
|
+
### Devices
|
|
239
|
+
|
|
240
|
+
An index guarantees that every device is identified by the tuple (service, subservice, apikey, id)
|
|
241
|
+
|
|
242
|
+
The index is created/ensured when iotagent starts, but it can be created from a mongoDB shell with
|
|
243
|
+
|
|
244
|
+
```javascript
|
|
245
|
+
db.devices.ensureIndex({ service: 1, subservice: 1, apikey: 1, id: 1 }, { unique: true });
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Groups
|
|
249
|
+
|
|
250
|
+
An index guarantees that every group is identified by the tuple (apikey, resource)
|
|
251
|
+
|
|
252
|
+
The index is created/ensured when iotagent starts, but it can be created from a mongoDB shell with
|
|
253
|
+
|
|
254
|
+
```javascript
|
|
255
|
+
db.groups.ensureIndex({ apikey: 1, resource: 1 }, { unique: true });
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Commands
|
|
259
|
+
|
|
260
|
+
None index is defined
|
package/doc/requirements.txt
CHANGED
|
@@ -47,17 +47,20 @@ const context = {
|
|
|
47
47
|
|
|
48
48
|
/* eslint-disable-next-line no-unused-vars */
|
|
49
49
|
function activateStatLogs(newConfig, callback) {
|
|
50
|
-
async.series(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
50
|
+
async.series(
|
|
51
|
+
[
|
|
52
|
+
apply(statsRegistry.globalLoad, {
|
|
53
|
+
deviceCreationRequests: 0,
|
|
54
|
+
deviceRemovalRequests: 0,
|
|
55
|
+
measureRequests: 0,
|
|
56
|
+
raiseAlarm: 0,
|
|
57
|
+
releaseAlarm: 0,
|
|
58
|
+
updateEntityRequestsOk: 0,
|
|
59
|
+
updateEntityRequestsError: 0
|
|
60
|
+
})
|
|
61
|
+
],
|
|
62
|
+
callback
|
|
63
|
+
);
|
|
61
64
|
}
|
|
62
65
|
|
|
63
66
|
/**
|
|
@@ -314,6 +317,7 @@ exports.setDataUpdateHandler = contextServer.setUpdateHandler;
|
|
|
314
317
|
exports.setCommandHandler = contextServer.setCommandHandler;
|
|
315
318
|
exports.setMergePatchHandler = contextServer.setMergePatchHandler;
|
|
316
319
|
exports.setDataQueryHandler = contextServer.setQueryHandler;
|
|
320
|
+
exports.executeUpdateSideEffects = contextServer.executeUpdateSideEffects;
|
|
317
321
|
exports.setConfigurationHandler = contextServer.setConfigurationHandler;
|
|
318
322
|
exports.setRemoveConfigurationHandler = contextServer.setRemoveConfigurationHandler;
|
|
319
323
|
exports.setProvisioningHandler = contextServer.setProvisioningHandler;
|