nodejs-poolcontroller 8.0.1 → 8.0.2

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 (47) hide show
  1. package/.docker/Dockerfile.armv6 +29 -0
  2. package/.docker/Dockerfile.armv7 +29 -0
  3. package/.docker/Dockerfile.linux +62 -0
  4. package/.docker/Dockerfile.windows +43 -0
  5. package/.docker/docker-compose.yml +47 -0
  6. package/.docker/ecosystem.config.js +35 -0
  7. package/.github/workflows/docker-publish-njsPC-linux.yml +81 -0
  8. package/.github/workflows/docker-publish-njsPC-windows.yml +41 -0
  9. package/Dockerfile +4 -3
  10. package/README.md +4 -1
  11. package/config/Config.ts +1 -1
  12. package/controller/Constants.ts +164 -67
  13. package/controller/Equipment.ts +78 -18
  14. package/controller/Lockouts.ts +15 -0
  15. package/controller/State.ts +280 -7
  16. package/controller/boards/EasyTouchBoard.ts +225 -101
  17. package/controller/boards/IntelliCenterBoard.ts +67 -18
  18. package/controller/boards/IntelliTouchBoard.ts +2 -4
  19. package/controller/boards/NixieBoard.ts +84 -27
  20. package/controller/boards/SunTouchBoard.ts +8 -2
  21. package/controller/boards/SystemBoard.ts +3259 -3242
  22. package/controller/comms/ScreenLogic.ts +47 -44
  23. package/controller/comms/messages/Messages.ts +4 -4
  24. package/controller/comms/messages/config/ChlorinatorMessage.ts +10 -3
  25. package/controller/comms/messages/config/ExternalMessage.ts +4 -1
  26. package/controller/comms/messages/config/PumpMessage.ts +8 -7
  27. package/controller/comms/messages/config/RemoteMessage.ts +48 -43
  28. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +8 -2
  29. package/controller/comms/messages/status/EquipmentStateMessage.ts +9 -4
  30. package/controller/nixie/NixieEquipment.ts +1 -1
  31. package/controller/nixie/bodies/Body.ts +1 -1
  32. package/controller/nixie/chemistry/ChemController.ts +37 -28
  33. package/controller/nixie/circuits/Circuit.ts +36 -0
  34. package/controller/nixie/heaters/Heater.ts +24 -5
  35. package/controller/nixie/pumps/Pump.ts +155 -97
  36. package/controller/nixie/schedules/Schedule.ts +207 -126
  37. package/defaultConfig.json +3 -3
  38. package/logger/DataLogger.ts +7 -7
  39. package/package.json +2 -2
  40. package/sendSocket.js +32 -0
  41. package/web/Server.ts +17 -11
  42. package/web/bindings/homeassistant.json +2 -2
  43. package/web/interfaces/mqttInterface.ts +18 -18
  44. package/web/services/config/Config.ts +34 -1
  45. package/web/services/state/State.ts +10 -3
  46. package/web/services/state/StateSocket.ts +7 -3
  47. package/web/services/utilities/Utilities.ts +3 -3
@@ -1,7 +1,7 @@
1
1
  import { clearTimeout, setTimeout } from 'timers';
2
2
  import { conn } from '../../../controller/comms/Comms';
3
3
  import { Outbound, Protocol, Response } from '../../../controller/comms/messages/Messages';
4
- import { IChemical, IChemController, ChemController, ChemControllerCollection, ChemFlowSensor, Chemical, ChemicalChlor, ChemicalORP, ChemicalORPProbe, ChemicalPh, ChemicalPhProbe, ChemicalProbe, ChemicalPump, ChemicalTank, sys } from "../../../controller/Equipment";
4
+ import { IChemical, IChemController, Chlorinator, ChemController, ChemControllerCollection, ChemFlowSensor, Chemical, ChemicalChlor, ChemicalORP, ChemicalORPProbe, ChemicalPh, ChemicalPhProbe, ChemicalProbe, ChemicalPump, ChemicalTank, sys } from "../../../controller/Equipment";
5
5
  import { logger } from '../../../logger/Logger';
6
6
  import { InterfaceServerResponse, webApp } from "../../../web/Server";
7
7
  import { Timestamp, utils } from '../../Constants';
@@ -21,6 +21,8 @@ export interface INixieChemical extends NixieEquipment {
21
21
  chemController: INixieChemController;
22
22
  chemical: IChemical;
23
23
  }
24
+
25
+ //#region
24
26
  export class NixieChemControllerCollection extends NixieEquipmentCollection<NixieChemControllerBase> {
25
27
  public async manualDoseAsync(id: number, data: any) {
26
28
  try {
@@ -208,6 +210,7 @@ export class NixieChemControllerBase extends NixieEquipment implements INixieChe
208
210
  else if (!isOn) this.bodyOnTime = undefined;
209
211
  return isOn;
210
212
  }
213
+ public get activeBodyId(): number { return sys.board.bodies.getActiveBody(this.chem.body); }
211
214
  public async setControllerAsync(data: any) { } // This is meant to be abstract override this value
212
215
  public processAlarms(schem: any) { }
213
216
 
@@ -692,6 +695,7 @@ export class NixieChemController extends NixieChemControllerBase {
692
695
  // to indicate this to the user.
693
696
  schem.alarms.flow = schem.isBodyOn && !schem.flowDetected ? 1 : 0;
694
697
  }
698
+ schem.activeBodyId = this.activeBodyId;
695
699
  schem.ph.dailyVolumeDosed = schem.ph.calcDoseHistory();
696
700
  schem.orp.dailyVolumeDosed = schem.orp.calcDoseHistory();
697
701
  let chem = this.chem;
@@ -699,7 +703,6 @@ export class NixieChemController extends NixieChemControllerBase {
699
703
  schem.ph.enabled = this.chem.ph.enabled;
700
704
  let probeType = chem.orp.probe.type;
701
705
  if (this.chem.orp.enabled) {
702
-
703
706
  let useChlorinator = chem.orp.useChlorinator;
704
707
  let pumpType = chem.orp.pump.type;
705
708
  let currLevelPercent = schem.orp.tank.level / schem.orp.tank.capacity * 100;
@@ -710,7 +713,7 @@ export class NixieChemController extends NixieChemControllerBase {
710
713
  else schem.alarms.orpTank = 0;
711
714
  // Alright we need to determine whether we need to adjust the volume any so that we get at least 3 seconds out of the pump.
712
715
  let padj = this.chem.orp.pump.type > 0 && !this.chem.orp.useChlorinator ? (this.chem.orp.pump.ratedFlow / 60) * 3 : 0;
713
- if (this.chem.orp.maxDailyVolume <= schem.orp.dailyVolumeDosed && !this.chem.orp.useChlorinator) {
716
+ if (this.chem.orp.dosingMethod !== 0 && this.chem.orp.maxDailyVolume <= schem.orp.dailyVolumeDosed && !this.chem.orp.useChlorinator) {
714
717
  schem.warnings.orpDailyLimitReached = 4;
715
718
  schem.orp.dailyLimitReached = true;
716
719
  }
@@ -722,7 +725,15 @@ export class NixieChemController extends NixieChemControllerBase {
722
725
  if (probeType !== 0 && chem.orp.tolerance.enabled)
723
726
  schem.alarms.orp = schem.orp.level < chem.orp.tolerance.low ? 16 : schem.orp.level > chem.orp.tolerance.high ? 8 : 0;
724
727
  else schem.alarms.orp = 0;
725
- schem.warnings.chlorinatorCommError = useChlorinator && schem.isBodyOn && state.chlorinators.getItemById(1).status & 0xF0 ? 16 : 0;
728
+ let chlorErr = 0;
729
+ if (useChlorinator && schem.isBodyOn) {
730
+ let chlors = sys.chlorinators.getByBody(schem.activeBodyId);
731
+ let chlor = chlors.getItemByIndex(0);
732
+ let schlor = state.chlorinators.getItemById(chlor.id);
733
+ this.orp.chlor.chlorId = chlor.id;
734
+ if (schlor.status & 0xF0) chlorErr = 16;
735
+ }
736
+ schem.warnings.chlorinatorCommError = chlorErr;
726
737
  schem.warnings.pHLockout = useChlorinator === false && probeType !== 0 && pumpType !== 0 && schem.ph.level >= chem.orp.phLockout ? 1 : 0;
727
738
  }
728
739
  else {
@@ -756,7 +767,7 @@ export class NixieChemController extends NixieChemControllerBase {
756
767
  schem.warnings.pHDailyLimitReached = 0;
757
768
  // Alright we need to determine whether we need to adjust the volume any so that we get at least 3 seconds out of the pump.
758
769
  let padj = this.chem.ph.pump.type > 0 ? (this.chem.ph.pump.ratedFlow / 60) * 3 : 0;
759
- if (this.chem.ph.maxDailyVolume <= schem.ph.dailyVolumeDosed + padj) {
770
+ if (this.chem.ph.dosingMethod !== 0 && this.chem.ph.maxDailyVolume <= schem.ph.dailyVolumeDosed + padj) {
760
771
  schem.warnings.pHDailyLimitReached = 2;
761
772
  schem.ph.dailyLimitReached = true;
762
773
  }
@@ -773,7 +784,6 @@ export class NixieChemController extends NixieChemControllerBase {
773
784
  else schem.alarms.pH = 0;
774
785
  schem.ph.freezeProtect = (state.freeze && chem.ph.disableOnFreeze && schem.isBodyOn);
775
786
  }
776
-
777
787
  else {
778
788
  schem.alarms.pHTank = 0;
779
789
  schem.warnings.pHDailyLimitReached = 0;
@@ -1057,7 +1067,7 @@ class NixieChemical extends NixieChildEquipment implements INixieChemical {
1057
1067
  schem.chlor.isDosing = schem.pump.isDosing = false;
1058
1068
  if (!this.chemical.flowOnlyMixing || (schem.chemController.isBodyOn && this.chemController.flowDetected && !schem.freezeProtect)) {
1059
1069
  if (this.chemType === 'orp' && typeof this.chemController.orp.orp.useChlorinator !== 'undefined' && this.chemController.orp.orp.useChlorinator && this.chemController.orp.orp.chlorDosingMethod > 0) {
1060
- if (state.chlorinators.getItemById(1).currentOutput !== 0) {
1070
+ if (state.chlorinators.getItemById(this.chemController.orp.chlor.chlorId).currentOutput !== 0) {
1061
1071
  logger.debug(`Chem mixing ORP (chlorinator) paused waiting for chlor current output to be 0%. Mix time remaining: ${utils.formatDuration(schem.mixTimeRemaining)} `);
1062
1072
  return;
1063
1073
  }
@@ -1130,10 +1140,10 @@ export class NixieChemTank extends NixieChildEquipment {
1130
1140
  try {
1131
1141
  if (typeof data !== 'undefined') {
1132
1142
  stank.level = typeof data.level !== 'undefined' ? parseFloat(data.level) : stank.level;
1133
- stank.capacity = this.tank.capacity = typeof data.capacity !== 'undefined' ? parseFloat(data.capacity) : stank.capacity;
1143
+ stank.capacity = this.tank.capacity = typeof data.capacity !== 'undefined' ? parseFloat(data.capacity) : this.tank.capacity;
1134
1144
  stank.units = this.tank.units = typeof data.units !== 'undefined' ? sys.board.valueMaps.volumeUnits.encode(data.units) : this.tank.units;
1135
- stank.alarmEmptyEnabled = this.tank.alarmEmptyEnabled = typeof data.alarmEmptyEnabled !== 'undefined' ? data.alarmEmptyEnabled : stank.alarmEmptyEnabled;
1136
- stank.alarmEmptyLevel = this.tank.alarmEmptyLevel = typeof data.alarmEmptyLevel !== 'undefined' ? data.alarmEmptyLevel : stank.alarmEmptyLevel;
1145
+ stank.alarmEmptyEnabled = this.tank.alarmEmptyEnabled = typeof data.alarmEmptyEnabled !== 'undefined' ? data.alarmEmptyEnabled : this.tank.alarmEmptyEnabled;
1146
+ stank.alarmEmptyLevel = this.tank.alarmEmptyLevel = typeof data.alarmEmptyLevel !== 'undefined' ? data.alarmEmptyLevel : this.tank.alarmEmptyLevel;
1137
1147
  }
1138
1148
  }
1139
1149
  catch (err) { logger.error(`setTankAsync: ${err.message}`); return Promise.reject(err); }
@@ -1370,8 +1380,7 @@ export class NixieChemPump extends NixieChildEquipment {
1370
1380
  await self.dose(schem);
1371
1381
  }
1372
1382
  catch (err) {
1373
- logger.error(`self.dose error in finally:`);
1374
- logger.error(err);
1383
+ logger.error(`self.dose error in finally: ${err.message}`);
1375
1384
  //return Promise.reject(err); // this isn't a promise we should be returning
1376
1385
  }
1377
1386
  }, 1000);
@@ -1383,8 +1392,7 @@ export class NixieChemPump extends NixieChildEquipment {
1383
1392
  await this.chemical.cancelDosing(schem, 'completed');
1384
1393
  }
1385
1394
  catch (err) {
1386
- logger.error(`this.chemical.cancelDosing error in finally:`);
1387
- logger.error(err);
1395
+ logger.error(`this.chemical.cancelDosing error in finally: ${err.message}`);
1388
1396
  }
1389
1397
  schem.pump.isDosing = this.isOn = false;
1390
1398
  schem.manualDosing = false;
@@ -1416,6 +1424,7 @@ export class NixieChemPump extends NixieChildEquipment {
1416
1424
  export class NixieChemChlor extends NixieChildEquipment {
1417
1425
  public chlor: ChemicalChlor;
1418
1426
  public isOn: boolean;
1427
+ public chlorId = 0;
1419
1428
  public _lastOnStatus: number;
1420
1429
  protected _dosingTimer: NodeJS.Timeout;
1421
1430
  private _isStopping = false;
@@ -1427,7 +1436,7 @@ export class NixieChemChlor extends NixieChildEquipment {
1427
1436
  if (typeof data.chlorDosingMethod !== 'undefined' && data.chlorDosingMethod === 0) {
1428
1437
  if (schlor.chemical.dosingStatus === 0) { await this.chemical.cancelDosing(schlor.chemController.orp, 'dosing method changed'); }
1429
1438
  if (schlor.chemical.dosingStatus === 1) { await this.chemical.cancelMixing(schlor.chemController.orp); }
1430
- let chlor = sys.chlorinators.getItemById(1);
1439
+ let chlor = sys.chlorinators.getItemById(this.chlorId);
1431
1440
  chlor.disabled = false;
1432
1441
  chlor.isDosing = false;
1433
1442
  }
@@ -1479,7 +1488,7 @@ export class NixieChemChlor extends NixieChildEquipment {
1479
1488
  let isBodyOn = schem.chemController.flowDetected;
1480
1489
  await this.chemical.initDose(schem);
1481
1490
  let chemController = schem.getParent()
1482
- let schlor = state.chlorinators.getItemById(1);
1491
+ let schlor = state.chlorinators.getItemById(this.chlorId);
1483
1492
  if (!isBodyOn) {
1484
1493
  // Make sure the chlor is off.
1485
1494
  logger.info(`Chem chlor flow not detected. Body is not running.`);
@@ -1538,7 +1547,7 @@ export class NixieChemChlor extends NixieChildEquipment {
1538
1547
  this._dosingTimer = setTimeout(async () => {
1539
1548
  try { await self.dose(schem); }
1540
1549
  catch (err) {
1541
- logger.error(err);
1550
+ logger.error(`Chem dosing error: ${err.message}`);
1542
1551
  // return Promise.reject(err); // should not be returning a promise in a finally
1543
1552
  }
1544
1553
  }, 1000);
@@ -1556,8 +1565,8 @@ export class NixieChemChlor extends NixieChildEquipment {
1556
1565
  public async turnOff(schem: IChemicalState): Promise<ChlorinatorState> {
1557
1566
  try {
1558
1567
  //logger.info(`Turning off the chlorinator`);
1559
- let chlor = sys.chlorinators.getItemById(1);
1560
- let schlor = state.chlorinators.getItemById(1);
1568
+ let chlor = sys.chlorinators.getItemById(this.chlorId);
1569
+ let schlor = state.chlorinators.getItemById(chlor.id);
1561
1570
  if (schlor.currentOutput === 0 && schlor.targetOutput === 0 && !schlor.superChlor && chlor.disabled && !chlor.isDosing) {
1562
1571
  this.isOn = schem.chlor.isDosing = false;
1563
1572
  return schlor;
@@ -1574,8 +1583,8 @@ export class NixieChemChlor extends NixieChildEquipment {
1574
1583
  }
1575
1584
  public async turnOn(schem: ChemicalState, latchTimeout?: number): Promise<ChlorinatorState> {
1576
1585
  try {
1577
- let chlor = sys.chlorinators.getItemById(1);
1578
- let schlor = state.chlorinators.getItemById(1);
1586
+ let chlor = sys.chlorinators.getItemById(this.chlorId);
1587
+ let schlor = state.chlorinators.getItemById(chlor.id);
1579
1588
  if (schlor.currentOutput === 100 && schlor.targetOutput === 100 && !schlor.superChlor && !chlor.disabled && chlor.isDosing) {
1580
1589
  this.isOn = schem.chlor.isDosing = true;
1581
1590
  return schlor;
@@ -1818,7 +1827,7 @@ export class NixieChemicalPh extends NixieChemical {
1818
1827
  }
1819
1828
  }
1820
1829
  }
1821
- catch (err) { logger.error(err); return Promise.reject(err); }
1830
+ catch (err) { logger.error(`Error checking for dosing: ${err.message}`); return Promise.reject(err); }
1822
1831
  finally {
1823
1832
  logger.debug(`End check ${sph.chemType} dosing status = ${sys.board.valueMaps.chemControllerDosingStatus.getName(sph.dosingStatus)}`);
1824
1833
  }
@@ -1870,7 +1879,7 @@ export class NixieChemicalPh extends NixieChemical {
1870
1879
  logger.verbose(`Chem acid manual calibration dose activate pump`);
1871
1880
  await this.pump.dose(sph);
1872
1881
  }
1873
- catch (err) { logger.error(`calibrateDoseAsync: ${err.message}`); logger.error(err); return Promise.reject(err); }
1882
+ catch (err) { logger.error(`calibrateDoseAsync: ${err.message}`); return Promise.reject(err); }
1874
1883
  }
1875
1884
  public async manualDoseVolumeAsync(sph: ChemicalPhState, volume: number) {
1876
1885
  try {
@@ -1903,7 +1912,7 @@ export class NixieChemicalPh extends NixieChemical {
1903
1912
  await this.pump.dose(sph);
1904
1913
  }
1905
1914
  }
1906
- catch (err) { logger.error(`manualDoseVolumeAsync: ${err.message}`); logger.error(err); return Promise.reject(err); }
1915
+ catch (err) { logger.error(`manualDoseVolumeAsync: ${err.message}`); return Promise.reject(err); }
1907
1916
  }
1908
1917
  public async initDose(sph: ChemicalPhState) {
1909
1918
  try {
@@ -2024,7 +2033,7 @@ export class NixieChemicalORP extends NixieChemical {
2024
2033
  await this.pump.dose(sorp);
2025
2034
  }
2026
2035
  }
2027
- catch (err) { logger.error(`manualDoseVolumeAsync ORP: ${err.message}`); logger.error(err); return Promise.reject(err); }
2036
+ catch (err) { logger.error(`manualDoseVolumeAsync ORP: ${err.message}`); return Promise.reject(err); }
2028
2037
  }
2029
2038
  public async calibrateDoseAsync(sorp: ChemicalORPState, time: number) {
2030
2039
  try {
@@ -2055,7 +2064,7 @@ export class NixieChemicalORP extends NixieChemical {
2055
2064
  logger.verbose(`Chem acid manual dose activate pump ${this.pump.pump.ratedFlow}mL/min`);
2056
2065
  await this.pump.dose(sorp);
2057
2066
  }
2058
- catch (err) { logger.error(`calibrateDoseAsync: ${err.message}`); logger.error(err); return Promise.reject(err); }
2067
+ catch (err) { logger.error(`calibrateDoseAsync: ${err.message}`); return Promise.reject(err); }
2059
2068
  }
2060
2069
 
2061
2070
  public async cancelDosing(sorp: ChemicalORPState, reason: string): Promise<void> {
@@ -2267,8 +2276,8 @@ export class NixieChemicalORP extends NixieChemical {
2267
2276
  }
2268
2277
 
2269
2278
 
2270
- let chlor = sys.chlorinators.getItemById(1); // Still haven't seen any systems with 2+ chlors
2271
- let schlor = state.chlorinators.getItemById(1);
2279
+ let chlor = sys.chlorinators.getItemById(this.chlor.chlorId); // Still haven't seen any systems with 2+ chlors
2280
+ let schlor = state.chlorinators.getItemById(chlor.id);
2272
2281
  // If someone or something is superchloring the pool, let it be
2273
2282
  if (schlor.superChlor) return;
2274
2283
  // Let's have some fun trying to figure out a dynamic approach to chlor management
@@ -140,6 +140,9 @@ export class NixieCircuit extends NixieEquipment {
140
140
  let cstate = state.circuits.getItemById(circuit.id);
141
141
  cstate.startDelay = false;
142
142
  cstate.stopDelay = false;
143
+ cstate.name = circuit.name;
144
+ cstate.type = circuit.type;
145
+ cstate.showInFeatures = circuit.showInFeatures;
143
146
  }
144
147
  public async setServiceModeAsync() {
145
148
  let cstate = state.circuits.getItemById(this.circuit.id);
@@ -174,6 +177,36 @@ export class NixieCircuit extends NixieEquipment {
174
177
  }
175
178
  return res;
176
179
  }
180
+ protected async setWaterColorsThemeAsync(cstate: CircuitState, theme: any): Promise<InterfaceServerResponse> {
181
+ let ptheme = sys.board.valueMaps.lightThemes.findItem(cstate.lightingTheme) || { val: 0, sequence: 0 };
182
+ // First check to see if we are on. If we are not then we need to emit our status as if we are initializing and busy.
183
+ let arr = [];
184
+ if (ptheme.val === 0) {
185
+ // We don't know our previous theme so we are going to sync the lights to get a starting point.
186
+ arr.push({ isOn: true, timeout: 1000 }); // Turn on for 1 second
187
+ arr.push({ isOn: false, timeout: 5000 }); // Turn off for 5 seconds
188
+ arr.push({ isOn: true, timeout: 1000 });
189
+ ptheme = sys.board.valueMaps.lightThemes.findItem('eveningsea');
190
+ }
191
+ let count = theme.sequence - ptheme.sequence;
192
+ if (count < 0) count = count + 16;
193
+ for (let i = 0; i < count; i++) {
194
+ arr.push({ isOn: true, timeout: 200 });
195
+ arr.push({ isOn: false, timeout: 200 });
196
+ }
197
+ console.log(arr);
198
+ if (arr.length === 0) return new InterfaceServerResponse(200, 'Success');
199
+ let res = await NixieEquipment.putDeviceService(this.circuit.connectionId, `/state/device/${this.circuit.deviceBinding}`, arr, 60000);
200
+ // Even though we ended with on we need to make sure that the relay stays on now that we are done.
201
+ if (!res.error) {
202
+ cstate.lightingTheme = ptheme.val;
203
+ cstate.isOn = true; // At this point the relay will be off but we want the process
204
+ // to assume that the relay state is not actually changing.
205
+ this._sequencing = false;
206
+ await this.setCircuitStateAsync(cstate, true, false);
207
+ }
208
+ return res;
209
+ }
177
210
  protected async setColorLogicThemeAsync(cstate: CircuitState, theme: any): Promise<InterfaceServerResponse> {
178
211
  let ptheme = sys.board.valueMaps.lightThemes.findItem(cstate.lightingTheme) || { val: 0, sequence: 0 };
179
212
  // First check to see if we are on. If we are not then we need to emit our status as if we are initializing and busy.
@@ -234,6 +267,9 @@ export class NixieCircuit extends NixieEquipment {
234
267
  case 'colorlogic':
235
268
  res = await this.setColorLogicThemeAsync(cstate, theme);
236
269
  break;
270
+ case 'watercolors':
271
+ res = await this.setWaterColorsThemeAsync(cstate, theme);
272
+ break;
237
273
  }
238
274
  cstate.action = 0;
239
275
  // Make sure clients know that we are done.
@@ -287,11 +287,15 @@ export class NixieSolarHeater extends NixieHeaterBase {
287
287
  if (typeof this._lastState === 'undefined' || target || this._lastState !== target) {
288
288
  if (utils.isNullOrEmpty(this.heater.connectionId) || utils.isNullOrEmpty(this.heater.deviceBinding)) {
289
289
  this._lastState = hstate.isOn = target;
290
+ hstate.isCooling = target && isCooling;
290
291
  }
291
292
  else {
292
293
  let res = await NixieEquipment.putDeviceService(this.heater.connectionId, `/state/device/${this.heater.deviceBinding}`,
293
294
  { isOn: target, latch: target ? 10000 : undefined });
294
- if (res.status.code === 200) this._lastState = hstate.isOn = target;
295
+ if (res.status.code === 200) {
296
+ this._lastState = hstate.isOn = target;
297
+ hstate.isCooling = target && isCooling;
298
+ }
295
299
  else logger.error(`Nixie Error setting heater state: ${res.status.code} -${res.status.message} ${res.error.message}`);
296
300
  }
297
301
  if (target) {
@@ -578,6 +582,7 @@ export class NixieMastertemp extends NixieGasHeater {
578
582
  // Set the polling interval to 3 seconds.
579
583
  this.pollEquipmentAsync();
580
584
  this.pollingInterval = 3000;
585
+
581
586
  }
582
587
  /* public getCooldownTime(): number {
583
588
  // Delays are always in terms of seconds so convert the minute to seconds.
@@ -589,12 +594,26 @@ export class NixieMastertemp extends NixieGasHeater {
589
594
  public async setHeaterStateAsync(hstate: HeaterState, isOn: boolean) {
590
595
  try {
591
596
  // Initialize the desired state.
597
+ this.isOn = isOn;
592
598
  this.isCooling = false;
593
- // Here we go we need to set the firemans switch state.
594
- if (hstate.isOn !== isOn) {
595
- logger.info(`Nixie: Set Heater ${hstate.id}-${hstate.name} to ${isOn}`);
599
+ let target = hstate.startupDelay === false && isOn;
600
+ if (target && typeof hstate.endTime !== 'undefined') {
601
+ // Calculate a short cycle time so that the gas heater does not cycle
602
+ // too often. For gas heaters this is 60 seconds. This gives enough time
603
+ // for the heater control circuit to make a full cycle.
604
+ if (new Date().getTime() - hstate.endTime.getTime() < this.heater.minCycleTime * 60000) {
605
+ logger.verbose(`${hstate.name} short cycle detected deferring turn on state`);
606
+ target = false;
607
+ }
608
+ }
609
+ // Here we go we need to set the state.
610
+ if (hstate.isOn !== target) {
611
+ logger.info(`Nixie: Set Mastertemp ${hstate.id}-${hstate.name} to ${isOn}`);
612
+ }
613
+ if (typeof this._lastState === 'undefined' || target || this._lastState !== target) {
614
+ this._lastState = hstate.isOn = target;
615
+ if (target) this.lastHeatCycle = new Date();
596
616
  }
597
- if (isOn && !hstate.startupDelay) this.lastHeatCycle = new Date();
598
617
  hstate.isOn = isOn;
599
618
  } catch (err) { return logger.error(`Nixie Error setting heater state ${hstate.id}-${hstate.name}: ${err.message}`); }
600
619
  }