nodejs-poolcontroller 7.5.1 → 7.7.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.
Files changed (64) hide show
  1. package/.github/ISSUE_TEMPLATE/1-bug-report.yml +84 -0
  2. package/.github/ISSUE_TEMPLATE/2-docs.md +12 -0
  3. package/.github/ISSUE_TEMPLATE/3-proposal.md +28 -0
  4. package/.github/ISSUE_TEMPLATE/config.yml +8 -0
  5. package/Changelog +19 -0
  6. package/Dockerfile +3 -3
  7. package/README.md +13 -8
  8. package/app.ts +1 -1
  9. package/config/Config.ts +38 -2
  10. package/config/VersionCheck.ts +27 -12
  11. package/controller/Constants.ts +2 -1
  12. package/controller/Equipment.ts +193 -9
  13. package/controller/Errors.ts +10 -0
  14. package/controller/Lockouts.ts +503 -0
  15. package/controller/State.ts +269 -64
  16. package/controller/boards/AquaLinkBoard.ts +1000 -0
  17. package/controller/boards/BoardFactory.ts +4 -0
  18. package/controller/boards/EasyTouchBoard.ts +468 -144
  19. package/controller/boards/IntelliCenterBoard.ts +466 -307
  20. package/controller/boards/IntelliTouchBoard.ts +37 -5
  21. package/controller/boards/NixieBoard.ts +671 -141
  22. package/controller/boards/SystemBoard.ts +1397 -641
  23. package/controller/comms/Comms.ts +462 -362
  24. package/controller/comms/messages/Messages.ts +174 -30
  25. package/controller/comms/messages/config/ChlorinatorMessage.ts +6 -3
  26. package/controller/comms/messages/config/CircuitMessage.ts +1 -0
  27. package/controller/comms/messages/config/ExternalMessage.ts +10 -8
  28. package/controller/comms/messages/config/HeaterMessage.ts +141 -29
  29. package/controller/comms/messages/config/OptionsMessage.ts +9 -2
  30. package/controller/comms/messages/config/PumpMessage.ts +53 -35
  31. package/controller/comms/messages/config/ScheduleMessage.ts +33 -25
  32. package/controller/comms/messages/config/ValveMessage.ts +2 -2
  33. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +38 -86
  34. package/controller/comms/messages/status/EquipmentStateMessage.ts +59 -23
  35. package/controller/comms/messages/status/HeaterStateMessage.ts +57 -3
  36. package/controller/comms/messages/status/IntelliChemStateMessage.ts +56 -8
  37. package/controller/comms/messages/status/PumpStateMessage.ts +23 -1
  38. package/controller/nixie/Nixie.ts +1 -1
  39. package/controller/nixie/bodies/Body.ts +3 -0
  40. package/controller/nixie/chemistry/ChemController.ts +164 -51
  41. package/controller/nixie/chemistry/Chlorinator.ts +137 -88
  42. package/controller/nixie/circuits/Circuit.ts +51 -19
  43. package/controller/nixie/heaters/Heater.ts +241 -31
  44. package/controller/nixie/pumps/Pump.ts +488 -206
  45. package/controller/nixie/schedules/Schedule.ts +91 -35
  46. package/controller/nixie/valves/Valve.ts +1 -1
  47. package/defaultConfig.json +20 -0
  48. package/package.json +21 -21
  49. package/web/Server.ts +94 -49
  50. package/web/bindings/aqualinkD.json +505 -0
  51. package/web/bindings/influxDB.json +71 -1
  52. package/web/bindings/mqtt.json +98 -39
  53. package/web/bindings/mqttAlt.json +59 -1
  54. package/web/interfaces/baseInterface.ts +1 -0
  55. package/web/interfaces/httpInterface.ts +23 -2
  56. package/web/interfaces/influxInterface.ts +45 -10
  57. package/web/interfaces/mqttInterface.ts +114 -54
  58. package/web/services/config/Config.ts +55 -132
  59. package/web/services/state/State.ts +81 -4
  60. package/web/services/state/StateSocket.ts +4 -4
  61. package/web/services/utilities/Utilities.ts +8 -6
  62. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -52
  63. package/config copy.json +0 -300
  64. package/issue_template.md +0 -52
@@ -0,0 +1,503 @@
1
+ /* nodejs-poolController. An application to control pool equipment.
2
+ Copyright (C) 2016, 2017, 2018, 2019, 2020. Russell Goldin, tagyoureit. russ.goldin@gmail.com
3
+
4
+ This program is free software: you can redistribute it and/or modify
5
+ it under the terms of the GNU Affero General Public License as
6
+ published by the Free Software Foundation, either version 3 of the
7
+ License, or (at your option) any later version.
8
+
9
+ This program is distributed in the hope that it will be useful,
10
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ GNU Affero General Public License for more details.
13
+
14
+ You should have received a copy of the GNU Affero General Public License
15
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+ */
17
+ import { PumpState, HeaterState, BodyTempState, ICircuitState, state } from "./State";
18
+ import { Equipment, sys } from "./Equipment";
19
+ import { Timestamp, utils } from "./Constants";
20
+ import { logger } from "../logger/Logger";
21
+ import { webApp } from "../web/Server";
22
+ // LOCKOUT PRIMER
23
+ // Lockouts are either time based (Delays) or based upon the current state configuration for
24
+ // the system. So in some cases circuits can only be engaged in pool mode or in spa mode. In
25
+ // others a period of time must occur before a particular action can continue. Delays can typically
26
+ // be cancelled manually while lockouts can only be cancelled when the condition required for the lockout
27
+ // is changed.
28
+
29
+ // DELAYS:
30
+ // Pump Off During Valve Rotation (30 sec): This turns any pump associated with the body being turned on to
31
+ // so that is is off. This gives the valves time to rotate so that cold water from the pool does not cycle into
32
+ // the spa and hot water from the spa does not escape into the pool. This has nothing to do with
33
+ // water hammer or anything else.
34
+ //
35
+ // Heater Cooldown Delay (based on max heater time): When the system is heating and an event is occurring
36
+ // that will cause the heater to be turned off, the current mode will be retained until the delay is either
37
+ // cancelled or expired.
38
+ // Delay Conditions:
39
+ // 1. Being in either pool or spa mode and simply turning off that mode where the heater will be turned off.
40
+ // 2. Switching between pool and spa when the target mode does not use the identified heater.
41
+ // Exceptions:
42
+ // 1. The last call for heat was earlier than the current time minus the cooldown delay defined for the heater.
43
+ // 2. The heater mode is in a cooling mode.
44
+ //
45
+ // Heater Startup: When a body is first turned on the heater will not be engaged for 10 seconds after any pump delay
46
+ // or the time that the body is engaged.
47
+ //
48
+ // Cleaner Circuit Start Delay: Delays turning on any circuit with a cleaner function until the delay expires. This is
49
+ // so booster pumps can be assured of sufficient forward pressure prior to turning on. These pumps often require sufficient
50
+ // pressure before engaging and will cavitate if they do not have it. The Pentair default is 5min.
51
+ //
52
+ // Cleaner Circuit Solar Delay: This only exists with Pentair panels. This shuts off any circuit
53
+ // designated as a pool cleaner circuit if it is on and delays turning it on for 5min after the solar starts. The assumption
54
+ // here is that pressure reduction that can occur when the solar kicks on can cavitate the pump.
55
+ //
56
+ // Manual Operation Priority Delay:
57
+ // From the manual:
58
+ // Manual OP Priority: ON: This feature allows for a circuit to be manually switched OFF and switched ON within
59
+ // a scheduled program, the circuit will continue to run for a maximum of 12 hours or whatever that circuit Egg
60
+ // Timer is set to, after which the scheduled program will resume. This feature will turn off any scheduled
61
+ // program to allow manual pump override. The Default setting is OFF.
62
+ //
63
+ // ## When on
64
+ // 1. If a schedule should be on and the user turns the schedule off then the schedule expires until such time
65
+ // as the time off has expired. When that occurs the schedule should be reset to run at the designated time.
66
+ // If the user resets the schedule by turning the circuit back on again then the schedule will be ignored and
67
+ // the circuit will run until the egg timer expires or the circuit/feature is manually turned off. This setting
68
+ // WILL affect other schedules that may impact this circuit.
69
+ //
70
+ // ## When off
71
+ // 1. "Normal" = If a schedule should be on and the user turns the schedule off then the schedule expires until
72
+ // such time as the time off has expired. When that occurs the schedule should be reset to run at the designated
73
+ // time. If the user resets the schedule by turning the circuit back on again then the schedule will resume and
74
+ // turn off at the specified time.
75
+ //
76
+ // LOCKOUTS (Proposed):
77
+ // Spillway Lockout: This locks out any circuit or feature that is marked with a Spillway circuit function (type) whenever
78
+ // whenever the pool circuit is not engaged. This should mark the spillway circuit as a delayStart then release it when the
79
+ // pool body starts.
80
+ interface ILockout {
81
+ type: string
82
+ }
83
+ export class EquipmentLockout implements ILockout {
84
+ public id = utils.uuid();
85
+ public create() { }
86
+ public startTime: Date;
87
+ public type: string = 'lockout';
88
+ public message: string = '';
89
+ }
90
+ export class EquipmentDelay implements ILockout {
91
+ public constructor() { this.id = delayMgr.getNextId(); }
92
+ public id;
93
+ public type: string = 'delay';
94
+ public startTime: Date;
95
+ public endTime: Date;
96
+ public canCancel: boolean = true;
97
+ public cancelDelay() { };
98
+ public reset() { };
99
+ public clearDelay() { };
100
+ public message;
101
+ protected _delayTimer: NodeJS.Timeout;
102
+ public serialize(): any {
103
+ return {
104
+ id: this.id,
105
+ type: this.type,
106
+ canCancel: this.canCancel,
107
+ message: this.message,
108
+ startTime: typeof this.startTime !== 'undefined' ? Timestamp.toISOLocal(this.startTime) : undefined,
109
+ endTime: typeof this.endTime !== 'undefined' ? Timestamp.toISOLocal(this.endTime) : undefined,
110
+ duration: typeof this.startTime !== 'undefined' && typeof this.endTime !== 'undefined' ? (this.endTime.getTime() - this.startTime.getTime()) / 1000 : 0
111
+ };
112
+ }
113
+ }
114
+ export class ManualPriorityDelay extends EquipmentDelay {
115
+ public constructor(cs: ICircuitState) {
116
+ super();
117
+ this.type = 'manualOperationPriorityDelay';
118
+ this.message = `${cs.name} will override future schedules until expired/cancelled.`;
119
+ this.circuitState = cs;
120
+ this.circuitState.manualPriorityActive = true;
121
+ this.startTime = new Date();
122
+ this.endTime = cs.endTime.clone().toDate();
123
+ this._delayTimer = setTimeout(() => {
124
+ logger.info(`Manual Operation Priority expired for ${this.circuitState.name}`);
125
+ this.circuitState.manualPriorityActive = false;
126
+ delayMgr.deleteDelay(this.id);
127
+ }, this.endTime.getTime() - new Date().getTime());
128
+ logger.info(`Manual Operation Priority delay in effect until ${this.circuitState.name} - ${cs.endTime.toDate()}`);
129
+ }
130
+ public circuitState: ICircuitState;
131
+ public cancelDelay() {
132
+ this.circuitState.manualPriorityActive = false;
133
+ if (typeof this._delayTimer !== 'undefined') clearTimeout(this._delayTimer);
134
+ logger.info(`Manual Operation Priority cancelled for ${this.circuitState.name}`);
135
+ this._delayTimer = undefined;
136
+ delayMgr.deleteDelay(this.id);
137
+ }
138
+ public clearDelay() {
139
+ if (typeof this._delayTimer !== 'undefined') clearTimeout(this._delayTimer);
140
+ logger.info(`Manual Operation Priority cleared for ${this.circuitState.name}`);
141
+ this._delayTimer = undefined;
142
+ delayMgr.deleteDelay(this.id);
143
+ }
144
+ }
145
+ export class PumpValveDelay extends EquipmentDelay {
146
+ public constructor(ps: PumpState, delay?: number) {
147
+ super();
148
+ this.type = 'pumpValveDelay';
149
+ this.message = `${ps.name} will start after valve delay`;
150
+ this.pumpState = ps;
151
+ this.pumpState.pumpOnDelay = true;
152
+ this.startTime = new Date();
153
+ this.endTime = new Date(this.startTime.getTime() + (delay * 1000 || sys.general.options.valveDelayTime * 1000));
154
+ this._delayTimer = setTimeout(() => {
155
+ logger.info(`Valve delay expired for ${this.pumpState.name}`);
156
+ this.pumpState.pumpOnDelay = false;
157
+ delayMgr.deleteDelay(this.id);
158
+ }, delay * 1000 || sys.general.options.valveDelayTime * 1000);
159
+ logger.info(`Valve delay started for ${this.pumpState.name} - ${delay || sys.general.options.valveDelayTime}sec`);
160
+ }
161
+ public pumpState: PumpState;
162
+ public cancelDelay() {
163
+ this.pumpState.pumpOnDelay = false;
164
+ if (typeof this._delayTimer !== 'undefined') clearTimeout(this._delayTimer);
165
+ logger.info(`Valve delay cancelled for ${this.pumpState.name}`);
166
+ this._delayTimer = undefined;
167
+ delayMgr.deleteDelay(this.id);
168
+ }
169
+ public clearDelay() {
170
+ if (typeof this._delayTimer !== 'undefined') clearTimeout(this._delayTimer);
171
+ logger.info(`Valve delay cleared for ${this.pumpState.name}`);
172
+ this._delayTimer = undefined;
173
+ delayMgr.deleteDelay(this.id);
174
+ }
175
+ }
176
+ export class HeaterStartupDelay extends EquipmentDelay {
177
+ public constructor(hs: HeaterState, delay?: number) {
178
+ super();
179
+ this.type = 'heaterStartupDelay';
180
+ this.message = `${hs.name} will start after delay`;
181
+ this.heaterState = hs;
182
+ this.heaterState.startupDelay = true;
183
+ this.startTime = new Date();
184
+ this.endTime = new Date(this.startTime.getTime() + (delay * 1000 || sys.general.options.heaterStartDelayTime * 1000));
185
+ this._delayTimer = setTimeout(() => {
186
+ logger.info(`Heater Startup delay expired for ${this.heaterState.name}`);
187
+ this.heaterState.startupDelay = false;
188
+ delayMgr.deleteDelay(this.id);
189
+ }, delay * 1000 || sys.general.options.heaterStartDelayTime * 1000);
190
+ logger.info(`Heater delay started for ${this.heaterState.name} - ${delay || sys.general.options.heaterStartDelayTime}sec`);
191
+ }
192
+ public heaterState: HeaterState;
193
+ public cancelDelay() {
194
+ this.heaterState.startupDelay = false;
195
+ if (typeof this._delayTimer !== 'undefined') clearTimeout(this._delayTimer);
196
+ logger.info(`Heater Startup delay cancelled for ${this.heaterState.name}`);
197
+ this._delayTimer = undefined;
198
+ delayMgr.deleteDelay(this.id);
199
+ }
200
+ }
201
+ export class HeaterCooldownDelay extends EquipmentDelay {
202
+ public constructor(bsoff: BodyTempState, bson?: BodyTempState, delay?: number) {
203
+ super();
204
+ this.type = 'heaterCooldownDelay';
205
+ this.message = `${bsoff.name} Heater Cooldown in progress`;
206
+ this.bodyStateOff = bsoff;
207
+ this.bodyStateOff.heaterCooldownDelay = true;
208
+ this.bodyStateOff.heatStatus = sys.board.valueMaps.heatStatus.getValue('cooldown');
209
+ let cstateOff = state.circuits.getItemById(bsoff.circuit);
210
+ this.bodyStateOn = bson;
211
+ this.bodyStateOff.stopDelay = cstateOff.stopDelay = true;
212
+ let cstateOn = (typeof bson !== 'undefined') ? state.circuits.getItemById(bson.circuit) : undefined;
213
+ if (typeof cstateOn !== 'undefined') {
214
+ this.bodyStateOn.startDelay = cstateOn.startDelay = true;
215
+ }
216
+ logger.verbose(`Heater Cooldown Delay started for ${this.bodyStateOff.name} - ${delay/1000}sec`);
217
+ this.startTime = new Date();
218
+ this.endTime = new Date(this.startTime.getTime() + (delay * 1000));
219
+ this._delayTimer = setTimeout(() => {
220
+ logger.verbose(`Heater Cooldown delay expired for ${this.bodyStateOff.name}`);
221
+ this.bodyStateOff.stopDelay = state.circuits.getItemById(this.bodyStateOff.circuit).stopDelay = false;
222
+ // Now that the startup delay expired cancel the delay and shut off the circuit.
223
+ (async () => {
224
+ try {
225
+ await sys.board.circuits.setCircuitStateAsync(cstateOff.id, false, true);
226
+ if (typeof this.bodyStateOn !== 'undefined') {
227
+ this.bodyStateOn.startDelay = state.circuits.getItemById(this.bodyStateOn.circuit).startDelay = false;
228
+ await sys.board.circuits.setCircuitStateAsync(this.bodyStateOn.circuit, true);
229
+ }
230
+ } catch (err) { logger.error(`Error executing Cooldown Delay completion: ${err}`); }
231
+ })();
232
+ this.bodyStateOff.heaterCooldownDelay = false;
233
+ this.bodyStateOff.heatStatus = sys.board.valueMaps.heatStatus.getValue('off');
234
+ delayMgr.deleteDelay(this.id);
235
+ }, delay);
236
+ state.emitEquipmentChanges();
237
+ }
238
+ public bodyStateOff: BodyTempState;
239
+ public bodyStateOn: BodyTempState;
240
+ public setBodyStateOn(bson?: BodyTempState) {
241
+ if (typeof this.bodyStateOn !== 'undefined' && (typeof bson === 'undefined' || this.bodyStateOn.id !== bson.id))
242
+ this.bodyStateOn.startDelay = state.circuits.getItemById(this.bodyStateOn.circuit).startDelay = false;
243
+ if (typeof bson !== 'undefined') {
244
+ if (typeof this.bodyStateOn === 'undefined' || this.bodyStateOn.id !== bson.id) {
245
+ bson.startDelay = state.circuits.getItemById(bson.circuit).startDelay = true;
246
+ logger.info(`${bson.name} will Start After Cooldown Delay`);
247
+ this.bodyStateOn = bson;
248
+ }
249
+ }
250
+ else this.bodyStateOn = undefined;
251
+ }
252
+ public cancelDelay() {
253
+ let cstateOff = state.circuits.getItemById(this.bodyStateOff.circuit);
254
+ cstateOff.stopDelay = false;
255
+ (async () => {
256
+ await sys.board.circuits.setCircuitStateAsync(cstateOff.id, false);
257
+ if (typeof this.bodyStateOn !== 'undefined') {
258
+ this.bodyStateOn.startDelay = state.circuits.getItemById(this.bodyStateOn.circuit).startDelay = false;
259
+ await sys.board.circuits.setCircuitStateAsync(this.bodyStateOn.circuit, true);
260
+ }
261
+ })();
262
+ this.bodyStateOff.stopDelay = this.bodyStateOff.heaterCooldownDelay = false;
263
+ this.bodyStateOff.heatStatus = sys.board.valueMaps.heatStatus.getValue('off');
264
+ if (typeof this._delayTimer !== 'undefined') clearTimeout(this._delayTimer);
265
+ logger.info(`Heater Cooldown delay cancelled for ${this.bodyStateOff.name}`);
266
+ this._delayTimer = undefined;
267
+ delayMgr.deleteDelay(this.id);
268
+ state.emitEquipmentChanges();
269
+ }
270
+
271
+ }
272
+ interface ICleanerDelay {
273
+ cleanerState: ICircuitState,
274
+ bodyId: number
275
+ }
276
+ export class CleanerStartDelay extends EquipmentDelay implements ICleanerDelay {
277
+ constructor(cs: ICircuitState, bodyId: number, delay?: number) {
278
+ super();
279
+ this.type = 'cleanerStartDelay';
280
+ this.message = `${cs.name} will start after delay`;
281
+ this.bodyId = bodyId;
282
+ this.cleanerState = cs;
283
+ cs.startDelay = true;
284
+ this.startTime = new Date();
285
+ this.endTime = new Date(this.startTime.getTime() + (delay * 1000 || sys.general.options.cleanerStartDelayTime * 1000));
286
+ this._delayTimer = setTimeout(() => {
287
+ logger.info(`Cleaner delay expired for ${this.cleanerState.name}`);
288
+ this.cleanerState.startDelay = false;
289
+ (async () => {
290
+ try {
291
+ await sys.board.circuits.setCircuitStateAsync(this.cleanerState.id, true, true);
292
+ this.cleanerState.startDelay = false;
293
+ } catch (err) { logger.error(`Error executing Cleaner Start Delay completion: ${err}`); }
294
+ })();
295
+ delayMgr.deleteDelay(this.id);
296
+ }, delay * 1000 || sys.general.options.cleanerStartDelayTime * 1000);
297
+ logger.info(`Cleaner delay started for ${this.cleanerState.name} - ${delay || sys.general.options.cleanerStartDelayTime}sec`);
298
+ }
299
+ public cleanerState: ICircuitState;
300
+ public bodyId: number;
301
+ public cancelDelay() {
302
+ this.cleanerState.startDelay = false;
303
+ if (typeof this._delayTimer !== 'undefined') clearTimeout(this._delayTimer);
304
+ logger.info(`Cleaner Start delay cancelled for ${this.cleanerState.name}`);
305
+ this._delayTimer = undefined;
306
+ this.cleanerState.startDelay = false;
307
+ (async () => {
308
+ try {
309
+ await sys.board.circuits.setCircuitStateAsync(this.cleanerState.id, true, true);
310
+ } catch (err) { logger.error(`Error executing Cleaner Start Delay completion: ${err}`); }
311
+ })();
312
+ delayMgr.deleteDelay(this.id);
313
+ }
314
+ public clearDelay() {
315
+ this.cleanerState.startDelay = false;
316
+ if (typeof this._delayTimer !== 'undefined') clearTimeout(this._delayTimer);
317
+ logger.info(`Cleaner Start delay cleared for ${this.cleanerState.name}`);
318
+ this._delayTimer = undefined;
319
+ this.cleanerState.startDelay = false;
320
+ delayMgr.deleteDelay(this.id);
321
+ }
322
+
323
+ public reset(delay?: number) {
324
+ if (typeof this._delayTimer !== 'undefined') clearTimeout(this._delayTimer);
325
+ this.cleanerState.startDelay = true;
326
+ logger.info(`Cleaner Start delay reset for ${this.cleanerState.name}`);
327
+ this.startTime = new Date();
328
+ this.endTime = new Date(this.startTime.getTime() + (delay * 1000 || sys.general.options.cleanerStartDelayTime * 1000));
329
+ this._delayTimer = setTimeout(() => {
330
+ logger.info(`Cleaner delay expired for ${this.cleanerState.name}`);
331
+ this.cleanerState.startDelay = false;
332
+ (async () => {
333
+ try {
334
+ await sys.board.circuits.setCircuitStateAsync(this.cleanerState.id, true);
335
+ this.cleanerState.startDelay = false;
336
+ } catch (err) { logger.error(`Error executing Cleaner Start Delay completion: ${err}`); }
337
+ })();
338
+ delayMgr.deleteDelay(this.id);
339
+ }, delay * 1000 || sys.general.options.cleanerStartDelayTime * 1000);
340
+ }
341
+ }
342
+ export class DelayManager extends Array<EquipmentDelay> {
343
+ protected _id = 1;
344
+ private _emitTimer: NodeJS.Timeout;
345
+ public setDirty() {
346
+ if (typeof this._emitTimer) clearTimeout(this._emitTimer);
347
+ this._emitTimer = setTimeout(() => this.emitDelayState(), 1000);
348
+ }
349
+ public getNextId() { return this._id++; }
350
+ public cancelDelay(id: number) {
351
+ let del = this.find(x => x.id === id);
352
+ if (typeof del !== 'undefined') del.cancelDelay();
353
+ }
354
+ public setManualPriorityDelay(cs: ICircuitState) {
355
+ let cds = this.filter(x => x.type === 'manualOperationPriorityDelay');
356
+ for (let i = 0; i < cds.length; i++) {
357
+ let delay = cds[i] as ManualPriorityDelay;
358
+ if (delay.circuitState.id === cs.id) delay.clearDelay();
359
+ }
360
+ this.push(new ManualPriorityDelay(cs)); this.setDirty();
361
+ }
362
+ public cancelManualPriorityDelays() { this.cancelDelaysByType('manualOperationPriorityDelay'); this.setDirty(); }
363
+ public cancelManualPriorityDelay(id: number){
364
+ let delays = this.filter(x => x.type === 'manualOperationPriorityDelay');
365
+ for (let i = 0; i < delays.length; i++) {
366
+ if((delays[i] as ManualPriorityDelay).circuitState.id === id) delays[i].cancelDelay();
367
+ }
368
+ }
369
+ public setPumpValveDelay(ps: PumpState, delay?: number) {
370
+ let cds = this.filter(x => x.type === 'pumpValveDelay');
371
+ for (let i = 0; i < cds.length; i++) {
372
+ let delay = cds[i] as PumpValveDelay;
373
+ if (delay.pumpState.id === ps.id) delay.clearDelay();
374
+ }
375
+ this.push(new PumpValveDelay(ps, delay)); this.setDirty();
376
+ }
377
+ public cancelPumpValveDelays() { this.cancelDelaysByType('pumpValveDelay'); this.setDirty(); }
378
+ public setHeaterStartupDelay(hs: HeaterState, delay?: number) {
379
+ let cds = this.filter(x => x.type === 'heaterStartupDelay');
380
+ for (let i = 0; i < cds.length; i++) {
381
+ let delay = cds[i] as HeaterStartupDelay;
382
+ if (delay.heaterState.id === hs.id) delay.cancelDelay();
383
+ }
384
+ this.push(new HeaterStartupDelay(hs, delay)); this.setDirty();
385
+ }
386
+ public cancelHeaterStartupDelays() {
387
+ this.cancelDelaysByType('heaterStartupDelay');
388
+ }
389
+ public setHeaterCooldownDelay(bsOff: BodyTempState, bsOn?: BodyTempState, delay?: number) {
390
+ logger.info(`Setting Heater Cooldown Delay for ${bsOff.name}`);
391
+ let cds = this.filter(x => x.type === 'heaterCooldownDelay');
392
+ for (let i = 0; i < cds.length; i++) {
393
+ let delay = cds[i] as HeaterCooldownDelay;
394
+ if (delay.bodyStateOff.id === bsOff.id) {
395
+ if(typeof bsOn !== 'undefined') logger.info(`Found Cooldown Delay adding on circuit ${bsOn.name}`);
396
+ delay.setBodyStateOn(bsOn);
397
+ this.setDirty();
398
+ return;
399
+ }
400
+ }
401
+ this.push(new HeaterCooldownDelay(bsOff, bsOn, delay));
402
+ this.setDirty();
403
+ }
404
+ public clearBodyStartupDelay(bs: BodyTempState) {
405
+ logger.info(`Clearing startup delays for ${bs.name}`);
406
+ // We are doing this non type safety thing below so that
407
+ // we can only emit when the body is cleared.
408
+ let cds = this.filter(x => {
409
+ return x.type === 'heaterCooldownDelay' &&
410
+ typeof x['bodyStateOn'] !== 'undefined' &&
411
+ x['bodyStateOn'].id === bs.id;
412
+ });
413
+ for (let i = 0; i < cds.length; i++) {
414
+ let delay = cds[i] as HeaterCooldownDelay;
415
+ logger.info(`Clearing ${bs.name} from Cooldown Delay`);
416
+ delay.setBodyStateOn();
417
+ }
418
+ if (cds.length) this.setDirty();
419
+ }
420
+ public cancelHeaterCooldownDelays() { this.cancelDelaysByType('heaterCooldownDelay'); }
421
+ public setCleanerStartDelay(cs: ICircuitState, bodyId: number, delay?: number) {
422
+ let cds = this.filter(x => x.type === ('cleanerStartDelay' || 'cleanerSolarDelay'));
423
+ let startDelay: CleanerStartDelay;
424
+ for (let i = 0; i < cds.length; i++) {
425
+ let delay = cds[i] as unknown as ICleanerDelay;
426
+ if (delay.cleanerState.id === cs.id) {
427
+ if (delay.bodyId !== bodyId || cds[i].type !== 'cleanerStartDelay') cds[i].cancelDelay();
428
+ else if (typeof startDelay !== 'undefined') {
429
+ startDelay.cancelDelay();
430
+ startDelay = cds[i] as CleanerStartDelay;
431
+ }
432
+ else startDelay = cds[i] as CleanerStartDelay;
433
+ }
434
+ }
435
+ if (typeof startDelay !== 'undefined') {
436
+ startDelay.reset(delay);
437
+ this.setDirty();
438
+ }
439
+ else {
440
+ this.push(new CleanerStartDelay(cs, bodyId, delay));
441
+ this.setDirty();
442
+ }
443
+ }
444
+ public cancelCleanerStartDelays(bodyId?: number) {
445
+ if (typeof bodyId === 'undefined') this.cancelDelaysByType('cleanerStartDelay');
446
+ else {
447
+ let delays = this.filter(x => x.type === 'cleanerStartDelay' && x['bodyId'] === bodyId);
448
+ for (let i = 0; i < delays.length; i++) {
449
+ delays[i].cancelDelay();
450
+ }
451
+ if (delays.length > 0) this.setDirty();
452
+ }
453
+ }
454
+ public clearCleanerStartDelays(bodyId?: number) {
455
+ if (typeof bodyId === 'undefined') this.clearDelaysByType('cleanerStartDelay');
456
+ else {
457
+ let delays = this.filter(x => x.type === 'cleanerStartDelay' && x['bodyId'] === bodyId);
458
+ for (let i = 0; i < delays.length; i++) {
459
+ delays[i].clearDelay();
460
+ }
461
+ if (delays.length > 0) this.setDirty();
462
+ }
463
+ }
464
+ public deleteDelay(id: number) {
465
+ for (let i = this.length - 1; i >= 0; i--) {
466
+ if (this[i].id === id) {
467
+ this.splice(i, 1);
468
+ this.setDirty();
469
+ }
470
+ }
471
+ }
472
+ public setSolarStartupDelay
473
+ protected cancelDelaysByType(type: string) {
474
+ let delays = this.filter(x => x.type === type);
475
+ for (let i = 0; i < delays.length; i++) {
476
+ delays[i].cancelDelay();
477
+ }
478
+ }
479
+ protected clearDelaysByType(type: string) {
480
+ let delays = this.filter(x => x.type === type);
481
+ for (let i = 0; i < delays.length; i++) {
482
+ delays[i].clearDelay();
483
+ }
484
+ if (delays.length > 0) this.setDirty();
485
+ }
486
+ public serialize() {
487
+ try {
488
+ let delays = [];
489
+ for (let i = 0; i < this.length; i++) {
490
+ delays.push(this[i].serialize());
491
+ }
492
+ return delays;
493
+ } catch (err) { logger.error(`Error serializing delays: ${err.message}`); }
494
+ }
495
+ public emitDelayState() {
496
+ try {
497
+ // We have to use a custom serializer because the properties of
498
+ // our delays will create a circular reference due to the timers and state references.
499
+ webApp.emitToClients('delays', this.serialize());
500
+ } catch (err) { logger.error(`Error emitting delay states ${err.message}`); }
501
+ }
502
+ }
503
+ export let delayMgr = new DelayManager();