nodejs-poolcontroller 8.3.0 → 8.4.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 (107) hide show
  1. package/.eslintrc.json +36 -36
  2. package/.github/ISSUE_TEMPLATE/1-bug-report.yml +84 -84
  3. package/.github/ISSUE_TEMPLATE/2-docs.md +12 -12
  4. package/.github/ISSUE_TEMPLATE/3-proposal.md +28 -28
  5. package/.github/ISSUE_TEMPLATE/config.yml +8 -8
  6. package/.github/copilot-instructions.md +63 -63
  7. package/.github/workflows/ghcr-publish.yml +67 -67
  8. package/157_issues.md +101 -0
  9. package/AGENTS.md +613 -0
  10. package/CONTRIBUTING.md +74 -74
  11. package/Changelog +292 -284
  12. package/Dockerfile +62 -62
  13. package/Gruntfile.js +40 -40
  14. package/LICENSE +661 -661
  15. package/README.md +329 -309
  16. package/anslq25/MessagesMock.ts +221 -221
  17. package/anslq25/boards/MockBoardFactory.ts +49 -49
  18. package/anslq25/boards/MockEasyTouchBoard.ts +696 -696
  19. package/anslq25/boards/MockSystemBoard.ts +216 -216
  20. package/anslq25/chemistry/MockChlorinator.ts +98 -98
  21. package/anslq25/pumps/MockPump.ts +83 -83
  22. package/app.ts +115 -115
  23. package/config/Config.ts +0 -0
  24. package/config/VersionCheck.ts +0 -0
  25. package/controller/Constants.ts +809 -805
  26. package/controller/Equipment.ts +2737 -2664
  27. package/controller/Errors.ts +181 -181
  28. package/controller/Lockouts.ts +549 -549
  29. package/controller/State.ts +3746 -3701
  30. package/controller/boards/AquaLinkBoard.ts +1175 -1003
  31. package/controller/boards/BoardFactory.ts +53 -53
  32. package/controller/boards/EasyTouchBoard.ts +3246 -3202
  33. package/controller/boards/IntelliCenterBoard.ts +4581 -3899
  34. package/controller/boards/IntelliComBoard.ts +69 -69
  35. package/controller/boards/IntelliTouchBoard.ts +382 -382
  36. package/controller/boards/NixieBoard.ts +1947 -1944
  37. package/controller/boards/SunTouchBoard.ts +401 -400
  38. package/controller/boards/SystemBoard.ts +5303 -5268
  39. package/controller/comms/Comms.ts +1278 -1255
  40. package/controller/comms/ScreenLogic.ts +1665 -1665
  41. package/controller/comms/messages/Messages.ts +1627 -1406
  42. package/controller/comms/messages/config/ChlorinatorMessage.ts +5 -0
  43. package/controller/comms/messages/config/CircuitGroupMessage.ts +0 -0
  44. package/controller/comms/messages/config/CircuitMessage.ts +0 -0
  45. package/controller/comms/messages/config/ConfigMessage.ts +6 -0
  46. package/controller/comms/messages/config/CoverMessage.ts +0 -0
  47. package/controller/comms/messages/config/CustomNameMessage.ts +31 -31
  48. package/controller/comms/messages/config/EquipmentMessage.ts +250 -210
  49. package/controller/comms/messages/config/ExternalMessage.ts +1051 -903
  50. package/controller/comms/messages/config/FeatureMessage.ts +0 -0
  51. package/controller/comms/messages/config/GeneralMessage.ts +65 -0
  52. package/controller/comms/messages/config/HeaterMessage.ts +0 -0
  53. package/controller/comms/messages/config/IntellichemMessage.ts +0 -0
  54. package/controller/comms/messages/config/OptionsMessage.ts +207 -174
  55. package/controller/comms/messages/config/PumpMessage.ts +427 -421
  56. package/controller/comms/messages/config/RemoteMessage.ts +0 -0
  57. package/controller/comms/messages/config/ScheduleMessage.ts +401 -390
  58. package/controller/comms/messages/config/SecurityMessage.ts +37 -13
  59. package/controller/comms/messages/config/ValveMessage.ts +0 -0
  60. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +0 -0
  61. package/controller/comms/messages/status/EquipmentStateMessage.ts +940 -822
  62. package/controller/comms/messages/status/HeaterStateMessage.ts +147 -135
  63. package/controller/comms/messages/status/IntelliChemStateMessage.ts +448 -448
  64. package/controller/comms/messages/status/IntelliValveStateMessage.ts +36 -36
  65. package/controller/comms/messages/status/NeptuneModbusStateMessage.ts +217 -0
  66. package/controller/comms/messages/status/PumpStateMessage.ts +0 -0
  67. package/controller/comms/messages/status/RegalModbusStateMessage.ts +410 -410
  68. package/controller/comms/messages/status/VersionMessage.ts +152 -41
  69. package/controller/nixie/Nixie.ts +173 -173
  70. package/controller/nixie/NixieEquipment.ts +104 -104
  71. package/controller/nixie/bodies/Body.ts +120 -120
  72. package/controller/nixie/bodies/Filter.ts +135 -135
  73. package/controller/nixie/chemistry/ChemController.ts +2756 -2724
  74. package/controller/nixie/chemistry/ChemDoser.ts +806 -806
  75. package/controller/nixie/chemistry/Chlorinator.ts +367 -367
  76. package/controller/nixie/circuits/Circuit.ts +478 -478
  77. package/controller/nixie/heaters/Heater.ts +843 -834
  78. package/controller/nixie/pumps/Pump.ts +1336 -1193
  79. package/controller/nixie/schedules/Schedule.ts +401 -401
  80. package/controller/nixie/valves/Valve.ts +170 -170
  81. package/defaultConfig.json +352 -352
  82. package/docker-compose.yml +32 -31
  83. package/logger/DataLogger.ts +448 -448
  84. package/logger/Logger.ts +459 -436
  85. package/package.json +58 -58
  86. package/sendSocket.js +32 -32
  87. package/tsconfig.json +26 -25
  88. package/types/express-multer.d.ts +32 -32
  89. package/web/Server.ts +1939 -1927
  90. package/web/bindings/aqualinkD.json +559 -559
  91. package/web/bindings/influxDB.json +1066 -1066
  92. package/web/bindings/mqtt.json +721 -721
  93. package/web/bindings/mqttAlt.json +746 -746
  94. package/web/bindings/rulesManager.json +54 -54
  95. package/web/bindings/smartThings-Hubitat.json +31 -31
  96. package/web/bindings/valveRelays.json +20 -20
  97. package/web/bindings/vera.json +25 -25
  98. package/web/interfaces/baseInterface.ts +188 -188
  99. package/web/interfaces/httpInterface.ts +148 -148
  100. package/web/interfaces/influxInterface.ts +283 -283
  101. package/web/interfaces/mqttInterface.ts +695 -695
  102. package/web/interfaces/ruleInterface.ts +101 -87
  103. package/web/services/config/Config.ts +1212 -1053
  104. package/web/services/config/ConfigSocket.ts +0 -0
  105. package/web/services/state/State.ts +21 -0
  106. package/web/services/state/StateSocket.ts +28 -0
  107. package/web/services/utilities/Utilities.ts +233 -233
@@ -1,401 +1,402 @@
1
- /* nodejs-poolController. An application to control pool equipment.
2
- Copyright (C) 2016, 2017, 2018, 2019, 2020, 2021, 2022.
3
- Russell Goldin, tagyoureit. russ.goldin@gmail.com
4
-
5
- This program is free software: you can redistribute it and/or modify
6
- it under the terms of the GNU Affero General Public License as
7
- published by the Free Software Foundation, either version 3 of the
8
- License, or (at your option) any later version.
9
-
10
- This program is distributed in the hope that it will be useful,
11
- but WITHOUT ANY WARRANTY; without even the implied warranty of
12
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
- GNU Affero General Public License for more details.
14
-
15
- You should have received a copy of the GNU Affero General Public License
16
- along with this program. If not, see <http://www.gnu.org/licenses/>.
17
- */
18
- import * as extend from 'extend';
19
- import { EventEmitter } from 'events';
20
- import { EasyTouchBoard, TouchConfigQueue, GetTouchConfigCategories, TouchCircuitCommands } from './EasyTouchBoard';
21
- import { sys, PoolSystem, Circuit, ICircuit } from '../Equipment';
22
- import { byteValueMap, EquipmentIdRange } from './SystemBoard';
23
- import { state, ICircuitState } from '../State';
24
- import { logger } from '../../logger/Logger';
25
- import { conn } from '../comms/Comms';
26
- import { Outbound } from "../comms/messages/Messages";
27
- import { InvalidEquipmentIdError } from "../Errors";
28
- import { utils } from "../Constants";
29
-
30
- export class SunTouchBoard extends EasyTouchBoard {
31
- constructor(system: PoolSystem) {
32
- super(system); // graph chain to EasyTouchBoard constructor.
33
- this.valueMaps.expansionBoards = new byteValueMap([
34
- // 33 added per #1108;
35
- [33, { name: 'stshared', part: '520820', desc: 'Pool and Spa controller', bodies: 2, valves: 4, circuits: 5, single: false, shared: true, dual: false, features: 4, chlorinators: 1, chemControllers: 1 }],
36
- [41, { name: 'stshared', part: '520820', desc: 'Pool and Spa controller', bodies: 2, valves: 4, circuits: 5, single: false, shared: true, dual: false, features: 4, chlorinators: 1, chemControllers: 1 }],
37
- [40, { name: 'stsingle', part: '520819', desc: 'Pool or Spa controller', bodies: 2, valves: 4, circuits: 5, single: true, shared: true, dual: false, features: 4, chlorinators: 1, chemControllers: 1 }]
38
- ]);
39
- this._statusInterval = -1;
40
- this.equipmentIds.circuits = new EquipmentIdRange(1, 6);
41
- this.equipmentIds.features = new EquipmentIdRange(7, 10);
42
- this.equipmentIds.virtualCircuits = new EquipmentIdRange(128, 136);
43
- this.equipmentIds.circuitGroups = new EquipmentIdRange(192, function () { return this.start + sys.equipment.maxCircuitGroups - 1; });
44
- this.equipmentIds.circuits.start = 1;
45
- this.equipmentIds.circuits.isInRange = (id: number) => { return [1, 2, 3, 4, 6].includes(id); };
46
- this.equipmentIds.features.isInRange = (id: number) => { return [7, 8, 9, 10].includes(id); };
47
- if (typeof sys.configVersion.equipment === 'undefined') { sys.configVersion.equipment = 0; }
48
- this.valueMaps.heatSources = new byteValueMap([
49
- [0, { name: 'off', desc: 'Off' }],
50
- [32, { name: 'nochange', desc: 'No Change' }]
51
- ]);
52
- this.valueMaps.heatStatus = new byteValueMap([
53
- [0, { name: 'off', desc: 'Off' }],
54
- [1, { name: 'heater', desc: 'Heater' }],
55
- [2, { name: 'cooling', desc: 'Cooling' }],
56
- [3, { name: 'solar', desc: 'Solar' }],
57
- [4, { name: 'hpheat', desc: 'Heatpump' }],
58
- [5, { name: 'dual', desc: 'Dual' }]
59
- ]);
60
- this.valueMaps.circuitFunctions = new byteValueMap([
61
- [0, { name: 'generic', desc: 'Generic' }],
62
- [1, { name: 'spa', desc: 'Spa', hasHeatSource: true, body: 2 }],
63
- [2, { name: 'pool', desc: 'Pool', hasHeatSource: true, body: 1 }],
64
- [5, { name: 'mastercleaner', desc: 'Master Cleaner', body: 1 }],
65
- [7, { name: 'light', desc: 'Light', isLight: true }],
66
- [9, { name: 'samlight', desc: 'SAM Light', isLight: true }],
67
- [10, { name: 'sallight', desc: 'SAL Light', isLight: true }],
68
- [11, { name: 'photongen', desc: 'Photon Gen', isLight: true }],
69
- [12, { name: 'colorwheel', desc: 'Color Wheel', isLight: true }],
70
- [13, { name: 'valve', desc: 'Valve' }],
71
- [14, { name: 'spillway', desc: 'Spillway' }],
72
- [15, { name: 'floorcleaner', desc: 'Floor Cleaner', body: 1 }], // This circuit function does not seem to exist in IntelliTouch.
73
- [19, { name: 'notused', desc: 'Not Used' }],
74
- [63, { name: 'cleaner', desc: 'Cleaner' }],
75
-
76
- ]);
77
- this.valueMaps.virtualCircuits = new byteValueMap([
78
- [20, { name: 'solar', desc: 'Solar', assignableToPumpCircuit: true }],
79
- [129, { name: 'poolspa', desc: 'Pool/Spa' }],
80
- [130, { name: 'poolHeater', desc: 'Pool Heater', assignableToPumpCircuit: true }],
81
- [131, { name: 'spaHeater', desc: 'Spa Heater', assignableToPumpCircuit: true }],
82
- [132, { name: 'freeze', desc: 'Freeze', assignableToPumpCircuit: true }],
83
- [258, { name: 'anyHeater', desc: 'Any Heater' }]
84
- ]);
85
- this.valueMaps.circuitNames = new byteValueMap([
86
- [3, { name: 'aux1', desc: 'AUX 1' }],
87
- [4, { name: 'aux2', desc: 'AUX 2' }],
88
- [5, { name: 'aux3', desc: 'AUX 3' }],
89
- [6, { name: 'feature1', desc: 'FEATURE 1' }],
90
- [7, { name: 'feature2', desc: 'FEATURE 2' }],
91
- [8, { name: 'feature3', desc: 'FEATURE 3' }],
92
- [9, { name: 'feature4', desc: 'FEATURE 4' }],
93
- [61, { name: 'pool', desc: 'Pool' }],
94
- [72, { name: 'spa', desc: 'Spa' }]
95
- ]);
96
- this._configQueue = new SunTouchConfigQueue();
97
- }
98
- public initExpansionModules(byte1: number, byte2: number) {
99
- console.log(`Pentair SunTouch System Detected!`);
100
- sys.equipment.model = 'Suntouch';
101
-
102
- // Initialize the installed personality board.
103
- let mt = this.valueMaps.expansionBoards.transform(byte1); // Only have one example of SunTouch and it is a single body system (40).
104
- let mod = sys.equipment.modules.getItemById(0, true);
105
- if (mod.name !== mt.name) {
106
- logger.info(`Clearing SunTouch configuration...`);
107
- sys.bodies.removeItemById(1);
108
- sys.bodies.removeItemById(2);
109
- sys.bodies.removeItemById(3);
110
- sys.bodies.removeItemById(4);
111
- sys.circuits.clear(0);
112
- sys.circuits.removeItemById(1);
113
- sys.circuits.removeItemById(6);
114
- sys.features.clear(0);
115
- state.circuits.clear();
116
- state.temps.clear();
117
- sys.filters.clear(0);
118
- state.filters.clear();
119
- }
120
- mod.name = mt.name;
121
- mod.desc = mt.desc;
122
- mod.type = byte1;
123
- mod.part = mt.part;
124
- let eq = sys.equipment;
125
- let md = mod.get();
126
- eq.maxBodies = md.bodies = typeof mt.bodies !== 'undefined' ? mt.bodies : mt.shared ? 2 : 1;
127
- eq.maxCircuits = md.circuits = typeof mt.circuits !== 'undefined' ? mt.circuits : 3;
128
- eq.maxFeatures = md.features = typeof mt.features !== 'undefined' ? mt.features : 0
129
- eq.maxValves = md.valves = typeof mt.valves !== 'undefined' ? mt.valves : 2;
130
- eq.maxPumps = md.maxPumps = typeof mt.pumps !== 'undefined' ? mt.pumps : 2;
131
- eq.shared = mt.shared || false;
132
- eq.dual = mt.dual || false;
133
- eq.single = mt.single || false;
134
- eq.maxChlorinators = md.chlorinators = 1;
135
- eq.maxChemControllers = md.chemControllers = 1;
136
- eq.maxCustomNames = 0;
137
- eq.maxSchedules = 6;
138
- if (sys.equipment.single) {
139
- sys.board.valueMaps.circuitNames.merge([[61, { name: 'pool', desc: 'LO-Temp' }], [72, { name: 'spa', desc: 'HI-Temp' }]]);
140
- sys.board.valueMaps.circuitFunctions.merge([[1, { name: 'pool', desc: 'LO-Temp', hasHeatSource: true }], [2, { name: 'spa', desc: 'HI-Temp', hasHeatSource: true }]]);
141
- sys.board.valueMaps.virtualCircuits.merge([[130, { name: 'poolHeater', desc: 'LO-Temp Heater' }], [131, { name: 'spaHeater', desc: 'HI-Temp Heater' }]]);
142
- sys.board.valueMaps.bodyTypes.merge([[0, { name: 'pool', desc: 'LO-Temp' }], [1, { name: 'spa', desc: 'HI-Temp' }]]);
143
-
144
- }
145
- else {
146
- sys.board.valueMaps.circuitNames.merge([[61, { name: 'pool', desc: 'Pool' }], [72, { name: 'spa', desc: 'Spa' }]]);
147
- sys.board.valueMaps.circuitFunctions.merge([[1, { name: 'pool', desc: 'Pool', hasHeatsource: true }], [2, { name: 'spa', desc: 'Pool', hasHeatSource: true }]]);
148
- sys.board.valueMaps.virtualCircuits.merge([[130, { name: 'poolHeater', desc: 'Pool Heater' }], [131, { name: 'spaHeater', desc: 'Spa Heater' }]]);
149
- sys.board.valueMaps.bodyTypes.merge([[0, { name: 'pool', desc: 'Pool' }], [1, { name: 'spa', desc: 'Spa' }]]);
150
- }
151
- // Calculate out the invalid ids.
152
- sys.board.equipmentIds.invalidIds.set([]);
153
- // SunTouch bit mapping for circuits and features
154
- // Bit Mask Circuit/Feature id
155
- // 1 = 0x01 Spa 1
156
- // 2 = 0x02 Aux 1 2
157
- // 3 = 0x04 Aux 2 3
158
- // 4 = 0x08 Aux 3 4
159
- // 5 = 0x10 Feature 1 7
160
- // 6 = 0x20 Pool 6
161
- // 7 = 0x40 Feature 2 8
162
- // 8 = 0x80 Feature 3 9
163
- // 9 = 0x01 Feature 4 10
164
- sys.board.equipmentIds.invalidIds.merge([5]);
165
- state.equipment.model = sys.equipment.model = 'SunTouch';
166
- sys.equipment.setEquipmentIds();
167
- this.initBodyDefaults();
168
- state.emitControllerChange();
169
- }
170
- public initBodyDefaults() {
171
- // Initialize the bodies. We will need these very soon.
172
- for (let i = 1; i <= sys.equipment.maxBodies; i++) {
173
- // Add in the bodies for the configuration. These need to be set.
174
- let cbody = sys.bodies.getItemById(i, true);
175
- let tbody = state.temps.bodies.getItemById(i, true);
176
- cbody.isActive = true;
177
- tbody.circuit = cbody.circuit = i === 1 ? 1 : 6;
178
- tbody.type = cbody.type = i - 1; // This will set the first body to pool/Lo-Temp and the second body to spa/Hi-Temp.
179
- if (typeof cbody.name === 'undefined') {
180
- if (sys.equipment.single) {
181
- tbody.name = cbody.name = i === 1 ? 'LO' : 'HI';
182
- }
183
- else {
184
- let bt = sys.board.valueMaps.bodyTypes.transform(cbody.type);
185
- tbody.name = cbody.name = bt.desc;
186
- }
187
- }
188
- let c = sys.circuits.getItemById(tbody.circuit, true, { isActive: false });
189
- c.master = 0;
190
- let cstate = state.circuits.getItemById(c.id, true);
191
- cstate.type = c.type = tbody.circuit === 6 ? sys.board.valueMaps.circuitFunctions.encode('pool') : sys.board.valueMaps.circuitFunctions.encode('spa');
192
- let name = sys.board.valueMaps.circuitNames.transform(c.id === 6 ? 61 : 72);
193
- cstate.nameId = c.nameId = name.val;
194
- // Check to see if the body circuit exists. We are going to create these so that they start
195
- // out with the proper type.
196
- if (!c.isActive) {
197
- cstate.showInFeatures = c.showInFeatures = false;
198
- c.isActive = cstate.isActive = true;
199
- console.log(name);
200
- cstate.name = c.name = name.desc;
201
- }
202
- }
203
- sys.bodies.removeItemById(3);
204
- sys.bodies.removeItemById(4);
205
- state.temps.bodies.removeItemById(3);
206
- state.temps.bodies.removeItemById(4);
207
- sys.board.heaters.initTempSensors();
208
- sys.general.options.clockMode = sys.general.options.clockMode || 12;
209
- sys.general.options.clockSource = sys.general.options.clockSource || 'manual';
210
- // We are going to intialize the pool circuits
211
- let filter = sys.filters.getItemById(1, true);
212
- if (typeof filter.name === 'undefined') filter.name = 'Filter';
213
- state.filters.getItemById(1, true).name = filter.name;
214
- }
215
- public circuits: SunTouchCircuitCommands = new SunTouchCircuitCommands(this);
216
-
217
- }
218
- class SunTouchConfigQueue extends TouchConfigQueue {
219
- public queueChanges() {
220
- this.reset();
221
- logger.info(`Requesting ${sys.controllerType} configuration`);
222
- // Config categories that do nothing
223
- // 195 - [0-2]
224
- // 196 - [0-2]
225
- // 198 - [0-2]
226
- // 199 - [0-2]
227
- // 200 - Heat/Temperature Status
228
- // 201 - [0-2]
229
- // 202 - [0-2] - Custom Names
230
- // 203 - Circuit Functions
231
- // 204 - [0-2]
232
- // 205 - [0-2]
233
- // 206 - [0-2]
234
- // 207 - [0-2]
235
- // 208 - [0-2]
236
- // 209 - [0-10] - This returns invalid data about schedules. It is simply not correct
237
- // 212 - [0-2]
238
- // 213 - [0-2]
239
- // 214 - [0]
240
- // 215 - [0-2]
241
- // 216 - [0-4] - This does not return anything about the pumps
242
- // 218 - [0-2]
243
- // 219 - [0-2]
244
- // 220 - [0-2]
245
- // 221 - Valve Assignments
246
- // 223 - [0-2]
247
- // 224 - [1-2]
248
- // 225 - Spa side remote
249
- // 226 - [0] - Solar/HeatPump config
250
- // 228 - [0-2]
251
- // 229 - [0-2]
252
- // 230 - [0-2]
253
- // 231 - [0-2]
254
- // 232 - Settings (Amazed that there is none of this)
255
- // 233 - [0-2]
256
- // 234 - [0-2]
257
- // 235 - [0-2]
258
- // 236 - [0-2]
259
- // 237 - [0-2]
260
- // 238 - [0-2]
261
- // 239 - [0-2]
262
- // 240 - [0-2]
263
- // 241 - [0-2]
264
- // 242 - [0-2]
265
- // 243 - [0-2]
266
- // 244 - [0-2]
267
- // 245 - [0-2]
268
- // 246 - [0-2]
269
- // 247 - [0-2]
270
- // 248 - [0-2]
271
- // 249 - [0-2]
272
- // 250 - [0-2]
273
- // 251 - [0-2]
274
- // 253 - Software Version
275
-
276
- this.queueItems(GetTouchConfigCategories.version); // 252
277
- this.queueItems(GetTouchConfigCategories.dateTime, [0]); //197
278
- this.queueItems(GetTouchConfigCategories.heatTemperature, [0]); // 200
279
- //this.queueRange(GetTouchConfigCategories.customNames, 0, sys.equipment.maxCustomNames - 1); 202 SunTouch does not appear to support custom names. No responses
280
- this.queueItems(GetTouchConfigCategories.solarHeatPump, [0]); // 208
281
- this.queueRange(GetTouchConfigCategories.circuits, 1, sys.board.equipmentIds.features.end); // 203 circuits & Features
282
- //this.queueRange(GetTouchConfigCategories.schedules, 1, sys.equipment.maxSchedules); // 209 This return is worthless in SunTouch
283
- this.queueItems(GetTouchConfigCategories.delays, [0]); // 227
284
- this.queueItems(GetTouchConfigCategories.settings, [0]); // 232
285
- this.queueItems(GetTouchConfigCategories.intellifloSpaSideRemotes, [0]); // 225 QuickTouch
286
- this.queueItems(GetTouchConfigCategories.valves, [0]); // 221
287
-
288
- // Check for these positions to see if we can get it to spit out all the schedules.
289
- this.queueItems(222, [0]); // First 2 schedules. This request ignores the payload and does not return additional items.
290
- this.queueItems(211, [0]);
291
- this.queueItems(19, [0]); // If we send this request it will respond with a valid 147. The correct request however should be 211.
292
- //this.queueRange(GetTouchConfigCategories.circuitGroups, 0, sys.equipment.maxFeatures - 1); SunTouch does not support macros
293
- this.queueItems(GetTouchConfigCategories.intellichlor, [0]); // 217
294
- //let test = [195, 196, 208, 214, 218, 219, 220, 226, 228, 229, 230, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251];
295
- //for (let i = 0; i < test.length; i++) {
296
- // let cat = test[i];
297
- // this.queueRange(cat, 0, 2);
298
- //}
299
-
300
- if (this.remainingItems > 0) {
301
- var self = this;
302
- setTimeout(() => { self.processNext(); }, 50);
303
- } else state.status = 1;
304
- state.emitControllerChange();
305
- }
306
-
307
- }
308
- class SunTouchCircuitCommands extends TouchCircuitCommands {
309
- public async setCircuitStateAsync(id: number, val: boolean, ignoreDelays?: boolean): Promise<ICircuitState> {
310
- if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError('Circuit or Feature id not valid', id, 'Circuit'));
311
- let c = sys.circuits.getInterfaceById(id);
312
- if (c.master !== 0) return await super.setCircuitStateAsync(id, val);
313
- if (id === 192 || c.type === 3) return await sys.board.circuits.setLightGroupThemeAsync(id - 191, val ? 1 : 0);
314
- if (id >= 192) return await sys.board.circuits.setCircuitGroupStateAsync(id, val);
315
-
316
- // for some dumb reason, if the spa is on and the pool circuit is desired to be on,
317
- // it will ignore the packet.
318
- // We can override that by emulating a click to turn off the spa instead of turning
319
- // on the pool
320
- if (sys.equipment.maxBodies > 1 && id === 6 && val && state.circuits.getItemById(1).isOn) {
321
- id = 1;
322
- val = false;
323
- }
324
- let mappedId = id;
325
- if (id === 7) mappedId = 5;
326
- else if (id > 6) mappedId = id - 1;
327
- let cstate = state.circuits.getInterfaceById(id);
328
- let out = Outbound.create({
329
- action: 134,
330
- payload: [mappedId, val ? 1 : 0],
331
- retries: 3,
332
- response: true,
333
- scope: `circuitState${id}`
334
- });
335
- await out.sendAsync();
336
- sys.board.circuits.setEndTime(c, cstate, val);
337
- cstate.isOn = val;
338
- state.emitEquipmentChanges();
339
- return cstate;
340
-
341
- }
342
- public async setCircuitAsync(data: any): Promise<ICircuit> {
343
- try {
344
- // example [255,0,255][165,33,16,34,139,5][17,14,209,0,0][2,120]
345
- // set circuit 17 to function 14 and name 209
346
- // response: [255,0,255][165,33,34,16,1,1][139][1,133]
347
- let id = parseInt(data.id, 10);
348
- if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError('Circuit Id is invalid', data.id, 'Feature'));
349
- if (id >= 255 || data.master === 1) return super.setCircuitAsync(data);
350
- let circuit = sys.circuits.getInterfaceById(id);
351
- // Alright check to see if we are adding a nixie circuit.
352
- if (id === -1 || circuit.master !== 0) {
353
- let circ = await super.setCircuitAsync(data);
354
- return circ;
355
- }
356
- let typeByte = parseInt(data.type, 10) || circuit.type || sys.board.valueMaps.circuitFunctions.getValue('generic');
357
- let nameByte = circuit.nameId; // You cannot change the Name Id in SunTouch.
358
- if (typeof data.nameId !== 'undefined') nameByte = data.nameId;
359
- let mappedId = id;
360
- if (id === 7) mappedId = 5;
361
- else if (id > 6) mappedId = id - 1;
362
-
363
- let out = Outbound.create({
364
- action: 139,
365
- payload: [mappedId, typeByte | (utils.makeBool(data.freeze) ? 64 : 0), nameByte, 0, 0],
366
- retries: 3,
367
- response: true
368
- });
369
- await out.sendAsync();
370
- circuit = sys.circuits.getInterfaceById(data.id);
371
- let cstate = state.circuits.getInterfaceById(data.id);
372
- circuit.nameId = cstate.nameId = nameByte;
373
- circuit.name = typeof data.name !== 'undefined' ? data.name.toString() : circuit.name;
374
- circuit.showInFeatures = cstate.showInFeatures = typeof data.showInFeatures !== 'undefined' ? data.showInFeatures : circuit.showInFeatures;
375
- circuit.freeze = typeof data.freeze !== 'undefined' ? utils.makeBool(data.freeze) : circuit.freeze;
376
- circuit.type = cstate.type = typeByte;
377
- circuit.eggTimer = typeof data.eggTimer !== 'undefined' ? parseInt(data.eggTimer, 10) : circuit.eggTimer || 720;
378
- circuit.dontStop = (typeof data.dontStop !== 'undefined') ? utils.makeBool(data.dontStop) : circuit.eggTimer === 1620;
379
- cstate.isActive = circuit.isActive = true;
380
- circuit.master = 0;
381
- let eggTimer = sys.eggTimers.find(elem => elem.circuit === parseInt(data.id, 10));
382
- try {
383
- if (circuit.eggTimer === 720) {
384
- if (typeof eggTimer !== 'undefined') await sys.board.schedules.deleteEggTimerAsync({ id: eggTimer.id });
385
- }
386
- else {
387
- await sys.board.schedules.setEggTimerAsync({ id: typeof eggTimer !== 'undefined' ? eggTimer.id : -1, runTime: circuit.eggTimer, dontStop: circuit.dontStop, circuit: circuit.id });
388
- }
389
- }
390
- catch (err) {
391
- // fail silently if there are no slots to fill in the schedules
392
- logger.info(`Cannot set/delete eggtimer on circuit ${circuit.id}. Error: ${err.message}`);
393
- circuit.eggTimer = 720;
394
- circuit.dontStop = false;
395
- }
396
- state.emitEquipmentChanges();
397
- return circuit;
398
- }
399
- catch (err) { logger.error(`setCircuitAsync error setting circuit ${JSON.stringify(data)}: ${err}`); return Promise.reject(err); }
400
- }
1
+ /* nodejs-poolController. An application to control pool equipment.
2
+ Copyright (C) 2016, 2017, 2018, 2019, 2020, 2021, 2022.
3
+ Russell Goldin, tagyoureit. russ.goldin@gmail.com
4
+
5
+ This program is free software: you can redistribute it and/or modify
6
+ it under the terms of the GNU Affero General Public License as
7
+ published by the Free Software Foundation, either version 3 of the
8
+ License, or (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU Affero General Public License for more details.
14
+
15
+ You should have received a copy of the GNU Affero General Public License
16
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+ */
18
+ import * as extend from 'extend';
19
+ import { EventEmitter } from 'events';
20
+ import { EasyTouchBoard, TouchConfigQueue, GetTouchConfigCategories, TouchCircuitCommands } from './EasyTouchBoard';
21
+ import { sys, PoolSystem, Circuit, ICircuit } from '../Equipment';
22
+ import { byteValueMap, EquipmentIdRange } from './SystemBoard';
23
+ import { state, ICircuitState } from '../State';
24
+ import { logger } from '../../logger/Logger';
25
+ import { conn } from '../comms/Comms';
26
+ import { Outbound } from "../comms/messages/Messages";
27
+ import { InvalidEquipmentIdError } from "../Errors";
28
+ import { utils } from "../Constants";
29
+
30
+ export class SunTouchBoard extends EasyTouchBoard {
31
+ constructor(system: PoolSystem) {
32
+ super(system); // graph chain to EasyTouchBoard constructor.
33
+ this.valueMaps.expansionBoards = new byteValueMap([
34
+ // 33 added per #1108;
35
+ [33, { name: 'stshared', part: '520820', desc: 'Pool and Spa controller', bodies: 2, valves: 4, circuits: 5, single: false, shared: true, dual: false, features: 4, chlorinators: 1, chemControllers: 1 }],
36
+ [41, { name: 'stshared', part: '520820', desc: 'Pool and Spa controller', bodies: 2, valves: 4, circuits: 5, single: false, shared: true, dual: false, features: 4, chlorinators: 1, chemControllers: 1 }],
37
+ [40, { name: 'stsingle', part: '520819', desc: 'Pool or Spa controller', bodies: 2, valves: 4, circuits: 5, single: true, shared: true, dual: false, features: 4, chlorinators: 1, chemControllers: 1 }]
38
+ ]);
39
+ this._statusInterval = -1;
40
+ this.equipmentIds.circuits = new EquipmentIdRange(1, 6);
41
+ this.equipmentIds.features = new EquipmentIdRange(7, 10);
42
+ this.equipmentIds.virtualCircuits = new EquipmentIdRange(128, 136);
43
+ this.equipmentIds.circuitGroups = new EquipmentIdRange(192, function () { return this.start + sys.equipment.maxCircuitGroups - 1; });
44
+ this.equipmentIds.circuits.start = 1;
45
+ this.equipmentIds.circuits.isInRange = (id: number) => { return [1, 2, 3, 4, 6].includes(id); };
46
+ this.equipmentIds.features.isInRange = (id: number) => { return [7, 8, 9, 10].includes(id); };
47
+ if (typeof sys.configVersion.equipment === 'undefined') { sys.configVersion.equipment = 0; }
48
+ this.valueMaps.heatSources = new byteValueMap([
49
+ [0, { name: 'off', desc: 'Off' }],
50
+ [32, { name: 'nochange', desc: 'No Change' }]
51
+ ]);
52
+ this.valueMaps.heatStatus = new byteValueMap([
53
+ [0, { name: 'off', desc: 'Off' }],
54
+ [1, { name: 'heater', desc: 'Heater' }],
55
+ [2, { name: 'cooling', desc: 'Cooling' }],
56
+ [3, { name: 'solar', desc: 'Solar' }],
57
+ [4, { name: 'hpheat', desc: 'Heatpump' }],
58
+ [5, { name: 'dual', desc: 'Dual' }]
59
+ ]);
60
+ this.valueMaps.circuitFunctions = new byteValueMap([
61
+ [0, { name: 'generic', desc: 'Generic' }],
62
+ [1, { name: 'spa', desc: 'Spa', hasHeatSource: true, body: 2 }],
63
+ [2, { name: 'pool', desc: 'Pool', hasHeatSource: true, body: 1 }],
64
+ [5, { name: 'mastercleaner', desc: 'Master Cleaner', body: 1 }],
65
+ [7, { name: 'light', desc: 'Light', isLight: true }],
66
+ [9, { name: 'samlight', desc: 'SAM Light', isLight: true }],
67
+ [10, { name: 'sallight', desc: 'SAL Light', isLight: true }],
68
+ [11, { name: 'photongen', desc: 'Photon Gen', isLight: true }],
69
+ [12, { name: 'colorwheel', desc: 'Color Wheel', isLight: true }],
70
+ [13, { name: 'valve', desc: 'Valve' }],
71
+ [14, { name: 'spillway', desc: 'Spillway' }],
72
+ [15, { name: 'floorcleaner', desc: 'Floor Cleaner', body: 1 }], // This circuit function does not seem to exist in IntelliTouch.
73
+ [19, { name: 'notused', desc: 'Not Used' }],
74
+ [63, { name: 'cleaner', desc: 'Cleaner' }],
75
+
76
+ ]);
77
+ this.valueMaps.virtualCircuits = new byteValueMap([
78
+ [20, { name: 'solar', desc: 'Solar', assignableToPumpCircuit: true }],
79
+ [129, { name: 'poolspa', desc: 'Pool/Spa' }],
80
+ [130, { name: 'poolHeater', desc: 'Pool Heater', assignableToPumpCircuit: true }],
81
+ [131, { name: 'spaHeater', desc: 'Spa Heater', assignableToPumpCircuit: true }],
82
+ [132, { name: 'freeze', desc: 'Freeze', assignableToPumpCircuit: true }],
83
+ [258, { name: 'anyHeater', desc: 'Any Heater' }]
84
+ ]);
85
+ this.valueMaps.circuitNames = new byteValueMap([
86
+ [3, { name: 'aux1', desc: 'AUX 1' }],
87
+ [4, { name: 'aux2', desc: 'AUX 2' }],
88
+ [5, { name: 'aux3', desc: 'AUX 3' }],
89
+ [6, { name: 'feature1', desc: 'FEATURE 1' }],
90
+ [7, { name: 'feature2', desc: 'FEATURE 2' }],
91
+ [8, { name: 'feature3', desc: 'FEATURE 3' }],
92
+ [9, { name: 'feature4', desc: 'FEATURE 4' }],
93
+ [61, { name: 'pool', desc: 'Pool' }],
94
+ [72, { name: 'spa', desc: 'Spa' }]
95
+ ]);
96
+ this._configQueue = new SunTouchConfigQueue();
97
+ }
98
+ public initExpansionModules(byte1: number, byte2: number) {
99
+ console.log(`Pentair SunTouch System Detected!`);
100
+ sys.equipment.model = 'Suntouch';
101
+
102
+ // Initialize the installed personality board.
103
+ let mt = this.valueMaps.expansionBoards.transform(byte1); // Only have one example of SunTouch and it is a single body system (40).
104
+ let mod = sys.equipment.modules.getItemById(0, true);
105
+ if (mod.name !== mt.name) {
106
+ logger.info(`Clearing SunTouch configuration...`);
107
+ sys.bodies.removeItemById(1);
108
+ sys.bodies.removeItemById(2);
109
+ sys.bodies.removeItemById(3);
110
+ sys.bodies.removeItemById(4);
111
+ sys.circuits.clear(0);
112
+ sys.circuits.removeItemById(1);
113
+ sys.circuits.removeItemById(6);
114
+ sys.features.clear(0);
115
+ state.circuits.clear();
116
+ state.temps.clear();
117
+ sys.filters.clear(0);
118
+ state.filters.clear();
119
+ }
120
+ mod.name = mt.name;
121
+ mod.desc = mt.desc;
122
+ mod.type = byte1;
123
+ mod.part = mt.part;
124
+ let eq = sys.equipment;
125
+ let md = mod.get();
126
+ eq.maxBodies = md.bodies = typeof mt.bodies !== 'undefined' ? mt.bodies : mt.shared ? 2 : 1;
127
+ eq.maxCircuits = md.circuits = typeof mt.circuits !== 'undefined' ? mt.circuits : 3;
128
+ eq.maxFeatures = md.features = typeof mt.features !== 'undefined' ? mt.features : 0
129
+ eq.maxValves = md.valves = typeof mt.valves !== 'undefined' ? mt.valves : 2;
130
+ eq.maxPumps = md.maxPumps = typeof mt.pumps !== 'undefined' ? mt.pumps : 2;
131
+ eq.shared = mt.shared || false;
132
+ eq.dual = mt.dual || false;
133
+ eq.single = mt.single || false;
134
+ eq.maxChlorinators = md.chlorinators = 1;
135
+ eq.maxChemControllers = md.chemControllers = 1;
136
+ eq.maxCustomNames = 0;
137
+ eq.maxSchedules = 6;
138
+ if (sys.equipment.single) {
139
+ sys.board.valueMaps.circuitNames.merge([[61, { name: 'pool', desc: 'LO-Temp' }], [72, { name: 'spa', desc: 'HI-Temp' }]]);
140
+ sys.board.valueMaps.circuitFunctions.merge([[1, { name: 'pool', desc: 'LO-Temp', hasHeatSource: true }], [2, { name: 'spa', desc: 'HI-Temp', hasHeatSource: true }]]);
141
+ sys.board.valueMaps.virtualCircuits.merge([[130, { name: 'poolHeater', desc: 'LO-Temp Heater' }], [131, { name: 'spaHeater', desc: 'HI-Temp Heater' }]]);
142
+ sys.board.valueMaps.bodyTypes.merge([[0, { name: 'pool', desc: 'LO-Temp' }], [1, { name: 'spa', desc: 'HI-Temp' }]]);
143
+
144
+ }
145
+ else {
146
+ sys.board.valueMaps.circuitNames.merge([[61, { name: 'pool', desc: 'Pool' }], [72, { name: 'spa', desc: 'Spa' }]]);
147
+ sys.board.valueMaps.circuitFunctions.merge([[1, { name: 'pool', desc: 'Pool', hasHeatsource: true }], [2, { name: 'spa', desc: 'Pool', hasHeatSource: true }]]);
148
+ sys.board.valueMaps.virtualCircuits.merge([[130, { name: 'poolHeater', desc: 'Pool Heater' }], [131, { name: 'spaHeater', desc: 'Spa Heater' }]]);
149
+ sys.board.valueMaps.bodyTypes.merge([[0, { name: 'pool', desc: 'Pool' }], [1, { name: 'spa', desc: 'Spa' }]]);
150
+ }
151
+ // Calculate out the invalid ids.
152
+ sys.board.equipmentIds.invalidIds.set([]);
153
+ // SunTouch bit mapping for circuits and features
154
+ // Bit Mask Circuit/Feature id
155
+ // 1 = 0x01 Spa 1
156
+ // 2 = 0x02 Aux 1 2
157
+ // 3 = 0x04 Aux 2 3
158
+ // 4 = 0x08 Aux 3 4
159
+ // 5 = 0x10 Feature 1 7
160
+ // 6 = 0x20 Pool 6
161
+ // 7 = 0x40 Feature 2 8
162
+ // 8 = 0x80 Feature 3 9
163
+ // 9 = 0x01 Feature 4 10
164
+ sys.board.equipmentIds.invalidIds.merge([5]);
165
+ state.equipment.model = sys.equipment.model = 'SunTouch';
166
+ sys.equipment.setEquipmentIds();
167
+ this.initBodyDefaults();
168
+ state.emitControllerChange();
169
+ }
170
+ public initBodyDefaults() {
171
+ // Initialize the bodies. We will need these very soon.
172
+ for (let i = 1; i <= sys.equipment.maxBodies; i++) {
173
+ // Add in the bodies for the configuration. These need to be set.
174
+ let cbody = sys.bodies.getItemById(i, true);
175
+ let tbody = state.temps.bodies.getItemById(i, true);
176
+ cbody.isActive = true;
177
+ tbody.circuit = cbody.circuit = i === 1 ? 1 : 6;
178
+ tbody.type = cbody.type = i - 1; // This will set the first body to pool/Lo-Temp and the second body to spa/Hi-Temp.
179
+ if (typeof cbody.name === 'undefined') {
180
+ if (sys.equipment.single) {
181
+ tbody.name = cbody.name = i === 1 ? 'LO' : 'HI';
182
+ }
183
+ else {
184
+ let bt = sys.board.valueMaps.bodyTypes.transform(cbody.type);
185
+ tbody.name = cbody.name = bt.desc;
186
+ }
187
+ }
188
+ let c = sys.circuits.getItemById(tbody.circuit, true, { isActive: false });
189
+ c.master = 0;
190
+ let cstate = state.circuits.getItemById(c.id, true);
191
+ cstate.type = c.type = tbody.circuit === 6 ? sys.board.valueMaps.circuitFunctions.encode('pool') : sys.board.valueMaps.circuitFunctions.encode('spa');
192
+ let name = sys.board.valueMaps.circuitNames.transform(c.id === 6 ? 61 : 72);
193
+ cstate.nameId = c.nameId = name.val;
194
+ // Check to see if the body circuit exists. We are going to create these so that they start
195
+ // out with the proper type.
196
+ if (!c.isActive) {
197
+ cstate.showInFeatures = c.showInFeatures = false;
198
+ c.isActive = cstate.isActive = true;
199
+ console.log(name);
200
+ cstate.name = c.name = name.desc;
201
+ }
202
+ }
203
+ sys.bodies.removeItemById(3);
204
+ sys.bodies.removeItemById(4);
205
+ state.temps.bodies.removeItemById(3);
206
+ state.temps.bodies.removeItemById(4);
207
+ sys.board.heaters.initTempSensors();
208
+ sys.general.options.clockMode = sys.general.options.clockMode || 12;
209
+ sys.general.options.clockSource = sys.general.options.clockSource || 'manual';
210
+ // We are going to intialize the pool circuits
211
+ let filter = sys.filters.getItemById(1, true);
212
+ if (typeof filter.name === 'undefined') filter.name = 'Filter';
213
+ state.filters.getItemById(1, true).name = filter.name;
214
+ }
215
+ public circuits: SunTouchCircuitCommands = new SunTouchCircuitCommands(this);
216
+
217
+ }
218
+ class SunTouchConfigQueue extends TouchConfigQueue {
219
+ public queueChanges() {
220
+ this.reset();
221
+ logger.info(`Requesting ${sys.controllerType} configuration`);
222
+ // Config categories that do nothing
223
+ // 195 - [0-2]
224
+ // 196 - [0-2]
225
+ // 198 - [0-2]
226
+ // 199 - [0-2]
227
+ // 200 - Heat/Temperature Status
228
+ // 201 - [0-2]
229
+ // 202 - [0-2] - Custom Names
230
+ // 203 - Circuit Functions
231
+ // 204 - [0-2]
232
+ // 205 - [0-2]
233
+ // 206 - [0-2]
234
+ // 207 - [0-2]
235
+ // 208 - [0-2]
236
+ // 209 - [0-10] - This returns invalid data about schedules. It is simply not correct
237
+ // 212 - [0-2]
238
+ // 213 - [0-2]
239
+ // 214 - [0]
240
+ // 215 - [0-2]
241
+ // 216 - [0-4] - This does not return anything about the pumps
242
+ // 218 - [0-2]
243
+ // 219 - [0-2]
244
+ // 220 - [0-2]
245
+ // 221 - Valve Assignments
246
+ // 223 - [0-2]
247
+ // 224 - [1-2]
248
+ // 225 - Spa side remote
249
+ // 226 - [0] - Solar/HeatPump config
250
+ // 228 - [0-2]
251
+ // 229 - [0-2]
252
+ // 230 - [0-2]
253
+ // 231 - [0-2]
254
+ // 232 - Settings (Amazed that there is none of this)
255
+ // 233 - [0-2]
256
+ // 234 - [0-2]
257
+ // 235 - [0-2]
258
+ // 236 - [0-2]
259
+ // 237 - [0-2]
260
+ // 238 - [0-2]
261
+ // 239 - [0-2]
262
+ // 240 - [0-2]
263
+ // 241 - [0-2]
264
+ // 242 - [0-2]
265
+ // 243 - [0-2]
266
+ // 244 - [0-2]
267
+ // 245 - [0-2]
268
+ // 246 - [0-2]
269
+ // 247 - [0-2]
270
+ // 248 - [0-2]
271
+ // 249 - [0-2]
272
+ // 250 - [0-2]
273
+ // 251 - [0-2]
274
+ // 253 - Software Version
275
+
276
+ this.queueItems(GetTouchConfigCategories.version); // 252
277
+ this.queueItems(GetTouchConfigCategories.dateTime, [0]); //197
278
+ this.queueItems(GetTouchConfigCategories.heatTemperature, [0]); // 200
279
+ //this.queueRange(GetTouchConfigCategories.customNames, 0, sys.equipment.maxCustomNames - 1); 202 SunTouch does not appear to support custom names. No responses
280
+ this.queueItems(GetTouchConfigCategories.solarHeatPump, [0]); // 208
281
+ this.queueRange(GetTouchConfigCategories.circuits, 1, sys.board.equipmentIds.features.end); // 203 circuits & Features
282
+ //this.queueRange(GetTouchConfigCategories.schedules, 1, sys.equipment.maxSchedules); // 209 This return is worthless in SunTouch
283
+ this.queueItems(GetTouchConfigCategories.delays, [0]); // 227
284
+ this.queueItems(GetTouchConfigCategories.settings, [0]); // 232
285
+ this.queueItems(GetTouchConfigCategories.intellifloSpaSideRemotes, [0]); // 225 QuickTouch
286
+ this.queueItems(GetTouchConfigCategories.valves, [0]); // 221
287
+
288
+ // Check for these positions to see if we can get it to spit out all the schedules.
289
+ this.queueItems(222, [0]); // First 2 schedules. This request ignores the payload and does not return additional items.
290
+ this.queueItems(211, [0]);
291
+ this.queueItems(19, [0]); // If we send this request it will respond with a valid 147. The correct request however should be 211.
292
+ //this.queueRange(GetTouchConfigCategories.circuitGroups, 0, sys.equipment.maxFeatures - 1); SunTouch does not support macros
293
+ this.queueItems(GetTouchConfigCategories.intellichlor, [0]); // 217
294
+ //let test = [195, 196, 208, 214, 218, 219, 220, 226, 228, 229, 230, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251];
295
+ //for (let i = 0; i < test.length; i++) {
296
+ // let cat = test[i];
297
+ // this.queueRange(cat, 0, 2);
298
+ //}
299
+
300
+ if (this.remainingItems > 0) {
301
+ var self = this;
302
+ setTimeout(() => { self.processNext(); }, 50);
303
+ } else state.status = 1;
304
+ state.emitControllerChange();
305
+ }
306
+
307
+ }
308
+ class SunTouchCircuitCommands extends TouchCircuitCommands {
309
+ public async setCircuitStateAsync(id: number, val: boolean, ignoreDelays?: boolean): Promise<ICircuitState> {
310
+ if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError('Circuit or Feature id not valid', id, 'Circuit'));
311
+ let c = sys.circuits.getInterfaceById(id);
312
+ if (c.master !== 0) return await super.setCircuitStateAsync(id, val);
313
+ if (id === 192 || c.type === 3) return await sys.board.circuits.setLightGroupThemeAsync(id - 191, val ? 1 : 0);
314
+ if (id >= 192) return await sys.board.circuits.setCircuitGroupStateAsync(id, val);
315
+
316
+ // for some dumb reason, if the spa is on and the pool circuit is desired to be on,
317
+ // it will ignore the packet.
318
+ // We can override that by emulating a click to turn off the spa instead of turning
319
+ // on the pool
320
+ if (sys.equipment.maxBodies > 1 && id === 6 && val && state.circuits.getItemById(1).isOn) {
321
+ id = 1;
322
+ val = false;
323
+ }
324
+ let mappedId = id;
325
+ if (id === 7) mappedId = 5;
326
+ else if (id > 6) mappedId = id - 1;
327
+ let cstate = state.circuits.getInterfaceById(id);
328
+ let out = Outbound.create({
329
+ action: 134,
330
+ payload: [mappedId, val ? 1 : 0],
331
+ retries: 3,
332
+ response: true,
333
+ scope: `circuitState${id}`
334
+ });
335
+ await out.sendAsync();
336
+ sys.board.circuits.setEndTime(c, cstate, val);
337
+ cstate.isOn = val;
338
+ state.emitEquipmentChanges();
339
+ return cstate;
340
+
341
+ }
342
+ public async setCircuitAsync(data: any): Promise<ICircuit> {
343
+ try {
344
+ // example [255,0,255][165,33,16,34,139,5][17,14,209,0,0][2,120]
345
+ // set circuit 17 to function 14 and name 209
346
+ // response: [255,0,255][165,33,34,16,1,1][139][1,133]
347
+ let id = parseInt(data.id, 10);
348
+ if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError('Circuit Id is invalid', data.id, 'Feature'));
349
+ if (id >= 255 || data.master === 1) return super.setCircuitAsync(data);
350
+ let circuit = sys.circuits.getInterfaceById(id);
351
+ // Alright check to see if we are adding a nixie circuit.
352
+ if (id === -1 || circuit.master !== 0) {
353
+ let circ = await super.setCircuitAsync(data);
354
+ return circ;
355
+ }
356
+ let typeByte = parseInt(data.type, 10) || circuit.type || sys.board.valueMaps.circuitFunctions.getValue('generic');
357
+ this.assertSinglePoolSpaType(id, typeByte);
358
+ let nameByte = circuit.nameId; // You cannot change the Name Id in SunTouch.
359
+ if (typeof data.nameId !== 'undefined') nameByte = data.nameId;
360
+ let mappedId = id;
361
+ if (id === 7) mappedId = 5;
362
+ else if (id > 6) mappedId = id - 1;
363
+
364
+ let out = Outbound.create({
365
+ action: 139,
366
+ payload: [mappedId, typeByte | (utils.makeBool(data.freeze) ? 64 : 0), nameByte, 0, 0],
367
+ retries: 3,
368
+ response: true
369
+ });
370
+ await out.sendAsync();
371
+ circuit = sys.circuits.getInterfaceById(data.id);
372
+ let cstate = state.circuits.getInterfaceById(data.id);
373
+ circuit.nameId = cstate.nameId = nameByte;
374
+ circuit.name = typeof data.name !== 'undefined' ? data.name.toString() : circuit.name;
375
+ circuit.showInFeatures = cstate.showInFeatures = typeof data.showInFeatures !== 'undefined' ? data.showInFeatures : circuit.showInFeatures;
376
+ circuit.freeze = typeof data.freeze !== 'undefined' ? utils.makeBool(data.freeze) : circuit.freeze;
377
+ circuit.type = cstate.type = typeByte;
378
+ circuit.eggTimer = typeof data.eggTimer !== 'undefined' ? parseInt(data.eggTimer, 10) : circuit.eggTimer || 720;
379
+ circuit.dontStop = (typeof data.dontStop !== 'undefined') ? utils.makeBool(data.dontStop) : circuit.eggTimer === 1620;
380
+ cstate.isActive = circuit.isActive = true;
381
+ circuit.master = 0;
382
+ let eggTimer = sys.eggTimers.find(elem => elem.circuit === parseInt(data.id, 10));
383
+ try {
384
+ if (circuit.eggTimer === 720) {
385
+ if (typeof eggTimer !== 'undefined') await sys.board.schedules.deleteEggTimerAsync({ id: eggTimer.id });
386
+ }
387
+ else {
388
+ await sys.board.schedules.setEggTimerAsync({ id: typeof eggTimer !== 'undefined' ? eggTimer.id : -1, runTime: circuit.eggTimer, dontStop: circuit.dontStop, circuit: circuit.id });
389
+ }
390
+ }
391
+ catch (err) {
392
+ // fail silently if there are no slots to fill in the schedules
393
+ logger.info(`Cannot set/delete eggtimer on circuit ${circuit.id}. Error: ${err.message}`);
394
+ circuit.eggTimer = 720;
395
+ circuit.dontStop = false;
396
+ }
397
+ state.emitEquipmentChanges();
398
+ return circuit;
399
+ }
400
+ catch (err) { logger.error(`setCircuitAsync error setting circuit ${JSON.stringify(data)}: ${err}`); return Promise.reject(err); }
401
+ }
401
402
  }