nodejs-poolcontroller 7.7.0 → 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 (82) hide show
  1. package/.eslintrc.json +26 -35
  2. package/Changelog +22 -0
  3. package/README.md +7 -3
  4. package/anslq25/MessagesMock.ts +218 -0
  5. package/anslq25/boards/MockBoardFactory.ts +50 -0
  6. package/anslq25/boards/MockEasyTouchBoard.ts +696 -0
  7. package/anslq25/boards/MockSystemBoard.ts +217 -0
  8. package/anslq25/chemistry/MockChlorinator.ts +75 -0
  9. package/anslq25/pumps/MockPump.ts +84 -0
  10. package/app.ts +10 -14
  11. package/config/Config.ts +13 -9
  12. package/config/VersionCheck.ts +6 -2
  13. package/controller/Constants.ts +58 -25
  14. package/controller/Equipment.ts +224 -41
  15. package/controller/Errors.ts +2 -1
  16. package/controller/Lockouts.ts +34 -2
  17. package/controller/State.ts +491 -48
  18. package/controller/boards/AquaLinkBoard.ts +6 -3
  19. package/controller/boards/BoardFactory.ts +5 -1
  20. package/controller/boards/EasyTouchBoard.ts +1971 -1751
  21. package/controller/boards/IntelliCenterBoard.ts +1311 -1688
  22. package/controller/boards/IntelliComBoard.ts +7 -1
  23. package/controller/boards/IntelliTouchBoard.ts +153 -42
  24. package/controller/boards/NixieBoard.ts +209 -66
  25. package/controller/boards/SunTouchBoard.ts +393 -0
  26. package/controller/boards/SystemBoard.ts +1862 -1543
  27. package/controller/comms/Comms.ts +539 -138
  28. package/controller/comms/ScreenLogic.ts +1663 -0
  29. package/controller/comms/messages/Messages.ts +242 -60
  30. package/controller/comms/messages/config/ChlorinatorMessage.ts +4 -3
  31. package/controller/comms/messages/config/CircuitGroupMessage.ts +5 -2
  32. package/controller/comms/messages/config/CircuitMessage.ts +81 -13
  33. package/controller/comms/messages/config/ConfigMessage.ts +3 -1
  34. package/controller/comms/messages/config/CoverMessage.ts +2 -1
  35. package/controller/comms/messages/config/CustomNameMessage.ts +2 -1
  36. package/controller/comms/messages/config/EquipmentMessage.ts +5 -1
  37. package/controller/comms/messages/config/ExternalMessage.ts +33 -3
  38. package/controller/comms/messages/config/FeatureMessage.ts +2 -1
  39. package/controller/comms/messages/config/GeneralMessage.ts +2 -1
  40. package/controller/comms/messages/config/HeaterMessage.ts +3 -1
  41. package/controller/comms/messages/config/IntellichemMessage.ts +2 -1
  42. package/controller/comms/messages/config/OptionsMessage.ts +12 -6
  43. package/controller/comms/messages/config/PumpMessage.ts +9 -12
  44. package/controller/comms/messages/config/RemoteMessage.ts +80 -13
  45. package/controller/comms/messages/config/ScheduleMessage.ts +43 -3
  46. package/controller/comms/messages/config/SecurityMessage.ts +2 -1
  47. package/controller/comms/messages/config/ValveMessage.ts +43 -26
  48. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +8 -7
  49. package/controller/comms/messages/status/EquipmentStateMessage.ts +93 -20
  50. package/controller/comms/messages/status/HeaterStateMessage.ts +24 -5
  51. package/controller/comms/messages/status/IntelliChemStateMessage.ts +7 -4
  52. package/controller/comms/messages/status/IntelliValveStateMessage.ts +2 -1
  53. package/controller/comms/messages/status/PumpStateMessage.ts +72 -4
  54. package/controller/comms/messages/status/VersionMessage.ts +2 -1
  55. package/controller/nixie/Nixie.ts +15 -4
  56. package/controller/nixie/NixieEquipment.ts +1 -0
  57. package/controller/nixie/chemistry/ChemController.ts +300 -129
  58. package/controller/nixie/chemistry/ChemDoser.ts +806 -0
  59. package/controller/nixie/chemistry/Chlorinator.ts +133 -129
  60. package/controller/nixie/circuits/Circuit.ts +171 -30
  61. package/controller/nixie/heaters/Heater.ts +337 -173
  62. package/controller/nixie/pumps/Pump.ts +264 -236
  63. package/controller/nixie/schedules/Schedule.ts +9 -3
  64. package/defaultConfig.json +45 -5
  65. package/logger/Logger.ts +38 -9
  66. package/package.json +13 -9
  67. package/web/Server.ts +235 -122
  68. package/web/bindings/aqualinkD.json +114 -59
  69. package/web/bindings/homeassistant.json +437 -0
  70. package/web/bindings/influxDB.json +15 -0
  71. package/web/bindings/mqtt.json +28 -9
  72. package/web/bindings/mqttAlt.json +15 -0
  73. package/web/interfaces/baseInterface.ts +58 -7
  74. package/web/interfaces/httpInterface.ts +5 -2
  75. package/web/interfaces/influxInterface.ts +9 -2
  76. package/web/interfaces/mqttInterface.ts +234 -74
  77. package/web/interfaces/ruleInterface.ts +87 -0
  78. package/web/services/config/Config.ts +140 -33
  79. package/web/services/config/ConfigSocket.ts +2 -1
  80. package/web/services/state/State.ts +144 -3
  81. package/web/services/state/StateSocket.ts +65 -14
  82. package/web/services/utilities/Utilities.ts +189 -1
@@ -4,12 +4,13 @@ import { logger } from '../../../logger/Logger';
4
4
 
5
5
  import { NixieEquipment, NixieChildEquipment, NixieEquipmentCollection, INixieControlPanel } from "../NixieEquipment";
6
6
  import { Chlorinator, sys, ChlorinatorCollection } from "../../../controller/Equipment";
7
- import { ChlorinatorState, state, } from "../../State";
8
- import { setTimeout, clearTimeout } from 'timers';
7
+ import { ChlorinatorState, state, } from "../../State";
8
+ import { setTimeout as setTimeoutSync, clearTimeout } from 'timers';
9
9
  import { webApp, InterfaceServerResponse } from "../../../web/Server";
10
10
  import { Outbound, Protocol, Response } from '../../comms/messages/Messages';
11
11
  import { conn } from '../../comms/Comms';
12
12
  import { ncp } from '../Nixie';
13
+ import { setTimeout } from 'timers/promises';
13
14
 
14
15
  export class NixieChlorinatorCollection extends NixieEquipmentCollection<NixieChlorinator> {
15
16
  public async deleteChlorinatorAsync(id: number) {
@@ -45,6 +46,14 @@ export class NixieChlorinatorCollection extends NixieEquipmentCollection<NixieCh
45
46
  }
46
47
  catch (err) { logger.error(`setChlorinatorAsync: ${err.message}`); return Promise.reject(err); }
47
48
  }
49
+ public async setServiceModeAsync() {
50
+ try {
51
+ for (let i = this.length - 1; i >= 0; i--) {
52
+ let c = this[i] as NixieChlorinator;
53
+ await c.setServiceModeAsync();
54
+ }
55
+ } catch (err) { logger.error(`Nixie Chlorinator Error setServiceModeAsync: ${err.message}`) }
56
+ }
48
57
  public async initAsync(chlorinators: ChlorinatorCollection) {
49
58
  try {
50
59
  for (let i = 0; i < chlorinators.length; i++) {
@@ -92,6 +101,13 @@ export class NixieChlorinator extends NixieEquipment {
92
101
  if (typeof this.superChlorStart === 'undefined' || this.superChlorStart === 0 || !this.chlor.superChlor) return 0;
93
102
  return Math.max(Math.floor(((this.chlor.superChlorHours * 3600 * 1000) - (new Date().getTime() - this.superChlorStart)) / 1000), 0);
94
103
  }
104
+ public async setServiceModeAsync() {
105
+ let cstate = state.chlorinators.getItemById(this.chlor.id);
106
+ cstate.targetOutput = 0;
107
+ cstate.superChlor = this.chlor.superChlor = false;
108
+ await this.setSuperChlor(cstate);
109
+ await this.sendOutputMessageAsync(cstate);
110
+ }
95
111
  public async setChlorinatorAsync(data: any) {
96
112
  try {
97
113
  let chlor = this.chlor;
@@ -107,14 +123,18 @@ export class NixieChlorinator extends NixieEquipment {
107
123
  let disabled = typeof data.disabled !== 'undefined' ? utils.makeBool(data.disabled) : chlor.disabled;
108
124
  let isDosing = typeof data.isDosing !== 'undefined' ? utils.makeBool(data.isDosing) : chlor.isDosing;
109
125
  let model = typeof data.model !== 'undefined' ? sys.board.valueMaps.chlorinatorModel.encode(data.model) : chlor.model || 0;
126
+ let saltTarget = typeof data.saltTarget === 'number' ? parseInt(data.saltTarget, 10) : chlor.saltTarget;
127
+ let address = typeof data.address !== 'undefined' ? parseInt(data.address, 10) : chlor.address || 80;
110
128
  let portId = typeof data.portId !== 'undefined' ? parseInt(data.portId, 10) : chlor.portId;
111
- if (portId === 0 && sys.controllerType !== ControllerType.Nixie) return Promise.reject(new InvalidEquipmentDataError(`You may not install a chlorinator on an ${sys.controllerType} system that is assigned to the Primary Port that is under Nixe control`, 'Chlorinator', portId));
129
+ if (chlor.portId !== portId) {
130
+ if (portId === 0 && sys.controllerType !== ControllerType.Nixie) return Promise.reject(new InvalidEquipmentDataError(`You may not install a chlorinator on an ${sys.controllerType} system that is assigned to the Primary Port that is under Nixe control`, 'Chlorinator', portId));
131
+ }
112
132
  if (portId !== chlor.portId && sys.chlorinators.count(elem => elem.id !== this.chlor.id && elem.portId === portId && elem.master !== 2) > 0) return Promise.reject(new InvalidEquipmentDataError(`Another chlorinator is installed on port #${portId}. Only one chlorinator can be installed per port.`, 'Chlorinator', portId));
113
133
  if (isNaN(portId)) return Promise.reject(new InvalidEquipmentDataError(`Invalid port Id`, 'Chlorinator', data.portId));
114
134
  if (typeof body === 'undefined') return Promise.reject(new InvalidEquipmentDataError(`Invalid body assignment`, 'Chlorinator', data.body || chlor.body));
115
135
  if (isNaN(poolSetpoint)) poolSetpoint = 0;
116
136
  if (isNaN(spaSetpoint)) spaSetpoint = 0;
117
-
137
+
118
138
  chlor.ignoreSaltReading = (typeof data.ignoreSaltReading !== 'undefined') ? utils.makeBool(data.ignoreSaltReading) : utils.makeBool(chlor.ignoreSaltReading);
119
139
  // Do a final validation pass so we dont send this off in a mess.
120
140
  let schlor = state.chlorinators.getItemById(chlor.id, true);
@@ -126,14 +146,18 @@ export class NixieChlorinator extends NixieEquipment {
126
146
  schlor.model = chlor.model = model;
127
147
  schlor.body = chlor.body = body.val;
128
148
  chlor.portId = portId;
149
+ chlor.address = address;
129
150
  chlor.disabled = disabled;
151
+ chlor.saltTarget = saltTarget;
130
152
  chlor.isDosing = isDosing;
131
153
  schlor.name = chlor.name = data.name || chlor.name || `Chlorinator ${chlor.id}`;
132
154
  schlor.isActive = chlor.isActive = true;
155
+ chlor.hasChanged = true;
133
156
  if (!chlor.superChlor) {
134
157
  this.superChlorStart = 0;
135
158
  this.superChlorinating = false;
136
159
  }
160
+ this.pollEquipmentAsync();
137
161
  }
138
162
  catch (err) { logger.error(`setChlorinatorAsync: ${err.message}`); return Promise.reject(err); }
139
163
  }
@@ -159,7 +183,8 @@ export class NixieChlorinator extends NixieEquipment {
159
183
  this.closing = false;
160
184
  this._suspendPolling = 0;
161
185
  // During startup it won't be uncommon for the comms to be out. This will be because the body will be off so don't stress it so much.
162
- this.pollEquipment();
186
+ logger.debug(`Begin sending chlorinator messages ${this.chlor.name}`);
187
+ this.pollEquipmentAsync();
163
188
  } catch (err) { logger.error(`Error initializing ${this.chlor.name} : ${err.message}`); }
164
189
  }
165
190
  public isBodyOn() { return sys.board.bodies.isBodyOn(this.chlor.body); }
@@ -180,7 +205,7 @@ export class NixieChlorinator extends NixieEquipment {
180
205
  cstate.superChlorRemaining = 0;
181
206
  }
182
207
  }
183
- public async pollEquipment() {
208
+ public async pollEquipmentAsync() {
184
209
  let self = this;
185
210
  try {
186
211
  if (this._pollTimer) {
@@ -188,14 +213,15 @@ export class NixieChlorinator extends NixieEquipment {
188
213
  this._pollTimer = undefined;
189
214
  }
190
215
  if (!this.suspendPolling) {
191
- logger.debug(`Begin sending chlorinator messages ${this.chlor.name}`);
192
216
  try {
193
217
  this.suspendPolling = true;
194
- if (!this.closing) await this.takeControl();
195
- if (!this.closing) await utils.sleep(300);
196
- if (!this.closing) await this.setOutput();
197
- if (!this.closing) await utils.sleep(300);
198
- if (!this.closing) await this.getModel();
218
+ if (state.mode === 0) {
219
+ if (!this.closing) await this.takeControlAsync();
220
+ if (!this.closing) await setTimeout(300);
221
+ if (!this.closing) await this.setOutputAsync();
222
+ if (!this.closing) await setTimeout(300);
223
+ if (!this.closing) await this.getModelAsync();
224
+ }
199
225
  } catch (err) {
200
226
  // We only display an error here if the body is on. The chlorinator should be powered down when it is not.
201
227
  if (this.isBodyOn()) logger.error(`Chlorinator ${this.chlor.name} comms failure: ${err.message}`);
@@ -206,57 +232,46 @@ export class NixieChlorinator extends NixieEquipment {
206
232
  // Comms failure will be handeled by the message processor.
207
233
  logger.error(`Chlorinator ${this.chlor.name} comms failure: ${err.message}`);
208
234
  }
209
- finally { if(!this.closing) this._pollTimer = setTimeout(() => {self.pollEquipment();}, this.pollingInterval); }
235
+ finally { if (!this.closing) this._pollTimer = setTimeoutSync(() => { self.pollEquipmentAsync(); }, this.pollingInterval); }
210
236
  }
211
- public async takeControl(): Promise<boolean> {
212
- try {
213
- let cstate = state.chlorinators.getItemById(this.chlor.id, true);
214
- // The sequence is as follows.
215
- // 0 = Disabled control panel by taking control of it.
216
- // 17 = Set the current setpoint
217
- // 20 = Request the status
218
- // Disable the control panel by sending an action 0 the chlorinator should respond with an action 1.
219
- //[16, 2, 80, 0][0][98, 16, 3]
220
- let success = await new Promise<boolean>((resolve, reject) => {
221
- if (conn.isPortEnabled(this.chlor.portId || 0)) {
222
- let out = Outbound.create({
223
- portId: this.chlor.portId || 0,
224
- protocol: Protocol.Chlorinator,
225
- //dest: this.chlor.id,
226
- dest: 1,
227
- action: 0,
228
- payload: [0],
229
- retries: 3, // IntelliCenter tries 4 times to get a response.
230
- response: Response.create({ protocol: Protocol.Chlorinator, action: 1 }),
231
- onAbort: () => { this.chlor.superChlor = cstate.superChlor = false; this.setSuperChlor(cstate); },
232
- onComplete: (err) => {
233
- if (err) {
234
- // This flag is cleared in ChlorinatorStateMessage
235
- this.chlor.superChlor = cstate.superChlor = false;
236
- this.setSuperChlor(cstate);
237
- cstate.status = 128;
238
- resolve(false);
239
- }
240
- else {
241
- // If this is successful the action 1 message will have been
242
- // digested by ChlorinatorStateMessage and the lastComm will have been set clearing the
243
- // communication lost flag.
244
- resolve(true);
245
- }
246
- cstate.emitEquipmentChange();
247
- }
248
- });
249
- conn.queueSendMessage(out);
250
- }
251
- else {
252
- cstate.status = 0;
253
- resolve(true);
254
- }
237
+ public async takeControlAsync(): Promise<void> {
238
+ //try {
239
+ let cstate = state.chlorinators.getItemById(this.chlor.id, true);
240
+ // The sequence is as follows.
241
+ // 0 = Disabled control panel by taking control of it.
242
+ // 17 = Set the current setpoint
243
+ // 20 = Request the status
244
+ // Disable the control panel by sending an action 0 the chlorinator should respond with an action 1.
245
+ //[16, 2, 80, 0][0][98, 16, 3]
246
+ //let success = await new Promise<boolean>((resolve, reject) => {
247
+ if (conn.isPortEnabled(this.chlor.portId || 0)) {
248
+ let out = Outbound.create({
249
+ portId: this.chlor.portId || 0,
250
+ protocol: Protocol.Chlorinator,
251
+ dest: this.chlor.address || 80,
252
+ action: 0,
253
+ payload: [0],
254
+ retries: 3, // IntelliCenter tries 4 times to get a response.
255
+ response: Response.create({ protocol: Protocol.Chlorinator, action: 1 }),
256
+ onAbort: () => { this.chlor.superChlor = cstate.superChlor = false; this.setSuperChlor(cstate); },
255
257
  });
256
- return success;
257
- } catch (err) { logger.error(`Communication error with Chlorinator ${this.chlor.name} : ${err.message}`); }
258
+ try {
259
+ // If this is successful the action 1 message will have been
260
+ // digested by ChlorinatorStateMessage and the lastComm will have been set clearing the
261
+ // communication lost flag.
262
+ await out.sendAsync();
263
+ cstate.emitEquipmentChange();
264
+ }
265
+ catch (err) {
266
+ logger.error(`Communication error with Chlorinator ${this.chlor.name} : ${err.message}`);
267
+ // This flag is cleared in ChlorinatorStateMessage
268
+ this.chlor.superChlor = cstate.superChlor = false;
269
+ this.setSuperChlor(cstate);
270
+ cstate.status = 128;
271
+ }
272
+ }
258
273
  }
259
- public async setOutput(): Promise<boolean> {
274
+ public async setOutputAsync(): Promise<void> {
260
275
  try {
261
276
  // A couple of things need to be in place before setting the output.
262
277
  // 1. The chlorinator will have to have responded to the takeControl message.
@@ -285,79 +300,68 @@ export class NixieChlorinator extends NixieEquipment {
285
300
  // Tell the chlorinator that we are to use the current output.
286
301
  //[16, 2, 80, 17][0][115, 16, 3]
287
302
  cstate.targetOutput = cstate.superChlor ? 100 : setpoint;
288
- let success = await new Promise<boolean>((resolve, reject) => {
289
- if (conn.isPortEnabled(this.chlor.portId || 0)) {
290
- let out = Outbound.create({
291
- portId: this.chlor.portId || 0,
292
- protocol: Protocol.Chlorinator,
293
- //dest: this.chlor.id,
294
- dest: 1,
295
- action: 17,
296
- payload: [cstate.targetOutput],
297
- retries: 7, // IntelliCenter tries 8 times to make this happen.
298
- response: Response.create({ protocol: Protocol.Chlorinator, action: 18 }),
299
- onAbort: () => { },
300
- onComplete: (err) => {
301
- if (err) {
302
- cstate.currentOutput = 0;
303
- cstate.status = 128;
304
- resolve(false);
305
- }
306
- else {
307
- cstate.currentOutput = cstate.targetOutput;
308
- this.setSuperChlor(cstate);
309
- resolve(true);
310
- }
311
- }
312
- });
313
- // #338
314
- if (setpoint === 16) { out.appendPayloadByte(0); }
315
- conn.queueSendMessage(out);
316
- }
317
- else {
318
- cstate.currentOutput = cstate.targetOutput;
319
- this.setSuperChlor(cstate);
320
- resolve(true);
321
- }
322
- });
303
+ await this.sendOutputMessageAsync(cstate);
323
304
  cstate.emitEquipmentChange();
324
- return success;
325
- } catch (err) { logger.error(`Communication error with Chlorinator ${this.chlor.name} : ${err.message}`); return Promise.reject(err);}
305
+ } catch (err) { logger.error(`Communication error with Chlorinator ${this.chlor.name} : ${err.message}`); return Promise.reject(err); }
326
306
 
327
307
  }
328
- public async getModel() {
329
- try {
330
- // We only need to ask for this if we can communicate with the chlorinator. IntelliCenter
331
- // asks for this anyway but it really is gratuitous. If the setOutput and takeControl fail
332
- // then this will too.
333
- let cstate = state.chlorinators.getItemById(this.chlor.id, true);
334
- if (cstate.status !== 128) {
335
- // Ask the chlorinator for its model.
336
- //[16, 2, 80, 20][0][118, 16, 3]
337
- let success = await new Promise<boolean>((resolve, reject) => {
338
- if (conn.isPortEnabled(this.chlor.portId || 0)) {
339
- let out = Outbound.create({
340
- portId: this.chlor.portId || 0,
341
- protocol: Protocol.Chlorinator,
342
- //dest: this.chlor.id,
343
- dest: 1,
344
- action: 20,
345
- payload: [0],
346
- retries: 3, // IntelliCenter tries 4 times to get a response.
347
- response: Response.create({ protocol: Protocol.Chlorinator, action: 3 }),
348
- onAbort: () => { },
349
- onComplete: (err) => {
350
- if (err) resolve(false);
351
- else resolve(true);
352
- }
353
- });
354
- conn.queueSendMessage(out);
355
- }
356
- else { resolve(true); }
308
+ public async sendOutputMessageAsync(cstate: ChlorinatorState): Promise<void> {
309
+ if (conn.isPortEnabled(this.chlor.portId || 0)) {
310
+ let out = Outbound.create({
311
+ portId: this.chlor.portId || 0,
312
+ protocol: Protocol.Chlorinator,
313
+ dest: this.chlor.address || 80,
314
+ action: 17,
315
+ payload: [cstate.targetOutput],
316
+ retries: 3, // IntelliCenter tries 8 times to make this happen.
317
+ response: Response.create({ protocol: Protocol.Chlorinator, action: 18 }),
318
+ onAbort: () => { }
319
+ });
320
+ // #338
321
+ if (cstate.targetOutput === 16) { out.appendPayloadByte(0); }
322
+ try {
323
+ await out.sendAsync();
324
+ cstate.currentOutput = cstate.targetOutput;
325
+ this.setSuperChlor(cstate);
326
+ }
327
+ catch (err) {
328
+ cstate.currentOutput = 0;
329
+ cstate.status = 128;
330
+ }
331
+ }
332
+ else {
333
+ cstate.currentOutput = cstate.targetOutput;
334
+ this.setSuperChlor(cstate);
335
+ }
336
+
337
+ }
338
+ public async getModelAsync() {
339
+ // We only need to ask for this if we can communicate with the chlorinator. IntelliCenter
340
+ // asks for this anyway but it really is gratuitous. If the setOutput and takeControl fail
341
+ // then this will too.
342
+ // RSG - Only need to ask if the model or name isn't defined. Added checks below.
343
+ let cstate = state.chlorinators.getItemById(this.chlor.id, true);
344
+ if (cstate.status !== 128 && (typeof cstate.model === 'undefined' || cstate.model === 0 || typeof cstate.name === 'undefined' || cstate.name === '')) {
345
+ // Ask the chlorinator for its model.
346
+ //[16, 2, 80, 20][0][118, 16, 3]
347
+ if (conn.isPortEnabled(this.chlor.portId || 0)) {
348
+ let out = Outbound.create({
349
+ portId: this.chlor.portId || 0,
350
+ protocol: Protocol.Chlorinator,
351
+ dest: this.chlor.address || 80,
352
+ action: 20,
353
+ payload: [0],
354
+ retries: 3, // IntelliCenter tries 4 times to get a response.
355
+ response: Response.create({ protocol: Protocol.Chlorinator, action: 3 }),
356
+ onAbort: () => { }
357
357
  });
358
- return success;
358
+ try {
359
+ await out.sendAsync();
360
+ }
361
+ catch (err) {
362
+ logger.error(`Communication error with Chlorinator ${this.chlor.name} : ${err.message}`);
363
+ }
359
364
  }
360
- else return false;
361
- } catch (err) { logger.error(`Communication error with Chlorinator ${this.chlor.name} : ${err.message}`); return Promise.reject(err);}
365
+ }
362
366
  }
363
367
  }
@@ -9,6 +9,7 @@ import { setTimeout, clearTimeout } from 'timers';
9
9
  import { NixieControlPanel } from '../Nixie';
10
10
  import { webApp, InterfaceServerResponse } from "../../../web/Server";
11
11
  import { delayMgr } from '../../../controller/Lockouts';
12
+ import { time } from 'console';
12
13
 
13
14
  export class NixieCircuitCollection extends NixieEquipmentCollection<NixieCircuit> {
14
15
  public pollingInterval: number = 2000;
@@ -32,6 +33,22 @@ export class NixieCircuitCollection extends NixieEquipmentCollection<NixieCircui
32
33
 
33
34
  } catch (err) { return logger.error(`NCP: sendOnOffSequence: ${err.message}`); }
34
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}`); }
35
52
  public async setCircuitStateAsync(cstate: ICircuitState, val: boolean) {
36
53
  try {
37
54
  let c: NixieCircuit = this.find(elem => elem.id === cstate.id) as NixieCircuit;
@@ -86,7 +103,6 @@ export class NixieCircuitCollection extends NixieEquipmentCollection<NixieCircui
86
103
  this.splice(i, 1);
87
104
  } catch (err) { logger.error(`Error stopping Nixie Circuit ${err}`); }
88
105
  }
89
-
90
106
  } catch (err) { } // Don't bail if we have an errror.
91
107
  }
92
108
 
@@ -116,6 +132,7 @@ export class NixieCircuit extends NixieEquipment {
116
132
  private _sequencing = false;
117
133
  private scheduled = false;
118
134
  private timeOn: Timestamp;
135
+ private timeOff: Timestamp;
119
136
  constructor(ncp: INixieControlPanel, circuit: Circuit) {
120
137
  super(ncp);
121
138
  this.circuit = circuit;
@@ -124,6 +141,10 @@ export class NixieCircuit extends NixieEquipment {
124
141
  cstate.startDelay = false;
125
142
  cstate.stopDelay = false;
126
143
  }
144
+ public async setServiceModeAsync() {
145
+ let cstate = state.circuits.getItemById(this.circuit.id);
146
+ await this.setCircuitStateAsync(cstate, false, false);
147
+ }
127
148
  public get id(): number { return typeof this.circuit !== 'undefined' ? this.circuit.id : -1; }
128
149
  public get eggTimerOff(): Timestamp { return typeof this.timeOn !== 'undefined' && !this.circuit.dontStop ? this.timeOn.clone().addMinutes(this.circuit.eggTimer) : undefined; }
129
150
  public async setCircuitAsync(data: any) {
@@ -132,49 +153,141 @@ export class NixieCircuit extends NixieEquipment {
132
153
  }
133
154
  catch (err) { logger.error(`Nixie setCircuitAsync: ${err.message}`); return Promise.reject(err); }
134
155
  }
135
- public async sendOnOffSequenceAsync(count: number | { isOn: boolean, timeout: number }[], timeout?:number): Promise<InterfaceServerResponse> {
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) {
136
217
  try {
137
218
  this._sequencing = true;
219
+ let res = new InterfaceServerResponse(200, 'Success');
138
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
+
139
252
  if (typeof count === 'number') {
253
+ if (cstate.isOn) arr.push({ isOn: false, timeout: 1000 });
140
254
  let t = typeof timeout === 'undefined' ? 100 : timeout;
141
- arr.push({ isOn: false, timeout: t }); // This may not be needed but we always need to start from off.
255
+ //arr.push({ isOn: false, timeout: t }); // This may not be needed but we always need to start from off.
142
256
  //[{ isOn: true, timeout: 1000 }, { isOn: false, timeout: 1000 }]
143
257
  for (let i = 0; i < count; i++) {
144
- arr.push({ isOn: true, timeout: t });
145
- if (i < count - 1) arr.push({ isOn: false, timeout: t });
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 });
146
263
  }
264
+ console.log(arr);
147
265
  }
148
266
  else arr = count;
149
- // The documentation for IntelliBrite is incorrect. The sequence below will give us Party mode.
150
- // Party mode:2
151
- // Start: Off
152
- // On
153
- // Off
154
- // On
155
- // According to the docs this is the sequence they lay out.
156
- // Party mode:2
157
- // Start: On
158
- // Off
159
- // On
160
- // Off
161
- // On
162
-
163
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
+ }
164
273
  return res;
165
274
  } catch (err) { logger.error(`Nixie: Error sending circuit sequence ${this.id}: ${count}`); }
166
275
  finally { this._sequencing = false; }
167
276
  }
168
- public async setThemeAsync(cstate: ICircuitState, theme: number): Promise<InterfaceServerResponse> {
169
- try {
170
-
171
-
172
-
173
- return new InterfaceServerResponse(200, 'Sucess');
174
- } catch (err) { logger.error(`Nixie: Error setting light theme ${cstate.id}-${cstate.name} to ${theme}`); }
175
- }
176
277
  public async setCircuitStateAsync(cstate: ICircuitState, val: boolean, scheduled: boolean = false): Promise<InterfaceServerResponse> {
177
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
+ }
178
291
  if (val !== cstate.isOn) {
179
292
  logger.info(`NCP: Setting Circuit ${cstate.name} to ${val}`);
180
293
  if (cstate.isOn && val) {
@@ -199,15 +312,43 @@ export class NixieCircuit extends NixieEquipment {
199
312
  let res = await NixieEquipment.putDeviceService(this.circuit.connectionId, `/state/device/${this.circuit.deviceBinding}`, { isOn: val, latch: val ? 10000 : undefined });
200
313
  if (res.status.code === 200) {
201
314
  // Set this up so we can process our egg timer.
202
- //if (!cstate.isOn && val) { cstate.startTime = this.timeOn = new Timestamp(); }
203
- //else if (!val) cstate.startTime = this.timeOn = undefined;
204
315
  if (val && val !== cstate.isOn){
205
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
+ }
206
346
  }
207
347
  else if (!val){
208
348
  delayMgr.cancelManualPriorityDelays();
209
349
  cstate.manualPriorityActive = false; // if the delay was previously cancelled, still need to turn this off
210
- }
350
+ }
351
+ if (!val && cstate.isOn) this.timeOff = new Timestamp();
211
352
  cstate.isOn = val;
212
353
  }
213
354
  return res;