@ncd-io/node-red-enterprise-sensors 1.4.7 → 1.6.1

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/wireless.js CHANGED
@@ -5,7 +5,10 @@ const Queue = require("promise-queue");
5
5
  const events = require("events");
6
6
  const fs = require('fs');
7
7
  const path = require('path');
8
- const home_dir = require('os').homedir
8
+ const home_dir = require('os').homedir;
9
+ const { isDeepStrictEqual } = require('util');
10
+
11
+
9
12
  module.exports = function(RED) {
10
13
  var gateway_pool = {};
11
14
  function NcdGatewayConfig(config){
@@ -21,9 +24,36 @@ module.exports = function(RED) {
21
24
  this._emitter = new events.EventEmitter();
22
25
  this.on = (e,c) => this._emitter.on(e, c);
23
26
 
27
+ const config_file_location = home_dir()+'/.node-red/node_modules/@ncd-io/node-red-enterprise-sensors/data/sensor_configs.json';
28
+ const configuration_map_location = home_dir()+'/.node-red/node_modules/@ncd-io/node-red-enterprise-sensors/data/configuration_map.json';
29
+ const sensor_type_map_location = home_dir()+'/.node-red/node_modules/@ncd-io/node-red-enterprise-sensors/data/sensor_type_map.json';
30
+
31
+ this.config_node_capable = [114];
32
+
24
33
  // comms_timer object var added to clear the time to prevent issues with rapid redeploy
25
34
  this.comms_timer;
26
35
 
36
+ // this.sensor_configs = {};
37
+ try {
38
+ this.sensor_configs = JSON.parse(fs.readFileSync(config_file_location,));
39
+ } catch(err){
40
+ this.sensor_configs = {};
41
+ };
42
+
43
+ try {
44
+ this.configuration_map = JSON.parse(fs.readFileSync(configuration_map_location,));
45
+ } catch(err){
46
+ this.configuration_map = {};
47
+ console.log('Error reading configuration list');
48
+ };
49
+
50
+ try {
51
+ this.sensor_type_map = JSON.parse(fs.readFileSync(sensor_type_map_location,));
52
+ } catch(err){
53
+ this.sensor_type_map = {};
54
+ console.log('Error reading sensor type map file');
55
+ };
56
+
27
57
  if(config.comm_type == 'serial'){
28
58
  this.key = config.port;
29
59
  }
@@ -31,6 +61,16 @@ module.exports = function(RED) {
31
61
  this.key = config.ip_address+":"+config.tcp_port;
32
62
  }
33
63
 
64
+ this.store_sensor_configs = function(sensor_configs){
65
+ fs.writeFile(config_file_location, sensor_configs, function(err){
66
+ if(err){
67
+ return err;
68
+ }else{
69
+ return true;
70
+ }
71
+ });
72
+ };
73
+
34
74
  var node = this;
35
75
 
36
76
  node.is_config = 3;
@@ -96,11 +136,113 @@ module.exports = function(RED) {
96
136
  }
97
137
  // Event listener to make sure this only triggers once no matter how many gateway nodes there are
98
138
  node.gateway.on('sensor_mode', (d) => {
99
- if(d.mode == "FLY"){
100
- if(Object.hasOwn(node.sensor_list, d.mac) && Object.hasOwn(node.sensor_list[d.mac], 'update_request')){
139
+ if(d.mode == "FLY" || d.mode == "OTF"){
140
+ if(Object.hasOwn(node.sensor_list, d.mac) && Object.hasOwn(node.sensor_list[d.mac], 'update_request') && d.mode == "FLY"){
101
141
  node.request_manifest(d.mac);
102
- };
103
- };
142
+ }else if(node.config_node_capable.includes(d.type)){
143
+ // API CONFIGURATION NODE
144
+ let values = d.reported_config.machine_values;
145
+ if(Object.hasOwn(values, 'uptime_counter')) delete values.uptime_counter;
146
+ if(Object.hasOwn(values, 'tx_lifetime_counter')) delete values.tx_lifetime_counter;
147
+ if(Object.hasOwn(values, 'reserved')) delete values.reserved;
148
+
149
+ let store_flag = false;
150
+
151
+ if(!Object.hasOwn(node.sensor_configs, d.mac)){
152
+ node.sensor_configs[d.mac] = {};
153
+ store_flag = true;
154
+ }
155
+ // Store hardware_id outside of reported_configs
156
+ if(!Object.hasOwn(node.sensor_configs[d.mac], 'hardware_id') && Object.hasOwn(values, 'hardware_id')){
157
+ node.sensor_configs[d.mac].hardware_id = values.hardware_id;
158
+ store_flag = true;
159
+ }
160
+ if(Object.hasOwn(values, 'hardware_id')) delete values.hardware_id;
161
+ // Loop through and translate and validate values.
162
+ // We want to allow passing in integers instead of byte arrays etc.
163
+ // This the FLY so we don't need to validate.
164
+ values.network_id = config.pan_id;
165
+ if(Object.hasOwn(d, 'nodeId')){
166
+ values.node_id = d.nodeId;
167
+ }
168
+ for(let key in values){
169
+ if(Object.hasOwn(node.sensor_type_map, d.type) && Object.hasOwn(node.sensor_type_map[d.type], 'configs') && Object.hasOwn(node.sensor_type_map[d.type].configs, key)){
170
+ let map_key = node.sensor_type_map[d.type].configs[key];
171
+ if(Object.hasOwn(node.configuration_map, map_key) && Object.hasOwn(node.configuration_map[map_key], 'validator')){
172
+ if(Object.hasOwn(node.configuration_map[map_key].validator, 'type')){
173
+ switch(node.configuration_map[map_key].validator.type){
174
+ // case 'uint8':
175
+ // console.log(key);
176
+ // console.log(values[key]);
177
+ // values[key] = values[key].reduce(msbLsb);
178
+ // break;
179
+ case 'uint16':
180
+ values[key] = values[key].reduce(msbLsb);
181
+ break;
182
+ case 'uint24':
183
+ values[key] = values[key].reduce(msbLsb);
184
+ break;
185
+ case 'uint32':
186
+ values[key] = values[key].reduce(msbLsb);
187
+ break;
188
+ case 'hexString':
189
+ values[key] = values[key].toLowerCase();
190
+ break;
191
+ }
192
+ }
193
+ }
194
+ }
195
+ }
196
+ // If object has no reported configs, instantiate them
197
+ if(!Object.hasOwn(node.sensor_configs[d.mac], 'reported_configs')){
198
+ node.sensor_configs[d.mac].reported_configs = {};
199
+ store_flag = true;
200
+ }
201
+ // If object has no desired configs, add them
202
+ if(!Object.hasOwn(node.sensor_configs[d.mac], 'desired_configs')){
203
+ node.sensor_configs[d.mac].desired_configs = values;
204
+ store_flag = true;
205
+ }
206
+ if(!Object.hasOwn(node.sensor_configs[d.mac], 'type')){
207
+ node.sensor_configs[d.mac].type = d.type;
208
+ store_flag = true;
209
+ }else if(node.sensor_configs[d.mac].type != d.type){
210
+ // This code is only called when the type doesn't match what the sensor reports, only foreseeable if user swaps sensor modules OR preloads configs for wrong sensor type
211
+ delete node.sensor_configs[d.mac].desired_configs;
212
+ node.sensor_configs[d.mac].type = d.type;
213
+ node._emitter.emit('config_node_error', {topic: 'sensor_type_error', payload: {'error': "Sensor type used for desired configs does not match sensor type reported by sensor. Deleting desired configs"}, addr: d.mac, time: Date.now()});
214
+ store_flag = true;
215
+ }
216
+ if(!isDeepStrictEqual(node.sensor_configs[d.mac].reported_configs, values)){
217
+ // Values are different, update the stored configs and write to file
218
+ node.sensor_configs[d.mac].reported_configs = values;
219
+ // If reported configs change, but the auto config does not control configs, update automatically
220
+ // Will duplicate setting desired configs on first run, but only once or if user clear desired_configs
221
+ if(!Object.hasOwn(node.sensor_configs[d.mac], 'api_config_override')){
222
+ node.sensor_configs[d.mac].desired_configs = values;
223
+ }
224
+ store_flag = true;
225
+ node._emitter.emit('config_node_msg', {topic: 'sensor_configs_update', payload: node.sensor_configs[d.mac], addr: d.mac, time: Date.now()});
226
+ // node.send({topic: 'sensor_configs_update', payload: node.sensor_configs[d.mac], time: Date.now()});
227
+ }
228
+
229
+ if(Object.hasOwn(node.sensor_configs[d.mac], 'desired_configs') && d.mode != "OTF"){
230
+ let required_configs = this.get_required_configs(node.sensor_configs[d.mac].desired_configs, node.sensor_configs[d.mac].reported_configs);
231
+ if(Object.keys(required_configs).length !== 0){
232
+ node.sensor_configs[d.mac].temp_required_configs = required_configs;
233
+ store_flag = true;
234
+ var tout = setTimeout(() => {
235
+ this._send_otn_request(d);
236
+ }, 100);
237
+ }
238
+ }
239
+ if(store_flag){
240
+ node.store_sensor_configs(JSON.stringify(node.sensor_configs));
241
+ }
242
+ }
243
+ }else if(d.mode == "OTN" && Object.hasOwn(node.sensor_configs, d.mac) && Object.hasOwn(node.sensor_configs[d.mac], 'temp_required_configs')){
244
+ node.configure(d);
245
+ }
104
246
  });
105
247
  node.gateway.on('manifest_received', (manifest_data) => {
106
248
  // read manifest length is 37. Could use the same event for both
@@ -147,7 +289,6 @@ module.exports = function(RED) {
147
289
  }else{
148
290
  manifest_data.enter_ota_success = false;
149
291
  }
150
- console.log(name);
151
292
  } else{
152
293
  if(manifest_data.enter_ota_success){
153
294
  // enter ota mode
@@ -185,6 +326,187 @@ module.exports = function(RED) {
185
326
  });
186
327
  }
187
328
  };
329
+
330
+ this.get_required_configs = function(desired_configs, reported_configs){
331
+ const mismatched = {};
332
+ for (const key in desired_configs) {
333
+ if (reported_configs.hasOwnProperty(key)) {
334
+ if (!isDeepStrictEqual(desired_configs[key], reported_configs[key])) {
335
+ // if (desired_configs[key] !== reported_configs[key]) {
336
+ mismatched[key] = desired_configs[key];
337
+ };
338
+ };
339
+ };
340
+ return mismatched;
341
+ };
342
+
343
+ // TODO consider adding logic for if only 1 config to go out, skip OTN request
344
+ this.configure = function(sensor){
345
+ return new Promise((top_fulfill, top_reject) => {
346
+ var success = {};
347
+ setTimeout(() => {
348
+ var tout = setTimeout(() => {
349
+ // TODO handle timeout and send emitters
350
+ node._emitter.emit('config_node_msg', {topic: 'Config Results', payload: success, addr: sensor.mac, time: Date.now()});
351
+ // node.status(modes.PGM_ERR);
352
+ // node.send({topic: 'Config Results', payload: success, time: Date.now(), addr: sensor.mac});
353
+ }, 60000);
354
+ // node.status(modes.PGM_NOW);
355
+ var mac = sensor.mac;
356
+ var promises = {};
357
+ var reboot = false;
358
+
359
+ let configs = node.sensor_configs[sensor.mac].temp_required_configs;
360
+ for (const key in configs) {
361
+ let config_obj = node.configuration_map[node.sensor_type_map[sensor.type].configs[key]];
362
+ if(Object.hasOwn(config_obj, 'validator') && Object.hasOwn(config_obj.validator, 'type')){
363
+ if(key == 'node_id' || key == 'delay'){
364
+ // TODO expand this logic in the future for cases where sensor doesn't report delay.
365
+ if(Object.hasOwn(configs, 'node_id') && Object.hasOwn(configs, 'delay') && Object.hasOwn(config_obj, "delay") && Object.hasOwn(config_obj, "node_id")){
366
+ // If the sensor has and should have node id and delay (standard sensor)
367
+ promises['node_id_and_delay'] = node.gateway.config_node_id_and_delay(sensor.mac, parseInt(configs['node_id']), parseInt(configs['delay']));
368
+ }else if(Object.hasOwn(configs, 'node_id') && Object.hasOwn(config_obj, "node_id") && !Object.hasOwn(config_obj, "delay")){
369
+ // If the sensor has and should have node id but should not have delay (special sensor)
370
+ // We will ignore delay setting in this case and set it to 0
371
+ promises['node_id_and_delay'] = node.gateway.config_node_id_and_delay(sensor.mac, parseInt(configs['node_id']), 0);
372
+ }
373
+ }
374
+ switch(config_obj.validator.type){
375
+ case 'hexString':
376
+ console.log('Configuring hexString '+key+' to '+configs[key] + ' eval as '+parseInt(configs[key], 16));
377
+ promises[key] = node.gateway[config_obj.call](sensor.mac, parseInt(configs[key], 16));
378
+ break;
379
+ default:
380
+ promises[key] = node.gateway[config_obj.call](sensor.mac, parseInt(configs[key]));
381
+ break;
382
+ }
383
+ }else{
384
+ promises[key] = node.gateway[config_obj.call](sensor.mac, parseInt(configs[key]));
385
+ }
386
+ }
387
+
388
+ // These sensors listed in original_otf_devices use a different OTF code.
389
+ let original_otf_devices = [53, 80, 81, 82, 83, 84, 87, 101, 102, 103, 110, 111, 112, 114, 117, 180, 181, 518, 519, 520, 538];
390
+ // If we changed the network ID reboot the sensor to take effect.
391
+ // TODO if we add the encryption key command to node-red we need to reboot for it as well.
392
+ if(reboot){
393
+ promises.reboot_sensor = node.gateway.config_reboot_sensor(mac);
394
+ } else {
395
+ if(original_otf_devices.includes(sensor.type)){
396
+ promises.exit_otn_mode = node.gateway.config_exit_otn_mode(mac);
397
+ }else{
398
+ promises.config_exit_otn_mode_common = node.gateway.config_exit_otn_mode_common(mac);
399
+ }
400
+ }
401
+ promises.finish = new Promise((fulfill, reject) => {
402
+ node.gateway.queue.add(() => {
403
+ return new Promise((f, r) => {
404
+ clearTimeout(tout);
405
+ // node.status(modes.READY);
406
+ fulfill();
407
+ f();
408
+ });
409
+ });
410
+ });
411
+ for(var i in promises){
412
+ (function(name){
413
+ promises[name].then((f) => {
414
+ if(name != 'finish'){
415
+ if(Object.hasOwn(f, 'result')){
416
+ switch(f.result){
417
+ case 255:
418
+ success[name] = true;
419
+ delete node.sensor_configs[mac].temp_required_configs[name];
420
+ break;
421
+ default:
422
+ success[name] = {
423
+ res: "Bad Response",
424
+ result: f.result,
425
+ sent: f.sent
426
+ };
427
+ }
428
+ }else{
429
+ success[name] = {
430
+ res: "no result",
431
+ result: null,
432
+ sent: f.sent
433
+ }
434
+ }
435
+ } else{
436
+ // #OTF
437
+ // Check if sensor responded with true for each config sent, if so delete from temp_required_configs
438
+ // we could auto reboot sensor, but this could lead to boot cycle for bad configs/cmds
439
+ if(Object.keys(node.sensor_configs[mac].temp_required_configs).length === 0){
440
+ delete node.sensor_configs[mac].temp_required_configs;
441
+ }
442
+ // TODO turn into event emitter.
443
+ node._emitter.emit('config_node_msg', {topic: 'Config Results', payload: success, addr: mac, time: Date.now()});
444
+ // node.send({topic: 'Config Results', payload: success, time: Date.now(), addr: mac});
445
+ top_fulfill(success);
446
+ }
447
+ }).catch((err) => {
448
+ success[name] = err;
449
+ });
450
+ })(i);
451
+ }
452
+ }, 1000);
453
+ });
454
+ };
455
+
456
+ this._send_otn_request = function(sensor){
457
+ return new Promise((top_fulfill, top_reject) => {
458
+ var msg = {};
459
+ setTimeout(() => {
460
+ var tout = setTimeout(() => {
461
+ node.status(modes.PGM_ERR);
462
+ node.send({topic: 'OTN Request Results', payload: msg, time: Date.now()});
463
+ }, 10000);
464
+
465
+ var promises = {};
466
+ // This command is used for OTF on types 53, 80,81,82,83,84, 101, 102, 110, 111, 518, 519
467
+ const original_otf_devices = [53, 80, 81, 82, 83, 84, 87, 101, 102, 103, 110, 111, 112, 114, 117, 180, 181, 518, 519, 520, 538];
468
+ if(original_otf_devices.includes(sensor.type)){
469
+ // This command is used for OTF on types 53, 80, 81, 82, 83, 84, 101, 102, 110, 111, 518, 519
470
+ promises.config_enter_otn_mode = node.gateway.config_enter_otn_mode(sensor.mac);
471
+ }else{
472
+ // This command is used for OTF on types not 53, 80, 81, 82, 83, 84, 101, 102, 110, 111, 518, 519
473
+ promises.config_enter_otn_mode = node.gateway.config_enter_otn_mode_common(sensor.mac);
474
+ }
475
+
476
+ promises.finish = new Promise((fulfill, reject) => {
477
+ node.gateway.queue.add(() => {
478
+ return new Promise((f, r) => {
479
+ clearTimeout(tout);
480
+ // TODO emitter to set status on config nodes.
481
+ // node.status(modes.FLY);
482
+ fulfill();
483
+ f();
484
+ });
485
+ });
486
+ });
487
+ for(var i in promises){
488
+ (function(name){
489
+ promises[name].then((f) => {
490
+ if(name != 'finish') msg[name] = true;
491
+ else{
492
+ // #OTF
493
+ node.warn('OTN Request Finished');
494
+ // node.send({topic: 'OTN Request Results', payload: msg, time: Date.now()});
495
+ top_fulfill(msg);
496
+ }
497
+ }).catch((err) => {
498
+ if(Object.hasOwn(node.sensor_configs[sensor.mac], 'temp_required_configs')){
499
+ delete node.sensor_configs[sensor.mac].temp_required_configs;
500
+ }
501
+ node.warn('Error entering OTN mode');
502
+ msg[name] = err;
503
+ });
504
+ })(i);
505
+ }
506
+ });
507
+ });
508
+ };
509
+
188
510
  node.check_mode = function(cb){
189
511
  node.gateway.digi.send.at_command("ID").then((res) => {
190
512
  var pan_id = (res.data[0] << 8) | res.data[1];
@@ -581,7 +903,7 @@ module.exports = function(RED) {
581
903
  this.gateway._emitter.removeAllListeners('link_info');
582
904
  this.gateway._emitter.removeAllListeners('converter_response');
583
905
  this.gateway._emitter.removeAllListeners('manifest_received');
584
- console.log(this.gateway._emitter.eventNames());
906
+ // console.log(this.gateway._emitter.eventNames());
585
907
  });
586
908
 
587
909
  node.is_config = false;
@@ -1291,12 +1613,12 @@ module.exports = function(RED) {
1291
1613
  promises.current_calibration_13 = node.config_gateway.config_set_current_calibration_13(mac, cali);
1292
1614
  }
1293
1615
  }
1294
- // if(config.current_calibration_13_dep_active){
1295
- // var cali = parseInt(config.current_calibration_13_dep);
1296
- // if(cali != 0){
1297
- // promises.current_calibration_13_dep = node.config_gateway.config_set_current_calibration_13_dep(mac, cali);
1298
- // }
1299
- // }
1616
+ if(config.current_calibration_13_dep_active){
1617
+ var cali = parseInt(config.current_calibration_13_dep);
1618
+ if(cali != 0){
1619
+ promises.current_calibration_13_dep = node.config_gateway.config_set_current_calibration_13_dep(mac, cali);
1620
+ }
1621
+ }
1300
1622
  if(config.change_detection_t3_active){
1301
1623
  promises.change_detection = node.config_gateway.config_set_change_detection(mac, config.change_enabled ? 1 : 0, parseInt(config.change_pr), parseInt(config.change_interval));
1302
1624
  }
@@ -1366,18 +1688,18 @@ module.exports = function(RED) {
1366
1688
  promises.current_calibration_ch2_19 = node.config_gateway.config_set_current_calibration_ch2_19(mac, cali);
1367
1689
  }
1368
1690
  }
1369
- // if(config.current_calibration_13_dep_active){
1370
- // var cali = parseInt(config.current_calibration_13_dep);
1371
- // if(cali != 0){
1372
- // promises.current_calibration_13_dep = node.config_gateway.config_set_current_calibration_13_dep(mac, cali);
1373
- // }
1374
- // }
1375
- // if(config.current_calibration_ch2_19_dep_active){
1376
- // var cali = parseInt(config.current_calibration_ch2_19_dep);
1377
- // if(cali != 0){
1378
- // promises.current_calibration_ch2_19_dep = node.config_gateway.config_set_current_calibration_ch2_19_dep(mac, cali);
1379
- // }
1380
- // }
1691
+ if(config.current_calibration_13_dep_active){
1692
+ var cali = parseInt(config.current_calibration_13_dep);
1693
+ if(cali != 0){
1694
+ promises.current_calibration_13_dep = node.config_gateway.config_set_current_calibration_13_dep(mac, cali);
1695
+ }
1696
+ }
1697
+ if(config.current_calibration_ch2_19_dep_active){
1698
+ var cali = parseInt(config.current_calibration_ch2_19_dep);
1699
+ if(cali != 0){
1700
+ promises.current_calibration_ch2_19_dep = node.config_gateway.config_set_current_calibration_ch2_19_dep(mac, cali);
1701
+ }
1702
+ }
1381
1703
  if(config.change_detection_t3_active){
1382
1704
  promises.change_detection = node.config_gateway.config_set_change_detection(mac, config.change_enabled ? 1 : 0, parseInt(config.change_pr), parseInt(config.change_interval));
1383
1705
  }
@@ -1475,24 +1797,24 @@ module.exports = function(RED) {
1475
1797
  promises.current_calibration_ch3_28 = node.config_gateway.config_set_current_calibration_ch3_28(mac, cali);
1476
1798
  }
1477
1799
  }
1478
- // if(config.current_calibration_13_dep_active){
1479
- // var cali = parseInt(config.current_calibration_13_dep);
1480
- // if(cali != 0){
1481
- // promises.current_calibration_13_dep = node.config_gateway.config_set_current_calibration_13_dep(mac, cali);
1482
- // }
1483
- // }
1484
- // if(config.current_calibration_ch2_19_dep_active){
1485
- // var cali = parseInt(config.current_calibration_ch2_19_dep);
1486
- // if(cali != 0){
1487
- // promises.current_calibration_ch2_19_dep = node.config_gateway.config_set_current_calibration_ch2_19_dep(mac, cali);
1488
- // }
1489
- // }
1490
- // if(config.current_calibration_ch3_28_dep_active){
1491
- // var cali = parseInt(config.current_calibration_ch3_28_dep);
1492
- // if(cali != 0){
1493
- // promises.current_calibration_ch3_28_dep = node.config_gateway.config_set_current_calibration_ch3_28_dep(mac, cali);
1494
- // }
1495
- // }
1800
+ if(config.current_calibration_13_dep_active){
1801
+ var cali = parseInt(config.current_calibration_13_dep);
1802
+ if(cali != 0){
1803
+ promises.current_calibration_13_dep = node.config_gateway.config_set_current_calibration_13_dep(mac, cali);
1804
+ }
1805
+ }
1806
+ if(config.current_calibration_ch2_19_dep_active){
1807
+ var cali = parseInt(config.current_calibration_ch2_19_dep);
1808
+ if(cali != 0){
1809
+ promises.current_calibration_ch2_19_dep = node.config_gateway.config_set_current_calibration_ch2_19_dep(mac, cali);
1810
+ }
1811
+ }
1812
+ if(config.current_calibration_ch3_28_dep_active){
1813
+ var cali = parseInt(config.current_calibration_ch3_28_dep);
1814
+ if(cali != 0){
1815
+ promises.current_calibration_ch3_28_dep = node.config_gateway.config_set_current_calibration_ch3_28_dep(mac, cali);
1816
+ }
1817
+ }
1496
1818
  if(config.change_detection_t3_active){
1497
1819
  promises.change_detection = node.config_gateway.config_set_change_detection(mac, config.change_enabled ? 1 : 0, parseInt(config.change_pr), parseInt(config.change_interval));
1498
1820
  }
@@ -2269,29 +2591,35 @@ module.exports = function(RED) {
2269
2591
  // promises.set_rtc_101 = node.config_gateway.config_set_rtc_101(mac);
2270
2592
  break;
2271
2593
  case 103:
2272
- if(config.output_data_rate_103_active){
2273
- promises.output_data_rate_103 = node.config_gateway.config_set_output_data_rate_101(mac, parseInt(config.output_data_rate_103));
2594
+ if(config.acc_output_data_rate_103_active){
2595
+ promises.acc_output_data_rate_103 = node.config_gateway.config_set_acc_output_data_rate_103(mac, parseInt(config.acc_output_data_rate_103));
2274
2596
  }
2275
- if(config.sampling_interval_101_active){
2276
- promises.sampling_interval_103 = node.config_gateway.config_set_sampling_interval_101(mac, parseInt(config.sampling_interval_101));
2597
+ if(config.output_data_rate_103_active){
2598
+ promises.gyro_output_data_rate_103 = node.config_gateway.config_set_output_data_rate_101(mac, parseInt(config.output_data_rate_103));
2277
2599
  }
2278
- if(config.acc_threshold_103_active){
2279
- promises.acc_threshold_103 = node.config_gateway.config_set_acc_threshold_103(mac, parseInt(config.acc_threshold_103));
2600
+ if(config.adxl_fsr_103_active){
2601
+ promises.acc_fsr_103 = node.config_gateway.config_set_adxl_fsr_103(mac, parseInt(config.adxl_fsr_103));
2280
2602
  }
2281
- if(config.enable_sensor_103_active){
2282
- promises.enable_sensor_103 = node.config_gateway.config_set_enable_sensor_103(mac, parseInt(config.enable_sensor_103));
2603
+ if(config.sampling_duration_103_active){
2604
+ promises.sampling_duration_103 = node.config_gateway.config_set_sampling_duration_101(mac, parseInt(config.sampling_duration_103));
2283
2605
  }
2284
2606
  if(config.enable_hp_filter_cutoff_103_active){
2285
2607
  promises.enable_hp_filter_cutoff_103 = node.config_gateway.config_set_enable_hp_filter_cutoff_103(mac, parseInt(config.enable_hp_filter_cutoff_103));
2286
2608
  }
2609
+ if(config.sampling_interval_101_active){
2610
+ promises.sampling_interval_103 = node.config_gateway.config_set_sampling_interval_101(mac, parseInt(config.sampling_interval_101));
2611
+ }
2287
2612
  if(config.gyro_fsr_103_active){
2288
2613
  promises.gyro_fsr_103 = node.config_gateway.config_set_gyro_fsr_103(mac, parseInt(config.gyro_fsr_103));
2289
2614
  }
2290
- if(config.adxl_fsr_103_active){
2291
- promises.adxl_fsr_103 = node.config_gateway.config_set_adxl_fsr_103(mac, parseInt(config.adxl_fsr_103));
2615
+ if(config.enable_sensor_103_active){
2616
+ promises.enable_sensor_103 = node.config_gateway.config_set_enable_sensor_103(mac, parseInt(config.enable_sensor_103));
2292
2617
  }
2293
- if(config.sampling_duration_103_active){
2294
- promises.sampling_duration_103 = node.config_gateway.config_set_sampling_duration_101(mac, parseInt(config.sampling_duration_103));
2618
+ if(config.acc_threshold_103_active){
2619
+ promises.acc_threshold_103 = node.config_gateway.config_set_acc_threshold_103(mac, parseInt(config.acc_threshold_103));
2620
+ }
2621
+ if(config.max_num_motion_103_active){
2622
+ promises.max_num_motion_103 = node.config_gateway.config_set_max_num_motion_103(mac, parseInt(config.max_num_motion_103));
2295
2623
  }
2296
2624
  if(config.set_rtc_101){
2297
2625
  promises.set_rtc_103 = node.config_gateway.config_set_rtc_101(mac);
@@ -3581,6 +3909,26 @@ module.exports = function(RED) {
3581
3909
  promises.max_num_motion_tx_delay_110 = node.config_gateway.config_set_max_num_motion_tx_delay_110(mac, parseInt(config.max_num_motion_tx_delay_110));
3582
3910
  }
3583
3911
  break;
3912
+ case 545:
3913
+ if(config.temperature_unit_545_active){
3914
+ promises.temperature_unit_545 = node.config_gateway.config_set_temperature_unit_545(mac, parseInt(config.temperature_unit_545));
3915
+ }
3916
+ if(config.flow_unit_545_active){
3917
+ promises.flow_unit_545 = node.config_gateway.config_set_flow_unit_545(mac, parseInt(config.flow_unit_545));
3918
+ }
3919
+ if(config.gas_type_545_active){
3920
+ promises.gas_type_545 = node.config_gateway.config_set_gas_type_545(mac, parseInt(config.gas_type_545));
3921
+ }
3922
+ if(config.number_of_gas_type_545_active){
3923
+ let gas_type_array = [];
3924
+ let percentage_array = [];
3925
+ for(let ind = 0; ind < config.number_of_gas_type_545; ind++){
3926
+ gas_type_array.push(parseInt(config['gas_type_'+ind+'_545']));
3927
+ percentage_array.push(parseInt(config['percentage_value_'+ind+'_545']));
3928
+ }
3929
+ promises.config_set_gas_type_mix_545 = node.config_gateway.config_set_gas_type_mix_545(mac, parseInt(config.number_of_gas_type_545), gas_type_array, percentage_array);
3930
+ }
3931
+ break;
3584
3932
  case 1010:
3585
3933
  if(config.stay_on_mode_539_active){
3586
3934
  promises.stay_on_mode_539 = node.config_gateway.config_set_stay_on_mode_539(mac, parseInt(config.stay_on_mode_539));
@@ -3770,10 +4118,13 @@ module.exports = function(RED) {
3770
4118
  // Added timeout to fix issue
3771
4119
  if(config.sensor_type == 1010 || config.sensor_type == 1011){
3772
4120
  _config(sensor, true);
3773
- }else{
4121
+ }else if(!Object.hasOwn(node.gateway_node.sensor_configs, sensor.mac) || !Object.hasOwn(node.gateway_node.sensor_configs[sensor.mac], 'api_config_override')){
4122
+ // node.send({topic: "debug", payload: 'WIRELESS DEVICE NODE IS CONFIGURING SENSORS no mac!!!!!'});
3774
4123
  var tout = setTimeout(() => {
3775
4124
  _send_otn_request(sensor);
3776
4125
  }, 100);
4126
+ }else{
4127
+ node.send({topic: 'warning', payload: {'warning': 'Wireless Device Node configurations overridden by API Configuration'}, addr: sensor.mac, time: Date.now()});
3777
4128
  }
3778
4129
  }else if(config.auto_config && config.on_the_fly_enable && sensor.mode == "OTN"){
3779
4130
  if(config.sensor_type == 101 || config.sensor_type == 102 || config.sensor_type == 202){
@@ -3795,9 +4146,12 @@ module.exports = function(RED) {
3795
4146
  _config(sensor, true);
3796
4147
  }, 3500);
3797
4148
  }
3798
- }else{
4149
+ }else if(!Object.hasOwn(node.gateway_node.sensor_configs, sensor.mac) || !Object.hasOwn(node.gateway_node.sensor_configs[sensor.mac], 'api_config_override')){
4150
+ // node.send({topic: "debug", payload: 'WIRELESS DEVICE NODE IS CONFIGURING SENSORS with mac!!!!!'});
3799
4151
  _config(sensor, true);
3800
- }
4152
+ }else{
4153
+ node.send({topic: 'warning', payload: {'warning': 'Wireless Device Node configurations overridden by API Configuration'}, addr: sensor.mac, time: Date.now()});
4154
+ };
3801
4155
  } else if(config.sensor_type == 101 && sensor.mode == "FLY" || config.sensor_type == 102 && sensor.mode == "FLY" || config.sensor_type == 202 && sensor.mode == "FLY"){
3802
4156
  if(this.gateway.hasOwnProperty('fly_101_in_progress') && this.gateway.fly_101_in_progress == false || !this.gateway.hasOwnProperty('fly_101_in_progress')){
3803
4157
  this.gateway.fly_101_in_progress = true;
@@ -3880,12 +4234,14 @@ module.exports = function(RED) {
3880
4234
  // Added timeout to fix issue
3881
4235
  if(config.sensor_type == 1010 || config.sensor_type == 1011){
3882
4236
  _config(sensor, true);
3883
- }else{
4237
+ }else if(!Object.hasOwn(node.gateway_node.sensor_configs, sensor.mac) || !Object.hasOwn(node.gateway_node.sensor_configs[sensor.mac], 'api_config_override')){
4238
+ // node.send({topic: "debug", payload: 'WIRELESS DEVICE NODE IS CONFIGURING SENSORS no mac!!!!!'});
3884
4239
  var tout = setTimeout(() => {
3885
4240
  _send_otn_request(sensor);
3886
4241
  }, 100);
4242
+ }else{
4243
+ node.send({topic: 'warning', payload: {'warning': 'Wireless Device Node configurations overridden by API Configuration'}, addr: sensor.mac, time: Date.now()});
3887
4244
  }
3888
-
3889
4245
  }else if(config.auto_config && config.on_the_fly_enable && sensor.mode == "OTN"){
3890
4246
  if(config.sensor_type == 101 || config.sensor_type == 102 || config.sensor_type == 202){
3891
4247
  if(this.gateway.hasOwnProperty('fly_101_in_progress') && this.gateway.fly_101_in_progress == false || !this.gateway.hasOwnProperty('fly_101_in_progress')){
@@ -3905,9 +4261,14 @@ module.exports = function(RED) {
3905
4261
  _config(sensor, true);
3906
4262
  }, 3500);
3907
4263
  }
3908
- }else{
4264
+ } else if(!Object.hasOwn(node.gateway_node.sensor_configs, sensor.mac) || !Object.hasOwn(node.gateway_node.sensor_configs[sensor.mac], 'api_config_override')){
4265
+ // node.send({topic: "debug", payload: 'WIRELESS DEVICE NODE IS CONFIGURING SENSORS with mac!!!!!'});
3909
4266
  _config(sensor, true);
3910
- }
4267
+ }else{
4268
+ // node.warn(!Object.hasOwn(node.gateway_node.sensor_configs, sensor.mac));
4269
+ // node.warn(!Object.hasOwn(node.gateway_node.sensor_configs[sensor.mac], 'desired_configs'));
4270
+ node.send({topic: 'warning', payload: {'warning': 'Wireless Device Node configurations overridden by API Configuration'}, addr: sensor.mac, time: Date.now()});
4271
+ };
3911
4272
 
3912
4273
  }else if(sensor.mode == "FLY" && config.sensor_type == 101 || sensor.mode == "FLY" && config.sensor_type == 102 || sensor.mode == "FLY" && config.sensor_type == 202){
3913
4274
  if(this.gateway.hasOwnProperty('fly_101_in_progress') && this.gateway.fly_101_in_progress == false || !this.gateway.hasOwnProperty('fly_101_in_progress')){
@@ -4045,6 +4406,597 @@ module.exports = function(RED) {
4045
4406
  res.json({needs_input: false});
4046
4407
  }
4047
4408
  });
4409
+
4410
+ function NcdAPIConfigurationNode(config){
4411
+ RED.nodes.createNode(this,config);
4412
+
4413
+ this._gateway_node = RED.nodes.getNode(config.connection);
4414
+
4415
+ this._gateway_node.open_comms();
4416
+ this.gateway = this._gateway_node.gateway;
4417
+
4418
+ var node = this;
4419
+
4420
+ node.on('close', function(){
4421
+ this._gateway_node.close_comms();
4422
+ });
4423
+
4424
+ var statuses =[
4425
+ {fill:"green",shape:"dot",text:"Ready"},
4426
+ {fill:"yellow",shape:"ring",text:"Configuring"},
4427
+ {fill:"red",shape:"dot",text:"Failed to Connect"},
4428
+ {fill:"green",shape:"ring",text:"Connecting..."}
4429
+ ];
4430
+
4431
+ // node.set_status = function(){
4432
+ // node.status(statuses[node._gateway_node.is_config]);
4433
+ // };
4434
+
4435
+ node.on('input', function(msg, send, done){
4436
+ var response;
4437
+ switch(msg.topic){
4438
+ case 'set_desired_configs':
4439
+ console.log('set_desired_configs triggered');
4440
+ response = this.set_desired_configs(msg.payload);
4441
+ msg.request = msg.payload;
4442
+ if(Object.hasOwn(response, 'error')){
4443
+ msg.payload = response;
4444
+ msg.status = 500;
4445
+ }else{
4446
+ msg.payload = response;
4447
+ msg.status = 200;
4448
+ }
4449
+ msg.time = Date.now();
4450
+ send(msg);
4451
+ break;
4452
+ case 'get_sensor_info':
4453
+ msg.request = msg.payload;
4454
+ if(typeof msg.payload == 'string'){
4455
+ msg.payload = [msg.payload];
4456
+ }
4457
+ response = this.get_sensor_array_info(msg.payload);
4458
+ msg.payload = response.body;
4459
+ msg.status = response.status;
4460
+ msg.time = Date.now();
4461
+ send(msg);
4462
+ break;
4463
+ case 'get_all_sensor_info':
4464
+ response = this.get_all_sensor_info();
4465
+ msg.request = msg.payload;
4466
+ msg.payload = response;
4467
+ msg.status = 200;
4468
+ msg.time = Date.now();
4469
+ send(msg);
4470
+ break;
4471
+ case 'get_sensor_list':
4472
+ node.warn("get_sensor_list not yet implemented");
4473
+ break;
4474
+ case 'reset_sensor_desired_configs':
4475
+ msg.request = msg.payload;
4476
+ if(typeof msg.payload == "string"){
4477
+ msg.payload = [msg.payload];
4478
+ }
4479
+ msg.payload = this.reset_desired_configs(msg.payload);
4480
+ // msg.payload = node._gateway_node.sensor_configs;
4481
+ msg.time = Date.now();
4482
+ msg.status = 200;
4483
+ send(msg);
4484
+ break;
4485
+ case 'sensor_config_options':
4486
+ msg.request = msg.payload;
4487
+ // TODO move logic to function
4488
+ if(typeof msg.payload == 'number' && Object.hasOwn(node._gateway_node.sensor_type_map, msg.payload)){
4489
+ let options = this.get_config_options(msg.payload);
4490
+ msg.payload = options;
4491
+ msg.status = 200;
4492
+ msg.time = Date.now();
4493
+ send(msg);
4494
+ }else{
4495
+ msg.payload = {error: 'get_config_options error: Payload must be a valid sensor type number'};
4496
+ msg.status = 500;
4497
+ msg.time = Date.now();
4498
+ send(msg);
4499
+ }
4500
+ break;
4501
+ default:
4502
+ msg.request = msg.payload;
4503
+ msg.payload = {error: 'Invalid topic. Valid topics are: set_desired_configs, get_sensor_info, get_all_sensor_info, reset_sensor_desired_configs, sensor_config_options'};
4504
+ msg.status = 500;
4505
+ msg.time = Date.now();
4506
+ send(msg);
4507
+ break;
4508
+ };
4509
+ done();
4510
+ });
4511
+
4512
+ this.reset_desired_configs = function(sensor_array){
4513
+ let store_flag = false;
4514
+ const response = {
4515
+ status: 200,
4516
+ body: {}
4517
+ };
4518
+ for (let addr of sensor_array){
4519
+ addr = addr.toLowerCase();
4520
+ response.body[addr] = {};
4521
+ if(Object.hasOwn(node._gateway_node.sensor_configs, addr)){
4522
+ if(Object.hasOwn(node._gateway_node.sensor_configs[addr], 'desired_configs')){
4523
+ node._gateway_node.sensor_configs[addr].desired_configs = node._gateway_node.sensor_configs[addr].reported_configs;
4524
+ response.body[addr].success = true;
4525
+ store_flag = true;
4526
+ }
4527
+ if(Object.hasOwn(node._gateway_node.sensor_configs[addr], 'api_config_override')){
4528
+ delete node._gateway_node.sensor_configs[addr].api_config_override;
4529
+ store_flag = true;
4530
+ }
4531
+ // node._gateway_node.sensor_configs[addr].desired_configs = node._gateway_node.sensor_configs[addr].reported_configs;
4532
+ // delete node._gateway_node.sensor_configs[addr].api_config_override;
4533
+ if(Object.hasOwn(node._gateway_node.sensor_configs[addr], 'temp_required_configs')){
4534
+ delete node._gateway_node.sensor_configs[addr].temp_required_configs;
4535
+ store_flag = true;
4536
+ }
4537
+ if(response.status < 8){
4538
+ response.status +=200;
4539
+ }
4540
+ response.body[addr].data = node._gateway_node.sensor_configs[addr];
4541
+ store_flag = true;
4542
+ }else{
4543
+ if(response.status < 7 || response.status == 200){
4544
+ response.status +=7;
4545
+ }
4546
+ response.body[addr] = {error: "Sensor not found in configuration store"};
4547
+ }
4548
+ };
4549
+ if(response.status == 7){
4550
+ response.status = 500;
4551
+ }
4552
+ if(store_flag) node._gateway_node.store_sensor_configs(JSON.stringify(node._gateway_node.sensor_configs));
4553
+ return response;
4554
+ };
4555
+
4556
+ this.get_config_options = function(sensor_type){
4557
+ if(Object.hasOwn(node._gateway_node.sensor_type_map, sensor_type)){
4558
+ const config_list = node._gateway_node.sensor_type_map[sensor_type].configs;
4559
+ let response = {};
4560
+ for (const key in config_list){
4561
+ const info = node._gateway_node.configuration_map[config_list[key]];
4562
+ // If sensors actually reports and supports the listed config
4563
+ if(Object.hasOwn(info, 'fly_not_reported') && info.fly_not_reported.includes(sensor_type)){
4564
+ continue;
4565
+ }
4566
+ response[key] = {
4567
+ title: info.title,
4568
+ default_value: info.default_value,
4569
+ };
4570
+ if(Object.hasOwn(info, 'main_caption')){
4571
+ response[key].description = info.main_caption;
4572
+ }
4573
+ if(Object.hasOwn(info, 'validator')){
4574
+ if(Object.hasOwn(info.validator, 'generated')){
4575
+ delete info.validator.generated;
4576
+ }
4577
+ response[key] = {...response[key], ...info.validator};
4578
+ }
4579
+ if(Object.hasOwn(info, 'options')){
4580
+ response[key].options = info.options;
4581
+ }
4582
+
4583
+ // if(Object.hasOwn(info, 'validator') && Object.hasOwn(info.validator, 'type')){
4584
+
4585
+ // response[key].value_type = info.validator.type;
4586
+ // }
4587
+ // if(Object.hasOwn(info, 'options')){
4588
+ // response[key].options = info.options;
4589
+ // }else{
4590
+ // if(Object.hasOwn(info, 'validator') && Object.hasOwn(info.validator, 'min')){
4591
+ // response[key].minimum_value = info.validator.min;
4592
+ // }
4593
+ // if(Object.hasOwn(info, 'validator') && Object.hasOwn(info.validator, 'max')){
4594
+ // response[key].maximum_value = info.validator.max;
4595
+ // }
4596
+ // }
4597
+ // node.warn(info);
4598
+ }
4599
+ return response;
4600
+ }else{
4601
+ return 'Invalid sensor type';
4602
+ }
4603
+ };
4604
+
4605
+ this.set_desired_configs = function(desired_configs){
4606
+ var _requires_file_update = false;
4607
+ var response = {};
4608
+ let error_msg = {};
4609
+ let warning_msg = {};
4610
+ if(desired_configs == null || typeof desired_configs == 'undefined'){
4611
+ return {error: 'msg.payload is required. It must be an array of sensor configuration objects. Each object in the array must have addr, type, and configs properties i.e. [{addr: "sensor_mac_address", type: sensor_type_number, configs: {config_name: desired_value}}]'};
4612
+ }else if(!Array.isArray(desired_configs) || desired_configs.length == 0){
4613
+ return {error: 'msg.payload must be an array of sensor configuration objects. Each object in the array must have addr, type, and configs properties i.e. [{addr: "sensor_mac_address", type: sensor_type_number, configs: {config_name: desired_value}}]'};
4614
+ }
4615
+ for (const sensor of desired_configs){
4616
+ // TODO validate sensor object
4617
+ // must have sensor.addr, sensor.type, sensor.configs
4618
+ // sensor.configs is an object with key value pairs of config name and desired value
4619
+ if(!Object.hasOwn(sensor, 'addr')){
4620
+ // throw new Error('Invalid sensor object, must have addr, type, and configs properties');
4621
+ error_msg.syntax_error ||= {};
4622
+ error_msg.syntax_error = {
4623
+ type: 'error',
4624
+ message: 'A Sensor object missing the addr property. Each object in the array must have addr, type, and configs properties i.e. [{addr: "sensor_mac_address", type: sensor_type_number, configs: {config_name: desired_value}}]',
4625
+ detail: {
4626
+ "text": "Missing addr property",
4627
+ "received": sensor,
4628
+ "help": "The following properties are required for each sensor: addr, type, configs i.e. [{addr: 'sensor_mac_address', type: sensor_type_number, configs: {config_name: desired_value}}]"
4629
+ }
4630
+ };
4631
+ continue;
4632
+ // return {error: 'Invalid sensor object in array. Each object in the array must have addr, type, and configs properties i.e. [{addr: "sensor_mac_address", type: sensor_type_number, configs: {config_name: desired_value}}]'};
4633
+ }
4634
+ if(!Object.hasOwn(sensor, 'type') || !Object.hasOwn(sensor, 'configs')){
4635
+ error_msg[sensor.addr] ||= {};
4636
+ error_msg[sensor.addr].syntax_error = {
4637
+ type: 'error',
4638
+ message: 'Invalid sensor object in array. Each object in the array must have addr, type, and configs properties i.e. [{addr: "sensor_mac_address", type: sensor_type_number, configs: {config_name: desired_value}}]',
4639
+ detail: {
4640
+ "text": "Missing type or configs property",
4641
+ "received": sensor,
4642
+ "help": "The following properties are required for each sensor: addr, type, configs. i.e. [{addr: 'sensor_mac_address', type: sensor_type_number, configs: {config_name: desired_value}}]"
4643
+ }
4644
+ };
4645
+ // error_msg[sensor.addr].syntax_error = 'Invalid sensor object in array. Each object in the array must have addr, type, and configs properties i.e. [{addr: "sensor_mac_address", type: sensor_type_number, configs: {config_name: desired_value}}]';
4646
+ continue;
4647
+ }
4648
+ // Force lowercase for consistency
4649
+ sensor.addr = sensor.addr.toLowerCase();
4650
+ // force configs values lowercase for consistency
4651
+ for (const key in sensor.configs){
4652
+ if(typeof sensor.configs[key] == 'string'){
4653
+ sensor.configs[key] = sensor.configs[key].toLowerCase();
4654
+ }
4655
+ }
4656
+
4657
+ // If sensor doesn't exist in sensor_configs, create it and default desired_configs for population.
4658
+ if(!Object.hasOwn(node._gateway_node.sensor_configs, sensor.addr) || !Object.hasOwn(node._gateway_node.sensor_configs[sensor.addr], 'desired_configs')){
4659
+ node._gateway_node.sensor_configs[sensor.addr] = {
4660
+ desired_configs: {}
4661
+ };
4662
+ }
4663
+ if(Object.hasOwn(node._gateway_node.sensor_configs[sensor.addr], 'type')){
4664
+ if(sensor.type != node._gateway_node.sensor_configs[sensor.addr].type){
4665
+ response[sensor.addr] = {error: `Sensor type mismatch for sensor ${sensor.addr}. Expected type ${node._gateway_node.sensor_configs[sensor.addr].type}, but got type ${sensor.type}`, error_object: {"text": "Sensor type mismatch", "expected_type": node._gateway_node.sensor_configs[sensor.addr].type, "received_type": sensor.type}};
4666
+ continue;
4667
+ }
4668
+ }else{
4669
+ node._gateway_node.sensor_configs[sensor.addr].type = sensor.type;
4670
+ _requires_file_update = true;
4671
+ }
4672
+ // Validate configs
4673
+ for (const config_name in sensor.configs){
4674
+ // Check config is valid for sensor type
4675
+ if(Object.hasOwn(node._gateway_node.sensor_type_map, sensor.type) && Object.hasOwn(node._gateway_node.sensor_type_map[sensor.type], 'configs') && Object.hasOwn(node._gateway_node.sensor_type_map[sensor.type].configs, config_name)){
4676
+ let map_key = node._gateway_node.sensor_type_map[sensor.type].configs[config_name];
4677
+ if(Object.hasOwn(node._gateway_node.configuration_map, map_key) && Object.hasOwn(node._gateway_node.configuration_map[map_key], 'validator')){
4678
+ // Check if config is reported by sensor
4679
+ if(Object.hasOwn(node._gateway_node.configuration_map[map_key], 'fly_not_reported') && node._gateway_node.configuration_map[map_key].fly_not_reported.includes(sensor.type)){
4680
+ error_msg[sensor.addr] ||= {};
4681
+ error_msg[sensor.addr][config_name] = {
4682
+ type: 'error',
4683
+ message: `Configuration ${config_name} is not supported for sensor type ${sensor.type} for sensor ${sensor.addr}. This configuration is not reported by the sensor and cannot be programmatically set through the configuration node.`,
4684
+ detail: {
4685
+ "text": "Configuration not supported due to reported configurations",
4686
+ "sensor_type": sensor.type,
4687
+ "config_name": config_name,
4688
+ "help": `Inject this message into the configuration node to get valid configuration values: {topic: 'sensor_config_options', payload: ${sensor.type}}`
4689
+ }
4690
+ };
4691
+ delete sensor.configs[config_name];
4692
+ continue;
4693
+ }
4694
+ if(Object.hasOwn(node._gateway_node.configuration_map[map_key].validator, 'type')){
4695
+ switch(node._gateway_node.configuration_map[map_key].validator.type){
4696
+ case 'hexString':
4697
+ // Remove colons from string
4698
+ sensor.configs[config_name] = sensor.configs[config_name].replace(/:/g, '');
4699
+
4700
+ // Invalid hex string error
4701
+ if (!/^[0-9a-f]+$/.test(sensor.configs[config_name])) {
4702
+ error_msg[sensor.addr] ||= {};
4703
+ error_msg[sensor.addr][config_name] = {
4704
+ type: 'error',
4705
+ message: `Invalid hex string for configuration ${config_name} for sensor ${sensor.addr}. Received value: ${sensor.configs[config_name]}`,
4706
+ detail: {
4707
+ "text": "Invalid hex string",
4708
+ "regex_validator": "/^[0-9a-f]+$/",
4709
+ "received": sensor.configs[config_name],
4710
+ "help": "Hex string should only contain characters 0-9 and a-f. Colons are removed during validation."
4711
+ }
4712
+ };
4713
+ delete sensor.configs[config_name];
4714
+ continue;
4715
+ }
4716
+ // Invalid length error
4717
+ if(Object.hasOwn(node._gateway_node.configuration_map[map_key].validator, 'length') && sensor.configs[config_name].length != node._gateway_node.configuration_map[map_key].validator.length){
4718
+ error_msg[sensor.addr] ||= {};
4719
+ error_msg[sensor.addr][config_name] = {
4720
+ type: 'error',
4721
+ message: `Invalid length for configuration Hex String ${config_name} for sensor ${sensor.addr}. Expected length: ${node._gateway_node.configuration_map[map_key].validator.length}, Received length: ${sensor.configs[config_name].length}`,
4722
+ detail: {
4723
+ "text": "Invalid length for hex string",
4724
+ "expected_length": node._gateway_node.configuration_map[map_key].validator.length,
4725
+ "received_length": sensor.configs[config_name].length,
4726
+ "help": "Add leading or trailing zeros to meet length requirement."
4727
+ }
4728
+ };
4729
+ delete sensor.configs[config_name];
4730
+ continue;
4731
+ }
4732
+ // Warning for missing length validator
4733
+ if (!Object.hasOwn(node._gateway_node.configuration_map[map_key].validator, 'length')) {
4734
+ warning_msg[sensor.addr] ||= {};
4735
+ warning_msg[sensor.addr][config_name] = {
4736
+ type: 'warning',
4737
+ message: `No length validator defined for hex string ${config_name} for sensor ${sensor.addr}.`,
4738
+ detail: {
4739
+ "text": "Missing length validator",
4740
+ "type": "hexString",
4741
+ "help": "This warning does not prevent the configuration from going through."
4742
+ }
4743
+ };
4744
+ }
4745
+ break;
4746
+ default:
4747
+ // Default should be number types. We should have min/max values on all number types.
4748
+ const var_store = sensor.configs[config_name];
4749
+ sensor.configs[config_name] = parseInt(sensor.configs[config_name]);
4750
+ // NaN (Non-Integer/Invalid Value) error
4751
+ if (isNaN(sensor.configs[config_name])) {
4752
+ error_msg[sensor.addr] ||= {};
4753
+ error_msg[sensor.addr][config_name] = {
4754
+ type: 'error',
4755
+ message: `Invalid value for configuration ${config_name} for sensor ${sensor.addr}. Number value expected. Received value: ${sensor.configs[config_name]}`,
4756
+ detail: {
4757
+ "text": "Integer value expected",
4758
+ "received": var_store,
4759
+ "expected_type": "number",
4760
+ "received_type": typeof var_store,
4761
+ "help": "Ensure the value is a valid number or can be evaluated as a number using javascript's parseInt()."
4762
+ }
4763
+ };
4764
+ delete sensor.configs[config_name];
4765
+ continue;
4766
+ }
4767
+ // Value below minimum error
4768
+ if(Object.hasOwn(node._gateway_node.configuration_map[map_key].validator, 'min') && sensor.configs[config_name] < node._gateway_node.configuration_map[map_key].validator.min){
4769
+ error_msg[sensor.addr] ||= {};
4770
+ error_msg[sensor.addr][config_name] = {
4771
+ type: 'error',
4772
+ message: `Invalid value for configuration ${config_name} for sensor ${sensor.addr}. Value ${sensor.configs[config_name]} is less than minimum allowed value of ${node._gateway_node.configuration_map[map_key].validator.min}`,
4773
+ detail: {
4774
+ "text": "Value below minimum",
4775
+ "min_allowed": node._gateway_node.configuration_map[map_key].validator.min,
4776
+ "received": sensor.configs[config_name],
4777
+ "help": `Inject this message into the configuration node to get valid configuration values: {topic: 'sensor_config_options', payload: ${sensor.type}}`
4778
+ }
4779
+ };
4780
+ delete sensor.configs[config_name];
4781
+ continue;
4782
+ // return {error: `Invalid value for configuration ${config_name} for sensor ${sensor.addr}. Value ${sensor.configs[config_name]} is less than minimum allowed value of ${node.configuration_map[map_key].validator.min}`};
4783
+ }
4784
+ // Value above maximum error
4785
+ if(Object.hasOwn(node._gateway_node.configuration_map[map_key].validator, 'max') && sensor.configs[config_name] > node._gateway_node.configuration_map[map_key].validator.max){
4786
+ error_msg[sensor.addr] ||= {};
4787
+ error_msg[sensor.addr][config_name] = {
4788
+ type: 'error',
4789
+ message: `Invalid value for configuration ${config_name} for sensor ${sensor.addr}. Value ${sensor.configs[config_name]} is greater than maximum allowed value of ${node._gateway_node.configuration_map[map_key].validator.max}`,
4790
+ detail: {
4791
+ "text": "Value above maximum",
4792
+ "max_allowed": node._gateway_node.configuration_map[map_key].validator.max,
4793
+ "received": sensor.configs[config_name],
4794
+ "help": `Inject this message into the configuration node to get valid configuration values: {topic: 'sensor_config_options', payload: ${sensor.type}}`
4795
+ }
4796
+ };
4797
+ delete sensor.configs[config_name];
4798
+ continue;
4799
+ // return {error: `Invalid value for configuration ${config_name} for sensor ${sensor.addr}. Value ${sensor.configs[config_name]} is greater than maximum allowed value of ${node.configuration_map[map_key].validator.max}`};
4800
+ }
4801
+ // Warning for missing min/max validators
4802
+ if(!Object.hasOwn(node._gateway_node.configuration_map[map_key].validator, 'min') || !Object.hasOwn(node._gateway_node.configuration_map[map_key].validator, 'max')){
4803
+ warning_msg[sensor.addr] ||= {};
4804
+ warning_msg[sensor.addr][config_name] = {
4805
+ type: 'warning',
4806
+ message: `No min/max validator defined for number type ${config_name} for sensor ${sensor.addr}.`,
4807
+ detail: {
4808
+ "text": "Missing min/max validator",
4809
+ "config_name": config_name,
4810
+ "help": "This warning does not prevent the configuration from going through."
4811
+ }
4812
+ };
4813
+ // console.log('Warning: No min/max validator for number type ' + config_name + ' for sensor type ' + sensor.type);
4814
+ }
4815
+ // If options exist, value must be one of the options
4816
+ // This is assuming that all configs with options are number types
4817
+ if(Object.hasOwn(node._gateway_node.configuration_map[map_key], 'options')){
4818
+ if(!Object.keys(node._gateway_node.configuration_map[map_key].options).includes(String(sensor.configs[config_name]))){
4819
+ error_msg[sensor.addr] ||= {};
4820
+ error_msg[sensor.addr][config_name] = {
4821
+ type: 'error',
4822
+ message: `Invalid option for configuration ${config_name} for sensor ${sensor.addr}. Received value: ${sensor.configs[config_name]}`,
4823
+ detail: {
4824
+ "text": "Invalid option",
4825
+ "valid_options": node._gateway_node.configuration_map[map_key].options,
4826
+ "received": sensor.configs[config_name],
4827
+ "help": `Inject this message into the configuration node to get valid configuration values: {topic: 'sensor_config_options', payload: ${sensor.type}}`
4828
+ }
4829
+ };
4830
+ delete sensor.configs[config_name];
4831
+ continue;
4832
+ }
4833
+ }
4834
+ break;
4835
+ // Add more validators as needed
4836
+ }
4837
+ }else{
4838
+ // Warning: No 'type' validator property found
4839
+ // Return error, but allow the configuration to go through.
4840
+ error_msg[sensor.addr] ||= {};
4841
+ error_msg[sensor.addr][config_name] = {
4842
+ type: 'error',
4843
+ message: `No type validator property found for ${config_name} for sensor type ${sensor.type}.`,
4844
+ detail: {"text":
4845
+ "Missing type validator",
4846
+ "map_key": map_key,
4847
+ "sensor_type": sensor.type,
4848
+ "help": "Contact ncd.io support with a copy of this msg object to have this issue resolved."
4849
+ }
4850
+ };
4851
+ delete sensor.configs[config_name];
4852
+ continue;
4853
+ }
4854
+ }else{
4855
+ // Warning: No 'validator' object found
4856
+ error_msg[sensor.addr] ||= {};
4857
+ error_msg[sensor.addr][config_name] = {
4858
+ type: 'error',
4859
+ message: `No validator object found for configuration ${config_name} for sensor type ${sensor.type}.`,
4860
+ detail: {
4861
+ "text": "Missing validator object",
4862
+ "map_key": map_key,
4863
+ "sensor_type": sensor.type,
4864
+ "help": "Contact ncd.io support with a copy of this msg object to have this issue resolved."
4865
+ }
4866
+ };
4867
+ delete sensor.configs[config_name];
4868
+ continue;
4869
+ }
4870
+ }else{
4871
+ // Error: Configuration name not valid for sensor type
4872
+ error_msg[sensor.addr] ||= {};
4873
+ error_msg[sensor.addr][config_name] = {
4874
+ type: 'error',
4875
+ message: `Configuration ${config_name} is not valid for sensor type ${sensor.type} for sensor ${sensor.addr}`,
4876
+ detail: {
4877
+ "text": "Invalid configuration for sensor type",
4878
+ "sensor_type": sensor.type,
4879
+ "help": "Contact ncd.io support with a copy of this msg object to have this issue resolved."
4880
+ }
4881
+ };
4882
+ delete sensor.configs[config_name];
4883
+ continue;
4884
+ }
4885
+ }
4886
+ let new_desired_configs = {...node._gateway_node.sensor_configs[sensor.addr].desired_configs, ...sensor.configs};
4887
+
4888
+
4889
+
4890
+ // If the desired configs are different than what is already stored we need to update the file
4891
+ if(!isDeepStrictEqual(node._gateway_node.sensor_configs[sensor.addr].desired_configs, new_desired_configs)){
4892
+ console.log('Configs differ, updating desired configs');
4893
+ node._gateway_node.sensor_configs[sensor.addr].desired_configs = {...node._gateway_node.sensor_configs[sensor.addr].desired_configs, ...sensor.configs};
4894
+ _requires_file_update = true;
4895
+ }else{
4896
+ console.log('Configs the same, not updating desired configs');
4897
+ };
4898
+ if(!Object.hasOwn(node._gateway_node.sensor_configs[sensor.addr], 'api_config_override')){
4899
+ node._gateway_node.sensor_configs[sensor.addr].api_config_override = true;
4900
+ _requires_file_update = true;
4901
+ }
4902
+ response[sensor.addr] = node._gateway_node.sensor_configs[sensor.addr];
4903
+ }
4904
+ if(_requires_file_update){
4905
+ node._gateway_node.store_sensor_configs(JSON.stringify(node._gateway_node.sensor_configs));
4906
+ }
4907
+ console.log(Object.keys(warning_msg).length);
4908
+ if(Object.keys(warning_msg).length){
4909
+ response.warnings = warning_msg;
4910
+ }
4911
+ console.log(Object.keys(error_msg).length);
4912
+ if(Object.keys(error_msg).length){
4913
+ response.errors = error_msg;
4914
+ }
4915
+ return response;
4916
+ };
4917
+
4918
+ node.gateway.on('sensor_mode', (d) => {
4919
+ if(d.mode == 'FLY'){
4920
+ if(Object.hasOwn(node._gateway_node.sensor_configs, d.mac) && Object.hasOwn(node._gateway_node.sensor_configs[d.mac], 'api_config_override')){
4921
+ node.send({topic: 'sensor_report', payload: node._gateway_node.sensor_configs[d.mac], addr: d.mac, time: Date.now()});
4922
+ }
4923
+ }else if(d.mode == 'OTF'){
4924
+ if(Object.hasOwn(node._gateway_node.sensor_configs, d.mac) && Object.hasOwn(node._gateway_node.sensor_configs[d.mac], 'api_config_override')){
4925
+ node.send({topic: 'post_update_sensor_report', payload: node._gateway_node.sensor_configs[d.mac], addr: d.mac, time: Date.now()});
4926
+ }
4927
+ }
4928
+ });
4929
+
4930
+ node._gateway_node.on('config_node_msg', (d) => {
4931
+ node.send(d);
4932
+ });
4933
+ node._gateway_node.on('config_node_error', (d) => {
4934
+ node.send(d);
4935
+ });
4936
+
4937
+ node.on('close', (done) => {
4938
+ node._gateway_node.removeAllListeners('config_node_msg');
4939
+ node._gateway_node.removeAllListeners('config_node_error');
4940
+ node._gateway_node.removeAllListeners('sensor_mode');
4941
+ done();
4942
+ });
4943
+
4944
+
4945
+ this.get_all_sensor_info = function(){
4946
+ return node._gateway_node.sensor_configs;
4947
+ };
4948
+ this.get_sensor_array_info = function(sensor_array){
4949
+ let response = {
4950
+ status: 0,
4951
+ body: {}
4952
+ };
4953
+ for (let addr of sensor_array){
4954
+ addr = addr.toLowerCase();
4955
+ if(Object.hasOwn(node._gateway_node.sensor_configs, addr)){
4956
+ response.body[addr] = node._gateway_node.sensor_configs[addr];
4957
+ // Increase by 200 for first succesful read 8 is to account for error below
4958
+ if(response.status < 8){
4959
+ response.status +=200;
4960
+ }
4961
+ }else{
4962
+ response.body[addr] = {error: 'No info for this sensor'};
4963
+ // Increase by 7 for first error read to make 207 partial success or set to 7 for later
4964
+ if(response.status < 7 || response.status == 200){
4965
+ response.status +=7;
4966
+ }
4967
+ }
4968
+ }
4969
+ // Set status to 500 for full error
4970
+ if(response.status == 7){
4971
+ response.status = 500;
4972
+ }
4973
+ return response;
4974
+ };
4975
+
4976
+ // node.gateway.on('sensor_data', (d) => {
4977
+ // node.set_status();
4978
+ // node.send({topic: 'sensor_data', payload: d, time: Date.now()});
4979
+ // });
4980
+ // node.gateway.on('sensor_mode', (d) => {
4981
+ // node.set_status();
4982
+ // node.send({topic: 'sensor_mode', payload: d, time: Date.now()});
4983
+ // });
4984
+ // node.gateway.on('receive_packet-unknown_device',(d)=>{
4985
+ // node.set_status();
4986
+ // msg1 = {topic:'somethingTopic',payload:"something"};
4987
+ // node.send([null,{topic: 'unknown_data', payload:d, time: Date.now()}]);
4988
+ // });
4989
+ // node._gateway_node.on('mode_change', (mode) => {
4990
+ // node.set_status();
4991
+ // if(this.gateway.modem_mac && this._gateway_node.is_config == 0 || this.gateway.modem_mac && this._gateway_node.is_config == 1){
4992
+ // node.send({topic: 'modem_mac', payload: this.gateway.modem_mac, time: Date.now()});
4993
+ // }else{
4994
+ // node.send({topic: 'error', payload: {code: 1, description: 'Wireless module did not respond'}, time: Date.now()});
4995
+ // }
4996
+ // });
4997
+ };
4998
+ // register a new node called ncd-api-config
4999
+ RED.nodes.registerType("ncd-api-configuration-node", NcdAPIConfigurationNode);
4048
5000
  };
4049
5001
  function getSerialDevices(ftdi, res){
4050
5002
  var busses = [];