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.
- package/Changelog +14 -0
- package/README.md +1 -1
- package/controller/Equipment.ts +107 -2
- package/controller/Errors.ts +10 -0
- package/controller/Lockouts.ts +423 -0
- package/controller/State.ts +112 -8
- package/controller/boards/EasyTouchBoard.ts +15 -13
- package/controller/boards/IntelliCenterBoard.ts +82 -87
- package/controller/boards/NixieBoard.ts +590 -128
- package/controller/boards/SystemBoard.ts +1264 -970
- package/controller/comms/messages/config/ExternalMessage.ts +1 -1
- package/controller/comms/messages/config/ValveMessage.ts +1 -1
- package/controller/comms/messages/status/HeaterStateMessage.ts +27 -3
- package/controller/nixie/bodies/Body.ts +3 -0
- package/controller/nixie/circuits/Circuit.ts +24 -8
- package/controller/nixie/heaters/Heater.ts +191 -31
- package/controller/nixie/pumps/Pump.ts +97 -60
- package/controller/nixie/valves/Valve.ts +1 -1
- package/package.json +1 -1
- package/web/bindings/mqtt.json +49 -38
- package/web/bindings/mqttAlt.json +12 -1
- package/web/services/config/Config.ts +6 -6
|
@@ -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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
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
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
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
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
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
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
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
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
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
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
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
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
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
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
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
|
-
|
|
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('
|
|
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
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
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
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
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
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
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
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
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
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
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
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
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
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
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
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
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
|
-
|
|
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
|
|
3479
|
-
|
|
3480
|
-
if (body.id ===
|
|
3481
|
-
if (body.id ===
|
|
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
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
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
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
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
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
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
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
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
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
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
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
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
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
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 (
|
|
3827
|
+
if (hstatus === 'mtheat') isHeating = isOn = true;
|
|
3603
3828
|
break;
|
|
3604
3829
|
case 'maxetherm':
|
|
3605
3830
|
case 'gas':
|
|
3606
|
-
if (
|
|
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 (
|
|
3613
|
-
else if (
|
|
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 (
|
|
3619
|
-
else if (
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
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
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
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
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
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
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
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);
|