nodejs-poolcontroller 8.1.0 → 8.1.2

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/README.md CHANGED
@@ -66,8 +66,6 @@ If you don't know anything about NodeJS, these directions might be helpful.
66
66
  1. Run the app with `npm start`.
67
67
  * `npm start` will compile the Typescript code. You should use this every time you download/clone/pull the latest code.
68
68
  * `npm run start:cached` will run the app without compiling the code which can be much faster.
69
- 1. Running `npm start` will also create a `config.json` file for your installation. If you need to modify any properties (e.g. the path to your serialport adapter, enabling socat, etc) then stop the app, edit the `config.json` per the [instructions](module_nodejs-poolController--config.json) below, and start the app again.
70
- 1. Verify your pool equipment is correctly identified by inspecting the `/data/*.json` files.
71
69
  1. Install a [webclient](module_nodejs-poolController--clients) for a browser experience and/or a [binding](module_nodejs-poolController--bindings) to have two way control with Home Automation systems.
72
70
 
73
71
  For a very thorough walk-through, see [this](https://www.troublefreepool.com/threads/pentair-intellicenter-pool-control-dashboard-instructional-guide.218514/) great thread on Trouble Free Pool. Thanks @MyAZPool.
@@ -101,48 +99,39 @@ To do anything with this app, you need a client to connect to it. A client can
101
99
 
102
100
  ## Web Clients
103
101
  1. [nodejs-poolController-dashPanel](https://github.com/rstrouse/nodejs-poolController-dashPanel). Full compatibility with IntelliCenter, *Touch, REM (RelayEquipmentManager).
104
- 1. Deprecated - ~~[nodejs-poolController-webClient](http://github.com/tagyoureit/nodejs-poolController-webClient). Built primarily around EasyTouch/IntelliTouch but will work with other systems.~~
105
102
 
106
- * This app has the default to only listen to clients from localhost (127.0.0.1). If you need to have clients connect from other machines you will need to change the [ip](#module_nodejs-poolController--config.json) in `config.json`.
107
103
 
108
104
  <a name="module_nodejs-poolController--bindings"></a>
109
105
 
110
106
  ## Home Automation Bindings (previously Integrations)
111
- **NOTE: Existing integrations built of 5.3 or earlier WILL NOT WORK. They need to be upgraded to leverage 6.0. **
112
107
 
113
- Available for 6.x:
114
- * [Vera Home Automation Hub](https://github.com/rstrouse/nodejs-poolController-veraPlugin) - A plugin that integrates with nodejs-poolController. [Bindings Directions](https://github.com/tagyoureit/nodejs-poolController/wiki/Bindings-Integrations-in-2.0#vera)
115
- * [Hubitat](https://github.com/bsileo/hubitat_poolcontroller) by @bsileo (prev help from @johnny2678, @donkarnag, @arrmo). [Bindings Directions](https://github.com/tagyoureit/nodejs-poolController/wiki/Bindings-Integrations-in-2.0#smartthingshubitat)
108
+ Available automations:
109
+ * [Vera Home Automation Hub](https://github.com/rstrouse/nodejs-poolController-veraPlugin) - A plugin that integrates with nodejs-poolController. [Bindings Directions](https://github.com/tagyoureit/nodejs-poolController/wiki/Bindings-Integrations#vera)
110
+ * [Hubitat](https://github.com/bsileo/hubitat_poolcontroller) by @bsileo (prev help from @johnny2678, @donkarnag, @arrmo). [Bindings Directions](https://github.com/tagyoureit/nodejs-poolController/wiki/Bindings-Integrations#smartthingshubitat)
116
111
  * [Homebridge/Siri/EVE](https://github.com/gadget-monk/homebridge-poolcontroller) by @gadget-monk, adopted from @leftyflip
117
- * InfluxDB - [Bindings Directions](https://github.com/tagyoureit/nodejs-poolController/wiki/Bindings-Integrations-in-2.0#influx)
118
- * [MQTT](https://github.com/crsherman/nodejs-poolController-mqtt) original release by @crsherman, re-write by @kkzonie, testing by @baudfather and others. [Bindings Directions](https://github.com/tagyoureit/nodejs-poolController/wiki/Bindings-Integrations-in-2.0#mqtt)
112
+ * InfluxDB - [Bindings Directions](https://github.com/tagyoureit/nodejs-poolController/wiki/Bindings-Integrations#influx)
113
+ * [MQTT](https://github.com/crsherman/nodejs-poolController-mqtt) original release by @crsherman, re-write by @kkzonie, testing by @baudfather and others. [Bindings Directions](https://github.com/tagyoureit/nodejs-poolController/wiki/Bindings-Integrations#mqtt)
119
114
  * [Homeseer](https://github.com/tagyoureit/nodejs-poolController/wiki/Homeseer-Setup-Instructions) - Integration directions by @miamijerry to integrate Homeseer through MQTT
120
115
 
121
- Need to be updated:
116
+ Outdated:
122
117
  * [Another SmartThings Controller](https://github.com/dhop90/pentair-pool-controller/blob/master/README.md) by @dhop90
123
118
  * [ISY](src/integrations/socketISY.js). Original credit to @blueman2, enhancements by @mayermd
124
119
  * [ISY Polyglot NodeServer](https://github.com/brianmtreese/nodejs-pool-controller-polyglotv2) created by @brianmtreese
125
120
 
126
121
  # Support
127
- 1. For discussions, recommendations, designs, and clarifications, we recommend you join the [Github discussions](https://github.com/tagyoureit/nodejs-poolController/discussions or [Gitter Chat room](https://gitter.im/pentair_pool/Lobby).
122
+ 1. For discussions, recommendations, designs, and clarifications, we recommend you join the [Github discussions](https://github.com/tagyoureit/nodejs-poolController/discussions.
128
123
  1. Check the [wiki](https://github.com/tagyoureit/nodejs-poolController/wiki) for tips, tricks and additional documentation.
129
124
  1. For bug reports you can open a [github issue](https://github.com/tagyoureit/nodejs-poolController/issues/new),
130
125
 
131
- ### Virtual Controller
132
- v6 adds all new configuration and support for virtual pumps, chlorinators (and soon, Intellichem)
133
-
134
- * [Virtual Pump Directions](https://github.com/tagyoureit/nodejs-poolController/wiki/Virtual-Pump-Controller---v6)
135
- * [Virtual Chlorinator Directions](https://github.com/tagyoureit/nodejs-poolController/wiki/Virtual-Chlorinator-Controller-v6)
136
- * Virtual Chem Controller
137
126
 
138
127
  # Changes
139
128
  See [Changelog](https://github.com/tagyoureit/nodejs-poolController/blob/master/Changelog)
140
129
 
141
-
142
130
  <a name="module_nodejs-poolController--config.json"></a>
143
131
  # Config.json changes
144
132
 
145
133
  ## Controller section - changes to the communications for the app
134
+ Most of these can be configured directly from the UI in dashPanel.
146
135
  * `rs485Port` - set to the name of you rs485 controller. See [wiki](https://github.com/tagyoureit/nodejs-poolController/wiki/RS-485-Adapter-Details) for details and testing.
147
136
  * `portSettings` - should not need to be changed for RS485
148
137
  * `mockPort` - opens a "fake" port for this app to communicate on. Can be used with [packet captures/replays](https://github.com/tagyoureit/nodejs-poolController/wiki/How-to-capture-all-packets-for-issue-resolution).
@@ -186,7 +175,7 @@ See [Changelog](https://github.com/tagyoureit/nodejs-poolController/blob/master/
186
175
  # License
187
176
 
188
177
  nodejs-poolController. An application to control pool equipment.
189
- Copyright (C) 2016, 2017. Russell Goldin, tagyoureit. russ.goldin@gmail.com
178
+ Copyright (C) 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025. Russell Goldin, tagyoureit. russ.goldin@gmail.com
190
179
 
191
180
  This program is free software: you can redistribute it and/or modify
192
181
  it under the terms of the GNU Affero General Public License as
@@ -196,7 +196,11 @@ export class MessagesMock {
196
196
  HeaterStateMessage.process(outboundMsg);
197
197
  break;*/
198
198
  case Protocol.Chlorinator:
199
- mockChlor.process(msg);
199
+ // Only process outbound messages (commands from OCP to chlorinator)
200
+ // Inbound messages (responses from chlorinator to OCP) should not be processed by mock
201
+ if (msg.dest >= 80) {
202
+ mockChlor.process(msg);
203
+ }
200
204
  /*
201
205
  case Protocol.Hayward:
202
206
  PumpStateMessage.processHayward(msg);
@@ -50,7 +50,7 @@ export class MockSystemBoard {
50
50
  return await msg.sendAsync();
51
51
  // is the controller on a real/physical port or a mock port?
52
52
  /* let port = conn.findPortById(sys.anslq25.portId);
53
- if (port.mockPort) {
53
+ if (port.mock) {
54
54
  let inbound = new Inbound();
55
55
  inbound.protocol = msg.protocol;
56
56
  inbound.header = msg.header;
@@ -15,13 +15,16 @@ export class MockChlorinator {
15
15
  switch (inbound.action){
16
16
  case 0: // Set control OCP->Chlorinator: [16,2,80,0][0][98,16,3]
17
17
  this.chlorSetControl(inbound, response);
18
+ break;
18
19
  case 17: // OCP->Chlorinator set output. [16,2,80,17][15][130,16,3]
19
20
  this.chlorSetOutput(inbound, response);
20
- case 19: // iChlor keep alive(?) [16, 2, 80, 19][117, 16, 3]
21
- // need response
21
+ break;
22
+ case 19: // iChlor keep alive(?) [16, 2, 80, 19][117, 16, 3]
23
+ this.chlorKeepAlive(inbound, response);
22
24
  break;
23
25
  case 20: // OCP->Chlorinator Get model [16,2,80,20][0][118,16,3]
24
26
  this.chlorGetModel(inbound, response);
27
+ break;
25
28
  default:
26
29
  logger.info(`No mock chlorinator response for ${inbound.toShortPacket()} `);
27
30
  }
@@ -45,13 +48,34 @@ export class MockChlorinator {
45
48
  /*
46
49
  {"port":0,"id":42639,"valid":true,"dir":"out","proto":"chlorinator","pkt":[[],[], [16,2,80,17], [100],[215,16,3]],"ts":"2022-07-19T21:46:00.302-0700"}
47
50
  {"port":0,"id":42640,"valid":true,"dir":"in","proto":"chlorinator","for":[42639],"pkt":[[],[],[16,2,0,18],[78,128],[242,16,3]],"ts": "2022-07-19T21:46:00.341-0700"} */
48
- response.action = 18;
49
- response.appendPayloadBytes(0, 2);
50
- // ideal high = 4500 = 90 * 50; ideal low = 2800 = 56 * 50
51
- response.setPayloadByte(0, this.random(90-56, true)+56, 75)
52
- response.setPayloadByte(1, 128);
53
- conn.queueSendMessage(response);
51
+ // Simulate a response from the chlorinator (inbound message)
52
+ const payload = [Math.floor(this.random(90-56, true))+56, 128];
53
+ const header = [16, 2, 0, 18];
54
+ const term = [242, 16, 3];
55
+ let responseMsg = new Inbound();
56
+ responseMsg.protocol = inbound.protocol;
57
+ responseMsg.portId = inbound.portId;
58
+ responseMsg.header = header;
59
+ responseMsg.payload = payload;
60
+ responseMsg.term = term;
61
+ // The Inbound class will parse header/payload/action/source/dest automatically
62
+ setTimeout(() => {
63
+ let port = conn.findPortById(inbound.portId);
64
+ if (port) {
65
+ port.pushIn(Buffer.from(responseMsg.toPacket()));
66
+ }
67
+ }, 50);
54
68
  }
69
+ public chlorKeepAlive(inbound: Inbound, response: Outbound){
70
+ /*
71
+ {"port":0,"id":42647,"valid":true,"dir":"out","proto":"chlorinator","pkt":[[],[], [16,2,80,19],[117,16,3]],"ts":"2022-07-19T21:46:00.645-0700"}
72
+ {"port":0,"id":42648,"valid":true,"dir":"in","proto":"chlorinator","for":[42647],"pkt":[[],[],[16,2,0,20],[0],[118,16,3]],"ts":"2022-07-19T21:46:00.700-0700"} */
73
+ response.action = 20;
74
+ response.appendPayloadBytes(0, 1);
75
+ response.setPayloadByte(0, 0);
76
+ conn.queueSendMessage(response);
77
+ }
78
+
55
79
  public chlorGetModel(inbound: Inbound, response: Outbound){
56
80
  /*
57
81
  {"port":0,"id":42645,"valid":true,"dir":"out","proto":"chlorinator","pkt":[[],[], [16,2,80,20], [0],[118,16,3]],"ts":"2022-07-19T21:46:00.645-0700"}
package/app.ts CHANGED
@@ -25,7 +25,7 @@ import { conn } from "./controller/comms/Comms";
25
25
  import { sys } from "./controller/Equipment";
26
26
 
27
27
  import { state } from "./controller/State";
28
- import { webApp } from "./web/Server";
28
+ import { webApp, REMInterfaceServer } from "./web/Server";
29
29
  import * as readline from 'readline';
30
30
  import { sl } from './controller/comms/ScreenLogic'
31
31
 
@@ -52,6 +52,9 @@ export async function startPacketCapture(bResetLogs: boolean) {
52
52
  if (bResetLogs){
53
53
  sys.resetSystem();
54
54
  }
55
+
56
+ // Start packet capture on the REM server
57
+ await REMInterfaceServer.startPacketCaptureOnRemServer();
55
58
  }
56
59
  catch (err) {
57
60
  console.error(`Error starting replay: ${ err.message }`);
@@ -61,7 +64,12 @@ export async function stopPacketCaptureAsync() {
61
64
  let log = config.getSection('log');
62
65
  log.app.captureForReplay = false;
63
66
  config.setSection('log', log);
64
- return logger.stopCaptureForReplayAsync();
67
+
68
+ // Stop packet capture on the REM server and collect its logs
69
+ let remLogs = await REMInterfaceServer.stopPacketCaptureOnRemServer();
70
+
71
+ // Pass REM logs to the logger for inclusion in the backup
72
+ return logger.stopCaptureForReplayAsync(remLogs);
65
73
  }
66
74
  export async function stopAsync(): Promise<void> {
67
75
  try {
@@ -1096,7 +1096,7 @@ export class ScheduleStateCollection extends EqStateCollection<ScheduleState> {
1096
1096
  }
1097
1097
  st.calcSchedule(state.time, sys.schedules.getItemById(ssched.id));
1098
1098
  if (typeof st.startTime === 'undefined') continue;
1099
- if (ssched.isOn || st.shouldBeOn || st.startTime.getTime() > new Date().getTime()) activeScheds.push(ssched);
1099
+ if (ssched.isOn || st.shouldBeOn || (st.startTime && st.startTime.getTime() > new Date().getTime())) activeScheds.push(ssched);
1100
1100
  }
1101
1101
  return activeScheds;
1102
1102
  }
@@ -1256,7 +1256,7 @@ export class ScheduleTime extends ChildEqState {
1256
1256
  let dtCalc = typeof this.calculatedDate !== 'undefined' && typeof this.calculatedDate.getTime === 'function' ? new Date(this.calculatedDate.getTime()).setHours(0, 0, 0, 0) : new Date(1970, 0, 1, 0, 0).getTime();
1257
1257
  let recalc = !this.calculated;
1258
1258
  if (!recalc && sod.getTime() !== dtCalc) recalc = true;
1259
- if (!recalc && (this.endTime.getTime() < new Date().getTime() && this.startTime.getTime() < dtCalc)) {
1259
+ if (!recalc && (this.endTime && this.endTime.getTime() < new Date().getTime() && this.startTime && this.startTime.getTime() < dtCalc)) {
1260
1260
  recalc = true;
1261
1261
  logger.info(`Recalculating expired schedule ${sched.id}`);
1262
1262
  }
@@ -3030,8 +3030,8 @@ class TouchChemControllerCommands extends ChemControllerCommands {
3030
3030
  if (isNaN(alkalinity)) return Promise.reject(new InvalidEquipmentDataError(`Invalid alkalinity`, 'chemController', alkalinity));
3031
3031
  if (isNaN(borates)) return Promise.reject(new InvalidEquipmentDataError(`Invalid borates`, 'chemController', borates));
3032
3032
  let schem = state.chemControllers.getItemById(chem.id, true);
3033
- let pHSetpoint = typeof data.ph !== 'undefined' && typeof data.ph.setpoint !== 'undefined' ? parseFloat(data.ph.setpoint) : chem.ph.setpoint;
3034
- let orpSetpoint = typeof data.orp !== 'undefined' && typeof data.orp.setpoint !== 'undefined' ? parseInt(data.orp.setpoint, 10) : chem.orp.setpoint;
3033
+ let pHSetpoint = (typeof data.ph !== 'undefined' && typeof data.ph.setpoint !== 'undefined') ? parseFloat(data.ph.setpoint) : chem.ph.setpoint;
3034
+ let orpSetpoint = (typeof data.orp !== 'undefined' && typeof data.orp.setpoint !== 'undefined') ? parseInt(data.orp.setpoint, 10) : chem.orp.setpoint;
3035
3035
  let lsiRange = typeof data.lsiRange !== 'undefined' ? data.lsiRange : chem.lsiRange || {};
3036
3036
  if (typeof data.lsiRange !== 'undefined') {
3037
3037
  if (typeof data.lsiRange.enabled !== 'undefined') lsiRange.enabled = utils.makeBool(data.lsiRange.enabled);
@@ -3040,31 +3040,31 @@ class TouchChemControllerCommands extends ChemControllerCommands {
3040
3040
  }
3041
3041
  if (isNaN(pHSetpoint) || pHSetpoint > type.ph.max || pHSetpoint < type.ph.min) return Promise.reject(new InvalidEquipmentDataError(`Invalid pH setpoint`, 'ph.setpoint', pHSetpoint));
3042
3042
  if (isNaN(orpSetpoint) || orpSetpoint > type.orp.max || orpSetpoint < type.orp.min) return Promise.reject(new InvalidEquipmentDataError(`Invalid orp setpoint`, 'orp.setpoint', orpSetpoint));
3043
- let phTolerance = typeof data.ph.tolerance !== 'undefined' ? data.ph.tolerance : chem.ph.tolerance;
3044
- let orpTolerance = typeof data.orp.tolerance !== 'undefined' ? data.orp.tolerance : chem.orp.tolerance;
3045
- if (typeof data.ph.tolerance !== 'undefined') {
3043
+ let phTolerance = (typeof data.ph !== 'undefined' && typeof data.ph.tolerance !== 'undefined') ? data.ph.tolerance : chem.ph.tolerance;
3044
+ let orpTolerance = (typeof data.orp !== 'undefined' && typeof data.orp.tolerance !== 'undefined') ? data.orp.tolerance : chem.orp.tolerance;
3045
+ if (typeof data.ph !== 'undefined' && typeof data.ph.tolerance !== 'undefined') {
3046
3046
  if (typeof data.ph.tolerance.enabled !== 'undefined') phTolerance.enabled = utils.makeBool(data.ph.tolerance.enabled);
3047
3047
  if (typeof data.ph.tolerance.low !== 'undefined') phTolerance.low = parseFloat(data.ph.tolerance.low);
3048
3048
  if (typeof data.ph.tolerance.high !== 'undefined') phTolerance.high = parseFloat(data.ph.tolerance.high);
3049
3049
  if (isNaN(phTolerance.low)) phTolerance.low = type.ph.min;
3050
3050
  if (isNaN(phTolerance.high)) phTolerance.high = type.ph.max;
3051
3051
  }
3052
- if (typeof data.orp.tolerance !== 'undefined') {
3052
+ if (typeof data.orp !== 'undefined' && typeof data.orp.tolerance !== 'undefined') {
3053
3053
  if (typeof data.orp.tolerance.enabled !== 'undefined') orpTolerance.enabled = utils.makeBool(data.orp.tolerance.enabled);
3054
3054
  if (typeof data.orp.tolerance.low !== 'undefined') orpTolerance.low = parseFloat(data.orp.tolerance.low);
3055
3055
  if (typeof data.orp.tolerance.high !== 'undefined') orpTolerance.high = parseFloat(data.orp.tolerance.high);
3056
3056
  if (isNaN(orpTolerance.low)) orpTolerance.low = type.orp.min;
3057
3057
  if (isNaN(orpTolerance.high)) orpTolerance.high = type.orp.max;
3058
3058
  }
3059
- let phEnabled = typeof data.ph.enabled !== 'undefined' ? utils.makeBool(data.ph.enabled) : chem.ph.enabled;
3060
- let orpEnabled = typeof data.orp.enabled !== 'undefined' ? utils.makeBool(data.orp.enabled) : chem.orp.enabled;
3059
+ let phEnabled = (typeof data.ph !== 'undefined' && typeof data.ph.enabled !== 'undefined') ? utils.makeBool(data.ph.enabled) : chem.ph.enabled;
3060
+ let orpEnabled = (typeof data.orp !== 'undefined' && typeof data.orp.enabled !== 'undefined') ? utils.makeBool(data.orp.enabled) : chem.orp.enabled;
3061
3061
  let siCalcType = typeof data.siCalcType !== 'undefined' ? sys.board.valueMaps.siCalcTypes.encode(data.siCalcType, 0) : chem.siCalcType;
3062
3062
 
3063
3063
  let saltLevel = (state.chlorinators.length > 0) ? state.chlorinators.getItemById(1).saltLevel || 1000 : 1000
3064
3064
  chem.ph.tank.capacity = 6;
3065
3065
  chem.orp.tank.capacity = 6;
3066
- let acidTankLevel = typeof data.ph !== 'undefined' && typeof data.ph.tank !== 'undefined' && typeof data.ph.tank.level !== 'undefined' ? parseInt(data.ph.tank.level, 10) : schem.ph.tank.level;
3067
- let orpTankLevel = typeof data.orp !== 'undefined' && typeof data.orp.tank !== 'undefined' && typeof data.orp.tank.level !== 'undefined' ? parseInt(data.orp.tank.level, 10) : schem.orp.tank.level;
3066
+ let acidTankLevel = (typeof data.ph !== 'undefined' && typeof data.ph.tank !== 'undefined' && typeof data.ph.tank.level !== 'undefined') ? parseInt(data.ph.tank.level, 10) : schem.ph.tank.level;
3067
+ let orpTankLevel = (typeof data.orp !== 'undefined' && typeof data.orp.tank !== 'undefined' && typeof data.orp.tank.level !== 'undefined') ? parseInt(data.orp.tank.level, 10) : schem.orp.tank.level;
3068
3068
  // OCP needs to set the IntelliChem as active so it knows that it exists
3069
3069
  if (sl.enabled && send) {
3070
3070
  if (!schem.isActive) {
@@ -2825,7 +2825,8 @@ class IntelliCenterBodyCommands extends BodyCommands {
2825
2825
  processing: boolean,
2826
2826
  bytes: number[],
2827
2827
  body1: { heatMode: number, heatSetpoint: number, coolSetpoint: number },
2828
- body2: { heatMode: number, heatSetpoint: number, coolSetpoint: number }
2828
+ body2: { heatMode: number, heatSetpoint: number, coolSetpoint: number },
2829
+ _processingStartTime?: number
2829
2830
  };
2830
2831
  private async queueBodyHeatSettings(bodyId?: number, byte?: number, data?: any): Promise<Boolean> {
2831
2832
  logger.debug(`queueBodyHeatSettings: ${JSON.stringify(this.bodyHeatSettings)}`); // remove this line if #848 is fixed
@@ -2837,9 +2838,18 @@ class IntelliCenterBodyCommands extends BodyCommands {
2837
2838
  bytes: [],
2838
2839
  body1: { heatMode: body1.heatMode || 1, heatSetpoint: body1.heatSetpoint || 78, coolSetpoint: body1.coolSetpoint || 100 },
2839
2840
  body2: { heatMode: body2.heatMode || 1, heatSetpoint: body2.heatSetpoint || 78, coolSetpoint: body2.coolSetpoint || 100 }
2840
- }
2841
+ };
2841
2842
  }
2842
2843
  let bhs = this.bodyHeatSettings;
2844
+
2845
+ // Reset processing state if it's been stuck for too long (more than 10 seconds)
2846
+ if (bhs.processing && bhs._processingStartTime && (Date.now() - bhs._processingStartTime > 10000)) {
2847
+ logger.warn(`Resetting stuck bodyHeatSettings processing state after timeout`);
2848
+ bhs.processing = false;
2849
+ bhs.bytes = [];
2850
+ delete bhs._processingStartTime;
2851
+ }
2852
+
2843
2853
  if (typeof data !== 'undefined' && typeof bodyId !== 'undefined' && bodyId > 0) {
2844
2854
  let body = bodyId === 2 ? bhs.body2 : bhs.body1;
2845
2855
  if (!bhs.bytes.includes(byte) && byte) bhs.bytes.push(byte);
@@ -2849,6 +2859,7 @@ class IntelliCenterBodyCommands extends BodyCommands {
2849
2859
  }
2850
2860
  if (!bhs.processing && bhs.bytes.length > 0) {
2851
2861
  bhs.processing = true;
2862
+ bhs._processingStartTime = Date.now();
2852
2863
  let byte2 = bhs.bytes.shift();
2853
2864
  let fnToByte = function (num) { return num < 0 ? Math.abs(num) | 0x80 : Math.abs(num) || 0; };
2854
2865
  let payload = [0, 0, byte2, 1,
@@ -2890,13 +2901,16 @@ class IntelliCenterBodyCommands extends BodyCommands {
2890
2901
  }
2891
2902
  state.emitEquipmentChanges();
2892
2903
  } catch (err) {
2904
+ logger.error(`Error in queueBodyHeatSettings: ${err.message}`);
2893
2905
  bhs.processing = false;
2894
2906
  bhs.bytes = [];
2907
+ delete bhs._processingStartTime;
2895
2908
  throw (err);
2896
2909
  }
2897
2910
  finally {
2898
2911
  bhs.processing = false;
2899
2912
  bhs.bytes = [];
2913
+ delete bhs._processingStartTime;
2900
2914
  }
2901
2915
  return true;
2902
2916
  }
@@ -2912,7 +2926,10 @@ class IntelliCenterBodyCommands extends BodyCommands {
2912
2926
  }
2913
2927
  }, 3000);
2914
2928
  }
2915
- else bhs.processing = false;
2929
+ else {
2930
+ bhs.processing = false;
2931
+ delete bhs._processingStartTime;
2932
+ }
2916
2933
  return true;
2917
2934
  }
2918
2935
  }
@@ -31,6 +31,8 @@ export class SunTouchBoard extends EasyTouchBoard {
31
31
  constructor(system: PoolSystem) {
32
32
  super(system); // graph chain to EasyTouchBoard constructor.
33
33
  this.valueMaps.expansionBoards = new byteValueMap([
34
+ // 33 added per #1108;
35
+ [33, { name: 'stshared', part: '520820', desc: 'Pool and Spa controller', bodies: 2, valves: 4, circuits: 5, single: false, shared: true, dual: false, features: 4, chlorinators: 1, chemControllers: 1 }],
34
36
  [41, { name: 'stshared', part: '520820', desc: 'Pool and Spa controller', bodies: 2, valves: 4, circuits: 5, single: false, shared: true, dual: false, features: 4, chlorinators: 1, chemControllers: 1 }],
35
37
  [40, { name: 'stsingle', part: '520819', desc: 'Pool or Spa controller', bodies: 2, valves: 4, circuits: 5, single: true, shared: true, dual: false, features: 4, chlorinators: 1, chemControllers: 1 }]
36
38
  ]);
@@ -582,7 +582,7 @@ export class byteValueMaps {
582
582
  public valveModes: byteValueMap = new byteValueMap([
583
583
  [0, { name: 'off', desc: 'Off' }],
584
584
  [1, { name: 'pool', desc: 'Pool' }],
585
- [2, { name: 'spa', dest: 'Spa' }],
585
+ [2, { name: 'spa', desc: 'Spa' }],
586
586
  [3, { name: 'spillway', desc: 'Spillway' }],
587
587
  [4, { name: 'spadrain', desc: 'Spa Drain' }]
588
588
  ]);
@@ -3656,8 +3656,12 @@ export class ScheduleCommands extends BoardCommands {
3656
3656
  ssched.display = sched.display = display;
3657
3657
  ssched.startTimeOffset = sched.startTimeOffset = startTimeOffset;
3658
3658
  ssched.endTimeOffset = sched.endTimeOffset = endTimeOffset;
3659
- if (typeof sched.startDate === 'undefined')
3659
+ // Nixie controller managing schedules (master = 1), physical OCP (master = 0)
3660
+ if (sys.controllerType === ControllerType.Nixie) {
3660
3661
  sched.master = 1;
3662
+ } else {
3663
+ sched.master = 0;
3664
+ }
3661
3665
  await ncp.schedules.setScheduleAsync(sched, data);
3662
3666
  // update end time in case sched is changed while circuit is on
3663
3667
  let cstate = state.circuits.getInterfaceById(sched.circuit);
@@ -198,7 +198,7 @@ export class Connection {
198
198
  let c = cfg[section];
199
199
  if (typeof c.type === 'undefined') {
200
200
  let type = 'local';
201
- if (c.mockPort) type = 'mock';
201
+ if (c.mock) type = 'mock';
202
202
  else if (c.netConnect) type = 'network';
203
203
  config.setSection(`controller.${section}`, c);
204
204
  console.log(section);
@@ -260,7 +260,7 @@ export class Connection {
260
260
  if (anslq25port >= 0) {
261
261
  let ports = this.rs485Ports;
262
262
  for (let i = 0; i < ports.length; i++) {
263
- // if (ports[i].mockPort) continue;
263
+ // if (ports[i].mock) continue;
264
264
  if (ports[i].portId === currPort.portId) continue;
265
265
  if (ports[i].portId === anslq25port) continue; // don't resend
266
266
  if (!ports[i].isOpen) continue;
@@ -380,7 +380,8 @@ export class Connection {
380
380
  let msgs = [];
381
381
  // conn.queueInboundToBroadcast(msg);
382
382
  conn.queueOutboundToBroadcast(msg);
383
- /* if (msgs.length > 0) {
383
+ /* if (msgs.le
384
+ ngth > 0) {
384
385
  msgs.push(msg);
385
386
  let promises: Promise<boolean>[] = [];
386
387
  for (let i = 0; i < msgs.length; i++) {
@@ -511,7 +512,7 @@ export class RS485Port {
511
512
  public reconnects: number = 0;
512
513
  public emitter: EventEmitter;
513
514
  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' };
515
+ public get type() { return typeof this._cfg.type !== 'undefined' ? this._cfg.type : this._cfg.netConnect ? 'netConnect' : this._cfg.mock ? 'mock' : 'local' };
515
516
  public isOpen: boolean = false;
516
517
  public closing: boolean = false;
517
518
  private _cfg: any;
@@ -682,7 +683,7 @@ export class RS485Port {
682
683
  // be open if a hardware interface is used and this method returns.
683
684
  sp.open((err) => {
684
685
  if (err) {
685
- this.resetConnTimer();
686
+ if (!this.mock) this.resetConnTimer();
686
687
  this.isOpen = false;
687
688
  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})`}`);
688
689
  resolve(false);
@@ -712,7 +713,7 @@ export class RS485Port {
712
713
  if (!this.mock && !this.isPaused) this.resetConnTimer();
713
714
  this.pushIn(data);
714
715
  });
715
- this.resetConnTimer();
716
+ if (!this.mock) this.resetConnTimer();
716
717
  this.emitPortStats();
717
718
  });
718
719
  sp.on('close', (err) => {
@@ -730,7 +731,7 @@ export class RS485Port {
730
731
  if (typeof this.writeTimer !== 'undefined') { clearTimeout(this.writeTimer); this.writeTimer = null; }
731
732
  this.isOpen = false;
732
733
  if (sp.isOpen) sp.close((err) => { }); // call this with the error callback so that it doesn't emit to the error again.
733
- this.resetConnTimer();
734
+ if (!this.mock) this.resetConnTimer();
734
735
  logger.error(`Serial Port ${this.portId}: An error occurred : ${this._cfg.rs485Port}: ${JSON.stringify(err)}`);
735
736
  this.emitPortStats();
736
737
 
@@ -823,7 +824,7 @@ export class RS485Port {
823
824
  protected resetConnTimer(...args) {
824
825
  //console.log(`resetting connection timer`);
825
826
  if (this.connTimer !== null) clearTimeout(this.connTimer);
826
- if (!this._cfg.mockPort && this._cfg.inactivityRetry > 0 && !this.closing) this.connTimer = setTimeout(async () => {
827
+ if (!this._cfg.mock && this._cfg.inactivityRetry > 0 && !this.closing) this.connTimer = setTimeout(async () => {
827
828
  try {
828
829
  if (this._cfg.netConnect)
829
830
  logger.warn(`Inactivity timeout for ${this.portId} serial port ${this._cfg.netHost}:${this._cfg.netPort}/${this._cfg.rs485Port} after ${this._cfg.inactivityRetry} seconds`);
@@ -873,7 +873,7 @@ class OutboundCommon extends Message {
873
873
  case Protocol.Heater:
874
874
  case Protocol.Hayward:
875
875
  this.chkHi = Math.floor(sum / 256);
876
- this.chkLo = (sum - (super.chkHi * 256));
876
+ this.chkLo = (sum - (this.chkHi * 256));
877
877
  break;
878
878
  case Protocol.AquaLink:
879
879
  case Protocol.Chlorinator:
@@ -385,7 +385,7 @@ export class NixieCircuit extends NixieEquipment {
385
385
  return new InterfaceServerResponse(200, 'Success');
386
386
  }
387
387
  if (this._sequencing) return new InterfaceServerResponse(200, 'Success');
388
- let res = await NixieEquipment.putDeviceService(this.circuit.connectionId, `/state/device/${this.circuit.deviceBinding}`, { isOn: val, latch: val ? 10000 : undefined });
388
+ let res = await NixieEquipment.putDeviceService(this.circuit.connectionId, `/state/device/${this.circuit.deviceBinding}`, { isOn: val, latch: val ? 10000 : undefined });
389
389
  if (res.status.code === 200) {
390
390
  // Set this up so we can process our egg timer.
391
391
  if (val && val !== cstate.isOn) {
package/logger/Logger.ts CHANGED
@@ -270,6 +270,10 @@ class Logger {
270
270
  // start new replay directory
271
271
 
272
272
  if (!fs.existsSync(this.captureForReplayPath)) fs.mkdirSync(this.captureForReplayBaseDir, { recursive: true });
273
+
274
+ // Create logs subdirectory for additional log files
275
+ let logsSubDir = path.join(this.captureForReplayBaseDir, 'logs');
276
+ if (!fs.existsSync(logsSubDir)) fs.mkdirSync(logsSubDir, { recursive: true });
273
277
  if (bResetLogs){
274
278
  if (fs.existsSync(path.join(process.cwd(), 'data/poolConfig.json'))) {
275
279
  fs.copyFileSync(path.join(process.cwd(), 'data/poolConfig.json'), path.join(process.cwd(),'data/', `poolConfig-${this.getLogTimestamp()}.json`));
@@ -360,29 +364,68 @@ class Logger {
360
364
  logger._logger.add(this.transports.file);
361
365
  this.transports.console.level = 'silly';
362
366
  }
363
- public async stopCaptureForReplayAsync():Promise<string> {
367
+ public async stopCaptureForReplayAsync(remLogs?: any[]):Promise<string> {
364
368
  return new Promise<string>(async (resolve, reject) => {
365
369
  try {
366
- fs.copyFileSync(path.join(process.cwd(), "/config.json"), path.join(this.captureForReplayBaseDir, `config.json`));
367
- fs.copyFileSync(path.join(process.cwd(), 'data/poolConfig.json'), path.join(this.captureForReplayBaseDir, 'poolConfig.json'));
368
- fs.copyFileSync(path.join(process.cwd(), 'data/poolState.json'), path.join(this.captureForReplayBaseDir, 'poolState.json'));
369
- fs.copyFileSync(logger.pktPath, path.join(this.captureForReplayBaseDir, `packetLog${this.getLogTimestamp()}`));
370
+ // Get REM server configurations from config
371
+ let configData = config.getSection();
372
+ let remServers = [];
373
+ if (configData.web && configData.web.interfaces) {
374
+ for (let interfaceName in configData.web.interfaces) {
375
+ let interfaceConfig = configData.web.interfaces[interfaceName];
376
+ if (interfaceConfig.type === 'rem' && interfaceConfig.enabled) {
377
+ remServers.push({
378
+ name: interfaceConfig.name || interfaceName,
379
+ uuid: interfaceConfig.uuid,
380
+ host: interfaceConfig.options?.host || '',
381
+ backup: true
382
+ });
383
+ }
384
+ }
385
+ }
386
+
387
+ // Use the existing backup logic to create the base backup
388
+ let backupOptions = {
389
+ njsPC: true,
390
+ servers: remServers,
391
+ name: `Packet Capture ${this.currentTimestamp}`,
392
+ automatic: false
393
+ };
394
+
395
+ let backupFile = await webApp.backupServer(backupOptions);
396
+
397
+ // Add packet capture logs to the existing backup zip
398
+ let jszip = require("jszip");
399
+ let zip = await jszip.loadAsync(fs.readFileSync(backupFile.filePath));
400
+
401
+ // Add packet capture logs to the njsPC/logs directory
402
+ zip.file(`njsPC/logs/${this.getPacketPath()}`, fs.readFileSync(logger.pktPath));
403
+ zip.file(`njsPC/logs/${this.getConsoleToFilePath()}`, fs.readFileSync(this.consoleToFilePath));
404
+
405
+ // Add REM server logs if provided
406
+ if (remLogs && remLogs.length > 0) {
407
+ logger.info(`Adding ${remLogs.length} REM logs to backup`);
408
+ for (let remLog of remLogs) {
409
+ // Create logs directory for the REM server using the hardcoded name
410
+ let logPath = `Relay Equipment Manager/logs/${remLog.logFileName}`;
411
+ logger.info(`Adding REM log to backup: ${logPath} (size: ${remLog.logData.length} characters)`);
412
+ zip.file(logPath, remLog.logData);
413
+ }
414
+ } else {
415
+ logger.info(`No REM logs provided to add to backup`);
416
+ }
417
+
418
+ // Generate the updated zip
419
+ await zip.generateAsync({type:'nodebuffer'}).then(content => {
420
+ fs.writeFileSync(backupFile.filePath, content);
421
+ });
422
+
423
+ // Restore original logging configuration
370
424
  this.cfg = config.getSection('log');
371
425
  logger._logger.remove(this.transports.file);
372
426
  this.transports.console.level = this.cfg.app.level;
373
- let jszip = require("jszip");
374
- let zipPath = path.join(this.captureForReplayBaseDir,`${this.currentTimestamp}.zip`);
375
- let zip = new jszip();
376
- zip.file('config.json', fs.readFileSync(path.join(this.captureForReplayBaseDir, 'config.json')));
377
- zip.file('poolConfig.json', fs.readFileSync(path.join(this.captureForReplayBaseDir, 'poolConfig.json')));
378
- zip.file('poolState.json', fs.readFileSync(path.join(this.captureForReplayBaseDir, 'poolState.json')));
379
- zip.file(this.getPacketPath(), fs.readFileSync(path.join(this.captureForReplayBaseDir, `packetLog${this.getLogTimestamp()}`)));
380
- zip.file(this.getConsoleToFilePath(), fs.readFileSync(this.consoleToFilePath));
381
- await zip.generateAsync({type:'nodebuffer'}).then(content=>
382
- {
383
- fs.writeFileSync(zipPath, content);
384
- });
385
- resolve(zipPath);
427
+
428
+ resolve(backupFile.filePath);
386
429
  }
387
430
  catch (err) {
388
431
  reject(err.message);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodejs-poolcontroller",
3
- "version": "8.1.0",
3
+ "version": "8.1.2",
4
4
  "description": "nodejs-poolController",
5
5
  "main": "app.js",
6
6
  "author": {
package/web/Server.ts CHANGED
@@ -1423,18 +1423,151 @@ export class REMInterfaceServer extends ProtoServer {
1423
1423
  try {
1424
1424
  let response = await this.sendClientRequest('GET', '/config/backup/controller', undefined, 10000);
1425
1425
  return response;
1426
- } catch (err) { logger.error(`Error requesting GET /config/backup/controller: ${err.message}`); }
1426
+ } catch (err) {
1427
+ logger.error(`Error requesting GET /config/backup/controller: ${err.message}`);
1428
+ let errorResponse = new InterfaceServerResponse();
1429
+ errorResponse.error = new Error(`Error requesting GET /config/backup/controller: ${err.message}`);
1430
+ return errorResponse;
1431
+ }
1427
1432
  }
1428
1433
  public async validateRestore(cfg): Promise<InterfaceServerResponse> {
1429
1434
  try {
1430
1435
  let response = await this.sendClientRequest('PUT', '/config/restore/validate', cfg, 10000);
1431
1436
  return response;
1432
- } catch (err) { logger.error(`Error requesting PUT /config/restore/validate ${err.message}`); }
1437
+ } catch (err) {
1438
+ logger.error(`Error requesting PUT /config/restore/validate ${err.message}`);
1439
+ let errorResponse = new InterfaceServerResponse();
1440
+ errorResponse.error = new Error(`Error requesting PUT /config/restore/validate: ${err.message}`);
1441
+ return errorResponse;
1442
+ }
1433
1443
  }
1434
1444
  public async restoreConfig(cfg): Promise<InterfaceServerResponse> {
1435
1445
  try {
1436
1446
  return await this.sendClientRequest('PUT', '/config/restore/file', cfg, 20000);
1437
- } catch (err) { logger.error(`Error requesting PUT /config/restore/file ${err.message}`); }
1447
+ } catch (err) {
1448
+ logger.error(`Error requesting PUT /config/restore/file ${err.message}`);
1449
+ let errorResponse = new InterfaceServerResponse();
1450
+ errorResponse.error = new Error(`Error requesting PUT /config/restore/file: ${err.message}`);
1451
+ return errorResponse;
1452
+ }
1453
+ }
1454
+ public async startPacketCapture(): Promise<InterfaceServerResponse> {
1455
+ try {
1456
+ let response = await this.sendClientRequest('PUT', '/config/packetCapture/start', undefined, 10000);
1457
+ return response;
1458
+ } catch (err) {
1459
+ logger.error(`Error requesting PUT /config/packetCapture/start: ${err.message}`);
1460
+ let errorResponse = new InterfaceServerResponse();
1461
+ errorResponse.error = new Error(`Error requesting PUT /config/packetCapture/start: ${err.message}`);
1462
+ return errorResponse;
1463
+ }
1464
+ }
1465
+ public async stopPacketCapture(): Promise<InterfaceServerResponse> {
1466
+ try {
1467
+ let response = await this.sendClientRequest('PUT', '/config/packetCapture/stop', undefined, 10000);
1468
+ return response;
1469
+ } catch (err) {
1470
+ logger.error(`Error requesting PUT /config/packetCapture/stop: ${err.message}`);
1471
+ let errorResponse = new InterfaceServerResponse();
1472
+ errorResponse.error = new Error(`Error requesting PUT /config/packetCapture/stop: ${err.message}`);
1473
+ return errorResponse;
1474
+ }
1475
+ }
1476
+ public async getPacketCaptureLog(): Promise<InterfaceServerResponse> {
1477
+ try {
1478
+ let response = await this.sendClientRequest('GET', '/config/packetCapture/log', undefined, 15000);
1479
+ return response;
1480
+ } catch (err) {
1481
+ logger.error(`Error requesting GET /config/packetCapture/log: ${err.message}`);
1482
+ let errorResponse = new InterfaceServerResponse();
1483
+ errorResponse.error = new Error(`Error requesting GET /config/packetCapture/log: ${err.message}`);
1484
+ return errorResponse;
1485
+ }
1486
+ }
1487
+
1488
+
1489
+ // Static methods to handle the REM server
1490
+ public static async startPacketCaptureOnRemServer(): Promise<void> {
1491
+ let remServers = webApp.findServersByType('rem') as REMInterfaceServer[];
1492
+ logger.info(`Found ${remServers ? remServers.length : 0} REM servers`);
1493
+
1494
+ if (remServers && remServers.length > 0) {
1495
+ let server = remServers[0]; // Get the single REM server
1496
+ logger.info(`Attempting to start packet capture on REM server: ${server.name} (connected: ${server.isConnected})`);
1497
+
1498
+ if (server.isConnected) {
1499
+ try {
1500
+ let response = await server.startPacketCapture();
1501
+ logger.info(`Start packet capture response: ${JSON.stringify(response)}`);
1502
+
1503
+ if (response && response.status.code === 200) {
1504
+ logger.info(`Started packet capture on REM server: ${server.name}`);
1505
+ } else {
1506
+ logger.warn(`Failed to start packet capture on REM server: ${server.name}. Status: ${response?.status?.code}, Error: ${response?.error?.message}`);
1507
+ }
1508
+ } catch (err) {
1509
+ logger.error(`Error starting packet capture on REM server ${server.name}: ${err.message}`);
1510
+ }
1511
+ } else {
1512
+ logger.warn(`REM server ${server.name} is not connected, cannot start packet capture`);
1513
+ }
1514
+ } else {
1515
+ logger.warn(`No REM servers found or configured`);
1516
+ }
1517
+ }
1518
+
1519
+ public static async stopPacketCaptureOnRemServer(): Promise<any[]> {
1520
+ let remServers = webApp.findServersByType('rem') as REMInterfaceServer[];
1521
+ let remLogs = [];
1522
+
1523
+ logger.info(`Found ${remServers ? remServers.length : 0} REM servers for stop packet capture`);
1524
+
1525
+ if (remServers && remServers.length > 0) {
1526
+ let server = remServers[0]; // Get the single REM server
1527
+ logger.info(`Attempting to stop packet capture on REM server: ${server.name} (connected: ${server.isConnected})`);
1528
+
1529
+ if (server.isConnected) {
1530
+ try {
1531
+ // Stop packet capture
1532
+ let stopResponse = await server.stopPacketCapture();
1533
+ logger.info(`Stop packet capture response: ${JSON.stringify(stopResponse)}`);
1534
+
1535
+ if (stopResponse && stopResponse.status.code === 200) {
1536
+ logger.info(`Stopped packet capture on REM server: ${server.name}`);
1537
+
1538
+ // Get the log file
1539
+ let logResponse = await server.getPacketCaptureLog();
1540
+ logger.info(`Get log response: ${JSON.stringify(logResponse)}`);
1541
+
1542
+ if (logResponse && logResponse.status.code === 200 && logResponse.data) {
1543
+ // Use the actual log file name from the REM response
1544
+ logger.info(`Log response obj: ${JSON.stringify(logResponse.obj)}`);
1545
+ let logFileName = logResponse.obj && logResponse.obj.logFile ? logResponse.obj.logFile : `rem_${server.name}_packetCapture.log`;
1546
+ logger.info(`Using log filename: ${logFileName}`);
1547
+ remLogs.push({
1548
+ serverName: server.name,
1549
+ logData: logResponse.data,
1550
+ logFileName: logFileName
1551
+ });
1552
+ logger.info(`Retrieved packet capture log from REM server: ${server.name}, log size: ${logResponse.data.length} characters, filename: ${logFileName}`);
1553
+ } else {
1554
+ logger.warn(`Failed to retrieve packet capture log from REM server: ${server.name}. Status: ${logResponse?.status?.code}, Error: ${logResponse?.error?.message}`);
1555
+ }
1556
+ } else {
1557
+ logger.warn(`Failed to stop packet capture on REM server: ${server.name}. Status: ${stopResponse?.status?.code}, Error: ${stopResponse?.error?.message}`);
1558
+ }
1559
+ } catch (err) {
1560
+ logger.error(`Error stopping packet capture on REM server ${server.name}: ${err.message}`);
1561
+ }
1562
+ } else {
1563
+ logger.warn(`REM server ${server.name} is not connected, cannot stop packet capture`);
1564
+ }
1565
+ } else {
1566
+ logger.warn(`No REM servers found or configured for stop packet capture`);
1567
+ }
1568
+
1569
+ logger.info(`Returning ${remLogs.length} REM logs`);
1570
+ return remLogs;
1438
1571
  }
1439
1572
  private async initConnection() {
1440
1573
  try {
@@ -1483,12 +1616,15 @@ export class REMInterfaceServer extends ProtoServer {
1483
1616
  resolve();
1484
1617
  }
1485
1618
  }
1486
- catch (err) { reject(new Error(`initConnection setTimeout: ${result.error.message}`)); }
1619
+ catch (err) {
1620
+ logger.error(`initConnection setTimeout error: ${err.message}`);
1621
+ reject(new Error(`initConnection setTimeout: ${err.message}`));
1622
+ }
1487
1623
  }, 3000);
1488
1624
  });
1489
1625
  }
1490
1626
  catch (err) {
1491
- logger.error(`Error with REM Interface Server initConnection: ${err}`)
1627
+ logger.error(`Error with REM Interface Server initConnection: ${err.message}`);
1492
1628
  }
1493
1629
  }
1494
1630
  public async stopAsync() {
@@ -1556,7 +1692,10 @@ export class REMInterfaceServer extends ProtoServer {
1556
1692
  });
1557
1693
  req.on('abort', () => { logger.warn('Request Aborted'); reject(new Error('Request Aborted.')); });
1558
1694
  req.end(sbody);
1559
- }).catch((err) => { logger.error(`Error Sending REM Request: ${opts.method} ${url} ${err.message}`); ret.error = err; });
1695
+ }).catch((err) => {
1696
+ logger.error(`Error Sending REM Request: ${opts.method} ${url} ${err.message}`);
1697
+ ret.error = err;
1698
+ });
1560
1699
  logger.verbose(`REM server request returned. ${opts.method} ${opts.path} ${sbody}`);
1561
1700
  if (ret.status.code > 200) {
1562
1701
  // We have an http error so let's parse it up.
@@ -1574,7 +1713,9 @@ export class REMInterfaceServer extends ProtoServer {
1574
1713
  }
1575
1714
  catch (err) {
1576
1715
  logger.error(`Error sending HTTP ${method} command to ${url}: ${err.message}`);
1577
- return Promise.reject(`Http ${method} Error ${url}:${err.message}`);
1716
+ let errorResponse = new InterfaceServerResponse();
1717
+ errorResponse.error = new Error(`Http ${method} Error ${url}:${err.message}`);
1718
+ return errorResponse;
1578
1719
  }
1579
1720
  }
1580
1721
  private initSockets() {
@@ -1585,7 +1726,10 @@ export class REMInterfaceServer extends ProtoServer {
1585
1726
  //console.log(this.cfg);
1586
1727
  this.sockClient = sockClient(url, extend(true,
1587
1728
  { reconnectionDelay: 2000, reconnection: true, reconnectionDelayMax: 20000, transports: ['websocket'], upgrade: true, }, this.cfg.socket));
1588
- if (typeof this.sockClient === 'undefined') return Promise.reject(new Error('Could not Initialize REM Server. Invalid configuration.'));
1729
+ if (typeof this.sockClient === 'undefined') {
1730
+ logger.error('Could not Initialize REM Server. Invalid configuration.');
1731
+ return;
1732
+ }
1589
1733
  //this.sockClient = io.connect(url, { reconnectionDelay: 2000, reconnection: true, reconnectionDelayMax: 20000 });
1590
1734
  //console.log(this.sockClient);
1591
1735
  //console.log(typeof this.sockClient.on);
@@ -1604,7 +1748,9 @@ export class REMInterfaceServer extends ProtoServer {
1604
1748
  });
1605
1749
  this.isRunning = true;
1606
1750
  }
1607
- catch (err) { logger.error(`Error Initializing Sockets: ${err.message}`); }
1751
+ catch (err) {
1752
+ logger.error(`Error Initializing Sockets: ${err.message}`);
1753
+ }
1608
1754
  }
1609
1755
  private isJSONString(s: string): boolean {
1610
1756
  if (typeof s !== 'string') return false;
@@ -1613,23 +1759,23 @@ export class REMInterfaceServer extends ProtoServer {
1613
1759
  }
1614
1760
  public async getApiService(url: string, data?: any, timeout: number = 3600): Promise<InterfaceServerResponse> {
1615
1761
  // Calls a rest service on the REM to set the state of a connected device.
1616
- try { let ret = await this.sendClientRequest('GET', url, data, timeout); return ret; }
1617
- catch (err) { return Promise.reject(err); }
1762
+ let ret = await this.sendClientRequest('GET', url, data, timeout);
1763
+ return ret;
1618
1764
  }
1619
1765
  public async putApiService(url: string, data?: any, timeout: number = 3600): Promise<InterfaceServerResponse> {
1620
1766
  // Calls a rest service on the REM to set the state of a connected device.
1621
- try { let ret = await this.sendClientRequest('PUT', url, data, timeout); return ret; }
1622
- catch (err) { return Promise.reject(err); }
1767
+ let ret = await this.sendClientRequest('PUT', url, data, timeout);
1768
+ return ret;
1623
1769
  }
1624
1770
  public async searchApiService(url: string, data?: any, timeout: number = 3600): Promise<InterfaceServerResponse> {
1625
1771
  // Calls a rest service on the REM to set the state of a connected device.
1626
- try { let ret = await this.sendClientRequest('SEARCH', url, data, timeout); return ret; }
1627
- catch (err) { return Promise.reject(err); }
1772
+ let ret = await this.sendClientRequest('SEARCH', url, data, timeout);
1773
+ return ret;
1628
1774
  }
1629
1775
  public async deleteApiService(url: string, data?: any, timeout: number = 3600): Promise<InterfaceServerResponse> {
1630
1776
  // Calls a rest service on the REM to set the state of a connected device.
1631
- try { let ret = await this.sendClientRequest('DELETE', url, data, timeout); return ret; }
1632
- catch (err) { return Promise.reject(err); }
1777
+ let ret = await this.sendClientRequest('DELETE', url, data, timeout);
1778
+ return ret;
1633
1779
  }
1634
1780
  public async getDevices() {
1635
1781
  try {
@@ -1640,7 +1786,10 @@ export class REMInterfaceServer extends ProtoServer {
1640
1786
  }
1641
1787
  return (response.status.code === 200) ? JSON.parse(response.data) : [];
1642
1788
  }
1643
- catch (err) { logger.error(`getDevices: ${err.message}`); }
1789
+ catch (err) {
1790
+ logger.error(`getDevices: ${err.message}`);
1791
+ return [];
1792
+ }
1644
1793
  }
1645
1794
  }
1646
1795
  export class BackupFile {
@@ -74,7 +74,7 @@ export class ConfigRoute {
74
74
  let cport = extend(true, { enabled: false, netConnect: false, mock: false }, cfg[section]);
75
75
  let port = conn.findPortById(cport.portId || 0);
76
76
  if (typeof cport.type === 'undefined'){
77
- cport.type = cport.netConnect ? 'netConnect' : cport.mockPort || cport.mock ? 'mock' : 'local'
77
+ cport.type = cport.netConnect ? 'netConnect' : cport.mock ? 'mock' : 'local'
78
78
  }
79
79
  if (typeof port !== 'undefined') cport.stats = port.stats;
80
80
  if (port.portId === 0 && port.type === 'screenlogic') {
@@ -1009,7 +1009,7 @@ export class ConfigRoute {
1009
1009
  return next(new ServiceProcessError(`File already exists ${req.file.originalname}`, 'POST: app/backup/file', 'writeFile'));
1010
1010
  else {
1011
1011
  try {
1012
- fs.writeFileSync(bf.filePath, req.file.buffer);
1012
+ fs.writeFileSync(bf.filePath, new Uint8Array(req.file.buffer));
1013
1013
  } catch (e) { logger.error(`Error writing backup file ${e.message}`); }
1014
1014
  }
1015
1015
  return res.status(200).send(bf);