nodejs-poolcontroller 8.1.2 → 8.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +36 -36
- package/.github/ISSUE_TEMPLATE/1-bug-report.yml +84 -84
- package/.github/ISSUE_TEMPLATE/2-docs.md +12 -12
- package/.github/ISSUE_TEMPLATE/3-proposal.md +28 -28
- package/.github/ISSUE_TEMPLATE/config.yml +8 -8
- package/.github/copilot-instructions.md +63 -0
- package/.github/workflows/ghcr-publish.yml +67 -0
- package/AGENTS.md +597 -0
- package/CONTRIBUTING.md +74 -74
- package/Changelog +292 -257
- package/Dockerfile +62 -19
- package/Gruntfile.js +40 -40
- package/LICENSE +661 -661
- package/README.md +318 -191
- package/anslq25/MessagesMock.ts +221 -221
- package/anslq25/boards/MockBoardFactory.ts +49 -49
- package/anslq25/boards/MockEasyTouchBoard.ts +696 -696
- package/anslq25/boards/MockSystemBoard.ts +216 -216
- package/anslq25/chemistry/MockChlorinator.ts +98 -98
- package/anslq25/pumps/MockPump.ts +83 -83
- package/app.ts +115 -115
- package/config/Config.ts +57 -7
- package/config/VersionCheck.ts +63 -35
- package/controller/Constants.ts +809 -805
- package/controller/Equipment.ts +2688 -2664
- package/controller/Errors.ts +181 -181
- package/controller/Lockouts.ts +549 -549
- package/controller/State.ts +3738 -3690
- package/controller/boards/AquaLinkBoard.ts +1003 -1003
- package/controller/boards/BoardFactory.ts +53 -53
- package/controller/boards/EasyTouchBoard.ts +3202 -3202
- package/controller/boards/IntelliCenterBoard.ts +4393 -3899
- package/controller/boards/IntelliComBoard.ts +69 -69
- package/controller/boards/IntelliTouchBoard.ts +382 -382
- package/controller/boards/NixieBoard.ts +1944 -1929
- package/controller/boards/SunTouchBoard.ts +400 -400
- package/controller/boards/SystemBoard.ts +5268 -5268
- package/controller/comms/Comms.ts +1272 -1214
- package/controller/comms/ScreenLogic.ts +1665 -1665
- package/controller/comms/messages/Messages.ts +1433 -1243
- package/controller/comms/messages/config/ChlorinatorMessage.ts +5 -0
- package/controller/comms/messages/config/CircuitGroupMessage.ts +0 -0
- package/controller/comms/messages/config/CircuitMessage.ts +0 -0
- package/controller/comms/messages/config/ConfigMessage.ts +6 -0
- package/controller/comms/messages/config/CoverMessage.ts +0 -0
- package/controller/comms/messages/config/CustomNameMessage.ts +31 -31
- package/controller/comms/messages/config/EquipmentMessage.ts +216 -210
- package/controller/comms/messages/config/ExternalMessage.ts +96 -10
- package/controller/comms/messages/config/FeatureMessage.ts +0 -0
- package/controller/comms/messages/config/GeneralMessage.ts +0 -0
- package/controller/comms/messages/config/HeaterMessage.ts +0 -0
- package/controller/comms/messages/config/IntellichemMessage.ts +0 -0
- package/controller/comms/messages/config/OptionsMessage.ts +194 -174
- package/controller/comms/messages/config/PumpMessage.ts +0 -0
- package/controller/comms/messages/config/RemoteMessage.ts +0 -0
- package/controller/comms/messages/config/ScheduleMessage.ts +401 -390
- package/controller/comms/messages/config/SecurityMessage.ts +0 -0
- package/controller/comms/messages/config/ValveMessage.ts +0 -0
- package/controller/comms/messages/status/ChlorinatorStateMessage.ts +0 -0
- package/controller/comms/messages/status/EquipmentStateMessage.ts +1158 -822
- package/controller/comms/messages/status/HeaterStateMessage.ts +135 -135
- package/controller/comms/messages/status/IntelliChemStateMessage.ts +448 -448
- package/controller/comms/messages/status/IntelliValveStateMessage.ts +36 -36
- package/controller/comms/messages/status/PumpStateMessage.ts +0 -0
- package/controller/comms/messages/status/RegalModbusStateMessage.ts +411 -0
- package/controller/comms/messages/status/VersionMessage.ts +103 -41
- package/controller/nixie/Nixie.ts +173 -173
- package/controller/nixie/NixieEquipment.ts +104 -104
- package/controller/nixie/bodies/Body.ts +120 -120
- package/controller/nixie/bodies/Filter.ts +135 -135
- package/controller/nixie/chemistry/ChemController.ts +2724 -2724
- package/controller/nixie/chemistry/ChemDoser.ts +806 -806
- package/controller/nixie/chemistry/Chlorinator.ts +367 -367
- package/controller/nixie/circuits/Circuit.ts +478 -478
- package/controller/nixie/heaters/Heater.ts +834 -834
- package/controller/nixie/pumps/Pump.ts +1194 -996
- package/controller/nixie/schedules/Schedule.ts +401 -401
- package/controller/nixie/valves/Valve.ts +170 -170
- package/defaultConfig.json +352 -347
- package/docker-compose.yml +32 -0
- package/logger/DataLogger.ts +448 -448
- package/logger/Logger.ts +448 -436
- package/package.json +58 -60
- package/sendSocket.js +32 -32
- package/tsconfig.json +25 -25
- package/types/express-multer.d.ts +32 -0
- package/web/Server.ts +1937 -1927
- package/web/bindings/aqualinkD.json +559 -559
- package/web/bindings/influxDB.json +1066 -1066
- package/web/bindings/mqtt.json +721 -721
- package/web/bindings/mqttAlt.json +746 -746
- package/web/bindings/rulesManager.json +54 -54
- package/web/bindings/smartThings-Hubitat.json +31 -31
- package/web/bindings/valveRelays.json +20 -20
- package/web/bindings/vera.json +25 -25
- package/web/interfaces/baseInterface.ts +188 -188
- package/web/interfaces/httpInterface.ts +148 -148
- package/web/interfaces/influxInterface.ts +283 -283
- package/web/interfaces/mqttInterface.ts +695 -695
- package/web/interfaces/ruleInterface.ts +101 -87
- package/web/services/config/Config.ts +1063 -1053
- package/web/services/config/ConfigSocket.ts +0 -0
- package/web/services/state/State.ts +0 -0
- package/web/services/state/StateSocket.ts +0 -0
- package/web/services/utilities/Utilities.ts +233 -233
- package/.github/workflows/docker-publish-njsPC-linux.yml +0 -50
|
@@ -1,1665 +1,1665 @@
|
|
|
1
|
-
import { ControllerType, Timestamp, Utils, utils } from '../../controller/Constants';
|
|
2
|
-
import { LightGroup, LightGroupCircuit, sys, Valve, Body, Pump, PumpCircuit, Remote } from '../../controller/Equipment';
|
|
3
|
-
import { CircuitState, state, ValveState } from '../../controller/State';
|
|
4
|
-
import { RemoteLogin, UnitConnection, FindUnits, SLEquipmentStateData, SLIntellichlorData, SLPumpStatusData, SLScheduleData, SLSystemTimeData, HeatModes, SLControllerConfigData, SLEquipmentConfigurationData, HeaterConfig, Valves, SLChemData, SLGetCustomNamesData } from 'node-screenlogic';
|
|
5
|
-
import * as Screenlogic from 'node-screenlogic';
|
|
6
|
-
import { EasyTouchBoard } from '../../controller/boards/EasyTouchBoard';
|
|
7
|
-
import { IntelliTouchBoard } from '../../controller/boards/IntelliTouchBoard';
|
|
8
|
-
import { logger } from '../../logger/Logger';
|
|
9
|
-
import { webApp } from '../../web/Server';
|
|
10
|
-
import { delayMgr } from '../../controller/Lockouts';
|
|
11
|
-
import { config } from '../../config/Config';
|
|
12
|
-
import { InvalidEquipmentDataError, InvalidEquipmentIdError, InvalidOperationError } from '../../controller/Errors';
|
|
13
|
-
import extend = require('extend');
|
|
14
|
-
import { Message } from './messages/Messages';
|
|
15
|
-
|
|
16
|
-
export class ScreenLogicComms {
|
|
17
|
-
constructor() {
|
|
18
|
-
this._client = new Screenlogic.UnitConnection();
|
|
19
|
-
};
|
|
20
|
-
public a: SLChemData;
|
|
21
|
-
public counter: SLCounter = new SLCounter();
|
|
22
|
-
private _gateway: RemoteLogin;
|
|
23
|
-
private _client: UnitConnection;
|
|
24
|
-
private _pollTimer: NodeJS.Timeout;
|
|
25
|
-
public circuits: SLCircuits;
|
|
26
|
-
public bodies: SLBodies;
|
|
27
|
-
public chlor: SLChlor;
|
|
28
|
-
public schedules: SLSchedule;
|
|
29
|
-
public pumps: SLPump;
|
|
30
|
-
public controller: SLController;
|
|
31
|
-
private _pollCountError: number = 0;
|
|
32
|
-
public isOpen: boolean = false;
|
|
33
|
-
private _cfg: any;
|
|
34
|
-
private _configData: { pumpsReported: number[], intellichemPresent: boolean };
|
|
35
|
-
private pollingInterval = 10000;
|
|
36
|
-
public enabled: boolean = false;
|
|
37
|
-
|
|
38
|
-
public eqConfig: any; // testing purposes
|
|
39
|
-
|
|
40
|
-
public async openAsync() {
|
|
41
|
-
let self = this;
|
|
42
|
-
this.circuits = new SLCircuits(this._client);
|
|
43
|
-
this.bodies = new SLBodies(this._client);
|
|
44
|
-
this.chlor = new SLChlor(this._client);
|
|
45
|
-
this.schedules = new SLSchedule(this._client);
|
|
46
|
-
this.pumps = new SLPump(this._client);
|
|
47
|
-
this.controller = new SLController(this._client);
|
|
48
|
-
let cfg = config.getSection('controller.comms');
|
|
49
|
-
if (typeof cfg !== 'undefined') this._cfg = cfg;
|
|
50
|
-
this.enabled = this._cfg.enabled && this._cfg.type === 'screenlogic';
|
|
51
|
-
if (!this._cfg.enabled || this._cfg.type !== 'screenlogic') {
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
let systemName = this._cfg.screenlogic.systemName; // 'Pentair: 00-00-00';
|
|
55
|
-
let password = this._cfg.screenlogic.password.toString(); // '1111';
|
|
56
|
-
|
|
57
|
-
this._gateway = new RemoteLogin(systemName);
|
|
58
|
-
this._gateway.on('error', async (err) => {
|
|
59
|
-
logger.error(`Screenlogic Gateway Error: ${err.message}`);
|
|
60
|
-
this.isOpen = false;
|
|
61
|
-
await this._gateway.closeAsync();
|
|
62
|
-
return Promise.resolve(false);
|
|
63
|
-
})
|
|
64
|
-
let unit = await this._gateway.connectAsync();
|
|
65
|
-
|
|
66
|
-
if (!unit || !unit.gatewayFound || unit.ipAddr === '') {
|
|
67
|
-
logger.error(`Screenlogic: No unit found called ${systemName}`);
|
|
68
|
-
this.isOpen = false;
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
await this._gateway.closeAsync();
|
|
72
|
-
this.isOpen = true;
|
|
73
|
-
logger.info(`Screenlogic: Unit ${this._gateway.systemName} found at ${unit.ipAddr}:${unit.port}`);
|
|
74
|
-
|
|
75
|
-
let delayCount = 0;
|
|
76
|
-
state.status = sys.board.valueMaps.controllerStatus.transform(0, 0);
|
|
77
|
-
state.emitControllerChange();
|
|
78
|
-
|
|
79
|
-
try {
|
|
80
|
-
try {
|
|
81
|
-
this._client.init(systemName, unit.ipAddr, unit.port, password);
|
|
82
|
-
await this._client.connectAsync();
|
|
83
|
-
this._client.removeAllListeners(); // clear out in case we are initializing again
|
|
84
|
-
this._client.on('slLogMessage', (msg) => {
|
|
85
|
-
let _id = Message.nextMessageId;
|
|
86
|
-
msg = { ...msg, _id };
|
|
87
|
-
logger.screenlogic(msg);
|
|
88
|
-
})
|
|
89
|
-
let ver = await this._client.getVersionAsync();
|
|
90
|
-
logger.info(`Screenlogic: connect to ${systemName} ${ver.version} at ${unit.ipAddr}:${unit.port}`);
|
|
91
|
-
|
|
92
|
-
let addClient = await this._client.addClientAsync();
|
|
93
|
-
logger.silly(`Screenlogic:Add client result: ${addClient}`);
|
|
94
|
-
} catch (err) {
|
|
95
|
-
throw err;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
state.status = sys.board.valueMaps.controllerStatus.transform(2, 12);
|
|
99
|
-
state.emitControllerChange();
|
|
100
|
-
try {
|
|
101
|
-
let equipConfig = await this._client.equipment.getEquipmentConfigurationAsync();
|
|
102
|
-
logger.silly(`Screenlogic: Equipment config: ${JSON.stringify(equipConfig, null, 2)}`);
|
|
103
|
-
await Controller.decodeEquipmentAsync(equipConfig);
|
|
104
|
-
} catch (err) {
|
|
105
|
-
logger.error(`Screenlogic: Error getting equipment configuration. ${err.message}`);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
state.status = sys.board.valueMaps.controllerStatus.transform(2, 24);
|
|
109
|
-
state.emitControllerChange();
|
|
110
|
-
try {
|
|
111
|
-
|
|
112
|
-
let customNames = await this._client.equipment.getCustomNamesAsync();
|
|
113
|
-
logger.silly(`Screenlogic: custom names ${customNames}`);
|
|
114
|
-
await Controller.decodeCustomNames(customNames);
|
|
115
|
-
} catch (err) {
|
|
116
|
-
logger.error(`Screenlogic: Error getting custom names. ${err.message}`);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
state.status = sys.board.valueMaps.controllerStatus.transform(2, 36);
|
|
120
|
-
state.emitControllerChange();
|
|
121
|
-
try {
|
|
122
|
-
let controller = await this._client.equipment.getControllerConfigAsync();
|
|
123
|
-
logger.silly(`Screenlogic:Controller: ${JSON.stringify(controller, null, 2)}`);
|
|
124
|
-
this._configData = await Controller.decodeController(controller);
|
|
125
|
-
} catch (err) {
|
|
126
|
-
logger.error(`Screenlogic: Error getting controller configuration. ${err.message}`);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
state.status = sys.board.valueMaps.controllerStatus.transform(2, 48);
|
|
130
|
-
state.emitControllerChange();
|
|
131
|
-
try {
|
|
132
|
-
let systemTime = await this._client.equipment.getSystemTimeAsync();
|
|
133
|
-
// logger.silly(`Screenlogic:System Time: ${JSON.stringify(systemTime)}`)
|
|
134
|
-
Controller.decodeDateTime(systemTime);
|
|
135
|
-
} catch (err) {
|
|
136
|
-
logger.error(`Screenlogic: Error getting system time. ${err.message}`);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// PUMPS
|
|
140
|
-
state.status = sys.board.valueMaps.controllerStatus.transform(2, 60);
|
|
141
|
-
state.emitControllerChange();
|
|
142
|
-
this._configData.pumpsReported.forEach(async pumpNum => {
|
|
143
|
-
try {
|
|
144
|
-
let pumpStatus = await this._client.pump.getPumpStatusAsync(pumpNum);
|
|
145
|
-
logger.silly(`Screenlogic:Pump ${pumpNum}: ${JSON.stringify(pumpStatus)}`);
|
|
146
|
-
await Controller.decodePumpStatusAsync(pumpNum, pumpStatus);
|
|
147
|
-
} catch (err) {
|
|
148
|
-
logger.error(`Screenlogic: Error getting pump configuration. ${err.message}`);
|
|
149
|
-
}
|
|
150
|
-
})
|
|
151
|
-
|
|
152
|
-
state.status = sys.board.valueMaps.controllerStatus.transform(2, 72);
|
|
153
|
-
state.emitControllerChange();
|
|
154
|
-
try {
|
|
155
|
-
let recurringSched = await this._client.schedule.getScheduleDataAsync(0);
|
|
156
|
-
logger.silly(`Screenlogic:reccuring schedules: ${JSON.stringify(recurringSched)}`);
|
|
157
|
-
let runOnceSched = await this._client.schedule.getScheduleDataAsync(1);
|
|
158
|
-
logger.silly(`Screenlogic:Run once schedules: ${JSON.stringify(runOnceSched)}`);
|
|
159
|
-
await Controller.decodeSchedules(recurringSched, runOnceSched);
|
|
160
|
-
} catch (err) {
|
|
161
|
-
logger.error(`Screenlogic: Error getting schedules. ${err.message}`);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
state.status = sys.board.valueMaps.controllerStatus.transform(2, 84);
|
|
165
|
-
state.emitControllerChange();
|
|
166
|
-
try {
|
|
167
|
-
let intellichlor = await this._client.chlor.getIntellichlorConfigAsync();
|
|
168
|
-
// logger.silly(`Screenlogic:Intellichlor: ${JSON.stringify(intellichlor)}`);
|
|
169
|
-
await Controller.decodeIntellichlorAsync(intellichlor);
|
|
170
|
-
} catch (err) {
|
|
171
|
-
logger.error(`Screenlogic: Error getting Intellichlor. ${err.message}`);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
state.status = sys.board.valueMaps.controllerStatus.transform(2, 95);
|
|
175
|
-
state.emitControllerChange();
|
|
176
|
-
try {
|
|
177
|
-
if (this._configData.intellichemPresent) {
|
|
178
|
-
let chem = await this._client.chem.getChemicalDataAsync();
|
|
179
|
-
logger.silly(`Screenlogic:Chem data: ${JSON.stringify(chem)}`);
|
|
180
|
-
await Controller.decodeChemController(chem);
|
|
181
|
-
}
|
|
182
|
-
} catch (err) {
|
|
183
|
-
logger.error(`Screenlogic: Error getting Intellichem. ${err.message}`);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
state.status = sys.board.valueMaps.controllerStatus.transform(2, 98);
|
|
187
|
-
state.emitControllerChange();
|
|
188
|
-
try {
|
|
189
|
-
let equipmentState = await this._client.equipment.getEquipmentStateAsync();
|
|
190
|
-
logger.silly(`Screenlogic: equipment state: ${JSON.stringify(equipmentState)}`);
|
|
191
|
-
await Controller.decodeEquipmentState(equipmentState);
|
|
192
|
-
} catch (err) {
|
|
193
|
-
logger.error(`Screenlogic: Error getting equipment state. ${err.message}`);
|
|
194
|
-
}
|
|
195
|
-
sys.board.circuits.syncVirtualCircuitStates()
|
|
196
|
-
state.status = sys.board.valueMaps.controllerStatus.transform(1, 100);
|
|
197
|
-
state.emitControllerChange();
|
|
198
|
-
|
|
199
|
-
this._client.on('equipmentState', async function (data) { await Controller.decodeEquipmentState(data); })
|
|
200
|
-
this._client.on('intellichlorConfig', async function (data) {
|
|
201
|
-
await Controller.decodeIntellichlorAsync(data);
|
|
202
|
-
});
|
|
203
|
-
this._client.on('equipmentConfig', async function (data) {
|
|
204
|
-
await Controller.decodeController(data);
|
|
205
|
-
});
|
|
206
|
-
this._client.on('chemicalData', async function (data) {
|
|
207
|
-
await Controller.decodeChemController(data);
|
|
208
|
-
|
|
209
|
-
});
|
|
210
|
-
this._client.on('getSystemTime', async function (data) {
|
|
211
|
-
Controller.decodeDateTime(data);
|
|
212
|
-
});
|
|
213
|
-
// client.on('getScheduleData', async function(){
|
|
214
|
-
// await Controller.decodeSchedules(recurringSched, runOnceSched);}); // how do we know if this is recurring or runonce? Investigate.
|
|
215
|
-
this._client.on('cancelDelay', async function (data) {
|
|
216
|
-
logger.silly(`Screenlogic:cancelDelay: ${data}`)
|
|
217
|
-
}) // not programmed yet});
|
|
218
|
-
this._client.on('equipmentConfiguration', async function (data) {
|
|
219
|
-
logger.silly(`Screenlogic:equipConfig ${JSON.stringify(data)}`)
|
|
220
|
-
})// which one?});
|
|
221
|
-
this._client.on('getPumpStatus', async function (data) {
|
|
222
|
-
logger.silly(`Screenlogic:getPumpStatus: ${JSON.stringify(data)}`);
|
|
223
|
-
// await Controller.decodePump(1, pumpStatus);
|
|
224
|
-
}); // how do we know which pump id? Investigate.
|
|
225
|
-
this._client.on('weatherForecast', async function (data) {
|
|
226
|
-
logger.silly(`Screenlogic:weatherforecast: ${JSON.stringify(data)}`)
|
|
227
|
-
});
|
|
228
|
-
this._client.on('circuitStateChanged', async function (data) {
|
|
229
|
-
logger.silly(`Screenlogic:circuitstatechanged: ${JSON.stringify(data)}`)
|
|
230
|
-
});
|
|
231
|
-
this._client.on('setPointChanged', async function (data) {
|
|
232
|
-
logger.silly(`Screenlogic:setpointchanged: ${JSON.stringify(data)}`)
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
// not working
|
|
236
|
-
|
|
237
|
-
this._client.on('heatModeChanged', async function (data) {
|
|
238
|
-
logger.silly(`Screenlogic:heat mode changed: ${JSON.stringify(data)}`);
|
|
239
|
-
});
|
|
240
|
-
this._client.on('intellibriteDelay', async function (data) {
|
|
241
|
-
logger.silly(`Screenlogic:intellibrite delay: ${JSON.stringify(data)}`)
|
|
242
|
-
});
|
|
243
|
-
this._client.on('weatherForecastChanged', async function () {
|
|
244
|
-
logger.silly(`Screenlogic:weather forecast changed}`);
|
|
245
|
-
// found - no data returned; need to request data
|
|
246
|
-
});
|
|
247
|
-
// No data comes through... maybe need to request weather data again?
|
|
248
|
-
this._client.on('scheduleChanged', async function (data) {
|
|
249
|
-
logger.silly(`Screenlogic:schedule changed: ${JSON.stringify(data)}`);
|
|
250
|
-
let recurringSched = await self._client.schedule.getScheduleDataAsync(0);
|
|
251
|
-
logger.silly(`Screenlogic:reccuring schedules: ${JSON.stringify(recurringSched)}`);
|
|
252
|
-
|
|
253
|
-
let runOnceSched = await self._client.schedule.getScheduleDataAsync(1);
|
|
254
|
-
logger.silly(`Screenlogic:Run once schedules: ${JSON.stringify(runOnceSched)}`);
|
|
255
|
-
await Controller.decodeSchedules(recurringSched, runOnceSched);
|
|
256
|
-
});
|
|
257
|
-
this._client.on('setCircuitRuntimebyId', async (data) => {
|
|
258
|
-
logger.silly(`Screenlogic:Set Circuit By Runtime event ${data}`);
|
|
259
|
-
await self._client.equipment.getControllerConfigAsync();
|
|
260
|
-
});
|
|
261
|
-
// this._client.on('error', async (e) => {
|
|
262
|
-
// // if the error event from the net.socket isn't caught, it sometimes crashes the app.
|
|
263
|
-
// logger.error(`Screenlogic error (net.socket): ${e.message}`);
|
|
264
|
-
// if (e.code === 'ECONNRESET') {
|
|
265
|
-
// try {
|
|
266
|
-
// logger.info(`Screenlogic net.socket timeout. Restarting.`)
|
|
267
|
-
// await self.stopAsync();
|
|
268
|
-
// await self.initAsync();
|
|
269
|
-
// }
|
|
270
|
-
// catch (err) {
|
|
271
|
-
// logger.error(`Error trying to reset Screenlogic comms. ${err.message}`);
|
|
272
|
-
// };
|
|
273
|
-
// }
|
|
274
|
-
// })
|
|
275
|
-
// this._client.on('clientError', (e) => {
|
|
276
|
-
// // if the error event from the net.socket isn't caught, it sometimes crashes the app.
|
|
277
|
-
// logger.error(`Screenlogic client error (net.socket): ${e.message}`);
|
|
278
|
-
// })
|
|
279
|
-
this._client.on('loginFailed', (data) => {
|
|
280
|
-
logger.error(`Screenlogic login failed. Invalid password.`);
|
|
281
|
-
this.isOpen = false;
|
|
282
|
-
})
|
|
283
|
-
this._client.on('bytesRead', (bytes) => {
|
|
284
|
-
logger.silly(`Screenlogic:SL Bytes Read: ${bytes}`);
|
|
285
|
-
this.counter.bytesReceived += bytes;
|
|
286
|
-
this.emitScreenlogicStats();
|
|
287
|
-
});
|
|
288
|
-
this._client.on('bytesWritten', (bytes) => {
|
|
289
|
-
logger.silly(`Screenlogic:SL Bytes written: ${bytes}`);
|
|
290
|
-
this.counter.bytesSent += bytes;
|
|
291
|
-
this.emitScreenlogicStats();
|
|
292
|
-
});
|
|
293
|
-
this.pollAsync();
|
|
294
|
-
// logger.silly(`Screenlogic:Equipment State: ${JSON.stringify(equipmentState, null, 2)}`);
|
|
295
|
-
/* // EQUIPMENT
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
let weatherForecast = await client.equipment.getWeatherForecast();
|
|
300
|
-
logger.silly(`Screenlogic:Weather: ${JSON.stringify(weatherForecast)}`);
|
|
301
|
-
|
|
302
|
-
let hist = await screenlogic.equipment.getHistoryData()
|
|
303
|
-
logger.silly(`Screenlogic:history data: ${JSON.stringify(hist)}`)
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
// CHEM
|
|
307
|
-
let chemHist = await screenlogic.chem.getChemHistoryData()
|
|
308
|
-
logger.silly(`Screenlogic:history data: ${JSON.stringify(chemHist)}`)
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
*/
|
|
313
|
-
// setTimeout(async () => {
|
|
314
|
-
// logger.silly(`Screenlogic:closing connection after 60s`);
|
|
315
|
-
// await client.closeAsync();
|
|
316
|
-
// }, 120 * 1000)
|
|
317
|
-
// let close = await client.closeAsync();
|
|
318
|
-
// logger.silly(`Screenlogic:client closed: ${close}`);
|
|
319
|
-
} catch (error) {
|
|
320
|
-
logger.error(`Screenlogic error: ${error.message}`);
|
|
321
|
-
await this._client.closeAsync();
|
|
322
|
-
return Promise.resolve(error);
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
public async closeAsync() {
|
|
326
|
-
await this._client.closeAsync();
|
|
327
|
-
this._client.removeAllListeners();
|
|
328
|
-
this.isOpen = false;
|
|
329
|
-
this.enabled = false;
|
|
330
|
-
if (typeof this._pollTimer !== 'undefined') clearTimeout(this._pollTimer);
|
|
331
|
-
this._pollTimer = null;
|
|
332
|
-
}
|
|
333
|
-
/* public async setScreenlogicAsync(data) {
|
|
334
|
-
let enabled = typeof data.enabled !== 'undefined' ? utils.makeBool(data.enabled) : false;
|
|
335
|
-
let systemName = typeof data.systemName !== 'undefined' ? data.systemName : this._cfg.systemName;
|
|
336
|
-
let password = typeof data.password !== 'undefined' ? data.password.toString() : this._cfg.password;
|
|
337
|
-
let regx = /Pentair: (?:(?:\d|[A-Z])(?:\d|[A-Z])-){2}(?:\d|[A-Z])(?:\d|[A-Z])/g;
|
|
338
|
-
let type = typeof data.connectionType !== 'undefined' ? data.connectionType : this._cfg.connectionType;
|
|
339
|
-
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));
|
|
340
|
-
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));
|
|
341
|
-
if (password.length > 4) return Promise.reject(new InvalidEquipmentDataError(`An invalid password was supplied for Screenlogic ${password}. (Length must be <= 4)}`, 'Screenlogic', data));
|
|
342
|
-
this.enabled = enabled;
|
|
343
|
-
if (this._cfg.enabled && !enabled || this._cfg.systemName !== systemName || this._cfg.password !== password || this._cfg.cype !== type) {
|
|
344
|
-
await this.closeAsync();
|
|
345
|
-
}
|
|
346
|
-
let obj = {
|
|
347
|
-
enabled,
|
|
348
|
-
type,
|
|
349
|
-
systemName,
|
|
350
|
-
password
|
|
351
|
-
}
|
|
352
|
-
config.setSection('controller.screenlogic', obj);
|
|
353
|
-
this._cfg = config.getSection('controller.screenlogic');
|
|
354
|
-
if (this._cfg.enabled) {
|
|
355
|
-
let error = await this.openAsync();
|
|
356
|
-
if (typeof error !== 'undefined') return Promise.reject(error);
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
} */
|
|
360
|
-
public async pollAsync() {
|
|
361
|
-
let self = this;
|
|
362
|
-
try {
|
|
363
|
-
if (typeof this._pollTimer !== 'undefined' || this._pollTimer) clearTimeout(this._pollTimer);
|
|
364
|
-
this._pollTimer = null;
|
|
365
|
-
if (!this.isOpen) { return; };
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
/*
|
|
369
|
-
// Uncomment this block to do a comparison of the 'getConfig' packets.
|
|
370
|
-
// RSG used this to recreate the setConfig packet arrays
|
|
371
|
-
let equipConfig = await this._client.equipment.getEquipmentConfigurationAsync();
|
|
372
|
-
logger.silly(`Screenlogic: Equipment config: ${JSON.stringify(equipConfig, null, 2)}`);
|
|
373
|
-
|
|
374
|
-
if (typeof this.eqConfig === 'undefined') this.eqConfig = equipConfig;
|
|
375
|
-
// let's compare so we can find differences easily
|
|
376
|
-
for (const [key, value] of Object.entries(this.eqConfig.rawData)) {
|
|
377
|
-
console.log(key);
|
|
378
|
-
for (let i = 0; i < this.eqConfig.rawData[key].length; i++) {
|
|
379
|
-
if (this.eqConfig.rawData[key][i] !== equipConfig.rawData[key][i]) {
|
|
380
|
-
console.log(`Difference at ${key}[${i}]. prev: ${this.eqConfig.rawData[key][i]} (${utils.dec2bin(this.eqConfig.rawData[key][i])})-> new: ${equipConfig.rawData[key][i]} (${utils.dec2bin(equipConfig.rawData[key][i])})`)
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
this.eqConfig = equipConfig;
|
|
386
|
-
*/
|
|
387
|
-
|
|
388
|
-
let pumps = sys.pumps.get();
|
|
389
|
-
let numPumps = pumps.length;
|
|
390
|
-
for (let i = 1; i < numPumps + 1; i++) {
|
|
391
|
-
if (pumps[i - 1].id === 10) continue; // skip dual speed
|
|
392
|
-
let pumpStatus = await self._client.pump.getPumpStatusAsync(i);
|
|
393
|
-
logger.silly(`Screenlogic:Pump ${i}: ${JSON.stringify(pumpStatus)}`);
|
|
394
|
-
await Controller.decodePumpStatusAsync(i, pumpStatus);
|
|
395
|
-
}
|
|
396
|
-
sys.board.heaters.syncHeaterStates();
|
|
397
|
-
sys.board.schedules.syncScheduleStates();
|
|
398
|
-
sys.board.circuits.syncVirtualCircuitStates();
|
|
399
|
-
}
|
|
400
|
-
catch (err) {
|
|
401
|
-
logger.error(`Error polling screenlogic (${this._pollCountError} errors)- ${err}`); this._pollCountError++;
|
|
402
|
-
/* if (this._pollCountError > 3) {
|
|
403
|
-
await this.initAsync();
|
|
404
|
-
} */
|
|
405
|
-
}
|
|
406
|
-
finally { this._pollTimer = setTimeout(async () => await self.pollAsync(), this.pollingInterval || 10000); }
|
|
407
|
-
}
|
|
408
|
-
public static async searchAsync() {
|
|
409
|
-
try {
|
|
410
|
-
let finder = new FindUnits();
|
|
411
|
-
let localUnits = await finder.searchAsync();
|
|
412
|
-
finder.close();
|
|
413
|
-
return Promise.resolve(localUnits);
|
|
414
|
-
}
|
|
415
|
-
catch (err) {
|
|
416
|
-
logger.error(`Screenlogic: Error searching for units: ${err.message}`);
|
|
417
|
-
return Promise.reject(err);
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
public get stats() {
|
|
421
|
-
let status = this.isOpen ? 'open' : this._cfg.enabled ? 'closed' : 'disabled';
|
|
422
|
-
let socketStatus = this._client.status();
|
|
423
|
-
return extend(true, { status: status }, this.counter, socketStatus);
|
|
424
|
-
}
|
|
425
|
-
public emitScreenlogicStats() {
|
|
426
|
-
webApp.emitToChannel('screenlogicStats', 'screenlogicStats', this.stats);
|
|
427
|
-
}
|
|
428
|
-
public toLog(msg): string {
|
|
429
|
-
return `{"systemName":"${msg.systemName}","dir":"${msg.dir}","protocol":"${msg.protocol}", "_id": ${msg._id}, "action": ${msg.action}, "payload":[${JSON.stringify(msg.payload)}],"ts":"${Timestamp.toISOLocal(new Date())}"}`;
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
class Controller {
|
|
434
|
-
public static async decodeController(config: SLControllerConfigData) {
|
|
435
|
-
sys.general.options.units = state.temps.units = config.degC ? sys.board.valueMaps.tempUnits.getValue('C') : sys.board.valueMaps.tempUnits.getValue('F');
|
|
436
|
-
let lightGroup: any = { circuits: [] };
|
|
437
|
-
let lgCircId = 1;
|
|
438
|
-
for (let i = 0; i < config.circuitArray.length; i++) {
|
|
439
|
-
let _circ = config.circuitArray[i];
|
|
440
|
-
let circuit = sys.circuits.getInterfaceById(_circ.circuitId);
|
|
441
|
-
let data: any = {
|
|
442
|
-
id: _circ.circuitId,
|
|
443
|
-
type: _circ.function,
|
|
444
|
-
nameId: _circ.nameIndex,
|
|
445
|
-
freeze: _circ.freeze,
|
|
446
|
-
eggTimer: _circ.eggTimer,
|
|
447
|
-
// 0 = pool; 1 = spa; 2 = features; 4 = lights; 5 = hide
|
|
448
|
-
showInFeatures: typeof circuit.showInFeatures !== 'undefined' ? circuit.showInFeatures : _circ.function === 16 ? true : _circ.interface !== 4 && _circ.interface !== 5,
|
|
449
|
-
|
|
450
|
-
}
|
|
451
|
-
// errr.... something is wrong. Why do I have circuit function = 5 here?
|
|
452
|
-
// why does it look like function/interface are reversed??
|
|
453
|
-
/*
|
|
454
|
-
{
|
|
455
|
-
"circuitId": 4,
|
|
456
|
-
"name": "Pool Light",
|
|
457
|
-
"nameIndex": 63,
|
|
458
|
-
"function": 2,
|
|
459
|
-
"interface": 16,
|
|
460
|
-
"freeze": 0,
|
|
461
|
-
"colorSet": 2,
|
|
462
|
-
"colorPos": 0,
|
|
463
|
-
"colorStagger": 20,
|
|
464
|
-
"deviceId": 4,
|
|
465
|
-
"eggTimer": 720
|
|
466
|
-
},
|
|
467
|
-
*/
|
|
468
|
-
|
|
469
|
-
/*
|
|
470
|
-
SL Circuits
|
|
471
|
-
POOLCIRCUIT_CLEANER = 5;
|
|
472
|
-
POOLCIRCUIT_CLEANER_SECOND = 6;
|
|
473
|
-
POOLCIRCUIT_COLOR_WHEEL = 12;
|
|
474
|
-
POOLCIRCUIT_DIMMER = 8;
|
|
475
|
-
POOLCIRCUIT_DIMMER_25 = 18;
|
|
476
|
-
POOLCIRCUIT_FLOORCLEANER = 15;
|
|
477
|
-
POOLCIRCUIT_GENERIC = 0;
|
|
478
|
-
POOLCIRCUIT_INTELLIBRITE = 16;
|
|
479
|
-
POOLCIRCUIT_LAST_ID = 19;
|
|
480
|
-
POOLCIRCUIT_LIGHT = 7;
|
|
481
|
-
POOLCIRCUIT_MAGICSTREAM = 17;
|
|
482
|
-
POOLCIRCUIT_PHOTON = 11;
|
|
483
|
-
POOLCIRCUIT_POOL = 2;
|
|
484
|
-
POOLCIRCUIT_POOL_SECOND = 4;
|
|
485
|
-
POOLCIRCUIT_SAL = 10;
|
|
486
|
-
POOLCIRCUIT_SAM = 9;
|
|
487
|
-
POOLCIRCUIT_SPA = 1;
|
|
488
|
-
POOLCIRCUIT_SPA_SECOND = 3;
|
|
489
|
-
POOLCIRCUIT_SPILLWAY = 14;
|
|
490
|
-
POOLCIRCUIT_UNUSED = 19;
|
|
491
|
-
POOLCIRCUIT_VALVE = 13;
|
|
492
|
-
*/
|
|
493
|
-
if (_circ.function === 16) {
|
|
494
|
-
let lgCirc = {
|
|
495
|
-
color: _circ.colorSet,
|
|
496
|
-
swimDelay: _circ.colorStagger,
|
|
497
|
-
position: _circ.colorPos,
|
|
498
|
-
circuit: _circ.circuitId,
|
|
499
|
-
...data,
|
|
500
|
-
id: lgCircId,
|
|
501
|
-
}
|
|
502
|
-
lgCircId++;
|
|
503
|
-
lightGroup.circuits.push(lgCirc);
|
|
504
|
-
}
|
|
505
|
-
await sys.board.circuits.setCircuitAsync(data, false);
|
|
506
|
-
}
|
|
507
|
-
if (lightGroup.circuits.length === 0) {
|
|
508
|
-
sys.lightGroups.removeItemById(192);
|
|
509
|
-
state.lightGroups.removeItemById(192);
|
|
510
|
-
}
|
|
511
|
-
else {
|
|
512
|
-
let grp = sys.lightGroups.getItemById(192);
|
|
513
|
-
lightGroup.name = typeof grp.name === 'undefined' ? 'Intellibrite' : grp.name;
|
|
514
|
-
lightGroup.id === grp.isActive ? grp.id : undefined;
|
|
515
|
-
await sys.board.circuits.setLightGroupAsync(lightGroup, false);
|
|
516
|
-
let sgroup = state.lightGroups.getItemById(192, true);
|
|
517
|
-
sgroup.isActive = true;
|
|
518
|
-
sgroup.name = lightGroup.name;
|
|
519
|
-
sgroup.type = 3;
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
// now go back through and remove and circuits that aren't in the received list
|
|
523
|
-
let circuits = sys.circuits.get();
|
|
524
|
-
for (let i = 0; i < circuits.length; i++) {
|
|
525
|
-
let circuit = sys.circuits.getItemById(circuits[i].id);
|
|
526
|
-
let _circ = config.circuitArray.find(el => { return el.circuitId === circuit.id });
|
|
527
|
-
if (typeof _circ === 'undefined') {
|
|
528
|
-
sys.circuits.removeItemById(circuit.id);
|
|
529
|
-
state.circuits.removeItemById(circuit.id);
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
let features = sys.features.get();
|
|
533
|
-
for (let i = 0; i < features.length; i++) {
|
|
534
|
-
let feature = sys.features.getItemById(features[i].id);
|
|
535
|
-
let _circ = config.circuitArray.find(el => { return el.circuitId === feature.id });
|
|
536
|
-
if (typeof _circ === 'undefined') {
|
|
537
|
-
sys.features.removeItemById(feature.id);
|
|
538
|
-
state.features.removeItemById(feature.id);
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
/* if (config.equipment.POOL_CHLORPRESENT) {
|
|
543
|
-
let chlor = sys.chlorinators.getItemById(1, true);
|
|
544
|
-
let chlorState = state.chlorinators.getItemById(1, true);
|
|
545
|
-
chlorState.isActive = chlor.isActive = true;
|
|
546
|
-
}
|
|
547
|
-
else {
|
|
548
|
-
sys.chlorinators.removeItemById(1);
|
|
549
|
-
state.chlorinators.removeItemById(1);
|
|
550
|
-
}; */
|
|
551
|
-
let pumpsReported: number[] = [];
|
|
552
|
-
if (config.equipment.POOL_IFLOWPRESENT0) {
|
|
553
|
-
pumpsReported.push(1);
|
|
554
|
-
}
|
|
555
|
-
else {
|
|
556
|
-
sys.pumps.removeItemById(1);
|
|
557
|
-
state.pumps.removeItemById(1);
|
|
558
|
-
};
|
|
559
|
-
if (config.equipment.POOL_IFLOWPRESENT1) {
|
|
560
|
-
pumpsReported.push(2);
|
|
561
|
-
}
|
|
562
|
-
else {
|
|
563
|
-
sys.pumps.removeItemById(2);
|
|
564
|
-
state.pumps.removeItemById(2);
|
|
565
|
-
};
|
|
566
|
-
if (config.equipment.POOL_IFLOWPRESENT2) {
|
|
567
|
-
pumpsReported.push(3);
|
|
568
|
-
}
|
|
569
|
-
else {
|
|
570
|
-
sys.pumps.removeItemById(3);
|
|
571
|
-
state.pumps.removeItemById(3);
|
|
572
|
-
};
|
|
573
|
-
if (config.equipment.POOL_IFLOWPRESENT3) {
|
|
574
|
-
pumpsReported.push(4);
|
|
575
|
-
}
|
|
576
|
-
else {
|
|
577
|
-
sys.pumps.removeItemById(4);
|
|
578
|
-
state.pumps.removeItemById(4);
|
|
579
|
-
};
|
|
580
|
-
if (config.equipment.POOL_IFLOWPRESENT4) {
|
|
581
|
-
pumpsReported.push(5);
|
|
582
|
-
}
|
|
583
|
-
else {
|
|
584
|
-
sys.pumps.removeItemById(5);
|
|
585
|
-
state.pumps.removeItemById(5);
|
|
586
|
-
};
|
|
587
|
-
if (config.equipment.POOL_IFLOWPRESENT5) {
|
|
588
|
-
pumpsReported.push(6);
|
|
589
|
-
}
|
|
590
|
-
else {
|
|
591
|
-
sys.pumps.removeItemById(6);
|
|
592
|
-
state.pumps.removeItemById(6);
|
|
593
|
-
};
|
|
594
|
-
if (config.equipment.POOL_IFLOWPRESENT6) {
|
|
595
|
-
pumpsReported.push(7);
|
|
596
|
-
}
|
|
597
|
-
else {
|
|
598
|
-
sys.pumps.removeItemById(7);
|
|
599
|
-
state.pumps.removeItemById(7);
|
|
600
|
-
};
|
|
601
|
-
if (config.equipment.POOL_IFLOWPRESENT7) {
|
|
602
|
-
pumpsReported.push(8);
|
|
603
|
-
}
|
|
604
|
-
else {
|
|
605
|
-
sys.pumps.removeItemById(8);
|
|
606
|
-
state.pumps.removeItemById(8);
|
|
607
|
-
};
|
|
608
|
-
|
|
609
|
-
/* // deal with these in other places
|
|
610
|
-
if (config.equipment.POOL_NO_SPECIAL_LIGHTS) { }; // ?? Nothing to see here?
|
|
611
|
-
if (config.equipment.POOL_MAGICSTREAMPRESENT) { }; // Ya, so what?
|
|
612
|
-
if (config.equipment.POOL_IBRITEPRESENT) { };
|
|
613
|
-
// set in equip message
|
|
614
|
-
if (config.equipment.POOL_SOLARPRESENT) { };
|
|
615
|
-
if (config.equipment.POOL_SOLARHEATPUMP) { };
|
|
616
|
-
if (config.equipment.POOL_HEATPUMPHASCOOL) { };
|
|
617
|
-
*/
|
|
618
|
-
let intellichemPresent: boolean = false;
|
|
619
|
-
if (config.equipment.POOL_ICHEMPRESENT) {
|
|
620
|
-
intellichemPresent = true;
|
|
621
|
-
};
|
|
622
|
-
return { pumpsReported, intellichemPresent };
|
|
623
|
-
}
|
|
624
|
-
public static async decodeEquipmentState(eqstate: SLEquipmentStateData) {
|
|
625
|
-
/*
|
|
626
|
-
{
|
|
627
|
-
panelMode: 0,
|
|
628
|
-
freezeMode: 0,
|
|
629
|
-
remotes: 32,
|
|
630
|
-
poolDelay: 0,
|
|
631
|
-
spaDelay: 0,
|
|
632
|
-
cleanerDelay: 0,
|
|
633
|
-
airTemp: 67,
|
|
634
|
-
bodiesCount: 2,
|
|
635
|
-
bodies: [
|
|
636
|
-
{
|
|
637
|
-
id: 1,
|
|
638
|
-
currentTemp: 62,
|
|
639
|
-
heatStatus: 0,
|
|
640
|
-
setPoint: 79,
|
|
641
|
-
coolSetPoint: 0,
|
|
642
|
-
heatMode: 0
|
|
643
|
-
},
|
|
644
|
-
{
|
|
645
|
-
id: 2,
|
|
646
|
-
currentTemp: 64,
|
|
647
|
-
heatStatus: 0,
|
|
648
|
-
setPoint: 101,
|
|
649
|
-
coolSetPoint: 67,
|
|
650
|
-
heatMode: 3
|
|
651
|
-
}
|
|
652
|
-
],
|
|
653
|
-
circuitArray: [
|
|
654
|
-
{
|
|
655
|
-
id: 1,
|
|
656
|
-
state: 0,
|
|
657
|
-
colorSet: 0,
|
|
658
|
-
colorPos: 0,
|
|
659
|
-
colorStagger: 0,
|
|
660
|
-
delay: 0
|
|
661
|
-
},
|
|
662
|
-
...
|
|
663
|
-
],
|
|
664
|
-
pH: 0,
|
|
665
|
-
orp: 0,
|
|
666
|
-
saturation: 0,
|
|
667
|
-
saltPPM: 0,
|
|
668
|
-
pHTank: 0,
|
|
669
|
-
orpTank: 0,
|
|
670
|
-
alarms: 0
|
|
671
|
-
}
|
|
672
|
-
*/
|
|
673
|
-
try {
|
|
674
|
-
/* public boolean isDeviceready() {
|
|
675
|
-
return this.m_panelMode == 1;
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
public boolean isDeviceSync() {
|
|
679
|
-
return this.m_panelMode == 2;
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
public boolean isDeviceServiceMode() {
|
|
683
|
-
return this.m_panelMode == 3;
|
|
684
|
-
} */
|
|
685
|
-
if (eqstate.panelMode === 1) {
|
|
686
|
-
state.mode = 0; // ready
|
|
687
|
-
state.status = sys.board.valueMaps.controllerStatus.transform(1);
|
|
688
|
-
}
|
|
689
|
-
else if (eqstate.panelMode === 2) {
|
|
690
|
-
// syncronizing...
|
|
691
|
-
state.mode = 0;
|
|
692
|
-
state.status = sys.board.valueMaps.controllerStatus.transform(2);
|
|
693
|
-
}
|
|
694
|
-
else if (eqstate.panelMode === 3) {
|
|
695
|
-
// service mode
|
|
696
|
-
state.mode = 1;
|
|
697
|
-
state.status = sys.board.valueMaps.controllerStatus.transform(1);
|
|
698
|
-
}
|
|
699
|
-
if (eqstate.freezeMode) {
|
|
700
|
-
state.mode = state.mode === 1 ? 1 : 8;
|
|
701
|
-
state.freeze = true;
|
|
702
|
-
}
|
|
703
|
-
else {
|
|
704
|
-
state.freeze = false;
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
// set delays
|
|
708
|
-
if (eqstate.cleanerDelay) {
|
|
709
|
-
let cleaner: CircuitState = state.circuits.find(elem => elem.type === 5);
|
|
710
|
-
let bodyIsOn = state.temps.bodies.getBodyIsOn();
|
|
711
|
-
let bodyId = bodyIsOn.circuit === 6 ? 1 : 2;
|
|
712
|
-
delayMgr.setCleanerStartDelay(cleaner, bodyId, 60);
|
|
713
|
-
}
|
|
714
|
-
if (eqstate.poolDelay) { delayMgr.setManualPriorityDelay(state.circuits.getItemById(6)) };
|
|
715
|
-
if (eqstate.spaDelay) { delayMgr.setManualPriorityDelay(state.circuits.getItemById(1)) };
|
|
716
|
-
state.temps.air = eqstate.airTemp;
|
|
717
|
-
for (let i = 0; i < eqstate.bodies.length; i++) {
|
|
718
|
-
let slbody = eqstate.bodies[i];
|
|
719
|
-
let tbody = state.temps.bodies.getItemById(i + 1);
|
|
720
|
-
let body = sys.bodies.getItemById(i + 1);
|
|
721
|
-
body.setPoint = tbody.setPoint = slbody.setPoint;
|
|
722
|
-
body.heatMode = tbody.heatMode = slbody.heatMode === 3 ? 1 : slbody.heatMode; // 0=off; 3=heater
|
|
723
|
-
tbody.heatStatus = slbody.heatStatus === 2 ? 1 : slbody.heatStatus; // 2=heater active
|
|
724
|
-
tbody.coolSetpoint = slbody.coolSetPoint;
|
|
725
|
-
tbody.temp = slbody.currentTemp;
|
|
726
|
-
}
|
|
727
|
-
for (let i = 0; i < eqstate.circuitArray.length; i++) {
|
|
728
|
-
let slcirc = eqstate.circuitArray[i];
|
|
729
|
-
let cstate = state.circuits.getInterfaceById(slcirc.id);
|
|
730
|
-
let slcircIsOn = utils.makeBool(slcirc.state);
|
|
731
|
-
if (cstate.isOn !== slcircIsOn) {
|
|
732
|
-
sys.board.circuits.setEndTime(sys.circuits.getItemById(cstate.id), cstate, slcircIsOn);
|
|
733
|
-
cstate.isOn = slcircIsOn;
|
|
734
|
-
if (cstate.id === 1 || cstate.id === 6) {
|
|
735
|
-
let tbody = state.temps.bodies.getBodyByCircuitId(cstate.id);
|
|
736
|
-
tbody.isOn = slcircIsOn;
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
if (slcirc.delay) {
|
|
740
|
-
// ??
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
let address = 144;
|
|
744
|
-
let chem = sys.chemControllers.getItemByAddress(address);
|
|
745
|
-
if (chem.isActive) {
|
|
746
|
-
let schem = state.chemControllers.getItemById(chem.id, true);
|
|
747
|
-
/* pH: 0,
|
|
748
|
-
orp: 0,
|
|
749
|
-
saturation: 0,
|
|
750
|
-
saltPPM: 0,
|
|
751
|
-
pHTank: 0,
|
|
752
|
-
orpTank: 0,
|
|
753
|
-
alarms: 0 */
|
|
754
|
-
schem.orp.level = eqstate.orp;
|
|
755
|
-
schem.saturationIndex = eqstate.saturation;
|
|
756
|
-
schem.ph.tank.level = eqstate.pHTank;
|
|
757
|
-
schem.orp.tank.level = eqstate.orpTank;
|
|
758
|
-
// saltPPM ==> set by intellichlor msg
|
|
759
|
-
// schem.alarms. ==> Need alarm mapping...
|
|
760
|
-
webApp.emitToClients('chemController', schem.getExtended()); // emit extended data
|
|
761
|
-
}
|
|
762
|
-
state.emitControllerChange();
|
|
763
|
-
state.emitEquipmentChanges();
|
|
764
|
-
} catch (err) {
|
|
765
|
-
logger.error(`Caught error in decodeEquipmentState: ${err.message}`);
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
public static async decodeCustomNames(customNames: SLGetCustomNamesData) {
|
|
769
|
-
for (let i = 0; i < sys.equipment.maxCustomNames; i++) {
|
|
770
|
-
let data = {
|
|
771
|
-
id: i,
|
|
772
|
-
name: customNames.names[i]
|
|
773
|
-
}
|
|
774
|
-
try {
|
|
775
|
-
|
|
776
|
-
await sys.board.system.setCustomNameAsync(data, false)
|
|
777
|
-
}
|
|
778
|
-
catch (err) {
|
|
779
|
-
logger.error(`Error setting custom name ${JSON.stringify(data)}`);
|
|
780
|
-
};
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
public static async decodeEquipmentAsync(equip: SLEquipmentConfigurationData) {
|
|
784
|
-
if (sys.controllerType !== ControllerType.EasyTouch && Controller.isEasyTouch(equip.controllerType)) {
|
|
785
|
-
sys.controllerType = ControllerType.EasyTouch;
|
|
786
|
-
(sys.board as EasyTouchBoard).initExpansionModules(equip.controllerType, equip.hardwareType);
|
|
787
|
-
}
|
|
788
|
-
else if (sys.controllerType !== ControllerType.IntelliTouch && Controller.isIntelliTouch(equip.controllerType)) {
|
|
789
|
-
sys.controllerType = ControllerType.IntelliTouch;
|
|
790
|
-
(sys.board as IntelliTouchBoard).initExpansionModules(equip.controllerType, equip.hardwareType);
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
let body = sys.bodies.getItemById(2);
|
|
794
|
-
sys.general.options.manualHeat = body.manualHeat = equip.misc.manualHeat;
|
|
795
|
-
|
|
796
|
-
await Controller.decodeHeatersAsync(equip.heaterConfig);
|
|
797
|
-
await Controller.decodeValvesAsync(equip.valves);
|
|
798
|
-
Controller.decodeHighSpeed(equip.highSpeedCircuits);
|
|
799
|
-
// delays
|
|
800
|
-
sys.general.options.pumpDelay = equip.delays.pumpOffDuringValveAction;
|
|
801
|
-
for (let i = 0; i < sys.bodies.length; i++) {
|
|
802
|
-
let bs = state.temps.bodies.getItemById(i + 1);
|
|
803
|
-
if (bs.circuit === 1) bs.heaterCooldownDelay = equip.delays.spaPumpOnDuringHeaterCooldown;
|
|
804
|
-
else if (bs.circuit === 6) bs.heaterCooldownDelay = equip.delays.poolPumpOnDuringHeaterCooldown;
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
Controller.decodeRemote(equip.remotes);
|
|
808
|
-
Controller.decodePumpAsync(equip.pumps);
|
|
809
|
-
// lights
|
|
810
|
-
// packet only lists all-on all-off for intellibrite.
|
|
811
|
-
|
|
812
|
-
// if (equip.misc.intelliChem) {
|
|
813
|
-
// let chem = sys.chemControllers.getItemByAddress(144, true);
|
|
814
|
-
// let schem = state.chemControllers.getItemById(1, true);
|
|
815
|
-
// schem.isActive = chem.isActive = true;
|
|
816
|
-
// }
|
|
817
|
-
// else {
|
|
818
|
-
// sys.chemControllers.removeItemById(1);
|
|
819
|
-
// state.chemControllers.removeItemById(1);
|
|
820
|
-
// }
|
|
821
|
-
sys.equipment.controllerFirmware = `${Math.floor(equip.version / 1000).toString()}.${(equip.version % 1000).toString()}`;
|
|
822
|
-
}
|
|
823
|
-
public static async decodeHeatersAsync(heaterConfig: HeaterConfig) {
|
|
824
|
-
let address: number;
|
|
825
|
-
let id: number;
|
|
826
|
-
let type: number = 1;
|
|
827
|
-
let cooling: boolean = false;
|
|
828
|
-
let body: number = 32;
|
|
829
|
-
// how do we know the heater is a hybrid (type=4)??
|
|
830
|
-
// if no hybrid, we do have a gas heater;
|
|
831
|
-
// it may not be possible to set a Hybrid heater from SL...
|
|
832
|
-
// will go with that until we learn otherwise
|
|
833
|
-
// also todo - how to add heaters to dual bodies?
|
|
834
|
-
let data: any = {
|
|
835
|
-
address,
|
|
836
|
-
id,
|
|
837
|
-
type,
|
|
838
|
-
cooling,
|
|
839
|
-
body
|
|
840
|
-
}
|
|
841
|
-
try {
|
|
842
|
-
let heater = state.heaters.getItemById(1);
|
|
843
|
-
if (heater.type === 0) {
|
|
844
|
-
// to add a heater, id must be 0;
|
|
845
|
-
// await sys.board.heaters.setHeaterAsync(data, false)
|
|
846
|
-
}
|
|
847
|
-
else {
|
|
848
|
-
data.id = 1;
|
|
849
|
-
}
|
|
850
|
-
await sys.board.heaters.setHeaterAsync(data, false);
|
|
851
|
-
}
|
|
852
|
-
catch (err) {
|
|
853
|
-
logger.error(`Error setting gas heater: ${err.message}`)
|
|
854
|
-
}
|
|
855
|
-
let add = false;
|
|
856
|
-
if (heaterConfig.thermaFloPresent) {
|
|
857
|
-
let heater = sys.heaters.getItemById(3);
|
|
858
|
-
if (!heater.isActive) {
|
|
859
|
-
data.address = 112;
|
|
860
|
-
data.type = 3;
|
|
861
|
-
if (heaterConfig.thermaFloCoolPresent) cooling = true;
|
|
862
|
-
add = true;
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
else if (heaterConfig.body1SolarPresent) {
|
|
866
|
-
let heater = sys.heaters.getItemById(2);
|
|
867
|
-
if (!heater.isActive) {
|
|
868
|
-
data.type = 2;
|
|
869
|
-
add = true;
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
// RSG - Which type is this? Duplicate of 3 above.
|
|
873
|
-
// else if (heaterConfig.solarHeatPumpPresent) {
|
|
874
|
-
// let heater = sys.heaters.getItemById(3);
|
|
875
|
-
// if (!heater.isActive) {
|
|
876
|
-
// data.type = 3;
|
|
877
|
-
// add = true;
|
|
878
|
-
// }
|
|
879
|
-
//batt}
|
|
880
|
-
// Need to figure out dual body here: body2SolarPresent
|
|
881
|
-
if (add) {
|
|
882
|
-
sys.board.heaters.setHeaterAsync(data, false).catch((err) => {
|
|
883
|
-
logger.error(`Error setting additional heaters: ${err.message}`)
|
|
884
|
-
});
|
|
885
|
-
}
|
|
886
|
-
if (typeof heaterConfig.units !== 'undefined') state.temps.units = sys.general.options.units = heaterConfig.units; // 0 = F, 1 = C
|
|
887
|
-
}
|
|
888
|
-
public static async decodeValvesAsync(valves: Valves[]) {
|
|
889
|
-
for (let i = 0; i < valves.length; i++) {
|
|
890
|
-
let _valve = valves[i];
|
|
891
|
-
let data: any = {
|
|
892
|
-
id: _valve.valveIndex,
|
|
893
|
-
name: _valve.valveName,
|
|
894
|
-
circuit: _valve.deviceId,
|
|
895
|
-
}
|
|
896
|
-
await sys.board.valves.setValveAsync(data, false);
|
|
897
|
-
}
|
|
898
|
-
/* "valves": [
|
|
899
|
-
{
|
|
900
|
-
"loadCenterIndex": 0,
|
|
901
|
-
"valveIndex": 1,
|
|
902
|
-
"valveName": "A",
|
|
903
|
-
"loadCenterName": "1",
|
|
904
|
-
"deviceId": 0
|
|
905
|
-
},
|
|
906
|
-
{
|
|
907
|
-
"loadCenterIndex": 0,
|
|
908
|
-
"valveIndex": 2,
|
|
909
|
-
"valveName": "B",
|
|
910
|
-
"loadCenterName": "1",
|
|
911
|
-
"deviceId": 0
|
|
912
|
-
}
|
|
913
|
-
], */
|
|
914
|
-
}
|
|
915
|
-
public static decodeHighSpeed(highSpeed: number[]) {
|
|
916
|
-
let maxCircuits = sys.controllerType === ControllerType.IntelliTouch ? 8 : 4;
|
|
917
|
-
let arrCircuits = [];
|
|
918
|
-
let pump = sys.pumps.find(x => { return x.master !== 1 && x.type === 65 });
|
|
919
|
-
for (let i = 0; i < maxCircuits && i < highSpeed.length; i++) {
|
|
920
|
-
let val = highSpeed[i];
|
|
921
|
-
if (val > 0) arrCircuits.push(val);
|
|
922
|
-
else if (typeof pump !== 'undefined') pump.circuits.removeItemById(i);
|
|
923
|
-
}
|
|
924
|
-
if (arrCircuits.length > 0) {
|
|
925
|
-
let pump = sys.pumps.getDualSpeed(true);
|
|
926
|
-
for (let j = 1; j <= arrCircuits.length; j++) pump.circuits.getItemById(j, true).circuit = arrCircuits[j - 1];
|
|
927
|
-
}
|
|
928
|
-
else if (typeof pump !== 'undefined') sys.pumps.removeItemById(pump.id);
|
|
929
|
-
}
|
|
930
|
-
public static decodeRemote(remoteDataArray) {
|
|
931
|
-
if (sys.controllerType === ControllerType.EasyTouch) {
|
|
932
|
-
|
|
933
|
-
let remote: Remote = sys.remotes.getItemById(5, true);
|
|
934
|
-
let bActive = false;
|
|
935
|
-
for (let i = 0; i < 10; i++) {
|
|
936
|
-
remote["button" + i] = remoteDataArray.fourButton[i];
|
|
937
|
-
bActive = bActive || remote["button" + i] > 0;
|
|
938
|
-
}
|
|
939
|
-
remote.isActive = bActive;
|
|
940
|
-
remote.type = 1;
|
|
941
|
-
remote.name = "is4";
|
|
942
|
-
|
|
943
|
-
remote = sys.remotes.getItemById(1, true);
|
|
944
|
-
bActive = false;
|
|
945
|
-
for (let i = 0; i < 10; i++) {
|
|
946
|
-
remote["button" + i] = remoteDataArray.tenButton[0][i];
|
|
947
|
-
bActive = bActive || remote["button" + i] > 0;
|
|
948
|
-
}
|
|
949
|
-
remote.isActive = bActive;
|
|
950
|
-
remote.type = 2;
|
|
951
|
-
remote.name = "is10";
|
|
952
|
-
}
|
|
953
|
-
else if (sys.controllerType === ControllerType.IntelliTouch) {
|
|
954
|
-
// Intellitouch
|
|
955
|
-
// 10 button #1
|
|
956
|
-
|
|
957
|
-
for (let r = 0; r < 4; r++) {
|
|
958
|
-
let remote: Remote = sys.remotes.getItemById(r + 1, true);
|
|
959
|
-
let bActive = false;
|
|
960
|
-
for (let i = 0; i < 10; i++) {
|
|
961
|
-
remote["button" + (i + 1)] = remoteDataArray.tenButton[r][i];
|
|
962
|
-
bActive = bActive || remote["button" + (i + 1)] > 0;
|
|
963
|
-
}
|
|
964
|
-
remote.isActive = bActive;
|
|
965
|
-
remote.type = 2;
|
|
966
|
-
remote.name = "is10";
|
|
967
|
-
if (r === 3) {
|
|
968
|
-
let remote5 = sys.remotes.getItemById(5);
|
|
969
|
-
let remote6 = sys.remotes.getItemById(6);
|
|
970
|
-
remote5.name = remote6.name = "is4";
|
|
971
|
-
remote5.type = remote6.type = 1;
|
|
972
|
-
if (!remote.button5 && !remote.button10) {
|
|
973
|
-
remote.isActive = false;
|
|
974
|
-
remote5.button1 = remote.button1;
|
|
975
|
-
remote5.button2 = remote.button2;
|
|
976
|
-
remote5.button3 = remote.button3;
|
|
977
|
-
remote5.button4 = remote.button4;
|
|
978
|
-
remote6.button1 = remote.button6;
|
|
979
|
-
remote6.button2 = remote.button7;
|
|
980
|
-
remote6.button3 = remote.button8;
|
|
981
|
-
remote6.button4 = remote.button9;
|
|
982
|
-
if (!remote5.button1 && !remote5.button2 && !remote5.button3 && !remote5.button4) remote5.isActive = false;
|
|
983
|
-
else remote5.isActive = true;
|
|
984
|
-
|
|
985
|
-
if (!remote6.button1 && !remote6.button2 && !remote6.button3 && !remote6.button4) remote6.isActive = false;
|
|
986
|
-
else remote6.isActive = true;
|
|
987
|
-
|
|
988
|
-
}
|
|
989
|
-
else {
|
|
990
|
-
remote5.isActive = remote6.isActive = false;
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
}
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
let remote = sys.remotes.getItemById(7, true);
|
|
997
|
-
remote.button1 = remoteDataArray.quickTouch[0];
|
|
998
|
-
remote.button2 = remoteDataArray.quickTouch[1];
|
|
999
|
-
remote.button3 = remoteDataArray.quickTouch[2];
|
|
1000
|
-
remote.button4 = remoteDataArray.quickTouch[3];
|
|
1001
|
-
|
|
1002
|
-
if (!remote.button1 && !remote.button2 && !remote.button3 && !remote.button4) remote.isActive = false;
|
|
1003
|
-
else remote.isActive = true;
|
|
1004
|
-
remote.name = "QuickTouch";
|
|
1005
|
-
}
|
|
1006
|
-
public static async decodeIntellichlorAsync(slchlor: SLIntellichlorData) {
|
|
1007
|
-
// Intellichlor: {"installed":false,"status":1,"poolSetPoint":12,"spaSetPoint":0,"salt":0,"flags":0,"superChlorTimer":0}
|
|
1008
|
-
let chlor = sys.chlorinators.getItemById(1);
|
|
1009
|
-
if (slchlor.installed) {
|
|
1010
|
-
let data: any = {
|
|
1011
|
-
id: chlor.isActive ? chlor.id : 0,
|
|
1012
|
-
superChlorHours: slchlor.superChlorTimer,
|
|
1013
|
-
poolSetpoint: slchlor.poolSetPoint,
|
|
1014
|
-
spaSetpoint: slchlor.spaSetPoint,
|
|
1015
|
-
model: chlor.model || 0,
|
|
1016
|
-
body: 32
|
|
1017
|
-
}
|
|
1018
|
-
await sys.board.chlorinator.setChlorAsync(data, false);
|
|
1019
|
-
let chlorState = state.chlorinators.getItemById(1, true);
|
|
1020
|
-
chlorState.saltLevel = slchlor.salt;
|
|
1021
|
-
chlorState.poolSetpoint = slchlor.poolSetPoint;
|
|
1022
|
-
chlorState.spaSetpoint = slchlor.spaSetPoint;
|
|
1023
|
-
state.emitEquipmentChanges();
|
|
1024
|
-
}
|
|
1025
|
-
else {
|
|
1026
|
-
sys.chlorinators.removeItemById(1);
|
|
1027
|
-
state.chlorinators.removeItemById(1);
|
|
1028
|
-
};
|
|
1029
|
-
|
|
1030
|
-
}
|
|
1031
|
-
public static async decodeChemController(slchem: SLChemData) {
|
|
1032
|
-
// Chem data: {"isValid":true,"pH":0,"orp":0,"pHSetPoint":0,"orpSetPoint":0,"pHTankLevel":0,"orpTankLevel":0,"saturation":0,"calcium":0,"cyanuricAcid":0,"alkalinity":0,"saltPPM":0,"temperature":0,"balance":0,"corrosive":false,"scaling":false,"error":false}
|
|
1033
|
-
let chem = sys.chemControllers.getItemByAddress(144);
|
|
1034
|
-
let data: any = {
|
|
1035
|
-
id: chem.isActive ? chem.id : undefined,
|
|
1036
|
-
address: 144,
|
|
1037
|
-
calciumHardness: slchem.calcium,
|
|
1038
|
-
cyanuricAcid: slchem.cyanuricAcid,
|
|
1039
|
-
alkalinity: slchem.alkalinity,
|
|
1040
|
-
body: 32,
|
|
1041
|
-
ph: {
|
|
1042
|
-
setpoint: slchem.pHSetPoint,
|
|
1043
|
-
enabled: true,
|
|
1044
|
-
tank: slchem.pHTankLevel
|
|
1045
|
-
},
|
|
1046
|
-
orp: {
|
|
1047
|
-
setpoint: slchem.orpSetPoint,
|
|
1048
|
-
enabled: true,
|
|
1049
|
-
tank: slchem.orpTankLevel
|
|
1050
|
-
},
|
|
1051
|
-
type: 2
|
|
1052
|
-
|
|
1053
|
-
}
|
|
1054
|
-
try {
|
|
1055
|
-
|
|
1056
|
-
await sys.board.chemControllers.setChemControllerAsync(data, false);
|
|
1057
|
-
let schem = state.chemControllers.getItemById(1);
|
|
1058
|
-
schem.ph.level = slchem.pH;
|
|
1059
|
-
schem.orp.level = slchem.orp;
|
|
1060
|
-
schem.saturationIndex = slchem.saturation;
|
|
1061
|
-
schem.alarms.bodyFault = slchem.error ? 1 : 0; // maybe a better place to assign the error?
|
|
1062
|
-
state.emitEquipmentChanges();
|
|
1063
|
-
}
|
|
1064
|
-
catch (err) {
|
|
1065
|
-
return Promise.reject(err);
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
}
|
|
1069
|
-
public static async decodePumpAsync(pDataArr: any) {
|
|
1070
|
-
pDataArr.forEach(async (pData, idx) => {
|
|
1071
|
-
await sys.board.pumps.setPumpAsync(pData, false);
|
|
1072
|
-
})
|
|
1073
|
-
}
|
|
1074
|
-
public static async decodePumpStatusAsync(id: number, slpump: SLPumpStatusData) {
|
|
1075
|
-
/* {
|
|
1076
|
-
pumpCircuits: [
|
|
1077
|
-
{ circuitId: 6,speed: 2000,isRPMs: true, },
|
|
1078
|
-
{ circuitId: 8, speed:2700,isRPMs: true, },
|
|
1079
|
-
{ circuitId: 2,speed: 2710,isRPMs: true, },
|
|
1080
|
-
{ circuitId: 2,speed:1000, isRPMs: true,},
|
|
1081
|
-
{ circuitId: 5,speed:2830, isRPMs: true,},
|
|
1082
|
-
{ circuitId: 0,speed: 30,isRPMs: false,},
|
|
1083
|
-
{ circuitId: 0,speed: 30,isRPMs: false,},
|
|
1084
|
-
{ circuitId: 0,speed: 30,isRPMs: false,},
|
|
1085
|
-
],
|
|
1086
|
-
pumpType: 4,
|
|
1087
|
-
isRunning: false,
|
|
1088
|
-
pumpWatts: 0,
|
|
1089
|
-
pumpRPMs: 0,
|
|
1090
|
-
pumpUnknown1: 0,
|
|
1091
|
-
pumpGPMs: 0,
|
|
1092
|
-
pumpUnknown2: 255,
|
|
1093
|
-
}
|
|
1094
|
-
*/
|
|
1095
|
-
// RKS: 05-07-23 - This process of getting the pump by its id is flawed. We need to pull this information by its address.
|
|
1096
|
-
//let pstate = state.pumps.getItemById(id);
|
|
1097
|
-
let pstate = state.pumps.find(x => x.address === 95 + id);
|
|
1098
|
-
if (typeof pstate !== 'undefined') {
|
|
1099
|
-
pstate.watts = slpump.pumpWatts;
|
|
1100
|
-
pstate.rpm = slpump.pumpRPMs;
|
|
1101
|
-
pstate.flow = slpump.pumpGPMs === 255 ? 0 : slpump.pumpGPMs;
|
|
1102
|
-
pstate.command = (pstate.rpm > 0 || pstate.watts > 0) ? 10 : 0;
|
|
1103
|
-
state.emitEquipmentChanges();
|
|
1104
|
-
}
|
|
1105
|
-
}
|
|
1106
|
-
public static async decodeSchedules(slrecurring: SLScheduleData, slrunonce: SLScheduleData) {
|
|
1107
|
-
/* reccuring schedules: [{"scheduleId":1,"circuitId":6,"startTime":"1800","stopTime":"0700","dayMask":127,"flags":0,"heatCmd":4,"heatSetPoint":70,"days":["Mon","Tue","Wed","Thu","Fri","Sat","Sun"]},
|
|
1108
|
-
|
|
1109
|
-
{"scheduleId":4,"circuitId":2,"startTime":"1800","stopTime":"2300","dayMask":127,"flags":0,"heatCmd":0,"heatSetPoint":0,"days":["Mon","Tue","Wed","Thu","Fri","Sat","Sun"]},{"scheduleId":11,"circuitId":6,"startTime":"0800","stopTime":"1700","dayMask":127,"flags":0,"heatCmd":4,"heatSetPoint":70,"days":["Mon","Tue","Wed","Thu","Fri","Sat","Sun"]}]
|
|
1110
|
-
|
|
1111
|
-
Run once schedules: [{"scheduleId":12,"circuitId":6,"startTime":"0800","stopTime":"1100","dayMask":1,"flags":1,"heatCmd":4,"heatSetPoint":70,"days":["Mon"]},{"scheduleId":13,"circuitId":6,"startTime":"0800","stopTime":"1100","dayMask":1,"flags":1,"heatCmd":4,"heatSetPoint":70,"days":["Mon"]}] */
|
|
1112
|
-
|
|
1113
|
-
for (let i = 0; i < slrecurring.data.length; i++) {
|
|
1114
|
-
let slsched = slrecurring.data[i];
|
|
1115
|
-
try {
|
|
1116
|
-
let data = {
|
|
1117
|
-
circuit: slsched.circuitId,
|
|
1118
|
-
startTime: Math.floor(parseInt(slsched.startTime, 10) / 100) * 60 + parseInt(slsched.startTime, 10) % 100,
|
|
1119
|
-
endTime: Math.floor(parseInt(slsched.stopTime, 10) / 100) * 60 + parseInt(slsched.stopTime, 10) % 100,
|
|
1120
|
-
scheduleDays: slsched.dayMask,
|
|
1121
|
-
changeHeatSetPoint: slsched.heatCmd > 0,
|
|
1122
|
-
heatSetPoint: slsched.heatSetPoint,
|
|
1123
|
-
schedType: 128 // recurring
|
|
1124
|
-
}
|
|
1125
|
-
await sys.board.schedules.setScheduleAsync(data, false)
|
|
1126
|
-
} catch (err) {
|
|
1127
|
-
logger.error(`Error setting schedule ${slsched.scheduleId}. ${err.message}`);
|
|
1128
|
-
}
|
|
1129
|
-
}
|
|
1130
|
-
for (let i = 0; i < slrunonce.data.length; i++) {
|
|
1131
|
-
let slsched = slrunonce.data[i];
|
|
1132
|
-
try {
|
|
1133
|
-
let data = {
|
|
1134
|
-
id: slsched.scheduleId,
|
|
1135
|
-
circuit: slsched.circuitId,
|
|
1136
|
-
// start and stop come in as military time string
|
|
1137
|
-
startTime: parseInt(slsched.startTime, 10),
|
|
1138
|
-
endTime: parseInt(slsched.stopTime, 10),
|
|
1139
|
-
scheduleDays: slsched.dayMask,
|
|
1140
|
-
changeHeatSetPoint: slsched.heatCmd > 0,
|
|
1141
|
-
heatSetPoint: slsched.heatSetPoint,
|
|
1142
|
-
schedType: 0 // runonce
|
|
1143
|
-
}
|
|
1144
|
-
await sys.board.schedules.setScheduleAsync(data, false);
|
|
1145
|
-
sys.board.system.setTZ();
|
|
1146
|
-
} catch (err) {
|
|
1147
|
-
logger.error(`Error setting schedule ${slsched.scheduleId}. ${err.message}`);
|
|
1148
|
-
}
|
|
1149
|
-
}
|
|
1150
|
-
|
|
1151
|
-
}
|
|
1152
|
-
public static decodeDateTime(systime: SLSystemTimeData) {
|
|
1153
|
-
// System Time: {"date":"2022-11-07T16:04:32.000Z","year":2022,"month":11,"dayOfWeek":1,"day":7,"hour":8,"minute":4,"second":32,"millisecond":0,"adjustForDST":true}
|
|
1154
|
-
if (sys.general.options.clockSource !== 'server') {
|
|
1155
|
-
state.time.year = systime.year;
|
|
1156
|
-
state.time.month = systime.month;
|
|
1157
|
-
state.time.date = systime.day;
|
|
1158
|
-
state.time.hours = systime.hour;
|
|
1159
|
-
state.time.minutes = systime.minute;
|
|
1160
|
-
state.time.seconds = systime.second;
|
|
1161
|
-
sys.general.options.adjustDST = systime.adjustForDST;
|
|
1162
|
-
state.emitEquipmentChanges();
|
|
1163
|
-
}
|
|
1164
|
-
}
|
|
1165
|
-
/*
|
|
1166
|
-
Controller Types
|
|
1167
|
-
// Dual Intellitouch
|
|
1168
|
-
I10_3D = 5;
|
|
1169
|
-
// Intellitouch
|
|
1170
|
-
I5 = 0;
|
|
1171
|
-
I7_3 = 1;
|
|
1172
|
-
I9_3 = 2;
|
|
1173
|
-
I5S = 3;
|
|
1174
|
-
I9_3S = 4;
|
|
1175
|
-
I10X = 6;
|
|
1176
|
-
// Not intellitouch...
|
|
1177
|
-
SUNTOUCH = 10;
|
|
1178
|
-
// EasyTouch
|
|
1179
|
-
EASYTOUCH2 = 13; //hwType & 4 = EasyTouchLite
|
|
1180
|
-
EASYTOUCH = 14;
|
|
1181
|
-
*/
|
|
1182
|
-
static isEasyTouch(controllerType) {
|
|
1183
|
-
return controllerType === 14 || controllerType === 13;
|
|
1184
|
-
}
|
|
1185
|
-
|
|
1186
|
-
static isIntelliTouch(controllerType) {
|
|
1187
|
-
return controllerType !== 14 && controllerType !== 13 && controllerType !== 10;
|
|
1188
|
-
}
|
|
1189
|
-
|
|
1190
|
-
static isEasyTouchLite(controllerType, hwType) {
|
|
1191
|
-
return controllerType === 13 && (hwType & 4) !== 0;
|
|
1192
|
-
}
|
|
1193
|
-
|
|
1194
|
-
static isDualBody(controllerType) {
|
|
1195
|
-
return controllerType === 5;
|
|
1196
|
-
}
|
|
1197
|
-
}
|
|
1198
|
-
export class SLCommands {
|
|
1199
|
-
constructor(unit: UnitConnection) {
|
|
1200
|
-
this._unit = unit;
|
|
1201
|
-
}
|
|
1202
|
-
protected _unit: UnitConnection;
|
|
1203
|
-
}
|
|
1204
|
-
export class SLCircuits extends SLCommands {
|
|
1205
|
-
public async setCircuitAsync(circuitId: number, nameIndex: number, circuitFunction: number, circuitInterface: number, freeze: boolean = false, colorPos: number = 0) {
|
|
1206
|
-
try {
|
|
1207
|
-
|
|
1208
|
-
let lg: LightGroup = sys.lightGroups.getItemById(1);
|
|
1209
|
-
for (let i = 0; i < lg.circuits.length; i++) {
|
|
1210
|
-
let cg: LightGroupCircuit = lg.circuits[i];
|
|
1211
|
-
if (cg.circuit === circuitId) colorPos = cg.position;
|
|
1212
|
-
}
|
|
1213
|
-
await this._unit.circuits.setCircuitAsync(circuitId, nameIndex, circuitFunction, circuitInterface, freeze, colorPos);
|
|
1214
|
-
} catch (err) {
|
|
1215
|
-
return Promise.reject(err);
|
|
1216
|
-
}
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
public async setCircuitStateAsync(id: number, val: boolean) {
|
|
1220
|
-
try {
|
|
1221
|
-
if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError('Circuit or Feature id not valid', id, 'Circuit'));
|
|
1222
|
-
let c = sys.circuits.getInterfaceById(id);
|
|
1223
|
-
// if (id === 192 || c.type === 3) return await sys.board.circuits.setLightGroupThemeAsync(id - 191, val ? 1 : 0);
|
|
1224
|
-
// if (id >= 192) return await sys.board.circuits.setCircuitGroupStateAsync(id, val);
|
|
1225
|
-
await this._unit.circuits.setCircuitStateAsync(id, val);
|
|
1226
|
-
// let cstate = state.circuits.getInterfaceById(id);
|
|
1227
|
-
// cstate.isOn = val;
|
|
1228
|
-
// state.emitEquipmentChanges();
|
|
1229
|
-
// return cstate;
|
|
1230
|
-
} catch (err) {
|
|
1231
|
-
return Promise.reject(err);
|
|
1232
|
-
}
|
|
1233
|
-
}
|
|
1234
|
-
|
|
1235
|
-
public async setLightGroupThemeAsync(lightTheme: number) {
|
|
1236
|
-
try {
|
|
1237
|
-
// SL Light Themes
|
|
1238
|
-
const ALLOFF = 0;
|
|
1239
|
-
const ALLON = 1;
|
|
1240
|
-
const SET = 2;
|
|
1241
|
-
const SYNC = 3;
|
|
1242
|
-
const SWIM = 4;
|
|
1243
|
-
const PARTY = 5;
|
|
1244
|
-
const ROMANCE = 6;
|
|
1245
|
-
const CARIBBEAN = 7;
|
|
1246
|
-
const AMERICAN = 8;
|
|
1247
|
-
const SUNSET = 9;
|
|
1248
|
-
const ROYALTY = 10;
|
|
1249
|
-
const SAVE = 11;
|
|
1250
|
-
const RECALL = 12;
|
|
1251
|
-
const BLUE = 13;
|
|
1252
|
-
const GREEN = 14;
|
|
1253
|
-
const RED = 15;
|
|
1254
|
-
const WHITE = 16;
|
|
1255
|
-
const MAGENTA = 17;
|
|
1256
|
-
const MS_THUMPER = 18;
|
|
1257
|
-
const MS_NEXT_MODE = 19;
|
|
1258
|
-
const MS_RESET = 20;
|
|
1259
|
-
const MS_HOLD = 21;
|
|
1260
|
-
|
|
1261
|
-
// Convert njsPC to SL themes
|
|
1262
|
-
switch (lightTheme) {
|
|
1263
|
-
// [0, { name: 'off', desc: 'Off' }],
|
|
1264
|
-
// [1, { name: 'on', desc: 'On' }],
|
|
1265
|
-
case 0:
|
|
1266
|
-
case 1:
|
|
1267
|
-
break;
|
|
1268
|
-
// [128, { name: 'colorsync', desc: 'Color Sync' }],
|
|
1269
|
-
case 128:
|
|
1270
|
-
lightTheme = SYNC;
|
|
1271
|
-
break;
|
|
1272
|
-
// [144, { name: 'colorswim', desc: 'Color Swim' }],
|
|
1273
|
-
case 144:
|
|
1274
|
-
lightTheme = SWIM;
|
|
1275
|
-
// [160, { name: 'colorset', desc: 'Color Set' }],
|
|
1276
|
-
case 160:
|
|
1277
|
-
lightTheme = SET;
|
|
1278
|
-
// [177, { name: 'party', desc: 'Party', types: ['intellibrite'], sequence: 2 }],
|
|
1279
|
-
case 177:
|
|
1280
|
-
lightTheme = PARTY;
|
|
1281
|
-
// [178, { name: 'romance', desc: 'Romance', types: ['intellibrite'], sequence: 3 }],
|
|
1282
|
-
case 178:
|
|
1283
|
-
lightTheme = ROMANCE;
|
|
1284
|
-
break;
|
|
1285
|
-
// [179, { name: 'caribbean', desc: 'Caribbean', types: ['intellibrite'], sequence: 4 }],
|
|
1286
|
-
case 179:
|
|
1287
|
-
lightTheme = CARIBBEAN;
|
|
1288
|
-
break;
|
|
1289
|
-
// [180, { name: 'american', desc: 'American', types: ['intellibrite'], sequence: 5 }],
|
|
1290
|
-
case 180:
|
|
1291
|
-
lightTheme = AMERICAN;
|
|
1292
|
-
break;
|
|
1293
|
-
// [181, { name: 'sunset', desc: 'Sunset', types: ['intellibrite'], sequence: 6 }],
|
|
1294
|
-
case 181:
|
|
1295
|
-
lightTheme = SUNSET;
|
|
1296
|
-
break;
|
|
1297
|
-
// [182, { name: 'royal', desc: 'Royal', types: ['intellibrite'], sequence: 7 }],
|
|
1298
|
-
case 182:
|
|
1299
|
-
lightTheme = ROYALTY;
|
|
1300
|
-
break;
|
|
1301
|
-
// [190, { name: 'save', desc: 'Save', types: ['intellibrite'], sequence: 13 }],
|
|
1302
|
-
case 190:
|
|
1303
|
-
lightTheme = SAVE;
|
|
1304
|
-
break;
|
|
1305
|
-
// [191, { name: 'recall', desc: 'Recall', types: ['intellibrite'], sequence: 14 }],
|
|
1306
|
-
case 191:
|
|
1307
|
-
lightTheme = RECALL;
|
|
1308
|
-
break;
|
|
1309
|
-
// [193, { name: 'blue', desc: 'Blue', types: ['intellibrite'], sequence: 8 }],
|
|
1310
|
-
case 192:
|
|
1311
|
-
lightTheme = BLUE;
|
|
1312
|
-
break;
|
|
1313
|
-
// [194, { name: 'green', desc: 'Green', types: ['intellibrite'], sequence: 9 }],
|
|
1314
|
-
case 194:
|
|
1315
|
-
lightTheme = GREEN;
|
|
1316
|
-
break;
|
|
1317
|
-
// [195, { name: 'red', desc: 'Red', types: ['intellibrite'], sequence: 10 }],
|
|
1318
|
-
case 195:
|
|
1319
|
-
lightTheme = RED;
|
|
1320
|
-
break;
|
|
1321
|
-
// [196, { name: 'white', desc: 'White', types: ['intellibrite'], sequence: 11 }],
|
|
1322
|
-
case 196:
|
|
1323
|
-
lightTheme = WHITE;
|
|
1324
|
-
break
|
|
1325
|
-
// [197, { name: 'magenta', desc: 'Magenta', types: ['intellibrite'], sequence: 12 }],
|
|
1326
|
-
case 197:
|
|
1327
|
-
lightTheme = MAGENTA;
|
|
1328
|
-
break
|
|
1329
|
-
// [208, { name: 'thumper', desc: 'Thumper', types: ['magicstream'] }],
|
|
1330
|
-
case 208:
|
|
1331
|
-
lightTheme = MS_THUMPER;
|
|
1332
|
-
break
|
|
1333
|
-
// [209, { name: 'hold', desc: 'Hold', types: ['magicstream'] }],
|
|
1334
|
-
case 209:
|
|
1335
|
-
lightTheme = MS_HOLD;
|
|
1336
|
-
break
|
|
1337
|
-
// [210, { name: 'reset', desc: 'Reset', types: ['magicstream'] }],
|
|
1338
|
-
case 210:
|
|
1339
|
-
lightTheme = MS_RESET;
|
|
1340
|
-
break
|
|
1341
|
-
// [211, { name: 'mode', desc: 'Mode', types: ['magicstream'] }],
|
|
1342
|
-
// [254, { name: 'unknown', desc: 'unknow
|
|
1343
|
-
default:
|
|
1344
|
-
return Promise.reject(`Screenlogic: Unknown light theme ${lightTheme}.`);
|
|
1345
|
-
}
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
let lightRes = await this._unit.circuits.sendLightCommandAsync(lightTheme);
|
|
1349
|
-
logger.silly(`Screenlogic:lightRes: ${lightRes}`);
|
|
1350
|
-
} catch (err) {
|
|
1351
|
-
return Promise.reject(err);
|
|
1352
|
-
}
|
|
1353
|
-
}
|
|
1354
|
-
}
|
|
1355
|
-
export class SLBodies extends SLCommands {
|
|
1356
|
-
public async setHeatModeAsync(body: Body, mode: number) {
|
|
1357
|
-
let htypes = sys.board.heaters.getInstalledHeaterTypes();
|
|
1358
|
-
let solarInstalled = htypes.solar > 0;
|
|
1359
|
-
let heatPumpInstalled = htypes.heatpump > 0;
|
|
1360
|
-
let ultratempInstalled = htypes.ultratemp > 0;
|
|
1361
|
-
let gasHeaterInstalled = htypes.gas > 0;
|
|
1362
|
-
let hybridInstalled = htypes.hybrid > 0;
|
|
1363
|
-
let slHeatMode = 0;
|
|
1364
|
-
switch (mode) {
|
|
1365
|
-
case 0:
|
|
1366
|
-
slHeatMode = HeatModes.HEAT_MODE_OFF;
|
|
1367
|
-
break;
|
|
1368
|
-
case 1:
|
|
1369
|
-
if (hybridInstalled) slHeatMode = HeatModes.HEAT_MODE_HEATPUMP;
|
|
1370
|
-
slHeatMode = HeatModes.HEAT_MODE_HEATER;
|
|
1371
|
-
break;
|
|
1372
|
-
case 2:
|
|
1373
|
-
if (hybridInstalled) slHeatMode = HeatModes.HEAT_MODE_HEATER;
|
|
1374
|
-
else if (solarInstalled) slHeatMode = HeatModes.HEAT_MODE_SOLARPREFERRED;
|
|
1375
|
-
break;
|
|
1376
|
-
case 3:
|
|
1377
|
-
if (hybridInstalled) slHeatMode = HeatModes.HEAT_MODE_SOLARPREFERRED; // ?? Should be heatpumppref but maybe this is the same?
|
|
1378
|
-
else if (solarInstalled) slHeatMode = HeatModes.HEAT_MODE_SOLAR;
|
|
1379
|
-
break;
|
|
1380
|
-
case 16:
|
|
1381
|
-
// ?? Should be Dual heat mode; maybe not supported on SL?
|
|
1382
|
-
break;
|
|
1383
|
-
default:
|
|
1384
|
-
logger.warn(`Screenlogic: No valid heat mode passed for ${body.name}: Mode=${mode}. `);
|
|
1385
|
-
return Promise.reject(`Screenlogic: No valid heat mode passed for ${body.name}: Mode=${mode}. `);
|
|
1386
|
-
|
|
1387
|
-
}
|
|
1388
|
-
try {
|
|
1389
|
-
await this._unit.bodies.setHeatModeAsync(body.id, slHeatMode);
|
|
1390
|
-
}
|
|
1391
|
-
catch (err) {
|
|
1392
|
-
return Promise.reject(err);
|
|
1393
|
-
}
|
|
1394
|
-
}
|
|
1395
|
-
public async setHeatSetpointAsync(body: Body, setPoint: number) {
|
|
1396
|
-
try {
|
|
1397
|
-
await this._unit.bodies.setSetPointAsync(body.id, setPoint);
|
|
1398
|
-
}
|
|
1399
|
-
catch (err) {
|
|
1400
|
-
return Promise.reject(err);
|
|
1401
|
-
}
|
|
1402
|
-
}
|
|
1403
|
-
public async setCoolSetpointAsync(body: Body, setPoint: number) {
|
|
1404
|
-
try {
|
|
1405
|
-
await this._unit.bodies.setCoolSetPointAsync(body.id, setPoint);
|
|
1406
|
-
}
|
|
1407
|
-
catch (err) {
|
|
1408
|
-
return Promise.reject(err);
|
|
1409
|
-
}
|
|
1410
|
-
}
|
|
1411
|
-
public async cancelDalayAsync() {
|
|
1412
|
-
try {
|
|
1413
|
-
await this._unit.equipment.cancelDelayAsync();
|
|
1414
|
-
} catch (err) {
|
|
1415
|
-
return Promise.reject(err);
|
|
1416
|
-
}
|
|
1417
|
-
}
|
|
1418
|
-
}
|
|
1419
|
-
export class SLCounter {
|
|
1420
|
-
constructor() {
|
|
1421
|
-
this.bytesReceived = 0;
|
|
1422
|
-
this.bytesSent = 0;
|
|
1423
|
-
}
|
|
1424
|
-
public bytesReceived: number;
|
|
1425
|
-
public bytesSent: number;
|
|
1426
|
-
public toLog(): string {
|
|
1427
|
-
return `{ "bytesReceived": ${this.bytesReceived}, "bytesSent": ${this.bytesSent} }`;
|
|
1428
|
-
}
|
|
1429
|
-
}
|
|
1430
|
-
export class SLChlor extends SLCommands {
|
|
1431
|
-
public async setChlorOutputAsync(poolSetpoint: number, spaSetpoint: number) {
|
|
1432
|
-
try {
|
|
1433
|
-
let res = await this._unit.chlor.setIntellichlorOutputAsync(poolSetpoint, spaSetpoint);
|
|
1434
|
-
if (!res) return Promise.reject(`Screenlogic: Unable to add schedule.`)
|
|
1435
|
-
} catch (err) {
|
|
1436
|
-
return Promise.reject(err);
|
|
1437
|
-
}
|
|
1438
|
-
}
|
|
1439
|
-
public async setChlorEnabledAsync(isActive: boolean) {
|
|
1440
|
-
try {
|
|
1441
|
-
let res = await this._unit.chlor.setIntellichlorIsActiveAsync(isActive);
|
|
1442
|
-
if (!res) return Promise.reject(`Screenlogic: Unable to add schedule.`)
|
|
1443
|
-
} catch (err) {
|
|
1444
|
-
return Promise.reject(err);
|
|
1445
|
-
}
|
|
1446
|
-
}
|
|
1447
|
-
}
|
|
1448
|
-
export class SLSchedule extends SLCommands {
|
|
1449
|
-
public async addScheduleAsync(type: number) {
|
|
1450
|
-
// Passed as an argument to the emitted addNewScheduleEvent event. Adds a new event to the specified schedule type, either 0 for regular events or 1 for one-time events.
|
|
1451
|
-
let slRet = this._unit.schedule.addNewScheduleEventAsync(0);
|
|
1452
|
-
return (await slRet).val;
|
|
1453
|
-
}
|
|
1454
|
-
// SCHEDULES
|
|
1455
|
-
|
|
1456
|
-
// let addSched = await client.schedule.addNewScheduleEvent(SchedTypes.RECURRING);
|
|
1457
|
-
// logger.silly(`Screenlogic:Add sched response: ${addSched}`);
|
|
1458
|
-
// let setSched = await client.schedule.setScheduleEventById(10, 2,500,1200,127,0,1,99);
|
|
1459
|
-
// logger.silly(`Screenlogic:Set sched result: ${setSched}`);
|
|
1460
|
-
// let delSched = await client.schedule.deleteScheduleEventById(10);
|
|
1461
|
-
// logger.silly(`Screenlogic:Deleted sched result: ${delSched}`);
|
|
1462
|
-
public async setScheduleAsync(id: number, circuit: number, startTime: number, endTime: number, schedDays: number, schedType: number, changeHeatSetPoint: boolean, heatSource?: number, setPoint?: number): Promise<number> {
|
|
1463
|
-
/*
|
|
1464
|
-
scheduleId - id of a schedule previously created, see SLAddNewScheduleEvent
|
|
1465
|
-
circuitId - id of the circuit to which this event applies
|
|
1466
|
-
startTime - the start time of the event, specified as minutes since midnight (see conversion functions)
|
|
1467
|
-
example: 6:00am would be 360
|
|
1468
|
-
example: 6:15am would be 375
|
|
1469
|
-
stopTime - the stop time of the event, specified as minutes since midnight (see conversion functions)
|
|
1470
|
-
dayMask
|
|
1471
|
-
7-bit mask that determines which days the schedule is active for, MSB is always 0, valid numbers 1-127
|
|
1472
|
-
flags
|
|
1473
|
-
bit 0 is the schedule type, if 0 then regular event, if 1 its a run-once
|
|
1474
|
-
bit 1 indicates whether heat setPoint should be changed
|
|
1475
|
-
heatCmd - integer indicating the desired heater mode. Valid values are:
|
|
1476
|
-
ScreenLogic.HEAT_MODE_OFF
|
|
1477
|
-
ScreenLogic.HEAT_MODE_SOLAR
|
|
1478
|
-
ScreenLogic.HEAT_MODE_SOLARPREFERRED
|
|
1479
|
-
ScreenLogic.HEAT_MODE_HEATPUMP
|
|
1480
|
-
ScreenLogic.HEAT_MODE_DONTCHANGE
|
|
1481
|
-
heatSetPoint - the temperature set point if heat is to be changed (ignored if bit 1 of flags is 0)
|
|
1482
|
-
*/
|
|
1483
|
-
try {
|
|
1484
|
-
|
|
1485
|
-
// if the id doesn't exist - we need to add a new schedule and then edit it;
|
|
1486
|
-
// this may not match our assigned id and we need to override it.
|
|
1487
|
-
let flags = 0;
|
|
1488
|
-
if (schedType === 26) {
|
|
1489
|
-
// 0 = repeat; 26 = run once
|
|
1490
|
-
flags = 1;
|
|
1491
|
-
}
|
|
1492
|
-
if (id <= 0) {
|
|
1493
|
-
id = await this.addScheduleAsync(flags);
|
|
1494
|
-
}
|
|
1495
|
-
|
|
1496
|
-
let SLheatSource = 0;
|
|
1497
|
-
if (changeHeatSetPoint) {
|
|
1498
|
-
flags = flags | (1 << 1);
|
|
1499
|
-
let htypes = sys.board.heaters.getInstalledHeaterTypes();
|
|
1500
|
-
let solarInstalled = htypes.solar > 0;
|
|
1501
|
-
let heatPumpInstalled = htypes.heatpump > 0;
|
|
1502
|
-
let ultratempInstalled = htypes.ultratemp > 0;
|
|
1503
|
-
let gasHeaterInstalled = htypes.gas > 0;
|
|
1504
|
-
let hybridInstalled = htypes.hybrid > 0;
|
|
1505
|
-
switch (heatSource) {
|
|
1506
|
-
case 0:
|
|
1507
|
-
SLheatSource = HeatModes.HEAT_MODE_OFF;
|
|
1508
|
-
break;
|
|
1509
|
-
case 3:
|
|
1510
|
-
if (hybridInstalled) SLheatSource = HeatModes.HEAT_MODE_HEATPUMP;
|
|
1511
|
-
SLheatSource = HeatModes.HEAT_MODE_HEATER;
|
|
1512
|
-
break;
|
|
1513
|
-
case 5:
|
|
1514
|
-
if (hybridInstalled) SLheatSource = HeatModes.HEAT_MODE_SOLARPREFERRED; // ?? Should be heatpumppref but maybe this is the same?
|
|
1515
|
-
else if (solarInstalled) SLheatSource = HeatModes.HEAT_MODE_SOLAR;
|
|
1516
|
-
break;
|
|
1517
|
-
case 21:
|
|
1518
|
-
if (hybridInstalled) SLheatSource = HeatModes.HEAT_MODE_HEATER;
|
|
1519
|
-
else if (solarInstalled) SLheatSource = HeatModes.HEAT_MODE_SOLARPREFERRED;
|
|
1520
|
-
break;
|
|
1521
|
-
case 32:
|
|
1522
|
-
// No change
|
|
1523
|
-
SLheatSource = HeatModes.HEAT_MODE_DONTCHANGE;
|
|
1524
|
-
break;
|
|
1525
|
-
default:
|
|
1526
|
-
logger.warn(`Screenlogic: No valid heat source passed for schedule: ${id}, heat source: ${heatSource}. `);
|
|
1527
|
-
SLheatSource = 0;
|
|
1528
|
-
flags = 1;
|
|
1529
|
-
}
|
|
1530
|
-
}
|
|
1531
|
-
await this._unit.schedule.setScheduleEventByIdAsync(id, circuit, startTime, endTime, schedDays, flags, SLheatSource, setPoint);
|
|
1532
|
-
return id;
|
|
1533
|
-
} catch (err) {
|
|
1534
|
-
logger.error(`Screenlogic: Error setting schedule ${id}`)
|
|
1535
|
-
}
|
|
1536
|
-
}
|
|
1537
|
-
public async deleteScheduleAsync(id: number) {
|
|
1538
|
-
try {
|
|
1539
|
-
await this._unit.schedule.deleteScheduleEventByIdAsync(id);
|
|
1540
|
-
} catch (err) {
|
|
1541
|
-
return Promise.reject(err);
|
|
1542
|
-
}
|
|
1543
|
-
}
|
|
1544
|
-
public async setEggTimerAsync(id: number, runTime: number) {
|
|
1545
|
-
try {
|
|
1546
|
-
await this._unit.circuits.setCircuitRuntimebyIdAsync(id, runTime);
|
|
1547
|
-
} catch (err) {
|
|
1548
|
-
return Promise.reject(err);
|
|
1549
|
-
}
|
|
1550
|
-
}
|
|
1551
|
-
public async deleteEggTimerAsync(id: number) {
|
|
1552
|
-
try {
|
|
1553
|
-
await this._unit.circuits.setCircuitRuntimebyIdAsync(id, 720);
|
|
1554
|
-
} catch (err) {
|
|
1555
|
-
return Promise.reject(err);
|
|
1556
|
-
}
|
|
1557
|
-
}
|
|
1558
|
-
}
|
|
1559
|
-
export class SLPump extends SLCommands {
|
|
1560
|
-
public async setPumpSpeedAsync(pump: Pump, circuit: PumpCircuit) {
|
|
1561
|
-
|
|
1562
|
-
// PUMPS
|
|
1563
|
-
// let pumpRes = await client.pump.setPumpSpeed(0,1,2000,true);
|
|
1564
|
-
// Currently, this only sets the pump circuit speed. Adding/removing pump needs to be
|
|
1565
|
-
// done through the equipment configuration message.
|
|
1566
|
-
|
|
1567
|
-
// This API call is indexed based.
|
|
1568
|
-
let pumpCircuits = pump.circuits.get();
|
|
1569
|
-
for (let i = 0; i < pumpCircuits.length; i++) {
|
|
1570
|
-
if (pumpCircuits[i].circuit === circuit.circuit) {
|
|
1571
|
-
let res = await this._unit.pump.setPumpSpeedAsync(pump.id, i, circuit.speed || circuit.flow, (circuit.speed || circuit.flow) > 400);
|
|
1572
|
-
if (res) {
|
|
1573
|
-
let pc = pump.circuits.getItemByIndex(i);
|
|
1574
|
-
pc.speed = typeof circuit.speed !== 'undefined' ? circuit.speed : pc.speed;
|
|
1575
|
-
pc.flow = typeof circuit.flow !== 'undefined' ? circuit.flow : pc.flow;
|
|
1576
|
-
return Promise.resolve(pump);
|
|
1577
|
-
}
|
|
1578
|
-
else {
|
|
1579
|
-
return Promise.reject(new InvalidEquipmentDataError('Unable to set pump speed', 'pump', pump))
|
|
1580
|
-
};
|
|
1581
|
-
}
|
|
1582
|
-
}
|
|
1583
|
-
return Promise.reject(new InvalidEquipmentDataError('Unable to set pump speed. Circuit not found', 'pump', pump));
|
|
1584
|
-
|
|
1585
|
-
}
|
|
1586
|
-
}
|
|
1587
|
-
export class SLController extends SLCommands {
|
|
1588
|
-
public async setEquipmentAsync(obj?: any, eq?: string) {
|
|
1589
|
-
|
|
1590
|
-
let poolPumpOnDuringHeaterCooldown = false;
|
|
1591
|
-
let spaPumpOnDuringHeaterCooldown = false;
|
|
1592
|
-
for (let i = 0; i < sys.bodies.length; i++) {
|
|
1593
|
-
let bs = state.temps.bodies.getItemById(i + 1);
|
|
1594
|
-
if (bs.circuit === 1) spaPumpOnDuringHeaterCooldown = bs.heaterCooldownDelay;
|
|
1595
|
-
else if (bs.circuit === 6) poolPumpOnDuringHeaterCooldown = bs.heaterCooldownDelay;
|
|
1596
|
-
}
|
|
1597
|
-
|
|
1598
|
-
let highSpeedCircuits = sys.pumps.getDualSpeed().circuits.toArray();
|
|
1599
|
-
let valves = sys.valves.get(true);
|
|
1600
|
-
let remotes = sys.remotes.get(true);
|
|
1601
|
-
let heaters = sys.heaters.get(true);
|
|
1602
|
-
let misc = { ...sys.general.options.get(), poolPumpOnDuringHeaterCooldown, spaPumpOnDuringHeaterCooldown, intellichem: state.chemControllers.getItemById(1, false).isActive || false };
|
|
1603
|
-
let circuitGroup = sys.circuitGroups.get(true);
|
|
1604
|
-
let lightGroup = sys.lightGroups.get(true)[0];
|
|
1605
|
-
let pumps = sys.pumps.get(true);
|
|
1606
|
-
const spaCommand: Remote = sys.remotes.getItemById(8).get();
|
|
1607
|
-
let alarm = 0;
|
|
1608
|
-
|
|
1609
|
-
switch (eq) {
|
|
1610
|
-
case 'misc': {
|
|
1611
|
-
misc = extend({}, true, misc, obj);
|
|
1612
|
-
break;
|
|
1613
|
-
}
|
|
1614
|
-
case 'lightGroup': {
|
|
1615
|
-
lightGroup = extend({}, true, lightGroup, obj);
|
|
1616
|
-
break;
|
|
1617
|
-
}
|
|
1618
|
-
case 'pump': {
|
|
1619
|
-
let idx = pumps.findIndex(el => { console.log(el.id); return el.id === obj.id; })
|
|
1620
|
-
if (idx >= 0) pumps = extend({}, true, pumps[idx], obj);
|
|
1621
|
-
else return Promise.reject(`Screenlogic: No pump found by that id: ${obj}`);
|
|
1622
|
-
break;
|
|
1623
|
-
}
|
|
1624
|
-
case 'heater': {
|
|
1625
|
-
let idx = heaters.findIndex(el => { console.log(el.id); return el.id === obj.id; })
|
|
1626
|
-
if (idx >= 0) heaters = extend({}, true, heaters[idx], obj);
|
|
1627
|
-
else return Promise.reject(`Screenlogic: No pump found by that id: ${obj}`);
|
|
1628
|
-
break;
|
|
1629
|
-
}
|
|
1630
|
-
}
|
|
1631
|
-
|
|
1632
|
-
let data = {
|
|
1633
|
-
highSpeedCircuits,
|
|
1634
|
-
valves,
|
|
1635
|
-
remotes,
|
|
1636
|
-
heaters,
|
|
1637
|
-
misc,
|
|
1638
|
-
circuitGroup,
|
|
1639
|
-
lightGroup,
|
|
1640
|
-
pumps,
|
|
1641
|
-
spaCommand,
|
|
1642
|
-
alarm
|
|
1643
|
-
}
|
|
1644
|
-
return Promise.reject(new InvalidOperationError('Operation not implemented yet.', 'setEquipmentConfigurationAsync'));
|
|
1645
|
-
// await this._unit.equipment.setEquipmentConfigurationAsync(data);
|
|
1646
|
-
}
|
|
1647
|
-
|
|
1648
|
-
public async setSystemTime() {
|
|
1649
|
-
try {
|
|
1650
|
-
let sysTime = await this._unit.equipment.setSystemTimeAsync(state.time.toDate(), sys.general.options.adjustDST);
|
|
1651
|
-
logger.silly(`Screenlogic:set time result: ${sysTime}`);
|
|
1652
|
-
} catch (error) {
|
|
1653
|
-
return Promise.reject(new InvalidOperationError('Unable to set system time.', error.message));
|
|
1654
|
-
}
|
|
1655
|
-
}
|
|
1656
|
-
public async setCustomName(idx: number, name: string) {
|
|
1657
|
-
try {
|
|
1658
|
-
let ack = await this._unit.equipment.setCustomNameAsync(idx, name);
|
|
1659
|
-
logger.silly(`Screenlogic:set custom name result: ${JSON.stringify(ack)}`);
|
|
1660
|
-
} catch (error) {
|
|
1661
|
-
return Promise.reject(new InvalidOperationError('Unable to set custom name.', error.message));
|
|
1662
|
-
}
|
|
1663
|
-
}
|
|
1664
|
-
}
|
|
1665
|
-
export let sl = new ScreenLogicComms();
|
|
1
|
+
import { ControllerType, Timestamp, Utils, utils } from '../../controller/Constants';
|
|
2
|
+
import { LightGroup, LightGroupCircuit, sys, Valve, Body, Pump, PumpCircuit, Remote } from '../../controller/Equipment';
|
|
3
|
+
import { CircuitState, state, ValveState } from '../../controller/State';
|
|
4
|
+
import { RemoteLogin, UnitConnection, FindUnits, SLEquipmentStateData, SLIntellichlorData, SLPumpStatusData, SLScheduleData, SLSystemTimeData, HeatModes, SLControllerConfigData, SLEquipmentConfigurationData, HeaterConfig, Valves, SLChemData, SLGetCustomNamesData } from 'node-screenlogic';
|
|
5
|
+
import * as Screenlogic from 'node-screenlogic';
|
|
6
|
+
import { EasyTouchBoard } from '../../controller/boards/EasyTouchBoard';
|
|
7
|
+
import { IntelliTouchBoard } from '../../controller/boards/IntelliTouchBoard';
|
|
8
|
+
import { logger } from '../../logger/Logger';
|
|
9
|
+
import { webApp } from '../../web/Server';
|
|
10
|
+
import { delayMgr } from '../../controller/Lockouts';
|
|
11
|
+
import { config } from '../../config/Config';
|
|
12
|
+
import { InvalidEquipmentDataError, InvalidEquipmentIdError, InvalidOperationError } from '../../controller/Errors';
|
|
13
|
+
import extend = require('extend');
|
|
14
|
+
import { Message } from './messages/Messages';
|
|
15
|
+
|
|
16
|
+
export class ScreenLogicComms {
|
|
17
|
+
constructor() {
|
|
18
|
+
this._client = new Screenlogic.UnitConnection();
|
|
19
|
+
};
|
|
20
|
+
public a: SLChemData;
|
|
21
|
+
public counter: SLCounter = new SLCounter();
|
|
22
|
+
private _gateway: RemoteLogin;
|
|
23
|
+
private _client: UnitConnection;
|
|
24
|
+
private _pollTimer: NodeJS.Timeout;
|
|
25
|
+
public circuits: SLCircuits;
|
|
26
|
+
public bodies: SLBodies;
|
|
27
|
+
public chlor: SLChlor;
|
|
28
|
+
public schedules: SLSchedule;
|
|
29
|
+
public pumps: SLPump;
|
|
30
|
+
public controller: SLController;
|
|
31
|
+
private _pollCountError: number = 0;
|
|
32
|
+
public isOpen: boolean = false;
|
|
33
|
+
private _cfg: any;
|
|
34
|
+
private _configData: { pumpsReported: number[], intellichemPresent: boolean };
|
|
35
|
+
private pollingInterval = 10000;
|
|
36
|
+
public enabled: boolean = false;
|
|
37
|
+
|
|
38
|
+
public eqConfig: any; // testing purposes
|
|
39
|
+
|
|
40
|
+
public async openAsync() {
|
|
41
|
+
let self = this;
|
|
42
|
+
this.circuits = new SLCircuits(this._client);
|
|
43
|
+
this.bodies = new SLBodies(this._client);
|
|
44
|
+
this.chlor = new SLChlor(this._client);
|
|
45
|
+
this.schedules = new SLSchedule(this._client);
|
|
46
|
+
this.pumps = new SLPump(this._client);
|
|
47
|
+
this.controller = new SLController(this._client);
|
|
48
|
+
let cfg = config.getSection('controller.comms');
|
|
49
|
+
if (typeof cfg !== 'undefined') this._cfg = cfg;
|
|
50
|
+
this.enabled = this._cfg.enabled && this._cfg.type === 'screenlogic';
|
|
51
|
+
if (!this._cfg.enabled || this._cfg.type !== 'screenlogic') {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
let systemName = this._cfg.screenlogic.systemName; // 'Pentair: 00-00-00';
|
|
55
|
+
let password = this._cfg.screenlogic.password.toString(); // '1111';
|
|
56
|
+
|
|
57
|
+
this._gateway = new RemoteLogin(systemName);
|
|
58
|
+
this._gateway.on('error', async (err) => {
|
|
59
|
+
logger.error(`Screenlogic Gateway Error: ${err.message}`);
|
|
60
|
+
this.isOpen = false;
|
|
61
|
+
await this._gateway.closeAsync();
|
|
62
|
+
return Promise.resolve(false);
|
|
63
|
+
})
|
|
64
|
+
let unit = await this._gateway.connectAsync();
|
|
65
|
+
|
|
66
|
+
if (!unit || !unit.gatewayFound || unit.ipAddr === '') {
|
|
67
|
+
logger.error(`Screenlogic: No unit found called ${systemName}`);
|
|
68
|
+
this.isOpen = false;
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
await this._gateway.closeAsync();
|
|
72
|
+
this.isOpen = true;
|
|
73
|
+
logger.info(`Screenlogic: Unit ${this._gateway.systemName} found at ${unit.ipAddr}:${unit.port}`);
|
|
74
|
+
|
|
75
|
+
let delayCount = 0;
|
|
76
|
+
state.status = sys.board.valueMaps.controllerStatus.transform(0, 0);
|
|
77
|
+
state.emitControllerChange();
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
try {
|
|
81
|
+
this._client.init(systemName, unit.ipAddr, unit.port, password);
|
|
82
|
+
await this._client.connectAsync();
|
|
83
|
+
this._client.removeAllListeners(); // clear out in case we are initializing again
|
|
84
|
+
this._client.on('slLogMessage', (msg) => {
|
|
85
|
+
let _id = Message.nextMessageId;
|
|
86
|
+
msg = { ...msg, _id };
|
|
87
|
+
logger.screenlogic(msg);
|
|
88
|
+
})
|
|
89
|
+
let ver = await this._client.getVersionAsync();
|
|
90
|
+
logger.info(`Screenlogic: connect to ${systemName} ${ver.version} at ${unit.ipAddr}:${unit.port}`);
|
|
91
|
+
|
|
92
|
+
let addClient = await this._client.addClientAsync();
|
|
93
|
+
logger.silly(`Screenlogic:Add client result: ${addClient}`);
|
|
94
|
+
} catch (err) {
|
|
95
|
+
throw err;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
state.status = sys.board.valueMaps.controllerStatus.transform(2, 12);
|
|
99
|
+
state.emitControllerChange();
|
|
100
|
+
try {
|
|
101
|
+
let equipConfig = await this._client.equipment.getEquipmentConfigurationAsync();
|
|
102
|
+
logger.silly(`Screenlogic: Equipment config: ${JSON.stringify(equipConfig, null, 2)}`);
|
|
103
|
+
await Controller.decodeEquipmentAsync(equipConfig);
|
|
104
|
+
} catch (err) {
|
|
105
|
+
logger.error(`Screenlogic: Error getting equipment configuration. ${err.message}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
state.status = sys.board.valueMaps.controllerStatus.transform(2, 24);
|
|
109
|
+
state.emitControllerChange();
|
|
110
|
+
try {
|
|
111
|
+
|
|
112
|
+
let customNames = await this._client.equipment.getCustomNamesAsync();
|
|
113
|
+
logger.silly(`Screenlogic: custom names ${customNames}`);
|
|
114
|
+
await Controller.decodeCustomNames(customNames);
|
|
115
|
+
} catch (err) {
|
|
116
|
+
logger.error(`Screenlogic: Error getting custom names. ${err.message}`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
state.status = sys.board.valueMaps.controllerStatus.transform(2, 36);
|
|
120
|
+
state.emitControllerChange();
|
|
121
|
+
try {
|
|
122
|
+
let controller = await this._client.equipment.getControllerConfigAsync();
|
|
123
|
+
logger.silly(`Screenlogic:Controller: ${JSON.stringify(controller, null, 2)}`);
|
|
124
|
+
this._configData = await Controller.decodeController(controller);
|
|
125
|
+
} catch (err) {
|
|
126
|
+
logger.error(`Screenlogic: Error getting controller configuration. ${err.message}`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
state.status = sys.board.valueMaps.controllerStatus.transform(2, 48);
|
|
130
|
+
state.emitControllerChange();
|
|
131
|
+
try {
|
|
132
|
+
let systemTime = await this._client.equipment.getSystemTimeAsync();
|
|
133
|
+
// logger.silly(`Screenlogic:System Time: ${JSON.stringify(systemTime)}`)
|
|
134
|
+
Controller.decodeDateTime(systemTime);
|
|
135
|
+
} catch (err) {
|
|
136
|
+
logger.error(`Screenlogic: Error getting system time. ${err.message}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// PUMPS
|
|
140
|
+
state.status = sys.board.valueMaps.controllerStatus.transform(2, 60);
|
|
141
|
+
state.emitControllerChange();
|
|
142
|
+
this._configData.pumpsReported.forEach(async pumpNum => {
|
|
143
|
+
try {
|
|
144
|
+
let pumpStatus = await this._client.pump.getPumpStatusAsync(pumpNum);
|
|
145
|
+
logger.silly(`Screenlogic:Pump ${pumpNum}: ${JSON.stringify(pumpStatus)}`);
|
|
146
|
+
await Controller.decodePumpStatusAsync(pumpNum, pumpStatus);
|
|
147
|
+
} catch (err) {
|
|
148
|
+
logger.error(`Screenlogic: Error getting pump configuration. ${err.message}`);
|
|
149
|
+
}
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
state.status = sys.board.valueMaps.controllerStatus.transform(2, 72);
|
|
153
|
+
state.emitControllerChange();
|
|
154
|
+
try {
|
|
155
|
+
let recurringSched = await this._client.schedule.getScheduleDataAsync(0);
|
|
156
|
+
logger.silly(`Screenlogic:reccuring schedules: ${JSON.stringify(recurringSched)}`);
|
|
157
|
+
let runOnceSched = await this._client.schedule.getScheduleDataAsync(1);
|
|
158
|
+
logger.silly(`Screenlogic:Run once schedules: ${JSON.stringify(runOnceSched)}`);
|
|
159
|
+
await Controller.decodeSchedules(recurringSched, runOnceSched);
|
|
160
|
+
} catch (err) {
|
|
161
|
+
logger.error(`Screenlogic: Error getting schedules. ${err.message}`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
state.status = sys.board.valueMaps.controllerStatus.transform(2, 84);
|
|
165
|
+
state.emitControllerChange();
|
|
166
|
+
try {
|
|
167
|
+
let intellichlor = await this._client.chlor.getIntellichlorConfigAsync();
|
|
168
|
+
// logger.silly(`Screenlogic:Intellichlor: ${JSON.stringify(intellichlor)}`);
|
|
169
|
+
await Controller.decodeIntellichlorAsync(intellichlor);
|
|
170
|
+
} catch (err) {
|
|
171
|
+
logger.error(`Screenlogic: Error getting Intellichlor. ${err.message}`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
state.status = sys.board.valueMaps.controllerStatus.transform(2, 95);
|
|
175
|
+
state.emitControllerChange();
|
|
176
|
+
try {
|
|
177
|
+
if (this._configData.intellichemPresent) {
|
|
178
|
+
let chem = await this._client.chem.getChemicalDataAsync();
|
|
179
|
+
logger.silly(`Screenlogic:Chem data: ${JSON.stringify(chem)}`);
|
|
180
|
+
await Controller.decodeChemController(chem);
|
|
181
|
+
}
|
|
182
|
+
} catch (err) {
|
|
183
|
+
logger.error(`Screenlogic: Error getting Intellichem. ${err.message}`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
state.status = sys.board.valueMaps.controllerStatus.transform(2, 98);
|
|
187
|
+
state.emitControllerChange();
|
|
188
|
+
try {
|
|
189
|
+
let equipmentState = await this._client.equipment.getEquipmentStateAsync();
|
|
190
|
+
logger.silly(`Screenlogic: equipment state: ${JSON.stringify(equipmentState)}`);
|
|
191
|
+
await Controller.decodeEquipmentState(equipmentState);
|
|
192
|
+
} catch (err) {
|
|
193
|
+
logger.error(`Screenlogic: Error getting equipment state. ${err.message}`);
|
|
194
|
+
}
|
|
195
|
+
sys.board.circuits.syncVirtualCircuitStates()
|
|
196
|
+
state.status = sys.board.valueMaps.controllerStatus.transform(1, 100);
|
|
197
|
+
state.emitControllerChange();
|
|
198
|
+
|
|
199
|
+
this._client.on('equipmentState', async function (data) { await Controller.decodeEquipmentState(data); })
|
|
200
|
+
this._client.on('intellichlorConfig', async function (data) {
|
|
201
|
+
await Controller.decodeIntellichlorAsync(data);
|
|
202
|
+
});
|
|
203
|
+
this._client.on('equipmentConfig', async function (data) {
|
|
204
|
+
await Controller.decodeController(data);
|
|
205
|
+
});
|
|
206
|
+
this._client.on('chemicalData', async function (data) {
|
|
207
|
+
await Controller.decodeChemController(data);
|
|
208
|
+
|
|
209
|
+
});
|
|
210
|
+
this._client.on('getSystemTime', async function (data) {
|
|
211
|
+
Controller.decodeDateTime(data);
|
|
212
|
+
});
|
|
213
|
+
// client.on('getScheduleData', async function(){
|
|
214
|
+
// await Controller.decodeSchedules(recurringSched, runOnceSched);}); // how do we know if this is recurring or runonce? Investigate.
|
|
215
|
+
this._client.on('cancelDelay', async function (data) {
|
|
216
|
+
logger.silly(`Screenlogic:cancelDelay: ${data}`)
|
|
217
|
+
}) // not programmed yet});
|
|
218
|
+
this._client.on('equipmentConfiguration', async function (data) {
|
|
219
|
+
logger.silly(`Screenlogic:equipConfig ${JSON.stringify(data)}`)
|
|
220
|
+
})// which one?});
|
|
221
|
+
this._client.on('getPumpStatus', async function (data) {
|
|
222
|
+
logger.silly(`Screenlogic:getPumpStatus: ${JSON.stringify(data)}`);
|
|
223
|
+
// await Controller.decodePump(1, pumpStatus);
|
|
224
|
+
}); // how do we know which pump id? Investigate.
|
|
225
|
+
this._client.on('weatherForecast', async function (data) {
|
|
226
|
+
logger.silly(`Screenlogic:weatherforecast: ${JSON.stringify(data)}`)
|
|
227
|
+
});
|
|
228
|
+
this._client.on('circuitStateChanged', async function (data) {
|
|
229
|
+
logger.silly(`Screenlogic:circuitstatechanged: ${JSON.stringify(data)}`)
|
|
230
|
+
});
|
|
231
|
+
this._client.on('setPointChanged', async function (data) {
|
|
232
|
+
logger.silly(`Screenlogic:setpointchanged: ${JSON.stringify(data)}`)
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// not working
|
|
236
|
+
|
|
237
|
+
this._client.on('heatModeChanged', async function (data) {
|
|
238
|
+
logger.silly(`Screenlogic:heat mode changed: ${JSON.stringify(data)}`);
|
|
239
|
+
});
|
|
240
|
+
this._client.on('intellibriteDelay', async function (data) {
|
|
241
|
+
logger.silly(`Screenlogic:intellibrite delay: ${JSON.stringify(data)}`)
|
|
242
|
+
});
|
|
243
|
+
this._client.on('weatherForecastChanged', async function () {
|
|
244
|
+
logger.silly(`Screenlogic:weather forecast changed}`);
|
|
245
|
+
// found - no data returned; need to request data
|
|
246
|
+
});
|
|
247
|
+
// No data comes through... maybe need to request weather data again?
|
|
248
|
+
this._client.on('scheduleChanged', async function (data) {
|
|
249
|
+
logger.silly(`Screenlogic:schedule changed: ${JSON.stringify(data)}`);
|
|
250
|
+
let recurringSched = await self._client.schedule.getScheduleDataAsync(0);
|
|
251
|
+
logger.silly(`Screenlogic:reccuring schedules: ${JSON.stringify(recurringSched)}`);
|
|
252
|
+
|
|
253
|
+
let runOnceSched = await self._client.schedule.getScheduleDataAsync(1);
|
|
254
|
+
logger.silly(`Screenlogic:Run once schedules: ${JSON.stringify(runOnceSched)}`);
|
|
255
|
+
await Controller.decodeSchedules(recurringSched, runOnceSched);
|
|
256
|
+
});
|
|
257
|
+
this._client.on('setCircuitRuntimebyId', async (data) => {
|
|
258
|
+
logger.silly(`Screenlogic:Set Circuit By Runtime event ${data}`);
|
|
259
|
+
await self._client.equipment.getControllerConfigAsync();
|
|
260
|
+
});
|
|
261
|
+
// this._client.on('error', async (e) => {
|
|
262
|
+
// // if the error event from the net.socket isn't caught, it sometimes crashes the app.
|
|
263
|
+
// logger.error(`Screenlogic error (net.socket): ${e.message}`);
|
|
264
|
+
// if (e.code === 'ECONNRESET') {
|
|
265
|
+
// try {
|
|
266
|
+
// logger.info(`Screenlogic net.socket timeout. Restarting.`)
|
|
267
|
+
// await self.stopAsync();
|
|
268
|
+
// await self.initAsync();
|
|
269
|
+
// }
|
|
270
|
+
// catch (err) {
|
|
271
|
+
// logger.error(`Error trying to reset Screenlogic comms. ${err.message}`);
|
|
272
|
+
// };
|
|
273
|
+
// }
|
|
274
|
+
// })
|
|
275
|
+
// this._client.on('clientError', (e) => {
|
|
276
|
+
// // if the error event from the net.socket isn't caught, it sometimes crashes the app.
|
|
277
|
+
// logger.error(`Screenlogic client error (net.socket): ${e.message}`);
|
|
278
|
+
// })
|
|
279
|
+
this._client.on('loginFailed', (data) => {
|
|
280
|
+
logger.error(`Screenlogic login failed. Invalid password.`);
|
|
281
|
+
this.isOpen = false;
|
|
282
|
+
})
|
|
283
|
+
this._client.on('bytesRead', (bytes) => {
|
|
284
|
+
logger.silly(`Screenlogic:SL Bytes Read: ${bytes}`);
|
|
285
|
+
this.counter.bytesReceived += bytes;
|
|
286
|
+
this.emitScreenlogicStats();
|
|
287
|
+
});
|
|
288
|
+
this._client.on('bytesWritten', (bytes) => {
|
|
289
|
+
logger.silly(`Screenlogic:SL Bytes written: ${bytes}`);
|
|
290
|
+
this.counter.bytesSent += bytes;
|
|
291
|
+
this.emitScreenlogicStats();
|
|
292
|
+
});
|
|
293
|
+
this.pollAsync();
|
|
294
|
+
// logger.silly(`Screenlogic:Equipment State: ${JSON.stringify(equipmentState, null, 2)}`);
|
|
295
|
+
/* // EQUIPMENT
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
let weatherForecast = await client.equipment.getWeatherForecast();
|
|
300
|
+
logger.silly(`Screenlogic:Weather: ${JSON.stringify(weatherForecast)}`);
|
|
301
|
+
|
|
302
|
+
let hist = await screenlogic.equipment.getHistoryData()
|
|
303
|
+
logger.silly(`Screenlogic:history data: ${JSON.stringify(hist)}`)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
// CHEM
|
|
307
|
+
let chemHist = await screenlogic.chem.getChemHistoryData()
|
|
308
|
+
logger.silly(`Screenlogic:history data: ${JSON.stringify(chemHist)}`)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
*/
|
|
313
|
+
// setTimeout(async () => {
|
|
314
|
+
// logger.silly(`Screenlogic:closing connection after 60s`);
|
|
315
|
+
// await client.closeAsync();
|
|
316
|
+
// }, 120 * 1000)
|
|
317
|
+
// let close = await client.closeAsync();
|
|
318
|
+
// logger.silly(`Screenlogic:client closed: ${close}`);
|
|
319
|
+
} catch (error) {
|
|
320
|
+
logger.error(`Screenlogic error: ${error.message}`);
|
|
321
|
+
await this._client.closeAsync();
|
|
322
|
+
return Promise.resolve(error);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
public async closeAsync() {
|
|
326
|
+
await this._client.closeAsync();
|
|
327
|
+
this._client.removeAllListeners();
|
|
328
|
+
this.isOpen = false;
|
|
329
|
+
this.enabled = false;
|
|
330
|
+
if (typeof this._pollTimer !== 'undefined') clearTimeout(this._pollTimer);
|
|
331
|
+
this._pollTimer = null;
|
|
332
|
+
}
|
|
333
|
+
/* public async setScreenlogicAsync(data) {
|
|
334
|
+
let enabled = typeof data.enabled !== 'undefined' ? utils.makeBool(data.enabled) : false;
|
|
335
|
+
let systemName = typeof data.systemName !== 'undefined' ? data.systemName : this._cfg.systemName;
|
|
336
|
+
let password = typeof data.password !== 'undefined' ? data.password.toString() : this._cfg.password;
|
|
337
|
+
let regx = /Pentair: (?:(?:\d|[A-Z])(?:\d|[A-Z])-){2}(?:\d|[A-Z])(?:\d|[A-Z])/g;
|
|
338
|
+
let type = typeof data.connectionType !== 'undefined' ? data.connectionType : this._cfg.connectionType;
|
|
339
|
+
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));
|
|
340
|
+
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));
|
|
341
|
+
if (password.length > 4) return Promise.reject(new InvalidEquipmentDataError(`An invalid password was supplied for Screenlogic ${password}. (Length must be <= 4)}`, 'Screenlogic', data));
|
|
342
|
+
this.enabled = enabled;
|
|
343
|
+
if (this._cfg.enabled && !enabled || this._cfg.systemName !== systemName || this._cfg.password !== password || this._cfg.cype !== type) {
|
|
344
|
+
await this.closeAsync();
|
|
345
|
+
}
|
|
346
|
+
let obj = {
|
|
347
|
+
enabled,
|
|
348
|
+
type,
|
|
349
|
+
systemName,
|
|
350
|
+
password
|
|
351
|
+
}
|
|
352
|
+
config.setSection('controller.screenlogic', obj);
|
|
353
|
+
this._cfg = config.getSection('controller.screenlogic');
|
|
354
|
+
if (this._cfg.enabled) {
|
|
355
|
+
let error = await this.openAsync();
|
|
356
|
+
if (typeof error !== 'undefined') return Promise.reject(error);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
} */
|
|
360
|
+
public async pollAsync() {
|
|
361
|
+
let self = this;
|
|
362
|
+
try {
|
|
363
|
+
if (typeof this._pollTimer !== 'undefined' || this._pollTimer) clearTimeout(this._pollTimer);
|
|
364
|
+
this._pollTimer = null;
|
|
365
|
+
if (!this.isOpen) { return; };
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
/*
|
|
369
|
+
// Uncomment this block to do a comparison of the 'getConfig' packets.
|
|
370
|
+
// RSG used this to recreate the setConfig packet arrays
|
|
371
|
+
let equipConfig = await this._client.equipment.getEquipmentConfigurationAsync();
|
|
372
|
+
logger.silly(`Screenlogic: Equipment config: ${JSON.stringify(equipConfig, null, 2)}`);
|
|
373
|
+
|
|
374
|
+
if (typeof this.eqConfig === 'undefined') this.eqConfig = equipConfig;
|
|
375
|
+
// let's compare so we can find differences easily
|
|
376
|
+
for (const [key, value] of Object.entries(this.eqConfig.rawData)) {
|
|
377
|
+
console.log(key);
|
|
378
|
+
for (let i = 0; i < this.eqConfig.rawData[key].length; i++) {
|
|
379
|
+
if (this.eqConfig.rawData[key][i] !== equipConfig.rawData[key][i]) {
|
|
380
|
+
console.log(`Difference at ${key}[${i}]. prev: ${this.eqConfig.rawData[key][i]} (${utils.dec2bin(this.eqConfig.rawData[key][i])})-> new: ${equipConfig.rawData[key][i]} (${utils.dec2bin(equipConfig.rawData[key][i])})`)
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
this.eqConfig = equipConfig;
|
|
386
|
+
*/
|
|
387
|
+
|
|
388
|
+
let pumps = sys.pumps.get();
|
|
389
|
+
let numPumps = pumps.length;
|
|
390
|
+
for (let i = 1; i < numPumps + 1; i++) {
|
|
391
|
+
if (pumps[i - 1].id === 10) continue; // skip dual speed
|
|
392
|
+
let pumpStatus = await self._client.pump.getPumpStatusAsync(i);
|
|
393
|
+
logger.silly(`Screenlogic:Pump ${i}: ${JSON.stringify(pumpStatus)}`);
|
|
394
|
+
await Controller.decodePumpStatusAsync(i, pumpStatus);
|
|
395
|
+
}
|
|
396
|
+
sys.board.heaters.syncHeaterStates();
|
|
397
|
+
sys.board.schedules.syncScheduleStates();
|
|
398
|
+
sys.board.circuits.syncVirtualCircuitStates();
|
|
399
|
+
}
|
|
400
|
+
catch (err) {
|
|
401
|
+
logger.error(`Error polling screenlogic (${this._pollCountError} errors)- ${err}`); this._pollCountError++;
|
|
402
|
+
/* if (this._pollCountError > 3) {
|
|
403
|
+
await this.initAsync();
|
|
404
|
+
} */
|
|
405
|
+
}
|
|
406
|
+
finally { this._pollTimer = setTimeout(async () => await self.pollAsync(), this.pollingInterval || 10000); }
|
|
407
|
+
}
|
|
408
|
+
public static async searchAsync() {
|
|
409
|
+
try {
|
|
410
|
+
let finder = new FindUnits();
|
|
411
|
+
let localUnits = await finder.searchAsync();
|
|
412
|
+
finder.close();
|
|
413
|
+
return Promise.resolve(localUnits);
|
|
414
|
+
}
|
|
415
|
+
catch (err) {
|
|
416
|
+
logger.error(`Screenlogic: Error searching for units: ${err.message}`);
|
|
417
|
+
return Promise.reject(err);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
public get stats() {
|
|
421
|
+
let status = this.isOpen ? 'open' : this._cfg.enabled ? 'closed' : 'disabled';
|
|
422
|
+
let socketStatus = this._client.status();
|
|
423
|
+
return extend(true, { status: status }, this.counter, socketStatus);
|
|
424
|
+
}
|
|
425
|
+
public emitScreenlogicStats() {
|
|
426
|
+
webApp.emitToChannel('screenlogicStats', 'screenlogicStats', this.stats);
|
|
427
|
+
}
|
|
428
|
+
public toLog(msg): string {
|
|
429
|
+
return `{"systemName":"${msg.systemName}","dir":"${msg.dir}","protocol":"${msg.protocol}", "_id": ${msg._id}, "action": ${msg.action}, "payload":[${JSON.stringify(msg.payload)}],"ts":"${Timestamp.toISOLocal(new Date())}"}`;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
class Controller {
|
|
434
|
+
public static async decodeController(config: SLControllerConfigData) {
|
|
435
|
+
sys.general.options.units = state.temps.units = config.degC ? sys.board.valueMaps.tempUnits.getValue('C') : sys.board.valueMaps.tempUnits.getValue('F');
|
|
436
|
+
let lightGroup: any = { circuits: [] };
|
|
437
|
+
let lgCircId = 1;
|
|
438
|
+
for (let i = 0; i < config.circuitArray.length; i++) {
|
|
439
|
+
let _circ = config.circuitArray[i];
|
|
440
|
+
let circuit = sys.circuits.getInterfaceById(_circ.circuitId);
|
|
441
|
+
let data: any = {
|
|
442
|
+
id: _circ.circuitId,
|
|
443
|
+
type: _circ.function,
|
|
444
|
+
nameId: _circ.nameIndex,
|
|
445
|
+
freeze: _circ.freeze,
|
|
446
|
+
eggTimer: _circ.eggTimer,
|
|
447
|
+
// 0 = pool; 1 = spa; 2 = features; 4 = lights; 5 = hide
|
|
448
|
+
showInFeatures: typeof circuit.showInFeatures !== 'undefined' ? circuit.showInFeatures : _circ.function === 16 ? true : _circ.interface !== 4 && _circ.interface !== 5,
|
|
449
|
+
|
|
450
|
+
}
|
|
451
|
+
// errr.... something is wrong. Why do I have circuit function = 5 here?
|
|
452
|
+
// why does it look like function/interface are reversed??
|
|
453
|
+
/*
|
|
454
|
+
{
|
|
455
|
+
"circuitId": 4,
|
|
456
|
+
"name": "Pool Light",
|
|
457
|
+
"nameIndex": 63,
|
|
458
|
+
"function": 2,
|
|
459
|
+
"interface": 16,
|
|
460
|
+
"freeze": 0,
|
|
461
|
+
"colorSet": 2,
|
|
462
|
+
"colorPos": 0,
|
|
463
|
+
"colorStagger": 20,
|
|
464
|
+
"deviceId": 4,
|
|
465
|
+
"eggTimer": 720
|
|
466
|
+
},
|
|
467
|
+
*/
|
|
468
|
+
|
|
469
|
+
/*
|
|
470
|
+
SL Circuits
|
|
471
|
+
POOLCIRCUIT_CLEANER = 5;
|
|
472
|
+
POOLCIRCUIT_CLEANER_SECOND = 6;
|
|
473
|
+
POOLCIRCUIT_COLOR_WHEEL = 12;
|
|
474
|
+
POOLCIRCUIT_DIMMER = 8;
|
|
475
|
+
POOLCIRCUIT_DIMMER_25 = 18;
|
|
476
|
+
POOLCIRCUIT_FLOORCLEANER = 15;
|
|
477
|
+
POOLCIRCUIT_GENERIC = 0;
|
|
478
|
+
POOLCIRCUIT_INTELLIBRITE = 16;
|
|
479
|
+
POOLCIRCUIT_LAST_ID = 19;
|
|
480
|
+
POOLCIRCUIT_LIGHT = 7;
|
|
481
|
+
POOLCIRCUIT_MAGICSTREAM = 17;
|
|
482
|
+
POOLCIRCUIT_PHOTON = 11;
|
|
483
|
+
POOLCIRCUIT_POOL = 2;
|
|
484
|
+
POOLCIRCUIT_POOL_SECOND = 4;
|
|
485
|
+
POOLCIRCUIT_SAL = 10;
|
|
486
|
+
POOLCIRCUIT_SAM = 9;
|
|
487
|
+
POOLCIRCUIT_SPA = 1;
|
|
488
|
+
POOLCIRCUIT_SPA_SECOND = 3;
|
|
489
|
+
POOLCIRCUIT_SPILLWAY = 14;
|
|
490
|
+
POOLCIRCUIT_UNUSED = 19;
|
|
491
|
+
POOLCIRCUIT_VALVE = 13;
|
|
492
|
+
*/
|
|
493
|
+
if (_circ.function === 16) {
|
|
494
|
+
let lgCirc = {
|
|
495
|
+
color: _circ.colorSet,
|
|
496
|
+
swimDelay: _circ.colorStagger,
|
|
497
|
+
position: _circ.colorPos,
|
|
498
|
+
circuit: _circ.circuitId,
|
|
499
|
+
...data,
|
|
500
|
+
id: lgCircId,
|
|
501
|
+
}
|
|
502
|
+
lgCircId++;
|
|
503
|
+
lightGroup.circuits.push(lgCirc);
|
|
504
|
+
}
|
|
505
|
+
await sys.board.circuits.setCircuitAsync(data, false);
|
|
506
|
+
}
|
|
507
|
+
if (lightGroup.circuits.length === 0) {
|
|
508
|
+
sys.lightGroups.removeItemById(192);
|
|
509
|
+
state.lightGroups.removeItemById(192);
|
|
510
|
+
}
|
|
511
|
+
else {
|
|
512
|
+
let grp = sys.lightGroups.getItemById(192);
|
|
513
|
+
lightGroup.name = typeof grp.name === 'undefined' ? 'Intellibrite' : grp.name;
|
|
514
|
+
lightGroup.id === grp.isActive ? grp.id : undefined;
|
|
515
|
+
await sys.board.circuits.setLightGroupAsync(lightGroup, false);
|
|
516
|
+
let sgroup = state.lightGroups.getItemById(192, true);
|
|
517
|
+
sgroup.isActive = true;
|
|
518
|
+
sgroup.name = lightGroup.name;
|
|
519
|
+
sgroup.type = 3;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// now go back through and remove and circuits that aren't in the received list
|
|
523
|
+
let circuits = sys.circuits.get();
|
|
524
|
+
for (let i = 0; i < circuits.length; i++) {
|
|
525
|
+
let circuit = sys.circuits.getItemById(circuits[i].id);
|
|
526
|
+
let _circ = config.circuitArray.find(el => { return el.circuitId === circuit.id });
|
|
527
|
+
if (typeof _circ === 'undefined') {
|
|
528
|
+
sys.circuits.removeItemById(circuit.id);
|
|
529
|
+
state.circuits.removeItemById(circuit.id);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
let features = sys.features.get();
|
|
533
|
+
for (let i = 0; i < features.length; i++) {
|
|
534
|
+
let feature = sys.features.getItemById(features[i].id);
|
|
535
|
+
let _circ = config.circuitArray.find(el => { return el.circuitId === feature.id });
|
|
536
|
+
if (typeof _circ === 'undefined') {
|
|
537
|
+
sys.features.removeItemById(feature.id);
|
|
538
|
+
state.features.removeItemById(feature.id);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/* if (config.equipment.POOL_CHLORPRESENT) {
|
|
543
|
+
let chlor = sys.chlorinators.getItemById(1, true);
|
|
544
|
+
let chlorState = state.chlorinators.getItemById(1, true);
|
|
545
|
+
chlorState.isActive = chlor.isActive = true;
|
|
546
|
+
}
|
|
547
|
+
else {
|
|
548
|
+
sys.chlorinators.removeItemById(1);
|
|
549
|
+
state.chlorinators.removeItemById(1);
|
|
550
|
+
}; */
|
|
551
|
+
let pumpsReported: number[] = [];
|
|
552
|
+
if (config.equipment.POOL_IFLOWPRESENT0) {
|
|
553
|
+
pumpsReported.push(1);
|
|
554
|
+
}
|
|
555
|
+
else {
|
|
556
|
+
sys.pumps.removeItemById(1);
|
|
557
|
+
state.pumps.removeItemById(1);
|
|
558
|
+
};
|
|
559
|
+
if (config.equipment.POOL_IFLOWPRESENT1) {
|
|
560
|
+
pumpsReported.push(2);
|
|
561
|
+
}
|
|
562
|
+
else {
|
|
563
|
+
sys.pumps.removeItemById(2);
|
|
564
|
+
state.pumps.removeItemById(2);
|
|
565
|
+
};
|
|
566
|
+
if (config.equipment.POOL_IFLOWPRESENT2) {
|
|
567
|
+
pumpsReported.push(3);
|
|
568
|
+
}
|
|
569
|
+
else {
|
|
570
|
+
sys.pumps.removeItemById(3);
|
|
571
|
+
state.pumps.removeItemById(3);
|
|
572
|
+
};
|
|
573
|
+
if (config.equipment.POOL_IFLOWPRESENT3) {
|
|
574
|
+
pumpsReported.push(4);
|
|
575
|
+
}
|
|
576
|
+
else {
|
|
577
|
+
sys.pumps.removeItemById(4);
|
|
578
|
+
state.pumps.removeItemById(4);
|
|
579
|
+
};
|
|
580
|
+
if (config.equipment.POOL_IFLOWPRESENT4) {
|
|
581
|
+
pumpsReported.push(5);
|
|
582
|
+
}
|
|
583
|
+
else {
|
|
584
|
+
sys.pumps.removeItemById(5);
|
|
585
|
+
state.pumps.removeItemById(5);
|
|
586
|
+
};
|
|
587
|
+
if (config.equipment.POOL_IFLOWPRESENT5) {
|
|
588
|
+
pumpsReported.push(6);
|
|
589
|
+
}
|
|
590
|
+
else {
|
|
591
|
+
sys.pumps.removeItemById(6);
|
|
592
|
+
state.pumps.removeItemById(6);
|
|
593
|
+
};
|
|
594
|
+
if (config.equipment.POOL_IFLOWPRESENT6) {
|
|
595
|
+
pumpsReported.push(7);
|
|
596
|
+
}
|
|
597
|
+
else {
|
|
598
|
+
sys.pumps.removeItemById(7);
|
|
599
|
+
state.pumps.removeItemById(7);
|
|
600
|
+
};
|
|
601
|
+
if (config.equipment.POOL_IFLOWPRESENT7) {
|
|
602
|
+
pumpsReported.push(8);
|
|
603
|
+
}
|
|
604
|
+
else {
|
|
605
|
+
sys.pumps.removeItemById(8);
|
|
606
|
+
state.pumps.removeItemById(8);
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
/* // deal with these in other places
|
|
610
|
+
if (config.equipment.POOL_NO_SPECIAL_LIGHTS) { }; // ?? Nothing to see here?
|
|
611
|
+
if (config.equipment.POOL_MAGICSTREAMPRESENT) { }; // Ya, so what?
|
|
612
|
+
if (config.equipment.POOL_IBRITEPRESENT) { };
|
|
613
|
+
// set in equip message
|
|
614
|
+
if (config.equipment.POOL_SOLARPRESENT) { };
|
|
615
|
+
if (config.equipment.POOL_SOLARHEATPUMP) { };
|
|
616
|
+
if (config.equipment.POOL_HEATPUMPHASCOOL) { };
|
|
617
|
+
*/
|
|
618
|
+
let intellichemPresent: boolean = false;
|
|
619
|
+
if (config.equipment.POOL_ICHEMPRESENT) {
|
|
620
|
+
intellichemPresent = true;
|
|
621
|
+
};
|
|
622
|
+
return { pumpsReported, intellichemPresent };
|
|
623
|
+
}
|
|
624
|
+
public static async decodeEquipmentState(eqstate: SLEquipmentStateData) {
|
|
625
|
+
/*
|
|
626
|
+
{
|
|
627
|
+
panelMode: 0,
|
|
628
|
+
freezeMode: 0,
|
|
629
|
+
remotes: 32,
|
|
630
|
+
poolDelay: 0,
|
|
631
|
+
spaDelay: 0,
|
|
632
|
+
cleanerDelay: 0,
|
|
633
|
+
airTemp: 67,
|
|
634
|
+
bodiesCount: 2,
|
|
635
|
+
bodies: [
|
|
636
|
+
{
|
|
637
|
+
id: 1,
|
|
638
|
+
currentTemp: 62,
|
|
639
|
+
heatStatus: 0,
|
|
640
|
+
setPoint: 79,
|
|
641
|
+
coolSetPoint: 0,
|
|
642
|
+
heatMode: 0
|
|
643
|
+
},
|
|
644
|
+
{
|
|
645
|
+
id: 2,
|
|
646
|
+
currentTemp: 64,
|
|
647
|
+
heatStatus: 0,
|
|
648
|
+
setPoint: 101,
|
|
649
|
+
coolSetPoint: 67,
|
|
650
|
+
heatMode: 3
|
|
651
|
+
}
|
|
652
|
+
],
|
|
653
|
+
circuitArray: [
|
|
654
|
+
{
|
|
655
|
+
id: 1,
|
|
656
|
+
state: 0,
|
|
657
|
+
colorSet: 0,
|
|
658
|
+
colorPos: 0,
|
|
659
|
+
colorStagger: 0,
|
|
660
|
+
delay: 0
|
|
661
|
+
},
|
|
662
|
+
...
|
|
663
|
+
],
|
|
664
|
+
pH: 0,
|
|
665
|
+
orp: 0,
|
|
666
|
+
saturation: 0,
|
|
667
|
+
saltPPM: 0,
|
|
668
|
+
pHTank: 0,
|
|
669
|
+
orpTank: 0,
|
|
670
|
+
alarms: 0
|
|
671
|
+
}
|
|
672
|
+
*/
|
|
673
|
+
try {
|
|
674
|
+
/* public boolean isDeviceready() {
|
|
675
|
+
return this.m_panelMode == 1;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
public boolean isDeviceSync() {
|
|
679
|
+
return this.m_panelMode == 2;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
public boolean isDeviceServiceMode() {
|
|
683
|
+
return this.m_panelMode == 3;
|
|
684
|
+
} */
|
|
685
|
+
if (eqstate.panelMode === 1) {
|
|
686
|
+
state.mode = 0; // ready
|
|
687
|
+
state.status = sys.board.valueMaps.controllerStatus.transform(1);
|
|
688
|
+
}
|
|
689
|
+
else if (eqstate.panelMode === 2) {
|
|
690
|
+
// syncronizing...
|
|
691
|
+
state.mode = 0;
|
|
692
|
+
state.status = sys.board.valueMaps.controllerStatus.transform(2);
|
|
693
|
+
}
|
|
694
|
+
else if (eqstate.panelMode === 3) {
|
|
695
|
+
// service mode
|
|
696
|
+
state.mode = 1;
|
|
697
|
+
state.status = sys.board.valueMaps.controllerStatus.transform(1);
|
|
698
|
+
}
|
|
699
|
+
if (eqstate.freezeMode) {
|
|
700
|
+
state.mode = state.mode === 1 ? 1 : 8;
|
|
701
|
+
state.freeze = true;
|
|
702
|
+
}
|
|
703
|
+
else {
|
|
704
|
+
state.freeze = false;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// set delays
|
|
708
|
+
if (eqstate.cleanerDelay) {
|
|
709
|
+
let cleaner: CircuitState = state.circuits.find(elem => elem.type === 5);
|
|
710
|
+
let bodyIsOn = state.temps.bodies.getBodyIsOn();
|
|
711
|
+
let bodyId = bodyIsOn.circuit === 6 ? 1 : 2;
|
|
712
|
+
delayMgr.setCleanerStartDelay(cleaner, bodyId, 60);
|
|
713
|
+
}
|
|
714
|
+
if (eqstate.poolDelay) { delayMgr.setManualPriorityDelay(state.circuits.getItemById(6)) };
|
|
715
|
+
if (eqstate.spaDelay) { delayMgr.setManualPriorityDelay(state.circuits.getItemById(1)) };
|
|
716
|
+
state.temps.air = eqstate.airTemp;
|
|
717
|
+
for (let i = 0; i < eqstate.bodies.length; i++) {
|
|
718
|
+
let slbody = eqstate.bodies[i];
|
|
719
|
+
let tbody = state.temps.bodies.getItemById(i + 1);
|
|
720
|
+
let body = sys.bodies.getItemById(i + 1);
|
|
721
|
+
body.setPoint = tbody.setPoint = slbody.setPoint;
|
|
722
|
+
body.heatMode = tbody.heatMode = slbody.heatMode === 3 ? 1 : slbody.heatMode; // 0=off; 3=heater
|
|
723
|
+
tbody.heatStatus = slbody.heatStatus === 2 ? 1 : slbody.heatStatus; // 2=heater active
|
|
724
|
+
tbody.coolSetpoint = slbody.coolSetPoint;
|
|
725
|
+
tbody.temp = slbody.currentTemp;
|
|
726
|
+
}
|
|
727
|
+
for (let i = 0; i < eqstate.circuitArray.length; i++) {
|
|
728
|
+
let slcirc = eqstate.circuitArray[i];
|
|
729
|
+
let cstate = state.circuits.getInterfaceById(slcirc.id);
|
|
730
|
+
let slcircIsOn = utils.makeBool(slcirc.state);
|
|
731
|
+
if (cstate.isOn !== slcircIsOn) {
|
|
732
|
+
sys.board.circuits.setEndTime(sys.circuits.getItemById(cstate.id), cstate, slcircIsOn);
|
|
733
|
+
cstate.isOn = slcircIsOn;
|
|
734
|
+
if (cstate.id === 1 || cstate.id === 6) {
|
|
735
|
+
let tbody = state.temps.bodies.getBodyByCircuitId(cstate.id);
|
|
736
|
+
tbody.isOn = slcircIsOn;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
if (slcirc.delay) {
|
|
740
|
+
// ??
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
let address = 144;
|
|
744
|
+
let chem = sys.chemControllers.getItemByAddress(address);
|
|
745
|
+
if (chem.isActive) {
|
|
746
|
+
let schem = state.chemControllers.getItemById(chem.id, true);
|
|
747
|
+
/* pH: 0,
|
|
748
|
+
orp: 0,
|
|
749
|
+
saturation: 0,
|
|
750
|
+
saltPPM: 0,
|
|
751
|
+
pHTank: 0,
|
|
752
|
+
orpTank: 0,
|
|
753
|
+
alarms: 0 */
|
|
754
|
+
schem.orp.level = eqstate.orp;
|
|
755
|
+
schem.saturationIndex = eqstate.saturation;
|
|
756
|
+
schem.ph.tank.level = eqstate.pHTank;
|
|
757
|
+
schem.orp.tank.level = eqstate.orpTank;
|
|
758
|
+
// saltPPM ==> set by intellichlor msg
|
|
759
|
+
// schem.alarms. ==> Need alarm mapping...
|
|
760
|
+
webApp.emitToClients('chemController', schem.getExtended()); // emit extended data
|
|
761
|
+
}
|
|
762
|
+
state.emitControllerChange();
|
|
763
|
+
state.emitEquipmentChanges();
|
|
764
|
+
} catch (err) {
|
|
765
|
+
logger.error(`Caught error in decodeEquipmentState: ${err.message}`);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
public static async decodeCustomNames(customNames: SLGetCustomNamesData) {
|
|
769
|
+
for (let i = 0; i < sys.equipment.maxCustomNames; i++) {
|
|
770
|
+
let data = {
|
|
771
|
+
id: i,
|
|
772
|
+
name: customNames.names[i]
|
|
773
|
+
}
|
|
774
|
+
try {
|
|
775
|
+
|
|
776
|
+
await sys.board.system.setCustomNameAsync(data, false)
|
|
777
|
+
}
|
|
778
|
+
catch (err) {
|
|
779
|
+
logger.error(`Error setting custom name ${JSON.stringify(data)}`);
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
public static async decodeEquipmentAsync(equip: SLEquipmentConfigurationData) {
|
|
784
|
+
if (sys.controllerType !== ControllerType.EasyTouch && Controller.isEasyTouch(equip.controllerType)) {
|
|
785
|
+
sys.controllerType = ControllerType.EasyTouch;
|
|
786
|
+
(sys.board as EasyTouchBoard).initExpansionModules(equip.controllerType, equip.hardwareType);
|
|
787
|
+
}
|
|
788
|
+
else if (sys.controllerType !== ControllerType.IntelliTouch && Controller.isIntelliTouch(equip.controllerType)) {
|
|
789
|
+
sys.controllerType = ControllerType.IntelliTouch;
|
|
790
|
+
(sys.board as IntelliTouchBoard).initExpansionModules(equip.controllerType, equip.hardwareType);
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
let body = sys.bodies.getItemById(2);
|
|
794
|
+
sys.general.options.manualHeat = body.manualHeat = equip.misc.manualHeat;
|
|
795
|
+
|
|
796
|
+
await Controller.decodeHeatersAsync(equip.heaterConfig);
|
|
797
|
+
await Controller.decodeValvesAsync(equip.valves);
|
|
798
|
+
Controller.decodeHighSpeed(equip.highSpeedCircuits);
|
|
799
|
+
// delays
|
|
800
|
+
sys.general.options.pumpDelay = equip.delays.pumpOffDuringValveAction;
|
|
801
|
+
for (let i = 0; i < sys.bodies.length; i++) {
|
|
802
|
+
let bs = state.temps.bodies.getItemById(i + 1);
|
|
803
|
+
if (bs.circuit === 1) bs.heaterCooldownDelay = equip.delays.spaPumpOnDuringHeaterCooldown;
|
|
804
|
+
else if (bs.circuit === 6) bs.heaterCooldownDelay = equip.delays.poolPumpOnDuringHeaterCooldown;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
Controller.decodeRemote(equip.remotes);
|
|
808
|
+
Controller.decodePumpAsync(equip.pumps);
|
|
809
|
+
// lights
|
|
810
|
+
// packet only lists all-on all-off for intellibrite.
|
|
811
|
+
|
|
812
|
+
// if (equip.misc.intelliChem) {
|
|
813
|
+
// let chem = sys.chemControllers.getItemByAddress(144, true);
|
|
814
|
+
// let schem = state.chemControllers.getItemById(1, true);
|
|
815
|
+
// schem.isActive = chem.isActive = true;
|
|
816
|
+
// }
|
|
817
|
+
// else {
|
|
818
|
+
// sys.chemControllers.removeItemById(1);
|
|
819
|
+
// state.chemControllers.removeItemById(1);
|
|
820
|
+
// }
|
|
821
|
+
sys.equipment.controllerFirmware = `${Math.floor(equip.version / 1000).toString()}.${(equip.version % 1000).toString()}`;
|
|
822
|
+
}
|
|
823
|
+
public static async decodeHeatersAsync(heaterConfig: HeaterConfig) {
|
|
824
|
+
let address: number;
|
|
825
|
+
let id: number;
|
|
826
|
+
let type: number = 1;
|
|
827
|
+
let cooling: boolean = false;
|
|
828
|
+
let body: number = 32;
|
|
829
|
+
// how do we know the heater is a hybrid (type=4)??
|
|
830
|
+
// if no hybrid, we do have a gas heater;
|
|
831
|
+
// it may not be possible to set a Hybrid heater from SL...
|
|
832
|
+
// will go with that until we learn otherwise
|
|
833
|
+
// also todo - how to add heaters to dual bodies?
|
|
834
|
+
let data: any = {
|
|
835
|
+
address,
|
|
836
|
+
id,
|
|
837
|
+
type,
|
|
838
|
+
cooling,
|
|
839
|
+
body
|
|
840
|
+
}
|
|
841
|
+
try {
|
|
842
|
+
let heater = state.heaters.getItemById(1);
|
|
843
|
+
if (heater.type === 0) {
|
|
844
|
+
// to add a heater, id must be 0;
|
|
845
|
+
// await sys.board.heaters.setHeaterAsync(data, false)
|
|
846
|
+
}
|
|
847
|
+
else {
|
|
848
|
+
data.id = 1;
|
|
849
|
+
}
|
|
850
|
+
await sys.board.heaters.setHeaterAsync(data, false);
|
|
851
|
+
}
|
|
852
|
+
catch (err) {
|
|
853
|
+
logger.error(`Error setting gas heater: ${err.message}`)
|
|
854
|
+
}
|
|
855
|
+
let add = false;
|
|
856
|
+
if (heaterConfig.thermaFloPresent) {
|
|
857
|
+
let heater = sys.heaters.getItemById(3);
|
|
858
|
+
if (!heater.isActive) {
|
|
859
|
+
data.address = 112;
|
|
860
|
+
data.type = 3;
|
|
861
|
+
if (heaterConfig.thermaFloCoolPresent) cooling = true;
|
|
862
|
+
add = true;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
else if (heaterConfig.body1SolarPresent) {
|
|
866
|
+
let heater = sys.heaters.getItemById(2);
|
|
867
|
+
if (!heater.isActive) {
|
|
868
|
+
data.type = 2;
|
|
869
|
+
add = true;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
// RSG - Which type is this? Duplicate of 3 above.
|
|
873
|
+
// else if (heaterConfig.solarHeatPumpPresent) {
|
|
874
|
+
// let heater = sys.heaters.getItemById(3);
|
|
875
|
+
// if (!heater.isActive) {
|
|
876
|
+
// data.type = 3;
|
|
877
|
+
// add = true;
|
|
878
|
+
// }
|
|
879
|
+
//batt}
|
|
880
|
+
// Need to figure out dual body here: body2SolarPresent
|
|
881
|
+
if (add) {
|
|
882
|
+
sys.board.heaters.setHeaterAsync(data, false).catch((err) => {
|
|
883
|
+
logger.error(`Error setting additional heaters: ${err.message}`)
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
if (typeof heaterConfig.units !== 'undefined') state.temps.units = sys.general.options.units = heaterConfig.units; // 0 = F, 1 = C
|
|
887
|
+
}
|
|
888
|
+
public static async decodeValvesAsync(valves: Valves[]) {
|
|
889
|
+
for (let i = 0; i < valves.length; i++) {
|
|
890
|
+
let _valve = valves[i];
|
|
891
|
+
let data: any = {
|
|
892
|
+
id: _valve.valveIndex,
|
|
893
|
+
name: _valve.valveName,
|
|
894
|
+
circuit: _valve.deviceId,
|
|
895
|
+
}
|
|
896
|
+
await sys.board.valves.setValveAsync(data, false);
|
|
897
|
+
}
|
|
898
|
+
/* "valves": [
|
|
899
|
+
{
|
|
900
|
+
"loadCenterIndex": 0,
|
|
901
|
+
"valveIndex": 1,
|
|
902
|
+
"valveName": "A",
|
|
903
|
+
"loadCenterName": "1",
|
|
904
|
+
"deviceId": 0
|
|
905
|
+
},
|
|
906
|
+
{
|
|
907
|
+
"loadCenterIndex": 0,
|
|
908
|
+
"valveIndex": 2,
|
|
909
|
+
"valveName": "B",
|
|
910
|
+
"loadCenterName": "1",
|
|
911
|
+
"deviceId": 0
|
|
912
|
+
}
|
|
913
|
+
], */
|
|
914
|
+
}
|
|
915
|
+
public static decodeHighSpeed(highSpeed: number[]) {
|
|
916
|
+
let maxCircuits = sys.controllerType === ControllerType.IntelliTouch ? 8 : 4;
|
|
917
|
+
let arrCircuits = [];
|
|
918
|
+
let pump = sys.pumps.find(x => { return x.master !== 1 && x.type === 65 });
|
|
919
|
+
for (let i = 0; i < maxCircuits && i < highSpeed.length; i++) {
|
|
920
|
+
let val = highSpeed[i];
|
|
921
|
+
if (val > 0) arrCircuits.push(val);
|
|
922
|
+
else if (typeof pump !== 'undefined') pump.circuits.removeItemById(i);
|
|
923
|
+
}
|
|
924
|
+
if (arrCircuits.length > 0) {
|
|
925
|
+
let pump = sys.pumps.getDualSpeed(true);
|
|
926
|
+
for (let j = 1; j <= arrCircuits.length; j++) pump.circuits.getItemById(j, true).circuit = arrCircuits[j - 1];
|
|
927
|
+
}
|
|
928
|
+
else if (typeof pump !== 'undefined') sys.pumps.removeItemById(pump.id);
|
|
929
|
+
}
|
|
930
|
+
public static decodeRemote(remoteDataArray) {
|
|
931
|
+
if (sys.controllerType === ControllerType.EasyTouch) {
|
|
932
|
+
|
|
933
|
+
let remote: Remote = sys.remotes.getItemById(5, true);
|
|
934
|
+
let bActive = false;
|
|
935
|
+
for (let i = 0; i < 10; i++) {
|
|
936
|
+
remote["button" + i] = remoteDataArray.fourButton[i];
|
|
937
|
+
bActive = bActive || remote["button" + i] > 0;
|
|
938
|
+
}
|
|
939
|
+
remote.isActive = bActive;
|
|
940
|
+
remote.type = 1;
|
|
941
|
+
remote.name = "is4";
|
|
942
|
+
|
|
943
|
+
remote = sys.remotes.getItemById(1, true);
|
|
944
|
+
bActive = false;
|
|
945
|
+
for (let i = 0; i < 10; i++) {
|
|
946
|
+
remote["button" + i] = remoteDataArray.tenButton[0][i];
|
|
947
|
+
bActive = bActive || remote["button" + i] > 0;
|
|
948
|
+
}
|
|
949
|
+
remote.isActive = bActive;
|
|
950
|
+
remote.type = 2;
|
|
951
|
+
remote.name = "is10";
|
|
952
|
+
}
|
|
953
|
+
else if (sys.controllerType === ControllerType.IntelliTouch) {
|
|
954
|
+
// Intellitouch
|
|
955
|
+
// 10 button #1
|
|
956
|
+
|
|
957
|
+
for (let r = 0; r < 4; r++) {
|
|
958
|
+
let remote: Remote = sys.remotes.getItemById(r + 1, true);
|
|
959
|
+
let bActive = false;
|
|
960
|
+
for (let i = 0; i < 10; i++) {
|
|
961
|
+
remote["button" + (i + 1)] = remoteDataArray.tenButton[r][i];
|
|
962
|
+
bActive = bActive || remote["button" + (i + 1)] > 0;
|
|
963
|
+
}
|
|
964
|
+
remote.isActive = bActive;
|
|
965
|
+
remote.type = 2;
|
|
966
|
+
remote.name = "is10";
|
|
967
|
+
if (r === 3) {
|
|
968
|
+
let remote5 = sys.remotes.getItemById(5);
|
|
969
|
+
let remote6 = sys.remotes.getItemById(6);
|
|
970
|
+
remote5.name = remote6.name = "is4";
|
|
971
|
+
remote5.type = remote6.type = 1;
|
|
972
|
+
if (!remote.button5 && !remote.button10) {
|
|
973
|
+
remote.isActive = false;
|
|
974
|
+
remote5.button1 = remote.button1;
|
|
975
|
+
remote5.button2 = remote.button2;
|
|
976
|
+
remote5.button3 = remote.button3;
|
|
977
|
+
remote5.button4 = remote.button4;
|
|
978
|
+
remote6.button1 = remote.button6;
|
|
979
|
+
remote6.button2 = remote.button7;
|
|
980
|
+
remote6.button3 = remote.button8;
|
|
981
|
+
remote6.button4 = remote.button9;
|
|
982
|
+
if (!remote5.button1 && !remote5.button2 && !remote5.button3 && !remote5.button4) remote5.isActive = false;
|
|
983
|
+
else remote5.isActive = true;
|
|
984
|
+
|
|
985
|
+
if (!remote6.button1 && !remote6.button2 && !remote6.button3 && !remote6.button4) remote6.isActive = false;
|
|
986
|
+
else remote6.isActive = true;
|
|
987
|
+
|
|
988
|
+
}
|
|
989
|
+
else {
|
|
990
|
+
remote5.isActive = remote6.isActive = false;
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
let remote = sys.remotes.getItemById(7, true);
|
|
997
|
+
remote.button1 = remoteDataArray.quickTouch[0];
|
|
998
|
+
remote.button2 = remoteDataArray.quickTouch[1];
|
|
999
|
+
remote.button3 = remoteDataArray.quickTouch[2];
|
|
1000
|
+
remote.button4 = remoteDataArray.quickTouch[3];
|
|
1001
|
+
|
|
1002
|
+
if (!remote.button1 && !remote.button2 && !remote.button3 && !remote.button4) remote.isActive = false;
|
|
1003
|
+
else remote.isActive = true;
|
|
1004
|
+
remote.name = "QuickTouch";
|
|
1005
|
+
}
|
|
1006
|
+
public static async decodeIntellichlorAsync(slchlor: SLIntellichlorData) {
|
|
1007
|
+
// Intellichlor: {"installed":false,"status":1,"poolSetPoint":12,"spaSetPoint":0,"salt":0,"flags":0,"superChlorTimer":0}
|
|
1008
|
+
let chlor = sys.chlorinators.getItemById(1);
|
|
1009
|
+
if (slchlor.installed) {
|
|
1010
|
+
let data: any = {
|
|
1011
|
+
id: chlor.isActive ? chlor.id : 0,
|
|
1012
|
+
superChlorHours: slchlor.superChlorTimer,
|
|
1013
|
+
poolSetpoint: slchlor.poolSetPoint,
|
|
1014
|
+
spaSetpoint: slchlor.spaSetPoint,
|
|
1015
|
+
model: chlor.model || 0,
|
|
1016
|
+
body: 32
|
|
1017
|
+
}
|
|
1018
|
+
await sys.board.chlorinator.setChlorAsync(data, false);
|
|
1019
|
+
let chlorState = state.chlorinators.getItemById(1, true);
|
|
1020
|
+
chlorState.saltLevel = slchlor.salt;
|
|
1021
|
+
chlorState.poolSetpoint = slchlor.poolSetPoint;
|
|
1022
|
+
chlorState.spaSetpoint = slchlor.spaSetPoint;
|
|
1023
|
+
state.emitEquipmentChanges();
|
|
1024
|
+
}
|
|
1025
|
+
else {
|
|
1026
|
+
sys.chlorinators.removeItemById(1);
|
|
1027
|
+
state.chlorinators.removeItemById(1);
|
|
1028
|
+
};
|
|
1029
|
+
|
|
1030
|
+
}
|
|
1031
|
+
public static async decodeChemController(slchem: SLChemData) {
|
|
1032
|
+
// Chem data: {"isValid":true,"pH":0,"orp":0,"pHSetPoint":0,"orpSetPoint":0,"pHTankLevel":0,"orpTankLevel":0,"saturation":0,"calcium":0,"cyanuricAcid":0,"alkalinity":0,"saltPPM":0,"temperature":0,"balance":0,"corrosive":false,"scaling":false,"error":false}
|
|
1033
|
+
let chem = sys.chemControllers.getItemByAddress(144);
|
|
1034
|
+
let data: any = {
|
|
1035
|
+
id: chem.isActive ? chem.id : undefined,
|
|
1036
|
+
address: 144,
|
|
1037
|
+
calciumHardness: slchem.calcium,
|
|
1038
|
+
cyanuricAcid: slchem.cyanuricAcid,
|
|
1039
|
+
alkalinity: slchem.alkalinity,
|
|
1040
|
+
body: 32,
|
|
1041
|
+
ph: {
|
|
1042
|
+
setpoint: slchem.pHSetPoint,
|
|
1043
|
+
enabled: true,
|
|
1044
|
+
tank: slchem.pHTankLevel
|
|
1045
|
+
},
|
|
1046
|
+
orp: {
|
|
1047
|
+
setpoint: slchem.orpSetPoint,
|
|
1048
|
+
enabled: true,
|
|
1049
|
+
tank: slchem.orpTankLevel
|
|
1050
|
+
},
|
|
1051
|
+
type: 2
|
|
1052
|
+
|
|
1053
|
+
}
|
|
1054
|
+
try {
|
|
1055
|
+
|
|
1056
|
+
await sys.board.chemControllers.setChemControllerAsync(data, false);
|
|
1057
|
+
let schem = state.chemControllers.getItemById(1);
|
|
1058
|
+
schem.ph.level = slchem.pH;
|
|
1059
|
+
schem.orp.level = slchem.orp;
|
|
1060
|
+
schem.saturationIndex = slchem.saturation;
|
|
1061
|
+
schem.alarms.bodyFault = slchem.error ? 1 : 0; // maybe a better place to assign the error?
|
|
1062
|
+
state.emitEquipmentChanges();
|
|
1063
|
+
}
|
|
1064
|
+
catch (err) {
|
|
1065
|
+
return Promise.reject(err);
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
}
|
|
1069
|
+
public static async decodePumpAsync(pDataArr: any) {
|
|
1070
|
+
pDataArr.forEach(async (pData, idx) => {
|
|
1071
|
+
await sys.board.pumps.setPumpAsync(pData, false);
|
|
1072
|
+
})
|
|
1073
|
+
}
|
|
1074
|
+
public static async decodePumpStatusAsync(id: number, slpump: SLPumpStatusData) {
|
|
1075
|
+
/* {
|
|
1076
|
+
pumpCircuits: [
|
|
1077
|
+
{ circuitId: 6,speed: 2000,isRPMs: true, },
|
|
1078
|
+
{ circuitId: 8, speed:2700,isRPMs: true, },
|
|
1079
|
+
{ circuitId: 2,speed: 2710,isRPMs: true, },
|
|
1080
|
+
{ circuitId: 2,speed:1000, isRPMs: true,},
|
|
1081
|
+
{ circuitId: 5,speed:2830, isRPMs: true,},
|
|
1082
|
+
{ circuitId: 0,speed: 30,isRPMs: false,},
|
|
1083
|
+
{ circuitId: 0,speed: 30,isRPMs: false,},
|
|
1084
|
+
{ circuitId: 0,speed: 30,isRPMs: false,},
|
|
1085
|
+
],
|
|
1086
|
+
pumpType: 4,
|
|
1087
|
+
isRunning: false,
|
|
1088
|
+
pumpWatts: 0,
|
|
1089
|
+
pumpRPMs: 0,
|
|
1090
|
+
pumpUnknown1: 0,
|
|
1091
|
+
pumpGPMs: 0,
|
|
1092
|
+
pumpUnknown2: 255,
|
|
1093
|
+
}
|
|
1094
|
+
*/
|
|
1095
|
+
// RKS: 05-07-23 - This process of getting the pump by its id is flawed. We need to pull this information by its address.
|
|
1096
|
+
//let pstate = state.pumps.getItemById(id);
|
|
1097
|
+
let pstate = state.pumps.find(x => x.address === 95 + id);
|
|
1098
|
+
if (typeof pstate !== 'undefined') {
|
|
1099
|
+
pstate.watts = slpump.pumpWatts;
|
|
1100
|
+
pstate.rpm = slpump.pumpRPMs;
|
|
1101
|
+
pstate.flow = slpump.pumpGPMs === 255 ? 0 : slpump.pumpGPMs;
|
|
1102
|
+
pstate.command = (pstate.rpm > 0 || pstate.watts > 0) ? 10 : 0;
|
|
1103
|
+
state.emitEquipmentChanges();
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
public static async decodeSchedules(slrecurring: SLScheduleData, slrunonce: SLScheduleData) {
|
|
1107
|
+
/* reccuring schedules: [{"scheduleId":1,"circuitId":6,"startTime":"1800","stopTime":"0700","dayMask":127,"flags":0,"heatCmd":4,"heatSetPoint":70,"days":["Mon","Tue","Wed","Thu","Fri","Sat","Sun"]},
|
|
1108
|
+
|
|
1109
|
+
{"scheduleId":4,"circuitId":2,"startTime":"1800","stopTime":"2300","dayMask":127,"flags":0,"heatCmd":0,"heatSetPoint":0,"days":["Mon","Tue","Wed","Thu","Fri","Sat","Sun"]},{"scheduleId":11,"circuitId":6,"startTime":"0800","stopTime":"1700","dayMask":127,"flags":0,"heatCmd":4,"heatSetPoint":70,"days":["Mon","Tue","Wed","Thu","Fri","Sat","Sun"]}]
|
|
1110
|
+
|
|
1111
|
+
Run once schedules: [{"scheduleId":12,"circuitId":6,"startTime":"0800","stopTime":"1100","dayMask":1,"flags":1,"heatCmd":4,"heatSetPoint":70,"days":["Mon"]},{"scheduleId":13,"circuitId":6,"startTime":"0800","stopTime":"1100","dayMask":1,"flags":1,"heatCmd":4,"heatSetPoint":70,"days":["Mon"]}] */
|
|
1112
|
+
|
|
1113
|
+
for (let i = 0; i < slrecurring.data.length; i++) {
|
|
1114
|
+
let slsched = slrecurring.data[i];
|
|
1115
|
+
try {
|
|
1116
|
+
let data = {
|
|
1117
|
+
circuit: slsched.circuitId,
|
|
1118
|
+
startTime: Math.floor(parseInt(slsched.startTime, 10) / 100) * 60 + parseInt(slsched.startTime, 10) % 100,
|
|
1119
|
+
endTime: Math.floor(parseInt(slsched.stopTime, 10) / 100) * 60 + parseInt(slsched.stopTime, 10) % 100,
|
|
1120
|
+
scheduleDays: slsched.dayMask,
|
|
1121
|
+
changeHeatSetPoint: slsched.heatCmd > 0,
|
|
1122
|
+
heatSetPoint: slsched.heatSetPoint,
|
|
1123
|
+
schedType: 128 // recurring
|
|
1124
|
+
}
|
|
1125
|
+
await sys.board.schedules.setScheduleAsync(data, false)
|
|
1126
|
+
} catch (err) {
|
|
1127
|
+
logger.error(`Error setting schedule ${slsched.scheduleId}. ${err.message}`);
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
for (let i = 0; i < slrunonce.data.length; i++) {
|
|
1131
|
+
let slsched = slrunonce.data[i];
|
|
1132
|
+
try {
|
|
1133
|
+
let data = {
|
|
1134
|
+
id: slsched.scheduleId,
|
|
1135
|
+
circuit: slsched.circuitId,
|
|
1136
|
+
// start and stop come in as military time string
|
|
1137
|
+
startTime: parseInt(slsched.startTime, 10),
|
|
1138
|
+
endTime: parseInt(slsched.stopTime, 10),
|
|
1139
|
+
scheduleDays: slsched.dayMask,
|
|
1140
|
+
changeHeatSetPoint: slsched.heatCmd > 0,
|
|
1141
|
+
heatSetPoint: slsched.heatSetPoint,
|
|
1142
|
+
schedType: 0 // runonce
|
|
1143
|
+
}
|
|
1144
|
+
await sys.board.schedules.setScheduleAsync(data, false);
|
|
1145
|
+
sys.board.system.setTZ();
|
|
1146
|
+
} catch (err) {
|
|
1147
|
+
logger.error(`Error setting schedule ${slsched.scheduleId}. ${err.message}`);
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
}
|
|
1152
|
+
public static decodeDateTime(systime: SLSystemTimeData) {
|
|
1153
|
+
// System Time: {"date":"2022-11-07T16:04:32.000Z","year":2022,"month":11,"dayOfWeek":1,"day":7,"hour":8,"minute":4,"second":32,"millisecond":0,"adjustForDST":true}
|
|
1154
|
+
if (sys.general.options.clockSource !== 'server') {
|
|
1155
|
+
state.time.year = systime.year;
|
|
1156
|
+
state.time.month = systime.month;
|
|
1157
|
+
state.time.date = systime.day;
|
|
1158
|
+
state.time.hours = systime.hour;
|
|
1159
|
+
state.time.minutes = systime.minute;
|
|
1160
|
+
state.time.seconds = systime.second;
|
|
1161
|
+
sys.general.options.adjustDST = systime.adjustForDST;
|
|
1162
|
+
state.emitEquipmentChanges();
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
/*
|
|
1166
|
+
Controller Types
|
|
1167
|
+
// Dual Intellitouch
|
|
1168
|
+
I10_3D = 5;
|
|
1169
|
+
// Intellitouch
|
|
1170
|
+
I5 = 0;
|
|
1171
|
+
I7_3 = 1;
|
|
1172
|
+
I9_3 = 2;
|
|
1173
|
+
I5S = 3;
|
|
1174
|
+
I9_3S = 4;
|
|
1175
|
+
I10X = 6;
|
|
1176
|
+
// Not intellitouch...
|
|
1177
|
+
SUNTOUCH = 10;
|
|
1178
|
+
// EasyTouch
|
|
1179
|
+
EASYTOUCH2 = 13; //hwType & 4 = EasyTouchLite
|
|
1180
|
+
EASYTOUCH = 14;
|
|
1181
|
+
*/
|
|
1182
|
+
static isEasyTouch(controllerType) {
|
|
1183
|
+
return controllerType === 14 || controllerType === 13;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
static isIntelliTouch(controllerType) {
|
|
1187
|
+
return controllerType !== 14 && controllerType !== 13 && controllerType !== 10;
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
static isEasyTouchLite(controllerType, hwType) {
|
|
1191
|
+
return controllerType === 13 && (hwType & 4) !== 0;
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
static isDualBody(controllerType) {
|
|
1195
|
+
return controllerType === 5;
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
export class SLCommands {
|
|
1199
|
+
constructor(unit: UnitConnection) {
|
|
1200
|
+
this._unit = unit;
|
|
1201
|
+
}
|
|
1202
|
+
protected _unit: UnitConnection;
|
|
1203
|
+
}
|
|
1204
|
+
export class SLCircuits extends SLCommands {
|
|
1205
|
+
public async setCircuitAsync(circuitId: number, nameIndex: number, circuitFunction: number, circuitInterface: number, freeze: boolean = false, colorPos: number = 0) {
|
|
1206
|
+
try {
|
|
1207
|
+
|
|
1208
|
+
let lg: LightGroup = sys.lightGroups.getItemById(1);
|
|
1209
|
+
for (let i = 0; i < lg.circuits.length; i++) {
|
|
1210
|
+
let cg: LightGroupCircuit = lg.circuits[i];
|
|
1211
|
+
if (cg.circuit === circuitId) colorPos = cg.position;
|
|
1212
|
+
}
|
|
1213
|
+
await this._unit.circuits.setCircuitAsync(circuitId, nameIndex, circuitFunction, circuitInterface, freeze, colorPos);
|
|
1214
|
+
} catch (err) {
|
|
1215
|
+
return Promise.reject(err);
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
public async setCircuitStateAsync(id: number, val: boolean) {
|
|
1220
|
+
try {
|
|
1221
|
+
if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError('Circuit or Feature id not valid', id, 'Circuit'));
|
|
1222
|
+
let c = sys.circuits.getInterfaceById(id);
|
|
1223
|
+
// if (id === 192 || c.type === 3) return await sys.board.circuits.setLightGroupThemeAsync(id - 191, val ? 1 : 0);
|
|
1224
|
+
// if (id >= 192) return await sys.board.circuits.setCircuitGroupStateAsync(id, val);
|
|
1225
|
+
await this._unit.circuits.setCircuitStateAsync(id, val);
|
|
1226
|
+
// let cstate = state.circuits.getInterfaceById(id);
|
|
1227
|
+
// cstate.isOn = val;
|
|
1228
|
+
// state.emitEquipmentChanges();
|
|
1229
|
+
// return cstate;
|
|
1230
|
+
} catch (err) {
|
|
1231
|
+
return Promise.reject(err);
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
public async setLightGroupThemeAsync(lightTheme: number) {
|
|
1236
|
+
try {
|
|
1237
|
+
// SL Light Themes
|
|
1238
|
+
const ALLOFF = 0;
|
|
1239
|
+
const ALLON = 1;
|
|
1240
|
+
const SET = 2;
|
|
1241
|
+
const SYNC = 3;
|
|
1242
|
+
const SWIM = 4;
|
|
1243
|
+
const PARTY = 5;
|
|
1244
|
+
const ROMANCE = 6;
|
|
1245
|
+
const CARIBBEAN = 7;
|
|
1246
|
+
const AMERICAN = 8;
|
|
1247
|
+
const SUNSET = 9;
|
|
1248
|
+
const ROYALTY = 10;
|
|
1249
|
+
const SAVE = 11;
|
|
1250
|
+
const RECALL = 12;
|
|
1251
|
+
const BLUE = 13;
|
|
1252
|
+
const GREEN = 14;
|
|
1253
|
+
const RED = 15;
|
|
1254
|
+
const WHITE = 16;
|
|
1255
|
+
const MAGENTA = 17;
|
|
1256
|
+
const MS_THUMPER = 18;
|
|
1257
|
+
const MS_NEXT_MODE = 19;
|
|
1258
|
+
const MS_RESET = 20;
|
|
1259
|
+
const MS_HOLD = 21;
|
|
1260
|
+
|
|
1261
|
+
// Convert njsPC to SL themes
|
|
1262
|
+
switch (lightTheme) {
|
|
1263
|
+
// [0, { name: 'off', desc: 'Off' }],
|
|
1264
|
+
// [1, { name: 'on', desc: 'On' }],
|
|
1265
|
+
case 0:
|
|
1266
|
+
case 1:
|
|
1267
|
+
break;
|
|
1268
|
+
// [128, { name: 'colorsync', desc: 'Color Sync' }],
|
|
1269
|
+
case 128:
|
|
1270
|
+
lightTheme = SYNC;
|
|
1271
|
+
break;
|
|
1272
|
+
// [144, { name: 'colorswim', desc: 'Color Swim' }],
|
|
1273
|
+
case 144:
|
|
1274
|
+
lightTheme = SWIM;
|
|
1275
|
+
// [160, { name: 'colorset', desc: 'Color Set' }],
|
|
1276
|
+
case 160:
|
|
1277
|
+
lightTheme = SET;
|
|
1278
|
+
// [177, { name: 'party', desc: 'Party', types: ['intellibrite'], sequence: 2 }],
|
|
1279
|
+
case 177:
|
|
1280
|
+
lightTheme = PARTY;
|
|
1281
|
+
// [178, { name: 'romance', desc: 'Romance', types: ['intellibrite'], sequence: 3 }],
|
|
1282
|
+
case 178:
|
|
1283
|
+
lightTheme = ROMANCE;
|
|
1284
|
+
break;
|
|
1285
|
+
// [179, { name: 'caribbean', desc: 'Caribbean', types: ['intellibrite'], sequence: 4 }],
|
|
1286
|
+
case 179:
|
|
1287
|
+
lightTheme = CARIBBEAN;
|
|
1288
|
+
break;
|
|
1289
|
+
// [180, { name: 'american', desc: 'American', types: ['intellibrite'], sequence: 5 }],
|
|
1290
|
+
case 180:
|
|
1291
|
+
lightTheme = AMERICAN;
|
|
1292
|
+
break;
|
|
1293
|
+
// [181, { name: 'sunset', desc: 'Sunset', types: ['intellibrite'], sequence: 6 }],
|
|
1294
|
+
case 181:
|
|
1295
|
+
lightTheme = SUNSET;
|
|
1296
|
+
break;
|
|
1297
|
+
// [182, { name: 'royal', desc: 'Royal', types: ['intellibrite'], sequence: 7 }],
|
|
1298
|
+
case 182:
|
|
1299
|
+
lightTheme = ROYALTY;
|
|
1300
|
+
break;
|
|
1301
|
+
// [190, { name: 'save', desc: 'Save', types: ['intellibrite'], sequence: 13 }],
|
|
1302
|
+
case 190:
|
|
1303
|
+
lightTheme = SAVE;
|
|
1304
|
+
break;
|
|
1305
|
+
// [191, { name: 'recall', desc: 'Recall', types: ['intellibrite'], sequence: 14 }],
|
|
1306
|
+
case 191:
|
|
1307
|
+
lightTheme = RECALL;
|
|
1308
|
+
break;
|
|
1309
|
+
// [193, { name: 'blue', desc: 'Blue', types: ['intellibrite'], sequence: 8 }],
|
|
1310
|
+
case 192:
|
|
1311
|
+
lightTheme = BLUE;
|
|
1312
|
+
break;
|
|
1313
|
+
// [194, { name: 'green', desc: 'Green', types: ['intellibrite'], sequence: 9 }],
|
|
1314
|
+
case 194:
|
|
1315
|
+
lightTheme = GREEN;
|
|
1316
|
+
break;
|
|
1317
|
+
// [195, { name: 'red', desc: 'Red', types: ['intellibrite'], sequence: 10 }],
|
|
1318
|
+
case 195:
|
|
1319
|
+
lightTheme = RED;
|
|
1320
|
+
break;
|
|
1321
|
+
// [196, { name: 'white', desc: 'White', types: ['intellibrite'], sequence: 11 }],
|
|
1322
|
+
case 196:
|
|
1323
|
+
lightTheme = WHITE;
|
|
1324
|
+
break
|
|
1325
|
+
// [197, { name: 'magenta', desc: 'Magenta', types: ['intellibrite'], sequence: 12 }],
|
|
1326
|
+
case 197:
|
|
1327
|
+
lightTheme = MAGENTA;
|
|
1328
|
+
break
|
|
1329
|
+
// [208, { name: 'thumper', desc: 'Thumper', types: ['magicstream'] }],
|
|
1330
|
+
case 208:
|
|
1331
|
+
lightTheme = MS_THUMPER;
|
|
1332
|
+
break
|
|
1333
|
+
// [209, { name: 'hold', desc: 'Hold', types: ['magicstream'] }],
|
|
1334
|
+
case 209:
|
|
1335
|
+
lightTheme = MS_HOLD;
|
|
1336
|
+
break
|
|
1337
|
+
// [210, { name: 'reset', desc: 'Reset', types: ['magicstream'] }],
|
|
1338
|
+
case 210:
|
|
1339
|
+
lightTheme = MS_RESET;
|
|
1340
|
+
break
|
|
1341
|
+
// [211, { name: 'mode', desc: 'Mode', types: ['magicstream'] }],
|
|
1342
|
+
// [254, { name: 'unknown', desc: 'unknow
|
|
1343
|
+
default:
|
|
1344
|
+
return Promise.reject(`Screenlogic: Unknown light theme ${lightTheme}.`);
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
|
|
1348
|
+
let lightRes = await this._unit.circuits.sendLightCommandAsync(lightTheme);
|
|
1349
|
+
logger.silly(`Screenlogic:lightRes: ${lightRes}`);
|
|
1350
|
+
} catch (err) {
|
|
1351
|
+
return Promise.reject(err);
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
export class SLBodies extends SLCommands {
|
|
1356
|
+
public async setHeatModeAsync(body: Body, mode: number) {
|
|
1357
|
+
let htypes = sys.board.heaters.getInstalledHeaterTypes();
|
|
1358
|
+
let solarInstalled = htypes.solar > 0;
|
|
1359
|
+
let heatPumpInstalled = htypes.heatpump > 0;
|
|
1360
|
+
let ultratempInstalled = htypes.ultratemp > 0;
|
|
1361
|
+
let gasHeaterInstalled = htypes.gas > 0;
|
|
1362
|
+
let hybridInstalled = htypes.hybrid > 0;
|
|
1363
|
+
let slHeatMode = 0;
|
|
1364
|
+
switch (mode) {
|
|
1365
|
+
case 0:
|
|
1366
|
+
slHeatMode = HeatModes.HEAT_MODE_OFF;
|
|
1367
|
+
break;
|
|
1368
|
+
case 1:
|
|
1369
|
+
if (hybridInstalled) slHeatMode = HeatModes.HEAT_MODE_HEATPUMP;
|
|
1370
|
+
slHeatMode = HeatModes.HEAT_MODE_HEATER;
|
|
1371
|
+
break;
|
|
1372
|
+
case 2:
|
|
1373
|
+
if (hybridInstalled) slHeatMode = HeatModes.HEAT_MODE_HEATER;
|
|
1374
|
+
else if (solarInstalled) slHeatMode = HeatModes.HEAT_MODE_SOLARPREFERRED;
|
|
1375
|
+
break;
|
|
1376
|
+
case 3:
|
|
1377
|
+
if (hybridInstalled) slHeatMode = HeatModes.HEAT_MODE_SOLARPREFERRED; // ?? Should be heatpumppref but maybe this is the same?
|
|
1378
|
+
else if (solarInstalled) slHeatMode = HeatModes.HEAT_MODE_SOLAR;
|
|
1379
|
+
break;
|
|
1380
|
+
case 16:
|
|
1381
|
+
// ?? Should be Dual heat mode; maybe not supported on SL?
|
|
1382
|
+
break;
|
|
1383
|
+
default:
|
|
1384
|
+
logger.warn(`Screenlogic: No valid heat mode passed for ${body.name}: Mode=${mode}. `);
|
|
1385
|
+
return Promise.reject(`Screenlogic: No valid heat mode passed for ${body.name}: Mode=${mode}. `);
|
|
1386
|
+
|
|
1387
|
+
}
|
|
1388
|
+
try {
|
|
1389
|
+
await this._unit.bodies.setHeatModeAsync(body.id, slHeatMode);
|
|
1390
|
+
}
|
|
1391
|
+
catch (err) {
|
|
1392
|
+
return Promise.reject(err);
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
public async setHeatSetpointAsync(body: Body, setPoint: number) {
|
|
1396
|
+
try {
|
|
1397
|
+
await this._unit.bodies.setSetPointAsync(body.id, setPoint);
|
|
1398
|
+
}
|
|
1399
|
+
catch (err) {
|
|
1400
|
+
return Promise.reject(err);
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
public async setCoolSetpointAsync(body: Body, setPoint: number) {
|
|
1404
|
+
try {
|
|
1405
|
+
await this._unit.bodies.setCoolSetPointAsync(body.id, setPoint);
|
|
1406
|
+
}
|
|
1407
|
+
catch (err) {
|
|
1408
|
+
return Promise.reject(err);
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
public async cancelDalayAsync() {
|
|
1412
|
+
try {
|
|
1413
|
+
await this._unit.equipment.cancelDelayAsync();
|
|
1414
|
+
} catch (err) {
|
|
1415
|
+
return Promise.reject(err);
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
export class SLCounter {
|
|
1420
|
+
constructor() {
|
|
1421
|
+
this.bytesReceived = 0;
|
|
1422
|
+
this.bytesSent = 0;
|
|
1423
|
+
}
|
|
1424
|
+
public bytesReceived: number;
|
|
1425
|
+
public bytesSent: number;
|
|
1426
|
+
public toLog(): string {
|
|
1427
|
+
return `{ "bytesReceived": ${this.bytesReceived}, "bytesSent": ${this.bytesSent} }`;
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
export class SLChlor extends SLCommands {
|
|
1431
|
+
public async setChlorOutputAsync(poolSetpoint: number, spaSetpoint: number) {
|
|
1432
|
+
try {
|
|
1433
|
+
let res = await this._unit.chlor.setIntellichlorOutputAsync(poolSetpoint, spaSetpoint);
|
|
1434
|
+
if (!res) return Promise.reject(`Screenlogic: Unable to add schedule.`)
|
|
1435
|
+
} catch (err) {
|
|
1436
|
+
return Promise.reject(err);
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
public async setChlorEnabledAsync(isActive: boolean) {
|
|
1440
|
+
try {
|
|
1441
|
+
let res = await this._unit.chlor.setIntellichlorIsActiveAsync(isActive);
|
|
1442
|
+
if (!res) return Promise.reject(`Screenlogic: Unable to add schedule.`)
|
|
1443
|
+
} catch (err) {
|
|
1444
|
+
return Promise.reject(err);
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
export class SLSchedule extends SLCommands {
|
|
1449
|
+
public async addScheduleAsync(type: number) {
|
|
1450
|
+
// Passed as an argument to the emitted addNewScheduleEvent event. Adds a new event to the specified schedule type, either 0 for regular events or 1 for one-time events.
|
|
1451
|
+
let slRet = this._unit.schedule.addNewScheduleEventAsync(0);
|
|
1452
|
+
return (await slRet).val;
|
|
1453
|
+
}
|
|
1454
|
+
// SCHEDULES
|
|
1455
|
+
|
|
1456
|
+
// let addSched = await client.schedule.addNewScheduleEvent(SchedTypes.RECURRING);
|
|
1457
|
+
// logger.silly(`Screenlogic:Add sched response: ${addSched}`);
|
|
1458
|
+
// let setSched = await client.schedule.setScheduleEventById(10, 2,500,1200,127,0,1,99);
|
|
1459
|
+
// logger.silly(`Screenlogic:Set sched result: ${setSched}`);
|
|
1460
|
+
// let delSched = await client.schedule.deleteScheduleEventById(10);
|
|
1461
|
+
// logger.silly(`Screenlogic:Deleted sched result: ${delSched}`);
|
|
1462
|
+
public async setScheduleAsync(id: number, circuit: number, startTime: number, endTime: number, schedDays: number, schedType: number, changeHeatSetPoint: boolean, heatSource?: number, setPoint?: number): Promise<number> {
|
|
1463
|
+
/*
|
|
1464
|
+
scheduleId - id of a schedule previously created, see SLAddNewScheduleEvent
|
|
1465
|
+
circuitId - id of the circuit to which this event applies
|
|
1466
|
+
startTime - the start time of the event, specified as minutes since midnight (see conversion functions)
|
|
1467
|
+
example: 6:00am would be 360
|
|
1468
|
+
example: 6:15am would be 375
|
|
1469
|
+
stopTime - the stop time of the event, specified as minutes since midnight (see conversion functions)
|
|
1470
|
+
dayMask
|
|
1471
|
+
7-bit mask that determines which days the schedule is active for, MSB is always 0, valid numbers 1-127
|
|
1472
|
+
flags
|
|
1473
|
+
bit 0 is the schedule type, if 0 then regular event, if 1 its a run-once
|
|
1474
|
+
bit 1 indicates whether heat setPoint should be changed
|
|
1475
|
+
heatCmd - integer indicating the desired heater mode. Valid values are:
|
|
1476
|
+
ScreenLogic.HEAT_MODE_OFF
|
|
1477
|
+
ScreenLogic.HEAT_MODE_SOLAR
|
|
1478
|
+
ScreenLogic.HEAT_MODE_SOLARPREFERRED
|
|
1479
|
+
ScreenLogic.HEAT_MODE_HEATPUMP
|
|
1480
|
+
ScreenLogic.HEAT_MODE_DONTCHANGE
|
|
1481
|
+
heatSetPoint - the temperature set point if heat is to be changed (ignored if bit 1 of flags is 0)
|
|
1482
|
+
*/
|
|
1483
|
+
try {
|
|
1484
|
+
|
|
1485
|
+
// if the id doesn't exist - we need to add a new schedule and then edit it;
|
|
1486
|
+
// this may not match our assigned id and we need to override it.
|
|
1487
|
+
let flags = 0;
|
|
1488
|
+
if (schedType === 26) {
|
|
1489
|
+
// 0 = repeat; 26 = run once
|
|
1490
|
+
flags = 1;
|
|
1491
|
+
}
|
|
1492
|
+
if (id <= 0) {
|
|
1493
|
+
id = await this.addScheduleAsync(flags);
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
let SLheatSource = 0;
|
|
1497
|
+
if (changeHeatSetPoint) {
|
|
1498
|
+
flags = flags | (1 << 1);
|
|
1499
|
+
let htypes = sys.board.heaters.getInstalledHeaterTypes();
|
|
1500
|
+
let solarInstalled = htypes.solar > 0;
|
|
1501
|
+
let heatPumpInstalled = htypes.heatpump > 0;
|
|
1502
|
+
let ultratempInstalled = htypes.ultratemp > 0;
|
|
1503
|
+
let gasHeaterInstalled = htypes.gas > 0;
|
|
1504
|
+
let hybridInstalled = htypes.hybrid > 0;
|
|
1505
|
+
switch (heatSource) {
|
|
1506
|
+
case 0:
|
|
1507
|
+
SLheatSource = HeatModes.HEAT_MODE_OFF;
|
|
1508
|
+
break;
|
|
1509
|
+
case 3:
|
|
1510
|
+
if (hybridInstalled) SLheatSource = HeatModes.HEAT_MODE_HEATPUMP;
|
|
1511
|
+
SLheatSource = HeatModes.HEAT_MODE_HEATER;
|
|
1512
|
+
break;
|
|
1513
|
+
case 5:
|
|
1514
|
+
if (hybridInstalled) SLheatSource = HeatModes.HEAT_MODE_SOLARPREFERRED; // ?? Should be heatpumppref but maybe this is the same?
|
|
1515
|
+
else if (solarInstalled) SLheatSource = HeatModes.HEAT_MODE_SOLAR;
|
|
1516
|
+
break;
|
|
1517
|
+
case 21:
|
|
1518
|
+
if (hybridInstalled) SLheatSource = HeatModes.HEAT_MODE_HEATER;
|
|
1519
|
+
else if (solarInstalled) SLheatSource = HeatModes.HEAT_MODE_SOLARPREFERRED;
|
|
1520
|
+
break;
|
|
1521
|
+
case 32:
|
|
1522
|
+
// No change
|
|
1523
|
+
SLheatSource = HeatModes.HEAT_MODE_DONTCHANGE;
|
|
1524
|
+
break;
|
|
1525
|
+
default:
|
|
1526
|
+
logger.warn(`Screenlogic: No valid heat source passed for schedule: ${id}, heat source: ${heatSource}. `);
|
|
1527
|
+
SLheatSource = 0;
|
|
1528
|
+
flags = 1;
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
await this._unit.schedule.setScheduleEventByIdAsync(id, circuit, startTime, endTime, schedDays, flags, SLheatSource, setPoint);
|
|
1532
|
+
return id;
|
|
1533
|
+
} catch (err) {
|
|
1534
|
+
logger.error(`Screenlogic: Error setting schedule ${id}`)
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
public async deleteScheduleAsync(id: number) {
|
|
1538
|
+
try {
|
|
1539
|
+
await this._unit.schedule.deleteScheduleEventByIdAsync(id);
|
|
1540
|
+
} catch (err) {
|
|
1541
|
+
return Promise.reject(err);
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
public async setEggTimerAsync(id: number, runTime: number) {
|
|
1545
|
+
try {
|
|
1546
|
+
await this._unit.circuits.setCircuitRuntimebyIdAsync(id, runTime);
|
|
1547
|
+
} catch (err) {
|
|
1548
|
+
return Promise.reject(err);
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
public async deleteEggTimerAsync(id: number) {
|
|
1552
|
+
try {
|
|
1553
|
+
await this._unit.circuits.setCircuitRuntimebyIdAsync(id, 720);
|
|
1554
|
+
} catch (err) {
|
|
1555
|
+
return Promise.reject(err);
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
export class SLPump extends SLCommands {
|
|
1560
|
+
public async setPumpSpeedAsync(pump: Pump, circuit: PumpCircuit) {
|
|
1561
|
+
|
|
1562
|
+
// PUMPS
|
|
1563
|
+
// let pumpRes = await client.pump.setPumpSpeed(0,1,2000,true);
|
|
1564
|
+
// Currently, this only sets the pump circuit speed. Adding/removing pump needs to be
|
|
1565
|
+
// done through the equipment configuration message.
|
|
1566
|
+
|
|
1567
|
+
// This API call is indexed based.
|
|
1568
|
+
let pumpCircuits = pump.circuits.get();
|
|
1569
|
+
for (let i = 0; i < pumpCircuits.length; i++) {
|
|
1570
|
+
if (pumpCircuits[i].circuit === circuit.circuit) {
|
|
1571
|
+
let res = await this._unit.pump.setPumpSpeedAsync(pump.id, i, circuit.speed || circuit.flow, (circuit.speed || circuit.flow) > 400);
|
|
1572
|
+
if (res) {
|
|
1573
|
+
let pc = pump.circuits.getItemByIndex(i);
|
|
1574
|
+
pc.speed = typeof circuit.speed !== 'undefined' ? circuit.speed : pc.speed;
|
|
1575
|
+
pc.flow = typeof circuit.flow !== 'undefined' ? circuit.flow : pc.flow;
|
|
1576
|
+
return Promise.resolve(pump);
|
|
1577
|
+
}
|
|
1578
|
+
else {
|
|
1579
|
+
return Promise.reject(new InvalidEquipmentDataError('Unable to set pump speed', 'pump', pump))
|
|
1580
|
+
};
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
return Promise.reject(new InvalidEquipmentDataError('Unable to set pump speed. Circuit not found', 'pump', pump));
|
|
1584
|
+
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
export class SLController extends SLCommands {
|
|
1588
|
+
public async setEquipmentAsync(obj?: any, eq?: string) {
|
|
1589
|
+
|
|
1590
|
+
let poolPumpOnDuringHeaterCooldown = false;
|
|
1591
|
+
let spaPumpOnDuringHeaterCooldown = false;
|
|
1592
|
+
for (let i = 0; i < sys.bodies.length; i++) {
|
|
1593
|
+
let bs = state.temps.bodies.getItemById(i + 1);
|
|
1594
|
+
if (bs.circuit === 1) spaPumpOnDuringHeaterCooldown = bs.heaterCooldownDelay;
|
|
1595
|
+
else if (bs.circuit === 6) poolPumpOnDuringHeaterCooldown = bs.heaterCooldownDelay;
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
let highSpeedCircuits = sys.pumps.getDualSpeed().circuits.toArray();
|
|
1599
|
+
let valves = sys.valves.get(true);
|
|
1600
|
+
let remotes = sys.remotes.get(true);
|
|
1601
|
+
let heaters = sys.heaters.get(true);
|
|
1602
|
+
let misc = { ...sys.general.options.get(), poolPumpOnDuringHeaterCooldown, spaPumpOnDuringHeaterCooldown, intellichem: state.chemControllers.getItemById(1, false).isActive || false };
|
|
1603
|
+
let circuitGroup = sys.circuitGroups.get(true);
|
|
1604
|
+
let lightGroup = sys.lightGroups.get(true)[0];
|
|
1605
|
+
let pumps = sys.pumps.get(true);
|
|
1606
|
+
const spaCommand: Remote = sys.remotes.getItemById(8).get();
|
|
1607
|
+
let alarm = 0;
|
|
1608
|
+
|
|
1609
|
+
switch (eq) {
|
|
1610
|
+
case 'misc': {
|
|
1611
|
+
misc = extend({}, true, misc, obj);
|
|
1612
|
+
break;
|
|
1613
|
+
}
|
|
1614
|
+
case 'lightGroup': {
|
|
1615
|
+
lightGroup = extend({}, true, lightGroup, obj);
|
|
1616
|
+
break;
|
|
1617
|
+
}
|
|
1618
|
+
case 'pump': {
|
|
1619
|
+
let idx = pumps.findIndex(el => { console.log(el.id); return el.id === obj.id; })
|
|
1620
|
+
if (idx >= 0) pumps = extend({}, true, pumps[idx], obj);
|
|
1621
|
+
else return Promise.reject(`Screenlogic: No pump found by that id: ${obj}`);
|
|
1622
|
+
break;
|
|
1623
|
+
}
|
|
1624
|
+
case 'heater': {
|
|
1625
|
+
let idx = heaters.findIndex(el => { console.log(el.id); return el.id === obj.id; })
|
|
1626
|
+
if (idx >= 0) heaters = extend({}, true, heaters[idx], obj);
|
|
1627
|
+
else return Promise.reject(`Screenlogic: No pump found by that id: ${obj}`);
|
|
1628
|
+
break;
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
let data = {
|
|
1633
|
+
highSpeedCircuits,
|
|
1634
|
+
valves,
|
|
1635
|
+
remotes,
|
|
1636
|
+
heaters,
|
|
1637
|
+
misc,
|
|
1638
|
+
circuitGroup,
|
|
1639
|
+
lightGroup,
|
|
1640
|
+
pumps,
|
|
1641
|
+
spaCommand,
|
|
1642
|
+
alarm
|
|
1643
|
+
}
|
|
1644
|
+
return Promise.reject(new InvalidOperationError('Operation not implemented yet.', 'setEquipmentConfigurationAsync'));
|
|
1645
|
+
// await this._unit.equipment.setEquipmentConfigurationAsync(data);
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
public async setSystemTime() {
|
|
1649
|
+
try {
|
|
1650
|
+
let sysTime = await this._unit.equipment.setSystemTimeAsync(state.time.toDate(), sys.general.options.adjustDST);
|
|
1651
|
+
logger.silly(`Screenlogic:set time result: ${sysTime}`);
|
|
1652
|
+
} catch (error) {
|
|
1653
|
+
return Promise.reject(new InvalidOperationError('Unable to set system time.', error.message));
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
public async setCustomName(idx: number, name: string) {
|
|
1657
|
+
try {
|
|
1658
|
+
let ack = await this._unit.equipment.setCustomNameAsync(idx, name);
|
|
1659
|
+
logger.silly(`Screenlogic:set custom name result: ${JSON.stringify(ack)}`);
|
|
1660
|
+
} catch (error) {
|
|
1661
|
+
return Promise.reject(new InvalidOperationError('Unable to set custom name.', error.message));
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
export let sl = new ScreenLogicComms();
|