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
@@ -1,398 +1,446 @@
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 { state } from "../../../State";
19
- import { sys, ControllerType } from "../../../Equipment";
20
- import { logger } from "../../../../logger/Logger";
21
- import { webApp } from "../../../../web/Server";
22
- import { Timestamp, utils } from "../../../Constants"
23
- export class IntelliChemStateMessage {
24
- public static process(msg: Inbound) {
25
- if (sys.controllerType === ControllerType.Unknown) return;
26
- let address = (msg.dest >= 144 && msg.dest <= 158) ? msg.dest : msg.source;
27
- if (address < 144 || address > 158) return;
28
- let controller = sys.chemControllers.getItemByAddress(address);
29
- if (!controller.isActive) {
30
- msg.isProcessed = true;
31
- return;
32
- }
33
- switch (msg.action) {
34
- // ---------- IntelliChem Control panel is spitting out its status ----------- //
35
- case 18: // IntelliChem is sending us it's status.
36
- IntelliChemStateMessage.processState(msg);
37
- break;
38
- case 210: // OCP is asking IntelliChem controller for it's current status info.
39
- // [165,0,144,16,210,1],[210],[2,234]
40
- let schem = state.chemControllers.getItemById(controller.id);
41
- if (schem.lastComm + (30 * 1000) < new Date().getTime()) {
42
- // We have not talked to the chem controller in 30 seconds so we have lost communication.
43
- schem.status = schem.alarms.comms = 1;
44
- }
45
- msg.isProcessed = true;
46
- break;
47
- // ---------- End IntelliChem set get ----------- //
48
-
49
- // ---------- OCP set get ----------- //
50
- case 19: // Request to OCP to return the status that ic currently has.
51
- // [165,14,16,34,19,1],[0],[0,249]
52
- break;
53
-
54
- /* RKS: This is processed in the IntellichemMessage.processTouch() and is the results of asking for the IntelliChem configuration.
55
- case 147: // OCP is broadcasting it's known ic values... Need to change our settings if virtual.
56
- // 147 is a proto:broadcast message;
57
- // it has exactly the same format as 18 but there is payload[0] which is inserted at the beginning. Likely the chem controller id.
58
- if (msg.dest < 144 || msg.dest > 158) return;
59
- IntelliChemStateMessage.processControllerChange(msg);
60
- break;
61
- // ---------- End OCP set get ----------- //
62
- */
63
- // ---------- ICP or SL set get ----------- //
64
- case 211: // SL or other controller is telling OCP to set IntelliChem value
65
- // It will take these values and pass them in 146 to IntelliChem
66
- // values that are NOT SET should be ignored
67
- msg.isProcessed = true;
68
- break;
69
- case 146: // OCP is telling IntelliChem that it needs to change its settings to...
70
- //let scontroller = state.chemControllers.getItemById(controller.id, true);
71
- //if (scontroller.lastComm + (30 * 1000) < new Date().getTime()) {
72
- // // We have not talked to the chem controller in 30 seconds so we have lost communication.
73
- // scontroller.status = scontroller.alarms.comms = 1;
74
- //}
75
- controller.ph.tank.capacity = controller.orp.tank.capacity = 6;
76
- controller.ph.tank.units = controller.orp.tank.units = '';
77
- msg.isProcessed = true;
78
- break;
79
- // ---------- OCP set get ----------- //
80
- }
81
- state.emitEquipmentChanges();
82
- }
83
- private static processControllerChange(msg: Inbound) {
84
- // We don't do anything with this inbound action 147 message.
85
- logger.info(`Incoming message from IntelliChem ${msg.toShortPacket()}`);
86
- msg.isProcessed = true;
87
- }
88
- private static processState(msg: Inbound) {
89
- if (msg.source < 144 || msg.source > 158) return;
90
- // This is an action 18 that comes from IntelliChem. There is also a 147 that comes from an OCP but this is the raw data.
91
- //[165, 0, 16, 144, 18, 41][2,228,3,2,2,228,2,188,0,0,0,16,0,0,0,0,0,35,0,0,6,6,3,0,250,0,44,0,160,20,0,81,8,0,149,0,80,1,0,0,0]
92
- // Bytes - Descrption
93
- // 0-1 : pH byte(0) x 256 + byte(1) / 100
94
- // 2-3 : ORP byte(2) x 256 + byte(3)
95
- // 4-5 : pH Setpoint : byte(4) x 256 + byte(5) / 100
96
- // 6-7 : ORP Setpoint : byte(6) x 256 + byte(7)
97
- // 8 : Unknown = 0
98
- // 9 : Unknown = 0
99
- // 10-11 : pH Dose time seconds. The number of seconds since the dose started. byte(10) x 256 + byte(11)
100
- // 12: Unknown
101
- // 13 : Unknown
102
- // 14-15 : ORP Dose time seconds. The number of seconds since the dose started. byte(14) x 256 + byte(15)
103
- // 16-17 : pH Dose volume (unknown units) - These appear to be mL.
104
- // 18-19 : ORP Dose volume (unknown units) - These appear to be mL
105
- // 20 : pH tank level 1-7
106
- // 21 : ORP tank level 1-7
107
- // 22 : LSI. (byte(22) & 0x80) === 0x80 ? (256 - byte(22) / -100 : byte(22)/100
108
- // 23-24 : Calcium Hardness = byte(23) x 256 + byte(24) = 250
109
- // 25 : Unknown = 0 (probably reserved CYA byte so the chem is always dealing with integers)
110
- // 26 : CYA Max value = 210.
111
- // 27-28 : Alkalinity = byte(27) x 256 + byte(28)
112
- // 29 : Salt level = byte(29) x 50
113
- // 30 : Unknown
114
- // 31 : Temperature
115
- // 32 : Alarms
116
- // 33 : Warnings pH Lockout, Daily Limit Reached, Invalid Setup, Chlorinator Comm error
117
- // 34 : Dosing Status (pH Monitoring, ORP Mixing)
118
- // 35 : Delays
119
- // 36-37 : Firmware = 80,1 = 1.080
120
- // 38 : Water Chemistry Warning (Corrosion...)
121
- // 39 : Unknown
122
- // 40 : Unknown
123
- let address = msg.source;
124
- // The address is king here. The id is not.
125
- let chem = sys.chemControllers.getItemByAddress(address, true);
126
- let schem = state.chemControllers.getItemById(chem.id, true);
127
- schem.isActive = chem.isActive = true;
128
- schem.status = 0;
129
- schem.type = chem.type = sys.board.valueMaps.chemControllerTypes.getValue('intellichem');
130
- chem.name = chem.name || `IntelliChem ${chem.address - 143}`; // default to true id if no name is set
131
- schem.lastComm = new Date().getTime();
132
- schem.status = schem.alarms.comms = 0;
133
- chem.ph.tank.capacity = chem.orp.tank.capacity = 6;
134
- chem.ph.tank.units = chem.orp.tank.units = '';
135
- chem.ph.tank.alarmEmptyEnabled = false;
136
- chem.ph.tank.alarmEmptyLevel = 1;
137
- chem.orp.tank.alarmEmptyEnabled = false;
138
- chem.orp.tank.alarmEmptyLevel = 1;
139
- schem.address = chem.address;
140
- schem.ph.level = schem.ph.probe.level = msg.extractPayloadIntBE(0) / 100;
141
- schem.orp.level = schem.orp.probe.level = msg.extractPayloadIntBE(2);
142
- chem.ph.setpoint = msg.extractPayloadIntBE(4) / 100;
143
- chem.orp.setpoint = msg.extractPayloadIntBE(6);
144
- // Missing information on the related bytes.
145
- // Bytes 8-14 (Probably Total Dissolved Solids in here if no IntelliChlor)
146
- let phPrev = { status: schem.ph.dosingStatus, time: schem.ph.timeDosed || 0, vol: schem.ph.volumeDosed };
147
- let orpPrev = { status: schem.orp.dosingStatus, time: schem.orp.timeDosed || 0, vol: schem.orp.volumeDosed };
148
- // IntelliChem never tells us what the dose time or volume is so we will let that dog lie.
149
- // 10-11 : pH Dose time
150
- schem.ph.timeDosed = (msg.extractPayloadByte(10) * 256) + msg.extractPayloadByte(11);
151
- // 14-15 : ORP Dose time seconds. The number of seconds since the dose started.
152
- schem.orp.timeDosed = (msg.extractPayloadByte(14) * 256) + msg.extractPayloadByte(15);
153
- // 16-17 : pH Dose volume (unknown units) = 35
154
- schem.ph.volumeDosed = (msg.extractPayloadByte(16) * 256) + msg.extractPayloadByte(17);
155
- // 18-19 : ORP Dose volume (unknown units) = 0
156
- schem.orp.volumeDosed = (msg.extractPayloadByte(18) * 256) + msg.extractPayloadByte(19);
157
- // 20 : pH tank level 1-7 = 6
158
- schem.ph.tank.level = Math.max(msg.extractPayloadByte(20) > 0 ? msg.extractPayloadByte(20) - 1 : msg.extractPayloadByte(20), 0); // values reported as 1-7; possibly 0 if no tank present
159
- // 21 : ORP tank level 1-7 = 6
160
- schem.orp.tank.level = Math.max(msg.extractPayloadByte(21) > 0 ? msg.extractPayloadByte(21) - 1 : msg.extractPayloadByte(21), 0); // values reported as 1-7; possibly 0 if no tank present
161
- // 22 : LSI = 3 & 0x80 === 0x80 ? (256 - 3) / -100 : 3/100 = .03
162
- let lsi = msg.extractPayloadByte(22);
163
- schem.lsi = (lsi & 0x80) === 0x80 ? (256 - lsi) / -100 : lsi / 100;
164
- // 23-24 : Calcium Hardness = 0x256+250 = 250
165
- chem.calciumHardness = (msg.extractPayloadByte(23) * 256) + msg.extractPayloadByte(24);
166
- // 26 : CYA = 44
167
- chem.cyanuricAcid = msg.extractPayloadByte(26);
168
- // 27-28 : Alkalinity
169
- chem.alkalinity = (msg.extractPayloadByte(27) * 256) + msg.extractPayloadByte(28);
170
- // 29 : Salt level = 20
171
- if (sys.chlorinators.length > 0) {
172
- let chlor = state.chlorinators.find(elem => elem.id === 1);
173
- schem.orp.probe.saltLevel = (typeof chlor !== 'undefined') ? chlor.saltLevel : msg.extractPayloadByte(29) * 50;
174
- }
175
- else schem.orp.probe.saltLevel = 0;
176
- // 31 : Temperature = 81
177
- schem.ph.probe.temperature = msg.extractPayloadByte(31);
178
- schem.ph.probe.tempUnits = state.temps.units;
179
- // 32 : Alarms = 8 = (no alarm)
180
- const alarms = schem.alarms;
181
- alarms.flow = msg.extractPayloadByte(32) & 0x01;
182
- if (alarms.flow === 0) schem.flowDetected = true;
183
- alarms.pH = msg.extractPayloadByte(32) & 0x06;
184
- alarms.orp = msg.extractPayloadByte(32) & 0x18;
185
- alarms.pHTank = msg.extractPayloadByte(32) & 0x20;
186
- alarms.orpTank = msg.extractPayloadByte(32) & 0x40;
187
- alarms.probeFault = msg.extractPayloadByte(32) & 0x80;
188
- // 33 : Warnings -- pH Lockout, Daily Limit Reached, Invalid Setup, Chlorinator Comm error
189
- const warnings = schem.warnings;
190
- warnings.pHLockout = msg.extractPayloadByte(33) & 0x01;
191
- warnings.pHDailyLimitReached = msg.extractPayloadByte(33) & 0x02;
192
- warnings.orpDailyLimitReached = msg.extractPayloadByte(33) & 0x04;
193
- warnings.invalidSetup = msg.extractPayloadByte(33) & 0x08;
194
- warnings.chlorinatorCommError = msg.extractPayloadByte(33) & 0x10;
195
- // So we need to do some calculation here.
196
-
197
- // 34 : Dosing Status = 149 = (pH Monitoring, ORP Mixing)
198
- schem.ph.dosingStatus = (msg.extractPayloadByte(34) & 0x30) >> 4; // mask 00xx0000 and shift bit 5 & 6
199
- schem.orp.dosingStatus = (msg.extractPayloadByte(34) & 0xC0) >> 6; // mask xx000000 and shift bit 7 & 8
200
- // 35 : Delays = 0
201
- schem.status = (msg.extractPayloadByte(35) & 0x80) >> 7; // to be verified as comms lost
202
- schem.ph.manualDosing = (msg.extractPayloadByte(35) & 0x08) === 1 ? true : false;
203
- chem.orp.useChlorinator = (msg.extractPayloadByte(35) & 0x10) === 1 ? true : false;
204
- chem.HMIAdvancedDisplay = (msg.extractPayloadByte(35) & 0x20) === 1 ? true : false;
205
- chem.ph.phSupply = (msg.extractPayloadByte(35) & 0x40) === 1 ? 'acid' : 'base'; // acid pH dosing = 1; base pH dosing = 0;
206
- // 36-37 : Firmware = 80,1 = 1.080
207
- chem.firmware = `${msg.extractPayloadByte(37)}.${msg.extractPayloadByte(36).toString().padStart(3, '0')}`
208
- // 38 : Water Chemistry Warning
209
- schem.warnings.waterChemistry = msg.extractPayloadByte(38);
210
- if (typeof chem.body === 'undefined') chem.body = schem.body = 0;
211
- if (state.equipment.controllerType === 'nixie') {
212
- if (chem.ph.probe.feedBodyTemp) {
213
- let temps: any = {};
214
- let body = state.temps.bodies.getBodyIsOn();
215
- if (typeof body !== 'undefined') {
216
- if (body.id === 1 && (schem.body === 0 || schem.body === 32)) {
217
- temps.waterSensor1 = schem.ph.probe.temperature;
218
- }
219
- else if (body.id === 2 && (schem.body === 2 || schem.body === 32)) {
220
- temps.waterSensor1 = schem.ph.probe.temperature;
221
- }
222
- else if (body.id === 2 && chem.body === 1) {
223
- temps.waterSensor2 = schem.ph.probe.temperature;
224
- }
225
- }
226
- sys.board.system.setTempsAsync(temps).catch(err => logger.error(err))
227
- }
228
- }
229
- schem.ph.pump.isDosing = schem.ph.dosingStatus === 0 && chem.ph.enabled;
230
- schem.orp.pump.isDosing = schem.orp.dosingStatus === 0 && chem.orp.enabled;
231
- schem.calculateSaturationIndex();
232
- schem.lastComm = new Date().getTime();
233
- // If we are changing states lets set that up.
234
- if (schem.ph.dosingStatus === 0 && phPrev.status !== 0) {
235
- if (schem.ph.dosingStatus === 0) {
236
- // We are starting a dose so we need to set the current dose.
237
- schem.ph.startDose(Timestamp.now.addSeconds(-schem.ph.doseTime).toDate(), schem.ph.manualDosing ? 'manual' : 'auto', 0, schem.ph.volumeDosed, 0, schem.ph.timeDosed * 1000);
238
- }
239
- }
240
- else if (schem.ph.dosingStatus !== 0 && phPrev.status === 0) {
241
- if (typeof schem.ph.currentDose !== 'undefined') {
242
- // We just ended a dose so write it out to the chem logs.
243
- schem.ph.endDose(Timestamp.now.addSeconds(-(schem.ph.doseTime - phPrev.time)).toDate(), 'completed',
244
- schem.ph.volumeDosed - phPrev.vol, (schem.ph.timeDosed - phPrev.time) * 1000);
245
- }
246
- }
247
- else if (schem.ph.dosingStatus === 0) {
248
- // We are still dosing so add the time and volume to the dose.
249
- schem.ph.appendDose(schem.ph.doseVolume - phPrev.vol, (schem.ph.timeDosed - phPrev.time) * 1000);
250
- }
251
- else {
252
- console.log(`DOSING STATUS === ${schem.ph.dosingStatus}`);
253
- // Make sure we don't have a current dose going.
254
- schem.ph.currentDose = undefined;
255
- }
256
- // If we are changing states lets set that up for orp.
257
- if (schem.orp.dosingStatus === 0 && orpPrev.status !== 0) {
258
- if (schem.orp.dosingStatus === 0) {
259
- // We are starting a dose so we need to set the current dose.
260
- schem.orp.startDose(Timestamp.now.addSeconds(-schem.orp.doseTime).toDate(), schem.orp.manualDosing ? 'manual' : 'auto', 0, schem.orp.volumeDosed, 0, schem.orp.timeDosed * 1000);
261
- }
262
- }
263
- else if (schem.orp.dosingStatus !== 0 && orpPrev.status === 0) {
264
- if (typeof schem.orp.currentDose !== 'undefined') {
265
- // We just ended a dose so write it out to the chem logs.
266
- schem.orp.endDose(Timestamp.now.addSeconds(-(schem.orp.doseTime - orpPrev.time)).toDate(), 'completed',
267
- schem.orp.volumeDosed - orpPrev.vol, (schem.orp.timeDosed - orpPrev.time) * 1000);
268
- }
269
- }
270
- else if (schem.orp.dosingStatus === 0) {
271
- // We are still dosing so add the time and volume to the dose.
272
- schem.orp.appendDose(schem.orp.doseVolume - orpPrev.vol, (schem.orp.timeDosed - orpPrev.time) * 1000);
273
- }
274
- else {
275
- // Make sure we don't have a current dose going.
276
- schem.orp.currentDose = undefined;
277
- }
278
- // manually emit extended values
279
- webApp.emitToClients('chemController', schem.getExtended()); // emit extended data
280
- schem.hasChanged = false; // try to avoid duplicate emits
281
- msg.isProcessed = true;
282
-
283
- /*
284
- //Status 0x12 (18) - Intellichem Status (length 41)
285
- example:
286
- 02 E3 02 AF 02 EE 02 BC 00 00 00 02 00 00 00 2A 00 04 00 5C 06 05 18 01 90 00 00 00 96 14 00 51 00 00 65 20 3C 01 00 00 00
287
-
288
- 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
289
- [165,16,15,16,18 41], [2 227 2 175 2 238 2 188 0 0 0 2 0 0 0 42 0 4 0 92 6 5 24 1 144 0 0 0 150 20 0 81 0 0 101 32 60 1 0 0 0]
290
- ph--- orp-- ph--- orp-- tanks CH--- CYA TA--- Wtr MODE
291
- 0-1 pH(1-2) / ORP(8-9) reading
292
- 02 E3 - pH 2*256 + e3(227) = 739
293
- 02 AF - ORP 2*256 + af(175) = 687
294
-
295
- 4-5 pH settings
296
- D0 = 7.2 (hi/lo bits - 720 = 7.2pH)
297
- DA = 7.3
298
- E4 = 7.4
299
- EE = 7.5
300
- F8 = 7.6
301
-
302
- 6-7 ORP settings
303
- 02 BC = 700 (hi/lo bits)
304
-
305
- 20-21 Tank levels; 21 is acid? 22 is chlorine?
306
- 06 and 05
307
-
308
- 23-24 Calcimum Hardness
309
- 90 is CH (90 = 400; 8b = 395) hi/lo bits
310
-
311
- 26
312
- 00 is CYA (00 = 0; 9 = 9; c9 = 201) (does not appear to have hi/lo - 201 is max
313
-
314
- 27-28 - Total Alkalinity
315
- 96 is TA (96 = 150)
316
-
317
- 30 - Water Flow Alarm (00 is ok; 01 is no flow)
318
- 00 flow is OK
319
- 01 flow is Alarm on (Water STOPPED)
320
-
321
- // todo: these are probably 2-byte status message but need to confirm
322
- 36 Mode
323
- 0x25 dosing (auto)?
324
- 0x45 dosing acid (manually?)
325
- 0x55 mixing???
326
- 0x65 monitoring
327
- 0x02 (12 when mixing) and 04 (27 when mixing) related???
328
-
329
- 37
330
- 20 Nothing
331
- 22 Dosing Chlorine(?)
332
- */
333
-
334
-
335
- //// Missing information on the related bytes.
336
- //// Bytes 8-14 (Probably Total Dissolved Solids in here if no IntelliChlor)
337
- //// controller.waterVolume = msg.extractPayloadByte(15) * 1000;
338
- //// Bytes 16-19
339
- //scontroller.ph.dosingVolumeRemaining = msg.extractPayloadByte(17); // Previous pH Dose volume
340
- //scontroller.orp.dosingVolumeRemaining = msg.extractPayloadByte(19); // Previous ORP Dose volume
341
- //scontroller.ph.tank.level = Math.max(msg.extractPayloadByte(20) > 0 ? msg.extractPayloadByte(20) - 1 : msg.extractPayloadByte(20), 0); // values reported as 1-7; possibly 0 if no tank present
342
- //scontroller.orp.tank.level = Math.max(msg.extractPayloadByte(21) > 0 ? msg.extractPayloadByte(21) - 1 : msg.extractPayloadByte(21), 0); // values reported as 1-7; possibly 0 if no tank present
343
- //let SIRaw = msg.extractPayloadByte(22);
344
- //if ((SIRaw & 0x80) === 0x80) {
345
- // // negative SI
346
- // scontroller.saturationIndex = (256 - SIRaw) / -100;
347
- //}
348
- //else {
349
- // scontroller.saturationIndex = msg.extractPayloadByte(22) / 100;
350
- //}
351
- //controller.calciumHardness = msg.extractPayloadIntBE(23);
352
-
353
- //// scontroller.status2 = msg.extractPayloadByte(25); // remove/unsure?
354
- //controller.cyanuricAcid = msg.extractPayloadByte(26);
355
- //controller.alkalinity = msg.extractPayloadIntBE(27);
356
-
357
- //// scontroller.waterFlow = msg.extractPayloadByte(30); // This is probably the temp units.
358
- //scontroller.ph.probe.tempUnits = 0;//msg.extractPayloadByte(30); See Above. This is probably the units.
359
- //scontroller.ph.probe.temperature = msg.extractPayloadByte(31);
360
-
361
- //msg.extractPayloadByte(33);
362
-
363
- //// [0, { name: 'dosing', desc: 'Dosing' }],
364
- //// [1, { name: 'monitoring', desc: 'Monitoring' }],
365
- //// [2, { name: 'mixing', desc: 'Mixing' }]
366
-
367
- //scontroller.ph.dosingStatus = (msg.extractPayloadByte(34) & 0x30) >> 4; // mask 00xx0000 and shift bit 5 & 6
368
- //scontroller.orp.dosingStatus = (msg.extractPayloadByte(34) & 0xC0) >> 6; // mask xx000000 and shift bit 7 & 8
369
- //scontroller.ph.flowDelay = scontroller.orp.flowDelay = (msg.extractPayloadByte(35) & 0x02) === 1 ? true : false;
370
- //scontroller.status = msg.extractPayloadByte(35) & 0x80 >> 7; // to be verified as comms lost
371
- //scontroller.ph.manualDosing = (msg.extractPayloadByte(35) & 0x08) === 1 ? true : false;
372
- //controller.orp.useChlorinator = (msg.extractPayloadByte(35) & 0x10) === 1 ? true : false;
373
- //controller.HMIAdvancedDisplay = (msg.extractPayloadByte(35) & 0x20) === 1 ? true : false;
374
- //controller.ph.phSupply = (msg.extractPayloadByte(35) & 0x40) === 1 ? true : false; // acid pH dosing = 1; base pH dosing = 0;
375
- //scontroller.firmware = `${msg.extractPayloadByte(37)}.${msg.extractPayloadByte(36).toString().padStart(3, '0')}`
376
-
377
- //const warnings = scontroller.warnings;
378
- //warnings.waterChemistry = msg.extractPayloadByte(38);
379
- //warnings.pHLockout = msg.extractPayloadByte(33) & 0x01;
380
- //warnings.pHDailyLimitReached = msg.extractPayloadByte(33) & 0x02;
381
- //warnings.orpDailyLimitReached = msg.extractPayloadByte(33) & 0x04;
382
- //warnings.invalidSetup = msg.extractPayloadByte(33) & 0x08;
383
- //warnings.chlorinatorCommError = msg.extractPayloadByte(33) & 0x10;
384
-
385
- //// RKS: This should really match against the body for the chlorinator when *Chem thinks it has been provided TDS.
386
- //// RG: Byte 35, bit 4 indicates IntelliChlor is used. Until we know more, this logic suffices.
387
- //if (sys.chlorinators.length > 0) {
388
- // let chlor = state.chlorinators.find(elem => elem.id === 1);
389
- // scontroller.orp.probe.saltLevel = (typeof chlor !== 'undefined') ? chlor.saltLevel : msg.extractPayloadByte(29) * 50;
390
- //}
391
- //else scontroller.orp.probe.saltLevel = 0;
392
-
393
- //// manually emit extended values
394
- //webApp.emitToClients('chemController', scontroller.getExtended()); // emit extended data
395
- //scontroller.hasChanged = false; // try to avoid duplicate emits
396
- //msg.isProcessed = true;
397
- }
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 { state } from "../../../State";
19
+ import { sys, ControllerType } from "../../../Equipment";
20
+ import { logger } from "../../../../logger/Logger";
21
+ import { webApp } from "../../../../web/Server";
22
+ import { Timestamp, utils } from "../../../Constants"
23
+ export class IntelliChemStateMessage {
24
+ public static process(msg: Inbound) {
25
+ if (sys.controllerType === ControllerType.Unknown) return;
26
+ let address = (msg.dest >= 144 && msg.dest <= 158) ? msg.dest : msg.source;
27
+ if (address < 144 || address > 158) return;
28
+ let controller = sys.chemControllers.getItemByAddress(address);
29
+ if (!controller.isActive) {
30
+ msg.isProcessed = true;
31
+ return;
32
+ }
33
+ switch (msg.action) {
34
+ // ---------- IntelliChem Control panel is spitting out its status ----------- //
35
+ case 18: // IntelliChem is sending us it's status.
36
+ IntelliChemStateMessage.processState(msg);
37
+ break;
38
+ case 210: // OCP is asking IntelliChem controller for it's current status info.
39
+ // [165,0,144,16,210,1],[210],[2,234]
40
+ let schem = state.chemControllers.getItemById(controller.id);
41
+ if (schem.lastComm + (30 * 1000) < new Date().getTime()) {
42
+ // We have not talked to the chem controller in 30 seconds so we have lost communication.
43
+ schem.status = schem.alarms.comms = 1;
44
+ }
45
+ msg.isProcessed = true;
46
+ break;
47
+ // ---------- End IntelliChem set get ----------- //
48
+
49
+ // ---------- OCP set get ----------- //
50
+ case 19: // Request to OCP to return the status that ic currently has.
51
+ // [165,14,16,34,19,1],[0],[0,249]
52
+ break;
53
+
54
+ /* RKS: This is processed in the IntellichemMessage.processTouch() and is the results of asking for the IntelliChem configuration.
55
+ case 147: // OCP is broadcasting it's known ic values... Need to change our settings if virtual.
56
+ // 147 is a proto:broadcast message;
57
+ // it has exactly the same format as 18 but there is payload[0] which is inserted at the beginning. Likely the chem controller id.
58
+ if (msg.dest < 144 || msg.dest > 158) return;
59
+ IntelliChemStateMessage.processControllerChange(msg);
60
+ break;
61
+ // ---------- End OCP set get ----------- //
62
+ */
63
+ // ---------- ICP or SL set get ----------- //
64
+ case 211: // SL or other controller is telling OCP to set IntelliChem value
65
+ // It will take these values and pass them in 146 to IntelliChem
66
+ // values that are NOT SET should be ignored
67
+ msg.isProcessed = true;
68
+ break;
69
+ case 146: // OCP is telling IntelliChem that it needs to change its settings to...
70
+ //let scontroller = state.chemControllers.getItemById(controller.id, true);
71
+ //if (scontroller.lastComm + (30 * 1000) < new Date().getTime()) {
72
+ // // We have not talked to the chem controller in 30 seconds so we have lost communication.
73
+ // scontroller.status = scontroller.alarms.comms = 1;
74
+ //}
75
+ controller.ph.tank.capacity = controller.orp.tank.capacity = 6;
76
+ controller.ph.tank.units = controller.orp.tank.units = '';
77
+ msg.isProcessed = true;
78
+ break;
79
+ // ---------- OCP set get ----------- //
80
+ }
81
+ state.emitEquipmentChanges();
82
+ }
83
+ private static processControllerChange(msg: Inbound) {
84
+ // We don't do anything with this inbound action 147 message.
85
+ logger.info(`Incoming message from IntelliChem ${msg.toShortPacket()}`);
86
+ msg.isProcessed = true;
87
+ }
88
+ private static processState(msg: Inbound) {
89
+ if (msg.source < 144 || msg.source > 158) return;
90
+
91
+ // Setup with CO2 dosing and IntelliChlor.
92
+ //[2, 245, 2, 162, 2, 248, 2, 88, 0, 0, [0-9]
93
+ // 0, 26, 0, 0, 0, 0, 0, 0, 0, 0, [10-19]
94
+ // 7, 0, 249, 1, 94, 0, 81, 0, 80, 57, [20-29]
95
+ // 0, 82, 0, 0, 162, 32, 80, 1, 0, 0, 0] [30-40]
96
+ // Setup with 2 Tanks
97
+ //[2, 238, 2, 208, 2, 248, 2, 188, 0, 0,
98
+ // 0, 2, 0, 0, 0, 29, 0, 4, 0, 63,
99
+ // 2, 2, 157, 0, 25, 0, 0, 0, 90, 20,
100
+ // 0, 83, 0, 0, 149, 0, 60, 1, 1, 0, 0]
101
+
102
+ // This is an action 18 that comes from IntelliChem. There is also a 147 that comes from an OCP but this is the raw data.
103
+ //[165, 0, 16, 144, 18, 41][2,228,3,2,2,228,2,188,0,0,0,16,0,0,0,0,0,35,0,0,6,6,3,0,250,0,44,0,160,20,0,81,8,0,149,0,80,1,0,0,0]
104
+ // Bytes - Descrption
105
+ // 0-1 : pH byte(0) x 256 + byte(1) / 100
106
+ // 2-3 : ORP byte(2) x 256 + byte(3)
107
+ // 4-5 : pH Setpoint : byte(4) x 256 + byte(5) / 100
108
+ // 6-7 : ORP Setpoint : byte(6) x 256 + byte(7)
109
+ // 8 : Unknown = 0
110
+ // 9 : Unknown = 0
111
+ // 10-11 : pH Dose time seconds. The number of seconds since the dose started. byte(10) x 256 + byte(11)
112
+ // 12: Unknown
113
+ // 13 : Unknown
114
+ // 14-15 : ORP Dose time seconds. The number of seconds since the dose started. byte(14) x 256 + byte(15)
115
+ // 16-17 : pH Dose volume (unknown units) - These appear to be mL.
116
+ // 18-19 : ORP Dose volume (unknown units) - These appear to be mL
117
+ // 20 : pH tank level 1-7
118
+ // 21 : ORP tank level 1-7
119
+ // 22 : LSI. (byte(22) & 0x80) === 0x80 ? (256 - byte(22) / -100 : byte(22)/100
120
+ // 23-24 : Calcium Hardness = byte(23) x 256 + byte(24) = 250
121
+ // 25 : Unknown = 0 (probably reserved CYA byte so the chem is always dealing with integers)
122
+ // 26 : CYA Max value = 210.
123
+ // 27-28 : Alkalinity = byte(27) x 256 + byte(28)
124
+ // 29 : Salt level = byte(29) x 50
125
+ // 30 : Unknown
126
+ // 31 : Temperature
127
+ // 32 : Alarms
128
+ // 33 : Warnings pH Lockout, Daily Limit Reached, Invalid Setup, Chlorinator Comm error
129
+ // 34 : Dosing Status/Doser Type (pH Monitoring, ORP Mixing)
130
+ // 35 : Delays
131
+ // 36-37 : Firmware = 80,1 = 1.080
132
+ // 38 : Water Chemistry Warning (Corrosion...)
133
+ // 39 : Unknown
134
+ // 40 : Unknown
135
+ let address = msg.source;
136
+
137
+ // The address is king here. The id is not.
138
+ let chem = sys.chemControllers.getItemByAddress(address, true);
139
+ let schem = state.chemControllers.getItemById(chem.id, true);
140
+
141
+ // Get the doser types and set up our capabilities
142
+ chem.ph.doserType = (msg.extractPayloadByte(34) & 0x03);
143
+ chem.orp.doserType = (msg.extractPayloadByte(34) & 0x0C) >> 2;
144
+ schem.ph.enabled = chem.ph.enabled = chem.ph.doserType !== 0;
145
+ schem.ph.enabled = chem.orp.enabled = chem.orp.doserType !== 0;
146
+ if (chem.ph.doserType === 2) schem.ph.chemType = 'CO2';
147
+ else if (chem.ph.doserType === 0) schem.ph.chemType = 'none';
148
+ else if (chem.ph.doserType === 1) schem.ph.chemType = 'acid';
149
+ else if (chem.ph.doserType === 3) schem.ph.chemType = 'acid';
150
+
151
+ if (chem.orp.doserType === 0) schem.orp.chemType = 'none';
152
+ else schem.orp.chemType = 'orp';
153
+
154
+ schem.isActive = chem.isActive = true;
155
+ schem.status = 0;
156
+ schem.type = chem.type = sys.board.valueMaps.chemControllerTypes.getValue('intellichem');
157
+ chem.name = chem.name || `IntelliChem ${chem.address - 143}`; // default to true id if no name is set
158
+ schem.lastComm = new Date().getTime();
159
+ schem.status = schem.alarms.comms = 0;
160
+ chem.ph.tank.capacity = chem.orp.tank.capacity = 6;
161
+ chem.ph.tank.units = chem.orp.tank.units = '';
162
+ chem.ph.tank.alarmEmptyEnabled = false;
163
+ chem.ph.tank.alarmEmptyLevel = 1;
164
+ chem.orp.tank.alarmEmptyEnabled = false;
165
+ chem.orp.tank.alarmEmptyLevel = 1;
166
+ schem.address = chem.address;
167
+ schem.ph.level = schem.ph.probe.level = msg.extractPayloadIntBE(0) / 100;
168
+ schem.orp.level = schem.orp.probe.level = msg.extractPayloadIntBE(2);
169
+ chem.ph.setpoint = msg.extractPayloadIntBE(4) / 100;
170
+ chem.orp.setpoint = msg.extractPayloadIntBE(6);
171
+ // Missing information on the related bytes.
172
+ // Bytes 8-14 (Probably Total Dissolved Solids in here if no IntelliChlor)
173
+ let phPrev = { status: schem.ph.dosingStatus, time: schem.ph.timeDosed || 0, vol: schem.ph.volumeDosed };
174
+ let orpPrev = { status: schem.orp.dosingStatus, time: schem.orp.timeDosed || 0, vol: schem.orp.volumeDosed };
175
+ // IntelliChem never tells us what the dose time or volume is so we will let that dog lie.
176
+ // 10-11 : pH Dose time
177
+ schem.ph.timeDosed = (msg.extractPayloadByte(10) * 256) + msg.extractPayloadByte(11);
178
+ // 14-15 : ORP Dose time seconds. The number of seconds since the dose started.
179
+ schem.orp.timeDosed = (msg.extractPayloadByte(14) * 256) + msg.extractPayloadByte(15);
180
+ // 16-17 : pH Dose volume (unknown units) = 35
181
+ schem.ph.volumeDosed = (msg.extractPayloadByte(16) * 256) + msg.extractPayloadByte(17);
182
+ // 18-19 : ORP Dose volume (unknown units) = 0
183
+ schem.orp.volumeDosed = (msg.extractPayloadByte(18) * 256) + msg.extractPayloadByte(19);
184
+ // 20 : pH tank level 1-7 = 6
185
+ schem.ph.tank.level = Math.max(msg.extractPayloadByte(20) > 0 ? msg.extractPayloadByte(20) - 1 : msg.extractPayloadByte(20), 0); // values reported as 1-7; possibly 0 if no tank present
186
+ // 21 : ORP tank level 1-7 = 6 if the tank levels report 0 then the chemical side is not enabled.
187
+ schem.orp.tank.level = Math.max(msg.extractPayloadByte(21) > 0 ? msg.extractPayloadByte(21) - 1 : msg.extractPayloadByte(21), 0); // values reported as 1-7; possibly 0 if no tank present
188
+
189
+ // 22 : LSI = 3 & 0x80 === 0x80 ? (256 - 3) / -100 : 3/100 = .03
190
+ let lsi = msg.extractPayloadByte(22);
191
+ schem.lsi = (lsi & 0x80) === 0x80 ? (256 - lsi) / -100 : lsi / 100;
192
+ // 23-24 : Calcium Hardness = 0x256+250 = 250
193
+ chem.calciumHardness = (msg.extractPayloadByte(23) * 256) + msg.extractPayloadByte(24);
194
+ // 26 : CYA = 44
195
+ chem.cyanuricAcid = msg.extractPayloadByte(26);
196
+ // 27-28 : Alkalinity
197
+ chem.alkalinity = (msg.extractPayloadByte(27) * 256) + msg.extractPayloadByte(28);
198
+ // 29 : Salt level = 20
199
+ if (sys.chlorinators.length > 0) {
200
+ let chlor = state.chlorinators.find(elem => elem.id === 1);
201
+ schem.orp.probe.saltLevel = (typeof chlor !== 'undefined') ? chlor.saltLevel : msg.extractPayloadByte(29) * 50;
202
+ }
203
+ else schem.orp.probe.saltLevel = 0;
204
+ // 31 : Temperature = 81
205
+ schem.ph.probe.temperature = msg.extractPayloadByte(31);
206
+ schem.ph.probe.tempUnits = state.temps.units;
207
+ // 32 : Alarms = 8 = (no alarm)
208
+ const alarms = schem.alarms;
209
+ alarms.flow = msg.extractPayloadByte(32) & 0x01;
210
+ if (alarms.flow === 0) schem.flowDetected = true;
211
+
212
+ // The pH and ORP alarms are in a word stupid for IntelliChem. So we are
213
+ // going to override these.
214
+ //alarms.pH = msg.extractPayloadByte(32) & 0x06;
215
+ //alarms.orp = msg.extractPayloadByte(32) & 0x18;
216
+ if (chem.ph.tolerance.enabled && schem.flowDetected) {
217
+ if (schem.ph.level > chem.ph.tolerance.high) alarms.pH = 2;
218
+ else if (schem.ph.level < chem.ph.tolerance.low) alarms.pH = 4;
219
+ else alarms.pH = 0;
220
+ }
221
+ else alarms.pH = 0;
222
+ if (chem.orp.tolerance.enabled && schem.flowDetected) {
223
+ if (schem.orp.level > chem.orp.tolerance.high) alarms.orp = 8;
224
+ else if (schem.orp.level < chem.orp.tolerance.low) alarms.orp = 16;
225
+ else alarms.orp = 0;
226
+ }
227
+ else alarms.orp = 0;
228
+ // IntelliChem will still send a tank empty alarm even if there is no tank.
229
+ alarms.pHTank = (chem.ph.enabled && (chem.ph.doserType === 1 || chem.ph.doserType === 3)) ? msg.extractPayloadByte(32) & 0x20 : 0;
230
+ alarms.orpTank = (chem.orp.enabled && (chem.orp.doserType === 1 || chem.orp.doserType === 3)) ? msg.extractPayloadByte(32) & 0x40 : 0;
231
+ alarms.probeFault = msg.extractPayloadByte(32) & 0x80;
232
+ // 33 : Warnings -- pH Lockout, Daily Limit Reached, Invalid Setup, Chlorinator Comm error
233
+ const warnings = schem.warnings;
234
+ warnings.pHLockout = msg.extractPayloadByte(33) & 0x01;
235
+ warnings.pHDailyLimitReached = msg.extractPayloadByte(33) & 0x02;
236
+ warnings.orpDailyLimitReached = msg.extractPayloadByte(33) & 0x04;
237
+ warnings.invalidSetup = msg.extractPayloadByte(33) & 0x08;
238
+ warnings.chlorinatorCommError = msg.extractPayloadByte(33) & 0x10;
239
+ // So we need to do some calculation here.
240
+
241
+ // 34 : Dosing Status = 149 = (pH Monitoring, ORP Mixing)
242
+ schem.ph.dosingStatus = (msg.extractPayloadByte(34) & 0x30) >> 4; // mask 00xx0000 and shift bit 5 & 6
243
+ schem.orp.dosingStatus = (msg.extractPayloadByte(34) & 0xC0) >> 6; // mask xx000000 and shift bit 7 & 8
244
+ // 35 : Delays = 0
245
+ schem.status = (msg.extractPayloadByte(35) & 0x80) >> 7; // to be verified as comms lost
246
+ schem.ph.manualDosing = (msg.extractPayloadByte(35) & 0x08) === 1 ? true : false;
247
+ chem.orp.useChlorinator = (msg.extractPayloadByte(35) & 0x10) === 1 ? true : false;
248
+ chem.HMIAdvancedDisplay = (msg.extractPayloadByte(35) & 0x20) === 1 ? true : false;
249
+ chem.ph.phSupply = (msg.extractPayloadByte(35) & 0x40) === 1 ? 'acid' : 'base'; // acid pH dosing = 1; base pH dosing = 0;
250
+ // 36-37 : Firmware = 80,1 = 1.080
251
+ chem.firmware = `${msg.extractPayloadByte(37)}.${msg.extractPayloadByte(36).toString().padStart(3, '0')}`
252
+ // 38 : Water Chemistry Warning
253
+ // The LSI handling is also stupid with IntelliChem so we are going to have our way with it.
254
+ // schem.warnings.waterChemistry = msg.extractPayloadByte(38);
255
+ schem.calculateSaturationIndex();
256
+ if (schem.saturationIndex > chem.lsiRange.high) schem.warnings.waterChemistry = 2;
257
+ else if (schem.saturationIndex < chem.lsiRange.low) schem.warnings.waterChemistry = 1;
258
+ else schem.warnings.waterChemistry = 0;
259
+ if (typeof chem.body === 'undefined') chem.body = schem.body = 0;
260
+ if (state.equipment.controllerType === 'nixie') {
261
+ if (chem.ph.probe.feedBodyTemp) {
262
+ let temps: any = {};
263
+ let body = state.temps.bodies.getBodyIsOn();
264
+ if (typeof body !== 'undefined') {
265
+ if (body.id === 1 && (schem.body === 0 || schem.body === 32)) {
266
+ temps.waterSensor1 = schem.ph.probe.temperature;
267
+ }
268
+ else if (body.id === 2 && (schem.body === 2 || schem.body === 32)) {
269
+ temps.waterSensor1 = schem.ph.probe.temperature;
270
+ }
271
+ else if (body.id === 2 && chem.body === 1) {
272
+ temps.waterSensor2 = schem.ph.probe.temperature;
273
+ }
274
+ }
275
+ sys.board.system.setTempsAsync(temps).catch(err => logger.error(err))
276
+ }
277
+ }
278
+ schem.ph.pump.isDosing = schem.ph.dosingStatus === 0 && chem.ph.enabled;
279
+ schem.orp.pump.isDosing = schem.orp.dosingStatus === 0 && chem.orp.enabled;
280
+ schem.lastComm = new Date().getTime();
281
+ // If we are changing states lets set that up.
282
+ if (schem.ph.dosingStatus === 0 && phPrev.status !== 0) {
283
+ if (schem.ph.dosingStatus === 0) {
284
+ // We are starting a dose so we need to set the current dose.
285
+ schem.ph.startDose(Timestamp.now.addSeconds(-schem.ph.doseTime).toDate(), schem.ph.manualDosing ? 'manual' : 'auto', 0, schem.ph.volumeDosed, 0, schem.ph.timeDosed * 1000);
286
+ }
287
+ }
288
+ else if (schem.ph.dosingStatus !== 0 && phPrev.status === 0) {
289
+ if (typeof schem.ph.currentDose !== 'undefined') {
290
+ // We just ended a dose so write it out to the chem logs.
291
+ schem.ph.endDose(Timestamp.now.addSeconds(-(schem.ph.doseTime - phPrev.time)).toDate(), 'completed',
292
+ schem.ph.volumeDosed - phPrev.vol, (schem.ph.timeDosed - phPrev.time) * 1000);
293
+ }
294
+ }
295
+ else if (schem.ph.dosingStatus === 0) {
296
+ // We are still dosing so add the time and volume to the dose.
297
+ schem.ph.appendDose(schem.ph.doseVolume - phPrev.vol, (schem.ph.timeDosed - phPrev.time) * 1000);
298
+ }
299
+ else {
300
+ console.log(`DOSING STATUS === ${schem.ph.dosingStatus}`);
301
+ // Make sure we don't have a current dose going.
302
+ schem.ph.currentDose = undefined;
303
+ }
304
+ // If we are changing states lets set that up for orp.
305
+ if (schem.orp.dosingStatus === 0 && orpPrev.status !== 0) {
306
+ if (schem.orp.dosingStatus === 0) {
307
+ // We are starting a dose so we need to set the current dose.
308
+ schem.orp.startDose(Timestamp.now.addSeconds(-schem.orp.doseTime).toDate(), schem.orp.manualDosing ? 'manual' : 'auto', 0, schem.orp.volumeDosed, 0, schem.orp.timeDosed * 1000);
309
+ }
310
+ }
311
+ else if (schem.orp.dosingStatus !== 0 && orpPrev.status === 0) {
312
+ if (typeof schem.orp.currentDose !== 'undefined') {
313
+ // We just ended a dose so write it out to the chem logs.
314
+ schem.orp.endDose(Timestamp.now.addSeconds(-(schem.orp.doseTime - orpPrev.time)).toDate(), 'completed',
315
+ schem.orp.volumeDosed - orpPrev.vol, (schem.orp.timeDosed - orpPrev.time) * 1000);
316
+ }
317
+ }
318
+ else if (schem.orp.dosingStatus === 0) {
319
+ // We are still dosing so add the time and volume to the dose.
320
+ schem.orp.appendDose(schem.orp.doseVolume - orpPrev.vol, (schem.orp.timeDosed - orpPrev.time) * 1000);
321
+ }
322
+ else {
323
+ // Make sure we don't have a current dose going.
324
+ schem.orp.currentDose = undefined;
325
+ }
326
+ // manually emit extended values
327
+ webApp.emitToClients('chemController', schem.getExtended()); // emit extended data
328
+ schem.hasChanged = false; // try to avoid duplicate emits
329
+ msg.isProcessed = true;
330
+
331
+ /*
332
+ //Status 0x12 (18) - Intellichem Status (length 41)
333
+ example:
334
+ 02 E3 02 AF 02 EE 02 BC 00 00 00 02 00 00 00 2A 00 04 00 5C 06 05 18 01 90 00 00 00 96 14 00 51 00 00 65 20 3C 01 00 00 00
335
+
336
+ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
337
+ [165,16,15,16,18 41], [2 227 2 175 2 238 2 188 0 0 0 2 0 0 0 42 0 4 0 92 6 5 24 1 144 0 0 0 150 20 0 81 0 0 101 32 60 1 0 0 0]
338
+ ph--- orp-- ph--- orp-- tanks CH--- CYA TA--- Wtr MODE
339
+ 0-1 pH(1-2) / ORP(8-9) reading
340
+ 02 E3 - pH 2*256 + e3(227) = 739
341
+ 02 AF - ORP 2*256 + af(175) = 687
342
+
343
+ 4-5 pH settings
344
+ D0 = 7.2 (hi/lo bits - 720 = 7.2pH)
345
+ DA = 7.3
346
+ E4 = 7.4
347
+ EE = 7.5
348
+ F8 = 7.6
349
+
350
+ 6-7 ORP settings
351
+ 02 BC = 700 (hi/lo bits)
352
+
353
+ 20-21 Tank levels; 21 is acid? 22 is chlorine?
354
+ 06 and 05
355
+
356
+ 23-24 Calcimum Hardness
357
+ 90 is CH (90 = 400; 8b = 395) hi/lo bits
358
+
359
+ 26
360
+ 00 is CYA (00 = 0; 9 = 9; c9 = 201) (does not appear to have hi/lo - 201 is max
361
+
362
+ 27-28 - Total Alkalinity
363
+ 96 is TA (96 = 150)
364
+
365
+ 30 - Water Flow Alarm (00 is ok; 01 is no flow)
366
+ 00 flow is OK
367
+ 01 flow is Alarm on (Water STOPPED)
368
+
369
+ // todo: these are probably 2-byte status message but need to confirm
370
+ 36 Mode
371
+ 0x25 dosing (auto)?
372
+ 0x45 dosing acid (manually?)
373
+ 0x55 mixing???
374
+ 0x65 monitoring
375
+ 0x02 (12 when mixing) and 04 (27 when mixing) related???
376
+
377
+ 37
378
+ 20 Nothing
379
+ 22 Dosing Chlorine(?)
380
+ */
381
+
382
+
383
+ //// Missing information on the related bytes.
384
+ //// Bytes 8-14 (Probably Total Dissolved Solids in here if no IntelliChlor)
385
+ //// controller.waterVolume = msg.extractPayloadByte(15) * 1000;
386
+ //// Bytes 16-19
387
+ //scontroller.ph.dosingVolumeRemaining = msg.extractPayloadByte(17); // Previous pH Dose volume
388
+ //scontroller.orp.dosingVolumeRemaining = msg.extractPayloadByte(19); // Previous ORP Dose volume
389
+ //scontroller.ph.tank.level = Math.max(msg.extractPayloadByte(20) > 0 ? msg.extractPayloadByte(20) - 1 : msg.extractPayloadByte(20), 0); // values reported as 1-7; possibly 0 if no tank present
390
+ //scontroller.orp.tank.level = Math.max(msg.extractPayloadByte(21) > 0 ? msg.extractPayloadByte(21) - 1 : msg.extractPayloadByte(21), 0); // values reported as 1-7; possibly 0 if no tank present
391
+ //let SIRaw = msg.extractPayloadByte(22);
392
+ //if ((SIRaw & 0x80) === 0x80) {
393
+ // // negative SI
394
+ // scontroller.saturationIndex = (256 - SIRaw) / -100;
395
+ //}
396
+ //else {
397
+ // scontroller.saturationIndex = msg.extractPayloadByte(22) / 100;
398
+ //}
399
+ //controller.calciumHardness = msg.extractPayloadIntBE(23);
400
+
401
+ //// scontroller.status2 = msg.extractPayloadByte(25); // remove/unsure?
402
+ //controller.cyanuricAcid = msg.extractPayloadByte(26);
403
+ //controller.alkalinity = msg.extractPayloadIntBE(27);
404
+
405
+ //// scontroller.waterFlow = msg.extractPayloadByte(30); // This is probably the temp units.
406
+ //scontroller.ph.probe.tempUnits = 0;//msg.extractPayloadByte(30); See Above. This is probably the units.
407
+ //scontroller.ph.probe.temperature = msg.extractPayloadByte(31);
408
+
409
+ //msg.extractPayloadByte(33);
410
+
411
+ //// [0, { name: 'dosing', desc: 'Dosing' }],
412
+ //// [1, { name: 'monitoring', desc: 'Monitoring' }],
413
+ //// [2, { name: 'mixing', desc: 'Mixing' }]
414
+
415
+ //scontroller.ph.dosingStatus = (msg.extractPayloadByte(34) & 0x30) >> 4; // mask 00xx0000 and shift bit 5 & 6
416
+ //scontroller.orp.dosingStatus = (msg.extractPayloadByte(34) & 0xC0) >> 6; // mask xx000000 and shift bit 7 & 8
417
+ //scontroller.ph.flowDelay = scontroller.orp.flowDelay = (msg.extractPayloadByte(35) & 0x02) === 1 ? true : false;
418
+ //scontroller.status = msg.extractPayloadByte(35) & 0x80 >> 7; // to be verified as comms lost
419
+ //scontroller.ph.manualDosing = (msg.extractPayloadByte(35) & 0x08) === 1 ? true : false;
420
+ //controller.orp.useChlorinator = (msg.extractPayloadByte(35) & 0x10) === 1 ? true : false;
421
+ //controller.HMIAdvancedDisplay = (msg.extractPayloadByte(35) & 0x20) === 1 ? true : false;
422
+ //controller.ph.phSupply = (msg.extractPayloadByte(35) & 0x40) === 1 ? true : false; // acid pH dosing = 1; base pH dosing = 0;
423
+ //scontroller.firmware = `${msg.extractPayloadByte(37)}.${msg.extractPayloadByte(36).toString().padStart(3, '0')}`
424
+
425
+ //const warnings = scontroller.warnings;
426
+ //warnings.waterChemistry = msg.extractPayloadByte(38);
427
+ //warnings.pHLockout = msg.extractPayloadByte(33) & 0x01;
428
+ //warnings.pHDailyLimitReached = msg.extractPayloadByte(33) & 0x02;
429
+ //warnings.orpDailyLimitReached = msg.extractPayloadByte(33) & 0x04;
430
+ //warnings.invalidSetup = msg.extractPayloadByte(33) & 0x08;
431
+ //warnings.chlorinatorCommError = msg.extractPayloadByte(33) & 0x10;
432
+
433
+ //// RKS: This should really match against the body for the chlorinator when *Chem thinks it has been provided TDS.
434
+ //// RG: Byte 35, bit 4 indicates IntelliChlor is used. Until we know more, this logic suffices.
435
+ //if (sys.chlorinators.length > 0) {
436
+ // let chlor = state.chlorinators.find(elem => elem.id === 1);
437
+ // scontroller.orp.probe.saltLevel = (typeof chlor !== 'undefined') ? chlor.saltLevel : msg.extractPayloadByte(29) * 50;
438
+ //}
439
+ //else scontroller.orp.probe.saltLevel = 0;
440
+
441
+ //// manually emit extended values
442
+ //webApp.emitToClients('chemController', scontroller.getExtended()); // emit extended data
443
+ //scontroller.hasChanged = false; // try to avoid duplicate emits
444
+ //msg.isProcessed = true;
445
+ }
398
446
  }