nodejs-poolcontroller 7.7.0 → 8.0.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.
- package/.eslintrc.json +26 -35
- package/Changelog +22 -0
- package/README.md +7 -3
- package/anslq25/MessagesMock.ts +218 -0
- package/anslq25/boards/MockBoardFactory.ts +50 -0
- package/anslq25/boards/MockEasyTouchBoard.ts +696 -0
- package/anslq25/boards/MockSystemBoard.ts +217 -0
- package/anslq25/chemistry/MockChlorinator.ts +75 -0
- package/anslq25/pumps/MockPump.ts +84 -0
- package/app.ts +10 -14
- package/config/Config.ts +13 -9
- package/config/VersionCheck.ts +6 -2
- package/controller/Constants.ts +58 -25
- package/controller/Equipment.ts +225 -41
- package/controller/Errors.ts +2 -1
- package/controller/Lockouts.ts +34 -2
- package/controller/State.ts +491 -48
- package/controller/boards/AquaLinkBoard.ts +6 -3
- package/controller/boards/BoardFactory.ts +5 -1
- package/controller/boards/EasyTouchBoard.ts +1971 -1751
- package/controller/boards/IntelliCenterBoard.ts +1311 -1688
- package/controller/boards/IntelliComBoard.ts +7 -1
- package/controller/boards/IntelliTouchBoard.ts +153 -42
- package/controller/boards/NixieBoard.ts +209 -66
- package/controller/boards/SunTouchBoard.ts +393 -0
- package/controller/boards/SystemBoard.ts +1862 -1543
- package/controller/comms/Comms.ts +539 -138
- package/controller/comms/ScreenLogic.ts +1663 -0
- package/controller/comms/messages/Messages.ts +242 -60
- package/controller/comms/messages/config/ChlorinatorMessage.ts +4 -3
- package/controller/comms/messages/config/CircuitGroupMessage.ts +5 -2
- package/controller/comms/messages/config/CircuitMessage.ts +81 -13
- package/controller/comms/messages/config/ConfigMessage.ts +3 -1
- package/controller/comms/messages/config/CoverMessage.ts +2 -1
- package/controller/comms/messages/config/CustomNameMessage.ts +2 -1
- package/controller/comms/messages/config/EquipmentMessage.ts +5 -1
- package/controller/comms/messages/config/ExternalMessage.ts +33 -3
- package/controller/comms/messages/config/FeatureMessage.ts +2 -1
- package/controller/comms/messages/config/GeneralMessage.ts +2 -1
- package/controller/comms/messages/config/HeaterMessage.ts +3 -1
- package/controller/comms/messages/config/IntellichemMessage.ts +2 -1
- package/controller/comms/messages/config/OptionsMessage.ts +12 -6
- package/controller/comms/messages/config/PumpMessage.ts +9 -12
- package/controller/comms/messages/config/RemoteMessage.ts +80 -13
- package/controller/comms/messages/config/ScheduleMessage.ts +43 -3
- package/controller/comms/messages/config/SecurityMessage.ts +2 -1
- package/controller/comms/messages/config/ValveMessage.ts +43 -26
- package/controller/comms/messages/status/ChlorinatorStateMessage.ts +8 -7
- package/controller/comms/messages/status/EquipmentStateMessage.ts +93 -20
- package/controller/comms/messages/status/HeaterStateMessage.ts +24 -5
- package/controller/comms/messages/status/IntelliChemStateMessage.ts +7 -4
- package/controller/comms/messages/status/IntelliValveStateMessage.ts +2 -1
- package/controller/comms/messages/status/PumpStateMessage.ts +72 -4
- package/controller/comms/messages/status/VersionMessage.ts +2 -1
- package/controller/nixie/Nixie.ts +15 -4
- package/controller/nixie/NixieEquipment.ts +1 -0
- package/controller/nixie/chemistry/ChemController.ts +300 -129
- package/controller/nixie/chemistry/ChemDoser.ts +806 -0
- package/controller/nixie/chemistry/Chlorinator.ts +133 -129
- package/controller/nixie/circuits/Circuit.ts +171 -30
- package/controller/nixie/heaters/Heater.ts +337 -173
- package/controller/nixie/pumps/Pump.ts +264 -236
- package/controller/nixie/schedules/Schedule.ts +9 -3
- package/defaultConfig.json +46 -5
- package/logger/Logger.ts +38 -9
- package/package.json +13 -9
- package/web/Server.ts +235 -122
- package/web/bindings/aqualinkD.json +114 -59
- package/web/bindings/homeassistant.json +437 -0
- package/web/bindings/influxDB.json +15 -0
- package/web/bindings/mqtt.json +28 -9
- package/web/bindings/mqttAlt.json +15 -0
- package/web/interfaces/baseInterface.ts +58 -7
- package/web/interfaces/httpInterface.ts +5 -2
- package/web/interfaces/influxInterface.ts +9 -2
- package/web/interfaces/mqttInterface.ts +234 -74
- package/web/interfaces/ruleInterface.ts +87 -0
- package/web/services/config/Config.ts +140 -33
- package/web/services/config/ConfigSocket.ts +2 -1
- package/web/services/state/State.ts +144 -3
- package/web/services/state/StateSocket.ts +65 -14
- 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,
|
|
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
|
|
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.
|
|
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
|
|
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 (
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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 =
|
|
235
|
+
finally { if (!this.closing) this._pollTimer = setTimeoutSync(() => { self.pollEquipmentAsync(); }, this.pollingInterval); }
|
|
210
236
|
}
|
|
211
|
-
public async
|
|
212
|
-
try {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
|
|
257
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
145
|
-
|
|
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;
|