nodejs-poolcontroller 7.2.0 → 7.5.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 (64) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
  2. package/Changelog +13 -0
  3. package/Dockerfile +1 -0
  4. package/README.md +5 -5
  5. package/app.ts +11 -0
  6. package/config/Config.ts +3 -0
  7. package/config/VersionCheck.ts +8 -4
  8. package/controller/Constants.ts +165 -9
  9. package/controller/Equipment.ts +186 -65
  10. package/controller/Errors.ts +22 -1
  11. package/controller/State.ts +273 -57
  12. package/controller/boards/EasyTouchBoard.ts +194 -95
  13. package/controller/boards/IntelliCenterBoard.ts +115 -42
  14. package/controller/boards/IntelliTouchBoard.ts +104 -30
  15. package/controller/boards/NixieBoard.ts +155 -53
  16. package/controller/boards/SystemBoard.ts +1529 -514
  17. package/controller/comms/Comms.ts +219 -42
  18. package/controller/comms/messages/Messages.ts +16 -4
  19. package/controller/comms/messages/config/ChlorinatorMessage.ts +13 -3
  20. package/controller/comms/messages/config/CircuitGroupMessage.ts +6 -0
  21. package/controller/comms/messages/config/CircuitMessage.ts +1 -1
  22. package/controller/comms/messages/config/CoverMessage.ts +1 -0
  23. package/controller/comms/messages/config/EquipmentMessage.ts +4 -0
  24. package/controller/comms/messages/config/ExternalMessage.ts +43 -25
  25. package/controller/comms/messages/config/FeatureMessage.ts +8 -1
  26. package/controller/comms/messages/config/GeneralMessage.ts +8 -0
  27. package/controller/comms/messages/config/HeaterMessage.ts +15 -9
  28. package/controller/comms/messages/config/IntellichemMessage.ts +4 -1
  29. package/controller/comms/messages/config/OptionsMessage.ts +13 -1
  30. package/controller/comms/messages/config/PumpMessage.ts +4 -20
  31. package/controller/comms/messages/config/RemoteMessage.ts +4 -0
  32. package/controller/comms/messages/config/ScheduleMessage.ts +11 -0
  33. package/controller/comms/messages/config/SecurityMessage.ts +1 -0
  34. package/controller/comms/messages/config/ValveMessage.ts +12 -2
  35. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +14 -6
  36. package/controller/comms/messages/status/EquipmentStateMessage.ts +78 -24
  37. package/controller/comms/messages/status/HeaterStateMessage.ts +25 -5
  38. package/controller/comms/messages/status/IntelliChemStateMessage.ts +55 -26
  39. package/controller/nixie/Nixie.ts +18 -16
  40. package/controller/nixie/NixieEquipment.ts +6 -6
  41. package/controller/nixie/bodies/Body.ts +7 -4
  42. package/controller/nixie/bodies/Filter.ts +7 -4
  43. package/controller/nixie/chemistry/ChemController.ts +800 -283
  44. package/controller/nixie/chemistry/Chlorinator.ts +22 -14
  45. package/controller/nixie/circuits/Circuit.ts +42 -7
  46. package/controller/nixie/heaters/Heater.ts +303 -30
  47. package/controller/nixie/pumps/Pump.ts +57 -30
  48. package/controller/nixie/schedules/Schedule.ts +10 -7
  49. package/controller/nixie/valves/Valve.ts +7 -5
  50. package/defaultConfig.json +32 -1
  51. package/issue_template.md +1 -1
  52. package/logger/DataLogger.ts +37 -22
  53. package/package.json +20 -18
  54. package/web/Server.ts +529 -31
  55. package/web/bindings/influxDB.json +157 -5
  56. package/web/bindings/mqtt.json +112 -13
  57. package/web/bindings/mqttAlt.json +109 -11
  58. package/web/interfaces/baseInterface.ts +2 -1
  59. package/web/interfaces/httpInterface.ts +2 -0
  60. package/web/interfaces/influxInterface.ts +103 -54
  61. package/web/interfaces/mqttInterface.ts +16 -5
  62. package/web/services/config/Config.ts +179 -43
  63. package/web/services/state/State.ts +51 -5
  64. package/web/services/state/StateSocket.ts +19 -2
@@ -63,8 +63,9 @@ export class ChlorinatorStateMessage {
63
63
  // I n t e l l i c h l o r - - 4 0
64
64
  //[16, 2, 0, 3][0, 73, 110, 116, 101, 108, 108, 105, 99, 104, 108, 111, 114, 45, 45, 52, 48][188, 16, 3]
65
65
  // This is the model number of the chlorinator and the address is actually the second byte.
66
- if (typeof chlor.name === 'undefined' || chlor.name === '') chlor.name = msg.extractPayloadString(1, 16);
67
- cstate.name = chlor.name;
66
+ let name = msg.extractPayloadString(1, 16);
67
+ if (typeof chlor.name === 'undefined' || chlor.name === '') chlor.name = cstate.name = name;
68
+ if (typeof chlor.model === 'undefined') chlor.model = sys.board.valueMaps.chlorinatorModel.getValue(name.toLowerCase());
68
69
  cstate.isActive = chlor.isActive;
69
70
  state.emitEquipmentChanges();
70
71
  break;
@@ -73,9 +74,12 @@ export class ChlorinatorStateMessage {
73
74
  // If the chlorinator is no longer talking to us then clear the current output.
74
75
  if (cstate.status === 128) cstate.currentOutput = 0;
75
76
  cstate.targetOutput = msg.extractPayloadByte(0);
76
- if (chlor.disabled && cstate.targetOutput !== 0) {
77
+ if (chlor.disabled && (cstate.targetOutput !== 0 || cstate.superChlor || cstate.superChlorHours > 0)) {
77
78
  // Some dumbass is trying to change our output. We need to set it back to 0.
78
- sys.board.chlorinator.setChlorAsync({ id: chlor.id, disabled: true });
79
+ sys.board.chlorinator.setChlorAsync({ id: chlor.id, disabled: chlor.disabled, superChlor: false, superChlorHours: 0 });
80
+ }
81
+ else if (chlor.isDosing && cstate.targetOutput !== 100){
82
+ sys.board.chlorinator.setChlorAsync({ id: chlor.id, isDosing: chlor.isDosing });
79
83
  }
80
84
  state.emitEquipmentChanges();
81
85
  break;
@@ -87,8 +91,12 @@ export class ChlorinatorStateMessage {
87
91
  // The current output here is not correct. The reason that is is because this is a request from the OCP to the Chlorinator.
88
92
  //cstate.currentOutput = msg.action === 17 ? msg.extractPayloadByte(0) : msg.extractPayloadByte(0) / 10;
89
93
  cstate.targetOutput = msg.extractPayloadByte(0) / 10;
90
- if (chlor.disabled && cstate.targetOutput !== 0) {
91
- sys.board.chlorinator.setChlorAsync({ id: chlor.id, disabled: true });
94
+ if (chlor.disabled && (cstate.targetOutput !== 0 || cstate.superChlor || cstate.superChlorHours > 0)) {
95
+ // Some dumbass is trying to change our output. We need to set it back to 0.
96
+ sys.board.chlorinator.setChlorAsync({ id: chlor.id, disabled: chlor.disabled, superChlor: false, superChlorHours: 0 });
97
+ }
98
+ else if (chlor.isDosing && cstate.targetOutput !== 100){
99
+ sys.board.chlorinator.setChlorAsync({ id: chlor.id, isDosing: chlor.isDosing });
92
100
  }
93
101
  state.emitEquipmentChanges();
94
102
  break;
@@ -101,7 +101,9 @@ export class EquipmentStateMessage {
101
101
  Message.headerSubByte = msg.header[1];
102
102
  //console.log(process.memoryUsage());
103
103
  if (msg.action === 2 && state.isInitialized && sys.controllerType === ControllerType.Nixie) {
104
- // Start over because we didn't have communication before but we now do.
104
+ // Start over because we didn't have communication before but we now do. This will fall into the if
105
+ // below so that it goes through the intialization process. In this case we didn't see an OCP when we started
106
+ // but there clearly is one now.
105
107
  sys.controllerType = ControllerType.Unknown;
106
108
  state.status = 0;
107
109
  }
@@ -139,19 +141,24 @@ export class EquipmentStateMessage {
139
141
 
140
142
  // Shared
141
143
  let dt = new Date();
142
- if (state.chemControllers.length > 0) {
143
- // TODO: move this to chemController when we understand the packets better
144
- for (let i = 0; i < state.chemControllers.length; i++) {
145
- let ccontroller = state.chemControllers.getItemByIndex(i);
146
- if (sys.board.valueMaps.chemControllerTypes.getName(ccontroller.type) === 'intellichem') {
147
- if (dt.getTime() - ccontroller.lastComm > 60000) ccontroller.status = 1;
148
- }
149
- }
150
- }
144
+ // RKS: This was moved to the ChemControllerState message. This is flawed in that it incorrectly sets IntelliChem to no comms.
145
+ //if (state.chemControllers.length > 0) {
146
+ // // TODO: move this to chemController when we understand the packets better
147
+ // for (let i = 0; i < state.chemControllers.length; i++) {
148
+ // let ccontroller = state.chemControllers.getItemByIndex(i);
149
+ // if (sys.board.valueMaps.chemControllerTypes.getName(ccontroller.type) === 'intellichem') {
150
+ // if (dt.getTime() - ccontroller.lastComm > 60000) ccontroller.status = 1;
151
+ // }
152
+ // }
153
+ //}
151
154
  state.time.hours = msg.extractPayloadByte(0);
152
155
  state.time.minutes = msg.extractPayloadByte(1);
153
156
  state.time.seconds = dt.getSeconds();
154
- state.mode = msg.extractPayloadByte(9) & 0x81;
157
+ state.mode = sys.controllerType !== ControllerType.IntelliCenter ? (msg.extractPayloadByte(9) & 0x81) : (msg.extractPayloadByte(9) & 0x01);
158
+
159
+ // RKS: The units have been normalized for English and Metric for the overall panel. It is important that the val numbers match for at least the temp units since
160
+ // the only unit of measure native to the Touch controllers is temperature they chose to name these C or F. However, with the njsPC extensions this is non-semantic
161
+ // since pressure, volume, and length have been introduced.
155
162
  sys.general.options.units = state.temps.units = msg.extractPayloadByte(9) & 0x04;
156
163
  state.valve = msg.extractPayloadByte(10);
157
164
 
@@ -163,8 +170,8 @@ export class EquipmentStateMessage {
163
170
  // 1. IntelliCenter has "manual" time set (Internet will automatically adjust) and autoAdjustDST is enabled
164
171
  // 2. *Touch is "manual" (only option) and autoAdjustDST is enabled - (same as #1)
165
172
  // 3. clock source is "server" isn't an OCP option but can be enabled on the clients
166
- if (dt.getMinutes() % 5 === 0 && sys.general.options.clockSource === 'server') {
167
- if ((Math.abs(dt.getTime() - state.time.getTime()) > 60 * 5 * 1000) && !state.time.isUpdating) {
173
+ if (dt.getMinutes() % 5 === 0 && dt.getSeconds() <= 10 && sys.general.options.clockSource === 'server') {
174
+ if ((Math.abs(dt.getTime() - state.time.getTime()) > 60 * 2 * 1000) && !state.time.isUpdating) {
168
175
  state.time.isUpdating = true;
169
176
  sys.board.system.setDateTimeAsync({ dt, dst: sys.general.options.adjustDST || 0, })
170
177
  .then(() => {
@@ -197,10 +204,19 @@ export class EquipmentStateMessage {
197
204
  tbody.name = cbody.name;
198
205
  tbody.circuit = cbody.circuit = 6;
199
206
  tbody.heatStatus = msg.extractPayloadByte(11) & 0x0F;
200
- if ((msg.extractPayloadByte(2) & 0x20) === 32) {
207
+ // With the IntelliCenter i10D, bit 6 is not reliable. It is not set properly and requires the 204 message
208
+ // to process the data.
209
+ if (!sys.equipment.dual) {
210
+ if ((msg.extractPayloadByte(2) & 0x20) === 32) {
211
+ tbody.temp = state.temps.waterSensor1;
212
+ tbody.isOn = true;
213
+ } else tbody.isOn = false;
214
+ }
215
+ else if (state.circuits.getItemById(6).isOn === true) {
201
216
  tbody.temp = state.temps.waterSensor1;
202
217
  tbody.isOn = true;
203
- } else tbody.isOn = false;
218
+ }
219
+ else tbody.isOn = false;
204
220
  }
205
221
  if (sys.bodies.length > 1) {
206
222
  const tbody: BodyTempState = state.temps.bodies.getItemById(2, true);
@@ -210,10 +226,16 @@ export class EquipmentStateMessage {
210
226
  tbody.name = cbody.name;
211
227
  tbody.circuit = cbody.circuit = 1;
212
228
  tbody.heatStatus = (msg.extractPayloadByte(11) & 0xF0) >> 4;
213
- if ((msg.extractPayloadByte(2) & 0x01) === 1) {
229
+ if (!sys.equipment.dual) {
230
+ if ((msg.extractPayloadByte(2) & 0x01) === 1) {
231
+ tbody.temp = sys.equipment.shared ? state.temps.waterSensor1 : state.temps.waterSensor2;
232
+ tbody.isOn = true;
233
+ } else tbody.isOn = false;
234
+ } else if (state.circuits.getItemById(1).isOn === true) {
214
235
  tbody.temp = sys.equipment.shared ? state.temps.waterSensor1 : state.temps.waterSensor2;
215
236
  tbody.isOn = true;
216
- } else tbody.isOn = false;
237
+ }
238
+ else tbody.isOn = false;
217
239
  }
218
240
  if (sys.bodies.length > 2) {
219
241
  state.temps.waterSensor3 = fnTempFromByte(msg.extractPayloadByte(20));
@@ -390,13 +412,14 @@ export class EquipmentStateMessage {
390
412
  sys.board.circuits.syncCircuitRelayStates();
391
413
  sys.board.circuits.syncVirtualCircuitStates();
392
414
  sys.board.valves.syncValveStates();
415
+ sys.board.filters.syncFilterStates();
393
416
  state.emitControllerChange();
394
417
  state.emitEquipmentChanges();
395
418
  sys.board.heaters.syncHeaterStates();
396
419
  break;
397
420
  }
398
- case ControllerType.IntelliCom:
399
421
  case ControllerType.EasyTouch:
422
+ case ControllerType.IntelliCom:
400
423
  case ControllerType.IntelliTouch:
401
424
  {
402
425
  EquipmentStateMessage.processTouchCircuits(msg);
@@ -405,6 +428,7 @@ export class EquipmentStateMessage {
405
428
  sys.board.features.syncGroupStates();
406
429
  sys.board.circuits.syncVirtualCircuitStates();
407
430
  sys.board.valves.syncValveStates();
431
+ sys.board.filters.syncFilterStates();
408
432
  state.emitControllerChange();
409
433
  state.emitEquipmentChanges();
410
434
  sys.board.heaters.syncHeaterStates();
@@ -495,8 +519,7 @@ export class EquipmentStateMessage {
495
519
  state.time.date = msg.extractPayloadByte(6);
496
520
  state.time.month = msg.extractPayloadByte(7);
497
521
  state.time.year = msg.extractPayloadByte(8);
498
- sys.equipment.controllerFirmware = (msg.extractPayloadByte(42)
499
- + (msg.extractPayloadByte(43) / 1000)).toString();
522
+ sys.equipment.controllerFirmware = (msg.extractPayloadByte(42) + (msg.extractPayloadByte(43) / 1000)).toString();
500
523
  if (sys.chlorinators.length > 0) {
501
524
  if (msg.extractPayloadByte(37, 255) !== 255) {
502
525
  const chlor = state.chlorinators.getItemById(1);
@@ -508,9 +531,38 @@ export class EquipmentStateMessage {
508
531
  }
509
532
  }
510
533
  ExternalMessage.processFeatureState(9, msg);
534
+ //if (sys.equipment.dual === true) {
535
+ // // For IntelliCenter i10D the body state is on byte 26 of the 204. This impacts circuit 6.
536
+ // let byte = msg.extractPayloadByte(26);
537
+ // let pstate = state.circuits.getItemById(6, true);
538
+ // let oldstate = pstate.isOn;
539
+ // pstate.isOn = ((byte & 0x0010) === 0x0010);
540
+ // logger.info(`Checking i10D pool state ${byte} old:${oldstate} new: ${pstate.isOn}`);
541
+ // //if (oldstate !== pstate.isOn) {
542
+ // state.temps.bodies.getItemById(1, true).isOn = pstate.isOn;
543
+ // sys.board.circuits.syncCircuitRelayStates();
544
+ // sys.board.circuits.syncVirtualCircuitStates();
545
+ // sys.board.valves.syncValveStates();
546
+ // sys.board.filters.syncFilterStates();
547
+ // sys.board.heaters.syncHeaterStates();
548
+ // //}
549
+ // if (oldstate !== pstate.isOn) pstate.emitEquipmentChange();
550
+ //}
551
+ // At this point normally on is ignored. Not sure what this does.
552
+ let cover1 = sys.covers.getItemById(1);
553
+ let cover2 = sys.covers.getItemById(2);
554
+ if (cover1.isActive) {
555
+ let scover1 = state.covers.getItemById(1, true);
556
+ scover1.name = cover1.name;
557
+ state.temps.bodies.getItemById(cover1.body + 1).isCovered = scover1.isClosed = (msg.extractPayloadByte(30) & 0x0001) > 0;
558
+ }
559
+ if (cover2.isActive) {
560
+ let scover2 = state.covers.getItemById(2, true);
561
+ scover2.name = cover2.name;
562
+ state.temps.bodies.getItemById(cover2.body + 1).isCovered = scover2.isClosed = (msg.extractPayloadByte(30) & 0x0002) > 0;
563
+ }
511
564
  msg.isProcessed = true;
512
- // state.emitControllerChange();
513
- // state.emitEquipmentChanges();
565
+ state.emitEquipmentChanges();
514
566
  break;
515
567
  }
516
568
  }
@@ -528,13 +580,15 @@ export class EquipmentStateMessage {
528
580
  let circuit = sys.circuits.getItemById(circuitId, false, { isActive: false });
529
581
  if (circuit.isActive !== false) {
530
582
  let cstate = state.circuits.getItemById(circuitId, circuit.isActive);
531
- let isOn = (byte & (1 << j)) > 0;
532
- sys.board.circuits.setEndTime(circuit, cstate, isOn);
583
+ // For IntelliCenter i10D body circuits are not reported here.
584
+ let isOn = ((circuitId === 6 || circuitId === 1) && sys.equipment.dual === true) ? cstate.isOn : (byte & (1 << j)) > 0;
585
+ //let isOn = (byte & (1 << j)) > 0;
533
586
  cstate.isOn = isOn;
534
587
  cstate.name = circuit.name;
535
588
  cstate.nameId = circuit.nameId;
536
589
  cstate.showInFeatures = circuit.showInFeatures;
537
590
  cstate.type = circuit.type;
591
+ sys.board.circuits.setEndTime(circuit, cstate, isOn);
538
592
  if (sys.controllerType === ControllerType.IntelliCenter) {
539
593
  // intellitouch sends a separate msg with themes
540
594
  switch (circuit.type) {
@@ -31,13 +31,33 @@ export class HeaterStateMessage {
31
31
  }
32
32
  }
33
33
  public static processHeaterStatus(msg: Inbound) {
34
- let heater: Heater;
35
- heater = sys.heaters.getItemByAddress(msg.source);
36
-
37
- // We need to decode the message. For a 2 of
38
- //[165, 1, 15, 16, 2, 29][16, 42, 3, 0, 0, 0, 0, 0, 0, 32, 0, 0, 2, 0, 88, 88, 0, 241, 95, 100, 24, 246, 0, 0, 0, 0, 0, 40, 0][4, 221]
34
+ // RKS: 07-03-21 - We only know byte 2 at this point for Ultratemp for the 115 message we are processing here. The
35
+ // byte description
36
+ // ------------------------------------------------
37
+ // 0 Unknown (always seems to be 160 for response)
38
+ // 1 Unknown (always 1)
39
+ // 2 Current heater status 0=off, 1=heat, 2=cool
40
+ // 3-9 Unknown
41
+
42
+ // 114 message - outbound response
39
43
  //[165, 0, 112, 16, 114, 10][144, 0, 0, 0, 0, 0, 0, 0, 0, 0][2, 49] // OCP to Heater
44
+ // byte description
45
+ // ------------------------------------------------
46
+ // 0 Unknown (always seems to be 144 for request)
47
+ // 1 Current heater status 0=off, 1=heat, 2=cool
48
+ // 3 Believed to be ofset temp
49
+ // 4-9 Unknown
50
+
51
+ // byto 0: always seems to be 144 for outbound
52
+ // byte 1: Sets heater mode to 0 = Off 1 = Heat 2 = Cool
40
53
  //[165, 0, 16, 112, 115, 10][160, 1, 0, 3, 0, 0, 0, 0, 0, 0][2, 70] // Heater Reply
54
+ let heater: Heater = sys.heaters.getItemByAddress(msg.source);
55
+ let sheater = state.heaters.getItemById(heater.id);
56
+ let byte = msg.extractPayloadByte(2);
57
+ sheater.isOn = byte >= 1;
58
+ sheater.isCooling = byte === 2;
59
+ sheater.commStatus = 0;
60
+ state.equipment.messages.removeItemByCode(`heater:${heater.id}:comms`);
41
61
  msg.isProcessed = true;
42
62
  }
43
63
  }
@@ -23,15 +23,25 @@ import { Timestamp, utils } from "../../../Constants"
23
23
  export class IntelliChemStateMessage {
24
24
  public static process(msg: Inbound) {
25
25
  if (sys.controllerType === ControllerType.Unknown) return;
26
- if (msg.source < 144 || msg.source > 158) 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
+ }
27
33
  switch (msg.action) {
28
-
29
34
  // ---------- IntelliChem Control panel is spitting out its status ----------- //
30
35
  case 18: // IntelliChem is sending us it's status.
31
36
  IntelliChemStateMessage.processState(msg);
32
37
  break;
33
38
  case 210: // OCP is asking IntelliChem controller for it's current status info.
34
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
+ }
35
45
  msg.isProcessed = true;
36
46
  break;
37
47
  // ---------- End IntelliChem set get ----------- //
@@ -43,7 +53,7 @@ export class IntelliChemStateMessage {
43
53
 
44
54
  /* RKS: This is processed in the IntellichemMessage.processTouch() and is the results of asking for the IntelliChem configuration.
45
55
  case 147: // OCP is broadcasting it's known ic values... Need to change our settings if virtual.
46
- // 147 is a proto:broadcast message;
56
+ // 147 is a proto:broadcast message;
47
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.
48
58
  if (msg.dest < 144 || msg.dest > 158) return;
49
59
  IntelliChemStateMessage.processControllerChange(msg);
@@ -57,14 +67,11 @@ export class IntelliChemStateMessage {
57
67
  msg.isProcessed = true;
58
68
  break;
59
69
  case 146: // OCP is telling IntelliChem that it needs to change its settings to...
60
- let address = msg.dest;
61
- // The address is king here. The id is not.
62
- let controller = sys.chemControllers.getItemByAddress(address, true);
63
- let scontroller = state.chemControllers.getItemById(controller.id, true);
64
- if (scontroller.lastComm + (30 * 1000) < new Date().getTime()) {
65
- // We have not talked to the chem controller in 30 seconds so we have lost communication.
66
- scontroller.status = scontroller.alarms.comms = 1;
67
- }
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
+ //}
68
75
  controller.ph.tank.capacity = controller.orp.tank.capacity = 6;
69
76
  controller.ph.tank.units = controller.orp.tank.units = '';
70
77
  msg.isProcessed = true;
@@ -89,8 +96,8 @@ export class IntelliChemStateMessage {
89
96
  // 6-7 : ORP Setpoint : byte(6) x 256 + byte(7)
90
97
  // 8 : Unknown = 0
91
98
  // 9 : Unknown = 0
92
- // 10 : Unknown = 0
93
- // 11-12 : pH Dose time seconds. The number of seconds since the dose started. byte(11) x 256 + byte(12)
99
+ // 10-11 : pH Dose time seconds. The number of seconds since the dose started. byte(10) x 256 + byte(11)
100
+ // 12: Unknown
94
101
  // 13 : Unknown
95
102
  // 14-15 : ORP Dose time seconds. The number of seconds since the dose started. byte(14) x 256 + byte(15)
96
103
  // 16-17 : pH Dose volume (unknown units) - These appear to be mL.
@@ -122,10 +129,13 @@ export class IntelliChemStateMessage {
122
129
  schem.type = chem.type = sys.board.valueMaps.chemControllerTypes.getValue('intellichem');
123
130
  chem.name = chem.name || `IntelliChem ${chem.address - 143}`; // default to true id if no name is set
124
131
  schem.lastComm = new Date().getTime();
125
- schem.status = schem.alarms.comms = 0;
132
+ schem.status = schem.alarms.comms = 0;
126
133
  chem.ph.tank.capacity = chem.orp.tank.capacity = 6;
127
134
  chem.ph.tank.units = chem.orp.tank.units = '';
128
-
135
+ chem.ph.tank.alarmEmptyEnabled = false;
136
+ chem.ph.tank.alarmEmptyLevel = 1;
137
+ chem.orp.tank.alarmEmptyEnabled = false;
138
+ chem.orp.tank.alarmEmptyLevel = 1;
129
139
  schem.address = chem.address;
130
140
  schem.ph.level = schem.ph.probe.level = msg.extractPayloadIntBE(0) / 100;
131
141
  schem.orp.level = schem.orp.probe.level = msg.extractPayloadIntBE(2);
@@ -133,11 +143,11 @@ export class IntelliChemStateMessage {
133
143
  chem.orp.setpoint = msg.extractPayloadIntBE(6);
134
144
  // Missing information on the related bytes.
135
145
  // Bytes 8-14 (Probably Total Dissolved Solids in here if no IntelliChlor)
136
- let phPrev = { status: schem.orp.dosingStatus, time: schem.ph.timeDosed || 0, vol: schem.ph.volumeDosed };
146
+ let phPrev = { status: schem.ph.dosingStatus, time: schem.ph.timeDosed || 0, vol: schem.ph.volumeDosed };
137
147
  let orpPrev = { status: schem.orp.dosingStatus, time: schem.orp.timeDosed || 0, vol: schem.orp.volumeDosed };
138
148
  // IntelliChem never tells us what the dose time or volume is so we will let that dog lie.
139
- // 11-12 : pH Dose time
140
- schem.ph.timeDosed = (msg.extractPayloadByte(11) * 256) + msg.extractPayloadByte(12);
149
+ // 10-11 : pH Dose time
150
+ schem.ph.timeDosed = (msg.extractPayloadByte(10) * 256) + msg.extractPayloadByte(11);
141
151
  // 14-15 : ORP Dose time seconds. The number of seconds since the dose started.
142
152
  schem.orp.timeDosed = (msg.extractPayloadByte(14) * 256) + msg.extractPayloadByte(15);
143
153
  // 16-17 : pH Dose volume (unknown units) = 35
@@ -157,7 +167,7 @@ export class IntelliChemStateMessage {
157
167
  chem.cyanuricAcid = msg.extractPayloadByte(26);
158
168
  // 27-28 : Alkalinity
159
169
  chem.alkalinity = (msg.extractPayloadByte(27) * 256) + msg.extractPayloadByte(28);
160
- // 29 : Salt level = 20
170
+ // 29 : Salt level = 20
161
171
  if (sys.chlorinators.length > 0) {
162
172
  let chlor = state.chlorinators.find(elem => elem.id === 1);
163
173
  schem.orp.probe.saltLevel = (typeof chlor !== 'undefined') ? chlor.saltLevel : msg.extractPayloadByte(29) * 50;
@@ -169,6 +179,7 @@ export class IntelliChemStateMessage {
169
179
  // 32 : Alarms = 8 = (no alarm)
170
180
  const alarms = schem.alarms;
171
181
  alarms.flow = msg.extractPayloadByte(32) & 0x01;
182
+ if (alarms.flow === 0) schem.flowDetected = true;
172
183
  alarms.pH = msg.extractPayloadByte(32) & 0x06;
173
184
  alarms.orp = msg.extractPayloadByte(32) & 0x18;
174
185
  alarms.pHTank = msg.extractPayloadByte(32) & 0x20;
@@ -187,16 +198,34 @@ export class IntelliChemStateMessage {
187
198
  schem.ph.dosingStatus = (msg.extractPayloadByte(34) & 0x30) >> 4; // mask 00xx0000 and shift bit 5 & 6
188
199
  schem.orp.dosingStatus = (msg.extractPayloadByte(34) & 0xC0) >> 6; // mask xx000000 and shift bit 7 & 8
189
200
  // 35 : Delays = 0
190
- schem.status = msg.extractPayloadByte(35) & 0x80 >> 7; // to be verified as comms lost
201
+ schem.status = (msg.extractPayloadByte(35) & 0x80) >> 7; // to be verified as comms lost
191
202
  schem.ph.manualDosing = (msg.extractPayloadByte(35) & 0x08) === 1 ? true : false;
192
203
  chem.orp.useChlorinator = (msg.extractPayloadByte(35) & 0x10) === 1 ? true : false;
193
204
  chem.HMIAdvancedDisplay = (msg.extractPayloadByte(35) & 0x20) === 1 ? true : false;
194
- chem.ph.phSupply = (msg.extractPayloadByte(35) & 0x40) === 1 ? true : false; // acid pH dosing = 1; base pH dosing = 0;
205
+ chem.ph.phSupply = (msg.extractPayloadByte(35) & 0x40) === 1 ? 'acid' : 'base'; // acid pH dosing = 1; base pH dosing = 0;
195
206
  // 36-37 : Firmware = 80,1 = 1.080
196
207
  chem.firmware = `${msg.extractPayloadByte(37)}.${msg.extractPayloadByte(36).toString().padStart(3, '0')}`
197
208
  // 38 : Water Chemistry Warning
198
209
  schem.warnings.waterChemistry = msg.extractPayloadByte(38);
199
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
+ }
200
229
  schem.ph.pump.isDosing = schem.ph.dosingStatus === 0 && chem.ph.enabled;
201
230
  schem.orp.pump.isDosing = schem.orp.dosingStatus === 0 && chem.orp.enabled;
202
231
  schem.calculateSaturationIndex();
@@ -212,12 +241,12 @@ export class IntelliChemStateMessage {
212
241
  if (typeof schem.ph.currentDose !== 'undefined') {
213
242
  // We just ended a dose so write it out to the chem logs.
214
243
  schem.ph.endDose(Timestamp.now.addSeconds(-(schem.ph.doseTime - phPrev.time)).toDate(), 'completed',
215
- schem.ph.volumeDosed - phPrev.vol, (schem.ph.doseTime - phPrev.time) * 1000);
244
+ schem.ph.volumeDosed - phPrev.vol, (schem.ph.timeDosed - phPrev.time) * 1000);
216
245
  }
217
246
  }
218
247
  else if (schem.ph.dosingStatus === 0) {
219
248
  // We are still dosing so add the time and volume to the dose.
220
- schem.ph.appendDose(schem.ph.doseVolume - phPrev.vol, (schem.ph.doseTime - phPrev.time) * 1000);
249
+ schem.ph.appendDose(schem.ph.doseVolume - phPrev.vol, (schem.ph.timeDosed - phPrev.time) * 1000);
221
250
  }
222
251
  else {
223
252
  console.log(`DOSING STATUS === ${schem.ph.dosingStatus}`);
@@ -228,19 +257,19 @@ export class IntelliChemStateMessage {
228
257
  if (schem.orp.dosingStatus === 0 && orpPrev.status !== 0) {
229
258
  if (schem.orp.dosingStatus === 0) {
230
259
  // We are starting a dose so we need to set the current dose.
231
- schem.orp.startDose(Timestamp.now.addSeconds(-schem.orp.doseTime).toDate(), schem.orp.manualDosing ? 'manual' : 'auto', 0, schem.orp.volumeDosed, schem.orp.timeDosed * 1000);
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);
232
261
  }
233
262
  }
234
263
  else if (schem.orp.dosingStatus !== 0 && orpPrev.status === 0) {
235
264
  if (typeof schem.orp.currentDose !== 'undefined') {
236
265
  // We just ended a dose so write it out to the chem logs.
237
266
  schem.orp.endDose(Timestamp.now.addSeconds(-(schem.orp.doseTime - orpPrev.time)).toDate(), 'completed',
238
- schem.orp.volumeDosed - orpPrev.vol, schem.orp.doseTime - orpPrev.time);
267
+ schem.orp.volumeDosed - orpPrev.vol, (schem.orp.timeDosed - orpPrev.time) * 1000);
239
268
  }
240
269
  }
241
270
  else if (schem.orp.dosingStatus === 0) {
242
271
  // We are still dosing so add the time and volume to the dose.
243
- schem.orp.appendDose(schem.orp.doseTime - orpPrev.time, schem.orp.doseVolume - orpPrev.vol);
272
+ schem.orp.appendDose(schem.orp.doseVolume - orpPrev.vol, (schem.orp.timeDosed - orpPrev.time) * 1000);
244
273
  }
245
274
  else {
246
275
  // Make sure we don't have a current dose going.
@@ -136,23 +136,25 @@ export class NixieControlPanel implements INixieControlPanel {
136
136
  try {
137
137
  let srv = [];
138
138
  let servers = webApp.findServersByType('rem');
139
- for (let i = 0; i < servers.length; i++) {
140
- let server = servers[i];
141
- // Sometimes I hate type safety.
142
- let devices = typeof server['getDevices'] === 'function' ? await server['getDevices']() : [];
143
- let int = config.getInterfaceByUuid(servers[i].uuid);
144
- srv.push({
145
- uuid: servers[i].uuid,
146
- name: servers[i].name,
147
- type: servers[i].type,
148
- isRunning: servers[i].isRunning,
149
- isConnected: servers[i].isConnected,
150
- devices: devices,
151
- remoteConnectionId: servers[i].remoteConnectionId,
152
- interface: int
153
- });
139
+ if (typeof servers !== 'undefined') {
140
+ for (let i = 0; i < servers.length; i++) {
141
+ let server = servers[i];
142
+ // Sometimes I hate type safety.
143
+ let devices = typeof server['getDevices'] === 'function' ? await server['getDevices']() : [];
144
+ let int = config.getInterfaceByUuid(servers[i].uuid);
145
+ srv.push({
146
+ uuid: servers[i].uuid,
147
+ name: servers[i].name,
148
+ type: servers[i].type,
149
+ isRunning: servers[i].isRunning,
150
+ isConnected: servers[i].isConnected,
151
+ devices: devices,
152
+ remoteConnectionId: servers[i].remoteConnectionId,
153
+ interface: int
154
+ });
155
+ }
156
+ await ncp.chemControllers.syncRemoteREMFeeds(srv);
154
157
  }
155
- await ncp.chemControllers.syncRemoteREMFeeds(srv);
156
158
  return srv;
157
159
  } catch (err) { logger.error(err); }
158
160
  }
@@ -93,12 +93,12 @@ export class NixieEquipmentCollection<T> extends Array<NixieEquipment> {
93
93
  catch (err) { return Promise.reject(err); }
94
94
  }
95
95
  }
96
- export class NixieRelay extends NixieEquipment {
96
+ //export class NixieRelay extends NixieEquipment {
97
97
 
98
- }
99
- export class NixieCircuit extends NixieRelay {
98
+ //}
99
+ //export class NixieCircuit extends NixieRelay {
100
100
 
101
- }
102
- export class NixieValve extends NixieRelay {
101
+ //}
102
+ //export class NixieValve extends NixieRelay {
103
103
 
104
- }
104
+ //}
@@ -33,9 +33,11 @@ export class NixieBodyCollection extends NixieEquipmentCollection<NixieBody> {
33
33
  for (let i = 0; i < bodies.length; i++) {
34
34
  let body = bodies.getItemByIndex(i);
35
35
  if (body.master === 1) {
36
- logger.info(`Initializing body ${body.name}`);
37
- let nbody = new NixieBody(this.controlPanel, body);
38
- this.push(nbody);
36
+ if (typeof this.find(elem => elem.id === body.id) === 'undefined') {
37
+ logger.info(`Initializing Nixie body ${body.name}`);
38
+ let nbody = new NixieBody(this.controlPanel, body);
39
+ this.push(nbody);
40
+ }
39
41
  }
40
42
  }
41
43
  }
@@ -81,13 +83,14 @@ export class NixieBody extends NixieEquipment {
81
83
  }
82
84
 
83
85
  public async pollEquipmentAsync() {
86
+ let self = this;
84
87
  try {
85
88
  if (typeof this._pollTimer !== 'undefined' || this._pollTimer) clearTimeout(this._pollTimer);
86
89
  this._pollTimer = null;
87
90
  let success = false;
88
91
  }
89
92
  catch (err) { logger.error(`Nixie Error polling body - ${err}`); }
90
- finally { this._pollTimer = setTimeout(async () => await this.pollEquipmentAsync(), this.pollingInterval || 10000); }
93
+ finally { this._pollTimer = setTimeout(async () => await self.pollEquipmentAsync(), this.pollingInterval || 10000); }
91
94
  }
92
95
  private async checkHardwareStatusAsync(connectionId: string, deviceBinding: string) {
93
96
  try {
@@ -42,9 +42,11 @@ export class NixieFilterCollection extends NixieEquipmentCollection<NixieFilter>
42
42
  for (let i = 0; i < filters.length; i++) {
43
43
  let filter = filters.getItemByIndex(i);
44
44
  if (filter.master === 1) {
45
- logger.info(`Initializing Filter ${Filter.name}`);
46
- let nFilter = new NixieFilter(this.controlPanel, filter);
47
- this.push(nFilter);
45
+ if (typeof this.find(elem => elem.id === filter.id) === 'undefined') {
46
+ logger.info(`Initializing Filter ${Filter.name}`);
47
+ let nFilter = new NixieFilter(this.controlPanel, filter);
48
+ this.push(nFilter);
49
+ }
48
50
  }
49
51
  }
50
52
  }
@@ -98,13 +100,14 @@ export class NixieFilter extends NixieEquipment {
98
100
  }
99
101
 
100
102
  public async pollEquipmentAsync() {
103
+ let self = this;
101
104
  try {
102
105
  if (typeof this._pollTimer !== 'undefined' || this._pollTimer) clearTimeout(this._pollTimer);
103
106
  this._pollTimer = null;
104
107
  let success = false;
105
108
  }
106
109
  catch (err) { logger.error(`Nixie Error polling Filter - ${err}`); }
107
- finally { this._pollTimer = setTimeout(async () => await this.pollEquipmentAsync(), this.pollingInterval || 10000); }
110
+ finally { this._pollTimer = setTimeout(async () => await self.pollEquipmentAsync(), this.pollingInterval || 10000); }
108
111
  }
109
112
  private async checkHardwareStatusAsync(connectionId: string, deviceBinding: string) {
110
113
  try {