nodejs-poolcontroller 8.3.0 → 8.4.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 (105) hide show
  1. package/.eslintrc.json +36 -36
  2. package/.github/ISSUE_TEMPLATE/1-bug-report.yml +84 -84
  3. package/.github/ISSUE_TEMPLATE/2-docs.md +12 -12
  4. package/.github/ISSUE_TEMPLATE/3-proposal.md +28 -28
  5. package/.github/ISSUE_TEMPLATE/config.yml +8 -8
  6. package/.github/copilot-instructions.md +63 -63
  7. package/.github/workflows/ghcr-publish.yml +67 -67
  8. package/AGENTS.md +597 -0
  9. package/CONTRIBUTING.md +74 -74
  10. package/Changelog +292 -284
  11. package/Dockerfile +62 -62
  12. package/Gruntfile.js +40 -40
  13. package/LICENSE +661 -661
  14. package/README.md +318 -309
  15. package/anslq25/MessagesMock.ts +221 -221
  16. package/anslq25/boards/MockBoardFactory.ts +49 -49
  17. package/anslq25/boards/MockEasyTouchBoard.ts +696 -696
  18. package/anslq25/boards/MockSystemBoard.ts +216 -216
  19. package/anslq25/chemistry/MockChlorinator.ts +98 -98
  20. package/anslq25/pumps/MockPump.ts +83 -83
  21. package/app.ts +115 -115
  22. package/config/Config.ts +0 -0
  23. package/config/VersionCheck.ts +0 -0
  24. package/controller/Constants.ts +809 -805
  25. package/controller/Equipment.ts +2688 -2664
  26. package/controller/Errors.ts +181 -181
  27. package/controller/Lockouts.ts +549 -549
  28. package/controller/State.ts +3738 -3701
  29. package/controller/boards/AquaLinkBoard.ts +1003 -1003
  30. package/controller/boards/BoardFactory.ts +53 -53
  31. package/controller/boards/EasyTouchBoard.ts +3202 -3202
  32. package/controller/boards/IntelliCenterBoard.ts +4393 -3899
  33. package/controller/boards/IntelliComBoard.ts +69 -69
  34. package/controller/boards/IntelliTouchBoard.ts +382 -382
  35. package/controller/boards/NixieBoard.ts +1944 -1944
  36. package/controller/boards/SunTouchBoard.ts +400 -400
  37. package/controller/boards/SystemBoard.ts +5268 -5268
  38. package/controller/comms/Comms.ts +1272 -1255
  39. package/controller/comms/ScreenLogic.ts +1665 -1665
  40. package/controller/comms/messages/Messages.ts +1433 -1406
  41. package/controller/comms/messages/config/ChlorinatorMessage.ts +5 -0
  42. package/controller/comms/messages/config/CircuitGroupMessage.ts +0 -0
  43. package/controller/comms/messages/config/CircuitMessage.ts +0 -0
  44. package/controller/comms/messages/config/ConfigMessage.ts +6 -0
  45. package/controller/comms/messages/config/CoverMessage.ts +0 -0
  46. package/controller/comms/messages/config/CustomNameMessage.ts +31 -31
  47. package/controller/comms/messages/config/EquipmentMessage.ts +216 -210
  48. package/controller/comms/messages/config/ExternalMessage.ts +96 -10
  49. package/controller/comms/messages/config/FeatureMessage.ts +0 -0
  50. package/controller/comms/messages/config/GeneralMessage.ts +0 -0
  51. package/controller/comms/messages/config/HeaterMessage.ts +0 -0
  52. package/controller/comms/messages/config/IntellichemMessage.ts +0 -0
  53. package/controller/comms/messages/config/OptionsMessage.ts +194 -174
  54. package/controller/comms/messages/config/PumpMessage.ts +0 -0
  55. package/controller/comms/messages/config/RemoteMessage.ts +0 -0
  56. package/controller/comms/messages/config/ScheduleMessage.ts +401 -390
  57. package/controller/comms/messages/config/SecurityMessage.ts +0 -0
  58. package/controller/comms/messages/config/ValveMessage.ts +0 -0
  59. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +0 -0
  60. package/controller/comms/messages/status/EquipmentStateMessage.ts +1158 -822
  61. package/controller/comms/messages/status/HeaterStateMessage.ts +135 -135
  62. package/controller/comms/messages/status/IntelliChemStateMessage.ts +448 -448
  63. package/controller/comms/messages/status/IntelliValveStateMessage.ts +36 -36
  64. package/controller/comms/messages/status/PumpStateMessage.ts +0 -0
  65. package/controller/comms/messages/status/RegalModbusStateMessage.ts +410 -410
  66. package/controller/comms/messages/status/VersionMessage.ts +103 -41
  67. package/controller/nixie/Nixie.ts +173 -173
  68. package/controller/nixie/NixieEquipment.ts +104 -104
  69. package/controller/nixie/bodies/Body.ts +120 -120
  70. package/controller/nixie/bodies/Filter.ts +135 -135
  71. package/controller/nixie/chemistry/ChemController.ts +2724 -2724
  72. package/controller/nixie/chemistry/ChemDoser.ts +806 -806
  73. package/controller/nixie/chemistry/Chlorinator.ts +367 -367
  74. package/controller/nixie/circuits/Circuit.ts +478 -478
  75. package/controller/nixie/heaters/Heater.ts +834 -834
  76. package/controller/nixie/pumps/Pump.ts +1193 -1193
  77. package/controller/nixie/schedules/Schedule.ts +401 -401
  78. package/controller/nixie/valves/Valve.ts +170 -170
  79. package/defaultConfig.json +352 -352
  80. package/docker-compose.yml +31 -31
  81. package/logger/DataLogger.ts +448 -448
  82. package/logger/Logger.ts +448 -436
  83. package/package.json +58 -58
  84. package/sendSocket.js +32 -32
  85. package/tsconfig.json +25 -25
  86. package/types/express-multer.d.ts +32 -32
  87. package/web/Server.ts +1937 -1927
  88. package/web/bindings/aqualinkD.json +559 -559
  89. package/web/bindings/influxDB.json +1066 -1066
  90. package/web/bindings/mqtt.json +721 -721
  91. package/web/bindings/mqttAlt.json +746 -746
  92. package/web/bindings/rulesManager.json +54 -54
  93. package/web/bindings/smartThings-Hubitat.json +31 -31
  94. package/web/bindings/valveRelays.json +20 -20
  95. package/web/bindings/vera.json +25 -25
  96. package/web/interfaces/baseInterface.ts +188 -188
  97. package/web/interfaces/httpInterface.ts +148 -148
  98. package/web/interfaces/influxInterface.ts +283 -283
  99. package/web/interfaces/mqttInterface.ts +695 -695
  100. package/web/interfaces/ruleInterface.ts +101 -87
  101. package/web/services/config/Config.ts +1063 -1053
  102. package/web/services/config/ConfigSocket.ts +0 -0
  103. package/web/services/state/State.ts +0 -0
  104. package/web/services/state/StateSocket.ts +0 -0
  105. package/web/services/utilities/Utilities.ts +233 -233
@@ -1,478 +1,478 @@
1
- import { EquipmentNotFoundError, InvalidEquipmentDataError, InvalidEquipmentIdError, ParameterOutOfRangeError } from '../../Errors';
2
- import { utils, Timestamp } from '../../Constants';
3
- import { logger } from '../../../logger/Logger';
4
-
5
- import { NixieEquipment, NixieChildEquipment, NixieEquipmentCollection, INixieControlPanel } from "../NixieEquipment";
6
- import { Circuit, CircuitCollection, sys } from "../../../controller/Equipment";
7
- import { CircuitState, state, ICircuitState, } from "../../State";
8
- import { setTimeout, clearTimeout } from 'timers';
9
- import { NixieControlPanel } from '../Nixie';
10
- import { webApp, InterfaceServerResponse } from "../../../web/Server";
11
- import { delayMgr } from '../../../controller/Lockouts';
12
- import { time } from 'console';
13
-
14
- export class NixieCircuitCollection extends NixieEquipmentCollection<NixieCircuit> {
15
- public pollingInterval: number = 2000;
16
- private _pollTimer: NodeJS.Timeout = null;
17
- public async deleteCircuitAsync(id: number) {
18
- try {
19
- for (let i = this.length - 1; i >= 0; i--) {
20
- let circ = this[i];
21
- if (circ.id === id) {
22
- await circ.closeAsync();
23
- this.splice(i, 1);
24
- }
25
- }
26
- } catch (err) { return Promise.reject(`Nixie Control Panel deleteCircuitAsync ${err.message}`); }
27
- }
28
- public async sendOnOffSequenceAsync(id: number, count: number | { isOn: boolean, timeout: number }[]) {
29
- try {
30
- let c: NixieCircuit = this.find(elem => elem.id === id) as NixieCircuit;
31
- if (typeof c === 'undefined') return Promise.reject(new Error(`NCP: Circuit ${id} could not be found to send sequence ${count}.`));
32
- await c.sendOnOffSequenceAsync(count);
33
-
34
- } catch (err) { return logger.error(`NCP: sendOnOffSequence: ${err.message}`); }
35
- }
36
- public async setServiceModeAsync() {
37
- try {
38
- for (let i = this.length - 1; i >= 0; i--) {
39
- try {
40
- let c = this[i] as NixieCircuit;
41
- await c.setServiceModeAsync();
42
- } catch (err) { logger.error(`Error stopping Nixie Circuit ${err}`); }
43
- }
44
-
45
- } catch (err) { return logger.error(`NCP: setServiceModeAsync: ${err.message}`); }
46
- }
47
- public async setLightThemeAsync(id: number, theme: any) {
48
- let c: NixieCircuit = this.find(elem => elem.id === id) as NixieCircuit;
49
- if (typeof c === 'undefined') return Promise.reject(new Error(`NCP: Circuit ${id} could not be found to set light theme ${theme.name}.`));
50
- await c.setLightThemeAsync(theme);
51
- } catch(err) { return logger.error(`NCP: sendOnOffSequence: ${err.message}`); }
52
- public async setCircuitStateAsync(cstate: ICircuitState, val: boolean) {
53
- try {
54
- let c: NixieCircuit = this.find(elem => elem.id === cstate.id) as NixieCircuit;
55
- if (typeof c === 'undefined') return Promise.reject(new Error(`NCP: Circuit ${cstate.id}-${cstate.name} could not be found to set the state to ${val}.`));
56
- await c.setCircuitStateAsync(cstate, val);
57
- }
58
- catch (err) { return logger.error(`NCP: setCircuitStateAsync ${cstate.id}-${cstate.name}: ${err.message}`); }
59
- }
60
- public async setCircuitAsync(circuit: Circuit, data: any) {
61
- // By the time we get here we know that we are in control and this is a REMChem.
62
- try {
63
- let c: NixieCircuit = this.find(elem => elem.id === circuit.id) as NixieCircuit;
64
- if (typeof c === 'undefined') {
65
- circuit.master = 1;
66
- c = new NixieCircuit(this.controlPanel, circuit);
67
- this.push(c);
68
- await c.setCircuitAsync(data);
69
- logger.debug(`NixieController: A circuit was not found for id #${circuit.id} creating circuit`);
70
- }
71
- else {
72
- await c.setCircuitAsync(data);
73
- }
74
- }
75
- catch (err) { logger.error(`setCircuitAsync: ${err.message}`); return Promise.reject(err); }
76
- }
77
- public async checkCircuitEggTimerExpirationAsync(cstate: ICircuitState) {
78
- try {
79
- let c: NixieCircuit = this.find(elem => elem.id === cstate.id) as NixieCircuit;
80
- await c.checkCircuitEggTimerExpirationAsync(cstate);
81
- } catch (err) { logger.error(`NCP: Error synching circuit states: ${err}`); }
82
- }
83
- public async initAsync(circuits: CircuitCollection) {
84
- try {
85
- for (let i = 0; i < circuits.length; i++) {
86
- let circuit = circuits.getItemByIndex(i);
87
- if (circuit.master === 1) {
88
- if (typeof this.find(elem => elem.id === circuit.id) === 'undefined') {
89
- logger.info(`Initializing Nixie circuit ${circuit.name}`);
90
- let ncircuit = new NixieCircuit(this.controlPanel, circuit);
91
- this.push(ncircuit);
92
- }
93
- }
94
- }
95
- }
96
- catch (err) { return Promise.reject(logger.error(`NixieController: Circuit initAsync: ${err.message}`)); }
97
- }
98
- public async closeAsync() {
99
- try {
100
- for (let i = this.length - 1; i >= 0; i--) {
101
- try {
102
- await this[i].closeAsync();
103
- this.splice(i, 1);
104
- } catch (err) { logger.error(`Error stopping Nixie Circuit ${err}`); }
105
- }
106
- } catch (err) { } // Don't bail if we have an errror.
107
- }
108
-
109
- public async initCircuitAsync(circuit: Circuit): Promise<NixieCircuit> {
110
- try {
111
- let c: NixieCircuit = this.find(elem => elem.id === circuit.id) as NixieCircuit;
112
- if (typeof c === 'undefined') {
113
- c = new NixieCircuit(this.controlPanel, circuit);
114
- this.push(c);
115
- }
116
- return c;
117
- } catch (err) { logger.error(`initCircuitAsync: ${err.message}`); return Promise.reject(err); }
118
- }
119
- public async pollCircuitsAsync() {
120
- let self = this;
121
- try {
122
- if (typeof this._pollTimer !== 'undefined' || this._pollTimer) clearTimeout(this._pollTimer);
123
- this._pollTimer = null;
124
- let success = false;
125
-
126
- } catch (err) { logger.error(`Error polling circuits: ${err.message}`); return Promise.reject(err); }
127
- finally { this._pollTimer = setTimeout(async () => await self.pollCircuitsAsync(), this.pollingInterval || 10000); }
128
- }
129
- }
130
- export class NixieCircuit extends NixieEquipment {
131
- public circuit: Circuit;
132
- private _sequencing = false;
133
- private scheduled = false;
134
- private timeOn: Timestamp;
135
- private timeOff: Timestamp;
136
- constructor(ncp: INixieControlPanel, circuit: Circuit) {
137
- super(ncp);
138
- this.circuit = circuit;
139
- // Clear out the delays.
140
- let cstate = state.circuits.getItemById(circuit.id);
141
- cstate.startDelay = false;
142
- cstate.stopDelay = false;
143
- cstate.name = circuit.name;
144
- cstate.type = circuit.type;
145
- cstate.showInFeatures = circuit.showInFeatures;
146
- }
147
- public async setServiceModeAsync() {
148
- let cstate = state.circuits.getItemById(this.circuit.id);
149
- await this.setCircuitStateAsync(cstate, false, false);
150
- }
151
- public get id(): number { return typeof this.circuit !== 'undefined' ? this.circuit.id : -1; }
152
- public get eggTimerOff(): Timestamp { return typeof this.timeOn !== 'undefined' && !this.circuit.dontStop ? this.timeOn.clone().addMinutes(this.circuit.eggTimer) : undefined; }
153
- public async setCircuitAsync(data: any) {
154
- try {
155
- let circuit = this.circuit;
156
- }
157
- catch (err) { logger.error(`Nixie setCircuitAsync: ${err.message}`); return Promise.reject(err); }
158
- }
159
- protected async setIntelliBriteThemeAsync(cstate: CircuitState, theme: any): Promise<InterfaceServerResponse> {
160
- let arr = [];
161
- let count = typeof theme !== 'undefined' && theme.sequence ? theme.sequence : 0;
162
-
163
- // Removing this. No need to turn the light off first. We actually need it on to start the sequence for theme setting to work correctly when the light is starting from the off state.
164
- // if (cstate.isOn) arr.push({ isOn: false, timeout: 1000 });
165
-
166
- // Start the sequence of off/on after the light is on.
167
- arr.push({ isOn: true, timeout: 100 });
168
- for (let i = 0; i < count; i++) {
169
- arr.push({ isOn: false, timeout: 100 });
170
- arr.push({ isOn: true, timeout: 100 });
171
- }
172
- // Ensure light stays on long enough for the theme to stick (required for light group theme setting to function correctly).
173
- // 2s was too short.
174
- arr.push({ isOn: true, timeout: 3000 });
175
-
176
- logger.debug(arr);
177
- let res = await NixieEquipment.putDeviceService(this.circuit.connectionId, `/state/device/${this.circuit.deviceBinding}`, arr, 60000);
178
- // Even though we ended with on we need to make sure that the relay stays on now that we are done.
179
- if (!res.error) {
180
- this._sequencing = false;
181
- await this.setCircuitStateAsync(cstate, true, false);
182
- }
183
- return res;
184
- }
185
- protected async setPoolToneThemeAsync(cstate: CircuitState, theme: any): Promise<InterfaceServerResponse> {
186
- let ptheme = sys.board.valueMaps.lightThemes.findItem(cstate.lightingTheme) || { val: 0, sequence: 0 };
187
- // First check to see if we are on. If we are not then we need to emit our status as if we are initializing and busy.
188
- let arr = [];
189
- if (ptheme.val === 0) {
190
- // We don't know our previous theme so we are going to sync the lights to get a starting point.
191
- arr.push({ isOn: true, timeout: 1000 }); // Turn on for 1 second
192
- arr.push({ isOn: false, timeout: 5000 }); // Turn off for 5 seconds
193
- arr.push({ isOn: true, timeout: 1000 });
194
- ptheme = sys.board.valueMaps.lightThemes.findItem('eveningsea');
195
- }
196
- let count = theme.sequence - ptheme.sequence;
197
- if (count < 0) count = count + 16;
198
- for (let i = 0; i < count; i++) {
199
- arr.push({ isOn: true, timeout: 200 });
200
- arr.push({ isOn: false, timeout: 200 });
201
- }
202
- console.log(arr);
203
- if (arr.length === 0) return new InterfaceServerResponse(200, 'Success');
204
- let res = await NixieEquipment.putDeviceService(this.circuit.connectionId, `/state/device/${this.circuit.deviceBinding}`, arr, 60000);
205
- // Even though we ended with on we need to make sure that the relay stays on now that we are done.
206
- if (!res.error) {
207
- cstate.lightingTheme = ptheme.val;
208
- cstate.isOn = true; // At this point the relay will be off but we want the process
209
- // to assume that the relay state is not actually changing.
210
- this._sequencing = false;
211
- await this.setCircuitStateAsync(cstate, true, false);
212
- }
213
- return res;
214
- }
215
- protected async setWaterColorsThemeAsync(cstate: CircuitState, theme: any): Promise<InterfaceServerResponse> {
216
- // RSG 2024.12.24 - This logic was aligned with the Pool Tone themes. I haven't checked if that
217
- // logic is correct, but made a copy and adjusted for the watercolors themes.
218
-
219
- let ptheme = sys.board.valueMaps.lightThemes.findItem(cstate.lightingTheme) || { val: 0, sequence: 0 };
220
- // First check to see if we are on. If we are not then we need to emit our status as if we are initializing and busy.
221
- let arr = [];
222
- if (ptheme.val === 0) {
223
- // We don't know our previous theme so we are going to sync the lights to get a starting point.
224
- arr.push({ isOn: true, timeout: 1000 }); // Turn on for 1 second
225
- arr.push({ isOn: false, timeout: 5000 }); // Turn off for 5 seconds
226
- arr.push({ isOn: true, timeout: 1000 });
227
- ptheme = sys.board.valueMaps.lightThemes.findItem('alpinewhite');
228
- }
229
- let count = theme.sequence - ptheme.sequence;
230
- if (count < 0) count = count + 14;
231
- for (let i = 0; i < count; i++) {
232
- arr.push({ isOn: true, timeout: 200 });
233
- arr.push({ isOn: false, timeout: 200 });
234
- }
235
- console.log(arr);
236
- if (arr.length === 0) return new InterfaceServerResponse(200, 'Success');
237
- let res = await NixieEquipment.putDeviceService(this.circuit.connectionId, `/state/device/${this.circuit.deviceBinding}`, arr, 60000);
238
- // Even though we ended with on we need to make sure that the relay stays on now that we are done.
239
- if (!res.error) {
240
- cstate.lightingTheme = ptheme.val;
241
- cstate.isOn = true; // At this point the relay will be off but we want the process
242
- // to assume that the relay state is not actually changing.
243
- this._sequencing = false;
244
- await this.setCircuitStateAsync(cstate, true, false);
245
- }
246
- return res;
247
- }
248
- protected async setColorLogicThemeAsync(cstate: CircuitState, theme: any): Promise<InterfaceServerResponse> {
249
- let ptheme = sys.board.valueMaps.lightThemes.findItem(cstate.lightingTheme) || { val: 0, sequence: 0 };
250
- // First check to see if we are on. If we are not then we need to emit our status as if we are initializing and busy.
251
- let arr = [];
252
- if (ptheme.val === 0) {
253
- // We don't know our previous theme so we are going to sync the lights to get a starting point.
254
- arr.push({ isOn: true, timeout: 1000 }); // Turn on for 1 second
255
- arr.push({ isOn: false, timeout: 12000 }); // Turn off for 12 seconds
256
- arr.push({ isOn: true, timeout: 1000 });
257
- ptheme = sys.board.valueMaps.lightThemes.findItem('voodoolounge');
258
- }
259
- else if (!cstate.isOn) {
260
- if (typeof this.timeOff === 'undefined' || new Date().getTime() - this.timeOff.getTime() > 15000) {
261
- // We have been off for more than 15 seconds so we need to turn it on then wait for 17 seconds while the safety light processes.
262
- arr.push({ isOn: true, timeout: 17000 }); // Crazy pants
263
- }
264
- else arr.push({ isOn: true, timeout: 1000 }); // Start with on
265
- }
266
- let count = theme.sequence - ptheme.sequence;
267
- if (count < 0) count = count + 17;
268
- for (let i = 0; i < count; i++) {
269
- arr.push({ isOn: true, timeout: 200 }); // Use 200ms since @Crewski verified 200ms is reliable
270
- arr.push({ isOn: false, timeout: 200 });
271
- }
272
- console.log(arr);
273
- if (arr.length === 0) return new InterfaceServerResponse(200, 'Success');
274
- let res = await NixieEquipment.putDeviceService(this.circuit.connectionId, `/state/device/${this.circuit.deviceBinding}`, arr, 60000);
275
- // Even though we ended with on we need to make sure that the relay stays on now that we are done.
276
- if (!res.error) {
277
- cstate.lightingTheme = ptheme.val;
278
- cstate.isOn = true; // At this point the relay will be off but we want the process
279
- // to assume that the relay state is not actually changing.
280
- this._sequencing = false;
281
- await this.setCircuitStateAsync(cstate, true, false);
282
- }
283
- return res;
284
- }
285
- // This method only dispatches to the proper light setting algorithm. Previously we assumed that simply switching on/off sequences the proper
286
- // number of times was all there was but the nutcases who make these things must torture small animals.
287
- public async setLightThemeAsync(theme: any) {
288
- try {
289
- this._sequencing = true;
290
- let res = new InterfaceServerResponse(200, 'Success');
291
- let arr = [];
292
- let cstate = state.circuits.getItemById(this.circuit.id);
293
- let type = sys.board.valueMaps.circuitFunctions.transform(this.circuit.type);
294
- // Now set the command state so that users do not get all button mashy.
295
- cstate.action = sys.board.valueMaps.circuitActions.getValue('settheme');
296
- cstate.emitEquipmentChange();
297
- switch (type.name) {
298
- case 'colorcascade':
299
- case 'globrite':
300
- case 'magicstream':
301
- case 'intellibrite':
302
- res = await this.setIntelliBriteThemeAsync(cstate, theme);
303
- break;
304
- case 'colorlogic':
305
- res = await this.setColorLogicThemeAsync(cstate, theme);
306
- break;
307
- case 'watercolors':
308
- res = await this.setWaterColorsThemeAsync(cstate, theme);
309
- break;
310
- case 'pooltone':
311
- res = await this.setPoolToneThemeAsync(cstate, theme);
312
- break;
313
- }
314
- cstate.action = 0;
315
- // Make sure clients know that we are done.
316
- cstate.emitEquipmentChange();
317
- return res;
318
- } catch (err) { logger.error(`Nixie: Error setting lighting theme ${this.id} - ${theme.desc}: ${err.message}`); }
319
- finally { this._sequencing = false; }
320
- }
321
- public async sendOnOffSequenceAsync(count: number | { isOn: boolean, timeout: number }[], timeout?: number): Promise<InterfaceServerResponse> {
322
- try {
323
-
324
- this._sequencing = true;
325
- let arr = [];
326
- let cstate = state.circuits.getItemById(this.circuit.id);
327
-
328
- if (typeof count === 'number') {
329
- if (cstate.isOn) arr.push({ isOn: false, timeout: 1000 });
330
- let t = typeof timeout === 'undefined' ? 100 : timeout;
331
- //arr.push({ isOn: false, timeout: t }); // This may not be needed but we always need to start from off.
332
- //[{ isOn: true, timeout: 1000 }, { isOn: false, timeout: 1000 }]
333
- for (let i = 0; i < count; i++) {
334
- if (i < count - 1) {
335
- arr.push({ isOn: true, timeout: t });
336
- arr.push({ isOn: false, timeout: t });
337
- }
338
- else arr.push({ isOn: true, timeout: 1000 });
339
- }
340
- console.log(arr);
341
- }
342
- else arr = count;
343
- let res = await NixieEquipment.putDeviceService(this.circuit.connectionId, `/state/device/${this.circuit.deviceBinding}`, arr, 60000);
344
- // Even though we ended with on we need to make sure that the relay stays on now that we are done.
345
- if (!res.error) {
346
- this._sequencing = false;
347
- await this.setCircuitStateAsync(cstate, true, false);
348
- }
349
- return res;
350
- } catch (err) { logger.error(`Nixie: Error sending circuit sequence ${this.id}: ${count}`); }
351
- finally { this._sequencing = false; }
352
- }
353
- public async setCircuitStateAsync(cstate: ICircuitState, val: boolean, scheduled: boolean = false): Promise<InterfaceServerResponse> {
354
- try {
355
- // Lets do some special processing here for service mode
356
- if (state.mode !== 0 && val) {
357
- // Always set the state to off if we are in service mode for bodies. Other circuits
358
- // may actually be turned on but only if they are not one of the body circuits.
359
- switch (sys.board.valueMaps.circuitFunctions.getName(this.circuit.type)) {
360
- case 'pool':
361
- case 'spa':
362
- case 'chemrelay':
363
- val = false;
364
- break;
365
- }
366
- }
367
- if (val !== cstate.isOn) {
368
- logger.info(`NCP: Setting Circuit ${cstate.name} to ${val}`);
369
- if (cstate.isOn && val) {
370
- // We are already on so lets check the egg timer and shut it off if it has expired.
371
- let eggOff = this.eggTimerOff;
372
- if (typeof eggOff !== 'undefined' && eggOff.getTime() <= new Date().getTime()) val = false;
373
- }
374
- // Check to see if we should be on by poking the schedules.
375
- }
376
- if (utils.isNullOrEmpty(this.circuit.connectionId) || utils.isNullOrEmpty(this.circuit.deviceBinding)) {
377
- if (val && val !== cstate.isOn) {
378
- sys.board.circuits.setEndTime(sys.circuits.getInterfaceById(cstate.id), cstate, val);
379
- }
380
- else if (!val) {
381
- if (cstate.manualPriorityActive) delayMgr.cancelManualPriorityDelay(cstate.id);
382
- cstate.manualPriorityActive = false; // if the delay was previously cancelled, still need to turn this off
383
- }
384
- cstate.isOn = val;
385
- return new InterfaceServerResponse(200, 'Success');
386
- }
387
- if (this._sequencing) return new InterfaceServerResponse(200, 'Success');
388
- let res = await NixieEquipment.putDeviceService(this.circuit.connectionId, `/state/device/${this.circuit.deviceBinding}`, { isOn: val, latch: val ? 10000 : undefined });
389
- if (res.status.code === 200) {
390
- // Set this up so we can process our egg timer.
391
- if (val && val !== cstate.isOn) {
392
- sys.board.circuits.setEndTime(sys.circuits.getInterfaceById(cstate.id), cstate, val);
393
- switch (sys.board.valueMaps.circuitFunctions.getName(this.circuit.type)) {
394
- case 'colorlogic':
395
- if (!this._sequencing) {
396
- // We need a little bit of special time for ColorLogic circuits.
397
- let timeDiff = typeof this.timeOff === 'undefined' ? 30000 : new Date().getTime() - this.timeOff.getTime();
398
- //logger.info(`Resetting ColorLogic themes ${cstate.isOn}:${val} ${cstate.lightingTheme}... ${timeDiff}`);
399
- if (timeDiff > 15000) {
400
- // There is this wacko thing that the lights will come on white for 15 seconds
401
- // so we need to make sure they don't try to advance the theme setting during this period. We will simply set this to a holding pattern for
402
- // that timeframe.
403
- cstate.action = sys.board.valueMaps.circuitActions.getValue('settheme');
404
- let theme = cstate.lightingTheme;
405
- cstate.lightingTheme = sys.board.valueMaps.lightThemes.getValue('cloudwhite');
406
- cstate.startDelay = true;
407
- setTimeout(() => { cstate.startDelay = false; cstate.action = 0; cstate.lightingTheme = theme; cstate.emitEquipmentChange(); }, 17000);
408
- }
409
- else if (timeDiff <= 10000) {
410
- // If the user turns the light back on within 10 seconds. Surprise! You are forced into the next theme.
411
- let thm = sys.board.valueMaps.lightThemes.get(cstate.lightingTheme);
412
- let themes = this.circuit.getLightThemes();
413
- cstate.lightingTheme = thm.sequence === 17 ? themes.find(elem => elem.sequence === 1).val : themes.find(elem => elem.sequence === thm.sequence + 1).val;
414
- }
415
- else if (timeDiff <= 15000) {
416
- // If the user turns the light back on before 15 seconds expire then we are going to do voodoo. Switch the theme to voodoolounge.
417
- cstate.lightingTheme = sys.board.valueMaps.lightThemes.getValue('voodoolounge');
418
- }
419
- }
420
- break;
421
- }
422
- }
423
- else if (!val) {
424
- delayMgr.cancelManualPriorityDelays();
425
- cstate.manualPriorityActive = false; // if the delay was previously cancelled, still need to turn this off
426
- }
427
- if (!val && cstate.isOn) this.timeOff = new Timestamp();
428
- cstate.isOn = val;
429
- }
430
- return res;
431
- } catch (err) { logger.error(`Nixie: Error setting circuit state ${cstate.id}-${cstate.name} to ${val}`); }
432
- }
433
- public async checkCircuitEggTimerExpirationAsync(cstate: ICircuitState) {
434
- // if circuit end time is past current time, either the schedule is finished
435
- // (this should already be turned off) or the egg timer has expired
436
- try {
437
- if (!cstate.isActive || !cstate.isOn) return;
438
- if (typeof cstate.endTime !== 'undefined') {
439
- if (cstate.endTime.toDate() < new Timestamp().toDate()) {
440
- await sys.board.circuits.setCircuitStateAsync(cstate.id, false);
441
- cstate.emitEquipmentChange();
442
- }
443
- }
444
- } catch (err) { logger.error(`Error syncing circuit: ${err}`); }
445
- }
446
- private async checkHardwareStatusAsync(connectionId: string, deviceBinding: string) {
447
- try {
448
- let dev = await NixieEquipment.getDeviceService(connectionId, `/status/device/${deviceBinding}`);
449
- return dev;
450
- } catch (err) { logger.error(`Nixie Circuit checkHardwareStatusAsync: ${err.message}`); return { hasFault: true } }
451
- }
452
- public async validateSetupAsync(circuit: Circuit, cstate: CircuitState) {
453
- try {
454
- if (typeof circuit.connectionId !== 'undefined' && circuit.connectionId !== ''
455
- && typeof circuit.deviceBinding !== 'undefined' && circuit.deviceBinding !== '') {
456
- try {
457
- let stat = await this.checkHardwareStatusAsync(circuit.connectionId, circuit.deviceBinding);
458
- // If we have a status check the return.
459
- cstate.commStatus = stat.hasFault ? 1 : 0;
460
- } catch (err) { cstate.commStatus = 1; }
461
- }
462
- else
463
- cstate.commStatus = 0;
464
- // The validation will be different if the circuit is on or not. So lets get that information.
465
- } catch (err) { logger.error(`Nixie Error checking Circuit Hardware ${this.circuit.name}: ${err.message}`); cstate.commStatus = 1; return Promise.reject(err); }
466
- }
467
- public async closeAsync() {
468
- try {
469
- let cstate = state.circuits.getItemById(this.circuit.id);
470
- cstate.stopDelay = false;
471
- cstate.startDelay = false;
472
- await this.setCircuitStateAsync(cstate, false);
473
- cstate.emitEquipmentChange();
474
- }
475
- catch (err) { logger.error(`Nixie Circuit closeAsync: ${err.message}`); return Promise.reject(err); }
476
- }
477
- public logData(filename: string, data: any) { this.controlPanel.logData(filename, data); }
478
- }
1
+ import { EquipmentNotFoundError, InvalidEquipmentDataError, InvalidEquipmentIdError, ParameterOutOfRangeError } from '../../Errors';
2
+ import { utils, Timestamp } from '../../Constants';
3
+ import { logger } from '../../../logger/Logger';
4
+
5
+ import { NixieEquipment, NixieChildEquipment, NixieEquipmentCollection, INixieControlPanel } from "../NixieEquipment";
6
+ import { Circuit, CircuitCollection, sys } from "../../../controller/Equipment";
7
+ import { CircuitState, state, ICircuitState, } from "../../State";
8
+ import { setTimeout, clearTimeout } from 'timers';
9
+ import { NixieControlPanel } from '../Nixie';
10
+ import { webApp, InterfaceServerResponse } from "../../../web/Server";
11
+ import { delayMgr } from '../../../controller/Lockouts';
12
+ import { time } from 'console';
13
+
14
+ export class NixieCircuitCollection extends NixieEquipmentCollection<NixieCircuit> {
15
+ public pollingInterval: number = 2000;
16
+ private _pollTimer: NodeJS.Timeout = null;
17
+ public async deleteCircuitAsync(id: number) {
18
+ try {
19
+ for (let i = this.length - 1; i >= 0; i--) {
20
+ let circ = this[i];
21
+ if (circ.id === id) {
22
+ await circ.closeAsync();
23
+ this.splice(i, 1);
24
+ }
25
+ }
26
+ } catch (err) { return Promise.reject(`Nixie Control Panel deleteCircuitAsync ${err.message}`); }
27
+ }
28
+ public async sendOnOffSequenceAsync(id: number, count: number | { isOn: boolean, timeout: number }[]) {
29
+ try {
30
+ let c: NixieCircuit = this.find(elem => elem.id === id) as NixieCircuit;
31
+ if (typeof c === 'undefined') return Promise.reject(new Error(`NCP: Circuit ${id} could not be found to send sequence ${count}.`));
32
+ await c.sendOnOffSequenceAsync(count);
33
+
34
+ } catch (err) { return logger.error(`NCP: sendOnOffSequence: ${err.message}`); }
35
+ }
36
+ public async setServiceModeAsync() {
37
+ try {
38
+ for (let i = this.length - 1; i >= 0; i--) {
39
+ try {
40
+ let c = this[i] as NixieCircuit;
41
+ await c.setServiceModeAsync();
42
+ } catch (err) { logger.error(`Error stopping Nixie Circuit ${err}`); }
43
+ }
44
+
45
+ } catch (err) { return logger.error(`NCP: setServiceModeAsync: ${err.message}`); }
46
+ }
47
+ public async setLightThemeAsync(id: number, theme: any) {
48
+ let c: NixieCircuit = this.find(elem => elem.id === id) as NixieCircuit;
49
+ if (typeof c === 'undefined') return Promise.reject(new Error(`NCP: Circuit ${id} could not be found to set light theme ${theme.name}.`));
50
+ await c.setLightThemeAsync(theme);
51
+ } catch(err) { return logger.error(`NCP: sendOnOffSequence: ${err.message}`); }
52
+ public async setCircuitStateAsync(cstate: ICircuitState, val: boolean) {
53
+ try {
54
+ let c: NixieCircuit = this.find(elem => elem.id === cstate.id) as NixieCircuit;
55
+ if (typeof c === 'undefined') return Promise.reject(new Error(`NCP: Circuit ${cstate.id}-${cstate.name} could not be found to set the state to ${val}.`));
56
+ await c.setCircuitStateAsync(cstate, val);
57
+ }
58
+ catch (err) { return logger.error(`NCP: setCircuitStateAsync ${cstate.id}-${cstate.name}: ${err.message}`); }
59
+ }
60
+ public async setCircuitAsync(circuit: Circuit, data: any) {
61
+ // By the time we get here we know that we are in control and this is a REMChem.
62
+ try {
63
+ let c: NixieCircuit = this.find(elem => elem.id === circuit.id) as NixieCircuit;
64
+ if (typeof c === 'undefined') {
65
+ circuit.master = 1;
66
+ c = new NixieCircuit(this.controlPanel, circuit);
67
+ this.push(c);
68
+ await c.setCircuitAsync(data);
69
+ logger.debug(`NixieController: A circuit was not found for id #${circuit.id} creating circuit`);
70
+ }
71
+ else {
72
+ await c.setCircuitAsync(data);
73
+ }
74
+ }
75
+ catch (err) { logger.error(`setCircuitAsync: ${err.message}`); return Promise.reject(err); }
76
+ }
77
+ public async checkCircuitEggTimerExpirationAsync(cstate: ICircuitState) {
78
+ try {
79
+ let c: NixieCircuit = this.find(elem => elem.id === cstate.id) as NixieCircuit;
80
+ await c.checkCircuitEggTimerExpirationAsync(cstate);
81
+ } catch (err) { logger.error(`NCP: Error synching circuit states: ${err}`); }
82
+ }
83
+ public async initAsync(circuits: CircuitCollection) {
84
+ try {
85
+ for (let i = 0; i < circuits.length; i++) {
86
+ let circuit = circuits.getItemByIndex(i);
87
+ if (circuit.master === 1) {
88
+ if (typeof this.find(elem => elem.id === circuit.id) === 'undefined') {
89
+ logger.info(`Initializing Nixie circuit ${circuit.name}`);
90
+ let ncircuit = new NixieCircuit(this.controlPanel, circuit);
91
+ this.push(ncircuit);
92
+ }
93
+ }
94
+ }
95
+ }
96
+ catch (err) { return Promise.reject(logger.error(`NixieController: Circuit initAsync: ${err.message}`)); }
97
+ }
98
+ public async closeAsync() {
99
+ try {
100
+ for (let i = this.length - 1; i >= 0; i--) {
101
+ try {
102
+ await this[i].closeAsync();
103
+ this.splice(i, 1);
104
+ } catch (err) { logger.error(`Error stopping Nixie Circuit ${err}`); }
105
+ }
106
+ } catch (err) { } // Don't bail if we have an errror.
107
+ }
108
+
109
+ public async initCircuitAsync(circuit: Circuit): Promise<NixieCircuit> {
110
+ try {
111
+ let c: NixieCircuit = this.find(elem => elem.id === circuit.id) as NixieCircuit;
112
+ if (typeof c === 'undefined') {
113
+ c = new NixieCircuit(this.controlPanel, circuit);
114
+ this.push(c);
115
+ }
116
+ return c;
117
+ } catch (err) { logger.error(`initCircuitAsync: ${err.message}`); return Promise.reject(err); }
118
+ }
119
+ public async pollCircuitsAsync() {
120
+ let self = this;
121
+ try {
122
+ if (typeof this._pollTimer !== 'undefined' || this._pollTimer) clearTimeout(this._pollTimer);
123
+ this._pollTimer = null;
124
+ let success = false;
125
+
126
+ } catch (err) { logger.error(`Error polling circuits: ${err.message}`); return Promise.reject(err); }
127
+ finally { this._pollTimer = setTimeout(async () => await self.pollCircuitsAsync(), this.pollingInterval || 10000); }
128
+ }
129
+ }
130
+ export class NixieCircuit extends NixieEquipment {
131
+ public circuit: Circuit;
132
+ private _sequencing = false;
133
+ private scheduled = false;
134
+ private timeOn: Timestamp;
135
+ private timeOff: Timestamp;
136
+ constructor(ncp: INixieControlPanel, circuit: Circuit) {
137
+ super(ncp);
138
+ this.circuit = circuit;
139
+ // Clear out the delays.
140
+ let cstate = state.circuits.getItemById(circuit.id);
141
+ cstate.startDelay = false;
142
+ cstate.stopDelay = false;
143
+ cstate.name = circuit.name;
144
+ cstate.type = circuit.type;
145
+ cstate.showInFeatures = circuit.showInFeatures;
146
+ }
147
+ public async setServiceModeAsync() {
148
+ let cstate = state.circuits.getItemById(this.circuit.id);
149
+ await this.setCircuitStateAsync(cstate, false, false);
150
+ }
151
+ public get id(): number { return typeof this.circuit !== 'undefined' ? this.circuit.id : -1; }
152
+ public get eggTimerOff(): Timestamp { return typeof this.timeOn !== 'undefined' && !this.circuit.dontStop ? this.timeOn.clone().addMinutes(this.circuit.eggTimer) : undefined; }
153
+ public async setCircuitAsync(data: any) {
154
+ try {
155
+ let circuit = this.circuit;
156
+ }
157
+ catch (err) { logger.error(`Nixie setCircuitAsync: ${err.message}`); return Promise.reject(err); }
158
+ }
159
+ protected async setIntelliBriteThemeAsync(cstate: CircuitState, theme: any): Promise<InterfaceServerResponse> {
160
+ let arr = [];
161
+ let count = typeof theme !== 'undefined' && theme.sequence ? theme.sequence : 0;
162
+
163
+ // Removing this. No need to turn the light off first. We actually need it on to start the sequence for theme setting to work correctly when the light is starting from the off state.
164
+ // if (cstate.isOn) arr.push({ isOn: false, timeout: 1000 });
165
+
166
+ // Start the sequence of off/on after the light is on.
167
+ arr.push({ isOn: true, timeout: 100 });
168
+ for (let i = 0; i < count; i++) {
169
+ arr.push({ isOn: false, timeout: 100 });
170
+ arr.push({ isOn: true, timeout: 100 });
171
+ }
172
+ // Ensure light stays on long enough for the theme to stick (required for light group theme setting to function correctly).
173
+ // 2s was too short.
174
+ arr.push({ isOn: true, timeout: 3000 });
175
+
176
+ logger.debug(arr);
177
+ let res = await NixieEquipment.putDeviceService(this.circuit.connectionId, `/state/device/${this.circuit.deviceBinding}`, arr, 60000);
178
+ // Even though we ended with on we need to make sure that the relay stays on now that we are done.
179
+ if (!res.error) {
180
+ this._sequencing = false;
181
+ await this.setCircuitStateAsync(cstate, true, false);
182
+ }
183
+ return res;
184
+ }
185
+ protected async setPoolToneThemeAsync(cstate: CircuitState, theme: any): Promise<InterfaceServerResponse> {
186
+ let ptheme = sys.board.valueMaps.lightThemes.findItem(cstate.lightingTheme) || { val: 0, sequence: 0 };
187
+ // First check to see if we are on. If we are not then we need to emit our status as if we are initializing and busy.
188
+ let arr = [];
189
+ if (ptheme.val === 0) {
190
+ // We don't know our previous theme so we are going to sync the lights to get a starting point.
191
+ arr.push({ isOn: true, timeout: 1000 }); // Turn on for 1 second
192
+ arr.push({ isOn: false, timeout: 5000 }); // Turn off for 5 seconds
193
+ arr.push({ isOn: true, timeout: 1000 });
194
+ ptheme = sys.board.valueMaps.lightThemes.findItem('eveningsea');
195
+ }
196
+ let count = theme.sequence - ptheme.sequence;
197
+ if (count < 0) count = count + 16;
198
+ for (let i = 0; i < count; i++) {
199
+ arr.push({ isOn: true, timeout: 200 });
200
+ arr.push({ isOn: false, timeout: 200 });
201
+ }
202
+ console.log(arr);
203
+ if (arr.length === 0) return new InterfaceServerResponse(200, 'Success');
204
+ let res = await NixieEquipment.putDeviceService(this.circuit.connectionId, `/state/device/${this.circuit.deviceBinding}`, arr, 60000);
205
+ // Even though we ended with on we need to make sure that the relay stays on now that we are done.
206
+ if (!res.error) {
207
+ cstate.lightingTheme = ptheme.val;
208
+ cstate.isOn = true; // At this point the relay will be off but we want the process
209
+ // to assume that the relay state is not actually changing.
210
+ this._sequencing = false;
211
+ await this.setCircuitStateAsync(cstate, true, false);
212
+ }
213
+ return res;
214
+ }
215
+ protected async setWaterColorsThemeAsync(cstate: CircuitState, theme: any): Promise<InterfaceServerResponse> {
216
+ // RSG 2024.12.24 - This logic was aligned with the Pool Tone themes. I haven't checked if that
217
+ // logic is correct, but made a copy and adjusted for the watercolors themes.
218
+
219
+ let ptheme = sys.board.valueMaps.lightThemes.findItem(cstate.lightingTheme) || { val: 0, sequence: 0 };
220
+ // First check to see if we are on. If we are not then we need to emit our status as if we are initializing and busy.
221
+ let arr = [];
222
+ if (ptheme.val === 0) {
223
+ // We don't know our previous theme so we are going to sync the lights to get a starting point.
224
+ arr.push({ isOn: true, timeout: 1000 }); // Turn on for 1 second
225
+ arr.push({ isOn: false, timeout: 5000 }); // Turn off for 5 seconds
226
+ arr.push({ isOn: true, timeout: 1000 });
227
+ ptheme = sys.board.valueMaps.lightThemes.findItem('alpinewhite');
228
+ }
229
+ let count = theme.sequence - ptheme.sequence;
230
+ if (count < 0) count = count + 14;
231
+ for (let i = 0; i < count; i++) {
232
+ arr.push({ isOn: true, timeout: 200 });
233
+ arr.push({ isOn: false, timeout: 200 });
234
+ }
235
+ console.log(arr);
236
+ if (arr.length === 0) return new InterfaceServerResponse(200, 'Success');
237
+ let res = await NixieEquipment.putDeviceService(this.circuit.connectionId, `/state/device/${this.circuit.deviceBinding}`, arr, 60000);
238
+ // Even though we ended with on we need to make sure that the relay stays on now that we are done.
239
+ if (!res.error) {
240
+ cstate.lightingTheme = ptheme.val;
241
+ cstate.isOn = true; // At this point the relay will be off but we want the process
242
+ // to assume that the relay state is not actually changing.
243
+ this._sequencing = false;
244
+ await this.setCircuitStateAsync(cstate, true, false);
245
+ }
246
+ return res;
247
+ }
248
+ protected async setColorLogicThemeAsync(cstate: CircuitState, theme: any): Promise<InterfaceServerResponse> {
249
+ let ptheme = sys.board.valueMaps.lightThemes.findItem(cstate.lightingTheme) || { val: 0, sequence: 0 };
250
+ // First check to see if we are on. If we are not then we need to emit our status as if we are initializing and busy.
251
+ let arr = [];
252
+ if (ptheme.val === 0) {
253
+ // We don't know our previous theme so we are going to sync the lights to get a starting point.
254
+ arr.push({ isOn: true, timeout: 1000 }); // Turn on for 1 second
255
+ arr.push({ isOn: false, timeout: 12000 }); // Turn off for 12 seconds
256
+ arr.push({ isOn: true, timeout: 1000 });
257
+ ptheme = sys.board.valueMaps.lightThemes.findItem('voodoolounge');
258
+ }
259
+ else if (!cstate.isOn) {
260
+ if (typeof this.timeOff === 'undefined' || new Date().getTime() - this.timeOff.getTime() > 15000) {
261
+ // We have been off for more than 15 seconds so we need to turn it on then wait for 17 seconds while the safety light processes.
262
+ arr.push({ isOn: true, timeout: 17000 }); // Crazy pants
263
+ }
264
+ else arr.push({ isOn: true, timeout: 1000 }); // Start with on
265
+ }
266
+ let count = theme.sequence - ptheme.sequence;
267
+ if (count < 0) count = count + 17;
268
+ for (let i = 0; i < count; i++) {
269
+ arr.push({ isOn: true, timeout: 200 }); // Use 200ms since @Crewski verified 200ms is reliable
270
+ arr.push({ isOn: false, timeout: 200 });
271
+ }
272
+ console.log(arr);
273
+ if (arr.length === 0) return new InterfaceServerResponse(200, 'Success');
274
+ let res = await NixieEquipment.putDeviceService(this.circuit.connectionId, `/state/device/${this.circuit.deviceBinding}`, arr, 60000);
275
+ // Even though we ended with on we need to make sure that the relay stays on now that we are done.
276
+ if (!res.error) {
277
+ cstate.lightingTheme = ptheme.val;
278
+ cstate.isOn = true; // At this point the relay will be off but we want the process
279
+ // to assume that the relay state is not actually changing.
280
+ this._sequencing = false;
281
+ await this.setCircuitStateAsync(cstate, true, false);
282
+ }
283
+ return res;
284
+ }
285
+ // This method only dispatches to the proper light setting algorithm. Previously we assumed that simply switching on/off sequences the proper
286
+ // number of times was all there was but the nutcases who make these things must torture small animals.
287
+ public async setLightThemeAsync(theme: any) {
288
+ try {
289
+ this._sequencing = true;
290
+ let res = new InterfaceServerResponse(200, 'Success');
291
+ let arr = [];
292
+ let cstate = state.circuits.getItemById(this.circuit.id);
293
+ let type = sys.board.valueMaps.circuitFunctions.transform(this.circuit.type);
294
+ // Now set the command state so that users do not get all button mashy.
295
+ cstate.action = sys.board.valueMaps.circuitActions.getValue('settheme');
296
+ cstate.emitEquipmentChange();
297
+ switch (type.name) {
298
+ case 'colorcascade':
299
+ case 'globrite':
300
+ case 'magicstream':
301
+ case 'intellibrite':
302
+ res = await this.setIntelliBriteThemeAsync(cstate, theme);
303
+ break;
304
+ case 'colorlogic':
305
+ res = await this.setColorLogicThemeAsync(cstate, theme);
306
+ break;
307
+ case 'watercolors':
308
+ res = await this.setWaterColorsThemeAsync(cstate, theme);
309
+ break;
310
+ case 'pooltone':
311
+ res = await this.setPoolToneThemeAsync(cstate, theme);
312
+ break;
313
+ }
314
+ cstate.action = 0;
315
+ // Make sure clients know that we are done.
316
+ cstate.emitEquipmentChange();
317
+ return res;
318
+ } catch (err) { logger.error(`Nixie: Error setting lighting theme ${this.id} - ${theme.desc}: ${err.message}`); }
319
+ finally { this._sequencing = false; }
320
+ }
321
+ public async sendOnOffSequenceAsync(count: number | { isOn: boolean, timeout: number }[], timeout?: number): Promise<InterfaceServerResponse> {
322
+ try {
323
+
324
+ this._sequencing = true;
325
+ let arr = [];
326
+ let cstate = state.circuits.getItemById(this.circuit.id);
327
+
328
+ if (typeof count === 'number') {
329
+ if (cstate.isOn) arr.push({ isOn: false, timeout: 1000 });
330
+ let t = typeof timeout === 'undefined' ? 100 : timeout;
331
+ //arr.push({ isOn: false, timeout: t }); // This may not be needed but we always need to start from off.
332
+ //[{ isOn: true, timeout: 1000 }, { isOn: false, timeout: 1000 }]
333
+ for (let i = 0; i < count; i++) {
334
+ if (i < count - 1) {
335
+ arr.push({ isOn: true, timeout: t });
336
+ arr.push({ isOn: false, timeout: t });
337
+ }
338
+ else arr.push({ isOn: true, timeout: 1000 });
339
+ }
340
+ console.log(arr);
341
+ }
342
+ else arr = count;
343
+ let res = await NixieEquipment.putDeviceService(this.circuit.connectionId, `/state/device/${this.circuit.deviceBinding}`, arr, 60000);
344
+ // Even though we ended with on we need to make sure that the relay stays on now that we are done.
345
+ if (!res.error) {
346
+ this._sequencing = false;
347
+ await this.setCircuitStateAsync(cstate, true, false);
348
+ }
349
+ return res;
350
+ } catch (err) { logger.error(`Nixie: Error sending circuit sequence ${this.id}: ${count}`); }
351
+ finally { this._sequencing = false; }
352
+ }
353
+ public async setCircuitStateAsync(cstate: ICircuitState, val: boolean, scheduled: boolean = false): Promise<InterfaceServerResponse> {
354
+ try {
355
+ // Lets do some special processing here for service mode
356
+ if (state.mode !== 0 && val) {
357
+ // Always set the state to off if we are in service mode for bodies. Other circuits
358
+ // may actually be turned on but only if they are not one of the body circuits.
359
+ switch (sys.board.valueMaps.circuitFunctions.getName(this.circuit.type)) {
360
+ case 'pool':
361
+ case 'spa':
362
+ case 'chemrelay':
363
+ val = false;
364
+ break;
365
+ }
366
+ }
367
+ if (val !== cstate.isOn) {
368
+ logger.info(`NCP: Setting Circuit ${cstate.name} to ${val}`);
369
+ if (cstate.isOn && val) {
370
+ // We are already on so lets check the egg timer and shut it off if it has expired.
371
+ let eggOff = this.eggTimerOff;
372
+ if (typeof eggOff !== 'undefined' && eggOff.getTime() <= new Date().getTime()) val = false;
373
+ }
374
+ // Check to see if we should be on by poking the schedules.
375
+ }
376
+ if (utils.isNullOrEmpty(this.circuit.connectionId) || utils.isNullOrEmpty(this.circuit.deviceBinding)) {
377
+ if (val && val !== cstate.isOn) {
378
+ sys.board.circuits.setEndTime(sys.circuits.getInterfaceById(cstate.id), cstate, val);
379
+ }
380
+ else if (!val) {
381
+ if (cstate.manualPriorityActive) delayMgr.cancelManualPriorityDelay(cstate.id);
382
+ cstate.manualPriorityActive = false; // if the delay was previously cancelled, still need to turn this off
383
+ }
384
+ cstate.isOn = val;
385
+ return new InterfaceServerResponse(200, 'Success');
386
+ }
387
+ if (this._sequencing) return new InterfaceServerResponse(200, 'Success');
388
+ let res = await NixieEquipment.putDeviceService(this.circuit.connectionId, `/state/device/${this.circuit.deviceBinding}`, { isOn: val, latch: val ? 10000 : undefined });
389
+ if (res.status.code === 200) {
390
+ // Set this up so we can process our egg timer.
391
+ if (val && val !== cstate.isOn) {
392
+ sys.board.circuits.setEndTime(sys.circuits.getInterfaceById(cstate.id), cstate, val);
393
+ switch (sys.board.valueMaps.circuitFunctions.getName(this.circuit.type)) {
394
+ case 'colorlogic':
395
+ if (!this._sequencing) {
396
+ // We need a little bit of special time for ColorLogic circuits.
397
+ let timeDiff = typeof this.timeOff === 'undefined' ? 30000 : new Date().getTime() - this.timeOff.getTime();
398
+ //logger.info(`Resetting ColorLogic themes ${cstate.isOn}:${val} ${cstate.lightingTheme}... ${timeDiff}`);
399
+ if (timeDiff > 15000) {
400
+ // There is this wacko thing that the lights will come on white for 15 seconds
401
+ // so we need to make sure they don't try to advance the theme setting during this period. We will simply set this to a holding pattern for
402
+ // that timeframe.
403
+ cstate.action = sys.board.valueMaps.circuitActions.getValue('settheme');
404
+ let theme = cstate.lightingTheme;
405
+ cstate.lightingTheme = sys.board.valueMaps.lightThemes.getValue('cloudwhite');
406
+ cstate.startDelay = true;
407
+ setTimeout(() => { cstate.startDelay = false; cstate.action = 0; cstate.lightingTheme = theme; cstate.emitEquipmentChange(); }, 17000);
408
+ }
409
+ else if (timeDiff <= 10000) {
410
+ // If the user turns the light back on within 10 seconds. Surprise! You are forced into the next theme.
411
+ let thm = sys.board.valueMaps.lightThemes.get(cstate.lightingTheme);
412
+ let themes = this.circuit.getLightThemes();
413
+ cstate.lightingTheme = thm.sequence === 17 ? themes.find(elem => elem.sequence === 1).val : themes.find(elem => elem.sequence === thm.sequence + 1).val;
414
+ }
415
+ else if (timeDiff <= 15000) {
416
+ // If the user turns the light back on before 15 seconds expire then we are going to do voodoo. Switch the theme to voodoolounge.
417
+ cstate.lightingTheme = sys.board.valueMaps.lightThemes.getValue('voodoolounge');
418
+ }
419
+ }
420
+ break;
421
+ }
422
+ }
423
+ else if (!val) {
424
+ delayMgr.cancelManualPriorityDelays();
425
+ cstate.manualPriorityActive = false; // if the delay was previously cancelled, still need to turn this off
426
+ }
427
+ if (!val && cstate.isOn) this.timeOff = new Timestamp();
428
+ cstate.isOn = val;
429
+ }
430
+ return res;
431
+ } catch (err) { logger.error(`Nixie: Error setting circuit state ${cstate.id}-${cstate.name} to ${val}`); }
432
+ }
433
+ public async checkCircuitEggTimerExpirationAsync(cstate: ICircuitState) {
434
+ // if circuit end time is past current time, either the schedule is finished
435
+ // (this should already be turned off) or the egg timer has expired
436
+ try {
437
+ if (!cstate.isActive || !cstate.isOn) return;
438
+ if (typeof cstate.endTime !== 'undefined') {
439
+ if (cstate.endTime.toDate() < new Timestamp().toDate()) {
440
+ await sys.board.circuits.setCircuitStateAsync(cstate.id, false);
441
+ cstate.emitEquipmentChange();
442
+ }
443
+ }
444
+ } catch (err) { logger.error(`Error syncing circuit: ${err}`); }
445
+ }
446
+ private async checkHardwareStatusAsync(connectionId: string, deviceBinding: string) {
447
+ try {
448
+ let dev = await NixieEquipment.getDeviceService(connectionId, `/status/device/${deviceBinding}`);
449
+ return dev;
450
+ } catch (err) { logger.error(`Nixie Circuit checkHardwareStatusAsync: ${err.message}`); return { hasFault: true } }
451
+ }
452
+ public async validateSetupAsync(circuit: Circuit, cstate: CircuitState) {
453
+ try {
454
+ if (typeof circuit.connectionId !== 'undefined' && circuit.connectionId !== ''
455
+ && typeof circuit.deviceBinding !== 'undefined' && circuit.deviceBinding !== '') {
456
+ try {
457
+ let stat = await this.checkHardwareStatusAsync(circuit.connectionId, circuit.deviceBinding);
458
+ // If we have a status check the return.
459
+ cstate.commStatus = stat.hasFault ? 1 : 0;
460
+ } catch (err) { cstate.commStatus = 1; }
461
+ }
462
+ else
463
+ cstate.commStatus = 0;
464
+ // The validation will be different if the circuit is on or not. So lets get that information.
465
+ } catch (err) { logger.error(`Nixie Error checking Circuit Hardware ${this.circuit.name}: ${err.message}`); cstate.commStatus = 1; return Promise.reject(err); }
466
+ }
467
+ public async closeAsync() {
468
+ try {
469
+ let cstate = state.circuits.getItemById(this.circuit.id);
470
+ cstate.stopDelay = false;
471
+ cstate.startDelay = false;
472
+ await this.setCircuitStateAsync(cstate, false);
473
+ cstate.emitEquipmentChange();
474
+ }
475
+ catch (err) { logger.error(`Nixie Circuit closeAsync: ${err.message}`); return Promise.reject(err); }
476
+ }
477
+ public logData(filename: string, data: any) { this.controlPanel.logData(filename, data); }
478
+ }