nodejs-poolcontroller 7.6.1 → 8.0.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 (102) hide show
  1. package/.eslintrc.json +36 -45
  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 +242 -215
  8. package/Dockerfile +17 -17
  9. package/Gruntfile.js +40 -40
  10. package/LICENSE +661 -661
  11. package/README.md +195 -191
  12. package/anslq25/MessagesMock.ts +218 -0
  13. package/anslq25/boards/MockBoardFactory.ts +50 -0
  14. package/anslq25/boards/MockEasyTouchBoard.ts +696 -0
  15. package/anslq25/boards/MockSystemBoard.ts +217 -0
  16. package/anslq25/chemistry/MockChlorinator.ts +75 -0
  17. package/anslq25/pumps/MockPump.ts +84 -0
  18. package/app.ts +10 -14
  19. package/config/Config.ts +26 -8
  20. package/config/VersionCheck.ts +8 -4
  21. package/controller/Constants.ts +59 -25
  22. package/controller/Equipment.ts +2667 -2459
  23. package/controller/Errors.ts +181 -180
  24. package/controller/Lockouts.ts +534 -436
  25. package/controller/State.ts +596 -77
  26. package/controller/boards/AquaLinkBoard.ts +1003 -0
  27. package/controller/boards/BoardFactory.ts +53 -45
  28. package/controller/boards/EasyTouchBoard.ts +3079 -2653
  29. package/controller/boards/IntelliCenterBoard.ts +3821 -4230
  30. package/controller/boards/IntelliComBoard.ts +69 -63
  31. package/controller/boards/IntelliTouchBoard.ts +384 -241
  32. package/controller/boards/NixieBoard.ts +1871 -1675
  33. package/controller/boards/SunTouchBoard.ts +393 -0
  34. package/controller/boards/SystemBoard.ts +5244 -4697
  35. package/controller/comms/Comms.ts +905 -541
  36. package/controller/comms/ScreenLogic.ts +1663 -0
  37. package/controller/comms/messages/Messages.ts +382 -54
  38. package/controller/comms/messages/config/ChlorinatorMessage.ts +8 -4
  39. package/controller/comms/messages/config/CircuitGroupMessage.ts +5 -2
  40. package/controller/comms/messages/config/CircuitMessage.ts +82 -13
  41. package/controller/comms/messages/config/ConfigMessage.ts +3 -1
  42. package/controller/comms/messages/config/CoverMessage.ts +2 -1
  43. package/controller/comms/messages/config/CustomNameMessage.ts +31 -30
  44. package/controller/comms/messages/config/EquipmentMessage.ts +5 -1
  45. package/controller/comms/messages/config/ExternalMessage.ts +33 -3
  46. package/controller/comms/messages/config/FeatureMessage.ts +2 -1
  47. package/controller/comms/messages/config/GeneralMessage.ts +2 -1
  48. package/controller/comms/messages/config/HeaterMessage.ts +145 -11
  49. package/controller/comms/messages/config/IntellichemMessage.ts +2 -1
  50. package/controller/comms/messages/config/OptionsMessage.ts +16 -27
  51. package/controller/comms/messages/config/PumpMessage.ts +62 -47
  52. package/controller/comms/messages/config/RemoteMessage.ts +80 -13
  53. package/controller/comms/messages/config/ScheduleMessage.ts +390 -347
  54. package/controller/comms/messages/config/SecurityMessage.ts +2 -1
  55. package/controller/comms/messages/config/ValveMessage.ts +44 -27
  56. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +44 -91
  57. package/controller/comms/messages/status/EquipmentStateMessage.ts +139 -30
  58. package/controller/comms/messages/status/HeaterStateMessage.ts +135 -86
  59. package/controller/comms/messages/status/IntelliChemStateMessage.ts +448 -445
  60. package/controller/comms/messages/status/IntelliValveStateMessage.ts +36 -35
  61. package/controller/comms/messages/status/PumpStateMessage.ts +92 -2
  62. package/controller/comms/messages/status/VersionMessage.ts +2 -1
  63. package/controller/nixie/Nixie.ts +173 -162
  64. package/controller/nixie/NixieEquipment.ts +104 -103
  65. package/controller/nixie/bodies/Body.ts +120 -120
  66. package/controller/nixie/bodies/Filter.ts +135 -135
  67. package/controller/nixie/chemistry/ChemController.ts +2682 -2498
  68. package/controller/nixie/chemistry/ChemDoser.ts +806 -0
  69. package/controller/nixie/chemistry/Chlorinator.ts +367 -314
  70. package/controller/nixie/circuits/Circuit.ts +402 -248
  71. package/controller/nixie/heaters/Heater.ts +815 -649
  72. package/controller/nixie/pumps/Pump.ts +934 -661
  73. package/controller/nixie/schedules/Schedule.ts +319 -257
  74. package/controller/nixie/valves/Valve.ts +170 -170
  75. package/defaultConfig.json +346 -286
  76. package/logger/DataLogger.ts +448 -448
  77. package/logger/Logger.ts +38 -9
  78. package/package.json +60 -56
  79. package/tsconfig.json +25 -25
  80. package/web/Server.ts +275 -117
  81. package/web/bindings/aqualinkD.json +560 -0
  82. package/web/bindings/homeassistant.json +437 -0
  83. package/web/bindings/influxDB.json +1066 -1021
  84. package/web/bindings/mqtt.json +721 -654
  85. package/web/bindings/mqttAlt.json +746 -684
  86. package/web/bindings/rulesManager.json +54 -54
  87. package/web/bindings/smartThings-Hubitat.json +31 -31
  88. package/web/bindings/valveRelays.json +20 -20
  89. package/web/bindings/vera.json +25 -25
  90. package/web/interfaces/baseInterface.ts +188 -136
  91. package/web/interfaces/httpInterface.ts +148 -124
  92. package/web/interfaces/influxInterface.ts +283 -245
  93. package/web/interfaces/mqttInterface.ts +695 -475
  94. package/web/interfaces/ruleInterface.ts +87 -0
  95. package/web/services/config/Config.ts +177 -49
  96. package/web/services/config/ConfigSocket.ts +2 -1
  97. package/web/services/state/State.ts +154 -3
  98. package/web/services/state/StateSocket.ts +69 -18
  99. package/web/services/utilities/Utilities.ts +232 -42
  100. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -52
  101. package/config copy.json +0 -300
  102. package/issue_template.md +0 -52
@@ -1,5 +1,6 @@
1
1
  /* nodejs-poolController. An application to control pool equipment.
2
- Copyright (C) 2016, 2017, 2018, 2019, 2020. Russell Goldin, tagyoureit. russ.goldin@gmail.com
2
+ Copyright (C) 2016, 2017, 2018, 2019, 2020, 2021, 2022.
3
+ Russell Goldin, tagyoureit. russ.goldin@gmail.com
3
4
 
4
5
  This program is free software: you can redistribute it and/or modify
5
6
  it under the terms of the GNU Affero General Public License as
@@ -16,6 +17,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17
  */
17
18
  import { IntelliCenterBoard } from 'controller/boards/IntelliCenterBoard';
18
19
  import { EasyTouchBoard } from 'controller/boards/EasyTouchBoard';
20
+ import { IntelliTouchBoard } from 'controller/boards/IntelliTouchBoard';
21
+ import { SunTouchBoard } from "controller/boards/SunTouchBoard";
19
22
 
20
23
  import { logger } from '../../../../logger/Logger';
21
24
  import { ControllerType } from '../../../Constants';
@@ -59,23 +62,29 @@ export class EquipmentStateMessage {
59
62
  case 3:
60
63
  case 4:
61
64
  case 5:
65
+ logger.info(`Found IntelliTouch Controller`);
62
66
  sys.controllerType = ControllerType.IntelliTouch;
63
67
  model1 = msg.extractPayloadByte(28);
64
68
  model2 = msg.extractPayloadByte(9);
69
+ (sys.board as IntelliTouchBoard).initExpansionModules(model1, model2);
65
70
  break;
66
71
  case 11:
67
- sys.controllerType = ControllerType.IntelliCom;
72
+ logger.info(`Found SunTouch Controller`);
73
+ sys.controllerType = ControllerType.SunTouch;
74
+ (sys.board as SunTouchBoard).initExpansionModules(model1, model2);
68
75
  break;
69
76
  case 13:
70
77
  case 14:
78
+ logger.info(`Found EasyTouch Controller`);
71
79
  sys.controllerType = ControllerType.EasyTouch;
80
+ (sys.board as EasyTouchBoard).initExpansionModules(model1, model2);
72
81
  break;
73
82
  default:
74
83
  logger.error(`Unknown Touch Controller ${msg.extractPayloadByte(28)}:${msg.extractPayloadByte(27)}`);
75
84
  break;
76
85
  }
77
- let board = sys.board as EasyTouchBoard;
78
- board.initExpansionModules(model1, model2);
86
+ //let board = sys.board as EasyTouchBoard;
87
+ //board.initExpansionModules(model1, model2);
79
88
  }
80
89
  private static initController(msg: Inbound) {
81
90
  state.status = 1;
@@ -83,7 +92,16 @@ export class EquipmentStateMessage {
83
92
  const model2 = msg.extractPayloadByte(28);
84
93
  // RKS: 06-15-20 -- While this works for now the way we are detecting seems a bit dubious. First, the 2 status message
85
94
  // contains two model bytes. Right now the ones witness in the wild include 23 = fw1.023, 40 = fw1.040, 47 = fw1.047.
86
- if (model2 === 0 && (model1 === 23 || model1 >= 40)) {
95
+ // RKS: 07-21-22 -- Pentair is about to release fw1.232. Unfortunately, the byte mapping for this has changed such that
96
+ // the bytes [27,28] are [0,2] respectively. This looks like it might be in conflict with IntelliTouch but it is not. Below
97
+ // are the combinations of 27,28 we have seen for IntelliTouch
98
+ // [1,0] = i5+3
99
+ // [0,1] = i7+3
100
+ // [1,3] = i5+3s
101
+ // [1,4] = i9+3s
102
+ // [1,5] = i10+3d
103
+ if ((model2 === 0 && (model1 === 23 || model1 >= 40)) ||
104
+ (model2 === 2 && model1 == 0)) {
87
105
  state.equipment.controllerType = 'intellicenter';
88
106
  sys.board.modulesAcquired = false;
89
107
  sys.controllerType = ControllerType.IntelliCenter;
@@ -92,7 +110,6 @@ export class EquipmentStateMessage {
92
110
  }
93
111
  else {
94
112
  EquipmentStateMessage.initTouch(msg);
95
- logger.info(`Found Controller Board ${state.equipment.model}`);
96
113
  sys.board.needsConfigChanges = true;
97
114
  setTimeout(function () { sys.checkConfiguration(); }, 300);
98
115
  }
@@ -165,7 +182,12 @@ export class EquipmentStateMessage {
165
182
 
166
183
  // RSG - added 7/8/2020
167
184
  // Every 30 mins, check the timezone and adjust DST settings
168
- if (dt.getMinutes() % 30 === 0) sys.board.system.setTZ();
185
+ if (dt.getMinutes() % 30 === 0) {
186
+ sys.board.system.setTZ();
187
+ sys.board.schedules.updateSunriseSunsetAsync().then((updated: boolean)=>{
188
+ if (updated) {logger.debug(`Sunrise/sunset times updated on schedules.`);}
189
+ });
190
+ }
169
191
  // Check and update clock when it is off by >5 mins (just for a small buffer) and:
170
192
  // 1. IntelliCenter has "manual" time set (Internet will automatically adjust) and autoAdjustDST is enabled
171
193
  // 2. *Touch is "manual" (only option) and autoAdjustDST is enabled - (same as #1)
@@ -357,17 +379,43 @@ export class EquipmentStateMessage {
357
379
  tbody.setPoint = cbody.setPoint;
358
380
  tbody.name = cbody.name;
359
381
  tbody.circuit = cbody.circuit = 6;
360
- tbody.heatMode = cbody.heatMode = msg.extractPayloadByte(22) & 0x03;
382
+
383
+ //RKS: This heat mode did not include all the bits necessary for hybrid heaters
384
+ //tbody.heatMode = cbody.heatMode = msg.extractPayloadByte(22) & 0x03;
385
+ tbody.heatMode = cbody.heatMode = msg.extractPayloadByte(22) & 0x33;
361
386
  let heatStatus = sys.board.valueMaps.heatStatus.getValue('off');
362
387
  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');
388
+ if (tbody.heaterOptions.hybrid > 0) {
389
+ // ETi When heating with
390
+ // Heatpump (1) = 12 H:true S:false C:false
391
+ // Gas (2) = 48 H:false S:true C:false
392
+ // Hybrid (3) = 48 H:true S:false C:false
393
+ // Dual (16) = 60 H:true S:true C:false
394
+ // What this means is that Touch actually treats the heat status as either heating with
395
+ // the primary heater for the body or the secondary. In the case of a hybrid heater
396
+ // the primary is a heatpump and the secondary is gas. In the case of gas + solar or gas + heatpump
397
+ // the gas heater is the primary and solar or heatpump is the secondary. So we need to dance a little bit
398
+ // here. We do this by checking the heater options.
399
+ if (tbody.heatMode > 0) { // Turns out that ET sometimes reports the last heat status when off.
400
+ // This can be the only heater solar cannot be installed with this.
401
+ let byte = msg.extractPayloadByte(10);
402
+ // Either the primary, secondary, or both is engaged.
403
+ if ((byte & 0x14) === 0x14) heatStatus = sys.board.valueMaps.heatStatus.getValue('dual');
404
+ // else if ((byte & 0x0c) === 0x0c) heatStatus = sys.board.valueMaps.heatStatus.getValue('off'); // don't need since we test for heatMode>0
405
+ else if (byte & 0x10) heatStatus = sys.board.valueMaps.heatStatus.getValue('heater');
406
+ else if (byte & 0x04) heatStatus = sys.board.valueMaps.heatStatus.getValue('hpheat');
407
+ }
408
+ }
409
+ else {
410
+ //const heaterActive = (msg.extractPayloadByte(10) & 0x0C) === 12;
411
+ //const solarActive = (msg.extractPayloadByte(10) & 0x30) === 48;
412
+ const heaterActive = (msg.extractPayloadByte(10) & 0x04) === 0x04;
413
+ const solarActive = (msg.extractPayloadByte(10) & 0x10) === 0x10;
414
+ const cooling = solarActive && tbody.temp > tbody.setPoint;
415
+ if (heaterActive) heatStatus = sys.board.valueMaps.heatStatus.getValue('heater');
416
+ if (cooling) heatStatus = sys.board.valueMaps.heatStatus.getValue('cooling');
417
+ else if (solarActive) heatStatus = sys.board.valueMaps.heatStatus.getValue('solar');
418
+ }
371
419
  }
372
420
  tbody.heatStatus = heatStatus;
373
421
  sys.board.schedules.syncScheduleHeatSourceAndSetpoint(cbody, tbody);
@@ -380,21 +428,34 @@ export class EquipmentStateMessage {
380
428
  tbody.temp = sys.equipment.shared ? state.temps.waterSensor1 : state.temps.waterSensor2;
381
429
  tbody.isOn = true;
382
430
  } else tbody.isOn = false;
383
- tbody.heatMode = cbody.heatMode = (msg.extractPayloadByte(22) & 0x0C) >> 2;
431
+ //RKS: This heat mode did not include all the bits necessary for hybrid heaters
432
+ //tbody.heatMode = cbody.heatMode = (msg.extractPayloadByte(22) & 0x0C) >> 2;
433
+ tbody.heatMode = cbody.heatMode = (msg.extractPayloadByte(22) & 0xCC) >> 2;
384
434
  tbody.setPoint = cbody.setPoint;
385
435
  tbody.name = cbody.name;
386
436
  tbody.circuit = cbody.circuit = 1;
387
437
  let heatStatus = sys.board.valueMaps.heatStatus.getValue('off');
388
438
  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');
439
+ if (tbody.heaterOptions.hybrid > 0) {
440
+ // This can be the only heater solar cannot be installed with this.
441
+ if (tbody.heatMode > 0) {
442
+ let byte = msg.extractPayloadByte(10);
443
+ // Either the primary, secondary, or both is engaged.
444
+ if ((byte & 0x28) === 0x28) heatStatus = sys.board.valueMaps.heatStatus.getValue('dual');
445
+ else if (byte & 0x20) heatStatus = sys.board.valueMaps.heatStatus.getValue('heater');
446
+ else if (byte & 0x08) heatStatus = sys.board.valueMaps.heatStatus.getValue('hpheat');
447
+ }
448
+ }
449
+ else {
450
+ //const heaterActive = (msg.extractPayloadByte(10) & 0x0C) === 12;
451
+ //const solarActive = (msg.extractPayloadByte(10) & 0x30) === 48;
452
+ const heaterActive = (msg.extractPayloadByte(10) & 0x08) === 0x08;
453
+ const solarActive = (msg.extractPayloadByte(10) & 0x20) === 0x20;
454
+ const cooling = solarActive && tbody.temp > tbody.setPoint;
455
+ if (heaterActive) heatStatus = sys.board.valueMaps.heatStatus.getValue('heater');
456
+ if (cooling) heatStatus = sys.board.valueMaps.heatStatus.getValue('cooling');
457
+ else if (solarActive) heatStatus = sys.board.valueMaps.heatStatus.getValue('solar');
458
+ }
398
459
  }
399
460
  tbody.heatStatus = heatStatus;
400
461
  sys.board.schedules.syncScheduleHeatSourceAndSetpoint(cbody, tbody);
@@ -418,6 +479,18 @@ export class EquipmentStateMessage {
418
479
  sys.board.heaters.syncHeaterStates();
419
480
  break;
420
481
  }
482
+ case ControllerType.SunTouch:
483
+ EquipmentStateMessage.processSunTouchCircuits(msg);
484
+ sys.board.circuits.syncCircuitRelayStates();
485
+ sys.board.features.syncGroupStates();
486
+ sys.board.circuits.syncVirtualCircuitStates();
487
+ sys.board.valves.syncValveStates();
488
+ sys.board.filters.syncFilterStates();
489
+ state.emitControllerChange();
490
+ state.emitEquipmentChanges();
491
+ sys.board.heaters.syncHeaterStates();
492
+ sys.board.schedules.syncScheduleStates();
493
+ break;
421
494
  case ControllerType.EasyTouch:
422
495
  case ControllerType.IntelliCom:
423
496
  case ControllerType.IntelliTouch:
@@ -478,11 +551,14 @@ export class EquipmentStateMessage {
478
551
  state.temps.waterSensor1 = msg.extractPayloadByte(0);
479
552
  state.temps.air = msg.extractPayloadByte(2);
480
553
  let solar: Heater = sys.heaters.getItemById(2);
481
- if (solar.isActive) state.temps.solar = msg.extractPayloadByte(8);
554
+ // RKS: 05-18-22 - This is not correct the solar temp is not stored on this message. It is always 0
555
+ // on an intelliTouch system with solar.
556
+ //if (solar.isActive) state.temps.solar = msg.extractPayloadByte(8);
482
557
  // pool
483
558
  let tbody: BodyTempState = state.temps.bodies.getItemById(1, true);
484
559
  let cbody: Body = sys.bodies.getItemById(1);
485
- tbody.heatMode = cbody.heatMode = msg.extractPayloadByte(5) & 3;
560
+ // RKS: 02-26-22 - See communications doc for explanation of bits. This needs to support UltraTemp ETi heatpumps.
561
+ tbody.heatMode = cbody.heatMode = msg.extractPayloadByte(5) & 0x33;
486
562
  tbody.setPoint = cbody.setPoint = msg.extractPayloadByte(3);
487
563
  tbody.coolSetpoint = cbody.coolSetpoint = msg.extractPayloadByte(9);
488
564
  if (tbody.isOn) tbody.temp = state.temps.waterSensor1;
@@ -490,8 +566,8 @@ export class EquipmentStateMessage {
490
566
  if (cbody.isActive) {
491
567
  // spa
492
568
  tbody = state.temps.bodies.getItemById(2, true);
493
- tbody.heatMode = cbody.heatMode =
494
- (msg.extractPayloadByte(5) & 12) >> 2;
569
+ tbody.heatMode = cbody.heatMode = (msg.extractPayloadByte(5) & 0xCC) >> 2;
570
+ //tbody.heatMode = cbody.heatMode = (msg.extractPayloadByte(5) & 12) >> 2;
495
571
  tbody.setPoint = cbody.setPoint = msg.extractPayloadByte(4);
496
572
  if (tbody.isOn) tbody.temp = state.temps.waterSensor2 = msg.extractPayloadByte(1);
497
573
  }
@@ -609,6 +685,39 @@ export class EquipmentStateMessage {
609
685
  }
610
686
  msg.isProcessed = true;
611
687
  }
688
+ private static processSunTouchCircuits(msg: Inbound) {
689
+ // SunTouch has really twisted bit mapping for its
690
+ // circuit states. Features are intertwined within the
691
+ // features.
692
+ let byte = msg.extractPayloadByte(2);
693
+ for (let i = 0; i < 8; i++) {
694
+ let id = i === 4 ? 7 : i > 5 ? i + 2 : i + 1;
695
+ let circ = sys.circuits.getInterfaceById(id, false, { isActive: false });
696
+ if (circ.isActive) {
697
+ let isOn = ((1 << i) & byte) > 0;
698
+ let cstate = state.circuits.getInterfaceById(id, circ.isActive);
699
+ if (isOn !== cstate.isOn) {
700
+ sys.board.circuits.setEndTime(circ, cstate, isOn);
701
+ cstate.isOn = isOn;
702
+ }
703
+ }
704
+ }
705
+ byte = msg.extractPayloadByte(3);
706
+ {
707
+ let circ = sys.circuits.getInterfaceById(10, false, { isActive: false });
708
+ if (circ.isActive) {
709
+ let isOn = (byte & 1) > 0;
710
+ let cstate = state.circuits.getInterfaceById(circ.id, circ.isActive);
711
+ if (isOn !== cstate.isOn) {
712
+ sys.board.circuits.setEndTime(circ, cstate, isOn);
713
+ cstate.isOn = isOn;
714
+ }
715
+ }
716
+ }
717
+ state.emitEquipmentChanges();
718
+ msg.isProcessed = true;
719
+ }
720
+
612
721
  private static processTouchCircuits(msg: Inbound) {
613
722
  let circuitId = 1;
614
723
  let maxCircuitId = sys.board.equipmentIds.features.end;
@@ -1,87 +1,136 @@
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, 2021, 2022.
3
+ Russell Goldin, tagyoureit. russ.goldin@gmail.com
4
+
5
+ This program is free software: you can redistribute it and/or modify
6
+ it under the terms of the GNU Affero General Public License as
7
+ published by the Free Software Foundation, either version 3 of the
8
+ License, or (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU Affero General Public License for more details.
14
+
15
+ You should have received a copy of the GNU Affero General Public License
16
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+ */
18
+ import { Inbound, Protocol } from "../Messages";
19
+ import { state, BodyTempState, HeaterState } from "../../../State";
20
+ import { sys, ControllerType, Heater } from "../../../Equipment";
21
+
22
+ export class HeaterStateMessage {
23
+ public static process(msg: Inbound) {
24
+ if (msg.protocol === Protocol.Heater) {
25
+ switch (msg.action) {
26
+ case 112: // This is a message from a master controlling MasterTemp or UltraTemp ETi
27
+ break;
28
+ case 114: // This is a message from a master controlling UltraTemp
29
+ msg.isProcessed = true;
30
+ break;
31
+ case 113:
32
+ HeaterStateMessage.processHybridStatus(msg);
33
+ break;
34
+ case 116:
35
+ HeaterStateMessage.processMasterTempStatus(msg);
36
+ break;
37
+ case 115:
38
+ HeaterStateMessage.processUltraTempStatus(msg);
39
+ break;
40
+ }
41
+ }
42
+ }
43
+ public static processHeaterCommand(msg: Inbound) {
44
+ let heater: Heater = sys.heaters.getItemByAddress(msg.source);
45
+ // At this point there is no other configuration data for ET
46
+ if (sys.controllerType === ControllerType.EasyTouch) {
47
+ let htype = sys.board.valueMaps.heaterTypes.transform(heater.type);
48
+ switch (htype.name) {
49
+ case 'hybrid':
50
+ heater.economyTime = msg.extractPayloadByte(3);
51
+ heater.maxBoostTemp = msg.extractPayloadByte(4);
52
+ break;
53
+ }
54
+ }
55
+ }
56
+ public static processHybridStatus(msg: Inbound) {
57
+ //[165, 0, 16, 112, 113, 10][1, 1, 0, 0, 0, 0, 0, 0, 0, 0][1, 162]
58
+ let heater: Heater = sys.heaters.getItemByAddress(msg.source);
59
+ let sheater = state.heaters.getItemById(heater.id);
60
+ sheater.isOn = msg.extractPayloadByte(0) > 0;
61
+ if (heater.master > 0) {
62
+ let sbody = sheater.bodyId > 0 ? state.temps.bodies.getItemById(sheater.bodyId) : undefined;
63
+ if (typeof sbody !== 'undefined') {
64
+ switch (msg.extractPayloadByte(1)) {
65
+ case 1:
66
+ sbody.heatStatus = sys.board.valueMaps.heatStatus.getValue('hpheat');
67
+ break;
68
+ case 2:
69
+ sbody.heatStatus = sys.board.valueMaps.heatStatus.getValue('heater');
70
+ break;
71
+ case 3:
72
+ sbody.heatStatus = sys.board.valueMaps.heatStatus.getValue('dual');
73
+ break;
74
+ case 4:
75
+ sbody.heatStatus = sys.board.valueMaps.heatStatus.getValue('dual');
76
+ break;
77
+ default:
78
+ sbody.heatStatus = sys.board.valueMaps.heatStatus.getValue('off');
79
+ break;
80
+ }
81
+ }
82
+ }
83
+ sheater.commStatus = 0;
84
+ state.equipment.messages.removeItemByCode(`heater:${heater.id}:comms`);
85
+ msg.isProcessed = true;
86
+ }
87
+ public static processUltraTempStatus(msg: Inbound) {
88
+ // RKS: 07-03-21 - We only know byte 2 at this point for Ultratemp for the 115 message we are processing here. The
89
+ // byte description
90
+ // ------------------------------------------------
91
+ // 0 Unknown (always seems to be 160 for response)
92
+ // 1 Unknown (always 1)
93
+ // 2 Current heater status 0=off, 1=heat, 2=cool
94
+ // 3-9 Unknown
95
+
96
+ // 114 message - outbound response
97
+ //[165, 0, 112, 16, 114, 10][144, 0, 0, 0, 0, 0, 0, 0, 0, 0][2, 49] // OCP to Heater
98
+ // byte description
99
+ // ------------------------------------------------
100
+ // 0 Unknown (always seems to be 144 for request)
101
+ // 1 Current heater status 0=off, 1=heat, 2=cool
102
+ // 3 Believed to be ofset temp
103
+ // 4-9 Unknown
104
+
105
+ // byto 0: always seems to be 144 for outbound
106
+ // byte 1: Sets heater mode to 0 = Off 1 = Heat 2 = Cool
107
+ //[165, 0, 16, 112, 115, 10][160, 1, 0, 3, 0, 0, 0, 0, 0, 0][2, 70] // Heater Reply
108
+ let heater: Heater = sys.heaters.getItemByAddress(msg.source);
109
+ let sheater = state.heaters.getItemById(heater.id);
110
+ let byte = msg.extractPayloadByte(2);
111
+ sheater.isOn = byte >= 1;
112
+ sheater.isCooling = byte === 2;
113
+ sheater.commStatus = 0;
114
+ state.equipment.messages.removeItemByCode(`heater:${heater.id}:comms`);
115
+ msg.isProcessed = true;
116
+ }
117
+ public static processMasterTempStatus(msg: Inbound) {
118
+ //[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]
119
+ // Byte 1 is the indicator to which setpoint it is heating to.
120
+ // Byte 8 increments over time when the heater is on.
121
+ // Byte 13 looks like the mode the heater is in for instance it is in cooldown mode.
122
+ // 0 = Normal
123
+ // 2 = ??????
124
+ // 6 = Cooldown
125
+ // Byte 14 looks like the cooldown delay in minutes.
126
+ let heater: Heater = sys.heaters.getItemByAddress(msg.source);
127
+ let sheater = state.heaters.getItemById(heater.id);
128
+ let byte = msg.extractPayloadByte(1);
129
+ sheater.isOn = byte >= 1;
130
+ sheater.isCooling = false;
131
+ sheater.commStatus = 0;
132
+ state.equipment.messages.removeItemByCode(`heater:${heater.id}:comms`);
133
+ msg.isProcessed = true;
134
+ }
135
+
87
136
  }