nodejs-poolcontroller 8.0.1 → 8.0.4

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 (41) hide show
  1. package/.github/workflows/docker-publish-njsPC-linux.yml +50 -0
  2. package/Dockerfile +5 -3
  3. package/README.md +4 -1
  4. package/config/Config.ts +1 -1
  5. package/controller/Constants.ts +164 -67
  6. package/controller/Equipment.ts +78 -18
  7. package/controller/Lockouts.ts +15 -0
  8. package/controller/State.ts +281 -7
  9. package/controller/boards/EasyTouchBoard.ts +225 -101
  10. package/controller/boards/IntelliCenterBoard.ts +84 -23
  11. package/controller/boards/IntelliTouchBoard.ts +2 -4
  12. package/controller/boards/NixieBoard.ts +84 -27
  13. package/controller/boards/SunTouchBoard.ts +8 -2
  14. package/controller/boards/SystemBoard.ts +3262 -3242
  15. package/controller/comms/ScreenLogic.ts +47 -44
  16. package/controller/comms/messages/Messages.ts +4 -4
  17. package/controller/comms/messages/config/ChlorinatorMessage.ts +10 -3
  18. package/controller/comms/messages/config/ExternalMessage.ts +4 -1
  19. package/controller/comms/messages/config/PumpMessage.ts +8 -7
  20. package/controller/comms/messages/config/RemoteMessage.ts +48 -43
  21. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +8 -2
  22. package/controller/comms/messages/status/EquipmentStateMessage.ts +9 -4
  23. package/controller/comms/messages/status/PumpStateMessage.ts +1 -1
  24. package/controller/nixie/NixieEquipment.ts +1 -1
  25. package/controller/nixie/bodies/Body.ts +1 -1
  26. package/controller/nixie/chemistry/ChemController.ts +37 -28
  27. package/controller/nixie/circuits/Circuit.ts +48 -7
  28. package/controller/nixie/heaters/Heater.ts +24 -5
  29. package/controller/nixie/pumps/Pump.ts +159 -97
  30. package/controller/nixie/schedules/Schedule.ts +207 -126
  31. package/defaultConfig.json +3 -3
  32. package/logger/DataLogger.ts +7 -7
  33. package/package.json +2 -2
  34. package/sendSocket.js +32 -0
  35. package/web/Server.ts +17 -11
  36. package/web/bindings/homeassistant.json +2 -2
  37. package/web/interfaces/mqttInterface.ts +18 -18
  38. package/web/services/config/Config.ts +34 -1
  39. package/web/services/state/State.ts +10 -3
  40. package/web/services/state/StateSocket.ts +7 -3
  41. package/web/services/utilities/Utilities.ts +3 -3
@@ -26,6 +26,7 @@ import { state, ChlorinatorState, LightGroupState, VirtualCircuitState, ICircuit
26
26
  import { utils } from '../../controller/Constants';
27
27
  import { InvalidEquipmentIdError, InvalidEquipmentDataError, EquipmentNotFoundError, MessageError, InvalidOperationError } from '../Errors';
28
28
  import { ncp } from '../nixie/Nixie';
29
+ import { Timestamp } from "../Constants"
29
30
  export class IntelliCenterBoard extends SystemBoard {
30
31
  public needsConfigChanges: boolean = false;
31
32
  constructor(system: PoolSystem) {
@@ -389,6 +390,10 @@ export class IntelliCenterBoard extends SystemBoard {
389
390
  for (let i = 0; i < sys.circuits.length; i++) {
390
391
  let c = sys.circuits.getItemByIndex(i);
391
392
  if (c.id <= 40) c.master = 0;
393
+ if (typeof sys.board.valueMaps.circuitFunctions.get(c.type).isLight) {
394
+ let s = state.circuits.getItemById(c.id);
395
+ if (s.action !== 0) s.action = 0;
396
+ }
392
397
  }
393
398
  for (let i = 0; i < sys.valves.length; i++) {
394
399
  let v = sys.valves.getItemByIndex(i);
@@ -2168,6 +2173,7 @@ class IntelliCenterCircuitCommands extends CircuitCommands {
2168
2173
  255, 255, 0, 0, 0, 0], // 30-35
2169
2174
  3);
2170
2175
 
2176
+
2171
2177
  // Circuits are always contiguous so we don't have to worry about
2172
2178
  // them having a strange offset like features and groups. However, in
2173
2179
  // single body systems they start with 2.
@@ -2175,13 +2181,14 @@ class IntelliCenterCircuitCommands extends CircuitCommands {
2175
2181
  // We are using the index and setting the circuits based upon
2176
2182
  // the index. This way it doesn't matter what the sort happens to
2177
2183
  // be and whether there are gaps in the ids or not. The ordinal is the bit number.
2178
- let circuit = state.circuits.getItemByIndex(i);
2179
- let ordinal = circuit.id - 1;
2184
+ let cstate = state.circuits.getItemByIndex(i);
2185
+ let ordinal = cstate.id - 1;
2186
+ if (ordinal >= 40) continue;
2180
2187
  let ndx = Math.floor(ordinal / 8);
2181
2188
  let byte = out.payload[ndx + 3];
2182
2189
  let bit = ordinal - (ndx * 8);
2183
- if (circuit.id === id) byte = isOn ? byte = byte | (1 << bit) : byte;
2184
- else if (circuit.isOn) byte = byte | (1 << bit);
2190
+ if (cstate.id === id) byte = isOn ? byte = byte | (1 << bit) : byte;
2191
+ else if (cstate.isOn) byte = byte | (1 << bit);
2185
2192
  out.payload[ndx + 3] = byte;
2186
2193
  }
2187
2194
  // Set the bits for the features.
@@ -2191,6 +2198,7 @@ class IntelliCenterCircuitCommands extends CircuitCommands {
2191
2198
  // be and whether there are gaps in the ids or not. The ordinal is the bit number.
2192
2199
  let feature = state.features.getItemByIndex(i);
2193
2200
  let ordinal = feature.id - sys.board.equipmentIds.features.start;
2201
+ if (ordinal >= 32) continue;
2194
2202
  let ndx = Math.floor(ordinal / 8);
2195
2203
  let byte = out.payload[ndx + 9];
2196
2204
  let bit = ordinal - (ndx * 8);
@@ -2202,6 +2210,7 @@ class IntelliCenterCircuitCommands extends CircuitCommands {
2202
2210
  for (let i = 0; i < state.data.circuitGroups.length; i++) {
2203
2211
  let group = state.circuitGroups.getItemByIndex(i);
2204
2212
  let ordinal = group.id - sys.board.equipmentIds.circuitGroups.start;
2213
+ if (ordinal >= 16) continue;
2205
2214
  let ndx = Math.floor(ordinal / 8);
2206
2215
  let byte = out.payload[ndx + 13];
2207
2216
  let bit = ordinal - (ndx * 8);
@@ -2213,6 +2222,7 @@ class IntelliCenterCircuitCommands extends CircuitCommands {
2213
2222
  for (let i = 0; i < state.data.lightGroups.length; i++) {
2214
2223
  let group = state.lightGroups.getItemByIndex(i);
2215
2224
  let ordinal = group.id - sys.board.equipmentIds.circuitGroups.start;
2225
+ if (ordinal >= 16) continue;
2216
2226
  let ndx = Math.floor(ordinal / 8);
2217
2227
  let byte = out.payload[ndx + 13];
2218
2228
  let bit = ordinal - (ndx * 8);
@@ -2248,6 +2258,7 @@ class IntelliCenterCircuitCommands extends CircuitCommands {
2248
2258
  for (let i = 0; i < state.data.schedules.length; i++) {
2249
2259
  let sched = state.schedules.getItemByIndex(i);
2250
2260
  let ordinal = sched.id - 1;
2261
+ if (ordinal >= 100) continue;
2251
2262
  let ndx = Math.floor(ordinal / 8);
2252
2263
  let byte = out.payload[ndx + 15];
2253
2264
  let bit = ordinal - (ndx * 8);
@@ -2258,9 +2269,9 @@ class IntelliCenterCircuitCommands extends CircuitCommands {
2258
2269
  let dow = dt.getDay();
2259
2270
  // Convert the dow to the bit value.
2260
2271
  let sd = sys.board.valueMaps.scheduleDays.toArray().find(elem => elem.dow === dow);
2261
- let dayVal = sd.bitVal || sd.val; // The bitval allows mask overrides.
2272
+ //let dayVal = sd.bitVal || sd.val; // The bitval allows mask overrides.
2262
2273
  let ts = dt.getHours() * 60 + dt.getMinutes();
2263
- if ((sched.scheduleDays & dayVal) > 0 && ts >= sched.startTime && ts <= sched.endTime) byte = byte | (1 << bit);
2274
+ if ((sched.scheduleDays & sd.bitval) > 0 && ts >= sched.startTime && ts <= sched.endTime) byte = byte | (1 << bit);
2264
2275
  }
2265
2276
  }
2266
2277
  else if (sched.isOn) byte = byte | (1 << bit);
@@ -2817,6 +2828,7 @@ class IntelliCenterBodyCommands extends BodyCommands {
2817
2828
  body2: { heatMode: number, heatSetpoint: number, coolSetpoint: number }
2818
2829
  };
2819
2830
  private async queueBodyHeatSettings(bodyId?: number, byte?: number, data?: any): Promise<Boolean> {
2831
+ logger.debug(`queueBodyHeatSettings: ${JSON.stringify(this.bodyHeatSettings)}`); // remove this line if #848 is fixed
2820
2832
  if (typeof this.bodyHeatSettings === 'undefined') {
2821
2833
  let body1 = sys.bodies.getItemById(1);
2822
2834
  let body2 = sys.bodies.getItemById(2);
@@ -2862,21 +2874,30 @@ class IntelliCenterBodyCommands extends BodyCommands {
2862
2874
  retries: 2,
2863
2875
  response: IntelliCenterBoard.getAckResponse(168)
2864
2876
  });
2865
- await out.sendAsync();
2866
- let body1 = sys.bodies.getItemById(1);
2867
- let sbody1 = state.temps.bodies.getItemById(1);
2868
- body1.heatMode = sbody1.heatMode = bhs.body1.heatMode;
2869
- body1.heatSetpoint = sbody1.heatSetpoint = bhs.body1.heatSetpoint;
2870
- body1.coolSetpoint = sbody1.coolSetpoint = bhs.body1.coolSetpoint;
2871
- if (sys.equipment.dual || sys.equipment.shared) {
2872
- let body2 = sys.bodies.getItemById(2);
2873
- let sbody2 = state.temps.bodies.getItemById(2);
2874
- body2.heatMode = sbody2.heatMode = bhs.body2.heatMode;
2875
- body2.heatSetpoint = sbody2.heatSetpoint = bhs.body2.heatSetpoint;
2876
- body2.coolSetpoint = sbody2.coolSetpoint = bhs.body2.coolSetpoint;
2877
- }
2878
- bhs.processing = false;
2879
- state.emitEquipmentChanges();
2877
+ try {
2878
+ await out.sendAsync();
2879
+ let body1 = sys.bodies.getItemById(1);
2880
+ let sbody1 = state.temps.bodies.getItemById(1);
2881
+ body1.heatMode = sbody1.heatMode = bhs.body1.heatMode;
2882
+ body1.heatSetpoint = sbody1.heatSetpoint = bhs.body1.heatSetpoint;
2883
+ body1.coolSetpoint = sbody1.coolSetpoint = bhs.body1.coolSetpoint;
2884
+ if (sys.equipment.dual || sys.equipment.shared) {
2885
+ let body2 = sys.bodies.getItemById(2);
2886
+ let sbody2 = state.temps.bodies.getItemById(2);
2887
+ body2.heatMode = sbody2.heatMode = bhs.body2.heatMode;
2888
+ body2.heatSetpoint = sbody2.heatSetpoint = bhs.body2.heatSetpoint;
2889
+ body2.coolSetpoint = sbody2.coolSetpoint = bhs.body2.coolSetpoint;
2890
+ }
2891
+ state.emitEquipmentChanges();
2892
+ } catch (err) {
2893
+ bhs.processing = false;
2894
+ bhs.bytes = [];
2895
+ throw (err);
2896
+ }
2897
+ finally {
2898
+ bhs.processing = false;
2899
+ bhs.bytes = [];
2900
+ }
2880
2901
  return true;
2881
2902
  }
2882
2903
  else {
@@ -2885,7 +2906,10 @@ class IntelliCenterBodyCommands extends BodyCommands {
2885
2906
  setTimeout(async () => {
2886
2907
  try {
2887
2908
  await this.queueBodyHeatSettings();
2888
- } catch (err) { logger.error(`Error sending queued body setpoint message: ${err.message}`); }
2909
+ } catch (err) {
2910
+ logger.error(`Error sending queued body setpoint message: ${err.message}`);
2911
+ throw (err);
2912
+ }
2889
2913
  }, 3000);
2890
2914
  }
2891
2915
  else bhs.processing = false;
@@ -3119,6 +3143,7 @@ class IntelliCenterBodyCommands extends BodyCommands {
3119
3143
  }
3120
3144
  }
3121
3145
  class IntelliCenterScheduleCommands extends ScheduleCommands {
3146
+ _lastScheduleCheck: number = 0;
3122
3147
  public async setScheduleAsync(data: any): Promise<Schedule> {
3123
3148
  if (typeof data.id !== 'undefined') {
3124
3149
  let id = typeof data.id === 'undefined' ? -1 : parseInt(data.id, 10);
@@ -3141,6 +3166,8 @@ class IntelliCenterScheduleCommands extends ScheduleCommands {
3141
3166
  let endTime = typeof data.endTime !== 'undefined' ? data.endTime : sched.endTime;
3142
3167
  let schedDays = sys.board.schedules.transformDays(typeof data.scheduleDays !== 'undefined' ? data.scheduleDays : sched.scheduleDays);
3143
3168
  let display = typeof data.display !== 'undefined' ? data.display : sched.display || 0;
3169
+ let endTimeOffset = typeof data.endTimeOffset !== 'undefined' ? data.endTimeOffset : sched.endTimeOffset;
3170
+ let startTimeOffset = typeof data.startTimeOffset !== 'undefined' ? data.startTimeOffset : sched.startTimeOffset;
3144
3171
 
3145
3172
  // Ensure all the defaults.
3146
3173
  if (isNaN(startDate.getTime())) startDate = new Date();
@@ -3216,11 +3243,45 @@ class IntelliCenterScheduleCommands extends ScheduleCommands {
3216
3243
  ssched.isActive = sched.isActive = true;
3217
3244
  ssched.display = sched.display = display;
3218
3245
  ssched.emitEquipmentChange();
3246
+ ssched.startTimeOffset = sched.startTimeOffset = startTimeOffset;
3247
+ ssched.endTimeOffset = sched.endTimeOffset = endTimeOffset;
3219
3248
  return sched;
3220
3249
  }
3221
3250
  else
3222
3251
  return Promise.reject(new InvalidEquipmentIdError('No schedule information provided', undefined, 'Schedule'));
3223
3252
  }
3253
+ public syncScheduleStates() {
3254
+ // This is triggered from the 204 message in IntelliCenter. We will
3255
+ // be checking to ensure it does not load the server so we only do this every 10 seconds.
3256
+ if (this._lastScheduleCheck > new Date().getTime() - 10000) return;
3257
+ try {
3258
+ // The call below also calculates the schedule window either the current or next.
3259
+ ncp.schedules.triggerSchedules(); // At this point we are not adding Nixie schedules to IntelliCenter but this will trigger
3260
+ // the proper time windows if they exist.
3261
+ // Check each running circuit/feature to see when it will be going off.
3262
+ let scheds = state.schedules.getActiveSchedules();
3263
+ let circs: { state: ICircuitState, endTime: number }[] = [];
3264
+ for (let i = 0; i < scheds.length; i++) {
3265
+ let ssched = scheds[i];
3266
+ if (!ssched.isOn || ssched.disabled || !ssched.isActive) continue;
3267
+ let c = circs.find(x => x.state.id === ssched.circuit);
3268
+ if (typeof c === 'undefined') {
3269
+ let cstate = state.circuits.getInterfaceById(ssched.circuit);
3270
+ c = { state: cstate, endTime: ssched.scheduleTime.endTime.getTime() };
3271
+ circs.push;
3272
+ }
3273
+ if (c.endTime < ssched.scheduleTime.endTime.getTime()) c.endTime = ssched.scheduleTime.endTime.getTime();
3274
+ }
3275
+ for (let i = 0; i < circs.length; i++) {
3276
+ let c = circs[i];
3277
+ if (c.state.endTime.getTime() !== c.endTime) {
3278
+ c.state.endTime = new Timestamp(new Date(c.endTime));
3279
+ c.state.emitEquipmentChange();
3280
+ }
3281
+ }
3282
+ this._lastScheduleCheck = new Date().getTime();
3283
+ } catch (err) { logger.error(`Error synchronizing schedule states`); }
3284
+ }
3224
3285
  public async deleteScheduleAsync(data: any): Promise<Schedule> {
3225
3286
  if (typeof data.id !== 'undefined') {
3226
3287
  let id = typeof data.id === 'undefined' ? -1 : parseInt(data.id, 10);
@@ -3484,7 +3545,7 @@ class IntelliCenterHeaterCommands extends HeaterCommands {
3484
3545
  if (gasHeaterInstalled) sys.board.valueMaps.heatSources.merge([[2, { name: 'heater', desc: 'Heater' }]]);
3485
3546
  if (mastertempInstalled) sys.board.valueMaps.heatSources.merge([[11, { name: 'mtheater', desc: 'MasterTemp' }]]);
3486
3547
  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 }]]);
3487
- else if (solarInstalled) sys.board.valueMaps.heatSources.merge([[3, { name: 'solar', desc: 'Solar', hasCoolsetpoint: htypes.hasCoolSetpoint }]]);
3548
+ else if (solarInstalled) sys.board.valueMaps.heatSources.merge([[3, { name: 'solar', desc: 'Solar', hasCoolSetpoint: htypes.hasCoolSetpoint }]]);
3488
3549
  if (heatPumpInstalled && (gasHeaterInstalled || solarInstalled)) sys.board.valueMaps.heatSources.merge([[9, { name: 'heatpump', desc: 'Heatpump Only' }], [25, { name: 'heatpumppref', desc: 'Heat Pump Pref' }]]);
3489
3550
  else if (heatPumpInstalled) sys.board.valueMaps.heatSources.merge([[9, { name: 'heatpump', desc: 'Heat Pump' }]]);
3490
3551
  if (ultratempInstalled && (gasHeaterInstalled || heatPumpInstalled)) sys.board.valueMaps.heatSources.merge([[5, { name: 'ultratemp', desc: 'UltraTemp Only', hasCoolSetpoint: htypes.hasCoolSetpoint }], [6, { name: 'ultratemppref', desc: 'UltraTemp Pref', hasCoolSetpoint: htypes.hasCoolSetpoint }]]);
@@ -86,7 +86,7 @@ export class IntelliTouchBoard extends EasyTouchBoard {
86
86
 
87
87
  }
88
88
  if (typeof sys.valves.find((v) => v.id === 2) === 'undefined') {
89
- let valve = sys.valves.getItemById(1, true);
89
+ let valve = sys.valves.getItemById(2, true);
90
90
  valve.isIntake = false;
91
91
  valve.isReturn = false;
92
92
  valve.type = 0;
@@ -360,9 +360,7 @@ class ITTouchConfigQueue extends TouchConfigQueue {
360
360
  this.queueItems(GetTouchConfigCategories.highSpeedCircuits, [0]);
361
361
  this.queueRange(GetTouchConfigCategories.pumpConfig, 1, sys.equipment.maxPumps);
362
362
  this.queueRange(GetTouchConfigCategories.circuitGroups, 0, sys.equipment.maxFeatures - 1);
363
- // items not required by ScreenLogic
364
- if (sys.chlorinators.getItemById(1).isActive)
365
- this.queueItems(GetTouchConfigCategories.intellichlor, [0]);
363
+ this.queueItems(GetTouchConfigCategories.intellichlor, [0]);
366
364
  if (this.remainingItems > 0) {
367
365
  var self = this;
368
366
  setTimeout(() => { self.processNext(); }, 50);
@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
18
18
  import * as extend from 'extend';
19
19
  import { ncp } from "../nixie/Nixie";
20
20
  import { NixieHeaterBase } from "../nixie/heaters/Heater";
21
- import { utils } from '../Constants';
21
+ import { Timestamp, utils } from '../Constants';
22
22
  import {SystemBoard, byteValueMap, BodyCommands, FilterCommands, PumpCommands, SystemCommands, CircuitCommands, FeatureCommands, ValveCommands, HeaterCommands, ChlorinatorCommands, ChemControllerCommands, EquipmentIdRange} from './SystemBoard';
23
23
  import { logger } from '../../logger/Logger';
24
24
  import { state, CircuitState, ICircuitState, ICircuitGroupState, LightGroupState, ValveState, FilterState, BodyTempState, FeatureState } from '../State';
@@ -221,6 +221,21 @@ export class NixieBoard extends SystemBoard {
221
221
  [53, { name: 'greenblue', desc: 'Green-Blue', types: ['pooltone'], sequence: 14 }],
222
222
  [54, { name: 'redgreen', desc: 'Red-Green', types: ['pooltone'], sequence: 15 }],
223
223
  [55, { name: 'bluered', desc: 'Blue-red', types: ['pooltone'], sequence: 16 }],
224
+ // Jandy Pro Series WaterColors Themes
225
+ [56, { name: 'alpinewhite', desc: 'Alpine White', types: ['watercolors'], sequence: 1 }],
226
+ [57, { name: 'skyblue', desc: 'Sky Blue', types: ['watercolors'], sequence: 2 }],
227
+ [58, { name: 'cobaltblue', desc: 'Cobalt Blue', types: ['watercolors'], sequence: 3 }],
228
+ [59, { name: 'caribbeanblue', desc: 'Caribbean Blue', types: ['watercolors'], sequence: 4 }],
229
+ [60, { name: 'springgreen', desc: 'Spring Green', types: ['watercolors'], sequence: 5 }],
230
+ [61, { name: 'emeraldgreen', desc: 'Emerald Green', types: ['watercolors'], sequence: 6 }],
231
+ [62, { name: 'emeraldrose', desc: 'Emerald Rose', types: ['watercolors'], sequence: 7 }],
232
+ [63, { name: 'magenta', desc: 'Magenta', types: ['watercolors'], sequence: 8 }],
233
+ [64, { name: 'violet', desc: 'Violet', types: ['watercolors'], sequence: 9 }],
234
+ [65, { name: 'slowcolorsplash', desc: 'Slow Color Splash', types: ['watercolors'], sequence: 10 }],
235
+ [66, { name: 'fastcolorsplash', desc: 'Fast Color Splash', types: ['watercolors'], sequence: 11 }],
236
+ [67, { name: 'americathebeautiful', desc: 'America the Beautiful', types: ['watercolors'], sequence: 12 }],
237
+ [68, { name: 'fattuesday', desc: 'Fat Tuesday', types: ['watercolors'], sequence: 13 }],
238
+ [69, { name: 'discotech', desc: 'Disco Tech', types: ['watercolors'], sequence: 14 }],
224
239
  [255, { name: 'none', desc: 'None' }]
225
240
  ]);
226
241
  this.valueMaps.lightColors = new byteValueMap([
@@ -266,9 +281,35 @@ export class NixieBoard extends SystemBoard {
266
281
  [4, { name: 'spaCommand', desc: 'Spa Command', maxButtons: 10 }]
267
282
  ]);
268
283
  }
284
+ public async closeAsync() {
285
+ logger.info(`Closing Nixie Board`);
286
+ await ncp.closeAsync();
287
+ }
269
288
  public async checkConfiguration() {
270
289
  state.status = sys.board.valueMaps.controllerStatus.transform(0, 0);
271
290
  state.emitControllerChange();
291
+ // Set all the schedule data based upon the config.
292
+ for (let i = 0; i < sys.schedules.length; i++) {
293
+ let sched = sys.schedules.getItemByIndex(i);
294
+ let ssched = state.schedules.getItemById(sched.id, true);
295
+ ssched.circuit = sched.circuit;
296
+ ssched.scheduleDays = sched.scheduleDays;
297
+ ssched.scheduleType = sched.scheduleType;
298
+ ssched.changeHeatSetpoint = sched.changeHeatSetpoint;
299
+ ssched.heatSetpoint = sched.heatSetpoint;
300
+ ssched.coolSetpoint = sched.coolSetpoint;
301
+ ssched.heatSource = sched.heatSource;
302
+ ssched.startTime = sched.startTime;
303
+ ssched.endTime = sched.endTime;
304
+ ssched.startTimeType = sched.startTimeType;
305
+ ssched.endTimeType = sched.endTimeType;
306
+ ssched.startDate = sched.startDate;
307
+ ssched.isActive = sched.isActive = true;
308
+ sched.disabled = sched.disabled;
309
+ ssched.display = sched.display;
310
+
311
+ }
312
+
272
313
  state.status = sys.board.valueMaps.controllerStatus.transform(1, 100);
273
314
  state.emitControllerChange();
274
315
  }
@@ -390,7 +431,7 @@ export class NixieBoard extends SystemBoard {
390
431
  sys.circuits.removeItemById(6);
391
432
  state.circuits.removeItemById(6);
392
433
  }
393
-
434
+
394
435
  sys.equipment.setEquipmentIds();
395
436
  sys.board.bodies.initFilters();
396
437
  state.status = sys.board.valueMaps.controllerStatus.transform(2, 0);
@@ -411,10 +452,11 @@ export class NixieBoard extends SystemBoard {
411
452
  sys.board.heaters.updateHeaterServices();
412
453
  state.cleanupState();
413
454
  logger.info(`${sys.equipment.model} control board initialized`);
414
- state.status = sys.board.valueMaps.controllerStatus.transform(1, 100);
455
+ //state.status = sys.board.valueMaps.controllerStatus.transform(1, 100);
415
456
  state.mode = sys.board.valueMaps.panelModes.encode('auto');
416
457
  // At this point we should have the start of a board so lets check to see if we are ready or if we are stuck initializing.
417
458
  await setTimeout(5000);
459
+ state.status = sys.board.valueMaps.controllerStatus.transform(1, 100);
418
460
  await self.processStatusAsync();
419
461
  } catch (err) { state.status = 255; logger.error(`Error Initializing Nixie Control Panel ${err.message}`); }
420
462
  }
@@ -529,11 +571,11 @@ export class NixieSystemCommands extends SystemCommands {
529
571
  state.delay = sys.board.valueMaps.delay.getValue('nodelay');
530
572
  return Promise.resolve(state.data.delay);
531
573
  }
532
- public setManualOperationPriority(id: number): Promise<any> {
574
+ public setManualOperationPriority(id: number): Promise<any> {
533
575
  let cstate = state.circuits.getInterfaceById(id);
534
576
  delayMgr.setManualPriorityDelay(cstate);
535
- return Promise.resolve(cstate);
536
- }
577
+ return Promise.resolve(cstate);
578
+ }
537
579
  public setDateTimeAsync(obj: any): Promise<any> { return Promise.resolve(); }
538
580
  public getDOW() { return this.board.valueMaps.scheduleDays.toArray(); }
539
581
  public async setGeneralAsync(obj: any): Promise<General> {
@@ -669,7 +711,6 @@ export class NixieCircuitCommands extends CircuitCommands {
669
711
  break;
670
712
  default:
671
713
  await ncp.circuits.setCircuitStateAsync(circ, newState);
672
- await sys.board.processStatusAsync();
673
714
  break;
674
715
  }
675
716
  // Let the main nixie controller set the circuit state and affect the relays if it needs to.
@@ -680,6 +721,7 @@ export class NixieCircuitCommands extends CircuitCommands {
680
721
  state.emitEquipmentChanges();
681
722
  ncp.pumps.syncPumpStates();
682
723
  sys.board.suspendStatus(false);
724
+ await sys.board.processStatusAsync();
683
725
  }
684
726
  }
685
727
  protected async setCleanerCircuitStateAsync(id: number, val: boolean, ignoreDelays?: boolean): Promise<ICircuitState> {
@@ -742,8 +784,9 @@ export class NixieCircuitCommands extends CircuitCommands {
742
784
  }
743
785
  if (sys.general.options.cleanerStartDelay && sys.general.options.cleanerStartDelayTime) {
744
786
  let bcstate = state.circuits.getItemById(bstate.circuit);
787
+ let stime = typeof bcstate.startTime === 'undefined' ? dtNow : (dtNow - bcstate.startTime.getTime());
745
788
  // So we should be started. Lets determine whethere there should be any delay.
746
- delayTime = Math.max(Math.round(((sys.general.options.cleanerStartDelayTime * 1000) - (dtNow - bcstate.startTime.getTime())) / 1000), delayTime);
789
+ delayTime = Math.max(Math.round(((sys.general.options.cleanerStartDelayTime * 1000) - stime) / 1000), delayTime);
747
790
  logger.info(`Cleaner delay time calculated to ${delayTime}`);
748
791
  }
749
792
  }
@@ -864,6 +907,7 @@ export class NixieCircuitCommands extends CircuitCommands {
864
907
  // circuit is already under delay it should have been processed out earlier.
865
908
  delayMgr.cancelPumpValveDelays();
866
909
  delayMgr.cancelHeaterStartupDelays();
910
+ sys.board.heaters.clearPrevHeaterOffTemp();
867
911
  if (cstate.startDelay) delayMgr.clearBodyStartupDelay(bstate);
868
912
  await this.turnOffCleanerCircuits(bstate);
869
913
  if (sys.equipment.shared && bstate.id === 2) await this.turnOffDrainCircuits(ignoreDelays);
@@ -967,7 +1011,7 @@ export class NixieCircuitCommands extends CircuitCommands {
967
1011
  return cstate;
968
1012
  } catch (err) { logger.error(`Nixie: Error setDrainCircuitStateAsync ${err.message}`); return Promise.reject(new BoardProcessError(`Nixie: Error setDrainCircuitStateAsync ${err.message}`, 'setDrainCircuitStateAsync')); }
969
1013
  }
970
-
1014
+
971
1015
  public toggleCircuitStateAsync(id: number): Promise<ICircuitState> {
972
1016
  let circ = state.circuits.getInterfaceById(id);
973
1017
  return this.setCircuitStateAsync(id, !(circ.isOn || false));
@@ -1112,7 +1156,7 @@ export class NixieCircuitCommands extends CircuitCommands {
1112
1156
  if (typeof obj.eggTimer !== 'undefined') group.eggTimer = Math.min(Math.max(parseInt(obj.eggTimer, 10), 0), 1440);
1113
1157
  if (typeof obj.showInFeatures !== 'undefined') sgroup.showInFeatures = group.showInFeatures = utils.makeBool(obj.showInFeatures);
1114
1158
  sgroup.type = group.type;
1115
-
1159
+
1116
1160
  group.dontStop = group.eggTimer === 1440;
1117
1161
  group.isActive = sgroup.isActive = true;
1118
1162
 
@@ -1179,7 +1223,7 @@ export class NixieCircuitCommands extends CircuitCommands {
1179
1223
  // group.circuits.length = obj.circuits.length; // RSG - removed as this will delete circuits that were not changed
1180
1224
  group.circuits.length = obj.circuits.length;
1181
1225
  sgroup.emitEquipmentChange();
1182
-
1226
+
1183
1227
  }
1184
1228
  resolve(group);
1185
1229
  });
@@ -1266,7 +1310,7 @@ export class NixieCircuitCommands extends CircuitCommands {
1266
1310
  //grp.lightingTheme = sgrp.lightingTheme = theme;
1267
1311
  let thm = sys.board.valueMaps.lightThemes.transform(theme);
1268
1312
  sgrp.action = sys.board.valueMaps.circuitActions.getValue('lighttheme');
1269
-
1313
+
1270
1314
  try {
1271
1315
  // Go through and set the theme for all lights in the group.
1272
1316
  for (let i = 0; i < grp.circuits.length; i++) {
@@ -1380,7 +1424,7 @@ export class NixieCircuitCommands extends CircuitCommands {
1380
1424
  else if (circuit.desiredState === 5){
1381
1425
  // off/ignore
1382
1426
  if (val) cval = false;
1383
- else continue;
1427
+ else continue;
1384
1428
  }
1385
1429
  await sys.board.circuits.setCircuitStateAsync(circuit.circuit, cval);
1386
1430
  //arr.push(sys.board.circuits.setCircuitStateAsync(circuit.circuit, cval));
@@ -1402,13 +1446,23 @@ export class NixieCircuitCommands extends CircuitCommands {
1402
1446
  let arr = [];
1403
1447
  for (let i = 0; i < circuits.length; i++) {
1404
1448
  let circuit = circuits[i];
1405
- arr.push(sys.board.circuits.setCircuitStateAsync(circuit.circuit, val));
1449
+ // RSG 4/3/24 - This function was executing and returing the results to the array; not pushing the fn to the array.
1450
+ //arr.push(sys.board.circuits.setCircuitStateAsync(circuit.circuit, val));
1451
+ arr.push(async () => { await sys.board.circuits.setCircuitStateAsync(circuit.circuit, val) });
1406
1452
  }
1453
+ // return new Promise<ICircuitGroupState>(async (resolve, reject) => {
1454
+ // await Promise.all(arr).catch((err) => { reject(err) });
1455
+ // resolve(gstate);
1456
+ // });
1407
1457
  return new Promise<ICircuitGroupState>(async (resolve, reject) => {
1408
- await Promise.all(arr).catch((err) => { reject(err) });
1409
- resolve(gstate);
1458
+ try {
1459
+ Promise.all(arr.map(async func => await func()));
1460
+ resolve(gstate);
1461
+ } catch (err) {
1462
+ reject(err);
1463
+ };
1410
1464
  });
1411
- }
1465
+ };
1412
1466
  }
1413
1467
  export class NixieFeatureCommands extends FeatureCommands {
1414
1468
  public async setFeatureAsync(obj: any): Promise<Feature> {
@@ -1484,7 +1538,7 @@ export class NixieFeatureCommands extends FeatureCommands {
1484
1538
  if (!val){
1485
1539
  if (fstate.manualPriorityActive) delayMgr.cancelManualPriorityDelay(fstate.id);
1486
1540
  fstate.manualPriorityActive = false; // if the delay was previously cancelled, still need to turn this off
1487
- }
1541
+ }
1488
1542
  state.emitEquipmentChanges();
1489
1543
  return fstate;
1490
1544
  } catch (err) { return Promise.reject(new Error(`Error setting feature state ${err.message}`)); }
@@ -1572,7 +1626,7 @@ export class NixieFeatureCommands extends FeatureCommands {
1572
1626
  let circuit: CircuitGroupCircuit = grp.circuits.getItemByIndex(j);
1573
1627
  let cstate = state.circuits.getInterfaceById(circuit.circuit);
1574
1628
  // RSG: desiredState for Nixie is 1=on, 2=off, 3=ignore
1575
- if (circuit.desiredState === 1 || circuit.desiredState === 4) {
1629
+ if (circuit.desiredState === 1 || circuit.desiredState === 4) {
1576
1630
  // The circuit should be on if the value is 1.
1577
1631
  // If we are on 'ignore' we should still only treat the circuit as
1578
1632
  // desiredstate = 1.
@@ -1583,12 +1637,15 @@ export class NixieFeatureCommands extends FeatureCommands {
1583
1637
  }
1584
1638
  }
1585
1639
  let sgrp = state.circuitGroups.getItemById(grp.id);
1640
+ if (bIsOn && typeof sgrp.endTime === 'undefined') {
1641
+ sys.board.circuits.setEndTime(grp, sgrp, bIsOn, true);
1642
+ }
1586
1643
  sgrp.isOn = bIsOn;
1587
- if (sgrp.isOn && typeof sgrp.endTime === 'undefined') sys.board.circuits.setEndTime(grp, sgrp, sgrp.isOn, true);
1644
+
1588
1645
  if (!sgrp.isOn && sgrp.manualPriorityActive){
1589
1646
  delayMgr.cancelManualPriorityDelays();
1590
1647
  sgrp.manualPriorityActive = false; // if the delay was previously cancelled, still need to turn this off
1591
- }
1648
+ }
1592
1649
  }
1593
1650
  sys.board.valves.syncValveStates();
1594
1651
  }
@@ -1610,9 +1667,9 @@ export class NixieFeatureCommands extends FeatureCommands {
1610
1667
  if (!sgrp.isOn && sgrp.manualPriorityActive){
1611
1668
  delayMgr.cancelManualPriorityDelay(grp.id);
1612
1669
  sgrp.manualPriorityActive = false; // if the delay was previously cancelled, still need to turn this off
1613
- }
1670
+ }
1614
1671
  }
1615
-
1672
+
1616
1673
  sys.board.valves.syncValveStates();
1617
1674
  }
1618
1675
  state.emitEquipmentChanges();
@@ -1644,8 +1701,8 @@ export class NixiePumpCommands extends PumpCommands {
1644
1701
  // to bodies.
1645
1702
  console.log(`Body: ${pump.body} Pump: ${pump.name} Pool: ${circuitIds.includes(6)} `);
1646
1703
  if ((pump.body === 255 && (circuitIds.includes(6) || circuitIds.includes(1))) ||
1647
- (pump.body === 0 && circuitIds.includes(6)) ||
1648
- (pump.body === 101 && circuitIds.includes(1))) {
1704
+ (pump.body === 0 && circuitIds.includes(6)) ||
1705
+ (pump.body === 101 && circuitIds.includes(1))) {
1649
1706
  delayMgr.setPumpValveDelay(pstate);
1650
1707
  }
1651
1708
  break;
@@ -1817,14 +1874,14 @@ export class NixieHeaterCommands extends HeaterCommands {
1817
1874
  if (gasHeaterInstalled) sys.board.valueMaps.heatSources.merge([[2, { name: 'heater', desc: 'Heater' }]]);
1818
1875
  if (mastertempInstalled) sys.board.valueMaps.heatSources.merge([[11, { name: 'mtheater', desc: 'MasterTemp' }]]);
1819
1876
  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 }]]);
1820
- else if (solarInstalled) sys.board.valueMaps.heatSources.merge([[3, { name: 'solar', desc: 'Solar', hasCoolsetpoint: htypes.hasCoolSetpoint }]]);
1877
+ else if (solarInstalled) sys.board.valueMaps.heatSources.merge([[3, { name: 'solar', desc: 'Solar', hasCoolSetpoint: htypes.hasCoolSetpoint }]]);
1821
1878
  if (heatPumpInstalled && (gasHeaterInstalled || solarInstalled)) sys.board.valueMaps.heatSources.merge([[9, { name: 'heatpump', desc: 'Heatpump Only' }], [25, { name: 'heatpumppref', desc: 'Heat Pump Pref' }]]);
1822
1879
  else if (heatPumpInstalled) sys.board.valueMaps.heatSources.merge([[9, { name: 'heatpump', desc: 'Heat Pump' }]]);
1823
1880
  if (ultratempInstalled && (gasHeaterInstalled || heatPumpInstalled)) sys.board.valueMaps.heatSources.merge([[5, { name: 'ultratemp', desc: 'UltraTemp Only', hasCoolSetpoint: htypes.hasCoolSetpoint }], [6, { name: 'ultratemppref', desc: 'UltraTemp Pref', hasCoolSetpoint: htypes.hasCoolSetpoint }]]);
1824
1881
  else if (ultratempInstalled) sys.board.valueMaps.heatSources.merge([[5, { name: 'ultratemp', desc: 'UltraTemp', hasCoolSetpoint: htypes.hasCoolSetpoint }]]);
1825
1882
  sys.board.valueMaps.heatSources.merge([[0, { name: 'nochange', desc: 'No Change' }]]);
1826
1883
 
1827
-
1884
+
1828
1885
  if (gasHeaterInstalled) sys.board.valueMaps.heatModes.merge([[2, { name: 'heater', desc: 'Heater' }]]);
1829
1886
  if (mastertempInstalled) sys.board.valueMaps.heatModes.merge([[11, { name: 'mtheater', desc: 'MasterTemp' }]]);
1830
1887
  if (solarInstalled && (gasHeaterInstalled || heatPumpInstalled || mastertempInstalled)) sys.board.valueMaps.heatModes.merge([[3, { name: 'solar', desc: 'Solar Only' }], [4, { name: 'solarpref', desc: 'Solar Preferred' }]]);
@@ -31,7 +31,7 @@ export class SunTouchBoard extends EasyTouchBoard {
31
31
  constructor(system: PoolSystem) {
32
32
  super(system); // graph chain to EasyTouchBoard constructor.
33
33
  this.valueMaps.expansionBoards = new byteValueMap([
34
- [41, { name: 'shared', part: '520820', desc: 'Pool and Spa controller', bodies: 2, valves: 4, circuits: 5, single: false, shared: true, dual: false, features: 4, chlorinators: 1, chemControllers: 1 }],
34
+ [41, { name: 'stshared', part: '520820', desc: 'Pool and Spa controller', bodies: 2, valves: 4, circuits: 5, single: false, shared: true, dual: false, features: 4, chlorinators: 1, chemControllers: 1 }],
35
35
  [40, { name: 'stsingle', part: '520819', desc: 'Pool or Spa controller', bodies: 2, valves: 4, circuits: 5, single: true, shared: true, dual: false, features: 4, chlorinators: 1, chemControllers: 1 }]
36
36
  ]);
37
37
  this._statusInterval = -1;
@@ -222,8 +222,10 @@ class SunTouchConfigQueue extends TouchConfigQueue {
222
222
  // 196 - [0-2]
223
223
  // 198 - [0-2]
224
224
  // 199 - [0-2]
225
+ // 200 - Heat/Temperature Status
225
226
  // 201 - [0-2]
226
227
  // 202 - [0-2] - Custom Names
228
+ // 203 - Circuit Functions
227
229
  // 204 - [0-2]
228
230
  // 205 - [0-2]
229
231
  // 206 - [0-2]
@@ -238,13 +240,16 @@ class SunTouchConfigQueue extends TouchConfigQueue {
238
240
  // 218 - [0-2]
239
241
  // 219 - [0-2]
240
242
  // 220 - [0-2]
243
+ // 221 - Valve Assignments
241
244
  // 223 - [0-2]
242
245
  // 224 - [1-2]
243
- // 226 - [0]
246
+ // 225 - Spa side remote
247
+ // 226 - [0] - Solar/HeatPump config
244
248
  // 228 - [0-2]
245
249
  // 229 - [0-2]
246
250
  // 230 - [0-2]
247
251
  // 231 - [0-2]
252
+ // 232 - Settings (Amazed that there is none of this)
248
253
  // 233 - [0-2]
249
254
  // 234 - [0-2]
250
255
  // 235 - [0-2]
@@ -264,6 +269,7 @@ class SunTouchConfigQueue extends TouchConfigQueue {
264
269
  // 249 - [0-2]
265
270
  // 250 - [0-2]
266
271
  // 251 - [0-2]
272
+ // 253 - Software Version
267
273
 
268
274
  this.queueItems(GetTouchConfigCategories.version); // 252
269
275
  this.queueItems(GetTouchConfigCategories.dateTime, [0]); //197