nodejs-poolcontroller 8.4.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 (35) hide show
  1. package/.github/workflows/ghcr-publish.yml +1 -1
  2. package/157_issues.md +101 -0
  3. package/AGENTS.md +17 -1
  4. package/README.md +13 -2
  5. package/controller/Equipment.ts +49 -0
  6. package/controller/State.ts +8 -0
  7. package/controller/boards/AquaLinkBoard.ts +174 -2
  8. package/controller/boards/EasyTouchBoard.ts +44 -0
  9. package/controller/boards/IntelliCenterBoard.ts +360 -172
  10. package/controller/boards/NixieBoard.ts +7 -4
  11. package/controller/boards/SunTouchBoard.ts +1 -0
  12. package/controller/boards/SystemBoard.ts +39 -4
  13. package/controller/comms/Comms.ts +9 -3
  14. package/controller/comms/messages/Messages.ts +218 -24
  15. package/controller/comms/messages/config/EquipmentMessage.ts +34 -0
  16. package/controller/comms/messages/config/ExternalMessage.ts +1051 -989
  17. package/controller/comms/messages/config/GeneralMessage.ts +65 -0
  18. package/controller/comms/messages/config/OptionsMessage.ts +15 -2
  19. package/controller/comms/messages/config/PumpMessage.ts +427 -421
  20. package/controller/comms/messages/config/SecurityMessage.ts +37 -13
  21. package/controller/comms/messages/status/EquipmentStateMessage.ts +0 -218
  22. package/controller/comms/messages/status/HeaterStateMessage.ts +27 -15
  23. package/controller/comms/messages/status/NeptuneModbusStateMessage.ts +217 -0
  24. package/controller/comms/messages/status/VersionMessage.ts +67 -18
  25. package/controller/nixie/chemistry/ChemController.ts +65 -33
  26. package/controller/nixie/heaters/Heater.ts +10 -1
  27. package/controller/nixie/pumps/Pump.ts +145 -2
  28. package/docker-compose.yml +1 -0
  29. package/logger/Logger.ts +75 -64
  30. package/package.json +1 -1
  31. package/tsconfig.json +2 -1
  32. package/web/Server.ts +3 -1
  33. package/web/services/config/Config.ts +150 -1
  34. package/web/services/state/State.ts +21 -0
  35. package/web/services/state/StateSocket.ts +28 -0
@@ -1,421 +1,427 @@
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 {Inbound} from "../Messages";
19
- import {sys, Pump, PumpCircuit} from "../../../Equipment";
20
- import {state, CircuitState} from "../../../State";
21
- import {ControllerType} from "../../../Constants";
22
- import { logger } from "../../../../logger/Logger";
23
- export class PumpMessage {
24
- public static process(msg: Inbound): void {
25
- switch (sys.controllerType) {
26
- case ControllerType.IntelliCenter:
27
- PumpMessage.processIntelliCenterPump(msg);
28
- break;
29
- case ControllerType.SunTouch:
30
- case ControllerType.IntelliCom:
31
- case ControllerType.EasyTouch:
32
- case ControllerType.IntelliTouch:
33
- PumpMessage.processPumpConfig_IT(msg);
34
- break;
35
- }
36
- }
37
- public static processPumpConfig_IT(msg: Inbound) {
38
- // packet 24/27/152/155 - Pump Config: IntelliTouch. These will always be addressable pumps ds & ss are not included.
39
- const pumpId = msg.extractPayloadByte(0);
40
- let type = msg.extractPayloadByte(1); // Avoid setting this then setting it back if we are mapping to a different value.
41
- let isActive = type !== 0 && pumpId <= sys.equipment.maxPumps;
42
- // RKS: 04-14-21 - Only create the pump if it is available. If the pump was previously defined as another type
43
- // then it will be removed and recreated.
44
- // RKS: 05-06-23 - The original code did not search for the pump by its address. This is not correct.
45
- let pump: Pump = sys.pumps.getPumpByAddress(95 + pumpId, isActive);
46
- if(isActive) {
47
- // Remap the combination pump types.
48
- switch (type) {
49
- case 0:
50
- case 64:
51
- case 169:
52
- break;
53
- case 255:
54
- case 128:
55
- case 134:
56
- type = 128;
57
- break;
58
- default:
59
- type = 1;
60
- break;
61
- }
62
- if (pump.type !== type) {
63
- sys.pumps.removePumpByAddress(95 + pumpId);
64
- if (isActive) pump = sys.pumps.getPumpByAddress(95 + pumpId, isActive);
65
- }
66
- pump.address = pumpId + 95;
67
- pump.master = 0;
68
- switch (type) {
69
- case 0: // none
70
- pump.type = 0;
71
- pump.isActive = false;
72
- break;
73
- case 64: // vsf
74
- pump.type = type;
75
- pump.isActive = true;
76
- PumpMessage.processVSF_IT(msg);
77
- break;
78
- case 255: // vs 3050 on old panels.
79
- case 128: // vs
80
- case 134: // vs Ultra Efficiency
81
- pump.type = 128;
82
- pump.isActive = true;
83
- PumpMessage.processVS_IT(msg);
84
- break;
85
- case 169: // vs+svrs
86
- pump.type = 169;
87
- pump.isActive = true;
88
- PumpMessage.processVS_IT(msg);
89
- break;
90
- default: // vf - type is the background circuit
91
- pump.type = 1; // force to type 1?
92
- pump.isActive = true;
93
- PumpMessage.processVF_IT(msg);
94
- break;
95
- }
96
- if (typeof pump.name === 'undefined') pump.name = sys.board.valueMaps.pumpTypes.get(pump.type).desc;
97
- const spump = state.pumps.getItemById(pump.id, true);
98
- spump.name = pump.name;
99
- spump.type = pump.type;
100
- spump.isActive = pump.isActive;
101
- spump.status = 0;
102
- }
103
- else {
104
- // RKS: Remove any pump that is not defined in the system.
105
- sys.pumps.removeItemById(pumpId);
106
- state.pumps.removeItemById(pumpId);
107
- }
108
- msg.isProcessed = true;
109
- }
110
- private static processIntelliCenterPump(msg: Inbound) {
111
- let pumpId: number;
112
- let pump: Pump;
113
- let msgId: number = msg.extractPayloadByte(1);
114
- // First process the pump types. This will allow us to add or remove any installed pumps. All subsequent messages will not create pumps in the collection.
115
- if (msgId === 4) PumpMessage.processPumpType(msg);
116
- if (msgId <= 15) {
117
- let circuitId = 1;
118
- pumpId = msgId + 1;
119
- pump = sys.pumps.getItemById(pumpId);
120
- if (pump.type === 1) { // If this is a single speed pump it will have the body stored in the first circuit position. All other pumps have no
121
- // reference to the body.
122
- pump.body = msg.extractPayloadByte(34);
123
- // Clear the circuits as there should be none.
124
- pump.circuits.clear();
125
- }
126
- else if (pump.type !== 0 && typeof pump.type !== 'undefined') {
127
- for (let i = 34; i < msg.payload.length && circuitId <= sys.board.valueMaps.pumpTypes.get(pump.type).maxCircuits; i++) {
128
- let circuit = msg.extractPayloadByte(i);
129
- if (circuit !== 255) pump.circuits.getItemById(circuitId++, true).circuit = circuit + 1;
130
- else pump.circuits.removeItemById(circuitId++);
131
- }
132
- }
133
- // Speed/Flow
134
- if (pump.type > 2) {
135
- // Filter out the single speed and dual speed pumps. We have no flow or speed for these.
136
- circuitId = 1;
137
- for (let i = 18; i < msg.payload.length && circuitId <= sys.board.valueMaps.pumpTypes.get(pump.type).maxCircuits;) {
138
- let circuit: PumpCircuit = pump.circuits.getItemById(circuitId);
139
- let rate = msg.extractPayloadInt(i);
140
- // If the rate is < 450 then this must be a flow based value.
141
- if (rate < 450) {
142
- circuit.flow = rate;
143
- circuit.units = 1;
144
- circuit.speed = undefined;
145
- } else {
146
- circuit.speed = msg.extractPayloadInt(i);
147
- circuit.units = 0;
148
- circuit.flow = undefined;
149
- }
150
- i += 2;
151
- circuitId++;
152
- }
153
- }
154
- }
155
- msg.isProcessed = true;
156
- switch (msgId) {
157
- case 0:
158
- msg.isProcessed = true;
159
- break;
160
- case 1:
161
- PumpMessage.processFlowStepSize(msg);
162
- break;
163
- case 2:
164
- PumpMessage.processMinFlow(msg);
165
- break;
166
- case 3:
167
- PumpMessage.processMaxFlow(msg);
168
- break;
169
- case 5:
170
- PumpMessage.processAddress(msg);
171
- break;
172
- case 6:
173
- PumpMessage.processPrimingTime(msg);
174
- break;
175
- case 7:
176
- PumpMessage.processSpeedStepSize(msg);
177
- break;
178
- case 8: // Unknown
179
- case 9:
180
- case 10:
181
- case 11:
182
- case 12:
183
- case 13:
184
- case 14:
185
- case 15:
186
- break;
187
- case 16:
188
- PumpMessage.processMinSpeed(msg);
189
- break;
190
- case 17:
191
- PumpMessage.processMaxSpeed(msg);
192
- break;
193
- case 18:
194
- PumpMessage.processPrimingSpeed(msg);
195
- break;
196
- case 19: // Pump names
197
- case 20:
198
- case 21:
199
- case 22:
200
- case 23:
201
- case 24:
202
- case 25:
203
- case 26:
204
- PumpMessage.processPumpNames(msg);
205
- break;
206
- default:
207
- logger.debug(`Unprocessed Config Message ${msg.toPacket()}`)
208
- break;
209
-
210
- }
211
- }
212
- private static processFlowStepSize(msg: Inbound) {
213
- let pumpId = 1;
214
- for (let i = 2; i < msg.payload.length && pumpId <= sys.equipment.maxPumps; i++) {
215
- sys.pumps.getItemById(pumpId++).flowStepSize = msg.extractPayloadByte(i);
216
- }
217
- }
218
- private static processMinFlow(msg: Inbound) {
219
- let pumpId = 1;
220
- for (let i = 2; i < msg.payload.length && pumpId <= sys.equipment.maxPumps; i++) {
221
- sys.pumps.getItemById(pumpId++).minFlow = msg.extractPayloadByte(i);
222
- }
223
- }
224
- private static processMaxFlow(msg: Inbound) {
225
- let pumpId = 1;
226
- for (let i = 2; i < msg.payload.length && pumpId <= sys.equipment.maxPumps; i++) {
227
- sys.pumps.getItemById(pumpId++).maxFlow = msg.extractPayloadByte(i);
228
- }
229
- }
230
- private static processPumpType(msg: Inbound) {
231
- let pumpId = 1;
232
- for (let i = 2; i < msg.payload.length && pumpId <= sys.equipment.maxPumps; i++) {
233
- let type = msg.extractPayloadByte(i);
234
- let pump: Pump = sys.pumps.getItemById(pumpId++, type !== 0);
235
- if (type === 0) {
236
- sys.pumps.removeItemById(pump.id); // Remove the pump if we don't need it.
237
- state.pumps.removeItemById(pump.id);
238
- }
239
- else {
240
- if (pump.type !== type) {
241
- let ptype = sys.board.valueMaps.pumpTypes.transform(type);
242
- if (ptype.name === 'ss') pump.circuits.clear();
243
- pump.model = 0;
244
- }
245
- if (typeof pump.model === 'undefined') pump.model = 0;
246
- pump.type = type;
247
- pump.master = 0;
248
- let spump = state.pumps.getItemById(pump.id, true);
249
- spump.type = pump.type;
250
- spump.isActive = pump.isActive = true;
251
- }
252
- }
253
- }
254
- private static processAddress(msg: Inbound) {
255
- let pumpId = 1;
256
- for (let i = 2; i < msg.payload.length && pumpId <= sys.equipment.maxPumps; i++) {
257
- sys.pumps.getItemById(pumpId++).address = msg.extractPayloadByte(i);
258
- }
259
- }
260
- private static processPrimingTime(msg: Inbound) {
261
- let pumpId = 1;
262
- for (let i = 2; i < msg.payload.length && pumpId <= sys.equipment.maxPumps; i++) {
263
- sys.pumps.getItemById(pumpId++).primingTime = msg.extractPayloadByte(i);
264
- }
265
- }
266
- private static processSpeedStepSize(msg: Inbound) {
267
- let pumpId = 1;
268
- for (let i = 2; i < msg.payload.length && pumpId <= sys.equipment.maxPumps; i++) {
269
- sys.pumps.getItemById(pumpId++).speedStepSize = msg.extractPayloadByte(i) * 10;
270
- }
271
- }
272
- private static processMinSpeed(msg: Inbound) {
273
- let pumpId = 1;
274
- for (let i = 2; i < msg.payload.length && pumpId <= sys.equipment.maxPumps;) {
275
- sys.pumps.getItemById(pumpId++).minSpeed = msg.extractPayloadInt(i);
276
- i += 2;
277
- }
278
- }
279
- private static processMaxSpeed(msg: Inbound) {
280
- let pumpId = 1;
281
- for (let i = 2; i < msg.payload.length && pumpId <= sys.equipment.maxPumps;) {
282
- sys.pumps.getItemById(pumpId++).maxSpeed = msg.extractPayloadInt(i);
283
- i += 2;
284
- }
285
- }
286
- private static processPrimingSpeed(msg: Inbound) {
287
- let pumpId = 1;
288
- for (let i = 2; i < msg.payload.length && pumpId <= sys.equipment.maxPumps;) {
289
- sys.pumps.getItemById(pumpId++).primingSpeed = msg.extractPayloadInt(i);
290
- i += 2;
291
- }
292
- }
293
- private static processPumpNames(msg: Inbound) {
294
- let pumpId = (msg.extractPayloadByte(1) - 19) * 2 + 1;
295
- if (pumpId <= sys.equipment.maxPumps) {
296
- let pump = sys.pumps.getItemById(pumpId);
297
- pump.name = msg.extractPayloadString(2, 16);
298
- if (pump.isActive) state.pumps.getItemById(pumpId).name = pump.name;
299
- pumpId++;
300
- }
301
- if (pumpId <= sys.equipment.maxPumps) {
302
- let pump = sys.pumps.getItemById(pumpId);
303
- pump.name = msg.extractPayloadString(18, 16);
304
- if (pump.isActive) state.pumps.getItemById(pumpId).name = pump.name;
305
- pumpId++;
306
- }
307
- }
308
- private static processVS_IT(msg: Inbound) {
309
- // Sample Packet
310
- // [255, 0, 255], [165, 33, 15, 16, 27, 46], [1, 128, 1, 2, 0, 1, 6, 2, 12, 4, 9, 11, 7, 6, 7, 128, 8, 132, 3, 15, 5, 3, 234, 128, 46, 108, 58, 2, 232, 220, 232, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [8, 5]
311
- const pumpId = msg.extractPayloadByte(0);
312
- const pump = sys.pumps.getPumpByAddress(95 + pumpId);
313
- // [1, 128, 0, 2, 0, 6, 5, 1, 5, 158, 9, 2, 10, 0, 3, 0, 3, 0, 3, 0, 3, 3, 120, 20, 146, 240, 232, 232, 232, 232, 232]
314
- // byte | val |
315
- // 0 | 1 | PumpId = 1
316
- // 1 | 128 | Pump Type = VS
317
- // 2 | 0 | Priming Time = 0
318
- // 3 | 2 | Unknown
319
- // 4 | 0 | Unknown
320
- // 5 | 6 | Circuit Speed #1 = Pool
321
- // 6 | 5 | Big endian for the speed (1400 rpm with byte(22))
322
- // 7 | 1 | Circuit Speed #2 = Spa
323
- // 8 | 5 | Big endian for the speed (1300 rpm with byte(23))
324
- // 9 | 158 | Circuit Speed #3 = Solar
325
- // 10 | 9 | Big endian for the speed (2450 rpm with byte(24))
326
- // 11 | 2 | Circuit Speed #4 = Air blower (Aux-2)
327
- // 12 | 10 | Big endian speed for the speed (2800 rpm with byte(25))
328
- // 13 | 0 | Circuit Speed #5 = No circuit
329
- // 14 | 3 | Big endian speed for the speed (1000 rpm with byte(26))
330
- // 15 | 0 | Circuit speed #6 = No circuit
331
- // 16 | 3 | Big endian speed for the speed (1000 rpm with byte(27))
332
- // 17 | 0 | Circuit speed #7 = No circuit
333
- // 18 | 3 | Big endian speed for the speed (1000 rpm with byte(28))
334
- // 19 | 0 | Circuit speed #8 = No circuit
335
- // 20 | 3 | Big endian speed for the speed (1000 rpm with byte(29))
336
- // 21 | 3 | Big endian speed for the priming speed (1000 rpm with byte(30))
337
- // All 30 bytes on this message are accounted for except for byte 3 & 4.
338
- if (typeof pump.model === 'undefined') pump.model = 0;
339
- for (let circuitId = 1; circuitId <= sys.board.valueMaps.pumpTypes.get(pump.type).maxCircuits; circuitId++) {
340
- let _circuit = msg.extractPayloadByte(circuitId * 2 + 3);
341
- if (_circuit !== 0) {
342
- let circuit = pump.circuits.getItemById(circuitId, true);
343
- circuit.circuit = _circuit;
344
- circuit.speed =
345
- msg.extractPayloadByte(circuitId * 2 + 4) * 256 +
346
- msg.extractPayloadByte(circuitId + 21);
347
- circuit.units = 0;
348
- }
349
- else {
350
- pump.circuits.removeItemById(circuitId);
351
- }
352
- }
353
- pump.primingSpeed = msg.extractPayloadByte(21) * 256 + msg.extractPayloadByte(30);
354
- pump.primingTime = msg.extractPayloadByte(2);
355
- pump.minSpeed = sys.board.valueMaps.pumpTypes.get(pump.type).minSpeed;
356
- pump.maxSpeed = sys.board.valueMaps.pumpTypes.get(pump.type).maxSpeed;
357
- pump.speedStepSize = sys.board.valueMaps.pumpTypes.get(pump.type).speedStepSize;
358
- }
359
- private static processVF_IT(msg: Inbound) {
360
- // Sample Packet
361
- // [255, 0, 255], [165, 33, 15, 16, 27, 46], [2, 6, 15, 2, 0, 1, 29, 11, 35, 0, 30, 0, 30, 0, 30, 0, 30, 0, 30, 0, 30, 30, 55, 5, 10, 60, 5, 1, 50, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [3, 41]
362
- const pumpId = msg.extractPayloadByte(0);
363
- const pump = sys.pumps.getPumpByAddress(95 + pumpId);
364
- if (typeof pump.model === 'undefined') pump.model = 0;
365
- for (let circuitId = 1; circuitId <= sys.board.valueMaps.pumpTypes.get(pump.type).maxCircuits; circuitId++) {
366
- let _circuit = msg.extractPayloadByte(circuitId * 2 + 3);
367
- if (_circuit !== 0) {
368
- const circuit: PumpCircuit = pump.circuits.getItemById(circuitId, true);
369
- circuit.circuit = _circuit;
370
- circuit.flow = msg.extractPayloadByte(circuitId * 2 + 4);
371
- circuit.units = 1;
372
- }
373
- else {
374
- pump.circuits.removeItemById(_circuit);
375
- }
376
- }
377
- pump.backgroundCircuit = msg.extractPayloadByte(1);
378
- pump.filterSize = msg.extractPayloadByte(2) * 1000;
379
- pump.turnovers = msg.extractPayloadByte(3);
380
- pump.manualFilterGPM = msg.extractPayloadByte(21);
381
- pump.primingSpeed = msg.extractPayloadByte(22);
382
- pump.primingTime = (msg.extractPayloadByte(23) & 0xf);
383
- pump.minFlow = sys.board.valueMaps.pumpTypes.get(pump.type).minFlow;
384
- pump.maxFlow = sys.board.valueMaps.pumpTypes.get(pump.type).maxFlow;
385
- pump.flowStepSize = sys.board.valueMaps.pumpTypes.get(pump.type).flowStepSize;
386
- pump.maxSystemTime = msg.extractPayloadByte(23) >> 4;
387
- pump.maxPressureIncrease = msg.extractPayloadByte(24);
388
- pump.backwashFlow = msg.extractPayloadByte(25);
389
- pump.backwashTime = msg.extractPayloadByte(26);
390
- pump.rinseTime = msg.extractPayloadByte(27);
391
- pump.vacuumFlow = msg.extractPayloadByte(28);
392
- pump.vacuumTime = msg.extractPayloadByte(30);
393
- }
394
- private static processVSF_IT(msg: Inbound) {
395
- // Sample packet
396
- //[255, 0, 255][165, 33, 15, 16, 27, 46][2, 64, 0, 0, 2, 1, 33, 2, 4, 0, 30, 0, 30, 0, 30, 0, 30, 0, 30, 0, 30, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [2, 94]
397
- //[255, 0, 255][165, 1, 15, 16, 24, 31][1, 64, 0, 0, 0, 6, 5, 2, 8, 1, 11, 7, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 220, 152, 184, 122, 0, 0, 0, 0, 0][4, 24]
398
- const pumpId = msg.extractPayloadByte(0);
399
- const pump = sys.pumps.getPumpByAddress(95 + pumpId);
400
- if (typeof pump.model === 'undefined') pump.model = 0;
401
- for (let circuitId = 1; circuitId <= sys.board.valueMaps.pumpTypes.get(pump.type).maxCircuits; circuitId++) {
402
- let _circuit = msg.extractPayloadByte(circuitId * 2 + 3);
403
- if (_circuit !== 0){
404
- const circuit: PumpCircuit = pump.circuits.getItemById(circuitId, true);
405
- circuit.circuit = _circuit;
406
- circuit.units = (msg.extractPayloadByte(4) >> circuitId - 1 & 1) === 0 ? 1 : 0;
407
- if (circuit.units) circuit.flow = msg.extractPayloadByte(circuitId * 2 + 4);
408
- else circuit.speed = msg.extractPayloadByte(circuitId * 2 + 4) * 256 + msg.extractPayloadByte(circuitId + 21);
409
- }
410
- else {
411
- pump.circuits.removeItemById(_circuit);
412
- }
413
- }
414
- pump.speedStepSize = sys.board.valueMaps.pumpTypes.get(pump.type).speedStepSize;
415
- pump.flowStepSize = sys.board.valueMaps.pumpTypes.get(pump.type).flowStepSize;
416
- pump.minFlow = sys.board.valueMaps.pumpTypes.get(pump.type).minFlow;
417
- pump.maxFlow = sys.board.valueMaps.pumpTypes.get(pump.type).maxFlow;
418
- pump.minSpeed = sys.board.valueMaps.pumpTypes.get(pump.type).minSpeed;
419
- pump.maxSpeed = sys.board.valueMaps.pumpTypes.get(pump.type).maxSpeed;
420
- }
421
- }
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 {Inbound} from "../Messages";
19
+ import {sys, Pump, PumpCircuit} from "../../../Equipment";
20
+ import {state, CircuitState} from "../../../State";
21
+ import {ControllerType} from "../../../Constants";
22
+ import { logger } from "../../../../logger/Logger";
23
+ export class PumpMessage {
24
+ public static process(msg: Inbound): void {
25
+ switch (sys.controllerType) {
26
+ case ControllerType.IntelliCenter:
27
+ PumpMessage.processIntelliCenterPump(msg);
28
+ break;
29
+ case ControllerType.SunTouch:
30
+ case ControllerType.IntelliCom:
31
+ case ControllerType.EasyTouch:
32
+ case ControllerType.IntelliTouch:
33
+ PumpMessage.processPumpConfig_IT(msg);
34
+ break;
35
+ }
36
+ }
37
+ public static processPumpConfig_IT(msg: Inbound) {
38
+ // packet 24/27/152/155 - Pump Config: IntelliTouch. These will always be addressable pumps ds & ss are not included.
39
+ const pumpId = msg.extractPayloadByte(0);
40
+ let type = msg.extractPayloadByte(1); // Avoid setting this then setting it back if we are mapping to a different value.
41
+ let isActive = type !== 0 && pumpId <= sys.equipment.maxPumps;
42
+ // RKS: 04-14-21 - Only create the pump if it is available. If the pump was previously defined as another type
43
+ // then it will be removed and recreated.
44
+ // RKS: 05-06-23 - The original code did not search for the pump by its address. This is not correct.
45
+ let pump: Pump = sys.pumps.getPumpByAddress(95 + pumpId, isActive);
46
+ if(isActive) {
47
+ // Remap the combination pump types.
48
+ switch (type) {
49
+ case 0:
50
+ case 64:
51
+ case 169:
52
+ break;
53
+ case 255:
54
+ case 128:
55
+ case 134:
56
+ type = 128;
57
+ break;
58
+ default:
59
+ type = 1;
60
+ break;
61
+ }
62
+ if (pump.type !== type) {
63
+ sys.pumps.removePumpByAddress(95 + pumpId);
64
+ if (isActive) pump = sys.pumps.getPumpByAddress(95 + pumpId, isActive);
65
+ }
66
+ pump.address = pumpId + 95;
67
+ pump.master = 0;
68
+ switch (type) {
69
+ case 0: // none
70
+ pump.type = 0;
71
+ pump.isActive = false;
72
+ break;
73
+ case 64: // vsf
74
+ pump.type = type;
75
+ pump.isActive = true;
76
+ PumpMessage.processVSF_IT(msg);
77
+ break;
78
+ case 255: // vs 3050 on old panels.
79
+ case 128: // vs
80
+ case 134: // vs Ultra Efficiency
81
+ pump.type = 128;
82
+ pump.isActive = true;
83
+ PumpMessage.processVS_IT(msg);
84
+ break;
85
+ case 169: // vs+svrs
86
+ pump.type = 169;
87
+ pump.isActive = true;
88
+ PumpMessage.processVS_IT(msg);
89
+ break;
90
+ default: // vf - type is the background circuit
91
+ pump.type = 1; // force to type 1?
92
+ pump.isActive = true;
93
+ PumpMessage.processVF_IT(msg);
94
+ break;
95
+ }
96
+ if (typeof pump.name === 'undefined') pump.name = sys.board.valueMaps.pumpTypes.get(pump.type).desc;
97
+ const spump = state.pumps.getItemById(pump.id, true);
98
+ spump.name = pump.name;
99
+ spump.type = pump.type;
100
+ spump.isActive = pump.isActive;
101
+ spump.status = 0;
102
+ }
103
+ else {
104
+ // RKS: Remove any pump that is not defined in the system.
105
+ sys.pumps.removeItemById(pumpId);
106
+ state.pumps.removeItemById(pumpId);
107
+ }
108
+ msg.isProcessed = true;
109
+ }
110
+ private static processIntelliCenterPump(msg: Inbound) {
111
+ let pumpId: number;
112
+ let pump: Pump;
113
+ let msgId: number = msg.extractPayloadByte(1);
114
+ // IntelliCenter v3 uses big-endian 16-bit values for pump speeds/flows.
115
+ const useBigEndian = sys.controllerType === ControllerType.IntelliCenter && sys.equipment.isIntellicenterV3;
116
+ const readInt = (ndx: number) => useBigEndian ? msg.extractPayloadIntBE(ndx) : msg.extractPayloadInt(ndx);
117
+ // First process the pump types. This will allow us to add or remove any installed pumps. All subsequent messages will not create pumps in the collection.
118
+ if (msgId === 4) PumpMessage.processPumpType(msg);
119
+ if (msgId <= 15) {
120
+ let circuitId = 1;
121
+ pumpId = msgId + 1;
122
+ pump = sys.pumps.getItemById(pumpId);
123
+ if (pump.type === 1) { // If this is a single speed pump it will have the body stored in the first circuit position. All other pumps have no
124
+ // reference to the body.
125
+ pump.body = msg.extractPayloadByte(34);
126
+ // Clear the circuits as there should be none.
127
+ pump.circuits.clear();
128
+ }
129
+ else if (pump.type !== 0 && typeof pump.type !== 'undefined') {
130
+ for (let i = 34; i < msg.payload.length && circuitId <= sys.board.valueMaps.pumpTypes.get(pump.type).maxCircuits; i++) {
131
+ let circuit = msg.extractPayloadByte(i);
132
+ if (circuit !== 255) pump.circuits.getItemById(circuitId++, true).circuit = circuit + 1;
133
+ else pump.circuits.removeItemById(circuitId++);
134
+ }
135
+ }
136
+ // Speed/Flow
137
+ if (pump.type > 2) {
138
+ // Filter out the single speed and dual speed pumps. We have no flow or speed for these.
139
+ circuitId = 1;
140
+ for (let i = 18; i < msg.payload.length && circuitId <= sys.board.valueMaps.pumpTypes.get(pump.type).maxCircuits;) {
141
+ let circuit: PumpCircuit = pump.circuits.getItemById(circuitId);
142
+ let rate = readInt(i);
143
+ // If the rate is < 450 then this must be a flow based value.
144
+ if (rate < 450) {
145
+ circuit.flow = rate;
146
+ circuit.units = 1;
147
+ circuit.speed = undefined;
148
+ } else {
149
+ circuit.speed = rate;
150
+ circuit.units = 0;
151
+ circuit.flow = undefined;
152
+ }
153
+ i += 2;
154
+ circuitId++;
155
+ }
156
+ }
157
+ }
158
+ msg.isProcessed = true;
159
+ switch (msgId) {
160
+ case 0:
161
+ msg.isProcessed = true;
162
+ break;
163
+ case 1:
164
+ PumpMessage.processFlowStepSize(msg);
165
+ break;
166
+ case 2:
167
+ PumpMessage.processMinFlow(msg);
168
+ break;
169
+ case 3:
170
+ PumpMessage.processMaxFlow(msg);
171
+ break;
172
+ case 5:
173
+ PumpMessage.processAddress(msg);
174
+ break;
175
+ case 6:
176
+ PumpMessage.processPrimingTime(msg);
177
+ break;
178
+ case 7:
179
+ PumpMessage.processSpeedStepSize(msg);
180
+ break;
181
+ case 8: // Unknown
182
+ case 9:
183
+ case 10:
184
+ case 11:
185
+ case 12:
186
+ case 13:
187
+ case 14:
188
+ case 15:
189
+ break;
190
+ case 16:
191
+ PumpMessage.processMinSpeed(msg);
192
+ break;
193
+ case 17:
194
+ PumpMessage.processMaxSpeed(msg);
195
+ break;
196
+ case 18:
197
+ PumpMessage.processPrimingSpeed(msg);
198
+ break;
199
+ case 19: // Pump names
200
+ case 20:
201
+ case 21:
202
+ case 22:
203
+ case 23:
204
+ case 24:
205
+ case 25:
206
+ case 26:
207
+ PumpMessage.processPumpNames(msg);
208
+ break;
209
+ default:
210
+ logger.debug(`Unprocessed Config Message ${msg.toPacket()}`)
211
+ break;
212
+
213
+ }
214
+ }
215
+ private static processFlowStepSize(msg: Inbound) {
216
+ let pumpId = 1;
217
+ for (let i = 2; i < msg.payload.length && pumpId <= sys.equipment.maxPumps; i++) {
218
+ sys.pumps.getItemById(pumpId++).flowStepSize = msg.extractPayloadByte(i);
219
+ }
220
+ }
221
+ private static processMinFlow(msg: Inbound) {
222
+ let pumpId = 1;
223
+ for (let i = 2; i < msg.payload.length && pumpId <= sys.equipment.maxPumps; i++) {
224
+ sys.pumps.getItemById(pumpId++).minFlow = msg.extractPayloadByte(i);
225
+ }
226
+ }
227
+ private static processMaxFlow(msg: Inbound) {
228
+ let pumpId = 1;
229
+ for (let i = 2; i < msg.payload.length && pumpId <= sys.equipment.maxPumps; i++) {
230
+ sys.pumps.getItemById(pumpId++).maxFlow = msg.extractPayloadByte(i);
231
+ }
232
+ }
233
+ private static processPumpType(msg: Inbound) {
234
+ let pumpId = 1;
235
+ for (let i = 2; i < msg.payload.length && pumpId <= sys.equipment.maxPumps; i++) {
236
+ let type = msg.extractPayloadByte(i);
237
+ let pump: Pump = sys.pumps.getItemById(pumpId++, type !== 0);
238
+ if (type === 0) {
239
+ sys.pumps.removeItemById(pump.id); // Remove the pump if we don't need it.
240
+ state.pumps.removeItemById(pump.id);
241
+ }
242
+ else {
243
+ if (pump.type !== type) {
244
+ let ptype = sys.board.valueMaps.pumpTypes.transform(type);
245
+ if (ptype.name === 'ss') pump.circuits.clear();
246
+ pump.model = 0;
247
+ }
248
+ if (typeof pump.model === 'undefined') pump.model = 0;
249
+ pump.type = type;
250
+ pump.master = 0;
251
+ let spump = state.pumps.getItemById(pump.id, true);
252
+ spump.type = pump.type;
253
+ spump.isActive = pump.isActive = true;
254
+ }
255
+ }
256
+ }
257
+ private static processAddress(msg: Inbound) {
258
+ let pumpId = 1;
259
+ for (let i = 2; i < msg.payload.length && pumpId <= sys.equipment.maxPumps; i++) {
260
+ sys.pumps.getItemById(pumpId++).address = msg.extractPayloadByte(i);
261
+ }
262
+ }
263
+ private static processPrimingTime(msg: Inbound) {
264
+ let pumpId = 1;
265
+ for (let i = 2; i < msg.payload.length && pumpId <= sys.equipment.maxPumps; i++) {
266
+ sys.pumps.getItemById(pumpId++).primingTime = msg.extractPayloadByte(i);
267
+ }
268
+ }
269
+ private static processSpeedStepSize(msg: Inbound) {
270
+ let pumpId = 1;
271
+ for (let i = 2; i < msg.payload.length && pumpId <= sys.equipment.maxPumps; i++) {
272
+ sys.pumps.getItemById(pumpId++).speedStepSize = msg.extractPayloadByte(i) * 10;
273
+ }
274
+ }
275
+ private static processMinSpeed(msg: Inbound) {
276
+ let pumpId = 1;
277
+ const useBigEndian = sys.controllerType === ControllerType.IntelliCenter && sys.equipment.isIntellicenterV3;
278
+ for (let i = 2; i < msg.payload.length && pumpId <= sys.equipment.maxPumps;) {
279
+ sys.pumps.getItemById(pumpId++).minSpeed = useBigEndian ? msg.extractPayloadIntBE(i) : msg.extractPayloadInt(i);
280
+ i += 2;
281
+ }
282
+ }
283
+ private static processMaxSpeed(msg: Inbound) {
284
+ let pumpId = 1;
285
+ const useBigEndian = sys.controllerType === ControllerType.IntelliCenter && sys.equipment.isIntellicenterV3;
286
+ for (let i = 2; i < msg.payload.length && pumpId <= sys.equipment.maxPumps;) {
287
+ sys.pumps.getItemById(pumpId++).maxSpeed = useBigEndian ? msg.extractPayloadIntBE(i) : msg.extractPayloadInt(i);
288
+ i += 2;
289
+ }
290
+ }
291
+ private static processPrimingSpeed(msg: Inbound) {
292
+ let pumpId = 1;
293
+ const useBigEndian = sys.controllerType === ControllerType.IntelliCenter && sys.equipment.isIntellicenterV3;
294
+ for (let i = 2; i < msg.payload.length && pumpId <= sys.equipment.maxPumps;) {
295
+ sys.pumps.getItemById(pumpId++).primingSpeed = useBigEndian ? msg.extractPayloadIntBE(i) : msg.extractPayloadInt(i);
296
+ i += 2;
297
+ }
298
+ }
299
+ private static processPumpNames(msg: Inbound) {
300
+ let pumpId = (msg.extractPayloadByte(1) - 19) * 2 + 1;
301
+ if (pumpId <= sys.equipment.maxPumps) {
302
+ let pump = sys.pumps.getItemById(pumpId);
303
+ pump.name = msg.extractPayloadString(2, 16);
304
+ if (pump.isActive) state.pumps.getItemById(pumpId).name = pump.name;
305
+ pumpId++;
306
+ }
307
+ if (pumpId <= sys.equipment.maxPumps) {
308
+ let pump = sys.pumps.getItemById(pumpId);
309
+ pump.name = msg.extractPayloadString(18, 16);
310
+ if (pump.isActive) state.pumps.getItemById(pumpId).name = pump.name;
311
+ pumpId++;
312
+ }
313
+ }
314
+ private static processVS_IT(msg: Inbound) {
315
+ // Sample Packet
316
+ // [255, 0, 255], [165, 33, 15, 16, 27, 46], [1, 128, 1, 2, 0, 1, 6, 2, 12, 4, 9, 11, 7, 6, 7, 128, 8, 132, 3, 15, 5, 3, 234, 128, 46, 108, 58, 2, 232, 220, 232, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [8, 5]
317
+ const pumpId = msg.extractPayloadByte(0);
318
+ const pump = sys.pumps.getPumpByAddress(95 + pumpId);
319
+ // [1, 128, 0, 2, 0, 6, 5, 1, 5, 158, 9, 2, 10, 0, 3, 0, 3, 0, 3, 0, 3, 3, 120, 20, 146, 240, 232, 232, 232, 232, 232]
320
+ // byte | val |
321
+ // 0 | 1 | PumpId = 1
322
+ // 1 | 128 | Pump Type = VS
323
+ // 2 | 0 | Priming Time = 0
324
+ // 3 | 2 | Unknown
325
+ // 4 | 0 | Unknown
326
+ // 5 | 6 | Circuit Speed #1 = Pool
327
+ // 6 | 5 | Big endian for the speed (1400 rpm with byte(22))
328
+ // 7 | 1 | Circuit Speed #2 = Spa
329
+ // 8 | 5 | Big endian for the speed (1300 rpm with byte(23))
330
+ // 9 | 158 | Circuit Speed #3 = Solar
331
+ // 10 | 9 | Big endian for the speed (2450 rpm with byte(24))
332
+ // 11 | 2 | Circuit Speed #4 = Air blower (Aux-2)
333
+ // 12 | 10 | Big endian speed for the speed (2800 rpm with byte(25))
334
+ // 13 | 0 | Circuit Speed #5 = No circuit
335
+ // 14 | 3 | Big endian speed for the speed (1000 rpm with byte(26))
336
+ // 15 | 0 | Circuit speed #6 = No circuit
337
+ // 16 | 3 | Big endian speed for the speed (1000 rpm with byte(27))
338
+ // 17 | 0 | Circuit speed #7 = No circuit
339
+ // 18 | 3 | Big endian speed for the speed (1000 rpm with byte(28))
340
+ // 19 | 0 | Circuit speed #8 = No circuit
341
+ // 20 | 3 | Big endian speed for the speed (1000 rpm with byte(29))
342
+ // 21 | 3 | Big endian speed for the priming speed (1000 rpm with byte(30))
343
+ // All 30 bytes on this message are accounted for except for byte 3 & 4.
344
+ if (typeof pump.model === 'undefined') pump.model = 0;
345
+ for (let circuitId = 1; circuitId <= sys.board.valueMaps.pumpTypes.get(pump.type).maxCircuits; circuitId++) {
346
+ let _circuit = msg.extractPayloadByte(circuitId * 2 + 3);
347
+ if (_circuit !== 0) {
348
+ let circuit = pump.circuits.getItemById(circuitId, true);
349
+ circuit.circuit = _circuit;
350
+ circuit.speed =
351
+ msg.extractPayloadByte(circuitId * 2 + 4) * 256 +
352
+ msg.extractPayloadByte(circuitId + 21);
353
+ circuit.units = 0;
354
+ }
355
+ else {
356
+ pump.circuits.removeItemById(circuitId);
357
+ }
358
+ }
359
+ pump.primingSpeed = msg.extractPayloadByte(21) * 256 + msg.extractPayloadByte(30);
360
+ pump.primingTime = msg.extractPayloadByte(2);
361
+ pump.minSpeed = sys.board.valueMaps.pumpTypes.get(pump.type).minSpeed;
362
+ pump.maxSpeed = sys.board.valueMaps.pumpTypes.get(pump.type).maxSpeed;
363
+ pump.speedStepSize = sys.board.valueMaps.pumpTypes.get(pump.type).speedStepSize;
364
+ }
365
+ private static processVF_IT(msg: Inbound) {
366
+ // Sample Packet
367
+ // [255, 0, 255], [165, 33, 15, 16, 27, 46], [2, 6, 15, 2, 0, 1, 29, 11, 35, 0, 30, 0, 30, 0, 30, 0, 30, 0, 30, 0, 30, 30, 55, 5, 10, 60, 5, 1, 50, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [3, 41]
368
+ const pumpId = msg.extractPayloadByte(0);
369
+ const pump = sys.pumps.getPumpByAddress(95 + pumpId);
370
+ if (typeof pump.model === 'undefined') pump.model = 0;
371
+ for (let circuitId = 1; circuitId <= sys.board.valueMaps.pumpTypes.get(pump.type).maxCircuits; circuitId++) {
372
+ let _circuit = msg.extractPayloadByte(circuitId * 2 + 3);
373
+ if (_circuit !== 0) {
374
+ const circuit: PumpCircuit = pump.circuits.getItemById(circuitId, true);
375
+ circuit.circuit = _circuit;
376
+ circuit.flow = msg.extractPayloadByte(circuitId * 2 + 4);
377
+ circuit.units = 1;
378
+ }
379
+ else {
380
+ pump.circuits.removeItemById(_circuit);
381
+ }
382
+ }
383
+ pump.backgroundCircuit = msg.extractPayloadByte(1);
384
+ pump.filterSize = msg.extractPayloadByte(2) * 1000;
385
+ pump.turnovers = msg.extractPayloadByte(3);
386
+ pump.manualFilterGPM = msg.extractPayloadByte(21);
387
+ pump.primingSpeed = msg.extractPayloadByte(22);
388
+ pump.primingTime = (msg.extractPayloadByte(23) & 0xf);
389
+ pump.minFlow = sys.board.valueMaps.pumpTypes.get(pump.type).minFlow;
390
+ pump.maxFlow = sys.board.valueMaps.pumpTypes.get(pump.type).maxFlow;
391
+ pump.flowStepSize = sys.board.valueMaps.pumpTypes.get(pump.type).flowStepSize;
392
+ pump.maxSystemTime = msg.extractPayloadByte(23) >> 4;
393
+ pump.maxPressureIncrease = msg.extractPayloadByte(24);
394
+ pump.backwashFlow = msg.extractPayloadByte(25);
395
+ pump.backwashTime = msg.extractPayloadByte(26);
396
+ pump.rinseTime = msg.extractPayloadByte(27);
397
+ pump.vacuumFlow = msg.extractPayloadByte(28);
398
+ pump.vacuumTime = msg.extractPayloadByte(30);
399
+ }
400
+ private static processVSF_IT(msg: Inbound) {
401
+ // Sample packet
402
+ //[255, 0, 255][165, 33, 15, 16, 27, 46][2, 64, 0, 0, 2, 1, 33, 2, 4, 0, 30, 0, 30, 0, 30, 0, 30, 0, 30, 0, 30, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [2, 94]
403
+ //[255, 0, 255][165, 1, 15, 16, 24, 31][1, 64, 0, 0, 0, 6, 5, 2, 8, 1, 11, 7, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 220, 152, 184, 122, 0, 0, 0, 0, 0][4, 24]
404
+ const pumpId = msg.extractPayloadByte(0);
405
+ const pump = sys.pumps.getPumpByAddress(95 + pumpId);
406
+ if (typeof pump.model === 'undefined') pump.model = 0;
407
+ for (let circuitId = 1; circuitId <= sys.board.valueMaps.pumpTypes.get(pump.type).maxCircuits; circuitId++) {
408
+ let _circuit = msg.extractPayloadByte(circuitId * 2 + 3);
409
+ if (_circuit !== 0){
410
+ const circuit: PumpCircuit = pump.circuits.getItemById(circuitId, true);
411
+ circuit.circuit = _circuit;
412
+ circuit.units = (msg.extractPayloadByte(4) >> circuitId - 1 & 1) === 0 ? 1 : 0;
413
+ if (circuit.units) circuit.flow = msg.extractPayloadByte(circuitId * 2 + 4);
414
+ else circuit.speed = msg.extractPayloadByte(circuitId * 2 + 4) * 256 + msg.extractPayloadByte(circuitId + 21);
415
+ }
416
+ else {
417
+ pump.circuits.removeItemById(_circuit);
418
+ }
419
+ }
420
+ pump.speedStepSize = sys.board.valueMaps.pumpTypes.get(pump.type).speedStepSize;
421
+ pump.flowStepSize = sys.board.valueMaps.pumpTypes.get(pump.type).flowStepSize;
422
+ pump.minFlow = sys.board.valueMaps.pumpTypes.get(pump.type).minFlow;
423
+ pump.maxFlow = sys.board.valueMaps.pumpTypes.get(pump.type).maxFlow;
424
+ pump.minSpeed = sys.board.valueMaps.pumpTypes.get(pump.type).minSpeed;
425
+ pump.maxSpeed = sys.board.valueMaps.pumpTypes.get(pump.type).maxSpeed;
426
+ }
427
+ }