nodejs-poolcontroller 7.7.0 → 8.0.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 (82) hide show
  1. package/.eslintrc.json +26 -35
  2. package/Changelog +22 -0
  3. package/README.md +7 -3
  4. package/anslq25/MessagesMock.ts +218 -0
  5. package/anslq25/boards/MockBoardFactory.ts +50 -0
  6. package/anslq25/boards/MockEasyTouchBoard.ts +696 -0
  7. package/anslq25/boards/MockSystemBoard.ts +217 -0
  8. package/anslq25/chemistry/MockChlorinator.ts +75 -0
  9. package/anslq25/pumps/MockPump.ts +84 -0
  10. package/app.ts +10 -14
  11. package/config/Config.ts +13 -9
  12. package/config/VersionCheck.ts +6 -2
  13. package/controller/Constants.ts +58 -25
  14. package/controller/Equipment.ts +224 -41
  15. package/controller/Errors.ts +2 -1
  16. package/controller/Lockouts.ts +34 -2
  17. package/controller/State.ts +491 -48
  18. package/controller/boards/AquaLinkBoard.ts +6 -3
  19. package/controller/boards/BoardFactory.ts +5 -1
  20. package/controller/boards/EasyTouchBoard.ts +1971 -1751
  21. package/controller/boards/IntelliCenterBoard.ts +1311 -1688
  22. package/controller/boards/IntelliComBoard.ts +7 -1
  23. package/controller/boards/IntelliTouchBoard.ts +153 -42
  24. package/controller/boards/NixieBoard.ts +209 -66
  25. package/controller/boards/SunTouchBoard.ts +393 -0
  26. package/controller/boards/SystemBoard.ts +1862 -1543
  27. package/controller/comms/Comms.ts +539 -138
  28. package/controller/comms/ScreenLogic.ts +1663 -0
  29. package/controller/comms/messages/Messages.ts +242 -60
  30. package/controller/comms/messages/config/ChlorinatorMessage.ts +4 -3
  31. package/controller/comms/messages/config/CircuitGroupMessage.ts +5 -2
  32. package/controller/comms/messages/config/CircuitMessage.ts +81 -13
  33. package/controller/comms/messages/config/ConfigMessage.ts +3 -1
  34. package/controller/comms/messages/config/CoverMessage.ts +2 -1
  35. package/controller/comms/messages/config/CustomNameMessage.ts +2 -1
  36. package/controller/comms/messages/config/EquipmentMessage.ts +5 -1
  37. package/controller/comms/messages/config/ExternalMessage.ts +33 -3
  38. package/controller/comms/messages/config/FeatureMessage.ts +2 -1
  39. package/controller/comms/messages/config/GeneralMessage.ts +2 -1
  40. package/controller/comms/messages/config/HeaterMessage.ts +3 -1
  41. package/controller/comms/messages/config/IntellichemMessage.ts +2 -1
  42. package/controller/comms/messages/config/OptionsMessage.ts +12 -6
  43. package/controller/comms/messages/config/PumpMessage.ts +9 -12
  44. package/controller/comms/messages/config/RemoteMessage.ts +80 -13
  45. package/controller/comms/messages/config/ScheduleMessage.ts +43 -3
  46. package/controller/comms/messages/config/SecurityMessage.ts +2 -1
  47. package/controller/comms/messages/config/ValveMessage.ts +43 -26
  48. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +8 -7
  49. package/controller/comms/messages/status/EquipmentStateMessage.ts +93 -20
  50. package/controller/comms/messages/status/HeaterStateMessage.ts +24 -5
  51. package/controller/comms/messages/status/IntelliChemStateMessage.ts +7 -4
  52. package/controller/comms/messages/status/IntelliValveStateMessage.ts +2 -1
  53. package/controller/comms/messages/status/PumpStateMessage.ts +72 -4
  54. package/controller/comms/messages/status/VersionMessage.ts +2 -1
  55. package/controller/nixie/Nixie.ts +15 -4
  56. package/controller/nixie/NixieEquipment.ts +1 -0
  57. package/controller/nixie/chemistry/ChemController.ts +300 -129
  58. package/controller/nixie/chemistry/ChemDoser.ts +806 -0
  59. package/controller/nixie/chemistry/Chlorinator.ts +133 -129
  60. package/controller/nixie/circuits/Circuit.ts +171 -30
  61. package/controller/nixie/heaters/Heater.ts +337 -173
  62. package/controller/nixie/pumps/Pump.ts +264 -236
  63. package/controller/nixie/schedules/Schedule.ts +9 -3
  64. package/defaultConfig.json +45 -5
  65. package/logger/Logger.ts +38 -9
  66. package/package.json +13 -9
  67. package/web/Server.ts +235 -122
  68. package/web/bindings/aqualinkD.json +114 -59
  69. package/web/bindings/homeassistant.json +437 -0
  70. package/web/bindings/influxDB.json +15 -0
  71. package/web/bindings/mqtt.json +28 -9
  72. package/web/bindings/mqttAlt.json +15 -0
  73. package/web/interfaces/baseInterface.ts +58 -7
  74. package/web/interfaces/httpInterface.ts +5 -2
  75. package/web/interfaces/influxInterface.ts +9 -2
  76. package/web/interfaces/mqttInterface.ts +234 -74
  77. package/web/interfaces/ruleInterface.ts +87 -0
  78. package/web/services/config/Config.ts +140 -33
  79. package/web/services/config/ConfigSocket.ts +2 -1
  80. package/web/services/state/State.ts +144 -3
  81. package/web/services/state/StateSocket.ts +65 -14
  82. package/web/services/utilities/Utilities.ts +189 -1
@@ -3,13 +3,13 @@ import { utils, Timestamp } from '../../Constants';
3
3
  import { logger } from '../../../logger/Logger';
4
4
 
5
5
  import { NixieEquipment, NixieChildEquipment, NixieEquipmentCollection, INixieControlPanel } from "../NixieEquipment";
6
- import { Heater, HeaterCollection, sys } from "../../../controller/Equipment";
6
+ import { Body, Heater, HeaterCollection, sys } from "../../../controller/Equipment";
7
7
  import { BodyTempState, HeaterState, state, } from "../../State";
8
8
  import { setTimeout, clearTimeout } from 'timers';
9
9
  import { NixieControlPanel } from '../Nixie';
10
10
  import { webApp, InterfaceServerResponse } from "../../../web/Server";
11
11
  import { conn } from '../../../controller/comms/Comms';
12
- import { Outbound, Protocol, Response } from '../../../controller/comms/messages/Messages';
12
+ import { Inbound, Outbound, Protocol, Response } from '../../../controller/comms/messages/Messages';
13
13
  import { delayMgr } from '../../Lockouts';
14
14
 
15
15
  export class NixieHeaterCollection extends NixieEquipmentCollection<NixieHeaterBase> {
@@ -86,6 +86,14 @@ export class NixieHeaterCollection extends NixieEquipmentCollection<NixieHeaterB
86
86
  return c;
87
87
  } catch (err) { logger.error(`initHeaterAsync: ${err.message}`); return Promise.reject(err); }
88
88
  }
89
+ public async setServiceModeAsync() {
90
+ try {
91
+ for (let i = this.length - 1; i >= 0; i--) {
92
+ let heater = this[i] as NixieHeaterBase;
93
+ await heater.setServiceModeAsync();
94
+ }
95
+ } catch (err) { return Promise.reject(`Nixie Control Panel setServiceMode ${err.message}`); }
96
+ }
89
97
  }
90
98
  export class NixieHeaterBase extends NixieEquipment {
91
99
  protected _suspendPolling: number = 0;
@@ -120,6 +128,8 @@ export class NixieHeaterBase extends NixieEquipment {
120
128
  return new NixieMastertemp(ncp, heater);
121
129
  case 'solar':
122
130
  return new NixieSolarHeater(ncp, heater);
131
+ case 'hybrid':
132
+ return new NixieUltraTempETi(ncp, heater);
123
133
  default:
124
134
  return new NixieHeaterBase(ncp, heater);
125
135
  }
@@ -140,11 +150,15 @@ export class NixieHeaterBase extends NixieEquipment {
140
150
  public async setHeaterAsync(data: any) {
141
151
  try {
142
152
  let heater = this.heater;
143
-
153
+
144
154
  }
145
155
  catch (err) { logger.error(`Nixie setHeaterAsync: ${err.message}`); return Promise.reject(err); }
146
156
  }
147
- public async closeAsync() {}
157
+ public async closeAsync() { }
158
+ public async setServiceModeAsync() {
159
+ let hstate = state.heaters.getItemById(this.heater.id);
160
+ await this.setHeaterStateAsync(hstate, false, false);
161
+ }
148
162
  }
149
163
  export class NixieGasHeater extends NixieHeaterBase {
150
164
  public pollingInterval: number = 10000;
@@ -161,7 +175,7 @@ export class NixieGasHeater extends NixieHeaterBase {
161
175
  // Delays are always in terms of seconds so convert the minute to seconds.
162
176
  if (this.heater.cooldownDelay === 0 || typeof this.lastHeatCycle === 'undefined') return 0;
163
177
  let now = new Date().getTime();
164
- let cooldown = this.isOn ? this.heater.cooldownDelay * 60000 : Math.round( ((this.lastHeatCycle.getDate() + this.heater.cooldownDelay * 60000) - now) / 1000);
178
+ let cooldown = this.isOn ? this.heater.cooldownDelay * 60000 : Math.round(((this.lastHeatCycle.getDate() + this.heater.cooldownDelay * 60000) - now) / 1000);
165
179
  return Math.min(Math.max(0, cooldown), this.heater.cooldownDelay * 60);
166
180
  }
167
181
  public async setHeaterStateAsync(hstate: HeaterState, isOn: boolean) {
@@ -349,93 +363,104 @@ export class NixieSolarHeater extends NixieHeaterBase {
349
363
  public logData(filename: string, data: any) { this.controlPanel.logData(filename, data); }
350
364
  }
351
365
  export class NixieHeatpump extends NixieHeaterBase {
352
- public configSent: boolean = false;
366
+ public pollingInterval: number = 10000;
367
+ //declare heater: Heater;
353
368
  constructor(ncp: INixieControlPanel, heater: Heater) {
354
369
  super(ncp, heater);
355
- // Set the polling interval to 3 seconds.
370
+ this.heater = heater;
371
+ if (typeof this.heater.stopTempDelta === 'undefined') this.heater.stopTempDelta = 1;
372
+ if (typeof this.heater.minCycleTime === 'undefined') this.heater.minCycleTime = 2;
356
373
  this.pollEquipmentAsync();
357
374
  }
358
- public async setHeaterStateAsync(hstate: HeaterState, isOn: boolean, isCooling: boolean) {
375
+ public get id(): number { return typeof this.heater !== 'undefined' ? this.heater.id : -1; }
376
+ public getCooldownTime(): number { return 0; } // There is no cooldown delay at this time for a heatpump
377
+ public async setHeaterStateAsync(hstate: HeaterState, isOn: boolean) {
359
378
  try {
360
- this.suspendPolling = true;
379
+ // Initialize the desired state.
361
380
  this.isOn = isOn;
362
- this.isCooling = isCooling;
363
- if (!hstate.startupDelay) {
364
- if (isOn && !isCooling) this.lastHeatCycle = new Date();
365
- else if (isCooling) this.lastCoolCycle = new Date();
381
+ this.isCooling = false;
382
+ let target = hstate.startupDelay === false && isOn;
383
+ if (target && typeof hstate.endTime !== 'undefined') {
384
+ // Calculate a short cycle time so that the gas heater does not cycle
385
+ // too often. For gas heaters this is 60 seconds. This gives enough time
386
+ // for the heater control circuit to make a full cycle.
387
+ if (new Date().getTime() - hstate.endTime.getTime() < this.heater.minCycleTime * 60000) {
388
+ logger.verbose(`${hstate.name} short cycle detected deferring turn on state`);
389
+ target = false;
390
+ }
366
391
  }
367
- // When this is implemented lets not forget to deal with the startup and any desired cool down delay. See UltraTemp for implementation.
368
- } catch (err) { return logger.error(`Nixie Error setting heater state ${hstate.id}-${hstate.name}: ${err.message}`); }
369
- finally { this.suspendPolling = false; }
392
+ // Here we go we need to set the firemans switch state.
393
+ if (hstate.isOn !== target) {
394
+ logger.info(`Nixie: Set Heatpump ${hstate.id}-${hstate.name} to ${isOn}`);
395
+ }
396
+ if (typeof this._lastState === 'undefined' || target || this._lastState !== target) {
397
+ if (utils.isNullOrEmpty(this.heater.connectionId) || utils.isNullOrEmpty(this.heater.deviceBinding)) {
398
+ this._lastState = hstate.isOn = target;
399
+ }
400
+ else {
401
+ let res = await NixieEquipment.putDeviceService(this.heater.connectionId, `/state/device/${this.heater.deviceBinding}`,
402
+ { isOn: target, latch: target ? 10000 : undefined });
403
+ if (res.status.code === 200) this._lastState = hstate.isOn = target;
404
+ else logger.error(`Nixie Error setting heatpump state: ${res.status.code} -${res.status.message} ${res.error.message}`);
405
+ }
406
+ if (target) this.lastHeatCycle = new Date();
407
+ }
408
+ } catch (err) { return logger.error(`Nixie Error setting heatpump state ${hstate.id}-${hstate.name}: ${err.message}`); }
370
409
  }
371
410
  public async pollEquipmentAsync() {
372
411
  let self = this;
373
412
  try {
374
- this.suspendPolling = true;
375
413
  if (typeof this._pollTimer !== 'undefined' || this._pollTimer) clearTimeout(this._pollTimer);
376
414
  this._pollTimer = null;
377
- if (this._suspendPolling > 1) return;
378
- let sheater = state.heaters.getItemById(this.heater.id, !this.closing);
379
- // If the body isn't on then we won't communicate with the chem controller. There is no need
380
- // since most of the time these are attached to the filter relay.
381
- if (this.isBodyOn() && !this.closing) {
382
- await this.sendState(sheater);
383
- //if (!this.closing) await this.requestStatus(sheater);
384
- }
385
- }
386
- catch (err) { logger.error(`Error polling Heat Pump - ${err}`); }
387
- finally {
388
- this.suspendPolling = false; if (!this.closing) this._pollTimer = setTimeout(async () => {
389
- try { await self.pollEquipmentAsync() } catch (err) { }
390
- }, this.pollingInterval || 10000);
415
+ let success = false;
391
416
  }
417
+ catch (err) { logger.error(`Nixie Error polling Heatpump - ${err}`); }
418
+ finally { this._pollTimer = setTimeout(async () => await self.pollEquipmentAsync(), this.pollingInterval || 10000); }
392
419
  }
393
- public async sendState(sheater: HeaterState): Promise<boolean> {
394
- try {
395
- sheater.type = 2;
396
- let success = await new Promise<boolean>((resolve, reject) => {
397
- let out = Outbound.create({
398
- portId: this.heater.portId || 0,
399
- protocol: Protocol.Heater,
400
- source: 16,
401
- dest: this.heater.address,
402
- action: 210,
403
- payload: [210],
404
- retries: 3, // We are going to try 4 times.
405
- response: Response.create({ protocol: Protocol.IntelliChem, action: 18 }),
406
- onAbort: () => { },
407
- onComplete: (err) => {
408
- if (err) {
409
- // If the IntelliChem is not responding we need to store that off. If an 18 does
410
- // come across this will be cleared by the processing of that message.
411
- resolve(false);
412
- }
413
- else { resolve(true); }
414
- }
415
- });
416
- conn.queueSendMessage(out);
417
- });
418
- return success;
419
- } catch (err) { logger.error(`Communication error with Ultratemp : ${err.message}`); }
420
+ private async checkHardwareStatusAsync(connectionId: string, deviceBinding: string) {
421
+ try {
422
+ let dev = await NixieEquipment.getDeviceService(connectionId, `/status/device/${deviceBinding}`);
423
+ return dev;
424
+ } catch (err) { logger.error(`Nixie Heatpump Error checkHardwareStatusAsync: ${err.message}`); return { hasFault: true } }
425
+ }
426
+ public async validateSetupAsync(heater: Heater, hstate: HeaterState) {
427
+ try {
428
+ if (typeof heater.connectionId !== 'undefined' && heater.connectionId !== ''
429
+ && typeof heater.deviceBinding !== 'undefined' && heater.deviceBinding !== '') {
430
+ try {
431
+ let stat = await this.checkHardwareStatusAsync(heater.connectionId, heater.deviceBinding);
432
+ // If we have a status check the return.
433
+ hstate.commStatus = stat.hasFault ? 1 : 0;
434
+ } catch (err) { hstate.commStatus = 1; }
435
+ }
436
+ else
437
+ hstate.commStatus = 0;
438
+ } catch (err) { logger.error(`Nixie Error checking heatpump Hardware ${this.heater.name}: ${err.message}`); hstate.commStatus = 1; return Promise.reject(err); }
420
439
  }
421
440
  public async closeAsync() {
422
441
  try {
423
- this.suspendPolling = true;
424
- this.closing = true;
425
442
  if (typeof this._pollTimer !== 'undefined' || this._pollTimer) clearTimeout(this._pollTimer);
426
443
  this._pollTimer = null;
427
- logger.info(`Closing Heater ${this.heater.name}`);
428
-
444
+ let hstate = state.heaters.getItemById(this.heater.id);
445
+ await this.setHeaterStateAsync(hstate, false);
446
+ hstate.emitEquipmentChange();
429
447
  }
430
- catch (err) { logger.error(`Ultratemp closeAsync: ${err.message}`); return Promise.reject(err); }
448
+ catch (err) { logger.error(`Nixie Heatpump closeAsync: ${err.message}`); return Promise.reject(err); }
431
449
  }
450
+ public logData(filename: string, data: any) { this.controlPanel.logData(filename, data); }
432
451
  }
433
- export class NixieUltratemp extends NixieHeatpump {
452
+ export class NixieUltratemp extends NixieHeaterBase {
434
453
  constructor(ncp: INixieControlPanel, heater: Heater) {
435
454
  super(ncp, heater);
436
455
  // Set the polling interval to 3 seconds.
437
456
  this.pollEquipmentAsync();
438
457
  }
458
+ public async setServiceModeAsync() {
459
+ let hstate = state.heaters.getItemById(this.heater.id);
460
+ await this.setHeaterStateAsync(hstate, false, false);
461
+ await this.releaseHeater(hstate);
462
+ }
463
+
439
464
  public async pollEquipmentAsync() {
440
465
  let self = this;
441
466
  try {
@@ -453,7 +478,7 @@ export class NixieUltratemp extends NixieHeatpump {
453
478
  catch (err) { logger.error(`Error polling UltraTemp heater - ${err}`); }
454
479
  finally {
455
480
  this.suspendPolling = false; if (!this.closing) this._pollTimer = setTimeout(async () => {
456
- try { await self.pollEquipmentAsync() } catch (err) {}
481
+ try { await self.pollEquipmentAsync() } catch (err) { }
457
482
  }, this.pollingInterval || 10000);
458
483
  }
459
484
  }
@@ -463,84 +488,76 @@ export class NixieUltratemp extends NixieHeatpump {
463
488
  this.isCooling = isCooling;
464
489
  if (hstate.isOn !== isOn) {
465
490
  logger.info(`Nixie: Set Heater ${hstate.id}-${hstate.name} to ${isCooling ? 'cooling' : isOn ? 'heating' : 'off'}`);
466
-
491
+
467
492
  }
468
493
  if (isOn && !hstate.startupDelay) this.lastHeatCycle = new Date();
469
- hstate.isOn = isOn;
494
+ this.isOn = hstate.isOn = isOn;
470
495
  } catch (err) { return logger.error(`Nixie Error setting heater state ${hstate.id}-${hstate.name}: ${err.message}`); }
471
496
  }
472
497
  public async releaseHeater(sheater: HeaterState): Promise<boolean> {
473
498
  try {
474
- let success = await new Promise<boolean>((resolve, reject) => {
475
- let out = Outbound.create({
476
- portId: this.heater.portId || 0,
477
- protocol: Protocol.Heater,
478
- source: 16,
479
- dest: this.heater.address,
480
- action: 114,
481
- payload: [],
482
- retries: 3, // We are going to try 4 times.
483
- response: Response.create({ protocol: Protocol.Heater, action: 115 }),
484
- onAbort: () => { },
485
- onComplete: (err) => {
486
- if (err) {
487
- // If the Ultratemp is not responding we need to store that off but at this point we know none of the codes. If a 115 does
488
- // come across this will be cleared by the processing of that message.
489
- sheater.commStatus = sys.board.valueMaps.equipmentCommStatus.getValue('commerr');
490
- state.equipment.messages.setMessageByCode(`heater:${sheater.id}:comms`, 'error', `Communication error with ${sheater.name}`);
491
- resolve(false);
492
- }
493
- else { resolve(true); }
494
- }
495
- });
496
- out.appendPayloadBytes(0, 10);
497
- out.setPayloadByte(0, 144);
498
- out.setPayloadByte(1, 0, 0);
499
- conn.queueSendMessage(out);
499
+ let out = Outbound.create({
500
+ portId: this.heater.portId || 0,
501
+ protocol: Protocol.Heater,
502
+ source: 16,
503
+ dest: this.heater.address,
504
+ action: 114,
505
+ payload: [],
506
+ retries: 3, // We are going to try 4 times.
507
+ response: Response.create({ protocol: Protocol.Heater, action: 115 }),
508
+ onAbort: () => { }
500
509
  });
501
- return success;
502
- } catch (err) { logger.error(`Communication error with Ultratemp : ${err.message}`); }
510
+ out.appendPayloadBytes(0, 10);
511
+ out.setPayloadByte(0, 144);
512
+ out.setPayloadByte(1, 0, 0);
513
+ await out.sendAsync();
514
+ return true;
515
+
516
+ } catch (err) {
517
+ // If the Ultratemp is not responding we need to store that off but at this point we know none of the codes. If a 115 does
518
+ // come across this will be cleared by the processing of that message.
519
+ sheater.commStatus = sys.board.valueMaps.equipmentCommStatus.getValue('commerr');
520
+ state.equipment.messages.setMessageByCode(`heater:${sheater.id}:comms`, 'error', `Communication error with ${sheater.name}`);
521
+ logger.error(`Communication error with Ultratemp : ${err.message}`);
522
+ return false;
523
+ }
503
524
  }
504
525
  public async setStatus(sheater: HeaterState): Promise<boolean> {
505
526
  try {
506
- let success = await new Promise<boolean>((resolve, reject) => {
507
- let out = Outbound.create({
508
- portId: this.heater.portId || 0,
509
- protocol: Protocol.Heater,
510
- source: 16,
511
- dest: this.heater.address,
512
- action: 114,
513
- payload: [],
514
- retries: 3, // We are going to try 4 times.
515
- response: Response.create({ protocol: Protocol.Heater, action: 115 }),
516
- onAbort: () => { },
517
- onComplete: (err) => {
518
- if (err) {
519
- // If the Ultratemp is not responding we need to store that off but at this point we know none of the codes. If a 115 does
520
- // come across this will be cleared by the processing of that message.
521
- sheater.commStatus = sys.board.valueMaps.equipmentCommStatus.getValue('commerr');
522
- state.equipment.messages.setMessageByCode(`heater:${sheater.id}:comms`, 'error', `Communication error with ${sheater.name}`);
523
- resolve(false);
524
- }
525
- else { resolve(true); }
526
- }
527
- });
528
- out.appendPayloadBytes(0, 10);
529
- out.setPayloadByte(0, 144);
530
- // If we are in startup delay simply tell the heater that it is off.
531
- if (sheater.startupDelay || this.closing)
532
- out.setPayloadByte(1, 0, 0);
533
- else {
534
- if (this.isOn) {
535
- if (!this.isCooling) this.lastHeatCycle = new Date();
536
- else this.lastCoolCycle = new Date();
537
- }
538
- out.setPayloadByte(1, this.isOn ? (this.isCooling ? 2 : 1) : 0, 0);
539
- }
540
- conn.queueSendMessage(out);
527
+ let out = Outbound.create({
528
+ portId: this.heater.portId || 0,
529
+ protocol: Protocol.Heater,
530
+ source: 16,
531
+ dest: this.heater.address,
532
+ action: 114,
533
+ payload: [],
534
+ retries: 3, // We are going to try 4 times.
535
+ response: Response.create({ protocol: Protocol.Heater, action: 115 }),
536
+ onAbort: () => { }
541
537
  });
538
+ out.appendPayloadBytes(0, 10);
539
+ out.setPayloadByte(0, 144);
540
+ // If we are in startup delay simply tell the heater that it is off.
541
+ if (sheater.startupDelay || this.closing)
542
+ out.setPayloadByte(1, 0, 0);
543
+ else {
544
+ if (this.isOn) {
545
+ if (!this.isCooling) this.lastHeatCycle = new Date();
546
+ else this.lastCoolCycle = new Date();
547
+ }
548
+ //console.log(`Setting the heater byte ${this.isOn} ${sheater.isOn} to ${this.isOn ? (this.isCooling ? 2 : 1) : 0}`);
549
+ out.setPayloadByte(1, this.isOn ? (this.isCooling ? 2 : 1) : 0, 0);
550
+ }
551
+ let success = await out.sendAsync();
542
552
  return success;
543
- } catch (err) { logger.error(`Communication error with Ultratemp : ${err.message}`); }
553
+ } catch (err) {
554
+ // If the Ultratemp is not responding we need to store that off but at this point we know none of the codes. If a 115 does
555
+ // come across this will be cleared by the processing of that message.
556
+ sheater.commStatus = sys.board.valueMaps.equipmentCommStatus.getValue('commerr');
557
+ state.equipment.messages.setMessageByCode(`heater:${sheater.id}:comms`, 'error', `Communication error with ${sheater.name}`);
558
+ logger.error(`Communication error with Ultratemp : ${err.message}`);
559
+ return false;
560
+ }
544
561
  }
545
562
  public async closeAsync() {
546
563
  try {
@@ -562,13 +579,13 @@ export class NixieMastertemp extends NixieGasHeater {
562
579
  this.pollEquipmentAsync();
563
580
  this.pollingInterval = 3000;
564
581
  }
565
- /* public getCooldownTime(): number {
566
- // Delays are always in terms of seconds so convert the minute to seconds.
567
- if (this.heater.cooldownDelay === 0 || typeof this.lastHeatCycle === 'undefined') return 0;
568
- let now = new Date().getTime();
569
- let cooldown = this.isOn ? this.heater.cooldownDelay * 60000 : Math.round(((this.lastHeatCycle.getDate() + this.heater.cooldownDelay * 60000) - now) / 1000);
570
- return Math.min(Math.max(0, cooldown), this.heater.cooldownDelay * 60);
571
- } */
582
+ /* public getCooldownTime(): number {
583
+ // Delays are always in terms of seconds so convert the minute to seconds.
584
+ if (this.heater.cooldownDelay === 0 || typeof this.lastHeatCycle === 'undefined') return 0;
585
+ let now = new Date().getTime();
586
+ let cooldown = this.isOn ? this.heater.cooldownDelay * 60000 : Math.round(((this.lastHeatCycle.getDate() + this.heater.cooldownDelay * 60000) - now) / 1000);
587
+ return Math.min(Math.max(0, cooldown), this.heater.cooldownDelay * 60);
588
+ } */
572
589
  public async setHeaterStateAsync(hstate: HeaterState, isOn: boolean) {
573
590
  try {
574
591
  // Initialize the desired state.
@@ -600,42 +617,41 @@ export class NixieMastertemp extends NixieGasHeater {
600
617
  }
601
618
  public async setStatus(sheater: HeaterState): Promise<boolean> {
602
619
  try {
603
- let success = await new Promise<boolean>((resolve, reject) => {
604
- let out = Outbound.create({
605
- portId: this.heater.portId || 0,
606
- protocol: Protocol.Heater,
607
- source: 16,
608
- dest: this.heater.address,
609
- action: 112,
610
- payload: [],
611
- retries: 3, // We are going to try 4 times.
612
- response: Response.create({ protocol: Protocol.Heater, action: 116 }),
613
- onAbort: () => { },
614
- onComplete: (err) => {
615
- if (err) {
616
- // If the MasterTemp is not responding we need to store that off but at this point we know none of the codes. If a 115 does
617
- // come across this will be cleared by the processing of that message.
618
- sheater.commStatus = sys.board.valueMaps.equipmentCommStatus.getValue('commerr');
619
- state.equipment.messages.setMessageByCode(`heater:${sheater.id}:comms`, 'error', `Communication error with ${sheater.name}`);
620
- resolve(false);
621
- }
622
- else { resolve(true); }
623
- }
624
- });
625
- out.appendPayloadBytes(0, 11);
626
- // If we have a startup delay we need to simply send 0 to the heater to make sure that it is off.
627
- if (sheater.startupDelay)
628
- out.setPayloadByte(0, 0);
629
- else {
630
- // The cooldown delay is a bit hard to figure out here since I think the heater does it on its own.
631
- out.setPayloadByte(0, sheater.bodyId <= 2 ? sheater.bodyId : 0);
632
- }
633
- out.setPayloadByte(1, sys.bodies.getItemById(1).heatSetpoint || 0);
634
- out.setPayloadByte(2, sys.bodies.getItemById(2).heatSetpoint || 0);
635
- conn.queueSendMessage(out);
620
+ let out = Outbound.create({
621
+ portId: this.heater.portId || 0,
622
+ protocol: Protocol.Heater,
623
+ source: 16,
624
+ dest: this.heater.address,
625
+ action: 112,
626
+ payload: [],
627
+ retries: 3, // We are going to try 4 times.
628
+ response: Response.create({ protocol: Protocol.Heater, action: 116 }),
629
+ onAbort: () => { }
636
630
  });
631
+ out.appendPayloadBytes(0, 11);
632
+ // If we have a startup delay we need to simply send 0 to the heater to make sure that it is off.
633
+ if (sheater.startupDelay)
634
+ out.setPayloadByte(0, 0);
635
+ else {
636
+ // The cooldown delay is a bit hard to figure out here since I think the heater does it on its own.
637
+ out.setPayloadByte(0, sheater.bodyId <= 2 ? sheater.bodyId : 0);
638
+ }
639
+ out.setPayloadByte(1, sys.bodies.getItemById(1).heatSetpoint || 0);
640
+ out.setPayloadByte(2, sys.bodies.getItemById(2).heatSetpoint || 0);
641
+ let success = await out.sendAsync();
637
642
  return success;
638
- } catch (err) { logger.error(`Communication error with MasterTemp : ${err.message}`); }
643
+ } catch (err) {
644
+ // If the MasterTemp is not responding we need to store that off but at this point we know none of the codes. If a 115 does
645
+ // come across this will be cleared by the processing of that message.
646
+ sheater.commStatus = sys.board.valueMaps.equipmentCommStatus.getValue('commerr');
647
+ state.equipment.messages.setMessageByCode(`heater:${sheater.id}:comms`, 'error', `Communication error with ${sheater.name}`);
648
+ logger.error(`Communication error with MasterTemp : ${err.message}`);
649
+ return false;
650
+ }
651
+ }
652
+ public async setServiceModeAsync() {
653
+ let hstate = state.heaters.getItemById(this.heater.id);
654
+ await this.setHeaterStateAsync(hstate, false);
639
655
  }
640
656
  public async closeAsync() {
641
657
  try {
@@ -648,4 +664,152 @@ export class NixieMastertemp extends NixieGasHeater {
648
664
  }
649
665
  catch (err) { logger.error(`MasterTemp closeAsync: ${err.message}`); return Promise.reject(err); }
650
666
  }
651
- }
667
+ }
668
+ export class NixieUltraTempETi extends NixieHeaterBase {
669
+ constructor(ncp: INixieControlPanel, heater: Heater) {
670
+ super(ncp, heater);
671
+ // Set the polling interval to 3 seconds.
672
+ this.pollEquipmentAsync();
673
+ }
674
+ public async pollEquipmentAsync() {
675
+ let self = this;
676
+ try {
677
+ this.suspendPolling = true;
678
+ if (typeof this._pollTimer !== 'undefined' || this._pollTimer) clearTimeout(this._pollTimer);
679
+ this._pollTimer = null;
680
+ if (this._suspendPolling > 1) return;
681
+ let sheater = state.heaters.getItemById(this.heater.id, !this.closing);
682
+ // If the body isn't on then we won't communicate with the chem controller. There is no need
683
+ // since most of the time these are attached to the filter relay.
684
+ if (!this.closing) {
685
+ await this.setStatus(sheater);
686
+ }
687
+ }
688
+ catch (err) { logger.error(`Error polling UltraTemp ETi heater - ${err}`); }
689
+ finally {
690
+ this.suspendPolling = false; if (!this.closing) this._pollTimer = setTimeout(async () => {
691
+ try { await self.pollEquipmentAsync() } catch (err) { }
692
+ }, this.pollingInterval || 10000);
693
+ }
694
+ }
695
+ public async setHeaterStateAsync(hstate: HeaterState, isOn: boolean, isCooling: boolean) {
696
+ try {
697
+ // Initialize the desired state.
698
+ this.isCooling = isCooling;
699
+ if (hstate.isOn !== isOn) {
700
+ logger.info(`Nixie: Set Heater ${hstate.id}-${hstate.name} to ${isCooling ? 'cooling' : isOn ? 'heating' : 'off'}`);
701
+
702
+ }
703
+ if (isOn && !hstate.startupDelay) this.lastHeatCycle = new Date();
704
+ this.isOn = hstate.isOn = isOn;
705
+ } catch (err) { return logger.error(`Nixie Error setting heater state ${hstate.id}-${hstate.name}: ${err.message}`); }
706
+ }
707
+ public async releaseHeater(sheater: HeaterState): Promise<boolean> {
708
+ try {
709
+ let out = Outbound.create({
710
+ portId: this.heater.portId || 0,
711
+ protocol: Protocol.Heater,
712
+ source: 16,
713
+ dest: this.heater.address,
714
+ action: 112,
715
+ payload: [],
716
+ retries: 3, // We are going to try 4 times.
717
+ response: Response.create({ protocol: Protocol.Heater, action: 113 }),
718
+ onAbort: () => { }
719
+ });
720
+ out.appendPayloadBytes(0, 10);
721
+ out.setPayloadByte(0, 0);
722
+ out.setPayloadByte(1, 0);
723
+ out.setPayloadByte(2, 78);
724
+ out.setPayloadByte(3, 1);
725
+ out.setPayloadByte(4, 5);
726
+ let success = await out.sendAsync();
727
+ return success;
728
+ } catch (err) {
729
+ // If the Ultratemp is not responding we need to store that off but at this point we know none of the codes. If a 113 does
730
+ // come across this will be cleared by the processing of that message.
731
+ sheater.commStatus = sys.board.valueMaps.equipmentCommStatus.getValue('commerr');
732
+ state.equipment.messages.setMessageByCode(`heater:${sheater.id}:comms`, 'error', `Communication error with ${sheater.name}`);
733
+ logger.error(`Communication error with Ultratemp : ${err.message}`);
734
+ return false;
735
+ }
736
+ }
737
+ protected calcHeatModeByte(body: Body): number {
738
+ let byte = 0;
739
+ if (this.closing) return 0; // We are closing so just set the heat mode to off.
740
+ let mode = sys.board.valueMaps.heatModes.transform(body.heatMode || 0);
741
+ switch (mode.name) {
742
+ case 'hpump':
743
+ case 'heatpump':
744
+ byte = 1;
745
+ break;
746
+ case 'heater':
747
+ byte = 2;
748
+ break;
749
+ case 'heatpumpref':
750
+ case 'heatpumppref':
751
+ case 'hybrid':
752
+ byte = 3;
753
+ break;
754
+ case 'dual':
755
+ byte = 4;
756
+ break;
757
+ }
758
+ return byte;
759
+ }
760
+ public async setServiceModeAsync() {
761
+ let hstate = state.heaters.getItemById(this.heater.id);
762
+ await this.setHeaterStateAsync(hstate, false, false);
763
+ await this.releaseHeater(hstate);
764
+ }
765
+ public async setStatus(sheater: HeaterState): Promise<boolean> {
766
+ try {
767
+ let out = Outbound.create({
768
+ portId: this.heater.portId || 0,
769
+ protocol: Protocol.Heater,
770
+ source: 16,
771
+ dest: this.heater.address,
772
+ action: 112,
773
+ payload: [],
774
+ retries: 3, // We are going to try 4 times.
775
+ response: Response.create({ protocol: Protocol.Heater, action: 113 }),
776
+ onAbort: () => { }
777
+ });
778
+ out.appendPayloadBytes(0, 10);
779
+ out.setPayloadByte(0, this.isOn && !sheater.startupDelay && !this.closing ? 1 : 0);
780
+ if (sheater.bodyId > 0) {
781
+ let body = sys.bodies.getItemById(sheater.bodyId);
782
+ out.setPayloadByte(1, this.calcHeatModeByte(body));
783
+ out.setPayloadByte(2, body.setPoint);
784
+ }
785
+ else out.setPayloadByte(2, utils.convert.temperature.convertUnits(78, 'F', sys.board.valueMaps.tempUnits.getName(state.temps.units) || 'F')); // Just set it to a valid setpoint and call it a day.
786
+ out.setPayloadByte(3, this.heater.economyTime, 1);
787
+ out.setPayloadByte(4, this.heater.maxBoostTemp, 5);
788
+ if (this.isOn) {
789
+ if (!this.isCooling) this.lastHeatCycle = new Date();
790
+ else this.lastCoolCycle = new Date();
791
+ }
792
+ let success = await out.sendAsync();
793
+ return success;
794
+ } catch (err) {
795
+ // If the Ultratemp ETi is not responding we need to store that off but at this point we know none of the codes. If a 113 does
796
+ // come across this will be cleared by the processing of that message.
797
+ sheater.commStatus = sys.board.valueMaps.equipmentCommStatus.getValue('commerr');
798
+ state.equipment.messages.setMessageByCode(`heater:${sheater.id}:comms`, 'error', `Communication error with ${sheater.name}`);
799
+ logger.error(`Communication error with Ultratemp ETi : ${err.message}`);
800
+ return false;
801
+ }
802
+ }
803
+ public async closeAsync() {
804
+ try {
805
+ this.suspendPolling = true;
806
+ this.closing = true;
807
+ if (typeof this._pollTimer !== 'undefined' || this._pollTimer) clearTimeout(this._pollTimer);
808
+ this._pollTimer = null;
809
+ let sheater = state.heaters.getItemById(this.id);
810
+ await this.releaseHeater(sheater);
811
+ logger.info(`Closing Heater ${this.heater.name}`);
812
+ }
813
+ catch (err) { logger.error(`Ultratemp closeAsync: ${err.message}`); return Promise.reject(err); }
814
+ }
815
+ }