nodejs-poolcontroller 8.3.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/.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/157_issues.md +101 -0
- package/AGENTS.md +613 -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 +329 -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 +2737 -2664
- package/controller/Errors.ts +181 -181
- package/controller/Lockouts.ts +549 -549
- package/controller/State.ts +3746 -3701
- package/controller/boards/AquaLinkBoard.ts +1175 -1003
- package/controller/boards/BoardFactory.ts +53 -53
- package/controller/boards/EasyTouchBoard.ts +3246 -3202
- package/controller/boards/IntelliCenterBoard.ts +4581 -3899
- package/controller/boards/IntelliComBoard.ts +69 -69
- package/controller/boards/IntelliTouchBoard.ts +382 -382
- package/controller/boards/NixieBoard.ts +1947 -1944
- package/controller/boards/SunTouchBoard.ts +401 -400
- package/controller/boards/SystemBoard.ts +5303 -5268
- package/controller/comms/Comms.ts +1278 -1255
- package/controller/comms/ScreenLogic.ts +1665 -1665
- package/controller/comms/messages/Messages.ts +1627 -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 +250 -210
- package/controller/comms/messages/config/ExternalMessage.ts +1051 -903
- package/controller/comms/messages/config/FeatureMessage.ts +0 -0
- package/controller/comms/messages/config/GeneralMessage.ts +65 -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 +207 -174
- package/controller/comms/messages/config/PumpMessage.ts +427 -421
- 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 +37 -13
- 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 +940 -822
- package/controller/comms/messages/status/HeaterStateMessage.ts +147 -135
- package/controller/comms/messages/status/IntelliChemStateMessage.ts +448 -448
- package/controller/comms/messages/status/IntelliValveStateMessage.ts +36 -36
- package/controller/comms/messages/status/NeptuneModbusStateMessage.ts +217 -0
- 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 +152 -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 +2756 -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 +843 -834
- package/controller/nixie/pumps/Pump.ts +1336 -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 +32 -31
- package/logger/DataLogger.ts +448 -448
- package/logger/Logger.ts +459 -436
- package/package.json +58 -58
- package/sendSocket.js +32 -32
- package/tsconfig.json +26 -25
- package/types/express-multer.d.ts +32 -32
- package/web/Server.ts +1939 -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 +1212 -1053
- package/web/services/config/ConfigSocket.ts +0 -0
- package/web/services/state/State.ts +21 -0
- package/web/services/state/StateSocket.ts +28 -0
- package/web/services/utilities/Utilities.ts +233 -233
|
@@ -1,37 +1,37 @@
|
|
|
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 { state } from "../../../State";
|
|
20
|
-
import { sys, ControllerType } from "../../../Equipment";
|
|
21
|
-
import { logger } from "../../../../logger/Logger";
|
|
22
|
-
|
|
23
|
-
export class IntelliValveStateMessage {
|
|
24
|
-
public static process(msg: Inbound) {
|
|
25
|
-
if (sys.controllerType === ControllerType.Unknown) return;
|
|
26
|
-
// We only want to process the messages that are coming from IntelliValve.
|
|
27
|
-
if (msg.source !== 12) return;
|
|
28
|
-
switch (msg.action) {
|
|
29
|
-
case 82: // This is hail from the valve that says it is not bound yet.
|
|
30
|
-
break;
|
|
31
|
-
default:
|
|
32
|
-
logger.info(`IntelliValve sent an unknown action ${msg.action}`);
|
|
33
|
-
break;
|
|
34
|
-
}
|
|
35
|
-
state.emitEquipmentChanges();
|
|
36
|
-
}
|
|
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 { state } from "../../../State";
|
|
20
|
+
import { sys, ControllerType } from "../../../Equipment";
|
|
21
|
+
import { logger } from "../../../../logger/Logger";
|
|
22
|
+
|
|
23
|
+
export class IntelliValveStateMessage {
|
|
24
|
+
public static process(msg: Inbound) {
|
|
25
|
+
if (sys.controllerType === ControllerType.Unknown) return;
|
|
26
|
+
// We only want to process the messages that are coming from IntelliValve.
|
|
27
|
+
if (msg.source !== 12) return;
|
|
28
|
+
switch (msg.action) {
|
|
29
|
+
case 82: // This is hail from the valve that says it is not bound yet.
|
|
30
|
+
break;
|
|
31
|
+
default:
|
|
32
|
+
logger.info(`IntelliValve sent an unknown action ${msg.action}`);
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
state.emitEquipmentChanges();
|
|
36
|
+
}
|
|
37
37
|
}
|
|
@@ -0,0 +1,217 @@
|
|
|
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 { state, PumpState } from "../../../State";
|
|
20
|
+
import { sys, Pump } from "../../../Equipment";
|
|
21
|
+
import { logger } from "../../../../logger/Logger";
|
|
22
|
+
|
|
23
|
+
type PendingRead = {
|
|
24
|
+
startAddr: number;
|
|
25
|
+
quantity: number;
|
|
26
|
+
requestedAt: number;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export class NeptuneModbusStateMessage {
|
|
30
|
+
private static pendingReads: Map<number, PendingRead[]> = new Map();
|
|
31
|
+
|
|
32
|
+
public static enqueueReadRequest(address: number, startAddr: number, quantity: number) {
|
|
33
|
+
const queue = this.pendingReads.get(address) || [];
|
|
34
|
+
queue.push({ startAddr, quantity, requestedAt: Date.now() });
|
|
35
|
+
this.pendingReads.set(address, queue);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public static clearReadRequests(address: number) {
|
|
39
|
+
this.pendingReads.delete(address);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private static dequeueReadRequest(address: number): PendingRead {
|
|
43
|
+
const queue = this.pendingReads.get(address);
|
|
44
|
+
if (!queue || queue.length === 0) return undefined;
|
|
45
|
+
const request = queue.shift();
|
|
46
|
+
if (queue.length === 0) this.pendingReads.delete(address);
|
|
47
|
+
else this.pendingReads.set(address, queue);
|
|
48
|
+
return request;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private static getNeptunePumpByAddress(address: number): Pump {
|
|
52
|
+
for (let i = 0; i < sys.pumps.length; i++) {
|
|
53
|
+
const pump = sys.pumps.getItemByIndex(i);
|
|
54
|
+
const typeName = sys.board.valueMaps.pumpTypes.getName(pump.type);
|
|
55
|
+
if (typeName === 'neptunemodbus' && pump.address === address) return pump;
|
|
56
|
+
}
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private static toSigned16(value: number): number {
|
|
61
|
+
return value > 0x7FFF ? value - 0x10000 : value;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private static decodeModbusException(code: number): string {
|
|
65
|
+
const modbusExceptions = {
|
|
66
|
+
0x01: 'Illegal function',
|
|
67
|
+
0x02: 'Illegal data address',
|
|
68
|
+
0x03: 'Illegal data value',
|
|
69
|
+
0x04: 'Server device failure',
|
|
70
|
+
0x05: 'Acknowledge',
|
|
71
|
+
0x06: 'Server device busy',
|
|
72
|
+
0x08: 'Memory parity error',
|
|
73
|
+
0x0A: 'Gateway path unavailable',
|
|
74
|
+
0x0B: 'Gateway target failed to respond',
|
|
75
|
+
};
|
|
76
|
+
return modbusExceptions[code] || `Unknown Modbus exception ${code}`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private static processReadInput(msg: Inbound, address: number, pumpState: PumpState) {
|
|
80
|
+
const request = this.dequeueReadRequest(address);
|
|
81
|
+
const byteCount = msg.extractPayloadByte(0, 0);
|
|
82
|
+
if (byteCount <= 0 || (byteCount % 2) !== 0) {
|
|
83
|
+
logger.debug(`NeptuneModbusStateMessage.processReadInput invalid byte count ${byteCount} (Address: ${address})`);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (msg.payload.length < byteCount + 1) {
|
|
87
|
+
logger.debug(`NeptuneModbusStateMessage.processReadInput short payload (Address: ${address})`);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let motorFaultCode = 0;
|
|
92
|
+
let interfaceFaultCode = 0;
|
|
93
|
+
let stoppedState: number = undefined;
|
|
94
|
+
|
|
95
|
+
for (let i = 0; i < byteCount; i += 2) {
|
|
96
|
+
const value = (msg.payload[i + 1] << 8) | msg.payload[i + 2];
|
|
97
|
+
const offset = i / 2;
|
|
98
|
+
const registerAddr = request ? request.startAddr + offset : -1;
|
|
99
|
+
switch (registerAddr) {
|
|
100
|
+
case 0: // 30001 Current Speed
|
|
101
|
+
pumpState.rpm = value;
|
|
102
|
+
break;
|
|
103
|
+
case 3: // 30004 Motor Power
|
|
104
|
+
pumpState.watts = value;
|
|
105
|
+
break;
|
|
106
|
+
case 5: // 30006 Motor Fault Status
|
|
107
|
+
logger.debug(`Neptune motor fault status ${value} (Address: ${address})`);
|
|
108
|
+
break;
|
|
109
|
+
case 6: // 30007 Motor Fault Code
|
|
110
|
+
motorFaultCode = value;
|
|
111
|
+
break;
|
|
112
|
+
case 30: // 30031 Interface Fault State
|
|
113
|
+
logger.debug(`Neptune interface fault state ${value} (Address: ${address})`);
|
|
114
|
+
break;
|
|
115
|
+
case 31: // 30032 Interface Fault Code
|
|
116
|
+
interfaceFaultCode = value;
|
|
117
|
+
break;
|
|
118
|
+
case 113: // 30114 Stopped State bit image
|
|
119
|
+
stoppedState = value;
|
|
120
|
+
break;
|
|
121
|
+
case 119: // 30120 Target shaft speed (signed)
|
|
122
|
+
pumpState.targetSpeed = Math.abs(this.toSigned16(value));
|
|
123
|
+
break;
|
|
124
|
+
default:
|
|
125
|
+
// Keep MVP mapping focused on existing pump state fields.
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const hasFault = motorFaultCode > 0 || interfaceFaultCode > 0;
|
|
131
|
+
if (hasFault) {
|
|
132
|
+
logger.warn(`Neptune fault detected (Address: ${address}) motorFault=${motorFaultCode} interfaceFault=${interfaceFaultCode}`);
|
|
133
|
+
pumpState.status = 16;
|
|
134
|
+
pumpState.driveState = 4;
|
|
135
|
+
pumpState.command = 4;
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (typeof stoppedState !== 'undefined') {
|
|
140
|
+
const isStopped = (stoppedState & 0x01) === 1;
|
|
141
|
+
pumpState.driveState = isStopped ? 0 : 2;
|
|
142
|
+
pumpState.command = isStopped ? 4 : 10;
|
|
143
|
+
pumpState.status = isStopped ? 0 : 1;
|
|
144
|
+
}
|
|
145
|
+
else if (pumpState.rpm > 0) {
|
|
146
|
+
pumpState.driveState = 2;
|
|
147
|
+
pumpState.command = 10;
|
|
148
|
+
pumpState.status = 1;
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
pumpState.driveState = 0;
|
|
152
|
+
pumpState.command = 4;
|
|
153
|
+
pumpState.status = 0;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private static processWriteSingle(msg: Inbound, address: number, pumpState: PumpState) {
|
|
158
|
+
if (msg.payload.length < 4) return;
|
|
159
|
+
const registerAddr = (msg.payload[0] << 8) | msg.payload[1];
|
|
160
|
+
const registerValue = (msg.payload[2] << 8) | msg.payload[3];
|
|
161
|
+
switch (registerAddr) {
|
|
162
|
+
case 0: // 40001 Motor On/Off
|
|
163
|
+
if (registerValue === 0) {
|
|
164
|
+
pumpState.driveState = 0;
|
|
165
|
+
pumpState.command = 4;
|
|
166
|
+
pumpState.status = 0;
|
|
167
|
+
}
|
|
168
|
+
else if (registerValue === 1) {
|
|
169
|
+
pumpState.driveState = 2;
|
|
170
|
+
pumpState.command = 10;
|
|
171
|
+
pumpState.status = 1;
|
|
172
|
+
}
|
|
173
|
+
break;
|
|
174
|
+
case 1: // 40002 Manual speed RPM
|
|
175
|
+
pumpState.rpm = registerValue;
|
|
176
|
+
break;
|
|
177
|
+
default:
|
|
178
|
+
logger.debug(`Neptune write ack for unhandled register ${registerAddr}=${registerValue} (Address: ${address})`);
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
public static process(msg: Inbound) {
|
|
184
|
+
const address = msg.dest;
|
|
185
|
+
const functionCode = msg.action;
|
|
186
|
+
const pumpCfg = this.getNeptunePumpByAddress(address);
|
|
187
|
+
if (typeof pumpCfg === 'undefined') {
|
|
188
|
+
logger.debug(`NeptuneModbusStateMessage.process ignored unconfigured address ${address}`);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const pumpState = state.pumps.getItemById(pumpCfg.id, pumpCfg.isActive === true);
|
|
192
|
+
|
|
193
|
+
if ((functionCode & 0x80) === 0x80) {
|
|
194
|
+
const exceptionCode = msg.extractPayloadByte(0, 0);
|
|
195
|
+
logger.warn(`Neptune Modbus exception response fn=0x${functionCode.toString(16)} code=${exceptionCode} (${this.decodeModbusException(exceptionCode)}) address=${address}`);
|
|
196
|
+
pumpState.status = 16;
|
|
197
|
+
state.emitEquipmentChanges();
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
switch (functionCode) {
|
|
202
|
+
case 0x04: // Read input registers
|
|
203
|
+
this.processReadInput(msg, address, pumpState);
|
|
204
|
+
break;
|
|
205
|
+
case 0x06: // Write single register
|
|
206
|
+
this.processWriteSingle(msg, address, pumpState);
|
|
207
|
+
break;
|
|
208
|
+
case 0x10: // Write multiple registers
|
|
209
|
+
logger.debug(`Neptune write-multiple response (Address: ${address})`);
|
|
210
|
+
break;
|
|
211
|
+
default:
|
|
212
|
+
logger.debug(`NeptuneModbusStateMessage.process unhandled function 0x${functionCode.toString(16)} (Address: ${address})`);
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
state.emitEquipmentChanges();
|
|
216
|
+
}
|
|
217
|
+
}
|
|
File without changes
|