iotagent-node-lib 4.6.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.
Files changed (70) hide show
  1. package/.github/workflows/ci.yml +2 -3
  2. package/CHANGES_NEXT_RELEASE +1 -0
  3. package/Changelog +20 -0
  4. package/config.js +3 -1
  5. package/doc/README.md +1 -0
  6. package/doc/admin.md +39 -5
  7. package/doc/api.md +74 -9
  8. package/doc/devel/northboundinteractions.md +122 -15
  9. package/doc/models/models.md +260 -0
  10. package/doc/requirements.txt +1 -1
  11. package/docker/Mosquitto/Dockerfile +1 -1
  12. package/lib/commonConfig.js +21 -2
  13. package/lib/fiware-iotagent-lib.js +15 -11
  14. package/lib/jexlTranformsMap.js +182 -35
  15. package/lib/model/Command.js +11 -2
  16. package/lib/model/Device.js +8 -3
  17. package/lib/model/Group.js +5 -3
  18. package/lib/model/dbConn.js +53 -112
  19. package/lib/services/commands/commandRegistryMongoDB.js +130 -76
  20. package/lib/services/commands/commandService.js +3 -3
  21. package/lib/services/common/iotManagerService.js +3 -1
  22. package/lib/services/devices/deviceRegistryMemory.js +36 -0
  23. package/lib/services/devices/deviceRegistryMongoDB.js +161 -88
  24. package/lib/services/devices/deviceService.js +44 -5
  25. package/lib/services/devices/devices-NGSI-v2.js +6 -1
  26. package/lib/services/groups/groupRegistryMongoDB.js +120 -83
  27. package/lib/services/ngsi/entities-NGSI-v2.js +14 -1
  28. package/lib/services/ngsi/ngsiService.js +32 -1
  29. package/lib/services/northBound/contextServer-NGSI-v2.js +12 -3
  30. package/lib/services/northBound/contextServer.js +2 -1
  31. package/lib/services/northBound/deviceProvisioningServer.js +12 -3
  32. package/lib/services/northBound/northboundServer.js +1 -0
  33. package/lib/templates/createDevice.json +4 -0
  34. package/lib/templates/updateDevice.json +16 -0
  35. package/lib/templates/updateDeviceLax.json +12 -0
  36. package/package.json +4 -4
  37. package/test/functional/config-test.js +3 -2
  38. package/test/functional/testUtils.js +15 -4
  39. package/test/unit/examples/groupProvisioningRequests/provisionFullGroup.json +1 -0
  40. package/test/unit/expressions/jexlExpression-test.js +165 -1
  41. package/test/unit/general/config-multi-core-test.js +1 -2
  42. package/test/unit/general/contextBrokerKeystoneSecurityAccess-test.js +5 -4
  43. package/test/unit/general/deviceService-test.js +6 -5
  44. package/test/unit/memoryRegistry/deviceRegistryMemory_test.js +6 -5
  45. package/test/unit/mongodb/mongodb-connectionoptions-test.js +7 -39
  46. package/test/unit/ngsi-ld/expressions/jexlBasedTransformations-test.js +1 -2
  47. package/test/unit/ngsi-ld/general/config-jsonld-contexts-test.js +1 -2
  48. package/test/unit/ngsi-mixed/provisioning/ngsi-versioning-test.js +8 -5
  49. package/test/unit/ngsiv2/expressions/jexlBasedTransformations-test.js +42 -41
  50. package/test/unit/ngsiv2/general/contextBrokerOAuthSecurityAccess-test.js +11 -10
  51. package/test/unit/ngsiv2/general/deviceService-test.js +6 -5
  52. package/test/unit/ngsiv2/general/https-support-test.js +1 -1
  53. package/test/unit/ngsiv2/lazyAndCommands/active-devices-attribute-update-test.js +4 -3
  54. package/test/unit/ngsiv2/lazyAndCommands/command-test.js +6 -5
  55. package/test/unit/ngsiv2/lazyAndCommands/lazy-devices-test.js +17 -16
  56. package/test/unit/ngsiv2/lazyAndCommands/polling-commands-test.js +10 -18
  57. package/test/unit/ngsiv2/ngsiService/active-devices-test.js +21 -20
  58. package/test/unit/ngsiv2/ngsiService/staticAttributes-test.js +8 -7
  59. package/test/unit/ngsiv2/plugins/alias-plugin_test.js +12 -11
  60. package/test/unit/ngsiv2/plugins/custom-plugin_test.js +3 -2
  61. package/test/unit/ngsiv2/plugins/multientity-plugin_test.js +28 -27
  62. package/test/unit/ngsiv2/provisioning/device-group-api-test.js +6 -4
  63. package/test/unit/ngsiv2/provisioning/device-provisioning-api_test.js +12 -11
  64. package/test/unit/ngsiv2/provisioning/device-provisioning-configGroup-api_test.js +11 -10
  65. package/test/unit/ngsiv2/provisioning/device-registration_test.js +5 -4
  66. package/test/unit/ngsiv2/provisioning/listProvisionedDevices-test.js +6 -5
  67. package/test/unit/ngsiv2/provisioning/provisionDeviceMultientity-test.js +1 -1
  68. package/test/unit/ngsiv2/provisioning/removeProvisionedDevice-test.js +5 -4
  69. package/test/unit/ngsiv2/provisioning/updateProvisionedDevices-test.js +8 -7
  70. package/test/unit/ngsiv2/ngsiService/subscriptions-test.js +0 -326
@@ -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
@@ -1,4 +1,4 @@
1
1
  mkdocs==1.2.4
2
2
  Pygments==2.15.0
3
3
  Markdown==3.3.4
4
- jinja2==3.1.4
4
+ jinja2==3.1.6
@@ -1,4 +1,4 @@
1
- ARG IMAGE_TAG=12.6-slim
1
+ ARG IMAGE_TAG=12.10-slim
2
2
  FROM debian:${IMAGE_TAG}
3
3
 
4
4
  ARG CLEAN_DEV_TOOLS
@@ -157,7 +157,9 @@ function processEnvironmentVariables() {
157
157
  'IOTA_FALLBACK_PATH',
158
158
  'IOTA_LD_SUPPORT_NULL',
159
159
  'IOTA_LD_SUPPORT_DATASET_ID',
160
- 'IOTA_EXPRESS_LIMIT'
160
+ 'IOTA_EXPRESS_LIMIT',
161
+ 'IOTA_USE_CB_FLOW_CONTROL',
162
+ 'IOTA_STORE_LAST_MEASURE'
161
163
  ];
162
164
  const iotamVariables = [
163
165
  'IOTA_IOTAM_URL',
@@ -350,6 +352,11 @@ function processEnvironmentVariables() {
350
352
  config.defaultResource = process.env.IOTA_DEFAULT_RESOURCE;
351
353
  }
352
354
 
355
+ // Default transport
356
+ if (process.env.IOTA_DEFAULT_TRANSPORT !== undefined) {
357
+ config.defaultTransport = process.env.IOTA_DEFAULT_TRANSPORT;
358
+ }
359
+
353
360
  // Default explicitAttrs
354
361
  if (process.env.IOTA_EXPLICIT_ATTRS !== undefined) {
355
362
  config.explicitAttrs = process.env.IOTA_EXPLICIT_ATTRS;
@@ -474,6 +481,16 @@ function processEnvironmentVariables() {
474
481
  } else {
475
482
  config.expressLimit = config.expressLimit ? config.expressLimit : '1mb';
476
483
  }
484
+ if (process.env.IOTA_USE_CB_FLOW_CONTROL) {
485
+ config.useCBflowControl = process.env.IOTA_USE_CB_FLOW_CONTROL === 'true';
486
+ } else {
487
+ config.useCBflowControl = config.useCBflowControl === true;
488
+ }
489
+ if (process.env.IOTA_STORE_LAST_MEASURE) {
490
+ config.storeLastMeasure = process.env.IOTA_STORE_LAST_MEASURE === 'true';
491
+ } else {
492
+ config.storeLastMeasure = config.storeLastMeasure === true;
493
+ }
477
494
  }
478
495
 
479
496
  function setConfig(newConfig) {
@@ -503,7 +520,9 @@ function getConfigForTypeInformation() {
503
520
  multiCore: config.multiCore,
504
521
  relaxTemplateValidation: config.relaxTemplateValidation,
505
522
  defaultEntityNameConjunction: config.defaultEntityNameConjunction,
506
- defaultType: config.defaultType
523
+ defaultType: config.defaultType,
524
+ useCBflowControl: config.useCBflowControl,
525
+ storeLastMeasure: config.storeLastMeasure
507
526
  };
508
527
  return conf;
509
528
  }
@@ -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
- apply(statsRegistry.globalLoad, {
52
- deviceCreationRequests: 0,
53
- deviceRemovalRequests: 0,
54
- measureRequests: 0,
55
- raiseAlarm: 0,
56
- releaseAlarm: 0,
57
- updateEntityRequestsOk: 0,
58
- updateEntityRequestsError: 0
59
- })
60
- ], callback);
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;
@@ -26,63 +26,210 @@
26
26
  JEXL avaliable transformations*/
27
27
 
28
28
  const map = {
29
- jsonparse: (str) => JSON.parse(str),
30
- jsonstringify: (obj) => JSON.stringify(obj),
29
+ jsonparse: (val) => {
30
+ const safeOperation =
31
+ (fn) =>
32
+ (...args) => {
33
+ try {
34
+ return fn(...args);
35
+ } catch (e) {
36
+ return null;
37
+ }
38
+ };
39
+ return safeOperation(JSON.parse)(val);
40
+ },
41
+ jsonstringify: (val) => {
42
+ const safeOperation =
43
+ (fn) =>
44
+ (...args) => {
45
+ try {
46
+ return fn(...args);
47
+ } catch (e) {
48
+ return null;
49
+ }
50
+ };
51
+ return safeOperation(JSON.stringify)(val);
52
+ },
31
53
  indexOf: (val, char) => String(val).indexOf(char),
32
54
  length: (val) => String(val).length,
33
55
  trim: (val) => String(val).trim(),
34
56
  substr: (val, int1, int2) => String(val).substr(int1, int2),
35
- addreduce: (arr) => arr.reduce((i, v) => i + v),
57
+ addreduce: (arr) => {
58
+ const safeOperation =
59
+ (fn) =>
60
+ (...args) => {
61
+ try {
62
+ return fn(...args);
63
+ } catch (e) {
64
+ return null;
65
+ }
66
+ };
67
+ return safeOperation((arr) => arr.reduce((i, v) => i + v))(arr);
68
+ },
36
69
  lengtharray: (arr) => arr.length,
37
70
  typeof: (val) => typeof val,
38
- isarray: (arr) => Array.isArray(arr),
39
- isnan: (val) => isNaN(val),
40
- parseint: (val) => parseInt(val),
41
- parsefloat: (val) => parseFloat(val),
42
- toisodate: (val) => new Date(val).toISOString(),
43
- timeoffset: (isostr) => new Date(isostr).getTimezoneOffset(),
44
- tostring: (val) => val.toString(),
45
- urlencode: (val) => encodeURI(val),
46
- urldecode: (val) => decodeURI(val),
71
+ isarray: Array.isArray,
72
+ isnan: isNaN,
73
+ parseint: (val) => {
74
+ const safeParseNumber = (fn) => (val) => {
75
+ const result = fn(val);
76
+ return isNaN(result) ? null : result;
77
+ };
78
+ return safeParseNumber((val) => parseInt(val, 10))(val);
79
+ },
80
+ parsefloat: (val) => {
81
+ const safeParseNumber = (fn) => (val) => {
82
+ const result = fn(val);
83
+ return isNaN(result) ? null : result;
84
+ };
85
+ return safeParseNumber(parseFloat)(val);
86
+ },
87
+ toisodate: (val) => {
88
+ const safeDateOperation = (fn) => (val) => {
89
+ const date = new Date(val);
90
+ return isNaN(date.getTime()) ? null : fn(date);
91
+ };
92
+ return safeDateOperation((date) => date.toISOString())(val);
93
+ },
94
+ timeoffset: (val) => {
95
+ const safeDateOperation = (fn) => (val) => {
96
+ const date = new Date(val);
97
+ return isNaN(date.getTime()) ? null : fn(date);
98
+ };
99
+ return safeDateOperation((date) => date.getTimezoneOffset())(val);
100
+ },
101
+ tostring: (val) => {
102
+ const safeOperation =
103
+ (fn) =>
104
+ (...args) => {
105
+ try {
106
+ return fn(...args);
107
+ } catch (e) {
108
+ return null;
109
+ }
110
+ };
111
+ return safeOperation((val) => val.toString())(val);
112
+ },
113
+ urlencode: encodeURI,
114
+ urldecode: (val) => {
115
+ const safeOperation =
116
+ (fn) =>
117
+ (...args) => {
118
+ try {
119
+ return fn(...args);
120
+ } catch (e) {
121
+ return null;
122
+ }
123
+ };
124
+ return safeOperation(decodeURI)(val);
125
+ },
47
126
  replacestr: (str, from, to) => str.replace(from, to),
48
- replaceregexp: (str, reg, to) => str.replace(new RegExp(reg), to),
49
- replaceallstr: (str, from, to) => str.replaceAll(from, to),
50
- replaceallregexp: (str, reg, to) => str.replaceAll(new RegExp(reg, 'g'), to),
127
+ replaceregexp: (str, reg, to) => {
128
+ const safeOperation =
129
+ (fn) =>
130
+ (...args) => {
131
+ try {
132
+ return fn(...args);
133
+ } catch (e) {
134
+ return null;
135
+ }
136
+ };
137
+ return safeOperation((str, reg, to) => str.replace(new RegExp(reg), to))(str, reg, to);
138
+ },
139
+ replaceallregexp: (str, reg, to) => {
140
+ const safeOperation =
141
+ (fn) =>
142
+ (...args) => {
143
+ try {
144
+ return fn(...args);
145
+ } catch (e) {
146
+ return null;
147
+ }
148
+ };
149
+ return safeOperation((str, reg, to) => str.replace(new RegExp(reg, 'g'), to))(str, reg, to);
150
+ },
51
151
  split: (str, ch) => str.split(ch),
52
152
  joinarrtostr: (arr, ch) => arr.join(ch),
53
153
  concatarr: (arr, arr2) => arr.concat(arr2),
54
154
  mapper: (val, values, choices) => choices[values.findIndex((target) => target === val)],
55
155
  thmapper: (val, values, choices) =>
56
- choices[
57
- values.reduce((acc, curr, i) => (acc === 0 || acc ? acc : val <= curr ? (acc = i) : (acc = null)), null)
58
- ],
156
+ choices[values.reduce((acc, curr, i) => (acc !== null ? acc : val <= curr ? i : null), null)],
59
157
  bitwisemask: (i, mask, op, shf) =>
60
158
  (op === '&' ? parseInt(i) & mask : op === '|' ? parseInt(i) | mask : op === '^' ? parseInt(i) ^ mask : i) >>
61
159
  shf,
62
160
  slice: (arr, init, end) => arr.slice(init, end),
63
- addset: (arr, x) => {
64
- return Array.from(new Set(arr).add(x));
65
- },
161
+ addset: (arr, x) => Array.from(new Set(arr).add(x)),
66
162
  removeset: (arr, x) => {
67
- let s = new Set(arr);
163
+ const s = new Set(arr);
68
164
  s.delete(x);
69
165
  return Array.from(s);
70
166
  },
71
167
  touppercase: (val) => String(val).toUpperCase(),
72
168
  tolowercase: (val) => String(val).toLowerCase(),
73
- floor: (val) => Math.floor(val),
74
- ceil: (val) => Math.ceil(val),
75
- round: (val) => Math.round(val),
76
- tofixed: (val, decimals) => Number.parseFloat(val).toFixed(decimals),
77
- gettime: (d) => new Date(d).getTime(),
78
- toisostring: (d) => new Date(d).toISOString(),
79
- // https://developer.mozilla.org/es/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleString
80
- localestring: (d, timezone, options) => new Date(d).toLocaleString(timezone, options),
81
- now: () => Date.now(),
82
- hextostring: (val) =>
83
- new TextDecoder().decode(new Uint8Array(val.match(/.{1,2}/g).map((byte) => parseInt(byte, 16)))),
84
- valuePicker: (val,pick) => Object.entries(val).filter(([, v]) => v === pick).map(([k,]) => k),
85
- valuePickerMulti: (val,pick) => Object.entries(val).filter(([, v]) => pick.includes(v)).map(([k,]) => k)
169
+ floor: Math.floor,
170
+ ceil: Math.ceil,
171
+ round: Math.round,
172
+ tofixed: (val, decimals) => {
173
+ const num = Number.parseFloat(val);
174
+ const dec = Number.parseInt(decimals);
175
+ return isNaN(num) || isNaN(dec) ? null : num.toFixed(dec);
176
+ },
177
+ gettime: (val) => {
178
+ const safeDateOperation = (fn) => (val) => {
179
+ const date = new Date(val);
180
+ return isNaN(date.getTime()) ? null : fn(date);
181
+ };
182
+ return safeDateOperation((date) => date.getTime())(val);
183
+ },
184
+ toisostring: (val) => {
185
+ const safeDateOperation = (fn) => (val) => {
186
+ const date = new Date(val);
187
+ return isNaN(date.getTime()) ? null : fn(date);
188
+ };
189
+ return safeDateOperation((date) => date.toISOString())(val);
190
+ },
191
+ localestring: (d, timezone, options) => {
192
+ const safeOperation =
193
+ (fn) =>
194
+ (...args) => {
195
+ try {
196
+ return fn(...args);
197
+ } catch (e) {
198
+ return null;
199
+ }
200
+ };
201
+ return safeOperation((d, timezone, options) => new Date(d).toLocaleString(timezone, options))(
202
+ d,
203
+ timezone,
204
+ options
205
+ );
206
+ },
207
+ now: Date.now,
208
+ hextostring: (val) => {
209
+ const safeOperation =
210
+ (fn) =>
211
+ (...args) => {
212
+ try {
213
+ return fn(...args);
214
+ } catch (e) {
215
+ return null;
216
+ }
217
+ };
218
+ return safeOperation((val) => {
219
+ if (typeof val !== 'string' || !/^[0-9a-fA-F]+$/.test(val) || val.length % 2 !== 0) {
220
+ return null;
221
+ }
222
+ return new TextDecoder().decode(new Uint8Array(val.match(/.{1,2}/g).map((byte) => parseInt(byte, 16))));
223
+ })(val);
224
+ },
225
+ valuePicker: (val, pick) =>
226
+ Object.entries(val)
227
+ .filter(([, v]) => v === pick)
228
+ .map(([k]) => k),
229
+ valuePickerMulti: (val, pick) =>
230
+ Object.entries(val)
231
+ .filter(([, v]) => pick.includes(v))
232
+ .map(([k]) => k)
86
233
  };
87
234
 
88
235
  exports.map = map;
@@ -31,11 +31,20 @@ const Command = new Schema({
31
31
  value: Object,
32
32
  service: { type: String, lowercase: true },
33
33
  subservice: String,
34
+ execTs: { type: Date },
35
+ status: String,
36
+ info: String,
37
+ onDelivered: Object,
38
+ onOk: Object,
39
+ onError: Object,
40
+ onInfo: Object,
41
+ cmdExecution: Boolean,
42
+ dateExpiration: { type: Date },
34
43
  creationDate: { type: Date, default: Date.now }
35
44
  });
36
45
 
37
- function load(db) {
38
- module.exports.model = db.model('Command', Command);
46
+ function load() {
47
+ module.exports.model = mongoose.model('Command', Command);
39
48
  module.exports.internalSchema = Command;
40
49
  }
41
50
 
@@ -53,11 +53,16 @@ const Device = new Schema({
53
53
  autoprovision: Boolean,
54
54
  explicitAttrs: Group.ExplicitAttrsType,
55
55
  ngsiVersion: String,
56
- payloadType: String
56
+ payloadType: String,
57
+ useCBflowControl: Boolean,
58
+ storeLastMeasure: Boolean,
59
+ lastMeasure: Object,
60
+ oldCtxt: Object
57
61
  });
58
62
 
59
- function load(db) {
60
- module.exports.model = db.model('Device', Device);
63
+ function load() {
64
+ Device.index({ service: 1, subservice: 1, apikey: 1, id: 1 }, { unique: true });
65
+ module.exports.model = mongoose.model('Device', Device);
61
66
  module.exports.internalSchema = Device;
62
67
  }
63
68
 
@@ -65,12 +65,14 @@ const Group = new Schema({
65
65
  defaultEntityNameConjunction: String,
66
66
  ngsiVersion: String,
67
67
  entityNameExp: String,
68
- payloadType: String
68
+ payloadType: String,
69
+ useCBflowControl: Boolean,
70
+ storeLastMeasure: Boolean
69
71
  });
70
72
 
71
- function load(db) {
73
+ function load() {
72
74
  Group.index({ apikey: 1, resource: 1 }, { unique: true });
73
- module.exports.model = db.model('Group', Group);
75
+ module.exports.model = mongoose.model('Group', Group);
74
76
  module.exports.internalSchema = Group;
75
77
  }
76
78