nodejs-poolcontroller 8.1.1 → 8.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,411 @@
1
+ /* nodejs-poolController. An application to control pool equipment.
2
+ Copyright (C) 2016, 2017, 2018, 2019, 2020, 2021, 2022.
3
+ Russell Goldin, tagyoureit. russ.goldin@gmail.com
4
+
5
+ This program is free software: you can redistribute it and/or modify
6
+ it under the terms of the GNU Affero General Public License as
7
+ published by the Free Software Foundation, either version 3 of the
8
+ License, or (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU Affero General Public License for more details.
14
+
15
+ You should have received a copy of the GNU Affero General Public License
16
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+ */
18
+ import { Inbound, Outbound, Protocol } from "../Messages";
19
+ import { state } from "../../../State";
20
+ import { sys, ControllerType } from "../../../Equipment";
21
+ import { conn } from "../../Comms";
22
+ import { logger } from "../../../../logger/Logger";
23
+
24
+ // Create a fault object to hold the fault codes and descriptions
25
+ let faultCodes = {
26
+ 0x21: "Software overcurrent",
27
+ 0x22: "DC overvoltage",
28
+ 0x23: "DC undervoltage",
29
+ 0x26: "Hardware overcurrent",
30
+ 0x2A: "Startup failure",
31
+ 0x2D: "Processor - Fatal",
32
+ 0x2E: "IGBT over temperature",
33
+ 0x2F: "Loss of phase",
34
+ 0x30: "Low power",
35
+ 0x31: "Processor - Registers",
36
+ 0x32: "Processor - Program counter",
37
+ 0x33: "Processor - Interrupt/Execution",
38
+ 0x34: "Processor - Clock",
39
+ 0x35: "Processor - Flash Memory",
40
+ 0x36: "Ras fault",
41
+ 0x37: "Processor - ADC",
42
+ 0x3C: "Keypad fault",
43
+ 0x3D: "LVB data flash fault",
44
+ 0x3E: "Comm loss fault - LVB & Drive",
45
+ 0x3F: "Generic fault",
46
+ 0x40: "Coherence fault",
47
+ 0x41: "UL fault",
48
+ 0x42: "SVRS fault type 1",
49
+ 0x43: "SVRS fault type 2",
50
+ 0x44: "SVRS fault type 13",
51
+ }
52
+
53
+ let nackErrors = {
54
+ 0x01: "Command not recognized / illegal",
55
+ 0x02: "Operand out of allowed range",
56
+ 0x03: "Data out of range",
57
+ 0x04: "General failure: fault mode",
58
+ 0x05: "Incorrect command length",
59
+ 0x06: "Command cannot be executed now",
60
+ 0x09: "Buffer error (not used)",
61
+ 0x0A: "Running parameters incomplete (not used)",
62
+ }
63
+
64
+ export class RegalModbusStateMessage {
65
+ public static process(msg: Inbound) {
66
+
67
+ // debug log the message object
68
+ logger.debug(`RegalModbusStateMessage.process ${JSON.stringify(msg)}`);
69
+
70
+ let addr = msg.header[0];
71
+ let functionCode = msg.header[1];
72
+ let ack = msg.header[2];
73
+
74
+ if (ack == 0x20) return;
75
+ if (ack in nackErrors) {
76
+ logger.debug(`RegalModbusStateMessage.process NACK: ${nackErrors[ack]} (Address: ${addr})`);
77
+ return;
78
+ }
79
+ if (ack != 0x10) {
80
+ logger.debug(`RegalModbusStateMessage.process Unknown ACK: ${ack} (Address: ${addr})`);
81
+ return;
82
+ }
83
+
84
+ // If we're here, we have an ack=0x10 message
85
+
86
+ let pumpCfg = sys.pumps.getPumpByAddress(addr, false, { isActive: false });
87
+ let pumpId = pumpCfg.id;
88
+ let pumpType = sys.board.valueMaps.pumpTypes.transform(pumpCfg.type);
89
+ let pumpState = state.pumps.getItemById(pumpId, pumpCfg.isActive === true);
90
+
91
+ logger.debug(`RegalModbusStateMessage.process.pstate ${JSON.stringify(pumpState)}`);
92
+
93
+
94
+ switch (functionCode) {
95
+ case 0x41: { // Go
96
+ logger.debug(`RegalModbusStateMessage.process Go (Address: ${addr})`);
97
+ break;
98
+ }
99
+ case 0x42: { // Stop
100
+ logger.debug(`RegalModbusStateMessage.process Stop (Address: ${addr})`);
101
+ break;
102
+ }
103
+ case 0x43: { // Status
104
+ let status = msg.extractPayloadByte(0);
105
+ switch (status) {
106
+ case 0x00: { // stop mode - motor stopped
107
+ logger.debug(`RegalModbusStateMessage.process Status: Stop (Address: ${addr})`);
108
+ pumpState.driveState = 0;
109
+ pumpState.command = 4; // dashPanel assumes command = 10 in running state
110
+ break;
111
+ }
112
+ case 0x09: { // run mode - boot (motor is getting ready to spin)
113
+ logger.debug(`RegalModbusStateMessage.process Status: Boot (Address: ${addr})`);
114
+ pumpState.driveState = 1;
115
+ pumpState.command = 10; // dashPanel assumes command = 10 in running state
116
+ break;
117
+ }
118
+ case 0x0B: { // run mode - vector
119
+ logger.debug(`RegalModbusStateMessage.process Status: Vector (Address: ${addr})`);
120
+ pumpState.driveState = 2;
121
+ pumpState.command = 10; // dashPanel assumes command = 10 in running state
122
+ break;
123
+ }
124
+ case 0x20: { // fault mode - motor stopped
125
+ logger.debug(`RegalModbusStateMessage.process Status: Fault (Address: ${addr})`);
126
+ pumpState.driveState = 4;
127
+ pumpState.command = 4; // dashPanel assumes command = 10 in running state
128
+ break;
129
+ }
130
+ }
131
+ break;
132
+ }
133
+ case 0x44: { // Set demand
134
+ let mode = msg.extractPayloadByte(0);
135
+ let demandLo = msg.extractPayloadByte(1);
136
+ let demandHi = msg.extractPayloadByte(2);
137
+
138
+ switch (mode) {
139
+ case 0: { // Speed control, demand = RPM * 4
140
+ let rpm = RegalModbusStateMessage.demandToRPM(demandLo, demandHi);
141
+ logger.debug(`RegalModbusStateMessage.process Speed: ${rpm} (Address: ${addr})`);
142
+ pumpState.rpm = rpm;
143
+ break;
144
+ }
145
+ case 1: { // Torque control, demand = lbf-ft * 1200
146
+ logger.debug(`RegalModbusStateMessage.process Ignoring torque: ${demandLo}, ${demandHi} (Address: ${addr})`);
147
+ break;
148
+ }
149
+ case 2: { // Reserved (used to be flow)
150
+ logger.debug(`RegalModbusStateMessage.process Ignoring reserved demand mode ${mode}: ${demandLo}, ${demandHi} (Address: ${addr})`);
151
+ break;
152
+ }
153
+ case 3: { // Reserved
154
+ logger.debug(`RegalModbusStateMessage.process Ignoring reserved demand mode ${mode}: ${demandLo}, ${demandHi} (Address: ${addr})`);
155
+ break;
156
+ }
157
+ }
158
+ break;
159
+ }
160
+ case 0x45: { // Read sensor
161
+ let page = msg.extractPayloadByte(0);
162
+ let sensorAddr = msg.extractPayloadByte(1);
163
+ let valueLo = msg.extractPayloadByte(2);
164
+ let valueHi = msg.extractPayloadByte(3);
165
+ let raw_value = (valueHi << 8) + valueLo;
166
+
167
+ let scaleValue = (value: number, scale: number) => {
168
+ return value / scale;
169
+ };
170
+ let scaled_value;
171
+
172
+ switch (page) {
173
+ case 0: {
174
+ switch (sensorAddr) {
175
+ case 0x00: { // Motor speed
176
+ scaled_value = scaleValue(raw_value, 4);
177
+ logger.debug(`RegalModbusStateMessage.process Motor speed: ${scaled_value} (Address: ${addr})`);
178
+ pumpState.rpm = scaled_value;
179
+ break;
180
+ }
181
+ case 0x01: { // Motor current
182
+ scaled_value = scaleValue(raw_value, 1000);
183
+ logger.debug(`RegalModbusStateMessage.process Motor current: ${scaled_value} (Address: ${addr})`);
184
+ break;
185
+ }
186
+ case 0x02: { // Operating mode
187
+ switch (raw_value) {
188
+ case 0: { // Speed control
189
+ logger.debug(`RegalModbusStateMessage.process Operating mode: Speed control (Address: ${addr})`);
190
+ break;
191
+ }
192
+ case 1: { // Torque control
193
+ logger.debug(`RegalModbusStateMessage.process Operating mode: Torque control (Address: ${addr})`);
194
+ break;
195
+ }
196
+ }
197
+ break;
198
+ }
199
+ case 0x03: { // Demand sent to motor
200
+ logger.debug(`RegalModbusStateMessage.process Raw (unscaled) demand sent to motor: ${raw_value} (Address: ${addr})`);
201
+ break;
202
+ }
203
+ case 0x04: { // Torque
204
+ scaled_value = scaleValue(raw_value, 1200);
205
+ logger.debug(`RegalModbusStateMessage.process Torque: ${scaled_value} (Address: ${addr})`);
206
+ break;
207
+ }
208
+ case 0x05: { // Inverter input power
209
+ logger.debug(`RegalModbusStateMessage.process Raw (unscaled) inverter input power: ${raw_value} (Address: ${addr})`);
210
+ break;
211
+ }
212
+ case 0x06: { // DC bus voltage
213
+ scaled_value = scaleValue(raw_value, 64);
214
+ logger.debug(`RegalModbusStateMessage.process DC bus voltage: ${scaled_value} (Address: ${addr})`);
215
+ break;
216
+ }
217
+ case 0x07: { // Ambient temperature
218
+ scaled_value = scaleValue(raw_value, 128);
219
+ logger.debug(`RegalModbusStateMessage.process Ambient temperature: ${scaled_value} (Address: ${addr})`);
220
+ break;
221
+ }
222
+ case 0x08: { // Status
223
+ switch (raw_value) {
224
+ case 0x00: { // stop mode - motor stopped
225
+ logger.debug(`RegalModbusStateMessage.process Status: Stop (Address: ${addr})`);
226
+ pumpState.driveState = 0;
227
+ break;
228
+ }
229
+ case 0x09: { // run mode - boot (motor is getting ready to spin)
230
+ logger.debug(`RegalModbusStateMessage.process Status: Boot (Address: ${addr})`);
231
+ pumpState.driveState = 1;
232
+ break;
233
+ }
234
+ case 0x0B: { // run mode - vector
235
+ logger.debug(`RegalModbusStateMessage.process Status: Vector (Address: ${addr})`);
236
+ pumpState.driveState = 2;
237
+ break;
238
+ }
239
+ case 0x20: { // fault mode - motor stopped
240
+ logger.debug(`RegalModbusStateMessage.process Status: Fault (Address: ${addr})`);
241
+ pumpState.driveState = 4;
242
+ break;
243
+ }
244
+ }
245
+ break;
246
+ }
247
+ case 0x09: { // Previous fault
248
+ if (raw_value in faultCodes) {
249
+ logger.debug(`RegalModbusStateMessage.process Previous fault: ${faultCodes[raw_value]} (Address: ${addr})`);
250
+ } else {
251
+ logger.debug(`RegalModbusStateMessage.process Previous fault: Unknown fault code ${raw_value} (Address: ${addr})`);
252
+ }
253
+ break;
254
+ }
255
+ case 0X0A: { // Output power
256
+ scaled_value = scaleValue(raw_value, 1);
257
+ logger.debug(`RegalModbusStateMessage.process Shaft power (W): ${scaled_value} (Address: ${addr})`);
258
+ pumpState.watts = scaled_value;
259
+ break;
260
+ }
261
+ case 0x0B: { // SVRS Bypass Status
262
+ break;
263
+ }
264
+ case 0x0C: { // Number of current faults
265
+ logger.debug(`RegalModbusStateMessage.process Number of current faults: ${raw_value} (Address: ${addr})`);
266
+ break;
267
+ }
268
+ case 0x0D: { // Motor line voltage
269
+ logger.debug(`RegalModbusStateMessage.process Raw (unscaled) motor line voltage: ${raw_value} (Address: ${addr})`);
270
+ break;
271
+ }
272
+ case 0x0E: { // Ramp status
273
+ logger.debug(`RegalModbusStateMessage.process Ramp status: ${raw_value} (Address: ${addr})`);
274
+ break;
275
+ }
276
+ case 0x0F: { // Number of total fault
277
+ logger.debug(`RegalModbusStateMessage.process Number of total faults: ${raw_value} (Address: ${addr})`);
278
+ break;
279
+ }
280
+ case 0x10: { // Prime status
281
+ switch (raw_value) {
282
+ case 0: { // Not priming
283
+ logger.debug(`RegalModbusStateMessage.process Prime status: Not priming (Address: ${addr})`);
284
+ break;
285
+ }
286
+ case 1: { // Priming running
287
+ logger.debug(`RegalModbusStateMessage.process Prime status: Priming running (Address: ${addr})`);
288
+ break;
289
+ }
290
+ case 2: { // Priming completed
291
+ logger.debug(`RegalModbusStateMessage.process Prime status: Priming completed (Address: ${addr})`);
292
+ break;
293
+ }
294
+ }
295
+ break;
296
+ }
297
+ case 0x11: { // Motor input power
298
+ logger.debug(`RegalModbusStateMessage.process Raw (unscaled) motor input power: ${raw_value} (Address: ${addr})`);
299
+ break;
300
+ }
301
+ case 0x12: { // IGBT temperature
302
+ scaled_value = scaleValue(raw_value, 128);
303
+ logger.debug(`RegalModbusStateMessage.process IGBT temperature: ${scaled_value} (Address: ${addr})`);
304
+ break;
305
+ }
306
+ case 0x13: { // PCB temperature
307
+ logger.debug(`RegalModbusStateMessage.process Raw (unscaled) PCB temperature: ${raw_value} (Address: ${addr})`);
308
+ break;
309
+ }
310
+ case 0x14: { // Status of external input
311
+ switch (raw_value) {
312
+ case 0: { // No external input
313
+ logger.debug(`RegalModbusStateMessage.process External input: No external input (Address: ${addr})`);
314
+ break;
315
+ }
316
+ case 3: { // PWM
317
+ logger.debug(`RegalModbusStateMessage.process External input: PWM (Address: ${addr})`);
318
+ break;
319
+ }
320
+ case 4: { // DI_1 present
321
+ logger.debug(`RegalModbusStateMessage.process External input: DI_1 present (Address: ${addr})`);
322
+ break;
323
+ }
324
+ case 5: { // DI_2 present
325
+ logger.debug(`RegalModbusStateMessage.process External input: DI_2 present (Address: ${addr})`);
326
+ break;
327
+ }
328
+ case 6: { // DI_3 present
329
+ logger.debug(`RegalModbusStateMessage.process External input: DI_3 present (Address: ${addr})`);
330
+ break;
331
+ }
332
+ case 7: { // DI_4 present
333
+ logger.debug(`RegalModbusStateMessage.process External input: DI_4 present (Address: ${addr})`);
334
+ break;
335
+ }
336
+ case 8: { // Serial input
337
+ logger.debug(`RegalModbusStateMessage.process External input: Serial input (Address: ${addr})`);
338
+ break;
339
+ }
340
+ }
341
+ break;
342
+ }
343
+ case 0x15: { // Reference speed
344
+ scaled_value = scaleValue(raw_value, 4);
345
+ logger.debug(`RegalModbusStateMessage.process Reference speed: ${scaled_value} (Address: ${addr})`);
346
+ break;
347
+ }
348
+ }
349
+ break;
350
+ }
351
+ default: {
352
+ logger.debug(`RegalModbusStateMessage.process Page 1: ${page} (Address: ${addr}) - Not yet implemented`);
353
+ break;
354
+ }
355
+ }
356
+ break;
357
+ }
358
+ case 0x46: { // Read identification
359
+ logger.debug(`RegalModbusStateMessage.process Read identification (Address: ${addr}) - Not yet implemented`);
360
+ break;
361
+ }
362
+ case 0x64: { // Read/write configuration
363
+ logger.debug(`RegalModbusStateMessage.process Read/write configuration (Address: ${addr}) - Not yet implemented`);
364
+ break;
365
+ }
366
+ default: {
367
+ logger.debug(`RegalModbusStateMessage.process Unknown function code: ${functionCode} (Address: ${addr})`);
368
+ break;
369
+ }
370
+ }
371
+ state.emitEquipmentChanges();
372
+ }
373
+
374
+ public static rpmToDemand(rpm: number): [number, number] {
375
+ /**
376
+ * Converts an RPM value to a RegalModbus demand payload in speed control mode.
377
+ *
378
+ * @param {number} rpm - Desired motor speed in RPM.
379
+ * @returns {[number, number]} - [demand_lo, demand_hi]
380
+ * - demand_lo: lower byte of demand
381
+ * - demand_hi: upper byte of demand
382
+ * @throws {Error} - If RPM is out of valid range for RegalModbus demand (0–16383).
383
+ */
384
+ if (rpm < 0 || rpm * 4 > 0xFFFF) {
385
+ throw new Error("RPM is out of valid range for RegalModbus demand (0–16383)");
386
+ }
387
+
388
+ const rawDemand = Math.round(rpm * 4); // Scale RPM by 4
389
+ const demandLo = rawDemand & 0xFF;
390
+ const demandHi = (rawDemand >> 8) & 0xFF;
391
+
392
+ return [demandLo, demandHi];
393
+ }
394
+
395
+ public static demandToRPM(demandLo: number, demandHi: number): number {
396
+ /**
397
+ * Converts a RegalModbus demand payload to an RPM value.
398
+ *
399
+ * @param {number} demandLo - Lower byte of demand.
400
+ * @param {number} demandHi - Upper byte of demand.
401
+ * @returns {number} - Motor speed in RPM.
402
+ * @throws {Error} - If demand is out of valid range for RPM (0–16383).
403
+ **/
404
+ const rawDemand = (demandHi << 8) | demandLo; // Combine high and low bytes
405
+ if (rawDemand < 0 || rawDemand > 0xFFFF) {
406
+ throw new Error("Demand is out of valid range for RPM (0–16383)");
407
+ }
408
+ const rpm = Math.round(rawDemand / 4); // Scale back to RPM
409
+ return rpm;
410
+ }
411
+ }
@@ -137,6 +137,8 @@ export class NixiePumpCollection extends NixieEquipmentCollection<NixiePump> {
137
137
  return new NixiePumpHWVS(this.controlPanel, pump);
138
138
  case 'hwrly':
139
139
  return new NixiePumpHWRLY(this.controlPanel, pump);
140
+ case 'regalmodbus':
141
+ return new NixiePumpRegalModbus(this.controlPanel, pump);
140
142
  default:
141
143
  throw new EquipmentNotFoundError(`NCP: Cannot create pump ${pump.name}.`, type);
142
144
  }
@@ -235,6 +237,7 @@ export class NixiePump extends NixieEquipment {
235
237
  case 'hwvs':
236
238
  case 'vssvrs':
237
239
  case 'vs':
240
+ case 'regalmodbus':
238
241
  c.units = sys.board.valueMaps.pumpUnits.getValue('rpm');
239
242
  break;
240
243
  case 'ss':
@@ -994,3 +997,198 @@ export class NixiePumpHWVS extends NixiePumpRS485 {
994
997
 
995
998
  };
996
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
+ }
1194
+ }
@@ -30,6 +30,11 @@
30
30
  "password": 1234
31
31
  }
32
32
  },
33
+ "txDelays": {
34
+ "idleBeforeTxMs": 0,
35
+ "interFrameDelayMs": 100,
36
+ "interByteDelayMs": 0
37
+ },
33
38
  "backups": {
34
39
  "automatic": false,
35
40
  "interval": {
@@ -0,0 +1,32 @@
1
+ services:
2
+ njspc:
3
+ build:
4
+ context: .
5
+ dockerfile: Dockerfile
6
+ image: njspc:local
7
+ container_name: njspc
8
+ restart: unless-stopped
9
+ ports:
10
+ - "4200:4200"
11
+ environment:
12
+ - TZ=${TZ:-UTC}
13
+ - NODE_ENV=production
14
+ # Example overrides as supported by Config.getEnvVariables()
15
+ # - POOL_WEB_SERVERS_HTTP_PORT=4200
16
+ # Disable network connection and use local serial (rs485)
17
+ - POOL_NET_CONNECT=false
18
+ # Map RS-485 USB adapter if present, adjust device path
19
+ devices:
20
+ - /dev/ttyACM0:/dev/ttyUSB0
21
+ # Persistence (create host directories/files first)
22
+ volumes:
23
+ - ./server-config.json:/app/config.json # Persisted config file on host
24
+ - njspc-data:/app/data # State & equipment snapshots
25
+ - njspc-backups:/app/backups # Backup archives
26
+ - njspc-logs:/app/logs # Logs
27
+ - njspc-bindings:/app/web/bindings/custom # Custom bindings
28
+ volumes:
29
+ njspc-data:
30
+ njspc-backups:
31
+ njspc-logs:
32
+ njspc-bindings: