nodejs-poolcontroller 7.2.0 → 7.5.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.
Files changed (64) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
  2. package/Changelog +13 -0
  3. package/Dockerfile +1 -0
  4. package/README.md +5 -5
  5. package/app.ts +11 -0
  6. package/config/Config.ts +3 -0
  7. package/config/VersionCheck.ts +8 -4
  8. package/controller/Constants.ts +165 -9
  9. package/controller/Equipment.ts +186 -65
  10. package/controller/Errors.ts +22 -1
  11. package/controller/State.ts +273 -57
  12. package/controller/boards/EasyTouchBoard.ts +194 -95
  13. package/controller/boards/IntelliCenterBoard.ts +115 -42
  14. package/controller/boards/IntelliTouchBoard.ts +104 -30
  15. package/controller/boards/NixieBoard.ts +155 -53
  16. package/controller/boards/SystemBoard.ts +1529 -514
  17. package/controller/comms/Comms.ts +219 -42
  18. package/controller/comms/messages/Messages.ts +16 -4
  19. package/controller/comms/messages/config/ChlorinatorMessage.ts +13 -3
  20. package/controller/comms/messages/config/CircuitGroupMessage.ts +6 -0
  21. package/controller/comms/messages/config/CircuitMessage.ts +1 -1
  22. package/controller/comms/messages/config/CoverMessage.ts +1 -0
  23. package/controller/comms/messages/config/EquipmentMessage.ts +4 -0
  24. package/controller/comms/messages/config/ExternalMessage.ts +43 -25
  25. package/controller/comms/messages/config/FeatureMessage.ts +8 -1
  26. package/controller/comms/messages/config/GeneralMessage.ts +8 -0
  27. package/controller/comms/messages/config/HeaterMessage.ts +15 -9
  28. package/controller/comms/messages/config/IntellichemMessage.ts +4 -1
  29. package/controller/comms/messages/config/OptionsMessage.ts +13 -1
  30. package/controller/comms/messages/config/PumpMessage.ts +4 -20
  31. package/controller/comms/messages/config/RemoteMessage.ts +4 -0
  32. package/controller/comms/messages/config/ScheduleMessage.ts +11 -0
  33. package/controller/comms/messages/config/SecurityMessage.ts +1 -0
  34. package/controller/comms/messages/config/ValveMessage.ts +12 -2
  35. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +14 -6
  36. package/controller/comms/messages/status/EquipmentStateMessage.ts +78 -24
  37. package/controller/comms/messages/status/HeaterStateMessage.ts +25 -5
  38. package/controller/comms/messages/status/IntelliChemStateMessage.ts +55 -26
  39. package/controller/nixie/Nixie.ts +18 -16
  40. package/controller/nixie/NixieEquipment.ts +6 -6
  41. package/controller/nixie/bodies/Body.ts +7 -4
  42. package/controller/nixie/bodies/Filter.ts +7 -4
  43. package/controller/nixie/chemistry/ChemController.ts +800 -283
  44. package/controller/nixie/chemistry/Chlorinator.ts +22 -14
  45. package/controller/nixie/circuits/Circuit.ts +42 -7
  46. package/controller/nixie/heaters/Heater.ts +303 -30
  47. package/controller/nixie/pumps/Pump.ts +57 -30
  48. package/controller/nixie/schedules/Schedule.ts +10 -7
  49. package/controller/nixie/valves/Valve.ts +7 -5
  50. package/defaultConfig.json +32 -1
  51. package/issue_template.md +1 -1
  52. package/logger/DataLogger.ts +37 -22
  53. package/package.json +20 -18
  54. package/web/Server.ts +529 -31
  55. package/web/bindings/influxDB.json +157 -5
  56. package/web/bindings/mqtt.json +112 -13
  57. package/web/bindings/mqttAlt.json +109 -11
  58. package/web/interfaces/baseInterface.ts +2 -1
  59. package/web/interfaces/httpInterface.ts +2 -0
  60. package/web/interfaces/influxInterface.ts +103 -54
  61. package/web/interfaces/mqttInterface.ts +16 -5
  62. package/web/services/config/Config.ts +179 -43
  63. package/web/services/state/State.ts +51 -5
  64. package/web/services/state/StateSocket.ts +19 -2
@@ -50,7 +50,8 @@ export class NixieBoard extends SystemBoard {
50
50
  [10, { name: 'colorcascade', desc: 'ColorCascade', isLight: true }],
51
51
  [11, { name: 'mastercleaner2', desc: 'Master Cleaner 2' }],
52
52
  [12, { name: 'pool', desc: 'Pool', hasHeatSource: true }],
53
- [13, { name: 'spa', desc: 'Spa', hasHeatSource: true }]
53
+ [13, { name: 'spa', desc: 'Spa', hasHeatSource: true }],
54
+ [14, { name: 'colorlogic', desc: 'ColorLogic', isLight:true }]
54
55
  ]);
55
56
  this.valueMaps.pumpTypes = new byteValueMap([
56
57
  [1, { name: 'ss', desc: 'Single Speed', maxCircuits: 0, hasAddress: false, hasBody: true, maxRelays: 1 }],
@@ -81,6 +82,20 @@ export class NixieBoard extends SystemBoard {
81
82
  [2, { name: 'off', desc: 'Off' }],
82
83
  [3, { name: 'ignore', desc: 'Ignore' }]
83
84
  ]);
85
+ this.valueMaps.chlorinatorModel = new byteValueMap([
86
+ [0, { name: 'unknown', desc: 'unknown', capacity: 0, chlorinePerDay: 0, chlorinePerSec: 0 }],
87
+ [1, { name: 'intellichlor--15', desc: 'IntelliChlor IC15', capacity: 15000, chlorinePerDay: 0.60, chlorinePerSec: 0.60 / 86400 }],
88
+ [2, { name: 'intellichlor--20', desc: 'IntelliChlor IC20', capacity: 20000, chlorinePerDay: 0.70, chlorinePerSec: 0.70 / 86400 }],
89
+ [3, { name: 'intellichlor--40', desc: 'IntelliChlor IC40', capacity: 40000, chlorinePerDay: 1.40, chlorinePerSec: 1.4 / 86400 }],
90
+ [4, { name: 'intellichlor--60', desc: 'IntelliChlor IC60', capacity: 60000, chlorinePerDay: 2, chlorinePerSec: 2 / 86400 }],
91
+ [5, { name: 'aquarite-t15', desc: 'AquaRite T15', capacity: 40000, chlorinePerDay: 1.47, chlorinePerSec: 1.47 / 86400 }],
92
+ [6, { name: 'aquarite-t9', desc: 'AquaRite T9', capacity: 30000, chlorinePerDay: 0.98, chlorinePerSec: 0.98 / 86400 }],
93
+ [7, { name: 'aquarite-t5', desc: 'AquaRite T5', capacity: 20000, chlorinePerDay: 0.735, chlorinePerSec: 0.735 / 86400 }],
94
+ [8, { name: 'aquarite-t3', desc: 'AquaRite T3', capacity: 15000, chlorinePerDay: 0.53, chlorinePerSec: 0.53 / 86400 }],
95
+ [9, { name: 'aquarite-925', desc: 'AquaRite 925', capacity: 25000, chlorinePerDay: 0.98, chlorinePerSec: 0.98 / 86400 }],
96
+ [10, { name: 'aquarite-940', desc: 'AquaRite 940', capacity: 40000, chlorinePerDay: 1.47, chlorinePerSec: 1.47 / 86400 }]
97
+ ]);
98
+
84
99
 
85
100
  // Keep this around for now so I can fart with the custom names array.
86
101
  //this.valueMaps.customNames = new byteValueMap(
@@ -126,7 +141,9 @@ export class NixieBoard extends SystemBoard {
126
141
  [1, { name: 'sunrise', desc: 'Sunrise' }],
127
142
  [2, { name: 'sunset', desc: 'Sunset' }]
128
143
  ]);
144
+
129
145
  this.valueMaps.lightThemes = new byteValueMap([
146
+ // IntelliBrite Themes
130
147
  [0, { name: 'white', desc: 'White', type: 'intellibrite', sequence: 11 }],
131
148
  [1, { name: 'green', desc: 'Green', type: 'intellibrite', sequence: 9 }],
132
149
  [2, { name: 'blue', desc: 'Blue', type: 'intellibrite', sequence: 8 }],
@@ -139,6 +156,24 @@ export class NixieBoard extends SystemBoard {
139
156
  [9, { name: 'american', desc: 'American', type: 'intellibrite', sequence: 5 }],
140
157
  [10, { name: 'sunset', desc: 'Sunset', type: 'intellibrite', sequence: 6 }],
141
158
  [11, { name: 'royal', desc: 'Royal', type: 'intellibrite', sequence: 7 }],
159
+ // ColorLogic Themes
160
+ [20, { name: 'cloudwhite', desc: 'Cloud White', type: 'colorlogic', sequence: 7 }],
161
+ [21, { name: 'deepsea', desc: 'Deep Sea', type: 'colorlogic', sequence: 2 }],
162
+ [22, { name: 'royalblue', desc: 'Royal Blue', type: 'colorlogic', sequence: 3 }],
163
+ [23, { name: 'afternoonskies', desc: 'Afternoon Skies', type: 'colorlogic', sequence: 4 }],
164
+ [24, { name: 'aquagreen', desc: 'Aqua Green', type: 'colorlogic', sequence: 5 }],
165
+ [25, { name: 'emerald', desc: 'Emerald', type: 'colorlogic', sequence: 6 }],
166
+ [26, { name: 'warmred', desc: 'Warm Red', type: 'colorlogic', sequence: 8 }],
167
+ [27, { name: 'flamingo', desc: 'Flamingo', type: 'colorlogic', sequence: 9 }],
168
+ [28, { name: 'vividviolet', desc: 'Vivid Violet', type: 'colorlogic', sequence: 10 }],
169
+ [29, { name: 'sangria', desc: 'Sangria', type: 'colorlogic', sequence: 11 }],
170
+ [30, { name: 'twilight', desc: 'Twilight', type: 'colorlogic', sequence: 12 }],
171
+ [31, { name: 'tranquility', desc: 'Tranquility', type: 'colorlogic', sequence: 13 }],
172
+ [32, { name: 'gemstone', desc: 'Gemstone', type: 'colorlogic', sequence: 14 }],
173
+ [33, { name: 'usa', desc: 'USA', type: 'colorlogic', sequence: 15 }],
174
+ [34, { name: 'mardigras', desc: 'Mardi Gras', type: 'colorlogic', sequence: 16 }],
175
+ [35, { name: 'coolcabaret', desc: 'Cabaret', type: 'colorlogic', sequence: 17 }],
176
+
142
177
  [255, { name: 'none', desc: 'None' }]
143
178
  ]);
144
179
  this.valueMaps.lightColors = new byteValueMap([
@@ -185,6 +220,7 @@ export class NixieBoard extends SystemBoard {
185
220
  public async initNixieBoard() {
186
221
  try {
187
222
  this.killStatusCheck();
223
+ let self = this;
188
224
  sys.general.options.clockSource = 'server';
189
225
  state.status = sys.board.valueMaps.controllerStatus.transform(0, 0);
190
226
  // First lets clear out all the messages.
@@ -224,7 +260,8 @@ export class NixieBoard extends SystemBoard {
224
260
  sys.equipment.maxCustomNames = 0;
225
261
  state.equipment.model = type.desc;
226
262
  state.equipment.maxBodies = sys.equipment.maxBodies = type.bodies;
227
-
263
+ let bodyUnits = sys.general.options.units === 0 ? 1 : 2;
264
+
228
265
  if (typeof state.temps.units === 'undefined' || state.temps.units < 0) state.temps.units = sys.general.options.units;
229
266
  if (type.bodies > 0) {
230
267
  let pool = sys.bodies.getItemById(1, true);
@@ -236,6 +273,7 @@ export class NixieBoard extends SystemBoard {
236
273
  pool.circuit = 6;
237
274
  pool.isActive = true;
238
275
  pool.master = 1;
276
+ pool.capacityUnits = bodyUnits;
239
277
  sbody.name = pool.name;
240
278
  sbody.setPoint = pool.setPoint;
241
279
  sbody.circuit = pool.circuit;
@@ -271,6 +309,7 @@ export class NixieBoard extends SystemBoard {
271
309
  sbody.setPoint = spa.setPoint;
272
310
  sbody.circuit = spa.circuit;
273
311
  sbody.type = spa.type;
312
+ spa.capacityUnits = bodyUnits;
274
313
  scirc = state.circuits.getItemById(1, true);
275
314
  scirc.showInFeatures = circ.showInFeatures;
276
315
  scirc.type = circ.type;
@@ -294,6 +333,7 @@ export class NixieBoard extends SystemBoard {
294
333
  sys.circuits.removeItemById(6);
295
334
  state.circuits.removeItemById(6);
296
335
  }
336
+
297
337
  sys.equipment.setEquipmentIds();
298
338
  sys.board.bodies.initFilters();
299
339
  state.status = sys.board.valueMaps.controllerStatus.transform(2, 0);
@@ -308,6 +348,7 @@ export class NixieBoard extends SystemBoard {
308
348
  total += sys.valves.length;
309
349
  total += sys.schedules.length;
310
350
  this.initValves();
351
+ sys.board.heaters.initTempSensors();
311
352
  await this.verifySetup();
312
353
  await ncp.initAsync(sys);
313
354
  sys.board.heaters.updateHeaterServices();
@@ -315,7 +356,7 @@ export class NixieBoard extends SystemBoard {
315
356
  logger.info(`${sys.equipment.model} control board initialized`);
316
357
  state.status = sys.board.valueMaps.controllerStatus.transform(1, 100);
317
358
  // At this point we should have the start of a board so lets check to see if we are ready or if we are stuck initializing.
318
- setTimeout(() => this.processStatusAsync(), 5000);
359
+ setTimeout(() => self.processStatusAsync(), 5000);
319
360
  } catch (err) { state.status = 255; logger.error(`Error Initializing Nixie Control Panel ${err.message}`); }
320
361
  }
321
362
  public initValves() {
@@ -476,15 +517,14 @@ export class NixieCircuitCommands extends CircuitCommands {
476
517
  else if (id === 1) state.temps.bodies.getItemById(2, true).isOn = val;
477
518
  // Let the main nixie controller set the circuit state and affect the relays if it needs to.
478
519
  await ncp.circuits.setCircuitStateAsync(circ, newState);
520
+ await sys.board.processStatusAsync();
479
521
  return state.circuits.getInterfaceById(circ.id);
480
522
  }
481
523
  catch (err) { return Promise.reject(`Nixie: Error setCircuitStateAsync ${err.message}`); }
482
524
  finally {
483
525
  state.emitEquipmentChanges();
484
- // sys.board.virtualPumpControllers.start();
485
526
  ncp.pumps.syncPumpStates();
486
527
  sys.board.suspendStatus(false);
487
- this.board.processStatusAsync();
488
528
  }
489
529
  }
490
530
  public toggleCircuitStateAsync(id: number): Promise<ICircuitState> {
@@ -493,6 +533,13 @@ export class NixieCircuitCommands extends CircuitCommands {
493
533
  }
494
534
  public async setLightThemeAsync(id: number, theme: number) {
495
535
  let cstate = state.circuits.getItemById(id);
536
+ let circ = sys.circuits.getItemById(id);
537
+ let thm = sys.board.valueMaps.lightThemes.findItem(theme);
538
+ if (typeof thm !== 'undefined' && typeof thm.sequence !== 'undefined' && circ.master === 1) {
539
+ logger.info(`Setting light theme for ${circ.name} to ${thm.name} [${thm.sequence}]`);
540
+ await sys.board.circuits.setCircuitStateAsync(id, true);
541
+ await ncp.circuits.sendOnOffSequenceAsync(id, thm.sequence);
542
+ }
496
543
  cstate.lightingTheme = theme;
497
544
  return Promise.resolve(cstate as ICircuitState);
498
545
  }
@@ -549,7 +596,15 @@ export class NixieCircuitCommands extends CircuitCommands {
549
596
  }
550
597
  return arrRefs;
551
598
  }
552
- public getLightThemes(type?: number) { return sys.board.valueMaps.lightThemes.toArray(); }
599
+ public getLightThemes(type?: number) {
600
+ let tobj = (typeof type === 'undefined') ? sys.board.valueMaps.circuitFunctions.transformByName('intellibrite') : sys.board.valueMaps.circuitFunctions.transform(type);
601
+ let arrThemes = sys.board.valueMaps.lightThemes.toArray();
602
+ let arr = [];
603
+ for (let i = 0; i < arrThemes.length; i++) {
604
+ if (tobj.name === arrThemes[i].type) arr.push(arrThemes[i]);
605
+ }
606
+ return arr;
607
+ }
553
608
  public getCircuitFunctions() { return sys.board.valueMaps.circuitFunctions.toArray(); }
554
609
  public getCircuitNames() {
555
610
  return [...sys.board.valueMaps.circuitNames.toArray(), ...sys.board.valueMaps.customNames.toArray()];
@@ -564,8 +619,8 @@ export class NixieCircuitCommands extends CircuitCommands {
564
619
  if (isNaN(id) || !sys.board.equipmentIds.circuits.isInRange(id)) return Promise.reject(new InvalidEquipmentIdError(`Invalid circuit id: ${data.id}`, data.id, 'Circuit'));
565
620
  let circuit = sys.circuits.getItemById(id, true);
566
621
  let scircuit = state.circuits.getItemById(id, true);
567
- circuit.isActive = true;
568
- scircuit.isOn = false;
622
+ scircuit.isActive = circuit.isActive = true;
623
+ circuit.master = 1;
569
624
  if (data.name) circuit.name = scircuit.name = data.name;
570
625
  else if (!circuit.name && !data.name) circuit.name = scircuit.name = Circuit.getIdName(id);
571
626
  if (typeof data.type !== 'undefined' || typeof circuit.type === 'undefined') circuit.type = scircuit.type = parseInt(data.type, 10) || 0;
@@ -586,8 +641,14 @@ export class NixieCircuitCommands extends CircuitCommands {
586
641
  let group: CircuitGroup = null;
587
642
  let id = typeof obj.id !== 'undefined' ? parseInt(obj.id, 10) : -1;
588
643
  if (id <= 0) {
589
- // We are adding a circuit group.
590
- id = sys.circuitGroups.getNextEquipmentId(sys.board.equipmentIds.circuitGroups);
644
+ // We are adding a circuit group so we need to get the next equipment id. For circuit groups and light groups, they share ids.
645
+ let range = sys.board.equipmentIds.circuitGroups;
646
+ for (let i = range.start; i <= range.end; i++) {
647
+ if (!sys.lightGroups.find(elem => elem.id === i) && !sys.circuitGroups.find(elem => elem.id === i)) {
648
+ id = i;
649
+ break;
650
+ }
651
+ }
591
652
  }
592
653
  if (typeof id === 'undefined') return Promise.reject(new InvalidEquipmentIdError(`Max circuit group id exceeded`, id, 'CircuitGroup'));
593
654
  if (isNaN(id) || !sys.board.equipmentIds.circuitGroups.isInRange(id)) return Promise.reject(new InvalidEquipmentIdError(`Invalid circuit group id: ${obj.id}`, obj.id, 'CircuitGroup'));
@@ -619,7 +680,7 @@ export class NixieCircuitCommands extends CircuitCommands {
619
680
  //RKS: 09-26-20 There is no such thing as a lighting theme on a circuit group circuit. That is what lighGroups are for.
620
681
  //if (typeof cobj.lightingTheme !== 'undefined') c.lightingTheme = parseInt(cobj.lightingTheme, 10);
621
682
  }
622
- // group.circuits.length = obj.circuits.length; // RSG - removed as this will delete circuits that were not changed
683
+ group.circuits.length = obj.circuits.length; // RSG - removed as this will delete circuits that were not changed
623
684
  }
624
685
  resolve(group);
625
686
  });
@@ -629,14 +690,21 @@ export class NixieCircuitCommands extends CircuitCommands {
629
690
  let group: LightGroup = null;
630
691
  let id = typeof obj.id !== 'undefined' ? parseInt(obj.id, 10) : -1;
631
692
  if (id <= 0) {
632
- // We are adding a circuit group.
633
- id = sys.circuitGroups.getNextEquipmentId(sys.board.equipmentIds.circuitGroups);
693
+ // We are adding a circuit group so we need to get the next equipment id. For circuit groups and light groups, they share ids.
694
+ let range = sys.board.equipmentIds.circuitGroups;
695
+ for (let i = range.start; i <= range.end; i++) {
696
+ if (!sys.lightGroups.find(elem => elem.id === i) && !sys.circuitGroups.find(elem => elem.id === i)) {
697
+ id = i;
698
+ break;
699
+ }
700
+ }
634
701
  }
635
702
  if (typeof id === 'undefined') return Promise.reject(new InvalidEquipmentIdError(`Max circuit light group id exceeded`, id, 'LightGroup'));
636
703
  if (isNaN(id) || !sys.board.equipmentIds.circuitGroups.isInRange(id)) return Promise.reject(new InvalidEquipmentIdError(`Invalid circuit group id: ${obj.id}`, obj.id, 'LightGroup'));
637
704
  group = sys.lightGroups.getItemById(id, true);
705
+ let sgroup = state.lightGroups.getItemById(id, true);
638
706
  return new Promise<LightGroup>((resolve, reject) => {
639
- if (typeof obj.name !== 'undefined') group.name = obj.name;
707
+ if (typeof obj.name !== 'undefined') sgroup.name = group.name = obj.name;
640
708
  if (typeof obj.dontStop !== 'undefined' && utils.makeBool(obj.dontStop) === true) obj.eggTimer = 1440;
641
709
  if (typeof obj.eggTimer !== 'undefined') group.eggTimer = Math.min(Math.max(parseInt(obj.eggTimer, 10), 0), 1440);
642
710
  group.dontStop = group.eggTimer === 1440;
@@ -654,7 +722,11 @@ export class NixieCircuitCommands extends CircuitCommands {
654
722
  if (typeof cobj.swimDelay !== 'undefined') c.swimDelay = parseInt(cobj.swimDelay, 10);
655
723
  if (typeof cobj.position !== 'undefined') c.position = parseInt(cobj.position, 10);
656
724
  }
725
+ // RKS: 09-25-21 - This has to be here. Not sure the goal of not setting the entire circuit array when saving the group.
657
726
  // group.circuits.length = obj.circuits.length; // RSG - removed as this will delete circuits that were not changed
727
+ group.circuits.length = obj.circuits.length;
728
+ sgroup.emitEquipmentChange();
729
+
658
730
  }
659
731
  resolve(group);
660
732
  });
@@ -696,24 +768,30 @@ export class NixieCircuitCommands extends CircuitCommands {
696
768
  return Promise.reject(new InvalidEquipmentIdError('Group id has not been defined', id, 'LightGroup'));
697
769
  }
698
770
  public async deleteCircuitAsync(data: any): Promise<ICircuit> {
699
- if (typeof data.id === 'undefined') return Promise.reject(new InvalidEquipmentIdError('You must provide an id to delete a circuit', data.id, 'Circuit'));
700
- let circuit = sys.circuits.getInterfaceById(data.id);
771
+ let id = parseInt(data.id, 10);
772
+ if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError(`Invalid circuit id: ${data.id}`, data.id, 'Circuit'));
773
+ if (!sys.board.equipmentIds.circuits.isInRange(id)) return Promise.reject(new InvalidEquipmentIdError(`Invalid circuit id: ${data.id}`, data.id, 'Circuit'));
774
+ let circuit = sys.circuits.getInterfaceById(id);
775
+ let cstate = state.circuits.getInterfaceById(id);
701
776
  if (circuit instanceof Circuit) {
702
- sys.circuits.removeItemById(data.id);
703
- state.circuits.removeItemById(data.id);
777
+ sys.circuits.removeItemById(circuit.id);
778
+ state.circuits.removeItemById(circuit.id);
779
+ cstate.isActive = circuit.isActive = false;
704
780
  }
705
781
  if (circuit instanceof Feature) {
706
- sys.features.removeItemById(data.id);
707
- state.features.removeItemById(data.id);
782
+ sys.features.removeItemById(circuit.id);
783
+ state.features.removeItemById(circuit.id);
784
+ cstate.isActive = circuit.isActive = false;
708
785
  }
786
+ cstate.emitEquipmentChange();
709
787
  return new Promise<ICircuit>((resolve, reject) => { resolve(circuit); });
710
788
  }
711
789
  public deleteCircuit(data: any) {
712
790
  if (typeof data.id !== 'undefined') {
713
791
  let circuit = sys.circuits.getInterfaceById(data.id);
714
792
  if (circuit instanceof Circuit) {
715
- sys.circuits.removeItemById(data.id);
716
- state.circuits.removeItemById(data.id);
793
+ sys.circuits.removeItemById(circuit.id);
794
+ state.circuits.removeItemById(circuit.id);
717
795
  return;
718
796
  }
719
797
  if (circuit instanceof Feature) {
@@ -800,13 +878,21 @@ export class NixieCircuitCommands extends CircuitCommands {
800
878
  }
801
879
  public async setCircuitGroupStateAsync(id: number, val: boolean): Promise<ICircuitGroupState> {
802
880
  let grp = sys.circuitGroups.getItemById(id, false, { isActive: false });
803
- let gstate = (grp.dataName === 'circuitGroupConfig') ? state.circuitGroups.getItemById(grp.id, grp.isActive !== false) : state.lightGroups.getItemById(grp.id, grp.isActive !== false);
881
+ if (grp.dataName !== 'circuitGroupConfig') return await sys.board.circuits.setLightGroupStateAsync(id, val);
882
+ let gstate = state.circuitGroups.getItemById(grp.id, grp.isActive !== false);
804
883
  let circuits = grp.circuits.toArray();
884
+ sys.board.circuits.setEndTime(sys.circuits.getInterfaceById(gstate.id), gstate, val);
805
885
  gstate.isOn = val;
806
886
  let arr = [];
807
887
  for (let i = 0; i < circuits.length; i++) {
808
888
  let circuit = circuits[i];
809
- arr.push(sys.board.circuits.setCircuitStateAsync(circuit.circuit, val));
889
+ // The desiredState will be as follows.
890
+ // 1 = on, 2 = off, 3 = ignore.
891
+ let cval = true;
892
+ if (circuit.desiredState === 1) cval = val ? true : false;
893
+ else if (circuit.desiredState === 2) cval = val ? false : true;
894
+ else continue;
895
+ arr.push(sys.board.circuits.setCircuitStateAsync(circuit.circuit, cval));
810
896
  }
811
897
  return new Promise<ICircuitGroupState>(async (resolve, reject) => {
812
898
  await Promise.all(arr).catch((err) => { reject(err) });
@@ -814,16 +900,22 @@ export class NixieCircuitCommands extends CircuitCommands {
814
900
  });
815
901
  }
816
902
  public async setLightGroupStateAsync(id: number, val: boolean): Promise<ICircuitGroupState> {
817
- return sys.board.circuits.setCircuitGroupStateAsync(id, val);
903
+ let grp = sys.circuitGroups.getItemById(id, false, { isActive: false });
904
+ if (grp.dataName === 'circuitGroupConfig') return await sys.board.circuits.setCircuitGroupStateAsync(id, val);
905
+ let gstate = state.lightGroups.getItemById(grp.id, grp.isActive !== false);
906
+ let circuits = grp.circuits.toArray();
907
+ sys.board.circuits.setEndTime(sys.circuits.getInterfaceById(gstate.id), gstate, val);
908
+ gstate.isOn = val;
909
+ let arr = [];
910
+ for (let i = 0; i < circuits.length; i++) {
911
+ let circuit = circuits[i];
912
+ arr.push(sys.board.circuits.setCircuitStateAsync(circuit.circuit, val));
913
+ }
914
+ return new Promise<ICircuitGroupState>(async (resolve, reject) => {
915
+ await Promise.all(arr).catch((err) => { reject(err) });
916
+ resolve(gstate);
917
+ });
818
918
  }
819
- /* public sequenceIntelliBrite(operation: string) {
820
- state.intellibrite.hasChanged = true;
821
- let nop = sys.board.valueMaps.intellibriteActions.getValue(operation);
822
- if (nop > 0) {
823
- state.intellibrite.action = nop;
824
- setTimeout(function() { state.intellibrite.action = 0; state.emitEquipmentChanges(); }, 20000); // It takes 20 seconds to sequence.
825
- }
826
- } */
827
919
  }
828
920
  export class NixieFeatureCommands extends FeatureCommands {
829
921
  public async setFeatureAsync(obj: any): Promise<Feature> {
@@ -864,6 +956,7 @@ export class NixieFeatureCommands extends FeatureCommands {
864
956
  feature.isActive = false;
865
957
  sfeature.isOn = false;
866
958
  sfeature.showInFeatures = false;
959
+ sfeature.isActive = false;
867
960
  sfeature.emitEquipmentChange();
868
961
  return new Promise<Feature>((resolve, reject) => { resolve(feature); });
869
962
  }
@@ -876,9 +969,9 @@ export class NixieFeatureCommands extends FeatureCommands {
876
969
  if (!sys.board.equipmentIds.features.isInRange(id)) return Promise.reject(new InvalidEquipmentIdError(`Invalid feature id: ${id}`, id, 'Feature'));
877
970
  let feature = sys.features.getItemById(id);
878
971
  let fstate = state.features.getItemById(feature.id, feature.isActive !== false);
972
+ sys.board.circuits.setEndTime(feature, fstate, val);
879
973
  fstate.isOn = val;
880
974
  sys.board.valves.syncValveStates();
881
- // sys.board.virtualPumpControllers.start();
882
975
  ncp.pumps.syncPumpStates();
883
976
  state.emitEquipmentChanges();
884
977
  return fstate;
@@ -889,39 +982,48 @@ export class NixieFeatureCommands extends FeatureCommands {
889
982
  return this.setFeatureStateAsync(id, !(feat.isOn || false));
890
983
  }
891
984
  public syncGroupStates() {
985
+ // The way this should work is that when all of the states are met
986
+ // the group should be on. Otherwise it should be off. That means that if
987
+ // you turned on all the group circuits that should be on individually then
988
+ // the group should be on.
892
989
  for (let i = 0; i < sys.circuitGroups.length; i++) {
893
990
  let grp: CircuitGroup = sys.circuitGroups.getItemByIndex(i);
894
991
  let circuits = grp.circuits.toArray();
895
- let bIsOn = false;
896
992
  if (grp.isActive) {
897
- for (let j = 0; j < circuits.length; j++) {
898
- let circuit: CircuitGroupCircuit = grp.circuits.getItemById(j);
993
+ let bIsOn = true;
994
+ // Iterate the circuits and break out should we find a condition
995
+ // where the group should be off.
996
+ for (let j = 0; j < circuits.length && bIsOn === true; j++) {
997
+ let circuit: CircuitGroupCircuit = grp.circuits.getItemByIndex(j);
899
998
  let cstate = state.circuits.getInterfaceById(circuit.circuit);
900
- if (circuit.desiredState === 1 || circuit.desiredState === 0) {
901
- if (cstate.isOn === utils.makeBool(circuit.desiredState)) bIsOn = true;
999
+ if (circuit.desiredState === 1) { // The circuit should be on.
1000
+ if (!utils.makeBool(cstate.isOn)) bIsOn = false;
1001
+ }
1002
+ else if (circuit.desiredState === 0) { // The circuit should be off.
1003
+ if (utils.makeBool(cstate.isOn)) bIsOn = false;
902
1004
  }
903
1005
  }
1006
+ let sgrp = state.circuitGroups.getItemById(grp.id);
1007
+ sgrp.isOn = bIsOn;
904
1008
  }
905
- let sgrp = state.circuitGroups.getItemById(grp.id);
906
- sgrp.isOn = bIsOn && grp.isActive;
907
-
908
1009
  sys.board.valves.syncValveStates();
909
1010
  }
910
1011
  // I am guessing that there will only be one here but iterate
911
1012
  // just in case we expand.
912
1013
  for (let i = 0; i < sys.lightGroups.length; i++) {
913
1014
  let grp: LightGroup = sys.lightGroups.getItemByIndex(i);
914
- let bIsOn = false;
1015
+ let circuits = grp.circuits.toArray();
915
1016
  if (grp.isActive) {
916
- let circuits = grp.circuits.toArray();
917
- for (let j = 0; j < circuits.length; j++) {
918
- let circuit = grp.circuits.getItemByIndex(j).circuit;
919
- let cstate = state.circuits.getInterfaceById(circuit);
920
- if (cstate.isOn) bIsOn = true;
1017
+ let bIsOn = true;
1018
+ for (let j = 0; j < circuits.length && bIsOn === true; j++) {
1019
+ let circuit: LightGroupCircuit = grp.circuits.getItemByIndex(j);
1020
+ let cstate = state.circuits.getInterfaceById(circuit.circuit);
1021
+ if (!utils.makeBool(cstate.isOn)) bIsOn = false;
921
1022
  }
1023
+ let sgrp = state.lightGroups.getItemById(grp.id);
1024
+ sgrp.isOn = bIsOn;
922
1025
  }
923
- let sgrp = state.lightGroups.getItemById(grp.id);
924
- sgrp.isOn = bIsOn;
1026
+ sys.board.valves.syncValveStates();
925
1027
  }
926
1028
  state.emitEquipmentChanges();
927
1029
  }
@@ -946,7 +1048,7 @@ export class NixieValveCommands extends ValveCommands {
946
1048
  valve.deviceBinding = typeof obj.deviceBinding !== 'undefined' ? obj.deviceBinding : valve.deviceBinding;
947
1049
  valve.pinId = typeof obj.pinId !== 'undefined' ? obj.pinId : valve.pinId;
948
1050
  await ncp.valves.setValveAsync(valve, obj);
949
- sys.board.processStatusAsync();
1051
+ await sys.board.syncEquipmentItems();
950
1052
  return valve;
951
1053
  } catch (err) { logger.error(`Nixie: Error setting valve definition. ${err.message}`); return Promise.reject(err); }
952
1054
  }
@@ -982,7 +1084,7 @@ export class NixieHeaterCommands extends HeaterCommands {
982
1084
  let heater: Heater;
983
1085
  if (id <= 0) {
984
1086
  // We are adding a heater. In this case all heaters are virtual.
985
- let vheaters = sys.heaters.filter(h => h.isVirtual === true);
1087
+ let vheaters = sys.heaters.filter(h => h.master === 1);
986
1088
  id = vheaters.length + 256;
987
1089
  }
988
1090
  heater = sys.heaters.getItemById(id, true);
@@ -993,7 +1095,7 @@ export class NixieHeaterCommands extends HeaterCommands {
993
1095
  }
994
1096
  }
995
1097
  let hstate = state.heaters.getItemById(id, true);
996
- hstate.isVirtual = heater.isVirtual = true;
1098
+ //hstate.isVirtual = heater.isVirtual = true;
997
1099
  hstate.name = heater.name;
998
1100
  hstate.type = heater.type;
999
1101
  heater.master = 1;