nodejs-poolcontroller 7.5.1 → 7.7.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/.github/ISSUE_TEMPLATE/1-bug-report.yml +84 -0
- package/.github/ISSUE_TEMPLATE/2-docs.md +12 -0
- package/.github/ISSUE_TEMPLATE/3-proposal.md +28 -0
- package/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/Changelog +19 -0
- package/Dockerfile +3 -3
- package/README.md +13 -8
- package/app.ts +1 -1
- package/config/Config.ts +38 -2
- package/config/VersionCheck.ts +27 -12
- package/controller/Constants.ts +2 -1
- package/controller/Equipment.ts +193 -9
- package/controller/Errors.ts +10 -0
- package/controller/Lockouts.ts +503 -0
- package/controller/State.ts +269 -64
- package/controller/boards/AquaLinkBoard.ts +1000 -0
- package/controller/boards/BoardFactory.ts +4 -0
- package/controller/boards/EasyTouchBoard.ts +468 -144
- package/controller/boards/IntelliCenterBoard.ts +466 -307
- package/controller/boards/IntelliTouchBoard.ts +37 -5
- package/controller/boards/NixieBoard.ts +671 -141
- package/controller/boards/SystemBoard.ts +1397 -641
- package/controller/comms/Comms.ts +462 -362
- package/controller/comms/messages/Messages.ts +174 -30
- package/controller/comms/messages/config/ChlorinatorMessage.ts +6 -3
- package/controller/comms/messages/config/CircuitMessage.ts +1 -0
- package/controller/comms/messages/config/ExternalMessage.ts +10 -8
- package/controller/comms/messages/config/HeaterMessage.ts +141 -29
- package/controller/comms/messages/config/OptionsMessage.ts +9 -2
- package/controller/comms/messages/config/PumpMessage.ts +53 -35
- package/controller/comms/messages/config/ScheduleMessage.ts +33 -25
- package/controller/comms/messages/config/ValveMessage.ts +2 -2
- package/controller/comms/messages/status/ChlorinatorStateMessage.ts +38 -86
- package/controller/comms/messages/status/EquipmentStateMessage.ts +59 -23
- package/controller/comms/messages/status/HeaterStateMessage.ts +57 -3
- package/controller/comms/messages/status/IntelliChemStateMessage.ts +56 -8
- package/controller/comms/messages/status/PumpStateMessage.ts +23 -1
- package/controller/nixie/Nixie.ts +1 -1
- package/controller/nixie/bodies/Body.ts +3 -0
- package/controller/nixie/chemistry/ChemController.ts +164 -51
- package/controller/nixie/chemistry/Chlorinator.ts +137 -88
- package/controller/nixie/circuits/Circuit.ts +51 -19
- package/controller/nixie/heaters/Heater.ts +241 -31
- package/controller/nixie/pumps/Pump.ts +488 -206
- package/controller/nixie/schedules/Schedule.ts +91 -35
- package/controller/nixie/valves/Valve.ts +1 -1
- package/defaultConfig.json +20 -0
- package/package.json +21 -21
- package/web/Server.ts +94 -49
- package/web/bindings/aqualinkD.json +505 -0
- package/web/bindings/influxDB.json +71 -1
- package/web/bindings/mqtt.json +98 -39
- package/web/bindings/mqttAlt.json +59 -1
- package/web/interfaces/baseInterface.ts +1 -0
- package/web/interfaces/httpInterface.ts +23 -2
- package/web/interfaces/influxInterface.ts +45 -10
- package/web/interfaces/mqttInterface.ts +114 -54
- package/web/services/config/Config.ts +55 -132
- package/web/services/state/State.ts +81 -4
- package/web/services/state/StateSocket.ts +4 -4
- package/web/services/utilities/Utilities.ts +8 -6
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -52
- package/config copy.json +0 -300
- package/issue_template.md +0 -52
|
@@ -17,14 +17,16 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
17
17
|
import * as extend from 'extend';
|
|
18
18
|
import { EventEmitter } from 'events';
|
|
19
19
|
import { ncp } from "../nixie/Nixie";
|
|
20
|
+
import { NixieHeaterBase } from "../nixie/heaters/Heater";
|
|
20
21
|
import { utils, Heliotrope, Timestamp } from '../Constants';
|
|
21
22
|
import {SystemBoard, byteValueMap, ConfigQueue, ConfigRequest, BodyCommands, FilterCommands, PumpCommands, SystemCommands, CircuitCommands, FeatureCommands, ValveCommands, HeaterCommands, ChlorinatorCommands, ChemControllerCommands, EquipmentIdRange} from './SystemBoard';
|
|
22
23
|
import { logger } from '../../logger/Logger';
|
|
23
|
-
import { state, ChlorinatorState, ChemControllerState, TemperatureState, VirtualCircuitState, ICircuitState, ICircuitGroupState, LightGroupState, ValveState, FilterState } from '../State';
|
|
24
|
+
import { state, ChlorinatorState, ChemControllerState, TemperatureState, VirtualCircuitState, CircuitState, ICircuitState, ICircuitGroupState, LightGroupState, ValveState, FilterState, BodyTempState, FeatureState } from '../State';
|
|
24
25
|
import { sys, Equipment, Options, Owner, Location, CircuitCollection, TempSensorCollection, General, PoolSystem, Body, Pump, CircuitGroupCircuit, CircuitGroup, ChemController, Circuit, Feature, Valve, ICircuit, Heater, LightGroup, LightGroupCircuit, ControllerType, Filter } from '../Equipment';
|
|
25
26
|
import { Protocol, Outbound, Message, Response } from '../comms/messages/Messages';
|
|
26
|
-
import { EquipmentNotFoundError, InvalidEquipmentDataError, InvalidEquipmentIdError, ParameterOutOfRangeError } from '../Errors';
|
|
27
|
-
import {conn} from '../comms/Comms';
|
|
27
|
+
import { BoardProcessError, EquipmentNotFoundError, InvalidEquipmentDataError, InvalidEquipmentIdError, InvalidOperationError, ParameterOutOfRangeError } from '../Errors';
|
|
28
|
+
import { conn } from '../comms/Comms';
|
|
29
|
+
import { delayMgr } from '../Lockouts';
|
|
28
30
|
export class NixieBoard extends SystemBoard {
|
|
29
31
|
constructor (system: PoolSystem){
|
|
30
32
|
super(system);
|
|
@@ -36,30 +38,44 @@ export class NixieBoard extends SystemBoard {
|
|
|
36
38
|
this.equipmentIds.features.start = 129;
|
|
37
39
|
this.equipmentIds.circuitGroups.start = 193;
|
|
38
40
|
this.equipmentIds.virtualCircuits.start = 237;
|
|
41
|
+
this.valueMaps.equipmentMaster = new byteValueMap([
|
|
42
|
+
[1, { val: 1, name: 'ncp', desc: 'Nixie Control Panel' }],
|
|
43
|
+
[2, { val: 2, name: 'ext', desc: 'External Control Panel'}]
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
this.valueMaps.featureFunctions = new byteValueMap([
|
|
47
|
+
[0, { name: 'generic', desc: 'Generic' }],
|
|
48
|
+
[1, { name: 'spillway', desc: 'Spillway' }],
|
|
49
|
+
[2, { name: 'spadrain', desc: 'Spa Drain' }]
|
|
50
|
+
]);
|
|
39
51
|
this.valueMaps.circuitFunctions = new byteValueMap([
|
|
40
52
|
[0, { name: 'generic', desc: 'Generic' }],
|
|
41
53
|
[1, { name: 'spillway', desc: 'Spillway' }],
|
|
42
|
-
[2, { name: 'mastercleaner', desc: 'Master Cleaner' }],
|
|
54
|
+
[2, { name: 'mastercleaner', desc: 'Master Cleaner', body: 1 }],
|
|
43
55
|
[3, { name: 'chemrelay', desc: 'Chem Relay' }],
|
|
44
56
|
[4, { name: 'light', desc: 'Light', isLight: true }],
|
|
45
|
-
[5, { name: 'intellibrite', desc: 'Intellibrite', isLight: true }],
|
|
46
|
-
[6, { name: 'globrite', desc: 'GloBrite', isLight: true }],
|
|
57
|
+
[5, { name: 'intellibrite', desc: 'Intellibrite', isLight: true, theme: 'intellibrite' }],
|
|
58
|
+
[6, { name: 'globrite', desc: 'GloBrite', isLight: true, theme: 'intellibrite' }],
|
|
47
59
|
[7, { name: 'globritewhite', desc: 'GloBrite White', isLight: true }],
|
|
48
|
-
[8, { name: 'magicstream', desc: 'Magicstream', isLight: true }],
|
|
60
|
+
[8, { name: 'magicstream', desc: 'Magicstream', isLight: true, theme: 'magicstream' }],
|
|
49
61
|
[9, { name: 'dimmer', desc: 'Dimmer', isLight: true }],
|
|
50
|
-
[10, { name: 'colorcascade', desc: 'ColorCascade', isLight: true }],
|
|
51
|
-
[11, { name: 'mastercleaner2', desc: 'Master Cleaner 2' }],
|
|
52
|
-
[12, { name: 'pool', desc: 'Pool', hasHeatSource: true }],
|
|
53
|
-
[13, { name: 'spa', desc: 'Spa', hasHeatSource: true }],
|
|
54
|
-
[14, { name: 'colorlogic', desc: 'ColorLogic', isLight:true }]
|
|
62
|
+
[10, { name: 'colorcascade', desc: 'ColorCascade', isLight: true, theme: 'intellibrite' }],
|
|
63
|
+
[11, { name: 'mastercleaner2', desc: 'Master Cleaner 2', body: 2 }],
|
|
64
|
+
[12, { name: 'pool', desc: 'Pool', hasHeatSource: true, body: 1 }],
|
|
65
|
+
[13, { name: 'spa', desc: 'Spa', hasHeatSource: true, body: 2 }],
|
|
66
|
+
[14, { name: 'colorlogic', desc: 'ColorLogic', isLight: true, theme: 'colorlogic' }],
|
|
67
|
+
[15, { name: 'spadrain', desc: 'Spa Drain' }],
|
|
68
|
+
[16, { name: 'pooltone', desc: 'Pool Tone', isLight: true, theme: 'pooltone' }],
|
|
55
69
|
]);
|
|
56
70
|
this.valueMaps.pumpTypes = new byteValueMap([
|
|
57
|
-
[1, { name: 'ss', desc: 'Single Speed', maxCircuits: 0, hasAddress: false, hasBody: true, maxRelays: 1 }],
|
|
58
|
-
[2, { name: 'ds', desc: 'Two Speed', maxCircuits: 8, hasAddress: false, hasBody: false, maxRelays: 2 }],
|
|
71
|
+
[1, { name: 'ss', desc: 'Single Speed', maxCircuits: 0, hasAddress: false, hasBody: true, maxRelays: 1, relays: [{ id: 1, name: 'Pump On/Off' }]}],
|
|
72
|
+
[2, { name: 'ds', desc: 'Two Speed', maxCircuits: 8, hasAddress: false, hasBody: false, maxRelays: 2, relays: [{ id: 1, name: 'Low Speed' }, { id: 2, name: 'High Speed' }]}],
|
|
59
73
|
[3, { name: 'vs', desc: 'Intelliflo VS', maxPrimingTime: 6, minSpeed: 450, maxSpeed: 3450, maxCircuits: 8, hasAddress: true }],
|
|
60
74
|
[4, { name: 'vsf', desc: 'Intelliflo VSF', minSpeed: 450, maxSpeed: 3450, minFlow: 15, maxFlow: 130, maxCircuits: 8, hasAddress: true }],
|
|
61
75
|
[5, { name: 'vf', desc: 'Intelliflo VF', minFlow: 15, maxFlow: 130, maxCircuits: 8, hasAddress: true }],
|
|
62
|
-
[
|
|
76
|
+
[6, { name: 'hwvs', desc: 'Hayward Eco/TriStar VS', minSpeed: 450, maxSpeed: 3450, maxCircuits: 8, hasAddress: true }],
|
|
77
|
+
[7, { name: 'hwrly', desc: 'Hayward Relay VS', hasAddress: false, maxCircuits: 8, maxRelays: 4, maxSpeeds: 8, relays: [{ id: 1, name: 'Step #1' }, { id: 2, name: 'Step #2'}, { id: 3, name: 'Step #3' }, { id: 4, name: 'Pump On' }] }],
|
|
78
|
+
[100, { name: 'sf', desc: 'SuperFlo VS', hasAddress: false, maxCircuits: 8, maxRelays: 4, equipmentMaster: 1, maxSpeeds: 4, relays: [{ id: 1, name: 'Program #1' }, { id: 2, name: 'Program #2' }, { id: 3, name: 'Program #3' }, { id: 4, name: 'Program #4' }]}]
|
|
63
79
|
]);
|
|
64
80
|
// RSG - same as systemBoard definition; can delete.
|
|
65
81
|
this.valueMaps.heatModes = new byteValueMap([
|
|
@@ -132,10 +148,14 @@ export class NixieBoard extends SystemBoard {
|
|
|
132
148
|
[245, { name: 'spaHeater', desc: 'Spa Heater' }],
|
|
133
149
|
[246, { name: 'freeze', desc: 'Freeze' }],
|
|
134
150
|
[247, { name: 'poolSpa', desc: 'Pool/Spa' }],
|
|
135
|
-
[248, { name: 'solarHeat', desc: 'Solar Heat' }],
|
|
136
151
|
[251, { name: 'heater', desc: 'Heater' }],
|
|
137
152
|
[252, { name: 'solar', desc: 'Solar' }],
|
|
138
|
-
[
|
|
153
|
+
[253, { name: 'solar1', desc: 'Solar Body 1' }],
|
|
154
|
+
[254, { name: 'solar2', desc: 'Solar Body 2' }],
|
|
155
|
+
[255, { name: 'solar3', desc: 'Solar Body 3' }],
|
|
156
|
+
[256, { name: 'solar4', desc: 'Solar Body 4' }],
|
|
157
|
+
[257, { name: 'poolHeatEnable', desc: 'Pool Heat Enable' }],
|
|
158
|
+
[258, { name: 'anyHeater', desc: 'Any Heater' }]
|
|
139
159
|
]);
|
|
140
160
|
this.valueMaps.scheduleTimeTypes.merge([
|
|
141
161
|
[1, { name: 'sunrise', desc: 'Sunrise' }],
|
|
@@ -144,36 +164,52 @@ export class NixieBoard extends SystemBoard {
|
|
|
144
164
|
|
|
145
165
|
this.valueMaps.lightThemes = new byteValueMap([
|
|
146
166
|
// IntelliBrite Themes
|
|
147
|
-
[0, { name: 'white', desc: 'White',
|
|
148
|
-
[1, { name: 'green', desc: 'Green',
|
|
149
|
-
[2, { name: 'blue', desc: 'Blue',
|
|
150
|
-
[3, { name: 'magenta', desc: 'Magenta',
|
|
151
|
-
[4, { name: 'red', desc: 'Red',
|
|
152
|
-
[5, { name: 'sam', desc: 'SAm Mode',
|
|
153
|
-
[6, { name: 'party', desc: 'Party',
|
|
154
|
-
[7, { name: 'romance', desc: 'Romance',
|
|
155
|
-
[8, { name: 'caribbean', desc: 'Caribbean',
|
|
156
|
-
[9, { name: 'american', desc: 'American',
|
|
157
|
-
[10, { name: 'sunset', desc: 'Sunset',
|
|
158
|
-
[11, { name: 'royal', desc: 'Royal',
|
|
167
|
+
[0, { name: 'white', desc: 'White', types: ['intellibrite', 'magicstream'], sequence: 11 }],
|
|
168
|
+
[1, { name: 'green', desc: 'Green', types: ['intellibrite', 'magicstream'], sequence: 9 }],
|
|
169
|
+
[2, { name: 'blue', desc: 'Blue', types: ['intellibrite', 'magicstream'], sequence: 8 }],
|
|
170
|
+
[3, { name: 'magenta', desc: 'Magenta', types: ['intellibrite', 'magicstream'], sequence: 12 }],
|
|
171
|
+
[4, { name: 'red', desc: 'Red', types: ['intellibrite', 'magicstream'], sequence: 10 }],
|
|
172
|
+
[5, { name: 'sam', desc: 'SAm Mode', types: ['intellibrite', 'magicstream'], sequence: 1 }],
|
|
173
|
+
[6, { name: 'party', desc: 'Party', types: ['intellibrite', 'magicstream'], sequence: 2 }],
|
|
174
|
+
[7, { name: 'romance', desc: 'Romance', types: ['intellibrite', 'magicstream'], sequence: 3 }],
|
|
175
|
+
[8, { name: 'caribbean', desc: 'Caribbean', types: ['intellibrite', 'magicstream'], sequence: 4 }],
|
|
176
|
+
[9, { name: 'american', desc: 'American', types: ['intellibrite', 'magicstream'], sequence: 5 }],
|
|
177
|
+
[10, { name: 'sunset', desc: 'Sunset', types: ['intellibrite', 'magicstream'], sequence: 6 }],
|
|
178
|
+
[11, { name: 'royal', desc: 'Royal', types: ['intellibrite', 'magicstream'], sequence: 7 }],
|
|
159
179
|
// ColorLogic Themes
|
|
160
|
-
[20, { name: 'cloudwhite', desc: 'Cloud White',
|
|
161
|
-
[21, { name: 'deepsea', desc: 'Deep Sea',
|
|
162
|
-
[22, { name: 'royalblue', desc: 'Royal Blue',
|
|
163
|
-
[23, { name: 'afternoonskies', desc: 'Afternoon Skies',
|
|
164
|
-
[24, { name: 'aquagreen', desc: 'Aqua Green',
|
|
165
|
-
[25, { name: 'emerald', desc: 'Emerald',
|
|
166
|
-
[26, { name: 'warmred', desc: 'Warm Red',
|
|
167
|
-
[27, { name: 'flamingo', desc: 'Flamingo',
|
|
168
|
-
[28, { name: 'vividviolet', desc: 'Vivid Violet',
|
|
169
|
-
[29, { name: 'sangria', desc: 'Sangria',
|
|
170
|
-
[30, { name: 'twilight', desc: 'Twilight',
|
|
171
|
-
[31, { name: 'tranquility', desc: 'Tranquility',
|
|
172
|
-
[32, { name: 'gemstone', desc: 'Gemstone',
|
|
173
|
-
[33, { name: 'usa', desc: 'USA',
|
|
174
|
-
[34, { name: 'mardigras', desc: 'Mardi Gras',
|
|
175
|
-
[35, { name: 'coolcabaret', desc: 'Cabaret',
|
|
176
|
-
|
|
180
|
+
[20, { name: 'cloudwhite', desc: 'Cloud White', types: ['colorlogic'], sequence: 7 }],
|
|
181
|
+
[21, { name: 'deepsea', desc: 'Deep Sea', types: ['colorlogic'], sequence: 2 }],
|
|
182
|
+
[22, { name: 'royalblue', desc: 'Royal Blue', types: ['colorlogic'], sequence: 3 }],
|
|
183
|
+
[23, { name: 'afternoonskies', desc: 'Afternoon Skies', types: ['colorlogic'], sequence: 4 }],
|
|
184
|
+
[24, { name: 'aquagreen', desc: 'Aqua Green', types: ['colorlogic'], sequence: 5 }],
|
|
185
|
+
[25, { name: 'emerald', desc: 'Emerald', types: ['colorlogic'], sequence: 6 }],
|
|
186
|
+
[26, { name: 'warmred', desc: 'Warm Red', types: ['colorlogic'], sequence: 8 }],
|
|
187
|
+
[27, { name: 'flamingo', desc: 'Flamingo', types: ['colorlogic'], sequence: 9 }],
|
|
188
|
+
[28, { name: 'vividviolet', desc: 'Vivid Violet', types: ['colorlogic'], sequence: 10 }],
|
|
189
|
+
[29, { name: 'sangria', desc: 'Sangria', types: ['colorlogic'], sequence: 11 }],
|
|
190
|
+
[30, { name: 'twilight', desc: 'Twilight', types: ['colorlogic'], sequence: 12 }],
|
|
191
|
+
[31, { name: 'tranquility', desc: 'Tranquility', types: ['colorlogic'], sequence: 13 }],
|
|
192
|
+
[32, { name: 'gemstone', desc: 'Gemstone', types: ['colorlogic'], sequence: 14 }],
|
|
193
|
+
[33, { name: 'usa', desc: 'USA', types: ['colorlogic'], sequence: 15 }],
|
|
194
|
+
[34, { name: 'mardigras', desc: 'Mardi Gras', types: ['colorlogic'], sequence: 16 }],
|
|
195
|
+
[35, { name: 'coolcabaret', desc: 'Cabaret', types: ['colorlogic'], sequence: 17 }],
|
|
196
|
+
// Sunseeker PoolTone Themes
|
|
197
|
+
[36, { name: 'eveningsea', desc: 'Evening Sea', types: ['pooltone'], sequence: 1 }],
|
|
198
|
+
[37, { name: 'eveningrivers', desc: 'Evening Rivers', types: ['pooltone'], sequence: 2 }],
|
|
199
|
+
[38, { name: 'riviera', desc: 'Riviera', types: ['pooltone'], sequence: 3 }],
|
|
200
|
+
[39, { name: 'neutralwhite', desc: 'Neutral White', types: ['pooltone'], sequence: 4 }],
|
|
201
|
+
[40, { name: 'rainbow', desc: 'Rainbow', types: ['pooltone'], sequence: 5 }],
|
|
202
|
+
[41, { name: 'colorriver', desc: 'Color River', types: ['pooltone'], sequence: 6 }],
|
|
203
|
+
[42, { name: 'disco', desc: 'Disco', types: ['pooltone'], sequence: 7 }],
|
|
204
|
+
[43, { name: 'fourseasons', desc: 'Four Seasons', types: ['pooltone'], sequence: 8 }],
|
|
205
|
+
[44, { name: 'Party', desc: 'Party', types: ['pooltone'], sequence: 9 }],
|
|
206
|
+
[45, { name: 'sunwhite', desc: 'Sun White', types: ['pooltone'], sequence: 10 }],
|
|
207
|
+
[46, { name: 'red', desc: 'Red', types: ['pooltone'], sequence: 11 }],
|
|
208
|
+
[47, { name: 'green', desc: 'Green', types: ['pooltone'], sequence: 12 }],
|
|
209
|
+
[48, { name: 'blue', desc: 'Blue', types: ['pooltone'], sequence: 13 }],
|
|
210
|
+
[49, { name: 'greenblue', desc: 'Green-Blue', types: ['pooltone'], sequence: 14 }],
|
|
211
|
+
[50, { name: 'redgreen', desc: 'Red-Green', types: ['pooltone'], sequence: 15 }],
|
|
212
|
+
[51, { name: 'bluered', desc: 'Blue-red', types: ['pooltone'], sequence: 16 }],
|
|
177
213
|
[255, { name: 'none', desc: 'None' }]
|
|
178
214
|
]);
|
|
179
215
|
this.valueMaps.lightColors = new byteValueMap([
|
|
@@ -202,8 +238,10 @@ export class NixieBoard extends SystemBoard {
|
|
|
202
238
|
[1, { name: 'heater', desc: 'Heater' }],
|
|
203
239
|
[2, { name: 'solar', desc: 'Solar' }],
|
|
204
240
|
[3, { name: 'cooling', desc: 'Cooling' }],
|
|
241
|
+
[6, { name: 'mtheat', desc: 'Heater' }],
|
|
205
242
|
[4, { name: 'hpheat', desc: 'Heating' }],
|
|
206
|
-
[8, { name: 'hpcool', desc: 'Cooling' }]
|
|
243
|
+
[8, { name: 'hpcool', desc: 'Cooling' }],
|
|
244
|
+
[128, {name: 'cooldown', desc: 'Cooldown'}]
|
|
207
245
|
]);
|
|
208
246
|
this.valueMaps.scheduleTypes = new byteValueMap([
|
|
209
247
|
[0, { name: 'runonce', desc: 'Run Once', startDate: true, startTime: true, endTime: true, days: false, heatSource: true, heatSetpoint: true }],
|
|
@@ -429,7 +467,7 @@ export class NixieBoard extends SystemBoard {
|
|
|
429
467
|
//public chlorinator: NixieChlorinatorCommands = new NixieChlorinatorCommands(this);
|
|
430
468
|
public bodies: NixieBodyCommands = new NixieBodyCommands(this);
|
|
431
469
|
public filters: NixieFilterCommands = new NixieFilterCommands(this);
|
|
432
|
-
|
|
470
|
+
public pumps: NixiePumpCommands = new NixiePumpCommands(this);
|
|
433
471
|
//public schedules: NixieScheduleCommands = new NixieScheduleCommands(this);
|
|
434
472
|
public heaters: NixieHeaterCommands = new NixieHeaterCommands(this);
|
|
435
473
|
public valves: NixieValveCommands = new NixieValveCommands(this);
|
|
@@ -456,12 +494,24 @@ export class NixieFilterCommands extends FilterCommands {
|
|
|
456
494
|
try {
|
|
457
495
|
await ncp.filters.setFilterStateAsync(fstate, isOn);
|
|
458
496
|
}
|
|
459
|
-
catch (err) { return Promise.reject(`Nixie: Error setFiterStateAsync ${err.message}
|
|
497
|
+
catch (err) { return Promise.reject(new BoardProcessError(`Nixie: Error setFiterStateAsync ${err.message}`, 'setFilterStateAsync')); }
|
|
460
498
|
}
|
|
461
499
|
}
|
|
462
|
-
|
|
463
500
|
export class NixieSystemCommands extends SystemCommands {
|
|
464
|
-
public cancelDelay(): Promise<any> {
|
|
501
|
+
public cancelDelay(): Promise<any> {
|
|
502
|
+
delayMgr.cancelPumpValveDelays();
|
|
503
|
+
delayMgr.cancelHeaterCooldownDelays();
|
|
504
|
+
delayMgr.cancelHeaterStartupDelays();
|
|
505
|
+
delayMgr.cancelCleanerStartDelays();
|
|
506
|
+
delayMgr.cancelManualPriorityDelays();
|
|
507
|
+
state.delay = sys.board.valueMaps.delay.getValue('nodelay');
|
|
508
|
+
return Promise.resolve(state.data.delay);
|
|
509
|
+
}
|
|
510
|
+
public setManualOperationPriority(id: number): Promise<any> {
|
|
511
|
+
let cstate = state.circuits.getInterfaceById(id);
|
|
512
|
+
delayMgr.setManualPriorityDelay(cstate);
|
|
513
|
+
return Promise.resolve(cstate);
|
|
514
|
+
}
|
|
465
515
|
public setDateTimeAsync(obj: any): Promise<any> { return Promise.resolve(); }
|
|
466
516
|
public getDOW() { return this.board.valueMaps.scheduleDays.toArray(); }
|
|
467
517
|
public async setGeneralAsync(obj: any): Promise<General> {
|
|
@@ -480,7 +530,20 @@ export class NixieSystemCommands extends SystemCommands {
|
|
|
480
530
|
}
|
|
481
531
|
}
|
|
482
532
|
export class NixieCircuitCommands extends CircuitCommands {
|
|
483
|
-
|
|
533
|
+
// This is our poll loop for circuit relay states.
|
|
534
|
+
public async syncCircuitRelayStates() {
|
|
535
|
+
try {
|
|
536
|
+
for (let i = 0; i < sys.circuits.length; i++) {
|
|
537
|
+
// Run through all the controlled circuits to see whether they should be triggered or not.
|
|
538
|
+
let circ = sys.circuits.getItemByIndex(i);
|
|
539
|
+
if (circ.master === 1 && circ.isActive) {
|
|
540
|
+
let cstate = state.circuits.getItemById(circ.id);
|
|
541
|
+
if (cstate.isOn) await ncp.circuits.setCircuitStateAsync(cstate, cstate.isOn);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
} catch (err) { logger.error(`syncCircuitRelayStates: Error synchronizing circuit relays ${err.message}`); }
|
|
545
|
+
}
|
|
546
|
+
public async setCircuitStateAsync(id: number, val: boolean, ignoreDelays?: boolean): Promise<ICircuitState> {
|
|
484
547
|
sys.board.suspendStatus(true);
|
|
485
548
|
try {
|
|
486
549
|
// We need to do some routing here as it is now critical that circuits, groups, and features
|
|
@@ -490,48 +553,340 @@ export class NixieCircuitCommands extends CircuitCommands {
|
|
|
490
553
|
else if (sys.board.equipmentIds.features.isInRange(id))
|
|
491
554
|
return await sys.board.features.setFeatureStateAsync(id, val);
|
|
492
555
|
|
|
556
|
+
|
|
493
557
|
let circuit: ICircuit = sys.circuits.getInterfaceById(id, false, { isActive: false });
|
|
494
558
|
if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError(`Circuit or Feature id ${id} not valid`, id, 'Circuit'));
|
|
495
559
|
let circ = state.circuits.getInterfaceById(id, circuit.isActive !== false);
|
|
560
|
+
if (circ.stopDelay) {
|
|
561
|
+
// Send this off so that the relays are properly set. In the end we cannot change right now. If this
|
|
562
|
+
// happens to be a body circuit then the relay state will be skipped anyway.
|
|
563
|
+
await ncp.circuits.setCircuitStateAsync(circ, circ.isOn);
|
|
564
|
+
return circ;
|
|
565
|
+
}
|
|
496
566
|
let newState = utils.makeBool(val);
|
|
497
|
-
|
|
498
|
-
//
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
567
|
+
let ctype = sys.board.valueMaps.circuitFunctions.getName(circ.type);
|
|
568
|
+
// Filter out any special circuit types.
|
|
569
|
+
switch (ctype) {
|
|
570
|
+
case 'pool':
|
|
571
|
+
case 'spa':
|
|
572
|
+
await this.setBodyCircuitStateAsync(id, newState, ignoreDelays);
|
|
573
|
+
break;
|
|
574
|
+
case 'mastercleaner':
|
|
575
|
+
case 'mastercleaner2':
|
|
576
|
+
await this.setCleanerCircuitStateAsync(id, newState, ignoreDelays);
|
|
577
|
+
break;
|
|
578
|
+
case 'spillway':
|
|
579
|
+
await this.setSpillwayCircuitStateAsync(id, newState, ignoreDelays);
|
|
580
|
+
break;
|
|
581
|
+
case 'spadrain':
|
|
582
|
+
await this.setDrainCircuitStateAsync(id, newState, ignoreDelays);
|
|
583
|
+
break;
|
|
584
|
+
default:
|
|
585
|
+
await ncp.circuits.setCircuitStateAsync(circ, newState);
|
|
586
|
+
await sys.board.processStatusAsync();
|
|
587
|
+
break;
|
|
588
|
+
}
|
|
589
|
+
// Let the main nixie controller set the circuit state and affect the relays if it needs to.
|
|
590
|
+
return state.circuits.getInterfaceById(circ.id);
|
|
591
|
+
}
|
|
592
|
+
catch (err) { logger.error(`Nixie: setCircuitState ${err.message}`); return Promise.reject(new BoardProcessError(`Nixie: Error setCircuitStateAsync ${err.message}`, 'setCircuitState')); }
|
|
593
|
+
finally {
|
|
594
|
+
state.emitEquipmentChanges();
|
|
595
|
+
ncp.pumps.syncPumpStates();
|
|
596
|
+
sys.board.suspendStatus(false);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
protected async setCleanerCircuitStateAsync(id: number, val: boolean, ignoreDelays?: boolean): Promise<ICircuitState> {
|
|
600
|
+
try {
|
|
601
|
+
let cstate = state.circuits.getItemById(id);
|
|
602
|
+
let circuit = sys.circuits.getItemById(id);
|
|
603
|
+
// We know which body the cleaner belongs to by an attribute on the circuit function.
|
|
604
|
+
let ctype = sys.board.valueMaps.circuitFunctions.get(circuit.type);
|
|
605
|
+
let bstate = state.temps.bodies.getItemById(ctype.body || 1);
|
|
606
|
+
// Cleaner lockout should occur when
|
|
607
|
+
// 1. The body circuit is off.
|
|
608
|
+
// 2. The spillway mode is running.
|
|
609
|
+
|
|
610
|
+
// Optional modes include
|
|
611
|
+
// 1. The current body is heating with solar.
|
|
612
|
+
|
|
613
|
+
// Lockouts are cleared when
|
|
614
|
+
// 1. The above conditions are no longer true.
|
|
615
|
+
// 2. The user requests the circuit to be off.
|
|
616
|
+
if (!val) {
|
|
617
|
+
// We can always turn a cleaner circuit off. Even if a delay is underway.
|
|
618
|
+
delayMgr.clearCleanerStartDelays(bstate.id);
|
|
619
|
+
await ncp.circuits.setCircuitStateAsync(cstate, false);
|
|
620
|
+
}
|
|
621
|
+
else if (val) {
|
|
622
|
+
logger.info(`Setting cleaner circuit ${cstate.name} to ${val}`);
|
|
623
|
+
// Alright we are turning the cleaner on.
|
|
624
|
+
// To turn on the cleaner circuit we must first ensure the body is on. If it is not then we abort.
|
|
625
|
+
if (!bstate.isOn) {
|
|
626
|
+
logger.info(`Cannot turn on cleaner circuit ${cstate.name}. ${bstate.name} is not running`);
|
|
627
|
+
await ncp.circuits.setCircuitStateAsync(cstate, false);
|
|
628
|
+
return cstate;
|
|
629
|
+
}
|
|
630
|
+
// If there is a drain circuit going shut that thing off.
|
|
631
|
+
await this.turnOffDrainCircuits(ignoreDelays);
|
|
632
|
+
// If solar is currently on and the cleaner solar delay is set then we need to calculate a delay
|
|
633
|
+
// to turn on the cleaner.
|
|
634
|
+
let delayTime = 0;
|
|
635
|
+
let dtNow = new Date().getTime();
|
|
636
|
+
if (typeof ignoreDelays === 'undefined' || !ignoreDelays) {
|
|
637
|
+
if (sys.general.options.cleanerSolarDelay && sys.general.options.cleanerSolarDelayTime > 0) {
|
|
638
|
+
let circBody = state.circuits.getItemById(bstate.circuit);
|
|
639
|
+
// If the body has not been on or the solar heater has not been on long enough then we need to delay the startup.
|
|
640
|
+
if (sys.board.valueMaps.heatStatus.getName(bstate.heatStatus) === 'solar') {
|
|
641
|
+
// Check for the solar delay. We need to know when the heater first kicked in. A cleaner and solar
|
|
642
|
+
// heater can run at the same time but the heater must be on long enough for the timer to expire.
|
|
643
|
+
|
|
644
|
+
// The reasoning behind this is so that the booster pump can be assured that there is sufficient pressure
|
|
645
|
+
// for it to start and any air from the solar has had time to purge through the system.
|
|
646
|
+
let heaters = sys.heaters.getSolarHeaters(bstate.id);
|
|
647
|
+
let startTime = 0;
|
|
648
|
+
for (let i = 0; i < heaters.length; i++) {
|
|
649
|
+
let heater = heaters.getItemByIndex(i);
|
|
650
|
+
let hstate = state.heaters.getItemById(heater.id);
|
|
651
|
+
startTime = Math.max(startTime, hstate.startTime.getTime());
|
|
652
|
+
}
|
|
653
|
+
// Lets see if we have a solar start delay.
|
|
654
|
+
delayTime = Math.max(Math.round(((sys.general.options.cleanerSolarDelayTime * 1000) - (dtNow - startTime))) / 1000, delayTime);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
if (sys.general.options.cleanerStartDelay && sys.general.options.cleanerStartDelayTime) {
|
|
658
|
+
let bcstate = state.circuits.getItemById(bstate.circuit);
|
|
659
|
+
// So we should be started. Lets determine whethere there should be any delay.
|
|
660
|
+
delayTime = Math.max(Math.round(((sys.general.options.cleanerStartDelayTime * 1000) - (dtNow - bcstate.startTime.getTime())) / 1000), delayTime);
|
|
661
|
+
logger.info(`Cleaner delay time calculated to ${delayTime}`);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
if (delayTime > 5) delayMgr.setCleanerStartDelay(cstate, bstate.id, delayTime);
|
|
665
|
+
else await ncp.circuits.setCircuitStateAsync(cstate, true);
|
|
666
|
+
}
|
|
667
|
+
return cstate;
|
|
668
|
+
} catch (err) { return Promise.reject(new BoardProcessError(`Nixie: Error setting cleaner circuit state: ${err.message}`, 'setCleanerCircuitStateAsync')); }
|
|
669
|
+
}
|
|
670
|
+
protected async setBodyCircuitStateAsync(id: number, val: boolean, ignoreDelays?: boolean): Promise<CircuitState> {
|
|
671
|
+
try {
|
|
672
|
+
let cstate = state.circuits.getItemById(id);
|
|
673
|
+
let circuit = sys.circuits.getItemById(id);
|
|
674
|
+
let bstate = state.temps.bodies.getBodyByCircuitId(id);
|
|
675
|
+
if (val) {
|
|
676
|
+
// We are turning on a body circuit.
|
|
677
|
+
logger.verbose(`Turning on a body circuit ${bstate.name}`);
|
|
502
678
|
if (sys.equipment.shared === true) {
|
|
679
|
+
// If we are turning on and this is a shared system it means that we need to turn off
|
|
680
|
+
// the other circuit.
|
|
681
|
+
let delayPumps = false;
|
|
682
|
+
await this.turnOffDrainCircuits(ignoreDelays);
|
|
683
|
+
if (bstate.id === 2) await this.turnOffSpillwayCircuits();
|
|
684
|
+
if (sys.general.options.pumpDelay === true && ignoreDelays !== true) {
|
|
685
|
+
// Now that this is off check the valve positions. If they are not currently in the correct position we need to delay any attached pump
|
|
686
|
+
// so that it does not come on while the valve is rotating. Default 30 seconds.
|
|
687
|
+
let iValves = sys.valves.getIntake();
|
|
688
|
+
for (let i = 0; i < iValves.length && !delayPumps; i++) {
|
|
689
|
+
let vstate = state.valves.getItemById(iValves[i].id);
|
|
690
|
+
if (vstate.isDiverted === true && circuit.type === 12) delayPumps = true;
|
|
691
|
+
else if (vstate.isDiverted === false && circuit.type === 13) delayPumps = true;
|
|
692
|
+
}
|
|
693
|
+
if (!delayPumps) {
|
|
694
|
+
let rValves = sys.valves.getReturn();
|
|
695
|
+
for (let i = 0; i < rValves.length && !delayPumps; i++) {
|
|
696
|
+
let vstate = state.valves.getItemById(rValves[i].id);
|
|
697
|
+
if (vstate.isDiverted === true && circuit.type === 12) delayPumps = true;
|
|
698
|
+
else if (vstate.isDiverted === false && circuit.type === 13) delayPumps = true;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
503
702
|
// If we are shared we need to turn off the other circuit.
|
|
504
|
-
let offType =
|
|
703
|
+
let offType = circuit.type === 12 ? 13 : 12;
|
|
505
704
|
let off = sys.circuits.get().filter(elem => elem.type === offType);
|
|
705
|
+
let delayCooldown = false;
|
|
506
706
|
// Turn the circuits off that are part of the shared system. We are going back to the board
|
|
507
707
|
// just in case we got here for a circuit that isn't on the current defined panel.
|
|
508
708
|
for (let i = 0; i < off.length; i++) {
|
|
509
709
|
let coff = off[i];
|
|
510
|
-
|
|
511
|
-
|
|
710
|
+
let bsoff = state.temps.bodies.getBodyByCircuitId(coff.id);
|
|
711
|
+
let csoff = state.circuits.getItemById(coff.id);
|
|
712
|
+
// Ensure the cleaner circuits for this body are off.
|
|
713
|
+
await this.turnOffCleanerCircuits(bsoff);
|
|
714
|
+
if (csoff.isOn) {
|
|
715
|
+
logger.verbose(`Turning off shared body ${coff.name} circuit`);
|
|
716
|
+
delayMgr.clearBodyStartupDelay(bsoff);
|
|
717
|
+
if (bsoff.heaterCooldownDelay && ignoreDelays !== true) {
|
|
718
|
+
// In this condition we are requesting that the shared body start when the cooldown delay
|
|
719
|
+
// has finished. This will add this request to the cooldown delay code. The setHeaterCooldownDelay
|
|
720
|
+
// code is expected to be re-entrant and checks the id so that it does not clear
|
|
721
|
+
// the original request if it is asked for again.
|
|
722
|
+
|
|
723
|
+
// NOTE: There is room for improvement here. For instance, if the result
|
|
724
|
+
// of turning on the circuit is that the heater(s) requiring cooldown will result in being on
|
|
725
|
+
// then why not cancel the current cooldown cycle and let the user get on with it.
|
|
726
|
+
// Consider:
|
|
727
|
+
// 1. Check each heater attached to the off body to see if it is also attached to the on body.
|
|
728
|
+
// 2. If the heater is attached check to see if there is any cooldown time left on it.
|
|
729
|
+
// 3. If the above conditions are true cancel the cooldown cycle.
|
|
730
|
+
logger.verbose(`${bsoff.name} is already in Cooldown mode`);
|
|
731
|
+
delayMgr.setHeaterCooldownDelay(bsoff, bstate);
|
|
732
|
+
delayCooldown = true;
|
|
733
|
+
}
|
|
734
|
+
else {
|
|
735
|
+
// We need to deal with heater cooldown delays here since you cannot turn off the body while the heater is
|
|
736
|
+
// cooling down. This means we need to check to see if the heater requires cooldown then set a delay for it
|
|
737
|
+
// if it does. The delay manager will shut the body off and start the new body when it is done.
|
|
738
|
+
let heaters = sys.board.heaters.getHeatersByCircuitId(circuit.id);
|
|
739
|
+
let cooldownTime = 0;
|
|
740
|
+
if (ignoreDelays !== true) {
|
|
741
|
+
for (let j = 0; j < heaters.length; j++) {
|
|
742
|
+
let nheater = ncp.heaters.find(x => x.id === heaters[j].id) as NixieHeaterBase;
|
|
743
|
+
cooldownTime = Math.max(nheater.getCooldownTime(), cooldownTime);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
if (cooldownTime > 0) {
|
|
747
|
+
// We need do start a cooldown cycle for the body. If there is already
|
|
748
|
+
// a cooldown underway this will append the on to it.
|
|
749
|
+
delayMgr.setHeaterCooldownDelay(bsoff, bstate, cooldownTime * 1000);
|
|
750
|
+
delayCooldown = true;
|
|
751
|
+
}
|
|
752
|
+
else {
|
|
753
|
+
await ncp.circuits.setCircuitStateAsync(csoff, false);
|
|
754
|
+
bsoff.isOn = false;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
if (delayCooldown) return cstate;
|
|
760
|
+
if (delayPumps === true) sys.board.pumps.setPumpValveDelays([id, bstate.circuit]);
|
|
761
|
+
}
|
|
762
|
+
// Now we need to set the startup delay for all the heaters. This is true whether
|
|
763
|
+
// the system is shared or not so lets get a list of all the associated heaters for the body in question.
|
|
764
|
+
if (sys.general.options.heaterStartDelay && sys.general.options.heaterStartDelayTime > 0) {
|
|
765
|
+
let heaters = sys.board.heaters.getHeatersByCircuitId(circuit.id);
|
|
766
|
+
for (let j = 0; j < heaters.length; j++) {
|
|
767
|
+
let hstate = state.heaters.getItemById(heaters[j].id);
|
|
768
|
+
delayMgr.setHeaterStartupDelay(hstate);
|
|
512
769
|
}
|
|
513
770
|
}
|
|
514
|
-
|
|
771
|
+
await ncp.circuits.setCircuitStateAsync(cstate, val);
|
|
772
|
+
bstate.isOn = val;
|
|
515
773
|
}
|
|
516
|
-
if (
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
774
|
+
else if (!val) {
|
|
775
|
+
// Alright we are turning off a circuit that will result in a body shutting off. If this
|
|
776
|
+
// circuit is already under delay it should have been processed out earlier.
|
|
777
|
+
delayMgr.cancelPumpValveDelays();
|
|
778
|
+
delayMgr.cancelHeaterStartupDelays();
|
|
779
|
+
if (cstate.startDelay) delayMgr.clearBodyStartupDelay(bstate);
|
|
780
|
+
await this.turnOffCleanerCircuits(bstate);
|
|
781
|
+
if (sys.equipment.shared && bstate.id === 2) await this.turnOffDrainCircuits(ignoreDelays);
|
|
782
|
+
logger.verbose(`Turning off a body circuit ${circuit.name}`);
|
|
783
|
+
if (cstate.isOn) {
|
|
784
|
+
|
|
785
|
+
// Check to see if we have any heater cooldown delays that need to take place.
|
|
786
|
+
let heaters = sys.board.heaters.getHeatersByCircuitId(circuit.id);
|
|
787
|
+
let cooldownTime = 0;
|
|
788
|
+
for (let j = 0; j < heaters.length; j++) {
|
|
789
|
+
let nheater = ncp.heaters.find(x => x.id === heaters[j].id) as NixieHeaterBase;
|
|
790
|
+
cooldownTime = Math.max(nheater.getCooldownTime(), cooldownTime);
|
|
791
|
+
}
|
|
792
|
+
if (cooldownTime > 0) {
|
|
793
|
+
logger.info(`Starting a Cooldown Delay ${cooldownTime}sec`);
|
|
794
|
+
// We need do start a cooldown cycle for the body.
|
|
795
|
+
delayMgr.setHeaterCooldownDelay(bstate, undefined, cooldownTime * 1000);
|
|
796
|
+
}
|
|
797
|
+
else {
|
|
798
|
+
await ncp.circuits.setCircuitStateAsync(cstate, val);
|
|
799
|
+
bstate.isOn = val;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
return cstate;
|
|
804
|
+
} catch (err) { logger.error(`Nixie: Error setBodyCircuitStateAsync ${err.message}`); return Promise.reject(new BoardProcessError(`Nixie: Error setBodyCircuitStateAsync ${err.message}`, 'setBodyCircuitStateAsync')); }
|
|
529
805
|
}
|
|
806
|
+
protected async setSpillwayCircuitStateAsync(id: number, val: boolean, ignoreDelays?: boolean): Promise<CircuitState> {
|
|
807
|
+
try {
|
|
808
|
+
let cstate = state.circuits.getItemById(id);
|
|
809
|
+
let delayPumps = false;
|
|
810
|
+
if (cstate.isOn !== val) {
|
|
811
|
+
if (sys.equipment.shared === true) {
|
|
812
|
+
// First we need to check to see if the pool is on.
|
|
813
|
+
if (val) {
|
|
814
|
+
let spastate = state.circuits.getItemById(1);
|
|
815
|
+
if (spastate.isOn) {
|
|
816
|
+
logger.warn(`Cannot turn ${cstate.name} on because ${spastate.name} is on`);
|
|
817
|
+
return cstate;
|
|
818
|
+
}
|
|
819
|
+
// If there are any drain circuits or features that are currently engaged we need to turn them off.
|
|
820
|
+
await this.turnOffDrainCircuits(ignoreDelays);
|
|
821
|
+
if (sys.general.options.pumpDelay && sys.general.options.valveDelayTime > 0) sys.board.pumps.setPumpValveDelays([6, id]);
|
|
822
|
+
}
|
|
823
|
+
else if (!val && !ignoreDelays) {
|
|
824
|
+
// If we are turning off and there is another circuit that ties to the same pumps then we need set a valve delay. This means
|
|
825
|
+
// that if the pool circuit is on then we need to delay the pumps. However, if there is no other circuit that needs
|
|
826
|
+
// the pump to be on, then no harm no foul a delay in the pump won't mean anything.
|
|
827
|
+
|
|
828
|
+
// Conditions where this should not delay.
|
|
829
|
+
// 1. Another spillway circuit or feature is on.
|
|
830
|
+
// 2. There is no other running circuit that will affect the intake or return.
|
|
831
|
+
let arrIds = sys.board.valves.getBodyValveCircuitIds(true);
|
|
832
|
+
if (arrIds.length > 1) {
|
|
833
|
+
if (sys.general.options.pumpDelay && sys.general.options.valveDelayTime > 0) {
|
|
834
|
+
sys.board.pumps.setPumpValveDelays([6, id]);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
logger.verbose(`Turning ${val ? 'on' : 'off'} a spillway circuit ${cstate.name}`);
|
|
841
|
+
await ncp.circuits.setCircuitStateAsync(cstate, val);
|
|
842
|
+
return cstate;
|
|
843
|
+
} catch (err) { logger.error(`Nixie: Error setSpillwayCircuitStateAsync ${err.message}`); return Promise.reject(new BoardProcessError(`Nixie: Error setSpillwayCircuitStateAsync ${err.message}`, 'setBodyCircuitStateAsync')); }
|
|
844
|
+
}
|
|
845
|
+
protected async setDrainCircuitStateAsync(id: number, val: boolean, ignoreDelays?: boolean): Promise<CircuitState> {
|
|
846
|
+
try {
|
|
847
|
+
// Drain circuits can be very bad. This is because they can be turned on then never turned off
|
|
848
|
+
// we may want to create some limits are to how long they can be on or even force them off
|
|
849
|
+
// if for instance the spa is not on.
|
|
850
|
+
// RULES FOR DRAIN CIRCUITS:
|
|
851
|
+
// 1. All spillway circuits must be off.
|
|
852
|
+
let cstate = state.circuits.getItemById(id);
|
|
853
|
+
let delayPumps = false;
|
|
854
|
+
if (cstate.isOn !== val) {
|
|
855
|
+
if (sys.equipment.shared === true) {
|
|
856
|
+
let spastate = state.temps.bodies.getItemById(2);
|
|
857
|
+
let poolstate = state.temps.bodies.getItemById(1);
|
|
858
|
+
// First we need to check to see if the pool is on.
|
|
859
|
+
if (val) {
|
|
860
|
+
if (spastate.isOn || spastate.startDelay || poolstate.isOn || poolstate.startDelay) {
|
|
861
|
+
logger.warn(`Cannot turn ${cstate.name} on because a body is on`);
|
|
862
|
+
return cstate;
|
|
863
|
+
}
|
|
864
|
+
// If there are any spillway circuits or features that are currently engaged we need to turn them off.
|
|
865
|
+
await this.turnOffSpillwayCircuits(true);
|
|
866
|
+
// If there are any cleaner circuits on for the main body turn them off.
|
|
867
|
+
await this.turnOffCleanerCircuits(state.temps.bodies.getItemById(1), true);
|
|
868
|
+
if (!ignoreDelays && sys.general.options.pumpDelay && sys.general.options.valveDelayTime > 0) sys.board.pumps.setPumpValveDelays([id, 1, 6]);
|
|
869
|
+
}
|
|
870
|
+
else if (!val && !ignoreDelays) {
|
|
871
|
+
if (!ignoreDelays && sys.general.options.pumpDelay && sys.general.options.valveDelayTime > 0) sys.board.pumps.setPumpValveDelays([id, 1, 6]);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
logger.verbose(`Turning ${val ? 'on' : 'off'} a drain circuit ${cstate.name}`);
|
|
876
|
+
await ncp.circuits.setCircuitStateAsync(cstate, val);
|
|
877
|
+
return cstate;
|
|
878
|
+
} catch (err) { logger.error(`Nixie: Error setSpillwayCircuitStateAsync ${err.message}`); return Promise.reject(new BoardProcessError(`Nixie: Error setBodyCircuitStateAsync ${err.message}`, 'setBodyCircuitStateAsync')); }
|
|
879
|
+
}
|
|
880
|
+
|
|
530
881
|
public toggleCircuitStateAsync(id: number): Promise<ICircuitState> {
|
|
531
882
|
let circ = state.circuits.getInterfaceById(id);
|
|
532
883
|
return this.setCircuitStateAsync(id, !(circ.isOn || false));
|
|
533
884
|
}
|
|
534
885
|
public async setLightThemeAsync(id: number, theme: number) {
|
|
886
|
+
if (sys.board.equipmentIds.circuitGroups.isInRange(id)) {
|
|
887
|
+
await this.setLightGroupThemeAsync(id, theme);
|
|
888
|
+
return Promise.resolve(state.lightGroups.getItemById(id));
|
|
889
|
+
}
|
|
535
890
|
let cstate = state.circuits.getItemById(id);
|
|
536
891
|
let circ = sys.circuits.getItemById(id);
|
|
537
892
|
let thm = sys.board.valueMaps.lightThemes.findItem(theme);
|
|
@@ -605,7 +960,11 @@ export class NixieCircuitCommands extends CircuitCommands {
|
|
|
605
960
|
}
|
|
606
961
|
return arr;
|
|
607
962
|
}
|
|
608
|
-
public getCircuitFunctions() {
|
|
963
|
+
public getCircuitFunctions() {
|
|
964
|
+
let cf = sys.board.valueMaps.circuitFunctions.toArray();
|
|
965
|
+
if (!sys.equipment.shared) cf = cf.filter(x => { return x.name !== 'spillway' && x.name !== 'spadrain' });
|
|
966
|
+
return cf;
|
|
967
|
+
}
|
|
609
968
|
public getCircuitNames() {
|
|
610
969
|
return [...sys.board.valueMaps.circuitNames.toArray(), ...sys.board.valueMaps.customNames.toArray()];
|
|
611
970
|
}
|
|
@@ -631,6 +990,8 @@ export class NixieCircuitCommands extends CircuitCommands {
|
|
|
631
990
|
if (typeof data.connectionId !== 'undefined') circuit.connectionId = data.connectionId;
|
|
632
991
|
if (typeof data.deviceBinding !== 'undefined') circuit.deviceBinding = data.deviceBinding;
|
|
633
992
|
circuit.dontStop = circuit.eggTimer === 1440;
|
|
993
|
+
// update end time in case egg timer is changed while circuit is on
|
|
994
|
+
sys.board.circuits.setEndTime(circuit, scircuit, scircuit.isOn, true);
|
|
634
995
|
sys.emitEquipmentChange();
|
|
635
996
|
state.emitEquipmentChanges();
|
|
636
997
|
ncp.circuits.setCircuitAsync(circuit, data);
|
|
@@ -682,6 +1043,8 @@ export class NixieCircuitCommands extends CircuitCommands {
|
|
|
682
1043
|
}
|
|
683
1044
|
group.circuits.length = obj.circuits.length; // RSG - removed as this will delete circuits that were not changed
|
|
684
1045
|
}
|
|
1046
|
+
// update end time in case group is changed while circuit is on
|
|
1047
|
+
sys.board.circuits.setEndTime(group, sgroup, sgroup.isOn, true);
|
|
685
1048
|
resolve(group);
|
|
686
1049
|
});
|
|
687
1050
|
|
|
@@ -810,42 +1173,33 @@ export class NixieCircuitCommands extends CircuitCommands {
|
|
|
810
1173
|
public async setLightGroupThemeAsync(id: number, theme: number): Promise<ICircuitState> {
|
|
811
1174
|
const grp = sys.lightGroups.getItemById(id);
|
|
812
1175
|
const sgrp = state.lightGroups.getItemById(id);
|
|
813
|
-
grp.lightingTheme = sgrp.lightingTheme = theme;
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
1176
|
+
//grp.lightingTheme = sgrp.lightingTheme = theme;
|
|
1177
|
+
let thm = sys.board.valueMaps.lightThemes.transform(theme);
|
|
1178
|
+
sgrp.action = sys.board.valueMaps.circuitActions.getValue('lighttheme');
|
|
1179
|
+
|
|
1180
|
+
try {
|
|
1181
|
+
// Go through and set the theme for all lights in the group.
|
|
1182
|
+
for (let i = 0; i < grp.circuits.length; i++) {
|
|
1183
|
+
let c = grp.circuits.getItemByIndex(i);
|
|
1184
|
+
//let cstate = state.circuits.getItemById(c.circuit);
|
|
1185
|
+
await sys.board.circuits.setLightThemeAsync(c.circuit, theme);
|
|
819
1186
|
await sys.board.circuits.setCircuitStateAsync(c.circuit, false);
|
|
820
|
-
|
|
1187
|
+
}
|
|
1188
|
+
await utils.sleep(5000);
|
|
1189
|
+
// Turn the circuits all back on again.
|
|
1190
|
+
for (let i = 0; i < grp.circuits.length; i++) {
|
|
1191
|
+
let c = grp.circuits.getItemByIndex(i);
|
|
1192
|
+
//let cstate = state.circuits.getItemById(c.circuit);
|
|
1193
|
+
await sys.board.circuits.setCircuitStateAsync(c.circuit, true);
|
|
1194
|
+
}
|
|
1195
|
+
sgrp.lightingTheme = theme;
|
|
1196
|
+
return sgrp;
|
|
821
1197
|
}
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
case 0: // off
|
|
827
|
-
case 1: // on
|
|
828
|
-
break;
|
|
829
|
-
case 128: // sync
|
|
830
|
-
setImmediate(function () { sys.board.circuits.sequenceLightGroupAsync(grp.id, 'sync'); });
|
|
831
|
-
break;
|
|
832
|
-
case 144: // swim
|
|
833
|
-
setImmediate(function () { sys.board.circuits.sequenceLightGroupAsync(grp.id, 'swim'); });
|
|
834
|
-
break;
|
|
835
|
-
case 160: // swim
|
|
836
|
-
setImmediate(function () { sys.board.circuits.sequenceLightGroupAsync(grp.id, 'set'); });
|
|
837
|
-
break;
|
|
838
|
-
case 190: // save
|
|
839
|
-
case 191: // recall
|
|
840
|
-
setImmediate(function () { sys.board.circuits.sequenceLightGroupAsync(grp.id, 'other'); });
|
|
841
|
-
break;
|
|
842
|
-
default:
|
|
843
|
-
setImmediate(function () { sys.board.circuits.sequenceLightGroupAsync(grp.id, 'color'); });
|
|
844
|
-
// other themes for magicstream?
|
|
1198
|
+
catch (err) { return Promise.reject(err); }
|
|
1199
|
+
finally {
|
|
1200
|
+
sgrp.action = 0;
|
|
1201
|
+
sgrp.emitEquipmentChange();
|
|
845
1202
|
}
|
|
846
|
-
sgrp.hasChanged = true; // Say we are dirty but we really are pure as the driven snow.
|
|
847
|
-
state.emitEquipmentChanges();
|
|
848
|
-
return Promise.resolve(sgrp);
|
|
849
1203
|
}
|
|
850
1204
|
public async setLightGroupAttribsAsync(group: LightGroup): Promise<LightGroup> {
|
|
851
1205
|
let grp = sys.lightGroups.getItemById(group.id);
|
|
@@ -861,20 +1215,54 @@ export class NixieCircuitCommands extends CircuitCommands {
|
|
|
861
1215
|
}
|
|
862
1216
|
catch (err) { return Promise.reject(err); }
|
|
863
1217
|
}
|
|
864
|
-
public
|
|
1218
|
+
//public async runLightCommandAsync(id: number, command: string): Promise<ICircuitState> {
|
|
1219
|
+
// let circ = sys.circuits.getItemById(id);
|
|
1220
|
+
// try {
|
|
1221
|
+
// let type = sys.board.valueMaps.circuitFunctions.transform(circ.type);
|
|
1222
|
+
// let cmd = sys.board.valueMaps.lightCommands.findItem(command);
|
|
1223
|
+
// if (typeof cmd === 'undefined') return Promise.reject(new InvalidOperationError(`Light command ${command} does not exist`, 'runLightCommandAsync'));
|
|
1224
|
+
// if (typeof cmd.sequence !== 'undefined' && circ.master === 1) {
|
|
1225
|
+
// await sys.board.circuits.setCircuitStateAsync(id, true);
|
|
1226
|
+
// await ncp.circuits.sendOnOffSequenceAsync(id, cmd.sequence);
|
|
1227
|
+
// }
|
|
1228
|
+
// return state.circuits.getItemById(id);
|
|
1229
|
+
// }
|
|
1230
|
+
// catch (err) { return Promise.reject(`Error runLightCommandAsync ${err.message}`); }
|
|
1231
|
+
//}
|
|
1232
|
+
public async sequenceLightGroupAsync(id: number, operation: string): Promise<LightGroupState> {
|
|
865
1233
|
let sgroup = state.lightGroups.getItemById(id);
|
|
866
|
-
let
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
1234
|
+
let grp = sys.lightGroups.getItemById(id);
|
|
1235
|
+
let nop = sys.board.valueMaps.circuitActions.getValue(operation);
|
|
1236
|
+
try {
|
|
1237
|
+
switch (operation) {
|
|
1238
|
+
case 'colorsync':
|
|
1239
|
+
sgroup.action = nop;
|
|
1240
|
+
sgroup.emitEquipmentChange();
|
|
1241
|
+
for (let i = 0; i < grp.circuits.length; i++) {
|
|
1242
|
+
let c = grp.circuits.getItemByIndex(i);
|
|
1243
|
+
await sys.board.circuits.setCircuitStateAsync(c.circuit, false);
|
|
1244
|
+
}
|
|
1245
|
+
await utils.sleep(10000);
|
|
1246
|
+
// Turn the circuits all back on again.
|
|
1247
|
+
for (let i = 0; i < grp.circuits.length; i++) {
|
|
1248
|
+
let c = grp.circuits.getItemByIndex(i);
|
|
1249
|
+
await sys.board.circuits.setCircuitStateAsync(c.circuit, true);
|
|
1250
|
+
}
|
|
1251
|
+
break;
|
|
1252
|
+
case 'colorset':
|
|
1253
|
+
sgroup.action = nop;
|
|
1254
|
+
sgroup.emitEquipmentChange();
|
|
1255
|
+
await utils.sleep(5000);
|
|
1256
|
+
break;
|
|
1257
|
+
case 'colorswim':
|
|
1258
|
+
sgroup.action = nop;
|
|
1259
|
+
sgroup.emitEquipmentChange();
|
|
1260
|
+
await utils.sleep(5000);
|
|
1261
|
+
break;
|
|
1262
|
+
}
|
|
1263
|
+
return sgroup;
|
|
1264
|
+
} catch (err) { return Promise.reject(err); }
|
|
1265
|
+
finally { sgroup.action = 0; sgroup.emitEquipmentChange(); }
|
|
878
1266
|
}
|
|
879
1267
|
public async setCircuitGroupStateAsync(id: number, val: boolean): Promise<ICircuitGroupState> {
|
|
880
1268
|
let grp = sys.circuitGroups.getItemById(id, false, { isActive: false });
|
|
@@ -892,19 +1280,21 @@ export class NixieCircuitCommands extends CircuitCommands {
|
|
|
892
1280
|
if (circuit.desiredState === 1) cval = val ? true : false;
|
|
893
1281
|
else if (circuit.desiredState === 2) cval = val ? false : true;
|
|
894
1282
|
else continue;
|
|
895
|
-
|
|
1283
|
+
await sys.board.circuits.setCircuitStateAsync(circuit.circuit, cval);
|
|
1284
|
+
//arr.push(sys.board.circuits.setCircuitStateAsync(circuit.circuit, cval));
|
|
896
1285
|
}
|
|
897
|
-
return
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
1286
|
+
return state.circuitGroups.getItemById(grp.id, grp.isActive !== false);
|
|
1287
|
+
//return new Promise<ICircuitGroupState>(async (resolve, reject) => {
|
|
1288
|
+
// await Promise.all(arr).catch((err) => { reject(err) });
|
|
1289
|
+
// resolve(gstate);
|
|
1290
|
+
//});
|
|
901
1291
|
}
|
|
902
1292
|
public async setLightGroupStateAsync(id: number, val: boolean): Promise<ICircuitGroupState> {
|
|
903
1293
|
let grp = sys.circuitGroups.getItemById(id, false, { isActive: false });
|
|
904
1294
|
if (grp.dataName === 'circuitGroupConfig') return await sys.board.circuits.setCircuitGroupStateAsync(id, val);
|
|
905
1295
|
let gstate = state.lightGroups.getItemById(grp.id, grp.isActive !== false);
|
|
906
1296
|
let circuits = grp.circuits.toArray();
|
|
907
|
-
sys.board.circuits.setEndTime(
|
|
1297
|
+
sys.board.circuits.setEndTime(grp, gstate, val);
|
|
908
1298
|
gstate.isOn = val;
|
|
909
1299
|
let arr = [];
|
|
910
1300
|
for (let i = 0; i < circuits.length; i++) {
|
|
@@ -942,6 +1332,8 @@ export class NixieFeatureCommands extends FeatureCommands {
|
|
|
942
1332
|
if (typeof obj.dontStop !== 'undefined' && utils.makeBool(obj.dontStop) === true) obj.eggTimer = 1440;
|
|
943
1333
|
if (typeof obj.eggTimer !== 'undefined') feature.eggTimer = parseInt(obj.eggTimer, 10);
|
|
944
1334
|
feature.dontStop = feature.eggTimer === 1440;
|
|
1335
|
+
// update end time in case feature is changed while circuit is on
|
|
1336
|
+
sys.board.circuits.setEndTime(feature, sfeature, sfeature.isOn, true);
|
|
945
1337
|
return new Promise<Feature>((resolve, reject) => { resolve(feature); });
|
|
946
1338
|
}
|
|
947
1339
|
public async deleteFeatureAsync(obj: any): Promise<Feature> {
|
|
@@ -963,20 +1355,99 @@ export class NixieFeatureCommands extends FeatureCommands {
|
|
|
963
1355
|
else
|
|
964
1356
|
Promise.reject(new InvalidEquipmentIdError('Feature id has not been defined', undefined, 'Feature'));
|
|
965
1357
|
}
|
|
966
|
-
public async setFeatureStateAsync(id: number, val: boolean): Promise<ICircuitState> {
|
|
1358
|
+
public async setFeatureStateAsync(id: number, val: boolean, ignoreDelays?: boolean): Promise<ICircuitState> {
|
|
967
1359
|
try {
|
|
968
1360
|
if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError(`Invalid feature id: ${id}`, id, 'Feature'));
|
|
969
1361
|
if (!sys.board.equipmentIds.features.isInRange(id)) return Promise.reject(new InvalidEquipmentIdError(`Invalid feature id: ${id}`, id, 'Feature'));
|
|
970
1362
|
let feature = sys.features.getItemById(id);
|
|
971
1363
|
let fstate = state.features.getItemById(feature.id, feature.isActive !== false);
|
|
972
|
-
|
|
973
|
-
|
|
1364
|
+
feature.master = 1;
|
|
1365
|
+
let ftype = sys.board.valueMaps.featureFunctions.getName(feature.type);
|
|
1366
|
+
if(val && !fstate.isOn) sys.board.circuits.setEndTime(feature, fstate, val);
|
|
1367
|
+
switch (ftype) {
|
|
1368
|
+
case 'spadrain':
|
|
1369
|
+
this.setDrainFeatureStateAsync(id, val, ignoreDelays);
|
|
1370
|
+
break;
|
|
1371
|
+
case 'spillway':
|
|
1372
|
+
this.setSpillwayFeatureStateAsync(id, val, ignoreDelays);
|
|
1373
|
+
break;
|
|
1374
|
+
default:
|
|
1375
|
+
fstate.isOn = val;
|
|
1376
|
+
break;
|
|
1377
|
+
}
|
|
974
1378
|
sys.board.valves.syncValveStates();
|
|
975
1379
|
ncp.pumps.syncPumpStates();
|
|
1380
|
+
if (!val){
|
|
1381
|
+
if (fstate.manualPriorityActive) delayMgr.cancelManualPriorityDelay(fstate.id);
|
|
1382
|
+
fstate.manualPriorityActive = false; // if the delay was previously cancelled, still need to turn this off
|
|
1383
|
+
}
|
|
976
1384
|
state.emitEquipmentChanges();
|
|
977
1385
|
return fstate;
|
|
978
1386
|
} catch (err) { return Promise.reject(new Error(`Error setting feature state ${err.message}`)); }
|
|
979
1387
|
}
|
|
1388
|
+
protected async setSpillwayFeatureStateAsync(id: number, val: boolean, ignoreDelays?: boolean): Promise<FeatureState> {
|
|
1389
|
+
try {
|
|
1390
|
+
let cstate = state.features.getItemById(id);
|
|
1391
|
+
if (cstate.isOn !== val) {
|
|
1392
|
+
if (sys.equipment.shared === true) {
|
|
1393
|
+
let spastate = state.temps.bodies.getItemById(2);
|
|
1394
|
+
if (val) {
|
|
1395
|
+
if (spastate.isOn || spastate.startDelay) {
|
|
1396
|
+
logger.warn(`Cannot turn ${cstate.name} on because ${spastate.name} is on`);
|
|
1397
|
+
return cstate;
|
|
1398
|
+
}
|
|
1399
|
+
// If there are any drain circuits or features that are currently engaged we need to turn them off.
|
|
1400
|
+
await sys.board.circuits.turnOffDrainCircuits(ignoreDelays);
|
|
1401
|
+
if (!ignoreDelays && sys.general.options.pumpDelay && sys.general.options.valveDelayTime > 0) sys.board.pumps.setPumpValveDelays([id, 6]);
|
|
1402
|
+
}
|
|
1403
|
+
else if (!val) {
|
|
1404
|
+
let arrIds = sys.board.valves.getBodyValveCircuitIds(true);
|
|
1405
|
+
if (arrIds.length > 1) {
|
|
1406
|
+
if (!ignoreDelays && sys.general.options.pumpDelay && sys.general.options.valveDelayTime > 0) sys.board.pumps.setPumpValveDelays([id, 6]);
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
logger.verbose(`Turning ${val ? 'on' : 'off'} a spillway feature ${cstate.name}`);
|
|
1411
|
+
cstate.isOn = val;
|
|
1412
|
+
}
|
|
1413
|
+
return cstate;
|
|
1414
|
+
} catch (err) { logger.error(`Nixie: Error setSpillwayFeatureStateAsync ${err.message}`); return Promise.reject(new BoardProcessError(`Nixie: Error setSpillwayFeatureStateAsync ${err.message}`, 'setSpillwayFeatureStateAsync')); }
|
|
1415
|
+
}
|
|
1416
|
+
protected async setDrainFeatureStateAsync(id: number, val: boolean, ignoreDelays?: boolean): Promise<FeatureState> {
|
|
1417
|
+
try {
|
|
1418
|
+
// Drain circuits can be very bad. This is because they can be turned on then never turned off
|
|
1419
|
+
// we may want to create some limits are to how long they can be on or even force them off
|
|
1420
|
+
// if for instance the spa is not on.
|
|
1421
|
+
// RULES FOR DRAIN CIRCUITS:
|
|
1422
|
+
// 1. All spillway circuits must be off.
|
|
1423
|
+
let cstate = state.features.getItemById(id);
|
|
1424
|
+
if (cstate.isOn !== val) {
|
|
1425
|
+
if (sys.equipment.shared === true) {
|
|
1426
|
+
if (val) {
|
|
1427
|
+
// First we need to check to see if the pool is on.
|
|
1428
|
+
let poolstate = state.temps.bodies.getItemById(1);
|
|
1429
|
+
let spastate = state.temps.bodies.getItemById(2);
|
|
1430
|
+
if ((spastate.isOn || spastate.startDelay || poolstate.isOn || poolstate.startDelay) && val) {
|
|
1431
|
+
logger.warn(`Cannot turn ${cstate.name} on because a body circuit is on`);
|
|
1432
|
+
return cstate;
|
|
1433
|
+
}
|
|
1434
|
+
// If there are any spillway circuits or features that are currently engaged we need to turn them off.
|
|
1435
|
+
await sys.board.circuits.turnOffSpillwayCircuits(true);
|
|
1436
|
+
// If there are any cleaner circuits on for the main body turn them off.
|
|
1437
|
+
await sys.board.circuits.turnOffCleanerCircuits(state.temps.bodies.getItemById(1), true);
|
|
1438
|
+
if (!ignoreDelays && sys.general.options.pumpDelay && sys.general.options.valveDelayTime > 0) sys.board.pumps.setPumpValveDelays([id, 1, 6]);
|
|
1439
|
+
}
|
|
1440
|
+
else if (!val) {
|
|
1441
|
+
if (!ignoreDelays && sys.general.options.pumpDelay && sys.general.options.valveDelayTime > 0) sys.board.pumps.setPumpValveDelays([id, 1, 6]);
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
logger.verbose(`Turning ${val ? 'on' : 'off'} a spa drain circuit ${cstate.name}`);
|
|
1445
|
+
cstate.isOn = val;
|
|
1446
|
+
}
|
|
1447
|
+
return cstate;
|
|
1448
|
+
} catch (err) { logger.error(`Nixie: Error setSpillwayCircuitStateAsync ${err.message}`); return Promise.reject(new BoardProcessError(`Nixie: Error setBodyCircuitStateAsync ${err.message}`, 'setBodyCircuitStateAsync')); }
|
|
1449
|
+
}
|
|
1450
|
+
|
|
980
1451
|
public async toggleFeatureStateAsync(id: number): Promise<ICircuitState> {
|
|
981
1452
|
let feat = state.features.getItemById(id);
|
|
982
1453
|
return this.setFeatureStateAsync(id, !(feat.isOn || false));
|
|
@@ -1005,6 +1476,11 @@ export class NixieFeatureCommands extends FeatureCommands {
|
|
|
1005
1476
|
}
|
|
1006
1477
|
let sgrp = state.circuitGroups.getItemById(grp.id);
|
|
1007
1478
|
sgrp.isOn = bIsOn;
|
|
1479
|
+
if (sgrp.isOn && typeof sgrp.endTime === 'undefined') sys.board.circuits.setEndTime(grp, sgrp, sgrp.isOn, true);
|
|
1480
|
+
if (!sgrp.isOn && sgrp.manualPriorityActive){
|
|
1481
|
+
delayMgr.cancelManualPriorityDelays();
|
|
1482
|
+
sgrp.manualPriorityActive = false; // if the delay was previously cancelled, still need to turn this off
|
|
1483
|
+
}
|
|
1008
1484
|
}
|
|
1009
1485
|
sys.board.valves.syncValveStates();
|
|
1010
1486
|
}
|
|
@@ -1022,12 +1498,61 @@ export class NixieFeatureCommands extends FeatureCommands {
|
|
|
1022
1498
|
}
|
|
1023
1499
|
let sgrp = state.lightGroups.getItemById(grp.id);
|
|
1024
1500
|
sgrp.isOn = bIsOn;
|
|
1501
|
+
if (sgrp.isOn && typeof sgrp.endTime === 'undefined') sys.board.circuits.setEndTime(grp, sgrp, sgrp.isOn, true);
|
|
1502
|
+
if (!sgrp.isOn && sgrp.manualPriorityActive){
|
|
1503
|
+
delayMgr.cancelManualPriorityDelay(grp.id);
|
|
1504
|
+
sgrp.manualPriorityActive = false; // if the delay was previously cancelled, still need to turn this off
|
|
1505
|
+
}
|
|
1025
1506
|
}
|
|
1507
|
+
|
|
1026
1508
|
sys.board.valves.syncValveStates();
|
|
1027
1509
|
}
|
|
1028
1510
|
state.emitEquipmentChanges();
|
|
1029
1511
|
}
|
|
1512
|
+
}
|
|
1513
|
+
export class NixiePumpCommands extends PumpCommands {
|
|
1514
|
+
public async setPumpValveDelays(circuitIds: number[], delay?: number) {
|
|
1515
|
+
try {
|
|
1516
|
+
logger.info(`Setting pump valve delays: ${JSON.stringify(circuitIds)}`);
|
|
1517
|
+
// Alright now we have to delay the pumps associated with the circuit. So lets iterate all our
|
|
1518
|
+
// pump states and see where we land.
|
|
1519
|
+
for (let i = 0; i < sys.pumps.length; i++) {
|
|
1520
|
+
let pump = sys.pumps.getItemByIndex(i);
|
|
1521
|
+
let pstate = state.pumps.getItemById(pump.id);
|
|
1522
|
+
let pt = sys.board.valueMaps.pumpTypes.get(pump.type);
|
|
1030
1523
|
|
|
1524
|
+
// [1, { name: 'ss', desc: 'Single Speed', maxCircuits: 0, hasAddress: false, hasBody: true, maxRelays: 1 }],
|
|
1525
|
+
// [2, { name: 'ds', desc: 'Two Speed', maxCircuits: 8, hasAddress: false, hasBody: false, maxRelays: 2 }],
|
|
1526
|
+
// [3, { name: 'vs', desc: 'Intelliflo VS', maxPrimingTime: 6, minSpeed: 450, maxSpeed: 3450, maxCircuits: 8, hasAddress: true }],
|
|
1527
|
+
// [4, { name: 'vsf', desc: 'Intelliflo VSF', minSpeed: 450, maxSpeed: 3450, minFlow: 15, maxFlow: 130, maxCircuits: 8, hasAddress: true }],
|
|
1528
|
+
// [5, { name: 'vf', desc: 'Intelliflo VF', minFlow: 15, maxFlow: 130, maxCircuits: 8, hasAddress: true }],
|
|
1529
|
+
// [100, { name: 'sf', desc: 'SuperFlo VS', hasAddress: false, maxCircuits: 8, maxRelays: 4, equipmentMaster: 1 }]
|
|
1530
|
+
switch (pt.name) {
|
|
1531
|
+
case 'ss':
|
|
1532
|
+
// If a single speed pump is designated it will be the filter pump but we need to map any settings
|
|
1533
|
+
// to bodies.
|
|
1534
|
+
console.log(`Body: ${pump.body} Pump: ${pump.name} Pool: ${circuitIds.includes(6)} `);
|
|
1535
|
+
if ((pump.body === 255 && (circuitIds.includes(6) || circuitIds.includes(1))) ||
|
|
1536
|
+
(pump.body === 0 && circuitIds.includes(6)) ||
|
|
1537
|
+
(pump.body === 101 && circuitIds.includes(1))) {
|
|
1538
|
+
delayMgr.setPumpValveDelay(pstate);
|
|
1539
|
+
}
|
|
1540
|
+
break;
|
|
1541
|
+
default:
|
|
1542
|
+
if (pt.maxCircuits > 0) {
|
|
1543
|
+
for (let j = 0; j < pump.circuits.length; j++) {
|
|
1544
|
+
let circ = pump.circuits.getItemByIndex(j);
|
|
1545
|
+
if (circuitIds.includes(circ.circuit)) {
|
|
1546
|
+
delayMgr.setPumpValveDelay(pstate);
|
|
1547
|
+
break;
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
break;
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
} catch (err) { }
|
|
1555
|
+
}
|
|
1031
1556
|
}
|
|
1032
1557
|
export class NixieValveCommands extends ValveCommands {
|
|
1033
1558
|
public async setValveAsync(obj: any): Promise<Valve> {
|
|
@@ -1078,14 +1603,15 @@ export class NixieValveCommands extends ValveCommands {
|
|
|
1078
1603
|
export class NixieHeaterCommands extends HeaterCommands {
|
|
1079
1604
|
public async setHeaterAsync(obj: any): Promise<Heater> {
|
|
1080
1605
|
try {
|
|
1081
|
-
let id = typeof obj.id === 'undefined' ? -1 : parseInt(obj.id, 10);
|
|
1606
|
+
let id = typeof obj.id === 'undefined' || !obj.id ? -1 : parseInt(obj.id, 10);
|
|
1082
1607
|
if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError('Heater Id is not valid.', obj.id, 'Heater'));
|
|
1083
|
-
else if (id < 256 && id > 0) return Promise.reject(new InvalidEquipmentIdError('
|
|
1608
|
+
else if (id < 256 && id > 0) return Promise.reject(new InvalidEquipmentIdError('Nixie Heaters controlled by njspc must have an Id > 256.', obj.id, 'Heater'));
|
|
1084
1609
|
let heater: Heater;
|
|
1085
1610
|
if (id <= 0) {
|
|
1086
1611
|
// We are adding a heater. In this case all heaters are virtual.
|
|
1087
1612
|
let vheaters = sys.heaters.filter(h => h.master === 1);
|
|
1088
|
-
id = vheaters.length + 256;
|
|
1613
|
+
id = Math.max(vheaters.getMaxId() + 1 || 0, vheaters.length + 256);
|
|
1614
|
+
logger.info(`Adding a new heater with id ${id}`);
|
|
1089
1615
|
}
|
|
1090
1616
|
heater = sys.heaters.getItemById(id, true);
|
|
1091
1617
|
if (typeof obj !== undefined) {
|
|
@@ -1105,16 +1631,17 @@ export class NixieHeaterCommands extends HeaterCommands {
|
|
|
1105
1631
|
} catch (err) { return Promise.reject(new Error(`Error setting heater configuration: ${err}`)); }
|
|
1106
1632
|
}
|
|
1107
1633
|
public async deleteHeaterAsync(obj: any): Promise<Heater> {
|
|
1108
|
-
|
|
1634
|
+
try {
|
|
1109
1635
|
let id = parseInt(obj.id, 10);
|
|
1110
|
-
if (isNaN(id)) return reject(new InvalidEquipmentIdError('Cannot delete. Heater Id is not valid.', obj.id, 'Heater'));
|
|
1636
|
+
if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError('Cannot delete. Heater Id is not valid.', obj.id, 'Heater'));
|
|
1111
1637
|
let heater = sys.heaters.getItemById(id);
|
|
1112
1638
|
heater.isActive = false;
|
|
1639
|
+
await ncp.heaters.deleteHeaterAsync(id);
|
|
1113
1640
|
sys.heaters.removeItemById(id);
|
|
1114
1641
|
state.heaters.removeItemById(id);
|
|
1115
1642
|
sys.board.heaters.updateHeaterServices();
|
|
1116
|
-
|
|
1117
|
-
});
|
|
1643
|
+
return heater;
|
|
1644
|
+
} catch (err) { return Promise.reject(new BoardProcessError(err.message, 'deleteHeaterAsync')); }
|
|
1118
1645
|
}
|
|
1119
1646
|
public updateHeaterServices() {
|
|
1120
1647
|
let htypes = sys.board.heaters.getInstalledHeaterTypes();
|
|
@@ -1122,7 +1649,7 @@ export class NixieHeaterCommands extends HeaterCommands {
|
|
|
1122
1649
|
let heatPumpInstalled = htypes.heatpump > 0;
|
|
1123
1650
|
let gasHeaterInstalled = htypes.gas > 0;
|
|
1124
1651
|
let ultratempInstalled = htypes.ultratemp > 0;
|
|
1125
|
-
|
|
1652
|
+
let mastertempInstalled = htypes.mastertemp > 0;
|
|
1126
1653
|
// The heat mode options are
|
|
1127
1654
|
// 1 = Off
|
|
1128
1655
|
// 2 = Gas Heater
|
|
@@ -1142,8 +1669,10 @@ export class NixieHeaterCommands extends HeaterCommands {
|
|
|
1142
1669
|
// 3 = Solar Heater
|
|
1143
1670
|
// 4 = Solar Preferred
|
|
1144
1671
|
// 5 = Heat Pump
|
|
1672
|
+
|
|
1145
1673
|
if (sys.heaters.length > 0) sys.board.valueMaps.heatSources = new byteValueMap([[1, { name: 'off', desc: 'Off' }]]);
|
|
1146
1674
|
if (gasHeaterInstalled) sys.board.valueMaps.heatSources.merge([[2, { name: 'heater', desc: 'Heater' }]]);
|
|
1675
|
+
if (mastertempInstalled) sys.board.valueMaps.heatSources.merge([[11, { name: 'mtheater', desc: 'MasterTemp' }]]);
|
|
1147
1676
|
if (solarInstalled && (gasHeaterInstalled || heatPumpInstalled)) sys.board.valueMaps.heatSources.merge([[3, { name: 'solar', desc: 'Solar Only', hasCoolSetpoint: htypes.hasCoolSetpoint }], [4, { name: 'solarpref', desc: 'Solar Preferred', hasCoolSetpoint: htypes.hasCoolSetpoint }]]);
|
|
1148
1677
|
else if (solarInstalled) sys.board.valueMaps.heatSources.merge([[3, { name: 'solar', desc: 'Solar', hasCoolsetpoint: htypes.hasCoolSetpoint }]]);
|
|
1149
1678
|
if (heatPumpInstalled && (gasHeaterInstalled || solarInstalled)) sys.board.valueMaps.heatSources.merge([[9, { name: 'heatpump', desc: 'Heatpump Only' }], [25, { name: 'heatpumppref', desc: 'Heat Pump Pref' }]]);
|
|
@@ -1154,11 +1683,12 @@ export class NixieHeaterCommands extends HeaterCommands {
|
|
|
1154
1683
|
|
|
1155
1684
|
sys.board.valueMaps.heatModes = new byteValueMap([[1, { name: 'off', desc: 'Off' }]]);
|
|
1156
1685
|
if (gasHeaterInstalled) sys.board.valueMaps.heatModes.merge([[2, { name: 'heater', desc: 'Heater' }]]);
|
|
1157
|
-
if (
|
|
1686
|
+
if (mastertempInstalled) sys.board.valueMaps.heatModes.merge([[11, { name: 'mtheater', desc: 'MasterTemp' }]]);
|
|
1687
|
+
if (solarInstalled && (gasHeaterInstalled || heatPumpInstalled || mastertempInstalled)) sys.board.valueMaps.heatModes.merge([[3, { name: 'solar', desc: 'Solar Only' }], [4, { name: 'solarpref', desc: 'Solar Preferred' }]]);
|
|
1158
1688
|
else if (solarInstalled) sys.board.valueMaps.heatModes.merge([[3, { name: 'solar', desc: 'Solar' }]]);
|
|
1159
|
-
if (ultratempInstalled && (gasHeaterInstalled || heatPumpInstalled)) sys.board.valueMaps.heatModes.merge([[5, { name: 'ultratemp', desc: 'UltraTemp Only' }], [6, { name: 'ultratemppref', desc: 'UltraTemp Pref' }]]);
|
|
1689
|
+
if (ultratempInstalled && (gasHeaterInstalled || heatPumpInstalled || mastertempInstalled)) sys.board.valueMaps.heatModes.merge([[5, { name: 'ultratemp', desc: 'UltraTemp Only' }], [6, { name: 'ultratemppref', desc: 'UltraTemp Pref' }]]);
|
|
1160
1690
|
else if (ultratempInstalled) sys.board.valueMaps.heatModes.merge([[5, { name: 'ultratemp', desc: 'UltraTemp' }]]);
|
|
1161
|
-
if (heatPumpInstalled && (gasHeaterInstalled || solarInstalled)) sys.board.valueMaps.heatModes.merge([[9, { name: 'heatpump', desc: 'Heatpump Only' }], [25, { name: 'heatpumppref', desc: 'Heat Pump Preferred' }]]);
|
|
1691
|
+
if (heatPumpInstalled && (gasHeaterInstalled || solarInstalled || mastertempInstalled)) sys.board.valueMaps.heatModes.merge([[9, { name: 'heatpump', desc: 'Heatpump Only' }], [25, { name: 'heatpumppref', desc: 'Heat Pump Preferred' }]]);
|
|
1162
1692
|
else if (heatPumpInstalled) sys.board.valueMaps.heatModes.merge([[9, { name: 'heatpump', desc: 'Heat Pump' }]]);
|
|
1163
1693
|
// Now set the body data.
|
|
1164
1694
|
for (let i = 0; i < sys.bodies.length; i++) {
|