nodejs-poolcontroller 7.7.0 → 8.0.1

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.
Files changed (82) hide show
  1. package/.eslintrc.json +26 -35
  2. package/Changelog +22 -0
  3. package/README.md +7 -3
  4. package/anslq25/MessagesMock.ts +218 -0
  5. package/anslq25/boards/MockBoardFactory.ts +50 -0
  6. package/anslq25/boards/MockEasyTouchBoard.ts +696 -0
  7. package/anslq25/boards/MockSystemBoard.ts +217 -0
  8. package/anslq25/chemistry/MockChlorinator.ts +75 -0
  9. package/anslq25/pumps/MockPump.ts +84 -0
  10. package/app.ts +10 -14
  11. package/config/Config.ts +13 -9
  12. package/config/VersionCheck.ts +6 -2
  13. package/controller/Constants.ts +58 -25
  14. package/controller/Equipment.ts +225 -41
  15. package/controller/Errors.ts +2 -1
  16. package/controller/Lockouts.ts +34 -2
  17. package/controller/State.ts +491 -48
  18. package/controller/boards/AquaLinkBoard.ts +6 -3
  19. package/controller/boards/BoardFactory.ts +5 -1
  20. package/controller/boards/EasyTouchBoard.ts +1971 -1751
  21. package/controller/boards/IntelliCenterBoard.ts +1311 -1688
  22. package/controller/boards/IntelliComBoard.ts +7 -1
  23. package/controller/boards/IntelliTouchBoard.ts +153 -42
  24. package/controller/boards/NixieBoard.ts +209 -66
  25. package/controller/boards/SunTouchBoard.ts +393 -0
  26. package/controller/boards/SystemBoard.ts +1862 -1543
  27. package/controller/comms/Comms.ts +539 -138
  28. package/controller/comms/ScreenLogic.ts +1663 -0
  29. package/controller/comms/messages/Messages.ts +242 -60
  30. package/controller/comms/messages/config/ChlorinatorMessage.ts +4 -3
  31. package/controller/comms/messages/config/CircuitGroupMessage.ts +5 -2
  32. package/controller/comms/messages/config/CircuitMessage.ts +81 -13
  33. package/controller/comms/messages/config/ConfigMessage.ts +3 -1
  34. package/controller/comms/messages/config/CoverMessage.ts +2 -1
  35. package/controller/comms/messages/config/CustomNameMessage.ts +2 -1
  36. package/controller/comms/messages/config/EquipmentMessage.ts +5 -1
  37. package/controller/comms/messages/config/ExternalMessage.ts +33 -3
  38. package/controller/comms/messages/config/FeatureMessage.ts +2 -1
  39. package/controller/comms/messages/config/GeneralMessage.ts +2 -1
  40. package/controller/comms/messages/config/HeaterMessage.ts +3 -1
  41. package/controller/comms/messages/config/IntellichemMessage.ts +2 -1
  42. package/controller/comms/messages/config/OptionsMessage.ts +12 -6
  43. package/controller/comms/messages/config/PumpMessage.ts +9 -12
  44. package/controller/comms/messages/config/RemoteMessage.ts +80 -13
  45. package/controller/comms/messages/config/ScheduleMessage.ts +43 -3
  46. package/controller/comms/messages/config/SecurityMessage.ts +2 -1
  47. package/controller/comms/messages/config/ValveMessage.ts +43 -26
  48. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +8 -7
  49. package/controller/comms/messages/status/EquipmentStateMessage.ts +93 -20
  50. package/controller/comms/messages/status/HeaterStateMessage.ts +24 -5
  51. package/controller/comms/messages/status/IntelliChemStateMessage.ts +7 -4
  52. package/controller/comms/messages/status/IntelliValveStateMessage.ts +2 -1
  53. package/controller/comms/messages/status/PumpStateMessage.ts +72 -4
  54. package/controller/comms/messages/status/VersionMessage.ts +2 -1
  55. package/controller/nixie/Nixie.ts +15 -4
  56. package/controller/nixie/NixieEquipment.ts +1 -0
  57. package/controller/nixie/chemistry/ChemController.ts +300 -129
  58. package/controller/nixie/chemistry/ChemDoser.ts +806 -0
  59. package/controller/nixie/chemistry/Chlorinator.ts +133 -129
  60. package/controller/nixie/circuits/Circuit.ts +171 -30
  61. package/controller/nixie/heaters/Heater.ts +337 -173
  62. package/controller/nixie/pumps/Pump.ts +264 -236
  63. package/controller/nixie/schedules/Schedule.ts +9 -3
  64. package/defaultConfig.json +46 -5
  65. package/logger/Logger.ts +38 -9
  66. package/package.json +13 -9
  67. package/web/Server.ts +235 -122
  68. package/web/bindings/aqualinkD.json +114 -59
  69. package/web/bindings/homeassistant.json +437 -0
  70. package/web/bindings/influxDB.json +15 -0
  71. package/web/bindings/mqtt.json +28 -9
  72. package/web/bindings/mqttAlt.json +15 -0
  73. package/web/interfaces/baseInterface.ts +58 -7
  74. package/web/interfaces/httpInterface.ts +5 -2
  75. package/web/interfaces/influxInterface.ts +9 -2
  76. package/web/interfaces/mqttInterface.ts +234 -74
  77. package/web/interfaces/ruleInterface.ts +87 -0
  78. package/web/services/config/Config.ts +140 -33
  79. package/web/services/config/ConfigSocket.ts +2 -1
  80. package/web/services/state/State.ts +144 -3
  81. package/web/services/state/StateSocket.ts +65 -14
  82. package/web/services/utilities/Utilities.ts +189 -1
@@ -1,5 +1,6 @@
1
1
  /* nodejs-poolController. An application to control pool equipment.
2
- Copyright (C) 2016, 2017, 2018, 2019, 2020. Russell Goldin, tagyoureit. russ.goldin@gmail.com
2
+ Copyright (C) 2016, 2017, 2018, 2019, 2020, 2021, 2022.
3
+ Russell Goldin, tagyoureit. russ.goldin@gmail.com
3
4
 
4
5
  This program is free software: you can redistribute it and/or modify
5
6
  it under the terms of the GNU Affero General Public License as
@@ -15,18 +16,19 @@ You should have received a copy of the GNU Affero General Public License
15
16
  along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17
  */
17
18
  import * as extend from 'extend';
18
- import { EventEmitter } from 'events';
19
19
  import { ncp } from "../nixie/Nixie";
20
20
  import { NixieHeaterBase } from "../nixie/heaters/Heater";
21
- import { utils, Heliotrope, Timestamp } from '../Constants';
22
- import {SystemBoard, byteValueMap, ConfigQueue, ConfigRequest, BodyCommands, FilterCommands, PumpCommands, SystemCommands, CircuitCommands, FeatureCommands, ValveCommands, HeaterCommands, ChlorinatorCommands, ChemControllerCommands, EquipmentIdRange} from './SystemBoard';
21
+ import { utils } from '../Constants';
22
+ import {SystemBoard, byteValueMap, BodyCommands, FilterCommands, PumpCommands, SystemCommands, CircuitCommands, FeatureCommands, ValveCommands, HeaterCommands, ChlorinatorCommands, ChemControllerCommands, EquipmentIdRange} from './SystemBoard';
23
23
  import { logger } from '../../logger/Logger';
24
- import { state, ChlorinatorState, ChemControllerState, TemperatureState, VirtualCircuitState, CircuitState, ICircuitState, ICircuitGroupState, LightGroupState, ValveState, FilterState, BodyTempState, FeatureState } from '../State';
25
- import { sys, Equipment, Options, Owner, Location, CircuitCollection, TempSensorCollection, General, PoolSystem, Body, Pump, CircuitGroupCircuit, CircuitGroup, ChemController, Circuit, Feature, Valve, ICircuit, Heater, LightGroup, LightGroupCircuit, ControllerType, Filter } from '../Equipment';
26
- import { Protocol, Outbound, Message, Response } from '../comms/messages/Messages';
27
- import { BoardProcessError, EquipmentNotFoundError, InvalidEquipmentDataError, InvalidEquipmentIdError, InvalidOperationError, ParameterOutOfRangeError } from '../Errors';
28
- import { conn } from '../comms/Comms';
24
+ import { state, CircuitState, ICircuitState, ICircuitGroupState, LightGroupState, ValveState, FilterState, BodyTempState, FeatureState } from '../State';
25
+ import { sys, Equipment, General, PoolSystem, CircuitGroupCircuit, CircuitGroup, ChemController, Circuit, Feature, Valve, ICircuit, Heater, LightGroup, LightGroupCircuit, ControllerType, Filter } from '../Equipment';
26
+ import { BoardProcessError, EquipmentNotFoundError, InvalidEquipmentDataError, InvalidEquipmentIdError, ServiceParameterError } from '../Errors';
29
27
  import { delayMgr } from '../Lockouts';
28
+ import { webApp } from "../../web/Server";
29
+ import { setTimeout } from 'timers/promises';
30
+ import { setTimeout as setTimeoutSync } from 'timers';
31
+
30
32
  export class NixieBoard extends SystemBoard {
31
33
  constructor (system: PoolSystem){
32
34
  super(system);
@@ -34,7 +36,7 @@ export class NixieBoard extends SystemBoard {
34
36
  this.equipmentIds.circuits = new EquipmentIdRange(1, function () { return this.start + sys.equipment.maxCircuits - 1; });
35
37
  this.equipmentIds.features = new EquipmentIdRange(function () { return 129; }, function () { return this.start + sys.equipment.maxFeatures - 1; });
36
38
  this.equipmentIds.circuitGroups = new EquipmentIdRange(function () { return this.start; }, function () { return this.start + sys.equipment.maxCircuitGroups - 1; });
37
- this.equipmentIds.virtualCircuits = new EquipmentIdRange(function () { return this.start; }, function () { return 254; });
39
+ this.equipmentIds.virtualCircuits = new EquipmentIdRange(function () { return this.start; }, function () { return 277; });
38
40
  this.equipmentIds.features.start = 129;
39
41
  this.equipmentIds.circuitGroups.start = 193;
40
42
  this.equipmentIds.virtualCircuits.start = 237;
@@ -42,7 +44,12 @@ export class NixieBoard extends SystemBoard {
42
44
  [1, { val: 1, name: 'ncp', desc: 'Nixie Control Panel' }],
43
45
  [2, { val: 2, name: 'ext', desc: 'External Control Panel'}]
44
46
  ]);
45
-
47
+ this.valueMaps.panelModes = new byteValueMap([
48
+ [0, { name: 'auto', desc: 'Auto' }],
49
+ [1, { name: 'service', desc: 'Service' }],
50
+ [128, { name: 'timeout', desc: 'Timeout' }],
51
+ [255, { name: 'error', desc: 'System Error' }]
52
+ ]);
46
53
  this.valueMaps.featureFunctions = new byteValueMap([
47
54
  [0, { name: 'generic', desc: 'Generic' }],
48
55
  [1, { name: 'spillway', desc: 'Spillway' }],
@@ -68,7 +75,7 @@ export class NixieBoard extends SystemBoard {
68
75
  [16, { name: 'pooltone', desc: 'Pool Tone', isLight: true, theme: 'pooltone' }],
69
76
  ]);
70
77
  this.valueMaps.pumpTypes = new byteValueMap([
71
- [1, { name: 'ss', desc: 'Single Speed', maxCircuits: 0, hasAddress: false, hasBody: true, maxRelays: 1, relays: [{ id: 1, name: 'Pump On/Off' }]}],
78
+ [1, { name: 'ss', desc: 'Single Speed', maxCircuits: 8, hasAddress: false, hasBody: false, maxRelays: 1, relays: [{ id: 1, name: 'Pump On/Off' }]}],
72
79
  [2, { name: 'ds', desc: 'Two Speed', maxCircuits: 8, hasAddress: false, hasBody: false, maxRelays: 2, relays: [{ id: 1, name: 'Low Speed' }, { id: 2, name: 'High Speed' }]}],
73
80
  [3, { name: 'vs', desc: 'Intelliflo VS', maxPrimingTime: 6, minSpeed: 450, maxSpeed: 3450, maxCircuits: 8, hasAddress: true }],
74
81
  [4, { name: 'vsf', desc: 'Intelliflo VSF', minSpeed: 450, maxSpeed: 3450, minFlow: 15, maxFlow: 130, maxCircuits: 8, hasAddress: true }],
@@ -94,9 +101,11 @@ export class NixieBoard extends SystemBoard {
94
101
  [7, { name: 'sun', desc: 'Sunday', dow: 0, bitval: 64 }]
95
102
  ]);
96
103
  this.valueMaps.groupCircuitStates = new byteValueMap([
97
- [1, { name: 'on', desc: 'On' }],
98
- [2, { name: 'off', desc: 'Off' }],
99
- [3, { name: 'ignore', desc: 'Ignore' }]
104
+ [1, { name: 'on', desc: 'On/Off' }],
105
+ [2, { name: 'off', desc: 'Off/On' }],
106
+ [3, { name: 'ignore', desc: 'Ignore' }],
107
+ [4, { name: 'on+ignore', desc: 'On/Ignore' }],
108
+ [5, { name: 'off+ignore', desc: 'Off/Ignore' }]
100
109
  ]);
101
110
  this.valueMaps.chlorinatorModel = new byteValueMap([
102
111
  [0, { name: 'unknown', desc: 'unknown', capacity: 0, chlorinePerDay: 0, chlorinePerSec: 0 }],
@@ -134,7 +143,7 @@ export class NixieBoard extends SystemBoard {
134
143
  return { val: b, days: days };
135
144
  };
136
145
  this.valueMaps.expansionBoards = new byteValueMap([
137
- [0, { name: 'nxp', part: 'NXP', desc: 'Nixie Single Body', bodies: 1, valves: 0, shared: false, dual: false }],
146
+ [0, { name: 'nxp', part: 'NXP', desc: 'Nixie Single Body', bodies: 1, valves: 0, single: true, shared: false, dual: false }],
138
147
  [1, { name: 'nxps', part: 'NXPS', desc: 'Nixie Shared Body', bodies: 2, valves: 2, shared: true, dual: false, chlorinators: 1, chemControllers: 1 }],
139
148
  [2, { name: 'nxpd', part: 'NXPD', desc: 'Nixie Dual Body', bodies: 2, valves: 0, shared: false, dual: true, chlorinators: 2, chemControllers: 2 }],
140
149
  [255, { name: 'nxnb', part: 'NXNB', desc: 'Nixie No Body', bodies: 0, valves: 0, shared: false, dual: false, chlorinators: 0, chemControllers: 0 }]
@@ -155,7 +164,8 @@ export class NixieBoard extends SystemBoard {
155
164
  [255, { name: 'solar3', desc: 'Solar Body 3' }],
156
165
  [256, { name: 'solar4', desc: 'Solar Body 4' }],
157
166
  [257, { name: 'poolHeatEnable', desc: 'Pool Heat Enable' }],
158
- [258, { name: 'anyHeater', desc: 'Any Heater' }]
167
+ [258, { name: 'anyHeater', desc: 'Any Heater' }],
168
+ [259, { name: 'heatpump', desc: 'Heat Pump'}]
159
169
  ]);
160
170
  this.valueMaps.scheduleTimeTypes.merge([
161
171
  [1, { name: 'sunrise', desc: 'Sunrise' }],
@@ -187,29 +197,30 @@ export class NixieBoard extends SystemBoard {
187
197
  [27, { name: 'flamingo', desc: 'Flamingo', types: ['colorlogic'], sequence: 9 }],
188
198
  [28, { name: 'vividviolet', desc: 'Vivid Violet', types: ['colorlogic'], sequence: 10 }],
189
199
  [29, { name: 'sangria', desc: 'Sangria', types: ['colorlogic'], sequence: 11 }],
190
- [30, { name: 'twilight', desc: 'Twilight', types: ['colorlogic'], sequence: 12 }],
191
- [31, { name: 'tranquility', desc: 'Tranquility', types: ['colorlogic'], sequence: 13 }],
192
- [32, { name: 'gemstone', desc: 'Gemstone', types: ['colorlogic'], sequence: 14 }],
193
- [33, { name: 'usa', desc: 'USA', types: ['colorlogic'], sequence: 15 }],
194
- [34, { name: 'mardigras', desc: 'Mardi Gras', types: ['colorlogic'], sequence: 16 }],
195
- [35, { name: 'coolcabaret', desc: 'Cabaret', types: ['colorlogic'], sequence: 17 }],
200
+ [30, { name: 'voodoolounge', desc: 'Voodoo Lounge', types: ['colorlogic'], sequence: 1 }],
201
+ [31, { name: 'twilight', desc: 'Twilight', types: ['colorlogic'], sequence: 12 }],
202
+ [32, { name: 'tranquility', desc: 'Tranquility', types: ['colorlogic'], sequence: 13 }],
203
+ [33, { name: 'gemstone', desc: 'Gemstone', types: ['colorlogic'], sequence: 14 }],
204
+ [34, { name: 'usa', desc: 'USA', types: ['colorlogic'], sequence: 15 }],
205
+ [35, { name: 'mardigras', desc: 'Mardi Gras', types: ['colorlogic'], sequence: 16 }],
206
+ [36, { name: 'coolcabaret', desc: 'Cabaret', types: ['colorlogic'], sequence: 17 }],
196
207
  // Sunseeker PoolTone Themes
197
- [36, { name: 'eveningsea', desc: 'Evening Sea', types: ['pooltone'], sequence: 1 }],
198
- [37, { name: 'eveningrivers', desc: 'Evening Rivers', types: ['pooltone'], sequence: 2 }],
199
- [38, { name: 'riviera', desc: 'Riviera', types: ['pooltone'], sequence: 3 }],
200
- [39, { name: 'neutralwhite', desc: 'Neutral White', types: ['pooltone'], sequence: 4 }],
201
- [40, { name: 'rainbow', desc: 'Rainbow', types: ['pooltone'], sequence: 5 }],
202
- [41, { name: 'colorriver', desc: 'Color River', types: ['pooltone'], sequence: 6 }],
203
- [42, { name: 'disco', desc: 'Disco', types: ['pooltone'], sequence: 7 }],
204
- [43, { name: 'fourseasons', desc: 'Four Seasons', types: ['pooltone'], sequence: 8 }],
205
- [44, { name: 'Party', desc: 'Party', types: ['pooltone'], sequence: 9 }],
206
- [45, { name: 'sunwhite', desc: 'Sun White', types: ['pooltone'], sequence: 10 }],
207
- [46, { name: 'red', desc: 'Red', types: ['pooltone'], sequence: 11 }],
208
- [47, { name: 'green', desc: 'Green', types: ['pooltone'], sequence: 12 }],
209
- [48, { name: 'blue', desc: 'Blue', types: ['pooltone'], sequence: 13 }],
210
- [49, { name: 'greenblue', desc: 'Green-Blue', types: ['pooltone'], sequence: 14 }],
211
- [50, { name: 'redgreen', desc: 'Red-Green', types: ['pooltone'], sequence: 15 }],
212
- [51, { name: 'bluered', desc: 'Blue-red', types: ['pooltone'], sequence: 16 }],
208
+ [40, { name: 'eveningsea', desc: 'Evening Sea', types: ['pooltone'], sequence: 1 }],
209
+ [41, { name: 'eveningrivers', desc: 'Evening Rivers', types: ['pooltone'], sequence: 2 }],
210
+ [42, { name: 'riviera', desc: 'Riviera', types: ['pooltone'], sequence: 3 }],
211
+ [43, { name: 'neutralwhite', desc: 'Neutral White', types: ['pooltone'], sequence: 4 }],
212
+ [44, { name: 'rainbow', desc: 'Rainbow', types: ['pooltone'], sequence: 5 }],
213
+ [45, { name: 'colorriver', desc: 'Color River', types: ['pooltone'], sequence: 6 }],
214
+ [46, { name: 'disco', desc: 'Disco', types: ['pooltone'], sequence: 7 }],
215
+ [47, { name: 'fourseasons', desc: 'Four Seasons', types: ['pooltone'], sequence: 8 }],
216
+ [48, { name: 'Party', desc: 'Party', types: ['pooltone'], sequence: 9 }],
217
+ [49, { name: 'sunwhite', desc: 'Sun White', types: ['pooltone'], sequence: 10 }],
218
+ [50, { name: 'red', desc: 'Red', types: ['pooltone'], sequence: 11 }],
219
+ [51, { name: 'green', desc: 'Green', types: ['pooltone'], sequence: 12 }],
220
+ [52, { name: 'blue', desc: 'Blue', types: ['pooltone'], sequence: 13 }],
221
+ [53, { name: 'greenblue', desc: 'Green-Blue', types: ['pooltone'], sequence: 14 }],
222
+ [54, { name: 'redgreen', desc: 'Red-Green', types: ['pooltone'], sequence: 15 }],
223
+ [55, { name: 'bluered', desc: 'Blue-red', types: ['pooltone'], sequence: 16 }],
213
224
  [255, { name: 'none', desc: 'None' }]
214
225
  ]);
215
226
  this.valueMaps.lightColors = new byteValueMap([
@@ -255,6 +266,12 @@ export class NixieBoard extends SystemBoard {
255
266
  [4, { name: 'spaCommand', desc: 'Spa Command', maxButtons: 10 }]
256
267
  ]);
257
268
  }
269
+ public async checkConfiguration() {
270
+ state.status = sys.board.valueMaps.controllerStatus.transform(0, 0);
271
+ state.emitControllerChange();
272
+ state.status = sys.board.valueMaps.controllerStatus.transform(1, 100);
273
+ state.emitControllerChange();
274
+ }
258
275
  public async initNixieBoard() {
259
276
  try {
260
277
  this.killStatusCheck();
@@ -275,8 +292,9 @@ export class NixieBoard extends SystemBoard {
275
292
  let type = typeof mod.type !== 'undefined' ? this.valueMaps.expansionBoards.transform(mod.type) : this.valueMaps.expansionBoards.transform(0);
276
293
  logger.info(`Initializing Nixie Control Panel for ${type.desc}`);
277
294
 
278
- sys.equipment.shared = type.shared;
279
- sys.equipment.dual = type.dual;
295
+ state.equipment.shared = sys.equipment.shared = type.shared;
296
+ state.equipment.dual = sys.equipment.dual = type.dual;
297
+ state.equipment.single = sys.equipment.single = sys.equipment.shared === false && sys.equipment.dual === false;
280
298
  sys.equipment.controllerFirmware = '1.0.0';
281
299
  mod.type = type.val;
282
300
  mod.part = type.part;
@@ -299,6 +317,7 @@ export class NixieBoard extends SystemBoard {
299
317
  state.equipment.model = type.desc;
300
318
  state.equipment.maxBodies = sys.equipment.maxBodies = type.bodies;
301
319
  let bodyUnits = sys.general.options.units === 0 ? 1 : 2;
320
+ sys.equipment.single = typeof type.single !== 'undefined' ? type.single : false;
302
321
 
303
322
  if (typeof state.temps.units === 'undefined' || state.temps.units < 0) state.temps.units = sys.general.options.units;
304
323
  if (type.bodies > 0) {
@@ -393,8 +412,10 @@ export class NixieBoard extends SystemBoard {
393
412
  state.cleanupState();
394
413
  logger.info(`${sys.equipment.model} control board initialized`);
395
414
  state.status = sys.board.valueMaps.controllerStatus.transform(1, 100);
415
+ state.mode = sys.board.valueMaps.panelModes.encode('auto');
396
416
  // At this point we should have the start of a board so lets check to see if we are ready or if we are stuck initializing.
397
- setTimeout(() => self.processStatusAsync(), 5000);
417
+ await setTimeout(5000);
418
+ await self.processStatusAsync();
398
419
  } catch (err) { state.status = 255; logger.error(`Error Initializing Nixie Control Panel ${err.message}`); }
399
420
  }
400
421
  public initValves() {
@@ -498,6 +519,7 @@ export class NixieFilterCommands extends FilterCommands {
498
519
  }
499
520
  }
500
521
  export class NixieSystemCommands extends SystemCommands {
522
+ protected _modeTimer: NodeJS.Timeout;
501
523
  public cancelDelay(): Promise<any> {
502
524
  delayMgr.cancelPumpValveDelays();
503
525
  delayMgr.cancelHeaterCooldownDelays();
@@ -528,11 +550,74 @@ export class NixieSystemCommands extends SystemCommands {
528
550
 
529
551
  } catch (err) { return logger.error(`Error setting Nixie Model: ${err.message}`); }
530
552
  }
553
+ public async setPanelModeAsync(data: any): Promise<any> {
554
+ let mode = sys.board.valueMaps.panelModes.findItem(data.mode);
555
+ let timeout = parseInt(data.timeout, 10);
556
+ if (typeof mode === 'undefined') return Promise.reject(new ServiceParameterError(`Invalid mode value cannot set mode`, 'setPanelModeAsync', 'mode', data.mode));
557
+ switch (mode.name) {
558
+ case 'timeout':
559
+ if (isNaN(timeout) || timeout <= 0) return Promise.reject(new ServiceParameterError(`Invalid timeout value cannot set mode`, 'setPanelModeAsync', 'timeout', data.timeout));
560
+ await this.initServiceMode(mode, timeout);
561
+ break;
562
+ case 'service':
563
+ await this.initServiceMode(mode);
564
+ break;
565
+ case 'auto':
566
+ // Ok we are switching back to auto.
567
+ // 1. Kill the timeout timer if it exists.
568
+ // 2. Set the mode to auto.
569
+ if (this._modeTimer) clearTimeout(this._modeTimer);
570
+ this._modeTimer = null;
571
+ state.mode = 0;
572
+ webApp.emitToClients('panelMode', { mode: mode, remaining: 0 });
573
+ break;
574
+ }
575
+ }
576
+ private checkServiceTimeout(mode: any, start: number, timeout: number, interval?: number) {
577
+ if (this._modeTimer) clearTimeout(this._modeTimer);
578
+ this._modeTimer = null;
579
+ // The timeout is in seconds so we will need to deal with that.
580
+ let elapsed = (new Date().getTime() - start) / 1000;
581
+ let remaining = timeout - elapsed;
582
+ logger.info(`Timeout: ${timeout} Elapsed: ${elapsed}`);
583
+ if (remaining > 0) {
584
+ webApp.emitToClients('panelMode', { mode: mode, remaining: remaining, elapsed: elapsed, timeout: timeout });
585
+ this._modeTimer = setTimeoutSync(() => { this.checkServiceTimeout(mode, start, timeout, interval || 1000); }, interval || 1000);
586
+ }
587
+ else {
588
+ webApp.emitToClients('panelMode', { mode: sys.board.valueMaps.panelModes.transform(0), remaining: 0 });
589
+ state.mode = 0;
590
+ }
591
+ }
592
+ public async initServiceMode(mode, timeout?: number) {
593
+ if (this._modeTimer) clearTimeout(this._modeTimer);
594
+ for (let i = 0; i < sys.circuits.length; i++) {
595
+ let circ = sys.circuits.getItemByIndex(i);
596
+ if (circ.master === 1) {
597
+ let cstate = state.circuits.getItemById(circ.id);
598
+ if (cstate.isOn) await sys.board.circuits.setCircuitStateAsync(circ.id, false, true);
599
+ }
600
+ }
601
+ delayMgr.clearAllDelays();
602
+ state.mode = mode.val;
603
+ // Shut everything down.
604
+ await ncp.setServiceModeAsync();
605
+ if (timeout > 0) {
606
+ let start = new Date().getTime();
607
+ this.checkServiceTimeout(mode, start, timeout, 1000);
608
+ webApp.emitToClients('panelMode', { mode: mode, remaining: timeout, elapsed: 0, timeout: timeout });
609
+ }
610
+ else {
611
+ webApp.emitToClients('panelMode', { mode: mode });
612
+ }
613
+
614
+ }
531
615
  }
532
616
  export class NixieCircuitCommands extends CircuitCommands {
533
617
  // This is our poll loop for circuit relay states.
534
618
  public async syncCircuitRelayStates() {
535
619
  try {
620
+ if (state.mode !== 0) return;
536
621
  for (let i = 0; i < sys.circuits.length; i++) {
537
622
  // Run through all the controlled circuits to see whether they should be triggered or not.
538
623
  let circ = sys.circuits.getItemByIndex(i);
@@ -557,6 +642,7 @@ export class NixieCircuitCommands extends CircuitCommands {
557
642
  let circuit: ICircuit = sys.circuits.getInterfaceById(id, false, { isActive: false });
558
643
  if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError(`Circuit or Feature id ${id} not valid`, id, 'Circuit'));
559
644
  let circ = state.circuits.getInterfaceById(id, circuit.isActive !== false);
645
+ if (state.mode !== 0) return circ;
560
646
  if (circ.stopDelay) {
561
647
  // Send this off so that the relays are properly set. In the end we cannot change right now. If this
562
648
  // happens to be a body circuit then the relay state will be skipped anyway.
@@ -672,6 +758,8 @@ export class NixieCircuitCommands extends CircuitCommands {
672
758
  let cstate = state.circuits.getItemById(id);
673
759
  let circuit = sys.circuits.getItemById(id);
674
760
  let bstate = state.temps.bodies.getBodyByCircuitId(id);
761
+ if (cstate.isOn === val) return; // If body is already in desired state, don't do anything.
762
+ // https://github.com/tagyoureit/nodejs-poolController/issues/361#issuecomment-1186087763
675
763
  if (val) {
676
764
  // We are turning on a body circuit.
677
765
  logger.verbose(`Turning on a body circuit ${bstate.name}`);
@@ -781,7 +869,6 @@ export class NixieCircuitCommands extends CircuitCommands {
781
869
  if (sys.equipment.shared && bstate.id === 2) await this.turnOffDrainCircuits(ignoreDelays);
782
870
  logger.verbose(`Turning off a body circuit ${circuit.name}`);
783
871
  if (cstate.isOn) {
784
-
785
872
  // Check to see if we have any heater cooldown delays that need to take place.
786
873
  let heaters = sys.board.heaters.getHeatersByCircuitId(circuit.id);
787
874
  let cooldownTime = 0;
@@ -799,6 +886,9 @@ export class NixieCircuitCommands extends CircuitCommands {
799
886
  bstate.isOn = val;
800
887
  }
801
888
  }
889
+ else {
890
+ bstate.isOn = val;
891
+ }
802
892
  }
803
893
  return cstate;
804
894
  } catch (err) { logger.error(`Nixie: Error setBodyCircuitStateAsync ${err.message}`); return Promise.reject(new BoardProcessError(`Nixie: Error setBodyCircuitStateAsync ${err.message}`, 'setBodyCircuitStateAsync')); }
@@ -875,7 +965,7 @@ export class NixieCircuitCommands extends CircuitCommands {
875
965
  logger.verbose(`Turning ${val ? 'on' : 'off'} a drain circuit ${cstate.name}`);
876
966
  await ncp.circuits.setCircuitStateAsync(cstate, val);
877
967
  return cstate;
878
- } catch (err) { logger.error(`Nixie: Error setSpillwayCircuitStateAsync ${err.message}`); return Promise.reject(new BoardProcessError(`Nixie: Error setBodyCircuitStateAsync ${err.message}`, 'setBodyCircuitStateAsync')); }
968
+ } catch (err) { logger.error(`Nixie: Error setDrainCircuitStateAsync ${err.message}`); return Promise.reject(new BoardProcessError(`Nixie: Error setDrainCircuitStateAsync ${err.message}`, 'setDrainCircuitStateAsync')); }
879
969
  }
880
970
 
881
971
  public toggleCircuitStateAsync(id: number): Promise<ICircuitState> {
@@ -888,12 +978,12 @@ export class NixieCircuitCommands extends CircuitCommands {
888
978
  return Promise.resolve(state.lightGroups.getItemById(id));
889
979
  }
890
980
  let cstate = state.circuits.getItemById(id);
981
+ if (state.mode !== 0) return cstate;
891
982
  let circ = sys.circuits.getItemById(id);
892
983
  let thm = sys.board.valueMaps.lightThemes.findItem(theme);
893
984
  if (typeof thm !== 'undefined' && typeof thm.sequence !== 'undefined' && circ.master === 1) {
894
985
  logger.info(`Setting light theme for ${circ.name} to ${thm.name} [${thm.sequence}]`);
895
- await sys.board.circuits.setCircuitStateAsync(id, true);
896
- await ncp.circuits.sendOnOffSequenceAsync(id, thm.sequence);
986
+ await ncp.circuits.setLightThemeAsync(id, thm);
897
987
  }
898
988
  cstate.lightingTheme = theme;
899
989
  return Promise.resolve(cstate as ICircuitState);
@@ -1185,7 +1275,7 @@ export class NixieCircuitCommands extends CircuitCommands {
1185
1275
  await sys.board.circuits.setLightThemeAsync(c.circuit, theme);
1186
1276
  await sys.board.circuits.setCircuitStateAsync(c.circuit, false);
1187
1277
  }
1188
- await utils.sleep(5000);
1278
+ await setTimeout(5000);
1189
1279
  // Turn the circuits all back on again.
1190
1280
  for (let i = 0; i < grp.circuits.length; i++) {
1191
1281
  let c = grp.circuits.getItemByIndex(i);
@@ -1231,6 +1321,7 @@ export class NixieCircuitCommands extends CircuitCommands {
1231
1321
  //}
1232
1322
  public async sequenceLightGroupAsync(id: number, operation: string): Promise<LightGroupState> {
1233
1323
  let sgroup = state.lightGroups.getItemById(id);
1324
+ if (state.mode !== 0) return sgroup;
1234
1325
  let grp = sys.lightGroups.getItemById(id);
1235
1326
  let nop = sys.board.valueMaps.circuitActions.getValue(operation);
1236
1327
  try {
@@ -1242,7 +1333,7 @@ export class NixieCircuitCommands extends CircuitCommands {
1242
1333
  let c = grp.circuits.getItemByIndex(i);
1243
1334
  await sys.board.circuits.setCircuitStateAsync(c.circuit, false);
1244
1335
  }
1245
- await utils.sleep(10000);
1336
+ await setTimeout(10000);
1246
1337
  // Turn the circuits all back on again.
1247
1338
  for (let i = 0; i < grp.circuits.length; i++) {
1248
1339
  let c = grp.circuits.getItemByIndex(i);
@@ -1252,12 +1343,12 @@ export class NixieCircuitCommands extends CircuitCommands {
1252
1343
  case 'colorset':
1253
1344
  sgroup.action = nop;
1254
1345
  sgroup.emitEquipmentChange();
1255
- await utils.sleep(5000);
1346
+ await setTimeout(5000);
1256
1347
  break;
1257
1348
  case 'colorswim':
1258
1349
  sgroup.action = nop;
1259
1350
  sgroup.emitEquipmentChange();
1260
- await utils.sleep(5000);
1351
+ await setTimeout(5000);
1261
1352
  break;
1262
1353
  }
1263
1354
  return sgroup;
@@ -1268,18 +1359,29 @@ export class NixieCircuitCommands extends CircuitCommands {
1268
1359
  let grp = sys.circuitGroups.getItemById(id, false, { isActive: false });
1269
1360
  if (grp.dataName !== 'circuitGroupConfig') return await sys.board.circuits.setLightGroupStateAsync(id, val);
1270
1361
  let gstate = state.circuitGroups.getItemById(grp.id, grp.isActive !== false);
1362
+ if (state.mode !== 0) return gstate;
1271
1363
  let circuits = grp.circuits.toArray();
1272
1364
  sys.board.circuits.setEndTime(sys.circuits.getInterfaceById(gstate.id), gstate, val);
1273
1365
  gstate.isOn = val;
1274
1366
  let arr = [];
1275
1367
  for (let i = 0; i < circuits.length; i++) {
1276
- let circuit = circuits[i];
1368
+ let circuit:CircuitGroupCircuit = circuits[i];
1277
1369
  // The desiredState will be as follows.
1278
1370
  // 1 = on, 2 = off, 3 = ignore.
1279
1371
  let cval = true;
1280
1372
  if (circuit.desiredState === 1) cval = val ? true : false;
1281
1373
  else if (circuit.desiredState === 2) cval = val ? false : true;
1282
- else continue;
1374
+ else if (circuit.desiredState === 3) continue;
1375
+ else if (circuit.desiredState === 4){
1376
+ // on/ignore
1377
+ if (val) cval = true;
1378
+ else continue;
1379
+ }
1380
+ else if (circuit.desiredState === 5){
1381
+ // off/ignore
1382
+ if (val) cval = false;
1383
+ else continue;
1384
+ }
1283
1385
  await sys.board.circuits.setCircuitStateAsync(circuit.circuit, cval);
1284
1386
  //arr.push(sys.board.circuits.setCircuitStateAsync(circuit.circuit, cval));
1285
1387
  }
@@ -1293,6 +1395,7 @@ export class NixieCircuitCommands extends CircuitCommands {
1293
1395
  let grp = sys.circuitGroups.getItemById(id, false, { isActive: false });
1294
1396
  if (grp.dataName === 'circuitGroupConfig') return await sys.board.circuits.setCircuitGroupStateAsync(id, val);
1295
1397
  let gstate = state.lightGroups.getItemById(grp.id, grp.isActive !== false);
1398
+ if (state.mode !== 0) return gstate;
1296
1399
  let circuits = grp.circuits.toArray();
1297
1400
  sys.board.circuits.setEndTime(grp, gstate, val);
1298
1401
  gstate.isOn = val;
@@ -1362,6 +1465,7 @@ export class NixieFeatureCommands extends FeatureCommands {
1362
1465
  let feature = sys.features.getItemById(id);
1363
1466
  let fstate = state.features.getItemById(feature.id, feature.isActive !== false);
1364
1467
  feature.master = 1;
1468
+ if (state.mode !== 0) return fstate;
1365
1469
  let ftype = sys.board.valueMaps.featureFunctions.getName(feature.type);
1366
1470
  if(val && !fstate.isOn) sys.board.circuits.setEndTime(feature, fstate, val);
1367
1471
  switch (ftype) {
@@ -1441,11 +1545,11 @@ export class NixieFeatureCommands extends FeatureCommands {
1441
1545
  if (!ignoreDelays && sys.general.options.pumpDelay && sys.general.options.valveDelayTime > 0) sys.board.pumps.setPumpValveDelays([id, 1, 6]);
1442
1546
  }
1443
1547
  }
1444
- logger.verbose(`Turning ${val ? 'on' : 'off'} a spa drain circuit ${cstate.name}`);
1548
+ logger.verbose(`Turning ${val ? 'on' : 'off'} a spa drain feature ${cstate.name}`);
1445
1549
  cstate.isOn = val;
1446
1550
  }
1447
1551
  return cstate;
1448
- } catch (err) { logger.error(`Nixie: Error setSpillwayCircuitStateAsync ${err.message}`); return Promise.reject(new BoardProcessError(`Nixie: Error setBodyCircuitStateAsync ${err.message}`, 'setBodyCircuitStateAsync')); }
1552
+ } catch (err) { logger.error(`Nixie: Error setDrainFeatureStateAsync ${err.message}`); return Promise.reject(new BoardProcessError(`Nixie: Error setDrainFeatureStateAsync ${err.message}`, 'setDrainFeatureStateAsync')); }
1449
1553
  }
1450
1554
 
1451
1555
  public async toggleFeatureStateAsync(id: number): Promise<ICircuitState> {
@@ -1467,10 +1571,14 @@ export class NixieFeatureCommands extends FeatureCommands {
1467
1571
  for (let j = 0; j < circuits.length && bIsOn === true; j++) {
1468
1572
  let circuit: CircuitGroupCircuit = grp.circuits.getItemByIndex(j);
1469
1573
  let cstate = state.circuits.getInterfaceById(circuit.circuit);
1470
- if (circuit.desiredState === 1) { // The circuit should be on.
1574
+ // RSG: desiredState for Nixie is 1=on, 2=off, 3=ignore
1575
+ if (circuit.desiredState === 1 || circuit.desiredState === 4) {
1576
+ // The circuit should be on if the value is 1.
1577
+ // If we are on 'ignore' we should still only treat the circuit as
1578
+ // desiredstate = 1.
1471
1579
  if (!utils.makeBool(cstate.isOn)) bIsOn = false;
1472
1580
  }
1473
- else if (circuit.desiredState === 0) { // The circuit should be off.
1581
+ else if (circuit.desiredState === 2 || circuit.desiredState === 5) { // The circuit should be off.
1474
1582
  if (utils.makeBool(cstate.isOn)) bIsOn = false;
1475
1583
  }
1476
1584
  }
@@ -1521,23 +1629,28 @@ export class NixiePumpCommands extends PumpCommands {
1521
1629
  let pstate = state.pumps.getItemById(pump.id);
1522
1630
  let pt = sys.board.valueMaps.pumpTypes.get(pump.type);
1523
1631
 
1524
- // [1, { name: 'ss', desc: 'Single Speed', maxCircuits: 0, hasAddress: false, hasBody: true, maxRelays: 1 }],
1632
+ // Old - [1, { name: 'ss', desc: 'Single Speed', maxCircuits: 0, hasAddress: false, hasBody: true, maxRelays: 1 }],
1633
+ // New 07/22 - [1, { name: 'ss', desc: 'Single Speed', maxCircuits: 8, hasAddress: false, hasBody: false, maxRelays: 1, relays: [{ id: 1, name: 'Pump On/Off' }]}],
1525
1634
  // [2, { name: 'ds', desc: 'Two Speed', maxCircuits: 8, hasAddress: false, hasBody: false, maxRelays: 2 }],
1526
1635
  // [3, { name: 'vs', desc: 'Intelliflo VS', maxPrimingTime: 6, minSpeed: 450, maxSpeed: 3450, maxCircuits: 8, hasAddress: true }],
1527
1636
  // [4, { name: 'vsf', desc: 'Intelliflo VSF', minSpeed: 450, maxSpeed: 3450, minFlow: 15, maxFlow: 130, maxCircuits: 8, hasAddress: true }],
1528
1637
  // [5, { name: 'vf', desc: 'Intelliflo VF', minFlow: 15, maxFlow: 130, maxCircuits: 8, hasAddress: true }],
1529
1638
  // [100, { name: 'sf', desc: 'SuperFlo VS', hasAddress: false, maxCircuits: 8, maxRelays: 4, equipmentMaster: 1 }]
1530
1639
  switch (pt.name) {
1531
- case 'ss':
1532
- // If a single speed pump is designated it will be the filter pump but we need to map any settings
1533
- // to bodies.
1534
- console.log(`Body: ${pump.body} Pump: ${pump.name} Pool: ${circuitIds.includes(6)} `);
1535
- if ((pump.body === 255 && (circuitIds.includes(6) || circuitIds.includes(1))) ||
1640
+ case 'ss':{
1641
+ // rsg - ss now has circuit assignments. will check but still leave existing code
1642
+ if (pt.maxCircuits === 0 || typeof pump.body !== 'undefined'){
1643
+ // If a single speed pump is designated it will be the filter pump but we need to map any settings
1644
+ // to bodies.
1645
+ console.log(`Body: ${pump.body} Pump: ${pump.name} Pool: ${circuitIds.includes(6)} `);
1646
+ if ((pump.body === 255 && (circuitIds.includes(6) || circuitIds.includes(1))) ||
1536
1647
  (pump.body === 0 && circuitIds.includes(6)) ||
1537
1648
  (pump.body === 101 && circuitIds.includes(1))) {
1538
- delayMgr.setPumpValveDelay(pstate);
1649
+ delayMgr.setPumpValveDelay(pstate);
1650
+ }
1651
+ break;
1539
1652
  }
1540
- break;
1653
+ }
1541
1654
  default:
1542
1655
  if (pt.maxCircuits > 0) {
1543
1656
  for (let j = 0; j < pump.circuits.length; j++) {
@@ -1650,6 +1763,7 @@ export class NixieHeaterCommands extends HeaterCommands {
1650
1763
  let gasHeaterInstalled = htypes.gas > 0;
1651
1764
  let ultratempInstalled = htypes.ultratemp > 0;
1652
1765
  let mastertempInstalled = htypes.mastertemp > 0;
1766
+ let hybridInstalled = htypes.hybrid > 0;
1653
1767
  // The heat mode options are
1654
1768
  // 1 = Off
1655
1769
  // 2 = Gas Heater
@@ -1669,8 +1783,37 @@ export class NixieHeaterCommands extends HeaterCommands {
1669
1783
  // 3 = Solar Heater
1670
1784
  // 4 = Solar Preferred
1671
1785
  // 5 = Heat Pump
1672
-
1673
1786
  if (sys.heaters.length > 0) sys.board.valueMaps.heatSources = new byteValueMap([[1, { name: 'off', desc: 'Off' }]]);
1787
+ sys.board.valueMaps.heatModes = new byteValueMap([[1, { name: 'off', desc: 'Off' }]]);
1788
+ if (hybridInstalled) {
1789
+ sys.board.valueMaps.heatModes.merge([
1790
+ [7, { name: 'hybheat', desc: 'Gas Only' }],
1791
+ [8, { name: 'hybheatpump', desc: 'Heat Pump Only' }],
1792
+ [9, { name: 'hybhybrid', desc: 'Hybrid' }],
1793
+ [10, { name: 'hybdual', desc: 'Dual Heat' }]
1794
+ ]);
1795
+ sys.board.valueMaps.heatSources.merge([
1796
+ [7, { name: 'hybheat', desc: 'Gas Only' }],
1797
+ [8, { name: 'hybheatpump', desc: 'Heat Pump Only' }],
1798
+ [9, { name: 'hybhybrid', desc: 'Hybrid' }],
1799
+ [10, { name: 'hybdual', desc: 'Dual Heat' }]
1800
+ ]);
1801
+ // RKS: 08-24-22 The heat modes and sources for the hybrid heater are unique. Turns out that
1802
+ // these should be available if there is a gas heater ganged to the body as well.
1803
+ // types cannot be ignored since they are specific to the heater.
1804
+ //sys.board.valueMaps.heatModes.merge([
1805
+ // [9, { name: 'heatpump', desc: 'Heat Pump' }],
1806
+ // [2, { name: 'heater', desc: 'Heater' }],
1807
+ // [25, { name: 'heatpumppref', desc: 'Hybrid' }],
1808
+ // [26, { name: 'dual', desc: 'Dual Heat' }]
1809
+ //]);
1810
+ //sys.board.valueMaps.heatSources.merge([
1811
+ // [2, { name: 'heater', desc: 'Gas Heat' }],
1812
+ // [9, { name: 'heatpump', desc: 'Heat Pump' }],
1813
+ // [20, { name: 'heatpumppref', desc: 'Hybrid' }],
1814
+ // [21, { name: 'dual', desc: 'Dual Heat' }]
1815
+ //]);
1816
+ }
1674
1817
  if (gasHeaterInstalled) sys.board.valueMaps.heatSources.merge([[2, { name: 'heater', desc: 'Heater' }]]);
1675
1818
  if (mastertempInstalled) sys.board.valueMaps.heatSources.merge([[11, { name: 'mtheater', desc: 'MasterTemp' }]]);
1676
1819
  if (solarInstalled && (gasHeaterInstalled || heatPumpInstalled)) sys.board.valueMaps.heatSources.merge([[3, { name: 'solar', desc: 'Solar Only', hasCoolSetpoint: htypes.hasCoolSetpoint }], [4, { name: 'solarpref', desc: 'Solar Preferred', hasCoolSetpoint: htypes.hasCoolSetpoint }]]);
@@ -1681,7 +1824,7 @@ export class NixieHeaterCommands extends HeaterCommands {
1681
1824
  else if (ultratempInstalled) sys.board.valueMaps.heatSources.merge([[5, { name: 'ultratemp', desc: 'UltraTemp', hasCoolSetpoint: htypes.hasCoolSetpoint }]]);
1682
1825
  sys.board.valueMaps.heatSources.merge([[0, { name: 'nochange', desc: 'No Change' }]]);
1683
1826
 
1684
- sys.board.valueMaps.heatModes = new byteValueMap([[1, { name: 'off', desc: 'Off' }]]);
1827
+
1685
1828
  if (gasHeaterInstalled) sys.board.valueMaps.heatModes.merge([[2, { name: 'heater', desc: 'Heater' }]]);
1686
1829
  if (mastertempInstalled) sys.board.valueMaps.heatModes.merge([[11, { name: 'mtheater', desc: 'MasterTemp' }]]);
1687
1830
  if (solarInstalled && (gasHeaterInstalled || heatPumpInstalled || mastertempInstalled)) sys.board.valueMaps.heatModes.merge([[3, { name: 'solar', desc: 'Solar Only' }], [4, { name: 'solarpref', desc: 'Solar Preferred' }]]);