nodejs-poolcontroller 7.2.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 +13 -0
- package/Dockerfile +1 -0
- package/README.md +5 -5
- package/app.ts +11 -0
- package/config/Config.ts +3 -0
- package/config/VersionCheck.ts +8 -4
- package/controller/Constants.ts +165 -9
- package/controller/Equipment.ts +186 -65
- package/controller/Errors.ts +22 -1
- package/controller/State.ts +273 -57
- package/controller/boards/EasyTouchBoard.ts +194 -95
- package/controller/boards/IntelliCenterBoard.ts +115 -42
- package/controller/boards/IntelliTouchBoard.ts +104 -30
- package/controller/boards/NixieBoard.ts +155 -53
- package/controller/boards/SystemBoard.ts +1529 -514
- package/controller/comms/Comms.ts +219 -42
- package/controller/comms/messages/Messages.ts +16 -4
- package/controller/comms/messages/config/ChlorinatorMessage.ts +13 -3
- package/controller/comms/messages/config/CircuitGroupMessage.ts +6 -0
- package/controller/comms/messages/config/CircuitMessage.ts +1 -1
- 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 +15 -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 +14 -6
- package/controller/comms/messages/status/EquipmentStateMessage.ts +78 -24
- package/controller/comms/messages/status/HeaterStateMessage.ts +25 -5
- package/controller/comms/messages/status/IntelliChemStateMessage.ts +55 -26
- package/controller/nixie/Nixie.ts +18 -16
- package/controller/nixie/NixieEquipment.ts +6 -6
- package/controller/nixie/bodies/Body.ts +7 -4
- package/controller/nixie/bodies/Filter.ts +7 -4
- package/controller/nixie/chemistry/ChemController.ts +800 -283
- package/controller/nixie/chemistry/Chlorinator.ts +22 -14
- package/controller/nixie/circuits/Circuit.ts +42 -7
- package/controller/nixie/heaters/Heater.ts +303 -30
- package/controller/nixie/pumps/Pump.ts +57 -30
- package/controller/nixie/schedules/Schedule.ts +10 -7
- package/controller/nixie/valves/Valve.ts +7 -5
- package/defaultConfig.json +32 -1
- package/issue_template.md +1 -1
- package/logger/DataLogger.ts +37 -22
- package/package.json +20 -18
- package/web/Server.ts +529 -31
- package/web/bindings/influxDB.json +157 -5
- package/web/bindings/mqtt.json +112 -13
- package/web/bindings/mqttAlt.json +109 -11
- package/web/interfaces/baseInterface.ts +2 -1
- package/web/interfaces/httpInterface.ts +2 -0
- package/web/interfaces/influxInterface.ts +103 -54
- package/web/interfaces/mqttInterface.ts +16 -5
- package/web/services/config/Config.ts +179 -43
- package/web/services/state/State.ts +51 -5
- package/web/services/state/StateSocket.ts +19 -2
|
@@ -16,14 +16,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
16
16
|
*/
|
|
17
17
|
import * as extend from 'extend';
|
|
18
18
|
import { logger } from '../../logger/Logger';
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
|
|
19
|
+
import { Message, Outbound } from '../comms/messages/Messages';
|
|
20
|
+
import { Timestamp, utils } from '../Constants';
|
|
21
|
+
import { Body, ChemController, Chlorinator, Circuit, CircuitGroup, CircuitGroupCircuit, ConfigVersion, ControllerType, CustomName, CustomNameCollection, EggTimer, Equipment, Feature, Filter, General, Heater, ICircuit, LightGroup, LightGroupCircuit, Location, Options, Owner, PoolSystem, Pump, Schedule, sys, TempSensorCollection, Valve } from '../Equipment';
|
|
22
|
+
import { EquipmentNotFoundError, InvalidEquipmentDataError, InvalidEquipmentIdError } from '../Errors';
|
|
23
|
+
import { ncp } from "../nixie/Nixie";
|
|
24
|
+
import { BodyTempState, ChemControllerState, ChlorinatorState, CircuitGroupState, FilterState, ICircuitGroupState, ICircuitState, LightGroupState, ScheduleState, state, TemperatureState, ValveState, VirtualCircuitState } from '../State';
|
|
25
|
+
import { RestoreResults } from '../../web/Server';
|
|
26
|
+
|
|
27
27
|
|
|
28
28
|
export class byteValueMap extends Map<number, any> {
|
|
29
29
|
public transform(byte: number, ext?: number) { return extend(true, { val: byte || 0 }, this.get(byte) || this.get(0)); }
|
|
@@ -218,10 +218,10 @@ export class byteValueMaps {
|
|
|
218
218
|
]);
|
|
219
219
|
public panelModes: byteValueMap = new byteValueMap([
|
|
220
220
|
[0, { val: 0, name: 'auto', desc: 'Auto' }],
|
|
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' }],
|
|
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' }],
|
|
225
225
|
[255, { name: 'error', desc: 'System Error' }]
|
|
226
226
|
]);
|
|
227
227
|
public controllerStatus: byteValueMap = new byteValueMap([
|
|
@@ -246,7 +246,9 @@ export class byteValueMaps {
|
|
|
246
246
|
[15, { name: 'floorcleaner', desc: 'Floor Cleaner' }],
|
|
247
247
|
[16, { name: 'intellibrite', desc: 'Intellibrite', isLight: true }],
|
|
248
248
|
[17, { name: 'magicstream', desc: 'Magicstream', isLight: true }],
|
|
249
|
-
[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' }]
|
|
250
252
|
]);
|
|
251
253
|
|
|
252
254
|
// Feature functions are used as the available options to define a circuit.
|
|
@@ -289,6 +291,26 @@ export class byteValueMaps {
|
|
|
289
291
|
[254, { name: 'unknown', desc: 'unknown' }],
|
|
290
292
|
[255, { name: 'none', desc: 'None' }]
|
|
291
293
|
]);
|
|
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
|
+
]);
|
|
313
|
+
|
|
292
314
|
public lightColors: byteValueMap = new byteValueMap([
|
|
293
315
|
[0, { name: 'white', desc: 'White' }],
|
|
294
316
|
[2, { name: 'lightgreen', desc: 'Light Green' }],
|
|
@@ -416,7 +438,9 @@ export class byteValueMaps {
|
|
|
416
438
|
]);
|
|
417
439
|
public bodyTypes: byteValueMap = new byteValueMap([
|
|
418
440
|
[0, { name: 'pool', desc: 'Pool' }],
|
|
419
|
-
[1, { name: 'spa', desc: 'Spa' }]
|
|
441
|
+
[1, { name: 'spa', desc: 'Spa' }],
|
|
442
|
+
[2, { name: 'spa', desc: 'Spa' }],
|
|
443
|
+
[3, { name: 'spa', desc: 'Spa' }]
|
|
420
444
|
]);
|
|
421
445
|
public bodies: byteValueMap = new byteValueMap([
|
|
422
446
|
[0, { name: 'pool', desc: 'Pool' }],
|
|
@@ -442,6 +466,19 @@ export class byteValueMaps {
|
|
|
442
466
|
[2, { name: 'aquarite', desc: 'Aquarite' }],
|
|
443
467
|
[3, { name: 'unknown', desc: 'unknown' }]
|
|
444
468
|
]);
|
|
469
|
+
public chlorinatorModel: byteValueMap = new byteValueMap([
|
|
470
|
+
[0, { name: 'unknown', desc: 'unknown', capacity: 0, chlorinePerDay: 0, chlorinePerSec: 0 }],
|
|
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 }]
|
|
481
|
+
])
|
|
445
482
|
public customNames: byteValueMap = new byteValueMap();
|
|
446
483
|
public circuitNames: byteValueMap = new byteValueMap();
|
|
447
484
|
public scheduleTypes: byteValueMap = new byteValueMap([
|
|
@@ -458,6 +495,10 @@ export class byteValueMaps {
|
|
|
458
495
|
[0, { name: 'off', desc: 'Off' }],
|
|
459
496
|
[1, { name: 'on', desc: 'On' }]
|
|
460
497
|
]);
|
|
498
|
+
public systemUnits: byteValueMap = new byteValueMap([
|
|
499
|
+
[0, { name: 'english', desc: 'English' }],
|
|
500
|
+
[4, { name: 'metric', desc: 'Metric' }]
|
|
501
|
+
]);
|
|
461
502
|
public tempUnits: byteValueMap = new byteValueMap([
|
|
462
503
|
[0, { name: 'F', desc: 'Fahrenheit' }],
|
|
463
504
|
[4, { name: 'C', desc: 'Celsius' }]
|
|
@@ -482,7 +523,7 @@ export class byteValueMaps {
|
|
|
482
523
|
[0, { name: 'none', desc: 'None', ph: { min: 6.8, max: 7.6 }, orp: { min: 400, max: 800 }, hasAddress: false }],
|
|
483
524
|
[1, { name: 'unknown', desc: 'Unknown', ph: { min: 6.8, max: 7.6 }, hasAddress: false }],
|
|
484
525
|
[2, { name: 'intellichem', desc: 'IntelliChem', ph: { min: 7.2, max: 7.6 }, orp: { min: 400, max: 800 }, hasAddress: true }],
|
|
485
|
-
[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 }],
|
|
486
527
|
[4, { name: 'rem', desc: 'REM Chem', ph: { min: 6.8, max: 8.0 }, hasAddress: false }]
|
|
487
528
|
]);
|
|
488
529
|
public siCalcTypes: byteValueMap = new byteValueMap([
|
|
@@ -510,12 +551,15 @@ export class byteValueMaps {
|
|
|
510
551
|
[2, { name: 'rate', desc: 'Rate Sensor', remAddress: true }],
|
|
511
552
|
[4, { name: 'pressure', desc: 'Pressure Sensor', remAddress: true }],
|
|
512
553
|
]);
|
|
513
|
-
|
|
514
554
|
public chemDosingMethods: byteValueMap = new byteValueMap([
|
|
515
555
|
[0, { name: 'manual', desc: 'Manual' }],
|
|
516
556
|
[1, { name: 'time', desc: 'Time' }],
|
|
517
557
|
[2, { name: 'volume', desc: 'Volume' }]
|
|
518
558
|
]);
|
|
559
|
+
public chemChlorDosingMethods: byteValueMap = new byteValueMap([
|
|
560
|
+
[0, { name: 'chlor', desc: 'Use Chlorinator Settings' }],
|
|
561
|
+
[1, { name: 'target', desc: 'Dynamic based on ORP Setpoint' }]
|
|
562
|
+
]);
|
|
519
563
|
public phSupplyTypes: byteValueMap = new byteValueMap([
|
|
520
564
|
[0, { name: 'base', desc: 'Base pH+' }],
|
|
521
565
|
[1, { name: 'acid', desc: 'Acid pH-' }]
|
|
@@ -530,6 +574,14 @@ export class byteValueMaps {
|
|
|
530
574
|
[6, { name: 'qt', desc: 'Quarts' }],
|
|
531
575
|
[7, { name: 'pt', desc: 'Pints' }]
|
|
532
576
|
]);
|
|
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
|
+
]);
|
|
584
|
+
|
|
533
585
|
public areaUnits: byteValueMap = new byteValueMap([
|
|
534
586
|
[0, { name: '', desc: 'No Units' }],
|
|
535
587
|
[1, { name: 'sqft', desc: 'Square Feet' }],
|
|
@@ -767,27 +819,36 @@ export class SystemBoard {
|
|
|
767
819
|
/// relays. This method does not control RS485 operations such as pumps and chlorinators. These are done through the respective
|
|
768
820
|
/// equipment polling functions.
|
|
769
821
|
public async processStatusAsync() {
|
|
822
|
+
let self = this;
|
|
770
823
|
try {
|
|
771
824
|
if (this._statusCheckRef > 0) return;
|
|
772
825
|
this.suspendStatus(true);
|
|
773
826
|
if (typeof this._statusTimer !== 'undefined' && this._statusTimer) clearTimeout(this._statusTimer);
|
|
774
827
|
// Go through all the assigned equipment and verify the current state.
|
|
775
828
|
sys.board.system.keepManualTime();
|
|
776
|
-
await sys.board.
|
|
777
|
-
await sys.board.
|
|
778
|
-
await sys.board.circuits.syncVirtualCircuitStates();
|
|
779
|
-
await sys.board.valves.syncValveStates();
|
|
780
|
-
await sys.board.filters.syncFilterStates();
|
|
781
|
-
await sys.board.heaters.syncHeaterStates();
|
|
829
|
+
await sys.board.bodies.syncFreezeProtection();
|
|
830
|
+
await sys.board.syncEquipmentItems();
|
|
782
831
|
await sys.board.schedules.syncScheduleStates();
|
|
832
|
+
await sys.board.circuits.checkEggTimerExpirationAsync();
|
|
783
833
|
state.emitControllerChange();
|
|
784
834
|
state.emitEquipmentChanges();
|
|
785
835
|
} catch (err) { state.status = 255; logger.error(`Error performing processStatusAsync ${err.message}`); }
|
|
786
836
|
finally {
|
|
787
837
|
this.suspendStatus(false);
|
|
788
|
-
if (this.statusInterval > 0) this._statusTimer = setTimeout(() =>
|
|
838
|
+
if (this.statusInterval > 0) this._statusTimer = setTimeout(async () => await self.processStatusAsync(), this.statusInterval);
|
|
789
839
|
}
|
|
790
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();
|
|
849
|
+
}
|
|
850
|
+
catch (err) { logger.error(`Error synchronizing equipment items: ${err.message}`); }
|
|
851
|
+
}
|
|
791
852
|
public async setControllerType(obj): Promise<Equipment> {
|
|
792
853
|
try {
|
|
793
854
|
if (obj.controllerType !== sys.controllerType)
|
|
@@ -865,6 +926,76 @@ export class BoardCommands {
|
|
|
865
926
|
constructor(parent: SystemBoard) { this.board = parent; }
|
|
866
927
|
}
|
|
867
928
|
export class SystemCommands extends BoardCommands {
|
|
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];
|
|
937
|
+
try {
|
|
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: [] } };
|
|
966
|
+
|
|
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
|
+
}
|
|
868
999
|
public cancelDelay(): Promise<any> { state.delay = sys.board.valueMaps.delay.getValue('nodelay'); return Promise.resolve(state.data.delay); }
|
|
869
1000
|
public setDateTimeAsync(obj: any): Promise<any> { return Promise.resolve(); }
|
|
870
1001
|
public keepManualTime() {
|
|
@@ -929,6 +1060,9 @@ export class SystemCommands extends BoardCommands {
|
|
|
929
1060
|
if (obj.clockSource === 'server') sys.board.system.setTZ();
|
|
930
1061
|
sys.board.system.setTempSensorsAsync(obj);
|
|
931
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;
|
|
932
1066
|
return new Promise<Options>(function (resolve, reject) { resolve(sys.general.options); });
|
|
933
1067
|
}
|
|
934
1068
|
public async setLocationAsync(obj: any): Promise<Location> {
|
|
@@ -959,7 +1093,10 @@ export class SystemCommands extends BoardCommands {
|
|
|
959
1093
|
state.temps.waterSensor1 = sys.equipment.tempSensors.getCalibration('water1') + temp;
|
|
960
1094
|
let body = state.temps.bodies.getItemById(1);
|
|
961
1095
|
if (body.isOn) body.temp = state.temps.waterSensor1;
|
|
962
|
-
|
|
1096
|
+
else if (sys.equipment.shared) {
|
|
1097
|
+
body = state.temps.bodies.getItemById(2);
|
|
1098
|
+
if (body.isOn) body.temp = state.temps.waterSensor1;
|
|
1099
|
+
}
|
|
963
1100
|
}
|
|
964
1101
|
break;
|
|
965
1102
|
case 'waterSensor2':
|
|
@@ -1132,17 +1269,182 @@ export class SystemCommands extends BoardCommands {
|
|
|
1132
1269
|
}
|
|
1133
1270
|
}
|
|
1134
1271
|
export class BodyCommands extends BoardCommands {
|
|
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];
|
|
1277
|
+
try {
|
|
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);
|
|
1385
|
+
}
|
|
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);
|
|
1404
|
+
}
|
|
1405
|
+
else {
|
|
1406
|
+
sstate.freezeProtect = false;
|
|
1407
|
+
pstate.freezeProtect = true;
|
|
1408
|
+
await sys.board.circuits.setCircuitStateAsync(6, true);
|
|
1409
|
+
}
|
|
1410
|
+
// Set a new date as this will be our rotation check now.
|
|
1411
|
+
this.freezeProtectBodyOn = new Date();
|
|
1412
|
+
}
|
|
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);
|
|
1426
|
+
}
|
|
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
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
catch (err) { logger.error(`syncFreezeProtection: Error synchronizing freeze protection states`); }
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1135
1437
|
public async initFilters() {
|
|
1136
1438
|
try {
|
|
1137
1439
|
let filter: Filter;
|
|
1138
1440
|
let sFilter: FilterState;
|
|
1139
1441
|
if (sys.equipment.maxBodies > 0) {
|
|
1140
1442
|
filter = sys.filters.getItemById(1, true, { filterType: 3, name: sys.equipment.shared ? 'Filter' : 'Filter 1' });
|
|
1141
|
-
sFilter = state.filters.getItemById(1, true, { name: filter.name });
|
|
1443
|
+
sFilter = state.filters.getItemById(1, true, { id: 1, name: filter.name });
|
|
1142
1444
|
filter.isActive = true;
|
|
1143
1445
|
filter.master = sys.board.equipmentMaster;
|
|
1144
1446
|
filter.body = sys.equipment.shared ? sys.board.valueMaps.bodies.transformByName('poolspa') : 0;
|
|
1145
|
-
sFilter = state.filters.getItemById(1, true);
|
|
1447
|
+
//sFilter = state.filters.getItemById(1, true);
|
|
1146
1448
|
sFilter.body = filter.body;
|
|
1147
1449
|
sFilter.filterType = filter.filterType;
|
|
1148
1450
|
sFilter.name = filter.name;
|
|
@@ -1268,53 +1570,54 @@ export class BodyCommands extends BoardCommands {
|
|
|
1268
1570
|
sys.board.heaters.syncHeaterStates();
|
|
1269
1571
|
return Promise.resolve(bstate);
|
|
1270
1572
|
}
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
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
|
+
}
|
|
1318
1621
|
public getPoolStates(): BodyTempState[] {
|
|
1319
1622
|
let arrPools = [];
|
|
1320
1623
|
for (let i = 0; i < state.temps.bodies.length; i++) {
|
|
@@ -1382,6 +1685,56 @@ export class BodyCommands extends BoardCommands {
|
|
|
1382
1685
|
}
|
|
1383
1686
|
}
|
|
1384
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
|
+
|
|
1385
1738
|
public getPumpTypes() { return this.board.valueMaps.pumpTypes.toArray(); }
|
|
1386
1739
|
public getCircuitUnits(pump?: Pump) {
|
|
1387
1740
|
if (typeof pump === 'undefined')
|
|
@@ -1446,14 +1799,15 @@ export class PumpCommands extends BoardCommands {
|
|
|
1446
1799
|
// and props that aren't for this pump type
|
|
1447
1800
|
let _id = pump.id;
|
|
1448
1801
|
if (pump.type !== pumpType || pumpType === 0) {
|
|
1449
|
-
|
|
1802
|
+
let _p = pump.get(true);
|
|
1803
|
+
// const _isVirtual = typeof _p.isVirtual !== 'undefined' ? _p.isVirtual : false;
|
|
1450
1804
|
sys.pumps.removeItemById(_id);
|
|
1451
|
-
|
|
1452
|
-
if (_isVirtual) {
|
|
1805
|
+
pump = sys.pumps.getItemById(_id, true);
|
|
1806
|
+
/* if (_isVirtual) {
|
|
1453
1807
|
// pump.isActive = true;
|
|
1454
1808
|
// pump.isVirtual = true;
|
|
1455
1809
|
pump.master = 1;
|
|
1456
|
-
}
|
|
1810
|
+
} */
|
|
1457
1811
|
state.pumps.removeItemById(pump.id);
|
|
1458
1812
|
pump.type = pumpType;
|
|
1459
1813
|
let type = sys.board.valueMaps.pumpTypes.transform(pumpType);
|
|
@@ -1505,10 +1859,164 @@ export class PumpCommands extends BoardCommands {
|
|
|
1505
1859
|
}
|
|
1506
1860
|
}
|
|
1507
1861
|
export class CircuitCommands extends BoardCommands {
|
|
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];
|
|
1867
|
+
try {
|
|
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];
|
|
1874
|
+
try {
|
|
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];
|
|
1881
|
+
try {
|
|
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
|
+
}
|
|
1508
2016
|
public async syncCircuitRelayStates() {
|
|
1509
2017
|
try {
|
|
1510
2018
|
for (let i = 0; i < sys.circuits.length; i++) {
|
|
1511
|
-
// Run through all the
|
|
2019
|
+
// Run through all the controlled circuits to see whether they should be triggered or not.
|
|
1512
2020
|
let circ = sys.circuits.getItemByIndex(i);
|
|
1513
2021
|
if (circ.master === 1 && circ.isActive) {
|
|
1514
2022
|
let cstate = state.circuits.getItemById(circ.id);
|
|
@@ -1517,7 +2025,6 @@ export class CircuitCommands extends BoardCommands {
|
|
|
1517
2025
|
}
|
|
1518
2026
|
} catch (err) { logger.error(`syncCircuitRelayStates: Error synchronizing circuit relays ${err.message}`); }
|
|
1519
2027
|
}
|
|
1520
|
-
|
|
1521
2028
|
public syncVirtualCircuitStates() {
|
|
1522
2029
|
try {
|
|
1523
2030
|
let arrCircuits = sys.board.valueMaps.virtualCircuits.toArray();
|
|
@@ -1650,7 +2157,6 @@ export class CircuitCommands extends BoardCommands {
|
|
|
1650
2157
|
//[13, { name: 'spa', desc: 'Spa', hasHeatSource: true }]
|
|
1651
2158
|
let func = sys.board.valueMaps.circuitFunctions.get(circuit.type);
|
|
1652
2159
|
if (newState && (func.name === 'pool' || func.name === 'spa') && sys.equipment.shared === true) {
|
|
1653
|
-
console.log(`Turning off shared body circuit`);
|
|
1654
2160
|
// If we are shared we need to turn off the other circuit.
|
|
1655
2161
|
let offType = func.name === 'pool' ? sys.board.valueMaps.circuitFunctions.getValue('spa') : sys.board.valueMaps.circuitFunctions.getValue('pool');
|
|
1656
2162
|
let off = sys.circuits.get().filter(elem => elem.type === offType);
|
|
@@ -1665,14 +2171,13 @@ export class CircuitCommands extends BoardCommands {
|
|
|
1665
2171
|
else if (id === 1) state.temps.bodies.getItemById(2, true).isOn = val;
|
|
1666
2172
|
// Let the main nixie controller set the circuit state and affect the relays if it needs to.
|
|
1667
2173
|
await ncp.circuits.setCircuitStateAsync(circ, newState);
|
|
2174
|
+
await sys.board.syncEquipmentItems();
|
|
1668
2175
|
return state.circuits.getInterfaceById(circ.id);
|
|
1669
2176
|
}
|
|
1670
2177
|
catch (err) { return Promise.reject(`Nixie: Error setCircuitStateAsync ${err.message}`); }
|
|
1671
2178
|
finally {
|
|
1672
|
-
// sys.board.virtualPumpControllers.start();
|
|
1673
2179
|
ncp.pumps.syncPumpStates();
|
|
1674
2180
|
sys.board.suspendStatus(false);
|
|
1675
|
-
this.board.processStatusAsync();
|
|
1676
2181
|
state.emitEquipmentChanges();
|
|
1677
2182
|
}
|
|
1678
2183
|
}
|
|
@@ -1770,7 +2275,7 @@ export class CircuitCommands extends BoardCommands {
|
|
|
1770
2275
|
if (typeof data.id !== 'undefined') {
|
|
1771
2276
|
let circuit = sys.circuits.getItemById(id, true);
|
|
1772
2277
|
let scircuit = state.circuits.getItemById(id, true);
|
|
1773
|
-
circuit.isActive = true;
|
|
2278
|
+
scircuit.isActive = circuit.isActive = true;
|
|
1774
2279
|
circuit.master = 1;
|
|
1775
2280
|
scircuit.isOn = false;
|
|
1776
2281
|
if (data.name) circuit.name = scircuit.name = data.name;
|
|
@@ -1788,6 +2293,7 @@ export class CircuitCommands extends BoardCommands {
|
|
|
1788
2293
|
if (typeof data.deviceBinding !== 'undefined') circuit.deviceBinding = data.deviceBinding;
|
|
1789
2294
|
if (typeof data.showInFeatures !== 'undefined') scircuit.showInFeatures = circuit.showInFeatures = utils.makeBool(data.showInFeatures);
|
|
1790
2295
|
circuit.dontStop = circuit.eggTimer === 1440;
|
|
2296
|
+
|
|
1791
2297
|
sys.emitEquipmentChange();
|
|
1792
2298
|
state.emitEquipmentChanges();
|
|
1793
2299
|
if (circuit.master === 1) await ncp.circuits.setCircuitAsync(circuit, data);
|
|
@@ -1870,8 +2376,9 @@ export class CircuitCommands extends BoardCommands {
|
|
|
1870
2376
|
if (typeof id === 'undefined') return Promise.reject(new InvalidEquipmentIdError(`Max circuit light group id exceeded`, id, 'LightGroup'));
|
|
1871
2377
|
if (isNaN(id) || !sys.board.equipmentIds.circuitGroups.isInRange(id)) return Promise.reject(new InvalidEquipmentIdError(`Invalid circuit group id: ${obj.id}`, obj.id, 'LightGroup'));
|
|
1872
2378
|
group = sys.lightGroups.getItemById(id, true);
|
|
2379
|
+
let sgroup = state.lightGroups.getItemById(id, true);
|
|
1873
2380
|
return new Promise<LightGroup>((resolve, reject) => {
|
|
1874
|
-
if (typeof obj.name !== 'undefined') group.name = obj.name;
|
|
2381
|
+
if (typeof obj.name !== 'undefined') sgroup.name = group.name = obj.name;
|
|
1875
2382
|
if (typeof obj.dontStop !== 'undefined' && utils.makeBool(obj.dontStop) === true) obj.eggTimer = 1440;
|
|
1876
2383
|
if (typeof obj.eggTimer !== 'undefined') group.eggTimer = Math.min(Math.max(parseInt(obj.eggTimer, 10), 0), 1440);
|
|
1877
2384
|
group.dontStop = group.eggTimer === 1440;
|
|
@@ -1978,7 +2485,9 @@ export class CircuitCommands extends BoardCommands {
|
|
|
1978
2485
|
await sys.board.circuits.setCircuitStateAsync(c.circuit, false);
|
|
1979
2486
|
else if (!cstate.isOn && sys.board.valueMaps.lightThemes.getName(theme) !== 'off') await sys.board.circuits.setCircuitStateAsync(c.circuit, true);
|
|
1980
2487
|
}
|
|
1981
|
-
|
|
2488
|
+
let isOn = sys.board.valueMaps.lightThemes.getName(theme) === 'off' ? false : true;
|
|
2489
|
+
sys.board.circuits.setEndTime(grp, sgrp, isOn);
|
|
2490
|
+
sgrp.isOn = isOn;
|
|
1982
2491
|
// If we truly want to support themes in lightGroups we probably need to program
|
|
1983
2492
|
// the specific on/off toggles to enable that. For now this will go through the motions but it's just a pretender.
|
|
1984
2493
|
switch (theme) {
|
|
@@ -2058,46 +2567,122 @@ export class CircuitCommands extends BoardCommands {
|
|
|
2058
2567
|
public async setLightGroupStateAsync(id: number, val: boolean): Promise<ICircuitGroupState> {
|
|
2059
2568
|
return sys.board.circuits.setCircuitGroupStateAsync(id, val);
|
|
2060
2569
|
}
|
|
2061
|
-
public setEndTime(thing: ICircuit, thingState: ICircuitState, isOn: boolean) {
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2570
|
+
public setEndTime(thing: ICircuit, thingState: ICircuitState, isOn: boolean, bForce: boolean= false) {
|
|
2571
|
+
/*
|
|
2572
|
+
this is a generic fn for circuits, features, circuitGroups, lightGroups
|
|
2573
|
+
to set the end time based on the egg timer.
|
|
2574
|
+
it will be called from set[]StateAsync calls as well as when then state is
|
|
2575
|
+
eval'ed from status packets/external messages and schedule changes.
|
|
2576
|
+
instead of maintaining timers here which would increase the amount of
|
|
2577
|
+
emits substantially, let the clients keep their own local timers
|
|
2578
|
+
or just display the end time.
|
|
2579
|
+
|
|
2580
|
+
bForce is an override sent by the syncScheduleStates. It gets set after the circuit gets set but we need to know if the sched is on. This allows the circuit end time to be
|
|
2581
|
+
re-evaluated even though it already has an end time.
|
|
2582
|
+
|
|
2583
|
+
Logic gets fun here...
|
|
2584
|
+
0. If the circuit is off, or has don't stop enabled, don't set an end time
|
|
2585
|
+
0.1. If the circuit state hasn't changed, abort (unless bForce is true).
|
|
2586
|
+
1. If the schedule is on, the egg timer does not come into play
|
|
2587
|
+
2. If the schedule is off...
|
|
2588
|
+
2.1. and the egg timer will turn off the circuit off before the schedule starts, use egg timer time
|
|
2589
|
+
2.2. else if the schedule will start before the egg timer turns it off, use the schedule end time
|
|
2590
|
+
3. Iterate over each schedule for 1-2 above; nearest end time wins
|
|
2591
|
+
*/
|
|
2592
|
+
try {
|
|
2593
|
+
if (thing.dontStop || !isOn) {
|
|
2594
|
+
thingState.endTime = undefined;
|
|
2595
|
+
}
|
|
2596
|
+
else if (!thingState.isOn && isOn || bForce) {
|
|
2597
|
+
let endTime: Timestamp;
|
|
2598
|
+
let eggTimerEndTime: Timestamp;
|
|
2599
|
+
// let remainingDuration: number;
|
|
2600
|
+
if (typeof thing.eggTimer !== 'undefined') {
|
|
2601
|
+
eggTimerEndTime = state.time.clone().addHours(0, thing.eggTimer);
|
|
2602
|
+
}
|
|
2603
|
+
// egg timers don't come into play if a schedule will control the circuit
|
|
2604
|
+
for (let i = 0; i < sys.schedules.length; i++) {
|
|
2605
|
+
let sched = sys.schedules.getItemByIndex(i);
|
|
2606
|
+
let ssched = state.schedules.getItemById(sched.id);
|
|
2607
|
+
if (sched.isActive && sys.board.schedules.includesCircuit(sched, thing.id)) {
|
|
2608
|
+
let nearestStartTime = sys.board.schedules.getNearestStartTime(sched);
|
|
2609
|
+
let nearestEndTime = sys.board.schedules.getNearestEndTime(sched);
|
|
2610
|
+
// if the schedule doesn't have an end date (eg no days)...
|
|
2611
|
+
if (nearestEndTime.getTime() === 0) continue;
|
|
2612
|
+
if (ssched.isOn) {
|
|
2613
|
+
if (typeof endTime === 'undefined' || nearestEndTime.getTime() < endTime.getTime()) {
|
|
2614
|
+
endTime = nearestEndTime.clone();
|
|
2615
|
+
eggTimerEndTime = undefined;
|
|
2616
|
+
}
|
|
2617
|
+
}
|
|
2618
|
+
else {
|
|
2619
|
+
if (typeof eggTimerEndTime !== 'undefined' && eggTimerEndTime.getTime() < nearestStartTime.getTime()) {
|
|
2620
|
+
if (typeof endTime === 'undefined' || eggTimerEndTime.getTime() < endTime.getTime()) endTime = eggTimerEndTime.clone();
|
|
2621
|
+
}
|
|
2622
|
+
else if (typeof endTime === 'undefined' || nearestEndTime.getTime() < endTime.getTime()) endTime = nearestEndTime.clone();
|
|
2623
|
+
}
|
|
2624
|
+
}
|
|
2094
2625
|
}
|
|
2626
|
+
if (typeof endTime !== 'undefined') thingState.endTime = endTime;
|
|
2627
|
+
else if (typeof eggTimerEndTime !== 'undefined') thingState.endTime = eggTimerEndTime;
|
|
2095
2628
|
}
|
|
2096
|
-
|
|
2629
|
+
}
|
|
2630
|
+
catch (err) {
|
|
2631
|
+
logger.error(`Error setting end time for ${thing.id}: ${err}`)
|
|
2097
2632
|
}
|
|
2098
2633
|
}
|
|
2099
2634
|
}
|
|
2100
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
|
+
|
|
2101
2686
|
public async setFeatureAsync(obj: any): Promise<Feature> {
|
|
2102
2687
|
let id = parseInt(obj.id, 10);
|
|
2103
2688
|
if (id <= 0 || isNaN(id)) {
|
|
@@ -2151,7 +2736,6 @@ export class FeatureCommands extends BoardCommands {
|
|
|
2151
2736
|
sys.board.circuits.setEndTime(feature, fstate, val);
|
|
2152
2737
|
fstate.isOn = val;
|
|
2153
2738
|
sys.board.valves.syncValveStates();
|
|
2154
|
-
// sys.board.virtualPumpControllers.start();
|
|
2155
2739
|
ncp.pumps.syncPumpStates();
|
|
2156
2740
|
state.emitEquipmentChanges();
|
|
2157
2741
|
return fstate;
|
|
@@ -2214,6 +2798,57 @@ export class FeatureCommands extends BoardCommands {
|
|
|
2214
2798
|
}
|
|
2215
2799
|
}
|
|
2216
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
|
+
|
|
2217
2852
|
public async setChlorAsync(obj: any): Promise<ChlorinatorState> {
|
|
2218
2853
|
try {
|
|
2219
2854
|
let id = parseInt(obj.id, 10);
|
|
@@ -2232,7 +2867,7 @@ export class ChlorinatorCommands extends BoardCommands {
|
|
|
2232
2867
|
public async deleteChlorAsync(obj: any): Promise<ChlorinatorState> {
|
|
2233
2868
|
try {
|
|
2234
2869
|
let id = parseInt(obj.id, 10);
|
|
2235
|
-
if (isNaN(id)) obj.id
|
|
2870
|
+
if (isNaN(id)) return Promise.reject(new InvalidEquipmentDataError(`Chlorinator id is not valid: ${obj.id}`, 'chlorinator', obj.id));
|
|
2236
2871
|
let chlor = state.chlorinators.getItemById(id);
|
|
2237
2872
|
chlor.isActive = false;
|
|
2238
2873
|
await ncp.chlorinators.deleteChlorinatorAsync(id);
|
|
@@ -2256,6 +2891,56 @@ export class ChlorinatorCommands extends BoardCommands {
|
|
|
2256
2891
|
}
|
|
2257
2892
|
}
|
|
2258
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
|
+
|
|
2259
2944
|
public transformDays(val: any): number {
|
|
2260
2945
|
if (typeof val === 'number') return val;
|
|
2261
2946
|
let edays = sys.board.valueMaps.scheduleDays.toArray();
|
|
@@ -2376,6 +3061,9 @@ export class ScheduleCommands extends BoardCommands {
|
|
|
2376
3061
|
return Promise.reject(new InvalidEquipmentDataError(`Invalid circuit reference: ${circuit}`, 'Schedule', circuit));
|
|
2377
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));
|
|
2378
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);
|
|
2379
3067
|
sched.circuit = ssched.circuit = circuit;
|
|
2380
3068
|
sched.scheduleDays = ssched.scheduleDays = schedDays;
|
|
2381
3069
|
sched.scheduleType = ssched.scheduleType = schedType;
|
|
@@ -2391,6 +3079,7 @@ export class ScheduleCommands extends BoardCommands {
|
|
|
2391
3079
|
sched.startYear = startDate.getFullYear();
|
|
2392
3080
|
sched.startMonth = startDate.getMonth() + 1;
|
|
2393
3081
|
sched.startDay = startDate.getDate();
|
|
3082
|
+
sched.isActive = sched.startTime !== 0;
|
|
2394
3083
|
|
|
2395
3084
|
ssched.display = sched.display = display;
|
|
2396
3085
|
if (typeof sched.startDate === 'undefined')
|
|
@@ -2428,10 +3117,10 @@ export class ScheduleCommands extends BoardCommands {
|
|
|
2428
3117
|
(ssched.scheduleDays & dayVal) > 0 &&
|
|
2429
3118
|
ts >= ssched.startTime && ts <= ssched.endTime) schedIsOn = true
|
|
2430
3119
|
else schedIsOn = false;
|
|
2431
|
-
if (schedIsOn !== ssched.isOn){
|
|
3120
|
+
if (schedIsOn !== ssched.isOn) {
|
|
2432
3121
|
// if the schedule state changes, it may affect the end time
|
|
2433
|
-
sys.board.circuits.setEndTime(sys.circuits.getInterfaceById(ssched.circuit), scirc, ssched.isOn);
|
|
2434
3122
|
ssched.isOn = schedIsOn;
|
|
3123
|
+
sys.board.circuits.setEndTime(sys.circuits.getInterfaceById(ssched.circuit), scirc, scirc.isOn, true);
|
|
2435
3124
|
}
|
|
2436
3125
|
ssched.emitEquipmentChange();
|
|
2437
3126
|
}
|
|
@@ -2460,8 +3149,8 @@ export class ScheduleCommands extends BoardCommands {
|
|
|
2460
3149
|
let days = sys.board.valueMaps.scheduleDays.transform(sched.scheduleDays).days;
|
|
2461
3150
|
for (let i = 0; i < days.length; i++) {
|
|
2462
3151
|
let schedDay = days[i].dow;
|
|
2463
|
-
let dateDiff = schedDay + 7 - startDateDay % 7;
|
|
2464
|
-
if (schedDay === startDateDay && sched.endTime
|
|
3152
|
+
let dateDiff = (schedDay + 7 - startDateDay) % 7;
|
|
3153
|
+
if (schedDay === startDateDay && sched.endTime < todayTime) dateDiff = 7;
|
|
2465
3154
|
let endDateTime = startDate.clone().addHours(dateDiff * 24, sched.endTime);
|
|
2466
3155
|
if (nearestEndTime.getTime() === 0 || endDateTime.getTime() < nearestEndTime.getTime()) nearestEndTime = endDateTime;
|
|
2467
3156
|
}
|
|
@@ -2477,8 +3166,8 @@ export class ScheduleCommands extends BoardCommands {
|
|
|
2477
3166
|
let days = sys.board.valueMaps.scheduleDays.transform(sched.scheduleDays).days;
|
|
2478
3167
|
for (let i = 0; i < days.length; i++) {
|
|
2479
3168
|
let schedDay = days[i].dow;
|
|
2480
|
-
let dateDiff = schedDay + 7 - startDateDay % 7;
|
|
2481
|
-
if (schedDay === startDateDay && sched.startTime
|
|
3169
|
+
let dateDiff = (schedDay + 7 - startDateDay) % 7;
|
|
3170
|
+
if (schedDay === startDateDay && sched.startTime < todayTime) dateDiff = 7;
|
|
2482
3171
|
let startDateTime = startDate.clone().addHours(dateDiff * 24, sched.startTime);
|
|
2483
3172
|
if (nearestStartTime.getTime() === 0 || startDateTime.getTime() < nearestStartTime.getTime()) nearestStartTime = startDateTime;
|
|
2484
3173
|
}
|
|
@@ -2486,387 +3175,535 @@ export class ScheduleCommands extends BoardCommands {
|
|
|
2486
3175
|
}
|
|
2487
3176
|
}
|
|
2488
3177
|
export class HeaterCommands extends BoardCommands {
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
if (typeof type !== 'undefined') {
|
|
2517
|
-
switch (type.name) {
|
|
2518
|
-
case 'solar':
|
|
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
|
+
}
|
|
2519
3205
|
return true;
|
|
2520
|
-
}
|
|
2521
|
-
}
|
|
3206
|
+
} catch (err) { logger.error(`Error restoring heaters: ${err.message}`); res.addModuleError('system', `Error restoring heaters: ${err.message}`); return false; }
|
|
2522
3207
|
}
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
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
|
+
|
|
3228
|
+
public getInstalledHeaterTypes(body?: number): any {
|
|
3229
|
+
let heaters = sys.heaters.get();
|
|
3230
|
+
let types = sys.board.valueMaps.heaterTypes.toArray();
|
|
3231
|
+
let inst = { total: 0 };
|
|
3232
|
+
for (let i = 0; i < types.length; i++) if (types[i].name !== 'none') inst[types[i].name] = 0;
|
|
3233
|
+
for (let i = 0; i < heaters.length; i++) {
|
|
3234
|
+
let heater = heaters[i];
|
|
3235
|
+
if (typeof body !== 'undefined' && heater.body !== 'undefined') {
|
|
3236
|
+
if ((heater.body !== 32 && body !== heater.body + 1) || (heater.body === 32 && body > 2)) continue;
|
|
3237
|
+
}
|
|
3238
|
+
let type = types.find(elem => elem.val === heater.type);
|
|
3239
|
+
if (typeof type !== 'undefined') {
|
|
3240
|
+
if (inst[type.name] === 'undefined') inst[type.name] = 0;
|
|
3241
|
+
inst[type.name] = inst[type.name] + 1;
|
|
3242
|
+
if (heater.coolingEnabled === true && type.hasCoolSetpoint === true) inst['hasCoolSetpoint'] = true;
|
|
3243
|
+
inst.total++;
|
|
3244
|
+
}
|
|
2535
3245
|
}
|
|
2536
|
-
|
|
3246
|
+
return inst;
|
|
2537
3247
|
}
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
3248
|
+
public isSolarInstalled(body?: number): boolean {
|
|
3249
|
+
let heaters = sys.heaters.get();
|
|
3250
|
+
let types = sys.board.valueMaps.heaterTypes.toArray();
|
|
3251
|
+
for (let i = 0; i < heaters.length; i++) {
|
|
3252
|
+
let heater = heaters[i];
|
|
3253
|
+
if (typeof body !== 'undefined' && body !== heater.body) continue;
|
|
3254
|
+
let type = types.find(elem => elem.val === heater.type);
|
|
3255
|
+
if (typeof type !== 'undefined') {
|
|
3256
|
+
switch (type.name) {
|
|
3257
|
+
case 'solar':
|
|
3258
|
+
return true;
|
|
3259
|
+
}
|
|
3260
|
+
}
|
|
3261
|
+
}
|
|
2543
3262
|
}
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
for (var s in obj) {
|
|
2559
|
-
if (s === 'id') continue;
|
|
2560
|
-
heater[s] = obj[s];
|
|
2561
|
-
}
|
|
2562
|
-
}
|
|
2563
|
-
let hstate = state.heaters.getItemById(id, true);
|
|
2564
|
-
hstate.isVirtual = heater.isVirtual = true;
|
|
2565
|
-
hstate.name = heater.name;
|
|
2566
|
-
hstate.type = heater.type;
|
|
2567
|
-
heater.master = 1;
|
|
2568
|
-
if (heater.master === 1) await ncp.heaters.setHeaterAsync(heater, obj);
|
|
2569
|
-
await sys.board.heaters.updateHeaterServices();
|
|
2570
|
-
await sys.board.heaters.syncHeaterStates();
|
|
2571
|
-
return heater;
|
|
2572
|
-
} catch (err) { return Promise.reject(new Error(`Error setting heater configuration: ${err}`)); }
|
|
2573
|
-
}
|
|
2574
|
-
public async deleteHeaterAsync(obj: any): Promise<Heater> {
|
|
2575
|
-
try {
|
|
2576
|
-
let id = parseInt(obj.id, 10);
|
|
2577
|
-
if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError('Cannot delete. Heater Id is not valid.', obj.id, 'Heater'));
|
|
2578
|
-
let heater = sys.heaters.getItemById(id);
|
|
2579
|
-
heater.isActive = false;
|
|
2580
|
-
if (heater.master === 1) await ncp.heaters.deleteHeaterAsync(heater.id);
|
|
2581
|
-
sys.heaters.removeItemById(id);
|
|
2582
|
-
state.heaters.removeItemById(id);
|
|
2583
|
-
sys.board.heaters.updateHeaterServices();
|
|
2584
|
-
sys.board.heaters.syncHeaterStates();
|
|
2585
|
-
return heater;
|
|
2586
|
-
} catch (err) { return Promise.reject(`Error deleting heater: ${err.message}`) }
|
|
2587
|
-
}
|
|
2588
|
-
public updateHeaterServices() {
|
|
2589
|
-
let htypes = sys.board.heaters.getInstalledHeaterTypes();
|
|
2590
|
-
let solarInstalled = htypes.solar > 0;
|
|
2591
|
-
let heatPumpInstalled = htypes.heatpump > 0;
|
|
2592
|
-
let gasHeaterInstalled = htypes.gas > 0;
|
|
2593
|
-
|
|
2594
|
-
if (sys.heaters.length > 0) sys.board.valueMaps.heatSources = new byteValueMap([[0, { name: 'off', desc: 'Off' }]]);
|
|
2595
|
-
if (gasHeaterInstalled) sys.board.valueMaps.heatSources.set(3, { name: 'heater', desc: 'Heater' });
|
|
2596
|
-
if (solarInstalled && (gasHeaterInstalled || heatPumpInstalled)) sys.board.valueMaps.heatSources.merge([[5, { name: 'solar', desc: 'Solar Only' }], [21, { name: 'solarpref', desc: 'Solar Preferred' }]]);
|
|
2597
|
-
else if (solarInstalled) sys.board.valueMaps.heatSources.set(5, { name: 'solar', desc: 'Solar' });
|
|
2598
|
-
if (heatPumpInstalled && (gasHeaterInstalled || solarInstalled)) sys.board.valueMaps.heatSources.merge([[9, { name: 'heatpump', desc: 'Heatpump Only' }], [25, { name: 'heatpumppref', desc: 'Heat Pump Preferred' }]]);
|
|
2599
|
-
else if (heatPumpInstalled) sys.board.valueMaps.heatSources.set(9, { name: 'heatpump', desc: 'Heat Pump' });
|
|
2600
|
-
sys.board.valueMaps.heatSources.set(32, { name: 'nochange', desc: 'No Change' });
|
|
2601
|
-
|
|
2602
|
-
sys.board.valueMaps.heatModes = new byteValueMap([[0, { name: 'off', desc: 'Off' }]]);
|
|
2603
|
-
if (gasHeaterInstalled) sys.board.valueMaps.heatModes.set(3, { name: 'heater', desc: 'Heater' });
|
|
2604
|
-
if (solarInstalled && (gasHeaterInstalled || heatPumpInstalled)) sys.board.valueMaps.heatModes.merge([[5, { name: 'solar', desc: 'Solar Only' }], [21, { name: 'solarpref', desc: 'Solar Preferred' }]]);
|
|
2605
|
-
else if (solarInstalled) sys.board.valueMaps.heatModes.set(5, { name: 'solar', desc: 'Solar' });
|
|
2606
|
-
if (heatPumpInstalled && (gasHeaterInstalled || solarInstalled)) sys.board.valueMaps.heatModes.merge([[9, { name: 'heatpump', desc: 'Heatpump Only' }], [25, { name: 'heatpumppref', desc: 'Heat Pump Preferred' }]]);
|
|
2607
|
-
else if (heatPumpInstalled) sys.board.valueMaps.heatModes.set(9, { name: 'heatpump', desc: 'Heat Pump' });
|
|
2608
|
-
// Now set the body data.
|
|
2609
|
-
for (let i = 0; i < sys.bodies.length; i++) {
|
|
2610
|
-
let body = sys.bodies.getItemByIndex(i);
|
|
2611
|
-
let btemp = state.temps.bodies.getItemById(body.id, body.isActive !== false);
|
|
2612
|
-
let opts = sys.board.heaters.getInstalledHeaterTypes(body.id);
|
|
2613
|
-
btemp.heaterOptions = opts;
|
|
2614
|
-
}
|
|
2615
|
-
this.setActiveTempSensors();
|
|
2616
|
-
}
|
|
2617
|
-
public initTempSensors() {
|
|
2618
|
-
// Add in the potential sensors and delete the ones that shouldn't exist.
|
|
2619
|
-
let maxPairs = sys.equipment.maxBodies + (sys.equipment.shared ? -1 : 0);
|
|
2620
|
-
sys.equipment.tempSensors.getItemById('air', true, { id: 'air', isActive: true, calibration: 0 }).name = 'Air';
|
|
2621
|
-
sys.equipment.tempSensors.getItemById('water1', true, { id: 'water1', isActive: true, calibration: 0 }).name = maxPairs == 1 ? 'Water' : 'Body 1';
|
|
2622
|
-
sys.equipment.tempSensors.getItemById('solar1', true, { id: 'solar1', isActive: false, calibration: 0 }).name = maxPairs == 1 ? 'Solar' : 'Solar 1';
|
|
2623
|
-
if (maxPairs > 1) {
|
|
2624
|
-
sys.equipment.tempSensors.getItemById('water2', true, { id: 'water2', isActive: false, calibration: 0 }).name = 'Body 2';
|
|
2625
|
-
sys.equipment.tempSensors.getItemById('solar2', true, { id: 'solar2', isActive: false, calibration: 0 }).name = 'Solar 2';
|
|
3263
|
+
public isHeatPumpInstalled(body?: number): boolean {
|
|
3264
|
+
let heaters = sys.heaters.get();
|
|
3265
|
+
let types = sys.board.valueMaps.heaterTypes.toArray();
|
|
3266
|
+
for (let i = 0; i < heaters.length; i++) {
|
|
3267
|
+
let heater = heaters[i];
|
|
3268
|
+
if (typeof body !== 'undefined' && body !== heater.body) continue;
|
|
3269
|
+
let type = types.find(elem => elem.val === heater.type);
|
|
3270
|
+
if (typeof type !== 'undefined') {
|
|
3271
|
+
switch (type.name) {
|
|
3272
|
+
case 'heatpump':
|
|
3273
|
+
return true;
|
|
3274
|
+
}
|
|
3275
|
+
}
|
|
3276
|
+
}
|
|
2626
3277
|
}
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
3278
|
+
public setHeater(heater: Heater, obj?: any) {
|
|
3279
|
+
if (typeof obj !== undefined) {
|
|
3280
|
+
for (var s in obj)
|
|
3281
|
+
heater[s] = obj[s];
|
|
3282
|
+
}
|
|
2630
3283
|
}
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
3284
|
+
public async setHeaterAsync(obj: any): Promise<Heater> {
|
|
3285
|
+
try {
|
|
3286
|
+
let id = typeof obj.id === 'undefined' ? -1 : parseInt(obj.id, 10);
|
|
3287
|
+
if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError('Heater Id is not valid.', obj.id, 'Heater'));
|
|
3288
|
+
else if (id < 256 && id > 0) return Promise.reject(new InvalidEquipmentIdError('Virtual Heaters controlled by njspc must have an Id > 256.', obj.id, 'Heater'));
|
|
3289
|
+
let heater: Heater;
|
|
3290
|
+
if (id <= 0) {
|
|
3291
|
+
// We are adding a heater. In this case all heaters are virtual.
|
|
3292
|
+
let vheaters = sys.heaters.filter(h => h.master === 1);
|
|
3293
|
+
id = vheaters.length + 256;
|
|
3294
|
+
}
|
|
3295
|
+
heater = sys.heaters.getItemById(id, true);
|
|
3296
|
+
if (typeof obj !== undefined) {
|
|
3297
|
+
for (var s in obj) {
|
|
3298
|
+
if (s === 'id') continue;
|
|
3299
|
+
heater[s] = obj[s];
|
|
3300
|
+
}
|
|
3301
|
+
}
|
|
3302
|
+
let hstate = state.heaters.getItemById(id, true);
|
|
3303
|
+
//hstate.isVirtual = heater.isVirtual = true;
|
|
3304
|
+
hstate.name = heater.name;
|
|
3305
|
+
hstate.type = heater.type;
|
|
3306
|
+
heater.master = 1;
|
|
3307
|
+
if (heater.master === 1) await ncp.heaters.setHeaterAsync(heater, obj);
|
|
3308
|
+
await sys.board.heaters.updateHeaterServices();
|
|
3309
|
+
await sys.board.heaters.syncHeaterStates();
|
|
3310
|
+
return heater;
|
|
3311
|
+
} catch (err) { return Promise.reject(new Error(`Error setting heater configuration: ${err}`)); }
|
|
2634
3312
|
}
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
3313
|
+
public async deleteHeaterAsync(obj: any): Promise<Heater> {
|
|
3314
|
+
try {
|
|
3315
|
+
let id = parseInt(obj.id, 10);
|
|
3316
|
+
if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError('Cannot delete. Heater Id is not valid.', obj.id, 'Heater'));
|
|
3317
|
+
let heater = sys.heaters.getItemById(id);
|
|
3318
|
+
heater.isActive = false;
|
|
3319
|
+
if (heater.master === 1) await ncp.heaters.deleteHeaterAsync(heater.id);
|
|
3320
|
+
sys.heaters.removeItemById(id);
|
|
3321
|
+
state.heaters.removeItemById(id);
|
|
3322
|
+
sys.board.heaters.updateHeaterServices();
|
|
3323
|
+
sys.board.heaters.syncHeaterStates();
|
|
3324
|
+
return heater;
|
|
3325
|
+
} catch (err) { return Promise.reject(`Error deleting heater: ${err.message}`) }
|
|
2638
3326
|
}
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
3327
|
+
public updateHeaterServices() {
|
|
3328
|
+
let htypes = sys.board.heaters.getInstalledHeaterTypes();
|
|
3329
|
+
let solarInstalled = htypes.solar > 0;
|
|
3330
|
+
let heatPumpInstalled = htypes.heatpump > 0;
|
|
3331
|
+
let gasHeaterInstalled = htypes.gas > 0;
|
|
3332
|
+
|
|
3333
|
+
if (sys.heaters.length > 0) sys.board.valueMaps.heatSources = new byteValueMap([[0, { name: 'off', desc: 'Off' }]]);
|
|
3334
|
+
if (gasHeaterInstalled) sys.board.valueMaps.heatSources.set(3, { name: 'heater', desc: 'Heater' });
|
|
3335
|
+
if (solarInstalled && (gasHeaterInstalled || heatPumpInstalled)) sys.board.valueMaps.heatSources.merge([[5, { name: 'solar', desc: 'Solar Only' }], [21, { name: 'solarpref', desc: 'Solar Preferred' }]]);
|
|
3336
|
+
else if (solarInstalled) sys.board.valueMaps.heatSources.set(5, { name: 'solar', desc: 'Solar' });
|
|
3337
|
+
if (heatPumpInstalled && (gasHeaterInstalled || solarInstalled)) sys.board.valueMaps.heatSources.merge([[9, { name: 'heatpump', desc: 'Heatpump Only' }], [25, { name: 'heatpumppref', desc: 'Heat Pump Preferred' }]]);
|
|
3338
|
+
else if (heatPumpInstalled) sys.board.valueMaps.heatSources.set(9, { name: 'heatpump', desc: 'Heat Pump' });
|
|
3339
|
+
sys.board.valueMaps.heatSources.set(32, { name: 'nochange', desc: 'No Change' });
|
|
3340
|
+
|
|
3341
|
+
sys.board.valueMaps.heatModes = new byteValueMap([[0, { name: 'off', desc: 'Off' }]]);
|
|
3342
|
+
if (gasHeaterInstalled) sys.board.valueMaps.heatModes.set(3, { name: 'heater', desc: 'Heater' });
|
|
3343
|
+
if (solarInstalled && (gasHeaterInstalled || heatPumpInstalled)) sys.board.valueMaps.heatModes.merge([[5, { name: 'solar', desc: 'Solar Only' }], [21, { name: 'solarpref', desc: 'Solar Preferred' }]]);
|
|
3344
|
+
else if (solarInstalled) sys.board.valueMaps.heatModes.set(5, { name: 'solar', desc: 'Solar' });
|
|
3345
|
+
if (heatPumpInstalled && (gasHeaterInstalled || solarInstalled)) sys.board.valueMaps.heatModes.merge([[9, { name: 'heatpump', desc: 'Heatpump Only' }], [25, { name: 'heatpumppref', desc: 'Heat Pump Preferred' }]]);
|
|
3346
|
+
else if (heatPumpInstalled) sys.board.valueMaps.heatModes.set(9, { name: 'heatpump', desc: 'Heat Pump' });
|
|
3347
|
+
// Now set the body data.
|
|
3348
|
+
for (let i = 0; i < sys.bodies.length; i++) {
|
|
3349
|
+
let body = sys.bodies.getItemByIndex(i);
|
|
3350
|
+
let btemp = state.temps.bodies.getItemById(body.id, body.isActive !== false);
|
|
3351
|
+
let opts = sys.board.heaters.getInstalledHeaterTypes(body.id);
|
|
3352
|
+
btemp.heaterOptions = opts;
|
|
3353
|
+
}
|
|
3354
|
+
this.setActiveTempSensors();
|
|
2642
3355
|
}
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
// board. If this situation ever comes up we will see if it works. Whether it reports is another story
|
|
2675
|
-
// since the 2 message is short a byte for this.
|
|
2676
|
-
sensor.isActive = sys.equipment.shared ? sys.equipment.maxBodies > 4 : sys.equipment.maxBodies > 3;
|
|
2677
|
-
break;
|
|
2678
|
-
// Solar sensors are funny ducks. This is because they are for both heatpumps and solar and the equipment
|
|
2679
|
-
// can be installed on specific bodies. This will be true for heaters installed in expansion panels for *Touch, dual body systems,
|
|
2680
|
-
// and any IntelliCenter with more than one body. At some point simply implementing the multi-body functions for touch will make
|
|
2681
|
-
// this all work. This will only be with i10D or expansion panels.
|
|
2682
|
-
case 'solar1':
|
|
2683
|
-
// The first solar sensor is a funny duck in that it should be active for shared systems
|
|
2684
|
-
// if either body has an active solar heater or heatpump.
|
|
2685
|
-
htypes = sys.board.heaters.getInstalledHeaterTypes(1);
|
|
2686
|
-
if ('solar' in htypes || 'heatpump' in htypes) sensor.isActive = true;
|
|
2687
|
-
else if (sys.equipment.shared) {
|
|
2688
|
-
htypes = sys.board.heaters.getInstalledHeaterTypes(2);
|
|
2689
|
-
sensor.isActive = ('solar' in htypes || 'heatpump' in htypes);
|
|
2690
|
-
}
|
|
2691
|
-
else sensor.isActive = false;
|
|
2692
|
-
break;
|
|
2693
|
-
case 'solar2':
|
|
2694
|
-
if (sys.equipment.maxBodies > 1 + (sys.equipment.shared ? 1 : 0)) {
|
|
2695
|
-
htypes = sys.board.heaters.getInstalledHeaterTypes(2 + (sys.equipment.shared ? 1 : 0));
|
|
2696
|
-
sensor.isActive = ('solar' in htypes || 'heatpump' in htypes);
|
|
2697
|
-
}
|
|
2698
|
-
else sensor.isActive = false;
|
|
2699
|
-
break;
|
|
2700
|
-
case 'solar3':
|
|
2701
|
-
if (sys.equipment.maxBodies > 2 + (sys.equipment.shared ? 1 : 0)) {
|
|
2702
|
-
htypes = sys.board.heaters.getInstalledHeaterTypes(3 + (sys.equipment.shared ? 1 : 0));
|
|
2703
|
-
sensor.isActive = ('solar' in htypes || 'heatpump' in htypes);
|
|
2704
|
-
}
|
|
2705
|
-
else sensor.isActive = false;
|
|
2706
|
-
break;
|
|
2707
|
-
case 'solar4':
|
|
2708
|
-
if (sys.equipment.maxBodies > 3 + (sys.equipment.shared ? 1 : 0)) {
|
|
2709
|
-
htypes = sys.board.heaters.getInstalledHeaterTypes(4 + (sys.equipment.shared ? 1 : 0));
|
|
2710
|
-
sensor.isActive = ('solar' in htypes || 'heatpump' in htypes);
|
|
2711
|
-
}
|
|
2712
|
-
else sensor.isActive = false;
|
|
2713
|
-
break;
|
|
2714
|
-
default:
|
|
2715
|
-
if (typeof sensor.id === 'undefined') sys.equipment.tempSensors.removeItemByIndex(i);
|
|
2716
|
-
break;
|
|
2717
|
-
}
|
|
3356
|
+
public initTempSensors() {
|
|
3357
|
+
// Add in the potential sensors and delete the ones that shouldn't exist.
|
|
3358
|
+
let maxPairs = sys.equipment.maxBodies + (sys.equipment.shared ? -1 : 0);
|
|
3359
|
+
sys.equipment.tempSensors.getItemById('air', true, { id: 'air', isActive: true, calibration: 0 }).name = 'Air';
|
|
3360
|
+
sys.equipment.tempSensors.getItemById('water1', true, { id: 'water1', isActive: true, calibration: 0 }).name = maxPairs == 1 ? 'Water' : 'Body 1';
|
|
3361
|
+
sys.equipment.tempSensors.getItemById('solar1', true, { id: 'solar1', isActive: false, calibration: 0 }).name = maxPairs == 1 ? 'Solar' : 'Solar 1';
|
|
3362
|
+
if (maxPairs > 1) {
|
|
3363
|
+
sys.equipment.tempSensors.getItemById('water2', true, { id: 'water2', isActive: false, calibration: 0 }).name = 'Body 2';
|
|
3364
|
+
sys.equipment.tempSensors.getItemById('solar2', true, { id: 'solar2', isActive: false, calibration: 0 }).name = 'Solar 2';
|
|
3365
|
+
}
|
|
3366
|
+
else {
|
|
3367
|
+
sys.equipment.tempSensors.removeItemById('water2');
|
|
3368
|
+
sys.equipment.tempSensors.removeItemById('solar2');
|
|
3369
|
+
}
|
|
3370
|
+
if (maxPairs > 2) {
|
|
3371
|
+
sys.equipment.tempSensors.getItemById('water3', true, { id: 'water3', isActive: false, calibration: 0 }).name = 'Body 3';
|
|
3372
|
+
sys.equipment.tempSensors.getItemById('solar3', true, { id: 'solar3', isActive: false, calibration: 0 }).name = 'Solar 3';
|
|
3373
|
+
}
|
|
3374
|
+
else {
|
|
3375
|
+
sys.equipment.tempSensors.removeItemById('water3');
|
|
3376
|
+
sys.equipment.tempSensors.removeItemById('solar3');
|
|
3377
|
+
}
|
|
3378
|
+
if (maxPairs > 3) {
|
|
3379
|
+
sys.equipment.tempSensors.getItemById('water4', true, { id: 'water4', isActive: false, calibration: 0 }).name = 'Body 4';
|
|
3380
|
+
sys.equipment.tempSensors.getItemById('solar4', true, { id: 'solar4', isActive: false, calibration: 0 }).name = 'Solar 4';
|
|
3381
|
+
}
|
|
3382
|
+
else {
|
|
3383
|
+
sys.equipment.tempSensors.removeItemById('water4');
|
|
3384
|
+
sys.equipment.tempSensors.removeItemById('solar4');
|
|
3385
|
+
}
|
|
3386
|
+
|
|
2718
3387
|
}
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
let b = sys.board.valueMaps.bodies.transform(heater.body);
|
|
2742
|
-
switch (b.name) {
|
|
2743
|
-
case 'body1':
|
|
2744
|
-
case 'pool':
|
|
2745
|
-
if (body.id === 1) isAssociated = true;
|
|
2746
|
-
break;
|
|
2747
|
-
case 'body2':
|
|
2748
|
-
case 'spa':
|
|
2749
|
-
if (body.id === 2) isAssociated = true;
|
|
2750
|
-
break;
|
|
2751
|
-
case 'poolspa':
|
|
2752
|
-
if (body.id === 1 || body.id === 2) isAssociated = true;
|
|
2753
|
-
break;
|
|
2754
|
-
case 'body3':
|
|
2755
|
-
if (body.id === 3) isAssociated = true;
|
|
2756
|
-
break;
|
|
2757
|
-
case 'body4':
|
|
2758
|
-
if (body.id === 4) isAssociated = true;
|
|
2759
|
-
break;
|
|
2760
|
-
}
|
|
2761
|
-
logger.silly(`Heater ${heater.name} is ${isAssociated === true ? '' : 'not '}associated with ${body.name}`);
|
|
2762
|
-
if (isAssociated) {
|
|
2763
|
-
let htype = sys.board.valueMaps.heaterTypes.transform(heater.type);
|
|
2764
|
-
let status = sys.board.valueMaps.heatStatus.transform(body.heatStatus);
|
|
2765
|
-
let hstate = state.heaters.getItemById(heater.id, true);
|
|
2766
|
-
if (heater.isVirtual === true || heater.master === 1) {
|
|
2767
|
-
// We need to do our own calculation as to whether it is on. This is for Nixie heaters.
|
|
2768
|
-
let mode = sys.board.valueMaps.heatModes.getName(body.heatMode);
|
|
2769
|
-
switch (htype.name) {
|
|
2770
|
-
case 'solar':
|
|
2771
|
-
if (mode === 'solar' || mode === 'solarpref') {
|
|
2772
|
-
// Measure up against start and stop temp deltas for effective solar heating.
|
|
2773
|
-
if (body.temp < cfgBody.heatSetpoint &&
|
|
2774
|
-
state.temps.solar > body.temp + (hstate.isOn ? heater.stopTempDelta : heater.startTempDelta)) {
|
|
2775
|
-
isOn = true;
|
|
2776
|
-
body.heatStatus = sys.board.valueMaps.heatStatus.getValue('solar');
|
|
2777
|
-
isHeating = true;
|
|
2778
|
-
}
|
|
2779
|
-
else if (heater.coolingEnabled && body.temp > cfgBody.coolSetpoint && state.heliotrope.isNight &&
|
|
2780
|
-
state.temps.solar > body.temp + (hstate.isOn ? heater.stopTempDelta : heater.startTempDelta)) {
|
|
2781
|
-
isOn = true;
|
|
2782
|
-
body.heatStatus = sys.board.valueMaps.heatStatus.getValue('cooling');
|
|
2783
|
-
isHeating = true;
|
|
2784
|
-
}
|
|
2785
|
-
//else if (heater.coolingEnabled && state.time.isNight)
|
|
2786
|
-
}
|
|
3388
|
+
// Sets the active temp sensors based upon the installed equipment. At this point all
|
|
3389
|
+
// detectable temp sensors should exist.
|
|
3390
|
+
public setActiveTempSensors() {
|
|
3391
|
+
let htypes;
|
|
3392
|
+
// We are iterating backwards through the sensors array on purpose. We do this just in case we need
|
|
3393
|
+
// to remove a sensor during the iteration. This way the index values will not be impacted and we can
|
|
3394
|
+
// safely remove from the array we are iterating.
|
|
3395
|
+
for (let i = sys.equipment.tempSensors.length - 1; i >= 0; i--) {
|
|
3396
|
+
let sensor = sys.equipment.tempSensors.getItemByIndex(i);
|
|
3397
|
+
// The names are normalized in this array.
|
|
3398
|
+
switch (sensor.id) {
|
|
3399
|
+
case 'air':
|
|
3400
|
+
sensor.isActive = true;
|
|
3401
|
+
break;
|
|
3402
|
+
case 'water1':
|
|
3403
|
+
sensor.isActive = sys.equipment.maxBodies > 0;
|
|
3404
|
+
break;
|
|
3405
|
+
case 'water2':
|
|
3406
|
+
sensor.isActive = sys.equipment.shared ? sys.equipment.maxBodies > 2 : sys.equipment.maxBodies > 1;
|
|
3407
|
+
break;
|
|
3408
|
+
case 'water3':
|
|
3409
|
+
sensor.isActive = sys.equipment.shared ? sys.equipment.maxBodies > 3 : sys.equipment.maxBodies > 2;
|
|
2787
3410
|
break;
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
3411
|
+
case 'water4':
|
|
3412
|
+
// It's a little weird but technically you should be able to install 3 expansions and a i10D personality
|
|
3413
|
+
// board. If this situation ever comes up we will see if it works. Whether it reports is another story
|
|
3414
|
+
// since the 2 message is short a byte for this.
|
|
3415
|
+
sensor.isActive = sys.equipment.shared ? sys.equipment.maxBodies > 4 : sys.equipment.maxBodies > 3;
|
|
3416
|
+
break;
|
|
3417
|
+
// Solar sensors are funny ducks. This is because they are for both heatpumps and solar and the equipment
|
|
3418
|
+
// can be installed on specific bodies. This will be true for heaters installed in expansion panels for *Touch, dual body systems,
|
|
3419
|
+
// and any IntelliCenter with more than one body. At some point simply implementing the multi-body functions for touch will make
|
|
3420
|
+
// this all work. This will only be with i10D or expansion panels.
|
|
3421
|
+
case 'solar1':
|
|
3422
|
+
// The first solar sensor is a funny duck in that it should be active for shared systems
|
|
3423
|
+
// if either body has an active solar heater or heatpump.
|
|
3424
|
+
htypes = sys.board.heaters.getInstalledHeaterTypes(1);
|
|
3425
|
+
if ('solar' in htypes || 'heatpump' in htypes) sensor.isActive = true;
|
|
3426
|
+
else if (sys.equipment.shared) {
|
|
3427
|
+
htypes = sys.board.heaters.getInstalledHeaterTypes(2);
|
|
3428
|
+
sensor.isActive = ('solar' in htypes || 'heatpump' in htypes);
|
|
2801
3429
|
}
|
|
3430
|
+
else sensor.isActive = false;
|
|
2802
3431
|
break;
|
|
2803
|
-
|
|
2804
|
-
if (
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
body.heatStatus = sys.board.valueMaps.heatStatus.getValue('heater');
|
|
2808
|
-
isHeating = true;
|
|
2809
|
-
}
|
|
3432
|
+
case 'solar2':
|
|
3433
|
+
if (sys.equipment.maxBodies > 1 + (sys.equipment.shared ? 1 : 0)) {
|
|
3434
|
+
htypes = sys.board.heaters.getInstalledHeaterTypes(2 + (sys.equipment.shared ? 1 : 0));
|
|
3435
|
+
sensor.isActive = ('solar' in htypes || 'heatpump' in htypes);
|
|
2810
3436
|
}
|
|
2811
|
-
else
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
body.heatStatus = sys.board.valueMaps.heatStatus.getValue('heater');
|
|
2818
|
-
isHeating = true;
|
|
2819
|
-
}
|
|
3437
|
+
else sensor.isActive = false;
|
|
3438
|
+
break;
|
|
3439
|
+
case 'solar3':
|
|
3440
|
+
if (sys.equipment.maxBodies > 2 + (sys.equipment.shared ? 1 : 0)) {
|
|
3441
|
+
htypes = sys.board.heaters.getInstalledHeaterTypes(3 + (sys.equipment.shared ? 1 : 0));
|
|
3442
|
+
sensor.isActive = ('solar' in htypes || 'heatpump' in htypes);
|
|
2820
3443
|
}
|
|
3444
|
+
else sensor.isActive = false;
|
|
2821
3445
|
break;
|
|
2822
|
-
|
|
2823
|
-
if (
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
isOn = true;
|
|
2827
|
-
body.heatStatus = sys.board.valueMaps.heatStatus.getValue('heater');
|
|
2828
|
-
isHeating = true;
|
|
2829
|
-
}
|
|
3446
|
+
case 'solar4':
|
|
3447
|
+
if (sys.equipment.maxBodies > 3 + (sys.equipment.shared ? 1 : 0)) {
|
|
3448
|
+
htypes = sys.board.heaters.getInstalledHeaterTypes(4 + (sys.equipment.shared ? 1 : 0));
|
|
3449
|
+
sensor.isActive = ('solar' in htypes || 'heatpump' in htypes);
|
|
2830
3450
|
}
|
|
3451
|
+
else sensor.isActive = false;
|
|
2831
3452
|
break;
|
|
2832
|
-
|
|
2833
|
-
|
|
3453
|
+
default:
|
|
3454
|
+
if (typeof sensor.id === 'undefined') sys.equipment.tempSensors.removeItemByIndex(i);
|
|
2834
3455
|
break;
|
|
2835
|
-
}
|
|
2836
|
-
logger.debug(`Heater Type: ${htype.name} Mode:${mode} Temp: ${body.temp} Setpoint: ${cfgBody.setPoint} Status: ${body.heatStatus}`);
|
|
2837
|
-
}
|
|
2838
|
-
if (isOn === true && typeof hon.find(elem => elem === heater.id) === 'undefined') {
|
|
2839
|
-
hon.push(heater.id);
|
|
2840
|
-
if (heater.master === 1 && isOn) (async () => {
|
|
2841
|
-
try {
|
|
2842
|
-
await ncp.heaters.setHeaterStateAsync(hstate, isOn);
|
|
2843
|
-
} catch (err) { logger.error(err.message); }
|
|
2844
|
-
})();
|
|
2845
|
-
else hstate.isOn = isOn;
|
|
2846
|
-
}
|
|
2847
3456
|
}
|
|
2848
|
-
}
|
|
2849
3457
|
}
|
|
2850
|
-
|
|
2851
|
-
|
|
3458
|
+
}
|
|
3459
|
+
// This updates the heater states based upon the installed heaters. This is true for heaters that are tied to the OCP
|
|
3460
|
+
// and those that are not.
|
|
3461
|
+
public syncHeaterStates() {
|
|
3462
|
+
try {
|
|
3463
|
+
// Go through the installed heaters and bodies to determine whether they should be on. If there is a
|
|
3464
|
+
// heater that is not controlled by the OCP then we need to determine whether it should be on.
|
|
3465
|
+
let heaters = sys.heaters.toArray();
|
|
3466
|
+
let bodies = state.temps.bodies.toArray();
|
|
3467
|
+
let hon = [];
|
|
3468
|
+
for (let i = 0; i < bodies.length; i++) {
|
|
3469
|
+
let body: BodyTempState = bodies[i];
|
|
3470
|
+
let cfgBody: Body = sys.bodies.getItemById(body.id);
|
|
3471
|
+
let isHeating = false;
|
|
3472
|
+
if (body.isOn) {
|
|
3473
|
+
if (typeof body.temp === 'undefined' && heaters.length > 0) logger.warn(`The body temperature for ${body.name} cannot be determined. Heater status for this body cannot be calculated.`);
|
|
3474
|
+
for (let j = 0; j < heaters.length; j++) {
|
|
3475
|
+
let heater: Heater = heaters[j];
|
|
3476
|
+
if (heater.isActive === false) continue;
|
|
3477
|
+
let isOn = false;
|
|
3478
|
+
let isCooling = false;
|
|
3479
|
+
let sensorTemp = state.temps.waterSensor1;
|
|
3480
|
+
if (body.id === 4) sensorTemp = state.temps.waterSensor4;
|
|
3481
|
+
if (body.id === 3) sensorTemp = state.temps.waterSensor3;
|
|
3482
|
+
if (body.id === 2 && !sys.equipment.shared) sensorTemp = state.temps.waterSensor2;
|
|
3483
|
+
|
|
3484
|
+
// Determine whether the heater can be used on this body.
|
|
3485
|
+
let isAssociated = false;
|
|
3486
|
+
let b = sys.board.valueMaps.bodies.transform(heater.body);
|
|
3487
|
+
switch (b.name) {
|
|
3488
|
+
case 'body1':
|
|
3489
|
+
case 'pool':
|
|
3490
|
+
if (body.id === 1) isAssociated = true;
|
|
3491
|
+
break;
|
|
3492
|
+
case 'body2':
|
|
3493
|
+
case 'spa':
|
|
3494
|
+
if (body.id === 2) isAssociated = true;
|
|
3495
|
+
break;
|
|
3496
|
+
case 'poolspa':
|
|
3497
|
+
if (body.id === 1 || body.id === 2) isAssociated = true;
|
|
3498
|
+
break;
|
|
3499
|
+
case 'body3':
|
|
3500
|
+
if (body.id === 3) isAssociated = true;
|
|
3501
|
+
break;
|
|
3502
|
+
case 'body4':
|
|
3503
|
+
if (body.id === 4) isAssociated = true;
|
|
3504
|
+
break;
|
|
3505
|
+
}
|
|
3506
|
+
// logger.silly(`Heater ${heater.name} is ${isAssociated === true ? '' : 'not '}associated with ${body.name}`);
|
|
3507
|
+
if (isAssociated) {
|
|
3508
|
+
let htype = sys.board.valueMaps.heaterTypes.transform(heater.type);
|
|
3509
|
+
let status = sys.board.valueMaps.heatStatus.transform(body.heatStatus);
|
|
3510
|
+
let hstate = state.heaters.getItemById(heater.id, true);
|
|
3511
|
+
if (heater.master === 1) {
|
|
3512
|
+
// We need to do our own calculation as to whether it is on. This is for Nixie heaters.
|
|
3513
|
+
let mode = sys.board.valueMaps.heatModes.getName(body.heatMode);
|
|
3514
|
+
switch (htype.name) {
|
|
3515
|
+
case 'solar':
|
|
3516
|
+
if (mode === 'solar' || mode === 'solarpref') {
|
|
3517
|
+
// Measure up against start and stop temp deltas for effective solar heating.
|
|
3518
|
+
if (body.temp < cfgBody.heatSetpoint &&
|
|
3519
|
+
state.temps.solar > body.temp + (hstate.isOn ? heater.stopTempDelta : heater.startTempDelta)) {
|
|
3520
|
+
isOn = true;
|
|
3521
|
+
body.heatStatus = sys.board.valueMaps.heatStatus.getValue('solar');
|
|
3522
|
+
isHeating = true;
|
|
3523
|
+
}
|
|
3524
|
+
else if (heater.coolingEnabled && body.temp > cfgBody.coolSetpoint && state.heliotrope.isNight &&
|
|
3525
|
+
state.temps.solar > body.temp + (hstate.isOn ? heater.stopTempDelta : heater.startTempDelta)) {
|
|
3526
|
+
isOn = true;
|
|
3527
|
+
body.heatStatus = sys.board.valueMaps.heatStatus.getValue('cooling');
|
|
3528
|
+
isHeating = true;
|
|
3529
|
+
isCooling = true;
|
|
3530
|
+
}
|
|
3531
|
+
}
|
|
3532
|
+
break;
|
|
3533
|
+
case 'ultratemp':
|
|
3534
|
+
// We need to determine whether we are going to use the air temp or the solar temp
|
|
3535
|
+
// for the sensor.
|
|
3536
|
+
let deltaTemp = Math.max(state.temps.air, state.temps.solar || 0);
|
|
3537
|
+
if (mode === 'ultratemp' || mode === 'ultratemppref') {
|
|
3538
|
+
if (body.temp < cfgBody.heatSetpoint &&
|
|
3539
|
+
deltaTemp > body.temp + heater.differentialTemp || 0) {
|
|
3540
|
+
isOn = true;
|
|
3541
|
+
body.heatStatus = sys.board.valueMaps.heatStatus.getValue('hpheat');
|
|
3542
|
+
isHeating = true;
|
|
3543
|
+
isCooling = false;
|
|
3544
|
+
}
|
|
3545
|
+
else if (body.temp > cfgBody.coolSetpoint && heater.coolingEnabled) {
|
|
3546
|
+
isOn = true;
|
|
3547
|
+
body.heatStatus = sys.board.valueMaps.heatStatus.getValue('hpcool');
|
|
3548
|
+
isHeating = true;
|
|
3549
|
+
isCooling = true;
|
|
3550
|
+
}
|
|
3551
|
+
}
|
|
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':
|
|
3563
|
+
case 'gas':
|
|
3564
|
+
if (mode === 'heater') {
|
|
3565
|
+
if (body.temp < cfgBody.setPoint) {
|
|
3566
|
+
isOn = true;
|
|
3567
|
+
body.heatStatus = sys.board.valueMaps.heatStatus.getValue('heater');
|
|
3568
|
+
isHeating = true;
|
|
3569
|
+
}
|
|
3570
|
+
}
|
|
3571
|
+
else if (mode === 'solarpref' || mode === 'heatpumppref') {
|
|
3572
|
+
// If solar should be running gas heater should be off.
|
|
3573
|
+
if (body.temp < cfgBody.setPoint &&
|
|
3574
|
+
state.temps.solar > body.temp + (hstate.isOn ? heater.stopTempDelta : heater.startTempDelta)) isOn = false;
|
|
3575
|
+
else if (body.temp < cfgBody.setPoint) {
|
|
3576
|
+
isOn = true;
|
|
3577
|
+
body.heatStatus = sys.board.valueMaps.heatStatus.getValue('heater');
|
|
3578
|
+
isHeating = true;
|
|
3579
|
+
}
|
|
3580
|
+
}
|
|
3581
|
+
break;
|
|
3582
|
+
case 'heatpump':
|
|
3583
|
+
if (mode === 'heatpump' || mode === 'heatpumppref') {
|
|
3584
|
+
if (body.temp < cfgBody.setPoint &&
|
|
3585
|
+
state.temps.solar > body.temp + (hstate.isOn ? heater.stopTempDelta : heater.startTempDelta)) {
|
|
3586
|
+
isOn = true;
|
|
3587
|
+
body.heatStatus = sys.board.valueMaps.heatStatus.getValue('heater');
|
|
3588
|
+
isHeating = true;
|
|
3589
|
+
}
|
|
3590
|
+
}
|
|
3591
|
+
break;
|
|
3592
|
+
default:
|
|
3593
|
+
isOn = utils.makeBool(hstate.isOn);
|
|
3594
|
+
break;
|
|
3595
|
+
}
|
|
3596
|
+
logger.debug(`Heater Type: ${htype.name} Mode:${mode} Temp: ${body.temp} Setpoint: ${cfgBody.setPoint} Status: ${body.heatStatus}`);
|
|
3597
|
+
}
|
|
3598
|
+
else {
|
|
3599
|
+
let mode = sys.board.valueMaps.heatModes.getName(body.heatMode);
|
|
3600
|
+
switch (htype.name) {
|
|
3601
|
+
case 'mastertemp':
|
|
3602
|
+
if (status === 'mtheater') isHeating = isOn = true;
|
|
3603
|
+
break;
|
|
3604
|
+
case 'maxetherm':
|
|
3605
|
+
case 'gas':
|
|
3606
|
+
if (status === 'heater') isHeating = isOn = true;
|
|
3607
|
+
break;
|
|
3608
|
+
case 'hybrid':
|
|
3609
|
+
case 'ultratemp':
|
|
3610
|
+
case 'heatpump':
|
|
3611
|
+
if (mode === 'ultratemp' || mode === 'ultratemppref' || mode === 'heatpump' || mode === 'heatpumppref') {
|
|
3612
|
+
if (status === 'heater') isHeating = isOn = true;
|
|
3613
|
+
else if (status === 'cooling') isCooling = isOn = true;
|
|
3614
|
+
}
|
|
3615
|
+
break;
|
|
3616
|
+
case 'solar':
|
|
3617
|
+
if (mode === 'solar' || mode === 'solarpref') {
|
|
3618
|
+
if (status === 'solar') isHeating = isOn = true;
|
|
3619
|
+
else if (status === 'cooling') isCooling = isOn = true;
|
|
3620
|
+
}
|
|
3621
|
+
break;
|
|
3622
|
+
}
|
|
3623
|
+
}
|
|
3624
|
+
if (isOn === true && typeof hon.find(elem => elem === heater.id) === 'undefined') {
|
|
3625
|
+
hon.push(heater.id);
|
|
3626
|
+
if (heater.master === 1 && isOn) (async () => {
|
|
3627
|
+
try {
|
|
3628
|
+
await ncp.heaters.setHeaterStateAsync(hstate, isOn, isCooling);
|
|
3629
|
+
} catch (err) { logger.error(err.message); }
|
|
3630
|
+
})();
|
|
3631
|
+
else hstate.isOn = isOn;
|
|
3632
|
+
}
|
|
3633
|
+
}
|
|
3634
|
+
}
|
|
3635
|
+
}
|
|
3636
|
+
// When the controller is a virtual one we need to control the heat status ourselves.
|
|
3637
|
+
if (!isHeating && (sys.controllerType === ControllerType.Nixie)) body.heatStatus = 0;
|
|
3638
|
+
}
|
|
3639
|
+
// Turn off any heaters that should be off. The code above only turns heaters on.
|
|
3640
|
+
for (let i = 0; i < heaters.length; i++) {
|
|
3641
|
+
let heater: Heater = heaters[i];
|
|
3642
|
+
if (typeof hon.find(elem => elem === heater.id) === 'undefined') {
|
|
3643
|
+
let hstate = state.heaters.getItemById(heater.id, true);
|
|
3644
|
+
if (heater.master === 1) (async () => {
|
|
3645
|
+
try {
|
|
3646
|
+
await ncp.heaters.setHeaterStateAsync(hstate, false, false);
|
|
3647
|
+
} catch (err) { logger.error(err.message); }
|
|
3648
|
+
})();
|
|
3649
|
+
else hstate.isOn = false;
|
|
3650
|
+
}
|
|
3651
|
+
}
|
|
3652
|
+
} catch (err) { logger.error(`Error synchronizing heater states`); }
|
|
3653
|
+
}
|
|
3654
|
+
}
|
|
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}`); }
|
|
2852
3665
|
}
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
try {
|
|
2860
|
-
await ncp.heaters.setHeaterStateAsync(hstate, false);
|
|
2861
|
-
} catch (err) { logger.error(err.message); }
|
|
2862
|
-
})();
|
|
2863
|
-
else hstate.isOn = false;
|
|
2864
|
-
}
|
|
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}`); }
|
|
2865
3672
|
}
|
|
2866
|
-
|
|
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; }
|
|
2867
3685
|
}
|
|
2868
|
-
|
|
2869
|
-
|
|
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
|
+
|
|
2870
3707
|
public async setValveStateAsync(valve: Valve, vstate: ValveState, isDiverted: boolean) {
|
|
2871
3708
|
if (valve.master === 1) await ncp.valves.setValveStateAsync(vstate, isDiverted);
|
|
2872
3709
|
else
|
|
@@ -2944,6 +3781,60 @@ export class ValveCommands extends BoardCommands {
|
|
|
2944
3781
|
}
|
|
2945
3782
|
}
|
|
2946
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
|
+
|
|
2947
3838
|
public async deleteChemControllerAsync(data: any): Promise<ChemController> {
|
|
2948
3839
|
try {
|
|
2949
3840
|
let id = typeof data.id !== 'undefined' ? parseInt(data.id, 10) : -1;
|
|
@@ -3082,12 +3973,62 @@ export class ChemControllerCommands extends BoardCommands {
|
|
|
3082
3973
|
}
|
|
3083
3974
|
}
|
|
3084
3975
|
export class FilterCommands extends BoardCommands {
|
|
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];
|
|
3981
|
+
try {
|
|
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];
|
|
3989
|
+
try {
|
|
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
|
+
|
|
3085
4026
|
public async syncFilterStates() {
|
|
3086
4027
|
try {
|
|
3087
4028
|
for (let i = 0; i < sys.filters.length; i++) {
|
|
3088
4029
|
// Run through all the valves to see whether they should be triggered or not.
|
|
3089
4030
|
let filter = sys.filters.getItemByIndex(i);
|
|
3090
|
-
if (filter.isActive) {
|
|
4031
|
+
if (filter.isActive && !isNaN(filter.id)) {
|
|
3091
4032
|
let fstate = state.filters.getItemById(filter.id, true);
|
|
3092
4033
|
// Check to see if the associated body is on.
|
|
3093
4034
|
await sys.board.filters.setFilterStateAsync(filter, fstate, sys.board.bodies.isBodyOn(filter.body));
|
|
@@ -3095,8 +4036,84 @@ export class FilterCommands extends BoardCommands {
|
|
|
3095
4036
|
}
|
|
3096
4037
|
} catch (err) { logger.error(`syncFilterStates: Error synchronizing filters ${err.message}`); }
|
|
3097
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;
|
|
4093
|
+
}
|
|
4094
|
+
}
|
|
4095
|
+
}
|
|
4096
|
+
}
|
|
4097
|
+
}
|
|
4098
|
+
if (!fon) {
|
|
4099
|
+
// Finally we have a value we can believe in.
|
|
4100
|
+
sfilter.refPressure = pressure;
|
|
4101
|
+
}
|
|
4102
|
+
}
|
|
4103
|
+
else {
|
|
4104
|
+
logger.verbose(`Circuit ${con.id}-${con.name} is currently on filter pressure for cleaning ignored.`);
|
|
4105
|
+
}
|
|
4106
|
+
}
|
|
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();
|
|
4112
|
+
}
|
|
4113
|
+
catch (err) { logger.error(`setFilterPressure: Error setting filter #${id} pressure to ${pressure}${units || ''}`); }
|
|
4114
|
+
}
|
|
3098
4115
|
public async setFilterStateAsync(filter: Filter, fstate: FilterState, isOn: boolean) { fstate.isOn = isOn; }
|
|
3099
|
-
public
|
|
4116
|
+
public async setFilterAsync(data: any): Promise<Filter> {
|
|
3100
4117
|
let id = typeof data.id === 'undefined' ? -1 : parseInt(data.id, 10);
|
|
3101
4118
|
if (id <= 0) id = sys.filters.length + 1; // set max filters?
|
|
3102
4119
|
if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError(`Invalid filter id: ${data.id}`, data.id, 'Filter'));
|
|
@@ -3105,50 +4122,48 @@ export class FilterCommands extends BoardCommands {
|
|
|
3105
4122
|
let filterType = typeof data.filterType !== 'undefined' ? parseInt(data.filterType, 10) : filter.filterType;
|
|
3106
4123
|
if (typeof filterType === 'undefined') filterType = sys.board.valueMaps.filterTypes.getValue('unknown');
|
|
3107
4124
|
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
}
|
|
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
|
+
//}
|
|
3115
4133
|
|
|
3116
4134
|
let body = typeof data.body !== 'undefined' ? data.body : filter.body;
|
|
3117
4135
|
let name = typeof data.name !== 'undefined' ? data.name : filter.name;
|
|
3118
|
-
|
|
3119
|
-
let psi = typeof data.psi !== 'undefined' ? parseFloat(data.psi) : sfilter.psi;
|
|
3120
|
-
let lastCleanDate = typeof data.lastCleanDate !== 'undefined' ? data.lastCleanDate : sfilter.lastCleanDate;
|
|
3121
|
-
let filterPsi = typeof data.filterPsi !== 'undefined' ? parseInt(data.filterPsi, 10) : sfilter.filterPsi;
|
|
3122
|
-
let needsCleaning = typeof data.needsCleaning !== 'undefined' ? data.needsCleaning : sfilter.needsCleaning;
|
|
3123
|
-
|
|
3124
|
-
// Ensure all the defaults.
|
|
3125
|
-
if (isNaN(psi)) psi = 0;
|
|
3126
4136
|
if (typeof body === 'undefined') body = 32;
|
|
3127
|
-
|
|
3128
4137
|
// At this point we should have all the data. Validate it.
|
|
3129
4138
|
if (!sys.board.valueMaps.filterTypes.valExists(filterType)) return Promise.reject(new InvalidEquipmentDataError(`Invalid filter type; ${filterType}`, 'Filter', filterType));
|
|
3130
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
|
+
|
|
3131
4145
|
filter.filterType = sfilter.filterType = filterType;
|
|
3132
4146
|
filter.body = sfilter.body = body;
|
|
3133
|
-
filter.filterType = sfilter.filterType = filterType;
|
|
3134
4147
|
filter.name = sfilter.name = name;
|
|
3135
4148
|
filter.capacity = typeof data.capacity === 'number' ? data.capacity : filter.capacity;
|
|
3136
4149
|
filter.capacityUnits = typeof data.capacityUnits !== 'undefined' ? data.capacityUnits : filter.capacity;
|
|
3137
|
-
sfilter.psi = psi;
|
|
3138
|
-
sfilter.filterPsi = filterPsi;
|
|
3139
|
-
filter.needsCleaning = sfilter.needsCleaning = needsCleaning;
|
|
3140
|
-
filter.lastCleanDate = sfilter.lastCleanDate = lastCleanDate;
|
|
3141
4150
|
filter.connectionId = typeof data.connectionId !== 'undefined' ? data.connectionId : filter.connectionId;
|
|
3142
4151
|
filter.deviceBinding = typeof data.deviceBinding !== 'undefined' ? data.deviceBinding : filter.deviceBinding;
|
|
4152
|
+
sfilter.pressureUnits = filter.pressureUnits;
|
|
4153
|
+
sfilter.calcCleanPercentage();
|
|
3143
4154
|
sfilter.emitEquipmentChange();
|
|
3144
4155
|
return filter; // Always return the config when we are dealing with the config not state.
|
|
3145
4156
|
}
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
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}`); }
|
|
3153
4168
|
}
|
|
3154
|
-
}
|
|
4169
|
+
}
|