nodejs-poolcontroller 7.6.1 → 7.7.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.
Files changed (91) hide show
  1. package/.eslintrc.json +44 -44
  2. package/.github/ISSUE_TEMPLATE/1-bug-report.yml +84 -0
  3. package/.github/ISSUE_TEMPLATE/2-docs.md +12 -0
  4. package/.github/ISSUE_TEMPLATE/3-proposal.md +28 -0
  5. package/.github/ISSUE_TEMPLATE/config.yml +8 -0
  6. package/CONTRIBUTING.md +74 -74
  7. package/Changelog +220 -215
  8. package/Dockerfile +17 -17
  9. package/Gruntfile.js +40 -40
  10. package/LICENSE +661 -661
  11. package/README.md +191 -191
  12. package/app.ts +1 -1
  13. package/config/Config.ts +14 -0
  14. package/config/VersionCheck.ts +2 -2
  15. package/controller/Constants.ts +2 -1
  16. package/controller/Equipment.ts +2484 -2459
  17. package/controller/Errors.ts +180 -180
  18. package/controller/Lockouts.ts +502 -436
  19. package/controller/State.ts +106 -30
  20. package/controller/boards/AquaLinkBoard.ts +1000 -0
  21. package/controller/boards/BoardFactory.ts +49 -45
  22. package/controller/boards/EasyTouchBoard.ts +2859 -2653
  23. package/controller/boards/IntelliCenterBoard.ts +4198 -4230
  24. package/controller/boards/IntelliComBoard.ts +63 -63
  25. package/controller/boards/IntelliTouchBoard.ts +273 -241
  26. package/controller/boards/NixieBoard.ts +1728 -1675
  27. package/controller/boards/SystemBoard.ts +4925 -4697
  28. package/controller/comms/Comms.ts +442 -479
  29. package/controller/comms/messages/Messages.ts +171 -25
  30. package/controller/comms/messages/config/ChlorinatorMessage.ts +5 -2
  31. package/controller/comms/messages/config/CircuitGroupMessage.ts +0 -0
  32. package/controller/comms/messages/config/CircuitMessage.ts +1 -0
  33. package/controller/comms/messages/config/ConfigMessage.ts +0 -0
  34. package/controller/comms/messages/config/CoverMessage.ts +0 -0
  35. package/controller/comms/messages/config/CustomNameMessage.ts +30 -30
  36. package/controller/comms/messages/config/EquipmentMessage.ts +0 -0
  37. package/controller/comms/messages/config/ExternalMessage.ts +0 -0
  38. package/controller/comms/messages/config/FeatureMessage.ts +0 -0
  39. package/controller/comms/messages/config/GeneralMessage.ts +0 -0
  40. package/controller/comms/messages/config/HeaterMessage.ts +142 -10
  41. package/controller/comms/messages/config/IntellichemMessage.ts +0 -0
  42. package/controller/comms/messages/config/OptionsMessage.ts +4 -21
  43. package/controller/comms/messages/config/PumpMessage.ts +53 -35
  44. package/controller/comms/messages/config/RemoteMessage.ts +0 -0
  45. package/controller/comms/messages/config/ScheduleMessage.ts +350 -347
  46. package/controller/comms/messages/config/SecurityMessage.ts +0 -0
  47. package/controller/comms/messages/config/ValveMessage.ts +1 -1
  48. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +38 -86
  49. package/controller/comms/messages/status/EquipmentStateMessage.ts +58 -22
  50. package/controller/comms/messages/status/HeaterStateMessage.ts +116 -86
  51. package/controller/comms/messages/status/IntelliChemStateMessage.ts +445 -445
  52. package/controller/comms/messages/status/IntelliValveStateMessage.ts +35 -35
  53. package/controller/comms/messages/status/PumpStateMessage.ts +23 -1
  54. package/controller/comms/messages/status/VersionMessage.ts +0 -0
  55. package/controller/nixie/Nixie.ts +162 -162
  56. package/controller/nixie/NixieEquipment.ts +103 -103
  57. package/controller/nixie/bodies/Body.ts +120 -120
  58. package/controller/nixie/bodies/Filter.ts +135 -135
  59. package/controller/nixie/chemistry/ChemController.ts +2511 -2498
  60. package/controller/nixie/chemistry/Chlorinator.ts +363 -314
  61. package/controller/nixie/circuits/Circuit.ts +261 -248
  62. package/controller/nixie/heaters/Heater.ts +650 -648
  63. package/controller/nixie/pumps/Pump.ts +906 -661
  64. package/controller/nixie/schedules/Schedule.ts +313 -257
  65. package/controller/nixie/valves/Valve.ts +170 -170
  66. package/defaultConfig.json +306 -286
  67. package/logger/DataLogger.ts +448 -448
  68. package/logger/Logger.ts +0 -0
  69. package/package.json +56 -56
  70. package/tsconfig.json +25 -25
  71. package/web/Server.ts +92 -47
  72. package/web/bindings/aqualinkD.json +505 -0
  73. package/web/bindings/influxDB.json +1051 -1021
  74. package/web/bindings/mqtt.json +702 -654
  75. package/web/bindings/mqttAlt.json +731 -684
  76. package/web/bindings/rulesManager.json +54 -54
  77. package/web/bindings/smartThings-Hubitat.json +31 -31
  78. package/web/bindings/valveRelays.json +20 -20
  79. package/web/bindings/vera.json +25 -25
  80. package/web/interfaces/baseInterface.ts +137 -136
  81. package/web/interfaces/httpInterface.ts +145 -124
  82. package/web/interfaces/influxInterface.ts +276 -245
  83. package/web/interfaces/mqttInterface.ts +535 -475
  84. package/web/services/config/Config.ts +39 -18
  85. package/web/services/config/ConfigSocket.ts +0 -0
  86. package/web/services/state/State.ts +10 -0
  87. package/web/services/state/StateSocket.ts +4 -4
  88. package/web/services/utilities/Utilities.ts +44 -42
  89. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -52
  90. package/config copy.json +0 -300
  91. package/issue_template.md +0 -52
@@ -1,661 +1,906 @@
1
- import { EquipmentNotFoundError, InvalidEquipmentDataError, InvalidEquipmentIdError, ParameterOutOfRangeError } from '../../Errors';
2
- import { utils, Timestamp } from '../../Constants';
3
- import { logger } from '../../../logger/Logger';
4
-
5
- import { NixieEquipment, NixieChildEquipment, NixieEquipmentCollection, INixieControlPanel } from "../NixieEquipment";
6
- import { Pump, PumpCircuit, PumpCollection, PumpRelay, sys } from "../../../controller/Equipment";
7
- import { CircuitState, PumpState, state, } from "../../State";
8
- import { setTimeout, clearTimeout } from 'timers';
9
- import { NixieControlPanel } from '../Nixie';
10
- import { webApp, InterfaceServerResponse } from "../../../web/Server";
11
- import { Outbound, Protocol } from '../../comms/messages/Messages';
12
- import { conn } from '../../comms/Comms';
13
-
14
- export class NixiePumpCollection extends NixieEquipmentCollection<NixiePump> {
15
- public async deletePumpAsync(id: number) {
16
- try {
17
- for (let i = this.length - 1; i >= 0; i--) {
18
- let pump = this[i];
19
- if (pump.id === id) {
20
- await pump.closeAsync();
21
- this.splice(i, 1);
22
- }
23
- }
24
- } catch (err) { logger.error(`Nixie Control Panel deletePumpAsync ${err.message}`); }
25
- }
26
- public async setPumpStateAsync(pstate: PumpState) {
27
- try {
28
- let pump: NixiePump = this.find(elem => elem.id === pstate.id) as NixiePump;
29
- if (typeof pump === 'undefined') {
30
- return logger.error(`Nixie Control Panel Error setPumpState could not find pump ${pstate.id}-${pstate.name}`);
31
- }
32
- await pump.setPumpStateAsync(pstate);
33
- } catch (err) { logger.error(`Nixie Error setting pump state ${pstate.id}-${pstate.name}: ${err.message}`); return Promise.reject(err); }
34
- }
35
- public async setPumpAsync(pump: Pump, data: any) {
36
- // By the time we get here we know that we are in control and this is a Nixie pump.
37
- try {
38
- let c: NixiePump = this.find(elem => elem.id === pump.id) as NixiePump;
39
- if (typeof c === 'undefined') {
40
- pump.master = 1;
41
- if (typeof data.type !== 'undefined') pump.type = data.type; // needed for init of correct type
42
- if (typeof pump.type === 'undefined') return Promise.reject(new InvalidEquipmentIdError(`Invalid pump type for ${pump.name}`, data.id, 'Pump'));
43
- c = this.pumpFactory(pump);
44
- // c = new NixiePump(this.controlPanel, pump);
45
- this.push(c);
46
- logger.info(`A pump was not found for id #${pump.id} creating pump`);
47
- return await c.setPumpAsync(data);
48
- }
49
- else {
50
- if (typeof data.type !== 'undefined' && c.pump.type !== data.type) {
51
- // pump exists, changing type
52
- await c.closeAsync();
53
- pump.type = data.type; // needed for init of correct type
54
- if (typeof pump.type === 'undefined') return Promise.reject(new InvalidEquipmentIdError(`Invalid pump type for ${pump.name}`, data.id, 'Pump'));
55
- c = this.pumpFactory(pump);
56
- }
57
- return await c.setPumpAsync(data);
58
- }
59
- }
60
- catch (err) { logger.error(`setPumpAsync: ${err.message}`); return Promise.reject(err); }
61
- }
62
- public async initAsync(pumps: PumpCollection) {
63
- try {
64
- for (let i = 0; i < pumps.length; i++) {
65
- let pump = pumps.getItemByIndex(i);
66
- if (pump.master === 1) {
67
- if (typeof this.find(elem => elem.id === pump.id) === 'undefined') {
68
- let type = sys.board.valueMaps.pumpTypes.getName(pump.type);
69
- let npump = this.pumpFactory(pump);
70
- logger.info(`Initializing Nixie Pump ${npump.id}-${pump.name}`);
71
- this.push(npump);
72
- }
73
- }
74
- }
75
- }
76
- catch (err) { logger.error(`Nixie Pump initAsync Error: ${err.message}`); return Promise.reject(err); }
77
- }
78
- public async closeAsync() {
79
- try {
80
- for (let i = this.length - 1; i >= 0; i--) {
81
- try {
82
- await this[i].closeAsync();
83
- this.splice(i, 1);
84
- } catch (err) { logger.error(`Error stopping Nixie Pump ${err}`); }
85
- }
86
- } catch (err) { } // Don't bail if we have an errror.
87
- }
88
-
89
- public async initPumpAsync(pump: Pump): Promise<NixiePump> {
90
- try {
91
- let c: NixiePump = this.find(elem => elem.id === pump.id) as NixiePump;
92
- if (pump.master === 1) {
93
- // if pump exists, close it so we can re-init
94
- // (EG if pump type changes, we need to setup a new instance of the pump)
95
- if (typeof c !== 'undefined' && c.pump.type !== pump.type) {
96
- await c.closeAsync();
97
- c = this.pumpFactory(pump);
98
- }
99
- logger.info(`Initializing Nixie Pump ${c.id}-${pump.name}`);
100
- this.push(c);
101
- }
102
- return c;
103
- } catch (err) { return Promise.reject(logger.error(`Nixie Controller: initPumpAsync Error: ${err.message}`)); }
104
- }
105
- private pumpFactory(pump: Pump) {
106
- let type = sys.board.valueMaps.pumpTypes.getName(pump.type);
107
- switch (type) {
108
- case 'ss':
109
- return new NixiePumpSS(this.controlPanel, pump);
110
- case 'ds':
111
- return new NixiePumpDS(this.controlPanel, pump);
112
- case 'vsf':
113
- return new NixiePumpVSF(this.controlPanel, pump);
114
- case 'vf':
115
- return new NixiePumpVF(this.controlPanel, pump);
116
- case 'sf':
117
- return new NixiePumpSF(this.controlPanel, pump);
118
- case 'vs':
119
- return new NixiePumpVS(this.controlPanel, pump);
120
- default:
121
- throw new EquipmentNotFoundError(`NCP: Cannot create pump ${pump.name}.`, type);
122
- }
123
- }
124
- public syncPumpStates() {
125
- // loop through all pumps and update rates based on circuit changes
126
- // this would happen in <2s anyway based on pollAsync but this is immediate.
127
- for (let i = this.length - 1; i >= 0; i--) {
128
- setTimeout(async () => {
129
- let pump = this[i] as NixiePump;
130
- try {
131
- if (!pump.closing) await pump.pollEquipmentAsync();
132
- } catch (err) { }
133
- }, 100);
134
-
135
- }
136
- }
137
- }
138
- export class NixiePump extends NixieEquipment {
139
- public pollingInterval: number = 2000;
140
- protected _pollTimer: NodeJS.Timeout = null;
141
- public pump: Pump;
142
- protected _targetSpeed: number;
143
- protected _suspendPolling = 0;
144
- public get suspendPolling(): boolean { return this._suspendPolling > 0; }
145
- public set suspendPolling(val: boolean) { this._suspendPolling = Math.max(0, this._suspendPolling + (val ? 1 : -1)); }
146
- public closing = false;
147
-
148
- /*
149
- _targetSpeed will hold values as follows:
150
- vs/vsf/vf: rpm/gpm;
151
- ss: 0=off, 1=on;
152
- ds/sf: bit shift 1-4 = values 1/2/4/8 for relays 1/2/3/4
153
- */
154
- constructor(ncp: INixieControlPanel, pump: Pump) {
155
- super(ncp);
156
- this.pump = pump;
157
- this._targetSpeed = 0;
158
- this.pollEquipmentAsync();
159
- }
160
- public get id(): number { return typeof this.pump !== 'undefined' ? this.pump.id : -1; }
161
- public async setPumpStateAsync(pstate: PumpState) {
162
- try {
163
- // Here we go we need to set the pump state.
164
- return new InterfaceServerResponse(200, 'Ok');
165
- } catch (err) { return Promise.reject(`Nixie Error setting pump state ${pstate.id}-${pstate.name}: ${err.message}`); }
166
- }
167
- public async setPumpAsync(data: any): Promise<InterfaceServerResponse> {
168
- try {
169
-
170
- this.pump.master = 1;
171
- // if (typeof data.isVirtual !== 'undefined') this.pump.isVirtual = data.isVirtual;
172
- this.pump.isActive = true;
173
- // if (typeof data.type !== 'undefined' && data.type !== this.pump.type) {
174
- // sys.board.pumps.setType(this.pump, data.type);
175
- // this.pump = sys.pumps.getItemById(id, true);
176
- // spump = state.pumps.getItemById(id, true);
177
- // }
178
- let type = sys.board.valueMaps.pumpTypes.transform(this.pump.type);
179
- this.pump.name = data.name || this.pump.name || type.desc;
180
- if (typeof type.maxCircuits !== 'undefined' && type.maxCircuits > 0 && typeof data.circuits !== 'undefined') { // This pump type supports circuits
181
- for (let i = 1; i <= data.circuits.length && i <= type.maxCircuits; i++) {
182
- let c = data.circuits[i - 1];
183
- let speed = parseInt(c.speed, 10);
184
- let relay = parseInt(c.relay, 10);
185
- let flow = parseInt(c.flow, 10);
186
- if (isNaN(speed)) speed = type.minSpeed;
187
- if (isNaN(flow)) flow = type.minFlow;
188
- if (isNaN(relay)) relay = 1;
189
- c.units = parseInt(c.units, 10) || type.name === 'vf' ? sys.board.valueMaps.pumpUnits.getValue('gpm') : sys.board.valueMaps.pumpUnits.getValue('rpm');
190
- if (typeof type.minSpeed !== 'undefined' && c.units === sys.board.valueMaps.pumpUnits.getValue('rpm')) {
191
- c.speed = speed;
192
- }
193
- else if (typeof type.minFlow !== 'undefined' && c.units === sys.board.valueMaps.pumpUnits.getValue('gpm')) {
194
- c.flow = flow;
195
- }
196
- else if (type.maxRelays > 0)
197
- c.relay = relay;
198
- }
199
- }
200
- this.pump.set(data); // Sets all the data back to the pump. This also sets the relays should it exist on the data.
201
- let spump = state.pumps.getItemById(this.pump.id, true);
202
- spump.name = this.pump.name;
203
- spump.address = this.pump.address;
204
- spump.type = this.pump.type;
205
- sys.pumps.sortById();
206
- state.pumps.sortById();
207
- return Promise.resolve(new InterfaceServerResponse(200, 'Ok'));
208
- }
209
- catch (err) { logger.error(`Nixie setPumpAsync: ${err.message}`); return Promise.reject(err); }
210
- }
211
- public async pollEquipmentAsync() {
212
- let self = this;
213
- try {
214
- if (this.suspendPolling || this.closing) return;
215
- if (typeof this._pollTimer !== 'undefined' || this._pollTimer) clearTimeout(this._pollTimer);
216
- this._pollTimer = null;
217
- // let success = false;
218
- let pstate = state.pumps.getItemById(this.pump.id);
219
- this.setTargetSpeed(pstate);
220
- await this.setPumpStateAsync(pstate);
221
- }
222
- catch (err) { logger.error(`Nixie Error running pump sequence - ${err}`); }
223
- finally { if (!this.closing) this._pollTimer = setTimeout(async () => await self.pollEquipmentAsync(), this.pollingInterval || 2000); }
224
- }
225
- private async checkHardwareStatusAsync(connectionId: string, deviceBinding: string) {
226
- try {
227
- let dev = await NixieEquipment.getDeviceService(connectionId, `/status/device/${deviceBinding}`);
228
- return dev;
229
- } catch (err) { logger.error(`Nixie Pump checkHardwareStatusAsync: ${err.message}`); return { hasFault: true } }
230
- }
231
- public async validateSetupAsync(pump: Pump, pstate: PumpState) {
232
- try {
233
- } catch (err) { logger.error(`Nixie Error checking Pump Hardware ${this.pump.name}: ${err.message}`); return Promise.reject(err); }
234
- }
235
- public async closeAsync() {
236
- try {
237
- logger.info(`Nixie Pump closing ${this.pump.name}.`)
238
- if (typeof this._pollTimer !== 'undefined' || this._pollTimer) clearTimeout(this._pollTimer);
239
- this._pollTimer = null;
240
- this._targetSpeed = 0;
241
- let pstate = state.pumps.getItemById(this.pump.id);
242
- try {
243
- await this.setPumpStateAsync(pstate);
244
- // Since we are closing we need to not reject.
245
- } catch (err) { logger.error(`Nixie Closing pump closeAsync: ${err.message}`); }
246
- // This will make sure the timer is dead and we are completely closed.
247
- this.closing = true;
248
- if (typeof this._pollTimer !== 'undefined' || this._pollTimer) clearTimeout(this._pollTimer);
249
- pstate.emitEquipmentChange();
250
- }
251
- catch (err) { logger.error(`Nixie Pump closeAsync: ${err.message}`); return Promise.reject(err); }
252
- }
253
- public logData(filename: string, data: any) { this.controlPanel.logData(filename, data); }
254
- protected setTargetSpeed(pstate: PumpState) { };
255
- protected isBodyOn(bodyCode: number) {
256
- let assoc = sys.board.valueMaps.pumpBodies.transform(bodyCode);
257
- switch (assoc.name) {
258
- case 'body1':
259
- case 'pool':
260
- return state.temps.bodies.getItemById(1).isOn;
261
- case 'body2':
262
- case 'spa':
263
- return state.temps.bodies.getItemById(2).isOn;
264
- case 'body3':
265
- return state.temps.bodies.getItemById(3).isOn;
266
- case 'body4':
267
- return state.temps.bodies.getItemById(4).isOn;
268
- case 'poolspa':
269
- if (sys.equipment.shared && sys.equipment.maxBodies >= 2) {
270
- return state.temps.bodies.getItemById(1).isOn === true || state.temps.bodies.getItemById(2).isOn === true;
271
- }
272
- else
273
- return state.temps.bodies.getItemById(1).isOn;
274
- }
275
- return false;
276
- }
277
- }
278
- export class NixiePumpSS extends NixiePump {
279
- public setTargetSpeed(pState: PumpState) {
280
- // Turn on ss pumps.
281
- let _newSpeed = 0;
282
- if (!pState.pumpOnDelay) {
283
- let pt = sys.board.valueMaps.pumpTypes.get(this.pump.type);
284
- if (pt.hasBody) _newSpeed = this.isBodyOn(this.pump.body) ? 1 : 0;
285
- //console.log(`BODY: ${sys.board.bodies.isBodyOn(this.pump.body)} CODE: ${this.pump.body}`);
286
- }
287
- if (this._targetSpeed !== _newSpeed) logger.info(`NCP: Setting Pump ${this.pump.name} to ${_newSpeed > 0 ? 'on' : 'off'}. ${sys.board.bodies.isBodyOn(this.pump.body)}`);
288
- if (isNaN(_newSpeed)) _newSpeed = 0;
289
- this._targetSpeed = _newSpeed;
290
- }
291
- public async setPumpStateAsync(pstate: PumpState) {
292
- let relays: PumpRelay[] = this.pump.relays.get();
293
- let relayState = 0;
294
- for (let i = 0; i < relays.length; i++) {
295
- let pr = relays[i];
296
- if (typeof pr.id === 'undefined') pr.id = i + 1; // remove when id is added to dP relays upon save.
297
- let isOn = this._targetSpeed >> pr.id - 1 & 1;
298
- if (utils.isNullOrEmpty(pr.connectionId) || utils.isNullOrEmpty(pr.deviceBinding)) {
299
- // If they haven't set a program for the relay bugger out.
300
- if (isOn) relayState |= (1 << pr.id - 1);
301
- }
302
- else {
303
- try {
304
- let res = await NixieEquipment.putDeviceService(pr.connectionId, `/state/device/${pr.deviceBinding}`, { isOn, latch: isOn ? 5000 : undefined });
305
- if (res.status.code === 200) {
306
- if (isOn) relayState |= (1 << pr.id - 1);
307
- }
308
- else pstate.status = 16;
309
- }
310
- catch (err) {
311
- logger.error(`NCP: Error setting pump ${this.pump.name} relay ${pr.id} to ${isOn ? 'on' : 'off'}. Error ${err.message}}`);
312
- pstate.status = 16;
313
- }
314
- }
315
- }
316
- if (pstate.targetSpeed === 0) {
317
- pstate.status = 0;
318
- pstate.driveState = 0; // We need to set this if it is a priming cycle but it might not matter for our relay based pumps.
319
- pstate.command = 0;
320
- }
321
- else if (relayState === pstate.targetSpeed) {
322
- pstate.status = 1;
323
- pstate.driveState = 2;
324
- pstate.command = 4;
325
- }
326
- pstate.relay = relayState;
327
- return new InterfaceServerResponse(200, 'Success');
328
- }
329
- }
330
- export class NixiePumpDS extends NixiePumpSS {
331
- public setTargetSpeed(pState: PumpState) {
332
- // Turn on sf pumps. The new speed will be the relays associated with the pump. I believe when this comes out in the final
333
- // wash it should engage all the relays for all speeds associated with the pump. The pump logic will determine which program is
334
- // the one to engage.
335
- let _newSpeed = 0;
336
- if (!pState.pumpOnDelay) {
337
- let pumpCircuits: PumpCircuit[] = this.pump.circuits.get();
338
- if (!pState.pumpOnDelay) {
339
- for (let i = 0; i < pumpCircuits.length; i++) {
340
- let circ = state.circuits.getInterfaceById(pumpCircuits[i].circuit);
341
- // relay speeds are bit-shifted 'or' based on 1,2,4,8
342
- if (circ.isOn) _newSpeed |= (1 << pumpCircuits[i].relay - 1);
343
- }
344
- }
345
- }
346
- if (isNaN(_newSpeed)) _newSpeed = 0;
347
- this.logSpeed(_newSpeed);
348
- this._targetSpeed = _newSpeed;
349
- }
350
- public logSpeed(_newSpeed: number) {
351
- if (this._targetSpeed !== _newSpeed) logger.info(`NCP: Setting Pump ${this.pump.name} relays to Relay 1: ${_newSpeed & 1 ? 'on' : 'off'}, Relay 2: ${_newSpeed & 2 ? 'on' : 'off'}.`);
352
- }
353
- }
354
- export class NixiePumpSF extends NixiePumpDS {
355
- // effectively operates the same way as a DS pump since we removed the body association on DS.
356
- // only logger msg is different
357
- public logSpeed(_newSpeed: number) {
358
- if (this._targetSpeed !== _newSpeed) logger.info(`NCP: Setting Pump ${this.pump.name} relays to Relay 1: ${_newSpeed & 1 ? 'on' : 'off'}, Relay 2: ${_newSpeed & 2 ? 'on' : 'off'}, Relay 3: ${_newSpeed & 4 ? 'on' : 'off'}, and Relay 4: ${_newSpeed & 8 ? 'on' : 'off'}.`);
359
- }
360
- }
361
- export class NixiePumpRS485 extends NixiePump {
362
- public async setPumpStateAsync(pstate: PumpState) {
363
- // Don't poll while we are seting the state.
364
- this.suspendPolling = true;
365
- try {
366
- let pt = sys.board.valueMaps.pumpTypes.get(this.pump.type);
367
- // Since these process are async the closing flag can be set
368
- // between calls. We need to check it in between each call.
369
- try { if (!this.closing) await this.setDriveStateAsync(); } catch (err) {}
370
- try { if (!this.closing) {
371
- if (this._targetSpeed >= pt.minFlow && this._targetSpeed <= pt.maxFlow) await this.setPumpGPMAsync();
372
- else if (this._targetSpeed >= pt.minSpeed && this._targetSpeed <= pt.maxSpeed) await this.setPumpRPMAsync();
373
- } } catch (err) {}
374
-
375
- try { if(!this.closing) await this.setPumpFeature(6); } catch (err) {};
376
- try { if(!this.closing) await utils.sleep(1000); } catch (err) {};
377
- try { if(!this.closing) await this.requestPumpStatus(); } catch (err) {};
378
- try { if(!this.closing) await this.setPumpToRemoteControl(); } catch (err) {};
379
- return new InterfaceServerResponse(200, 'Success');
380
- }
381
- catch (err) {
382
- logger.error(`Error running pump sequence for ${this.pump.name}: ${err.message}`);
383
- return Promise.reject(err);
384
- }
385
- finally { this.suspendPolling = false; }
386
- };
387
- protected async setDriveStateAsync(running: boolean = true) {
388
- return new Promise<void>((resolve, reject) => {
389
- let out = Outbound.create({
390
- protocol: Protocol.Pump,
391
- dest: this.pump.address,
392
- action: 6,
393
- payload: running && this._targetSpeed > 0 ? [10] : [4],
394
- retries: 1,
395
- response: true,
396
- onComplete: (err, msg: Outbound) => {
397
- if (err) {
398
- logger.error(`Error sending setDriveState for ${this.pump.name} : ${err.message}`);
399
- reject(err);
400
- }
401
- else resolve();
402
- }
403
- });
404
- conn.queueSendMessage(out);
405
- });
406
- };
407
- protected async requestPumpStatus() {
408
- return new Promise<void>((resolve, reject) => {
409
- let out = Outbound.create({
410
- protocol: Protocol.Pump,
411
- dest: this.pump.address,
412
- action: 7,
413
- payload: [],
414
- retries: 2,
415
- response: true,
416
- onComplete: (err, msg) => {
417
- if (err) {
418
- logger.error(`Error sending requestPumpStatus for ${this.pump.name}: ${err.message}`);
419
- reject(err);
420
- }
421
- else resolve();
422
- }
423
- });
424
- conn.queueSendMessage(out);
425
- })
426
- };
427
- protected setPumpToRemoteControl(running: boolean = true) {
428
- return new Promise<void>((resolve, reject) => {
429
- let out = Outbound.create({
430
- protocol: Protocol.Pump,
431
- dest: this.pump.address,
432
- action: 4,
433
- payload: running ? [255] : [0], // when stopAsync is called, pass false to return control to pump panel
434
- // payload: spump.virtualControllerStatus === sys.board.valueMaps.virtualControllerStatus.getValue('running') ? [255] : [0],
435
- retries: 1,
436
- response: true,
437
- onComplete: (err) => {
438
- if (err) {
439
- logger.error(`Error sending setPumpToRemoteControl for ${this.pump.name}: ${err.message}`);
440
- reject(err);
441
- }
442
- else resolve();
443
- }
444
- });
445
- conn.queueSendMessage(out);
446
- });
447
- }
448
- protected setPumpFeature(feature?: number) {
449
- // empty payload (possibly 0?, too) is no feature
450
- // 6: Feature 1
451
- return new Promise<void>((resolve, reject) => {
452
- let out = Outbound.create({
453
- protocol: Protocol.Pump,
454
- dest: this.pump.address,
455
- action: 5,
456
- payload: typeof feature === 'undefined' ? [] : [ feature ],
457
- retries: 2,
458
- repsonse: true,
459
- onComplete: (err, msg: Outbound) => {
460
- if (err) {
461
- logger.error(`Error sending setPumpManual for ${this.pump.name}: ${err.message}`);
462
- reject(err);
463
- }
464
- else resolve();
465
- }
466
- });
467
- conn.queueSendMessage(out);
468
- });
469
- };
470
- protected async setPumpRPMAsync() {
471
- return new Promise<void>((resolve, reject) => {
472
- let out = Outbound.create({
473
- protocol: Protocol.Pump,
474
- dest: this.pump.address,
475
- action: 1,
476
- payload: [2, 196, Math.floor(this._targetSpeed / 256), this._targetSpeed % 256],
477
- retries: 1,
478
- // timeout: 250,
479
- response: true,
480
- onComplete: (err, msg) => {
481
- if (err) {
482
- logger.error(`Error sending setPumpRPMAsync for ${this.pump.name}: ${err.message}`);
483
- reject(err);
484
- }
485
- else resolve();
486
- }
487
- });
488
- conn.queueSendMessage(out);
489
- });
490
- };
491
- protected async setPumpGPMAsync() {
492
- // packet for vf; vsf will override
493
- return new Promise<void>((resolve, reject) => {
494
- let out = Outbound.create({
495
- protocol: Protocol.Pump,
496
- dest: this.pump.address,
497
- action: 1,
498
- payload: [2, 228, 0, this._targetSpeed],
499
- retries: 1,
500
- response: true,
501
- onComplete: (err, msg) => {
502
- if (err) {
503
- logger.error(`Error sending setPumpGPMAsync for ${this.pump.name}: ${err.message}`);
504
- reject(err);
505
- }
506
- else resolve();
507
- }
508
- });
509
- conn.queueSendMessage(out);
510
- });
511
- };
512
- public async closeAsync() {
513
- try {
514
- this.suspendPolling = true;
515
- logger.info(`Nixie Pump closing ${this.pump.name}.`)
516
- if (typeof this._pollTimer !== 'undefined' || this._pollTimer) clearTimeout(this._pollTimer);
517
- this._pollTimer = null;
518
- let pstate = state.pumps.getItemById(this.pump.id);
519
- this._targetSpeed = 0;
520
- try { await this.setDriveStateAsync(false); } catch (err) { logger.error(`Error closing pump ${this.pump.name}: ${err.message}`) }
521
- try { await this.setPumpFeature(); } catch (err) { logger.error(`Error closing pump ${this.pump.name}: ${err.message}`) }
522
- try { await this.setDriveStateAsync(false); } catch (err) { logger.error(`Error closing pump ${this.pump.name}: ${err.message}`) }
523
- try { await this.setPumpToRemoteControl(false); } catch (err) { logger.error(`Error closing pump ${this.pump.name}: ${err.message}`) }
524
- this.closing = true;
525
- // Make sure the polling timer is dead after we have closted this all off. That way we do not
526
- // have another process that revives it from the dead.
527
- if (typeof this._pollTimer !== 'undefined' || this._pollTimer) clearTimeout(this._pollTimer);
528
- this._pollTimer = null;
529
- pstate.emitEquipmentChange();
530
- }
531
- catch (err) { logger.error(`Nixie Pump closeAsync: ${err.message}`); return Promise.reject(err); }
532
- finally { this.suspendPolling = false; }
533
- }
534
- }
535
- export class NixiePumpVS extends NixiePumpRS485 {
536
- public setTargetSpeed(pState: PumpState) {
537
- let _newSpeed = 0;
538
- if (!pState.pumpOnDelay) {
539
- let pumpCircuits = this.pump.circuits.get();
540
-
541
- for (let i = 0; i < pumpCircuits.length; i++) {
542
- let circ = state.circuits.getInterfaceById(pumpCircuits[i].circuit);
543
- let pc = pumpCircuits[i];
544
- if (circ.isOn) _newSpeed = Math.max(_newSpeed, pc.speed);
545
- }
546
- }
547
- if (isNaN(_newSpeed)) _newSpeed = 0;
548
- this._targetSpeed = _newSpeed;
549
- if (this._targetSpeed !== 0) Math.min(Math.max(this.pump.minSpeed, this._targetSpeed), this.pump.maxSpeed);
550
- if (this._targetSpeed !== _newSpeed) logger.info(`NCP: Setting Pump ${this.pump.name} to ${_newSpeed} RPM.`);
551
- }
552
- }
553
- export class NixiePumpVF extends NixiePumpRS485 {
554
- public setTargetSpeed(pState: PumpState) {
555
- let _newSpeed = 0;
556
- if (!pState.pumpOnDelay) {
557
- let pumpCircuits = this.pump.circuits.get();
558
- for (let i = 0; i < pumpCircuits.length; i++) {
559
- let circ = state.circuits.getInterfaceById(pumpCircuits[i].circuit);
560
- let pc = pumpCircuits[i];
561
- if (circ.isOn) _newSpeed = Math.max(_newSpeed, pc.flow);
562
- }
563
- }
564
- if (isNaN(_newSpeed)) _newSpeed = 0;
565
- this._targetSpeed = _newSpeed;
566
- if (this._targetSpeed !== 0) Math.min(Math.max(this.pump.minFlow, this._targetSpeed), this.pump.maxFlow);
567
- if (this._targetSpeed !== _newSpeed) logger.info(`NCP: Setting Pump ${this.pump.name} to ${_newSpeed} GPM.`);
568
- }
569
- }
570
- export class NixiePumpVSF extends NixiePumpRS485 {
571
- public setTargetSpeed(pState: PumpState) {
572
- let _newSpeed = 0;
573
- let maxRPM = 0;
574
- let maxGPM = 0;
575
- let flows = 0;
576
- let speeds = 0;
577
- if (!pState.pumpOnDelay) {
578
- let pumpCircuits = this.pump.circuits.get();
579
- let pt = sys.board.valueMaps.pumpTypes.get(this.pump.type);
580
- // VSF pumps present a problem. In fact they do not currently operate properly on Touch panels. On touch these need to either be all in RPM or GPM
581
- // if there is a mix in the circuit array then they will not work. In IntelliCenter if there is an RPM setting in the mix it will use RPM by converting
582
- // the GPM to RPM but if there is none then it will use GPM.
583
- let toRPM = (flowRate: number, minSpeed: number = 450, maxSpeed: number = 3450) => {
584
- let eff = .03317 * maxSpeed;
585
- let rpm = Math.min((flowRate * maxSpeed) / eff, maxSpeed);
586
- return rpm > 0 ? Math.max(rpm, minSpeed) : 0;
587
- };
588
- let toGPM = (speed: number, maxSpeed: number = 3450, minFlow: number = 15, maxFlow: number = 140) => {
589
- let eff = .03317 * maxSpeed;
590
- let gpm = Math.min((eff * speed) / maxSpeed, maxFlow);
591
- return gpm > 0 ? Math.max(gpm, minFlow) : 0;
592
- }
593
- for (let i = 0; i < pumpCircuits.length; i++) {
594
- let circ = state.circuits.getInterfaceById(pumpCircuits[i].circuit);
595
- let pc = pumpCircuits[i];
596
- if (circ.isOn) {
597
- if (pc.units > 0) {
598
- maxGPM = Math.max(maxGPM, pc.flow);
599
- // Calculate an RPM from this flow.
600
- maxRPM = Math.max(maxGPM, toRPM(pc.flow, pt.minSpeed, pt.maxSpeed));
601
- flows++;
602
- }
603
- else {
604
- maxRPM = Math.max(maxRPM, pc.speed);
605
- maxGPM = Math.max(maxGPM, toGPM(pc.speed, pt.maxSpeed, pt.minFlow, pt.maxFlow));
606
- speeds++;
607
- }
608
- }
609
- }
610
- _newSpeed = speeds > 0 || flows === 0 ? maxRPM : maxGPM;
611
- }
612
- if (isNaN(_newSpeed)) _newSpeed = 0;
613
- // Send the flow message if it is flow and the rpm message if it is rpm.
614
- if (this._targetSpeed !== _newSpeed) logger.info(`NCP: Setting Pump ${this.pump.name} to ${_newSpeed} ${flows > 0 ? 'GPM' : 'RPM'}.`);
615
- this._targetSpeed = _newSpeed;
616
- }
617
- protected async setPumpRPMAsync() {
618
- // vsf action is 10 for rpm
619
- return new Promise<void>((resolve, reject) => {
620
- let out = Outbound.create({
621
- protocol: Protocol.Pump,
622
- dest: this.pump.address,
623
- action: 10,
624
- payload: [2, 196, Math.floor(this._targetSpeed / 256), this._targetSpeed % 256],
625
- retries: 1,
626
- // timeout: 250,
627
- response: true,
628
- onComplete: (err, msg) => {
629
- if (err) {
630
- logger.error(`Error sending setPumpRPMAsync for ${this.pump.name}: ${err.message}`);
631
- reject(err);
632
- }
633
- else resolve();
634
- }
635
- });
636
- conn.queueSendMessage(out);
637
- });
638
- };
639
- protected async setPumpGPMAsync() {
640
- // vsf payload; different from vf payload
641
- return new Promise<void>((resolve, reject) => {
642
- let out = Outbound.create({
643
- protocol: Protocol.Pump,
644
- dest: this.pump.address,
645
- action: 9,
646
- payload: [2, 196, 0, this._targetSpeed],
647
- retries: 1,
648
- response: true,
649
- onComplete: (err, msg) => {
650
- if (err) {
651
- logger.error(`Error sending setPumpGPMAsync for ${this.pump.name}: ${err.message}`);
652
- reject(err);
653
- }
654
- else resolve();
655
- return
656
- }
657
- });
658
- conn.queueSendMessage(out);
659
- });
660
- };
661
- };
1
+ import { EquipmentNotFoundError, InvalidEquipmentDataError, InvalidEquipmentIdError, ParameterOutOfRangeError } from '../../Errors';
2
+ import { utils, Timestamp } from '../../Constants';
3
+ import { logger } from '../../../logger/Logger';
4
+
5
+ import { NixieEquipment, NixieChildEquipment, NixieEquipmentCollection, INixieControlPanel } from "../NixieEquipment";
6
+ import { Pump, PumpCircuit, PumpCollection, PumpRelay, sys } from "../../../controller/Equipment";
7
+ import { CircuitState, PumpState, state, } from "../../State";
8
+ import { setTimeout, clearTimeout } from 'timers';
9
+ import { NixieControlPanel } from '../Nixie';
10
+ import { webApp, InterfaceServerResponse } from "../../../web/Server";
11
+ import { Outbound, Protocol, Response } from '../../comms/messages/Messages';
12
+ import { conn } from '../../comms/Comms';
13
+
14
+ export class NixiePumpCollection extends NixieEquipmentCollection<NixiePump> {
15
+ public async deletePumpAsync(id: number) {
16
+ try {
17
+ for (let i = this.length - 1; i >= 0; i--) {
18
+ let pump = this[i];
19
+ if (pump.id === id) {
20
+ await pump.closeAsync();
21
+ this.splice(i, 1);
22
+ }
23
+ }
24
+ } catch (err) { logger.error(`Nixie Control Panel deletePumpAsync ${err.message}`); }
25
+ }
26
+ public async setPumpStateAsync(pstate: PumpState) {
27
+ try {
28
+ let pump: NixiePump = this.find(elem => elem.id === pstate.id) as NixiePump;
29
+ if (typeof pump === 'undefined') {
30
+ return logger.error(`Nixie Control Panel Error setPumpState could not find pump ${pstate.id}-${pstate.name}`);
31
+ }
32
+ await pump.setPumpStateAsync(pstate);
33
+ } catch (err) { logger.error(`Nixie Error setting pump state ${pstate.id}-${pstate.name}: ${err.message}`); return Promise.reject(err); }
34
+ }
35
+ public async setPumpAsync(pump: Pump, data: any) {
36
+ // By the time we get here we know that we are in control and this is a Nixie pump.
37
+ try {
38
+ let c: NixiePump = this.find(elem => elem.id === pump.id) as NixiePump;
39
+ if (typeof c === 'undefined') {
40
+ pump.master = 1;
41
+ if (typeof data.type !== 'undefined') pump.type = data.type; // needed for init of correct type
42
+ if (typeof pump.type === 'undefined') return Promise.reject(new InvalidEquipmentIdError(`Invalid pump type for ${pump.name}`, data.id, 'Pump'));
43
+ c = this.pumpFactory(pump);
44
+ // c = new NixiePump(this.controlPanel, pump);
45
+ this.push(c);
46
+ logger.info(`A pump was not found for id #${pump.id} creating pump`);
47
+ return await c.setPumpAsync(data);
48
+ }
49
+ else {
50
+ if (typeof data.type !== 'undefined' && c.pump.type !== data.type) {
51
+ // pump exists, changing type
52
+ await c.closeAsync();
53
+ pump.type = data.type; // needed for init of correct type
54
+ if (typeof pump.type === 'undefined') return Promise.reject(new InvalidEquipmentIdError(`Invalid pump type for ${pump.name}`, data.id, 'Pump'));
55
+ c = this.pumpFactory(pump);
56
+ }
57
+ return await c.setPumpAsync(data);
58
+ }
59
+ }
60
+ catch (err) { logger.error(`setPumpAsync: ${err.message}`); return Promise.reject(err); }
61
+ }
62
+ public async initAsync(pumps: PumpCollection) {
63
+ try {
64
+ for (let i = 0; i < pumps.length; i++) {
65
+ let pump = pumps.getItemByIndex(i);
66
+ if (pump.master === 1) {
67
+ if (typeof this.find(elem => elem.id === pump.id) === 'undefined') {
68
+ let type = sys.board.valueMaps.pumpTypes.getName(pump.type);
69
+ let npump = this.pumpFactory(pump);
70
+ logger.info(`Initializing Nixie Pump ${npump.id}-${pump.name}`);
71
+ this.push(npump);
72
+ }
73
+ }
74
+ }
75
+ }
76
+ catch (err) { logger.error(`Nixie Pump initAsync Error: ${err.message}`); return Promise.reject(err); }
77
+ }
78
+ public async closeAsync() {
79
+ try {
80
+ for (let i = this.length - 1; i >= 0; i--) {
81
+ try {
82
+ await this[i].closeAsync();
83
+ this.splice(i, 1);
84
+ } catch (err) { logger.error(`Error stopping Nixie Pump ${err}`); }
85
+ }
86
+ } catch (err) { } // Don't bail if we have an errror.
87
+ }
88
+
89
+ public async initPumpAsync(pump: Pump): Promise<NixiePump> {
90
+ try {
91
+ let c: NixiePump = this.find(elem => elem.id === pump.id) as NixiePump;
92
+ if (pump.master === 1) {
93
+ // if pump exists, close it so we can re-init
94
+ // (EG if pump type changes, we need to setup a new instance of the pump)
95
+ if (typeof c !== 'undefined' && c.pump.type !== pump.type) {
96
+ await c.closeAsync();
97
+ c = this.pumpFactory(pump);
98
+ }
99
+ logger.info(`Initializing Nixie Pump ${c.id}-${pump.name}`);
100
+ this.push(c);
101
+ }
102
+ return c;
103
+ } catch (err) { return Promise.reject(logger.error(`Nixie Controller: initPumpAsync Error: ${err.message}`)); }
104
+ }
105
+ private pumpFactory(pump: Pump) {
106
+ let type = sys.board.valueMaps.pumpTypes.getName(pump.type);
107
+ switch (type) {
108
+ case 'ss':
109
+ return new NixiePumpSS(this.controlPanel, pump);
110
+ case 'ds':
111
+ return new NixiePumpDS(this.controlPanel, pump);
112
+ case 'vsf':
113
+ return new NixiePumpVSF(this.controlPanel, pump);
114
+ case 'vf':
115
+ return new NixiePumpVF(this.controlPanel, pump);
116
+ case 'sf':
117
+ return new NixiePumpSF(this.controlPanel, pump);
118
+ case 'vs':
119
+ return new NixiePumpVS(this.controlPanel, pump);
120
+ case 'hwvs':
121
+ return new NixiePumpHWVS(this.controlPanel, pump);
122
+ case 'hwrly':
123
+ return new NixiePumpHWRLY(this.controlPanel, pump);
124
+ default:
125
+ throw new EquipmentNotFoundError(`NCP: Cannot create pump ${pump.name}.`, type);
126
+ }
127
+ }
128
+ public syncPumpStates() {
129
+ // loop through all pumps and update rates based on circuit changes
130
+ // this would happen in <2s anyway based on pollAsync but this is immediate.
131
+ for (let i = this.length - 1; i >= 0; i--) {
132
+ setTimeout(async () => {
133
+ let pump = this[i] as NixiePump;
134
+ try {
135
+ if (!pump.closing) await pump.pollEquipmentAsync();
136
+ } catch (err) { }
137
+ }, 100);
138
+
139
+ }
140
+ }
141
+ }
142
+ export class NixiePump extends NixieEquipment {
143
+ public pollingInterval: number = 2000;
144
+ protected _pollTimer: NodeJS.Timeout = null;
145
+ public pump: Pump;
146
+ protected _targetSpeed: number;
147
+ protected _suspendPolling = 0;
148
+ public get suspendPolling(): boolean { return this._suspendPolling > 0; }
149
+ public set suspendPolling(val: boolean) { this._suspendPolling = Math.max(0, this._suspendPolling + (val ? 1 : -1)); }
150
+ public closing = false;
151
+
152
+ /*
153
+ _targetSpeed will hold values as follows:
154
+ vs/vsf/vf: rpm/gpm;
155
+ ss: 0=off, 1=on;
156
+ ds/sf: bit shift 1-4 = values 1/2/4/8 for relays 1/2/3/4
157
+ */
158
+ constructor(ncp: INixieControlPanel, pump: Pump) {
159
+ super(ncp);
160
+ this.pump = pump;
161
+ this._targetSpeed = 0;
162
+ this.pollEquipmentAsync();
163
+ }
164
+ public get id(): number { return typeof this.pump !== 'undefined' ? this.pump.id : -1; }
165
+ public async setPumpStateAsync(pstate: PumpState) {
166
+ try {
167
+ // Here we go we need to set the pump state.
168
+ return new InterfaceServerResponse(200, 'Ok');
169
+ } catch (err) { return Promise.reject(`Nixie Error setting pump state ${pstate.id}-${pstate.name}: ${err.message}`); }
170
+ }
171
+ public async setPumpAsync(data: any): Promise<InterfaceServerResponse> {
172
+ try {
173
+
174
+ this.pump.master = 1;
175
+ // if (typeof data.isVirtual !== 'undefined') this.pump.isVirtual = data.isVirtual;
176
+ this.pump.isActive = true;
177
+ // if (typeof data.type !== 'undefined' && data.type !== this.pump.type) {
178
+ // sys.board.pumps.setType(this.pump, data.type);
179
+ // this.pump = sys.pumps.getItemById(id, true);
180
+ // spump = state.pumps.getItemById(id, true);
181
+ // }
182
+ let type = sys.board.valueMaps.pumpTypes.transform(this.pump.type);
183
+ this.pump.name = data.name || this.pump.name || type.desc;
184
+ if (typeof type.maxCircuits !== 'undefined' && type.maxCircuits > 0 && typeof data.circuits !== 'undefined') { // This pump type supports circuits
185
+ for (let i = 1; i <= data.circuits.length && i <= type.maxCircuits; i++) {
186
+ let c = data.circuits[i - 1];
187
+ let circuit = parseInt(c.circuit, 10);
188
+ let cd = this.pump.circuits.find(elem => elem.circuit === circuit);
189
+ let speed = parseInt(c.speed, 10);
190
+ let relay = parseInt(c.relay, 10);
191
+ let flow = parseInt(c.flow, 10);
192
+ let units = typeof c.units !== 'undefined' ? sys.board.valueMaps.pumpUnits.encode(c.units) : undefined;
193
+ switch (type.name) {
194
+ case 'vf':
195
+ units = sys.board.valueMaps.pumpUnits.getValue('gpm');
196
+ break;
197
+ case 'hwvs':
198
+ case 'vssvrs':
199
+ case 'vs':
200
+ c.units = sys.board.valueMaps.pumpUnits.getValue('rpm');
201
+ break;
202
+ case 'ss':
203
+ case 'ds':
204
+ case 'sf':
205
+ case 'hwrly':
206
+ c.units = 'undefined';
207
+ break;
208
+ }
209
+ if (isNaN(units)) units = typeof cd !== 'undefined' ? cd.units : sys.board.valueMaps.pumpUnits.getValue('rpm');
210
+ if (isNaN(speed)) speed = type.minSpeed;
211
+ if (isNaN(flow)) flow = type.minFlow;
212
+ if (isNaN(relay)) relay = 1;
213
+ c.units = units;
214
+ //c.units = parseInt(c.units, 10) || type.name === 'vf' ? sys.board.valueMaps.pumpUnits.getValue('gpm') : sys.board.valueMaps.pumpUnits.getValue('rpm');
215
+ if (typeof type.minSpeed !== 'undefined' && c.units === sys.board.valueMaps.pumpUnits.getValue('rpm')) {
216
+ c.speed = speed;
217
+ }
218
+ else if (typeof type.minFlow !== 'undefined' && c.units === sys.board.valueMaps.pumpUnits.getValue('gpm')) {
219
+ c.flow = flow;
220
+ }
221
+ else if (type.maxRelays > 0)
222
+ c.relay = relay;
223
+ }
224
+ }
225
+ this.pump.set(data); // Sets all the data back to the pump. This also sets the relays should it exist on the data.
226
+ let spump = state.pumps.getItemById(this.pump.id, true);
227
+ spump.name = this.pump.name;
228
+ spump.address = this.pump.address;
229
+ spump.type = this.pump.type;
230
+ sys.pumps.sortById();
231
+ state.pumps.sortById();
232
+ return Promise.resolve(new InterfaceServerResponse(200, 'Ok'));
233
+ }
234
+ catch (err) { logger.error(`Nixie setPumpAsync: ${err.message}`); return Promise.reject(err); }
235
+ }
236
+ public async pollEquipmentAsync() {
237
+ let self = this;
238
+ try {
239
+ if (this.suspendPolling || this.closing) return;
240
+ if (typeof this._pollTimer !== 'undefined' || this._pollTimer) clearTimeout(this._pollTimer);
241
+ this._pollTimer = null;
242
+ // let success = false;
243
+ let pstate = state.pumps.getItemById(this.pump.id);
244
+ this.setTargetSpeed(pstate);
245
+ await this.setPumpStateAsync(pstate);
246
+ }
247
+ catch (err) { logger.error(`Nixie Error running pump sequence - ${err}`); }
248
+ finally { if (!this.closing) this._pollTimer = setTimeout(async () => await self.pollEquipmentAsync(), this.pollingInterval || 2000); }
249
+ }
250
+ private async checkHardwareStatusAsync(connectionId: string, deviceBinding: string) {
251
+ try {
252
+ let dev = await NixieEquipment.getDeviceService(connectionId, `/status/device/${deviceBinding}`);
253
+ return dev;
254
+ } catch (err) { logger.error(`Nixie Pump checkHardwareStatusAsync: ${err.message}`); return { hasFault: true } }
255
+ }
256
+ public async validateSetupAsync(pump: Pump, pstate: PumpState) {
257
+ try {
258
+ } catch (err) { logger.error(`Nixie Error checking Pump Hardware ${this.pump.name}: ${err.message}`); return Promise.reject(err); }
259
+ }
260
+ public async closeAsync() {
261
+ try {
262
+ logger.info(`Nixie Pump closing ${this.pump.name}.`)
263
+ if (typeof this._pollTimer !== 'undefined' || this._pollTimer) clearTimeout(this._pollTimer);
264
+ this._pollTimer = null;
265
+ this._targetSpeed = 0;
266
+ let pstate = state.pumps.getItemById(this.pump.id);
267
+ try {
268
+ await this.setPumpStateAsync(pstate);
269
+ // Since we are closing we need to not reject.
270
+ } catch (err) { logger.error(`Nixie Closing pump closeAsync: ${err.message}`); }
271
+ // This will make sure the timer is dead and we are completely closed.
272
+ this.closing = true;
273
+ if (typeof this._pollTimer !== 'undefined' || this._pollTimer) clearTimeout(this._pollTimer);
274
+ pstate.emitEquipmentChange();
275
+ }
276
+ catch (err) { logger.error(`Nixie Pump closeAsync: ${err.message}`); return Promise.reject(err); }
277
+ }
278
+ public logData(filename: string, data: any) { this.controlPanel.logData(filename, data); }
279
+ protected setTargetSpeed(pstate: PumpState) { };
280
+ protected isBodyOn(bodyCode: number) {
281
+ let assoc = sys.board.valueMaps.pumpBodies.transform(bodyCode);
282
+ switch (assoc.name) {
283
+ case 'body1':
284
+ case 'pool':
285
+ return state.temps.bodies.getItemById(1).isOn;
286
+ case 'body2':
287
+ case 'spa':
288
+ return state.temps.bodies.getItemById(2).isOn;
289
+ case 'body3':
290
+ return state.temps.bodies.getItemById(3).isOn;
291
+ case 'body4':
292
+ return state.temps.bodies.getItemById(4).isOn;
293
+ case 'poolspa':
294
+ if (sys.equipment.shared && sys.equipment.maxBodies >= 2) {
295
+ return state.temps.bodies.getItemById(1).isOn === true || state.temps.bodies.getItemById(2).isOn === true;
296
+ }
297
+ else
298
+ return state.temps.bodies.getItemById(1).isOn;
299
+ }
300
+ return false;
301
+ }
302
+ }
303
+ export class NixiePumpSS extends NixiePump {
304
+ public setTargetSpeed(pState: PumpState) {
305
+ // Turn on ss pumps.
306
+ let _newSpeed = 0;
307
+ if (!pState.pumpOnDelay) {
308
+ let pt = sys.board.valueMaps.pumpTypes.get(this.pump.type);
309
+ if (pt.hasBody) _newSpeed = this.isBodyOn(this.pump.body) ? 1 : 0;
310
+ //console.log(`BODY: ${sys.board.bodies.isBodyOn(this.pump.body)} CODE: ${this.pump.body}`);
311
+ }
312
+ if (this._targetSpeed !== _newSpeed) logger.info(`NCP: Setting Pump ${this.pump.name} to ${_newSpeed > 0 ? 'on' : 'off'}. ${sys.board.bodies.isBodyOn(this.pump.body)}`);
313
+ if (isNaN(_newSpeed)) _newSpeed = 0;
314
+ this._targetSpeed = _newSpeed;
315
+ }
316
+ public async setPumpStateAsync(pstate: PumpState) {
317
+ let relays: PumpRelay[] = this.pump.relays.get();
318
+ let relayState = 0;
319
+ for (let i = 0; i < relays.length; i++) {
320
+ let pr = relays[i];
321
+ if (typeof pr.id === 'undefined') pr.id = i + 1; // remove when id is added to dP relays upon save.
322
+ let isOn = this._targetSpeed >> pr.id - 1 & 1;
323
+ if (utils.isNullOrEmpty(pr.connectionId) || utils.isNullOrEmpty(pr.deviceBinding)) {
324
+ // If they haven't set a program for the relay bugger out.
325
+ if (isOn) relayState |= (1 << pr.id - 1);
326
+ }
327
+ else {
328
+ try {
329
+ let res = await NixieEquipment.putDeviceService(pr.connectionId, `/state/device/${pr.deviceBinding}`, { isOn, latch: isOn ? 5000 : undefined });
330
+ if (res.status.code === 200) {
331
+ if (isOn) relayState |= (1 << pr.id - 1);
332
+ }
333
+ else pstate.status = 16;
334
+ }
335
+ catch (err) {
336
+ logger.error(`NCP: Error setting pump ${this.pump.name} relay ${pr.id} to ${isOn ? 'on' : 'off'}. Error ${err.message}}`);
337
+ pstate.status = 16;
338
+ }
339
+ }
340
+ }
341
+ if (pstate.targetSpeed === 0) {
342
+ pstate.status = 0;
343
+ pstate.driveState = 0; // We need to set this if it is a priming cycle but it might not matter for our relay based pumps.
344
+ pstate.command = 0;
345
+ }
346
+ else if (relayState === pstate.targetSpeed) {
347
+ pstate.status = 1;
348
+ pstate.driveState = 2;
349
+ pstate.command = 4;
350
+ }
351
+ pstate.relay = relayState;
352
+ return new InterfaceServerResponse(200, 'Success');
353
+ }
354
+ }
355
+ export class NixiePumpDS extends NixiePumpSS {
356
+ public setTargetSpeed(pState: PumpState) {
357
+ // Turn on sf pumps. The new speed will be the relays associated with the pump. I believe when this comes out in the final
358
+ // wash it should engage all the relays for all speeds associated with the pump. The pump logic will determine which program is
359
+ // the one to engage.
360
+ let _newSpeed = 0;
361
+ if (!pState.pumpOnDelay) {
362
+ let pumpCircuits: PumpCircuit[] = this.pump.circuits.get();
363
+ if (!pState.pumpOnDelay) {
364
+ for (let i = 0; i < pumpCircuits.length; i++) {
365
+ let circ = state.circuits.getInterfaceById(pumpCircuits[i].circuit);
366
+ // relay speeds are bit-shifted 'or' based on 1,2,4,8
367
+ if (circ.isOn) _newSpeed |= (1 << pumpCircuits[i].relay - 1);
368
+ }
369
+ }
370
+ }
371
+ if (isNaN(_newSpeed)) _newSpeed = 0;
372
+ this.logSpeed(_newSpeed);
373
+ this._targetSpeed = _newSpeed;
374
+ }
375
+ public logSpeed(_newSpeed: number) {
376
+ if (this._targetSpeed !== _newSpeed) logger.info(`NCP: Setting Pump ${this.pump.name} relays to Relay 1: ${_newSpeed & 1 ? 'on' : 'off'}, Relay 2: ${_newSpeed & 2 ? 'on' : 'off'}.`);
377
+ }
378
+ }
379
+ export class NixiePumpSF extends NixiePumpDS {
380
+ // effectively operates the same way as a DS pump since we removed the body association on DS.
381
+ // only logger msg is different
382
+ public logSpeed(_newSpeed: number) {
383
+ if (this._targetSpeed !== _newSpeed) logger.info(`NCP: Setting Pump ${this.pump.name} relays to Relay 1: ${_newSpeed & 1 ? 'on' : 'off'}, Relay 2: ${_newSpeed & 2 ? 'on' : 'off'}, Relay 3: ${_newSpeed & 4 ? 'on' : 'off'}, and Relay 4: ${_newSpeed & 8 ? 'on' : 'off'}.`);
384
+ }
385
+ }
386
+ export class NixiePumpHWRLY extends NixiePumpDS {
387
+ // This operates as a relay pump with up to 8 speeds. The speeds are defined as follows. The override
388
+ // relay should be defined as being normally closed. When it opens then the pump will turn on to the speed.
389
+ // +-------+---------+---------+---------+---------+
390
+ // + Speed | Relay 1 | Relay 2 | Relay 3 | OVRD |
391
+ // +-------+---------+---------+---------+---------+
392
+ // | OFF | OFF | OFF | OFF | OFF |
393
+ // +-------+---------+---------+---------+---------+
394
+ // | 1 | OFF | OFF | OFF | ON |
395
+ // +-------+---------+---------+---------+---------+
396
+ // | 2 | ON | OFF | OFF | ON |
397
+ // +-------+---------+---------+---------+---------+
398
+ // | 3 | OFF | ON | OFF | ON |
399
+ // +-------+---------+---------+---------+---------+
400
+ // | 4 | ON | ON | OFF | ON |
401
+ // +-------+---------+---------+---------+---------+
402
+ // | 5 | OFF | OFF | ON | ON |
403
+ // +-------+---------+---------+---------+---------+
404
+ // | 6 | ON | OFF | ON | ON |
405
+ // +-------+---------+---------+---------+---------+
406
+ // | 7 | OFF | ON | ON | ON |
407
+ // +-------+---------+---------+---------+---------+
408
+ // | 8 | ON | ON | ON | ON |
409
+ // +-------+---------+---------+---------+---------+
410
+
411
+ public setTargetSpeed(pState: PumpState) {
412
+ let _newSpeed = 0;
413
+ if (!pState.pumpOnDelay) {
414
+ let pumpCircuits = this.pump.circuits.get();
415
+ for (let i = 0; i < pumpCircuits.length; i++) {
416
+ let circ = state.circuits.getInterfaceById(pumpCircuits[i].circuit);
417
+ let pc = pumpCircuits[i];
418
+ if (circ.isOn) {
419
+ _newSpeed = Math.max(_newSpeed, pc.relay);
420
+ }
421
+ }
422
+ }
423
+ if (isNaN(_newSpeed)) _newSpeed = 0;
424
+ this._targetSpeed = _newSpeed;
425
+ if (this._targetSpeed !== 0) Math.min(Math.max(this.pump.minSpeed, this._targetSpeed), this.pump.maxSpeed);
426
+ if (this._targetSpeed !== _newSpeed) logger.info(`NCP: Setting Pump ${this.pump.name} to ${_newSpeed}.`);
427
+ }
428
+ public async setPumpStateAsync(pstate: PumpState) {
429
+ // Don't poll while we are seting the state.
430
+ this.suspendPolling = true;
431
+ try {
432
+ let relays: PumpRelay[] = this.pump.relays.get();
433
+ let relayState = 0;
434
+ let targetState = 0;
435
+ for (let i = 0; i < relays.length; i++) {
436
+ let pr = relays[i];
437
+ if (typeof pr.id === 'undefined') pr.id = i + 1; // remove when id is added to dP relays upon save.
438
+ // If we are turning on the pump relay #4 needs to be on. NOTE: It is expected that the OVRD relay is hooked up in a normally closed
439
+ // configuration so that whenever the pump is off the relay terminals are closed.
440
+ let isOn = this._targetSpeed > 0 ? i === 3 ? true : (this._targetSpeed - 1 & (1 << i)) > 0 : false;
441
+ let bit = isOn ? (1 << i) : 0;
442
+ targetState |= bit;
443
+ if (utils.isNullOrEmpty(pr.connectionId) || utils.isNullOrEmpty(pr.deviceBinding)) {
444
+ // Determine whether the relay should be on.
445
+ relayState |= bit;
446
+ }
447
+ else {
448
+ try {
449
+ let res = await NixieEquipment.putDeviceService(pr.connectionId, `/state/device/${pr.deviceBinding}`, { isOn, latch: isOn ? 5000 : undefined });
450
+ if (res.status.code === 200) {
451
+ relayState |= bit;
452
+ }
453
+ else pstate.status = 16;
454
+ }
455
+ catch (err) {
456
+ logger.error(`NCP: Error setting pump ${this.pump.name} relay ${pr.id} to ${isOn ? 'on' : 'off'}. Error ${err.message}}`);
457
+ pstate.status = 16;
458
+ }
459
+ }
460
+ }
461
+ pstate.command = this._targetSpeed;
462
+ if (targetState === relayState) {
463
+ pstate.status = relayState > 0 ? 1 : 0;
464
+ pstate.driveState = relayState > 0 ? 2 : 0;
465
+ pstate.relay = relayState;
466
+ }
467
+ else {
468
+ pstate.driveState = 0;
469
+ }
470
+ return new InterfaceServerResponse(200, 'Success');
471
+ }
472
+ catch (err) {
473
+ logger.error(`Error running pump sequence for ${this.pump.name}: ${err.message}`);
474
+ return Promise.reject(err);
475
+ }
476
+ finally { this.suspendPolling = false; }
477
+ };
478
+
479
+ }
480
+ export class NixiePumpRS485 extends NixiePump {
481
+ public async setPumpStateAsync(pstate: PumpState) {
482
+ // Don't poll while we are seting the state.
483
+ this.suspendPolling = true;
484
+ try {
485
+ let pt = sys.board.valueMaps.pumpTypes.get(this.pump.type);
486
+ // Since these process are async the closing flag can be set
487
+ // between calls. We need to check it in between each call.
488
+ try { if (!this.closing) await this.setDriveStateAsync(); } catch (err) {}
489
+ try {
490
+ if (!this.closing) {
491
+ if (this._targetSpeed >= pt.minFlow && this._targetSpeed <= pt.maxFlow) await this.setPumpGPMAsync();
492
+ else if (this._targetSpeed >= pt.minSpeed && this._targetSpeed <= pt.maxSpeed) await this.setPumpRPMAsync();
493
+ }
494
+ } catch (err) { };
495
+ try { if (!this.closing && pt.name !== 'vsf') await this.setPumpFeature(6); } catch (err) { };
496
+ try { if(!this.closing) await utils.sleep(1000); } catch (err) { };
497
+ try { if (!this.closing) await this.requestPumpStatus(); } catch (err) { };
498
+ try { if (!this.closing) await this.setPumpToRemoteControl(); } catch (err) { };
499
+ return new InterfaceServerResponse(200, 'Success');
500
+ }
501
+ catch (err) {
502
+ logger.error(`Error running pump sequence for ${this.pump.name}: ${err.message}`);
503
+ return Promise.reject(err);
504
+ }
505
+ finally { this.suspendPolling = false; }
506
+ };
507
+ protected async setDriveStateAsync(running: boolean = true) {
508
+ return new Promise<void>((resolve, reject) => {
509
+ if (conn.isPortEnabled(this.pump.portId || 0)) {
510
+ let out = Outbound.create({
511
+ portId: this.pump.portId || 0,
512
+ protocol: Protocol.Pump,
513
+ dest: this.pump.address,
514
+ action: 6,
515
+ payload: running && this._targetSpeed > 0 ? [10] : [4],
516
+ retries: 1,
517
+ response: true,
518
+ onComplete: (err, msg: Outbound) => {
519
+ if (err) {
520
+ logger.error(`Error sending setDriveState for ${this.pump.name} : ${err.message}`);
521
+ reject(err);
522
+ }
523
+ else resolve();
524
+ }
525
+ });
526
+ conn.queueSendMessage(out);
527
+ }
528
+ else {
529
+ let pstate = state.pumps.getItemById(this.pump.id);
530
+ pstate.command = pstate.rpm > 0 || pstate.flow > 0 ? 10 : 0;
531
+ resolve();
532
+ }
533
+ });
534
+ };
535
+ protected async requestPumpStatus() {
536
+ if (conn.isPortEnabled(this.pump.portId || 0)) {
537
+ return new Promise<void>((resolve, reject) => {
538
+ let out = Outbound.create({
539
+ portId: this.pump.portId || 0,
540
+ protocol: Protocol.Pump,
541
+ dest: this.pump.address,
542
+ action: 7,
543
+ payload: [],
544
+ retries: 2,
545
+ response: true,
546
+ onComplete: (err, msg) => {
547
+ if (err) {
548
+ logger.error(`Error sending requestPumpStatus for ${this.pump.name}: ${err.message}`);
549
+ reject(err);
550
+ }
551
+ else resolve();
552
+ }
553
+ });
554
+ conn.queueSendMessage(out);
555
+ });
556
+ }
557
+ };
558
+ protected setPumpToRemoteControl(running: boolean = true) {
559
+ if (conn.isPortEnabled(this.pump.portId || 0)) {
560
+ return new Promise<void>((resolve, reject) => {
561
+ let out = Outbound.create({
562
+ portId: this.pump.portId || 0,
563
+ protocol: Protocol.Pump,
564
+ dest: this.pump.address,
565
+ action: 4,
566
+ payload: running ? [255] : [0], // when stopAsync is called, pass false to return control to pump panel
567
+ // payload: spump.virtualControllerStatus === sys.board.valueMaps.virtualControllerStatus.getValue('running') ? [255] : [0],
568
+ retries: 1,
569
+ response: true,
570
+ onComplete: (err) => {
571
+ if (err) {
572
+ logger.error(`Error sending setPumpToRemoteControl for ${this.pump.name}: ${err.message}`);
573
+ reject(err);
574
+ }
575
+ else resolve();
576
+ }
577
+ });
578
+ conn.queueSendMessage(out);
579
+ });
580
+ }
581
+ }
582
+ protected setPumpFeature(feature?: number) {
583
+ if (conn.isPortEnabled(this.pump.portId || 0)) {
584
+ // empty payload (possibly 0?, too) is no feature
585
+ // 6: Feature 1
586
+ return new Promise<void>((resolve, reject) => {
587
+ let out = Outbound.create({
588
+ portId: this.pump.portId || 0,
589
+ protocol: Protocol.Pump,
590
+ dest: this.pump.address,
591
+ action: 5,
592
+ payload: typeof feature === 'undefined' ? [] : [feature],
593
+ retries: 2,
594
+ response: true,
595
+ onComplete: (err, msg: Outbound) => {
596
+ if (err) {
597
+ logger.error(`Error sending setPumpManual for ${this.pump.name}: ${err.message}`);
598
+ reject(err);
599
+ }
600
+ else resolve();
601
+ }
602
+ });
603
+ conn.queueSendMessage(out);
604
+ });
605
+ }
606
+ else {
607
+
608
+ }
609
+ };
610
+ protected async setPumpRPMAsync() {
611
+ if (conn.isPortEnabled(this.pump.portId || 0)) {
612
+ return new Promise<void>((resolve, reject) => {
613
+ let out = Outbound.create({
614
+ portId: this.pump.portId || 0,
615
+ protocol: Protocol.Pump,
616
+ dest: this.pump.address,
617
+ action: 1,
618
+ payload: [2, 196, Math.floor(this._targetSpeed / 256), this._targetSpeed % 256],
619
+ retries: 1,
620
+ // timeout: 250,
621
+ response: true,
622
+ onComplete: (err, msg) => {
623
+ if (err) {
624
+ logger.error(`Error sending setPumpRPMAsync for ${this.pump.name}: ${err.message}`);
625
+ reject(err);
626
+ }
627
+ else resolve();
628
+ }
629
+ });
630
+ conn.queueSendMessage(out);
631
+ });
632
+ }
633
+ else {
634
+
635
+ }
636
+ };
637
+ protected async setPumpGPMAsync() {
638
+ if (conn.isPortEnabled(this.pump.portId || 0)) {
639
+ // packet for vf; vsf will override
640
+ return new Promise<void>((resolve, reject) => {
641
+ let out = Outbound.create({
642
+ portId: this.pump.portId || 0,
643
+ protocol: Protocol.Pump,
644
+ dest: this.pump.address,
645
+ action: 1,
646
+ payload: [2, 228, 0, this._targetSpeed],
647
+ retries: 1,
648
+ response: true,
649
+ onComplete: (err, msg) => {
650
+ if (err) {
651
+ logger.error(`Error sending setPumpGPMAsync for ${this.pump.name}: ${err.message}`);
652
+ reject(err);
653
+ }
654
+ else resolve();
655
+ }
656
+ });
657
+ conn.queueSendMessage(out);
658
+ });
659
+ }
660
+ };
661
+ public async closeAsync() {
662
+ try {
663
+ this.suspendPolling = true;
664
+ logger.info(`Nixie Pump closing ${this.pump.name}.`)
665
+ if (typeof this._pollTimer !== 'undefined' || this._pollTimer) clearTimeout(this._pollTimer);
666
+ this._pollTimer = null;
667
+ let pstate = state.pumps.getItemById(this.pump.id);
668
+ this._targetSpeed = 0;
669
+ try { await this.setDriveStateAsync(false); } catch (err) { logger.error(`Error closing pump ${this.pump.name}: ${err.message}`) }
670
+ try { await this.setPumpFeature(); } catch (err) { logger.error(`Error closing pump ${this.pump.name}: ${err.message}`) }
671
+ try { await this.setDriveStateAsync(false); } catch (err) { logger.error(`Error closing pump ${this.pump.name}: ${err.message}`) }
672
+ try { await this.setPumpToRemoteControl(false); } catch (err) { logger.error(`Error closing pump ${this.pump.name}: ${err.message}`) }
673
+ this.closing = true;
674
+ // Make sure the polling timer is dead after we have closted this all off. That way we do not
675
+ // have another process that revives it from the dead.
676
+ if (typeof this._pollTimer !== 'undefined' || this._pollTimer) clearTimeout(this._pollTimer);
677
+ this._pollTimer = null;
678
+ pstate.emitEquipmentChange();
679
+ }
680
+ catch (err) { logger.error(`Nixie Pump closeAsync: ${err.message}`); return Promise.reject(err); }
681
+ finally { this.suspendPolling = false; }
682
+ }
683
+ }
684
+ export class NixiePumpVS extends NixiePumpRS485 {
685
+ public setTargetSpeed(pState: PumpState) {
686
+ let _newSpeed = 0;
687
+ if (!pState.pumpOnDelay) {
688
+ let pumpCircuits = this.pump.circuits.get();
689
+
690
+ for (let i = 0; i < pumpCircuits.length; i++) {
691
+ let circ = state.circuits.getInterfaceById(pumpCircuits[i].circuit);
692
+ let pc = pumpCircuits[i];
693
+ if (circ.isOn) _newSpeed = Math.max(_newSpeed, pc.speed);
694
+ }
695
+ }
696
+ if (isNaN(_newSpeed)) _newSpeed = 0;
697
+ this._targetSpeed = _newSpeed;
698
+ if (this._targetSpeed !== 0) Math.min(Math.max(this.pump.minSpeed, this._targetSpeed), this.pump.maxSpeed);
699
+ if (this._targetSpeed !== _newSpeed) logger.info(`NCP: Setting Pump ${this.pump.name} to ${_newSpeed} RPM.`);
700
+ }
701
+ }
702
+ export class NixiePumpVF extends NixiePumpRS485 {
703
+ public setTargetSpeed(pState: PumpState) {
704
+ let _newSpeed = 0;
705
+ if (!pState.pumpOnDelay) {
706
+ let pumpCircuits = this.pump.circuits.get();
707
+ for (let i = 0; i < pumpCircuits.length; i++) {
708
+ let circ = state.circuits.getInterfaceById(pumpCircuits[i].circuit);
709
+ let pc = pumpCircuits[i];
710
+ if (circ.isOn) _newSpeed = Math.max(_newSpeed, pc.flow);
711
+ }
712
+ }
713
+ if (isNaN(_newSpeed)) _newSpeed = 0;
714
+ this._targetSpeed = _newSpeed;
715
+ if (this._targetSpeed !== 0) Math.min(Math.max(this.pump.minFlow, this._targetSpeed), this.pump.maxFlow);
716
+ if (this._targetSpeed !== _newSpeed) logger.info(`NCP: Setting Pump ${this.pump.name} to ${_newSpeed} GPM.`);
717
+ }
718
+ }
719
+ export class NixiePumpVSF extends NixiePumpRS485 {
720
+ public setTargetSpeed(pState: PumpState) {
721
+ let _newSpeed = 0;
722
+ let maxRPM = 0;
723
+ let maxGPM = 0;
724
+ let flows = 0;
725
+ let speeds = 0;
726
+ if (!pState.pumpOnDelay) {
727
+ let pumpCircuits = this.pump.circuits.get();
728
+ let pt = sys.board.valueMaps.pumpTypes.get(this.pump.type);
729
+ // VSF pumps present a problem. In fact they do not currently operate properly on Touch panels. On touch these need to either be all in RPM or GPM
730
+ // if there is a mix in the circuit array then they will not work. In IntelliCenter if there is an RPM setting in the mix it will use RPM by converting
731
+ // the GPM to RPM but if there is none then it will use GPM.
732
+ let toRPM = (flowRate: number, minSpeed: number = 450, maxSpeed: number = 3450) => {
733
+ let eff = .03317 * maxSpeed;
734
+ let rpm = Math.min((flowRate * maxSpeed) / eff, maxSpeed);
735
+ return rpm > 0 ? Math.max(rpm, minSpeed) : 0;
736
+ };
737
+ let toGPM = (speed: number, maxSpeed: number = 3450, minFlow: number = 15, maxFlow: number = 140) => {
738
+ let eff = .03317 * maxSpeed;
739
+ let gpm = Math.min((eff * speed) / maxSpeed, maxFlow);
740
+ return gpm > 0 ? Math.max(gpm, minFlow) : 0;
741
+ }
742
+ for (let i = 0; i < pumpCircuits.length; i++) {
743
+ let circ = state.circuits.getInterfaceById(pumpCircuits[i].circuit);
744
+ let pc = pumpCircuits[i];
745
+ if (circ.isOn) {
746
+ if (pc.units > 0) {
747
+ maxGPM = Math.max(maxGPM, pc.flow);
748
+ // Calculate an RPM from this flow.
749
+ maxRPM = Math.max(maxGPM, toRPM(pc.flow, pt.minSpeed, pt.maxSpeed));
750
+ flows++;
751
+ }
752
+ else {
753
+ maxRPM = Math.max(maxRPM, pc.speed);
754
+ maxGPM = Math.max(maxGPM, toGPM(pc.speed, pt.maxSpeed, pt.minFlow, pt.maxFlow));
755
+ speeds++;
756
+ }
757
+ }
758
+ }
759
+ _newSpeed = speeds > 0 || flows === 0 ? maxRPM : maxGPM;
760
+ }
761
+ if (isNaN(_newSpeed)) _newSpeed = 0;
762
+ // Send the flow message if it is flow and the rpm message if it is rpm.
763
+ if (this._targetSpeed !== _newSpeed) logger.info(`NCP: Setting Pump ${this.pump.name} to ${_newSpeed} ${flows > 0 ? 'GPM' : 'RPM'}.`);
764
+ this._targetSpeed = _newSpeed;
765
+ }
766
+ protected async setPumpRPMAsync() {
767
+ // vsf action is 10 for rpm
768
+ if (conn.isPortEnabled(this.pump.portId || 0)) {
769
+ return new Promise<void>((resolve, reject) => {
770
+ let out = Outbound.create({
771
+ portId: this.pump.portId || 0,
772
+ protocol: Protocol.Pump,
773
+ dest: this.pump.address,
774
+ action: 10,
775
+ payload: [2, 196, Math.floor(this._targetSpeed / 256), this._targetSpeed % 256],
776
+ retries: 1,
777
+ // timeout: 250,
778
+ response: true,
779
+ onComplete: (err, msg) => {
780
+ if (err) {
781
+ logger.error(`Error sending setPumpRPMAsync for ${this.pump.name}: ${err.message}`);
782
+ reject(err);
783
+ }
784
+ else resolve();
785
+ }
786
+ });
787
+ conn.queueSendMessage(out);
788
+ });
789
+ }
790
+ };
791
+ protected async setPumpGPMAsync() {
792
+ if (conn.isPortEnabled(this.pump.portId || 0)) {
793
+ // vsf payload; different from vf payload
794
+ return new Promise<void>((resolve, reject) => {
795
+ let out = Outbound.create({
796
+ portId: this.pump.portId || 0,
797
+ protocol: Protocol.Pump,
798
+ dest: this.pump.address,
799
+ action: 9,
800
+ payload: [2, 196, 0, this._targetSpeed],
801
+ retries: 1,
802
+ response: true,
803
+ onComplete: (err, msg) => {
804
+ if (err) {
805
+ logger.error(`Error sending setPumpGPMAsync for ${this.pump.name}: ${err.message}`);
806
+ reject(err);
807
+ }
808
+ else resolve();
809
+ return
810
+ }
811
+ });
812
+ conn.queueSendMessage(out);
813
+ });
814
+ }
815
+ };
816
+ };
817
+ export class NixiePumpHWVS extends NixiePumpRS485 {
818
+ public setTargetSpeed(pState: PumpState) {
819
+ let _newSpeed = 0;
820
+ if (!pState.pumpOnDelay) {
821
+ let pumpCircuits = this.pump.circuits.get();
822
+
823
+ for (let i = 0; i < pumpCircuits.length; i++) {
824
+ let circ = state.circuits.getInterfaceById(pumpCircuits[i].circuit);
825
+ let pc = pumpCircuits[i];
826
+ if (circ.isOn) _newSpeed = Math.max(_newSpeed, pc.speed);
827
+ }
828
+ }
829
+ if (isNaN(_newSpeed)) _newSpeed = 0;
830
+ this._targetSpeed = _newSpeed;
831
+ if (this._targetSpeed !== 0) Math.min(Math.max(this.pump.minSpeed, this._targetSpeed), this.pump.maxSpeed);
832
+ if (this._targetSpeed !== _newSpeed) logger.info(`NCP: Setting Pump ${this.pump.name} to ${_newSpeed} RPM.`);
833
+ }
834
+ public async setPumpStateAsync(pstate: PumpState) {
835
+ // Don't poll while we are seting the state.
836
+ this.suspendPolling = true;
837
+ try {
838
+ let pt = sys.board.valueMaps.pumpTypes.get(this.pump.type);
839
+ // Since these process are async the closing flag can be set
840
+ // between calls. We need to check it in between each call.
841
+ try { if (!this.closing) { await this.setPumpRPMAsync(); } } catch (err) { }
842
+ return new InterfaceServerResponse(200, 'Success');
843
+ }
844
+ catch (err) {
845
+ logger.error(`Error running pump sequence for ${this.pump.name}: ${err.message}`);
846
+ return Promise.reject(err);
847
+ }
848
+ finally { this.suspendPolling = false; }
849
+ };
850
+ protected async requestPumpStatus() { return Promise.resolve(); };
851
+ protected setPumpFeature(feature?: number) { return Promise.resolve(); }
852
+ protected setPumpToRemoteControl(running: boolean = true) {
853
+ if (conn.isPortEnabled(this.pump.portId || 0)) {
854
+ // We do nothing on this pump to set it to remote control. That is unless we are turning it off.
855
+ return new Promise<void>((resolve, reject) => {
856
+ if (!running) {
857
+ let out = Outbound.create({
858
+ portId: this.pump.portId || 0,
859
+ protocol: Protocol.Hayward,
860
+ source: 12, // Use the broadcast address
861
+ dest: this.pump.address,
862
+ action: 1,
863
+ payload: [0], // when stopAsync is called, pass false to return control to pump panel
864
+ // payload: spump.virtualControllerStatus === sys.board.valueMaps.virtualControllerStatus.getValue('running') ? [255] : [0],
865
+ retries: 1,
866
+ response: Response.create({ protocol: Protocol.Hayward, action: 12, source: this.pump.address }),
867
+ onComplete: (err) => {
868
+ if (err) {
869
+ logger.error(`Error sending setPumpToRemoteControl for ${this.pump.name}: ${err.message}`);
870
+ reject(err);
871
+ }
872
+ else resolve();
873
+ }
874
+ });
875
+ conn.queueSendMessage(out);
876
+ }
877
+ else resolve();
878
+ });
879
+ }
880
+ }
881
+ protected async setPumpRPMAsync() {
882
+ if (conn.isPortEnabled(this.pump.portId || 0)) {
883
+ return new Promise<void>((resolve, reject) => {
884
+ let pt = sys.board.valueMaps.pumpTypes.get(this.pump.type);
885
+ let out = Outbound.create({
886
+ portId: this.pump.portId || 0,
887
+ protocol: Protocol.Hayward,
888
+ source: 12, // Use the broadcast address
889
+ dest: this.pump.address - 96,
890
+ action: 1,
891
+ payload: [Math.min(Math.round((this._targetSpeed / pt.maxSpeed) * 100), 100)], // when stopAsync is called, pass false to return control to pump panel
892
+ retries: 1,
893
+ response: Response.create({ protocol: Protocol.Hayward, action: 12, source: this.pump.address }),
894
+ onComplete: (err) => {
895
+ if (err) {
896
+ logger.error(`Error sending setPumpRPM for ${this.pump.name}: ${err.message}`);
897
+ reject(err);
898
+ }
899
+ else resolve();
900
+ }
901
+ });
902
+ conn.queueSendMessage(out);
903
+ });
904
+ }
905
+ };
906
+ }