nodejs-poolcontroller 7.5.1 → 7.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/ISSUE_TEMPLATE/1-bug-report.yml +84 -0
- package/.github/ISSUE_TEMPLATE/2-docs.md +12 -0
- package/.github/ISSUE_TEMPLATE/3-proposal.md +28 -0
- package/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/Changelog +19 -0
- package/Dockerfile +3 -3
- package/README.md +13 -8
- package/app.ts +1 -1
- package/config/Config.ts +38 -2
- package/config/VersionCheck.ts +27 -12
- package/controller/Constants.ts +2 -1
- package/controller/Equipment.ts +193 -9
- package/controller/Errors.ts +10 -0
- package/controller/Lockouts.ts +503 -0
- package/controller/State.ts +269 -64
- package/controller/boards/AquaLinkBoard.ts +1000 -0
- package/controller/boards/BoardFactory.ts +4 -0
- package/controller/boards/EasyTouchBoard.ts +468 -144
- package/controller/boards/IntelliCenterBoard.ts +466 -307
- package/controller/boards/IntelliTouchBoard.ts +37 -5
- package/controller/boards/NixieBoard.ts +671 -141
- package/controller/boards/SystemBoard.ts +1397 -641
- package/controller/comms/Comms.ts +462 -362
- package/controller/comms/messages/Messages.ts +174 -30
- package/controller/comms/messages/config/ChlorinatorMessage.ts +6 -3
- package/controller/comms/messages/config/CircuitMessage.ts +1 -0
- package/controller/comms/messages/config/ExternalMessage.ts +10 -8
- package/controller/comms/messages/config/HeaterMessage.ts +141 -29
- package/controller/comms/messages/config/OptionsMessage.ts +9 -2
- package/controller/comms/messages/config/PumpMessage.ts +53 -35
- package/controller/comms/messages/config/ScheduleMessage.ts +33 -25
- package/controller/comms/messages/config/ValveMessage.ts +2 -2
- package/controller/comms/messages/status/ChlorinatorStateMessage.ts +38 -86
- package/controller/comms/messages/status/EquipmentStateMessage.ts +59 -23
- package/controller/comms/messages/status/HeaterStateMessage.ts +57 -3
- package/controller/comms/messages/status/IntelliChemStateMessage.ts +56 -8
- package/controller/comms/messages/status/PumpStateMessage.ts +23 -1
- package/controller/nixie/Nixie.ts +1 -1
- package/controller/nixie/bodies/Body.ts +3 -0
- package/controller/nixie/chemistry/ChemController.ts +164 -51
- package/controller/nixie/chemistry/Chlorinator.ts +137 -88
- package/controller/nixie/circuits/Circuit.ts +51 -19
- package/controller/nixie/heaters/Heater.ts +241 -31
- package/controller/nixie/pumps/Pump.ts +488 -206
- package/controller/nixie/schedules/Schedule.ts +91 -35
- package/controller/nixie/valves/Valve.ts +1 -1
- package/defaultConfig.json +20 -0
- package/package.json +21 -21
- package/web/Server.ts +94 -49
- package/web/bindings/aqualinkD.json +505 -0
- package/web/bindings/influxDB.json +71 -1
- package/web/bindings/mqtt.json +98 -39
- package/web/bindings/mqttAlt.json +59 -1
- package/web/interfaces/baseInterface.ts +1 -0
- package/web/interfaces/httpInterface.ts +23 -2
- package/web/interfaces/influxInterface.ts +45 -10
- package/web/interfaces/mqttInterface.ts +114 -54
- package/web/services/config/Config.ts +55 -132
- package/web/services/state/State.ts +81 -4
- package/web/services/state/StateSocket.ts +4 -4
- package/web/services/utilities/Utilities.ts +8 -6
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -52
- package/config copy.json +0 -300
- package/issue_template.md +0 -52
|
@@ -19,8 +19,8 @@ import { logger } from '../../logger/Logger';
|
|
|
19
19
|
import { conn } from '../comms/Comms';
|
|
20
20
|
import { Message, Outbound, Protocol, Response } from '../comms/messages/Messages';
|
|
21
21
|
import { utils } from '../Constants';
|
|
22
|
-
import { Body, ChemController, ConfigVersion, CustomName, EggTimer, Feature, Heater, ICircuit, LightGroup, LightGroupCircuit, PoolSystem, Pump, Schedule, sys } from '../Equipment';
|
|
23
|
-
import { EquipmentTimeoutError, InvalidEquipmentDataError, InvalidEquipmentIdError } from '../Errors';
|
|
22
|
+
import { Body, ChemController, ConfigVersion, CustomName, EggTimer, Feature, Heater, ICircuit, LightGroup, LightGroupCircuit, Options, PoolSystem, Pump, Schedule, sys } from '../Equipment';
|
|
23
|
+
import { EquipmentTimeoutError, InvalidEquipmentDataError, InvalidEquipmentIdError, InvalidOperationError } from '../Errors';
|
|
24
24
|
import { ncp } from "../nixie/Nixie";
|
|
25
25
|
import { BodyTempState, ChlorinatorState, ICircuitGroupState, ICircuitState, LightGroupState, state } from '../State';
|
|
26
26
|
import { BodyCommands, byteValueMap, ChemControllerCommands, ChlorinatorCommands, CircuitCommands, ConfigQueue, ConfigRequest, EquipmentIdRange, FeatureCommands, HeaterCommands, PumpCommands, ScheduleCommands, SystemBoard, SystemCommands } from './SystemBoard';
|
|
@@ -44,7 +44,9 @@ export class EasyTouchBoard extends SystemBoard {
|
|
|
44
44
|
[0, { name: 'off', desc: 'Off' }],
|
|
45
45
|
[1, { name: 'heater', desc: 'Heater' }],
|
|
46
46
|
[2, { name: 'cooling', desc: 'Cooling' }],
|
|
47
|
-
[3, { name: 'solar', desc: 'Solar' }]
|
|
47
|
+
[3, { name: 'solar', desc: 'Solar' }],
|
|
48
|
+
[4, { name: 'hpheat', desc: 'Heatpump' }],
|
|
49
|
+
[5, { name: 'dual', desc: 'Dual'}]
|
|
48
50
|
]);
|
|
49
51
|
this.valueMaps.customNames = new byteValueMap(
|
|
50
52
|
sys.customNames.get().map((el, idx) => {
|
|
@@ -160,22 +162,25 @@ export class EasyTouchBoard extends SystemBoard {
|
|
|
160
162
|
[101, { name: 'feature8', desc: 'Feature 8' }]
|
|
161
163
|
]);
|
|
162
164
|
// We need this because there is a no-pump thing in *Touch.
|
|
163
|
-
// RKS: 05-04-21 The no-pump item was removed as this was only required for -webClient. deletePumpAsync should remove the pump from operation.
|
|
165
|
+
// RKS: 05-04-21 The no-pump item was removed as this was only required for -webClient. deletePumpAsync should remove the pump from operation. Do not use 255 as EasyTouch reports
|
|
166
|
+
// 255 or 0 for pumps that are not installed.
|
|
164
167
|
this.valueMaps.pumpTypes = new byteValueMap([
|
|
165
168
|
[1, { name: 'vf', desc: 'Intelliflo VF', maxPrimingTime: 6, minFlow: 15, maxFlow: 130, flowStepSize: 1, maxCircuits: 8, hasAddress: true }],
|
|
166
169
|
[64, { name: 'vsf', desc: 'Intelliflo VSF', minSpeed: 450, maxSpeed: 3450, speedStepSize: 10, minFlow: 15, maxFlow: 130, flowStepSize: 1, maxCircuits: 8, hasAddress: true }],
|
|
167
170
|
[65, { name: 'ds', desc: 'Two-Speed', maxCircuits: 40, hasAddress: false, hasBody: true }],
|
|
168
171
|
[128, { name: 'vs', desc: 'Intelliflo VS', maxPrimingTime: 10, minSpeed: 450, maxSpeed: 3450, speedStepSize: 10, maxCircuits: 8, hasAddress: true }],
|
|
169
172
|
[169, { name: 'vssvrs', desc: 'IntelliFlo VS+SVRS', maxPrimingTime: 6, minSpeed: 450, maxSpeed: 3450, speedStepSize: 10, maxCircuits: 8, hasAddress: true }],
|
|
170
|
-
[257, { name: 'ss', desc: 'Single Speed', maxCircuits: 0, hasAddress: false, hasBody: true, equipmentMaster: 1 }],
|
|
171
|
-
[256, { name: 'sf', desc: 'SuperFlo VS', hasAddress: false, maxCircuits: 8, maxRelays: 4, equipmentMaster: 1 }]
|
|
173
|
+
[257, { name: 'ss', desc: 'Single Speed', maxCircuits: 0, hasAddress: false, hasBody: true, equipmentMaster: 1, maxRelays: 1, relays: [{ id: 1, name: 'Pump On/Off' }] }],
|
|
174
|
+
[256, { name: 'sf', desc: 'SuperFlo VS', hasAddress: false, maxCircuits: 8, maxRelays: 4, equipmentMaster: 1, maxSpeeds: 4, relays: [{ id: 1, name: 'Program #1' }, { id: 2, name: 'Program #2' }, { id: 3, name: 'Program #3' }, { id: 4, name: 'Program #4' }] }],
|
|
175
|
+
[258, { name: 'hwrly', desc: 'Hayward Relay VS', hasAddress: false, maxCircuits: 8, maxRelays: 4, equipmentMaster: 1, maxSpeeds: 8, relays: [{ id: 1, name: 'Step #1' }, { id: 2, name: 'Step #2' }, { id: 3, name: 'Step #3' }, { id: 4, name: 'Pump On' }] }],
|
|
176
|
+
[259, { name: 'hwvs', desc: 'Hayward Eco/TriStar VS', minSpeed: 450, maxSpeed: 3450, maxCircuits: 8, hasAddress: true, equipmentMaster: 1 }]
|
|
172
177
|
]);
|
|
173
178
|
this.valueMaps.heaterTypes = new byteValueMap([
|
|
174
179
|
[0, { name: 'none', desc: 'No Heater', hasAddress: false }],
|
|
175
180
|
[1, { name: 'gas', desc: 'Gas Heater', hasAddress: false }],
|
|
176
|
-
[2, { name: 'solar', desc: 'Solar Heater', hasAddress: false }],
|
|
177
|
-
[3, { name: 'heatpump', desc: 'Heat Pump', hasAddress: true }],
|
|
178
|
-
[4, { name: 'ultratemp', desc: 'UltraTemp', hasAddress: true, hasCoolSetpoint: true }],
|
|
181
|
+
[2, { name: 'solar', desc: 'Solar Heater', hasAddress: false, hasPreference: true }],
|
|
182
|
+
[3, { name: 'heatpump', desc: 'Heat Pump', hasAddress: true, hasPreference: true }],
|
|
183
|
+
[4, { name: 'ultratemp', desc: 'UltraTemp', hasAddress: true, hasCoolSetpoint: true, hasPreference: true }],
|
|
179
184
|
[5, { name: 'hybrid', desc: 'Hybrid', hasAddress: true }],
|
|
180
185
|
[6, { name: 'maxetherm', desc: 'Max-E-Therm', hasAddress: true }],
|
|
181
186
|
[7, { name: 'mastertemp', desc: 'MasterTemp', hasAddress: true }]
|
|
@@ -296,6 +301,14 @@ export class EasyTouchBoard extends SystemBoard {
|
|
|
296
301
|
}
|
|
297
302
|
return { val: b, days: days };
|
|
298
303
|
};
|
|
304
|
+
this.valueMaps.lightCommands = new byteValueMap([
|
|
305
|
+
[128, { name: 'colorsync', desc: 'Sync', types: ['intellibrite'] }],
|
|
306
|
+
[144, { name: 'colorset', desc: 'Set', types: ['intellibrite'] }],
|
|
307
|
+
[160, { name: 'colorswim', desc: 'Swim', types: ['intellibrite'] }],
|
|
308
|
+
[190, { name: 'colorhold', desc: 'Hold', types: ['intellibrite'], sequence: 13 }],
|
|
309
|
+
[191, { name: 'colorrecall', desc: 'Recall', types: ['intellibrite'], sequence: 14 }],
|
|
310
|
+
[208, { name: 'thumper', desc: 'Thumper', types: ['magicstream'] }]
|
|
311
|
+
]);
|
|
299
312
|
this.valueMaps.lightThemes.transform = function (byte) { return extend(true, { val: byte }, this.get(byte) || this.get(255)); };
|
|
300
313
|
this.valueMaps.circuitNames.transform = function (byte) {
|
|
301
314
|
if (byte < 200) {
|
|
@@ -313,7 +326,7 @@ export class EasyTouchBoard extends SystemBoard {
|
|
|
313
326
|
[128, { val: 128, name: 'timeout', desc: 'Timeout' }],
|
|
314
327
|
[129, { val: 129, name: 'service-timeout', desc: 'Service/Timeout' }],
|
|
315
328
|
[255, { name: 'error', desc: 'System Error' }]
|
|
316
|
-
|
|
329
|
+
]);
|
|
317
330
|
this.valueMaps.expansionBoards = new byteValueMap([
|
|
318
331
|
[0, { name: 'ET28', part: 'ET2-8', desc: 'EasyTouch2 8', circuits: 8, shared: true }],
|
|
319
332
|
[1, { name: 'ET28P', part: 'ET2-8P', desc: 'EasyTouch2 8P', circuits: 8, shared: false }],
|
|
@@ -329,15 +342,18 @@ export class EasyTouchBoard extends SystemBoard {
|
|
|
329
342
|
]);
|
|
330
343
|
}
|
|
331
344
|
public initHeaterDefaults() {
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
heater.
|
|
335
|
-
heater
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
//sheater
|
|
340
|
-
|
|
345
|
+
sys.board.heaters.updateHeaterServices();
|
|
346
|
+
// RKS: 03-03-22 This is not correct. As it turns out there is a case where the only heater installed is not
|
|
347
|
+
// a gas heater. This also does not work for dual body systems.
|
|
348
|
+
//let heater = sys.heaters.getItemById(1, true);
|
|
349
|
+
//heater.isActive = true;
|
|
350
|
+
//heater.type = 1;
|
|
351
|
+
//heater.name = "Gas Heater";
|
|
352
|
+
//let sheater = state.heaters.getItemById(1, true);
|
|
353
|
+
//sheater.type = heater.type;
|
|
354
|
+
//sheater.name = heater.name;
|
|
355
|
+
////sheater.isVirtual = heater.isVirtual = false;
|
|
356
|
+
//sys.equipment.shared ? heater.body = 32 : heater.body = 0;
|
|
341
357
|
}
|
|
342
358
|
public initBodyDefaults() {
|
|
343
359
|
// Initialize the bodies. We will need these very soon.
|
|
@@ -348,10 +364,15 @@ export class EasyTouchBoard extends SystemBoard {
|
|
|
348
364
|
cbody.isActive = true;
|
|
349
365
|
// If the body doesn't represent a spa then we set the type.
|
|
350
366
|
// RSG - 10-5-21: If a single body IT (i5+3s/i9+3s) the bodies are the same; set to pool
|
|
351
|
-
|
|
367
|
+
// RKS: 04-13-22 - This is not really correct. IntelliTouch (S) models are actually shared body systems that do
|
|
368
|
+
// not have intake/return valves but there are two bodies that are named Hi-Temp (spa) and Lo-Temp (pool). This
|
|
369
|
+
// is very confusing in the control panels but I see why it is done this way. If they didn't they would need
|
|
370
|
+
// different controllers for the Indoor and Wireless controllers since the top 2 horizontal buttons are body controls.
|
|
371
|
+
//tbody.type = cbody.type = i > 1 && !sys.equipment.shared && sys.equipment.intakeReturnValves ? 1 : 0;
|
|
372
|
+
tbody.type = cbody.type = i - 1; // This will set the first body to pool/Lo-Temp and the second body to spa/Hi-Temp.
|
|
352
373
|
if (typeof cbody.name === 'undefined') {
|
|
353
374
|
let bt = sys.board.valueMaps.bodyTypes.transform(cbody.type);
|
|
354
|
-
tbody.name = cbody.name = bt.
|
|
375
|
+
tbody.name = cbody.name = bt.desc;
|
|
355
376
|
}
|
|
356
377
|
}
|
|
357
378
|
if (!sys.equipment.shared && !sys.equipment.dual && state.equipment.controllerType !== 'intellitouch') {
|
|
@@ -420,6 +441,17 @@ export class EasyTouchBoard extends SystemBoard {
|
|
|
420
441
|
let b = sys.bodies.getItemByIndex(i);
|
|
421
442
|
b.master = 0;
|
|
422
443
|
}
|
|
444
|
+
state.equipment.maxBodies = sys.equipment.maxBodies;
|
|
445
|
+
state.equipment.maxCircuitGroups = sys.equipment.maxCircuitGroups;
|
|
446
|
+
state.equipment.maxCircuits = sys.equipment.maxCircuits;
|
|
447
|
+
state.equipment.maxFeatures = sys.equipment.maxFeatures;
|
|
448
|
+
state.equipment.maxHeaters = sys.equipment.maxHeaters;
|
|
449
|
+
state.equipment.maxLightGroups = sys.equipment.maxLightGroups;
|
|
450
|
+
state.equipment.maxPumps = sys.equipment.maxPumps;
|
|
451
|
+
state.equipment.maxSchedules = sys.equipment.maxSchedules;
|
|
452
|
+
state.equipment.maxValves = sys.equipment.maxValves;
|
|
453
|
+
state.equipment.shared = sys.equipment.shared;
|
|
454
|
+
state.equipment.dual = sys.equipment.dual;
|
|
423
455
|
state.emitControllerChange();
|
|
424
456
|
}
|
|
425
457
|
public bodies: TouchBodyCommands = new TouchBodyCommands(this);
|
|
@@ -1054,8 +1086,69 @@ class TouchSystemCommands extends SystemCommands {
|
|
|
1054
1086
|
conn.queueSendMessage(out);
|
|
1055
1087
|
});
|
|
1056
1088
|
}
|
|
1089
|
+
public async setOptionsAsync(obj: any): Promise<Options> {
|
|
1090
|
+
// Proxy for setBodyAsync. See below for explanation.
|
|
1091
|
+
await sys.board.bodies.setBodyAsync(obj);
|
|
1092
|
+
if (typeof obj.clockSource !== 'undefined') {
|
|
1093
|
+
sys.general.options.clockSource = obj.clockSource;
|
|
1094
|
+
if (sys.general.options.clockSource === 'server') sys.board.system.setTZ();
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
return sys.general.options;
|
|
1098
|
+
}
|
|
1057
1099
|
}
|
|
1058
1100
|
class TouchBodyCommands extends BodyCommands {
|
|
1101
|
+
public async setBodyAsync(obj: any): Promise<Body> {
|
|
1102
|
+
// The 168 is a funky packet in *Touch because it can set:
|
|
1103
|
+
// * Intellichem Installed (byte 3, bit 1)
|
|
1104
|
+
// * Manual spa heat (byte 4, bit 1) which only applies to the spa but is a
|
|
1105
|
+
// general option
|
|
1106
|
+
// * Manual Priority (byte 5, bit 1 - Intellitouch only)
|
|
1107
|
+
// and this function can be called by either setIntelliChem (protected)
|
|
1108
|
+
// or directly from setBodyAsync (/config/body endpoint) or from setGeneralAsync (/config/options)
|
|
1109
|
+
// for Manual Priority.
|
|
1110
|
+
// We also need to return the proper body setting manual heat, but it is irrelevant
|
|
1111
|
+
// for when we are returning to chemController
|
|
1112
|
+
try {
|
|
1113
|
+
return new Promise<Body>((resolve, reject) => {
|
|
1114
|
+
let manualHeat = sys.general.options.manualHeat;
|
|
1115
|
+
let manualPriority = sys.general.options.manualPriority;
|
|
1116
|
+
if (typeof obj.manualHeat !== 'undefined') manualHeat = utils.makeBool(obj.manualHeat);
|
|
1117
|
+
if (typeof obj.manualPriority !== 'undefined') manualPriority = utils.makeBool(obj.manualPriority);
|
|
1118
|
+
let body = sys.bodies.getItemById(obj.id, false);
|
|
1119
|
+
let intellichemInstalled = sys.chemControllers.getItemByAddress(144, false).isActive;
|
|
1120
|
+
let out = Outbound.create({
|
|
1121
|
+
dest: 16,
|
|
1122
|
+
action: 168,
|
|
1123
|
+
retries: 3,
|
|
1124
|
+
response: true,
|
|
1125
|
+
onComplete: (err, msg) => {
|
|
1126
|
+
if (err) reject(err);
|
|
1127
|
+
else {
|
|
1128
|
+
sys.general.options.manualHeat = manualHeat;
|
|
1129
|
+
sys.general.options.manualPriority = manualPriority;
|
|
1130
|
+
let sbody = state.temps.bodies.getItemById(body.id, true);
|
|
1131
|
+
if (body.type === 1){ // spa
|
|
1132
|
+
body.manualHeat = manualHeat;
|
|
1133
|
+
};
|
|
1134
|
+
if (typeof obj.name !== 'undefined') body.name = sbody.name = obj.name;
|
|
1135
|
+
if (typeof obj.capacity !== 'undefined') body.capacity = parseInt(obj.capacity, 10);
|
|
1136
|
+
if (typeof obj.showInDashboard !== 'undefined') body.showInDashboard = sbody.showInDashboard = utils.makeBool(obj.showInDashboard);
|
|
1137
|
+
state.emitEquipmentChanges();
|
|
1138
|
+
resolve(body);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
});
|
|
1142
|
+
out.insertPayloadBytes(0, 0, 9);
|
|
1143
|
+
out.setPayloadByte(3, intellichemInstalled ? 255 : 254);
|
|
1144
|
+
out.setPayloadByte(4, manualHeat ? 1 : 0);
|
|
1145
|
+
out.setPayloadByte(5, manualPriority ? 1 : 0);
|
|
1146
|
+
conn.queueSendMessage(out);
|
|
1147
|
+
});
|
|
1148
|
+
|
|
1149
|
+
}
|
|
1150
|
+
catch (err) { return Promise.reject(err); }
|
|
1151
|
+
}
|
|
1059
1152
|
public async setHeatModeAsync(body: Body, mode: number): Promise<BodyTempState> {
|
|
1060
1153
|
return new Promise<BodyTempState>((resolve, reject) => {
|
|
1061
1154
|
// [16,34,136,4],[POOL HEAT Temp,SPA HEAT Temp,Heat Mode,0,2,56]
|
|
@@ -1065,6 +1158,15 @@ class TouchBodyCommands extends BodyCommands {
|
|
|
1065
1158
|
// 1 | 97 | Spa setpoint
|
|
1066
1159
|
// 2 | 7 | Pool/spa heat modes (01 = Heater spa 11 = Solar Only pool)
|
|
1067
1160
|
// 3 | 0 | Cool set point for ultratemp
|
|
1161
|
+
|
|
1162
|
+
|
|
1163
|
+
// Heat modes
|
|
1164
|
+
// 0 = Off
|
|
1165
|
+
// 1 = Heater
|
|
1166
|
+
// 2 = Solar/Heatpump Pref
|
|
1167
|
+
// 3 = Solar
|
|
1168
|
+
//
|
|
1169
|
+
|
|
1068
1170
|
const body1 = sys.bodies.getItemById(1);
|
|
1069
1171
|
const body2 = sys.bodies.getItemById(2);
|
|
1070
1172
|
const temp1 = body1.setPoint || 100;
|
|
@@ -1253,18 +1355,20 @@ class TouchBodyCommands extends BodyCommands {
|
|
|
1253
1355
|
}
|
|
1254
1356
|
}
|
|
1255
1357
|
export class TouchCircuitCommands extends CircuitCommands {
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1358
|
+
// RKS: 12-01-2021 This has been deprecated we are now driving this through metadata on the valuemaps. This allows
|
|
1359
|
+
// for multiple types of standardized on/off sequences with nixie controllers.
|
|
1360
|
+
//public getLightThemes(type?: number): any[] {
|
|
1361
|
+
// let themes = sys.board.valueMaps.lightThemes.toArray();
|
|
1362
|
+
// if (typeof type === 'undefined') return themes;
|
|
1363
|
+
// switch (type) {
|
|
1364
|
+
// case 8: // Magicstream
|
|
1365
|
+
// return themes.filter(theme => theme.types.includes('magicstream'));
|
|
1366
|
+
// case 16: // Intellibrite
|
|
1367
|
+
// return themes.filter(theme => theme.types.includes('intellibrite'));
|
|
1368
|
+
// default:
|
|
1369
|
+
// return [];
|
|
1370
|
+
// }
|
|
1371
|
+
//}
|
|
1268
1372
|
public async setCircuitAsync(data: any): Promise<ICircuit> {
|
|
1269
1373
|
try {
|
|
1270
1374
|
// example [255,0,255][165,33,16,34,139,5][17,14,209,0,0][2,120]
|
|
@@ -1336,7 +1440,7 @@ export class TouchCircuitCommands extends CircuitCommands {
|
|
|
1336
1440
|
data.functionId = sys.board.valueMaps.circuitFunctions.getValue('notused');
|
|
1337
1441
|
return this.setCircuitAsync(data);
|
|
1338
1442
|
}
|
|
1339
|
-
public async setCircuitStateAsync(id: number, val: boolean): Promise<ICircuitState> {
|
|
1443
|
+
public async setCircuitStateAsync(id: number, val: boolean, ignoreDelays?: boolean): Promise<ICircuitState> {
|
|
1340
1444
|
if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError('Circuit or Feature id not valid', id, 'Circuit'));
|
|
1341
1445
|
let c = sys.circuits.getInterfaceById(id);
|
|
1342
1446
|
if (c.master !== 0) return await super.setCircuitStateAsync(id, val);
|
|
@@ -1523,15 +1627,74 @@ export class TouchCircuitCommands extends CircuitCommands {
|
|
|
1523
1627
|
});
|
|
1524
1628
|
|
|
1525
1629
|
}
|
|
1526
|
-
public async setLightThemeAsync(id: number, theme: number) {
|
|
1630
|
+
public async setLightThemeAsync(id: number, theme: number): Promise<ICircuitState> {
|
|
1527
1631
|
// Re-route this as we cannot set individual circuit themes in *Touch.
|
|
1528
1632
|
return this.setLightGroupThemeAsync(id, theme);
|
|
1529
1633
|
}
|
|
1634
|
+
public async runLightGroupCommandAsync(obj: any): Promise<ICircuitState> {
|
|
1635
|
+
// Do all our validation.
|
|
1636
|
+
try {
|
|
1637
|
+
let id = parseInt(obj.id, 10);
|
|
1638
|
+
let cmd = typeof obj.command !== 'undefined' ? sys.board.valueMaps.lightGroupCommands.findItem(obj.command) : { val: 0, name: 'undefined' };
|
|
1639
|
+
if (cmd.val === 0) return Promise.reject(new InvalidOperationError(`Light group command ${cmd.name} does not exist`, 'runLightGroupCommandAsync'));
|
|
1640
|
+
if (isNaN(id)) return Promise.reject(new InvalidOperationError(`Light group ${id} does not exist`, 'runLightGroupCommandAsync'));
|
|
1641
|
+
let grp = sys.lightGroups.getItemById(id);
|
|
1642
|
+
let nop = sys.board.valueMaps.circuitActions.getValue(cmd.name);
|
|
1643
|
+
let sgrp = state.lightGroups.getItemById(grp.id);
|
|
1644
|
+
sgrp.action = nop;
|
|
1645
|
+
sgrp.emitEquipmentChange();
|
|
1646
|
+
switch (cmd.name) {
|
|
1647
|
+
case 'colorset':
|
|
1648
|
+
await this.sequenceLightGroupAsync(id, 'colorset');
|
|
1649
|
+
break;
|
|
1650
|
+
case 'colorswim':
|
|
1651
|
+
await this.sequenceLightGroupAsync(id, 'colorswim');
|
|
1652
|
+
break;
|
|
1653
|
+
case 'colorhold':
|
|
1654
|
+
await this.setLightGroupThemeAsync(id, 190);
|
|
1655
|
+
break;
|
|
1656
|
+
case 'colorrecall':
|
|
1657
|
+
await this.setLightGroupThemeAsync(id, 191);
|
|
1658
|
+
break;
|
|
1659
|
+
case 'lightthumper':
|
|
1660
|
+
await this.setLightGroupThemeAsync(id, 208);
|
|
1661
|
+
break;
|
|
1662
|
+
}
|
|
1663
|
+
sgrp.action = 0;
|
|
1664
|
+
sgrp.emitEquipmentChange();
|
|
1665
|
+
return sgrp;
|
|
1666
|
+
}
|
|
1667
|
+
catch (err) { return Promise.reject(`Error runLightGroupCommandAsync ${err.message}`); }
|
|
1668
|
+
}
|
|
1669
|
+
public async runLightCommandAsync(obj: any): Promise<ICircuitState> {
|
|
1670
|
+
// Do all our validation.
|
|
1671
|
+
try {
|
|
1672
|
+
let id = parseInt(obj.id, 10);
|
|
1673
|
+
let cmd = typeof obj.command !== 'undefined' ? sys.board.valueMaps.lightCommands.findItem(obj.command) : { val: 0, name: 'undefined' };
|
|
1674
|
+
if (cmd.val === 0) return Promise.reject(new InvalidOperationError(`Light command ${cmd.name} does not exist`, 'runLightCommandAsync'));
|
|
1675
|
+
if (isNaN(id)) return Promise.reject(new InvalidOperationError(`Light ${id} does not exist`, 'runLightCommandAsync'));
|
|
1676
|
+
let circ = sys.circuits.getItemById(id);
|
|
1677
|
+
if (!circ.isActive) return Promise.reject(new InvalidOperationError(`Light circuit #${id} is not active`, 'runLightCommandAsync'));
|
|
1678
|
+
let type = sys.board.valueMaps.circuitFunctions.transform(circ.type);
|
|
1679
|
+
if (!type.isLight) return Promise.reject(new InvalidOperationError(`Circuit #${id} is not a light`, 'runLightCommandAsync'));
|
|
1680
|
+
let nop = sys.board.valueMaps.circuitActions.getValue(cmd.name);
|
|
1681
|
+
let slight = state.circuits.getItemById(circ.id);
|
|
1682
|
+
slight.action = nop;
|
|
1683
|
+
slight.emitEquipmentChange();
|
|
1684
|
+
// Touch boards cannot change the theme or color of a single light.
|
|
1685
|
+
slight.action = 0;
|
|
1686
|
+
slight.emitEquipmentChange();
|
|
1687
|
+
return slight;
|
|
1688
|
+
}
|
|
1689
|
+
catch (err) { return Promise.reject(`Error runLightCommandAsync ${err.message}`); }
|
|
1690
|
+
}
|
|
1530
1691
|
public async setLightGroupThemeAsync(id = sys.board.equipmentIds.circuitGroups.start, theme: number): Promise<ICircuitState> {
|
|
1531
1692
|
return new Promise<ICircuitState>((resolve, reject) => {
|
|
1532
1693
|
const grp = sys.lightGroups.getItemById(id);
|
|
1533
1694
|
const sgrp = state.lightGroups.getItemById(id);
|
|
1534
1695
|
grp.lightingTheme = sgrp.lightingTheme = theme;
|
|
1696
|
+
sgrp.action = sys.board.valueMaps.circuitActions.getValue('lighttheme');
|
|
1697
|
+
sgrp.emitEquipmentChange();
|
|
1535
1698
|
let out = Outbound.create({
|
|
1536
1699
|
action: 96,
|
|
1537
1700
|
payload: [theme, 0],
|
|
@@ -1575,6 +1738,7 @@ export class TouchCircuitCommands extends CircuitCommands {
|
|
|
1575
1738
|
setImmediate(function () { sys.board.circuits.sequenceLightGroupAsync(grp.id, 'color'); });
|
|
1576
1739
|
// other themes for magicstream?
|
|
1577
1740
|
}
|
|
1741
|
+
sgrp.action = 0;
|
|
1578
1742
|
sgrp.hasChanged = true; // Say we are dirty but we really are pure as the driven snow.
|
|
1579
1743
|
state.emitEquipmentChanges();
|
|
1580
1744
|
resolve(sgrp);
|
|
@@ -1668,17 +1832,18 @@ class TouchFeatureCommands extends FeatureCommands {
|
|
|
1668
1832
|
class TouchChlorinatorCommands extends ChlorinatorCommands {
|
|
1669
1833
|
public async setChlorAsync(obj: any): Promise<ChlorinatorState> {
|
|
1670
1834
|
let id = parseInt(obj.id, 10);
|
|
1835
|
+
// Bail out right away if this is not controlled by the OCP.
|
|
1836
|
+
if (typeof obj.master !== 'undefined' && parseInt(obj.master, 10) !== 0) return super.setChlorAsync(obj);
|
|
1671
1837
|
let isAdd = false;
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
chlor.master = utils.makeBool(obj.master) ? 1 : 0;
|
|
1676
|
-
// Calculate an id for the chlorinator. The messed up part is that if a chlorinator is not attached to the OCP, its address
|
|
1677
|
-
// cannot be set by the MUX. This will have to wait.
|
|
1838
|
+
if (isNaN(id) || id <= 0) {
|
|
1839
|
+
// We are adding so we need to see if there is another chlorinator that is not external.
|
|
1840
|
+
if (sys.chlorinators.count(elem => elem.master !== 2) > sys.equipment.maxChlorinators) return Promise.reject(new InvalidEquipmentDataError(`The max number of chlorinators has been exceeded you may only add ${sys.equipment.maxChlorinators}`, 'chlorinator', sys.equipment.maxChlorinators));
|
|
1678
1841
|
id = 1;
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1842
|
+
isAdd = true;
|
|
1843
|
+
}
|
|
1844
|
+
let chlor = sys.chlorinators.getItemById(id);
|
|
1845
|
+
if (chlor.master !== 0 && !isAdd) return super.setChlorAsync(obj);
|
|
1846
|
+
|
|
1682
1847
|
// RKS: I am not even sure this can be done with Touch as the master on the RS485 bus.
|
|
1683
1848
|
if (typeof chlor.master === 'undefined') chlor.master = 0;
|
|
1684
1849
|
let name = obj.name || chlor.name || 'IntelliChlor' + id;
|
|
@@ -1689,8 +1854,10 @@ class TouchChlorinatorCommands extends ChlorinatorCommands {
|
|
|
1689
1854
|
let disabled = typeof obj.disabled !== 'undefined' ? utils.makeBool(obj.disabled) : chlor.disabled;
|
|
1690
1855
|
let poolSetpoint = typeof obj.poolSetpoint !== 'undefined' ? parseInt(obj.poolSetpoint, 10) : chlor.poolSetpoint;
|
|
1691
1856
|
let spaSetpoint = typeof obj.spaSetpoint !== 'undefined' ? parseInt(obj.spaSetpoint, 10) : chlor.spaSetpoint;
|
|
1692
|
-
let model = typeof obj.model !== 'undefined' ? obj.model : chlor.model;
|
|
1857
|
+
let model = typeof obj.model !== 'undefined' ? sys.board.valueMaps.chlorinatorModel.encode(obj.model) : chlor.model || 0;
|
|
1693
1858
|
let chlorType = typeof obj.type !== 'undefined' ? sys.board.valueMaps.chlorinatorType.encode(obj.type) : chlor.type || 0;
|
|
1859
|
+
let portId = typeof obj.portId !== 'undefined' ? parseInt(obj.portId, 10) : chlor.portId;
|
|
1860
|
+
if (portId !== chlor.portId && sys.chlorinators.count(elem => elem.id !== chlor.id && elem.portId === portId && elem.master !== 2) > 0) return Promise.reject(new InvalidEquipmentDataError(`Another chlorinator is installed on port #${portId}. Only one chlorinator can be installed per port.`, 'Chlorinator', portId));
|
|
1694
1861
|
if (isAdd) {
|
|
1695
1862
|
if (isNaN(poolSetpoint)) poolSetpoint = 50;
|
|
1696
1863
|
if (isNaN(spaSetpoint)) spaSetpoint = 10;
|
|
@@ -1709,7 +1876,7 @@ class TouchChlorinatorCommands extends ChlorinatorCommands {
|
|
|
1709
1876
|
let body = sys.board.bodies.mapBodyAssociation(chlor.body);
|
|
1710
1877
|
if (typeof body === 'undefined') {
|
|
1711
1878
|
if (sys.equipment.shared) body = 32;
|
|
1712
|
-
else if (!sys.equipment.dual) body =
|
|
1879
|
+
else if (!sys.equipment.dual) body = 0;
|
|
1713
1880
|
else return Promise.reject(new InvalidEquipmentDataError(`Chlorinator body association is not valid: ${body}`, 'chlorinator', body));
|
|
1714
1881
|
}
|
|
1715
1882
|
if (poolSetpoint > 100 || poolSetpoint < 0) return Promise.reject(new InvalidEquipmentDataError(`Chlorinator poolSetpoint is out of range: ${chlor.poolSetpoint}`, 'chlorinator', chlor.poolSetpoint));
|
|
@@ -1727,7 +1894,7 @@ class TouchChlorinatorCommands extends ChlorinatorCommands {
|
|
|
1727
1894
|
utils.makeBool(superChlorinate) && superChlorHours > 0 ? superChlorHours + 128 : 0, // We only want to set the superChlor when the user sends superChlor = true
|
|
1728
1895
|
0, 0, 0, 0, 0, 0, 0],
|
|
1729
1896
|
retries: 3,
|
|
1730
|
-
response: true,
|
|
1897
|
+
response: true,
|
|
1731
1898
|
// scope: Math.random(),
|
|
1732
1899
|
onComplete: (err)=>{
|
|
1733
1900
|
if (err) {
|
|
@@ -1747,7 +1914,7 @@ class TouchChlorinatorCommands extends ChlorinatorCommands {
|
|
|
1747
1914
|
if (typeof reject === 'undefined' || typeof resolve === 'undefined') return;
|
|
1748
1915
|
reject(new EquipmentTimeoutError(`no chlor response in 7 seconds`, `chlorTimeOut`));
|
|
1749
1916
|
reject = undefined;
|
|
1750
|
-
|
|
1917
|
+
|
|
1751
1918
|
}, 3000);
|
|
1752
1919
|
});
|
|
1753
1920
|
await request153packet;
|
|
@@ -1761,9 +1928,10 @@ class TouchChlorinatorCommands extends ChlorinatorCommands {
|
|
|
1761
1928
|
schlor.body = chlor.body = body;
|
|
1762
1929
|
chlor.address = 79 + id;
|
|
1763
1930
|
chlor.name = schlor.name = name;
|
|
1764
|
-
chlor.model = model;
|
|
1931
|
+
schlor.model = chlor.model = model;
|
|
1765
1932
|
schlor.type = chlor.type = chlorType;
|
|
1766
1933
|
chlor.isDosing = isDosing;
|
|
1934
|
+
chlor.portId = portId;
|
|
1767
1935
|
|
|
1768
1936
|
let request217Packet = new Promise<void>((resolve, reject) => {
|
|
1769
1937
|
let out = Outbound.create({
|
|
@@ -1792,7 +1960,7 @@ class TouchChlorinatorCommands extends ChlorinatorCommands {
|
|
|
1792
1960
|
if (typeof _timeout !== 'undefined'){
|
|
1793
1961
|
clearTimeout(_timeout);
|
|
1794
1962
|
_timeout = undefined;
|
|
1795
|
-
}
|
|
1963
|
+
}
|
|
1796
1964
|
state.emitEquipmentChanges();
|
|
1797
1965
|
return state.chlorinators.getItemById(id);
|
|
1798
1966
|
} catch (err) {
|
|
@@ -2058,13 +2226,22 @@ class TouchPumpCommands extends PumpCommands {
|
|
|
2058
2226
|
}
|
|
2059
2227
|
if (typeof type.maxCircuits !== 'undefined' && type.maxCircuits > 0 && typeof data.circuits !== 'undefined') { // This pump type supports circuits
|
|
2060
2228
|
for (let i = 1; i <= data.circuits.length && i <= type.maxCircuits; i++) {
|
|
2061
|
-
|
|
2229
|
+
// RKS: This notion of always returning the max number of circuits was misguided. It leaves gaps in the circuit definitions and makes the pump
|
|
2230
|
+
// layouts difficult when there are a variety of supported circuits. For instance with SF pumps you only get 4.
|
|
2231
|
+
let c = i >= data.circuits.length - 1 ? { speed: type.minSpeed || 0, flow: type.minFlow || 0, circuit: 0 } : data.circuits[i - 1];
|
|
2232
|
+
//let c = data.circuits[i - 1];
|
|
2062
2233
|
let speed = parseInt(c.speed, 10);
|
|
2063
2234
|
let flow = parseInt(c.flow, 10);
|
|
2064
2235
|
if (isNaN(speed)) speed = type.minSpeed;
|
|
2065
2236
|
if (isNaN(flow)) flow = type.minFlow;
|
|
2066
2237
|
outc.setPayloadByte(i * 2 + 3, parseInt(c.circuit, 10), 0);
|
|
2067
|
-
|
|
2238
|
+
let units;
|
|
2239
|
+
if (type.name === 'vf') units = sys.board.valueMaps.pumpUnits.getValue('gpm');
|
|
2240
|
+
else if (type.name === 'vs') units = sys.board.valueMaps.pumpUnits.getValue('rpm');
|
|
2241
|
+
else units = sys.board.valueMaps.pumpUnits.encode(c.units);
|
|
2242
|
+
if (isNaN(units)) units = sys.board.valueMaps.pumpUnits.getValue('rpm');
|
|
2243
|
+
c.units = units;
|
|
2244
|
+
//c.units = parseInt(c.units, 10) || type.name === 'vf' ? sys.board.valueMaps.pumpUnits.getValue('gpm') : sys.board.valueMaps.pumpUnits.getValue('rpm');
|
|
2068
2245
|
if (typeof type.minSpeed !== 'undefined' && c.units === sys.board.valueMaps.pumpUnits.getValue('rpm')) {
|
|
2069
2246
|
outc.setPayloadByte(i * 2 + 4, Math.floor(speed / 256)); // Set to rpm
|
|
2070
2247
|
outc.setPayloadByte(i + 21, speed % 256);
|
|
@@ -2180,9 +2357,11 @@ class TouchPumpCommands extends PumpCommands {
|
|
|
2180
2357
|
spump.type = pump.type;
|
|
2181
2358
|
spump.status = 0;
|
|
2182
2359
|
}
|
|
2183
|
-
public async deletePumpAsync(
|
|
2184
|
-
let id =
|
|
2185
|
-
if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError(`
|
|
2360
|
+
public async deletePumpAsync(data: any):Promise<Pump>{
|
|
2361
|
+
let id = parseInt(data.id, 10);
|
|
2362
|
+
if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError(`deletePumpAsync: Pump ${id} is not valid.`, 0, `pump`));
|
|
2363
|
+
let pump = sys.pumps.getItemById(id, false);
|
|
2364
|
+
if (pump.master === 1) return super.deletePumpAsync(data);
|
|
2186
2365
|
const outc = Outbound.create({
|
|
2187
2366
|
action: 155,
|
|
2188
2367
|
payload: [id, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
@@ -2273,26 +2452,146 @@ class TouchHeaterCommands extends HeaterCommands {
|
|
|
2273
2452
|
let id = typeof obj.id === 'undefined' ? -1 : parseInt(obj.id, 10);
|
|
2274
2453
|
if (isNaN(id)) return reject(new InvalidEquipmentIdError('Heater Id is not valid.', obj.id, 'Heater'));
|
|
2275
2454
|
let heater: Heater;
|
|
2455
|
+
let address: number;
|
|
2456
|
+
let out = Outbound.create({
|
|
2457
|
+
action: 162,
|
|
2458
|
+
payload: [5, 0, 0],
|
|
2459
|
+
retries: 2,
|
|
2460
|
+
// I am assuming that there should be an action 34 when the 162 is sent but I do not have this
|
|
2461
|
+
// data.
|
|
2462
|
+
response: Response.create({ dest: -1, action: 34 })
|
|
2463
|
+
});
|
|
2464
|
+
let htype;
|
|
2276
2465
|
if (id <= 0) {
|
|
2277
|
-
//
|
|
2278
|
-
|
|
2279
|
-
|
|
2466
|
+
// Touch only supports two installed heaters. So the type determines the id.
|
|
2467
|
+
if (sys.heaters.length > sys.equipment.maxHeaters) return reject(new InvalidEquipmentDataError('The maximum number of heaters are already installed.', 'Heater', sys.heaters.length));
|
|
2468
|
+
htype = sys.board.valueMaps.heaterTypes.findItem(obj.type);
|
|
2469
|
+
if (typeof htype === 'undefined') return reject(new InvalidEquipmentDataError('Heater type is not valid.', 'Heater', obj.heaterType));
|
|
2470
|
+
// Check to see if we can find any heaters of this type already installed.
|
|
2471
|
+
if (sys.heaters.count(h => h.type === htype.val) > 0) return reject(new InvalidEquipmentDataError(`Only one ${htype.desc} heater can be installed`, 'Heater', htype));
|
|
2472
|
+
// Next we need to see if this heater is compatible with all the other heaters. For Touch you may only have the following combos.
|
|
2473
|
+
// 1 Gas + 1 Solar
|
|
2474
|
+
// 1 Gas + 1 Heatpump
|
|
2475
|
+
// 1 Hybrid
|
|
2476
|
+
|
|
2477
|
+
// Heater ids are as follows.
|
|
2478
|
+
// 1 = Gas Heater
|
|
2479
|
+
// 2 = Solar
|
|
2480
|
+
// 3 = UltraTemp (HEATPUMPCOM)
|
|
2481
|
+
// 4 = UltraTemp ETi (Hybrid)
|
|
2482
|
+
switch (htype.name) {
|
|
2483
|
+
case 'gas':
|
|
2484
|
+
id = 1;
|
|
2485
|
+
break;
|
|
2486
|
+
case 'solar':
|
|
2487
|
+
out.setPayloadByte(0, out.payload[0] | 0x02);
|
|
2488
|
+
// Set the start and stop temp delta.
|
|
2489
|
+
out.setPayloadByte(1, (obj.freeze ? 0x80 : 0x00) | (obj.coolingEnabled ? 0x20 : 0x00));
|
|
2490
|
+
out.setPayloadByte(2, ((obj.startTempDelta || 6) - 3 << 6) | ((obj.stopTempDelta || 3) - 2 << 1));
|
|
2491
|
+
id = 2;
|
|
2492
|
+
break;
|
|
2493
|
+
case 'ultratemp':
|
|
2494
|
+
case 'heatpump':
|
|
2495
|
+
address = 112;
|
|
2496
|
+
out.setPayloadByte(0, out.payload[0] | 0x02);
|
|
2497
|
+
out.setPayloadByte(1, out.payload[1] | 0x10 | (obj.coolingEnabled ? 0x20 : 0x00));
|
|
2498
|
+
id = 3;
|
|
2499
|
+
break;
|
|
2500
|
+
case 'hybrid':
|
|
2501
|
+
// If we are adding a hybrid heater this means that the gas heater is to be replaced. This means that only
|
|
2502
|
+
// a gas heater can be installed.
|
|
2503
|
+
if (sys.heaters.length > 1) return reject(new InvalidEquipmentDataError(`Hybrid heaters can only be installed by themselves`, 'Heater', htype));
|
|
2504
|
+
if (sys.heaters.getItemByIndex(0).type > 1) return reject(new InvalidEquipmentDataError(`Hybrid heaters can only replace the gas heater`, 'Heater', htype));
|
|
2505
|
+
out.setPayloadByte(0, 5);
|
|
2506
|
+
out.setPayloadByte(1, 16);
|
|
2507
|
+
// NOTE: byte 2 makes absolutely no sense. Perhaps this is because we have no idea what message action 16 is. This probably contains the rest of the info
|
|
2508
|
+
// for heaters on Touch panels.
|
|
2509
|
+
out.setPayloadByte(2, 118);
|
|
2510
|
+
id = 4;
|
|
2511
|
+
break;
|
|
2512
|
+
}
|
|
2280
2513
|
}
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2514
|
+
else {
|
|
2515
|
+
// This all works because there are 0 items that can be set on a Touch heater with the exception of a few items on solar. This means that the
|
|
2516
|
+
// first two bytes are calculated based upon the existing heaters.
|
|
2517
|
+
heater = sys.heaters.find(x => id === x.id);
|
|
2518
|
+
if (typeof heater === 'undefined') return reject(new InvalidEquipmentIdError(`Heater #${id} is not installed and cannot be updated.`, id, 'Heater'));
|
|
2519
|
+
// So here we go with the settings.
|
|
2520
|
+
htype = sys.board.valueMaps.heaterTypes.findItem(heater.type);
|
|
2521
|
+
switch (htype.name) {
|
|
2522
|
+
case 'gas':
|
|
2523
|
+
break;
|
|
2524
|
+
case 'solar':
|
|
2525
|
+
out.setPayloadByte(0, out.payload[0] | 0x02);
|
|
2526
|
+
// Set the start and stop temp delta.
|
|
2527
|
+
out.setPayloadByte(1, (obj.freeze ? 0x80 : 0x00) | (obj.coolingEnabled ? 0x20 : 0x00));
|
|
2528
|
+
out.setPayloadByte(2, ((obj.startTempDelta || 6) - 3 << 6) | ((obj.stopTempDelta || 3) - 2 << 1));
|
|
2529
|
+
break;
|
|
2530
|
+
case 'ultratemp':
|
|
2531
|
+
case 'heatpump':
|
|
2532
|
+
address = 112;
|
|
2533
|
+
out.setPayloadByte(0, out.payload[0] | 0x02);
|
|
2534
|
+
out.setPayloadByte(1, out.payload[1] | 0x10 | (obj.coolingEnabled ? 0x20 : 0x00));
|
|
2535
|
+
break;
|
|
2536
|
+
case 'hybrid':
|
|
2537
|
+
address = 112;
|
|
2538
|
+
out.setPayloadByte(0, 5);
|
|
2539
|
+
out.setPayloadByte(1, 16);
|
|
2540
|
+
// NOTE: byte 2 makes absolutely no sense. Perhaps this is because we have no idea what message action 144/16 is. This probably contains the rest of the info
|
|
2541
|
+
// for heaters on Touch panels.
|
|
2542
|
+
out.setPayloadByte(2, 118);
|
|
2543
|
+
break;
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2546
|
+
// Set the bytes from the existing installed heaters.
|
|
2547
|
+
for (let i = 0; i < sys.heaters.length; i++) {
|
|
2548
|
+
let h = sys.heaters.getItemByIndex(i);
|
|
2549
|
+
if (h.id === id) continue;
|
|
2550
|
+
let ht = sys.board.valueMaps.heaterTypes.transform(h.type);
|
|
2551
|
+
switch (ht.name) {
|
|
2552
|
+
case 'gas':
|
|
2553
|
+
break;
|
|
2554
|
+
case 'solar':
|
|
2555
|
+
out.setPayloadByte(0, out.payload[0] | 0x02);
|
|
2556
|
+
out.setPayloadByte(1, (h.freeze ? 0x80 : 0x00) | (h.coolingEnabled ? 0x20 : 0x00));
|
|
2557
|
+
out.setPayloadByte(2, ((h.startTempDelta || 6) - 3 << 6) | ((h.stopTempDelta || 3) - 2 << 1));
|
|
2558
|
+
break;
|
|
2559
|
+
case 'ultratemp':
|
|
2560
|
+
case 'heatpump':
|
|
2561
|
+
out.setPayloadByte(0, out.payload[0] | 0x02);
|
|
2562
|
+
out.setPayloadByte(1, out.payload[1] | 0x10 | (h.coolingEnabled ? 0x20 : 0x00));
|
|
2563
|
+
break;
|
|
2564
|
+
case 'hybrid':
|
|
2565
|
+
break;
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2568
|
+
out.onComplete = (err, msg) => {
|
|
2569
|
+
if (err) reject(err);
|
|
2570
|
+
else {
|
|
2571
|
+
heater = sys.heaters.getItemById(id, true);
|
|
2572
|
+
let sheater = state.heaters.getItemById(id, true);
|
|
2573
|
+
for (var s in obj) {
|
|
2574
|
+
switch (s) {
|
|
2575
|
+
case 'id':
|
|
2576
|
+
case 'name':
|
|
2577
|
+
case 'type':
|
|
2578
|
+
case 'address':
|
|
2579
|
+
break;
|
|
2580
|
+
default:
|
|
2581
|
+
heater[s] = obj[s];
|
|
2582
|
+
break;
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2585
|
+
sheater.name = heater.name = typeof obj.name !== 'undefined' ? obj.name : heater.name;
|
|
2586
|
+
sheater.type = heater.type = htype.val;
|
|
2587
|
+
heater.address = address;
|
|
2588
|
+
heater.master = 0;
|
|
2589
|
+
heater.body = sys.equipment.shared ? 32 : 0;
|
|
2590
|
+
sys.board.heaters.updateHeaterServices();
|
|
2591
|
+
sys.board.heaters.syncHeaterStates();
|
|
2592
|
+
resolve(heater);
|
|
2286
2593
|
}
|
|
2287
2594
|
}
|
|
2288
|
-
let hstate = state.heaters.getItemById(id, true);
|
|
2289
|
-
|
|
2290
|
-
hstate.name = heater.name;
|
|
2291
|
-
hstate.type = heater.type;
|
|
2292
|
-
heater.master = 1;
|
|
2293
|
-
sys.board.heaters.updateHeaterServices();
|
|
2294
|
-
sys.board.heaters.syncHeaterStates();
|
|
2295
|
-
resolve(heater);
|
|
2296
2595
|
});
|
|
2297
2596
|
}
|
|
2298
2597
|
public async deleteHeaterAsync(obj: any): Promise<Heater> {
|
|
@@ -2315,45 +2614,64 @@ class TouchHeaterCommands extends HeaterCommands {
|
|
|
2315
2614
|
let heatPumpInstalled = htypes.heatpump > 0;
|
|
2316
2615
|
let ultratempInstalled = htypes.ultratemp > 0;
|
|
2317
2616
|
let gasHeaterInstalled = htypes.gas > 0;
|
|
2617
|
+
let hybridInstalled = htypes.hybrid > 0;
|
|
2318
2618
|
sys.board.valueMaps.heatModes.set(0, { name: 'off', desc: 'Off' });
|
|
2319
2619
|
sys.board.valueMaps.heatSources.set(0, { name: 'off', desc: 'Off' });
|
|
2320
|
-
if (
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
//
|
|
2326
|
-
sys.board.valueMaps.heatModes.
|
|
2327
|
-
sys.board.valueMaps.
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
sys.board.valueMaps.
|
|
2332
|
-
sys.board.valueMaps.heatSources.set(5, { name: '
|
|
2333
|
-
sys.board.valueMaps.heatSources.set(
|
|
2334
|
-
|
|
2335
|
-
else if (heatPumpInstalled && gasHeaterInstalled) {
|
|
2336
|
-
sys.board.valueMaps.heatModes.set(2, { name: 'heatpumppref', desc: 'Heat Pump Preferred' });
|
|
2337
|
-
sys.board.valueMaps.heatModes.set(3, { name: 'heatpump', desc: 'Heat Pump Only' });
|
|
2338
|
-
sys.board.valueMaps.heatSources.set(5, { name: 'heatpumppref', desc: 'Heat Pump Preferred' });
|
|
2339
|
-
sys.board.valueMaps.heatSources.set(21, { name: 'heatpump', desc: 'Heat Pump Only' });
|
|
2340
|
-
}
|
|
2341
|
-
else if (ultratempInstalled && gasHeaterInstalled) {
|
|
2342
|
-
sys.board.valueMaps.heatModes.merge([
|
|
2343
|
-
[2, { name: 'ultratemppref', desc: 'UltraTemp Pref' }],
|
|
2344
|
-
[3, { name: 'ultratemp', desc: 'UltraTemp Only' }]
|
|
2345
|
-
]);
|
|
2346
|
-
sys.board.valueMaps.heatSources.merge([
|
|
2347
|
-
[5, { name: 'ultratemppref', desc: 'Ultratemp Pref', hasCoolSetpoint: htypes.hasCoolSetpoint }],
|
|
2348
|
-
[21, { name: 'ultratemp', desc: 'Ultratemp Only', hasCoolSetpoint: htypes.hasCoolSetpoint }]
|
|
2349
|
-
])
|
|
2620
|
+
if (hybridInstalled) {
|
|
2621
|
+
// Source Issue #390
|
|
2622
|
+
// 1 = Heat Pump
|
|
2623
|
+
// 2 = Gas Heater
|
|
2624
|
+
// 3 = Hybrid
|
|
2625
|
+
// 16 = Dual
|
|
2626
|
+
sys.board.valueMaps.heatModes.set(1, { name: 'heatpump', desc: 'Heat Pump' });
|
|
2627
|
+
sys.board.valueMaps.heatModes.set(2, { name: 'heater', desc: 'Gas Heat' });
|
|
2628
|
+
sys.board.valueMaps.heatModes.set(3, { name: 'heatpumppref', desc: 'Hybrid' });
|
|
2629
|
+
sys.board.valueMaps.heatModes.set(16, { name: 'dual', desc: 'Dual Heat' });
|
|
2630
|
+
|
|
2631
|
+
sys.board.valueMaps.heatSources.set(2, { name: 'heater', desc: 'Gas Heat' });
|
|
2632
|
+
sys.board.valueMaps.heatSources.set(5, { name: 'heatpumppref', desc: 'Hybrid' });
|
|
2633
|
+
sys.board.valueMaps.heatSources.set(20, { name: 'dual', desc: 'Dual Heat' });
|
|
2634
|
+
sys.board.valueMaps.heatSources.set(21, { name: 'heatpump', desc: 'Heat Pump' });
|
|
2350
2635
|
}
|
|
2351
2636
|
else {
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2637
|
+
if (gasHeaterInstalled) {
|
|
2638
|
+
sys.board.valueMaps.heatModes.set(1, { name: 'heater', desc: 'Heater' });
|
|
2639
|
+
sys.board.valueMaps.heatSources.set(2, { name: 'heater', desc: 'Heater' });
|
|
2640
|
+
}
|
|
2641
|
+
else {
|
|
2642
|
+
// no heaters (virtual controller)
|
|
2643
|
+
sys.board.valueMaps.heatModes.delete(1);
|
|
2644
|
+
sys.board.valueMaps.heatSources.delete(2);
|
|
2645
|
+
}
|
|
2646
|
+
if (solarInstalled && gasHeaterInstalled) {
|
|
2647
|
+
sys.board.valueMaps.heatModes.set(2, { name: 'solarpref', desc: 'Solar Preferred' });
|
|
2648
|
+
sys.board.valueMaps.heatModes.set(3, { name: 'solar', desc: 'Solar Only' });
|
|
2649
|
+
sys.board.valueMaps.heatSources.set(5, { name: 'solarpref', desc: 'Solar Preferred' });
|
|
2650
|
+
sys.board.valueMaps.heatSources.set(21, { name: 'solar', desc: 'Solar Only' });
|
|
2651
|
+
}
|
|
2652
|
+
else if (heatPumpInstalled && gasHeaterInstalled) {
|
|
2653
|
+
sys.board.valueMaps.heatModes.set(2, { name: 'heatpumppref', desc: 'Heat Pump Preferred' });
|
|
2654
|
+
sys.board.valueMaps.heatModes.set(3, { name: 'heatpump', desc: 'Heat Pump Only' });
|
|
2655
|
+
sys.board.valueMaps.heatSources.set(5, { name: 'heatpumppref', desc: 'Heat Pump Preferred' });
|
|
2656
|
+
sys.board.valueMaps.heatSources.set(21, { name: 'heatpump', desc: 'Heat Pump Only' });
|
|
2657
|
+
}
|
|
2658
|
+
else if (ultratempInstalled && gasHeaterInstalled) {
|
|
2659
|
+
sys.board.valueMaps.heatModes.merge([
|
|
2660
|
+
[2, { name: 'ultratemppref', desc: 'UltraTemp Pref' }],
|
|
2661
|
+
[3, { name: 'ultratemp', desc: 'UltraTemp Only' }]
|
|
2662
|
+
]);
|
|
2663
|
+
sys.board.valueMaps.heatSources.merge([
|
|
2664
|
+
[5, { name: 'ultratemppref', desc: 'Ultratemp Pref', hasCoolSetpoint: htypes.hasCoolSetpoint }],
|
|
2665
|
+
[21, { name: 'ultratemp', desc: 'Ultratemp Only', hasCoolSetpoint: htypes.hasCoolSetpoint }]
|
|
2666
|
+
])
|
|
2667
|
+
}
|
|
2668
|
+
else {
|
|
2669
|
+
// only gas
|
|
2670
|
+
sys.board.valueMaps.heatModes.delete(2);
|
|
2671
|
+
sys.board.valueMaps.heatModes.delete(3);
|
|
2672
|
+
sys.board.valueMaps.heatSources.delete(5);
|
|
2673
|
+
sys.board.valueMaps.heatSources.delete(21);
|
|
2674
|
+
}
|
|
2357
2675
|
}
|
|
2358
2676
|
sys.board.valueMaps.heatSources.set(32, { name: 'nochange', desc: 'No Change' });
|
|
2359
2677
|
this.setActiveTempSensors();
|
|
@@ -2430,6 +2748,8 @@ class TouchChemControllerCommands extends ChemControllerCommands {
|
|
|
2430
2748
|
chem.orp.tank.capacity = 6;
|
|
2431
2749
|
let acidTankLevel = typeof data.ph !== 'undefined' && typeof data.ph.tank !== 'undefined' && typeof data.ph.tank.level !== 'undefined' ? parseInt(data.ph.tank.level, 10) : schem.ph.tank.level;
|
|
2432
2750
|
let orpTankLevel = typeof data.orp !== 'undefined' && typeof data.orp.tank !== 'undefined' && typeof data.orp.tank.level !== 'undefined' ? parseInt(data.orp.tank.level, 10) : schem.orp.tank.level;
|
|
2751
|
+
// OCP needs to set the IntelliChem as active so it knows that it exists
|
|
2752
|
+
|
|
2433
2753
|
return new Promise<ChemController>((resolve, reject) => {
|
|
2434
2754
|
let out = Outbound.create({
|
|
2435
2755
|
action: 211,
|
|
@@ -2450,7 +2770,7 @@ class TouchChemControllerCommands extends ChemControllerCommands {
|
|
|
2450
2770
|
chem.cyanuricAcid = cyanuricAcid;
|
|
2451
2771
|
chem.alkalinity = alkalinity;
|
|
2452
2772
|
chem.borates = borates;
|
|
2453
|
-
chem.body = schem.body = body;
|
|
2773
|
+
chem.body = schem.body = body.val;
|
|
2454
2774
|
schem.isActive = chem.isActive = true;
|
|
2455
2775
|
chem.lsiRange.enabled = lsiRange.enabled;
|
|
2456
2776
|
chem.lsiRange.low = lsiRange.low;
|
|
@@ -2467,7 +2787,8 @@ class TouchChemControllerCommands extends ChemControllerCommands {
|
|
|
2467
2787
|
chem.address = schem.address = address;
|
|
2468
2788
|
chem.name = schem.name = name;
|
|
2469
2789
|
chem.flowSensor.enabled = false;
|
|
2470
|
-
|
|
2790
|
+
sys.board.bodies.setBodyAsync(sys.bodies.getItemById(1, false))
|
|
2791
|
+
.then(()=>{resolve(chem)});
|
|
2471
2792
|
}
|
|
2472
2793
|
}
|
|
2473
2794
|
});
|
|
@@ -2491,45 +2812,48 @@ class TouchChemControllerCommands extends ChemControllerCommands {
|
|
|
2491
2812
|
public async deleteChemControllerAsync(data: any): Promise<ChemController> {
|
|
2492
2813
|
let id = typeof data.id !== 'undefined' ? parseInt(data.id, 10) : -1;
|
|
2493
2814
|
if (typeof id === 'undefined' || isNaN(id)) return Promise.reject(new InvalidEquipmentIdError(`Invalid Chem Controller Id`, id, 'chemController'));
|
|
2494
|
-
let chem = sys.chemControllers.
|
|
2815
|
+
let chem = sys.board.chemControllers.findChemController(data);
|
|
2495
2816
|
if (chem.master === 1) return super.deleteChemControllerAsync(data);
|
|
2496
2817
|
return new Promise<ChemController>((resolve, reject) => {
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2818
|
+
let out = Outbound.create({
|
|
2819
|
+
action: 211,
|
|
2820
|
+
response: Response.create({ protocol: Protocol.IntelliChem, action: 1, payload: [211] }),
|
|
2821
|
+
retries: 3,
|
|
2822
|
+
payload: [],
|
|
2823
|
+
onComplete: (err) => {
|
|
2824
|
+
if (err) { reject(err); }
|
|
2825
|
+
else {
|
|
2826
|
+
let schem = state.chemControllers.getItemById(id);
|
|
2827
|
+
chem.isActive = false;
|
|
2828
|
+
chem.ph.tank.capacity = chem.orp.tank.capacity = 6;
|
|
2829
|
+
chem.ph.tank.units = chem.orp.tank.units = '';
|
|
2830
|
+
schem.isActive = false;
|
|
2831
|
+
sys.board.bodies.setBodyAsync(sys.bodies.getItemById(1, false))
|
|
2832
|
+
.then(()=>{
|
|
2833
|
+
sys.chemControllers.removeItemById(id);
|
|
2834
|
+
state.chemControllers.removeItemById(id);
|
|
2835
|
+
resolve(chem);
|
|
2836
|
+
})
|
|
2837
|
+
.catch(()=>{reject(err);});
|
|
2514
2838
|
}
|
|
2515
|
-
}
|
|
2516
|
-
// I think this payload should delete the controller on Touch.
|
|
2517
|
-
out.insertPayloadBytes(0, 0, 22);
|
|
2518
|
-
out.setPayloadByte(0, chem.address - 144);
|
|
2519
|
-
out.setPayloadByte(1, Math.floor((chem.ph.setpoint * 100) / 256) || 0);
|
|
2520
|
-
out.setPayloadByte(2, Math.round((chem.ph.setpoint * 100) % 256) || 0);
|
|
2521
|
-
out.setPayloadByte(3, Math.floor(chem.orp.setpoint / 256) || 0);
|
|
2522
|
-
out.setPayloadByte(4, Math.round(chem.orp.setpoint % 256) || 0);
|
|
2523
|
-
out.setPayloadByte(5, 0);
|
|
2524
|
-
out.setPayloadByte(6, 0);
|
|
2525
|
-
out.setPayloadByte(7, Math.floor(chem.calciumHardness / 256) || 0);
|
|
2526
|
-
out.setPayloadByte(8, Math.round(chem.calciumHardness % 256) || 0);
|
|
2527
|
-
out.setPayloadByte(9, chem.cyanuricAcid || 0);
|
|
2528
|
-
out.setPayloadByte(11, Math.floor(chem.alkalinity / 256) || 0);
|
|
2529
|
-
out.setPayloadByte(12, Math.round(chem.alkalinity % 256) || 0);
|
|
2530
|
-
out.setPayloadByte(13, 20);
|
|
2531
|
-
conn.queueSendMessage(out);
|
|
2839
|
+
}
|
|
2532
2840
|
});
|
|
2841
|
+
// I think this payload should delete the controller on Touch.
|
|
2842
|
+
out.insertPayloadBytes(0, 0, 22);
|
|
2843
|
+
out.setPayloadByte(0, chem.address - 144 || 0);
|
|
2844
|
+
out.setPayloadByte(1, Math.floor((chem.ph.setpoint * 100) / 256) || 0);
|
|
2845
|
+
out.setPayloadByte(2, Math.round((chem.ph.setpoint * 100) % 256) || 0);
|
|
2846
|
+
out.setPayloadByte(3, Math.floor(chem.orp.setpoint / 256) || 0);
|
|
2847
|
+
out.setPayloadByte(4, Math.round(chem.orp.setpoint % 256) || 0);
|
|
2848
|
+
out.setPayloadByte(5, 0);
|
|
2849
|
+
out.setPayloadByte(6, 0);
|
|
2850
|
+
out.setPayloadByte(7, Math.floor(chem.calciumHardness / 256) || 0);
|
|
2851
|
+
out.setPayloadByte(8, Math.round(chem.calciumHardness % 256) || 0);
|
|
2852
|
+
out.setPayloadByte(9, chem.cyanuricAcid || 0);
|
|
2853
|
+
out.setPayloadByte(11, Math.floor(chem.alkalinity / 256) || 0);
|
|
2854
|
+
out.setPayloadByte(12, Math.round(chem.alkalinity % 256) || 0);
|
|
2855
|
+
out.setPayloadByte(13, 20);
|
|
2856
|
+
conn.queueSendMessage(out);
|
|
2857
|
+
});
|
|
2533
2858
|
}
|
|
2534
|
-
|
|
2535
2859
|
}
|