nodejs-poolcontroller 8.3.0 → 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.
- package/.eslintrc.json +36 -36
- package/.github/ISSUE_TEMPLATE/1-bug-report.yml +84 -84
- package/.github/ISSUE_TEMPLATE/2-docs.md +12 -12
- package/.github/ISSUE_TEMPLATE/3-proposal.md +28 -28
- package/.github/ISSUE_TEMPLATE/config.yml +8 -8
- package/.github/copilot-instructions.md +63 -63
- package/.github/workflows/ghcr-publish.yml +67 -67
- package/AGENTS.md +597 -0
- package/CONTRIBUTING.md +74 -74
- package/Changelog +292 -284
- package/Dockerfile +62 -62
- package/Gruntfile.js +40 -40
- package/LICENSE +661 -661
- package/README.md +318 -309
- package/anslq25/MessagesMock.ts +221 -221
- package/anslq25/boards/MockBoardFactory.ts +49 -49
- package/anslq25/boards/MockEasyTouchBoard.ts +696 -696
- package/anslq25/boards/MockSystemBoard.ts +216 -216
- package/anslq25/chemistry/MockChlorinator.ts +98 -98
- package/anslq25/pumps/MockPump.ts +83 -83
- package/app.ts +115 -115
- package/config/Config.ts +0 -0
- package/config/VersionCheck.ts +0 -0
- package/controller/Constants.ts +809 -805
- package/controller/Equipment.ts +2688 -2664
- package/controller/Errors.ts +181 -181
- package/controller/Lockouts.ts +549 -549
- package/controller/State.ts +3738 -3701
- package/controller/boards/AquaLinkBoard.ts +1003 -1003
- package/controller/boards/BoardFactory.ts +53 -53
- package/controller/boards/EasyTouchBoard.ts +3202 -3202
- package/controller/boards/IntelliCenterBoard.ts +4393 -3899
- package/controller/boards/IntelliComBoard.ts +69 -69
- package/controller/boards/IntelliTouchBoard.ts +382 -382
- package/controller/boards/NixieBoard.ts +1944 -1944
- package/controller/boards/SunTouchBoard.ts +400 -400
- package/controller/boards/SystemBoard.ts +5268 -5268
- package/controller/comms/Comms.ts +1272 -1255
- package/controller/comms/ScreenLogic.ts +1665 -1665
- package/controller/comms/messages/Messages.ts +1433 -1406
- package/controller/comms/messages/config/ChlorinatorMessage.ts +5 -0
- package/controller/comms/messages/config/CircuitGroupMessage.ts +0 -0
- package/controller/comms/messages/config/CircuitMessage.ts +0 -0
- package/controller/comms/messages/config/ConfigMessage.ts +6 -0
- package/controller/comms/messages/config/CoverMessage.ts +0 -0
- package/controller/comms/messages/config/CustomNameMessage.ts +31 -31
- package/controller/comms/messages/config/EquipmentMessage.ts +216 -210
- package/controller/comms/messages/config/ExternalMessage.ts +96 -10
- package/controller/comms/messages/config/FeatureMessage.ts +0 -0
- package/controller/comms/messages/config/GeneralMessage.ts +0 -0
- package/controller/comms/messages/config/HeaterMessage.ts +0 -0
- package/controller/comms/messages/config/IntellichemMessage.ts +0 -0
- package/controller/comms/messages/config/OptionsMessage.ts +194 -174
- package/controller/comms/messages/config/PumpMessage.ts +0 -0
- package/controller/comms/messages/config/RemoteMessage.ts +0 -0
- package/controller/comms/messages/config/ScheduleMessage.ts +401 -390
- package/controller/comms/messages/config/SecurityMessage.ts +0 -0
- package/controller/comms/messages/config/ValveMessage.ts +0 -0
- package/controller/comms/messages/status/ChlorinatorStateMessage.ts +0 -0
- package/controller/comms/messages/status/EquipmentStateMessage.ts +1158 -822
- package/controller/comms/messages/status/HeaterStateMessage.ts +135 -135
- package/controller/comms/messages/status/IntelliChemStateMessage.ts +448 -448
- package/controller/comms/messages/status/IntelliValveStateMessage.ts +36 -36
- package/controller/comms/messages/status/PumpStateMessage.ts +0 -0
- package/controller/comms/messages/status/RegalModbusStateMessage.ts +410 -410
- package/controller/comms/messages/status/VersionMessage.ts +103 -41
- package/controller/nixie/Nixie.ts +173 -173
- package/controller/nixie/NixieEquipment.ts +104 -104
- package/controller/nixie/bodies/Body.ts +120 -120
- package/controller/nixie/bodies/Filter.ts +135 -135
- package/controller/nixie/chemistry/ChemController.ts +2724 -2724
- package/controller/nixie/chemistry/ChemDoser.ts +806 -806
- package/controller/nixie/chemistry/Chlorinator.ts +367 -367
- package/controller/nixie/circuits/Circuit.ts +478 -478
- package/controller/nixie/heaters/Heater.ts +834 -834
- package/controller/nixie/pumps/Pump.ts +1193 -1193
- package/controller/nixie/schedules/Schedule.ts +401 -401
- package/controller/nixie/valves/Valve.ts +170 -170
- package/defaultConfig.json +352 -352
- package/docker-compose.yml +31 -31
- package/logger/DataLogger.ts +448 -448
- package/logger/Logger.ts +448 -436
- package/package.json +58 -58
- package/sendSocket.js +32 -32
- package/tsconfig.json +25 -25
- package/types/express-multer.d.ts +32 -32
- package/web/Server.ts +1937 -1927
- package/web/bindings/aqualinkD.json +559 -559
- package/web/bindings/influxDB.json +1066 -1066
- package/web/bindings/mqtt.json +721 -721
- package/web/bindings/mqttAlt.json +746 -746
- package/web/bindings/rulesManager.json +54 -54
- package/web/bindings/smartThings-Hubitat.json +31 -31
- package/web/bindings/valveRelays.json +20 -20
- package/web/bindings/vera.json +25 -25
- package/web/interfaces/baseInterface.ts +188 -188
- package/web/interfaces/httpInterface.ts +148 -148
- package/web/interfaces/influxInterface.ts +283 -283
- package/web/interfaces/mqttInterface.ts +695 -695
- package/web/interfaces/ruleInterface.ts +101 -87
- package/web/services/config/Config.ts +1063 -1053
- package/web/services/config/ConfigSocket.ts +0 -0
- package/web/services/state/State.ts +0 -0
- package/web/services/state/StateSocket.ts +0 -0
- package/web/services/utilities/Utilities.ts +233 -233
|
@@ -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
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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
|
-
|
|
512
|
-
|
|
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
|
-
|
|
706
|
-
//
|
|
707
|
-
//
|
|
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
|
|
File without changes
|
|
File without changes
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|