nodejs-poolcontroller 7.6.1 → 7.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/.eslintrc.json +44 -44
  2. package/.github/ISSUE_TEMPLATE/1-bug-report.yml +84 -0
  3. package/.github/ISSUE_TEMPLATE/2-docs.md +12 -0
  4. package/.github/ISSUE_TEMPLATE/3-proposal.md +28 -0
  5. package/.github/ISSUE_TEMPLATE/config.yml +8 -0
  6. package/CONTRIBUTING.md +74 -74
  7. package/Changelog +220 -215
  8. package/Dockerfile +17 -17
  9. package/Gruntfile.js +40 -40
  10. package/LICENSE +661 -661
  11. package/README.md +191 -191
  12. package/app.ts +1 -1
  13. package/config/Config.ts +14 -0
  14. package/config/VersionCheck.ts +2 -2
  15. package/controller/Constants.ts +2 -1
  16. package/controller/Equipment.ts +2484 -2459
  17. package/controller/Errors.ts +180 -180
  18. package/controller/Lockouts.ts +502 -436
  19. package/controller/State.ts +106 -30
  20. package/controller/boards/AquaLinkBoard.ts +1000 -0
  21. package/controller/boards/BoardFactory.ts +49 -45
  22. package/controller/boards/EasyTouchBoard.ts +2859 -2653
  23. package/controller/boards/IntelliCenterBoard.ts +4198 -4230
  24. package/controller/boards/IntelliComBoard.ts +63 -63
  25. package/controller/boards/IntelliTouchBoard.ts +273 -241
  26. package/controller/boards/NixieBoard.ts +1728 -1675
  27. package/controller/boards/SystemBoard.ts +4925 -4697
  28. package/controller/comms/Comms.ts +442 -479
  29. package/controller/comms/messages/Messages.ts +171 -25
  30. package/controller/comms/messages/config/ChlorinatorMessage.ts +5 -2
  31. package/controller/comms/messages/config/CircuitGroupMessage.ts +0 -0
  32. package/controller/comms/messages/config/CircuitMessage.ts +1 -0
  33. package/controller/comms/messages/config/ConfigMessage.ts +0 -0
  34. package/controller/comms/messages/config/CoverMessage.ts +0 -0
  35. package/controller/comms/messages/config/CustomNameMessage.ts +30 -30
  36. package/controller/comms/messages/config/EquipmentMessage.ts +0 -0
  37. package/controller/comms/messages/config/ExternalMessage.ts +0 -0
  38. package/controller/comms/messages/config/FeatureMessage.ts +0 -0
  39. package/controller/comms/messages/config/GeneralMessage.ts +0 -0
  40. package/controller/comms/messages/config/HeaterMessage.ts +142 -10
  41. package/controller/comms/messages/config/IntellichemMessage.ts +0 -0
  42. package/controller/comms/messages/config/OptionsMessage.ts +4 -21
  43. package/controller/comms/messages/config/PumpMessage.ts +53 -35
  44. package/controller/comms/messages/config/RemoteMessage.ts +0 -0
  45. package/controller/comms/messages/config/ScheduleMessage.ts +350 -347
  46. package/controller/comms/messages/config/SecurityMessage.ts +0 -0
  47. package/controller/comms/messages/config/ValveMessage.ts +1 -1
  48. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +38 -86
  49. package/controller/comms/messages/status/EquipmentStateMessage.ts +58 -22
  50. package/controller/comms/messages/status/HeaterStateMessage.ts +116 -86
  51. package/controller/comms/messages/status/IntelliChemStateMessage.ts +445 -445
  52. package/controller/comms/messages/status/IntelliValveStateMessage.ts +35 -35
  53. package/controller/comms/messages/status/PumpStateMessage.ts +23 -1
  54. package/controller/comms/messages/status/VersionMessage.ts +0 -0
  55. package/controller/nixie/Nixie.ts +162 -162
  56. package/controller/nixie/NixieEquipment.ts +103 -103
  57. package/controller/nixie/bodies/Body.ts +120 -120
  58. package/controller/nixie/bodies/Filter.ts +135 -135
  59. package/controller/nixie/chemistry/ChemController.ts +2511 -2498
  60. package/controller/nixie/chemistry/Chlorinator.ts +363 -314
  61. package/controller/nixie/circuits/Circuit.ts +261 -248
  62. package/controller/nixie/heaters/Heater.ts +650 -648
  63. package/controller/nixie/pumps/Pump.ts +906 -661
  64. package/controller/nixie/schedules/Schedule.ts +313 -257
  65. package/controller/nixie/valves/Valve.ts +170 -170
  66. package/defaultConfig.json +306 -286
  67. package/logger/DataLogger.ts +448 -448
  68. package/logger/Logger.ts +0 -0
  69. package/package.json +56 -56
  70. package/tsconfig.json +25 -25
  71. package/web/Server.ts +92 -47
  72. package/web/bindings/aqualinkD.json +505 -0
  73. package/web/bindings/influxDB.json +1051 -1021
  74. package/web/bindings/mqtt.json +702 -654
  75. package/web/bindings/mqttAlt.json +731 -684
  76. package/web/bindings/rulesManager.json +54 -54
  77. package/web/bindings/smartThings-Hubitat.json +31 -31
  78. package/web/bindings/valveRelays.json +20 -20
  79. package/web/bindings/vera.json +25 -25
  80. package/web/interfaces/baseInterface.ts +137 -136
  81. package/web/interfaces/httpInterface.ts +145 -124
  82. package/web/interfaces/influxInterface.ts +276 -245
  83. package/web/interfaces/mqttInterface.ts +535 -475
  84. package/web/services/config/Config.ts +39 -18
  85. package/web/services/config/ConfigSocket.ts +0 -0
  86. package/web/services/state/State.ts +10 -0
  87. package/web/services/state/StateSocket.ts +4 -4
  88. package/web/services/utilities/Utilities.ts +44 -42
  89. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -52
  90. package/config copy.json +0 -300
  91. package/issue_template.md +0 -52
@@ -131,7 +131,7 @@ export class ValveMessage {
131
131
  // there is no circuit. The circuitry for the valve always exists although I am not sure
132
132
  // how the valve expansion is represented.
133
133
  valve.isActive = true;
134
- valve.isReturn = false;
134
+ valve.isReturn = false;
135
135
  valve.isIntake = false;
136
136
  valve.type = 0;
137
137
  // Allow users to name the valve whatever they want. *Touch apparently only allows the valve to be named the same
@@ -21,13 +21,19 @@ import { sys, ControllerType, Chlorinator } from "../../../Equipment";
21
21
  export class ChlorinatorStateMessage {
22
22
  public static process(msg: Inbound) {
23
23
  if (msg.protocol === Protocol.Chlorinator) {
24
- let chlor: Chlorinator;
25
- let cstate: ChlorinatorState;
26
-
24
+ // RKS: 03-29-22 A lot of water has gone under the bridge at this point and we know much more. First there are two types of messages. Those inbound
25
+ // messages that are being sent to the chlorinator as commands and responses from the chlorinator. We have also determined that the chlorinator does
26
+ // not actually support a RS485 address. So njsPC can declare as many ports as required to communicate with multiple chlorinators.
27
+ //
28
+ // So instead of matching the chlorinator up with a specific id we need to match on the port as well for the inbound message. OCPs always operate on
29
+ // portId = 0. If the portId is > 0 then it is considered an Aux port. At this point you can only control 1 chlorinator per port so in order to
30
+ // control more than one chlorinator there needs to be only one chlorinator on each port.
31
+ let chlor: Chlorinator = sys.chlorinators.findItemByPortId(msg.portId || 0);
32
+ if (typeof chlor === 'undefined' || chlor.isActive === false) return; // Bail out of here if we don't find an active chlorinator.
33
+ let cstate: ChlorinatorState = state.chlorinators.getItemById(chlor.id, true);
27
34
  if (msg.dest >= 1 && msg.dest <= 4) {
28
- // RKS: The dest for these message are 80+ in raw terms. The msg object translates these into 1-4 for the installed chlorinators. This message
29
- // is from the OCP to the chlorinator.
30
- cstate = state.chlorinators.getItemById(msg.dest, true);
35
+ // RKS: This message is from the OCP to the chlorinator. NOTE: We will not see these messages when the communication is coming from njsPC on any
36
+ // comms port. The processing for no comms is done in the Nixe control when the message is sent.
31
37
  if (typeof cstate.lastComm === 'undefined') cstate.lastComm = new Date(1970, 0, 1, 0, 0, 0, 0).getTime();
32
38
  // RG: I was getting some time deltas of 25-30s and bumped this up
33
39
  else if (cstate.lastComm + (30 * 1000) < new Date().getTime()) {
@@ -35,27 +41,24 @@ export class ChlorinatorStateMessage {
35
41
  cstate.status = 128;
36
42
  }
37
43
  chlor = sys.chlorinators.getItemById(msg.dest, true);
38
- chlor.address = msg.dest + 79;
44
+ chlor.address = msg.dest + 79; // Theoretically, this will always be 80.
39
45
  if (typeof chlor.isActive === 'undefined') cstate.isActive = chlor.isActive = true;
40
46
  }
41
47
  else {
42
- // Message from chlorinator
43
- cstate = state.chlorinators.getItemById(msg.dest + 1, true);
44
- chlor = sys.chlorinators.getItemById(msg.dest + 1, true);
48
+ // Response message from chlorinator. If the chlorinator is speaking to us then we need to hear it and clear the status when
49
+ // the previous status was no comms.
45
50
  cstate.lastComm = new Date().getTime();
46
51
  if (cstate.status === 128) cstate.status = 0;
47
52
  }
48
53
  cstate.body = chlor.body;
49
54
  switch (msg.action) {
50
- case 0: // request status (0): [16,2,80,0][0][98,16,3]
55
+ case 0: // Set control OCP->Chlorinator: [16,2,80,0][0][98,16,3]
51
56
  break;
52
- case 1: // response to request status: [16,2,0,1][0,0][19,16,3]
53
- {
54
- // let chlor = sys.chlorinators.getItemById(1, true);
55
- // chlor.isActive = true;
56
- break;
57
- }
58
- case 3: {
57
+ case 1: // Ack control Chlorinator->OCP response: [16,2,0,1][0,0][19,16,3]
58
+ // let chlor = sys.chlorinators.getItemById(1, true);
59
+ // chlor.isActive = true;
60
+ break;
61
+ case 3: // Chlorinator->OCP response for Get Model: [16,2,0,3][0,73,110,116,101,108,108,105,99,104,108,111,114,45,45,52,48][188,16,3]
59
62
  // RKS: 07-16-20 -- It appears that this message doesn't always come. It is very likely that the newer versions of IntelliChlor
60
63
  // do not respond to the 20 message. There have been multiple instances of this with recent versions of IntelliChlor. As a result,
61
64
  // don't overwrite the name should the user set it.
@@ -65,12 +68,11 @@ export class ChlorinatorStateMessage {
65
68
  // This is the model number of the chlorinator and the address is actually the second byte.
66
69
  let name = msg.extractPayloadString(1, 16);
67
70
  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());
71
+ if (typeof chlor.model === 'undefined') chlor.model = sys.board.valueMaps.chlorinatorModel.getValue(name.toLowerCase());
69
72
  cstate.isActive = chlor.isActive;
70
73
  state.emitEquipmentChanges();
71
74
  break;
72
- }
73
- case 17: {
75
+ case 17: // OCP->Chlorinator set output. [16,2,80,17][15][130,16,3]
74
76
  // If the chlorinator is no longer talking to us then clear the current output.
75
77
  if (cstate.status === 128) cstate.currentOutput = 0;
76
78
  cstate.targetOutput = msg.extractPayloadByte(0);
@@ -78,13 +80,16 @@ export class ChlorinatorStateMessage {
78
80
  // Some dumbass is trying to change our output. We need to set it back to 0.
79
81
  sys.board.chlorinator.setChlorAsync({ id: chlor.id, disabled: chlor.disabled, superChlor: false, superChlorHours: 0 });
80
82
  }
81
- else if (chlor.isDosing && cstate.targetOutput !== 100){
83
+ else if (chlor.isDosing && cstate.targetOutput !== 100) {
82
84
  sys.board.chlorinator.setChlorAsync({ id: chlor.id, isDosing: chlor.isDosing });
83
85
  }
84
86
  state.emitEquipmentChanges();
85
87
  break;
86
- }
87
- case 21: {
88
+ case 21: // Set output OCP->Chlorinator [16,2,80,21][0][119,16,3]
89
+ // RKS: 03-29-22 There is something absolutley wrong about the understanding of this message. It has to do with the fact that you could never set
90
+ // the output percentage to anything greater than 25.6% if this were the case. I assume this has something to do with the
91
+ // way IntelliChlor works. Values between 0 and 20% can be fractional. In this case the OCP would send a 21 instead of a 17 with
92
+ // the fractional value. Anything above that value will be sent in a 17 message.
88
93
  // Set Cell Output / 10
89
94
  // This packet is coming through differently on the IntelliConnect.
90
95
  // eg 13:42:31.304 VERBOSE Msg# 1531 Controller --> Salt cell: Set current output to 1.6 %: 16,2,80,21,0,119,16,3
@@ -95,34 +100,31 @@ export class ChlorinatorStateMessage {
95
100
  // Some dumbass is trying to change our output. We need to set it back to 0.
96
101
  sys.board.chlorinator.setChlorAsync({ id: chlor.id, disabled: chlor.disabled, superChlor: false, superChlorHours: 0 });
97
102
  }
98
- else if (chlor.isDosing && cstate.targetOutput !== 100){
103
+ else if (chlor.isDosing && cstate.targetOutput !== 100) {
99
104
  sys.board.chlorinator.setChlorAsync({ id: chlor.id, isDosing: chlor.isDosing });
100
105
  }
101
106
  state.emitEquipmentChanges();
102
107
  break;
103
- }
104
- case 18: {
108
+ case 18:
105
109
  // Response to Set Salt Output (17 & 20)
106
110
  // The most common failure with IntelliChlor is that the salt level stops reporting. Below should allow it to be fed from an alternate
107
111
  // source like REM.
108
- if(!chlor.ignoreSaltReading) cstate.saltLevel = msg.extractPayloadByte(0) * 50 || cstate.saltLevel || 0;
112
+ if (!chlor.ignoreSaltReading) cstate.saltLevel = msg.extractPayloadByte(0) * 50 || cstate.saltLevel || 0;
109
113
  cstate.status = (msg.extractPayloadByte(1) & 0x007F); // Strip off the high bit. The chlorinator does not actually report this.
110
114
  cstate.currentOutput = chlor.disabled ? 0 : cstate.setPointForCurrentBody;
111
115
  state.emitEquipmentChanges();
112
116
  break;
113
- }
114
- case 19: {
117
+ case 19:
115
118
  // This is an iChlor message with no payload. Perhaps simply a keep alive for the iChlor.
116
119
  // [16, 2, 80, 19][117, 16, 3]
117
120
  break;
118
- }
119
- case 20: {
120
- // Get version
121
- chlor.type = cstate.type = msg.extractPayloadByte(0);
121
+ case 20:
122
+ // OCP->Chlorinator Get model [16,2,80,20][0][118,16,3]
123
+ if (typeof chlor.type === 'undefined') chlor.type = msg.extractPayloadByte(0);
124
+ cstate.type = chlor.type;
122
125
  state.emitEquipmentChanges();
123
126
  break;
124
- }
125
- case 22: {
127
+ case 22: // Chlorinator->OCP this is actually an iChlor message and has no bearing on IntelliConnect.
126
128
  // temp and output as seen from IntelliConnect.
127
129
  // Issue #157 - https://github.com/tagyoureit/nodejs-poolController/issues/157
128
130
  // [10 02, 10, 16], [00, 0f, 49, 00,05, 10], [85, 10,03] = hex
@@ -141,57 +143,7 @@ export class ChlorinatorStateMessage {
141
143
  }
142
144
  state.emitEquipmentChanges();
143
145
  break;
144
- }
145
146
  }
146
147
  }
147
- // question: does IntelliCenter ever broadcast Chlorinator packet? Answer: Never. My guess is that this is actually
148
- // a configuration message rather than a status message. Also, IntelliCenter has a 204 extension status that contains
149
- // the current countdown timer for the superChlor in the last 2 bytes. Perhaps this is the equivalent.
150
- //else if (msg.protocol === Protocol.Broadcast) {
151
- // if (!state.isInitialized) return;
152
- // // sample packet
153
- // // [165,33,15,16,25,22],[1,10,128,29,132,0,73,110,116,101,108,108,105,99,104,108,111,114,45,45,52,48],[7,231]
154
- // let chlorId = 1;
155
- // let chlor = sys.chlorinators.getItemById(chlorId, true);
156
- // if (chlor.isVirtual) { return; } // shouldn't get here except for testing Chlor on *Touch system.
157
- // // installed = (aaaaaaa)1 so 1 = installed
158
- // chlor.isActive = (msg.extractPayloadByte(0) & 0x01) === 1;
159
- // if (chlor.isActive) {
160
- // // RSG : making the assumption here that the chlorinator will be tied to the pool in any system that is not a shared body
161
- // sys.equipment.maxBodies >= 1 || sys.equipment.shared === true ? chlor.body = 32 : chlor.body = 0;
162
- // // outputSpaPercent field is aaaaaaab (binary) where aaaaaaa = % and b===installed (0=no,1=yes)
163
- // // eg. a value of 41 is 00101001
164
- // // spa percent = 0010100(b) so 10100 = 20
165
- // if (!chlor.disabled) {
166
- // // RKS: We don't want these setpoints if our chem controller disabled the
167
- // // chlorinator. These should be 0 anyway.
168
- // chlor.spaSetpoint = msg.extractPayloadByte(0) >> 1;
169
- // chlor.poolSetpoint = msg.extractPayloadByte(1);
170
- // }
171
- // chlor.address = chlor.id + 79;
172
- // chlor.superChlor = msg.extractPayloadByte(5) > 0;
173
- // chlor.superChlorHours = msg.extractPayloadByte(5);
174
- // chlor.name = msg.extractPayloadString(6, 16);
175
- // let schlor = state.chlorinators.getItemById(chlorId, true);
176
- // // The most common failure with IntelliChlor is that the salt level stops reporting. Below should allow it to be fed from an alternate
177
- // // source like REM.
178
- // schlor.saltLevel = msg.extractPayloadByte(3) * 50 || schlor.saltLevel || 0;
179
- // schlor.status = msg.extractPayloadByte(4) & 0x007F; // Strip off the high bit. The chlorinator does not actually report this.;
180
- // schlor.lastComm = new Date().getTime(); // rely solely on "true" chlor messages for this?
181
- // schlor.poolSetpoint = chlor.poolSetpoint;
182
- // schlor.spaSetpoint = chlor.spaSetpoint;
183
- // schlor.superChlor = chlor.superChlor;
184
- // schlor.superChlorHours = chlor.superChlorHours;
185
- // schlor.name = chlor.name;
186
- // schlor.body = chlor.body;
187
- // if (state.temps.bodies.getItemById(1).isOn) schlor.targetOutput = chlor.disabled ? 0 : chlor.poolSetpoint;
188
- // else if (state.temps.bodies.getItemById(2).isOn) schlor.targetOutput = chlor.disabled ? 0 : chlor.spaSetpoint;
189
- // state.emitEquipmentChanges();
190
- // }
191
- // else {
192
- // sys.chlorinators.removeItemById(chlorId);
193
- // state.chlorinators.removeItemById(chlorId);
194
- // }
195
- //}
196
148
  }
197
149
  }
@@ -357,17 +357,41 @@ export class EquipmentStateMessage {
357
357
  tbody.setPoint = cbody.setPoint;
358
358
  tbody.name = cbody.name;
359
359
  tbody.circuit = cbody.circuit = 6;
360
- tbody.heatMode = cbody.heatMode = msg.extractPayloadByte(22) & 0x03;
360
+
361
+ //RKS: This heat mode did not include all the bits necessary for hybrid heaters
362
+ //tbody.heatMode = cbody.heatMode = msg.extractPayloadByte(22) & 0x03;
363
+ tbody.heatMode = cbody.heatMode = msg.extractPayloadByte(22) & 0x33;
361
364
  let heatStatus = sys.board.valueMaps.heatStatus.getValue('off');
362
365
  if (tbody.isOn) {
363
- //const heaterActive = (msg.extractPayloadByte(10) & 0x0C) === 12;
364
- //const solarActive = (msg.extractPayloadByte(10) & 0x30) === 48;
365
- const heaterActive = (msg.extractPayloadByte(10) & 0x04) === 0x04;
366
- const solarActive = (msg.extractPayloadByte(10) & 0x10) === 0x10;
367
- const cooling = solarActive && tbody.temp > tbody.setPoint;
368
- if (heaterActive) heatStatus = sys.board.valueMaps.heatStatus.getValue('heater');
369
- if (cooling) heatStatus = sys.board.valueMaps.heatStatus.getValue('cooling');
370
- else if (solarActive) heatStatus = sys.board.valueMaps.heatStatus.getValue('solar');
366
+ if (tbody.heaterOptions.hybrid > 0) {
367
+ // ETi When heating with
368
+ // Heatpump (1) = 12 H:true S:false C:false
369
+ // Gas (2) = 48 H:false S:true C:false
370
+ // Hybrid (3) = 48 H:true S:false C:false
371
+ // Dual (16) = 60 H:true S:true C:false
372
+ // What this means is that Touch actually treats the heat status as either heating with
373
+ // the primary heater for the body or the secondary. In the case of a hybrid heater
374
+ // the primary is a heatpump and the secondary is gas. In the case of gas + solar or gas + heatpump
375
+ // the gas heater is the primary and solar or heatpump is the secondary. So we need to dance a little bit
376
+ // here. We do this by checking the heater options.
377
+
378
+ // This can be the only heater solar cannot be installed with this.
379
+ let byte = msg.extractPayloadByte(10);
380
+ // Either the primary, secondary, or both is engaged.
381
+ if ((byte & 0x14) === 0x14) heatStatus = sys.board.valueMaps.heatStatus.getValue('dual');
382
+ else if (byte & 0x10) heatStatus = sys.board.valueMaps.heatStatus.getValue('heater');
383
+ else if (byte & 0x04) heatStatus = sys.board.valueMaps.heatStatus.getValue('hpheat');
384
+ }
385
+ else {
386
+ //const heaterActive = (msg.extractPayloadByte(10) & 0x0C) === 12;
387
+ //const solarActive = (msg.extractPayloadByte(10) & 0x30) === 48;
388
+ const heaterActive = (msg.extractPayloadByte(10) & 0x04) === 0x04;
389
+ const solarActive = (msg.extractPayloadByte(10) & 0x10) === 0x10;
390
+ const cooling = solarActive && tbody.temp > tbody.setPoint;
391
+ if (heaterActive) heatStatus = sys.board.valueMaps.heatStatus.getValue('heater');
392
+ if (cooling) heatStatus = sys.board.valueMaps.heatStatus.getValue('cooling');
393
+ else if (solarActive) heatStatus = sys.board.valueMaps.heatStatus.getValue('solar');
394
+ }
371
395
  }
372
396
  tbody.heatStatus = heatStatus;
373
397
  sys.board.schedules.syncScheduleHeatSourceAndSetpoint(cbody, tbody);
@@ -380,21 +404,32 @@ export class EquipmentStateMessage {
380
404
  tbody.temp = sys.equipment.shared ? state.temps.waterSensor1 : state.temps.waterSensor2;
381
405
  tbody.isOn = true;
382
406
  } else tbody.isOn = false;
383
- tbody.heatMode = cbody.heatMode = (msg.extractPayloadByte(22) & 0x0C) >> 2;
407
+ //RKS: This heat mode did not include all the bits necessary for hybrid heaters
408
+ //tbody.heatMode = cbody.heatMode = (msg.extractPayloadByte(22) & 0x0C) >> 2;
409
+ tbody.heatMode = cbody.heatMode = (msg.extractPayloadByte(22) & 0xCC) >> 2;
384
410
  tbody.setPoint = cbody.setPoint;
385
411
  tbody.name = cbody.name;
386
412
  tbody.circuit = cbody.circuit = 1;
387
413
  let heatStatus = sys.board.valueMaps.heatStatus.getValue('off');
388
414
  if (tbody.isOn) {
389
- //const heaterActive = (msg.extractPayloadByte(10) & 0x0C) === 12;
390
- //const solarActive = (msg.extractPayloadByte(10) & 0x30) === 48;
391
- const heaterActive = (msg.extractPayloadByte(10) & 0x08) === 0x08;
392
- const solarActive = (msg.extractPayloadByte(10) & 0x20) === 0x20;
393
-
394
- const cooling = solarActive && tbody.temp > tbody.setPoint;
395
- if (heaterActive) heatStatus = sys.board.valueMaps.heatStatus.getValue('heater');
396
- if (cooling) heatStatus = sys.board.valueMaps.heatStatus.getValue('cooling');
397
- else if (solarActive) heatStatus = sys.board.valueMaps.heatStatus.getValue('solar');
415
+ if (tbody.heaterOptions.hybrid > 0) {
416
+ // This can be the only heater solar cannot be installed with this.
417
+ let byte = msg.extractPayloadByte(10);
418
+ // Either the primary, secondary, or both is engaged.
419
+ if ((byte & 0x28) === 0x28) heatStatus = sys.board.valueMaps.heatStatus.getValue('dual');
420
+ else if (byte & 0x20) heatStatus = sys.board.valueMaps.heatStatus.getValue('heater');
421
+ else if (byte & 0x08) heatStatus = sys.board.valueMaps.heatStatus.getValue('hpheat');
422
+ }
423
+ else {
424
+ //const heaterActive = (msg.extractPayloadByte(10) & 0x0C) === 12;
425
+ //const solarActive = (msg.extractPayloadByte(10) & 0x30) === 48;
426
+ const heaterActive = (msg.extractPayloadByte(10) & 0x08) === 0x08;
427
+ const solarActive = (msg.extractPayloadByte(10) & 0x20) === 0x20;
428
+ const cooling = solarActive && tbody.temp > tbody.setPoint;
429
+ if (heaterActive) heatStatus = sys.board.valueMaps.heatStatus.getValue('heater');
430
+ if (cooling) heatStatus = sys.board.valueMaps.heatStatus.getValue('cooling');
431
+ else if (solarActive) heatStatus = sys.board.valueMaps.heatStatus.getValue('solar');
432
+ }
398
433
  }
399
434
  tbody.heatStatus = heatStatus;
400
435
  sys.board.schedules.syncScheduleHeatSourceAndSetpoint(cbody, tbody);
@@ -482,7 +517,8 @@ export class EquipmentStateMessage {
482
517
  // pool
483
518
  let tbody: BodyTempState = state.temps.bodies.getItemById(1, true);
484
519
  let cbody: Body = sys.bodies.getItemById(1);
485
- tbody.heatMode = cbody.heatMode = msg.extractPayloadByte(5) & 3;
520
+ // RKS: 02-26-22 - See communications doc for explanation of bits. This needs to support UltraTemp ETi heatpumps.
521
+ tbody.heatMode = cbody.heatMode = msg.extractPayloadByte(5) & 0x33;
486
522
  tbody.setPoint = cbody.setPoint = msg.extractPayloadByte(3);
487
523
  tbody.coolSetpoint = cbody.coolSetpoint = msg.extractPayloadByte(9);
488
524
  if (tbody.isOn) tbody.temp = state.temps.waterSensor1;
@@ -490,8 +526,8 @@ export class EquipmentStateMessage {
490
526
  if (cbody.isActive) {
491
527
  // spa
492
528
  tbody = state.temps.bodies.getItemById(2, true);
493
- tbody.heatMode = cbody.heatMode =
494
- (msg.extractPayloadByte(5) & 12) >> 2;
529
+ tbody.heatMode = cbody.heatMode = (msg.extractPayloadByte(5) & 0xCC) >> 2;
530
+ //tbody.heatMode = cbody.heatMode = (msg.extractPayloadByte(5) & 12) >> 2;
495
531
  tbody.setPoint = cbody.setPoint = msg.extractPayloadByte(4);
496
532
  if (tbody.isOn) tbody.temp = state.temps.waterSensor2 = msg.extractPayloadByte(1);
497
533
  }
@@ -1,87 +1,117 @@
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, Protocol } from "../Messages";
18
- import { state, BodyTempState, HeaterState } from "../../../State";
19
- import { sys, ControllerType, Heater } from "../../../Equipment";
20
-
21
- export class HeaterStateMessage {
22
- public static process(msg: Inbound) {
23
- if (msg.protocol === Protocol.Heater) {
24
- switch (msg.action) {
25
- case 112: // This is a message from a master controlling MasterTemp
26
- case 114: // This is a message from a master controlling UltraTemp
27
- msg.isProcessed = true;
28
- break;
29
- case 116:
30
- HeaterStateMessage.processMasterTempStatus(msg);
31
- break;
32
- case 115:
33
- HeaterStateMessage.processUltraTempStatus(msg);
34
- break;
35
- }
36
- }
37
- }
38
- public static processUltraTempStatus(msg: Inbound) {
39
- // RKS: 07-03-21 - We only know byte 2 at this point for Ultratemp for the 115 message we are processing here. The
40
- // byte description
41
- // ------------------------------------------------
42
- // 0 Unknown (always seems to be 160 for response)
43
- // 1 Unknown (always 1)
44
- // 2 Current heater status 0=off, 1=heat, 2=cool
45
- // 3-9 Unknown
46
-
47
- // 114 message - outbound response
48
- //[165, 0, 112, 16, 114, 10][144, 0, 0, 0, 0, 0, 0, 0, 0, 0][2, 49] // OCP to Heater
49
- // byte description
50
- // ------------------------------------------------
51
- // 0 Unknown (always seems to be 144 for request)
52
- // 1 Current heater status 0=off, 1=heat, 2=cool
53
- // 3 Believed to be ofset temp
54
- // 4-9 Unknown
55
-
56
- // byto 0: always seems to be 144 for outbound
57
- // byte 1: Sets heater mode to 0 = Off 1 = Heat 2 = Cool
58
- //[165, 0, 16, 112, 115, 10][160, 1, 0, 3, 0, 0, 0, 0, 0, 0][2, 70] // Heater Reply
59
- let heater: Heater = sys.heaters.getItemByAddress(msg.source);
60
- let sheater = state.heaters.getItemById(heater.id);
61
- let byte = msg.extractPayloadByte(2);
62
- sheater.isOn = byte >= 1;
63
- sheater.isCooling = byte === 2;
64
- sheater.commStatus = 0;
65
- state.equipment.messages.removeItemByCode(`heater:${heater.id}:comms`);
66
- msg.isProcessed = true;
67
- }
68
- public static processMasterTempStatus(msg: Inbound) {
69
- //[255, 0, 255][165, 0, 16, 112, 116, 23][67, 0, 0, 0, 0, 0, 0, 0, 68, 0, 0, 0, 10, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0][2, 66]
70
- // Byte 1 is the indicator to which setpoint it is heating to.
71
- // Byte 8 increments over time when the heater is on.
72
- // Byte 13 looks like the mode the heater is in for instance it is in cooldown mode.
73
- // 0 = Normal
74
- // 2 = ??????
75
- // 6 = Cooldown
76
- // Byte 14 looks like the cooldown delay in minutes.
77
- let heater: Heater = sys.heaters.getItemByAddress(msg.source);
78
- let sheater = state.heaters.getItemById(heater.id);
79
- let byte = msg.extractPayloadByte(1);
80
- sheater.isOn = byte >= 1;
81
- sheater.isCooling = false;
82
- sheater.commStatus = 0;
83
- state.equipment.messages.removeItemByCode(`heater:${heater.id}:comms`);
84
- msg.isProcessed = true;
85
- }
86
-
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, Protocol } from "../Messages";
18
+ import { state, BodyTempState, HeaterState } from "../../../State";
19
+ import { sys, ControllerType, Heater } from "../../../Equipment";
20
+
21
+ export class HeaterStateMessage {
22
+ public static process(msg: Inbound) {
23
+ if (msg.protocol === Protocol.Heater) {
24
+ switch (msg.action) {
25
+ case 112: // This is a message from a master controlling MasterTemp or UltraTemp ETi
26
+ break;
27
+ case 114: // This is a message from a master controlling UltraTemp
28
+ msg.isProcessed = true;
29
+ break;
30
+ case 113:
31
+ HeaterStateMessage.processHybridStatus(msg);
32
+ break;
33
+ case 116:
34
+ HeaterStateMessage.processMasterTempStatus(msg);
35
+ break;
36
+ case 115:
37
+ HeaterStateMessage.processUltraTempStatus(msg);
38
+ break;
39
+ }
40
+ }
41
+ }
42
+ public static processHeaterCommand(msg: Inbound) {
43
+ let heater: Heater = sys.heaters.getItemByAddress(msg.source);
44
+ // At this point there is no other configuration data for ET
45
+ if (sys.controllerType === ControllerType.EasyTouch) {
46
+ let htype = sys.board.valueMaps.heaterTypes.transform(heater.type);
47
+ switch (htype.name) {
48
+ case 'hybrid':
49
+ heater.economyTime = msg.extractPayloadByte(3);
50
+ heater.maxBoostTemp = msg.extractPayloadByte(4);
51
+ break;
52
+ }
53
+ }
54
+ }
55
+ public static processHybridStatus(msg: Inbound) {
56
+ //[165, 0, 16, 112, 113, 10][1, 1, 0, 0, 0, 0, 0, 0, 0, 0][1, 162]
57
+ let heater: Heater = sys.heaters.getItemByAddress(msg.source);
58
+ let sheater = state.heaters.getItemById(heater.id);
59
+ sheater.isOn = msg.extractPayloadByte(0) > 0;
60
+
61
+ let byte = msg.extractPayloadByte(2);
62
+ //sheater.isOn = byte >= 1;
63
+ //sheater.isCooling = byte === 2;
64
+ sheater.commStatus = 0;
65
+ state.equipment.messages.removeItemByCode(`heater:${heater.id}:comms`);
66
+ msg.isProcessed = true;
67
+ }
68
+ public static processUltraTempStatus(msg: Inbound) {
69
+ // RKS: 07-03-21 - We only know byte 2 at this point for Ultratemp for the 115 message we are processing here. The
70
+ // byte description
71
+ // ------------------------------------------------
72
+ // 0 Unknown (always seems to be 160 for response)
73
+ // 1 Unknown (always 1)
74
+ // 2 Current heater status 0=off, 1=heat, 2=cool
75
+ // 3-9 Unknown
76
+
77
+ // 114 message - outbound response
78
+ //[165, 0, 112, 16, 114, 10][144, 0, 0, 0, 0, 0, 0, 0, 0, 0][2, 49] // OCP to Heater
79
+ // byte description
80
+ // ------------------------------------------------
81
+ // 0 Unknown (always seems to be 144 for request)
82
+ // 1 Current heater status 0=off, 1=heat, 2=cool
83
+ // 3 Believed to be ofset temp
84
+ // 4-9 Unknown
85
+
86
+ // byto 0: always seems to be 144 for outbound
87
+ // byte 1: Sets heater mode to 0 = Off 1 = Heat 2 = Cool
88
+ //[165, 0, 16, 112, 115, 10][160, 1, 0, 3, 0, 0, 0, 0, 0, 0][2, 70] // Heater Reply
89
+ let heater: Heater = sys.heaters.getItemByAddress(msg.source);
90
+ let sheater = state.heaters.getItemById(heater.id);
91
+ let byte = msg.extractPayloadByte(2);
92
+ sheater.isOn = byte >= 1;
93
+ sheater.isCooling = byte === 2;
94
+ sheater.commStatus = 0;
95
+ state.equipment.messages.removeItemByCode(`heater:${heater.id}:comms`);
96
+ msg.isProcessed = true;
97
+ }
98
+ public static processMasterTempStatus(msg: Inbound) {
99
+ //[255, 0, 255][165, 0, 16, 112, 116, 23][67, 0, 0, 0, 0, 0, 0, 0, 68, 0, 0, 0, 10, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0][2, 66]
100
+ // Byte 1 is the indicator to which setpoint it is heating to.
101
+ // Byte 8 increments over time when the heater is on.
102
+ // Byte 13 looks like the mode the heater is in for instance it is in cooldown mode.
103
+ // 0 = Normal
104
+ // 2 = ??????
105
+ // 6 = Cooldown
106
+ // Byte 14 looks like the cooldown delay in minutes.
107
+ let heater: Heater = sys.heaters.getItemByAddress(msg.source);
108
+ let sheater = state.heaters.getItemById(heater.id);
109
+ let byte = msg.extractPayloadByte(1);
110
+ sheater.isOn = byte >= 1;
111
+ sheater.isCooling = false;
112
+ sheater.commStatus = 0;
113
+ state.equipment.messages.removeItemByCode(`heater:${heater.id}:comms`);
114
+ msg.isProcessed = true;
115
+ }
116
+
87
117
  }