nodejs-poolcontroller 7.7.0 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +26 -35
- package/Changelog +22 -0
- package/README.md +7 -3
- package/anslq25/MessagesMock.ts +218 -0
- package/anslq25/boards/MockBoardFactory.ts +50 -0
- package/anslq25/boards/MockEasyTouchBoard.ts +696 -0
- package/anslq25/boards/MockSystemBoard.ts +217 -0
- package/anslq25/chemistry/MockChlorinator.ts +75 -0
- package/anslq25/pumps/MockPump.ts +84 -0
- package/app.ts +10 -14
- package/config/Config.ts +13 -9
- package/config/VersionCheck.ts +6 -2
- package/controller/Constants.ts +58 -25
- package/controller/Equipment.ts +224 -41
- package/controller/Errors.ts +2 -1
- package/controller/Lockouts.ts +34 -2
- package/controller/State.ts +491 -48
- package/controller/boards/AquaLinkBoard.ts +6 -3
- package/controller/boards/BoardFactory.ts +5 -1
- package/controller/boards/EasyTouchBoard.ts +1971 -1751
- package/controller/boards/IntelliCenterBoard.ts +1311 -1688
- package/controller/boards/IntelliComBoard.ts +7 -1
- package/controller/boards/IntelliTouchBoard.ts +153 -42
- package/controller/boards/NixieBoard.ts +209 -66
- package/controller/boards/SunTouchBoard.ts +393 -0
- package/controller/boards/SystemBoard.ts +1862 -1543
- package/controller/comms/Comms.ts +539 -138
- package/controller/comms/ScreenLogic.ts +1663 -0
- package/controller/comms/messages/Messages.ts +242 -60
- package/controller/comms/messages/config/ChlorinatorMessage.ts +4 -3
- package/controller/comms/messages/config/CircuitGroupMessage.ts +5 -2
- package/controller/comms/messages/config/CircuitMessage.ts +81 -13
- package/controller/comms/messages/config/ConfigMessage.ts +3 -1
- package/controller/comms/messages/config/CoverMessage.ts +2 -1
- package/controller/comms/messages/config/CustomNameMessage.ts +2 -1
- package/controller/comms/messages/config/EquipmentMessage.ts +5 -1
- package/controller/comms/messages/config/ExternalMessage.ts +33 -3
- package/controller/comms/messages/config/FeatureMessage.ts +2 -1
- package/controller/comms/messages/config/GeneralMessage.ts +2 -1
- package/controller/comms/messages/config/HeaterMessage.ts +3 -1
- package/controller/comms/messages/config/IntellichemMessage.ts +2 -1
- package/controller/comms/messages/config/OptionsMessage.ts +12 -6
- package/controller/comms/messages/config/PumpMessage.ts +9 -12
- package/controller/comms/messages/config/RemoteMessage.ts +80 -13
- package/controller/comms/messages/config/ScheduleMessage.ts +43 -3
- package/controller/comms/messages/config/SecurityMessage.ts +2 -1
- package/controller/comms/messages/config/ValveMessage.ts +43 -26
- package/controller/comms/messages/status/ChlorinatorStateMessage.ts +8 -7
- package/controller/comms/messages/status/EquipmentStateMessage.ts +93 -20
- package/controller/comms/messages/status/HeaterStateMessage.ts +24 -5
- package/controller/comms/messages/status/IntelliChemStateMessage.ts +7 -4
- package/controller/comms/messages/status/IntelliValveStateMessage.ts +2 -1
- package/controller/comms/messages/status/PumpStateMessage.ts +72 -4
- package/controller/comms/messages/status/VersionMessage.ts +2 -1
- package/controller/nixie/Nixie.ts +15 -4
- package/controller/nixie/NixieEquipment.ts +1 -0
- package/controller/nixie/chemistry/ChemController.ts +300 -129
- package/controller/nixie/chemistry/ChemDoser.ts +806 -0
- package/controller/nixie/chemistry/Chlorinator.ts +133 -129
- package/controller/nixie/circuits/Circuit.ts +171 -30
- package/controller/nixie/heaters/Heater.ts +337 -173
- package/controller/nixie/pumps/Pump.ts +264 -236
- package/controller/nixie/schedules/Schedule.ts +9 -3
- package/defaultConfig.json +45 -5
- package/logger/Logger.ts +38 -9
- package/package.json +13 -9
- package/web/Server.ts +235 -122
- package/web/bindings/aqualinkD.json +114 -59
- package/web/bindings/homeassistant.json +437 -0
- package/web/bindings/influxDB.json +15 -0
- package/web/bindings/mqtt.json +28 -9
- package/web/bindings/mqttAlt.json +15 -0
- package/web/interfaces/baseInterface.ts +58 -7
- package/web/interfaces/httpInterface.ts +5 -2
- package/web/interfaces/influxInterface.ts +9 -2
- package/web/interfaces/mqttInterface.ts +234 -74
- package/web/interfaces/ruleInterface.ts +87 -0
- package/web/services/config/Config.ts +140 -33
- package/web/services/config/ConfigSocket.ts +2 -1
- package/web/services/state/State.ts +144 -3
- package/web/services/state/StateSocket.ts +65 -14
- package/web/services/utilities/Utilities.ts +189 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* nodejs-poolController. An application to control pool equipment.
|
|
2
|
-
Copyright (C) 2016, 2017, 2018, 2019, 2020
|
|
2
|
+
Copyright (C) 2016, 2017, 2018, 2019, 2020, 2021, 2022.
|
|
3
|
+
Russell Goldin, tagyoureit. russ.goldin@gmail.com
|
|
3
4
|
|
|
4
5
|
This program is free software: you can redistribute it and/or modify
|
|
5
6
|
it under the terms of the GNU Affero General Public License as
|
|
@@ -18,12 +19,13 @@ import * as extend from 'extend';
|
|
|
18
19
|
import { logger } from '../../logger/Logger';
|
|
19
20
|
import { Message, Outbound } from '../comms/messages/Messages';
|
|
20
21
|
import { Timestamp, utils } from '../Constants';
|
|
21
|
-
import { Body, ChemController, Chlorinator, Circuit, CircuitGroup, CircuitGroupCircuit, ConfigVersion, ControllerType, CustomName, CustomNameCollection, EggTimer, Equipment, Feature, Filter, General, Heater, ICircuit, ICircuitGroup, ICircuitGroupCircuit, LightGroup, LightGroupCircuit, Location, Options, Owner, PoolSystem, Pump, Schedule, sys, TempSensorCollection, Valve } from '../Equipment';
|
|
22
|
+
import { Body, ChemController, ChemDoser, Chlorinator, Circuit, CircuitGroup, CircuitGroupCircuit, ConfigVersion, ControllerType, CustomName, CustomNameCollection, EggTimer, Equipment, Feature, Filter, General, Heater, ICircuit, ICircuitGroup, ICircuitGroupCircuit, LightGroup, LightGroupCircuit, Location, Options, Owner, PoolSystem, Pump, Schedule, sys, TempSensorCollection, Valve } from '../Equipment';
|
|
22
23
|
import { EquipmentNotFoundError, InvalidEquipmentDataError, InvalidEquipmentIdError, BoardProcessError, InvalidOperationError } from '../Errors';
|
|
23
24
|
import { ncp } from "../nixie/Nixie";
|
|
24
|
-
import { BodyTempState, ChemControllerState, ChlorinatorState, CircuitGroupState, FilterState, ICircuitGroupState, ICircuitState, LightGroupState, ScheduleState, state, TemperatureState, ValveState, VirtualCircuitState } from '../State';
|
|
25
|
+
import { BodyTempState, ChemControllerState, ChemDoserState, ChlorinatorState, CircuitGroupState, FilterState, ICircuitGroupState, ICircuitState, LightGroupState, ScheduleState, state, TemperatureState, ValveState, VirtualCircuitState } from '../State';
|
|
25
26
|
import { RestoreResults } from '../../web/Server';
|
|
26
|
-
import {
|
|
27
|
+
import { setTimeout } from 'timers/promises';
|
|
28
|
+
import { setTimeout as setTimeoutSync } from 'timers';
|
|
27
29
|
|
|
28
30
|
|
|
29
31
|
export class byteValueMap extends Map<number, any> {
|
|
@@ -265,7 +267,8 @@ export class byteValueMaps {
|
|
|
265
267
|
[134, { name: 'heatEnable', desc: 'Heat Enable', assignableToPumpCircuit: false }],
|
|
266
268
|
[135, { name: 'pumpSpeedUp', desc: 'Pump Speed +', assignableToPumpCircuit: false }],
|
|
267
269
|
[136, { name: 'pumpSpeedDown', desc: 'Pump Speed -', assignableToPumpCircuit: false }],
|
|
268
|
-
[255, { name: 'notused', desc: 'NOT USED', assignableToPumpCircuit: true }]
|
|
270
|
+
[255, { name: 'notused', desc: 'NOT USED', assignableToPumpCircuit: true }],
|
|
271
|
+
[258, { name: 'anyHeater', desc: 'Any Heater' }],
|
|
269
272
|
]);
|
|
270
273
|
public lightThemes: byteValueMap = new byteValueMap([
|
|
271
274
|
[0, { name: 'off', desc: 'Off' }],
|
|
@@ -330,7 +333,16 @@ export class byteValueMaps {
|
|
|
330
333
|
{ isOn: true, timeout: 100 },
|
|
331
334
|
{ isOn: false, timeout: 100 }
|
|
332
335
|
]
|
|
333
|
-
|
|
336
|
+
}],
|
|
337
|
+
[7, {
|
|
338
|
+
name: 'colorsync', desc: 'Sync', types: ['colorlogic'], command: 'colorSync', message: 'Synchronizing Lights', endingTheme: 'voodoolounge',
|
|
339
|
+
sequence: [
|
|
340
|
+
{ isOn: true, timeout: 1000 },
|
|
341
|
+
{ isOn: false, timeout: 12000 },
|
|
342
|
+
{ isOn: true }
|
|
343
|
+
]
|
|
344
|
+
}],
|
|
345
|
+
[100, { name: 'settheme', types: ['all'], desc: 'Set Theme', message: 'Sequencing Theme' }]
|
|
334
346
|
]);
|
|
335
347
|
public lightGroupCommands = new byteValueMap([
|
|
336
348
|
[1, { name: 'colorsync', desc: 'Sync', types: ['intellibrite'], command: 'colorSync', message:'Synchronizing' }],
|
|
@@ -364,7 +376,8 @@ export class byteValueMaps {
|
|
|
364
376
|
[4, { name: 'lighttheme', desc: 'Sequencing Theme/Color Operation' }],
|
|
365
377
|
[5, { name: 'colorhold', desc: 'Saving Current Color' }],
|
|
366
378
|
[6, { name: 'colorrecall', desc: 'Recalling Saved Color' }],
|
|
367
|
-
[7, { name: 'lightthumper', desc: 'Setting Light Thumper' }]
|
|
379
|
+
[7, { name: 'lightthumper', desc: 'Setting Light Thumper' }],
|
|
380
|
+
[100, { name: 'settheme', desc: 'Setting Light Theme' }]
|
|
368
381
|
]);
|
|
369
382
|
public lightColors: byteValueMap = new byteValueMap([
|
|
370
383
|
[0, { name: 'white', desc: 'White' }],
|
|
@@ -458,13 +471,15 @@ export class byteValueMaps {
|
|
|
458
471
|
[21, { name: 'solarpref', desc: 'Solar Preferred' }],
|
|
459
472
|
[32, { name: 'nochange', desc: 'No Change' }]
|
|
460
473
|
]);
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
474
|
+
public heatStatus: byteValueMap = new byteValueMap([
|
|
475
|
+
[0, { name: 'off', desc: 'Off' }],
|
|
476
|
+
[1, { name: 'heater', desc: 'Heater' }],
|
|
477
|
+
[2, { name: 'solar', desc: 'Solar' }],
|
|
478
|
+
[3, { name: 'cooling', desc: 'Cooling' }],
|
|
479
|
+
[4, { name: 'hpheat', desc: 'Heatpump' }],
|
|
480
|
+
[5, { name: 'dual', desc: 'Dual' }],
|
|
481
|
+
[128, { name: 'cooldown', desc: 'Cooldown' }]
|
|
482
|
+
]);
|
|
468
483
|
public pumpStatus: byteValueMap = new byteValueMap([
|
|
469
484
|
[0, { name: 'off', desc: 'Off' }], // When the pump is disconnected or has no power then we simply report off as the status. This is not the recommended wiring
|
|
470
485
|
// for a VS/VF pump as is should be powered at all times. When it is, the status will always report a value > 0.
|
|
@@ -573,6 +588,10 @@ export class byteValueMaps {
|
|
|
573
588
|
[2, { name: 'status', desc: 'Equipment Status' }],
|
|
574
589
|
[82, { name: 'ivstatus', desc: 'IntelliValve Status' }]
|
|
575
590
|
]);
|
|
591
|
+
public chemDoserTypes: byteValueMap = new byteValueMap([
|
|
592
|
+
[0, { name: 'acid', desc: 'Acid' }],
|
|
593
|
+
[1, { name: 'chlor', desc: 'Chlorine' }]
|
|
594
|
+
]);
|
|
576
595
|
public chemControllerTypes: byteValueMap = new byteValueMap([
|
|
577
596
|
[0, { name: 'none', desc: 'None', ph: { min: 6.8, max: 7.6 }, orp: { min: 400, max: 800 }, hasAddress: false }],
|
|
578
597
|
[1, { name: 'unknown', desc: 'Unknown', ph: { min: 6.8, max: 7.6 }, hasAddress: false }],
|
|
@@ -587,7 +606,7 @@ export class byteValueMaps {
|
|
|
587
606
|
public chemPumpTypes: byteValueMap = new byteValueMap([
|
|
588
607
|
[0, { name: 'none', desc: 'No Pump', ratedFlow: false, tank: false, remAddress: false }],
|
|
589
608
|
[1, { name: 'relay', desc: 'Relay Pump', ratedFlow: true, tank: true, remAddress: true }],
|
|
590
|
-
[2, { name: 'ezo-pmp', desc: 'Altas EZO-PMP', ratedFlow:
|
|
609
|
+
[2, { name: 'ezo-pmp', desc: 'Altas EZO-PMP', ratedFlow: true, tank: true, remAddress: true }]
|
|
591
610
|
]);
|
|
592
611
|
public chemPhProbeTypes: byteValueMap = new byteValueMap([
|
|
593
612
|
[0, { name: 'none', desc: 'No Probe' }],
|
|
@@ -658,6 +677,43 @@ export class byteValueMaps {
|
|
|
658
677
|
[1, { name: 'nocomms', desc: 'No Communication' }],
|
|
659
678
|
[2, { name: 'config', desc: 'Invalid Configuration' }]
|
|
660
679
|
]);
|
|
680
|
+
public chemDoserStatus: byteValueMap = new byteValueMap([
|
|
681
|
+
[0, { name: 'ok', desc: 'Ok' }],
|
|
682
|
+
[1, { name: 'nocomms', desc: 'No Communication' }],
|
|
683
|
+
[2, { name: 'config', desc: 'Invalid Configuration' }]
|
|
684
|
+
]);
|
|
685
|
+
public chemDoserHardwareFaults: byteValueMap = new byteValueMap([
|
|
686
|
+
[0, { name: 'ok', desc: 'Ok - No Faults' }],
|
|
687
|
+
[2, { name: 'pump', desc: 'Pump Fault' }],
|
|
688
|
+
[5, { name: 'chlormismatch', desc: 'Chlorinator body mismatch' }],
|
|
689
|
+
[6, { name: 'invalidbody', desc: 'Body capacity not valid' }],
|
|
690
|
+
[7, { name: 'flowsensor', desc: 'Flow Sensor Fault' }]
|
|
691
|
+
]);
|
|
692
|
+
public chemDoserAlarms: byteValueMap = new byteValueMap([
|
|
693
|
+
[0, { name: 'ok', desc: 'Ok - No alarm' }],
|
|
694
|
+
[1, { name: 'noflow', desc: 'No Flow Detected' }],
|
|
695
|
+
[32, { name: 'tankempty', desc: 'Tank Empty' }],
|
|
696
|
+
[129, { name: 'tanklow', desc: 'Tank Low' }],
|
|
697
|
+
[131, { name: 'freezeprotect', desc: 'Freeze Protection Lockout' }]
|
|
698
|
+
]);
|
|
699
|
+
public chemDoserWarnings: byteValueMap = new byteValueMap([
|
|
700
|
+
[0, { name: 'ok', desc: 'Ok - No Warning' }],
|
|
701
|
+
[8, { name: 'invalidsetup', desc: 'Invalid Setup' }],
|
|
702
|
+
[16, { name: 'chlorinatorComms', desc: 'Chlorinator Comms Error' }]
|
|
703
|
+
]);
|
|
704
|
+
public chemDoserLimits: byteValueMap = new byteValueMap([
|
|
705
|
+
[0, { name: 'ok', desc: 'Ok - No limits reached' }],
|
|
706
|
+
[1, { name: 'lockout', desc: 'Lockout - Chemical will not dose' }],
|
|
707
|
+
[2, { name: 'dailylimit', desc: 'Daily Limit Reached' }],
|
|
708
|
+
[128, { name: 'commslost', desc: 'Communications with Chem Doser Lost' }]
|
|
709
|
+
]);
|
|
710
|
+
public chemDoserDosingStatus: byteValueMap = new byteValueMap([
|
|
711
|
+
[0, { name: 'dosing', desc: 'Dosing' }],
|
|
712
|
+
[1, { name: 'mixing', desc: 'Mixing' }],
|
|
713
|
+
[2, { name: 'monitoring', desc: 'Monitoring' }]
|
|
714
|
+
]);
|
|
715
|
+
|
|
716
|
+
|
|
661
717
|
public chemControllerAlarms: byteValueMap = new byteValueMap([
|
|
662
718
|
[0, { name: 'ok', desc: 'Ok - No alarm' }],
|
|
663
719
|
[1, { name: 'noflow', desc: 'No Flow Detected' }],
|
|
@@ -681,7 +737,6 @@ export class byteValueMaps {
|
|
|
681
737
|
[5, { name: 'chlormismatch', desc: 'Chlorinator body mismatch' }],
|
|
682
738
|
[6, { name: 'invalidbody', desc: 'Body capacity not valid' }],
|
|
683
739
|
[7, { name: 'flowsensor', desc: 'Flow Sensor Fault' }]
|
|
684
|
-
|
|
685
740
|
]);
|
|
686
741
|
public chemControllerWarnings: byteValueMap = new byteValueMap([
|
|
687
742
|
[0, { name: 'ok', desc: 'Ok - No Warning' }],
|
|
@@ -769,12 +824,12 @@ export class byteValueMaps {
|
|
|
769
824
|
]);
|
|
770
825
|
public eqMessageSeverities: byteValueMap = new byteValueMap([
|
|
771
826
|
[-1, { name: 'unspecified', desc: 'Unspecified' }],
|
|
772
|
-
[0, { name: 'info', desc: 'Information' }],
|
|
773
|
-
[1, { name: 'reminder', desc: 'Reminder' }],
|
|
774
|
-
[2, { name: 'alert', desc: 'Alert' }],
|
|
775
|
-
[3, { name: 'warning', desc: 'Warning' }],
|
|
776
|
-
[4, { name: 'error', desc: 'Error' }],
|
|
777
|
-
[5, { name: 'fatal', desc: 'Fatal' }]
|
|
827
|
+
[0, { name: 'info', desc: 'Information', icon: 'fas fa-circle-info' }],
|
|
828
|
+
[1, { name: 'reminder', desc: 'Reminder', icon: 'fas fa-bell' }],
|
|
829
|
+
[2, { name: 'alert', desc: 'Alert', icon: 'fas fa-circle-exclamation' }],
|
|
830
|
+
[3, { name: 'warning', desc: 'Warning', icon: 'fas fa-circle-exclamation' }],
|
|
831
|
+
[4, { name: 'error', desc: 'Error', icon: 'fas fa-triangle-exclamation' }],
|
|
832
|
+
[5, { name: 'fatal', desc: 'Fatal', icon: 'fas fa-skull-crossbones' }]
|
|
778
833
|
]);
|
|
779
834
|
// need to validate these...
|
|
780
835
|
public delay: byteValueMap = new byteValueMap([
|
|
@@ -826,20 +881,19 @@ export class SystemBoard {
|
|
|
826
881
|
public async turnOffAllCircuits() {
|
|
827
882
|
// turn off all circuits/features
|
|
828
883
|
for (let i = 0; i < state.circuits.length; i++) {
|
|
829
|
-
let s = state.circuits.getItemByIndex(i)
|
|
830
|
-
|
|
884
|
+
let s = state.circuits.getItemByIndex(i)
|
|
885
|
+
s.isOn = s.manualPriorityActive = false;
|
|
831
886
|
}
|
|
832
887
|
for (let i = 0; i < state.features.length; i++) {
|
|
833
|
-
let s = state.features.getItemByIndex(i)
|
|
834
|
-
|
|
888
|
+
let s = state.features.getItemByIndex(i)
|
|
889
|
+
s.isOn = s.manualPriorityActive = false;
|
|
835
890
|
}
|
|
836
891
|
for (let i = 0; i < state.lightGroups.length; i++) {
|
|
837
|
-
let s = state.lightGroups.getItemByIndex(i)
|
|
838
|
-
|
|
892
|
+
let s = state.lightGroups.getItemByIndex(i)
|
|
893
|
+
s.isOn = s.manualPriorityActive = false;
|
|
839
894
|
}
|
|
840
895
|
for (let i = 0; i < state.temps.bodies.length; i++) {
|
|
841
|
-
|
|
842
|
-
await sys.board.circuits.setCircuitStateAsync(s.id, false);
|
|
896
|
+
state.temps.bodies.getItemByIndex(i).isOn = false;
|
|
843
897
|
}
|
|
844
898
|
// sys.board.virtualPumpControllers.setTargetSpeed();
|
|
845
899
|
state.emitEquipmentChanges();
|
|
@@ -853,8 +907,8 @@ export class SystemBoard {
|
|
|
853
907
|
public chlorinator: ChlorinatorCommands = new ChlorinatorCommands(this);
|
|
854
908
|
public heaters: HeaterCommands = new HeaterCommands(this);
|
|
855
909
|
public filters: FilterCommands = new FilterCommands(this);
|
|
856
|
-
|
|
857
|
-
|
|
910
|
+
public chemControllers: ChemControllerCommands = new ChemControllerCommands(this);
|
|
911
|
+
public chemDosers: ChemDoserCommands = new ChemDoserCommands(this);
|
|
858
912
|
public schedules: ScheduleCommands = new ScheduleCommands(this);
|
|
859
913
|
public equipmentIds: EquipmentIds = new EquipmentIds();
|
|
860
914
|
//public virtualChlorinatorController = new VirtualChlorinatorController(this);
|
|
@@ -879,14 +933,14 @@ export class SystemBoard {
|
|
|
879
933
|
this._statusTimer = undefined;
|
|
880
934
|
this._statusCheckRef = 0;
|
|
881
935
|
}
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
936
|
+
public suspendStatus(bSuspend: boolean) {
|
|
937
|
+
// The way status suspension works is by using a reference value that is incremented and decremented
|
|
938
|
+
// the status check is only performed when the reference value is 0. So suspending the status check 3 times and un-suspending
|
|
939
|
+
// it 2 times will still result in the status check being suspended. This method also ensures the reference never falls below 0.
|
|
940
|
+
if (bSuspend) this._statusCheckRef++;
|
|
941
|
+
else this._statusCheckRef = Math.max(0, this._statusCheckRef - 1);
|
|
942
|
+
if (this._statusCheckRef > 1) logger.verbose(`Suspending status check: ${bSuspend} -- ${this._statusCheckRef}`);
|
|
943
|
+
}
|
|
890
944
|
/// This method processes the status message periodically. The role of this method is to verify the circuit, valve, and heater
|
|
891
945
|
/// relays. This method does not control RS485 operations such as pumps and chlorinators. These are done through the respective
|
|
892
946
|
/// equipment polling functions.
|
|
@@ -907,20 +961,20 @@ export class SystemBoard {
|
|
|
907
961
|
} catch (err) { state.status = 255; logger.error(`Error performing processStatusAsync ${err.message}`); }
|
|
908
962
|
finally {
|
|
909
963
|
this.suspendStatus(false);
|
|
910
|
-
if (this.statusInterval > 0) this._statusTimer =
|
|
964
|
+
if (this.statusInterval > 0) this._statusTimer = setTimeoutSync(async () => await self.processStatusAsync(), this.statusInterval);
|
|
911
965
|
}
|
|
912
966
|
}
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
967
|
+
public async syncEquipmentItems() {
|
|
968
|
+
try {
|
|
969
|
+
await sys.board.circuits.syncCircuitRelayStates();
|
|
970
|
+
await sys.board.features.syncGroupStates();
|
|
971
|
+
await sys.board.circuits.syncVirtualCircuitStates();
|
|
972
|
+
await sys.board.valves.syncValveStates();
|
|
973
|
+
await sys.board.filters.syncFilterStates();
|
|
974
|
+
await sys.board.heaters.syncHeaterStates();
|
|
975
|
+
}
|
|
976
|
+
catch (err) { logger.error(`Error synchronizing equipment items: ${err.message}`); }
|
|
921
977
|
}
|
|
922
|
-
catch (err) { logger.error(`Error synchronizing equipment items: ${err.message}`); }
|
|
923
|
-
}
|
|
924
978
|
public async setControllerType(obj): Promise<Equipment> {
|
|
925
979
|
try {
|
|
926
980
|
if (obj.controllerType !== sys.controllerType)
|
|
@@ -998,347 +1052,413 @@ export class BoardCommands {
|
|
|
998
1052
|
constructor(parent: SystemBoard) { this.board = parent; }
|
|
999
1053
|
}
|
|
1000
1054
|
export class SystemCommands extends BoardCommands {
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
try {
|
|
1004
|
-
let ctx = await sys.board.system.validateRestore(rest);
|
|
1005
|
-
// Restore the general stuff.
|
|
1006
|
-
if (ctx.general.update.length > 0) await sys.board.system.setGeneralAsync(ctx.general.update[0]);
|
|
1007
|
-
for (let i = 0; i < ctx.customNames.update.length; i++) {
|
|
1008
|
-
let cn = ctx.customNames.update[i];
|
|
1055
|
+
public async restore(rest: { poolConfig: any, poolState: any }): Promise<RestoreResults> {
|
|
1056
|
+
let res = new RestoreResults();
|
|
1009
1057
|
try {
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1058
|
+
let ctx = await sys.board.system.validateRestore(rest);
|
|
1059
|
+
// Restore the general stuff.
|
|
1060
|
+
if (ctx.general.update.length > 0) await sys.board.system.setGeneralAsync(ctx.general.update[0]);
|
|
1061
|
+
for (let i = 0; i < ctx.customNames.update.length; i++) {
|
|
1062
|
+
let cn = ctx.customNames.update[i];
|
|
1063
|
+
try {
|
|
1064
|
+
await sys.board.system.setCustomNameAsync(cn);
|
|
1065
|
+
res.addModuleSuccess('customName', `Update: ${cn.id}-${cn.name}`);
|
|
1066
|
+
} catch (err) { res.addModuleError('customName', `Update: ${cn.id}-${cn.name}: ${err.message}`); }
|
|
1067
|
+
}
|
|
1068
|
+
for (let i = 0; i < ctx.customNames.add.length; i++) {
|
|
1069
|
+
let cn = ctx.customNames.add[i];
|
|
1070
|
+
try {
|
|
1071
|
+
await sys.board.system.setCustomNameAsync(cn);
|
|
1072
|
+
res.addModuleSuccess('customName', `Add: ${cn.id}-${cn.name}`);
|
|
1073
|
+
} catch (err) { res.addModuleError('customName', `Add: ${cn.id}-${cn.name}: ${err.message}`); }
|
|
1074
|
+
}
|
|
1075
|
+
await sys.board.bodies.restore(rest, ctx, res);
|
|
1076
|
+
await sys.board.filters.restore(rest, ctx, res);
|
|
1077
|
+
await sys.board.circuits.restore(rest, ctx, res);
|
|
1078
|
+
await sys.board.heaters.restore(rest, ctx, res);
|
|
1079
|
+
await sys.board.features.restore(rest, ctx, res);
|
|
1080
|
+
await sys.board.pumps.restore(rest, ctx, res);
|
|
1081
|
+
await sys.board.valves.restore(rest, ctx, res);
|
|
1082
|
+
await sys.board.chlorinator.restore(rest, ctx, res);
|
|
1083
|
+
await sys.board.chemControllers.restore(rest, ctx, res);
|
|
1084
|
+
await sys.board.schedules.restore(rest, ctx, res);
|
|
1085
|
+
return res;
|
|
1086
|
+
//await sys.board.covers.restore(rest, ctx);
|
|
1087
|
+
} catch (err) { logger.error(`Error restoring njsPC server: ${err.message}`); res.addModuleError('system', err.message); return Promise.reject(err); }
|
|
1088
|
+
}
|
|
1089
|
+
public async validateRestore(rest: { poolConfig: any, poolState: any }): Promise<any> {
|
|
1016
1090
|
try {
|
|
1017
|
-
|
|
1018
|
-
res.addModuleSuccess('customName', `Add: ${cn.id}-${cn.name}`);
|
|
1019
|
-
} catch (err) { res.addModuleError('customName', `Add: ${cn.id}-${cn.name}: ${err.message}`); }
|
|
1020
|
-
}
|
|
1021
|
-
await sys.board.bodies.restore(rest, ctx, res);
|
|
1022
|
-
await sys.board.filters.restore(rest, ctx, res);
|
|
1023
|
-
await sys.board.circuits.restore(rest, ctx, res);
|
|
1024
|
-
await sys.board.heaters.restore(rest, ctx, res);
|
|
1025
|
-
await sys.board.features.restore(rest, ctx, res);
|
|
1026
|
-
await sys.board.pumps.restore(rest, ctx, res);
|
|
1027
|
-
await sys.board.valves.restore(rest, ctx, res);
|
|
1028
|
-
await sys.board.chlorinator.restore(rest, ctx, res);
|
|
1029
|
-
await sys.board.chemControllers.restore(rest, ctx, res);
|
|
1030
|
-
await sys.board.schedules.restore(rest, ctx, res);
|
|
1031
|
-
return res;
|
|
1032
|
-
//await sys.board.covers.restore(rest, ctx);
|
|
1033
|
-
} catch (err) { logger.error(`Error restoring njsPC server: ${err.message}`); res.addModuleError('system', err.message); return Promise.reject(err);}
|
|
1034
|
-
}
|
|
1035
|
-
public async validateRestore(rest: { poolConfig: any, poolState: any }): Promise<any> {
|
|
1036
|
-
try {
|
|
1037
|
-
let ctx: any = { board: { errors: [], warnings: [] } };
|
|
1091
|
+
let ctx: any = { board: { errors: [], warnings: [] } };
|
|
1038
1092
|
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1093
|
+
// Step 1 - Verify that the boards are the same. For instance you do not want to restore an IntelliTouch to an IntelliCenter.
|
|
1094
|
+
let cfg = rest.poolConfig;
|
|
1095
|
+
if (sys.controllerType === cfg.controllerType) {
|
|
1096
|
+
ctx.customNames = { errors: [], warnings: [], add: [], update: [], remove: [] };
|
|
1097
|
+
let customNames = sys.customNames.get();
|
|
1098
|
+
for (let i = 0; i < rest.poolConfig.customNames.length; i++) {
|
|
1099
|
+
let cn = customNames.find(elem => elem.id === rest.poolConfig.customNames[i].id);
|
|
1100
|
+
if (typeof cn === 'undefined') ctx.customNames.add.push(rest.poolConfig.customNames[i]);
|
|
1101
|
+
else if (JSON.stringify(rest.poolConfig.customNames[i]) !== JSON.stringify(cn)) ctx.customNames.update.push(cn);
|
|
1102
|
+
}
|
|
1103
|
+
ctx.general = { errors: [], warnings: [], add: [], update: [], remove: [] };
|
|
1104
|
+
if (JSON.stringify(sys.general.get()) !== JSON.stringify(cfg.pool)) ctx.general.update.push(cfg.pool);
|
|
1105
|
+
ctx.bodies = await sys.board.bodies.validateRestore(rest);
|
|
1106
|
+
ctx.pumps = await sys.board.pumps.validateRestore(rest);
|
|
1107
|
+
await sys.board.circuits.validateRestore(rest, ctx);
|
|
1108
|
+
ctx.features = await sys.board.features.validateRestore(rest);
|
|
1109
|
+
ctx.chlorinators = await sys.board.chlorinator.validateRestore(rest);
|
|
1110
|
+
ctx.heaters = await sys.board.heaters.validateRestore(rest);
|
|
1111
|
+
ctx.valves = await sys.board.valves.validateRestore(rest);
|
|
1058
1112
|
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1113
|
+
//ctx.covers = await sys.board.covers.validateRestore(rest);
|
|
1114
|
+
ctx.chemControllers = await sys.board.chemControllers.validateRestore(rest);
|
|
1115
|
+
ctx.filters = await sys.board.filters.validateRestore(rest);
|
|
1116
|
+
ctx.schedules = await sys.board.schedules.validateRestore(rest);
|
|
1117
|
+
}
|
|
1118
|
+
else ctx.board.errors.push(`Panel Types do not match cannot restore backup from ${sys.controllerType} to ${rest.poolConfig.controllerType}`);
|
|
1065
1119
|
|
|
1066
|
-
|
|
1120
|
+
return ctx;
|
|
1067
1121
|
|
|
1068
|
-
|
|
1122
|
+
} catch (err) { logger.error(`Error validating restore file: ${err.message}`); return Promise.reject(err); }
|
|
1069
1123
|
|
|
1070
|
-
}
|
|
1071
|
-
public cancelDelay(): Promise<any> { state.delay = sys.board.valueMaps.delay.getValue('nodelay'); return Promise.resolve(state.data.delay); }
|
|
1072
|
-
public setManualOperationPriority(id: number): Promise<any> { return Promise.resolve(); }
|
|
1073
|
-
public setDateTimeAsync(obj: any): Promise<any> { return Promise.resolve(); }
|
|
1074
|
-
public keepManualTime() {
|
|
1075
|
-
try {
|
|
1076
|
-
// every minute, updated the time from the system clock in server mode
|
|
1077
|
-
// but only for Virtual. Likely 'manual' on *Center means OCP time
|
|
1078
|
-
if (sys.general.options.clockSource !== 'server') return;
|
|
1079
|
-
state.time.setTimeFromSystemClock();
|
|
1080
|
-
sys.board.system.setTZ();
|
|
1081
|
-
} catch (err) { logger.error(`Error setting manual time: ${err.message}`); }
|
|
1082
|
-
}
|
|
1083
|
-
public setTZ() {
|
|
1084
|
-
let tzOffsetObj = state.time.calcTZOffset();
|
|
1085
|
-
if (sys.general.options.clockSource === 'server' || typeof sys.general.location.timeZone === 'undefined') {
|
|
1086
|
-
let tzs = sys.board.valueMaps.timeZones.toArray();
|
|
1087
|
-
sys.general.location.timeZone = tzs.find(tz => tz.utcOffset === tzOffsetObj.tzOffset).val;
|
|
1088
|
-
}
|
|
1089
|
-
if (sys.general.options.clockSource === 'server' || typeof sys.general.options.adjustDST === 'undefined') {
|
|
1090
|
-
sys.general.options.adjustDST = tzOffsetObj.adjustDST;
|
|
1091
|
-
}
|
|
1092
|
-
}
|
|
1093
|
-
public getDOW() { return this.board.valueMaps.scheduleDays.toArray(); }
|
|
1094
|
-
public async setGeneralAsync(obj: any): Promise<General> {
|
|
1095
|
-
if (typeof obj.alias === 'string') sys.general.alias = obj.alias;
|
|
1096
|
-
if (typeof obj.options !== 'undefined') await sys.board.system.setOptionsAsync(obj.options);
|
|
1097
|
-
if (typeof obj.location !== 'undefined') await sys.board.system.setLocationAsync(obj.location);
|
|
1098
|
-
if (typeof obj.owner !== 'undefined') await sys.board.system.setOwnerAsync(obj.owner);
|
|
1099
|
-
return sys.general;
|
|
1100
|
-
}
|
|
1101
|
-
public async setTempSensorsAsync(obj: any): Promise<TempSensorCollection> {
|
|
1102
|
-
if (typeof obj.waterTempAdj1 != 'undefined' && obj.waterTempAdj1 !== sys.equipment.tempSensors.getCalibration('water1')) {
|
|
1103
|
-
sys.equipment.tempSensors.setCalibration('water1', parseFloat(obj.waterTempAdj1));
|
|
1104
|
-
}
|
|
1105
|
-
if (typeof obj.waterTempAdj2 != 'undefined' && obj.waterTempAdj2 !== sys.equipment.tempSensors.getCalibration('water2')) {
|
|
1106
|
-
sys.equipment.tempSensors.setCalibration('water2', parseFloat(obj.waterTempAdj2));
|
|
1107
1124
|
}
|
|
1108
|
-
|
|
1109
|
-
|
|
1125
|
+
public cancelDelay(): Promise<any> { state.delay = sys.board.valueMaps.delay.getValue('nodelay'); return Promise.resolve(state.data.delay); }
|
|
1126
|
+
public setManualOperationPriority(id: number): Promise<any> { return Promise.resolve(); }
|
|
1127
|
+
public setDateTimeAsync(obj: any): Promise<any> { return Promise.resolve(); }
|
|
1128
|
+
public keepManualTime() {
|
|
1129
|
+
try {
|
|
1130
|
+
// every minute, updated the time from the system clock in server mode
|
|
1131
|
+
// but only for Virtual. Likely 'manual' on *Center means OCP time
|
|
1132
|
+
if (sys.general.options.clockSource !== 'server') return;
|
|
1133
|
+
state.time.setTimeFromSystemClock();
|
|
1134
|
+
sys.board.system.setTZ();
|
|
1135
|
+
} catch (err) { logger.error(`Error setting manual time: ${err.message}`); }
|
|
1110
1136
|
}
|
|
1111
|
-
|
|
1112
|
-
|
|
1137
|
+
public setTZ() {
|
|
1138
|
+
let tzOffsetObj = state.time.calcTZOffset();
|
|
1139
|
+
if (sys.general.options.clockSource === 'server' || typeof sys.general.location.timeZone === 'undefined') {
|
|
1140
|
+
let tzs = sys.board.valueMaps.timeZones.toArray();
|
|
1141
|
+
sys.general.location.timeZone = tzs.find(tz => tz.utcOffset === tzOffsetObj.tzOffset).val;
|
|
1142
|
+
}
|
|
1143
|
+
if (sys.general.options.clockSource === 'server' || typeof sys.general.options.adjustDST === 'undefined') {
|
|
1144
|
+
sys.general.options.adjustDST = tzOffsetObj.adjustDST;
|
|
1145
|
+
}
|
|
1113
1146
|
}
|
|
1114
|
-
|
|
1115
|
-
|
|
1147
|
+
public getDOW() { return this.board.valueMaps.scheduleDays.toArray(); }
|
|
1148
|
+
public async setGeneralAsync(obj: any): Promise<General> {
|
|
1149
|
+
if (typeof obj.alias === 'string') sys.general.alias = obj.alias;
|
|
1150
|
+
if (typeof obj.options !== 'undefined') await sys.board.system.setOptionsAsync(obj.options);
|
|
1151
|
+
if (typeof obj.location !== 'undefined') await sys.board.system.setLocationAsync(obj.location);
|
|
1152
|
+
if (typeof obj.owner !== 'undefined') await sys.board.system.setOwnerAsync(obj.owner);
|
|
1153
|
+
return sys.general;
|
|
1116
1154
|
}
|
|
1117
|
-
|
|
1118
|
-
|
|
1155
|
+
public async setTempSensorsAsync(obj: any): Promise<TempSensorCollection> {
|
|
1156
|
+
if (typeof obj.waterTempAdj1 != 'undefined' && obj.waterTempAdj1 !== sys.equipment.tempSensors.getCalibration('water1')) {
|
|
1157
|
+
sys.equipment.tempSensors.setCalibration('water1', parseFloat(obj.waterTempAdj1));
|
|
1158
|
+
}
|
|
1159
|
+
if (typeof obj.waterTempAdj2 != 'undefined' && obj.waterTempAdj2 !== sys.equipment.tempSensors.getCalibration('water2')) {
|
|
1160
|
+
sys.equipment.tempSensors.setCalibration('water2', parseFloat(obj.waterTempAdj2));
|
|
1161
|
+
}
|
|
1162
|
+
if (typeof obj.waterTempAdj3 != 'undefined' && obj.waterTempAdj3 !== sys.equipment.tempSensors.getCalibration('water3')) {
|
|
1163
|
+
sys.equipment.tempSensors.setCalibration('water3', parseFloat(obj.waterTempAdj3));
|
|
1164
|
+
}
|
|
1165
|
+
if (typeof obj.waterTempAdj4 != 'undefined' && obj.waterTempAdj4 !== sys.equipment.tempSensors.getCalibration('water4')) {
|
|
1166
|
+
sys.equipment.tempSensors.setCalibration('water4', parseFloat(obj.waterTempAdj4));
|
|
1167
|
+
}
|
|
1168
|
+
if (typeof obj.solarTempAdj1 != 'undefined' && obj.solarTempAdj1 !== sys.equipment.tempSensors.getCalibration('solar1')) {
|
|
1169
|
+
sys.equipment.tempSensors.setCalibration('solar1', parseFloat(obj.solarTempAdj1));
|
|
1170
|
+
}
|
|
1171
|
+
if (typeof obj.solarTempAdj2 != 'undefined' && obj.solarTempAdj2 !== sys.equipment.tempSensors.getCalibration('solar2')) {
|
|
1172
|
+
sys.equipment.tempSensors.setCalibration('solar2', parseFloat(obj.solarTempAdj2));
|
|
1173
|
+
}
|
|
1174
|
+
if (typeof obj.solarTempAdj3 != 'undefined' && obj.solarTempAdj3 !== sys.equipment.tempSensors.getCalibration('solar3')) {
|
|
1175
|
+
sys.equipment.tempSensors.setCalibration('solar3', parseFloat(obj.solarTempAdj3));
|
|
1176
|
+
}
|
|
1177
|
+
if (typeof obj.solarTempAdj4 != 'undefined' && obj.solarTempAdj4 !== sys.equipment.tempSensors.getCalibration('solar4')) {
|
|
1178
|
+
sys.equipment.tempSensors.setCalibration('solar4', parseFloat(obj.solarTempAdj4));
|
|
1179
|
+
}
|
|
1180
|
+
if (typeof obj.airTempAdj != 'undefined' && obj.airTempAdj !== sys.equipment.tempSensors.getCalibration('air')) {
|
|
1181
|
+
sys.equipment.tempSensors.setCalibration('air', parseFloat(obj.airTempAdj));
|
|
1182
|
+
}
|
|
1183
|
+
return sys.equipment.tempSensors;
|
|
1119
1184
|
}
|
|
1120
|
-
|
|
1121
|
-
|
|
1185
|
+
public async setOptionsAsync(obj: any): Promise<Options> {
|
|
1186
|
+
if (obj.clockSource === 'server') sys.board.system.setTZ();
|
|
1187
|
+
sys.board.system.setTempSensorsAsync(obj);
|
|
1188
|
+
sys.general.options.set(obj);
|
|
1189
|
+
let bodyUnits = sys.general.options.units === 0 ? 1 : 2;
|
|
1190
|
+
for (let i = 0; i < sys.bodies.length; i++) sys.bodies.getItemByIndex(i).capacityUnits = bodyUnits;
|
|
1191
|
+
state.temps.units = sys.general.options.units === 0 ? 1 : 4;
|
|
1192
|
+
return sys.general.options;
|
|
1122
1193
|
}
|
|
1123
|
-
|
|
1124
|
-
|
|
1194
|
+
public async setLocationAsync(obj: any): Promise<Location> {
|
|
1195
|
+
sys.general.location.set(obj);
|
|
1196
|
+
return sys.general.location;
|
|
1125
1197
|
}
|
|
1126
|
-
|
|
1127
|
-
|
|
1198
|
+
public async setOwnerAsync(obj: any): Promise<Owner> {
|
|
1199
|
+
sys.general.owner.set(obj);
|
|
1200
|
+
return sys.general.owner;
|
|
1128
1201
|
}
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1202
|
+
public async setTempsAsync(obj: any): Promise<TemperatureState> {
|
|
1203
|
+
return new Promise<TemperatureState>((resolve, reject) => {
|
|
1204
|
+
let units = sys.board.valueMaps.tempUnits.getName(state.temps.units) || 'F';
|
|
1205
|
+
for (let prop in obj) {
|
|
1206
|
+
switch (prop) {
|
|
1207
|
+
case 'air':
|
|
1208
|
+
case 'airSensor':
|
|
1209
|
+
case 'airSensor1':
|
|
1210
|
+
{
|
|
1211
|
+
let temp = 0;
|
|
1212
|
+
if (obj[prop] !== null) {
|
|
1213
|
+
if (typeof obj[prop].temperature !== 'undefined') {
|
|
1214
|
+
temp = parseFloat(obj[prop].temperature);
|
|
1215
|
+
if (typeof obj[prop].units === 'string') temp = utils.convert.temperature.convertUnits(temp, obj[prop].units || units, units);
|
|
1216
|
+
}
|
|
1217
|
+
else temp = parseFloat(obj[prop]);
|
|
1218
|
+
}
|
|
1219
|
+
if (isNaN(temp)) return reject(new InvalidEquipmentDataError(`Invalid value for ${prop} ${obj[prop]}`, `Temps:${prop}`, obj[prop]));
|
|
1220
|
+
state.temps.air = sys.equipment.tempSensors.getCalibration('air') + temp;
|
|
1221
|
+
}
|
|
1222
|
+
break;
|
|
1223
|
+
case 'waterSensor1':
|
|
1224
|
+
{
|
|
1225
|
+
let temp = 0;
|
|
1226
|
+
if (obj[prop] !== null) {
|
|
1227
|
+
if (typeof obj[prop].temperature !== 'undefined') {
|
|
1228
|
+
temp = parseFloat(obj[prop].temperature);
|
|
1229
|
+
if (typeof obj[prop].units === 'string') temp = utils.convert.temperature.convertUnits(temp, obj[prop].units || units, units);
|
|
1230
|
+
}
|
|
1231
|
+
else temp = parseFloat(obj[prop]);
|
|
1232
|
+
}
|
|
1233
|
+
if (isNaN(temp)) return reject(new InvalidEquipmentDataError(`Invalid value for ${prop} ${obj[prop]}`, `Temps:${prop}`, obj[prop]));
|
|
1234
|
+
state.temps.waterSensor1 = sys.equipment.tempSensors.getCalibration('water1') + temp;
|
|
1235
|
+
let body = state.temps.bodies.getItemById(1);
|
|
1236
|
+
if (body.isOn) body.temp = state.temps.waterSensor1;
|
|
1237
|
+
else if (!sys.equipment.dual) {
|
|
1238
|
+
body = state.temps.bodies.find(elem => elem.id === 2);
|
|
1239
|
+
if (typeof body !== 'undefined') {
|
|
1240
|
+
body = state.temps.bodies.getItemById(2);
|
|
1241
|
+
if (body.isOn) body.temp = state.temps.waterSensor1;
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
break;
|
|
1246
|
+
case 'waterSensor2':
|
|
1247
|
+
{
|
|
1248
|
+
let temp = 0;
|
|
1249
|
+
if (obj[prop] !== null) {
|
|
1250
|
+
if (typeof obj[prop].temperature !== 'undefined') {
|
|
1251
|
+
temp = parseFloat(obj[prop].temperature);
|
|
1252
|
+
if (typeof obj[prop].units === 'string') temp = utils.convert.temperature.convertUnits(temp, obj[prop].units || units, units);
|
|
1253
|
+
}
|
|
1254
|
+
else temp = parseFloat(obj[prop]);
|
|
1255
|
+
}
|
|
1256
|
+
if (isNaN(temp)) return reject(new InvalidEquipmentDataError(`Invalid value for ${prop} ${obj[prop]}`, `Temps:${prop}`, obj[prop]));
|
|
1257
|
+
state.temps.waterSensor2 = sys.equipment.tempSensors.getCalibration('water2') + temp;
|
|
1258
|
+
if (state.equipment.dual) {
|
|
1259
|
+
let body = state.temps.bodies.getItemById(2);
|
|
1260
|
+
if (body.isOn) body.temp = state.temps.waterSensor2;
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
break;
|
|
1264
|
+
case 'waterSensor3':
|
|
1265
|
+
{
|
|
1266
|
+
let temp = 0;
|
|
1267
|
+
if (obj[prop] !== null) {
|
|
1268
|
+
if (typeof obj[prop].temperature !== 'undefined') {
|
|
1269
|
+
temp = parseFloat(obj[prop].temperature);
|
|
1270
|
+
if (typeof obj[prop].units === 'string') temp = utils.convert.temperature.convertUnits(temp, obj[prop].units || units, units);
|
|
1271
|
+
}
|
|
1272
|
+
else temp = parseFloat(obj[prop]);
|
|
1273
|
+
}
|
|
1274
|
+
if (isNaN(temp)) return reject(new InvalidEquipmentDataError(`Invalid value for ${prop} ${obj[prop]}`, `Temps:${prop}`, obj[prop]));
|
|
1275
|
+
state.temps.waterSensor3 = sys.equipment.tempSensors.getCalibration('water3') + temp;
|
|
1276
|
+
let body = state.temps.bodies.getItemById(3);
|
|
1277
|
+
if (body.isOn) body.temp = state.temps.waterSensor3;
|
|
1278
|
+
}
|
|
1279
|
+
break;
|
|
1280
|
+
case 'waterSensor4':
|
|
1281
|
+
{
|
|
1282
|
+
let temp = 0;
|
|
1283
|
+
if (obj[prop] !== null) {
|
|
1284
|
+
if (typeof obj[prop].temperature !== 'undefined') {
|
|
1285
|
+
temp = parseFloat(obj[prop].temperature);
|
|
1286
|
+
if (typeof obj[prop].units === 'string') temp = utils.convert.temperature.convertUnits(temp, obj[prop].units || units, units);
|
|
1287
|
+
}
|
|
1288
|
+
else temp = parseFloat(obj[prop]);
|
|
1289
|
+
}
|
|
1290
|
+
if (isNaN(temp)) return reject(new InvalidEquipmentDataError(`Invalid value for ${prop} ${obj[prop]}`, `Temps:${prop}`, obj[prop]));
|
|
1291
|
+
let body = state.temps.bodies.getItemById(4);
|
|
1292
|
+
if (body.isOn) body.temp = state.temps.waterSensor4;
|
|
1293
|
+
}
|
|
1294
|
+
break;
|
|
1196
1295
|
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1296
|
+
case 'solarSensor1':
|
|
1297
|
+
case 'solar1':
|
|
1298
|
+
case 'solar':
|
|
1299
|
+
{
|
|
1300
|
+
let temp = 0;
|
|
1301
|
+
if (obj[prop] !== null) {
|
|
1302
|
+
if (typeof obj[prop].temperature !== 'undefined') {
|
|
1303
|
+
temp = parseFloat(obj[prop].temperature);
|
|
1304
|
+
if (typeof obj[prop].units === 'string') temp = utils.convert.temperature.convertUnits(temp, obj[prop].units || units, units);
|
|
1305
|
+
}
|
|
1306
|
+
else temp = parseFloat(obj[prop]);
|
|
1307
|
+
}
|
|
1308
|
+
if (isNaN(temp)) return reject(new InvalidEquipmentDataError(`Invalid value for ${prop} ${obj[prop]}`, `Temps:${prop}`, obj[prop]));
|
|
1309
|
+
state.temps.solar = sys.equipment.tempSensors.getCalibration('solar1') + temp;
|
|
1310
|
+
}
|
|
1311
|
+
break;
|
|
1312
|
+
case 'solar2':
|
|
1313
|
+
case 'solarSensor2':
|
|
1314
|
+
{
|
|
1315
|
+
let temp = 0;
|
|
1316
|
+
if (obj[prop] !== null) {
|
|
1317
|
+
if (typeof obj[prop].temperature !== 'undefined') {
|
|
1318
|
+
temp = parseFloat(obj[prop].temperature);
|
|
1319
|
+
if (typeof obj[prop].units === 'string') temp = utils.convert.temperature.convertUnits(temp, obj[prop].units || units, units);
|
|
1320
|
+
}
|
|
1321
|
+
else temp = parseFloat(obj[prop]);
|
|
1322
|
+
}
|
|
1323
|
+
if (isNaN(temp)) return reject(new InvalidEquipmentDataError(`Invalid value for ${prop} ${obj[prop]}`, `Temps:${prop}`, obj[prop]));
|
|
1324
|
+
state.temps.solarSensor2 = sys.equipment.tempSensors.getCalibration('solar2') + temp;
|
|
1325
|
+
}
|
|
1326
|
+
break;
|
|
1327
|
+
case 'solar3':
|
|
1328
|
+
case 'solarSensor3':
|
|
1329
|
+
{
|
|
1330
|
+
let temp = 0;
|
|
1331
|
+
if (obj[prop] !== null) {
|
|
1332
|
+
if (typeof obj[prop].temperature !== 'undefined') {
|
|
1333
|
+
temp = parseFloat(obj[prop].temperature);
|
|
1334
|
+
if (typeof obj[prop].units === 'string') temp = utils.convert.temperature.convertUnits(temp, obj[prop].units || units, units);
|
|
1335
|
+
}
|
|
1336
|
+
else temp = parseFloat(obj[prop]);
|
|
1337
|
+
}
|
|
1338
|
+
if (isNaN(temp)) return reject(new InvalidEquipmentDataError(`Invalid value for ${prop} ${obj[prop]}`, `Temps:${prop}`, obj[prop]));
|
|
1339
|
+
state.temps.solarSensor3 = sys.equipment.tempSensors.getCalibration('solar3') + temp;
|
|
1340
|
+
}
|
|
1341
|
+
break;
|
|
1342
|
+
case 'solar4':
|
|
1343
|
+
case 'solarSensor4':
|
|
1344
|
+
{
|
|
1345
|
+
let temp = 0;
|
|
1346
|
+
if (obj[prop] !== null) {
|
|
1347
|
+
if (typeof obj[prop].temperature !== 'undefined') {
|
|
1348
|
+
temp = parseFloat(obj[prop].temperature);
|
|
1349
|
+
if (typeof obj[prop].units === 'string') temp = utils.convert.temperature.convertUnits(temp, obj[prop].units || units, units);
|
|
1350
|
+
}
|
|
1351
|
+
else temp = parseFloat(obj[prop]);
|
|
1352
|
+
}
|
|
1353
|
+
if (isNaN(temp)) return reject(new InvalidEquipmentDataError(`Invalid value for ${prop} ${obj[prop]}`, `Temps:${prop}`, obj[prop]));
|
|
1354
|
+
state.temps.solarSensor4 = sys.equipment.tempSensors.getCalibration('solar4') + temp;
|
|
1355
|
+
}
|
|
1356
|
+
break;
|
|
1357
|
+
}
|
|
1202
1358
|
}
|
|
1203
|
-
|
|
1359
|
+
sys.board.heaters.syncHeaterStates();
|
|
1360
|
+
resolve(state.temps);
|
|
1361
|
+
});
|
|
1362
|
+
}
|
|
1363
|
+
public getSensors() {
|
|
1364
|
+
let sensors = [{ name: 'Air Sensor', temp: state.temps.air, tempAdj: sys.equipment.tempSensors.getCalibration('air'), binding: 'airTempAdj' }];
|
|
1365
|
+
if (sys.equipment.shared) {
|
|
1366
|
+
if (sys.equipment.maxBodies > 2)
|
|
1367
|
+
sensors.push({ name: 'Water Sensor 1', temp: state.temps.waterSensor1, tempAdj: sys.equipment.tempSensors.getCalibration('water1'), binding: 'waterTempAdj1' },
|
|
1368
|
+
{ name: 'Water Sensor 2', temp: state.temps.waterSensor2, tempAdj: sys.equipment.tempSensors.getCalibration('water2'), binding: 'waterTempAdj2' },
|
|
1369
|
+
{ name: 'Water Sensor 3', temp: state.temps.waterSensor4, tempAdj: sys.equipment.tempSensors.getCalibration('water3'), binding: 'waterTempAdj3' });
|
|
1370
|
+
else
|
|
1371
|
+
sensors.push({ name: 'Water Sensor', temp: state.temps.waterSensor1, tempAdj: sys.equipment.tempSensors.getCalibration('water1'), binding: 'waterTempAdj1' });
|
|
1372
|
+
if (sys.equipment.maxBodies > 3)
|
|
1373
|
+
sensors.push({ name: 'Water Sensor 4', temp: state.temps.waterSensor4, tempAdj: sys.equipment.tempSensors.getCalibration('water4'), binding: 'waterTempAdj4' });
|
|
1204
1374
|
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1375
|
+
if (sys.board.heaters.isSolarInstalled()) {
|
|
1376
|
+
if (sys.equipment.maxBodies > 2) {
|
|
1377
|
+
sensors.push({ name: 'Solar Sensor 1', temp: state.temps.solar, tempAdj: sys.equipment.tempSensors.getCalibration('solar1'), binding: 'solarTempAdj1' },
|
|
1378
|
+
{ name: 'Solar Sensor 2', temp: state.temps.solarSensor2, tempAdj: sys.equipment.tempSensors.getCalibration('solar2'), binding: 'solarTempAdj2' });
|
|
1379
|
+
}
|
|
1380
|
+
else
|
|
1381
|
+
sensors.push({ name: 'Solar Sensor', temp: state.temps.solar, tempAdj: sys.equipment.tempSensors.getCalibration('solar1'), binding: 'solarTempAdj1' });
|
|
1382
|
+
if (sys.equipment.maxBodies > 3)
|
|
1383
|
+
sensors.push({ name: 'Solar Sensor 4', temp: state.temps.solarSensor4, tempAdj: sys.equipment.tempSensors.getCalibration('solar4'), binding: 'solarTempAdj4' });
|
|
1212
1384
|
}
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1385
|
+
}
|
|
1386
|
+
else if (sys.equipment.dual) {
|
|
1387
|
+
sensors.push({ name: 'Water Sensor 1', temp: state.temps.waterSensor1, tempAdj: sys.equipment.tempSensors.getCalibration('water1'), binding: 'waterTempAdj1' },
|
|
1388
|
+
{ name: 'Water Sensor 2', temp: state.temps.waterSensor2, tempAdj: sys.equipment.tempSensors.getCalibration('water2'), binding: 'waterTempAdj2' });
|
|
1389
|
+
if (sys.equipment.maxBodies > 2)
|
|
1390
|
+
sensors.push({ name: 'Water Sensor 3', temp: state.temps.waterSensor3, tempAdj: sys.equipment.tempSensors.getCalibration('water3'), binding: 'waterTempAdj3' });
|
|
1391
|
+
if (sys.equipment.maxBodies > 3)
|
|
1392
|
+
sensors.push({ name: 'Water Sensor 4', temp: state.temps.waterSensor4, tempAdj: sys.equipment.tempSensors.getCalibration('water4'), binding: 'waterTempAdj4' });
|
|
1393
|
+
if (sys.board.heaters.isSolarInstalled()) {
|
|
1394
|
+
sensors.push({ name: 'Solar Sensor 1', temp: state.temps.solar, tempAdj: sys.equipment.tempSensors.getCalibration('solar1'), binding: 'solarTempAdj1' },
|
|
1395
|
+
{ name: 'Solar Sensor 2', temp: state.temps.solarSensor2, tempAdj: sys.equipment.tempSensors.getCalibration('solar2'), binding: 'solarTempAdj2' });
|
|
1396
|
+
if (sys.equipment.maxBodies > 2)
|
|
1397
|
+
sensors.push({ name: 'Solar Sensor 3', temp: state.temps.solarSensor3, tempAdj: sys.equipment.tempSensors.getCalibration('solar3'), binding: 'solarTempAdj3' });
|
|
1398
|
+
if (sys.equipment.maxBodies > 3)
|
|
1399
|
+
sensors.push({ name: 'Solar Sensor 4', temp: state.temps.solarSensor4, tempAdj: sys.equipment.tempSensors.getCalibration('solar4'), binding: 'solarTempAdj4' });
|
|
1220
1400
|
}
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1401
|
+
}
|
|
1402
|
+
else {
|
|
1403
|
+
if (sys.equipment.maxBodies > 1) {
|
|
1404
|
+
sensors.push({ name: 'Water Sensor 1', temp: state.temps.waterSensor1, tempAdj: sys.equipment.tempSensors.getCalibration('water1'), binding: 'waterTempAdj1' },
|
|
1405
|
+
{ name: 'Water Sensor 2', temp: state.temps.waterSensor2, tempAdj: sys.equipment.tempSensors.getCalibration('water2'), binding: 'waterTempAdj2' });
|
|
1406
|
+
if (sys.equipment.maxBodies > 2)
|
|
1407
|
+
sensors.push({ name: 'Water Sensor 3', temp: state.temps.waterSensor3, tempAdj: sys.equipment.tempSensors.getCalibration('water3'), binding: 'waterTempAdj3' });
|
|
1408
|
+
if (sys.equipment.maxBodies > 3)
|
|
1409
|
+
sensors.push({ name: 'Water Sensor 4', temp: state.temps.waterSensor4, tempAdj: sys.equipment.tempSensors.getCalibration('water4'), binding: 'waterTempAdj4' });
|
|
1410
|
+
|
|
1411
|
+
if (sys.board.heaters.isSolarInstalled()) {
|
|
1412
|
+
sensors.push({ name: 'Solar Sensor 1', temp: state.temps.solarSensor1, tempAdj: sys.equipment.tempSensors.getCalibration('solar1'), binding: 'solarTempAdj1' },
|
|
1413
|
+
{ name: 'Solar Sensor 2', temp: state.temps.solarSensor2, tempAdj: sys.equipment.tempSensors.getCalibration('solar2'), binding: 'solarTempAdj2' });
|
|
1414
|
+
if (sys.equipment.maxBodies > 2)
|
|
1415
|
+
sensors.push({ name: 'Solar Sensor 3', temp: state.temps.solarSensor3, tempAdj: sys.equipment.tempSensors.getCalibration('solar3'), binding: 'solarTempAdj3' });
|
|
1416
|
+
if (sys.equipment.maxBodies > 3)
|
|
1417
|
+
sensors.push({ name: 'Water Sensor 4', temp: state.temps.solarSensor4, tempAdj: sys.equipment.tempSensors.getCalibration('solar4'), binding: 'solarTempAdj4' });
|
|
1418
|
+
}
|
|
1228
1419
|
}
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
let temp = obj[prop] !== null ? parseFloat(obj[prop]) : 0;
|
|
1234
|
-
if (isNaN(temp)) return reject(new InvalidEquipmentDataError(`Invalid value for ${prop} ${obj[prop]}`, `Temps:${prop}`, obj[prop]));
|
|
1235
|
-
state.temps.solarSensor4 = sys.equipment.tempSensors.getCalibration('solar4') + temp;
|
|
1420
|
+
else {
|
|
1421
|
+
sensors.push({ name: 'Water Sensor', temp: state.temps.waterSensor1, tempAdj: sys.equipment.tempSensors.getCalibration('water1'), binding: 'waterTempAdj1' });
|
|
1422
|
+
if (sys.board.heaters.isSolarInstalled())
|
|
1423
|
+
sensors.push({ name: 'Solar Sensor', temp: state.temps.solar, tempAdj: sys.equipment.tempSensors.getCalibration('solar1'), binding: 'solarTempAdj1' });
|
|
1236
1424
|
}
|
|
1237
|
-
break;
|
|
1238
1425
|
}
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
{ name: 'Water Sensor 2', temp: state.temps.waterSensor2, tempAdj: sys.equipment.tempSensors.getCalibration('water2'), binding: 'waterTempAdj2' },
|
|
1250
|
-
{ name: 'Water Sensor 3', temp: state.temps.waterSensor4, tempAdj: sys.equipment.tempSensors.getCalibration('water3'), binding: 'waterTempAdj3' });
|
|
1251
|
-
else
|
|
1252
|
-
sensors.push({ name: 'Water Sensor', temp: state.temps.waterSensor1, tempAdj: sys.equipment.tempSensors.getCalibration('water1'), binding: 'waterTempAdj1' });
|
|
1253
|
-
if (sys.equipment.maxBodies > 3)
|
|
1254
|
-
sensors.push({ name: 'Water Sensor 4', temp: state.temps.waterSensor4, tempAdj: sys.equipment.tempSensors.getCalibration('water4'), binding: 'waterTempAdj4' });
|
|
1426
|
+
return sensors;
|
|
1427
|
+
}
|
|
1428
|
+
public async setCustomNamesAsync(names: any[]): Promise<CustomNameCollection> {
|
|
1429
|
+
if (!Array.isArray(names)) return Promise.reject(new InvalidEquipmentDataError(`Data is not an array`, 'customNames', names))
|
|
1430
|
+
let arr = [];
|
|
1431
|
+
for (let i = 0; i < names.length; i++) { arr.push(sys.board.system.setCustomNameAsync(names[i])); }
|
|
1432
|
+
return new Promise<CustomNameCollection>(async (resolve, reject) => {
|
|
1433
|
+
try {
|
|
1434
|
+
await Promise.all(arr).catch(err => reject(err));
|
|
1435
|
+
// sys.board.system.syncCustomNamesValueMap(); Each custom name promise is already syncing the bytevalue array
|
|
1255
1436
|
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
}
|
|
1261
|
-
else
|
|
1262
|
-
sensors.push({ name: 'Solar Sensor', temp: state.temps.solar, tempAdj: sys.equipment.tempSensors.getCalibration('solar1'), binding: 'solarTempAdj1' });
|
|
1263
|
-
if (sys.equipment.maxBodies > 3)
|
|
1264
|
-
sensors.push({ name: 'Solar Sensor 4', temp: state.temps.solarSensor4, tempAdj: sys.equipment.tempSensors.getCalibration('solar4'), binding: 'solarTempAdj4' });
|
|
1265
|
-
}
|
|
1437
|
+
resolve(sys.customNames);
|
|
1438
|
+
}
|
|
1439
|
+
catch (err) { reject(err); }
|
|
1440
|
+
});
|
|
1266
1441
|
}
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
if (sys.equipment.maxBodies > 2)
|
|
1278
|
-
sensors.push({ name: 'Solar Sensor 3', temp: state.temps.solarSensor3, tempAdj: sys.equipment.tempSensors.getCalibration('solar3'), binding: 'solarTempAdj3' });
|
|
1279
|
-
if (sys.equipment.maxBodies > 3)
|
|
1280
|
-
sensors.push({ name: 'Solar Sensor 4', temp: state.temps.solarSensor4, tempAdj: sys.equipment.tempSensors.getCalibration('solar4'), binding: 'solarTempAdj4' });
|
|
1281
|
-
}
|
|
1442
|
+
public async setCustomNameAsync(data: any, send: boolean = false): Promise<CustomName> {
|
|
1443
|
+
return new Promise<CustomName>((resolve, reject) => {
|
|
1444
|
+
let id = parseInt(data.id, 10);
|
|
1445
|
+
if (isNaN(id)) return reject(new InvalidEquipmentIdError('Invalid Custom Name Id', data.id, 'customName'));
|
|
1446
|
+
if (id > sys.equipment.maxCustomNames) return reject(new InvalidEquipmentIdError('Custom Name Id out of range', data.id, 'customName'));
|
|
1447
|
+
let cname = sys.customNames.getItemById(id, true);
|
|
1448
|
+
cname.name = data.name;
|
|
1449
|
+
sys.board.system.syncCustomNamesValueMap();
|
|
1450
|
+
return resolve(cname);
|
|
1451
|
+
});
|
|
1282
1452
|
}
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
sensors.push({ name: 'Water Sensor 4', temp: state.temps.waterSensor4, tempAdj: sys.equipment.tempSensors.getCalibration('water4'), binding: 'waterTempAdj4' });
|
|
1291
|
-
|
|
1292
|
-
if (sys.board.heaters.isSolarInstalled()) {
|
|
1293
|
-
sensors.push({ name: 'Solar Sensor 1', temp: state.temps.solarSensor1, tempAdj: sys.equipment.tempSensors.getCalibration('solar1'), binding: 'solarTempAdj1' },
|
|
1294
|
-
{ name: 'Solar Sensor 2', temp: state.temps.solarSensor2, tempAdj: sys.equipment.tempSensors.getCalibration('solar2'), binding: 'solarTempAdj2' });
|
|
1295
|
-
if (sys.equipment.maxBodies > 2)
|
|
1296
|
-
sensors.push({ name: 'Solar Sensor 3', temp: state.temps.solarSensor3, tempAdj: sys.equipment.tempSensors.getCalibration('solar3'), binding: 'solarTempAdj3' });
|
|
1297
|
-
if (sys.equipment.maxBodies > 3)
|
|
1298
|
-
sensors.push({ name: 'Water Sensor 4', temp: state.temps.solarSensor4, tempAdj: sys.equipment.tempSensors.getCalibration('solar4'), binding: 'solarTempAdj4' });
|
|
1299
|
-
}
|
|
1300
|
-
}
|
|
1301
|
-
else {
|
|
1302
|
-
sensors.push({ name: 'Water Sensor', temp: state.temps.waterSensor1, tempAdj: sys.equipment.tempSensors.getCalibration('water1'), binding: 'waterTempAdj1' });
|
|
1303
|
-
if (sys.board.heaters.isSolarInstalled())
|
|
1304
|
-
sensors.push({ name: 'Solar Sensor', temp: state.temps.solar, tempAdj: sys.equipment.tempSensors.getCalibration('solar1'), binding: 'solarTempAdj1' });
|
|
1305
|
-
}
|
|
1453
|
+
public syncCustomNamesValueMap() {
|
|
1454
|
+
sys.customNames.sortById();
|
|
1455
|
+
sys.board.valueMaps.customNames = new byteValueMap(
|
|
1456
|
+
sys.customNames.get().map((el, idx) => {
|
|
1457
|
+
return [idx + 200, { name: el.name, desc: el.name }];
|
|
1458
|
+
})
|
|
1459
|
+
);
|
|
1306
1460
|
}
|
|
1307
|
-
return
|
|
1308
|
-
}
|
|
1309
|
-
public async setCustomNamesAsync(names: any[]): Promise<CustomNameCollection> {
|
|
1310
|
-
if (!Array.isArray(names)) return Promise.reject(new InvalidEquipmentDataError(`Data is not an array`, 'customNames', names))
|
|
1311
|
-
let arr = [];
|
|
1312
|
-
for (let i = 0; i < names.length; i++) { arr.push(sys.board.system.setCustomNameAsync(names[i])); }
|
|
1313
|
-
return new Promise<CustomNameCollection>(async (resolve, reject) => {
|
|
1314
|
-
try {
|
|
1315
|
-
await Promise.all(arr).catch(err => reject(err));
|
|
1316
|
-
// sys.board.system.syncCustomNamesValueMap(); Each custom name promise is already syncing the bytevalue array
|
|
1317
|
-
|
|
1318
|
-
resolve(sys.customNames);
|
|
1319
|
-
}
|
|
1320
|
-
catch (err) { reject(err); }
|
|
1321
|
-
});
|
|
1322
|
-
}
|
|
1323
|
-
public async setCustomNameAsync(data: any): Promise<CustomName> {
|
|
1324
|
-
return new Promise<CustomName>((resolve, reject) => {
|
|
1325
|
-
let id = parseInt(data.id, 10);
|
|
1326
|
-
if (isNaN(id)) return reject(new InvalidEquipmentIdError('Invalid Custom Name Id', data.id, 'customName'));
|
|
1327
|
-
if (id > sys.equipment.maxCustomNames) return reject(new InvalidEquipmentIdError('Custom Name Id out of range', data.id, 'customName'));
|
|
1328
|
-
let cname = sys.customNames.getItemById(id, true);
|
|
1329
|
-
cname.name = data.name;
|
|
1330
|
-
sys.board.system.syncCustomNamesValueMap();
|
|
1331
|
-
return resolve(cname);
|
|
1332
|
-
});
|
|
1333
|
-
}
|
|
1334
|
-
public syncCustomNamesValueMap() {
|
|
1335
|
-
sys.customNames.sortById();
|
|
1336
|
-
sys.board.valueMaps.customNames = new byteValueMap(
|
|
1337
|
-
sys.customNames.get().map((el, idx) => {
|
|
1338
|
-
return [idx + 200, { name: el.name, desc: el.name }];
|
|
1339
|
-
})
|
|
1340
|
-
);
|
|
1341
|
-
}
|
|
1461
|
+
public async setPanelModeAsync(data: any): Promise<any> { return { mode: state.mode }; }
|
|
1342
1462
|
}
|
|
1343
1463
|
export class BodyCommands extends BoardCommands {
|
|
1344
1464
|
public async restore(rest: { poolConfig: any, poolState: any }, ctx: any, res: RestoreResults): Promise<boolean> {
|
|
@@ -1391,125 +1511,125 @@ export class BodyCommands extends BoardCommands {
|
|
|
1391
1511
|
}
|
|
1392
1512
|
public freezeProtectBodyOn: Date;
|
|
1393
1513
|
public freezeProtectStart: Date;
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1514
|
+
public async syncFreezeProtection() {
|
|
1515
|
+
try {
|
|
1516
|
+
// Go through all the features and circuits to make sure we have the freeze protect set appropriately. The freeze
|
|
1517
|
+
// flag will have already been set whether this is a Nixie setup or there is an OCP involved.
|
|
1398
1518
|
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1519
|
+
// First turn on/off any features that are in our control that should be under our control. If this is an OCP we
|
|
1520
|
+
// do not create features beyond those controlled by the OCP so we don't need to check these in that condition. That is
|
|
1521
|
+
// why it first checks the controller type.
|
|
1522
|
+
let freeze = utils.makeBool(state.freeze);
|
|
1523
|
+
if (sys.controllerType === ControllerType.Nixie) {
|
|
1524
|
+
// If we are a Nixie controller we need to evaluate the current freeze settings against the air temperature.
|
|
1525
|
+
if (typeof state.temps.air !== 'undefined') {
|
|
1526
|
+
// Start freeze protection when the temperature is <= the threshold but don't stop it until we are 2 degrees above the threshold. This
|
|
1527
|
+
// makes for a 3 degree offset.
|
|
1528
|
+
if (state.temps.air <= sys.general.options.freezeThreshold) freeze = true;
|
|
1529
|
+
else if (state.freeze && state.temps.air - 2 > sys.general.options.freezeThreshold) freeze = false;
|
|
1530
|
+
}
|
|
1531
|
+
else freeze = false;
|
|
1412
1532
|
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
}
|
|
1438
|
-
let bodyRotationChecked = false;
|
|
1439
|
-
for (let i = 0; i < sys.circuits.length; i++) {
|
|
1440
|
-
let circ = sys.circuits.getItemByIndex(i);
|
|
1441
|
-
let cstate = state.circuits.getItemById(circ.id);
|
|
1442
|
-
if (!circ.freeze || !circ.isActive === true || circ.master !== 1) {
|
|
1443
|
-
cstate.freezeProtect = false;
|
|
1444
|
-
continue; // This is not affected by freeze conditions.
|
|
1445
|
-
}
|
|
1446
|
-
if (sys.equipment.shared && freeze && (circ.id === 1 || circ.id === 6)) {
|
|
1447
|
-
// Exit out of here because we already checked the body rotation. We only want to do this once since it can be expensive turning
|
|
1448
|
-
// on a particular body.
|
|
1449
|
-
if (bodyRotationChecked) continue;
|
|
1450
|
-
// These are our body circuits so we need to check to see if they need to be rotated between pool and spa.
|
|
1451
|
-
let pool = circ.id === 6 ? circ : sys.circuits.getItemById(6);
|
|
1452
|
-
let spa = circ.id === 1 ? circ : sys.circuits.getItemById(1);
|
|
1453
|
-
if (pool.freeze && spa.freeze) {
|
|
1454
|
-
// We only need to rotate between pool and spa when they are both checked.
|
|
1455
|
-
let pstate = circ.id === 6 ? cstate : state.circuits.getItemById(6);
|
|
1456
|
-
let sstate = circ.id === 1 ? cstate : state.circuits.getItemById(1);
|
|
1457
|
-
if (!pstate.isOn && !sstate.isOn) {
|
|
1458
|
-
// Neither the pool or spa are on so we will turn on the pool first.
|
|
1459
|
-
pstate.freezeProtect = true;
|
|
1460
|
-
this.freezeProtectBodyOn = new Date();
|
|
1461
|
-
await sys.board.circuits.setCircuitStateAsync(6, true);
|
|
1533
|
+
// We need to know when we first turned the freeze protection on. This is because we will be rotating between pool and spa
|
|
1534
|
+
// on shared body systems when both pool and spa have freeze protection checked.
|
|
1535
|
+
if (state.freeze !== freeze) {
|
|
1536
|
+
this.freezeProtectStart = freeze ? new Date() : undefined;
|
|
1537
|
+
state.freeze = freeze;
|
|
1538
|
+
}
|
|
1539
|
+
for (let i = 0; i < sys.features.length; i++) {
|
|
1540
|
+
let feature = sys.features.getItemByIndex(i);
|
|
1541
|
+
let fstate = state.features.getItemById(feature.id, true);
|
|
1542
|
+
if (!feature.freeze || !feature.isActive === true || feature.master !== 1) {
|
|
1543
|
+
fstate.freezeProtect = false;
|
|
1544
|
+
continue; // This is not affected by freeze conditions.
|
|
1545
|
+
}
|
|
1546
|
+
if (freeze && !fstate.isOn) {
|
|
1547
|
+
// This feature should be on because we are freezing.
|
|
1548
|
+
fstate.freezeProtect = true;
|
|
1549
|
+
await sys.board.features.setFeatureStateAsync(feature.id, true);
|
|
1550
|
+
}
|
|
1551
|
+
else if (!freeze && fstate.freezeProtect) {
|
|
1552
|
+
// This feature was turned on by freeze protection. We need to turn it off because it has warmed up.
|
|
1553
|
+
fstate.freezeProtect = false;
|
|
1554
|
+
await sys.board.features.setFeatureStateAsync(feature.id, false);
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1462
1557
|
}
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1558
|
+
let bodyRotationChecked = false;
|
|
1559
|
+
for (let i = 0; i < sys.circuits.length; i++) {
|
|
1560
|
+
let circ = sys.circuits.getItemByIndex(i);
|
|
1561
|
+
let cstate = state.circuits.getItemById(circ.id);
|
|
1562
|
+
if (!circ.freeze || !circ.isActive === true || circ.master !== 1) {
|
|
1563
|
+
cstate.freezeProtect = false;
|
|
1564
|
+
continue; // This is not affected by freeze conditions.
|
|
1565
|
+
}
|
|
1566
|
+
if (sys.equipment.shared && freeze && (circ.id === 1 || circ.id === 6)) {
|
|
1567
|
+
// Exit out of here because we already checked the body rotation. We only want to do this once since it can be expensive turning
|
|
1568
|
+
// on a particular body.
|
|
1569
|
+
if (bodyRotationChecked) continue;
|
|
1570
|
+
// These are our body circuits so we need to check to see if they need to be rotated between pool and spa.
|
|
1571
|
+
let pool = circ.id === 6 ? circ : sys.circuits.getItemById(6);
|
|
1572
|
+
let spa = circ.id === 1 ? circ : sys.circuits.getItemById(1);
|
|
1573
|
+
if (pool.freeze && spa.freeze) {
|
|
1574
|
+
// We only need to rotate between pool and spa when they are both checked.
|
|
1575
|
+
let pstate = circ.id === 6 ? cstate : state.circuits.getItemById(6);
|
|
1576
|
+
let sstate = circ.id === 1 ? cstate : state.circuits.getItemById(1);
|
|
1577
|
+
if (!pstate.isOn && !sstate.isOn) {
|
|
1578
|
+
// Neither the pool or spa are on so we will turn on the pool first.
|
|
1579
|
+
pstate.freezeProtect = true;
|
|
1580
|
+
this.freezeProtectBodyOn = new Date();
|
|
1581
|
+
await sys.board.circuits.setCircuitStateAsync(6, true);
|
|
1582
|
+
}
|
|
1583
|
+
else {
|
|
1584
|
+
// If neither of the bodies were turned on for freeze protection then we need to ignore this.
|
|
1585
|
+
if (!pstate.freezeProtect && !sstate.freezeProtect) {
|
|
1586
|
+
this.freezeProtectBodyOn = undefined;
|
|
1587
|
+
continue;
|
|
1588
|
+
}
|
|
1469
1589
|
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1590
|
+
// One of the two bodies is on so we need to check for the rotation. If it is time to rotate do the rotation.
|
|
1591
|
+
if (typeof this.freezeProtectBodyOn === 'undefined') this.freezeProtectBodyOn = new Date();
|
|
1592
|
+
let dt = new Date().getTime();
|
|
1593
|
+
if (dt - 1000 * 60 * 15 > this.freezeProtectBodyOn.getTime()) {
|
|
1594
|
+
logger.info(`Swapping bodies for freeze protection pool:${pstate.isOn} spa:${sstate.isOn} interval: ${utils.formatDuration(dt - this.freezeProtectBodyOn.getTime() / 1000)}`);
|
|
1595
|
+
// 10 minutes has elapsed so we will be rotating to the other body.
|
|
1596
|
+
if (pstate.isOn) {
|
|
1597
|
+
// The setCircuitState method will handle turning off the pool body.
|
|
1598
|
+
sstate.freezeProtect = true;
|
|
1599
|
+
pstate.freezeProtect = false;
|
|
1600
|
+
await sys.board.circuits.setCircuitStateAsync(1, true);
|
|
1601
|
+
}
|
|
1602
|
+
else {
|
|
1603
|
+
sstate.freezeProtect = false;
|
|
1604
|
+
pstate.freezeProtect = true;
|
|
1605
|
+
await sys.board.circuits.setCircuitStateAsync(6, true);
|
|
1606
|
+
}
|
|
1607
|
+
// Set a new date as this will be our rotation check now.
|
|
1608
|
+
this.freezeProtectBodyOn = new Date();
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
else {
|
|
1613
|
+
// Only this circuit is selected for freeze protection so we don't need any special treatment.
|
|
1614
|
+
cstate.freezeProtect = true;
|
|
1615
|
+
if (!cstate.isOn) await sys.board.circuits.setCircuitStateAsync(circ.id, true);
|
|
1616
|
+
}
|
|
1617
|
+
bodyRotationChecked = true;
|
|
1481
1618
|
}
|
|
1482
|
-
else {
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1619
|
+
else if (freeze && !cstate.isOn) {
|
|
1620
|
+
// This circuit should be on because we are freezing.
|
|
1621
|
+
cstate.freezeProtect = true;
|
|
1622
|
+
await sys.board.circuits.setCircuitStateAsync(circ.id, true);
|
|
1623
|
+
}
|
|
1624
|
+
else if (!freeze && cstate.freezeProtect) {
|
|
1625
|
+
// This feature was turned on by freeze protection. We need to turn it off because it has warmed up.
|
|
1626
|
+
await sys.board.circuits.setCircuitStateAsync(circ.id, false);
|
|
1627
|
+
cstate.freezeProtect = false;
|
|
1486
1628
|
}
|
|
1487
|
-
// Set a new date as this will be our rotation check now.
|
|
1488
|
-
this.freezeProtectBodyOn = new Date();
|
|
1489
|
-
}
|
|
1490
1629
|
}
|
|
1491
|
-
}
|
|
1492
|
-
else {
|
|
1493
|
-
// Only this circuit is selected for freeze protection so we don't need any special treatment.
|
|
1494
|
-
cstate.freezeProtect = true;
|
|
1495
|
-
if (!cstate.isOn) await sys.board.circuits.setCircuitStateAsync(circ.id, true);
|
|
1496
|
-
}
|
|
1497
|
-
bodyRotationChecked = true;
|
|
1498
|
-
}
|
|
1499
|
-
else if (freeze && !cstate.isOn) {
|
|
1500
|
-
// This circuit should be on because we are freezing.
|
|
1501
|
-
cstate.freezeProtect = true;
|
|
1502
|
-
await sys.board.circuits.setCircuitStateAsync(circ.id, true);
|
|
1503
1630
|
}
|
|
1504
|
-
|
|
1505
|
-
// This feature was turned on by freeze protection. We need to turn it off because it has warmed up.
|
|
1506
|
-
await sys.board.circuits.setCircuitStateAsync(circ.id, false);
|
|
1507
|
-
cstate.freezeProtect = false;
|
|
1508
|
-
}
|
|
1509
|
-
}
|
|
1631
|
+
catch (err) { logger.error(`syncFreezeProtection: Error synchronizing freeze protection states: ${err.message}`); }
|
|
1510
1632
|
}
|
|
1511
|
-
catch (err) { logger.error(`syncFreezeProtection: Error synchronizing freeze protection states: ${err.message}`); }
|
|
1512
|
-
}
|
|
1513
1633
|
|
|
1514
1634
|
public async initFilters() {
|
|
1515
1635
|
try {
|
|
@@ -1650,57 +1770,73 @@ export class BodyCommands extends BoardCommands {
|
|
|
1650
1770
|
sys.board.heaters.syncHeaterStates();
|
|
1651
1771
|
return Promise.resolve(bstate);
|
|
1652
1772
|
}
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1773
|
+
public getHeatSources(bodyId: number) {
|
|
1774
|
+
let heatSources = [];
|
|
1775
|
+
let heatTypes = this.board.heaters.getInstalledHeaterTypes(bodyId);
|
|
1776
|
+
heatSources.push(this.board.valueMaps.heatSources.transformByName('nochange'));
|
|
1777
|
+
if (heatTypes.total > 0) heatSources.push(this.board.valueMaps.heatSources.transformByName('off'));
|
|
1778
|
+
if (heatTypes.gas > 0) heatSources.push(this.board.valueMaps.heatSources.transformByName('heater'));
|
|
1779
|
+
if (heatTypes.mastertemp > 0) heatSources.push(this.board.valueMaps.heatSources.transformByName('mastertemp'));
|
|
1780
|
+
if (heatTypes.solar > 0) {
|
|
1781
|
+
let hm = this.board.valueMaps.heatSources.transformByName('solar');
|
|
1782
|
+
heatSources.push(hm);
|
|
1783
|
+
if (heatTypes.total > 1) heatSources.push(this.board.valueMaps.heatSources.transformByName('solarpref'));
|
|
1784
|
+
}
|
|
1785
|
+
if (heatTypes.heatpump > 0) {
|
|
1786
|
+
let hm = this.board.valueMaps.heatSources.transformByName('heatpump');
|
|
1787
|
+
heatSources.push(hm);
|
|
1788
|
+
if (heatTypes.total > 1) heatSources.push(this.board.valueMaps.heatSources.transformByName('heatpumppref'));
|
|
1789
|
+
}
|
|
1790
|
+
if (heatTypes.ultratemp > 0) {
|
|
1791
|
+
let hm = this.board.valueMaps.heatSources.transformByName('ultratemp');
|
|
1792
|
+
heatSources.push(hm);
|
|
1793
|
+
if (heatTypes.total > 1) heatSources.push(this.board.valueMaps.heatSources.transformByName('ultratemppref'));
|
|
1794
|
+
}
|
|
1795
|
+
if (heatTypes.hybrid > 0) {
|
|
1796
|
+
heatSources.push(this.board.valueMaps.heatSources.transformByName('hybheat'));
|
|
1797
|
+
heatSources.push(this.board.valueMaps.heatSources.transformByName('hybheatpump'));
|
|
1798
|
+
heatSources.push(this.board.valueMaps.heatSources.transformByName('hybhybrid'));
|
|
1799
|
+
heatSources.push(this.board.valueMaps.heatSources.transformByName('hybdual'));
|
|
1800
|
+
}
|
|
1801
|
+
return heatSources;
|
|
1674
1802
|
}
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
let heatModes = [];
|
|
1679
|
-
sys.board.heaters.updateHeaterServices();
|
|
1803
|
+
public getHeatModes(bodyId: number) {
|
|
1804
|
+
let heatModes = [];
|
|
1805
|
+
sys.board.heaters.updateHeaterServices();
|
|
1680
1806
|
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1807
|
+
// 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)
|
|
1808
|
+
heatModes.push(this.board.valueMaps.heatModes.transformByName('off')); // In IC fw 1.047 off is no longer 0.
|
|
1809
|
+
let heatTypes = this.board.heaters.getInstalledHeaterTypes(bodyId);
|
|
1810
|
+
if (heatTypes.hybrid > 0) {
|
|
1811
|
+
// RKS: 08-24-22 Unfortunately we mistakenly thought that these needed to be matched to the other heater types. The heat modes
|
|
1812
|
+
// are unique for the hybrid heater.
|
|
1813
|
+
heatModes.push(sys.board.valueMaps.heatModes.transformByName('hybheat'));
|
|
1814
|
+
heatModes.push(sys.board.valueMaps.heatModes.transformByName('hybheatpump'));
|
|
1815
|
+
heatModes.push(sys.board.valueMaps.heatModes.transformByName('hybhybrid'));
|
|
1816
|
+
heatModes.push(sys.board.valueMaps.heatModes.transformByName('hybdual'));
|
|
1817
|
+
//heatModes = this.board.valueMaps.heatModes.toArray();
|
|
1818
|
+
}
|
|
1819
|
+
if (heatTypes.gas > 0) {
|
|
1820
|
+
heatModes.push(this.board.valueMaps.heatModes.transformByName('heater'));
|
|
1821
|
+
}
|
|
1822
|
+
if (heatTypes.mastertemp > 0) heatModes.push(this.board.valueMaps.heatModes.transformByName('mtheater'));
|
|
1823
|
+
if (heatTypes.solar > 0) {
|
|
1824
|
+
let hm = this.board.valueMaps.heatModes.transformByName('solar');
|
|
1825
|
+
heatModes.push(hm);
|
|
1826
|
+
if (heatTypes.total > 1) heatModes.push(this.board.valueMaps.heatModes.transformByName('solarpref'));
|
|
1827
|
+
}
|
|
1828
|
+
if (heatTypes.heatpump > 0) {
|
|
1829
|
+
let hm = this.board.valueMaps.heatModes.transformByName('heatpump');
|
|
1830
|
+
heatModes.push(hm);
|
|
1831
|
+
if (heatTypes.total > 1) heatModes.push(this.board.valueMaps.heatModes.transformByName('heatpumppref'));
|
|
1832
|
+
}
|
|
1833
|
+
if (heatTypes.ultratemp > 0) {
|
|
1834
|
+
let hm = this.board.valueMaps.heatModes.transformByName('ultratemp');
|
|
1835
|
+
heatModes.push(hm);
|
|
1836
|
+
if (heatTypes.total > 1) heatModes.push(this.board.valueMaps.heatModes.transformByName('ultratemppref'));
|
|
1837
|
+
}
|
|
1838
|
+
return heatModes;
|
|
1701
1839
|
}
|
|
1702
|
-
return heatModes;
|
|
1703
|
-
}
|
|
1704
1840
|
public getPoolStates(): BodyTempState[] {
|
|
1705
1841
|
let arrPools = [];
|
|
1706
1842
|
for (let i = 0; i < state.temps.bodies.length; i++) {
|
|
@@ -1833,7 +1969,7 @@ export class PumpCommands extends BoardCommands {
|
|
|
1833
1969
|
return this.board.valueMaps.pumpUnits.transform(val);
|
|
1834
1970
|
}
|
|
1835
1971
|
}
|
|
1836
|
-
public async setPumpAsync(data: any): Promise<Pump> {
|
|
1972
|
+
public async setPumpAsync(data: any, send: boolean = true): Promise<Pump> {
|
|
1837
1973
|
try {
|
|
1838
1974
|
let id = typeof data.id === 'undefined' ? -1 : parseInt(data.id, 10);
|
|
1839
1975
|
if (id <= 0) id = sys.pumps.filter(elem => elem.master === 1).getMaxId(false, 49) + 1;
|
|
@@ -1877,42 +2013,6 @@ export class PumpCommands extends BoardCommands {
|
|
|
1877
2013
|
let spump = state.pumps.getItemById(pump.id);
|
|
1878
2014
|
spump.emitData('pumpExt', spump.getExtended());
|
|
1879
2015
|
}
|
|
1880
|
-
|
|
1881
|
-
public setType(pump: Pump, pumpType: number) {
|
|
1882
|
-
// if we are changing pump types, need to clear out circuits
|
|
1883
|
-
// and props that aren't for this pump type
|
|
1884
|
-
let _id = pump.id;
|
|
1885
|
-
if (pump.type !== pumpType || pumpType === 0) {
|
|
1886
|
-
let _p = pump.get(true);
|
|
1887
|
-
// const _isVirtual = typeof _p.isVirtual !== 'undefined' ? _p.isVirtual : false;
|
|
1888
|
-
sys.pumps.removeItemById(_id);
|
|
1889
|
-
pump = sys.pumps.getItemById(_id, true);
|
|
1890
|
-
/* if (_isVirtual) {
|
|
1891
|
-
// pump.isActive = true;
|
|
1892
|
-
// pump.isVirtual = true;
|
|
1893
|
-
pump.master = 1;
|
|
1894
|
-
} */
|
|
1895
|
-
state.pumps.removeItemById(pump.id);
|
|
1896
|
-
pump.type = pumpType;
|
|
1897
|
-
let type = sys.board.valueMaps.pumpTypes.transform(pumpType);
|
|
1898
|
-
|
|
1899
|
-
if (type.name === 'vs' || type.name === 'vsf') {
|
|
1900
|
-
pump.speedStepSize = 10;
|
|
1901
|
-
pump.minSpeed = type.minSpeed;
|
|
1902
|
-
pump.maxSpeed = type.maxSpeed;
|
|
1903
|
-
}
|
|
1904
|
-
if (type.name === 'vf' || type.name === 'vsf') {
|
|
1905
|
-
pump.flowStepSize = 1;
|
|
1906
|
-
pump.minFlow = type.minFlow;
|
|
1907
|
-
pump.maxFlow = type.maxFlow;
|
|
1908
|
-
}
|
|
1909
|
-
let spump = state.pumps.getItemById(pump.id, true);
|
|
1910
|
-
spump.type = pump.type;
|
|
1911
|
-
spump.isActive = pump.isActive;
|
|
1912
|
-
spump.status = 0;
|
|
1913
|
-
spump.emitData('pumpExt', spump.getExtended());
|
|
1914
|
-
}
|
|
1915
|
-
}
|
|
1916
2016
|
public availableCircuits() {
|
|
1917
2017
|
let _availCircuits = [];
|
|
1918
2018
|
for (let i = 0; i < sys.circuits.length; i++) {
|
|
@@ -2110,262 +2210,270 @@ export class CircuitCommands extends BoardCommands {
|
|
|
2110
2210
|
}
|
|
2111
2211
|
} catch (err) { logger.error(`syncCircuitRelayStates: Error synchronizing circuit relays ${err.message}`); }
|
|
2112
2212
|
}
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2213
|
+
public syncVirtualCircuitStates() {
|
|
2214
|
+
try {
|
|
2215
|
+
let arrCircuits = sys.board.valueMaps.virtualCircuits.toArray();
|
|
2216
|
+
let poolStates = sys.board.bodies.getPoolStates();
|
|
2217
|
+
let spaStates = sys.board.bodies.getSpaStates();
|
|
2218
|
+
// The following should work for all board types if the virtualCiruit valuemaps use common names. The circuit ids can be
|
|
2219
|
+
// different as well as the descriptions but these should have common names since they are all derived from existing states.
|
|
2120
2220
|
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2221
|
+
// This also removes virtual circuits depending on whether heaters exsits on the bodies. Not sure why we are doing this
|
|
2222
|
+
// as the body data contains whether a body is heated or not. Perhapse some attached interface is using
|
|
2223
|
+
// the virtual circuit list as a means to determine whether solar is available. That is totally flawed if that is the case.
|
|
2224
|
+
for (let i = 0; i < arrCircuits.length; i++) {
|
|
2225
|
+
let vc = arrCircuits[i];
|
|
2226
|
+
let remove = false;
|
|
2227
|
+
let bState = false;
|
|
2228
|
+
let cstate: VirtualCircuitState = null;
|
|
2229
|
+
switch (vc.name) {
|
|
2230
|
+
case 'poolHeater':
|
|
2231
|
+
// If any pool is heating up.
|
|
2232
|
+
remove = true;
|
|
2233
|
+
for (let j = 0; j < poolStates.length; j++) {
|
|
2234
|
+
if (poolStates[j].heaterOptions.total > 0) remove = false;
|
|
2235
|
+
}
|
|
2236
|
+
if (!remove) {
|
|
2237
|
+
// Determine whether the pool heater is on.
|
|
2238
|
+
for (let j = 0; j < poolStates.length; j++) {
|
|
2239
|
+
let hstatus = sys.board.valueMaps.heatStatus.getName(poolStates[j].heatStatus);
|
|
2240
|
+
if (hstatus !== 'off' && hstatus !== 'solar') {
|
|
2241
|
+
// In this instance we may have a delay underway.
|
|
2242
|
+
let hstate = state.heaters.find(x => x.bodyId === 1 && x.startupDelay === true && x.type.name !== 'solar');
|
|
2243
|
+
bState = typeof hstate === 'undefined';
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
break;
|
|
2248
|
+
case 'spaHeater':
|
|
2249
|
+
remove = true;
|
|
2250
|
+
for (let j = 0; j < spaStates.length; j++) {
|
|
2251
|
+
if (spaStates[j].heaterOptions.total > 0) remove = false;
|
|
2252
|
+
}
|
|
2253
|
+
if (!remove) {
|
|
2254
|
+
// Determine whether the spa heater is on.
|
|
2255
|
+
for (let j = 0; j < spaStates.length; j++) {
|
|
2256
|
+
let hstatus = sys.board.valueMaps.heatStatus.getName(spaStates[j].heatStatus);
|
|
2257
|
+
if (hstatus !== 'off' && hstatus !== 'solar') {
|
|
2258
|
+
// In this instance we may have a delay underway.
|
|
2259
|
+
let hstate = state.heaters.find(x => x.bodyId === 2 && x.startupDelay === true && x.type.name !== 'solar');
|
|
2260
|
+
bState = typeof hstate === 'undefined';
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
//for (let j = 0; j < spaStates.length; j++) {
|
|
2264
|
+
// if (sys.board.valueMaps.heatStatus.getName(spaStates[j].heatStatus) === 'heater') bState = true;
|
|
2265
|
+
//}
|
|
2266
|
+
}
|
|
2267
|
+
break;
|
|
2268
|
+
case 'heater':
|
|
2269
|
+
// If heater is on for any body
|
|
2270
|
+
// RSG 5-3-22: Heater will now refer to any poolHeater or spaHeater but not solar or other types. anyHeater now takes that role.
|
|
2271
|
+
remove = true;
|
|
2272
|
+
for (let j = 0; j < poolStates.length; j++) {
|
|
2273
|
+
if (poolStates[j].heaterOptions.solar + poolStates[j].heaterOptions.heatpump > 0) remove = false;
|
|
2274
|
+
}
|
|
2275
|
+
if (remove) {
|
|
2276
|
+
for (let j = 0; j < spaStates.length; j++) {
|
|
2277
|
+
if (spaStates[j].heaterOptions.solar + spaStates[j].heaterOptions.heatpump > 0) remove = false;
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
if (!remove) {
|
|
2281
|
+
for (let j = 0; j < poolStates.length && !bState; j++) {
|
|
2282
|
+
let hstatus = sys.board.valueMaps.heatStatus.getName(poolStates[j].heatStatus);
|
|
2283
|
+
if (hstatus === 'heater' || hstatus === 'hpheat' || hstatus === 'mtheat' || hstatus === 'hybheat') bState = true;
|
|
2284
|
+
}
|
|
2285
|
+
for (let j = 0; j < spaStates.length && !bState; j++) {
|
|
2286
|
+
let hstatus = sys.board.valueMaps.heatStatus.getName(spaStates[j].heatStatus);
|
|
2287
|
+
if (hstatus === 'heater' || hstatus === 'hpheat' || hstatus === 'mtheat' || hstatus === 'hybheat') bState = true;
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
break;
|
|
2291
|
+
case 'freeze':
|
|
2292
|
+
// If freeze protection has been turned on.
|
|
2293
|
+
bState = state.freeze;
|
|
2294
|
+
break;
|
|
2295
|
+
case 'poolSpa':
|
|
2296
|
+
// If any pool or spa is on
|
|
2297
|
+
for (let j = 0; j < poolStates.length && !bState; j++) {
|
|
2298
|
+
if (poolStates[j].isOn) bState = true;
|
|
2299
|
+
}
|
|
2300
|
+
for (let j = 0; j < spaStates.length && !bState; j++) {
|
|
2301
|
+
if (spaStates[j].isOn) bState = true;
|
|
2302
|
+
}
|
|
2303
|
+
break;
|
|
2304
|
+
case 'solarHeat':
|
|
2305
|
+
case 'solar':
|
|
2306
|
+
// If solar is on for any body
|
|
2307
|
+
remove = true;
|
|
2308
|
+
for (let j = 0; j < poolStates.length; j++) {
|
|
2309
|
+
// RKS: 05-30-22 - I have no idea why this would include the heatpump options
|
|
2310
|
+
//if (poolStates[j].heaterOptions.solar + poolStates[j].heaterOptions.heatpump > 0) remove = false;
|
|
2311
|
+
if (poolStates[j].heaterOptions.solar) remove = false;
|
|
2312
|
+
}
|
|
2313
|
+
if (remove) {
|
|
2314
|
+
for (let j = 0; j < spaStates.length; j++) {
|
|
2315
|
+
// RKS: 05-30-22 - I have no idea why this would include the heatpump options
|
|
2316
|
+
//if (spaStates[j].heaterOptions.solar + spaStates[j].heaterOptions.heatpump > 0) remove = false;
|
|
2317
|
+
if (spaStates[j].heaterOptions.solar) remove = false;
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
if (!remove) {
|
|
2321
|
+
for (let j = 0; j < poolStates.length && !bState; j++) {
|
|
2322
|
+
if (sys.board.valueMaps.heatStatus.getName(poolStates[j].heatStatus) === 'solar') bState = true;
|
|
2323
|
+
}
|
|
2324
|
+
for (let j = 0; j < spaStates.length && !bState; j++) {
|
|
2325
|
+
if (sys.board.valueMaps.heatStatus.getName(spaStates[j].heatStatus) === 'solar') bState = true;
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2328
|
+
break;
|
|
2329
|
+
case 'solar1':
|
|
2330
|
+
remove = true;
|
|
2331
|
+
for (let j = 0; j < poolStates.length; j++) {
|
|
2332
|
+
if (poolStates[j].id === 1 && poolStates[j].heaterOptions.solar) {
|
|
2333
|
+
remove = false;
|
|
2334
|
+
vc.desc = `${poolStates[j].name} Solar`;
|
|
2335
|
+
if (sys.board.valueMaps.heatStatus.getName(poolStates[j].heatStatus) === 'solar') {
|
|
2336
|
+
// In this instance we may have a delay underway.
|
|
2337
|
+
let hstate = state.heaters.find(x => x.bodyId === 1 && x.startupDelay === true && x.type.name === 'solar');
|
|
2338
|
+
bState = typeof hstate === 'undefined';
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2342
|
+
for (let j = 0; j < spaStates.length; j++) {
|
|
2343
|
+
if (spaStates[j].id === 1 && spaStates[j].heaterOptions.solar) {
|
|
2344
|
+
remove = false;
|
|
2345
|
+
vc.desc = `${spaStates[j].name} Solar`;
|
|
2346
|
+
if (sys.board.valueMaps.heatStatus.getName(spaStates[j].heatStatus) === 'solar') {
|
|
2347
|
+
// In this instance we may have a delay underway.
|
|
2348
|
+
let hstate = state.heaters.find(x => x.bodyId === 1 && x.startupDelay === true && x.type.name === 'solar');
|
|
2349
|
+
bState = typeof hstate === 'undefined';
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2245
2353
|
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2354
|
+
break;
|
|
2355
|
+
case 'solar2':
|
|
2356
|
+
remove = true;
|
|
2357
|
+
for (let j = 0; j < poolStates.length; j++) {
|
|
2358
|
+
if (poolStates[j].id === 2 && poolStates[j].heaterOptions.solar) {
|
|
2359
|
+
remove = false;
|
|
2360
|
+
vc.desc = `${poolStates[j].name} Solar`;
|
|
2361
|
+
if (sys.board.valueMaps.heatStatus.getName(spaStates[j].heatStatus) === 'solar') {
|
|
2362
|
+
// In this instance we may have a delay underway.
|
|
2363
|
+
let hstate = state.heaters.find(x => x.bodyId === 2 && x.startupDelay === true && x.type.name === 'solar');
|
|
2364
|
+
bState = typeof hstate === 'undefined';
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
2368
|
+
for (let j = 0; j < spaStates.length; j++) {
|
|
2369
|
+
if (spaStates[j].id === 2 && spaStates[j].heaterOptions.solar) {
|
|
2370
|
+
remove = false;
|
|
2371
|
+
vc.desc = `${spaStates[j].name} Solar`;
|
|
2372
|
+
if (sys.board.valueMaps.heatStatus.getName(spaStates[j].heatStatus) === 'solar') {
|
|
2373
|
+
// In this instance we may have a delay underway.
|
|
2374
|
+
let hstate = state.heaters.find(x => x.bodyId === 2 && x.startupDelay === true && x.type.name === 'solar');
|
|
2375
|
+
bState = typeof hstate === 'undefined';
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
break;
|
|
2380
|
+
case 'solar3':
|
|
2381
|
+
remove = true;
|
|
2382
|
+
for (let j = 0; j < poolStates.length; j++) {
|
|
2383
|
+
if (poolStates[j].id === 3 && poolStates[j].heaterOptions.solar) {
|
|
2384
|
+
remove = false;
|
|
2385
|
+
vc.desc = `${poolStates[j].name} Solar`;
|
|
2386
|
+
if (sys.board.valueMaps.heatStatus.getName(spaStates[j].heatStatus) === 'solar') {
|
|
2387
|
+
// In this instance we may have a delay underway.
|
|
2388
|
+
let hstate = state.heaters.find(x => x.bodyId === 3 && x.startupDelay === true && x.type.name === 'solar');
|
|
2389
|
+
bState = typeof hstate === 'undefined';
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
for (let j = 0; j < spaStates.length; j++) {
|
|
2394
|
+
if (spaStates[j].id === 3 && spaStates[j].heaterOptions.solar) {
|
|
2395
|
+
remove = false;
|
|
2396
|
+
vc.desc = `${spaStates[j].name} Solar`;
|
|
2397
|
+
if (sys.board.valueMaps.heatStatus.getName(spaStates[j].heatStatus) === 'solar') {
|
|
2398
|
+
// In this instance we may have a delay underway.
|
|
2399
|
+
let hstate = state.heaters.find(x => x.bodyId === 3 && x.startupDelay === true && x.type.name === 'solar');
|
|
2400
|
+
bState = typeof hstate === 'undefined';
|
|
2401
|
+
}
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2296
2404
|
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2405
|
+
break;
|
|
2406
|
+
case 'solar4':
|
|
2407
|
+
remove = true;
|
|
2408
|
+
for (let j = 0; j < poolStates.length; j++) {
|
|
2409
|
+
if (poolStates[j].id === 4 && poolStates[j].heaterOptions.solar) {
|
|
2410
|
+
remove = false;
|
|
2411
|
+
vc.desc = `${poolStates[j].name} Solar`;
|
|
2412
|
+
if (sys.board.valueMaps.heatStatus.getName(spaStates[j].heatStatus) === 'solar') {
|
|
2413
|
+
// In this instance we may have a delay underway.
|
|
2414
|
+
let hstate = state.heaters.find(x => x.bodyId === 4 && x.startupDelay === true && x.type.name === 'solar');
|
|
2415
|
+
bState = typeof hstate === 'undefined';
|
|
2416
|
+
}
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
2419
|
+
for (let j = 0; j < spaStates.length; j++) {
|
|
2420
|
+
if (spaStates[j].id === 4 && spaStates[j].heaterOptions.solar) {
|
|
2421
|
+
remove = false;
|
|
2422
|
+
vc.desc = `${spaStates[j].name} Solar`;
|
|
2423
|
+
if (sys.board.valueMaps.heatStatus.getName(spaStates[j].heatStatus) === 'solar') {
|
|
2424
|
+
// In this instance we may have a delay underway.
|
|
2425
|
+
let hstate = state.heaters.find(x => x.bodyId === 4 && x.startupDelay === true && x.type.name === 'solar');
|
|
2426
|
+
bState = typeof hstate === 'undefined';
|
|
2427
|
+
}
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
break;
|
|
2431
|
+
case 'anyHeater':
|
|
2432
|
+
// RSG 5-3-22 anyHeater now represents any solar, gas, etc heater. This replaces 'heater' which now refers to only gas heaters.
|
|
2433
|
+
remove = true;
|
|
2434
|
+
for (let j = 0; j < poolStates.length; j++) {
|
|
2435
|
+
if (poolStates[j].heaterOptions.total > 0) remove = false;
|
|
2436
|
+
}
|
|
2437
|
+
if (remove) {
|
|
2438
|
+
for (let j = 0; j < spaStates.length; j++) {
|
|
2439
|
+
if (spaStates[j].heaterOptions.total > 0) remove = false;
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2442
|
+
if (!remove) {
|
|
2443
|
+
for (let j = 0; j < poolStates.length && !bState; j++) {
|
|
2444
|
+
let heat = sys.board.valueMaps.heatStatus.getName(poolStates[j].heatStatus);
|
|
2445
|
+
if (heat !== 'off') bState = true;
|
|
2446
|
+
}
|
|
2447
|
+
for (let j = 0; j < spaStates.length && !bState; j++) {
|
|
2448
|
+
let heat = sys.board.valueMaps.heatStatus.getName(spaStates[j].heatStatus);
|
|
2449
|
+
if (heat !== 'off') bState = true;
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
2452
|
+
break;
|
|
2453
|
+
default:
|
|
2454
|
+
remove = true;
|
|
2455
|
+
break;
|
|
2308
2456
|
}
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2457
|
+
if (remove) {
|
|
2458
|
+
if (state.virtualCircuits.exists(x => vc.val === x.id)) {
|
|
2459
|
+
cstate = state.virtualCircuits.getItemById(vc.val, true);
|
|
2460
|
+
cstate.isActive = false;
|
|
2461
|
+
cstate.emitEquipmentChange();
|
|
2462
|
+
}
|
|
2463
|
+
state.virtualCircuits.removeItemById(vc.val);
|
|
2464
|
+
}
|
|
2465
|
+
else {
|
|
2466
|
+
cstate = state.virtualCircuits.getItemById(vc.val, true);
|
|
2467
|
+
cstate.isActive = true;
|
|
2468
|
+
if (cstate !== null) {
|
|
2469
|
+
cstate.isOn = bState;
|
|
2470
|
+
cstate.type = vc.val;
|
|
2471
|
+
cstate.name = vc.desc;
|
|
2472
|
+
}
|
|
2319
2473
|
}
|
|
2320
|
-
}
|
|
2321
|
-
}
|
|
2322
|
-
break;
|
|
2323
|
-
case 'anyHeater':
|
|
2324
|
-
// RSG 5-3-22 anyHeater now represents any solar, gas, etc heater. This replaces 'heater' which now refers to only gas heaters.
|
|
2325
|
-
remove = true;
|
|
2326
|
-
for (let j = 0; j < poolStates.length; j++) {
|
|
2327
|
-
if (poolStates[j].heaterOptions.total > 0) remove = false;
|
|
2328
|
-
}
|
|
2329
|
-
if (remove) {
|
|
2330
|
-
for (let j = 0; j < spaStates.length; j++) {
|
|
2331
|
-
if (spaStates[j].heaterOptions.total > 0) remove = false;
|
|
2332
|
-
}
|
|
2333
|
-
}
|
|
2334
|
-
if (!remove) {
|
|
2335
|
-
for (let j = 0; j < poolStates.length && !bState; j++) {
|
|
2336
|
-
let heat = sys.board.valueMaps.heatStatus.getName(poolStates[j].heatStatus);
|
|
2337
|
-
if (heat !== 'off') bState = true;
|
|
2338
|
-
}
|
|
2339
|
-
for (let j = 0; j < spaStates.length && !bState; j++) {
|
|
2340
|
-
let heat = sys.board.valueMaps.heatStatus.getName(spaStates[j].heatStatus);
|
|
2341
|
-
if (heat !== 'off') bState = true;
|
|
2342
|
-
}
|
|
2343
2474
|
}
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
remove = true;
|
|
2347
|
-
break;
|
|
2348
|
-
}
|
|
2349
|
-
if (remove) {
|
|
2350
|
-
if (state.virtualCircuits.exists(x => vc.val === x.id)) {
|
|
2351
|
-
cstate = state.virtualCircuits.getItemById(vc.val, true);
|
|
2352
|
-
cstate.isActive = false;
|
|
2353
|
-
cstate.emitEquipmentChange();
|
|
2354
|
-
}
|
|
2355
|
-
state.virtualCircuits.removeItemById(vc.val);
|
|
2356
|
-
}
|
|
2357
|
-
else {
|
|
2358
|
-
cstate = state.virtualCircuits.getItemById(vc.val, true);
|
|
2359
|
-
cstate.isActive = true;
|
|
2360
|
-
if (cstate !== null) {
|
|
2361
|
-
cstate.isOn = bState;
|
|
2362
|
-
cstate.type = vc.val;
|
|
2363
|
-
cstate.name = vc.desc;
|
|
2364
|
-
}
|
|
2365
|
-
}
|
|
2366
|
-
}
|
|
2367
|
-
} catch (err) { logger.error(`Error syncronizing virtual circuits`); }
|
|
2368
|
-
}
|
|
2475
|
+
} catch (err) { logger.error(`Error synchronizing virtual circuits`); }
|
|
2476
|
+
}
|
|
2369
2477
|
public async setCircuitStateAsync(id: number, val: boolean, ignoreDelays?: boolean): Promise<ICircuitState> {
|
|
2370
2478
|
sys.board.suspendStatus(true);
|
|
2371
2479
|
try {
|
|
@@ -2443,60 +2551,50 @@ export class CircuitCommands extends BoardCommands {
|
|
|
2443
2551
|
await sys.board.circuits.setCircuitStateAsync(arrCircs[i].id, false);
|
|
2444
2552
|
//proms.push(ncp.circuits.sendOnOffSequenceAsync(arrCircs[i].id, cmd.sequence));
|
|
2445
2553
|
}
|
|
2446
|
-
await
|
|
2554
|
+
await setTimeout(10000);
|
|
2447
2555
|
for (let i = 0; i < arrCircs.length; i++) {
|
|
2448
2556
|
await sys.board.circuits.setCircuitStateAsync(arrCircs[i].id, true);
|
|
2449
2557
|
//proms.push(ncp.circuits.sendOnOffSequenceAsync(arrCircs[i].id, cmd.sequence));
|
|
2450
2558
|
}
|
|
2451
|
-
|
|
2452
|
-
//if (proms.length > 0) {
|
|
2453
|
-
// //await Promise.all(proms);
|
|
2454
|
-
// // Let it simmer for 6 seconds then turn it off and back on.
|
|
2455
|
-
// proms.length = 0;
|
|
2456
|
-
// for (let i = 0; i < arrCircs.length; i++) {
|
|
2457
|
-
// proms.push(sys.board.circuits.setCircuitStateAsync(arrCircs[i].id, false));
|
|
2458
|
-
// }
|
|
2459
|
-
// await Promise.all(proms);
|
|
2460
|
-
// // Let it be off for 3 seconds then turn it back on.
|
|
2461
|
-
// await utils.sleep(10000);
|
|
2462
|
-
// proms.length = 0;
|
|
2463
|
-
// for (let i = 0; i < arrCircs.length; i++) {
|
|
2464
|
-
// proms.push(sys.board.circuits.setCircuitStateAsync(arrCircs[i].id, true));
|
|
2465
|
-
// }
|
|
2466
|
-
// await Promise.all(proms);
|
|
2467
|
-
//}
|
|
2468
2559
|
sgrp.action = 0;
|
|
2469
2560
|
sgrp.emitEquipmentChange();
|
|
2470
2561
|
return state.lightGroups.getItemById(id);
|
|
2471
2562
|
}
|
|
2472
2563
|
catch (err) { return Promise.reject(`Error runLightGroupCommandAsync ${err.message}`); }
|
|
2473
2564
|
}
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2565
|
+
public async runLightCommandAsync(obj: any): Promise<ICircuitState> {
|
|
2566
|
+
// Do all our validation.
|
|
2567
|
+
try {
|
|
2568
|
+
let id = parseInt(obj.id, 10);
|
|
2569
|
+
let cmd = typeof obj.command !== 'undefined' ? sys.board.valueMaps.lightCommands.findItem(obj.command) : { val: 0, name: 'undefined' };
|
|
2570
|
+
if (cmd.val === 0) return Promise.reject(new InvalidOperationError(`Light command ${cmd.name} does not exist`, 'runLightCommandAsync'));
|
|
2571
|
+
if (isNaN(id)) return Promise.reject(new InvalidOperationError(`Light ${id} does not exist`, 'runLightCommandAsync'));
|
|
2572
|
+
let circ = sys.circuits.getItemById(id);
|
|
2573
|
+
if (!circ.isActive) return Promise.reject(new InvalidOperationError(`Light circuit #${id} is not active`, 'runLightCommandAsync'));
|
|
2574
|
+
let type = sys.board.valueMaps.circuitFunctions.transform(circ.type);
|
|
2575
|
+
if (!type.isLight) return Promise.reject(new InvalidOperationError(`Circuit #${id} is not a light`, 'runLightCommandAsync'));
|
|
2576
|
+
let nop = sys.board.valueMaps.circuitActions.getValue(cmd.name);
|
|
2577
|
+
let slight = state.circuits.getItemById(circ.id);
|
|
2578
|
+
slight.action = nop;
|
|
2579
|
+
console.log(nop);
|
|
2580
|
+
slight.emitEquipmentChange();
|
|
2581
|
+
await ncp.circuits.sendOnOffSequenceAsync(circ.id, cmd.sequence);
|
|
2582
|
+
if (cmd.sequence.length > 0) {
|
|
2583
|
+
await sys.board.circuits.setCircuitStateAsync(circ.id, cmd.sequence[cmd.sequence.length - 1].isOn);
|
|
2584
|
+
}
|
|
2585
|
+
if (typeof cmd.endingTheme !== 'undefined') {
|
|
2586
|
+
let thm = sys.board.valueMaps.lightThemes.findItem(cmd.endingTheme);
|
|
2587
|
+
if (typeof thm !== 'undefined') slight.lightingTheme = circ.lightingTheme = thm.val;
|
|
2588
|
+
}
|
|
2589
|
+
//await setTimeout(7000);
|
|
2590
|
+
//await sys.board.circuits.setCircuitStateAsync(circ.id, false);
|
|
2591
|
+
//await sys.board.circuits.setCircuitStateAsync(circ.id, true);
|
|
2592
|
+
slight.action = 0;
|
|
2593
|
+
slight.emitEquipmentChange();
|
|
2594
|
+
return slight;
|
|
2595
|
+
}
|
|
2596
|
+
catch (err) { return Promise.reject(`Error runLightCommandAsync ${err.message}`); }
|
|
2497
2597
|
}
|
|
2498
|
-
catch (err) { return Promise.reject(`Error runLightCommandAsync ${err.message}`); }
|
|
2499
|
-
}
|
|
2500
2598
|
public async setLightThemeAsync(id: number, theme: number): Promise<ICircuitState> {
|
|
2501
2599
|
let cstate = state.circuits.getItemById(id);
|
|
2502
2600
|
let circ = sys.circuits.getItemById(id);
|
|
@@ -2507,7 +2605,7 @@ export class CircuitCommands extends BoardCommands {
|
|
|
2507
2605
|
try {
|
|
2508
2606
|
if (typeof thm !== 'undefined' && typeof thm.sequence !== 'undefined' && circ.master === 1) {
|
|
2509
2607
|
await sys.board.circuits.setCircuitStateAsync(id, true);
|
|
2510
|
-
await ncp.circuits.
|
|
2608
|
+
await ncp.circuits.setLightThemeAsync(id, thm);
|
|
2511
2609
|
}
|
|
2512
2610
|
cstate.lightingTheme = theme;
|
|
2513
2611
|
return cstate;
|
|
@@ -2595,63 +2693,64 @@ export class CircuitCommands extends BoardCommands {
|
|
|
2595
2693
|
}
|
|
2596
2694
|
public getLightThemes(type?: number) { return sys.board.valueMaps.lightThemes.toArray(); }
|
|
2597
2695
|
public getCircuitFunctions() {
|
|
2598
|
-
let cf = sys.board.valueMaps.circuitFunctions.toArray();
|
|
2599
|
-
if (!sys.equipment.shared) cf = cf.filter(x => { return x.name !== 'spillway' && x.name !== 'spadrain' });
|
|
2600
|
-
return cf;
|
|
2601
|
-
}
|
|
2602
|
-
public getCircuitNames() { return [...sys.board.valueMaps.circuitNames.toArray(), ...sys.board.valueMaps.customNames.toArray()]; }
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2696
|
+
let cf = sys.board.valueMaps.circuitFunctions.toArray();
|
|
2697
|
+
if (!sys.equipment.shared) cf = cf.filter(x => { return x.name !== 'spillway' && x.name !== 'spadrain' });
|
|
2698
|
+
return cf;
|
|
2699
|
+
}
|
|
2700
|
+
public getCircuitNames() { return [...sys.board.valueMaps.circuitNames.toArray(), ...sys.board.valueMaps.customNames.toArray()]; }
|
|
2701
|
+
public async setCircuitAsync(data: any, send: boolean = true): Promise<ICircuit> {
|
|
2702
|
+
try {
|
|
2703
|
+
let id = parseInt(data.id, 10);
|
|
2704
|
+
if (id <= 0 || typeof data.id === 'undefined') {
|
|
2705
|
+
// We are adding a new circuit. If we are operating as a nixie controller then we need to start this
|
|
2706
|
+
// circuit outside the range of circuits that can be defined on the panel. For any of the non-OCP controllers
|
|
2707
|
+
// these are added within the range of the circuits starting with 1. For all others these are added with an id > 255.
|
|
2708
|
+
switch (state.equipment.controllerType) {
|
|
2709
|
+
case 'intellicenter':
|
|
2710
|
+
case 'intellitouch':
|
|
2711
|
+
case 'easytouch':
|
|
2712
|
+
case 'suntouch':
|
|
2713
|
+
id = sys.circuits.getNextEquipmentId(new EquipmentIdRange(255, 300));
|
|
2714
|
+
break;
|
|
2715
|
+
default:
|
|
2716
|
+
id = sys.circuits.getNextEquipmentId(sys.board.equipmentIds.circuits, [1, 6]);
|
|
2717
|
+
break;
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError(`Invalid circuit id: ${data.id}`, data.id, 'Circuit'));
|
|
2721
|
+
//if (!sys.board.equipmentIds.circuits.isInRange(id)) return Promise.reject(new InvalidEquipmentIdError(`Circuit id is out of range: ${id}`, data.id, 'Circuit'));;
|
|
2722
|
+
if (typeof data.id !== 'undefined') {
|
|
2723
|
+
let circuit = sys.circuits.getItemById(id, true);
|
|
2724
|
+
let scircuit = state.circuits.getItemById(id, true);
|
|
2725
|
+
scircuit.isActive = circuit.isActive = true;
|
|
2726
|
+
circuit.master = 1;
|
|
2727
|
+
scircuit.isOn = false;
|
|
2728
|
+
if (data.name) circuit.name = scircuit.name = data.name;
|
|
2729
|
+
else if (!circuit.name && !data.name) circuit.name = scircuit.name = `circuit${data.id}`;
|
|
2730
|
+
if (typeof data.type !== 'undefined' || typeof circuit.type === 'undefined') {
|
|
2731
|
+
circuit.type = scircuit.type = parseInt(data.type, 10) || 0;
|
|
2732
|
+
}
|
|
2733
|
+
if (id === 6) circuit.type = sys.board.valueMaps.circuitFunctions.getValue('pool');
|
|
2734
|
+
if (id === 1 && sys.equipment.shared) circuit.type = sys.board.valueMaps.circuitFunctions.getValue('spa');
|
|
2735
|
+
if (typeof data.freeze !== 'undefined' || typeof circuit.freeze === 'undefined') circuit.freeze = utils.makeBool(data.freeze) || false;
|
|
2736
|
+
if (typeof data.showInFeatures !== 'undefined' || typeof data.showInFeatures === 'undefined') circuit.showInFeatures = scircuit.showInFeatures = utils.makeBool(data.showInFeatures);
|
|
2737
|
+
if (typeof data.dontStop !== 'undefined' && utils.makeBool(data.dontStop) === true) data.eggTimer = 1440;
|
|
2738
|
+
if (typeof data.eggTimer !== 'undefined' || typeof circuit.eggTimer === 'undefined') circuit.eggTimer = parseInt(data.eggTimer, 10) || 0;
|
|
2739
|
+
if (typeof data.connectionId !== 'undefined') circuit.connectionId = data.connectionId;
|
|
2740
|
+
if (typeof data.deviceBinding !== 'undefined') circuit.deviceBinding = data.deviceBinding;
|
|
2741
|
+
if (typeof data.showInFeatures !== 'undefined') scircuit.showInFeatures = circuit.showInFeatures = utils.makeBool(data.showInFeatures);
|
|
2742
|
+
circuit.dontStop = circuit.eggTimer === 1440;
|
|
2644
2743
|
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2744
|
+
sys.emitEquipmentChange();
|
|
2745
|
+
state.emitEquipmentChanges();
|
|
2746
|
+
if (circuit.master === 1) await ncp.circuits.setCircuitAsync(circuit, data);
|
|
2747
|
+
return Promise.resolve(circuit);
|
|
2748
|
+
}
|
|
2749
|
+
else
|
|
2750
|
+
return Promise.reject(new Error('Circuit id has not been defined'));
|
|
2751
|
+
}
|
|
2752
|
+
catch (err) { logger.error(`setCircuitAsync error with ${data}. ${err}`); return Promise.reject(err); }
|
|
2652
2753
|
}
|
|
2653
|
-
catch (err) { logger.error(`setCircuitAsync error with ${data}. ${err}`); return Promise.reject(err); }
|
|
2654
|
-
}
|
|
2655
2754
|
public async setCircuitGroupAsync(obj: any): Promise<CircuitGroup> {
|
|
2656
2755
|
let group: CircuitGroup = null;
|
|
2657
2756
|
let sgroup: CircuitGroupState = null;
|
|
@@ -2714,7 +2813,7 @@ export class CircuitCommands extends BoardCommands {
|
|
|
2714
2813
|
});
|
|
2715
2814
|
|
|
2716
2815
|
}
|
|
2717
|
-
public async setLightGroupAsync(obj: any): Promise<LightGroup> {
|
|
2816
|
+
public async setLightGroupAsync(obj: any, send: boolean = true): Promise<LightGroup> {
|
|
2718
2817
|
let group: LightGroup = null;
|
|
2719
2818
|
let id = typeof obj.id !== 'undefined' ? parseInt(obj.id, 10) : -1;
|
|
2720
2819
|
if (id <= 0) {
|
|
@@ -2885,7 +2984,7 @@ export class CircuitCommands extends BoardCommands {
|
|
|
2885
2984
|
if (nop > 0) {
|
|
2886
2985
|
sgroup.action = nop;
|
|
2887
2986
|
sgroup.emitEquipmentChange();
|
|
2888
|
-
await
|
|
2987
|
+
await setTimeout(10000);
|
|
2889
2988
|
sgroup.action = 0;
|
|
2890
2989
|
state.emitAllEquipmentChanges();
|
|
2891
2990
|
}
|
|
@@ -3112,32 +3211,33 @@ export class FeatureCommands extends BoardCommands {
|
|
|
3112
3211
|
} catch (err) { logger.error(`Error validating features for restore: ${err.message}`); }
|
|
3113
3212
|
}
|
|
3114
3213
|
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3214
|
+
public async setFeatureAsync(obj: any): Promise<Feature> {
|
|
3215
|
+
let id = parseInt(obj.id, 10);
|
|
3216
|
+
if (id <= 0 || isNaN(id)) {
|
|
3217
|
+
id = sys.features.getNextEquipmentId(sys.board.equipmentIds.features);
|
|
3218
|
+
}
|
|
3219
|
+
if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError(`Invalid feature id: ${obj.id}`, obj.id, 'Feature'));
|
|
3220
|
+
if (!sys.board.equipmentIds.features.isInRange(id)) return Promise.reject(new InvalidEquipmentIdError(`Feature id out of range: ${id}: ${sys.board.equipmentIds.features.start} to ${sys.board.equipmentIds.features.end}`, obj.id, 'Feature'));
|
|
3221
|
+
let feature = sys.features.getItemById(id, true);
|
|
3222
|
+
let sfeature = state.features.getItemById(id, true);
|
|
3223
|
+
feature.isActive = true;
|
|
3224
|
+
sfeature.isOn = false;
|
|
3225
|
+
if (obj.nameId) {
|
|
3226
|
+
feature.nameId = sfeature.nameId = obj.nameId;
|
|
3227
|
+
feature.name = sfeature.name = sys.board.valueMaps.circuitNames.get(obj.nameId);
|
|
3228
|
+
}
|
|
3229
|
+
else if (obj.name) feature.name = sfeature.name = obj.name;
|
|
3230
|
+
else if (!feature.name && !obj.name) feature.name = sfeature.name = `feature${obj.id}`;
|
|
3231
|
+
if (typeof obj.type !== 'undefined') feature.type = sfeature.type = parseInt(obj.type, 10);
|
|
3232
|
+
else if (!feature.type && typeof obj.type !== 'undefined') feature.type = sfeature.type = 0;
|
|
3233
|
+
if (typeof obj.freeze !== 'undefined') feature.freeze = utils.makeBool(obj.freeze);
|
|
3234
|
+
if (typeof obj.showInFeatures !== 'undefined') feature.showInFeatures = sfeature.showInFeatures = utils.makeBool(obj.showInFeatures);
|
|
3235
|
+
sfeature.showInFeatures = feature.showInFeatures;
|
|
3236
|
+
if (typeof obj.dontStop !== 'undefined' && utils.makeBool(obj.dontStop) === true) obj.eggTimer = 1440;
|
|
3237
|
+
if (typeof obj.eggTimer !== 'undefined') feature.eggTimer = parseInt(obj.eggTimer, 10);
|
|
3238
|
+
feature.dontStop = feature.eggTimer === 1440;
|
|
3239
|
+
return new Promise<Feature>((resolve, reject) => { resolve(feature); });
|
|
3129
3240
|
}
|
|
3130
|
-
else if (obj.name) feature.name = sfeature.name = obj.name;
|
|
3131
|
-
else if (!feature.name && !obj.name) feature.name = sfeature.name = `feature${obj.id}`;
|
|
3132
|
-
if (typeof obj.type !== 'undefined') feature.type = sfeature.type = parseInt(obj.type, 10);
|
|
3133
|
-
else if (!feature.type && typeof obj.type !== 'undefined') feature.type = sfeature.type = 0;
|
|
3134
|
-
if (typeof obj.freeze !== 'undefined') feature.freeze = utils.makeBool(obj.freeze);
|
|
3135
|
-
if (typeof obj.showInFeatures !== 'undefined') feature.showInFeatures = sfeature.showInFeatures = utils.makeBool(obj.showInFeatures);
|
|
3136
|
-
if (typeof obj.dontStop !== 'undefined' && utils.makeBool(obj.dontStop) === true) obj.eggTimer = 1440;
|
|
3137
|
-
if (typeof obj.eggTimer !== 'undefined') feature.eggTimer = parseInt(obj.eggTimer, 10);
|
|
3138
|
-
feature.dontStop = feature.eggTimer === 1440;
|
|
3139
|
-
return new Promise<Feature>((resolve, reject) => { resolve(feature); });
|
|
3140
|
-
}
|
|
3141
3241
|
public async deleteFeatureAsync(obj: any): Promise<Feature> {
|
|
3142
3242
|
let id = parseInt(obj.id, 10);
|
|
3143
3243
|
if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError(`Invalid feature id: ${obj.id}`, obj.id, 'Feature'));
|
|
@@ -3278,7 +3378,7 @@ export class ChlorinatorCommands extends BoardCommands {
|
|
|
3278
3378
|
} catch (err) { logger.error(`Error validating chlorinators for restore: ${err.message}`); }
|
|
3279
3379
|
}
|
|
3280
3380
|
|
|
3281
|
-
public async setChlorAsync(obj: any): Promise<ChlorinatorState> {
|
|
3381
|
+
public async setChlorAsync(obj: any, send: boolean = true): Promise<ChlorinatorState> {
|
|
3282
3382
|
try {
|
|
3283
3383
|
let id = parseInt(obj.id, 10);
|
|
3284
3384
|
let chlor: Chlorinator;
|
|
@@ -3338,6 +3438,7 @@ export class ChlorinatorCommands extends BoardCommands {
|
|
|
3338
3438
|
schlor.model = chlor.model = typeof obj.model !== 'undefined' ? sys.board.valueMaps.chlorinatorModel.encode(obj.model) : chlor.model;
|
|
3339
3439
|
chlor.type = schlor.type = typeof obj.type !== 'undefined' ? sys.board.valueMaps.chlorinatorType.encode(obj.type) : chlor.type || 0;
|
|
3340
3440
|
chlor.body = schlor.body = body.val;
|
|
3441
|
+
chlor.address = typeof obj.address !== 'undefined' ? parseInt(obj.address,10) : 80;
|
|
3341
3442
|
schlor.poolSetpoint = chlor.poolSetpoint = poolSetpoint;
|
|
3342
3443
|
schlor.spaSetpoint = chlor.spaSetpoint = spaSetpoint;
|
|
3343
3444
|
chlor.ignoreSaltReading = typeof obj.ignoreSaltReading !== 'undefined' ? utils.makeBool(obj.ignoreSaltReading) : utils.makeBool(chlor.ignoreSaltReading);
|
|
@@ -3506,16 +3607,16 @@ export class ScheduleCommands extends BoardCommands {
|
|
|
3506
3607
|
// if scheduleDays includes today
|
|
3507
3608
|
if (days.includes(state.time.toDate().getDay())) {
|
|
3508
3609
|
if (sched.changeHeatSetpoint && (sched.heatSource as any).val !== sys.board.valueMaps.heatSources.getValue('off') && sched.heatSetpoint > 0 && sched.heatSetpoint !== tbody.setPoint) {
|
|
3509
|
-
|
|
3610
|
+
setTimeoutSync(() => sys.board.bodies.setHeatSetpointAsync(cbody, sched.heatSetpoint), 100);
|
|
3510
3611
|
}
|
|
3511
3612
|
if ((sched.heatSource as any).val !== sys.board.valueMaps.heatSources.getValue('nochange') && sched.heatSource !== tbody.heatMode) {
|
|
3512
|
-
|
|
3613
|
+
setTimeoutSync(() => sys.board.bodies.setHeatModeAsync(cbody, sys.board.valueMaps.heatModes.getValue((sched.heatSource as any).name)), 100);
|
|
3513
3614
|
}
|
|
3514
3615
|
}
|
|
3515
3616
|
}
|
|
3516
3617
|
};
|
|
3517
3618
|
}
|
|
3518
|
-
public async setScheduleAsync(data: any): Promise<Schedule> {
|
|
3619
|
+
public async setScheduleAsync(data: any, send: boolean = true): Promise<Schedule> {
|
|
3519
3620
|
let id = typeof data.id === 'undefined' ? -1 : parseInt(data.id, 10);
|
|
3520
3621
|
if (id <= 0) id = sys.schedules.getNextEquipmentId(new EquipmentIdRange(1, sys.equipment.maxSchedules));
|
|
3521
3622
|
if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError(`Invalid schedule id: ${data.id}`, data.id, 'Schedule'));
|
|
@@ -3626,7 +3727,7 @@ export class ScheduleCommands extends BoardCommands {
|
|
|
3626
3727
|
}
|
|
3627
3728
|
} catch (err) { logger.error(`Error synchronizing schedule states`); }
|
|
3628
3729
|
}
|
|
3629
|
-
public async setEggTimerAsync(data?: any): Promise<EggTimer> { return Promise.resolve(sys.eggTimers.getItemByIndex(1)); }
|
|
3730
|
+
public async setEggTimerAsync(data?: any, send: boolean = true): Promise<EggTimer> { return Promise.resolve(sys.eggTimers.getItemByIndex(1)); }
|
|
3630
3731
|
public async deleteEggTimerAsync(data?: any): Promise<EggTimer> { return Promise.resolve(sys.eggTimers.getItemByIndex(1)); }
|
|
3631
3732
|
public includesCircuit(sched: Schedule, circuit: number) {
|
|
3632
3733
|
let bIncludes = false;
|
|
@@ -3682,7 +3783,7 @@ export class ScheduleCommands extends BoardCommands {
|
|
|
3682
3783
|
if (state.circuitGroups.getInterfaceById(circuit).manualPriorityActive) return true;
|
|
3683
3784
|
if (grp && grp.isActive) cgc = grp.circuits.toArray();
|
|
3684
3785
|
for (let i = 0; i < cgc.length; i++) {
|
|
3685
|
-
let c = state.circuits.getInterfaceById(cgc[i].
|
|
3786
|
+
let c = state.circuits.getInterfaceById(cgc[i].circuit);
|
|
3686
3787
|
if (c.manualPriorityActive) return true;
|
|
3687
3788
|
}
|
|
3688
3789
|
return false;
|
|
@@ -3702,7 +3803,7 @@ export class ScheduleCommands extends BoardCommands {
|
|
|
3702
3803
|
|
|
3703
3804
|
if (schedule.isActive === false) return false;
|
|
3704
3805
|
if (schedule.disabled) return false;
|
|
3705
|
-
if (!sys.general.options.manualPriority) return false;
|
|
3806
|
+
//if (!sys.general.options.manualPriority) return false; //if we override a circuit to be mOP, this will not be true
|
|
3706
3807
|
|
|
3707
3808
|
let currGrp: ICircuitGroup;
|
|
3708
3809
|
let currSchedGrpCircs = [];
|
|
@@ -3743,7 +3844,7 @@ export class ScheduleCommands extends BoardCommands {
|
|
|
3743
3844
|
let cgc = grp.circuits.getItemByIndex(j);
|
|
3744
3845
|
let scgc = state.circuits.getInterfaceById(cgc.circuit);
|
|
3745
3846
|
// if the circuit id's match and mOP is active, we delay
|
|
3746
|
-
if (scgc.id === schedule.circuit &&
|
|
3847
|
+
if (scgc.id === schedule.circuit && manualPriorityByProxy) return true;
|
|
3747
3848
|
// check all the other cgc against this cgc
|
|
3748
3849
|
// note: circuit/light groups cannot be part of a group themselves
|
|
3749
3850
|
for (let k = 0; k < currSchedGrpCircs.length; k++) {
|
|
@@ -3788,569 +3889,607 @@ export class ScheduleCommands extends BoardCommands {
|
|
|
3788
3889
|
// if we make it this far, nothing is impacting us
|
|
3789
3890
|
return false;
|
|
3790
3891
|
}
|
|
3892
|
+
public async updateSunriseSunsetAsync():Promise<boolean> {return Promise.resolve(false);};
|
|
3791
3893
|
}
|
|
3792
3894
|
export class HeaterCommands extends BoardCommands {
|
|
3793
|
-
|
|
3794
|
-
try {
|
|
3795
|
-
// First delete the heaters that should be removed.
|
|
3796
|
-
for (let i = 0; i < ctx.heaters.remove.length; i++) {
|
|
3797
|
-
let h = ctx.heaters.remove[i];
|
|
3798
|
-
try {
|
|
3799
|
-
await sys.board.heaters.deleteHeaterAsync(h);
|
|
3800
|
-
res.addModuleSuccess('heater', `Remove: ${h.id}-${h.name}`);
|
|
3801
|
-
} catch (err) { res.addModuleError('heater', `Remove: ${h.id}-${h.name}: ${err.message}`); }
|
|
3802
|
-
}
|
|
3803
|
-
for (let i = 0; i < ctx.heaters.update.length; i++) {
|
|
3804
|
-
let h = ctx.heaters.update[i];
|
|
3805
|
-
try {
|
|
3806
|
-
await sys.board.heaters.setHeaterAsync(h);
|
|
3807
|
-
res.addModuleSuccess('heater', `Update: ${h.id}-${h.name}`);
|
|
3808
|
-
} catch (err) { res.addModuleError('heater', `Update: ${h.id}-${h.name}: ${err.message}`); }
|
|
3809
|
-
}
|
|
3810
|
-
for (let i = 0; i < ctx.heaters.add.length; i++) {
|
|
3811
|
-
let h = ctx.heaters.add[i];
|
|
3895
|
+
public async restore(rest: { poolConfig: any, poolState: any }, ctx: any, res: RestoreResults): Promise<boolean> {
|
|
3812
3896
|
try {
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
} catch (err) { logger.error(`Error validating heaters for restore: ${err.message}`); }
|
|
3841
|
-
}
|
|
3842
|
-
public getHeatersByCircuitId(circuitId: number): Heater[] {
|
|
3843
|
-
let heaters: Heater[] = [];
|
|
3844
|
-
let bodyId = circuitId === 6 ? 1 : circuitId === 1 ? 2 : 0;
|
|
3845
|
-
if (bodyId > 0) {
|
|
3846
|
-
for (let i = 0; i < sys.heaters.length; i++) {
|
|
3847
|
-
let heater = sys.heaters.getItemByIndex(i);
|
|
3848
|
-
if (!heater.isActive) continue;
|
|
3849
|
-
if (bodyId === heater.body || sys.equipment.shared && heater.body === 32) heaters.push(heater);
|
|
3850
|
-
}
|
|
3897
|
+
// First delete the heaters that should be removed.
|
|
3898
|
+
for (let i = 0; i < ctx.heaters.remove.length; i++) {
|
|
3899
|
+
let h = ctx.heaters.remove[i];
|
|
3900
|
+
try {
|
|
3901
|
+
await sys.board.heaters.deleteHeaterAsync(h);
|
|
3902
|
+
res.addModuleSuccess('heater', `Remove: ${h.id}-${h.name}`);
|
|
3903
|
+
} catch (err) { res.addModuleError('heater', `Remove: ${h.id}-${h.name}: ${err.message}`); }
|
|
3904
|
+
}
|
|
3905
|
+
for (let i = 0; i < ctx.heaters.update.length; i++) {
|
|
3906
|
+
let h = ctx.heaters.update[i];
|
|
3907
|
+
try {
|
|
3908
|
+
await sys.board.heaters.setHeaterAsync(h);
|
|
3909
|
+
res.addModuleSuccess('heater', `Update: ${h.id}-${h.name}`);
|
|
3910
|
+
} catch (err) { res.addModuleError('heater', `Update: ${h.id}-${h.name}: ${err.message}`); }
|
|
3911
|
+
}
|
|
3912
|
+
for (let i = 0; i < ctx.heaters.add.length; i++) {
|
|
3913
|
+
let h = ctx.heaters.add[i];
|
|
3914
|
+
try {
|
|
3915
|
+
// pull a little trick to first add the data then perform the update. This way we won't get a new id or
|
|
3916
|
+
// it won't error out.
|
|
3917
|
+
sys.heaters.getItemById(h.id, true);
|
|
3918
|
+
await sys.board.heaters.setHeaterAsync(h);
|
|
3919
|
+
res.addModuleSuccess('heater', `Add: ${h.id}-${h.name}`);
|
|
3920
|
+
} catch (err) { res.addModuleError('heater', `Add: ${h.id}-${h.name}: ${err.message}`); }
|
|
3921
|
+
}
|
|
3922
|
+
return true;
|
|
3923
|
+
} catch (err) { logger.error(`Error restoring heaters: ${err.message}`); res.addModuleError('system', `Error restoring heaters: ${err.message}`); return false; }
|
|
3851
3924
|
}
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
}
|
|
3925
|
+
public async validateRestore(rest: { poolConfig: any, poolState: any }): Promise<{ errors: any, warnings: any, add: any, update: any, remove: any }> {
|
|
3926
|
+
try {
|
|
3927
|
+
let ctx = { errors: [], warnings: [], add: [], update: [], remove: [] };
|
|
3928
|
+
// Look at heaters.
|
|
3929
|
+
let cfg = rest.poolConfig;
|
|
3930
|
+
for (let i = 0; i < cfg.heaters.length; i++) {
|
|
3931
|
+
let r = cfg.heaters[i];
|
|
3932
|
+
let c = sys.heaters.find(elem => r.id === elem.id);
|
|
3933
|
+
if (typeof c === 'undefined') ctx.add.push(r);
|
|
3934
|
+
else if (JSON.stringify(c.get()) !== JSON.stringify(r)) ctx.update.push(r);
|
|
3935
|
+
}
|
|
3936
|
+
for (let i = 0; i < sys.heaters.length; i++) {
|
|
3937
|
+
let c = sys.heaters.getItemByIndex(i);
|
|
3938
|
+
let r = cfg.heaters.find(elem => elem.id == c.id);
|
|
3939
|
+
if (typeof r === 'undefined') ctx.remove.push(c.get(true));
|
|
3940
|
+
}
|
|
3941
|
+
return ctx;
|
|
3942
|
+
} catch (err) { logger.error(`Error validating heaters for restore: ${err.message}`); }
|
|
3871
3943
|
}
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
if (typeof type !== 'undefined') {
|
|
3882
|
-
switch (type.name) {
|
|
3883
|
-
case 'solar':
|
|
3884
|
-
return true;
|
|
3944
|
+
public getHeatersByCircuitId(circuitId: number): Heater[] {
|
|
3945
|
+
let heaters: Heater[] = [];
|
|
3946
|
+
let bodyId = circuitId === 6 ? 1 : circuitId === 1 ? 2 : 0;
|
|
3947
|
+
if (bodyId > 0) {
|
|
3948
|
+
for (let i = 0; i < sys.heaters.length; i++) {
|
|
3949
|
+
let heater = sys.heaters.getItemByIndex(i);
|
|
3950
|
+
if (!heater.isActive) continue;
|
|
3951
|
+
if (bodyId === heater.body || sys.equipment.shared && heater.body === 32) heaters.push(heater);
|
|
3952
|
+
}
|
|
3885
3953
|
}
|
|
3886
|
-
|
|
3954
|
+
return heaters;
|
|
3887
3955
|
}
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3956
|
+
public getInstalledHeaterTypes(body?: number): any {
|
|
3957
|
+
let heaters = sys.heaters.get();
|
|
3958
|
+
let types = sys.board.valueMaps.heaterTypes.toArray();
|
|
3959
|
+
let inst = { total: 0 };
|
|
3960
|
+
for (let i = 0; i < types.length; i++) if (types[i].name !== 'none') inst[types[i].name] = 0;
|
|
3961
|
+
for (let i = 0; i < heaters.length; i++) {
|
|
3962
|
+
let heater = heaters[i];
|
|
3963
|
+
if (typeof body !== 'undefined' && heater.body !== 'undefined') {
|
|
3964
|
+
if ((heater.body !== 32 && body !== heater.body + 1) || (heater.body === 32 && body > 2)) continue;
|
|
3965
|
+
}
|
|
3966
|
+
let type = types.find(elem => elem.val === heater.type);
|
|
3967
|
+
if (typeof type !== 'undefined') {
|
|
3968
|
+
if (inst[type.name] === 'undefined') inst[type.name] = 0;
|
|
3969
|
+
inst[type.name] = inst[type.name] + 1;
|
|
3970
|
+
if (heater.coolingEnabled === true && type.hasCoolSetpoint === true) inst['hasCoolSetpoint'] = true;
|
|
3971
|
+
inst.total++;
|
|
3972
|
+
}
|
|
3900
3973
|
}
|
|
3901
|
-
|
|
3902
|
-
}
|
|
3903
|
-
}
|
|
3904
|
-
public setHeater(heater: Heater, obj?: any) {
|
|
3905
|
-
if (typeof obj !== undefined) {
|
|
3906
|
-
for (var s in obj)
|
|
3907
|
-
heater[s] = obj[s];
|
|
3974
|
+
return inst;
|
|
3908
3975
|
}
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
if (typeof obj !== undefined) {
|
|
3923
|
-
for (var s in obj) {
|
|
3924
|
-
if (s === 'id') continue;
|
|
3925
|
-
heater[s] = obj[s];
|
|
3976
|
+
public isSolarInstalled(body?: number): boolean {
|
|
3977
|
+
let heaters = sys.heaters.get();
|
|
3978
|
+
let types = sys.board.valueMaps.heaterTypes.toArray();
|
|
3979
|
+
for (let i = 0; i < heaters.length; i++) {
|
|
3980
|
+
let heater = heaters[i];
|
|
3981
|
+
if (typeof body !== 'undefined' && body !== heater.body) continue;
|
|
3982
|
+
let type = types.find(elem => elem.val === heater.type);
|
|
3983
|
+
if (typeof type !== 'undefined') {
|
|
3984
|
+
switch (type.name) {
|
|
3985
|
+
case 'solar':
|
|
3986
|
+
return true;
|
|
3987
|
+
}
|
|
3988
|
+
}
|
|
3926
3989
|
}
|
|
3927
|
-
}
|
|
3928
|
-
let hstate = state.heaters.getItemById(id, true);
|
|
3929
|
-
//hstate.isVirtual = heater.isVirtual = true;
|
|
3930
|
-
hstate.name = heater.name;
|
|
3931
|
-
hstate.type = heater.type;
|
|
3932
|
-
heater.master = 1;
|
|
3933
|
-
if (heater.master === 1) await ncp.heaters.setHeaterAsync(heater, obj);
|
|
3934
|
-
await sys.board.heaters.updateHeaterServices();
|
|
3935
|
-
await sys.board.heaters.syncHeaterStates();
|
|
3936
|
-
return heater;
|
|
3937
|
-
} catch (err) { return Promise.reject(new Error(`Error setting heater configuration: ${err}`)); }
|
|
3938
|
-
}
|
|
3939
|
-
public async deleteHeaterAsync(obj: any): Promise<Heater> {
|
|
3940
|
-
try {
|
|
3941
|
-
let id = parseInt(obj.id, 10);
|
|
3942
|
-
if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError('Cannot delete. Heater Id is not valid.', obj.id, 'Heater'));
|
|
3943
|
-
let heater = sys.heaters.getItemById(id);
|
|
3944
|
-
heater.isActive = false;
|
|
3945
|
-
if (heater.master === 1) await ncp.heaters.deleteHeaterAsync(heater.id);
|
|
3946
|
-
sys.heaters.removeItemById(id);
|
|
3947
|
-
state.heaters.removeItemById(id);
|
|
3948
|
-
sys.board.heaters.updateHeaterServices();
|
|
3949
|
-
sys.board.heaters.syncHeaterStates();
|
|
3950
|
-
return heater;
|
|
3951
|
-
} catch (err) { return Promise.reject(`Error deleting heater: ${err.message}`) }
|
|
3952
|
-
}
|
|
3953
|
-
public updateHeaterServices() {
|
|
3954
|
-
let htypes = sys.board.heaters.getInstalledHeaterTypes();
|
|
3955
|
-
let solarInstalled = htypes.solar > 0;
|
|
3956
|
-
let heatPumpInstalled = htypes.heatpump > 0;
|
|
3957
|
-
let gasHeaterInstalled = htypes.gas > 0;
|
|
3958
|
-
|
|
3959
|
-
if (sys.heaters.length > 0) sys.board.valueMaps.heatSources = new byteValueMap([[0, { name: 'off', desc: 'Off' }]]);
|
|
3960
|
-
if (gasHeaterInstalled) sys.board.valueMaps.heatSources.set(3, { name: 'heater', desc: 'Heater' });
|
|
3961
|
-
if (solarInstalled && (gasHeaterInstalled || heatPumpInstalled)) sys.board.valueMaps.heatSources.merge([[5, { name: 'solar', desc: 'Solar Only' }], [21, { name: 'solarpref', desc: 'Solar Preferred' }]]);
|
|
3962
|
-
else if (solarInstalled) sys.board.valueMaps.heatSources.set(5, { name: 'solar', desc: 'Solar' });
|
|
3963
|
-
if (heatPumpInstalled && (gasHeaterInstalled || solarInstalled)) sys.board.valueMaps.heatSources.merge([[9, { name: 'heatpump', desc: 'Heatpump Only' }], [25, { name: 'heatpumppref', desc: 'Heat Pump Preferred' }]]);
|
|
3964
|
-
else if (heatPumpInstalled) sys.board.valueMaps.heatSources.set(9, { name: 'heatpump', desc: 'Heat Pump' });
|
|
3965
|
-
sys.board.valueMaps.heatSources.set(32, { name: 'nochange', desc: 'No Change' });
|
|
3966
|
-
|
|
3967
|
-
sys.board.valueMaps.heatModes = new byteValueMap([[0, { name: 'off', desc: 'Off' }]]);
|
|
3968
|
-
if (gasHeaterInstalled) sys.board.valueMaps.heatModes.set(3, { name: 'heater', desc: 'Heater' });
|
|
3969
|
-
if (solarInstalled && (gasHeaterInstalled || heatPumpInstalled)) sys.board.valueMaps.heatModes.merge([[5, { name: 'solar', desc: 'Solar Only' }], [21, { name: 'solarpref', desc: 'Solar Preferred' }]]);
|
|
3970
|
-
else if (solarInstalled) sys.board.valueMaps.heatModes.set(5, { name: 'solar', desc: 'Solar' });
|
|
3971
|
-
if (heatPumpInstalled && (gasHeaterInstalled || solarInstalled)) sys.board.valueMaps.heatModes.merge([[9, { name: 'heatpump', desc: 'Heatpump Only' }], [25, { name: 'heatpumppref', desc: 'Heat Pump Preferred' }]]);
|
|
3972
|
-
else if (heatPumpInstalled) sys.board.valueMaps.heatModes.set(9, { name: 'heatpump', desc: 'Heat Pump' });
|
|
3973
|
-
// Now set the body data.
|
|
3974
|
-
for (let i = 0; i < sys.bodies.length; i++) {
|
|
3975
|
-
let body = sys.bodies.getItemByIndex(i);
|
|
3976
|
-
let btemp = state.temps.bodies.getItemById(body.id, body.isActive !== false);
|
|
3977
|
-
let opts = sys.board.heaters.getInstalledHeaterTypes(body.id);
|
|
3978
|
-
btemp.heaterOptions = opts;
|
|
3979
|
-
}
|
|
3980
|
-
this.setActiveTempSensors();
|
|
3981
|
-
}
|
|
3982
|
-
public initTempSensors() {
|
|
3983
|
-
// Add in the potential sensors and delete the ones that shouldn't exist.
|
|
3984
|
-
let maxPairs = sys.equipment.maxBodies + (sys.equipment.shared ? -1 : 0);
|
|
3985
|
-
sys.equipment.tempSensors.getItemById('air', true, { id: 'air', isActive: true, calibration: 0 }).name = 'Air';
|
|
3986
|
-
sys.equipment.tempSensors.getItemById('water1', true, { id: 'water1', isActive: true, calibration: 0 }).name = maxPairs == 1 ? 'Water' : 'Body 1';
|
|
3987
|
-
sys.equipment.tempSensors.getItemById('solar1', true, { id: 'solar1', isActive: false, calibration: 0 }).name = maxPairs == 1 ? 'Solar' : 'Solar 1';
|
|
3988
|
-
if (maxPairs > 1) {
|
|
3989
|
-
sys.equipment.tempSensors.getItemById('water2', true, { id: 'water2', isActive: false, calibration: 0 }).name = 'Body 2';
|
|
3990
|
-
sys.equipment.tempSensors.getItemById('solar2', true, { id: 'solar2', isActive: false, calibration: 0 }).name = 'Solar 2';
|
|
3991
3990
|
}
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3991
|
+
public isHeatPumpInstalled(body?: number): boolean {
|
|
3992
|
+
let heaters = sys.heaters.get();
|
|
3993
|
+
let types = sys.board.valueMaps.heaterTypes.toArray();
|
|
3994
|
+
for (let i = 0; i < heaters.length; i++) {
|
|
3995
|
+
let heater = heaters[i];
|
|
3996
|
+
if (typeof body !== 'undefined' && body !== heater.body) continue;
|
|
3997
|
+
let type = types.find(elem => elem.val === heater.type);
|
|
3998
|
+
if (typeof type !== 'undefined') {
|
|
3999
|
+
switch (type.name) {
|
|
4000
|
+
case 'heatpump':
|
|
4001
|
+
return true;
|
|
4002
|
+
}
|
|
4003
|
+
}
|
|
4004
|
+
}
|
|
3999
4005
|
}
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4006
|
+
public setHeater(heater: Heater, obj?: any) {
|
|
4007
|
+
if (typeof obj !== undefined) {
|
|
4008
|
+
for (var s in obj)
|
|
4009
|
+
heater[s] = obj[s];
|
|
4010
|
+
}
|
|
4003
4011
|
}
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4012
|
+
public async setHeaterAsync(obj: any, send: boolean = true): Promise<Heater> {
|
|
4013
|
+
try {
|
|
4014
|
+
let id = typeof obj.id === 'undefined' ? -1 : parseInt(obj.id, 10);
|
|
4015
|
+
if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError('Heater Id is not valid.', obj.id, 'Heater'));
|
|
4016
|
+
else if (id < 256 && id > 0) return Promise.reject(new InvalidEquipmentIdError('Virtual Heaters controlled by njspc must have an Id > 256.', obj.id, 'Heater'));
|
|
4017
|
+
let heater: Heater;
|
|
4018
|
+
if (id <= 0) {
|
|
4019
|
+
// We are adding a heater. In this case all heaters are virtual.
|
|
4020
|
+
let vheaters = sys.heaters.filter(h => h.master === 1);
|
|
4021
|
+
id = vheaters.length + 256;
|
|
4022
|
+
}
|
|
4023
|
+
heater = sys.heaters.getItemById(id, true);
|
|
4024
|
+
if (typeof obj !== undefined) {
|
|
4025
|
+
for (var s in obj) {
|
|
4026
|
+
if (s === 'id') continue;
|
|
4027
|
+
heater[s] = obj[s];
|
|
4028
|
+
}
|
|
4029
|
+
}
|
|
4030
|
+
let hstate = state.heaters.getItemById(id, true);
|
|
4031
|
+
//hstate.isVirtual = heater.isVirtual = true;
|
|
4032
|
+
hstate.name = heater.name;
|
|
4033
|
+
hstate.type = heater.type;
|
|
4034
|
+
heater.master = 1;
|
|
4035
|
+
if (heater.master === 1) await ncp.heaters.setHeaterAsync(heater, obj);
|
|
4036
|
+
await sys.board.heaters.updateHeaterServices();
|
|
4037
|
+
await sys.board.heaters.syncHeaterStates();
|
|
4038
|
+
return heater;
|
|
4039
|
+
} catch (err) { return Promise.reject(new Error(`Error setting heater configuration: ${err}`)); }
|
|
4007
4040
|
}
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4041
|
+
public async deleteHeaterAsync(obj: any): Promise<Heater> {
|
|
4042
|
+
try {
|
|
4043
|
+
let id = parseInt(obj.id, 10);
|
|
4044
|
+
if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError('Cannot delete. Heater Id is not valid.', obj.id, 'Heater'));
|
|
4045
|
+
let heater = sys.heaters.getItemById(id);
|
|
4046
|
+
heater.isActive = false;
|
|
4047
|
+
if (heater.master === 1) await ncp.heaters.deleteHeaterAsync(heater.id);
|
|
4048
|
+
sys.heaters.removeItemById(id);
|
|
4049
|
+
state.heaters.removeItemById(id);
|
|
4050
|
+
sys.board.heaters.updateHeaterServices();
|
|
4051
|
+
sys.board.heaters.syncHeaterStates();
|
|
4052
|
+
return heater;
|
|
4053
|
+
} catch (err) { return Promise.reject(`Error deleting heater: ${err.message}`) }
|
|
4011
4054
|
}
|
|
4055
|
+
public updateHeaterServices() {
|
|
4056
|
+
let htypes = sys.board.heaters.getInstalledHeaterTypes();
|
|
4057
|
+
let solarInstalled = htypes.solar > 0;
|
|
4058
|
+
let heatPumpInstalled = htypes.heatpump > 0;
|
|
4059
|
+
let gasHeaterInstalled = htypes.gas > 0;
|
|
4012
4060
|
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
sensor.isActive = sys.equipment.shared ? sys.equipment.maxBodies > 3 : sys.equipment.maxBodies > 2;
|
|
4036
|
-
break;
|
|
4037
|
-
case 'water4':
|
|
4038
|
-
// It's a little weird but technically you should be able to install 3 expansions and a i10D personality
|
|
4039
|
-
// board. If this situation ever comes up we will see if it works. Whether it reports is another story
|
|
4040
|
-
// since the 2 message is short a byte for this.
|
|
4041
|
-
sensor.isActive = sys.equipment.shared ? sys.equipment.maxBodies > 4 : sys.equipment.maxBodies > 3;
|
|
4042
|
-
break;
|
|
4043
|
-
// Solar sensors are funny ducks. This is because they are for both heatpumps and solar and the equipment
|
|
4044
|
-
// can be installed on specific bodies. This will be true for heaters installed in expansion panels for *Touch, dual body systems,
|
|
4045
|
-
// and any IntelliCenter with more than one body. At some point simply implementing the multi-body functions for touch will make
|
|
4046
|
-
// this all work. This will only be with i10D or expansion panels.
|
|
4047
|
-
case 'solar1':
|
|
4048
|
-
// The first solar sensor is a funny duck in that it should be active for shared systems
|
|
4049
|
-
// if either body has an active solar heater or heatpump.
|
|
4050
|
-
htypes = sys.board.heaters.getInstalledHeaterTypes(1);
|
|
4051
|
-
if ('solar' in htypes || 'heatpump' in htypes) sensor.isActive = true;
|
|
4052
|
-
else if (sys.equipment.shared) {
|
|
4053
|
-
htypes = sys.board.heaters.getInstalledHeaterTypes(2);
|
|
4054
|
-
sensor.isActive = ('solar' in htypes || 'heatpump' in htypes);
|
|
4055
|
-
}
|
|
4056
|
-
else sensor.isActive = false;
|
|
4057
|
-
break;
|
|
4058
|
-
case 'solar2':
|
|
4059
|
-
if (sys.equipment.maxBodies > 1 + (sys.equipment.shared ? 1 : 0)) {
|
|
4060
|
-
htypes = sys.board.heaters.getInstalledHeaterTypes(2 + (sys.equipment.shared ? 1 : 0));
|
|
4061
|
-
sensor.isActive = ('solar' in htypes || 'heatpump' in htypes);
|
|
4062
|
-
}
|
|
4063
|
-
else sensor.isActive = false;
|
|
4064
|
-
break;
|
|
4065
|
-
case 'solar3':
|
|
4066
|
-
if (sys.equipment.maxBodies > 2 + (sys.equipment.shared ? 1 : 0)) {
|
|
4067
|
-
htypes = sys.board.heaters.getInstalledHeaterTypes(3 + (sys.equipment.shared ? 1 : 0));
|
|
4068
|
-
sensor.isActive = ('solar' in htypes || 'heatpump' in htypes);
|
|
4069
|
-
}
|
|
4070
|
-
else sensor.isActive = false;
|
|
4071
|
-
break;
|
|
4072
|
-
case 'solar4':
|
|
4073
|
-
if (sys.equipment.maxBodies > 3 + (sys.equipment.shared ? 1 : 0)) {
|
|
4074
|
-
htypes = sys.board.heaters.getInstalledHeaterTypes(4 + (sys.equipment.shared ? 1 : 0));
|
|
4075
|
-
sensor.isActive = ('solar' in htypes || 'heatpump' in htypes);
|
|
4076
|
-
}
|
|
4077
|
-
else sensor.isActive = false;
|
|
4078
|
-
break;
|
|
4079
|
-
default:
|
|
4080
|
-
if (typeof sensor.id === 'undefined') sys.equipment.tempSensors.removeItemByIndex(i);
|
|
4081
|
-
break;
|
|
4082
|
-
}
|
|
4061
|
+
if (sys.heaters.length > 0) sys.board.valueMaps.heatSources = new byteValueMap([[0, { name: 'off', desc: 'Off' }]]);
|
|
4062
|
+
if (gasHeaterInstalled) sys.board.valueMaps.heatSources.set(3, { name: 'heater', desc: 'Heater' });
|
|
4063
|
+
if (solarInstalled && (gasHeaterInstalled || heatPumpInstalled)) sys.board.valueMaps.heatSources.merge([[5, { name: 'solar', desc: 'Solar Only' }], [21, { name: 'solarpref', desc: 'Solar Preferred' }]]);
|
|
4064
|
+
else if (solarInstalled) sys.board.valueMaps.heatSources.set(5, { name: 'solar', desc: 'Solar' });
|
|
4065
|
+
if (heatPumpInstalled && (gasHeaterInstalled || solarInstalled)) sys.board.valueMaps.heatSources.merge([[9, { name: 'heatpump', desc: 'Heatpump Only' }], [25, { name: 'heatpumppref', desc: 'Heat Pump Preferred' }]]);
|
|
4066
|
+
else if (heatPumpInstalled) sys.board.valueMaps.heatSources.set(9, { name: 'heatpump', desc: 'Heat Pump' });
|
|
4067
|
+
sys.board.valueMaps.heatSources.set(32, { name: 'nochange', desc: 'No Change' });
|
|
4068
|
+
|
|
4069
|
+
sys.board.valueMaps.heatModes = new byteValueMap([[0, { name: 'off', desc: 'Off' }]]);
|
|
4070
|
+
if (gasHeaterInstalled) sys.board.valueMaps.heatModes.set(3, { name: 'heater', desc: 'Heater' });
|
|
4071
|
+
if (solarInstalled && (gasHeaterInstalled || heatPumpInstalled)) sys.board.valueMaps.heatModes.merge([[5, { name: 'solar', desc: 'Solar Only' }], [21, { name: 'solarpref', desc: 'Solar Preferred' }]]);
|
|
4072
|
+
else if (solarInstalled) sys.board.valueMaps.heatModes.set(5, { name: 'solar', desc: 'Solar' });
|
|
4073
|
+
if (heatPumpInstalled && (gasHeaterInstalled || solarInstalled)) sys.board.valueMaps.heatModes.merge([[9, { name: 'heatpump', desc: 'Heatpump Only' }], [25, { name: 'heatpumppref', desc: 'Heat Pump Preferred' }]]);
|
|
4074
|
+
else if (heatPumpInstalled) sys.board.valueMaps.heatModes.set(9, { name: 'heatpump', desc: 'Heat Pump' });
|
|
4075
|
+
// Now set the body data.
|
|
4076
|
+
for (let i = 0; i < sys.bodies.length; i++) {
|
|
4077
|
+
let body = sys.bodies.getItemByIndex(i);
|
|
4078
|
+
let btemp = state.temps.bodies.getItemById(body.id, body.isActive !== false);
|
|
4079
|
+
let opts = sys.board.heaters.getInstalledHeaterTypes(body.id);
|
|
4080
|
+
btemp.heaterOptions = opts;
|
|
4081
|
+
}
|
|
4082
|
+
this.setActiveTempSensors();
|
|
4083
4083
|
}
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
if (body.id === 1) bodyHeaters.push(heater);
|
|
4115
|
-
break;
|
|
4116
|
-
case 'body2':
|
|
4117
|
-
case 'spa':
|
|
4118
|
-
if (body.id === 2) bodyHeaters.push(heater);
|
|
4119
|
-
break;
|
|
4120
|
-
case 'poolspa':
|
|
4121
|
-
if (body.id === 1 || body.id === 2) bodyHeaters.push(heater);
|
|
4122
|
-
break;
|
|
4123
|
-
case 'body3':
|
|
4124
|
-
if (body.id === 3) bodyHeaters.push(heater);
|
|
4125
|
-
break;
|
|
4126
|
-
case 'body4':
|
|
4127
|
-
if (body.id === 4) bodyHeaters.push(heater);
|
|
4128
|
-
break;
|
|
4129
|
-
}
|
|
4130
|
-
}
|
|
4131
|
-
}
|
|
4132
|
-
// Alright we have all the body heaters so sort them in a way that will make our heater preferences work. Solar, heatpumps, and ultratemp should be in the list first
|
|
4133
|
-
// so that if we have a heater preference set up then we do not have to evaluate the other heater.
|
|
4134
|
-
let heaterTypes = sys.board.valueMaps.heaterTypes;
|
|
4135
|
-
bodyHeaters.sort((a, b) => {
|
|
4136
|
-
if (heaterTypes.transform(a.type).hasPreference) return -1;
|
|
4137
|
-
else if (heaterTypes.transform(b.type).hasPreference) return 1;
|
|
4138
|
-
return 0;
|
|
4139
|
-
});
|
|
4084
|
+
public initTempSensors() {
|
|
4085
|
+
// Add in the potential sensors and delete the ones that shouldn't exist.
|
|
4086
|
+
let maxPairs = sys.equipment.maxBodies + (sys.equipment.shared ? -1 : 0);
|
|
4087
|
+
sys.equipment.tempSensors.getItemById('air', true, { id: 'air', isActive: true, calibration: 0 }).name = 'Air';
|
|
4088
|
+
sys.equipment.tempSensors.getItemById('water1', true, { id: 'water1', isActive: true, calibration: 0 }).name = maxPairs == 1 ? 'Water' : 'Body 1';
|
|
4089
|
+
sys.equipment.tempSensors.getItemById('solar1', true, { id: 'solar1', isActive: false, calibration: 0 }).name = maxPairs == 1 ? 'Solar' : 'Solar 1';
|
|
4090
|
+
if (maxPairs > 1) {
|
|
4091
|
+
sys.equipment.tempSensors.getItemById('water2', true, { id: 'water2', isActive: false, calibration: 0 }).name = 'Body 2';
|
|
4092
|
+
sys.equipment.tempSensors.getItemById('solar2', true, { id: 'solar2', isActive: false, calibration: 0 }).name = 'Solar 2';
|
|
4093
|
+
}
|
|
4094
|
+
else {
|
|
4095
|
+
sys.equipment.tempSensors.removeItemById('water2');
|
|
4096
|
+
sys.equipment.tempSensors.removeItemById('solar2');
|
|
4097
|
+
}
|
|
4098
|
+
if (maxPairs > 2) {
|
|
4099
|
+
sys.equipment.tempSensors.getItemById('water3', true, { id: 'water3', isActive: false, calibration: 0 }).name = 'Body 3';
|
|
4100
|
+
sys.equipment.tempSensors.getItemById('solar3', true, { id: 'solar3', isActive: false, calibration: 0 }).name = 'Solar 3';
|
|
4101
|
+
}
|
|
4102
|
+
else {
|
|
4103
|
+
sys.equipment.tempSensors.removeItemById('water3');
|
|
4104
|
+
sys.equipment.tempSensors.removeItemById('solar3');
|
|
4105
|
+
}
|
|
4106
|
+
if (maxPairs > 3) {
|
|
4107
|
+
sys.equipment.tempSensors.getItemById('water4', true, { id: 'water4', isActive: false, calibration: 0 }).name = 'Body 4';
|
|
4108
|
+
sys.equipment.tempSensors.getItemById('solar4', true, { id: 'solar4', isActive: false, calibration: 0 }).name = 'Solar 4';
|
|
4109
|
+
}
|
|
4110
|
+
else {
|
|
4111
|
+
sys.equipment.tempSensors.removeItemById('water4');
|
|
4112
|
+
sys.equipment.tempSensors.removeItemById('solar4');
|
|
4113
|
+
}
|
|
4140
4114
|
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4115
|
+
}
|
|
4116
|
+
// Sets the active temp sensors based upon the installed equipment. At this point all
|
|
4117
|
+
// detectable temp sensors should exist.
|
|
4118
|
+
public setActiveTempSensors() {
|
|
4119
|
+
let htypes;
|
|
4120
|
+
// We are iterating backwards through the sensors array on purpose. We do this just in case we need
|
|
4121
|
+
// to remove a sensor during the iteration. This way the index values will not be impacted and we can
|
|
4122
|
+
// safely remove from the array we are iterating.
|
|
4123
|
+
for (let i = sys.equipment.tempSensors.length - 1; i >= 0; i--) {
|
|
4124
|
+
let sensor = sys.equipment.tempSensors.getItemByIndex(i);
|
|
4125
|
+
// The names are normalized in this array.
|
|
4126
|
+
switch (sensor.id) {
|
|
4127
|
+
case 'air':
|
|
4128
|
+
sensor.isActive = true;
|
|
4129
|
+
break;
|
|
4130
|
+
case 'water1':
|
|
4131
|
+
sensor.isActive = sys.equipment.maxBodies > 0;
|
|
4132
|
+
break;
|
|
4133
|
+
case 'water2':
|
|
4134
|
+
sensor.isActive = sys.equipment.shared ? sys.equipment.maxBodies > 2 : sys.equipment.maxBodies > 1;
|
|
4135
|
+
break;
|
|
4136
|
+
case 'water3':
|
|
4137
|
+
sensor.isActive = sys.equipment.shared ? sys.equipment.maxBodies > 3 : sys.equipment.maxBodies > 2;
|
|
4138
|
+
break;
|
|
4139
|
+
case 'water4':
|
|
4140
|
+
// It's a little weird but technically you should be able to install 3 expansions and a i10D personality
|
|
4141
|
+
// board. If this situation ever comes up we will see if it works. Whether it reports is another story
|
|
4142
|
+
// since the 2 message is short a byte for this.
|
|
4143
|
+
sensor.isActive = sys.equipment.shared ? sys.equipment.maxBodies > 4 : sys.equipment.maxBodies > 3;
|
|
4144
|
+
break;
|
|
4145
|
+
// Solar sensors are funny ducks. This is because they are for both heatpumps and solar and the equipment
|
|
4146
|
+
// can be installed on specific bodies. This will be true for heaters installed in expansion panels for *Touch, dual body systems,
|
|
4147
|
+
// and any IntelliCenter with more than one body. At some point simply implementing the multi-body functions for touch will make
|
|
4148
|
+
// this all work. This will only be with i10D or expansion panels.
|
|
4149
|
+
case 'solar1':
|
|
4150
|
+
// The first solar sensor is a funny duck in that it should be active for shared systems
|
|
4151
|
+
// if either body has an active solar heater or heatpump.
|
|
4152
|
+
htypes = sys.board.heaters.getInstalledHeaterTypes(1);
|
|
4153
|
+
if ('solar' in htypes || 'heatpump' in htypes) sensor.isActive = true;
|
|
4154
|
+
else if (sys.equipment.shared) {
|
|
4155
|
+
htypes = sys.board.heaters.getInstalledHeaterTypes(2);
|
|
4156
|
+
sensor.isActive = ('solar' in htypes || 'heatpump' in htypes);
|
|
4167
4157
|
}
|
|
4158
|
+
else sensor.isActive = false;
|
|
4168
4159
|
break;
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
// heater will shut off and not turn on again until the water temperature = setpoint - differentialTemperature.
|
|
4174
|
-
// This is the default operation on IntelliCenter and it appears to simply not start on the setpoint. We can do better
|
|
4175
|
-
// than this by heating 1 degree past the setpoint then applying this rule for 30 minutes. This allows for a more
|
|
4176
|
-
// responsive heater.
|
|
4177
|
-
//
|
|
4178
|
-
// For Ultratemp we need to determine whether the differential temp
|
|
4179
|
-
// is within range. The other thing that needs to be calculated here is
|
|
4180
|
-
// whether Ultratemp can effeciently heat the pool.
|
|
4181
|
-
if (mode === 'ultratemp' || mode === 'ultratemppref') {
|
|
4182
|
-
if (hstate.isOn) {
|
|
4183
|
-
// For the preference mode we will try to reach the setpoint for a period of time then
|
|
4184
|
-
// switch over to the gas heater. Our algorithm for this is to check the rate of
|
|
4185
|
-
// change when the heater first kicks on. If we go for longer than an hour and still
|
|
4186
|
-
// haven't reached the setpoint then we will switch to gas.
|
|
4187
|
-
if (mode === 'ultratemppref' &&
|
|
4188
|
-
typeof hstate.startTime !== 'undefined' &&
|
|
4189
|
-
hstate.startTime.getTime() < new Date().getTime() - (60 * 60 * 1000))
|
|
4190
|
-
break;
|
|
4191
|
-
// If the heater is already on we will heat to 1 degree past the setpoint.
|
|
4192
|
-
if (body.temp - 1 < cfgBody.heatSetpoint) {
|
|
4193
|
-
isOn = true;
|
|
4194
|
-
body.heatStatus = sys.board.valueMaps.heatStatus.getValue('hpheat');
|
|
4195
|
-
isHeating = true;
|
|
4196
|
-
isCooling = false;
|
|
4197
|
-
}
|
|
4198
|
-
else if (body.temp + 1 > cfgBody.coolSetpoint && heater.coolingEnabled) {
|
|
4199
|
-
isOn = true;
|
|
4200
|
-
body.heatStatus = sys.board.valueMaps.heatStatus.getValue('hpcool');
|
|
4201
|
-
isHeating = false;
|
|
4202
|
-
isCooling = true;
|
|
4203
|
-
}
|
|
4204
|
-
}
|
|
4205
|
-
else {
|
|
4206
|
-
let delayStart = typeof hstate.endTime !== 'undefined' ? (hstate.endTime.getTime() + (30 * 60 * 1000)) > new Date().getTime() : false;
|
|
4207
|
-
// The heater is not currently on lets turn it on if we pass all the criteria.
|
|
4208
|
-
if ((body.temp < cfgBody.heatSetpoint && !delayStart)
|
|
4209
|
-
|| body.temp + heater.differentialTemp < cfgBody.heatSetpoint) {
|
|
4210
|
-
isOn = true;
|
|
4211
|
-
body.heatStatus = sys.board.valueMaps.heatStatus.getValue('hpheat');
|
|
4212
|
-
isHeating = true;
|
|
4213
|
-
isCooling = false;
|
|
4214
|
-
}
|
|
4215
|
-
else if (body.temp > cfgBody.coolSetpoint && heater.coolingEnabled) {
|
|
4216
|
-
if (!delayStart || body.temp - heater.differentialTemp > cfgBody.coolSetpoint) {
|
|
4217
|
-
isOn = true;
|
|
4218
|
-
body.heatStatus = sys.board.valueMaps.heatStatus.getValue('hpcool');
|
|
4219
|
-
isHeating = false;
|
|
4220
|
-
isCooling = true;
|
|
4221
|
-
}
|
|
4222
|
-
}
|
|
4223
|
-
}
|
|
4160
|
+
case 'solar2':
|
|
4161
|
+
if (sys.equipment.maxBodies > 1 + (sys.equipment.shared ? 1 : 0)) {
|
|
4162
|
+
htypes = sys.board.heaters.getInstalledHeaterTypes(2 + (sys.equipment.shared ? 1 : 0));
|
|
4163
|
+
sensor.isActive = ('solar' in htypes || 'heatpump' in htypes);
|
|
4224
4164
|
}
|
|
4165
|
+
else sensor.isActive = false;
|
|
4225
4166
|
break;
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
isOn = true;
|
|
4231
|
-
body.heatStatus = sys.board.valueMaps.heatStatus.getValue('mtheat');
|
|
4232
|
-
isHeating = true;
|
|
4233
|
-
}
|
|
4167
|
+
case 'solar3':
|
|
4168
|
+
if (sys.equipment.maxBodies > 2 + (sys.equipment.shared ? 1 : 0)) {
|
|
4169
|
+
htypes = sys.board.heaters.getInstalledHeaterTypes(3 + (sys.equipment.shared ? 1 : 0));
|
|
4170
|
+
sensor.isActive = ('solar' in htypes || 'heatpump' in htypes);
|
|
4234
4171
|
}
|
|
4172
|
+
else sensor.isActive = false;
|
|
4235
4173
|
break;
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
// Heat past the setpoint for the heater but only if the heater is currently on.
|
|
4241
|
-
if ((body.temp - (hstate.isOn ? heater.stopTempDelta : 0)) < cfgBody.setPoint) {
|
|
4242
|
-
isOn = true;
|
|
4243
|
-
body.heatStatus = sys.board.valueMaps.heatStatus.getValue('heater');
|
|
4244
|
-
isHeating = true;
|
|
4245
|
-
}
|
|
4174
|
+
case 'solar4':
|
|
4175
|
+
if (sys.equipment.maxBodies > 3 + (sys.equipment.shared ? 1 : 0)) {
|
|
4176
|
+
htypes = sys.board.heaters.getInstalledHeaterTypes(4 + (sys.equipment.shared ? 1 : 0));
|
|
4177
|
+
sensor.isActive = ('solar' in htypes || 'heatpump' in htypes);
|
|
4246
4178
|
}
|
|
4179
|
+
else sensor.isActive = false;
|
|
4247
4180
|
break;
|
|
4248
|
-
|
|
4249
|
-
if (
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4181
|
+
default:
|
|
4182
|
+
if (typeof sensor.id === 'undefined') sys.equipment.tempSensors.removeItemByIndex(i);
|
|
4183
|
+
break;
|
|
4184
|
+
}
|
|
4185
|
+
}
|
|
4186
|
+
}
|
|
4187
|
+
// This updates the heater states based upon the installed heaters. This is true for heaters that are tied to the OCP
|
|
4188
|
+
// and those that are not.
|
|
4189
|
+
public syncHeaterStates() {
|
|
4190
|
+
try {
|
|
4191
|
+
|
|
4192
|
+
// Go through the installed heaters and bodies to determine whether they should be on. If there is a
|
|
4193
|
+
// heater that is not controlled by the OCP then we need to determine whether it should be on.
|
|
4194
|
+
let heaters = sys.heaters.toArray();
|
|
4195
|
+
let bodies = state.temps.bodies.toArray();
|
|
4196
|
+
let hon = [];
|
|
4197
|
+
for (let i = 0; i < bodies.length; i++) {
|
|
4198
|
+
let body: BodyTempState = bodies[i];
|
|
4199
|
+
let cfgBody: Body = sys.bodies.getItemById(body.id);
|
|
4200
|
+
let isHeating = false;
|
|
4201
|
+
let isCooling = false;
|
|
4202
|
+
let hstatus = sys.board.valueMaps.heatStatus.getName(body.heatStatus);
|
|
4203
|
+
let mode = sys.board.valueMaps.heatModes.getName(body.heatMode);
|
|
4204
|
+
if (body.isOn) {
|
|
4205
|
+
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.`);
|
|
4206
|
+
// Now get all the heaters associated with the body in an array.
|
|
4207
|
+
let bodyHeaters: Heater[] = [];
|
|
4208
|
+
for (let j = 0; j < heaters.length; j++) {
|
|
4209
|
+
let heater: Heater = heaters[j];
|
|
4210
|
+
if (heater.isActive === false) continue;
|
|
4211
|
+
if (heater.body === body.id) bodyHeaters.push(heater);
|
|
4212
|
+
else {
|
|
4213
|
+
let b = sys.board.valueMaps.bodies.transform(heater.body);
|
|
4214
|
+
switch (b.name) {
|
|
4215
|
+
case 'body1':
|
|
4216
|
+
case 'pool':
|
|
4217
|
+
if (body.id === 1) bodyHeaters.push(heater);
|
|
4218
|
+
break;
|
|
4219
|
+
case 'body2':
|
|
4220
|
+
case 'spa':
|
|
4221
|
+
if (body.id === 2) bodyHeaters.push(heater);
|
|
4222
|
+
break;
|
|
4223
|
+
case 'poolspa':
|
|
4224
|
+
if (body.id === 1 || body.id === 2) bodyHeaters.push(heater);
|
|
4225
|
+
break;
|
|
4226
|
+
case 'body3':
|
|
4227
|
+
if (body.id === 3) bodyHeaters.push(heater);
|
|
4228
|
+
break;
|
|
4229
|
+
case 'body4':
|
|
4230
|
+
if (body.id === 4) bodyHeaters.push(heater);
|
|
4231
|
+
break;
|
|
4232
|
+
}
|
|
4233
|
+
}
|
|
4234
|
+
}
|
|
4235
|
+
// Alright we have all the body heaters so sort them in a way that will make our heater preferences work. Solar, heatpumps, and ultratemp should be in the list first
|
|
4236
|
+
// so that if we have a heater preference set up then we do not have to evaluate the other heater.
|
|
4237
|
+
let heaterTypes = sys.board.valueMaps.heaterTypes;
|
|
4238
|
+
bodyHeaters.sort((a, b) => {
|
|
4239
|
+
if (heaterTypes.transform(a.type).hasPreference) return -1;
|
|
4240
|
+
else if (heaterTypes.transform(b.type).hasPreference) return 1;
|
|
4241
|
+
return 0;
|
|
4242
|
+
});
|
|
4243
|
+
|
|
4244
|
+
// Alright so now we should have a sorted array that has preference type heaters first.
|
|
4245
|
+
for (let j = 0; j < bodyHeaters.length; j++) {
|
|
4246
|
+
let heater: Heater = bodyHeaters[j];
|
|
4247
|
+
let isOn = false;
|
|
4248
|
+
let htype = sys.board.valueMaps.heaterTypes.transform(heater.type);
|
|
4249
|
+
let hstate = state.heaters.getItemById(heater.id, true);
|
|
4250
|
+
if (heater.master === 1) {
|
|
4251
|
+
if (hstatus !== 'cooldown') {
|
|
4252
|
+
// We need to do our own calculation as to whether it is on. This is for Nixie heaters.
|
|
4253
|
+
switch (htype.name) {
|
|
4254
|
+
case 'solar':
|
|
4255
|
+
if (mode === 'solar' || mode === 'solarpref') {
|
|
4256
|
+
// Measure up against start and stop temp deltas for effective solar heating.
|
|
4257
|
+
if (body.temp < cfgBody.heatSetpoint &&
|
|
4258
|
+
state.temps.solar > body.temp + (hstate.isOn ? heater.stopTempDelta : heater.startTempDelta)) {
|
|
4259
|
+
isOn = true;
|
|
4260
|
+
body.heatStatus = sys.board.valueMaps.heatStatus.getValue('solar');
|
|
4261
|
+
isHeating = true;
|
|
4262
|
+
}
|
|
4263
|
+
else if (heater.coolingEnabled && body.temp > cfgBody.coolSetpoint && state.heliotrope.isNight &&
|
|
4264
|
+
state.temps.solar > body.temp + (hstate.isOn ? heater.stopTempDelta : heater.startTempDelta)) {
|
|
4265
|
+
isOn = true;
|
|
4266
|
+
body.heatStatus = sys.board.valueMaps.heatStatus.getValue('cooling');
|
|
4267
|
+
isHeating = true;
|
|
4268
|
+
isCooling = true;
|
|
4269
|
+
}
|
|
4270
|
+
}
|
|
4271
|
+
break;
|
|
4272
|
+
case 'ultratemp':
|
|
4273
|
+
// There is a temperature differential setting on UltraTemp. This is how
|
|
4274
|
+
// much the water temperature needs to drop below the set temperature, for the heater
|
|
4275
|
+
// to start up again. For instance, if the set temperature and the water temperature is 82 and then the
|
|
4276
|
+
// heater will shut off and not turn on again until the water temperature = setpoint - differentialTemperature.
|
|
4277
|
+
// This is the default operation on IntelliCenter and it appears to simply not start on the setpoint. We can do better
|
|
4278
|
+
// than this by heating 1 degree past the setpoint then applying this rule for 30 minutes. This allows for a more
|
|
4279
|
+
// responsive heater.
|
|
4280
|
+
//
|
|
4281
|
+
// For Ultratemp we need to determine whether the differential temp
|
|
4282
|
+
// is within range. The other thing that needs to be calculated here is
|
|
4283
|
+
// whether Ultratemp can effeciently heat the pool.
|
|
4284
|
+
if (mode === 'ultratemp' || mode === 'ultratemppref') {
|
|
4285
|
+
if (hstate.isOn) {
|
|
4286
|
+
// For the preference mode we will try to reach the setpoint for a period of time then
|
|
4287
|
+
// switch over to the gas heater. Our algorithm for this is to check the rate of
|
|
4288
|
+
// change when the heater first kicks on. If we go for longer than an hour and still
|
|
4289
|
+
// haven't reached the setpoint then we will switch to gas.
|
|
4290
|
+
if (mode === 'ultratemppref' &&
|
|
4291
|
+
typeof hstate.startTime !== 'undefined' &&
|
|
4292
|
+
hstate.startTime.getTime() < new Date().getTime() - (60 * 60 * 1000))
|
|
4293
|
+
break;
|
|
4294
|
+
// If the heater is already on we will heat to 1 degree past the setpoint.
|
|
4295
|
+
if (body.temp - 1 < cfgBody.heatSetpoint) {
|
|
4296
|
+
isOn = true;
|
|
4297
|
+
body.heatStatus = sys.board.valueMaps.heatStatus.getValue('hpheat');
|
|
4298
|
+
isHeating = true;
|
|
4299
|
+
isCooling = false;
|
|
4300
|
+
}
|
|
4301
|
+
else if (body.temp + 1 > cfgBody.coolSetpoint && heater.coolingEnabled) {
|
|
4302
|
+
isOn = true;
|
|
4303
|
+
body.heatStatus = sys.board.valueMaps.heatStatus.getValue('hpcool');
|
|
4304
|
+
isHeating = false;
|
|
4305
|
+
isCooling = true;
|
|
4306
|
+
}
|
|
4307
|
+
}
|
|
4308
|
+
else {
|
|
4309
|
+
let delayStart = typeof hstate.endTime !== 'undefined' ? (hstate.endTime.getTime() + (30 * 60 * 1000)) > new Date().getTime() : false;
|
|
4310
|
+
// The heater is not currently on lets turn it on if we pass all the criteria.
|
|
4311
|
+
if ((body.temp < cfgBody.heatSetpoint && !delayStart)
|
|
4312
|
+
|| body.temp + heater.differentialTemp < cfgBody.heatSetpoint) {
|
|
4313
|
+
isOn = true;
|
|
4314
|
+
body.heatStatus = sys.board.valueMaps.heatStatus.getValue('hpheat');
|
|
4315
|
+
isHeating = true;
|
|
4316
|
+
isCooling = false;
|
|
4317
|
+
}
|
|
4318
|
+
else if (body.temp > cfgBody.coolSetpoint && heater.coolingEnabled) {
|
|
4319
|
+
if (!delayStart || body.temp - heater.differentialTemp > cfgBody.coolSetpoint) {
|
|
4320
|
+
isOn = true;
|
|
4321
|
+
body.heatStatus = sys.board.valueMaps.heatStatus.getValue('hpcool');
|
|
4322
|
+
isHeating = false;
|
|
4323
|
+
isCooling = true;
|
|
4324
|
+
}
|
|
4325
|
+
}
|
|
4326
|
+
}
|
|
4327
|
+
}
|
|
4328
|
+
break;
|
|
4329
|
+
case 'hybrid':
|
|
4330
|
+
if (mode !== 'off') {
|
|
4331
|
+
//console.log(`Mode: ${mode} Setpoint: ${cfgBody.setPoint}`);
|
|
4332
|
+
isHeating = isOn = true;
|
|
4333
|
+
isCooling = false;
|
|
4334
|
+
if (hstate.isOn) {
|
|
4335
|
+
// If the heater is already on we will heat to 1 degree past the setpoint.
|
|
4336
|
+
if (body.temp - 1 < cfgBody.heatSetpoint) {
|
|
4337
|
+
isOn = true;
|
|
4338
|
+
// Heat Status will be set by the returns from the heater.
|
|
4339
|
+
//body.heatStatus = sys.board.valueMaps.heatStatus.getValue('heater');
|
|
4340
|
+
isHeating = true;
|
|
4341
|
+
isCooling = false;
|
|
4342
|
+
}
|
|
4343
|
+
}
|
|
4344
|
+
else {
|
|
4345
|
+
let delayStart = typeof hstate.endTime !== 'undefined' ? (hstate.endTime.getTime() + (30 * 60 * 1000)) > new Date().getTime() : false;
|
|
4346
|
+
// The heater is not currently on lets turn it on if we pass all the criteria.
|
|
4347
|
+
if ((body.temp < cfgBody.heatSetpoint && !delayStart)
|
|
4348
|
+
|| body.temp + heater.differentialTemp < cfgBody.heatSetpoint) {
|
|
4349
|
+
isOn = true;
|
|
4350
|
+
// Heat Status will be set by the returns from the heater.
|
|
4351
|
+
//body.heatStatus = sys.board.valueMaps.heatStatus.getValue('heater');
|
|
4352
|
+
isHeating = true;
|
|
4353
|
+
isCooling = false;
|
|
4354
|
+
}
|
|
4355
|
+
}
|
|
4356
|
+
}
|
|
4357
|
+
break;
|
|
4358
|
+
|
|
4359
|
+
case 'mastertemp':
|
|
4360
|
+
// If we make it here, the other heater is not heating the body.
|
|
4361
|
+
if (mode === 'mtheater' || mode === 'heatpumppref' || mode === 'ultratemppref' || mode === 'solarpref') {
|
|
4362
|
+
if (body.temp < cfgBody.setPoint) {
|
|
4363
|
+
isOn = true;
|
|
4364
|
+
body.heatStatus = sys.board.valueMaps.heatStatus.getValue('mtheat');
|
|
4365
|
+
isHeating = true;
|
|
4366
|
+
}
|
|
4367
|
+
}
|
|
4368
|
+
break;
|
|
4369
|
+
case 'maxetherm':
|
|
4370
|
+
case 'gas':
|
|
4371
|
+
// If we make it here, the other heater is not heating the body.
|
|
4372
|
+
if (mode === 'heater' || mode === 'solarpref' || mode === 'heatpumppref' || mode === 'ultratemppref') {
|
|
4373
|
+
// Heat past the setpoint for the heater but only if the heater is currently on.
|
|
4374
|
+
if ((body.temp - (hstate.isOn ? heater.stopTempDelta : 0)) < cfgBody.setPoint) {
|
|
4375
|
+
isOn = true;
|
|
4376
|
+
body.heatStatus = sys.board.valueMaps.heatStatus.getValue('heater');
|
|
4377
|
+
isHeating = true;
|
|
4378
|
+
}
|
|
4379
|
+
}
|
|
4380
|
+
break;
|
|
4381
|
+
case 'heatpump':
|
|
4382
|
+
if (mode === 'heatpump' || mode === 'heatpumppref') {
|
|
4383
|
+
// Heat past the setpoint for the heater but only if the heater is currently on.
|
|
4384
|
+
if ((body.temp - (hstate.isOn ? heater.stopTempDelta : 0)) < cfgBody.setPoint) {
|
|
4385
|
+
isOn = true;
|
|
4386
|
+
body.heatStatus = sys.board.valueMaps.heatStatus.getValue('hpheat');
|
|
4387
|
+
isHeating = true;
|
|
4388
|
+
}
|
|
4389
|
+
//if (hstate.isOn) {
|
|
4390
|
+
// // If the heater is already on we will heat to 1 degree past the setpoint.
|
|
4391
|
+
// if (body.temp - 1 < cfgBody.heatSetpoint) {
|
|
4392
|
+
// isOn = true;
|
|
4393
|
+
// body.heatStatus = sys.board.valueMaps.heatStatus.getValue('hpheat');
|
|
4394
|
+
// isHeating = true;
|
|
4395
|
+
// isCooling = false;
|
|
4396
|
+
// }
|
|
4397
|
+
//}
|
|
4398
|
+
//else {
|
|
4399
|
+
// // The heater is not currently on lets turn it on if we pass all the criteria.
|
|
4400
|
+
// if ((body.temp < cfgBody.heatSetpoint && hstate.endTime.getTime() < new Date().getTime() + (30 * 60 * 1000))
|
|
4401
|
+
// || body.temp + heater.differentialTemp < cfgBody.heatSetpoint) {
|
|
4402
|
+
// isOn = true;
|
|
4403
|
+
// body.heatStatus = sys.board.valueMaps.heatStatus.getValue('hpcool');
|
|
4404
|
+
// isHeating = true;
|
|
4405
|
+
// isCooling = false;
|
|
4406
|
+
// }
|
|
4407
|
+
//}
|
|
4408
|
+
}
|
|
4409
|
+
break;
|
|
4410
|
+
default:
|
|
4411
|
+
isOn = utils.makeBool(hstate.isOn);
|
|
4412
|
+
break;
|
|
4413
|
+
}
|
|
4414
|
+
logger.debug(`Heater Type: ${htype.name} Mode:${mode} Temp: ${body.temp} Setpoint: ${cfgBody.setPoint} Status: ${body.heatStatus}`);
|
|
4415
|
+
}
|
|
4416
|
+
}
|
|
4417
|
+
else {
|
|
4418
|
+
let mode = sys.board.valueMaps.heatModes.getName(body.heatMode);
|
|
4419
|
+
switch (htype.name) {
|
|
4420
|
+
case 'mastertemp':
|
|
4421
|
+
if (hstatus === 'mtheat') isHeating = isOn = true;
|
|
4422
|
+
break;
|
|
4423
|
+
case 'maxetherm':
|
|
4424
|
+
case 'gas':
|
|
4425
|
+
if (hstatus === 'heater') isHeating = isOn = true;
|
|
4426
|
+
break;
|
|
4427
|
+
case 'hybrid':
|
|
4428
|
+
if (hstatus === 'mtheat' || hstatus === 'heater' || hstatus === 'dual' || hstatus === 'hybheat') isHeating = isOn = true;
|
|
4429
|
+
break;
|
|
4430
|
+
case 'ultratemp':
|
|
4431
|
+
case 'heatpump':
|
|
4432
|
+
if (mode === 'ultratemp' || mode === 'ultratemppref' || mode === 'heatpump' || mode === 'heatpumppref') {
|
|
4433
|
+
if (hstatus === 'heater') isHeating = isOn = true;
|
|
4434
|
+
else if (hstatus === 'cooling') isCooling = isOn = true;
|
|
4435
|
+
}
|
|
4436
|
+
break;
|
|
4437
|
+
case 'solar':
|
|
4438
|
+
if (mode === 'solar' || mode === 'solarpref') {
|
|
4439
|
+
if (hstatus === 'solar') isHeating = isOn = true;
|
|
4440
|
+
else if (hstatus === 'cooling') isCooling = isOn = true;
|
|
4441
|
+
}
|
|
4442
|
+
break;
|
|
4443
|
+
}
|
|
4257
4444
|
}
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4445
|
+
if (isOn === true && typeof hon.find(elem => elem === heater.id) === 'undefined') {
|
|
4446
|
+
hon.push(heater.id);
|
|
4447
|
+
if (heater.master === 1 && isOn) (async () => {
|
|
4448
|
+
try {
|
|
4449
|
+
hstate.bodyId = body.id;
|
|
4450
|
+
if (sys.board.valueMaps.heatStatus.getName(body.heatStatus) === 'cooldown')
|
|
4451
|
+
await ncp.heaters.setHeaterStateAsync(hstate, false, false);
|
|
4452
|
+
else if (isOn) {
|
|
4453
|
+
hstate.bodyId = body.id;
|
|
4454
|
+
await ncp.heaters.setHeaterStateAsync(hstate, isOn, isCooling);
|
|
4455
|
+
}
|
|
4456
|
+
else if (hstate.isOn !== isOn || hstate.isCooling !== isCooling) {
|
|
4457
|
+
await ncp.heaters.setHeaterStateAsync(hstate, isOn, isCooling);
|
|
4458
|
+
}
|
|
4459
|
+
} catch (err) { logger.error(`Error setting heater state ${hstate.name}: ${err.message}`); }
|
|
4460
|
+
})();
|
|
4461
|
+
else {
|
|
4462
|
+
hstate.isOn = isOn;
|
|
4463
|
+
hstate.bodyId = body.id;
|
|
4464
|
+
}
|
|
4267
4465
|
}
|
|
4268
|
-
|
|
4466
|
+
// If there is a heater on for the body we need break out of the loop. This will make sure for instance a gas heater
|
|
4467
|
+
// isn't started when one of the more economical methods are.
|
|
4468
|
+
if (isOn === true) break;
|
|
4269
4469
|
}
|
|
4270
|
-
break;
|
|
4271
|
-
default:
|
|
4272
|
-
isOn = utils.makeBool(hstate.isOn);
|
|
4273
|
-
break;
|
|
4274
4470
|
}
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
}
|
|
4278
|
-
else {
|
|
4279
|
-
let mode = sys.board.valueMaps.heatModes.getName(body.heatMode);
|
|
4280
|
-
switch (htype.name) {
|
|
4281
|
-
case 'mastertemp':
|
|
4282
|
-
if (hstatus === 'mtheat') isHeating = isOn = true;
|
|
4283
|
-
break;
|
|
4284
|
-
case 'maxetherm':
|
|
4285
|
-
case 'gas':
|
|
4286
|
-
if (hstatus === 'heater') isHeating = isOn = true;
|
|
4287
|
-
break;
|
|
4288
|
-
case 'hybrid':
|
|
4289
|
-
if (hstatus === 'mtheat' || hstatus === 'heater' || hstatus === 'dual') isHeating = isOn = true;
|
|
4290
|
-
break;
|
|
4291
|
-
case 'ultratemp':
|
|
4292
|
-
case 'heatpump':
|
|
4293
|
-
if (mode === 'ultratemp' || mode === 'ultratemppref' || mode === 'heatpump' || mode === 'heatpumppref') {
|
|
4294
|
-
if (hstatus === 'heater') isHeating = isOn = true;
|
|
4295
|
-
else if (hstatus === 'cooling') isCooling = isOn = true;
|
|
4296
|
-
}
|
|
4297
|
-
break;
|
|
4298
|
-
case 'solar':
|
|
4299
|
-
if (mode === 'solar' || mode === 'solarpref') {
|
|
4300
|
-
if (hstatus === 'solar') isHeating = isOn = true;
|
|
4301
|
-
else if (hstatus === 'cooling') isCooling = isOn = true;
|
|
4302
|
-
}
|
|
4303
|
-
break;
|
|
4304
|
-
}
|
|
4471
|
+
if (sys.controllerType === ControllerType.Nixie && !isHeating && !isCooling && hstatus !== 'cooldown') body.heatStatus = sys.board.valueMaps.heatStatus.getValue('off');
|
|
4472
|
+
//else if (sys.controllerType === ControllerType.Nixie) body.heatStatus = 0;
|
|
4305
4473
|
}
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
else {
|
|
4323
|
-
hstate.isOn = isOn;
|
|
4324
|
-
hstate.bodyId = body.id;
|
|
4325
|
-
}
|
|
4474
|
+
// Turn off any heaters that should be off. The code above only turns heaters on.
|
|
4475
|
+
for (let i = 0; i < heaters.length; i++) {
|
|
4476
|
+
let heater: Heater = heaters[i];
|
|
4477
|
+
if (typeof hon.find(elem => elem === heater.id) === 'undefined') {
|
|
4478
|
+
let hstate = state.heaters.getItemById(heater.id, true);
|
|
4479
|
+
if (heater.master === 1) (async () => {
|
|
4480
|
+
try {
|
|
4481
|
+
await ncp.heaters.setHeaterStateAsync(hstate, false, false);
|
|
4482
|
+
hstate.bodyId = 0;
|
|
4483
|
+
} catch (err) { logger.error(`Error turning off heater ${heater.name}: ${err.message}`); }
|
|
4484
|
+
})();
|
|
4485
|
+
else {
|
|
4486
|
+
hstate.isOn = false;
|
|
4487
|
+
hstate.bodyId = 0;
|
|
4488
|
+
}
|
|
4489
|
+
}
|
|
4326
4490
|
}
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
if (isOn === true) break;
|
|
4330
|
-
}
|
|
4331
|
-
}
|
|
4332
|
-
if (sys.controllerType === ControllerType.Nixie && !isHeating && !isCooling && hstatus !== 'cooldown') body.heatStatus = sys.board.valueMaps.heatStatus.getValue('off');
|
|
4333
|
-
//else if (sys.controllerType === ControllerType.Nixie) body.heatStatus = 0;
|
|
4334
|
-
}
|
|
4335
|
-
// Turn off any heaters that should be off. The code above only turns heaters on.
|
|
4336
|
-
for (let i = 0; i < heaters.length; i++) {
|
|
4337
|
-
let heater: Heater = heaters[i];
|
|
4338
|
-
if (typeof hon.find(elem => elem === heater.id) === 'undefined') {
|
|
4339
|
-
let hstate = state.heaters.getItemById(heater.id, true);
|
|
4340
|
-
if (heater.master === 1) (async () => {
|
|
4341
|
-
try {
|
|
4342
|
-
await ncp.heaters.setHeaterStateAsync(hstate, false, false);
|
|
4343
|
-
hstate.bodyId = 0;
|
|
4344
|
-
} catch (err) { logger.error(err.message); }
|
|
4345
|
-
})();
|
|
4346
|
-
else {
|
|
4347
|
-
hstate.isOn = false;
|
|
4348
|
-
hstate.bodyId = 0;
|
|
4349
|
-
}
|
|
4350
|
-
}
|
|
4351
|
-
}
|
|
4352
|
-
} catch (err) { logger.error(`Error synchronizing heater states: ${err.message}`); }
|
|
4353
|
-
}
|
|
4491
|
+
} catch (err) { logger.error(`Error synchronizing heater states: ${err.message}`); }
|
|
4492
|
+
}
|
|
4354
4493
|
}
|
|
4355
4494
|
export class ValveCommands extends BoardCommands {
|
|
4356
4495
|
public async restore(rest: { poolConfig: any, poolState: any }, ctx: any, res: RestoreResults): Promise<boolean> {
|
|
@@ -4409,7 +4548,7 @@ export class ValveCommands extends BoardCommands {
|
|
|
4409
4548
|
else
|
|
4410
4549
|
vstate.isDiverted = isDiverted;
|
|
4411
4550
|
}
|
|
4412
|
-
public async setValveAsync(obj: any): Promise<Valve> {
|
|
4551
|
+
public async setValveAsync(obj: any, send: boolean = true): Promise<Valve> {
|
|
4413
4552
|
try {
|
|
4414
4553
|
let id = typeof obj.id !== 'undefined' ? parseInt(obj.id, 10) : -1;
|
|
4415
4554
|
obj.master = 1;
|
|
@@ -4533,60 +4672,224 @@ export class ValveCommands extends BoardCommands {
|
|
|
4533
4672
|
return arrIds;
|
|
4534
4673
|
}
|
|
4535
4674
|
}
|
|
4536
|
-
export class
|
|
4537
|
-
|
|
4538
|
-
try {
|
|
4539
|
-
// First delete the chemControllers that should be removed.
|
|
4540
|
-
for (let i = 0; i < ctx.chemControllers.remove.length; i++) {
|
|
4541
|
-
let c = ctx.chemControllers.remove[i];
|
|
4675
|
+
export class ChemDoserCommands extends BoardCommands {
|
|
4676
|
+
public async restore(rest: { poolConfig: any, poolState: any }, ctx: any, res: RestoreResults): Promise<boolean> {
|
|
4542
4677
|
try {
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4678
|
+
// First delete the chemDosers that should be removed.
|
|
4679
|
+
for (let i = 0; i < ctx.chemDosers.remove.length; i++) {
|
|
4680
|
+
let c = ctx.chemDosers.remove[i];
|
|
4681
|
+
try {
|
|
4682
|
+
await sys.board.chemDosers.deleteChemDoserAsync(c);
|
|
4683
|
+
res.addModuleSuccess('chemDoser', `Remove: ${c.id}-${c.name}`);
|
|
4684
|
+
} catch (err) { res.addModuleError('chemDoser', `Remove: ${c.id}-${c.name}: ${err.message}`); }
|
|
4685
|
+
}
|
|
4686
|
+
for (let i = 0; i < ctx.chemDosers.update.length; i++) {
|
|
4687
|
+
let c = ctx.chemDosers.update[i];
|
|
4688
|
+
try {
|
|
4689
|
+
await sys.board.chemDosers.setChemDoserAsync(c);
|
|
4690
|
+
res.addModuleSuccess('chemDoser', `Update: ${c.id}-${c.name}`);
|
|
4691
|
+
} catch (err) { res.addModuleError('chemDoser', `Update: ${c.id}-${c.name}: ${err.message}`); }
|
|
4692
|
+
}
|
|
4693
|
+
for (let i = 0; i < ctx.chemDosers.add.length; i++) {
|
|
4694
|
+
let c = ctx.chemDosers.add[i];
|
|
4695
|
+
try {
|
|
4696
|
+
// pull a little trick to first add the data then perform the update. This way we won't get a new id or
|
|
4697
|
+
// it won't error out.
|
|
4698
|
+
let chem = sys.chemDosers.getItemById(c.id, true);
|
|
4699
|
+
await sys.board.chemDosers.setChemDoserAsync(c);
|
|
4700
|
+
res.addModuleSuccess('chemDoser', `Add: ${c.id}-${c.name}`);
|
|
4701
|
+
} catch (err) { res.addModuleError('chemDoser', `Add: ${c.id}-${c.name}: ${err.message}`); }
|
|
4702
|
+
}
|
|
4703
|
+
return true;
|
|
4704
|
+
} catch (err) { logger.error(`Error restoring chemDosers: ${err.message}`); res.addModuleError('system', `Error restoring chemDosers: ${err.message}`); return false; }
|
|
4705
|
+
}
|
|
4706
|
+
public async validateRestore(rest: { poolConfig: any, poolState: any }): Promise<{ errors: any, warnings: any, add: any, update: any, remove: any }> {
|
|
4549
4707
|
try {
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4708
|
+
let ctx = { errors: [], warnings: [], add: [], update: [], remove: [] };
|
|
4709
|
+
// Look at chemDosers.
|
|
4710
|
+
let cfg = rest.poolConfig;
|
|
4711
|
+
for (let i = 0; i < cfg.chemDosers.length; i++) {
|
|
4712
|
+
let r = cfg.chemDosers[i];
|
|
4713
|
+
let c = sys.chemDosers.find(elem => r.id === elem.id);
|
|
4714
|
+
if (typeof c === 'undefined') ctx.add.push(r);
|
|
4715
|
+
else if (JSON.stringify(c.get()) !== JSON.stringify(r)) ctx.update.push(r);
|
|
4716
|
+
}
|
|
4717
|
+
for (let i = 0; i < sys.chemDosers.length; i++) {
|
|
4718
|
+
let c = sys.chemDosers.getItemByIndex(i);
|
|
4719
|
+
let r = cfg.chemDosers.find(elem => elem.id == c.id);
|
|
4720
|
+
if (typeof r === 'undefined') ctx.remove.push(c.get(true));
|
|
4721
|
+
}
|
|
4722
|
+
return ctx;
|
|
4723
|
+
} catch (err) { logger.error(`Error validating chemDosers for restore: ${err.message}`); }
|
|
4724
|
+
}
|
|
4725
|
+
|
|
4726
|
+
public async deleteChemDoserAsync(data: any): Promise<ChemDoser> {
|
|
4556
4727
|
try {
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4728
|
+
let id = typeof data.id !== 'undefined' ? parseInt(data.id, 10) : -1;
|
|
4729
|
+
if (typeof id === 'undefined' || isNaN(id)) return Promise.reject(new InvalidEquipmentIdError(`Invalid Chem Controller Id`, id, 'chemDoser'));
|
|
4730
|
+
let chem = sys.chemDosers.getItemById(id);
|
|
4731
|
+
let schem = state.chemDosers.getItemById(id);
|
|
4732
|
+
schem.isActive = chem.isActive = false;
|
|
4733
|
+
await ncp.chemDosers.removeById(id);
|
|
4734
|
+
sys.chemDosers.removeItemById(id);
|
|
4735
|
+
state.chemDosers.removeItemById(id);
|
|
4736
|
+
sys.emitEquipmentChange();
|
|
4737
|
+
return Promise.resolve(chem);
|
|
4738
|
+
} catch (err) { logger.error(`Error deleting chem controller ${err.message}`); }
|
|
4739
|
+
}
|
|
4740
|
+
public async manualDoseAsync(data: any): Promise<ChemDoserState> {
|
|
4741
|
+
try {
|
|
4742
|
+
let id = typeof data.id !== 'undefined' ? parseInt(data.id) : undefined;
|
|
4743
|
+
if (isNaN(id)) return Promise.reject(new InvalidEquipmentDataError(`Cannot begin dosing: Invalid chem doser id was provided ${data.id}`, 'chemDoser', data.id));
|
|
4744
|
+
let chem = sys.chemDosers.find(elem => elem.id === id);
|
|
4745
|
+
if (typeof chem === 'undefined') return Promise.reject(new InvalidEquipmentDataError(`Cannot begin dosing: Chem doser was not found ${data.id}`, 'chemDoser', data.id));
|
|
4746
|
+
// Let's check the type. AFAIK you cannot manual dose an IntelliChem.
|
|
4747
|
+
// We are down to the nitty gritty. Let REM Chem do its thing.
|
|
4748
|
+
await ncp.chemDosers.manualDoseAsync(chem.id, data);
|
|
4749
|
+
return Promise.resolve(state.chemDosers.getItemById(id));
|
|
4750
|
+
} catch (err) { return Promise.reject(err); }
|
|
4751
|
+
}
|
|
4752
|
+
public async calibrateDoseAsync(data: any): Promise<ChemDoserState> {
|
|
4753
|
+
try {
|
|
4754
|
+
let id = typeof data.id !== 'undefined' ? parseInt(data.id) : undefined;
|
|
4755
|
+
if (isNaN(id)) return Promise.reject(new InvalidEquipmentDataError(`Cannot begin calibration: Invalid chem doser id was provided ${data.id}`, 'chemDoser', data.id));
|
|
4756
|
+
let chem = sys.chemDosers.find(elem => elem.id === id);
|
|
4757
|
+
if (typeof chem === 'undefined') return Promise.reject(new InvalidEquipmentDataError(`Cannot begin calibration: Chem doser was not found ${data.id}`, 'chemDoser', data.id));
|
|
4758
|
+
// We are down to the nitty gritty. Let REM Chem do its thing.
|
|
4759
|
+
await ncp.chemDosers.calibrateDoseAsync(chem.id, data);
|
|
4760
|
+
return Promise.resolve(state.chemDosers.getItemById(id));
|
|
4761
|
+
} catch (err) { return Promise.reject(err); }
|
|
4762
|
+
}
|
|
4763
|
+
|
|
4764
|
+
public async cancelDosingAsync(data: any): Promise<ChemDoserState> {
|
|
4765
|
+
try {
|
|
4766
|
+
let id = typeof data.id !== 'undefined' ? parseInt(data.id) : undefined;
|
|
4767
|
+
if (isNaN(id)) return Promise.reject(new InvalidEquipmentDataError(`Cannot cancel dosing: Invalid chem doser id was provided ${data.id}`, 'chemDoser', data.id));
|
|
4768
|
+
let chem = sys.chemDosers.find(elem => elem.id === id);
|
|
4769
|
+
if (typeof chem === 'undefined') return Promise.reject(new InvalidEquipmentDataError(`Cannot cancel dosing: Chem doser was not found ${data.id}`, 'chemDoser', data.id));
|
|
4770
|
+
// We are down to the nitty gritty. Let REM Chem do its thing.
|
|
4771
|
+
await ncp.chemDosers.cancelDoseAsync(chem.id, data);
|
|
4772
|
+
return Promise.resolve(state.chemDosers.getItemById(id));
|
|
4773
|
+
} catch (err) { return Promise.reject(err); }
|
|
4774
|
+
}
|
|
4775
|
+
public async manualMixAsync(data: any): Promise<ChemDoserState> {
|
|
4776
|
+
try {
|
|
4777
|
+
let id = typeof data.id !== 'undefined' ? parseInt(data.id) : undefined;
|
|
4778
|
+
if (isNaN(id)) return Promise.reject(new InvalidEquipmentDataError(`Cannot begin mixing: Invalid chem doser id was provided ${data.id}`, 'chemDoser', data.id));
|
|
4779
|
+
let chem = sys.chemDosers.find(elem => elem.id === id);
|
|
4780
|
+
if (typeof chem === 'undefined') return Promise.reject(new InvalidEquipmentDataError(`Cannot begin mixing: Chem doser was not found ${data.id}`, 'chemDoser', data.id));
|
|
4781
|
+
// We are down to the nitty gritty. Let REM Chem do its thing.
|
|
4782
|
+
await ncp.chemDosers.manualMixAsync(chem.id, data);
|
|
4783
|
+
return Promise.resolve(state.chemDosers.getItemById(id));
|
|
4784
|
+
} catch (err) { return Promise.reject(err); }
|
|
4785
|
+
}
|
|
4786
|
+
public async cancelMixingAsync(data: any): Promise<ChemDoserState> {
|
|
4787
|
+
try {
|
|
4788
|
+
let id = typeof data.id !== 'undefined' ? parseInt(data.id) : undefined;
|
|
4789
|
+
if (isNaN(id)) return Promise.reject(new InvalidEquipmentDataError(`Cannot cancel mixing: Invalid chem doser id was provided ${data.id}`, 'chemDoser', data.id));
|
|
4790
|
+
let chem = sys.chemDosers.find(elem => elem.id === id);
|
|
4791
|
+
if (typeof chem === 'undefined') return Promise.reject(new InvalidEquipmentDataError(`Cannot cancel mixing: Chem doser was not found ${data.id}`, 'chemDoser', data.id));
|
|
4792
|
+
// We are down to the nitty gritty. Let REM Chem do its thing.
|
|
4793
|
+
await ncp.chemDosers.cancelMixingAsync(chem.id, data);
|
|
4794
|
+
return Promise.resolve(state.chemDosers.getItemById(id));
|
|
4795
|
+
} catch (err) { return Promise.reject(err); }
|
|
4796
|
+
}
|
|
4797
|
+
public findChemDoser(data: any) {
|
|
4798
|
+
let id = parseInt(data.id, 10);
|
|
4799
|
+
if (!isNaN(id)) return sys.chemDosers.find(x => x.id === id);
|
|
4800
|
+
}
|
|
4801
|
+
public async setChemDoserAsync(data: any): Promise<ChemDoser> {
|
|
4802
|
+
// The following are the rules related to when an OCP is present.
|
|
4803
|
+
// ==============================================================
|
|
4804
|
+
// 1. IntelliChem cannot be controlled/polled via Nixie, since there is no enable/disable from the OCP at this point we don't know who is in control of polling.
|
|
4805
|
+
// 2. With *Touch Commands will be sent directly to the IntelliChem controller in the hopes that the OCP will pick it up. Turns out this is not correct. The TouchBoard now has the proper interface.
|
|
4806
|
+
// 3. njspc will communicate to the OCP for IntelliChem control via the configuration interface.
|
|
4807
|
+
|
|
4808
|
+
// The following are the rules related to when no OCP is present.
|
|
4809
|
+
// =============================================================
|
|
4810
|
+
// 1. All chemDosers will be controlled via Nixie (IntelliChem, REM Chem).
|
|
4811
|
+
try {
|
|
4812
|
+
let chem = sys.board.chemDosers.findChemDoser(data);
|
|
4813
|
+
let isAdd = typeof chem === 'undefined';
|
|
4814
|
+
if (isAdd && sys.equipment.maxChemDosers <= sys.chemDosers.length) return Promise.reject(new InvalidEquipmentDataError(`The maximum number of chem controllers have been added to your controller`, 'chemDoser', sys.equipment.maxChemDosers));
|
|
4815
|
+
if (isAdd) {
|
|
4816
|
+
// At this point we are going to add the chem controller no matter what.
|
|
4817
|
+
data.id = sys.chemDosers.getNextEquipmentId(new EquipmentIdRange(1, 10));
|
|
4818
|
+
chem = sys.chemDosers.getItemById(data.id, true);
|
|
4819
|
+
}
|
|
4820
|
+
chem.isActive = true;
|
|
4821
|
+
// So here is the thing. If you have an OCP then the IntelliChem must be controlled by that.
|
|
4822
|
+
// the messages on the bus will talk back to the OCP so if you do not do this mayhem will ensue.
|
|
4823
|
+
await ncp.chemDosers.setDoserAsync(chem, data);
|
|
4824
|
+
return Promise.resolve(chem);
|
|
4825
|
+
}
|
|
4826
|
+
catch (err) { return Promise.reject(err); }
|
|
4827
|
+
}
|
|
4828
|
+
public async setChemDoserStateAsync(data: any): Promise<ChemDoserState> {
|
|
4829
|
+
let chem = sys.board.chemDosers.findChemDoser(data);
|
|
4830
|
+
if (typeof chem === 'undefined') return Promise.reject(new InvalidEquipmentIdError(`A valid chem doser could not be found for id:${data.id}`, data.id, 'chemDoser'));
|
|
4831
|
+
data.id = chem.id;
|
|
4832
|
+
logger.info(`Setting ${chem.name} data ${chem.master}`);
|
|
4833
|
+
if (chem.master === 1) await ncp.chemDosers.setDoserAsync(chem, data);
|
|
4834
|
+
else await sys.board.chemDosers.setChemDoserAsync(data);
|
|
4835
|
+
let schem = state.chemDosers.getItemById(chem.id, true);
|
|
4836
|
+
return Promise.resolve(schem);
|
|
4837
|
+
}
|
|
4838
|
+
}
|
|
4839
|
+
export class ChemControllerCommands extends BoardCommands {
|
|
4840
|
+
public async restore(rest: { poolConfig: any, poolState: any }, ctx: any, res: RestoreResults): Promise<boolean> {
|
|
4841
|
+
try {
|
|
4842
|
+
// First delete the chemControllers that should be removed.
|
|
4843
|
+
for (let i = 0; i < ctx.chemControllers.remove.length; i++) {
|
|
4844
|
+
let c = ctx.chemControllers.remove[i];
|
|
4845
|
+
try {
|
|
4846
|
+
await sys.board.chemControllers.deleteChemControllerAsync(c);
|
|
4847
|
+
res.addModuleSuccess('chemController', `Remove: ${c.id}-${c.name}`);
|
|
4848
|
+
} catch (err) { res.addModuleError('chemController', `Remove: ${c.id}-${c.name}: ${err.message}`); }
|
|
4849
|
+
}
|
|
4850
|
+
for (let i = 0; i < ctx.chemControllers.update.length; i++) {
|
|
4851
|
+
let c = ctx.chemControllers.update[i];
|
|
4852
|
+
try {
|
|
4853
|
+
await sys.board.chemControllers.setChemControllerAsync(c);
|
|
4854
|
+
res.addModuleSuccess('chemController', `Update: ${c.id}-${c.name}`);
|
|
4855
|
+
} catch (err) { res.addModuleError('chemController', `Update: ${c.id}-${c.name}: ${err.message}`); }
|
|
4856
|
+
}
|
|
4857
|
+
for (let i = 0; i < ctx.chemControllers.add.length; i++) {
|
|
4858
|
+
let c = ctx.chemControllers.add[i];
|
|
4859
|
+
try {
|
|
4860
|
+
// pull a little trick to first add the data then perform the update. This way we won't get a new id or
|
|
4861
|
+
// it won't error out.
|
|
4862
|
+
let chem = sys.chemControllers.getItemById(c.id, true);
|
|
4863
|
+
// RSG 11.24.21. setChemControllerAsync will only set the type/address if it thinks it's new.
|
|
4864
|
+
// For a restore, if we set the type/address here it will pass the validation steps.
|
|
4865
|
+
chem.type = c.type;
|
|
4866
|
+
// chem.address = c.address;
|
|
4867
|
+
await sys.board.chemControllers.setChemControllerAsync(c);
|
|
4868
|
+
res.addModuleSuccess('chemController', `Add: ${c.id}-${c.name}`);
|
|
4869
|
+
} catch (err) { res.addModuleError('chemController', `Add: ${c.id}-${c.name}: ${err.message}`); }
|
|
4870
|
+
}
|
|
4871
|
+
return true;
|
|
4872
|
+
} catch (err) { logger.error(`Error restoring chemControllers: ${err.message}`); res.addModuleError('system', `Error restoring chemControllers: ${err.message}`); return false; }
|
|
4873
|
+
}
|
|
4874
|
+
public async validateRestore(rest: { poolConfig: any, poolState: any }): Promise<{ errors: any, warnings: any, add: any, update: any, remove: any }> {
|
|
4875
|
+
try {
|
|
4876
|
+
let ctx = { errors: [], warnings: [], add: [], update: [], remove: [] };
|
|
4877
|
+
// Look at chemControllers.
|
|
4878
|
+
let cfg = rest.poolConfig;
|
|
4879
|
+
for (let i = 0; i < cfg.chemControllers.length; i++) {
|
|
4880
|
+
let r = cfg.chemControllers[i];
|
|
4881
|
+
let c = sys.chemControllers.find(elem => r.id === elem.id);
|
|
4882
|
+
if (typeof c === 'undefined') ctx.add.push(r);
|
|
4883
|
+
else if (JSON.stringify(c.get()) !== JSON.stringify(r)) ctx.update.push(r);
|
|
4884
|
+
}
|
|
4885
|
+
for (let i = 0; i < sys.chemControllers.length; i++) {
|
|
4886
|
+
let c = sys.chemControllers.getItemByIndex(i);
|
|
4887
|
+
let r = cfg.chemControllers.find(elem => elem.id == c.id);
|
|
4888
|
+
if (typeof r === 'undefined') ctx.remove.push(c.get(true));
|
|
4889
|
+
}
|
|
4890
|
+
return ctx;
|
|
4891
|
+
} catch (err) { logger.error(`Error validating chemControllers for restore: ${err.message}`); }
|
|
4892
|
+
}
|
|
4590
4893
|
|
|
4591
4894
|
public async deleteChemControllerAsync(data: any): Promise<ChemController> {
|
|
4592
4895
|
try {
|
|
@@ -4602,20 +4905,35 @@ export class ChemControllerCommands extends BoardCommands {
|
|
|
4602
4905
|
return Promise.resolve(chem);
|
|
4603
4906
|
} catch (err) { logger.error(`Error deleting chem controller ${err.message}`); }
|
|
4604
4907
|
}
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4908
|
+
public async manualDoseAsync(data: any): Promise<ChemControllerState> {
|
|
4909
|
+
try {
|
|
4910
|
+
let id = typeof data.id !== 'undefined' ? parseInt(data.id) : undefined;
|
|
4911
|
+
if (isNaN(id)) return Promise.reject(new InvalidEquipmentDataError(`Cannot begin dosing: Invalid chem controller id was provided ${data.id}`, 'chemController', data.id));
|
|
4912
|
+
let chem = sys.chemControllers.find(elem => elem.id === id);
|
|
4913
|
+
if (typeof chem === 'undefined') return Promise.reject(new InvalidEquipmentDataError(`Cannot begin dosing: Chem controller was not found ${data.id}`, 'chemController', data.id));
|
|
4914
|
+
// Let's check the type. AFAIK you cannot manual dose an IntelliChem.
|
|
4915
|
+
let type = sys.board.valueMaps.chemControllerTypes.transform(chem.type);
|
|
4916
|
+
if (type.name !== 'rem') return Promise.reject(new InvalidEquipmentDataError(`You can only perform manual dosing on REM Chem controllers. Cannot manually dose ${type.desc}`, 'chemController', data.id));
|
|
4917
|
+
// We are down to the nitty gritty. Let REM Chem do its thing.
|
|
4918
|
+
await ncp.chemControllers.manualDoseAsync(chem.id, data);
|
|
4919
|
+
return Promise.resolve(state.chemControllers.getItemById(id));
|
|
4920
|
+
} catch (err) { return Promise.reject(err); }
|
|
4921
|
+
}
|
|
4922
|
+
public async calibrateDoseAsync(data: any): Promise<ChemControllerState> {
|
|
4923
|
+
try {
|
|
4924
|
+
let id = typeof data.id !== 'undefined' ? parseInt(data.id) : undefined;
|
|
4925
|
+
if (isNaN(id)) return Promise.reject(new InvalidEquipmentDataError(`Cannot begin calibration: Invalid chem controller id was provided ${data.id}`, 'chemController', data.id));
|
|
4926
|
+
let chem = sys.chemControllers.find(elem => elem.id === id);
|
|
4927
|
+
if (typeof chem === 'undefined') return Promise.reject(new InvalidEquipmentDataError(`Cannot begin calibration: Chem controller was not found ${data.id}`, 'chemController', data.id));
|
|
4928
|
+
// Let's check the type. AFAIK you cannot manual dose an IntelliChem.
|
|
4929
|
+
let type = sys.board.valueMaps.chemControllerTypes.transform(chem.type);
|
|
4930
|
+
if (type.name !== 'rem') return Promise.reject(new InvalidEquipmentDataError(`You can only perform calibration dosing on REM Chem controllers. Cannot calibrate ${type.desc}`, 'chemController', data.id));
|
|
4931
|
+
// We are down to the nitty gritty. Let REM Chem do its thing.
|
|
4932
|
+
await ncp.chemControllers.calibrateDoseAsync(chem.id, data);
|
|
4933
|
+
return Promise.resolve(state.chemControllers.getItemById(id));
|
|
4934
|
+
} catch (err) { return Promise.reject(err); }
|
|
4935
|
+
}
|
|
4936
|
+
|
|
4619
4937
|
public async cancelDosingAsync(data: any): Promise<ChemControllerState> {
|
|
4620
4938
|
try {
|
|
4621
4939
|
let id = typeof data.id !== 'undefined' ? parseInt(data.id) : undefined;
|
|
@@ -4672,7 +4990,7 @@ export class ChemControllerCommands extends BoardCommands {
|
|
|
4672
4990
|
if (!isNaN(id)) return sys.chemControllers.find(x => x.id === id);
|
|
4673
4991
|
else if (!isNaN(address)) return sys.chemControllers.find(x => x.address === address);
|
|
4674
4992
|
}
|
|
4675
|
-
public async setChemControllerAsync(data: any): Promise<ChemController> {
|
|
4993
|
+
public async setChemControllerAsync(data: any, send: boolean = true): Promise<ChemController> {
|
|
4676
4994
|
// The following are the rules related to when an OCP is present.
|
|
4677
4995
|
// ==============================================================
|
|
4678
4996
|
// 1. IntelliChem cannot be controlled/polled via Nixie, since there is no enable/disable from the OCP at this point we don't know who is in control of polling.
|
|
@@ -4683,8 +5001,9 @@ export class ChemControllerCommands extends BoardCommands {
|
|
|
4683
5001
|
// =============================================================
|
|
4684
5002
|
// 1. All chemControllers will be controlled via Nixie (IntelliChem, REM Chem).
|
|
4685
5003
|
try {
|
|
5004
|
+
// let c1 = sys.chemControllers.getItemById(1);
|
|
4686
5005
|
let chem = sys.board.chemControllers.findChemController(data);
|
|
4687
|
-
let isAdd = typeof chem === 'undefined';
|
|
5006
|
+
let isAdd = typeof chem === 'undefined' || typeof chem.isActive === 'undefined';
|
|
4688
5007
|
let type = sys.board.valueMaps.chemControllerTypes.encode(isAdd ? data.type : chem.type);
|
|
4689
5008
|
if (typeof type === 'undefined') return Promise.reject(new InvalidEquipmentDataError(`The chem controller type could not be determined ${data.type || type}`, 'chemController', type));
|
|
4690
5009
|
if (isAdd && sys.equipment.maxChemControllers <= sys.chemControllers.length) return Promise.reject(new InvalidEquipmentDataError(`The maximum number of chem controllers have been added to your controller`, 'chemController', sys.equipment.maxChemControllers));
|