nodejs-poolcontroller 7.7.0 → 8.0.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 +26 -35
- package/Changelog +22 -0
- package/README.md +7 -3
- 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 +13 -9
- package/config/VersionCheck.ts +6 -2
- package/controller/Constants.ts +58 -25
- package/controller/Equipment.ts +225 -41
- package/controller/Errors.ts +2 -1
- package/controller/Lockouts.ts +34 -2
- package/controller/State.ts +491 -48
- package/controller/boards/AquaLinkBoard.ts +6 -3
- package/controller/boards/BoardFactory.ts +5 -1
- package/controller/boards/EasyTouchBoard.ts +1971 -1751
- package/controller/boards/IntelliCenterBoard.ts +1311 -1688
- package/controller/boards/IntelliComBoard.ts +7 -1
- package/controller/boards/IntelliTouchBoard.ts +153 -42
- package/controller/boards/NixieBoard.ts +209 -66
- package/controller/boards/SunTouchBoard.ts +393 -0
- package/controller/boards/SystemBoard.ts +1862 -1543
- package/controller/comms/Comms.ts +539 -138
- package/controller/comms/ScreenLogic.ts +1663 -0
- package/controller/comms/messages/Messages.ts +242 -60
- package/controller/comms/messages/config/ChlorinatorMessage.ts +4 -3
- package/controller/comms/messages/config/CircuitGroupMessage.ts +5 -2
- package/controller/comms/messages/config/CircuitMessage.ts +81 -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 +2 -1
- 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 +3 -1
- package/controller/comms/messages/config/IntellichemMessage.ts +2 -1
- package/controller/comms/messages/config/OptionsMessage.ts +12 -6
- package/controller/comms/messages/config/PumpMessage.ts +9 -12
- package/controller/comms/messages/config/RemoteMessage.ts +80 -13
- package/controller/comms/messages/config/ScheduleMessage.ts +43 -3
- package/controller/comms/messages/config/SecurityMessage.ts +2 -1
- package/controller/comms/messages/config/ValveMessage.ts +43 -26
- package/controller/comms/messages/status/ChlorinatorStateMessage.ts +8 -7
- package/controller/comms/messages/status/EquipmentStateMessage.ts +93 -20
- package/controller/comms/messages/status/HeaterStateMessage.ts +24 -5
- package/controller/comms/messages/status/IntelliChemStateMessage.ts +7 -4
- package/controller/comms/messages/status/IntelliValveStateMessage.ts +2 -1
- package/controller/comms/messages/status/PumpStateMessage.ts +72 -4
- package/controller/comms/messages/status/VersionMessage.ts +2 -1
- package/controller/nixie/Nixie.ts +15 -4
- package/controller/nixie/NixieEquipment.ts +1 -0
- package/controller/nixie/chemistry/ChemController.ts +300 -129
- package/controller/nixie/chemistry/ChemDoser.ts +806 -0
- package/controller/nixie/chemistry/Chlorinator.ts +133 -129
- package/controller/nixie/circuits/Circuit.ts +171 -30
- package/controller/nixie/heaters/Heater.ts +337 -173
- package/controller/nixie/pumps/Pump.ts +264 -236
- package/controller/nixie/schedules/Schedule.ts +9 -3
- package/defaultConfig.json +46 -5
- package/logger/Logger.ts +38 -9
- package/package.json +13 -9
- package/web/Server.ts +235 -122
- package/web/bindings/aqualinkD.json +114 -59
- package/web/bindings/homeassistant.json +437 -0
- package/web/bindings/influxDB.json +15 -0
- package/web/bindings/mqtt.json +28 -9
- package/web/bindings/mqttAlt.json +15 -0
- package/web/interfaces/baseInterface.ts +58 -7
- package/web/interfaces/httpInterface.ts +5 -2
- package/web/interfaces/influxInterface.ts +9 -2
- package/web/interfaces/mqttInterface.ts +234 -74
- package/web/interfaces/ruleInterface.ts +87 -0
- package/web/services/config/Config.ts +140 -33
- package/web/services/config/ConfigSocket.ts +2 -1
- package/web/services/state/State.ts +144 -3
- package/web/services/state/StateSocket.ts +65 -14
- package/web/services/utilities/Utilities.ts +189 -1
|
@@ -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.initAsync();
|
|
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);
|
|
@@ -98,7 +101,7 @@ class Config {
|
|
|
98
101
|
section = arr[arr.length - 1];
|
|
99
102
|
}
|
|
100
103
|
if(typeof c[section] !== 'undefined') delete c[section];
|
|
101
|
-
this.
|
|
104
|
+
this.updateAsync();
|
|
102
105
|
}
|
|
103
106
|
public setSection(section: string, val) {
|
|
104
107
|
let c = this._cfg;
|
|
@@ -112,7 +115,7 @@ class Config {
|
|
|
112
115
|
section = arr[arr.length - 1];
|
|
113
116
|
}
|
|
114
117
|
c[section] = val;
|
|
115
|
-
this.
|
|
118
|
+
this.updateAsync();
|
|
116
119
|
}
|
|
117
120
|
// RKS: 09-21-21 - We are counting on the return from this being immutable. A copy of the data
|
|
118
121
|
// should always be returned here.
|
|
@@ -135,6 +138,7 @@ class Config {
|
|
|
135
138
|
this.ensurePath(baseDir + '/logs/');
|
|
136
139
|
this.ensurePath(baseDir + '/data/');
|
|
137
140
|
this.ensurePath(baseDir + '/backups/');
|
|
141
|
+
this.ensurePath(baseDir + '/web/bindings/custom/')
|
|
138
142
|
// this.ensurePath(baseDir + '/replay/');
|
|
139
143
|
//setTimeout(() => { config.update(); }, 100);
|
|
140
144
|
}
|
|
@@ -152,7 +156,7 @@ class Config {
|
|
|
152
156
|
for (var i in interfaces) {
|
|
153
157
|
if (interfaces[i].uuid === obj.uuid) {
|
|
154
158
|
interfaces[i] = obj;
|
|
155
|
-
this.
|
|
159
|
+
this.updateAsync();
|
|
156
160
|
return {[i]: interfaces[i]};
|
|
157
161
|
}
|
|
158
162
|
}
|
|
@@ -185,7 +189,7 @@ class Config {
|
|
|
185
189
|
this._cfg.controller.comms.netPort = env.POOL_NET_PORT;
|
|
186
190
|
bUpdate = true;
|
|
187
191
|
}
|
|
188
|
-
if (bUpdate) this.
|
|
192
|
+
if (bUpdate) this.updateAsync();
|
|
189
193
|
}
|
|
190
194
|
}
|
|
191
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
|
|
@@ -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);
|