nodejs-poolcontroller 8.3.0 → 8.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/.eslintrc.json +36 -36
  2. package/.github/ISSUE_TEMPLATE/1-bug-report.yml +84 -84
  3. package/.github/ISSUE_TEMPLATE/2-docs.md +12 -12
  4. package/.github/ISSUE_TEMPLATE/3-proposal.md +28 -28
  5. package/.github/ISSUE_TEMPLATE/config.yml +8 -8
  6. package/.github/copilot-instructions.md +63 -63
  7. package/.github/workflows/ghcr-publish.yml +67 -67
  8. package/157_issues.md +101 -0
  9. package/AGENTS.md +613 -0
  10. package/CONTRIBUTING.md +74 -74
  11. package/Changelog +292 -284
  12. package/Dockerfile +62 -62
  13. package/Gruntfile.js +40 -40
  14. package/LICENSE +661 -661
  15. package/README.md +329 -309
  16. package/anslq25/MessagesMock.ts +221 -221
  17. package/anslq25/boards/MockBoardFactory.ts +49 -49
  18. package/anslq25/boards/MockEasyTouchBoard.ts +696 -696
  19. package/anslq25/boards/MockSystemBoard.ts +216 -216
  20. package/anslq25/chemistry/MockChlorinator.ts +98 -98
  21. package/anslq25/pumps/MockPump.ts +83 -83
  22. package/app.ts +115 -115
  23. package/config/Config.ts +0 -0
  24. package/config/VersionCheck.ts +0 -0
  25. package/controller/Constants.ts +809 -805
  26. package/controller/Equipment.ts +2737 -2664
  27. package/controller/Errors.ts +181 -181
  28. package/controller/Lockouts.ts +549 -549
  29. package/controller/State.ts +3746 -3701
  30. package/controller/boards/AquaLinkBoard.ts +1175 -1003
  31. package/controller/boards/BoardFactory.ts +53 -53
  32. package/controller/boards/EasyTouchBoard.ts +3246 -3202
  33. package/controller/boards/IntelliCenterBoard.ts +4581 -3899
  34. package/controller/boards/IntelliComBoard.ts +69 -69
  35. package/controller/boards/IntelliTouchBoard.ts +382 -382
  36. package/controller/boards/NixieBoard.ts +1947 -1944
  37. package/controller/boards/SunTouchBoard.ts +401 -400
  38. package/controller/boards/SystemBoard.ts +5303 -5268
  39. package/controller/comms/Comms.ts +1278 -1255
  40. package/controller/comms/ScreenLogic.ts +1665 -1665
  41. package/controller/comms/messages/Messages.ts +1627 -1406
  42. package/controller/comms/messages/config/ChlorinatorMessage.ts +5 -0
  43. package/controller/comms/messages/config/CircuitGroupMessage.ts +0 -0
  44. package/controller/comms/messages/config/CircuitMessage.ts +0 -0
  45. package/controller/comms/messages/config/ConfigMessage.ts +6 -0
  46. package/controller/comms/messages/config/CoverMessage.ts +0 -0
  47. package/controller/comms/messages/config/CustomNameMessage.ts +31 -31
  48. package/controller/comms/messages/config/EquipmentMessage.ts +250 -210
  49. package/controller/comms/messages/config/ExternalMessage.ts +1051 -903
  50. package/controller/comms/messages/config/FeatureMessage.ts +0 -0
  51. package/controller/comms/messages/config/GeneralMessage.ts +65 -0
  52. package/controller/comms/messages/config/HeaterMessage.ts +0 -0
  53. package/controller/comms/messages/config/IntellichemMessage.ts +0 -0
  54. package/controller/comms/messages/config/OptionsMessage.ts +207 -174
  55. package/controller/comms/messages/config/PumpMessage.ts +427 -421
  56. package/controller/comms/messages/config/RemoteMessage.ts +0 -0
  57. package/controller/comms/messages/config/ScheduleMessage.ts +401 -390
  58. package/controller/comms/messages/config/SecurityMessage.ts +37 -13
  59. package/controller/comms/messages/config/ValveMessage.ts +0 -0
  60. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +0 -0
  61. package/controller/comms/messages/status/EquipmentStateMessage.ts +940 -822
  62. package/controller/comms/messages/status/HeaterStateMessage.ts +147 -135
  63. package/controller/comms/messages/status/IntelliChemStateMessage.ts +448 -448
  64. package/controller/comms/messages/status/IntelliValveStateMessage.ts +36 -36
  65. package/controller/comms/messages/status/NeptuneModbusStateMessage.ts +217 -0
  66. package/controller/comms/messages/status/PumpStateMessage.ts +0 -0
  67. package/controller/comms/messages/status/RegalModbusStateMessage.ts +410 -410
  68. package/controller/comms/messages/status/VersionMessage.ts +152 -41
  69. package/controller/nixie/Nixie.ts +173 -173
  70. package/controller/nixie/NixieEquipment.ts +104 -104
  71. package/controller/nixie/bodies/Body.ts +120 -120
  72. package/controller/nixie/bodies/Filter.ts +135 -135
  73. package/controller/nixie/chemistry/ChemController.ts +2756 -2724
  74. package/controller/nixie/chemistry/ChemDoser.ts +806 -806
  75. package/controller/nixie/chemistry/Chlorinator.ts +367 -367
  76. package/controller/nixie/circuits/Circuit.ts +478 -478
  77. package/controller/nixie/heaters/Heater.ts +843 -834
  78. package/controller/nixie/pumps/Pump.ts +1336 -1193
  79. package/controller/nixie/schedules/Schedule.ts +401 -401
  80. package/controller/nixie/valves/Valve.ts +170 -170
  81. package/defaultConfig.json +352 -352
  82. package/docker-compose.yml +32 -31
  83. package/logger/DataLogger.ts +448 -448
  84. package/logger/Logger.ts +459 -436
  85. package/package.json +58 -58
  86. package/sendSocket.js +32 -32
  87. package/tsconfig.json +26 -25
  88. package/types/express-multer.d.ts +32 -32
  89. package/web/Server.ts +1939 -1927
  90. package/web/bindings/aqualinkD.json +559 -559
  91. package/web/bindings/influxDB.json +1066 -1066
  92. package/web/bindings/mqtt.json +721 -721
  93. package/web/bindings/mqttAlt.json +746 -746
  94. package/web/bindings/rulesManager.json +54 -54
  95. package/web/bindings/smartThings-Hubitat.json +31 -31
  96. package/web/bindings/valveRelays.json +20 -20
  97. package/web/bindings/vera.json +25 -25
  98. package/web/interfaces/baseInterface.ts +188 -188
  99. package/web/interfaces/httpInterface.ts +148 -148
  100. package/web/interfaces/influxInterface.ts +283 -283
  101. package/web/interfaces/mqttInterface.ts +695 -695
  102. package/web/interfaces/ruleInterface.ts +101 -87
  103. package/web/services/config/Config.ts +1212 -1053
  104. package/web/services/config/ConfigSocket.ts +0 -0
  105. package/web/services/state/State.ts +21 -0
  106. package/web/services/state/StateSocket.ts +28 -0
  107. package/web/services/utilities/Utilities.ts +233 -233
@@ -1,1194 +1,1337 @@
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 as setTimeoutSync, 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
- import { setTimeout } from 'timers/promises';
14
-
15
- export class NixiePumpCollection extends NixieEquipmentCollection<NixiePump> {
16
- public async deletePumpAsync(id: number) {
17
- try {
18
- for (let i = this.length - 1; i >= 0; i--) {
19
- let pump = this[i];
20
- if (pump.id === id) {
21
- await pump.closeAsync();
22
- this.splice(i, 1);
23
- }
24
- }
25
- } catch (err) { logger.error(`Nixie Control Panel deletePumpAsync ${err.message}`); }
26
- }
27
- public async setPumpStateAsync(pstate: PumpState) {
28
- try {
29
- let pump: NixiePump = this.find(elem => elem.id === pstate.id) as NixiePump;
30
- if (typeof pump === 'undefined') {
31
- return logger.error(`Nixie Control Panel Error setPumpState could not find pump ${pstate.id}-${pstate.name}`);
32
- }
33
- await pump.setPumpStateAsync(pstate);
34
- } catch (err) { logger.error(`Nixie Error setting pump state ${pstate.id}-${pstate.name}: ${err.message}`); return Promise.reject(err); }
35
- }
36
- public async setPumpAsync(pump: Pump, data: any) {
37
- // By the time we get here we know that we are in control and this is a Nixie pump.
38
- try {
39
- let c: NixiePump = this.find(elem => elem.id === pump.id) as NixiePump;
40
- if (typeof c === 'undefined') {
41
- pump.master = 1;
42
- if (typeof data.type !== 'undefined') pump.type = data.type; // needed for init of correct type
43
- if (typeof pump.type === 'undefined') return Promise.reject(new InvalidEquipmentIdError(`Invalid pump type for ${pump.name}`, data.id, 'Pump'));
44
- c = this.pumpFactory(pump);
45
- // c = new NixiePump(this.controlPanel, pump);
46
- this.push(c);
47
- logger.info(`A pump was not found for id #${pump.id} creating pump`);
48
- return await c.setPumpAsync(data);
49
- }
50
- else {
51
- if (typeof data.type !== 'undefined' && c.pump.type !== data.type) {
52
- // pump exists, changing type
53
- await c.closeAsync();
54
- pump.type = data.type; // needed for init of correct type
55
- if (typeof pump.type === 'undefined') return Promise.reject(new InvalidEquipmentIdError(`Invalid pump type for ${pump.name}`, data.id, 'Pump'));
56
- c = this.pumpFactory(pump);
57
- }
58
- return await c.setPumpAsync(data);
59
- }
60
- }
61
- catch (err) { logger.error(`setPumpAsync: ${err.message}`); return Promise.reject(err); }
62
- }
63
- public async initAsync(pumps: PumpCollection) {
64
- try {
65
- for (let i = 0; i < pumps.length; i++) {
66
- let pump = pumps.getItemByIndex(i);
67
- if (pump.master === 1) {
68
- let p: NixiePump = this.find(elem => elem.id === pump.id) as NixiePump;
69
- if (typeof p === 'undefined') {
70
- let type = sys.board.valueMaps.pumpTypes.getName(pump.type);
71
- let npump = this.pumpFactory(pump);
72
- logger.info(`Initializing Nixie Pump ${npump.id}-${pump.name}`);
73
- this.push(npump);
74
- await npump.initAsync();
75
- }
76
- }
77
- }
78
- }
79
- catch (err) { logger.error(`Nixie Pump initAsync Error: ${err.message}`); return Promise.reject(err); }
80
- }
81
- public async closeAsync() {
82
- try {
83
- for (let i = this.length - 1; i >= 0; i--) {
84
- try {
85
- try {
86
- await this[i].closeAsync();
87
- } catch (err) { logger.error(`Error attempting to close pump ${this[i].id}`); }
88
- this.splice(i, 1);
89
- } catch (err) { logger.error(`Error stopping Nixie Pump ${err}`); }
90
- }
91
- } catch (err) { } // Don't bail if we have an errror.
92
- }
93
- public async setServiceModeAsync() {
94
- try {
95
- for (let i = this.length - 1; i >= 0; i--) {
96
- try {
97
- let p = this[i] as NixiePump;
98
- await p.setServiceModeAsync();
99
- } catch (err) { logger.error(`Error setting service mode for Nixie Pump ${err}`); }
100
- }
101
- } catch (err) { } // Don't bail if we have an errror.
102
- }
103
- public async initPumpAsync(pump: Pump): Promise<NixiePump> {
104
- try {
105
- let c: NixiePump = this.find(elem => elem.id === pump.id) as NixiePump;
106
- if (pump.master === 1) {
107
- // if pump exists, close it so we can re-init
108
- // (EG if pump type changes, we need to setup a new instance of the pump)
109
- if (typeof c !== 'undefined' && c.pump.type !== pump.type) {
110
- await c.closeAsync();
111
- await this.deletePumpAsync(pump.id);
112
- c = this.pumpFactory(pump);
113
- this.push(c);
114
- }
115
- logger.info(`Initializing Existing Nixie Pump ${c.id}-${pump.name}`);
116
-
117
- }
118
- return c;
119
- } catch (err) { return Promise.reject(logger.error(`Nixie Controller: initPumpAsync Error: ${err.message}`)); }
120
- }
121
- private pumpFactory(pump: Pump) {
122
- let type = sys.board.valueMaps.pumpTypes.getName(pump.type);
123
- switch (type) {
124
- case 'ss':
125
- return new NixiePumpSS(this.controlPanel, pump);
126
- case 'ds':
127
- return new NixiePumpDS(this.controlPanel, pump);
128
- case 'vsf':
129
- return new NixiePumpVSF(this.controlPanel, pump);
130
- case 'vf':
131
- return new NixiePumpVF(this.controlPanel, pump);
132
- case 'sf':
133
- return new NixiePumpSF(this.controlPanel, pump);
134
- case 'vs':
135
- return new NixiePumpVS(this.controlPanel, pump);
136
- case 'hwvs':
137
- return new NixiePumpHWVS(this.controlPanel, pump);
138
- case 'hwrly':
139
- return new NixiePumpHWRLY(this.controlPanel, pump);
140
- case 'regalmodbus':
141
- return new NixiePumpRegalModbus(this.controlPanel, pump);
142
- default:
143
- throw new EquipmentNotFoundError(`NCP: Cannot create pump ${pump.name}.`, type);
144
- }
145
- }
146
- public syncPumpStates() {
147
- // loop through all pumps and update rates based on circuit changes
148
- // this would happen in <2s anyway based on pollAsync but this is immediate.
149
- for (let i = this.length - 1; i >= 0; i--) {
150
- let pump = this[i] as NixiePump;
151
- if (!pump.suspendPolling) setTimeoutSync(async () => { await pump.pollEquipmentAsync(); }, 100);
152
- else {
153
- pump.setTargetSpeed(state.pumps.getItemById(pump.id));
154
- }
155
- // RKS: 05-16-23 - Below backs up the processing.
156
- /*
157
-
158
- setTimeoutSync(async () => {
159
- let pump = this[i] as NixiePump;
160
- try {
161
- if (!pump.closing) await pump.pollEquipmentAsync();
162
- } catch (err) { }
163
- }, 100);
164
- */
165
- }
166
- }
167
- }
168
- export class NixiePump extends NixieEquipment {
169
- public pollingInterval: number = 2000;
170
- protected _pollTimer: NodeJS.Timeout = null;
171
- public pump: Pump;
172
- protected _targetSpeed: number;
173
- protected _suspendPolling = 0;
174
- public get suspendPolling(): boolean { return this._suspendPolling > 0; }
175
- public set suspendPolling(val: boolean) {
176
- this._suspendPolling = Math.max(0, this._suspendPolling + (val ? 1 : -1));
177
- if (this._suspendPolling > 1) console.log(`Suspend Polling ${this._suspendPolling}`);
178
- }
179
- public closing = false;
180
- public async setServiceModeAsync() {
181
- let pstate = state.pumps.getItemById(this.pump.id);
182
- await this.setPumpStateAsync(pstate);
183
- }
184
- /*
185
- _targetSpeed will hold values as follows:
186
- vs/vsf/vf: rpm/gpm;
187
- ss: 0=off, 1=on;
188
- ds/sf: bit shift 1-4 = values 1/2/4/8 for relays 1/2/3/4
189
- */
190
- constructor(ncp: INixieControlPanel, pump: Pump) {
191
- super(ncp);
192
- this.pump = pump;
193
- this._targetSpeed = 0;
194
- }
195
- public async initAsync() {
196
- if (this._pollTimer) {
197
- clearTimeout(this._pollTimer);
198
- this._pollTimer = undefined;
199
- }
200
- this.closing = false;
201
- this._suspendPolling = 0;
202
- this.pollEquipmentAsync();
203
- }
204
- public get id(): number { return typeof this.pump !== 'undefined' ? this.pump.id : -1; }
205
- public async setPumpStateAsync(pstate: PumpState) {
206
- try {
207
- // Here we go we need to set the pump state.
208
- return new InterfaceServerResponse(200, 'Ok');
209
- } catch (err) { return Promise.reject(`Nixie Error setting pump state ${pstate.id}-${pstate.name}: ${err.message}`); }
210
- }
211
- public async setPumpAsync(data: any): Promise<InterfaceServerResponse> {
212
- try {
213
- this.pump.master = 1;
214
- // if (typeof data.isVirtual !== 'undefined') this.pump.isVirtual = data.isVirtual;
215
- this.pump.isActive = true;
216
- // if (typeof data.type !== 'undefined' && data.type !== this.pump.type) {
217
- // sys.board.pumps.setType(this.pump, data.type);
218
- // this.pump = sys.pumps.getItemById(id, true);
219
- // spump = state.pumps.getItemById(id, true);
220
- // }
221
- let type = sys.board.valueMaps.pumpTypes.transform(this.pump.type);
222
- this.pump.name = data.name || this.pump.name || type.desc;
223
- if (typeof type.maxCircuits !== 'undefined' && type.maxCircuits > 0 && typeof data.circuits !== 'undefined') { // This pump type supports circuits
224
- for (let i = 1; i <= data.circuits.length && i <= type.maxCircuits; i++) {
225
- let c = data.circuits[i - 1];
226
- c.id = i;
227
- let circuit = parseInt(c.circuit, 10);
228
- let cd = this.pump.circuits.find(elem => elem.circuit === circuit);
229
- let speed = parseInt(c.speed, 10);
230
- let relay = parseInt(c.relay, 10);
231
- let flow = parseInt(c.flow, 10);
232
- let units = typeof c.units !== 'undefined' ? sys.board.valueMaps.pumpUnits.encode(c.units) : undefined;
233
- switch (type.name) {
234
- case 'vf':
235
- units = sys.board.valueMaps.pumpUnits.getValue('gpm');
236
- break;
237
- case 'hwvs':
238
- case 'vssvrs':
239
- case 'vs':
240
- case 'regalmodbus':
241
- c.units = sys.board.valueMaps.pumpUnits.getValue('rpm');
242
- break;
243
- case 'ss':
244
- case 'ds':
245
- case 'sf':
246
- case 'hwrly':
247
- c.units = undefined;
248
- break;
249
- }
250
- if (isNaN(units)) units = typeof cd !== 'undefined' ? cd.units : sys.board.valueMaps.pumpUnits.getValue('rpm');
251
- if (isNaN(speed)) speed = type.minSpeed;
252
- if (isNaN(flow)) flow = type.minFlow;
253
- if (isNaN(relay)) relay = 1;
254
- c.units = units;
255
- //c.units = parseInt(c.units, 10) || type.name === 'vf' ? sys.board.valueMaps.pumpUnits.getValue('gpm') : sys.board.valueMaps.pumpUnits.getValue('rpm');
256
- if (typeof type.minSpeed !== 'undefined' && c.units === sys.board.valueMaps.pumpUnits.getValue('rpm')) {
257
- c.speed = speed;
258
- }
259
- else if (typeof type.minFlow !== 'undefined' && c.units === sys.board.valueMaps.pumpUnits.getValue('gpm')) {
260
- c.flow = flow;
261
- }
262
- else if (type.maxRelays > 0)
263
- c.relay = relay;
264
- }
265
- }
266
- else data.circuits = [];
267
- this.pump.set(data); // Sets all the data back to the pump. This also sets the relays should it exist on the data.
268
- let spump = state.pumps.getItemById(this.pump.id, true);
269
- spump.name = this.pump.name;
270
- spump.address = this.pump.address;
271
- spump.type = this.pump.type;
272
- sys.pumps.sortById();
273
- state.pumps.sortById();
274
- this.pump.hasChanged = true;
275
- this.pollEquipmentAsync();
276
- return Promise.resolve(new InterfaceServerResponse(200, 'Ok'));
277
- }
278
- catch (err) { logger.error(`Nixie setPumpAsync: ${err.message}`); return Promise.reject(err); }
279
- }
280
- public async pollEquipmentAsync() {
281
- let self = this;
282
- try {
283
- // RSG 8-2022. Refactored to add initasync. With this.pollEquipmentAsync inside the
284
- // constructor we could get here before the pump is initialized. The added check
285
- // for the 112 address prevented that previously, but now is just a final fail safe.
286
- if (typeof this._pollTimer !== 'undefined' || this._pollTimer) clearTimeout(this._pollTimer);
287
- this._pollTimer = null;
288
- if (this.suspendPolling || this.closing || this.pump.address > 112) {
289
- if (this.suspendPolling) logger.info(`Pump ${this.id} Polling Suspended`);
290
- if (this.closing) logger.info(`Pump ${this.id} is closing`);
291
- return;
292
- }
293
- let pstate = state.pumps.getItemById(this.pump.id);
294
- this.setTargetSpeed(pstate);
295
- await this.setPumpStateAsync(pstate);
296
- }
297
- catch (err) { logger.error(`Nixie Error running pump sequence - ${err}`); }
298
- finally { if (!self.closing) this._pollTimer = setTimeoutSync(async () => await self.pollEquipmentAsync(), self.pollingInterval || 2000); }
299
- }
300
- private async checkHardwareStatusAsync(connectionId: string, deviceBinding: string) {
301
- try {
302
- let dev = await NixieEquipment.getDeviceService(connectionId, `/status/device/${deviceBinding}`);
303
- return dev;
304
- } catch (err) { logger.error(`Nixie Pump checkHardwareStatusAsync: ${err.message}`); return { hasFault: true } }
305
- }
306
- public async validateSetupAsync(pump: Pump, pstate: PumpState) {
307
- try {
308
- } catch (err) { logger.error(`Nixie Error checking Pump Hardware ${this.pump.name}: ${err.message}`); return Promise.reject(err); }
309
- }
310
- public async closeAsync() {
311
- try {
312
- logger.info(`Nixie Pump closing ${this.pump.name}.`)
313
- if (typeof this._pollTimer !== 'undefined' || this._pollTimer) clearTimeout(this._pollTimer);
314
- this._pollTimer = null;
315
- this._targetSpeed = 0;
316
- let pstate = state.pumps.getItemById(this.pump.id);
317
- try {
318
- await this.setPumpStateAsync(pstate);
319
- // Since we are closing we need to not reject.
320
- } catch (err) { logger.error(`Nixie Closing pump closeAsync: ${err.message}`); }
321
- // This will make sure the timer is dead and we are completely closed.
322
- this.closing = true;
323
- if (typeof this._pollTimer !== 'undefined' || this._pollTimer) clearTimeout(this._pollTimer);
324
- pstate.emitEquipmentChange();
325
- }
326
- catch (err) { logger.error(`Nixie Pump closeAsync: ${err.message}`); return Promise.reject(err); }
327
- }
328
- public logData(filename: string, data: any) { this.controlPanel.logData(filename, data); }
329
- public setTargetSpeed(pstate: PumpState) { };
330
- protected isBodyOn(bodyCode: number) {
331
- let assoc = sys.board.valueMaps.pumpBodies.transform(bodyCode);
332
- switch (assoc.name) {
333
- case 'body1':
334
- case 'pool':
335
- return state.temps.bodies.getItemById(1).isOn;
336
- case 'body2':
337
- case 'spa':
338
- return state.temps.bodies.getItemById(2).isOn;
339
- case 'body3':
340
- return state.temps.bodies.getItemById(3).isOn;
341
- case 'body4':
342
- return state.temps.bodies.getItemById(4).isOn;
343
- case 'poolspa':
344
- if (sys.equipment.shared && sys.equipment.maxBodies >= 2) {
345
- return state.temps.bodies.getItemById(1).isOn === true || state.temps.bodies.getItemById(2).isOn === true;
346
- }
347
- else
348
- return state.temps.bodies.getItemById(1).isOn;
349
- }
350
- return false;
351
- }
352
- }
353
- export class NixiePumpSS extends NixiePump {
354
- public setTargetSpeed(pState: PumpState) {
355
- // Turn on ss pumps.
356
- let _newSpeed = 0;
357
- if (!pState.pumpOnDelay) {
358
- let pt = sys.board.valueMaps.pumpTypes.get(this.pump.type);
359
- if (pt.maxCircuits === 0 || pt.hasBody) {
360
- _newSpeed = this.isBodyOn(this.pump.body) ? 1 : 0;
361
- //console.log(`BODY: ${sys.board.bodies.isBodyOn(this.pump.body)} CODE: ${this.pump.body}`);
362
- }
363
- else if (!pState.pumpOnDelay) {
364
- let pumpCircuits: PumpCircuit[] = this.pump.circuits.get();
365
- if (!pState.pumpOnDelay) {
366
- for (let i = 0; i < pumpCircuits.length; i++) {
367
- let circ = state.circuits.getInterfaceById(pumpCircuits[i].circuit);
368
- if (circ.isOn) _newSpeed = 1;
369
- }
370
- }
371
- }
372
- }
373
- if (this._targetSpeed !== _newSpeed) logger.info(`NCP: Setting Pump ${this.pump.name} to ${_newSpeed > 0 ? 'on' : 'off'}. ${sys.board.bodies.isBodyOn(this.pump.body)}`);
374
- if (isNaN(_newSpeed)) _newSpeed = 0;
375
- this._targetSpeed = _newSpeed;
376
- }
377
- public async setServiceModeAsync() {
378
- let pstate = state.pumps.getItemById(this.pump.id);
379
- pstate.targetSpeed = this._targetSpeed = 0;
380
- await this.setPumpStateAsync(pstate);
381
- }
382
- public async setPumpStateAsync(pstate: PumpState) {
383
- let relays: PumpRelay[] = this.pump.relays.get();
384
- let relayState = 0;
385
- for (let i = 0; i < relays.length; i++) {
386
- let pr = relays[i];
387
- if (typeof pr.id === 'undefined') pr.id = i + 1; // remove when id is added to dP relays upon save.
388
- let isOn = this._targetSpeed >> pr.id - 1 & 1;
389
- if (utils.isNullOrEmpty(pr.connectionId) || utils.isNullOrEmpty(pr.deviceBinding)) {
390
- // If they haven't set a program for the relay bugger out.
391
- if (isOn) relayState |= (1 << pr.id - 1);
392
- }
393
- else {
394
- try {
395
- let res = await NixieEquipment.putDeviceService(pr.connectionId, `/state/device/${pr.deviceBinding}`, { isOn, latch: isOn ? 5000 : undefined });
396
- if (res.status.code === 200) {
397
- if (isOn) relayState |= (1 << pr.id - 1);
398
- }
399
- else pstate.status = 16;
400
- }
401
- catch (err) {
402
- logger.error(`NCP: Error setting pump ${this.pump.name} relay ${pr.id} to ${isOn ? 'on' : 'off'}. Error ${err.message}}`);
403
- pstate.status = 16;
404
- }
405
- }
406
- }
407
- if (pstate.targetSpeed === 0) {
408
- pstate.status = 0;
409
- pstate.driveState = 0; // We need to set this if it is a priming cycle but it might not matter for our relay based pumps.
410
- pstate.command = 0;
411
- }
412
- else if (relayState === pstate.targetSpeed) {
413
- pstate.status = 1;
414
- pstate.driveState = 2;
415
- pstate.command = 4;
416
- }
417
- pstate.relay = relayState;
418
- return new InterfaceServerResponse(200, 'Success');
419
- }
420
- }
421
- export class NixiePumpDS extends NixiePumpSS {
422
- public setTargetSpeed(pState: PumpState) {
423
- // Turn on sf pumps. The new speed will be the relays associated with the pump. I believe when this comes out in the final
424
- // wash it should engage all the relays for all speeds associated with the pump. The pump logic will determine which program is
425
- // the one to engage.
426
- let _newSpeed = 0;
427
- if (!pState.pumpOnDelay) {
428
- let pumpCircuits: PumpCircuit[] = this.pump.circuits.get();
429
- if (!pState.pumpOnDelay) {
430
- for (let i = 0; i < pumpCircuits.length; i++) {
431
- let circ = state.circuits.getInterfaceById(pumpCircuits[i].circuit);
432
- // relay speeds are bit-shifted 'or' based on 1,2,4,8
433
- if (circ.isOn) _newSpeed |= (1 << pumpCircuits[i].relay - 1);
434
- }
435
- }
436
- }
437
- if (isNaN(_newSpeed)) _newSpeed = 0;
438
- this.logSpeed(_newSpeed);
439
- this._targetSpeed = _newSpeed;
440
- }
441
- public logSpeed(_newSpeed: number) {
442
- 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'}.`);
443
- }
444
- }
445
- export class NixiePumpSF extends NixiePumpDS {
446
- // effectively operates the same way as a DS pump since we removed the body association on DS.
447
- // only logger msg is different
448
- public logSpeed(_newSpeed: number) {
449
- 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'}.`);
450
- }
451
- }
452
- export class NixiePumpHWRLY extends NixiePumpDS {
453
- // This operates as a relay pump with up to 8 speeds. The speeds are defined as follows. The override
454
- // relay should be defined as being normally closed. When it opens then the pump will turn on to the speed.
455
- // +-------+---------+---------+---------+---------+
456
- // + Speed | Relay 1 | Relay 2 | Relay 3 | OVRD |
457
- // +-------+---------+---------+---------+---------+
458
- // | OFF | OFF | OFF | OFF | OFF |
459
- // +-------+---------+---------+---------+---------+
460
- // | 1 | OFF | OFF | OFF | ON |
461
- // +-------+---------+---------+---------+---------+
462
- // | 2 | ON | OFF | OFF | ON |
463
- // +-------+---------+---------+---------+---------+
464
- // | 3 | OFF | ON | OFF | ON |
465
- // +-------+---------+---------+---------+---------+
466
- // | 4 | ON | ON | OFF | ON |
467
- // +-------+---------+---------+---------+---------+
468
- // | 5 | OFF | OFF | ON | ON |
469
- // +-------+---------+---------+---------+---------+
470
- // | 6 | ON | OFF | ON | ON |
471
- // +-------+---------+---------+---------+---------+
472
- // | 7 | OFF | ON | ON | ON |
473
- // +-------+---------+---------+---------+---------+
474
- // | 8 | ON | ON | ON | ON |
475
- // +-------+---------+---------+---------+---------+
476
-
477
- public setTargetSpeed(pState: PumpState) {
478
- let _newSpeed = 0;
479
- if (!pState.pumpOnDelay) {
480
- let pumpCircuits = this.pump.circuits.get();
481
- for (let i = 0; i < pumpCircuits.length; i++) {
482
- let circ = state.circuits.getInterfaceById(pumpCircuits[i].circuit);
483
- let pc = pumpCircuits[i];
484
- if (circ.isOn) {
485
- _newSpeed = Math.max(_newSpeed, pc.relay);
486
- }
487
- }
488
- }
489
- if (isNaN(_newSpeed)) _newSpeed = 0;
490
- this._targetSpeed = _newSpeed;
491
- if (this._targetSpeed !== 0) Math.min(Math.max(this.pump.minSpeed, this._targetSpeed), this.pump.maxSpeed);
492
- if (this._targetSpeed !== _newSpeed) logger.info(`NCP: Setting Pump ${this.pump.name} to ${_newSpeed}.`);
493
- }
494
- public async setPumpStateAsync(pstate: PumpState) {
495
- // Don't poll while we are seting the state.
496
- this.suspendPolling = true;
497
- try {
498
- let relays: PumpRelay[] = this.pump.relays.get();
499
- let relayState = 0;
500
- let targetState = 0;
501
- for (let i = 0; i < relays.length; i++) {
502
- let pr = relays[i];
503
- if (typeof pr.id === 'undefined') pr.id = i + 1; // remove when id is added to dP relays upon save.
504
- // 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
505
- // configuration so that whenever the pump is off the relay terminals are closed.
506
- let isOn = this._targetSpeed > 0 ? i === 3 ? true : (this._targetSpeed - 1 & (1 << i)) > 0 : false;
507
- let bit = isOn ? (1 << i) : 0;
508
- targetState |= bit;
509
- if (utils.isNullOrEmpty(pr.connectionId) || utils.isNullOrEmpty(pr.deviceBinding)) {
510
- // Determine whether the relay should be on.
511
- relayState |= bit;
512
- }
513
- else {
514
- try {
515
- let res = await NixieEquipment.putDeviceService(pr.connectionId, `/state/device/${pr.deviceBinding}`, { isOn, latch: isOn ? 5000 : undefined });
516
- if (res.status.code === 200) {
517
- relayState |= bit;
518
- }
519
- else pstate.status = 16;
520
- }
521
- catch (err) {
522
- logger.error(`NCP: Error setting pump ${this.pump.name} relay ${pr.id} to ${isOn ? 'on' : 'off'}. Error ${err.message}}`);
523
- pstate.status = 16;
524
- }
525
- }
526
- }
527
- pstate.command = this._targetSpeed;
528
- if (targetState === relayState) {
529
- pstate.status = relayState > 0 ? 1 : 0;
530
- pstate.driveState = relayState > 0 ? 2 : 0;
531
- pstate.relay = relayState;
532
- }
533
- else {
534
- pstate.driveState = 0;
535
- }
536
- return new InterfaceServerResponse(200, 'Success');
537
- }
538
- catch (err) {
539
- logger.error(`Error running pump sequence for ${this.pump.name}: ${err.message}`);
540
- return Promise.reject(err);
541
- }
542
- finally { this.suspendPolling = false; }
543
- };
544
-
545
- }
546
- export class NixiePumpRS485 extends NixiePump {
547
- public async setServiceModeAsync() {
548
- this._targetSpeed = 0;
549
- await this.setDriveStateAsync(false);
550
- await this.setPumpToRemoteControlAsync(false);
551
- }
552
- public async setPumpStateAsync(pstate: PumpState) {
553
- // Don't poll while we are seting the state.
554
- this.suspendPolling = true;
555
- try {
556
- let pt = sys.board.valueMaps.pumpTypes.get(this.pump.type);
557
- if (state.mode === 0) {
558
- // Since these process are async the closing flag can be set
559
- // between calls. We need to check it in between each call.
560
- if (!this.closing) await this.setDriveStateAsync();
561
- if (!this.closing) {
562
- if (this._targetSpeed >= pt.minFlow && this._targetSpeed <= pt.maxFlow) await this.setPumpGPMAsync();
563
- else if (this._targetSpeed >= pt.minSpeed && this._targetSpeed <= pt.maxSpeed) await this.setPumpRPMAsync();
564
- }
565
- ;
566
- if (!this.closing && pt.name !== 'vsf' && pt.name !== 'vs') await this.setPumpFeatureAsync(6);;
567
- if (!this.closing) await setTimeout(1000);;
568
- if (!this.closing) await this.requestPumpStatusAsync();;
569
- if (!this.closing) await this.setPumpToRemoteControlAsync();;
570
- }
571
- return new InterfaceServerResponse(200, 'Success');
572
- }
573
- catch (err) {
574
- logger.error(`Error running pump sequence for ${this.pump.name}: ${err.message}`);
575
- return Promise.reject(err);
576
- }
577
- finally { this.suspendPolling = false; }
578
- };
579
- protected async setDriveStateAsync(running: boolean = true) {
580
- try {
581
- if (conn.isPortEnabled(this.pump.portId || 0)) {
582
- let out = Outbound.create({
583
- portId: this.pump.portId || 0,
584
- protocol: Protocol.Pump,
585
- dest: this.pump.address,
586
- action: 6,
587
- payload: running && this._targetSpeed > 0 ? [10] : [4],
588
- retries: 1,
589
- response: true
590
- });
591
- try {
592
- await out.sendAsync();
593
- }
594
- catch (err) {
595
- logger.error(`Error sending setDriveState for ${this.pump.name}: ${err.message}`);
596
- }
597
- }
598
- else {
599
- let pstate = state.pumps.getItemById(this.pump.id);
600
- pstate.command = pstate.rpm > 0 || pstate.flow > 0 ? 10 : 0;
601
- }
602
- } catch (err) { logger.error(`Error setting driveState for ${this.pump.name}: ${err.message}`); }
603
- };
604
- protected async requestPumpStatusAsync() {
605
- if (conn.isPortEnabled(this.pump.portId || 0)) {
606
- let out = Outbound.create({
607
- portId: this.pump.portId || 0,
608
- protocol: Protocol.Pump,
609
- dest: this.pump.address,
610
- action: 7,
611
- payload: [],
612
- retries: 2,
613
- response: true,
614
- });
615
- try {
616
- await out.sendAsync();
617
- }
618
- catch (err) {
619
- logger.error(`Error sending requestPumpStatus for ${this.pump.name}: ${err.message}`);
620
- }
621
- }
622
- };
623
- protected async setPumpToRemoteControlAsync(running: boolean = true) {
624
- try {
625
- if (conn.isPortEnabled(this.pump.portId || 0)) {
626
- let out = Outbound.create({
627
- portId: this.pump.portId || 0,
628
- protocol: Protocol.Pump,
629
- dest: this.pump.address,
630
- action: 4,
631
- payload: running ? [255] : [0], // when stopAsync is called, pass false to return control to pump panel
632
- retries: 1,
633
- response: true
634
- });
635
- try {
636
- await out.sendAsync();
637
- }
638
- catch (err) {
639
- logger.error(`Error sending setPumpToRemoteControl for ${this.pump.name}: ${err.message}`);
640
- }
641
- }
642
- } catch (err) { logger.error(`Error setting pump to Remote Control for ${this.pump.name}: ${err.message}`); }
643
- }
644
- protected async setPumpFeatureAsync(feature?: number) {
645
- // empty payload (possibly 0?, too) is no feature
646
- // 6: Feature 1
647
- try {
648
- if (conn.isPortEnabled(this.pump.portId || 0)) {
649
- let out = Outbound.create({
650
- portId: this.pump.portId || 0,
651
- protocol: Protocol.Pump,
652
- dest: this.pump.address,
653
- action: 5,
654
- payload: typeof feature === 'undefined' ? [] : [feature],
655
- retries: 2,
656
- response: true
657
- });
658
- try {
659
- await out.sendAsync();
660
- }
661
- catch (err) {
662
- logger.error(`Error sending setPumpFeature for ${this.pump.name}: ${err.message}`);
663
- }
664
- }
665
- } catch (err) { logger.error(`Error setting pump feature for ${this.pump.name}: ${err.message}`); }
666
- };
667
- protected async setPumpRPMAsync() {
668
- if (conn.isPortEnabled(this.pump.portId || 0)) {
669
- let out = Outbound.create({
670
- portId: this.pump.portId || 0,
671
- protocol: Protocol.Pump,
672
- dest: this.pump.address,
673
- action: 1,
674
- payload: [2, 196, Math.floor(this._targetSpeed / 256), this._targetSpeed % 256],
675
- retries: 1,
676
- // timeout: 250,
677
- response: true
678
- });
679
- try {
680
- await out.sendAsync();
681
- }
682
- catch (err) {
683
- logger.error(`Error sending setPumpRPMAsync for ${this.pump.name}: ${err.message}`);
684
- }
685
- }
686
- };
687
- protected async setPumpGPMAsync() {
688
- // packet for vf; vsf will override
689
- if (conn.isPortEnabled(this.pump.portId || 0)) {
690
- let out = Outbound.create({
691
- portId: this.pump.portId || 0,
692
- protocol: Protocol.Pump,
693
- dest: this.pump.address,
694
- action: 1,
695
- payload: [2, 228, 0, this._targetSpeed],
696
- retries: 1,
697
- response: true
698
- });
699
- try {
700
- await out.sendAsync();
701
- }
702
- catch (err) {
703
- logger.error(`Error sending setPumpGPMAsync for ${this.pump.name}: ${err.message}`);
704
- }
705
- }
706
- };
707
- public async closeAsync() {
708
- try {
709
- this.suspendPolling = true;
710
- logger.info(`Nixie Pump closing ${this.pump.name}.`)
711
- if (typeof this._pollTimer !== 'undefined' || this._pollTimer) clearTimeout(this._pollTimer);
712
- this._pollTimer = null;
713
- this.closing = true;
714
- let pt = sys.board.valueMaps.pumpTypes.get(this.pump.type);
715
- let pstate = state.pumps.getItemById(this.pump.id);
716
- this._targetSpeed = 0;
717
- await this.setDriveStateAsync(false);
718
- if (!this.closing && pt.name !== 'vsf' && pt.name !== 'vs') await this.setPumpFeatureAsync();
719
- //await this.setPumpFeature();
720
- //await this.setDriveStateAsync(false);
721
- await this.setPumpToRemoteControlAsync(false);
722
- // Make sure the polling timer is dead after we have closed this all off. That way we do not
723
- // have another process that revives it from the dead.
724
- if (typeof this._pollTimer !== 'undefined' || this._pollTimer) clearTimeout(this._pollTimer);
725
- this._pollTimer = null;
726
- pstate.emitEquipmentChange();
727
- }
728
- catch (err) { logger.error(`Nixie Pump closeAsync: ${err.message}`); return Promise.reject(err); }
729
- finally { this.suspendPolling = false; }
730
- }
731
- }
732
- export class NixiePumpVS extends NixiePumpRS485 {
733
- public setTargetSpeed(pState: PumpState) {
734
- let _newSpeed = 0;
735
- if (!pState.pumpOnDelay) {
736
- let pumpCircuits = this.pump.circuits.get();
737
-
738
- for (let i = 0; i < pumpCircuits.length; i++) {
739
- let circ = state.circuits.getInterfaceById(pumpCircuits[i].circuit);
740
- let pc = pumpCircuits[i];
741
- if (circ.isOn) _newSpeed = Math.max(_newSpeed, pc.speed);
742
- }
743
- }
744
- if (isNaN(_newSpeed)) _newSpeed = 0;
745
- this._targetSpeed = _newSpeed;
746
- if (this._targetSpeed !== 0) Math.min(Math.max(this.pump.minSpeed, this._targetSpeed), this.pump.maxSpeed);
747
- if (this._targetSpeed !== _newSpeed) logger.info(`NCP: Setting Pump ${this.pump.name} to ${_newSpeed} RPM.`);
748
- }
749
- }
750
- export class NixiePumpVF extends NixiePumpRS485 {
751
- public setTargetSpeed(pState: PumpState) {
752
- let _newSpeed = 0;
753
- if (!pState.pumpOnDelay) {
754
- let pumpCircuits = this.pump.circuits.get();
755
- for (let i = 0; i < pumpCircuits.length; i++) {
756
- let circ = state.circuits.getInterfaceById(pumpCircuits[i].circuit);
757
- let pc = pumpCircuits[i];
758
- if (circ.isOn) _newSpeed = Math.max(_newSpeed, pc.flow);
759
- }
760
- }
761
- if (isNaN(_newSpeed)) _newSpeed = 0;
762
- this._targetSpeed = _newSpeed;
763
- if (this._targetSpeed !== 0) Math.min(Math.max(this.pump.minFlow, this._targetSpeed), this.pump.maxFlow);
764
- if (this._targetSpeed !== _newSpeed) logger.info(`NCP: Setting Pump ${this.pump.name} to ${_newSpeed} GPM.`);
765
- }
766
- public async setPumpStateAsync(pstate: PumpState) {
767
- // Don't poll while we are seting the state.
768
- this.suspendPolling = true;
769
- try {
770
- let pt = sys.board.valueMaps.pumpTypes.get(this.pump.type);
771
- if (state.mode === 0) {
772
- // Since these process are async the closing flag can be set
773
- // between calls. We need to check it in between each call. // 4, 6, 5, 7
774
- // When we are 0 then it sends 4[255], 6[4], 5[6]
775
- // When we are not 0 then it sends 4[255], 6[10], 5[6], 1[flow]
776
- if (!this.closing) await this.setPumpToRemoteControlAsync(); // Action 4
777
- if (!this.closing && this._targetSpeed > 0) await this.setPumpGPMAsync(); // Action 1
778
- if (!this.closing && this._targetSpeed > 0) await this.setPumpFeatureAsync(6); // Action 5
779
- // RKS: 07-21-24 - This used to send an empty payload when the pump should be off. For VF pumps it
780
- // appears that not setting the feature or target flow will set the pump off when it gets to
781
- // the drive state.
782
- //if (!this.closing) await this.setPumpFeatureAsync(this._targetSpeed > 0 ? 6 : undefined); // Action 5
783
- if (!this.closing) await this.setDriveStateAsync(); // Action 6
784
- if (!this.closing) await setTimeout(200);
785
- if (!this.closing) await this.requestPumpStatusAsync(); // Action 7
786
- }
787
- return new InterfaceServerResponse(200, 'Success');
788
- }
789
- catch (err) {
790
- logger.error(`Error running pump sequence for ${this.pump.name}: ${err.message}`);
791
- return Promise.reject(err);
792
- }
793
- finally { this.suspendPolling = false; }
794
- };
795
- }
796
- export class NixiePumpVSF extends NixiePumpRS485 {
797
- public setTargetSpeed(pState: PumpState) {
798
- let _newSpeed = 0;
799
- let maxRPM = 0;
800
- let maxGPM = 0;
801
- let useFlow = false;
802
- if (!pState.pumpOnDelay) {
803
- let pumpCircuits = this.pump.circuits.get();
804
- let pt = sys.board.valueMaps.pumpTypes.get(this.pump.type);
805
- // 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
806
- // 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
807
- // the GPM to RPM but if there is none then it will use GPM.
808
- let toRPM = (flowRate: number, minSpeed: number = 450, maxSpeed: number = 3450) => {
809
- // eff = 114.4365
810
- // gpm = 80
811
- // speed = 2412
812
- let eff = .03317 * maxSpeed;
813
- let rpm = Math.min(Math.round((flowRate * maxSpeed) / eff), maxSpeed);
814
- return rpm > 0 ? Math.max(rpm, minSpeed) : 0;
815
- };
816
- let toGPM = (speed: number, maxSpeed: number = 3450, minFlow: number = 15, maxFlow: number = 140) => {
817
- // eff = 114.4365
818
- // speed = 1100
819
- // gpm = (114.4365 * 1100)/3450 = 36
820
- let eff = .03317 * maxSpeed;
821
- let gpm = Math.min(Math.round((eff * speed) / maxSpeed), maxFlow);
822
- return gpm > 0 ? Math.max(gpm, minFlow) : 0;
823
- }
824
- for (let i = 0; i < pumpCircuits.length; i++) {
825
- let circ = state.circuits.getInterfaceById(pumpCircuits[i].circuit);
826
- let pc = pumpCircuits[i];
827
- if (circ.isOn) {
828
- if (pc.units > 0) {
829
- let rpm = toRPM(pc.flow, pt.minSpeed, pt.MaxSpeed);
830
- if (rpm > maxRPM) useFlow = true;
831
- maxGPM = Math.max(maxGPM, pc.flow);
832
- rpm = Math.max(maxRPM, rpm);
833
- }
834
- else {
835
- let gpm = toGPM(pc.speed, pt.maxSpeed, pt.minFlow, pt.maxFlow);
836
- if (gpm > maxGPM) useFlow = false;
837
- maxRPM = Math.max(maxRPM, pc.speed);
838
- maxGPM = Math.max(maxGPM, gpm);
839
- }
840
- }
841
- }
842
- _newSpeed = useFlow ? maxGPM : maxRPM;
843
- }
844
- if (isNaN(_newSpeed)) _newSpeed = 0;
845
- // Send the flow message if it is flow and the rpm message if it is rpm.
846
- if (this._targetSpeed !== _newSpeed) logger.info(`NCP: Setting Pump ${this.pump.name} to ${_newSpeed} ${useFlow ? 'GPM' : 'RPM'}.`);
847
- this._targetSpeed = _newSpeed;
848
- }
849
- protected async setPumpRPMAsync() {
850
- // vsf action is 10 for rpm
851
- if (conn.isPortEnabled(this.pump.portId || 0)) {
852
- let out = Outbound.create({
853
- portId: this.pump.portId || 0,
854
- protocol: Protocol.Pump,
855
- dest: this.pump.address,
856
- action: 10,
857
- payload: [2, 196, Math.floor(this._targetSpeed / 256), this._targetSpeed % 256],
858
- retries: 1,
859
- // timeout: 250,
860
- response: true
861
- });
862
- try {
863
- await out.sendAsync();
864
- }
865
- catch (err) {
866
- logger.error(`Error sending setPumpRPMAsync for ${this.pump.name}: ${err.message}`);
867
- }
868
- }
869
- };
870
- protected async setPumpGPMAsync() {
871
- // vsf payload; different from vf payload
872
- if (conn.isPortEnabled(this.pump.portId || 0)) {
873
- let out = Outbound.create({
874
- portId: this.pump.portId || 0,
875
- protocol: Protocol.Pump,
876
- dest: this.pump.address,
877
- action: 9,
878
- payload: [2, 196, 0, this._targetSpeed],
879
- retries: 1,
880
- response: true
881
- });
882
- try {
883
- await out.sendAsync();
884
- }
885
- catch (err) {
886
- logger.error(`Error sending setPumpGPMAsync for ${this.pump.name}: ${err.message}`);
887
- }
888
- }
889
- };
890
- };
891
- export class NixiePumpHWVS extends NixiePumpRS485 {
892
- public setTargetSpeed(pState: PumpState) {
893
- let _newSpeed = 0;
894
- if (!pState.pumpOnDelay) {
895
- let pumpCircuits = this.pump.circuits.get();
896
-
897
- for (let i = 0; i < pumpCircuits.length; i++) {
898
- let circ = state.circuits.getInterfaceById(pumpCircuits[i].circuit);
899
- let pc = pumpCircuits[i];
900
- if (circ.isOn) _newSpeed = Math.max(_newSpeed, pc.speed);
901
- }
902
- }
903
- if (isNaN(_newSpeed)) _newSpeed = 0;
904
- this._targetSpeed = _newSpeed;
905
- if (this._targetSpeed !== 0) Math.min(Math.max(this.pump.minSpeed, this._targetSpeed), this.pump.maxSpeed);
906
- if (this._targetSpeed !== _newSpeed) logger.info(`NCP: Setting Pump ${this.pump.name} to ${_newSpeed} RPM.`);
907
- }
908
- public async setServiceModeAsync() {
909
- this._targetSpeed = 0;
910
- await this.setPumpRPMAsync();
911
- }
912
- public async setDriveStateAsync(running: boolean = false) { return Promise.resolve(); }
913
- public async setPumpStateAsync(pstate: PumpState) {
914
- // Don't poll while we are seting the state.
915
- this.suspendPolling = true;
916
- try {
917
- // Since these process are async the closing flag can be set
918
- // between calls. We need to check it in between each call.
919
- if (!this.closing) { await this.setPumpRPMAsync(); }
920
- return new InterfaceServerResponse(200, 'Success');
921
- }
922
- catch (err) {
923
- logger.error(`Error running pump sequence for ${this.pump.name}: ${err.message}`);
924
- return Promise.reject(err);
925
- }
926
- finally { this.suspendPolling = false; }
927
- };
928
- protected async requestPumpStatusAsync() { return Promise.resolve(); };
929
- protected setPumpFeatureAsync(feature?: number) { return Promise.resolve(); }
930
- protected async setPumpToRemoteControlAsync(running: boolean = true) {
931
- try {
932
- // We do nothing on this pump to set it to remote control. That is unless we are turning it off.
933
- if (conn.isPortEnabled(this.pump.portId || 0)) {
934
- if (!running) {
935
- let out = Outbound.create({
936
- portId: this.pump.portId || 0,
937
- protocol: Protocol.Hayward,
938
- source: 12, // Use the broadcast address
939
- dest: this.pump.address,
940
- action: 1,
941
- payload: [0], // when stopAsync is called, pass false to return control to pump panel
942
- // payload: spump.virtualControllerStatus === sys.board.valueMaps.virtualControllerStatus.getValue('running') ? [255] : [0],
943
- retries: 1,
944
- response: Response.create({ protocol: Protocol.Hayward, action: 12, source: this.pump.address - 96 })
945
- });
946
- try {
947
- await out.sendAsync();
948
- }
949
- catch (err) {
950
- logger.error(`Error sending setPumpToRemoteControl for ${this.pump.name}: ${err.message}`);
951
-
952
- }
953
- }
954
- }
955
- } catch(err) { `Error sending setPumpToRemoteControl message for ${this.pump.name}: ${err.message}` };
956
- }
957
- protected async setPumpRPMAsync() {
958
- // Address 1
959
- //[][16, 2, 12, 1, 0][41][0, 72, 16, 3] out
960
- //[][16, 2, 0, 12, 0][0, 41, 0, 135][0, 206, 16, 3] In
961
- // Address 2
962
- //[][16, 2, 12, 1, 1][100][0, 132, 16, 3] out
963
- //[][16, 2, 0, 12, 1][0, 96, 21, 64][0, 212, 16, 3] in
964
- // Note that action 12 is in a different position for the outbound than the inbound. The source and destination are kind
965
- // of a misnomer in that it identifies the equipment address in byte(4) of the header and flips the command address around.
966
- // So in essence for equipment item 0-16 (pump addresses) the outbound is really a broadcast on 12 (broadcast) from 1 and the inbound is
967
- // broadcast from the equipment item to 0 (anybody).
968
- if (conn.isPortEnabled(this.pump.portId || 0)) {
969
- let pt = sys.board.valueMaps.pumpTypes.get(this.pump.type);
970
- let out = Outbound.create({
971
- portId: this.pump.portId || 0,
972
- protocol: Protocol.Hayward,
973
- source: 1, // Use the broadcast address
974
- dest: this.pump.address - 96,
975
- action: 12,
976
- payload: [Math.min(Math.round((this._targetSpeed / pt.maxSpeed) * 100), 100)], // when stopAsync is called, pass false to return control to pump panel
977
- retries: 1,
978
- response: Response.create({ protocol: Protocol.Hayward, action: 12, source: this.pump.address - 96 })
979
- });
980
- try {
981
- await out.sendAsync();
982
- }
983
- catch (err) {
984
- logger.error(`Error sending setPumpRPM for ${this.pump.name}: ${err.message}`);
985
- let pstate = state.pumps.getItemById(this.pump.id);
986
- pstate.command = 0;
987
- pstate.rpm = 0;
988
- pstate.watts = 0;
989
- }
990
- }
991
- else {
992
- let pstate = state.pumps.getItemById(this.pump.id);
993
- pstate.command = 0;
994
- pstate.rpm = 0;
995
- pstate.watts = 0;
996
- }
997
-
998
- };
999
- }
1000
-
1001
- export class NixiePumpRegalModbus extends NixiePump {
1002
-
1003
- constructor(ncp: INixieControlPanel, pump: Pump) {
1004
- super(ncp, pump);
1005
- // this.pump = pump;
1006
- // this._targetSpeed = 0;
1007
- }
1008
-
1009
- public setTargetSpeed(pumpState: PumpState) {
1010
- let newSpeed = 0;
1011
- if (!pumpState.pumpOnDelay) {
1012
- let circuitConfigs = this.pump.circuits.get();
1013
- for (let i = 0; i < circuitConfigs.length; i++) {
1014
- let circuitConfig = circuitConfigs[i];
1015
- let circ = state.circuits.getInterfaceById(circuitConfig.circuit);
1016
- if (circ.isOn) newSpeed = Math.max(newSpeed, circuitConfig.speed);
1017
- }
1018
- }
1019
- if (isNaN(newSpeed)) newSpeed = 0;
1020
- this._targetSpeed = newSpeed;
1021
- if (this._targetSpeed !== 0) Math.min(Math.max(this.pump.minSpeed, this._targetSpeed), this.pump.maxSpeed);
1022
- if (this._targetSpeed !== newSpeed) logger.info(`NCP: Setting Pump ${this.pump.name} to ${newSpeed} RPM.`);
1023
- }
1024
-
1025
- public async setServiceModeAsync() {
1026
- this._targetSpeed = 0;
1027
- await this.setDriveStateAsync(false);
1028
- // await this.setPumpToRemoteControlAsync(false);
1029
- }
1030
-
1031
- public async setPumpStateAsync(pstate: PumpState) {
1032
- // Don't poll while we are seting the state.
1033
- this.suspendPolling = true;
1034
- try {
1035
- let pt = sys.board.valueMaps.pumpTypes.get(this.pump.type);
1036
- if (state.mode === 0) {
1037
- // Since these process are async the closing flag can be set
1038
- // between calls. We need to check it in between each call.
1039
- if (!this.closing) {
1040
- if (this._targetSpeed >= pt.minSpeed && this._targetSpeed <= pt.maxSpeed) await this.setPumpRPMAsync();
1041
- }
1042
- if (!this.closing) await this.setDriveStateAsync();
1043
- ;
1044
- // if (!this.closing && pt.name !== 'vsf' && pt.name !== 'vs') await this.setPumpFeatureAsync(6);;
1045
- if (!this.closing) await setTimeout(1000);;
1046
- if (!this.closing) await this.requestPumpStatusAsync();;
1047
- // if (!this.closing) await this.setPumpToRemoteControlAsync();;
1048
- }
1049
- return new InterfaceServerResponse(200, 'Success');
1050
- }
1051
- catch (err) {
1052
- logger.error(`Error running pump sequence for ${this.pump.name}: ${err.message}`);
1053
- return Promise.reject(err);
1054
- }
1055
- finally { this.suspendPolling = false; }
1056
- };
1057
- protected async setDriveStateAsync(isRunning: boolean = true) {
1058
- let functionCode = this._targetSpeed > 0 ? 0x41 : 0x42;
1059
- logger.debug(`NixiePumpRegalModbus: setDriveStateAsync ${this.pump.name} ${functionCode == 0x41 ? 'RUN' : functionCode == 0x42 ? 'STOP' : 'UNKNOWN'}`);
1060
- try {
1061
- if (conn.isPortEnabled(this.pump.portId || 0)) {
1062
- let functionCode = this._targetSpeed > 0 ? 0x41 : 0x42;
1063
- let out = Outbound.create({
1064
- portId: this.pump.portId || 0,
1065
- protocol: Protocol.RegalModbus,
1066
- dest: this.pump.address,
1067
- action: functionCode,
1068
- payload: [],
1069
- retries: 1,
1070
- response: true
1071
- });
1072
- try {
1073
- await out.sendAsync();
1074
- }
1075
- catch (err) {
1076
- logger.error(`Error sending setDriveState for ${this.pump.name}: ${err.message}`);
1077
- }
1078
- }
1079
- else {
1080
- let pumpState = state.pumps.getItemById(this.pump.id);
1081
- pumpState.command = pumpState.rpm > 0 || pumpState.flow > 0 ? 10 : 0; // dashPanel needs this to be set to 10 for running.
1082
- }
1083
- } catch (err) { logger.error(`Error setting driveState for ${this.pump.name}: ${err.message}`); }
1084
- };
1085
-
1086
- protected async requestPumpDriveStateAsync() {
1087
- if (conn.isPortEnabled(this.pump.portId || 0)) {
1088
- let out = Outbound.create({
1089
- portId: this.pump.portId || 0,
1090
- protocol: Protocol.RegalModbus,
1091
- dest: this.pump.address,
1092
- action: 0x43,
1093
- payload: [],
1094
- retries: 2,
1095
- response: true,
1096
- });
1097
- try {
1098
- await out.sendAsync();
1099
- }
1100
- catch (err) {
1101
- logger.error(`Error sending requestPumpDriveState for ${this.pump.name}: ${err.message}`);
1102
- }
1103
- }
1104
- }
1105
-
1106
- protected async requestSensorAsync(page: number, sensorAddr: number, retries: number = 2) {
1107
- if (conn.isPortEnabled(this.pump.portId || 0)) {
1108
- let out = Outbound.create({
1109
- portId: this.pump.portId || 0,
1110
- protocol: Protocol.RegalModbus,
1111
- dest: this.pump.address,
1112
- action: 0x45,
1113
- payload: [page, sensorAddr],
1114
- retries: retries,
1115
- response: true,
1116
- });
1117
- try {
1118
- await out.sendAsync();
1119
- }
1120
- catch (err) {
1121
- logger.error(`Error sending requestSensor for ${this.pump.name} page ${page} sensor ${sensorAddr}: ${err.message}`);
1122
- }
1123
- }
1124
- }
1125
-
1126
- protected async requestPumpStatusAsync() {
1127
-
1128
- await this.requestPumpDriveStateAsync();
1129
- await this.requestSensorAsync(0, 0x00); // motor speed
1130
- // await this.requestSensorAsync(0, 0x01); // motor current
1131
- // await this.requestSensorAsync(0, 0x04); // torque
1132
- // await this.requestSensorAsync(0, 0x05); // inverter input power
1133
- // await this.requestSensorAsync(0, 0x06); // DC bus voltage
1134
- // await this.requestSensorAsync(0, 0x07); // ambient temperature
1135
- await this.requestSensorAsync(0, 0x0A); // output power
1136
- // await this.requestSensorAsync(0, 0x0D); // motor line voltage
1137
- // await this.requestSensorAsync(0, 0x0E); // ramp status
1138
- // await this.requestSensorAsync(0, 0x0F); // no of total fault
1139
- // await this.requestSensorAsync(0, 0x10); // prime status
1140
- // await this.requestSensorAsync(0, 0x11); // motor input power
1141
- // await this.requestSensorAsync(0, 0x12); // IGBT temperature
1142
- // await this.requestSensorAsync(0, 0x13); // PCB temperature
1143
- };
1144
-
1145
- protected async setPumpRPMAsync() {
1146
- logger.debug(`NixiePumpRegalModbus: setPumpRPMAsync ${this.pump.name} ${this._targetSpeed}`);
1147
- if (conn.isPortEnabled(this.pump.portId || 0)) {
1148
- // get demand bytes from rpm
1149
- const mode = 0x00; // 0x00 for speed control mode
1150
- const demandLo = Math.round(this._targetSpeed * 4) & 0xFF;
1151
- const demandHi = (Math.round(this._targetSpeed * 4) >> 8) & 0xFF;
1152
- let out = Outbound.create({
1153
- portId: this.pump.portId || 0,
1154
- protocol: Protocol.RegalModbus,
1155
- dest: this.pump.address,
1156
- action: 0x44,
1157
- payload: [mode, demandLo, demandHi],
1158
- retries: 1,
1159
- // timeout: 250,
1160
- response: true
1161
- });
1162
- try {
1163
- await out.sendAsync();
1164
- }
1165
- catch (err) {
1166
- logger.error(`Error sending setPumpRPMAsync for ${this.pump.name}: ${err.message}`);
1167
- }
1168
- }
1169
- };
1170
- public async closeAsync() {
1171
- try {
1172
- this.suspendPolling = true;
1173
- logger.info(`Nixie Pump closing ${this.pump.name}.`)
1174
- if (typeof this._pollTimer !== 'undefined' || this._pollTimer) clearTimeout(this._pollTimer);
1175
- this._pollTimer = null;
1176
- this.closing = true;
1177
- let pumpType = sys.board.valueMaps.pumpTypes.get(this.pump.type);
1178
- let pumpState = state.pumps.getItemById(this.pump.id);
1179
- this._targetSpeed = 0;
1180
- await this.setDriveStateAsync(false);
1181
- // if (!this.closing && pt.name !== 'vsf' && pt.name !== 'vs') await this.setPumpFeatureAsync();
1182
- //await this.setPumpFeature();
1183
- //await this.setDriveStateAsync(false);
1184
- // await this.setPumpToRemoteControlAsync(false);
1185
- // Make sure the polling timer is dead after we have closed this all off. That way we do not
1186
- // have another process that revives it from the dead.
1187
- if (typeof this._pollTimer !== 'undefined' || this._pollTimer) clearTimeout(this._pollTimer);
1188
- this._pollTimer = null;
1189
- pumpState.emitEquipmentChange();
1190
- }
1191
- catch (err) { logger.error(`Nixie Pump closeAsync: ${err.message}`); return Promise.reject(err); }
1192
- finally { this.suspendPolling = false; }
1193
- }
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 as setTimeoutSync, 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
+ import { setTimeout } from 'timers/promises';
14
+ import { NeptuneModbusStateMessage } from '../../comms/messages/status/NeptuneModbusStateMessage';
15
+
16
+ export class NixiePumpCollection extends NixieEquipmentCollection<NixiePump> {
17
+ public async deletePumpAsync(id: number) {
18
+ try {
19
+ for (let i = this.length - 1; i >= 0; i--) {
20
+ let pump = this[i];
21
+ if (pump.id === id) {
22
+ await pump.closeAsync();
23
+ this.splice(i, 1);
24
+ }
25
+ }
26
+ } catch (err) { logger.error(`Nixie Control Panel deletePumpAsync ${err.message}`); }
27
+ }
28
+ public async setPumpStateAsync(pstate: PumpState) {
29
+ try {
30
+ let pump: NixiePump = this.find(elem => elem.id === pstate.id) as NixiePump;
31
+ if (typeof pump === 'undefined') {
32
+ return logger.error(`Nixie Control Panel Error setPumpState could not find pump ${pstate.id}-${pstate.name}`);
33
+ }
34
+ await pump.setPumpStateAsync(pstate);
35
+ } catch (err) { logger.error(`Nixie Error setting pump state ${pstate.id}-${pstate.name}: ${err.message}`); return Promise.reject(err); }
36
+ }
37
+ public async setPumpAsync(pump: Pump, data: any) {
38
+ // By the time we get here we know that we are in control and this is a Nixie pump.
39
+ try {
40
+ let c: NixiePump = this.find(elem => elem.id === pump.id) as NixiePump;
41
+ if (typeof c === 'undefined') {
42
+ pump.master = 1;
43
+ if (typeof data.type !== 'undefined') pump.type = data.type; // needed for init of correct type
44
+ if (typeof pump.type === 'undefined') return Promise.reject(new InvalidEquipmentIdError(`Invalid pump type for ${pump.name}`, data.id, 'Pump'));
45
+ c = this.pumpFactory(pump);
46
+ // c = new NixiePump(this.controlPanel, pump);
47
+ this.push(c);
48
+ logger.info(`A pump was not found for id #${pump.id} creating pump`);
49
+ return await c.setPumpAsync(data);
50
+ }
51
+ else {
52
+ if (typeof data.type !== 'undefined' && c.pump.type !== data.type) {
53
+ // pump exists, changing type
54
+ await c.closeAsync();
55
+ pump.type = data.type; // needed for init of correct type
56
+ if (typeof pump.type === 'undefined') return Promise.reject(new InvalidEquipmentIdError(`Invalid pump type for ${pump.name}`, data.id, 'Pump'));
57
+ c = this.pumpFactory(pump);
58
+ }
59
+ return await c.setPumpAsync(data);
60
+ }
61
+ }
62
+ catch (err) { logger.error(`setPumpAsync: ${err.message}`); return Promise.reject(err); }
63
+ }
64
+ public async initAsync(pumps: PumpCollection) {
65
+ try {
66
+ for (let i = 0; i < pumps.length; i++) {
67
+ let pump = pumps.getItemByIndex(i);
68
+ if (pump.master === 1) {
69
+ let p: NixiePump = this.find(elem => elem.id === pump.id) as NixiePump;
70
+ if (typeof p === 'undefined') {
71
+ let type = sys.board.valueMaps.pumpTypes.getName(pump.type);
72
+ let npump = this.pumpFactory(pump);
73
+ logger.info(`Initializing Nixie Pump ${npump.id}-${pump.name}`);
74
+ this.push(npump);
75
+ await npump.initAsync();
76
+ }
77
+ }
78
+ }
79
+ }
80
+ catch (err) { logger.error(`Nixie Pump initAsync Error: ${err.message}`); return Promise.reject(err); }
81
+ }
82
+ public async closeAsync() {
83
+ try {
84
+ for (let i = this.length - 1; i >= 0; i--) {
85
+ try {
86
+ try {
87
+ await this[i].closeAsync();
88
+ } catch (err) { logger.error(`Error attempting to close pump ${this[i].id}`); }
89
+ this.splice(i, 1);
90
+ } catch (err) { logger.error(`Error stopping Nixie Pump ${err}`); }
91
+ }
92
+ } catch (err) { } // Don't bail if we have an errror.
93
+ }
94
+ public async setServiceModeAsync() {
95
+ try {
96
+ for (let i = this.length - 1; i >= 0; i--) {
97
+ try {
98
+ let p = this[i] as NixiePump;
99
+ await p.setServiceModeAsync();
100
+ } catch (err) { logger.error(`Error setting service mode for Nixie Pump ${err}`); }
101
+ }
102
+ } catch (err) { } // Don't bail if we have an errror.
103
+ }
104
+ public async initPumpAsync(pump: Pump): Promise<NixiePump> {
105
+ try {
106
+ let c: NixiePump = this.find(elem => elem.id === pump.id) as NixiePump;
107
+ if (pump.master === 1) {
108
+ // if pump exists, close it so we can re-init
109
+ // (EG if pump type changes, we need to setup a new instance of the pump)
110
+ if (typeof c !== 'undefined' && c.pump.type !== pump.type) {
111
+ await c.closeAsync();
112
+ await this.deletePumpAsync(pump.id);
113
+ c = this.pumpFactory(pump);
114
+ this.push(c);
115
+ }
116
+ logger.info(`Initializing Existing Nixie Pump ${c.id}-${pump.name}`);
117
+
118
+ }
119
+ return c;
120
+ } catch (err) { return Promise.reject(logger.error(`Nixie Controller: initPumpAsync Error: ${err.message}`)); }
121
+ }
122
+ private pumpFactory(pump: Pump) {
123
+ let type = sys.board.valueMaps.pumpTypes.getName(pump.type);
124
+ switch (type) {
125
+ case 'ss':
126
+ return new NixiePumpSS(this.controlPanel, pump);
127
+ case 'ds':
128
+ return new NixiePumpDS(this.controlPanel, pump);
129
+ case 'vsf':
130
+ return new NixiePumpVSF(this.controlPanel, pump);
131
+ case 'vf':
132
+ return new NixiePumpVF(this.controlPanel, pump);
133
+ case 'sf':
134
+ return new NixiePumpSF(this.controlPanel, pump);
135
+ case 'vs':
136
+ return new NixiePumpVS(this.controlPanel, pump);
137
+ case 'hwvs':
138
+ return new NixiePumpHWVS(this.controlPanel, pump);
139
+ case 'hwrly':
140
+ return new NixiePumpHWRLY(this.controlPanel, pump);
141
+ case 'regalmodbus':
142
+ return new NixiePumpRegalModbus(this.controlPanel, pump);
143
+ case 'neptunemodbus':
144
+ return new NixiePumpNeptuneModbus(this.controlPanel, pump);
145
+ default:
146
+ throw new EquipmentNotFoundError(`NCP: Cannot create pump ${pump.name}.`, type);
147
+ }
148
+ }
149
+ public syncPumpStates() {
150
+ // loop through all pumps and update rates based on circuit changes
151
+ // this would happen in <2s anyway based on pollAsync but this is immediate.
152
+ for (let i = this.length - 1; i >= 0; i--) {
153
+ let pump = this[i] as NixiePump;
154
+ if (!pump.suspendPolling) setTimeoutSync(async () => { await pump.pollEquipmentAsync(); }, 100);
155
+ else {
156
+ pump.setTargetSpeed(state.pumps.getItemById(pump.id));
157
+ }
158
+ // RKS: 05-16-23 - Below backs up the processing.
159
+ /*
160
+
161
+ setTimeoutSync(async () => {
162
+ let pump = this[i] as NixiePump;
163
+ try {
164
+ if (!pump.closing) await pump.pollEquipmentAsync();
165
+ } catch (err) { }
166
+ }, 100);
167
+ */
168
+ }
169
+ }
170
+ }
171
+ export class NixiePump extends NixieEquipment {
172
+ public pollingInterval: number = 2000;
173
+ protected _pollTimer: NodeJS.Timeout = null;
174
+ public pump: Pump;
175
+ protected _targetSpeed: number;
176
+ protected _suspendPolling = 0;
177
+ public get suspendPolling(): boolean { return this._suspendPolling > 0; }
178
+ public set suspendPolling(val: boolean) {
179
+ this._suspendPolling = Math.max(0, this._suspendPolling + (val ? 1 : -1));
180
+ if (this._suspendPolling > 1) console.log(`Suspend Polling ${this._suspendPolling}`);
181
+ }
182
+ public closing = false;
183
+ public async setServiceModeAsync() {
184
+ let pstate = state.pumps.getItemById(this.pump.id);
185
+ await this.setPumpStateAsync(pstate);
186
+ }
187
+ /*
188
+ _targetSpeed will hold values as follows:
189
+ vs/vsf/vf: rpm/gpm;
190
+ ss: 0=off, 1=on;
191
+ ds/sf: bit shift 1-4 = values 1/2/4/8 for relays 1/2/3/4
192
+ */
193
+ constructor(ncp: INixieControlPanel, pump: Pump) {
194
+ super(ncp);
195
+ this.pump = pump;
196
+ this._targetSpeed = 0;
197
+ }
198
+ public async initAsync() {
199
+ if (this._pollTimer) {
200
+ clearTimeout(this._pollTimer);
201
+ this._pollTimer = undefined;
202
+ }
203
+ this.closing = false;
204
+ this._suspendPolling = 0;
205
+ this.pollEquipmentAsync();
206
+ }
207
+ public get id(): number { return typeof this.pump !== 'undefined' ? this.pump.id : -1; }
208
+ public async setPumpStateAsync(pstate: PumpState) {
209
+ try {
210
+ // Here we go we need to set the pump state.
211
+ return new InterfaceServerResponse(200, 'Ok');
212
+ } catch (err) { return Promise.reject(`Nixie Error setting pump state ${pstate.id}-${pstate.name}: ${err.message}`); }
213
+ }
214
+ public async setPumpAsync(data: any): Promise<InterfaceServerResponse> {
215
+ try {
216
+ this.pump.master = 1;
217
+ // if (typeof data.isVirtual !== 'undefined') this.pump.isVirtual = data.isVirtual;
218
+ this.pump.isActive = true;
219
+ // if (typeof data.type !== 'undefined' && data.type !== this.pump.type) {
220
+ // sys.board.pumps.setType(this.pump, data.type);
221
+ // this.pump = sys.pumps.getItemById(id, true);
222
+ // spump = state.pumps.getItemById(id, true);
223
+ // }
224
+ let type = sys.board.valueMaps.pumpTypes.transform(this.pump.type);
225
+ this.pump.name = data.name || this.pump.name || type.desc;
226
+ if (typeof type.maxCircuits !== 'undefined' && type.maxCircuits > 0 && typeof data.circuits !== 'undefined') { // This pump type supports circuits
227
+ for (let i = 1; i <= data.circuits.length && i <= type.maxCircuits; i++) {
228
+ let c = data.circuits[i - 1];
229
+ c.id = i;
230
+ let circuit = parseInt(c.circuit, 10);
231
+ let cd = this.pump.circuits.find(elem => elem.circuit === circuit);
232
+ let speed = parseInt(c.speed, 10);
233
+ let relay = parseInt(c.relay, 10);
234
+ let flow = parseInt(c.flow, 10);
235
+ let units = typeof c.units !== 'undefined' ? sys.board.valueMaps.pumpUnits.encode(c.units) : undefined;
236
+ switch (type.name) {
237
+ case 'vf':
238
+ units = sys.board.valueMaps.pumpUnits.getValue('gpm');
239
+ break;
240
+ case 'hwvs':
241
+ case 'vssvrs':
242
+ case 'vs':
243
+ case 'regalmodbus':
244
+ case 'neptunemodbus':
245
+ c.units = sys.board.valueMaps.pumpUnits.getValue('rpm');
246
+ break;
247
+ case 'ss':
248
+ case 'ds':
249
+ case 'sf':
250
+ case 'hwrly':
251
+ c.units = undefined;
252
+ break;
253
+ }
254
+ if (isNaN(units)) units = typeof cd !== 'undefined' ? cd.units : sys.board.valueMaps.pumpUnits.getValue('rpm');
255
+ if (isNaN(speed)) speed = type.minSpeed;
256
+ if (isNaN(flow)) flow = type.minFlow;
257
+ if (isNaN(relay)) relay = 1;
258
+ c.units = units;
259
+ //c.units = parseInt(c.units, 10) || type.name === 'vf' ? sys.board.valueMaps.pumpUnits.getValue('gpm') : sys.board.valueMaps.pumpUnits.getValue('rpm');
260
+ if (typeof type.minSpeed !== 'undefined' && c.units === sys.board.valueMaps.pumpUnits.getValue('rpm')) {
261
+ c.speed = speed;
262
+ }
263
+ else if (typeof type.minFlow !== 'undefined' && c.units === sys.board.valueMaps.pumpUnits.getValue('gpm')) {
264
+ c.flow = flow;
265
+ }
266
+ else if (type.maxRelays > 0)
267
+ c.relay = relay;
268
+ }
269
+ }
270
+ else data.circuits = [];
271
+ this.pump.set(data); // Sets all the data back to the pump. This also sets the relays should it exist on the data.
272
+ let spump = state.pumps.getItemById(this.pump.id, true);
273
+ spump.name = this.pump.name;
274
+ spump.address = this.pump.address;
275
+ spump.type = this.pump.type;
276
+ sys.pumps.sortById();
277
+ state.pumps.sortById();
278
+ this.pump.hasChanged = true;
279
+ this.pollEquipmentAsync();
280
+ return Promise.resolve(new InterfaceServerResponse(200, 'Ok'));
281
+ }
282
+ catch (err) { logger.error(`Nixie setPumpAsync: ${err.message}`); return Promise.reject(err); }
283
+ }
284
+ public async pollEquipmentAsync() {
285
+ let self = this;
286
+ try {
287
+ // RSG 8-2022. Refactored to add initasync. With this.pollEquipmentAsync inside the
288
+ // constructor we could get here before the pump is initialized. The added check
289
+ // for the 112 address prevented that previously, but now is just a final fail safe.
290
+ if (typeof this._pollTimer !== 'undefined' || this._pollTimer) clearTimeout(this._pollTimer);
291
+ this._pollTimer = null;
292
+ const pumpTypeName = sys.board.valueMaps.pumpTypes.getName(this.pump.type);
293
+ const supportsHighAddress = pumpTypeName === 'regalmodbus' || pumpTypeName === 'neptunemodbus';
294
+ if (this.suspendPolling || this.closing || (!supportsHighAddress && this.pump.address > 112)) {
295
+ if (this.suspendPolling) logger.info(`Pump ${this.id} Polling Suspended`);
296
+ if (this.closing) logger.info(`Pump ${this.id} is closing`);
297
+ return;
298
+ }
299
+ let pstate = state.pumps.getItemById(this.pump.id);
300
+ this.setTargetSpeed(pstate);
301
+ await this.setPumpStateAsync(pstate);
302
+ }
303
+ catch (err) { logger.error(`Nixie Error running pump sequence - ${err}`); }
304
+ finally { if (!self.closing) this._pollTimer = setTimeoutSync(async () => await self.pollEquipmentAsync(), self.pollingInterval || 2000); }
305
+ }
306
+ private async checkHardwareStatusAsync(connectionId: string, deviceBinding: string) {
307
+ try {
308
+ let dev = await NixieEquipment.getDeviceService(connectionId, `/status/device/${deviceBinding}`);
309
+ return dev;
310
+ } catch (err) { logger.error(`Nixie Pump checkHardwareStatusAsync: ${err.message}`); return { hasFault: true } }
311
+ }
312
+ public async validateSetupAsync(pump: Pump, pstate: PumpState) {
313
+ try {
314
+ } catch (err) { logger.error(`Nixie Error checking Pump Hardware ${this.pump.name}: ${err.message}`); return Promise.reject(err); }
315
+ }
316
+ public async closeAsync() {
317
+ try {
318
+ logger.info(`Nixie Pump closing ${this.pump.name}.`)
319
+ if (typeof this._pollTimer !== 'undefined' || this._pollTimer) clearTimeout(this._pollTimer);
320
+ this._pollTimer = null;
321
+ this._targetSpeed = 0;
322
+ let pstate = state.pumps.getItemById(this.pump.id);
323
+ try {
324
+ await this.setPumpStateAsync(pstate);
325
+ // Since we are closing we need to not reject.
326
+ } catch (err) { logger.error(`Nixie Closing pump closeAsync: ${err.message}`); }
327
+ // This will make sure the timer is dead and we are completely closed.
328
+ this.closing = true;
329
+ if (typeof this._pollTimer !== 'undefined' || this._pollTimer) clearTimeout(this._pollTimer);
330
+ pstate.emitEquipmentChange();
331
+ }
332
+ catch (err) { logger.error(`Nixie Pump closeAsync: ${err.message}`); return Promise.reject(err); }
333
+ }
334
+ public logData(filename: string, data: any) { this.controlPanel.logData(filename, data); }
335
+ public setTargetSpeed(pstate: PumpState) { };
336
+ protected isBodyOn(bodyCode: number) {
337
+ let assoc = sys.board.valueMaps.pumpBodies.transform(bodyCode);
338
+ switch (assoc.name) {
339
+ case 'body1':
340
+ case 'pool':
341
+ return state.temps.bodies.getItemById(1).isOn;
342
+ case 'body2':
343
+ case 'spa':
344
+ return state.temps.bodies.getItemById(2).isOn;
345
+ case 'body3':
346
+ return state.temps.bodies.getItemById(3).isOn;
347
+ case 'body4':
348
+ return state.temps.bodies.getItemById(4).isOn;
349
+ case 'poolspa':
350
+ if (sys.equipment.shared && sys.equipment.maxBodies >= 2) {
351
+ return state.temps.bodies.getItemById(1).isOn === true || state.temps.bodies.getItemById(2).isOn === true;
352
+ }
353
+ else
354
+ return state.temps.bodies.getItemById(1).isOn;
355
+ }
356
+ return false;
357
+ }
358
+ }
359
+ export class NixiePumpSS extends NixiePump {
360
+ public setTargetSpeed(pState: PumpState) {
361
+ // Turn on ss pumps.
362
+ let _newSpeed = 0;
363
+ if (!pState.pumpOnDelay) {
364
+ let pt = sys.board.valueMaps.pumpTypes.get(this.pump.type);
365
+ if (pt.maxCircuits === 0 || pt.hasBody) {
366
+ _newSpeed = this.isBodyOn(this.pump.body) ? 1 : 0;
367
+ //console.log(`BODY: ${sys.board.bodies.isBodyOn(this.pump.body)} CODE: ${this.pump.body}`);
368
+ }
369
+ else if (!pState.pumpOnDelay) {
370
+ let pumpCircuits: PumpCircuit[] = this.pump.circuits.get();
371
+ if (!pState.pumpOnDelay) {
372
+ for (let i = 0; i < pumpCircuits.length; i++) {
373
+ let circ = state.circuits.getInterfaceById(pumpCircuits[i].circuit);
374
+ if (circ.isOn) _newSpeed = 1;
375
+ }
376
+ }
377
+ }
378
+ }
379
+ if (this._targetSpeed !== _newSpeed) logger.info(`NCP: Setting Pump ${this.pump.name} to ${_newSpeed > 0 ? 'on' : 'off'}. ${sys.board.bodies.isBodyOn(this.pump.body)}`);
380
+ if (isNaN(_newSpeed)) _newSpeed = 0;
381
+ this._targetSpeed = _newSpeed;
382
+ }
383
+ public async setServiceModeAsync() {
384
+ let pstate = state.pumps.getItemById(this.pump.id);
385
+ pstate.targetSpeed = this._targetSpeed = 0;
386
+ await this.setPumpStateAsync(pstate);
387
+ }
388
+ public async setPumpStateAsync(pstate: PumpState) {
389
+ let relays: PumpRelay[] = this.pump.relays.get();
390
+ let relayState = 0;
391
+ for (let i = 0; i < relays.length; i++) {
392
+ let pr = relays[i];
393
+ if (typeof pr.id === 'undefined') pr.id = i + 1; // remove when id is added to dP relays upon save.
394
+ let isOn = this._targetSpeed >> pr.id - 1 & 1;
395
+ if (utils.isNullOrEmpty(pr.connectionId) || utils.isNullOrEmpty(pr.deviceBinding)) {
396
+ // If they haven't set a program for the relay bugger out.
397
+ if (isOn) relayState |= (1 << pr.id - 1);
398
+ }
399
+ else {
400
+ try {
401
+ let res = await NixieEquipment.putDeviceService(pr.connectionId, `/state/device/${pr.deviceBinding}`, { isOn, latch: isOn ? 5000 : undefined });
402
+ if (res.status.code === 200) {
403
+ if (isOn) relayState |= (1 << pr.id - 1);
404
+ }
405
+ else pstate.status = 16;
406
+ }
407
+ catch (err) {
408
+ logger.error(`NCP: Error setting pump ${this.pump.name} relay ${pr.id} to ${isOn ? 'on' : 'off'}. Error ${err.message}}`);
409
+ pstate.status = 16;
410
+ }
411
+ }
412
+ }
413
+ if (pstate.targetSpeed === 0) {
414
+ pstate.status = 0;
415
+ pstate.driveState = 0; // We need to set this if it is a priming cycle but it might not matter for our relay based pumps.
416
+ pstate.command = 0;
417
+ }
418
+ else if (relayState === pstate.targetSpeed) {
419
+ pstate.status = 1;
420
+ pstate.driveState = 2;
421
+ pstate.command = 4;
422
+ }
423
+ pstate.relay = relayState;
424
+ return new InterfaceServerResponse(200, 'Success');
425
+ }
426
+ }
427
+ export class NixiePumpDS extends NixiePumpSS {
428
+ public setTargetSpeed(pState: PumpState) {
429
+ // Turn on sf pumps. The new speed will be the relays associated with the pump. I believe when this comes out in the final
430
+ // wash it should engage all the relays for all speeds associated with the pump. The pump logic will determine which program is
431
+ // the one to engage.
432
+ let _newSpeed = 0;
433
+ if (!pState.pumpOnDelay) {
434
+ let pumpCircuits: PumpCircuit[] = this.pump.circuits.get();
435
+ if (!pState.pumpOnDelay) {
436
+ for (let i = 0; i < pumpCircuits.length; i++) {
437
+ let circ = state.circuits.getInterfaceById(pumpCircuits[i].circuit);
438
+ // relay speeds are bit-shifted 'or' based on 1,2,4,8
439
+ if (circ.isOn) _newSpeed |= (1 << pumpCircuits[i].relay - 1);
440
+ }
441
+ }
442
+ }
443
+ if (isNaN(_newSpeed)) _newSpeed = 0;
444
+ this.logSpeed(_newSpeed);
445
+ this._targetSpeed = _newSpeed;
446
+ }
447
+ public logSpeed(_newSpeed: number) {
448
+ 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'}.`);
449
+ }
450
+ }
451
+ export class NixiePumpSF extends NixiePumpDS {
452
+ // effectively operates the same way as a DS pump since we removed the body association on DS.
453
+ // only logger msg is different
454
+ public logSpeed(_newSpeed: number) {
455
+ 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'}.`);
456
+ }
457
+ }
458
+ export class NixiePumpHWRLY extends NixiePumpDS {
459
+ // This operates as a relay pump with up to 8 speeds. The speeds are defined as follows. The override
460
+ // relay should be defined as being normally closed. When it opens then the pump will turn on to the speed.
461
+ // +-------+---------+---------+---------+---------+
462
+ // + Speed | Relay 1 | Relay 2 | Relay 3 | OVRD |
463
+ // +-------+---------+---------+---------+---------+
464
+ // | OFF | OFF | OFF | OFF | OFF |
465
+ // +-------+---------+---------+---------+---------+
466
+ // | 1 | OFF | OFF | OFF | ON |
467
+ // +-------+---------+---------+---------+---------+
468
+ // | 2 | ON | OFF | OFF | ON |
469
+ // +-------+---------+---------+---------+---------+
470
+ // | 3 | OFF | ON | OFF | ON |
471
+ // +-------+---------+---------+---------+---------+
472
+ // | 4 | ON | ON | OFF | ON |
473
+ // +-------+---------+---------+---------+---------+
474
+ // | 5 | OFF | OFF | ON | ON |
475
+ // +-------+---------+---------+---------+---------+
476
+ // | 6 | ON | OFF | ON | ON |
477
+ // +-------+---------+---------+---------+---------+
478
+ // | 7 | OFF | ON | ON | ON |
479
+ // +-------+---------+---------+---------+---------+
480
+ // | 8 | ON | ON | ON | ON |
481
+ // +-------+---------+---------+---------+---------+
482
+
483
+ public setTargetSpeed(pState: PumpState) {
484
+ let _newSpeed = 0;
485
+ if (!pState.pumpOnDelay) {
486
+ let pumpCircuits = this.pump.circuits.get();
487
+ for (let i = 0; i < pumpCircuits.length; i++) {
488
+ let circ = state.circuits.getInterfaceById(pumpCircuits[i].circuit);
489
+ let pc = pumpCircuits[i];
490
+ if (circ.isOn) {
491
+ _newSpeed = Math.max(_newSpeed, pc.relay);
492
+ }
493
+ }
494
+ }
495
+ if (isNaN(_newSpeed)) _newSpeed = 0;
496
+ this._targetSpeed = _newSpeed;
497
+ if (this._targetSpeed !== 0) Math.min(Math.max(this.pump.minSpeed, this._targetSpeed), this.pump.maxSpeed);
498
+ if (this._targetSpeed !== _newSpeed) logger.info(`NCP: Setting Pump ${this.pump.name} to ${_newSpeed}.`);
499
+ }
500
+ public async setPumpStateAsync(pstate: PumpState) {
501
+ // Don't poll while we are seting the state.
502
+ this.suspendPolling = true;
503
+ try {
504
+ let relays: PumpRelay[] = this.pump.relays.get();
505
+ let relayState = 0;
506
+ let targetState = 0;
507
+ for (let i = 0; i < relays.length; i++) {
508
+ let pr = relays[i];
509
+ if (typeof pr.id === 'undefined') pr.id = i + 1; // remove when id is added to dP relays upon save.
510
+ // 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
511
+ // configuration so that whenever the pump is off the relay terminals are closed.
512
+ let isOn = this._targetSpeed > 0 ? i === 3 ? true : (this._targetSpeed - 1 & (1 << i)) > 0 : false;
513
+ let bit = isOn ? (1 << i) : 0;
514
+ targetState |= bit;
515
+ if (utils.isNullOrEmpty(pr.connectionId) || utils.isNullOrEmpty(pr.deviceBinding)) {
516
+ // Determine whether the relay should be on.
517
+ relayState |= bit;
518
+ }
519
+ else {
520
+ try {
521
+ let res = await NixieEquipment.putDeviceService(pr.connectionId, `/state/device/${pr.deviceBinding}`, { isOn, latch: isOn ? 5000 : undefined });
522
+ if (res.status.code === 200) {
523
+ relayState |= bit;
524
+ }
525
+ else pstate.status = 16;
526
+ }
527
+ catch (err) {
528
+ logger.error(`NCP: Error setting pump ${this.pump.name} relay ${pr.id} to ${isOn ? 'on' : 'off'}. Error ${err.message}}`);
529
+ pstate.status = 16;
530
+ }
531
+ }
532
+ }
533
+ pstate.command = this._targetSpeed;
534
+ if (targetState === relayState) {
535
+ pstate.status = relayState > 0 ? 1 : 0;
536
+ pstate.driveState = relayState > 0 ? 2 : 0;
537
+ pstate.relay = relayState;
538
+ }
539
+ else {
540
+ pstate.driveState = 0;
541
+ }
542
+ return new InterfaceServerResponse(200, 'Success');
543
+ }
544
+ catch (err) {
545
+ logger.error(`Error running pump sequence for ${this.pump.name}: ${err.message}`);
546
+ return Promise.reject(err);
547
+ }
548
+ finally { this.suspendPolling = false; }
549
+ };
550
+
551
+ }
552
+ export class NixiePumpRS485 extends NixiePump {
553
+ public async setServiceModeAsync() {
554
+ this._targetSpeed = 0;
555
+ await this.setDriveStateAsync(false);
556
+ await this.setPumpToRemoteControlAsync(false);
557
+ }
558
+ public async setPumpStateAsync(pstate: PumpState) {
559
+ // Don't poll while we are seting the state.
560
+ this.suspendPolling = true;
561
+ try {
562
+ let pt = sys.board.valueMaps.pumpTypes.get(this.pump.type);
563
+ if (state.mode === 0) {
564
+ // Since these process are async the closing flag can be set
565
+ // between calls. We need to check it in between each call.
566
+ if (!this.closing) await this.setDriveStateAsync();
567
+ if (!this.closing) {
568
+ if (this._targetSpeed >= pt.minFlow && this._targetSpeed <= pt.maxFlow) await this.setPumpGPMAsync();
569
+ else if (this._targetSpeed >= pt.minSpeed && this._targetSpeed <= pt.maxSpeed) await this.setPumpRPMAsync();
570
+ }
571
+ ;
572
+ if (!this.closing && pt.name !== 'vsf' && pt.name !== 'vs') await this.setPumpFeatureAsync(6);;
573
+ if (!this.closing) await setTimeout(1000);;
574
+ if (!this.closing) await this.requestPumpStatusAsync();;
575
+ if (!this.closing) await this.setPumpToRemoteControlAsync();;
576
+ }
577
+ return new InterfaceServerResponse(200, 'Success');
578
+ }
579
+ catch (err) {
580
+ logger.error(`Error running pump sequence for ${this.pump.name}: ${err.message}`);
581
+ return Promise.reject(err);
582
+ }
583
+ finally { this.suspendPolling = false; }
584
+ };
585
+ protected async setDriveStateAsync(running: boolean = true) {
586
+ try {
587
+ if (conn.isPortEnabled(this.pump.portId || 0)) {
588
+ let out = Outbound.create({
589
+ portId: this.pump.portId || 0,
590
+ protocol: Protocol.Pump,
591
+ dest: this.pump.address,
592
+ action: 6,
593
+ payload: running && this._targetSpeed > 0 ? [10] : [4],
594
+ retries: 1,
595
+ response: true
596
+ });
597
+ try {
598
+ await out.sendAsync();
599
+ }
600
+ catch (err) {
601
+ logger.error(`Error sending setDriveState for ${this.pump.name}: ${err.message}`);
602
+ }
603
+ }
604
+ else {
605
+ let pstate = state.pumps.getItemById(this.pump.id);
606
+ pstate.command = pstate.rpm > 0 || pstate.flow > 0 ? 10 : 0;
607
+ }
608
+ } catch (err) { logger.error(`Error setting driveState for ${this.pump.name}: ${err.message}`); }
609
+ };
610
+ protected async requestPumpStatusAsync() {
611
+ if (conn.isPortEnabled(this.pump.portId || 0)) {
612
+ let out = Outbound.create({
613
+ portId: this.pump.portId || 0,
614
+ protocol: Protocol.Pump,
615
+ dest: this.pump.address,
616
+ action: 7,
617
+ payload: [],
618
+ retries: 2,
619
+ response: true,
620
+ });
621
+ try {
622
+ await out.sendAsync();
623
+ }
624
+ catch (err) {
625
+ logger.error(`Error sending requestPumpStatus for ${this.pump.name}: ${err.message}`);
626
+ }
627
+ }
628
+ };
629
+ protected async setPumpToRemoteControlAsync(running: boolean = true) {
630
+ try {
631
+ if (conn.isPortEnabled(this.pump.portId || 0)) {
632
+ let out = Outbound.create({
633
+ portId: this.pump.portId || 0,
634
+ protocol: Protocol.Pump,
635
+ dest: this.pump.address,
636
+ action: 4,
637
+ payload: running ? [255] : [0], // when stopAsync is called, pass false to return control to pump panel
638
+ retries: 1,
639
+ response: true
640
+ });
641
+ try {
642
+ await out.sendAsync();
643
+ }
644
+ catch (err) {
645
+ logger.error(`Error sending setPumpToRemoteControl for ${this.pump.name}: ${err.message}`);
646
+ }
647
+ }
648
+ } catch (err) { logger.error(`Error setting pump to Remote Control for ${this.pump.name}: ${err.message}`); }
649
+ }
650
+ protected async setPumpFeatureAsync(feature?: number) {
651
+ // empty payload (possibly 0?, too) is no feature
652
+ // 6: Feature 1
653
+ try {
654
+ if (conn.isPortEnabled(this.pump.portId || 0)) {
655
+ let out = Outbound.create({
656
+ portId: this.pump.portId || 0,
657
+ protocol: Protocol.Pump,
658
+ dest: this.pump.address,
659
+ action: 5,
660
+ payload: typeof feature === 'undefined' ? [] : [feature],
661
+ retries: 2,
662
+ response: true
663
+ });
664
+ try {
665
+ await out.sendAsync();
666
+ }
667
+ catch (err) {
668
+ logger.error(`Error sending setPumpFeature for ${this.pump.name}: ${err.message}`);
669
+ }
670
+ }
671
+ } catch (err) { logger.error(`Error setting pump feature for ${this.pump.name}: ${err.message}`); }
672
+ };
673
+ protected async setPumpRPMAsync() {
674
+ if (conn.isPortEnabled(this.pump.portId || 0)) {
675
+ let out = Outbound.create({
676
+ portId: this.pump.portId || 0,
677
+ protocol: Protocol.Pump,
678
+ dest: this.pump.address,
679
+ action: 1,
680
+ payload: [2, 196, Math.floor(this._targetSpeed / 256), this._targetSpeed % 256],
681
+ retries: 1,
682
+ // timeout: 250,
683
+ response: true
684
+ });
685
+ try {
686
+ await out.sendAsync();
687
+ }
688
+ catch (err) {
689
+ logger.error(`Error sending setPumpRPMAsync for ${this.pump.name}: ${err.message}`);
690
+ }
691
+ }
692
+ };
693
+ protected async setPumpGPMAsync() {
694
+ // packet for vf; vsf will override
695
+ if (conn.isPortEnabled(this.pump.portId || 0)) {
696
+ let out = Outbound.create({
697
+ portId: this.pump.portId || 0,
698
+ protocol: Protocol.Pump,
699
+ dest: this.pump.address,
700
+ action: 1,
701
+ payload: [2, 228, 0, this._targetSpeed],
702
+ retries: 1,
703
+ response: true
704
+ });
705
+ try {
706
+ await out.sendAsync();
707
+ }
708
+ catch (err) {
709
+ logger.error(`Error sending setPumpGPMAsync for ${this.pump.name}: ${err.message}`);
710
+ }
711
+ }
712
+ };
713
+ public async closeAsync() {
714
+ try {
715
+ this.suspendPolling = true;
716
+ logger.info(`Nixie Pump closing ${this.pump.name}.`)
717
+ if (typeof this._pollTimer !== 'undefined' || this._pollTimer) clearTimeout(this._pollTimer);
718
+ this._pollTimer = null;
719
+ this.closing = true;
720
+ let pt = sys.board.valueMaps.pumpTypes.get(this.pump.type);
721
+ let pstate = state.pumps.getItemById(this.pump.id);
722
+ this._targetSpeed = 0;
723
+ await this.setDriveStateAsync(false);
724
+ if (!this.closing && pt.name !== 'vsf' && pt.name !== 'vs') await this.setPumpFeatureAsync();
725
+ //await this.setPumpFeature();
726
+ //await this.setDriveStateAsync(false);
727
+ await this.setPumpToRemoteControlAsync(false);
728
+ // Make sure the polling timer is dead after we have closed this all off. That way we do not
729
+ // have another process that revives it from the dead.
730
+ if (typeof this._pollTimer !== 'undefined' || this._pollTimer) clearTimeout(this._pollTimer);
731
+ this._pollTimer = null;
732
+ pstate.emitEquipmentChange();
733
+ }
734
+ catch (err) { logger.error(`Nixie Pump closeAsync: ${err.message}`); return Promise.reject(err); }
735
+ finally { this.suspendPolling = false; }
736
+ }
737
+ }
738
+ export class NixiePumpVS extends NixiePumpRS485 {
739
+ public setTargetSpeed(pState: PumpState) {
740
+ let _newSpeed = 0;
741
+ if (!pState.pumpOnDelay) {
742
+ let pumpCircuits = this.pump.circuits.get();
743
+
744
+ for (let i = 0; i < pumpCircuits.length; i++) {
745
+ let circ = state.circuits.getInterfaceById(pumpCircuits[i].circuit);
746
+ let pc = pumpCircuits[i];
747
+ if (circ.isOn) _newSpeed = Math.max(_newSpeed, pc.speed);
748
+ }
749
+ }
750
+ if (isNaN(_newSpeed)) _newSpeed = 0;
751
+ this._targetSpeed = _newSpeed;
752
+ if (this._targetSpeed !== 0) Math.min(Math.max(this.pump.minSpeed, this._targetSpeed), this.pump.maxSpeed);
753
+ if (this._targetSpeed !== _newSpeed) logger.info(`NCP: Setting Pump ${this.pump.name} to ${_newSpeed} RPM.`);
754
+ }
755
+ }
756
+ export class NixiePumpVF extends NixiePumpRS485 {
757
+ public setTargetSpeed(pState: PumpState) {
758
+ let _newSpeed = 0;
759
+ if (!pState.pumpOnDelay) {
760
+ let pumpCircuits = this.pump.circuits.get();
761
+ for (let i = 0; i < pumpCircuits.length; i++) {
762
+ let circ = state.circuits.getInterfaceById(pumpCircuits[i].circuit);
763
+ let pc = pumpCircuits[i];
764
+ if (circ.isOn) _newSpeed = Math.max(_newSpeed, pc.flow);
765
+ }
766
+ }
767
+ if (isNaN(_newSpeed)) _newSpeed = 0;
768
+ this._targetSpeed = _newSpeed;
769
+ if (this._targetSpeed !== 0) Math.min(Math.max(this.pump.minFlow, this._targetSpeed), this.pump.maxFlow);
770
+ if (this._targetSpeed !== _newSpeed) logger.info(`NCP: Setting Pump ${this.pump.name} to ${_newSpeed} GPM.`);
771
+ }
772
+ public async setPumpStateAsync(pstate: PumpState) {
773
+ // Don't poll while we are seting the state.
774
+ this.suspendPolling = true;
775
+ try {
776
+ let pt = sys.board.valueMaps.pumpTypes.get(this.pump.type);
777
+ if (state.mode === 0) {
778
+ // Since these process are async the closing flag can be set
779
+ // between calls. We need to check it in between each call. // 4, 6, 5, 7
780
+ // When we are 0 then it sends 4[255], 6[4], 5[6]
781
+ // When we are not 0 then it sends 4[255], 6[10], 5[6], 1[flow]
782
+ if (!this.closing) await this.setPumpToRemoteControlAsync(); // Action 4
783
+ if (!this.closing && this._targetSpeed > 0) await this.setPumpGPMAsync(); // Action 1
784
+ if (!this.closing && this._targetSpeed > 0) await this.setPumpFeatureAsync(6); // Action 5
785
+ // RKS: 07-21-24 - This used to send an empty payload when the pump should be off. For VF pumps it
786
+ // appears that not setting the feature or target flow will set the pump off when it gets to
787
+ // the drive state.
788
+ //if (!this.closing) await this.setPumpFeatureAsync(this._targetSpeed > 0 ? 6 : undefined); // Action 5
789
+ if (!this.closing) await this.setDriveStateAsync(); // Action 6
790
+ if (!this.closing) await setTimeout(200);
791
+ if (!this.closing) await this.requestPumpStatusAsync(); // Action 7
792
+ }
793
+ return new InterfaceServerResponse(200, 'Success');
794
+ }
795
+ catch (err) {
796
+ logger.error(`Error running pump sequence for ${this.pump.name}: ${err.message}`);
797
+ return Promise.reject(err);
798
+ }
799
+ finally { this.suspendPolling = false; }
800
+ };
801
+ }
802
+ export class NixiePumpVSF extends NixiePumpRS485 {
803
+ public setTargetSpeed(pState: PumpState) {
804
+ let _newSpeed = 0;
805
+ let maxRPM = 0;
806
+ let maxGPM = 0;
807
+ let useFlow = false;
808
+ if (!pState.pumpOnDelay) {
809
+ let pumpCircuits = this.pump.circuits.get();
810
+ let pt = sys.board.valueMaps.pumpTypes.get(this.pump.type);
811
+ // 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
812
+ // 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
813
+ // the GPM to RPM but if there is none then it will use GPM.
814
+ let toRPM = (flowRate: number, minSpeed: number = 450, maxSpeed: number = 3450) => {
815
+ // eff = 114.4365
816
+ // gpm = 80
817
+ // speed = 2412
818
+ let eff = .03317 * maxSpeed;
819
+ let rpm = Math.min(Math.round((flowRate * maxSpeed) / eff), maxSpeed);
820
+ return rpm > 0 ? Math.max(rpm, minSpeed) : 0;
821
+ };
822
+ let toGPM = (speed: number, maxSpeed: number = 3450, minFlow: number = 15, maxFlow: number = 140) => {
823
+ // eff = 114.4365
824
+ // speed = 1100
825
+ // gpm = (114.4365 * 1100)/3450 = 36
826
+ let eff = .03317 * maxSpeed;
827
+ let gpm = Math.min(Math.round((eff * speed) / maxSpeed), maxFlow);
828
+ return gpm > 0 ? Math.max(gpm, minFlow) : 0;
829
+ }
830
+ for (let i = 0; i < pumpCircuits.length; i++) {
831
+ let circ = state.circuits.getInterfaceById(pumpCircuits[i].circuit);
832
+ let pc = pumpCircuits[i];
833
+ if (circ.isOn) {
834
+ if (pc.units > 0) {
835
+ let rpm = toRPM(pc.flow, pt.minSpeed, pt.MaxSpeed);
836
+ if (rpm > maxRPM) useFlow = true;
837
+ maxGPM = Math.max(maxGPM, pc.flow);
838
+ rpm = Math.max(maxRPM, rpm);
839
+ }
840
+ else {
841
+ let gpm = toGPM(pc.speed, pt.maxSpeed, pt.minFlow, pt.maxFlow);
842
+ if (gpm > maxGPM) useFlow = false;
843
+ maxRPM = Math.max(maxRPM, pc.speed);
844
+ maxGPM = Math.max(maxGPM, gpm);
845
+ }
846
+ }
847
+ }
848
+ _newSpeed = useFlow ? maxGPM : maxRPM;
849
+ }
850
+ if (isNaN(_newSpeed)) _newSpeed = 0;
851
+ // Send the flow message if it is flow and the rpm message if it is rpm.
852
+ if (this._targetSpeed !== _newSpeed) logger.info(`NCP: Setting Pump ${this.pump.name} to ${_newSpeed} ${useFlow ? 'GPM' : 'RPM'}.`);
853
+ this._targetSpeed = _newSpeed;
854
+ }
855
+ protected async setPumpRPMAsync() {
856
+ // vsf action is 10 for rpm
857
+ if (conn.isPortEnabled(this.pump.portId || 0)) {
858
+ let out = Outbound.create({
859
+ portId: this.pump.portId || 0,
860
+ protocol: Protocol.Pump,
861
+ dest: this.pump.address,
862
+ action: 10,
863
+ payload: [2, 196, Math.floor(this._targetSpeed / 256), this._targetSpeed % 256],
864
+ retries: 1,
865
+ // timeout: 250,
866
+ response: true
867
+ });
868
+ try {
869
+ await out.sendAsync();
870
+ }
871
+ catch (err) {
872
+ logger.error(`Error sending setPumpRPMAsync for ${this.pump.name}: ${err.message}`);
873
+ }
874
+ }
875
+ };
876
+ protected async setPumpGPMAsync() {
877
+ // vsf payload; different from vf payload
878
+ if (conn.isPortEnabled(this.pump.portId || 0)) {
879
+ let out = Outbound.create({
880
+ portId: this.pump.portId || 0,
881
+ protocol: Protocol.Pump,
882
+ dest: this.pump.address,
883
+ action: 9,
884
+ payload: [2, 196, 0, this._targetSpeed],
885
+ retries: 1,
886
+ response: true
887
+ });
888
+ try {
889
+ await out.sendAsync();
890
+ }
891
+ catch (err) {
892
+ logger.error(`Error sending setPumpGPMAsync for ${this.pump.name}: ${err.message}`);
893
+ }
894
+ }
895
+ };
896
+ };
897
+ export class NixiePumpHWVS extends NixiePumpRS485 {
898
+ public setTargetSpeed(pState: PumpState) {
899
+ let _newSpeed = 0;
900
+ if (!pState.pumpOnDelay) {
901
+ let pumpCircuits = this.pump.circuits.get();
902
+
903
+ for (let i = 0; i < pumpCircuits.length; i++) {
904
+ let circ = state.circuits.getInterfaceById(pumpCircuits[i].circuit);
905
+ let pc = pumpCircuits[i];
906
+ if (circ.isOn) _newSpeed = Math.max(_newSpeed, pc.speed);
907
+ }
908
+ }
909
+ if (isNaN(_newSpeed)) _newSpeed = 0;
910
+ this._targetSpeed = _newSpeed;
911
+ if (this._targetSpeed !== 0) Math.min(Math.max(this.pump.minSpeed, this._targetSpeed), this.pump.maxSpeed);
912
+ if (this._targetSpeed !== _newSpeed) logger.info(`NCP: Setting Pump ${this.pump.name} to ${_newSpeed} RPM.`);
913
+ }
914
+ public async setServiceModeAsync() {
915
+ this._targetSpeed = 0;
916
+ await this.setPumpRPMAsync();
917
+ }
918
+ public async setDriveStateAsync(running: boolean = false) { return Promise.resolve(); }
919
+ public async setPumpStateAsync(pstate: PumpState) {
920
+ // Don't poll while we are seting the state.
921
+ this.suspendPolling = true;
922
+ try {
923
+ // Since these process are async the closing flag can be set
924
+ // between calls. We need to check it in between each call.
925
+ if (!this.closing) { await this.setPumpRPMAsync(); }
926
+ return new InterfaceServerResponse(200, 'Success');
927
+ }
928
+ catch (err) {
929
+ logger.error(`Error running pump sequence for ${this.pump.name}: ${err.message}`);
930
+ return Promise.reject(err);
931
+ }
932
+ finally { this.suspendPolling = false; }
933
+ };
934
+ protected async requestPumpStatusAsync() { return Promise.resolve(); };
935
+ protected setPumpFeatureAsync(feature?: number) { return Promise.resolve(); }
936
+ protected async setPumpToRemoteControlAsync(running: boolean = true) {
937
+ try {
938
+ // We do nothing on this pump to set it to remote control. That is unless we are turning it off.
939
+ if (conn.isPortEnabled(this.pump.portId || 0)) {
940
+ if (!running) {
941
+ let out = Outbound.create({
942
+ portId: this.pump.portId || 0,
943
+ protocol: Protocol.Hayward,
944
+ source: 12, // Use the broadcast address
945
+ dest: this.pump.address,
946
+ action: 1,
947
+ payload: [0], // when stopAsync is called, pass false to return control to pump panel
948
+ // payload: spump.virtualControllerStatus === sys.board.valueMaps.virtualControllerStatus.getValue('running') ? [255] : [0],
949
+ retries: 1,
950
+ response: Response.create({ protocol: Protocol.Hayward, action: 12, source: this.pump.address - 96 })
951
+ });
952
+ try {
953
+ await out.sendAsync();
954
+ }
955
+ catch (err) {
956
+ logger.error(`Error sending setPumpToRemoteControl for ${this.pump.name}: ${err.message}`);
957
+
958
+ }
959
+ }
960
+ }
961
+ } catch(err) { `Error sending setPumpToRemoteControl message for ${this.pump.name}: ${err.message}` };
962
+ }
963
+ protected async setPumpRPMAsync() {
964
+ // Address 1
965
+ //[][16, 2, 12, 1, 0][41][0, 72, 16, 3] out
966
+ //[][16, 2, 0, 12, 0][0, 41, 0, 135][0, 206, 16, 3] In
967
+ // Address 2
968
+ //[][16, 2, 12, 1, 1][100][0, 132, 16, 3] out
969
+ //[][16, 2, 0, 12, 1][0, 96, 21, 64][0, 212, 16, 3] in
970
+ // Note that action 12 is in a different position for the outbound than the inbound. The source and destination are kind
971
+ // of a misnomer in that it identifies the equipment address in byte(4) of the header and flips the command address around.
972
+ // So in essence for equipment item 0-16 (pump addresses) the outbound is really a broadcast on 12 (broadcast) from 1 and the inbound is
973
+ // broadcast from the equipment item to 0 (anybody).
974
+ if (conn.isPortEnabled(this.pump.portId || 0)) {
975
+ let pt = sys.board.valueMaps.pumpTypes.get(this.pump.type);
976
+ let out = Outbound.create({
977
+ portId: this.pump.portId || 0,
978
+ protocol: Protocol.Hayward,
979
+ source: 1, // Use the broadcast address
980
+ dest: this.pump.address - 96,
981
+ action: 12,
982
+ payload: [Math.min(Math.round((this._targetSpeed / pt.maxSpeed) * 100), 100)], // when stopAsync is called, pass false to return control to pump panel
983
+ retries: 1,
984
+ response: Response.create({ protocol: Protocol.Hayward, action: 12, source: this.pump.address - 96 })
985
+ });
986
+ try {
987
+ await out.sendAsync();
988
+ }
989
+ catch (err) {
990
+ logger.error(`Error sending setPumpRPM for ${this.pump.name}: ${err.message}`);
991
+ let pstate = state.pumps.getItemById(this.pump.id);
992
+ pstate.command = 0;
993
+ pstate.rpm = 0;
994
+ pstate.watts = 0;
995
+ }
996
+ }
997
+ else {
998
+ let pstate = state.pumps.getItemById(this.pump.id);
999
+ pstate.command = 0;
1000
+ pstate.rpm = 0;
1001
+ pstate.watts = 0;
1002
+ }
1003
+
1004
+ };
1005
+ }
1006
+
1007
+ export class NixiePumpRegalModbus extends NixiePump {
1008
+
1009
+ constructor(ncp: INixieControlPanel, pump: Pump) {
1010
+ super(ncp, pump);
1011
+ // this.pump = pump;
1012
+ // this._targetSpeed = 0;
1013
+ }
1014
+
1015
+ public setTargetSpeed(pumpState: PumpState) {
1016
+ let newSpeed = 0;
1017
+ if (!pumpState.pumpOnDelay) {
1018
+ let circuitConfigs = this.pump.circuits.get();
1019
+ for (let i = 0; i < circuitConfigs.length; i++) {
1020
+ let circuitConfig = circuitConfigs[i];
1021
+ let circ = state.circuits.getInterfaceById(circuitConfig.circuit);
1022
+ if (circ.isOn) newSpeed = Math.max(newSpeed, circuitConfig.speed);
1023
+ }
1024
+ }
1025
+ if (isNaN(newSpeed)) newSpeed = 0;
1026
+ this._targetSpeed = newSpeed;
1027
+ if (this._targetSpeed !== 0) Math.min(Math.max(this.pump.minSpeed, this._targetSpeed), this.pump.maxSpeed);
1028
+ if (this._targetSpeed !== newSpeed) logger.info(`NCP: Setting Pump ${this.pump.name} to ${newSpeed} RPM.`);
1029
+ }
1030
+
1031
+ public async setServiceModeAsync() {
1032
+ this._targetSpeed = 0;
1033
+ await this.setDriveStateAsync(false);
1034
+ // await this.setPumpToRemoteControlAsync(false);
1035
+ }
1036
+
1037
+ public async setPumpStateAsync(pstate: PumpState) {
1038
+ // Don't poll while we are seting the state.
1039
+ this.suspendPolling = true;
1040
+ try {
1041
+ let pt = sys.board.valueMaps.pumpTypes.get(this.pump.type);
1042
+ if (state.mode === 0) {
1043
+ // Since these process are async the closing flag can be set
1044
+ // between calls. We need to check it in between each call.
1045
+ if (!this.closing) {
1046
+ if (this._targetSpeed >= pt.minSpeed && this._targetSpeed <= pt.maxSpeed) await this.setPumpRPMAsync();
1047
+ }
1048
+ if (!this.closing) await this.setDriveStateAsync();
1049
+ ;
1050
+ // if (!this.closing && pt.name !== 'vsf' && pt.name !== 'vs') await this.setPumpFeatureAsync(6);;
1051
+ if (!this.closing) await setTimeout(1000);;
1052
+ if (!this.closing) await this.requestPumpStatusAsync();;
1053
+ // if (!this.closing) await this.setPumpToRemoteControlAsync();;
1054
+ }
1055
+ return new InterfaceServerResponse(200, 'Success');
1056
+ }
1057
+ catch (err) {
1058
+ logger.error(`Error running pump sequence for ${this.pump.name}: ${err.message}`);
1059
+ return Promise.reject(err);
1060
+ }
1061
+ finally { this.suspendPolling = false; }
1062
+ };
1063
+ protected async setDriveStateAsync(isRunning: boolean = true) {
1064
+ let functionCode = this._targetSpeed > 0 ? 0x41 : 0x42;
1065
+ logger.debug(`NixiePumpRegalModbus: setDriveStateAsync ${this.pump.name} ${functionCode == 0x41 ? 'RUN' : functionCode == 0x42 ? 'STOP' : 'UNKNOWN'}`);
1066
+ try {
1067
+ if (conn.isPortEnabled(this.pump.portId || 0)) {
1068
+ let functionCode = this._targetSpeed > 0 ? 0x41 : 0x42;
1069
+ let out = Outbound.create({
1070
+ portId: this.pump.portId || 0,
1071
+ protocol: Protocol.RegalModbus,
1072
+ dest: this.pump.address,
1073
+ action: functionCode,
1074
+ payload: [],
1075
+ retries: 1,
1076
+ response: true
1077
+ });
1078
+ try {
1079
+ await out.sendAsync();
1080
+ }
1081
+ catch (err) {
1082
+ logger.error(`Error sending setDriveState for ${this.pump.name}: ${err.message}`);
1083
+ }
1084
+ }
1085
+ else {
1086
+ let pumpState = state.pumps.getItemById(this.pump.id);
1087
+ pumpState.command = pumpState.rpm > 0 || pumpState.flow > 0 ? 10 : 0; // dashPanel needs this to be set to 10 for running.
1088
+ }
1089
+ } catch (err) { logger.error(`Error setting driveState for ${this.pump.name}: ${err.message}`); }
1090
+ };
1091
+
1092
+ protected async requestPumpDriveStateAsync() {
1093
+ if (conn.isPortEnabled(this.pump.portId || 0)) {
1094
+ let out = Outbound.create({
1095
+ portId: this.pump.portId || 0,
1096
+ protocol: Protocol.RegalModbus,
1097
+ dest: this.pump.address,
1098
+ action: 0x43,
1099
+ payload: [],
1100
+ retries: 2,
1101
+ response: true,
1102
+ });
1103
+ try {
1104
+ await out.sendAsync();
1105
+ }
1106
+ catch (err) {
1107
+ logger.error(`Error sending requestPumpDriveState for ${this.pump.name}: ${err.message}`);
1108
+ }
1109
+ }
1110
+ }
1111
+
1112
+ protected async requestSensorAsync(page: number, sensorAddr: number, retries: number = 2) {
1113
+ if (conn.isPortEnabled(this.pump.portId || 0)) {
1114
+ let out = Outbound.create({
1115
+ portId: this.pump.portId || 0,
1116
+ protocol: Protocol.RegalModbus,
1117
+ dest: this.pump.address,
1118
+ action: 0x45,
1119
+ payload: [page, sensorAddr],
1120
+ retries: retries,
1121
+ response: true,
1122
+ });
1123
+ try {
1124
+ await out.sendAsync();
1125
+ }
1126
+ catch (err) {
1127
+ logger.error(`Error sending requestSensor for ${this.pump.name} page ${page} sensor ${sensorAddr}: ${err.message}`);
1128
+ }
1129
+ }
1130
+ }
1131
+
1132
+ protected async requestPumpStatusAsync() {
1133
+
1134
+ await this.requestPumpDriveStateAsync();
1135
+ await this.requestSensorAsync(0, 0x00); // motor speed
1136
+ // await this.requestSensorAsync(0, 0x01); // motor current
1137
+ // await this.requestSensorAsync(0, 0x04); // torque
1138
+ // await this.requestSensorAsync(0, 0x05); // inverter input power
1139
+ // await this.requestSensorAsync(0, 0x06); // DC bus voltage
1140
+ // await this.requestSensorAsync(0, 0x07); // ambient temperature
1141
+ await this.requestSensorAsync(0, 0x0A); // output power
1142
+ // await this.requestSensorAsync(0, 0x0D); // motor line voltage
1143
+ // await this.requestSensorAsync(0, 0x0E); // ramp status
1144
+ // await this.requestSensorAsync(0, 0x0F); // no of total fault
1145
+ // await this.requestSensorAsync(0, 0x10); // prime status
1146
+ // await this.requestSensorAsync(0, 0x11); // motor input power
1147
+ // await this.requestSensorAsync(0, 0x12); // IGBT temperature
1148
+ // await this.requestSensorAsync(0, 0x13); // PCB temperature
1149
+ };
1150
+
1151
+ protected async setPumpRPMAsync() {
1152
+ logger.debug(`NixiePumpRegalModbus: setPumpRPMAsync ${this.pump.name} ${this._targetSpeed}`);
1153
+ if (conn.isPortEnabled(this.pump.portId || 0)) {
1154
+ // get demand bytes from rpm
1155
+ const mode = 0x00; // 0x00 for speed control mode
1156
+ const demandLo = Math.round(this._targetSpeed * 4) & 0xFF;
1157
+ const demandHi = (Math.round(this._targetSpeed * 4) >> 8) & 0xFF;
1158
+ let out = Outbound.create({
1159
+ portId: this.pump.portId || 0,
1160
+ protocol: Protocol.RegalModbus,
1161
+ dest: this.pump.address,
1162
+ action: 0x44,
1163
+ payload: [mode, demandLo, demandHi],
1164
+ retries: 1,
1165
+ // timeout: 250,
1166
+ response: true
1167
+ });
1168
+ try {
1169
+ await out.sendAsync();
1170
+ }
1171
+ catch (err) {
1172
+ logger.error(`Error sending setPumpRPMAsync for ${this.pump.name}: ${err.message}`);
1173
+ }
1174
+ }
1175
+ };
1176
+ public async closeAsync() {
1177
+ try {
1178
+ this.suspendPolling = true;
1179
+ logger.info(`Nixie Pump closing ${this.pump.name}.`)
1180
+ if (typeof this._pollTimer !== 'undefined' || this._pollTimer) clearTimeout(this._pollTimer);
1181
+ this._pollTimer = null;
1182
+ this.closing = true;
1183
+ let pumpType = sys.board.valueMaps.pumpTypes.get(this.pump.type);
1184
+ let pumpState = state.pumps.getItemById(this.pump.id);
1185
+ this._targetSpeed = 0;
1186
+ await this.setDriveStateAsync(false);
1187
+ // if (!this.closing && pt.name !== 'vsf' && pt.name !== 'vs') await this.setPumpFeatureAsync();
1188
+ //await this.setPumpFeature();
1189
+ //await this.setDriveStateAsync(false);
1190
+ // await this.setPumpToRemoteControlAsync(false);
1191
+ // Make sure the polling timer is dead after we have closed this all off. That way we do not
1192
+ // have another process that revives it from the dead.
1193
+ if (typeof this._pollTimer !== 'undefined' || this._pollTimer) clearTimeout(this._pollTimer);
1194
+ this._pollTimer = null;
1195
+ pumpState.emitEquipmentChange();
1196
+ }
1197
+ catch (err) { logger.error(`Nixie Pump closeAsync: ${err.message}`); return Promise.reject(err); }
1198
+ finally { this.suspendPolling = false; }
1199
+ }
1200
+ }
1201
+
1202
+ export class NixiePumpNeptuneModbus extends NixiePump {
1203
+ private static readonly REG_MOTOR_ON_OFF = 0; // 40001 address offset
1204
+ private static readonly REG_MANUAL_SPEED = 1; // 40002 address offset
1205
+
1206
+ constructor(ncp: INixieControlPanel, pump: Pump) {
1207
+ super(ncp, pump);
1208
+ }
1209
+
1210
+ public setTargetSpeed(pumpState: PumpState) {
1211
+ let newSpeed = 0;
1212
+ if (!pumpState.pumpOnDelay) {
1213
+ const circuitConfigs = this.pump.circuits.get();
1214
+ for (let i = 0; i < circuitConfigs.length; i++) {
1215
+ const circuitConfig = circuitConfigs[i];
1216
+ const circ = state.circuits.getInterfaceById(circuitConfig.circuit);
1217
+ if (circ.isOn) newSpeed = Math.max(newSpeed, circuitConfig.speed);
1218
+ }
1219
+ }
1220
+ if (isNaN(newSpeed)) newSpeed = 0;
1221
+ this._targetSpeed = newSpeed;
1222
+ if (this._targetSpeed !== 0) Math.min(Math.max(this.pump.minSpeed, this._targetSpeed), this.pump.maxSpeed);
1223
+ if (this._targetSpeed !== newSpeed) logger.info(`NCP: Setting Pump ${this.pump.name} to ${newSpeed} RPM.`);
1224
+ }
1225
+
1226
+ public async setServiceModeAsync() {
1227
+ this._targetSpeed = 0;
1228
+ await this.setDriveStateAsync(false);
1229
+ }
1230
+
1231
+ public async setPumpStateAsync(pstate: PumpState) {
1232
+ this.suspendPolling = true;
1233
+ try {
1234
+ const pt = sys.board.valueMaps.pumpTypes.get(this.pump.type);
1235
+ if (state.mode === 0) {
1236
+ if (!this.closing && this._targetSpeed >= pt.minSpeed && this._targetSpeed <= pt.maxSpeed) {
1237
+ await this.setPumpRPMAsync();
1238
+ }
1239
+ if (!this.closing) await this.setDriveStateAsync();
1240
+ if (!this.closing) await setTimeout(500);
1241
+ if (!this.closing) await this.requestPumpStatusAsync();
1242
+ }
1243
+ return new InterfaceServerResponse(200, 'Success');
1244
+ }
1245
+ catch (err) {
1246
+ logger.error(`Error running pump sequence for ${this.pump.name}: ${err.message}`);
1247
+ return Promise.reject(err);
1248
+ }
1249
+ finally { this.suspendPolling = false; }
1250
+ };
1251
+
1252
+ private toWord(value: number): [number, number] {
1253
+ return [(value >> 8) & 0xFF, value & 0xFF];
1254
+ }
1255
+
1256
+ private async writeSingleRegisterAsync(registerAddr: number, value: number, retries: number = 1) {
1257
+ if (!conn.isPortEnabled(this.pump.portId || 0)) return;
1258
+ const [regHi, regLo] = this.toWord(registerAddr);
1259
+ const [valHi, valLo] = this.toWord(value & 0xFFFF);
1260
+ const out = Outbound.create({
1261
+ portId: this.pump.portId || 0,
1262
+ protocol: Protocol.NeptuneModbus,
1263
+ dest: this.pump.address,
1264
+ action: 0x06,
1265
+ payload: [regHi, regLo, valHi, valLo],
1266
+ retries,
1267
+ response: true,
1268
+ });
1269
+ try {
1270
+ await out.sendAsync();
1271
+ }
1272
+ catch (err) {
1273
+ logger.error(`Error writing Neptune register ${registerAddr} for ${this.pump.name}: ${err.message}`);
1274
+ }
1275
+ }
1276
+
1277
+ private async readInputRegistersAsync(startAddr: number, quantity: number, retries: number = 2) {
1278
+ if (!conn.isPortEnabled(this.pump.portId || 0)) return;
1279
+ const [startHi, startLo] = this.toWord(startAddr);
1280
+ const [qtyHi, qtyLo] = this.toWord(quantity);
1281
+ const out = Outbound.create({
1282
+ portId: this.pump.portId || 0,
1283
+ protocol: Protocol.NeptuneModbus,
1284
+ dest: this.pump.address,
1285
+ action: 0x04,
1286
+ payload: [startHi, startLo, qtyHi, qtyLo],
1287
+ retries,
1288
+ response: true,
1289
+ });
1290
+ NeptuneModbusStateMessage.enqueueReadRequest(this.pump.address, startAddr, quantity);
1291
+ try {
1292
+ await out.sendAsync();
1293
+ }
1294
+ catch (err) {
1295
+ NeptuneModbusStateMessage.clearReadRequests(this.pump.address);
1296
+ logger.error(`Error reading Neptune registers ${startAddr}-${startAddr + quantity - 1} for ${this.pump.name}: ${err.message}`);
1297
+ }
1298
+ }
1299
+
1300
+ protected async setDriveStateAsync(isRunning: boolean = true) {
1301
+ const shouldRun = isRunning && this._targetSpeed > 0;
1302
+ logger.debug(`NixiePumpNeptuneModbus: setDriveStateAsync ${this.pump.name} ${shouldRun ? 'RUN' : 'STOP'}`);
1303
+ await this.writeSingleRegisterAsync(NixiePumpNeptuneModbus.REG_MOTOR_ON_OFF, shouldRun ? 1 : 0);
1304
+ }
1305
+
1306
+ protected async requestPumpStatusAsync() {
1307
+ // Block 30001-30007 (speed/power/fault summary).
1308
+ await this.readInputRegistersAsync(0, 7);
1309
+ // Block 30031-30033 (interface fault state/code).
1310
+ await this.readInputRegistersAsync(30, 3);
1311
+ // Block 30114-30128 (stopped state, line volts, temps, target speed, etc.).
1312
+ await this.readInputRegistersAsync(113, 15);
1313
+ }
1314
+
1315
+ protected async setPumpRPMAsync() {
1316
+ logger.debug(`NixiePumpNeptuneModbus: setPumpRPMAsync ${this.pump.name} ${this._targetSpeed}`);
1317
+ await this.writeSingleRegisterAsync(NixiePumpNeptuneModbus.REG_MANUAL_SPEED, Math.round(this._targetSpeed));
1318
+ }
1319
+
1320
+ public async closeAsync() {
1321
+ try {
1322
+ this.suspendPolling = true;
1323
+ logger.info(`Nixie Pump closing ${this.pump.name}.`)
1324
+ if (typeof this._pollTimer !== 'undefined' || this._pollTimer) clearTimeout(this._pollTimer);
1325
+ this._pollTimer = null;
1326
+ this.closing = true;
1327
+ const pumpState = state.pumps.getItemById(this.pump.id);
1328
+ this._targetSpeed = 0;
1329
+ await this.setDriveStateAsync(false);
1330
+ if (typeof this._pollTimer !== 'undefined' || this._pollTimer) clearTimeout(this._pollTimer);
1331
+ this._pollTimer = null;
1332
+ pumpState.emitEquipmentChange();
1333
+ }
1334
+ catch (err) { logger.error(`Nixie Pump closeAsync: ${err.message}`); return Promise.reject(err); }
1335
+ finally { this.suspendPolling = false; }
1336
+ }
1194
1337
  }