nodejs-poolcontroller 7.2.0 → 7.5.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/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
- package/Changelog +13 -0
- package/Dockerfile +1 -0
- package/README.md +5 -5
- package/app.ts +11 -0
- package/config/Config.ts +3 -0
- package/config/VersionCheck.ts +8 -4
- package/controller/Constants.ts +165 -9
- package/controller/Equipment.ts +186 -65
- package/controller/Errors.ts +22 -1
- package/controller/State.ts +273 -57
- package/controller/boards/EasyTouchBoard.ts +194 -95
- package/controller/boards/IntelliCenterBoard.ts +115 -42
- package/controller/boards/IntelliTouchBoard.ts +104 -30
- package/controller/boards/NixieBoard.ts +155 -53
- package/controller/boards/SystemBoard.ts +1529 -514
- package/controller/comms/Comms.ts +219 -42
- package/controller/comms/messages/Messages.ts +16 -4
- package/controller/comms/messages/config/ChlorinatorMessage.ts +13 -3
- package/controller/comms/messages/config/CircuitGroupMessage.ts +6 -0
- package/controller/comms/messages/config/CircuitMessage.ts +1 -1
- package/controller/comms/messages/config/CoverMessage.ts +1 -0
- package/controller/comms/messages/config/EquipmentMessage.ts +4 -0
- package/controller/comms/messages/config/ExternalMessage.ts +43 -25
- 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 +15 -9
- package/controller/comms/messages/config/IntellichemMessage.ts +4 -1
- package/controller/comms/messages/config/OptionsMessage.ts +13 -1
- 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 +11 -0
- package/controller/comms/messages/config/SecurityMessage.ts +1 -0
- package/controller/comms/messages/config/ValveMessage.ts +12 -2
- package/controller/comms/messages/status/ChlorinatorStateMessage.ts +14 -6
- package/controller/comms/messages/status/EquipmentStateMessage.ts +78 -24
- package/controller/comms/messages/status/HeaterStateMessage.ts +25 -5
- package/controller/comms/messages/status/IntelliChemStateMessage.ts +55 -26
- package/controller/nixie/Nixie.ts +18 -16
- package/controller/nixie/NixieEquipment.ts +6 -6
- package/controller/nixie/bodies/Body.ts +7 -4
- package/controller/nixie/bodies/Filter.ts +7 -4
- package/controller/nixie/chemistry/ChemController.ts +800 -283
- package/controller/nixie/chemistry/Chlorinator.ts +22 -14
- package/controller/nixie/circuits/Circuit.ts +42 -7
- package/controller/nixie/heaters/Heater.ts +303 -30
- package/controller/nixie/pumps/Pump.ts +57 -30
- package/controller/nixie/schedules/Schedule.ts +10 -7
- package/controller/nixie/valves/Valve.ts +7 -5
- package/defaultConfig.json +32 -1
- package/issue_template.md +1 -1
- package/logger/DataLogger.ts +37 -22
- package/package.json +20 -18
- package/web/Server.ts +529 -31
- package/web/bindings/influxDB.json +157 -5
- package/web/bindings/mqtt.json +112 -13
- package/web/bindings/mqttAlt.json +109 -11
- package/web/interfaces/baseInterface.ts +2 -1
- package/web/interfaces/httpInterface.ts +2 -0
- package/web/interfaces/influxInterface.ts +103 -54
- package/web/interfaces/mqttInterface.ts +16 -5
- package/web/services/config/Config.ts +179 -43
- package/web/services/state/State.ts +51 -5
- package/web/services/state/StateSocket.ts +19 -2
|
@@ -22,13 +22,16 @@ import { logger } from '../../logger/Logger';
|
|
|
22
22
|
import * as net from 'net';
|
|
23
23
|
import { setTimeout, setInterval } from 'timers';
|
|
24
24
|
import { Message, Outbound, Inbound, Response } from './messages/Messages';
|
|
25
|
-
import { MessageError, OutboundMessageError } from '../Errors';
|
|
25
|
+
import { InvalidOperationError, MessageError, OutboundMessageError } from '../Errors';
|
|
26
|
+
import { utils } from "../Constants";
|
|
27
|
+
import { webApp } from "../../web/Server";
|
|
26
28
|
const extend = require("extend");
|
|
27
29
|
export class Connection {
|
|
28
30
|
constructor() {
|
|
29
31
|
this.emitter = new EventEmitter();
|
|
30
32
|
}
|
|
31
33
|
public isOpen: boolean = false;
|
|
34
|
+
private _closing: boolean = false;
|
|
32
35
|
private _cfg: any;
|
|
33
36
|
private _port: any;
|
|
34
37
|
public mockPort: boolean = false;
|
|
@@ -38,19 +41,49 @@ export class Connection {
|
|
|
38
41
|
protected resetConnTimer(...args) {
|
|
39
42
|
//console.log(`resetting connection timer`);
|
|
40
43
|
if (conn.connTimer !== null) clearTimeout(conn.connTimer);
|
|
41
|
-
if (!conn._cfg.mockPort && conn._cfg.inactivityRetry > 0) conn.connTimer = setTimeout(async () => {
|
|
44
|
+
if (!conn._cfg.mockPort && conn._cfg.inactivityRetry > 0 && !conn._closing) conn.connTimer = setTimeout(async () => {
|
|
42
45
|
try {
|
|
43
|
-
await conn.openAsync()
|
|
46
|
+
await conn.openAsync();
|
|
44
47
|
}
|
|
45
|
-
catch (err) {};
|
|
46
|
-
}
|
|
47
|
-
, conn._cfg.inactivityRetry * 1000);
|
|
48
|
+
catch (err) { logger.error(`Error resetting RS485 port on inactivity: ${err.message}`); };
|
|
49
|
+
}, conn._cfg.inactivityRetry * 1000);
|
|
48
50
|
}
|
|
49
51
|
public isRTS: boolean = true;
|
|
50
52
|
public emitter: EventEmitter;
|
|
51
53
|
public get enabled(): boolean { return typeof this._cfg !== 'undefined' && this._cfg.enabled; }
|
|
54
|
+
public async setPortAsync(data: any) : Promise<any> {
|
|
55
|
+
try {
|
|
56
|
+
// Lets set the config data.
|
|
57
|
+
let pdata = config.getSection('controller.comms', {});
|
|
58
|
+
pdata.enabled = typeof data.enabled !== 'undefined' ? utils.makeBool(data.enabled) : utils.makeBool(pdata.enabled);
|
|
59
|
+
pdata.netConnect = typeof data.netConnect !== 'undefined' ? utils.makeBool(data.netConnect) : utils.makeBool(pdata.netConnect);
|
|
60
|
+
pdata.rs485Port = typeof data.rs485Port !== 'undefined' ? data.rs485Port : pdata.rs485Port;
|
|
61
|
+
pdata.inactivityRetry = typeof data.inactivityRetry === 'number' ? data.inactivityRetry : pdata.inactivityRetry;
|
|
62
|
+
if (pdata.netConnect) {
|
|
63
|
+
pdata.netHost = typeof data.netHost !== 'undefined' ? data.netHost : pdata.netHost;
|
|
64
|
+
pdata.netPort = typeof data.netPort === 'number' ? data.netPort : pdata.netPort;
|
|
65
|
+
}
|
|
66
|
+
if (!await this.closeAsync()) {
|
|
67
|
+
return Promise.reject(new InvalidOperationError(`Unable to close the current RS485 port`, 'setPortAsync'));
|
|
68
|
+
}
|
|
69
|
+
config.setSection('controller.comms', pdata);
|
|
70
|
+
this._cfg = config.getSection('controller.comms', {
|
|
71
|
+
rs485Port: "/dev/ttyUSB0",
|
|
72
|
+
portSettings: { baudRate: 9600, dataBits: 8, parity: 'none', stopBits: 1, flowControl: false, autoOpen: false, lock: false },
|
|
73
|
+
mockPort: false,
|
|
74
|
+
netConnect: false,
|
|
75
|
+
netHost: "raspberrypi",
|
|
76
|
+
netPort: 9801,
|
|
77
|
+
inactivityRetry: 10
|
|
78
|
+
});
|
|
79
|
+
if (!await this.openAsync()) {
|
|
80
|
+
return Promise.reject(new InvalidOperationError(`Unable to open RS485 port ${pdata.rs485Port}`, 'setPortAsync'));
|
|
81
|
+
}
|
|
82
|
+
return this._cfg;
|
|
83
|
+
} catch (err) { return Promise.reject(err); }
|
|
84
|
+
}
|
|
52
85
|
public async openAsync(): Promise<boolean> {
|
|
53
|
-
if (typeof
|
|
86
|
+
if (typeof this.buffer === 'undefined') {
|
|
54
87
|
this.buffer = new SendRecieveBuffer();
|
|
55
88
|
this.emitter.on('packetread', (pkt) => { this.buffer.pushIn(pkt); });
|
|
56
89
|
this.emitter.on('messagewrite', (msg) => { this.buffer.pushOut(msg); });
|
|
@@ -64,18 +97,24 @@ export class Connection {
|
|
|
64
97
|
let nc: net.Socket = new net.Socket();
|
|
65
98
|
nc.on('connect', () => { logger.info(`Net connect (socat) connected to: ${this._cfg.netHost}:${this._cfg.netPort}`); }); // Socket is opened but not yet ready.
|
|
66
99
|
nc.on('ready', () => {
|
|
100
|
+
this.isOpen = true;
|
|
101
|
+
this.isRTS = true;
|
|
67
102
|
logger.info(`Net connect (socat) ready and communicating: ${this._cfg.netHost}:${this._cfg.netPort}`);
|
|
68
|
-
nc.on('data', (data) => {
|
|
103
|
+
nc.on('data', (data) => {
|
|
104
|
+
//this.resetConnTimer();
|
|
105
|
+
if (data.length > 0 && !this.isPaused) this.emitter.emit('packetread', data);
|
|
106
|
+
});
|
|
69
107
|
});
|
|
70
108
|
nc.on('close', (p) => {
|
|
71
109
|
this.isOpen = false;
|
|
72
110
|
if (typeof this._port !== 'undefined') this._port.destroy();
|
|
73
111
|
this._port = undefined;
|
|
112
|
+
this.buffer.clearOutbound();
|
|
74
113
|
logger.info(`Net connect (socat) closed ${p === true ? 'due to error' : ''}: ${this._cfg.netHost}:${this._cfg.netPort}`);
|
|
75
114
|
});
|
|
76
115
|
nc.on('end', () => { // Happens when the other end of the socket closes.
|
|
77
116
|
this.isOpen = false;
|
|
78
|
-
this.resetConnTimer();
|
|
117
|
+
//this.resetConnTimer();
|
|
79
118
|
logger.info(`Net connect (socat) end event was fired`);
|
|
80
119
|
});
|
|
81
120
|
//nc.on('drain', () => { logger.info(`The drain event was fired.`); });
|
|
@@ -83,18 +122,32 @@ export class Connection {
|
|
|
83
122
|
// Occurs when there is no activity. This should not reset the connection, the previous implementation did so and
|
|
84
123
|
// left the connection in a weird state where the previous connection was processing events and the new connection was
|
|
85
124
|
// 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.
|
|
86
|
-
nc.on('timeout', () => { logger.warn(`Net connect (socat) Connection Idle: ${this._cfg.netHost}:${this._cfg.netPort}`); });
|
|
87
|
-
|
|
125
|
+
//nc.on('timeout', () => { logger.warn(`Net connect (socat) Connection Idle: ${this._cfg.netHost}:${this._cfg.netPort}`); });
|
|
126
|
+
nc.setTimeout(Math.max(this._cfg.inactivityRetry, 10) * 1000, async () => {
|
|
127
|
+
logger.warn(`Net connect (socat) connection idle: ${this._cfg.netHost}:${this._cfg.netPort} retrying connection.`);
|
|
128
|
+
try {
|
|
129
|
+
await conn.endAsync();
|
|
130
|
+
await conn.openAsync();
|
|
131
|
+
} catch (err) { logger.error(`Net connect (socat) error retrying connection ${err.message}`); }
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
return await new Promise<boolean>((resolve, _) => {
|
|
88
135
|
// We only connect an error once as we will destroy this connection on error then recreate a new socket on failure.
|
|
89
136
|
nc.once('error', (err) => {
|
|
90
|
-
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}`}`);
|
|
91
|
-
this.resetConnTimer();
|
|
137
|
+
//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}`}`);
|
|
138
|
+
//this.resetConnTimer();
|
|
92
139
|
this.isOpen = false;
|
|
93
140
|
// if the promise has already been fulfilled, but the error happens later, we don't want to call the promise again.
|
|
94
141
|
if (typeof resolve !== 'undefined') { resolve(false); }
|
|
142
|
+
if (this._cfg.inactivityRetry > 0) {
|
|
143
|
+
logger.error(`Net connect (socat) connection error: ${err}. Retry in ${this._cfg.inactivityRetry} seconds`);
|
|
144
|
+
setTimeout(async () => { try { await conn.openAsync(); } catch (err) { } }, this._cfg.inactivityRetry * 1000);
|
|
145
|
+
}
|
|
146
|
+
else logger.error(`Net connect (socat) connection error: ${err}. Never retrying -- No retry time set`);
|
|
95
147
|
});
|
|
96
148
|
nc.connect(conn._cfg.netPort, conn._cfg.netHost, () => {
|
|
97
149
|
if (typeof this._port !== 'undefined') logger.warn('Net connect (socat) recovered from lost connection.');
|
|
150
|
+
logger.info(`Net connect (socat) Connection connected`);
|
|
98
151
|
this._port = nc;
|
|
99
152
|
this.isOpen = true;
|
|
100
153
|
resolve(true);
|
|
@@ -159,19 +212,117 @@ export class Connection {
|
|
|
159
212
|
});
|
|
160
213
|
}
|
|
161
214
|
}
|
|
162
|
-
public closeAsync() {
|
|
215
|
+
public async closeAsync(): Promise<boolean> {
|
|
163
216
|
try {
|
|
164
|
-
|
|
165
|
-
if (
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
217
|
+
this._closing = true;
|
|
218
|
+
if (this.connTimer) clearTimeout(this.connTimer);
|
|
219
|
+
if (typeof this._port !== 'undefined' && this.isOpen) {
|
|
220
|
+
let success = await new Promise<boolean>((resolve, reject) => {
|
|
221
|
+
if (this._cfg.netConnect) {
|
|
222
|
+
this._port.removeAllListeners();
|
|
223
|
+
this._port.once('error', (err) => {
|
|
224
|
+
if (err) {
|
|
225
|
+
logger.error(`Error closing ${this._cfg.netHost}:${this._cfg.netPort}/${this._cfg.rs485Port}: ${err}`);
|
|
226
|
+
resolve(false);
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
conn._port = undefined;
|
|
230
|
+
this.isOpen = false;
|
|
231
|
+
logger.info(`Successfully closed (socat) port ${this._cfg.netHost}:${this._cfg.netPort}/${this._cfg.rs485Port}`);
|
|
232
|
+
resolve(true);
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
this._port.once('close', (p) => {
|
|
236
|
+
this.isOpen = false;
|
|
237
|
+
this._port = undefined;
|
|
238
|
+
logger.info(`Net connect (socat) successfully closed: ${this._cfg.netHost}:${this._cfg.netPort}`);
|
|
239
|
+
resolve(true);
|
|
240
|
+
});
|
|
241
|
+
this._port.destroy();
|
|
242
|
+
}
|
|
243
|
+
else if (typeof conn._port.close === 'function') {
|
|
244
|
+
conn._port.close((err) => {
|
|
245
|
+
if (err) {
|
|
246
|
+
logger.error(`Error closing ${this._cfg.rs485Port}: ${err}`);
|
|
247
|
+
resolve(false);
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
conn._port = undefined;
|
|
251
|
+
logger.info(`Successfully closed seral port ${this._cfg.rs485Port}`);
|
|
252
|
+
resolve(true);
|
|
253
|
+
this.isOpen = false;
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
resolve(true);
|
|
259
|
+
conn._port = undefined;
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
if (success) {
|
|
263
|
+
if (typeof conn.buffer !== 'undefined') conn.buffer.close();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return success;
|
|
172
267
|
}
|
|
173
|
-
|
|
174
|
-
} catch (err) { logger.error(`Error closing comms connection: ${err.message}`); }
|
|
268
|
+
return true;
|
|
269
|
+
} catch (err) { logger.error(`Error closing comms connection: ${err.message}`); return Promise.resolve(false); }
|
|
270
|
+
}
|
|
271
|
+
public async endAsync(): Promise<boolean> {
|
|
272
|
+
try {
|
|
273
|
+
this._closing = true;
|
|
274
|
+
if (this.connTimer) clearTimeout(this.connTimer);
|
|
275
|
+
if (typeof this._port !== 'undefined' && this.isOpen) {
|
|
276
|
+
let success = await new Promise<boolean>((resolve, reject) => {
|
|
277
|
+
if (this._cfg.netConnect) {
|
|
278
|
+
this._port.removeAllListeners();
|
|
279
|
+
this._port.once('error', (err) => {
|
|
280
|
+
if (err) {
|
|
281
|
+
logger.error(`Error closing ${this._cfg.netHost}:${this._cfg.netPort}/${this._cfg.rs485Port}: ${err}`);
|
|
282
|
+
resolve(false);
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
conn._port = undefined;
|
|
286
|
+
this.isOpen = false;
|
|
287
|
+
logger.info(`Successfully closed (socat) port ${this._cfg.netHost}:${this._cfg.netPort}/${this._cfg.rs485Port}`);
|
|
288
|
+
resolve(true);
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
this._port.once('close', (p) => {
|
|
292
|
+
this.isOpen = false;
|
|
293
|
+
this._port = undefined;
|
|
294
|
+
logger.info(`Net connect (socat) successfully closed: ${this._cfg.netHost}:${this._cfg.netPort}`);
|
|
295
|
+
resolve(true);
|
|
296
|
+
});
|
|
297
|
+
this._port.destroy();
|
|
298
|
+
}
|
|
299
|
+
else if (typeof conn._port.close === 'function') {
|
|
300
|
+
conn._port.close((err) => {
|
|
301
|
+
if (err) {
|
|
302
|
+
logger.error(`Error closing ${this._cfg.rs485Port}: ${err}`);
|
|
303
|
+
resolve(false);
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
conn._port = undefined;
|
|
307
|
+
logger.info(`Successfully closed seral port ${this._cfg.rs485Port}`);
|
|
308
|
+
resolve(true);
|
|
309
|
+
this.isOpen = false;
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
resolve(true);
|
|
315
|
+
conn._port = undefined;
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
if (success) {
|
|
319
|
+
if (typeof conn.buffer !== 'undefined') conn.buffer.close();
|
|
320
|
+
}
|
|
321
|
+
return success;
|
|
322
|
+
}
|
|
323
|
+
return true;
|
|
324
|
+
} catch (err) { logger.error(`Error closing comms connection: ${err.message}`); return Promise.resolve(false); }
|
|
325
|
+
finally { this._closing = false; }
|
|
175
326
|
}
|
|
176
327
|
public drain(cb: Function) {
|
|
177
328
|
if (typeof (conn._port.drain) === 'function')
|
|
@@ -265,12 +416,13 @@ export class SendRecieveBuffer {
|
|
|
265
416
|
private _waitingPacket: Outbound;
|
|
266
417
|
private _msg: Inbound;
|
|
267
418
|
public pushIn(pkt) {
|
|
268
|
-
|
|
419
|
+
let self = this;
|
|
420
|
+
conn.buffer._inBuffer.push.apply(conn.buffer._inBuffer, pkt.toJSON().data); setTimeout(() => { self.processPackets(); }, 0);
|
|
269
421
|
}
|
|
270
422
|
public pushOut(msg) { conn.buffer._outBuffer.push(msg); setTimeout(() => { this.processPackets(); }, 0); }
|
|
271
423
|
public clear() { conn.buffer._inBuffer.length = 0; conn.buffer._outBuffer.length = 0; }
|
|
272
424
|
public close() { clearTimeout(conn.buffer.procTimer); conn.buffer.clear(); this._msg = undefined; }
|
|
273
|
-
|
|
425
|
+
public clearOutbound() { conn.buffer._outBuffer.length = 0; conn.buffer._waitingPacket = undefined; }
|
|
274
426
|
/********************************************************************
|
|
275
427
|
* RKS: 06-06-20
|
|
276
428
|
* This used to process every 175ms. While the processing was light
|
|
@@ -303,6 +455,7 @@ export class SendRecieveBuffer {
|
|
|
303
455
|
let dt = new Date();
|
|
304
456
|
if (conn.buffer._waitingPacket.timestamp.getTime() + timeout < dt.getTime()) {
|
|
305
457
|
logger.silly(`Retrying outbound message after ${(dt.getTime() - conn.buffer._waitingPacket.timestamp.getTime()) / 1000} secs with ${conn.buffer._waitingPacket.remainingTries} attempt(s) left. - ${conn.buffer._waitingPacket.toShortPacket()} `);
|
|
458
|
+
conn.buffer.counter.sndRetries++;
|
|
306
459
|
conn.buffer.writeMessage(conn.buffer._waitingPacket);
|
|
307
460
|
}
|
|
308
461
|
return true;
|
|
@@ -339,7 +492,8 @@ export class SendRecieveBuffer {
|
|
|
339
492
|
// would be sitting idle for eternity.
|
|
340
493
|
if (conn.buffer._outBuffer.length > 0 || typeof conn.buffer._waitingPacket !== 'undefined' || conn.buffer._waitingPacket || typeof msg !== 'undefined') {
|
|
341
494
|
// Come back later as we still have items to send.
|
|
342
|
-
|
|
495
|
+
let self = this;
|
|
496
|
+
conn.buffer.procTimer = setTimeout(() => self.processPackets(), 100);
|
|
343
497
|
}
|
|
344
498
|
}
|
|
345
499
|
/*
|
|
@@ -375,6 +529,7 @@ export class SendRecieveBuffer {
|
|
|
375
529
|
setTimeout(msg.response.callback, 100, msg);
|
|
376
530
|
}
|
|
377
531
|
}
|
|
532
|
+
conn.buffer.counter.sndAborted++;
|
|
378
533
|
conn.isRTS = true;
|
|
379
534
|
return;
|
|
380
535
|
}
|
|
@@ -396,21 +551,26 @@ export class SendRecieveBuffer {
|
|
|
396
551
|
let error = new OutboundMessageError(msg, `Message aborted after ${msg.tries} attempt(s): ${err} `);
|
|
397
552
|
if (typeof msg.onComplete === 'function') msg.onComplete(error, undefined);
|
|
398
553
|
conn.buffer._waitingPacket = null;
|
|
554
|
+
conn.buffer.counter.sndAborted++;
|
|
399
555
|
}
|
|
400
556
|
}
|
|
401
557
|
else {
|
|
402
558
|
logger.verbose(`Wrote packet[${bytes}].Retries remaining: ${msg.remainingTries} `);
|
|
403
559
|
// We have all the success we are going to get so if the call succeeded then
|
|
404
560
|
// don't set the waiting packet when we aren't actually waiting for a response.
|
|
561
|
+
conn.buffer.counter.sndSuccess++;
|
|
405
562
|
if (!msg.requiresResponse) {
|
|
406
563
|
// As far as we know the message made it to OCP.
|
|
407
564
|
conn.buffer._waitingPacket = null;
|
|
408
565
|
if (typeof msg.onComplete === 'function') msg.onComplete(err, undefined);
|
|
566
|
+
|
|
409
567
|
}
|
|
410
568
|
else if (msg.remainingTries >= 0) {
|
|
411
569
|
conn.buffer._waitingPacket = msg;
|
|
412
570
|
}
|
|
413
571
|
}
|
|
572
|
+
conn.buffer.counter.updatefailureRate();
|
|
573
|
+
webApp.emitToChannel('rs485PortStats', 'rs485Stats', conn.buffer.counter);
|
|
414
574
|
});
|
|
415
575
|
}
|
|
416
576
|
}
|
|
@@ -439,7 +599,9 @@ export class SendRecieveBuffer {
|
|
|
439
599
|
let out = conn.buffer._outBuffer[i--];
|
|
440
600
|
if (typeof out === 'undefined') continue;
|
|
441
601
|
let resp = out.response;
|
|
442
|
-
|
|
602
|
+
// RG - added check for msgOut because the *Touch chlor packet 153 adds an status packet 217
|
|
603
|
+
// but if it is the only packet on the queue the outbound will have been cleared out already.
|
|
604
|
+
if (out.requiresResponse && msgOut !== null) {
|
|
443
605
|
if (resp instanceof Response && resp.isResponse(msgIn, out) && (typeof out.scope === 'undefined' || out.scope === msgOut.scope)) {
|
|
444
606
|
resp.message = msgIn;
|
|
445
607
|
if (typeof (resp.callback) === 'function' && resp.callback) callback = resp.callback;
|
|
@@ -456,20 +618,21 @@ export class SendRecieveBuffer {
|
|
|
456
618
|
private processCompletedMessage(msg: Inbound, ndx): number {
|
|
457
619
|
msg.timestamp = new Date();
|
|
458
620
|
msg.id = Message.nextMessageId;
|
|
459
|
-
conn.buffer.counter.
|
|
621
|
+
conn.buffer.counter.recCollisions += msg.collisions;
|
|
622
|
+
logger.packet(msg);
|
|
623
|
+
webApp.emitToChannel('rs485PortStats', 'rs485Stats', conn.buffer.counter);
|
|
460
624
|
if (msg.isValid) {
|
|
461
|
-
conn.buffer.counter.
|
|
625
|
+
conn.buffer.counter.recSuccess++;
|
|
462
626
|
conn.buffer.counter.updatefailureRate();
|
|
463
627
|
msg.process();
|
|
464
628
|
conn.buffer.clearResponses(msg);
|
|
465
629
|
}
|
|
466
630
|
else {
|
|
467
|
-
conn.buffer.counter.
|
|
631
|
+
conn.buffer.counter.recFailed++;
|
|
468
632
|
conn.buffer.counter.updatefailureRate();
|
|
469
|
-
console.log('RS485 Stats:' +
|
|
633
|
+
console.log('RS485 Stats:' + conn.buffer.counter.toLog());
|
|
470
634
|
ndx = this.rewindFailedMessage(msg, ndx);
|
|
471
635
|
}
|
|
472
|
-
logger.packet(msg);
|
|
473
636
|
return ndx;
|
|
474
637
|
}
|
|
475
638
|
private rewindFailedMessage(msg: Inbound, ndx: number): number {
|
|
@@ -513,20 +676,34 @@ export class SendRecieveBuffer {
|
|
|
513
676
|
export class Counter {
|
|
514
677
|
constructor() {
|
|
515
678
|
this.bytesReceived = 0;
|
|
516
|
-
this.
|
|
517
|
-
this.
|
|
679
|
+
this.recSuccess = 0;
|
|
680
|
+
this.recFailed = 0;
|
|
681
|
+
this.recCollisions = 0;
|
|
518
682
|
this.bytesSent = 0;
|
|
519
|
-
this.
|
|
520
|
-
this.
|
|
683
|
+
this.sndAborted = 0;
|
|
684
|
+
this.sndRetries = 0;
|
|
685
|
+
this.sndSuccess = 0;
|
|
686
|
+
this.recFailureRate = 0;
|
|
687
|
+
this.sndFailureRate = 0;
|
|
521
688
|
}
|
|
522
689
|
public bytesReceived: number;
|
|
523
|
-
public success: number;
|
|
524
|
-
public failed: number;
|
|
525
690
|
public bytesSent: number;
|
|
526
|
-
public
|
|
527
|
-
public
|
|
691
|
+
public recSuccess: number;
|
|
692
|
+
public recFailed: number;
|
|
693
|
+
public recCollisions: number;
|
|
694
|
+
public recFailureRate: number;
|
|
695
|
+
public sndSuccess: number;
|
|
696
|
+
public sndAborted: number;
|
|
697
|
+
public sndRetries: number;
|
|
698
|
+
public sndFailureRate: number;
|
|
528
699
|
public updatefailureRate(): void {
|
|
529
|
-
conn.buffer.counter.
|
|
700
|
+
conn.buffer.counter.recFailureRate = (this.recFailed + this.recSuccess) !== 0 ? (this.recFailed / (this.recFailed + this.recSuccess) * 100) : 0;
|
|
701
|
+
conn.buffer.counter.sndFailureRate = (this.sndAborted + this.sndSuccess) !== 0 ? (this.sndAborted / (this.sndAborted + this.sndSuccess) * 100) : 0;
|
|
702
|
+
//conn.buffer.counter.recFailureRate = `${(conn.buffer.counter.recFailed / (conn.buffer.counter.recFailed + conn.buffer.counter.recSuccess) * 100).toFixed(2)}% `;
|
|
703
|
+
//conn.buffer.counter.sndFailureRate = `${(conn.buffer.counter.sndAborted / (conn.buffer.counter.sndAborted + conn.buffer.counter.sndSuccess) * 100).toFixed(2)}% `;
|
|
704
|
+
}
|
|
705
|
+
public toLog(): string {
|
|
706
|
+
return `{ "bytesReceived": ${this.bytesReceived} "success": ${this.recSuccess}, "failed": ${this.recFailed}, "bytesSent": ${this.bytesSent}, "collisions": ${this.recCollisions}, "failureRate": ${this.recFailureRate.toFixed(2)}% }`;
|
|
530
707
|
}
|
|
531
708
|
}
|
|
532
709
|
export var conn: Connection = new Connection();
|
|
@@ -168,7 +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
|
+
// if packets have 16,2 (eg status=16,2,29) in them and they come as partial packets, they would have
|
|
173
|
+
// prev been detected as chlor packets;
|
|
174
|
+
// valid chlor packets should have 16,2,0 or 16,2,[80-96];
|
|
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)))
|
|
177
|
+
}
|
|
172
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; }
|
|
173
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; }
|
|
174
180
|
private testChlorTerm(bytes: number[], ndx: number): boolean { return ndx < bytes.length - 2 && bytes[ndx + 1] === 16 && bytes[ndx + 2] === 3; }
|
|
@@ -399,6 +405,7 @@ export class Inbound extends Message {
|
|
|
399
405
|
case ControllerType.IntelliCenter:
|
|
400
406
|
switch (this.action) {
|
|
401
407
|
case 1: // ACK
|
|
408
|
+
this.isProcessed = true;
|
|
402
409
|
break;
|
|
403
410
|
case 2:
|
|
404
411
|
case 204:
|
|
@@ -418,12 +425,14 @@ export class Inbound extends Message {
|
|
|
418
425
|
break;
|
|
419
426
|
case 222: // A panel is asking for action 30s
|
|
420
427
|
case 228: // A panel is asking for the current version
|
|
428
|
+
this.isProcessed = true;
|
|
421
429
|
break;
|
|
422
430
|
default:
|
|
423
431
|
logger.info(`An unprocessed message was received ${this.toPacket()}`)
|
|
424
432
|
break;
|
|
425
433
|
|
|
426
434
|
}
|
|
435
|
+
if (!this.isProcessed) logger.info(`The message was not processed ${this.action} - ${this.toPacket()}`);
|
|
427
436
|
break;
|
|
428
437
|
default:
|
|
429
438
|
switch (this.action) {
|
|
@@ -495,17 +504,19 @@ export class Inbound extends Message {
|
|
|
495
504
|
case 9:
|
|
496
505
|
case 16:
|
|
497
506
|
case 34:
|
|
498
|
-
case 114:
|
|
499
507
|
case 137:
|
|
500
508
|
case 144:
|
|
501
509
|
case 162:
|
|
502
510
|
HeaterMessage.process(this);
|
|
503
511
|
break;
|
|
512
|
+
case 114:
|
|
513
|
+
case 115:
|
|
514
|
+
HeaterStateMessage.process(this);
|
|
515
|
+
break
|
|
504
516
|
case 147:
|
|
505
517
|
IntellichemMessage.process(this);
|
|
506
518
|
break;
|
|
507
519
|
default:
|
|
508
|
-
// take these out...
|
|
509
520
|
if (this.action === 109 && this.payload[1] === 3) break;
|
|
510
521
|
if (this.source === 17 && this.payload[0] === 109) break;
|
|
511
522
|
logger.debug(`Packet not processed: ${this.toPacket()}`);
|
|
@@ -568,6 +579,7 @@ class OutboundCommon extends Message {
|
|
|
568
579
|
case Protocol.IntelliValve:
|
|
569
580
|
case Protocol.Unidentified:
|
|
570
581
|
case Protocol.IntelliChem:
|
|
582
|
+
case Protocol.Heater:
|
|
571
583
|
this.chkHi = Math.floor(sum / 256);
|
|
572
584
|
this.chkLo = (sum - (super.chkHi * 256));
|
|
573
585
|
break;
|
|
@@ -597,7 +609,7 @@ export class Outbound extends OutboundCommon {
|
|
|
597
609
|
this.header.push.apply(this.header, [165, Message.headerSubByte, 15, Message.pluginAddress, 0, 0]);
|
|
598
610
|
this.term.push.apply(this.term, [0, 0]);
|
|
599
611
|
}
|
|
600
|
-
else if (proto === Protocol.Pump || proto === Protocol.IntelliValve || proto === Protocol.IntelliChem) {
|
|
612
|
+
else if (proto === Protocol.Pump || proto === Protocol.IntelliValve || proto === Protocol.IntelliChem || proto === Protocol.Heater) {
|
|
601
613
|
this.preamble.push.apply(this.preamble, [255, 0, 255]);
|
|
602
614
|
this.header.push.apply(this.header, [165, 0, 15, Message.pluginAddress, 0, 0]);
|
|
603
615
|
this.term.push.apply(this.term, [0, 0]);
|
|
@@ -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) 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,7 +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);
|
|
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;
|
|
82
91
|
schlor.saltLevel = msg.extractPayloadByte(3) * 50 || schlor.saltLevel;
|
|
83
92
|
schlor.status = msg.extractPayloadByte(4) & 0x007F; // Strip off the high bit. The chlorinator does not actually report this.;
|
|
84
93
|
// Pull the hours from the 25 message.
|
|
@@ -109,5 +118,6 @@ export class ChlorinatorMessage {
|
|
|
109
118
|
sys.chlorinators.removeItemById(1);
|
|
110
119
|
state.chlorinators.removeItemById(1);
|
|
111
120
|
}
|
|
121
|
+
msg.isProcessed = true;
|
|
112
122
|
}
|
|
113
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
|
}
|
|
@@ -250,7 +250,7 @@ export class CircuitMessage {
|
|
|
250
250
|
circuit.freeze = (functionId & 64) === 64;
|
|
251
251
|
circuit.showInFeatures = typeof circuit.showInFeatures === 'undefined' ? true : circuit.showInFeatures;
|
|
252
252
|
circuit.isActive = _isActive;
|
|
253
|
-
if (typeof circuit.eggTimer === 'undefined') circuit.eggTimer = 720;
|
|
253
|
+
if (typeof circuit.eggTimer === 'undefined' || circuit.eggTimer === 0) circuit.eggTimer = 720;
|
|
254
254
|
if (typeof circuit.dontStop === 'undefined') circuit.dontStop = circuit.eggTimer === 1620;
|
|
255
255
|
if ([9, 10, 16, 17].includes(circuit.type)) {
|
|
256
256
|
const lg = sys.lightGroups.getItemById(sys.board.equipmentIds.circuitGroups.start, true);
|
|
@@ -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()}`)
|
|
@@ -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()}`)
|