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.
Files changed (64) hide show
  1. package/.github/ISSUE_TEMPLATE/1-bug-report.yml +84 -0
  2. package/.github/ISSUE_TEMPLATE/2-docs.md +12 -0
  3. package/.github/ISSUE_TEMPLATE/3-proposal.md +28 -0
  4. package/.github/ISSUE_TEMPLATE/config.yml +8 -0
  5. package/Changelog +19 -0
  6. package/Dockerfile +3 -3
  7. package/README.md +13 -8
  8. package/app.ts +1 -1
  9. package/config/Config.ts +38 -2
  10. package/config/VersionCheck.ts +27 -12
  11. package/controller/Constants.ts +2 -1
  12. package/controller/Equipment.ts +193 -9
  13. package/controller/Errors.ts +10 -0
  14. package/controller/Lockouts.ts +503 -0
  15. package/controller/State.ts +269 -64
  16. package/controller/boards/AquaLinkBoard.ts +1000 -0
  17. package/controller/boards/BoardFactory.ts +4 -0
  18. package/controller/boards/EasyTouchBoard.ts +468 -144
  19. package/controller/boards/IntelliCenterBoard.ts +466 -307
  20. package/controller/boards/IntelliTouchBoard.ts +37 -5
  21. package/controller/boards/NixieBoard.ts +671 -141
  22. package/controller/boards/SystemBoard.ts +1397 -641
  23. package/controller/comms/Comms.ts +462 -362
  24. package/controller/comms/messages/Messages.ts +174 -30
  25. package/controller/comms/messages/config/ChlorinatorMessage.ts +6 -3
  26. package/controller/comms/messages/config/CircuitMessage.ts +1 -0
  27. package/controller/comms/messages/config/ExternalMessage.ts +10 -8
  28. package/controller/comms/messages/config/HeaterMessage.ts +141 -29
  29. package/controller/comms/messages/config/OptionsMessage.ts +9 -2
  30. package/controller/comms/messages/config/PumpMessage.ts +53 -35
  31. package/controller/comms/messages/config/ScheduleMessage.ts +33 -25
  32. package/controller/comms/messages/config/ValveMessage.ts +2 -2
  33. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +38 -86
  34. package/controller/comms/messages/status/EquipmentStateMessage.ts +59 -23
  35. package/controller/comms/messages/status/HeaterStateMessage.ts +57 -3
  36. package/controller/comms/messages/status/IntelliChemStateMessage.ts +56 -8
  37. package/controller/comms/messages/status/PumpStateMessage.ts +23 -1
  38. package/controller/nixie/Nixie.ts +1 -1
  39. package/controller/nixie/bodies/Body.ts +3 -0
  40. package/controller/nixie/chemistry/ChemController.ts +164 -51
  41. package/controller/nixie/chemistry/Chlorinator.ts +137 -88
  42. package/controller/nixie/circuits/Circuit.ts +51 -19
  43. package/controller/nixie/heaters/Heater.ts +241 -31
  44. package/controller/nixie/pumps/Pump.ts +488 -206
  45. package/controller/nixie/schedules/Schedule.ts +91 -35
  46. package/controller/nixie/valves/Valve.ts +1 -1
  47. package/defaultConfig.json +20 -0
  48. package/package.json +21 -21
  49. package/web/Server.ts +94 -49
  50. package/web/bindings/aqualinkD.json +505 -0
  51. package/web/bindings/influxDB.json +71 -1
  52. package/web/bindings/mqtt.json +98 -39
  53. package/web/bindings/mqttAlt.json +59 -1
  54. package/web/interfaces/baseInterface.ts +1 -0
  55. package/web/interfaces/httpInterface.ts +23 -2
  56. package/web/interfaces/influxInterface.ts +45 -10
  57. package/web/interfaces/mqttInterface.ts +114 -54
  58. package/web/services/config/Config.ts +55 -132
  59. package/web/services/state/State.ts +81 -4
  60. package/web/services/state/StateSocket.ts +4 -4
  61. package/web/services/utilities/Utilities.ts +8 -6
  62. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -52
  63. package/config copy.json +0 -300
  64. 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. Then the off time will come into play.
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 resume and turn off at the specified
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(ssched); // This should also set the validity for the schedule if there are errors.
129
- //console.log(`Processing schedule ${this.schedule.id} - ${circuit.name} : ShouldBeOn: ${shouldBeOn} Suspended: ${this.suspended} Running: ${this.running}`);
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
- ctx.setCircuit(circuit.id, true);
139
- // Alright we are turning on the circuit. If these are body circuits then we need to determine
140
- // whether we will be setting the setpoints/heatmode on the body.
141
- let body = sys.bodies.find(elem => elem.circuit === circuit.id);
142
- if (typeof body !== 'undefined') {
143
- let heatSource = sys.board.valueMaps.heatSources.transform(this.schedule.heatSource);
144
- if (heatSource.name !== 'nochange') {
145
- switch (heatSource.name) {
146
- case 'nochange':
147
- case 'dontchange':
148
- break;
149
- case 'off':
150
- ctx.setHeatMode(body.id, 'off');
151
- break;
152
- default:
153
- ctx.setHeatMode(body.id, heatSource.name, this.schedule.heatSetpoint, heatSource.hasCoolSetpoint ? this.schedule.coolSetpoint : undefined);
154
- break;
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
- // We do nothing here.
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(sstate: ScheduleState): boolean {
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.startTime, this.schedule.startTime).getTime();
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 NixieScheduleContext {
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.info(`Nixie: Set valve ${vstate.id}-${vstate.name} to ${isDiverted}`);
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;
@@ -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.5.1",
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.17.0",
22
+ "@influxdata/influxdb-client": "^1.25.0",
23
23
  "eslint-config-promise": "^2.0.2",
24
- "express": "^4.17.1",
24
+ "express": "^4.18.1",
25
25
  "extend": "^3.0.2",
26
- "jszip": "^3.7.1",
27
- "mqtt": "^4.2.8",
28
- "multer": "^1.4.3",
29
- "multicast-dns": "^7.2.3",
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.1",
32
- "socket.io": "^4.2.0",
33
- "socket.io-client": "^4.2.0",
34
- "source-map-support": "^0.5.13",
35
- "winston": "^3.3.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.23",
42
- "@typescript-eslint/eslint-plugin": "^4.30.0",
43
- "@typescript-eslint/parser": "^4.30.0",
44
- "eslint": "^7.32.0",
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.24.2",
47
+ "eslint-plugin-import": "^2.26.0",
48
48
  "eslint-plugin-node": "^11.1.0",
49
- "eslint-plugin-promise": "^5.1.0",
49
+ "eslint-plugin-promise": "^6.0.0",
50
50
  "eslint-plugin-standard": "^5.0.0",
51
- "grunt": "^1.4.1",
51
+ "grunt": "^1.5.3",
52
52
  "grunt-banner": "^0.6.0",
53
- "ts-node": "^10.2.1",
54
- "typescript": "^4.4.2"
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
- return typeof this.getInterface() === 'undefined' ? '0.0.0.0' : this.getInterface().address;
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 !== '/device') {
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 !== '/device') {
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 (typeof s.startsWith('{') || typeof s.startsWith('[')) return true;
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> {