nodejs-poolcontroller 7.5.1 → 7.7.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/.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/Changelog +19 -0
- package/Dockerfile +3 -3
- package/README.md +13 -8
- package/app.ts +1 -1
- package/config/Config.ts +38 -2
- package/config/VersionCheck.ts +27 -12
- package/controller/Constants.ts +2 -1
- package/controller/Equipment.ts +193 -9
- package/controller/Errors.ts +10 -0
- package/controller/Lockouts.ts +503 -0
- package/controller/State.ts +269 -64
- package/controller/boards/AquaLinkBoard.ts +1000 -0
- package/controller/boards/BoardFactory.ts +4 -0
- package/controller/boards/EasyTouchBoard.ts +468 -144
- package/controller/boards/IntelliCenterBoard.ts +466 -307
- package/controller/boards/IntelliTouchBoard.ts +37 -5
- package/controller/boards/NixieBoard.ts +671 -141
- package/controller/boards/SystemBoard.ts +1397 -641
- package/controller/comms/Comms.ts +462 -362
- package/controller/comms/messages/Messages.ts +174 -30
- package/controller/comms/messages/config/ChlorinatorMessage.ts +6 -3
- package/controller/comms/messages/config/CircuitMessage.ts +1 -0
- package/controller/comms/messages/config/ExternalMessage.ts +10 -8
- package/controller/comms/messages/config/HeaterMessage.ts +141 -29
- package/controller/comms/messages/config/OptionsMessage.ts +9 -2
- package/controller/comms/messages/config/PumpMessage.ts +53 -35
- package/controller/comms/messages/config/ScheduleMessage.ts +33 -25
- package/controller/comms/messages/config/ValveMessage.ts +2 -2
- package/controller/comms/messages/status/ChlorinatorStateMessage.ts +38 -86
- package/controller/comms/messages/status/EquipmentStateMessage.ts +59 -23
- package/controller/comms/messages/status/HeaterStateMessage.ts +57 -3
- package/controller/comms/messages/status/IntelliChemStateMessage.ts +56 -8
- package/controller/comms/messages/status/PumpStateMessage.ts +23 -1
- package/controller/nixie/Nixie.ts +1 -1
- package/controller/nixie/bodies/Body.ts +3 -0
- package/controller/nixie/chemistry/ChemController.ts +164 -51
- package/controller/nixie/chemistry/Chlorinator.ts +137 -88
- package/controller/nixie/circuits/Circuit.ts +51 -19
- package/controller/nixie/heaters/Heater.ts +241 -31
- package/controller/nixie/pumps/Pump.ts +488 -206
- package/controller/nixie/schedules/Schedule.ts +91 -35
- package/controller/nixie/valves/Valve.ts +1 -1
- package/defaultConfig.json +20 -0
- package/package.json +21 -21
- package/web/Server.ts +94 -49
- package/web/bindings/aqualinkD.json +505 -0
- package/web/bindings/influxDB.json +71 -1
- package/web/bindings/mqtt.json +98 -39
- package/web/bindings/mqttAlt.json +59 -1
- package/web/interfaces/baseInterface.ts +1 -0
- package/web/interfaces/httpInterface.ts +23 -2
- package/web/interfaces/influxInterface.ts +45 -10
- package/web/interfaces/mqttInterface.ts +114 -54
- package/web/services/config/Config.ts +55 -132
- package/web/services/state/State.ts +81 -4
- package/web/services/state/StateSocket.ts +4 -4
- package/web/services/utilities/Utilities.ts +8 -6
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -52
- package/config copy.json +0 -300
- package/issue_template.md +0 -52
|
@@ -3,11 +3,13 @@ import { utils, Timestamp } from '../../Constants';
|
|
|
3
3
|
import { logger } from '../../../logger/Logger';
|
|
4
4
|
|
|
5
5
|
import { NixieEquipment, NixieChildEquipment, NixieEquipmentCollection, INixieControlPanel } from "../NixieEquipment";
|
|
6
|
-
import { Schedule, ScheduleCollection, sys } from "../../../controller/Equipment";
|
|
7
|
-
import { ScheduleState, state, } from "../../State";
|
|
6
|
+
import { CircuitGroup, CircuitGroupCircuit, ICircuitGroup, ICircuitGroupCircuit, LightGroup, LightGroupCircuit, Schedule, ScheduleCollection, sys } from "../../../controller/Equipment";
|
|
7
|
+
import { CircuitGroupState, ICircuitGroupState, ScheduleState, state, } from "../../State";
|
|
8
8
|
import { setTimeout, clearTimeout } from 'timers';
|
|
9
9
|
import { NixieControlPanel } from '../Nixie';
|
|
10
10
|
import { webApp, InterfaceServerResponse } from "../../../web/Server";
|
|
11
|
+
import { delayMgr } from '../../../controller/Lockouts';
|
|
12
|
+
|
|
11
13
|
|
|
12
14
|
export class NixieScheduleCollection extends NixieEquipmentCollection<NixieSchedule> {
|
|
13
15
|
public async setScheduleAsync(schedule: Schedule, data: any) {
|
|
@@ -69,6 +71,7 @@ export class NixieSchedule extends NixieEquipment {
|
|
|
69
71
|
private _pollTimer: NodeJS.Timeout = null;
|
|
70
72
|
public schedule: Schedule;
|
|
71
73
|
private suspended: boolean = false;
|
|
74
|
+
private resumed: boolean = false;
|
|
72
75
|
private running: boolean = false;
|
|
73
76
|
constructor(ncp: INixieControlPanel, schedule: Schedule) {
|
|
74
77
|
super(ncp);
|
|
@@ -106,74 +109,122 @@ export class NixieSchedule extends NixieEquipment {
|
|
|
106
109
|
// Schedules can be overridden so it is important that when the
|
|
107
110
|
// state is changed for the schedule if it is currently active that
|
|
108
111
|
// Nixie does not override the state of the scheduled circuit or feature.
|
|
112
|
+
/*
|
|
113
|
+
RSG 5-8-22
|
|
114
|
+
Manual OP needs to play a role here. From the IC manual:
|
|
115
|
+
# Manual OP General
|
|
116
|
+
|
|
117
|
+
From the manual:
|
|
118
|
+
Manual OP Priority: ON: This feature allows for a circuit to be manually switched OFF and switched ON within a scheduled program, the circuit will continue to run for a maximum of 12 hours or whatever that circuit Egg Timer is set to, after which the scheduled program will resume. This feature will turn off any scheduled program to allow manual pump override. The Default setting is OFF.
|
|
119
|
+
|
|
120
|
+
## When on
|
|
121
|
+
1. If a schedule should be on and the user turns the schedule off then the schedule expires until such time as the time off has expired. When that occurs the schedule should be reset to run at the designated time. If the user resets the schedule by turning the circuit back on again ~~then the schedule will resume and turn off at the specified time~~ then the schedule will be ignored and the circuit will run until the egg timer expires or the circuit/feature is manually turned off. This setting WILL affect other schedules that may impact this circuit.
|
|
122
|
+
|
|
123
|
+
## When off
|
|
124
|
+
1. "Normal" = If a schedule should be on and the user turns the schedule off then the schedule expires until such time as the time off has expired. When that occurs the schedule should be reset to run at the designated time. If the user resets the schedule by turning the circuit back on again then the schedule will resume and turn off at the specified time.
|
|
125
|
+
|
|
126
|
+
Interestingly, there also seems to be a schedule level setting for this. We will ignore that for now as the logic could get much more complicated.
|
|
127
|
+
*/
|
|
109
128
|
// 1. If the feature happens to be running and the schedule is not yet turned on then
|
|
110
129
|
// it should not override what the user says.
|
|
111
130
|
// 2. If a schedule is running and the state of the circuit changes to off then the new state should suspend the schedule
|
|
112
|
-
// until which time the feature is turned back on again.
|
|
131
|
+
// until which time the feature is turned back on again.
|
|
132
|
+
// Manual OP Off: Then the off time will come into play.
|
|
133
|
+
// Manual OP On: The egg timer will take precedence. No other schedules will turn off this feature
|
|
113
134
|
// 3. Egg timers will be managed by the individual circuit. If this is being turned on via the schedule then
|
|
114
135
|
// the egg timer is not in effect.
|
|
115
136
|
// 4. If there are overlapping schedules, then the off date is determined by
|
|
116
137
|
// the maximum off date.
|
|
117
138
|
// 5. If a schedule should be on and the user turns the schedule off then the schedule expires until such time
|
|
118
139
|
// as the time off has expired. When that occurs the schedule should be reset to run at the designated time. If the
|
|
119
|
-
// user resets the schedule by turning the circuit back on again then the schedule will
|
|
120
|
-
// time.
|
|
140
|
+
// user resets the schedule by turning the circuit back on again then the schedule will...
|
|
141
|
+
// Manual OP Off: ..resume and turn off at the specified time.
|
|
142
|
+
// Manual OP On: ...continue to run until the egg timer time is reached or the circuit is manually turned off.
|
|
121
143
|
// 6. Heat setpoints should only be changed when the schedule is first turning on the scheduled circuit.
|
|
144
|
+
// 7. If schedule is disabled, skip it
|
|
145
|
+
// 8. Manual OP On: If another schedule has been resumed and this schedule would affect that feature, do not start this schedule.
|
|
146
|
+
|
|
147
|
+
// Definitions:
|
|
148
|
+
// this.resumed = Flag to show if this schedule has been suspended and resumed.
|
|
149
|
+
// this.manualPriorityActive = Flag to show if this schedule has been suspended and resumed, and Manual Priority (global is active)
|
|
150
|
+
// this.delayed = Flag to show if this schedule is running, but another schedule is mOP and overriding this one.
|
|
122
151
|
let cstate = state.circuits.getInterfaceById(this.schedule.circuit, false);
|
|
123
152
|
let circuit = sys.circuits.getInterfaceById(this.schedule.circuit, false, { isActive: false });
|
|
124
153
|
if (circuit.isActive === false) {
|
|
125
154
|
ssched.isOn = false;
|
|
126
155
|
return;
|
|
127
156
|
}
|
|
128
|
-
let shouldBeOn = this.shouldBeOn(
|
|
129
|
-
|
|
157
|
+
let shouldBeOn = this.shouldBeOn(); // This should also set the validity for the schedule if there are errors.
|
|
158
|
+
|
|
159
|
+
let manualPriorityActive: boolean = shouldBeOn ? sys.board.schedules.manualPriorityActive(ssched) : false;
|
|
160
|
+
console.log(`Processing schedule ${this.schedule.id} - ${circuit.name} : ShouldBeOn: ${shouldBeOn} ManualPriorityActive: ${manualPriorityActive} Running: ${this.running} Suspended: ${this.suspended} Resumed: ${this.resumed}`);
|
|
161
|
+
|
|
162
|
+
|
|
130
163
|
// COND 1: The schedule should be on and the schedule is not yet on.
|
|
131
|
-
if (shouldBeOn && !this.running && !this.suspended) {
|
|
164
|
+
if (shouldBeOn && !this.running && !this.suspended || manualPriorityActive) {
|
|
132
165
|
// If the circuit is on then we need to clear the suspended flag and set the running flag.
|
|
133
166
|
if (cstate.isOn) {
|
|
134
167
|
// If the suspended flag was previously on then we need to clear it
|
|
135
168
|
// because the user turned it back on.
|
|
136
169
|
this.suspended = false;
|
|
137
170
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
171
|
+
if (manualPriorityActive) {
|
|
172
|
+
ssched.manualPriorityActive = true;
|
|
173
|
+
ssched.isOn = false;
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
ctx.setCircuit(circuit.id, true);
|
|
177
|
+
// Alright we are turning on the circuit. If these are body circuits then we need to determine
|
|
178
|
+
// whether we will be setting the setpoints/heatmode on the body.
|
|
179
|
+
let body = sys.bodies.find(elem => elem.circuit === circuit.id);
|
|
180
|
+
if (typeof body !== 'undefined') {
|
|
181
|
+
let heatSource = sys.board.valueMaps.heatSources.transform(this.schedule.heatSource);
|
|
182
|
+
if (heatSource.name !== 'nochange') {
|
|
183
|
+
switch (heatSource.name) {
|
|
184
|
+
case 'nochange':
|
|
185
|
+
case 'dontchange':
|
|
186
|
+
break;
|
|
187
|
+
case 'off':
|
|
188
|
+
ctx.setHeatMode(body.id, 'off');
|
|
189
|
+
break;
|
|
190
|
+
default:
|
|
191
|
+
ctx.setHeatMode(body.id, heatSource.name, this.schedule.heatSetpoint, heatSource.hasCoolSetpoint ? this.schedule.coolSetpoint : undefined);
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
155
194
|
}
|
|
156
195
|
}
|
|
196
|
+
ssched.manualPriorityActive = false;
|
|
197
|
+
ssched.isOn = true;
|
|
157
198
|
}
|
|
158
|
-
ssched.isOn = true;
|
|
159
199
|
this.running = true;
|
|
160
200
|
}
|
|
161
201
|
else if (shouldBeOn && this.running) {
|
|
162
|
-
//
|
|
202
|
+
// With mOP, we need to see if the schedule will come back into play and also set the circut
|
|
203
|
+
if (this.suspended && cstate.isOn) {
|
|
204
|
+
if (sys.general.options.manualPriority) {
|
|
205
|
+
delayMgr.setManualPriorityDelay(cstate);
|
|
206
|
+
ssched.manualPriorityActive = true;
|
|
207
|
+
}
|
|
208
|
+
this.resumed = true;
|
|
209
|
+
}
|
|
163
210
|
this.suspended = !cstate.isOn;
|
|
211
|
+
ssched.isOn = cstate.isOn && !manualPriorityActive;
|
|
212
|
+
if (this.suspended && !cstate.isOn) ssched.manualPriorityActive = false;
|
|
164
213
|
}
|
|
165
|
-
// Our schedule has expired it is time to turn it off.
|
|
214
|
+
// Our schedule has expired it is time to turn it off, but only if !manualPriorityActive.
|
|
166
215
|
else if (!shouldBeOn) {
|
|
167
216
|
// Turn this sucker off. But wait if there is an overlapping schedule then we should
|
|
168
217
|
// not turn it off. We will need some logic to deal with this.
|
|
169
|
-
if (this.running) ctx.setCircuit(circuit.id, false);
|
|
218
|
+
if (this.running && !cstate.manualPriorityActive) ctx.setCircuit(circuit.id, false);
|
|
170
219
|
ssched.isOn = false;
|
|
171
220
|
this.running = false;
|
|
172
221
|
this.suspended = false;
|
|
222
|
+
this.resumed = false;
|
|
223
|
+
ssched.manualPriorityActive = false;
|
|
173
224
|
}
|
|
174
225
|
if (!shouldBeOn && ssched.isOn === true) {
|
|
175
226
|
// Turn off the circuit.
|
|
176
|
-
ctx.setCircuit(circuit.id, false);
|
|
227
|
+
if (!manualPriorityActive) ctx.setCircuit(circuit.id, false);
|
|
177
228
|
ssched.isOn = false;
|
|
178
229
|
}
|
|
179
230
|
ssched.emitEquipmentChange();
|
|
@@ -191,8 +242,9 @@ export class NixieSchedule extends NixieEquipment {
|
|
|
191
242
|
return dt.startOfDay().addMinutes(offset);
|
|
192
243
|
}
|
|
193
244
|
}
|
|
194
|
-
protected shouldBeOn(
|
|
245
|
+
protected shouldBeOn(): boolean {
|
|
195
246
|
if (this.schedule.isActive === false) return false;
|
|
247
|
+
if (this.schedule.disabled) return false;
|
|
196
248
|
// Be careful with toDate since this returns a mutable date object from the state timestamp. startOfDay makes it immutable.
|
|
197
249
|
let sod = state.time.startOfDay()
|
|
198
250
|
let dow = sod.toDate().getDay();
|
|
@@ -212,19 +264,23 @@ export class NixieSchedule extends NixieEquipment {
|
|
|
212
264
|
// [0, {name: 'manual', desc: 'Manual }]
|
|
213
265
|
// [1, { name: 'sunrise', desc: 'Sunrise' }],
|
|
214
266
|
// [2, { name: 'sunset', desc: 'Sunset' }]
|
|
215
|
-
let tmStart = this.calcTime(sod, this.schedule.
|
|
267
|
+
let tmStart = this.calcTime(sod, this.schedule.startTimeType, this.schedule.startTime).getTime();
|
|
216
268
|
let tmEnd = this.calcTime(sod, this.schedule.endTimeType, this.schedule.endTime).getTime();
|
|
217
|
-
|
|
269
|
+
|
|
218
270
|
if (isNaN(tmStart)) return false;
|
|
219
271
|
if (isNaN(tmEnd)) return false;
|
|
220
272
|
// If we are past our window we should be off.
|
|
221
273
|
let tm = state.time.getTime();
|
|
222
274
|
if (tm >= tmEnd) return false;
|
|
223
275
|
if (tm <= tmStart) return false;
|
|
224
|
-
|
|
276
|
+
|
|
277
|
+
// Let's now check to see
|
|
278
|
+
|
|
225
279
|
// If we make it here we should be on.
|
|
226
280
|
return true;
|
|
227
281
|
}
|
|
282
|
+
|
|
283
|
+
|
|
228
284
|
public async closeAsync() {
|
|
229
285
|
try {
|
|
230
286
|
if (typeof this._pollTimer !== 'undefined' || this._pollTimer) clearTimeout(this._pollTimer);
|
|
@@ -234,7 +290,7 @@ export class NixieSchedule extends NixieEquipment {
|
|
|
234
290
|
}
|
|
235
291
|
public logData(filename: string, data: any) { this.controlPanel.logData(filename, data); }
|
|
236
292
|
}
|
|
237
|
-
class
|
|
293
|
+
class NixieScheduleContext {
|
|
238
294
|
constructor() {
|
|
239
295
|
|
|
240
296
|
}
|
|
@@ -102,7 +102,7 @@ export class NixieValve extends NixieEquipment {
|
|
|
102
102
|
try {
|
|
103
103
|
// Here we go we need to set the valve state.
|
|
104
104
|
if (vstate.isDiverted !== isDiverted) {
|
|
105
|
-
logger.
|
|
105
|
+
logger.verbose(`Nixie: Set valve ${vstate.id}-${vstate.name} to ${isDiverted}`);
|
|
106
106
|
}
|
|
107
107
|
if (utils.isNullOrEmpty(this.valve.connectionId) || utils.isNullOrEmpty(this.valve.deviceBinding)) {
|
|
108
108
|
vstate.isDiverted = isDiverted;
|
package/defaultConfig.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"controller": {
|
|
3
3
|
"comms": {
|
|
4
|
+
"portId": 0,
|
|
4
5
|
"enabled": true,
|
|
5
6
|
"rs485Port": "/dev/ttyUSB0",
|
|
6
7
|
"mockPort": false,
|
|
@@ -159,6 +160,25 @@
|
|
|
159
160
|
"changesOnly": true
|
|
160
161
|
}
|
|
161
162
|
},
|
|
163
|
+
"aqualinkD": {
|
|
164
|
+
"name": "AquaLinkD",
|
|
165
|
+
"type": "mqtt",
|
|
166
|
+
"enabled": false,
|
|
167
|
+
"fileName": "aqualinkD.json",
|
|
168
|
+
"globals": {},
|
|
169
|
+
"options": {
|
|
170
|
+
"protocol": "mqtt://",
|
|
171
|
+
"host": "192.168.0.1",
|
|
172
|
+
"port": 1883,
|
|
173
|
+
"username": "",
|
|
174
|
+
"password": "",
|
|
175
|
+
"rootTopic": "aqualinkd",
|
|
176
|
+
"retain": true,
|
|
177
|
+
"qos": 0,
|
|
178
|
+
"changesOnly": true
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
},
|
|
162
182
|
"mqttAlt": {
|
|
163
183
|
"name": "MQTTAlt",
|
|
164
184
|
"type": "mqtt",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nodejs-poolcontroller",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.7.0",
|
|
4
4
|
"description": "nodejs-poolController",
|
|
5
5
|
"main": "app.js",
|
|
6
6
|
"author": {
|
|
@@ -19,38 +19,38 @@
|
|
|
19
19
|
"watch": "tsc -w"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@influxdata/influxdb-client": "^1.
|
|
22
|
+
"@influxdata/influxdb-client": "^1.25.0",
|
|
23
23
|
"eslint-config-promise": "^2.0.2",
|
|
24
|
-
"express": "^4.
|
|
24
|
+
"express": "^4.18.1",
|
|
25
25
|
"extend": "^3.0.2",
|
|
26
|
-
"jszip": "^3.
|
|
27
|
-
"mqtt": "^4.
|
|
28
|
-
"multer": "^1.4.
|
|
29
|
-
"multicast-dns": "^7.2.
|
|
26
|
+
"jszip": "^3.9.1",
|
|
27
|
+
"mqtt": "^4.3.7",
|
|
28
|
+
"multer": "^1.4.4",
|
|
29
|
+
"multicast-dns": "^7.2.4",
|
|
30
30
|
"node-ssdp": "^4.0.1",
|
|
31
|
-
"serialport": "^9.2.
|
|
32
|
-
"socket.io": "^4.
|
|
33
|
-
"socket.io-client": "^4.
|
|
34
|
-
"source-map-support": "^0.5.
|
|
35
|
-
"winston": "^3.
|
|
31
|
+
"serialport": "^9.2.8",
|
|
32
|
+
"socket.io": "^4.5.0",
|
|
33
|
+
"socket.io-client": "^4.5.0",
|
|
34
|
+
"source-map-support": "^0.5.21",
|
|
35
|
+
"winston": "^3.7.2"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/express": "^4.17.13",
|
|
39
39
|
"@types/extend": "^3.0.1",
|
|
40
40
|
"@types/multer": "^1.4.7",
|
|
41
|
-
"@types/node": "^12.20.
|
|
42
|
-
"@typescript-eslint/eslint-plugin": "^
|
|
43
|
-
"@typescript-eslint/parser": "^
|
|
44
|
-
"eslint": "^
|
|
41
|
+
"@types/node": "^12.20.52",
|
|
42
|
+
"@typescript-eslint/eslint-plugin": "^5.23.0",
|
|
43
|
+
"@typescript-eslint/parser": "^5.23.0",
|
|
44
|
+
"eslint": "^8.15.0",
|
|
45
45
|
"eslint-config-defaults": "^9.0.0",
|
|
46
46
|
"eslint-config-standard": "^16.0.3",
|
|
47
|
-
"eslint-plugin-import": "^2.
|
|
47
|
+
"eslint-plugin-import": "^2.26.0",
|
|
48
48
|
"eslint-plugin-node": "^11.1.0",
|
|
49
|
-
"eslint-plugin-promise": "^
|
|
49
|
+
"eslint-plugin-promise": "^6.0.0",
|
|
50
50
|
"eslint-plugin-standard": "^5.0.0",
|
|
51
|
-
"grunt": "^1.
|
|
51
|
+
"grunt": "^1.5.3",
|
|
52
52
|
"grunt-banner": "^0.6.0",
|
|
53
|
-
"ts-node": "^10.
|
|
54
|
-
"typescript": "^4.4
|
|
53
|
+
"ts-node": "^10.7.0",
|
|
54
|
+
"typescript": "^4.6.4"
|
|
55
55
|
}
|
|
56
56
|
}
|
package/web/Server.ts
CHANGED
|
@@ -54,6 +54,7 @@ export class WebServer {
|
|
|
54
54
|
private _servers: ProtoServer[] = [];
|
|
55
55
|
private family = 'IPv4';
|
|
56
56
|
private _autoBackupTimer: NodeJS.Timeout;
|
|
57
|
+
private _httpPort: number;
|
|
57
58
|
constructor() { }
|
|
58
59
|
public async init() {
|
|
59
60
|
try {
|
|
@@ -68,12 +69,15 @@ export class WebServer {
|
|
|
68
69
|
switch (s) {
|
|
69
70
|
case 'http':
|
|
70
71
|
srv = new HttpServer(s, s);
|
|
72
|
+
if (c.enabled !== false) this._httpPort = c.port;
|
|
71
73
|
break;
|
|
72
74
|
case 'http2':
|
|
73
75
|
srv = new Http2Server(s, s);
|
|
76
|
+
if (c.enabled !== false) this._httpPort = c.port;
|
|
74
77
|
break;
|
|
75
78
|
case 'https':
|
|
76
79
|
srv = new HttpsServer(s, s);
|
|
80
|
+
if (c.enabled !== false) this._httpPort = c.port;
|
|
77
81
|
break;
|
|
78
82
|
case 'mdns':
|
|
79
83
|
srv = new MdnsServer(s, s);
|
|
@@ -173,12 +177,9 @@ export class WebServer {
|
|
|
173
177
|
}
|
|
174
178
|
}
|
|
175
179
|
}
|
|
176
|
-
public ip() {
|
|
177
|
-
|
|
178
|
-
}
|
|
179
|
-
public mac() {
|
|
180
|
-
return typeof this.getInterface() === 'undefined' ? '00:00:00:00' : this.getInterface().mac;
|
|
181
|
-
}
|
|
180
|
+
public ip() { return typeof this.getInterface() === 'undefined' ? '0.0.0.0' : this.getInterface().address; }
|
|
181
|
+
public mac() { return typeof this.getInterface() === 'undefined' ? '00:00:00:00' : this.getInterface().mac; }
|
|
182
|
+
public httpPort(): number { return this._httpPort }
|
|
182
183
|
public findServer(name: string): ProtoServer { return this._servers.find(elem => elem.name === name); }
|
|
183
184
|
public findServersByType(type: string) { return this._servers.filter(elem => elem.type === type); }
|
|
184
185
|
public findServerByGuid(uuid: string) { return this._servers.find(elem => elem.uuid === uuid); }
|
|
@@ -563,7 +564,7 @@ export class HttpServer extends ProtoServer {
|
|
|
563
564
|
public app: express.Application;
|
|
564
565
|
public server: http.Server;
|
|
565
566
|
public sockServer: SocketIoServer<ClientToServerEvents, ServerToClientEvents>;
|
|
566
|
-
private _sockets: RemoteSocket<ServerToClientEvents>[] = [];
|
|
567
|
+
private _sockets: RemoteSocket<ServerToClientEvents, any>[] = [];
|
|
567
568
|
public emitToClients(evt: string, ...data: any) {
|
|
568
569
|
if (this.isRunning) {
|
|
569
570
|
this.sockServer.emit(evt, ...data);
|
|
@@ -618,42 +619,6 @@ export class HttpServer extends ProtoServer {
|
|
|
618
619
|
self._sockets = await self.sockServer.fetchSockets();
|
|
619
620
|
});
|
|
620
621
|
sock.on('echo', (msg) => { sock.emit('echo', msg); });
|
|
621
|
-
/* sock.on('receivePacketRaw', function (incomingPacket: any[]) {
|
|
622
|
-
//var str = 'Add packet(s) to incoming buffer: ';
|
|
623
|
-
logger.silly('User request (replay.html) to RECEIVE packet: %s', JSON.stringify(incomingPacket));
|
|
624
|
-
for (var i = 0; i < incomingPacket.length; i++) {
|
|
625
|
-
conn.buffer.pushIn(Buffer.from(incomingPacket[i]));
|
|
626
|
-
// str += JSON.stringify(incomingPacket[i]) + ' ';
|
|
627
|
-
}
|
|
628
|
-
//logger.info(str);
|
|
629
|
-
});
|
|
630
|
-
sock.on('replayPackets', function (inboundPkts: number[][]) {
|
|
631
|
-
// used for replay
|
|
632
|
-
logger.debug(`Received replayPackets: ${inboundPkts}`);
|
|
633
|
-
inboundPkts.forEach(inbound => {
|
|
634
|
-
conn.buffer.pushIn(Buffer.from([].concat.apply([], inbound)));
|
|
635
|
-
// conn.queueInboundMessage([].concat.apply([], inbound));
|
|
636
|
-
});
|
|
637
|
-
});
|
|
638
|
-
sock.on('sendPackets', function (bytesToProcessArr: number[][]) {
|
|
639
|
-
// takes an input of bytes (src/dest/action/payload) and sends
|
|
640
|
-
if (!bytesToProcessArr.length) return;
|
|
641
|
-
logger.silly('User request (replay.html) to SEND packet: %s', JSON.stringify(bytesToProcessArr));
|
|
642
|
-
|
|
643
|
-
do {
|
|
644
|
-
let bytesToProcess: number[] = bytesToProcessArr.shift();
|
|
645
|
-
|
|
646
|
-
// todo: logic for chlor packets
|
|
647
|
-
let out = Outbound.create({
|
|
648
|
-
source: bytesToProcess.shift(),
|
|
649
|
-
dest: bytesToProcess.shift(),
|
|
650
|
-
action: bytesToProcess.shift(),
|
|
651
|
-
payload: bytesToProcess.splice(1, bytesToProcess[0])
|
|
652
|
-
});
|
|
653
|
-
conn.queueSendMessage(out);
|
|
654
|
-
} while (bytesToProcessArr.length > 0);
|
|
655
|
-
|
|
656
|
-
}); */
|
|
657
622
|
sock.on('sendOutboundMessage', (mdata) => {
|
|
658
623
|
let msg: Outbound = Outbound.create({});
|
|
659
624
|
Object.assign(msg, mdata);
|
|
@@ -733,7 +698,7 @@ export class HttpServer extends ProtoServer {
|
|
|
733
698
|
res.header('Access-Control-Allow-Methods', 'OPTIONS, GET, POST, PUT, DELETE');
|
|
734
699
|
if ('OPTIONS' === req.method) { res.sendStatus(200); }
|
|
735
700
|
else {
|
|
736
|
-
if (req.url !== '/
|
|
701
|
+
if (req.url !== '/upnp.xml') {
|
|
737
702
|
logger.info(`[${new Date().toLocaleTimeString()}] ${req.ip} ${req.method} ${req.url} ${typeof req.body === 'undefined' ? '' : JSON.stringify(req.body)}`);
|
|
738
703
|
logger.logAPI(`{"dir":"in","proto":"api","requestor":"${req.ip}","method":"${req.method}","path":"${req.url}",${typeof req.body === 'undefined' ? '' : `"body":${JSON.stringify(req.body)},`}"ts":"${Timestamp.toISOLocal(new Date())}"}${os.EOL}`);
|
|
739
704
|
}
|
|
@@ -824,7 +789,7 @@ export class HttpsServer extends HttpServer {
|
|
|
824
789
|
res.header('Access-Control-Allow-Methods', 'OPTIONS, GET, POST, PUT, DELETE');
|
|
825
790
|
if ('OPTIONS' === req.method) { res.sendStatus(200); }
|
|
826
791
|
else {
|
|
827
|
-
if (req.url !== '/
|
|
792
|
+
if (req.url !== '/upnp.xml') {
|
|
828
793
|
logger.info(`[${new Date().toLocaleString()}] ${req.ip} ${req.method} ${req.url} ${typeof req.body === 'undefined' ? '' : JSON.stringify(req.body)}`);
|
|
829
794
|
logger.logAPI(`{"dir":"in","proto":"api","requestor":"${req.ip}","method":"${req.method}","path":"${req.url}",${typeof req.body === 'undefined' ? '' : `"body":${JSON.stringify(req.body)},`}"ts":"${Timestamp.toISOLocal(new Date())}"}${os.EOL}`);
|
|
830
795
|
}
|
|
@@ -874,6 +839,84 @@ export class HttpsServer extends HttpServer {
|
|
|
874
839
|
}
|
|
875
840
|
}
|
|
876
841
|
export class SsdpServer extends ProtoServer {
|
|
842
|
+
// Simple service discovery protocol
|
|
843
|
+
public server: any; //node-ssdp;
|
|
844
|
+
public deviceUUID: string;
|
|
845
|
+
public upnpPath: string;
|
|
846
|
+
public modelName: string;
|
|
847
|
+
public modelNumber: string;
|
|
848
|
+
public serialNumber: string;
|
|
849
|
+
public deviceType = 'urn:schemas-tagyoureit-org:device:PoolController:1';
|
|
850
|
+
public async init(cfg) {
|
|
851
|
+
this.uuid = cfg.uuid;
|
|
852
|
+
if (cfg.enabled) {
|
|
853
|
+
let self = this;
|
|
854
|
+
logger.info('Starting up SSDP server');
|
|
855
|
+
let ver = JSON.parse(fs.readFileSync(path.posix.join(process.cwd(), '/package.json'), 'utf8')).version || '0.0.0';
|
|
856
|
+
this.deviceUUID = 'uuid:806f52f4-1f35-4e33-9299-' + webApp.mac().replace(/:/g, '');
|
|
857
|
+
this.serialNumber = webApp.mac();
|
|
858
|
+
this.modelName = `njsPC v${ver}`;
|
|
859
|
+
this.modelNumber = `njsPC${ver.replace(/\./g, '-')}`;
|
|
860
|
+
// todo: should probably check if http/https is enabled at this point
|
|
861
|
+
let port = config.getSection('web').servers.http.port || 7777;
|
|
862
|
+
this.upnpPath = 'http://' + webApp.ip() + ':' + port + '/upnp.xml';
|
|
863
|
+
let SSDP = ssdp.Server;
|
|
864
|
+
this.server = new SSDP({
|
|
865
|
+
//customLogger: (...args) => console.log.apply(null, args),
|
|
866
|
+
logLevel: 'INFO',
|
|
867
|
+
udn: this.deviceUUID,
|
|
868
|
+
location: this.upnpPath,
|
|
869
|
+
sourcePort: 1900
|
|
870
|
+
});
|
|
871
|
+
this.server.addUSN('upnp:rootdevice'); // This line will make the server show up in windows.
|
|
872
|
+
this.server.addUSN(this.deviceType);
|
|
873
|
+
// start the server
|
|
874
|
+
this.server.start()
|
|
875
|
+
.then(function () {
|
|
876
|
+
logger.silly('SSDP/UPnP Server started.');
|
|
877
|
+
self.isRunning = true;
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
this.server.on('error', function (e) {
|
|
881
|
+
logger.error('error from SSDP:', e);
|
|
882
|
+
});
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
public deviceXML(): string {
|
|
886
|
+
let XML = `<?xml version="1.0"?>
|
|
887
|
+
<root xmlns="urn:schemas-upnp-org:device-1-0">
|
|
888
|
+
<specVersion>
|
|
889
|
+
<major>1</major>
|
|
890
|
+
<minor>0</minor>
|
|
891
|
+
</specVersion>
|
|
892
|
+
<device>
|
|
893
|
+
<deviceType>${this.deviceType}</deviceType>
|
|
894
|
+
<friendlyName>NodeJS Pool Controller</friendlyName>
|
|
895
|
+
<manufacturer>tagyoureit</manufacturer>
|
|
896
|
+
<manufacturerURL>https://github.com/tagyoureit/nodejs-poolController</manufacturerURL>
|
|
897
|
+
<presentationURL>http://${webApp.ip()}:${webApp.httpPort()}/state/all</presentationURL>
|
|
898
|
+
<modelName>${this.modelName}</modelName>
|
|
899
|
+
<modelNumber>${this.modelNumber}</modelNumber>
|
|
900
|
+
<modelDescription>An application to control pool equipment.</modelDescription>
|
|
901
|
+
<serialNumber>${this.serialNumber}</serialNumber>
|
|
902
|
+
<UDN>${this.deviceUUID}::${this.deviceType}</UDN>
|
|
903
|
+
<serviceList></serviceList>
|
|
904
|
+
<deviceList></deviceList>
|
|
905
|
+
</device>
|
|
906
|
+
</root>`;
|
|
907
|
+
return XML;
|
|
908
|
+
}
|
|
909
|
+
public async stopAsync() {
|
|
910
|
+
try {
|
|
911
|
+
if (typeof this.server !== 'undefined') {
|
|
912
|
+
this.server.stop();
|
|
913
|
+
logger.info(`Stopped SSDP server: ${this.name}`);
|
|
914
|
+
}
|
|
915
|
+
} catch (err) { logger.error(`Error stopping SSDP server ${err.message}`); }
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
/* RKS DEPRECATED: 05-07-22 - This did not follow the upnp rules so it was not detecting properly. The entire class above emits the proper xml layout.
|
|
919
|
+
export class SsdpServer1 extends ProtoServer {
|
|
877
920
|
// Simple service discovery protocol
|
|
878
921
|
public server: any; //node-ssdp;
|
|
879
922
|
public async init(cfg) {
|
|
@@ -939,6 +982,10 @@ export class SsdpServer extends ProtoServer {
|
|
|
939
982
|
} catch (err) { logger.error(`Error stopping SSDP server ${err.message}`); }
|
|
940
983
|
}
|
|
941
984
|
}
|
|
985
|
+
*/
|
|
986
|
+
|
|
987
|
+
|
|
988
|
+
|
|
942
989
|
export class MdnsServer extends ProtoServer {
|
|
943
990
|
// Multi-cast DNS server
|
|
944
991
|
public server;
|
|
@@ -1102,7 +1149,6 @@ export class HttpInterfaceServer extends ProtoServer {
|
|
|
1102
1149
|
catch (err) { }
|
|
1103
1150
|
}
|
|
1104
1151
|
}
|
|
1105
|
-
|
|
1106
1152
|
export class InfluxInterfaceServer extends ProtoServer {
|
|
1107
1153
|
public bindingsPath: string;
|
|
1108
1154
|
public bindings: InfluxInterfaceBindings;
|
|
@@ -1163,7 +1209,6 @@ export class InfluxInterfaceServer extends ProtoServer {
|
|
|
1163
1209
|
}
|
|
1164
1210
|
}
|
|
1165
1211
|
}
|
|
1166
|
-
|
|
1167
1212
|
export class MqttInterfaceServer extends ProtoServer {
|
|
1168
1213
|
public bindingsPath: string;
|
|
1169
1214
|
public bindings: HttpInterfaceBindings;
|
|
@@ -1344,7 +1389,7 @@ export class REMInterfaceServer extends ProtoServer {
|
|
|
1344
1389
|
public sockClient;
|
|
1345
1390
|
protected agent: http.Agent = new http.Agent({ keepAlive: true });
|
|
1346
1391
|
public get isConnected() { return this.sockClient !== 'undefined' && this.sockClient.connected; };
|
|
1347
|
-
private _sockets: RemoteSocket<ServerToClientEvents>[] = [];
|
|
1392
|
+
private _sockets: RemoteSocket<ServerToClientEvents, any>[] = [];
|
|
1348
1393
|
private async sendClientRequest(method: string, url: string, data?: any, timeout: number = 10000): Promise<InterfaceServerResponse> {
|
|
1349
1394
|
try {
|
|
1350
1395
|
|
|
@@ -1450,7 +1495,7 @@ export class REMInterfaceServer extends ProtoServer {
|
|
|
1450
1495
|
}
|
|
1451
1496
|
private isJSONString(s: string): boolean {
|
|
1452
1497
|
if (typeof s !== 'string') return false;
|
|
1453
|
-
if (
|
|
1498
|
+
if (s.startsWith('{') || s.startsWith('[')) return true;
|
|
1454
1499
|
return false;
|
|
1455
1500
|
}
|
|
1456
1501
|
public async getApiService(url: string, data?: any, timeout: number = 3600): Promise<InterfaceServerResponse> {
|