nodejs-poolcontroller 7.6.1 → 8.0.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 -45
- package/.github/ISSUE_TEMPLATE/1-bug-report.yml +84 -0
- package/.github/ISSUE_TEMPLATE/2-docs.md +12 -0
- package/.github/ISSUE_TEMPLATE/3-proposal.md +28 -0
- package/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/CONTRIBUTING.md +74 -74
- package/Changelog +242 -215
- package/Dockerfile +17 -17
- package/Gruntfile.js +40 -40
- package/LICENSE +661 -661
- package/README.md +195 -191
- package/anslq25/MessagesMock.ts +218 -0
- package/anslq25/boards/MockBoardFactory.ts +50 -0
- package/anslq25/boards/MockEasyTouchBoard.ts +696 -0
- package/anslq25/boards/MockSystemBoard.ts +217 -0
- package/anslq25/chemistry/MockChlorinator.ts +75 -0
- package/anslq25/pumps/MockPump.ts +84 -0
- package/app.ts +10 -14
- package/config/Config.ts +26 -8
- package/config/VersionCheck.ts +8 -4
- package/controller/Constants.ts +59 -25
- package/controller/Equipment.ts +2667 -2459
- package/controller/Errors.ts +181 -180
- package/controller/Lockouts.ts +534 -436
- package/controller/State.ts +596 -77
- package/controller/boards/AquaLinkBoard.ts +1003 -0
- package/controller/boards/BoardFactory.ts +53 -45
- package/controller/boards/EasyTouchBoard.ts +3079 -2653
- package/controller/boards/IntelliCenterBoard.ts +3821 -4230
- package/controller/boards/IntelliComBoard.ts +69 -63
- package/controller/boards/IntelliTouchBoard.ts +384 -241
- package/controller/boards/NixieBoard.ts +1871 -1675
- package/controller/boards/SunTouchBoard.ts +393 -0
- package/controller/boards/SystemBoard.ts +5244 -4697
- package/controller/comms/Comms.ts +905 -541
- package/controller/comms/ScreenLogic.ts +1663 -0
- package/controller/comms/messages/Messages.ts +382 -54
- package/controller/comms/messages/config/ChlorinatorMessage.ts +8 -4
- package/controller/comms/messages/config/CircuitGroupMessage.ts +5 -2
- package/controller/comms/messages/config/CircuitMessage.ts +82 -13
- package/controller/comms/messages/config/ConfigMessage.ts +3 -1
- package/controller/comms/messages/config/CoverMessage.ts +2 -1
- package/controller/comms/messages/config/CustomNameMessage.ts +31 -30
- package/controller/comms/messages/config/EquipmentMessage.ts +5 -1
- package/controller/comms/messages/config/ExternalMessage.ts +33 -3
- package/controller/comms/messages/config/FeatureMessage.ts +2 -1
- package/controller/comms/messages/config/GeneralMessage.ts +2 -1
- package/controller/comms/messages/config/HeaterMessage.ts +145 -11
- package/controller/comms/messages/config/IntellichemMessage.ts +2 -1
- package/controller/comms/messages/config/OptionsMessage.ts +16 -27
- package/controller/comms/messages/config/PumpMessage.ts +62 -47
- package/controller/comms/messages/config/RemoteMessage.ts +80 -13
- package/controller/comms/messages/config/ScheduleMessage.ts +390 -347
- package/controller/comms/messages/config/SecurityMessage.ts +2 -1
- package/controller/comms/messages/config/ValveMessage.ts +44 -27
- package/controller/comms/messages/status/ChlorinatorStateMessage.ts +44 -91
- package/controller/comms/messages/status/EquipmentStateMessage.ts +139 -30
- package/controller/comms/messages/status/HeaterStateMessage.ts +135 -86
- package/controller/comms/messages/status/IntelliChemStateMessage.ts +448 -445
- package/controller/comms/messages/status/IntelliValveStateMessage.ts +36 -35
- package/controller/comms/messages/status/PumpStateMessage.ts +92 -2
- package/controller/comms/messages/status/VersionMessage.ts +2 -1
- package/controller/nixie/Nixie.ts +173 -162
- package/controller/nixie/NixieEquipment.ts +104 -103
- package/controller/nixie/bodies/Body.ts +120 -120
- package/controller/nixie/bodies/Filter.ts +135 -135
- package/controller/nixie/chemistry/ChemController.ts +2682 -2498
- package/controller/nixie/chemistry/ChemDoser.ts +806 -0
- package/controller/nixie/chemistry/Chlorinator.ts +367 -314
- package/controller/nixie/circuits/Circuit.ts +402 -248
- package/controller/nixie/heaters/Heater.ts +815 -649
- package/controller/nixie/pumps/Pump.ts +934 -661
- package/controller/nixie/schedules/Schedule.ts +319 -257
- package/controller/nixie/valves/Valve.ts +170 -170
- package/defaultConfig.json +346 -286
- package/logger/DataLogger.ts +448 -448
- package/logger/Logger.ts +38 -9
- package/package.json +60 -56
- package/tsconfig.json +25 -25
- package/web/Server.ts +275 -117
- package/web/bindings/aqualinkD.json +560 -0
- package/web/bindings/homeassistant.json +437 -0
- package/web/bindings/influxDB.json +1066 -1021
- package/web/bindings/mqtt.json +721 -654
- package/web/bindings/mqttAlt.json +746 -684
- 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 -136
- package/web/interfaces/httpInterface.ts +148 -124
- package/web/interfaces/influxInterface.ts +283 -245
- package/web/interfaces/mqttInterface.ts +695 -475
- package/web/interfaces/ruleInterface.ts +87 -0
- package/web/services/config/Config.ts +177 -49
- package/web/services/config/ConfigSocket.ts +2 -1
- package/web/services/state/State.ts +154 -3
- package/web/services/state/StateSocket.ts +69 -18
- package/web/services/utilities/Utilities.ts +232 -42
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -52
- package/config copy.json +0 -300
- package/issue_template.md +0 -52
|
@@ -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
|
+
|
|
19
|
+
import { logger } from "../../logger/Logger";
|
|
20
|
+
import { setTimeout as setTimeoutSync } from 'timers';
|
|
21
|
+
import { Inbound, Outbound, Protocol } from "../../controller/comms/messages/Messages";
|
|
22
|
+
import { byteValueMap, byteValueMaps, SystemBoard } from "../../controller/boards/SystemBoard";
|
|
23
|
+
import { Anslq25, PoolSystem, sys } from "../../controller/Equipment";
|
|
24
|
+
import { ControllerType, utils } from "../../controller/Constants";
|
|
25
|
+
import { conn } from "../../controller/comms/Comms";
|
|
26
|
+
import { MockEasyTouch } from "./MockEasyTouchBoard";
|
|
27
|
+
|
|
28
|
+
export class MockSystemBoard {
|
|
29
|
+
public valueMaps: byteValueMaps = new byteValueMaps();
|
|
30
|
+
protected _statusTimer: NodeJS.Timeout;
|
|
31
|
+
protected _statusCheckRef: number = 0;
|
|
32
|
+
protected _statusInterval: number = 5000;
|
|
33
|
+
constructor(system: PoolSystem) {
|
|
34
|
+
// sys.anslq25.portId = 0; // pass this in.
|
|
35
|
+
setTimeout(() => {
|
|
36
|
+
this.processStatusAsync().then(() => { });
|
|
37
|
+
}, 5000);
|
|
38
|
+
}
|
|
39
|
+
public expansionBoards: byteValueMap = new byteValueMap();
|
|
40
|
+
public get statusInterval(): number { return this._statusInterval };
|
|
41
|
+
public system: MockSystemCommands = new MockSystemCommands(this);
|
|
42
|
+
public circuits: MockCircuitCommands = new MockCircuitCommands(this);
|
|
43
|
+
public schedules: MockScheduleCommands = new MockScheduleCommands(this);
|
|
44
|
+
public heaters: MockHeaterCommands = new MockHeaterCommands(this);
|
|
45
|
+
public valves: MockValveCommands = new MockValveCommands(this);
|
|
46
|
+
public remotes: MockRemoteCommands = new MockRemoteCommands(this);
|
|
47
|
+
public pumps: MockPumpCommands = new MockPumpCommands(this);
|
|
48
|
+
public static convertOutbound(outboundMsg: Outbound) { };
|
|
49
|
+
public async sendAsync(msg: Outbound){
|
|
50
|
+
return await msg.sendAsync();
|
|
51
|
+
// is the controller on a real/physical port or a mock port?
|
|
52
|
+
/* let port = conn.findPortById(sys.anslq25.portId);
|
|
53
|
+
if (port.mockPort) {
|
|
54
|
+
let inbound = new Inbound();
|
|
55
|
+
inbound.protocol = msg.protocol;
|
|
56
|
+
inbound.header = msg.header;
|
|
57
|
+
inbound.payload = msg.payload;
|
|
58
|
+
inbound.term = msg.term;
|
|
59
|
+
inbound.portId = msg.portId;
|
|
60
|
+
// don't need to wait for packet to process
|
|
61
|
+
setTimeout(()=>{conn.sendMockPacket(inbound)}, 10);
|
|
62
|
+
return Promise.resolve();
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
return await msg.sendAsync();
|
|
66
|
+
} */
|
|
67
|
+
}
|
|
68
|
+
public process(msg: Inbound): Outbound { return new Outbound(Protocol.Broadcast,0,0,0,[]); }
|
|
69
|
+
protected killStatusCheck() {
|
|
70
|
+
if (typeof this._statusTimer !== 'undefined' && this._statusTimer) clearTimeout(this._statusTimer);
|
|
71
|
+
this._statusTimer = undefined;
|
|
72
|
+
this._statusCheckRef = 0;
|
|
73
|
+
}
|
|
74
|
+
public suspendStatus(bSuspend: boolean) {
|
|
75
|
+
// The way status suspension works is by using a reference value that is incremented and decremented
|
|
76
|
+
// the status check is only performed when the reference value is 0. So suspending the status check 3 times and un-suspending
|
|
77
|
+
// it 2 times will still result in the status check being suspended. This method also ensures the reference never falls below 0.
|
|
78
|
+
if (bSuspend) this._statusCheckRef++;
|
|
79
|
+
else this._statusCheckRef = Math.max(0, this._statusCheckRef - 1);
|
|
80
|
+
if (this._statusCheckRef > 1) logger.verbose(`Suspending ANSLQ25 status check: ${bSuspend} -- ${this._statusCheckRef}`);
|
|
81
|
+
}
|
|
82
|
+
public async setAnslq25Async(data: any): Promise<Anslq25> {
|
|
83
|
+
let self = this;
|
|
84
|
+
try {
|
|
85
|
+
this.suspendStatus(true);
|
|
86
|
+
// if (typeof data.isActive === 'undefined') return Promise.reject(`Mock System Board: No isActive flag provided.`);
|
|
87
|
+
if (typeof data.anslq25portId === 'undefined') return Promise.reject(new Error(`Mock System Board: No portId provided.`));
|
|
88
|
+
if (typeof data.anslq25ControllerType === 'undefined') return Promise.reject(new Error(`Mock System Board: No controller type provided.`));
|
|
89
|
+
if (typeof data.anslq25model === 'undefined') return Promise.reject(new Error(`Mock System Board: No model provided.`));
|
|
90
|
+
//for (let i = 1; i <= )
|
|
91
|
+
let isActive = true; // utils.makeBool(data.isActive);
|
|
92
|
+
let portId = parseInt(data.anslq25portId, 10);
|
|
93
|
+
let port = conn.findPortById(portId);
|
|
94
|
+
if (typeof port === 'undefined') return Promise.reject(new Error(`Mock System Board: Invalid portId provided.`));
|
|
95
|
+
if (portId === 0) return Promise.reject(new Error(`Please choose a port other than the primary port.`));
|
|
96
|
+
let mockControllerType = data.anslq25ControllerType;
|
|
97
|
+
let model = parseInt(data.anslq25model, 10);
|
|
98
|
+
let broadcastComms = data.broadcastComms;
|
|
99
|
+
if (typeof broadcastComms === 'undefined') return Promise.reject(new Error(`A value for broadcast comms must be provided.`));
|
|
100
|
+
sys.anslq25.portId = portId;
|
|
101
|
+
sys.anslq25.broadcastComms = broadcastComms;
|
|
102
|
+
switch (mockControllerType) {
|
|
103
|
+
case ControllerType.EasyTouch:{
|
|
104
|
+
sys.anslq25ControllerType = ControllerType.EasyTouch;
|
|
105
|
+
// (sys.anslq25Board as MockEasyTouch).initExpansionModules(model);
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
default: {
|
|
109
|
+
logger.warn(`No ANSLQ25 Mock Board definiton yet for: ${mockControllerType}`);
|
|
110
|
+
return Promise.reject(new Error(`No ANSLQ25 Mock Board definiton yet for: ${mockControllerType}`));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
sys.anslq25.isActive = isActive;
|
|
114
|
+
sys.anslq25.model = model;
|
|
115
|
+
|
|
116
|
+
} catch (err) {
|
|
117
|
+
logger.error(`Error changing port id: ${err.message}`);
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
this.suspendStatus(false);
|
|
121
|
+
this._statusTimer = setTimeoutSync(async () => await self.processStatusAsync(), this.statusInterval);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
public async deleteAnslq25Async(data: any) {
|
|
125
|
+
try {
|
|
126
|
+
|
|
127
|
+
this.killStatusCheck();
|
|
128
|
+
this.closeAsync();
|
|
129
|
+
sys.anslq25.isActive = false;
|
|
130
|
+
sys.anslq25.portId = undefined;
|
|
131
|
+
sys.anslq25.model = undefined;
|
|
132
|
+
sys.anslq25ControllerType = ControllerType.None;
|
|
133
|
+
}
|
|
134
|
+
catch (err){
|
|
135
|
+
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
this.suspendStatus(false);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
public async processStatusAsync() {
|
|
144
|
+
let self = this;
|
|
145
|
+
try {
|
|
146
|
+
if (this._statusCheckRef > 0) return;
|
|
147
|
+
this.suspendStatus(true);
|
|
148
|
+
|
|
149
|
+
await sys.anslq25Board.system.sendStatusAsync();
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
logger.error(`Error running mock processStatusAsync: ${err}`);
|
|
153
|
+
}
|
|
154
|
+
finally {
|
|
155
|
+
this.suspendStatus(false);
|
|
156
|
+
if (sys.anslq25.isActive){
|
|
157
|
+
if (this.statusInterval > 0) this._statusTimer = setTimeoutSync(async () => await self.processStatusAsync(), this.statusInterval);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// public async setPortId(portId: number) {
|
|
163
|
+
// let self = this;
|
|
164
|
+
// try {
|
|
165
|
+
// this.suspendStatus(true);
|
|
166
|
+
// sys.anslq25.portId = portId;
|
|
167
|
+
|
|
168
|
+
// } catch (err) {
|
|
169
|
+
// logger.error(`Error changing port id: ${err.message}`);
|
|
170
|
+
// }
|
|
171
|
+
// finally {
|
|
172
|
+
// this.suspendStatus(false);
|
|
173
|
+
// this._statusTimer = setTimeoutSync(async () => await self.processStatusAsync(), this.statusInterval);
|
|
174
|
+
// }
|
|
175
|
+
// }
|
|
176
|
+
public async closeAsync() {
|
|
177
|
+
try {
|
|
178
|
+
}
|
|
179
|
+
catch (err) { logger.error(err); }
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
export class MockBoardCommands {
|
|
183
|
+
protected mockBoard: MockSystemBoard = null;
|
|
184
|
+
constructor(parent: MockSystemBoard) { this.mockBoard = parent; }
|
|
185
|
+
}
|
|
186
|
+
export class MockSystemCommands extends MockBoardCommands {
|
|
187
|
+
public sendAck(msg:Inbound) { };
|
|
188
|
+
public async processDateTimeAsync(msg: Inbound){ };
|
|
189
|
+
public async processCustomNameAsync(msg: Inbound){ };
|
|
190
|
+
public async processSettingsAsync(msg: Inbound){ };
|
|
191
|
+
public async sendStatusAsync() { };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export class MockCircuitCommands extends MockBoardCommands {
|
|
195
|
+
public async processCircuitAsync( msg: Inbound) { };
|
|
196
|
+
public async processLightGroupAsync( msg: Inbound) { };
|
|
197
|
+
}
|
|
198
|
+
export class MockScheduleCommands extends MockBoardCommands {
|
|
199
|
+
public async processScheduleAsync( msg: Inbound) { };
|
|
200
|
+
}
|
|
201
|
+
export class MockHeaterCommands extends MockBoardCommands {
|
|
202
|
+
public async processHeatModesAsync(msg: Inbound) { };
|
|
203
|
+
public async processHeaterConfigAsync(msg: Inbound) { };
|
|
204
|
+
}
|
|
205
|
+
export class MockValveCommands extends MockBoardCommands {
|
|
206
|
+
public async processValveOptionsAsync(msg: Inbound) { };
|
|
207
|
+
public async processValveAssignmentsAsync(msg: Inbound) { };
|
|
208
|
+
}
|
|
209
|
+
export class MockRemoteCommands extends MockBoardCommands {
|
|
210
|
+
public async processIS4IS10RemoteAsync(msg: Inbound) { };
|
|
211
|
+
public async processQuickTouchRemoteAsync(msg: Inbound) { };
|
|
212
|
+
public async processSpaCommandRemoteAsync(msg: Inbound) { };
|
|
213
|
+
}
|
|
214
|
+
export class MockPumpCommands extends MockBoardCommands {
|
|
215
|
+
public async processPumpConfigAsync(msg: Inbound) { };
|
|
216
|
+
public async processHighSpeedCircuitsAsync(msg: Inbound) { };
|
|
217
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { logger } from "../../logger/Logger";
|
|
2
|
+
import { Inbound, Outbound } from "../../controller/comms/messages/Messages";
|
|
3
|
+
import { conn } from "../../controller/comms/Comms";
|
|
4
|
+
|
|
5
|
+
export class MockChlorinator {
|
|
6
|
+
constructor(){}
|
|
7
|
+
|
|
8
|
+
public process(inbound: Inbound){
|
|
9
|
+
let response: Outbound = Outbound.create({
|
|
10
|
+
portId: inbound.portId,
|
|
11
|
+
protocol: inbound.protocol,
|
|
12
|
+
dest: 0
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
switch (inbound.action){
|
|
16
|
+
case 0: // Set control OCP->Chlorinator: [16,2,80,0][0][98,16,3]
|
|
17
|
+
this.chlorSetControl(inbound, response);
|
|
18
|
+
case 17: // OCP->Chlorinator set output. [16,2,80,17][15][130,16,3]
|
|
19
|
+
this.chlorSetOutput(inbound, response);
|
|
20
|
+
case 19: // iChlor keep alive(?) [16, 2, 80, 19][117, 16, 3]
|
|
21
|
+
// need response
|
|
22
|
+
break;
|
|
23
|
+
case 20: // OCP->Chlorinator Get model [16,2,80,20][0][118,16,3]
|
|
24
|
+
this.chlorGetModel(inbound, response);
|
|
25
|
+
default:
|
|
26
|
+
logger.info(`No mock chlorinator response for ${inbound.toShortPacket()} `);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public async chlorSetControl(inbound: Inbound, response: Outbound){
|
|
31
|
+
/*
|
|
32
|
+
{"port":0,"id":42633,"valid":true,"dir":"out","proto":"chlorinator","pkt":[[],[], [16,2,80,0], [0],[98,16,3]],"ts":"2022-07-19T21:45:59.959-0700"}
|
|
33
|
+
{"port":0,"id":42634,"valid":true,"dir":"in","proto":"chlorinator","for":[42633],"pkt":[[],[],[16,2,0,1],[0,0],[19,16,3]],"ts": "2022-07-19T21:45:59.999-0700"} */
|
|
34
|
+
try {
|
|
35
|
+
|
|
36
|
+
response.action = 1;
|
|
37
|
+
response.appendPayloadBytes(0, 2);
|
|
38
|
+
await response.sendAsync()
|
|
39
|
+
}
|
|
40
|
+
catch (err){
|
|
41
|
+
logger.error(`Error sending mock chlor packet ${response.toPacket}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
public chlorSetOutput(inbound: Inbound, response: Outbound){
|
|
45
|
+
/*
|
|
46
|
+
{"port":0,"id":42639,"valid":true,"dir":"out","proto":"chlorinator","pkt":[[],[], [16,2,80,17], [100],[215,16,3]],"ts":"2022-07-19T21:46:00.302-0700"}
|
|
47
|
+
{"port":0,"id":42640,"valid":true,"dir":"in","proto":"chlorinator","for":[42639],"pkt":[[],[],[16,2,0,18],[78,128],[242,16,3]],"ts": "2022-07-19T21:46:00.341-0700"} */
|
|
48
|
+
response.action = 18;
|
|
49
|
+
response.appendPayloadBytes(0, 2);
|
|
50
|
+
// ideal high = 4500 = 90 * 50; ideal low = 2800 = 56 * 50
|
|
51
|
+
response.setPayloadByte(0, this.random(90-56, true)+56, 75)
|
|
52
|
+
response.setPayloadByte(1, 128);
|
|
53
|
+
conn.queueSendMessage(response);
|
|
54
|
+
}
|
|
55
|
+
public chlorGetModel(inbound: Inbound, response: Outbound){
|
|
56
|
+
/*
|
|
57
|
+
{"port":0,"id":42645,"valid":true,"dir":"out","proto":"chlorinator","pkt":[[],[], [16,2,80,20], [0],[118,16,3]],"ts":"2022-07-19T21:46:00.645-0700"}
|
|
58
|
+
{"port":0,"id":42646,"valid":true,"dir":"in","proto":"chlorinator","for":[42645],"pkt":[[],[],[16,2,0,3],[0,73,110,116,101,108,108,105,99,104,108,111,114,45,45,54,48],[190,16,3]],"ts": "2022-07-19T21:46:00.700-0700"} */
|
|
59
|
+
response.action = 3;
|
|
60
|
+
response.appendPayloadBytes(0, 17);
|
|
61
|
+
response.insertPayloadString(1, 'INTELLICHLOR--60');
|
|
62
|
+
conn.queueSendMessage(response);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private random(bounds: number, onlyPositive: boolean = false){
|
|
66
|
+
let rand = Math.random() * bounds;
|
|
67
|
+
if (!onlyPositive) {
|
|
68
|
+
if (Math.random()<=.5) rand = rand * -1;
|
|
69
|
+
}
|
|
70
|
+
return rand;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export var mockChlor: MockChlorinator = new MockChlorinator();
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { sys } from "../../controller/Equipment";
|
|
2
|
+
import { PumpState, state } from "../../controller/State";
|
|
3
|
+
import { Outbound } from "../../controller/comms/messages/Messages";
|
|
4
|
+
import { conn } from "controller/comms/Comms";
|
|
5
|
+
|
|
6
|
+
export class MockPump {
|
|
7
|
+
constructor(){}
|
|
8
|
+
|
|
9
|
+
public process(outboundMsg: Outbound){
|
|
10
|
+
let response: Outbound = Outbound.create({
|
|
11
|
+
portId: outboundMsg.portId,
|
|
12
|
+
protocol: outboundMsg.protocol
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
switch (outboundMsg.action){
|
|
16
|
+
case 7:
|
|
17
|
+
this.pumpStatus(outboundMsg, response);
|
|
18
|
+
default:
|
|
19
|
+
this.pumpAck(outboundMsg, response);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public pumpStatus(outboundMsg: Outbound, response: Outbound){
|
|
24
|
+
let pState:PumpState = state.pumps.getItemById(outboundMsg.dest - 96);
|
|
25
|
+
let pt = sys.board.valueMaps.pumpTypes.get(pState.type);
|
|
26
|
+
response.action = 7;
|
|
27
|
+
response.source = outboundMsg.dest;
|
|
28
|
+
response.dest = outboundMsg.source;
|
|
29
|
+
response.appendPayloadBytes(0, 15);
|
|
30
|
+
response.setPayloadByte(0, pState.command, 2);
|
|
31
|
+
response.setPayloadByte(1, pState.mode, 0);
|
|
32
|
+
response.setPayloadByte(2, pState.driveState, 2);
|
|
33
|
+
let watts = 0;
|
|
34
|
+
if (Math.max(pState.rpm, pState.flow) > 0){
|
|
35
|
+
if (pState.rpm > 0) watts = pState.rpm/pt.maxSpeed * 2000 + this.random(100);
|
|
36
|
+
else if (pState.flow > 0) watts = pState.flow/pt.maxFlow * 2000 + this.random(100);
|
|
37
|
+
else //ss, ds, etc
|
|
38
|
+
watts = 2000 + this.random(250);
|
|
39
|
+
}
|
|
40
|
+
response.setPayloadByte(3, Math.floor(watts / 256), 0);
|
|
41
|
+
response.setPayloadByte(4, watts % 256, 0);
|
|
42
|
+
response.setPayloadByte(5, Math.floor(pState.rpm / 256), 0);
|
|
43
|
+
response.setPayloadByte(6, pState.rpm % 256, 0);
|
|
44
|
+
response.setPayloadByte(7, pState.flow, 0);
|
|
45
|
+
response.setPayloadByte(8, pState.ppc, 0);
|
|
46
|
+
// 9, 10 = unknown
|
|
47
|
+
// 11, 12 = Status code;
|
|
48
|
+
response.setPayloadByte(11, Math.floor(pState.status / 256), 0);
|
|
49
|
+
response.setPayloadByte(12, pState.status % 256, 1);
|
|
50
|
+
let time = new Date();
|
|
51
|
+
response.setPayloadByte(13, time.getHours() * 60);
|
|
52
|
+
response.setPayloadByte(14, time.getMinutes());
|
|
53
|
+
|
|
54
|
+
conn.queueSendMessage(response);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
public pumpAck(outboundMsg: Outbound, response: Outbound){
|
|
58
|
+
response.action = outboundMsg.action;
|
|
59
|
+
response.source = outboundMsg.dest;
|
|
60
|
+
response.dest = outboundMsg.source;
|
|
61
|
+
switch (outboundMsg.action){
|
|
62
|
+
case 1:
|
|
63
|
+
case 10: {
|
|
64
|
+
response.appendPayloadByte(outboundMsg.payload[2]);
|
|
65
|
+
response.appendPayloadByte(outboundMsg.payload[3]);
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
default:
|
|
69
|
+
response.appendPayloadByte(outboundMsg.payload[0]);
|
|
70
|
+
}
|
|
71
|
+
conn.queueSendMessage(response);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private random(bounds: number, onlyPositive: boolean = false){
|
|
75
|
+
let rand = Math.random() * bounds;
|
|
76
|
+
if (!onlyPositive) {
|
|
77
|
+
if (Math.random()<=.5) rand = rand * -1;
|
|
78
|
+
}
|
|
79
|
+
return rand;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export var mockPump: MockPump = new MockPump();
|
package/app.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* nodejs-poolController. An application to control pool equipment.
|
|
2
|
-
Copyright (C) 2016, 2017, 2018, 2019, 2020
|
|
2
|
+
Copyright (C) 2016, 2017, 2018, 2019, 2020, 2021, 2022.
|
|
3
|
+
Russell Goldin, tagyoureit. russ.goldin@gmail.com
|
|
3
4
|
|
|
4
5
|
This program is free software: you can redistribute it and/or modify
|
|
5
6
|
it under the terms of the GNU Affero General Public License as
|
|
@@ -15,36 +16,31 @@ You should have received a copy of the GNU Affero General Public License
|
|
|
15
16
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
16
17
|
*/
|
|
17
18
|
// add source map support for .js to .ts files
|
|
18
|
-
require('source-map-support').install();
|
|
19
|
+
//require('source-map-support').install();
|
|
20
|
+
import 'source-map-support/register';
|
|
19
21
|
|
|
20
22
|
import { logger } from "./logger/Logger";
|
|
21
23
|
import { config } from "./config/Config";
|
|
22
24
|
import { conn } from "./controller/comms/Comms";
|
|
23
|
-
import { sys
|
|
25
|
+
import { sys } from "./controller/Equipment";
|
|
24
26
|
|
|
25
27
|
import { state } from "./controller/State";
|
|
26
28
|
import { webApp } from "./web/Server";
|
|
27
29
|
import * as readline from 'readline';
|
|
30
|
+
import { sl } from './controller/comms/ScreenLogic'
|
|
28
31
|
|
|
29
32
|
export async function initAsync() {
|
|
30
33
|
try {
|
|
31
34
|
await config.init();
|
|
32
35
|
await logger.init();
|
|
33
|
-
await conn.init();
|
|
34
36
|
await sys.init();
|
|
35
37
|
await state.init();
|
|
36
38
|
await webApp.init();
|
|
39
|
+
await conn.initAsync();
|
|
37
40
|
await sys.start();
|
|
38
41
|
await webApp.initAutoBackup();
|
|
42
|
+
await sl.openAsync();
|
|
39
43
|
} catch (err) { console.log(`Error Initializing nodejs-PoolController ${err.message}`); }
|
|
40
|
-
//return Promise.resolve()
|
|
41
|
-
// .then(function () { config.init(); })
|
|
42
|
-
// .then(function () { logger.init(); })
|
|
43
|
-
// .then(function () { conn.init(); })
|
|
44
|
-
// .then(function () { sys.init(); })
|
|
45
|
-
// .then(function () { state.init(); })
|
|
46
|
-
// .then(function () { webApp.init(); })
|
|
47
|
-
// .then(function () { sys.start(); });
|
|
48
44
|
}
|
|
49
45
|
|
|
50
46
|
export async function startPacketCapture(bResetLogs: boolean) {
|
|
@@ -70,13 +66,13 @@ export async function stopPacketCaptureAsync() {
|
|
|
70
66
|
export async function stopAsync(): Promise<void> {
|
|
71
67
|
try {
|
|
72
68
|
console.log('Shutting down open processes');
|
|
73
|
-
// await sys.board.virtualPumpControllers.stopAsync();
|
|
74
69
|
await webApp.stopAutoBackup();
|
|
75
70
|
await sys.stopAsync();
|
|
76
71
|
await state.stopAsync();
|
|
77
72
|
await conn.stopAsync();
|
|
73
|
+
await sl.closeAsync();
|
|
78
74
|
await webApp.stopAsync();
|
|
79
|
-
config.
|
|
75
|
+
await config.updateAsync();
|
|
80
76
|
await logger.stopAsync();
|
|
81
77
|
// RKS: Uncomment below to see the shutdown process
|
|
82
78
|
//await new Promise<void>((resolve, reject) => { setTimeout(() => { resolve(); }, 20000); });
|
package/config/Config.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* nodejs-poolController. An application to control pool equipment.
|
|
2
|
-
Copyright (C) 2016, 2017, 2018, 2019, 2020
|
|
2
|
+
Copyright (C) 2016, 2017, 2018, 2019, 2020, 2021, 2022.
|
|
3
|
+
Russell Goldin, tagyoureit. russ.goldin@gmail.com
|
|
3
4
|
|
|
4
5
|
This program is free software: you can redistribute it and/or modify
|
|
5
6
|
it under the terms of the GNU Affero General Public License as
|
|
@@ -20,6 +21,7 @@ import { EventEmitter } from 'events';
|
|
|
20
21
|
const extend = require("extend");
|
|
21
22
|
import { logger } from "../logger/Logger";
|
|
22
23
|
import { utils } from "../controller/Constants";
|
|
24
|
+
import { setTimeout } from 'timers/promises';
|
|
23
25
|
class Config {
|
|
24
26
|
private cfgPath: string;
|
|
25
27
|
private _cfg: any;
|
|
@@ -39,7 +41,7 @@ class Config {
|
|
|
39
41
|
const packageJson = JSON.parse(fs.readFileSync(path.join(process.cwd(), "/package.json"), "utf8").trim());
|
|
40
42
|
this._cfg = extend(true, {}, def, this._cfg, { appVersion: packageJson.version });
|
|
41
43
|
this._isInitialized = true;
|
|
42
|
-
this.
|
|
44
|
+
this.updateAsync((err) => {
|
|
43
45
|
if (typeof err === 'undefined') {
|
|
44
46
|
fs.watch(this.cfgPath, (event, fileName) => {
|
|
45
47
|
if (fileName && event === 'change') {
|
|
@@ -55,7 +57,7 @@ class Config {
|
|
|
55
57
|
}
|
|
56
58
|
});
|
|
57
59
|
}
|
|
58
|
-
else
|
|
60
|
+
else return Promise.reject(err);
|
|
59
61
|
});
|
|
60
62
|
this._isLoading = false;
|
|
61
63
|
this.getEnvVariables();
|
|
@@ -65,7 +67,7 @@ class Config {
|
|
|
65
67
|
throw err;
|
|
66
68
|
}
|
|
67
69
|
}
|
|
68
|
-
public
|
|
70
|
+
public async updateAsync(callback?: (err?) => void) {
|
|
69
71
|
// Don't overwrite the configuration if we failed during the initialization.
|
|
70
72
|
try {
|
|
71
73
|
if (!this._isInitialized) {
|
|
@@ -78,7 +80,8 @@ class Config {
|
|
|
78
80
|
JSON.stringify(this._cfg, undefined, 2)
|
|
79
81
|
);
|
|
80
82
|
if (typeof callback === 'function') callback();
|
|
81
|
-
setTimeout(
|
|
83
|
+
await setTimeout(2000);
|
|
84
|
+
this._isLoading = false;
|
|
82
85
|
}
|
|
83
86
|
catch (err) {
|
|
84
87
|
logger.error("Error writing configuration file %s", err);
|
|
@@ -86,6 +89,20 @@ class Config {
|
|
|
86
89
|
|
|
87
90
|
}
|
|
88
91
|
}
|
|
92
|
+
public removeSection(section: string) {
|
|
93
|
+
let c = this._cfg;
|
|
94
|
+
if (section.indexOf('.') !== -1) {
|
|
95
|
+
let arr = section.split('.');
|
|
96
|
+
for (let i = 0; i < arr.length - 1; i++) {
|
|
97
|
+
if (typeof c[arr[i]] === 'undefined')
|
|
98
|
+
c[arr[i]] = {};
|
|
99
|
+
c = c[arr[i]];
|
|
100
|
+
}
|
|
101
|
+
section = arr[arr.length - 1];
|
|
102
|
+
}
|
|
103
|
+
if(typeof c[section] !== 'undefined') delete c[section];
|
|
104
|
+
this.updateAsync();
|
|
105
|
+
}
|
|
89
106
|
public setSection(section: string, val) {
|
|
90
107
|
let c = this._cfg;
|
|
91
108
|
if (section.indexOf('.') !== -1) {
|
|
@@ -98,7 +115,7 @@ class Config {
|
|
|
98
115
|
section = arr[arr.length - 1];
|
|
99
116
|
}
|
|
100
117
|
c[section] = val;
|
|
101
|
-
this.
|
|
118
|
+
this.updateAsync();
|
|
102
119
|
}
|
|
103
120
|
// RKS: 09-21-21 - We are counting on the return from this being immutable. A copy of the data
|
|
104
121
|
// should always be returned here.
|
|
@@ -121,6 +138,7 @@ class Config {
|
|
|
121
138
|
this.ensurePath(baseDir + '/logs/');
|
|
122
139
|
this.ensurePath(baseDir + '/data/');
|
|
123
140
|
this.ensurePath(baseDir + '/backups/');
|
|
141
|
+
this.ensurePath(baseDir + '/web/bindings/custom/')
|
|
124
142
|
// this.ensurePath(baseDir + '/replay/');
|
|
125
143
|
//setTimeout(() => { config.update(); }, 100);
|
|
126
144
|
}
|
|
@@ -138,7 +156,7 @@ class Config {
|
|
|
138
156
|
for (var i in interfaces) {
|
|
139
157
|
if (interfaces[i].uuid === obj.uuid) {
|
|
140
158
|
interfaces[i] = obj;
|
|
141
|
-
this.
|
|
159
|
+
this.updateAsync();
|
|
142
160
|
return {[i]: interfaces[i]};
|
|
143
161
|
}
|
|
144
162
|
}
|
|
@@ -171,7 +189,7 @@ class Config {
|
|
|
171
189
|
this._cfg.controller.comms.netPort = env.POOL_NET_PORT;
|
|
172
190
|
bUpdate = true;
|
|
173
191
|
}
|
|
174
|
-
if (bUpdate) this.
|
|
192
|
+
if (bUpdate) this.updateAsync();
|
|
175
193
|
}
|
|
176
194
|
}
|
|
177
195
|
export const config: Config = new Config();
|
package/config/VersionCheck.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* nodejs-poolController. An application to control pool equipment.
|
|
2
|
-
Copyright (C) 2016, 2017, 2018, 2019, 2020
|
|
2
|
+
Copyright (C) 2016, 2017, 2018, 2019, 2020, 2021, 2022.
|
|
3
|
+
Russell Goldin, tagyoureit. russ.goldin@gmail.com
|
|
3
4
|
|
|
4
5
|
This program is free software: you can redistribute it and/or modify
|
|
5
6
|
it under the terms of the GNU Affero General Public License as
|
|
@@ -45,7 +46,7 @@ class VersionCheck {
|
|
|
45
46
|
let out: string;
|
|
46
47
|
if (typeof env.SOURCE_BRANCH !== 'undefined')
|
|
47
48
|
{
|
|
48
|
-
out =
|
|
49
|
+
out = env.SOURCE_BRANCH // check for docker variable
|
|
49
50
|
}
|
|
50
51
|
else {
|
|
51
52
|
let res = execSync('git rev-parse --abbrev-ref HEAD', { stdio: 'pipe' });
|
|
@@ -69,7 +70,7 @@ class VersionCheck {
|
|
|
69
70
|
let out: string;
|
|
70
71
|
if (typeof env.SOURCE_COMMIT !== 'undefined')
|
|
71
72
|
{
|
|
72
|
-
out =
|
|
73
|
+
out = env.SOURCE_COMMIT; // check for docker variable
|
|
73
74
|
}
|
|
74
75
|
else {
|
|
75
76
|
let res = execSync('git rev-parse HEAD', { stdio: 'pipe' });
|
|
@@ -124,7 +125,7 @@ class VersionCheck {
|
|
|
124
125
|
if (this.redirects >= 20) return Promise.reject(`Too many redirects.`)
|
|
125
126
|
return new Promise<string>((resolve, reject) => {
|
|
126
127
|
try {
|
|
127
|
-
https.request(url, options, async res => {
|
|
128
|
+
let req = https.request(url, options, async res => {
|
|
128
129
|
if (res.statusCode > 300 && res.statusCode < 400 && res.headers.location) await this.getLatestRelease(res.headers.location);
|
|
129
130
|
let data = '';
|
|
130
131
|
res.on('data', d => { data += d; });
|
|
@@ -137,6 +138,9 @@ class VersionCheck {
|
|
|
137
138
|
})
|
|
138
139
|
})
|
|
139
140
|
.end();
|
|
141
|
+
req.on('error', (err) => {
|
|
142
|
+
logger.error(`Error getting Github API latest release. ${err.message}`)
|
|
143
|
+
})
|
|
140
144
|
}
|
|
141
145
|
catch (err) {
|
|
142
146
|
logger.error('Error contacting Github for latest published release: ' + err);
|