nodejs-poolcontroller 7.4.0 → 7.5.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.
- package/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
- package/Changelog +3 -0
- package/README.md +2 -2
- package/app.ts +2 -0
- package/config/Config.ts +3 -0
- package/config/VersionCheck.ts +8 -4
- package/controller/Equipment.ts +89 -29
- package/controller/Errors.ts +14 -1
- package/controller/State.ts +75 -31
- package/controller/boards/EasyTouchBoard.ts +81 -36
- package/controller/boards/IntelliCenterBoard.ts +96 -32
- package/controller/boards/IntelliTouchBoard.ts +103 -29
- package/controller/boards/NixieBoard.ts +79 -27
- package/controller/boards/SystemBoard.ts +1552 -822
- package/controller/comms/Comms.ts +84 -9
- package/controller/comms/messages/Messages.ts +10 -4
- package/controller/comms/messages/config/ChlorinatorMessage.ts +13 -4
- package/controller/comms/messages/config/CircuitGroupMessage.ts +6 -0
- package/controller/comms/messages/config/CoverMessage.ts +1 -0
- package/controller/comms/messages/config/EquipmentMessage.ts +4 -0
- package/controller/comms/messages/config/ExternalMessage.ts +43 -25
- package/controller/comms/messages/config/FeatureMessage.ts +8 -1
- package/controller/comms/messages/config/GeneralMessage.ts +8 -0
- package/controller/comms/messages/config/HeaterMessage.ts +10 -9
- package/controller/comms/messages/config/IntellichemMessage.ts +4 -1
- package/controller/comms/messages/config/OptionsMessage.ts +13 -1
- package/controller/comms/messages/config/PumpMessage.ts +4 -20
- package/controller/comms/messages/config/RemoteMessage.ts +4 -0
- package/controller/comms/messages/config/ScheduleMessage.ts +11 -0
- package/controller/comms/messages/config/SecurityMessage.ts +1 -0
- package/controller/comms/messages/config/ValveMessage.ts +12 -2
- package/controller/comms/messages/status/ChlorinatorStateMessage.ts +2 -3
- package/controller/comms/messages/status/EquipmentStateMessage.ts +74 -22
- package/controller/comms/messages/status/HeaterStateMessage.ts +15 -6
- package/controller/comms/messages/status/IntelliChemStateMessage.ts +37 -26
- package/controller/nixie/Nixie.ts +18 -16
- package/controller/nixie/chemistry/ChemController.ts +57 -37
- package/controller/nixie/chemistry/Chlorinator.ts +7 -8
- package/controller/nixie/circuits/Circuit.ts +17 -0
- package/controller/nixie/pumps/Pump.ts +49 -24
- package/controller/nixie/schedules/Schedule.ts +1 -1
- package/defaultConfig.json +15 -0
- package/issue_template.md +1 -1
- package/logger/DataLogger.ts +37 -22
- package/package.json +3 -1
- package/web/Server.ts +515 -27
- package/web/bindings/influxDB.json +35 -0
- package/web/bindings/mqtt.json +62 -3
- package/web/bindings/mqttAlt.json +57 -4
- package/web/interfaces/httpInterface.ts +2 -0
- package/web/interfaces/influxInterface.ts +3 -2
- package/web/interfaces/mqttInterface.ts +12 -1
- package/web/services/config/Config.ts +162 -37
- package/web/services/state/State.ts +47 -3
- package/web/services/state/StateSocket.ts +1 -1
|
@@ -23,15 +23,25 @@ import { Timestamp, utils } from "../../../Constants"
|
|
|
23
23
|
export class IntelliChemStateMessage {
|
|
24
24
|
public static process(msg: Inbound) {
|
|
25
25
|
if (sys.controllerType === ControllerType.Unknown) return;
|
|
26
|
-
|
|
26
|
+
let address = (msg.dest >= 144 && msg.dest <= 158) ? msg.dest : msg.source;
|
|
27
|
+
if (address < 144 || address > 158) return;
|
|
28
|
+
let controller = sys.chemControllers.getItemByAddress(address);
|
|
29
|
+
if (!controller.isActive) {
|
|
30
|
+
msg.isProcessed = true;
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
27
33
|
switch (msg.action) {
|
|
28
|
-
|
|
29
34
|
// ---------- IntelliChem Control panel is spitting out its status ----------- //
|
|
30
35
|
case 18: // IntelliChem is sending us it's status.
|
|
31
36
|
IntelliChemStateMessage.processState(msg);
|
|
32
37
|
break;
|
|
33
38
|
case 210: // OCP is asking IntelliChem controller for it's current status info.
|
|
34
39
|
// [165,0,144,16,210,1],[210],[2,234]
|
|
40
|
+
let schem = state.chemControllers.getItemById(controller.id);
|
|
41
|
+
if (schem.lastComm + (30 * 1000) < new Date().getTime()) {
|
|
42
|
+
// We have not talked to the chem controller in 30 seconds so we have lost communication.
|
|
43
|
+
schem.status = schem.alarms.comms = 1;
|
|
44
|
+
}
|
|
35
45
|
msg.isProcessed = true;
|
|
36
46
|
break;
|
|
37
47
|
// ---------- End IntelliChem set get ----------- //
|
|
@@ -43,7 +53,7 @@ export class IntelliChemStateMessage {
|
|
|
43
53
|
|
|
44
54
|
/* RKS: This is processed in the IntellichemMessage.processTouch() and is the results of asking for the IntelliChem configuration.
|
|
45
55
|
case 147: // OCP is broadcasting it's known ic values... Need to change our settings if virtual.
|
|
46
|
-
// 147 is a proto:broadcast message;
|
|
56
|
+
// 147 is a proto:broadcast message;
|
|
47
57
|
// it has exactly the same format as 18 but there is payload[0] which is inserted at the beginning. Likely the chem controller id.
|
|
48
58
|
if (msg.dest < 144 || msg.dest > 158) return;
|
|
49
59
|
IntelliChemStateMessage.processControllerChange(msg);
|
|
@@ -57,14 +67,11 @@ export class IntelliChemStateMessage {
|
|
|
57
67
|
msg.isProcessed = true;
|
|
58
68
|
break;
|
|
59
69
|
case 146: // OCP is telling IntelliChem that it needs to change its settings to...
|
|
60
|
-
let
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
// We have not talked to the chem controller in 30 seconds so we have lost communication.
|
|
66
|
-
scontroller.status = scontroller.alarms.comms = 1;
|
|
67
|
-
}
|
|
70
|
+
//let scontroller = state.chemControllers.getItemById(controller.id, true);
|
|
71
|
+
//if (scontroller.lastComm + (30 * 1000) < new Date().getTime()) {
|
|
72
|
+
// // We have not talked to the chem controller in 30 seconds so we have lost communication.
|
|
73
|
+
// scontroller.status = scontroller.alarms.comms = 1;
|
|
74
|
+
//}
|
|
68
75
|
controller.ph.tank.capacity = controller.orp.tank.capacity = 6;
|
|
69
76
|
controller.ph.tank.units = controller.orp.tank.units = '';
|
|
70
77
|
msg.isProcessed = true;
|
|
@@ -89,8 +96,8 @@ export class IntelliChemStateMessage {
|
|
|
89
96
|
// 6-7 : ORP Setpoint : byte(6) x 256 + byte(7)
|
|
90
97
|
// 8 : Unknown = 0
|
|
91
98
|
// 9 : Unknown = 0
|
|
92
|
-
// 10 :
|
|
93
|
-
//
|
|
99
|
+
// 10-11 : pH Dose time seconds. The number of seconds since the dose started. byte(10) x 256 + byte(11)
|
|
100
|
+
// 12: Unknown
|
|
94
101
|
// 13 : Unknown
|
|
95
102
|
// 14-15 : ORP Dose time seconds. The number of seconds since the dose started. byte(14) x 256 + byte(15)
|
|
96
103
|
// 16-17 : pH Dose volume (unknown units) - These appear to be mL.
|
|
@@ -122,10 +129,13 @@ export class IntelliChemStateMessage {
|
|
|
122
129
|
schem.type = chem.type = sys.board.valueMaps.chemControllerTypes.getValue('intellichem');
|
|
123
130
|
chem.name = chem.name || `IntelliChem ${chem.address - 143}`; // default to true id if no name is set
|
|
124
131
|
schem.lastComm = new Date().getTime();
|
|
125
|
-
schem.status = schem.alarms.comms = 0;
|
|
132
|
+
schem.status = schem.alarms.comms = 0;
|
|
126
133
|
chem.ph.tank.capacity = chem.orp.tank.capacity = 6;
|
|
127
134
|
chem.ph.tank.units = chem.orp.tank.units = '';
|
|
128
|
-
|
|
135
|
+
chem.ph.tank.alarmEmptyEnabled = false;
|
|
136
|
+
chem.ph.tank.alarmEmptyLevel = 1;
|
|
137
|
+
chem.orp.tank.alarmEmptyEnabled = false;
|
|
138
|
+
chem.orp.tank.alarmEmptyLevel = 1;
|
|
129
139
|
schem.address = chem.address;
|
|
130
140
|
schem.ph.level = schem.ph.probe.level = msg.extractPayloadIntBE(0) / 100;
|
|
131
141
|
schem.orp.level = schem.orp.probe.level = msg.extractPayloadIntBE(2);
|
|
@@ -133,11 +143,11 @@ export class IntelliChemStateMessage {
|
|
|
133
143
|
chem.orp.setpoint = msg.extractPayloadIntBE(6);
|
|
134
144
|
// Missing information on the related bytes.
|
|
135
145
|
// Bytes 8-14 (Probably Total Dissolved Solids in here if no IntelliChlor)
|
|
136
|
-
let phPrev = { status: schem.
|
|
146
|
+
let phPrev = { status: schem.ph.dosingStatus, time: schem.ph.timeDosed || 0, vol: schem.ph.volumeDosed };
|
|
137
147
|
let orpPrev = { status: schem.orp.dosingStatus, time: schem.orp.timeDosed || 0, vol: schem.orp.volumeDosed };
|
|
138
148
|
// IntelliChem never tells us what the dose time or volume is so we will let that dog lie.
|
|
139
|
-
// 11
|
|
140
|
-
schem.ph.timeDosed = (msg.extractPayloadByte(
|
|
149
|
+
// 10-11 : pH Dose time
|
|
150
|
+
schem.ph.timeDosed = (msg.extractPayloadByte(10) * 256) + msg.extractPayloadByte(11);
|
|
141
151
|
// 14-15 : ORP Dose time seconds. The number of seconds since the dose started.
|
|
142
152
|
schem.orp.timeDosed = (msg.extractPayloadByte(14) * 256) + msg.extractPayloadByte(15);
|
|
143
153
|
// 16-17 : pH Dose volume (unknown units) = 35
|
|
@@ -157,7 +167,7 @@ export class IntelliChemStateMessage {
|
|
|
157
167
|
chem.cyanuricAcid = msg.extractPayloadByte(26);
|
|
158
168
|
// 27-28 : Alkalinity
|
|
159
169
|
chem.alkalinity = (msg.extractPayloadByte(27) * 256) + msg.extractPayloadByte(28);
|
|
160
|
-
// 29 : Salt level = 20
|
|
170
|
+
// 29 : Salt level = 20
|
|
161
171
|
if (sys.chlorinators.length > 0) {
|
|
162
172
|
let chlor = state.chlorinators.find(elem => elem.id === 1);
|
|
163
173
|
schem.orp.probe.saltLevel = (typeof chlor !== 'undefined') ? chlor.saltLevel : msg.extractPayloadByte(29) * 50;
|
|
@@ -169,6 +179,7 @@ export class IntelliChemStateMessage {
|
|
|
169
179
|
// 32 : Alarms = 8 = (no alarm)
|
|
170
180
|
const alarms = schem.alarms;
|
|
171
181
|
alarms.flow = msg.extractPayloadByte(32) & 0x01;
|
|
182
|
+
if (alarms.flow === 0) schem.flowDetected = true;
|
|
172
183
|
alarms.pH = msg.extractPayloadByte(32) & 0x06;
|
|
173
184
|
alarms.orp = msg.extractPayloadByte(32) & 0x18;
|
|
174
185
|
alarms.pHTank = msg.extractPayloadByte(32) & 0x20;
|
|
@@ -187,11 +198,11 @@ export class IntelliChemStateMessage {
|
|
|
187
198
|
schem.ph.dosingStatus = (msg.extractPayloadByte(34) & 0x30) >> 4; // mask 00xx0000 and shift bit 5 & 6
|
|
188
199
|
schem.orp.dosingStatus = (msg.extractPayloadByte(34) & 0xC0) >> 6; // mask xx000000 and shift bit 7 & 8
|
|
189
200
|
// 35 : Delays = 0
|
|
190
|
-
schem.status = msg.extractPayloadByte(35) & 0x80 >> 7; // to be verified as comms lost
|
|
201
|
+
schem.status = (msg.extractPayloadByte(35) & 0x80) >> 7; // to be verified as comms lost
|
|
191
202
|
schem.ph.manualDosing = (msg.extractPayloadByte(35) & 0x08) === 1 ? true : false;
|
|
192
203
|
chem.orp.useChlorinator = (msg.extractPayloadByte(35) & 0x10) === 1 ? true : false;
|
|
193
204
|
chem.HMIAdvancedDisplay = (msg.extractPayloadByte(35) & 0x20) === 1 ? true : false;
|
|
194
|
-
chem.ph.phSupply = (msg.extractPayloadByte(35) & 0x40) === 1 ?
|
|
205
|
+
chem.ph.phSupply = (msg.extractPayloadByte(35) & 0x40) === 1 ? 'acid' : 'base'; // acid pH dosing = 1; base pH dosing = 0;
|
|
195
206
|
// 36-37 : Firmware = 80,1 = 1.080
|
|
196
207
|
chem.firmware = `${msg.extractPayloadByte(37)}.${msg.extractPayloadByte(36).toString().padStart(3, '0')}`
|
|
197
208
|
// 38 : Water Chemistry Warning
|
|
@@ -230,12 +241,12 @@ export class IntelliChemStateMessage {
|
|
|
230
241
|
if (typeof schem.ph.currentDose !== 'undefined') {
|
|
231
242
|
// We just ended a dose so write it out to the chem logs.
|
|
232
243
|
schem.ph.endDose(Timestamp.now.addSeconds(-(schem.ph.doseTime - phPrev.time)).toDate(), 'completed',
|
|
233
|
-
schem.ph.volumeDosed - phPrev.vol, (schem.ph.
|
|
244
|
+
schem.ph.volumeDosed - phPrev.vol, (schem.ph.timeDosed - phPrev.time) * 1000);
|
|
234
245
|
}
|
|
235
246
|
}
|
|
236
247
|
else if (schem.ph.dosingStatus === 0) {
|
|
237
248
|
// We are still dosing so add the time and volume to the dose.
|
|
238
|
-
schem.ph.appendDose(schem.ph.doseVolume - phPrev.vol, (schem.ph.
|
|
249
|
+
schem.ph.appendDose(schem.ph.doseVolume - phPrev.vol, (schem.ph.timeDosed - phPrev.time) * 1000);
|
|
239
250
|
}
|
|
240
251
|
else {
|
|
241
252
|
console.log(`DOSING STATUS === ${schem.ph.dosingStatus}`);
|
|
@@ -246,19 +257,19 @@ export class IntelliChemStateMessage {
|
|
|
246
257
|
if (schem.orp.dosingStatus === 0 && orpPrev.status !== 0) {
|
|
247
258
|
if (schem.orp.dosingStatus === 0) {
|
|
248
259
|
// We are starting a dose so we need to set the current dose.
|
|
249
|
-
schem.orp.startDose(Timestamp.now.addSeconds(-schem.orp.doseTime).toDate(), schem.orp.manualDosing ? 'manual' : 'auto', 0, schem.orp.volumeDosed, schem.orp.timeDosed * 1000);
|
|
260
|
+
schem.orp.startDose(Timestamp.now.addSeconds(-schem.orp.doseTime).toDate(), schem.orp.manualDosing ? 'manual' : 'auto', 0, schem.orp.volumeDosed, 0, schem.orp.timeDosed * 1000);
|
|
250
261
|
}
|
|
251
262
|
}
|
|
252
263
|
else if (schem.orp.dosingStatus !== 0 && orpPrev.status === 0) {
|
|
253
264
|
if (typeof schem.orp.currentDose !== 'undefined') {
|
|
254
265
|
// We just ended a dose so write it out to the chem logs.
|
|
255
266
|
schem.orp.endDose(Timestamp.now.addSeconds(-(schem.orp.doseTime - orpPrev.time)).toDate(), 'completed',
|
|
256
|
-
schem.orp.volumeDosed - orpPrev.vol, schem.orp.
|
|
267
|
+
schem.orp.volumeDosed - orpPrev.vol, (schem.orp.timeDosed - orpPrev.time) * 1000);
|
|
257
268
|
}
|
|
258
269
|
}
|
|
259
270
|
else if (schem.orp.dosingStatus === 0) {
|
|
260
271
|
// We are still dosing so add the time and volume to the dose.
|
|
261
|
-
schem.orp.appendDose(schem.orp.
|
|
272
|
+
schem.orp.appendDose(schem.orp.doseVolume - orpPrev.vol, (schem.orp.timeDosed - orpPrev.time) * 1000);
|
|
262
273
|
}
|
|
263
274
|
else {
|
|
264
275
|
// Make sure we don't have a current dose going.
|
|
@@ -136,23 +136,25 @@ export class NixieControlPanel implements INixieControlPanel {
|
|
|
136
136
|
try {
|
|
137
137
|
let srv = [];
|
|
138
138
|
let servers = webApp.findServersByType('rem');
|
|
139
|
-
|
|
140
|
-
let
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
139
|
+
if (typeof servers !== 'undefined') {
|
|
140
|
+
for (let i = 0; i < servers.length; i++) {
|
|
141
|
+
let server = servers[i];
|
|
142
|
+
// Sometimes I hate type safety.
|
|
143
|
+
let devices = typeof server['getDevices'] === 'function' ? await server['getDevices']() : [];
|
|
144
|
+
let int = config.getInterfaceByUuid(servers[i].uuid);
|
|
145
|
+
srv.push({
|
|
146
|
+
uuid: servers[i].uuid,
|
|
147
|
+
name: servers[i].name,
|
|
148
|
+
type: servers[i].type,
|
|
149
|
+
isRunning: servers[i].isRunning,
|
|
150
|
+
isConnected: servers[i].isConnected,
|
|
151
|
+
devices: devices,
|
|
152
|
+
remoteConnectionId: servers[i].remoteConnectionId,
|
|
153
|
+
interface: int
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
await ncp.chemControllers.syncRemoteREMFeeds(srv);
|
|
154
157
|
}
|
|
155
|
-
await ncp.chemControllers.syncRemoteREMFeeds(srv);
|
|
156
158
|
return srv;
|
|
157
159
|
} catch (err) { logger.error(err); }
|
|
158
160
|
}
|
|
@@ -9,6 +9,7 @@ import { EquipmentNotFoundError, EquipmentTimeoutError, InvalidEquipmentDataErro
|
|
|
9
9
|
import { ChemControllerState, ChemicalChlorState, ChemicalDoseState, ChemicalORPState, ChemicalPhState, ChemicalProbeORPState, ChemicalProbePHState, ChemicalProbeState, ChemicalPumpState, ChemicalState, ChemicalTankState, ChlorinatorState, state } from "../../State";
|
|
10
10
|
import { ncp } from '../Nixie';
|
|
11
11
|
import { INixieControlPanel, NixieChildEquipment, NixieEquipment, NixieEquipmentCollection } from "../NixieEquipment";
|
|
12
|
+
import { NixieChlorinator } from './Chlorinator';
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
export class NixieChemControllerCollection extends NixieEquipmentCollection<NixieChemControllerBase> {
|
|
@@ -50,7 +51,7 @@ export class NixieChemControllerCollection extends NixieEquipmentCollection<Nixi
|
|
|
50
51
|
ncc = NixieChemControllerBase.create(this.controlPanel, chem);
|
|
51
52
|
this.push(ncc);
|
|
52
53
|
let ctype = sys.board.valueMaps.chemControllerTypes.transform(chem.type);
|
|
53
|
-
logger.info(`
|
|
54
|
+
logger.info(`Nixie Chem Controller was created at id #${chem.id} for type ${ctype.desc}`);
|
|
54
55
|
await ncc.setControllerAsync(data);
|
|
55
56
|
}
|
|
56
57
|
else {
|
|
@@ -72,9 +73,11 @@ export class NixieChemControllerCollection extends NixieEquipmentCollection<Nixi
|
|
|
72
73
|
for (let i = 0; i < controllers.length; i++) {
|
|
73
74
|
let cc = controllers.getItemByIndex(i);
|
|
74
75
|
if (cc.master === 1) {
|
|
76
|
+
let type = sys.board.valueMaps.chemControllerTypes.transform(cc.type);
|
|
75
77
|
logger.info(`Initializing chemController ${cc.name}`);
|
|
76
78
|
// First check to make sure it isnt already there.
|
|
77
79
|
if (typeof this.find(elem => elem.id === cc.id) === 'undefined') {
|
|
80
|
+
|
|
78
81
|
let ncc = NixieChemControllerBase.create(this.controlPanel, cc);
|
|
79
82
|
this.push(ncc);
|
|
80
83
|
}
|
|
@@ -98,6 +101,19 @@ export class NixieChemControllerCollection extends NixieEquipmentCollection<Nixi
|
|
|
98
101
|
|
|
99
102
|
} catch (err) { } // Don't bail if we have an error
|
|
100
103
|
}
|
|
104
|
+
public async deleteChlorAsync(chlor: NixieChlorinator) {
|
|
105
|
+
// if we delete the chlor, make sure it is removed from all REM Chem Controllers
|
|
106
|
+
try {
|
|
107
|
+
for (let i = this.length - 1; i >= 0; i--) {
|
|
108
|
+
try {
|
|
109
|
+
let ncc = this[i] as NixieChemControllerBase;;
|
|
110
|
+
ncc.orp.deleteChlorAsync(chlor);
|
|
111
|
+
} catch (err) { logger.error(`Error deleting chlor from Nixie Chem Controller ${err}`); return Promise.reject(err); }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
}
|
|
115
|
+
catch (err) { logger.error(`ncp.deleteChlorAsync: ${err.message}`); return Promise.reject(err); }
|
|
116
|
+
}
|
|
101
117
|
// This is currently not used for anything.
|
|
102
118
|
/* public async searchIntelliChem(): Promise<number[]> {
|
|
103
119
|
let arr = [];
|
|
@@ -145,12 +161,10 @@ export class NixieChemControllerBase extends NixieEquipment {
|
|
|
145
161
|
public chem: ChemController;
|
|
146
162
|
public syncRemoteREMFeeds(servers) { }
|
|
147
163
|
public static create(ncp: INixieControlPanel, chem: ChemController): NixieChemControllerBase {
|
|
148
|
-
// RKS: 06-25-21 - Keeping the homegrown around for now but I don't really know why we care.
|
|
149
164
|
let type = sys.board.valueMaps.chemControllerTypes.transform(chem.type);
|
|
150
165
|
switch (type.name) {
|
|
151
166
|
case 'intellichem':
|
|
152
167
|
return new NixieIntelliChemController(ncp, chem);
|
|
153
|
-
case 'homegrown':
|
|
154
168
|
case 'rem':
|
|
155
169
|
return new NixieChemController(ncp, chem);
|
|
156
170
|
default:
|
|
@@ -203,7 +217,7 @@ export class NixieIntelliChemController extends NixieChemControllerBase {
|
|
|
203
217
|
let address = typeof data.address !== 'undefined' ? parseInt(data.address) : chem.address;
|
|
204
218
|
let name = typeof data.name !== 'undefined' ? data.name : chem.name || `IntelliChem - ${address - 143}`;
|
|
205
219
|
let type = sys.board.valueMaps.chemControllerTypes.transformByName('intellichem');
|
|
206
|
-
// So now we are down to the nitty gritty setting the data for the REM
|
|
220
|
+
// So now we are down to the nitty gritty setting the data for the REM Chem controller.
|
|
207
221
|
let calciumHardness = typeof data.calciumHardness !== 'undefined' ? parseInt(data.calciumHardness, 10) : chem.calciumHardness;
|
|
208
222
|
let cyanuricAcid = typeof data.cyanuricAcid !== 'undefined' ? parseInt(data.cyanuricAcid, 10) : chem.cyanuricAcid;
|
|
209
223
|
let alkalinity = typeof data.alkalinity !== 'undefined' ? parseInt(data.alkalinity, 10) : chem.alkalinity;
|
|
@@ -543,6 +557,7 @@ export class NixieChemController extends NixieChemControllerBase {
|
|
|
543
557
|
else if (typeof ret.obj.state === 'boolean') v = ret.obj.state;
|
|
544
558
|
else if (typeof ret.obj.state === 'number') v = utils.makeBool(ret.obj.state);
|
|
545
559
|
else if (typeof ret.obj.state.val === 'number') v = utils.makeBool(ret.obj.state.val);
|
|
560
|
+
else if (typeof ret.obj.state.value === 'number') v = utils.makeBool(ret.obj.state.value);
|
|
546
561
|
else v = false;
|
|
547
562
|
this.flowDetected = schem.flowDetected = v;
|
|
548
563
|
}
|
|
@@ -584,7 +599,6 @@ export class NixieChemController extends NixieChemControllerBase {
|
|
|
584
599
|
await this.checkFlowAsync(schem);
|
|
585
600
|
await this.validateSetupAsync(this.chem, schem);
|
|
586
601
|
if (this.chem.ph.enabled) await this.ph.probe.setTempCompensationAsync(schem.ph.probe);
|
|
587
|
-
// We are not processing Homegrown at this point.
|
|
588
602
|
// Check each piece of equipment to make sure it is doing its thing.
|
|
589
603
|
schem.calculateSaturationIndex();
|
|
590
604
|
this.processAlarms(schem);
|
|
@@ -596,15 +610,10 @@ export class NixieChemController extends NixieChemControllerBase {
|
|
|
596
610
|
}
|
|
597
611
|
this._ispolling = false;
|
|
598
612
|
}
|
|
599
|
-
catch (err) { this._ispolling = false; logger.error(`Error polling Chem Controller - ${err}`);
|
|
613
|
+
catch (err) { this._ispolling = false; logger.error(`Error polling Chem Controller - ${err}`); }
|
|
600
614
|
finally {
|
|
601
615
|
if (!this.closing && !this._ispolling)
|
|
602
|
-
this._pollTimer = setTimeout(
|
|
603
|
-
try { await self.pollEquipmentAsync() } catch (err) {
|
|
604
|
-
//return Promise.reject(err);
|
|
605
|
-
logger.error(err);
|
|
606
|
-
}
|
|
607
|
-
}, this.pollingInterval || 10000);
|
|
616
|
+
this._pollTimer = setTimeout(() => { self.pollEquipmentAsync(); }, this.pollingInterval || 10000);
|
|
608
617
|
logger.verbose(`End polling Chem Controller ${this.id}`);
|
|
609
618
|
}
|
|
610
619
|
}
|
|
@@ -964,9 +973,8 @@ class NixieChemical extends NixieChildEquipment {
|
|
|
964
973
|
schem.chlor.isDosing = schem.pump.isDosing = false;
|
|
965
974
|
if (!this.chemical.flowOnlyMixing || (schem.chemController.isBodyOn && this.chemController.flowDetected)) {
|
|
966
975
|
if (this.chemType === 'orp' && typeof this.chemController.orp.orp.useChlorinator !== 'undefined' && this.chemController.orp.orp.useChlorinator && this.chemController.orp.orp.dosingMethod > 0) {
|
|
967
|
-
await this.chlor.stopDosing(schem, 'mixing');
|
|
968
976
|
if (state.chlorinators.getItemById(1).currentOutput !== 0) {
|
|
969
|
-
logger.debug(`Chem mixing ORP (chlorinator) paused
|
|
977
|
+
logger.debug(`Chem mixing ORP (chlorinator) paused waiting for chlor current output to be 0%. Mix time remaining: ${utils.formatDuration(schem.mixTimeRemaining)} `);
|
|
970
978
|
return;
|
|
971
979
|
}
|
|
972
980
|
}
|
|
@@ -1365,7 +1373,7 @@ export class NixieChemChlor extends NixieChildEquipment {
|
|
|
1365
1373
|
try {
|
|
1366
1374
|
await this.turnOn(schem);
|
|
1367
1375
|
if (schlor.currentOutput !== 100) {
|
|
1368
|
-
logger.
|
|
1376
|
+
logger.warn(`Chlor dose not added because current output is not 100%`);
|
|
1369
1377
|
}
|
|
1370
1378
|
else {
|
|
1371
1379
|
if (typeof dose._lastLatch !== 'undefined') {
|
|
@@ -1416,9 +1424,10 @@ export class NixieChemChlor extends NixieChildEquipment {
|
|
|
1416
1424
|
}
|
|
1417
1425
|
public async turnOff(schem: ChemicalState): Promise<ChlorinatorState> {
|
|
1418
1426
|
try {
|
|
1427
|
+
logger.info(`Turning off the chlorinator`);
|
|
1419
1428
|
let chlor = sys.chlorinators.getItemById(1);
|
|
1420
1429
|
let schlor = state.chlorinators.getItemById(1);
|
|
1421
|
-
if (schlor.currentOutput === 0 && schlor.targetOutput === 0 && !schlor.superChlor && chlor.disabled &&
|
|
1430
|
+
if (schlor.currentOutput === 0 && schlor.targetOutput === 0 && !schlor.superChlor && chlor.disabled && !chlor.isDosing) {
|
|
1422
1431
|
this.isOn = schem.chlor.isDosing = false;
|
|
1423
1432
|
return schlor;
|
|
1424
1433
|
}
|
|
@@ -1436,11 +1445,7 @@ export class NixieChemChlor extends NixieChildEquipment {
|
|
|
1436
1445
|
try {
|
|
1437
1446
|
let chlor = sys.chlorinators.getItemById(1);
|
|
1438
1447
|
let schlor = state.chlorinators.getItemById(1);
|
|
1439
|
-
|
|
1440
|
-
let poolSetpoint: number = 0, spaSetpoint: number = 0;
|
|
1441
|
-
if (chem.body === 0 || chem.body === 32) poolSetpoint = 100;
|
|
1442
|
-
if (chem.body === 1 || chem.body === 32) spaSetpoint = 100;
|
|
1443
|
-
if (schlor.currentOutput === 100 && schlor.targetOutput === 100 && !schlor.superChlor && !chlor.disabled && schlor.poolSetpoint === poolSetpoint && schlor.spaSetpoint === spaSetpoint && chlor.isDosing) {
|
|
1448
|
+
if (schlor.currentOutput === 100 && schlor.targetOutput === 100 && !schlor.superChlor && !chlor.disabled && chlor.isDosing) {
|
|
1444
1449
|
this.isOn = schem.chlor.isDosing = true;
|
|
1445
1450
|
return schlor;
|
|
1446
1451
|
}
|
|
@@ -1802,13 +1807,17 @@ export class NixieChemicalORP extends NixieChemical {
|
|
|
1802
1807
|
if (sorp.doseHistory.length) {
|
|
1803
1808
|
// if last dose was within 15 minutes, set mix time to 15 mins-lastdose
|
|
1804
1809
|
// if no dose in last 15, then we should be monitoring
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1810
|
+
if (new Date().getTime() - sorp.doseHistory[0].end.getTime() < this.chlor.chlorInterval * 60 * 1000){
|
|
1811
|
+
let lastDoseTime = sorp.doseHistory[0].timeDosed;
|
|
1812
|
+
let mixTime = Math.min(Math.max(this.chlor.chlorInterval * 60 - lastDoseTime, 0), this.chlor.chlorInterval * 60);
|
|
1813
|
+
// if (mixTime === 0) return; // due to delays with setting chlor, let the checkDosing pick up the cycle again with the chlor already on.
|
|
1814
|
+
if (sorp.dosingStatus === 0) await this.mixChemicals(sorp, mixTime);
|
|
1815
|
+
}
|
|
1809
1816
|
}
|
|
1810
|
-
else
|
|
1817
|
+
else{
|
|
1811
1818
|
if (sorp.dosingStatus === 0) await this.mixChemicals(sorp);
|
|
1819
|
+
}
|
|
1820
|
+
return;
|
|
1812
1821
|
}
|
|
1813
1822
|
else {
|
|
1814
1823
|
// Just stop the pump for now but we will do some logging later.
|
|
@@ -2030,7 +2039,6 @@ export class NixieChemicalORP extends NixieChemical {
|
|
|
2030
2039
|
percentOfTime = 1;
|
|
2031
2040
|
}
|
|
2032
2041
|
else if (sorp.demand < -20) {
|
|
2033
|
-
logger.info(`Chlor % of time should be 0%`)
|
|
2034
2042
|
await this.cancelDosing(sorp, 'demand < -20');
|
|
2035
2043
|
}
|
|
2036
2044
|
else {
|
|
@@ -2064,16 +2072,16 @@ export class NixieChemicalORP extends NixieChemical {
|
|
|
2064
2072
|
// convert the % of time back to an amount of chlorine over 15 minutes;
|
|
2065
2073
|
let time = this.chlor.chlorInterval * 60 * percentOfTime;
|
|
2066
2074
|
let dose = model.chlorinePerSec * time;
|
|
2067
|
-
|
|
2068
|
-
|
|
2075
|
+
|
|
2069
2076
|
if (dose > 0) {
|
|
2077
|
+
logger.info(`Chem chlor calculated dosing at ${Math.round(percentOfTime * 10000) / 100}% and will dose ${Math.round(dose * 1000000) / 1000000}Lbs of chlorine over the next ${utils.formatDuration(time)}.`)
|
|
2070
2078
|
sorp.startDose(new Date(), 'auto', dose, 0, time, 0);
|
|
2071
2079
|
await this.chlor.dose(sorp);
|
|
2072
2080
|
return;
|
|
2073
2081
|
}
|
|
2074
2082
|
|
|
2075
2083
|
// if none of the other conditions are true, mix
|
|
2076
|
-
await this.mixChemicals(sorp, this.chlor.chlorInterval * 60);
|
|
2084
|
+
// await this.mixChemicals(sorp, this.chlor.chlorInterval * 60);
|
|
2077
2085
|
|
|
2078
2086
|
}
|
|
2079
2087
|
else if (this.orp.setpoint > sorp.level) {
|
|
@@ -2137,6 +2145,14 @@ export class NixieChemicalORP extends NixieChemical {
|
|
|
2137
2145
|
}
|
|
2138
2146
|
catch (err) { logger.error(`checkDosing ORP: ${err.message}`); return Promise.reject(err); }
|
|
2139
2147
|
}
|
|
2148
|
+
public async deleteChlorAsync(chlor: NixieChlorinator) {
|
|
2149
|
+
logger.info(`Removing chlor ${chlor.id} from Chem Controller ${this.getParent().id}`);
|
|
2150
|
+
let schem = state.chemControllers.getItemById(this.getParent().id);
|
|
2151
|
+
this.orp.useChlorinator = false;
|
|
2152
|
+
schem.orp.useChlorinator = false;
|
|
2153
|
+
if (schem.orp.dosingStatus === 0) { await this.cancelDosing(schem.orp, 'deleting chlorinator'); }
|
|
2154
|
+
if (schem.orp.dosingStatus === 1) { await this.cancelMixing(schem.orp); }
|
|
2155
|
+
}
|
|
2140
2156
|
}
|
|
2141
2157
|
class NixieChemProbe extends NixieChildEquipment {
|
|
2142
2158
|
constructor(parent: NixieChemical) { super(parent); }
|
|
@@ -2190,7 +2206,7 @@ export class NixieChemProbePh extends NixieChemProbe {
|
|
|
2190
2206
|
// Set the current body so that it references the temperature of the current running body.
|
|
2191
2207
|
let body = sys.board.bodies.getBodyState(this.chemical.chemController.chem.body);
|
|
2192
2208
|
if (typeof body !== 'undefined' && body.isOn) {
|
|
2193
|
-
let units = sys.board.valueMaps.tempUnits.transform(
|
|
2209
|
+
let units = sys.board.valueMaps.tempUnits.transform(state.temps.units);
|
|
2194
2210
|
let obj = {};
|
|
2195
2211
|
obj[`temp${units.name.toUpperCase()}`] = body.temp;
|
|
2196
2212
|
sprobe.tempUnits = units.val;
|
|
@@ -2216,7 +2232,7 @@ export class NixieChemProbePh extends NixieChemProbe {
|
|
|
2216
2232
|
deviceBinding: this.probe.deviceBinding,
|
|
2217
2233
|
eventName: "chemController",
|
|
2218
2234
|
property: "pHLevel",
|
|
2219
|
-
sendValue: '
|
|
2235
|
+
sendValue: 'all',
|
|
2220
2236
|
isActive: data.remFeedEnabled,
|
|
2221
2237
|
sampling: 1,
|
|
2222
2238
|
changesOnly: false,
|
|
@@ -2224,7 +2240,7 @@ export class NixieChemProbePh extends NixieChemProbe {
|
|
|
2224
2240
|
}
|
|
2225
2241
|
let res = await NixieChemController.putDeviceService(this.probe.connectionId, '/config/feed', d);
|
|
2226
2242
|
if (res.status.code === 200) { this.probe.remFeedEnabled = data.remFeedEnabled; }
|
|
2227
|
-
else { logger.warn(`setRemoteREMFeed: Cannot set remote feed. Message:${JSON.stringify(res.status)} for feed: ${JSON.stringify(d)}.`);
|
|
2243
|
+
else { logger.warn(`setRemoteREMFeed: Cannot set remote feed. Message:${JSON.stringify(res.status)} for feed: ${JSON.stringify(d)}.`); }
|
|
2228
2244
|
}
|
|
2229
2245
|
catch (err) { logger.error(`setRemoteREMFeed: ${err.message}`); return Promise.reject(err); }
|
|
2230
2246
|
}
|
|
@@ -2307,11 +2323,15 @@ export class NixieChemProbeORP extends NixieChemProbe {
|
|
|
2307
2323
|
}
|
|
2308
2324
|
let res = await NixieChemController.putDeviceService(this.probe.connectionId, '/config/feed', d);
|
|
2309
2325
|
if (res.status.code === 200) { this.probe.remFeedEnabled = data.remFeedEnabled; }
|
|
2310
|
-
else {
|
|
2326
|
+
else {
|
|
2327
|
+
logger.warn(`setRemoteREMFeed: Cannot set remote feed. Message:${JSON.stringify(res.status)} for feed: ${JSON.stringify(d)}.`);
|
|
2328
|
+
// return Promise.reject(new InvalidOperationError(`Nixie could not set remote REM feed for the ORP probe.`, this.probe.dataName));
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
catch (err) {
|
|
2332
|
+
logger.error(`setRemoteREMFeed: ${err.message}`);
|
|
2333
|
+
//return Promise.reject(err); // don't muck up chem controller if we can't set the feeds.
|
|
2311
2334
|
}
|
|
2312
|
-
catch (err) { logger.error(`setRemoteREMFeed: ${err.message}`);
|
|
2313
|
-
//return Promise.reject(err); // don't muck up chem controller if we can't set the feeds.
|
|
2314
|
-
}
|
|
2315
2335
|
}
|
|
2316
2336
|
public syncRemoteREMFeeds(chem: ChemController, servers) {
|
|
2317
2337
|
// match any feeds and store the id/statusf
|
|
@@ -9,6 +9,7 @@ import { setTimeout, clearTimeout } from 'timers';
|
|
|
9
9
|
import { webApp, InterfaceServerResponse } from "../../../web/Server";
|
|
10
10
|
import { Outbound, Protocol, Response } from '../../comms/messages/Messages';
|
|
11
11
|
import { conn } from '../../comms/Comms';
|
|
12
|
+
import { ncp } from '../Nixie';
|
|
12
13
|
|
|
13
14
|
export class NixieChlorinatorCollection extends NixieEquipmentCollection<NixieChlorinator> {
|
|
14
15
|
public async deleteChlorinatorAsync(id: number) {
|
|
@@ -18,6 +19,7 @@ export class NixieChlorinatorCollection extends NixieEquipmentCollection<NixieCh
|
|
|
18
19
|
for (let i = this.length - 1; i >= 0; i--) {
|
|
19
20
|
let c = this[i];
|
|
20
21
|
if (c.id === id) {
|
|
22
|
+
await ncp.chemControllers.deleteChlorAsync(c as NixieChlorinator);
|
|
21
23
|
await c.closeAsync();
|
|
22
24
|
this.splice(i, 1);
|
|
23
25
|
}
|
|
@@ -75,7 +77,6 @@ export class NixieChlorinator extends NixieEquipment {
|
|
|
75
77
|
private _pollTimer: NodeJS.Timeout = null;
|
|
76
78
|
private superChlorinating: boolean = false;
|
|
77
79
|
private superChlorStart: number = 0;
|
|
78
|
-
private chlorinating: boolean = false;
|
|
79
80
|
public chlor: Chlorinator;
|
|
80
81
|
public bodyOnTime: number;
|
|
81
82
|
protected _suspendPolling: number = 0;
|
|
@@ -101,7 +102,7 @@ export class NixieChlorinator extends NixieEquipment {
|
|
|
101
102
|
let superChlorHours = typeof data.superChlorHours !== 'undefined' ? parseInt(data.superChlorHours, 10) : chlor.superChlorHours;
|
|
102
103
|
let disabled = typeof data.disabled !== 'undefined' ? utils.makeBool(data.disabled) : chlor.disabled;
|
|
103
104
|
let isDosing = typeof data.isDosing !== 'undefined' ? utils.makeBool(data.isDosing) : chlor.isDosing;
|
|
104
|
-
let model = typeof data.model !== 'undefined' ? data.model : chlor.model;
|
|
105
|
+
let model = typeof data.model !== 'undefined' ? data.model : chlor.model || 0;
|
|
105
106
|
if (typeof body === 'undefined') return Promise.reject(new InvalidEquipmentDataError(`Invalid body assignment`, 'chlorinator', data.body || chlor.body));
|
|
106
107
|
if (isNaN(poolSetpoint)) poolSetpoint = 0;
|
|
107
108
|
if (isNaN(spaSetpoint)) spaSetpoint = 0;
|
|
@@ -113,9 +114,8 @@ export class NixieChlorinator extends NixieEquipment {
|
|
|
113
114
|
schlor.superChlor = chlor.superChlor = superChlor;
|
|
114
115
|
schlor.superChlorHours = chlor.superChlorHours = superChlorHours;
|
|
115
116
|
schlor.type = chlor.type = chlorType;
|
|
116
|
-
chlor.body = body;
|
|
117
117
|
chlor.model = model;
|
|
118
|
-
schlor.body = chlor.body;
|
|
118
|
+
schlor.body = chlor.body = body.val;
|
|
119
119
|
chlor.disabled = disabled;
|
|
120
120
|
chlor.isDosing = isDosing;
|
|
121
121
|
schlor.name = chlor.name = data.name || chlor.name || `Chlorinator ${chlor.id}`;
|
|
@@ -178,7 +178,7 @@ export class NixieChlorinator extends NixieEquipment {
|
|
|
178
178
|
// Comms failure will be handeled by the message processor.
|
|
179
179
|
logger.error(`Chlorinator ${this.chlor.name} comms failure: ${err.message}`);
|
|
180
180
|
}
|
|
181
|
-
finally { if(!this.closing) this._pollTimer = setTimeout(
|
|
181
|
+
finally { if(!this.closing) this._pollTimer = setTimeout(() => {self.pollEquipment();}, this.pollingInterval); }
|
|
182
182
|
}
|
|
183
183
|
public async takeControl(): Promise<boolean> {
|
|
184
184
|
try {
|
|
@@ -230,7 +230,7 @@ export class NixieChlorinator extends NixieEquipment {
|
|
|
230
230
|
let setpoint = 0;
|
|
231
231
|
if (typeof body !== 'undefined') {
|
|
232
232
|
setpoint = (body.id === 1) ? this.chlor.poolSetpoint : this.chlor.spaSetpoint;
|
|
233
|
-
if (this.chlor.superChlor === true) setpoint = 100;
|
|
233
|
+
if (this.chlor.superChlor === true || this.chlor.isDosing) setpoint = 100;
|
|
234
234
|
if (this.chlor.disabled === true) setpoint = 0; // Our target should be 0 because we have other things going on. For instance,
|
|
235
235
|
// we may be dosing acid which will cause the disabled flag to be true.
|
|
236
236
|
}
|
|
@@ -251,7 +251,6 @@ export class NixieChlorinator extends NixieEquipment {
|
|
|
251
251
|
onAbort: () => {},
|
|
252
252
|
onComplete: (err) => {
|
|
253
253
|
if (err) {
|
|
254
|
-
this.chlorinating = false;
|
|
255
254
|
cstate.currentOutput = 0;
|
|
256
255
|
cstate.status = 128;
|
|
257
256
|
resolve(false);
|
|
@@ -260,7 +259,6 @@ export class NixieChlorinator extends NixieEquipment {
|
|
|
260
259
|
// The action:17 message originated from us so we will not see it in the
|
|
261
260
|
// ChlorinatorStateMessage module.
|
|
262
261
|
cstate.currentOutput = setpoint;
|
|
263
|
-
this.chlorinating = true;
|
|
264
262
|
if (!this.superChlorinating && cstate.superChlor) {
|
|
265
263
|
cstate.superChlorRemaining = cstate.superChlorHours * 3600;
|
|
266
264
|
this.superChlorStart = Math.floor(new Date().getTime() / 1000) * 1000;
|
|
@@ -308,6 +306,7 @@ export class NixieChlorinator extends NixieEquipment {
|
|
|
308
306
|
conn.queueSendMessage(out);
|
|
309
307
|
});
|
|
310
308
|
}
|
|
309
|
+
else return Promise.resolve(false);
|
|
311
310
|
} catch (err) { logger.error(`Communication error with Chlorinator ${this.chlor.name} : ${err.message}`); return Promise.reject(err);}
|
|
312
311
|
|
|
313
312
|
}
|
|
@@ -56,6 +56,12 @@ export class NixieCircuitCollection extends NixieEquipmentCollection<NixieCircui
|
|
|
56
56
|
}
|
|
57
57
|
catch (err) { logger.error(`setCircuitAsync: ${err.message}`); return Promise.reject(err); }
|
|
58
58
|
}
|
|
59
|
+
public async checkCircuitEggTimerExpirationAsync(cstate: ICircuitState) {
|
|
60
|
+
try {
|
|
61
|
+
let c: NixieCircuit = this.find(elem => elem.id === cstate.id) as NixieCircuit;
|
|
62
|
+
await c.checkCircuitEggTimerExpirationAsync(cstate);
|
|
63
|
+
} catch (err) { logger.error(`NCP: Error syncing circuit states: ${err}`); }
|
|
64
|
+
}
|
|
59
65
|
public async initAsync(circuits: CircuitCollection) {
|
|
60
66
|
try {
|
|
61
67
|
for (let i = 0; i < circuits.length; i++) {
|
|
@@ -179,6 +185,17 @@ export class NixieCircuit extends NixieEquipment {
|
|
|
179
185
|
return res;
|
|
180
186
|
} catch (err) { logger.error(`Nixie: Error setting circuit state ${cstate.id}-${cstate.name} to ${val}`); }
|
|
181
187
|
}
|
|
188
|
+
public async checkCircuitEggTimerExpirationAsync(cstate: ICircuitState) {
|
|
189
|
+
// if circuit end time is past current time, either the schedule is finished
|
|
190
|
+
// (this should already be turned off) or the egg timer has expired
|
|
191
|
+
try {
|
|
192
|
+
if (!cstate.isActive || !cstate.isOn) return;
|
|
193
|
+
if (cstate.endTime.toDate() < new Timestamp().toDate()) {
|
|
194
|
+
await sys.board.circuits.setCircuitStateAsync(cstate.id, false);
|
|
195
|
+
cstate.emitEquipmentChange();
|
|
196
|
+
}
|
|
197
|
+
} catch (err) { logger.error(`Error syncing circuit: ${err}`); }
|
|
198
|
+
}
|
|
182
199
|
private async checkHardwareStatusAsync(connectionId: string, deviceBinding: string) {
|
|
183
200
|
try {
|
|
184
201
|
let dev = await NixieEquipment.getDeviceService(connectionId, `/status/device/${deviceBinding}`);
|