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.
Files changed (55) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
  2. package/Changelog +3 -0
  3. package/README.md +2 -2
  4. package/app.ts +2 -0
  5. package/config/Config.ts +3 -0
  6. package/config/VersionCheck.ts +8 -4
  7. package/controller/Equipment.ts +89 -29
  8. package/controller/Errors.ts +14 -1
  9. package/controller/State.ts +75 -31
  10. package/controller/boards/EasyTouchBoard.ts +81 -36
  11. package/controller/boards/IntelliCenterBoard.ts +96 -32
  12. package/controller/boards/IntelliTouchBoard.ts +103 -29
  13. package/controller/boards/NixieBoard.ts +79 -27
  14. package/controller/boards/SystemBoard.ts +1552 -822
  15. package/controller/comms/Comms.ts +84 -9
  16. package/controller/comms/messages/Messages.ts +10 -4
  17. package/controller/comms/messages/config/ChlorinatorMessage.ts +13 -4
  18. package/controller/comms/messages/config/CircuitGroupMessage.ts +6 -0
  19. package/controller/comms/messages/config/CoverMessage.ts +1 -0
  20. package/controller/comms/messages/config/EquipmentMessage.ts +4 -0
  21. package/controller/comms/messages/config/ExternalMessage.ts +43 -25
  22. package/controller/comms/messages/config/FeatureMessage.ts +8 -1
  23. package/controller/comms/messages/config/GeneralMessage.ts +8 -0
  24. package/controller/comms/messages/config/HeaterMessage.ts +10 -9
  25. package/controller/comms/messages/config/IntellichemMessage.ts +4 -1
  26. package/controller/comms/messages/config/OptionsMessage.ts +13 -1
  27. package/controller/comms/messages/config/PumpMessage.ts +4 -20
  28. package/controller/comms/messages/config/RemoteMessage.ts +4 -0
  29. package/controller/comms/messages/config/ScheduleMessage.ts +11 -0
  30. package/controller/comms/messages/config/SecurityMessage.ts +1 -0
  31. package/controller/comms/messages/config/ValveMessage.ts +12 -2
  32. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +2 -3
  33. package/controller/comms/messages/status/EquipmentStateMessage.ts +74 -22
  34. package/controller/comms/messages/status/HeaterStateMessage.ts +15 -6
  35. package/controller/comms/messages/status/IntelliChemStateMessage.ts +37 -26
  36. package/controller/nixie/Nixie.ts +18 -16
  37. package/controller/nixie/chemistry/ChemController.ts +57 -37
  38. package/controller/nixie/chemistry/Chlorinator.ts +7 -8
  39. package/controller/nixie/circuits/Circuit.ts +17 -0
  40. package/controller/nixie/pumps/Pump.ts +49 -24
  41. package/controller/nixie/schedules/Schedule.ts +1 -1
  42. package/defaultConfig.json +15 -0
  43. package/issue_template.md +1 -1
  44. package/logger/DataLogger.ts +37 -22
  45. package/package.json +3 -1
  46. package/web/Server.ts +515 -27
  47. package/web/bindings/influxDB.json +35 -0
  48. package/web/bindings/mqtt.json +62 -3
  49. package/web/bindings/mqttAlt.json +57 -4
  50. package/web/interfaces/httpInterface.ts +2 -0
  51. package/web/interfaces/influxInterface.ts +3 -2
  52. package/web/interfaces/mqttInterface.ts +12 -1
  53. package/web/services/config/Config.ts +162 -37
  54. package/web/services/state/State.ts +47 -3
  55. package/web/services/state/StateSocket.ts +1 -1
@@ -38,6 +38,12 @@ export class IntelliCenterBoard extends SystemBoard {
38
38
  this.equipmentIds.features.start = 129;
39
39
  this.equipmentIds.circuitGroups.start = 193;
40
40
  this.equipmentIds.virtualCircuits.start = 237;
41
+ this.valueMaps.panelModes = new byteValueMap([
42
+ [0, { val: 0, name: 'auto', desc: 'Auto' }],
43
+ [1, { val: 1, name: 'service', desc: 'Service' }],
44
+ [8, { val: 8, name: 'freeze', desc: 'Freeze' }],
45
+ [255, { name: 'error', desc: 'System Error' }]
46
+ ]);
41
47
  this.valueMaps.circuitFunctions = new byteValueMap([
42
48
  [0, { name: 'generic', desc: 'Generic' }],
43
49
  [1, { name: 'spillway', desc: 'Spillway' }],
@@ -59,12 +65,12 @@ export class IntelliCenterBoard extends SystemBoard {
59
65
  [2, { name: 'ds', desc: 'Two Speed', maxCircuits: 8, hasAddress: false, hasBody:true }],
60
66
  [3, { name: 'vs', desc: 'Intelliflo VS', maxPrimingTime: 6, minSpeed: 450, maxSpeed: 3450, maxCircuits: 8, hasAddress: true }],
61
67
  [4, { name: 'vsf', desc: 'Intelliflo VSF', minSpeed: 450, maxSpeed: 3450, minFlow: 15, maxFlow: 130, maxCircuits: 8, hasAddress: true }],
62
- [5, { name: 'vf', desc: 'Intelliflo VF', minFlow: 15, maxFlow: 130, maxCircuits: 8, hasAddress: true }],
68
+ [5, { name: 'vf', desc: 'Intelliflo VF', maxPrimingTime: 6, minFlow: 15, maxFlow: 130, maxCircuits: 8, hasAddress: true }],
63
69
  [100, {name: 'sf', desc: 'SuperFlo VS', hasAddress: false, maxCircuits: 8, maxRelays: 4, equipmentMaster: 1}]
64
70
  ]);
65
71
  // RSG - same as systemBoard definition; can delete.
66
72
  this.valueMaps.heatModes = new byteValueMap([
67
- [0, { name: 'off', desc: 'Off' }],
73
+ [1, { name: 'off', desc: 'Off' }],
68
74
  [3, { name: 'heater', desc: 'Heater' }],
69
75
  [5, { name: 'solar', desc: 'Solar Only' }],
70
76
  [12, { name: 'solarpref', desc: 'Solar Preferred' }]
@@ -207,8 +213,9 @@ export class IntelliCenterBoard extends SystemBoard {
207
213
  [1, { name: 'heater', desc: 'Heater' }],
208
214
  [2, { name: 'solar', desc: 'Solar' }],
209
215
  [3, { name: 'cooling', desc: 'Cooling' }],
216
+ [6, { name: 'mtheat', desc: 'Heater' }],
210
217
  [4, { name: 'hpheat', desc: 'Heating' }],
211
- [8, { name: 'hpcool', desc: 'Cooling'}]
218
+ [8, { name: 'hpcool', desc: 'Cooling' }]
212
219
  ]);
213
220
  this.valueMaps.scheduleTypes = new byteValueMap([
214
221
  [0, { name: 'runonce', desc: 'Run Once', startDate: true, startTime: true, endTime: true, days: false, heatSource: true, heatSetpoint: true }],
@@ -1389,6 +1396,19 @@ class IntelliCenterSystemCommands extends SystemCommands {
1389
1396
  }
1390
1397
  class IntelliCenterCircuitCommands extends CircuitCommands {
1391
1398
  declare board: IntelliCenterBoard;
1399
+ // Need to override this as IntelliCenter manages all the egg timers for all circuit types.
1400
+ public async checkEggTimerExpirationAsync() {
1401
+ try {
1402
+ for (let i = 0; i < sys.circuits.length; i++) {
1403
+ let c = sys.circuits.getItemByIndex(i);
1404
+ let cstate = state.circuits.getItemByIndex(i);
1405
+ if (!cstate.isActive || !cstate.isOn) continue;
1406
+ if (c.master === 1) {
1407
+ await ncp.circuits.checkCircuitEggTimerExpirationAsync(cstate);
1408
+ }
1409
+ }
1410
+ } catch (err) { logger.error(`checkEggTimerExpiration: Error synchronizing circuit relays ${err.message}`); }
1411
+ }
1392
1412
  public async setCircuitAsync(data: any): Promise<ICircuit> {
1393
1413
  let id = parseInt(data.id, 10);
1394
1414
  let circuit = sys.circuits.getItemById(id, false);
@@ -1405,7 +1425,7 @@ class IntelliCenterCircuitCommands extends CircuitCommands {
1405
1425
  let eggHrs = Math.floor(eggTimer / 60);
1406
1426
  let eggMins = eggTimer - (eggHrs * 60);
1407
1427
  let type = typeof data.type !== 'undefined' ? parseInt(data.type, 10) : circuit.type;
1408
- let theme = typeof data.lightingTheme !== 'undefined' ? data.ligthingTheme : circuit.lightingTheme;
1428
+ let theme = typeof data.lightingTheme !== 'undefined' ? data.lightingTheme : circuit.lightingTheme;
1409
1429
  if (circuit.type === 9) theme = typeof data.level !== 'undefined' ? data.level : circuit.level;
1410
1430
  if (typeof theme === 'undefined') theme = 0;
1411
1431
  let out = Outbound.create({
@@ -1425,10 +1445,18 @@ class IntelliCenterCircuitCommands extends CircuitCommands {
1425
1445
  circuit.freeze = (typeof data.freeze !== 'undefined' ? utils.makeBool(data.freeze) : circuit.freeze);
1426
1446
  circuit.showInFeatures = (typeof data.showInFeatures !== 'undefined' ? utils.makeBool(data.showInFeatures) : circuit.showInFeatures);
1427
1447
  if (type === 9) scircuit.level = circuit.level = theme;
1428
- else scircuit.lightingTheme = circuit.lightingTheme = theme;
1429
- scircuit.lightingTheme = circuit.name = typeof data.name !== 'undefined' ? data.name.toString().substring(0, 16) : circuit.name;
1448
+ else {
1449
+ let t = sys.board.valueMaps.circuitFunctions.transform(type);
1450
+ if (t.isLight == true) scircuit.lightingTheme = circuit.lightingTheme = theme;
1451
+ else {
1452
+ scircuit.lightingTheme = undefined;
1453
+ circuit.lightingTheme = 0;
1454
+ }
1455
+ }
1456
+ scircuit.name = circuit.name = typeof data.name !== 'undefined' ? data.name.toString().substring(0, 16) : circuit.name;
1430
1457
  scircuit.type = circuit.type = type;
1431
1458
  scircuit.isActive = circuit.isActive = true;
1459
+ circuit.master = 0;
1432
1460
  resolve(circuit);
1433
1461
  }
1434
1462
  }
@@ -2057,6 +2085,14 @@ class IntelliCenterCircuitCommands extends CircuitCommands {
2057
2085
  // NOT SURE IF COINCIDENTAL: The ICP seems to respond immediately after action 2.
2058
2086
  // 7. ICP Sends 168[15,0,... new options, 0,0,0,0]
2059
2087
  // 8. OCP responds ACK(168)
2088
+ // i10D turn on pool
2089
+ // OCP
2090
+ // Schedule on
2091
+ // [255, 0, 255][165, 1, 15, 16, 168, 36][15, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 1][5, 226]
2092
+ // No schedules
2093
+ // [255, 0, 255][165, 1, 15, 16, 168, 36][15, 0, 0, 38, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 1, 0][5, 195]
2094
+ // njsPC
2095
+ // [255, 0, 255][165, 1, 15, 33, 168, 36][15, 0, 0, 33, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0][5, 216]
2060
2096
 
2061
2097
  // The previous sequence is just additional noise on the bus. There is no need for it. We just
2062
2098
  // need to send the set circuit message. It will reliably work 100% of the time but the ICP
@@ -2066,6 +2102,9 @@ class IntelliCenterCircuitCommands extends CircuitCommands {
2066
2102
  //if (b) b = await this.getConfigAsync([15, 0]);
2067
2103
  return new Promise<ICircuitState>((resolve, reject) => {
2068
2104
  let out = this.createCircuitStateMessage(id, val);
2105
+ //if (sys.equipment.dual && id === 6) out.setPayloadByte(35, 1);
2106
+ out.setPayloadByte(34, 1);
2107
+ out.source = 16;
2069
2108
  out.onComplete = async (err, msg: Inbound) => {
2070
2109
  if (err) reject(err);
2071
2110
  else {
@@ -2299,7 +2338,19 @@ class IntelliCenterCircuitCommands extends CircuitCommands {
2299
2338
  let ndx = Math.floor(ordinal / 8);
2300
2339
  let byte = out.payload[ndx + 15];
2301
2340
  let bit = ordinal - (ndx * 8);
2302
- if (sched.isOn) byte = byte | (1 << bit);
2341
+ // Lets determine if this schedule should be on.
2342
+ if (sched.circuit === id) {
2343
+ if (isOn) {
2344
+ let dt = state.time.toDate();
2345
+ let dow = dt.getDay();
2346
+ // Convert the dow to the bit value.
2347
+ let sd = sys.board.valueMaps.scheduleDays.toArray().find(elem => elem.dow === dow);
2348
+ let dayVal = sd.bitVal || sd.val; // The bitval allows mask overrides.
2349
+ let ts = dt.getHours() * 60 + dt.getMinutes();
2350
+ if ((sched.scheduleDays & dayVal) > 0 && ts >= sched.startTime && ts <= sched.endTime) byte = byte | (1 << bit);
2351
+ }
2352
+ }
2353
+ else if (sched.isOn) byte = byte | (1 << bit);
2303
2354
  out.payload[ndx + 15] = byte;
2304
2355
  }
2305
2356
  return out;
@@ -2429,12 +2480,10 @@ class IntelliCenterChlorinatorCommands extends ChlorinatorCommands {
2429
2480
  public async setChlorAsync(obj: any): Promise<ChlorinatorState> {
2430
2481
  let id = parseInt(obj.id, 10);
2431
2482
  let isAdd = false;
2432
- let isVirtual = false;
2433
- if (id <= 0 || isNaN(id)) id = 1;
2434
2483
  let chlor = sys.chlorinators.getItemById(id);
2435
- if (id < 0 || isNaN(id)) {
2484
+ if (id <= 0 || isNaN(id)) {
2436
2485
  isAdd = true;
2437
- chlor.master = utils.makeBool(obj.isVirtual) ? 0 : 1;
2486
+ chlor.master = utils.makeBool(obj.master) ? 1 : 0;
2438
2487
  // Calculate an id for the chlorinator. The messed up part is that if a chlorinator is not attached to the OCP, its address
2439
2488
  // cannot be set by the MUX. This will have to wait.
2440
2489
  id = 1;
@@ -2443,14 +2492,20 @@ class IntelliCenterChlorinatorCommands extends ChlorinatorCommands {
2443
2492
  //let chlor = extend(true, {}, sys.chlorinators.getItemById(id).get(), obj);
2444
2493
  // If this is a virtual chlorinator then go to the base class and handle it from there.
2445
2494
  if (chlor.master === 1) return super.setChlorAsync(obj);
2495
+ if (typeof chlor.master === 'undefined') chlor.master = 0;
2446
2496
  let name = obj.name || chlor.name || 'IntelliChlor' + id;
2447
- let poolSetpoint = parseInt(obj.poolSetpoint, 10);
2448
- let spaSetpoint = parseInt(obj.spaSetpoint, 10);
2449
2497
  let superChlorHours = parseInt(obj.superChlorHours, 10);
2450
2498
  if (typeof obj.superChlorinate !== 'undefined') obj.superChlor = utils.makeBool(obj.superChlorinate);
2451
2499
  let superChlorinate = typeof obj.superChlor === 'undefined' ? undefined : utils.makeBool(obj.superChlor);
2452
- let disabled = typeof obj.disabled !== 'undefined' ? utils.makeBool(obj.disabled) : chlor.disabled;
2453
2500
  let isDosing = typeof obj.isDosing !== 'undefined' ? utils.makeBool(obj.isDosing) : chlor.isDosing;
2501
+ let disabled = typeof obj.disabled !== 'undefined' ? utils.makeBool(obj.disabled) : chlor.disabled;
2502
+ // This should never never never modify the setpoints based upon the disabled or isDosing flags.
2503
+ //let poolSetpoint = isDosing ? 100 : disabled ? 0 : parseInt(obj.poolSetpoint, 10);
2504
+ //let spaSetpoint = isDosing ? 100 : disabled ? 0 : parseInt(obj.spaSetpoint, 10);
2505
+ let poolSetpoint = typeof obj.poolSetpoint !== 'undefined' ? parseInt(obj.poolSetpoint, 10) : chlor.poolSetpoint;
2506
+ let spaSetpoint = typeof obj.spaSetpoint !== 'undefined' ? parseInt(obj.spaSetpoint, 10) : chlor.spaSetpoint;
2507
+ if (poolSetpoint === 0) console.log(obj);
2508
+
2454
2509
  let model = typeof obj.model !== 'undefined' ? obj.model : chlor.model;
2455
2510
  let chlorType = typeof obj.type !== 'undefined' ? sys.board.valueMaps.chlorinatorType.encode(obj.type) : chlor.type || 0;
2456
2511
  if (isAdd) {
@@ -2476,7 +2531,10 @@ class IntelliCenterChlorinatorCommands extends ChlorinatorCommands {
2476
2531
  return new Promise<ChlorinatorState>((resolve, reject) => {
2477
2532
  let out = Outbound.create({
2478
2533
  action: 168,
2479
- payload: [7, 0, id - 1, body.val, 1, disabled ? 0 : isDosing ? 100 : poolSetpoint, disabled ? 0 : isDosing ? 100 : spaSetpoint, superChlorinate ? 1 : 0, superChlorHours, 0, 1],
2534
+ payload: [7, 0, id - 1, body.val, 1,
2535
+ disabled ? 0 : isDosing ? 100 : poolSetpoint,
2536
+ disabled ? 0 : isDosing ? 100 : spaSetpoint,
2537
+ superChlorinate ? 1 : 0, superChlorHours, 0, 1],
2480
2538
  response: IntelliCenterBoard.getAckResponse(168),
2481
2539
  retries: 5,
2482
2540
  onComplete: (err, msg) => {
@@ -2504,21 +2562,22 @@ class IntelliCenterChlorinatorCommands extends ChlorinatorCommands {
2504
2562
  }
2505
2563
  public async deleteChlorAsync(obj: any): Promise<ChlorinatorState> {
2506
2564
  let id = parseInt(obj.id, 10);
2507
- if (isNaN(id)) obj.id = 1;
2508
-
2509
- // Merge all the information.
2510
- let chlor = state.chlorinators.getItemById(id);
2565
+ if (isNaN(id)) return Promise.reject(new InvalidEquipmentDataError(`Chlorinator id is not valid: ${obj.id}`, 'chlorinator', obj.id));
2566
+ let chlor = sys.chlorinators.getItemById(id);
2567
+ if (chlor.master === 1) return await super.deleteChlorAsync(obj);
2568
+ let schlor = state.chlorinators.getItemById(id);
2511
2569
  // Verify the data.
2512
2570
  return new Promise<ChlorinatorState>((resolve, reject) => {
2513
2571
  let out = Outbound.create({
2514
2572
  action: 168,
2515
- payload: [7, 0, id - 1, chlor.body || 0, 0, chlor.poolSetpoint || 0, chlor.spaSetpoint || 0, 0, chlor.superChlorHours || 0, 0, 0],
2573
+ payload: [7, 0, id - 1, schlor.body || 0, 0, schlor.poolSetpoint || 0, schlor.spaSetpoint || 0, 0, schlor.superChlorHours || 0, 0, 0],
2516
2574
  response: IntelliCenterBoard.getAckResponse(168),
2517
2575
  retries: 5,
2518
2576
  onComplete: (err, msg) => {
2519
2577
  if (err) reject(err);
2520
2578
  else {
2521
- let schlor = state.chlorinators.getItemById(id, true);
2579
+ ncp.chlorinators.deleteChlorinatorAsync(id).then(()=>{});
2580
+ schlor = state.chlorinators.getItemById(id, true);
2522
2581
  state.chlorinators.removeItemById(id);
2523
2582
  sys.chlorinators.removeItemById(id);
2524
2583
  resolve(schlor);
@@ -2607,7 +2666,7 @@ class IntelliCenterPumpCommands extends PumpCommands {
2607
2666
  let id = (typeof data.id === 'undefined' || data.id <= 0) ? sys.pumps.getNextEquipmentId(sys.board.equipmentIds.pumps) : parseInt(data.id, 10);
2608
2667
  if (isNaN(id)) return Promise.reject(new Error(`Invalid pump id: ${data.id}`));
2609
2668
  let pump = sys.pumps.getItemById(id, false);
2610
- if (data.master > 0 || pump.master > 0 || pump.isVirtual) return await super.setPumpAsync(data);
2669
+ if (data.master > 0 || pump.master > 0) return await super.setPumpAsync(data);
2611
2670
 
2612
2671
  // 0 6 10 11 12 15
2613
2672
  //[255, 0, 255][165, 63, 15, 16, 168, 34][4, 0, 0, 3, 0, 96, 194, 1, 122, 13, 15, 130, 1, 196, 9, 128, 2, 255, 5, 0, 251, 128, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0][11, 218]
@@ -3186,6 +3245,8 @@ class IntelliCenterScheduleCommands extends ScheduleCommands {
3186
3245
  out.retries = 5;
3187
3246
  out.onComplete = (err, msg) => {
3188
3247
  if (!err) {
3248
+ sched = sys.schedules.getItemById(id, true);
3249
+ ssched = state.schedules.getItemById(id, true);
3189
3250
  sched.circuit = ssched.circuit = circuit;
3190
3251
  sched.scheduleDays = ssched.scheduleDays = schedDays;
3191
3252
  sched.scheduleType = ssched.scheduleType = schedType;
@@ -3282,14 +3343,14 @@ class IntelliCenterHeaterCommands extends HeaterCommands {
3282
3343
  conn.queueSendMessage(out);
3283
3344
  }
3284
3345
  public async setHeaterAsync(obj: any): Promise<Heater> {
3285
- if (utils.makeBool(obj.isVirtual) || parseInt(obj.id, 10) > 255) return super.setHeaterAsync(obj);
3346
+ if (obj.master === 1 || parseInt(obj.id, 10) > 255) return super.setHeaterAsync(obj);
3286
3347
  return new Promise<Heater>((resolve, reject) => {
3287
3348
  let id = typeof obj.id === 'undefined' ? -1 : parseInt(obj.id, 10);
3288
3349
  if (isNaN(id)) return reject(new InvalidEquipmentIdError('Heater Id is not valid.', obj.id, 'Heater'));
3289
3350
  let heater: Heater;
3290
3351
  if (id <= 0) {
3291
3352
  // We are adding a heater. In this case all heaters are virtual.
3292
- let vheaters = sys.heaters.filter(h => h.isVirtual);
3353
+ let vheaters = sys.heaters.filter(h => h.master === 1);
3293
3354
  id = vheaters.length + 1;
3294
3355
  }
3295
3356
  heater = sys.heaters.getItemById(id, false);
@@ -3397,7 +3458,6 @@ class IntelliCenterHeaterCommands extends HeaterCommands {
3397
3458
  heater.economyTime = economyTime;
3398
3459
  heater.startTempDelta = startTempDelta;
3399
3460
  heater.stopTempDelta = stopTempDelta;
3400
- //hstate.isVirtual = heater.isVirtual = false;
3401
3461
  heater.cooldownDelay = cooldownDelay;
3402
3462
  sys.board.heaters.updateHeaterServices();
3403
3463
  sys.board.heaters.syncHeaterStates();
@@ -3410,7 +3470,7 @@ class IntelliCenterHeaterCommands extends HeaterCommands {
3410
3470
  });
3411
3471
  }
3412
3472
  public async deleteHeaterAsync(obj): Promise<Heater> {
3413
- if (utils.makeBool(obj.isVirtual) || obj.master === 1 || parseInt(obj.id, 10) > 255) return await super.deleteHeaterAsync(obj);
3473
+ if (obj.master === 1 || parseInt(obj.id, 10) > 255) return await super.deleteHeaterAsync(obj);
3414
3474
  return new Promise<Heater>((resolve, reject) => {
3415
3475
  let id = parseInt(obj.id, 10);
3416
3476
  if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError('Cannot delete. Heater Id is not valid.', obj.id, 'Heater'));
@@ -3452,6 +3512,8 @@ class IntelliCenterHeaterCommands extends HeaterCommands {
3452
3512
  let heatPumpInstalled = htypes.heatpump > 0;
3453
3513
  let gasHeaterInstalled = htypes.gas > 0;
3454
3514
  let ultratempInstalled = htypes.ultratemp > 0;
3515
+ let mastertempInstalled = htypes.mastertemp > 0;
3516
+
3455
3517
 
3456
3518
  // RKS: 09-26-20 This is a hack to maintain backward compatability with fw versions 1.04 and below. Ultratemp is not
3457
3519
  // supported on 1.04 and below.
@@ -3475,9 +3537,10 @@ class IntelliCenterHeaterCommands extends HeaterCommands {
3475
3537
  // 3 = Solar Heater
3476
3538
  // 4 = Solar Preferred
3477
3539
  // 5 = Heat Pump
3478
-
3540
+
3479
3541
  if (sys.heaters.length > 0) sys.board.valueMaps.heatSources = new byteValueMap([[1, { name: 'off', desc: 'Off' }]]);
3480
3542
  if (gasHeaterInstalled) sys.board.valueMaps.heatSources.merge([[2, { name: 'heater', desc: 'Heater' }]]);
3543
+ if (mastertempInstalled) sys.board.valueMaps.heatSources.merge([[11, { name: 'mtheater', desc: 'MasterTemp' }]]);
3481
3544
  if (solarInstalled && (gasHeaterInstalled || heatPumpInstalled)) sys.board.valueMaps.heatSources.merge([[3, { name: 'solar', desc: 'Solar Only', hasCoolSetpoint: htypes.hasCoolSetpoint }], [4, { name: 'solarpref', desc: 'Solar Preferred', hasCoolSetpoint: htypes.hasCoolSetpoint }]]);
3482
3545
  else if (solarInstalled) sys.board.valueMaps.heatSources.merge([[3, { name: 'solar', desc: 'Solar', hasCoolsetpoint: htypes.hasCoolSetpoint }]]);
3483
3546
  if (heatPumpInstalled && (gasHeaterInstalled || solarInstalled)) sys.board.valueMaps.heatSources.merge([[9, { name: 'heatpump', desc: 'Heatpump Only' }], [25, { name: 'heatpumppref', desc: 'Heat Pump Pref' }]]);
@@ -3488,11 +3551,12 @@ class IntelliCenterHeaterCommands extends HeaterCommands {
3488
3551
 
3489
3552
  sys.board.valueMaps.heatModes = new byteValueMap([[1, { name: 'off', desc: 'Off' }]]);
3490
3553
  if (gasHeaterInstalled) sys.board.valueMaps.heatModes.merge([[2, { name: 'heater', desc: 'Heater' }]]);
3491
- if (solarInstalled && (gasHeaterInstalled || heatPumpInstalled)) sys.board.valueMaps.heatModes.merge([[3, { name: 'solar', desc: 'Solar Only' }], [4, { name: 'solarpref', desc: 'Solar Preferred' }]]);
3554
+ if (mastertempInstalled) sys.board.valueMaps.heatModes.merge([11, { name: 'mtheater', desc: 'MasterTemp' }]);
3555
+ if (solarInstalled && (gasHeaterInstalled || heatPumpInstalled || mastertempInstalled)) sys.board.valueMaps.heatModes.merge([[3, { name: 'solar', desc: 'Solar Only' }], [4, { name: 'solarpref', desc: 'Solar Preferred' }]]);
3492
3556
  else if (solarInstalled) sys.board.valueMaps.heatModes.merge([[3, { name: 'solar', desc: 'Solar' }]]);
3493
- if (ultratempInstalled && (gasHeaterInstalled || heatPumpInstalled)) sys.board.valueMaps.heatModes.merge([[5, { name: 'ultratemp', desc: 'UltraTemp Only'}], [6, { name: 'ultratemppref', desc: 'UltraTemp Pref' }]]);
3557
+ if (ultratempInstalled && (gasHeaterInstalled || heatPumpInstalled || mastertempInstalled)) sys.board.valueMaps.heatModes.merge([[5, { name: 'ultratemp', desc: 'UltraTemp Only' }], [6, { name: 'ultratemppref', desc: 'UltraTemp Pref' }]]);
3494
3558
  else if (ultratempInstalled) sys.board.valueMaps.heatModes.merge([[5, { name: 'ultratemp', desc: 'UltraTemp' }]]);
3495
- if (heatPumpInstalled && (gasHeaterInstalled || solarInstalled)) sys.board.valueMaps.heatModes.merge([[9, { name: 'heatpump', desc: 'Heatpump Only' }], [25, { name: 'heatpumppref', desc: 'Heat Pump Preferred' }]]);
3559
+ if (heatPumpInstalled && (gasHeaterInstalled || solarInstalled || mastertempInstalled)) sys.board.valueMaps.heatModes.merge([[9, { name: 'heatpump', desc: 'Heatpump Only' }], [25, { name: 'heatpumppref', desc: 'Heat Pump Preferred' }]]);
3496
3560
  else if (heatPumpInstalled) sys.board.valueMaps.heatModes.merge([[9, { name: 'heatpump', desc: 'Heat Pump' }]]);
3497
3561
  }
3498
3562
  else {
@@ -3524,7 +3588,7 @@ class IntelliCenterHeaterCommands extends HeaterCommands {
3524
3588
  }
3525
3589
  class IntelliCenterValveCommands extends ValveCommands {
3526
3590
  public async setValveAsync(obj?: any): Promise<Valve> {
3527
- if (obj.isVirtual || obj.master === 1) return super.setValveAsync(obj);
3591
+ if (obj.master === 1) return super.setValveAsync(obj);
3528
3592
  let id = parseInt(obj.id, 10);
3529
3593
  if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError('Valve Id has not been defined', obj.id, 'Valve'));
3530
3594
  let valve = sys.valves.getItemById(id);
@@ -3574,7 +3638,7 @@ export class IntelliCenterChemControllerCommands extends ChemControllerCommands
3574
3638
  // Now lets do all our validation to the incoming chem controller data.
3575
3639
  let name = typeof data.name !== 'undefined' ? data.name : chem.name || `IntelliChem - ${address - 143}`;
3576
3640
  let type = sys.board.valueMaps.chemControllerTypes.transformByName('intellichem');
3577
- // So now we are down to the nitty gritty setting the data for the REM or Homegrown Chem controller.
3641
+ // So now we are down to the nitty gritty setting the data for the REM Chem controller.
3578
3642
  let calciumHardness = typeof data.calciumHardness !== 'undefined' ? parseInt(data.calciumHardness, 10) : chem.calciumHardness;
3579
3643
  let cyanuricAcid = typeof data.cyanuricAcid !== 'undefined' ? parseInt(data.cyanuricAcid, 10) : chem.cyanuricAcid;
3580
3644
  let alkalinity = typeof data.alkalinity !== 'undefined' ? parseInt(data.alkalinity, 10) : chem.alkalinity;
@@ -14,16 +14,17 @@ GNU Affero General Public License for more details.
14
14
  You should have received a copy of the GNU Affero General Public License
15
15
  along with this program. If not, see <http://www.gnu.org/licenses/>.
16
16
  */
17
- import {byteValueMap} from './SystemBoard';
18
- import {logger} from '../../logger/Logger';
17
+ import { byteValueMap } from './SystemBoard';
18
+ import { logger } from '../../logger/Logger';
19
19
  import { EasyTouchBoard, TouchConfigQueue, GetTouchConfigCategories, TouchCircuitCommands } from './EasyTouchBoard';
20
20
  import { state, ICircuitGroupState } from '../State';
21
- import { PoolSystem, sys, ExpansionPanel, ExpansionModule } from '../Equipment';
22
- import { Protocol, Outbound, Message, Response } from '../comms/messages/Messages';
21
+ import { PoolSystem, sys, ExpansionPanel, Equipment } from '../Equipment';
22
+
23
+ import { conn } from '../comms/Comms';
24
+ import { InvalidEquipmentDataError } from '../Errors';
23
25
 
24
- import {conn} from '../comms/Comms';
25
26
  export class IntelliTouchBoard extends EasyTouchBoard {
26
- constructor (system: PoolSystem){
27
+ constructor(system: PoolSystem) {
27
28
  super(system);
28
29
  this.equipmentIds.features.start = 41;
29
30
  this.equipmentIds.features.end = 50;
@@ -32,10 +33,11 @@ export class IntelliTouchBoard extends EasyTouchBoard {
32
33
  [0, { name: 'IT5', part: 'i5+3', desc: 'IntelliTouch i5+3', circuits: 6, shared: true }],
33
34
  [1, { name: 'IT7', part: 'i7+3', desc: 'IntelliTouch i7+3', circuits: 8, shared: true }],
34
35
  [2, { name: 'IT9', part: 'i9+3', desc: 'IntelliTouch i9+3', circuits: 10, shared: true }],
35
- [3, { name: 'IT5S', part: 'i5+3S', desc: 'IntelliTouch i5+3S', circuits: 5, shared: false }],
36
- [4, { name: 'IT9S', part: 'i9+3S', desc: 'IntelliTouch i9+3S', circuits: 9, shared: false }],
36
+ [3, { name: 'IT5S', part: 'i5+3S', desc: 'IntelliTouch i5+3S', circuits: 5, shared: false, bodies: 1, intakeReturnValves: false }],
37
+ [4, { name: 'IT9S', part: 'i9+3S', desc: 'IntelliTouch i9+3S', circuits: 9, shared: false, bodies: 1, intakeReturnValves: false }],
37
38
  [5, { name: 'IT10D', part: 'i10D', desc: 'IntelliTouch i10D', circuits: 10, shared: false, dual: true }],
38
- [32, { name: 'IT10X', part: 'i10X', desc: 'IntelliTouch i10X', circuits: 10, shared: false }]
39
+ [32, { name: 'IT5X', part: 'i5X', desc: 'IntelliTouch i5X', circuits: 5 }],
40
+ [33, { name: 'IT10X', part: 'i10X', desc: 'IntelliTouch i10X', circuits: 10 }]
39
41
  ]);
40
42
  }
41
43
  public initExpansionModules(byte1: number, byte2: number) {
@@ -48,6 +50,7 @@ export class IntelliTouchBoard extends EasyTouchBoard {
48
50
  mod.type = byte1;
49
51
  mod.part = mt.part;
50
52
  let eq = sys.equipment;
53
+ let bd = sys.board;
51
54
  let md = mod.get();
52
55
 
53
56
  eq.maxBodies = md.bodies = typeof mt.bodies !== 'undefined' ? mt.bodies : mt.shared || mt.dual ? 2 : 1;
@@ -57,16 +60,18 @@ export class IntelliTouchBoard extends EasyTouchBoard {
57
60
  eq.maxPumps = md.maxPumps = typeof mt.pumps !== 'undefined' ? mt.pumps : 8;
58
61
  eq.shared = mt.shared;
59
62
  eq.dual = typeof mt.dual !== 'undefined' ? mt.dual : false;
63
+ eq.intakeReturnValves = md.intakeReturnValves = typeof mt.intakeReturnValves !== 'undefined' ? mt.intakeReturnValves : false;
60
64
  eq.maxChlorinators = md.chlorinators = 1;
61
65
  eq.maxChemControllers = md.chemControllers = 1;
62
66
  eq.maxCustomNames = 20;
63
67
  eq.maxCircuitGroups = 10; // Not sure why this is 10 other than to allow for those that we are in control of.
64
68
 
65
69
  // Calculate out the invalid ids.
66
- sys.board.equipmentIds.invalidIds.set([]);
67
- if (!eq.shared) sys.board.equipmentIds.invalidIds.merge([1]);
70
+ // sys.board.equipmentIds.invalidIds.set([]);
68
71
  // Add in all the invalid ids from the base personality board.
69
72
  sys.board.equipmentIds.invalidIds.set([16, 17, 18]); // These appear to alway be invalid in IntelliTouch.
73
+ // RGS 10-7-21: Since single bodies have hi-temp/lo-temp we will always want ID 1.
74
+ // if (!eq.shared) sys.board.equipmentIds.invalidIds.merge([1]);
70
75
  //if (eq.maxCircuits < 9) sys.board.equipmentIds.invalidIds.merge([9]);
71
76
  for (let i = 7; i <= 10; i++) {
72
77
  // This will add all the invalid ids between 7 and 10 that are omitted for IntelliTouch models.
@@ -74,26 +79,42 @@ export class IntelliTouchBoard extends EasyTouchBoard {
74
79
  if (i > eq.maxCircuits) sys.board.equipmentIds.invalidIds.merge([i]);
75
80
  }
76
81
  // This code should be repeated if we ever see a panel with more than one expansion panel.
77
- let pnl: ExpansionPanel;
78
- pnl = sys.equipment.expansions.getItemById(1, true);
79
- pnl.type = byte2 & 0x20;
80
- pnl.name = pnl.type === 32 ? 'i10X' : 'none';
81
- pnl.isActive = pnl.type !== 0;
82
- // if type is i9 or i10 we can have up to 3 expansion boards. These expansion boards only add
83
- // circuits.
84
- if (pnl.isActive) {
85
- let emt = this.valueMaps.expansionBoards.transform(pnl.type);
86
- let emd = pnl.modules.getItemById(1, true).get();
87
- eq.maxCircuits += emd.circuits = typeof emt.circuits !== 'undefined' ? emt.circuits : 0;
82
+ let pnl1: ExpansionPanel;
83
+ if ((byte2 & 0x40) === 64) {
84
+ // 64 indicates one expansion panel; SL defaults to i10x but it could also be i5x until we know better
85
+ pnl1 = sys.equipment.expansions.getItemById(1, true);
86
+ pnl1.type = 32;
87
+ let emt = this.valueMaps.expansionBoards.transform(pnl1.type);
88
+ pnl1.name = emt.desc;
89
+ pnl1.isActive = true;
90
+ eq.maxCircuits += emt.circuits;
88
91
  }
89
- else pnl.modules.removeItemById(1);
90
-
92
+ else sys.equipment.expansions.removeItemById(1);
93
+ let pnl2: ExpansionPanel;
94
+ if ((byte2 & 0x80) === 128) {
95
+ // SL defaults to i5x but it could also be i10x until we know better
96
+ pnl2 = sys.equipment.expansions.getItemById(2, true);
97
+ pnl2.type = 32;
98
+ let emt = this.valueMaps.expansionBoards.transform(pnl2.type);
99
+ pnl2.name = emt.desc;
100
+ pnl2.isActive = true;
101
+ eq.maxCircuits += emt.circuits;
102
+ }
103
+ else sys.equipment.expansions.removeItemById(2);
104
+ let pnl3: ExpansionPanel;
105
+ if ((byte2 & 0xC0) === 192) {
106
+ // SL defaults to i5x but it could also be i10x until we know better
107
+ pnl3 = sys.equipment.expansions.getItemById(3, true);
108
+ pnl3.type = 32;
109
+ let emt = this.valueMaps.expansionBoards.transform(pnl3.type);
110
+ pnl3.name = emt.desc;
111
+ pnl3.isActive = true;
112
+ eq.maxCircuits += emt.circuits;
113
+ }
114
+ else sys.equipment.expansions.removeItemById(3);
91
115
  if (byte1 !== 14) sys.board.equipmentIds.invalidIds.merge([10, 19]);
92
116
  state.equipment.model = sys.equipment.model = mt.desc;
93
117
  state.equipment.controllerType = 'intellitouch';
94
- // The code above should be repeated if we ever see a panel with more than one expansion panel.
95
- sys.equipment.expansions.getItemById(2, true).isActive = false;
96
- sys.equipment.expansions.getItemById(3, true).isActive = false;
97
118
  sys.equipment.shared ? sys.board.equipmentIds.circuits.start = 1 : sys.board.equipmentIds.circuits.start = 2;
98
119
  this.initBodyDefaults();
99
120
  this.initHeaterDefaults();
@@ -117,6 +138,59 @@ export class IntelliTouchBoard extends EasyTouchBoard {
117
138
  state.emitControllerChange();
118
139
  }
119
140
  public circuits: ITTouchCircuitCommands = new ITTouchCircuitCommands(this);
141
+ public async setControllerType(obj): Promise<Equipment> {
142
+ try {
143
+ if (obj.controllerType !== sys.controllerType) {
144
+ return Promise.reject(new InvalidEquipmentDataError(`You may not change the controller type data for ${sys.controllerType} controllers`, 'controllerType', obj.controllerType));
145
+ }
146
+
147
+ let mod = sys.equipment.modules.getItemById(0);
148
+ let mt = this.valueMaps.expansionBoards.get(mod.type);
149
+ let _circuits = mt.circuits;
150
+ let pnl1 = sys.equipment.expansions.getItemById(1);
151
+ if (typeof obj.expansion1 !== 'undefined' && obj.expansion1 !== pnl1.type) {
152
+ let emt = this.valueMaps.expansionBoards.transform(obj.expansion1);
153
+ logger.info(`Changing expansion 1 to ${emt.desc}.`);
154
+ pnl1.type = emt.val;
155
+ pnl1.name = emt.desc;
156
+ pnl1.isActive = true;
157
+ }
158
+ let pnl2 = sys.equipment.expansions.getItemById(2);
159
+ if (typeof obj.expansion2 !== 'undefined' && obj.expansion2 !== pnl2.type) {
160
+ let emt = this.valueMaps.expansionBoards.transform(obj.expansion2);
161
+ logger.info(`Changing expansion 2 to ${emt.desc}.`);
162
+ pnl2.type = emt.val;
163
+ pnl2.name = emt.desc;
164
+ pnl2.isActive = true;
165
+ }
166
+ let pnl3 = sys.equipment.expansions.getItemById(3);
167
+ if (typeof obj.expansion3 !== 'undefined' && obj.expansion3 !== pnl3.type) {
168
+ let emt = this.valueMaps.expansionBoards.transform(obj.expansion3);
169
+ logger.info(`Changing expansion 3 to ${emt.desc}.`);
170
+ pnl3.type = emt.val;
171
+ pnl3.name = emt.desc;
172
+ pnl3.isActive = true;
173
+ }
174
+ let prevMaxCircuits = sys.equipment.maxCircuits;
175
+ if (pnl1.isActive) _circuits += this.valueMaps.expansionBoards.get(pnl1.type).circuits;
176
+ if (pnl2.isActive) _circuits += this.valueMaps.expansionBoards.get(pnl2.type).circuits;
177
+ if (pnl3.isActive) _circuits += this.valueMaps.expansionBoards.get(pnl3.type).circuits;
178
+ if (_circuits < prevMaxCircuits) {
179
+ // if we downsize expansions, remove circuits
180
+ for (let i = _circuits + 1; i <= prevMaxCircuits; i++) {
181
+ sys.circuits.removeItemById(i);
182
+ state.circuits.removeItemById(i);
183
+ }
184
+ }
185
+ else if (_circuits > prevMaxCircuits) {
186
+ this._configQueue.queueChanges();
187
+ }
188
+ sys.equipment.maxCircuits = _circuits;
189
+ return sys.equipment;
190
+ } catch (err) {
191
+ logger.error(`Error setting expansion panels: ${err.message}`);
192
+ }
193
+ }
120
194
  }
121
195
  class ITTouchConfigQueue extends TouchConfigQueue {
122
196
  public queueChanges() {
@@ -148,7 +222,7 @@ class ITTouchConfigQueue extends TouchConfigQueue {
148
222
  }
149
223
  if (this.remainingItems > 0) {
150
224
  var self = this;
151
- setTimeout(() => {self.processNext();}, 50);
225
+ setTimeout(() => { self.processNext(); }, 50);
152
226
  } else state.status = 1;
153
227
  state.emitControllerChange();
154
228
  }
@@ -164,5 +238,5 @@ class ITTouchCircuitCommands extends TouchCircuitCommands {
164
238
  }
165
239
  catch (err) { reject(err); }
166
240
  });
167
- }
241
+ }
168
242
  }