nodejs-poolcontroller 7.5.1 → 7.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/ISSUE_TEMPLATE/1-bug-report.yml +84 -0
- package/.github/ISSUE_TEMPLATE/2-docs.md +12 -0
- package/.github/ISSUE_TEMPLATE/3-proposal.md +28 -0
- package/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/Changelog +19 -0
- package/Dockerfile +3 -3
- package/README.md +13 -8
- package/app.ts +1 -1
- package/config/Config.ts +38 -2
- package/config/VersionCheck.ts +27 -12
- package/controller/Constants.ts +2 -1
- package/controller/Equipment.ts +193 -9
- package/controller/Errors.ts +10 -0
- package/controller/Lockouts.ts +503 -0
- package/controller/State.ts +269 -64
- package/controller/boards/AquaLinkBoard.ts +1000 -0
- package/controller/boards/BoardFactory.ts +4 -0
- package/controller/boards/EasyTouchBoard.ts +468 -144
- package/controller/boards/IntelliCenterBoard.ts +466 -307
- package/controller/boards/IntelliTouchBoard.ts +37 -5
- package/controller/boards/NixieBoard.ts +671 -141
- package/controller/boards/SystemBoard.ts +1397 -641
- package/controller/comms/Comms.ts +462 -362
- package/controller/comms/messages/Messages.ts +174 -30
- package/controller/comms/messages/config/ChlorinatorMessage.ts +6 -3
- package/controller/comms/messages/config/CircuitMessage.ts +1 -0
- package/controller/comms/messages/config/ExternalMessage.ts +10 -8
- package/controller/comms/messages/config/HeaterMessage.ts +141 -29
- package/controller/comms/messages/config/OptionsMessage.ts +9 -2
- package/controller/comms/messages/config/PumpMessage.ts +53 -35
- package/controller/comms/messages/config/ScheduleMessage.ts +33 -25
- package/controller/comms/messages/config/ValveMessage.ts +2 -2
- package/controller/comms/messages/status/ChlorinatorStateMessage.ts +38 -86
- package/controller/comms/messages/status/EquipmentStateMessage.ts +59 -23
- package/controller/comms/messages/status/HeaterStateMessage.ts +57 -3
- package/controller/comms/messages/status/IntelliChemStateMessage.ts +56 -8
- package/controller/comms/messages/status/PumpStateMessage.ts +23 -1
- package/controller/nixie/Nixie.ts +1 -1
- package/controller/nixie/bodies/Body.ts +3 -0
- package/controller/nixie/chemistry/ChemController.ts +164 -51
- package/controller/nixie/chemistry/Chlorinator.ts +137 -88
- package/controller/nixie/circuits/Circuit.ts +51 -19
- package/controller/nixie/heaters/Heater.ts +241 -31
- package/controller/nixie/pumps/Pump.ts +488 -206
- package/controller/nixie/schedules/Schedule.ts +91 -35
- package/controller/nixie/valves/Valve.ts +1 -1
- package/defaultConfig.json +20 -0
- package/package.json +21 -21
- package/web/Server.ts +94 -49
- package/web/bindings/aqualinkD.json +505 -0
- package/web/bindings/influxDB.json +71 -1
- package/web/bindings/mqtt.json +98 -39
- package/web/bindings/mqttAlt.json +59 -1
- package/web/interfaces/baseInterface.ts +1 -0
- package/web/interfaces/httpInterface.ts +23 -2
- package/web/interfaces/influxInterface.ts +45 -10
- package/web/interfaces/mqttInterface.ts +114 -54
- package/web/services/config/Config.ts +55 -132
- package/web/services/state/State.ts +81 -4
- package/web/services/state/StateSocket.ts +4 -4
- package/web/services/utilities/Utilities.ts +8 -6
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -52
- package/config copy.json +0 -300
- package/issue_template.md +0 -52
|
@@ -18,11 +18,12 @@ import * as extend from 'extend';
|
|
|
18
18
|
import { logger } from '../../logger/Logger';
|
|
19
19
|
import { Message, Outbound } from '../comms/messages/Messages';
|
|
20
20
|
import { Timestamp, utils } from '../Constants';
|
|
21
|
-
import { Body, ChemController, Chlorinator, Circuit, CircuitGroup, CircuitGroupCircuit, ConfigVersion, ControllerType, CustomName, CustomNameCollection, EggTimer, Equipment, Feature, Filter, General, Heater, ICircuit, LightGroup, LightGroupCircuit, Location, Options, Owner, PoolSystem, Pump, Schedule, sys, TempSensorCollection, Valve } from '../Equipment';
|
|
22
|
-
import { EquipmentNotFoundError, InvalidEquipmentDataError, InvalidEquipmentIdError } from '../Errors';
|
|
21
|
+
import { Body, ChemController, Chlorinator, Circuit, CircuitGroup, CircuitGroupCircuit, ConfigVersion, ControllerType, CustomName, CustomNameCollection, EggTimer, Equipment, Feature, Filter, General, Heater, ICircuit, ICircuitGroup, ICircuitGroupCircuit, LightGroup, LightGroupCircuit, Location, Options, Owner, PoolSystem, Pump, Schedule, sys, TempSensorCollection, Valve } from '../Equipment';
|
|
22
|
+
import { EquipmentNotFoundError, InvalidEquipmentDataError, InvalidEquipmentIdError, BoardProcessError, InvalidOperationError } 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';
|
|
26
|
+
import { group } from 'console';
|
|
26
27
|
|
|
27
28
|
|
|
28
29
|
export class byteValueMap extends Map<number, any> {
|
|
@@ -210,7 +211,8 @@ export class byteValueMaps {
|
|
|
210
211
|
// Identifies which controller manages the underlying equipment.
|
|
211
212
|
public equipmentMaster: byteValueMap = new byteValueMap([
|
|
212
213
|
[0, { val: 0, name: 'ocp', desc: 'Outdoor Control Panel' }],
|
|
213
|
-
[1, { val: 1, name: 'ncp', desc: 'Nixie Control Panel' }]
|
|
214
|
+
[1, { val: 1, name: 'ncp', desc: 'Nixie Control Panel' }],
|
|
215
|
+
[2, { val: 2, name: 'ext', desc: 'External Control Panel' }]
|
|
214
216
|
]);
|
|
215
217
|
public equipmentCommStatus: byteValueMap = new byteValueMap([
|
|
216
218
|
[0, { val: 0, name: 'ready', desc: 'Ready' }],
|
|
@@ -233,9 +235,9 @@ export class byteValueMaps {
|
|
|
233
235
|
|
|
234
236
|
public circuitFunctions: byteValueMap = new byteValueMap([
|
|
235
237
|
[0, { name: 'generic', desc: 'Generic' }],
|
|
236
|
-
[1, { name: 'spa', desc: 'Spa', hasHeatSource: true }],
|
|
237
|
-
[2, { name: 'pool', desc: 'Pool', hasHeatSource: true }],
|
|
238
|
-
[5, { name: 'mastercleaner', desc: 'Master Cleaner' }],
|
|
238
|
+
[1, { name: 'spa', desc: 'Spa', hasHeatSource: true, body: 2 }],
|
|
239
|
+
[2, { name: 'pool', desc: 'Pool', hasHeatSource: true, body: 1 }],
|
|
240
|
+
[5, { name: 'mastercleaner', desc: 'Master Cleaner', body: 1 }],
|
|
239
241
|
[7, { name: 'light', desc: 'Light', isLight: true }],
|
|
240
242
|
[9, { name: 'samlight', desc: 'SAM Light', isLight: true }],
|
|
241
243
|
[10, { name: 'sallight', desc: 'SAL Light', isLight: true }],
|
|
@@ -243,9 +245,9 @@ export class byteValueMaps {
|
|
|
243
245
|
[12, { name: 'colorwheel', desc: 'Color Wheel', isLight: true }],
|
|
244
246
|
[13, { name: 'valve', desc: 'Valve' }],
|
|
245
247
|
[14, { name: 'spillway', desc: 'Spillway' }],
|
|
246
|
-
[15, { name: 'floorcleaner', desc: 'Floor Cleaner' }],
|
|
247
|
-
[16, { name: 'intellibrite', desc: 'Intellibrite', isLight: true }],
|
|
248
|
-
[17, { name: 'magicstream', desc: 'Magicstream', isLight: true }],
|
|
248
|
+
[15, { name: 'floorcleaner', desc: 'Floor Cleaner', body: 1 }], // This circuit function does not seem to exist in IntelliTouch.
|
|
249
|
+
[16, { name: 'intellibrite', desc: 'Intellibrite', isLight: true, theme: 'intellibrite' }],
|
|
250
|
+
[17, { name: 'magicstream', desc: 'Magicstream', isLight: true, theme: 'magicstream' }],
|
|
249
251
|
[19, { name: 'notused', desc: 'Not Used' }],
|
|
250
252
|
[65, { name: 'lotemp', desc: 'Lo-Temp' }],
|
|
251
253
|
[66, { name: 'hightemp', desc: 'Hi-Temp' }]
|
|
@@ -266,51 +268,104 @@ export class byteValueMaps {
|
|
|
266
268
|
[255, { name: 'notused', desc: 'NOT USED', assignableToPumpCircuit: true }]
|
|
267
269
|
]);
|
|
268
270
|
public lightThemes: byteValueMap = new byteValueMap([
|
|
269
|
-
[0, { name: 'off', desc: 'Off'
|
|
270
|
-
[1, { name: 'on', desc: 'On'
|
|
271
|
-
[128, { name: 'colorsync', desc: 'Color Sync'
|
|
272
|
-
[144, { name: 'colorswim', desc: 'Color Swim'
|
|
273
|
-
[160, { name: 'colorset', desc: 'Color Set'
|
|
274
|
-
[177, { name: 'party', desc: 'Party',
|
|
275
|
-
[178, { name: 'romance', desc: 'Romance',
|
|
276
|
-
[179, { name: 'caribbean', desc: 'Caribbean',
|
|
277
|
-
[180, { name: 'american', desc: 'American',
|
|
278
|
-
[181, { name: 'sunset', desc: 'Sunset',
|
|
279
|
-
[182, { name: 'royal', desc: 'Royal',
|
|
280
|
-
[190, { name: 'save', desc: 'Save',
|
|
281
|
-
[191, { name: 'recall', desc: 'Recall',
|
|
282
|
-
[193, { name: 'blue', desc: 'Blue',
|
|
283
|
-
[194, { name: 'green', desc: 'Green',
|
|
284
|
-
[195, { name: 'red', desc: 'Red',
|
|
285
|
-
[196, { name: 'white', desc: 'White',
|
|
286
|
-
[197, { name: 'magenta', desc: 'Magenta',
|
|
287
|
-
[208, { name: 'thumper', desc: 'Thumper',
|
|
288
|
-
[209, { name: 'hold', desc: 'Hold',
|
|
289
|
-
[210, { name: 'reset', desc: 'Reset',
|
|
290
|
-
[211, { name: 'mode', desc: 'Mode',
|
|
271
|
+
[0, { name: 'off', desc: 'Off' }],
|
|
272
|
+
[1, { name: 'on', desc: 'On' }],
|
|
273
|
+
[128, { name: 'colorsync', desc: 'Color Sync' }],
|
|
274
|
+
[144, { name: 'colorswim', desc: 'Color Swim' }],
|
|
275
|
+
[160, { name: 'colorset', desc: 'Color Set' }],
|
|
276
|
+
[177, { name: 'party', desc: 'Party', types: ['intellibrite'], sequence: 2 }],
|
|
277
|
+
[178, { name: 'romance', desc: 'Romance', types: ['intellibrite'], sequence: 3 }],
|
|
278
|
+
[179, { name: 'caribbean', desc: 'Caribbean', types: ['intellibrite'], sequence: 4 }],
|
|
279
|
+
[180, { name: 'american', desc: 'American', types: ['intellibrite'], sequence: 5 }],
|
|
280
|
+
[181, { name: 'sunset', desc: 'Sunset', types: ['intellibrite'], sequence: 6 }],
|
|
281
|
+
[182, { name: 'royal', desc: 'Royal', types: ['intellibrite'], sequence: 7 }],
|
|
282
|
+
[190, { name: 'save', desc: 'Save', types: ['intellibrite'], sequence: 13 }],
|
|
283
|
+
[191, { name: 'recall', desc: 'Recall', types: ['intellibrite'], sequence: 14 }],
|
|
284
|
+
[193, { name: 'blue', desc: 'Blue', types: ['intellibrite'], sequence: 8 }],
|
|
285
|
+
[194, { name: 'green', desc: 'Green', types: ['intellibrite'], sequence: 9 }],
|
|
286
|
+
[195, { name: 'red', desc: 'Red', types: ['intellibrite'], sequence: 10 }],
|
|
287
|
+
[196, { name: 'white', desc: 'White', types: ['intellibrite'], sequence: 11 }],
|
|
288
|
+
[197, { name: 'magenta', desc: 'Magenta', types: ['intellibrite'], sequence: 12 }],
|
|
289
|
+
[208, { name: 'thumper', desc: 'Thumper', types: ['magicstream'] }],
|
|
290
|
+
[209, { name: 'hold', desc: 'Hold', types: ['magicstream'] }],
|
|
291
|
+
[210, { name: 'reset', desc: 'Reset', types: ['magicstream'] }],
|
|
292
|
+
[211, { name: 'mode', desc: 'Mode', types: ['magicstream'] }],
|
|
291
293
|
[254, { name: 'unknown', desc: 'unknown' }],
|
|
292
294
|
[255, { name: 'none', desc: 'None' }]
|
|
293
295
|
]);
|
|
294
296
|
public colorLogicThemes = new byteValueMap([
|
|
295
|
-
[0, { name: 'cloudwhite', desc: 'Cloud White',
|
|
296
|
-
[1, { name: 'deepsea', desc: 'Deep Sea',
|
|
297
|
-
[2, { name: 'royalblue', desc: 'Royal Blue',
|
|
298
|
-
[3, { name: 'afernoonskies', desc: 'Afternoon Skies',
|
|
299
|
-
[4, { name: 'aquagreen', desc: 'Aqua Green',
|
|
300
|
-
[5, { name: 'emerald', desc: 'Emerald',
|
|
301
|
-
[6, { name: 'warmred', desc: 'Warm Red',
|
|
302
|
-
[7, { name: 'flamingo', desc: 'Flamingo',
|
|
303
|
-
[8, { name: 'vividviolet', desc: 'Vivid Violet',
|
|
304
|
-
[9, { name: 'sangria', desc: 'Sangria',
|
|
305
|
-
[10, { name: 'twilight', desc: 'Twilight',
|
|
306
|
-
[11, { name: 'tranquility', desc: 'Tranquility',
|
|
307
|
-
[12, { name: 'gemstone', desc: 'Gemstone',
|
|
308
|
-
[13, { name: 'usa', desc: 'USA',
|
|
309
|
-
[14, { name: 'mardigras', desc: 'Mardi Gras',
|
|
310
|
-
[15, { name: 'cabaret', desc: 'Cabaret',
|
|
297
|
+
[0, { name: 'cloudwhite', desc: 'Cloud White', types: ['colorlogic'], sequence: 7 }],
|
|
298
|
+
[1, { name: 'deepsea', desc: 'Deep Sea', types: ['colorlogic'], sequence: 2 }],
|
|
299
|
+
[2, { name: 'royalblue', desc: 'Royal Blue', types: ['colorlogic'], sequence: 3 }],
|
|
300
|
+
[3, { name: 'afernoonskies', desc: 'Afternoon Skies', types: ['colorlogic'], sequence: 4 }],
|
|
301
|
+
[4, { name: 'aquagreen', desc: 'Aqua Green', types: ['colorlogic'], sequence: 5 }],
|
|
302
|
+
[5, { name: 'emerald', desc: 'Emerald', types: ['colorlogic'], sequence: 6 }],
|
|
303
|
+
[6, { name: 'warmred', desc: 'Warm Red', types: ['colorlogic'], sequence: 8 }],
|
|
304
|
+
[7, { name: 'flamingo', desc: 'Flamingo', types: ['colorlogic'], sequence: 9 }],
|
|
305
|
+
[8, { name: 'vividviolet', desc: 'Vivid Violet', types: ['colorlogic'], sequence: 10 }],
|
|
306
|
+
[9, { name: 'sangria', desc: 'Sangria', types: ['colorlogic'], sequence: 11 }],
|
|
307
|
+
[10, { name: 'twilight', desc: 'Twilight', types: ['colorlogic'], sequence: 12 }],
|
|
308
|
+
[11, { name: 'tranquility', desc: 'Tranquility', types: ['colorlogic'], sequence: 13 }],
|
|
309
|
+
[12, { name: 'gemstone', desc: 'Gemstone', types: ['colorlogic'], sequence: 14 }],
|
|
310
|
+
[13, { name: 'usa', desc: 'USA', types: ['colorlogic'], sequence: 15 }],
|
|
311
|
+
[14, { name: 'mardigras', desc: 'Mardi Gras', types: ['colorlogic'], sequence: 16 }],
|
|
312
|
+
[15, { name: 'cabaret', desc: 'Cabaret', types: ['colorlogic'], sequence: 17 }],
|
|
311
313
|
[255, { name: 'none', desc: 'None' }]
|
|
312
314
|
]);
|
|
313
|
-
|
|
315
|
+
public lightCommands = new byteValueMap([
|
|
316
|
+
[4, { name: 'colorhold', desc: 'Hold', types: ['intellibrite', 'magicstream'], command: 'colorHold', sequence: 13 }],
|
|
317
|
+
[5, { name: 'colorrecall', desc: 'Recall', types: ['intellibrite', 'magicstream'], command: 'colorRecall', sequence: 14 }],
|
|
318
|
+
[6, {
|
|
319
|
+
name: 'lightthumper', desc: 'Thumper', types: ['magicstream'], command: 'lightThumper', message: 'Toggling Thumper',
|
|
320
|
+
sequence: [ // Cycle party mode 3 times.
|
|
321
|
+
{ isOn: false, timeout: 100 },
|
|
322
|
+
{ isOn: true, timeout: 100 },
|
|
323
|
+
{ isOn: false, timeout: 100 },
|
|
324
|
+
{ isOn: true, timeout: 5000 },
|
|
325
|
+
{ isOn: false, timeout: 100 },
|
|
326
|
+
{ isOn: true, timeout: 100 },
|
|
327
|
+
{ isOn: false, timeout: 100 },
|
|
328
|
+
{ isOn: true, timeout: 5000 },
|
|
329
|
+
{ isOn: false, timeout: 100 },
|
|
330
|
+
{ isOn: true, timeout: 100 },
|
|
331
|
+
{ isOn: false, timeout: 100 }
|
|
332
|
+
]
|
|
333
|
+
}]
|
|
334
|
+
]);
|
|
335
|
+
public lightGroupCommands = new byteValueMap([
|
|
336
|
+
[1, { name: 'colorsync', desc: 'Sync', types: ['intellibrite'], command: 'colorSync', message:'Synchronizing' }],
|
|
337
|
+
[2, { name: 'colorset', desc: 'Set', types: ['intellibrite'], command: 'colorSet', message: 'Sequencing Set Operation' }],
|
|
338
|
+
[3, { name: 'colorswim', desc: 'Swim', types: ['intellibrite'], command: 'colorSwim', message:'Sequencing Swim Operation' }],
|
|
339
|
+
[4, { name: 'colorhold', desc: 'Hold', types: ['intellibrite', 'magicstream'], command: 'colorHold', message: 'Saving Current Colors', sequence: 13 }],
|
|
340
|
+
[5, { name: 'colorrecall', desc: 'Recall', types: ['intellibrite', 'magicstream'], command: 'colorRecall', message: 'Recalling Saved Colors', sequence: 14 }],
|
|
341
|
+
[6, {
|
|
342
|
+
name: 'lightthumper', desc: 'Thumper', types: ['magicstream'], command: 'lightThumper', message: 'Toggling Thumper',
|
|
343
|
+
sequence: [ // Cycle party mode 3 times.
|
|
344
|
+
{ isOn: false, timeout: 100 },
|
|
345
|
+
{ isOn: true, timeout: 100 },
|
|
346
|
+
{ isOn: false, timeout: 100 },
|
|
347
|
+
{ isOn: true, timeout: 5000 },
|
|
348
|
+
{ isOn: false, timeout: 100 },
|
|
349
|
+
{ isOn: true, timeout: 100 },
|
|
350
|
+
{ isOn: false, timeout: 100 },
|
|
351
|
+
{ isOn: true, timeout: 5000 },
|
|
352
|
+
{ isOn: false, timeout: 100 },
|
|
353
|
+
{ isOn: true, timeout: 100 },
|
|
354
|
+
{ isOn: false, timeout: 100 },
|
|
355
|
+
{ isOn: true, timeout: 1000 },
|
|
356
|
+
]
|
|
357
|
+
}]
|
|
358
|
+
]);
|
|
359
|
+
public circuitActions: byteValueMap = new byteValueMap([
|
|
360
|
+
[0, { name: 'ready', desc: 'Ready' }],
|
|
361
|
+
[1, { name: 'colorsync', desc: 'Synchronizing' }],
|
|
362
|
+
[2, { name: 'colorset', desc: 'Sequencing Set Operation' }],
|
|
363
|
+
[3, { name: 'colorswim', desc: 'Sequencing Swim Operation' }],
|
|
364
|
+
[4, { name: 'lighttheme', desc: 'Sequencing Theme/Color Operation' }],
|
|
365
|
+
[5, { name: 'colorhold', desc: 'Saving Current Color' }],
|
|
366
|
+
[6, { name: 'colorrecall', desc: 'Recalling Saved Color' }],
|
|
367
|
+
[7, { name: 'lightthumper', desc: 'Setting Light Thumper' }]
|
|
368
|
+
]);
|
|
314
369
|
public lightColors: byteValueMap = new byteValueMap([
|
|
315
370
|
[0, { name: 'white', desc: 'White' }],
|
|
316
371
|
[2, { name: 'lightgreen', desc: 'Light Green' }],
|
|
@@ -338,7 +393,6 @@ export class byteValueMaps {
|
|
|
338
393
|
[1, { name: 'active', desc: 'When Active' }],
|
|
339
394
|
[2, { name: 'never', desc: 'Never' }]
|
|
340
395
|
]);
|
|
341
|
-
|
|
342
396
|
public pumpTypes: byteValueMap = new byteValueMap([
|
|
343
397
|
[1, { name: 'vf', desc: 'Intelliflo VF', minFlow: 15, maxFlow: 130, flowStepSize: 1, maxCircuits: 8, hasAddress: true }],
|
|
344
398
|
[64, { name: 'vsf', desc: 'Intelliflo VSF', minSpeed: 450, maxSpeed: 3450, speedStepSize: 10, minFlow: 15, maxFlow: 130, flowStepSize: 1, maxCircuits: 8, hasAddress: true }],
|
|
@@ -384,12 +438,12 @@ export class byteValueMaps {
|
|
|
384
438
|
]);
|
|
385
439
|
public heaterTypes: byteValueMap = new byteValueMap([
|
|
386
440
|
[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 }],
|
|
441
|
+
[2, { name: 'solar', desc: 'Solar Heater', hasAddress: false, hasCoolSetpoint: true, hasPreference: true }],
|
|
442
|
+
[3, { name: 'heatpump', desc: 'Heat Pump', hasAddress: true, hasPreference: true }],
|
|
443
|
+
[4, { name: 'ultratemp', desc: 'UltraTemp', hasAddress: true, hasCoolSetpoint: true, hasPreference: true }],
|
|
390
444
|
[5, { name: 'hybrid', desc: 'Hybrid', hasAddress: true }],
|
|
391
|
-
[6, { name: '
|
|
392
|
-
[7, { name: '
|
|
445
|
+
[6, { name: 'mastertemp', desc: 'MasterTemp', hasAddress: true }],
|
|
446
|
+
[7, { name: 'maxetherm', desc: 'Max-E-Therm', hasAddress: true }],
|
|
393
447
|
]);
|
|
394
448
|
public heatModes: byteValueMap = new byteValueMap([
|
|
395
449
|
[0, { name: 'off', desc: 'Off' }],
|
|
@@ -408,7 +462,8 @@ export class byteValueMaps {
|
|
|
408
462
|
[0, { name: 'off', desc: 'Off' }],
|
|
409
463
|
[1, { name: 'heater', desc: 'Heater' }],
|
|
410
464
|
[2, { name: 'solar', desc: 'Solar' }],
|
|
411
|
-
[3, { name: 'cooling', desc: 'Cooling' }]
|
|
465
|
+
[3, { name: 'cooling', desc: 'Cooling' }],
|
|
466
|
+
[128, { name: 'cooldown', desc: 'Cooldown' }]
|
|
412
467
|
]);
|
|
413
468
|
public pumpStatus: byteValueMap = new byteValueMap([
|
|
414
469
|
[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
|
|
@@ -507,13 +562,12 @@ export class byteValueMaps {
|
|
|
507
562
|
[0, { name: 'standard', desc: 'Standard' }],
|
|
508
563
|
[1, { name: 'intellivalve', desc: 'IntelliValve' }]
|
|
509
564
|
]);
|
|
510
|
-
public
|
|
511
|
-
[0, { name: '
|
|
512
|
-
[1, { name: '
|
|
513
|
-
[2, { name: '
|
|
514
|
-
[3, { name: '
|
|
515
|
-
[4, { name: '
|
|
516
|
-
[5, { name: 'other', desc: 'Sequencing Save/Recall Operation' }]
|
|
565
|
+
public valveModes: byteValueMap = new byteValueMap([
|
|
566
|
+
[0, { name: 'off', desc: 'Off' }],
|
|
567
|
+
[1, { name: 'pool', desc: 'Pool' }],
|
|
568
|
+
[2, { name: 'spa', dest: 'Spa' }],
|
|
569
|
+
[3, { name: 'spillway', desc: 'Spillway' }],
|
|
570
|
+
[4, { name: 'spadrain', desc: 'Spa Drain' }]
|
|
517
571
|
]);
|
|
518
572
|
public msgBroadcastActions: byteValueMap = new byteValueMap([
|
|
519
573
|
[2, { name: 'status', desc: 'Equipment Status' }],
|
|
@@ -564,6 +618,18 @@ export class byteValueMaps {
|
|
|
564
618
|
[0, { name: 'base', desc: 'Base pH+' }],
|
|
565
619
|
[1, { name: 'acid', desc: 'Acid pH-' }]
|
|
566
620
|
]);
|
|
621
|
+
public phDoserTypes: byteValueMap = new byteValueMap([
|
|
622
|
+
[0, { name: 'none', desc: 'No Doser Attached' }],
|
|
623
|
+
[1, { name: 'extrelay', desc: 'External Relay' }],
|
|
624
|
+
[2, { name: 'co2', desc: 'CO2 Tank' }],
|
|
625
|
+
[3, { name: 'intrelay', desc: 'Internal Relay'}]
|
|
626
|
+
]);
|
|
627
|
+
public orpDoserTypes: byteValueMap = new byteValueMap([
|
|
628
|
+
[0, { name: 'none', desc: 'No Doser Attached' }],
|
|
629
|
+
[1, { name: 'extrelay', desc: 'External Relay' }],
|
|
630
|
+
[2, { name: 'chlorinator', desc: 'Chlorinator'}],
|
|
631
|
+
[3, { name: 'intrelay', desc: 'Internal Relay'}]
|
|
632
|
+
])
|
|
567
633
|
public volumeUnits: byteValueMap = new byteValueMap([
|
|
568
634
|
[0, { name: '', desc: 'No Units' }],
|
|
569
635
|
[1, { name: 'gal', desc: 'Gallons' }],
|
|
@@ -603,7 +669,8 @@ export class byteValueMaps {
|
|
|
603
669
|
[64, { name: 'orptankempty', desc: 'orp Tank Empty' }],
|
|
604
670
|
[128, { name: 'probefault', desc: 'Probe Fault' }],
|
|
605
671
|
[129, { name: 'phtanklow', desc: 'pH Tank Low' }],
|
|
606
|
-
[130, { name: 'orptanklow', desc: 'orp Tank Low' }]
|
|
672
|
+
[130, { name: 'orptanklow', desc: 'orp Tank Low' }],
|
|
673
|
+
[131, { name: 'freezeprotect', desc: 'Freeze Protection Lockout'}]
|
|
607
674
|
]);
|
|
608
675
|
public chemControllerHardwareFaults: byteValueMap = new byteValueMap([
|
|
609
676
|
[0, { name: 'ok', desc: 'Ok - No Faults' }],
|
|
@@ -614,6 +681,7 @@ export class byteValueMaps {
|
|
|
614
681
|
[5, { name: 'chlormismatch', desc: 'Chlorinator body mismatch' }],
|
|
615
682
|
[6, { name: 'invalidbody', desc: 'Body capacity not valid' }],
|
|
616
683
|
[7, { name: 'flowsensor', desc: 'Flow Sensor Fault' }]
|
|
684
|
+
|
|
617
685
|
]);
|
|
618
686
|
public chemControllerWarnings: byteValueMap = new byteValueMap([
|
|
619
687
|
[0, { name: 'ok', desc: 'Ok - No Warning' }],
|
|
@@ -749,7 +817,7 @@ export class SystemBoard {
|
|
|
749
817
|
// turn off chlor
|
|
750
818
|
console.log(`Stopping sys`);
|
|
751
819
|
//sys.board.virtualChlorinatorController.stop();
|
|
752
|
-
if (sys.controllerType === ControllerType.
|
|
820
|
+
if (sys.controllerType === ControllerType.Nixie) this.turnOffAllCircuits();
|
|
753
821
|
// sys.board.virtualChemControllers.stop();
|
|
754
822
|
this.killStatusCheck();
|
|
755
823
|
await ncp.closeAsync();
|
|
@@ -758,16 +826,20 @@ export class SystemBoard {
|
|
|
758
826
|
public async turnOffAllCircuits() {
|
|
759
827
|
// turn off all circuits/features
|
|
760
828
|
for (let i = 0; i < state.circuits.length; i++) {
|
|
761
|
-
state.circuits.getItemByIndex(i)
|
|
829
|
+
let s = state.circuits.getItemByIndex(i);
|
|
830
|
+
await sys.board.circuits.setCircuitStateAsync(s.id, false);
|
|
762
831
|
}
|
|
763
832
|
for (let i = 0; i < state.features.length; i++) {
|
|
764
|
-
state.features.getItemByIndex(i)
|
|
833
|
+
let s = state.features.getItemByIndex(i);
|
|
834
|
+
await sys.board.features.setFeatureStateAsync(s.id, false);
|
|
765
835
|
}
|
|
766
836
|
for (let i = 0; i < state.lightGroups.length; i++) {
|
|
767
|
-
state.lightGroups.getItemByIndex(i)
|
|
837
|
+
let s = state.lightGroups.getItemByIndex(i);
|
|
838
|
+
await sys.board.circuits.setCircuitStateAsync(s.id, false);
|
|
768
839
|
}
|
|
769
840
|
for (let i = 0; i < state.temps.bodies.length; i++) {
|
|
770
|
-
state.temps.bodies.getItemByIndex(i)
|
|
841
|
+
let s = state.temps.bodies.getItemByIndex(i);
|
|
842
|
+
await sys.board.circuits.setCircuitStateAsync(s.id, false);
|
|
771
843
|
}
|
|
772
844
|
// sys.board.virtualPumpControllers.setTargetSpeed();
|
|
773
845
|
state.emitEquipmentChanges();
|
|
@@ -989,14 +1061,15 @@ export class SystemCommands extends BoardCommands {
|
|
|
989
1061
|
ctx.filters = await sys.board.filters.validateRestore(rest);
|
|
990
1062
|
ctx.schedules = await sys.board.schedules.validateRestore(rest);
|
|
991
1063
|
}
|
|
992
|
-
else ctx.board.errors.push(`Panel Types do not match cannot restore
|
|
1064
|
+
else ctx.board.errors.push(`Panel Types do not match cannot restore backup from ${sys.controllerType} to ${rest.poolConfig.controllerType}`);
|
|
993
1065
|
|
|
994
1066
|
return ctx;
|
|
995
1067
|
|
|
996
|
-
} catch (err) { logger.error(`Error validating restore file: ${err.message}`); return Promise.reject(err);}
|
|
1068
|
+
} catch (err) { logger.error(`Error validating restore file: ${err.message}`); return Promise.reject(err); }
|
|
997
1069
|
|
|
998
1070
|
}
|
|
999
1071
|
public cancelDelay(): Promise<any> { state.delay = sys.board.valueMaps.delay.getValue('nodelay'); return Promise.resolve(state.data.delay); }
|
|
1072
|
+
public setManualOperationPriority(id: number): Promise<any> { return Promise.resolve(); }
|
|
1000
1073
|
public setDateTimeAsync(obj: any): Promise<any> { return Promise.resolve(); }
|
|
1001
1074
|
public keepManualTime() {
|
|
1002
1075
|
try {
|
|
@@ -1019,12 +1092,11 @@ export class SystemCommands extends BoardCommands {
|
|
|
1019
1092
|
}
|
|
1020
1093
|
public getDOW() { return this.board.valueMaps.scheduleDays.toArray(); }
|
|
1021
1094
|
public async setGeneralAsync(obj: any): Promise<General> {
|
|
1022
|
-
let general = sys.general.get();
|
|
1023
1095
|
if (typeof obj.alias === 'string') sys.general.alias = obj.alias;
|
|
1024
1096
|
if (typeof obj.options !== 'undefined') await sys.board.system.setOptionsAsync(obj.options);
|
|
1025
1097
|
if (typeof obj.location !== 'undefined') await sys.board.system.setLocationAsync(obj.location);
|
|
1026
1098
|
if (typeof obj.owner !== 'undefined') await sys.board.system.setOwnerAsync(obj.owner);
|
|
1027
|
-
return
|
|
1099
|
+
return sys.general;
|
|
1028
1100
|
}
|
|
1029
1101
|
public async setTempSensorsAsync(obj: any): Promise<TempSensorCollection> {
|
|
1030
1102
|
if (typeof obj.waterTempAdj1 != 'undefined' && obj.waterTempAdj1 !== sys.equipment.tempSensors.getCalibration('water1')) {
|
|
@@ -1054,7 +1126,7 @@ export class SystemCommands extends BoardCommands {
|
|
|
1054
1126
|
if (typeof obj.airTempAdj != 'undefined' && obj.airTempAdj !== sys.equipment.tempSensors.getCalibration('air')) {
|
|
1055
1127
|
sys.equipment.tempSensors.setCalibration('air', parseFloat(obj.airTempAdj));
|
|
1056
1128
|
}
|
|
1057
|
-
return
|
|
1129
|
+
return sys.equipment.tempSensors;
|
|
1058
1130
|
}
|
|
1059
1131
|
public async setOptionsAsync(obj: any): Promise<Options> {
|
|
1060
1132
|
if (obj.clockSource === 'server') sys.board.system.setTZ();
|
|
@@ -1063,15 +1135,15 @@ export class SystemCommands extends BoardCommands {
|
|
|
1063
1135
|
let bodyUnits = sys.general.options.units === 0 ? 1 : 2;
|
|
1064
1136
|
for (let i = 0; i < sys.bodies.length; i++) sys.bodies.getItemByIndex(i).capacityUnits = bodyUnits;
|
|
1065
1137
|
state.temps.units = sys.general.options.units === 0 ? 1 : 4;
|
|
1066
|
-
return
|
|
1138
|
+
return sys.general.options;
|
|
1067
1139
|
}
|
|
1068
1140
|
public async setLocationAsync(obj: any): Promise<Location> {
|
|
1069
1141
|
sys.general.location.set(obj);
|
|
1070
|
-
return
|
|
1142
|
+
return sys.general.location;
|
|
1071
1143
|
}
|
|
1072
1144
|
public async setOwnerAsync(obj: any): Promise<Owner> {
|
|
1073
1145
|
sys.general.owner.set(obj);
|
|
1074
|
-
return
|
|
1146
|
+
return sys.general.owner;
|
|
1075
1147
|
}
|
|
1076
1148
|
public async setTempsAsync(obj: any): Promise<TemperatureState> {
|
|
1077
1149
|
return new Promise<TemperatureState>((resolve, reject) => {
|
|
@@ -1330,7 +1402,12 @@ export class BodyCommands extends BoardCommands {
|
|
|
1330
1402
|
let freeze = utils.makeBool(state.freeze);
|
|
1331
1403
|
if (sys.controllerType === ControllerType.Nixie) {
|
|
1332
1404
|
// If we are a Nixie controller we need to evaluate the current freeze settings against the air temperature.
|
|
1333
|
-
if (typeof state.temps.air !== 'undefined')
|
|
1405
|
+
if (typeof state.temps.air !== 'undefined') {
|
|
1406
|
+
// Start freeze protection when the temperature is <= the threshold but don't stop it until we are 2 degrees above the threshold. This
|
|
1407
|
+
// makes for a 3 degree offset.
|
|
1408
|
+
if (state.temps.air <= sys.general.options.freezeThreshold) freeze = true;
|
|
1409
|
+
else if (state.freeze && state.temps.air - 2 > sys.general.options.freezeThreshold) freeze = false;
|
|
1410
|
+
}
|
|
1334
1411
|
else freeze = false;
|
|
1335
1412
|
|
|
1336
1413
|
// We need to know when we first turned the freeze protection on. This is because we will be rotating between pool and spa
|
|
@@ -1422,7 +1499,7 @@ export class BodyCommands extends BoardCommands {
|
|
|
1422
1499
|
else if (freeze && !cstate.isOn) {
|
|
1423
1500
|
// This circuit should be on because we are freezing.
|
|
1424
1501
|
cstate.freezeProtect = true;
|
|
1425
|
-
await sys.board.
|
|
1502
|
+
await sys.board.circuits.setCircuitStateAsync(circ.id, true);
|
|
1426
1503
|
}
|
|
1427
1504
|
else if (!freeze && cstate.freezeProtect) {
|
|
1428
1505
|
// This feature was turned on by freeze protection. We need to turn it off because it has warmed up.
|
|
@@ -1431,7 +1508,7 @@ export class BodyCommands extends BoardCommands {
|
|
|
1431
1508
|
}
|
|
1432
1509
|
}
|
|
1433
1510
|
}
|
|
1434
|
-
catch (err) { logger.error(`syncFreezeProtection: Error synchronizing freeze protection states`); }
|
|
1511
|
+
catch (err) { logger.error(`syncFreezeProtection: Error synchronizing freeze protection states: ${err.message}`); }
|
|
1435
1512
|
}
|
|
1436
1513
|
|
|
1437
1514
|
public async initFilters() {
|
|
@@ -1477,7 +1554,10 @@ export class BodyCommands extends BoardCommands {
|
|
|
1477
1554
|
let id = parseInt(obj.id, 10); 1
|
|
1478
1555
|
if (isNaN(id)) reject(new InvalidEquipmentIdError('Body Id has not been defined', obj.id, 'Body'));
|
|
1479
1556
|
let body = sys.bodies.getItemById(id, false);
|
|
1557
|
+
let sbody = state.temps.bodies.getItemById(id, false);
|
|
1480
1558
|
body.set(obj);
|
|
1559
|
+
sbody.name = body.name;
|
|
1560
|
+
sbody.showInDashboard = body.showInDashboard;
|
|
1481
1561
|
resolve(body);
|
|
1482
1562
|
});
|
|
1483
1563
|
}
|
|
@@ -1570,54 +1650,57 @@ export class BodyCommands extends BoardCommands {
|
|
|
1570
1650
|
sys.board.heaters.syncHeaterStates();
|
|
1571
1651
|
return Promise.resolve(bstate);
|
|
1572
1652
|
}
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
}
|
|
1585
|
-
if (heatTypes.heatpump > 0) {
|
|
1586
|
-
let hm = this.board.valueMaps.heatSources.transformByName('heatpump');
|
|
1587
|
-
heatSources.push(hm);
|
|
1588
|
-
if (heatTypes.total > 1) heatSources.push(this.board.valueMaps.heatSources.transformByName('heatpumppref'));
|
|
1589
|
-
}
|
|
1590
|
-
if (heatTypes.ultratemp > 0) {
|
|
1591
|
-
let hm = this.board.valueMaps.heatSources.transformByName('ultratemp');
|
|
1592
|
-
heatSources.push(hm);
|
|
1593
|
-
if (heatTypes.total > 1) heatSources.push(this.board.valueMaps.heatSources.transformByName('ultratemppref'));
|
|
1594
|
-
}
|
|
1595
|
-
return heatSources;
|
|
1653
|
+
public getHeatSources(bodyId: number) {
|
|
1654
|
+
let heatSources = [];
|
|
1655
|
+
let heatTypes = this.board.heaters.getInstalledHeaterTypes(bodyId);
|
|
1656
|
+
heatSources.push(this.board.valueMaps.heatSources.transformByName('nochange'));
|
|
1657
|
+
if (heatTypes.total > 0) heatSources.push(this.board.valueMaps.heatSources.transformByName('off'));
|
|
1658
|
+
if (heatTypes.gas > 0) heatSources.push(this.board.valueMaps.heatSources.transformByName('heater'));
|
|
1659
|
+
if (heatTypes.mastertemp > 0) heatSources.push(this.board.valueMaps.heatSources.transformByName('mastertemp'));
|
|
1660
|
+
if (heatTypes.solar > 0) {
|
|
1661
|
+
let hm = this.board.valueMaps.heatSources.transformByName('solar');
|
|
1662
|
+
heatSources.push(hm);
|
|
1663
|
+
if (heatTypes.total > 1) heatSources.push(this.board.valueMaps.heatSources.transformByName('solarpref'));
|
|
1596
1664
|
}
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1665
|
+
if (heatTypes.heatpump > 0) {
|
|
1666
|
+
let hm = this.board.valueMaps.heatSources.transformByName('heatpump');
|
|
1667
|
+
heatSources.push(hm);
|
|
1668
|
+
if (heatTypes.total > 1) heatSources.push(this.board.valueMaps.heatSources.transformByName('heatpumppref'));
|
|
1669
|
+
}
|
|
1670
|
+
if (heatTypes.ultratemp > 0) {
|
|
1671
|
+
let hm = this.board.valueMaps.heatSources.transformByName('ultratemp');
|
|
1672
|
+
heatSources.push(hm);
|
|
1673
|
+
if (heatTypes.total > 1) heatSources.push(this.board.valueMaps.heatSources.transformByName('ultratemppref'));
|
|
1674
|
+
}
|
|
1675
|
+
return heatSources;
|
|
1676
|
+
}
|
|
1677
|
+
public getHeatModes(bodyId: number) {
|
|
1678
|
+
let heatModes = [];
|
|
1679
|
+
sys.board.heaters.updateHeaterServices();
|
|
1680
|
+
|
|
1681
|
+
// RKS: 09-26-20 This will need to be overloaded in IntelliCenterBoard when the other heater types are identified. (e.g. ultratemp, hybrid, maxetherm, and mastertemp)
|
|
1682
|
+
heatModes.push(this.board.valueMaps.heatModes.transformByName('off')); // In IC fw 1.047 off is no longer 0.
|
|
1683
|
+
let heatTypes = this.board.heaters.getInstalledHeaterTypes(bodyId);
|
|
1684
|
+
if (heatTypes.hybrid > 0) heatModes = this.board.valueMaps.heatModes.toArray();
|
|
1685
|
+
if (heatTypes.gas > 0) heatModes.push(this.board.valueMaps.heatModes.transformByName('heater'));
|
|
1686
|
+
if (heatTypes.mastertemp > 0) heatModes.push(this.board.valueMaps.heatModes.transformByName('mtheater'));
|
|
1687
|
+
if (heatTypes.solar > 0) {
|
|
1688
|
+
let hm = this.board.valueMaps.heatModes.transformByName('solar');
|
|
1689
|
+
heatModes.push(hm);
|
|
1690
|
+
if (heatTypes.total > 1) heatModes.push(this.board.valueMaps.heatModes.transformByName('solarpref'));
|
|
1691
|
+
}
|
|
1692
|
+
if (heatTypes.heatpump > 0) {
|
|
1693
|
+
let hm = this.board.valueMaps.heatModes.transformByName('heatpump');
|
|
1694
|
+
heatModes.push(hm);
|
|
1695
|
+
if (heatTypes.total > 1) heatModes.push(this.board.valueMaps.heatModes.transformByName('heatpumppref'));
|
|
1696
|
+
}
|
|
1697
|
+
if (heatTypes.ultratemp > 0) {
|
|
1698
|
+
let hm = this.board.valueMaps.heatModes.transformByName('ultratemp');
|
|
1699
|
+
heatModes.push(hm);
|
|
1700
|
+
if (heatTypes.total > 1) heatModes.push(this.board.valueMaps.heatModes.transformByName('ultratemppref'));
|
|
1620
1701
|
}
|
|
1702
|
+
return heatModes;
|
|
1703
|
+
}
|
|
1621
1704
|
public getPoolStates(): BodyTempState[] {
|
|
1622
1705
|
let arrPools = [];
|
|
1623
1706
|
for (let i = 0; i < state.temps.bodies.length; i++) {
|
|
@@ -1676,8 +1759,9 @@ export class BodyCommands extends BoardCommands {
|
|
|
1676
1759
|
case 'body4':
|
|
1677
1760
|
return state.temps.bodies.getItemById(4).isOn;
|
|
1678
1761
|
case 'poolspa':
|
|
1679
|
-
if (sys.equipment.shared && sys.equipment.maxBodies >= 2)
|
|
1680
|
-
return state.temps.bodies.getItemById(1).isOn || state.temps.bodies.getItemById(2).isOn;
|
|
1762
|
+
if (sys.equipment.shared && sys.equipment.maxBodies >= 2) {
|
|
1763
|
+
return state.temps.bodies.getItemById(1).isOn === true || state.temps.bodies.getItemById(2).isOn === true;
|
|
1764
|
+
}
|
|
1681
1765
|
else
|
|
1682
1766
|
return state.temps.bodies.getItemById(1).isOn;
|
|
1683
1767
|
}
|
|
@@ -1857,6 +1941,7 @@ export class PumpCommands extends BoardCommands {
|
|
|
1857
1941
|
_availCircuits.push({ type: 'none', id: 255, name: 'Remove' });
|
|
1858
1942
|
return _availCircuits;
|
|
1859
1943
|
}
|
|
1944
|
+
public setPumpValveDelays(circuitIds: number[], delay?: number) {}
|
|
1860
1945
|
}
|
|
1861
1946
|
export class CircuitCommands extends BoardCommands {
|
|
1862
1947
|
public async restore(rest: { poolConfig: any, poolState: any }, ctx: any, res: RestoreResults): Promise<boolean> {
|
|
@@ -1918,7 +2003,7 @@ export class CircuitCommands extends BoardCommands {
|
|
|
1918
2003
|
res.addModuleSuccess('circuitGroup', `Update: ${c.id}-${c.name}`);
|
|
1919
2004
|
} catch (err) { res.addModuleError('circuitGroup', `Update: ${c.id}-${c.name}: ${err.message}`); }
|
|
1920
2005
|
}
|
|
1921
|
-
for (let i = 0; i < ctx.lightGroups.
|
|
2006
|
+
for (let i = 0; i < ctx.lightGroups.update.length; i++) {
|
|
1922
2007
|
let c = ctx.lightGroups.update[i];
|
|
1923
2008
|
try {
|
|
1924
2009
|
await sys.board.circuits.setLightGroupAsync(c);
|
|
@@ -1982,14 +2067,14 @@ export class CircuitCommands extends BoardCommands {
|
|
|
1982
2067
|
for (let i = 0; i < sys.circuits.length; i++) {
|
|
1983
2068
|
let c = sys.circuits.getItemByIndex(i);
|
|
1984
2069
|
let cstate = state.circuits.getItemByIndex(i);
|
|
1985
|
-
if (!cstate.isActive || !cstate.isOn) continue;
|
|
2070
|
+
if (!cstate.isActive || !cstate.isOn || typeof cstate.endTime === 'undefined') continue;
|
|
1986
2071
|
if (c.master === 1) {
|
|
1987
2072
|
await ncp.circuits.checkCircuitEggTimerExpirationAsync(cstate);
|
|
1988
2073
|
}
|
|
1989
2074
|
}
|
|
1990
2075
|
for (let i = 0; i < sys.features.length; i++) {
|
|
1991
2076
|
let fstate = state.features.getItemByIndex(i);
|
|
1992
|
-
if (!fstate.isActive || !fstate.isOn) continue;
|
|
2077
|
+
if (!fstate.isActive || !fstate.isOn || typeof fstate.endTime === 'undefined') continue;
|
|
1993
2078
|
if (fstate.endTime.toDate() < new Timestamp().toDate()) {
|
|
1994
2079
|
await sys.board.circuits.setCircuitStateAsync(fstate.id, false);
|
|
1995
2080
|
fstate.emitEquipmentChange();
|
|
@@ -1997,7 +2082,7 @@ export class CircuitCommands extends BoardCommands {
|
|
|
1997
2082
|
}
|
|
1998
2083
|
for (let i = 0; i < sys.circuitGroups.length; i++) {
|
|
1999
2084
|
let cgstate = state.circuitGroups.getItemByIndex(i);
|
|
2000
|
-
if (!cgstate.isActive || !cgstate.isOn) continue;
|
|
2085
|
+
if (!cgstate.isActive || !cgstate.isOn || typeof cgstate.endTime === 'undefined') continue;
|
|
2001
2086
|
if (cgstate.endTime.toDate() < new Timestamp().toDate()) {
|
|
2002
2087
|
await sys.board.circuits.setCircuitGroupStateAsync(cgstate.id, false);
|
|
2003
2088
|
cgstate.emitEquipmentChange();
|
|
@@ -2005,7 +2090,7 @@ export class CircuitCommands extends BoardCommands {
|
|
|
2005
2090
|
}
|
|
2006
2091
|
for (let i = 0; i < sys.lightGroups.length; i++) {
|
|
2007
2092
|
let lgstate = state.lightGroups.getItemByIndex(i);
|
|
2008
|
-
if (!lgstate.isActive || !lgstate.isOn) continue;
|
|
2093
|
+
if (!lgstate.isActive || !lgstate.isOn || typeof lgstate.endTime === 'undefined') continue;
|
|
2009
2094
|
if (lgstate.endTime.toDate() < new Timestamp().toDate()) {
|
|
2010
2095
|
await sys.board.circuits.setLightGroupStateAsync(lgstate.id, false);
|
|
2011
2096
|
lgstate.emitEquipmentChange();
|
|
@@ -2050,8 +2135,13 @@ export class CircuitCommands extends BoardCommands {
|
|
|
2050
2135
|
}
|
|
2051
2136
|
if (!remove) {
|
|
2052
2137
|
// Determine whether the pool heater is on.
|
|
2053
|
-
for (let j = 0; j < poolStates.length; j++)
|
|
2054
|
-
if (sys.board.valueMaps.heatStatus.getName(poolStates[j].heatStatus) === 'heater')
|
|
2138
|
+
for (let j = 0; j < poolStates.length; j++) {
|
|
2139
|
+
if (sys.board.valueMaps.heatStatus.getName(poolStates[j].heatStatus) === 'heater') {
|
|
2140
|
+
// In this instance we may have a delay underway.
|
|
2141
|
+
let hstate = state.heaters.find(x => x.bodyId === 1 && x.startupDelay === true && x.type.name !== 'solar');
|
|
2142
|
+
bState = typeof hstate === 'undefined';
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2055
2145
|
}
|
|
2056
2146
|
break;
|
|
2057
2147
|
case 'spaHeater':
|
|
@@ -2062,6 +2152,34 @@ export class CircuitCommands extends BoardCommands {
|
|
|
2062
2152
|
if (!remove) {
|
|
2063
2153
|
// Determine whether the spa heater is on.
|
|
2064
2154
|
for (let j = 0; j < spaStates.length; j++) {
|
|
2155
|
+
if (sys.board.valueMaps.heatStatus.getName(spaStates[j].heatStatus) === 'heater') {
|
|
2156
|
+
// In this instance we may have a delay underway.
|
|
2157
|
+
let hstate = state.heaters.find(x => x.bodyId === 1 && x.startupDelay === true && x.type.name !== 'solar');
|
|
2158
|
+
bState = typeof hstate === 'undefined';
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
//for (let j = 0; j < spaStates.length; j++) {
|
|
2162
|
+
// if (sys.board.valueMaps.heatStatus.getName(spaStates[j].heatStatus) === 'heater') bState = true;
|
|
2163
|
+
//}
|
|
2164
|
+
}
|
|
2165
|
+
break;
|
|
2166
|
+
case 'heater':
|
|
2167
|
+
// If heater is on for any body
|
|
2168
|
+
// RSG 5-3-22: Heater will now refer to any poolHeater or spaHeater but not solar or other types. anyHeater now takes that role.
|
|
2169
|
+
remove = true;
|
|
2170
|
+
for (let j = 0; j < poolStates.length; j++) {
|
|
2171
|
+
if (poolStates[j].heaterOptions.solar + poolStates[j].heaterOptions.heatpump > 0) remove = false;
|
|
2172
|
+
}
|
|
2173
|
+
if (remove) {
|
|
2174
|
+
for (let j = 0; j < spaStates.length; j++) {
|
|
2175
|
+
if (spaStates[j].heaterOptions.solar + spaStates[j].heaterOptions.heatpump > 0) remove = false;
|
|
2176
|
+
}
|
|
2177
|
+
}
|
|
2178
|
+
if (!remove) {
|
|
2179
|
+
for (let j = 0; j < poolStates.length && !bState; j++) {
|
|
2180
|
+
if (sys.board.valueMaps.heatStatus.getName(poolStates[j].heatStatus) === 'heater') bState = true;
|
|
2181
|
+
}
|
|
2182
|
+
for (let j = 0; j < spaStates.length && !bState; j++) {
|
|
2065
2183
|
if (sys.board.valueMaps.heatStatus.getName(spaStates[j].heatStatus) === 'heater') bState = true;
|
|
2066
2184
|
}
|
|
2067
2185
|
}
|
|
@@ -2100,7 +2218,110 @@ export class CircuitCommands extends BoardCommands {
|
|
|
2100
2218
|
}
|
|
2101
2219
|
}
|
|
2102
2220
|
break;
|
|
2103
|
-
case '
|
|
2221
|
+
case 'solar1':
|
|
2222
|
+
remove = true;
|
|
2223
|
+
for (let j = 0; j < poolStates.length; j++) {
|
|
2224
|
+
if (poolStates[j].id === 1 && poolStates[j].heaterOptions.solar) {
|
|
2225
|
+
remove = false;
|
|
2226
|
+
vc.desc = `${poolStates[j].name} Solar`;
|
|
2227
|
+
if (sys.board.valueMaps.heatStatus.getName(poolStates[j].heatStatus) === 'solar') {
|
|
2228
|
+
// In this instance we may have a delay underway.
|
|
2229
|
+
let hstate = state.heaters.find(x => x.bodyId === 1 && x.startupDelay === true && x.type.name === 'solar');
|
|
2230
|
+
bState = typeof hstate === 'undefined';
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
for (let j = 0; j < spaStates.length; j++) {
|
|
2235
|
+
if (spaStates[j].id === 1 && spaStates[j].heaterOptions.solar) {
|
|
2236
|
+
remove = false;
|
|
2237
|
+
vc.desc = `${spaStates[j].name} Solar`;
|
|
2238
|
+
if (sys.board.valueMaps.heatStatus.getName(spaStates[j].heatStatus) === 'solar') {
|
|
2239
|
+
// In this instance we may have a delay underway.
|
|
2240
|
+
let hstate = state.heaters.find(x => x.bodyId === 1 && x.startupDelay === true && x.type.name === 'solar');
|
|
2241
|
+
bState = typeof hstate === 'undefined';
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
|
|
2246
|
+
break;
|
|
2247
|
+
case 'solar2':
|
|
2248
|
+
remove = true;
|
|
2249
|
+
for (let j = 0; j < poolStates.length; j++) {
|
|
2250
|
+
if (poolStates[j].id === 2 && poolStates[j].heaterOptions.solar) {
|
|
2251
|
+
remove = false;
|
|
2252
|
+
vc.desc = `${poolStates[j].name} Solar`;
|
|
2253
|
+
if (sys.board.valueMaps.heatStatus.getName(spaStates[j].heatStatus) === 'solar') {
|
|
2254
|
+
// In this instance we may have a delay underway.
|
|
2255
|
+
let hstate = state.heaters.find(x => x.bodyId === 2 && x.startupDelay === true && x.type.name === 'solar');
|
|
2256
|
+
bState = typeof hstate === 'undefined';
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
for (let j = 0; j < spaStates.length; j++) {
|
|
2261
|
+
if (spaStates[j].id === 2 && spaStates[j].heaterOptions.solar) {
|
|
2262
|
+
remove = false;
|
|
2263
|
+
vc.desc = `${spaStates[j].name} Solar`;
|
|
2264
|
+
if (sys.board.valueMaps.heatStatus.getName(spaStates[j].heatStatus) === 'solar') {
|
|
2265
|
+
// In this instance we may have a delay underway.
|
|
2266
|
+
let hstate = state.heaters.find(x => x.bodyId === 2 && x.startupDelay === true && x.type.name === 'solar');
|
|
2267
|
+
bState = typeof hstate === 'undefined';
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2271
|
+
break;
|
|
2272
|
+
case 'solar3':
|
|
2273
|
+
remove = true;
|
|
2274
|
+
for (let j = 0; j < poolStates.length; j++) {
|
|
2275
|
+
if (poolStates[j].id === 3 && poolStates[j].heaterOptions.solar) {
|
|
2276
|
+
remove = false;
|
|
2277
|
+
vc.desc = `${poolStates[j].name} Solar`;
|
|
2278
|
+
if (sys.board.valueMaps.heatStatus.getName(spaStates[j].heatStatus) === 'solar') {
|
|
2279
|
+
// In this instance we may have a delay underway.
|
|
2280
|
+
let hstate = state.heaters.find(x => x.bodyId === 3 && x.startupDelay === true && x.type.name === 'solar');
|
|
2281
|
+
bState = typeof hstate === 'undefined';
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
for (let j = 0; j < spaStates.length; j++) {
|
|
2286
|
+
if (spaStates[j].id === 3 && spaStates[j].heaterOptions.solar) {
|
|
2287
|
+
remove = false;
|
|
2288
|
+
vc.desc = `${spaStates[j].name} Solar`;
|
|
2289
|
+
if (sys.board.valueMaps.heatStatus.getName(spaStates[j].heatStatus) === 'solar') {
|
|
2290
|
+
// In this instance we may have a delay underway.
|
|
2291
|
+
let hstate = state.heaters.find(x => x.bodyId === 3 && x.startupDelay === true && x.type.name === 'solar');
|
|
2292
|
+
bState = typeof hstate === 'undefined';
|
|
2293
|
+
}
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
|
|
2297
|
+
break;
|
|
2298
|
+
case 'solar4':
|
|
2299
|
+
remove = true;
|
|
2300
|
+
for (let j = 0; j < poolStates.length; j++) {
|
|
2301
|
+
if (poolStates[j].id === 4 && poolStates[j].heaterOptions.solar) {
|
|
2302
|
+
remove = false;
|
|
2303
|
+
vc.desc = `${poolStates[j].name} Solar`;
|
|
2304
|
+
if (sys.board.valueMaps.heatStatus.getName(spaStates[j].heatStatus) === 'solar') {
|
|
2305
|
+
// In this instance we may have a delay underway.
|
|
2306
|
+
let hstate = state.heaters.find(x => x.bodyId === 4 && x.startupDelay === true && x.type.name === 'solar');
|
|
2307
|
+
bState = typeof hstate === 'undefined';
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
for (let j = 0; j < spaStates.length; j++) {
|
|
2312
|
+
if (spaStates[j].id === 4 && spaStates[j].heaterOptions.solar) {
|
|
2313
|
+
remove = false;
|
|
2314
|
+
vc.desc = `${spaStates[j].name} Solar`;
|
|
2315
|
+
if (sys.board.valueMaps.heatStatus.getName(spaStates[j].heatStatus) === 'solar') {
|
|
2316
|
+
// In this instance we may have a delay underway.
|
|
2317
|
+
let hstate = state.heaters.find(x => x.bodyId === 4 && x.startupDelay === true && x.type.name === 'solar');
|
|
2318
|
+
bState = typeof hstate === 'undefined';
|
|
2319
|
+
}
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
break;
|
|
2323
|
+
case 'anyHeater':
|
|
2324
|
+
// RSG 5-3-22 anyHeater now represents any solar, gas, etc heater. This replaces 'heater' which now refers to only gas heaters.
|
|
2104
2325
|
remove = true;
|
|
2105
2326
|
for (let j = 0; j < poolStates.length; j++) {
|
|
2106
2327
|
if (poolStates[j].heaterOptions.total > 0) remove = false;
|
|
@@ -2125,10 +2346,17 @@ export class CircuitCommands extends BoardCommands {
|
|
|
2125
2346
|
remove = true;
|
|
2126
2347
|
break;
|
|
2127
2348
|
}
|
|
2128
|
-
if (remove)
|
|
2349
|
+
if (remove) {
|
|
2350
|
+
if (state.virtualCircuits.exists(x => vc.val === x.id)) {
|
|
2351
|
+
cstate = state.virtualCircuits.getItemById(vc.val, true);
|
|
2352
|
+
cstate.isActive = false;
|
|
2353
|
+
cstate.emitEquipmentChange();
|
|
2354
|
+
}
|
|
2129
2355
|
state.virtualCircuits.removeItemById(vc.val);
|
|
2356
|
+
}
|
|
2130
2357
|
else {
|
|
2131
2358
|
cstate = state.virtualCircuits.getItemById(vc.val, true);
|
|
2359
|
+
cstate.isActive = true;
|
|
2132
2360
|
if (cstate !== null) {
|
|
2133
2361
|
cstate.isOn = bState;
|
|
2134
2362
|
cstate.type = vc.val;
|
|
@@ -2138,7 +2366,7 @@ export class CircuitCommands extends BoardCommands {
|
|
|
2138
2366
|
}
|
|
2139
2367
|
} catch (err) { logger.error(`Error syncronizing virtual circuits`); }
|
|
2140
2368
|
}
|
|
2141
|
-
public async setCircuitStateAsync(id: number, val: boolean): Promise<ICircuitState> {
|
|
2369
|
+
public async setCircuitStateAsync(id: number, val: boolean, ignoreDelays?: boolean): Promise<ICircuitState> {
|
|
2142
2370
|
sys.board.suspendStatus(true);
|
|
2143
2371
|
try {
|
|
2144
2372
|
// We need to do some routing here as it is now critical that circuits, groups, and features
|
|
@@ -2185,17 +2413,133 @@ export class CircuitCommands extends BoardCommands {
|
|
|
2185
2413
|
let circ = state.circuits.getInterfaceById(id);
|
|
2186
2414
|
return await this.setCircuitStateAsync(id, !(circ.isOn || false));
|
|
2187
2415
|
}
|
|
2188
|
-
public async
|
|
2416
|
+
public async runLightGroupCommandAsync(obj: any): Promise<ICircuitState> {
|
|
2417
|
+
// Do all our validation.
|
|
2418
|
+
try {
|
|
2419
|
+
let id = parseInt(obj.id, 10);
|
|
2420
|
+
let cmd = typeof obj.command !== 'undefined' ? sys.board.valueMaps.lightGroupCommands.findItem(obj.command) : { val: 0, name: 'undefined' };
|
|
2421
|
+
if (cmd.val === 0) return Promise.reject(new InvalidOperationError(`Light group command ${cmd.name} does not exist`, 'runLightGroupCommandAsync'));
|
|
2422
|
+
if (isNaN(id)) return Promise.reject(new InvalidOperationError(`Light group ${id} does not exist`, 'runLightGroupCommandAsync'));
|
|
2423
|
+
let grp = sys.lightGroups.getItemById(id);
|
|
2424
|
+
let nop = sys.board.valueMaps.circuitActions.getValue(cmd.name);
|
|
2425
|
+
let sgrp = state.lightGroups.getItemById(grp.id);
|
|
2426
|
+
sgrp.action = nop;
|
|
2427
|
+
sgrp.emitEquipmentChange();
|
|
2428
|
+
// So here we are now we can run the command against all lights in the group that match the command so get a list of the lights.
|
|
2429
|
+
let arrCircs = [];
|
|
2430
|
+
for (let i = 0; i < grp.circuits.length; i++) {
|
|
2431
|
+
let circ = sys.circuits.getItemById(grp.circuits.getItemByIndex(i).circuit);
|
|
2432
|
+
let type = sys.board.valueMaps.circuitFunctions.transform(circ.type);
|
|
2433
|
+
if (type.isLight && cmd.types.includes(type.theme)) arrCircs.push(circ);
|
|
2434
|
+
}
|
|
2435
|
+
// So now we should hav a complete list of the lights that are part of the command list so start them off on their sequence. We want all the lights
|
|
2436
|
+
// to be doing their thing at the same time so in the lieu of threads we will ceate a promise all.
|
|
2437
|
+
let proms = [];
|
|
2438
|
+
for (let i = 0; i < arrCircs.length; i++) {
|
|
2439
|
+
await ncp.circuits.sendOnOffSequenceAsync(arrCircs[i].id, cmd.sequence);
|
|
2440
|
+
//proms.push(ncp.circuits.sendOnOffSequenceAsync(arrCircs[i].id, cmd.sequence));
|
|
2441
|
+
}
|
|
2442
|
+
for (let i = 0; i < arrCircs.length; i++) {
|
|
2443
|
+
await sys.board.circuits.setCircuitStateAsync(arrCircs[i].id, false);
|
|
2444
|
+
//proms.push(ncp.circuits.sendOnOffSequenceAsync(arrCircs[i].id, cmd.sequence));
|
|
2445
|
+
}
|
|
2446
|
+
await utils.sleep(10000);
|
|
2447
|
+
for (let i = 0; i < arrCircs.length; i++) {
|
|
2448
|
+
await sys.board.circuits.setCircuitStateAsync(arrCircs[i].id, true);
|
|
2449
|
+
//proms.push(ncp.circuits.sendOnOffSequenceAsync(arrCircs[i].id, cmd.sequence));
|
|
2450
|
+
}
|
|
2451
|
+
|
|
2452
|
+
//if (proms.length > 0) {
|
|
2453
|
+
// //await Promise.all(proms);
|
|
2454
|
+
// // Let it simmer for 6 seconds then turn it off and back on.
|
|
2455
|
+
// proms.length = 0;
|
|
2456
|
+
// for (let i = 0; i < arrCircs.length; i++) {
|
|
2457
|
+
// proms.push(sys.board.circuits.setCircuitStateAsync(arrCircs[i].id, false));
|
|
2458
|
+
// }
|
|
2459
|
+
// await Promise.all(proms);
|
|
2460
|
+
// // Let it be off for 3 seconds then turn it back on.
|
|
2461
|
+
// await utils.sleep(10000);
|
|
2462
|
+
// proms.length = 0;
|
|
2463
|
+
// for (let i = 0; i < arrCircs.length; i++) {
|
|
2464
|
+
// proms.push(sys.board.circuits.setCircuitStateAsync(arrCircs[i].id, true));
|
|
2465
|
+
// }
|
|
2466
|
+
// await Promise.all(proms);
|
|
2467
|
+
//}
|
|
2468
|
+
sgrp.action = 0;
|
|
2469
|
+
sgrp.emitEquipmentChange();
|
|
2470
|
+
return state.lightGroups.getItemById(id);
|
|
2471
|
+
}
|
|
2472
|
+
catch (err) { return Promise.reject(`Error runLightGroupCommandAsync ${err.message}`); }
|
|
2473
|
+
}
|
|
2474
|
+
public async runLightCommandAsync(obj: any): Promise<ICircuitState> {
|
|
2475
|
+
// Do all our validation.
|
|
2476
|
+
try {
|
|
2477
|
+
let id = parseInt(obj.id, 10);
|
|
2478
|
+
let cmd = typeof obj.command !== 'undefined' ? sys.board.valueMaps.lightCommands.findItem(obj.command) : { val: 0, name: 'undefined' };
|
|
2479
|
+
if (cmd.val === 0) return Promise.reject(new InvalidOperationError(`Light command ${cmd.name} does not exist`, 'runLightCommandAsync'));
|
|
2480
|
+
if (isNaN(id)) return Promise.reject(new InvalidOperationError(`Light ${id} does not exist`, 'runLightCommandAsync'));
|
|
2481
|
+
let circ = sys.circuits.getItemById(id);
|
|
2482
|
+
if (!circ.isActive) return Promise.reject(new InvalidOperationError(`Light circuit #${id} is not active`, 'runLightCommandAsync'));
|
|
2483
|
+
let type = sys.board.valueMaps.circuitFunctions.transform(circ.type);
|
|
2484
|
+
if (!type.isLight) return Promise.reject(new InvalidOperationError(`Circuit #${id} is not a light`, 'runLightCommandAsync'));
|
|
2485
|
+
let nop = sys.board.valueMaps.circuitActions.getValue(cmd.name);
|
|
2486
|
+
let slight = state.circuits.getItemById(circ.id);
|
|
2487
|
+
slight.action = nop;
|
|
2488
|
+
console.log(nop);
|
|
2489
|
+
slight.emitEquipmentChange();
|
|
2490
|
+
await ncp.circuits.sendOnOffSequenceAsync(circ.id, cmd.sequence);
|
|
2491
|
+
await utils.sleep(7000);
|
|
2492
|
+
await sys.board.circuits.setCircuitStateAsync(circ.id, false);
|
|
2493
|
+
await sys.board.circuits.setCircuitStateAsync(circ.id, true);
|
|
2494
|
+
slight.action = 0;
|
|
2495
|
+
slight.emitEquipmentChange();
|
|
2496
|
+
return slight;
|
|
2497
|
+
}
|
|
2498
|
+
catch (err) { return Promise.reject(`Error runLightCommandAsync ${err.message}`); }
|
|
2499
|
+
}
|
|
2500
|
+
public async setLightThemeAsync(id: number, theme: number): Promise<ICircuitState> {
|
|
2189
2501
|
let cstate = state.circuits.getItemById(id);
|
|
2190
2502
|
let circ = sys.circuits.getItemById(id);
|
|
2191
2503
|
let thm = sys.board.valueMaps.lightThemes.findItem(theme);
|
|
2192
|
-
|
|
2504
|
+
let nop = sys.board.valueMaps.circuitActions.getValue('lighttheme');
|
|
2505
|
+
cstate.action = nop;
|
|
2506
|
+
cstate.emitEquipmentChange();
|
|
2507
|
+
try {
|
|
2508
|
+
if (typeof thm !== 'undefined' && typeof thm.sequence !== 'undefined' && circ.master === 1) {
|
|
2509
|
+
await sys.board.circuits.setCircuitStateAsync(id, true);
|
|
2510
|
+
await ncp.circuits.sendOnOffSequenceAsync(id, thm.sequence);
|
|
2511
|
+
}
|
|
2512
|
+
cstate.lightingTheme = theme;
|
|
2513
|
+
return cstate;
|
|
2514
|
+
} catch (err) { return Promise.reject(new InvalidOperationError(err.message, 'setLightThemeAsync')); }
|
|
2515
|
+
finally { cstate.action = 0; cstate.emitEquipmentChange(); }
|
|
2516
|
+
}
|
|
2517
|
+
public async setColorHoldAsync(id: number): Promise<ICircuitState> {
|
|
2518
|
+
try {
|
|
2519
|
+
let circ = sys.circuits.getItemById(id);
|
|
2520
|
+
if (!circ.isActive) return Promise.reject(new InvalidEquipmentIdError(`Invalid circuit id ${id}`, id, 'circuit'));
|
|
2521
|
+
let cstate = state.circuits.getItemById(circ.id);
|
|
2522
|
+
let cmd = sys.board.valueMaps.lightCommands.findItem('colorhold');
|
|
2523
|
+
await sys.board.circuits.setCircuitStateAsync(id, true);
|
|
2524
|
+
if (circ.master === 1) await ncp.circuits.sendOnOffSequenceAsync(id, cmd.sequence);
|
|
2525
|
+
return cstate;
|
|
2526
|
+
}
|
|
2527
|
+
catch (err) { return Promise.reject(`Nixie: Error setColorHoldAsync ${err.message}`); }
|
|
2528
|
+
}
|
|
2529
|
+
public async setColorRecallAsync(id: number): Promise<ICircuitState> {
|
|
2530
|
+
try {
|
|
2531
|
+
let circ = sys.circuits.getItemById(id);
|
|
2532
|
+
if (!circ.isActive) return Promise.reject(new InvalidEquipmentIdError(`Invalid circuit id ${id}`, id, 'circuit'));
|
|
2533
|
+
let cstate = state.circuits.getItemById(circ.id);
|
|
2534
|
+
let cmd = sys.board.valueMaps.lightCommands.findItem('colorrecall');
|
|
2193
2535
|
await sys.board.circuits.setCircuitStateAsync(id, true);
|
|
2194
|
-
await ncp.circuits.sendOnOffSequenceAsync(id,
|
|
2536
|
+
if (circ.master === 1) await ncp.circuits.sendOnOffSequenceAsync(id, cmd.sequence);
|
|
2537
|
+
return cstate;
|
|
2195
2538
|
}
|
|
2196
|
-
|
|
2197
|
-
return Promise.resolve(cstate as ICircuitState);
|
|
2539
|
+
catch (err) { return Promise.reject(`Nixie: Error setColorHoldAsync ${err.message}`); }
|
|
2198
2540
|
}
|
|
2541
|
+
public async setLightThumperAsync(id: number): Promise<ICircuitState> { return state.circuits.getItemById(id); }
|
|
2542
|
+
|
|
2199
2543
|
public setDimmerLevelAsync(id: number, level: number): Promise<ICircuitState> {
|
|
2200
2544
|
let circ = state.circuits.getItemById(id);
|
|
2201
2545
|
circ.level = level;
|
|
@@ -2250,7 +2594,11 @@ export class CircuitCommands extends BoardCommands {
|
|
|
2250
2594
|
return arrRefs;
|
|
2251
2595
|
}
|
|
2252
2596
|
public getLightThemes(type?: number) { return sys.board.valueMaps.lightThemes.toArray(); }
|
|
2253
|
-
public getCircuitFunctions() {
|
|
2597
|
+
public getCircuitFunctions() {
|
|
2598
|
+
let cf = sys.board.valueMaps.circuitFunctions.toArray();
|
|
2599
|
+
if (!sys.equipment.shared) cf = cf.filter(x => { return x.name !== 'spillway' && x.name !== 'spadrain' });
|
|
2600
|
+
return cf;
|
|
2601
|
+
}
|
|
2254
2602
|
public getCircuitNames() { return [...sys.board.valueMaps.circuitNames.toArray(), ...sys.board.valueMaps.customNames.toArray()]; }
|
|
2255
2603
|
public async setCircuitAsync(data: any): Promise<ICircuit> {
|
|
2256
2604
|
try {
|
|
@@ -2529,20 +2877,20 @@ export class CircuitCommands extends BoardCommands {
|
|
|
2529
2877
|
}
|
|
2530
2878
|
catch (err) { return Promise.reject(err); }
|
|
2531
2879
|
}
|
|
2532
|
-
public sequenceLightGroupAsync(id: number, operation: string): Promise<LightGroupState> {
|
|
2880
|
+
public async sequenceLightGroupAsync(id: number, operation: string): Promise<LightGroupState> {
|
|
2533
2881
|
let sgroup = state.lightGroups.getItemById(id);
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2882
|
+
// This is the default action which really does nothing.
|
|
2883
|
+
try {
|
|
2884
|
+
let nop = sys.board.valueMaps.circuitActions.getValue(operation);
|
|
2885
|
+
if (nop > 0) {
|
|
2886
|
+
sgroup.action = nop;
|
|
2887
|
+
sgroup.emitEquipmentChange();
|
|
2888
|
+
await utils.sleep(10000);
|
|
2540
2889
|
sgroup.action = 0;
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
}
|
|
2545
|
-
return Promise.resolve(sgroup);
|
|
2890
|
+
state.emitAllEquipmentChanges();
|
|
2891
|
+
}
|
|
2892
|
+
return sgroup;
|
|
2893
|
+
} catch (err) { return Promise.reject(new InvalidOperationError(`Error sequencing light group ${err.message}`, 'sequenceLightGroupAsync')); }
|
|
2546
2894
|
}
|
|
2547
2895
|
public async setCircuitGroupStateAsync(id: number, val: boolean): Promise<ICircuitGroupState> {
|
|
2548
2896
|
let grp = sys.circuitGroups.getItemById(id, false, { isActive: false });
|
|
@@ -2567,7 +2915,7 @@ export class CircuitCommands extends BoardCommands {
|
|
|
2567
2915
|
public async setLightGroupStateAsync(id: number, val: boolean): Promise<ICircuitGroupState> {
|
|
2568
2916
|
return sys.board.circuits.setCircuitGroupStateAsync(id, val);
|
|
2569
2917
|
}
|
|
2570
|
-
public setEndTime(thing: ICircuit, thingState: ICircuitState, isOn: boolean, bForce: boolean= false) {
|
|
2918
|
+
public setEndTime(thing: ICircuit, thingState: ICircuitState, isOn: boolean, bForce: boolean = false) {
|
|
2571
2919
|
/*
|
|
2572
2920
|
this is a generic fn for circuits, features, circuitGroups, lightGroups
|
|
2573
2921
|
to set the end time based on the egg timer.
|
|
@@ -2601,25 +2949,29 @@ export class CircuitCommands extends BoardCommands {
|
|
|
2601
2949
|
eggTimerEndTime = state.time.clone().addHours(0, thing.eggTimer);
|
|
2602
2950
|
}
|
|
2603
2951
|
// egg timers don't come into play if a schedule will control the circuit
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
let
|
|
2609
|
-
let
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
if
|
|
2614
|
-
|
|
2615
|
-
|
|
2952
|
+
// schedules don't come into play if the circuit is in manualPriority
|
|
2953
|
+
if (!thingState.manualPriorityActive) {
|
|
2954
|
+
|
|
2955
|
+
for (let i = 0; i < sys.schedules.length; i++) {
|
|
2956
|
+
let sched = sys.schedules.getItemByIndex(i);
|
|
2957
|
+
let ssched = state.schedules.getItemById(sched.id);
|
|
2958
|
+
if (sched.isActive && sys.board.schedules.includesCircuit(sched, thing.id)) {
|
|
2959
|
+
let nearestStartTime = sys.board.schedules.getNearestStartTime(sched);
|
|
2960
|
+
let nearestEndTime = sys.board.schedules.getNearestEndTime(sched);
|
|
2961
|
+
// if the schedule doesn't have an end date (eg no days)...
|
|
2962
|
+
if (nearestEndTime.getTime() === 0) continue;
|
|
2963
|
+
if (ssched.isOn) {
|
|
2964
|
+
if (typeof endTime === 'undefined' || nearestEndTime.getTime() < endTime.getTime()) {
|
|
2965
|
+
endTime = nearestEndTime.clone();
|
|
2966
|
+
eggTimerEndTime = undefined;
|
|
2967
|
+
}
|
|
2616
2968
|
}
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2969
|
+
else {
|
|
2970
|
+
if (typeof eggTimerEndTime !== 'undefined' && eggTimerEndTime.getTime() < nearestStartTime.getTime()) {
|
|
2971
|
+
if (typeof endTime === 'undefined' || eggTimerEndTime.getTime() < endTime.getTime()) endTime = eggTimerEndTime.clone();
|
|
2972
|
+
}
|
|
2973
|
+
else if (typeof endTime === 'undefined' || nearestEndTime.getTime() < endTime.getTime()) endTime = nearestEndTime.clone();
|
|
2621
2974
|
}
|
|
2622
|
-
else if (typeof endTime === 'undefined' || nearestEndTime.getTime() < endTime.getTime()) endTime = nearestEndTime.clone();
|
|
2623
2975
|
}
|
|
2624
2976
|
}
|
|
2625
2977
|
}
|
|
@@ -2631,8 +2983,85 @@ export class CircuitCommands extends BoardCommands {
|
|
|
2631
2983
|
logger.error(`Error setting end time for ${thing.id}: ${err}`)
|
|
2632
2984
|
}
|
|
2633
2985
|
}
|
|
2986
|
+
public async turnOffDrainCircuits(ignoreDelays: boolean) {
|
|
2987
|
+
try {
|
|
2988
|
+
{
|
|
2989
|
+
let drt = sys.board.valueMaps.circuitFunctions.getValue('spadrain');
|
|
2990
|
+
let drains = sys.circuits.filter(x => { return x.type === drt });
|
|
2991
|
+
for (let i = 0; i < drains.length; i++) {
|
|
2992
|
+
let drain = drains.getItemByIndex(i);
|
|
2993
|
+
let sdrain = state.circuits.getItemById(drain.id);
|
|
2994
|
+
if (sdrain.isOn) await sys.board.circuits.setCircuitStateAsync(drain.id, false, ignoreDelays);
|
|
2995
|
+
sdrain.startDelay = false;
|
|
2996
|
+
sdrain.stopDelay = false;
|
|
2997
|
+
}
|
|
2998
|
+
}
|
|
2999
|
+
{
|
|
3000
|
+
let drt = sys.board.valueMaps.featureFunctions.getValue('spadrain');
|
|
3001
|
+
let drains = sys.features.filter(x => { return x.type === drt });
|
|
3002
|
+
for (let i = 0; i < drains.length; i++) {
|
|
3003
|
+
let drain = drains.getItemByIndex(i);
|
|
3004
|
+
let sdrain = state.features.getItemById(drain.id);
|
|
3005
|
+
if (sdrain.isOn) await sys.board.features.setFeatureStateAsync(drain.id, false, ignoreDelays);
|
|
3006
|
+
}
|
|
3007
|
+
}
|
|
3008
|
+
|
|
3009
|
+
} catch (err) { return Promise.reject(new BoardProcessError(`turnOffDrainCircuits: ${err.message}`)); }
|
|
3010
|
+
}
|
|
3011
|
+
public async turnOffCleanerCircuits(bstate: BodyTempState, ignoreDelays?: boolean) {
|
|
3012
|
+
try {
|
|
3013
|
+
// First we have to get all the cleaner circuits that are associated with the
|
|
3014
|
+
// body. To do this we get the circuit functions for all cleaner types associated with the body.
|
|
3015
|
+
//
|
|
3016
|
+
// Cleaner ciruits can always be turned off. However, they cannot always be turned on.
|
|
3017
|
+
let arrTypes = sys.board.valueMaps.circuitFunctions.toArray().filter(x => { return x.name.indexOf('cleaner') !== -1 && x.body === bstate.id; });
|
|
3018
|
+
let cleaners = sys.circuits.filter(x => { return arrTypes.findIndex(t => { return t.val === x.type }) !== -1 });
|
|
3019
|
+
// So now we should have all the cleaner circuits so lets make sure they are off.
|
|
3020
|
+
for (let i = 0; i < cleaners.length; i++) {
|
|
3021
|
+
let cleaner = cleaners.getItemByIndex(i);
|
|
3022
|
+
if (cleaner.isActive) {
|
|
3023
|
+
let cstate = state.circuits.getItemById(cleaner.id, true);
|
|
3024
|
+
if (cstate.isOn || cstate.startDelay) await sys.board.circuits.setCircuitStateAsync(cleaner.id, false, ignoreDelays);
|
|
3025
|
+
}
|
|
3026
|
+
}
|
|
3027
|
+
} catch (err) { return Promise.reject(new BoardProcessError(`turnOffCleanerCircuits: ${err.message}`)); }
|
|
3028
|
+
}
|
|
3029
|
+
public async turnOffSpillwayCircuits(ignoreDelays?: boolean) {
|
|
3030
|
+
try {
|
|
3031
|
+
{
|
|
3032
|
+
let arrTypes = sys.board.valueMaps.circuitFunctions.toArray().filter(x => { return x.name.indexOf('spillway') !== -1 });
|
|
3033
|
+
let spillways = sys.circuits.filter(x => { return arrTypes.findIndex(t => { return t.val === x.type }) !== -1 });
|
|
3034
|
+
// So now we should have all the cleaner circuits so lets make sure they are off.
|
|
3035
|
+
for (let i = 0; i < spillways.length; i++) {
|
|
3036
|
+
let spillway = spillways.getItemByIndex(i);
|
|
3037
|
+
if (spillway.isActive) {
|
|
3038
|
+
let cstate = state.circuits.getItemById(spillway.id, true);
|
|
3039
|
+
if (cstate.isOn || cstate.startDelay) await sys.board.circuits.setCircuitStateAsync(spillway.id, false, ignoreDelays);
|
|
3040
|
+
}
|
|
3041
|
+
}
|
|
3042
|
+
}
|
|
3043
|
+
{
|
|
3044
|
+
let arrTypes = sys.board.valueMaps.featureFunctions.toArray().filter(x => { return x.name.indexOf('spillway') !== -1 });
|
|
3045
|
+
let spillways = sys.features.filter(x => { return arrTypes.findIndex(t => { return t.val === x.type }) !== -1 });
|
|
3046
|
+
// So now we should have all the cleaner features so lets make sure they are off.
|
|
3047
|
+
for (let i = 0; i < spillways.length; i++) {
|
|
3048
|
+
let spillway = spillways.getItemByIndex(i);
|
|
3049
|
+
if (spillway.isActive) {
|
|
3050
|
+
let cstate = state.features.getItemById(spillway.id, true);
|
|
3051
|
+
if (cstate.isOn) await sys.board.features.setFeatureStateAsync(spillway.id, false, ignoreDelays);
|
|
3052
|
+
}
|
|
3053
|
+
}
|
|
3054
|
+
}
|
|
3055
|
+
} catch (err) { return Promise.reject(new BoardProcessError(`turnOffSpillwayCircuits: ${err.message}`)); }
|
|
3056
|
+
}
|
|
2634
3057
|
}
|
|
2635
3058
|
export class FeatureCommands extends BoardCommands {
|
|
3059
|
+
public getFeatureFunctions() {
|
|
3060
|
+
let cf = sys.board.valueMaps.featureFunctions.toArray();
|
|
3061
|
+
if (!sys.equipment.shared) cf = cf.filter(x => { return x.name !== 'spillway' && x.name !== 'spadrain' });
|
|
3062
|
+
return cf;
|
|
3063
|
+
}
|
|
3064
|
+
|
|
2636
3065
|
public async restore(rest: { poolConfig: any, poolState: any }, ctx: any, res: RestoreResults): Promise<boolean> {
|
|
2637
3066
|
try {
|
|
2638
3067
|
// First delete the features that should be removed.
|
|
@@ -2727,7 +3156,7 @@ export class FeatureCommands extends BoardCommands {
|
|
|
2727
3156
|
else
|
|
2728
3157
|
Promise.reject(new InvalidEquipmentIdError('Feature id has not been defined', undefined, 'Feature'));
|
|
2729
3158
|
}
|
|
2730
|
-
public async setFeatureStateAsync(id: number, val: boolean): Promise<ICircuitState> {
|
|
3159
|
+
public async setFeatureStateAsync(id: number, val: boolean, ignoreDelays?: boolean): Promise<ICircuitState> {
|
|
2731
3160
|
try {
|
|
2732
3161
|
if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError(`Invalid feature id: ${id}`, id, 'Feature'));
|
|
2733
3162
|
if (!sys.board.equipmentIds.features.isInRange(id)) return Promise.reject(new InvalidEquipmentIdError(`Invalid feature id: ${id}`, id, 'Feature'));
|
|
@@ -2852,12 +3281,77 @@ export class ChlorinatorCommands extends BoardCommands {
|
|
|
2852
3281
|
public async setChlorAsync(obj: any): Promise<ChlorinatorState> {
|
|
2853
3282
|
try {
|
|
2854
3283
|
let id = parseInt(obj.id, 10);
|
|
2855
|
-
|
|
2856
|
-
let
|
|
2857
|
-
|
|
2858
|
-
|
|
3284
|
+
let chlor: Chlorinator;
|
|
3285
|
+
let master = parseInt(obj.master, 10);
|
|
3286
|
+
let portId = typeof obj.portId !== 'undefined' ? parseInt(obj.portId, 10) : 0;
|
|
3287
|
+
if (isNaN(master)) master = 1; // NCP to control.
|
|
3288
|
+
if (isNaN(id) || id <= 0) {
|
|
3289
|
+
let body = sys.board.bodies.mapBodyAssociation(typeof obj.body !== 'undefined' ? parseInt(obj.body, 10) : 0);
|
|
3290
|
+
if (typeof body === 'undefined') {
|
|
3291
|
+
if (sys.equipment.shared) body = 32;
|
|
3292
|
+
else if (!sys.equipment.dual) body = 1;
|
|
3293
|
+
else return Promise.reject(new InvalidEquipmentDataError(`Chlorinator body association is not valid: ${body}`, 'chlorinator', body));
|
|
3294
|
+
}
|
|
3295
|
+
let poolSetpoint = typeof obj.poolSetpoint !== 'undefined' ? parseInt(obj.poolSetpoint, 10) : 50;
|
|
3296
|
+
let spaSetpoint = typeof obj.spaSetpoint !== 'undefined' ? parseInt(obj.spaSetpoint, 10) : 10;
|
|
3297
|
+
if (isNaN(poolSetpoint) || poolSetpoint > 100 || poolSetpoint < 0) return Promise.reject(new InvalidEquipmentDataError(`Chlorinator poolSetpoint is out of range: ${chlor.poolSetpoint}`, 'chlorinator', poolSetpoint));
|
|
3298
|
+
if (isNaN(spaSetpoint) || spaSetpoint > 100 || spaSetpoint < 0) return Promise.reject(new InvalidEquipmentDataError(`Chlorinator spaSetpoint is out of range: ${chlor.poolSetpoint}`, 'chlorinator', spaSetpoint));
|
|
3299
|
+
if (master === 2) {
|
|
3300
|
+
// We can add as many external chlorinators as we want.
|
|
3301
|
+
id = sys.chlorinators.count(elem => elem.master === 2) + 50;
|
|
3302
|
+
chlor = sys.chlorinators.getItemById(id, true, { id: id, master: parseInt(obj.master, 10) });
|
|
3303
|
+
}
|
|
3304
|
+
else {
|
|
3305
|
+
if (portId === 0 && sys.controllerType !== ControllerType.Nixie) return Promise.reject(new InvalidEquipmentDataError(`You may not install a chlorinator on an ${sys.controllerType} system that is assigned to the Primary Port`, 'Chlorinator', portId));
|
|
3306
|
+
if (sys.chlorinators.count(elem => elem.portId === portId && elem.master !== 2) > 0) return Promise.reject(new InvalidEquipmentDataError(`There is already a chlorinator using port #${portId}. Only one chlorinator may be installed per port.`, 'Chlorinator', portId));
|
|
3307
|
+
// We are adding so we need to see if there is another chlorinator that is not external.
|
|
3308
|
+
if (sys.chlorinators.count(elem => elem.master !== 2) > sys.equipment.maxChlorinators) return Promise.reject(new InvalidEquipmentDataError(`The max number of chlorinators has been exceeded you may only add ${sys.equipment.maxChlorinators}`, 'Chlorinator', sys.equipment.maxChlorinators));
|
|
3309
|
+
id = sys.chlorinators.getMaxId(false, 0) + 1;
|
|
3310
|
+
chlor = sys.chlorinators.getItemById(id, true, { id: id, master: 1 });
|
|
3311
|
+
}
|
|
3312
|
+
}
|
|
3313
|
+
else chlor = sys.chlorinators.getItemById(id, false);
|
|
3314
|
+
|
|
3315
|
+
if (chlor.master === 1)
|
|
3316
|
+
await ncp.chlorinators.setChlorinatorAsync(chlor, obj);
|
|
3317
|
+
else {
|
|
3318
|
+
let body = sys.board.bodies.mapBodyAssociation(typeof obj.body !== 'undefined' ? parseInt(obj.body, 10) : chlor.body);
|
|
3319
|
+
if (typeof body === 'undefined') {
|
|
3320
|
+
if (sys.equipment.shared) body = 32;
|
|
3321
|
+
else if (!sys.equipment.dual) body = 1;
|
|
3322
|
+
else return Promise.reject(new InvalidEquipmentDataError(`Chlorinator body association is not valid: ${body}`, 'chlorinator', body));
|
|
3323
|
+
}
|
|
3324
|
+
let poolSetpoint = typeof obj.poolSetpoint !== 'undefined' ? parseInt(obj.poolSetpoint, 10) : isNaN(chlor.poolSetpoint) ? 50 : chlor.poolSetpoint;
|
|
3325
|
+
let spaSetpoint = typeof obj.spaSetpoint !== 'undefined' ? parseInt(obj.spaSetpoint, 10) : isNaN(chlor.spaSetpoint) ? 10 : chlor.spaSetpoint;
|
|
3326
|
+
if (poolSetpoint > 100 || poolSetpoint < 0) return Promise.reject(new InvalidEquipmentDataError(`Chlorinator poolSetpoint is out of range: ${chlor.poolSetpoint}`, 'chlorinator', chlor.poolSetpoint));
|
|
3327
|
+
if (spaSetpoint > 100 || spaSetpoint < 0) return Promise.reject(new InvalidEquipmentDataError(`Chlorinator spaSetpoint is out of range: ${chlor.poolSetpoint}`, 'chlorinator', chlor.spaSetpoint));
|
|
3328
|
+
|
|
3329
|
+
chlor = sys.chlorinators.getItemById(id, true);
|
|
3330
|
+
let schlor = state.chlorinators.getItemById(chlor.id, true);
|
|
3331
|
+
chlor.name = schlor.name = obj.name || chlor.name || 'Chlorinator --' + id;
|
|
3332
|
+
chlor.superChlorHours = schlor.superChlorHours = typeof obj.superChlorHours !== 'undefined' ? parseInt(obj.superChlorHours, 10) : isNaN(chlor.superChlorHours) ? 8 : chlor.superChlorHours;
|
|
3333
|
+
chlor.superChlor = schlor.superChlor = typeof obj.superChlorinate !== 'undefined' ? utils.makeBool(obj.superChlorinate) : chlor.superChlor;
|
|
3334
|
+
chlor.superChlor = schlor.superChlor = typeof obj.superChlor !== 'undefined' ? utils.makeBool(obj.superChlor) : chlor.superChlor;
|
|
3335
|
+
|
|
3336
|
+
chlor.isDosing = typeof obj.isDosing !== 'undefined' ? utils.makeBool(obj.isDosing) : chlor.isDosing || false;
|
|
3337
|
+
chlor.disabled = typeof obj.disabled !== 'undefined' ? utils.makeBool(obj.disabled) : chlor.disabled || false;
|
|
3338
|
+
schlor.model = chlor.model = typeof obj.model !== 'undefined' ? sys.board.valueMaps.chlorinatorModel.encode(obj.model) : chlor.model;
|
|
3339
|
+
chlor.type = schlor.type = typeof obj.type !== 'undefined' ? sys.board.valueMaps.chlorinatorType.encode(obj.type) : chlor.type || 0;
|
|
3340
|
+
chlor.body = schlor.body = body.val;
|
|
3341
|
+
schlor.poolSetpoint = chlor.poolSetpoint = poolSetpoint;
|
|
3342
|
+
schlor.spaSetpoint = chlor.spaSetpoint = spaSetpoint;
|
|
3343
|
+
chlor.ignoreSaltReading = typeof obj.ignoreSaltReading !== 'undefined' ? utils.makeBool(obj.ignoreSaltReading) : utils.makeBool(chlor.ignoreSaltReading);
|
|
3344
|
+
schlor.isActive = chlor.isActive = typeof obj.isActive !== 'undefined' ? utils.makeBool(obj.isActive) : typeof chlor.isActive !== 'undefined' ? utils.makeBool(chlor.isActive) : true;
|
|
3345
|
+
chlor.master = 2;
|
|
3346
|
+
schlor.currentOutput = typeof obj.currentOutput !== 'undefined' ? parseInt(obj.currentOutput, 10) : schlor.currentOutput;
|
|
3347
|
+
schlor.lastComm = typeof obj.lastComm !== 'undefined' ? obj.lastComm : schlor.lastComm || Date.now();
|
|
3348
|
+
schlor.status = typeof obj.status !== 'undefined' ? sys.board.valueMaps.chlorinatorStatus.encode(obj.status) : sys.board.valueMaps.chlorinatorStatus.encode(schlor.status || 0);
|
|
3349
|
+
if (typeof obj.superChlorRemaining !== 'undefined') schlor.superChlorRemaining = parseInt(obj.superChlorRemaining, 10);
|
|
3350
|
+
schlor.targetOutput = typeof obj.targetOutput !== 'undefined' ? parseInt(obj.targetOutput, 10) : schlor.targetOutput;
|
|
3351
|
+
schlor.saltLevel = typeof obj.saltLevel !== 'undefined' ? parseInt(obj.saltLevel, 10) : schlor.saltLevel;
|
|
3352
|
+
}
|
|
2859
3353
|
state.emitEquipmentChanges();
|
|
2860
|
-
return Promise.resolve(
|
|
3354
|
+
return Promise.resolve(state.chlorinators.getItemById(id));
|
|
2861
3355
|
}
|
|
2862
3356
|
catch (err) {
|
|
2863
3357
|
logger.error(`Error setting chlorinator: ${err}`)
|
|
@@ -3043,6 +3537,7 @@ export class ScheduleCommands extends BoardCommands {
|
|
|
3043
3537
|
let schedDays = sys.board.schedules.transformDays(typeof data.scheduleDays !== 'undefined' ? data.scheduleDays : sched.scheduleDays);
|
|
3044
3538
|
let changeHeatSetpoint = typeof (data.changeHeatSetpoint !== 'undefined') ? data.changeHeatSetpoint : false;
|
|
3045
3539
|
let display = typeof data.display !== 'undefined' ? data.display : sched.display || 0;
|
|
3540
|
+
let disabled = typeof data.disabled !== 'undefined' ? utils.makeBool(data.disabled) : sched.disabled;
|
|
3046
3541
|
|
|
3047
3542
|
// Ensure all the defaults.
|
|
3048
3543
|
if (isNaN(startDate.getTime())) startDate = new Date();
|
|
@@ -3079,12 +3574,16 @@ export class ScheduleCommands extends BoardCommands {
|
|
|
3079
3574
|
sched.startYear = startDate.getFullYear();
|
|
3080
3575
|
sched.startMonth = startDate.getMonth() + 1;
|
|
3081
3576
|
sched.startDay = startDate.getDate();
|
|
3082
|
-
|
|
3083
|
-
|
|
3577
|
+
ssched.isActive = sched.isActive = true;
|
|
3578
|
+
ssched.disabled = sched.disabled = disabled;
|
|
3084
3579
|
ssched.display = sched.display = display;
|
|
3085
3580
|
if (typeof sched.startDate === 'undefined')
|
|
3086
3581
|
sched.master = 1;
|
|
3087
3582
|
await ncp.schedules.setScheduleAsync(sched, data);
|
|
3583
|
+
// update end time in case sched is changed while circuit is on
|
|
3584
|
+
let cstate = state.circuits.getInterfaceById(sched.circuit);
|
|
3585
|
+
sys.board.circuits.setEndTime(sys.circuits.getInterfaceById(sched.circuit), cstate, cstate.isOn, true);
|
|
3586
|
+
cstate.emitEquipmentChange();
|
|
3088
3587
|
ssched.emitEquipmentChange();
|
|
3089
3588
|
return sched;
|
|
3090
3589
|
}
|
|
@@ -3113,7 +3612,8 @@ export class ScheduleCommands extends BoardCommands {
|
|
|
3113
3612
|
let schedIsOn: boolean;
|
|
3114
3613
|
let ssched = state.schedules.getItemByIndex(i);
|
|
3115
3614
|
let scirc = state.circuits.getInterfaceById(ssched.circuit);
|
|
3116
|
-
|
|
3615
|
+
let mOP = sys.board.schedules.manualPriorityActive(ssched); //sys.board.schedules.manualPriorityActiveByProxy(scirc.id);
|
|
3616
|
+
if (scirc.isOn && !mOP &&
|
|
3117
3617
|
(ssched.scheduleDays & dayVal) > 0 &&
|
|
3118
3618
|
ts >= ssched.startTime && ts <= ssched.endTime) schedIsOn = true
|
|
3119
3619
|
else schedIsOn = false;
|
|
@@ -3173,484 +3673,684 @@ export class ScheduleCommands extends BoardCommands {
|
|
|
3173
3673
|
}
|
|
3174
3674
|
return nearestStartTime;
|
|
3175
3675
|
}
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
}
|
|
3188
|
-
for (let i = 0; i < ctx.heaters.update.length; i++) {
|
|
3189
|
-
let h = ctx.heaters.update[i];
|
|
3190
|
-
try {
|
|
3191
|
-
await sys.board.heaters.setHeaterAsync(h);
|
|
3192
|
-
res.addModuleSuccess('heater', `Update: ${h.id}-${h.name}`);
|
|
3193
|
-
} catch (err) { res.addModuleError('heater', `Update: ${h.id}-${h.name}: ${err.message}`); }
|
|
3194
|
-
}
|
|
3195
|
-
for (let i = 0; i < ctx.heaters.add.length; i++) {
|
|
3196
|
-
let h = ctx.heaters.add[i];
|
|
3197
|
-
try {
|
|
3198
|
-
// pull a little trick to first add the data then perform the update. This way we won't get a new id or
|
|
3199
|
-
// it won't error out.
|
|
3200
|
-
sys.heaters.getItemById(h, true);
|
|
3201
|
-
await sys.board.heaters.setHeaterAsync(h);
|
|
3202
|
-
res.addModuleSuccess('heater', `Add: ${h.id}-${h.name}`);
|
|
3203
|
-
} catch (err) { res.addModuleError('heater', `Add: ${h.id}-${h.name}: ${err.message}`); }
|
|
3204
|
-
}
|
|
3205
|
-
return true;
|
|
3206
|
-
} catch (err) { logger.error(`Error restoring heaters: ${err.message}`); res.addModuleError('system', `Error restoring heaters: ${err.message}`); return false; }
|
|
3207
|
-
}
|
|
3208
|
-
public async validateRestore(rest: { poolConfig: any, poolState: any }): Promise<{ errors: any, warnings: any, add: any, update: any, remove: any }> {
|
|
3209
|
-
try {
|
|
3210
|
-
let ctx = { errors: [], warnings: [], add: [], update: [], remove: [] };
|
|
3211
|
-
// Look at heaters.
|
|
3212
|
-
let cfg = rest.poolConfig;
|
|
3213
|
-
for (let i = 0; i < cfg.heaters.length; i++) {
|
|
3214
|
-
let r = cfg.heaters[i];
|
|
3215
|
-
let c = sys.heaters.find(elem => r.id === elem.id);
|
|
3216
|
-
if (typeof c === 'undefined') ctx.add.push(r);
|
|
3217
|
-
else if (JSON.stringify(c.get()) !== JSON.stringify(r)) ctx.update.push(r);
|
|
3218
|
-
}
|
|
3219
|
-
for (let i = 0; i < sys.heaters.length; i++) {
|
|
3220
|
-
let c = sys.heaters.getItemByIndex(i);
|
|
3221
|
-
let r = cfg.heaters.find(elem => elem.id == c.id);
|
|
3222
|
-
if (typeof r === 'undefined') ctx.remove.push(c.get(true));
|
|
3223
|
-
}
|
|
3224
|
-
return ctx;
|
|
3225
|
-
} catch (err) { logger.error(`Error validating heaters for restore: ${err.message}`); }
|
|
3676
|
+
public manualPriorityForThisCircuit(circuit: number): boolean {
|
|
3677
|
+
// This fn will test if this circuit/light group has any circuit group circuits that have manual priority active
|
|
3678
|
+
let grp: ICircuitGroup;
|
|
3679
|
+
let cgc: ICircuitGroupCircuit[] = [];
|
|
3680
|
+
if (sys.board.equipmentIds.circuitGroups.isInRange(circuit) || sys.board.equipmentIds.features.isInRange(circuit))
|
|
3681
|
+
grp = sys.circuitGroups.getInterfaceById(circuit);
|
|
3682
|
+
if (state.circuitGroups.getInterfaceById(circuit).manualPriorityActive) return true;
|
|
3683
|
+
if (grp && grp.isActive) cgc = grp.circuits.toArray();
|
|
3684
|
+
for (let i = 0; i < cgc.length; i++) {
|
|
3685
|
+
let c = state.circuits.getInterfaceById(cgc[i].id);
|
|
3686
|
+
if (c.manualPriorityActive) return true;
|
|
3226
3687
|
}
|
|
3688
|
+
return false;
|
|
3689
|
+
}
|
|
3690
|
+
public manualPriorityActive(schedule: ScheduleState): boolean {
|
|
3691
|
+
// This method will look at all other schedules. If any of them have been resumed,
|
|
3692
|
+
// and manualPriority (global setting) is on, and this schedule would otherwise impact
|
|
3693
|
+
// that circuit, then we declared this schedule as being delayed due to manual override
|
|
3694
|
+
// priority (mOP).
|
|
3695
|
+
// We only need to check this if shouldBeOn = true; if that's false, exit.
|
|
3696
|
+
// Rules:
|
|
3697
|
+
// 1. If the circuit id for this schedule is in manual priority, then true
|
|
3698
|
+
// 2. If the other schedule will turn on a body in a shared body, and it will affect
|
|
3699
|
+
// this circuit id, return true
|
|
3700
|
+
// 3. If this is a circuit/light group schedule, check to see if any member circuit/lights have mOP active
|
|
3701
|
+
// 4. If this is a circuit/light/feature, is there another group that has this same id with mOP active
|
|
3227
3702
|
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3703
|
+
if (schedule.isActive === false) return false;
|
|
3704
|
+
if (schedule.disabled) return false;
|
|
3705
|
+
if (!sys.general.options.manualPriority) return false;
|
|
3706
|
+
|
|
3707
|
+
let currGrp: ICircuitGroup;
|
|
3708
|
+
let currSchedGrpCircs = [];
|
|
3709
|
+
if (sys.board.equipmentIds.circuitGroups.isInRange(schedule.circuit) || sys.board.equipmentIds.features.isInRange(schedule.circuit))
|
|
3710
|
+
currGrp = sys.circuitGroups.getInterfaceById(schedule.circuit);
|
|
3711
|
+
if (currGrp && currGrp.isActive) currSchedGrpCircs = currGrp.circuits.toArray();
|
|
3712
|
+
let circuitGrps: ICircuitGroup[] = sys.circuitGroups.toArray();
|
|
3713
|
+
let lightGrps: ICircuitGroup[] = sys.lightGroups.toArray();
|
|
3714
|
+
let currManualPriorityByProxy = sys.board.schedules.manualPriorityForThisCircuit(schedule.circuit);
|
|
3715
|
+
// check this circuit
|
|
3716
|
+
if (state.circuits.getInterfaceById(schedule.circuit).manualPriorityActive) return true;
|
|
3717
|
+
// check this group, if present
|
|
3718
|
+
if (currManualPriorityByProxy) return true;
|
|
3719
|
+
|
|
3720
|
+
let schedules: ScheduleState[] = state.schedules.get(true);
|
|
3721
|
+
for (let i = 0; i < schedules.length; i++) {
|
|
3722
|
+
let sched = schedules[i];
|
|
3723
|
+
// if the id of another circuit is the same as this, we should delay
|
|
3724
|
+
let schedCState = state.circuits.getInterfaceById(sched.circuit);
|
|
3725
|
+
if (schedule.circuit === schedCState.id && schedCState.manualPriorityActive) return true;
|
|
3726
|
+
// if OCP includes a shared body, and this schedule affects the shared body,
|
|
3727
|
+
// and this body is still on, we should delay
|
|
3728
|
+
if (sys.equipment.shared && schedCState.dataName === 'circuit') {
|
|
3729
|
+
let otherBody = sys.bodies.find(elem => elem.circuit === sched.circuit);
|
|
3730
|
+
// let otherBodyIsOn = state.circuits.getInterfaceById(sched.circuit).isOn;
|
|
3731
|
+
let thisBody = sys.bodies.find(elem => elem.circuit === schedule.circuit);
|
|
3732
|
+
if (typeof otherBody !== 'undefined' && typeof thisBody !== 'undefined' && schedCState.manualPriorityActive) return true;
|
|
3733
|
+
}
|
|
3734
|
+
// if other circuit/schedule groups have this circ id, and it's mOP, return true
|
|
3735
|
+
if (schedCState.dataName === 'circuitGroup') {
|
|
3736
|
+
for (let i = 0; i < circuitGrps.length; i++) {
|
|
3737
|
+
let grp: ICircuitGroup = circuitGrps[i];
|
|
3738
|
+
let sgrp: ICircuitGroupState = state.circuitGroups.getInterfaceById(grp.id);
|
|
3739
|
+
let circuits = grp.circuits.toArray();
|
|
3740
|
+
if (grp.isActive) {
|
|
3741
|
+
let manualPriorityByProxy = sys.board.schedules.manualPriorityForThisCircuit(grp.id);
|
|
3742
|
+
for (let j = 0; j < circuits.length; j++) {
|
|
3743
|
+
let cgc = grp.circuits.getItemByIndex(j);
|
|
3744
|
+
let scgc = state.circuits.getInterfaceById(cgc.circuit);
|
|
3745
|
+
// if the circuit id's match and mOP is active, we delay
|
|
3746
|
+
if (scgc.id === schedule.circuit && scgc.manualPriorityActive) return true;
|
|
3747
|
+
// check all the other cgc against this cgc
|
|
3748
|
+
// note: circuit/light groups cannot be part of a group themselves
|
|
3749
|
+
for (let k = 0; k < currSchedGrpCircs.length; k++) {
|
|
3750
|
+
let currCircGrpCirc = state.circuits.getInterfaceById(currSchedGrpCircs[k].circuit);
|
|
3751
|
+
// if either circuit in either group has mOP then delay
|
|
3752
|
+
if (currManualPriorityByProxy || manualPriorityByProxy) {
|
|
3753
|
+
if (currCircGrpCirc.id === schedCState.id) return true;
|
|
3754
|
+
if (currCircGrpCirc.id === scgc.id) return true;
|
|
3259
3755
|
}
|
|
3756
|
+
}
|
|
3260
3757
|
}
|
|
3758
|
+
}
|
|
3261
3759
|
}
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
let
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
let
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3760
|
+
}
|
|
3761
|
+
if (schedCState.dataName === 'lightGroup') {
|
|
3762
|
+
for (let i = 0; i < lightGrps.length; i++) {
|
|
3763
|
+
let grp: ICircuitGroup = lightGrps[i];
|
|
3764
|
+
let sgrp: ICircuitGroupState = state.circuitGroups.getInterfaceById(grp.id);
|
|
3765
|
+
let circuits = grp.circuits.toArray();
|
|
3766
|
+
if (grp.isActive) {
|
|
3767
|
+
let manualPriorityByProxy = sys.board.schedules.manualPriorityForThisCircuit(grp.id);
|
|
3768
|
+
for (let j = 0; j < circuits.length; j++) {
|
|
3769
|
+
let cgc = grp.circuits.getItemByIndex(j);
|
|
3770
|
+
let scgc = state.circuits.getInterfaceById(cgc.circuit);
|
|
3771
|
+
// if the circuit id's match and mOP is active, we delay
|
|
3772
|
+
if (scgc.id === schedule.circuit && scgc.manualPriorityActive) return true;
|
|
3773
|
+
// check all the other cgc against this cgc
|
|
3774
|
+
// note: circuit/light groups cannot be part of a group themselves
|
|
3775
|
+
for (let k = 0; k < currSchedGrpCircs.length; k++) {
|
|
3776
|
+
let currCircGrpCirc = state.circuits.getInterfaceById(currSchedGrpCircs[k].circuit);
|
|
3777
|
+
// if either circuit in either group has mOP then delay
|
|
3778
|
+
if (currManualPriorityByProxy || manualPriorityByProxy) {
|
|
3779
|
+
if (currCircGrpCirc.id === schedCState.id) return true;
|
|
3780
|
+
if (currCircGrpCirc.id === scgc.id) return true;
|
|
3274
3781
|
}
|
|
3782
|
+
}
|
|
3275
3783
|
}
|
|
3784
|
+
}
|
|
3276
3785
|
}
|
|
3786
|
+
}
|
|
3277
3787
|
}
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3788
|
+
// if we make it this far, nothing is impacting us
|
|
3789
|
+
return false;
|
|
3790
|
+
}
|
|
3791
|
+
}
|
|
3792
|
+
export class HeaterCommands extends BoardCommands {
|
|
3793
|
+
public async restore(rest: { poolConfig: any, poolState: any }, ctx: any, res: RestoreResults): Promise<boolean> {
|
|
3794
|
+
try {
|
|
3795
|
+
// First delete the heaters that should be removed.
|
|
3796
|
+
for (let i = 0; i < ctx.heaters.remove.length; i++) {
|
|
3797
|
+
let h = ctx.heaters.remove[i];
|
|
3285
3798
|
try {
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
let vheaters = sys.heaters.filter(h => h.master === 1);
|
|
3293
|
-
id = vheaters.length + 256;
|
|
3294
|
-
}
|
|
3295
|
-
heater = sys.heaters.getItemById(id, true);
|
|
3296
|
-
if (typeof obj !== undefined) {
|
|
3297
|
-
for (var s in obj) {
|
|
3298
|
-
if (s === 'id') continue;
|
|
3299
|
-
heater[s] = obj[s];
|
|
3300
|
-
}
|
|
3301
|
-
}
|
|
3302
|
-
let hstate = state.heaters.getItemById(id, true);
|
|
3303
|
-
//hstate.isVirtual = heater.isVirtual = true;
|
|
3304
|
-
hstate.name = heater.name;
|
|
3305
|
-
hstate.type = heater.type;
|
|
3306
|
-
heater.master = 1;
|
|
3307
|
-
if (heater.master === 1) await ncp.heaters.setHeaterAsync(heater, obj);
|
|
3308
|
-
await sys.board.heaters.updateHeaterServices();
|
|
3309
|
-
await sys.board.heaters.syncHeaterStates();
|
|
3310
|
-
return heater;
|
|
3311
|
-
} catch (err) { return Promise.reject(new Error(`Error setting heater configuration: ${err}`)); }
|
|
3312
|
-
}
|
|
3313
|
-
public async deleteHeaterAsync(obj: any): Promise<Heater> {
|
|
3799
|
+
await sys.board.heaters.deleteHeaterAsync(h);
|
|
3800
|
+
res.addModuleSuccess('heater', `Remove: ${h.id}-${h.name}`);
|
|
3801
|
+
} catch (err) { res.addModuleError('heater', `Remove: ${h.id}-${h.name}: ${err.message}`); }
|
|
3802
|
+
}
|
|
3803
|
+
for (let i = 0; i < ctx.heaters.update.length; i++) {
|
|
3804
|
+
let h = ctx.heaters.update[i];
|
|
3314
3805
|
try {
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3806
|
+
await sys.board.heaters.setHeaterAsync(h);
|
|
3807
|
+
res.addModuleSuccess('heater', `Update: ${h.id}-${h.name}`);
|
|
3808
|
+
} catch (err) { res.addModuleError('heater', `Update: ${h.id}-${h.name}: ${err.message}`); }
|
|
3809
|
+
}
|
|
3810
|
+
for (let i = 0; i < ctx.heaters.add.length; i++) {
|
|
3811
|
+
let h = ctx.heaters.add[i];
|
|
3812
|
+
try {
|
|
3813
|
+
// pull a little trick to first add the data then perform the update. This way we won't get a new id or
|
|
3814
|
+
// it won't error out.
|
|
3815
|
+
sys.heaters.getItemById(h.id, true);
|
|
3816
|
+
await sys.board.heaters.setHeaterAsync(h);
|
|
3817
|
+
res.addModuleSuccess('heater', `Add: ${h.id}-${h.name}`);
|
|
3818
|
+
} catch (err) { res.addModuleError('heater', `Add: ${h.id}-${h.name}: ${err.message}`); }
|
|
3819
|
+
}
|
|
3820
|
+
return true;
|
|
3821
|
+
} catch (err) { logger.error(`Error restoring heaters: ${err.message}`); res.addModuleError('system', `Error restoring heaters: ${err.message}`); return false; }
|
|
3822
|
+
}
|
|
3823
|
+
public async validateRestore(rest: { poolConfig: any, poolState: any }): Promise<{ errors: any, warnings: any, add: any, update: any, remove: any }> {
|
|
3824
|
+
try {
|
|
3825
|
+
let ctx = { errors: [], warnings: [], add: [], update: [], remove: [] };
|
|
3826
|
+
// Look at heaters.
|
|
3827
|
+
let cfg = rest.poolConfig;
|
|
3828
|
+
for (let i = 0; i < cfg.heaters.length; i++) {
|
|
3829
|
+
let r = cfg.heaters[i];
|
|
3830
|
+
let c = sys.heaters.find(elem => r.id === elem.id);
|
|
3831
|
+
if (typeof c === 'undefined') ctx.add.push(r);
|
|
3832
|
+
else if (JSON.stringify(c.get()) !== JSON.stringify(r)) ctx.update.push(r);
|
|
3833
|
+
}
|
|
3834
|
+
for (let i = 0; i < sys.heaters.length; i++) {
|
|
3835
|
+
let c = sys.heaters.getItemByIndex(i);
|
|
3836
|
+
let r = cfg.heaters.find(elem => elem.id == c.id);
|
|
3837
|
+
if (typeof r === 'undefined') ctx.remove.push(c.get(true));
|
|
3838
|
+
}
|
|
3839
|
+
return ctx;
|
|
3840
|
+
} catch (err) { logger.error(`Error validating heaters for restore: ${err.message}`); }
|
|
3841
|
+
}
|
|
3842
|
+
public getHeatersByCircuitId(circuitId: number): Heater[] {
|
|
3843
|
+
let heaters: Heater[] = [];
|
|
3844
|
+
let bodyId = circuitId === 6 ? 1 : circuitId === 1 ? 2 : 0;
|
|
3845
|
+
if (bodyId > 0) {
|
|
3846
|
+
for (let i = 0; i < sys.heaters.length; i++) {
|
|
3847
|
+
let heater = sys.heaters.getItemByIndex(i);
|
|
3848
|
+
if (!heater.isActive) continue;
|
|
3849
|
+
if (bodyId === heater.body || sys.equipment.shared && heater.body === 32) heaters.push(heater);
|
|
3850
|
+
}
|
|
3326
3851
|
}
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
if (
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
if (
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
else if (heatPumpInstalled) sys.board.valueMaps.heatModes.set(9, { name: 'heatpump', desc: 'Heat Pump' });
|
|
3347
|
-
// Now set the body data.
|
|
3348
|
-
for (let i = 0; i < sys.bodies.length; i++) {
|
|
3349
|
-
let body = sys.bodies.getItemByIndex(i);
|
|
3350
|
-
let btemp = state.temps.bodies.getItemById(body.id, body.isActive !== false);
|
|
3351
|
-
let opts = sys.board.heaters.getInstalledHeaterTypes(body.id);
|
|
3352
|
-
btemp.heaterOptions = opts;
|
|
3353
|
-
}
|
|
3354
|
-
this.setActiveTempSensors();
|
|
3852
|
+
return heaters;
|
|
3853
|
+
}
|
|
3854
|
+
public getInstalledHeaterTypes(body?: number): any {
|
|
3855
|
+
let heaters = sys.heaters.get();
|
|
3856
|
+
let types = sys.board.valueMaps.heaterTypes.toArray();
|
|
3857
|
+
let inst = { total: 0 };
|
|
3858
|
+
for (let i = 0; i < types.length; i++) if (types[i].name !== 'none') inst[types[i].name] = 0;
|
|
3859
|
+
for (let i = 0; i < heaters.length; i++) {
|
|
3860
|
+
let heater = heaters[i];
|
|
3861
|
+
if (typeof body !== 'undefined' && heater.body !== 'undefined') {
|
|
3862
|
+
if ((heater.body !== 32 && body !== heater.body + 1) || (heater.body === 32 && body > 2)) continue;
|
|
3863
|
+
}
|
|
3864
|
+
let type = types.find(elem => elem.val === heater.type);
|
|
3865
|
+
if (typeof type !== 'undefined') {
|
|
3866
|
+
if (inst[type.name] === 'undefined') inst[type.name] = 0;
|
|
3867
|
+
inst[type.name] = inst[type.name] + 1;
|
|
3868
|
+
if (heater.coolingEnabled === true && type.hasCoolSetpoint === true) inst['hasCoolSetpoint'] = true;
|
|
3869
|
+
inst.total++;
|
|
3870
|
+
}
|
|
3355
3871
|
}
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
}
|
|
3370
|
-
if (maxPairs > 2) {
|
|
3371
|
-
sys.equipment.tempSensors.getItemById('water3', true, { id: 'water3', isActive: false, calibration: 0 }).name = 'Body 3';
|
|
3372
|
-
sys.equipment.tempSensors.getItemById('solar3', true, { id: 'solar3', isActive: false, calibration: 0 }).name = 'Solar 3';
|
|
3373
|
-
}
|
|
3374
|
-
else {
|
|
3375
|
-
sys.equipment.tempSensors.removeItemById('water3');
|
|
3376
|
-
sys.equipment.tempSensors.removeItemById('solar3');
|
|
3872
|
+
return inst;
|
|
3873
|
+
}
|
|
3874
|
+
public isSolarInstalled(body?: number): boolean {
|
|
3875
|
+
let heaters = sys.heaters.get();
|
|
3876
|
+
let types = sys.board.valueMaps.heaterTypes.toArray();
|
|
3877
|
+
for (let i = 0; i < heaters.length; i++) {
|
|
3878
|
+
let heater = heaters[i];
|
|
3879
|
+
if (typeof body !== 'undefined' && body !== heater.body) continue;
|
|
3880
|
+
let type = types.find(elem => elem.val === heater.type);
|
|
3881
|
+
if (typeof type !== 'undefined') {
|
|
3882
|
+
switch (type.name) {
|
|
3883
|
+
case 'solar':
|
|
3884
|
+
return true;
|
|
3377
3885
|
}
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3886
|
+
}
|
|
3887
|
+
}
|
|
3888
|
+
}
|
|
3889
|
+
public isHeatPumpInstalled(body?: number): boolean {
|
|
3890
|
+
let heaters = sys.heaters.get();
|
|
3891
|
+
let types = sys.board.valueMaps.heaterTypes.toArray();
|
|
3892
|
+
for (let i = 0; i < heaters.length; i++) {
|
|
3893
|
+
let heater = heaters[i];
|
|
3894
|
+
if (typeof body !== 'undefined' && body !== heater.body) continue;
|
|
3895
|
+
let type = types.find(elem => elem.val === heater.type);
|
|
3896
|
+
if (typeof type !== 'undefined') {
|
|
3897
|
+
switch (type.name) {
|
|
3898
|
+
case 'heatpump':
|
|
3899
|
+
return true;
|
|
3381
3900
|
}
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3901
|
+
}
|
|
3902
|
+
}
|
|
3903
|
+
}
|
|
3904
|
+
public setHeater(heater: Heater, obj?: any) {
|
|
3905
|
+
if (typeof obj !== undefined) {
|
|
3906
|
+
for (var s in obj)
|
|
3907
|
+
heater[s] = obj[s];
|
|
3908
|
+
}
|
|
3909
|
+
}
|
|
3910
|
+
public async setHeaterAsync(obj: any): Promise<Heater> {
|
|
3911
|
+
try {
|
|
3912
|
+
let id = typeof obj.id === 'undefined' ? -1 : parseInt(obj.id, 10);
|
|
3913
|
+
if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError('Heater Id is not valid.', obj.id, 'Heater'));
|
|
3914
|
+
else if (id < 256 && id > 0) return Promise.reject(new InvalidEquipmentIdError('Virtual Heaters controlled by njspc must have an Id > 256.', obj.id, 'Heater'));
|
|
3915
|
+
let heater: Heater;
|
|
3916
|
+
if (id <= 0) {
|
|
3917
|
+
// We are adding a heater. In this case all heaters are virtual.
|
|
3918
|
+
let vheaters = sys.heaters.filter(h => h.master === 1);
|
|
3919
|
+
id = vheaters.length + 256;
|
|
3920
|
+
}
|
|
3921
|
+
heater = sys.heaters.getItemById(id, true);
|
|
3922
|
+
if (typeof obj !== undefined) {
|
|
3923
|
+
for (var s in obj) {
|
|
3924
|
+
if (s === 'id') continue;
|
|
3925
|
+
heater[s] = obj[s];
|
|
3385
3926
|
}
|
|
3927
|
+
}
|
|
3928
|
+
let hstate = state.heaters.getItemById(id, true);
|
|
3929
|
+
//hstate.isVirtual = heater.isVirtual = true;
|
|
3930
|
+
hstate.name = heater.name;
|
|
3931
|
+
hstate.type = heater.type;
|
|
3932
|
+
heater.master = 1;
|
|
3933
|
+
if (heater.master === 1) await ncp.heaters.setHeaterAsync(heater, obj);
|
|
3934
|
+
await sys.board.heaters.updateHeaterServices();
|
|
3935
|
+
await sys.board.heaters.syncHeaterStates();
|
|
3936
|
+
return heater;
|
|
3937
|
+
} catch (err) { return Promise.reject(new Error(`Error setting heater configuration: ${err}`)); }
|
|
3938
|
+
}
|
|
3939
|
+
public async deleteHeaterAsync(obj: any): Promise<Heater> {
|
|
3940
|
+
try {
|
|
3941
|
+
let id = parseInt(obj.id, 10);
|
|
3942
|
+
if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError('Cannot delete. Heater Id is not valid.', obj.id, 'Heater'));
|
|
3943
|
+
let heater = sys.heaters.getItemById(id);
|
|
3944
|
+
heater.isActive = false;
|
|
3945
|
+
if (heater.master === 1) await ncp.heaters.deleteHeaterAsync(heater.id);
|
|
3946
|
+
sys.heaters.removeItemById(id);
|
|
3947
|
+
state.heaters.removeItemById(id);
|
|
3948
|
+
sys.board.heaters.updateHeaterServices();
|
|
3949
|
+
sys.board.heaters.syncHeaterStates();
|
|
3950
|
+
return heater;
|
|
3951
|
+
} catch (err) { return Promise.reject(`Error deleting heater: ${err.message}`) }
|
|
3952
|
+
}
|
|
3953
|
+
public updateHeaterServices() {
|
|
3954
|
+
let htypes = sys.board.heaters.getInstalledHeaterTypes();
|
|
3955
|
+
let solarInstalled = htypes.solar > 0;
|
|
3956
|
+
let heatPumpInstalled = htypes.heatpump > 0;
|
|
3957
|
+
let gasHeaterInstalled = htypes.gas > 0;
|
|
3958
|
+
|
|
3959
|
+
if (sys.heaters.length > 0) sys.board.valueMaps.heatSources = new byteValueMap([[0, { name: 'off', desc: 'Off' }]]);
|
|
3960
|
+
if (gasHeaterInstalled) sys.board.valueMaps.heatSources.set(3, { name: 'heater', desc: 'Heater' });
|
|
3961
|
+
if (solarInstalled && (gasHeaterInstalled || heatPumpInstalled)) sys.board.valueMaps.heatSources.merge([[5, { name: 'solar', desc: 'Solar Only' }], [21, { name: 'solarpref', desc: 'Solar Preferred' }]]);
|
|
3962
|
+
else if (solarInstalled) sys.board.valueMaps.heatSources.set(5, { name: 'solar', desc: 'Solar' });
|
|
3963
|
+
if (heatPumpInstalled && (gasHeaterInstalled || solarInstalled)) sys.board.valueMaps.heatSources.merge([[9, { name: 'heatpump', desc: 'Heatpump Only' }], [25, { name: 'heatpumppref', desc: 'Heat Pump Preferred' }]]);
|
|
3964
|
+
else if (heatPumpInstalled) sys.board.valueMaps.heatSources.set(9, { name: 'heatpump', desc: 'Heat Pump' });
|
|
3965
|
+
sys.board.valueMaps.heatSources.set(32, { name: 'nochange', desc: 'No Change' });
|
|
3386
3966
|
|
|
3967
|
+
sys.board.valueMaps.heatModes = new byteValueMap([[0, { name: 'off', desc: 'Off' }]]);
|
|
3968
|
+
if (gasHeaterInstalled) sys.board.valueMaps.heatModes.set(3, { name: 'heater', desc: 'Heater' });
|
|
3969
|
+
if (solarInstalled && (gasHeaterInstalled || heatPumpInstalled)) sys.board.valueMaps.heatModes.merge([[5, { name: 'solar', desc: 'Solar Only' }], [21, { name: 'solarpref', desc: 'Solar Preferred' }]]);
|
|
3970
|
+
else if (solarInstalled) sys.board.valueMaps.heatModes.set(5, { name: 'solar', desc: 'Solar' });
|
|
3971
|
+
if (heatPumpInstalled && (gasHeaterInstalled || solarInstalled)) sys.board.valueMaps.heatModes.merge([[9, { name: 'heatpump', desc: 'Heatpump Only' }], [25, { name: 'heatpumppref', desc: 'Heat Pump Preferred' }]]);
|
|
3972
|
+
else if (heatPumpInstalled) sys.board.valueMaps.heatModes.set(9, { name: 'heatpump', desc: 'Heat Pump' });
|
|
3973
|
+
// Now set the body data.
|
|
3974
|
+
for (let i = 0; i < sys.bodies.length; i++) {
|
|
3975
|
+
let body = sys.bodies.getItemByIndex(i);
|
|
3976
|
+
let btemp = state.temps.bodies.getItemById(body.id, body.isActive !== false);
|
|
3977
|
+
let opts = sys.board.heaters.getInstalledHeaterTypes(body.id);
|
|
3978
|
+
btemp.heaterOptions = opts;
|
|
3387
3979
|
}
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3980
|
+
this.setActiveTempSensors();
|
|
3981
|
+
}
|
|
3982
|
+
public initTempSensors() {
|
|
3983
|
+
// Add in the potential sensors and delete the ones that shouldn't exist.
|
|
3984
|
+
let maxPairs = sys.equipment.maxBodies + (sys.equipment.shared ? -1 : 0);
|
|
3985
|
+
sys.equipment.tempSensors.getItemById('air', true, { id: 'air', isActive: true, calibration: 0 }).name = 'Air';
|
|
3986
|
+
sys.equipment.tempSensors.getItemById('water1', true, { id: 'water1', isActive: true, calibration: 0 }).name = maxPairs == 1 ? 'Water' : 'Body 1';
|
|
3987
|
+
sys.equipment.tempSensors.getItemById('solar1', true, { id: 'solar1', isActive: false, calibration: 0 }).name = maxPairs == 1 ? 'Solar' : 'Solar 1';
|
|
3988
|
+
if (maxPairs > 1) {
|
|
3989
|
+
sys.equipment.tempSensors.getItemById('water2', true, { id: 'water2', isActive: false, calibration: 0 }).name = 'Body 2';
|
|
3990
|
+
sys.equipment.tempSensors.getItemById('solar2', true, { id: 'solar2', isActive: false, calibration: 0 }).name = 'Solar 2';
|
|
3991
|
+
}
|
|
3992
|
+
else {
|
|
3993
|
+
sys.equipment.tempSensors.removeItemById('water2');
|
|
3994
|
+
sys.equipment.tempSensors.removeItemById('solar2');
|
|
3995
|
+
}
|
|
3996
|
+
if (maxPairs > 2) {
|
|
3997
|
+
sys.equipment.tempSensors.getItemById('water3', true, { id: 'water3', isActive: false, calibration: 0 }).name = 'Body 3';
|
|
3998
|
+
sys.equipment.tempSensors.getItemById('solar3', true, { id: 'solar3', isActive: false, calibration: 0 }).name = 'Solar 3';
|
|
3999
|
+
}
|
|
4000
|
+
else {
|
|
4001
|
+
sys.equipment.tempSensors.removeItemById('water3');
|
|
4002
|
+
sys.equipment.tempSensors.removeItemById('solar3');
|
|
4003
|
+
}
|
|
4004
|
+
if (maxPairs > 3) {
|
|
4005
|
+
sys.equipment.tempSensors.getItemById('water4', true, { id: 'water4', isActive: false, calibration: 0 }).name = 'Body 4';
|
|
4006
|
+
sys.equipment.tempSensors.getItemById('solar4', true, { id: 'solar4', isActive: false, calibration: 0 }).name = 'Solar 4';
|
|
4007
|
+
}
|
|
4008
|
+
else {
|
|
4009
|
+
sys.equipment.tempSensors.removeItemById('water4');
|
|
4010
|
+
sys.equipment.tempSensors.removeItemById('solar4');
|
|
4011
|
+
}
|
|
4012
|
+
|
|
4013
|
+
}
|
|
4014
|
+
// Sets the active temp sensors based upon the installed equipment. At this point all
|
|
4015
|
+
// detectable temp sensors should exist.
|
|
4016
|
+
public setActiveTempSensors() {
|
|
4017
|
+
let htypes;
|
|
4018
|
+
// We are iterating backwards through the sensors array on purpose. We do this just in case we need
|
|
4019
|
+
// to remove a sensor during the iteration. This way the index values will not be impacted and we can
|
|
4020
|
+
// safely remove from the array we are iterating.
|
|
4021
|
+
for (let i = sys.equipment.tempSensors.length - 1; i >= 0; i--) {
|
|
4022
|
+
let sensor = sys.equipment.tempSensors.getItemByIndex(i);
|
|
4023
|
+
// The names are normalized in this array.
|
|
4024
|
+
switch (sensor.id) {
|
|
4025
|
+
case 'air':
|
|
4026
|
+
sensor.isActive = true;
|
|
4027
|
+
break;
|
|
4028
|
+
case 'water1':
|
|
4029
|
+
sensor.isActive = sys.equipment.maxBodies > 0;
|
|
4030
|
+
break;
|
|
4031
|
+
case 'water2':
|
|
4032
|
+
sensor.isActive = sys.equipment.shared ? sys.equipment.maxBodies > 2 : sys.equipment.maxBodies > 1;
|
|
4033
|
+
break;
|
|
4034
|
+
case 'water3':
|
|
4035
|
+
sensor.isActive = sys.equipment.shared ? sys.equipment.maxBodies > 3 : sys.equipment.maxBodies > 2;
|
|
4036
|
+
break;
|
|
4037
|
+
case 'water4':
|
|
4038
|
+
// It's a little weird but technically you should be able to install 3 expansions and a i10D personality
|
|
4039
|
+
// board. If this situation ever comes up we will see if it works. Whether it reports is another story
|
|
4040
|
+
// since the 2 message is short a byte for this.
|
|
4041
|
+
sensor.isActive = sys.equipment.shared ? sys.equipment.maxBodies > 4 : sys.equipment.maxBodies > 3;
|
|
4042
|
+
break;
|
|
4043
|
+
// Solar sensors are funny ducks. This is because they are for both heatpumps and solar and the equipment
|
|
4044
|
+
// can be installed on specific bodies. This will be true for heaters installed in expansion panels for *Touch, dual body systems,
|
|
4045
|
+
// and any IntelliCenter with more than one body. At some point simply implementing the multi-body functions for touch will make
|
|
4046
|
+
// this all work. This will only be with i10D or expansion panels.
|
|
4047
|
+
case 'solar1':
|
|
4048
|
+
// The first solar sensor is a funny duck in that it should be active for shared systems
|
|
4049
|
+
// if either body has an active solar heater or heatpump.
|
|
4050
|
+
htypes = sys.board.heaters.getInstalledHeaterTypes(1);
|
|
4051
|
+
if ('solar' in htypes || 'heatpump' in htypes) sensor.isActive = true;
|
|
4052
|
+
else if (sys.equipment.shared) {
|
|
4053
|
+
htypes = sys.board.heaters.getInstalledHeaterTypes(2);
|
|
4054
|
+
sensor.isActive = ('solar' in htypes || 'heatpump' in htypes);
|
|
4055
|
+
}
|
|
4056
|
+
else sensor.isActive = false;
|
|
4057
|
+
break;
|
|
4058
|
+
case 'solar2':
|
|
4059
|
+
if (sys.equipment.maxBodies > 1 + (sys.equipment.shared ? 1 : 0)) {
|
|
4060
|
+
htypes = sys.board.heaters.getInstalledHeaterTypes(2 + (sys.equipment.shared ? 1 : 0));
|
|
4061
|
+
sensor.isActive = ('solar' in htypes || 'heatpump' in htypes);
|
|
4062
|
+
}
|
|
4063
|
+
else sensor.isActive = false;
|
|
4064
|
+
break;
|
|
4065
|
+
case 'solar3':
|
|
4066
|
+
if (sys.equipment.maxBodies > 2 + (sys.equipment.shared ? 1 : 0)) {
|
|
4067
|
+
htypes = sys.board.heaters.getInstalledHeaterTypes(3 + (sys.equipment.shared ? 1 : 0));
|
|
4068
|
+
sensor.isActive = ('solar' in htypes || 'heatpump' in htypes);
|
|
4069
|
+
}
|
|
4070
|
+
else sensor.isActive = false;
|
|
4071
|
+
break;
|
|
4072
|
+
case 'solar4':
|
|
4073
|
+
if (sys.equipment.maxBodies > 3 + (sys.equipment.shared ? 1 : 0)) {
|
|
4074
|
+
htypes = sys.board.heaters.getInstalledHeaterTypes(4 + (sys.equipment.shared ? 1 : 0));
|
|
4075
|
+
sensor.isActive = ('solar' in htypes || 'heatpump' in htypes);
|
|
4076
|
+
}
|
|
4077
|
+
else sensor.isActive = false;
|
|
4078
|
+
break;
|
|
4079
|
+
default:
|
|
4080
|
+
if (typeof sensor.id === 'undefined') sys.equipment.tempSensors.removeItemByIndex(i);
|
|
4081
|
+
break;
|
|
4082
|
+
}
|
|
4083
|
+
}
|
|
4084
|
+
}
|
|
4085
|
+
// This updates the heater states based upon the installed heaters. This is true for heaters that are tied to the OCP
|
|
4086
|
+
// and those that are not.
|
|
4087
|
+
public syncHeaterStates() {
|
|
4088
|
+
try {
|
|
4089
|
+
// Go through the installed heaters and bodies to determine whether they should be on. If there is a
|
|
4090
|
+
// heater that is not controlled by the OCP then we need to determine whether it should be on.
|
|
4091
|
+
let heaters = sys.heaters.toArray();
|
|
4092
|
+
let bodies = state.temps.bodies.toArray();
|
|
4093
|
+
let hon = [];
|
|
4094
|
+
for (let i = 0; i < bodies.length; i++) {
|
|
4095
|
+
let body: BodyTempState = bodies[i];
|
|
4096
|
+
let cfgBody: Body = sys.bodies.getItemById(body.id);
|
|
4097
|
+
let isHeating = false;
|
|
4098
|
+
let isCooling = false;
|
|
4099
|
+
let hstatus = sys.board.valueMaps.heatStatus.getName(body.heatStatus);
|
|
4100
|
+
let mode = sys.board.valueMaps.heatModes.getName(body.heatMode);
|
|
4101
|
+
if (body.isOn) {
|
|
4102
|
+
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.`);
|
|
4103
|
+
// Now get all the heaters associated with the body in an array.
|
|
4104
|
+
let bodyHeaters: Heater[] = [];
|
|
4105
|
+
for (let j = 0; j < heaters.length; j++) {
|
|
4106
|
+
let heater: Heater = heaters[j];
|
|
4107
|
+
if (heater.isActive === false) continue;
|
|
4108
|
+
if (heater.body === body.id) bodyHeaters.push(heater);
|
|
4109
|
+
else {
|
|
4110
|
+
let b = sys.board.valueMaps.bodies.transform(heater.body);
|
|
4111
|
+
switch (b.name) {
|
|
4112
|
+
case 'body1':
|
|
4113
|
+
case 'pool':
|
|
4114
|
+
if (body.id === 1) bodyHeaters.push(heater);
|
|
4115
|
+
break;
|
|
4116
|
+
case 'body2':
|
|
4117
|
+
case 'spa':
|
|
4118
|
+
if (body.id === 2) bodyHeaters.push(heater);
|
|
4119
|
+
break;
|
|
4120
|
+
case 'poolspa':
|
|
4121
|
+
if (body.id === 1 || body.id === 2) bodyHeaters.push(heater);
|
|
4122
|
+
break;
|
|
4123
|
+
case 'body3':
|
|
4124
|
+
if (body.id === 3) bodyHeaters.push(heater);
|
|
4125
|
+
break;
|
|
4126
|
+
case 'body4':
|
|
4127
|
+
if (body.id === 4) bodyHeaters.push(heater);
|
|
4128
|
+
break;
|
|
4129
|
+
}
|
|
4130
|
+
}
|
|
4131
|
+
}
|
|
4132
|
+
// Alright we have all the body heaters so sort them in a way that will make our heater preferences work. Solar, heatpumps, and ultratemp should be in the list first
|
|
4133
|
+
// so that if we have a heater preference set up then we do not have to evaluate the other heater.
|
|
4134
|
+
let heaterTypes = sys.board.valueMaps.heaterTypes;
|
|
4135
|
+
bodyHeaters.sort((a, b) => {
|
|
4136
|
+
if (heaterTypes.transform(a.type).hasPreference) return -1;
|
|
4137
|
+
else if (heaterTypes.transform(b.type).hasPreference) return 1;
|
|
4138
|
+
return 0;
|
|
4139
|
+
});
|
|
4140
|
+
|
|
4141
|
+
// Alright so now we should have a sorted array that has preference type heaters first.
|
|
4142
|
+
for (let j = 0; j < bodyHeaters.length; j++) {
|
|
4143
|
+
let heater: Heater = bodyHeaters[j];
|
|
4144
|
+
let isOn = false;
|
|
4145
|
+
let htype = sys.board.valueMaps.heaterTypes.transform(heater.type);
|
|
4146
|
+
let hstate = state.heaters.getItemById(heater.id, true);
|
|
4147
|
+
if (heater.master === 1) {
|
|
4148
|
+
if (hstatus !== 'cooldown') {
|
|
4149
|
+
// We need to do our own calculation as to whether it is on. This is for Nixie heaters.
|
|
4150
|
+
switch (htype.name) {
|
|
4151
|
+
case 'solar':
|
|
4152
|
+
if (mode === 'solar' || mode === 'solarpref') {
|
|
4153
|
+
// Measure up against start and stop temp deltas for effective solar heating.
|
|
4154
|
+
if (body.temp < cfgBody.heatSetpoint &&
|
|
4155
|
+
state.temps.solar > body.temp + (hstate.isOn ? heater.stopTempDelta : heater.startTempDelta)) {
|
|
4156
|
+
isOn = true;
|
|
4157
|
+
body.heatStatus = sys.board.valueMaps.heatStatus.getValue('solar');
|
|
4158
|
+
isHeating = true;
|
|
4159
|
+
}
|
|
4160
|
+
else if (heater.coolingEnabled && body.temp > cfgBody.coolSetpoint && state.heliotrope.isNight &&
|
|
4161
|
+
state.temps.solar > body.temp + (hstate.isOn ? heater.stopTempDelta : heater.startTempDelta)) {
|
|
4162
|
+
isOn = true;
|
|
4163
|
+
body.heatStatus = sys.board.valueMaps.heatStatus.getValue('cooling');
|
|
4164
|
+
isHeating = true;
|
|
4165
|
+
isCooling = true;
|
|
4166
|
+
}
|
|
3429
4167
|
}
|
|
3430
|
-
else sensor.isActive = false;
|
|
3431
4168
|
break;
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
4169
|
+
case 'ultratemp':
|
|
4170
|
+
// There is a temperature differential setting on UltraTemp. This is how
|
|
4171
|
+
// much the water temperature needs to drop below the set temperature, for the heater
|
|
4172
|
+
// to start up again. For instance, if the set temperature and the water temperature is 82 and then the
|
|
4173
|
+
// heater will shut off and not turn on again until the water temperature = setpoint - differentialTemperature.
|
|
4174
|
+
// This is the default operation on IntelliCenter and it appears to simply not start on the setpoint. We can do better
|
|
4175
|
+
// than this by heating 1 degree past the setpoint then applying this rule for 30 minutes. This allows for a more
|
|
4176
|
+
// responsive heater.
|
|
4177
|
+
//
|
|
4178
|
+
// For Ultratemp we need to determine whether the differential temp
|
|
4179
|
+
// is within range. The other thing that needs to be calculated here is
|
|
4180
|
+
// whether Ultratemp can effeciently heat the pool.
|
|
4181
|
+
if (mode === 'ultratemp' || mode === 'ultratemppref') {
|
|
4182
|
+
if (hstate.isOn) {
|
|
4183
|
+
// For the preference mode we will try to reach the setpoint for a period of time then
|
|
4184
|
+
// switch over to the gas heater. Our algorithm for this is to check the rate of
|
|
4185
|
+
// change when the heater first kicks on. If we go for longer than an hour and still
|
|
4186
|
+
// haven't reached the setpoint then we will switch to gas.
|
|
4187
|
+
if (mode === 'ultratemppref' &&
|
|
4188
|
+
typeof hstate.startTime !== 'undefined' &&
|
|
4189
|
+
hstate.startTime.getTime() < new Date().getTime() - (60 * 60 * 1000))
|
|
4190
|
+
break;
|
|
4191
|
+
// If the heater is already on we will heat to 1 degree past the setpoint.
|
|
4192
|
+
if (body.temp - 1 < cfgBody.heatSetpoint) {
|
|
4193
|
+
isOn = true;
|
|
4194
|
+
body.heatStatus = sys.board.valueMaps.heatStatus.getValue('hpheat');
|
|
4195
|
+
isHeating = true;
|
|
4196
|
+
isCooling = false;
|
|
4197
|
+
}
|
|
4198
|
+
else if (body.temp + 1 > cfgBody.coolSetpoint && heater.coolingEnabled) {
|
|
4199
|
+
isOn = true;
|
|
4200
|
+
body.heatStatus = sys.board.valueMaps.heatStatus.getValue('hpcool');
|
|
4201
|
+
isHeating = false;
|
|
4202
|
+
isCooling = true;
|
|
4203
|
+
}
|
|
4204
|
+
}
|
|
4205
|
+
else {
|
|
4206
|
+
let delayStart = typeof hstate.endTime !== 'undefined' ? (hstate.endTime.getTime() + (30 * 60 * 1000)) > new Date().getTime() : false;
|
|
4207
|
+
// The heater is not currently on lets turn it on if we pass all the criteria.
|
|
4208
|
+
if ((body.temp < cfgBody.heatSetpoint && !delayStart)
|
|
4209
|
+
|| body.temp + heater.differentialTemp < cfgBody.heatSetpoint) {
|
|
4210
|
+
isOn = true;
|
|
4211
|
+
body.heatStatus = sys.board.valueMaps.heatStatus.getValue('hpheat');
|
|
4212
|
+
isHeating = true;
|
|
4213
|
+
isCooling = false;
|
|
4214
|
+
}
|
|
4215
|
+
else if (body.temp > cfgBody.coolSetpoint && heater.coolingEnabled) {
|
|
4216
|
+
if (!delayStart || body.temp - heater.differentialTemp > cfgBody.coolSetpoint) {
|
|
4217
|
+
isOn = true;
|
|
4218
|
+
body.heatStatus = sys.board.valueMaps.heatStatus.getValue('hpcool');
|
|
4219
|
+
isHeating = false;
|
|
4220
|
+
isCooling = true;
|
|
4221
|
+
}
|
|
4222
|
+
}
|
|
4223
|
+
}
|
|
3436
4224
|
}
|
|
3437
|
-
else sensor.isActive = false;
|
|
3438
4225
|
break;
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
4226
|
+
case 'mastertemp':
|
|
4227
|
+
// If we make it here, the other heater is not heating the body.
|
|
4228
|
+
if (mode === 'mtheater' || mode === 'heatpumppref' || mode === 'ultratemppref' || mode === 'solarpref') {
|
|
4229
|
+
if (body.temp < cfgBody.setPoint) {
|
|
4230
|
+
isOn = true;
|
|
4231
|
+
body.heatStatus = sys.board.valueMaps.heatStatus.getValue('mtheat');
|
|
4232
|
+
isHeating = true;
|
|
4233
|
+
}
|
|
3443
4234
|
}
|
|
3444
|
-
else sensor.isActive = false;
|
|
3445
4235
|
break;
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
4236
|
+
case 'maxetherm':
|
|
4237
|
+
case 'gas':
|
|
4238
|
+
// If we make it here, the other heater is not heating the body.
|
|
4239
|
+
if (mode === 'heater' || mode === 'solarpref' || mode === 'heatpumppref' || mode === 'ultratemppref') {
|
|
4240
|
+
// Heat past the setpoint for the heater but only if the heater is currently on.
|
|
4241
|
+
if ((body.temp - (hstate.isOn ? heater.stopTempDelta : 0)) < cfgBody.setPoint) {
|
|
4242
|
+
isOn = true;
|
|
4243
|
+
body.heatStatus = sys.board.valueMaps.heatStatus.getValue('heater');
|
|
4244
|
+
isHeating = true;
|
|
4245
|
+
}
|
|
3450
4246
|
}
|
|
3451
|
-
else sensor.isActive = false;
|
|
3452
|
-
break;
|
|
3453
|
-
default:
|
|
3454
|
-
if (typeof sensor.id === 'undefined') sys.equipment.tempSensors.removeItemByIndex(i);
|
|
3455
4247
|
break;
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
let heaters = sys.heaters.toArray();
|
|
3466
|
-
let bodies = state.temps.bodies.toArray();
|
|
3467
|
-
let hon = [];
|
|
3468
|
-
for (let i = 0; i < bodies.length; i++) {
|
|
3469
|
-
let body: BodyTempState = bodies[i];
|
|
3470
|
-
let cfgBody: Body = sys.bodies.getItemById(body.id);
|
|
3471
|
-
let isHeating = false;
|
|
3472
|
-
if (body.isOn) {
|
|
3473
|
-
if (typeof body.temp === 'undefined' && heaters.length > 0) logger.warn(`The body temperature for ${body.name} cannot be determined. Heater status for this body cannot be calculated.`);
|
|
3474
|
-
for (let j = 0; j < heaters.length; j++) {
|
|
3475
|
-
let heater: Heater = heaters[j];
|
|
3476
|
-
if (heater.isActive === false) continue;
|
|
3477
|
-
let isOn = false;
|
|
3478
|
-
let isCooling = false;
|
|
3479
|
-
let sensorTemp = state.temps.waterSensor1;
|
|
3480
|
-
if (body.id === 4) sensorTemp = state.temps.waterSensor4;
|
|
3481
|
-
if (body.id === 3) sensorTemp = state.temps.waterSensor3;
|
|
3482
|
-
if (body.id === 2 && !sys.equipment.shared) sensorTemp = state.temps.waterSensor2;
|
|
3483
|
-
|
|
3484
|
-
// Determine whether the heater can be used on this body.
|
|
3485
|
-
let isAssociated = false;
|
|
3486
|
-
let b = sys.board.valueMaps.bodies.transform(heater.body);
|
|
3487
|
-
switch (b.name) {
|
|
3488
|
-
case 'body1':
|
|
3489
|
-
case 'pool':
|
|
3490
|
-
if (body.id === 1) isAssociated = true;
|
|
3491
|
-
break;
|
|
3492
|
-
case 'body2':
|
|
3493
|
-
case 'spa':
|
|
3494
|
-
if (body.id === 2) isAssociated = true;
|
|
3495
|
-
break;
|
|
3496
|
-
case 'poolspa':
|
|
3497
|
-
if (body.id === 1 || body.id === 2) isAssociated = true;
|
|
3498
|
-
break;
|
|
3499
|
-
case 'body3':
|
|
3500
|
-
if (body.id === 3) isAssociated = true;
|
|
3501
|
-
break;
|
|
3502
|
-
case 'body4':
|
|
3503
|
-
if (body.id === 4) isAssociated = true;
|
|
3504
|
-
break;
|
|
4248
|
+
case 'heatpump':
|
|
4249
|
+
if (mode === 'heatpump' || mode === 'heatpumppref') {
|
|
4250
|
+
if (hstate.isOn) {
|
|
4251
|
+
// If the heater is already on we will heat to 1 degree past the setpoint.
|
|
4252
|
+
if (body.temp - 1 < cfgBody.heatSetpoint) {
|
|
4253
|
+
isOn = true;
|
|
4254
|
+
body.heatStatus = sys.board.valueMaps.heatStatus.getValue('hpheat');
|
|
4255
|
+
isHeating = true;
|
|
4256
|
+
isCooling = false;
|
|
3505
4257
|
}
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
case 'solar':
|
|
3516
|
-
if (mode === 'solar' || mode === 'solarpref') {
|
|
3517
|
-
// Measure up against start and stop temp deltas for effective solar heating.
|
|
3518
|
-
if (body.temp < cfgBody.heatSetpoint &&
|
|
3519
|
-
state.temps.solar > body.temp + (hstate.isOn ? heater.stopTempDelta : heater.startTempDelta)) {
|
|
3520
|
-
isOn = true;
|
|
3521
|
-
body.heatStatus = sys.board.valueMaps.heatStatus.getValue('solar');
|
|
3522
|
-
isHeating = true;
|
|
3523
|
-
}
|
|
3524
|
-
else if (heater.coolingEnabled && body.temp > cfgBody.coolSetpoint && state.heliotrope.isNight &&
|
|
3525
|
-
state.temps.solar > body.temp + (hstate.isOn ? heater.stopTempDelta : heater.startTempDelta)) {
|
|
3526
|
-
isOn = true;
|
|
3527
|
-
body.heatStatus = sys.board.valueMaps.heatStatus.getValue('cooling');
|
|
3528
|
-
isHeating = true;
|
|
3529
|
-
isCooling = true;
|
|
3530
|
-
}
|
|
3531
|
-
}
|
|
3532
|
-
break;
|
|
3533
|
-
case 'ultratemp':
|
|
3534
|
-
// We need to determine whether we are going to use the air temp or the solar temp
|
|
3535
|
-
// for the sensor.
|
|
3536
|
-
let deltaTemp = Math.max(state.temps.air, state.temps.solar || 0);
|
|
3537
|
-
if (mode === 'ultratemp' || mode === 'ultratemppref') {
|
|
3538
|
-
if (body.temp < cfgBody.heatSetpoint &&
|
|
3539
|
-
deltaTemp > body.temp + heater.differentialTemp || 0) {
|
|
3540
|
-
isOn = true;
|
|
3541
|
-
body.heatStatus = sys.board.valueMaps.heatStatus.getValue('hpheat');
|
|
3542
|
-
isHeating = true;
|
|
3543
|
-
isCooling = false;
|
|
3544
|
-
}
|
|
3545
|
-
else if (body.temp > cfgBody.coolSetpoint && heater.coolingEnabled) {
|
|
3546
|
-
isOn = true;
|
|
3547
|
-
body.heatStatus = sys.board.valueMaps.heatStatus.getValue('hpcool');
|
|
3548
|
-
isHeating = true;
|
|
3549
|
-
isCooling = true;
|
|
3550
|
-
}
|
|
3551
|
-
}
|
|
3552
|
-
break;
|
|
3553
|
-
case 'mastertemp':
|
|
3554
|
-
if (mode === 'mtheater') {
|
|
3555
|
-
if (body.temp < cfgBody.setPoint) {
|
|
3556
|
-
isOn = true;
|
|
3557
|
-
body.heatStatus = sys.board.valueMaps.heaterTypes.getValue('mtheater');
|
|
3558
|
-
isHeating = true;
|
|
3559
|
-
}
|
|
3560
|
-
}
|
|
3561
|
-
break;
|
|
3562
|
-
case 'maxetherm':
|
|
3563
|
-
case 'gas':
|
|
3564
|
-
if (mode === 'heater') {
|
|
3565
|
-
if (body.temp < cfgBody.setPoint) {
|
|
3566
|
-
isOn = true;
|
|
3567
|
-
body.heatStatus = sys.board.valueMaps.heatStatus.getValue('heater');
|
|
3568
|
-
isHeating = true;
|
|
3569
|
-
}
|
|
3570
|
-
}
|
|
3571
|
-
else if (mode === 'solarpref' || mode === 'heatpumppref') {
|
|
3572
|
-
// If solar should be running gas heater should be off.
|
|
3573
|
-
if (body.temp < cfgBody.setPoint &&
|
|
3574
|
-
state.temps.solar > body.temp + (hstate.isOn ? heater.stopTempDelta : heater.startTempDelta)) isOn = false;
|
|
3575
|
-
else if (body.temp < cfgBody.setPoint) {
|
|
3576
|
-
isOn = true;
|
|
3577
|
-
body.heatStatus = sys.board.valueMaps.heatStatus.getValue('heater');
|
|
3578
|
-
isHeating = true;
|
|
3579
|
-
}
|
|
3580
|
-
}
|
|
3581
|
-
break;
|
|
3582
|
-
case 'heatpump':
|
|
3583
|
-
if (mode === 'heatpump' || mode === 'heatpumppref') {
|
|
3584
|
-
if (body.temp < cfgBody.setPoint &&
|
|
3585
|
-
state.temps.solar > body.temp + (hstate.isOn ? heater.stopTempDelta : heater.startTempDelta)) {
|
|
3586
|
-
isOn = true;
|
|
3587
|
-
body.heatStatus = sys.board.valueMaps.heatStatus.getValue('heater');
|
|
3588
|
-
isHeating = true;
|
|
3589
|
-
}
|
|
3590
|
-
}
|
|
3591
|
-
break;
|
|
3592
|
-
default:
|
|
3593
|
-
isOn = utils.makeBool(hstate.isOn);
|
|
3594
|
-
break;
|
|
3595
|
-
}
|
|
3596
|
-
logger.debug(`Heater Type: ${htype.name} Mode:${mode} Temp: ${body.temp} Setpoint: ${cfgBody.setPoint} Status: ${body.heatStatus}`);
|
|
3597
|
-
}
|
|
3598
|
-
else {
|
|
3599
|
-
let mode = sys.board.valueMaps.heatModes.getName(body.heatMode);
|
|
3600
|
-
switch (htype.name) {
|
|
3601
|
-
case 'mastertemp':
|
|
3602
|
-
if (status === 'mtheater') isHeating = isOn = true;
|
|
3603
|
-
break;
|
|
3604
|
-
case 'maxetherm':
|
|
3605
|
-
case 'gas':
|
|
3606
|
-
if (status === 'heater') isHeating = isOn = true;
|
|
3607
|
-
break;
|
|
3608
|
-
case 'hybrid':
|
|
3609
|
-
case 'ultratemp':
|
|
3610
|
-
case 'heatpump':
|
|
3611
|
-
if (mode === 'ultratemp' || mode === 'ultratemppref' || mode === 'heatpump' || mode === 'heatpumppref') {
|
|
3612
|
-
if (status === 'heater') isHeating = isOn = true;
|
|
3613
|
-
else if (status === 'cooling') isCooling = isOn = true;
|
|
3614
|
-
}
|
|
3615
|
-
break;
|
|
3616
|
-
case 'solar':
|
|
3617
|
-
if (mode === 'solar' || mode === 'solarpref') {
|
|
3618
|
-
if (status === 'solar') isHeating = isOn = true;
|
|
3619
|
-
else if (status === 'cooling') isCooling = isOn = true;
|
|
3620
|
-
}
|
|
3621
|
-
break;
|
|
3622
|
-
}
|
|
3623
|
-
}
|
|
3624
|
-
if (isOn === true && typeof hon.find(elem => elem === heater.id) === 'undefined') {
|
|
3625
|
-
hon.push(heater.id);
|
|
3626
|
-
if (heater.master === 1 && isOn) (async () => {
|
|
3627
|
-
try {
|
|
3628
|
-
await ncp.heaters.setHeaterStateAsync(hstate, isOn, isCooling);
|
|
3629
|
-
} catch (err) { logger.error(err.message); }
|
|
3630
|
-
})();
|
|
3631
|
-
else hstate.isOn = isOn;
|
|
3632
|
-
}
|
|
4258
|
+
}
|
|
4259
|
+
else {
|
|
4260
|
+
// The heater is not currently on lets turn it on if we pass all the criteria.
|
|
4261
|
+
if ((body.temp < cfgBody.heatSetpoint && hstate.endTime.getTime() < new Date().getTime() + (30 * 60 * 1000))
|
|
4262
|
+
|| body.temp + heater.differentialTemp < cfgBody.heatSetpoint) {
|
|
4263
|
+
isOn = true;
|
|
4264
|
+
body.heatStatus = sys.board.valueMaps.heatStatus.getValue('hpcool');
|
|
4265
|
+
isHeating = true;
|
|
4266
|
+
isCooling = false;
|
|
3633
4267
|
}
|
|
4268
|
+
}
|
|
3634
4269
|
}
|
|
4270
|
+
break;
|
|
4271
|
+
default:
|
|
4272
|
+
isOn = utils.makeBool(hstate.isOn);
|
|
4273
|
+
break;
|
|
3635
4274
|
}
|
|
3636
|
-
|
|
3637
|
-
|
|
4275
|
+
logger.debug(`Heater Type: ${htype.name} Mode:${mode} Temp: ${body.temp} Setpoint: ${cfgBody.setPoint} Status: ${body.heatStatus}`);
|
|
4276
|
+
}
|
|
3638
4277
|
}
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
4278
|
+
else {
|
|
4279
|
+
let mode = sys.board.valueMaps.heatModes.getName(body.heatMode);
|
|
4280
|
+
switch (htype.name) {
|
|
4281
|
+
case 'mastertemp':
|
|
4282
|
+
if (hstatus === 'mtheat') isHeating = isOn = true;
|
|
4283
|
+
break;
|
|
4284
|
+
case 'maxetherm':
|
|
4285
|
+
case 'gas':
|
|
4286
|
+
if (hstatus === 'heater') isHeating = isOn = true;
|
|
4287
|
+
break;
|
|
4288
|
+
case 'hybrid':
|
|
4289
|
+
if (hstatus === 'mtheat' || hstatus === 'heater' || hstatus === 'dual') isHeating = isOn = true;
|
|
4290
|
+
break;
|
|
4291
|
+
case 'ultratemp':
|
|
4292
|
+
case 'heatpump':
|
|
4293
|
+
if (mode === 'ultratemp' || mode === 'ultratemppref' || mode === 'heatpump' || mode === 'heatpumppref') {
|
|
4294
|
+
if (hstatus === 'heater') isHeating = isOn = true;
|
|
4295
|
+
else if (hstatus === 'cooling') isCooling = isOn = true;
|
|
4296
|
+
}
|
|
4297
|
+
break;
|
|
4298
|
+
case 'solar':
|
|
4299
|
+
if (mode === 'solar' || mode === 'solarpref') {
|
|
4300
|
+
if (hstatus === 'solar') isHeating = isOn = true;
|
|
4301
|
+
else if (hstatus === 'cooling') isCooling = isOn = true;
|
|
4302
|
+
}
|
|
4303
|
+
break;
|
|
4304
|
+
}
|
|
3651
4305
|
}
|
|
3652
|
-
|
|
3653
|
-
|
|
4306
|
+
if (isOn === true && typeof hon.find(elem => elem === heater.id) === 'undefined') {
|
|
4307
|
+
hon.push(heater.id);
|
|
4308
|
+
if (heater.master === 1 && isOn) (async () => {
|
|
4309
|
+
try {
|
|
4310
|
+
hstate.bodyId = body.id;
|
|
4311
|
+
if (sys.board.valueMaps.heatStatus.getName(body.heatStatus) === 'cooldown')
|
|
4312
|
+
await ncp.heaters.setHeaterStateAsync(hstate, false, false);
|
|
4313
|
+
else if (isOn) {
|
|
4314
|
+
hstate.bodyId = body.id;
|
|
4315
|
+
await ncp.heaters.setHeaterStateAsync(hstate, isOn, isCooling);
|
|
4316
|
+
}
|
|
4317
|
+
else if (hstate.isOn !== isOn || hstate.isCooling !== isCooling) {
|
|
4318
|
+
await ncp.heaters.setHeaterStateAsync(hstate, isOn, isCooling);
|
|
4319
|
+
}
|
|
4320
|
+
} catch (err) { logger.error(err.message); }
|
|
4321
|
+
})();
|
|
4322
|
+
else {
|
|
4323
|
+
hstate.isOn = isOn;
|
|
4324
|
+
hstate.bodyId = body.id;
|
|
4325
|
+
}
|
|
4326
|
+
}
|
|
4327
|
+
// If there is a heater on for the body we need break out of the loop. This will make sure for instance a gas heater
|
|
4328
|
+
// isn't started when one of the more economical methods are.
|
|
4329
|
+
if (isOn === true) break;
|
|
4330
|
+
}
|
|
4331
|
+
}
|
|
4332
|
+
if (sys.controllerType === ControllerType.Nixie && !isHeating && !isCooling && hstatus !== 'cooldown') body.heatStatus = sys.board.valueMaps.heatStatus.getValue('off');
|
|
4333
|
+
//else if (sys.controllerType === ControllerType.Nixie) body.heatStatus = 0;
|
|
4334
|
+
}
|
|
4335
|
+
// Turn off any heaters that should be off. The code above only turns heaters on.
|
|
4336
|
+
for (let i = 0; i < heaters.length; i++) {
|
|
4337
|
+
let heater: Heater = heaters[i];
|
|
4338
|
+
if (typeof hon.find(elem => elem === heater.id) === 'undefined') {
|
|
4339
|
+
let hstate = state.heaters.getItemById(heater.id, true);
|
|
4340
|
+
if (heater.master === 1) (async () => {
|
|
4341
|
+
try {
|
|
4342
|
+
await ncp.heaters.setHeaterStateAsync(hstate, false, false);
|
|
4343
|
+
hstate.bodyId = 0;
|
|
4344
|
+
} catch (err) { logger.error(err.message); }
|
|
4345
|
+
})();
|
|
4346
|
+
else {
|
|
4347
|
+
hstate.isOn = false;
|
|
4348
|
+
hstate.bodyId = 0;
|
|
4349
|
+
}
|
|
4350
|
+
}
|
|
4351
|
+
}
|
|
4352
|
+
} catch (err) { logger.error(`Error synchronizing heater states: ${err.message}`); }
|
|
4353
|
+
}
|
|
3654
4354
|
}
|
|
3655
4355
|
export class ValveCommands extends BoardCommands {
|
|
3656
4356
|
public async restore(rest: { poolConfig: any, poolState: any }, ctx: any, res: RestoreResults): Promise<boolean> {
|
|
@@ -3750,6 +4450,22 @@ export class ValveCommands extends BoardCommands {
|
|
|
3750
4450
|
}
|
|
3751
4451
|
public async syncValveStates() {
|
|
3752
4452
|
try {
|
|
4453
|
+
// 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.
|
|
4454
|
+
let drain = sys.equipment.shared ? typeof state.circuits.get().find(elem => typeof elem.type !== 'undefined' && elem.type.name === 'spadrain' && elem.isOn === true) !== 'undefined' ||
|
|
4455
|
+
typeof state.features.get().find(elem => typeof elem.type !== 'undefined' && elem.type.name === 'spadrain' && elem.isOn === true) !== 'undefined' : false;
|
|
4456
|
+
// 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.
|
|
4457
|
+
let spillway = sys.equipment.shared ? typeof state.circuits.get().find(elem => typeof elem.type !== 'undefined' && elem.type.name === 'spillway' && elem.isOn === true) !== 'undefined' ||
|
|
4458
|
+
typeof state.features.get().find(elem => typeof elem.type !== 'undefined' && elem.type.name === 'spillway' && elem.isOn === true) !== 'undefined' : false;
|
|
4459
|
+
let spa = sys.equipment.shared ? state.circuits.getItemById(1).isOn : false;
|
|
4460
|
+
let pool = sys.equipment.shared ? state.circuits.getItemById(6).isOn : false;
|
|
4461
|
+
// Set the valve mode.
|
|
4462
|
+
if (!sys.equipment.shared) state.valveMode = sys.board.valueMaps.valveModes.getValue('off');
|
|
4463
|
+
else if (drain) state.valveMode = sys.board.valueMaps.valveModes.getValue('spadrain');
|
|
4464
|
+
else if (spillway) state.valveMode = sys.board.valueMaps.valveModes.getValue('spillway');
|
|
4465
|
+
else if (spa) state.valveMode = sys.board.valueMaps.valveModes.getValue('spa');
|
|
4466
|
+
else if (pool) state.valveMode = sys.board.valueMaps.valveModes.getValue('pool');
|
|
4467
|
+
else state.valveMode = sys.board.valueMaps.valveModes.getValue('off');
|
|
4468
|
+
|
|
3753
4469
|
for (let i = 0; i < sys.valves.length; i++) {
|
|
3754
4470
|
// Run through all the valves to see whether they should be triggered or not.
|
|
3755
4471
|
let valve = sys.valves.getItemByIndex(i);
|
|
@@ -3757,13 +4473,21 @@ export class ValveCommands extends BoardCommands {
|
|
|
3757
4473
|
let vstate = state.valves.getItemById(valve.id, true);
|
|
3758
4474
|
let isDiverted = vstate.isDiverted;
|
|
3759
4475
|
if (typeof valve.circuit !== 'undefined' && valve.circuit > 0) {
|
|
3760
|
-
if (sys.equipment.shared && valve.isIntake === true)
|
|
3761
|
-
|
|
4476
|
+
if (sys.equipment.shared && valve.isIntake === true) {
|
|
4477
|
+
// Valve Diverted Positions
|
|
4478
|
+
// Spa: Y
|
|
4479
|
+
// Drain: Y
|
|
4480
|
+
// Spillway: N
|
|
4481
|
+
// Pool: N
|
|
4482
|
+
isDiverted = utils.makeBool(spa || drain); // If the spa is on then the intake is diverted.
|
|
4483
|
+
}
|
|
3762
4484
|
else if (sys.equipment.shared && valve.isReturn === true) {
|
|
3763
|
-
//
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
4485
|
+
// Valve Diverted Positions
|
|
4486
|
+
// Spa: Y
|
|
4487
|
+
// Drain: N
|
|
4488
|
+
// Spillway: Y
|
|
4489
|
+
// Pool: N
|
|
4490
|
+
isDiverted = utils.makeBool((spa || spillway) && !drain);
|
|
3767
4491
|
}
|
|
3768
4492
|
else {
|
|
3769
4493
|
let circ = state.circuits.getInterfaceById(valve.circuit);
|
|
@@ -3779,6 +4503,35 @@ export class ValveCommands extends BoardCommands {
|
|
|
3779
4503
|
}
|
|
3780
4504
|
} catch (err) { logger.error(`syncValveStates: Error synchronizing valves ${err.message}`); }
|
|
3781
4505
|
}
|
|
4506
|
+
public getBodyValveCircuitIds(isOn?: boolean): number[] {
|
|
4507
|
+
let arrIds: number[] = [];
|
|
4508
|
+
if (sys.equipment.shared !== true) return arrIds;
|
|
4509
|
+
|
|
4510
|
+
{
|
|
4511
|
+
let dtype = sys.board.valueMaps.circuitFunctions.getValue('spadrain');
|
|
4512
|
+
let stype = sys.board.valueMaps.circuitFunctions.getValue('spillway');
|
|
4513
|
+
let ptype = sys.board.valueMaps.circuitFunctions.getValue('pool');
|
|
4514
|
+
let sptype = sys.board.valueMaps.circuitFunctions.getValue('spa');
|
|
4515
|
+
for (let i = 0; i < state.circuits.length; i++) {
|
|
4516
|
+
let cstate = state.circuits.getItemByIndex(i);
|
|
4517
|
+
if (typeof isOn === 'undefined' || cstate.isOn === isOn) {
|
|
4518
|
+
if (cstate.id === 1 || cstate.id === 6) arrIds.push(cstate.id);
|
|
4519
|
+
if (cstate.type === dtype || cstate.type === stype || cstate.type === ptype || cstate.type === sptype) arrIds.push(cstate.id);
|
|
4520
|
+
}
|
|
4521
|
+
}
|
|
4522
|
+
}
|
|
4523
|
+
{
|
|
4524
|
+
let dtype = sys.board.valueMaps.featureFunctions.getValue('spadrain');
|
|
4525
|
+
let stype = sys.board.valueMaps.featureFunctions.getValue('spillway');
|
|
4526
|
+
for (let i = 0; i < state.features.length; i++) {
|
|
4527
|
+
let fstate = state.features.getItemByIndex(i);
|
|
4528
|
+
if (typeof isOn === 'undefined' || fstate.isOn === isOn) {
|
|
4529
|
+
if (fstate.type === dtype || fstate.type === stype) arrIds.push(fstate.id);
|
|
4530
|
+
}
|
|
4531
|
+
}
|
|
4532
|
+
}
|
|
4533
|
+
return arrIds;
|
|
4534
|
+
}
|
|
3782
4535
|
}
|
|
3783
4536
|
export class ChemControllerCommands extends BoardCommands {
|
|
3784
4537
|
public async restore(rest: { poolConfig: any, poolState: any }, ctx: any, res: RestoreResults): Promise<boolean> {
|
|
@@ -3910,7 +4663,7 @@ export class ChemControllerCommands extends BoardCommands {
|
|
|
3910
4663
|
protected async setIntelliChemAsync(data: any): Promise<ChemController> {
|
|
3911
4664
|
try {
|
|
3912
4665
|
let chem = sys.chemControllers.getItemById(data.id);
|
|
3913
|
-
return await ncp.chemControllers.setControllerAsync(chem, data);
|
|
4666
|
+
return chem.master === 1 ? await ncp.chemControllers.setControllerAsync(chem, data) : chem;
|
|
3914
4667
|
} catch (err) { return Promise.reject(err); }
|
|
3915
4668
|
}
|
|
3916
4669
|
public findChemController(data: any) {
|
|
@@ -3935,11 +4688,11 @@ export class ChemControllerCommands extends BoardCommands {
|
|
|
3935
4688
|
let type = sys.board.valueMaps.chemControllerTypes.encode(isAdd ? data.type : chem.type);
|
|
3936
4689
|
if (typeof type === 'undefined') return Promise.reject(new InvalidEquipmentDataError(`The chem controller type could not be determined ${data.type || type}`, 'chemController', type));
|
|
3937
4690
|
if (isAdd && sys.equipment.maxChemControllers <= sys.chemControllers.length) return Promise.reject(new InvalidEquipmentDataError(`The maximum number of chem controllers have been added to your controller`, 'chemController', sys.equipment.maxChemControllers));
|
|
3938
|
-
let address = parseInt(data.address, 10);
|
|
4691
|
+
let address = typeof data.address !== 'undefined' ? parseInt(data.address, 10) : isAdd ? undefined : chem.address;
|
|
3939
4692
|
let t = sys.board.valueMaps.chemControllerTypes.transform(type);
|
|
3940
4693
|
if (t.hasAddress) {
|
|
3941
4694
|
// First lets make sure the user supplied an address.
|
|
3942
|
-
if (isNaN(address)) return Promise.reject(new InvalidEquipmentDataError(`${
|
|
4695
|
+
if (isNaN(address)) return Promise.reject(new InvalidEquipmentDataError(`${t.desc} chem controllers require a valid address`, 'chemController', data.address));
|
|
3943
4696
|
if (typeof sys.chemControllers.find(x => x.address === address && x.id !== (isAdd ? -1 : chem.id)) !== 'undefined') return Promise.reject(new InvalidEquipmentDataError(`${type.desc} chem controller addresses must be unique`, 'chemController', data.address));
|
|
3944
4697
|
}
|
|
3945
4698
|
if (isAdd) {
|
|
@@ -3952,8 +4705,10 @@ export class ChemControllerCommands extends BoardCommands {
|
|
|
3952
4705
|
chem.isActive = true;
|
|
3953
4706
|
// So here is the thing. If you have an OCP then the IntelliChem must be controlled by that.
|
|
3954
4707
|
// the messages on the bus will talk back to the OCP so if you do not do this mayhem will ensue.
|
|
3955
|
-
if (
|
|
3956
|
-
|
|
4708
|
+
if (t.name === 'intellichem') {
|
|
4709
|
+
logger.info(`${chem.name} - ${chem.id} routing IntelliChem to OCP`);
|
|
4710
|
+
await sys.board.chemControllers.setIntelliChemAsync(data);
|
|
4711
|
+
}
|
|
3957
4712
|
else
|
|
3958
4713
|
await ncp.chemControllers.setControllerAsync(chem, data);
|
|
3959
4714
|
return Promise.resolve(chem);
|
|
@@ -3966,7 +4721,8 @@ export class ChemControllerCommands extends BoardCommands {
|
|
|
3966
4721
|
let chem = sys.board.chemControllers.findChemController(data);
|
|
3967
4722
|
if (typeof chem === 'undefined') return Promise.reject(new InvalidEquipmentIdError(`A valid chem controller could not be found for id:${data.id} or address ${data.address}`, data.id || data.address, 'chemController'));
|
|
3968
4723
|
data.id = chem.id;
|
|
3969
|
-
|
|
4724
|
+
logger.info(`Setting ${chem.name} data ${chem.master}`);
|
|
4725
|
+
if (chem.master === 1) await ncp.chemControllers.setControllerAsync(chem, data);
|
|
3970
4726
|
else await sys.board.chemControllers.setChemControllerAsync(data);
|
|
3971
4727
|
let schem = state.chemControllers.getItemById(chem.id, true);
|
|
3972
4728
|
return Promise.resolve(schem);
|
|
@@ -4079,7 +4835,7 @@ export class FilterCommands extends BoardCommands {
|
|
|
4079
4835
|
if (f.type !== 0) fon = true;
|
|
4080
4836
|
// Check to see if this feature is used on a valve. This will make it
|
|
4081
4837
|
// not include this pressure either. We do not care whether the valve is diverted or not.
|
|
4082
|
-
if (typeof sys.valves.find(elem => elem.
|
|
4838
|
+
if (typeof sys.valves.find(elem => elem.circuit === f.id) !== 'undefined')
|
|
4083
4839
|
fon = true;
|
|
4084
4840
|
else {
|
|
4085
4841
|
// Finally if the feature happens to be used on a pump then we don't want it either.
|