nodejs-poolcontroller 7.7.0 → 8.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/.eslintrc.json +26 -35
  2. package/Changelog +22 -0
  3. package/README.md +7 -3
  4. package/anslq25/MessagesMock.ts +218 -0
  5. package/anslq25/boards/MockBoardFactory.ts +50 -0
  6. package/anslq25/boards/MockEasyTouchBoard.ts +696 -0
  7. package/anslq25/boards/MockSystemBoard.ts +217 -0
  8. package/anslq25/chemistry/MockChlorinator.ts +75 -0
  9. package/anslq25/pumps/MockPump.ts +84 -0
  10. package/app.ts +10 -14
  11. package/config/Config.ts +13 -9
  12. package/config/VersionCheck.ts +6 -2
  13. package/controller/Constants.ts +58 -25
  14. package/controller/Equipment.ts +225 -41
  15. package/controller/Errors.ts +2 -1
  16. package/controller/Lockouts.ts +34 -2
  17. package/controller/State.ts +491 -48
  18. package/controller/boards/AquaLinkBoard.ts +6 -3
  19. package/controller/boards/BoardFactory.ts +5 -1
  20. package/controller/boards/EasyTouchBoard.ts +1971 -1751
  21. package/controller/boards/IntelliCenterBoard.ts +1311 -1688
  22. package/controller/boards/IntelliComBoard.ts +7 -1
  23. package/controller/boards/IntelliTouchBoard.ts +153 -42
  24. package/controller/boards/NixieBoard.ts +209 -66
  25. package/controller/boards/SunTouchBoard.ts +393 -0
  26. package/controller/boards/SystemBoard.ts +1862 -1543
  27. package/controller/comms/Comms.ts +539 -138
  28. package/controller/comms/ScreenLogic.ts +1663 -0
  29. package/controller/comms/messages/Messages.ts +242 -60
  30. package/controller/comms/messages/config/ChlorinatorMessage.ts +4 -3
  31. package/controller/comms/messages/config/CircuitGroupMessage.ts +5 -2
  32. package/controller/comms/messages/config/CircuitMessage.ts +81 -13
  33. package/controller/comms/messages/config/ConfigMessage.ts +3 -1
  34. package/controller/comms/messages/config/CoverMessage.ts +2 -1
  35. package/controller/comms/messages/config/CustomNameMessage.ts +2 -1
  36. package/controller/comms/messages/config/EquipmentMessage.ts +5 -1
  37. package/controller/comms/messages/config/ExternalMessage.ts +33 -3
  38. package/controller/comms/messages/config/FeatureMessage.ts +2 -1
  39. package/controller/comms/messages/config/GeneralMessage.ts +2 -1
  40. package/controller/comms/messages/config/HeaterMessage.ts +3 -1
  41. package/controller/comms/messages/config/IntellichemMessage.ts +2 -1
  42. package/controller/comms/messages/config/OptionsMessage.ts +12 -6
  43. package/controller/comms/messages/config/PumpMessage.ts +9 -12
  44. package/controller/comms/messages/config/RemoteMessage.ts +80 -13
  45. package/controller/comms/messages/config/ScheduleMessage.ts +43 -3
  46. package/controller/comms/messages/config/SecurityMessage.ts +2 -1
  47. package/controller/comms/messages/config/ValveMessage.ts +43 -26
  48. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +8 -7
  49. package/controller/comms/messages/status/EquipmentStateMessage.ts +93 -20
  50. package/controller/comms/messages/status/HeaterStateMessage.ts +24 -5
  51. package/controller/comms/messages/status/IntelliChemStateMessage.ts +7 -4
  52. package/controller/comms/messages/status/IntelliValveStateMessage.ts +2 -1
  53. package/controller/comms/messages/status/PumpStateMessage.ts +72 -4
  54. package/controller/comms/messages/status/VersionMessage.ts +2 -1
  55. package/controller/nixie/Nixie.ts +15 -4
  56. package/controller/nixie/NixieEquipment.ts +1 -0
  57. package/controller/nixie/chemistry/ChemController.ts +300 -129
  58. package/controller/nixie/chemistry/ChemDoser.ts +806 -0
  59. package/controller/nixie/chemistry/Chlorinator.ts +133 -129
  60. package/controller/nixie/circuits/Circuit.ts +171 -30
  61. package/controller/nixie/heaters/Heater.ts +337 -173
  62. package/controller/nixie/pumps/Pump.ts +264 -236
  63. package/controller/nixie/schedules/Schedule.ts +9 -3
  64. package/defaultConfig.json +46 -5
  65. package/logger/Logger.ts +38 -9
  66. package/package.json +13 -9
  67. package/web/Server.ts +235 -122
  68. package/web/bindings/aqualinkD.json +114 -59
  69. package/web/bindings/homeassistant.json +437 -0
  70. package/web/bindings/influxDB.json +15 -0
  71. package/web/bindings/mqtt.json +28 -9
  72. package/web/bindings/mqttAlt.json +15 -0
  73. package/web/interfaces/baseInterface.ts +58 -7
  74. package/web/interfaces/httpInterface.ts +5 -2
  75. package/web/interfaces/influxInterface.ts +9 -2
  76. package/web/interfaces/mqttInterface.ts +234 -74
  77. package/web/interfaces/ruleInterface.ts +87 -0
  78. package/web/services/config/Config.ts +140 -33
  79. package/web/services/config/ConfigSocket.ts +2 -1
  80. package/web/services/state/State.ts +144 -3
  81. package/web/services/state/StateSocket.ts +65 -14
  82. package/web/services/utilities/Utilities.ts +189 -1
@@ -1,5 +1,6 @@
1
1
  /* nodejs-poolController. An application to control pool equipment.
2
- Copyright (C) 2016, 2017, 2018, 2019, 2020. Russell Goldin, tagyoureit. russ.goldin@gmail.com
2
+ Copyright (C) 2016, 2017, 2018, 2019, 2020, 2021, 2022.
3
+ Russell Goldin, tagyoureit. russ.goldin@gmail.com
3
4
 
4
5
  This program is free software: you can redistribute it and/or modify
5
6
  it under the terms of the GNU Affero General Public License as
@@ -31,13 +32,16 @@ import { conn } from './comms/Comms';
31
32
  import { versionCheck } from "../config/VersionCheck";
32
33
  import { NixieControlPanel } from "./nixie/Nixie";
33
34
  import { NixieBoard } from 'controller/boards/NixieBoard';
34
- import { child } from "winston";
35
+ import { MockSystemBoard } from "../anslq25/boards/MockSystemBoard";
36
+ import { MockBoardFactory } from "../anslq25/boards/MockBoardFactory";
37
+ import { ScreenLogicComms } from "./comms/ScreenLogic";
35
38
 
36
39
  interface IPoolSystem {
37
40
  cfgPath: string;
38
41
  data: any;
39
42
  stopAsync(): void;
40
43
  persist(): void;
44
+ anslq25: Anslq25;
41
45
  general: General;
42
46
  equipment: Equipment;
43
47
  configVersion: ConfigVersion;
@@ -74,9 +78,9 @@ export class PoolSystem implements IPoolSystem {
74
78
  constructor() {
75
79
  this.cfgPath = path.posix.join(process.cwd(), '/data/poolConfig.json');
76
80
  }
77
- public getAvailableControllerTypes() {
81
+ public getAvailableControllerTypes(include:string[] = ['easytouch', 'intellitouch', 'intellicenter', 'nixie']) {
78
82
  let arr = [];
79
- arr.push({
83
+ if (include.indexOf('easytouch')>=0) arr.push({
80
84
  type: 'easytouch', name: 'EasyTouch',
81
85
  models: [
82
86
  { val: 0, name: 'ET28', part: 'ET2-8', desc: 'EasyTouch2 8', circuits: 8, bodies: 2, shared: true },
@@ -89,10 +93,10 @@ export class PoolSystem implements IPoolSystem {
89
93
  { val: 128, name: 'ET8', part: 'ET-8', desc: 'EasyTouch 8', circuits: 8, bodies: 2, shared: true },
90
94
  { val: 129, name: 'ET8P', part: 'ET-8P', desc: 'EasyTouch 8', circuits: 8, bodies: 1, shared: false },
91
95
  { val: 130, name: 'ET4', part: 'ET-4', desc: 'EasyTouch 4', circuits: 4, bodies: 2, shared: true },
92
- { val: 129, name: 'ET4P', part: 'ET-4P', desc: 'EasyTouch 4P', circuits: 4, bodies: 1, shared: false }
96
+ { val: 131, name: 'ET4P', part: 'ET-4P', desc: 'EasyTouch 4P', circuits: 4, bodies: 1, shared: false }
93
97
  ]
94
98
  });
95
- arr.push({
99
+ if (include.indexOf('intellitouch')>=0) arr.push({
96
100
  type: 'intellitouch', name: 'IntelliTouch',
97
101
  models: [
98
102
  { val: 0, name: 'IT5', part: 'i5+3', desc: 'IntelliTouch i5+3', bodies: 2, circuits: 6, shared: true },
@@ -108,7 +112,7 @@ export class PoolSystem implements IPoolSystem {
108
112
  ]
109
113
 
110
114
  });
111
- arr.push({
115
+ if (include.indexOf('intellicenter')>=0) arr.push({
112
116
  type: 'intellicenter', name: 'IntelliCenter',
113
117
  models: [
114
118
  { val: 0, name: 'i5P', part: '523125Z', desc: 'IntelliCenter i5P', bodies: 1, valves: 2, circuits: 5, shared: false, dual: false, chlorinators: 1, chemControllers: 1 },
@@ -120,7 +124,7 @@ export class PoolSystem implements IPoolSystem {
120
124
  { val: 7, name: 'i10D', part: '523029Z', desc: 'IntelliCenter i10D', bodies: 2, valves: 2, circuits: 11, shared: false, dual: true, chlorinators: 2, chemControllers: 2 },
121
125
  ]
122
126
  });
123
- arr.push({
127
+ if (include.indexOf('nixie')>=0) arr.push({
124
128
  type: 'nixie', name: 'Nixie', canChange: true,
125
129
  models: [
126
130
  { val: 0, name: 'nxp', part: 'NXP', desc: 'Nixie Single Body', bodies: 1, shared: false, dual: false },
@@ -163,8 +167,15 @@ export class PoolSystem implements IPoolSystem {
163
167
  EqItemCollection.removeNullIds(cfg.lightGroups);
164
168
  EqItemCollection.removeNullIds(cfg.remotes);
165
169
  EqItemCollection.removeNullIds(cfg.chemControllers);
170
+ EqItemCollection.removeNullIds(cfg.chemDosers);
166
171
  EqItemCollection.removeNullIds(cfg.filters);
172
+ if (typeof cfg.pumps !== 'undefined') {
173
+ for (let i = 0; i < cfg.pumps.length; i++) {
174
+ EqItemCollection.removeNullIds(cfg.pumps[i].circuits);
175
+ }
176
+ }
167
177
  this.data = this.onchange(cfg, function () { sys.dirty = true; });
178
+ this.anslq25 = new Anslq25(this.data, 'anslq25');
168
179
  this.general = new General(this.data, 'pool');
169
180
  this.equipment = new Equipment(this.data, 'equipment');
170
181
  this.configVersion = new ConfigVersion(this.data, 'configVersion');
@@ -184,8 +195,10 @@ export class PoolSystem implements IPoolSystem {
184
195
  this.customNames = new CustomNameCollection(this.data, 'customNames');
185
196
  this.eggTimers = new EggTimerCollection(this.data, 'eggTimers');
186
197
  this.chemControllers = new ChemControllerCollection(this.data, 'chemControllers');
198
+ this.chemDosers = new ChemDoserCollection(this.data, 'chemDosers');
187
199
  this.filters = new FilterCollection(this.data, 'filters');
188
200
  this.board = BoardFactory.fromControllerType(this.controllerType, this);
201
+ this.anslq25Board = MockBoardFactory.fromControllerType(this.data.anslq25.controllerType, this);
189
202
  }
190
203
  // This performs a safe load of the config file. If the file gets corrupt or actually does not exist
191
204
  // it will not break the overall system and allow hardened recovery.
@@ -214,11 +227,26 @@ export class PoolSystem implements IPoolSystem {
214
227
  this.board = BoardFactory.fromControllerType(ControllerType.Unknown, this);
215
228
  setTimeout(function () { state.status = 0; conn.resumeAll(); }, 0);
216
229
  }
230
+ public get anslq25ControllerType(): ControllerType { return this.data.anslq25.controllerType as ControllerType; }
231
+ public set anslq25ControllerType(val: ControllerType) {
232
+ //let self = this;
233
+ if (this.anslq25ControllerType !== val) {
234
+ console.log(`Mock Controller type changed from ${this.anslq25.controllerType} to ${val}`);
235
+ // Only go in here if there is a change to the controller type.
236
+ //this.resetData(); // Clear the configuration data.
237
+ //state.resetData(); // Clear the state data.
238
+ this.data.anslq25.controllerType = val;
239
+ //EquipmentStateMessage.initDefaults();
240
+ // We are actually changing the config so lets clear out all the data.
241
+ this.anslq25Board = MockBoardFactory.fromControllerType(val, this);
242
+ //if (this.data.anslq25.controllerType === ControllerType.Unknown) setTimeout(() => { self.initNixieController(); }, 7500);
243
+ }
244
+ }
217
245
  public get controllerType(): ControllerType { return this.data.controllerType as ControllerType; }
218
246
  public set controllerType(val: ControllerType) {
219
247
  let self = this;
220
- if (this.controllerType !== val || this.controllerType === ControllerType.Virtual) {
221
- console.log('RESETTING DATA -- Data files backed up to ./logs directory.');
248
+ if (this.controllerType !== val) {
249
+ console.log(`RESETTING DATA -- Controller type changed from ${this.controllerType} to ${val}`);
222
250
  // Only go in here if there is a change to the controller type.
223
251
  this.resetData(); // Clear the configuration data.
224
252
  state.resetData(); // Clear the state data.
@@ -226,11 +254,11 @@ export class PoolSystem implements IPoolSystem {
226
254
  EquipmentStateMessage.initDefaults();
227
255
  // We are actually changing the config so lets clear out all the data.
228
256
  this.board = BoardFactory.fromControllerType(val, this);
229
- if (this.data.controllerType === ControllerType.Unknown || this.controllerType === ControllerType.Virtual) setTimeout(() => { self.initNixieController(); }, 7500);
257
+ if (this.data.controllerType === ControllerType.Unknown) setTimeout(() => { self.initNixieController(); }, 7500);
230
258
  }
231
259
  }
232
260
  public resetData() {
233
- if (sys.controllerType !== ControllerType.Virtual && sys.controllerType !== ControllerType.Nixie) {
261
+ if (sys.controllerType !== ControllerType.Nixie) {
234
262
  // Do not clear this out if it is a virtual controller this causes problems.
235
263
  this.equipment.reset();
236
264
  this.circuitGroups.clear(0);
@@ -259,7 +287,7 @@ export class PoolSystem implements IPoolSystem {
259
287
  public async stopAsync() {
260
288
  if (this._timerChanges) clearTimeout(this._timerChanges);
261
289
  if (this._timerDirty) clearTimeout(this._timerDirty);
262
- logger.info(`Shut down sys (config) object timers`);
290
+ logger.info(`Shut down sys (config) object timers`);
263
291
  return this.board.stopAsync();
264
292
  }
265
293
  public initNixieController() {
@@ -267,7 +295,6 @@ export class PoolSystem implements IPoolSystem {
267
295
  switch (sys.controllerType || ControllerType.Unknown) {
268
296
  case ControllerType.Unknown:
269
297
  case ControllerType.Nixie:
270
- case ControllerType.Virtual:
271
298
  state.equipment.controllerType = sys.controllerType = ControllerType.Nixie;
272
299
  let board = sys.board as NixieBoard;
273
300
  (async () => { await board.initNixieBoard(); })();
@@ -275,6 +302,7 @@ export class PoolSystem implements IPoolSystem {
275
302
  }
276
303
  }
277
304
  public board: SystemBoard = new SystemBoard(this);
305
+ public anslq25Board: MockSystemBoard; // = new MockSystemBoard(this);
278
306
  public ncp: NixieControlPanel = new NixieControlPanel();
279
307
  public processVersionChanges(ver: ConfigVersion) { this.board.requestConfiguration(ver); }
280
308
  public checkConfiguration() { this.board.checkConfiguration(); }
@@ -286,6 +314,7 @@ export class PoolSystem implements IPoolSystem {
286
314
  protected _timerChanges: NodeJS.Timeout;
287
315
  protected _needsChanges: boolean;
288
316
  // All the equipment items below.
317
+ public anslq25: Anslq25;
289
318
  public general: General;
290
319
  public equipment: Equipment;
291
320
  public configVersion: ConfigVersion;
@@ -305,7 +334,9 @@ export class PoolSystem implements IPoolSystem {
305
334
  public security: Security;
306
335
  public customNames: CustomNameCollection;
307
336
  public chemControllers: ChemControllerCollection;
337
+ public chemDosers: ChemDoserCollection;
308
338
  public filters: FilterCollection;
339
+ public screenlogic: ScreenLogicComms;
309
340
  public appVersion: string;
310
341
  public get dirty(): boolean { return this._isDirty; }
311
342
  public set dirty(val) {
@@ -527,7 +558,9 @@ class EqItemCollection<T> implements IEqItemCollection {
527
558
  public static removeNullIds(data: any) {
528
559
  if (typeof data !== 'undefined' && Array.isArray(data) && typeof data.length === 'number') {
529
560
  for (let i = data.length - 1; i >= 0; i--) {
530
- if (typeof data[i].id !== 'number') data.splice(i, 1);
561
+ if (typeof data[i].id !== 'number') {
562
+ data.splice(i, 1);
563
+ }
531
564
  else if (typeof data[i].id === 'undefined' || isNaN(data[i].id)) data.splice(i, 1);
532
565
  }
533
566
  }
@@ -601,7 +634,9 @@ class EqItemCollection<T> implements IEqItemCollection {
601
634
  public get length(): number { return typeof this.data !== 'undefined' ? this.data.length : 0; }
602
635
  public set length(val: number) { if (typeof val !== 'undefined' && typeof this.data !== 'undefined') this.data.length = val; }
603
636
  public add(obj: any): T { this.data.push(obj); return this.createItem(obj); }
604
- public get(): any { return this.data; }
637
+ public get(bCopy?: boolean): any {
638
+ return bCopy ? JSON.parse(JSON.stringify(this.data)) : this.data;
639
+ }
605
640
  public emitEquipmentChange() { webApp.emitToClients(this.name, this.data); }
606
641
  public sortByName() {
607
642
  this.sort((a, b) => {
@@ -615,8 +650,11 @@ class EqItemCollection<T> implements IEqItemCollection {
615
650
  }
616
651
  public sort(fn: (a, b) => number) { this.data.sort(fn); }
617
652
  public count(fn: (value: T, index?: any, array?: any[]) => boolean): number { return this.data.filter(fn).length; }
618
- public getNextEquipmentId(range: EquipmentIdRange, exclude?: number[]): number {
619
- for (let i = range.start; i <= range.end; i++) {
653
+ public getNextEquipmentId(range?: EquipmentIdRange, exclude?: number[]): number {
654
+ // RG 12-4-22 for some reason extend(true, {...}, range) was not evaluating the accessors
655
+ // for range.start & range.end
656
+ let r = extend(true, { start: 1, end: 255 }, {start: range.start }, {end: range.end});
657
+ for (let i = r.start; i <= r.end; i++) {
620
658
  let eq = this.data.find(elem => elem.id === i);
621
659
  if (typeof eq === 'undefined') {
622
660
  if (typeof exclude !== 'undefined' && exclude.indexOf(i) !== -1) continue;
@@ -645,6 +683,23 @@ class EqItemCollection<T> implements IEqItemCollection {
645
683
  return typeof minId !== 'undefined' ? minId : defId;
646
684
  }
647
685
  }
686
+ export class Anslq25 extends EqItem {
687
+ ctor(data: any, name?: any): Anslq25 { return new Anslq25(data, name || 'anslq25'); }
688
+ public initData(){
689
+ if (typeof this.data.isActive === 'undefined') this.data.isActive = false;
690
+ }
691
+ public get controllerType(): ControllerType { return this.data.controllerType; }
692
+ public set controllerType(val: ControllerType) { this.setDataVal('controllerType', val); }
693
+ public get model(): number { return this.data.model; }
694
+ public set model(val: number) { this.setDataVal('model', val); }
695
+ public get isActive(): boolean { return this.data.isActive; }
696
+ public set isActive(val: boolean) { this.setDataVal('isActive', val); }
697
+ public get portId(): number { return this.data.portId; }
698
+ public set portId(val: number) { this.setDataVal('portId', val); }
699
+ public get broadcastComms(): boolean { return this.data.broadcastComms; }
700
+ public set broadcastComms(val: boolean) { this.setDataVal('broadcastComms', val); }
701
+ public get modules(): ExpansionModuleCollection { return new ExpansionModuleCollection(this.data, "modules"); }
702
+ }
648
703
  export class General extends EqItem {
649
704
  ctor(data: any, name?: any): General { return new General(data, name || 'pool'); }
650
705
  public get alias(): string { return this.data.alias; }
@@ -771,17 +826,6 @@ export class Options extends EqItem {
771
826
  public set cleanerSolarDelay(val: boolean) { this.setDataVal('cleanerSolarDelay', val); }
772
827
  public get cleanerSolarDelayTime(): number { return this.data.cleanerSolarDelayTime; }
773
828
  public set cleanerSolarDelayTime(val: number) { this.setDataVal('cleanerSolarDelayTime', val); }
774
-
775
- //public get airTempAdj(): number { return typeof this.data.airTempAdj === 'undefined' ? 0 : this.data.airTempAdj; }
776
- //public set airTempAdj(val: number) { this.setDataVal('airTempAdj', val); }
777
- //public get waterTempAdj1(): number { return typeof this.data.waterTempAdj1 === 'undefined' ? 0 : this.data.waterTempAdj1; }
778
- //public set waterTempAdj1(val: number) { this.setDataVal('waterTempAdj1', val); }
779
- //public get solarTempAdj1(): number { return typeof this.data.solarTempAdj1 === 'undefined' ? 0 : this.data.solarTempAdj1; }
780
- //public set solarTempAdj1(val: number) { this.setDataVal('solarTempAdj1', val); }
781
- //public get waterTempAdj2(): number { return typeof this.data.waterTempAdj2 === 'undefined' ? 0 : this.data.waterTempAdj2; }
782
- //public set waterTempAdj2(val: number) { this.setDataVal('waterTempAdj2', val); }
783
- //public get solarTempAdj2(): number { return typeof this.data.solarTempAdj2 === 'undefined' ? 0 : this.data.solarTempAdj2; }
784
- //public set solarTempAdj2(val: number) { this.setDataVal('solarTempAd2', val); }
785
829
  }
786
830
  export class VacationOptions extends ChildEqItem {
787
831
  public initData() {
@@ -874,11 +918,20 @@ export class ExpansionPanel extends EqItem {
874
918
  export class Equipment extends EqItem {
875
919
  public dataName = 'equipmentConfig';
876
920
  public initData() {
921
+ if (typeof this.data.single === 'undefined') {
922
+ if (this.data.dual === true || this.data.shared === true) this.data.single = false;
923
+ else if (sys.controllerType !== ControllerType.IntelliTouch) this.data.single = true;
924
+ }
925
+ if (typeof this.data.softwareVersion === 'undefined') this.data.softwareVersion = '';
926
+ if (typeof this.data.bootloaderVersion === 'undefined') this.data.bootloaderVersion = '';
927
+ if (typeof this.data.maxChemDosers === 'undefined') this.data.maxChemDosers = 10;
877
928
  }
878
929
  public get name(): string { return this.data.name; }
879
930
  public set name(val: string) { this.setDataVal('name', val); }
880
931
  public get type(): number { return this.data.type; }
881
932
  public set type(val: number) { this.setDataVal('type', val); }
933
+ public get single(): boolean { return this.data.single; }
934
+ public set single(val: boolean) { this.setDataVal('single', val); }
882
935
  public get shared(): boolean { return this.data.shared; }
883
936
  public set shared(val: boolean) { this.setDataVal('shared', val); }
884
937
  public get dual(): boolean { return this.data.dual; }
@@ -913,6 +966,8 @@ export class Equipment extends EqItem {
913
966
  public set maxIntelliBrites(val: number) { this.setDataVal('maxIntelliBrites', val); }
914
967
  public get maxChemControllers(): number { return this.data.maxChemControllers; }
915
968
  public set maxChemControllers(val: number) { this.setDataVal('maxChemControllers', val); }
969
+ public get maxChemDosers(): number { return this.data.maxChemDosers; }
970
+ public set maxChemDosers(val: number) { this.setDataVal('maxChemDosers', val); }
916
971
  public get expansions(): ExpansionPanelCollection { return new ExpansionPanelCollection(this.data, "expansions"); }
917
972
  public get modules(): ExpansionModuleCollection { return new ExpansionModuleCollection(this.data, "modules"); }
918
973
  public get maxCustomNames(): number { return this.data.maxCustomNames || 10; }
@@ -1032,6 +1087,7 @@ export class Body extends EqItem {
1032
1087
  public initData() {
1033
1088
  if (typeof this.data.capacityUnits === 'undefined') this.data.capacityUnits = 1;
1034
1089
  if (typeof this.data.showInDashboard === 'undefined') this.data.showInDashboard = true;
1090
+ if (typeof this.data.heatMode === 'undefined') this.data.heatMode = 0;
1035
1091
  }
1036
1092
  public get id(): number { return this.data.id; }
1037
1093
  public set id(val: number) { this.data.id = val; }
@@ -1248,13 +1304,26 @@ export class Circuit extends EqItem implements ICircuit {
1248
1304
  }
1249
1305
  else return [];
1250
1306
  }
1251
-
1252
1307
  public static getIdName(id: number) {
1253
- // todo: adjust for intellitouch
1254
- let defName = "Aux" + (id + 1).toString();
1255
- if (id === 0) defName = "Spa";
1256
- else if (id === 5) defName = "Pool";
1257
- else if (id < 5) defName = "Aux" + id.toString();
1308
+ let defName;
1309
+ // RKS: 07-19-22 I think this is some sort of artifact. The system should not be creating this data as a default because
1310
+ // the board itself should be coming up with these names.
1311
+ switch (sys.controllerType) {
1312
+ case ControllerType.SunTouch:
1313
+ break;
1314
+ default:
1315
+ if (sys.board.equipmentIds.circuitGroups.isInRange(id))
1316
+ defName = `Group ${id - sys.board.equipmentIds.circuitGroups.start + 1}`;
1317
+ else if (sys.board.equipmentIds.features.isInRange(id))
1318
+ defName = `Feature ${id - sys.board.equipmentIds.features.start + 1}`;
1319
+ else if (sys.board.equipmentIds.circuits.isInRange(id)) {
1320
+ if (id <= 1) defName = 'Spa';
1321
+ else if (id === 6) defName = 'Pool';
1322
+ else if (id < 6) defName = `Aux ${id - 1}`;
1323
+ else defName = `Aux ${id - 2}`;
1324
+ }
1325
+ break;
1326
+ }
1258
1327
  return defName;
1259
1328
  }
1260
1329
  }
@@ -1329,12 +1398,23 @@ export class PumpCollection extends EqItemCollection<Pump> {
1329
1398
  if (typeof add !== 'undefined' && add) return this.add(data || { id: this.data.length + 1, address: address });
1330
1399
  return this.createItem(data || { id: this.data.length + 1, address: address });
1331
1400
  }
1401
+ public getNextEquipmentId(range?: EquipmentIdRange, exclude?: number[]): number { return super.getNextEquipmentId(typeof range === 'undefined' ? sys.board.equipmentIds.pumps : range, exclude); }
1332
1402
  }
1333
1403
  export class Pump extends EqItem {
1334
1404
  public dataName = 'pumpConfig';
1335
1405
  public initData() {
1336
1406
  if (typeof this.data.isVirtual !== 'undefined') delete this.data.isVirtual;
1337
1407
  if (typeof this.data.portId === 'undefined') this.data.portId = 0;
1408
+ if (typeof this.data.body === 'number' && this.data.model === 2 && this.data.master === 1){
1409
+ // convert SS from body types to circuit arrays
1410
+ if (this.data.body === 255 || this.data.body === 0 && !this.data.circuits.find(el => el.circuit === 6)) {
1411
+ this.data.circuits.push({"circuit": 6, "relay": 1, "units": 0, "id": this.data.circuits.length + 1, "master": 1})
1412
+ }
1413
+ if (this.data.body === 255 || this.data.body === 101 && !this.data.circuits.find(el => el.circuit === 1)) {
1414
+ this.data.circuits.push({"circuit": 1, "relay": 1, "units": 0, "id": this.data.circuits.length + 1, "master": 1})
1415
+ }
1416
+ this.data.body = undefined;
1417
+ }
1338
1418
  }
1339
1419
  public get id(): number { return this.data.id; }
1340
1420
  public set id(val: number) { this.setDataVal('id', val); }
@@ -1409,9 +1489,9 @@ export class Pump extends EqItem {
1409
1489
  public deletePumpCircuit(pumpCircuitId: number) {
1410
1490
  return sys.board.pumps.deletePumpCircuit(this, pumpCircuitId);
1411
1491
  }
1412
- public setType(pumpType: number) {
1413
- sys.board.pumps.setType(this, pumpType);
1414
- }
1492
+ //public setType(pumpType: number) {
1493
+ // sys.board.pumps.setType(this, pumpType);
1494
+ //}
1415
1495
  public nextAvailablePumpCircuit(): number {
1416
1496
  let pumpCircuits = this.circuits;
1417
1497
  for (let i = 1; i <= 8; i++) {
@@ -1498,6 +1578,7 @@ export class Chlorinator extends EqItem {
1498
1578
  if (typeof this.data.ignoreSaltReading === 'undefined') this.data.ignoreSaltReading = false;
1499
1579
  if (typeof this.data.isVirtual !== 'undefined') delete this.data.isVirtual;
1500
1580
  if (typeof this.data.portId === 'undefined') this.data.portId = 0;
1581
+ if (typeof this.data.saltTarget === 'undefined') this.data.saltTarget = 3400;
1501
1582
  }
1502
1583
  public get id(): number { return this.data.id; }
1503
1584
  public set id(val: number) { this.setDataVal('id', val); }
@@ -1513,6 +1594,14 @@ export class Chlorinator extends EqItem {
1513
1594
  public set spaSetpoint(val: number) { this.setDataVal('spaSetpoint', val); }
1514
1595
  public get superChlorHours(): number { return this.data.superChlorHours; }
1515
1596
  public set superChlorHours(val: number) { this.setDataVal('superChlorHours', val); }
1597
+ public get saltTarget(): number { return this.data.saltTarget; }
1598
+ public set saltTarget(val: number) {
1599
+ if (this.data.saltTarget !== val) {
1600
+ this.setDataVal('saltTarget', val);
1601
+ let cstate = state.chlorinators.getItemById(this.id, true);
1602
+ cstate.calcSaltRequired(this.saltTarget);
1603
+ }
1604
+ }
1516
1605
  public get isActive(): boolean { return this.data.isActive; }
1517
1606
  public set isActive(val: boolean) { this.setDataVal('isActive', val); }
1518
1607
  public get address(): number { return this.data.address; }
@@ -1899,6 +1988,9 @@ export class CircuitGroupCollection extends EqItemCollection<CircuitGroup> {
1899
1988
  }
1900
1989
  export class CircuitGroup extends EqItem implements ICircuitGroup, ICircuit {
1901
1990
  public dataName = 'circuitGroupConfig';
1991
+ public initData() {
1992
+ if (typeof this.data.showInFeatures === 'undefined') this.data.showInFeatures = true;
1993
+ }
1902
1994
  public get id(): number { return this.data.id; }
1903
1995
  public set id(val: number) { this.setDataVal('id', val); }
1904
1996
  public get name(): string { return this.data.name; }
@@ -2003,7 +2095,7 @@ export class Security extends EqItem {
2003
2095
  public get roles(): SecurityRoleCollection { return new SecurityRoleCollection(this.data, "roles"); }
2004
2096
  }
2005
2097
  export class ChemControllerCollection extends EqItemCollection<ChemController> {
2006
- constructor(data: any, name?: string) { super(data, name || "chemcontrollers"); }
2098
+ constructor(data: any, name?: string) { super(data, name || "chemControllers"); }
2007
2099
  public createItem(data: any): ChemController { return new ChemController(data); }
2008
2100
  public getItemByAddress(address: number, add?: boolean, data?: any): ChemController {
2009
2101
  let itm = this.find(elem => elem.address === address && typeof elem.address !== 'undefined');
@@ -2027,6 +2119,11 @@ export class ChemControllerCollection extends EqItemCollection<ChemController> {
2027
2119
  return id + 1;
2028
2120
  }
2029
2121
  }
2122
+ export class ChemDoserCollection extends EqItemCollection<ChemDoser> {
2123
+ constructor(data: any, name?: string) { super(data, name || "chemDosers"); }
2124
+ public createItem(data: any): ChemDoser { return new ChemDoser(data); }
2125
+ }
2126
+
2030
2127
  export class FlowSensor extends ChildEqItem {
2031
2128
  public dataName = 'flowSensorConfig';
2032
2129
  public initData() {
@@ -2048,7 +2145,9 @@ export class FlowSensor extends ChildEqItem {
2048
2145
  return sensor;
2049
2146
  }
2050
2147
  }
2051
- export class ChemController extends EqItem {
2148
+ export interface IChemController {
2149
+ }
2150
+ export class ChemController extends EqItem implements IChemController {
2052
2151
  public initData() {
2053
2152
  //var chemController = {
2054
2153
  // id: 'number', // Id of the controller
@@ -2184,6 +2283,68 @@ export class ChemController extends EqItem {
2184
2283
  // ORP
2185
2284
  // 1. Chlorinator Comms Lost.
2186
2285
  }
2286
+ export class ChemDoser extends EqItem implements IChemical {
2287
+ public dataName = 'chemDoserConfig';
2288
+ public initData() {
2289
+ if (typeof this.data.pump === 'undefined') this.data.pump = {};
2290
+ if (typeof this.data.tank === 'undefined') this.data.tank = {};
2291
+ if (typeof this.data.flowSensor === 'undefined') this.data.flowSensor = {};
2292
+ if (typeof this.data.enabled === 'undefined') this.data.enabled = true;
2293
+ if (typeof this.data.dosingMethod === 'undefined') this.data.dosingMethod = 0;
2294
+ if (typeof this.data.startDelay === 'undefined') this.data.startDelay = 1.5;
2295
+ if (typeof this.data.maxDailyVolume === 'undefined') this.data.maxDailyVolume = 500;
2296
+ if (typeof this.data.disableOnFreeze === 'undefined') this.data.disableOnFreeze = true;
2297
+ if (typeof this.data.disableChlorinator === 'undefined') this.data.disableChlorinator = true;
2298
+ if (typeof this.mixingTime === 'undefined') this.data.mixingTime = 3600;
2299
+ if (typeof this.data.setpoint === 'undefined') this.data.setpoint = 100;
2300
+ if (typeof this.data.type === 'undefined') this.data.type = 0;
2301
+ super.initData();
2302
+ }
2303
+ public get id(): number { return this.data.id; }
2304
+ public set id(val: number) { this.setDataVal('id', val); }
2305
+ public get name(): string { return this.data.name; }
2306
+ public set name(val: string) { this.setDataVal('name', val); }
2307
+ public get setpoint(): number { return this.data.setpoint; }
2308
+ public set setpoint(val: number) { this.setDataVal('setpoint', val); }
2309
+ public get body(): number | any { return this.data.body; }
2310
+ public set body(val: number | any) { this.setDataVal('body', sys.board.valueMaps.bodies.encode(val)); }
2311
+ public get isActive(): boolean { return this.data.isActive; }
2312
+ public set isActive(val: boolean) { this.setDataVal('isActive', val); }
2313
+ public get chemType(): string { return this.data.chemType; }
2314
+ public get type(): number | any { return this.data.type; }
2315
+ public set type(val: number | any) {
2316
+ this.setDataVal('type', sys.board.valueMaps.chemDoserTypes.encode(val));
2317
+ let t = sys.board.valueMaps.chemDoserTypes.findItem(val) || { val: 0, name: 'acid', desc: 'Acid' };
2318
+ this.setDataVal('chemType', t.desc);
2319
+ }
2320
+ public get enabled(): boolean { return utils.makeBool(this.data.enabled); }
2321
+ public set enabled(val: boolean) { this.setDataVal('enabled', val); }
2322
+ public get disableChlorinator(): boolean { return utils.makeBool(this.data.disableChlorinator); }
2323
+ public set disableChlorinator(val: boolean) { this.setDataVal('disableChlorinator', val); }
2324
+ public get disableOnFreeze(): boolean { return utils.makeBool(this.data.disableOnFreeze); }
2325
+ public set disableOnFreeze(val: boolean) { this.setDataVal('disableOnFreeze', val); }
2326
+ public get dosingVolume(): number { return this.data.dosingVolume; }
2327
+ public set dosingVolume(val: number) { this.setDataVal('dosingVolume', val); }
2328
+ public get mixingTime(): number { return this.data.mixingTime; }
2329
+ public set mixingTime(val: number) { this.setDataVal('mixingTime', val); }
2330
+ public get maxDailyVolume(): number { return this.data.maxDailyVolume; }
2331
+ public set maxDailyVolume(val: number) { this.setDataVal('maxDailyVolume', val); }
2332
+ public get dosingMethod(): number | any { return this.data.dosingMethod; }
2333
+ public set dosingMethod(val: number | any) { this.setDataVal('dosingMethod', sys.board.valueMaps.chemDosingMethods.encode(val)); }
2334
+ public get startDelay(): number { return this.data.startDelay; }
2335
+ public set startDelay(val: number) { this.setDataVal('startDelay', val); }
2336
+ public get flowSensor(): ChemFlowSensor { return new ChemFlowSensor(this.data, 'flowSensor', this); }
2337
+ public get flowOnlyMixing(): boolean { return utils.makeBool(this.data.flowOnlyMixing); }
2338
+ public set flowOnlyMixing(val: boolean) { this.setDataVal('flowOnlyMixing', val); }
2339
+ public get pump(): ChemicalPump { return new ChemicalPump(this.data, 'pump', this); }
2340
+ public get tank(): ChemicalTank { return new ChemicalTank(this.data, 'tank', this); }
2341
+ public getExtended() {
2342
+ let chem = this.get(true);
2343
+ chem.tank = this.tank.getExtended();
2344
+ chem.pump = this.pump.getExtended();
2345
+ return chem;
2346
+ }
2347
+ }
2187
2348
  export class ChemFlowSensor extends FlowSensor {
2188
2349
  public dataName = 'flowSensorConfig';
2189
2350
  public initData() {
@@ -2199,7 +2360,13 @@ export class ChemFlowSensor extends FlowSensor {
2199
2360
  return sensor;
2200
2361
  }
2201
2362
  }
2202
- export class Chemical extends ChildEqItem {
2363
+ export interface IChemical {
2364
+ dosingMethod: number;
2365
+ startDelay: number;
2366
+ maxDosingTime?: number;
2367
+ maxDosingVolume?: number;
2368
+ }
2369
+ export class Chemical extends ChildEqItem implements IChemical {
2203
2370
  public dataName = 'chemicalConfig';
2204
2371
  public initData() {
2205
2372
  if (typeof this.data.pump === 'undefined') this.data.pump = {};
@@ -2482,4 +2649,21 @@ export class AlarmSetting extends ChildEqItem {
2482
2649
  public get high(): number { return this.data.high; }
2483
2650
  public set high(val: number) { this.setDataVal('high', val); }
2484
2651
  }
2652
+ export class Screenlogic extends EqItem {
2653
+ ctor(data: any, name?: any): General { return new General(data, name || 'pool'); }
2654
+ public get enabled(): boolean { return this.data.enabled; }
2655
+ public set enabled(val: boolean) { this.setDataVal('enabled', val); }
2656
+ public get type(): 'local'|'remote' { return this.data.type; }
2657
+ public set type(val: 'local'|'remote') { this.setDataVal('type', val); }
2658
+ public get systemName(): string { return this.data.systemName; }
2659
+ public set systemName(val: string) { this.setDataVal('systemName', val); }
2660
+ public get password(): string { return this.data.password; }
2661
+ public set password(val: string) { this.setDataVal('password', val); }
2662
+
2663
+ public clear(master: number = -1) {
2664
+ if (master === -1)
2665
+ super.clear();
2666
+ }
2667
+
2668
+ }
2485
2669
  export let sys = new PoolSystem();
@@ -1,5 +1,6 @@
1
1
  /* nodejs-poolController. An application to control pool equipment.
2
- Copyright (C) 2016, 2017, 2018, 2019, 2020. Russell Goldin, tagyoureit. russ.goldin@gmail.com
2
+ Copyright (C) 2016, 2017, 2018, 2019, 2020, 2021, 2022.
3
+ Russell Goldin, tagyoureit. russ.goldin@gmail.com
3
4
 
4
5
  This program is free software: you can redistribute it and/or modify
5
6
  it under the terms of the GNU Affero General Public License as
@@ -1,5 +1,6 @@
1
1
  /* nodejs-poolController. An application to control pool equipment.
2
- Copyright (C) 2016, 2017, 2018, 2019, 2020. Russell Goldin, tagyoureit. russ.goldin@gmail.com
2
+ Copyright (C) 2016, 2017, 2018, 2019, 2020, 2021, 2022.
3
+ Russell Goldin, tagyoureit. russ.goldin@gmail.com
3
4
 
4
5
  This program is free software: you can redistribute it and/or modify
5
6
  it under the terms of the GNU Affero General Public License as
@@ -133,6 +134,7 @@ export class ManualPriorityDelay extends EquipmentDelay {
133
134
  if (typeof this._delayTimer !== 'undefined') clearTimeout(this._delayTimer);
134
135
  logger.info(`Manual Operation Priority cancelled for ${this.circuitState.name}`);
135
136
  this._delayTimer = undefined;
137
+ this.circuitState.manualPriorityActive = false;
136
138
  delayMgr.deleteDelay(this.id);
137
139
  }
138
140
  public clearDelay() {
@@ -197,6 +199,13 @@ export class HeaterStartupDelay extends EquipmentDelay {
197
199
  this._delayTimer = undefined;
198
200
  delayMgr.deleteDelay(this.id);
199
201
  }
202
+ public clearDelay() {
203
+ this.heaterState.startupDelay = false;
204
+ if (typeof this._delayTimer !== 'undefined') clearTimeout(this._delayTimer);
205
+ logger.info(`Heater Startup delay cancelled for ${this.heaterState.name}`);
206
+ this._delayTimer = undefined;
207
+ delayMgr.deleteDelay(this.id);
208
+ }
200
209
  }
201
210
  export class HeaterCooldownDelay extends EquipmentDelay {
202
211
  public constructor(bsoff: BodyTempState, bson?: BodyTempState, delay?: number) {
@@ -267,7 +276,24 @@ export class HeaterCooldownDelay extends EquipmentDelay {
267
276
  delayMgr.deleteDelay(this.id);
268
277
  state.emitEquipmentChanges();
269
278
  }
270
-
279
+ public clearDelay() {
280
+ let cstateOff = state.circuits.getItemById(this.bodyStateOff.circuit);
281
+ cstateOff.stopDelay = false;
282
+ (async () => {
283
+ await sys.board.circuits.setCircuitStateAsync(cstateOff.id, false);
284
+ if (typeof this.bodyStateOn !== 'undefined') {
285
+ this.bodyStateOn.startDelay = state.circuits.getItemById(this.bodyStateOn.circuit).startDelay = false;
286
+ await sys.board.circuits.setCircuitStateAsync(this.bodyStateOn.circuit, true);
287
+ }
288
+ })();
289
+ this.bodyStateOff.stopDelay = this.bodyStateOff.heaterCooldownDelay = false;
290
+ this.bodyStateOff.heatStatus = sys.board.valueMaps.heatStatus.getValue('off');
291
+ if (typeof this._delayTimer !== 'undefined') clearTimeout(this._delayTimer);
292
+ logger.info(`Heater Cooldown delay cleared for ${this.bodyStateOff.name}`);
293
+ this._delayTimer = undefined;
294
+ delayMgr.deleteDelay(this.id);
295
+ state.emitEquipmentChanges();
296
+ }
271
297
  }
272
298
  interface ICleanerDelay {
273
299
  cleanerState: ICircuitState,
@@ -351,6 +377,12 @@ export class DelayManager extends Array<EquipmentDelay> {
351
377
  let del = this.find(x => x.id === id);
352
378
  if (typeof del !== 'undefined') del.cancelDelay();
353
379
  }
380
+ public clearAllDelays() {
381
+ for (let i = this.length - 1; i >= 0; i--) {
382
+ let del = this[i];
383
+ del.clearDelay();
384
+ }
385
+ }
354
386
  public setManualPriorityDelay(cs: ICircuitState) {
355
387
  let cds = this.filter(x => x.type === 'manualOperationPriorityDelay');
356
388
  for (let i = 0; i < cds.length; i++) {