nodejs-poolcontroller 7.6.0 → 7.6.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 (85) hide show
  1. package/.eslintrc.json +44 -44
  2. package/.github/ISSUE_TEMPLATE/bug_report.md +52 -52
  3. package/CONTRIBUTING.md +74 -74
  4. package/Changelog +215 -215
  5. package/Dockerfile +17 -17
  6. package/Gruntfile.js +40 -40
  7. package/LICENSE +661 -661
  8. package/README.md +191 -186
  9. package/app.ts +0 -0
  10. package/config/Config.ts +24 -2
  11. package/config/VersionCheck.ts +27 -12
  12. package/config copy.json +299 -299
  13. package/controller/Constants.ts +0 -0
  14. package/controller/Equipment.ts +2459 -2405
  15. package/controller/Errors.ts +180 -180
  16. package/controller/Lockouts.ts +436 -422
  17. package/controller/State.ts +51 -26
  18. package/controller/boards/BoardFactory.ts +45 -45
  19. package/controller/boards/EasyTouchBoard.ts +2653 -2537
  20. package/controller/boards/IntelliCenterBoard.ts +4230 -4034
  21. package/controller/boards/IntelliComBoard.ts +63 -63
  22. package/controller/boards/IntelliTouchBoard.ts +241 -241
  23. package/controller/boards/NixieBoard.ts +1675 -1660
  24. package/controller/boards/SystemBoard.ts +4697 -4463
  25. package/controller/comms/Comms.ts +138 -1
  26. package/controller/comms/messages/Messages.ts +3 -5
  27. package/controller/comms/messages/config/ChlorinatorMessage.ts +1 -1
  28. package/controller/comms/messages/config/CircuitGroupMessage.ts +0 -0
  29. package/controller/comms/messages/config/CircuitMessage.ts +0 -0
  30. package/controller/comms/messages/config/ConfigMessage.ts +0 -0
  31. package/controller/comms/messages/config/CoverMessage.ts +0 -0
  32. package/controller/comms/messages/config/CustomNameMessage.ts +30 -30
  33. package/controller/comms/messages/config/EquipmentMessage.ts +0 -0
  34. package/controller/comms/messages/config/ExternalMessage.ts +9 -7
  35. package/controller/comms/messages/config/FeatureMessage.ts +0 -0
  36. package/controller/comms/messages/config/GeneralMessage.ts +0 -0
  37. package/controller/comms/messages/config/HeaterMessage.ts +0 -20
  38. package/controller/comms/messages/config/IntellichemMessage.ts +0 -0
  39. package/controller/comms/messages/config/OptionsMessage.ts +25 -1
  40. package/controller/comms/messages/config/PumpMessage.ts +0 -0
  41. package/controller/comms/messages/config/RemoteMessage.ts +0 -0
  42. package/controller/comms/messages/config/ScheduleMessage.ts +347 -342
  43. package/controller/comms/messages/config/SecurityMessage.ts +0 -0
  44. package/controller/comms/messages/config/ValveMessage.ts +0 -0
  45. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +0 -0
  46. package/controller/comms/messages/status/EquipmentStateMessage.ts +1 -1
  47. package/controller/comms/messages/status/HeaterStateMessage.ts +86 -86
  48. package/controller/comms/messages/status/IntelliChemStateMessage.ts +445 -397
  49. package/controller/comms/messages/status/IntelliValveStateMessage.ts +35 -35
  50. package/controller/comms/messages/status/PumpStateMessage.ts +0 -0
  51. package/controller/comms/messages/status/VersionMessage.ts +0 -0
  52. package/controller/nixie/Nixie.ts +162 -162
  53. package/controller/nixie/NixieEquipment.ts +103 -103
  54. package/controller/nixie/bodies/Body.ts +120 -120
  55. package/controller/nixie/bodies/Filter.ts +135 -135
  56. package/controller/nixie/chemistry/ChemController.ts +2498 -2398
  57. package/controller/nixie/chemistry/Chlorinator.ts +314 -314
  58. package/controller/nixie/circuits/Circuit.ts +248 -245
  59. package/controller/nixie/heaters/Heater.ts +648 -600
  60. package/controller/nixie/pumps/Pump.ts +661 -661
  61. package/controller/nixie/schedules/Schedule.ts +257 -257
  62. package/controller/nixie/valves/Valve.ts +170 -170
  63. package/defaultConfig.json +286 -286
  64. package/issue_template.md +51 -51
  65. package/logger/DataLogger.ts +448 -448
  66. package/logger/Logger.ts +0 -0
  67. package/package.json +56 -56
  68. package/tsconfig.json +25 -25
  69. package/web/Server.ts +2 -2
  70. package/web/bindings/influxDB.json +1021 -981
  71. package/web/bindings/mqtt.json +654 -654
  72. package/web/bindings/mqttAlt.json +684 -684
  73. package/web/bindings/rulesManager.json +54 -54
  74. package/web/bindings/smartThings-Hubitat.json +31 -31
  75. package/web/bindings/valveRelays.json +20 -20
  76. package/web/bindings/vera.json +25 -25
  77. package/web/interfaces/baseInterface.ts +136 -136
  78. package/web/interfaces/httpInterface.ts +124 -124
  79. package/web/interfaces/influxInterface.ts +245 -241
  80. package/web/interfaces/mqttInterface.ts +475 -475
  81. package/web/services/config/Config.ts +10 -108
  82. package/web/services/config/ConfigSocket.ts +0 -0
  83. package/web/services/state/State.ts +71 -4
  84. package/web/services/state/StateSocket.ts +0 -0
  85. package/web/services/utilities/Utilities.ts +42 -42
@@ -82,6 +82,130 @@ export class Connection {
82
82
  return this._cfg;
83
83
  } catch (err) { return Promise.reject(err); }
84
84
  }
85
+ // RKS: 12-18-21 This is a bullpen method to work through the inconsistencies in the socket implementation for node. There
86
+ // are issues related to the event listeners and the construction of a socket. We want an implementation that awaits until the socket
87
+ // is completely open before continuing.
88
+ // We also need to be able to destroy the port without it restarting on its own when tearing the port down or deliberately closing it. This means
89
+ // that the listeners need to be removed before closing via method but re-open the port when the close is hit prematurely.
90
+ protected async openNetSerialPort(): Promise<boolean> {
91
+ try {
92
+ let opts: net.NetConnectOpts = {
93
+ host: this._cfg.netHost,
94
+ port: this._cfg.netPort
95
+ };
96
+ if (typeof this.connTimer !== 'undefined' && this.connTimer) clearTimeout(this.connTimer);
97
+ this.connTimer = null;
98
+ let ret = await new Promise<boolean>((resolve, _) => {
99
+ let nc = net.createConnection(opts, () => {
100
+ nc.on('connect', () => { logger.info(`Net connect (socat) connected to: ${this._cfg.netHost}:${this._cfg.netPort}`); }); // Socket is opened but not yet ready.
101
+ nc.on('ready', () => {
102
+ this.isOpen = true;
103
+ this.isRTS = true;
104
+ logger.info(`Net connect (socat) ready and communicating: ${this._cfg.netHost}:${this._cfg.netPort}`);
105
+ nc.on('data', (data) => {
106
+ if (data.length > 0 && !this.isPaused) this.emitter.emit('packetread', data);
107
+ });
108
+ this._port = nc;
109
+ // After the port is fully opened, set an inactivity timeout to restart it when it stops communicating.
110
+ nc.setTimeout(Math.max(this._cfg.inactivityRetry, 10) * 1000, async () => {
111
+ logger.warn(`Net connect (socat) connection idle: ${this._cfg.netHost}:${this._cfg.netPort} retrying connection.`);
112
+ try {
113
+ await conn.endAsync();
114
+ await conn.openAsync();
115
+ } catch (err) { logger.error(`Net connect (socat) error retrying connection ${err.message}`); }
116
+ });
117
+ resolve(true);
118
+ });
119
+ nc.on('close', (hadError: boolean) => {
120
+ this.isOpen = false;
121
+ if (typeof this._port !== 'undefined') {
122
+ this._port.destroy();
123
+ this._port.removeAllListeners();
124
+ }
125
+ this._port = undefined;
126
+ this.buffer.clearOutbound();
127
+ logger.info(`Net connect (socat) closed ${hadError === true ? 'due to error' : ''}: ${this._cfg.netHost}:${this._cfg.netPort}`);
128
+ if (!this._closing) {
129
+ // If we are closing manually this event should have been cleared already and should never be called. If this is fired out
130
+ // of sequence then we will check the closing flag to ensure we are not forcibly closing the socket.
131
+ if (typeof this.connTimer !== 'undefined' && this.connTimer) {
132
+ clearTimeout(this.connTimer);
133
+ this.connTimer = null;
134
+ }
135
+ this.connTimer = setTimeout(async () => {
136
+ try {
137
+ // We are already closed so give some inactivity retry and try again.
138
+ await conn.openAsync();
139
+ } catch (err) { }
140
+ }, this._cfg.inactivityRetry * 1000);
141
+ }
142
+ });
143
+ nc.on('end', () => { // Happens when the other end of the socket closes.
144
+ this.isOpen = false;
145
+ logger.info(`Net connect (socat) end event was fired`);
146
+ });
147
+ });
148
+ nc.once('error', (err) => {
149
+ // if the promise has already been fulfilled, but the error happens later, we don't want to call the promise again.
150
+ if (this._cfg.inactivityRetry > 0) {
151
+ logger.error(`Net connect (socat) connection error: ${err}. Retry in ${this._cfg.inactivityRetry} seconds`);
152
+ this.connTimer = setTimeout(async () => {
153
+ try {
154
+ await conn.closeAsync();
155
+ await conn.openAsync();
156
+ } catch (err) { }
157
+ }, this._cfg.inactivityRetry * 1000);
158
+ }
159
+ else logger.error(`Net connect (socat) connection error: ${err}. Never retrying -- No retry time set`);
160
+ resolve(false);
161
+ });
162
+ });
163
+ return ret;
164
+ } catch (err) { logger.error(`Error opening net serial port. ${this._cfg.netHost}:${this._cfg.netPort}`); }
165
+ }
166
+ protected async closeNetSerialPort(): Promise<boolean> {
167
+ try {
168
+ this._closing = true;
169
+ if (this.connTimer) clearTimeout(this.connTimer);
170
+ this.connTimer = null;
171
+ if (typeof this._port !== 'undefined' && this.isOpen) {
172
+ let success = await new Promise<boolean>((resolve, reject) => {
173
+ if (this._cfg.netConnect) {
174
+ this._port.removeAllListeners();
175
+ this._port.once('error', (err) => {
176
+ if (err) {
177
+ logger.error(`Net connect (socat) error closing ${this._cfg.netHost}:${this._cfg.netPort}: ${err}`);
178
+ resolve(false);
179
+ }
180
+ else {
181
+ conn._port = undefined;
182
+ this.isOpen = false;
183
+ logger.info(`Successfully closed (socat) port ${this._cfg.netHost}:${this._cfg.netPort}`);
184
+ resolve(true);
185
+ }
186
+ });
187
+ this._port.once('close', (p) => {
188
+ this.isOpen = false;
189
+ this._port = undefined;
190
+ logger.info(`Net connect (socat) successfully closed: ${this._cfg.netHost}:${this._cfg.netPort}`);
191
+ resolve(true);
192
+ });
193
+ this._port.destroy();
194
+ }
195
+ else {
196
+ resolve(true);
197
+ conn._port = undefined;
198
+ }
199
+ });
200
+ if (success) {
201
+ if (typeof conn.buffer !== 'undefined') conn.buffer.close();
202
+ }
203
+ return success;
204
+ }
205
+ return true;
206
+ } catch (err) { logger.error(`Error closing comms connection: ${err.message}`); return Promise.resolve(false); }
207
+
208
+ }
85
209
  public async openAsync(): Promise<boolean> {
86
210
  if (typeof this.buffer === 'undefined') {
87
211
  this.buffer = new SendRecieveBuffer();
@@ -110,6 +234,20 @@ export class Connection {
110
234
  if (typeof this._port !== 'undefined') this._port.destroy();
111
235
  this._port = undefined;
112
236
  this.buffer.clearOutbound();
237
+ if (!this._closing) {
238
+ // If we are closing manually this event should have been cleared already and should never be called. If this is fired out
239
+ // of sequence then we will check the closing flag to ensure we are not forcibly closing the socket.
240
+ if (typeof this.connTimer !== 'undefined' && this.connTimer) {
241
+ clearTimeout(this.connTimer);
242
+ this.connTimer = null;
243
+ }
244
+ this.connTimer = setTimeout(async () => {
245
+ try {
246
+ // We are already closed so give some inactivity retry and try again.
247
+ await conn.openAsync();
248
+ } catch (err) { }
249
+ }, this._cfg.inactivityRetry * 1000);
250
+ }
113
251
  logger.info(`Net connect (socat) closed ${p === true ? 'due to error' : ''}: ${this._cfg.netHost}:${this._cfg.netPort}`);
114
252
  });
115
253
  nc.on('end', () => { // Happens when the other end of the socket closes.
@@ -262,7 +400,6 @@ export class Connection {
262
400
  if (success) {
263
401
  if (typeof conn.buffer !== 'undefined') conn.buffer.close();
264
402
  }
265
-
266
403
  return success;
267
404
  }
268
405
  return true;
@@ -487,14 +487,12 @@ export class Inbound extends Message {
487
487
  CircuitMessage.processTouch(this);
488
488
  break;
489
489
  case 40:
490
+ case 168:
490
491
  OptionsMessage.process(this);
491
492
  break;
492
493
  case 41:
493
494
  CircuitGroupMessage.process(this);
494
495
  break;
495
- case 168:
496
- if (sys.controllerType !== ControllerType.Unknown) HeaterMessage.process(this);
497
- break;
498
496
  case 197:
499
497
  EquipmentStateMessage.process(this); // Date/Time request
500
498
  break;
@@ -632,11 +630,11 @@ export class Outbound extends OutboundCommon {
632
630
  out.onComplete = obj.onComplete;
633
631
  out.onAbort = obj.onAbort;
634
632
  out.timeout = obj.timeout;
635
- for (let i = 0; i < out.header.length; i++){
633
+ for (let i = 0; i < out.header.length; i++) {
636
634
  if (out.header[i] >= 0 && out.header[i] <= 255 && out.header[i] !== null && typeof out.header[i] !== 'undefined') continue;
637
635
  throw new OutboundMessageError(out, `Invalid header detected: ${out.toShortPacket()}`);
638
636
  }
639
- for (let i = 0; i < out.payload.length; i++){
637
+ for (let i = 0; i < out.payload.length; i++) {
640
638
  if (out.payload[i] >= 0 && out.payload[i] <= 255 && out.payload[i] !== null && typeof out.payload[i] !== 'undefined') continue;
641
639
  throw new OutboundMessageError(out, `Invalid payload detected: ${out.toShortPacket()}`);
642
640
  }
@@ -38,7 +38,7 @@ export class ChlorinatorMessage {
38
38
  if (!chlor.disabled && !chlor.isDosing) {
39
39
  // RKS: We don't want to change the setpoints if our chem controller disabled
40
40
  // the chlorinator. These should be 0.
41
- if (msg.extractPayloadByte(i + 10) === 0) logger.info(`Changing pool setpoint to 0 ${msg.extractPayloadByte(i + 10)}`);
41
+ if (msg.extractPayloadByte(i + 10) === 0 && chlor.poolSetpoint > 0) logger.info(`Changing pool setpoint to 0 ${msg.extractPayloadByte(i + 10)}`);
42
42
 
43
43
  chlor.poolSetpoint = msg.extractPayloadByte(i + 10);
44
44
  chlor.spaSetpoint = msg.extractPayloadByte(i + 14);
File without changes
File without changes
@@ -1,31 +1,31 @@
1
- /* nodejs-poolController. An application to control pool equipment.
2
- Copyright (C) 2016, 2017, 2018, 2019, 2020. Russell Goldin, tagyoureit. russ.goldin@gmail.com
3
-
4
- This program is free software: you can redistribute it and/or modify
5
- it under the terms of the GNU Affero General Public License as
6
- published by the Free Software Foundation, either version 3 of the
7
- License, or (at your option) any later version.
8
-
9
- This program is distributed in the hope that it will be useful,
10
- but WITHOUT ANY WARRANTY; without even the implied warranty of
11
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
- GNU Affero General Public License for more details.
13
-
14
- You should have received a copy of the GNU Affero General Public License
15
- along with this program. If not, see <http://www.gnu.org/licenses/>.
16
- */
17
- import { Inbound } from "../Messages";
18
- import { sys } from "../../../Equipment";
19
- export class CustomNameMessage
20
- {
21
- public static process ( msg: Inbound ): void
22
- {
23
- let customNameId = msg.extractPayloadByte( 0 );
24
- let customName = sys.customNames.getItemById( customNameId, customNameId <= sys.equipment.maxCustomNames );
25
- customName.name = msg.extractPayloadString( 1, 11 );
26
- // customName.isActive = customNameId <= sys.equipment.maxCustomNames && !customName.name.includes('USERNAME-')
27
- if (customNameId >= sys.equipment.maxCustomNames) sys.equipment.maxCustomNames = customNameId + 1;
28
- sys.board.system.syncCustomNamesValueMap();
29
- msg.isProcessed = true;
30
- }
1
+ /* nodejs-poolController. An application to control pool equipment.
2
+ Copyright (C) 2016, 2017, 2018, 2019, 2020. Russell Goldin, tagyoureit. russ.goldin@gmail.com
3
+
4
+ This program is free software: you can redistribute it and/or modify
5
+ it under the terms of the GNU Affero General Public License as
6
+ published by the Free Software Foundation, either version 3 of the
7
+ License, or (at your option) any later version.
8
+
9
+ This program is distributed in the hope that it will be useful,
10
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ GNU Affero General Public License for more details.
13
+
14
+ You should have received a copy of the GNU Affero General Public License
15
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+ */
17
+ import { Inbound } from "../Messages";
18
+ import { sys } from "../../../Equipment";
19
+ export class CustomNameMessage
20
+ {
21
+ public static process ( msg: Inbound ): void
22
+ {
23
+ let customNameId = msg.extractPayloadByte( 0 );
24
+ let customName = sys.customNames.getItemById( customNameId, customNameId <= sys.equipment.maxCustomNames );
25
+ customName.name = msg.extractPayloadString( 1, 11 );
26
+ // customName.isActive = customNameId <= sys.equipment.maxCustomNames && !customName.name.includes('USERNAME-')
27
+ if (customNameId >= sys.equipment.maxCustomNames) sys.equipment.maxCustomNames = customNameId + 1;
28
+ sys.board.system.syncCustomNamesValueMap();
29
+ msg.isProcessed = true;
30
+ }
31
31
  }
@@ -217,7 +217,7 @@ export class ExternalMessage {
217
217
  if (group.isActive) {
218
218
  for (let i = 0; i < 16; i++) {
219
219
  let circuitId = msg.extractPayloadByte(i + 6);
220
- let circuit = group.circuits.getItemById(i + 1, circuitId !== 255);
220
+ let circuit = group.circuits.getItemById(i + 1, circuitId < 255);
221
221
  if (circuitId === 255) group.circuits.removeItemById(i + 1);
222
222
  circuit.circuit = circuitId + 1;
223
223
 
@@ -429,13 +429,13 @@ export class ExternalMessage {
429
429
  // [11] = No sequencing underway.
430
430
  switch (byte) {
431
431
  case 0: // Sync
432
- lg.action = 1;
432
+ lg.action = sys.board.valueMaps.circuitActions.getValue('colorsync');
433
433
  break;
434
434
  case 1: // Color swim
435
- lg.action = 3;
435
+ lg.action = sys.board.valueMaps.circuitActions.getValue('colorswim');
436
436
  break;
437
437
  case 2: // Color set
438
- lg.action = 2;
438
+ lg.action = sys.board.valueMaps.circuitActions.getValue('colorset');
439
439
  break;
440
440
  default:
441
441
  lg.action = 0;
@@ -500,11 +500,14 @@ export class ExternalMessage {
500
500
  let startTime = msg.extractPayloadInt(3);
501
501
  let endTime = msg.extractPayloadInt(5);
502
502
  let circuit = msg.extractPayloadByte(7) + 1;
503
- let cfg = sys.schedules.getItemById(schedId, circuit !== 256 && startTime !== 0 && endTime !== 0);
504
- cfg.isActive = (circuit !== 256 && startTime !== 0 && endTime !== 0);
503
+ let isActive = (msg.extractPayloadByte(8) & 128) === 128; // Inactive schedules do not have bit 8 set.
504
+ let cfg = sys.schedules.getItemById(schedId, isActive);
505
+ let s = state.schedules.getItemById(schedId, cfg.isActive);
506
+ //cfg.isActive = (circuit !== 256);
505
507
  cfg.startTime = startTime;
506
508
  cfg.endTime = endTime;
507
509
  cfg.circuit = circuit;
510
+ cfg.isActive = isActive;
508
511
  let byte = msg.extractPayloadByte(8);
509
512
  cfg.scheduleType = (byte & 1 & 0xFF) === 1 ? 0 : 128;
510
513
  if ((byte & 4 & 0xFF) === 4) cfg.startTimeType = 1;
@@ -525,7 +528,6 @@ export class ExternalMessage {
525
528
  cfg.heatSource = hs;
526
529
  cfg.heatSetpoint = msg.extractPayloadByte(14);
527
530
  cfg.coolSetpoint = msg.extractPayloadByte(15);
528
- let s = state.schedules.getItemById(schedId, cfg.isActive);
529
531
  if (cfg.isActive) {
530
532
  let s = state.schedules.getItemById(schedId, cfg.isActive);
531
533
  s.isActive = cfg.isActive = true;
@@ -221,26 +221,6 @@ export class HeaterMessage {
221
221
  sys.equipment.setEquipmentIds();
222
222
  msg.isProcessed = true;
223
223
  break;
224
- case 168:
225
- {
226
- // IntelliChem Installed
227
- if ((msg.extractPayloadByte(3) & 0x01) === 1) {
228
- // only support for 1 ic with EasyTouch
229
- let chem = sys.chemControllers.getItemByAddress(144, true);
230
- let schem = state.chemControllers.getItemById(chem.id, true);
231
- chem.ph.tank.capacity = chem.orp.tank.capacity = 6;
232
- chem.ph.tank.units = chem.orp.tank.units = '';
233
-
234
- }
235
- else {
236
- let chem = sys.chemControllers.getItemByAddress(144);
237
- state.chemControllers.removeItemById(chem.id);
238
- sys.chemControllers.removeItemById(chem.id);
239
- }
240
- // Spa Manual Heat on/off
241
- sys.general.options.manualHeat = msg.extractPayloadByte(4) === 1 ? true : false;
242
- msg.isProcessed = true;
243
- }
244
224
  }
245
225
  }
246
226
  private static processCooldownDelay(msg: Inbound) {
@@ -139,6 +139,9 @@ export class OptionsMessage {
139
139
  break;
140
140
  }
141
141
  case 40:
142
+ case 168:
143
+ {
144
+
142
145
  // [165,33,16,34,168,10],[0,0,0,254,0,0,0,0,0,0],[2,168 = manual heat mode off
143
146
  // [165,33,16,34,168,10],[0,0,0,254,1,0,0,0,0,0],[2,169] = manual heat mode on
144
147
  sys.general.options.manualHeat = msg.extractPayloadByte(4) === 1;
@@ -148,7 +151,7 @@ export class OptionsMessage {
148
151
  let schem = state.chemControllers.getItemById(chem.id, true);
149
152
  chem.ph.tank.capacity = chem.orp.tank.capacity = 6;
150
153
  chem.ph.tank.units = chem.orp.tank.units = '';
151
-
154
+
152
155
  }
153
156
  else {
154
157
  let chem = sys.chemControllers.getItemByAddress(144);
@@ -157,6 +160,27 @@ export class OptionsMessage {
157
160
  }
158
161
  msg.isProcessed = true;
159
162
  break;
163
+ }
164
+ /* case 168:
165
+ {
166
+ // IntelliChem Installed
167
+ if ((msg.extractPayloadByte(3) & 0x01) === 1) {
168
+ // only support for 1 ic with EasyTouch
169
+ let chem = sys.chemControllers.getItemByAddress(144, true);
170
+ let schem = state.chemControllers.getItemById(chem.id, true);
171
+ chem.ph.tank.capacity = chem.orp.tank.capacity = 6;
172
+ chem.ph.tank.units = chem.orp.tank.units = '';
173
+
174
+ }
175
+ else {
176
+ let chem = sys.chemControllers.getItemByAddress(144);
177
+ state.chemControllers.removeItemById(chem.id);
178
+ sys.chemControllers.removeItemById(chem.id);
179
+ }
180
+ // Spa Manual Heat on/off
181
+ sys.general.options.manualHeat = msg.extractPayloadByte(4) === 1 ? true : false;
182
+ msg.isProcessed = true;
183
+ } */
160
184
  }
161
185
  }
162
186
  }
File without changes
File without changes