@ncd-io/node-red-enterprise-sensors 1.6.3 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/wireless.js CHANGED
@@ -28,8 +28,6 @@ module.exports = function(RED) {
28
28
  const configuration_map_location = home_dir()+'/.node-red/node_modules/@ncd-io/node-red-enterprise-sensors/data/configuration_map.json';
29
29
  const sensor_type_map_location = home_dir()+'/.node-red/node_modules/@ncd-io/node-red-enterprise-sensors/data/sensor_type_map.json';
30
30
 
31
- this.config_node_capable = [114];
32
-
33
31
  // comms_timer object var added to clear the time to prevent issues with rapid redeploy
34
32
  this.comms_timer;
35
33
 
@@ -134,114 +132,154 @@ module.exports = function(RED) {
134
132
  node.gateway.digi.serial.reconnect();
135
133
  });
136
134
  }
137
- // Event listener to make sure this only triggers once no matter how many gateway nodes there are
135
+ // Listen for FLY messages to run FOTA updates on older FLY messages
138
136
  node.gateway.on('sensor_mode', (d) => {
139
- if(d.mode == "FLY" || d.mode == "OTF"){
137
+ if(d.mode == "FLY" && !Object.hasOwn(d, 'sync')){
140
138
  if(Object.hasOwn(node.sensor_list, d.mac) && Object.hasOwn(node.sensor_list[d.mac], 'update_request') && d.mode == "FLY"){
141
139
  node.request_manifest(d.mac);
142
- }else if(node.config_node_capable.includes(d.type)){
140
+ }
141
+ };
142
+ });
143
+ // Event listener to make sure this only triggers once no matter how many gateway nodes there are
144
+ node.gateway.on('sync', (d) => {
145
+ // console.log('Sync Received in Config Node');
146
+ // console.log(d);
147
+ // const payload = structuredClone(d.payload);
148
+ const payload = JSON.parse(JSON.stringify(d.payload));
149
+ const addr = payload.address;
150
+ const sensor_type = payload.sensor_type;
151
+ const type = payload.type;
152
+ if(payload.type == 'sync_check_in' || payload.type == 'sync_end' || payload.type == 'manual_sync_check_in'){
153
+ if(Object.hasOwn(node.sensor_list, addr) && Object.hasOwn(node.sensor_list[addr], 'update_request')){
154
+ node.request_manifest(addr);
155
+ }else if(Object.hasOwn(this.gateway.sensor_libs, sensor_type)){
143
156
  // 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
-
157
+ // let values = d.reported_config.machine_values;
158
+ const config_map = this.gateway.sensor_libs[sensor_type].get_config_map(payload.firmware_version);
149
159
  let store_flag = false;
150
160
 
151
- if(!Object.hasOwn(node.sensor_configs, d.mac)){
152
- node.sensor_configs[d.mac] = {};
161
+
162
+ if(!Object.hasOwn(node.sensor_configs, addr)){
163
+ node.sensor_configs[addr] = {};
153
164
  store_flag = true;
154
165
  }
166
+
167
+ // delete so we don't have to consider it for storage
168
+ // TODO re-add, but remove from storage consideration for mapping
169
+ // if(Object.hasOwn(d.payload, 'tx_lifetime_counter')){
170
+ // delete d.payload.tx_lifetime_counter;
171
+ // }
172
+
155
173
  // 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;
174
+ // if(!Object.hasOwn(node.sensor_configs[addr], 'hardware_id') && Object.hasOwn(d.payload, 'hardware_id')){
175
+ // node.sensor_configs[addr].hardware_id = d.payload.hardware_id;
176
+ // delete d.payload.hardware_id;
177
+ // store_flag = true;
178
+ // }
179
+
180
+ // Store core_version outside of reported_configs
181
+ // if(Object.hasOwn(d.payload, 'core_version')){
182
+ // node.sensor_configs[addr].core_version = d.payload.core_version;
183
+ // store_flag = true;
184
+ // }
185
+
161
186
  // Loop through and translate and validate values.
162
187
  // We want to allow passing in integers instead of byte arrays etc.
163
188
  // 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;
189
+ // values.network_id = config.pan_id;
190
+ // if(Object.hasOwn(d, 'node_id')){
191
+ // values.node_id = d.nodeId;
192
+ // }
193
+ for(let key in payload){
194
+ if(Object.hasOwn(config_map, key)){
195
+ if(!Object.hasOwn(config_map[key], 'write_index')){
196
+ // TODO
197
+ if(!Object.hasOwn(node.sensor_configs[addr], key) || Object.hasOwn(node.sensor_configs[addr], key) && node.sensor_configs[addr][key] != payload[key]){
198
+ // console.log('Config '+key+' is read only, moving from payload');
199
+ node.sensor_configs[addr][key] = payload[key];
200
+ if(key !== 'tx_lifetime_counter'){
201
+ store_flag = true;
191
202
  }
192
203
  }
204
+ delete payload[key];
193
205
  }
206
+ }else{
207
+ // console.log('Not in config map, deleting key: '+key);
208
+ delete payload[key];
194
209
  }
195
210
  }
196
211
  // 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 = {};
212
+ if(!Object.hasOwn(node.sensor_configs[addr], 'reported_configs')){
213
+ // node.sensor_configs[d.mac].reported_configs = {};
214
+ node.sensor_configs[addr].reported_configs = payload;
199
215
  store_flag = true;
200
216
  }
201
217
  // 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()});
218
+ if(!Object.hasOwn(node.sensor_configs[addr], 'desired_configs')){
219
+ node.sensor_configs[addr].desired_configs = payload;
214
220
  store_flag = true;
215
221
  }
216
- if(!isDeepStrictEqual(node.sensor_configs[d.mac].reported_configs, values)){
222
+
223
+ // We're setting sensor_type above, so we can remove this.
224
+ // if(!Object.hasOwn(node.sensor_configs[addr], 'type')){
225
+ // node.sensor_configs[addr].type = d.type;
226
+ // store_flag = true;
227
+ // }else if(node.sensor_configs[d.mac].type != d.type){
228
+ // // 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
229
+ // delete node.sensor_configs[d.mac].desired_configs;
230
+ // node.sensor_configs[d.mac].type = d.type;
231
+ // 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()});
232
+ // store_flag = true;
233
+ // }
234
+
235
+
236
+ if(!isDeepStrictEqual(node.sensor_configs[addr].reported_configs, payload)){
217
237
  // Values are different, update the stored configs and write to file
218
- node.sensor_configs[d.mac].reported_configs = values;
238
+ node.sensor_configs[addr].reported_configs = payload;
219
239
  // If reported configs change, but the auto config does not control configs, update automatically
220
240
  // 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;
241
+ if(!Object.hasOwn(node.sensor_configs[addr], 'api_config_override')){
242
+ node.sensor_configs[addr].desired_configs = payload;
223
243
  }
224
244
  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()});
245
+ node._emitter.emit('config_node_msg', {topic: 'sensor_configs_update', payload: node.sensor_configs[addr], address: addr, time: Date.now()});
226
246
  // node.send({topic: 'sensor_configs_update', payload: node.sensor_configs[d.mac], time: Date.now()});
227
247
  }
228
248
 
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;
249
+ if(type == 'sync_check_in'){
250
+ // TODO add backwards compatibility
251
+ if(config.enable_fly_compatibility){
252
+ this.gateway._emitter.emit('sensor_mode', {mac: addr, type: sensor_type, nodeId: payload.node_id, mode: 'FLY', lastHeard: Date.now(), reported_config: payload, sync: true});
253
+ this.gateway._emitter.emit('sensor_mode-'+addr, {mac: addr, type: sensor_type, nodeId: payload.node_id, mode: 'FLY', lastHeard: Date.now(), reported_config: payload, sync: true});
254
+ }
255
+ 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
256
  store_flag = true;
234
257
  var tout = setTimeout(() => {
235
- this._send_otn_request(d);
258
+ this.sync_init(addr, sensor_type);
236
259
  }, 100);
237
260
  }
261
+ }else if(type == 'manual_sync_check_in'){
262
+ 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)){
263
+ node.configure(addr, sensor_type);
264
+ }
265
+ }else if(type == 'sync_end'){
266
+ if(config.enable_fly_compatibility){
267
+ this.gateway._emitter.emit('sensor_mode', {mac: addr, type: sensor_type, nodeId: payload.node_id, mode: 'OTF', lastHeard: Date.now(), reported_config: payload, sync: true});
268
+ this.gateway._emitter.emit('sensor_mode-'+addr, {mac: addr, type: sensor_type, nodeId: payload.node_id, mode: 'OTF', lastHeard: Date.now(), reported_config: payload, sync: true});
269
+ }
238
270
  }
239
271
  if(store_flag){
240
272
  node.store_sensor_configs(JSON.stringify(node.sensor_configs));
241
273
  }
242
274
  }
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);
275
+ }else if(type == 'sync_init'){
276
+ if(config.enable_fly_compatibility){
277
+ this.gateway._emitter.emit('sensor_mode', {mac: addr, type: sensor_type, nodeId: d.payload.node_id, mode: 'OTN', lastHeard: Date.now(), reported_config: d.payload, sync: true});
278
+ this.gateway._emitter.emit('sensor_mode-'+addr, {mac: addr, type: sensor_type, nodeId: d.payload.node_id, mode: 'OTN', lastHeard: Date.now(), reported_config: d.payload, sync: true});
279
+ }
280
+ if(node.sensor_configs[addr] && node.sensor_configs[addr].api_config_override){
281
+ node.configure(addr, sensor_type);
282
+ }
245
283
  }
246
284
  });
247
285
  node.gateway.on('manifest_received', (manifest_data) => {
@@ -327,6 +365,58 @@ module.exports = function(RED) {
327
365
  }
328
366
  };
329
367
 
368
+ this.sync_init = function(addr, type){
369
+ // console.log('!!!! Sync Init for sensor '+addr);
370
+ return new Promise((top_fulfill, top_reject) => {
371
+ var msg = {};
372
+ setTimeout(() => {
373
+ var tout = setTimeout(() => {
374
+ // switch to emitter for this
375
+ // node.status(modes.PGM_ERR);
376
+ // node.send({topic: 'OTN Request Results', payload: msg, time: Date.now()});
377
+ console.log('Sync Request Timed Out');
378
+ }, 10000);
379
+
380
+ var promises = {};
381
+ // This command is used for OTF on types 53, 80,81,82,83,84, 101, 102, 110, 111, 518, 519
382
+ 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];
383
+ if(original_otf_devices.includes(type)){
384
+ console.log('!!!! Entering Sync mode with original command');
385
+ // This command is used for OTF on types 53, 80, 81, 82, 83, 84, 101, 102, 110, 111, 518, 519
386
+ promises.config_enter_otn_mode = node.gateway.config_enter_otn_mode(addr);
387
+ }else{
388
+ console.log('!!!! Entering Sync mode with other command');
389
+ // This command is used for OTF on types not 53, 80, 81, 82, 83, 84, 101, 102, 110, 111, 518, 519
390
+ promises.config_enter_otn_mode = node.gateway.config_enter_otn_mode_common(addr);
391
+ }
392
+ promises.finish = new Promise((fulfill, reject) => {
393
+ node.gateway.queue.add(() => {
394
+ return new Promise((f, r) => {
395
+ clearTimeout(tout);
396
+ // node.status(modes.FLY);
397
+ fulfill();
398
+ f();
399
+ });
400
+ });
401
+ });
402
+ for(var i in promises){
403
+ (function(name){
404
+ promises[name].then((f) => {
405
+ if(name != 'finish') msg[name] = true;
406
+ else{
407
+ // #OTF
408
+ node.send({topic: 'OTN Request Results', payload: msg, time: Date.now()});
409
+ top_fulfill(msg);
410
+ }
411
+ }).catch((err) => {
412
+ msg[name] = err;
413
+ });
414
+ })(i);
415
+ }
416
+ });
417
+ });
418
+ };
419
+
330
420
  this.get_required_configs = function(desired_configs, reported_configs){
331
421
  const mismatched = {};
332
422
  for (const key in desired_configs) {
@@ -340,8 +430,15 @@ module.exports = function(RED) {
340
430
  return mismatched;
341
431
  };
342
432
 
343
- // TODO consider adding logic for if only 1 config to go out, skip OTN request
344
- this.configure = function(sensor){
433
+ this.sync_check_reboot = function(addr){
434
+ if(node.sensor_configs[addr].reported_configs.network_id != node.sensor_configs[addr].desired_configs.network_id){
435
+ return true;
436
+ }else{
437
+ return false;
438
+ }
439
+ };
440
+
441
+ this.configure = function(addr, type){
345
442
  return new Promise((top_fulfill, top_reject) => {
346
443
  var success = {};
347
444
  setTimeout(() => {
@@ -352,50 +449,23 @@ module.exports = function(RED) {
352
449
  // node.send({topic: 'Config Results', payload: success, time: Date.now(), addr: sensor.mac});
353
450
  }, 60000);
354
451
  // node.status(modes.PGM_NOW);
355
- var mac = sensor.mac;
452
+ var mac = addr;
356
453
  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
- }
454
+ var reboot = this.sync_check_reboot(addr);
455
+
456
+ promises.sync_command = node.gateway.send_sync_configs(addr, node.sensor_configs[addr]);
387
457
 
388
458
  // These sensors listed in original_otf_devices use a different OTF code.
389
459
  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
460
  // If we changed the network ID reboot the sensor to take effect.
391
461
  // TODO if we add the encryption key command to node-red we need to reboot for it as well.
392
462
  if(reboot){
393
- promises.reboot_sensor = node.gateway.config_reboot_sensor(mac);
463
+ promises.reboot_sensor = node.gateway.config_reboot_sensor(addr);
394
464
  } else {
395
- if(original_otf_devices.includes(sensor.type)){
396
- promises.exit_otn_mode = node.gateway.config_exit_otn_mode(mac);
465
+ if(original_otf_devices.includes(type)){
466
+ promises.exit_otn_mode = node.gateway.config_exit_otn_mode(addr);
397
467
  }else{
398
- promises.config_exit_otn_mode_common = node.gateway.config_exit_otn_mode_common(mac);
468
+ promises.config_exit_otn_mode_common = node.gateway.config_exit_otn_mode_common(addr);
399
469
  }
400
470
  }
401
471
  promises.finish = new Promise((fulfill, reject) => {
@@ -416,7 +486,6 @@ module.exports = function(RED) {
416
486
  switch(f.result){
417
487
  case 255:
418
488
  success[name] = true;
419
- delete node.sensor_configs[mac].temp_required_configs[name];
420
489
  break;
421
490
  default:
422
491
  success[name] = {
@@ -442,7 +511,7 @@ module.exports = function(RED) {
442
511
  // TODO turn into event emitter.
443
512
  node._emitter.emit('config_node_msg', {topic: 'Config Results', payload: success, addr: mac, time: Date.now()});
444
513
  // node.send({topic: 'Config Results', payload: success, time: Date.now(), addr: mac});
445
- top_fulfill(success);
514
+ // top_fulfill(success);
446
515
  }
447
516
  }).catch((err) => {
448
517
  success[name] = err;
@@ -903,6 +972,7 @@ module.exports = function(RED) {
903
972
  this.gateway._emitter.removeAllListeners('link_info');
904
973
  this.gateway._emitter.removeAllListeners('converter_response');
905
974
  this.gateway._emitter.removeAllListeners('manifest_received');
975
+ this.gateway._emitter.removeAllListeners('sync');
906
976
  // console.log(this.gateway._emitter.eventNames());
907
977
  });
908
978
 
@@ -1278,6 +1348,28 @@ module.exports = function(RED) {
1278
1348
  node.set_status();
1279
1349
  node.send({topic: 'sensor_data', payload: d, time: Date.now()});
1280
1350
  });
1351
+ node.gateway.on('sync', (d) => {
1352
+ switch (d.payload.type){
1353
+ case 'sync_check_in':
1354
+ node.set_status();
1355
+ node.send({
1356
+ 'topic': 'sync',
1357
+ 'type': d.payload.type,
1358
+ ...d,
1359
+ 'time': Date.now()
1360
+ });
1361
+ break;
1362
+ case 'sync_init':
1363
+ node.set_status();
1364
+ node.send({
1365
+ 'topic': 'sync',
1366
+ 'type': d.payload.type,
1367
+ ...d,
1368
+ 'time': Date.now()
1369
+ });
1370
+ break;
1371
+ }
1372
+ });
1281
1373
  node.gateway.on('sensor_mode', (d) => {
1282
1374
  node.set_status();
1283
1375
  node.send({topic: 'sensor_mode', payload: d, time: Date.now()});
@@ -1344,6 +1436,8 @@ module.exports = function(RED) {
1344
1436
  // OTN: {fill:"yellow",shape:"ring",text:"OTN Received, OTF Configuration Initiated"},
1345
1437
  // OFF: {fill:"green",shape:"dot",text:"OFF Recieved, OTF Configuration Completed"}
1346
1438
  FLY: {fill:"yellow",shape:"ring",text:"FLY"},
1439
+ INI: {fill:"yellow",shape:"ring",text:"Sync-Init"},
1440
+ END: {fill:"yellow",shape:"ring",text:"Sync-End"},
1347
1441
  OTN: {fill:"yellow",shape:"ring",text:"OTN Received, Config Entered"},
1348
1442
  OTF: {fill:"green",shape:"dot",text:"OTF Received, Config Complete"},
1349
1443
  UPTHWRN: {fill:"yellow",shape:"ring",text:"Threshold is low"}
@@ -1842,29 +1936,119 @@ module.exports = function(RED) {
1842
1936
  }
1843
1937
  break;
1844
1938
  case 33:
1845
- if(config.clear_counter_33){
1846
- promises.clear_counter_33 = node.config_gateway.config_set_clear_counter_33(mac);
1939
+ if(config.debounce_time_108_active){
1940
+ promises.debounce_time_33 = node.config_gateway.config_set_debounce_time_108(mac, parseInt(config.debounce_time_108));
1847
1941
  }
1848
1942
  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));
1943
+ promises.input_detection_33 = node.config_gateway.config_set_input_two_108(mac, parseInt(config.input_two_33));
1944
+ }
1945
+ if(config.debounce_time_123_active){
1946
+ promises.debounce_time_2byte_33 = node.config_gateway.config_set_debounce_time_v10_108(mac, parseInt(config.debounce_time_123));
1850
1947
  }
1851
1948
  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));
1949
+ promises.counter_threshold_33 = node.config_gateway.config_set_counter_threshold_108(mac, parseInt(config.counter_threshold_108));
1853
1950
  }
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));
1951
+ if(config.transmission_interval_108_active){
1952
+ promises.transmission_interval_33 = node.config_gateway.config_set_transmission_interval_108(mac, parseInt(config.transmission_interval_108));
1856
1953
  }
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));
1954
+ if(config.reset_mode_to_disabled_108_active){
1955
+ promises.reset_mode_33 = node.config_gateway.config_set_reset_mode_to_disabled_108(mac, parseInt(config.reset_mode_to_disabled_108));
1859
1956
  }
1860
- break;
1957
+ if(config.reset_timeout_108_active){
1958
+ promises.reset_timeout_33 = node.config_gateway.config_set_reset_timeout_108(mac, parseInt(config.reset_timeout_108));
1959
+ }
1960
+ if(config.shift_one_108_active){
1961
+ 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));
1962
+ }
1963
+ if(config.shift_two_108_active){
1964
+ 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));
1965
+ }
1966
+ if(config.shift_three_108_active){
1967
+ 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));
1968
+ }
1969
+ if(config.shift_four_108_active){
1970
+ 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));
1971
+ }
1972
+ if(config.quality_of_service_108_active){
1973
+ promises.quality_of_service_33 = node.config_gateway.config_set_quality_of_service_108(mac, parseInt(config.quality_of_service_108));
1974
+ }
1975
+ if(config.rtc_108){
1976
+ promises.rtc_33 = node.config_gateway.config_set_rtc_108(mac);
1977
+ }
1978
+ if(config.clear_timers_35){
1979
+ promises.clear_counters_33 = node.config_gateway.config_set_clear_timers_35(mac);
1980
+ }
1981
+ if(config.push_notification_35_active){
1982
+ promises.push_notification_33 = node.config_gateway.config_set_push_notification_108(mac, parseInt(config.push_notification_35));
1983
+ }
1984
+ if(config.interrupt_timeout_35_active){
1985
+ promises.interrupt_timeout_33 = node.config_gateway.config_set_interrupt_timeout_108(mac, parseInt(config.interrupt_timeout_35));
1986
+ }
1987
+ if(config.probe_one_126_active){
1988
+ promises.probe_one_33 = node.config_gateway.config_set_probe_one_ct_126(mac, parseInt(config.probe_one_126));
1989
+ }
1990
+ if(config.threshold_probe_one_126_active){
1991
+ promises.threshold_probe_one_33 = node.config_gateway.config_set_probe_one_current_threshold_126(mac, parseInt(config.threshold_probe_one_126));
1992
+ }
1993
+ break;
1861
1994
  case 35:
1862
1995
  if(config.counter_threshold_35_active){
1863
1996
  promises.config_set_counter_threshold_35 = node.config_gateway.config_set_counter_threshold_35(mac, parseInt(config.counter_threshold_35));
1864
1997
  }
1998
+ if(config.counter_threshold_35_gen2_active){
1999
+ promises.config_set_counter_threshold_35_gen2 = node.config_gateway.config_set_counter_threshold_108(mac, parseInt(config.counter_threshold_35_gen2));
2000
+ }
1865
2001
  if(config.debounce_time_2_active){
1866
2002
  promises.config_set_debounce_time_35 = node.config_gateway.config_set_debounce_time_35(mac, parseInt(config.debounce_time_2));
1867
2003
  }
2004
+ if(config.debounce_time_123_active){
2005
+ promises.debounce_time_35 = node.config_gateway.config_set_debounce_time_v10_108(mac, parseInt(config.debounce_time_123));
2006
+ }
2007
+ if(config.input_one_123_active){
2008
+ promises.input_one_35 = node.config_gateway.config_set_input_one_108(mac, parseInt(config.input_one_123));
2009
+ }
2010
+ if(config.counter_threshold_108_active){
2011
+ promises.counter_threshold_35 = node.config_gateway.config_set_counter_threshold_108(mac, parseInt(config.counter_threshold_108));
2012
+ }
2013
+ if(config.reset_timeout_108_active){
2014
+ promises.reset_timeout_35 = node.config_gateway.config_set_reset_timeout_108(mac, parseInt(config.reset_timeout_108));
2015
+ }
2016
+ if(config.reset_mode_to_disabled_108_active){
2017
+ promises.reset_mode_35 = node.config_gateway.config_set_reset_mode_to_disabled_108(mac, parseInt(config.reset_mode_to_disabled_108));
2018
+ }
2019
+ if(config.rtc_108){
2020
+ promises.rtc_35 = node.config_gateway.config_set_rtc_108(mac);
2021
+ }
2022
+ if(config.transmission_interval_108_active){
2023
+ promises.transmission_interval_35 = node.config_gateway.config_set_transmission_interval_108(mac, parseInt(config.transmission_interval_108));
2024
+ }
2025
+ if(config.shift_one_108_active){
2026
+ 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));
2027
+ }
2028
+ if(config.shift_two_108_active){
2029
+ 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));
2030
+ }
2031
+ if(config.shift_three_108_active){
2032
+ 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));
2033
+ }
2034
+ if(config.shift_four_108_active){
2035
+ 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));
2036
+ }
2037
+ if(config.quality_of_service_108_active){
2038
+ promises.quality_of_service_35 = node.config_gateway.config_set_quality_of_service_108(mac, parseInt(config.quality_of_service_108));
2039
+ }
2040
+ if(config.fly_interval_108_active){
2041
+ promises.fly_interval_35 = node.config_gateway.config_set_fly_interval_108(mac, parseInt(config.fly_interval_108));
2042
+ }
2043
+ if(config.clear_timers_35){
2044
+ promises.clear_timers_35 = node.config_gateway.config_set_clear_timers_35(mac);
2045
+ }
2046
+ if(config.push_notification_35_active){
2047
+ promises.push_notification_35 = node.config_gateway.config_set_push_notification_108(mac, parseInt(config.push_notification_35));
2048
+ }
2049
+ if(config.interrupt_timeout_35_active){
2050
+ promises.interrupt_timeout_35 = node.config_gateway.config_set_interrupt_timeout_108(mac, parseInt(config.interrupt_timeout_35));
2051
+ }
1868
2052
  break;
1869
2053
  case 36:
1870
2054
  if(config.counter_threshold_35_active){
@@ -2451,6 +2635,11 @@ module.exports = function(RED) {
2451
2635
  promises.sensor_boot_time_78 = node.config_gateway.config_set_sensor_boot_time_78(mac, parseInt(config.sensor_boot_time_78));
2452
2636
  }
2453
2637
  break;
2638
+ case 93:
2639
+ if(config.sensor_boot_time_78_active){
2640
+ promises.sensor_boot_time_93 = node.config_gateway.config_set_sensor_boot_time_78(mac, parseInt(config.sensor_boot_time_78));
2641
+ }
2642
+ break;
2454
2643
  case 95:
2455
2644
  if(config.sensor_boot_time_420ma_active){
2456
2645
  promises.sensor_boot_time_420ma = node.config_gateway.config_set_sensor_boot_time_420ma(mac, parseInt(config.sensor_boot_time_420ma));
@@ -2644,6 +2833,9 @@ module.exports = function(RED) {
2644
2833
  if(config.set_rtc_101){
2645
2834
  promises.set_rtc_103 = node.config_gateway.config_set_rtc_101(mac);
2646
2835
  }
2836
+ if(config.send_raw_on_motion_only_103_active){
2837
+ 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));
2838
+ }
2647
2839
  break;
2648
2840
  case 105:
2649
2841
  if(config.sensor_boot_time_420ma_active){
@@ -3281,8 +3473,8 @@ module.exports = function(RED) {
3281
3473
  }
3282
3474
  break;
3283
3475
  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));
3476
+ if(config.clear_timers_123){
3477
+ promises.clear_timers_123 = node.config_gateway.config_set_clear_timers_108(mac, 7);
3286
3478
  }
3287
3479
  if(config.debounce_time_123_active){
3288
3480
  promises.debounce_time_123 = node.config_gateway.config_set_debounce_time_v10_108(mac, parseInt(config.debounce_time_123));
@@ -3368,6 +3560,139 @@ module.exports = function(RED) {
3368
3560
  promises.reset_all_totalizers_124 = node.config_gateway.config_set_reset_all_totalizers_124(mac);
3369
3561
  }
3370
3562
  break;
3563
+ case 125:
3564
+ if(config.clear_timers_123){
3565
+ promises.clear_timers_125 = node.config_gateway.config_set_clear_timers_108(mac, 7);
3566
+ }
3567
+ if(config.debounce_time_123_active){
3568
+ promises.debounce_time_125 = node.config_gateway.config_set_debounce_time_v10_108(mac, parseInt(config.debounce_time_123));
3569
+ }
3570
+ if(config.input_one_123_active){
3571
+ promises.input_one_125 = node.config_gateway.config_set_input_one_108(mac, parseInt(config.input_one_123));
3572
+ }
3573
+ if(config.input_two_123_active){
3574
+ promises.input_two_125 = node.config_gateway.config_set_input_two_108(mac, parseInt(config.input_two_123));
3575
+ }
3576
+ if(config.counter_threshold_108_active){
3577
+ promises.counter_threshold_125 = node.config_gateway.config_set_counter_threshold_108(mac, parseInt(config.counter_threshold_108));
3578
+ }
3579
+ if(config.push_notification_125_active){
3580
+ promises.push_notification_125 = node.config_gateway.config_set_push_notification_108(mac, parseInt(config.push_notification_125));
3581
+ }
3582
+ if(config.reset_timeout_108_active){
3583
+ promises.reset_timeout_125 = node.config_gateway.config_set_reset_timeout_108(mac, parseInt(config.reset_timeout_108));
3584
+ }
3585
+ if(config.reset_mode_to_disabled_108_active){
3586
+ promises.reset_mode_125 = node.config_gateway.config_set_reset_mode_to_disabled_108(mac, parseInt(config.reset_mode_to_disabled_108));
3587
+ }
3588
+ if(config.rtc_108){
3589
+ promises.rtc_125 = node.config_gateway.config_set_rtc_108(mac);
3590
+ }
3591
+ if(config.transmission_interval_108_active){
3592
+ promises.transmission_interval_125 = node.config_gateway.config_set_transmission_interval_108(mac, parseInt(config.transmission_interval_108));
3593
+ }
3594
+ if(config.shift_one_108_active){
3595
+ 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));
3596
+ }
3597
+ if(config.shift_two_108_active){
3598
+ 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));
3599
+ }
3600
+ if(config.shift_three_108_active){
3601
+ 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));
3602
+ }
3603
+ if(config.shift_four_108_active){
3604
+ 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));
3605
+ }
3606
+ if(config.quality_of_service_108_active){
3607
+ promises.quality_of_service_125 = node.config_gateway.config_set_quality_of_service_108(mac, parseInt(config.quality_of_service_108));
3608
+ }
3609
+ if(config.interrupt_timeout_108_active){
3610
+ promises.interrupt_timeout_125 = node.config_gateway.config_set_interrupt_timeout_108(mac, parseInt(config.interrupt_timeout_108));
3611
+ }
3612
+ if(config.probe_one_126_active){
3613
+ promises.probe_one_125 = node.config_gateway.config_set_probe_one_ct_126(mac, parseInt(config.probe_one_126));
3614
+ }
3615
+ if(config.probe_two_126_active){
3616
+ promises.probe_two_125 = node.config_gateway.config_set_probe_two_ct_126(mac, parseInt(config.probe_two_126));
3617
+ }
3618
+ if(config.threshold_probe_one_126_active){
3619
+ promises.threshold_probe_one_125 = node.config_gateway.config_set_probe_one_current_threshold_126(mac, parseInt(config.threshold_probe_one_126));
3620
+ }
3621
+ if(config.threshold_probe_two_126_active){
3622
+ promises.threshold_probe_two_125 = node.config_gateway.config_set_probe_two_current_threshold_126(mac, parseInt(config.threshold_probe_two_126));
3623
+ }
3624
+ break;
3625
+ case 126:
3626
+ if(config.clear_timers_123){
3627
+ promises.clear_timers_126 = node.config_gateway.config_set_clear_timers_108(mac, 7);
3628
+ }
3629
+ if(config.debounce_time_123_active){
3630
+ promises.debounce_time_126 = node.config_gateway.config_set_debounce_time_v10_108(mac, parseInt(config.debounce_time_123));
3631
+ }
3632
+ if(config.input_one_123_active){
3633
+ promises.input_one_126 = node.config_gateway.config_set_input_one_108(mac, parseInt(config.input_one_123));
3634
+ }
3635
+ if(config.input_two_123_active){
3636
+ promises.input_two_126 = node.config_gateway.config_set_input_two_108(mac, parseInt(config.input_two_123));
3637
+ }
3638
+ if(config.input_three_123_active){
3639
+ promises.input_three_126 = node.config_gateway.config_set_input_three_108(mac, parseInt(config.input_three_123));
3640
+ }
3641
+ if(config.counter_threshold_108_active){
3642
+ promises.counter_threshold_126 = node.config_gateway.config_set_counter_threshold_108(mac, parseInt(config.counter_threshold_108));
3643
+ }
3644
+ if(config.push_notification_123_active){
3645
+ promises.push_notification_126 = node.config_gateway.config_set_push_notification_108(mac, parseInt(config.push_notification_123));
3646
+ }
3647
+ if(config.reset_timeout_108_active){
3648
+ promises.reset_timeout_126 = node.config_gateway.config_set_reset_timeout_108(mac, parseInt(config.reset_timeout_108));
3649
+ }
3650
+ if(config.reset_mode_to_disabled_108_active){
3651
+ promises.reset_mode_126 = node.config_gateway.config_set_reset_mode_to_disabled_108(mac, parseInt(config.reset_mode_to_disabled_108));
3652
+ }
3653
+ if(config.rtc_108){
3654
+ promises.rtc_126 = node.config_gateway.config_set_rtc_108(mac);
3655
+ }
3656
+ if(config.transmission_interval_108_active){
3657
+ promises.transmission_interval_126 = node.config_gateway.config_set_transmission_interval_108(mac, parseInt(config.transmission_interval_108));
3658
+ }
3659
+ if(config.shift_one_108_active){
3660
+ 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));
3661
+ }
3662
+ if(config.shift_two_108_active){
3663
+ 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));
3664
+ }
3665
+ if(config.shift_three_108_active){
3666
+ 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));
3667
+ }
3668
+ if(config.shift_four_108_active){
3669
+ 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));
3670
+ }
3671
+ if(config.quality_of_service_108_active){
3672
+ promises.quality_of_service_126 = node.config_gateway.config_set_quality_of_service_108(mac, parseInt(config.quality_of_service_108));
3673
+ }
3674
+ if(config.interrupt_timeout_108_active){
3675
+ promises.interrupt_timeout_126 = node.config_gateway.config_set_interrupt_timeout_108(mac, parseInt(config.interrupt_timeout_108));
3676
+ }
3677
+ if(config.probe_one_126_active){
3678
+ promises.probe_one_126 = node.config_gateway.config_set_probe_one_ct_126(mac, parseInt(config.probe_one_126));
3679
+ }
3680
+ if(config.probe_two_126_active){
3681
+ promises.probe_two_126 = node.config_gateway.config_set_probe_two_ct_126(mac, parseInt(config.probe_two_126));
3682
+ }
3683
+ if(config.probe_three_126_active){
3684
+ promises.probe_three_126 = node.config_gateway.config_set_probe_three_ct_126(mac, parseInt(config.probe_three_126));
3685
+ }
3686
+ if(config.threshold_probe_one_126_active){
3687
+ promises.threshold_probe_one_126 = node.config_gateway.config_set_probe_one_current_threshold_126(mac, parseInt(config.threshold_probe_one_126));
3688
+ }
3689
+ if(config.threshold_probe_two_126_active){
3690
+ promises.threshold_probe_two_126 = node.config_gateway.config_set_probe_two_current_threshold_126(mac, parseInt(config.threshold_probe_two_126));
3691
+ }
3692
+ if(config.threshold_probe_three_126_active){
3693
+ promises.threshold_probe_three_126 = node.config_gateway.config_set_probe_three_current_threshold_126(mac, parseInt(config.threshold_probe_three_126));
3694
+ }
3695
+ break;
3371
3696
  case 180:
3372
3697
  if(config.output_data_rate_101_active){
3373
3698
  promises.output_data_rate_101 = node.config_gateway.config_set_output_data_rate_101(mac, parseInt(config.output_data_rate_101));
@@ -4033,8 +4358,6 @@ module.exports = function(RED) {
4033
4358
  (function(name){
4034
4359
  promises[name].then((f) => {
4035
4360
  if(name != 'finish'){
4036
- // console.log('IN PROMISE RESOLVE');
4037
- // console.log(f);
4038
4361
  // success[name] = true;
4039
4362
  if(Object.hasOwn(f, 'result')){
4040
4363
  switch(f.result){
@@ -4132,6 +4455,106 @@ module.exports = function(RED) {
4132
4455
  });
4133
4456
  }
4134
4457
  });
4458
+ this.pgm_on('sync-'+config.addr, (data) => {
4459
+ let message = {
4460
+ topic: 'sync',
4461
+ type: data.payload.type,
4462
+ ...data,
4463
+ time: Date.now()
4464
+ };
4465
+
4466
+ // switch(data.type){
4467
+ // case 'sync_check_in':
4468
+ // break;
4469
+ // case 'sync_init':
4470
+ // break;
4471
+ // case 'sync_acknowledgment':
4472
+ // break;
4473
+ // case 'sync_acknowledgment_error':
4474
+ // break;
4475
+ // case 'sync_end':
4476
+ // break;
4477
+ // default:
4478
+ // console.log('Default in device node sync');
4479
+ // console.log(data.type);
4480
+ // }
4481
+ if(data.type == 'sync_check_in' || data.type == 'manual_sync_check_in'){
4482
+ if(config.auto_config && config.on_the_fly_enable || data.type == 'manual_sync_check_in' && config.auto_config){
4483
+ if(Object.hasOwn(this.gateway_node.sensor_configs, data.payload.address) && !Object.hasOwn(this.gateway_node.sensor_configs[data.payload.address], 'api_config_override')){
4484
+ const html_map = this.config_gateway.get_intended_wireless_node_configs(data, config);
4485
+
4486
+ let update_flag = false;
4487
+ for(let key in html_map){
4488
+ if(html_map[key].html_value != node.gateway_node.sensor_configs[data.payload.address].reported_configs[key]){
4489
+ // console.log('Comparing ' + html_map[key].html_value + ' to ' + node.gateway_node.sensor_configs[data.payload.address].reported_configs[key]);
4490
+ // console.log('Value is different, updating config for ' + key);
4491
+ update_flag = true;
4492
+ break;
4493
+ }
4494
+ }
4495
+ if(update_flag){
4496
+ promises = {};
4497
+ setTimeout(() => {
4498
+ let msg = {
4499
+ values: {},
4500
+ pass: {},
4501
+ status: 'Configuring'
4502
+ };
4503
+ var tout = setTimeout(() => {
4504
+ console.log('Sync Request Timed Out');
4505
+ }, 20000);
4506
+ promises.send_sync_configs = this.config_gateway.send_sync_config_wireless_node(data, html_map, node.gateway_node.sensor_configs[data.payload.address]);
4507
+
4508
+ promises.finish = new Promise((fulfill, reject) => {
4509
+ node.config_gateway.queue.add(() => {
4510
+ return new Promise((f, r) => {
4511
+ clearTimeout(tout);
4512
+ node.status(modes.FLY);
4513
+ fulfill();
4514
+ f();
4515
+ });
4516
+ });
4517
+ });
4518
+ for(var i in promises){
4519
+ (function(name){
4520
+ promises[name].then((f) => {
4521
+ if(name != 'finish'){
4522
+ let fail_flag = false;
4523
+ for(const key in html_map){
4524
+ if(Object.hasOwn(f.payload, key) && f.payload[key] == html_map[key].html_value){
4525
+ msg.pass[key] = true;
4526
+ msg.values[key] = f.payload[key];
4527
+ }else{
4528
+ msg.pass[key] = false;
4529
+ msg.values[key] = f.payload[key];
4530
+ fail_flag = true;
4531
+ }
4532
+ }
4533
+ if(fail_flag){
4534
+ msg.status = 'Error';
4535
+ }else{
4536
+ msg.status = 'Success';
4537
+ }
4538
+ }
4539
+ else{
4540
+ node.send({topic: 'sync', type: 'sync_response', payload: msg, time: Date.now()});
4541
+ // top_fulfill(msg);
4542
+ }
4543
+ }).catch((err) => {
4544
+ msg[name] = err;
4545
+ });
4546
+ })(i);
4547
+ }
4548
+ });
4549
+ }else{
4550
+ // console.log('No Config Differences Detected, Skipping Sync Configs');
4551
+ node.send({topic: 'sync', type: 'sync_response', payload: {info: "Reported configurations match desired configurations. Skipping Sync."}, time: Date.now()});
4552
+ }
4553
+ }
4554
+ }
4555
+ }
4556
+ node.send(message);
4557
+ });
4135
4558
  this.pgm_on('sensor_mode-'+config.addr, (sensor) => {
4136
4559
  if(sensor.mode in modes){
4137
4560
  node.status(modes[sensor.mode]);
@@ -4141,7 +4564,7 @@ module.exports = function(RED) {
4141
4564
  }
4142
4565
  if(config.auto_config && sensor.mode == "PGM"){
4143
4566
  _config(sensor);
4144
- }else if(config.auto_config && config.on_the_fly_enable && sensor.mode == "FLY"){
4567
+ }else if(config.auto_config && config.on_the_fly_enable && sensor.mode == "FLY" && !Object.hasOwn(sensor, 'sync')){
4145
4568
  // _send_otn_request(sensor);
4146
4569
  // Sensors having issues seeing OTN request sent too quickly
4147
4570
  // Added timeout to fix issue
@@ -4155,11 +4578,11 @@ module.exports = function(RED) {
4155
4578
  }else{
4156
4579
  node.send({topic: 'warning', payload: {'warning': 'Wireless Device Node configurations overridden by API Configuration'}, addr: sensor.mac, time: Date.now()});
4157
4580
  }
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){
4581
+ }else if(config.auto_config && config.on_the_fly_enable && sensor.mode == "OTN" && !Object.hasOwn(sensor, 'sync')){
4582
+ if(config.sensor_type == 101 || config.sensor_type == 102 || config.sensor_type == 103 || config.sensor_type == 202){
4160
4583
  if(this.gateway.hasOwnProperty('fly_101_in_progress') && this.gateway.fly_101_in_progress == false || !this.gateway.hasOwnProperty('fly_101_in_progress')){
4161
4584
  this.gateway.fly_101_in_progress = true;
4162
- node.warn('Starting RTC Timer' + Date.now());
4585
+ node.warn('Starting RTC Timer ' + Date.now());
4163
4586
  node.warn('Sensor checked in for RTC: ' + sensor.mac + ' at ' + Date.now());
4164
4587
  var broadcast_tout = setTimeout(() => {
4165
4588
  node.warn('Sending RTC Broadcast ' + Date.now());
@@ -4181,10 +4604,10 @@ module.exports = function(RED) {
4181
4604
  }else{
4182
4605
  node.send({topic: 'warning', payload: {'warning': 'Wireless Device Node configurations overridden by API Configuration'}, addr: sensor.mac, time: Date.now()});
4183
4606
  };
4184
- } else if(config.sensor_type == 101 && sensor.mode == "FLY" || config.sensor_type == 102 && sensor.mode == "FLY" || config.sensor_type == 202 && sensor.mode == "FLY"){
4607
+ } 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
4608
  if(this.gateway.hasOwnProperty('fly_101_in_progress') && this.gateway.fly_101_in_progress == false || !this.gateway.hasOwnProperty('fly_101_in_progress')){
4186
4609
  this.gateway.fly_101_in_progress = true;
4187
- node.warn('Starting RTC Timer' + Date.now());
4610
+ node.warn('Starting RTC Timer ' + Date.now());
4188
4611
  node.warn('Sensor checked in for RTC: ' + sensor.mac + ' at ' + Date.now());
4189
4612
  var broadcast_tout = setTimeout(() => {
4190
4613
  node.warn('Sending RTC Broadcast ' + Date.now());
@@ -4257,7 +4680,7 @@ module.exports = function(RED) {
4257
4680
  }
4258
4681
  if(config.auto_config && sensor.mode == 'PGM'){
4259
4682
  _config(sensor);
4260
- }else if(config.auto_config && config.on_the_fly_enable && sensor.mode == "FLY"){
4683
+ }else if(config.auto_config && config.on_the_fly_enable && sensor.mode == "FLY" && !Object.hasOwn(sensor, 'sync')){
4261
4684
  // _send_otn_request(sensor);
4262
4685
  // Sensors having issues seeing OTN request sent too quickly
4263
4686
  // Added timeout to fix issue
@@ -4271,8 +4694,8 @@ module.exports = function(RED) {
4271
4694
  }else{
4272
4695
  node.send({topic: 'warning', payload: {'warning': 'Wireless Device Node configurations overridden by API Configuration'}, addr: sensor.mac, time: Date.now()});
4273
4696
  }
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){
4697
+ }else if(config.auto_config && config.on_the_fly_enable && sensor.mode == "OTN" && !Object.hasOwn(sensor, 'sync')){
4698
+ if(config.sensor_type == 101 || config.sensor_type == 102 || config.sensor_type == 103|| config.sensor_type == 202){
4276
4699
  if(this.gateway.hasOwnProperty('fly_101_in_progress') && this.gateway.fly_101_in_progress == false || !this.gateway.hasOwnProperty('fly_101_in_progress')){
4277
4700
  this.gateway.fly_101_in_progress = true;
4278
4701
  node.warn('Starting RTC Timer' + Date.now());
@@ -4299,10 +4722,10 @@ module.exports = function(RED) {
4299
4722
  node.send({topic: 'warning', payload: {'warning': 'Wireless Device Node configurations overridden by API Configuration'}, addr: sensor.mac, time: Date.now()});
4300
4723
  };
4301
4724
 
4302
- }else if(sensor.mode == "FLY" && config.sensor_type == 101 || sensor.mode == "FLY" && config.sensor_type == 102 || sensor.mode == "FLY" && config.sensor_type == 202){
4725
+ }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
4726
  if(this.gateway.hasOwnProperty('fly_101_in_progress') && this.gateway.fly_101_in_progress == false || !this.gateway.hasOwnProperty('fly_101_in_progress')){
4304
4727
  this.gateway.fly_101_in_progress = true;
4305
- node.warn('Starting RTC Timer' + Date.now());
4728
+ node.warn('Starting RTC Timer ' + Date.now());
4306
4729
  node.warn('Sensor checked in for RTC: ' + sensor.mac + ' at ' + Date.now());
4307
4730
  var broadcast_tout = setTimeout(() => {
4308
4731
  node.warn('Sending RTC Broadcast ' + Date.now());
@@ -4513,15 +4936,17 @@ module.exports = function(RED) {
4513
4936
  break;
4514
4937
  case 'sensor_config_options':
4515
4938
  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);
4939
+ // console.log(typeof msg.payload == 'object');
4940
+ // console.log(Object.hasOwn(msg.payload, 'sensor_type'));
4941
+ // console.log(Object.hasOwn(this.gateway.sensor_libs, msg.payload.sensor_type));
4942
+ if(typeof msg.payload == 'object' && Object.hasOwn(msg.payload, 'sensor_type') && Object.hasOwn(this.gateway.sensor_libs, msg.payload.sensor_type)){
4943
+ let options = this.get_config_options(msg.payload.sensor_type, msg.payload.firmware_version);
4519
4944
  msg.payload = options;
4520
4945
  msg.status = 200;
4521
4946
  msg.time = Date.now();
4522
4947
  send(msg);
4523
4948
  }else{
4524
- msg.payload = {error: 'get_config_options error: Payload must be a valid sensor type number'};
4949
+ 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
4950
  msg.status = 500;
4526
4951
  msg.time = Date.now();
4527
4952
  send(msg);
@@ -4582,48 +5007,36 @@ module.exports = function(RED) {
4582
5007
  return response;
4583
5008
  };
4584
5009
 
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;
5010
+ this.get_config_options = function(sensor_type, firmware_version = 1){
5011
+ if(Object.hasOwn(this.gateway.sensor_libs, sensor_type)){
5012
+ const config_list = this.gateway.sensor_libs[sensor_type].get_config_map(firmware_version);
4588
5013
  let response = {};
4589
5014
  for (const key in config_list){
4590
- const info = node._gateway_node.configuration_map[config_list[key]];
5015
+ const info = config_list[key];
4591
5016
  // 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;
5017
+ // console.log(key);
5018
+ // console.log(info);
5019
+ if(Object.hasOwn(info, 'write_index') && !Object.hasOwn(info, 'read_only')){
5020
+ response[key] = {
5021
+ title: info.descriptions.title,
5022
+ default_value: info.default_value,
5023
+ };
5024
+ if(Object.hasOwn(info.descriptions, 'main_caption')){
5025
+ response[key].description = info.descriptions.main_caption;
5026
+ }
5027
+ if(Object.hasOwn(info.descriptions, 'sub_caption')){
5028
+ response[key].description = info.descriptions.main_caption;
5029
+ }
5030
+ if(Object.hasOwn(info, 'validator')){
5031
+ if(Object.hasOwn(info.validator, 'generated')){
5032
+ delete info.validator.generated;
5033
+ }
5034
+ response[key] = {...response[key], ...info.validator};
5035
+ }
5036
+ if(Object.hasOwn(info, 'options')){
5037
+ response[key].options = info.options;
4605
5038
  }
4606
- response[key] = {...response[key], ...info.validator};
4607
- }
4608
- if(Object.hasOwn(info, 'options')){
4609
- response[key].options = info.options;
4610
5039
  }
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
5040
  }
4628
5041
  return response;
4629
5042
  }else{
@@ -4636,12 +5049,96 @@ module.exports = function(RED) {
4636
5049
  var response = {};
4637
5050
  let error_msg = {};
4638
5051
  let warning_msg = {};
5052
+
4639
5053
  if(desired_configs == null || typeof desired_configs == 'undefined'){
4640
5054
  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
5055
  }else if(!Array.isArray(desired_configs) || desired_configs.length == 0){
4642
5056
  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
5057
  }
5058
+
5059
+ const build_error = (sensor, key, detail) => {
5060
+ return {
5061
+ type: 'error',
5062
+ message: `${detail.text} for ${key} on sensor ${sensor.addr}.`,
5063
+ detail: {
5064
+ config_name: key,
5065
+ ...detail // Merges everything: received, help, expected, etc.
5066
+ }
5067
+ };
5068
+ };
5069
+
5070
+ const build_warning = (sensor, key, detail) => {
5071
+ return {
5072
+ type: 'warning',
5073
+ message: `${detail.text} for ${key} on sensor ${sensor.addr}.`,
5074
+ detail: {
5075
+ config_name: key,
5076
+ ...detail,
5077
+ help: detail.help || "This warning does not prevent the configuration from going through."
5078
+ }
5079
+ };
5080
+ };
5081
+
5082
+ const validation_methods = {
5083
+ // HEX: Strict, no colons allowed
5084
+ hex: (val, validator, configName, sensor) => {
5085
+ const strVal = String(val);
5086
+ if (!/^[0-9a-f]+$/i.test(strVal)) {
5087
+ return { error: this.build_error(sensor, configName, val, "Invalid hex string", "Hex string should only contain 0-9 and a-f.") };
5088
+ }
5089
+ if (validator.length && strVal.length !== validator.length) {
5090
+ return { error: this.build_error(sensor, configName, val, "Invalid length for hex string", `Expected ${validator.length}, got ${strVal.length}`) };
5091
+ }
5092
+
5093
+ const result = { value: strVal.toLowerCase() };
5094
+ if (!validator.length) {
5095
+ result.warning = this.build_warning(sensor, configName, "Missing length validator", "hexString");
5096
+ }
5097
+ return result;
5098
+ },
5099
+
5100
+ // MAC: Flexible, strips colons first
5101
+ mac: (val, validator, configName, sensor) => {
5102
+ const clean = String(val).replace(/:/g, '');
5103
+ if (!/^[0-9a-f]+$/i.test(clean)) {
5104
+ return { error: this.build_error(sensor, configName, val, "Invalid MAC format", "MAC string should only contain 0-9, a-f, and colons.") };
5105
+ }
5106
+ if (validator.length && clean.length !== validator.length) {
5107
+ return { error: this.build_error(sensor, configName, clean, "Invalid length for MAC", `Expected ${validator.length}, got ${clean.length}`) };
5108
+ }
5109
+ return { value: clean.toLowerCase() };
5110
+ },
5111
+
5112
+ // NUMBERS: uint8, uint16be, uint32be
5113
+ number: (val, validator, configName, sensor, options) => {
5114
+ const num = parseInt(val);
5115
+ if (isNaN(num)) {
5116
+ return { error: this.build_error(sensor, configName, val, "Integer value expected", "Ensure the value is a valid number.") };
5117
+ }
5118
+
5119
+ // Check Min/Max
5120
+ if (validator.min !== undefined && num < validator.min) {
5121
+ return { error: this.build_error(sensor, configName, num, "Value below minimum", `Min allowed: ${validator.min}`) };
5122
+ }
5123
+ if (validator.max !== undefined && num > validator.max) {
5124
+ return { error: this.build_error(sensor, configName, num, "Value above maximum", `Max allowed: ${validator.max}`) };
5125
+ }
5126
+
5127
+ // Check Options
5128
+ if (options && !Object.keys(options).includes(String(num))) {
5129
+ return { error: this.build_error(sensor, configName, num, "Invalid option", `Valid options: ${JSON.stringify(options)}`) };
5130
+ }
5131
+
5132
+ const result = { value: num };
5133
+ if (validator.min === undefined || validator.max === undefined) {
5134
+ result.warning = this.build_warning(sensor, configName, "Missing min/max validator", "number");
5135
+ }
5136
+ return result;
5137
+ }
5138
+ };
5139
+
4644
5140
  for (const sensor of desired_configs){
5141
+
4645
5142
  // TODO validate sensor object
4646
5143
  // must have sensor.addr, sensor.type, sensor.configs
4647
5144
  // sensor.configs is an object with key value pairs of config name and desired value
@@ -4674,6 +5171,21 @@ module.exports = function(RED) {
4674
5171
  // 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
5172
  continue;
4676
5173
  }
5174
+ if(!Object.hasOwn(node.gateway.sensor_libs, sensor.type)){
5175
+ error_msg.syntax_error ||= {};
5176
+ error_msg.syntax_error = {
5177
+ type: 'error',
5178
+ message: `Invalid sensor type ${sensor.type} for sensor ${sensor.addr}. See detail.valid_sensor in this message for a list of valid sensors.`,
5179
+ detail: {
5180
+ "text": "Invlalid sensor type",
5181
+ "received": sensor.type,
5182
+ "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}}]",
5183
+ "valid_sensors": Object.keys(node.gateway.sensor_libs)
5184
+ }
5185
+ };
5186
+ continue;
5187
+ }
5188
+
4677
5189
  // Force lowercase for consistency
4678
5190
  sensor.addr = sensor.addr.toLowerCase();
4679
5191
  // force configs values lowercase for consistency
@@ -4698,31 +5210,20 @@ module.exports = function(RED) {
4698
5210
  node._gateway_node.sensor_configs[sensor.addr].type = sensor.type;
4699
5211
  _requires_file_update = true;
4700
5212
  }
5213
+
5214
+ const config_map = node.gateway.sensor_libs[sensor.type].get_config_map();
5215
+
4701
5216
  // Validate configs
4702
5217
  for (const config_name in sensor.configs){
4703
5218
  // 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':
5219
+ if(Object.hasOwn(config_map, config_name)){
5220
+ let map_key = config_map[config_name];
5221
+ if(Object.hasOwn(map_key, 'validator')){
5222
+ if(Object.hasOwn(map_key.validator, 'type')){
5223
+
5224
+ // TODO replace with validation_methods object approach for cleaner code
5225
+ switch(map_key.validator.type){
5226
+ case 'hex':
4726
5227
  // Remove colons from string
4727
5228
  sensor.configs[config_name] = sensor.configs[config_name].replace(/:/g, '');
4728
5229
 
@@ -4743,7 +5244,7 @@ module.exports = function(RED) {
4743
5244
  continue;
4744
5245
  }
4745
5246
  // 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){
5247
+ if(Object.hasOwn(map_key.validator, 'length') && sensor.configs[config_name].length != map_key.validator.length){
4747
5248
  error_msg[sensor.addr] ||= {};
4748
5249
  error_msg[sensor.addr][config_name] = {
4749
5250
  type: 'error',
@@ -4759,7 +5260,57 @@ module.exports = function(RED) {
4759
5260
  continue;
4760
5261
  }
4761
5262
  // Warning for missing length validator
4762
- if (!Object.hasOwn(node._gateway_node.configuration_map[map_key].validator, 'length')) {
5263
+ if (!Object.hasOwn(map_key.validator, 'length')) {
5264
+ warning_msg[sensor.addr] ||= {};
5265
+ warning_msg[sensor.addr][config_name] = {
5266
+ type: 'warning',
5267
+ message: `No length validator defined for hex string ${config_name} for sensor ${sensor.addr}.`,
5268
+ detail: {
5269
+ "text": "Missing length validator",
5270
+ "type": "hexString",
5271
+ "help": "This warning does not prevent the configuration from going through."
5272
+ }
5273
+ };
5274
+ }
5275
+ break;
5276
+ case 'mac':
5277
+ // Remove colons from string
5278
+ sensor.configs[config_name] = sensor.configs[config_name].replace(/:/g, '');
5279
+
5280
+ // Invalid hex string error
5281
+ if (!/^[0-9a-f]+$/.test(sensor.configs[config_name])) {
5282
+ error_msg[sensor.addr] ||= {};
5283
+ error_msg[sensor.addr][config_name] = {
5284
+ type: 'error',
5285
+ message: `Invalid hex string for configuration ${config_name} for sensor ${sensor.addr}. Received value: ${sensor.configs[config_name]}`,
5286
+ detail: {
5287
+ "text": "Invalid hex string",
5288
+ "regex_validator": "/^[0-9a-f]+$/",
5289
+ "received": sensor.configs[config_name],
5290
+ "help": "Hex string should only contain characters 0-9 and a-f. Colons are removed during validation."
5291
+ }
5292
+ };
5293
+ delete sensor.configs[config_name];
5294
+ continue;
5295
+ }
5296
+ // Invalid length error
5297
+ if(Object.hasOwn(map_key.validator, 'length') && sensor.configs[config_name].length != map_key.validator.length){
5298
+ error_msg[sensor.addr] ||= {};
5299
+ error_msg[sensor.addr][config_name] = {
5300
+ type: 'error',
5301
+ 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}`,
5302
+ detail: {
5303
+ "text": "Invalid length for hex string",
5304
+ "expected_length": node._gateway_node.configuration_map[map_key].validator.length,
5305
+ "received_length": sensor.configs[config_name].length,
5306
+ "help": "Add leading or trailing zeros to meet length requirement."
5307
+ }
5308
+ };
5309
+ delete sensor.configs[config_name];
5310
+ continue;
5311
+ }
5312
+ // Warning for missing length validator
5313
+ if (!Object.hasOwn(map_key.validator, 'length')) {
4763
5314
  warning_msg[sensor.addr] ||= {};
4764
5315
  warning_msg[sensor.addr][config_name] = {
4765
5316
  type: 'warning',
@@ -4794,14 +5345,14 @@ module.exports = function(RED) {
4794
5345
  continue;
4795
5346
  }
4796
5347
  // 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){
5348
+ if(Object.hasOwn(map_key.validator, 'min') && sensor.configs[config_name] < map_key.validator.min){
4798
5349
  error_msg[sensor.addr] ||= {};
4799
5350
  error_msg[sensor.addr][config_name] = {
4800
5351
  type: 'error',
4801
5352
  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
5353
  detail: {
4803
5354
  "text": "Value below minimum",
4804
- "min_allowed": node._gateway_node.configuration_map[map_key].validator.min,
5355
+ "min_allowed": map_key.validator.min,
4805
5356
  "received": sensor.configs[config_name],
4806
5357
  "help": `Inject this message into the configuration node to get valid configuration values: {topic: 'sensor_config_options', payload: ${sensor.type}}`
4807
5358
  }
@@ -4811,14 +5362,14 @@ module.exports = function(RED) {
4811
5362
  // 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
5363
  }
4813
5364
  // 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){
5365
+ if(Object.hasOwn(map_key.validator, 'max') && sensor.configs[config_name] > map_key.validator.max){
4815
5366
  error_msg[sensor.addr] ||= {};
4816
5367
  error_msg[sensor.addr][config_name] = {
4817
5368
  type: 'error',
4818
5369
  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
5370
  detail: {
4820
5371
  "text": "Value above maximum",
4821
- "max_allowed": node._gateway_node.configuration_map[map_key].validator.max,
5372
+ "max_allowed": map_key.validator.max,
4822
5373
  "received": sensor.configs[config_name],
4823
5374
  "help": `Inject this message into the configuration node to get valid configuration values: {topic: 'sensor_config_options', payload: ${sensor.type}}`
4824
5375
  }
@@ -4828,7 +5379,7 @@ module.exports = function(RED) {
4828
5379
  // 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
5380
  }
4830
5381
  // 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')){
5382
+ if(!Object.hasOwn(map_key.validator, 'min') || !Object.hasOwn(map_key.validator, 'max')){
4832
5383
  warning_msg[sensor.addr] ||= {};
4833
5384
  warning_msg[sensor.addr][config_name] = {
4834
5385
  type: 'warning',
@@ -4843,15 +5394,15 @@ module.exports = function(RED) {
4843
5394
  }
4844
5395
  // If options exist, value must be one of the options
4845
5396
  // 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]))){
5397
+ if(Object.hasOwn(map_key, 'options')){
5398
+ if(!Object.keys(map_key.options).includes(String(sensor.configs[config_name]))){
4848
5399
  error_msg[sensor.addr] ||= {};
4849
5400
  error_msg[sensor.addr][config_name] = {
4850
5401
  type: 'error',
4851
5402
  message: `Invalid option for configuration ${config_name} for sensor ${sensor.addr}. Received value: ${sensor.configs[config_name]}`,
4852
5403
  detail: {
4853
5404
  "text": "Invalid option",
4854
- "valid_options": node._gateway_node.configuration_map[map_key].options,
5405
+ "valid_options": map_key.options,
4855
5406
  "received": sensor.configs[config_name],
4856
5407
  "help": `Inject this message into the configuration node to get valid configuration values: {topic: 'sensor_config_options', payload: ${sensor.type}}`
4857
5408
  }
@@ -4933,29 +5484,78 @@ module.exports = function(RED) {
4933
5484
  if(_requires_file_update){
4934
5485
  node._gateway_node.store_sensor_configs(JSON.stringify(node._gateway_node.sensor_configs));
4935
5486
  }
4936
- console.log(Object.keys(warning_msg).length);
5487
+ // console.log(Object.keys(warning_msg).length);
4937
5488
  if(Object.keys(warning_msg).length){
4938
5489
  response.warnings = warning_msg;
4939
5490
  }
4940
- console.log(Object.keys(error_msg).length);
5491
+ // console.log(Object.keys(error_msg).length);
4941
5492
  if(Object.keys(error_msg).length){
4942
5493
  response.errors = error_msg;
4943
5494
  }
4944
5495
  return response;
4945
5496
  };
4946
5497
 
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
- }
5498
+ // node.gateway.on('sensor_mode', (d) => {
5499
+ // if(d.mode == 'FLY'){
5500
+ // if(Object.hasOwn(node._gateway_node.sensor_configs, d.mac) && Object.hasOwn(node._gateway_node.sensor_configs[d.mac], 'api_config_override')){
5501
+ // node.send({topic: 'sensor_report', payload: node._gateway_node.sensor_configs[d.mac], addr: d.mac, time: Date.now()});
5502
+ // }
5503
+ // }else if(d.mode == 'OTF'){
5504
+ // if(Object.hasOwn(node._gateway_node.sensor_configs, d.mac) && Object.hasOwn(node._gateway_node.sensor_configs[d.mac], 'api_config_override')){
5505
+ // node.send({topic: 'post_update_sensor_report', payload: node._gateway_node.sensor_configs[d.mac], addr: d.mac, time: Date.now()});
5506
+ // }
5507
+ // }
5508
+ // });
5509
+
5510
+ node.gateway.on('sync', (d) => {
5511
+ switch (d.payload.type){
5512
+ case 'sync_check_in':
5513
+ // node.set_status();
5514
+ node.send({
5515
+ 'topic': 'sync',
5516
+ 'type': d.payload.type,
5517
+ ...d,
5518
+ 'time': Date.now()
5519
+ });
5520
+ break;
5521
+ case 'sync_init':
5522
+ // node.set_status();
5523
+ node.send({
5524
+ 'topic': 'sync',
5525
+ 'type': d.payload.type,
5526
+ ...d,
5527
+ 'time': Date.now()
5528
+ });
5529
+ break;
5530
+ case 'sync_end':
5531
+ // node.set_status();
5532
+ node.send({
5533
+ 'topic': 'sync',
5534
+ 'type': d.payload.type,
5535
+ ...d,
5536
+ 'time': Date.now()
5537
+ });
5538
+ break;
5539
+ case 'manual_sync_check_in':
5540
+ // node.set_status();
5541
+ node.send({
5542
+ 'topic': 'sync',
5543
+ 'type': d.payload.type,
5544
+ ...d,
5545
+ 'time': Date.now()
5546
+ });
5547
+ break;
5548
+ case 'sync_acknowledgment':
5549
+ // node.set_status();
5550
+ node.send({
5551
+ 'topic': 'sync',
5552
+ 'type': d.payload.type,
5553
+ ...d,
5554
+ 'time': Date.now()
5555
+ });
5556
+ break;
4956
5557
  }
4957
5558
  });
4958
-
4959
5559
  node._gateway_node.on('config_node_msg', (d) => {
4960
5560
  node.send(d);
4961
5561
  });
@@ -4967,13 +5567,14 @@ module.exports = function(RED) {
4967
5567
  node._gateway_node.removeAllListeners('config_node_msg');
4968
5568
  node._gateway_node.removeAllListeners('config_node_error');
4969
5569
  node._gateway_node.removeAllListeners('sensor_mode');
5570
+ node._gateway_node.removeAllListeners('sync');
4970
5571
  done();
4971
5572
  });
4972
-
4973
5573
 
4974
5574
  this.get_all_sensor_info = function(){
4975
5575
  return node._gateway_node.sensor_configs;
4976
5576
  };
5577
+
4977
5578
  this.get_sensor_array_info = function(sensor_array){
4978
5579
  let response = {
4979
5580
  status: 0,