nodejs-poolcontroller 7.5.1 → 7.6.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.
@@ -19,7 +19,7 @@ import { logger } from '../../logger/Logger';
19
19
  import { Message, Outbound } from '../comms/messages/Messages';
20
20
  import { Timestamp, utils } from '../Constants';
21
21
  import { Body, ChemController, Chlorinator, Circuit, CircuitGroup, CircuitGroupCircuit, ConfigVersion, ControllerType, CustomName, CustomNameCollection, EggTimer, Equipment, Feature, Filter, General, Heater, ICircuit, LightGroup, LightGroupCircuit, Location, Options, Owner, PoolSystem, Pump, Schedule, sys, TempSensorCollection, Valve } from '../Equipment';
22
- import { EquipmentNotFoundError, InvalidEquipmentDataError, InvalidEquipmentIdError } from '../Errors';
22
+ import { EquipmentNotFoundError, InvalidEquipmentDataError, InvalidEquipmentIdError, BoardProcessError } from '../Errors';
23
23
  import { ncp } from "../nixie/Nixie";
24
24
  import { BodyTempState, ChemControllerState, ChlorinatorState, CircuitGroupState, FilterState, ICircuitGroupState, ICircuitState, LightGroupState, ScheduleState, state, TemperatureState, ValveState, VirtualCircuitState } from '../State';
25
25
  import { RestoreResults } from '../../web/Server';
@@ -117,396 +117,404 @@ export class EquipmentIds {
117
117
  public invalidIds: InvalidEquipmentIdArray = new InvalidEquipmentIdArray([]);
118
118
  }
119
119
  export class byteValueMaps {
120
- constructor() {
121
- this.pumpStatus.transform = function (byte) {
122
- // if (byte === 0) return this.get(0);
123
- if (byte === 0) return extend(true, {}, this.get(0), { val: byte });
124
- for (let b = 16; b > 0; b--) {
125
- let bit = (1 << (b - 1));
126
- if ((byte & bit) > 0) {
127
- let v = this.get(b);
128
- if (typeof v !== 'undefined') {
129
- return extend(true, {}, v, { val: byte });
130
- }
131
- }
132
- }
133
- return { val: byte, name: 'error' + byte, desc: 'Unspecified Error ' + byte };
134
- };
135
- this.chlorinatorStatus.transform = function (byte) {
136
- if (byte === 128) return { val: 128, name: 'commlost', desc: 'Communication Lost' };
137
- else if (byte === 0) return { val: 0, name: 'ok', desc: 'Ok' };
138
- for (let b = 8; b > 0; b--) {
139
- let bit = (1 << (b - 1));
140
- if ((byte & bit) > 0) {
141
- let v = this.get(b);
142
- if (typeof v !== "undefined") {
143
- return extend(true, {}, v, { val: byte & 0x00FF });
144
- }
145
- }
146
- }
147
- return { val: byte, name: 'unknown' + byte, desc: 'Unknown status ' + byte };
148
- };
149
- this.scheduleTypes.transform = function (byte) {
150
- return (byte & 128) > 0 ? extend(true, { val: 128 }, this.get(128)) : extend(true, { val: 0 }, this.get(0));
151
- };
152
- this.scheduleDays.transform = function (byte) {
153
- let days = [];
154
- let b = byte & 0x007F;
155
- for (let bit = 7; bit >= 0; bit--) {
156
- if ((byte & (1 << (bit - 1))) > 0) days.push(extend(true, {}, this.get(bit)));
157
- }
158
- return { val: b, days: days };
159
- };
160
- this.scheduleDays.toArray = function () {
161
- let arrKeys = Array.from(this.keys());
162
- let arr = [];
163
- for (let i = 0; i < arrKeys.length; i++) arr.push(extend(true, { val: arrKeys[i] }, this.get(arrKeys[i])));
164
- return arr;
165
- };
166
- this.virtualCircuits.transform = function (byte) {
167
- return extend(true, {}, { val: byte, name: 'Unknown ' + byte }, this.get(byte), { val: byte });
168
- };
169
- this.tempUnits.transform = function (byte) { return extend(true, {}, { val: byte & 0x04 }, this.get(byte & 0x04)); };
170
- this.panelModes.transform = function (byte) { return extend(true, { val: byte & 0x83 }, this.get(byte & 0x83)); };
171
- this.controllerStatus.transform = function (byte: number, percent?: number) {
172
- let v = extend(true, {}, this.get(byte) || this.get(0));
173
- if (typeof percent !== 'undefined') v.percent = percent;
174
- return v;
175
- };
176
- this.lightThemes.transform = function (byte) { return typeof byte === 'undefined' ? this.get(255) : extend(true, { val: byte }, this.get(byte) || this.get(255)); };
177
- this.timeZones.findItem = function (val: string | number | { val: any, name: string }) {
178
- if (typeof val === null || typeof val === 'undefined') return;
179
- else if (typeof val === 'number') {
180
- if (val <= 12) { // We are looking for timezones based upon the utcOffset.
181
- let arr = this.toArray();
182
- let tz = arr.find(elem => elem.utcOffset === val);
183
- return typeof tz !== 'undefined' ? this.transform(tz.val) : undefined;
184
- }
185
- return this.transform(val);
186
- }
187
- else if (typeof val === 'string') {
188
- let v = parseInt(val, 10);
189
- if (!isNaN(v)) {
190
- if (v <= 12) {
191
- let arr = this.toArray();
192
- let tz = arr.find(elem => elem.utcOffset === val);
193
- return typeof tz !== 'undefined' ? this.transform(tz.val) : undefined;
194
- }
195
- return this.transform(v);
196
- }
197
- else {
198
- let arr = this.toArray();
199
- let tz = arr.find(elem => elem.abbrev === val || elem.name === val);
200
- return typeof tz !== 'undefined' ? this.transform(tz.val) : undefined;
120
+ constructor() {
121
+ this.pumpStatus.transform = function (byte) {
122
+ // if (byte === 0) return this.get(0);
123
+ if (byte === 0) return extend(true, {}, this.get(0), { val: byte });
124
+ for (let b = 16; b > 0; b--) {
125
+ let bit = (1 << (b - 1));
126
+ if ((byte & bit) > 0) {
127
+ let v = this.get(b);
128
+ if (typeof v !== 'undefined') {
129
+ return extend(true, {}, v, { val: byte });
130
+ }
131
+ }
132
+ }
133
+ return { val: byte, name: 'error' + byte, desc: 'Unspecified Error ' + byte };
134
+ };
135
+ this.chlorinatorStatus.transform = function (byte) {
136
+ if (byte === 128) return { val: 128, name: 'commlost', desc: 'Communication Lost' };
137
+ else if (byte === 0) return { val: 0, name: 'ok', desc: 'Ok' };
138
+ for (let b = 8; b > 0; b--) {
139
+ let bit = (1 << (b - 1));
140
+ if ((byte & bit) > 0) {
141
+ let v = this.get(b);
142
+ if (typeof v !== "undefined") {
143
+ return extend(true, {}, v, { val: byte & 0x00FF });
144
+ }
145
+ }
146
+ }
147
+ return { val: byte, name: 'unknown' + byte, desc: 'Unknown status ' + byte };
148
+ };
149
+ this.scheduleTypes.transform = function (byte) {
150
+ return (byte & 128) > 0 ? extend(true, { val: 128 }, this.get(128)) : extend(true, { val: 0 }, this.get(0));
151
+ };
152
+ this.scheduleDays.transform = function (byte) {
153
+ let days = [];
154
+ let b = byte & 0x007F;
155
+ for (let bit = 7; bit >= 0; bit--) {
156
+ if ((byte & (1 << (bit - 1))) > 0) days.push(extend(true, {}, this.get(bit)));
157
+ }
158
+ return { val: b, days: days };
159
+ };
160
+ this.scheduleDays.toArray = function () {
161
+ let arrKeys = Array.from(this.keys());
162
+ let arr = [];
163
+ for (let i = 0; i < arrKeys.length; i++) arr.push(extend(true, { val: arrKeys[i] }, this.get(arrKeys[i])));
164
+ return arr;
165
+ };
166
+ this.virtualCircuits.transform = function (byte) {
167
+ return extend(true, {}, { val: byte, name: 'Unknown ' + byte }, this.get(byte), { val: byte });
168
+ };
169
+ this.tempUnits.transform = function (byte) { return extend(true, {}, { val: byte & 0x04 }, this.get(byte & 0x04)); };
170
+ this.panelModes.transform = function (byte) { return extend(true, { val: byte & 0x83 }, this.get(byte & 0x83)); };
171
+ this.controllerStatus.transform = function (byte: number, percent?: number) {
172
+ let v = extend(true, {}, this.get(byte) || this.get(0));
173
+ if (typeof percent !== 'undefined') v.percent = percent;
174
+ return v;
175
+ };
176
+ this.lightThemes.transform = function (byte) { return typeof byte === 'undefined' ? this.get(255) : extend(true, { val: byte }, this.get(byte) || this.get(255)); };
177
+ this.timeZones.findItem = function (val: string | number | { val: any, name: string }) {
178
+ if (typeof val === null || typeof val === 'undefined') return;
179
+ else if (typeof val === 'number') {
180
+ if (val <= 12) { // We are looking for timezones based upon the utcOffset.
181
+ let arr = this.toArray();
182
+ let tz = arr.find(elem => elem.utcOffset === val);
183
+ return typeof tz !== 'undefined' ? this.transform(tz.val) : undefined;
184
+ }
185
+ return this.transform(val);
186
+ }
187
+ else if (typeof val === 'string') {
188
+ let v = parseInt(val, 10);
189
+ if (!isNaN(v)) {
190
+ if (v <= 12) {
191
+ let arr = this.toArray();
192
+ let tz = arr.find(elem => elem.utcOffset === val);
193
+ return typeof tz !== 'undefined' ? this.transform(tz.val) : undefined;
194
+ }
195
+ return this.transform(v);
196
+ }
197
+ else {
198
+ let arr = this.toArray();
199
+ let tz = arr.find(elem => elem.abbrev === val || elem.name === val);
200
+ return typeof tz !== 'undefined' ? this.transform(tz.val) : undefined;
201
+ }
202
+ }
203
+ else if (typeof val === 'object') {
204
+ if (typeof val.val !== 'undefined') return this.transform(parseInt(val.val, 10));
205
+ else if (typeof val.name !== 'undefined') return this.transformByName(val.name);
206
+ }
201
207
  }
202
- }
203
- else if (typeof val === 'object') {
204
- if (typeof val.val !== 'undefined') return this.transform(parseInt(val.val, 10));
205
- else if (typeof val.name !== 'undefined') return this.transformByName(val.name);
206
- }
207
208
  }
208
- }
209
- public expansionBoards: byteValueMap = new byteValueMap();
210
- // Identifies which controller manages the underlying equipment.
211
- public equipmentMaster: byteValueMap = new byteValueMap([
212
- [0, { val: 0, name: 'ocp', desc: 'Outdoor Control Panel' }],
213
- [1, { val: 1, name: 'ncp', desc: 'Nixie Control Panel' }]
214
- ]);
215
- public equipmentCommStatus: byteValueMap = new byteValueMap([
216
- [0, { val: 0, name: 'ready', desc: 'Ready' }],
217
- [1, { val: 1, name: 'commerr', desc: 'Communication Error' }]
218
- ]);
219
- public panelModes: byteValueMap = new byteValueMap([
220
- [0, { val: 0, name: 'auto', desc: 'Auto' }],
221
- // [1, { val: 1, name: 'service', desc: 'Service' }],
222
- // [8, { val: 8, name: 'freeze', desc: 'Freeze' }],
223
- // [128, { val: 128, name: 'timeout', desc: 'Timeout' }],
224
- // [129, { val: 129, name: 'service-timeout', desc: 'Service/Timeout' }],
225
- [255, { name: 'error', desc: 'System Error' }]
226
- ]);
227
- public controllerStatus: byteValueMap = new byteValueMap([
228
- [0, { val: 0, name: 'initializing', desc: 'Initializing', percent: 0 }],
229
- [1, { val: 1, name: 'ready', desc: 'Ready', percent: 100 }],
230
- [2, { val: 2, name: 'loading', desc: 'Loading', percent: 0 }],
231
- [3, { val: 255, name: 'Error', desc: 'Error', percent: 0 }]
232
- ]);
209
+ public expansionBoards: byteValueMap = new byteValueMap();
210
+ // Identifies which controller manages the underlying equipment.
211
+ public equipmentMaster: byteValueMap = new byteValueMap([
212
+ [0, { val: 0, name: 'ocp', desc: 'Outdoor Control Panel' }],
213
+ [1, { val: 1, name: 'ncp', desc: 'Nixie Control Panel' }]
214
+ ]);
215
+ public equipmentCommStatus: byteValueMap = new byteValueMap([
216
+ [0, { val: 0, name: 'ready', desc: 'Ready' }],
217
+ [1, { val: 1, name: 'commerr', desc: 'Communication Error' }]
218
+ ]);
219
+ public panelModes: byteValueMap = new byteValueMap([
220
+ [0, { val: 0, name: 'auto', desc: 'Auto' }],
221
+ // [1, { val: 1, name: 'service', desc: 'Service' }],
222
+ // [8, { val: 8, name: 'freeze', desc: 'Freeze' }],
223
+ // [128, { val: 128, name: 'timeout', desc: 'Timeout' }],
224
+ // [129, { val: 129, name: 'service-timeout', desc: 'Service/Timeout' }],
225
+ [255, { name: 'error', desc: 'System Error' }]
226
+ ]);
227
+ public controllerStatus: byteValueMap = new byteValueMap([
228
+ [0, { val: 0, name: 'initializing', desc: 'Initializing', percent: 0 }],
229
+ [1, { val: 1, name: 'ready', desc: 'Ready', percent: 100 }],
230
+ [2, { val: 2, name: 'loading', desc: 'Loading', percent: 0 }],
231
+ [3, { val: 255, name: 'Error', desc: 'Error', percent: 0 }]
232
+ ]);
233
233
 
234
- public circuitFunctions: byteValueMap = new byteValueMap([
235
- [0, { name: 'generic', desc: 'Generic' }],
236
- [1, { name: 'spa', desc: 'Spa', hasHeatSource: true }],
237
- [2, { name: 'pool', desc: 'Pool', hasHeatSource: true }],
238
- [5, { name: 'mastercleaner', desc: 'Master Cleaner' }],
239
- [7, { name: 'light', desc: 'Light', isLight: true }],
240
- [9, { name: 'samlight', desc: 'SAM Light', isLight: true }],
241
- [10, { name: 'sallight', desc: 'SAL Light', isLight: true }],
242
- [11, { name: 'photongen', desc: 'Photon Gen', isLight: true }],
243
- [12, { name: 'colorwheel', desc: 'Color Wheel', isLight: true }],
244
- [13, { name: 'valve', desc: 'Valve' }],
245
- [14, { name: 'spillway', desc: 'Spillway' }],
246
- [15, { name: 'floorcleaner', desc: 'Floor Cleaner' }],
247
- [16, { name: 'intellibrite', desc: 'Intellibrite', isLight: true }],
248
- [17, { name: 'magicstream', desc: 'Magicstream', isLight: true }],
249
- [19, { name: 'notused', desc: 'Not Used' }],
250
- [65, { name: 'lotemp', desc: 'Lo-Temp' }],
251
- [66, { name: 'hightemp', desc: 'Hi-Temp' }]
252
- ]);
234
+ public circuitFunctions: byteValueMap = new byteValueMap([
235
+ [0, { name: 'generic', desc: 'Generic' }],
236
+ [1, { name: 'spa', desc: 'Spa', hasHeatSource: true, body: 2 }],
237
+ [2, { name: 'pool', desc: 'Pool', hasHeatSource: true, body: 1 }],
238
+ [5, { name: 'mastercleaner', desc: 'Master Cleaner', body: 1 }],
239
+ [7, { name: 'light', desc: 'Light', isLight: true }],
240
+ [9, { name: 'samlight', desc: 'SAM Light', isLight: true }],
241
+ [10, { name: 'sallight', desc: 'SAL Light', isLight: true }],
242
+ [11, { name: 'photongen', desc: 'Photon Gen', isLight: true }],
243
+ [12, { name: 'colorwheel', desc: 'Color Wheel', isLight: true }],
244
+ [13, { name: 'valve', desc: 'Valve' }],
245
+ [14, { name: 'spillway', desc: 'Spillway' }],
246
+ [15, { name: 'floorcleaner', desc: 'Floor Cleaner', body: 1 }], // This circuit function does not seem to exist in IntelliTouch.
247
+ [16, { name: 'intellibrite', desc: 'Intellibrite', isLight: true, themes: 'intellibrite' }],
248
+ [17, { name: 'magicstream', desc: 'Magicstream', isLight: true, themes: 'magicstream' }],
249
+ [19, { name: 'notused', desc: 'Not Used' }],
250
+ [65, { name: 'lotemp', desc: 'Lo-Temp' }],
251
+ [66, { name: 'hightemp', desc: 'Hi-Temp' }]
252
+ ]);
253
253
 
254
- // Feature functions are used as the available options to define a circuit.
255
- public featureFunctions: byteValueMap = new byteValueMap([[0, { name: 'generic', desc: 'Generic' }], [1, { name: 'spillway', desc: 'Spillway' }]]);
256
- public virtualCircuits: byteValueMap = new byteValueMap([
257
- [128, { name: 'solar', desc: 'Solar', assignableToPumpCircuit: true }],
258
- [129, { name: 'heater', desc: 'Either Heater', assignableToPumpCircuit: true }],
259
- [130, { name: 'poolHeater', desc: 'Pool Heater', assignableToPumpCircuit: true }],
260
- [131, { name: 'spaHeater', desc: 'Spa Heater', assignableToPumpCircuit: true }],
261
- [132, { name: 'freeze', desc: 'Freeze', assignableToPumpCircuit: true }],
262
- [133, { name: 'heatBoost', desc: 'Heat Boost', assignableToPumpCircuit: false }],
263
- [134, { name: 'heatEnable', desc: 'Heat Enable', assignableToPumpCircuit: false }],
264
- [135, { name: 'pumpSpeedUp', desc: 'Pump Speed +', assignableToPumpCircuit: false }],
265
- [136, { name: 'pumpSpeedDown', desc: 'Pump Speed -', assignableToPumpCircuit: false }],
266
- [255, { name: 'notused', desc: 'NOT USED', assignableToPumpCircuit: true }]
267
- ]);
268
- public lightThemes: byteValueMap = new byteValueMap([
269
- [0, { name: 'off', desc: 'Off', type: 'intellibrite' }],
270
- [1, { name: 'on', desc: 'On', type: 'intellibrite' }],
271
- [128, { name: 'colorsync', desc: 'Color Sync', type: 'intellibrite' }],
272
- [144, { name: 'colorswim', desc: 'Color Swim', type: 'intellibrite' }],
273
- [160, { name: 'colorset', desc: 'Color Set', type: 'intellibrite' }],
274
- [177, { name: 'party', desc: 'Party', type: 'intellibrite', sequence: 2 }],
275
- [178, { name: 'romance', desc: 'Romance', type: 'intellibrite', sequence: 3 }],
276
- [179, { name: 'caribbean', desc: 'Caribbean', type: 'intellibrite', sequence: 4 }],
277
- [180, { name: 'american', desc: 'American', type: 'intellibrite', sequence: 5 }],
278
- [181, { name: 'sunset', desc: 'Sunset', type: 'intellibrite', sequence: 6 }],
279
- [182, { name: 'royal', desc: 'Royal', type: 'intellibrite', sequence: 7 }],
280
- [190, { name: 'save', desc: 'Save', type: 'intellibrite', sequence: 13 }],
281
- [191, { name: 'recall', desc: 'Recall', type: 'intellibrite', sequence: 14 }],
282
- [193, { name: 'blue', desc: 'Blue', type: 'intellibrite', sequence: 8 }],
283
- [194, { name: 'green', desc: 'Green', type: 'intellibrite', sequence: 9 }],
284
- [195, { name: 'red', desc: 'Red', type: 'intellibrite', sequence: 10 }],
285
- [196, { name: 'white', desc: 'White', type: 'intellibrite', sequence: 11 }],
286
- [197, { name: 'magenta', desc: 'Magenta', type: 'intellibrite', sequence: 12 }],
287
- [208, { name: 'thumper', desc: 'Thumper', type: 'magicstream' }],
288
- [209, { name: 'hold', desc: 'Hold', type: 'magicstream' }],
289
- [210, { name: 'reset', desc: 'Reset', type: 'magicstream' }],
290
- [211, { name: 'mode', desc: 'Mode', type: 'magicstream' }],
291
- [254, { name: 'unknown', desc: 'unknown' }],
292
- [255, { name: 'none', desc: 'None' }]
293
- ]);
294
- public colorLogicThemes = new byteValueMap([
295
- [0, { name: 'cloudwhite', desc: 'Cloud White', type: 'colorlogic', sequence: 7 }],
296
- [1, { name: 'deepsea', desc: 'Deep Sea', type: 'colorlogic', sequence: 2 }],
297
- [2, { name: 'royalblue', desc: 'Royal Blue', type: 'colorlogic', sequence: 3 }],
298
- [3, { name: 'afernoonskies', desc: 'Afternoon Skies', type: 'colorlogic', sequence: 4 }],
299
- [4, { name: 'aquagreen', desc: 'Aqua Green', type: 'colorlogic', sequence: 5 }],
300
- [5, { name: 'emerald', desc: 'Emerald', type: 'colorlogic', sequence: 6 }],
301
- [6, { name: 'warmred', desc: 'Warm Red', type: 'colorlogic', sequence: 8 }],
302
- [7, { name: 'flamingo', desc: 'Flamingo', type: 'colorlogic', sequence: 9 }],
303
- [8, { name: 'vividviolet', desc: 'Vivid Violet', type: 'colorlogic', sequence: 10 }],
304
- [9, { name: 'sangria', desc: 'Sangria', type: 'colorlogic', sequence: 11 }],
305
- [10, { name: 'twilight', desc: 'Twilight', type: 'colorlogic', sequence: 12 }],
306
- [11, { name: 'tranquility', desc: 'Tranquility', type: 'colorlogic', sequence: 13 }],
307
- [12, { name: 'gemstone', desc: 'Gemstone', type: 'colorlogic', sequence: 14 }],
308
- [13, { name: 'usa', desc: 'USA', type: 'colorlogic', sequence: 15 }],
309
- [14, { name: 'mardigras', desc: 'Mardi Gras', type: 'colorlogic', sequence: 16 }],
310
- [15, { name: 'cabaret', desc: 'Cabaret', type: 'colorlogic', sequence: 17 }],
311
- [255, { name: 'none', desc: 'None' }]
312
- ]);
254
+ // Feature functions are used as the available options to define a circuit.
255
+ public featureFunctions: byteValueMap = new byteValueMap([[0, { name: 'generic', desc: 'Generic' }], [1, { name: 'spillway', desc: 'Spillway' }]]);
256
+ public virtualCircuits: byteValueMap = new byteValueMap([
257
+ [128, { name: 'solar', desc: 'Solar', assignableToPumpCircuit: true }],
258
+ [129, { name: 'heater', desc: 'Either Heater', assignableToPumpCircuit: true }],
259
+ [130, { name: 'poolHeater', desc: 'Pool Heater', assignableToPumpCircuit: true }],
260
+ [131, { name: 'spaHeater', desc: 'Spa Heater', assignableToPumpCircuit: true }],
261
+ [132, { name: 'freeze', desc: 'Freeze', assignableToPumpCircuit: true }],
262
+ [133, { name: 'heatBoost', desc: 'Heat Boost', assignableToPumpCircuit: false }],
263
+ [134, { name: 'heatEnable', desc: 'Heat Enable', assignableToPumpCircuit: false }],
264
+ [135, { name: 'pumpSpeedUp', desc: 'Pump Speed +', assignableToPumpCircuit: false }],
265
+ [136, { name: 'pumpSpeedDown', desc: 'Pump Speed -', assignableToPumpCircuit: false }],
266
+ [255, { name: 'notused', desc: 'NOT USED', assignableToPumpCircuit: true }]
267
+ ]);
268
+ public lightThemes: byteValueMap = new byteValueMap([
269
+ [0, { name: 'off', desc: 'Off', types: ['intellibrite'] }],
270
+ [1, { name: 'on', desc: 'On', types: ['intellibrite'] }],
271
+ [128, { name: 'colorsync', desc: 'Color Sync', types: ['intellibrite'] }],
272
+ [144, { name: 'colorswim', desc: 'Color Swim', types: ['intellibrite'] }],
273
+ [160, { name: 'colorset', desc: 'Color Set', types: ['intellibrite'] }],
274
+ [177, { name: 'party', desc: 'Party', types: ['intellibrite'], sequence: 2 }],
275
+ [178, { name: 'romance', desc: 'Romance', types: ['intellibrite'], sequence: 3 }],
276
+ [179, { name: 'caribbean', desc: 'Caribbean', types: ['intellibrite'], sequence: 4 }],
277
+ [180, { name: 'american', desc: 'American', types: ['intellibrite'], sequence: 5 }],
278
+ [181, { name: 'sunset', desc: 'Sunset', types: ['intellibrite'], sequence: 6 }],
279
+ [182, { name: 'royal', desc: 'Royal', types: ['intellibrite'], sequence: 7 }],
280
+ [190, { name: 'save', desc: 'Save', types: ['intellibrite'], sequence: 13 }],
281
+ [191, { name: 'recall', desc: 'Recall', types: ['intellibrite'], sequence: 14 }],
282
+ [193, { name: 'blue', desc: 'Blue', types: ['intellibrite'], sequence: 8 }],
283
+ [194, { name: 'green', desc: 'Green', types: ['intellibrite'], sequence: 9 }],
284
+ [195, { name: 'red', desc: 'Red', types: ['intellibrite'], sequence: 10 }],
285
+ [196, { name: 'white', desc: 'White', types: ['intellibrite'], sequence: 11 }],
286
+ [197, { name: 'magenta', desc: 'Magenta', types: ['intellibrite'], sequence: 12 }],
287
+ [208, { name: 'thumper', desc: 'Thumper', types: ['magicstream'] }],
288
+ [209, { name: 'hold', desc: 'Hold', types: ['magicstream'] }],
289
+ [210, { name: 'reset', desc: 'Reset', types: ['magicstream'] }],
290
+ [211, { name: 'mode', desc: 'Mode', types: ['magicstream'] }],
291
+ [254, { name: 'unknown', desc: 'unknown' }],
292
+ [255, { name: 'none', desc: 'None' }]
293
+ ]);
294
+ public colorLogicThemes = new byteValueMap([
295
+ [0, { name: 'cloudwhite', desc: 'Cloud White', types: ['colorlogic'], sequence: 7 }],
296
+ [1, { name: 'deepsea', desc: 'Deep Sea', types: ['colorlogic'], sequence: 2 }],
297
+ [2, { name: 'royalblue', desc: 'Royal Blue', types: ['colorlogic'], sequence: 3 }],
298
+ [3, { name: 'afernoonskies', desc: 'Afternoon Skies', types: ['colorlogic'], sequence: 4 }],
299
+ [4, { name: 'aquagreen', desc: 'Aqua Green', types: ['colorlogic'], sequence: 5 }],
300
+ [5, { name: 'emerald', desc: 'Emerald', types: ['colorlogic'], sequence: 6 }],
301
+ [6, { name: 'warmred', desc: 'Warm Red', types: ['colorlogic'], sequence: 8 }],
302
+ [7, { name: 'flamingo', desc: 'Flamingo', types: ['colorlogic'], sequence: 9 }],
303
+ [8, { name: 'vividviolet', desc: 'Vivid Violet', types: ['colorlogic'], sequence: 10 }],
304
+ [9, { name: 'sangria', desc: 'Sangria', types: ['colorlogic'], sequence: 11 }],
305
+ [10, { name: 'twilight', desc: 'Twilight', types: ['colorlogic'], sequence: 12 }],
306
+ [11, { name: 'tranquility', desc: 'Tranquility', types: ['colorlogic'], sequence: 13 }],
307
+ [12, { name: 'gemstone', desc: 'Gemstone', types: ['colorlogic'], sequence: 14 }],
308
+ [13, { name: 'usa', desc: 'USA', types: ['colorlogic'], sequence: 15 }],
309
+ [14, { name: 'mardigras', desc: 'Mardi Gras', types: ['colorlogic'], sequence: 16 }],
310
+ [15, { name: 'cabaret', desc: 'Cabaret', types: ['colorlogic'], sequence: 17 }],
311
+ [255, { name: 'none', desc: 'None' }]
312
+ ]);
313
313
 
314
- public lightColors: byteValueMap = new byteValueMap([
315
- [0, { name: 'white', desc: 'White' }],
316
- [2, { name: 'lightgreen', desc: 'Light Green' }],
317
- [4, { name: 'green', desc: 'Green' }],
318
- [6, { name: 'cyan', desc: 'Cyan' }],
319
- [8, { name: 'blue', desc: 'Blue' }],
320
- [10, { name: 'lavender', desc: 'Lavender' }],
321
- [12, { name: 'magenta', desc: 'Magenta' }],
322
- [14, { name: 'lightmagenta', desc: 'Light Magenta' }]
323
- ]);
324
- public scheduleDays: byteValueMap = new byteValueMap([
325
- [1, { name: 'sat', desc: 'Saturday', dow: 6 }],
326
- [2, { name: 'fri', desc: 'Friday', dow: 5 }],
327
- [3, { name: 'thu', desc: 'Thursday', dow: 4 }],
328
- [4, { name: 'wed', desc: 'Wednesday', dow: 3 }],
329
- [5, { name: 'tue', desc: 'Tuesday', dow: 2 }],
330
- [6, { name: 'mon', desc: 'Monday', dow: 1 }],
331
- [7, { name: 'sun', desc: 'Sunday', dow: 0 }]
332
- ]);
333
- public scheduleTimeTypes: byteValueMap = new byteValueMap([
334
- [0, { name: 'manual', desc: 'Manual' }]
335
- ]);
336
- public scheduleDisplayTypes: byteValueMap = new byteValueMap([
337
- [0, { name: 'always', desc: 'Always' }],
338
- [1, { name: 'active', desc: 'When Active' }],
339
- [2, { name: 'never', desc: 'Never' }]
340
- ]);
314
+ public lightColors: byteValueMap = new byteValueMap([
315
+ [0, { name: 'white', desc: 'White' }],
316
+ [2, { name: 'lightgreen', desc: 'Light Green' }],
317
+ [4, { name: 'green', desc: 'Green' }],
318
+ [6, { name: 'cyan', desc: 'Cyan' }],
319
+ [8, { name: 'blue', desc: 'Blue' }],
320
+ [10, { name: 'lavender', desc: 'Lavender' }],
321
+ [12, { name: 'magenta', desc: 'Magenta' }],
322
+ [14, { name: 'lightmagenta', desc: 'Light Magenta' }]
323
+ ]);
324
+ public scheduleDays: byteValueMap = new byteValueMap([
325
+ [1, { name: 'sat', desc: 'Saturday', dow: 6 }],
326
+ [2, { name: 'fri', desc: 'Friday', dow: 5 }],
327
+ [3, { name: 'thu', desc: 'Thursday', dow: 4 }],
328
+ [4, { name: 'wed', desc: 'Wednesday', dow: 3 }],
329
+ [5, { name: 'tue', desc: 'Tuesday', dow: 2 }],
330
+ [6, { name: 'mon', desc: 'Monday', dow: 1 }],
331
+ [7, { name: 'sun', desc: 'Sunday', dow: 0 }]
332
+ ]);
333
+ public scheduleTimeTypes: byteValueMap = new byteValueMap([
334
+ [0, { name: 'manual', desc: 'Manual' }]
335
+ ]);
336
+ public scheduleDisplayTypes: byteValueMap = new byteValueMap([
337
+ [0, { name: 'always', desc: 'Always' }],
338
+ [1, { name: 'active', desc: 'When Active' }],
339
+ [2, { name: 'never', desc: 'Never' }]
340
+ ]);
341
341
 
342
- public pumpTypes: byteValueMap = new byteValueMap([
343
- [1, { name: 'vf', desc: 'Intelliflo VF', minFlow: 15, maxFlow: 130, flowStepSize: 1, maxCircuits: 8, hasAddress: true }],
344
- [64, { name: 'vsf', desc: 'Intelliflo VSF', minSpeed: 450, maxSpeed: 3450, speedStepSize: 10, minFlow: 15, maxFlow: 130, flowStepSize: 1, maxCircuits: 8, hasAddress: true }],
345
- [65, { name: 'ds', desc: 'Two-Speed', maxCircuits: 40, hasAddress: false, hasBody: true }],
346
- [128, { name: 'vs', desc: 'Intelliflo VS', maxPrimingTime: 6, minSpeed: 450, maxSpeed: 3450, speedStepSize: 10, maxCircuits: 8, hasAddress: true }],
347
- [169, { name: 'vssvrs', desc: 'IntelliFlo VS+SVRS', maxPrimingTime: 6, minSpeed: 450, maxSpeed: 3450, speedStepSize: 10, maxCircuits: 8, hasAddress: true }]
348
- ]);
349
- public pumpSSModels: byteValueMap = new byteValueMap([
350
- [0, { name: 'unspecified', desc: 'Unspecified', amps: 0, pf: 0, volts: 0, watts: 0 }],
351
- [1, { name: 'wf1hpE', desc: '1hp WhisperFlo E+', amps: 7.4, pf: .9, volts: 230, watts: 1532 }],
352
- [2, { name: 'wf1hpMax', desc: '1hp WhisperFlo Max', amps: 9, pf: .87, volts: 230, watts: 1600 }],
353
- [3, { name: 'generic15hp', desc: '1.5hp Pump', amps: 9.3, pf: .9, volts: 230, watts: 1925 }],
354
- [4, { name: 'generic2hp', desc: '2hp Pump', amps: 12, pf: .9, volts: 230, watts: 2484 }],
355
- [5, { name: 'generic25hp', desc: '2.5hp Pump', amps: 12.5, pf: .9, volts: 230, watts: 2587 }],
356
- [6, { name: 'generic3hp', desc: '3hp Pump', amps: 13.5, pf: .9, volts: 230, watts: 2794 }]
357
- ]);
358
- public pumpDSModels: byteValueMap = new byteValueMap([
359
- [0, { name: 'unspecified', desc: 'Unspecified', loAmps: 0, hiAmps: 0, pf: 0, volts: 0, loWatts: 0, hiWatts: 0 }],
360
- [1, { name: 'generic1hp', desc: '1hp Pump', loAmps: 2.4, hiAmps: 6.5, pf: .9, volts: 230, loWatts: 497, hiWatts: 1345 }],
361
- [2, { name: 'generic15hp', desc: '1.5hp Pump', loAmps: 2.7, hiAmps: 9.3, pf: .9, volts: 230, loWatts: 558, hiWatts: 1925 }],
362
- [3, { name: 'generic2hp', desc: '2hp Pump', loAmps: 2.9, hiAmps: 12, pf: .9, volts: 230, loWatts: 600, hiWatts: 2484 }],
363
- [4, { name: 'generic25hp', desc: '2.5hp Pump', loAmps: 3.1, hiAmps: 12.5, pf: .9, volts: 230, loWatts: 642, hiWatts: 2587 }],
364
- [5, { name: 'generic3hp', desc: '3hp Pump', loAmps: 3.3, hiAmps: 13.5, pf: .9, volts: 230, loWatts: 683, hiWatts: 2794 }]
365
- ]);
366
- public pumpVSModels: byteValueMap = new byteValueMap([
367
- [0, { name: 'intelliflovs', desc: 'IntelliFlo VS' }]
368
- ]);
369
- public pumpVFModels: byteValueMap = new byteValueMap([
370
- [0, { name: 'intelliflovf', desc: 'IntelliFlo VF' }]
371
- ]);
372
- public pumpVSFModels: byteValueMap = new byteValueMap([
373
- [0, { name: 'intelliflovsf', desc: 'IntelliFlo VSF' }]
374
- ]);
375
- public pumpVSSVRSModels: byteValueMap = new byteValueMap([
376
- [0, { name: 'intelliflovssvrs', desc: 'IntelliFlo VS+SVRS' }]
377
- ]);
378
- // These are used for single-speed pump definitions. Essentially the way this works is that when
379
- // the body circuit is running the single speed pump is on.
380
- public pumpBodies: byteValueMap = new byteValueMap([
381
- [0, { name: 'pool', desc: 'Pool' }],
382
- [101, { name: 'spa', desc: 'Spa' }],
383
- [255, { name: 'poolspa', desc: 'Pool/Spa' }]
384
- ]);
385
- public heaterTypes: byteValueMap = new byteValueMap([
386
- [1, { name: 'gas', desc: 'Gas Heater', hasAddress: false }],
387
- [2, { name: 'solar', desc: 'Solar Heater', hasAddress: false, hasCoolSetpoint: true }],
388
- [3, { name: 'heatpump', desc: 'Heat Pump', hasAddress: true }],
389
- [4, { name: 'ultratemp', desc: 'UltraTemp', hasAddress: true, hasCoolSetpoint: true }],
390
- [5, { name: 'hybrid', desc: 'Hybrid', hasAddress: true }],
391
- [6, { name: 'maxetherm', desc: 'Max-E-Therm', hasAddress: true }],
392
- [7, { name: 'mastertemp', desc: 'MasterTemp', hasAddress: true }]
393
- ]);
394
- public heatModes: byteValueMap = new byteValueMap([
395
- [0, { name: 'off', desc: 'Off' }],
396
- [3, { name: 'heater', desc: 'Heater' }],
397
- [5, { name: 'solar', desc: 'Solar Only' }],
398
- [12, { name: 'solarpref', desc: 'Solar Preferred' }]
399
- ]);
400
- public heatSources: byteValueMap = new byteValueMap([
401
- [0, { name: 'off', desc: 'No Heater' }],
402
- [3, { name: 'heater', desc: 'Heater' }],
403
- [5, { name: 'solar', desc: 'Solar Only' }],
404
- [21, { name: 'solarpref', desc: 'Solar Preferred' }],
405
- [32, { name: 'nochange', desc: 'No Change' }]
406
- ]);
407
- public heatStatus: byteValueMap = new byteValueMap([
408
- [0, { name: 'off', desc: 'Off' }],
409
- [1, { name: 'heater', desc: 'Heater' }],
410
- [2, { name: 'solar', desc: 'Solar' }],
411
- [3, { name: 'cooling', desc: 'Cooling' }]
412
- ]);
413
- public pumpStatus: byteValueMap = new byteValueMap([
414
- [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
415
- // for a VS/VF pump as is should be powered at all times. When it is, the status will always report a value > 0.
416
- [1, { name: 'ok', desc: 'Ok' }], // Status is always reported when the pump is not wired to a relay regardless of whether it is on or not
417
- // as is should be if this is a VS / VF pump. However if it is wired to a relay most often filter, the pump will report status
418
- // 0 if it is not running. Essentially this is no error but it is not a status either.
419
- [2, { name: 'filter', desc: 'Filter warning' }],
420
- [3, { name: 'overcurrent', desc: 'Overcurrent condition' }],
421
- [4, { name: 'priming', desc: 'Priming' }],
422
- [5, { name: 'blocked', desc: 'System blocked' }],
423
- [6, { name: 'general', desc: 'General alarm' }],
424
- [7, { name: 'overtemp', desc: 'Overtemp condition' }],
425
- [8, { name: 'power', dec: 'Power outage' }],
426
- [9, { name: 'overcurrent2', desc: 'Overcurrent condition 2' }],
427
- [10, { name: 'overvoltage', desc: 'Overvoltage condition' }],
428
- [11, { name: 'error11', desc: 'Unspecified Error 11' }],
429
- [12, { name: 'error12', desc: 'Unspecified Error 12' }],
430
- [13, { name: 'error13', desc: 'Unspecified Error 13' }],
431
- [14, { name: 'error14', desc: 'Unspecified Error 14' }],
432
- [15, { name: 'error15', desc: 'Unspecified Error 15' }],
433
- [16, { name: 'commfailure', desc: 'Communication failure' }]
434
- ]);
435
- public pumpUnits: byteValueMap = new byteValueMap([
436
- [0, { name: 'rpm', desc: 'RPM' }],
437
- [1, { name: 'gpm', desc: 'GPM' }]
438
- ]);
439
- public bodyTypes: byteValueMap = new byteValueMap([
440
- [0, { name: 'pool', desc: 'Pool' }],
441
- [1, { name: 'spa', desc: 'Spa' }],
442
- [2, { name: 'spa', desc: 'Spa' }],
443
- [3, { name: 'spa', desc: 'Spa' }]
444
- ]);
445
- public bodies: byteValueMap = new byteValueMap([
446
- [0, { name: 'pool', desc: 'Pool' }],
447
- [1, { name: 'spa', desc: 'Spa' }],
448
- [2, { name: 'body3', desc: 'Body 3' }],
449
- [3, { name: 'body4', desc: 'Body 4' }],
450
- [32, { name: 'poolspa', desc: 'Pool/Spa' }]
451
- ]);
452
- public chlorinatorStatus: byteValueMap = new byteValueMap([
453
- [0, { name: 'ok', desc: 'Ok' }],
454
- [1, { name: 'lowflow', desc: 'Low Flow' }],
455
- [2, { name: 'lowsalt', desc: 'Low Salt' }],
456
- [3, { name: 'verylowsalt', desc: 'Very Low Salt' }],
457
- [4, { name: 'highcurrent', desc: 'High Current' }],
458
- [5, { name: 'clean', desc: 'Clean Cell' }],
459
- [6, { name: 'lowvoltage', desc: 'Low Voltage' }],
460
- [7, { name: 'lowtemp', desc: 'Water Temp Low' }],
461
- [8, { name: 'commlost', desc: 'Communication Lost' }]
462
- ]);
463
- public chlorinatorType: byteValueMap = new byteValueMap([
464
- [0, { name: 'pentair', desc: 'Pentair' }],
465
- [1, { name: 'unknown', desc: 'unknown' }],
466
- [2, { name: 'aquarite', desc: 'Aquarite' }],
467
- [3, { name: 'unknown', desc: 'unknown' }]
468
- ]);
469
- public chlorinatorModel: byteValueMap = new byteValueMap([
470
- [0, { name: 'unknown', desc: 'unknown', capacity: 0, chlorinePerDay: 0, chlorinePerSec: 0 }],
471
- [1, { name: 'intellichlor--15', desc: 'IntelliChlor IC15', capacity: 15000, chlorinePerDay: 0.60, chlorinePerSec: 0.60 / 86400 }],
472
- [2, { name: 'intellichlor--20', desc: 'IntelliChlor IC20', capacity: 20000, chlorinePerDay: 0.70, chlorinePerSec: 0.70 / 86400 }],
473
- [3, { name: 'intellichlor--40', desc: 'IntelliChlor IC40', capacity: 40000, chlorinePerDay: 1.40, chlorinePerSec: 1.4 / 86400 }],
474
- [4, { name: 'intellichlor--60', desc: 'IntelliChlor IC60', capacity: 60000, chlorinePerDay: 2, chlorinePerSec: 2 / 86400 }],
475
- [5, { name: 'aquarite-t15', desc: 'AquaRite T15', capacity: 40000, chlorinePerDay: 1.47, chlorinePerSec: 1.47 / 86400 }],
476
- [6, { name: 'aquarite-t9', desc: 'AquaRite T9', capacity: 30000, chlorinePerDay: 0.98, chlorinePerSec: 0.98 / 86400 }],
477
- [7, { name: 'aquarite-t5', desc: 'AquaRite T5', capacity: 20000, chlorinePerDay: 0.735, chlorinePerSec: 0.735 / 86400 }],
478
- [8, { name: 'aquarite-t3', desc: 'AquaRite T3', capacity: 15000, chlorinePerDay: 0.53, chlorinePerSec: 0.53 / 86400 }],
479
- [9, { name: 'aquarite-925', desc: 'AquaRite 925', capacity: 25000, chlorinePerDay: 0.98, chlorinePerSec: 0.98 / 86400 }],
480
- [10, { name: 'aquarite-940', desc: 'AquaRite 940', capacity: 40000, chlorinePerDay: 1.47, chlorinePerSec: 1.47 / 86400 }]
481
- ])
482
- public customNames: byteValueMap = new byteValueMap();
483
- public circuitNames: byteValueMap = new byteValueMap();
484
- public scheduleTypes: byteValueMap = new byteValueMap([
485
- [0, { name: 'runonce', desc: 'Run Once', startDate: true, startTime: true, endTime: true, days: false, heatSource: true, heatSetpoint: true }],
486
- [128, { name: 'repeat', desc: 'Repeats', startDate: false, startTime: true, endTime: true, days: 'multi', heatSource: true, heatSetpoint: true }]
487
- ]);
488
- public circuitGroupTypes: byteValueMap = new byteValueMap([
489
- [0, { name: 'none', desc: 'Unspecified' }],
490
- [1, { name: 'light', desc: 'Light' }],
491
- [2, { name: 'circuit', desc: 'Circuit' }],
492
- [3, { name: 'intellibrite', desc: 'IntelliBrite' }]
493
- ]);
494
- public groupCircuitStates: byteValueMap = new byteValueMap([
495
- [0, { name: 'off', desc: 'Off' }],
496
- [1, { name: 'on', desc: 'On' }]
497
- ]);
498
- public systemUnits: byteValueMap = new byteValueMap([
499
- [0, { name: 'english', desc: 'English' }],
500
- [4, { name: 'metric', desc: 'Metric' }]
501
- ]);
502
- public tempUnits: byteValueMap = new byteValueMap([
503
- [0, { name: 'F', desc: 'Fahrenheit' }],
504
- [4, { name: 'C', desc: 'Celsius' }]
505
- ]);
506
- public valveTypes: byteValueMap = new byteValueMap([
507
- [0, { name: 'standard', desc: 'Standard' }],
508
- [1, { name: 'intellivalve', desc: 'IntelliValve' }]
509
- ]);
342
+ public pumpTypes: byteValueMap = new byteValueMap([
343
+ [1, { name: 'vf', desc: 'Intelliflo VF', minFlow: 15, maxFlow: 130, flowStepSize: 1, maxCircuits: 8, hasAddress: true }],
344
+ [64, { name: 'vsf', desc: 'Intelliflo VSF', minSpeed: 450, maxSpeed: 3450, speedStepSize: 10, minFlow: 15, maxFlow: 130, flowStepSize: 1, maxCircuits: 8, hasAddress: true }],
345
+ [65, { name: 'ds', desc: 'Two-Speed', maxCircuits: 40, hasAddress: false, hasBody: true }],
346
+ [128, { name: 'vs', desc: 'Intelliflo VS', maxPrimingTime: 6, minSpeed: 450, maxSpeed: 3450, speedStepSize: 10, maxCircuits: 8, hasAddress: true }],
347
+ [169, { name: 'vssvrs', desc: 'IntelliFlo VS+SVRS', maxPrimingTime: 6, minSpeed: 450, maxSpeed: 3450, speedStepSize: 10, maxCircuits: 8, hasAddress: true }]
348
+ ]);
349
+ public pumpSSModels: byteValueMap = new byteValueMap([
350
+ [0, { name: 'unspecified', desc: 'Unspecified', amps: 0, pf: 0, volts: 0, watts: 0 }],
351
+ [1, { name: 'wf1hpE', desc: '1hp WhisperFlo E+', amps: 7.4, pf: .9, volts: 230, watts: 1532 }],
352
+ [2, { name: 'wf1hpMax', desc: '1hp WhisperFlo Max', amps: 9, pf: .87, volts: 230, watts: 1600 }],
353
+ [3, { name: 'generic15hp', desc: '1.5hp Pump', amps: 9.3, pf: .9, volts: 230, watts: 1925 }],
354
+ [4, { name: 'generic2hp', desc: '2hp Pump', amps: 12, pf: .9, volts: 230, watts: 2484 }],
355
+ [5, { name: 'generic25hp', desc: '2.5hp Pump', amps: 12.5, pf: .9, volts: 230, watts: 2587 }],
356
+ [6, { name: 'generic3hp', desc: '3hp Pump', amps: 13.5, pf: .9, volts: 230, watts: 2794 }]
357
+ ]);
358
+ public pumpDSModels: byteValueMap = new byteValueMap([
359
+ [0, { name: 'unspecified', desc: 'Unspecified', loAmps: 0, hiAmps: 0, pf: 0, volts: 0, loWatts: 0, hiWatts: 0 }],
360
+ [1, { name: 'generic1hp', desc: '1hp Pump', loAmps: 2.4, hiAmps: 6.5, pf: .9, volts: 230, loWatts: 497, hiWatts: 1345 }],
361
+ [2, { name: 'generic15hp', desc: '1.5hp Pump', loAmps: 2.7, hiAmps: 9.3, pf: .9, volts: 230, loWatts: 558, hiWatts: 1925 }],
362
+ [3, { name: 'generic2hp', desc: '2hp Pump', loAmps: 2.9, hiAmps: 12, pf: .9, volts: 230, loWatts: 600, hiWatts: 2484 }],
363
+ [4, { name: 'generic25hp', desc: '2.5hp Pump', loAmps: 3.1, hiAmps: 12.5, pf: .9, volts: 230, loWatts: 642, hiWatts: 2587 }],
364
+ [5, { name: 'generic3hp', desc: '3hp Pump', loAmps: 3.3, hiAmps: 13.5, pf: .9, volts: 230, loWatts: 683, hiWatts: 2794 }]
365
+ ]);
366
+ public pumpVSModels: byteValueMap = new byteValueMap([
367
+ [0, { name: 'intelliflovs', desc: 'IntelliFlo VS' }]
368
+ ]);
369
+ public pumpVFModels: byteValueMap = new byteValueMap([
370
+ [0, { name: 'intelliflovf', desc: 'IntelliFlo VF' }]
371
+ ]);
372
+ public pumpVSFModels: byteValueMap = new byteValueMap([
373
+ [0, { name: 'intelliflovsf', desc: 'IntelliFlo VSF' }]
374
+ ]);
375
+ public pumpVSSVRSModels: byteValueMap = new byteValueMap([
376
+ [0, { name: 'intelliflovssvrs', desc: 'IntelliFlo VS+SVRS' }]
377
+ ]);
378
+ // These are used for single-speed pump definitions. Essentially the way this works is that when
379
+ // the body circuit is running the single speed pump is on.
380
+ public pumpBodies: byteValueMap = new byteValueMap([
381
+ [0, { name: 'pool', desc: 'Pool' }],
382
+ [101, { name: 'spa', desc: 'Spa' }],
383
+ [255, { name: 'poolspa', desc: 'Pool/Spa' }]
384
+ ]);
385
+ public heaterTypes: byteValueMap = new byteValueMap([
386
+ [1, { name: 'gas', desc: 'Gas Heater', hasAddress: false }],
387
+ [2, { name: 'solar', desc: 'Solar Heater', hasAddress: false, hasCoolSetpoint: true }],
388
+ [3, { name: 'heatpump', desc: 'Heat Pump', hasAddress: true }],
389
+ [4, { name: 'ultratemp', desc: 'UltraTemp', hasAddress: true, hasCoolSetpoint: true }],
390
+ [5, { name: 'hybrid', desc: 'Hybrid', hasAddress: true }],
391
+ [6, { name: 'mastertemp', desc: 'MasterTemp', hasAddress: true }],
392
+ [7, { name: 'maxetherm', desc: 'Max-E-Therm', hasAddress: true }],
393
+ ]);
394
+ public heatModes: byteValueMap = new byteValueMap([
395
+ [0, { name: 'off', desc: 'Off' }],
396
+ [3, { name: 'heater', desc: 'Heater' }],
397
+ [5, { name: 'solar', desc: 'Solar Only' }],
398
+ [12, { name: 'solarpref', desc: 'Solar Preferred' }]
399
+ ]);
400
+ public heatSources: byteValueMap = new byteValueMap([
401
+ [0, { name: 'off', desc: 'No Heater' }],
402
+ [3, { name: 'heater', desc: 'Heater' }],
403
+ [5, { name: 'solar', desc: 'Solar Only' }],
404
+ [21, { name: 'solarpref', desc: 'Solar Preferred' }],
405
+ [32, { name: 'nochange', desc: 'No Change' }]
406
+ ]);
407
+ public heatStatus: byteValueMap = new byteValueMap([
408
+ [0, { name: 'off', desc: 'Off' }],
409
+ [1, { name: 'heater', desc: 'Heater' }],
410
+ [2, { name: 'solar', desc: 'Solar' }],
411
+ [3, { name: 'cooling', desc: 'Cooling' }],
412
+ [128, { name: 'cooldown', desc: 'Cooldown' }]
413
+ ]);
414
+ public pumpStatus: byteValueMap = new byteValueMap([
415
+ [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
416
+ // for a VS/VF pump as is should be powered at all times. When it is, the status will always report a value > 0.
417
+ [1, { name: 'ok', desc: 'Ok' }], // Status is always reported when the pump is not wired to a relay regardless of whether it is on or not
418
+ // as is should be if this is a VS / VF pump. However if it is wired to a relay most often filter, the pump will report status
419
+ // 0 if it is not running. Essentially this is no error but it is not a status either.
420
+ [2, { name: 'filter', desc: 'Filter warning' }],
421
+ [3, { name: 'overcurrent', desc: 'Overcurrent condition' }],
422
+ [4, { name: 'priming', desc: 'Priming' }],
423
+ [5, { name: 'blocked', desc: 'System blocked' }],
424
+ [6, { name: 'general', desc: 'General alarm' }],
425
+ [7, { name: 'overtemp', desc: 'Overtemp condition' }],
426
+ [8, { name: 'power', dec: 'Power outage' }],
427
+ [9, { name: 'overcurrent2', desc: 'Overcurrent condition 2' }],
428
+ [10, { name: 'overvoltage', desc: 'Overvoltage condition' }],
429
+ [11, { name: 'error11', desc: 'Unspecified Error 11' }],
430
+ [12, { name: 'error12', desc: 'Unspecified Error 12' }],
431
+ [13, { name: 'error13', desc: 'Unspecified Error 13' }],
432
+ [14, { name: 'error14', desc: 'Unspecified Error 14' }],
433
+ [15, { name: 'error15', desc: 'Unspecified Error 15' }],
434
+ [16, { name: 'commfailure', desc: 'Communication failure' }]
435
+ ]);
436
+ public pumpUnits: byteValueMap = new byteValueMap([
437
+ [0, { name: 'rpm', desc: 'RPM' }],
438
+ [1, { name: 'gpm', desc: 'GPM' }]
439
+ ]);
440
+ public bodyTypes: byteValueMap = new byteValueMap([
441
+ [0, { name: 'pool', desc: 'Pool' }],
442
+ [1, { name: 'spa', desc: 'Spa' }],
443
+ [2, { name: 'spa', desc: 'Spa' }],
444
+ [3, { name: 'spa', desc: 'Spa' }]
445
+ ]);
446
+ public bodies: byteValueMap = new byteValueMap([
447
+ [0, { name: 'pool', desc: 'Pool' }],
448
+ [1, { name: 'spa', desc: 'Spa' }],
449
+ [2, { name: 'body3', desc: 'Body 3' }],
450
+ [3, { name: 'body4', desc: 'Body 4' }],
451
+ [32, { name: 'poolspa', desc: 'Pool/Spa' }]
452
+ ]);
453
+ public chlorinatorStatus: byteValueMap = new byteValueMap([
454
+ [0, { name: 'ok', desc: 'Ok' }],
455
+ [1, { name: 'lowflow', desc: 'Low Flow' }],
456
+ [2, { name: 'lowsalt', desc: 'Low Salt' }],
457
+ [3, { name: 'verylowsalt', desc: 'Very Low Salt' }],
458
+ [4, { name: 'highcurrent', desc: 'High Current' }],
459
+ [5, { name: 'clean', desc: 'Clean Cell' }],
460
+ [6, { name: 'lowvoltage', desc: 'Low Voltage' }],
461
+ [7, { name: 'lowtemp', desc: 'Water Temp Low' }],
462
+ [8, { name: 'commlost', desc: 'Communication Lost' }]
463
+ ]);
464
+ public chlorinatorType: byteValueMap = new byteValueMap([
465
+ [0, { name: 'pentair', desc: 'Pentair' }],
466
+ [1, { name: 'unknown', desc: 'unknown' }],
467
+ [2, { name: 'aquarite', desc: 'Aquarite' }],
468
+ [3, { name: 'unknown', desc: 'unknown' }]
469
+ ]);
470
+ public chlorinatorModel: byteValueMap = new byteValueMap([
471
+ [0, { name: 'unknown', desc: 'unknown', capacity: 0, chlorinePerDay: 0, chlorinePerSec: 0 }],
472
+ [1, { name: 'intellichlor--15', desc: 'IntelliChlor IC15', capacity: 15000, chlorinePerDay: 0.60, chlorinePerSec: 0.60 / 86400 }],
473
+ [2, { name: 'intellichlor--20', desc: 'IntelliChlor IC20', capacity: 20000, chlorinePerDay: 0.70, chlorinePerSec: 0.70 / 86400 }],
474
+ [3, { name: 'intellichlor--40', desc: 'IntelliChlor IC40', capacity: 40000, chlorinePerDay: 1.40, chlorinePerSec: 1.4 / 86400 }],
475
+ [4, { name: 'intellichlor--60', desc: 'IntelliChlor IC60', capacity: 60000, chlorinePerDay: 2, chlorinePerSec: 2 / 86400 }],
476
+ [5, { name: 'aquarite-t15', desc: 'AquaRite T15', capacity: 40000, chlorinePerDay: 1.47, chlorinePerSec: 1.47 / 86400 }],
477
+ [6, { name: 'aquarite-t9', desc: 'AquaRite T9', capacity: 30000, chlorinePerDay: 0.98, chlorinePerSec: 0.98 / 86400 }],
478
+ [7, { name: 'aquarite-t5', desc: 'AquaRite T5', capacity: 20000, chlorinePerDay: 0.735, chlorinePerSec: 0.735 / 86400 }],
479
+ [8, { name: 'aquarite-t3', desc: 'AquaRite T3', capacity: 15000, chlorinePerDay: 0.53, chlorinePerSec: 0.53 / 86400 }],
480
+ [9, { name: 'aquarite-925', desc: 'AquaRite 925', capacity: 25000, chlorinePerDay: 0.98, chlorinePerSec: 0.98 / 86400 }],
481
+ [10, { name: 'aquarite-940', desc: 'AquaRite 940', capacity: 40000, chlorinePerDay: 1.47, chlorinePerSec: 1.47 / 86400 }]
482
+ ])
483
+ public customNames: byteValueMap = new byteValueMap();
484
+ public circuitNames: byteValueMap = new byteValueMap();
485
+ public scheduleTypes: byteValueMap = new byteValueMap([
486
+ [0, { name: 'runonce', desc: 'Run Once', startDate: true, startTime: true, endTime: true, days: false, heatSource: true, heatSetpoint: true }],
487
+ [128, { name: 'repeat', desc: 'Repeats', startDate: false, startTime: true, endTime: true, days: 'multi', heatSource: true, heatSetpoint: true }]
488
+ ]);
489
+ public circuitGroupTypes: byteValueMap = new byteValueMap([
490
+ [0, { name: 'none', desc: 'Unspecified' }],
491
+ [1, { name: 'light', desc: 'Light' }],
492
+ [2, { name: 'circuit', desc: 'Circuit' }],
493
+ [3, { name: 'intellibrite', desc: 'IntelliBrite' }]
494
+ ]);
495
+ public groupCircuitStates: byteValueMap = new byteValueMap([
496
+ [0, { name: 'off', desc: 'Off' }],
497
+ [1, { name: 'on', desc: 'On' }]
498
+ ]);
499
+ public systemUnits: byteValueMap = new byteValueMap([
500
+ [0, { name: 'english', desc: 'English' }],
501
+ [4, { name: 'metric', desc: 'Metric' }]
502
+ ]);
503
+ public tempUnits: byteValueMap = new byteValueMap([
504
+ [0, { name: 'F', desc: 'Fahrenheit' }],
505
+ [4, { name: 'C', desc: 'Celsius' }]
506
+ ]);
507
+ public valveTypes: byteValueMap = new byteValueMap([
508
+ [0, { name: 'standard', desc: 'Standard' }],
509
+ [1, { name: 'intellivalve', desc: 'IntelliValve' }]
510
+ ]);
511
+ public valveModes: byteValueMap = new byteValueMap([
512
+ [0, { name: 'off', desc: 'Off' }],
513
+ [1, { name: 'pool', desc: 'Pool' }],
514
+ [2, { name: 'spa', dest: 'Spa' }],
515
+ [3, { name: 'spillway', desc: 'Spillway' }],
516
+ [4, { name: 'spadrain', desc: 'Spa Drain' }]
517
+ ]);
510
518
  public intellibriteActions: byteValueMap = new byteValueMap([
511
519
  [0, { name: 'ready', desc: 'Ready' }],
512
520
  [1, { name: 'sync', desc: 'Synchronizing' }],
@@ -1319,120 +1327,120 @@ export class BodyCommands extends BoardCommands {
1319
1327
  }
1320
1328
  public freezeProtectBodyOn: Date;
1321
1329
  public freezeProtectStart: Date;
1322
- public async syncFreezeProtection() {
1323
- try {
1324
- // Go through all the features and circuits to make sure we have the freeze protect set appropriately. The freeze
1325
- // flag will have already been set whether this is a Nixie setup or there is an OCP involved.
1330
+ public async syncFreezeProtection() {
1331
+ try {
1332
+ // Go through all the features and circuits to make sure we have the freeze protect set appropriately. The freeze
1333
+ // flag will have already been set whether this is a Nixie setup or there is an OCP involved.
1326
1334
 
1327
- // First turn on/off any features that are in our control that should be under our control. If this is an OCP we
1328
- // do not create features beyond those controlled by the OCP so we don't need to check these in that condition. That is
1329
- // why it first checks the controller type.
1330
- let freeze = utils.makeBool(state.freeze);
1331
- if (sys.controllerType === ControllerType.Nixie) {
1332
- // If we are a Nixie controller we need to evaluate the current freeze settings against the air temperature.
1333
- if (typeof state.temps.air !== 'undefined') freeze = state.temps.air <= sys.general.options.freezeThreshold;
1334
- else freeze = false;
1335
+ // First turn on/off any features that are in our control that should be under our control. If this is an OCP we
1336
+ // do not create features beyond those controlled by the OCP so we don't need to check these in that condition. That is
1337
+ // why it first checks the controller type.
1338
+ let freeze = utils.makeBool(state.freeze);
1339
+ if (sys.controllerType === ControllerType.Nixie) {
1340
+ // If we are a Nixie controller we need to evaluate the current freeze settings against the air temperature.
1341
+ if (typeof state.temps.air !== 'undefined') freeze = state.temps.air <= sys.general.options.freezeThreshold;
1342
+ else freeze = false;
1335
1343
 
1336
- // We need to know when we first turned the freeze protection on. This is because we will be rotating between pool and spa
1337
- // on shared body systems when both pool and spa have freeze protection checked.
1338
- if (state.freeze !== freeze) {
1339
- this.freezeProtectStart = freeze ? new Date() : undefined;
1340
- state.freeze = freeze;
1341
- }
1342
- for (let i = 0; i < sys.features.length; i++) {
1343
- let feature = sys.features.getItemByIndex(i);
1344
- let fstate = state.features.getItemById(feature.id, true);
1345
- if (!feature.freeze || !feature.isActive === true || feature.master !== 1) {
1346
- fstate.freezeProtect = false;
1347
- continue; // This is not affected by freeze conditions.
1348
- }
1349
- if (freeze && !fstate.isOn) {
1350
- // This feature should be on because we are freezing.
1351
- fstate.freezeProtect = true;
1352
- await sys.board.features.setFeatureStateAsync(feature.id, true);
1353
- }
1354
- else if (!freeze && fstate.freezeProtect) {
1355
- // This feature was turned on by freeze protection. We need to turn it off because it has warmed up.
1356
- fstate.freezeProtect = false;
1357
- await sys.board.features.setFeatureStateAsync(feature.id, false);
1358
- }
1359
- }
1360
- }
1361
- let bodyRotationChecked = false;
1362
- for (let i = 0; i < sys.circuits.length; i++) {
1363
- let circ = sys.circuits.getItemByIndex(i);
1364
- let cstate = state.circuits.getItemById(circ.id);
1365
- if (!circ.freeze || !circ.isActive === true || circ.master !== 1) {
1366
- cstate.freezeProtect = false;
1367
- continue; // This is not affected by freeze conditions.
1368
- }
1369
- if (sys.equipment.shared && freeze && (circ.id === 1 || circ.id === 6)) {
1370
- // Exit out of here because we already checked the body rotation. We only want to do this once since it can be expensive turning
1371
- // on a particular body.
1372
- if (bodyRotationChecked) continue;
1373
- // These are our body circuits so we need to check to see if they need to be rotated between pool and spa.
1374
- let pool = circ.id === 6 ? circ : sys.circuits.getItemById(6);
1375
- let spa = circ.id === 1 ? circ : sys.circuits.getItemById(1);
1376
- if (pool.freeze && spa.freeze) {
1377
- // We only need to rotate between pool and spa when they are both checked.
1378
- let pstate = circ.id === 6 ? cstate : state.circuits.getItemById(6);
1379
- let sstate = circ.id === 1 ? cstate : state.circuits.getItemById(1);
1380
- if (!pstate.isOn && !sstate.isOn) {
1381
- // Neither the pool or spa are on so we will turn on the pool first.
1382
- pstate.freezeProtect = true;
1383
- this.freezeProtectBodyOn = new Date();
1384
- await sys.board.circuits.setCircuitStateAsync(6, true);
1344
+ // We need to know when we first turned the freeze protection on. This is because we will be rotating between pool and spa
1345
+ // on shared body systems when both pool and spa have freeze protection checked.
1346
+ if (state.freeze !== freeze) {
1347
+ this.freezeProtectStart = freeze ? new Date() : undefined;
1348
+ state.freeze = freeze;
1349
+ }
1350
+ for (let i = 0; i < sys.features.length; i++) {
1351
+ let feature = sys.features.getItemByIndex(i);
1352
+ let fstate = state.features.getItemById(feature.id, true);
1353
+ if (!feature.freeze || !feature.isActive === true || feature.master !== 1) {
1354
+ fstate.freezeProtect = false;
1355
+ continue; // This is not affected by freeze conditions.
1356
+ }
1357
+ if (freeze && !fstate.isOn) {
1358
+ // This feature should be on because we are freezing.
1359
+ fstate.freezeProtect = true;
1360
+ await sys.board.features.setFeatureStateAsync(feature.id, true);
1361
+ }
1362
+ else if (!freeze && fstate.freezeProtect) {
1363
+ // This feature was turned on by freeze protection. We need to turn it off because it has warmed up.
1364
+ fstate.freezeProtect = false;
1365
+ await sys.board.features.setFeatureStateAsync(feature.id, false);
1366
+ }
1367
+ }
1385
1368
  }
1386
- else {
1387
- // If neither of the bodies were turned on for freeze protection then we need to ignore this.
1388
- if (!pstate.freezeProtect && !sstate.freezeProtect) {
1389
- this.freezeProtectBodyOn = undefined;
1390
- continue;
1391
- }
1369
+ let bodyRotationChecked = false;
1370
+ for (let i = 0; i < sys.circuits.length; i++) {
1371
+ let circ = sys.circuits.getItemByIndex(i);
1372
+ let cstate = state.circuits.getItemById(circ.id);
1373
+ if (!circ.freeze || !circ.isActive === true || circ.master !== 1) {
1374
+ cstate.freezeProtect = false;
1375
+ continue; // This is not affected by freeze conditions.
1376
+ }
1377
+ if (sys.equipment.shared && freeze && (circ.id === 1 || circ.id === 6)) {
1378
+ // Exit out of here because we already checked the body rotation. We only want to do this once since it can be expensive turning
1379
+ // on a particular body.
1380
+ if (bodyRotationChecked) continue;
1381
+ // These are our body circuits so we need to check to see if they need to be rotated between pool and spa.
1382
+ let pool = circ.id === 6 ? circ : sys.circuits.getItemById(6);
1383
+ let spa = circ.id === 1 ? circ : sys.circuits.getItemById(1);
1384
+ if (pool.freeze && spa.freeze) {
1385
+ // We only need to rotate between pool and spa when they are both checked.
1386
+ let pstate = circ.id === 6 ? cstate : state.circuits.getItemById(6);
1387
+ let sstate = circ.id === 1 ? cstate : state.circuits.getItemById(1);
1388
+ if (!pstate.isOn && !sstate.isOn) {
1389
+ // Neither the pool or spa are on so we will turn on the pool first.
1390
+ pstate.freezeProtect = true;
1391
+ this.freezeProtectBodyOn = new Date();
1392
+ await sys.board.circuits.setCircuitStateAsync(6, true);
1393
+ }
1394
+ else {
1395
+ // If neither of the bodies were turned on for freeze protection then we need to ignore this.
1396
+ if (!pstate.freezeProtect && !sstate.freezeProtect) {
1397
+ this.freezeProtectBodyOn = undefined;
1398
+ continue;
1399
+ }
1392
1400
 
1393
- // One of the two bodies is on so we need to check for the rotation. If it is time to rotate do the rotation.
1394
- if (typeof this.freezeProtectBodyOn === 'undefined') this.freezeProtectBodyOn = new Date();
1395
- let dt = new Date().getTime();
1396
- if (dt - 1000 * 60 * 15 > this.freezeProtectBodyOn.getTime()) {
1397
- logger.info(`Swapping bodies for freeze protection pool:${pstate.isOn} spa:${sstate.isOn} interval: ${utils.formatDuration(dt - this.freezeProtectBodyOn.getTime() / 1000)}`);
1398
- // 10 minutes has elapsed so we will be rotating to the other body.
1399
- if (pstate.isOn) {
1400
- // The setCircuitState method will handle turning off the pool body.
1401
- sstate.freezeProtect = true;
1402
- pstate.freezeProtect = false;
1403
- await sys.board.circuits.setCircuitStateAsync(1, true);
1401
+ // One of the two bodies is on so we need to check for the rotation. If it is time to rotate do the rotation.
1402
+ if (typeof this.freezeProtectBodyOn === 'undefined') this.freezeProtectBodyOn = new Date();
1403
+ let dt = new Date().getTime();
1404
+ if (dt - 1000 * 60 * 15 > this.freezeProtectBodyOn.getTime()) {
1405
+ logger.info(`Swapping bodies for freeze protection pool:${pstate.isOn} spa:${sstate.isOn} interval: ${utils.formatDuration(dt - this.freezeProtectBodyOn.getTime() / 1000)}`);
1406
+ // 10 minutes has elapsed so we will be rotating to the other body.
1407
+ if (pstate.isOn) {
1408
+ // The setCircuitState method will handle turning off the pool body.
1409
+ sstate.freezeProtect = true;
1410
+ pstate.freezeProtect = false;
1411
+ await sys.board.circuits.setCircuitStateAsync(1, true);
1412
+ }
1413
+ else {
1414
+ sstate.freezeProtect = false;
1415
+ pstate.freezeProtect = true;
1416
+ await sys.board.circuits.setCircuitStateAsync(6, true);
1417
+ }
1418
+ // Set a new date as this will be our rotation check now.
1419
+ this.freezeProtectBodyOn = new Date();
1420
+ }
1421
+ }
1422
+ }
1423
+ else {
1424
+ // Only this circuit is selected for freeze protection so we don't need any special treatment.
1425
+ cstate.freezeProtect = true;
1426
+ if (!cstate.isOn) await sys.board.circuits.setCircuitStateAsync(circ.id, true);
1427
+ }
1428
+ bodyRotationChecked = true;
1404
1429
  }
1405
- else {
1406
- sstate.freezeProtect = false;
1407
- pstate.freezeProtect = true;
1408
- await sys.board.circuits.setCircuitStateAsync(6, true);
1430
+ else if (freeze && !cstate.isOn) {
1431
+ // This circuit should be on because we are freezing.
1432
+ cstate.freezeProtect = true;
1433
+ await sys.board.circuits.setCircuitStateAsync(circ.id, true);
1434
+ }
1435
+ else if (!freeze && cstate.freezeProtect) {
1436
+ // This feature was turned on by freeze protection. We need to turn it off because it has warmed up.
1437
+ await sys.board.circuits.setCircuitStateAsync(circ.id, false);
1438
+ cstate.freezeProtect = false;
1409
1439
  }
1410
- // Set a new date as this will be our rotation check now.
1411
- this.freezeProtectBodyOn = new Date();
1412
- }
1413
1440
  }
1414
- }
1415
- else {
1416
- // Only this circuit is selected for freeze protection so we don't need any special treatment.
1417
- cstate.freezeProtect = true;
1418
- if (!cstate.isOn) await sys.board.circuits.setCircuitStateAsync(circ.id, true);
1419
- }
1420
- bodyRotationChecked = true;
1421
- }
1422
- else if (freeze && !cstate.isOn) {
1423
- // This circuit should be on because we are freezing.
1424
- cstate.freezeProtect = true;
1425
- await sys.board.features.setFeatureStateAsync(circ.id, true);
1426
1441
  }
1427
- else if (!freeze && cstate.freezeProtect) {
1428
- // This feature was turned on by freeze protection. We need to turn it off because it has warmed up.
1429
- await sys.board.circuits.setCircuitStateAsync(circ.id, false);
1430
- cstate.freezeProtect = false;
1431
- }
1432
- }
1442
+ catch (err) { logger.error(`syncFreezeProtection: Error synchronizing freeze protection states: ${err.message}`); }
1433
1443
  }
1434
- catch (err) { logger.error(`syncFreezeProtection: Error synchronizing freeze protection states`); }
1435
- }
1436
1444
 
1437
1445
  public async initFilters() {
1438
1446
  try {
@@ -1600,7 +1608,7 @@ export class BodyCommands extends BoardCommands {
1600
1608
  heatModes.push(this.board.valueMaps.heatModes.transformByName('off')); // In IC fw 1.047 off is no longer 0.
1601
1609
  let heatTypes = this.board.heaters.getInstalledHeaterTypes(bodyId);
1602
1610
  if (heatTypes.gas > 0) heatModes.push(this.board.valueMaps.heatModes.transformByName('heater'));
1603
- if (heatTypes.mastertemp > 0) heatModes.push(this.board.valueMaps.heatModes.transformByName('mastertemp'));
1611
+ if (heatTypes.mastertemp > 0) heatModes.push(this.board.valueMaps.heatModes.transformByName('mtheater'));
1604
1612
  if (heatTypes.solar > 0) {
1605
1613
  let hm = this.board.valueMaps.heatModes.transformByName('solar');
1606
1614
  heatModes.push(hm);
@@ -1637,52 +1645,53 @@ export class BodyCommands extends BoardCommands {
1637
1645
  }
1638
1646
  return arrSpas;
1639
1647
  }
1640
- public getBodyState(bodyCode: number): BodyTempState {
1641
- let assoc = sys.board.valueMaps.bodies.transform(bodyCode);
1642
- switch (assoc.name) {
1643
- case 'body1':
1644
- case 'pool':
1645
- return state.temps.bodies.getItemById(1);
1646
- case 'body2':
1647
- case 'spa':
1648
- return state.temps.bodies.getItemById(2);
1649
- case 'body3':
1650
- return state.temps.bodies.getItemById(3);
1651
- case 'body4':
1652
- return state.temps.bodies.getItemById(4);
1653
- case 'poolspa':
1654
- if (sys.equipment.shared && sys.equipment.maxBodies >= 2) {
1655
- let body = state.temps.bodies.getItemById(1);
1656
- if (body.isOn) return body;
1657
- body = state.temps.bodies.getItemById(2);
1658
- if (body.isOn) return body;
1659
- return state.temps.bodies.getItemById(1);
1648
+ public getBodyState(bodyCode: number): BodyTempState {
1649
+ let assoc = sys.board.valueMaps.bodies.transform(bodyCode);
1650
+ switch (assoc.name) {
1651
+ case 'body1':
1652
+ case 'pool':
1653
+ return state.temps.bodies.getItemById(1);
1654
+ case 'body2':
1655
+ case 'spa':
1656
+ return state.temps.bodies.getItemById(2);
1657
+ case 'body3':
1658
+ return state.temps.bodies.getItemById(3);
1659
+ case 'body4':
1660
+ return state.temps.bodies.getItemById(4);
1661
+ case 'poolspa':
1662
+ if (sys.equipment.shared && sys.equipment.maxBodies >= 2) {
1663
+ let body = state.temps.bodies.getItemById(1);
1664
+ if (body.isOn) return body;
1665
+ body = state.temps.bodies.getItemById(2);
1666
+ if (body.isOn) return body;
1667
+ return state.temps.bodies.getItemById(1);
1668
+ }
1669
+ else
1670
+ return state.temps.bodies.getItemById(1);
1660
1671
  }
1661
- else
1662
- return state.temps.bodies.getItemById(1);
1663
1672
  }
1664
- }
1665
- public isBodyOn(bodyCode: number): boolean {
1666
- let assoc = sys.board.valueMaps.bodies.transform(bodyCode);
1667
- switch (assoc.name) {
1668
- case 'body1':
1669
- case 'pool':
1670
- return state.temps.bodies.getItemById(1).isOn;
1671
- case 'body2':
1672
- case 'spa':
1673
- return state.temps.bodies.getItemById(2).isOn;
1674
- case 'body3':
1675
- return state.temps.bodies.getItemById(3).isOn;
1676
- case 'body4':
1677
- return state.temps.bodies.getItemById(4).isOn;
1678
- case 'poolspa':
1679
- if (sys.equipment.shared && sys.equipment.maxBodies >= 2)
1680
- return state.temps.bodies.getItemById(1).isOn || state.temps.bodies.getItemById(2).isOn;
1681
- else
1682
- return state.temps.bodies.getItemById(1).isOn;
1673
+ public isBodyOn(bodyCode: number): boolean {
1674
+ let assoc = sys.board.valueMaps.bodies.transform(bodyCode);
1675
+ switch (assoc.name) {
1676
+ case 'body1':
1677
+ case 'pool':
1678
+ return state.temps.bodies.getItemById(1).isOn;
1679
+ case 'body2':
1680
+ case 'spa':
1681
+ return state.temps.bodies.getItemById(2).isOn;
1682
+ case 'body3':
1683
+ return state.temps.bodies.getItemById(3).isOn;
1684
+ case 'body4':
1685
+ return state.temps.bodies.getItemById(4).isOn;
1686
+ case 'poolspa':
1687
+ if (sys.equipment.shared && sys.equipment.maxBodies >= 2) {
1688
+ return state.temps.bodies.getItemById(1).isOn === true || state.temps.bodies.getItemById(2).isOn === true;
1689
+ }
1690
+ else
1691
+ return state.temps.bodies.getItemById(1).isOn;
1692
+ }
1693
+ return false;
1683
1694
  }
1684
- return false;
1685
- }
1686
1695
  }
1687
1696
  export class PumpCommands extends BoardCommands {
1688
1697
  public async restore(rest: { poolConfig: any, poolState: any }, ctx: any, res: RestoreResults): Promise<boolean> {
@@ -1857,77 +1866,78 @@ export class PumpCommands extends BoardCommands {
1857
1866
  _availCircuits.push({ type: 'none', id: 255, name: 'Remove' });
1858
1867
  return _availCircuits;
1859
1868
  }
1869
+ public setPumpValveDelays(circuitIds: number[], delay?: number) {}
1860
1870
  }
1861
1871
  export class CircuitCommands extends BoardCommands {
1862
- public async restore(rest: { poolConfig: any, poolState: any }, ctx: any, res: RestoreResults): Promise<boolean> {
1863
- try {
1864
- // First delete the circuit/lightGroups that should be removed.
1865
- for (let i = 0; i < ctx.circuitGroups.remove.length; i++) {
1866
- let c = ctx.circuitGroups.remove[i];
1867
- try {
1868
- await sys.board.circuits.deleteCircuitGroupAsync(c);
1869
- res.addModuleSuccess('circuitGroup', `Remove: ${c.id}-${c.name}`);
1870
- } catch (err) { res.addModuleError('circuitGroup', `Remove: ${c.id}-${c.name}: ${err.message}`); }
1871
- }
1872
- for (let i = 0; i < ctx.lightGroups.remove.length; i++) {
1873
- let c = ctx.lightGroups.remove[i];
1874
- try {
1875
- await sys.board.circuits.deleteLightGroupAsync(c);
1876
- res.addModuleSuccess('lightGroup', `Remove: ${c.id}-${c.name}`);
1877
- } catch (err) { res.addModuleError('lightGroup', `Remove: ${c.id}-${c.name}: ${err.message}`); }
1878
- }
1879
- for (let i = 0; i < ctx.circuits.remove.length; i++) {
1880
- let c = ctx.circuits.remove[i];
1881
- try {
1882
- await sys.board.circuits.deleteCircuitAsync(c);
1883
- res.addModuleSuccess('circuit', `Remove: ${c.id}-${c.name}`);
1884
- } catch (err) { res.addModuleError('circuit', `Remove: ${c.id}-${c.name}: ${err.message}`); }
1885
- }
1886
- for (let i = 0; i < ctx.circuits.add.length; i++) {
1887
- let c = ctx.circuits.add[i];
1888
- try {
1889
- await sys.board.circuits.setCircuitAsync(c);
1890
- res.addModuleSuccess('circuit', `Add: ${c.id}-${c.name}`);
1891
- } catch (err) { res.addModuleError('circuit', `Add: ${c.id}-${c.name}: ${err.message}`); }
1892
- }
1893
- for (let i = 0; i < ctx.circuitGroups.add.length; i++) {
1894
- let c = ctx.circuitGroups.add[i];
1895
- try {
1896
- await sys.board.circuits.setCircuitGroupAsync(c);
1897
- res.addModuleSuccess('circuitGroup', `Add: ${c.id}-${c.name}`);
1898
- } catch (err) { res.addModuleError('circuitGroup', `Add: ${c.id}-${c.name}: ${err.message}`); }
1899
- }
1900
- for (let i = 0; i < ctx.lightGroups.add.length; i++) {
1901
- let c = ctx.lightGroups.add[i];
1902
- try {
1903
- await sys.board.circuits.setLightGroupAsync(c);
1904
- res.addModuleSuccess('lightGroup', `Add: ${c.id}-${c.name}`);
1905
- } catch (err) { res.addModuleError('lightGroup', `Add: ${c.id}-${c.name}: ${err.message}`); }
1906
- }
1907
- for (let i = 0; i < ctx.circuits.update.length; i++) {
1908
- let c = ctx.circuits.update[i];
1909
- try {
1910
- await sys.board.circuits.setCircuitAsync(c);
1911
- res.addModuleSuccess('circuit', `Update: ${c.id}-${c.name}`);
1912
- } catch (err) { res.addModuleError('circuit', `Update: ${c.id}-${c.name}: ${err.message}`); }
1913
- }
1914
- for (let i = 0; i < ctx.circuitGroups.update.length; i++) {
1915
- let c = ctx.circuitGroups.update[i];
1916
- try {
1917
- await sys.board.circuits.setCircuitGroupAsync(c);
1918
- res.addModuleSuccess('circuitGroup', `Update: ${c.id}-${c.name}`);
1919
- } catch (err) { res.addModuleError('circuitGroup', `Update: ${c.id}-${c.name}: ${err.message}`); }
1920
- }
1921
- for (let i = 0; i < ctx.lightGroups.add.length; i++) {
1922
- let c = ctx.lightGroups.update[i];
1872
+ public async restore(rest: { poolConfig: any, poolState: any }, ctx: any, res: RestoreResults): Promise<boolean> {
1923
1873
  try {
1924
- await sys.board.circuits.setLightGroupAsync(c);
1925
- res.addModuleSuccess('lightGroup', `Update: ${c.id}-${c.name}`);
1926
- } catch (err) { res.addModuleError('lightGroup', `Update: ${c.id}-${c.name}: ${err.message}`); }
1927
- }
1928
- return true;
1929
- } catch (err) { logger.error(`Error restoring circuits: ${err.message}`); res.addModuleError('system', `Error restoring circuits/features: ${err.message}`); return false; }
1930
- }
1874
+ // First delete the circuit/lightGroups that should be removed.
1875
+ for (let i = 0; i < ctx.circuitGroups.remove.length; i++) {
1876
+ let c = ctx.circuitGroups.remove[i];
1877
+ try {
1878
+ await sys.board.circuits.deleteCircuitGroupAsync(c);
1879
+ res.addModuleSuccess('circuitGroup', `Remove: ${c.id}-${c.name}`);
1880
+ } catch (err) { res.addModuleError('circuitGroup', `Remove: ${c.id}-${c.name}: ${err.message}`); }
1881
+ }
1882
+ for (let i = 0; i < ctx.lightGroups.remove.length; i++) {
1883
+ let c = ctx.lightGroups.remove[i];
1884
+ try {
1885
+ await sys.board.circuits.deleteLightGroupAsync(c);
1886
+ res.addModuleSuccess('lightGroup', `Remove: ${c.id}-${c.name}`);
1887
+ } catch (err) { res.addModuleError('lightGroup', `Remove: ${c.id}-${c.name}: ${err.message}`); }
1888
+ }
1889
+ for (let i = 0; i < ctx.circuits.remove.length; i++) {
1890
+ let c = ctx.circuits.remove[i];
1891
+ try {
1892
+ await sys.board.circuits.deleteCircuitAsync(c);
1893
+ res.addModuleSuccess('circuit', `Remove: ${c.id}-${c.name}`);
1894
+ } catch (err) { res.addModuleError('circuit', `Remove: ${c.id}-${c.name}: ${err.message}`); }
1895
+ }
1896
+ for (let i = 0; i < ctx.circuits.add.length; i++) {
1897
+ let c = ctx.circuits.add[i];
1898
+ try {
1899
+ await sys.board.circuits.setCircuitAsync(c);
1900
+ res.addModuleSuccess('circuit', `Add: ${c.id}-${c.name}`);
1901
+ } catch (err) { res.addModuleError('circuit', `Add: ${c.id}-${c.name}: ${err.message}`); }
1902
+ }
1903
+ for (let i = 0; i < ctx.circuitGroups.add.length; i++) {
1904
+ let c = ctx.circuitGroups.add[i];
1905
+ try {
1906
+ await sys.board.circuits.setCircuitGroupAsync(c);
1907
+ res.addModuleSuccess('circuitGroup', `Add: ${c.id}-${c.name}`);
1908
+ } catch (err) { res.addModuleError('circuitGroup', `Add: ${c.id}-${c.name}: ${err.message}`); }
1909
+ }
1910
+ for (let i = 0; i < ctx.lightGroups.add.length; i++) {
1911
+ let c = ctx.lightGroups.add[i];
1912
+ try {
1913
+ await sys.board.circuits.setLightGroupAsync(c);
1914
+ res.addModuleSuccess('lightGroup', `Add: ${c.id}-${c.name}`);
1915
+ } catch (err) { res.addModuleError('lightGroup', `Add: ${c.id}-${c.name}: ${err.message}`); }
1916
+ }
1917
+ for (let i = 0; i < ctx.circuits.update.length; i++) {
1918
+ let c = ctx.circuits.update[i];
1919
+ try {
1920
+ await sys.board.circuits.setCircuitAsync(c);
1921
+ res.addModuleSuccess('circuit', `Update: ${c.id}-${c.name}`);
1922
+ } catch (err) { res.addModuleError('circuit', `Update: ${c.id}-${c.name}: ${err.message}`); }
1923
+ }
1924
+ for (let i = 0; i < ctx.circuitGroups.update.length; i++) {
1925
+ let c = ctx.circuitGroups.update[i];
1926
+ try {
1927
+ await sys.board.circuits.setCircuitGroupAsync(c);
1928
+ res.addModuleSuccess('circuitGroup', `Update: ${c.id}-${c.name}`);
1929
+ } catch (err) { res.addModuleError('circuitGroup', `Update: ${c.id}-${c.name}: ${err.message}`); }
1930
+ }
1931
+ for (let i = 0; i < ctx.lightGroups.update.length; i++) {
1932
+ let c = ctx.lightGroups.update[i];
1933
+ try {
1934
+ await sys.board.circuits.setLightGroupAsync(c);
1935
+ res.addModuleSuccess('lightGroup', `Update: ${c.id}-${c.name}`);
1936
+ } catch (err) { res.addModuleError('lightGroup', `Update: ${c.id}-${c.name}: ${err.message}`); }
1937
+ }
1938
+ return true;
1939
+ } catch (err) { logger.error(`Error restoring circuits: ${err.message}`); res.addModuleError('system', `Error restoring circuits/features: ${err.message}`); return false; }
1940
+ }
1931
1941
  public async validateRestore(rest: { poolConfig: any, poolState: any }, ctxRoot): Promise<boolean> {
1932
1942
  try {
1933
1943
  let ctx = { errors: [], warnings: [], add: [], update: [], remove: [] };
@@ -1974,45 +1984,45 @@ export class CircuitCommands extends BoardCommands {
1974
1984
  return true;
1975
1985
  } catch (err) { logger.error(`Error validating circuits for restore: ${err.message}`); }
1976
1986
  }
1977
- public async checkEggTimerExpirationAsync() {
1978
- // turn off any circuits that have reached their egg timer;
1979
- // Nixie circuits we have 100% control over;
1980
- // but features/cg/lg may override OCP control
1981
- try {
1982
- for (let i = 0; i < sys.circuits.length; i++) {
1983
- let c = sys.circuits.getItemByIndex(i);
1984
- let cstate = state.circuits.getItemByIndex(i);
1985
- if (!cstate.isActive || !cstate.isOn) continue;
1986
- if (c.master === 1) {
1987
- await ncp.circuits.checkCircuitEggTimerExpirationAsync(cstate);
1988
- }
1989
- }
1990
- for (let i = 0; i < sys.features.length; i++) {
1991
- let fstate = state.features.getItemByIndex(i);
1992
- if (!fstate.isActive || !fstate.isOn) continue;
1993
- if (fstate.endTime.toDate() < new Timestamp().toDate()) {
1994
- await sys.board.circuits.setCircuitStateAsync(fstate.id, false);
1995
- fstate.emitEquipmentChange();
1996
- }
1997
- }
1998
- for (let i = 0; i < sys.circuitGroups.length; i++) {
1999
- let cgstate = state.circuitGroups.getItemByIndex(i);
2000
- if (!cgstate.isActive || !cgstate.isOn) continue;
2001
- if (cgstate.endTime.toDate() < new Timestamp().toDate()) {
2002
- await sys.board.circuits.setCircuitGroupStateAsync(cgstate.id, false);
2003
- cgstate.emitEquipmentChange();
2004
- }
2005
- }
2006
- for (let i = 0; i < sys.lightGroups.length; i++) {
2007
- let lgstate = state.lightGroups.getItemByIndex(i);
2008
- if (!lgstate.isActive || !lgstate.isOn) continue;
2009
- if (lgstate.endTime.toDate() < new Timestamp().toDate()) {
2010
- await sys.board.circuits.setLightGroupStateAsync(lgstate.id, false);
2011
- lgstate.emitEquipmentChange();
2012
- }
2013
- }
2014
- } catch (err) { logger.error(`checkEggTimerExpiration: Error synchronizing circuit relays ${err.message}`); }
2015
- }
1987
+ public async checkEggTimerExpirationAsync() {
1988
+ // turn off any circuits that have reached their egg timer;
1989
+ // Nixie circuits we have 100% control over;
1990
+ // but features/cg/lg may override OCP control
1991
+ try {
1992
+ for (let i = 0; i < sys.circuits.length; i++) {
1993
+ let c = sys.circuits.getItemByIndex(i);
1994
+ let cstate = state.circuits.getItemByIndex(i);
1995
+ if (!cstate.isActive || !cstate.isOn || typeof cstate.endTime === 'undefined') continue;
1996
+ if (c.master === 1) {
1997
+ await ncp.circuits.checkCircuitEggTimerExpirationAsync(cstate);
1998
+ }
1999
+ }
2000
+ for (let i = 0; i < sys.features.length; i++) {
2001
+ let fstate = state.features.getItemByIndex(i);
2002
+ if (!fstate.isActive || !fstate.isOn || typeof fstate.endTime === 'undefined') continue;
2003
+ if (fstate.endTime.toDate() < new Timestamp().toDate()) {
2004
+ await sys.board.circuits.setCircuitStateAsync(fstate.id, false);
2005
+ fstate.emitEquipmentChange();
2006
+ }
2007
+ }
2008
+ for (let i = 0; i < sys.circuitGroups.length; i++) {
2009
+ let cgstate = state.circuitGroups.getItemByIndex(i);
2010
+ if (!cgstate.isActive || !cgstate.isOn || typeof cgstate.endTime === 'undefined') continue;
2011
+ if (cgstate.endTime.toDate() < new Timestamp().toDate()) {
2012
+ await sys.board.circuits.setCircuitGroupStateAsync(cgstate.id, false);
2013
+ cgstate.emitEquipmentChange();
2014
+ }
2015
+ }
2016
+ for (let i = 0; i < sys.lightGroups.length; i++) {
2017
+ let lgstate = state.lightGroups.getItemByIndex(i);
2018
+ if (!lgstate.isActive || !lgstate.isOn || typeof lgstate.endTime === 'undefined') continue;
2019
+ if (lgstate.endTime.toDate() < new Timestamp().toDate()) {
2020
+ await sys.board.circuits.setLightGroupStateAsync(lgstate.id, false);
2021
+ lgstate.emitEquipmentChange();
2022
+ }
2023
+ }
2024
+ } catch (err) { logger.error(`checkEggTimerExpiration: Error synchronizing circuit relays ${err.message}`); }
2025
+ }
2016
2026
  public async syncCircuitRelayStates() {
2017
2027
  try {
2018
2028
  for (let i = 0; i < sys.circuits.length; i++) {
@@ -2025,162 +2035,283 @@ export class CircuitCommands extends BoardCommands {
2025
2035
  }
2026
2036
  } catch (err) { logger.error(`syncCircuitRelayStates: Error synchronizing circuit relays ${err.message}`); }
2027
2037
  }
2028
- public syncVirtualCircuitStates() {
2029
- try {
2030
- let arrCircuits = sys.board.valueMaps.virtualCircuits.toArray();
2031
- let poolStates = sys.board.bodies.getPoolStates();
2032
- let spaStates = sys.board.bodies.getSpaStates();
2033
- // The following should work for all board types if the virtualCiruit valuemaps use common names. The circuit ids can be
2034
- // different as well as the descriptions but these should have common names since they are all derived from existing states.
2038
+ public syncVirtualCircuitStates() {
2039
+ try {
2040
+ let arrCircuits = sys.board.valueMaps.virtualCircuits.toArray();
2041
+ let poolStates = sys.board.bodies.getPoolStates();
2042
+ let spaStates = sys.board.bodies.getSpaStates();
2043
+ // The following should work for all board types if the virtualCiruit valuemaps use common names. The circuit ids can be
2044
+ // different as well as the descriptions but these should have common names since they are all derived from existing states.
2035
2045
 
2036
- // This also removes virtual circuits depending on whether heaters exsits on the bodies. Not sure why we are doing this
2037
- // as the body data contains whether a body is heated or not. Perhapse some attached interface is using
2038
- // the virtual circuit list as a means to determine whether solar is available. That is totally flawed if that is the case.
2039
- for (let i = 0; i < arrCircuits.length; i++) {
2040
- let vc = arrCircuits[i];
2041
- let remove = false;
2042
- let bState = false;
2043
- let cstate: VirtualCircuitState = null;
2044
- switch (vc.name) {
2045
- case 'poolHeater':
2046
- // If any pool is heating up.
2047
- remove = true;
2048
- for (let j = 0; j < poolStates.length; j++) {
2049
- if (poolStates[j].heaterOptions.total > 0) remove = false;
2050
- }
2051
- if (!remove) {
2052
- // Determine whether the pool heater is on.
2053
- for (let j = 0; j < poolStates.length; j++)
2054
- if (sys.board.valueMaps.heatStatus.getName(poolStates[j].heatStatus) === 'heater') bState = true;
2055
- }
2056
- break;
2057
- case 'spaHeater':
2058
- remove = true;
2059
- for (let j = 0; j < spaStates.length; j++) {
2060
- if (spaStates[j].heaterOptions.total > 0) remove = false;
2061
- }
2062
- if (!remove) {
2063
- // Determine whether the spa heater is on.
2064
- for (let j = 0; j < spaStates.length; j++) {
2065
- if (sys.board.valueMaps.heatStatus.getName(spaStates[j].heatStatus) === 'heater') bState = true;
2066
- }
2067
- }
2068
- break;
2069
- case 'freeze':
2070
- // If freeze protection has been turned on.
2071
- bState = state.freeze;
2072
- break;
2073
- case 'poolSpa':
2074
- // If any pool or spa is on
2075
- for (let j = 0; j < poolStates.length && !bState; j++) {
2076
- if (poolStates[j].isOn) bState = true;
2077
- }
2078
- for (let j = 0; j < spaStates.length && !bState; j++) {
2079
- if (spaStates[j].isOn) bState = true;
2080
- }
2081
- break;
2082
- case 'solarHeat':
2083
- case 'solar':
2084
- // If solar is on for any body
2085
- remove = true;
2086
- for (let j = 0; j < poolStates.length; j++) {
2087
- if (poolStates[j].heaterOptions.solar + poolStates[j].heaterOptions.heatpump > 0) remove = false;
2088
- }
2089
- if (remove) {
2090
- for (let j = 0; j < spaStates.length; j++) {
2091
- if (spaStates[j].heaterOptions.solar + spaStates[j].heaterOptions.heatpump > 0) remove = false;
2092
- }
2093
- }
2094
- if (!remove) {
2095
- for (let j = 0; j < poolStates.length && !bState; j++) {
2096
- if (sys.board.valueMaps.heatStatus.getName(poolStates[j].heatStatus) === 'solar') bState = true;
2097
- }
2098
- for (let j = 0; j < spaStates.length && !bState; j++) {
2099
- if (sys.board.valueMaps.heatStatus.getName(spaStates[j].heatStatus) === 'solar') bState = true;
2100
- }
2101
- }
2102
- break;
2103
- case 'heater':
2104
- remove = true;
2105
- for (let j = 0; j < poolStates.length; j++) {
2106
- if (poolStates[j].heaterOptions.total > 0) remove = false;
2107
- }
2108
- if (remove) {
2109
- for (let j = 0; j < spaStates.length; j++) {
2110
- if (spaStates[j].heaterOptions.total > 0) remove = false;
2111
- }
2046
+ // This also removes virtual circuits depending on whether heaters exsits on the bodies. Not sure why we are doing this
2047
+ // as the body data contains whether a body is heated or not. Perhapse some attached interface is using
2048
+ // the virtual circuit list as a means to determine whether solar is available. That is totally flawed if that is the case.
2049
+ for (let i = 0; i < arrCircuits.length; i++) {
2050
+ let vc = arrCircuits[i];
2051
+ let remove = false;
2052
+ let bState = false;
2053
+ let cstate: VirtualCircuitState = null;
2054
+ switch (vc.name) {
2055
+ case 'poolHeater':
2056
+ // If any pool is heating up.
2057
+ remove = true;
2058
+ for (let j = 0; j < poolStates.length; j++) {
2059
+ if (poolStates[j].heaterOptions.total > 0) remove = false;
2060
+ }
2061
+ if (!remove) {
2062
+ // Determine whether the pool heater is on.
2063
+ for (let j = 0; j < poolStates.length; j++) {
2064
+ if (sys.board.valueMaps.heatStatus.getName(poolStates[j].heatStatus) === 'heater') {
2065
+ // In this instance we may have a delay underway.
2066
+ let hstate = state.heaters.find(x => x.bodyId === 1 && x.startupDelay === true && x.type.name !== 'solar');
2067
+ bState = typeof hstate === 'undefined';
2068
+ }
2069
+ }
2070
+ }
2071
+ break;
2072
+ case 'spaHeater':
2073
+ remove = true;
2074
+ for (let j = 0; j < spaStates.length; j++) {
2075
+ if (spaStates[j].heaterOptions.total > 0) remove = false;
2076
+ }
2077
+ if (!remove) {
2078
+ // Determine whether the spa heater is on.
2079
+ for (let j = 0; j < spaStates.length; j++) {
2080
+ if (sys.board.valueMaps.heatStatus.getName(spaStates[j].heatStatus) === 'heater') {
2081
+ // In this instance we may have a delay underway.
2082
+ let hstate = state.heaters.find(x => x.bodyId === 1 && x.startupDelay === true && x.type.name !== 'solar');
2083
+ bState = typeof hstate === 'undefined';
2084
+ }
2085
+ }
2086
+ //for (let j = 0; j < spaStates.length; j++) {
2087
+ // if (sys.board.valueMaps.heatStatus.getName(spaStates[j].heatStatus) === 'heater') bState = true;
2088
+ //}
2089
+ }
2090
+ break;
2091
+ case 'freeze':
2092
+ // If freeze protection has been turned on.
2093
+ bState = state.freeze;
2094
+ break;
2095
+ case 'poolSpa':
2096
+ // If any pool or spa is on
2097
+ for (let j = 0; j < poolStates.length && !bState; j++) {
2098
+ if (poolStates[j].isOn) bState = true;
2099
+ }
2100
+ for (let j = 0; j < spaStates.length && !bState; j++) {
2101
+ if (spaStates[j].isOn) bState = true;
2102
+ }
2103
+ break;
2104
+ case 'solarHeat':
2105
+ case 'solar':
2106
+ // If solar is on for any body
2107
+ remove = true;
2108
+ for (let j = 0; j < poolStates.length; j++) {
2109
+ if (poolStates[j].heaterOptions.solar + poolStates[j].heaterOptions.heatpump > 0) remove = false;
2110
+ }
2111
+ if (remove) {
2112
+ for (let j = 0; j < spaStates.length; j++) {
2113
+ if (spaStates[j].heaterOptions.solar + spaStates[j].heaterOptions.heatpump > 0) remove = false;
2114
+ }
2115
+ }
2116
+ if (!remove) {
2117
+ for (let j = 0; j < poolStates.length && !bState; j++) {
2118
+ if (sys.board.valueMaps.heatStatus.getName(poolStates[j].heatStatus) === 'solar') bState = true;
2119
+ }
2120
+ for (let j = 0; j < spaStates.length && !bState; j++) {
2121
+ if (sys.board.valueMaps.heatStatus.getName(spaStates[j].heatStatus) === 'solar') bState = true;
2122
+ }
2123
+ }
2124
+ break;
2125
+ case 'solar1':
2126
+ remove = true;
2127
+ for (let j = 0; j < poolStates.length; j++) {
2128
+ if (poolStates[j].id === 1 && poolStates[j].heaterOptions.solar) {
2129
+ remove = false;
2130
+ vc.desc = `${poolStates[j].name} Solar`;
2131
+ if (sys.board.valueMaps.heatStatus.getName(poolStates[j].heatStatus) === 'solar') {
2132
+ // In this instance we may have a delay underway.
2133
+ let hstate = state.heaters.find(x => x.bodyId === 1 && x.startupDelay === true && x.type.name === 'solar');
2134
+ bState = typeof hstate === 'undefined';
2135
+ }
2136
+ }
2137
+ }
2138
+ for (let j = 0; j < spaStates.length; j++) {
2139
+ if (spaStates[j].id === 1 && spaStates[j].heaterOptions.solar) {
2140
+ remove = false;
2141
+ vc.desc = `${spaStates[j].name} Solar`;
2142
+ if (sys.board.valueMaps.heatStatus.getName(spaStates[j].heatStatus) === 'solar') {
2143
+ // In this instance we may have a delay underway.
2144
+ let hstate = state.heaters.find(x => x.bodyId === 1 && x.startupDelay === true && x.type.name === 'solar');
2145
+ bState = typeof hstate === 'undefined';
2146
+ }
2147
+ }
2148
+ }
2149
+
2150
+ break;
2151
+ case 'solar2':
2152
+ remove = true;
2153
+ for (let j = 0; j < poolStates.length; j++) {
2154
+ if (poolStates[j].id === 2 && poolStates[j].heaterOptions.solar) {
2155
+ remove = false;
2156
+ vc.desc = `${poolStates[j].name} Solar`;
2157
+ if (sys.board.valueMaps.heatStatus.getName(spaStates[j].heatStatus) === 'solar') {
2158
+ // In this instance we may have a delay underway.
2159
+ let hstate = state.heaters.find(x => x.bodyId === 2 && x.startupDelay === true && x.type.name === 'solar');
2160
+ bState = typeof hstate === 'undefined';
2161
+ }
2162
+ }
2163
+ }
2164
+ for (let j = 0; j < spaStates.length; j++) {
2165
+ if (spaStates[j].id === 2 && spaStates[j].heaterOptions.solar) {
2166
+ remove = false;
2167
+ vc.desc = `${spaStates[j].name} Solar`;
2168
+ if (sys.board.valueMaps.heatStatus.getName(spaStates[j].heatStatus) === 'solar') {
2169
+ // In this instance we may have a delay underway.
2170
+ let hstate = state.heaters.find(x => x.bodyId === 2 && x.startupDelay === true && x.type.name === 'solar');
2171
+ bState = typeof hstate === 'undefined';
2172
+ }
2173
+ }
2174
+ }
2175
+ break;
2176
+ case 'solar3':
2177
+ remove = true;
2178
+ for (let j = 0; j < poolStates.length; j++) {
2179
+ if (poolStates[j].id === 3 && poolStates[j].heaterOptions.solar) {
2180
+ remove = false;
2181
+ vc.desc = `${poolStates[j].name} Solar`;
2182
+ if (sys.board.valueMaps.heatStatus.getName(spaStates[j].heatStatus) === 'solar') {
2183
+ // In this instance we may have a delay underway.
2184
+ let hstate = state.heaters.find(x => x.bodyId === 3 && x.startupDelay === true && x.type.name === 'solar');
2185
+ bState = typeof hstate === 'undefined';
2186
+ }
2187
+ }
2188
+ }
2189
+ for (let j = 0; j < spaStates.length; j++) {
2190
+ if (spaStates[j].id === 3 && spaStates[j].heaterOptions.solar) {
2191
+ remove = false;
2192
+ vc.desc = `${spaStates[j].name} Solar`;
2193
+ if (sys.board.valueMaps.heatStatus.getName(spaStates[j].heatStatus) === 'solar') {
2194
+ // In this instance we may have a delay underway.
2195
+ let hstate = state.heaters.find(x => x.bodyId === 3 && x.startupDelay === true && x.type.name === 'solar');
2196
+ bState = typeof hstate === 'undefined';
2197
+ }
2198
+ }
2199
+ }
2200
+
2201
+ break;
2202
+ case 'solar4':
2203
+ remove = true;
2204
+ for (let j = 0; j < poolStates.length; j++) {
2205
+ if (poolStates[j].id === 4 && poolStates[j].heaterOptions.solar) {
2206
+ remove = false;
2207
+ vc.desc = `${poolStates[j].name} Solar`;
2208
+ if (sys.board.valueMaps.heatStatus.getName(spaStates[j].heatStatus) === 'solar') {
2209
+ // In this instance we may have a delay underway.
2210
+ let hstate = state.heaters.find(x => x.bodyId === 4 && x.startupDelay === true && x.type.name === 'solar');
2211
+ bState = typeof hstate === 'undefined';
2212
+ }
2213
+ }
2214
+ }
2215
+ for (let j = 0; j < spaStates.length; j++) {
2216
+ if (spaStates[j].id === 4 && spaStates[j].heaterOptions.solar) {
2217
+ remove = false;
2218
+ vc.desc = `${spaStates[j].name} Solar`;
2219
+ if (sys.board.valueMaps.heatStatus.getName(spaStates[j].heatStatus) === 'solar') {
2220
+ // In this instance we may have a delay underway.
2221
+ let hstate = state.heaters.find(x => x.bodyId === 4 && x.startupDelay === true && x.type.name === 'solar');
2222
+ bState = typeof hstate === 'undefined';
2223
+ }
2224
+ }
2225
+ }
2226
+ break;
2227
+ case 'heater':
2228
+ remove = true;
2229
+ for (let j = 0; j < poolStates.length; j++) {
2230
+ if (poolStates[j].heaterOptions.total > 0) remove = false;
2231
+ }
2232
+ if (remove) {
2233
+ for (let j = 0; j < spaStates.length; j++) {
2234
+ if (spaStates[j].heaterOptions.total > 0) remove = false;
2235
+ }
2236
+ }
2237
+ if (!remove) {
2238
+ for (let j = 0; j < poolStates.length && !bState; j++) {
2239
+ let heat = sys.board.valueMaps.heatStatus.getName(poolStates[j].heatStatus);
2240
+ if (heat !== 'off') bState = true;
2241
+ }
2242
+ for (let j = 0; j < spaStates.length && !bState; j++) {
2243
+ let heat = sys.board.valueMaps.heatStatus.getName(spaStates[j].heatStatus);
2244
+ if (heat !== 'off') bState = true;
2245
+ }
2246
+ }
2247
+ break;
2248
+ default:
2249
+ remove = true;
2250
+ break;
2251
+ }
2252
+ if (remove) {
2253
+ if (state.virtualCircuits.exists(x => vc.val === x.id)) {
2254
+ cstate = state.virtualCircuits.getItemById(vc.val, true);
2255
+ cstate.isActive = false;
2256
+ cstate.emitEquipmentChange();
2257
+ }
2258
+ state.virtualCircuits.removeItemById(vc.val);
2259
+ }
2260
+ else {
2261
+ cstate = state.virtualCircuits.getItemById(vc.val, true);
2262
+ cstate.isActive = true;
2263
+ if (cstate !== null) {
2264
+ cstate.isOn = bState;
2265
+ cstate.type = vc.val;
2266
+ cstate.name = vc.desc;
2267
+ }
2268
+ }
2112
2269
  }
2113
- if (!remove) {
2114
- for (let j = 0; j < poolStates.length && !bState; j++) {
2115
- let heat = sys.board.valueMaps.heatStatus.getName(poolStates[j].heatStatus);
2116
- if (heat !== 'off') bState = true;
2117
- }
2118
- for (let j = 0; j < spaStates.length && !bState; j++) {
2119
- let heat = sys.board.valueMaps.heatStatus.getName(spaStates[j].heatStatus);
2120
- if (heat !== 'off') bState = true;
2121
- }
2270
+ } catch (err) { logger.error(`Error syncronizing virtual circuits`); }
2271
+ }
2272
+ public async setCircuitStateAsync(id: number, val: boolean, ignoreDelays?: boolean): Promise<ICircuitState> {
2273
+ sys.board.suspendStatus(true);
2274
+ try {
2275
+ // We need to do some routing here as it is now critical that circuits, groups, and features
2276
+ // have their own processing. The virtual controller used to only deal with one circuit.
2277
+ if (sys.board.equipmentIds.circuitGroups.isInRange(id))
2278
+ return await sys.board.circuits.setCircuitGroupStateAsync(id, val);
2279
+ else if (sys.board.equipmentIds.features.isInRange(id))
2280
+ return await sys.board.features.setFeatureStateAsync(id, val);
2281
+ let circuit: ICircuit = sys.circuits.getInterfaceById(id, false, { isActive: false });
2282
+ if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError(`Circuit or Feature id ${id} not valid`, id, 'Circuit'));
2283
+ let circ = state.circuits.getInterfaceById(id, circuit.isActive !== false);
2284
+ let newState = utils.makeBool(val);
2285
+ // First, if we are turning the circuit on, lets determine whether the circuit is a pool or spa circuit and if this is a shared system then we need
2286
+ // to turn off the other body first.
2287
+ //[12, { name: 'pool', desc: 'Pool', hasHeatSource: true }],
2288
+ //[13, { name: 'spa', desc: 'Spa', hasHeatSource: true }]
2289
+ let func = sys.board.valueMaps.circuitFunctions.get(circuit.type);
2290
+ if (newState && (func.name === 'pool' || func.name === 'spa') && sys.equipment.shared === true) {
2291
+ // If we are shared we need to turn off the other circuit.
2292
+ let offType = func.name === 'pool' ? sys.board.valueMaps.circuitFunctions.getValue('spa') : sys.board.valueMaps.circuitFunctions.getValue('pool');
2293
+ let off = sys.circuits.get().filter(elem => elem.type === offType);
2294
+ // Turn the circuits off that are part of the shared system. We are going back to the board
2295
+ // just in case we got here for a circuit that isn't on the current defined panel.
2296
+ for (let i = 0; i < off.length; i++) {
2297
+ let coff = off[i];
2298
+ await sys.board.circuits.setCircuitStateAsync(coff.id, false);
2299
+ }
2122
2300
  }
2123
- break;
2124
- default:
2125
- remove = true;
2126
- break;
2127
- }
2128
- if (remove)
2129
- state.virtualCircuits.removeItemById(vc.val);
2130
- else {
2131
- cstate = state.virtualCircuits.getItemById(vc.val, true);
2132
- if (cstate !== null) {
2133
- cstate.isOn = bState;
2134
- cstate.type = vc.val;
2135
- cstate.name = vc.desc;
2136
- }
2301
+ if (id === 6) state.temps.bodies.getItemById(1, true).isOn = val;
2302
+ else if (id === 1) state.temps.bodies.getItemById(2, true).isOn = val;
2303
+ // Let the main nixie controller set the circuit state and affect the relays if it needs to.
2304
+ await ncp.circuits.setCircuitStateAsync(circ, newState);
2305
+ await sys.board.syncEquipmentItems();
2306
+ return state.circuits.getInterfaceById(circ.id);
2137
2307
  }
2138
- }
2139
- } catch (err) { logger.error(`Error syncronizing virtual circuits`); }
2140
- }
2141
- public async setCircuitStateAsync(id: number, val: boolean): Promise<ICircuitState> {
2142
- sys.board.suspendStatus(true);
2143
- try {
2144
- // We need to do some routing here as it is now critical that circuits, groups, and features
2145
- // have their own processing. The virtual controller used to only deal with one circuit.
2146
- if (sys.board.equipmentIds.circuitGroups.isInRange(id))
2147
- return await sys.board.circuits.setCircuitGroupStateAsync(id, val);
2148
- else if (sys.board.equipmentIds.features.isInRange(id))
2149
- return await sys.board.features.setFeatureStateAsync(id, val);
2150
- let circuit: ICircuit = sys.circuits.getInterfaceById(id, false, { isActive: false });
2151
- if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError(`Circuit or Feature id ${id} not valid`, id, 'Circuit'));
2152
- let circ = state.circuits.getInterfaceById(id, circuit.isActive !== false);
2153
- let newState = utils.makeBool(val);
2154
- // First, if we are turning the circuit on, lets determine whether the circuit is a pool or spa circuit and if this is a shared system then we need
2155
- // to turn off the other body first.
2156
- //[12, { name: 'pool', desc: 'Pool', hasHeatSource: true }],
2157
- //[13, { name: 'spa', desc: 'Spa', hasHeatSource: true }]
2158
- let func = sys.board.valueMaps.circuitFunctions.get(circuit.type);
2159
- if (newState && (func.name === 'pool' || func.name === 'spa') && sys.equipment.shared === true) {
2160
- // If we are shared we need to turn off the other circuit.
2161
- let offType = func.name === 'pool' ? sys.board.valueMaps.circuitFunctions.getValue('spa') : sys.board.valueMaps.circuitFunctions.getValue('pool');
2162
- let off = sys.circuits.get().filter(elem => elem.type === offType);
2163
- // Turn the circuits off that are part of the shared system. We are going back to the board
2164
- // just in case we got here for a circuit that isn't on the current defined panel.
2165
- for (let i = 0; i < off.length; i++) {
2166
- let coff = off[i];
2167
- await sys.board.circuits.setCircuitStateAsync(coff.id, false);
2308
+ catch (err) { return Promise.reject(`Nixie: Error setCircuitStateAsync ${err.message}`); }
2309
+ finally {
2310
+ ncp.pumps.syncPumpStates();
2311
+ sys.board.suspendStatus(false);
2312
+ state.emitEquipmentChanges();
2168
2313
  }
2169
- }
2170
- if (id === 6) state.temps.bodies.getItemById(1, true).isOn = val;
2171
- else if (id === 1) state.temps.bodies.getItemById(2, true).isOn = val;
2172
- // Let the main nixie controller set the circuit state and affect the relays if it needs to.
2173
- await ncp.circuits.setCircuitStateAsync(circ, newState);
2174
- await sys.board.syncEquipmentItems();
2175
- return state.circuits.getInterfaceById(circ.id);
2176
- }
2177
- catch (err) { return Promise.reject(`Nixie: Error setCircuitStateAsync ${err.message}`); }
2178
- finally {
2179
- ncp.pumps.syncPumpStates();
2180
- sys.board.suspendStatus(false);
2181
- state.emitEquipmentChanges();
2182
2314
  }
2183
- }
2184
2315
  public async toggleCircuitStateAsync(id: number): Promise<ICircuitState> {
2185
2316
  let circ = state.circuits.getInterfaceById(id);
2186
2317
  return await this.setCircuitStateAsync(id, !(circ.isOn || false));
@@ -2250,7 +2381,11 @@ export class CircuitCommands extends BoardCommands {
2250
2381
  return arrRefs;
2251
2382
  }
2252
2383
  public getLightThemes(type?: number) { return sys.board.valueMaps.lightThemes.toArray(); }
2253
- public getCircuitFunctions() { return sys.board.valueMaps.circuitFunctions.toArray(); }
2384
+ public getCircuitFunctions() {
2385
+ let cf = sys.board.valueMaps.circuitFunctions.toArray();
2386
+ if (!sys.equipment.shared) cf = cf.filter(x => { return x.name !== 'spillway' && x.name !== 'spadrain' });
2387
+ return cf;
2388
+ }
2254
2389
  public getCircuitNames() { return [...sys.board.valueMaps.circuitNames.toArray(), ...sys.board.valueMaps.customNames.toArray()]; }
2255
2390
  public async setCircuitAsync(data: any): Promise<ICircuit> {
2256
2391
  try {
@@ -2631,8 +2766,85 @@ export class CircuitCommands extends BoardCommands {
2631
2766
  logger.error(`Error setting end time for ${thing.id}: ${err}`)
2632
2767
  }
2633
2768
  }
2769
+ public async turnOffDrainCircuits(ignoreDelays: boolean) {
2770
+ try {
2771
+ {
2772
+ let drt = sys.board.valueMaps.circuitFunctions.getValue('spadrain');
2773
+ let drains = sys.circuits.filter(x => { return x.type === drt });
2774
+ for (let i = 0; i < drains.length; i++) {
2775
+ let drain = drains.getItemByIndex(i);
2776
+ let sdrain = state.circuits.getItemById(drain.id);
2777
+ if (sdrain.isOn) await sys.board.circuits.setCircuitStateAsync(drain.id, false, ignoreDelays);
2778
+ sdrain.startDelay = false;
2779
+ sdrain.stopDelay = false;
2780
+ }
2781
+ }
2782
+ {
2783
+ let drt = sys.board.valueMaps.featureFunctions.getValue('spadrain');
2784
+ let drains = sys.features.filter(x => { return x.type === drt });
2785
+ for (let i = 0; i < drains.length; i++) {
2786
+ let drain = drains.getItemByIndex(i);
2787
+ let sdrain = state.features.getItemById(drain.id);
2788
+ if (sdrain.isOn) await sys.board.features.setFeatureStateAsync(drain.id, false, ignoreDelays);
2789
+ }
2790
+ }
2791
+
2792
+ } catch (err) { return Promise.reject(new BoardProcessError(`turnOffDrainCircuits: ${err.message}`)); }
2793
+ }
2794
+ public async turnOffCleanerCircuits(bstate: BodyTempState, ignoreDelays?: boolean) {
2795
+ try {
2796
+ // First we have to get all the cleaner circuits that are associated with the
2797
+ // body. To do this we get the circuit functions for all cleaner types associated with the body.
2798
+ //
2799
+ // Cleaner ciruits can always be turned off. However, they cannot always be turned on.
2800
+ let arrTypes = sys.board.valueMaps.circuitFunctions.toArray().filter(x => { return x.name.indexOf('cleaner') !== -1 && x.body === bstate.id; });
2801
+ let cleaners = sys.circuits.filter(x => { return arrTypes.findIndex(t => { return t.val === x.type }) !== -1 });
2802
+ // So now we should have all the cleaner circuits so lets make sure they are off.
2803
+ for (let i = 0; i < cleaners.length; i++) {
2804
+ let cleaner = cleaners.getItemByIndex(i);
2805
+ if (cleaner.isActive) {
2806
+ let cstate = state.circuits.getItemById(cleaner.id, true);
2807
+ if (cstate.isOn || cstate.startDelay) await sys.board.circuits.setCircuitStateAsync(cleaner.id, false, ignoreDelays);
2808
+ }
2809
+ }
2810
+ } catch (err) { return Promise.reject(new BoardProcessError(`turnOffCleanerCircuits: ${err.message}`)); }
2811
+ }
2812
+ public async turnOffSpillwayCircuits(ignoreDelays?: boolean) {
2813
+ try {
2814
+ {
2815
+ let arrTypes = sys.board.valueMaps.circuitFunctions.toArray().filter(x => { return x.name.indexOf('spillway') !== -1 });
2816
+ let spillways = sys.circuits.filter(x => { return arrTypes.findIndex(t => { return t.val === x.type }) !== -1 });
2817
+ // So now we should have all the cleaner circuits so lets make sure they are off.
2818
+ for (let i = 0; i < spillways.length; i++) {
2819
+ let spillway = spillways.getItemByIndex(i);
2820
+ if (spillway.isActive) {
2821
+ let cstate = state.circuits.getItemById(spillway.id, true);
2822
+ if (cstate.isOn || cstate.startDelay) await sys.board.circuits.setCircuitStateAsync(spillway.id, false, ignoreDelays);
2823
+ }
2824
+ }
2825
+ }
2826
+ {
2827
+ let arrTypes = sys.board.valueMaps.featureFunctions.toArray().filter(x => { return x.name.indexOf('spillway') !== -1 });
2828
+ let spillways = sys.features.filter(x => { return arrTypes.findIndex(t => { return t.val === x.type }) !== -1 });
2829
+ // So now we should have all the cleaner features so lets make sure they are off.
2830
+ for (let i = 0; i < spillways.length; i++) {
2831
+ let spillway = spillways.getItemByIndex(i);
2832
+ if (spillway.isActive) {
2833
+ let cstate = state.features.getItemById(spillway.id, true);
2834
+ if (cstate.isOn) await sys.board.features.setFeatureStateAsync(spillway.id, false, ignoreDelays);
2835
+ }
2836
+ }
2837
+ }
2838
+ } catch (err) { return Promise.reject(new BoardProcessError(`turnOffSpillwayCircuits: ${err.message}`)); }
2839
+ }
2634
2840
  }
2635
2841
  export class FeatureCommands extends BoardCommands {
2842
+ public getFeatureFunctions() {
2843
+ let cf = sys.board.valueMaps.featureFunctions.toArray();
2844
+ if (!sys.equipment.shared) cf = cf.filter(x => { return x.name !== 'spillway' && x.name !== 'spadrain' });
2845
+ return cf;
2846
+ }
2847
+
2636
2848
  public async restore(rest: { poolConfig: any, poolState: any }, ctx: any, res: RestoreResults): Promise<boolean> {
2637
2849
  try {
2638
2850
  // First delete the features that should be removed.
@@ -2727,7 +2939,7 @@ export class FeatureCommands extends BoardCommands {
2727
2939
  else
2728
2940
  Promise.reject(new InvalidEquipmentIdError('Feature id has not been defined', undefined, 'Feature'));
2729
2941
  }
2730
- public async setFeatureStateAsync(id: number, val: boolean): Promise<ICircuitState> {
2942
+ public async setFeatureStateAsync(id: number, val: boolean, ignoreDelays?: boolean): Promise<ICircuitState> {
2731
2943
  try {
2732
2944
  if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError(`Invalid feature id: ${id}`, id, 'Feature'));
2733
2945
  if (!sys.board.equipmentIds.features.isInRange(id)) return Promise.reject(new InvalidEquipmentIdError(`Invalid feature id: ${id}`, id, 'Feature'));
@@ -3197,7 +3409,7 @@ export class HeaterCommands extends BoardCommands {
3197
3409
  try {
3198
3410
  // pull a little trick to first add the data then perform the update. This way we won't get a new id or
3199
3411
  // it won't error out.
3200
- sys.heaters.getItemById(h, true);
3412
+ sys.heaters.getItemById(h.id, true);
3201
3413
  await sys.board.heaters.setHeaterAsync(h);
3202
3414
  res.addModuleSuccess('heater', `Add: ${h.id}-${h.name}`);
3203
3415
  } catch (err) { res.addModuleError('heater', `Add: ${h.id}-${h.name}: ${err.message}`); }
@@ -3224,7 +3436,18 @@ export class HeaterCommands extends BoardCommands {
3224
3436
  return ctx;
3225
3437
  } catch (err) { logger.error(`Error validating heaters for restore: ${err.message}`); }
3226
3438
  }
3227
-
3439
+ public getHeatersByCircuitId(circuitId: number): Heater[] {
3440
+ let heaters: Heater[] = [];
3441
+ let bodyId = circuitId === 6 ? 1 : circuitId === 1 ? 2 : 0;
3442
+ if (bodyId > 0) {
3443
+ for (let i = 0; i < sys.heaters.length; i++) {
3444
+ let heater = sys.heaters.getItemByIndex(i);
3445
+ if (!heater.isActive) continue;
3446
+ if (bodyId === heater.body || sys.equipment.shared && heater.body === 32) heaters.push(heater);
3447
+ }
3448
+ }
3449
+ return heaters;
3450
+ }
3228
3451
  public getInstalledHeaterTypes(body?: number): any {
3229
3452
  let heaters = sys.heaters.get();
3230
3453
  let types = sys.board.valueMaps.heaterTypes.toArray();
@@ -3469,17 +3692,19 @@ export class HeaterCommands extends BoardCommands {
3469
3692
  let body: BodyTempState = bodies[i];
3470
3693
  let cfgBody: Body = sys.bodies.getItemById(body.id);
3471
3694
  let isHeating = false;
3695
+ let isCooling = false;
3696
+ let hstatus = sys.board.valueMaps.heatStatus.getName(body.heatStatus);
3697
+ let mode = sys.board.valueMaps.heatModes.getName(body.heatMode);
3472
3698
  if (body.isOn) {
3473
3699
  if (typeof body.temp === 'undefined' && heaters.length > 0) logger.warn(`The body temperature for ${body.name} cannot be determined. Heater status for this body cannot be calculated.`);
3474
3700
  for (let j = 0; j < heaters.length; j++) {
3475
3701
  let heater: Heater = heaters[j];
3476
3702
  if (heater.isActive === false) continue;
3477
3703
  let isOn = false;
3478
- let isCooling = false;
3479
- let sensorTemp = state.temps.waterSensor1;
3480
- if (body.id === 4) sensorTemp = state.temps.waterSensor4;
3481
- if (body.id === 3) sensorTemp = state.temps.waterSensor3;
3482
- if (body.id === 2 && !sys.equipment.shared) sensorTemp = state.temps.waterSensor2;
3704
+ //let sensorTemp = state.temps.waterSensor1;
3705
+ //if (body.id === 4) sensorTemp = state.temps.waterSensor4;
3706
+ //if (body.id === 3) sensorTemp = state.temps.waterSensor3;
3707
+ //if (body.id === 2 && !sys.equipment.shared) sensorTemp = state.temps.waterSensor2;
3483
3708
 
3484
3709
  // Determine whether the heater can be used on this body.
3485
3710
  let isAssociated = false;
@@ -3506,92 +3731,92 @@ export class HeaterCommands extends BoardCommands {
3506
3731
  // logger.silly(`Heater ${heater.name} is ${isAssociated === true ? '' : 'not '}associated with ${body.name}`);
3507
3732
  if (isAssociated) {
3508
3733
  let htype = sys.board.valueMaps.heaterTypes.transform(heater.type);
3509
- let status = sys.board.valueMaps.heatStatus.transform(body.heatStatus);
3510
3734
  let hstate = state.heaters.getItemById(heater.id, true);
3511
3735
  if (heater.master === 1) {
3512
- // We need to do our own calculation as to whether it is on. This is for Nixie heaters.
3513
- let mode = sys.board.valueMaps.heatModes.getName(body.heatMode);
3514
- switch (htype.name) {
3515
- case 'solar':
3516
- if (mode === 'solar' || mode === 'solarpref') {
3517
- // Measure up against start and stop temp deltas for effective solar heating.
3518
- if (body.temp < cfgBody.heatSetpoint &&
3519
- state.temps.solar > body.temp + (hstate.isOn ? heater.stopTempDelta : heater.startTempDelta)) {
3520
- isOn = true;
3521
- body.heatStatus = sys.board.valueMaps.heatStatus.getValue('solar');
3522
- isHeating = true;
3523
- }
3524
- else if (heater.coolingEnabled && body.temp > cfgBody.coolSetpoint && state.heliotrope.isNight &&
3525
- state.temps.solar > body.temp + (hstate.isOn ? heater.stopTempDelta : heater.startTempDelta)) {
3526
- isOn = true;
3527
- body.heatStatus = sys.board.valueMaps.heatStatus.getValue('cooling');
3528
- isHeating = true;
3529
- isCooling = true;
3530
- }
3531
- }
3532
- break;
3533
- case 'ultratemp':
3534
- // We need to determine whether we are going to use the air temp or the solar temp
3535
- // for the sensor.
3536
- let deltaTemp = Math.max(state.temps.air, state.temps.solar || 0);
3537
- if (mode === 'ultratemp' || mode === 'ultratemppref') {
3538
- if (body.temp < cfgBody.heatSetpoint &&
3539
- deltaTemp > body.temp + heater.differentialTemp || 0) {
3540
- isOn = true;
3541
- body.heatStatus = sys.board.valueMaps.heatStatus.getValue('hpheat');
3542
- isHeating = true;
3543
- isCooling = false;
3736
+ if (hstatus !== 'cooldown') {
3737
+ // We need to do our own calculation as to whether it is on. This is for Nixie heaters.
3738
+ switch (htype.name) {
3739
+ case 'solar':
3740
+ if (mode === 'solar' || mode === 'solarpref') {
3741
+ // Measure up against start and stop temp deltas for effective solar heating.
3742
+ if (body.temp < cfgBody.heatSetpoint &&
3743
+ state.temps.solar > body.temp + (hstate.isOn ? heater.stopTempDelta : heater.startTempDelta)) {
3744
+ isOn = true;
3745
+ body.heatStatus = sys.board.valueMaps.heatStatus.getValue('solar');
3746
+ isHeating = true;
3747
+ }
3748
+ else if (heater.coolingEnabled && body.temp > cfgBody.coolSetpoint && state.heliotrope.isNight &&
3749
+ state.temps.solar > body.temp + (hstate.isOn ? heater.stopTempDelta : heater.startTempDelta)) {
3750
+ isOn = true;
3751
+ body.heatStatus = sys.board.valueMaps.heatStatus.getValue('cooling');
3752
+ isHeating = true;
3753
+ isCooling = true;
3754
+ }
3544
3755
  }
3545
- else if (body.temp > cfgBody.coolSetpoint && heater.coolingEnabled) {
3546
- isOn = true;
3547
- body.heatStatus = sys.board.valueMaps.heatStatus.getValue('hpcool');
3548
- isHeating = true;
3549
- isCooling = true;
3756
+ break;
3757
+ case 'ultratemp':
3758
+ // We need to determine whether we are going to use the air temp or the solar temp
3759
+ // for the sensor.
3760
+ let deltaTemp = Math.max(state.temps.air, state.temps.solar || 0);
3761
+ if (mode === 'ultratemp' || mode === 'ultratemppref') {
3762
+ if (body.temp < cfgBody.heatSetpoint &&
3763
+ deltaTemp > body.temp + heater.differentialTemp || 0) {
3764
+ isOn = true;
3765
+ body.heatStatus = sys.board.valueMaps.heatStatus.getValue('hpheat');
3766
+ isHeating = true;
3767
+ isCooling = false;
3768
+ }
3769
+ else if (body.temp > cfgBody.coolSetpoint && heater.coolingEnabled) {
3770
+ isOn = true;
3771
+ body.heatStatus = sys.board.valueMaps.heatStatus.getValue('hpcool');
3772
+ isHeating = true;
3773
+ isCooling = true;
3774
+ }
3550
3775
  }
3551
- }
3552
- break;
3553
- case 'mastertemp':
3554
- if (mode === 'mtheater') {
3555
- if (body.temp < cfgBody.setPoint) {
3556
- isOn = true;
3557
- body.heatStatus = sys.board.valueMaps.heaterTypes.getValue('mtheater');
3558
- isHeating = true;
3776
+ break;
3777
+ case 'mastertemp':
3778
+ if (mode === 'mtheater') {
3779
+ if (body.temp < cfgBody.setPoint) {
3780
+ isOn = true;
3781
+ body.heatStatus = sys.board.valueMaps.heatStatus.getValue('mtheat');
3782
+ isHeating = true;
3783
+ }
3559
3784
  }
3560
- }
3561
- break;
3562
- case 'maxetherm':
3563
- case 'gas':
3564
- if (mode === 'heater') {
3565
- if (body.temp < cfgBody.setPoint) {
3566
- isOn = true;
3567
- body.heatStatus = sys.board.valueMaps.heatStatus.getValue('heater');
3568
- isHeating = true;
3785
+ break;
3786
+ case 'maxetherm':
3787
+ case 'gas':
3788
+ if (mode === 'heater') {
3789
+ if (body.temp < cfgBody.setPoint) {
3790
+ isOn = true;
3791
+ body.heatStatus = sys.board.valueMaps.heatStatus.getValue('heater');
3792
+ isHeating = true;
3793
+ }
3569
3794
  }
3570
- }
3571
- else if (mode === 'solarpref' || mode === 'heatpumppref') {
3572
- // If solar should be running gas heater should be off.
3573
- if (body.temp < cfgBody.setPoint &&
3574
- state.temps.solar > body.temp + (hstate.isOn ? heater.stopTempDelta : heater.startTempDelta)) isOn = false;
3575
- else if (body.temp < cfgBody.setPoint) {
3576
- isOn = true;
3577
- body.heatStatus = sys.board.valueMaps.heatStatus.getValue('heater');
3578
- isHeating = true;
3795
+ else if (mode === 'solarpref' || mode === 'heatpumppref') {
3796
+ // If solar should be running gas heater should be off.
3797
+ if (body.temp < cfgBody.setPoint &&
3798
+ state.temps.solar > body.temp + (hstate.isOn ? heater.stopTempDelta : heater.startTempDelta)) isOn = false;
3799
+ else if (body.temp < cfgBody.setPoint) {
3800
+ isOn = true;
3801
+ body.heatStatus = sys.board.valueMaps.heatStatus.getValue('heater');
3802
+ isHeating = true;
3803
+ }
3579
3804
  }
3580
- }
3581
- break;
3582
- case 'heatpump':
3583
- if (mode === 'heatpump' || mode === 'heatpumppref') {
3584
- if (body.temp < cfgBody.setPoint &&
3585
- state.temps.solar > body.temp + (hstate.isOn ? heater.stopTempDelta : heater.startTempDelta)) {
3586
- isOn = true;
3587
- body.heatStatus = sys.board.valueMaps.heatStatus.getValue('heater');
3588
- isHeating = true;
3805
+ break;
3806
+ case 'heatpump':
3807
+ if (mode === 'heatpump' || mode === 'heatpumppref') {
3808
+ if (body.temp < cfgBody.setPoint &&
3809
+ state.temps.solar > body.temp + (hstate.isOn ? heater.stopTempDelta : heater.startTempDelta)) {
3810
+ isOn = true;
3811
+ body.heatStatus = sys.board.valueMaps.heatStatus.getValue('heater');
3812
+ isHeating = true;
3813
+ }
3589
3814
  }
3590
- }
3591
- break;
3592
- default:
3593
- isOn = utils.makeBool(hstate.isOn);
3594
- break;
3815
+ break;
3816
+ default:
3817
+ isOn = utils.makeBool(hstate.isOn);
3818
+ break;
3819
+ }
3595
3820
  }
3596
3821
  logger.debug(`Heater Type: ${htype.name} Mode:${mode} Temp: ${body.temp} Setpoint: ${cfgBody.setPoint} Status: ${body.heatStatus}`);
3597
3822
  }
@@ -3599,24 +3824,24 @@ export class HeaterCommands extends BoardCommands {
3599
3824
  let mode = sys.board.valueMaps.heatModes.getName(body.heatMode);
3600
3825
  switch (htype.name) {
3601
3826
  case 'mastertemp':
3602
- if (status === 'mtheater') isHeating = isOn = true;
3827
+ if (hstatus === 'mtheat') isHeating = isOn = true;
3603
3828
  break;
3604
3829
  case 'maxetherm':
3605
3830
  case 'gas':
3606
- if (status === 'heater') isHeating = isOn = true;
3831
+ if (hstatus === 'heater') isHeating = isOn = true;
3607
3832
  break;
3608
3833
  case 'hybrid':
3609
3834
  case 'ultratemp':
3610
3835
  case 'heatpump':
3611
3836
  if (mode === 'ultratemp' || mode === 'ultratemppref' || mode === 'heatpump' || mode === 'heatpumppref') {
3612
- if (status === 'heater') isHeating = isOn = true;
3613
- else if (status === 'cooling') isCooling = isOn = true;
3837
+ if (hstatus === 'heater') isHeating = isOn = true;
3838
+ else if (hstatus === 'cooling') isCooling = isOn = true;
3614
3839
  }
3615
3840
  break;
3616
3841
  case 'solar':
3617
3842
  if (mode === 'solar' || mode === 'solarpref') {
3618
- if (status === 'solar') isHeating = isOn = true;
3619
- else if (status === 'cooling') isCooling = isOn = true;
3843
+ if (hstatus === 'solar') isHeating = isOn = true;
3844
+ else if (hstatus === 'cooling') isCooling = isOn = true;
3620
3845
  }
3621
3846
  break;
3622
3847
  }
@@ -3625,16 +3850,28 @@ export class HeaterCommands extends BoardCommands {
3625
3850
  hon.push(heater.id);
3626
3851
  if (heater.master === 1 && isOn) (async () => {
3627
3852
  try {
3628
- await ncp.heaters.setHeaterStateAsync(hstate, isOn, isCooling);
3853
+ if (sys.board.valueMaps.heatStatus.getName(body.heatStatus) === 'cooldown')
3854
+ await ncp.heaters.setHeaterStateAsync(hstate, false, false);
3855
+ else if (isOn) {
3856
+ hstate.bodyId = body.id;
3857
+ await ncp.heaters.setHeaterStateAsync(hstate, isOn, isCooling);
3858
+ }
3859
+ else if (hstate.isOn !== isOn || hstate.isCooling !== isCooling) {
3860
+ await ncp.heaters.setHeaterStateAsync(hstate, isOn, isCooling);
3861
+ }
3629
3862
  } catch (err) { logger.error(err.message); }
3630
3863
  })();
3631
- else hstate.isOn = isOn;
3864
+ else {
3865
+ hstate.isOn = isOn;
3866
+ hstate.bodyId = body.id;
3867
+ }
3632
3868
  }
3633
3869
  }
3634
3870
  }
3871
+ if (sys.controllerType === ControllerType.Nixie && !isHeating && !isCooling && hstatus !== 'cooldown') body.heatStatus = sys.board.valueMaps.heatStatus.getValue('off');
3872
+
3635
3873
  }
3636
- // When the controller is a virtual one we need to control the heat status ourselves.
3637
- if (!isHeating && (sys.controllerType === ControllerType.Nixie)) body.heatStatus = 0;
3874
+ else if (sys.controllerType === ControllerType.Nixie) body.heatStatus = 0;
3638
3875
  }
3639
3876
  // Turn off any heaters that should be off. The code above only turns heaters on.
3640
3877
  for (let i = 0; i < heaters.length; i++) {
@@ -3644,12 +3881,16 @@ export class HeaterCommands extends BoardCommands {
3644
3881
  if (heater.master === 1) (async () => {
3645
3882
  try {
3646
3883
  await ncp.heaters.setHeaterStateAsync(hstate, false, false);
3884
+ hstate.bodyId = 0;
3647
3885
  } catch (err) { logger.error(err.message); }
3648
3886
  })();
3649
- else hstate.isOn = false;
3887
+ else {
3888
+ hstate.isOn = false;
3889
+ hstate.bodyId = 0;
3890
+ }
3650
3891
  }
3651
3892
  }
3652
- } catch (err) { logger.error(`Error synchronizing heater states`); }
3893
+ } catch (err) { logger.error(`Error synchronizing heater states: ${err.message}`); }
3653
3894
  }
3654
3895
  }
3655
3896
  export class ValveCommands extends BoardCommands {
@@ -3748,37 +3989,90 @@ export class ValveCommands extends BoardCommands {
3748
3989
  } catch (err) { return Promise.reject(new Error(`Error deleting valve: ${err.message}`)); }
3749
3990
  // The following code will make sure we do not encroach on any valves defined by the OCP.
3750
3991
  }
3751
- public async syncValveStates() {
3752
- try {
3753
- for (let i = 0; i < sys.valves.length; i++) {
3754
- // Run through all the valves to see whether they should be triggered or not.
3755
- let valve = sys.valves.getItemByIndex(i);
3756
- if (valve.isActive) {
3757
- let vstate = state.valves.getItemById(valve.id, true);
3758
- let isDiverted = vstate.isDiverted;
3759
- if (typeof valve.circuit !== 'undefined' && valve.circuit > 0) {
3760
- if (sys.equipment.shared && valve.isIntake === true)
3761
- isDiverted = utils.makeBool(state.circuits.getItemById(1).isOn); // If the spa is on then the intake is diverted.
3762
- else if (sys.equipment.shared && valve.isReturn === true) {
3763
- // Check to see if there is a spillway circuit or feature on. If it is on then the return will be diverted no mater what.
3764
- let spillway = typeof state.circuits.get().find(elem => typeof elem.type !== 'undefined' && elem.type.name === 'spillway' && elem.isOn === true) !== 'undefined' ||
3765
- typeof state.features.get().find(elem => typeof elem.type !== 'undefined' && elem.type.name === 'spillway' && elem.isOn === true) !== 'undefined';
3766
- isDiverted = utils.makeBool(spillway || state.circuits.getItemById(1).isOn);
3992
+ public async syncValveStates() {
3993
+ try {
3994
+ // Check to see if there is a drain circuit or feature on. If it is on then the intake will be diverted no mater what.
3995
+ let drain = sys.equipment.shared ? typeof state.circuits.get().find(elem => typeof elem.type !== 'undefined' && elem.type.name === 'spadrain' && elem.isOn === true) !== 'undefined' ||
3996
+ typeof state.features.get().find(elem => typeof elem.type !== 'undefined' && elem.type.name === 'spadrain' && elem.isOn === true) !== 'undefined' : false;
3997
+ // Check to see if there is a spillway circuit or feature on. If it is on then the return will be diverted no mater what.
3998
+ let spillway = sys.equipment.shared ? typeof state.circuits.get().find(elem => typeof elem.type !== 'undefined' && elem.type.name === 'spillway' && elem.isOn === true) !== 'undefined' ||
3999
+ typeof state.features.get().find(elem => typeof elem.type !== 'undefined' && elem.type.name === 'spillway' && elem.isOn === true) !== 'undefined' : false;
4000
+ let spa = sys.equipment.shared ? state.circuits.getItemById(1).isOn : false;
4001
+ let pool = sys.equipment.shared ? state.circuits.getItemById(6).isOn : false;
4002
+ // Set the valve mode.
4003
+ if (!sys.equipment.shared) state.valveMode = sys.board.valueMaps.valveModes.getValue('off');
4004
+ else if (drain) state.valveMode = sys.board.valueMaps.valveModes.getValue('spadrain');
4005
+ else if (spillway) state.valveMode = sys.board.valueMaps.valveModes.getValue('spillway');
4006
+ else if (spa) state.valveMode = sys.board.valueMaps.valveModes.getValue('spa');
4007
+ else if (pool) state.valveMode = sys.board.valueMaps.valveModes.getValue('pool');
4008
+ else state.valveMode = sys.board.valueMaps.valveModes.getValue('off');
4009
+
4010
+ for (let i = 0; i < sys.valves.length; i++) {
4011
+ // Run through all the valves to see whether they should be triggered or not.
4012
+ let valve = sys.valves.getItemByIndex(i);
4013
+ if (valve.isActive) {
4014
+ let vstate = state.valves.getItemById(valve.id, true);
4015
+ let isDiverted = vstate.isDiverted;
4016
+ if (typeof valve.circuit !== 'undefined' && valve.circuit > 0) {
4017
+ if (sys.equipment.shared && valve.isIntake === true) {
4018
+ // Valve Diverted Positions
4019
+ // Spa: Y
4020
+ // Drain: Y
4021
+ // Spillway: N
4022
+ // Pool: N
4023
+ isDiverted = utils.makeBool(spa || drain); // If the spa is on then the intake is diverted.
4024
+ }
4025
+ else if (sys.equipment.shared && valve.isReturn === true) {
4026
+ // Valve Diverted Positions
4027
+ // Spa: Y
4028
+ // Drain: N
4029
+ // Spillway: Y
4030
+ // Pool: N
4031
+ isDiverted = utils.makeBool((spa || spillway) && !drain);
4032
+ }
4033
+ else {
4034
+ let circ = state.circuits.getInterfaceById(valve.circuit);
4035
+ isDiverted = utils.makeBool(circ.isOn);
4036
+ }
4037
+ }
4038
+ else
4039
+ isDiverted = false;
4040
+ vstate.type = valve.type;
4041
+ vstate.name = valve.name;
4042
+ await sys.board.valves.setValveStateAsync(valve, vstate, isDiverted);
4043
+ }
3767
4044
  }
3768
- else {
3769
- let circ = state.circuits.getInterfaceById(valve.circuit);
3770
- isDiverted = utils.makeBool(circ.isOn);
4045
+ } catch (err) { logger.error(`syncValveStates: Error synchronizing valves ${err.message}`); }
4046
+ }
4047
+ public getBodyValveCircuitIds(isOn?: boolean): number[] {
4048
+ let arrIds: number[] = [];
4049
+ if (sys.equipment.shared !== true) return arrIds;
4050
+
4051
+ {
4052
+ let dtype = sys.board.valueMaps.circuitFunctions.getValue('spadrain');
4053
+ let stype = sys.board.valueMaps.circuitFunctions.getValue('spillway');
4054
+ let ptype = sys.board.valueMaps.circuitFunctions.getValue('pool');
4055
+ let sptype = sys.board.valueMaps.circuitFunctions.getValue('spa');
4056
+ for (let i = 0; i < state.circuits.length; i++) {
4057
+ let cstate = state.circuits.getItemByIndex(i);
4058
+ if (typeof isOn === 'undefined' || cstate.isOn === isOn) {
4059
+ if (cstate.id === 1 || cstate.id === 6) arrIds.push(cstate.id);
4060
+ if (cstate.type === dtype || cstate.type === stype || cstate.type === ptype || cstate.type === sptype) arrIds.push(cstate.id);
4061
+ }
3771
4062
  }
3772
- }
3773
- else
3774
- isDiverted = false;
3775
- vstate.type = valve.type;
3776
- vstate.name = valve.name;
3777
- await sys.board.valves.setValveStateAsync(valve, vstate, isDiverted);
3778
4063
  }
3779
- }
3780
- } catch (err) { logger.error(`syncValveStates: Error synchronizing valves ${err.message}`); }
3781
- }
4064
+ {
4065
+ let dtype = sys.board.valueMaps.featureFunctions.getValue('spadrain');
4066
+ let stype = sys.board.valueMaps.featureFunctions.getValue('spillway');
4067
+ for (let i = 0; i < state.features.length; i++) {
4068
+ let fstate = state.features.getItemByIndex(i);
4069
+ if (typeof isOn === 'undefined' || fstate.isOn === isOn) {
4070
+ if (fstate.type === dtype || fstate.type === stype) arrIds.push(fstate.id);
4071
+ }
4072
+ }
4073
+ }
4074
+ return arrIds;
4075
+ }
3782
4076
  }
3783
4077
  export class ChemControllerCommands extends BoardCommands {
3784
4078
  public async restore(rest: { poolConfig: any, poolState: any }, ctx: any, res: RestoreResults): Promise<boolean> {
@@ -3919,47 +4213,47 @@ export class ChemControllerCommands extends BoardCommands {
3919
4213
  if (!isNaN(id)) return sys.chemControllers.find(x => x.id === id);
3920
4214
  else if (!isNaN(address)) return sys.chemControllers.find(x => x.address === address);
3921
4215
  }
3922
- public async setChemControllerAsync(data: any): Promise<ChemController> {
3923
- // The following are the rules related to when an OCP is present.
3924
- // ==============================================================
3925
- // 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.
3926
- // 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.
3927
- // 3. njspc will communicate to the OCP for IntelliChem control via the configuration interface.
4216
+ public async setChemControllerAsync(data: any): Promise<ChemController> {
4217
+ // The following are the rules related to when an OCP is present.
4218
+ // ==============================================================
4219
+ // 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.
4220
+ // 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.
4221
+ // 3. njspc will communicate to the OCP for IntelliChem control via the configuration interface.
3928
4222
 
3929
- // The following are the rules related to when no OCP is present.
3930
- // =============================================================
3931
- // 1. All chemControllers will be controlled via Nixie (IntelliChem, REM Chem).
3932
- try {
3933
- let chem = sys.board.chemControllers.findChemController(data);
3934
- let isAdd = typeof chem === 'undefined';
3935
- let type = sys.board.valueMaps.chemControllerTypes.encode(isAdd ? data.type : chem.type);
3936
- if (typeof type === 'undefined') return Promise.reject(new InvalidEquipmentDataError(`The chem controller type could not be determined ${data.type || type}`, 'chemController', type));
3937
- 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));
3938
- let address = parseInt(data.address, 10);
3939
- let t = sys.board.valueMaps.chemControllerTypes.transform(type);
3940
- if (t.hasAddress) {
3941
- // First lets make sure the user supplied an address.
3942
- if (isNaN(address)) return Promise.reject(new InvalidEquipmentDataError(`${type.desc} chem controllers require a valid address`, 'chemController', data.address));
3943
- if (typeof sys.chemControllers.find(x => x.address === address && x.id !== (isAdd ? -1 : chem.id)) !== 'undefined') return Promise.reject(new InvalidEquipmentDataError(`${type.desc} chem controller addresses must be unique`, 'chemController', data.address));
3944
- }
3945
- if (isAdd) {
3946
- // At this point we are going to add the chem controller no matter what.
3947
- data.id = sys.chemControllers.getNextControllerId(type);
3948
- chem = sys.chemControllers.getItemById(data.id, true);
3949
- chem.type = type;
3950
- if (t.hasAddress) chem.address = address;
3951
- }
3952
- chem.isActive = true;
3953
- // So here is the thing. If you have an OCP then the IntelliChem must be controlled by that.
3954
- // the messages on the bus will talk back to the OCP so if you do not do this mayhem will ensue.
3955
- if (type.name === 'intellichem')
3956
- await this.setIntelliChemAsync(data);
3957
- else
3958
- await ncp.chemControllers.setControllerAsync(chem, data);
3959
- return Promise.resolve(chem);
4223
+ // The following are the rules related to when no OCP is present.
4224
+ // =============================================================
4225
+ // 1. All chemControllers will be controlled via Nixie (IntelliChem, REM Chem).
4226
+ try {
4227
+ let chem = sys.board.chemControllers.findChemController(data);
4228
+ let isAdd = typeof chem === 'undefined';
4229
+ let type = sys.board.valueMaps.chemControllerTypes.encode(isAdd ? data.type : chem.type);
4230
+ if (typeof type === 'undefined') return Promise.reject(new InvalidEquipmentDataError(`The chem controller type could not be determined ${data.type || type}`, 'chemController', type));
4231
+ 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));
4232
+ let address = typeof data.address !== 'undefined' ? parseInt(data.address, 10) : isAdd ? undefined : chem.address;
4233
+ let t = sys.board.valueMaps.chemControllerTypes.transform(type);
4234
+ if (t.hasAddress) {
4235
+ // First lets make sure the user supplied an address.
4236
+ if (isNaN(address)) return Promise.reject(new InvalidEquipmentDataError(`${t.desc} chem controllers require a valid address`, 'chemController', data.address));
4237
+ if (typeof sys.chemControllers.find(x => x.address === address && x.id !== (isAdd ? -1 : chem.id)) !== 'undefined') return Promise.reject(new InvalidEquipmentDataError(`${type.desc} chem controller addresses must be unique`, 'chemController', data.address));
4238
+ }
4239
+ if (isAdd) {
4240
+ // At this point we are going to add the chem controller no matter what.
4241
+ data.id = sys.chemControllers.getNextControllerId(type);
4242
+ chem = sys.chemControllers.getItemById(data.id, true);
4243
+ chem.type = type;
4244
+ if (t.hasAddress) chem.address = address;
4245
+ }
4246
+ chem.isActive = true;
4247
+ // So here is the thing. If you have an OCP then the IntelliChem must be controlled by that.
4248
+ // the messages on the bus will talk back to the OCP so if you do not do this mayhem will ensue.
4249
+ if (type.name === 'intellichem')
4250
+ await this.setIntelliChemAsync(data);
4251
+ else
4252
+ await ncp.chemControllers.setControllerAsync(chem, data);
4253
+ return Promise.resolve(chem);
4254
+ }
4255
+ catch (err) { return Promise.reject(err); }
3960
4256
  }
3961
- catch (err) { return Promise.reject(err); }
3962
- }
3963
4257
  public async setChemControllerStateAsync(data: any): Promise<ChemControllerState> {
3964
4258
  // For the most part all of the settable settings for IntelliChem are config settings. REM is a bit of a different story so that
3965
4259
  // should map to the ncp
@@ -4023,19 +4317,19 @@ export class FilterCommands extends BoardCommands {
4023
4317
  } catch (err) { logger.error(`Error validating filters for restore: ${err.message}`); }
4024
4318
  }
4025
4319
 
4026
- public async syncFilterStates() {
4027
- try {
4028
- for (let i = 0; i < sys.filters.length; i++) {
4029
- // Run through all the valves to see whether they should be triggered or not.
4030
- let filter = sys.filters.getItemByIndex(i);
4031
- if (filter.isActive && !isNaN(filter.id)) {
4032
- let fstate = state.filters.getItemById(filter.id, true);
4033
- // Check to see if the associated body is on.
4034
- await sys.board.filters.setFilterStateAsync(filter, fstate, sys.board.bodies.isBodyOn(filter.body));
4035
- }
4036
- }
4037
- } catch (err) { logger.error(`syncFilterStates: Error synchronizing filters ${err.message}`); }
4038
- }
4320
+ public async syncFilterStates() {
4321
+ try {
4322
+ for (let i = 0; i < sys.filters.length; i++) {
4323
+ // Run through all the valves to see whether they should be triggered or not.
4324
+ let filter = sys.filters.getItemByIndex(i);
4325
+ if (filter.isActive && !isNaN(filter.id)) {
4326
+ let fstate = state.filters.getItemById(filter.id, true);
4327
+ // Check to see if the associated body is on.
4328
+ await sys.board.filters.setFilterStateAsync(filter, fstate, sys.board.bodies.isBodyOn(filter.body));
4329
+ }
4330
+ }
4331
+ } catch (err) { logger.error(`syncFilterStates: Error synchronizing filters ${err.message}`); }
4332
+ }
4039
4333
  public async setFilterPressure(id: number, pressure: number, units?: string) {
4040
4334
  try {
4041
4335
  let filter = sys.filters.find(elem => elem.id === id);