nodejs-poolcontroller 8.4.0 → 8.4.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.
- package/.github/workflows/ghcr-publish.yml +1 -1
- package/157_issues.md +101 -0
- package/AGENTS.md +17 -1
- package/README.md +13 -2
- package/controller/Equipment.ts +49 -0
- package/controller/State.ts +8 -0
- package/controller/boards/AquaLinkBoard.ts +174 -2
- package/controller/boards/EasyTouchBoard.ts +44 -0
- package/controller/boards/IntelliCenterBoard.ts +360 -172
- package/controller/boards/NixieBoard.ts +7 -4
- package/controller/boards/SunTouchBoard.ts +1 -0
- package/controller/boards/SystemBoard.ts +39 -4
- package/controller/comms/Comms.ts +9 -3
- package/controller/comms/messages/Messages.ts +218 -24
- package/controller/comms/messages/config/EquipmentMessage.ts +34 -0
- package/controller/comms/messages/config/ExternalMessage.ts +1051 -989
- package/controller/comms/messages/config/GeneralMessage.ts +65 -0
- package/controller/comms/messages/config/OptionsMessage.ts +15 -2
- package/controller/comms/messages/config/PumpMessage.ts +427 -421
- package/controller/comms/messages/config/SecurityMessage.ts +37 -13
- package/controller/comms/messages/status/EquipmentStateMessage.ts +0 -218
- package/controller/comms/messages/status/HeaterStateMessage.ts +27 -15
- package/controller/comms/messages/status/NeptuneModbusStateMessage.ts +217 -0
- package/controller/comms/messages/status/VersionMessage.ts +67 -18
- package/controller/nixie/chemistry/ChemController.ts +65 -33
- package/controller/nixie/heaters/Heater.ts +10 -1
- package/controller/nixie/pumps/Pump.ts +145 -2
- package/docker-compose.yml +1 -0
- package/logger/Logger.ts +75 -64
- package/package.json +1 -1
- package/tsconfig.json +2 -1
- package/web/Server.ts +3 -1
- package/web/services/config/Config.ts +150 -1
- package/web/services/state/State.ts +21 -0
- package/web/services/state/StateSocket.ts +28 -0
|
@@ -17,45 +17,110 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
17
17
|
*/
|
|
18
18
|
import { Inbound } from "../Messages";
|
|
19
19
|
import { sys, General } from "../../../Equipment";
|
|
20
|
+
import { ControllerType } from "../../../Constants";
|
|
20
21
|
import { logger } from "../../../../logger/Logger";
|
|
21
22
|
export class GeneralMessage {
|
|
23
|
+
private static isIntellicenterV3(): boolean {
|
|
24
|
+
return sys.controllerType === ControllerType.IntelliCenter && sys.equipment.isIntellicenterV3 === true;
|
|
25
|
+
}
|
|
26
|
+
private static getTrimmed(msg: Inbound, start: number, len: number): string {
|
|
27
|
+
return (msg.extractPayloadString(start, len) || '').replace(/\0+$/g, '').trim();
|
|
28
|
+
}
|
|
22
29
|
public static process(msg: Inbound): void {
|
|
30
|
+
const isIntellicenterV3 = GeneralMessage.isIntellicenterV3();
|
|
23
31
|
switch (msg.extractPayloadByte(1)) {
|
|
24
32
|
case 0:
|
|
33
|
+
if (isIntellicenterV3) {
|
|
34
|
+
// v3.008+ sends location-focused data for item 0.
|
|
35
|
+
const zip = GeneralMessage.getTrimmed(msg, 2, 6);
|
|
36
|
+
if (zip.length > 0) sys.general.location.zip = zip;
|
|
37
|
+
// In captured v3.008 packets, bytes 13/14 map cleanly to longitude magnitude.
|
|
38
|
+
const lonLo = msg.extractPayloadByte(13, 255);
|
|
39
|
+
const lonHi = msg.extractPayloadByte(14, 255);
|
|
40
|
+
if (lonLo !== 255 && lonHi !== 255) {
|
|
41
|
+
const lon = ((lonHi * 256) + lonLo) / 100;
|
|
42
|
+
if (!isNaN(lon) && lon > 0 && lon <= 180) sys.general.location.longitude = -lon;
|
|
43
|
+
}
|
|
44
|
+
msg.isProcessed = true;
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
25
47
|
sys.general.alias = msg.extractPayloadString(2, 16);
|
|
26
48
|
sys.general.owner.name = msg.extractPayloadString(18, 16);
|
|
27
49
|
sys.general.location.zip = msg.extractPayloadString(34, 6);
|
|
28
50
|
msg.isProcessed = true;
|
|
29
51
|
break;
|
|
30
52
|
case 1:
|
|
53
|
+
if (isIntellicenterV3) {
|
|
54
|
+
// v3.008+ item 1 carries city text (not phone fields).
|
|
55
|
+
const city = GeneralMessage.getTrimmed(msg, 2, 32);
|
|
56
|
+
if (city.length > 0) sys.general.location.city = city;
|
|
57
|
+
msg.isProcessed = true;
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
31
60
|
sys.general.owner.phone = msg.extractPayloadString(2, 20);
|
|
32
61
|
sys.general.owner.phone2 = msg.extractPayloadString(21, 15);
|
|
33
62
|
sys.general.location.latitude = ((msg.extractPayloadByte(35) * 256) + msg.extractPayloadByte(34)) / 100;
|
|
34
63
|
msg.isProcessed = true;
|
|
35
64
|
break;
|
|
36
65
|
case 2:
|
|
66
|
+
if (isIntellicenterV3) {
|
|
67
|
+
const owner = GeneralMessage.getTrimmed(msg, 2, 16);
|
|
68
|
+
if (owner.length > 0) sys.general.owner.name = owner;
|
|
69
|
+
msg.isProcessed = true;
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
37
72
|
sys.general.location.address = msg.extractPayloadString(2, 32);
|
|
38
73
|
sys.general.location.longitude = -(((msg.extractPayloadByte(35) * 256) + msg.extractPayloadByte(34)) / 100);
|
|
39
74
|
msg.isProcessed = true;
|
|
40
75
|
break;
|
|
41
76
|
case 3:
|
|
77
|
+
if (isIntellicenterV3) {
|
|
78
|
+
const email = GeneralMessage.getTrimmed(msg, 2, 32);
|
|
79
|
+
if (email.length > 0) sys.general.owner.email = email;
|
|
80
|
+
msg.isProcessed = true;
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
42
83
|
sys.general.owner.email = msg.extractPayloadString(2, 32);
|
|
43
84
|
sys.general.location.timeZone = msg.extractPayloadByte(34);
|
|
44
85
|
msg.isProcessed = true;
|
|
45
86
|
break;
|
|
46
87
|
case 4:
|
|
88
|
+
if (isIntellicenterV3) {
|
|
89
|
+
const email2 = GeneralMessage.getTrimmed(msg, 2, 32);
|
|
90
|
+
if (email2.length > 0) sys.general.owner.email2 = email2;
|
|
91
|
+
msg.isProcessed = true;
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
47
94
|
sys.general.owner.email2 = msg.extractPayloadString(2, 32);
|
|
48
95
|
msg.isProcessed = true;
|
|
49
96
|
break;
|
|
50
97
|
case 5:
|
|
98
|
+
if (isIntellicenterV3) {
|
|
99
|
+
const country = GeneralMessage.getTrimmed(msg, 2, 32);
|
|
100
|
+
if (country.length > 0) sys.general.location.country = country;
|
|
101
|
+
msg.isProcessed = true;
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
51
104
|
sys.general.location.country = msg.extractPayloadString(2, 32);
|
|
52
105
|
msg.isProcessed = true;
|
|
53
106
|
break;
|
|
54
107
|
case 6:
|
|
108
|
+
if (isIntellicenterV3) {
|
|
109
|
+
const city = GeneralMessage.getTrimmed(msg, 2, 32);
|
|
110
|
+
if (city.length > 0) sys.general.location.city = city;
|
|
111
|
+
msg.isProcessed = true;
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
55
114
|
sys.general.location.city = msg.extractPayloadString(2, 32);
|
|
56
115
|
msg.isProcessed = true;
|
|
57
116
|
break;
|
|
58
117
|
case 7:
|
|
118
|
+
if (isIntellicenterV3) {
|
|
119
|
+
const stateText = GeneralMessage.getTrimmed(msg, 2, 32);
|
|
120
|
+
if (stateText.length > 0) sys.general.location.state = stateText;
|
|
121
|
+
msg.isProcessed = true;
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
59
124
|
sys.general.location.state = msg.extractPayloadString(2, 32);
|
|
60
125
|
msg.isProcessed = true;
|
|
61
126
|
break;
|
|
@@ -20,6 +20,10 @@ import { sys } from "../../../Equipment";
|
|
|
20
20
|
import { state } from "../../../State";
|
|
21
21
|
import { ControllerType } from "../../../Constants";
|
|
22
22
|
export class OptionsMessage {
|
|
23
|
+
private static decodeFreezeOverride(raw: number): number {
|
|
24
|
+
// v3.008 captures show this as a compact code where 0 => 30 min and 1 => 90 min.
|
|
25
|
+
return raw <= 3 ? (30 + (raw * 60)) : raw;
|
|
26
|
+
}
|
|
23
27
|
public static process(msg: Inbound): void {
|
|
24
28
|
switch (sys.controllerType) {
|
|
25
29
|
case ControllerType.IntelliCenter:
|
|
@@ -55,7 +59,17 @@ export class OptionsMessage {
|
|
|
55
59
|
// cooldownDelay
|
|
56
60
|
//[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
61
|
sys.general.options.cooldownDelay = msg.extractPayloadByte(30) === 1;
|
|
58
|
-
|
|
62
|
+
let manualPriorityByte = msg.extractPayloadByte(38, 255);
|
|
63
|
+
const isIntellicenterV3 = (sys.controllerType === ControllerType.IntelliCenter && sys.equipment.isIntellicenterV3);
|
|
64
|
+
if (isIntellicenterV3) {
|
|
65
|
+
const v3ManualPriorityByte = msg.extractPayloadByte(28, 255);
|
|
66
|
+
if (v3ManualPriorityByte === 0 || v3ManualPriorityByte === 1) manualPriorityByte = v3ManualPriorityByte;
|
|
67
|
+
const freezeCycleTime = msg.extractPayloadByte(26, 255);
|
|
68
|
+
if (freezeCycleTime !== 255) sys.general.options.freezeCycleTime = freezeCycleTime;
|
|
69
|
+
const freezeOverrideRaw = msg.extractPayloadByte(27, 255);
|
|
70
|
+
if (freezeOverrideRaw !== 255) sys.general.options.freezeOverride = OptionsMessage.decodeFreezeOverride(freezeOverrideRaw);
|
|
71
|
+
}
|
|
72
|
+
if (manualPriorityByte !== 255) sys.general.options.manualPriority = manualPriorityByte === 1;
|
|
59
73
|
sys.general.options.manualHeat = msg.extractPayloadByte(39) === 1;
|
|
60
74
|
let fnTranslateByte = (byte):number => { return (byte & 0x007F) * (((byte & 0x0080) > 0) ? -1 : 1); }
|
|
61
75
|
sys.equipment.tempSensors.setCalibration('water1', fnTranslateByte(msg.extractPayloadByte(3)));
|
|
@@ -79,7 +93,6 @@ export class OptionsMessage {
|
|
|
79
93
|
|
|
80
94
|
// v3.004+: payload layout shifted by 1 byte vs v1.x (timestamp insertion earlier in the packet).
|
|
81
95
|
// 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
96
|
const poolHeatNdx = isIntellicenterV3 ? 19 : 20;
|
|
84
97
|
const poolCoolNdx = isIntellicenterV3 ? 20 : 21;
|
|
85
98
|
const spaHeatNdx = isIntellicenterV3 ? 21 : 22;
|