nodejs-poolcontroller 8.1.2 → 8.4.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.
Files changed (106) 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 -0
  7. package/.github/workflows/ghcr-publish.yml +67 -0
  8. package/AGENTS.md +597 -0
  9. package/CONTRIBUTING.md +74 -74
  10. package/Changelog +292 -257
  11. package/Dockerfile +62 -19
  12. package/Gruntfile.js +40 -40
  13. package/LICENSE +661 -661
  14. package/README.md +318 -191
  15. package/anslq25/MessagesMock.ts +221 -221
  16. package/anslq25/boards/MockBoardFactory.ts +49 -49
  17. package/anslq25/boards/MockEasyTouchBoard.ts +696 -696
  18. package/anslq25/boards/MockSystemBoard.ts +216 -216
  19. package/anslq25/chemistry/MockChlorinator.ts +98 -98
  20. package/anslq25/pumps/MockPump.ts +83 -83
  21. package/app.ts +115 -115
  22. package/config/Config.ts +57 -7
  23. package/config/VersionCheck.ts +63 -35
  24. package/controller/Constants.ts +809 -805
  25. package/controller/Equipment.ts +2688 -2664
  26. package/controller/Errors.ts +181 -181
  27. package/controller/Lockouts.ts +549 -549
  28. package/controller/State.ts +3738 -3690
  29. package/controller/boards/AquaLinkBoard.ts +1003 -1003
  30. package/controller/boards/BoardFactory.ts +53 -53
  31. package/controller/boards/EasyTouchBoard.ts +3202 -3202
  32. package/controller/boards/IntelliCenterBoard.ts +4393 -3899
  33. package/controller/boards/IntelliComBoard.ts +69 -69
  34. package/controller/boards/IntelliTouchBoard.ts +382 -382
  35. package/controller/boards/NixieBoard.ts +1944 -1929
  36. package/controller/boards/SunTouchBoard.ts +400 -400
  37. package/controller/boards/SystemBoard.ts +5268 -5268
  38. package/controller/comms/Comms.ts +1272 -1214
  39. package/controller/comms/ScreenLogic.ts +1665 -1665
  40. package/controller/comms/messages/Messages.ts +1433 -1243
  41. package/controller/comms/messages/config/ChlorinatorMessage.ts +5 -0
  42. package/controller/comms/messages/config/CircuitGroupMessage.ts +0 -0
  43. package/controller/comms/messages/config/CircuitMessage.ts +0 -0
  44. package/controller/comms/messages/config/ConfigMessage.ts +6 -0
  45. package/controller/comms/messages/config/CoverMessage.ts +0 -0
  46. package/controller/comms/messages/config/CustomNameMessage.ts +31 -31
  47. package/controller/comms/messages/config/EquipmentMessage.ts +216 -210
  48. package/controller/comms/messages/config/ExternalMessage.ts +96 -10
  49. package/controller/comms/messages/config/FeatureMessage.ts +0 -0
  50. package/controller/comms/messages/config/GeneralMessage.ts +0 -0
  51. package/controller/comms/messages/config/HeaterMessage.ts +0 -0
  52. package/controller/comms/messages/config/IntellichemMessage.ts +0 -0
  53. package/controller/comms/messages/config/OptionsMessage.ts +194 -174
  54. package/controller/comms/messages/config/PumpMessage.ts +0 -0
  55. package/controller/comms/messages/config/RemoteMessage.ts +0 -0
  56. package/controller/comms/messages/config/ScheduleMessage.ts +401 -390
  57. package/controller/comms/messages/config/SecurityMessage.ts +0 -0
  58. package/controller/comms/messages/config/ValveMessage.ts +0 -0
  59. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +0 -0
  60. package/controller/comms/messages/status/EquipmentStateMessage.ts +1158 -822
  61. package/controller/comms/messages/status/HeaterStateMessage.ts +135 -135
  62. package/controller/comms/messages/status/IntelliChemStateMessage.ts +448 -448
  63. package/controller/comms/messages/status/IntelliValveStateMessage.ts +36 -36
  64. package/controller/comms/messages/status/PumpStateMessage.ts +0 -0
  65. package/controller/comms/messages/status/RegalModbusStateMessage.ts +411 -0
  66. package/controller/comms/messages/status/VersionMessage.ts +103 -41
  67. package/controller/nixie/Nixie.ts +173 -173
  68. package/controller/nixie/NixieEquipment.ts +104 -104
  69. package/controller/nixie/bodies/Body.ts +120 -120
  70. package/controller/nixie/bodies/Filter.ts +135 -135
  71. package/controller/nixie/chemistry/ChemController.ts +2724 -2724
  72. package/controller/nixie/chemistry/ChemDoser.ts +806 -806
  73. package/controller/nixie/chemistry/Chlorinator.ts +367 -367
  74. package/controller/nixie/circuits/Circuit.ts +478 -478
  75. package/controller/nixie/heaters/Heater.ts +834 -834
  76. package/controller/nixie/pumps/Pump.ts +1194 -996
  77. package/controller/nixie/schedules/Schedule.ts +401 -401
  78. package/controller/nixie/valves/Valve.ts +170 -170
  79. package/defaultConfig.json +352 -347
  80. package/docker-compose.yml +32 -0
  81. package/logger/DataLogger.ts +448 -448
  82. package/logger/Logger.ts +448 -436
  83. package/package.json +58 -60
  84. package/sendSocket.js +32 -32
  85. package/tsconfig.json +25 -25
  86. package/types/express-multer.d.ts +32 -0
  87. package/web/Server.ts +1937 -1927
  88. package/web/bindings/aqualinkD.json +559 -559
  89. package/web/bindings/influxDB.json +1066 -1066
  90. package/web/bindings/mqtt.json +721 -721
  91. package/web/bindings/mqttAlt.json +746 -746
  92. package/web/bindings/rulesManager.json +54 -54
  93. package/web/bindings/smartThings-Hubitat.json +31 -31
  94. package/web/bindings/valveRelays.json +20 -20
  95. package/web/bindings/vera.json +25 -25
  96. package/web/interfaces/baseInterface.ts +188 -188
  97. package/web/interfaces/httpInterface.ts +148 -148
  98. package/web/interfaces/influxInterface.ts +283 -283
  99. package/web/interfaces/mqttInterface.ts +695 -695
  100. package/web/interfaces/ruleInterface.ts +101 -87
  101. package/web/services/config/Config.ts +1063 -1053
  102. package/web/services/config/ConfigSocket.ts +0 -0
  103. package/web/services/state/State.ts +0 -0
  104. package/web/services/state/StateSocket.ts +0 -0
  105. package/web/services/utilities/Utilities.ts +233 -233
  106. package/.github/workflows/docker-publish-njsPC-linux.yml +0 -50
@@ -1,401 +1,401 @@
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
+ 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
+ }
401
401
  }