nodejs-poolcontroller 7.7.0 → 8.0.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 (82) hide show
  1. package/.eslintrc.json +26 -35
  2. package/Changelog +22 -0
  3. package/README.md +7 -3
  4. package/anslq25/MessagesMock.ts +218 -0
  5. package/anslq25/boards/MockBoardFactory.ts +50 -0
  6. package/anslq25/boards/MockEasyTouchBoard.ts +696 -0
  7. package/anslq25/boards/MockSystemBoard.ts +217 -0
  8. package/anslq25/chemistry/MockChlorinator.ts +75 -0
  9. package/anslq25/pumps/MockPump.ts +84 -0
  10. package/app.ts +10 -14
  11. package/config/Config.ts +13 -9
  12. package/config/VersionCheck.ts +6 -2
  13. package/controller/Constants.ts +58 -25
  14. package/controller/Equipment.ts +225 -41
  15. package/controller/Errors.ts +2 -1
  16. package/controller/Lockouts.ts +34 -2
  17. package/controller/State.ts +491 -48
  18. package/controller/boards/AquaLinkBoard.ts +6 -3
  19. package/controller/boards/BoardFactory.ts +5 -1
  20. package/controller/boards/EasyTouchBoard.ts +1971 -1751
  21. package/controller/boards/IntelliCenterBoard.ts +1311 -1688
  22. package/controller/boards/IntelliComBoard.ts +7 -1
  23. package/controller/boards/IntelliTouchBoard.ts +153 -42
  24. package/controller/boards/NixieBoard.ts +209 -66
  25. package/controller/boards/SunTouchBoard.ts +393 -0
  26. package/controller/boards/SystemBoard.ts +1862 -1543
  27. package/controller/comms/Comms.ts +539 -138
  28. package/controller/comms/ScreenLogic.ts +1663 -0
  29. package/controller/comms/messages/Messages.ts +242 -60
  30. package/controller/comms/messages/config/ChlorinatorMessage.ts +4 -3
  31. package/controller/comms/messages/config/CircuitGroupMessage.ts +5 -2
  32. package/controller/comms/messages/config/CircuitMessage.ts +81 -13
  33. package/controller/comms/messages/config/ConfigMessage.ts +3 -1
  34. package/controller/comms/messages/config/CoverMessage.ts +2 -1
  35. package/controller/comms/messages/config/CustomNameMessage.ts +2 -1
  36. package/controller/comms/messages/config/EquipmentMessage.ts +5 -1
  37. package/controller/comms/messages/config/ExternalMessage.ts +33 -3
  38. package/controller/comms/messages/config/FeatureMessage.ts +2 -1
  39. package/controller/comms/messages/config/GeneralMessage.ts +2 -1
  40. package/controller/comms/messages/config/HeaterMessage.ts +3 -1
  41. package/controller/comms/messages/config/IntellichemMessage.ts +2 -1
  42. package/controller/comms/messages/config/OptionsMessage.ts +12 -6
  43. package/controller/comms/messages/config/PumpMessage.ts +9 -12
  44. package/controller/comms/messages/config/RemoteMessage.ts +80 -13
  45. package/controller/comms/messages/config/ScheduleMessage.ts +43 -3
  46. package/controller/comms/messages/config/SecurityMessage.ts +2 -1
  47. package/controller/comms/messages/config/ValveMessage.ts +43 -26
  48. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +8 -7
  49. package/controller/comms/messages/status/EquipmentStateMessage.ts +93 -20
  50. package/controller/comms/messages/status/HeaterStateMessage.ts +24 -5
  51. package/controller/comms/messages/status/IntelliChemStateMessage.ts +7 -4
  52. package/controller/comms/messages/status/IntelliValveStateMessage.ts +2 -1
  53. package/controller/comms/messages/status/PumpStateMessage.ts +72 -4
  54. package/controller/comms/messages/status/VersionMessage.ts +2 -1
  55. package/controller/nixie/Nixie.ts +15 -4
  56. package/controller/nixie/NixieEquipment.ts +1 -0
  57. package/controller/nixie/chemistry/ChemController.ts +300 -129
  58. package/controller/nixie/chemistry/ChemDoser.ts +806 -0
  59. package/controller/nixie/chemistry/Chlorinator.ts +133 -129
  60. package/controller/nixie/circuits/Circuit.ts +171 -30
  61. package/controller/nixie/heaters/Heater.ts +337 -173
  62. package/controller/nixie/pumps/Pump.ts +264 -236
  63. package/controller/nixie/schedules/Schedule.ts +9 -3
  64. package/defaultConfig.json +46 -5
  65. package/logger/Logger.ts +38 -9
  66. package/package.json +13 -9
  67. package/web/Server.ts +235 -122
  68. package/web/bindings/aqualinkD.json +114 -59
  69. package/web/bindings/homeassistant.json +437 -0
  70. package/web/bindings/influxDB.json +15 -0
  71. package/web/bindings/mqtt.json +28 -9
  72. package/web/bindings/mqttAlt.json +15 -0
  73. package/web/interfaces/baseInterface.ts +58 -7
  74. package/web/interfaces/httpInterface.ts +5 -2
  75. package/web/interfaces/influxInterface.ts +9 -2
  76. package/web/interfaces/mqttInterface.ts +234 -74
  77. package/web/interfaces/ruleInterface.ts +87 -0
  78. package/web/services/config/Config.ts +140 -33
  79. package/web/services/config/ConfigSocket.ts +2 -1
  80. package/web/services/state/State.ts +144 -3
  81. package/web/services/state/StateSocket.ts +65 -14
  82. package/web/services/utilities/Utilities.ts +189 -1
@@ -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
@@ -100,9 +101,48 @@ export class ScheduleMessage {
100
101
  break;
101
102
 
102
103
  }
103
- else if (sys.controllerType !== ControllerType.Unknown)
104
+ else if (sys.controllerType !== ControllerType.Unknown && sys.controllerType !== ControllerType.SunTouch)
104
105
  ScheduleMessage.processScheduleDetails(msg);
105
106
  }
107
+ public static processSunTouch(msg: Inbound): void {
108
+ //[255, 0, 255][165, 1, 15, 16, 30, 16][0, 0, 0, 0, 2, 0, 1, 224, 1, 239, 6, 1, 1, 224, 3, 132][4, 53]
109
+ // Bytes 0-3 contain no data.
110
+ for (let i = 0; i < 2; i++) {
111
+ let schedId = i + 1;
112
+ let pos = (i * 6) + 4;
113
+ let cid = msg.extractPayloadByte(pos);
114
+ if (cid === 5) cid = 7;
115
+ else if (cid > 6) cid = cid + 1;
116
+ let start = (msg.extractPayloadByte(pos + 2) * 256) + msg.extractPayloadByte(pos + 3);
117
+ let end = (msg.extractPayloadByte(pos + 4) * 256) + msg.extractPayloadByte(pos + 5);
118
+ let circ = cid > 0 && start < end ? sys.circuits.getInterfaceById(cid, false) : undefined;
119
+ if (typeof circ !== 'undefined' && circ.isActive) {
120
+ let sched = sys.schedules.getItemById(schedId, true);
121
+ let ssched = state.schedules.getItemById(schedId, true);
122
+ sched.master = 0;
123
+ ssched.circuit = sched.circuit = circ.id;
124
+ ssched.scheduleDays = sched.scheduleDays = 127; // SunTouch does not allow you to set the days.
125
+ ssched.startTime = sched.startTime = start;
126
+ ssched.endTime = sched.endTime = end;
127
+ ssched.startTimeType = sched.startTimeType = 0;
128
+ ssched.endTimeType = sched.endTimeType = 0;
129
+ ssched.isActive = sched.isActive = true;
130
+ ssched.scheduleType = sched.scheduleType = sys.board.valueMaps.scheduleTypes.getValue('repeat');
131
+ if (sys.circuits.getItemById(circ.id).hasHeatSource && typeof sched.heatSource === 'undefined') ssched.heatSource = sched.heatSource = sys.board.valueMaps.heatSources.getValue('nochange');
132
+ if (typeof sched.heatSetpoint === 'undefined') sched.heatSetpoint = 78;
133
+ ssched.emitEquipmentChange();
134
+ }
135
+ else {
136
+ let ssched = state.schedules.find(elem => elem.id === schedId);
137
+ if (typeof ssched !== 'undefined') {
138
+ ssched.isActive = false;
139
+ ssched.emitEquipmentChange();
140
+ state.schedules.removeItemById(schedId);
141
+ }
142
+ sys.schedules.removeItemById(schedId);
143
+ }
144
+ }
145
+ }
106
146
  public static processScheduleDetails(msg: Inbound) {
107
147
  // Sample packet
108
148
  // [165,33,15,16,17,7][6,12,25,0,6,30,0][1,76]
@@ -119,7 +159,7 @@ export class ScheduleMessage {
119
159
  eggTimer.isActive = eggTimerActive;
120
160
  const circuit = sys.circuits.getInterfaceById(circuitId);
121
161
  circuit.eggTimer = eggTimer.runTime;
122
- circuit.eggTimer === 720;
162
+ //circuit.eggTimer === 720;
123
163
  circuit.dontStop = circuit.eggTimer === 1620;
124
164
  // When eggTimers are found go back and check existing schedules to see if a runOnce schedule already exists.
125
165
  // It is possible that the runOnce schedule will be discovered before the eggTimer so we need to adjust the endTime
@@ -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
@@ -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
@@ -56,9 +57,10 @@ export class ValveMessage {
56
57
  case ControllerType.IntelliCom:
57
58
  case ControllerType.EasyTouch:
58
59
  case ControllerType.IntelliTouch:
60
+ case ControllerType.SunTouch:
59
61
  switch (msg.action) {
60
62
  case 29:
61
- ValveMessage.process_ValveAssignment_IT(msg);
63
+ sys.controllerType === ControllerType.SunTouch ? ValveMessage.process_ValveAssignment_ST(msg) : ValveMessage.process_ValveAssignment_IT(msg);
62
64
  break;
63
65
  case 35:
64
66
  ValveMessage.process_ValveOptions_IT(msg);
@@ -73,19 +75,38 @@ export class ValveMessage {
73
75
  sys.general.options.pumpDelay = msg.extractPayloadByte(0) >> 7 === 1;
74
76
  msg.isProcessed = true;
75
77
  }
78
+ private static process_ValveAssignment_ST(msg: Inbound) {
79
+ // SunTouch example
80
+ //[165,1,15,16,29,24][2,0,0,0,20,255,255,1,2,3,4,1,72,0,0,0,3,0,0,63,4,0,0,0][3,167]
81
+ let vA = sys.valves.getItemById(1, true);
82
+ let vB = sys.valves.getItemById(2, true);
83
+ let vC = sys.valves.getItemById(3, true);
84
+ if (sys.equipment.shared && !sys.equipment.single) {
85
+ vA.name = 'Intake';
86
+ vB.circuit = vA.circuit = sys.board.valueMaps.virtualCircuits.encode('poolspa');
87
+ vB.name = 'Return';
88
+ }
89
+ else {
90
+ vA.name = 'Valve A';
91
+ vB.name = 'Valve B';
92
+ vA.circuit = msg.extractPayloadByte(1);
93
+ vB.circuit = msg.extractPayloadByte(2);
94
+ }
95
+ vC.circuit = msg.extractPayloadByte(4);
96
+ vC.name = 'Valve C'
97
+ }
76
98
  private static process_ValveAssignment_IT(msg: Inbound) {
77
99
  // sample packet
78
- // 165,33,16,34,157,6,0,0,1,255,255,255,4,153 [set]
100
+ // [165,33,16,34,157,6],[0,0,1,255,255,255],[4,153] [set]
101
+ // [165,33,16,34,157,6],[0,0,7,255,255,255],[4,159]] [set]
79
102
  // [165,33,15,16,29,24],[2,0,0,0,128,1,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[4,154] [get]
80
- // [[][255,0,255][165,33,16,34,157,6][0,0,7,255,255,255][4,159]] [set]
81
103
  // what is payload[0]?
82
104
  for (let ndx = 4, id = 1; id <= sys.equipment.maxValves; ndx++) {
83
- let valve: Valve = sys.valves.getItemById(id);
84
105
  if (id === 3) {
85
- if (sys.equipment.shared) {
86
- valve = sys.valves.getItemById(id, true);
106
+ if (sys.equipment.shared && !sys.equipment.single) {
107
+ let valve: Valve = sys.valves.getItemById(id);
87
108
  valve.circuit = 6; // pool/spa -- fix
88
- valve.name = ValveMessage.getName(id, valve.circuit);
109
+ valve.name = 'Intake';
89
110
  valve.isIntake = true;
90
111
  valve.isReturn = false;
91
112
  valve.isActive = true;
@@ -99,13 +120,12 @@ export class ValveMessage {
99
120
  sys.valves.removeItemById(id);
100
121
  state.valves.removeItemById(id);
101
122
  }
102
- id++;
103
123
  }
104
124
  else if (id === 4) {
105
- if (sys.equipment.shared) {
106
- valve = sys.valves.getItemById(id, true);
125
+ if (sys.equipment.shared && !sys.equipment.single) {
126
+ let valve: Valve = sys.valves.getItemById(id);
107
127
  valve.circuit = 6; // pool/spa -- fix
108
- valve.name = ValveMessage.getName(id, valve.circuit);
128
+ valve.name = 'Return';
109
129
  valve.isIntake = false;
110
130
  valve.isReturn = true;
111
131
  valve.isActive = true;
@@ -119,13 +139,12 @@ export class ValveMessage {
119
139
  sys.valves.removeItemById(id);
120
140
  state.valves.removeItemById(id);
121
141
  }
122
- id++;
123
142
  }
124
143
  else {
125
- valve = sys.valves.getItemById(id, true);
144
+ let valve: Valve = sys.valves.getItemById(id);
126
145
  let circ = msg.extractPayloadByte(ndx);
127
146
  valve.circuit = circ > 0 && circ < 255 ? circ : 0;
128
- valve.circuit = msg.extractPayloadByte(ndx);
147
+ //valve.circuit = msg.extractPayloadByte(ndx);
129
148
  //valve.isActive = valve.circuit > 0 && valve.circuit < 255;
130
149
  // RKS: 04-14-21 -- Valves should always be active but shown with no assignment when
131
150
  // there is no circuit. The circuitry for the valve always exists although I am not sure
@@ -134,25 +153,23 @@ export class ValveMessage {
134
153
  valve.isReturn = false;
135
154
  valve.isIntake = false;
136
155
  valve.type = 0;
156
+ valve.master = 0;
137
157
  // Allow users to name the valve whatever they want. *Touch apparently only allows the valve to be named the same
138
158
  // as the circuit but this should be fine if we allow the user to edit it.
139
159
  valve.name = (typeof valve.name === 'undefined') ? ValveMessage.getName(id, valve.circuit) : valve.name;
140
160
  let svalve = state.valves.getItemById(id, true);
141
161
  svalve.name = valve.name;
142
- svalve.type = valve.type;
143
- valve.master = 0;
144
- }
145
- if (!valve.isActive) {
146
- sys.valves.removeItemById(id);
147
- state.valves.removeItemById(id);
148
- }
149
- else {
150
- valve.master = 0;
151
- // valve.isVirtual = false;
152
- valve.type = 0;
153
162
  }
154
163
  id++;
155
164
  }
165
+ // Clean up any valves that are leftovers from previous configs.
166
+ for (let i = sys.valves.length - 1; i >= 0; i--) {
167
+ let v = sys.valves.getItemByIndex(i);
168
+ if (v.master === 0 && v.id > sys.equipment.maxValves) {
169
+ sys.valves.removeItemByIndex(i);
170
+ state.valves.removeItemById(v.id);
171
+ }
172
+ }
156
173
  msg.isProcessed = true;
157
174
  }
158
175
  private static getName(id: number, cir: number) {
@@ -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
@@ -31,7 +32,7 @@ export class ChlorinatorStateMessage {
31
32
  let chlor: Chlorinator = sys.chlorinators.findItemByPortId(msg.portId || 0);
32
33
  if (typeof chlor === 'undefined' || chlor.isActive === false) return; // Bail out of here if we don't find an active chlorinator.
33
34
  let cstate: ChlorinatorState = state.chlorinators.getItemById(chlor.id, true);
34
- if (msg.dest >= 1 && msg.dest <= 4) {
35
+ if (msg.dest >= 80 && msg.dest <= 83) {
35
36
  // 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
37
  // comms port. The processing for no comms is done in the Nixe control when the message is sent.
37
38
  if (typeof cstate.lastComm === 'undefined') cstate.lastComm = new Date(1970, 0, 1, 0, 0, 0, 0).getTime();
@@ -40,8 +41,8 @@ export class ChlorinatorStateMessage {
40
41
  // We have not talked to the chlorinator in 30 seconds so we have lost communication.
41
42
  cstate.status = 128;
42
43
  }
43
- chlor = sys.chlorinators.getItemById(msg.dest, true);
44
- chlor.address = msg.dest + 79; // Theoretically, this will always be 80.
44
+ // chlor = sys.chlorinators.getItemById(msg.dest - 79, true); chlor is retrieved above; don't get in incorrectly here.
45
+ chlor.address = msg.dest; // Theoretically, this will always be 80.
45
46
  if (typeof chlor.isActive === 'undefined') cstate.isActive = chlor.isActive = true;
46
47
  }
47
48
  else {
@@ -68,7 +69,7 @@ export class ChlorinatorStateMessage {
68
69
  // This is the model number of the chlorinator and the address is actually the second byte.
69
70
  let name = msg.extractPayloadString(1, 16);
70
71
  if (typeof chlor.name === 'undefined' || chlor.name === '') chlor.name = cstate.name = name;
71
- if (typeof chlor.model === 'undefined') chlor.model = sys.board.valueMaps.chlorinatorModel.getValue(name.toLowerCase());
72
+ if (typeof chlor.model === 'undefined' || chlor.model === 0) chlor.model = sys.board.valueMaps.chlorinatorModel.getValue(name.toLowerCase());
72
73
  cstate.isActive = chlor.isActive;
73
74
  state.emitEquipmentChanges();
74
75
  break;
@@ -136,8 +137,8 @@ export class ChlorinatorStateMessage {
136
137
  // Set it back to disabled. Some asshole is futzing with the chlorinator output.
137
138
  sys.board.chlorinator.setChlorAsync({ id: chlor.id, disabled: true });
138
139
  }
139
- if (sys.controllerType === ControllerType.Virtual) {
140
- // This lets the iChlor use the temp probe from the
140
+ if (sys.controllerType === ControllerType.Nixie) {
141
+ // This lets the current body use the iChlor temp probe
141
142
  let tbody: BodyTempState = sys.pumps.length > 0 ? state.temps.bodies.getBodyIsOn() : state.temps.bodies.getItemById(1, true);
142
143
  if (msg.extractPayloadByte(2) >= 40 && typeof tbody !== 'undefined') tbody.temp = msg.extractPayloadByte(2);
143
144
  }
@@ -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)
@@ -374,13 +396,15 @@ export class EquipmentStateMessage {
374
396
  // the primary is a heatpump and the secondary is gas. In the case of gas + solar or gas + heatpump
375
397
  // the gas heater is the primary and solar or heatpump is the secondary. So we need to dance a little bit
376
398
  // 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');
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
+ }
384
408
  }
385
409
  else {
386
410
  //const heaterActive = (msg.extractPayloadByte(10) & 0x0C) === 12;
@@ -414,11 +438,13 @@ export class EquipmentStateMessage {
414
438
  if (tbody.isOn) {
415
439
  if (tbody.heaterOptions.hybrid > 0) {
416
440
  // 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');
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
+ }
422
448
  }
423
449
  else {
424
450
  //const heaterActive = (msg.extractPayloadByte(10) & 0x0C) === 12;
@@ -453,6 +479,18 @@ export class EquipmentStateMessage {
453
479
  sys.board.heaters.syncHeaterStates();
454
480
  break;
455
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;
456
494
  case ControllerType.EasyTouch:
457
495
  case ControllerType.IntelliCom:
458
496
  case ControllerType.IntelliTouch:
@@ -513,7 +551,9 @@ export class EquipmentStateMessage {
513
551
  state.temps.waterSensor1 = msg.extractPayloadByte(0);
514
552
  state.temps.air = msg.extractPayloadByte(2);
515
553
  let solar: Heater = sys.heaters.getItemById(2);
516
- 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);
517
557
  // pool
518
558
  let tbody: BodyTempState = state.temps.bodies.getItemById(1, true);
519
559
  let cbody: Body = sys.bodies.getItemById(1);
@@ -645,6 +685,39 @@ export class EquipmentStateMessage {
645
685
  }
646
686
  msg.isProcessed = true;
647
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
+
648
721
  private static processTouchCircuits(msg: Inbound) {
649
722
  let circuitId = 1;
650
723
  let maxCircuitId = sys.board.equipmentIds.features.end;
@@ -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
@@ -57,10 +58,28 @@ export class HeaterStateMessage {
57
58
  let heater: Heater = sys.heaters.getItemByAddress(msg.source);
58
59
  let sheater = state.heaters.getItemById(heater.id);
59
60
  sheater.isOn = msg.extractPayloadByte(0) > 0;
60
-
61
- let byte = msg.extractPayloadByte(2);
62
- //sheater.isOn = byte >= 1;
63
- //sheater.isCooling = byte === 2;
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
+ }
64
83
  sheater.commStatus = 0;
65
84
  state.equipment.messages.removeItemByCode(`heater:${heater.id}:comms`);
66
85
  msg.isProcessed = true;
@@ -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
@@ -26,7 +27,9 @@ export class IntelliChemStateMessage {
26
27
  let address = (msg.dest >= 144 && msg.dest <= 158) ? msg.dest : msg.source;
27
28
  if (address < 144 || address > 158) return;
28
29
  let controller = sys.chemControllers.getItemByAddress(address);
29
- if (!controller.isActive) {
30
+ // RKS: 07-13-22 Lets just assume that SunTouch doesn't report its IntelliChem at this point. The action 40 return
31
+ // does not contain the IntelliChem bit when it is returned for this controller.
32
+ if (!controller.isActive && sys.controllerType !== ControllerType.SunTouch) {
30
33
  msg.isProcessed = true;
31
34
  return;
32
35
  }
@@ -272,7 +275,7 @@ export class IntelliChemStateMessage {
272
275
  temps.waterSensor2 = schem.ph.probe.temperature;
273
276
  }
274
277
  }
275
- sys.board.system.setTempsAsync(temps).catch(err => logger.error(err))
278
+ sys.board.system.setTempsAsync(temps).catch(err => logger.error(`Error setting temp compensation for IntelliChem State: ${err.message}`))
276
279
  }
277
280
  }
278
281
  schem.ph.pump.isDosing = schem.ph.dosingStatus === 0 && chem.ph.enabled;
@@ -297,7 +300,7 @@ export class IntelliChemStateMessage {
297
300
  schem.ph.appendDose(schem.ph.doseVolume - phPrev.vol, (schem.ph.timeDosed - phPrev.time) * 1000);
298
301
  }
299
302
  else {
300
- console.log(`DOSING STATUS === ${schem.ph.dosingStatus}`);
303
+ //console.log(`DOSING STATUS === ${schem.ph.dosingStatus}`);
301
304
  // Make sure we don't have a current dose going.
302
305
  schem.ph.currentDose = undefined;
303
306
  }
@@ -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