nodejs-poolcontroller 7.3.0 → 7.6.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.
Files changed (60) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
  2. package/Changelog +23 -0
  3. package/README.md +5 -5
  4. package/app.ts +2 -0
  5. package/config/Config.ts +3 -0
  6. package/config/VersionCheck.ts +8 -4
  7. package/controller/Constants.ts +88 -0
  8. package/controller/Equipment.ts +246 -66
  9. package/controller/Errors.ts +24 -1
  10. package/controller/Lockouts.ts +423 -0
  11. package/controller/State.ts +314 -54
  12. package/controller/boards/EasyTouchBoard.ts +107 -59
  13. package/controller/boards/IntelliCenterBoard.ts +186 -125
  14. package/controller/boards/IntelliTouchBoard.ts +104 -30
  15. package/controller/boards/NixieBoard.ts +721 -159
  16. package/controller/boards/SystemBoard.ts +2370 -1108
  17. package/controller/comms/Comms.ts +85 -10
  18. package/controller/comms/messages/Messages.ts +10 -4
  19. package/controller/comms/messages/config/ChlorinatorMessage.ts +13 -4
  20. package/controller/comms/messages/config/CircuitGroupMessage.ts +6 -0
  21. package/controller/comms/messages/config/CoverMessage.ts +1 -0
  22. package/controller/comms/messages/config/EquipmentMessage.ts +4 -0
  23. package/controller/comms/messages/config/ExternalMessage.ts +44 -26
  24. package/controller/comms/messages/config/FeatureMessage.ts +8 -1
  25. package/controller/comms/messages/config/GeneralMessage.ts +8 -0
  26. package/controller/comms/messages/config/HeaterMessage.ts +15 -9
  27. package/controller/comms/messages/config/IntellichemMessage.ts +4 -1
  28. package/controller/comms/messages/config/OptionsMessage.ts +13 -1
  29. package/controller/comms/messages/config/PumpMessage.ts +4 -20
  30. package/controller/comms/messages/config/RemoteMessage.ts +4 -0
  31. package/controller/comms/messages/config/ScheduleMessage.ts +11 -0
  32. package/controller/comms/messages/config/SecurityMessage.ts +1 -0
  33. package/controller/comms/messages/config/ValveMessage.ts +13 -3
  34. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +2 -3
  35. package/controller/comms/messages/status/EquipmentStateMessage.ts +78 -24
  36. package/controller/comms/messages/status/HeaterStateMessage.ts +42 -9
  37. package/controller/comms/messages/status/IntelliChemStateMessage.ts +37 -26
  38. package/controller/nixie/Nixie.ts +18 -16
  39. package/controller/nixie/bodies/Body.ts +4 -1
  40. package/controller/nixie/chemistry/ChemController.ts +80 -77
  41. package/controller/nixie/chemistry/Chlorinator.ts +9 -8
  42. package/controller/nixie/circuits/Circuit.ts +55 -6
  43. package/controller/nixie/heaters/Heater.ts +192 -32
  44. package/controller/nixie/pumps/Pump.ts +146 -84
  45. package/controller/nixie/schedules/Schedule.ts +3 -2
  46. package/controller/nixie/valves/Valve.ts +1 -1
  47. package/defaultConfig.json +32 -1
  48. package/issue_template.md +1 -1
  49. package/logger/DataLogger.ts +37 -22
  50. package/package.json +20 -18
  51. package/web/Server.ts +520 -29
  52. package/web/bindings/influxDB.json +96 -8
  53. package/web/bindings/mqtt.json +151 -40
  54. package/web/bindings/mqttAlt.json +114 -4
  55. package/web/interfaces/httpInterface.ts +2 -0
  56. package/web/interfaces/influxInterface.ts +36 -19
  57. package/web/interfaces/mqttInterface.ts +14 -3
  58. package/web/services/config/Config.ts +171 -44
  59. package/web/services/state/State.ts +49 -5
  60. package/web/services/state/StateSocket.ts +18 -1
@@ -38,33 +38,39 @@ 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' }],
44
- [2, { name: 'mastercleaner', desc: 'Master Cleaner' }],
50
+ [2, { name: 'mastercleaner', desc: 'Master Cleaner', body: 1 }],
45
51
  [3, { name: 'chemrelay', desc: 'Chem Relay' }],
46
52
  [4, { name: 'light', desc: 'Light', isLight: true }],
47
- [5, { name: 'intellibrite', desc: 'Intellibrite', isLight: true }],
48
- [6, { name: 'globrite', desc: 'GloBrite', isLight: true }],
53
+ [5, { name: 'intellibrite', desc: 'Intellibrite', isLight: true, theme: 'intellibrite' }],
54
+ [6, { name: 'globrite', desc: 'GloBrite', isLight: true, themes: 'intellibrite' }],
49
55
  [7, { name: 'globritewhite', desc: 'GloBrite White', isLight: true }],
50
- [8, { name: 'magicstream', desc: 'Magicstream', isLight: true }],
56
+ [8, { name: 'magicstream', desc: 'Magicstream', isLight: true, theme: 'intellibrite' }],
51
57
  [9, { name: 'dimmer', desc: 'Dimmer', isLight: true }],
52
- [10, { name: 'colorcascade', desc: 'ColorCascade', isLight: true }],
53
- [11, { name: 'mastercleaner2', desc: 'Master Cleaner 2' }],
54
- [12, { name: 'pool', desc: 'Pool', hasHeatSource: true }],
55
- [13, { name: 'spa', desc: 'Spa', hasHeatSource: true }]
58
+ [10, { name: 'colorcascade', desc: 'ColorCascade', isLight: true, theme: 'intellibrite' }],
59
+ [11, { name: 'mastercleaner2', desc: 'Master Cleaner 2', body: 2 }],
60
+ [12, { name: 'pool', desc: 'Pool', hasHeatSource: true, body: 1 }],
61
+ [13, { name: 'spa', desc: 'Spa', hasHeatSource: true, body: 2 }]
56
62
  ]);
57
63
  this.valueMaps.pumpTypes = new byteValueMap([
58
64
  [1, { name: 'ss', desc: 'Single Speed', maxCircuits: 0, hasAddress: false, hasBody:true }],
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' }]
@@ -89,8 +95,8 @@ export class IntelliCenterBoard extends SystemBoard {
89
95
  [3, { name: 'heatpump', desc: 'Heat Pump', hasAddress: true }],
90
96
  [4, { name: 'ultratemp', desc: 'UltraTemp', hasAddress: true, hasCoolSetpoint: true }],
91
97
  [5, { name: 'hybrid', desc: 'Hybrid', hasAddress: true }],
92
- [6, { name: 'maxetherm', desc: 'Max-E-Therm', hasAddress: true }],
93
- [7, { name: 'mastertemp', desc: 'MasterTemp', hasAddress: true }]
98
+ [6, { name: 'mastertemp', desc: 'MasterTemp', hasAddress: true }],
99
+ [7, { name: 'maxetherm', desc: 'Max-E-Therm', hasAddress: true }],
94
100
  ]);
95
101
 
96
102
 
@@ -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
 
@@ -167,18 +173,18 @@ export class IntelliCenterBoard extends SystemBoard {
167
173
  [2, { name: 'sunset', desc: 'Sunset' }]
168
174
  ]);
169
175
  this.valueMaps.lightThemes = new byteValueMap([
170
- [0, { name: 'white', desc: 'White', sequence: 11 }],
171
- [1, { name: 'green', desc: 'Green', sequence: 9 }],
172
- [2, { name: 'blue', desc: 'Blue', sequence: 8 }],
173
- [3, { name: 'magenta', desc: 'Magenta', sequence: 12 }],
174
- [4, { name: 'red', desc: 'Red', sequence: 10 }],
175
- [5, { name: 'sam', desc: 'SAm Mode', sequence: 1 }],
176
- [6, { name: 'party', desc: 'Party', sequence: 2 }],
177
- [7, { name: 'romance', desc: 'Romance', sequence: 3 }],
178
- [8, { name: 'caribbean', desc: 'Caribbean', sequence: 4 }],
179
- [9, { name: 'american', desc: 'American', sequence: 5 }],
180
- [10, { name: 'sunset', desc: 'Sunset', sequence: 6 }],
181
- [11, { name: 'royal', desc: 'Royal', sequence: 7 }],
176
+ [0, { name: 'white', desc: 'White', sequence: 11, types:['intellibrite', 'magicstream'] }],
177
+ [1, { name: 'green', desc: 'Green', sequence: 9, types: ['intellibrite', 'magicstream'] }],
178
+ [2, { name: 'blue', desc: 'Blue', sequence: 8, types: ['intellibrite', 'magicstream'] }],
179
+ [3, { name: 'magenta', desc: 'Magenta', sequence: 12, types: ['intellibrite', 'magicstream'] }],
180
+ [4, { name: 'red', desc: 'Red', sequence: 10, types: ['intellibrite', 'magicstream'] }],
181
+ [5, { name: 'sam', desc: 'SAm Mode', sequence: 1, types: ['intellibrite', 'magicstream'] }],
182
+ [6, { name: 'party', desc: 'Party', sequence: 2, types: ['intellibrite', 'magicstream'] }],
183
+ [7, { name: 'romance', desc: 'Romance', sequence: 3, types: ['intellibrite', 'magicstream'] }],
184
+ [8, { name: 'caribbean', desc: 'Caribbean', sequence: 4, types: ['intellibrite', 'magicstream'] }],
185
+ [9, { name: 'american', desc: 'American', sequence: 5, types: ['intellibrite', 'magicstream'] }],
186
+ [10, { name: 'sunset', desc: 'Sunset', sequence: 6, types: ['intellibrite', 'magicstream'] }],
187
+ [11, { name: 'royal', desc: 'Royal', sequence: 7, types: ['intellibrite', 'magicstream'] }],
182
188
  [255, { name: 'none', desc: 'None' }]
183
189
  ]);
184
190
  this.valueMaps.lightColors = new byteValueMap([
@@ -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;
@@ -1285,7 +1292,7 @@ class IntelliCenterSystemCommands extends SystemCommands {
1285
1292
  let out = Outbound.create({
1286
1293
  action: 168,
1287
1294
  retries: 5,
1288
- payload: [12, 0, 10, parseInt(obj.timeZone, 10)],
1295
+ payload: [12, 0, 13, parseInt(obj.timeZone, 10)],
1289
1296
  response: IntelliCenterBoard.getAckResponse(168),
1290
1297
  onComplete: (err, msg) => {
1291
1298
  if (err) reject(err);
@@ -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
  }
@@ -1972,17 +2002,18 @@ class IntelliCenterCircuitCommands extends CircuitCommands {
1972
2002
  }
1973
2003
  return Promise.resolve(sgroup);
1974
2004
  }
1975
- public getLightThemes(type: number): any[] {
1976
- switch (type) {
1977
- case 5: // Intellibrite
1978
- case 6: // Globrite
1979
- case 8: // Magicstream
1980
- case 10: // ColorCascade
1981
- return sys.board.valueMaps.lightThemes.toArray();
1982
- default:
1983
- return [];
1984
- }
1985
- }
2005
+ // 12-01-21 RKS: This has been deprecated. This allows for multiple vendor light themes driven by the metadata on the valuemaps.
2006
+ //public getLightThemes(type: number): any[] {
2007
+ // switch (type) {
2008
+ // case 5: // Intellibrite
2009
+ // case 6: // Globrite
2010
+ // case 8: // Magicstream
2011
+ // case 10: // ColorCascade
2012
+ // return sys.board.valueMaps.lightThemes.toArray();
2013
+ // default:
2014
+ // return [];
2015
+ // }
2016
+ //}
1986
2017
  private async verifyVersionAsync(): Promise<boolean> {
1987
2018
  return new Promise<boolean>((resolve, reject) => {
1988
2019
  let out = Outbound.create({
@@ -2042,7 +2073,7 @@ class IntelliCenterCircuitCommands extends CircuitCommands {
2042
2073
  conn.queueSendMessage(out);
2043
2074
  });
2044
2075
  }
2045
- public async setCircuitStateAsync(id: number, val: boolean): Promise<ICircuitState> {
2076
+ public async setCircuitStateAsync(id: number, val: boolean, ignoreDelays?: boolean): Promise<ICircuitState> {
2046
2077
  let c = sys.circuits.getInterfaceById(id);
2047
2078
  if (c.master !== 0) return await super.setCircuitStateAsync(id, val);
2048
2079
  // As of 1.047 there is a sequence to this.
@@ -2055,6 +2086,14 @@ class IntelliCenterCircuitCommands extends CircuitCommands {
2055
2086
  // NOT SURE IF COINCIDENTAL: The ICP seems to respond immediately after action 2.
2056
2087
  // 7. ICP Sends 168[15,0,... new options, 0,0,0,0]
2057
2088
  // 8. OCP responds ACK(168)
2089
+ // i10D turn on pool
2090
+ // OCP
2091
+ // Schedule on
2092
+ // [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]
2093
+ // No schedules
2094
+ // [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]
2095
+ // njsPC
2096
+ // [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
2097
 
2059
2098
  // The previous sequence is just additional noise on the bus. There is no need for it. We just
2060
2099
  // need to send the set circuit message. It will reliably work 100% of the time but the ICP
@@ -2064,6 +2103,9 @@ class IntelliCenterCircuitCommands extends CircuitCommands {
2064
2103
  //if (b) b = await this.getConfigAsync([15, 0]);
2065
2104
  return new Promise<ICircuitState>((resolve, reject) => {
2066
2105
  let out = this.createCircuitStateMessage(id, val);
2106
+ //if (sys.equipment.dual && id === 6) out.setPayloadByte(35, 1);
2107
+ out.setPayloadByte(34, 1);
2108
+ out.source = 16;
2067
2109
  out.onComplete = async (err, msg: Inbound) => {
2068
2110
  if (err) reject(err);
2069
2111
  else {
@@ -2297,7 +2339,19 @@ class IntelliCenterCircuitCommands extends CircuitCommands {
2297
2339
  let ndx = Math.floor(ordinal / 8);
2298
2340
  let byte = out.payload[ndx + 15];
2299
2341
  let bit = ordinal - (ndx * 8);
2300
- if (sched.isOn) byte = byte | (1 << bit);
2342
+ // Lets determine if this schedule should be on.
2343
+ if (sched.circuit === id) {
2344
+ if (isOn) {
2345
+ let dt = state.time.toDate();
2346
+ let dow = dt.getDay();
2347
+ // Convert the dow to the bit value.
2348
+ let sd = sys.board.valueMaps.scheduleDays.toArray().find(elem => elem.dow === dow);
2349
+ let dayVal = sd.bitVal || sd.val; // The bitval allows mask overrides.
2350
+ let ts = dt.getHours() * 60 + dt.getMinutes();
2351
+ if ((sched.scheduleDays & dayVal) > 0 && ts >= sched.startTime && ts <= sched.endTime) byte = byte | (1 << bit);
2352
+ }
2353
+ }
2354
+ else if (sched.isOn) byte = byte | (1 << bit);
2301
2355
  out.payload[ndx + 15] = byte;
2302
2356
  }
2303
2357
  return out;
@@ -2340,7 +2394,7 @@ class IntelliCenterCircuitCommands extends CircuitCommands {
2340
2394
  }
2341
2395
  }
2342
2396
  class IntelliCenterFeatureCommands extends FeatureCommands {
2343
- public board: IntelliCenterBoard;
2397
+ declare board: IntelliCenterBoard;
2344
2398
  public async setFeatureStateAsync(id, val): Promise<ICircuitState> { return sys.board.circuits.setCircuitStateAsync(id, val); }
2345
2399
  public async toggleFeatureStateAsync(id): Promise<ICircuitState> { return sys.board.circuits.toggleCircuitStateAsync(id); }
2346
2400
  public syncGroupStates() { } // Do nothing and let IntelliCenter do it.
@@ -2427,12 +2481,10 @@ class IntelliCenterChlorinatorCommands extends ChlorinatorCommands {
2427
2481
  public async setChlorAsync(obj: any): Promise<ChlorinatorState> {
2428
2482
  let id = parseInt(obj.id, 10);
2429
2483
  let isAdd = false;
2430
- let isVirtual = false;
2431
- if (id <= 0 || isNaN(id)) id = 1;
2432
2484
  let chlor = sys.chlorinators.getItemById(id);
2433
- if (id < 0 || isNaN(id)) {
2485
+ if (id <= 0 || isNaN(id)) {
2434
2486
  isAdd = true;
2435
- chlor.master = utils.makeBool(obj.isVirtual) ? 0 : 1;
2487
+ chlor.master = utils.makeBool(obj.master) ? 1 : 0;
2436
2488
  // Calculate an id for the chlorinator. The messed up part is that if a chlorinator is not attached to the OCP, its address
2437
2489
  // cannot be set by the MUX. This will have to wait.
2438
2490
  id = 1;
@@ -2441,14 +2493,20 @@ class IntelliCenterChlorinatorCommands extends ChlorinatorCommands {
2441
2493
  //let chlor = extend(true, {}, sys.chlorinators.getItemById(id).get(), obj);
2442
2494
  // If this is a virtual chlorinator then go to the base class and handle it from there.
2443
2495
  if (chlor.master === 1) return super.setChlorAsync(obj);
2496
+ if (typeof chlor.master === 'undefined') chlor.master = 0;
2444
2497
  let name = obj.name || chlor.name || 'IntelliChlor' + id;
2445
- let poolSetpoint = parseInt(obj.poolSetpoint, 10);
2446
- let spaSetpoint = parseInt(obj.spaSetpoint, 10);
2447
2498
  let superChlorHours = parseInt(obj.superChlorHours, 10);
2448
2499
  if (typeof obj.superChlorinate !== 'undefined') obj.superChlor = utils.makeBool(obj.superChlorinate);
2449
2500
  let superChlorinate = typeof obj.superChlor === 'undefined' ? undefined : utils.makeBool(obj.superChlor);
2450
- let disabled = typeof obj.disabled !== 'undefined' ? utils.makeBool(obj.disabled) : chlor.disabled;
2451
2501
  let isDosing = typeof obj.isDosing !== 'undefined' ? utils.makeBool(obj.isDosing) : chlor.isDosing;
2502
+ let disabled = typeof obj.disabled !== 'undefined' ? utils.makeBool(obj.disabled) : chlor.disabled;
2503
+ // This should never never never modify the setpoints based upon the disabled or isDosing flags.
2504
+ //let poolSetpoint = isDosing ? 100 : disabled ? 0 : parseInt(obj.poolSetpoint, 10);
2505
+ //let spaSetpoint = isDosing ? 100 : disabled ? 0 : parseInt(obj.spaSetpoint, 10);
2506
+ let poolSetpoint = typeof obj.poolSetpoint !== 'undefined' ? parseInt(obj.poolSetpoint, 10) : chlor.poolSetpoint;
2507
+ let spaSetpoint = typeof obj.spaSetpoint !== 'undefined' ? parseInt(obj.spaSetpoint, 10) : chlor.spaSetpoint;
2508
+ if (poolSetpoint === 0) console.log(obj);
2509
+
2452
2510
  let model = typeof obj.model !== 'undefined' ? obj.model : chlor.model;
2453
2511
  let chlorType = typeof obj.type !== 'undefined' ? sys.board.valueMaps.chlorinatorType.encode(obj.type) : chlor.type || 0;
2454
2512
  if (isAdd) {
@@ -2466,7 +2524,7 @@ class IntelliCenterChlorinatorCommands extends ChlorinatorCommands {
2466
2524
  if (typeof obj.disabled !== 'undefined') chlor.disabled = utils.makeBool(obj.disabled);
2467
2525
  if (typeof chlor.body === 'undefined') chlor.body = obj.body || 32;
2468
2526
  // Verify the data.
2469
- let body = sys.board.bodies.mapBodyAssociation(chlor.body);
2527
+ let body = sys.board.bodies.mapBodyAssociation(typeof obj.body === 'undefined' ? chlor.body || 0 : obj.body);
2470
2528
  if (typeof body === 'undefined') return Promise.reject(new InvalidEquipmentDataError(`Chlorinator body association is not valid: ${chlor.body}`, 'chlorinator', chlor.body));
2471
2529
  if (poolSetpoint > 100 || poolSetpoint < 0) return Promise.reject(new InvalidEquipmentDataError(`Chlorinator poolSetpoint is out of range: ${chlor.poolSetpoint}`, 'chlorinator', chlor.poolSetpoint));
2472
2530
  if (spaSetpoint > 100 || spaSetpoint < 0) return Promise.reject(new InvalidEquipmentDataError(`Chlorinator spaSetpoint is out of range: ${chlor.poolSetpoint}`, 'chlorinator', chlor.spaSetpoint));
@@ -2474,7 +2532,10 @@ class IntelliCenterChlorinatorCommands extends ChlorinatorCommands {
2474
2532
  return new Promise<ChlorinatorState>((resolve, reject) => {
2475
2533
  let out = Outbound.create({
2476
2534
  action: 168,
2477
- payload: [7, 0, id - 1, body.val, 1, disabled ? 0 : poolSetpoint, disabled ? 0 : spaSetpoint, superChlorinate ? 1 : 0, superChlorHours, 0, 1],
2535
+ payload: [7, 0, id - 1, body.val, 1,
2536
+ disabled ? 0 : isDosing ? 100 : poolSetpoint,
2537
+ disabled ? 0 : isDosing ? 100 : spaSetpoint,
2538
+ superChlorinate ? 1 : 0, superChlorHours, 0, 1],
2478
2539
  response: IntelliCenterBoard.getAckResponse(168),
2479
2540
  retries: 5,
2480
2541
  onComplete: (err, msg) => {
@@ -2482,6 +2543,7 @@ class IntelliCenterChlorinatorCommands extends ChlorinatorCommands {
2482
2543
  else {
2483
2544
  let schlor = state.chlorinators.getItemById(id, true);
2484
2545
  let cchlor = sys.chlorinators.getItemById(id, true);
2546
+ schlor.body = chlor.body = body.val;
2485
2547
  chlor.disabled = disabled;
2486
2548
  chlor.model = model;
2487
2549
  schlor.type = chlor.type = chlorType;
@@ -2502,21 +2564,22 @@ class IntelliCenterChlorinatorCommands extends ChlorinatorCommands {
2502
2564
  }
2503
2565
  public async deleteChlorAsync(obj: any): Promise<ChlorinatorState> {
2504
2566
  let id = parseInt(obj.id, 10);
2505
- if (isNaN(id)) obj.id = 1;
2506
-
2507
- // Merge all the information.
2508
- let chlor = state.chlorinators.getItemById(id);
2567
+ if (isNaN(id)) return Promise.reject(new InvalidEquipmentDataError(`Chlorinator id is not valid: ${obj.id}`, 'chlorinator', obj.id));
2568
+ let chlor = sys.chlorinators.getItemById(id);
2569
+ if (chlor.master === 1) return await super.deleteChlorAsync(obj);
2570
+ let schlor = state.chlorinators.getItemById(id);
2509
2571
  // Verify the data.
2510
2572
  return new Promise<ChlorinatorState>((resolve, reject) => {
2511
2573
  let out = Outbound.create({
2512
2574
  action: 168,
2513
- payload: [7, 0, id - 1, chlor.body || 0, 0, chlor.poolSetpoint || 0, chlor.spaSetpoint || 0, 0, chlor.superChlorHours || 0, 0, 0],
2575
+ payload: [7, 0, id - 1, schlor.body || 0, 0, schlor.poolSetpoint || 0, schlor.spaSetpoint || 0, 0, schlor.superChlorHours || 0, 0, 0],
2514
2576
  response: IntelliCenterBoard.getAckResponse(168),
2515
2577
  retries: 5,
2516
2578
  onComplete: (err, msg) => {
2517
2579
  if (err) reject(err);
2518
2580
  else {
2519
- let schlor = state.chlorinators.getItemById(id, true);
2581
+ ncp.chlorinators.deleteChlorinatorAsync(id).then(()=>{});
2582
+ schlor = state.chlorinators.getItemById(id, true);
2520
2583
  state.chlorinators.removeItemById(id);
2521
2584
  sys.chlorinators.removeItemById(id);
2522
2585
  resolve(schlor);
@@ -2605,7 +2668,7 @@ class IntelliCenterPumpCommands extends PumpCommands {
2605
2668
  let id = (typeof data.id === 'undefined' || data.id <= 0) ? sys.pumps.getNextEquipmentId(sys.board.equipmentIds.pumps) : parseInt(data.id, 10);
2606
2669
  if (isNaN(id)) return Promise.reject(new Error(`Invalid pump id: ${data.id}`));
2607
2670
  let pump = sys.pumps.getItemById(id, false);
2608
- if (data.master > 0 || pump.master > 0 || pump.isVirtual) return await super.setPumpAsync(data);
2671
+ if (data.master > 0 || pump.master > 0) return await super.setPumpAsync(data);
2609
2672
 
2610
2673
  // 0 6 10 11 12 15
2611
2674
  //[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]
@@ -2959,11 +3022,22 @@ class IntelliCenterBodyCommands extends BodyCommands {
2959
3022
  public async setHeatModeAsync(body: Body, mode: number): Promise<BodyTempState> {
2960
3023
  return new Promise<BodyTempState>((resolve, reject) => {
2961
3024
  const self = this;
2962
- let byte2 = 18;
2963
- let mode1 = sys.bodies.getItemById(1).setPoint || 100;
2964
- let mode2 = sys.bodies.getItemById(2).setPoint || 100;
2965
- let mode3 = sys.bodies.getItemById(3).setPoint || 100;
2966
- let mode4 = sys.bodies.getItemById(4).setPoint || 100;
3025
+ let byte2 = 22;
3026
+ let body1 = sys.bodies.getItemById(1);
3027
+ let body2 = sys.bodies.getItemById(2);
3028
+
3029
+ let heat1 = body1.heatSetpoint || 78;
3030
+ let cool1 = body1.coolSetpoint || 100;
3031
+ let heat2 = body2.heatSetpoint || 78;
3032
+ let cool2 = body2.coolSetpoint || 103;
3033
+
3034
+ let mode1 = body1.heatMode || 1;
3035
+ let mode2 = body2.heatMode || 1;
3036
+ let bitopts = 0;
3037
+ if (sys.general.options.clockSource) bitopts += 32;
3038
+ if (sys.general.options.clockMode === 24) bitopts += 64;
3039
+ if (sys.general.options.adjustDST) bitopts += 128;
3040
+
2967
3041
  switch (body.id) {
2968
3042
  case 1:
2969
3043
  byte2 = 22;
@@ -2973,19 +3047,12 @@ class IntelliCenterBodyCommands extends BodyCommands {
2973
3047
  byte2 = 23;
2974
3048
  mode2 = mode;
2975
3049
  break;
2976
- case 3:
2977
- byte2 = 24;
2978
- mode3 = mode;
2979
- break;
2980
- case 4:
2981
- byte2 = 25;
2982
- mode4 = mode;
2983
- break;
2984
3050
  }
2985
3051
  let out = Outbound.create({
2986
3052
  action: 168,
2987
- payload: [0, 0, byte2, 1, 0, 0, 129, 0, 0, 0, 0, 0, 0, 0, 176, 89, 27, 110, 3, 0, 0, 100, 100, 100, 100, mode1, mode2, mode3, mode4, 15, 0
2988
- , 0, 0, 0, 100, 0, 0, 0, 0, 0, 0],
3053
+ payload: [0, 0, byte2, 1, 0, 0, 129, 0, 0, 0, 0, 0, 0, 0, bitopts, 89, 27, 110, 3, 0, 0,
3054
+ heat1, cool1, heat2, cool2, mode1, mode2, 0, 0, 15,
3055
+ sys.general.options.pumpDelay ? 1 : 0, sys.general.options.cooldownDelay ? 1 : 0, 0, 100, 0, 0, 0, 0, sys.general.options.manualPriority ? 1 : 0, sys.general.options.manualHeat ? 1 : 0, 0],
2989
3056
  retries: 5,
2990
3057
  response: IntelliCenterBoard.getAckResponse(168),
2991
3058
  onComplete: (err, msg) => {
@@ -3006,31 +3073,25 @@ class IntelliCenterBodyCommands extends BodyCommands {
3006
3073
  let byte2 = 18;
3007
3074
  let body1 = sys.bodies.getItemById(1);
3008
3075
  let body2 = sys.bodies.getItemById(2);
3009
- let body3 = sys.bodies.getItemById(3);
3010
- let body4 = sys.bodies.getItemById(4);
3011
3076
 
3012
- let temp1 = sys.bodies.getItemById(1).setPoint || 100;
3013
- let temp2 = sys.bodies.getItemById(2).setPoint || 100;
3014
- let temp3 = sys.bodies.getItemById(3).setPoint || 100;
3015
- let temp4 = sys.bodies.getItemById(4).setPoint || 100;
3077
+ let heat1 = body1.heatSetpoint || 78;
3078
+ let cool1 = body1.coolSetpoint || 100;
3079
+ let heat2 = body2.heatSetpoint || 78;
3080
+ let cool2 = body2.coolSetpoint || 103;
3016
3081
  switch (body.id) {
3017
3082
  case 1:
3018
3083
  byte2 = 18;
3019
- temp1 = setPoint;
3084
+ heat1 = setPoint;
3020
3085
  break;
3021
3086
  case 2:
3022
3087
  byte2 = 20;
3023
- temp2 = setPoint;
3024
- break;
3025
- case 3:
3026
- byte2 = 19;
3027
- temp3 = setPoint;
3028
- break;
3029
- case 4:
3030
- byte2 = 21;
3031
- temp4 = setPoint;
3088
+ heat2 = setPoint;
3032
3089
  break;
3033
3090
  }
3091
+ let bitopts = 0;
3092
+ if (sys.general.options.clockSource) bitopts += 32;
3093
+ if (sys.general.options.clockMode === 24) bitopts += 64;
3094
+ if (sys.general.options.adjustDST) bitopts += 128;
3034
3095
  // 6 15 17 18 21 22 24 25
3035
3096
  //[255, 0, 255][165, 63, 15, 16, 168, 41][0, 0, 18, 1, 0, 0, 129, 0, 0, 0, 0, 0, 0, 0, 176, 89, 27, 110, 3, 0, 0, 89, 100, 98, 100, 0, 0, 0, 0, 15, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0][5, 243]
3036
3097
  //[255, 0, 255][165, 63, 15, 16, 168, 41][0, 0, 18, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 176, 235, 27, 167, 1, 0, 0, 89, 81, 98, 103, 5, 0, 0, 0, 15, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0][6, 48]
@@ -3038,8 +3099,8 @@ class IntelliCenterBodyCommands extends BodyCommands {
3038
3099
  action: 168,
3039
3100
  response: IntelliCenterBoard.getAckResponse(168),
3040
3101
  retries: 5,
3041
- payload: [0, 0, byte2, 1, 0, 0, 129, 0, 0, 0, 0, 0, 0, 0, 176, 89, 27, 110, 3, 0, 0,
3042
- temp1, temp3, temp2, temp4, body1.heatMode || 0, body2.heatMode || 0, body3.heatMode || 0, body4.heatMode || 0, 15,
3102
+ payload: [0, 0, byte2, 1, 0, 0, 129, 0, 0, 0, 0, 0, 0, 0, bitopts, 89, 27, 110, 3, 0, 0,
3103
+ heat1, cool1, heat2, cool2, body1.heatMode || 1, body2.heatMode || 1, 0, 0, 15,
3043
3104
  sys.general.options.pumpDelay ? 1 : 0, sys.general.options.cooldownDelay ? 1 : 0, 0, 100, 0, 0, 0, 0, sys.general.options.manualPriority ? 1 : 0, sys.general.options.manualHeat ? 1 : 0, 0]
3044
3105
  });
3045
3106
  return new Promise<BodyTempState>((resolve, reject) => {
@@ -3055,23 +3116,14 @@ class IntelliCenterBodyCommands extends BodyCommands {
3055
3116
  });
3056
3117
  }
3057
3118
  public async setCoolSetpointAsync(body: Body, setPoint: number): Promise<BodyTempState> {
3058
- //[165, 1, 15, 16, 168, 41][0, 0, 19, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 176, 110, 30, 188, 3, 0, 0, 76, 99, 78, 100, 5, 5, 0, 0, 15, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0][5, 33]
3059
- let byte2 = 19;
3119
+ let byte2 = 18;
3060
3120
  let body1 = sys.bodies.getItemById(1);
3061
3121
  let body2 = sys.bodies.getItemById(2);
3062
- let body3 = sys.bodies.getItemById(3);
3063
- let body4 = sys.bodies.getItemById(3);
3064
3122
 
3065
- let temp1 = sys.bodies.getItemById(1).setPoint || 100;
3066
- let cool1 = sys.bodies.getItemById(1).coolSetpoint || 100;
3067
- let temp2 = sys.bodies.getItemById(2).setPoint || 100;
3068
- let cool2 = sys.bodies.getItemById(2).coolSetpoint || 100;
3069
-
3070
- //Them
3071
- //[165, 63, 15, 16, 168, 41][0, 0, 19, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 176, 59, 30, 5, 5, 0, 0, 90, 102, 98, 81, 3, 1, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0][4, 129]
3072
- //Us
3073
- //[165, 63, 15, 33, 168, 40][0, 0, 19, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 176, 89, 27, 5, 5, 0, 0, 90, 103, 98, 81, 3, 1, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0][5, 249]
3074
- //[165, 63, 15, 33, 168, 40][0, 0, 19, 1, 0, 0, 129, 0, 0, 0, 0, 0, 0, 0, 176, 89, 27, 110, 3, 0, 0, 90, 103, 98, 81, 3, 1, 0, 0, 15, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0][5, 249]
3123
+ let heat1 = body1.heatSetpoint || 78;
3124
+ let cool1 = body1.coolSetpoint || 100;
3125
+ let heat2 = body2.heatSetpoint || 78;
3126
+ let cool2 = body2.coolSetpoint || 103;
3075
3127
  switch (body.id) {
3076
3128
  case 1:
3077
3129
  byte2 = 19;
@@ -3082,6 +3134,10 @@ class IntelliCenterBodyCommands extends BodyCommands {
3082
3134
  cool2 = setPoint;
3083
3135
  break;
3084
3136
  }
3137
+ let bitopts = 0;
3138
+ if (sys.general.options.clockSource) bitopts += 32;
3139
+ if (sys.general.options.clockMode === 24) bitopts += 64;
3140
+ if (sys.general.options.adjustDST) bitopts += 128;
3085
3141
  // 6 15 17 18 21 22 24 25
3086
3142
  //[255, 0, 255][165, 63, 15, 16, 168, 41][0, 0, 18, 1, 0, 0, 129, 0, 0, 0, 0, 0, 0, 0, 176, 89, 27, 110, 3, 0, 0, 89, 100, 98, 100, 0, 0, 0, 0, 15, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0][5, 243]
3087
3143
  //[255, 0, 255][165, 63, 15, 16, 168, 41][0, 0, 18, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 176, 235, 27, 167, 1, 0, 0, 89, 81, 98, 103, 5, 0, 0, 0, 15, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0][6, 48]
@@ -3089,8 +3145,8 @@ class IntelliCenterBodyCommands extends BodyCommands {
3089
3145
  action: 168,
3090
3146
  response: IntelliCenterBoard.getAckResponse(168),
3091
3147
  retries: 5,
3092
- payload: [0, 0, byte2, 1, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 176, 89, 27, 110, 3, 0, 0,
3093
- temp1, cool1, temp2, cool2, body1.heatMode || 0, body2.heatMode || 0, body3.heatMode || 0, body4.heatMode || 0, 15,
3148
+ payload: [0, 0, byte2, 1, 0, 0, 129, 0, 0, 0, 0, 0, 0, 0, bitopts, 89, 27, 110, 3, 0, 0,
3149
+ heat1, cool1, heat2, cool2, body1.heatMode || 1, body2.heatMode || 1, 0, 0, 15,
3094
3150
  sys.general.options.pumpDelay ? 1 : 0, sys.general.options.cooldownDelay ? 1 : 0, 0, 100, 0, 0, 0, 0, sys.general.options.manualPriority ? 1 : 0, sys.general.options.manualHeat ? 1 : 0, 0]
3095
3151
  });
3096
3152
  return new Promise<BodyTempState>((resolve, reject) => {
@@ -3098,7 +3154,7 @@ class IntelliCenterBodyCommands extends BodyCommands {
3098
3154
  if (err) reject(err);
3099
3155
  else {
3100
3156
  let bstate = state.temps.bodies.getItemById(body.id);
3101
- body.coolSetpoint = bstate.coolSetpoint = setPoint;
3157
+ body.heatSetpoint = bstate.heatSetpoint = setPoint;
3102
3158
  resolve(bstate);
3103
3159
  }
3104
3160
  };
@@ -3184,6 +3240,8 @@ class IntelliCenterScheduleCommands extends ScheduleCommands {
3184
3240
  out.retries = 5;
3185
3241
  out.onComplete = (err, msg) => {
3186
3242
  if (!err) {
3243
+ sched = sys.schedules.getItemById(id, true);
3244
+ ssched = state.schedules.getItemById(id, true);
3187
3245
  sched.circuit = ssched.circuit = circuit;
3188
3246
  sched.scheduleDays = ssched.scheduleDays = schedDays;
3189
3247
  sched.scheduleType = ssched.scheduleType = schedType;
@@ -3280,14 +3338,14 @@ class IntelliCenterHeaterCommands extends HeaterCommands {
3280
3338
  conn.queueSendMessage(out);
3281
3339
  }
3282
3340
  public async setHeaterAsync(obj: any): Promise<Heater> {
3283
- if (utils.makeBool(obj.isVirtual) || parseInt(obj.id, 10) > 255) return super.setHeaterAsync(obj);
3341
+ if (obj.master === 1 || parseInt(obj.id, 10) > 255) return super.setHeaterAsync(obj);
3284
3342
  return new Promise<Heater>((resolve, reject) => {
3285
3343
  let id = typeof obj.id === 'undefined' ? -1 : parseInt(obj.id, 10);
3286
3344
  if (isNaN(id)) return reject(new InvalidEquipmentIdError('Heater Id is not valid.', obj.id, 'Heater'));
3287
3345
  let heater: Heater;
3288
3346
  if (id <= 0) {
3289
3347
  // We are adding a heater. In this case all heaters are virtual.
3290
- let vheaters = sys.heaters.filter(h => h.isVirtual);
3348
+ let vheaters = sys.heaters.filter(h => h.master === 1);
3291
3349
  id = vheaters.length + 1;
3292
3350
  }
3293
3351
  heater = sys.heaters.getItemById(id, false);
@@ -3395,7 +3453,6 @@ class IntelliCenterHeaterCommands extends HeaterCommands {
3395
3453
  heater.economyTime = economyTime;
3396
3454
  heater.startTempDelta = startTempDelta;
3397
3455
  heater.stopTempDelta = stopTempDelta;
3398
- //hstate.isVirtual = heater.isVirtual = false;
3399
3456
  heater.cooldownDelay = cooldownDelay;
3400
3457
  sys.board.heaters.updateHeaterServices();
3401
3458
  sys.board.heaters.syncHeaterStates();
@@ -3408,7 +3465,7 @@ class IntelliCenterHeaterCommands extends HeaterCommands {
3408
3465
  });
3409
3466
  }
3410
3467
  public async deleteHeaterAsync(obj): Promise<Heater> {
3411
- if (utils.makeBool(obj.isVirtual) || obj.master === 1 || parseInt(obj.id, 10) > 255) return await super.deleteHeaterAsync(obj);
3468
+ if (obj.master === 1 || parseInt(obj.id, 10) > 255) return await super.deleteHeaterAsync(obj);
3412
3469
  return new Promise<Heater>((resolve, reject) => {
3413
3470
  let id = parseInt(obj.id, 10);
3414
3471
  if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError('Cannot delete. Heater Id is not valid.', obj.id, 'Heater'));
@@ -3450,6 +3507,8 @@ class IntelliCenterHeaterCommands extends HeaterCommands {
3450
3507
  let heatPumpInstalled = htypes.heatpump > 0;
3451
3508
  let gasHeaterInstalled = htypes.gas > 0;
3452
3509
  let ultratempInstalled = htypes.ultratemp > 0;
3510
+ let mastertempInstalled = htypes.mastertemp > 0;
3511
+
3453
3512
 
3454
3513
  // RKS: 09-26-20 This is a hack to maintain backward compatability with fw versions 1.04 and below. Ultratemp is not
3455
3514
  // supported on 1.04 and below.
@@ -3473,9 +3532,10 @@ class IntelliCenterHeaterCommands extends HeaterCommands {
3473
3532
  // 3 = Solar Heater
3474
3533
  // 4 = Solar Preferred
3475
3534
  // 5 = Heat Pump
3476
-
3535
+
3477
3536
  if (sys.heaters.length > 0) sys.board.valueMaps.heatSources = new byteValueMap([[1, { name: 'off', desc: 'Off' }]]);
3478
3537
  if (gasHeaterInstalled) sys.board.valueMaps.heatSources.merge([[2, { name: 'heater', desc: 'Heater' }]]);
3538
+ if (mastertempInstalled) sys.board.valueMaps.heatSources.merge([[11, { name: 'mtheater', desc: 'MasterTemp' }]]);
3479
3539
  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 }]]);
3480
3540
  else if (solarInstalled) sys.board.valueMaps.heatSources.merge([[3, { name: 'solar', desc: 'Solar', hasCoolsetpoint: htypes.hasCoolSetpoint }]]);
3481
3541
  if (heatPumpInstalled && (gasHeaterInstalled || solarInstalled)) sys.board.valueMaps.heatSources.merge([[9, { name: 'heatpump', desc: 'Heatpump Only' }], [25, { name: 'heatpumppref', desc: 'Heat Pump Pref' }]]);
@@ -3486,11 +3546,12 @@ class IntelliCenterHeaterCommands extends HeaterCommands {
3486
3546
 
3487
3547
  sys.board.valueMaps.heatModes = new byteValueMap([[1, { name: 'off', desc: 'Off' }]]);
3488
3548
  if (gasHeaterInstalled) sys.board.valueMaps.heatModes.merge([[2, { name: 'heater', desc: 'Heater' }]]);
3489
- if (solarInstalled && (gasHeaterInstalled || heatPumpInstalled)) sys.board.valueMaps.heatModes.merge([[3, { name: 'solar', desc: 'Solar Only' }], [4, { name: 'solarpref', desc: 'Solar Preferred' }]]);
3549
+ if (mastertempInstalled) sys.board.valueMaps.heatModes.merge([[11, { name: 'mtheater', desc: 'MasterTemp' }]]);
3550
+ if (solarInstalled && (gasHeaterInstalled || heatPumpInstalled || mastertempInstalled)) sys.board.valueMaps.heatModes.merge([[3, { name: 'solar', desc: 'Solar Only' }], [4, { name: 'solarpref', desc: 'Solar Preferred' }]]);
3490
3551
  else if (solarInstalled) sys.board.valueMaps.heatModes.merge([[3, { name: 'solar', desc: 'Solar' }]]);
3491
- if (ultratempInstalled && (gasHeaterInstalled || heatPumpInstalled)) sys.board.valueMaps.heatModes.merge([[5, { name: 'ultratemp', desc: 'UltraTemp Only'}], [6, { name: 'ultratemppref', desc: 'UltraTemp Pref' }]]);
3552
+ if (ultratempInstalled && (gasHeaterInstalled || heatPumpInstalled || mastertempInstalled)) sys.board.valueMaps.heatModes.merge([[5, { name: 'ultratemp', desc: 'UltraTemp Only' }], [6, { name: 'ultratemppref', desc: 'UltraTemp Pref' }]]);
3492
3553
  else if (ultratempInstalled) sys.board.valueMaps.heatModes.merge([[5, { name: 'ultratemp', desc: 'UltraTemp' }]]);
3493
- if (heatPumpInstalled && (gasHeaterInstalled || solarInstalled)) sys.board.valueMaps.heatModes.merge([[9, { name: 'heatpump', desc: 'Heatpump Only' }], [25, { name: 'heatpumppref', desc: 'Heat Pump Preferred' }]]);
3554
+ if (heatPumpInstalled && (gasHeaterInstalled || solarInstalled || mastertempInstalled)) sys.board.valueMaps.heatModes.merge([[9, { name: 'heatpump', desc: 'Heatpump Only' }], [25, { name: 'heatpumppref', desc: 'Heat Pump Preferred' }]]);
3494
3555
  else if (heatPumpInstalled) sys.board.valueMaps.heatModes.merge([[9, { name: 'heatpump', desc: 'Heat Pump' }]]);
3495
3556
  }
3496
3557
  else {
@@ -3522,7 +3583,7 @@ class IntelliCenterHeaterCommands extends HeaterCommands {
3522
3583
  }
3523
3584
  class IntelliCenterValveCommands extends ValveCommands {
3524
3585
  public async setValveAsync(obj?: any): Promise<Valve> {
3525
- if (obj.isVirtual || obj.master === 1) return super.setValveAsync(obj);
3586
+ if (obj.master === 1) return super.setValveAsync(obj);
3526
3587
  let id = parseInt(obj.id, 10);
3527
3588
  if (isNaN(id)) return Promise.reject(new InvalidEquipmentIdError('Valve Id has not been defined', obj.id, 'Valve'));
3528
3589
  let valve = sys.valves.getItemById(id);
@@ -3572,7 +3633,7 @@ export class IntelliCenterChemControllerCommands extends ChemControllerCommands
3572
3633
  // Now lets do all our validation to the incoming chem controller data.
3573
3634
  let name = typeof data.name !== 'undefined' ? data.name : chem.name || `IntelliChem - ${address - 143}`;
3574
3635
  let type = sys.board.valueMaps.chemControllerTypes.transformByName('intellichem');
3575
- // So now we are down to the nitty gritty setting the data for the REM or Homegrown Chem controller.
3636
+ // So now we are down to the nitty gritty setting the data for the REM Chem controller.
3576
3637
  let calciumHardness = typeof data.calciumHardness !== 'undefined' ? parseInt(data.calciumHardness, 10) : chem.calciumHardness;
3577
3638
  let cyanuricAcid = typeof data.cyanuricAcid !== 'undefined' ? parseInt(data.cyanuricAcid, 10) : chem.cyanuricAcid;
3578
3639
  let alkalinity = typeof data.alkalinity !== 'undefined' ? parseInt(data.alkalinity, 10) : chem.alkalinity;