@ncd-io/node-red-enterprise-sensors 1.6.4 → 2.0.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
@@ -7,6 +7,8 @@ const fs = require('fs');
7
7
  const path = require('path');
8
8
  const home_dir = require('os').homedir;
9
9
  const { isDeepStrictEqual } = require('util');
10
+ // We're using v8 to handle deep cloning of objects with buffer data.
11
+ const v8 = require('v8');
10
12
 
11
13
 
12
14
  module.exports = function(RED) {
@@ -28,8 +30,6 @@ module.exports = function(RED) {
28
30
  const configuration_map_location = home_dir()+'/.node-red/node_modules/@ncd-io/node-red-enterprise-sensors/data/configuration_map.json';
29
31
  const sensor_type_map_location = home_dir()+'/.node-red/node_modules/@ncd-io/node-red-enterprise-sensors/data/sensor_type_map.json';
30
32
 
31
- this.config_node_capable = [114];
32
-
33
33
  // comms_timer object var added to clear the time to prevent issues with rapid redeploy
34
34
  this.comms_timer;
35
35
 
@@ -134,114 +134,186 @@ module.exports = function(RED) {
134
134
  node.gateway.digi.serial.reconnect();
135
135
  });
136
136
  }
137
- // Event listener to make sure this only triggers once no matter how many gateway nodes there are
137
+ // Listen for FLY messages to run FOTA updates on older FLY messages
138
138
  node.gateway.on('sensor_mode', (d) => {
139
- if(d.mode == "FLY" || d.mode == "OTF"){
139
+ if(d.mode == "FLY" && !Object.hasOwn(d, 'sync')){
140
140
  if(Object.hasOwn(node.sensor_list, d.mac) && Object.hasOwn(node.sensor_list[d.mac], 'update_request') && d.mode == "FLY"){
141
141
  node.request_manifest(d.mac);
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;
142
+ }
143
+ };
144
+ });
145
+ // Event listener to make sure this only triggers once no matter how many gateway nodes there are
146
+ node.gateway.on('sync', (d) => {
147
+ if(d.type == 'sync_check_in' || d.type == 'sync_end' || d.type == 'manual_sync_check_in' || d.type == 'sync_acknowledgment'){
148
+ // TODO replace with deepclone once we update node version on Gateway.
149
+ const cloned_payload = v8.deserialize(v8.serialize(d.payload));
150
+ // const payload = structuredClone(d.payload);
151
+ const {
152
+ machine_values: { sensor_type, ...actual_values},
153
+ human_readable,
154
+ address: addr,
155
+ type,
156
+ ...remaining
157
+ } = cloned_payload;
148
158
 
159
+ const payload = { ...remaining, ...actual_values, sensor_type };
160
+
161
+ if(Object.hasOwn(node.sensor_list, addr) && Object.hasOwn(node.sensor_list[addr], 'update_request')){
162
+ node.request_manifest(addr);
163
+ }else if(Object.hasOwn(this.gateway.sensor_libs, sensor_type)){
164
+ // API CONFIGURATION NODE
165
+ // let values = d.reported_config.machine_values;
166
+ const config_map = this.gateway.sensor_libs[sensor_type].get_config_map(payload.firmware_version);
149
167
  let store_flag = false;
150
168
 
151
- if(!Object.hasOwn(node.sensor_configs, d.mac)){
152
- node.sensor_configs[d.mac] = {};
153
- store_flag = true;
169
+ let fly_payload = null;
170
+
171
+ if(config.enable_fly_compatibility){
172
+ fly_payload = {
173
+ ...human_readable,
174
+ 'machine_values': {...actual_values}
175
+ };
176
+ for (const [new_key, conf] of Object.entries(config_map)) {
177
+ if (conf.old_fly_id) {
178
+ const old_key = conf.old_fly_id;
179
+ // Make sure a small slipup in the config map doesn't cause issues with the payload
180
+ if(old_key !== new_key){
181
+ if (Object.hasOwn(fly_payload, new_key)) {
182
+ fly_payload[old_key] = fly_payload[new_key];
183
+ delete fly_payload[new_key];
184
+ }
185
+ // Alias the machine value (inside machine_values)
186
+ if (Object.hasOwn(fly_payload.machine_values, new_key)) {
187
+ fly_payload.machine_values[old_key] = fly_payload.machine_values[new_key];
188
+ delete fly_payload.machine_values[new_key];
189
+ }
190
+ }
191
+ }
192
+ };
154
193
  }
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;
194
+
195
+ if(!Object.hasOwn(node.sensor_configs, addr)){
196
+ node.sensor_configs[addr] = {};
158
197
  store_flag = true;
159
198
  }
160
- if(Object.hasOwn(values, 'hardware_id')) delete values.hardware_id;
199
+
200
+ // delete so we don't have to consider it for storage
201
+ // TODO re-add, but remove from storage consideration for mapping
202
+ // if(Object.hasOwn(d.payload, 'tx_lifetime_counter')){
203
+ // delete d.payload.tx_lifetime_counter;
204
+ // }
205
+
206
+ // Store hardware_id outside of reported_configs
207
+ // if(!Object.hasOwn(node.sensor_configs[addr], 'hardware_id') && Object.hasOwn(d.payload, 'hardware_id')){
208
+ // node.sensor_configs[addr].hardware_id = d.payload.hardware_id;
209
+ // delete d.payload.hardware_id;
210
+ // store_flag = true;
211
+ // }
212
+
213
+ // Store core_version outside of reported_configs
214
+ // if(Object.hasOwn(d.payload, 'core_version')){
215
+ // node.sensor_configs[addr].core_version = d.payload.core_version;
216
+ // store_flag = true;
217
+ // }
218
+
161
219
  // Loop through and translate and validate values.
162
220
  // We want to allow passing in integers instead of byte arrays etc.
163
221
  // 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;
222
+ // values.network_id = config.pan_id;
223
+ // if(Object.hasOwn(d, 'node_id')){
224
+ // values.node_id = d.nodeId;
225
+ // }
226
+ for(let key in payload){
227
+ if(Object.hasOwn(config_map, key)){
228
+ if(!Object.hasOwn(config_map[key], 'write_index')){
229
+ // TODO
230
+ if(!Object.hasOwn(node.sensor_configs[addr], key) || Object.hasOwn(node.sensor_configs[addr], key) && node.sensor_configs[addr][key] != payload[key]){
231
+ // console.log('Config '+key+' is read only, moving from payload');
232
+ node.sensor_configs[addr][key] = payload[key];
233
+ if(key !== 'tx_lifetime_counter'){
234
+ store_flag = true;
191
235
  }
192
236
  }
237
+ delete payload[key];
193
238
  }
239
+ }else{
240
+ // console.log('Not in config map, deleting key: '+key);
241
+ delete payload[key];
194
242
  }
195
243
  }
196
244
  // 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 = {};
245
+ if(!Object.hasOwn(node.sensor_configs[addr], 'reported_configs')){
246
+ // node.sensor_configs[d.mac].reported_configs = {};
247
+ node.sensor_configs[addr].reported_configs = payload;
199
248
  store_flag = true;
200
249
  }
201
250
  // 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()});
251
+ if(!Object.hasOwn(node.sensor_configs[addr], 'desired_configs')){
252
+ node.sensor_configs[addr].desired_configs = payload;
214
253
  store_flag = true;
215
254
  }
216
- if(!isDeepStrictEqual(node.sensor_configs[d.mac].reported_configs, values)){
255
+
256
+ // We're setting sensor_type above, so we can remove this.
257
+ // if(!Object.hasOwn(node.sensor_configs[addr], 'type')){
258
+ // node.sensor_configs[addr].type = d.type;
259
+ // store_flag = true;
260
+ // }else if(node.sensor_configs[d.mac].type != d.type){
261
+ // // 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
262
+ // delete node.sensor_configs[d.mac].desired_configs;
263
+ // node.sensor_configs[d.mac].type = d.type;
264
+ // 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()});
265
+ // store_flag = true;
266
+ // }
267
+
268
+
269
+ if(!isDeepStrictEqual(node.sensor_configs[addr].reported_configs, payload)){
217
270
  // Values are different, update the stored configs and write to file
218
- node.sensor_configs[d.mac].reported_configs = values;
271
+ node.sensor_configs[addr].reported_configs = payload;
219
272
  // If reported configs change, but the auto config does not control configs, update automatically
220
273
  // 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;
274
+ if(!Object.hasOwn(node.sensor_configs[addr], 'api_config_override')){
275
+ node.sensor_configs[addr].desired_configs = payload;
223
276
  }
224
277
  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()});
278
+ node._emitter.emit('config_node_msg', {topic: 'sensor_configs_update', payload: node.sensor_configs[addr], address: addr, time: Date.now()});
226
279
  // node.send({topic: 'sensor_configs_update', payload: node.sensor_configs[d.mac], time: Date.now()});
227
280
  }
228
281
 
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;
282
+ if(type == 'sync_check_in'){
283
+ // TODO add backwards compatibility
284
+ if(config.enable_fly_compatibility){
285
+ this.gateway._emitter.emit('sensor_mode', {mac: addr, type: sensor_type, nodeId: payload.node_id, mode: 'FLY', lastHeard: Date.now(), reported_config: fly_payload, sync: true});
286
+ this.gateway._emitter.emit('sensor_mode-'+addr, {mac: addr, type: sensor_type, nodeId: payload.node_id, mode: 'FLY', lastHeard: Date.now(), reported_config: fly_payload, sync: true});
287
+ }
288
+ if(Object.hasOwn(node.sensor_configs[addr], 'desired_configs') && Object.hasOwn(node.sensor_configs[addr], 'api_config_override') && !isDeepStrictEqual(node.sensor_configs[addr].reported_configs, node.sensor_configs[addr].desired_configs)){
233
289
  store_flag = true;
234
290
  var tout = setTimeout(() => {
235
- this._send_otn_request(d);
291
+ node.configure(d.address, d.payload.sensor_type);
292
+ // this.sync_init(addr, sensor_type);
236
293
  }, 100);
237
294
  }
295
+ }else if(type == 'manual_sync_check_in'){
296
+ if(Object.hasOwn(node.sensor_configs[addr], 'desired_configs') && Object.hasOwn(node.sensor_configs[addr], 'api_config_override') && !isDeepStrictEqual(node.sensor_configs[addr].reported_configs, node.sensor_configs[addr].desired_configs)){
297
+ node.configure(addr, sensor_type);
298
+ }
299
+ }else if(type == 'sync_end'){
300
+ if(config.enable_fly_compatibility){
301
+ this.gateway._emitter.emit('sensor_mode', {mac: addr, type: sensor_type, nodeId: payload.node_id, mode: 'OTF', lastHeard: Date.now(), reported_config: fly_payload, sync: true});
302
+ this.gateway._emitter.emit('sensor_mode-'+addr, {mac: addr, type: sensor_type, nodeId: payload.node_id, mode: 'OTF', lastHeard: Date.now(), reported_config: fly_payload, sync: true});
303
+ }
238
304
  }
239
305
  if(store_flag){
240
306
  node.store_sensor_configs(JSON.stringify(node.sensor_configs));
241
307
  }
242
308
  }
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);
309
+ }else if(d.type == 'sync_init'){
310
+ if(config.enable_fly_compatibility){
311
+ this.gateway._emitter.emit('sensor_mode', {mac: d.address, type: d.payload.sensor_type, nodeId: d.payload.node_id, mode: 'OTN', lastHeard: Date.now(), sync: true});
312
+ this.gateway._emitter.emit('sensor_mode-'+d.address, {mac: d.address, type: d.payload.sensor_type, nodeId: d.payload.node_id, mode: 'OTN', lastHeard: Date.now(), sync: true});
313
+ }
314
+ // if(node.sensor_configs[d.address] && node.sensor_configs[d.address].api_config_override){
315
+ // node.configure(d.address, d.payload.sensor_type);
316
+ // }
245
317
  }
246
318
  });
247
319
  node.gateway.on('manifest_received', (manifest_data) => {
@@ -327,6 +399,58 @@ module.exports = function(RED) {
327
399
  }
328
400
  };
329
401
 
402
+ this.sync_init = function(addr, type){
403
+ // console.log('!!!! Sync Init for sensor '+addr);
404
+ return new Promise((top_fulfill, top_reject) => {
405
+ var msg = {};
406
+ setTimeout(() => {
407
+ var tout = setTimeout(() => {
408
+ // switch to emitter for this
409
+ // node.status(modes.PGM_ERR);
410
+ // node.send({topic: 'OTN Request Results', payload: msg, time: Date.now()});
411
+ console.log('Sync Request Timed Out');
412
+ }, 10000);
413
+
414
+ var promises = {};
415
+ // This command is used for OTF on types 53, 80,81,82,83,84, 101, 102, 110, 111, 518, 519
416
+ 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, 543];
417
+ if(original_otf_devices.includes(type)){
418
+ console.log('!!!! Entering Sync mode with original command');
419
+ // This command is used for OTF on types 53, 80, 81, 82, 83, 84, 101, 102, 110, 111, 518, 519
420
+ promises.config_enter_otn_mode = node.gateway.config_enter_otn_mode(addr);
421
+ }else{
422
+ console.log('!!!! Entering Sync mode with other command');
423
+ // This command is used for OTF on types not 53, 80, 81, 82, 83, 84, 101, 102, 110, 111, 518, 519
424
+ promises.config_enter_otn_mode = node.gateway.config_enter_otn_mode_common(addr);
425
+ }
426
+ promises.finish = new Promise((fulfill, reject) => {
427
+ node.gateway.queue.add(() => {
428
+ return new Promise((f, r) => {
429
+ clearTimeout(tout);
430
+ // node.status(modes.FLY);
431
+ fulfill();
432
+ f();
433
+ });
434
+ });
435
+ });
436
+ for(var i in promises){
437
+ (function(name){
438
+ promises[name].then((f) => {
439
+ if(name != 'finish') msg[name] = true;
440
+ else{
441
+ // #OTF
442
+ node.send({topic: 'OTN Request Results', payload: msg, time: Date.now()});
443
+ top_fulfill(msg);
444
+ }
445
+ }).catch((err) => {
446
+ msg[name] = err;
447
+ });
448
+ })(i);
449
+ }
450
+ });
451
+ });
452
+ };
453
+
330
454
  this.get_required_configs = function(desired_configs, reported_configs){
331
455
  const mismatched = {};
332
456
  for (const key in desired_configs) {
@@ -340,8 +464,15 @@ module.exports = function(RED) {
340
464
  return mismatched;
341
465
  };
342
466
 
343
- // TODO consider adding logic for if only 1 config to go out, skip OTN request
344
- this.configure = function(sensor){
467
+ this.sync_check_reboot = function(addr){
468
+ if(node.sensor_configs[addr].reported_configs.network_id != node.sensor_configs[addr].desired_configs.network_id){
469
+ return true;
470
+ }else{
471
+ return false;
472
+ }
473
+ };
474
+
475
+ this.configure = function(addr, type){
345
476
  return new Promise((top_fulfill, top_reject) => {
346
477
  var success = {};
347
478
  setTimeout(() => {
@@ -352,50 +483,23 @@ module.exports = function(RED) {
352
483
  // node.send({topic: 'Config Results', payload: success, time: Date.now(), addr: sensor.mac});
353
484
  }, 60000);
354
485
  // node.status(modes.PGM_NOW);
355
- var mac = sensor.mac;
486
+ var mac = addr;
356
487
  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
- }
488
+ var reboot = this.sync_check_reboot(addr);
489
+
490
+ promises.sync_command = node.gateway.send_sync_configs(addr, node.sensor_configs[addr]);
387
491
 
388
492
  // These sensors listed in original_otf_devices use a different OTF code.
389
493
  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
494
  // If we changed the network ID reboot the sensor to take effect.
391
495
  // TODO if we add the encryption key command to node-red we need to reboot for it as well.
392
496
  if(reboot){
393
- promises.reboot_sensor = node.gateway.config_reboot_sensor(mac);
497
+ promises.reboot_sensor = node.gateway.config_reboot_sensor(addr);
394
498
  } else {
395
- if(original_otf_devices.includes(sensor.type)){
396
- promises.exit_otn_mode = node.gateway.config_exit_otn_mode(mac);
499
+ if(original_otf_devices.includes(type)){
500
+ promises.exit_otn_mode = node.gateway.config_exit_otn_mode(addr);
397
501
  }else{
398
- promises.config_exit_otn_mode_common = node.gateway.config_exit_otn_mode_common(mac);
502
+ promises.config_exit_otn_mode_common = node.gateway.config_exit_otn_mode_common(addr);
399
503
  }
400
504
  }
401
505
  promises.finish = new Promise((fulfill, reject) => {
@@ -416,7 +520,6 @@ module.exports = function(RED) {
416
520
  switch(f.result){
417
521
  case 255:
418
522
  success[name] = true;
419
- delete node.sensor_configs[mac].temp_required_configs[name];
420
523
  break;
421
524
  default:
422
525
  success[name] = {
@@ -442,7 +545,7 @@ module.exports = function(RED) {
442
545
  // TODO turn into event emitter.
443
546
  node._emitter.emit('config_node_msg', {topic: 'Config Results', payload: success, addr: mac, time: Date.now()});
444
547
  // node.send({topic: 'Config Results', payload: success, time: Date.now(), addr: mac});
445
- top_fulfill(success);
548
+ // top_fulfill(success);
446
549
  }
447
550
  }).catch((err) => {
448
551
  success[name] = err;
@@ -903,6 +1006,7 @@ module.exports = function(RED) {
903
1006
  this.gateway._emitter.removeAllListeners('link_info');
904
1007
  this.gateway._emitter.removeAllListeners('converter_response');
905
1008
  this.gateway._emitter.removeAllListeners('manifest_received');
1009
+ this.gateway._emitter.removeAllListeners('sync');
906
1010
  // console.log(this.gateway._emitter.eventNames());
907
1011
  });
908
1012
 
@@ -1278,6 +1382,28 @@ module.exports = function(RED) {
1278
1382
  node.set_status();
1279
1383
  node.send({topic: 'sensor_data', payload: d, time: Date.now()});
1280
1384
  });
1385
+ node.gateway.on('sync', (d) => {
1386
+ switch (d.payload.type){
1387
+ case 'sync_check_in':
1388
+ node.set_status();
1389
+ node.send({
1390
+ 'topic': 'sync',
1391
+ 'type': d.payload.type,
1392
+ ...d,
1393
+ 'time': Date.now()
1394
+ });
1395
+ break;
1396
+ case 'sync_init':
1397
+ node.set_status();
1398
+ node.send({
1399
+ 'topic': 'sync',
1400
+ 'type': d.payload.type,
1401
+ ...d,
1402
+ 'time': Date.now()
1403
+ });
1404
+ break;
1405
+ }
1406
+ });
1281
1407
  node.gateway.on('sensor_mode', (d) => {
1282
1408
  node.set_status();
1283
1409
  node.send({topic: 'sensor_mode', payload: d, time: Date.now()});
@@ -1329,6 +1455,10 @@ module.exports = function(RED) {
1329
1455
  this.config_gateway = this.config_gateway_node.gateway;
1330
1456
  dedicated_config = true;
1331
1457
  }
1458
+ this.config_sync_listener;
1459
+
1460
+
1461
+
1332
1462
  // this.queue = new Queue(1);
1333
1463
  var node = this;
1334
1464
  var modes = {
@@ -1344,6 +1474,8 @@ module.exports = function(RED) {
1344
1474
  // OTN: {fill:"yellow",shape:"ring",text:"OTN Received, OTF Configuration Initiated"},
1345
1475
  // OFF: {fill:"green",shape:"dot",text:"OFF Recieved, OTF Configuration Completed"}
1346
1476
  FLY: {fill:"yellow",shape:"ring",text:"FLY"},
1477
+ INI: {fill:"yellow",shape:"ring",text:"Sync-Init"},
1478
+ END: {fill:"yellow",shape:"ring",text:"Sync-End"},
1347
1479
  OTN: {fill:"yellow",shape:"ring",text:"OTN Received, Config Entered"},
1348
1480
  OTF: {fill:"green",shape:"dot",text:"OTF Received, Config Complete"},
1349
1481
  UPTHWRN: {fill:"yellow",shape:"ring",text:"Threshold is low"}
@@ -1842,29 +1974,119 @@ module.exports = function(RED) {
1842
1974
  }
1843
1975
  break;
1844
1976
  case 33:
1845
- if(config.clear_counter_33){
1846
- promises.clear_counter_33 = node.config_gateway.config_set_clear_counter_33(mac);
1977
+ if(config.debounce_time_108_active){
1978
+ promises.debounce_time_33 = node.config_gateway.config_set_debounce_time_108(mac, parseInt(config.debounce_time_108));
1847
1979
  }
1848
1980
  if(config.input_two_33_active){
1849
- promises.input_two_33 = node.config_gateway.config_set_input_two_108(mac, parseInt(config.input_two_33));
1981
+ promises.input_detection_33 = node.config_gateway.config_set_input_two_108(mac, parseInt(config.input_two_33));
1982
+ }
1983
+ if(config.debounce_time_123_active){
1984
+ promises.debounce_time_2byte_33 = node.config_gateway.config_set_debounce_time_v10_108(mac, parseInt(config.debounce_time_123));
1850
1985
  }
1851
1986
  if(config.counter_threshold_108_active){
1852
- promises.counter_threshold_108 = node.config_gateway.config_set_counter_threshold_108(mac, parseInt(config.counter_threshold_108));
1987
+ promises.counter_threshold_33 = node.config_gateway.config_set_counter_threshold_108(mac, parseInt(config.counter_threshold_108));
1853
1988
  }
1854
- if(config.debounce_time_108_active){
1855
- promises.debounce_time_108 = node.config_gateway.config_set_debounce_time_108(mac, parseInt(config.debounce_time_108));
1989
+ if(config.transmission_interval_108_active){
1990
+ promises.transmission_interval_33 = node.config_gateway.config_set_transmission_interval_108(mac, parseInt(config.transmission_interval_108));
1856
1991
  }
1857
- if(config.push_notification_33_active){
1858
- promises.push_notification_33 = node.config_gateway.config_set_push_notification_33(mac, parseInt(config.push_notification_33));
1992
+ if(config.reset_mode_to_disabled_108_active){
1993
+ promises.reset_mode_33 = node.config_gateway.config_set_reset_mode_to_disabled_108(mac, parseInt(config.reset_mode_to_disabled_108));
1859
1994
  }
1860
- break;
1995
+ if(config.reset_timeout_108_active){
1996
+ promises.reset_timeout_33 = node.config_gateway.config_set_reset_timeout_108(mac, parseInt(config.reset_timeout_108));
1997
+ }
1998
+ if(config.shift_one_108_active){
1999
+ promises.shift_time1_33 = node.config_gateway.config_set_shift_one_108(mac, parseInt(config.shift_one_hours_108), parseInt(config.shift_one_minutes_108));
2000
+ }
2001
+ if(config.shift_two_108_active){
2002
+ promises.shift_time2_33 = node.config_gateway.config_set_shift_two_108(mac, parseInt(config.shift_two_hours_108), parseInt(config.shift_two_minutes_108));
2003
+ }
2004
+ if(config.shift_three_108_active){
2005
+ promises.shift_time3_33 = node.config_gateway.config_set_shift_three_108(mac, parseInt(config.shift_three_hours_108), parseInt(config.shift_three_minutes_108));
2006
+ }
2007
+ if(config.shift_four_108_active){
2008
+ promises.shift_time4_33 = node.config_gateway.config_set_shift_four_108(mac, parseInt(config.shift_four_hours_108), parseInt(config.shift_four_minutes_108));
2009
+ }
2010
+ if(config.quality_of_service_108_active){
2011
+ promises.quality_of_service_33 = node.config_gateway.config_set_quality_of_service_108(mac, parseInt(config.quality_of_service_108));
2012
+ }
2013
+ if(config.rtc_108){
2014
+ promises.rtc_33 = node.config_gateway.config_set_rtc_108(mac);
2015
+ }
2016
+ if(config.clear_timers_35){
2017
+ promises.clear_counters_33 = node.config_gateway.config_set_clear_timers_35(mac);
2018
+ }
2019
+ if(config.push_notification_35_active){
2020
+ promises.push_notification_33 = node.config_gateway.config_set_push_notification_108(mac, parseInt(config.push_notification_35));
2021
+ }
2022
+ if(config.interrupt_timeout_35_active){
2023
+ promises.interrupt_timeout_33 = node.config_gateway.config_set_interrupt_timeout_108(mac, parseInt(config.interrupt_timeout_35));
2024
+ }
2025
+ if(config.probe_one_126_active){
2026
+ promises.probe_one_33 = node.config_gateway.config_set_probe_one_ct_126(mac, parseInt(config.probe_one_126));
2027
+ }
2028
+ if(config.threshold_probe_one_126_active){
2029
+ promises.threshold_probe_one_33 = node.config_gateway.config_set_probe_one_current_threshold_126(mac, parseInt(config.threshold_probe_one_126));
2030
+ }
2031
+ break;
1861
2032
  case 35:
1862
2033
  if(config.counter_threshold_35_active){
1863
2034
  promises.config_set_counter_threshold_35 = node.config_gateway.config_set_counter_threshold_35(mac, parseInt(config.counter_threshold_35));
1864
2035
  }
2036
+ if(config.counter_threshold_35_gen2_active){
2037
+ promises.config_set_counter_threshold_35_gen2 = node.config_gateway.config_set_counter_threshold_108(mac, parseInt(config.counter_threshold_35_gen2));
2038
+ }
1865
2039
  if(config.debounce_time_2_active){
1866
2040
  promises.config_set_debounce_time_35 = node.config_gateway.config_set_debounce_time_35(mac, parseInt(config.debounce_time_2));
1867
2041
  }
2042
+ if(config.debounce_time_123_active){
2043
+ promises.debounce_time_35 = node.config_gateway.config_set_debounce_time_v10_108(mac, parseInt(config.debounce_time_123));
2044
+ }
2045
+ if(config.input_one_123_active){
2046
+ promises.input_one_35 = node.config_gateway.config_set_input_one_108(mac, parseInt(config.input_one_123));
2047
+ }
2048
+ if(config.counter_threshold_108_active){
2049
+ promises.counter_threshold_35 = node.config_gateway.config_set_counter_threshold_108(mac, parseInt(config.counter_threshold_108));
2050
+ }
2051
+ if(config.reset_timeout_108_active){
2052
+ promises.reset_timeout_35 = node.config_gateway.config_set_reset_timeout_108(mac, parseInt(config.reset_timeout_108));
2053
+ }
2054
+ if(config.reset_mode_to_disabled_108_active){
2055
+ promises.reset_mode_35 = node.config_gateway.config_set_reset_mode_to_disabled_108(mac, parseInt(config.reset_mode_to_disabled_108));
2056
+ }
2057
+ if(config.rtc_108){
2058
+ promises.rtc_35 = node.config_gateway.config_set_rtc_108(mac);
2059
+ }
2060
+ if(config.transmission_interval_108_active){
2061
+ promises.transmission_interval_35 = node.config_gateway.config_set_transmission_interval_108(mac, parseInt(config.transmission_interval_108));
2062
+ }
2063
+ if(config.shift_one_108_active){
2064
+ promises.shift_time1_35 = node.config_gateway.config_set_shift_one_108(mac, parseInt(config.shift_one_hours_108), parseInt(config.shift_one_minutes_108));
2065
+ }
2066
+ if(config.shift_two_108_active){
2067
+ promises.shift_time2_35 = node.config_gateway.config_set_shift_two_108(mac, parseInt(config.shift_two_hours_108), parseInt(config.shift_two_minutes_108));
2068
+ }
2069
+ if(config.shift_three_108_active){
2070
+ promises.shift_time3_35 = node.config_gateway.config_set_shift_three_108(mac, parseInt(config.shift_three_hours_108), parseInt(config.shift_three_minutes_108));
2071
+ }
2072
+ if(config.shift_four_108_active){
2073
+ promises.shift_time4_35 = node.config_gateway.config_set_shift_four_108(mac, parseInt(config.shift_four_hours_108), parseInt(config.shift_four_minutes_108));
2074
+ }
2075
+ if(config.quality_of_service_108_active){
2076
+ promises.quality_of_service_35 = node.config_gateway.config_set_quality_of_service_108(mac, parseInt(config.quality_of_service_108));
2077
+ }
2078
+ if(config.fly_interval_108_active){
2079
+ promises.fly_interval_35 = node.config_gateway.config_set_fly_interval_108(mac, parseInt(config.fly_interval_108));
2080
+ }
2081
+ if(config.clear_timers_35){
2082
+ promises.clear_timers_35 = node.config_gateway.config_set_clear_timers_35(mac);
2083
+ }
2084
+ if(config.push_notification_35_active){
2085
+ promises.push_notification_35 = node.config_gateway.config_set_push_notification_108(mac, parseInt(config.push_notification_35));
2086
+ }
2087
+ if(config.interrupt_timeout_35_active){
2088
+ promises.interrupt_timeout_35 = node.config_gateway.config_set_interrupt_timeout_108(mac, parseInt(config.interrupt_timeout_35));
2089
+ }
1868
2090
  break;
1869
2091
  case 36:
1870
2092
  if(config.counter_threshold_35_active){
@@ -2451,6 +2673,11 @@ module.exports = function(RED) {
2451
2673
  promises.sensor_boot_time_78 = node.config_gateway.config_set_sensor_boot_time_78(mac, parseInt(config.sensor_boot_time_78));
2452
2674
  }
2453
2675
  break;
2676
+ case 93:
2677
+ if(config.sensor_boot_time_78_active){
2678
+ promises.sensor_boot_time_93 = node.config_gateway.config_set_sensor_boot_time_78(mac, parseInt(config.sensor_boot_time_78));
2679
+ }
2680
+ break;
2454
2681
  case 95:
2455
2682
  if(config.sensor_boot_time_420ma_active){
2456
2683
  promises.sensor_boot_time_420ma = node.config_gateway.config_set_sensor_boot_time_420ma(mac, parseInt(config.sensor_boot_time_420ma));
@@ -2644,6 +2871,9 @@ module.exports = function(RED) {
2644
2871
  if(config.set_rtc_101){
2645
2872
  promises.set_rtc_103 = node.config_gateway.config_set_rtc_101(mac);
2646
2873
  }
2874
+ if(config.send_raw_on_motion_only_103_active){
2875
+ promises.send_raw_on_motion_only_103 = node.config_gateway.config_set_send_raw_on_motion_only_103(mac, parseInt(config.send_raw_on_motion_only_103));
2876
+ }
2647
2877
  break;
2648
2878
  case 105:
2649
2879
  if(config.sensor_boot_time_420ma_active){
@@ -3281,8 +3511,8 @@ module.exports = function(RED) {
3281
3511
  }
3282
3512
  break;
3283
3513
  case 123:
3284
- if(config.clear_timers_123_active){
3285
- promises.clear_timers_123 = node.config_gateway.config_set_clear_timers_108(mac, parseInt(config.clear_timers_123));
3514
+ if(config.clear_timers_123){
3515
+ promises.clear_timers_123 = node.config_gateway.config_set_clear_timers_108(mac, 7);
3286
3516
  }
3287
3517
  if(config.debounce_time_123_active){
3288
3518
  promises.debounce_time_123 = node.config_gateway.config_set_debounce_time_v10_108(mac, parseInt(config.debounce_time_123));
@@ -3368,6 +3598,150 @@ module.exports = function(RED) {
3368
3598
  promises.reset_all_totalizers_124 = node.config_gateway.config_set_reset_all_totalizers_124(mac);
3369
3599
  }
3370
3600
  break;
3601
+ case 125:
3602
+ if(config.clear_timers_123){
3603
+ promises.clear_timers_125 = node.config_gateway.config_set_clear_timers_108(mac, 7);
3604
+ }
3605
+ if(config.debounce_time_123_active){
3606
+ promises.debounce_time_125 = node.config_gateway.config_set_debounce_time_v10_108(mac, parseInt(config.debounce_time_123));
3607
+ }
3608
+ if(config.input_one_123_active){
3609
+ promises.input_one_125 = node.config_gateway.config_set_input_one_108(mac, parseInt(config.input_one_123));
3610
+ }
3611
+ if(config.input_two_123_active){
3612
+ promises.input_two_125 = node.config_gateway.config_set_input_two_108(mac, parseInt(config.input_two_123));
3613
+ }
3614
+ if(config.counter_threshold_108_active){
3615
+ promises.counter_threshold_125 = node.config_gateway.config_set_counter_threshold_108(mac, parseInt(config.counter_threshold_108));
3616
+ }
3617
+ if(config.push_notification_125_active){
3618
+ promises.push_notification_125 = node.config_gateway.config_set_push_notification_108(mac, parseInt(config.push_notification_125));
3619
+ }
3620
+ if(config.reset_timeout_108_active){
3621
+ promises.reset_timeout_125 = node.config_gateway.config_set_reset_timeout_108(mac, parseInt(config.reset_timeout_108));
3622
+ }
3623
+ if(config.reset_mode_to_disabled_108_active){
3624
+ promises.reset_mode_125 = node.config_gateway.config_set_reset_mode_to_disabled_108(mac, parseInt(config.reset_mode_to_disabled_108));
3625
+ }
3626
+ if(config.rtc_108){
3627
+ promises.rtc_125 = node.config_gateway.config_set_rtc_108(mac);
3628
+ }
3629
+ if(config.transmission_interval_108_active){
3630
+ promises.transmission_interval_125 = node.config_gateway.config_set_transmission_interval_108(mac, parseInt(config.transmission_interval_108));
3631
+ }
3632
+ if(config.shift_one_108_active){
3633
+ promises.shift_time1_125 = node.config_gateway.config_set_shift_one_108(mac, parseInt(config.shift_one_hours_108), parseInt(config.shift_one_minutes_108));
3634
+ }
3635
+ if(config.shift_two_108_active){
3636
+ promises.shift_time2_125 = node.config_gateway.config_set_shift_two_108(mac, parseInt(config.shift_two_hours_108), parseInt(config.shift_two_minutes_108));
3637
+ }
3638
+ if(config.shift_three_108_active){
3639
+ promises.shift_time3_125 = node.config_gateway.config_set_shift_three_108(mac, parseInt(config.shift_three_hours_108), parseInt(config.shift_three_minutes_108));
3640
+ }
3641
+ if(config.shift_four_108_active){
3642
+ promises.shift_time4_125 = node.config_gateway.config_set_shift_four_108(mac, parseInt(config.shift_four_hours_108), parseInt(config.shift_four_minutes_108));
3643
+ }
3644
+ if(config.quality_of_service_108_active){
3645
+ promises.quality_of_service_125 = node.config_gateway.config_set_quality_of_service_108(mac, parseInt(config.quality_of_service_108));
3646
+ }
3647
+ if(config.interrupt_timeout_108_active){
3648
+ promises.interrupt_timeout_125 = node.config_gateway.config_set_interrupt_timeout_108(mac, parseInt(config.interrupt_timeout_108));
3649
+ }
3650
+ if(config.probe_one_126_active){
3651
+ promises.probe_one_125 = node.config_gateway.config_set_probe_one_ct_126(mac, parseInt(config.probe_one_126));
3652
+ }
3653
+ if(config.probe_two_126_active){
3654
+ promises.probe_two_125 = node.config_gateway.config_set_probe_two_ct_126(mac, parseInt(config.probe_two_126));
3655
+ }
3656
+ if(config.threshold_probe_one_126_active){
3657
+ promises.threshold_probe_one_125 = node.config_gateway.config_set_probe_one_current_threshold_126(mac, parseInt(config.threshold_probe_one_126));
3658
+ }
3659
+ if(config.threshold_probe_two_126_active){
3660
+ promises.threshold_probe_two_125 = node.config_gateway.config_set_probe_two_current_threshold_126(mac, parseInt(config.threshold_probe_two_126));
3661
+ }
3662
+ break;
3663
+ case 126:
3664
+ if(config.clear_timers_123){
3665
+ promises.clear_timers_126 = node.config_gateway.config_set_clear_timers_108(mac, 7);
3666
+ }
3667
+ if(config.debounce_time_123_active){
3668
+ promises.debounce_time_126 = node.config_gateway.config_set_debounce_time_v10_108(mac, parseInt(config.debounce_time_123));
3669
+ }
3670
+ if(config.input_one_123_active){
3671
+ promises.input_one_126 = node.config_gateway.config_set_input_one_108(mac, parseInt(config.input_one_123));
3672
+ }
3673
+ if(config.input_two_123_active){
3674
+ promises.input_two_126 = node.config_gateway.config_set_input_two_108(mac, parseInt(config.input_two_123));
3675
+ }
3676
+ if(config.input_three_123_active){
3677
+ promises.input_three_126 = node.config_gateway.config_set_input_three_108(mac, parseInt(config.input_three_123));
3678
+ }
3679
+ if(config.counter_threshold_108_active){
3680
+ promises.counter_threshold_126 = node.config_gateway.config_set_counter_threshold_108(mac, parseInt(config.counter_threshold_108));
3681
+ }
3682
+ if(config.push_notification_123_active){
3683
+ promises.push_notification_126 = node.config_gateway.config_set_push_notification_108(mac, parseInt(config.push_notification_123));
3684
+ }
3685
+ if(config.reset_timeout_108_active){
3686
+ promises.reset_timeout_126 = node.config_gateway.config_set_reset_timeout_108(mac, parseInt(config.reset_timeout_108));
3687
+ }
3688
+ if(config.reset_mode_to_disabled_108_active){
3689
+ promises.reset_mode_126 = node.config_gateway.config_set_reset_mode_to_disabled_108(mac, parseInt(config.reset_mode_to_disabled_108));
3690
+ }
3691
+ if(config.rtc_108){
3692
+ promises.rtc_126 = node.config_gateway.config_set_rtc_108(mac);
3693
+ }
3694
+ if(config.transmission_interval_108_active){
3695
+ promises.transmission_interval_126 = node.config_gateway.config_set_transmission_interval_108(mac, parseInt(config.transmission_interval_108));
3696
+ }
3697
+ if(config.shift_one_108_active){
3698
+ promises.shift_time1_126 = node.config_gateway.config_set_shift_one_108(mac, parseInt(config.shift_one_hours_108), parseInt(config.shift_one_minutes_108));
3699
+ }
3700
+ if(config.shift_two_108_active){
3701
+ promises.shift_time2_126 = node.config_gateway.config_set_shift_two_108(mac, parseInt(config.shift_two_hours_108), parseInt(config.shift_two_minutes_108));
3702
+ }
3703
+ if(config.shift_three_108_active){
3704
+ promises.shift_time3_126 = node.config_gateway.config_set_shift_three_108(mac, parseInt(config.shift_three_hours_108), parseInt(config.shift_three_minutes_108));
3705
+ }
3706
+ if(config.shift_four_108_active){
3707
+ promises.shift_time4_126 = node.config_gateway.config_set_shift_four_108(mac, parseInt(config.shift_four_hours_108), parseInt(config.shift_four_minutes_108));
3708
+ }
3709
+ if(config.quality_of_service_108_active){
3710
+ promises.quality_of_service_126 = node.config_gateway.config_set_quality_of_service_108(mac, parseInt(config.quality_of_service_108));
3711
+ }
3712
+ if(config.interrupt_timeout_108_active){
3713
+ promises.interrupt_timeout_126 = node.config_gateway.config_set_interrupt_timeout_108(mac, parseInt(config.interrupt_timeout_108));
3714
+ }
3715
+ if(config.probe_one_126_active){
3716
+ promises.probe_one_126 = node.config_gateway.config_set_probe_one_ct_126(mac, parseInt(config.probe_one_126));
3717
+ }
3718
+ if(config.probe_two_126_active){
3719
+ promises.probe_two_126 = node.config_gateway.config_set_probe_two_ct_126(mac, parseInt(config.probe_two_126));
3720
+ }
3721
+ if(config.probe_three_126_active){
3722
+ promises.probe_three_126 = node.config_gateway.config_set_probe_three_ct_126(mac, parseInt(config.probe_three_126));
3723
+ }
3724
+ if(config.threshold_probe_one_126_active){
3725
+ promises.threshold_probe_one_126 = node.config_gateway.config_set_probe_one_current_threshold_126(mac, parseInt(config.threshold_probe_one_126));
3726
+ }
3727
+ if(config.threshold_probe_two_126_active){
3728
+ promises.threshold_probe_two_126 = node.config_gateway.config_set_probe_two_current_threshold_126(mac, parseInt(config.threshold_probe_two_126));
3729
+ }
3730
+ if(config.threshold_probe_three_126_active){
3731
+ promises.threshold_probe_three_126 = node.config_gateway.config_set_probe_three_current_threshold_126(mac, parseInt(config.threshold_probe_three_126));
3732
+ }
3733
+ break;
3734
+ case 128:
3735
+ if(config.adc_threshold_128_active){
3736
+ promises.adc_threshold_128 = node.config_gateway.config_set_adc_threshold_128(mac, parseInt(config.adc_threshold_128));
3737
+ }
3738
+ if(config.auto_check_interval_128_active){
3739
+ promises.auto_check_interval_128 = node.config_gateway.config_set_auto_check_interval_128(mac, parseInt(config.auto_check_interval_128));
3740
+ }
3741
+ if(config.auto_calibration_128){
3742
+ promises.auto_calibration_128 = node.config_gateway.config_set_auto_calibration_128(mac);
3743
+ }
3744
+ break;
3371
3745
  case 180:
3372
3746
  if(config.output_data_rate_101_active){
3373
3747
  promises.output_data_rate_101 = node.config_gateway.config_set_output_data_rate_101(mac, parseInt(config.output_data_rate_101));
@@ -4033,8 +4407,6 @@ module.exports = function(RED) {
4033
4407
  (function(name){
4034
4408
  promises[name].then((f) => {
4035
4409
  if(name != 'finish'){
4036
- // console.log('IN PROMISE RESOLVE');
4037
- // console.log(f);
4038
4410
  // success[name] = true;
4039
4411
  if(Object.hasOwn(f, 'result')){
4040
4412
  switch(f.result){
@@ -4070,6 +4442,152 @@ module.exports = function(RED) {
4070
4442
  });
4071
4443
  }
4072
4444
  node._sensor_config = _config;
4445
+ let sync_topic = '';
4446
+ if(Object.hasOwn(config, 'addr') && config.addr != ''){
4447
+ sync_topic = 'sync-'+config.addr;
4448
+ }else{
4449
+ sync_topic = 'sync';
4450
+ }
4451
+
4452
+ this.pgm_on(sync_topic, (data) => {
4453
+ if(data.sensor_type == config.sensor_type){
4454
+ let message = {
4455
+ topic: 'sync',
4456
+ type: data.payload.type,
4457
+ ...data,
4458
+ time: Date.now()
4459
+ };
4460
+
4461
+ // switch(data.type){
4462
+ // case 'sync_check_in':
4463
+ // break;
4464
+ // case 'sync_init':
4465
+ // break;
4466
+ // case 'sync_acknowledgment':
4467
+ // break;
4468
+ // case 'sync_acknowledgment_error':
4469
+ // break;
4470
+ // case 'sync_end':
4471
+ // break;
4472
+ // default:
4473
+ // console.log('Default in device node sync');
4474
+ // console.log(data.type);
4475
+ // }
4476
+ if(data.type == 'sync_check_in' || data.type == 'manual_sync_check_in'){
4477
+ if(config.auto_config && config.on_the_fly_enable || data.type == 'manual_sync_check_in' && config.auto_config){
4478
+ if(Object.hasOwn(this.gateway_node.sensor_configs, data.payload.address) && !Object.hasOwn(this.gateway_node.sensor_configs[data.payload.address], 'api_config_override')){
4479
+ const html_map = this.config_gateway.get_intended_wireless_node_configs(data, config);
4480
+
4481
+ console.log(html_map);
4482
+
4483
+ let update_flag = false;
4484
+ for(let key in html_map){
4485
+ if(html_map[key].html_value != node.gateway_node.sensor_configs[data.payload.address].reported_configs[key]){
4486
+ // console.log('Comparing ' + html_map[key].html_value + ' to ' + node.gateway_node.sensor_configs[data.payload.address].reported_configs[key]);
4487
+ // console.log('Value is different, updating config for ' + key);
4488
+ update_flag = true;
4489
+ break;
4490
+ }
4491
+ }
4492
+ if(update_flag){
4493
+ promises = {};
4494
+ setTimeout(() => {
4495
+ let msg = {
4496
+ values: {},
4497
+ pass: {},
4498
+ status: 'Configuring'
4499
+ };
4500
+ var tout = setTimeout(() => {
4501
+ console.log('Sync Request Timed Out');
4502
+ }, 20000);
4503
+ promises.send_sync_configs = this.config_gateway.send_sync_config_wireless_node(data, html_map, node.gateway_node.sensor_configs[data.payload.address]);
4504
+
4505
+ promises.finish = new Promise((fulfill, reject) => {
4506
+ node.config_gateway.queue.add(() => {
4507
+ return new Promise((f, r) => {
4508
+ clearTimeout(tout);
4509
+ node.status(modes.FLY);
4510
+ fulfill();
4511
+ f();
4512
+ });
4513
+ });
4514
+ });
4515
+ for(var i in promises){
4516
+ (function(name){
4517
+ promises[name].then((f) => {
4518
+ if(name != 'finish'){
4519
+ let fail_flag = false;
4520
+ for(const key in html_map){
4521
+ if(Object.hasOwn(f.payload.machine_values, key) && f.payload.machine_values[key] == html_map[key].html_value){
4522
+ msg.pass[key] = true;
4523
+ msg.values[key] = f.payload.human_readable[key];
4524
+ }else{
4525
+ msg.pass[key] = false;
4526
+ msg.values[key] = f.payload[key];
4527
+ fail_flag = true;
4528
+ }
4529
+ }
4530
+ if(fail_flag){
4531
+ msg.status = 'Error';
4532
+ }else{
4533
+ msg.status = 'Success';
4534
+ }
4535
+ }
4536
+ else{
4537
+ node.send({
4538
+ topic: 'sync',
4539
+ type: 'sync_response',
4540
+ address: data.address,
4541
+ sensor_type: data.sensor_type,
4542
+ payload: {
4543
+ address: data.address,
4544
+ sensor_type: data.sensor_type,
4545
+ ...msg
4546
+ },
4547
+ time: Date.now()
4548
+ });
4549
+ // top_fulfill(msg);
4550
+ }
4551
+ }).catch((err) => {
4552
+ msg[name] = err;
4553
+ });
4554
+ })(i);
4555
+ }
4556
+ });
4557
+ } else if(config.sensor_type == 101 || config.sensor_type == 102 || config.sensor_type == 103 || config.sensor_type == 202){
4558
+ // type 101, 102, 103, 202 sensors broadcast handling. Requires setting RTC every checkin although we can skip it if there are configs
4559
+ if(this.gateway.hasOwnProperty('fly_101_in_progress') && this.gateway.fly_101_in_progress == false || !this.gateway.hasOwnProperty('fly_101_in_progress')){
4560
+ this.gateway.fly_101_in_progress = true;
4561
+ node.warn('Starting RTC Timer ' + Date.now());
4562
+ node.warn('Sensor checked in for RTC: ' + data.payload.address + ' at ' + Date.now());
4563
+ var broadcast_tout = setTimeout(() => {
4564
+ node.warn('Sending RTC Broadcast ' + Date.now());
4565
+ _broadcast_rtc(data);
4566
+ }, 2000);
4567
+ }else{
4568
+ node.warn('Sensor checked in for RTC: ' + data.payload.address + ' at ' + Date.now());
4569
+ }
4570
+ } else{
4571
+ // console.log('No Config Differences Detected, Skipping Sync Configs');
4572
+ node.send({
4573
+ topic: 'sync',
4574
+ type: 'sync_response',
4575
+ address: data.address,
4576
+ sensor_type: data.sensor_type,
4577
+ payload: {
4578
+ address: data.address,
4579
+ sensor_type: data.sensor_type,
4580
+ info: "Reported configurations match desired configurations. Skipping Sync."
4581
+ },
4582
+ time: Date.now()
4583
+ });
4584
+ }
4585
+ }
4586
+ }
4587
+ }
4588
+ node.send(message);
4589
+ }
4590
+ });
4073
4591
  if(config.addr){
4074
4592
  config.addr = config.addr.toLowerCase();
4075
4593
 
@@ -4132,6 +4650,7 @@ module.exports = function(RED) {
4132
4650
  });
4133
4651
  }
4134
4652
  });
4653
+
4135
4654
  this.pgm_on('sensor_mode-'+config.addr, (sensor) => {
4136
4655
  if(sensor.mode in modes){
4137
4656
  node.status(modes[sensor.mode]);
@@ -4141,7 +4660,7 @@ module.exports = function(RED) {
4141
4660
  }
4142
4661
  if(config.auto_config && sensor.mode == "PGM"){
4143
4662
  _config(sensor);
4144
- }else if(config.auto_config && config.on_the_fly_enable && sensor.mode == "FLY"){
4663
+ }else if(config.auto_config && config.on_the_fly_enable && sensor.mode == "FLY" && !Object.hasOwn(sensor, 'sync')){
4145
4664
  // _send_otn_request(sensor);
4146
4665
  // Sensors having issues seeing OTN request sent too quickly
4147
4666
  // Added timeout to fix issue
@@ -4155,11 +4674,11 @@ module.exports = function(RED) {
4155
4674
  }else{
4156
4675
  node.send({topic: 'warning', payload: {'warning': 'Wireless Device Node configurations overridden by API Configuration'}, addr: sensor.mac, time: Date.now()});
4157
4676
  }
4158
- }else if(config.auto_config && config.on_the_fly_enable && sensor.mode == "OTN"){
4159
- if(config.sensor_type == 101 || config.sensor_type == 102 || config.sensor_type == 202){
4677
+ }else if(config.auto_config && config.on_the_fly_enable && sensor.mode == "OTN" && !Object.hasOwn(sensor, 'sync')){
4678
+ if(config.sensor_type == 101 || config.sensor_type == 102 || config.sensor_type == 103 || config.sensor_type == 202){
4160
4679
  if(this.gateway.hasOwnProperty('fly_101_in_progress') && this.gateway.fly_101_in_progress == false || !this.gateway.hasOwnProperty('fly_101_in_progress')){
4161
4680
  this.gateway.fly_101_in_progress = true;
4162
- node.warn('Starting RTC Timer' + Date.now());
4681
+ node.warn('Starting RTC Timer ' + Date.now());
4163
4682
  node.warn('Sensor checked in for RTC: ' + sensor.mac + ' at ' + Date.now());
4164
4683
  var broadcast_tout = setTimeout(() => {
4165
4684
  node.warn('Sending RTC Broadcast ' + Date.now());
@@ -4181,10 +4700,10 @@ module.exports = function(RED) {
4181
4700
  }else{
4182
4701
  node.send({topic: 'warning', payload: {'warning': 'Wireless Device Node configurations overridden by API Configuration'}, addr: sensor.mac, time: Date.now()});
4183
4702
  };
4184
- } else if(config.sensor_type == 101 && sensor.mode == "FLY" || config.sensor_type == 102 && sensor.mode == "FLY" || config.sensor_type == 202 && sensor.mode == "FLY"){
4703
+ } else if(config.sensor_type == 101 && sensor.mode == "FLY" && !Object.hasOwn(sensor, 'sync') || config.sensor_type == 102 && sensor.mode == "FLY" && !Object.hasOwn(sensor, 'sync') || config.sensor_type == 103 && sensor.mode == "FLY" && !Object.hasOwn(sensor, 'sync') || config.sensor_type == 202 && sensor.mode == "FLY" && !Object.hasOwn(sensor, 'sync')){
4185
4704
  if(this.gateway.hasOwnProperty('fly_101_in_progress') && this.gateway.fly_101_in_progress == false || !this.gateway.hasOwnProperty('fly_101_in_progress')){
4186
4705
  this.gateway.fly_101_in_progress = true;
4187
- node.warn('Starting RTC Timer' + Date.now());
4706
+ node.warn('Starting RTC Timer ' + Date.now());
4188
4707
  node.warn('Sensor checked in for RTC: ' + sensor.mac + ' at ' + Date.now());
4189
4708
  var broadcast_tout = setTimeout(() => {
4190
4709
  node.warn('Sending RTC Broadcast ' + Date.now());
@@ -4257,7 +4776,7 @@ module.exports = function(RED) {
4257
4776
  }
4258
4777
  if(config.auto_config && sensor.mode == 'PGM'){
4259
4778
  _config(sensor);
4260
- }else if(config.auto_config && config.on_the_fly_enable && sensor.mode == "FLY"){
4779
+ }else if(config.auto_config && config.on_the_fly_enable && sensor.mode == "FLY" && !Object.hasOwn(sensor, 'sync')){
4261
4780
  // _send_otn_request(sensor);
4262
4781
  // Sensors having issues seeing OTN request sent too quickly
4263
4782
  // Added timeout to fix issue
@@ -4271,8 +4790,8 @@ module.exports = function(RED) {
4271
4790
  }else{
4272
4791
  node.send({topic: 'warning', payload: {'warning': 'Wireless Device Node configurations overridden by API Configuration'}, addr: sensor.mac, time: Date.now()});
4273
4792
  }
4274
- }else if(config.auto_config && config.on_the_fly_enable && sensor.mode == "OTN"){
4275
- if(config.sensor_type == 101 || config.sensor_type == 102 || config.sensor_type == 202){
4793
+ }else if(config.auto_config && config.on_the_fly_enable && sensor.mode == "OTN" && !Object.hasOwn(sensor, 'sync')){
4794
+ if(config.sensor_type == 101 || config.sensor_type == 102 || config.sensor_type == 103|| config.sensor_type == 202){
4276
4795
  if(this.gateway.hasOwnProperty('fly_101_in_progress') && this.gateway.fly_101_in_progress == false || !this.gateway.hasOwnProperty('fly_101_in_progress')){
4277
4796
  this.gateway.fly_101_in_progress = true;
4278
4797
  node.warn('Starting RTC Timer' + Date.now());
@@ -4299,10 +4818,10 @@ module.exports = function(RED) {
4299
4818
  node.send({topic: 'warning', payload: {'warning': 'Wireless Device Node configurations overridden by API Configuration'}, addr: sensor.mac, time: Date.now()});
4300
4819
  };
4301
4820
 
4302
- }else if(sensor.mode == "FLY" && config.sensor_type == 101 || sensor.mode == "FLY" && config.sensor_type == 102 || sensor.mode == "FLY" && config.sensor_type == 202){
4821
+ }else if(sensor.mode == "FLY" && config.sensor_type == 101 && !Object.hasOwn(sensor, 'sync') || sensor.mode == "FLY" && config.sensor_type == 102 && !Object.hasOwn(sensor, 'sync') || sensor.mode == "FLY" && config.sensor_type == 103 && !Object.hasOwn(sensor, 'sync') || sensor.mode == "FLY" && config.sensor_type == 202 && !Object.hasOwn(sensor, 'sync')){
4303
4822
  if(this.gateway.hasOwnProperty('fly_101_in_progress') && this.gateway.fly_101_in_progress == false || !this.gateway.hasOwnProperty('fly_101_in_progress')){
4304
4823
  this.gateway.fly_101_in_progress = true;
4305
- node.warn('Starting RTC Timer' + Date.now());
4824
+ node.warn('Starting RTC Timer ' + Date.now());
4306
4825
  node.warn('Sensor checked in for RTC: ' + sensor.mac + ' at ' + Date.now());
4307
4826
  var broadcast_tout = setTimeout(() => {
4308
4827
  node.warn('Sending RTC Broadcast ' + Date.now());
@@ -4513,15 +5032,17 @@ module.exports = function(RED) {
4513
5032
  break;
4514
5033
  case 'sensor_config_options':
4515
5034
  msg.request = msg.payload;
4516
- // TODO move logic to function
4517
- if(typeof msg.payload == 'number' && Object.hasOwn(node._gateway_node.sensor_type_map, msg.payload)){
4518
- let options = this.get_config_options(msg.payload);
5035
+ // console.log(typeof msg.payload == 'object');
5036
+ // console.log(Object.hasOwn(msg.payload, 'sensor_type'));
5037
+ // console.log(Object.hasOwn(this.gateway.sensor_libs, msg.payload.sensor_type));
5038
+ if(typeof msg.payload == 'object' && Object.hasOwn(msg.payload, 'sensor_type') && Object.hasOwn(this.gateway.sensor_libs, msg.payload.sensor_type)){
5039
+ let options = this.get_config_options(msg.payload.sensor_type, msg.payload.firmware_version);
4519
5040
  msg.payload = options;
4520
5041
  msg.status = 200;
4521
5042
  msg.time = Date.now();
4522
5043
  send(msg);
4523
5044
  }else{
4524
- msg.payload = {error: 'get_config_options error: Payload must be a valid sensor type number'};
5045
+ msg.payload = {error: 'get_config_options error: msg.payload must be object with sensor_type property corresponding to a valid sensor type number. e.g. {sensor_type: 114}. Optionally pass in firmware_version property to get options for specific firmware version e.g. {sensor_type: 114, firmware_version: 2}.'};
4525
5046
  msg.status = 500;
4526
5047
  msg.time = Date.now();
4527
5048
  send(msg);
@@ -4582,48 +5103,36 @@ module.exports = function(RED) {
4582
5103
  return response;
4583
5104
  };
4584
5105
 
4585
- this.get_config_options = function(sensor_type){
4586
- if(Object.hasOwn(node._gateway_node.sensor_type_map, sensor_type)){
4587
- const config_list = node._gateway_node.sensor_type_map[sensor_type].configs;
5106
+ this.get_config_options = function(sensor_type, firmware_version = 1){
5107
+ if(Object.hasOwn(this.gateway.sensor_libs, sensor_type)){
5108
+ const config_list = this.gateway.sensor_libs[sensor_type].get_config_map(firmware_version);
4588
5109
  let response = {};
4589
5110
  for (const key in config_list){
4590
- const info = node._gateway_node.configuration_map[config_list[key]];
5111
+ const info = config_list[key];
4591
5112
  // If sensors actually reports and supports the listed config
4592
- if(Object.hasOwn(info, 'fly_not_reported') && info.fly_not_reported.includes(sensor_type)){
4593
- continue;
4594
- }
4595
- response[key] = {
4596
- title: info.title,
4597
- default_value: info.default_value,
4598
- };
4599
- if(Object.hasOwn(info, 'main_caption')){
4600
- response[key].description = info.main_caption;
4601
- }
4602
- if(Object.hasOwn(info, 'validator')){
4603
- if(Object.hasOwn(info.validator, 'generated')){
4604
- delete info.validator.generated;
5113
+ // console.log(key);
5114
+ // console.log(info);
5115
+ if(Object.hasOwn(info, 'write_index') && !Object.hasOwn(info, 'read_only')){
5116
+ response[key] = {
5117
+ title: info.descriptions.title,
5118
+ default_value: info.default_value,
5119
+ };
5120
+ if(Object.hasOwn(info.descriptions, 'main_caption')){
5121
+ response[key].description = info.descriptions.main_caption;
5122
+ }
5123
+ if(Object.hasOwn(info.descriptions, 'sub_caption')){
5124
+ response[key].description = info.descriptions.main_caption;
5125
+ }
5126
+ if(Object.hasOwn(info, 'validator')){
5127
+ if(Object.hasOwn(info.validator, 'generated')){
5128
+ delete info.validator.generated;
5129
+ }
5130
+ response[key] = {...response[key], ...info.validator};
5131
+ }
5132
+ if(Object.hasOwn(info, 'options')){
5133
+ response[key].options = info.options;
4605
5134
  }
4606
- response[key] = {...response[key], ...info.validator};
4607
- }
4608
- if(Object.hasOwn(info, 'options')){
4609
- response[key].options = info.options;
4610
5135
  }
4611
-
4612
- // if(Object.hasOwn(info, 'validator') && Object.hasOwn(info.validator, 'type')){
4613
-
4614
- // response[key].value_type = info.validator.type;
4615
- // }
4616
- // if(Object.hasOwn(info, 'options')){
4617
- // response[key].options = info.options;
4618
- // }else{
4619
- // if(Object.hasOwn(info, 'validator') && Object.hasOwn(info.validator, 'min')){
4620
- // response[key].minimum_value = info.validator.min;
4621
- // }
4622
- // if(Object.hasOwn(info, 'validator') && Object.hasOwn(info.validator, 'max')){
4623
- // response[key].maximum_value = info.validator.max;
4624
- // }
4625
- // }
4626
- // node.warn(info);
4627
5136
  }
4628
5137
  return response;
4629
5138
  }else{
@@ -4636,12 +5145,96 @@ module.exports = function(RED) {
4636
5145
  var response = {};
4637
5146
  let error_msg = {};
4638
5147
  let warning_msg = {};
5148
+
4639
5149
  if(desired_configs == null || typeof desired_configs == 'undefined'){
4640
5150
  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}}]'};
4641
5151
  }else if(!Array.isArray(desired_configs) || desired_configs.length == 0){
4642
5152
  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}}]'};
4643
5153
  }
5154
+
5155
+ const build_error = (sensor, key, detail) => {
5156
+ return {
5157
+ type: 'error',
5158
+ message: `${detail.text} for ${key} on sensor ${sensor.addr}.`,
5159
+ detail: {
5160
+ config_name: key,
5161
+ ...detail // Merges everything: received, help, expected, etc.
5162
+ }
5163
+ };
5164
+ };
5165
+
5166
+ const build_warning = (sensor, key, detail) => {
5167
+ return {
5168
+ type: 'warning',
5169
+ message: `${detail.text} for ${key} on sensor ${sensor.addr}.`,
5170
+ detail: {
5171
+ config_name: key,
5172
+ ...detail,
5173
+ help: detail.help || "This warning does not prevent the configuration from going through."
5174
+ }
5175
+ };
5176
+ };
5177
+
5178
+ const validation_methods = {
5179
+ // HEX: Strict, no colons allowed
5180
+ hex: (val, validator, configName, sensor) => {
5181
+ const strVal = String(val);
5182
+ if (!/^[0-9a-f]+$/i.test(strVal)) {
5183
+ return { error: this.build_error(sensor, configName, val, "Invalid hex string", "Hex string should only contain 0-9 and a-f.") };
5184
+ }
5185
+ if (validator.length && strVal.length !== validator.length) {
5186
+ return { error: this.build_error(sensor, configName, val, "Invalid length for hex string", `Expected ${validator.length}, got ${strVal.length}`) };
5187
+ }
5188
+
5189
+ const result = { value: strVal.toLowerCase() };
5190
+ if (!validator.length) {
5191
+ result.warning = this.build_warning(sensor, configName, "Missing length validator", "hexString");
5192
+ }
5193
+ return result;
5194
+ },
5195
+
5196
+ // MAC: Flexible, strips colons first
5197
+ mac: (val, validator, configName, sensor) => {
5198
+ const clean = String(val).replace(/:/g, '');
5199
+ if (!/^[0-9a-f]+$/i.test(clean)) {
5200
+ return { error: this.build_error(sensor, configName, val, "Invalid MAC format", "MAC string should only contain 0-9, a-f, and colons.") };
5201
+ }
5202
+ if (validator.length && clean.length !== validator.length) {
5203
+ return { error: this.build_error(sensor, configName, clean, "Invalid length for MAC", `Expected ${validator.length}, got ${clean.length}`) };
5204
+ }
5205
+ return { value: clean.toLowerCase() };
5206
+ },
5207
+
5208
+ // NUMBERS: uint8, uint16be, uint32be
5209
+ number: (val, validator, configName, sensor, options) => {
5210
+ const num = parseInt(val);
5211
+ if (isNaN(num)) {
5212
+ return { error: this.build_error(sensor, configName, val, "Integer value expected", "Ensure the value is a valid number.") };
5213
+ }
5214
+
5215
+ // Check Min/Max
5216
+ if (validator.min !== undefined && num < validator.min) {
5217
+ return { error: this.build_error(sensor, configName, num, "Value below minimum", `Min allowed: ${validator.min}`) };
5218
+ }
5219
+ if (validator.max !== undefined && num > validator.max) {
5220
+ return { error: this.build_error(sensor, configName, num, "Value above maximum", `Max allowed: ${validator.max}`) };
5221
+ }
5222
+
5223
+ // Check Options
5224
+ if (options && !Object.keys(options).includes(String(num))) {
5225
+ return { error: this.build_error(sensor, configName, num, "Invalid option", `Valid options: ${JSON.stringify(options)}`) };
5226
+ }
5227
+
5228
+ const result = { value: num };
5229
+ if (validator.min === undefined || validator.max === undefined) {
5230
+ result.warning = this.build_warning(sensor, configName, "Missing min/max validator", "number");
5231
+ }
5232
+ return result;
5233
+ }
5234
+ };
5235
+
4644
5236
  for (const sensor of desired_configs){
5237
+
4645
5238
  // TODO validate sensor object
4646
5239
  // must have sensor.addr, sensor.type, sensor.configs
4647
5240
  // sensor.configs is an object with key value pairs of config name and desired value
@@ -4674,6 +5267,21 @@ module.exports = function(RED) {
4674
5267
  // 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}}]';
4675
5268
  continue;
4676
5269
  }
5270
+ if(!Object.hasOwn(node.gateway.sensor_libs, sensor.type)){
5271
+ error_msg.syntax_error ||= {};
5272
+ error_msg.syntax_error = {
5273
+ type: 'error',
5274
+ message: `Invalid sensor type ${sensor.type} for sensor ${sensor.addr}. See detail.valid_sensor in this message for a list of valid sensors.`,
5275
+ detail: {
5276
+ "text": "Invlalid sensor type",
5277
+ "received": sensor.type,
5278
+ "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}}]",
5279
+ "valid_sensors": Object.keys(node.gateway.sensor_libs)
5280
+ }
5281
+ };
5282
+ continue;
5283
+ }
5284
+
4677
5285
  // Force lowercase for consistency
4678
5286
  sensor.addr = sensor.addr.toLowerCase();
4679
5287
  // force configs values lowercase for consistency
@@ -4698,31 +5306,20 @@ module.exports = function(RED) {
4698
5306
  node._gateway_node.sensor_configs[sensor.addr].type = sensor.type;
4699
5307
  _requires_file_update = true;
4700
5308
  }
5309
+
5310
+ const config_map = node.gateway.sensor_libs[sensor.type].get_config_map();
5311
+
4701
5312
  // Validate configs
4702
5313
  for (const config_name in sensor.configs){
4703
5314
  // Check config is valid for sensor type
4704
- 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)){
4705
- let map_key = node._gateway_node.sensor_type_map[sensor.type].configs[config_name];
4706
- if(Object.hasOwn(node._gateway_node.configuration_map, map_key) && Object.hasOwn(node._gateway_node.configuration_map[map_key], 'validator')){
4707
- // Check if config is reported by sensor
4708
- 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)){
4709
- error_msg[sensor.addr] ||= {};
4710
- error_msg[sensor.addr][config_name] = {
4711
- type: 'error',
4712
- 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.`,
4713
- detail: {
4714
- "text": "Configuration not supported due to reported configurations",
4715
- "sensor_type": sensor.type,
4716
- "config_name": config_name,
4717
- "help": `Inject this message into the configuration node to get valid configuration values: {topic: 'sensor_config_options', payload: ${sensor.type}}`
4718
- }
4719
- };
4720
- delete sensor.configs[config_name];
4721
- continue;
4722
- }
4723
- if(Object.hasOwn(node._gateway_node.configuration_map[map_key].validator, 'type')){
4724
- switch(node._gateway_node.configuration_map[map_key].validator.type){
4725
- case 'hexString':
5315
+ if(Object.hasOwn(config_map, config_name)){
5316
+ let map_key = config_map[config_name];
5317
+ if(Object.hasOwn(map_key, 'validator')){
5318
+ if(Object.hasOwn(map_key.validator, 'type')){
5319
+
5320
+ // TODO replace with validation_methods object approach for cleaner code
5321
+ switch(map_key.validator.type){
5322
+ case 'hex':
4726
5323
  // Remove colons from string
4727
5324
  sensor.configs[config_name] = sensor.configs[config_name].replace(/:/g, '');
4728
5325
 
@@ -4743,7 +5340,7 @@ module.exports = function(RED) {
4743
5340
  continue;
4744
5341
  }
4745
5342
  // Invalid length error
4746
- 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){
5343
+ if(Object.hasOwn(map_key.validator, 'length') && sensor.configs[config_name].length != map_key.validator.length){
4747
5344
  error_msg[sensor.addr] ||= {};
4748
5345
  error_msg[sensor.addr][config_name] = {
4749
5346
  type: 'error',
@@ -4759,7 +5356,57 @@ module.exports = function(RED) {
4759
5356
  continue;
4760
5357
  }
4761
5358
  // Warning for missing length validator
4762
- if (!Object.hasOwn(node._gateway_node.configuration_map[map_key].validator, 'length')) {
5359
+ if (!Object.hasOwn(map_key.validator, 'length')) {
5360
+ warning_msg[sensor.addr] ||= {};
5361
+ warning_msg[sensor.addr][config_name] = {
5362
+ type: 'warning',
5363
+ message: `No length validator defined for hex string ${config_name} for sensor ${sensor.addr}.`,
5364
+ detail: {
5365
+ "text": "Missing length validator",
5366
+ "type": "hexString",
5367
+ "help": "This warning does not prevent the configuration from going through."
5368
+ }
5369
+ };
5370
+ }
5371
+ break;
5372
+ case 'mac':
5373
+ // Remove colons from string
5374
+ sensor.configs[config_name] = sensor.configs[config_name].replace(/:/g, '');
5375
+
5376
+ // Invalid hex string error
5377
+ if (!/^[0-9a-f]+$/.test(sensor.configs[config_name])) {
5378
+ error_msg[sensor.addr] ||= {};
5379
+ error_msg[sensor.addr][config_name] = {
5380
+ type: 'error',
5381
+ message: `Invalid hex string for configuration ${config_name} for sensor ${sensor.addr}. Received value: ${sensor.configs[config_name]}`,
5382
+ detail: {
5383
+ "text": "Invalid hex string",
5384
+ "regex_validator": "/^[0-9a-f]+$/",
5385
+ "received": sensor.configs[config_name],
5386
+ "help": "Hex string should only contain characters 0-9 and a-f. Colons are removed during validation."
5387
+ }
5388
+ };
5389
+ delete sensor.configs[config_name];
5390
+ continue;
5391
+ }
5392
+ // Invalid length error
5393
+ if(Object.hasOwn(map_key.validator, 'length') && sensor.configs[config_name].length != map_key.validator.length){
5394
+ error_msg[sensor.addr] ||= {};
5395
+ error_msg[sensor.addr][config_name] = {
5396
+ type: 'error',
5397
+ 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}`,
5398
+ detail: {
5399
+ "text": "Invalid length for hex string",
5400
+ "expected_length": node._gateway_node.configuration_map[map_key].validator.length,
5401
+ "received_length": sensor.configs[config_name].length,
5402
+ "help": "Add leading or trailing zeros to meet length requirement."
5403
+ }
5404
+ };
5405
+ delete sensor.configs[config_name];
5406
+ continue;
5407
+ }
5408
+ // Warning for missing length validator
5409
+ if (!Object.hasOwn(map_key.validator, 'length')) {
4763
5410
  warning_msg[sensor.addr] ||= {};
4764
5411
  warning_msg[sensor.addr][config_name] = {
4765
5412
  type: 'warning',
@@ -4794,14 +5441,14 @@ module.exports = function(RED) {
4794
5441
  continue;
4795
5442
  }
4796
5443
  // Value below minimum error
4797
- 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){
5444
+ if(Object.hasOwn(map_key.validator, 'min') && sensor.configs[config_name] < map_key.validator.min){
4798
5445
  error_msg[sensor.addr] ||= {};
4799
5446
  error_msg[sensor.addr][config_name] = {
4800
5447
  type: 'error',
4801
5448
  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}`,
4802
5449
  detail: {
4803
5450
  "text": "Value below minimum",
4804
- "min_allowed": node._gateway_node.configuration_map[map_key].validator.min,
5451
+ "min_allowed": map_key.validator.min,
4805
5452
  "received": sensor.configs[config_name],
4806
5453
  "help": `Inject this message into the configuration node to get valid configuration values: {topic: 'sensor_config_options', payload: ${sensor.type}}`
4807
5454
  }
@@ -4811,14 +5458,14 @@ module.exports = function(RED) {
4811
5458
  // 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}`};
4812
5459
  }
4813
5460
  // Value above maximum error
4814
- 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){
5461
+ if(Object.hasOwn(map_key.validator, 'max') && sensor.configs[config_name] > map_key.validator.max){
4815
5462
  error_msg[sensor.addr] ||= {};
4816
5463
  error_msg[sensor.addr][config_name] = {
4817
5464
  type: 'error',
4818
5465
  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}`,
4819
5466
  detail: {
4820
5467
  "text": "Value above maximum",
4821
- "max_allowed": node._gateway_node.configuration_map[map_key].validator.max,
5468
+ "max_allowed": map_key.validator.max,
4822
5469
  "received": sensor.configs[config_name],
4823
5470
  "help": `Inject this message into the configuration node to get valid configuration values: {topic: 'sensor_config_options', payload: ${sensor.type}}`
4824
5471
  }
@@ -4828,7 +5475,7 @@ module.exports = function(RED) {
4828
5475
  // 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}`};
4829
5476
  }
4830
5477
  // Warning for missing min/max validators
4831
- if(!Object.hasOwn(node._gateway_node.configuration_map[map_key].validator, 'min') || !Object.hasOwn(node._gateway_node.configuration_map[map_key].validator, 'max')){
5478
+ if(!Object.hasOwn(map_key.validator, 'min') || !Object.hasOwn(map_key.validator, 'max')){
4832
5479
  warning_msg[sensor.addr] ||= {};
4833
5480
  warning_msg[sensor.addr][config_name] = {
4834
5481
  type: 'warning',
@@ -4843,15 +5490,15 @@ module.exports = function(RED) {
4843
5490
  }
4844
5491
  // If options exist, value must be one of the options
4845
5492
  // This is assuming that all configs with options are number types
4846
- if(Object.hasOwn(node._gateway_node.configuration_map[map_key], 'options')){
4847
- if(!Object.keys(node._gateway_node.configuration_map[map_key].options).includes(String(sensor.configs[config_name]))){
5493
+ if(Object.hasOwn(map_key, 'options')){
5494
+ if(!Object.keys(map_key.options).includes(String(sensor.configs[config_name]))){
4848
5495
  error_msg[sensor.addr] ||= {};
4849
5496
  error_msg[sensor.addr][config_name] = {
4850
5497
  type: 'error',
4851
5498
  message: `Invalid option for configuration ${config_name} for sensor ${sensor.addr}. Received value: ${sensor.configs[config_name]}`,
4852
5499
  detail: {
4853
5500
  "text": "Invalid option",
4854
- "valid_options": node._gateway_node.configuration_map[map_key].options,
5501
+ "valid_options": map_key.options,
4855
5502
  "received": sensor.configs[config_name],
4856
5503
  "help": `Inject this message into the configuration node to get valid configuration values: {topic: 'sensor_config_options', payload: ${sensor.type}}`
4857
5504
  }
@@ -4933,29 +5580,78 @@ module.exports = function(RED) {
4933
5580
  if(_requires_file_update){
4934
5581
  node._gateway_node.store_sensor_configs(JSON.stringify(node._gateway_node.sensor_configs));
4935
5582
  }
4936
- console.log(Object.keys(warning_msg).length);
5583
+ // console.log(Object.keys(warning_msg).length);
4937
5584
  if(Object.keys(warning_msg).length){
4938
5585
  response.warnings = warning_msg;
4939
5586
  }
4940
- console.log(Object.keys(error_msg).length);
5587
+ // console.log(Object.keys(error_msg).length);
4941
5588
  if(Object.keys(error_msg).length){
4942
5589
  response.errors = error_msg;
4943
5590
  }
4944
5591
  return response;
4945
5592
  };
4946
5593
 
4947
- node.gateway.on('sensor_mode', (d) => {
4948
- if(d.mode == 'FLY'){
4949
- if(Object.hasOwn(node._gateway_node.sensor_configs, d.mac) && Object.hasOwn(node._gateway_node.sensor_configs[d.mac], 'api_config_override')){
4950
- node.send({topic: 'sensor_report', payload: node._gateway_node.sensor_configs[d.mac], addr: d.mac, time: Date.now()});
4951
- }
4952
- }else if(d.mode == 'OTF'){
4953
- if(Object.hasOwn(node._gateway_node.sensor_configs, d.mac) && Object.hasOwn(node._gateway_node.sensor_configs[d.mac], 'api_config_override')){
4954
- node.send({topic: 'post_update_sensor_report', payload: node._gateway_node.sensor_configs[d.mac], addr: d.mac, time: Date.now()});
4955
- }
5594
+ // node.gateway.on('sensor_mode', (d) => {
5595
+ // if(d.mode == 'FLY'){
5596
+ // if(Object.hasOwn(node._gateway_node.sensor_configs, d.mac) && Object.hasOwn(node._gateway_node.sensor_configs[d.mac], 'api_config_override')){
5597
+ // node.send({topic: 'sensor_report', payload: node._gateway_node.sensor_configs[d.mac], addr: d.mac, time: Date.now()});
5598
+ // }
5599
+ // }else if(d.mode == 'OTF'){
5600
+ // if(Object.hasOwn(node._gateway_node.sensor_configs, d.mac) && Object.hasOwn(node._gateway_node.sensor_configs[d.mac], 'api_config_override')){
5601
+ // node.send({topic: 'post_update_sensor_report', payload: node._gateway_node.sensor_configs[d.mac], addr: d.mac, time: Date.now()});
5602
+ // }
5603
+ // }
5604
+ // });
5605
+
5606
+ node.gateway.on('sync', (d) => {
5607
+ switch (d.payload.type){
5608
+ case 'sync_check_in':
5609
+ // node.set_status();
5610
+ node.send({
5611
+ 'topic': 'sync',
5612
+ 'type': d.payload.type,
5613
+ ...d,
5614
+ 'time': Date.now()
5615
+ });
5616
+ break;
5617
+ case 'sync_init':
5618
+ // node.set_status();
5619
+ node.send({
5620
+ 'topic': 'sync',
5621
+ 'type': d.payload.type,
5622
+ ...d,
5623
+ 'time': Date.now()
5624
+ });
5625
+ break;
5626
+ case 'sync_end':
5627
+ // node.set_status();
5628
+ node.send({
5629
+ 'topic': 'sync',
5630
+ 'type': d.payload.type,
5631
+ ...d,
5632
+ 'time': Date.now()
5633
+ });
5634
+ break;
5635
+ case 'manual_sync_check_in':
5636
+ // node.set_status();
5637
+ node.send({
5638
+ 'topic': 'sync',
5639
+ 'type': d.payload.type,
5640
+ ...d,
5641
+ 'time': Date.now()
5642
+ });
5643
+ break;
5644
+ case 'sync_acknowledgment':
5645
+ // node.set_status();
5646
+ node.send({
5647
+ 'topic': 'sync',
5648
+ 'type': d.payload.type,
5649
+ ...d,
5650
+ 'time': Date.now()
5651
+ });
5652
+ break;
4956
5653
  }
4957
5654
  });
4958
-
4959
5655
  node._gateway_node.on('config_node_msg', (d) => {
4960
5656
  node.send(d);
4961
5657
  });
@@ -4967,13 +5663,14 @@ module.exports = function(RED) {
4967
5663
  node._gateway_node.removeAllListeners('config_node_msg');
4968
5664
  node._gateway_node.removeAllListeners('config_node_error');
4969
5665
  node._gateway_node.removeAllListeners('sensor_mode');
5666
+ node._gateway_node.removeAllListeners('sync');
4970
5667
  done();
4971
5668
  });
4972
-
4973
5669
 
4974
5670
  this.get_all_sensor_info = function(){
4975
5671
  return node._gateway_node.sensor_configs;
4976
5672
  };
5673
+
4977
5674
  this.get_sensor_array_info = function(sensor_array){
4978
5675
  let response = {
4979
5676
  status: 0,