nodejs-poolcontroller 8.3.0 → 8.4.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 (107) 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/157_issues.md +101 -0
  9. package/AGENTS.md +613 -0
  10. package/CONTRIBUTING.md +74 -74
  11. package/Changelog +292 -284
  12. package/Dockerfile +62 -62
  13. package/Gruntfile.js +40 -40
  14. package/LICENSE +661 -661
  15. package/README.md +329 -309
  16. package/anslq25/MessagesMock.ts +221 -221
  17. package/anslq25/boards/MockBoardFactory.ts +49 -49
  18. package/anslq25/boards/MockEasyTouchBoard.ts +696 -696
  19. package/anslq25/boards/MockSystemBoard.ts +216 -216
  20. package/anslq25/chemistry/MockChlorinator.ts +98 -98
  21. package/anslq25/pumps/MockPump.ts +83 -83
  22. package/app.ts +115 -115
  23. package/config/Config.ts +0 -0
  24. package/config/VersionCheck.ts +0 -0
  25. package/controller/Constants.ts +809 -805
  26. package/controller/Equipment.ts +2737 -2664
  27. package/controller/Errors.ts +181 -181
  28. package/controller/Lockouts.ts +549 -549
  29. package/controller/State.ts +3746 -3701
  30. package/controller/boards/AquaLinkBoard.ts +1175 -1003
  31. package/controller/boards/BoardFactory.ts +53 -53
  32. package/controller/boards/EasyTouchBoard.ts +3246 -3202
  33. package/controller/boards/IntelliCenterBoard.ts +4581 -3899
  34. package/controller/boards/IntelliComBoard.ts +69 -69
  35. package/controller/boards/IntelliTouchBoard.ts +382 -382
  36. package/controller/boards/NixieBoard.ts +1947 -1944
  37. package/controller/boards/SunTouchBoard.ts +401 -400
  38. package/controller/boards/SystemBoard.ts +5303 -5268
  39. package/controller/comms/Comms.ts +1278 -1255
  40. package/controller/comms/ScreenLogic.ts +1665 -1665
  41. package/controller/comms/messages/Messages.ts +1627 -1406
  42. package/controller/comms/messages/config/ChlorinatorMessage.ts +5 -0
  43. package/controller/comms/messages/config/CircuitGroupMessage.ts +0 -0
  44. package/controller/comms/messages/config/CircuitMessage.ts +0 -0
  45. package/controller/comms/messages/config/ConfigMessage.ts +6 -0
  46. package/controller/comms/messages/config/CoverMessage.ts +0 -0
  47. package/controller/comms/messages/config/CustomNameMessage.ts +31 -31
  48. package/controller/comms/messages/config/EquipmentMessage.ts +250 -210
  49. package/controller/comms/messages/config/ExternalMessage.ts +1051 -903
  50. package/controller/comms/messages/config/FeatureMessage.ts +0 -0
  51. package/controller/comms/messages/config/GeneralMessage.ts +65 -0
  52. package/controller/comms/messages/config/HeaterMessage.ts +0 -0
  53. package/controller/comms/messages/config/IntellichemMessage.ts +0 -0
  54. package/controller/comms/messages/config/OptionsMessage.ts +207 -174
  55. package/controller/comms/messages/config/PumpMessage.ts +427 -421
  56. package/controller/comms/messages/config/RemoteMessage.ts +0 -0
  57. package/controller/comms/messages/config/ScheduleMessage.ts +401 -390
  58. package/controller/comms/messages/config/SecurityMessage.ts +37 -13
  59. package/controller/comms/messages/config/ValveMessage.ts +0 -0
  60. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +0 -0
  61. package/controller/comms/messages/status/EquipmentStateMessage.ts +940 -822
  62. package/controller/comms/messages/status/HeaterStateMessage.ts +147 -135
  63. package/controller/comms/messages/status/IntelliChemStateMessage.ts +448 -448
  64. package/controller/comms/messages/status/IntelliValveStateMessage.ts +36 -36
  65. package/controller/comms/messages/status/NeptuneModbusStateMessage.ts +217 -0
  66. package/controller/comms/messages/status/PumpStateMessage.ts +0 -0
  67. package/controller/comms/messages/status/RegalModbusStateMessage.ts +410 -410
  68. package/controller/comms/messages/status/VersionMessage.ts +152 -41
  69. package/controller/nixie/Nixie.ts +173 -173
  70. package/controller/nixie/NixieEquipment.ts +104 -104
  71. package/controller/nixie/bodies/Body.ts +120 -120
  72. package/controller/nixie/bodies/Filter.ts +135 -135
  73. package/controller/nixie/chemistry/ChemController.ts +2756 -2724
  74. package/controller/nixie/chemistry/ChemDoser.ts +806 -806
  75. package/controller/nixie/chemistry/Chlorinator.ts +367 -367
  76. package/controller/nixie/circuits/Circuit.ts +478 -478
  77. package/controller/nixie/heaters/Heater.ts +843 -834
  78. package/controller/nixie/pumps/Pump.ts +1336 -1193
  79. package/controller/nixie/schedules/Schedule.ts +401 -401
  80. package/controller/nixie/valves/Valve.ts +170 -170
  81. package/defaultConfig.json +352 -352
  82. package/docker-compose.yml +32 -31
  83. package/logger/DataLogger.ts +448 -448
  84. package/logger/Logger.ts +459 -436
  85. package/package.json +58 -58
  86. package/sendSocket.js +32 -32
  87. package/tsconfig.json +26 -25
  88. package/types/express-multer.d.ts +32 -32
  89. package/web/Server.ts +1939 -1927
  90. package/web/bindings/aqualinkD.json +559 -559
  91. package/web/bindings/influxDB.json +1066 -1066
  92. package/web/bindings/mqtt.json +721 -721
  93. package/web/bindings/mqttAlt.json +746 -746
  94. package/web/bindings/rulesManager.json +54 -54
  95. package/web/bindings/smartThings-Hubitat.json +31 -31
  96. package/web/bindings/valveRelays.json +20 -20
  97. package/web/bindings/vera.json +25 -25
  98. package/web/interfaces/baseInterface.ts +188 -188
  99. package/web/interfaces/httpInterface.ts +148 -148
  100. package/web/interfaces/influxInterface.ts +283 -283
  101. package/web/interfaces/mqttInterface.ts +695 -695
  102. package/web/interfaces/ruleInterface.ts +101 -87
  103. package/web/services/config/Config.ts +1212 -1053
  104. package/web/services/config/ConfigSocket.ts +0 -0
  105. package/web/services/state/State.ts +21 -0
  106. package/web/services/state/StateSocket.ts +28 -0
  107. 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
+ }