nodejs-poolcontroller 7.4.0 → 7.5.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/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
- package/Changelog +3 -0
- package/README.md +2 -2
- package/app.ts +2 -0
- package/config/Config.ts +3 -0
- package/config/VersionCheck.ts +8 -4
- package/controller/Equipment.ts +89 -29
- package/controller/Errors.ts +14 -1
- package/controller/State.ts +75 -31
- package/controller/boards/EasyTouchBoard.ts +81 -36
- package/controller/boards/IntelliCenterBoard.ts +96 -32
- package/controller/boards/IntelliTouchBoard.ts +103 -29
- package/controller/boards/NixieBoard.ts +79 -27
- package/controller/boards/SystemBoard.ts +1552 -822
- package/controller/comms/Comms.ts +84 -9
- package/controller/comms/messages/Messages.ts +10 -4
- package/controller/comms/messages/config/ChlorinatorMessage.ts +13 -4
- package/controller/comms/messages/config/CircuitGroupMessage.ts +6 -0
- package/controller/comms/messages/config/CoverMessage.ts +1 -0
- package/controller/comms/messages/config/EquipmentMessage.ts +4 -0
- package/controller/comms/messages/config/ExternalMessage.ts +43 -25
- package/controller/comms/messages/config/FeatureMessage.ts +8 -1
- package/controller/comms/messages/config/GeneralMessage.ts +8 -0
- package/controller/comms/messages/config/HeaterMessage.ts +10 -9
- package/controller/comms/messages/config/IntellichemMessage.ts +4 -1
- package/controller/comms/messages/config/OptionsMessage.ts +13 -1
- package/controller/comms/messages/config/PumpMessage.ts +4 -20
- package/controller/comms/messages/config/RemoteMessage.ts +4 -0
- package/controller/comms/messages/config/ScheduleMessage.ts +11 -0
- package/controller/comms/messages/config/SecurityMessage.ts +1 -0
- package/controller/comms/messages/config/ValveMessage.ts +12 -2
- package/controller/comms/messages/status/ChlorinatorStateMessage.ts +2 -3
- package/controller/comms/messages/status/EquipmentStateMessage.ts +74 -22
- package/controller/comms/messages/status/HeaterStateMessage.ts +15 -6
- package/controller/comms/messages/status/IntelliChemStateMessage.ts +37 -26
- package/controller/nixie/Nixie.ts +18 -16
- package/controller/nixie/chemistry/ChemController.ts +57 -37
- package/controller/nixie/chemistry/Chlorinator.ts +7 -8
- package/controller/nixie/circuits/Circuit.ts +17 -0
- package/controller/nixie/pumps/Pump.ts +49 -24
- package/controller/nixie/schedules/Schedule.ts +1 -1
- package/defaultConfig.json +15 -0
- package/issue_template.md +1 -1
- package/logger/DataLogger.ts +37 -22
- package/package.json +3 -1
- package/web/Server.ts +515 -27
- package/web/bindings/influxDB.json +35 -0
- package/web/bindings/mqtt.json +62 -3
- package/web/bindings/mqttAlt.json +57 -4
- package/web/interfaces/httpInterface.ts +2 -0
- package/web/interfaces/influxInterface.ts +3 -2
- package/web/interfaces/mqttInterface.ts +12 -1
- package/web/services/config/Config.ts +162 -37
- package/web/services/state/State.ts +47 -3
- package/web/services/state/StateSocket.ts +1 -1
|
@@ -22,6 +22,8 @@ import { Body, ChemController, Chlorinator, Circuit, CircuitGroup, CircuitGroupC
|
|
|
22
22
|
import { EquipmentNotFoundError, InvalidEquipmentDataError, InvalidEquipmentIdError } from '../Errors';
|
|
23
23
|
import { ncp } from "../nixie/Nixie";
|
|
24
24
|
import { BodyTempState, ChemControllerState, ChlorinatorState, CircuitGroupState, FilterState, ICircuitGroupState, ICircuitState, LightGroupState, ScheduleState, state, TemperatureState, ValveState, VirtualCircuitState } from '../State';
|
|
25
|
+
import { RestoreResults } from '../../web/Server';
|
|
26
|
+
|
|
25
27
|
|
|
26
28
|
export class byteValueMap extends Map<number, any> {
|
|
27
29
|
public transform(byte: number, ext?: number) { return extend(true, { val: byte || 0 }, this.get(byte) || this.get(0)); }
|
|
@@ -216,10 +218,10 @@ export class byteValueMaps {
|
|
|
216
218
|
]);
|
|
217
219
|
public panelModes: byteValueMap = new byteValueMap([
|
|
218
220
|
[0, { val: 0, name: 'auto', desc: 'Auto' }],
|
|
219
|
-
[1, { val: 1, name: 'service', desc: 'Service' }],
|
|
220
|
-
[8, { val: 8, name: 'freeze', desc: 'Freeze' }],
|
|
221
|
-
[128, { val: 128, name: 'timeout', desc: 'Timeout' }],
|
|
222
|
-
[129, { val: 129, name: 'service-timeout', desc: 'Service/Timeout' }],
|
|
221
|
+
// [1, { val: 1, name: 'service', desc: 'Service' }],
|
|
222
|
+
// [8, { val: 8, name: 'freeze', desc: 'Freeze' }],
|
|
223
|
+
// [128, { val: 128, name: 'timeout', desc: 'Timeout' }],
|
|
224
|
+
// [129, { val: 129, name: 'service-timeout', desc: 'Service/Timeout' }],
|
|
223
225
|
[255, { name: 'error', desc: 'System Error' }]
|
|
224
226
|
]);
|
|
225
227
|
public controllerStatus: byteValueMap = new byteValueMap([
|
|
@@ -244,7 +246,9 @@ export class byteValueMaps {
|
|
|
244
246
|
[15, { name: 'floorcleaner', desc: 'Floor Cleaner' }],
|
|
245
247
|
[16, { name: 'intellibrite', desc: 'Intellibrite', isLight: true }],
|
|
246
248
|
[17, { name: 'magicstream', desc: 'Magicstream', isLight: true }],
|
|
247
|
-
[19, { name: 'notused', desc: 'Not Used' }]
|
|
249
|
+
[19, { name: 'notused', desc: 'Not Used' }],
|
|
250
|
+
[65, { name: 'lotemp', desc: 'Lo-Temp' }],
|
|
251
|
+
[66, { name: 'hightemp', desc: 'Hi-Temp' }]
|
|
248
252
|
]);
|
|
249
253
|
|
|
250
254
|
// Feature functions are used as the available options to define a circuit.
|
|
@@ -287,25 +291,25 @@ export class byteValueMaps {
|
|
|
287
291
|
[254, { name: 'unknown', desc: 'unknown' }],
|
|
288
292
|
[255, { name: 'none', desc: 'None' }]
|
|
289
293
|
]);
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
294
|
+
public colorLogicThemes = new byteValueMap([
|
|
295
|
+
[0, { name: 'cloudwhite', desc: 'Cloud White', type: 'colorlogic', sequence: 7 }],
|
|
296
|
+
[1, { name: 'deepsea', desc: 'Deep Sea', type: 'colorlogic', sequence: 2 }],
|
|
297
|
+
[2, { name: 'royalblue', desc: 'Royal Blue', type: 'colorlogic', sequence: 3 }],
|
|
298
|
+
[3, { name: 'afernoonskies', desc: 'Afternoon Skies', type: 'colorlogic', sequence: 4 }],
|
|
299
|
+
[4, { name: 'aquagreen', desc: 'Aqua Green', type: 'colorlogic', sequence: 5 }],
|
|
300
|
+
[5, { name: 'emerald', desc: 'Emerald', type: 'colorlogic', sequence: 6 }],
|
|
301
|
+
[6, { name: 'warmred', desc: 'Warm Red', type: 'colorlogic', sequence: 8 }],
|
|
302
|
+
[7, { name: 'flamingo', desc: 'Flamingo', type: 'colorlogic', sequence: 9 }],
|
|
303
|
+
[8, { name: 'vividviolet', desc: 'Vivid Violet', type: 'colorlogic', sequence: 10 }],
|
|
304
|
+
[9, { name: 'sangria', desc: 'Sangria', type: 'colorlogic', sequence: 11 }],
|
|
305
|
+
[10, { name: 'twilight', desc: 'Twilight', type: 'colorlogic', sequence: 12 }],
|
|
306
|
+
[11, { name: 'tranquility', desc: 'Tranquility', type: 'colorlogic', sequence: 13 }],
|
|
307
|
+
[12, { name: 'gemstone', desc: 'Gemstone', type: 'colorlogic', sequence: 14 }],
|
|
308
|
+
[13, { name: 'usa', desc: 'USA', type: 'colorlogic', sequence: 15 }],
|
|
309
|
+
[14, { name: 'mardigras', desc: 'Mardi Gras', type: 'colorlogic', sequence: 16 }],
|
|
310
|
+
[15, { name: 'cabaret', desc: 'Cabaret', type: 'colorlogic', sequence: 17 }],
|
|
311
|
+
[255, { name: 'none', desc: 'None' }]
|
|
312
|
+
]);
|
|
309
313
|
|
|
310
314
|
public lightColors: byteValueMap = new byteValueMap([
|
|
311
315
|
[0, { name: 'white', desc: 'White' }],
|
|
@@ -434,7 +438,9 @@ export class byteValueMaps {
|
|
|
434
438
|
]);
|
|
435
439
|
public bodyTypes: byteValueMap = new byteValueMap([
|
|
436
440
|
[0, { name: 'pool', desc: 'Pool' }],
|
|
437
|
-
[1, { name: 'spa', desc: 'Spa' }]
|
|
441
|
+
[1, { name: 'spa', desc: 'Spa' }],
|
|
442
|
+
[2, { name: 'spa', desc: 'Spa' }],
|
|
443
|
+
[3, { name: 'spa', desc: 'Spa' }]
|
|
438
444
|
]);
|
|
439
445
|
public bodies: byteValueMap = new byteValueMap([
|
|
440
446
|
[0, { name: 'pool', desc: 'Pool' }],
|
|
@@ -462,10 +468,16 @@ export class byteValueMaps {
|
|
|
462
468
|
]);
|
|
463
469
|
public chlorinatorModel: byteValueMap = new byteValueMap([
|
|
464
470
|
[0, { name: 'unknown', desc: 'unknown', capacity: 0, chlorinePerDay: 0, chlorinePerSec: 0 }],
|
|
465
|
-
[1, { name: 'intellichlor--15', desc: 'IC15', capacity: 15000, chlorinePerDay: 0.60, chlorinePerSec: 0.60/86400 }],
|
|
466
|
-
[2, { name: 'intellichlor--20', desc: 'IC20', capacity: 20000, chlorinePerDay: 0.70, chlorinePerSec: 0.70/86400 }],
|
|
467
|
-
[3, { name: 'intellichlor--40', desc: 'IC40', capacity: 40000, chlorinePerDay: 1.40, chlorinePerSec: 1.4/86400 }],
|
|
468
|
-
[4, { name: 'intellichlor--60', desc: 'IC60', capacity: 60000, chlorinePerDay: 2, chlorinePerSec: 2/86400 }],
|
|
471
|
+
[1, { name: 'intellichlor--15', desc: 'IntelliChlor IC15', capacity: 15000, chlorinePerDay: 0.60, chlorinePerSec: 0.60 / 86400 }],
|
|
472
|
+
[2, { name: 'intellichlor--20', desc: 'IntelliChlor IC20', capacity: 20000, chlorinePerDay: 0.70, chlorinePerSec: 0.70 / 86400 }],
|
|
473
|
+
[3, { name: 'intellichlor--40', desc: 'IntelliChlor IC40', capacity: 40000, chlorinePerDay: 1.40, chlorinePerSec: 1.4 / 86400 }],
|
|
474
|
+
[4, { name: 'intellichlor--60', desc: 'IntelliChlor IC60', capacity: 60000, chlorinePerDay: 2, chlorinePerSec: 2 / 86400 }],
|
|
475
|
+
[5, { name: 'aquarite-t15', desc: 'AquaRite T15', capacity: 40000, chlorinePerDay: 1.47, chlorinePerSec: 1.47 / 86400 }],
|
|
476
|
+
[6, { name: 'aquarite-t9', desc: 'AquaRite T9', capacity: 30000, chlorinePerDay: 0.98, chlorinePerSec: 0.98 / 86400 }],
|
|
477
|
+
[7, { name: 'aquarite-t5', desc: 'AquaRite T5', capacity: 20000, chlorinePerDay: 0.735, chlorinePerSec: 0.735 / 86400 }],
|
|
478
|
+
[8, { name: 'aquarite-t3', desc: 'AquaRite T3', capacity: 15000, chlorinePerDay: 0.53, chlorinePerSec: 0.53 / 86400 }],
|
|
479
|
+
[9, { name: 'aquarite-925', desc: 'AquaRite 925', capacity: 25000, chlorinePerDay: 0.98, chlorinePerSec: 0.98 / 86400 }],
|
|
480
|
+
[10, { name: 'aquarite-940', desc: 'AquaRite 940', capacity: 40000, chlorinePerDay: 1.47, chlorinePerSec: 1.47 / 86400 }]
|
|
469
481
|
])
|
|
470
482
|
public customNames: byteValueMap = new byteValueMap();
|
|
471
483
|
public circuitNames: byteValueMap = new byteValueMap();
|
|
@@ -483,6 +495,10 @@ export class byteValueMaps {
|
|
|
483
495
|
[0, { name: 'off', desc: 'Off' }],
|
|
484
496
|
[1, { name: 'on', desc: 'On' }]
|
|
485
497
|
]);
|
|
498
|
+
public systemUnits: byteValueMap = new byteValueMap([
|
|
499
|
+
[0, { name: 'english', desc: 'English' }],
|
|
500
|
+
[4, { name: 'metric', desc: 'Metric' }]
|
|
501
|
+
]);
|
|
486
502
|
public tempUnits: byteValueMap = new byteValueMap([
|
|
487
503
|
[0, { name: 'F', desc: 'Fahrenheit' }],
|
|
488
504
|
[4, { name: 'C', desc: 'Celsius' }]
|
|
@@ -507,7 +523,7 @@ export class byteValueMaps {
|
|
|
507
523
|
[0, { name: 'none', desc: 'None', ph: { min: 6.8, max: 7.6 }, orp: { min: 400, max: 800 }, hasAddress: false }],
|
|
508
524
|
[1, { name: 'unknown', desc: 'Unknown', ph: { min: 6.8, max: 7.6 }, hasAddress: false }],
|
|
509
525
|
[2, { name: 'intellichem', desc: 'IntelliChem', ph: { min: 7.2, max: 7.6 }, orp: { min: 400, max: 800 }, hasAddress: true }],
|
|
510
|
-
[3, { name: 'homegrown', desc: 'Homegrown', ph: { min: 6.8, max: 7.6 }, hasAddress: false }],
|
|
526
|
+
// [3, { name: 'homegrown', desc: 'Homegrown', ph: { min: 6.8, max: 7.6 }, hasAddress: false }],
|
|
511
527
|
[4, { name: 'rem', desc: 'REM Chem', ph: { min: 6.8, max: 8.0 }, hasAddress: false }]
|
|
512
528
|
]);
|
|
513
529
|
public siCalcTypes: byteValueMap = new byteValueMap([
|
|
@@ -558,13 +574,13 @@ export class byteValueMaps {
|
|
|
558
574
|
[6, { name: 'qt', desc: 'Quarts' }],
|
|
559
575
|
[7, { name: 'pt', desc: 'Pints' }]
|
|
560
576
|
]);
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
577
|
+
public pressureUnits: byteValueMap = new byteValueMap([
|
|
578
|
+
[0, { name: 'psi', desc: 'Pounds per Sqare Inch' }],
|
|
579
|
+
[1, { name: 'Pa', desc: 'Pascal' }],
|
|
580
|
+
[2, { name: 'kPa', desc: 'Kilo-pascals' }],
|
|
581
|
+
[3, { name: 'atm', desc: 'Atmospheres' }],
|
|
582
|
+
[4, { name: 'bar', desc: 'Barometric' }]
|
|
583
|
+
]);
|
|
568
584
|
|
|
569
585
|
public areaUnits: byteValueMap = new byteValueMap([
|
|
570
586
|
[0, { name: '', desc: 'No Units' }],
|
|
@@ -802,36 +818,37 @@ export class SystemBoard {
|
|
|
802
818
|
/// This method processes the status message periodically. The role of this method is to verify the circuit, valve, and heater
|
|
803
819
|
/// relays. This method does not control RS485 operations such as pumps and chlorinators. These are done through the respective
|
|
804
820
|
/// equipment polling functions.
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
catch (err) { logger.error(`Error synchronizing equipment items: ${err.message}`); }
|
|
821
|
+
public async processStatusAsync() {
|
|
822
|
+
let self = this;
|
|
823
|
+
try {
|
|
824
|
+
if (this._statusCheckRef > 0) return;
|
|
825
|
+
this.suspendStatus(true);
|
|
826
|
+
if (typeof this._statusTimer !== 'undefined' && this._statusTimer) clearTimeout(this._statusTimer);
|
|
827
|
+
// Go through all the assigned equipment and verify the current state.
|
|
828
|
+
sys.board.system.keepManualTime();
|
|
829
|
+
await sys.board.bodies.syncFreezeProtection();
|
|
830
|
+
await sys.board.syncEquipmentItems();
|
|
831
|
+
await sys.board.schedules.syncScheduleStates();
|
|
832
|
+
await sys.board.circuits.checkEggTimerExpirationAsync();
|
|
833
|
+
state.emitControllerChange();
|
|
834
|
+
state.emitEquipmentChanges();
|
|
835
|
+
} catch (err) { state.status = 255; logger.error(`Error performing processStatusAsync ${err.message}`); }
|
|
836
|
+
finally {
|
|
837
|
+
this.suspendStatus(false);
|
|
838
|
+
if (this.statusInterval > 0) this._statusTimer = setTimeout(async () => await self.processStatusAsync(), this.statusInterval);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
public async syncEquipmentItems() {
|
|
842
|
+
try {
|
|
843
|
+
await sys.board.circuits.syncCircuitRelayStates();
|
|
844
|
+
await sys.board.features.syncGroupStates();
|
|
845
|
+
await sys.board.circuits.syncVirtualCircuitStates();
|
|
846
|
+
await sys.board.valves.syncValveStates();
|
|
847
|
+
await sys.board.filters.syncFilterStates();
|
|
848
|
+
await sys.board.heaters.syncHeaterStates();
|
|
834
849
|
}
|
|
850
|
+
catch (err) { logger.error(`Error synchronizing equipment items: ${err.message}`); }
|
|
851
|
+
}
|
|
835
852
|
public async setControllerType(obj): Promise<Equipment> {
|
|
836
853
|
try {
|
|
837
854
|
if (obj.controllerType !== sys.controllerType)
|
|
@@ -909,392 +926,513 @@ export class BoardCommands {
|
|
|
909
926
|
constructor(parent: SystemBoard) { this.board = parent; }
|
|
910
927
|
}
|
|
911
928
|
export class SystemCommands extends BoardCommands {
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
929
|
+
public async restore(rest: { poolConfig: any, poolState: any }): Promise<RestoreResults> {
|
|
930
|
+
let res = new RestoreResults();
|
|
931
|
+
try {
|
|
932
|
+
let ctx = await sys.board.system.validateRestore(rest);
|
|
933
|
+
// Restore the general stuff.
|
|
934
|
+
if (ctx.general.update.length > 0) await sys.board.system.setGeneralAsync(ctx.general.update[0]);
|
|
935
|
+
for (let i = 0; i < ctx.customNames.update.length; i++) {
|
|
936
|
+
let cn = ctx.customNames.update[i];
|
|
915
937
|
try {
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
sys.equipment.tempSensors.setCalibration('water1', parseFloat(obj.waterTempAdj1));
|
|
945
|
-
}
|
|
946
|
-
if (typeof obj.waterTempAdj2 != 'undefined' && obj.waterTempAdj2 !== sys.equipment.tempSensors.getCalibration('water2')) {
|
|
947
|
-
sys.equipment.tempSensors.setCalibration('water2', parseFloat(obj.waterTempAdj2));
|
|
948
|
-
}
|
|
949
|
-
if (typeof obj.waterTempAdj3 != 'undefined' && obj.waterTempAdj3 !== sys.equipment.tempSensors.getCalibration('water3')) {
|
|
950
|
-
sys.equipment.tempSensors.setCalibration('water3', parseFloat(obj.waterTempAdj3));
|
|
951
|
-
}
|
|
952
|
-
if (typeof obj.waterTempAdj4 != 'undefined' && obj.waterTempAdj4 !== sys.equipment.tempSensors.getCalibration('water4')) {
|
|
953
|
-
sys.equipment.tempSensors.setCalibration('water4', parseFloat(obj.waterTempAdj4));
|
|
954
|
-
}
|
|
955
|
-
if (typeof obj.solarTempAdj1 != 'undefined' && obj.solarTempAdj1 !== sys.equipment.tempSensors.getCalibration('solar1')) {
|
|
956
|
-
sys.equipment.tempSensors.setCalibration('solar1', parseFloat(obj.solarTempAdj1));
|
|
957
|
-
}
|
|
958
|
-
if (typeof obj.solarTempAdj2 != 'undefined' && obj.solarTempAdj2 !== sys.equipment.tempSensors.getCalibration('solar2')) {
|
|
959
|
-
sys.equipment.tempSensors.setCalibration('solar2', parseFloat(obj.solarTempAdj2));
|
|
960
|
-
}
|
|
961
|
-
if (typeof obj.solarTempAdj3 != 'undefined' && obj.solarTempAdj3 !== sys.equipment.tempSensors.getCalibration('solar3')) {
|
|
962
|
-
sys.equipment.tempSensors.setCalibration('solar3', parseFloat(obj.solarTempAdj3));
|
|
963
|
-
}
|
|
964
|
-
if (typeof obj.solarTempAdj4 != 'undefined' && obj.solarTempAdj4 !== sys.equipment.tempSensors.getCalibration('solar4')) {
|
|
965
|
-
sys.equipment.tempSensors.setCalibration('solar4', parseFloat(obj.solarTempAdj4));
|
|
966
|
-
}
|
|
967
|
-
if (typeof obj.airTempAdj != 'undefined' && obj.airTempAdj !== sys.equipment.tempSensors.getCalibration('air')) {
|
|
968
|
-
sys.equipment.tempSensors.setCalibration('air', parseFloat(obj.airTempAdj));
|
|
969
|
-
}
|
|
970
|
-
return new Promise<TempSensorCollection>((resolve, reject) => { resolve(sys.equipment.tempSensors); });
|
|
971
|
-
}
|
|
972
|
-
public async setOptionsAsync(obj: any): Promise<Options> {
|
|
973
|
-
if (obj.clockSource === 'server') sys.board.system.setTZ();
|
|
974
|
-
sys.board.system.setTempSensorsAsync(obj);
|
|
975
|
-
sys.general.options.set(obj);
|
|
976
|
-
return new Promise<Options>(function (resolve, reject) { resolve(sys.general.options); });
|
|
977
|
-
}
|
|
978
|
-
public async setLocationAsync(obj: any): Promise<Location> {
|
|
979
|
-
sys.general.location.set(obj);
|
|
980
|
-
return new Promise<Location>(function (resolve, reject) { resolve(sys.general.location); });
|
|
981
|
-
}
|
|
982
|
-
public async setOwnerAsync(obj: any): Promise<Owner> {
|
|
983
|
-
sys.general.owner.set(obj);
|
|
984
|
-
return new Promise<Owner>(function (resolve, reject) { resolve(sys.general.owner); });
|
|
985
|
-
}
|
|
986
|
-
public async setTempsAsync(obj: any): Promise<TemperatureState> {
|
|
987
|
-
return new Promise<TemperatureState>((resolve, reject) => {
|
|
988
|
-
for (let prop in obj) {
|
|
989
|
-
switch (prop) {
|
|
990
|
-
case 'air':
|
|
991
|
-
case 'airSensor':
|
|
992
|
-
case 'airSensor1':
|
|
993
|
-
{
|
|
994
|
-
let temp = obj[prop] !== null ? parseFloat(obj[prop]) : 0;
|
|
995
|
-
if (isNaN(temp)) return reject(new InvalidEquipmentDataError(`Invalid value for ${prop} ${obj[prop]}`, `Temps:${prop}`, obj[prop]));
|
|
996
|
-
state.temps.air = sys.equipment.tempSensors.getCalibration('air') + temp;
|
|
997
|
-
}
|
|
998
|
-
break;
|
|
999
|
-
case 'waterSensor1':
|
|
1000
|
-
{
|
|
1001
|
-
let temp = obj[prop] !== null ? parseFloat(obj[prop]) : 0;
|
|
1002
|
-
if (isNaN(temp)) return reject(new InvalidEquipmentDataError(`Invalid value for ${prop} ${obj[prop]}`, `Temps:${prop}`, obj[prop]));
|
|
1003
|
-
state.temps.waterSensor1 = sys.equipment.tempSensors.getCalibration('water1') + temp;
|
|
1004
|
-
let body = state.temps.bodies.getItemById(1);
|
|
1005
|
-
if (body.isOn) body.temp = state.temps.waterSensor1;
|
|
1006
|
-
else if (sys.equipment.shared) {
|
|
1007
|
-
body = state.temps.bodies.getItemById(2);
|
|
1008
|
-
if (body.isOn) body.temp = state.temps.waterSensor1;
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
|
-
break;
|
|
1012
|
-
case 'waterSensor2':
|
|
1013
|
-
{
|
|
1014
|
-
let temp = obj[prop] !== null ? parseFloat(obj[prop]) : 0;
|
|
1015
|
-
if (isNaN(temp)) return reject(new InvalidEquipmentDataError(`Invalid value for ${prop} ${obj[prop]}`, `Temps:${prop}`, obj[prop]));
|
|
1016
|
-
state.temps.waterSensor2 = sys.equipment.tempSensors.getCalibration('water2') + temp;
|
|
1017
|
-
if (!state.equipment.shared) {
|
|
1018
|
-
let body = state.temps.bodies.getItemById(2);
|
|
1019
|
-
if (body.isOn) body.temp = state.temps.waterSensor2;
|
|
1020
|
-
}
|
|
1021
|
-
}
|
|
1022
|
-
break;
|
|
1023
|
-
case 'waterSensor3':
|
|
1024
|
-
{
|
|
1025
|
-
let temp = obj[prop] !== null ? parseFloat(obj[prop]) : 0;
|
|
1026
|
-
if (isNaN(temp)) return reject(new InvalidEquipmentDataError(`Invalid value for ${prop} ${obj[prop]}`, `Temps:${prop}`, obj[prop]));
|
|
1027
|
-
state.temps.waterSensor3 = sys.equipment.tempSensors.getCalibration('water3') + temp;
|
|
1028
|
-
let body = state.temps.bodies.getItemById(3);
|
|
1029
|
-
if (body.isOn) body.temp = state.temps.waterSensor3;
|
|
1030
|
-
}
|
|
1031
|
-
break;
|
|
1032
|
-
case 'waterSensor4':
|
|
1033
|
-
{
|
|
1034
|
-
|
|
1035
|
-
let temp = obj[prop] !== null ? parseFloat(obj[prop]) : 0;
|
|
1036
|
-
if (isNaN(temp)) return reject(new InvalidEquipmentDataError(`Invalid value for ${prop} ${obj[prop]}`, `Temps:${prop}`, obj[prop]));
|
|
1037
|
-
state.temps.waterSensor4 = sys.equipment.tempSensors.getCalibration('water4') + temp;
|
|
1038
|
-
let body = state.temps.bodies.getItemById(4);
|
|
1039
|
-
if (body.isOn) body.temp = state.temps.waterSensor4;
|
|
1040
|
-
}
|
|
1041
|
-
break;
|
|
938
|
+
await sys.board.system.setCustomNameAsync(cn);
|
|
939
|
+
res.addModuleSuccess('customName', `Update: ${cn.id}-${cn.name}`);
|
|
940
|
+
} catch (err) { res.addModuleError('customName', `Update: ${cn.id}-${cn.name}: ${err.message}`); }
|
|
941
|
+
}
|
|
942
|
+
for (let i = 0; i < ctx.customNames.add.length; i++) {
|
|
943
|
+
let cn = ctx.customNames.add[i];
|
|
944
|
+
try {
|
|
945
|
+
await sys.board.system.setCustomNameAsync(cn);
|
|
946
|
+
res.addModuleSuccess('customName', `Add: ${cn.id}-${cn.name}`);
|
|
947
|
+
} catch (err) { res.addModuleError('customName', `Add: ${cn.id}-${cn.name}: ${err.message}`); }
|
|
948
|
+
}
|
|
949
|
+
await sys.board.bodies.restore(rest, ctx, res);
|
|
950
|
+
await sys.board.filters.restore(rest, ctx, res);
|
|
951
|
+
await sys.board.circuits.restore(rest, ctx, res);
|
|
952
|
+
await sys.board.heaters.restore(rest, ctx, res);
|
|
953
|
+
await sys.board.features.restore(rest, ctx, res);
|
|
954
|
+
await sys.board.pumps.restore(rest, ctx, res);
|
|
955
|
+
await sys.board.valves.restore(rest, ctx, res);
|
|
956
|
+
await sys.board.chlorinator.restore(rest, ctx, res);
|
|
957
|
+
await sys.board.chemControllers.restore(rest, ctx, res);
|
|
958
|
+
await sys.board.schedules.restore(rest, ctx, res);
|
|
959
|
+
return res;
|
|
960
|
+
//await sys.board.covers.restore(rest, ctx);
|
|
961
|
+
} catch (err) { logger.error(`Error restoring njsPC server: ${err.message}`); res.addModuleError('system', err.message); return Promise.reject(err);}
|
|
962
|
+
}
|
|
963
|
+
public async validateRestore(rest: { poolConfig: any, poolState: any }): Promise<any> {
|
|
964
|
+
try {
|
|
965
|
+
let ctx: any = { board: { errors: [], warnings: [] } };
|
|
1042
966
|
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
967
|
+
// Step 1 - Verify that the boards are the same. For instance you do not want to restore an IntelliTouch to an IntelliCenter.
|
|
968
|
+
let cfg = rest.poolConfig;
|
|
969
|
+
if (sys.controllerType === cfg.controllerType) {
|
|
970
|
+
ctx.customNames = { errors: [], warnings: [], add: [], update: [], remove: [] };
|
|
971
|
+
let customNames = sys.customNames.get();
|
|
972
|
+
for (let i = 0; i < rest.poolConfig.customNames.length; i++) {
|
|
973
|
+
let cn = customNames.find(elem => elem.id === rest.poolConfig.customNames[i].id);
|
|
974
|
+
if (typeof cn === 'undefined') ctx.customNames.add.push(rest.poolConfig.customNames[i]);
|
|
975
|
+
else if (JSON.stringify(rest.poolConfig.customNames[i]) !== JSON.stringify(cn)) ctx.customNames.update.push(cn);
|
|
976
|
+
}
|
|
977
|
+
ctx.general = { errors: [], warnings: [], add: [], update: [], remove: [] };
|
|
978
|
+
if (JSON.stringify(sys.general.get()) !== JSON.stringify(cfg.pool)) ctx.general.update.push(cfg.pool);
|
|
979
|
+
ctx.bodies = await sys.board.bodies.validateRestore(rest);
|
|
980
|
+
ctx.pumps = await sys.board.pumps.validateRestore(rest);
|
|
981
|
+
await sys.board.circuits.validateRestore(rest, ctx);
|
|
982
|
+
ctx.features = await sys.board.features.validateRestore(rest);
|
|
983
|
+
ctx.chlorinators = await sys.board.chlorinator.validateRestore(rest);
|
|
984
|
+
ctx.heaters = await sys.board.heaters.validateRestore(rest);
|
|
985
|
+
ctx.valves = await sys.board.valves.validateRestore(rest);
|
|
986
|
+
|
|
987
|
+
//ctx.covers = await sys.board.covers.validateRestore(rest);
|
|
988
|
+
ctx.chemControllers = await sys.board.chemControllers.validateRestore(rest);
|
|
989
|
+
ctx.filters = await sys.board.filters.validateRestore(rest);
|
|
990
|
+
ctx.schedules = await sys.board.schedules.validateRestore(rest);
|
|
991
|
+
}
|
|
992
|
+
else ctx.board.errors.push(`Panel Types do not match cannot restore bakup from ${sys.controllerType} to ${rest.poolConfig.controllerType}`);
|
|
993
|
+
|
|
994
|
+
return ctx;
|
|
995
|
+
|
|
996
|
+
} catch (err) { logger.error(`Error validating restore file: ${err.message}`); return Promise.reject(err);}
|
|
997
|
+
|
|
998
|
+
}
|
|
999
|
+
public cancelDelay(): Promise<any> { state.delay = sys.board.valueMaps.delay.getValue('nodelay'); return Promise.resolve(state.data.delay); }
|
|
1000
|
+
public setDateTimeAsync(obj: any): Promise<any> { return Promise.resolve(); }
|
|
1001
|
+
public keepManualTime() {
|
|
1002
|
+
try {
|
|
1003
|
+
// every minute, updated the time from the system clock in server mode
|
|
1004
|
+
// but only for Virtual. Likely 'manual' on *Center means OCP time
|
|
1005
|
+
if (sys.general.options.clockSource !== 'server') return;
|
|
1006
|
+
state.time.setTimeFromSystemClock();
|
|
1007
|
+
sys.board.system.setTZ();
|
|
1008
|
+
} catch (err) { logger.error(`Error setting manual time: ${err.message}`); }
|
|
1009
|
+
}
|
|
1010
|
+
public setTZ() {
|
|
1011
|
+
let tzOffsetObj = state.time.calcTZOffset();
|
|
1012
|
+
if (sys.general.options.clockSource === 'server' || typeof sys.general.location.timeZone === 'undefined') {
|
|
1013
|
+
let tzs = sys.board.valueMaps.timeZones.toArray();
|
|
1014
|
+
sys.general.location.timeZone = tzs.find(tz => tz.utcOffset === tzOffsetObj.tzOffset).val;
|
|
1015
|
+
}
|
|
1016
|
+
if (sys.general.options.clockSource === 'server' || typeof sys.general.options.adjustDST === 'undefined') {
|
|
1017
|
+
sys.general.options.adjustDST = tzOffsetObj.adjustDST;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
public getDOW() { return this.board.valueMaps.scheduleDays.toArray(); }
|
|
1021
|
+
public async setGeneralAsync(obj: any): Promise<General> {
|
|
1022
|
+
let general = sys.general.get();
|
|
1023
|
+
if (typeof obj.alias === 'string') sys.general.alias = obj.alias;
|
|
1024
|
+
if (typeof obj.options !== 'undefined') await sys.board.system.setOptionsAsync(obj.options);
|
|
1025
|
+
if (typeof obj.location !== 'undefined') await sys.board.system.setLocationAsync(obj.location);
|
|
1026
|
+
if (typeof obj.owner !== 'undefined') await sys.board.system.setOwnerAsync(obj.owner);
|
|
1027
|
+
return new Promise<General>(function (resolve, reject) { resolve(sys.general); });
|
|
1028
|
+
}
|
|
1029
|
+
public async setTempSensorsAsync(obj: any): Promise<TempSensorCollection> {
|
|
1030
|
+
if (typeof obj.waterTempAdj1 != 'undefined' && obj.waterTempAdj1 !== sys.equipment.tempSensors.getCalibration('water1')) {
|
|
1031
|
+
sys.equipment.tempSensors.setCalibration('water1', parseFloat(obj.waterTempAdj1));
|
|
1032
|
+
}
|
|
1033
|
+
if (typeof obj.waterTempAdj2 != 'undefined' && obj.waterTempAdj2 !== sys.equipment.tempSensors.getCalibration('water2')) {
|
|
1034
|
+
sys.equipment.tempSensors.setCalibration('water2', parseFloat(obj.waterTempAdj2));
|
|
1035
|
+
}
|
|
1036
|
+
if (typeof obj.waterTempAdj3 != 'undefined' && obj.waterTempAdj3 !== sys.equipment.tempSensors.getCalibration('water3')) {
|
|
1037
|
+
sys.equipment.tempSensors.setCalibration('water3', parseFloat(obj.waterTempAdj3));
|
|
1038
|
+
}
|
|
1039
|
+
if (typeof obj.waterTempAdj4 != 'undefined' && obj.waterTempAdj4 !== sys.equipment.tempSensors.getCalibration('water4')) {
|
|
1040
|
+
sys.equipment.tempSensors.setCalibration('water4', parseFloat(obj.waterTempAdj4));
|
|
1041
|
+
}
|
|
1042
|
+
if (typeof obj.solarTempAdj1 != 'undefined' && obj.solarTempAdj1 !== sys.equipment.tempSensors.getCalibration('solar1')) {
|
|
1043
|
+
sys.equipment.tempSensors.setCalibration('solar1', parseFloat(obj.solarTempAdj1));
|
|
1044
|
+
}
|
|
1045
|
+
if (typeof obj.solarTempAdj2 != 'undefined' && obj.solarTempAdj2 !== sys.equipment.tempSensors.getCalibration('solar2')) {
|
|
1046
|
+
sys.equipment.tempSensors.setCalibration('solar2', parseFloat(obj.solarTempAdj2));
|
|
1047
|
+
}
|
|
1048
|
+
if (typeof obj.solarTempAdj3 != 'undefined' && obj.solarTempAdj3 !== sys.equipment.tempSensors.getCalibration('solar3')) {
|
|
1049
|
+
sys.equipment.tempSensors.setCalibration('solar3', parseFloat(obj.solarTempAdj3));
|
|
1050
|
+
}
|
|
1051
|
+
if (typeof obj.solarTempAdj4 != 'undefined' && obj.solarTempAdj4 !== sys.equipment.tempSensors.getCalibration('solar4')) {
|
|
1052
|
+
sys.equipment.tempSensors.setCalibration('solar4', parseFloat(obj.solarTempAdj4));
|
|
1053
|
+
}
|
|
1054
|
+
if (typeof obj.airTempAdj != 'undefined' && obj.airTempAdj !== sys.equipment.tempSensors.getCalibration('air')) {
|
|
1055
|
+
sys.equipment.tempSensors.setCalibration('air', parseFloat(obj.airTempAdj));
|
|
1056
|
+
}
|
|
1057
|
+
return new Promise<TempSensorCollection>((resolve, reject) => { resolve(sys.equipment.tempSensors); });
|
|
1058
|
+
}
|
|
1059
|
+
public async setOptionsAsync(obj: any): Promise<Options> {
|
|
1060
|
+
if (obj.clockSource === 'server') sys.board.system.setTZ();
|
|
1061
|
+
sys.board.system.setTempSensorsAsync(obj);
|
|
1062
|
+
sys.general.options.set(obj);
|
|
1063
|
+
let bodyUnits = sys.general.options.units === 0 ? 1 : 2;
|
|
1064
|
+
for (let i = 0; i < sys.bodies.length; i++) sys.bodies.getItemByIndex(i).capacityUnits = bodyUnits;
|
|
1065
|
+
state.temps.units = sys.general.options.units === 0 ? 1 : 4;
|
|
1066
|
+
return new Promise<Options>(function (resolve, reject) { resolve(sys.general.options); });
|
|
1067
|
+
}
|
|
1068
|
+
public async setLocationAsync(obj: any): Promise<Location> {
|
|
1069
|
+
sys.general.location.set(obj);
|
|
1070
|
+
return new Promise<Location>(function (resolve, reject) { resolve(sys.general.location); });
|
|
1071
|
+
}
|
|
1072
|
+
public async setOwnerAsync(obj: any): Promise<Owner> {
|
|
1073
|
+
sys.general.owner.set(obj);
|
|
1074
|
+
return new Promise<Owner>(function (resolve, reject) { resolve(sys.general.owner); });
|
|
1075
|
+
}
|
|
1076
|
+
public async setTempsAsync(obj: any): Promise<TemperatureState> {
|
|
1077
|
+
return new Promise<TemperatureState>((resolve, reject) => {
|
|
1078
|
+
for (let prop in obj) {
|
|
1079
|
+
switch (prop) {
|
|
1080
|
+
case 'air':
|
|
1081
|
+
case 'airSensor':
|
|
1082
|
+
case 'airSensor1':
|
|
1083
|
+
{
|
|
1084
|
+
let temp = obj[prop] !== null ? parseFloat(obj[prop]) : 0;
|
|
1085
|
+
if (isNaN(temp)) return reject(new InvalidEquipmentDataError(`Invalid value for ${prop} ${obj[prop]}`, `Temps:${prop}`, obj[prop]));
|
|
1086
|
+
state.temps.air = sys.equipment.tempSensors.getCalibration('air') + temp;
|
|
1077
1087
|
}
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
sensors.push({ name: 'Water Sensor', temp: state.temps.waterSensor1, tempAdj: sys.equipment.tempSensors.getCalibration('water1'), binding: 'waterTempAdj1' });
|
|
1091
|
-
if (sys.equipment.maxBodies > 3)
|
|
1092
|
-
sensors.push({ name: 'Water Sensor 4', temp: state.temps.waterSensor4, tempAdj: sys.equipment.tempSensors.getCalibration('water4'), binding: 'waterTempAdj4' });
|
|
1093
|
-
|
|
1094
|
-
if (sys.board.heaters.isSolarInstalled()) {
|
|
1095
|
-
if (sys.equipment.maxBodies > 2) {
|
|
1096
|
-
sensors.push({ name: 'Solar Sensor 1', temp: state.temps.solar, tempAdj: sys.equipment.tempSensors.getCalibration('solar1'), binding: 'solarTempAdj1' },
|
|
1097
|
-
{ name: 'Solar Sensor 2', temp: state.temps.solarSensor2, tempAdj: sys.equipment.tempSensors.getCalibration('solar2'), binding: 'solarTempAdj2' });
|
|
1098
|
-
}
|
|
1099
|
-
else
|
|
1100
|
-
sensors.push({ name: 'Solar Sensor', temp: state.temps.solar, tempAdj: sys.equipment.tempSensors.getCalibration('solar1'), binding: 'solarTempAdj1' });
|
|
1101
|
-
if (sys.equipment.maxBodies > 3)
|
|
1102
|
-
sensors.push({ name: 'Solar Sensor 4', temp: state.temps.solarSensor4, tempAdj: sys.equipment.tempSensors.getCalibration('solar4'), binding: 'solarTempAdj4' });
|
|
1088
|
+
break;
|
|
1089
|
+
case 'waterSensor1':
|
|
1090
|
+
{
|
|
1091
|
+
let temp = obj[prop] !== null ? parseFloat(obj[prop]) : 0;
|
|
1092
|
+
if (isNaN(temp)) return reject(new InvalidEquipmentDataError(`Invalid value for ${prop} ${obj[prop]}`, `Temps:${prop}`, obj[prop]));
|
|
1093
|
+
state.temps.waterSensor1 = sys.equipment.tempSensors.getCalibration('water1') + temp;
|
|
1094
|
+
let body = state.temps.bodies.getItemById(1);
|
|
1095
|
+
if (body.isOn) body.temp = state.temps.waterSensor1;
|
|
1096
|
+
else if (sys.equipment.shared) {
|
|
1097
|
+
body = state.temps.bodies.getItemById(2);
|
|
1098
|
+
if (body.isOn) body.temp = state.temps.waterSensor1;
|
|
1099
|
+
}
|
|
1103
1100
|
}
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
{ name: 'Solar Sensor 2', temp: state.temps.solarSensor2, tempAdj: sys.equipment.tempSensors.getCalibration('solar2'), binding: 'solarTempAdj2' });
|
|
1115
|
-
if (sys.equipment.maxBodies > 2)
|
|
1116
|
-
sensors.push({ name: 'Solar Sensor 3', temp: state.temps.solarSensor3, tempAdj: sys.equipment.tempSensors.getCalibration('solar3'), binding: 'solarTempAdj3' });
|
|
1117
|
-
if (sys.equipment.maxBodies > 3)
|
|
1118
|
-
sensors.push({ name: 'Solar Sensor 4', temp: state.temps.solarSensor4, tempAdj: sys.equipment.tempSensors.getCalibration('solar4'), binding: 'solarTempAdj4' });
|
|
1101
|
+
break;
|
|
1102
|
+
case 'waterSensor2':
|
|
1103
|
+
{
|
|
1104
|
+
let temp = obj[prop] !== null ? parseFloat(obj[prop]) : 0;
|
|
1105
|
+
if (isNaN(temp)) return reject(new InvalidEquipmentDataError(`Invalid value for ${prop} ${obj[prop]}`, `Temps:${prop}`, obj[prop]));
|
|
1106
|
+
state.temps.waterSensor2 = sys.equipment.tempSensors.getCalibration('water2') + temp;
|
|
1107
|
+
if (!state.equipment.shared) {
|
|
1108
|
+
let body = state.temps.bodies.getItemById(2);
|
|
1109
|
+
if (body.isOn) body.temp = state.temps.waterSensor2;
|
|
1110
|
+
}
|
|
1119
1111
|
}
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
sensors.push({ name: 'Water Sensor 4', temp: state.temps.waterSensor4, tempAdj: sys.equipment.tempSensors.getCalibration('water4'), binding: 'waterTempAdj4' });
|
|
1129
|
-
|
|
1130
|
-
if (sys.board.heaters.isSolarInstalled()) {
|
|
1131
|
-
sensors.push({ name: 'Solar Sensor 1', temp: state.temps.solarSensor1, tempAdj: sys.equipment.tempSensors.getCalibration('solar1'), binding: 'solarTempAdj1' },
|
|
1132
|
-
{ name: 'Solar Sensor 2', temp: state.temps.solarSensor2, tempAdj: sys.equipment.tempSensors.getCalibration('solar2'), binding: 'solarTempAdj2' });
|
|
1133
|
-
if (sys.equipment.maxBodies > 2)
|
|
1134
|
-
sensors.push({ name: 'Solar Sensor 3', temp: state.temps.solarSensor3, tempAdj: sys.equipment.tempSensors.getCalibration('solar3'), binding: 'solarTempAdj3' });
|
|
1135
|
-
if (sys.equipment.maxBodies > 3)
|
|
1136
|
-
sensors.push({ name: 'Water Sensor 4', temp: state.temps.solarSensor4, tempAdj: sys.equipment.tempSensors.getCalibration('solar4'), binding: 'solarTempAdj4' });
|
|
1137
|
-
}
|
|
1112
|
+
break;
|
|
1113
|
+
case 'waterSensor3':
|
|
1114
|
+
{
|
|
1115
|
+
let temp = obj[prop] !== null ? parseFloat(obj[prop]) : 0;
|
|
1116
|
+
if (isNaN(temp)) return reject(new InvalidEquipmentDataError(`Invalid value for ${prop} ${obj[prop]}`, `Temps:${prop}`, obj[prop]));
|
|
1117
|
+
state.temps.waterSensor3 = sys.equipment.tempSensors.getCalibration('water3') + temp;
|
|
1118
|
+
let body = state.temps.bodies.getItemById(3);
|
|
1119
|
+
if (body.isOn) body.temp = state.temps.waterSensor3;
|
|
1138
1120
|
}
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1121
|
+
break;
|
|
1122
|
+
case 'waterSensor4':
|
|
1123
|
+
{
|
|
1124
|
+
|
|
1125
|
+
let temp = obj[prop] !== null ? parseFloat(obj[prop]) : 0;
|
|
1126
|
+
if (isNaN(temp)) return reject(new InvalidEquipmentDataError(`Invalid value for ${prop} ${obj[prop]}`, `Temps:${prop}`, obj[prop]));
|
|
1127
|
+
state.temps.waterSensor4 = sys.equipment.tempSensors.getCalibration('water4') + temp;
|
|
1128
|
+
let body = state.temps.bodies.getItemById(4);
|
|
1129
|
+
if (body.isOn) body.temp = state.temps.waterSensor4;
|
|
1130
|
+
}
|
|
1131
|
+
break;
|
|
1132
|
+
|
|
1133
|
+
case 'solarSensor1':
|
|
1134
|
+
case 'solar1':
|
|
1135
|
+
case 'solar':
|
|
1136
|
+
{
|
|
1137
|
+
let temp = obj[prop] !== null ? parseFloat(obj[prop]) : 0;
|
|
1138
|
+
if (isNaN(temp)) return reject(new InvalidEquipmentDataError(`Invalid value for ${prop} ${obj[prop]}`, `Temps:${prop}`, obj[prop]));
|
|
1139
|
+
state.temps.solar = sys.equipment.tempSensors.getCalibration('solar1') + temp;
|
|
1143
1140
|
}
|
|
1141
|
+
break;
|
|
1142
|
+
case 'solar2':
|
|
1143
|
+
case 'solarSensor2':
|
|
1144
|
+
{
|
|
1145
|
+
let temp = obj[prop] !== null ? parseFloat(obj[prop]) : 0;
|
|
1146
|
+
if (isNaN(temp)) return reject(new InvalidEquipmentDataError(`Invalid value for ${prop} ${obj[prop]}`, `Temps:${prop}`, obj[prop]));
|
|
1147
|
+
state.temps.solarSensor2 = sys.equipment.tempSensors.getCalibration('solar2') + temp;
|
|
1148
|
+
}
|
|
1149
|
+
break;
|
|
1150
|
+
case 'solar3':
|
|
1151
|
+
case 'solarSensor3':
|
|
1152
|
+
{
|
|
1153
|
+
let temp = obj[prop] !== null ? parseFloat(obj[prop]) : 0;
|
|
1154
|
+
if (isNaN(temp)) return reject(new InvalidEquipmentDataError(`Invalid value for ${prop} ${obj[prop]}`, `Temps:${prop}`, obj[prop]));
|
|
1155
|
+
state.temps.solarSensor3 = sys.equipment.tempSensors.getCalibration('solar3') + temp;
|
|
1156
|
+
}
|
|
1157
|
+
break;
|
|
1158
|
+
case 'solar4':
|
|
1159
|
+
case 'solarSensor4':
|
|
1160
|
+
{
|
|
1161
|
+
let temp = obj[prop] !== null ? parseFloat(obj[prop]) : 0;
|
|
1162
|
+
if (isNaN(temp)) return reject(new InvalidEquipmentDataError(`Invalid value for ${prop} ${obj[prop]}`, `Temps:${prop}`, obj[prop]));
|
|
1163
|
+
state.temps.solarSensor4 = sys.equipment.tempSensors.getCalibration('solar4') + temp;
|
|
1164
|
+
}
|
|
1165
|
+
break;
|
|
1144
1166
|
}
|
|
1145
|
-
|
|
1167
|
+
}
|
|
1168
|
+
sys.board.heaters.syncHeaterStates();
|
|
1169
|
+
resolve(state.temps);
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1172
|
+
public getSensors() {
|
|
1173
|
+
let sensors = [{ name: 'Air Sensor', temp: state.temps.air, tempAdj: sys.equipment.tempSensors.getCalibration('air'), binding: 'airTempAdj' }];
|
|
1174
|
+
if (sys.equipment.shared) {
|
|
1175
|
+
if (sys.equipment.maxBodies > 2)
|
|
1176
|
+
sensors.push({ name: 'Water Sensor 1', temp: state.temps.waterSensor1, tempAdj: sys.equipment.tempSensors.getCalibration('water1'), binding: 'waterTempAdj1' },
|
|
1177
|
+
{ name: 'Water Sensor 2', temp: state.temps.waterSensor2, tempAdj: sys.equipment.tempSensors.getCalibration('water2'), binding: 'waterTempAdj2' },
|
|
1178
|
+
{ name: 'Water Sensor 3', temp: state.temps.waterSensor4, tempAdj: sys.equipment.tempSensors.getCalibration('water3'), binding: 'waterTempAdj3' });
|
|
1179
|
+
else
|
|
1180
|
+
sensors.push({ name: 'Water Sensor', temp: state.temps.waterSensor1, tempAdj: sys.equipment.tempSensors.getCalibration('water1'), binding: 'waterTempAdj1' });
|
|
1181
|
+
if (sys.equipment.maxBodies > 3)
|
|
1182
|
+
sensors.push({ name: 'Water Sensor 4', temp: state.temps.waterSensor4, tempAdj: sys.equipment.tempSensors.getCalibration('water4'), binding: 'waterTempAdj4' });
|
|
1183
|
+
|
|
1184
|
+
if (sys.board.heaters.isSolarInstalled()) {
|
|
1185
|
+
if (sys.equipment.maxBodies > 2) {
|
|
1186
|
+
sensors.push({ name: 'Solar Sensor 1', temp: state.temps.solar, tempAdj: sys.equipment.tempSensors.getCalibration('solar1'), binding: 'solarTempAdj1' },
|
|
1187
|
+
{ name: 'Solar Sensor 2', temp: state.temps.solarSensor2, tempAdj: sys.equipment.tempSensors.getCalibration('solar2'), binding: 'solarTempAdj2' });
|
|
1188
|
+
}
|
|
1189
|
+
else
|
|
1190
|
+
sensors.push({ name: 'Solar Sensor', temp: state.temps.solar, tempAdj: sys.equipment.tempSensors.getCalibration('solar1'), binding: 'solarTempAdj1' });
|
|
1191
|
+
if (sys.equipment.maxBodies > 3)
|
|
1192
|
+
sensors.push({ name: 'Solar Sensor 4', temp: state.temps.solarSensor4, tempAdj: sys.equipment.tempSensors.getCalibration('solar4'), binding: 'solarTempAdj4' });
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
else if (sys.equipment.dual) {
|
|
1196
|
+
sensors.push({ name: 'Water Sensor 1', temp: state.temps.waterSensor1, tempAdj: sys.equipment.tempSensors.getCalibration('water1'), binding: 'waterTempAdj1' },
|
|
1197
|
+
{ name: 'Water Sensor 2', temp: state.temps.waterSensor2, tempAdj: sys.equipment.tempSensors.getCalibration('water2'), binding: 'waterTempAdj2' });
|
|
1198
|
+
if (sys.equipment.maxBodies > 2)
|
|
1199
|
+
sensors.push({ name: 'Water Sensor 3', temp: state.temps.waterSensor3, tempAdj: sys.equipment.tempSensors.getCalibration('water3'), binding: 'waterTempAdj3' });
|
|
1200
|
+
if (sys.equipment.maxBodies > 3)
|
|
1201
|
+
sensors.push({ name: 'Water Sensor 4', temp: state.temps.waterSensor4, tempAdj: sys.equipment.tempSensors.getCalibration('water4'), binding: 'waterTempAdj4' });
|
|
1202
|
+
if (sys.board.heaters.isSolarInstalled()) {
|
|
1203
|
+
sensors.push({ name: 'Solar Sensor 1', temp: state.temps.solar, tempAdj: sys.equipment.tempSensors.getCalibration('solar1'), binding: 'solarTempAdj1' },
|
|
1204
|
+
{ name: 'Solar Sensor 2', temp: state.temps.solarSensor2, tempAdj: sys.equipment.tempSensors.getCalibration('solar2'), binding: 'solarTempAdj2' });
|
|
1205
|
+
if (sys.equipment.maxBodies > 2)
|
|
1206
|
+
sensors.push({ name: 'Solar Sensor 3', temp: state.temps.solarSensor3, tempAdj: sys.equipment.tempSensors.getCalibration('solar3'), binding: 'solarTempAdj3' });
|
|
1207
|
+
if (sys.equipment.maxBodies > 3)
|
|
1208
|
+
sensors.push({ name: 'Solar Sensor 4', temp: state.temps.solarSensor4, tempAdj: sys.equipment.tempSensors.getCalibration('solar4'), binding: 'solarTempAdj4' });
|
|
1209
|
+
}
|
|
1146
1210
|
}
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1211
|
+
else {
|
|
1212
|
+
if (sys.equipment.maxBodies > 1) {
|
|
1213
|
+
sensors.push({ name: 'Water Sensor 1', temp: state.temps.waterSensor1, tempAdj: sys.equipment.tempSensors.getCalibration('water1'), binding: 'waterTempAdj1' },
|
|
1214
|
+
{ name: 'Water Sensor 2', temp: state.temps.waterSensor2, tempAdj: sys.equipment.tempSensors.getCalibration('water2'), binding: 'waterTempAdj2' });
|
|
1215
|
+
if (sys.equipment.maxBodies > 2)
|
|
1216
|
+
sensors.push({ name: 'Water Sensor 3', temp: state.temps.waterSensor3, tempAdj: sys.equipment.tempSensors.getCalibration('water3'), binding: 'waterTempAdj3' });
|
|
1217
|
+
if (sys.equipment.maxBodies > 3)
|
|
1218
|
+
sensors.push({ name: 'Water Sensor 4', temp: state.temps.waterSensor4, tempAdj: sys.equipment.tempSensors.getCalibration('water4'), binding: 'waterTempAdj4' });
|
|
1155
1219
|
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
});
|
|
1171
|
-
}
|
|
1172
|
-
public syncCustomNamesValueMap() {
|
|
1173
|
-
sys.customNames.sortById();
|
|
1174
|
-
sys.board.valueMaps.customNames = new byteValueMap(
|
|
1175
|
-
sys.customNames.get().map((el, idx) => {
|
|
1176
|
-
return [idx + 200, { name: el.name, desc: el.name }];
|
|
1177
|
-
})
|
|
1178
|
-
);
|
|
1220
|
+
if (sys.board.heaters.isSolarInstalled()) {
|
|
1221
|
+
sensors.push({ name: 'Solar Sensor 1', temp: state.temps.solarSensor1, tempAdj: sys.equipment.tempSensors.getCalibration('solar1'), binding: 'solarTempAdj1' },
|
|
1222
|
+
{ name: 'Solar Sensor 2', temp: state.temps.solarSensor2, tempAdj: sys.equipment.tempSensors.getCalibration('solar2'), binding: 'solarTempAdj2' });
|
|
1223
|
+
if (sys.equipment.maxBodies > 2)
|
|
1224
|
+
sensors.push({ name: 'Solar Sensor 3', temp: state.temps.solarSensor3, tempAdj: sys.equipment.tempSensors.getCalibration('solar3'), binding: 'solarTempAdj3' });
|
|
1225
|
+
if (sys.equipment.maxBodies > 3)
|
|
1226
|
+
sensors.push({ name: 'Water Sensor 4', temp: state.temps.solarSensor4, tempAdj: sys.equipment.tempSensors.getCalibration('solar4'), binding: 'solarTempAdj4' });
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
else {
|
|
1230
|
+
sensors.push({ name: 'Water Sensor', temp: state.temps.waterSensor1, tempAdj: sys.equipment.tempSensors.getCalibration('water1'), binding: 'waterTempAdj1' });
|
|
1231
|
+
if (sys.board.heaters.isSolarInstalled())
|
|
1232
|
+
sensors.push({ name: 'Solar Sensor', temp: state.temps.solar, tempAdj: sys.equipment.tempSensors.getCalibration('solar1'), binding: 'solarTempAdj1' });
|
|
1233
|
+
}
|
|
1179
1234
|
}
|
|
1235
|
+
return sensors;
|
|
1236
|
+
}
|
|
1237
|
+
public async setCustomNamesAsync(names: any[]): Promise<CustomNameCollection> {
|
|
1238
|
+
if (!Array.isArray(names)) return Promise.reject(new InvalidEquipmentDataError(`Data is not an array`, 'customNames', names))
|
|
1239
|
+
let arr = [];
|
|
1240
|
+
for (let i = 0; i < names.length; i++) { arr.push(sys.board.system.setCustomNameAsync(names[i])); }
|
|
1241
|
+
return new Promise<CustomNameCollection>(async (resolve, reject) => {
|
|
1242
|
+
try {
|
|
1243
|
+
await Promise.all(arr).catch(err => reject(err));
|
|
1244
|
+
// sys.board.system.syncCustomNamesValueMap(); Each custom name promise is already syncing the bytevalue array
|
|
1245
|
+
|
|
1246
|
+
resolve(sys.customNames);
|
|
1247
|
+
}
|
|
1248
|
+
catch (err) { reject(err); }
|
|
1249
|
+
});
|
|
1250
|
+
}
|
|
1251
|
+
public async setCustomNameAsync(data: any): Promise<CustomName> {
|
|
1252
|
+
return new Promise<CustomName>((resolve, reject) => {
|
|
1253
|
+
let id = parseInt(data.id, 10);
|
|
1254
|
+
if (isNaN(id)) return reject(new InvalidEquipmentIdError('Invalid Custom Name Id', data.id, 'customName'));
|
|
1255
|
+
if (id > sys.equipment.maxCustomNames) return reject(new InvalidEquipmentIdError('Custom Name Id out of range', data.id, 'customName'));
|
|
1256
|
+
let cname = sys.customNames.getItemById(id, true);
|
|
1257
|
+
cname.name = data.name;
|
|
1258
|
+
sys.board.system.syncCustomNamesValueMap();
|
|
1259
|
+
return resolve(cname);
|
|
1260
|
+
});
|
|
1261
|
+
}
|
|
1262
|
+
public syncCustomNamesValueMap() {
|
|
1263
|
+
sys.customNames.sortById();
|
|
1264
|
+
sys.board.valueMaps.customNames = new byteValueMap(
|
|
1265
|
+
sys.customNames.get().map((el, idx) => {
|
|
1266
|
+
return [idx + 200, { name: el.name, desc: el.name }];
|
|
1267
|
+
})
|
|
1268
|
+
);
|
|
1269
|
+
}
|
|
1180
1270
|
}
|
|
1181
1271
|
export class BodyCommands extends BoardCommands {
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1272
|
+
public async restore(rest: { poolConfig: any, poolState: any }, ctx: any, res: RestoreResults): Promise<boolean> {
|
|
1273
|
+
try {
|
|
1274
|
+
// First delete the bodies that should be removed.
|
|
1275
|
+
for (let i = 0; i < ctx.bodies.remove.length; i++) {
|
|
1276
|
+
let body = ctx.bodies.remove[i];
|
|
1185
1277
|
try {
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1278
|
+
sys.bodies.removeItemById(body.id);
|
|
1279
|
+
state.temps.bodies.removeItemById(body.id);
|
|
1280
|
+
res.addModuleSuccess('body', `Remove: ${body.id}-${body.name}`);
|
|
1281
|
+
} catch (err) { res.addModuleError('body', `Remove: ${body.id}-${body.name}: ${err.message}`); }
|
|
1282
|
+
}
|
|
1283
|
+
for (let i = 0; i < ctx.bodies.update.length; i++) {
|
|
1284
|
+
let body = ctx.bodies.update[i];
|
|
1285
|
+
try {
|
|
1286
|
+
await sys.board.bodies.setBodyAsync(body);
|
|
1287
|
+
res.addModuleSuccess('body', `Update: ${body.id}-${body.name}`);
|
|
1288
|
+
} catch (err) { res.addModuleError('body', `Update: ${body.id}-${body.name}: ${err.message}`); }
|
|
1289
|
+
}
|
|
1290
|
+
for (let i = 0; i < ctx.bodies.add.length; i++) {
|
|
1291
|
+
let body = ctx.bodies.add[i];
|
|
1292
|
+
try {
|
|
1293
|
+
// pull a little trick to first add the data then perform the update.
|
|
1294
|
+
sys.bodies.getItemById(body.id, true);
|
|
1295
|
+
await sys.board.bodies.setBodyAsync(body);
|
|
1296
|
+
} catch (err) { res.addModuleError('body', `Add: ${body.id}-${body.name}: ${err.message}`); }
|
|
1297
|
+
}
|
|
1298
|
+
return true;
|
|
1299
|
+
} catch (err) { logger.error(`Error restoring bodies: ${err.message}`); res.addModuleError('system', `Error restoring bodies: ${err.message}`); return false; }
|
|
1300
|
+
}
|
|
1301
|
+
public async validateRestore(rest: { poolConfig: any, poolState: any }): Promise<{ errors: any, warnings: any, add: any, update: any, remove: any}> {
|
|
1302
|
+
try {
|
|
1303
|
+
let ctx = { errors: [], warnings: [], add: [], update: [], remove: [] };
|
|
1304
|
+
// Look at bodies.
|
|
1305
|
+
let cfg = rest.poolConfig;
|
|
1306
|
+
for (let i = 0; i < cfg.bodies.length; i++) {
|
|
1307
|
+
let r = cfg.bodies[i];
|
|
1308
|
+
let c = sys.bodies.find(elem => r.id === elem.id);
|
|
1309
|
+
if (typeof c === 'undefined') ctx.add.push(r);
|
|
1310
|
+
else if (JSON.stringify(c.get()) !== JSON.stringify(r)) ctx.update.push(r);
|
|
1311
|
+
}
|
|
1312
|
+
for (let i = 0; i < sys.bodies.length; i++) {
|
|
1313
|
+
let c = sys.bodies.getItemByIndex(i);
|
|
1314
|
+
let r = cfg.bodies.find(elem => elem.id == c.id);
|
|
1315
|
+
if (typeof r === 'undefined') ctx.remove.push(c.get(true));
|
|
1316
|
+
}
|
|
1317
|
+
return ctx;
|
|
1318
|
+
} catch (err) { logger.error(`Error validating bodies for restore: ${err.message}`); }
|
|
1319
|
+
}
|
|
1320
|
+
public freezeProtectBodyOn: Date;
|
|
1321
|
+
public freezeProtectStart: Date;
|
|
1322
|
+
public async syncFreezeProtection() {
|
|
1323
|
+
try {
|
|
1324
|
+
// Go through all the features and circuits to make sure we have the freeze protect set appropriately. The freeze
|
|
1325
|
+
// flag will have already been set whether this is a Nixie setup or there is an OCP involved.
|
|
1326
|
+
|
|
1327
|
+
// First turn on/off any features that are in our control that should be under our control. If this is an OCP we
|
|
1328
|
+
// do not create features beyond those controlled by the OCP so we don't need to check these in that condition. That is
|
|
1329
|
+
// why it first checks the controller type.
|
|
1330
|
+
let freeze = utils.makeBool(state.freeze);
|
|
1331
|
+
if (sys.controllerType === ControllerType.Nixie) {
|
|
1332
|
+
// If we are a Nixie controller we need to evaluate the current freeze settings against the air temperature.
|
|
1333
|
+
if (typeof state.temps.air !== 'undefined') freeze = state.temps.air <= sys.general.options.freezeThreshold;
|
|
1334
|
+
else freeze = false;
|
|
1335
|
+
|
|
1336
|
+
// We need to know when we first turned the freeze protection on. This is because we will be rotating between pool and spa
|
|
1337
|
+
// on shared body systems when both pool and spa have freeze protection checked.
|
|
1338
|
+
if (state.freeze !== freeze) {
|
|
1339
|
+
this.freezeProtectStart = freeze ? new Date() : undefined;
|
|
1340
|
+
state.freeze = freeze;
|
|
1341
|
+
}
|
|
1342
|
+
for (let i = 0; i < sys.features.length; i++) {
|
|
1343
|
+
let feature = sys.features.getItemByIndex(i);
|
|
1344
|
+
let fstate = state.features.getItemById(feature.id, true);
|
|
1345
|
+
if (!feature.freeze || !feature.isActive === true || feature.master !== 1) {
|
|
1346
|
+
fstate.freezeProtect = false;
|
|
1347
|
+
continue; // This is not affected by freeze conditions.
|
|
1348
|
+
}
|
|
1349
|
+
if (freeze && !fstate.isOn) {
|
|
1350
|
+
// This feature should be on because we are freezing.
|
|
1351
|
+
fstate.freezeProtect = true;
|
|
1352
|
+
await sys.board.features.setFeatureStateAsync(feature.id, true);
|
|
1353
|
+
}
|
|
1354
|
+
else if (!freeze && fstate.freezeProtect) {
|
|
1355
|
+
// This feature was turned on by freeze protection. We need to turn it off because it has warmed up.
|
|
1356
|
+
fstate.freezeProtect = false;
|
|
1357
|
+
await sys.board.features.setFeatureStateAsync(feature.id, false);
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
let bodyRotationChecked = false;
|
|
1362
|
+
for (let i = 0; i < sys.circuits.length; i++) {
|
|
1363
|
+
let circ = sys.circuits.getItemByIndex(i);
|
|
1364
|
+
let cstate = state.circuits.getItemById(circ.id);
|
|
1365
|
+
if (!circ.freeze || !circ.isActive === true || circ.master !== 1) {
|
|
1366
|
+
cstate.freezeProtect = false;
|
|
1367
|
+
continue; // This is not affected by freeze conditions.
|
|
1368
|
+
}
|
|
1369
|
+
if (sys.equipment.shared && freeze && (circ.id === 1 || circ.id === 6)) {
|
|
1370
|
+
// Exit out of here because we already checked the body rotation. We only want to do this once since it can be expensive turning
|
|
1371
|
+
// on a particular body.
|
|
1372
|
+
if (bodyRotationChecked) continue;
|
|
1373
|
+
// These are our body circuits so we need to check to see if they need to be rotated between pool and spa.
|
|
1374
|
+
let pool = circ.id === 6 ? circ : sys.circuits.getItemById(6);
|
|
1375
|
+
let spa = circ.id === 1 ? circ : sys.circuits.getItemById(1);
|
|
1376
|
+
if (pool.freeze && spa.freeze) {
|
|
1377
|
+
// We only need to rotate between pool and spa when they are both checked.
|
|
1378
|
+
let pstate = circ.id === 6 ? cstate : state.circuits.getItemById(6);
|
|
1379
|
+
let sstate = circ.id === 1 ? cstate : state.circuits.getItemById(1);
|
|
1380
|
+
if (!pstate.isOn && !sstate.isOn) {
|
|
1381
|
+
// Neither the pool or spa are on so we will turn on the pool first.
|
|
1382
|
+
pstate.freezeProtect = true;
|
|
1383
|
+
this.freezeProtectBodyOn = new Date();
|
|
1384
|
+
await sys.board.circuits.setCircuitStateAsync(6, true);
|
|
1222
1385
|
}
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
let sstate = circ.id === 1 ? cstate : state.circuits.getItemById(1);
|
|
1242
|
-
if (!pstate.isOn && !sstate.isOn) {
|
|
1243
|
-
// Neither the pool or spa are on so we will turn on the pool first.
|
|
1244
|
-
pstate.freezeProtect = true;
|
|
1245
|
-
this.freezeProtectBodyOn = new Date();
|
|
1246
|
-
await sys.board.circuits.setCircuitStateAsync(6, true);
|
|
1247
|
-
}
|
|
1248
|
-
else {
|
|
1249
|
-
// If neither of the bodies were turned on for freeze protection then we need to ignore this.
|
|
1250
|
-
if (!pstate.freezeProtect && !sstate.freezeProtect) {
|
|
1251
|
-
this.freezeProtectBodyOn = undefined;
|
|
1252
|
-
continue;
|
|
1253
|
-
}
|
|
1254
|
-
|
|
1255
|
-
// One of the two bodies is on so we need to check for the rotation. If it is time to rotate do the rotation.
|
|
1256
|
-
if (typeof this.freezeProtectBodyOn === 'undefined') this.freezeProtectBodyOn = new Date();
|
|
1257
|
-
let dt = new Date().getTime();
|
|
1258
|
-
if (dt - 1000 * 60 * 15 > this.freezeProtectBodyOn.getTime()) {
|
|
1259
|
-
logger.info(`Swapping bodies for freeze protection pool:${pstate.isOn} spa:${sstate.isOn} interval: ${utils.formatDuration(dt - this.freezeProtectBodyOn.getTime() / 1000)}`);
|
|
1260
|
-
// 10 minutes has elapsed so we will be rotating to the other body.
|
|
1261
|
-
if (pstate.isOn) {
|
|
1262
|
-
// The setCircuitState method will handle turning off the pool body.
|
|
1263
|
-
sstate.freezeProtect = true;
|
|
1264
|
-
pstate.freezeProtect = false;
|
|
1265
|
-
await sys.board.circuits.setCircuitStateAsync(1, true);
|
|
1266
|
-
}
|
|
1267
|
-
else {
|
|
1268
|
-
sstate.freezeProtect = false;
|
|
1269
|
-
pstate.freezeProtect = true;
|
|
1270
|
-
await sys.board.circuits.setCircuitStateAsync(6, true);
|
|
1271
|
-
}
|
|
1272
|
-
// Set a new date as this will be our rotation check now.
|
|
1273
|
-
this.freezeProtectBodyOn = new Date();
|
|
1274
|
-
}
|
|
1275
|
-
}
|
|
1276
|
-
}
|
|
1277
|
-
else {
|
|
1278
|
-
// Only this circuit is selected for freeze protection so we don't need any special treatment.
|
|
1279
|
-
cstate.freezeProtect = true;
|
|
1280
|
-
if (!cstate.isOn) await sys.board.circuits.setCircuitStateAsync(circ.id, true);
|
|
1281
|
-
}
|
|
1282
|
-
bodyRotationChecked = true;
|
|
1283
|
-
}
|
|
1284
|
-
else if (freeze && !cstate.isOn) {
|
|
1285
|
-
// This circuit should be on because we are freezing.
|
|
1286
|
-
cstate.freezeProtect = true;
|
|
1287
|
-
await sys.board.features.setFeatureStateAsync(circ.id, true);
|
|
1386
|
+
else {
|
|
1387
|
+
// If neither of the bodies were turned on for freeze protection then we need to ignore this.
|
|
1388
|
+
if (!pstate.freezeProtect && !sstate.freezeProtect) {
|
|
1389
|
+
this.freezeProtectBodyOn = undefined;
|
|
1390
|
+
continue;
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
// One of the two bodies is on so we need to check for the rotation. If it is time to rotate do the rotation.
|
|
1394
|
+
if (typeof this.freezeProtectBodyOn === 'undefined') this.freezeProtectBodyOn = new Date();
|
|
1395
|
+
let dt = new Date().getTime();
|
|
1396
|
+
if (dt - 1000 * 60 * 15 > this.freezeProtectBodyOn.getTime()) {
|
|
1397
|
+
logger.info(`Swapping bodies for freeze protection pool:${pstate.isOn} spa:${sstate.isOn} interval: ${utils.formatDuration(dt - this.freezeProtectBodyOn.getTime() / 1000)}`);
|
|
1398
|
+
// 10 minutes has elapsed so we will be rotating to the other body.
|
|
1399
|
+
if (pstate.isOn) {
|
|
1400
|
+
// The setCircuitState method will handle turning off the pool body.
|
|
1401
|
+
sstate.freezeProtect = true;
|
|
1402
|
+
pstate.freezeProtect = false;
|
|
1403
|
+
await sys.board.circuits.setCircuitStateAsync(1, true);
|
|
1288
1404
|
}
|
|
1289
|
-
else
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1405
|
+
else {
|
|
1406
|
+
sstate.freezeProtect = false;
|
|
1407
|
+
pstate.freezeProtect = true;
|
|
1408
|
+
await sys.board.circuits.setCircuitStateAsync(6, true);
|
|
1293
1409
|
}
|
|
1410
|
+
// Set a new date as this will be our rotation check now.
|
|
1411
|
+
this.freezeProtectBodyOn = new Date();
|
|
1412
|
+
}
|
|
1294
1413
|
}
|
|
1414
|
+
}
|
|
1415
|
+
else {
|
|
1416
|
+
// Only this circuit is selected for freeze protection so we don't need any special treatment.
|
|
1417
|
+
cstate.freezeProtect = true;
|
|
1418
|
+
if (!cstate.isOn) await sys.board.circuits.setCircuitStateAsync(circ.id, true);
|
|
1419
|
+
}
|
|
1420
|
+
bodyRotationChecked = true;
|
|
1421
|
+
}
|
|
1422
|
+
else if (freeze && !cstate.isOn) {
|
|
1423
|
+
// This circuit should be on because we are freezing.
|
|
1424
|
+
cstate.freezeProtect = true;
|
|
1425
|
+
await sys.board.features.setFeatureStateAsync(circ.id, true);
|
|
1295
1426
|
}
|
|
1296
|
-
|
|
1427
|
+
else if (!freeze && cstate.freezeProtect) {
|
|
1428
|
+
// This feature was turned on by freeze protection. We need to turn it off because it has warmed up.
|
|
1429
|
+
await sys.board.circuits.setCircuitStateAsync(circ.id, false);
|
|
1430
|
+
cstate.freezeProtect = false;
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1297
1433
|
}
|
|
1434
|
+
catch (err) { logger.error(`syncFreezeProtection: Error synchronizing freeze protection states`); }
|
|
1435
|
+
}
|
|
1298
1436
|
|
|
1299
1437
|
public async initFilters() {
|
|
1300
1438
|
try {
|
|
@@ -1302,7 +1440,7 @@ export class BodyCommands extends BoardCommands {
|
|
|
1302
1440
|
let sFilter: FilterState;
|
|
1303
1441
|
if (sys.equipment.maxBodies > 0) {
|
|
1304
1442
|
filter = sys.filters.getItemById(1, true, { filterType: 3, name: sys.equipment.shared ? 'Filter' : 'Filter 1' });
|
|
1305
|
-
sFilter = state.filters.getItemById(1, true, { id: 1,
|
|
1443
|
+
sFilter = state.filters.getItemById(1, true, { id: 1, name: filter.name });
|
|
1306
1444
|
filter.isActive = true;
|
|
1307
1445
|
filter.master = sys.board.equipmentMaster;
|
|
1308
1446
|
filter.body = sys.equipment.shared ? sys.board.valueMaps.bodies.transformByName('poolspa') : 0;
|
|
@@ -1432,53 +1570,54 @@ export class BodyCommands extends BoardCommands {
|
|
|
1432
1570
|
sys.board.heaters.syncHeaterStates();
|
|
1433
1571
|
return Promise.resolve(bstate);
|
|
1434
1572
|
}
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1573
|
+
public getHeatSources(bodyId: number) {
|
|
1574
|
+
let heatSources = [];
|
|
1575
|
+
let heatTypes = this.board.heaters.getInstalledHeaterTypes(bodyId);
|
|
1576
|
+
heatSources.push(this.board.valueMaps.heatSources.transformByName('nochange'));
|
|
1577
|
+
if (heatTypes.total > 0) heatSources.push(this.board.valueMaps.heatSources.transformByName('off'));
|
|
1578
|
+
if (heatTypes.gas > 0) heatSources.push(this.board.valueMaps.heatSources.transformByName('heater'));
|
|
1579
|
+
if (heatTypes.mastertemp > 0) heatSources.push(this.board.valueMaps.heatSources.transformByName('mastertemp'));
|
|
1580
|
+
if (heatTypes.solar > 0) {
|
|
1581
|
+
let hm = this.board.valueMaps.heatSources.transformByName('solar');
|
|
1582
|
+
heatSources.push(hm);
|
|
1583
|
+
if (heatTypes.total > 1) heatSources.push(this.board.valueMaps.heatSources.transformByName('solarpref'));
|
|
1584
|
+
}
|
|
1585
|
+
if (heatTypes.heatpump > 0) {
|
|
1586
|
+
let hm = this.board.valueMaps.heatSources.transformByName('heatpump');
|
|
1587
|
+
heatSources.push(hm);
|
|
1588
|
+
if (heatTypes.total > 1) heatSources.push(this.board.valueMaps.heatSources.transformByName('heatpumppref'));
|
|
1589
|
+
}
|
|
1590
|
+
if (heatTypes.ultratemp > 0) {
|
|
1591
|
+
let hm = this.board.valueMaps.heatSources.transformByName('ultratemp');
|
|
1592
|
+
heatSources.push(hm);
|
|
1593
|
+
if (heatTypes.total > 1) heatSources.push(this.board.valueMaps.heatSources.transformByName('ultratemppref'));
|
|
1594
|
+
}
|
|
1595
|
+
return heatSources;
|
|
1596
|
+
}
|
|
1597
|
+
public getHeatModes(bodyId: number) {
|
|
1598
|
+
let heatModes = [];
|
|
1599
|
+
// RKS: 09-26-20 This will need to be overloaded in IntelliCenterBoard when the other heater types are identified. (e.g. ultratemp, hybrid, maxetherm, and mastertemp)
|
|
1600
|
+
heatModes.push(this.board.valueMaps.heatModes.transformByName('off')); // In IC fw 1.047 off is no longer 0.
|
|
1601
|
+
let heatTypes = this.board.heaters.getInstalledHeaterTypes(bodyId);
|
|
1602
|
+
if (heatTypes.gas > 0) heatModes.push(this.board.valueMaps.heatModes.transformByName('heater'));
|
|
1603
|
+
if (heatTypes.mastertemp > 0) heatModes.push(this.board.valueMaps.heatModes.transformByName('mastertemp'));
|
|
1604
|
+
if (heatTypes.solar > 0) {
|
|
1605
|
+
let hm = this.board.valueMaps.heatModes.transformByName('solar');
|
|
1606
|
+
heatModes.push(hm);
|
|
1607
|
+
if (heatTypes.total > 1) heatModes.push(this.board.valueMaps.heatModes.transformByName('solarpref'));
|
|
1608
|
+
}
|
|
1609
|
+
if (heatTypes.heatpump > 0) {
|
|
1610
|
+
let hm = this.board.valueMaps.heatModes.transformByName('heatpump');
|
|
1611
|
+
heatModes.push(hm);
|
|
1612
|
+
if (heatTypes.total > 1) heatModes.push(this.board.valueMaps.heatModes.transformByName('heatpumppref'));
|
|
1613
|
+
}
|
|
1614
|
+
if (heatTypes.ultratemp > 0) {
|
|
1615
|
+
let hm = this.board.valueMaps.heatModes.transformByName('ultratemp');
|
|
1616
|
+
heatModes.push(hm);
|
|
1617
|
+
if (heatTypes.total > 1) heatModes.push(this.board.valueMaps.heatModes.transformByName('ultratemppref'));
|
|
1618
|
+
}
|
|
1619
|
+
return heatModes;
|
|
1620
|
+
}
|
|
1482
1621
|
public getPoolStates(): BodyTempState[] {
|
|
1483
1622
|
let arrPools = [];
|
|
1484
1623
|
for (let i = 0; i < state.temps.bodies.length; i++) {
|
|
@@ -1546,6 +1685,56 @@ export class BodyCommands extends BoardCommands {
|
|
|
1546
1685
|
}
|
|
1547
1686
|
}
|
|
1548
1687
|
export class PumpCommands extends BoardCommands {
|
|
1688
|
+
public async restore(rest: { poolConfig: any, poolState: any }, ctx: any, res: RestoreResults): Promise<boolean> {
|
|
1689
|
+
try {
|
|
1690
|
+
// First delete the pumps that should be removed.
|
|
1691
|
+
for (let i = 0; i < ctx.pumps.remove.length; i++) {
|
|
1692
|
+
let p = ctx.pumps.remove[i];
|
|
1693
|
+
try {
|
|
1694
|
+
await sys.board.pumps.deletePumpAsync(p);
|
|
1695
|
+
res.addModuleSuccess('pump', `Remove: ${p.id}-${p.name}`);
|
|
1696
|
+
} catch (err) { res.addModuleError('pump', `Remove: ${p.id}-${p.name}: ${err.message}`); }
|
|
1697
|
+
}
|
|
1698
|
+
for (let i = 0; i < ctx.pumps.update.length; i++) {
|
|
1699
|
+
let p = ctx.pumps.update[i];
|
|
1700
|
+
try {
|
|
1701
|
+
await sys.board.pumps.setPumpAsync(p);
|
|
1702
|
+
res.addModuleSuccess('pump', `Update: ${p.id}-${p.name}`);
|
|
1703
|
+
} catch (err) { res.addModuleError('pump', `Update: ${p.id}-${p.name}: ${err.message}`); }
|
|
1704
|
+
}
|
|
1705
|
+
for (let i = 0; i < ctx.pumps.add.length; i++) {
|
|
1706
|
+
let p = ctx.pumps.add[i];
|
|
1707
|
+
try {
|
|
1708
|
+
// pull a little trick to first add the data then perform the update. This way we won't get a new id or
|
|
1709
|
+
// it won't error out.
|
|
1710
|
+
sys.pumps.getItemById(p, true);
|
|
1711
|
+
await sys.board.pumps.setPumpAsync(p);
|
|
1712
|
+
res.addModuleSuccess('pump', `Add: ${p.id}-${p.name}`);
|
|
1713
|
+
} catch (err) { res.addModuleError('pump', `Add: ${p.id}-${p.name}: ${err.message}`); }
|
|
1714
|
+
}
|
|
1715
|
+
return true;
|
|
1716
|
+
} catch (err) { logger.error(`Error restoring pumps: ${err.message}`); res.addModuleError('system', `Error restoring pumps: ${err.message}`); return false; }
|
|
1717
|
+
}
|
|
1718
|
+
public async validateRestore(rest: { poolConfig: any, poolState: any }): Promise<{ errors: any, warnings: any, add: any, update: any, remove: any }> {
|
|
1719
|
+
try {
|
|
1720
|
+
let ctx = { errors: [], warnings: [], add: [], update: [], remove: [] };
|
|
1721
|
+
// Look at pumps.
|
|
1722
|
+
let cfg = rest.poolConfig;
|
|
1723
|
+
for (let i = 0; i < cfg.pumps.length; i++) {
|
|
1724
|
+
let r = cfg.pumps[i];
|
|
1725
|
+
let c = sys.pumps.find(elem => r.id === elem.id);
|
|
1726
|
+
if (typeof c === 'undefined') ctx.add.push(r);
|
|
1727
|
+
else if (JSON.stringify(c.get()) !== JSON.stringify(r)) ctx.update.push(r);
|
|
1728
|
+
}
|
|
1729
|
+
for (let i = 0; i < sys.pumps.length; i++) {
|
|
1730
|
+
let c = sys.pumps.getItemByIndex(i);
|
|
1731
|
+
let r = cfg.pumps.find(elem => elem.id == c.id);
|
|
1732
|
+
if (typeof r === 'undefined') ctx.remove.push(c.get(true));
|
|
1733
|
+
}
|
|
1734
|
+
return ctx;
|
|
1735
|
+
} catch (err) { logger.error(`Error validating pumps for restore: ${err.message}`); }
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1549
1738
|
public getPumpTypes() { return this.board.valueMaps.pumpTypes.toArray(); }
|
|
1550
1739
|
public getCircuitUnits(pump?: Pump) {
|
|
1551
1740
|
if (typeof pump === 'undefined')
|
|
@@ -1610,14 +1799,15 @@ export class PumpCommands extends BoardCommands {
|
|
|
1610
1799
|
// and props that aren't for this pump type
|
|
1611
1800
|
let _id = pump.id;
|
|
1612
1801
|
if (pump.type !== pumpType || pumpType === 0) {
|
|
1613
|
-
|
|
1802
|
+
let _p = pump.get(true);
|
|
1803
|
+
// const _isVirtual = typeof _p.isVirtual !== 'undefined' ? _p.isVirtual : false;
|
|
1614
1804
|
sys.pumps.removeItemById(_id);
|
|
1615
|
-
|
|
1616
|
-
if (_isVirtual) {
|
|
1805
|
+
pump = sys.pumps.getItemById(_id, true);
|
|
1806
|
+
/* if (_isVirtual) {
|
|
1617
1807
|
// pump.isActive = true;
|
|
1618
1808
|
// pump.isVirtual = true;
|
|
1619
1809
|
pump.master = 1;
|
|
1620
|
-
}
|
|
1810
|
+
} */
|
|
1621
1811
|
state.pumps.removeItemById(pump.id);
|
|
1622
1812
|
pump.type = pumpType;
|
|
1623
1813
|
let type = sys.board.valueMaps.pumpTypes.transform(pumpType);
|
|
@@ -1669,174 +1859,328 @@ export class PumpCommands extends BoardCommands {
|
|
|
1669
1859
|
}
|
|
1670
1860
|
}
|
|
1671
1861
|
export class CircuitCommands extends BoardCommands {
|
|
1672
|
-
|
|
1862
|
+
public async restore(rest: { poolConfig: any, poolState: any }, ctx: any, res: RestoreResults): Promise<boolean> {
|
|
1863
|
+
try {
|
|
1864
|
+
// First delete the circuit/lightGroups that should be removed.
|
|
1865
|
+
for (let i = 0; i < ctx.circuitGroups.remove.length; i++) {
|
|
1866
|
+
let c = ctx.circuitGroups.remove[i];
|
|
1673
1867
|
try {
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
}
|
|
1681
|
-
}
|
|
1682
|
-
} catch (err) { logger.error(`syncCircuitRelayStates: Error synchronizing circuit relays ${err.message}`); }
|
|
1683
|
-
}
|
|
1684
|
-
public syncVirtualCircuitStates() {
|
|
1868
|
+
await sys.board.circuits.deleteCircuitGroupAsync(c);
|
|
1869
|
+
res.addModuleSuccess('circuitGroup', `Remove: ${c.id}-${c.name}`);
|
|
1870
|
+
} catch (err) { res.addModuleError('circuitGroup', `Remove: ${c.id}-${c.name}: ${err.message}`); }
|
|
1871
|
+
}
|
|
1872
|
+
for (let i = 0; i < ctx.lightGroups.remove.length; i++) {
|
|
1873
|
+
let c = ctx.lightGroups.remove[i];
|
|
1685
1874
|
try {
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
// This also removes virtual circuits depending on whether heaters exsits on the bodies. Not sure why we are doing this
|
|
1693
|
-
// as the body data contains whether a body is heated or not. Perhapse some attached interface is using
|
|
1694
|
-
// the virtual circuit list as a means to determine whether solar is available. That is totally flawed if that is the case.
|
|
1695
|
-
for (let i = 0; i < arrCircuits.length; i++) {
|
|
1696
|
-
let vc = arrCircuits[i];
|
|
1697
|
-
let remove = false;
|
|
1698
|
-
let bState = false;
|
|
1699
|
-
let cstate: VirtualCircuitState = null;
|
|
1700
|
-
switch (vc.name) {
|
|
1701
|
-
case 'poolHeater':
|
|
1702
|
-
// If any pool is heating up.
|
|
1703
|
-
remove = true;
|
|
1704
|
-
for (let j = 0; j < poolStates.length; j++) {
|
|
1705
|
-
if (poolStates[j].heaterOptions.total > 0) remove = false;
|
|
1706
|
-
}
|
|
1707
|
-
if (!remove) {
|
|
1708
|
-
// Determine whether the pool heater is on.
|
|
1709
|
-
for (let j = 0; j < poolStates.length; j++)
|
|
1710
|
-
if (sys.board.valueMaps.heatStatus.getName(poolStates[j].heatStatus) === 'heater') bState = true;
|
|
1711
|
-
}
|
|
1712
|
-
break;
|
|
1713
|
-
case 'spaHeater':
|
|
1714
|
-
remove = true;
|
|
1715
|
-
for (let j = 0; j < spaStates.length; j++) {
|
|
1716
|
-
if (spaStates[j].heaterOptions.total > 0) remove = false;
|
|
1717
|
-
}
|
|
1718
|
-
if (!remove) {
|
|
1719
|
-
// Determine whether the spa heater is on.
|
|
1720
|
-
for (let j = 0; j < spaStates.length; j++) {
|
|
1721
|
-
if (sys.board.valueMaps.heatStatus.getName(spaStates[j].heatStatus) === 'heater') bState = true;
|
|
1722
|
-
}
|
|
1723
|
-
}
|
|
1724
|
-
break;
|
|
1725
|
-
case 'freeze':
|
|
1726
|
-
// If freeze protection has been turned on.
|
|
1727
|
-
bState = state.freeze;
|
|
1728
|
-
break;
|
|
1729
|
-
case 'poolSpa':
|
|
1730
|
-
// If any pool or spa is on
|
|
1731
|
-
for (let j = 0; j < poolStates.length && !bState; j++) {
|
|
1732
|
-
if (poolStates[j].isOn) bState = true;
|
|
1733
|
-
}
|
|
1734
|
-
for (let j = 0; j < spaStates.length && !bState; j++) {
|
|
1735
|
-
if (spaStates[j].isOn) bState = true;
|
|
1736
|
-
}
|
|
1737
|
-
break;
|
|
1738
|
-
case 'solarHeat':
|
|
1739
|
-
case 'solar':
|
|
1740
|
-
// If solar is on for any body
|
|
1741
|
-
remove = true;
|
|
1742
|
-
for (let j = 0; j < poolStates.length; j++) {
|
|
1743
|
-
if (poolStates[j].heaterOptions.solar + poolStates[j].heaterOptions.heatpump > 0) remove = false;
|
|
1744
|
-
}
|
|
1745
|
-
if (remove) {
|
|
1746
|
-
for (let j = 0; j < spaStates.length; j++) {
|
|
1747
|
-
if (spaStates[j].heaterOptions.solar + spaStates[j].heaterOptions.heatpump > 0) remove = false;
|
|
1748
|
-
}
|
|
1749
|
-
}
|
|
1750
|
-
if (!remove) {
|
|
1751
|
-
for (let j = 0; j < poolStates.length && !bState; j++) {
|
|
1752
|
-
if (sys.board.valueMaps.heatStatus.getName(poolStates[j].heatStatus) === 'solar') bState = true;
|
|
1753
|
-
}
|
|
1754
|
-
for (let j = 0; j < spaStates.length && !bState; j++) {
|
|
1755
|
-
if (sys.board.valueMaps.heatStatus.getName(spaStates[j].heatStatus) === 'solar') bState = true;
|
|
1756
|
-
}
|
|
1757
|
-
}
|
|
1758
|
-
break;
|
|
1759
|
-
case 'heater':
|
|
1760
|
-
remove = true;
|
|
1761
|
-
for (let j = 0; j < poolStates.length; j++) {
|
|
1762
|
-
if (poolStates[j].heaterOptions.total > 0) remove = false;
|
|
1763
|
-
}
|
|
1764
|
-
if (remove) {
|
|
1765
|
-
for (let j = 0; j < spaStates.length; j++) {
|
|
1766
|
-
if (spaStates[j].heaterOptions.total > 0) remove = false;
|
|
1767
|
-
}
|
|
1768
|
-
}
|
|
1769
|
-
if (!remove) {
|
|
1770
|
-
for (let j = 0; j < poolStates.length && !bState; j++) {
|
|
1771
|
-
let heat = sys.board.valueMaps.heatStatus.getName(poolStates[j].heatStatus);
|
|
1772
|
-
if (heat !== 'off') bState = true;
|
|
1773
|
-
}
|
|
1774
|
-
for (let j = 0; j < spaStates.length && !bState; j++) {
|
|
1775
|
-
let heat = sys.board.valueMaps.heatStatus.getName(spaStates[j].heatStatus);
|
|
1776
|
-
if (heat !== 'off') bState = true;
|
|
1777
|
-
}
|
|
1778
|
-
}
|
|
1779
|
-
break;
|
|
1780
|
-
default:
|
|
1781
|
-
remove = true;
|
|
1782
|
-
break;
|
|
1783
|
-
}
|
|
1784
|
-
if (remove)
|
|
1785
|
-
state.virtualCircuits.removeItemById(vc.val);
|
|
1786
|
-
else {
|
|
1787
|
-
cstate = state.virtualCircuits.getItemById(vc.val, true);
|
|
1788
|
-
if (cstate !== null) {
|
|
1789
|
-
cstate.isOn = bState;
|
|
1790
|
-
cstate.type = vc.val;
|
|
1791
|
-
cstate.name = vc.desc;
|
|
1792
|
-
}
|
|
1793
|
-
}
|
|
1794
|
-
}
|
|
1795
|
-
} catch (err) { logger.error(`Error syncronizing virtual circuits`); }
|
|
1796
|
-
}
|
|
1797
|
-
public async setCircuitStateAsync(id: number, val: boolean): Promise<ICircuitState> {
|
|
1798
|
-
sys.board.suspendStatus(true);
|
|
1875
|
+
await sys.board.circuits.deleteLightGroupAsync(c);
|
|
1876
|
+
res.addModuleSuccess('lightGroup', `Remove: ${c.id}-${c.name}`);
|
|
1877
|
+
} catch (err) { res.addModuleError('lightGroup', `Remove: ${c.id}-${c.name}: ${err.message}`); }
|
|
1878
|
+
}
|
|
1879
|
+
for (let i = 0; i < ctx.circuits.remove.length; i++) {
|
|
1880
|
+
let c = ctx.circuits.remove[i];
|
|
1799
1881
|
try {
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1882
|
+
await sys.board.circuits.deleteCircuitAsync(c);
|
|
1883
|
+
res.addModuleSuccess('circuit', `Remove: ${c.id}-${c.name}`);
|
|
1884
|
+
} catch (err) { res.addModuleError('circuit', `Remove: ${c.id}-${c.name}: ${err.message}`); }
|
|
1885
|
+
}
|
|
1886
|
+
for (let i = 0; i < ctx.circuits.add.length; i++) {
|
|
1887
|
+
let c = ctx.circuits.add[i];
|
|
1888
|
+
try {
|
|
1889
|
+
await sys.board.circuits.setCircuitAsync(c);
|
|
1890
|
+
res.addModuleSuccess('circuit', `Add: ${c.id}-${c.name}`);
|
|
1891
|
+
} catch (err) { res.addModuleError('circuit', `Add: ${c.id}-${c.name}: ${err.message}`); }
|
|
1892
|
+
}
|
|
1893
|
+
for (let i = 0; i < ctx.circuitGroups.add.length; i++) {
|
|
1894
|
+
let c = ctx.circuitGroups.add[i];
|
|
1895
|
+
try {
|
|
1896
|
+
await sys.board.circuits.setCircuitGroupAsync(c);
|
|
1897
|
+
res.addModuleSuccess('circuitGroup', `Add: ${c.id}-${c.name}`);
|
|
1898
|
+
} catch (err) { res.addModuleError('circuitGroup', `Add: ${c.id}-${c.name}: ${err.message}`); }
|
|
1899
|
+
}
|
|
1900
|
+
for (let i = 0; i < ctx.lightGroups.add.length; i++) {
|
|
1901
|
+
let c = ctx.lightGroups.add[i];
|
|
1902
|
+
try {
|
|
1903
|
+
await sys.board.circuits.setLightGroupAsync(c);
|
|
1904
|
+
res.addModuleSuccess('lightGroup', `Add: ${c.id}-${c.name}`);
|
|
1905
|
+
} catch (err) { res.addModuleError('lightGroup', `Add: ${c.id}-${c.name}: ${err.message}`); }
|
|
1906
|
+
}
|
|
1907
|
+
for (let i = 0; i < ctx.circuits.update.length; i++) {
|
|
1908
|
+
let c = ctx.circuits.update[i];
|
|
1909
|
+
try {
|
|
1910
|
+
await sys.board.circuits.setCircuitAsync(c);
|
|
1911
|
+
res.addModuleSuccess('circuit', `Update: ${c.id}-${c.name}`);
|
|
1912
|
+
} catch (err) { res.addModuleError('circuit', `Update: ${c.id}-${c.name}: ${err.message}`); }
|
|
1913
|
+
}
|
|
1914
|
+
for (let i = 0; i < ctx.circuitGroups.update.length; i++) {
|
|
1915
|
+
let c = ctx.circuitGroups.update[i];
|
|
1916
|
+
try {
|
|
1917
|
+
await sys.board.circuits.setCircuitGroupAsync(c);
|
|
1918
|
+
res.addModuleSuccess('circuitGroup', `Update: ${c.id}-${c.name}`);
|
|
1919
|
+
} catch (err) { res.addModuleError('circuitGroup', `Update: ${c.id}-${c.name}: ${err.message}`); }
|
|
1920
|
+
}
|
|
1921
|
+
for (let i = 0; i < ctx.lightGroups.add.length; i++) {
|
|
1922
|
+
let c = ctx.lightGroups.update[i];
|
|
1923
|
+
try {
|
|
1924
|
+
await sys.board.circuits.setLightGroupAsync(c);
|
|
1925
|
+
res.addModuleSuccess('lightGroup', `Update: ${c.id}-${c.name}`);
|
|
1926
|
+
} catch (err) { res.addModuleError('lightGroup', `Update: ${c.id}-${c.name}: ${err.message}`); }
|
|
1927
|
+
}
|
|
1928
|
+
return true;
|
|
1929
|
+
} catch (err) { logger.error(`Error restoring circuits: ${err.message}`); res.addModuleError('system', `Error restoring circuits/features: ${err.message}`); return false; }
|
|
1930
|
+
}
|
|
1931
|
+
public async validateRestore(rest: { poolConfig: any, poolState: any }, ctxRoot): Promise<boolean> {
|
|
1932
|
+
try {
|
|
1933
|
+
let ctx = { errors: [], warnings: [], add: [], update: [], remove: [] };
|
|
1934
|
+
// Look at circuits.
|
|
1935
|
+
let cfg = rest.poolConfig;
|
|
1936
|
+
for (let i = 0; i < cfg.circuits.length; i++) {
|
|
1937
|
+
let r = cfg.circuits[i];
|
|
1938
|
+
let c = sys.circuits.find(elem => r.id === elem.id);
|
|
1939
|
+
if (typeof c === 'undefined') ctx.add.push(r);
|
|
1940
|
+
else if (JSON.stringify(c.get()) !== JSON.stringify(r)) ctx.update.push(r);
|
|
1941
|
+
}
|
|
1942
|
+
for (let i = 0; i < sys.circuits.length; i++) {
|
|
1943
|
+
let c = sys.circuits.getItemByIndex(i);
|
|
1944
|
+
let r = cfg.circuits.find(elem => elem.id == c.id);
|
|
1945
|
+
if (typeof r === 'undefined') ctx.remove.push(c.get(true));
|
|
1946
|
+
}
|
|
1947
|
+
ctxRoot.circuits = ctx;
|
|
1948
|
+
ctx = { errors: [], warnings: [], add: [], update: [], remove: [] };
|
|
1949
|
+
for (let i = 0; i < cfg.circuitGroups.length; i++) {
|
|
1950
|
+
let r = cfg.circuitGroups[i];
|
|
1951
|
+
let c = sys.circuitGroups.find(elem => r.id === elem.id);
|
|
1952
|
+
if (typeof c === 'undefined') ctx.add.push(r);
|
|
1953
|
+
else if (JSON.stringify(c.get()) !== JSON.stringify(r)) ctx.update.push(r);
|
|
1954
|
+
}
|
|
1955
|
+
for (let i = 0; i < sys.circuitGroups.length; i++) {
|
|
1956
|
+
let c = sys.circuitGroups.getItemByIndex(i);
|
|
1957
|
+
let r = cfg.circuitGroups.find(elem => elem.id == c.id);
|
|
1958
|
+
if (typeof r === 'undefined') ctx.remove.push(c.get(true));
|
|
1959
|
+
}
|
|
1960
|
+
ctxRoot.circuitGroups = ctx;
|
|
1961
|
+
ctx = { errors: [], warnings: [], add: [], update: [], remove: [] };
|
|
1962
|
+
for (let i = 0; i < cfg.lightGroups.length; i++) {
|
|
1963
|
+
let r = cfg.lightGroups[i];
|
|
1964
|
+
let c = sys.lightGroups.find(elem => r.id === elem.id);
|
|
1965
|
+
if (typeof c === 'undefined') ctx.add.push(r);
|
|
1966
|
+
else if (JSON.stringify(c.get()) !== JSON.stringify(r)) ctx.update.push(r);
|
|
1967
|
+
}
|
|
1968
|
+
for (let i = 0; i < sys.lightGroups.length; i++) {
|
|
1969
|
+
let c = sys.lightGroups.getItemByIndex(i);
|
|
1970
|
+
let r = cfg.lightGroups.find(elem => elem.id == c.id);
|
|
1971
|
+
if (typeof r === 'undefined') ctx.remove.push(c.get(true));
|
|
1972
|
+
}
|
|
1973
|
+
ctxRoot.lightGroups = ctx;
|
|
1974
|
+
return true;
|
|
1975
|
+
} catch (err) { logger.error(`Error validating circuits for restore: ${err.message}`); }
|
|
1976
|
+
}
|
|
1977
|
+
public async checkEggTimerExpirationAsync() {
|
|
1978
|
+
// turn off any circuits that have reached their egg timer;
|
|
1979
|
+
// Nixie circuits we have 100% control over;
|
|
1980
|
+
// but features/cg/lg may override OCP control
|
|
1981
|
+
try {
|
|
1982
|
+
for (let i = 0; i < sys.circuits.length; i++) {
|
|
1983
|
+
let c = sys.circuits.getItemByIndex(i);
|
|
1984
|
+
let cstate = state.circuits.getItemByIndex(i);
|
|
1985
|
+
if (!cstate.isActive || !cstate.isOn) continue;
|
|
1986
|
+
if (c.master === 1) {
|
|
1987
|
+
await ncp.circuits.checkCircuitEggTimerExpirationAsync(cstate);
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
for (let i = 0; i < sys.features.length; i++) {
|
|
1991
|
+
let fstate = state.features.getItemByIndex(i);
|
|
1992
|
+
if (!fstate.isActive || !fstate.isOn) continue;
|
|
1993
|
+
if (fstate.endTime.toDate() < new Timestamp().toDate()) {
|
|
1994
|
+
await sys.board.circuits.setCircuitStateAsync(fstate.id, false);
|
|
1995
|
+
fstate.emitEquipmentChange();
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
for (let i = 0; i < sys.circuitGroups.length; i++) {
|
|
1999
|
+
let cgstate = state.circuitGroups.getItemByIndex(i);
|
|
2000
|
+
if (!cgstate.isActive || !cgstate.isOn) continue;
|
|
2001
|
+
if (cgstate.endTime.toDate() < new Timestamp().toDate()) {
|
|
2002
|
+
await sys.board.circuits.setCircuitGroupStateAsync(cgstate.id, false);
|
|
2003
|
+
cgstate.emitEquipmentChange();
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
for (let i = 0; i < sys.lightGroups.length; i++) {
|
|
2007
|
+
let lgstate = state.lightGroups.getItemByIndex(i);
|
|
2008
|
+
if (!lgstate.isActive || !lgstate.isOn) continue;
|
|
2009
|
+
if (lgstate.endTime.toDate() < new Timestamp().toDate()) {
|
|
2010
|
+
await sys.board.circuits.setLightGroupStateAsync(lgstate.id, false);
|
|
2011
|
+
lgstate.emitEquipmentChange();
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
} catch (err) { logger.error(`checkEggTimerExpiration: Error synchronizing circuit relays ${err.message}`); }
|
|
2015
|
+
}
|
|
2016
|
+
public async syncCircuitRelayStates() {
|
|
2017
|
+
try {
|
|
2018
|
+
for (let i = 0; i < sys.circuits.length; i++) {
|
|
2019
|
+
// Run through all the controlled circuits to see whether they should be triggered or not.
|
|
2020
|
+
let circ = sys.circuits.getItemByIndex(i);
|
|
2021
|
+
if (circ.master === 1 && circ.isActive) {
|
|
2022
|
+
let cstate = state.circuits.getItemById(circ.id);
|
|
2023
|
+
if (cstate.isOn) await ncp.circuits.setCircuitStateAsync(cstate, cstate.isOn);
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
} catch (err) { logger.error(`syncCircuitRelayStates: Error synchronizing circuit relays ${err.message}`); }
|
|
2027
|
+
}
|
|
2028
|
+
public syncVirtualCircuitStates() {
|
|
2029
|
+
try {
|
|
2030
|
+
let arrCircuits = sys.board.valueMaps.virtualCircuits.toArray();
|
|
2031
|
+
let poolStates = sys.board.bodies.getPoolStates();
|
|
2032
|
+
let spaStates = sys.board.bodies.getSpaStates();
|
|
2033
|
+
// The following should work for all board types if the virtualCiruit valuemaps use common names. The circuit ids can be
|
|
2034
|
+
// different as well as the descriptions but these should have common names since they are all derived from existing states.
|
|
2035
|
+
|
|
2036
|
+
// This also removes virtual circuits depending on whether heaters exsits on the bodies. Not sure why we are doing this
|
|
2037
|
+
// as the body data contains whether a body is heated or not. Perhapse some attached interface is using
|
|
2038
|
+
// the virtual circuit list as a means to determine whether solar is available. That is totally flawed if that is the case.
|
|
2039
|
+
for (let i = 0; i < arrCircuits.length; i++) {
|
|
2040
|
+
let vc = arrCircuits[i];
|
|
2041
|
+
let remove = false;
|
|
2042
|
+
let bState = false;
|
|
2043
|
+
let cstate: VirtualCircuitState = null;
|
|
2044
|
+
switch (vc.name) {
|
|
2045
|
+
case 'poolHeater':
|
|
2046
|
+
// If any pool is heating up.
|
|
2047
|
+
remove = true;
|
|
2048
|
+
for (let j = 0; j < poolStates.length; j++) {
|
|
2049
|
+
if (poolStates[j].heaterOptions.total > 0) remove = false;
|
|
2050
|
+
}
|
|
2051
|
+
if (!remove) {
|
|
2052
|
+
// Determine whether the pool heater is on.
|
|
2053
|
+
for (let j = 0; j < poolStates.length; j++)
|
|
2054
|
+
if (sys.board.valueMaps.heatStatus.getName(poolStates[j].heatStatus) === 'heater') bState = true;
|
|
2055
|
+
}
|
|
2056
|
+
break;
|
|
2057
|
+
case 'spaHeater':
|
|
2058
|
+
remove = true;
|
|
2059
|
+
for (let j = 0; j < spaStates.length; j++) {
|
|
2060
|
+
if (spaStates[j].heaterOptions.total > 0) remove = false;
|
|
2061
|
+
}
|
|
2062
|
+
if (!remove) {
|
|
2063
|
+
// Determine whether the spa heater is on.
|
|
2064
|
+
for (let j = 0; j < spaStates.length; j++) {
|
|
2065
|
+
if (sys.board.valueMaps.heatStatus.getName(spaStates[j].heatStatus) === 'heater') bState = true;
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
break;
|
|
2069
|
+
case 'freeze':
|
|
2070
|
+
// If freeze protection has been turned on.
|
|
2071
|
+
bState = state.freeze;
|
|
2072
|
+
break;
|
|
2073
|
+
case 'poolSpa':
|
|
2074
|
+
// If any pool or spa is on
|
|
2075
|
+
for (let j = 0; j < poolStates.length && !bState; j++) {
|
|
2076
|
+
if (poolStates[j].isOn) bState = true;
|
|
2077
|
+
}
|
|
2078
|
+
for (let j = 0; j < spaStates.length && !bState; j++) {
|
|
2079
|
+
if (spaStates[j].isOn) bState = true;
|
|
2080
|
+
}
|
|
2081
|
+
break;
|
|
2082
|
+
case 'solarHeat':
|
|
2083
|
+
case 'solar':
|
|
2084
|
+
// If solar is on for any body
|
|
2085
|
+
remove = true;
|
|
2086
|
+
for (let j = 0; j < poolStates.length; j++) {
|
|
2087
|
+
if (poolStates[j].heaterOptions.solar + poolStates[j].heaterOptions.heatpump > 0) remove = false;
|
|
2088
|
+
}
|
|
2089
|
+
if (remove) {
|
|
2090
|
+
for (let j = 0; j < spaStates.length; j++) {
|
|
2091
|
+
if (spaStates[j].heaterOptions.solar + spaStates[j].heaterOptions.heatpump > 0) remove = false;
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
if (!remove) {
|
|
2095
|
+
for (let j = 0; j < poolStates.length && !bState; j++) {
|
|
2096
|
+
if (sys.board.valueMaps.heatStatus.getName(poolStates[j].heatStatus) === 'solar') bState = true;
|
|
2097
|
+
}
|
|
2098
|
+
for (let j = 0; j < spaStates.length && !bState; j++) {
|
|
2099
|
+
if (sys.board.valueMaps.heatStatus.getName(spaStates[j].heatStatus) === 'solar') bState = true;
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
break;
|
|
2103
|
+
case 'heater':
|
|
2104
|
+
remove = true;
|
|
2105
|
+
for (let j = 0; j < poolStates.length; j++) {
|
|
2106
|
+
if (poolStates[j].heaterOptions.total > 0) remove = false;
|
|
2107
|
+
}
|
|
2108
|
+
if (remove) {
|
|
2109
|
+
for (let j = 0; j < spaStates.length; j++) {
|
|
2110
|
+
if (spaStates[j].heaterOptions.total > 0) remove = false;
|
|
2111
|
+
}
|
|
1825
2112
|
}
|
|
1826
|
-
if (
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
2113
|
+
if (!remove) {
|
|
2114
|
+
for (let j = 0; j < poolStates.length && !bState; j++) {
|
|
2115
|
+
let heat = sys.board.valueMaps.heatStatus.getName(poolStates[j].heatStatus);
|
|
2116
|
+
if (heat !== 'off') bState = true;
|
|
2117
|
+
}
|
|
2118
|
+
for (let j = 0; j < spaStates.length && !bState; j++) {
|
|
2119
|
+
let heat = sys.board.valueMaps.heatStatus.getName(spaStates[j].heatStatus);
|
|
2120
|
+
if (heat !== 'off') bState = true;
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
break;
|
|
2124
|
+
default:
|
|
2125
|
+
remove = true;
|
|
2126
|
+
break;
|
|
2127
|
+
}
|
|
2128
|
+
if (remove)
|
|
2129
|
+
state.virtualCircuits.removeItemById(vc.val);
|
|
2130
|
+
else {
|
|
2131
|
+
cstate = state.virtualCircuits.getItemById(vc.val, true);
|
|
2132
|
+
if (cstate !== null) {
|
|
2133
|
+
cstate.isOn = bState;
|
|
2134
|
+
cstate.type = vc.val;
|
|
2135
|
+
cstate.name = vc.desc;
|
|
2136
|
+
}
|
|
1832
2137
|
}
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
2138
|
+
}
|
|
2139
|
+
} catch (err) { logger.error(`Error syncronizing virtual circuits`); }
|
|
2140
|
+
}
|
|
2141
|
+
public async setCircuitStateAsync(id: number, val: boolean): Promise<ICircuitState> {
|
|
2142
|
+
sys.board.suspendStatus(true);
|
|
2143
|
+
try {
|
|
2144
|
+
// We need to do some routing here as it is now critical that circuits, groups, and features
|
|
2145
|
+
// have their own processing. The virtual controller used to only deal with one circuit.
|
|
2146
|
+
if (sys.board.equipmentIds.circuitGroups.isInRange(id))
|
|
2147
|
+
return await sys.board.circuits.setCircuitGroupStateAsync(id, val);
|
|
2148
|
+
else if (sys.board.equipmentIds.features.isInRange(id))
|
|
2149
|
+
return await sys.board.features.setFeatureStateAsync(id, val);
|
|
2150
|
+
let circuit: ICircuit = sys.circuits.getInterfaceById(id, false, { isActive: false });
|
|
2151
|
+
if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError(`Circuit or Feature id ${id} not valid`, id, 'Circuit'));
|
|
2152
|
+
let circ = state.circuits.getInterfaceById(id, circuit.isActive !== false);
|
|
2153
|
+
let newState = utils.makeBool(val);
|
|
2154
|
+
// First, if we are turning the circuit on, lets determine whether the circuit is a pool or spa circuit and if this is a shared system then we need
|
|
2155
|
+
// to turn off the other body first.
|
|
2156
|
+
//[12, { name: 'pool', desc: 'Pool', hasHeatSource: true }],
|
|
2157
|
+
//[13, { name: 'spa', desc: 'Spa', hasHeatSource: true }]
|
|
2158
|
+
let func = sys.board.valueMaps.circuitFunctions.get(circuit.type);
|
|
2159
|
+
if (newState && (func.name === 'pool' || func.name === 'spa') && sys.equipment.shared === true) {
|
|
2160
|
+
// If we are shared we need to turn off the other circuit.
|
|
2161
|
+
let offType = func.name === 'pool' ? sys.board.valueMaps.circuitFunctions.getValue('spa') : sys.board.valueMaps.circuitFunctions.getValue('pool');
|
|
2162
|
+
let off = sys.circuits.get().filter(elem => elem.type === offType);
|
|
2163
|
+
// Turn the circuits off that are part of the shared system. We are going back to the board
|
|
2164
|
+
// just in case we got here for a circuit that isn't on the current defined panel.
|
|
2165
|
+
for (let i = 0; i < off.length; i++) {
|
|
2166
|
+
let coff = off[i];
|
|
2167
|
+
await sys.board.circuits.setCircuitStateAsync(coff.id, false);
|
|
1838
2168
|
}
|
|
2169
|
+
}
|
|
2170
|
+
if (id === 6) state.temps.bodies.getItemById(1, true).isOn = val;
|
|
2171
|
+
else if (id === 1) state.temps.bodies.getItemById(2, true).isOn = val;
|
|
2172
|
+
// Let the main nixie controller set the circuit state and affect the relays if it needs to.
|
|
2173
|
+
await ncp.circuits.setCircuitStateAsync(circ, newState);
|
|
2174
|
+
await sys.board.syncEquipmentItems();
|
|
2175
|
+
return state.circuits.getInterfaceById(circ.id);
|
|
2176
|
+
}
|
|
2177
|
+
catch (err) { return Promise.reject(`Nixie: Error setCircuitStateAsync ${err.message}`); }
|
|
2178
|
+
finally {
|
|
2179
|
+
ncp.pumps.syncPumpStates();
|
|
2180
|
+
sys.board.suspendStatus(false);
|
|
2181
|
+
state.emitEquipmentChanges();
|
|
1839
2182
|
}
|
|
2183
|
+
}
|
|
1840
2184
|
public async toggleCircuitStateAsync(id: number): Promise<ICircuitState> {
|
|
1841
2185
|
let circ = state.circuits.getInterfaceById(id);
|
|
1842
2186
|
return await this.setCircuitStateAsync(id, !(circ.isOn || false));
|
|
@@ -1908,58 +2252,58 @@ export class CircuitCommands extends BoardCommands {
|
|
|
1908
2252
|
public getLightThemes(type?: number) { return sys.board.valueMaps.lightThemes.toArray(); }
|
|
1909
2253
|
public getCircuitFunctions() { return sys.board.valueMaps.circuitFunctions.toArray(); }
|
|
1910
2254
|
public getCircuitNames() { return [...sys.board.valueMaps.circuitNames.toArray(), ...sys.board.valueMaps.customNames.toArray()]; }
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
}
|
|
1942
|
-
if (id === 6) circuit.type = sys.board.valueMaps.circuitFunctions.getValue('pool');
|
|
1943
|
-
if (id === 1 && sys.equipment.shared) circuit.type = sys.board.valueMaps.circuitFunctions.getValue('spa');
|
|
1944
|
-
if (typeof data.freeze !== 'undefined' || typeof circuit.freeze === 'undefined') circuit.freeze = utils.makeBool(data.freeze) || false;
|
|
1945
|
-
if (typeof data.showInFeatures !== 'undefined' || typeof data.showInFeatures === 'undefined') circuit.showInFeatures = scircuit.showInFeatures = utils.makeBool(data.showInFeatures) || true;
|
|
1946
|
-
if (typeof data.dontStop !== 'undefined' && utils.makeBool(data.dontStop) === true) data.eggTimer = 1440;
|
|
1947
|
-
if (typeof data.eggTimer !== 'undefined' || typeof circuit.eggTimer === 'undefined') circuit.eggTimer = parseInt(data.eggTimer, 10) || 0;
|
|
1948
|
-
if (typeof data.connectionId !== 'undefined') circuit.connectionId = data.connectionId;
|
|
1949
|
-
if (typeof data.deviceBinding !== 'undefined') circuit.deviceBinding = data.deviceBinding;
|
|
1950
|
-
if (typeof data.showInFeatures !== 'undefined') scircuit.showInFeatures = circuit.showInFeatures = utils.makeBool(data.showInFeatures);
|
|
1951
|
-
circuit.dontStop = circuit.eggTimer === 1440;
|
|
1952
|
-
|
|
1953
|
-
sys.emitEquipmentChange();
|
|
1954
|
-
state.emitEquipmentChanges();
|
|
1955
|
-
if (circuit.master === 1) await ncp.circuits.setCircuitAsync(circuit, data);
|
|
1956
|
-
return Promise.resolve(circuit);
|
|
1957
|
-
}
|
|
1958
|
-
else
|
|
1959
|
-
return Promise.reject(new Error('Circuit id has not been defined'));
|
|
2255
|
+
public async setCircuitAsync(data: any): Promise<ICircuit> {
|
|
2256
|
+
try {
|
|
2257
|
+
let id = parseInt(data.id, 10);
|
|
2258
|
+
if (id <= 0 || typeof data.id === 'undefined') {
|
|
2259
|
+
// We are adding a new circuit. If we are operating as a nixie controller then we need to start this
|
|
2260
|
+
// circuit outside the range of circuits that can be defined on the panel. For any of the non-OCP controllers
|
|
2261
|
+
// these are added within the range of the circuits starting with 1. For all others these are added with an id > 255.
|
|
2262
|
+
switch (state.equipment.controllerType) {
|
|
2263
|
+
case 'intellicenter':
|
|
2264
|
+
case 'intellitouch':
|
|
2265
|
+
case 'easytouch':
|
|
2266
|
+
id = sys.circuits.getNextEquipmentId(new EquipmentIdRange(255, 300));
|
|
2267
|
+
break;
|
|
2268
|
+
default:
|
|
2269
|
+
id = sys.circuits.getNextEquipmentId(sys.board.equipmentIds.circuits, [1, 6]);
|
|
2270
|
+
break;
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError(`Invalid circuit id: ${data.id}`, data.id, 'Circuit'));
|
|
2274
|
+
//if (!sys.board.equipmentIds.circuits.isInRange(id)) return Promise.reject(new InvalidEquipmentIdError(`Circuit id is out of range: ${id}`, data.id, 'Circuit'));;
|
|
2275
|
+
if (typeof data.id !== 'undefined') {
|
|
2276
|
+
let circuit = sys.circuits.getItemById(id, true);
|
|
2277
|
+
let scircuit = state.circuits.getItemById(id, true);
|
|
2278
|
+
scircuit.isActive = circuit.isActive = true;
|
|
2279
|
+
circuit.master = 1;
|
|
2280
|
+
scircuit.isOn = false;
|
|
2281
|
+
if (data.name) circuit.name = scircuit.name = data.name;
|
|
2282
|
+
else if (!circuit.name && !data.name) circuit.name = scircuit.name = `circuit${data.id}`;
|
|
2283
|
+
if (typeof data.type !== 'undefined' || typeof circuit.type === 'undefined') {
|
|
2284
|
+
circuit.type = scircuit.type = parseInt(data.type, 10) || 0;
|
|
1960
2285
|
}
|
|
1961
|
-
|
|
2286
|
+
if (id === 6) circuit.type = sys.board.valueMaps.circuitFunctions.getValue('pool');
|
|
2287
|
+
if (id === 1 && sys.equipment.shared) circuit.type = sys.board.valueMaps.circuitFunctions.getValue('spa');
|
|
2288
|
+
if (typeof data.freeze !== 'undefined' || typeof circuit.freeze === 'undefined') circuit.freeze = utils.makeBool(data.freeze) || false;
|
|
2289
|
+
if (typeof data.showInFeatures !== 'undefined' || typeof data.showInFeatures === 'undefined') circuit.showInFeatures = scircuit.showInFeatures = utils.makeBool(data.showInFeatures) || true;
|
|
2290
|
+
if (typeof data.dontStop !== 'undefined' && utils.makeBool(data.dontStop) === true) data.eggTimer = 1440;
|
|
2291
|
+
if (typeof data.eggTimer !== 'undefined' || typeof circuit.eggTimer === 'undefined') circuit.eggTimer = parseInt(data.eggTimer, 10) || 0;
|
|
2292
|
+
if (typeof data.connectionId !== 'undefined') circuit.connectionId = data.connectionId;
|
|
2293
|
+
if (typeof data.deviceBinding !== 'undefined') circuit.deviceBinding = data.deviceBinding;
|
|
2294
|
+
if (typeof data.showInFeatures !== 'undefined') scircuit.showInFeatures = circuit.showInFeatures = utils.makeBool(data.showInFeatures);
|
|
2295
|
+
circuit.dontStop = circuit.eggTimer === 1440;
|
|
2296
|
+
|
|
2297
|
+
sys.emitEquipmentChange();
|
|
2298
|
+
state.emitEquipmentChanges();
|
|
2299
|
+
if (circuit.master === 1) await ncp.circuits.setCircuitAsync(circuit, data);
|
|
2300
|
+
return Promise.resolve(circuit);
|
|
2301
|
+
}
|
|
2302
|
+
else
|
|
2303
|
+
return Promise.reject(new Error('Circuit id has not been defined'));
|
|
1962
2304
|
}
|
|
2305
|
+
catch (err) { logger.error(`setCircuitAsync error with ${data}. ${err}`); return Promise.reject(err); }
|
|
2306
|
+
}
|
|
1963
2307
|
public async setCircuitGroupAsync(obj: any): Promise<CircuitGroup> {
|
|
1964
2308
|
let group: CircuitGroup = null;
|
|
1965
2309
|
let sgroup: CircuitGroupState = null;
|
|
@@ -2032,8 +2376,9 @@ export class CircuitCommands extends BoardCommands {
|
|
|
2032
2376
|
if (typeof id === 'undefined') return Promise.reject(new InvalidEquipmentIdError(`Max circuit light group id exceeded`, id, 'LightGroup'));
|
|
2033
2377
|
if (isNaN(id) || !sys.board.equipmentIds.circuitGroups.isInRange(id)) return Promise.reject(new InvalidEquipmentIdError(`Invalid circuit group id: ${obj.id}`, obj.id, 'LightGroup'));
|
|
2034
2378
|
group = sys.lightGroups.getItemById(id, true);
|
|
2379
|
+
let sgroup = state.lightGroups.getItemById(id, true);
|
|
2035
2380
|
return new Promise<LightGroup>((resolve, reject) => {
|
|
2036
|
-
if (typeof obj.name !== 'undefined') group.name = obj.name;
|
|
2381
|
+
if (typeof obj.name !== 'undefined') sgroup.name = group.name = obj.name;
|
|
2037
2382
|
if (typeof obj.dontStop !== 'undefined' && utils.makeBool(obj.dontStop) === true) obj.eggTimer = 1440;
|
|
2038
2383
|
if (typeof obj.eggTimer !== 'undefined') group.eggTimer = Math.min(Math.max(parseInt(obj.eggTimer, 10), 0), 1440);
|
|
2039
2384
|
group.dontStop = group.eggTimer === 1440;
|
|
@@ -2288,6 +2633,56 @@ export class CircuitCommands extends BoardCommands {
|
|
|
2288
2633
|
}
|
|
2289
2634
|
}
|
|
2290
2635
|
export class FeatureCommands extends BoardCommands {
|
|
2636
|
+
public async restore(rest: { poolConfig: any, poolState: any }, ctx: any, res: RestoreResults): Promise<boolean> {
|
|
2637
|
+
try {
|
|
2638
|
+
// First delete the features that should be removed.
|
|
2639
|
+
for (let i = 0; i < ctx.features.remove.length; i++) {
|
|
2640
|
+
let f = ctx.features.remove[i];
|
|
2641
|
+
try {
|
|
2642
|
+
await sys.board.features.deleteFeatureAsync(f);
|
|
2643
|
+
res.addModuleSuccess('feature', `Remove: ${f.id}-${f.name}`);
|
|
2644
|
+
} catch (err) { res.addModuleError('feature', `Remove: ${f.id}-${f.name}: ${err.message}`) }
|
|
2645
|
+
}
|
|
2646
|
+
for (let i = 0; i < ctx.features.update.length; i++) {
|
|
2647
|
+
let f = ctx.features.update[i];
|
|
2648
|
+
try {
|
|
2649
|
+
await sys.board.features.setFeatureAsync(f);
|
|
2650
|
+
res.addModuleSuccess('feature', `Update: ${f.id}-${f.name}`);
|
|
2651
|
+
} catch (err) { res.addModuleError('feature', `Update: ${f.id}-${f.name}: ${err.message}`); }
|
|
2652
|
+
}
|
|
2653
|
+
for (let i = 0; i < ctx.features.add.length; i++) {
|
|
2654
|
+
// pull a little trick to first add the data then perform the update. This way we won't get a new id or
|
|
2655
|
+
// it won't error out.
|
|
2656
|
+
let f = ctx.features.add[i];
|
|
2657
|
+
try {
|
|
2658
|
+
sys.features.getItemById(f, true);
|
|
2659
|
+
await sys.board.features.setFeatureAsync(f);
|
|
2660
|
+
res.addModuleSuccess('feature', `Add: ${f.id}-${f.name}`);
|
|
2661
|
+
} catch (err) { res.addModuleError('feature', `Add: ${f.id}-${f.name}: ${err.message}`) }
|
|
2662
|
+
}
|
|
2663
|
+
return true;
|
|
2664
|
+
} catch (err) { logger.error(`Error restoring features: ${err.message}`); res.addModuleError('system', `Error restoring features: ${err.message}`); return false; }
|
|
2665
|
+
}
|
|
2666
|
+
public async validateRestore(rest: { poolConfig: any, poolState: any }): Promise<{ errors: any, warnings: any, add: any, update: any, remove: any }> {
|
|
2667
|
+
try {
|
|
2668
|
+
let ctx = { errors: [], warnings: [], add: [], update: [], remove: [] };
|
|
2669
|
+
// Look at features.
|
|
2670
|
+
let cfg = rest.poolConfig;
|
|
2671
|
+
for (let i = 0; i < cfg.features.length; i++) {
|
|
2672
|
+
let r = cfg.features[i];
|
|
2673
|
+
let c = sys.features.find(elem => r.id === elem.id);
|
|
2674
|
+
if (typeof c === 'undefined') ctx.add.push(r);
|
|
2675
|
+
else if (JSON.stringify(c.get()) !== JSON.stringify(r)) ctx.update.push(r);
|
|
2676
|
+
}
|
|
2677
|
+
for (let i = 0; i < sys.features.length; i++) {
|
|
2678
|
+
let c = sys.features.getItemByIndex(i);
|
|
2679
|
+
let r = cfg.features.find(elem => elem.id == c.id);
|
|
2680
|
+
if (typeof r === 'undefined') ctx.remove.push(c.get(true));
|
|
2681
|
+
}
|
|
2682
|
+
return ctx;
|
|
2683
|
+
} catch (err) { logger.error(`Error validating features for restore: ${err.message}`); }
|
|
2684
|
+
}
|
|
2685
|
+
|
|
2291
2686
|
public async setFeatureAsync(obj: any): Promise<Feature> {
|
|
2292
2687
|
let id = parseInt(obj.id, 10);
|
|
2293
2688
|
if (id <= 0 || isNaN(id)) {
|
|
@@ -2403,6 +2798,57 @@ export class FeatureCommands extends BoardCommands {
|
|
|
2403
2798
|
}
|
|
2404
2799
|
}
|
|
2405
2800
|
export class ChlorinatorCommands extends BoardCommands {
|
|
2801
|
+
public async restore(rest: { poolConfig: any, poolState: any }, ctx: any, res: RestoreResults): Promise<boolean> {
|
|
2802
|
+
try {
|
|
2803
|
+
// First delete the chlorinators that should be removed.
|
|
2804
|
+
for (let i = 0; i < ctx.chlorinators.remove.length; i++) {
|
|
2805
|
+
let c = ctx.chlorinators.remove[i];
|
|
2806
|
+
try {
|
|
2807
|
+
await sys.board.chlorinator.deleteChlorAsync(c);
|
|
2808
|
+
res.addModuleSuccess('chlorinator', `Remove: ${c.id}-${c.name}`);
|
|
2809
|
+
} catch (err) { res.addModuleError('chlorinator', `Remove: ${c.id}-${c.name}: ${err.message}`); }
|
|
2810
|
+
}
|
|
2811
|
+
for (let i = 0; i < ctx.chlorinators.update.length; i++) {
|
|
2812
|
+
let c = ctx.chlorinators.update[i];
|
|
2813
|
+
try {
|
|
2814
|
+
await sys.board.chlorinator.setChlorAsync(c);
|
|
2815
|
+
res.addModuleSuccess('chlorinator', `Update: ${c.id}-${c.name}`);
|
|
2816
|
+
} catch (err) { res.addModuleError('chlorinator', `Update: ${c.id}-${c.name}: ${err.message}`); }
|
|
2817
|
+
}
|
|
2818
|
+
for (let i = 0; i < ctx.chlorinators.add.length; i++) {
|
|
2819
|
+
let c = ctx.chlorinators.add[i];
|
|
2820
|
+
try {
|
|
2821
|
+
// pull a little trick to first add the data then perform the update. This way we won't get a new id or
|
|
2822
|
+
// it won't error out.
|
|
2823
|
+
sys.chlorinators.getItemById(c.id, true);
|
|
2824
|
+
await sys.board.chlorinator.setChlorAsync(c);
|
|
2825
|
+
res.addModuleSuccess('chlorinator', `Add: ${c.id}-${c.name}`);
|
|
2826
|
+
} catch (err) { res.addModuleError('chlorinator', `Add: ${c.id}-${c.name}: ${err.message}`); }
|
|
2827
|
+
}
|
|
2828
|
+
return true;
|
|
2829
|
+
} catch (err) { logger.error(`Error restoring chlorinators: ${err.message}`); res.addModuleError('system', `Error restoring chlorinators: ${err.message}`); return false; }
|
|
2830
|
+
}
|
|
2831
|
+
|
|
2832
|
+
public async validateRestore(rest: { poolConfig: any, poolState: any }): Promise<{ errors: any, warnings: any, add: any, update: any, remove: any }> {
|
|
2833
|
+
try {
|
|
2834
|
+
let ctx = { errors: [], warnings: [], add: [], update: [], remove: [] };
|
|
2835
|
+
// Look at chlorinators.
|
|
2836
|
+
let cfg = rest.poolConfig;
|
|
2837
|
+
for (let i = 0; i < cfg.chlorinators.length; i++) {
|
|
2838
|
+
let r = cfg.chlorinators[i];
|
|
2839
|
+
let c = sys.chlorinators.find(elem => r.id === elem.id);
|
|
2840
|
+
if (typeof c === 'undefined') ctx.add.push(r);
|
|
2841
|
+
else if (JSON.stringify(c.get()) !== JSON.stringify(r)) ctx.update.push(r);
|
|
2842
|
+
}
|
|
2843
|
+
for (let i = 0; i < sys.chlorinators.length; i++) {
|
|
2844
|
+
let c = sys.chlorinators.getItemByIndex(i);
|
|
2845
|
+
let r = cfg.chlorinators.find(elem => elem.id == c.id);
|
|
2846
|
+
if (typeof r === 'undefined') ctx.remove.push(c.get(true));
|
|
2847
|
+
}
|
|
2848
|
+
return ctx;
|
|
2849
|
+
} catch (err) { logger.error(`Error validating chlorinators for restore: ${err.message}`); }
|
|
2850
|
+
}
|
|
2851
|
+
|
|
2406
2852
|
public async setChlorAsync(obj: any): Promise<ChlorinatorState> {
|
|
2407
2853
|
try {
|
|
2408
2854
|
let id = parseInt(obj.id, 10);
|
|
@@ -2421,7 +2867,7 @@ export class ChlorinatorCommands extends BoardCommands {
|
|
|
2421
2867
|
public async deleteChlorAsync(obj: any): Promise<ChlorinatorState> {
|
|
2422
2868
|
try {
|
|
2423
2869
|
let id = parseInt(obj.id, 10);
|
|
2424
|
-
if (isNaN(id)) obj.id
|
|
2870
|
+
if (isNaN(id)) return Promise.reject(new InvalidEquipmentDataError(`Chlorinator id is not valid: ${obj.id}`, 'chlorinator', obj.id));
|
|
2425
2871
|
let chlor = state.chlorinators.getItemById(id);
|
|
2426
2872
|
chlor.isActive = false;
|
|
2427
2873
|
await ncp.chlorinators.deleteChlorinatorAsync(id);
|
|
@@ -2445,6 +2891,56 @@ export class ChlorinatorCommands extends BoardCommands {
|
|
|
2445
2891
|
}
|
|
2446
2892
|
}
|
|
2447
2893
|
export class ScheduleCommands extends BoardCommands {
|
|
2894
|
+
public async restore(rest: { poolConfig: any, poolState: any }, ctx: any, res: RestoreResults): Promise<boolean> {
|
|
2895
|
+
try {
|
|
2896
|
+
// First delete the schedules that should be removed.
|
|
2897
|
+
for (let i = 0; i < ctx.schedules.remove.length; i++) {
|
|
2898
|
+
let s = ctx.schedules.remove[i];
|
|
2899
|
+
try {
|
|
2900
|
+
await sys.board.schedules.deleteScheduleAsync(ctx.schedules.remove[i]);
|
|
2901
|
+
res.addModuleSuccess('schedule', `Remove: ${s.id}-${s.circuitId}`);
|
|
2902
|
+
} catch (err) { res.addModuleError('schedule', `Remove: ${s.id}-${s.circuitId} ${err.message}`); }
|
|
2903
|
+
}
|
|
2904
|
+
for (let i = 0; i < ctx.schedules.update.length; i++) {
|
|
2905
|
+
let s = ctx.schedules.update[i];
|
|
2906
|
+
try {
|
|
2907
|
+
await sys.board.schedules.setScheduleAsync(s);
|
|
2908
|
+
res.addModuleSuccess('schedule', `Update: ${s.id}-${s.circuitId}`);
|
|
2909
|
+
} catch (err) { res.addModuleError('schedule', `Update: ${s.id}-${s.circuitId} ${err.message}`); }
|
|
2910
|
+
}
|
|
2911
|
+
for (let i = 0; i < ctx.schedules.add.length; i++) {
|
|
2912
|
+
let s = ctx.schedules.add[i];
|
|
2913
|
+
try {
|
|
2914
|
+
// pull a little trick to first add the data then perform the update. This way we won't get a new id or
|
|
2915
|
+
// it won't error out.
|
|
2916
|
+
sys.schedules.getItemById(s.id, true);
|
|
2917
|
+
await sys.board.schedules.setScheduleAsync(s);
|
|
2918
|
+
res.addModuleSuccess('schedule', `Add: ${s.id}-${s.circuitId}`);
|
|
2919
|
+
} catch (err) { res.addModuleError('schedule', `Add: ${s.id}-${s.circuitId} ${err.message}`); }
|
|
2920
|
+
}
|
|
2921
|
+
return true;
|
|
2922
|
+
} catch (err) { logger.error(`Error restoring schedules: ${err.message}`); res.addModuleError('system', `Error restoring schedules: ${err.message}`); return false; }
|
|
2923
|
+
}
|
|
2924
|
+
public async validateRestore(rest: { poolConfig: any, poolState: any }): Promise<{ errors: any, warnings: any, add: any, update: any, remove: any }> {
|
|
2925
|
+
try {
|
|
2926
|
+
let ctx = { errors: [], warnings: [], add: [], update: [], remove: [] };
|
|
2927
|
+
// Look at schedules.
|
|
2928
|
+
let cfg = rest.poolConfig;
|
|
2929
|
+
for (let i = 0; i < cfg.schedules.length; i++) {
|
|
2930
|
+
let r = cfg.schedules[i];
|
|
2931
|
+
let c = sys.schedules.find(elem => r.id === elem.id);
|
|
2932
|
+
if (typeof c === 'undefined') ctx.add.push(r);
|
|
2933
|
+
else if (JSON.stringify(c.get()) !== JSON.stringify(r)) ctx.update.push(r);
|
|
2934
|
+
}
|
|
2935
|
+
for (let i = 0; i < sys.schedules.length; i++) {
|
|
2936
|
+
let c = sys.schedules.getItemByIndex(i);
|
|
2937
|
+
let r = cfg.schedules.find(elem => elem.id == c.id);
|
|
2938
|
+
if (typeof r === 'undefined') ctx.remove.push(c.get(true));
|
|
2939
|
+
}
|
|
2940
|
+
return ctx;
|
|
2941
|
+
} catch (err) { logger.error(`Error validating schedules for restore: ${err.message}`); }
|
|
2942
|
+
}
|
|
2943
|
+
|
|
2448
2944
|
public transformDays(val: any): number {
|
|
2449
2945
|
if (typeof val === 'number') return val;
|
|
2450
2946
|
let edays = sys.board.valueMaps.scheduleDays.toArray();
|
|
@@ -2565,6 +3061,9 @@ export class ScheduleCommands extends BoardCommands {
|
|
|
2565
3061
|
return Promise.reject(new InvalidEquipmentDataError(`Invalid circuit reference: ${circuit}`, 'Schedule', circuit));
|
|
2566
3062
|
if (schedType === 128 && schedDays === 0) return Promise.reject(new InvalidEquipmentDataError(`Invalid schedule days: ${schedDays}. You must supply days that the schedule is to run.`, 'Schedule', schedDays));
|
|
2567
3063
|
|
|
3064
|
+
// If we made it to here we are valid and the schedula and it state should exist.
|
|
3065
|
+
sched = sys.schedules.getItemById(id, true);
|
|
3066
|
+
ssched = state.schedules.getItemById(id, true);
|
|
2568
3067
|
sched.circuit = ssched.circuit = circuit;
|
|
2569
3068
|
sched.scheduleDays = ssched.scheduleDays = schedDays;
|
|
2570
3069
|
sched.scheduleType = ssched.scheduleType = schedType;
|
|
@@ -2676,6 +3175,56 @@ export class ScheduleCommands extends BoardCommands {
|
|
|
2676
3175
|
}
|
|
2677
3176
|
}
|
|
2678
3177
|
export class HeaterCommands extends BoardCommands {
|
|
3178
|
+
public async restore(rest: { poolConfig: any, poolState: any }, ctx: any, res: RestoreResults): Promise<boolean> {
|
|
3179
|
+
try {
|
|
3180
|
+
// First delete the heaters that should be removed.
|
|
3181
|
+
for (let i = 0; i < ctx.heaters.remove.length; i++) {
|
|
3182
|
+
let h = ctx.heaters.remove[i];
|
|
3183
|
+
try {
|
|
3184
|
+
await sys.board.heaters.deleteHeaterAsync(h);
|
|
3185
|
+
res.addModuleSuccess('heater', `Remove: ${h.id}-${h.name}`);
|
|
3186
|
+
} catch (err) { res.addModuleError('heater', `Remove: ${h.id}-${h.name}: ${err.message}`); }
|
|
3187
|
+
}
|
|
3188
|
+
for (let i = 0; i < ctx.heaters.update.length; i++) {
|
|
3189
|
+
let h = ctx.heaters.update[i];
|
|
3190
|
+
try {
|
|
3191
|
+
await sys.board.heaters.setHeaterAsync(h);
|
|
3192
|
+
res.addModuleSuccess('heater', `Update: ${h.id}-${h.name}`);
|
|
3193
|
+
} catch (err) { res.addModuleError('heater', `Update: ${h.id}-${h.name}: ${err.message}`); }
|
|
3194
|
+
}
|
|
3195
|
+
for (let i = 0; i < ctx.heaters.add.length; i++) {
|
|
3196
|
+
let h = ctx.heaters.add[i];
|
|
3197
|
+
try {
|
|
3198
|
+
// pull a little trick to first add the data then perform the update. This way we won't get a new id or
|
|
3199
|
+
// it won't error out.
|
|
3200
|
+
sys.heaters.getItemById(h, true);
|
|
3201
|
+
await sys.board.heaters.setHeaterAsync(h);
|
|
3202
|
+
res.addModuleSuccess('heater', `Add: ${h.id}-${h.name}`);
|
|
3203
|
+
} catch (err) { res.addModuleError('heater', `Add: ${h.id}-${h.name}: ${err.message}`); }
|
|
3204
|
+
}
|
|
3205
|
+
return true;
|
|
3206
|
+
} catch (err) { logger.error(`Error restoring heaters: ${err.message}`); res.addModuleError('system', `Error restoring heaters: ${err.message}`); return false; }
|
|
3207
|
+
}
|
|
3208
|
+
public async validateRestore(rest: { poolConfig: any, poolState: any }): Promise<{ errors: any, warnings: any, add: any, update: any, remove: any }> {
|
|
3209
|
+
try {
|
|
3210
|
+
let ctx = { errors: [], warnings: [], add: [], update: [], remove: [] };
|
|
3211
|
+
// Look at heaters.
|
|
3212
|
+
let cfg = rest.poolConfig;
|
|
3213
|
+
for (let i = 0; i < cfg.heaters.length; i++) {
|
|
3214
|
+
let r = cfg.heaters[i];
|
|
3215
|
+
let c = sys.heaters.find(elem => r.id === elem.id);
|
|
3216
|
+
if (typeof c === 'undefined') ctx.add.push(r);
|
|
3217
|
+
else if (JSON.stringify(c.get()) !== JSON.stringify(r)) ctx.update.push(r);
|
|
3218
|
+
}
|
|
3219
|
+
for (let i = 0; i < sys.heaters.length; i++) {
|
|
3220
|
+
let c = sys.heaters.getItemByIndex(i);
|
|
3221
|
+
let r = cfg.heaters.find(elem => elem.id == c.id);
|
|
3222
|
+
if (typeof r === 'undefined') ctx.remove.push(c.get(true));
|
|
3223
|
+
}
|
|
3224
|
+
return ctx;
|
|
3225
|
+
} catch (err) { logger.error(`Error validating heaters for restore: ${err.message}`); }
|
|
3226
|
+
}
|
|
3227
|
+
|
|
2679
3228
|
public getInstalledHeaterTypes(body?: number): any {
|
|
2680
3229
|
let heaters = sys.heaters.get();
|
|
2681
3230
|
let types = sys.board.valueMaps.heaterTypes.toArray();
|
|
@@ -2740,7 +3289,7 @@ export class HeaterCommands extends BoardCommands {
|
|
|
2740
3289
|
let heater: Heater;
|
|
2741
3290
|
if (id <= 0) {
|
|
2742
3291
|
// We are adding a heater. In this case all heaters are virtual.
|
|
2743
|
-
let vheaters = sys.heaters.filter(h => h.
|
|
3292
|
+
let vheaters = sys.heaters.filter(h => h.master === 1);
|
|
2744
3293
|
id = vheaters.length + 256;
|
|
2745
3294
|
}
|
|
2746
3295
|
heater = sys.heaters.getItemById(id, true);
|
|
@@ -2959,7 +3508,7 @@ export class HeaterCommands extends BoardCommands {
|
|
|
2959
3508
|
let htype = sys.board.valueMaps.heaterTypes.transform(heater.type);
|
|
2960
3509
|
let status = sys.board.valueMaps.heatStatus.transform(body.heatStatus);
|
|
2961
3510
|
let hstate = state.heaters.getItemById(heater.id, true);
|
|
2962
|
-
if (heater.
|
|
3511
|
+
if (heater.master === 1) {
|
|
2963
3512
|
// We need to do our own calculation as to whether it is on. This is for Nixie heaters.
|
|
2964
3513
|
let mode = sys.board.valueMaps.heatModes.getName(body.heatMode);
|
|
2965
3514
|
switch (htype.name) {
|
|
@@ -3001,6 +3550,16 @@ export class HeaterCommands extends BoardCommands {
|
|
|
3001
3550
|
}
|
|
3002
3551
|
}
|
|
3003
3552
|
break;
|
|
3553
|
+
case 'mastertemp':
|
|
3554
|
+
if (mode === 'mtheater') {
|
|
3555
|
+
if (body.temp < cfgBody.setPoint) {
|
|
3556
|
+
isOn = true;
|
|
3557
|
+
body.heatStatus = sys.board.valueMaps.heaterTypes.getValue('mtheater');
|
|
3558
|
+
isHeating = true;
|
|
3559
|
+
}
|
|
3560
|
+
}
|
|
3561
|
+
break;
|
|
3562
|
+
case 'maxetherm':
|
|
3004
3563
|
case 'gas':
|
|
3005
3564
|
if (mode === 'heater') {
|
|
3006
3565
|
if (body.temp < cfgBody.setPoint) {
|
|
@@ -3040,6 +3599,8 @@ export class HeaterCommands extends BoardCommands {
|
|
|
3040
3599
|
let mode = sys.board.valueMaps.heatModes.getName(body.heatMode);
|
|
3041
3600
|
switch (htype.name) {
|
|
3042
3601
|
case 'mastertemp':
|
|
3602
|
+
if (status === 'mtheater') isHeating = isOn = true;
|
|
3603
|
+
break;
|
|
3043
3604
|
case 'maxetherm':
|
|
3044
3605
|
case 'gas':
|
|
3045
3606
|
if (status === 'heater') isHeating = isOn = true;
|
|
@@ -3073,7 +3634,7 @@ export class HeaterCommands extends BoardCommands {
|
|
|
3073
3634
|
}
|
|
3074
3635
|
}
|
|
3075
3636
|
// When the controller is a virtual one we need to control the heat status ourselves.
|
|
3076
|
-
if (!isHeating && (sys.controllerType === ControllerType.
|
|
3637
|
+
if (!isHeating && (sys.controllerType === ControllerType.Nixie)) body.heatStatus = 0;
|
|
3077
3638
|
}
|
|
3078
3639
|
// Turn off any heaters that should be off. The code above only turns heaters on.
|
|
3079
3640
|
for (let i = 0; i < heaters.length; i++) {
|
|
@@ -3092,6 +3653,57 @@ export class HeaterCommands extends BoardCommands {
|
|
|
3092
3653
|
}
|
|
3093
3654
|
}
|
|
3094
3655
|
export class ValveCommands extends BoardCommands {
|
|
3656
|
+
public async restore(rest: { poolConfig: any, poolState: any }, ctx: any, res: RestoreResults): Promise<boolean> {
|
|
3657
|
+
try {
|
|
3658
|
+
// First delete the valves that should be removed.
|
|
3659
|
+
for (let i = 0; i < ctx.valves.remove.length; i++) {
|
|
3660
|
+
let v = ctx.valves.remove[i];
|
|
3661
|
+
try {
|
|
3662
|
+
await sys.board.valves.deleteValveAsync(v);
|
|
3663
|
+
res.addModuleSuccess('valve', `Remove: ${v.id}-${v.name}`);
|
|
3664
|
+
} catch (err) { res.addModuleError('valve', `Remove: ${v.id}-${v.name}: ${err.message}`); }
|
|
3665
|
+
}
|
|
3666
|
+
for (let i = 0; i < ctx.valves.update.length; i++) {
|
|
3667
|
+
let v = ctx.valves.update[i];
|
|
3668
|
+
try {
|
|
3669
|
+
await sys.board.valves.setValveAsync(v);
|
|
3670
|
+
res.addModuleSuccess('valve', `Update: ${v.id}-${v.name}`);
|
|
3671
|
+
} catch (err) { res.addModuleError('valve', `Update: ${v.id}-${v.name}: ${err.message}`); }
|
|
3672
|
+
}
|
|
3673
|
+
for (let i = 0; i < ctx.valves.add.length; i++) {
|
|
3674
|
+
let v = ctx.valves.add[i];
|
|
3675
|
+
try {
|
|
3676
|
+
// pull a little trick to first add the data then perform the update. This way we won't get a new id or
|
|
3677
|
+
// it won't error out.
|
|
3678
|
+
sys.valves.getItemById(ctx.valves.add[i].id, true);
|
|
3679
|
+
await sys.board.valves.setValveAsync(v);
|
|
3680
|
+
res.addModuleSuccess('valve', `Add: ${v.id}-${v.name}`);
|
|
3681
|
+
} catch (err) { res.addModuleError('valve', `Add: ${v.id}-${v.name}: ${err.message}`); }
|
|
3682
|
+
}
|
|
3683
|
+
return true;
|
|
3684
|
+
} catch (err) { logger.error(`Error restoring valves: ${err.message}`); res.addModuleError('system', `Error restoring valves: ${err.message}`); return false; }
|
|
3685
|
+
}
|
|
3686
|
+
|
|
3687
|
+
public async validateRestore(rest: { poolConfig: any, poolState: any }): Promise<{ errors: any, warnings: any, add: any, update: any, remove: any }> {
|
|
3688
|
+
try {
|
|
3689
|
+
let ctx = { errors: [], warnings: [], add: [], update: [], remove: [] };
|
|
3690
|
+
// Look at valves.
|
|
3691
|
+
let cfg = rest.poolConfig;
|
|
3692
|
+
for (let i = 0; i < cfg.valves.length; i++) {
|
|
3693
|
+
let r = cfg.valves[i];
|
|
3694
|
+
let c = sys.valves.find(elem => r.id === elem.id);
|
|
3695
|
+
if (typeof c === 'undefined') ctx.add.push(r);
|
|
3696
|
+
else if (JSON.stringify(c.get()) !== JSON.stringify(r)) ctx.update.push(r);
|
|
3697
|
+
}
|
|
3698
|
+
for (let i = 0; i < sys.valves.length; i++) {
|
|
3699
|
+
let c = sys.valves.getItemByIndex(i);
|
|
3700
|
+
let r = cfg.valves.find(elem => elem.id == c.id);
|
|
3701
|
+
if (typeof r === 'undefined') ctx.remove.push(c.get(true));
|
|
3702
|
+
}
|
|
3703
|
+
return ctx;
|
|
3704
|
+
} catch (err) { logger.error(`Error validating valves for restore: ${err.message}`); }
|
|
3705
|
+
}
|
|
3706
|
+
|
|
3095
3707
|
public async setValveStateAsync(valve: Valve, vstate: ValveState, isDiverted: boolean) {
|
|
3096
3708
|
if (valve.master === 1) await ncp.valves.setValveStateAsync(vstate, isDiverted);
|
|
3097
3709
|
else
|
|
@@ -3169,6 +3781,60 @@ export class ValveCommands extends BoardCommands {
|
|
|
3169
3781
|
}
|
|
3170
3782
|
}
|
|
3171
3783
|
export class ChemControllerCommands extends BoardCommands {
|
|
3784
|
+
public async restore(rest: { poolConfig: any, poolState: any }, ctx: any, res: RestoreResults): Promise<boolean> {
|
|
3785
|
+
try {
|
|
3786
|
+
// First delete the chemControllers that should be removed.
|
|
3787
|
+
for (let i = 0; i < ctx.chemControllers.remove.length; i++) {
|
|
3788
|
+
let c = ctx.chemControllers.remove[i];
|
|
3789
|
+
try {
|
|
3790
|
+
await sys.board.chemControllers.deleteChemControllerAsync(c);
|
|
3791
|
+
res.addModuleSuccess('chemController', `Remove: ${c.id}-${c.name}`);
|
|
3792
|
+
} catch (err) { res.addModuleError('chemController', `Remove: ${c.id}-${c.name}: ${err.message}`); }
|
|
3793
|
+
}
|
|
3794
|
+
for (let i = 0; i < ctx.chemControllers.update.length; i++) {
|
|
3795
|
+
let c = ctx.chemControllers.update[i];
|
|
3796
|
+
try {
|
|
3797
|
+
await sys.board.chemControllers.setChemControllerAsync(c);
|
|
3798
|
+
res.addModuleSuccess('chemController', `Update: ${c.id}-${c.name}`);
|
|
3799
|
+
} catch (err) { res.addModuleError('chemController', `Update: ${c.id}-${c.name}: ${err.message}`); }
|
|
3800
|
+
}
|
|
3801
|
+
for (let i = 0; i < ctx.chemControllers.add.length; i++) {
|
|
3802
|
+
let c = ctx.chemControllers.add[i];
|
|
3803
|
+
try {
|
|
3804
|
+
// pull a little trick to first add the data then perform the update. This way we won't get a new id or
|
|
3805
|
+
// it won't error out.
|
|
3806
|
+
let chem = sys.chemControllers.getItemById(c.id, true);
|
|
3807
|
+
// RSG 11.24.21. setChemControllerAsync will only set the type/address if it thinks it's new.
|
|
3808
|
+
// For a restore, if we set the type/address here it will pass the validation steps.
|
|
3809
|
+
chem.type = c.type;
|
|
3810
|
+
// chem.address = c.address;
|
|
3811
|
+
await sys.board.chemControllers.setChemControllerAsync(c);
|
|
3812
|
+
res.addModuleSuccess('chemController', `Add: ${c.id}-${c.name}`);
|
|
3813
|
+
} catch (err) { res.addModuleError('chemController', `Add: ${c.id}-${c.name}: ${err.message}`); }
|
|
3814
|
+
}
|
|
3815
|
+
return true;
|
|
3816
|
+
} catch (err) { logger.error(`Error restoring chemControllers: ${err.message}`); res.addModuleError('system', `Error restoring chemControllers: ${err.message}`); return false; }
|
|
3817
|
+
}
|
|
3818
|
+
public async validateRestore(rest: { poolConfig: any, poolState: any }): Promise<{ errors: any, warnings: any, add: any, update: any, remove: any }> {
|
|
3819
|
+
try {
|
|
3820
|
+
let ctx = { errors: [], warnings: [], add: [], update: [], remove: [] };
|
|
3821
|
+
// Look at chemControllers.
|
|
3822
|
+
let cfg = rest.poolConfig;
|
|
3823
|
+
for (let i = 0; i < cfg.chemControllers.length; i++) {
|
|
3824
|
+
let r = cfg.chemControllers[i];
|
|
3825
|
+
let c = sys.chemControllers.find(elem => r.id === elem.id);
|
|
3826
|
+
if (typeof c === 'undefined') ctx.add.push(r);
|
|
3827
|
+
else if (JSON.stringify(c.get()) !== JSON.stringify(r)) ctx.update.push(r);
|
|
3828
|
+
}
|
|
3829
|
+
for (let i = 0; i < sys.chemControllers.length; i++) {
|
|
3830
|
+
let c = sys.chemControllers.getItemByIndex(i);
|
|
3831
|
+
let r = cfg.chemControllers.find(elem => elem.id == c.id);
|
|
3832
|
+
if (typeof r === 'undefined') ctx.remove.push(c.get(true));
|
|
3833
|
+
}
|
|
3834
|
+
return ctx;
|
|
3835
|
+
} catch (err) { logger.error(`Error validating chemControllers for restore: ${err.message}`); }
|
|
3836
|
+
}
|
|
3837
|
+
|
|
3172
3838
|
public async deleteChemControllerAsync(data: any): Promise<ChemController> {
|
|
3173
3839
|
try {
|
|
3174
3840
|
let id = typeof data.id !== 'undefined' ? parseInt(data.id, 10) : -1;
|
|
@@ -3307,133 +3973,197 @@ export class ChemControllerCommands extends BoardCommands {
|
|
|
3307
3973
|
}
|
|
3308
3974
|
}
|
|
3309
3975
|
export class FilterCommands extends BoardCommands {
|
|
3310
|
-
|
|
3976
|
+
public async restore(rest: { poolConfig: any, poolState: any }, ctx: any, res: RestoreResults): Promise<boolean> {
|
|
3977
|
+
try {
|
|
3978
|
+
// First delete the filters that should be removed.
|
|
3979
|
+
for (let i = 0; i < ctx.filters.remove.length; i++) {
|
|
3980
|
+
let filter = ctx.filters.remove[i];
|
|
3311
3981
|
try {
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
}
|
|
3320
|
-
}
|
|
3321
|
-
} catch (err) { logger.error(`syncFilterStates: Error synchronizing filters ${err.message}`); }
|
|
3322
|
-
}
|
|
3323
|
-
public async setFilterPressure(id: number, pressure: number, units?: string) {
|
|
3982
|
+
sys.filters.removeItemById(filter.id);
|
|
3983
|
+
state.filters.removeItemById(filter.id);
|
|
3984
|
+
res.addModuleSuccess('filter', `Remove: ${filter.id}-${filter.name}`);
|
|
3985
|
+
} catch (err) { res.addModuleError('filter', `Remove: ${filter.id}-${filter.name}: ${err.message}`); }
|
|
3986
|
+
}
|
|
3987
|
+
for (let i = 0; i < ctx.filters.update.length; i++) {
|
|
3988
|
+
let filter = ctx.filters.update[i];
|
|
3324
3989
|
try {
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3990
|
+
await sys.board.filters.setFilterAsync(filter);
|
|
3991
|
+
res.addModuleSuccess('filter', `Update: ${filter.id}-${filter.name}`);
|
|
3992
|
+
} catch (err) { res.addModuleError('filter', `Update: ${filter.id}-${filter.name}: ${err.message}`); }
|
|
3993
|
+
}
|
|
3994
|
+
for (let i = 0; i < ctx.filters.add.length; i++) {
|
|
3995
|
+
let filter = ctx.filters.add[i];
|
|
3996
|
+
try {
|
|
3997
|
+
// pull a little trick to first add the data then perform the update.
|
|
3998
|
+
sys.filters.getItemById(filter.id, true);
|
|
3999
|
+
await sys.board.filters.setFilterAsync(filter);
|
|
4000
|
+
res.addModuleSuccess('filter', `Add: ${filter.id}-${filter.name}`);
|
|
4001
|
+
} catch (err) { res.addModuleError('filter', `Add: ${filter.id}-${filter.name}: ${err.message}`); }
|
|
4002
|
+
}
|
|
4003
|
+
return true;
|
|
4004
|
+
} catch (err) { logger.error(`Error restoring filters: ${err.message}`); res.addModuleError('system', `Error restoring filters: ${err.message}`); return false; }
|
|
4005
|
+
}
|
|
4006
|
+
public async validateRestore(rest: { poolConfig: any, poolState: any }): Promise<{ errors: any, warnings: any, add: any, update: any, remove: any }> {
|
|
4007
|
+
try {
|
|
4008
|
+
let ctx = { errors: [], warnings: [], add: [], update: [], remove: [] };
|
|
4009
|
+
// Look at filters.
|
|
4010
|
+
let cfg = rest.poolConfig;
|
|
4011
|
+
for (let i = 0; i < cfg.filters.length; i++) {
|
|
4012
|
+
let r = cfg.filters[i];
|
|
4013
|
+
let c = sys.filters.find(elem => r.id === elem.id);
|
|
4014
|
+
if (typeof c === 'undefined') ctx.add.push(r);
|
|
4015
|
+
else if (JSON.stringify(c.get()) !== JSON.stringify(r)) ctx.update.push(r);
|
|
4016
|
+
}
|
|
4017
|
+
for (let i = 0; i < sys.filters.length; i++) {
|
|
4018
|
+
let c = sys.filters.getItemByIndex(i);
|
|
4019
|
+
let r = cfg.filters.find(elem => elem.id == c.id);
|
|
4020
|
+
if (typeof r === 'undefined') ctx.remove.push(c.get(true));
|
|
4021
|
+
}
|
|
4022
|
+
return ctx;
|
|
4023
|
+
} catch (err) { logger.error(`Error validating filters for restore: ${err.message}`); }
|
|
4024
|
+
}
|
|
4025
|
+
|
|
4026
|
+
public async syncFilterStates() {
|
|
4027
|
+
try {
|
|
4028
|
+
for (let i = 0; i < sys.filters.length; i++) {
|
|
4029
|
+
// Run through all the valves to see whether they should be triggered or not.
|
|
4030
|
+
let filter = sys.filters.getItemByIndex(i);
|
|
4031
|
+
if (filter.isActive && !isNaN(filter.id)) {
|
|
4032
|
+
let fstate = state.filters.getItemById(filter.id, true);
|
|
4033
|
+
// Check to see if the associated body is on.
|
|
4034
|
+
await sys.board.filters.setFilterStateAsync(filter, fstate, sys.board.bodies.isBodyOn(filter.body));
|
|
4035
|
+
}
|
|
4036
|
+
}
|
|
4037
|
+
} catch (err) { logger.error(`syncFilterStates: Error synchronizing filters ${err.message}`); }
|
|
4038
|
+
}
|
|
4039
|
+
public async setFilterPressure(id: number, pressure: number, units?: string) {
|
|
4040
|
+
try {
|
|
4041
|
+
let filter = sys.filters.find(elem => elem.id === id);
|
|
4042
|
+
if (typeof filter === 'undefined' || isNaN(id)) return Promise.reject(new InvalidEquipmentIdError(`setFilterPressure: Invalid equipmentId ${id}`, id, 'Filter'));
|
|
4043
|
+
if (isNaN(pressure)) return Promise.reject(new InvalidEquipmentDataError(`setFilterPressure: Invalid filter pressure ${pressure} for ${filter.name}`, 'Filter', pressure));
|
|
4044
|
+
let sfilter = state.filters.getItemById(filter.id, true);
|
|
4045
|
+
// Convert the pressure to the units that we have set on the filter for the pressure units.
|
|
4046
|
+
let pu = sys.board.valueMaps.pressureUnits.transform(filter.pressureUnits || 0);
|
|
4047
|
+
if (typeof units === 'undefined' || units === '') units = pu.name;
|
|
4048
|
+
sfilter.pressureUnits = filter.pressureUnits;
|
|
4049
|
+
sfilter.pressure = Math.round(pressure * 1000) / 1000; // Round this to 3 decimal places just in case we are getting stupid scales.
|
|
4050
|
+
// Check to see if our circuit is the only thing on. If it is then we will be setting our current clean pressure to the incoming pressure and calculating a percentage.
|
|
4051
|
+
// Rules for the circuit.
|
|
4052
|
+
// 1. The assigned circuit must be on.
|
|
4053
|
+
// 2. There must not be a current freeze condition
|
|
4054
|
+
// 3. No heaters can be on.
|
|
4055
|
+
// 4. The assigned circuit must be on exclusively but we will be ignoring any of the light circuit types for the exclusivity.
|
|
4056
|
+
let cstate = state.circuits.getInterfaceById(filter.pressureCircuitId);
|
|
4057
|
+
if (cstate.isOn && state.freeze !== true) {
|
|
4058
|
+
// Ok so our circuit is on. We need to check to see if any other circuits are on. This includes heaters. The reason for this is that even with
|
|
4059
|
+
// a gas heater there may be a heater bypass that will screw up our numbers. Certainly reflow on a solar heater will skew the numbers.
|
|
4060
|
+
let hon = state.temps.bodies.toArray().find(elem => elem.isOn && (elem.heatStatus || 0) !== 0);
|
|
4061
|
+
if (typeof hon === 'undefined') {
|
|
4062
|
+
// Put together the circuit types that could be lights. We don't want these.
|
|
4063
|
+
let ctypes = [];
|
|
4064
|
+
let funcs = sys.board.valueMaps.circuitFunctions.toArray();
|
|
4065
|
+
for (let i = 0; i < funcs.length; i++) {
|
|
4066
|
+
let f = funcs[i];
|
|
4067
|
+
if (f.isLight) ctypes.push(f.val);
|
|
4068
|
+
}
|
|
4069
|
+
let con = state.circuits.find(elem => elem.isOn === true && elem.id !== filter.pressureCircuitId && elem.id !== 1 && elem.id !== 6 && !ctypes.includes(elem.type));
|
|
4070
|
+
if (typeof con === 'undefined') {
|
|
4071
|
+
// This check is the one that will be the most problematic. For this reason we are only going to check features that are not generic. If they are spillway
|
|
4072
|
+
// it definitely has to be off.
|
|
4073
|
+
let feats = state.features.toArray();
|
|
4074
|
+
let fon = false;
|
|
4075
|
+
for (let i = 0; i < feats.length && fon === false; i++) {
|
|
4076
|
+
let f = feats[i];
|
|
4077
|
+
if (!f.isOn) continue;
|
|
4078
|
+
if (f.id === filter.pressureCircuitId) continue;
|
|
4079
|
+
if (f.type !== 0) fon = true;
|
|
4080
|
+
// Check to see if this feature is used on a valve. This will make it
|
|
4081
|
+
// not include this pressure either. We do not care whether the valve is diverted or not.
|
|
4082
|
+
if (typeof sys.valves.find(elem => elem.circuitId === f.id) !== 'undefined')
|
|
4083
|
+
fon = true;
|
|
4084
|
+
else {
|
|
4085
|
+
// Finally if the feature happens to be used on a pump then we don't want it either.
|
|
4086
|
+
let pumps = sys.pumps.get();
|
|
4087
|
+
for (let j = 0; j < pumps.length; j++) {
|
|
4088
|
+
let pmp = pumps[j];
|
|
4089
|
+
if (typeof pmp.circuits !== 'undefined') {
|
|
4090
|
+
if (typeof pmp.circuits.find(elem => elem.circuit === f.id) !== 'undefined') {
|
|
4091
|
+
fon = true;
|
|
4092
|
+
break;
|
|
3375
4093
|
}
|
|
4094
|
+
}
|
|
3376
4095
|
}
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
4096
|
+
}
|
|
4097
|
+
}
|
|
4098
|
+
if (!fon) {
|
|
4099
|
+
// Finally we have a value we can believe in.
|
|
4100
|
+
sfilter.refPressure = pressure;
|
|
3380
4101
|
}
|
|
3381
|
-
|
|
4102
|
+
}
|
|
4103
|
+
else {
|
|
4104
|
+
logger.verbose(`Circuit ${con.id}-${con.name} is currently on filter pressure for cleaning ignored.`);
|
|
4105
|
+
}
|
|
3382
4106
|
}
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
if (id <= 0) id = sys.filters.length + 1; // set max filters?
|
|
3389
|
-
if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError(`Invalid filter id: ${data.id}`, data.id, 'Filter'));
|
|
3390
|
-
let filter = sys.filters.getItemById(id, id > 0);
|
|
3391
|
-
let sfilter = state.filters.getItemById(id, id > 0);
|
|
3392
|
-
let filterType = typeof data.filterType !== 'undefined' ? parseInt(data.filterType, 10) : filter.filterType;
|
|
3393
|
-
if (typeof filterType === 'undefined') filterType = sys.board.valueMaps.filterTypes.getValue('unknown');
|
|
3394
|
-
|
|
3395
|
-
// The only way to delete a filter is to call deleteFilterAsync.
|
|
3396
|
-
//if (typeof data.isActive !== 'undefined') {
|
|
3397
|
-
// if (utils.makeBool(data.isActive) === false) {
|
|
3398
|
-
// sys.filters.removeItemById(id);
|
|
3399
|
-
// state.filters.removeItemById(id);
|
|
3400
|
-
// return;
|
|
3401
|
-
// }
|
|
3402
|
-
//}
|
|
3403
|
-
|
|
3404
|
-
let body = typeof data.body !== 'undefined' ? data.body : filter.body;
|
|
3405
|
-
let name = typeof data.name !== 'undefined' ? data.name : filter.name;
|
|
3406
|
-
if (typeof body === 'undefined') body = 32;
|
|
3407
|
-
// At this point we should have all the data. Validate it.
|
|
3408
|
-
if (!sys.board.valueMaps.filterTypes.valExists(filterType)) return Promise.reject(new InvalidEquipmentDataError(`Invalid filter type; ${filterType}`, 'Filter', filterType));
|
|
3409
|
-
|
|
3410
|
-
filter.pressureUnits = typeof data.pressureUnits !== 'undefined' ? data.pressureUnits || 0 : filter.pressureUnits || 0;
|
|
3411
|
-
filter.pressureCircuitId = parseInt(data.pressureCicuitId || filter.pressureCircuitId || 6, 10);
|
|
3412
|
-
filter.cleanPressure = parseFloat(data.cleanPressure || filter.cleanPressure || 0);
|
|
3413
|
-
filter.dirtyPressure = parseFloat(data.dirtyPressure || filter.dirtyPressure || 0);
|
|
3414
|
-
|
|
3415
|
-
filter.filterType = sfilter.filterType = filterType;
|
|
3416
|
-
filter.body = sfilter.body = body;
|
|
3417
|
-
filter.name = sfilter.name = name;
|
|
3418
|
-
filter.capacity = typeof data.capacity === 'number' ? data.capacity : filter.capacity;
|
|
3419
|
-
filter.capacityUnits = typeof data.capacityUnits !== 'undefined' ? data.capacityUnits : filter.capacity;
|
|
3420
|
-
filter.connectionId = typeof data.connectionId !== 'undefined' ? data.connectionId : filter.connectionId;
|
|
3421
|
-
filter.deviceBinding = typeof data.deviceBinding !== 'undefined' ? data.deviceBinding : filter.deviceBinding;
|
|
3422
|
-
sfilter.pressureUnits = filter.pressureUnits;
|
|
3423
|
-
sfilter.calcCleanPercentage();
|
|
3424
|
-
sfilter.emitEquipmentChange();
|
|
3425
|
-
return filter; // Always return the config when we are dealing with the config not state.
|
|
3426
|
-
}
|
|
3427
|
-
public async deleteFilterAsync(data: any): Promise<Filter> {
|
|
3428
|
-
try {
|
|
3429
|
-
let id = typeof data.id === 'undefined' ? -1 : parseInt(data.id, 10);
|
|
3430
|
-
let filter = sys.filters.getItemById(id);
|
|
3431
|
-
let sfilter = state.filters.getItemById(filter.id);
|
|
3432
|
-
filter.isActive = false;
|
|
3433
|
-
sys.filters.removeItemById(id);
|
|
3434
|
-
state.filters.removeItemById(id);
|
|
3435
|
-
sfilter.emitEquipmentChange();
|
|
3436
|
-
return filter;
|
|
3437
|
-
} catch (err) { logger.error(`deleteFilterAsync: Error deleting filter ${err.message}`); }
|
|
4107
|
+
else {
|
|
4108
|
+
logger.verbose(`Heater for body ${hon.name} is currently on ${hon.heatStatus} filter pressure for cleaning skipped.`);
|
|
4109
|
+
}
|
|
4110
|
+
}
|
|
4111
|
+
sfilter.emitEquipmentChange();
|
|
3438
4112
|
}
|
|
4113
|
+
catch (err) { logger.error(`setFilterPressure: Error setting filter #${id} pressure to ${pressure}${units || ''}`); }
|
|
4114
|
+
}
|
|
4115
|
+
public async setFilterStateAsync(filter: Filter, fstate: FilterState, isOn: boolean) { fstate.isOn = isOn; }
|
|
4116
|
+
public async setFilterAsync(data: any): Promise<Filter> {
|
|
4117
|
+
let id = typeof data.id === 'undefined' ? -1 : parseInt(data.id, 10);
|
|
4118
|
+
if (id <= 0) id = sys.filters.length + 1; // set max filters?
|
|
4119
|
+
if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError(`Invalid filter id: ${data.id}`, data.id, 'Filter'));
|
|
4120
|
+
let filter = sys.filters.getItemById(id, id > 0);
|
|
4121
|
+
let sfilter = state.filters.getItemById(id, id > 0);
|
|
4122
|
+
let filterType = typeof data.filterType !== 'undefined' ? parseInt(data.filterType, 10) : filter.filterType;
|
|
4123
|
+
if (typeof filterType === 'undefined') filterType = sys.board.valueMaps.filterTypes.getValue('unknown');
|
|
4124
|
+
|
|
4125
|
+
// The only way to delete a filter is to call deleteFilterAsync.
|
|
4126
|
+
//if (typeof data.isActive !== 'undefined') {
|
|
4127
|
+
// if (utils.makeBool(data.isActive) === false) {
|
|
4128
|
+
// sys.filters.removeItemById(id);
|
|
4129
|
+
// state.filters.removeItemById(id);
|
|
4130
|
+
// return;
|
|
4131
|
+
// }
|
|
4132
|
+
//}
|
|
4133
|
+
|
|
4134
|
+
let body = typeof data.body !== 'undefined' ? data.body : filter.body;
|
|
4135
|
+
let name = typeof data.name !== 'undefined' ? data.name : filter.name;
|
|
4136
|
+
if (typeof body === 'undefined') body = 32;
|
|
4137
|
+
// At this point we should have all the data. Validate it.
|
|
4138
|
+
if (!sys.board.valueMaps.filterTypes.valExists(filterType)) return Promise.reject(new InvalidEquipmentDataError(`Invalid filter type; ${filterType}`, 'Filter', filterType));
|
|
4139
|
+
|
|
4140
|
+
filter.pressureUnits = typeof data.pressureUnits !== 'undefined' ? data.pressureUnits || 0 : filter.pressureUnits || 0;
|
|
4141
|
+
filter.pressureCircuitId = parseInt(data.pressureCircuitId || filter.pressureCircuitId || 6, 10);
|
|
4142
|
+
filter.cleanPressure = parseFloat(data.cleanPressure || filter.cleanPressure || 0);
|
|
4143
|
+
filter.dirtyPressure = parseFloat(data.dirtyPressure || filter.dirtyPressure || 0);
|
|
4144
|
+
|
|
4145
|
+
filter.filterType = sfilter.filterType = filterType;
|
|
4146
|
+
filter.body = sfilter.body = body;
|
|
4147
|
+
filter.name = sfilter.name = name;
|
|
4148
|
+
filter.capacity = typeof data.capacity === 'number' ? data.capacity : filter.capacity;
|
|
4149
|
+
filter.capacityUnits = typeof data.capacityUnits !== 'undefined' ? data.capacityUnits : filter.capacity;
|
|
4150
|
+
filter.connectionId = typeof data.connectionId !== 'undefined' ? data.connectionId : filter.connectionId;
|
|
4151
|
+
filter.deviceBinding = typeof data.deviceBinding !== 'undefined' ? data.deviceBinding : filter.deviceBinding;
|
|
4152
|
+
sfilter.pressureUnits = filter.pressureUnits;
|
|
4153
|
+
sfilter.calcCleanPercentage();
|
|
4154
|
+
sfilter.emitEquipmentChange();
|
|
4155
|
+
return filter; // Always return the config when we are dealing with the config not state.
|
|
4156
|
+
}
|
|
4157
|
+
public async deleteFilterAsync(data: any): Promise<Filter> {
|
|
4158
|
+
try {
|
|
4159
|
+
let id = typeof data.id === 'undefined' ? -1 : parseInt(data.id, 10);
|
|
4160
|
+
let filter = sys.filters.getItemById(id);
|
|
4161
|
+
let sfilter = state.filters.getItemById(filter.id);
|
|
4162
|
+
filter.isActive = false;
|
|
4163
|
+
sys.filters.removeItemById(id);
|
|
4164
|
+
state.filters.removeItemById(id);
|
|
4165
|
+
sfilter.emitEquipmentChange();
|
|
4166
|
+
return filter;
|
|
4167
|
+
} catch (err) { logger.error(`deleteFilterAsync: Error deleting filter ${err.message}`); }
|
|
4168
|
+
}
|
|
3439
4169
|
}
|