nodejs-poolcontroller 7.7.0 → 8.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/.eslintrc.json +26 -35
  2. package/Changelog +22 -0
  3. package/README.md +7 -3
  4. package/anslq25/MessagesMock.ts +218 -0
  5. package/anslq25/boards/MockBoardFactory.ts +50 -0
  6. package/anslq25/boards/MockEasyTouchBoard.ts +696 -0
  7. package/anslq25/boards/MockSystemBoard.ts +217 -0
  8. package/anslq25/chemistry/MockChlorinator.ts +75 -0
  9. package/anslq25/pumps/MockPump.ts +84 -0
  10. package/app.ts +10 -14
  11. package/config/Config.ts +13 -9
  12. package/config/VersionCheck.ts +6 -2
  13. package/controller/Constants.ts +58 -25
  14. package/controller/Equipment.ts +225 -41
  15. package/controller/Errors.ts +2 -1
  16. package/controller/Lockouts.ts +34 -2
  17. package/controller/State.ts +491 -48
  18. package/controller/boards/AquaLinkBoard.ts +6 -3
  19. package/controller/boards/BoardFactory.ts +5 -1
  20. package/controller/boards/EasyTouchBoard.ts +1971 -1751
  21. package/controller/boards/IntelliCenterBoard.ts +1311 -1688
  22. package/controller/boards/IntelliComBoard.ts +7 -1
  23. package/controller/boards/IntelliTouchBoard.ts +153 -42
  24. package/controller/boards/NixieBoard.ts +209 -66
  25. package/controller/boards/SunTouchBoard.ts +393 -0
  26. package/controller/boards/SystemBoard.ts +1862 -1543
  27. package/controller/comms/Comms.ts +539 -138
  28. package/controller/comms/ScreenLogic.ts +1663 -0
  29. package/controller/comms/messages/Messages.ts +242 -60
  30. package/controller/comms/messages/config/ChlorinatorMessage.ts +4 -3
  31. package/controller/comms/messages/config/CircuitGroupMessage.ts +5 -2
  32. package/controller/comms/messages/config/CircuitMessage.ts +81 -13
  33. package/controller/comms/messages/config/ConfigMessage.ts +3 -1
  34. package/controller/comms/messages/config/CoverMessage.ts +2 -1
  35. package/controller/comms/messages/config/CustomNameMessage.ts +2 -1
  36. package/controller/comms/messages/config/EquipmentMessage.ts +5 -1
  37. package/controller/comms/messages/config/ExternalMessage.ts +33 -3
  38. package/controller/comms/messages/config/FeatureMessage.ts +2 -1
  39. package/controller/comms/messages/config/GeneralMessage.ts +2 -1
  40. package/controller/comms/messages/config/HeaterMessage.ts +3 -1
  41. package/controller/comms/messages/config/IntellichemMessage.ts +2 -1
  42. package/controller/comms/messages/config/OptionsMessage.ts +12 -6
  43. package/controller/comms/messages/config/PumpMessage.ts +9 -12
  44. package/controller/comms/messages/config/RemoteMessage.ts +80 -13
  45. package/controller/comms/messages/config/ScheduleMessage.ts +43 -3
  46. package/controller/comms/messages/config/SecurityMessage.ts +2 -1
  47. package/controller/comms/messages/config/ValveMessage.ts +43 -26
  48. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +8 -7
  49. package/controller/comms/messages/status/EquipmentStateMessage.ts +93 -20
  50. package/controller/comms/messages/status/HeaterStateMessage.ts +24 -5
  51. package/controller/comms/messages/status/IntelliChemStateMessage.ts +7 -4
  52. package/controller/comms/messages/status/IntelliValveStateMessage.ts +2 -1
  53. package/controller/comms/messages/status/PumpStateMessage.ts +72 -4
  54. package/controller/comms/messages/status/VersionMessage.ts +2 -1
  55. package/controller/nixie/Nixie.ts +15 -4
  56. package/controller/nixie/NixieEquipment.ts +1 -0
  57. package/controller/nixie/chemistry/ChemController.ts +300 -129
  58. package/controller/nixie/chemistry/ChemDoser.ts +806 -0
  59. package/controller/nixie/chemistry/Chlorinator.ts +133 -129
  60. package/controller/nixie/circuits/Circuit.ts +171 -30
  61. package/controller/nixie/heaters/Heater.ts +337 -173
  62. package/controller/nixie/pumps/Pump.ts +264 -236
  63. package/controller/nixie/schedules/Schedule.ts +9 -3
  64. package/defaultConfig.json +46 -5
  65. package/logger/Logger.ts +38 -9
  66. package/package.json +13 -9
  67. package/web/Server.ts +235 -122
  68. package/web/bindings/aqualinkD.json +114 -59
  69. package/web/bindings/homeassistant.json +437 -0
  70. package/web/bindings/influxDB.json +15 -0
  71. package/web/bindings/mqtt.json +28 -9
  72. package/web/bindings/mqttAlt.json +15 -0
  73. package/web/interfaces/baseInterface.ts +58 -7
  74. package/web/interfaces/httpInterface.ts +5 -2
  75. package/web/interfaces/influxInterface.ts +9 -2
  76. package/web/interfaces/mqttInterface.ts +234 -74
  77. package/web/interfaces/ruleInterface.ts +87 -0
  78. package/web/services/config/Config.ts +140 -33
  79. package/web/services/config/ConfigSocket.ts +2 -1
  80. package/web/services/state/State.ts +144 -3
  81. package/web/services/state/StateSocket.ts +65 -14
  82. package/web/services/utilities/Utilities.ts +189 -1
@@ -1,5 +1,6 @@
1
1
  /* nodejs-poolController. An application to control pool equipment.
2
- Copyright (C) 2016, 2017, 2018, 2019, 2020. Russell Goldin, tagyoureit. russ.goldin@gmail.com
2
+ Copyright (C) 2016, 2017, 2018, 2019, 2020, 2021, 2022.
3
+ Russell Goldin, tagyoureit. russ.goldin@gmail.com
3
4
 
4
5
  This program is free software: you can redistribute it and/or modify
5
6
  it under the terms of the GNU Affero General Public License as
@@ -14,29 +15,31 @@ GNU Affero General Public License for more details.
14
15
  You should have received a copy of the GNU Affero General Public License
15
16
  along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17
  */
18
+ import { AutoDetectTypes } from '@serialport/bindings-cpp';
17
19
  import { EventEmitter } from 'events';
18
- import * as SerialPort from 'serialport';
19
- import * as MockBinding from '@serialport/binding-mock';
20
+ import * as net from 'net';
21
+ import { SerialPort, SerialPortMock, SerialPortOpenOptions } from 'serialport';
22
+ import { setTimeout } from 'timers';
20
23
  import { config } from '../../config/Config';
21
24
  import { logger } from '../../logger/Logger';
22
- import * as net from 'net';
23
- import { setTimeout, setInterval } from 'timers';
24
- import { Message, Outbound, Inbound, Response } from './messages/Messages';
25
- import { InvalidEquipmentDataError, InvalidOperationError, MessageError, OutboundMessageError } from '../Errors';
25
+ import { webApp } from "../../web/Server";
26
26
  import { utils } from "../Constants";
27
27
  import { sys } from "../Equipment";
28
- import { webApp } from "../../web/Server";
28
+ import { InvalidEquipmentDataError, InvalidOperationError, OutboundMessageError } from '../Errors';
29
+ import { state } from "../State";
30
+ import { Inbound, Message, Outbound, Response } from './messages/Messages';
31
+ import { sl } from './ScreenLogic';
29
32
  const extend = require("extend");
30
33
  export class Connection {
31
- constructor() {}
34
+ constructor() { }
32
35
  public rs485Ports: RS485Port[] = [];
33
- public get mockPort(): boolean {
36
+ public get mock(): boolean {
34
37
  let port = this.findPortById(0);
35
- return typeof port !== 'undefined' && port.mockPort ? true : false;
38
+ return typeof port !== 'undefined' && port.mock ? true : false;
36
39
  }
37
40
  public isPortEnabled(portId: number) {
38
41
  let port: RS485Port = this.findPortById(portId);
39
- return typeof port === 'undefined' ? false : port.enabled;
42
+ return typeof port === 'undefined' ? false : port.enabled && port.isOpen && !port.closing;
40
43
  }
41
44
  public async deleteAuxPort(data: any): Promise<any> {
42
45
  try {
@@ -48,11 +51,19 @@ export class Connection {
48
51
  let section = `controller.comms` + (portId === 0 ? '' : portId);
49
52
  let cfg = config.getSection(section, {});
50
53
  config.removeSection(section);
54
+ state.equipment.messages.removeItemByCode(`rs485:${portId}:connection`);
51
55
  return cfg;
52
56
  } catch (err) { logger.error(`Error deleting aux port`) }
53
57
  }
58
+ public async setScreenlogicAsync(data: any) {
59
+ let ccfg = config.getSection('controller.screenlogic');
60
+ if (typeof data.type === 'undefined' || data.type !== 'local' || data.type !== 'remote') return Promise.reject(new InvalidEquipmentDataError(`Invalid Screenlogic type (${data.type}). Allowed values are 'local' or 'remote'`, 'Screenlogic', 'screenlogic'));
61
+ if ((data.address as string).slice(8) !== 'Pentair:') return Promise.reject(new InvalidEquipmentDataError(`Invalid address (${data.address}). Must start with 'Pentair:'`, 'Screenlogic', 'screenlogic'));
62
+ }
63
+
54
64
  public async setPortAsync(data: any): Promise<any> {
55
65
  try {
66
+
56
67
  let ccfg = config.getSection('controller');
57
68
  let pConfig;
58
69
  let portId;
@@ -75,46 +86,94 @@ export class Connection {
75
86
  // Lets set the config data.
76
87
  let pdata = config.getSection(section, {
77
88
  portId: portId,
89
+ type: 'local',
78
90
  rs485Port: "/dev/ttyUSB0",
79
91
  portSettings: { baudRate: 9600, dataBits: 8, parity: 'none', stopBits: 1, flowControl: false, autoOpen: false, lock: false },
80
- mockPort: false,
92
+ netSettings: { allowHalfOpen: false, keepAlive: false, keepAliveInitialDelay: 1000 },
93
+ mock: false,
81
94
  netConnect: false,
82
95
  netHost: "raspberrypi",
83
96
  netPort: 9801,
84
97
  inactivityRetry: 10
85
98
  });
99
+ if (portId === 0) {
100
+ pdata.screenlogic = {
101
+ connectionType: "local",
102
+ systemName: "Pentair: 00-00-00",
103
+ password: 1234
104
+ }
105
+ }
106
+
86
107
  pdata.enabled = typeof data.enabled !== 'undefined' ? utils.makeBool(data.enabled) : utils.makeBool(pdata.enabled);
87
- pdata.netConnect = typeof data.netConnect !== 'undefined' ? utils.makeBool(data.netConnect) : utils.makeBool(pdata.netConnect);
108
+ pdata.type = data.type;
109
+ pdata.netConnect = data.type === 'network' || data.type === 'netConnect'; // typeof data.netConnect !== 'undefined' ? utils.makeBool(data.netConnect) : utils.makeBool(pdata.netConnect);
88
110
  pdata.rs485Port = typeof data.rs485Port !== 'undefined' ? data.rs485Port : pdata.rs485Port;
89
111
  pdata.inactivityRetry = typeof data.inactivityRetry === 'number' ? data.inactivityRetry : pdata.inactivityRetry;
90
- if (pdata.netConnect) {
112
+ pdata.mock = data.mock; // typeof data.mockPort !== 'undefined' ? utils.makeBool(data.mockPort) : utils.makeBool(pdata.mockPort);
113
+ if (pdata.mock) { pdata.rs485Port = 'MOCK_PORT'; }
114
+ if (pdata.type === 'netConnect') { // (pdata.netConnect) {
91
115
  pdata.netHost = typeof data.netHost !== 'undefined' ? data.netHost : pdata.netHost;
92
116
  pdata.netPort = typeof data.netPort === 'number' ? data.netPort : pdata.netPort;
93
117
  }
94
118
  if (typeof data.portSettings !== 'undefined') {
95
119
  pdata.portSettings = extend(true, { baudRate: 9600, dataBits: 8, parity: 'none', stopBits: 1, flowControl: false, autoOpen: false, lock: false }, pdata.portSettings, data.portSettings);
96
120
  }
121
+ if (typeof data.netSettings !== 'undefined') {
122
+ pdata.netSettings = extend(true, { keepAlive: false, allowHalfOpen: false, keepAliveInitialDelay: 10000 }, pdata.netSettings, data.netSettings);
123
+ }
124
+ if (pdata.type === 'screenlogic') {
125
+ let password = data.screenlogic.password.toString();
126
+ let regx = /Pentair: (?:(?:\d|[A-Z])(?:\d|[A-Z])-){2}(?:\d|[A-Z])(?:\d|[A-Z])/g;
127
+ let type = data.screenlogic.connectionType;
128
+ let systemName = data.screenlogic.systemName;
129
+ if (type !== 'remote' && type !== 'local') return Promise.reject(new InvalidEquipmentDataError(`An invalid type was supplied for Screenlogic ${type}. Must be remote or local.`, 'Screenlogic', data));
130
+ if (systemName.match(regx) === null) return Promise.reject(new InvalidEquipmentDataError(`An invalid system name was supplied for Screenlogic ${systemName}}. Must be in the format 'Pentair: xx-xx-xx'.`, 'Screenlogic', data));
131
+ if (password.length !== 4) return Promise.reject(new InvalidEquipmentDataError(`An invalid password was supplied for Screenlogic ${password}. (Length must be <= 4)}`, 'Screenlogic', data));
132
+ pdata.screenlogic = data.screenlogic;
133
+ }
97
134
  let existing = this.findPortById(portId);
98
- if (typeof existing !== 'undefined') {
99
- if (!await existing.closeAsync()) {
100
- return Promise.reject(new InvalidOperationError(`Unable to close the current RS485 port`, 'setPortAsync'));
135
+ if (typeof existing !== 'undefined')
136
+ if (existing.type === 'screenlogic' || sl.enabled) {
137
+ await sl.closeAsync();
138
+ }
139
+ else {
140
+ if (!await existing.closeAsync()) {
141
+ existing.closing = false; // if closing fails, reset flag so user can try again
142
+ return Promise.reject(new InvalidOperationError(`Unable to close the current RS485 port`, 'setPortAsync'));
143
+ }
101
144
  }
102
- }
103
145
  config.setSection(section, pdata);
104
146
  let cfg = config.getSection(section, {
147
+ type: 'local',
105
148
  rs485Port: "/dev/ttyUSB0",
106
149
  portSettings: { baudRate: 9600, dataBits: 8, parity: 'none', stopBits: 1, flowControl: false, autoOpen: false, lock: false },
107
- mockPort: false,
150
+ netSettings: { allowHalfOpen: false, keepAlive: false, keepAliveInitialDelay: 5 },
151
+ mock: false,
108
152
  netConnect: false,
109
153
  netHost: "raspberrypi",
110
154
  netPort: 9801,
111
155
  inactivityRetry: 10
112
156
  });
113
- existing = this.getPortById(cfg);
157
+ if (portId === 0) {
158
+ cfg.screenlogic = {
159
+ connectionType: "local",
160
+ systemName: "Pentair: 00-00-00",
161
+ password: 1234
162
+ }
163
+ }
164
+ existing = this.getPortByCfg(cfg);
165
+
114
166
  if (typeof existing !== 'undefined') {
115
- existing.reconnects = 0;
116
- if (!await existing.openAsync(cfg)) {
117
- return Promise.reject(new InvalidOperationError(`Unable to open RS485 port ${pdata.rs485Port}`, 'setPortAsync'));
167
+ if (pdata.type === 'screenlogic') {
168
+ await sl.openAsync();
169
+ }
170
+ else {
171
+ existing.reconnects = 0;
172
+ //existing.emitPortStats();
173
+ if (!await existing.openAsync(cfg)) {
174
+ if (cfg.netConnect) return Promise.reject(new InvalidOperationError(`Unable to open Socat Connection to ${pdata.netHost}`, 'setPortAsync'));
175
+ return Promise.reject(new InvalidOperationError(`Unable to open RS485 port ${pdata.rs485Port}`, 'setPortAsync'));
176
+ }
118
177
  }
119
178
  }
120
179
  return cfg;
@@ -136,7 +195,18 @@ export class Connection {
136
195
  let cfg = config.getSection('controller');
137
196
  for (let section in cfg) {
138
197
  if (section.startsWith('comms')) {
139
- let port = new RS485Port(cfg[section]);
198
+ let c = cfg[section];
199
+ if (typeof c.type === 'undefined') {
200
+ let type = 'local';
201
+ if (c.mockPort) type = 'mock';
202
+ else if (c.netConnect) type = 'network';
203
+ config.setSection(`controller.${section}`, c);
204
+ console.log(section);
205
+ console.log(c);
206
+ }
207
+ let port = new RS485Port(c);
208
+ // Alright now lets do some conversion of the existing data.
209
+
140
210
  this.rs485Ports.push(port);
141
211
  await port.openAsync();
142
212
  }
@@ -150,11 +220,12 @@ export class Connection {
150
220
  if (port.portId === portId) {
151
221
  await port.closeAsync();
152
222
  // Don't remove the primary port. You cannot delete this one.
153
- if(portId !== 0) this.rs485Ports.splice(i, 1);
223
+ if (portId !== 0) this.rs485Ports.splice(i, 1);
154
224
  }
155
225
  }
156
226
  }
157
- public getPortById(cfg: any) {
227
+ public
228
+ getPortByCfg(cfg: any) {
158
229
  let port = this.findPortById(cfg.portId || 0);
159
230
  if (typeof port === 'undefined') {
160
231
  port = new RS485Port(cfg);
@@ -182,13 +253,187 @@ export class Connection {
182
253
  } catch (err) { logger.error(`Error listing installed RS485 ports ${err.message}`); }
183
254
 
184
255
  }
256
+ private getBroadcastPorts(currPort: RS485Port) {
257
+ // if an ANSLQ25 controller is present, broadcast outbound writes to all other ports that are not mock or dedicated for a pump or chlor
258
+ let anslq25port = sys.anslq25.portId;
259
+ let duplicateTo: number[] = [];
260
+ if (anslq25port >= 0) {
261
+ let ports = this.rs485Ports;
262
+ for (let i = 0; i < ports.length; i++) {
263
+ // if (ports[i].mockPort) continue;
264
+ if (ports[i].portId === currPort.portId) continue;
265
+ if (ports[i].portId === anslq25port) continue; // don't resend
266
+ if (!ports[i].isOpen) continue;
267
+ duplicateTo.push(ports[i].portId);
268
+ }
269
+ let pumps = sys.pumps.get();
270
+ for (let i = 0; i < pumps.length; i++) {
271
+ if (pumps[i].portId === currPort.portId ||
272
+ pumps[i].portId === anslq25port) {
273
+ if (duplicateTo.includes(pumps[i].portId)) duplicateTo.splice(duplicateTo.indexOf(pumps[i].portId, 1));
274
+ }
275
+ }
276
+ let chlors = sys.chlorinators.get();
277
+ for (let i = 0; i < chlors.length; i++) {
278
+ if (chlors[i].portId === currPort.portId ||
279
+ chlors[i].portId === anslq25port) {
280
+ if (duplicateTo.includes(chlors[i].portId)) duplicateTo.splice(duplicateTo.indexOf(chlors[i].portId, 1));
281
+ }
282
+ }
283
+ }
284
+ // send to the ansql25 port first, where possible
285
+ if (currPort.portId !== anslq25port) duplicateTo.unshift(anslq25port);
286
+ return duplicateTo;
287
+ }
288
+ /* public queueInboundToAnslq25(_msg: Inbound) {
289
+ // if we have a valid inbound packet on any port (besides dedicated pump/chlor) then also send to anslq25
290
+ if (!sys.anslq25.isActive || sys.anslq25.portId < 0 || !sys.anslq25.broadcastComms) return;
291
+ if (typeof _msg.isClone !== 'undefined' && _msg.isClone) return;
292
+ let anslq25port = sys.anslq25.portId;
293
+ if (anslq25port === _msg.portId) return;
294
+ let port = this.findPortById(anslq25port);
295
+ let msg = _msg.clone();
296
+ msg.portId = port.portId;
297
+ msg.isClone = true;
298
+ msg.id = Message.nextMessageId;
299
+ (msg as Inbound).process();
300
+ } */
301
+
302
+
303
+ /* public queueInboundToBroadcast(_msg: Outbound) {
304
+ // if we have a valid inbound packet on any port (besides dedicated pump/chlor) then also send to anslq25
305
+ if (!sys.anslq25.isActive || sys.anslq25.portId < 0 || !sys.anslq25.broadcastComms) return;
306
+ if (typeof _msg.isClone !== 'undefined' && _msg.isClone) return;
307
+ let anslq25port = sys.anslq25.portId;
308
+ if (anslq25port === _msg.portId) return;
309
+ let port = this.findPortById(anslq25port);
310
+ let msg = _msg.clone();
311
+ msg.portId = port.portId;
312
+ msg.isClone = true;
313
+ msg.id = Message.nextMessageId;
314
+ (msg as Inbound).process();
315
+ } */
316
+
317
+ /* public queueOutboundToAnslq25(_msg: Outbound) {
318
+ // if we have a valid inbound packet on any port (besides dedicated pump/chlor) then also send to anslq25
319
+ if (!sys.anslq25.isActive || sys.anslq25.portId < 0 || !sys.anslq25.broadcastComms) return;
320
+ if (typeof _msg.isClone !== 'undefined' && _msg.isClone) return;
321
+ let anslq25port = sys.anslq25.portId;
322
+ let _ports = this.getBroadcastPorts(this.findPortById(_msg.portId));
323
+ let msgs: Outbound[] = [];
324
+ for (let i = 0; i < _ports.length; i++) {
325
+ let port = this.findPortById(_ports[i]);
326
+ if (port.portId === _msg.portId) continue;
327
+ let msg = _msg.clone() as Outbound;
328
+ msg.isClone = true;
329
+ msg.portId = port.portId;
330
+ msg.response = _msg.response;
331
+ msgs.push(msg);
332
+ }
333
+ return msgs;
334
+ } */
335
+ public queueOutboundToBroadcast(_msg: Outbound) {
336
+ // if we have a valid inbound packet on any port (besides dedicated pump/chlor) then also send to anslq25
337
+ if (!sys.anslq25.isActive || sys.anslq25.portId < 0 || !sys.anslq25.broadcastComms) return;
338
+ if (typeof _msg.isClone !== 'undefined' && _msg.isClone) return;
339
+ let anslq25port = sys.anslq25.portId;
340
+ let _ports = this.getBroadcastPorts(this.findPortById(_msg.portId));
341
+ let msgs: Inbound[] = [];
342
+ for (let i = 0; i < _ports.length; i++) {
343
+ let port = this.findPortById(_ports[i]);
344
+ if (port.portId === _msg.portId) continue;
345
+ // // let msg = _msg.clone() as Inbound;
346
+ // let msg = Message.convertOutboundToInbound(_msg);
347
+ // msg.isClone = true;
348
+ // msg.portId = port.portId;
349
+
350
+ // msg.process();
351
+ setTimeout(() => { port.pushIn(Buffer.from(_msg.toPacket())) }, 100);
352
+ logger.silly(`mock inbound write bytes port:${_msg.portId} id:${_msg.id} bytes:${_msg.toShortPacket()}`)
353
+ // logger.packet()
354
+ // (msg as Inbound).process();
355
+ // msgs.push(msg);
356
+ }
357
+ // return msgs;
358
+ }
185
359
  public queueSendMessage(msg: Outbound) {
186
360
  let port = this.findPortById(msg.portId);
187
- if (typeof port !== 'undefined')
361
+ if (typeof port !== 'undefined') {
188
362
  port.emitter.emit('messagewrite', msg);
363
+ }
189
364
  else
190
365
  logger.error(`queueSendMessage: Message was targeted for undefined port ${msg.portId || 0}`);
191
366
  }
367
+
368
+ public async queueSendMessageAsync(msg: Outbound): Promise<boolean> {
369
+ return new Promise(async (resolve, reject) => {
370
+
371
+
372
+ let port = this.findPortById(msg.portId);
373
+
374
+ if (typeof port === 'undefined') {
375
+ logger.error(`queueSendMessage: Message was targeted for undefined port ${msg.portId || 0}`);
376
+ return;
377
+ }
378
+ // also send to other broadcast ports
379
+ // let msgs = conn.queueOutboundToAnslq25(msg);
380
+ let msgs = [];
381
+ // conn.queueInboundToBroadcast(msg);
382
+ conn.queueOutboundToBroadcast(msg);
383
+ /* if (msgs.length > 0) {
384
+ msgs.push(msg);
385
+ let promises: Promise<boolean>[] = [];
386
+ for (let i = 0; i < msgs.length; i++) {
387
+ let p: Promise<boolean> = new Promise((_resolve, _reject) => {
388
+ msgs[i].onComplete = (err) => {
389
+ if (err) {
390
+ console.log(`rejecting ${msg.id} ${msg.portId} ${msg.action}`);
391
+ _reject(err);
392
+ }
393
+ else
394
+ {
395
+ console.log(`resolving id:${msg.id} portid:${msg.portId} dir:${msg.direction} action:${msg.action}`);
396
+ _resolve(true);
397
+ }
398
+ }
399
+ let _port = this.findPortById(msgs[i].portId);
400
+ _port.emitter.emit('messagewrite', msgs[i]);
401
+ });
402
+ promises.push(p);
403
+ }
404
+ let res = false;
405
+ await Promise.allSettled(promises).
406
+ then((results) => {
407
+
408
+ results.forEach((result) => {
409
+ console.log(result.status);
410
+ if (result.status === 'fulfilled') {res = true;}
411
+ });
412
+ });
413
+ if (res) resolve(true); else reject(`No packets had responses.`);
414
+ }
415
+ else { */
416
+ msg.onComplete = (err) => {
417
+ if (err) {
418
+ reject(err);
419
+ }
420
+ else resolve(true);
421
+ }
422
+ port.emitter.emit('messagewrite', msg);
423
+ // let ports = this.getBroadcastPorts(port);
424
+ //}
425
+
426
+
427
+
428
+
429
+ })
430
+ }
431
+
432
+ // public sendMockPacket(msg: Inbound) {
433
+ // let port = this.findPortById(msg.portId);
434
+ // port.emitter.emit('mockmessagewrite', msg);
435
+ // }
436
+
192
437
  public pauseAll() {
193
438
  for (let i = 0; i < this.rs485Ports.length; i++) {
194
439
  let port = this.rs485Ports[i];
@@ -246,28 +491,39 @@ export class Counter {
246
491
  export class RS485Port {
247
492
  constructor(cfg: any) {
248
493
  this._cfg = cfg;
249
-
494
+
250
495
  this.emitter = new EventEmitter();
251
496
  this._inBuffer = [];
252
497
  this._outBuffer = [];
253
498
  this.procTimer = null;
254
499
  this.emitter.on('messagewrite', (msg) => { this.pushOut(msg); });
500
+ this.emitter.on('mockmessagewrite', (msg) => {
501
+ let bytes = msg.toPacket();
502
+ this.counter.bytesSent += bytes.length;
503
+ this.counter.sndSuccess++;
504
+ this.emitPortStats();
505
+ msg.process();
506
+ });
507
+
255
508
  }
509
+ public get name(): string { return this.portId === 0 ? 'Primary' : `Aux${this.portId}` }
256
510
  public isRTS: boolean = true;
257
- public reconnects:number = 0;
511
+ public reconnects: number = 0;
258
512
  public emitter: EventEmitter;
259
513
  public get portId() { return typeof this._cfg !== 'undefined' && typeof this._cfg.portId !== 'undefined' ? this._cfg.portId : 0; }
514
+ public get type() { return typeof this._cfg.type !== 'undefined' ? this._cfg.type : this._cfg.netConnect ? 'netConnect' : this._cfg.mockPort || this._cfg.mock ? 'mock' : 'local' };
260
515
  public isOpen: boolean = false;
261
- private _closing: boolean = false;
516
+ public closing: boolean = false;
262
517
  private _cfg: any;
263
- private _port: any;
264
- public mockPort: boolean = false;
518
+ private _port: SerialPort | SerialPortMock | net.Socket;
519
+ public mock: boolean = false;
265
520
  private isPaused: boolean = false;
266
521
  private connTimer: NodeJS.Timeout;
267
522
  //public buffer: SendRecieveBuffer;
268
523
  public get enabled(): boolean { return typeof this._cfg !== 'undefined' && this._cfg.enabled; }
269
524
  public counter: Counter = new Counter();
270
525
  private procTimer: NodeJS.Timeout;
526
+ public writeTimer: NodeJS.Timeout
271
527
  private _processing: boolean = false;
272
528
  private _inBytes: number[] = [];
273
529
  private _inBuffer: number[] = [];
@@ -278,9 +534,12 @@ export class RS485Port {
278
534
  public async openAsync(cfg?: any): Promise<boolean> {
279
535
  if (this.isOpen) await this.closeAsync();
280
536
  if (typeof cfg !== 'undefined') this._cfg = cfg;
281
- if (!this._cfg.enabled) return true;
282
- if (this._cfg.netConnect && !this._cfg.mockPort) {
283
- let sock: net.Socket = this._port as net.Socket;
537
+ if (!this._cfg.enabled) {
538
+ this.emitPortStats();
539
+ state.equipment.messages.removeItemByCode(`rs485:${this.portId}:connection`);
540
+ return true;
541
+ }
542
+ if (this._cfg.netConnect && !this._cfg.mock) {
284
543
  if (typeof this._port !== 'undefined' && this.isOpen) {
285
544
  // This used to try to reconnect and recreate events even though the socket was already connected. This resulted in
286
545
  // instances where multiple event processors were present. Node doesn't give us any indication that the socket is
@@ -289,9 +548,18 @@ export class RS485Port {
289
548
  }
290
549
  else if (typeof this._port !== 'undefined') {
291
550
  // We need to kill the existing connection by ending it.
292
- this._port.end();
551
+ let port = this._port as net.Socket;
552
+ await new Promise<boolean>((resolve, _) => {
553
+ port.end(() => {
554
+ resolve(true);
555
+ });
556
+ });
557
+ port.destroy();
293
558
  }
294
- let nc: net.Socket = new net.Socket();
559
+ let opts = extend(true, { keepAliveInitialDelay: 0 }, this._cfg.netSettings);
560
+ // Convert the initial delay to milliseconds.
561
+ if (typeof this._cfg.netSettings !== 'undefined' && typeof this._cfg.netSettings.keepAliveInitialDelay === 'number') opts.keepAliveInitialDelay = this._cfg.netSettings.keepAliveInitialDelay * 1000;
562
+ let nc: net.Socket = new net.Socket(opts);
295
563
  nc.once('connect', () => { logger.info(`Net connect (socat) ${this._cfg.portId} connected to: ${this._cfg.netHost}:${this._cfg.netPort}`); }); // Socket is opened but not yet ready.
296
564
  nc.once('ready', () => {
297
565
  this.isOpen = true;
@@ -302,14 +570,17 @@ export class RS485Port {
302
570
  if (data.length > 0 && !this.isPaused) this.pushIn(data);
303
571
  });
304
572
  this.emitPortStats();
573
+ this.processPackets(); // if any new packets have been added to queue, process them.
574
+ state.equipment.messages.removeItemByCode(`rs485:${this.portId}:connection`);
305
575
  });
576
+
306
577
  nc.once('close', (p) => {
307
578
  this.isOpen = false;
308
579
  if (typeof this._port !== 'undefined' && !this._port.destroyed) this._port.destroy();
309
580
  this._port = undefined;
310
581
  this.clearOutboundBuffer();
311
582
  this.emitPortStats();
312
- if (!this._closing) {
583
+ if (!this.closing) {
313
584
  // If we are closing manually this event should have been cleared already and should never be called. If this is fired out
314
585
  // of sequence then we will check the closing flag to ensure we are not forcibly closing the socket.
315
586
  if (typeof this.connTimer !== 'undefined' && this.connTimer) {
@@ -348,23 +619,32 @@ export class RS485Port {
348
619
  return await new Promise<boolean>((resolve, _) => {
349
620
  // We only connect an error once as we will destroy this connection on error then recreate a new socket on failure.
350
621
  nc.once('error', (err) => {
622
+ logger.error(`Net connect (socat) error: ${err.message}`);
351
623
  //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}`}`);
352
624
  //this.resetConnTimer();
353
625
  this.isOpen = false;
354
626
  this.emitPortStats();
627
+ this.processPackets(); // if any new packets have been added to queue, process them.
628
+
355
629
  // if the promise has already been fulfilled, but the error happens later, we don't want to call the promise again.
356
630
  if (typeof resolve !== 'undefined') { resolve(false); }
357
631
  if (this._cfg.inactivityRetry > 0) {
358
632
  logger.error(`Net connect (socat) connection ${this.portId} error: ${err}. Retry in ${this._cfg.inactivityRetry} seconds`);
359
- setTimeout(async () => { try { await this.openAsync(); } catch (err) { } }, this._cfg.inactivityRetry * 1000);
633
+ if (this.connTimer) clearTimeout(this.connTimer);
634
+ this.connTimer = setTimeout(async () => { try { await this.openAsync(); } catch (err) { } }, this._cfg.inactivityRetry * 1000);
360
635
  }
361
636
  else logger.error(`Net connect (socat) connection ${this.portId} error: ${err}. Never retrying -- No retry time set`);
637
+ state.equipment.messages.setMessageByCode(`rs485:${this.portId}:connection`, 'error', `${this.name} RS485 port disconnected`);
362
638
  });
363
639
  nc.connect(this._cfg.netPort, this._cfg.netHost, () => {
364
640
  if (typeof this._port !== 'undefined') logger.warn(`Net connect (socat) ${this.portId} recovered from lost connection.`);
365
641
  logger.info(`Net connect (socat) Connection ${this.portId} connected`);
366
642
  this._port = nc;
643
+ // if just changing existing port, reset key flags
367
644
  this.isOpen = true;
645
+ this.isRTS = true;
646
+ this.closing = false;
647
+ this._processing = false;
368
648
  this.emitPortStats();
369
649
  resolve(true);
370
650
  resolve = undefined;
@@ -372,23 +652,26 @@ export class RS485Port {
372
652
  });
373
653
  }
374
654
  else {
375
- if (typeof this._port !== 'undefined' && this._port.isOpen) {
655
+ if (typeof this._port !== 'undefined' && this.isOpen) {
376
656
  // This used to try to reconnect even though the serial port was already connected. This resulted in
377
657
  // instances where an access denied error was emitted. So if the port is open we will simply return.
378
658
  this.resetConnTimer();
379
659
  return true;
380
660
  }
381
- let sp: SerialPort = null;
382
- if (this._cfg.mockPort) {
383
- this.mockPort = true;
384
- SerialPort.Binding = MockBinding;
385
- let portPath = 'FAKE_PORT';
386
- MockBinding.createPort(portPath, { echo: false, record: true });
387
- sp = new SerialPort(portPath, { autoOpen: false });
661
+ let sp: SerialPort | SerialPortMock = null;
662
+ if (this._cfg.mock) {
663
+ this.mock = true;
664
+ let portPath = 'MOCK_PORT';
665
+ SerialPortMock.binding.createPort(portPath)
666
+ // SerialPortMock.binding = SerialPortMock;
667
+ // SerialPortMock.createPort(portPath, { echo: false, record: true });
668
+ let opts: SerialPortOpenOptions<AutoDetectTypes> = { path: portPath, autoOpen: false, baudRate: 9600 };
669
+ sp = new SerialPortMock(opts);
388
670
  }
389
671
  else {
390
- this.mockPort = false;
391
- sp = new SerialPort(this._cfg.rs485Port, this._cfg.portSettings);
672
+ this.mock = false;
673
+ let opts: SerialPortOpenOptions<AutoDetectTypes> = extend(true, { path: this._cfg.rs485Port }, this._cfg.portSettings);
674
+ sp = new SerialPort(opts);
392
675
  }
393
676
  return await new Promise<boolean>((resolve, _) => {
394
677
  // The serial port open method calls the callback just once. Unfortunately that is not the case for
@@ -398,10 +681,16 @@ export class RS485Port {
398
681
  if (err) {
399
682
  this.resetConnTimer();
400
683
  this.isOpen = false;
401
- logger.error(`Error opening port ${this.portId}: ${err.message}. ${this._cfg.inactivityRetry > 0 ? `Retry in ${this._cfg.inactivityRetry} seconds` : `Never retrying; inactivityRetry set to ${this._cfg.inactivityRetry}`}`);
684
+ logger.error(`Error opening port ${this.portId}: ${err.message}. ${this._cfg.inactivityRetry > 0 && !this.mock ? `Retry in ${this._cfg.inactivityRetry} seconds` : `Never retrying; (fwiw, inactivityRetry set to ${this._cfg.inactivityRetry})`}`);
402
685
  resolve(false);
686
+ state.equipment.messages.setMessageByCode(`rs485:${this.portId}:connection`, 'error', `${this.name} RS485 port disconnected`);
403
687
  }
404
- else resolve(true);
688
+ else {
689
+ state.equipment.messages.removeItemByCode(`rs485:${this.portId}:connection`);
690
+ resolve(true);
691
+ }
692
+ this.emitPortStats();
693
+
405
694
  });
406
695
  // The event processors below should not resolve or reject the promise. This is the misnomer with the stupid javascript promise
407
696
  // structure when dealing with serial ports. The original promise will be either accepted or rejected above with the open method. These
@@ -409,42 +698,62 @@ export class RS485Port {
409
698
  // for a successul connect and false otherwise.
410
699
  sp.on('open', () => {
411
700
  if (typeof this._port !== 'undefined') logger.info(`Serial Port ${this.portId}: ${this._cfg.rs485Port} recovered from lost connection.`)
412
- else logger.info(`Serial port: ${this._cfg.rs485Port} request to open successful ${this._cfg.portSettings.baudRate}b ${this._cfg.portSettings.dataBits}-${this._cfg.portSettings.parity}-${this._cfg.portSettings.stopBits}`);
701
+ else logger.info(`Serial port: ${sp.path} request to open successful ${sp.baudRate}b ${sp.port.openOptions.dataBits}-${sp.port.openOptions.parity}-${sp.port.openOptions.stopBits}`);
413
702
  this._port = sp;
414
703
  this.isOpen = true;
415
- sp.on('data', (data) => { if (!this.mockPort && !this.isPaused) this.resetConnTimer(); this.pushIn(data); });
704
+ /// if just changing existing port, reset key flags
705
+ this.isRTS = true;
706
+ this.closing = false;
707
+ this._processing = false;
708
+ sp.on('data', (data) => {
709
+ if (!this.mock && !this.isPaused) this.resetConnTimer();
710
+ this.pushIn(data);
711
+ });
416
712
  this.resetConnTimer();
417
713
  this.emitPortStats();
418
714
  });
419
715
  sp.on('close', (err) => {
420
716
  this.isOpen = false;
421
- logger.info(`Serial Port ${this.portId} has been closed ${this.portId}: ${err ? JSON.stringify(err) : ''}`);
717
+ if (err && err.disconnected) {
718
+ logger.info(`Serial Port ${this.portId} - ${this._cfg.rs485Port} has been disconnected and closed. ${JSON.stringify(err)}`)
719
+ }
720
+ else {
721
+ logger.info(`Serial Port ${this.portId} - ${this._cfg.rs485Port} has been closed. ${err ? JSON.stringify(err) : ''}`);
722
+ }
422
723
  });
423
724
  sp.on('error', (err) => {
725
+ // an underlying streams error from a SP write may call the error event
726
+ // instead/in leiu of the error callback
727
+ if (typeof this.writeTimer !== 'undefined') { clearTimeout(this.writeTimer); this.writeTimer = null; }
424
728
  this.isOpen = false;
425
729
  if (sp.isOpen) sp.close((err) => { }); // call this with the error callback so that it doesn't emit to the error again.
426
730
  this.resetConnTimer();
427
731
  logger.error(`Serial Port ${this.portId}: An error occurred : ${this._cfg.rs485Port}: ${JSON.stringify(err)}`);
428
732
  this.emitPortStats();
733
+
429
734
  });
430
735
  });
431
736
  }
432
737
  }
433
738
  public async closeAsync(): Promise<boolean> {
434
739
  try {
435
- if (this._closing) return false;
436
- this._closing = true;
740
+ if (this.closing) return false;
741
+ this.closing = true;
437
742
  if (this.connTimer) clearTimeout(this.connTimer);
438
743
  if (typeof this._port !== 'undefined' && this.isOpen) {
439
- let success = await new Promise<boolean>((resolve, reject) => {
744
+ let success = await new Promise<boolean>(async (resolve, reject) => {
440
745
  if (this._cfg.netConnect) {
441
746
  this._port.removeAllListeners();
442
747
  this._port.once('error', (err) => {
443
748
  if (err) {
444
- logger.error(`Error closing ${this.portId} ${ this._cfg.netHost }: ${ this._cfg.netPort } / ${ this._cfg.rs485Port }: ${ err }`);
749
+ logger.error(`Error closing ${this.portId} ${this._cfg.netHost}: ${this._cfg.netPort} / ${this._cfg.rs485Port}: ${err}`);
445
750
  resolve(false);
446
751
  }
447
752
  else {
753
+ // RSG - per the docs the error event will subsequently
754
+ // fire the close event. This block should never be called and
755
+ // likely isn't needed; error listener should always have an err passed
756
+ this._port.removeAllListeners(); // call again since we added 2x .once below.
448
757
  this._port = undefined;
449
758
  this.isOpen = false;
450
759
  logger.info(`Successfully closed (socat) ${this.portId} port ${this._cfg.netHost}:${this._cfg.netPort} / ${this._cfg.rs485Port}`);
@@ -455,6 +764,7 @@ export class RS485Port {
455
764
  logger.info(`Net connect (socat) ${this.portId} closing: ${this._cfg.netHost}:${this._cfg.netPort}`);
456
765
  });
457
766
  this._port.once('close', (p) => {
767
+ this._port.removeAllListeners(); // call again since we added 2x .once above.
458
768
  this.isOpen = false;
459
769
  this._port = undefined;
460
770
  logger.info(`Net connect (socat) ${this.portId} successfully closed: ${this._cfg.netHost}:${this._cfg.netPort}`);
@@ -463,19 +773,31 @@ export class RS485Port {
463
773
  logger.info(`Net connect (socat) ${this.portId} request close: ${this._cfg.netHost}:${this._cfg.netPort}`);
464
774
  // Unfortunately the end call does not actually work in node. It will simply not return anything so we are going to
465
775
  // just call destroy and forcibly close it.
466
- this._port.destroy();
776
+ let port = this._port as net.Socket;
777
+ await new Promise<boolean>((resfin, _) => {
778
+ port.end(() => {
779
+ logger.info(`Net connect (socat) ${this.portId} sent FIN packet: ${this._cfg.netHost}:${this._cfg.netPort}`);
780
+ resfin(true);
781
+ });
782
+ });
783
+
784
+ if (typeof this._port !== 'undefined') {
785
+ logger.info(`Net connect (socat) destroy socket: ${this._cfg.netHost}:${this._cfg.netPort}`);
786
+ this._port.destroy();
787
+ }
467
788
  }
468
- else if (typeof this._port.close === 'function') {
789
+ else if (!(this._port instanceof net.Socket) && typeof this._port.close === 'function') {
469
790
  this._port.close((err) => {
470
791
  if (err) {
471
792
  logger.error(`Error closing ${this.portId} serial port ${this._cfg.rs485Port}: ${err}`);
472
793
  resolve(false);
473
794
  }
474
795
  else {
796
+ this._port.removeAllListeners(); // remove any listeners still around
475
797
  this._port = undefined;
476
- logger.info(`Successfully closed ${this.portId} serial port ${this._cfg.rs485Port}`);
477
- resolve(true);
798
+ logger.info(`Successfully closed portId ${this.portId} for serial port ${this._cfg.rs485Port}`);
478
799
  this.isOpen = false;
800
+ resolve(true);
479
801
  }
480
802
  });
481
803
  }
@@ -488,8 +810,8 @@ export class RS485Port {
488
810
  return success;
489
811
  }
490
812
  return true;
491
- } catch (err) { logger.error(`Error closing comms connection ${this.portId}: ${err.message}`); return Promise.resolve(false); }
492
- finally { this._closing = false; this.emitPortStats(); }
813
+ } catch (err) { logger.error(`Error closing comms connection ${this.portId}: ${err.message}`); return false; }
814
+ finally { this.emitPortStats(); }
493
815
  }
494
816
  public pause() { this.isPaused = true; this.clearBuffer(); this.drain(function (err) { }); }
495
817
  // RKS: Resume is executed in a closure. This is because we want the current async process to complete
@@ -498,7 +820,7 @@ export class RS485Port {
498
820
  protected resetConnTimer(...args) {
499
821
  //console.log(`resetting connection timer`);
500
822
  if (this.connTimer !== null) clearTimeout(this.connTimer);
501
- if (!this._cfg.mockPort && this._cfg.inactivityRetry > 0 && !this._closing) this.connTimer = setTimeout(async () => {
823
+ if (!this._cfg.mockPort && this._cfg.inactivityRetry > 0 && !this.closing) this.connTimer = setTimeout(async () => {
502
824
  try {
503
825
  if (this._cfg.netConnect)
504
826
  logger.warn(`Inactivity timeout for ${this.portId} serial port ${this._cfg.netHost}:${this._cfg.netPort}/${this._cfg.rs485Port} after ${this._cfg.inactivityRetry} seconds`);
@@ -512,36 +834,74 @@ export class RS485Port {
512
834
  }, this._cfg.inactivityRetry * 1000);
513
835
  }
514
836
  // Data management functions
515
- public drain(cb: Function) {
837
+ public drain(cb: (err?: Error) => void) {
516
838
  if (typeof this._port === 'undefined') {
517
839
  logger.debug(`Serial Port ${this.portId}: Cannot perform drain function on port that is not open.`);
518
840
  cb();
519
841
  }
520
- if (typeof (this._port.drain) === 'function')
521
- this._port.drain(cb);
842
+ if ((this._port instanceof SerialPort || this._port instanceof SerialPortMock) && typeof (this._port.drain) === 'function')
843
+ this._port.drain(cb as (err) => void);
522
844
  else // Call the method immediately as the port doesn't wait to send.
523
845
  cb();
524
846
  }
525
- public write(bytes: Buffer, cb: Function) {
847
+ public write(msg: Outbound, cb: (err?: Error) => void) {
848
+ let bytes = Buffer.from(msg.toPacket());
849
+ let _cb = cb;
526
850
  if (this._cfg.netConnect) {
527
851
  // SOCAT drops the connection and destroys the stream. Could be weeks or as little as a day.
528
852
  if (typeof this._port === 'undefined' || this._port.destroyed !== false) {
529
853
  this.openAsync().then(() => {
530
- this._port.write(bytes, 'binary', cb);
854
+ (this._port as net.Socket).write(bytes, 'binary', cb);
531
855
  });
532
856
  }
533
857
  else
534
- this._port.write(bytes, 'binary', cb);
858
+ (this._port as net.Socket).write(bytes, 'binary', cb);
535
859
  }
536
- else
537
- this._port.write(bytes, cb);
860
+ else {
861
+ if (this._port instanceof SerialPortMock && this.mock === true) {
862
+ msg.processMock();
863
+ cb();
864
+ }
865
+ else {
866
+
867
+ this.writeTimer = setTimeout(() => {
868
+ // RSG - I ran into a scenario where the underlying stream
869
+ // processor was not retuning the CB and comms would
870
+ // completely stop. This timeout is a failsafe.
871
+ // Further, the underlying stream may throw an event error
872
+ // and not call the callback (per node docs) hence the
873
+ // public writeTimer.
874
+ if (typeof cb === 'function') {
875
+ cb = undefined;
876
+ _cb(new Error(`Serialport stream has not called the callback in 3s.`));
877
+ }
878
+ }, 3000);
879
+ this._port.write(bytes, (err) => {
880
+ if (typeof this.writeTimer !== 'undefined') {
881
+ clearTimeout(this.writeTimer);
882
+ this.writeTimer = null;
883
+ // resolve();
884
+ if (typeof cb === 'function') {
885
+ cb = undefined;
886
+ _cb(err);
887
+ }
888
+ }
889
+ });
890
+ }
891
+
892
+ }
893
+ }
894
+ // make public for now; should enable writing directly to mock port at Conn level...
895
+ public pushIn(pkt: Buffer) {
896
+ this._inBuffer.push.apply(this._inBuffer, pkt.toJSON().data); if (sys.isReady) setImmediate(() => { this.processPackets(); });
897
+ }
898
+ private pushOut(msg) {
899
+ this._outBuffer.push(msg); setImmediate(() => { this.processPackets(); });
538
900
  }
539
- private pushIn(pkt) { this._inBuffer.push.apply(this._inBuffer, pkt.toJSON().data); if(sys.isReady) setImmediate(() => { this.processPackets(); }); }
540
- private pushOut(msg) { this._outBuffer.push(msg); setImmediate(() => { this.processPackets(); }); }
541
901
  private clearBuffer() { this._inBuffer.length = 0; this.clearOutboundBuffer(); }
542
902
  private closeBuffer() { clearTimeout(this.procTimer); this.clearBuffer(); this._msg = undefined; }
543
903
  private clearOutboundBuffer() {
544
- let processing = this._processing;
904
+ // let processing = this._processing; // we are closing the port. don't need to reinstate this status afterwards
545
905
  clearTimeout(this.procTimer);
546
906
  this.procTimer = null;
547
907
  this._processing = true;
@@ -562,11 +922,11 @@ export class RS485Port {
562
922
  this.counter.sndAborted++;
563
923
  msg = this._outBuffer.shift();
564
924
  }
565
- this._processing = processing;
566
- this.isRTS = true;
925
+ //this._processing = false; // processing; - we are closing the port
926
+ //this.isRTS = true; // - we are closing the port
567
927
  }
568
928
  private processPackets() {
569
- if (this._processing) return;
929
+ if (this._processing || this.closing) return;
570
930
  if (this.procTimer) {
571
931
  clearTimeout(this.procTimer);
572
932
  this.procTimer = null;
@@ -592,7 +952,7 @@ export class RS485Port {
592
952
  protected processOutboundPackets() {
593
953
  let msg: Outbound;
594
954
  if (!this.processWaitPacket() && this._outBuffer.length > 0) {
595
- if (this.isOpen) {
955
+ if (this.isOpen || this.closing) {
596
956
  if (this.isRTS) {
597
957
  msg = this._outBuffer.shift();
598
958
  if (typeof msg === 'undefined' || !msg) return;
@@ -617,6 +977,7 @@ export class RS485Port {
617
977
  this.counter.sndAborted++;
618
978
  this.counter.updatefailureRate();
619
979
  this.emitPortStats();
980
+ // return; // if port isn't open, do not continue and setTimeout
620
981
  }
621
982
  }
622
983
  // RG: added the last `|| typeof msg !== 'undef'` because virtual chem controller only sends a single packet
@@ -633,67 +994,90 @@ export class RS485Port {
633
994
  // This ends in goofiness as it can send more than one message at a time while it
634
995
  // waits for the command buffer to be flushed. NOTE: There is no success message and the callback to
635
996
  // write only verifies that the buffer got ahold of it.
636
- if (!this.isRTS || this.mockPort) return;
637
- this.isRTS = false;
638
- var bytes = msg.toPacket();
639
- if (this.isOpen) {
640
- if (msg.remainingTries <= 0) {
641
- // It will almost never fall into here. The rare case where
642
- // we have an RTS semaphore and a waiting response might make it go here.
643
- msg.failed = true;
644
- this._waitingPacket = null;
645
- if (typeof msg.onAbort === 'function') msg.onAbort();
646
- else logger.warn(`Message aborted after ${msg.tries} attempt(s): ${msg.toShortPacket()} `);
647
- let err = new OutboundMessageError(msg, `Message aborted after ${msg.tries} attempt(s): ${msg.toShortPacket()} `);
648
- if (typeof msg.onComplete === 'function') msg.onComplete(err, undefined);
649
- if (msg.requiresResponse) {
650
- if (msg.response instanceof Response && typeof (msg.response.callback) === 'function') {
651
- setTimeout(msg.response.callback, 100, msg);
997
+ let self = this;
998
+ try {
999
+ if (!this.isRTS || this.closing) return;
1000
+ var bytes = msg.toPacket();
1001
+ if (this.isOpen) {
1002
+ this.isRTS = false; // only set if port is open, otherwise it won't be set back to true
1003
+ if (msg.remainingTries <= 0) {
1004
+ // It will almost never fall into here. The rare case where
1005
+ // we have an RTS semaphore and a waiting response might make it go here.
1006
+ msg.failed = true;
1007
+ this._waitingPacket = null;
1008
+ if (typeof msg.onAbort === 'function') msg.onAbort();
1009
+ else logger.warn(`Message aborted after ${msg.tries} attempt(s): ${msg.toShortPacket()} `);
1010
+ let err = new OutboundMessageError(msg, `Message aborted after ${msg.tries} attempt(s): ${msg.toShortPacket()} `);
1011
+ if (typeof msg.onComplete === 'function') msg.onComplete(err, undefined);
1012
+ if (msg.requiresResponse) {
1013
+ if (msg.response instanceof Response && typeof (msg.response.callback) === 'function') {
1014
+ setTimeout(msg.response.callback, 100, msg);
1015
+ }
652
1016
  }
1017
+ this.counter.sndAborted++;
1018
+ this.isRTS = true;
1019
+ return;
653
1020
  }
654
- this.counter.sndAborted++;
655
- this.isRTS = true;
656
- return;
1021
+ this.counter.bytesSent += bytes.length;
1022
+ msg.timestamp = new Date();
1023
+ logger.packet(msg);
1024
+ this.write(msg, (err) => {
1025
+ clearTimeout(this.writeTimer);
1026
+ this.writeTimer = null;
1027
+ msg.tries++;
1028
+ this.isRTS = true;
1029
+ if (err) {
1030
+ if (msg.remainingTries > 0) self._waitingPacket = msg;
1031
+ else {
1032
+ msg.failed = true;
1033
+ logger.warn(`Message aborted after ${msg.tries} attempt(s): ${bytes}: ${err} `);
1034
+ // this is a hard fail. We don't have any more tries left and the message didn't
1035
+ // make it onto the wire.
1036
+ let error = new OutboundMessageError(msg, `Message aborted after ${msg.tries} attempt(s): ${err} `);
1037
+ if (typeof msg.onComplete === 'function') msg.onComplete(error, undefined);
1038
+ self._waitingPacket = null;
1039
+ self.counter.sndAborted++;
1040
+ }
1041
+ return;
1042
+ }
1043
+ else {
1044
+ logger.verbose(`Wrote packet [Port ${this.portId} id: ${msg.id}] [${bytes}].Retries remaining: ${msg.remainingTries} `);
1045
+ // We have all the success we are going to get so if the call succeeded then
1046
+ // don't set the waiting packet when we aren't actually waiting for a response.
1047
+ if (!msg.requiresResponse) {
1048
+ // As far as we know the message made it to OCP.
1049
+ self._waitingPacket = null;
1050
+ self.counter.sndSuccess++;
1051
+ if (typeof msg.onComplete === 'function') msg.onComplete(err, undefined);
1052
+
1053
+ }
1054
+ else if (msg.remainingTries >= 0) {
1055
+ self._waitingPacket = msg;
1056
+ }
1057
+ }
1058
+ self.counter.updatefailureRate();
1059
+ self.emitPortStats();
1060
+ });
657
1061
  }
658
- this.counter.bytesSent += bytes.length;
659
- msg.timestamp = new Date();
660
- logger.packet(msg);
661
- this.write(Buffer.from(bytes), (err) => {
1062
+ }
1063
+ catch (err) {
1064
+ logger.error(`Error sending message: ${err.message}
1065
+ for message: ${msg.toShortPacket()}`)
1066
+ // the show, err, messages, must go on!
1067
+ if (this.isOpen) {
1068
+ clearTimeout(this.writeTimer);
1069
+ this.writeTimer = null;
662
1070
  msg.tries++;
663
1071
  this.isRTS = true;
664
- if (err) {
665
- logger.error('Error writing packet %s', err);
666
- // We had an error so we need to set the waiting packet if there are retries
667
- if (msg.remainingTries > 0) this._waitingPacket = msg;
668
- else {
669
- msg.failed = true;
670
- logger.warn(`Message aborted after ${msg.tries} attempt(s): ${bytes}: ${err} `);
671
- // This is a hard fail. We don't have any more tries left and the message didn't
672
- // make it onto the wire.
673
- let error = new OutboundMessageError(msg, `Message aborted after ${msg.tries} attempt(s): ${err} `);
674
- if (typeof msg.onComplete === 'function') msg.onComplete(error, undefined);
675
- this._waitingPacket = null;
676
- this.counter.sndAborted++;
677
- }
678
- }
679
- else {
680
- logger.verbose(`Wrote packet[${bytes}].Retries remaining: ${msg.remainingTries} `);
681
- // We have all the success we are going to get so if the call succeeded then
682
- // don't set the waiting packet when we aren't actually waiting for a response.
683
- if (!msg.requiresResponse) {
684
- // As far as we know the message made it to OCP.
685
- this._waitingPacket = null;
686
- this.counter.sndSuccess++;
687
- if (typeof msg.onComplete === 'function') msg.onComplete(err, undefined);
1072
+ msg.failed = true;
1073
+ // this is a hard fail. We don't have any more tries left and the message didn't
1074
+ // make it onto the wire.
1075
+ let error = new OutboundMessageError(msg, `Message aborted after ${msg.tries} attempt(s): ${err} `);
1076
+ if (typeof msg.onComplete === 'function') msg.onComplete(error, undefined);
1077
+ this._waitingPacket = null;
1078
+ this.counter.sndAborted++;
688
1079
 
689
- }
690
- else if (msg.remainingTries >= 0) {
691
- this._waitingPacket = msg;
692
- }
693
- }
694
- this.counter.updatefailureRate();
695
- this.emitPortStats();
696
- });
1080
+ }
697
1081
  }
698
1082
  }
699
1083
  private clearResponses(msgIn: Inbound) {
@@ -742,21 +1126,22 @@ export class RS485Port {
742
1126
  let status = this.isOpen ? 'open' : this._cfg.enabled ? 'closed' : 'disabled';
743
1127
  return extend(true, { portId: this.portId, status: status, reconnects: this.reconnects }, this.counter)
744
1128
  }
745
- private emitPortStats() {
1129
+ public emitPortStats() {
746
1130
  webApp.emitToChannel('rs485PortStats', 'rs485Stats', this.stats);
747
1131
  }
748
1132
  private processCompletedMessage(msg: Inbound, ndx): number {
749
1133
  msg.timestamp = new Date();
750
1134
  msg.portId = this.portId;
751
1135
  msg.id = Message.nextMessageId;
1136
+ //console.log(`msg id ${msg.id} assigned to port${msg.portId} action:${msg.action} ${msg.toShortPacket()}`)
752
1137
  this.counter.recCollisions += msg.collisions;
753
1138
  this.counter.recRewinds += msg.rewinds;
754
- logger.packet(msg);
755
1139
  this.emitPortStats();
756
1140
  if (msg.isValid) {
757
1141
  this.counter.recSuccess++;
758
1142
  this.counter.updatefailureRate();
759
1143
  msg.process();
1144
+ //conn.queueInboundToAnslq25(msg);
760
1145
  this.clearResponses(msg);
761
1146
  }
762
1147
  else {
@@ -765,6 +1150,7 @@ export class RS485Port {
765
1150
  console.log('RS485 Stats:' + this.counter.toLog());
766
1151
  ndx = this.rewindFailedMessage(msg, ndx);
767
1152
  }
1153
+ logger.packet(msg); // RSG - Moving this after msg clearing responses so emit will include responseFor data
768
1154
  return ndx;
769
1155
  }
770
1156
  private rewindFailedMessage(msg: Inbound, ndx: number): number {
@@ -805,5 +1191,20 @@ export class RS485Port {
805
1191
  } while (ndx < this._inBytes.length);
806
1192
  }
807
1193
  }
1194
+ public hasAssignedEquipment() {
1195
+ let pumps = sys.pumps.get();
1196
+ for (let i = 0; i < pumps.length; i++) {
1197
+ if (pumps[i].portId === this.portId) {
1198
+ return true;
1199
+ }
1200
+ }
1201
+ let chlors = sys.chlorinators.get();
1202
+ for (let i = 0; i < chlors.length; i++) {
1203
+ if (chlors[i].portId === this.portId) {
1204
+ return true;
1205
+ }
1206
+ }
1207
+ return false;
1208
+ }
808
1209
  }
809
1210
  export var conn: Connection = new Connection();