nodejs-poolcontroller 7.2.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 (64) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
  2. package/Changelog +13 -0
  3. package/Dockerfile +1 -0
  4. package/README.md +5 -5
  5. package/app.ts +11 -0
  6. package/config/Config.ts +3 -0
  7. package/config/VersionCheck.ts +8 -4
  8. package/controller/Constants.ts +165 -9
  9. package/controller/Equipment.ts +186 -65
  10. package/controller/Errors.ts +22 -1
  11. package/controller/State.ts +273 -57
  12. package/controller/boards/EasyTouchBoard.ts +194 -95
  13. package/controller/boards/IntelliCenterBoard.ts +115 -42
  14. package/controller/boards/IntelliTouchBoard.ts +104 -30
  15. package/controller/boards/NixieBoard.ts +155 -53
  16. package/controller/boards/SystemBoard.ts +1529 -514
  17. package/controller/comms/Comms.ts +219 -42
  18. package/controller/comms/messages/Messages.ts +16 -4
  19. package/controller/comms/messages/config/ChlorinatorMessage.ts +13 -3
  20. package/controller/comms/messages/config/CircuitGroupMessage.ts +6 -0
  21. package/controller/comms/messages/config/CircuitMessage.ts +1 -1
  22. package/controller/comms/messages/config/CoverMessage.ts +1 -0
  23. package/controller/comms/messages/config/EquipmentMessage.ts +4 -0
  24. package/controller/comms/messages/config/ExternalMessage.ts +43 -25
  25. package/controller/comms/messages/config/FeatureMessage.ts +8 -1
  26. package/controller/comms/messages/config/GeneralMessage.ts +8 -0
  27. package/controller/comms/messages/config/HeaterMessage.ts +15 -9
  28. package/controller/comms/messages/config/IntellichemMessage.ts +4 -1
  29. package/controller/comms/messages/config/OptionsMessage.ts +13 -1
  30. package/controller/comms/messages/config/PumpMessage.ts +4 -20
  31. package/controller/comms/messages/config/RemoteMessage.ts +4 -0
  32. package/controller/comms/messages/config/ScheduleMessage.ts +11 -0
  33. package/controller/comms/messages/config/SecurityMessage.ts +1 -0
  34. package/controller/comms/messages/config/ValveMessage.ts +12 -2
  35. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +14 -6
  36. package/controller/comms/messages/status/EquipmentStateMessage.ts +78 -24
  37. package/controller/comms/messages/status/HeaterStateMessage.ts +25 -5
  38. package/controller/comms/messages/status/IntelliChemStateMessage.ts +55 -26
  39. package/controller/nixie/Nixie.ts +18 -16
  40. package/controller/nixie/NixieEquipment.ts +6 -6
  41. package/controller/nixie/bodies/Body.ts +7 -4
  42. package/controller/nixie/bodies/Filter.ts +7 -4
  43. package/controller/nixie/chemistry/ChemController.ts +800 -283
  44. package/controller/nixie/chemistry/Chlorinator.ts +22 -14
  45. package/controller/nixie/circuits/Circuit.ts +42 -7
  46. package/controller/nixie/heaters/Heater.ts +303 -30
  47. package/controller/nixie/pumps/Pump.ts +57 -30
  48. package/controller/nixie/schedules/Schedule.ts +10 -7
  49. package/controller/nixie/valves/Valve.ts +7 -5
  50. package/defaultConfig.json +32 -1
  51. package/issue_template.md +1 -1
  52. package/logger/DataLogger.ts +37 -22
  53. package/package.json +20 -18
  54. package/web/Server.ts +529 -31
  55. package/web/bindings/influxDB.json +157 -5
  56. package/web/bindings/mqtt.json +112 -13
  57. package/web/bindings/mqttAlt.json +109 -11
  58. package/web/interfaces/baseInterface.ts +2 -1
  59. package/web/interfaces/httpInterface.ts +2 -0
  60. package/web/interfaces/influxInterface.ts +103 -54
  61. package/web/interfaces/mqttInterface.ts +16 -5
  62. package/web/services/config/Config.ts +179 -43
  63. package/web/services/state/State.ts +51 -5
  64. package/web/services/state/StateSocket.ts +19 -2
@@ -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' }]
@@ -129,8 +135,8 @@ export class IntelliCenterBoard extends SystemBoard {
129
135
  [6, { name: 'i10x', part: '522997Z', desc: 'i10x Expansion Module', circuits: 10 }],
130
136
  [7, { name: 'i10D', part: '523029Z', desc: 'i10D Personality Card', bodies: 2, valves: 2, circuits: 11, shared: false, dual: true, chlorinators: 1, chemControllers: 1 }], // We have witnessed this in the wild
131
137
  [8, { name: 'Valve Exp', part: '522440', desc: 'Valve Expansion Module', valves: 6 }],
132
- [9, { name: 'iChlor Mux', part: '522719', desc: 'iChlor MUX Card', chlorinators: 3 }], // This is a guess
133
- [10, { name: 'A/D Module', part: '522039', desc: 'A/D Cover Module', covers: 2 }], // This is a guess
138
+ [9, { name: 'A/D Module', part: '522039', desc: 'A/D Cover Module', covers: 2 }], // Finally have a user with one of these
139
+ [10, { name: 'iChlor Mux', part: '522719', desc: 'iChlor MUX Card', chlorinators: 3 }], // This is a guess
134
140
  [255, {name: 'i5x', part: '522033', desc: 'i5x Expansion Module', circuits: 5}] // This does not actually map to a known value at this point but we do know it will be > 6.
135
141
  ]);
136
142
 
@@ -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 }],
@@ -572,7 +579,7 @@ class IntelliCenterConfigRequest extends ConfigRequest {
572
579
  if (typeof items !== 'undefined') this.items.push(...items);
573
580
  this.oncomplete = oncomplete;
574
581
  }
575
- public category: ConfigCategories;
582
+ declare category: ConfigCategories;
576
583
  }
577
584
  class IntelliCenterConfigQueue extends ConfigQueue {
578
585
  public _processing: boolean = false;
@@ -1388,7 +1395,20 @@ class IntelliCenterSystemCommands extends SystemCommands {
1388
1395
  }
1389
1396
  }
1390
1397
  class IntelliCenterCircuitCommands extends CircuitCommands {
1391
- public board: IntelliCenterBoard;
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({
@@ -1419,14 +1439,24 @@ class IntelliCenterCircuitCommands extends CircuitCommands {
1419
1439
  onComplete: (err, msg) => {
1420
1440
  if (err) reject(err);
1421
1441
  else {
1442
+ let scircuit = state.circuits.getItemById(circuit.id, true);
1422
1443
  circuit.eggTimer = eggTimer;
1423
1444
  circuit.dontStop = data.dontStop;
1424
1445
  circuit.freeze = (typeof data.freeze !== 'undefined' ? utils.makeBool(data.freeze) : circuit.freeze);
1425
1446
  circuit.showInFeatures = (typeof data.showInFeatures !== 'undefined' ? utils.makeBool(data.showInFeatures) : circuit.showInFeatures);
1426
- if (type === 9) circuit.level = theme;
1427
- else circuit.lightingTheme = theme;
1428
- circuit.name = typeof data.name !== 'undefined' ? data.name.toString().substring(0, 16) : circuit.name;
1429
- circuit.type = type;
1447
+ if (type === 9) scircuit.level = circuit.level = theme;
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;
1457
+ scircuit.type = circuit.type = type;
1458
+ scircuit.isActive = circuit.isActive = true;
1459
+ circuit.master = 0;
1430
1460
  resolve(circuit);
1431
1461
  }
1432
1462
  }
@@ -2055,6 +2085,14 @@ class IntelliCenterCircuitCommands extends CircuitCommands {
2055
2085
  // NOT SURE IF COINCIDENTAL: The ICP seems to respond immediately after action 2.
2056
2086
  // 7. ICP Sends 168[15,0,... new options, 0,0,0,0]
2057
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]
2058
2096
 
2059
2097
  // The previous sequence is just additional noise on the bus. There is no need for it. We just
2060
2098
  // need to send the set circuit message. It will reliably work 100% of the time but the ICP
@@ -2064,6 +2102,9 @@ class IntelliCenterCircuitCommands extends CircuitCommands {
2064
2102
  //if (b) b = await this.getConfigAsync([15, 0]);
2065
2103
  return new Promise<ICircuitState>((resolve, reject) => {
2066
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;
2067
2108
  out.onComplete = async (err, msg: Inbound) => {
2068
2109
  if (err) reject(err);
2069
2110
  else {
@@ -2297,7 +2338,19 @@ class IntelliCenterCircuitCommands extends CircuitCommands {
2297
2338
  let ndx = Math.floor(ordinal / 8);
2298
2339
  let byte = out.payload[ndx + 15];
2299
2340
  let bit = ordinal - (ndx * 8);
2300
- 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);
2301
2354
  out.payload[ndx + 15] = byte;
2302
2355
  }
2303
2356
  return out;
@@ -2340,7 +2393,7 @@ class IntelliCenterCircuitCommands extends CircuitCommands {
2340
2393
  }
2341
2394
  }
2342
2395
  class IntelliCenterFeatureCommands extends FeatureCommands {
2343
- public board: IntelliCenterBoard;
2396
+ declare board: IntelliCenterBoard;
2344
2397
  public async setFeatureStateAsync(id, val): Promise<ICircuitState> { return sys.board.circuits.setCircuitStateAsync(id, val); }
2345
2398
  public async toggleFeatureStateAsync(id): Promise<ICircuitState> { return sys.board.circuits.toggleCircuitStateAsync(id); }
2346
2399
  public syncGroupStates() { } // Do nothing and let IntelliCenter do it.
@@ -2427,12 +2480,10 @@ class IntelliCenterChlorinatorCommands extends ChlorinatorCommands {
2427
2480
  public async setChlorAsync(obj: any): Promise<ChlorinatorState> {
2428
2481
  let id = parseInt(obj.id, 10);
2429
2482
  let isAdd = false;
2430
- let isVirtual = false;
2431
- if (id <= 0 || isNaN(id)) id = 1;
2432
2483
  let chlor = sys.chlorinators.getItemById(id);
2433
- if (id < 0 || isNaN(id)) {
2484
+ if (id <= 0 || isNaN(id)) {
2434
2485
  isAdd = true;
2435
- chlor.master = utils.makeBool(obj.isVirtual) ? 0 : 1;
2486
+ chlor.master = utils.makeBool(obj.master) ? 1 : 0;
2436
2487
  // Calculate an id for the chlorinator. The messed up part is that if a chlorinator is not attached to the OCP, its address
2437
2488
  // cannot be set by the MUX. This will have to wait.
2438
2489
  id = 1;
@@ -2441,13 +2492,22 @@ class IntelliCenterChlorinatorCommands extends ChlorinatorCommands {
2441
2492
  //let chlor = extend(true, {}, sys.chlorinators.getItemById(id).get(), obj);
2442
2493
  // If this is a virtual chlorinator then go to the base class and handle it from there.
2443
2494
  if (chlor.master === 1) return super.setChlorAsync(obj);
2444
- let name = obj.name || 'IntelliChlor' + id;
2445
- let poolSetpoint = parseInt(obj.poolSetpoint, 10);
2446
- let spaSetpoint = parseInt(obj.spaSetpoint, 10);
2495
+ if (typeof chlor.master === 'undefined') chlor.master = 0;
2496
+ let name = obj.name || chlor.name || 'IntelliChlor' + id;
2447
2497
  let superChlorHours = parseInt(obj.superChlorHours, 10);
2448
2498
  if (typeof obj.superChlorinate !== 'undefined') obj.superChlor = utils.makeBool(obj.superChlorinate);
2449
2499
  let superChlorinate = typeof obj.superChlor === 'undefined' ? undefined : utils.makeBool(obj.superChlor);
2500
+ let isDosing = typeof obj.isDosing !== 'undefined' ? utils.makeBool(obj.isDosing) : chlor.isDosing;
2450
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
+
2509
+ let model = typeof obj.model !== 'undefined' ? obj.model : chlor.model;
2510
+ let chlorType = typeof obj.type !== 'undefined' ? sys.board.valueMaps.chlorinatorType.encode(obj.type) : chlor.type || 0;
2451
2511
  if (isAdd) {
2452
2512
  if (isNaN(poolSetpoint)) poolSetpoint = 50;
2453
2513
  if (isNaN(spaSetpoint)) spaSetpoint = 10;
@@ -2455,8 +2515,8 @@ class IntelliCenterChlorinatorCommands extends ChlorinatorCommands {
2455
2515
  if (typeof superChlorinate === 'undefined') superChlorinate = false;
2456
2516
  }
2457
2517
  else {
2458
- if (isNaN(poolSetpoint)) poolSetpoint = chlor.poolSetpoint;
2459
- if (isNaN(spaSetpoint)) spaSetpoint = chlor.spaSetpoint;
2518
+ if (isNaN(poolSetpoint)) poolSetpoint = chlor.poolSetpoint || 0;
2519
+ if (isNaN(spaSetpoint)) spaSetpoint = chlor.spaSetpoint || 0;
2460
2520
  if (isNaN(superChlorHours)) superChlorHours = chlor.superChlorHours;
2461
2521
  if (typeof superChlorinate === 'undefined') superChlorinate = utils.makeBool(chlor.superChlor);
2462
2522
  }
@@ -2471,7 +2531,10 @@ class IntelliCenterChlorinatorCommands extends ChlorinatorCommands {
2471
2531
  return new Promise<ChlorinatorState>((resolve, reject) => {
2472
2532
  let out = Outbound.create({
2473
2533
  action: 168,
2474
- payload: [7, 0, id - 1, body.val, 1, disabled ? 0 : poolSetpoint, disabled ? 0 : 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],
2475
2538
  response: IntelliCenterBoard.getAckResponse(168),
2476
2539
  retries: 5,
2477
2540
  onComplete: (err, msg) => {
@@ -2480,6 +2543,10 @@ class IntelliCenterChlorinatorCommands extends ChlorinatorCommands {
2480
2543
  let schlor = state.chlorinators.getItemById(id, true);
2481
2544
  let cchlor = sys.chlorinators.getItemById(id, true);
2482
2545
  chlor.disabled = disabled;
2546
+ chlor.model = model;
2547
+ schlor.type = chlor.type = chlorType;
2548
+ chlor.name = schlor.name = name;
2549
+ chlor.isDosing = isDosing;
2483
2550
  schlor.isActive = cchlor.isActive = true;
2484
2551
  schlor.poolSetpoint = cchlor.poolSetpoint = poolSetpoint;
2485
2552
  schlor.spaSetpoint = cchlor.spaSetpoint = spaSetpoint;
@@ -2495,21 +2562,22 @@ class IntelliCenterChlorinatorCommands extends ChlorinatorCommands {
2495
2562
  }
2496
2563
  public async deleteChlorAsync(obj: any): Promise<ChlorinatorState> {
2497
2564
  let id = parseInt(obj.id, 10);
2498
- if (isNaN(id)) obj.id = 1;
2499
-
2500
- // Merge all the information.
2501
- 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);
2502
2569
  // Verify the data.
2503
2570
  return new Promise<ChlorinatorState>((resolve, reject) => {
2504
2571
  let out = Outbound.create({
2505
2572
  action: 168,
2506
- 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],
2507
2574
  response: IntelliCenterBoard.getAckResponse(168),
2508
2575
  retries: 5,
2509
2576
  onComplete: (err, msg) => {
2510
2577
  if (err) reject(err);
2511
2578
  else {
2512
- let schlor = state.chlorinators.getItemById(id, true);
2579
+ ncp.chlorinators.deleteChlorinatorAsync(id).then(()=>{});
2580
+ schlor = state.chlorinators.getItemById(id, true);
2513
2581
  state.chlorinators.removeItemById(id);
2514
2582
  sys.chlorinators.removeItemById(id);
2515
2583
  resolve(schlor);
@@ -2598,7 +2666,7 @@ class IntelliCenterPumpCommands extends PumpCommands {
2598
2666
  let id = (typeof data.id === 'undefined' || data.id <= 0) ? sys.pumps.getNextEquipmentId(sys.board.equipmentIds.pumps) : parseInt(data.id, 10);
2599
2667
  if (isNaN(id)) return Promise.reject(new Error(`Invalid pump id: ${data.id}`));
2600
2668
  let pump = sys.pumps.getItemById(id, false);
2601
- 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);
2602
2670
 
2603
2671
  // 0 6 10 11 12 15
2604
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]
@@ -3177,6 +3245,8 @@ class IntelliCenterScheduleCommands extends ScheduleCommands {
3177
3245
  out.retries = 5;
3178
3246
  out.onComplete = (err, msg) => {
3179
3247
  if (!err) {
3248
+ sched = sys.schedules.getItemById(id, true);
3249
+ ssched = state.schedules.getItemById(id, true);
3180
3250
  sched.circuit = ssched.circuit = circuit;
3181
3251
  sched.scheduleDays = ssched.scheduleDays = schedDays;
3182
3252
  sched.scheduleType = ssched.scheduleType = schedType;
@@ -3273,14 +3343,14 @@ class IntelliCenterHeaterCommands extends HeaterCommands {
3273
3343
  conn.queueSendMessage(out);
3274
3344
  }
3275
3345
  public async setHeaterAsync(obj: any): Promise<Heater> {
3276
- 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);
3277
3347
  return new Promise<Heater>((resolve, reject) => {
3278
3348
  let id = typeof obj.id === 'undefined' ? -1 : parseInt(obj.id, 10);
3279
3349
  if (isNaN(id)) return reject(new InvalidEquipmentIdError('Heater Id is not valid.', obj.id, 'Heater'));
3280
3350
  let heater: Heater;
3281
3351
  if (id <= 0) {
3282
3352
  // We are adding a heater. In this case all heaters are virtual.
3283
- let vheaters = sys.heaters.filter(h => h.isVirtual);
3353
+ let vheaters = sys.heaters.filter(h => h.master === 1);
3284
3354
  id = vheaters.length + 1;
3285
3355
  }
3286
3356
  heater = sys.heaters.getItemById(id, false);
@@ -3388,7 +3458,6 @@ class IntelliCenterHeaterCommands extends HeaterCommands {
3388
3458
  heater.economyTime = economyTime;
3389
3459
  heater.startTempDelta = startTempDelta;
3390
3460
  heater.stopTempDelta = stopTempDelta;
3391
- hstate.isVirtual = heater.isVirtual = false;
3392
3461
  heater.cooldownDelay = cooldownDelay;
3393
3462
  sys.board.heaters.updateHeaterServices();
3394
3463
  sys.board.heaters.syncHeaterStates();
@@ -3401,7 +3470,7 @@ class IntelliCenterHeaterCommands extends HeaterCommands {
3401
3470
  });
3402
3471
  }
3403
3472
  public async deleteHeaterAsync(obj): Promise<Heater> {
3404
- 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);
3405
3474
  return new Promise<Heater>((resolve, reject) => {
3406
3475
  let id = parseInt(obj.id, 10);
3407
3476
  if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError('Cannot delete. Heater Id is not valid.', obj.id, 'Heater'));
@@ -3443,6 +3512,8 @@ class IntelliCenterHeaterCommands extends HeaterCommands {
3443
3512
  let heatPumpInstalled = htypes.heatpump > 0;
3444
3513
  let gasHeaterInstalled = htypes.gas > 0;
3445
3514
  let ultratempInstalled = htypes.ultratemp > 0;
3515
+ let mastertempInstalled = htypes.mastertemp > 0;
3516
+
3446
3517
 
3447
3518
  // RKS: 09-26-20 This is a hack to maintain backward compatability with fw versions 1.04 and below. Ultratemp is not
3448
3519
  // supported on 1.04 and below.
@@ -3466,9 +3537,10 @@ class IntelliCenterHeaterCommands extends HeaterCommands {
3466
3537
  // 3 = Solar Heater
3467
3538
  // 4 = Solar Preferred
3468
3539
  // 5 = Heat Pump
3469
-
3540
+
3470
3541
  if (sys.heaters.length > 0) sys.board.valueMaps.heatSources = new byteValueMap([[1, { name: 'off', desc: 'Off' }]]);
3471
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' }]]);
3472
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 }]]);
3473
3545
  else if (solarInstalled) sys.board.valueMaps.heatSources.merge([[3, { name: 'solar', desc: 'Solar', hasCoolsetpoint: htypes.hasCoolSetpoint }]]);
3474
3546
  if (heatPumpInstalled && (gasHeaterInstalled || solarInstalled)) sys.board.valueMaps.heatSources.merge([[9, { name: 'heatpump', desc: 'Heatpump Only' }], [25, { name: 'heatpumppref', desc: 'Heat Pump Pref' }]]);
@@ -3479,11 +3551,12 @@ class IntelliCenterHeaterCommands extends HeaterCommands {
3479
3551
 
3480
3552
  sys.board.valueMaps.heatModes = new byteValueMap([[1, { name: 'off', desc: 'Off' }]]);
3481
3553
  if (gasHeaterInstalled) sys.board.valueMaps.heatModes.merge([[2, { name: 'heater', desc: 'Heater' }]]);
3482
- 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' }]]);
3483
3556
  else if (solarInstalled) sys.board.valueMaps.heatModes.merge([[3, { name: 'solar', desc: 'Solar' }]]);
3484
- 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' }]]);
3485
3558
  else if (ultratempInstalled) sys.board.valueMaps.heatModes.merge([[5, { name: 'ultratemp', desc: 'UltraTemp' }]]);
3486
- 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' }]]);
3487
3560
  else if (heatPumpInstalled) sys.board.valueMaps.heatModes.merge([[9, { name: 'heatpump', desc: 'Heat Pump' }]]);
3488
3561
  }
3489
3562
  else {
@@ -3515,7 +3588,7 @@ class IntelliCenterHeaterCommands extends HeaterCommands {
3515
3588
  }
3516
3589
  class IntelliCenterValveCommands extends ValveCommands {
3517
3590
  public async setValveAsync(obj?: any): Promise<Valve> {
3518
- if (obj.isVirtual || obj.master === 1) return super.setValveAsync(obj);
3591
+ if (obj.master === 1) return super.setValveAsync(obj);
3519
3592
  let id = parseInt(obj.id, 10);
3520
3593
  if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError('Valve Id has not been defined', obj.id, 'Valve'));
3521
3594
  let valve = sys.valves.getItemById(id);
@@ -3565,7 +3638,7 @@ export class IntelliCenterChemControllerCommands extends ChemControllerCommands
3565
3638
  // Now lets do all our validation to the incoming chem controller data.
3566
3639
  let name = typeof data.name !== 'undefined' ? data.name : chem.name || `IntelliChem - ${address - 143}`;
3567
3640
  let type = sys.board.valueMaps.chemControllerTypes.transformByName('intellichem');
3568
- // 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.
3569
3642
  let calciumHardness = typeof data.calciumHardness !== 'undefined' ? parseInt(data.calciumHardness, 10) : chem.calciumHardness;
3570
3643
  let cyanuricAcid = typeof data.cyanuricAcid !== 'undefined' ? parseInt(data.cyanuricAcid, 10) : chem.cyanuricAcid;
3571
3644
  let alkalinity = typeof data.alkalinity !== 'undefined' ? parseInt(data.alkalinity, 10) : chem.alkalinity;
@@ -3647,7 +3720,7 @@ export class IntelliCenterChemControllerCommands extends ChemControllerCommands
3647
3720
  chem.orp.tolerance.high = orpTolerance.high;
3648
3721
  chem.ph.setpoint = pHSetpoint;
3649
3722
  chem.orp.setpoint = orpSetpoint;
3650
- chem.siCalcType = siCalcType;
3723
+ schem.siCalcType = chem.siCalcType = siCalcType;
3651
3724
  chem.address = schem.address = address;
3652
3725
  chem.name = schem.name = name;
3653
3726
  chem.flowSensor.enabled = false;
@@ -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,25 +50,28 @@ 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;
54
57
  eq.maxCircuits = md.circuits = typeof mt.circuits !== 'undefined' ? mt.circuits : 6;
55
- eq.maxFeatures = md.features = typeof mt.features !== 'undefined' ? mt.features : 10
58
+ eq.maxFeatures = md.features = typeof mt.features !== 'undefined' ? mt.features : 8;
56
59
  eq.maxValves = md.valves = typeof mt.valves !== 'undefined' ? mt.valves : mt.shared ? 4 : 2;
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
  }