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
package/controller/State.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* nodejs-poolController. An application to control pool equipment.
|
|
2
|
-
Copyright (C) 2016, 2017, 2018, 2019, 2020
|
|
2
|
+
Copyright (C) 2016, 2017, 2018, 2019, 2020, 2021, 2022.
|
|
3
|
+
Russell Goldin, tagyoureit. russ.goldin@gmail.com
|
|
3
4
|
|
|
4
5
|
This program is free software: you can redistribute it and/or modify
|
|
5
6
|
it under the terms of the GNU Affero General Public License as
|
|
@@ -22,10 +23,9 @@ import * as util from 'util';
|
|
|
22
23
|
import { logger } from '../logger/Logger';
|
|
23
24
|
import { webApp } from '../web/Server';
|
|
24
25
|
import { ControllerType, Timestamp, utils, Heliotrope } from './Constants';
|
|
25
|
-
import { sys, Chemical, ChemController } from './Equipment';
|
|
26
|
+
import { sys, Chemical, ChemController, ChemicalTank, ChemicalPump } from './Equipment';
|
|
26
27
|
import { versionCheck } from '../config/VersionCheck';
|
|
27
|
-
import {
|
|
28
|
-
import { DataLogger, DataLoggerEntry, IDataLoggerEntry } from '../logger/DataLogger';
|
|
28
|
+
import { DataLogger, DataLoggerEntry } from '../logger/DataLogger';
|
|
29
29
|
import { delayMgr } from './Lockouts';
|
|
30
30
|
|
|
31
31
|
export class State implements IState {
|
|
@@ -99,7 +99,7 @@ export class State implements IState {
|
|
|
99
99
|
lines = buff.toString().split('\n');
|
|
100
100
|
}
|
|
101
101
|
return lines;
|
|
102
|
-
} catch (err) { logger.error(err); }
|
|
102
|
+
} catch (err) { logger.error(`Error reading log file ${logFile}: ${err.message}`); }
|
|
103
103
|
}
|
|
104
104
|
public async logData(logFile: string, data: any) {
|
|
105
105
|
try {
|
|
@@ -116,7 +116,7 @@ export class State implements IState {
|
|
|
116
116
|
else
|
|
117
117
|
lines.unshift(data.toString());
|
|
118
118
|
fs.writeFileSync(logPath, lines.join('\n'));
|
|
119
|
-
} catch (err) { logger.error(err); }
|
|
119
|
+
} catch (err) { logger.error(`Error reading or writing logData ${logFile}: ${err.message}`); }
|
|
120
120
|
}
|
|
121
121
|
public getState(section?: string): any {
|
|
122
122
|
// todo: getState('time') returns an array of chars. Needs no be fixed.
|
|
@@ -140,6 +140,7 @@ export class State implements IState {
|
|
|
140
140
|
_state.filters = this.filters.getExtended();
|
|
141
141
|
_state.schedules = this.schedules.getExtended();
|
|
142
142
|
_state.chemControllers = this.chemControllers.getExtended();
|
|
143
|
+
_state.chemDosers = this.chemDosers.getExtended();
|
|
143
144
|
_state.delays = delayMgr.serialize();
|
|
144
145
|
return _state;
|
|
145
146
|
}
|
|
@@ -160,7 +161,7 @@ export class State implements IState {
|
|
|
160
161
|
try {
|
|
161
162
|
if (this._timerDirty) clearTimeout(this._timerDirty);
|
|
162
163
|
this.persist();
|
|
163
|
-
|
|
164
|
+
/* if (sys.controllerType === ControllerType.Virtual) {
|
|
164
165
|
for (let i = 0; i < state.temps.bodies.length; i++) {
|
|
165
166
|
state.temps.bodies.getItemByIndex(i).isOn = false;
|
|
166
167
|
}
|
|
@@ -170,7 +171,7 @@ export class State implements IState {
|
|
|
170
171
|
for (let i = 0; i < state.features.length; i++) {
|
|
171
172
|
state.features.getItemByIndex(i).isOn = false;
|
|
172
173
|
}
|
|
173
|
-
}
|
|
174
|
+
} */
|
|
174
175
|
logger.info('State process shut down');
|
|
175
176
|
} catch (err) { logger.error(`Error shutting down state process: ${err.message}`); }
|
|
176
177
|
}
|
|
@@ -265,6 +266,9 @@ export class State implements IState {
|
|
|
265
266
|
for (let i = 0; i < state.chemControllers.length; i++) {
|
|
266
267
|
state.chemControllers.getItemByIndex(i).hasChanged = true;
|
|
267
268
|
}
|
|
269
|
+
for (let i = 0; i < state.chemDosers.length; i++) {
|
|
270
|
+
state.chemDosers.getItemByIndex(i).hasChanged = true;
|
|
271
|
+
}
|
|
268
272
|
state.emitEquipmentChanges();
|
|
269
273
|
}
|
|
270
274
|
public emitEquipmentChanges() {
|
|
@@ -341,6 +345,7 @@ export class State implements IState {
|
|
|
341
345
|
public get isInitialized(): boolean { return typeof (this.data.status) !== 'undefined' && this.data.status.val !== 0; }
|
|
342
346
|
public init() {
|
|
343
347
|
console.log(`Init state for Pool Controller`);
|
|
348
|
+
|
|
344
349
|
var sdata = this.loadFile(this.statePath, {});
|
|
345
350
|
sdata = extend(true, { mode: { val: -1 }, temps: { units: { val: 0, name: 'F', desc: 'Fahrenheit' } } }, sdata);
|
|
346
351
|
if (typeof sdata.temps !== 'undefined' && typeof sdata.temps.bodies !== 'undefined') {
|
|
@@ -358,6 +363,7 @@ export class State implements IState {
|
|
|
358
363
|
EqStateCollection.removeNullIds(sdata.lightGroups);
|
|
359
364
|
EqStateCollection.removeNullIds(sdata.remotes);
|
|
360
365
|
EqStateCollection.removeNullIds(sdata.chemControllers);
|
|
366
|
+
EqStateCollection.removeNullIds(sdata.chemDosers);
|
|
361
367
|
EqStateCollection.removeNullIds(sdata.filters);
|
|
362
368
|
var self = this;
|
|
363
369
|
let pnlTime = typeof sdata.time !== 'undefined' ? new Date(sdata.time) : new Date();
|
|
@@ -365,6 +371,9 @@ export class State implements IState {
|
|
|
365
371
|
this._dt = new Timestamp(pnlTime);
|
|
366
372
|
this._dt.milliseconds = 0;
|
|
367
373
|
this.data = sdata;
|
|
374
|
+
this.equipment = new EquipmentState(this.data, 'equipment');
|
|
375
|
+
this.equipment.messages.clear();
|
|
376
|
+
|
|
368
377
|
//this.onchange(state, function () { self.dirty = true; });
|
|
369
378
|
this._dt.emitter.on('change', function () {
|
|
370
379
|
self.data.time = self._dt.format();
|
|
@@ -378,7 +387,6 @@ export class State implements IState {
|
|
|
378
387
|
versionCheck.checkGitRemote();
|
|
379
388
|
});
|
|
380
389
|
this.status = 0; // Initializing
|
|
381
|
-
this.equipment = new EquipmentState(this.data, 'equipment');
|
|
382
390
|
this.equipment.controllerType = this._controllerType;
|
|
383
391
|
this.temps = new TemperatureState(this.data, 'temps');
|
|
384
392
|
this.pumps = new PumpStateCollection(this.data, 'pumps');
|
|
@@ -392,6 +400,7 @@ export class State implements IState {
|
|
|
392
400
|
this.lightGroups = new LightGroupStateCollection(this.data, 'lightGroups');
|
|
393
401
|
this.virtualCircuits = new VirtualCircuitStateCollection(this.data, 'virtualCircuits');
|
|
394
402
|
this.chemControllers = new ChemControllerStateCollection(this.data, 'chemControllers');
|
|
403
|
+
this.chemDosers = new ChemDoserStateCollection(this.data, 'chemDosers');
|
|
395
404
|
this.covers = new CoverStateCollection(this.data, 'covers');
|
|
396
405
|
this.filters = new FilterStateCollection(this.data, 'filters');
|
|
397
406
|
this.comms = new CommsState();
|
|
@@ -433,6 +442,7 @@ export class State implements IState {
|
|
|
433
442
|
public covers: CoverStateCollection;
|
|
434
443
|
public filters: FilterStateCollection;
|
|
435
444
|
public chemControllers: ChemControllerStateCollection;
|
|
445
|
+
public chemDosers: ChemDoserStateCollection;
|
|
436
446
|
public comms: CommsState;
|
|
437
447
|
public appVersion: AppVersionState;
|
|
438
448
|
|
|
@@ -641,6 +651,10 @@ class EqStateCollection<T> {
|
|
|
641
651
|
}
|
|
642
652
|
return rem;
|
|
643
653
|
}
|
|
654
|
+
public removeItemByIndex(ndx: number) {
|
|
655
|
+
return this.data.splice(ndx, 1);
|
|
656
|
+
}
|
|
657
|
+
|
|
644
658
|
public createItem(data: any): T { return new EqState(data) as unknown as T; }
|
|
645
659
|
public clear() { this.data.length = 0; }
|
|
646
660
|
public get length(): number { return typeof (this.data) !== 'undefined' ? this.data.length : 0; }
|
|
@@ -732,6 +746,8 @@ export class EquipmentState extends EqState {
|
|
|
732
746
|
public set name(val: string) { this.setDataVal('name', val); }
|
|
733
747
|
public get model(): string { return this.data.model; }
|
|
734
748
|
public set model(val: string) { this.setDataVal('model', val); }
|
|
749
|
+
public get single(): boolean { return this.data.single; }
|
|
750
|
+
public set single(val: boolean) { this.setDataVal('single', val); }
|
|
735
751
|
public get shared(): boolean { return this.data.shared; }
|
|
736
752
|
public set shared(val: boolean) { this.setDataVal('shared', val); }
|
|
737
753
|
public get dual(): boolean { return this.data.dual; }
|
|
@@ -807,12 +823,14 @@ export class EquipmentMessages extends EqStateCollection<EquipmentMessage> {
|
|
|
807
823
|
rem = this.data.splice(i, 1);
|
|
808
824
|
}
|
|
809
825
|
}
|
|
826
|
+
if (typeof rem !== 'undefined') webApp.emitToClients('sysmessages', this.get(true));
|
|
810
827
|
return typeof rem !== 'undefined' ? new EquipmentMessage(rem, undefined, undefined) : undefined;
|
|
811
828
|
}
|
|
812
829
|
// For lack of a better term category includes the equipment identifier if supplied.
|
|
813
830
|
public removeItemByCategory(category: string) {
|
|
814
831
|
let rem: EquipmentMessage[] = [];
|
|
815
832
|
let cmr = EquipmentMessage.parseMessageCode(category);
|
|
833
|
+
let hasChanges = false;
|
|
816
834
|
for (let i = this.data.length - 1; i >= 0; i--) {
|
|
817
835
|
if (typeof (this.data[i].code) !== 'undefined') {
|
|
818
836
|
let cm = EquipmentMessage.parseMessageCode(this.data.code);
|
|
@@ -821,17 +839,20 @@ export class EquipmentMessages extends EqStateCollection<EquipmentMessage> {
|
|
|
821
839
|
if (typeof cmr.messageId === 'undefined' || cm.messageId === cmr.messageId) {
|
|
822
840
|
let data = this.data.splice(i, 1);
|
|
823
841
|
rem.push(new EquipmentMessage(data, undefined, undefined));
|
|
842
|
+
hasChanges = true;
|
|
824
843
|
}
|
|
825
844
|
}
|
|
826
845
|
}
|
|
827
846
|
}
|
|
828
847
|
}
|
|
848
|
+
if (hasChanges) webApp.emitToClients('sysmessages', this.get(true));
|
|
829
849
|
return rem;
|
|
830
850
|
}
|
|
831
851
|
public setMessageByCode(code: string, severity: string | number, message: string): EquipmentMessage {
|
|
832
852
|
let msg = this.getItemByCode(code, true);
|
|
833
853
|
msg.severity = sys.board.valueMaps.eqMessageSeverities.encode(severity, 0);
|
|
834
854
|
msg.message = message;
|
|
855
|
+
webApp.emitToClients('sysmessages', this.get(true));
|
|
835
856
|
return msg;
|
|
836
857
|
}
|
|
837
858
|
}
|
|
@@ -912,15 +933,18 @@ export class PumpState extends EqState {
|
|
|
912
933
|
public get name(): string { return this.data.name; }
|
|
913
934
|
public set name(val: string) { this.setDataVal('name', val); }
|
|
914
935
|
public get rpm(): number { return this.data.rpm; }
|
|
915
|
-
public set rpm(val: number) { this.setDataVal('rpm', val
|
|
936
|
+
public set rpm(val: number) { this.setDataVal('rpm', val); }
|
|
937
|
+
//public set rpm(val: number) { this.setDataVal('rpm', val, this.exceedsThreshold(this.data.rpm, val)); }
|
|
916
938
|
public get relay(): number { return this.data.relay; }
|
|
917
939
|
public set relay(val: number) { this.setDataVal('relay', val); }
|
|
918
940
|
public get program(): number { return this.data.program; }
|
|
919
941
|
public set program(val: number) { this.setDataVal('program', val); }
|
|
920
942
|
public get watts(): number { return this.data.watts; }
|
|
921
|
-
public set watts(val: number) { this.setDataVal('watts', val
|
|
943
|
+
public set watts(val: number) { this.setDataVal('watts', val); }
|
|
944
|
+
//public set watts(val: number) { this.setDataVal('watts', val, this.exceedsThreshold(this.data.watts, val)); }
|
|
922
945
|
public get flow(): number { return this.data.flow; }
|
|
923
|
-
public set flow(val: number) { this.setDataVal('flow', val
|
|
946
|
+
public set flow(val: number) { this.setDataVal('flow', val); }
|
|
947
|
+
//public set flow(val: number) { this.setDataVal('flow', val, this.exceedsThreshold(this.data.flow, val)); }
|
|
924
948
|
public get mode(): number { return this.data.mode; }
|
|
925
949
|
public set mode(val: number) { this.setDataVal('mode', val); }
|
|
926
950
|
public get driveState(): number { return this.data.driveState; }
|
|
@@ -942,7 +966,7 @@ export class PumpState extends EqState {
|
|
|
942
966
|
this.hasChanged = true;
|
|
943
967
|
}
|
|
944
968
|
}
|
|
945
|
-
|
|
969
|
+
/* public get virtualControllerStatus(): number {
|
|
946
970
|
return typeof (this.data.virtualControllerStatus) !== 'undefined' ? this.data.virtualControllerStatus.val : -1;
|
|
947
971
|
}
|
|
948
972
|
public set virtualControllerStatus(val: number) {
|
|
@@ -950,7 +974,7 @@ export class PumpState extends EqState {
|
|
|
950
974
|
this.data.virtualControllerStatus = sys.board.valueMaps.virtualControllerStatus.transform(val);
|
|
951
975
|
this.hasChanged = true;
|
|
952
976
|
}
|
|
953
|
-
}
|
|
977
|
+
} */
|
|
954
978
|
public get targetSpeed(): number { return this.data.targetSpeed; } // used for virtual controller
|
|
955
979
|
public set targetSpeed(val: number) { this.setDataVal('targetSpeed', val); }
|
|
956
980
|
public get type() { return typeof (this.data.type) !== 'undefined' ? this.data.type.val : -1; }
|
|
@@ -1214,7 +1238,10 @@ export class CircuitGroupState extends EqState implements ICircuitGroupState, IC
|
|
|
1214
1238
|
public getExtended() {
|
|
1215
1239
|
let sgrp = this.get(true); // Always operate on a copy.
|
|
1216
1240
|
if (typeof sgrp.showInFeatures === 'undefined') sgrp.showInFeatures = true;
|
|
1241
|
+
|
|
1217
1242
|
let cgrp = sys.circuitGroups.getItemById(this.id);
|
|
1243
|
+
sgrp.showInFeatures = this.showInFeatures = cgrp.showInFeatures;
|
|
1244
|
+
sgrp.isActive = this.isActive = cgrp.isActive;
|
|
1218
1245
|
sgrp.circuits = [];
|
|
1219
1246
|
for (let i = 0; i < cgrp.circuits.length; i++) {
|
|
1220
1247
|
let cgc = cgrp.circuits.getItemByIndex(i).get(true);
|
|
@@ -1368,6 +1395,7 @@ export class BodyTempState extends EqState {
|
|
|
1368
1395
|
if (typeof this.data.startDelay === 'undefined') this.data.startDelay = false;
|
|
1369
1396
|
if (typeof this.data.stopDelay === 'undefined') this.data.stopDelay = false;
|
|
1370
1397
|
if (typeof this.data.showInDashboard === 'undefined') this.data.showInDashboard = true;
|
|
1398
|
+
if (typeof this.data.heatMode === 'undefined') this.data.heatMode = sys.board.valueMaps.heatModes.transform(0);
|
|
1371
1399
|
}
|
|
1372
1400
|
public get id(): number { return this.data.id; }
|
|
1373
1401
|
public set id(val: number) { this.setDataVal('id', val); }
|
|
@@ -1580,7 +1608,6 @@ export class FeatureStateCollection extends EqStateCollection<FeatureState> {
|
|
|
1580
1608
|
}
|
|
1581
1609
|
}
|
|
1582
1610
|
}
|
|
1583
|
-
|
|
1584
1611
|
export class FeatureState extends EqState implements ICircuitState {
|
|
1585
1612
|
public dataName: string = 'feature';
|
|
1586
1613
|
public initData() {
|
|
@@ -1840,7 +1867,7 @@ export class CoverState extends EqState {
|
|
|
1840
1867
|
public set isClosed(val: boolean) { this.setDataVal('isClosed', val); }
|
|
1841
1868
|
}
|
|
1842
1869
|
export class ChlorinatorStateCollection extends EqStateCollection<ChlorinatorState> {
|
|
1843
|
-
public superChlor: { id:number, lastDispatch: number, reference: number }[] = [];
|
|
1870
|
+
public superChlor: { id: number, lastDispatch: number, reference: number }[] = [];
|
|
1844
1871
|
public getSuperChlor(id: number): { id: number, lastDispatch: number, reference: number } {
|
|
1845
1872
|
let sc = this.superChlor.find(elem => id === elem.id);
|
|
1846
1873
|
if (typeof sc === 'undefined') {
|
|
@@ -1935,32 +1962,15 @@ export class ChlorinatorState extends EqState {
|
|
|
1935
1962
|
public set spaSetpoint(val: number) { this.setDataVal('spaSetpoint', val); }
|
|
1936
1963
|
public get superChlorHours(): number { return this.data.superChlorHours; }
|
|
1937
1964
|
public set superChlorHours(val: number) { this.setDataVal('superChlorHours', val); }
|
|
1965
|
+
public get saltTarget(): number { return this.data.saltTarget; }
|
|
1966
|
+
public set saltTarget(val: number) { this.setDataVal('saltTarget', val); }
|
|
1938
1967
|
public get saltRequired(): number { return this.data.saltRequired; }
|
|
1939
1968
|
public get saltLevel(): number { return this.data.saltLevel; }
|
|
1940
1969
|
public set saltLevel(val: number) {
|
|
1941
|
-
this.
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
let capacity = 0;
|
|
1945
|
-
for (let i = 0; i < sys.bodies.length; i++) {
|
|
1946
|
-
let body = sys.bodies.getItemById(i + 1);
|
|
1947
|
-
if (this.body === 32)
|
|
1948
|
-
capacity = Math.max(body.capacity, capacity);
|
|
1949
|
-
else if (this.body === 0 && body.id === 1)
|
|
1950
|
-
capacity = Math.max(body.capacity, capacity);
|
|
1951
|
-
else if (this.body === 1 && body.id === 2)
|
|
1952
|
-
capacity = Math.max(body.capacity, capacity);
|
|
1953
|
-
}
|
|
1954
|
-
if (capacity > 0 && this.saltLevel < 3100) {
|
|
1955
|
-
// Salt requirements calculation.
|
|
1956
|
-
// Target - SaltLevel = NeededSalt = 3400 - 2900 = 500ppm
|
|
1957
|
-
// So to raise 120ppm you need to add 1lb per 1000 gal.
|
|
1958
|
-
// (NeededSalt/120ppm) * (MaxBody/1000) = (500/120) * (33000/1000) = 137.5lbs of salt required to hit target.
|
|
1959
|
-
let dec = Math.pow(10, 2);
|
|
1960
|
-
this.data.saltRequired = Math.round((((3400 - this.saltLevel) / 120) * (capacity / 1000)) * dec) / dec;
|
|
1970
|
+
if (this.saltLevel !== val) {
|
|
1971
|
+
this.setDataVal('saltLevel', val);
|
|
1972
|
+
this.calcSaltRequired();
|
|
1961
1973
|
}
|
|
1962
|
-
else
|
|
1963
|
-
this.data.saltRequired = 0;
|
|
1964
1974
|
}
|
|
1965
1975
|
public get superChlor(): boolean { return this.data.superChlor; }
|
|
1966
1976
|
public set superChlor(val: boolean) {
|
|
@@ -2023,9 +2033,38 @@ export class ChlorinatorState extends EqState {
|
|
|
2023
2033
|
this.setDataVal('superChlor', remaining > 0);
|
|
2024
2034
|
chlor.superChlor = remaining > 0;
|
|
2025
2035
|
}
|
|
2036
|
+
public calcSaltRequired(saltTarget?: number) : number {
|
|
2037
|
+
if (typeof saltTarget === 'undefined') saltTarget = sys.chlorinators.getItemById(this.id, false).saltTarget || 0;
|
|
2038
|
+
let saltRequired = 0;
|
|
2039
|
+
//this.data.saltLevel = val;
|
|
2040
|
+
// Calculate the salt required.
|
|
2041
|
+
let capacity = 0;
|
|
2042
|
+
for (let i = 0; i < sys.bodies.length; i++) {
|
|
2043
|
+
let body = sys.bodies.getItemById(i + 1);
|
|
2044
|
+
if (this.body === 32)
|
|
2045
|
+
capacity = Math.max(body.capacity, capacity);
|
|
2046
|
+
else if (this.body === 0 && body.id === 1)
|
|
2047
|
+
capacity = Math.max(body.capacity, capacity);
|
|
2048
|
+
else if (this.body === 1 && body.id === 2)
|
|
2049
|
+
capacity = Math.max(body.capacity, capacity);
|
|
2050
|
+
}
|
|
2051
|
+
if (capacity > 0 && this.saltLevel < saltTarget) {
|
|
2052
|
+
// Salt requirements calculation.
|
|
2053
|
+
// Target - SaltLevel = NeededSalt = 3400 - 2900 = 500ppm
|
|
2054
|
+
// So to raise 120ppm you need to add 1lb per 1000 gal.
|
|
2055
|
+
// (NeededSalt/120ppm) * (MaxBody/1000) = (500/120) * (33000/1000) = 137.5lbs of salt required to hit target.
|
|
2056
|
+
let dec = Math.pow(10, 2);
|
|
2057
|
+
saltRequired = Math.round((((saltTarget - this.saltLevel) / 120) * (capacity / 1000)) * dec) / dec;
|
|
2058
|
+
if (this.saltRequired < 0) saltRequired = 0;
|
|
2059
|
+
}
|
|
2060
|
+
this.setDataVal('saltRequired', saltRequired);
|
|
2061
|
+
return saltRequired;
|
|
2062
|
+
}
|
|
2063
|
+
public getEmitData() { return this.getExtended(); }
|
|
2026
2064
|
public getExtended(): any {
|
|
2027
2065
|
let schlor = this.get(true);
|
|
2028
2066
|
let chlor = sys.chlorinators.getItemById(this.id, false);
|
|
2067
|
+
schlor.saltTarget = chlor.saltTarget;
|
|
2029
2068
|
schlor.lockSetpoints = chlor.disabled || chlor.isDosing;
|
|
2030
2069
|
return schlor;
|
|
2031
2070
|
}
|
|
@@ -2052,8 +2091,278 @@ export class ChemControllerStateCollection extends EqStateCollection<ChemControl
|
|
|
2052
2091
|
}
|
|
2053
2092
|
}
|
|
2054
2093
|
}
|
|
2094
|
+
export class ChemDoserStateCollection extends EqStateCollection<ChemDoserState> {
|
|
2095
|
+
public createItem(data: any): ChemDoserState { return new ChemDoserState(data); }
|
|
2096
|
+
public cleanupState() {
|
|
2097
|
+
for (let i = this.data.length - 1; i >= 0; i--) {
|
|
2098
|
+
if (isNaN(this.data[i].id)) this.data.splice(i, 1);
|
|
2099
|
+
else {
|
|
2100
|
+
if (typeof sys.chemControllers.find(elem => elem.id === this.data[i].id) === 'undefined') this.removeItemById(this.data[i].id);
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
2103
|
+
// Make sure we have at least the items that exist in the config.
|
|
2104
|
+
let cfg = sys.chemControllers.toArray();
|
|
2105
|
+
for (let i = 0; i < cfg.length; i++) {
|
|
2106
|
+
let c = cfg[i];
|
|
2107
|
+
let s = this.getItemById(cfg[i].id, true);
|
|
2108
|
+
s.body = c.body;
|
|
2109
|
+
s.name = c.name;
|
|
2110
|
+
s.type = c.type;
|
|
2111
|
+
s.isActive = c.isActive;
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
export interface IChemControllerState {
|
|
2116
|
+
flowDetected: boolean;
|
|
2117
|
+
isBodyOn: boolean;
|
|
2118
|
+
emitEquipmentChange: () => void;
|
|
2119
|
+
}
|
|
2120
|
+
export interface IChemicalState {
|
|
2121
|
+
enabled: boolean;
|
|
2122
|
+
currentDose: ChemicalDoseState;
|
|
2123
|
+
manualDosing: boolean;
|
|
2124
|
+
manualMixing: boolean;
|
|
2125
|
+
dosingTimeRemaining: number;
|
|
2126
|
+
dosingVolumeRemaining: number;
|
|
2127
|
+
volumeDosed: number;
|
|
2128
|
+
timeDosed: number;
|
|
2129
|
+
endDose: (dtEnd?: Date, status?: string, volumeDosed?: number, timeDosed?: number) => ChemicalDoseState;
|
|
2130
|
+
appendDose: (volumeDosed: number, timeDosed: number) => ChemicalDoseState;
|
|
2131
|
+
tank: ChemicalTankState;
|
|
2132
|
+
pump: ChemicalPumpState;
|
|
2133
|
+
doseHistory: ChemicalDoseState[];
|
|
2134
|
+
freezeProtect: boolean;
|
|
2135
|
+
mixTimeRemaining: number;
|
|
2136
|
+
delayTimeRemaining: number;
|
|
2137
|
+
flowDelay: boolean;
|
|
2138
|
+
dailyVolumeDosed: number;
|
|
2139
|
+
chemType: string;
|
|
2140
|
+
dosingStatus: number;
|
|
2141
|
+
chemController: IChemControllerState;
|
|
2142
|
+
chlor?: ChemicalChlorState;
|
|
2143
|
+
|
|
2144
|
+
}
|
|
2145
|
+
export class ChemDoserState extends EqState implements IChemicalState, IChemControllerState {
|
|
2146
|
+
public initData() {
|
|
2147
|
+
if (typeof this.data.flowDetected === 'undefined') this.data.flowDetected = false;
|
|
2148
|
+
if (typeof this.data.flowSensor === 'undefined') this.data.flowSensor = {};
|
|
2149
|
+
if (typeof this.data.alarms === 'undefined') { let a = this.alarms; }
|
|
2150
|
+
if (typeof this.data.warnings === 'undefined') { let w = this.warnings; }
|
|
2151
|
+
if (typeof this.data.tank === 'undefined') this.data.tank = { capacity: 0, level: 0, units: 0 };
|
|
2152
|
+
if (typeof this.data.pump === 'undefined') this.data.pump = { isDosing: false };
|
|
2153
|
+
if (typeof this.data.dosingTimeRemaining === 'undefined') this.data.dosingTimeRemaining = 0;
|
|
2154
|
+
if (typeof this.data.delayTimeRemaining === 'undefined') this.data.delayTimeRemaining = 0;
|
|
2155
|
+
if (typeof this.data.dosingVolumeRemaining === 'undefined') this.data.dosingVolumeRemaining = 0;
|
|
2156
|
+
if (typeof this.data.doseVolume === 'undefined') this.data.doseVolume = 0;
|
|
2157
|
+
if (typeof this.data.doseTime === 'undefined') this.data.doseTime = 0;
|
|
2158
|
+
if (typeof this.data.lockout === 'undefined') this.data.lockout = false;
|
|
2159
|
+
if (typeof this.data.level === 'undefined') this.data.level = 0;
|
|
2160
|
+
if (typeof this.data.mixTimeRemaining === 'undefined') this.data.mixTimeRemaining = 0;
|
|
2161
|
+
if (typeof this.data.dailyLimitReached === 'undefined') this.data.dailyLimitReached = false;
|
|
2162
|
+
if (typeof this.data.manualDosing === 'undefined') this.data.manualDosing = false;
|
|
2163
|
+
if (typeof this.data.manualMixing === 'undefined') this.data.manualMixing = false;
|
|
2164
|
+
if (typeof this.data.flowDelay === 'undefined') this.data.flowDelay = false;
|
|
2165
|
+
if (typeof this.data.dosingStatus === 'undefined') this.dosingStatus = 2;
|
|
2166
|
+
if (typeof this.data.enabled === 'undefined') this.data.enabled = true;
|
|
2167
|
+
if (typeof this.data.freezeProtect === 'undefined') this.data.freezeProtect = false;
|
|
2168
|
+
if (typeof this.data.setpoint === 'undefined') this.data.setpoint = 100;
|
|
2169
|
+
if (typeof this.data.suspendDosing === 'undefined') this.data.suspendDosing = false;
|
|
2170
|
+
}
|
|
2171
|
+
public dataName: string = 'chemDoser';
|
|
2172
|
+
public get id(): number { return this.data.id; }
|
|
2173
|
+
public set id(val: number) { this.setDataVal('id', val); }
|
|
2174
|
+
public get isActive(): boolean { return this.data.isActive; }
|
|
2175
|
+
public set isActive(val: boolean) { this.setDataVal('isActive', val); }
|
|
2176
|
+
public get name(): string { return this.data.name; }
|
|
2177
|
+
public set name(val: string) { this.setDataVal('name', val); }
|
|
2178
|
+
public get setpoint(): number { return this.data.setpoint; }
|
|
2179
|
+
public set setpoint(val: number) { this.setDataVal('setpoint', val); }
|
|
2180
|
+
|
|
2181
|
+
public get lastComm(): number { return this.data.lastComm || 0; }
|
|
2182
|
+
public set lastComm(val: number) { this.setDataVal('lastComm', val, false); }
|
|
2183
|
+
public get isBodyOn(): boolean { return this.data.isBodyOn; }
|
|
2184
|
+
public set isBodyOn(val: boolean) { this.data.isBodyOn = val; }
|
|
2185
|
+
public get flowDetected(): boolean { return this.data.flowDetected; }
|
|
2186
|
+
public set flowDetected(val: boolean) { this.data.flowDetected = val; }
|
|
2187
|
+
public get status(): number { return typeof (this.data.status) !== 'undefined' ? this.data.status.val : -1; }
|
|
2188
|
+
public set status(val: number) {
|
|
2189
|
+
if (this.status !== val) {
|
|
2190
|
+
this.data.status = sys.board.valueMaps.chemDoserStatus.transform(val);
|
|
2191
|
+
this.hasChanged = true;
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
public get body(): number { return typeof (this.data.body) !== 'undefined' ? this.data.body.val : -1; }
|
|
2195
|
+
public set body(val: number) {
|
|
2196
|
+
if (this.body !== val) {
|
|
2197
|
+
this.data.body = sys.board.valueMaps.bodies.transform(val);
|
|
2198
|
+
this.hasChanged = true;
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2201
|
+
public get type(): number { return typeof (this.data.type) !== 'undefined' ? this.data.type.val : 0; }
|
|
2202
|
+
public set type(val: number) {
|
|
2203
|
+
if (this.type !== val) {
|
|
2204
|
+
this.data.type = sys.board.valueMaps.chemDoserTypes.transform(val);
|
|
2205
|
+
this.data.chemType = this.data.type.desc;
|
|
2206
|
+
this.hasChanged = true;
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
public get enabled(): boolean { return this.data.enabled; }
|
|
2210
|
+
public set enabled(val: boolean) { this.data.enabled = val; }
|
|
2211
|
+
public get freezeProtect(): boolean { return this.data.freezeProtect; }
|
|
2212
|
+
public set freezeProtect(val: boolean) { this.data.freezeProtect = val; }
|
|
2213
|
+
public get suspendDosing(): boolean { return this.data.suspendDosing; }
|
|
2214
|
+
public set suspendDosing(val: boolean) { this.data.suspendDosing = val; }
|
|
2215
|
+
public get chemType(): string { return this.data.chemType; }
|
|
2216
|
+
public get delayTimeRemaining(): number { return this.data.delayTimeRemaining; }
|
|
2217
|
+
public set delayTimeRemaining(val: number) { this.setDataVal('delayTimeRemaining', val); }
|
|
2218
|
+
public get doseTime(): number { return this.data.doseTime; }
|
|
2219
|
+
public set doseTime(val: number) { this.setDataVal('doseTime', val); }
|
|
2220
|
+
public get doseVolume(): number { return this.data.doseVolume; }
|
|
2221
|
+
public set doseVolume(val: number) { this.setDataVal('doseVolume', val); }
|
|
2222
|
+
public get dosingTimeRemaining(): number { return this.data.dosingTimeRemaining; }
|
|
2223
|
+
public set dosingTimeRemaining(val: number) { this.setDataVal('dosingTimeRemaining', val); }
|
|
2224
|
+
public get dosingVolumeRemaining(): number { return this.data.dosingVolumeRemaining; }
|
|
2225
|
+
public set dosingVolumeRemaining(val: number) { this.setDataVal('dosingVolumeRemaining', val); }
|
|
2226
|
+
public get volumeDosed(): number { return this.data.volumeDosed; }
|
|
2227
|
+
public set volumeDosed(val: number) { this.setDataVal('volumeDosed', val); }
|
|
2228
|
+
public get timeDosed(): number { return this.data.timeDosed; }
|
|
2229
|
+
public set timeDosed(val: number) { this.setDataVal('timeDosed', val); }
|
|
2230
|
+
public get dailyVolumeDosed(): number { return this.data.dailyVolumeDosed; }
|
|
2231
|
+
public set dailyVolumeDosed(val: number) { this.setDataVal('dailyVolumeDosed', val); }
|
|
2232
|
+
public get mixTimeRemaining(): number { return this.data.mixTimeRemaining; }
|
|
2233
|
+
public set mixTimeRemaining(val: number) { this.setDataVal('mixTimeRemaining', val); }
|
|
2234
|
+
public get dosingStatus(): number { return typeof (this.data.dosingStatus) !== 'undefined' ? this.data.dosingStatus.val : undefined; }
|
|
2235
|
+
public set dosingStatus(val: number) {
|
|
2236
|
+
if (this.dosingStatus !== val) {
|
|
2237
|
+
logger.debug(`${this.chemType} dosing status changed from ${sys.board.valueMaps.chemControllerDosingStatus.getName(this.dosingStatus)} (${this.dosingStatus}) to ${sys.board.valueMaps.chemControllerDosingStatus.getName(val)}(${val})`);
|
|
2238
|
+
this.data.dosingStatus = sys.board.valueMaps.chemControllerDosingStatus.transform(val);
|
|
2239
|
+
this.hasChanged = true;
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2242
|
+
public get lockout(): boolean { return utils.makeBool(this.data.lockout); }
|
|
2243
|
+
public set lockout(val: boolean) { this.setDataVal('lockout', val); }
|
|
2244
|
+
public get flowDelay(): boolean { return utils.makeBool(this.data.flowDelay); }
|
|
2245
|
+
public set flowDelay(val: boolean) { this.data.flowDelay = val; }
|
|
2246
|
+
public get manualDosing(): boolean { return utils.makeBool(this.data.manualDosing); }
|
|
2247
|
+
public set manualDosing(val: boolean) { this.setDataVal('manualDosing', val); }
|
|
2248
|
+
public get manualMixing(): boolean { return utils.makeBool(this.data.manualMixing); }
|
|
2249
|
+
public set manualMixing(val: boolean) { this.setDataVal('manualMixing', val); }
|
|
2250
|
+
public get dailyLimitReached(): boolean { return utils.makeBool(this.data.dailyLimitReached); }
|
|
2251
|
+
public set dailyLimitReached(val: boolean) { this.data.dailyLimitReached = val; }
|
|
2252
|
+
public get tank(): ChemicalTankState { return new ChemicalTankState(this.data, 'tank', this); }
|
|
2253
|
+
public get pump(): ChemicalPumpState { return new ChemicalPumpState(this.data, 'pump', this); }
|
|
2254
|
+
public get flowSensor(): ChemicalFlowSensorState { return new ChemicalFlowSensorState(this.data, 'flowSensor', this); }
|
|
2255
|
+
public get warnings(): ChemDoserStateWarnings { return new ChemDoserStateWarnings(this.data, 'warnings', this); }
|
|
2256
|
+
public get alarms(): ChemDoserStateAlarms { return new ChemDoserStateAlarms(this.data, 'alarms', this); }
|
|
2257
|
+
public calcDoseHistory(): number {
|
|
2258
|
+
// The dose history records will already exist when the state is loaded. There are enough records to cover 24 hours in this
|
|
2259
|
+
// instance. We need to prune off any records that are > 24 hours when we calculate.
|
|
2260
|
+
let dailyVolumeDosed = 0;
|
|
2261
|
+
let dt = new Date();
|
|
2262
|
+
let dtMax = dt.setTime(dt.getTime() - (24 * 60 * 60 * 1000));
|
|
2263
|
+
for (let i = this.doseHistory.length - 1; i >= 0; i--) {
|
|
2264
|
+
let dh = this.doseHistory[i];
|
|
2265
|
+
if (typeof dh.end !== 'undefined'
|
|
2266
|
+
&& typeof dh.end.getTime == 'function'
|
|
2267
|
+
&& dh.end.getTime() > dtMax
|
|
2268
|
+
&& dh.volumeDosed > 0) dailyVolumeDosed += dh.volumeDosed;
|
|
2269
|
+
else {
|
|
2270
|
+
logger.info(`Removing dose history ${dh.chem} ${dh.end}`);
|
|
2271
|
+
this.doseHistory.splice(i, 1);
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
return dailyVolumeDosed + (typeof this.currentDose !== 'undefined' ? this.currentDose.volumeDosed : 0);
|
|
2275
|
+
}
|
|
2276
|
+
public startDose(dtStart: Date = new Date(), method: string = 'auto', volume: number = 0, volumeDosed: number = 0, time: number = 0, timeDosed: number = 0): ChemicalDoseState {
|
|
2277
|
+
this.currentDose = new ChemicalDoseState();
|
|
2278
|
+
this.currentDose.type = this.chemType;
|
|
2279
|
+
this.currentDose.id = this.id;
|
|
2280
|
+
this.currentDose.start = dtStart;
|
|
2281
|
+
this.currentDose.method = method;
|
|
2282
|
+
this.currentDose.volumeDosed = volumeDosed;
|
|
2283
|
+
this.doseVolume = this.currentDose.volume = volume;
|
|
2284
|
+
this.currentDose.chem = this.chemType;
|
|
2285
|
+
this.currentDose.time = time;
|
|
2286
|
+
this.currentDose._timeDosed = timeDosed;
|
|
2287
|
+
this.volumeDosed = this.currentDose.volumeDosed;
|
|
2288
|
+
this.timeDosed = Math.round(timeDosed / 1000);
|
|
2289
|
+
this.dosingTimeRemaining = this.currentDose.timeRemaining;
|
|
2290
|
+
this.dosingVolumeRemaining = this.currentDose.volumeRemaining;
|
|
2291
|
+
this.doseTime = Math.round(this.currentDose.time / 1000);
|
|
2292
|
+
this.currentDose._isManual = method === 'manual';
|
|
2293
|
+
this.currentDose.status = 'current';
|
|
2294
|
+
//webApp.emitToClients(`chemicalDose`, this.currentDose);
|
|
2295
|
+
return this.currentDose;
|
|
2296
|
+
}
|
|
2297
|
+
public endDose(dtEnd: Date = new Date(), status: string = 'completed', volumeDosed: number = 0, timeDosed: number = 0): ChemicalDoseState {
|
|
2298
|
+
let dose = this.currentDose;
|
|
2299
|
+
if (typeof dose !== 'undefined') {
|
|
2300
|
+
dose.type = 'Peristaltic';
|
|
2301
|
+
dose._timeDosed += timeDosed;
|
|
2302
|
+
dose.volumeDosed += volumeDosed;
|
|
2303
|
+
dose.end = dtEnd;
|
|
2304
|
+
dose.timeDosed = dose._timeDosed / 1000;
|
|
2305
|
+
dose.status = status;
|
|
2306
|
+
this.volumeDosed = dose.volumeDosed;
|
|
2307
|
+
this.timeDosed = Math.round(dose._timeDosed / 1000);
|
|
2308
|
+
this.dosingTimeRemaining = 0;
|
|
2309
|
+
this.dosingVolumeRemaining = 0;
|
|
2310
|
+
if (dose.volumeDosed > 0 || dose.timeDosed > 0) {
|
|
2311
|
+
this.dailyVolumeDosed = this.calcDoseHistory();
|
|
2312
|
+
this.doseHistory.unshift(dose);
|
|
2313
|
+
DataLogger.writeEnd(`chemDosage_${this.chemType}.log`, dose);
|
|
2314
|
+
setImmediate(() => { webApp.emitToClients(`chemicalDose`, dose); });
|
|
2315
|
+
}
|
|
2316
|
+
this.currentDose = undefined;
|
|
2317
|
+
}
|
|
2318
|
+
return dose;
|
|
2319
|
+
}
|
|
2320
|
+
// Appends dose information to the current dose. The time here is in ms and our target will be in sec.
|
|
2321
|
+
public appendDose(volumeDosed: number, timeDosed: number): ChemicalDoseState {
|
|
2322
|
+
let dose = typeof this.currentDose !== 'undefined' ? this.currentDose : this.currentDose = this.startDose();
|
|
2323
|
+
dose._timeDosed += timeDosed;
|
|
2324
|
+
dose.volumeDosed += volumeDosed;
|
|
2325
|
+
dose.timeDosed = dose._timeDosed / 1000;
|
|
2326
|
+
dose.type = 'Peristaltic';
|
|
2327
|
+
this.volumeDosed = dose.volumeDosed;
|
|
2328
|
+
this.timeDosed = Math.round(dose._timeDosed / 1000);
|
|
2329
|
+
this.dosingTimeRemaining = dose.timeRemaining;
|
|
2330
|
+
this.dosingVolumeRemaining = dose.volumeRemaining;
|
|
2331
|
+
|
|
2332
|
+
if (dose.volumeDosed > 0 || timeDosed > 0) setImmediate(() => { webApp.emitToClients(`chemicalDose`, dose); });
|
|
2333
|
+
return dose;
|
|
2334
|
+
}
|
|
2335
|
+
public get currentDose(): ChemicalDoseState {
|
|
2336
|
+
if (typeof this.data.currentDose === 'undefined') return this.data.currentDose;
|
|
2337
|
+
if (typeof this.data.currentDose.save !== 'function') this.data.currentDose = new ChemicalDoseState(this.data.currentDose);
|
|
2338
|
+
return this.data.currentDose;
|
|
2339
|
+
}
|
|
2340
|
+
public set currentDose(val: ChemicalDoseState) {
|
|
2341
|
+
this.setDataVal('currentDose', val);
|
|
2342
|
+
}
|
|
2343
|
+
public get doseHistory(): ChemicalDoseState[] {
|
|
2344
|
+
if (typeof this.data.doseHistory === 'undefined') this.data.doseHistory = [];
|
|
2345
|
+
if (this.data.doseHistory.length === 0) return this.data.doseHistory;
|
|
2346
|
+
if (typeof this.data.doseHistory[0].save !== 'function') {
|
|
2347
|
+
let arr: ChemicalDoseState[] = [];
|
|
2348
|
+
for (let i = 0; i < this.data.doseHistory.length; i++) {
|
|
2349
|
+
arr.push(new ChemicalDoseState(this.data.doseHistory[i]));
|
|
2350
|
+
}
|
|
2351
|
+
this.data.doseHistory = arr;
|
|
2352
|
+
}
|
|
2353
|
+
return this.data.doseHistory;
|
|
2354
|
+
}
|
|
2355
|
+
public set doseHistory(val: ChemicalDoseState[]) { this.setDataVal('doseHistory', val); }
|
|
2356
|
+
public get chemController() { return this; }
|
|
2357
|
+
public getExtended(): any {
|
|
2358
|
+
let chem = sys.chemDosers.getItemById(this.id);
|
|
2359
|
+
let obj = this.get(true);
|
|
2360
|
+
obj = extend(true, obj, chem.getExtended());
|
|
2361
|
+
return obj;
|
|
2362
|
+
}
|
|
2363
|
+
}
|
|
2055
2364
|
|
|
2056
|
-
export class ChemControllerState extends EqState {
|
|
2365
|
+
export class ChemControllerState extends EqState implements IChemControllerState {
|
|
2057
2366
|
public initData() {
|
|
2058
2367
|
if (typeof this.data.saturationIndex === 'undefined') this.data.saturationIndex = 0;
|
|
2059
2368
|
if (typeof this.data.flowDetected === 'undefined') this.data.flowDetected = false;
|
|
@@ -2285,10 +2594,22 @@ export class ChemControllerState extends EqState {
|
|
|
2285
2594
|
this.hasChanged = true;
|
|
2286
2595
|
}
|
|
2287
2596
|
}
|
|
2597
|
+
public getEmitData(): any {
|
|
2598
|
+
let chem = sys.chemControllers.getItemById(this.id);
|
|
2599
|
+
let obj = this.get(true);
|
|
2600
|
+
obj.address = chem.address;
|
|
2601
|
+
obj.borates = chem.borates;
|
|
2602
|
+
obj.saturationIndex = this.saturationIndex || 0;
|
|
2603
|
+
obj.alkalinity = chem.alkalinity;
|
|
2604
|
+
obj.calciumHardness = chem.calciumHardness;
|
|
2605
|
+
obj.cyanuricAcid = chem.cyanuricAcid;
|
|
2606
|
+
return obj;
|
|
2607
|
+
}
|
|
2288
2608
|
public getExtended(): any {
|
|
2289
2609
|
let chem = sys.chemControllers.getItemById(this.id);
|
|
2290
2610
|
let obj = this.get(true);
|
|
2291
2611
|
obj.address = chem.address;
|
|
2612
|
+
obj.borates = chem.borates;
|
|
2292
2613
|
obj.saturationIndex = this.saturationIndex || 0;
|
|
2293
2614
|
obj.alkalinity = chem.alkalinity;
|
|
2294
2615
|
obj.calciumHardness = chem.calciumHardness;
|
|
@@ -2299,7 +2620,7 @@ export class ChemControllerState extends EqState {
|
|
|
2299
2620
|
return obj;
|
|
2300
2621
|
}
|
|
2301
2622
|
}
|
|
2302
|
-
export class ChemicalState extends ChildEqState {
|
|
2623
|
+
export class ChemicalState extends ChildEqState implements IChemicalState {
|
|
2303
2624
|
public initData() {
|
|
2304
2625
|
if (typeof this.data.probe === 'undefined') this.data.probe = {};
|
|
2305
2626
|
if (typeof this.data.tank === 'undefined') this.data.tank = { capacity: 0, level: 0, units: 0 };
|
|
@@ -2342,6 +2663,7 @@ export class ChemicalState extends ChildEqState {
|
|
|
2342
2663
|
}
|
|
2343
2664
|
public startDose(dtStart: Date = new Date(), method: string = 'auto', volume: number = 0, volumeDosed: number = 0, time: number = 0, timeDosed: number = 0): ChemicalDoseState {
|
|
2344
2665
|
this.currentDose = new ChemicalDoseState();
|
|
2666
|
+
this.currentDose.type = this.type;
|
|
2345
2667
|
this.currentDose.id = this.chemController.id;
|
|
2346
2668
|
this.currentDose.start = dtStart;
|
|
2347
2669
|
this.currentDose.method = method;
|
|
@@ -2375,9 +2697,9 @@ export class ChemicalState extends ChildEqState {
|
|
|
2375
2697
|
this.timeDosed = Math.round(dose._timeDosed / 1000);
|
|
2376
2698
|
this.dosingTimeRemaining = 0;
|
|
2377
2699
|
this.dosingVolumeRemaining = 0;
|
|
2378
|
-
if (dose.volumeDosed > 0) {
|
|
2379
|
-
this.doseHistory.unshift(dose);
|
|
2700
|
+
if (dose.volumeDosed > 0 || dose.timeDosed > 0) {
|
|
2380
2701
|
this.dailyVolumeDosed = this.calcDoseHistory();
|
|
2702
|
+
this.doseHistory.unshift(dose);
|
|
2381
2703
|
DataLogger.writeEnd(`chemDosage_${this.chemType}.log`, dose);
|
|
2382
2704
|
setImmediate(() => { webApp.emitToClients(`chemicalDose`, dose); });
|
|
2383
2705
|
}
|
|
@@ -2390,11 +2712,12 @@ export class ChemicalState extends ChildEqState {
|
|
|
2390
2712
|
let dose = typeof this.currentDose !== 'undefined' ? this.currentDose : this.currentDose = this.startDose();
|
|
2391
2713
|
dose._timeDosed += timeDosed;
|
|
2392
2714
|
dose.volumeDosed += volumeDosed;
|
|
2715
|
+
dose.timeDosed = dose._timeDosed/1000;
|
|
2393
2716
|
this.volumeDosed = dose.volumeDosed;
|
|
2394
2717
|
this.timeDosed = Math.round(dose._timeDosed / 1000);
|
|
2395
2718
|
this.dosingTimeRemaining = dose.timeRemaining;
|
|
2396
2719
|
this.dosingVolumeRemaining = dose.volumeRemaining;
|
|
2397
|
-
if (dose.volumeDosed > 0) setImmediate(() => { webApp.emitToClients(`chemicalDose`, dose); });
|
|
2720
|
+
if (dose.volumeDosed > 0 || timeDosed > 0) setImmediate(() => { webApp.emitToClients(`chemicalDose`, dose); });
|
|
2398
2721
|
return dose;
|
|
2399
2722
|
}
|
|
2400
2723
|
public get currentDose(): ChemicalDoseState {
|
|
@@ -2422,6 +2745,7 @@ export class ChemicalState extends ChildEqState {
|
|
|
2422
2745
|
let dH = this.demandHistory;
|
|
2423
2746
|
dH.appendDemand(time, val);
|
|
2424
2747
|
}
|
|
2748
|
+
public get type() { return this.data.type; };
|
|
2425
2749
|
public get demandHistory() { return new ChemicalDemandState(this.data, 'demandHistory', this) };
|
|
2426
2750
|
public get enabled(): boolean { return this.data.enabled; }
|
|
2427
2751
|
public set enabled(val: boolean) { this.data.enabled = val; }
|
|
@@ -2486,6 +2810,7 @@ export class ChemicalPhState extends ChemicalState {
|
|
|
2486
2810
|
public initData() {
|
|
2487
2811
|
super.initData();
|
|
2488
2812
|
if (typeof this.data.chemType === 'undefined') this.data.chemType = 'none';
|
|
2813
|
+
if (typeof this.data.type === 'undefined') this.data.type = 'ph';
|
|
2489
2814
|
}
|
|
2490
2815
|
public getConfig() {
|
|
2491
2816
|
let schem = this.chemController;
|
|
@@ -2512,13 +2837,27 @@ export class ChemicalPhState extends ChemicalState {
|
|
|
2512
2837
|
// Calculate how many mL are required to raise to our pH level.
|
|
2513
2838
|
// 1. Get the total gallons of water that the chem controller is in
|
|
2514
2839
|
// control of.
|
|
2840
|
+
// 2. RSG 5-22-22 - If the spa is on, calc demand only based on the spa volume. Otherwise, long periods of spa usage
|
|
2841
|
+
// will result in an overdose if pH is high.
|
|
2515
2842
|
let totalGallons = 0;
|
|
2843
|
+
// The bodyIsOn code was throwing an exception whenver no bodies were on.
|
|
2844
|
+
if (chem.body === 32 && sys.equipment.shared) {
|
|
2845
|
+
// We are shared and when body 2 (spa) is on body 1 (pool) is off.
|
|
2846
|
+
if (state.temps.bodies.getItemById(2).isOn === true) totalGallons = sys.bodies.getItemById(2).capacity;
|
|
2847
|
+
else totalGallons = sys.bodies.getItemById(1).capacity + sys.bodies.getItemById(2).capacity;
|
|
2848
|
+
}
|
|
2849
|
+
else {
|
|
2850
|
+
// These are all single body implementations so we simply match to the body.
|
|
2851
|
+
totalGallons = sys.bodies.getItemById(chem.body + 1).capacity;
|
|
2852
|
+
}
|
|
2516
2853
|
|
|
2517
|
-
if (chem.body === 0 || chem.body === 32 || sys.equipment.shared) totalGallons += sys.bodies.getItemById(1).capacity;
|
|
2518
|
-
|
|
2519
|
-
if (chem.body ===
|
|
2520
|
-
if (chem.body ===
|
|
2521
|
-
|
|
2854
|
+
//if (chem.body === 0 || chem.body === 32 || sys.equipment.shared) totalGallons += sys.bodies.getItemById(1).capacity;
|
|
2855
|
+
//let bodyIsOn = state.temps.bodies.getBodyIsOn();
|
|
2856
|
+
//if (bodyIsOn.circuit === 1 && sys.circuits.getInterfaceById(bodyIsOn.circuit).type === sys.board.valueMaps.circuitFunctions.getValue('spa') && (chem.body === 1 || chem.body === 32 || sys.equipment.shared)) totalGallons = sys.bodies.getItemById(2).capacity;
|
|
2857
|
+
//else if (chem.body === 1 || chem.body === 32 || sys.equipment.shared) totalGallons += sys.bodies.getItemById(2).capacity;
|
|
2858
|
+
//if (chem.body === 2) totalGallons += sys.bodies.getItemById(3).capacity;
|
|
2859
|
+
//if (chem.body === 3) totalGallons += sys.bodies.getItemById(4).capacity;
|
|
2860
|
+
logger.verbose(`Chem begin calculating ${this.chemType} demand: ${this.level} setpoint: ${this.setpoint} total gallons: ${totalGallons}`);
|
|
2522
2861
|
let chg = this.setpoint - this.level;
|
|
2523
2862
|
let delta = chg * totalGallons;
|
|
2524
2863
|
let temp = (this.level + this.setpoint) / 2;
|
|
@@ -2546,6 +2885,8 @@ export class ChemicalORPState extends ChemicalState {
|
|
|
2546
2885
|
if (typeof this.data.probe === 'undefined') this.data.probe = {};
|
|
2547
2886
|
if (typeof this.data.chemType === 'undefined') this.data.chemType = 'none';
|
|
2548
2887
|
if (typeof this.data.useChlorinator === 'undefined') this.data.useChlorinator = false;
|
|
2888
|
+
if (typeof this.data.type === 'undefined') this.data.type = 'orp';
|
|
2889
|
+
|
|
2549
2890
|
super.initData();
|
|
2550
2891
|
// Load up the 24 hours doseHistory.
|
|
2551
2892
|
//this.doseHistory = DataLogger.readFromEnd(`chemDosage_${this.chemType}.log`, ChemicalDoseState, (lineNumber: number, entry: ChemicalDoseState): boolean => {
|
|
@@ -2716,6 +3057,7 @@ export class ChemicalDoseState extends DataLoggerEntry {
|
|
|
2716
3057
|
public volumeDosed: number;
|
|
2717
3058
|
public time: number;
|
|
2718
3059
|
public timeDosed: number;
|
|
3060
|
+
public type: string;
|
|
2719
3061
|
|
|
2720
3062
|
public static createInstance(entry?: string): ChemicalDoseState { return new ChemicalDoseState(entry); }
|
|
2721
3063
|
public save() { DataLogger.writeEnd(`chemDosage_${this.chem}.log`, this); }
|
|
@@ -2831,6 +3173,107 @@ export class ChemControllerStateWarnings extends ChildEqState {
|
|
|
2831
3173
|
}
|
|
2832
3174
|
}
|
|
2833
3175
|
|
|
3176
|
+
}
|
|
3177
|
+
export class ChemDoserStateWarnings extends ChildEqState {
|
|
3178
|
+
///ctor(data): ChemControllerStateWarnings { return new ChemControllerStateWarnings(data, name || 'warnings'); }
|
|
3179
|
+
public dataName = 'chemDoserWarnings';
|
|
3180
|
+
public initData() {
|
|
3181
|
+
if (typeof this.data.lockout === 'undefined') this.lockout = 0;
|
|
3182
|
+
if (typeof this.data.pHDailyLimitReached === 'undefined') this.dailyLimitReached = 0;
|
|
3183
|
+
if (typeof this.data.invalidSetup === 'undefined') this.invalidSetup = 0;
|
|
3184
|
+
if (typeof this.data.chlorinatorCommError === 'undefined') this.chlorinatorCommError = 0;
|
|
3185
|
+
}
|
|
3186
|
+
public get lockout(): number { return this.data.lockout; }
|
|
3187
|
+
public set lockout(val: number) {
|
|
3188
|
+
if (this.lockout !== val) {
|
|
3189
|
+
this.data.lockout = sys.board.valueMaps.chemDoserLimits.transform(val);
|
|
3190
|
+
this.hasChanged = true;
|
|
3191
|
+
}
|
|
3192
|
+
}
|
|
3193
|
+
public get dailyLimitReached(): number { return this.data.dailyLimitReached; }
|
|
3194
|
+
public set dailyLimitReached(val: number) {
|
|
3195
|
+
if (this.dailyLimitReached !== val) {
|
|
3196
|
+
this.data.dailyLimitReached = sys.board.valueMaps.chemDoserLimits.transform(val);
|
|
3197
|
+
this.hasChanged = true;
|
|
3198
|
+
}
|
|
3199
|
+
}
|
|
3200
|
+
public get invalidSetup(): number { return this.data.invalidSetup; }
|
|
3201
|
+
public set invalidSetup(val: number) {
|
|
3202
|
+
if (this.invalidSetup !== val) {
|
|
3203
|
+
this.data.invalidSetup = sys.board.valueMaps.chemDoserLimits.transform(val);
|
|
3204
|
+
this.hasChanged = true;
|
|
3205
|
+
}
|
|
3206
|
+
}
|
|
3207
|
+
public get chlorinatorCommError(): number { return this.data.chlorinatorCommError; }
|
|
3208
|
+
public set chlorinatorCommError(val: number) {
|
|
3209
|
+
if (this.chlorinatorCommError !== val) {
|
|
3210
|
+
this.data.chlorinatorCommError = sys.board.valueMaps.chemDoserWarnings.transform(val);
|
|
3211
|
+
this.hasChanged = true;
|
|
3212
|
+
}
|
|
3213
|
+
}
|
|
3214
|
+
}
|
|
3215
|
+
|
|
3216
|
+
export class ChemDoserStateAlarms extends ChildEqState {
|
|
3217
|
+
public dataName = 'chemControllerAlarms';
|
|
3218
|
+
public initData() {
|
|
3219
|
+
if (typeof this.data.flow === 'undefined') this.data.flow = sys.board.valueMaps.chemDoserAlarms.transform(0);
|
|
3220
|
+
if (typeof this.data.tank === 'undefined') this.data.tank = sys.board.valueMaps.chemDoserAlarms.transform(0);
|
|
3221
|
+
if (typeof this.data.pumpFault === 'undefined') this.data.pumpFault = sys.board.valueMaps.chemDoserHardwareFaults.transform(0);
|
|
3222
|
+
if (typeof this.data.bodyFault === 'undefined') this.data.bodyFault = sys.board.valueMaps.chemDoserHardwareFaults.transform(0);
|
|
3223
|
+
if (typeof this.data.flowSensorFault === 'undefined') this.data.flowSensorFault = sys.board.valueMaps.chemDoserHardwareFaults.transform(0);
|
|
3224
|
+
if (typeof this.data.comms === 'undefined') this.data.comms = sys.board.valueMaps.chemDoserStatus.transform(0);
|
|
3225
|
+
if (typeof this.data.freezeProtect === 'undefined') this.data.freezeProtect = sys.board.valueMaps.chemDoserAlarms.transform(0);
|
|
3226
|
+
}
|
|
3227
|
+
public get flow(): number { return typeof this.data.flow === 'undefined' ? undefined : this.data.flow.val; }
|
|
3228
|
+
public set flow(val: number) {
|
|
3229
|
+
if (this.flow !== val) {
|
|
3230
|
+
this.data.flow = sys.board.valueMaps.chemDoserAlarms.transform(val);
|
|
3231
|
+
this.hasChanged = true;
|
|
3232
|
+
}
|
|
3233
|
+
}
|
|
3234
|
+
public get tank(): number { return typeof this.data.pHTank === 'undefined' ? undefined : this.data.tank.val; }
|
|
3235
|
+
public set tank(val: number) {
|
|
3236
|
+
if (this.tank !== val) {
|
|
3237
|
+
this.data.tank = sys.board.valueMaps.chemDoserAlarms.transform(val);
|
|
3238
|
+
this.hasChanged = true;
|
|
3239
|
+
}
|
|
3240
|
+
}
|
|
3241
|
+
public get pumpFault(): number { return typeof this.data.pumpFault === 'undefined' ? undefined : this.data.pumpFault.val; }
|
|
3242
|
+
public set pumpFault(val: number) {
|
|
3243
|
+
if (this.pumpFault !== val) {
|
|
3244
|
+
this.data.pumpFault = sys.board.valueMaps.chemDoserHardwareFaults.transform(val);
|
|
3245
|
+
this.hasChanged = true;
|
|
3246
|
+
}
|
|
3247
|
+
}
|
|
3248
|
+
public get bodyFault(): number { return typeof this.data.bodyFault === 'undefined' ? undefined : this.data.bodyFault.val; }
|
|
3249
|
+
public set bodyFault(val: number) {
|
|
3250
|
+
if (this.bodyFault !== val) {
|
|
3251
|
+
this.data.bodyFault = sys.board.valueMaps.chemDoserHardwareFaults.transform(val);
|
|
3252
|
+
this.hasChanged = true;
|
|
3253
|
+
}
|
|
3254
|
+
}
|
|
3255
|
+
public get flowSensorFault(): number { return typeof this.data.flowSensorFault === 'undefined' ? undefined : this.data.flowSensorFault.val; }
|
|
3256
|
+
public set flowSensorFault(val: number) {
|
|
3257
|
+
if (this.flowSensorFault !== val) {
|
|
3258
|
+
this.data.flowSensorFault = sys.board.valueMaps.chemDoserHardwareFaults.transform(val);
|
|
3259
|
+
this.hasChanged = true;
|
|
3260
|
+
}
|
|
3261
|
+
}
|
|
3262
|
+
public get comms(): number { return typeof this.data.comms === 'undefined' ? undefined : this.data.comms.val; }
|
|
3263
|
+
public set comms(val: number) {
|
|
3264
|
+
if (this.comms !== val) {
|
|
3265
|
+
this.data.comms = sys.board.valueMaps.chemDoserStatus.transform(val);
|
|
3266
|
+
this.hasChanged = true;
|
|
3267
|
+
}
|
|
3268
|
+
}
|
|
3269
|
+
public get freezeProtect(): number { return typeof this.data.freezeProtect === 'undefined' ? undefined : this.data.freezeProtect.val; }
|
|
3270
|
+
public set freezeProtect(val: number) {
|
|
3271
|
+
if (this.freezeProtect !== val) {
|
|
3272
|
+
this.data.freezeProtect = sys.board.valueMaps.chemDoserAlarms.transform(val);
|
|
3273
|
+
this.hasChanged = true;
|
|
3274
|
+
}
|
|
3275
|
+
}
|
|
3276
|
+
|
|
2834
3277
|
}
|
|
2835
3278
|
export class ChemControllerStateAlarms extends ChildEqState {
|
|
2836
3279
|
//ctor(data): ChemControllerStateWarnings { return new ChemControllerStateWarnings(data, name || 'alarms'); }
|