nodejs-poolcontroller 8.1.2 → 8.4.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 (106) hide show
  1. package/.eslintrc.json +36 -36
  2. package/.github/ISSUE_TEMPLATE/1-bug-report.yml +84 -84
  3. package/.github/ISSUE_TEMPLATE/2-docs.md +12 -12
  4. package/.github/ISSUE_TEMPLATE/3-proposal.md +28 -28
  5. package/.github/ISSUE_TEMPLATE/config.yml +8 -8
  6. package/.github/copilot-instructions.md +63 -0
  7. package/.github/workflows/ghcr-publish.yml +67 -0
  8. package/AGENTS.md +597 -0
  9. package/CONTRIBUTING.md +74 -74
  10. package/Changelog +292 -257
  11. package/Dockerfile +62 -19
  12. package/Gruntfile.js +40 -40
  13. package/LICENSE +661 -661
  14. package/README.md +318 -191
  15. package/anslq25/MessagesMock.ts +221 -221
  16. package/anslq25/boards/MockBoardFactory.ts +49 -49
  17. package/anslq25/boards/MockEasyTouchBoard.ts +696 -696
  18. package/anslq25/boards/MockSystemBoard.ts +216 -216
  19. package/anslq25/chemistry/MockChlorinator.ts +98 -98
  20. package/anslq25/pumps/MockPump.ts +83 -83
  21. package/app.ts +115 -115
  22. package/config/Config.ts +57 -7
  23. package/config/VersionCheck.ts +63 -35
  24. package/controller/Constants.ts +809 -805
  25. package/controller/Equipment.ts +2688 -2664
  26. package/controller/Errors.ts +181 -181
  27. package/controller/Lockouts.ts +549 -549
  28. package/controller/State.ts +3738 -3690
  29. package/controller/boards/AquaLinkBoard.ts +1003 -1003
  30. package/controller/boards/BoardFactory.ts +53 -53
  31. package/controller/boards/EasyTouchBoard.ts +3202 -3202
  32. package/controller/boards/IntelliCenterBoard.ts +4393 -3899
  33. package/controller/boards/IntelliComBoard.ts +69 -69
  34. package/controller/boards/IntelliTouchBoard.ts +382 -382
  35. package/controller/boards/NixieBoard.ts +1944 -1929
  36. package/controller/boards/SunTouchBoard.ts +400 -400
  37. package/controller/boards/SystemBoard.ts +5268 -5268
  38. package/controller/comms/Comms.ts +1272 -1214
  39. package/controller/comms/ScreenLogic.ts +1665 -1665
  40. package/controller/comms/messages/Messages.ts +1433 -1243
  41. package/controller/comms/messages/config/ChlorinatorMessage.ts +5 -0
  42. package/controller/comms/messages/config/CircuitGroupMessage.ts +0 -0
  43. package/controller/comms/messages/config/CircuitMessage.ts +0 -0
  44. package/controller/comms/messages/config/ConfigMessage.ts +6 -0
  45. package/controller/comms/messages/config/CoverMessage.ts +0 -0
  46. package/controller/comms/messages/config/CustomNameMessage.ts +31 -31
  47. package/controller/comms/messages/config/EquipmentMessage.ts +216 -210
  48. package/controller/comms/messages/config/ExternalMessage.ts +96 -10
  49. package/controller/comms/messages/config/FeatureMessage.ts +0 -0
  50. package/controller/comms/messages/config/GeneralMessage.ts +0 -0
  51. package/controller/comms/messages/config/HeaterMessage.ts +0 -0
  52. package/controller/comms/messages/config/IntellichemMessage.ts +0 -0
  53. package/controller/comms/messages/config/OptionsMessage.ts +194 -174
  54. package/controller/comms/messages/config/PumpMessage.ts +0 -0
  55. package/controller/comms/messages/config/RemoteMessage.ts +0 -0
  56. package/controller/comms/messages/config/ScheduleMessage.ts +401 -390
  57. package/controller/comms/messages/config/SecurityMessage.ts +0 -0
  58. package/controller/comms/messages/config/ValveMessage.ts +0 -0
  59. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +0 -0
  60. package/controller/comms/messages/status/EquipmentStateMessage.ts +1158 -822
  61. package/controller/comms/messages/status/HeaterStateMessage.ts +135 -135
  62. package/controller/comms/messages/status/IntelliChemStateMessage.ts +448 -448
  63. package/controller/comms/messages/status/IntelliValveStateMessage.ts +36 -36
  64. package/controller/comms/messages/status/PumpStateMessage.ts +0 -0
  65. package/controller/comms/messages/status/RegalModbusStateMessage.ts +411 -0
  66. package/controller/comms/messages/status/VersionMessage.ts +103 -41
  67. package/controller/nixie/Nixie.ts +173 -173
  68. package/controller/nixie/NixieEquipment.ts +104 -104
  69. package/controller/nixie/bodies/Body.ts +120 -120
  70. package/controller/nixie/bodies/Filter.ts +135 -135
  71. package/controller/nixie/chemistry/ChemController.ts +2724 -2724
  72. package/controller/nixie/chemistry/ChemDoser.ts +806 -806
  73. package/controller/nixie/chemistry/Chlorinator.ts +367 -367
  74. package/controller/nixie/circuits/Circuit.ts +478 -478
  75. package/controller/nixie/heaters/Heater.ts +834 -834
  76. package/controller/nixie/pumps/Pump.ts +1194 -996
  77. package/controller/nixie/schedules/Schedule.ts +401 -401
  78. package/controller/nixie/valves/Valve.ts +170 -170
  79. package/defaultConfig.json +352 -347
  80. package/docker-compose.yml +32 -0
  81. package/logger/DataLogger.ts +448 -448
  82. package/logger/Logger.ts +448 -436
  83. package/package.json +58 -60
  84. package/sendSocket.js +32 -32
  85. package/tsconfig.json +25 -25
  86. package/types/express-multer.d.ts +32 -0
  87. package/web/Server.ts +1937 -1927
  88. package/web/bindings/aqualinkD.json +559 -559
  89. package/web/bindings/influxDB.json +1066 -1066
  90. package/web/bindings/mqtt.json +721 -721
  91. package/web/bindings/mqttAlt.json +746 -746
  92. package/web/bindings/rulesManager.json +54 -54
  93. package/web/bindings/smartThings-Hubitat.json +31 -31
  94. package/web/bindings/valveRelays.json +20 -20
  95. package/web/bindings/vera.json +25 -25
  96. package/web/interfaces/baseInterface.ts +188 -188
  97. package/web/interfaces/httpInterface.ts +148 -148
  98. package/web/interfaces/influxInterface.ts +283 -283
  99. package/web/interfaces/mqttInterface.ts +695 -695
  100. package/web/interfaces/ruleInterface.ts +101 -87
  101. package/web/services/config/Config.ts +1063 -1053
  102. package/web/services/config/ConfigSocket.ts +0 -0
  103. package/web/services/state/State.ts +0 -0
  104. package/web/services/state/StateSocket.ts +0 -0
  105. package/web/services/utilities/Utilities.ts +233 -233
  106. package/.github/workflows/docker-publish-njsPC-linux.yml +0 -50
@@ -18,10 +18,18 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
18
18
  import { Inbound } from "../Messages";
19
19
  import { sys, Body, ICircuitGroup, LightGroup, CircuitGroup } from "../../../Equipment";
20
20
  import { state, ICircuitGroupState, LightGroupState, CircuitGroupState } from "../../../State";
21
- import { Timestamp, utils } from "../../../Constants";
21
+ import { ControllerType, Timestamp, utils } from "../../../Constants";
22
22
  import { logger } from "../../../../logger/Logger";
23
23
  export class ExternalMessage {
24
24
  public static processIntelliCenter(msg: Inbound): void {
25
+ // IntelliCenter v3.x: treat Wireless/ICP/Indoor -> OCP packets as requests, not source-of-truth.
26
+ // We are a bus listener, so we will see traffic not addressed to us; do not apply those requests to state.
27
+ // Only accept OCP-originated messages here. If/when OCP applies a request, it will broadcast authoritative
28
+ // state/config via other message types (e.g., Action 30 / 204).
29
+ if (sys.equipment.isIntellicenterV3 && msg.dest === 16 && msg.source !== 16) {
30
+ msg.isProcessed = true;
31
+ return;
32
+ }
25
33
  switch (msg.extractPayloadByte(0)) {
26
34
  case 0: // Setpoints/HeatMode
27
35
  ExternalMessage.processTempSettings(msg);
@@ -268,10 +276,23 @@ export class ExternalMessage {
268
276
  }
269
277
  }
270
278
  public static processIntelliCenterState(msg) {
271
- ExternalMessage.processCircuitState(2, msg);
272
- ExternalMessage.processFeatureState(8, msg);
273
- ExternalMessage.processScheduleState(14, msg);
274
- ExternalMessage.processCircuitGroupState(12, msg);
279
+ // This is called from Action 30 case 15 (config message) - NOT Action 168 case 15 (wireless message).
280
+ // Action 30 and Action 168 have different payload structures!
281
+ //
282
+ // v1.x: Original offsets (2, 8, 14, 12) - in place since Oct 2019, working.
283
+ // v3.004+: Different structure, requires offsets (3, 9, 15, 13) to match wireless message layout.
284
+ if (sys.equipment.isIntellicenterV3) {
285
+ ExternalMessage.processCircuitState(3, msg);
286
+ ExternalMessage.processFeatureState(9, msg);
287
+ ExternalMessage.processScheduleState(15, msg);
288
+ ExternalMessage.processCircuitGroupState(13, msg);
289
+ } else {
290
+ // v1.x offsets - preserve original behavior since Oct 2019
291
+ ExternalMessage.processCircuitState(2, msg);
292
+ ExternalMessage.processFeatureState(8, msg);
293
+ ExternalMessage.processScheduleState(14, msg);
294
+ ExternalMessage.processCircuitGroupState(12, msg);
295
+ }
275
296
  }
276
297
  private static processHeater(msg: Inbound) {
277
298
  // So a user is changing the heater info. Lets
@@ -508,8 +529,17 @@ export class ExternalMessage {
508
529
  }
509
530
  private static processSchedules(msg: Inbound) {
510
531
  let schedId = msg.extractPayloadByte(2) + 1;
511
- let startTime = msg.extractPayloadInt(3);
512
- let endTime = msg.extractPayloadInt(5);
532
+ // v3.004+: schedule times are big-endian (hi,lo) in Action 168 payloads.
533
+ // v1.x: schedule times are little-endian (lo,hi).
534
+ let startTime: number;
535
+ let endTime: number;
536
+ if (sys.controllerType === ControllerType.IntelliCenter && sys.equipment.isIntellicenterV3) {
537
+ startTime = msg.extractPayloadIntBE(3);
538
+ endTime = msg.extractPayloadIntBE(5);
539
+ } else {
540
+ startTime = msg.extractPayloadInt(3);
541
+ endTime = msg.extractPayloadInt(5);
542
+ }
513
543
  let circuit = msg.extractPayloadByte(7) + 1;
514
544
  let isActive = (msg.extractPayloadByte(8) & 128) === 128; // Inactive schedules do not have bit 8 set.
515
545
  let cfg = sys.schedules.getItemById(schedId, isActive);
@@ -702,9 +732,65 @@ export class ExternalMessage {
702
732
  }
703
733
  private static processTempSettings(msg: Inbound) {
704
734
  let fnTranslateByte = (byte: number) => { return (byte & 0x007F) * (((byte & 0x0080) > 0) ? -1 : 1); }
705
- // What the developers did is supply an offset index into the payload for the byte that is
706
- // changing. I suppose this may have been easier but we are not using that logic. We want the
707
- // information to remain decoded so that we aren't guessing which byte does what.
735
+
736
+ // v3.004+: Wireless sends the FULL options block, not a single-field notification.
737
+ // v1.x: Used byte[2] as a pivot/index indicating which field changed, then byte[byte[2]+3] = new value.
738
+ //
739
+ // IMPORTANT: v3 Action 168 from Wireless has DIFFERENT offsets than Action 30 type 0!
740
+ // - Action 30 type 0: poolHeatNdx=19, spaHeatNdx=21, poolModeNdx=23, spaModeNdx=24
741
+ // - Action 168 Wireless: poolHeatNdx=20, spaHeatNdx=22, poolModeNdx=24, spaModeNdx=25
742
+ // The Wireless payload has an extra byte at position 4, shifting everything by +1.
743
+ const isIntellicenterV3 = (sys.controllerType === ControllerType.IntelliCenter && sys.equipment.isIntellicenterV3);
744
+
745
+ // Detect v3 full-block format by payload length (v3 sends 41 bytes for msgType 0)
746
+ if (isIntellicenterV3 && msg.payload.length >= 27) {
747
+ // v3.004+ Wireless full options block - different offsets than Action 30!
748
+ // Verified from replay 48: Pool setpoint at byte 20, Spa setpoint at byte 22
749
+ const poolHeatNdx = 20;
750
+ const poolCoolNdx = 21;
751
+ const spaHeatNdx = 22;
752
+ const spaCoolNdx = 23;
753
+ const poolModeNdx = 24;
754
+ const spaModeNdx = 25;
755
+
756
+ // Update Body 1 (Pool)
757
+ let body = sys.bodies.getItemById(1, sys.equipment.maxBodies > 0);
758
+ let sbody = state.temps.bodies.getItemById(1);
759
+ if (body.isActive) {
760
+ const newPoolHeat = msg.extractPayloadByte(poolHeatNdx);
761
+ const newPoolCool = msg.extractPayloadByte(poolCoolNdx);
762
+ const newPoolMode = msg.extractPayloadByte(poolModeNdx);
763
+ logger.silly(`v3.004+ Action 168: Pool setpoint ${body.heatSetpoint} → ${newPoolHeat}, coolSetpoint ${body.coolSetpoint} → ${newPoolCool}, mode ${body.heatMode} → ${newPoolMode}`);
764
+ body.heatSetpoint = newPoolHeat;
765
+ body.coolSetpoint = newPoolCool;
766
+ body.heatMode = newPoolMode;
767
+ sbody.heatSetpoint = body.heatSetpoint;
768
+ sbody.coolSetpoint = body.coolSetpoint;
769
+ sbody.heatMode = body.heatMode;
770
+ }
771
+
772
+ // Update Body 2 (Spa)
773
+ body = sys.bodies.getItemById(2, sys.equipment.maxBodies > 1);
774
+ sbody = state.temps.bodies.getItemById(2);
775
+ if (body.isActive) {
776
+ const newSpaHeat = msg.extractPayloadByte(spaHeatNdx);
777
+ const newSpaCool = msg.extractPayloadByte(spaCoolNdx);
778
+ const newSpaMode = msg.extractPayloadByte(spaModeNdx);
779
+ logger.silly(`v3.004+ Action 168: Spa setpoint ${body.heatSetpoint} → ${newSpaHeat}, coolSetpoint ${body.coolSetpoint} → ${newSpaCool}, mode ${body.heatMode} → ${newSpaMode}`);
780
+ body.heatSetpoint = newSpaHeat;
781
+ body.coolSetpoint = newSpaCool;
782
+ body.heatMode = newSpaMode;
783
+ sbody.heatSetpoint = body.heatSetpoint;
784
+ sbody.coolSetpoint = body.coolSetpoint;
785
+ sbody.heatMode = body.heatMode;
786
+ }
787
+
788
+ state.emitEquipmentChanges();
789
+ msg.isProcessed = true;
790
+ return;
791
+ }
792
+
793
+ // v1.x: Single-field-changed notification using byte[2] as pivot index.
708
794
  // payLoadIndex = byte(2) + 3 where the first 3 bytes indicate what value changed.
709
795
  let body: Body = null;
710
796
  switch (msg.extractPayloadByte(2)) {
File without changes
@@ -1,175 +1,195 @@
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 } from "../Messages";
19
- import { sys } from "../../../Equipment";
20
- import { state } from "../../../State";
21
- import { ControllerType } from "../../../Constants";
22
- export class OptionsMessage {
23
- public static process(msg: Inbound): void {
24
- switch (sys.controllerType) {
25
- case ControllerType.IntelliCenter:
26
- OptionsMessage.processIntelliCenter(msg);
27
- break;
28
- case ControllerType.IntelliCom:
29
- case ControllerType.SunTouch:
30
- case ControllerType.EasyTouch:
31
- case ControllerType.IntelliTouch:
32
- OptionsMessage.processIntelliTouch(msg);
33
- break;
34
- }
35
- }
36
- private static processIntelliCenter(msg: Inbound) {
37
- switch (msg.action) {
38
- case 30:
39
- switch (msg.extractPayloadByte(1)) {
40
- case 0:
41
- {
42
- if ((msg.extractPayloadByte(13) & 32) === 32)
43
- sys.general.options.clockSource = 'internet';
44
- else if (sys.general.options.clockSource !== 'server')
45
- sys.general.options.clockSource = 'manual';
46
- sys.general.options.clockMode = (msg.extractPayloadByte(13) & 64) === 64 ? 24 : 12;
47
- if (sys.general.options.clockSource !== 'server' || typeof sys.general.options.adjustDST === 'undefined') sys.general.options.adjustDST = (msg.extractPayloadByte(13) & 128) === 128;
48
- // No pumpDelay
49
- //[255, 0, 255][165, 63, 15, 16, 30, 40][0, 0, 1, 129, 0, 0, 0, 0, 0, 0, 0, 0, 0, 176, 149, 29, 35, 3, 0, 0, 92, 81, 91, 81, 3, 3, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0][4, 193]
50
- // pumpDelay
51
- //[255, 0, 255][165, 63, 15, 16, 30, 40][0, 0, 1, 129, 0, 0, 0, 0, 0, 0, 0, 0, 0, 176, 149, 29, 35, 3, 0, 0, 92, 81, 91, 81, 3, 3, 0, 0, 15, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0][4, 194]
52
- sys.general.options.pumpDelay = msg.extractPayloadByte(29) === 1;
53
- // No cooldownDelay
54
- //[255, 0, 255][165, 63, 15, 16, 30, 40][0, 0, 1, 129, 0, 0, 0, 0, 0, 0, 0, 0, 0, 176, 149, 29, 35, 3, 0, 0, 92, 81, 91, 81, 3, 3, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0][4, 193]
55
- // cooldownDelay
56
- //[255, 0, 255][165, 63, 15, 16, 30, 40][0, 0, 1, 129, 0, 0, 0, 0, 0, 0, 0, 0, 0, 176, 149, 29, 35, 3, 0, 0, 92, 81, 91, 81, 3, 3, 0, 0, 15, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0][4, 194]
57
- sys.general.options.cooldownDelay = msg.extractPayloadByte(30) === 1;
58
- sys.general.options.manualPriority = msg.extractPayloadByte(38) === 1;
59
- sys.general.options.manualHeat = msg.extractPayloadByte(39) === 1;
60
- let fnTranslateByte = (byte):number => { return (byte & 0x007F) * (((byte & 0x0080) > 0) ? -1 : 1); }
61
- sys.equipment.tempSensors.setCalibration('water1', fnTranslateByte(msg.extractPayloadByte(3)));
62
- sys.equipment.tempSensors.setCalibration('solar1', fnTranslateByte(msg.extractPayloadByte(4)));
63
- sys.equipment.tempSensors.setCalibration('air', fnTranslateByte(msg.extractPayloadByte(5)));
64
- sys.equipment.tempSensors.setCalibration('water2', fnTranslateByte(msg.extractPayloadByte(6)));
65
- sys.equipment.tempSensors.setCalibration('solar2', fnTranslateByte(msg.extractPayloadByte(7)));
66
- sys.equipment.tempSensors.setCalibration('water3', fnTranslateByte(msg.extractPayloadByte(8)));
67
- sys.equipment.tempSensors.setCalibration('solar3', fnTranslateByte(msg.extractPayloadByte(9)));
68
- sys.equipment.tempSensors.setCalibration('water4', fnTranslateByte(msg.extractPayloadByte(10)));
69
- sys.equipment.tempSensors.setCalibration('solar4', fnTranslateByte(msg.extractPayloadByte(11)));
70
-
71
- // When we complete our transition for the calibration make this go away.
72
- //sys.general.options.waterTempAdj2 = (msg.extractPayloadByte(2) & 0x007F) * (((msg.extractPayloadByte(2) & 0x0080) > 0) ? -1 : 1);
73
- //sys.general.options.waterTempAdj1 = (msg.extractPayloadByte(3) & 0x007F) * (((msg.extractPayloadByte(3) & 0x0080) > 0) ? -1 : 1);
74
- //sys.general.options.solarTempAdj1 = (msg.extractPayloadByte(4) & 0x007F) * (((msg.extractPayloadByte(4) & 0x0080) > 0) ? -1 : 1);
75
- //sys.general.options.airTempAdj = (msg.extractPayloadByte(5) & 0x007F) * (((msg.extractPayloadByte(5) & 0x0080) > 0) ? -1 : 1);
76
- //sys.general.options.waterTempAdj2 = (msg.extractPayloadByte(6) & 0x007F) * (((msg.extractPayloadByte(6) & 0x0080) > 0) ? -1 : 1);
77
-
78
- // Somewhere in here are the units.
79
-
80
- let body = sys.bodies.getItemById(1, sys.equipment.maxBodies > 0);
81
- body.heatMode = msg.extractPayloadByte(24);
82
- body.heatSetpoint = msg.extractPayloadByte(20);
83
- body.coolSetpoint = msg.extractPayloadByte(21);
84
-
85
- body = sys.bodies.getItemById(2, sys.equipment.maxBodies > 1);
86
- body.heatMode = msg.extractPayloadByte(25);
87
- body.heatSetpoint = msg.extractPayloadByte(22);
88
- body.coolSetpoint = msg.extractPayloadByte(23);
89
-
90
- //body = sys.bodies.getItemById(3, sys.equipment.maxBodies > 2);
91
- //body.heatMode = msg.extractPayloadByte(26);
92
- //body.heatSetpoint = msg.extractPayloadByte(21);
93
- //body.manualHeat = sys.general.options.manualHeat;
94
- //body = sys.bodies.getItemById(4, sys.equipment.maxBodies > 3);
95
- //body.heatMode = msg.extractPayloadByte(27);
96
- //body.heatSetpoint = msg.extractPayloadByte(23);
97
- msg.isProcessed = true;
98
- break;
99
- }
100
- case 1: // Vacation mode
101
- let yy = msg.extractPayloadByte(4) + 2000;
102
- let mm = msg.extractPayloadByte(5);
103
- let dd = msg.extractPayloadByte(6);
104
- sys.general.options.vacation.startDate = new Date(yy, mm - 1, dd);
105
- yy = msg.extractPayloadByte(7) + 2000;
106
- mm = msg.extractPayloadByte(8);
107
- dd = msg.extractPayloadByte(9);
108
- sys.general.options.vacation.endDate = new Date(yy, mm - 1, dd);
109
- sys.general.options.vacation.enabled = msg.extractPayloadByte(2) > 0;
110
- sys.general.options.vacation.useTimeframe = msg.extractPayloadByte(3) > 0;
111
- msg.isProcessed = true;
112
- break;
113
- }
114
- msg.isProcessed = true;
115
- break;
116
- }
117
- }
118
- private static processIntelliTouch(msg: Inbound) {
119
- switch (msg.action) {
120
- case 30: {
121
- // sample packet
122
- // [165,33,15,16,30,16],[4,9,16,0,1,72,0,0,16,205,0,0,0,2,0,0],[2,88]
123
- // this is (I believe) to assign circuits that require high speed mode with a dual speed pump
124
-
125
- // We don't want the dual speed pump to even exist unless there are no circuit controlling it.
126
- // It should not be showing up in our pumps list or emitting state unless the user has defined
127
- // circuits to it on *Touch interfaces.
128
- // RSG 1/5/23 - Intellitouch (and Dual Body) accept 8 high speed circuits
129
- let maxCircuits = sys.controllerType === ControllerType.IntelliTouch ? 8 : 4;
130
- let arrCircuits = [];
131
- let pump = sys.pumps.getDualSpeed(true);
132
- for (let i = 0; i < maxCircuits; i++) {
133
- let val = msg.extractPayloadByte(i);
134
- if (val > 0) arrCircuits.push(val);
135
- else pump.circuits.removeItemById(i);
136
- }
137
- if (arrCircuits.length > 0) {
138
- let pump = sys.pumps.getDualSpeed(true);
139
- for (let j = 1; j <= arrCircuits.length; j++) pump.circuits.getItemById(j, true).circuit = arrCircuits[j-1];
140
- }
141
- else sys.pumps.removeItemById(10);
142
- msg.isProcessed = true;
143
- break;
144
- }
145
- case 40:
146
- case 168:
147
- {
148
-
149
- // [165,33,16,34,168,10],[0,0,0,254,0,0,0,0,0,0],[2,168 = manual heat mode off
150
- // [165,33,16,34,168,10],[0,0,0,254,1,0,0,0,0,0],[2,169] = manual heat mode on
151
- sys.general.options.manualHeat = msg.extractPayloadByte(4) === 1;
152
- // From https://github.com/tagyoureit/nodejs-poolController/issues/362 = Intellitouch
153
- // [0,0,0,0,1,x,0,0,0,0] x=0 Manual OP heat Off; x=1 Manual OP heat On
154
- sys.general.options.manualPriority = msg.extractPayloadByte(5) === 1;
155
- if ((msg.extractPayloadByte(3) & 0x01) === 1) {
156
- // only support for 1 ic with EasyTouch
157
- let chem = sys.chemControllers.getItemByAddress(144, true);
158
- //let schem = state.chemControllers.getItemById(chem.id, true);
159
- chem.ph.tank.capacity = chem.orp.tank.capacity = 6;
160
- chem.ph.tank.units = chem.orp.tank.units = '';
161
-
162
- }
163
- else {
164
- if (sys.controllerType !== ControllerType.SunTouch) {
165
- let chem = sys.chemControllers.getItemByAddress(144);
166
- state.chemControllers.removeItemById(chem.id);
167
- sys.chemControllers.removeItemById(chem.id);
168
- }
169
- }
170
- msg.isProcessed = true;
171
- break;
172
- }
173
- }
174
- }
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 } from "../Messages";
19
+ import { sys } from "../../../Equipment";
20
+ import { state } from "../../../State";
21
+ import { ControllerType } from "../../../Constants";
22
+ export class OptionsMessage {
23
+ public static process(msg: Inbound): void {
24
+ switch (sys.controllerType) {
25
+ case ControllerType.IntelliCenter:
26
+ OptionsMessage.processIntelliCenter(msg);
27
+ break;
28
+ case ControllerType.IntelliCom:
29
+ case ControllerType.SunTouch:
30
+ case ControllerType.EasyTouch:
31
+ case ControllerType.IntelliTouch:
32
+ OptionsMessage.processIntelliTouch(msg);
33
+ break;
34
+ }
35
+ }
36
+ private static processIntelliCenter(msg: Inbound) {
37
+ switch (msg.action) {
38
+ case 30:
39
+ switch (msg.extractPayloadByte(1)) {
40
+ case 0:
41
+ {
42
+ if ((msg.extractPayloadByte(13) & 32) === 32)
43
+ sys.general.options.clockSource = 'internet';
44
+ else if (sys.general.options.clockSource !== 'server')
45
+ sys.general.options.clockSource = 'manual';
46
+ sys.general.options.clockMode = (msg.extractPayloadByte(13) & 64) === 64 ? 24 : 12;
47
+ if (sys.general.options.clockSource !== 'server' || typeof sys.general.options.adjustDST === 'undefined') sys.general.options.adjustDST = (msg.extractPayloadByte(13) & 128) === 128;
48
+ // No pumpDelay
49
+ //[255, 0, 255][165, 63, 15, 16, 30, 40][0, 0, 1, 129, 0, 0, 0, 0, 0, 0, 0, 0, 0, 176, 149, 29, 35, 3, 0, 0, 92, 81, 91, 81, 3, 3, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0][4, 193]
50
+ // pumpDelay
51
+ //[255, 0, 255][165, 63, 15, 16, 30, 40][0, 0, 1, 129, 0, 0, 0, 0, 0, 0, 0, 0, 0, 176, 149, 29, 35, 3, 0, 0, 92, 81, 91, 81, 3, 3, 0, 0, 15, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0][4, 194]
52
+ sys.general.options.pumpDelay = msg.extractPayloadByte(29) === 1;
53
+ // No cooldownDelay
54
+ //[255, 0, 255][165, 63, 15, 16, 30, 40][0, 0, 1, 129, 0, 0, 0, 0, 0, 0, 0, 0, 0, 176, 149, 29, 35, 3, 0, 0, 92, 81, 91, 81, 3, 3, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0][4, 193]
55
+ // cooldownDelay
56
+ //[255, 0, 255][165, 63, 15, 16, 30, 40][0, 0, 1, 129, 0, 0, 0, 0, 0, 0, 0, 0, 0, 176, 149, 29, 35, 3, 0, 0, 92, 81, 91, 81, 3, 3, 0, 0, 15, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0][4, 194]
57
+ sys.general.options.cooldownDelay = msg.extractPayloadByte(30) === 1;
58
+ sys.general.options.manualPriority = msg.extractPayloadByte(38) === 1;
59
+ sys.general.options.manualHeat = msg.extractPayloadByte(39) === 1;
60
+ let fnTranslateByte = (byte):number => { return (byte & 0x007F) * (((byte & 0x0080) > 0) ? -1 : 1); }
61
+ sys.equipment.tempSensors.setCalibration('water1', fnTranslateByte(msg.extractPayloadByte(3)));
62
+ sys.equipment.tempSensors.setCalibration('solar1', fnTranslateByte(msg.extractPayloadByte(4)));
63
+ sys.equipment.tempSensors.setCalibration('air', fnTranslateByte(msg.extractPayloadByte(5)));
64
+ sys.equipment.tempSensors.setCalibration('water2', fnTranslateByte(msg.extractPayloadByte(6)));
65
+ sys.equipment.tempSensors.setCalibration('solar2', fnTranslateByte(msg.extractPayloadByte(7)));
66
+ sys.equipment.tempSensors.setCalibration('water3', fnTranslateByte(msg.extractPayloadByte(8)));
67
+ sys.equipment.tempSensors.setCalibration('solar3', fnTranslateByte(msg.extractPayloadByte(9)));
68
+ sys.equipment.tempSensors.setCalibration('water4', fnTranslateByte(msg.extractPayloadByte(10)));
69
+ sys.equipment.tempSensors.setCalibration('solar4', fnTranslateByte(msg.extractPayloadByte(11)));
70
+
71
+ // When we complete our transition for the calibration make this go away.
72
+ //sys.general.options.waterTempAdj2 = (msg.extractPayloadByte(2) & 0x007F) * (((msg.extractPayloadByte(2) & 0x0080) > 0) ? -1 : 1);
73
+ //sys.general.options.waterTempAdj1 = (msg.extractPayloadByte(3) & 0x007F) * (((msg.extractPayloadByte(3) & 0x0080) > 0) ? -1 : 1);
74
+ //sys.general.options.solarTempAdj1 = (msg.extractPayloadByte(4) & 0x007F) * (((msg.extractPayloadByte(4) & 0x0080) > 0) ? -1 : 1);
75
+ //sys.general.options.airTempAdj = (msg.extractPayloadByte(5) & 0x007F) * (((msg.extractPayloadByte(5) & 0x0080) > 0) ? -1 : 1);
76
+ //sys.general.options.waterTempAdj2 = (msg.extractPayloadByte(6) & 0x007F) * (((msg.extractPayloadByte(6) & 0x0080) > 0) ? -1 : 1);
77
+
78
+ // Somewhere in here are the units.
79
+
80
+ // v3.004+: payload layout shifted by 1 byte vs v1.x (timestamp insertion earlier in the packet).
81
+ // Evidence: replay.21 Action 30 type 0 has [.., 85,100,94,103, 3,3 ..] at bytes 19-24.
82
+ const isIntellicenterV3 = (sys.controllerType === ControllerType.IntelliCenter && sys.equipment.isIntellicenterV3);
83
+ const poolHeatNdx = isIntellicenterV3 ? 19 : 20;
84
+ const poolCoolNdx = isIntellicenterV3 ? 20 : 21;
85
+ const spaHeatNdx = isIntellicenterV3 ? 21 : 22;
86
+ const spaCoolNdx = isIntellicenterV3 ? 22 : 23;
87
+ const poolModeNdx = isIntellicenterV3 ? 23 : 24;
88
+ const spaModeNdx = isIntellicenterV3 ? 24 : 25;
89
+
90
+ let body = sys.bodies.getItemById(1, sys.equipment.maxBodies > 0);
91
+ body.heatMode = msg.extractPayloadByte(poolModeNdx);
92
+ body.heatSetpoint = msg.extractPayloadByte(poolHeatNdx);
93
+ body.coolSetpoint = msg.extractPayloadByte(poolCoolNdx);
94
+ // Keep runtime state in sync with config values so UIs (dashPanel/MQTT/etc) reflect
95
+ // authoritative OCP updates, including changes initiated by other panels (Wireless/OP).
96
+ let sbody = state.temps.bodies.getItemById(1, true);
97
+ sbody.heatMode = body.heatMode;
98
+ sbody.heatSetpoint = body.heatSetpoint;
99
+ sbody.coolSetpoint = body.coolSetpoint;
100
+
101
+ body = sys.bodies.getItemById(2, sys.equipment.maxBodies > 1);
102
+ body.heatMode = msg.extractPayloadByte(spaModeNdx);
103
+ body.heatSetpoint = msg.extractPayloadByte(spaHeatNdx);
104
+ body.coolSetpoint = msg.extractPayloadByte(spaCoolNdx);
105
+ sbody = state.temps.bodies.getItemById(2, true);
106
+ sbody.heatMode = body.heatMode;
107
+ sbody.heatSetpoint = body.heatSetpoint;
108
+ sbody.coolSetpoint = body.coolSetpoint;
109
+
110
+ //body = sys.bodies.getItemById(3, sys.equipment.maxBodies > 2);
111
+ //body.heatMode = msg.extractPayloadByte(26);
112
+ //body.heatSetpoint = msg.extractPayloadByte(21);
113
+ //body.manualHeat = sys.general.options.manualHeat;
114
+ //body = sys.bodies.getItemById(4, sys.equipment.maxBodies > 3);
115
+ //body.heatMode = msg.extractPayloadByte(27);
116
+ //body.heatSetpoint = msg.extractPayloadByte(23);
117
+ msg.isProcessed = true;
118
+ break;
119
+ }
120
+ case 1: // Vacation mode
121
+ let yy = msg.extractPayloadByte(4) + 2000;
122
+ let mm = msg.extractPayloadByte(5);
123
+ let dd = msg.extractPayloadByte(6);
124
+ sys.general.options.vacation.startDate = new Date(yy, mm - 1, dd);
125
+ yy = msg.extractPayloadByte(7) + 2000;
126
+ mm = msg.extractPayloadByte(8);
127
+ dd = msg.extractPayloadByte(9);
128
+ sys.general.options.vacation.endDate = new Date(yy, mm - 1, dd);
129
+ sys.general.options.vacation.enabled = msg.extractPayloadByte(2) > 0;
130
+ sys.general.options.vacation.useTimeframe = msg.extractPayloadByte(3) > 0;
131
+ msg.isProcessed = true;
132
+ break;
133
+ }
134
+ msg.isProcessed = true;
135
+ break;
136
+ }
137
+ }
138
+ private static processIntelliTouch(msg: Inbound) {
139
+ switch (msg.action) {
140
+ case 30: {
141
+ // sample packet
142
+ // [165,33,15,16,30,16],[4,9,16,0,1,72,0,0,16,205,0,0,0,2,0,0],[2,88]
143
+ // this is (I believe) to assign circuits that require high speed mode with a dual speed pump
144
+
145
+ // We don't want the dual speed pump to even exist unless there are no circuit controlling it.
146
+ // It should not be showing up in our pumps list or emitting state unless the user has defined
147
+ // circuits to it on *Touch interfaces.
148
+ // RSG 1/5/23 - Intellitouch (and Dual Body) accept 8 high speed circuits
149
+ let maxCircuits = sys.controllerType === ControllerType.IntelliTouch ? 8 : 4;
150
+ let arrCircuits = [];
151
+ let pump = sys.pumps.getDualSpeed(true);
152
+ for (let i = 0; i < maxCircuits; i++) {
153
+ let val = msg.extractPayloadByte(i);
154
+ if (val > 0) arrCircuits.push(val);
155
+ else pump.circuits.removeItemById(i);
156
+ }
157
+ if (arrCircuits.length > 0) {
158
+ let pump = sys.pumps.getDualSpeed(true);
159
+ for (let j = 1; j <= arrCircuits.length; j++) pump.circuits.getItemById(j, true).circuit = arrCircuits[j-1];
160
+ }
161
+ else sys.pumps.removeItemById(10);
162
+ msg.isProcessed = true;
163
+ break;
164
+ }
165
+ case 40:
166
+ case 168:
167
+ {
168
+
169
+ // [165,33,16,34,168,10],[0,0,0,254,0,0,0,0,0,0],[2,168 = manual heat mode off
170
+ // [165,33,16,34,168,10],[0,0,0,254,1,0,0,0,0,0],[2,169] = manual heat mode on
171
+ sys.general.options.manualHeat = msg.extractPayloadByte(4) === 1;
172
+ // From https://github.com/tagyoureit/nodejs-poolController/issues/362 = Intellitouch
173
+ // [0,0,0,0,1,x,0,0,0,0] x=0 Manual OP heat Off; x=1 Manual OP heat On
174
+ sys.general.options.manualPriority = msg.extractPayloadByte(5) === 1;
175
+ if ((msg.extractPayloadByte(3) & 0x01) === 1) {
176
+ // only support for 1 ic with EasyTouch
177
+ let chem = sys.chemControllers.getItemByAddress(144, true);
178
+ //let schem = state.chemControllers.getItemById(chem.id, true);
179
+ chem.ph.tank.capacity = chem.orp.tank.capacity = 6;
180
+ chem.ph.tank.units = chem.orp.tank.units = '';
181
+
182
+ }
183
+ else {
184
+ if (sys.controllerType !== ControllerType.SunTouch) {
185
+ let chem = sys.chemControllers.getItemByAddress(144);
186
+ state.chemControllers.removeItemById(chem.id);
187
+ sys.chemControllers.removeItemById(chem.id);
188
+ }
189
+ }
190
+ msg.isProcessed = true;
191
+ break;
192
+ }
193
+ }
194
+ }
175
195
  }
File without changes
File without changes