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