nodejs-poolcontroller 7.3.1 → 7.6.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 +44 -44
- package/.github/ISSUE_TEMPLATE/bug_report.md +52 -52
- package/CONTRIBUTING.md +74 -74
- package/Changelog +215 -195
- package/Dockerfile +17 -17
- package/Gruntfile.js +40 -40
- package/LICENSE +661 -661
- package/README.md +191 -186
- package/app.ts +2 -0
- package/config/Config.ts +27 -2
- package/config/VersionCheck.ts +33 -14
- package/config copy.json +299 -299
- package/controller/Constants.ts +88 -0
- package/controller/Equipment.ts +2459 -2225
- package/controller/Errors.ts +180 -157
- package/controller/Lockouts.ts +437 -0
- package/controller/State.ts +364 -79
- package/controller/boards/BoardFactory.ts +45 -45
- package/controller/boards/EasyTouchBoard.ts +2653 -2489
- package/controller/boards/IntelliCenterBoard.ts +4230 -3973
- package/controller/boards/IntelliComBoard.ts +63 -63
- package/controller/boards/IntelliTouchBoard.ts +241 -167
- package/controller/boards/NixieBoard.ts +1675 -1105
- package/controller/boards/SystemBoard.ts +4697 -3201
- package/controller/comms/Comms.ts +222 -10
- package/controller/comms/messages/Messages.ts +13 -9
- package/controller/comms/messages/config/ChlorinatorMessage.ts +13 -4
- package/controller/comms/messages/config/CircuitGroupMessage.ts +6 -0
- package/controller/comms/messages/config/CircuitMessage.ts +0 -0
- package/controller/comms/messages/config/ConfigMessage.ts +0 -0
- package/controller/comms/messages/config/CoverMessage.ts +1 -0
- package/controller/comms/messages/config/CustomNameMessage.ts +30 -30
- package/controller/comms/messages/config/EquipmentMessage.ts +4 -0
- package/controller/comms/messages/config/ExternalMessage.ts +53 -33
- package/controller/comms/messages/config/FeatureMessage.ts +8 -1
- package/controller/comms/messages/config/GeneralMessage.ts +8 -0
- package/controller/comms/messages/config/HeaterMessage.ts +14 -28
- package/controller/comms/messages/config/IntellichemMessage.ts +4 -1
- package/controller/comms/messages/config/OptionsMessage.ts +38 -2
- package/controller/comms/messages/config/PumpMessage.ts +4 -20
- package/controller/comms/messages/config/RemoteMessage.ts +4 -0
- package/controller/comms/messages/config/ScheduleMessage.ts +347 -331
- package/controller/comms/messages/config/SecurityMessage.ts +1 -0
- package/controller/comms/messages/config/ValveMessage.ts +13 -3
- package/controller/comms/messages/status/ChlorinatorStateMessage.ts +2 -3
- package/controller/comms/messages/status/EquipmentStateMessage.ts +79 -25
- package/controller/comms/messages/status/HeaterStateMessage.ts +86 -53
- package/controller/comms/messages/status/IntelliChemStateMessage.ts +445 -386
- package/controller/comms/messages/status/IntelliValveStateMessage.ts +35 -35
- package/controller/comms/messages/status/PumpStateMessage.ts +0 -0
- package/controller/comms/messages/status/VersionMessage.ts +0 -0
- package/controller/nixie/Nixie.ts +162 -160
- package/controller/nixie/NixieEquipment.ts +103 -103
- package/controller/nixie/bodies/Body.ts +120 -117
- package/controller/nixie/bodies/Filter.ts +135 -135
- package/controller/nixie/chemistry/ChemController.ts +2498 -2395
- package/controller/nixie/chemistry/Chlorinator.ts +314 -313
- package/controller/nixie/circuits/Circuit.ts +248 -210
- package/controller/nixie/heaters/Heater.ts +649 -441
- package/controller/nixie/pumps/Pump.ts +661 -599
- package/controller/nixie/schedules/Schedule.ts +257 -256
- package/controller/nixie/valves/Valve.ts +170 -170
- package/defaultConfig.json +286 -271
- package/issue_template.md +51 -51
- package/logger/DataLogger.ts +448 -433
- package/logger/Logger.ts +0 -0
- package/package.json +56 -54
- package/tsconfig.json +25 -25
- package/web/Server.ts +522 -31
- package/web/bindings/influxDB.json +1022 -894
- package/web/bindings/mqtt.json +654 -543
- package/web/bindings/mqttAlt.json +684 -574
- package/web/bindings/rulesManager.json +54 -54
- package/web/bindings/smartThings-Hubitat.json +31 -31
- package/web/bindings/valveRelays.json +20 -20
- package/web/bindings/vera.json +25 -25
- package/web/interfaces/baseInterface.ts +136 -136
- package/web/interfaces/httpInterface.ts +124 -122
- package/web/interfaces/influxInterface.ts +245 -240
- package/web/interfaces/mqttInterface.ts +475 -464
- package/web/services/config/Config.ts +181 -152
- package/web/services/config/ConfigSocket.ts +0 -0
- package/web/services/state/State.ts +118 -7
- package/web/services/state/StateSocket.ts +18 -1
- package/web/services/utilities/Utilities.ts +42 -42
|
@@ -43,9 +43,9 @@ export class Connection {
|
|
|
43
43
|
if (conn.connTimer !== null) clearTimeout(conn.connTimer);
|
|
44
44
|
if (!conn._cfg.mockPort && conn._cfg.inactivityRetry > 0 && !conn._closing) conn.connTimer = setTimeout(async () => {
|
|
45
45
|
try {
|
|
46
|
-
await conn.openAsync()
|
|
46
|
+
await conn.openAsync();
|
|
47
47
|
}
|
|
48
|
-
catch (err) {};
|
|
48
|
+
catch (err) { logger.error(`Error resetting RS485 port on inactivity: ${err.message}`); };
|
|
49
49
|
}, conn._cfg.inactivityRetry * 1000);
|
|
50
50
|
}
|
|
51
51
|
public isRTS: boolean = true;
|
|
@@ -82,8 +82,132 @@ export class Connection {
|
|
|
82
82
|
return this._cfg;
|
|
83
83
|
} catch (err) { return Promise.reject(err); }
|
|
84
84
|
}
|
|
85
|
+
// RKS: 12-18-21 This is a bullpen method to work through the inconsistencies in the socket implementation for node. There
|
|
86
|
+
// are issues related to the event listeners and the construction of a socket. We want an implementation that awaits until the socket
|
|
87
|
+
// is completely open before continuing.
|
|
88
|
+
// We also need to be able to destroy the port without it restarting on its own when tearing the port down or deliberately closing it. This means
|
|
89
|
+
// that the listeners need to be removed before closing via method but re-open the port when the close is hit prematurely.
|
|
90
|
+
protected async openNetSerialPort(): Promise<boolean> {
|
|
91
|
+
try {
|
|
92
|
+
let opts: net.NetConnectOpts = {
|
|
93
|
+
host: this._cfg.netHost,
|
|
94
|
+
port: this._cfg.netPort
|
|
95
|
+
};
|
|
96
|
+
if (typeof this.connTimer !== 'undefined' && this.connTimer) clearTimeout(this.connTimer);
|
|
97
|
+
this.connTimer = null;
|
|
98
|
+
let ret = await new Promise<boolean>((resolve, _) => {
|
|
99
|
+
let nc = net.createConnection(opts, () => {
|
|
100
|
+
nc.on('connect', () => { logger.info(`Net connect (socat) connected to: ${this._cfg.netHost}:${this._cfg.netPort}`); }); // Socket is opened but not yet ready.
|
|
101
|
+
nc.on('ready', () => {
|
|
102
|
+
this.isOpen = true;
|
|
103
|
+
this.isRTS = true;
|
|
104
|
+
logger.info(`Net connect (socat) ready and communicating: ${this._cfg.netHost}:${this._cfg.netPort}`);
|
|
105
|
+
nc.on('data', (data) => {
|
|
106
|
+
if (data.length > 0 && !this.isPaused) this.emitter.emit('packetread', data);
|
|
107
|
+
});
|
|
108
|
+
this._port = nc;
|
|
109
|
+
// After the port is fully opened, set an inactivity timeout to restart it when it stops communicating.
|
|
110
|
+
nc.setTimeout(Math.max(this._cfg.inactivityRetry, 10) * 1000, async () => {
|
|
111
|
+
logger.warn(`Net connect (socat) connection idle: ${this._cfg.netHost}:${this._cfg.netPort} retrying connection.`);
|
|
112
|
+
try {
|
|
113
|
+
await conn.endAsync();
|
|
114
|
+
await conn.openAsync();
|
|
115
|
+
} catch (err) { logger.error(`Net connect (socat) error retrying connection ${err.message}`); }
|
|
116
|
+
});
|
|
117
|
+
resolve(true);
|
|
118
|
+
});
|
|
119
|
+
nc.on('close', (hadError: boolean) => {
|
|
120
|
+
this.isOpen = false;
|
|
121
|
+
if (typeof this._port !== 'undefined') {
|
|
122
|
+
this._port.destroy();
|
|
123
|
+
this._port.removeAllListeners();
|
|
124
|
+
}
|
|
125
|
+
this._port = undefined;
|
|
126
|
+
this.buffer.clearOutbound();
|
|
127
|
+
logger.info(`Net connect (socat) closed ${hadError === true ? 'due to error' : ''}: ${this._cfg.netHost}:${this._cfg.netPort}`);
|
|
128
|
+
if (!this._closing) {
|
|
129
|
+
// If we are closing manually this event should have been cleared already and should never be called. If this is fired out
|
|
130
|
+
// of sequence then we will check the closing flag to ensure we are not forcibly closing the socket.
|
|
131
|
+
if (typeof this.connTimer !== 'undefined' && this.connTimer) {
|
|
132
|
+
clearTimeout(this.connTimer);
|
|
133
|
+
this.connTimer = null;
|
|
134
|
+
}
|
|
135
|
+
this.connTimer = setTimeout(async () => {
|
|
136
|
+
try {
|
|
137
|
+
// We are already closed so give some inactivity retry and try again.
|
|
138
|
+
await conn.openAsync();
|
|
139
|
+
} catch (err) { }
|
|
140
|
+
}, this._cfg.inactivityRetry * 1000);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
nc.on('end', () => { // Happens when the other end of the socket closes.
|
|
144
|
+
this.isOpen = false;
|
|
145
|
+
logger.info(`Net connect (socat) end event was fired`);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
nc.once('error', (err) => {
|
|
149
|
+
// if the promise has already been fulfilled, but the error happens later, we don't want to call the promise again.
|
|
150
|
+
if (this._cfg.inactivityRetry > 0) {
|
|
151
|
+
logger.error(`Net connect (socat) connection error: ${err}. Retry in ${this._cfg.inactivityRetry} seconds`);
|
|
152
|
+
this.connTimer = setTimeout(async () => {
|
|
153
|
+
try {
|
|
154
|
+
await conn.closeAsync();
|
|
155
|
+
await conn.openAsync();
|
|
156
|
+
} catch (err) { }
|
|
157
|
+
}, this._cfg.inactivityRetry * 1000);
|
|
158
|
+
}
|
|
159
|
+
else logger.error(`Net connect (socat) connection error: ${err}. Never retrying -- No retry time set`);
|
|
160
|
+
resolve(false);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
return ret;
|
|
164
|
+
} catch (err) { logger.error(`Error opening net serial port. ${this._cfg.netHost}:${this._cfg.netPort}`); }
|
|
165
|
+
}
|
|
166
|
+
protected async closeNetSerialPort(): Promise<boolean> {
|
|
167
|
+
try {
|
|
168
|
+
this._closing = true;
|
|
169
|
+
if (this.connTimer) clearTimeout(this.connTimer);
|
|
170
|
+
this.connTimer = null;
|
|
171
|
+
if (typeof this._port !== 'undefined' && this.isOpen) {
|
|
172
|
+
let success = await new Promise<boolean>((resolve, reject) => {
|
|
173
|
+
if (this._cfg.netConnect) {
|
|
174
|
+
this._port.removeAllListeners();
|
|
175
|
+
this._port.once('error', (err) => {
|
|
176
|
+
if (err) {
|
|
177
|
+
logger.error(`Net connect (socat) error closing ${this._cfg.netHost}:${this._cfg.netPort}: ${err}`);
|
|
178
|
+
resolve(false);
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
conn._port = undefined;
|
|
182
|
+
this.isOpen = false;
|
|
183
|
+
logger.info(`Successfully closed (socat) port ${this._cfg.netHost}:${this._cfg.netPort}`);
|
|
184
|
+
resolve(true);
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
this._port.once('close', (p) => {
|
|
188
|
+
this.isOpen = false;
|
|
189
|
+
this._port = undefined;
|
|
190
|
+
logger.info(`Net connect (socat) successfully closed: ${this._cfg.netHost}:${this._cfg.netPort}`);
|
|
191
|
+
resolve(true);
|
|
192
|
+
});
|
|
193
|
+
this._port.destroy();
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
resolve(true);
|
|
197
|
+
conn._port = undefined;
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
if (success) {
|
|
201
|
+
if (typeof conn.buffer !== 'undefined') conn.buffer.close();
|
|
202
|
+
}
|
|
203
|
+
return success;
|
|
204
|
+
}
|
|
205
|
+
return true;
|
|
206
|
+
} catch (err) { logger.error(`Error closing comms connection: ${err.message}`); return Promise.resolve(false); }
|
|
207
|
+
|
|
208
|
+
}
|
|
85
209
|
public async openAsync(): Promise<boolean> {
|
|
86
|
-
if (typeof
|
|
210
|
+
if (typeof this.buffer === 'undefined') {
|
|
87
211
|
this.buffer = new SendRecieveBuffer();
|
|
88
212
|
this.emitter.on('packetread', (pkt) => { this.buffer.pushIn(pkt); });
|
|
89
213
|
this.emitter.on('messagewrite', (msg) => { this.buffer.pushOut(msg); });
|
|
@@ -97,18 +221,38 @@ export class Connection {
|
|
|
97
221
|
let nc: net.Socket = new net.Socket();
|
|
98
222
|
nc.on('connect', () => { logger.info(`Net connect (socat) connected to: ${this._cfg.netHost}:${this._cfg.netPort}`); }); // Socket is opened but not yet ready.
|
|
99
223
|
nc.on('ready', () => {
|
|
224
|
+
this.isOpen = true;
|
|
225
|
+
this.isRTS = true;
|
|
100
226
|
logger.info(`Net connect (socat) ready and communicating: ${this._cfg.netHost}:${this._cfg.netPort}`);
|
|
101
|
-
nc.on('data', (data) => {
|
|
227
|
+
nc.on('data', (data) => {
|
|
228
|
+
//this.resetConnTimer();
|
|
229
|
+
if (data.length > 0 && !this.isPaused) this.emitter.emit('packetread', data);
|
|
230
|
+
});
|
|
102
231
|
});
|
|
103
232
|
nc.on('close', (p) => {
|
|
104
233
|
this.isOpen = false;
|
|
105
234
|
if (typeof this._port !== 'undefined') this._port.destroy();
|
|
106
235
|
this._port = undefined;
|
|
236
|
+
this.buffer.clearOutbound();
|
|
237
|
+
if (!this._closing) {
|
|
238
|
+
// If we are closing manually this event should have been cleared already and should never be called. If this is fired out
|
|
239
|
+
// of sequence then we will check the closing flag to ensure we are not forcibly closing the socket.
|
|
240
|
+
if (typeof this.connTimer !== 'undefined' && this.connTimer) {
|
|
241
|
+
clearTimeout(this.connTimer);
|
|
242
|
+
this.connTimer = null;
|
|
243
|
+
}
|
|
244
|
+
this.connTimer = setTimeout(async () => {
|
|
245
|
+
try {
|
|
246
|
+
// We are already closed so give some inactivity retry and try again.
|
|
247
|
+
await conn.openAsync();
|
|
248
|
+
} catch (err) { }
|
|
249
|
+
}, this._cfg.inactivityRetry * 1000);
|
|
250
|
+
}
|
|
107
251
|
logger.info(`Net connect (socat) closed ${p === true ? 'due to error' : ''}: ${this._cfg.netHost}:${this._cfg.netPort}`);
|
|
108
252
|
});
|
|
109
253
|
nc.on('end', () => { // Happens when the other end of the socket closes.
|
|
110
254
|
this.isOpen = false;
|
|
111
|
-
this.resetConnTimer();
|
|
255
|
+
//this.resetConnTimer();
|
|
112
256
|
logger.info(`Net connect (socat) end event was fired`);
|
|
113
257
|
});
|
|
114
258
|
//nc.on('drain', () => { logger.info(`The drain event was fired.`); });
|
|
@@ -116,15 +260,28 @@ export class Connection {
|
|
|
116
260
|
// Occurs when there is no activity. This should not reset the connection, the previous implementation did so and
|
|
117
261
|
// left the connection in a weird state where the previous connection was processing events and the new connection was
|
|
118
262
|
// doing so as well. This isn't an error it is a warning as the RS485 bus will most likely be communicating at all times.
|
|
119
|
-
nc.on('timeout', () => { logger.warn(`Net connect (socat) Connection Idle: ${this._cfg.netHost}:${this._cfg.netPort}`); });
|
|
263
|
+
//nc.on('timeout', () => { logger.warn(`Net connect (socat) Connection Idle: ${this._cfg.netHost}:${this._cfg.netPort}`); });
|
|
264
|
+
nc.setTimeout(Math.max(this._cfg.inactivityRetry, 10) * 1000, async () => {
|
|
265
|
+
logger.warn(`Net connect (socat) connection idle: ${this._cfg.netHost}:${this._cfg.netPort} retrying connection.`);
|
|
266
|
+
try {
|
|
267
|
+
await conn.endAsync();
|
|
268
|
+
await conn.openAsync();
|
|
269
|
+
} catch (err) { logger.error(`Net connect (socat) error retrying connection ${err.message}`); }
|
|
270
|
+
});
|
|
271
|
+
|
|
120
272
|
return await new Promise<boolean>((resolve, _) => {
|
|
121
273
|
// We only connect an error once as we will destroy this connection on error then recreate a new socket on failure.
|
|
122
274
|
nc.once('error', (err) => {
|
|
123
|
-
logger.error(`Net connect (socat) Connection: ${err}. ${this._cfg.inactivityRetry > 0 ? `Retry in ${this._cfg.inactivityRetry} seconds` : `Never retrying; inactivityRetry set to ${this._cfg.inactivityRetry}`}`);
|
|
124
|
-
this.resetConnTimer();
|
|
275
|
+
//logger.error(`Net connect (socat) Connection: ${err}. ${this._cfg.inactivityRetry > 0 ? `Retry in ${this._cfg.inactivityRetry} seconds` : `Never retrying; inactivityRetry set to ${this._cfg.inactivityRetry}`}`);
|
|
276
|
+
//this.resetConnTimer();
|
|
125
277
|
this.isOpen = false;
|
|
126
278
|
// if the promise has already been fulfilled, but the error happens later, we don't want to call the promise again.
|
|
127
279
|
if (typeof resolve !== 'undefined') { resolve(false); }
|
|
280
|
+
if (this._cfg.inactivityRetry > 0) {
|
|
281
|
+
logger.error(`Net connect (socat) connection error: ${err}. Retry in ${this._cfg.inactivityRetry} seconds`);
|
|
282
|
+
setTimeout(async () => { try { await conn.openAsync(); } catch (err) { } }, this._cfg.inactivityRetry * 1000);
|
|
283
|
+
}
|
|
284
|
+
else logger.error(`Net connect (socat) connection error: ${err}. Never retrying -- No retry time set`);
|
|
128
285
|
});
|
|
129
286
|
nc.connect(conn._cfg.netPort, conn._cfg.netHost, () => {
|
|
130
287
|
if (typeof this._port !== 'undefined') logger.warn('Net connect (socat) recovered from lost connection.');
|
|
@@ -243,12 +400,67 @@ export class Connection {
|
|
|
243
400
|
if (success) {
|
|
244
401
|
if (typeof conn.buffer !== 'undefined') conn.buffer.close();
|
|
245
402
|
}
|
|
246
|
-
|
|
247
403
|
return success;
|
|
248
404
|
}
|
|
249
405
|
return true;
|
|
250
406
|
} catch (err) { logger.error(`Error closing comms connection: ${err.message}`); return Promise.resolve(false); }
|
|
251
407
|
}
|
|
408
|
+
public async endAsync(): Promise<boolean> {
|
|
409
|
+
try {
|
|
410
|
+
this._closing = true;
|
|
411
|
+
if (this.connTimer) clearTimeout(this.connTimer);
|
|
412
|
+
if (typeof this._port !== 'undefined' && this.isOpen) {
|
|
413
|
+
let success = await new Promise<boolean>((resolve, reject) => {
|
|
414
|
+
if (this._cfg.netConnect) {
|
|
415
|
+
this._port.removeAllListeners();
|
|
416
|
+
this._port.once('error', (err) => {
|
|
417
|
+
if (err) {
|
|
418
|
+
logger.error(`Error closing ${this._cfg.netHost}:${this._cfg.netPort}/${this._cfg.rs485Port}: ${err}`);
|
|
419
|
+
resolve(false);
|
|
420
|
+
}
|
|
421
|
+
else {
|
|
422
|
+
conn._port = undefined;
|
|
423
|
+
this.isOpen = false;
|
|
424
|
+
logger.info(`Successfully closed (socat) port ${this._cfg.netHost}:${this._cfg.netPort}/${this._cfg.rs485Port}`);
|
|
425
|
+
resolve(true);
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
this._port.once('close', (p) => {
|
|
429
|
+
this.isOpen = false;
|
|
430
|
+
this._port = undefined;
|
|
431
|
+
logger.info(`Net connect (socat) successfully closed: ${this._cfg.netHost}:${this._cfg.netPort}`);
|
|
432
|
+
resolve(true);
|
|
433
|
+
});
|
|
434
|
+
this._port.destroy();
|
|
435
|
+
}
|
|
436
|
+
else if (typeof conn._port.close === 'function') {
|
|
437
|
+
conn._port.close((err) => {
|
|
438
|
+
if (err) {
|
|
439
|
+
logger.error(`Error closing ${this._cfg.rs485Port}: ${err}`);
|
|
440
|
+
resolve(false);
|
|
441
|
+
}
|
|
442
|
+
else {
|
|
443
|
+
conn._port = undefined;
|
|
444
|
+
logger.info(`Successfully closed seral port ${this._cfg.rs485Port}`);
|
|
445
|
+
resolve(true);
|
|
446
|
+
this.isOpen = false;
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
else {
|
|
451
|
+
resolve(true);
|
|
452
|
+
conn._port = undefined;
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
if (success) {
|
|
456
|
+
if (typeof conn.buffer !== 'undefined') conn.buffer.close();
|
|
457
|
+
}
|
|
458
|
+
return success;
|
|
459
|
+
}
|
|
460
|
+
return true;
|
|
461
|
+
} catch (err) { logger.error(`Error closing comms connection: ${err.message}`); return Promise.resolve(false); }
|
|
462
|
+
finally { this._closing = false; }
|
|
463
|
+
}
|
|
252
464
|
public drain(cb: Function) {
|
|
253
465
|
if (typeof (conn._port.drain) === 'function')
|
|
254
466
|
conn._port.drain(cb);
|
|
@@ -347,7 +559,7 @@ export class SendRecieveBuffer {
|
|
|
347
559
|
public pushOut(msg) { conn.buffer._outBuffer.push(msg); setTimeout(() => { this.processPackets(); }, 0); }
|
|
348
560
|
public clear() { conn.buffer._inBuffer.length = 0; conn.buffer._outBuffer.length = 0; }
|
|
349
561
|
public close() { clearTimeout(conn.buffer.procTimer); conn.buffer.clear(); this._msg = undefined; }
|
|
350
|
-
|
|
562
|
+
public clearOutbound() { conn.buffer._outBuffer.length = 0; conn.buffer._waitingPacket = undefined; }
|
|
351
563
|
/********************************************************************
|
|
352
564
|
* RKS: 06-06-20
|
|
353
565
|
* This used to process every 175ms. While the processing was light
|
|
@@ -168,12 +168,13 @@ export class Inbound extends Message {
|
|
|
168
168
|
return `{"id":${this.id},"valid":${this.isValid},"dir":"${this.direction}","proto":"${this.protocol}","for":${JSON.stringify(this.responseFor)},"pkt":[${JSON.stringify(this.padding)},${JSON.stringify(this.preamble)},${JSON.stringify(this.header)},${JSON.stringify(this.payload)},${JSON.stringify(this.term)}],"ts": "${Timestamp.toISOLocal(this.timestamp)}"}`;
|
|
169
169
|
return `{"id":${this.id},"valid":${this.isValid},"dir":"${this.direction}","proto":"${this.protocol}","pkt":[${JSON.stringify(this.padding)},${JSON.stringify(this.preamble)},${JSON.stringify(this.header)},${JSON.stringify(this.payload)},${JSON.stringify(this.term)}],"ts": "${Timestamp.toISOLocal(this.timestamp)}"}`;
|
|
170
170
|
}
|
|
171
|
-
private testChlorHeader(bytes: number[], ndx: number): boolean {
|
|
171
|
+
private testChlorHeader(bytes: number[], ndx: number): boolean {
|
|
172
172
|
// if packets have 16,2 (eg status=16,2,29) in them and they come as partial packets, they would have
|
|
173
173
|
// prev been detected as chlor packets;
|
|
174
174
|
// valid chlor packets should have 16,2,0 or 16,2,[80-96];
|
|
175
175
|
// this should reduce the number of false chlor packets
|
|
176
|
-
return (ndx + 2 < bytes.length && bytes[ndx] === 16 && bytes[ndx + 1] === 2 && (bytes[ndx + 2] === 0 || (bytes[ndx + 2] >= 80 && bytes[ndx + 2] <= 96)))
|
|
176
|
+
return (ndx + 2 < bytes.length && bytes[ndx] === 16 && bytes[ndx + 1] === 2 && (bytes[ndx + 2] === 0 || (bytes[ndx + 2] >= 80 && bytes[ndx + 2] <= 96)))
|
|
177
|
+
}
|
|
177
178
|
private testBroadcastHeader(bytes: number[], ndx: number): boolean { return ndx < bytes.length - 3 && bytes[ndx] === 255 && bytes[ndx + 1] === 0 && bytes[ndx + 2] === 255 && bytes[ndx + 3] === 165; }
|
|
178
179
|
private testUnidentifiedHeader(bytes: number[], ndx: number): boolean { return ndx < bytes.length - 3 && bytes[ndx] === 255 && bytes[ndx + 1] === 0 && bytes[ndx + 2] === 255 && bytes[ndx + 3] !== 165; }
|
|
179
180
|
private testChlorTerm(bytes: number[], ndx: number): boolean { return ndx < bytes.length - 2 && bytes[ndx + 1] === 16 && bytes[ndx + 2] === 3; }
|
|
@@ -404,6 +405,7 @@ export class Inbound extends Message {
|
|
|
404
405
|
case ControllerType.IntelliCenter:
|
|
405
406
|
switch (this.action) {
|
|
406
407
|
case 1: // ACK
|
|
408
|
+
this.isProcessed = true;
|
|
407
409
|
break;
|
|
408
410
|
case 2:
|
|
409
411
|
case 204:
|
|
@@ -423,12 +425,14 @@ export class Inbound extends Message {
|
|
|
423
425
|
break;
|
|
424
426
|
case 222: // A panel is asking for action 30s
|
|
425
427
|
case 228: // A panel is asking for the current version
|
|
428
|
+
this.isProcessed = true;
|
|
426
429
|
break;
|
|
427
430
|
default:
|
|
428
431
|
logger.info(`An unprocessed message was received ${this.toPacket()}`)
|
|
429
432
|
break;
|
|
430
433
|
|
|
431
434
|
}
|
|
435
|
+
if (!this.isProcessed) logger.info(`The message was not processed ${this.action} - ${this.toPacket()}`);
|
|
432
436
|
break;
|
|
433
437
|
default:
|
|
434
438
|
switch (this.action) {
|
|
@@ -483,14 +487,12 @@ export class Inbound extends Message {
|
|
|
483
487
|
CircuitMessage.processTouch(this);
|
|
484
488
|
break;
|
|
485
489
|
case 40:
|
|
490
|
+
case 168:
|
|
486
491
|
OptionsMessage.process(this);
|
|
487
492
|
break;
|
|
488
493
|
case 41:
|
|
489
494
|
CircuitGroupMessage.process(this);
|
|
490
495
|
break;
|
|
491
|
-
case 168:
|
|
492
|
-
if (sys.controllerType !== ControllerType.Unknown) HeaterMessage.process(this);
|
|
493
|
-
break;
|
|
494
496
|
case 197:
|
|
495
497
|
EquipmentStateMessage.process(this); // Date/Time request
|
|
496
498
|
break;
|
|
@@ -500,17 +502,19 @@ export class Inbound extends Message {
|
|
|
500
502
|
case 9:
|
|
501
503
|
case 16:
|
|
502
504
|
case 34:
|
|
503
|
-
case 114:
|
|
504
505
|
case 137:
|
|
505
506
|
case 144:
|
|
506
507
|
case 162:
|
|
507
508
|
HeaterMessage.process(this);
|
|
508
509
|
break;
|
|
510
|
+
case 114:
|
|
511
|
+
case 115:
|
|
512
|
+
HeaterStateMessage.process(this);
|
|
513
|
+
break
|
|
509
514
|
case 147:
|
|
510
515
|
IntellichemMessage.process(this);
|
|
511
516
|
break;
|
|
512
517
|
default:
|
|
513
|
-
// take these out...
|
|
514
518
|
if (this.action === 109 && this.payload[1] === 3) break;
|
|
515
519
|
if (this.source === 17 && this.payload[0] === 109) break;
|
|
516
520
|
logger.debug(`Packet not processed: ${this.toPacket()}`);
|
|
@@ -626,11 +630,11 @@ export class Outbound extends OutboundCommon {
|
|
|
626
630
|
out.onComplete = obj.onComplete;
|
|
627
631
|
out.onAbort = obj.onAbort;
|
|
628
632
|
out.timeout = obj.timeout;
|
|
629
|
-
for (let i = 0; i < out.header.length; i++){
|
|
633
|
+
for (let i = 0; i < out.header.length; i++) {
|
|
630
634
|
if (out.header[i] >= 0 && out.header[i] <= 255 && out.header[i] !== null && typeof out.header[i] !== 'undefined') continue;
|
|
631
635
|
throw new OutboundMessageError(out, `Invalid header detected: ${out.toShortPacket()}`);
|
|
632
636
|
}
|
|
633
|
-
for (let i = 0; i < out.payload.length; i++){
|
|
637
|
+
for (let i = 0; i < out.payload.length; i++) {
|
|
634
638
|
if (out.payload[i] >= 0 && out.payload[i] <= 255 && out.payload[i] !== null && typeof out.payload[i] !== 'undefined') continue;
|
|
635
639
|
throw new OutboundMessageError(out, `Invalid payload detected: ${out.toShortPacket()}`);
|
|
636
640
|
}
|
|
@@ -27,15 +27,19 @@ export class ChlorinatorMessage {
|
|
|
27
27
|
chlorId = 1;
|
|
28
28
|
for (let i = 0; i < 4 && i + 30 < msg.payload.length; i++) {
|
|
29
29
|
let isActive = msg.extractPayloadByte(i + 22) === 1;
|
|
30
|
+
chlor = sys.chlorinators.getItemById(chlorId);
|
|
31
|
+
if (chlor.master === 1) continue; // RSG: probably never need this. See Touch chlor below.
|
|
30
32
|
if (isActive) {
|
|
31
|
-
|
|
33
|
+
chlor = sys.chlorinators.getItemById(chlorId, true);
|
|
32
34
|
let schlor = state.chlorinators.getItemById(chlor.id, true);
|
|
33
35
|
chlor.isActive = schlor.isActive = true;
|
|
34
36
|
chlor.body = msg.extractPayloadByte(i + 2);
|
|
35
37
|
chlor.type = msg.extractPayloadByte(i + 6);
|
|
36
|
-
if (!chlor.disabled) {
|
|
38
|
+
if (!chlor.disabled && !chlor.isDosing) {
|
|
37
39
|
// RKS: We don't want to change the setpoints if our chem controller disabled
|
|
38
40
|
// the chlorinator. These should be 0.
|
|
41
|
+
if (msg.extractPayloadByte(i + 10) === 0 && chlor.poolSetpoint > 0) logger.info(`Changing pool setpoint to 0 ${msg.extractPayloadByte(i + 10)}`);
|
|
42
|
+
|
|
39
43
|
chlor.poolSetpoint = msg.extractPayloadByte(i + 10);
|
|
40
44
|
chlor.spaSetpoint = msg.extractPayloadByte(i + 14);
|
|
41
45
|
}
|
|
@@ -57,6 +61,7 @@ export class ChlorinatorMessage {
|
|
|
57
61
|
}
|
|
58
62
|
chlorId++;
|
|
59
63
|
}
|
|
64
|
+
msg.isProcessed = true;
|
|
60
65
|
break;
|
|
61
66
|
default:
|
|
62
67
|
logger.debug(`Unprocessed Config Message ${msg.toPacket()}`)
|
|
@@ -65,6 +70,8 @@ export class ChlorinatorMessage {
|
|
|
65
70
|
}
|
|
66
71
|
public static processTouch(msg: Inbound) {
|
|
67
72
|
// This is for the 25 message that is broadcast from the OCP.
|
|
73
|
+
let chlor = sys.chlorinators.getItemById(1);
|
|
74
|
+
if (chlor.master === 1) return; // Some Aquarite chlors need more frequent control (via Nixie) but will be disabled via Touch. https://github.com/tagyoureit/nodejs-poolController/issues/349
|
|
68
75
|
let isActive = (msg.extractPayloadByte(0) & 0x01) === 1;
|
|
69
76
|
if (isActive) {
|
|
70
77
|
let chlor = sys.chlorinators.getItemById(1, true);
|
|
@@ -78,8 +85,9 @@ export class ChlorinatorMessage {
|
|
|
78
85
|
chlor.address = chlor.id + 79;
|
|
79
86
|
schlor.body = chlor.body = sys.equipment.maxBodies >= 1 || sys.equipment.shared === true ? 32 : 0;
|
|
80
87
|
}
|
|
81
|
-
schlor.name = chlor.name = msg.extractPayloadString(6, 16);
|
|
82
|
-
if (typeof chlor.model === 'undefined') chlor.model = schlor.name;
|
|
88
|
+
if (typeof chlor.name === 'undefined') schlor.name = chlor.name = msg.extractPayloadString(6, 16);
|
|
89
|
+
if (typeof chlor.model === 'undefined') chlor.model = sys.board.valueMaps.chlorinatorModel.getValue(schlor.name.toLowerCase());
|
|
90
|
+
if (typeof chlor.type === 'undefined') chlor.type = schlor.type = 0;
|
|
83
91
|
schlor.saltLevel = msg.extractPayloadByte(3) * 50 || schlor.saltLevel;
|
|
84
92
|
schlor.status = msg.extractPayloadByte(4) & 0x007F; // Strip off the high bit. The chlorinator does not actually report this.;
|
|
85
93
|
// Pull the hours from the 25 message.
|
|
@@ -110,5 +118,6 @@ export class ChlorinatorMessage {
|
|
|
110
118
|
sys.chlorinators.removeItemById(1);
|
|
111
119
|
state.chlorinators.removeItemById(1);
|
|
112
120
|
}
|
|
121
|
+
msg.isProcessed = true;
|
|
113
122
|
}
|
|
114
123
|
}
|
|
@@ -80,6 +80,7 @@ export class CircuitGroupMessage {
|
|
|
80
80
|
group.circuits.removeItemByIndex(i);
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
|
+
msg.isProcessed = true;
|
|
83
84
|
}
|
|
84
85
|
else if (msgId >= 16 && msgId <= 31) {
|
|
85
86
|
groupId = msgId - 16 + sys.board.equipmentIds.circuitGroups.start;
|
|
@@ -90,6 +91,7 @@ export class CircuitGroupMessage {
|
|
|
90
91
|
group.name = msg.extractPayloadString(2, 16);
|
|
91
92
|
sgroup.name = group.name;
|
|
92
93
|
}
|
|
94
|
+
msg.isProcessed = true;
|
|
93
95
|
}
|
|
94
96
|
}
|
|
95
97
|
}
|
|
@@ -143,6 +145,7 @@ export class CircuitGroupMessage {
|
|
|
143
145
|
state.circuitGroups.removeItemById(groupId);
|
|
144
146
|
}
|
|
145
147
|
state.emitEquipmentChanges();
|
|
148
|
+
msg.isProcessed = true;
|
|
146
149
|
}
|
|
147
150
|
private static processGroupType(msg: Inbound) {
|
|
148
151
|
var groupId = ((msg.extractPayloadByte(1) - 32) * 16) + sys.board.equipmentIds.circuitGroups.start;
|
|
@@ -188,6 +191,7 @@ export class CircuitGroupMessage {
|
|
|
188
191
|
sgroup.type = group.type;
|
|
189
192
|
}
|
|
190
193
|
state.emitEquipmentChanges();
|
|
194
|
+
msg.isProcessed = true;
|
|
191
195
|
}
|
|
192
196
|
private static processColor(msg: Inbound) {
|
|
193
197
|
var groupId = ((msg.extractPayloadByte(1) - 35)) + sys.board.equipmentIds.circuitGroups.start;
|
|
@@ -208,6 +212,7 @@ export class CircuitGroupMessage {
|
|
|
208
212
|
}
|
|
209
213
|
|
|
210
214
|
}
|
|
215
|
+
msg.isProcessed = true;
|
|
211
216
|
}
|
|
212
217
|
private static processEggTimer(msg: Inbound) {
|
|
213
218
|
var groupId = ((msg.extractPayloadByte(1) - 34) * 16) + sys.board.equipmentIds.circuitGroups.start;
|
|
@@ -220,5 +225,6 @@ export class CircuitGroupMessage {
|
|
|
220
225
|
// sgroup.eggTimer = group.eggTimer;
|
|
221
226
|
}
|
|
222
227
|
}
|
|
228
|
+
msg.isProcessed = true;
|
|
223
229
|
}
|
|
224
230
|
}
|
|
File without changes
|
|
File without changes
|
|
@@ -32,6 +32,7 @@ export class CoverMessage {
|
|
|
32
32
|
for (let i = 1; i < 10; i++) {
|
|
33
33
|
if (msg.extractPayloadByte(i + 18) !== 255) cover.circuits.push(msg.extractPayloadByte(i + 18));
|
|
34
34
|
}
|
|
35
|
+
msg.isProcessed = true;
|
|
35
36
|
break;
|
|
36
37
|
default:
|
|
37
38
|
logger.debug(`Unprocessed Config Message ${msg.toPacket()}`)
|
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
/* nodejs-poolController. An application to control pool equipment.
|
|
2
|
-
Copyright (C) 2016, 2017, 2018, 2019, 2020. Russell Goldin, tagyoureit. russ.goldin@gmail.com
|
|
3
|
-
|
|
4
|
-
This program is free software: you can redistribute it and/or modify
|
|
5
|
-
it under the terms of the GNU Affero General Public License as
|
|
6
|
-
published by the Free Software Foundation, either version 3 of the
|
|
7
|
-
License, or (at your option) any later version.
|
|
8
|
-
|
|
9
|
-
This program is distributed in the hope that it will be useful,
|
|
10
|
-
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
-
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
-
GNU Affero General Public License for more details.
|
|
13
|
-
|
|
14
|
-
You should have received a copy of the GNU Affero General Public License
|
|
15
|
-
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
16
|
-
*/
|
|
17
|
-
import { Inbound } from "../Messages";
|
|
18
|
-
import { sys } from "../../../Equipment";
|
|
19
|
-
export class CustomNameMessage
|
|
20
|
-
{
|
|
21
|
-
public static process ( msg: Inbound ): void
|
|
22
|
-
{
|
|
23
|
-
let customNameId = msg.extractPayloadByte( 0 );
|
|
24
|
-
let customName = sys.customNames.getItemById( customNameId, customNameId <= sys.equipment.maxCustomNames );
|
|
25
|
-
customName.name = msg.extractPayloadString( 1, 11 );
|
|
26
|
-
// customName.isActive = customNameId <= sys.equipment.maxCustomNames && !customName.name.includes('USERNAME-')
|
|
27
|
-
if (customNameId >= sys.equipment.maxCustomNames) sys.equipment.maxCustomNames = customNameId + 1;
|
|
28
|
-
sys.board.system.syncCustomNamesValueMap();
|
|
29
|
-
msg.isProcessed = true;
|
|
30
|
-
}
|
|
1
|
+
/* nodejs-poolController. An application to control pool equipment.
|
|
2
|
+
Copyright (C) 2016, 2017, 2018, 2019, 2020. Russell Goldin, tagyoureit. russ.goldin@gmail.com
|
|
3
|
+
|
|
4
|
+
This program is free software: you can redistribute it and/or modify
|
|
5
|
+
it under the terms of the GNU Affero General Public License as
|
|
6
|
+
published by the Free Software Foundation, either version 3 of the
|
|
7
|
+
License, or (at your option) any later version.
|
|
8
|
+
|
|
9
|
+
This program is distributed in the hope that it will be useful,
|
|
10
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
GNU Affero General Public License for more details.
|
|
13
|
+
|
|
14
|
+
You should have received a copy of the GNU Affero General Public License
|
|
15
|
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
16
|
+
*/
|
|
17
|
+
import { Inbound } from "../Messages";
|
|
18
|
+
import { sys } from "../../../Equipment";
|
|
19
|
+
export class CustomNameMessage
|
|
20
|
+
{
|
|
21
|
+
public static process ( msg: Inbound ): void
|
|
22
|
+
{
|
|
23
|
+
let customNameId = msg.extractPayloadByte( 0 );
|
|
24
|
+
let customName = sys.customNames.getItemById( customNameId, customNameId <= sys.equipment.maxCustomNames );
|
|
25
|
+
customName.name = msg.extractPayloadString( 1, 11 );
|
|
26
|
+
// customName.isActive = customNameId <= sys.equipment.maxCustomNames && !customName.name.includes('USERNAME-')
|
|
27
|
+
if (customNameId >= sys.equipment.maxCustomNames) sys.equipment.maxCustomNames = customNameId + 1;
|
|
28
|
+
sys.board.system.syncCustomNamesValueMap();
|
|
29
|
+
msg.isProcessed = true;
|
|
30
|
+
}
|
|
31
31
|
}
|
|
@@ -48,6 +48,7 @@ export class EquipmentMessage {
|
|
|
48
48
|
body.capacity = msg.extractPayloadByte(34) * 1000;
|
|
49
49
|
if (body.isActive && sys.equipment.maxBodies === 0) sys.bodies.removeItemById(1);
|
|
50
50
|
body.isActive = sys.equipment.maxBodies > 0;
|
|
51
|
+
msg.isProcessed = true;
|
|
51
52
|
break;
|
|
52
53
|
case 1:
|
|
53
54
|
pnl = sys.equipment.expansions.getItemById(2);
|
|
@@ -66,6 +67,7 @@ export class EquipmentMessage {
|
|
|
66
67
|
}
|
|
67
68
|
pnl = sys.equipment.expansions.getItemById(3);
|
|
68
69
|
pnl.name = msg.extractPayloadString(18, 16);
|
|
70
|
+
msg.isProcessed = true;
|
|
69
71
|
break;
|
|
70
72
|
case 2:
|
|
71
73
|
// The first name is the first body in this packet and the second is the third. Go figure.
|
|
@@ -87,6 +89,7 @@ export class EquipmentMessage {
|
|
|
87
89
|
sys.bodies.removeItemById(bodyId);
|
|
88
90
|
state.temps.bodies.removeItemById(bodyId);
|
|
89
91
|
}
|
|
92
|
+
msg.isProcessed = true;
|
|
90
93
|
break;
|
|
91
94
|
case 3:
|
|
92
95
|
// The first name is the second body and the 2nd is the 4th. This packet also contains
|
|
@@ -136,6 +139,7 @@ export class EquipmentMessage {
|
|
|
136
139
|
state.equipment.maxValves = sys.equipment.maxValves;
|
|
137
140
|
state.equipment.maxSchedules = sys.equipment.maxSchedules;
|
|
138
141
|
state.equipment.maxPumps = sys.equipment.maxPumps;
|
|
142
|
+
msg.isProcessed = true;
|
|
139
143
|
break;
|
|
140
144
|
default:
|
|
141
145
|
logger.debug(`Unprocessed Config Message ${msg.toPacket()}`)
|