nodejs-poolcontroller 7.5.1 → 7.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/.github/ISSUE_TEMPLATE/1-bug-report.yml +84 -0
  2. package/.github/ISSUE_TEMPLATE/2-docs.md +12 -0
  3. package/.github/ISSUE_TEMPLATE/3-proposal.md +28 -0
  4. package/.github/ISSUE_TEMPLATE/config.yml +8 -0
  5. package/Changelog +19 -0
  6. package/Dockerfile +3 -3
  7. package/README.md +13 -8
  8. package/app.ts +1 -1
  9. package/config/Config.ts +38 -2
  10. package/config/VersionCheck.ts +27 -12
  11. package/controller/Constants.ts +2 -1
  12. package/controller/Equipment.ts +193 -9
  13. package/controller/Errors.ts +10 -0
  14. package/controller/Lockouts.ts +503 -0
  15. package/controller/State.ts +269 -64
  16. package/controller/boards/AquaLinkBoard.ts +1000 -0
  17. package/controller/boards/BoardFactory.ts +4 -0
  18. package/controller/boards/EasyTouchBoard.ts +468 -144
  19. package/controller/boards/IntelliCenterBoard.ts +466 -307
  20. package/controller/boards/IntelliTouchBoard.ts +37 -5
  21. package/controller/boards/NixieBoard.ts +671 -141
  22. package/controller/boards/SystemBoard.ts +1397 -641
  23. package/controller/comms/Comms.ts +462 -362
  24. package/controller/comms/messages/Messages.ts +174 -30
  25. package/controller/comms/messages/config/ChlorinatorMessage.ts +6 -3
  26. package/controller/comms/messages/config/CircuitMessage.ts +1 -0
  27. package/controller/comms/messages/config/ExternalMessage.ts +10 -8
  28. package/controller/comms/messages/config/HeaterMessage.ts +141 -29
  29. package/controller/comms/messages/config/OptionsMessage.ts +9 -2
  30. package/controller/comms/messages/config/PumpMessage.ts +53 -35
  31. package/controller/comms/messages/config/ScheduleMessage.ts +33 -25
  32. package/controller/comms/messages/config/ValveMessage.ts +2 -2
  33. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +38 -86
  34. package/controller/comms/messages/status/EquipmentStateMessage.ts +59 -23
  35. package/controller/comms/messages/status/HeaterStateMessage.ts +57 -3
  36. package/controller/comms/messages/status/IntelliChemStateMessage.ts +56 -8
  37. package/controller/comms/messages/status/PumpStateMessage.ts +23 -1
  38. package/controller/nixie/Nixie.ts +1 -1
  39. package/controller/nixie/bodies/Body.ts +3 -0
  40. package/controller/nixie/chemistry/ChemController.ts +164 -51
  41. package/controller/nixie/chemistry/Chlorinator.ts +137 -88
  42. package/controller/nixie/circuits/Circuit.ts +51 -19
  43. package/controller/nixie/heaters/Heater.ts +241 -31
  44. package/controller/nixie/pumps/Pump.ts +488 -206
  45. package/controller/nixie/schedules/Schedule.ts +91 -35
  46. package/controller/nixie/valves/Valve.ts +1 -1
  47. package/defaultConfig.json +20 -0
  48. package/package.json +21 -21
  49. package/web/Server.ts +94 -49
  50. package/web/bindings/aqualinkD.json +505 -0
  51. package/web/bindings/influxDB.json +71 -1
  52. package/web/bindings/mqtt.json +98 -39
  53. package/web/bindings/mqttAlt.json +59 -1
  54. package/web/interfaces/baseInterface.ts +1 -0
  55. package/web/interfaces/httpInterface.ts +23 -2
  56. package/web/interfaces/influxInterface.ts +45 -10
  57. package/web/interfaces/mqttInterface.ts +114 -54
  58. package/web/services/config/Config.ts +55 -132
  59. package/web/services/state/State.ts +81 -4
  60. package/web/services/state/StateSocket.ts +4 -4
  61. package/web/services/utilities/Utilities.ts +8 -6
  62. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -52
  63. package/config copy.json +0 -300
  64. package/issue_template.md +0 -52
@@ -64,6 +64,7 @@ export class HeaterMessage {
64
64
  case ControllerType.IntelliTouch:
65
65
  case ControllerType.EasyTouch:
66
66
  HeaterMessage.processIntelliTouch(msg);
67
+ break;
67
68
  }
68
69
  }
69
70
  private static processIntelliTouch(msg: Inbound) {
@@ -71,8 +72,22 @@ export class HeaterMessage {
71
72
  // Gas heater setup in EquipmentStateMessage upon *Touch discovery
72
73
  switch (msg.action) {
73
74
  // 1 = gas heater, 2 = solar, 3 = heat pump
74
- case 34:
75
- case 162:
75
+ case 34: // Somebody requested the configuration
76
+ case 162: // Somebody set the configuration
77
+ // Release any heaters that are tied to Nixie that should not be.
78
+ for (let i = sys.heaters.length - 1; i >= 0; i--) {
79
+ let heater = sys.heaters.getItemByIndex(i);
80
+ if (heater.id <= 4 && heater.master === 1) {
81
+ // Return the heater control back to Touch
82
+ (async function () {
83
+ try {
84
+ await ncp.heaters.deleteHeaterAsync(heater.id);
85
+ logger.debug(`${heater.name} control returned to OCP.`);
86
+ }
87
+ catch (err) { logger.error(`Error with OCP reclaiming control over gas ${heater.name}: ${err}`) }
88
+ })();
89
+ }
90
+ }
76
91
  // byte 0
77
92
  // 21 = solar or heat pump disabled
78
93
  // 23 = solar or heat pump enabled
@@ -94,6 +109,121 @@ export class HeaterMessage {
94
109
  // on/off (16) = solar as a heat pump
95
110
  // bits 7,8 = stop temp delta
96
111
 
112
+
113
+
114
+ // Determine what type of heater should be in id number 1. If this is a hybrid heater
115
+ // we will be installing it in id 1 although it will perform dual purpose.
116
+ let hasHeater = (msg.extractPayloadByte(0) & 0x01) === 0x01 || true;
117
+ let hasSolar = (msg.extractPayloadByte(0) & 0x02) === 0x02;
118
+ let hasHeatpump = (msg.extractPayloadByte(1) & 0x20) === 0x20;
119
+ let hasHybrid = !hasHeatpump && (msg.extractPayloadByte(1) & 0x10) === 0x10;
120
+
121
+ // Ok so it appears that the heater ids are as follows.
122
+ // 1 = Gas Heater
123
+ // 2 = Solar
124
+ // 3 = UltraTemp (HEATPUMPCOM)
125
+ // 4 = UltraTemp ETi (Hybrid)
126
+ // If an UltraTemp ETi is installed, no other heaters can be installed so they should be removed. This is not
127
+ // an available option for IntelliTouch i10D so it does not apply to it.
128
+ if (!hasHeater) {
129
+ sys.heaters.removeItemById(1);
130
+ state.heaters.removeItemById(1);
131
+ sys.heaters.removeItemById(2);
132
+ state.heaters.removeItemById(2);
133
+ sys.heaters.removeItemById(3);
134
+ state.heaters.removeItemById(3);
135
+ sys.heaters.removeItemById(4);
136
+ state.heaters.removeItemById(4);
137
+ }
138
+ else {
139
+ if (hasHybrid) {
140
+ sys.heaters.removeItemById(1);
141
+ state.heaters.removeItemById(1);
142
+ sys.heaters.removeItemById(2);
143
+ state.heaters.removeItemById(2);
144
+ sys.heaters.removeItemById(3);
145
+ state.heaters.removeItemById(3);
146
+ let hybrid = sys.heaters.getItemById(4, true);
147
+ let shybrid = state.heaters.getItemById(4, true);
148
+ // [5, { name: 'hybrid', desc: 'Hybrid', hasAddress: true }],
149
+ shybrid.type = hybrid.type = 5;
150
+ hybrid.address = 112; // Touch only supports address 1.
151
+ hybrid.isActive = true;
152
+ hybrid.master = 0;
153
+ hybrid.body = sys.equipment.shared ? 32 : 0;
154
+ if (typeof hybrid.name === 'undefined') shybrid.name = hybrid.name = 'UltraTemp ETi';
155
+ // The following 2 values need to come from somewhere.
156
+ if (typeof hybrid.economyTime === 'undefined') hybrid.economyTime = 1;
157
+ if (typeof hybrid.maxBoostTemp === 'undefined') hybrid.maxBoostTemp = 5;
158
+ hasHeatpump = false; // You cannot have a heatpump and a hybrid heater.
159
+ }
160
+ else {
161
+ // Hybrid heaters and gas heaters cannot co-exist but it appears you cannot disable the gas
162
+ // heater on the touch panels.
163
+ sys.heaters.removeItemById(4);
164
+ state.heaters.removeItemById(4);
165
+ let heater = sys.heaters.getItemById(1, true);
166
+ let hstate = state.heaters.getItemById(1, true);
167
+ heater.body = sys.equipment.shared ? 32 : 0;
168
+ // [1, { name: 'gas', desc: 'Gas Heater', hasAddress: false }],
169
+ heater.type = hstate.type = 1;
170
+ heater.isActive = true;
171
+ heater.master = 0;
172
+ if (typeof heater.name === 'undefined') heater.name = hstate.name = 'Gas Heater';
173
+ if (typeof heater.cooldownDelay === 'undefined') heater.cooldownDelay = 5;
174
+ }
175
+ // Check to see if a heatpump is installed. This will replace the solar heater so they cannot coexist.
176
+ if (hasHeatpump) {
177
+ // Remove the solar heater. This will be replaced with the heatpump.
178
+ sys.heaters.removeItemById(2);
179
+ state.heaters.removeItemById(2);
180
+ let heatpump = sys.heaters.getItemById(3, true);
181
+ let sheatpump = state.heaters.getItemById(3, true);
182
+ // [3, { name: 'heatpump', desc: 'Heat Pump', hasAddress: true, hasPreference: true }],
183
+ heatpump.type = sheatpump.type = 3;
184
+ heatpump.body = sys.equipment.shared ? 32 : 0;
185
+ heatpump.isActive = true;
186
+ heatpump.master = 0;
187
+
188
+ if (typeof heatpump.name === 'undefined') sheatpump.name = heatpump.name = 'UltraTemp';
189
+ heatpump.heatingEnabled = (msg.extractPayloadByte(1) & 0x01) === 1;
190
+ heatpump.coolingEnabled = (msg.extractPayloadByte(1) & 0x02) === 2;
191
+ sys.board.equipmentIds.invalidIds.add(20); // exclude Aux Extra
192
+ }
193
+ else if (hasSolar) {
194
+ sys.heaters.removeItemById(3);
195
+ state.heaters.removeItemById(3);
196
+ let solar = sys.heaters.getItemById(2, true);
197
+ let ssolar = sys.heaters.getItemById(2, true);
198
+ // [2, { name: 'solar', desc: 'Solar Heater', hasAddress: false, hasPreference: true }],
199
+ solar.type = ssolar.type = 2;
200
+ solar.body = sys.equipment.shared ? 32 : 0;
201
+ solar.isActive = true;
202
+ solar.master = 0;
203
+ if (typeof solar.name === 'undefined') solar.name = ssolar.name = 'Solar Heater';
204
+ solar.freeze = (msg.extractPayloadByte(1) & 0x80) >> 7 === 1;
205
+ solar.coolingEnabled = (msg.extractPayloadByte(1) & 0x20) >> 5 === 1;
206
+ solar.startTempDelta = ((msg.extractPayloadByte(2) & 0xE) >> 1) + 3;
207
+ solar.stopTempDelta = ((msg.extractPayloadByte(2) & 0xC0) >> 6) + 2;
208
+ sys.board.equipmentIds.invalidIds.add(20); // exclude Aux Extra
209
+ }
210
+ else {
211
+ sys.board.equipmentIds.invalidIds.remove(20); // Allow access to Aux Extra
212
+ }
213
+ }
214
+ sys.board.heaters.updateHeaterServices();
215
+ for (let i = 0; i < sys.bodies.length; i++) {
216
+ let body = sys.bodies.getItemByIndex(i);
217
+ let btemp = state.temps.bodies.getItemById(body.id, body.isActive !== false);
218
+ let opts = sys.board.heaters.getInstalledHeaterTypes(body.id);
219
+ btemp.heaterOptions = opts;
220
+ }
221
+ sys.board.heaters.syncHeaterStates();
222
+ sys.equipment.setEquipmentIds();
223
+ msg.isProcessed = true;
224
+
225
+
226
+ /*
97
227
  // gas heater only; solar/heatpump/ultratemp disabled
98
228
  if ((msg.extractPayloadByte(0) & 0x2) === 0) {
99
229
  let heater = sys.heaters.getItemById(1);
@@ -112,14 +242,14 @@ export class HeaterMessage {
112
242
  sys.heaters.getItemById(3).isActive = false;
113
243
  sys.heaters.getItemById(4).isActive = false;
114
244
  sys.board.equipmentIds.invalidIds.remove(20); // include Aux Extra
115
- /* sys.equipment.setEquipmentIds();
116
- for (let i = 0; i < sys.bodies.length; i++) {
117
- let body = sys.bodies.getItemByIndex(i);
118
- let btemp = state.temps.bodies.getItemById(body.id, body.isActive !== false);
119
- let opts = sys.board.heaters.getInstalledHeaterTypes(body.id);
120
- btemp.heaterOptions = opts;
121
- }
122
- return; */
245
+ //sys.equipment.setEquipmentIds();
246
+ //for (let i = 0; i < sys.bodies.length; i++) {
247
+ // let body = sys.bodies.getItemByIndex(i);
248
+ // let btemp = state.temps.bodies.getItemById(body.id, body.isActive !== false);
249
+ // let opts = sys.board.heaters.getInstalledHeaterTypes(body.id);
250
+ // btemp.heaterOptions = opts;
251
+ //}
252
+ return;
123
253
  }
124
254
  // Ultratemp (+ cooling?);
125
255
  else if ((msg.extractPayloadByte(2) & 0x30) === 0x30) {
@@ -220,27 +350,9 @@ export class HeaterMessage {
220
350
  sys.board.heaters.syncHeaterStates();
221
351
  sys.equipment.setEquipmentIds();
222
352
  msg.isProcessed = true;
353
+ */
223
354
  break;
224
- case 168:
225
- {
226
- // IntelliChem Installed
227
- if ((msg.extractPayloadByte(3) & 0x01) === 1) {
228
- // only support for 1 ic with EasyTouch
229
- let chem = sys.chemControllers.getItemByAddress(144, true);
230
- let schem = state.chemControllers.getItemById(chem.id, true);
231
- chem.ph.tank.capacity = chem.orp.tank.capacity = 6;
232
- chem.ph.tank.units = chem.orp.tank.units = '';
233
355
 
234
- }
235
- else {
236
- let chem = sys.chemControllers.getItemByAddress(144);
237
- state.chemControllers.removeItemById(chem.id);
238
- sys.chemControllers.removeItemById(chem.id);
239
- }
240
- // Spa Manual Heat on/off
241
- sys.general.options.manualHeat = msg.extractPayloadByte(4) === 1 ? true : false;
242
- msg.isProcessed = true;
243
- }
244
356
  }
245
357
  }
246
358
  private static processCooldownDelay(msg: Inbound) {
@@ -139,16 +139,22 @@ export class OptionsMessage {
139
139
  break;
140
140
  }
141
141
  case 40:
142
+ case 168:
143
+ {
144
+
142
145
  // [165,33,16,34,168,10],[0,0,0,254,0,0,0,0,0,0],[2,168 = manual heat mode off
143
146
  // [165,33,16,34,168,10],[0,0,0,254,1,0,0,0,0,0],[2,169] = manual heat mode on
144
147
  sys.general.options.manualHeat = msg.extractPayloadByte(4) === 1;
148
+ // From https://github.com/tagyoureit/nodejs-poolController/issues/362 = Intellitouch
149
+ // [0,0,0,0,1,x,0,0,0,0] x=0 Manual OP heat Off; x=1 Manual OP heat On
150
+ sys.general.options.manualPriority = msg.extractPayloadByte(5) === 1;
145
151
  if ((msg.extractPayloadByte(3) & 0x01) === 1) {
146
152
  // only support for 1 ic with EasyTouch
147
153
  let chem = sys.chemControllers.getItemByAddress(144, true);
148
- let schem = state.chemControllers.getItemById(chem.id, true);
154
+ //let schem = state.chemControllers.getItemById(chem.id, true);
149
155
  chem.ph.tank.capacity = chem.orp.tank.capacity = 6;
150
156
  chem.ph.tank.units = chem.orp.tank.units = '';
151
-
157
+
152
158
  }
153
159
  else {
154
160
  let chem = sys.chemControllers.getItemByAddress(144);
@@ -157,6 +163,7 @@ export class OptionsMessage {
157
163
  }
158
164
  msg.isProcessed = true;
159
165
  break;
166
+ }
160
167
  }
161
168
  }
162
169
  }
@@ -36,43 +36,60 @@ export class PumpMessage {
36
36
  // packet 24/27/152/155 - Pump Config: IntelliTouch
37
37
  const pumpId = msg.extractPayloadByte(0);
38
38
  let type = msg.extractPayloadByte(1); // Avoid setting this then setting it back if we are mapping to a different value.
39
+ let isActive = type !== 0 && pumpId <= sys.equipment.maxPumps;
39
40
  // RKS: 04-14-21 - Only create the pump if it is available. If the pump was previously defined as another type
40
41
  // then it will be removed and recreated.
41
- let pump: Pump = sys.pumps.getItemById(pumpId, pumpId <= sys.equipment.maxPumps && type !== 0);
42
- if (pump.type !== type && type !== 0) {
43
- sys.pumps.removeItemById(pumpId);
44
- pump = sys.pumps.getItemById(pumpId, true);
45
- }
46
- pump.address = pumpId + 95;
47
- switch (type) {
48
- case 0: // none
49
- pump.type = 0;
50
- pump.isActive = false;
51
- break;
52
- case 64: // vsf
53
- pump.type = type;
54
- pump.isActive = true;
55
- PumpMessage.processVSF_IT(msg);
56
- break;
57
- case 255: // vs 3050 on old panels.
58
- case 128: // vs
59
- case 134: // vs Ultra Efficiency
60
- pump.type = 128;
61
- pump.isActive = true;
62
- PumpMessage.processVS_IT(msg);
63
- break;
64
- case 169: // vs+svrs
65
- pump.type = 169;
66
- pump.isActive = true;
67
- PumpMessage.processVS_IT(msg);
68
- break;
69
- default: // vf - type is the background circuit
70
- pump.type = 1; // force to type 1?
71
- pump.isActive = true;
72
- PumpMessage.processVF_IT(msg);
73
- break;
74
- }
75
- if (pump.isActive) {
42
+ let pump: Pump = sys.pumps.getItemById(pumpId, isActive);
43
+ if(isActive) {
44
+ // Remap the combination pump types.
45
+ switch (type) {
46
+ case 0:
47
+ case 64:
48
+ case 169:
49
+ break;
50
+ case 255:
51
+ case 128:
52
+ case 134:
53
+ type = 128;
54
+ break;
55
+ default:
56
+ type = 1;
57
+ break;
58
+ }
59
+ if (pump.type !== type) {
60
+ sys.pumps.removeItemById(pumpId);
61
+ pump = sys.pumps.getItemById(pumpId, isActive);
62
+ }
63
+ pump.address = pumpId + 95;
64
+ pump.master = 0;
65
+ switch (type) {
66
+ case 0: // none
67
+ pump.type = 0;
68
+ pump.isActive = false;
69
+ break;
70
+ case 64: // vsf
71
+ pump.type = type;
72
+ pump.isActive = true;
73
+ PumpMessage.processVSF_IT(msg);
74
+ break;
75
+ case 255: // vs 3050 on old panels.
76
+ case 128: // vs
77
+ case 134: // vs Ultra Efficiency
78
+ pump.type = 128;
79
+ pump.isActive = true;
80
+ PumpMessage.processVS_IT(msg);
81
+ break;
82
+ case 169: // vs+svrs
83
+ pump.type = 169;
84
+ pump.isActive = true;
85
+ PumpMessage.processVS_IT(msg);
86
+ break;
87
+ default: // vf - type is the background circuit
88
+ pump.type = 1; // force to type 1?
89
+ pump.isActive = true;
90
+ PumpMessage.processVF_IT(msg);
91
+ break;
92
+ }
76
93
  if (typeof pump.name === 'undefined') pump.name = sys.board.valueMaps.pumpTypes.get(pump.type).desc;
77
94
  const spump = state.pumps.getItemById(pump.id, true);
78
95
  spump.name = pump.name;
@@ -224,6 +241,7 @@ export class PumpMessage {
224
241
  }
225
242
  if (typeof pump.model === 'undefined') pump.model = 0;
226
243
  pump.type = type;
244
+ pump.master = 0;
227
245
  let spump = state.pumps.getItemById(pump.id, true);
228
246
  spump.type = pump.type;
229
247
  spump.isActive = pump.isActive = true;
@@ -48,10 +48,10 @@ export class ScheduleMessage {
48
48
  case 6:
49
49
  ScheduleMessage.processCircuit(msg);
50
50
  break;
51
- case 8: // Run Once Flags
51
+ case 8: // Schedule Type
52
52
  case 9:
53
53
  case 10:
54
- ScheduleMessage.processRunOnce(msg);
54
+ ScheduleMessage.processScheduleType(msg);
55
55
  break;
56
56
  case 11:
57
57
  case 12:
@@ -127,6 +127,7 @@ export class ScheduleMessage {
127
127
  const schedule: Schedule = sys.schedules.getItemByIndex(i);
128
128
  if (schedule.scheduleType === sys.board.valueMaps.scheduleTypes.getValue('runonce') && schedule.circuit === eggTimer.circuit){
129
129
  const sstate = state.schedules.getItemById(schedule.id);
130
+ schedule.master = 0;
130
131
  sstate.endTime = schedule.endTime = (schedule.startTime + eggTimer.runTime) % 1440; // remove days if we go past midnight
131
132
  }
132
133
  }
@@ -150,6 +151,7 @@ export class ScheduleMessage {
150
151
  if (sys.circuits.getItemById(schedule.circuit).hasHeatSource && typeof schedule.heatSource === 'undefined') schedule.heatSource = sys.board.valueMaps.heatSources.getValue('nochange');
151
152
  // todo: add to base sched item
152
153
  // (msg.extractPayloadByte(1) & 128) === 1 ? schedule.smartStart = 1 : schedule.smartStart = 0;
154
+ schedule.master = 0;
153
155
  if (schedule.isActive) {
154
156
  const sstate = state.schedules.getItemById(schedule.id, true);
155
157
  sstate.circuit = schedule.circuit;
@@ -191,7 +193,7 @@ export class ScheduleMessage {
191
193
  }
192
194
  private static processStartDay(msg: Inbound) {
193
195
  let schedId = (msg.extractPayloadByte(1) - 17) * 40 + 1;
194
- for (let i = 1; i < msg.payload.length; i++) {
196
+ for (let i = 1; i < msg.payload.length && schedId <= ScheduleMessage._maxSchedId; i++) {
195
197
  const schedule: Schedule = sys.schedules.getItemById(schedId++, false, { isActive: false });
196
198
  if (schedule.isActive !== false) {
197
199
  schedule.startDay = msg.extractPayloadByte(i + 1);
@@ -203,7 +205,7 @@ export class ScheduleMessage {
203
205
  }
204
206
  private static processStartYear(msg: Inbound) {
205
207
  let schedId = (msg.extractPayloadByte(1) - 20) * 40 + 1;
206
- for (let i = 1; i < msg.payload.length; i++) {
208
+ for (let i = 1; i < msg.payload.length && schedId <= ScheduleMessage._maxSchedId; i++) {
207
209
  const schedule: Schedule = sys.schedules.getItemById(schedId++, false, { isActive: false });
208
210
  if (schedule.isActive !== false) {
209
211
  schedule.startYear = msg.extractPayloadByte(i + 1);
@@ -213,22 +215,15 @@ export class ScheduleMessage {
213
215
  }
214
216
  private static processStartTimes(msg: Inbound) {
215
217
  let schedId = msg.extractPayloadByte(1) * 20 + 1;
216
- for (let i = 1; i < msg.payload.length - 1;) {
217
- let time = msg.extractPayloadInt(i + 1);
218
- let schedule: Schedule = sys.schedules.getItemById(schedId++, time !== 0);
219
- if (time !== 0) {
220
- schedule.startTime = time;
221
- schedule.isActive = schedule.startTime !== 0;
218
+ for (let i = 1; i < msg.payload.length - 1 && schedId <= ScheduleMessage._maxSchedId;) {
219
+ let schedule: Schedule = sys.schedules.getItemById(schedId++, false, { isActive: false });
220
+ if (schedule.isActive) {
221
+ schedule.startTime = msg.extractPayloadInt(i + 1);
222
222
  let csched = state.schedules.getItemById(schedule.id, true);
223
223
  csched.startTime = schedule.startTime;
224
224
  }
225
- else {
226
- state.schedules.removeItemById(schedule.id);
227
- sys.schedules.removeItemById(schedule.id);
228
- }
229
225
  i += 2;
230
226
  }
231
- ScheduleMessage._maxSchedId = sys.schedules.getMaxId(true, 0);
232
227
  msg.isProcessed = true;
233
228
  }
234
229
  private static processEndTimes(msg: Inbound) {
@@ -238,7 +233,7 @@ export class ScheduleMessage {
238
233
  const schedule: Schedule = sys.schedules.getItemById(schedId++, false, { isActive: false });
239
234
  if (schedule.isActive !== false) {
240
235
  schedule.endTime = time;
241
- let csched = state.schedules.getItemById(schedule.id);
236
+ let csched = state.schedules.getItemById(schedule.id, true);
242
237
  csched.endTime = schedule.endTime;
243
238
  }
244
239
  i += 2;
@@ -250,11 +245,10 @@ export class ScheduleMessage {
250
245
  for (let i = 1; i < msg.payload.length && schedId <= ScheduleMessage._maxSchedId; i++) {
251
246
  let schedule: Schedule = sys.schedules.getItemById(schedId++, false, { isActive: false });
252
247
  if (schedule.isActive) {
253
- let csched = state.schedules.getItemById(schedule.id);
248
+ let csched = state.schedules.getItemById(schedule.id, true);
254
249
  schedule.circuit = msg.extractPayloadByte(i + 1) + 1;
255
250
  if (schedule.circuit === 256 || schedule.circuit === 0) {
256
- // This is some of the IntelliCenter craziness where the schedule has a start time but
257
- // the circuit is undefined.
251
+ // This is some of the IntelliCenter craziness where the schedule is marked as active but the circuit is not defined.
258
252
  csched.isActive = false;
259
253
  state.schedules.removeItemById(schedule.id);
260
254
  sys.schedules.removeItemById(schedule.id);
@@ -265,13 +259,16 @@ export class ScheduleMessage {
265
259
  }
266
260
  msg.isProcessed = true;
267
261
  }
268
- private static processRunOnce(msg: Inbound) {
262
+ private static processScheduleType(msg: Inbound) {
269
263
  let schedId = (msg.extractPayloadByte(1) - 8) * 40 + 1;
270
- for (let i = 1; i < msg.payload.length && schedId <= ScheduleMessage._maxSchedId; i++) {
271
- let schedule: Schedule = sys.schedules.getItemById(schedId++, false, { isActive: false });
272
- if (schedule.isActive !== false) {
273
- let byte = msg.extractPayloadByte(i + 1);
274
- // schedule.runOnce = byte;
264
+ for (let i = 1; i < msg.payload.length; i++) {
265
+ let byte = msg.extractPayloadByte(i + 1);
266
+ let schedule: Schedule = sys.schedules.getItemById(schedId++, (byte & 128) === 128);
267
+ if ((byte & 128) === 128) {
268
+ // If bit 8 is set on the time type then this indicates whether the schedule is active. If it is not
269
+ // active then we will be removing it.
270
+ schedule.isActive = true;
271
+ schedule.master = 0;
275
272
  schedule.scheduleType = (byte & 1 & 0xFF) === 1 ? 0 : 128;
276
273
  if ((byte & 4 & 0xFF) === 4) schedule.startTimeType = 1;
277
274
  else if ((byte & 8 & 0xFF) === 8) schedule.startTimeType = 2;
@@ -281,11 +278,22 @@ export class ScheduleMessage {
281
278
  else if ((byte & 32 & 0xFF) === 32) schedule.endTimeType = 2;
282
279
  else schedule.endTimeType = 0;
283
280
  let csched = state.schedules.getItemById(schedule.id);
281
+ csched.isActive = true;
284
282
  csched.startTimeType = schedule.startTimeType;
285
283
  csched.endTimeType = schedule.endTimeType;
286
284
  csched.scheduleType = schedule.scheduleType;
287
285
  }
286
+ else {
287
+ // Now we need to remove this pig because this is not an active schedule.
288
+ sys.schedules.removeItemById(schedule.id);
289
+ if (schedule.isActive) {
290
+ let csched = state.schedules.getItemById(schedule.id);
291
+ schedule.isActive = csched.isActive = false;
292
+ }
293
+ state.schedules.removeItemById(schedule.id);
294
+ }
288
295
  }
296
+ ScheduleMessage._maxSchedId = sys.schedules.getMaxId(true, 0);
289
297
  msg.isProcessed = true;
290
298
  }
291
299
  private static processDays(msg: Inbound) {
@@ -131,7 +131,7 @@ export class ValveMessage {
131
131
  // there is no circuit. The circuitry for the valve always exists although I am not sure
132
132
  // how the valve expansion is represented.
133
133
  valve.isActive = true;
134
- valve.isReturn = false;
134
+ valve.isReturn = false;
135
135
  valve.isIntake = false;
136
136
  valve.type = 0;
137
137
  // Allow users to name the valve whatever they want. *Touch apparently only allows the valve to be named the same
@@ -174,7 +174,7 @@ export class ValveMessage {
174
174
  // for i10d.
175
175
  let ndx: number = 2;
176
176
  let id = 1;
177
- for (let i = 0; i < sys.equipment.maxValves - 1; i++) {
177
+ for (let i = 0; i < sys.equipment.maxValves; i++) {
178
178
  if (id === 3 && !sys.equipment.shared) {
179
179
  // The intake/return valves are skipped for non-shared systems.
180
180
  sys.valves.removeItemById(3);